From 3ad34f875986ba0ec6750081e0e6eeb3d290058f Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 24 Feb 2024 02:45:23 +1300 Subject: [PATCH 0001/1377] E-Ink: change inaccurate terminology (#3269) Follows a discussion with @markbirss on discord --- src/graphics/EInkDisplay2.cpp | 76 ++++++++++----------- src/graphics/EInkDisplay2.h | 40 +++++------ variants/heltec_wireless_paper_v1/variant.h | 8 +-- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index d5e3f5263..de53daaee 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -18,7 +18,7 @@ SPIClass *hspi = NULL; #define TECHO_DISPLAY_MODEL GxEPD2_154_D67 #elif defined(RAK4630) -// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give partial update +// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give fast refresh // support #define TECHO_DISPLAY_MODEL GxEPD2_213_BN @@ -131,8 +131,8 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) // No need to grab this lock because we are on our own SPI bus // concurrency::LockGuard g(spiLock); -#if defined(USE_EINK_DYNAMIC_PARTIAL) - // Decide if update is partial or full +#if defined(USE_EINK_DYNAMIC_REFRESH) + // Decide between full refresh, fast refresh, or skipping the update bool continueUpdate = determineRefreshMode(); if (!continueUpdate) return false; @@ -165,12 +165,12 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) adafruitDisplay->nextPage(); #elif defined(RAK4630) || defined(MAKERPYTHON) - // RAK14000 2.13 inch b/w 250x122 actually now does support partial updates + // RAK14000 2.13 inch b/w 250x122 actually now does support fast refresh // Full update mode (slow) - // adafruitDisplay->display(false); // FIXME, use partial update mode + // adafruitDisplay->display(false); // FIXME, use fast refresh mode - // Only enable for e-Paper with support for partial updates and comment out above adafruitDisplay->display(false); + // Only enable for e-Paper with support for fast updates and comment out above adafruitDisplay->display(false); // 1.54 inch 200x200 - GxEPD2_154_M09 // 2.13 inch 250x122 - GxEPD2_213_BN // 2.9 inch 296x128 - GxEPD2_290_T5D @@ -203,7 +203,7 @@ void EInkDisplay::display(void) // at least one forceDisplay() keyframe. This prevents flashing when we should the critical // bootscreen (that we want to look nice) -#ifdef USE_EINK_DYNAMIC_PARTIAL +#ifdef USE_EINK_DYNAMIC_REFRESH lowPriority(); forceDisplay(); highPriority(); @@ -257,9 +257,9 @@ bool EInkDisplay::connect() auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); - // RAK14000 2.13 inch b/w 250x122 does actually now support partial updates + // RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh adafruitDisplay->setRotation(3); - // Partial update support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2 + // Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2 // adafruitDisplay->setRotation(1); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); } else { @@ -347,10 +347,10 @@ bool EInkDisplay::connect() return true; } -// Use a mix of full and partial refreshes, to preserve display health -#if defined(USE_EINK_DYNAMIC_PARTIAL) +// Use a mix of full refresh, fast refresh, and update skipping, to balance urgency and display health +#if defined(USE_EINK_DYNAMIC_REFRESH) -// Suggest that subsequent updates should use partial-refresh +// Suggest that subsequent updates should use fast-refresh void EInkDisplay::highPriority() { isHighPriority = true; @@ -368,8 +368,8 @@ void EInkDisplay::demandFullRefresh() demandingFull = true; } -// configure display for partial-refresh -void EInkDisplay::configForPartialRefresh() +// configure display for fast-refresh +void EInkDisplay::configForFastRefresh() { // Display-specific code can go here #if defined(PRIVATE_HW) @@ -390,7 +390,7 @@ void EInkDisplay::configForFullRefresh() #endif } -#ifdef EINK_PARTIAL_ERASURE_LIMIT +#ifdef EINK_FASTREFRESH_ERASURE_LIMIT // Count black pixels in an image. Used for "erasure tracking" int32_t EInkDisplay::countBlackPixels() { @@ -422,16 +422,16 @@ bool EInkDisplay::tooManyErasures() prevBlackCount = blackCount; // Log the running total - help devs setup new boards - LOG_DEBUG("Dynamic Partial: erasedSinceFull=%hu, EINK_PARTIAL_ERASURE_LIMIT=%hu\n", erasedSinceFull, - EINK_PARTIAL_ERASURE_LIMIT); + LOG_DEBUG("Dynamic Refresh: erasedSinceFull=%hu, EINK_FASTREFRESH_ERASURE_LIMIT=%hu\n", erasedSinceFull, + EINK_FASTREFRESH_ERASURE_LIMIT); // Check if too many pixels have been erased - if (erasedSinceFull > EINK_PARTIAL_ERASURE_LIMIT) + if (erasedSinceFull > EINK_FASTREFRESH_ERASURE_LIMIT) return true; // Too many else return false; // Still okay } -#endif // ifdef EINK_PARTIAL_BRIGHTEN_LIMIT_PX +#endif // ifdef EINK_FASTREFRESH_ERASURE_LIMIT bool EInkDisplay::newImageMatchesOld() { @@ -452,7 +452,7 @@ bool EInkDisplay::newImageMatchesOld() return hashMatches; } -// Change between partial and full refresh config, or skip update, balancing urgency and display health. +// Choose between, full-refresh, fast refresh, and update skipping, to balance urgency and display health. bool EInkDisplay::determineRefreshMode() { uint32_t now = millis(); @@ -466,8 +466,8 @@ bool EInkDisplay::determineRefreshMode() } // Abort: if too soon for a new frame (unless demanding full) - if (!demandingFull && isHighPriority && partialRefreshCount > 0 && sinceLast < highPriorityLimitMsec) { - LOG_DEBUG("Dynamic Partial: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n"); + if (!demandingFull && isHighPriority && fastRefreshCount > 0 && sinceLast < highPriorityLimitMsec) { + LOG_DEBUG("Dynamic Refresh: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n"); missedHighPriorityUpdate = true; return false; } @@ -479,16 +479,16 @@ bool EInkDisplay::determineRefreshMode() if (demandingFull) needsFull = true; - // Check if old image (partial) should be redrawn (as full), for image quality - if (partialRefreshCount > 0 && !isHighPriority) + // Check if old image (fast-refresh) should be redrawn (as full), for image quality + if (fastRefreshCount > 0 && !isHighPriority) needsFull = true; - // If too many partials, require a full-refresh (display health) - if (partialRefreshCount >= partialRefreshLimit) + // If too many fast updates, require a full-refresh (display health) + if (fastRefreshCount >= fastRefreshLimit) needsFull = true; -#ifdef EINK_PARTIAL_ERASURE_LIMIT - // Some displays struggle with erasing black pixels to white, during partial refresh +#ifdef EINK_FASTREFRESH_ERASURE_LIMIT + // Some displays struggle with erasing black pixels to white, during fast-refresh if (tooManyErasures()) needsFull = true; #endif @@ -510,29 +510,29 @@ bool EInkDisplay::determineRefreshMode() // If options require a full refresh if (!isHighPriority || needsFull) { - if (partialRefreshCount > 0) + if (fastRefreshCount > 0) configForFullRefresh(); - LOG_DEBUG("Dynamic Partial: conditions met for full-refresh\n"); - partialRefreshCount = 0; + LOG_DEBUG("Dynamic Refresh: conditions met for full-refresh\n"); + fastRefreshCount = 0; needsFull = false; demandingFull = false; - erasedSinceFull = 0; // Reset the count for EINK_PARTIAL_ERASURE_LIMIT - tracks ghosting buildup + erasedSinceFull = 0; // Reset the count for EINK_FASTREFRESH_ERASURE_LIMIT - tracks ghosting buildup } - // If options allow a partial refresh + // If options allow a fast-refresh else { - if (partialRefreshCount == 0) - configForPartialRefresh(); + if (fastRefreshCount == 0) + configForFastRefresh(); - LOG_DEBUG("Dynamic Partial: conditions met for partial-refresh\n"); - partialRefreshCount++; + LOG_DEBUG("Dynamic Refresh: conditions met for fast-refresh\n"); + fastRefreshCount++; } lastUpdateMsec = now; // Mark time for rate limiting return true; // Instruct calling method to continue with update } -#endif // End USE_EINK_DYNAMIC_PARTIAL +#endif // End USE_EINK_DYNAMIC_REFRESH #endif diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index aeaddee2d..260a79755 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -55,23 +55,23 @@ class EInkDisplay : public OLEDDisplay // Connect to the display virtual bool connect() override; -#if defined(USE_EINK_DYNAMIC_PARTIAL) - // Full, partial, or skip: balance urgency with display health +#if defined(USE_EINK_DYNAMIC_REFRESH) + // Full, fast, or skip: balance urgency with display health - // Use partial refresh if EITHER: + // Use fast refresh if EITHER: // * highPriority() was set // * a highPriority() update was previously skipped, for rate-limiting - (EINK_HIGHPRIORITY_LIMIT_SECONDS) // Use full refresh if EITHER: // * lowPriority() was set // * demandFullRefresh() was called - (single shot) - // * too many partial updates in a row: protect display - (EINK_PARTIAL_REPEAT_LIMIT) - // * no recent updates, and last update was partial: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS) + // * too many fast updates in a row: protect display - (EINK_FASTREFRESH_REPEAT_LIMIT) + // * no recent updates, and last update was fast: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS) // * (optional) too many "erasures" since full-refresh (black pixels cleared to white) // Rate limit if: // * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS) - // * highPriority(), if multiple partials have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS) + // * highPriority(), if multiple fast updates have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS) // Skip update entirely if ALL criteria met: // * new image matches old image @@ -83,29 +83,29 @@ class EInkDisplay : public OLEDDisplay // ------------------------------------ // To implement for your E-Ink display: - // * edit configForPartialRefresh() + // * edit configForFastRefresh() // * edit configForFullRefresh() // * add macros to variant.h, and adjust to taste: /* - #define USE_EINK_DYNAMIC_PARTIAL + #define USE_EINK_DYNAMIC_REFRESH #define EINK_LOWPRIORITY_LIMIT_SECONDS 30 #define EINK_HIGHPRIORITY_LIMIT_SECONDS 1 - #define EINK_PARTIAL_REPEAT_LIMIT 5 - #define EINK_PARTIAL_ERASURE_LIMIT 300 // optional + #define EINK_FASTREFRESH_REPEAT_LIMIT 5 + #define EINK_FASTREFRESH_ERASURE_LIMIT 300 // optional */ public: - void highPriority(); // Suggest partial refresh + void highPriority(); // Suggest fast refresh void lowPriority(); // Suggest full refresh void demandFullRefresh(); // For next update: explicitly request full refresh protected: - void configForPartialRefresh(); // Display specific code to select partial refresh mode - void configForFullRefresh(); // Display specific code to return to full refresh mode - bool newImageMatchesOld(); // Is the new update actually different to the last image? - bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update -#ifdef EINK_PARTIAL_ERASURE_LIMIT + void configForFastRefresh(); // Display specific code to select fast refresh mode + void configForFullRefresh(); // Display specific code to return to full refresh mode + bool newImageMatchesOld(); // Is the new update actually different to the last image? + bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update +#ifdef EINK_FASTREFRESH_ERASURE_LIMIT int32_t countBlackPixels(); // Calculate the number of black pixels in the new image bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh? #endif @@ -114,18 +114,18 @@ class EInkDisplay : public OLEDDisplay bool needsFull = false; // Is a full refresh forced? (display health) bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc) bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting? - uint16_t partialRefreshCount = 0; // How many partials have occurred since last full refresh? + uint16_t fastRefreshCount = 0; // How many fast updates have occurred since last full refresh? uint32_t lastUpdateMsec = 0; // When did the last update occur? uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not) int32_t prevBlackCount = 0; // How many black pixels were in the previous image uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly) // Set in variant.h - const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for partial refreshes + const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for fast refreshes const uint32_t highPriorityLimitMsec = (uint32_t)1000 * EINK_HIGHPRIORITY_LIMIT_SECONDS; // Max rate for full refreshes - const uint32_t partialRefreshLimit = EINK_PARTIAL_REPEAT_LIMIT; // Max consecutive partials, before full is triggered + const uint32_t fastRefreshLimit = EINK_FASTREFRESH_REPEAT_LIMIT; // Max consecutive fast updates, before full is triggered -#else // !USE_EINK_DYNAMIC_PARTIAL +#else // !USE_EINK_DYNAMIC_REFRESH // Tolerate calls to these methods anywhere, just to be safe void highPriority() {} void lowPriority() {} diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 166b7f30e..25e061938 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -6,12 +6,12 @@ #define USE_EINK -// Settings for Dynamic Partial mode -// Change between partial and full refresh config, or skip update, balancing urgency and display health. -#define USE_EINK_DYNAMIC_PARTIAL +// Settings for Dynamic Refresh mode +// Change between full-refresh, fast-refresh, or update-skipping, to balance urgency and display health. +#define USE_EINK_DYNAMIC_REFRESH #define EINK_LOWPRIORITY_LIMIT_SECONDS 30 #define EINK_HIGHPRIORITY_LIMIT_SECONDS 1 -#define EINK_PARTIAL_REPEAT_LIMIT 5 +#define EINK_FASTREFRESH_REPEAT_LIMIT 5 /* * eink display pins From b2ea1e23be49ddc63b7032531572c656d559bcb0 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Feb 2024 15:39:32 -0600 Subject: [PATCH 0002/1377] Move imprecise locations to grid middle --- src/modules/PositionModule.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 7ef539b1e..d90df89e9 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -83,6 +83,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } nodeDB.updatePosition(getFrom(&mp), p); + precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; return false; // Let others look at this message also if they want } @@ -113,6 +114,12 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_DEBUG("Sending location with precision %i\n", precision); p.latitude_i = localPosition.latitude_i & (INT32_MAX << (32 - precision)); p.longitude_i = localPosition.longitude_i & (INT32_MAX << (32 - precision)); + + // We want the imprecise position to be the middle of the possible location, not + if (precision < 31 && precision > 1) { + p.latitude_i += (1 << 31 - precision); + p.longitude_i += (1 << 31 - precision); + } p.precision_bits = precision; p.time = localPosition.time; From f1b314251c3e04b49d5bc18af5f428aea39723ac Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Sat, 24 Feb 2024 06:49:15 -0700 Subject: [PATCH 0003/1377] remove flasher and replace with web on unset screen (#3272) --- src/graphics/Screen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index c0e55ea83..5197e4dbc 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -274,7 +274,7 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i if ((millis() / 10000) % 2) { display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Flasher or CLI client."); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); } else { display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); From 730429fc9b95adc2e33a48a4ee0c5e95b24bac09 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 24 Feb 2024 07:55:00 -0600 Subject: [PATCH 0004/1377] Routers / Repeaters deep sleep default w/ LoRA interrupts (#3251) * Experimenting with deep sleep routers / repeaters * Make decision to SDS or LS based on Router/Repeater role * Don't sleep LoRA on router / repeater deep sleep * Guards * Platform guards * Rename method --- src/PowerFSM.cpp | 5 +++-- src/sleep.cpp | 46 ++++++++++++++++++++++++++++++++++++++++------ src/sleep.h | 6 +++++- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index bac3899bb..c359e4c12 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -245,6 +245,7 @@ Fsm powerFSM(&stateBOOT); void PowerFSM_setup() { bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); + bool isInfrastructureRole = isRouter || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER; bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; @@ -357,10 +358,10 @@ void PowerFSM_setup() // Don't add power saving transitions if we are a power saving tracker or sensor. Sleep will be initiatiated through the // modules if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) { - powerFSM.add_timed_transition(&stateNB, &stateLS, + powerFSM.add_timed_transition(&stateNB, isInfrastructureRole ? &stateSDS : &stateLS, getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, "Min wake timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateLS, + powerFSM.add_timed_transition(&stateDARK, isInfrastructureRole ? &stateSDS : &stateLS, getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, "Bluetooth timeout"); } diff --git a/src/sleep.cpp b/src/sleep.cpp index 0f71ab25b..1afba1173 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -186,7 +186,15 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(skipPreflight); +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + notifySleep.notifyObservers(NULL); + } else { + notifyDeepSleep.notifyObservers(NULL); + } +#else notifyDeepSleep.notifyObservers(NULL); +#endif screen->doDeepSleep(); // datasheet says this will draw only 10ua @@ -240,6 +248,11 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) } #endif +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + enableLoraInterrupt(); + } +#endif cpuDeepSleep(msecToWake); } @@ -294,12 +307,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); #endif #endif -#if defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high -#endif -#ifdef RF95_IRQ - gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high -#endif + enableLoraInterrupt(); #ifdef PMU_IRQ // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills if (pmu_found) @@ -359,4 +367,30 @@ void enableModemSleep() int rv = esp_pm_configure(&esp32_config); LOG_DEBUG("Sleep request result %x\n", rv); } + +bool shouldLoraWake(uint32_t msecToWake) +{ + return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER); +} + +void enableLoraInterrupt() +{ +#if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + rtc_gpio_pulldown_en((gpio_num_t)LORA_DIO1); +#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) + rtc_gpio_pullup_en((gpio_num_t)LORA_RESET); +#endif +#if defined(LORA_CS) && (LORA_CS != RADIOLIB_NC) + rtc_gpio_pullup_en((gpio_num_t)LORA_CS); +#endif + // Setup deep sleep with wakeup by external source + esp_sleep_enable_ext0_wakeup((gpio_num_t)LORA_DIO1, RISING); +#elif defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high +#endif +#ifdef RF95_IRQ + gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high +#endif +} #endif \ No newline at end of file diff --git a/src/sleep.h b/src/sleep.h index a592ad2c1..8d5b9a94f 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -43,4 +43,8 @@ extern Observable notifyDeepSleep; /// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 extern Observable notifyGPSSleep; -void enableModemSleep(); \ No newline at end of file +void enableModemSleep(); +#ifdef ARCH_ESP32 +void enableLoraInterrupt(); +bool shouldLoraWake(uint32_t msecToWake); +#endif \ No newline at end of file From c2085c2c888b361b9f7bc15f0107a9ce3425e9f7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 24 Feb 2024 12:53:46 -0600 Subject: [PATCH 0005/1377] Fix default stance in position_precision --- src/mesh/Channels.cpp | 1 + src/modules/PositionModule.cpp | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index fe1041d3d..2d27c737d 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -88,6 +88,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) channelSettings.psk.size = 1; strncpy(channelSettings.name, "", sizeof(channelSettings.name)); channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel + channelSettings.has_module_settings = true; ch.has_settings = true; ch.role = meshtastic_Channel_Role_PRIMARY; diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index d90df89e9..5f20dbafb 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -83,7 +83,13 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } nodeDB.updatePosition(getFrom(&mp), p); - precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; + if (channels.getByIndex(mp.channel).settings.has_module_settings) { + precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; + } else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { + precision = 32; + } else { + precision = 0; + } return false; // Let others look at this message also if they want } @@ -117,8 +123,8 @@ meshtastic_MeshPacket *PositionModule::allocReply() // We want the imprecise position to be the middle of the possible location, not if (precision < 31 && precision > 1) { - p.latitude_i += (1 << 31 - precision); - p.longitude_i += (1 << 31 - precision); + p.latitude_i += (1 << (31 - precision)); + p.longitude_i += (1 << (31 - precision)); } p.precision_bits = precision; p.time = localPosition.time; @@ -217,7 +223,13 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha service.cancelSending(prevPacketId); // Set's the class precision value for this particular packet - precision = channels.getByIndex(channel).settings.module_settings.position_precision; + if (channels.getByIndex(channel).settings.has_module_settings) { + precision = channels.getByIndex(channel).settings.module_settings.position_precision; + } else if (channels.getByIndex(channel).role == meshtastic_Channel_Role_PRIMARY) { + precision = 32; + } else { + precision = 0; + } meshtastic_MeshPacket *p = allocReply(); if (p == nullptr) { From 9c4d1b5ac864f694323c37235194728891e5e55c Mon Sep 17 00:00:00 2001 From: caveman99 Date: Sat, 24 Feb 2024 20:08:49 +0000 Subject: [PATCH 0006/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/protobufs b/protobufs index 24edea644..524158356 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 24edea64429de4474c00d09990ef4c496614dc5d +Subproject commit 5241583565ccbbb4986180bf4c6eb7f8a0dec285 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index e8a27d43f..6ffd93294 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -75,6 +75,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_CANARYONE = 29, /* Waveshare RP2040 LoRa - https://www.waveshare.com/rp2040-lora.htm */ meshtastic_HardwareModel_RP2040_LORA = 30, + /* B&Q Consulting Station G2: https://wiki.uniteng.com/en/meshtastic/station-g2 */ + meshtastic_HardwareModel_STATION_G2 = 31, /* --------------------------------------------------------------------------- Less common/prototype boards listed here (needs one more byte over the air) --------------------------------------------------------------------------- */ @@ -530,8 +532,7 @@ typedef PB_BYTES_ARRAY_T(256) meshtastic_MeshPacket_encrypted_t; typedef struct _meshtastic_MeshPacket { /* The sending node number. Note: Our crypto implementation uses this field as well. - See [crypto](/docs/overview/encryption) for details. - FIXME - really should be fixed32 instead, this encoding only hurts the ble link though. */ + See [crypto](/docs/overview/encryption) for details. */ uint32_t from; /* The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only hurts the ble link though. */ @@ -558,9 +559,7 @@ typedef struct _meshtastic_MeshPacket { needs to be unique for a few minutes (long enough to last for the length of any ACK or the completion of a mesh broadcast flood). Note: Our crypto implementation uses this id as well. - See [crypto](/docs/overview/encryption) for details. - FIXME - really should be fixed32 instead, this encoding only - hurts the ble link though. */ + See [crypto](/docs/overview/encryption) for details. */ uint32_t id; /* The time this message was received by the esp32 (secs since 1970). Note: this field is _never_ sent on the radio link itself (to save space) Times From 74714bf0c58d8dc898281835b52be9844d7301a2 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 24 Feb 2024 14:28:58 -0800 Subject: [PATCH 0007/1377] station-g2 --- .vscode/extensions.json | 9 +++--- boards/station-g2.json | 41 +++++++++++++++++++++++ src/platform/esp32/architecture.h | 4 ++- variants/station-g2/pins_arduino.h | 29 +++++++++++++++++ variants/station-g2/platformio.ini | 15 +++++++++ variants/station-g2/variant.h | 52 ++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100755 boards/station-g2.json create mode 100755 variants/station-g2/pins_arduino.h create mode 100755 variants/station-g2/platformio.ini create mode 100755 variants/station-g2/variant.h diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 783791f0b..080e70d08 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,9 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", - "platformio.platformio-ide", - "trunk.io" + "platformio.platformio-ide" ], -} \ No newline at end of file + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/boards/station-g2.json b/boards/station-g2.json new file mode 100755 index 000000000..871f067aa --- /dev/null +++ b/boards/station-g2.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "station-g2" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "BQ Station G2", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://wiki.uniteng.com/en/meshtastic/station-g2", + "vendor": "BQ Consulting" +} diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 7fab475f3..703bcefc9 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -133,6 +133,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_HT62 #elif defined(CHATTER_2) #define HW_VENDOR meshtastic_HardwareModel_CHATTER_2 +#elif defined(STATION_G2) +#define HW_VENDOR meshtastic_HardwareModel_STATION_G2 #endif // ----------------------------------------------------------------------------- @@ -155,4 +157,4 @@ #define LORA_CS 18 #endif -#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. +#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. \ No newline at end of file diff --git a/variants/station-g2/pins_arduino.h b/variants/station-g2/pins_arduino.h new file mode 100755 index 000000000..98cbd46d3 --- /dev/null +++ b/variants/station-g2/pins_arduino.h @@ -0,0 +1,29 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) <= 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +// GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600 + +// The default Wire will be mapped to Screen and Sensors +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 14; +static const uint8_t SCK = 12; +static const uint8_t MOSI = 13; +static const uint8_t SS = 11; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/station-g2/platformio.ini b/variants/station-g2/platformio.ini new file mode 100755 index 000000000..b39136684 --- /dev/null +++ b/variants/station-g2/platformio.ini @@ -0,0 +1,15 @@ +[env:station-g2] +extends = esp32s3_base +board = station-g2 +board_build.mcu = esp32s3 +upload_protocol = esptool +;upload_port = /dev/ttyACM0 +upload_speed = 921600 +lib_deps = + ${esp32s3_base.lib_deps} +build_unflags = -DARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} -D STATION_G2 -I variants/station-g2 + -DBOARD_HAS_PSRAM + -DSTATION_G2 + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/station-g2/variant.h b/variants/station-g2/variant.h new file mode 100755 index 000000000..6a24cd016 --- /dev/null +++ b/variants/station-g2/variant.h @@ -0,0 +1,52 @@ +/* +Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 +*/ + +// Station G2 may not have GPS installed, but it has a GROVE GPS Socket for Optional GPS Module +#define GPS_RX_PIN 7 +#define GPS_TX_PIN 15 + +// Station G2 has 1.3 inch OLED Screen +#define USE_SH1107_128_64 + +#define I2C_SDA 5 // I2C pins for this board +#define I2C_SCL 6 + +#define BUTTON_PIN 38 // This is the Program Button +#define BUTTON_NEED_PULLUP + +#define USE_SX1262 + +#define LORA_MISO 14 +#define LORA_SCK 12 +#define LORA_MOSI 13 +#define LORA_CS 11 + +#define LORA_RESET 21 +#define LORA_DIO1 48 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 47 +#define SX126X_RESET LORA_RESET + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define SX126X_MAX_POWER \ + 19 // Ensure the PA does not exceed the saturation output power. More + // Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test +#endif + +#define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_MULTIPLIER 4 +#define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. +#define BAT_FULLVOLT 8400 +#define BAT_EMPTYVOLT 5000 +#define BAT_CHARGINGVOLT 8400 +#define BAT_NOBATVOLT 4460 +#define CELL_TYPE_LION // same curve for liion/lipo +#define NUM_CELLS 2 \ No newline at end of file From 1fe230a0653556c849e21ec6630e6ee24df51db4 Mon Sep 17 00:00:00 2001 From: Neil Hao Date: Sun, 25 Feb 2024 08:18:30 +0800 Subject: [PATCH 0008/1377] Undo VS automatic modifications to this file --- .vscode/extensions.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 080e70d08..4fc84fa78 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,9 +2,8 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "platformio.platformio-ide" + "ms-vscode.cpptools", + "platformio.platformio-ide", + "trunk.io" ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] } From 8c7ee1a7bb6f375b817638c884851713aa3e8ff9 Mon Sep 17 00:00:00 2001 From: Neil Hao Date: Sun, 25 Feb 2024 18:32:46 +0800 Subject: [PATCH 0009/1377] Corrected the Trunk Problem --- variants/station-g2/variant.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/variants/station-g2/variant.h b/variants/station-g2/variant.h index 6a24cd016..b1d6914bb 100755 --- a/variants/station-g2/variant.h +++ b/variants/station-g2/variant.h @@ -35,9 +35,8 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#define SX126X_MAX_POWER \ - 19 // Ensure the PA does not exceed the saturation output power. More - // Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test +// Ensure the PA does not exceed the saturation output power. More Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test +#define SX126X_MAX_POWER 19 #endif #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage @@ -49,4 +48,4 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 #define BAT_CHARGINGVOLT 8400 #define BAT_NOBATVOLT 4460 #define CELL_TYPE_LION // same curve for liion/lipo -#define NUM_CELLS 2 \ No newline at end of file +#define NUM_CELLS 2 From 8726cb830ed6e5c3286d5979545f6bd5a3ac4b05 Mon Sep 17 00:00:00 2001 From: Neil Hao Date: Sun, 25 Feb 2024 18:44:43 +0800 Subject: [PATCH 0010/1377] Trunk don't like long line:) --- variants/station-g2/variant.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/station-g2/variant.h b/variants/station-g2/variant.h index b1d6914bb..f781ceb24 100755 --- a/variants/station-g2/variant.h +++ b/variants/station-g2/variant.h @@ -35,7 +35,8 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -// Ensure the PA does not exceed the saturation output power. More Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test +// Ensure the PA does not exceed the saturation output power. More +// Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test #define SX126X_MAX_POWER 19 #endif From 6932f073100092211f8a1d8a4d29efab1a2777ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 25 Feb 2024 12:11:11 +0100 Subject: [PATCH 0011/1377] Add Station G2 to the build matrix --- .github/workflows/main_matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 5c1cf4c21..1e3e07106 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -32,7 +32,7 @@ jobs: - board: meshtastic-diy-v1 - board: rak4631 - board: t-echo - - board: station-g1 + - board: station-g2 - board: m5stack-coreink - board: tbeam-s3-core - board: tlora-t3s3-v1 @@ -99,6 +99,7 @@ jobs: - board: t-watch-s3 - board: t-deck - board: picomputer-s3 + - board: station-g2 uses: ./.github/workflows/build_esp32_s3.yml with: board: ${{ matrix.board }} From b98ddbddf413084ce3258cafb482d90767398853 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Sat, 24 Feb 2024 08:19:31 -0700 Subject: [PATCH 0012/1377] update node db lite log message --- src/mesh/NodeDB.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index add1b1296..506adda5c 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -903,8 +903,8 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) if (!lite) { if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { if (screen) - screen->print("warning: node_db_lite full! erasing oldest entry\n"); - LOG_INFO("warning: node_db_lite full! erasing oldest entry\n"); + screen->print("Warn: node database full!\nErasing oldest entry\n"); + LOG_INFO("Warn: node database full!\nErasing oldest entry\n"); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; From 02192e1163d1145974b4004db72dd86b4f9f30df Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:29:34 +0100 Subject: [PATCH 0013/1377] More StoreForward updates (#3274) * More StoreForward updates * Disable heartbeat again if not in config --------- Co-authored-by: Ben Meadors --- src/modules/esp32/StoreForwardModule.cpp | 78 ++++++++++++++---------- src/modules/esp32/StoreForwardModule.h | 2 +- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index cb1552521..70d13afca 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -45,7 +45,8 @@ int32_t StoreForwardModule::runOnce() this->busy = false; } } - } else if ((millis() - lastHeartbeat > (heartbeatInterval * 1000)) && airTime->isTxAllowedChannelUtil(true)) { + } else if (this->heartbeat && (millis() - lastHeartbeat > (heartbeatInterval * 1000)) && + airTime->isTxAllowedChannelUtil(true)) { lastHeartbeat = millis(); LOG_INFO("*** Sending heartbeat\n"); meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; @@ -141,25 +142,31 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to, uin LOG_DEBUG("SF historyQueueCreate - millis %d\n", millis()); LOG_DEBUG("SF historyQueueCreate - math %d\n", (millis() - msAgo)); */ - if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { - /* Copy the messages that were received by the router in the last msAgo - to the packetHistoryTXQueue structure. - Client not interested in packets from itself and only in broadcast packets or packets towards it. */ - if (this->packetHistory[i].from != to && - (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) { - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = this->packetHistory[i].payload_size; - memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, - meshtastic_Constants_DATA_PAYLOAD_LEN); - this->packetHistoryTXQueue_size++; - *last_request_index = i + 1; // Set to one higher such that we don't send the same message again + if (this->packetHistoryTXQueue_size < this->historyReturnMax) { + if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { + /* Copy the messages that were received by the router in the last msAgo + to the packetHistoryTXQueue structure. + Client not interested in packets from itself and only in broadcast packets or packets towards it. */ + if (this->packetHistory[i].from != to && + (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) { + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = + this->packetHistory[i].payload_size; + memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, + meshtastic_Constants_DATA_PAYLOAD_LEN); + this->packetHistoryTXQueue_size++; + *last_request_index = i + 1; // Set to one higher such that we don't send the same message again - LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time, - this->packetHistory[i].payload); + LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time, + this->packetHistory[i].payload); + } } + } else { + LOG_WARN("*** S&F - Maximum history return reached.\n"); + return this->packetHistoryTXQueue_size; } } return this->packetHistoryTXQueue_size; @@ -174,20 +181,24 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) { const auto &p = mp.decoded; - if (this->packetHistoryCurrent < this->records) { - this->packetHistory[this->packetHistoryCurrent].time = millis(); - this->packetHistory[this->packetHistoryCurrent].to = mp.to; - this->packetHistory[this->packetHistoryCurrent].channel = mp.channel; - this->packetHistory[this->packetHistoryCurrent].from = mp.from; - this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size; - memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); - - this->packetHistoryCurrent++; - this->packetHistoryMax++; - } else { - // TODO: Overwrite the oldest message in the history buffer when it is full. - LOG_WARN("*** S&F - PSRAM Full. Packet is not added to the history.\n"); + if (this->packetHistoryCurrent == this->records) { + LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n"); + this->packetHistoryCurrent = 0; + this->packetHistoryMax = 0; + for (auto &i : lastRequest) { + i.second = 0; // Clear the last request index for each client device + } } + + this->packetHistory[this->packetHistoryCurrent].time = millis(); + this->packetHistory[this->packetHistoryCurrent].to = mp.to; + this->packetHistory[this->packetHistoryCurrent].channel = mp.channel; + this->packetHistory[this->packetHistoryCurrent].from = mp.from; + this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size; + memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); + + this->packetHistoryCurrent++; + this->packetHistoryMax++; } meshtastic_MeshPacket *StoreForwardModule::allocReply() @@ -313,7 +324,8 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; - if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) { + if (mp.to == nodeDB.getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && + (p.payload.bytes[2] == 0x00)) { LOG_DEBUG("*** Legacy Request to send\n"); // Send the last 60 minutes of messages. @@ -543,6 +555,8 @@ StoreForwardModule::StoreForwardModule() // send heartbeat advertising? if (moduleConfig.store_forward.heartbeat) this->heartbeat = moduleConfig.store_forward.heartbeat; + else + this->heartbeat = false; // Popupate PSRAM with our data structures. this->populatePSRAM(); diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h index cfa9945d5..0d2fb9fce 100644 --- a/src/modules/esp32/StoreForwardModule.h +++ b/src/modules/esp32/StoreForwardModule.h @@ -44,7 +44,7 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< StoreForwardModule(); unsigned long lastHeartbeat = 0; - uint32_t heartbeatInterval = default_broadcast_interval_secs; + uint32_t heartbeatInterval = 900; /** Update our local reference of when we last saw that node. From 824991c17804d2418405af85b6c734618b213b8c Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:43:44 +0100 Subject: [PATCH 0014/1377] Ignore MQTT by default if region has a duty cycle limit (#3277) Co-authored-by: Ben Meadors --- src/modules/AdminModule.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 94df601d8..abd7c2e54 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -332,6 +332,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { config.lora.tx_enabled = true; initRegion(); + if (myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit + } if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) { sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; From 0f27992c5a95c73e7a71876d2bd049d46ff2a1d7 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:44:08 +0100 Subject: [PATCH 0015/1377] Ignore JSON enabled setting on nRF52 platforms (#3286) Not supported, see #2804 Co-authored-by: Ben Meadors --- src/mqtt/MQTT.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 8c241a302..898607eca 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -351,11 +351,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } +#endif // ARCH_NRF52 } } #endif @@ -450,6 +452,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = this->meshPacketToJson(env->packet); @@ -460,6 +463,7 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } +#endif // ARCH_NRF52 mqttPool.release(env); } } @@ -504,6 +508,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = this->meshPacketToJson((meshtastic_MeshPacket *)&mp_decoded); @@ -514,7 +519,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topicJson.c_str(), jsonString.c_str(), false); } } - +#endif // ARCH_NRF52 } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { From d434117ffdded1e1dbb118264c546fafab097375 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Sun, 25 Feb 2024 20:03:54 +0100 Subject: [PATCH 0016/1377] add UI frame for PaxCounter module (#3285) * add UI frame to display PaxCounter module data * only acquire screen when paxcounter is active, i.e. enabled and wifi and ble are both off * sync font #define with other occurrences in code * protect screen specific code with #if HAS_SCREEN * limit upload_speed to 115200 for TLORA_V2_1_16 * fix failing trunk checks; sorry * Revert "limit upload_speed to 115200 for TLORA_V2_1_16" This reverts commit 4eb549c5e886508a4d39a7bfe689bc1976cbaa4b. --------- Co-authored-by: Ben Meadors --- src/modules/esp32/PaxcounterModule.cpp | 45 +++++++++++++++++++++++++- src/modules/esp32/PaxcounterModule.h | 5 +++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index f3df7ffdf..b3506891d 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -57,7 +57,7 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply() int32_t PaxcounterModule::runOnce() { - if (moduleConfig.paxcounter.enabled && !config.bluetooth.enabled && !config.network.wifi_enabled) { + if (isActive()) { if (firstTime) { firstTime = false; LOG_DEBUG( @@ -87,4 +87,47 @@ int32_t PaxcounterModule::runOnce() } } +#if HAS_SCREEN + +// TODO / FIXME: This code is copied from src/graphics/Screen.cpp +// It appears (in slightly variants) also in other modules like +// src/modules/Telemetry/PowerTelemetry.cpp, src/modules/Telemetry/EnvironmentTelemetry.cpp +// and src/modules/CannedMessageModule.cpp +// It probably should go to a common header file for consistency +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) +// The screen is bigger so use bigger fonts +#define FONT_SMALL ArialMT_Plain_16 // Height: 19 +#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#else +#ifdef OLED_RU +#define FONT_SMALL ArialMT_Plain_10_RU +#else +#ifdef OLED_UA +#define FONT_SMALL ArialMT_Plain_10_UA +#else +#define FONT_SMALL ArialMT_Plain_10 // Height: 13 +#endif +#endif +#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#endif + +void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + char buffer[50]; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(x + 0, y + 0, "PAX"); + + libpax_counter_count(&count_from_libpax); + + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_SMALL); + display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, "WiFi: %d\nBLE: %d\nuptime: %ds", + count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000); +} +#endif // HAS_SCREEN + #endif \ No newline at end of file diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h index 0aa9be68d..e72f87450 100644 --- a/src/modules/esp32/PaxcounterModule.h +++ b/src/modules/esp32/PaxcounterModule.h @@ -23,6 +23,11 @@ class PaxcounterModule : private concurrency::OSThread, public ProtobufModule Date: Sun, 25 Feb 2024 21:27:32 -0600 Subject: [PATCH 0017/1377] Fix wrong-side-of-globe when precision set to 32 --- src/modules/PositionModule.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 5f20dbafb..d8d52667c 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -118,13 +118,16 @@ meshtastic_MeshPacket *PositionModule::allocReply() // lat/lon are unconditionally included - IF AVAILABLE! LOG_DEBUG("Sending location with precision %i\n", precision); - p.latitude_i = localPosition.latitude_i & (INT32_MAX << (32 - precision)); - p.longitude_i = localPosition.longitude_i & (INT32_MAX << (32 - precision)); + if (precision < 32 && precision > 0) { + p.latitude_i = localPosition.latitude_i & (INT32_MAX << (32 - precision)); + p.longitude_i = localPosition.longitude_i & (INT32_MAX << (32 - precision)); - // We want the imprecise position to be the middle of the possible location, not - if (precision < 31 && precision > 1) { + // We want the imprecise position to be the middle of the possible location, not p.latitude_i += (1 << (31 - precision)); p.longitude_i += (1 << (31 - precision)); + } else { + p.latitude_i = localPosition.latitude_i; + p.longitude_i = localPosition.longitude_i; } p.precision_bits = precision; p.time = localPosition.time; From 146b5b557adc5653287d7cf02792aa4e59aab56f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Feb 2024 00:22:05 -0600 Subject: [PATCH 0018/1377] UINT32_MAX is not the same as INT32_MAX --- src/modules/PositionModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index d8d52667c..92e8f8a4d 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -119,8 +119,8 @@ meshtastic_MeshPacket *PositionModule::allocReply() // lat/lon are unconditionally included - IF AVAILABLE! LOG_DEBUG("Sending location with precision %i\n", precision); if (precision < 32 && precision > 0) { - p.latitude_i = localPosition.latitude_i & (INT32_MAX << (32 - precision)); - p.longitude_i = localPosition.longitude_i & (INT32_MAX << (32 - precision)); + p.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - precision)); + p.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - precision)); // We want the imprecise position to be the middle of the possible location, not p.latitude_i += (1 << (31 - precision)); From d556d59308faa5404f987eac0c6e1544cf55a3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 26 Feb 2024 09:42:12 +0100 Subject: [PATCH 0019/1377] fix compilation for Cyrillic fonts --- src/modules/esp32/PaxcounterModule.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index b3506891d..89bf8f072 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -89,6 +89,14 @@ int32_t PaxcounterModule::runOnce() #if HAS_SCREEN +#ifdef OLED_RU +#include "graphics/fonts/OLEDDisplayFontsRU.h" +#endif + +#ifdef OLED_UA +#include "graphics/fonts/OLEDDisplayFontsUA.h" +#endif + // TODO / FIXME: This code is copied from src/graphics/Screen.cpp // It appears (in slightly variants) also in other modules like // src/modules/Telemetry/PowerTelemetry.cpp, src/modules/Telemetry/EnvironmentTelemetry.cpp From 4796c8edc488b71571ad5c1eeb9905abde09cbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 26 Feb 2024 17:38:16 +0100 Subject: [PATCH 0020/1377] Update trunk to latest version (#3295) --- .trunk/.gitignore | 1 + .trunk/trunk.yaml | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.trunk/.gitignore b/.trunk/.gitignore index 1e2465290..15966d087 100644 --- a/.trunk/.gitignore +++ b/.trunk/.gitignore @@ -6,3 +6,4 @@ plugins user_trunk.yaml user.yaml +tmp diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 81a35f8f1..af7d3d21d 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,34 +1,36 @@ version: 0.1 cli: - version: 1.17.2 + version: 1.20.1 plugins: sources: - id: trunk - ref: v1.3.0 + ref: v1.4.3 uri: https://github.com/trunk-io/plugins lint: enabled: - - bandit@1.7.5 - - checkov@3.1.9 - - terrascan@1.18.5 - - trivy@0.47.0 + - trufflehog@3.68.2 + - yamllint@1.35.1 + - bandit@1.7.7 + - checkov@3.2.26 + - terrascan@1.18.11 + - trivy@0.49.1 #- trufflehog@3.63.2-rc0 - taplo@0.8.1 - - ruff@0.1.6 - - isort@5.12.0 - - markdownlint@0.37.0 + - ruff@0.2.2 + - isort@5.13.2 + - markdownlint@0.39.0 - oxipng@9.0.0 - - svgo@3.0.5 - - actionlint@1.6.26 - - flake8@6.1.0 + - svgo@3.2.0 + - actionlint@1.6.27 + - flake8@7.0.0 - hadolint@2.12.0 - shfmt@3.6.0 - shellcheck@0.9.0 - - black@23.9.1 + - black@24.2.0 - git-diff-check - - gitleaks@8.18.1 + - gitleaks@8.18.2 - clang-format@16.0.3 - - prettier@3.1.0 + - prettier@3.2.5 runtimes: enabled: - python@3.10.8 From 59bbd1ad00fafc18553cd01f4673fd9deac4af2e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 26 Feb 2024 19:32:58 -0600 Subject: [PATCH 0021/1377] Revert I2C changes --- variants/heltec_wireless_tracker_V1_0/pins_arduino.h | 4 ++-- variants/heltec_wireless_tracker_V1_0/platformio.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h index 5c0b529b0..42c5770a9 100644 --- a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -26,8 +26,8 @@ static const uint8_t LED_BUILTIN = 18; static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 45; -static const uint8_t SCL = 46; +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/heltec_wireless_tracker_V1_0/platformio.ini b/variants/heltec_wireless_tracker_V1_0/platformio.ini index 034360c3d..303e27dbf 100644 --- a/variants/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/heltec_wireless_tracker_V1_0/platformio.ini @@ -1,7 +1,7 @@ [env:heltec-wireless-tracker-V1-0] extends = esp32s3_base board = heltec_wireless_tracker -upload_protocol = esp-builtin +upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker_V1_0 From ce0e5c0ce72db7746c923a0ede1d8e84b92032dd Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 26 Feb 2024 19:49:43 -0600 Subject: [PATCH 0022/1377] SDA and SCL remap --- variants/heltec_wireless_tracker_V1_0/pins_arduino.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h index 42c5770a9..f72c7661a 100644 --- a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -26,8 +26,8 @@ static const uint8_t LED_BUILTIN = 18; static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; static const uint8_t SS = 8; static const uint8_t MOSI = 10; From e6a2c06346e37a2a9b03469ba6a60d3632ddb51d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 26 Feb 2024 20:24:36 -0600 Subject: [PATCH 0023/1377] Various position fixes (#3297) * Guard against no movement * Add newlines * Fix printfs --- src/gps/GPS.cpp | 6 ++---- src/gps/GeoCoord.cpp | 15 +++++++++------ src/gps/GeoCoord.h | 1 + src/modules/PositionModule.cpp | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 08ef116b2..3a3660308 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -319,7 +319,6 @@ bool GPS::setup() delay(250); _serial_gps->write("$CFGMSG,6,1,0\r\n"); delay(250); - } else if (gnssModel == GNSS_MODEL_UBLOX) { // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) // We need set it because by default it is GPS only, and we want to use GLONASS too @@ -458,7 +457,6 @@ bool GPS::setup() LOG_WARN("Unable to enable NMEA 4.10.\n"); } } - } else { if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode is only for Neo-6 msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_ECO); @@ -642,12 +640,12 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) #endif #ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones if (on) { - LOG_INFO("Waking GPS"); + LOG_INFO("Waking GPS\n"); pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, 1); return; } else { - LOG_INFO("GPS entering sleep"); + LOG_INFO("GPS entering sleep\n"); // notifyGPSSleep.notifyObservers(NULL); pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, 0); diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index 19a753c02..cb4e69ff2 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -376,14 +376,17 @@ void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double & } /// Ported from my old java code, returns distance in meters along the globe -/// surface (by magic?) +/// surface (by Haversine formula) float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b) { - double pk = (180 / 3.14169); - double a1 = lat_a / pk; - double a2 = lng_a / pk; - double b1 = lat_b / pk; - double b2 = lng_b / pk; + // Don't do math if the points are the same + if (lat_a == lat_b && lng_a == lng_b) + return 0.0; + + double a1 = lat_a / DEG_CONVERT; + double a2 = lng_a / DEG_CONVERT; + double b1 = lat_b / DEG_CONVERT; + double b2 = lng_b / DEG_CONVERT; double cos_b1 = cos(b1); double cos_a1 = cos(a1); double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2); diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index 06b11c3de..9f911ed93 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -11,6 +11,7 @@ #define PI 3.1415926535897932384626433832795 #define OLC_CODE_LEN 11 +#define DEG_CONVERT 180 / PI // Helper functions // Raises a number to an exponent, handling negative exponents. diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 92e8f8a4d..1e4ea5de1 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -372,7 +372,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic LOG_DEBUG("currentPosition.latitude_i=%i, currentPosition.longitude_i=%i\n", lastGpsLatitude, lastGpsLongitude); LOG_DEBUG("--------SMART POSITION-----------------------------------\n"); - LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%d, distanceThreshold=% u\n", + LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%f, distanceThreshold=%f\n", abs(distanceTraveledSinceLastSend) >= distanceTravelThreshold, abs(distanceTraveledSinceLastSend), distanceTravelThreshold); From f7758b4e4401640c55d320d60fffc30ecc0868da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 21:32:49 -0600 Subject: [PATCH 0024/1377] [create-pull-request] automated change (#3298) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index ed7e93154..14d1884fb 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 2 -build = 24 +build = 25 From 4ffb906fe8f45cb40e628ad0edbe146670d725ed Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Tue, 27 Feb 2024 19:49:46 +0100 Subject: [PATCH 0025/1377] move duplicate #define for screen to unified header file (#3302) --- src/graphics/Screen.cpp | 34 +----------------- src/graphics/ScreenFonts.h | 35 +++++++++++++++++++ src/modules/CannedMessageModule.cpp | 35 +------------------ .../Telemetry/EnvironmentTelemetry.cpp | 18 +--------- src/modules/Telemetry/PowerTelemetry.cpp | 18 +--------- src/modules/esp32/AudioModule.cpp | 35 +------------------ src/modules/esp32/PaxcounterModule.cpp | 35 ++----------------- 7 files changed, 42 insertions(+), 168 deletions(-) create mode 100644 src/graphics/ScreenFonts.h diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 5197e4dbc..129af226b 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -56,14 +56,6 @@ along with this program. If not, see . #include "platform/portduino/PortduinoGlue.h" #endif -#ifdef OLED_RU -#include "fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "fonts/OLEDDisplayFontsUA.h" -#endif - using namespace meshtastic; /** @todo remove */ namespace graphics @@ -111,31 +103,7 @@ static uint16_t displayWidth, displayHeight; #define SCREEN_WIDTH displayWidth #define SCREEN_HEIGHT displayHeight -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ - !defined(DISPLAY_FORCE_SMALL_FONTS) -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 // Height: 19 -#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 // Height: 13 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#include "graphics/ScreenFonts.h" #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h new file mode 100644 index 000000000..d858add2c --- /dev/null +++ b/src/graphics/ScreenFonts.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef OLED_RU +#include "graphics/fonts/OLEDDisplayFontsRU.h" +#endif + +#ifdef OLED_UA +#include "graphics/fonts/OLEDDisplayFontsUA.h" +#endif + +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) +// The screen is bigger so use bigger fonts +#define FONT_SMALL ArialMT_Plain_16 // Height: 19 +#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#else +#ifdef OLED_RU +#define FONT_SMALL ArialMT_Plain_10_RU +#else +#ifdef OLED_UA +#define FONT_SMALL ArialMT_Plain_10_UA +#else +#define FONT_SMALL ArialMT_Plain_10 // Height: 13 +#endif +#endif +#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#endif + +#define fontHeight(font) ((font)[1] + 1) // height is position 1 + +#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 3127b0986..b2b52d1ab 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -18,40 +18,7 @@ #define INPUTBROKER_MATRIX_TYPE 0 #endif -#ifdef OLED_RU -#include "graphics/fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "graphics/fonts/OLEDDisplayFontsUA.h" -#endif - -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ - !defined(DISPLAY_FORCE_SMALL_FONTS) - -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#include "graphics/ScreenFonts.h" // Remove Canned message screen if no action is taken for some milliseconds #define INACTIVATE_AFTER_MS 20000 diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 9c7b406e9..e501f17c2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -33,23 +33,7 @@ SHT31Sensor sht31Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ - !defined(DISPLAY_FORCE_SMALL_FONTS) - -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#define FONT_SMALL ArialMT_Plain_10 -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#include "graphics/ScreenFonts.h" int32_t EnvironmentTelemetryModule::runOnce() { diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 032d7fc27..30628bfd7 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -14,23 +14,7 @@ #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ - !defined(DISPLAY_FORCE_SMALL_FONTS) - -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#define FONT_SMALL ArialMT_Plain_10 -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#include "graphics/ScreenFonts.h" int32_t PowerTelemetryModule::runOnce() { diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index bc104df11..a10cae954 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -8,14 +8,6 @@ #include "RTC.h" #include "Router.h" -#ifdef OLED_RU -#include "graphics/fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "graphics/fonts/OLEDDisplayFontsUA.h" -#endif - /* AudioModule A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project. @@ -48,32 +40,7 @@ AudioModule *audioModule; #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ - !defined(DISPLAY_FORCE_SMALL_FONTS) - -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#include "graphics/ScreenFonts.h" void run_codec2(void *parameter) { diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 89bf8f072..2182ed124 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -89,38 +89,7 @@ int32_t PaxcounterModule::runOnce() #if HAS_SCREEN -#ifdef OLED_RU -#include "graphics/fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "graphics/fonts/OLEDDisplayFontsUA.h" -#endif - -// TODO / FIXME: This code is copied from src/graphics/Screen.cpp -// It appears (in slightly variants) also in other modules like -// src/modules/Telemetry/PowerTelemetry.cpp, src/modules/Telemetry/EnvironmentTelemetry.cpp -// and src/modules/CannedMessageModule.cpp -// It probably should go to a common header file for consistency -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ - !defined(DISPLAY_FORCE_SMALL_FONTS) -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 // Height: 19 -#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 // Height: 13 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#endif +#include "graphics/ScreenFonts.h" void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -138,4 +107,4 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state } #endif // HAS_SCREEN -#endif \ No newline at end of file +#endif From 2786db499df7b824845578082256a14deab9acf3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Feb 2024 08:01:59 -0600 Subject: [PATCH 0026/1377] Missing include --- src/graphics/Screen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 129af226b..c92877d41 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . #include "error.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" +#include "graphics/ScreenFonts.h" #include "graphics/images.h" #include "input/TouchScreenImpl1.h" #include "main.h" From 7aee014f5e545550c27be135bba2f5370f9b2e5e Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Thu, 29 Feb 2024 04:20:20 +1300 Subject: [PATCH 0027/1377] Add Heltec Wireless Paper V1.0 to the build matrix (#3306) --- .github/workflows/main_matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 1e3e07106..e77b4a261 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -93,7 +93,8 @@ jobs: - board: heltec-wsl-v3 - board: heltec-wireless-tracker - board: heltec-wireless-tracker-V1-0 - - board: heltec-wireless-paper + - board: heltec-wireless-paper-v1_0 + - board: heltec-wireless-paper #v1.1 - board: tbeam-s3-core - board: tlora-t3s3-v1 - board: t-watch-s3 From 6acc63729b702501dba4a83e7285f068d6a21d13 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Thu, 29 Feb 2024 04:45:15 +1300 Subject: [PATCH 0028/1377] Refactor EInkDisplay (#3299) * Refactor EInkDisplay A lot of variant specific code is merged, with the macros pushed to the respective variant.h files. "Dynamic Partial" code has been purged, pending a rewrite. * fix: declare class only if USE_EINK, init all members * refactor: move macros to platformio.ini Responds to https://github.com/meshtastic/firmware/pull/3299#issuecomment-1966425926 * fix: EInkDisplay::connect() references old macros Usage was in a block of variant-specific code, which had been intentionally left untouched. * fix: remove duplicate macros from variant.h --------- Co-authored-by: Ben Meadors --- src/graphics/EInkDisplay2.cpp | 409 +++--------------- src/graphics/EInkDisplay2.h | 87 +--- .../platformio.ini | 3 + .../Dongle_nRF52840-pca10059-v1/variant.h | 2 +- .../MakePython_nRF52840_eink/platformio.ini | 3 + variants/esp32-s3-pico/platformio.ini | 6 +- variants/esp32-s3-pico/variant.h | 2 +- variants/heltec_wireless_paper/platformio.ini | 7 +- .../heltec_wireless_paper_v1/platformio.ini | 3 + variants/heltec_wireless_paper_v1/variant.h | 7 - variants/m5stack_coreink/platformio.ini | 5 +- variants/my_esp32s3_diy_eink/platformio.ini | 6 +- variants/my_esp32s3_diy_eink/variant.h | 2 +- variants/rak10701/platformio.ini | 3 + variants/rak10701/variant.h | 14 +- variants/rak4631/platformio.ini | 3 + variants/rak4631/variant.h | 14 +- variants/rak4631_epaper/platformio.ini | 3 + variants/rak4631_epaper_onrxtx/platformio.ini | 3 + variants/t-echo/platformio.ini | 3 + 20 files changed, 124 insertions(+), 461 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index de53daaee..d715d398b 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -2,127 +2,49 @@ #ifdef USE_EINK #include "EInkDisplay2.h" -#include "GxEPD2_BW.h" #include "SPILock.h" #include "main.h" #include -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) -SPIClass *hspi = NULL; -#endif +/* + The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini + Previously, these macros were defined at the top of this file. -#define COLORED GxEPD_BLACK -#define UNCOLORED GxEPD_WHITE + For archival reasons, note that the following configurations had also been tested during this period: + * ifdef RAK4631 + - 4.2 inch + EINK_DISPLAY_MODEL: GxEPD2_420_M01 + EINK_WIDTH: 300 + EINK_WIDTH: 400 -#if defined(TTGO_T_ECHO) -#define TECHO_DISPLAY_MODEL GxEPD2_154_D67 -#elif defined(RAK4630) + - 2.9 inch + EINK_DISPLAY_MODEL: GxEPD2_290_T5D + EINK_WIDTH: 296 + EINK_HEIGHT: 128 -// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give fast refresh -// support -#define TECHO_DISPLAY_MODEL GxEPD2_213_BN - -// 4.2 inch 300x400 - GxEPD2_420_M01 -// #define TECHO_DISPLAY_MODEL GxEPD2_420_M01 - -// 2.9 inch 296x128 - GxEPD2_290_T5D -// #define TECHO_DISPLAY_MODEL GxEPD2_290_T5D - -// 1.54 inch 200x200 - GxEPD2_154_M09 -// #define TECHO_DISPLAY_MODEL GxEPD2_154_M09 - -#elif defined(MAKERPYTHON) -// 2.9 inch 296x128 - GxEPD2_290_T5D -#define TECHO_DISPLAY_MODEL GxEPD2_290_T5D - -#elif defined(PCA10059) - -// 4.2 inch 300x400 - GxEPD2_420_M01 -#define TECHO_DISPLAY_MODEL GxEPD2_420_M01 - -#elif defined(M5_COREINK) -// M5Stack CoreInk -// 1.54 inch 200x200 - GxEPD2_154_M09 -#define TECHO_DISPLAY_MODEL GxEPD2_154_M09 - -#elif defined(HELTEC_WIRELESS_PAPER) -// #define TECHO_DISPLAY_MODEL GxEPD2_213_T5D -#define TECHO_DISPLAY_MODEL GxEPD2_213_FC1 - -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) -// 2.13" 122x250 - DEPG0213BNS800 -#define TECHO_DISPLAY_MODEL GxEPD2_213_BN - -#endif - -GxEPD2_BW *adafruitDisplay; + - 1.54 inch + EINK_DISPLAY_MODEL: GxEPD2_154_M09 + EINK_WIDTH: 200 + EINK_HEIGHT: 200 +*/ +// Constructor EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { -#if defined(TTGO_T_ECHO) - setGeometry(GEOMETRY_RAWMODE, 200, 200); -#elif defined(RAK4630) - - // GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - setGeometry(GEOMETRY_RAWMODE, 250, 122); - this->displayBufferSize = 250 * (128 / 8); - // GxEPD2_420_M01 - // setGeometry(GEOMETRY_RAWMODE, 300, 400); - - // GxEPD2_290_T5D - // setGeometry(GEOMETRY_RAWMODE, 296, 128); - - // GxEPD2_154_M09 - // setGeometry(GEOMETRY_RAWMODE, 200, 200); - -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) - - // The display's memory is actually 128px x 250px - // Setting the buffersize manually prevents 122/8 truncating to a 15 byte width - // (Or something like that..) - + // Set dimensions in OLEDDisplay base class this->geometry = GEOMETRY_RAWMODE; - this->displayWidth = 250; - this->displayHeight = 122; - this->displayBufferSize = 250 * (128 / 8); + this->displayWidth = EINK_WIDTH; + this->displayHeight = EINK_HEIGHT; -#elif defined(HELTEC_WIRELESS_PAPER) - // GxEPD2_213_BN - 2.13 inch b/w 250x122 - setGeometry(GEOMETRY_RAWMODE, 250, 122); -#elif defined(MAKERPYTHON) - // GxEPD2_290_T5D - setGeometry(GEOMETRY_RAWMODE, 296, 128); + // Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer + uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT); + uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT); + if (shortSide % 8 != 0) + shortSide = (shortSide | 7) + 1; -#elif defined(PCA10059) - - // GxEPD2_420_M01 - setGeometry(GEOMETRY_RAWMODE, 300, 400); - -#elif defined(M5_COREINK) - - // M5Stack_CoreInk 200x200 - // 1.54 inch 200x200 - GxEPD2_154_M09 - setGeometry(GEOMETRY_RAWMODE, EPD_HEIGHT, EPD_WIDTH); -#elif defined(my) - - // GxEPD2_290_T5D - setGeometry(GEOMETRY_RAWMODE, 296, 128); - LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n"); - -#elif defined(ESP32_S3_PICO) - - // GxEPD2_290_T94_V2 - setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT); - LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n"); - -#endif - // setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution - // setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does + this->displayBufferSize = longSide * (shortSide / 8); } -// FIXME quick hack to limit drawing to a very slow rate -uint32_t lastDrawMsec; - /** * Force a display update if we haven't drawn within the specified msecLimit */ @@ -131,13 +53,6 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) // No need to grab this lock because we are on our own SPI bus // concurrency::LockGuard g(spiLock); -#if defined(USE_EINK_DYNAMIC_REFRESH) - // Decide between full refresh, fast refresh, or skipping the update - bool continueUpdate = determineRefreshMode(); - if (!continueUpdate) - return false; -#else - uint32_t now = millis(); uint32_t sinceLast = now - lastDrawMsec; @@ -146,52 +61,34 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) else return false; -#endif - // FIXME - only draw bits have changed (use backbuf similar to the other displays) - // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK); for (uint32_t y = 0; y < displayHeight; y++) { for (uint32_t x = 0; x < displayWidth; x++) { // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); - adafruitDisplay->drawPixel(x, y, isset ? COLORED : UNCOLORED); + adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); } } LOG_DEBUG("Updating E-Paper... "); -#if defined(TTGO_T_ECHO) - adafruitDisplay->nextPage(); -#elif defined(RAK4630) || defined(MAKERPYTHON) - - // RAK14000 2.13 inch b/w 250x122 actually now does support fast refresh +#if false + // Currently unused; rescued from commented-out line during a refactor + // Use a meaningful macro here if variant doesn't want fast refresh // Full update mode (slow) - // adafruitDisplay->display(false); // FIXME, use fast refresh mode - - // Only enable for e-Paper with support for fast updates and comment out above adafruitDisplay->display(false); - // 1.54 inch 200x200 - GxEPD2_154_M09 - // 2.13 inch 250x122 - GxEPD2_213_BN - // 2.9 inch 296x128 - GxEPD2_290_T5D - // 4.2 inch 300x400 - GxEPD2_420_M01 - adafruitDisplay->nextPage(); - -#elif defined(PCA10059) || defined(M5_COREINK) - adafruitDisplay->nextPage(); -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) - adafruitDisplay->nextPage(); -#elif defined(HELTEC_WIRELESS_PAPER) - adafruitDisplay->nextPage(); -#elif defined(ESP32_S3_PICO) - adafruitDisplay->nextPage(); -#elif defined(PRIVATE_HW) || defined(my) + adafruitDisplay->display(false) +#else + // Fast update mode adafruitDisplay->nextPage(); #endif +#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display) adafruitDisplay->hibernate(); LOG_DEBUG("done\n"); +#endif return true; } @@ -203,15 +100,9 @@ void EInkDisplay::display(void) // at least one forceDisplay() keyframe. This prevents flashing when we should the critical // bootscreen (that we want to look nice) -#ifdef USE_EINK_DYNAMIC_REFRESH - lowPriority(); - forceDisplay(); - highPriority(); -#else if (lastDrawMsec) { forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower } -#endif } // Send a command to the display (low level function) @@ -226,7 +117,7 @@ void EInkDisplay::setDetected(uint8_t detected) (void)detected; } -// Connect to the display +// Connect to the display - variant specific bool EInkDisplay::connect() { LOG_INFO("Doing EInk init\n"); @@ -244,9 +135,9 @@ bool EInkDisplay::connect() #if defined(TTGO_T_ECHO) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(); adafruitDisplay->setRotation(3); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); @@ -254,8 +145,8 @@ bool EInkDisplay::connect() #elif defined(RAK4630) || defined(MAKERPYTHON) { if (eink_found) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh adafruitDisplay->setRotation(3); @@ -296,8 +187,8 @@ bool EInkDisplay::connect() delay(100); // Create GxEPD2 objects - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); + adafruitDisplay = new GxEPD2_BW(*lowLevel); // Init GxEPD2 adafruitDisplay->init(); @@ -311,228 +202,36 @@ bool EInkDisplay::connect() pinMode(Vext, OUTPUT); digitalWrite(Vext, LOW); delay(100); - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(); adafruitDisplay->setRotation(3); } #elif defined(PCA10059) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->setRotation(3); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); } #elif defined(M5_COREINK) - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->setRotation(0); - adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); #elif defined(my) || defined(ESP32_S3_PICO) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->setRotation(1); - adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } #endif - // adafruitDisplay->setFullWindow(); - // adafruitDisplay->fillScreen(UNCOLORED); - // adafruitDisplay->drawCircle(100, 100, 20, COLORED); - // adafruitDisplay->display(false); return true; } -// Use a mix of full refresh, fast refresh, and update skipping, to balance urgency and display health -#if defined(USE_EINK_DYNAMIC_REFRESH) - -// Suggest that subsequent updates should use fast-refresh -void EInkDisplay::highPriority() -{ - isHighPriority = true; -} - -// Suggest that subsequent updates should use full-refresh -void EInkDisplay::lowPriority() -{ - isHighPriority = false; -} - -// Full-refresh is explicitly requested for next one update - no skipping please -void EInkDisplay::demandFullRefresh() -{ - demandingFull = true; -} - -// configure display for fast-refresh -void EInkDisplay::configForFastRefresh() -{ - // Display-specific code can go here -#if defined(PRIVATE_HW) -#else - // Otherwise: - adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height()); -#endif -} - -// Configure display for full-refresh -void EInkDisplay::configForFullRefresh() -{ - // Display-specific code can go here -#if defined(PRIVATE_HW) -#else - // Otherwise: - adafruitDisplay->setFullWindow(); -#endif -} - -#ifdef EINK_FASTREFRESH_ERASURE_LIMIT -// Count black pixels in an image. Used for "erasure tracking" -int32_t EInkDisplay::countBlackPixels() -{ - int32_t blackCount = 0; // Signed, to avoid underflow when comparing - for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { - for (uint8_t i = 0; i < 7; i++) { - // Check if each bit is black or white - blackCount += (buffer[b] >> i) & 1; - } - } - return blackCount; -} - -// Evaluate the (rough) amount of black->white pixel change since last full refresh -bool EInkDisplay::tooManyErasures() -{ - // Ideally, we would compare the new and old buffers, to count *actual* white-to-black pixel changes - // but that would require substantially more "code tampering" - - // Get the black pixel stats for this image - int32_t blackCount = countBlackPixels(); - int32_t blackDifference = blackCount - prevBlackCount; - - // Update the running total of "erasures" - black pixels which have become white, since last full-refresh - if (blackDifference < 0) - erasedSinceFull -= blackDifference; - - // Store black pixel count for next time - prevBlackCount = blackCount; - - // Log the running total - help devs setup new boards - LOG_DEBUG("Dynamic Refresh: erasedSinceFull=%hu, EINK_FASTREFRESH_ERASURE_LIMIT=%hu\n", erasedSinceFull, - EINK_FASTREFRESH_ERASURE_LIMIT); - - // Check if too many pixels have been erased - if (erasedSinceFull > EINK_FASTREFRESH_ERASURE_LIMIT) - return true; // Too many - else - return false; // Still okay -} -#endif // ifdef EINK_FASTREFRESH_ERASURE_LIMIT - -bool EInkDisplay::newImageMatchesOld() -{ - uint32_t newImageHash = 0; - - // Generate hash: sum all bytes in the image buffer - for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { - newImageHash += buffer[b]; - } - - // Compare hashes - bool hashMatches = (newImageHash == prevImageHash); - - // Update the cached hash - prevImageHash = newImageHash; - - // Return the comparison result - return hashMatches; -} - -// Choose between, full-refresh, fast refresh, and update skipping, to balance urgency and display health. -bool EInkDisplay::determineRefreshMode() -{ - uint32_t now = millis(); - uint32_t sinceLast = now - lastUpdateMsec; - - // If rate-limiting dropped a high-priority update: - // promote this update, so it runs ASAP - if (missedHighPriorityUpdate) { - isHighPriority = true; - missedHighPriorityUpdate = false; - } - - // Abort: if too soon for a new frame (unless demanding full) - if (!demandingFull && isHighPriority && fastRefreshCount > 0 && sinceLast < highPriorityLimitMsec) { - LOG_DEBUG("Dynamic Refresh: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n"); - missedHighPriorityUpdate = true; - return false; - } - if (!demandingFull && !isHighPriority && !demandingFull && sinceLast < lowPriorityLimitMsec) { - return false; - } - - // If demanded full refresh: give it to them - if (demandingFull) - needsFull = true; - - // Check if old image (fast-refresh) should be redrawn (as full), for image quality - if (fastRefreshCount > 0 && !isHighPriority) - needsFull = true; - - // If too many fast updates, require a full-refresh (display health) - if (fastRefreshCount >= fastRefreshLimit) - needsFull = true; - -#ifdef EINK_FASTREFRESH_ERASURE_LIMIT - // Some displays struggle with erasing black pixels to white, during fast-refresh - if (tooManyErasures()) - needsFull = true; -#endif - - // If image matches - // (Block must run, even if full already selected, to store hash for next time) - if (newImageMatchesOld()) { - // If low priority: limit rate - // otherwise, every loop() will run the hash method - if (!isHighPriority) - lastUpdateMsec = now; - - // If update is *not* for display health or image quality, skip it - if (!needsFull) - return false; - } - - // Conditions assessed - not skipping - load the appropriate config - - // If options require a full refresh - if (!isHighPriority || needsFull) { - if (fastRefreshCount > 0) - configForFullRefresh(); - - LOG_DEBUG("Dynamic Refresh: conditions met for full-refresh\n"); - fastRefreshCount = 0; - needsFull = false; - demandingFull = false; - erasedSinceFull = 0; // Reset the count for EINK_FASTREFRESH_ERASURE_LIMIT - tracks ghosting buildup - } - - // If options allow a fast-refresh - else { - if (fastRefreshCount == 0) - configForFastRefresh(); - - LOG_DEBUG("Dynamic Refresh: conditions met for fast-refresh\n"); - fastRefreshCount++; - } - - lastUpdateMsec = now; // Mark time for rate limiting - return true; // Instruct calling method to continue with update -} - -#endif // End USE_EINK_DYNAMIC_REFRESH - #endif diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 260a79755..f40747f26 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -1,5 +1,8 @@ #pragma once +#ifdef USE_EINK + +#include "GxEPD2_BW.h" #include #if defined(HELTEC_WIRELESS_PAPER_V1_0) @@ -16,6 +19,7 @@ * Use the fast NRF52 SPI API rather than the slow standard arduino version * * turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted? + * Suggestion: perhaps similar to HELTEC_WIRELESS_PAPER issue, which resolved with rtc_gpio_hold_dis() */ class EInkDisplay : public OLEDDisplay { @@ -55,80 +59,17 @@ class EInkDisplay : public OLEDDisplay // Connect to the display virtual bool connect() override; -#if defined(USE_EINK_DYNAMIC_REFRESH) - // Full, fast, or skip: balance urgency with display health + // AdafruitGFX display object - instantiated in connect(), variant specific + GxEPD2_BW *adafruitDisplay = NULL; - // Use fast refresh if EITHER: - // * highPriority() was set - // * a highPriority() update was previously skipped, for rate-limiting - (EINK_HIGHPRIORITY_LIMIT_SECONDS) - - // Use full refresh if EITHER: - // * lowPriority() was set - // * demandFullRefresh() was called - (single shot) - // * too many fast updates in a row: protect display - (EINK_FASTREFRESH_REPEAT_LIMIT) - // * no recent updates, and last update was fast: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS) - // * (optional) too many "erasures" since full-refresh (black pixels cleared to white) - - // Rate limit if: - // * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS) - // * highPriority(), if multiple fast updates have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS) - - // Skip update entirely if ALL criteria met: - // * new image matches old image - // * lowPriority() - // * no call to demandFullRefresh() - // * not redrawing for image quality - // * not refreshing for display health - - // ------------------------------------ - - // To implement for your E-Ink display: - // * edit configForFastRefresh() - // * edit configForFullRefresh() - // * add macros to variant.h, and adjust to taste: - - /* - #define USE_EINK_DYNAMIC_REFRESH - #define EINK_LOWPRIORITY_LIMIT_SECONDS 30 - #define EINK_HIGHPRIORITY_LIMIT_SECONDS 1 - #define EINK_FASTREFRESH_REPEAT_LIMIT 5 - #define EINK_FASTREFRESH_ERASURE_LIMIT 300 // optional - */ - - public: - void highPriority(); // Suggest fast refresh - void lowPriority(); // Suggest full refresh - void demandFullRefresh(); // For next update: explicitly request full refresh - - protected: - void configForFastRefresh(); // Display specific code to select fast refresh mode - void configForFullRefresh(); // Display specific code to return to full refresh mode - bool newImageMatchesOld(); // Is the new update actually different to the last image? - bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update -#ifdef EINK_FASTREFRESH_ERASURE_LIMIT - int32_t countBlackPixels(); // Calculate the number of black pixels in the new image - bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh? + // If display uses HSPI +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) + SPIClass *hspi = NULL; #endif - bool isHighPriority = true; // Does the method calling update believe that this is urgent? - bool needsFull = false; // Is a full refresh forced? (display health) - bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc) - bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting? - uint16_t fastRefreshCount = 0; // How many fast updates have occurred since last full refresh? - uint32_t lastUpdateMsec = 0; // When did the last update occur? - uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not) - int32_t prevBlackCount = 0; // How many black pixels were in the previous image - uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly) - - // Set in variant.h - const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for fast refreshes - const uint32_t highPriorityLimitMsec = (uint32_t)1000 * EINK_HIGHPRIORITY_LIMIT_SECONDS; // Max rate for full refreshes - const uint32_t fastRefreshLimit = EINK_FASTREFRESH_REPEAT_LIMIT; // Max consecutive fast updates, before full is triggered - -#else // !USE_EINK_DYNAMIC_REFRESH - // Tolerate calls to these methods anywhere, just to be safe - void highPriority() {} - void lowPriority() {} - void demandFullRefresh() {} -#endif + private: + // FIXME quick hack to limit drawing to a very slow rate + uint32_t lastDrawMsec = 0; }; + +#endif \ No newline at end of file diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini index a937bfa98..b1608770e 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -4,6 +4,9 @@ board = nordic_pca10059 board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_420_M01 + -DEINK_WIDTH=300 + -DEINK_HEIGHT=400 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/Dongle_nRF52840-pca10059-v1> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/Dongle_nRF52840-pca10059-v1/variant.h index 81e2ad995..0f1bf15da 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.h @@ -136,7 +136,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define USE_SX1262 #define SX126X_CS (0 + 31) // LORA_CS P0.31 #define SX126X_DIO1 (0 + 29) // DIO1 P0.29 -#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02 +#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02 #define SX126X_RESET (32 + 15) // LORA_RESET P1.15 #define SX126X_TXEN (32 + 13) // TXEN P1.13 NiceRF 868 dont use #define SX126X_RXEN (32 + 10) // RXEN P1.10 NiceRF 868 dont use diff --git a/variants/MakePython_nRF52840_eink/platformio.ini b/variants/MakePython_nRF52840_eink/platformio.ini index 3ac18bcf0..f421466ec 100644 --- a/variants/MakePython_nRF52840_eink/platformio.ini +++ b/variants/MakePython_nRF52840_eink/platformio.ini @@ -10,5 +10,8 @@ lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f zinggjm/GxEPD2@^1.4.9 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 debug_tool = jlink ;upload_port = /dev/ttyACM4 diff --git a/variants/esp32-s3-pico/platformio.ini b/variants/esp32-s3-pico/platformio.ini index f39004c36..ef737d98a 100644 --- a/variants/esp32-s3-pico/platformio.ini +++ b/variants/esp32-s3-pico/platformio.ini @@ -16,9 +16,9 @@ build_flags = ${esp32_base.build_flags} ;-DPRIVATE_HW -Ivariants/esp32-s3-pico -DBOARD_HAS_PSRAM - -DTECHO_DISPLAY_MODEL=GxEPD2_290_T94_V2 - -DEPD_HEIGHT=128 - -DEPD_WIDTH=296 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T94_V2 + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 lib_deps = ${esp32s3_base.lib_deps} zinggjm/GxEPD2@^1.5.3 diff --git a/variants/esp32-s3-pico/variant.h b/variants/esp32-s3-pico/variant.h index 9e10183fb..87378d378 100644 --- a/variants/esp32-s3-pico/variant.h +++ b/variants/esp32-s3-pico/variant.h @@ -74,4 +74,4 @@ #define PIN_EINK_DC 33 #define PIN_EINK_RES 42 // 37 //(-1) // cant be MISO Waveshare ??) #define PIN_EINK_SCLK 35 -#define PIN_EINK_MOSI 36 +#define PIN_EINK_MOSI 36 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index dd93b52cb..56446dc8b 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -2,7 +2,12 @@ extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = - ${esp32s3_base.build_flags} -D HELTEC_WIRELESS_PAPER -I variants/heltec_wireless_paper + ${esp32s3_base.build_flags} + -I variants/heltec_wireless_paper + -D HELTEC_WIRELESS_PAPER + -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 lib_deps = ${esp32s3_base.lib_deps} https://github.com/ixt/GxEPD2#39f325b677713eb04dfcc83b8e402e77523fb8bf diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 7d7f4eb14..01b68f5f0 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -5,6 +5,9 @@ build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_paper_v1 -D HELTEC_WIRELESS_PAPER_V1_0 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2/ diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 25e061938..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -6,13 +6,6 @@ #define USE_EINK -// Settings for Dynamic Refresh mode -// Change between full-refresh, fast-refresh, or update-skipping, to balance urgency and display health. -#define USE_EINK_DYNAMIC_REFRESH -#define EINK_LOWPRIORITY_LIMIT_SECONDS 30 -#define EINK_HIGHPRIORITY_LIMIT_SECONDS 1 -#define EINK_FASTREFRESH_REPEAT_LIMIT 5 - /* * eink display pins */ diff --git a/variants/m5stack_coreink/platformio.ini b/variants/m5stack_coreink/platformio.ini index ee6d340dc..dfb078a0a 100644 --- a/variants/m5stack_coreink/platformio.ini +++ b/variants/m5stack_coreink/platformio.ini @@ -9,8 +9,9 @@ build_flags = ;-D RADIOLIB_VERBOSE -Ofast -D__MCUXPRESSO - -DEPD_HEIGHT=200 - -DEPD_WIDTH=200 + -DEINK_DISPLAY_MODEL=GxEPD2_154_M09 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 -DUSER_SETUP_LOADED -DM5_COREINK -DM5STACK diff --git a/variants/my_esp32s3_diy_eink/platformio.ini b/variants/my_esp32s3_diy_eink/platformio.ini index d3c55afda..966bc580e 100644 --- a/variants/my_esp32s3_diy_eink/platformio.ini +++ b/variants/my_esp32s3_diy_eink/platformio.ini @@ -19,9 +19,9 @@ build_flags = ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink ${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink -Dmy - -DTECHO_DISPLAY_MODEL=GxEPD2_290_T5D - -DEPD_HEIGHT=128 - -DEPD_WIDTH=296 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DARDUINO_USB_MODE=0 diff --git a/variants/my_esp32s3_diy_eink/variant.h b/variants/my_esp32s3_diy_eink/variant.h index a5bebdacc..516fa7f34 100644 --- a/variants/my_esp32s3_diy_eink/variant.h +++ b/variants/my_esp32s3_diy_eink/variant.h @@ -53,4 +53,4 @@ #define PIN_EINK_DC 1 #define PIN_EINK_RES (-1) #define PIN_EINK_SCLK 5 -#define PIN_EINK_MOSI 6 +#define PIN_EINK_MOSI 6 \ No newline at end of file diff --git a/variants/rak10701/platformio.ini b/variants/rak10701/platformio.ini index 736329eb8..37f785e84 100644 --- a/variants/rak10701/platformio.ini +++ b/variants/rak10701/platformio.ini @@ -5,6 +5,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak10701 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak10701> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak10701/variant.h b/variants/rak10701/variant.h index 5ff12a7de..837d081ff 100644 --- a/variants/rak10701/variant.h +++ b/variants/rak10701/variant.h @@ -202,13 +202,13 @@ static const uint8_t WB_SPI_MOSI = 30; // IO_SLOT /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) Important for successful SX1262 initialization: diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b9789166f..b1bc2d9b5 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -5,6 +5,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index cc18a901f..e22ff0b63 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -181,13 +181,13 @@ static const uint8_t SCK = PIN_SPI_SCK; /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) Important for successful SX1262 initialization: diff --git a/variants/rak4631_epaper/platformio.ini b/variants/rak4631_epaper/platformio.ini index 0871eee45..ced732d94 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/rak4631_epaper/platformio.ini @@ -4,6 +4,9 @@ extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini index 0a4f05344..c4a13ec82 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -6,6 +6,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -D PIN_EINK_EN=34 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 843bd88ff..49ba3bb34 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -8,6 +8,9 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DGPS_POWER_TOGGLE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From bf88773b6b47386005e087e6a424f4aed97bf504 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Feb 2024 13:44:15 -0600 Subject: [PATCH 0029/1377] Don't send anybody to null island ever (#3308) --- src/modules/PositionModule.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 1e4ea5de1..addff8966 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -176,6 +176,11 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_INFO("Providing time to mesh %u\n", p.time); } + if (p.latitude_i == 0 && p.longitude_i == 0) { + LOG_WARN("Skipping position send because lat/lon are zero!\n"); + return nullptr; + } + LOG_INFO("Position reply: time=%i, latI=%i, lonI=%i\n", p.time, p.latitude_i, p.longitude_i); // TAK Tracker devices should send their position in a TAK packet over the ATAK port From d20fa6e927b2aea46e6485f929cf0de24d1140e9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 2 Mar 2024 07:21:53 -0600 Subject: [PATCH 0030/1377] Comment out ReplyModule registration, but leave it in as example (#3312) --- src/modules/Modules.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index fd109b765..2d45868fd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -13,7 +13,6 @@ #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" #include "modules/RemoteHardwareModule.h" -#include "modules/ReplyModule.h" #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" #include "modules/TraceRouteModule.h" @@ -67,7 +66,8 @@ void setupModules() // to a global variable. new RemoteHardwareModule(); - new ReplyModule(); + // Example: Put your module here + // new ReplyModule(); #if HAS_BUTTON || ARCH_PORTDUINO rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { From a58348369d0ffeaef28bd585c89a649ee7840d93 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 2 Mar 2024 10:20:27 -0600 Subject: [PATCH 0031/1377] Update protos and add new fields to type conversions (#3315) --- protobufs | 2 +- src/mesh/TypeConversions.cpp | 2 ++ src/mesh/generated/meshtastic/deviceonly.pb.h | 18 ++++++++--- src/mesh/generated/meshtastic/mesh.pb.h | 32 +++++++++++++------ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/protobufs b/protobufs index 524158356..62b7d8b88 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5241583565ccbbb4986180bf4c6eb7f8a0dec285 +Subproject commit 62b7d8b884d70aed5ff18c3b0e228095eeb48de2 diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 4e0fdd385..20b1cb31e 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -10,6 +10,8 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo info.snr = lite->snr; info.last_heard = lite->last_heard; info.channel = lite->channel; + info.via_mqtt = lite->via_mqtt; + info.hops_away = lite->hops_away; if (lite->has_position) { info.has_position = true; diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 735644c47..ca4b2176b 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -65,6 +65,10 @@ typedef struct _meshtastic_NodeInfoLite { meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; } meshtastic_NodeInfoLite; /* The on-disk saved channels */ @@ -175,13 +179,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, 0, {meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default}} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, 0, {meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero}} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -200,6 +204,8 @@ extern "C" { #define meshtastic_NodeInfoLite_last_heard_tag 5 #define meshtastic_NodeInfoLite_device_metrics_tag 6 #define meshtastic_NodeInfoLite_channel_tag 7 +#define meshtastic_NodeInfoLite_via_mqtt_tag 8 +#define meshtastic_NodeInfoLite_hops_away_tag 9 #define meshtastic_ChannelFile_channels_tag 1 #define meshtastic_ChannelFile_version_tag 2 #define meshtastic_OEMStore_oem_icon_width_tag 1 @@ -252,7 +258,9 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User @@ -313,8 +321,8 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Maximum encoded size of messages (where known) */ #define meshtastic_ChannelFile_size 702 -#define meshtastic_DeviceState_size 17062 -#define meshtastic_NodeInfoLite_size 153 +#define meshtastic_DeviceState_size 17571 +#define meshtastic_NodeInfoLite_size 158 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_OEMStore_size 3246 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 6ffd93294..04590210e 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -534,8 +534,7 @@ typedef struct _meshtastic_MeshPacket { Note: Our crypto implementation uses this field as well. See [crypto](/docs/overview/encryption) for details. */ uint32_t from; - /* The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only - hurts the ble link though. */ + /* The (immediate) destination for this packet */ uint32_t to; /* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on. If unset, packet was on the primary channel. @@ -594,6 +593,9 @@ typedef struct _meshtastic_MeshPacket { meshtastic_MeshPacket_Delayed delayed; /* Describes whether this packet passed via MQTT somewhere along the path it currently took. */ bool via_mqtt; + /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. + When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */ + uint8_t hop_start; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -632,6 +634,10 @@ typedef struct _meshtastic_NodeInfo { meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; } meshtastic_NodeInfo; /* Unique local debugging info for this node @@ -890,8 +896,8 @@ extern "C" { #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -908,8 +914,8 @@ extern "C" { #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -989,6 +995,7 @@ extern "C" { #define meshtastic_MeshPacket_rx_rssi_tag 12 #define meshtastic_MeshPacket_delayed_tag 13 #define meshtastic_MeshPacket_via_mqtt_tag 14 +#define meshtastic_MeshPacket_hop_start_tag 15 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -996,6 +1003,8 @@ extern "C" { #define meshtastic_NodeInfo_last_heard_tag 5 #define meshtastic_NodeInfo_device_metrics_tag 6 #define meshtastic_NodeInfo_channel_tag 7 +#define meshtastic_NodeInfo_via_mqtt_tag 8 +#define meshtastic_NodeInfo_hops_away_tag 9 #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 @@ -1146,7 +1155,8 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 10) \ X(a, STATIC, SINGULAR, UENUM, priority, 11) \ X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \ X(a, STATIC, SINGULAR, UENUM, delayed, 13) \ -X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \ +X(a, STATIC, SINGULAR, UINT32, hop_start, 15) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -1158,7 +1168,9 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL #define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User @@ -1311,12 +1323,12 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; #define meshtastic_DeviceMetadata_size 46 #define meshtastic_FromRadio_size 510 #define meshtastic_LogRecord_size 81 -#define meshtastic_MeshPacket_size 323 +#define meshtastic_MeshPacket_size 326 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 270 +#define meshtastic_NodeInfo_size 275 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 From 905718e2ac88138d7ddf975a6fe2d38e84fe7390 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 2 Mar 2024 15:45:59 -0600 Subject: [PATCH 0032/1377] Remove problematic IO2 3V3 toggling (again) as GPS EN pin (#3317) * Remove problematic IO2 3V3 toggling (again) as GPS EN pin * Comment * Trunk --- src/gps/GPS.cpp | 13 ------------- variants/rak4631/variant.h | 1 + 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 3a3660308..2bdb8e176 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -255,19 +255,6 @@ bool GPS::setup() if (!didSerialInit) { #if !defined(GPS_UC6580) -#if defined(RAK4630) && defined(PIN_3V3_EN) - // If we are using the RAK4630 and we have no other peripherals on the I2C bus or module interest in 3V3_S, - // then we can safely set en_gpio turn off power to 3V3 (IO2) to hard sleep the GPS - if (rtc_found.port == ScanI2C::DeviceType::NONE && rgb_found.type == ScanI2C::DeviceType::NONE && - accelerometer_found.port == ScanI2C::DeviceType::NONE && !moduleConfig.detection_sensor.enabled && - !moduleConfig.telemetry.air_quality_enabled && !moduleConfig.telemetry.environment_measurement_enabled && - config.power.device_battery_ina_address == 0 && en_gpio == 0) { - LOG_DEBUG("Since no problematic peripherals or interested modules were found, setting power save GPS_EN to pin %i\n", - PIN_3V3_EN); - en_gpio = PIN_3V3_EN; - } -#endif - if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]); gnssModel = probe(serialSpeeds[speedSelect]); diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index e22ff0b63..4ad99df44 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -219,6 +219,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define NRF_APM // enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings #define PIN_3V3_EN (34) // RAK1910 GPS module From c65929283647b8af1388a765a816d7ebb60782d5 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sun, 3 Mar 2024 15:07:29 +1300 Subject: [PATCH 0033/1377] Reimplement "Dynamic E-Ink" as a derived class (#3316) Co-authored-by: Ben Meadors --- src/graphics/EInkDisplay2.cpp | 2 +- src/graphics/EInkDisplay2.h | 4 +- src/graphics/EInkDynamicDisplay.cpp | 384 ++++++++++++++++++ src/graphics/EInkDynamicDisplay.h | 104 +++++ src/graphics/Screen.cpp | 5 +- src/graphics/Screen.h | 1 + .../heltec_wireless_paper_v1/platformio.ini | 6 + 7 files changed, 503 insertions(+), 3 deletions(-) create mode 100644 src/graphics/EInkDynamicDisplay.cpp create mode 100644 src/graphics/EInkDynamicDisplay.h diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index d715d398b..d790e30c1 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -87,9 +87,9 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) #ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display) adafruitDisplay->hibernate(); - LOG_DEBUG("done\n"); #endif + LOG_DEBUG("done\n"); return true; } diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index f40747f26..369378132 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -13,6 +13,8 @@ /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * + * Note: EInkDynamicDisplay derives from this class. + * * Remaining TODO: * optimize display() to only draw changed pixels (see other OLED subclasses for examples) * implement displayOn/displayOff to turn off the TFT device (and backlight) @@ -41,7 +43,7 @@ class EInkDisplay : public OLEDDisplay * * @return true if we did draw the screen */ - bool forceDisplay(uint32_t msecLimit = 1000); + virtual bool forceDisplay(uint32_t msecLimit = 1000); /** * shim to make the abstraction happy diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp new file mode 100644 index 000000000..ae1e30fe1 --- /dev/null +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -0,0 +1,384 @@ +#include "configuration.h" + +#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) +#include "EInkDynamicDisplay.h" + +// Constructor +EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) + : EInkDisplay(address, sda, scl, geometry, i2cBus) +{ + // If tracking ghost pixels, grab memory +#ifdef EINK_LIMIT_GHOSTING_PX + dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros +#endif +} + +// Destructor +EInkDynamicDisplay::~EInkDynamicDisplay() +{ + // If we were tracking ghost pixels, free the memory +#ifdef EINK_LIMIT_GHOSTING_PX + delete[] dirtyPixels; +#endif +} + +// Screen requests a BACKGROUND frame +void EInkDynamicDisplay::display() +{ + setFrameFlag(BACKGROUND); + update(); +} + +// Screen requests a RESPONSIVE frame +bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit) +{ + setFrameFlag(RESPONSIVE); + return update(); // (Unutilized) Base class promises to return true if update ran +} + +// Add flag for the next frame +void EInkDynamicDisplay::setFrameFlag(frameFlagTypes flag) +{ + // OR the new flag into the existing flags + this->frameFlags = (frameFlagTypes)(this->frameFlags | flag); +} + +// GxEPD2 code to set fast refresh +void EInkDynamicDisplay::configForFastRefresh() +{ + // Variant-specific code can go here +#if defined(PRIVATE_HW) +#else + // Otherwise: + adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height()); +#endif +} + +// GxEPD2 code to set full refresh +void EInkDynamicDisplay::configForFullRefresh() +{ + // Variant-specific code can go here +#if defined(PRIVATE_HW) +#else + // Otherwise: + adafruitDisplay->setFullWindow(); +#endif +} + +// Run any relevant GxEPD2 code, so next update will use correct refresh type +void EInkDynamicDisplay::applyRefreshMode() +{ + // Change from FULL to FAST + if (currentConfig == FULL && refresh == FAST) { + configForFastRefresh(); + currentConfig = FAST; + } + + // Change from FAST back to FULL + else if (currentConfig == FAST && refresh == FULL) { + configForFullRefresh(); + currentConfig = FULL; + } +} + +// Update fastRefreshCount +void EInkDynamicDisplay::adjustRefreshCounters() +{ + if (refresh == FAST) + fastRefreshCount++; + + else if (refresh == FULL) + fastRefreshCount = 0; +} + +// Trigger the display update by calling base class +bool EInkDynamicDisplay::update() +{ + bool refreshApproved = determineMode(); + if (refreshApproved) + EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system + return refreshApproved; // (Unutilized) Base class promises to return true if update ran +} + +// Assess situation, pick a refresh type +bool EInkDynamicDisplay::determineMode() +{ + checkWasFlooded(); + checkRateLimiting(); + + // If too soon for a new time, abort here + if (refresh == SKIPPED) { + storeAndReset(); + return false; // No refresh + } + + // -- New frame is due -- + + resetRateLimiting(); // Once determineMode() ends, will have to wait again + hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check + LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry + + // Once mode determined, any remaining checks will bypass + checkCosmetic(); + checkDemandingFast(); + checkConsecutiveFastRefreshes(); +#ifdef EINK_LIMIT_GHOSTING_PX + checkExcessiveGhosting(); +#endif + checkFrameMatchesPrevious(); + checkFastRequested(); + + if (refresh == UNSPECIFIED) + LOG_WARN("There was a flaw in the determineMode() logic.\n"); + + // -- Decision has been reached -- + applyRefreshMode(); + adjustRefreshCounters(); + +#ifdef EINK_LIMIT_GHOSTING_PX + // Full refresh clears any ghosting + if (refresh == FULL) + resetGhostPixelTracking(); +#endif + + // Return - call a refresh or not? + if (refresh == SKIPPED) { + storeAndReset(); + return false; // Don't trigger a refresh + } else { + storeAndReset(); + return true; // Do trigger a refresh + } +} + +// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh? +void EInkDynamicDisplay::checkWasFlooded() +{ + if (previousReason == EXCEEDED_RATELIMIT_FAST) { + // If so, allow a BACKGROUND frame to draw as RESPONSIVE + // Because we DID want a RESPONSIVE frame last time, we just didn't get it + setFrameFlag(RESPONSIVE); + } +} + +// Is it too soon for another frame of this type? +void EInkDynamicDisplay::checkRateLimiting() +{ + uint32_t now = millis(); + + // Sanity check: millis() overflow - just let the update run.. + if (previousRunMs > now) + return; + + // Skip update: too soon for BACKGROUND + if (frameFlags == BACKGROUND) { + if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) { + refresh = SKIPPED; + reason = EXCEEDED_RATELIMIT_FULL; + return; + } + } + + // No rate-limit for these special cases + if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST) + return; + + // Skip update: too soon for RESPONSIVE + if (frameFlags & RESPONSIVE) { + if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) { + refresh = SKIPPED; + reason = EXCEEDED_RATELIMIT_FAST; + return; + } + } +} + +// Is this frame COSMETIC (splash screens?) +void EInkDynamicDisplay::checkCosmetic() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // A full refresh is requested for cosmetic purposes: we have a decision + if (frameFlags & COSMETIC) { + refresh = FULL; + reason = FLAGGED_COSMETIC; + LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC\n"); + } +} + +// Is this a one-off special circumstance, where we REALLY want a fast refresh? +void EInkDynamicDisplay::checkDemandingFast() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // A fast refresh is demanded: we have a decision + if (frameFlags & DEMAND_FAST) { + refresh = FAST; + reason = FLAGGED_DEMAND_FAST; + LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST\n"); + } +} + +// Have too many fast-refreshes occured consecutively, since last full refresh? +void EInkDynamicDisplay::checkConsecutiveFastRefreshes() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // If too many FAST refreshes consecutively - force a FULL refresh + if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) { + refresh = FULL; + reason = EXCEEDED_LIMIT_FASTREFRESH; + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n"); + } +} + +// Does the new frame match the currently displayed image? +void EInkDynamicDisplay::checkFrameMatchesPrevious() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // If frame is *not* a duplicate, abort the check + if (imageHash != previousImageHash) + return; + +#if !defined(EINK_BACKGROUND_USES_FAST) + // If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality) + if (frameFlags == BACKGROUND && fastRefreshCount > 0) { + refresh = FULL; + reason = REDRAW_WITH_FULL; + LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL\n"); + return; + } +#endif + + // Not redrawn, not COSMETIC, not DEMAND_FAST + refresh = SKIPPED; + reason = FRAME_MATCHED_PREVIOUS; + LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS\n"); +} + +// No objections, we can perform fast-refresh, if desired +void EInkDynamicDisplay::checkFastRequested() +{ + if (refresh != UNSPECIFIED) + return; + + if (frameFlags == BACKGROUND) { +#ifdef EINK_BACKGROUND_USES_FAST + // If we want BACKGROUND to use fast. (FULL only when a limit is hit) + refresh = FAST; + reason = BACKGROUND_USES_FAST; + LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu\n", fastRefreshCount); +#else + // If we do want to use FULL for BACKGROUND updates + refresh = FULL; + reason = FLAGGED_BACKGROUND; + LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n"); +#endif + } + + // Sanity: confirm that we did ask for a RESPONSIVE frame. + if (frameFlags & RESPONSIVE) { + refresh = FAST; + reason = NO_OBJECTIONS; + LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu\n", fastRefreshCount); + } +} + +// Reset the timer used for rate-limiting +void EInkDynamicDisplay::resetRateLimiting() +{ + previousRunMs = millis(); +} + +// Generate a hash of this frame, to compare against previous update +void EInkDynamicDisplay::hashImage() +{ + imageHash = 0; + + // Sum all bytes of the image buffer together + for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { + imageHash += buffer[b]; + } +} + +// Store the results of determineMode() for future use, and reset for next call +void EInkDynamicDisplay::storeAndReset() +{ + previousRefresh = refresh; + previousReason = reason; + + // Only store image hash if the display will update + if (refresh != SKIPPED) { + previousImageHash = imageHash; + } + + frameFlags = BACKGROUND; + refresh = UNSPECIFIED; +} + +#ifdef EINK_LIMIT_GHOSTING_PX +// Count how many ghost pixels the new image will display +void EInkDynamicDisplay::countGhostPixels() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // Start a new count + ghostPixelCount = 0; + + // Check new image, bit by bit, for any white pixels at locations marked "dirty" + for (uint16_t i = 0; i < displayBufferSize; i++) { + for (uint8_t bit = 0; bit < 7; bit++) { + + const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh? + const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image? + + // If pixel is (or has been) black since last full-refresh, and now is white: ghosting + if (dirty && shouldBeBlank) + ghostPixelCount++; + + // Update the dirty status for this pixel - will this location become a ghost if set white in future? + if (!dirty && !shouldBeBlank) + dirtyPixels[i] |= (1 << bit); + } + } + + LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount); +} + +// Check if ghost pixel count exceeds the defined limit +void EInkDynamicDisplay::checkExcessiveGhosting() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + countGhostPixels(); + + // If too many ghost pixels, select full refresh + if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) { + refresh = FULL; + reason = EXCEEDED_GHOSTINGLIMIT; + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT\n"); + } +} + +// Clear the dirty pixels array. Call when full-refresh cleans the display. +void EInkDynamicDisplay::resetGhostPixelTracking() +{ + // Copy the current frame into dirtyPixels[] from the display buffer + memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize); +} +#endif // EINK_LIMIT_GHOSTING_PX + +#endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h new file mode 100644 index 000000000..2880c716b --- /dev/null +++ b/src/graphics/EInkDynamicDisplay.h @@ -0,0 +1,104 @@ +#pragma once + +#include "configuration.h" + +#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) + +#include "EInkDisplay2.h" +#include "GxEPD2_BW.h" + +/* + Derives from the EInkDisplay adapter class. + Accepts suggestions from Screen class about frame type. + Determines which refresh type is most suitable. + (Full, Fast, Skip) +*/ + +class EInkDynamicDisplay : public EInkDisplay +{ + public: + // Constructor + // ( Parameters unused, passed to EInkDisplay. Maintains compatibility OLEDDisplay class ) + EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus); + ~EInkDynamicDisplay(); + + // What kind of frame is this + enum frameFlagTypes : uint8_t { + BACKGROUND = (1 << 0), // For frames via display() + RESPONSIVE = (1 << 1), // For frames via forceDisplay() + COSMETIC = (1 << 2), // For splashes + DEMAND_FAST = (1 << 3), // Special case only + }; + void setFrameFlag(frameFlagTypes flag); + + // Set the correct frame flag, then call universal "update()" method + void display() override; + bool forceDisplay(uint32_t msecLimit) override; // Shadows base class. Parameter and return val unused. + + protected: + enum refreshTypes : uint8_t { // Which refresh operation will be used + UNSPECIFIED, + FULL, + FAST, + SKIPPED, + }; + enum reasonTypes : uint8_t { // How was the decision reached + NO_OBJECTIONS, + EXCEEDED_RATELIMIT_FAST, + EXCEEDED_RATELIMIT_FULL, + FLAGGED_COSMETIC, + FLAGGED_DEMAND_FAST, + EXCEEDED_LIMIT_FASTREFRESH, + EXCEEDED_GHOSTINGLIMIT, + FRAME_MATCHED_PREVIOUS, + BACKGROUND_USES_FAST, + FLAGGED_BACKGROUND, + REDRAW_WITH_FULL, + }; + + void configForFastRefresh(); // GxEPD2 code to set fast-refresh + void configForFullRefresh(); // GxEPD2 code to set full-refresh + bool determineMode(); // Assess situation, pick a refresh type + void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type + void adjustRefreshCounters(); // Update fastRefreshCount + bool update(); // Trigger the display update - determine mode, then call base class + + // Checks as part of determineMode() + void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC? + void checkRateLimiting(); // Is this frame too soon? + void checkCosmetic(); // Was the COSMETIC flag set? + void checkDemandingFast(); // Was the DEMAND_FAST flag set? + void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively? + void checkFrameMatchesPrevious(); // Does the new frame match the existing display image? + void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND? + + void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting + void hashImage(); // Generate a hashed version of this frame, to compare against previous update + void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call + + // What we are determining for this frame + frameFlagTypes frameFlags = BACKGROUND; // Frame type(s) - determineMode() input + refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output + reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used + + // What happened last time determineMode() ran + refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome + reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason + + uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting) + uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed! + uint32_t previousImageHash = 0; // Hash of the previous update's frame + uint32_t fastRefreshCount = 0; // How many fast-refreshes consecutively since last full refresh? + refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for + + // Optional - track ghosting, pixel by pixel +#ifdef EINK_LIMIT_GHOSTING_PX + void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh + void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit + void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display. + uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) + uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use +#endif +}; + +#endif \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index c92877d41..33df78462 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -898,9 +898,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif defined(USE_EINK) +#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) dispdev = new EInkDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) + dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_ST7567) dispdev = new ST7567Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index baee4b140..69e858dd2 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -47,6 +47,7 @@ class Screen #endif #include "EInkDisplay2.h" +#include "EInkDynamicDisplay.h" #include "TFTDisplay.h" #include "TypedQueue.h" #include "commands.h" diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 01b68f5f0..4e5e291e0 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -8,6 +8,12 @@ build_flags = -D EINK_DISPLAY_MODEL=GxEPD2_213_BN -D EINK_WIDTH=250 -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2/ From 5865add857faff2f4e08d1a12059a77f4bfe55ca Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 4 Mar 2024 02:13:56 +1300 Subject: [PATCH 0034/1377] E-Ink: fast refresh for Wireless Paper V1.1 (#3320) --- src/graphics/EInkDisplay2.cpp | 15 +-------------- src/graphics/EInkDisplay2.h | 2 +- variants/heltec_wireless_paper/platformio.ini | 8 +++++++- variants/heltec_wireless_paper/variant.h | 2 ++ 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index d790e30c1..aee30c7f8 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -158,7 +158,7 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) { // Is this a normal boot, or a wake from deep sleep? esp_sleep_wakeup_cause_t wakeReason = esp_sleep_get_wakeup_cause(); @@ -194,19 +194,6 @@ bool EInkDisplay::connect() adafruitDisplay->init(); adafruitDisplay->setRotation(3); } -#elif defined(HELTEC_WIRELESS_PAPER) - { - hspi = new SPIClass(HSPI); - hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS - delay(100); - pinMode(Vext, OUTPUT); - digitalWrite(Vext, LOW); - delay(100); - auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); - adafruitDisplay = new GxEPD2_BW(*lowLevel); - adafruitDisplay->init(); - adafruitDisplay->setRotation(3); - } #elif defined(PCA10059) { auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 369378132..75770a3bc 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -5,7 +5,7 @@ #include "GxEPD2_BW.h" #include -#if defined(HELTEC_WIRELESS_PAPER_V1_0) +#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) // Re-enable SPI after deep sleep: rtc_gpio_hold_dis() #include "driver/rtc_io.h" #endif diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 56446dc8b..0abbe085e 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -8,9 +8,15 @@ build_flags = -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 -D EINK_WIDTH=250 -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/ixt/GxEPD2#39f325b677713eb04dfcc83b8e402e77523fb8bf + https://github.com/meshtastic/GxEPD2 adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index c2a030ed0..28bc8628a 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -5,6 +5,8 @@ #define I2C_SCL SCL #define USE_EINK +#define EINK_NO_HIBERNATE + /* * eink display pins */ From 495840c777d1b31bad92263da6c1b1c29b4bcef0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 3 Mar 2024 08:36:36 -0600 Subject: [PATCH 0035/1377] Filter out neighborinfo if we don't have the module enabled (#3314) * Filter out neighborinfo if we don't have the module enabled * Handlereceived instead * Add debug message --- src/mesh/Router.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 4a6dc9007..1d6a2d96b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -435,6 +435,7 @@ NodeNum Router::getNodeNum() */ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) { + bool skipHandle = false; // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone // Store a copy of encrypted packet for MQTT @@ -451,8 +452,17 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) else printPacket("handleReceived(REMOTE)", p); + // Neighbor info module is disabled, ignore expensive neighbor info packets + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP && + (!moduleConfig.has_neighbor_info || !moduleConfig.neighbor_info.enabled)) { + LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet\n"); + cancelSending(p->from, p->id); + skipHandle = true; + } + // Publish received message to MQTT if we're not the original transmitter of the packet - if (moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt) + if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); } else { printPacket("packet decoding failed or skipped (no PSK?)", p); @@ -461,7 +471,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) packetPool.release(p_encrypted); // Release the encrypted packet // call modules here - MeshModule::callPlugins(*p, src); + if (!skipHandle) + MeshModule::callPlugins(*p, src); } void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) From 9b3e519487ed421a29112bcd047096d2cd3131a5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 3 Mar 2024 08:55:52 -0600 Subject: [PATCH 0036/1377] Revert "Fix LED pinout for T-Echo board marked v1.0" (#3304) * Revert "Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 (#3051)" This reverts commit c2afa879b879371d7f19d5b4c088f4ba66ab8ecd. * Remove / comment out unused LED pins --- variants/t-echo/variant.h | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 345091c2f..dffa08698 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -43,16 +43,12 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (0 + 14) // blue (confirmed on boards marked v1.0, date 2021-6-28) -#define PIN_LED2 (32 + 1) // green -#define PIN_LED3 (32 + 3) // red +#define PIN_LED1 (0 + 14) // 13 red (confirmed on 1.0 board) +// Unused(by firmware) LEDs: +// #define PIN_LED2 (0 + 15) // 14 blue +// #define PIN_LED3 (0 + 13) // 15 green -#define LED_RED PIN_LED3 -#define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 - -#define LED_BUILTIN LED_BLUE -#define LED_CONN PIN_GREEN +#define LED_BUILTIN PIN_LED1 #define LED_STATE_ON 0 // State when LED is lit #define LED_INVERTED 1 @@ -232,4 +228,4 @@ External serial flash WP25R1635FZUIL0 * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif From 6dbb6583ef50e8b3d5a297ac50046a139eafb485 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 3 Mar 2024 09:33:18 -0600 Subject: [PATCH 0037/1377] Put these back --- variants/t-echo/variant.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index dffa08698..71795dce0 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -45,8 +45,8 @@ extern "C" { // LEDs #define PIN_LED1 (0 + 14) // 13 red (confirmed on 1.0 board) // Unused(by firmware) LEDs: -// #define PIN_LED2 (0 + 15) // 14 blue -// #define PIN_LED3 (0 + 13) // 15 green +#define PIN_LED2 (0 + 15) // 14 blue +#define PIN_LED3 (0 + 13) // 15 green #define LED_BUILTIN PIN_LED1 @@ -228,4 +228,4 @@ External serial flash WP25R1635FZUIL0 * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file From e3063a278506beab50e62d57e61538282f269bf3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 3 Mar 2024 09:46:36 -0600 Subject: [PATCH 0038/1377] Turns out bluefruit uses some of these macros even though "we" don't :-/ --- variants/t-echo/variant.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 71795dce0..1af68863e 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -48,7 +48,12 @@ extern "C" { #define PIN_LED2 (0 + 15) // 14 blue #define PIN_LED3 (0 + 13) // 15 green -#define LED_BUILTIN PIN_LED1 +#define LED_RED PIN_LED3 +#define LED_BLUE PIN_LED1 +#define LED_GREEN PIN_LED2 + +#define LED_BUILTIN LED_BLUE +#define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit #define LED_INVERTED 1 From 3c3d3910446cd85784693aca3bd56cfd765025ac Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 3 Mar 2024 10:33:30 -0600 Subject: [PATCH 0039/1377] Remove rangetest file on factory reset (#3322) --- src/mesh/NodeDB.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 506adda5c..dc8d7540c 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -130,6 +130,9 @@ bool NodeDB::factoryReset() LOG_INFO("Performing factory reset!\n"); // first, remove the "/prefs" (this removes most prefs) rmDir("/prefs"); + if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) { + LOG_WARN("Could not remove rangetest.csv file\n"); + } // second, install default state (this will deal with the duplicate mac address issue) installDefaultDeviceState(); installDefaultConfig(); From 7ab9a94edbb62f709599050a07ec13f25140fb36 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 3 Mar 2024 11:19:30 -0600 Subject: [PATCH 0040/1377] just off the coast of NULL Island isn't OK either. --- src/modules/PositionModule.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index addff8966..4634f8fef 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -116,6 +116,11 @@ meshtastic_MeshPacket *PositionModule::allocReply() } localPosition.seq_number++; + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { + LOG_WARN("Skipping position send because lat/lon are zero!\n"); + return nullptr; + } + // lat/lon are unconditionally included - IF AVAILABLE! LOG_DEBUG("Sending location with precision %i\n", precision); if (precision < 32 && precision > 0) { @@ -176,11 +181,6 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_INFO("Providing time to mesh %u\n", p.time); } - if (p.latitude_i == 0 && p.longitude_i == 0) { - LOG_WARN("Skipping position send because lat/lon are zero!\n"); - return nullptr; - } - LOG_INFO("Position reply: time=%i, latI=%i, lonI=%i\n", p.time, p.latitude_i, p.longitude_i); // TAK Tracker devices should send their position in a TAK packet over the ATAK port From e5bf07d4fbeba7c914288ed5774f9bb74b8dc8ce Mon Sep 17 00:00:00 2001 From: Ken McGuire Date: Sun, 3 Mar 2024 12:08:47 -0700 Subject: [PATCH 0041/1377] Fix for issue #3310 (#3327) * Portduino multiple logging levels * Fixes based on GPSFan work * Fix derped logic * Correct size field for AID message * Reformat to add comments, beginning of GPS rework * Update PM2 message for Neo-6 * Correct ECO mode logic as ECO mode is only for Neo-6 * Cleanup ubx.h add a few more comments * GPS rework, changes for M8 and stub for M10 * Add VALSET commands for u-blox M10 receivers * Add VALSET commands for u-blox M10 receivers tweak M8 commands add comments for VALSET configuration commands * Add commands to init M10 receivers, tweak the M8 init sequence, this is a WIP as there are still some issues during init. Add M10 version of PMREQ. * Add wakeup source of uartrx to PMREQ_10 The M10 does not respond to commands when asleep, may need to do this for the M8 as well * Enable NMEA messages on USB port. Normally, it is a good idea to disable messages on unused ports. Native Linux needs to be able to use GNSS modules connected via via either serial or USB. In the future I2C connections may be required, but are not enabled for now. * Save the config for all u-blox receiver types. The M10 supports this command in addition to saving using the VALSET commands for the RAM & BBR layers. --------- Co-authored-by: Jonathan Bennett Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 2bdb8e176..849c38794 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -476,13 +476,6 @@ bool GPS::setup() } } - msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to save GNSS module configuration.\n"); - } else { - LOG_INFO("GNSS module configuration saved!\n"); - } } else { // LOG_INFO("u-blox M10 hardware found.\n"); delay(1000); @@ -575,6 +568,13 @@ bool GPS::setup() // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. } + msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); + _serial_gps->write(UBXscratch, msglen); + if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { + LOG_WARN("Unable to save GNSS module configuration.\n"); + } else { + LOG_INFO("GNSS module configuration saved!\n"); + } } didSerialInit = true; } From 72050530f119853707192b78b999552224c1bf20 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 3 Mar 2024 13:56:55 -0600 Subject: [PATCH 0042/1377] NRF52 bluetooth cleanup and fix (#3328) * NRF52 bluetooth cleanup. Fixes BLE not returning after serial PhoneAPI connection * Use new var name in esp32 arch --- src/nimble/NimbleBluetooth.h | 4 ++-- src/platform/esp32/main-esp32.cpp | 8 +++---- src/platform/nrf52/NRF52Bluetooth.cpp | 12 +++++++++- src/platform/nrf52/NRF52Bluetooth.h | 3 ++- src/platform/nrf52/main-nrf52.cpp | 29 ++++++++++++------------ src/platform/portduino/PortduinoGlue.cpp | 2 +- src/platform/rp2040/main-rp2040.cpp | 2 +- src/platform/stm32wl/main-stm32wl.cpp | 4 ++-- src/target_specific.h | 2 +- 9 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 4080a7cbc..df2d3e45a 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -16,5 +16,5 @@ class NimbleBluetooth : BluetoothApi void startAdvertising(); }; -void setBluetoothEnable(bool on); -void clearNVS(); +void setBluetoothEnable(bool enable); +void clearNVS(); \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index c994eea48..f97f6d121 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -20,21 +20,21 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { if (!isWifiAvailable() && config.bluetooth.enabled == true) { if (!nimbleBluetooth) { nimbleBluetooth = new NimbleBluetooth(); } - if (on && !nimbleBluetooth->isActive()) { + if (enable && !nimbleBluetooth->isActive()) { nimbleBluetooth->setup(); - } else if (!on) { + } else if (!enable) { nimbleBluetooth->shutdown(); } } } #else -void setBluetoothEnable(bool on) {} +void setBluetoothEnable(bool enable) {} void updateBatteryLevel(uint8_t level) {} #endif diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index dd81929c8..9a93f5cc6 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -210,8 +210,10 @@ void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth\n"); + if (connectionHandle != 0) { + Bluefruit.disconnect(connectionHandle); + } Bluefruit.Advertising.stop(); - Bluefruit.setTxPower(0); // Minimum power } bool NRF52Bluetooth::isConnected() @@ -289,6 +291,14 @@ void NRF52Bluetooth::setup() } } +void NRF52Bluetooth::resumeAdverising() +{ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // 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) { diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 193e86cf8..fd27bbf04 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -8,6 +8,7 @@ class NRF52Bluetooth : BluetoothApi public: void setup(); void shutdown(); + void resumeAdverising(); void clearBonds(); bool isConnected(); int getRssi(); @@ -17,4 +18,4 @@ class NRF52Bluetooth : BluetoothApi void convertToUint8(uint8_t target[4], uint32_t source); static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request); static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status); -}; +}; \ No newline at end of file diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 63b014c46..9e8798e37 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -63,28 +63,29 @@ static void initBrownout() // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice } -static bool bleOn = false; static const bool useSoftDevice = true; // Set to false for easier debugging -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { - if (on != bleOn && config.bluetooth.enabled == true) { - if (on) { + if (enable && config.bluetooth.enabled) { + if (!useSoftDevice) { + LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); + } else { if (!nrf52Bluetooth) { - if (!useSoftDevice) - LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); - else { - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); + LOG_DEBUG("Initializing NRF52 Bluetooth\n"); + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); - } + // We delay brownout init until after BLE because BLE starts soft device + initBrownout(); + } else { + nrf52Bluetooth->resumeAdverising(); } - } else if (nrf52Bluetooth) { + } + } else { + if (nrf52Bluetooth) { nrf52Bluetooth->shutdown(); } - bleOn = on; } } diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index c8fcc3d13..046509fab 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -20,7 +20,7 @@ std::map settingsStrings; char *configPath = nullptr; // FIXME - move setBluetoothEnable into a HALPlatform class -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { // not needed } diff --git a/src/platform/rp2040/main-rp2040.cpp b/src/platform/rp2040/main-rp2040.cpp index 3359263e9..283b801f1 100644 --- a/src/platform/rp2040/main-rp2040.cpp +++ b/src/platform/rp2040/main-rp2040.cpp @@ -2,7 +2,7 @@ #include #include -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { // not needed } diff --git a/src/platform/stm32wl/main-stm32wl.cpp b/src/platform/stm32wl/main-stm32wl.cpp index f57928c60..60c3cce10 100644 --- a/src/platform/stm32wl/main-stm32wl.cpp +++ b/src/platform/stm32wl/main-stm32wl.cpp @@ -3,7 +3,7 @@ #include #include -void setBluetoothEnable(bool on) {} +void setBluetoothEnable(bool enable) {} void playStartMelody() {} @@ -33,4 +33,4 @@ int _gettimeofday(struct timeval *tv, void *tzvp) { return -1; } -} +} \ No newline at end of file diff --git a/src/target_specific.h b/src/target_specific.h index 1e79df510..7404a3720 100644 --- a/src/target_specific.h +++ b/src/target_specific.h @@ -5,6 +5,6 @@ // Functions that are unique to particular target types (esp32, bare, nrf52 etc...) // Enable/disable bluetooth. -void setBluetoothEnable(bool on); +void setBluetoothEnable(bool enable); void getMacAddr(uint8_t *dmac); \ No newline at end of file From f5ff77c2b94c5aaea4dc049baefc158fede7dd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 5 Mar 2024 14:50:52 +0100 Subject: [PATCH 0043/1377] Turn off certain modules not wanted in custom builds (#3337) --- src/configuration.h | 20 +++++++++++- src/modules/Modules.cpp | 72 ++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index d8b0dba5f..03170c1c7 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -221,4 +221,22 @@ along with this program. If not, see . #ifndef HW_VENDOR #error HW_VENDOR must be defined -#endif \ No newline at end of file +#endif + +// global switch to turn off all optional modules for a minimzed build +#ifdef MESHTASTIC_EXCLUDE_MODULES +#define MESHTASTIC_EXCLUDE_AUDIO 1 +#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1 +#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1 +#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1 +#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1 +#define MESHTASTIC_EXCLUDE_POWER_TELEMETRY 1 +#define MESHTASTIC_EXCLUDE_RANGETEST 1 +#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1 +#define MESHTASTIC_EXCLUDE_STOREFORWARD 1 +#define MESHTASTIC_EXCLUDE_ATAK 1 +#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1 +#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1 +#define MESHTASTIC_EXCLUDE_TRACEROUTE 1 +#define MESHTASTIC_EXCLUDE_WAYPOINT 1 +#endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 2d45868fd..4f0b8f2b0 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,74 +1,112 @@ #include "configuration.h" +#if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "input/InputBroker.h" #include "input/RotaryEncoderInterruptImpl1.h" #include "input/TrackballInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h" #include "input/cardKbI2cImpl.h" #include "input/kbMatrixImpl.h" +#endif #include "modules/AdminModule.h" +#if !MESHTASTIC_EXCLUDE_ATAK #include "modules/AtakPluginModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_CANNEDMESSAGES #include "modules/CannedMessageModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR #include "modules/DetectionSensorModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO #include "modules/NeighborInfoModule.h" +#endif #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" +#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE #include "modules/RemoteHardwareModule.h" +#endif #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" +#if !MESHTASTIC_EXCLUDE_TRACEROUTE #include "modules/TraceRouteModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_WAYPOINT #include "modules/WaypointModule.h" +#endif #if ARCH_PORTDUINO #include "input/LinuxInputImpl.h" #endif #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" #endif -#if HAS_SENSOR +#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" #endif #ifdef ARCH_ESP32 -#ifdef USE_SX1280 +#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO #include "modules/esp32/AudioModule.h" #endif +#if !MESHTASTIC_EXCLUDE_PAXCOUNTER #include "modules/esp32/PaxcounterModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/esp32/StoreForwardModule.h" #endif +#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) +#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #include "modules/ExternalNotificationModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_RANGETEST #include "modules/RangeTestModule.h" +#endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) +#if !MESHTASTIC_EXCLUDE_SERIAL #include "modules/SerialModule.h" #endif #endif +#endif /** * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) */ void setupModules() { if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { -#if HAS_BUTTON || ARCH_PORTDUINO +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER inputBroker = new InputBroker(); #endif adminModule = new AdminModule(); nodeInfoModule = new NodeInfoModule(); positionModule = new PositionModule(); +#if !MESHTASTIC_EXCLUDE_WAYPOINT waypointModule = new WaypointModule(); +#endif textMessageModule = new TextMessageModule(); +#if !MESHTASTIC_EXCLUDE_TRACEROUTE traceRouteModule = new TraceRouteModule(); +#endif +#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO neighborInfoModule = new NeighborInfoModule(); +#endif +#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR detectionSensorModule = new DetectionSensorModule(); +#endif +#if !MESHTASTIC_EXCLUDE_ATAK atakPluginModule = new AtakPluginModule(); +#endif // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable. +#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE new RemoteHardwareModule(); +#endif // Example: Put your module here // new ReplyModule(); -#if HAS_BUTTON || ARCH_PORTDUINO +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { delete rotaryEncoderInterruptImpl1; @@ -90,47 +128,59 @@ void setupModules() aLinuxInputImpl = new LinuxInputImpl(); aLinuxInputImpl->init(); #endif -#if HAS_TRACKBALL +#if HAS_TRACKBALL && !MESHTASTIC_EXCLUDE_INPUTBROKER trackballInterruptImpl1 = new TrackballInterruptImpl1(); trackballInterruptImpl1->init(); #endif -#if HAS_SCREEN +#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES cannedMessageModule = new CannedMessageModule(); #endif -#if HAS_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) new DeviceTelemetryModule(); #endif -#if HAS_SENSOR +#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR new EnvironmentTelemetryModule(); if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY new PowerTelemetryModule(); #endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !MESHTASTIC_EXCLUDE_SERIAL new SerialModule(); #endif +#endif #ifdef ARCH_ESP32 // Only run on an esp32 based device. -#ifdef USE_SX1280 +#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO audioModule = new AudioModule(); #endif +#if !MESHTASTIC_EXCLUDE_STOREFORWARD storeForwardModule = new StoreForwardModule(); +#endif +#if !MESHTASTIC_EXCLUDE_PAXCOUNTER paxcounterModule = new PaxcounterModule(); #endif +#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) +#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION externalNotificationModule = new ExternalNotificationModule(); +#endif +#if !MESHTASTIC_EXCLUDE_RANGETEST new RangeTestModule(); +#endif #endif } else { adminModule = new AdminModule(); #if HAS_TELEMETRY new DeviceTelemetryModule(); #endif +#if !MESHTASTIC_EXCLUDE_TRACEROUTE traceRouteModule = new TraceRouteModule(); +#endif } // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks From 9d37a8d17fcebfee819986b69104e820ba521994 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 6 Mar 2024 13:09:46 -0600 Subject: [PATCH 0044/1377] Stop Fiddling with Newlines! (#3341) --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 03922dc72..e86d31c7d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "editor.formatOnSave": true, "editor.defaultFormatter": "trunk.io", - "trunk.enableWindows": true + "trunk.enableWindows": true, + "files.insertFinalNewline": false, + "files.trimFinalNewlines": false } From e174328de36bc9a989986cf7a1d8b59802ec5e29 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 6 Mar 2024 16:23:04 -0600 Subject: [PATCH 0045/1377] Native Webserver (#3343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added WebServer/WebServices for Native Linux Meshtastic and web gui * Fix bug in login functionality * Added customized config of portdunio.ini with LovyannGFX from marelab repro * Compile Problem resolved with developer version of LovyanGFX.git * Compile against dev version * Fixes to fit into main branch * Update variant.h, main.cpp, .gitignore, WebServer.cpp, esp32s2.ini, WebServer.h, ContentHandler.cpp, rp2040.ini, nrf52.ini, ContentHelper.cpp, Dockerfile, ContentHandler.h, esp32.ini, stm32wl5e.ini * Added linux pi std /usr/include dir * Adding /usr/innclude for Linux compile against native libs that are not hadled by platformio * Review log level changes & translation * Update Dockerfile * Fix Typo & VFS ref. Part1 * Fix Typo & VFS ref. * Dev Version for ulfius web lib * Update platformio.ini * Free VFS path string * Remove unintended changes * More unintentional changes * Make the HTTP server optional on native * Tune-up for Native web defaults * Don't modify build system yet * Remove more unneeded changes --------- Co-authored-by: marc hammermann Co-authored-by: Ben Meadors Co-authored-by: Thomas Göttgens --- .gitignore | 2 + arch/esp32/esp32.ini | 2 +- arch/esp32/esp32s2.ini | 5 +- arch/nrf52/nrf52.ini | 2 +- arch/portduino/portduino.ini | 1 + arch/rp2040/rp2040.ini | 2 +- arch/stm32/stm32wl5e.ini | 2 +- bin/config-dist.yaml | 4 + src/main.cpp | 6 + src/mesh/raspihttp/PiWebServer.cpp | 530 +++++++++++++++++++++++ src/mesh/raspihttp/PiWebServer.h | 61 +++ src/platform/portduino/PortduinoGlue.cpp | 5 + src/platform/portduino/PortduinoGlue.h | 5 +- variants/portduino/platformio.ini | 8 +- 14 files changed, 625 insertions(+), 10 deletions(-) create mode 100644 src/mesh/raspihttp/PiWebServer.cpp create mode 100644 src/mesh/raspihttp/PiWebServer.h diff --git a/.gitignore b/.gitignore index 89f8ee065..0f2202f8d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ venv/ release/ .vscode/extensions.json /compile_commands.json +src/mesh/raspihttp/certificate.pem +src/mesh/raspihttp/private_key.pem \ No newline at end of file diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index bf84dd939..39935b849 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -4,7 +4,7 @@ extends = arduino_base platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch. build_src_filter = - ${arduino_base.build_src_filter} - - - - + ${arduino_base.build_src_filter} - - - - - upload_speed = 921600 debug_init_break = tbreak setup diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini index 3bde3465a..5de0fa549 100644 --- a/arch/esp32/esp32s2.ini +++ b/arch/esp32/esp32s2.ini @@ -2,7 +2,7 @@ extends = esp32_base build_src_filter = - ${esp32_base.build_src_filter} - + ${esp32_base.build_src_filter} - - monitor_speed = 115200 @@ -12,5 +12,4 @@ build_flags = lib_ignore = ${esp32_base.lib_ignore} - NimBLE-Arduino - + NimBLE-Arduino \ No newline at end of file diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 04ca89a54..5155eaadc 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -11,7 +11,7 @@ build_flags = -Isrc/platform/nrf52 build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - lib_deps= ${arduino_base.lib_deps} diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 0dcc9afc2..368fb5d0e 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -12,6 +12,7 @@ build_src_filter = - - - + + - - - diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini index 48fe0dae6..edc4373ad 100644 --- a/arch/rp2040/rp2040.ini +++ b/arch/rp2040/rp2040.ini @@ -12,7 +12,7 @@ build_flags = -D__PLAT_RP2040__ # -D _POSIX_THREADS build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini index 4483ff526..4d74ade8f 100644 --- a/arch/stm32/stm32wl5e.ini +++ b/arch/stm32/stm32wl5e.ini @@ -13,7 +13,7 @@ build_flags = -DVECT_TAB_OFFSET=0x08000000 build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - board_upload.offset_address = 0x08000000 upload_protocol = stlink diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index b5b105e4c..a241a929a 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -117,3 +117,7 @@ Input: Logging: LogLevel: info # debug, info, warn, error + +Webserver: +# Port: 443 # Port for Webserver & Webservices +# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer diff --git a/src/main.cpp b/src/main.cpp index fbfb983d2..3619b0053 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,6 +68,7 @@ NRF52Bluetooth *nrf52Bluetooth; #ifdef ARCH_PORTDUINO #include "linux/LinuxHardwareI2C.h" +#include "mesh/raspihttp/PiWebServer.h" #include "platform/portduino/PortduinoGlue.h" #include #include @@ -857,6 +858,11 @@ void setup() #endif #ifdef ARCH_PORTDUINO +#if __has_include() + if (settingsMap[webserverport] != -1) { + piwebServerThread = new PiWebServerThread(); + } +#endif initApiServer(TCPPort); #endif diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp new file mode 100644 index 000000000..41f6727a4 --- /dev/null +++ b/src/mesh/raspihttp/PiWebServer.cpp @@ -0,0 +1,530 @@ +/* +Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices +runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32 +Webserver libs including generation of SSL certifcicates, because the use ESP specific details in +the lib that can't be emulated. + +The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio" +The WebServer just adds basaic support to deliver WebContent, so it can be used to +deliver the WebGui definded by the WebClient Project. + +Steps to get it running: +1.) Add these Linux Libs to the compile and target machine: + + sudo apt update && \ + apt -y install openssl libssl-dev libopenssl libsdl2-dev \ + libulfius-dev liborcania-dev + +2.) Configure the root directory of the web Content in the config.yaml file. + The followinng tags should be included and set at your needs + + Example entry in the config.yaml + Webserver: + Port: 9001 # Port for Webserver & Webservices + RootPath: /home/marc/web # Root Dir of WebServer + +3.) Checkout the web project + https://github.com/meshtastic/web.git + + Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath" + +!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!! + +Author: Marc Philipp Hammermann +mail: marchammermann@googlemail.com + +*/ +#ifdef PORTDUINO_LINUX_HARDWARE +#if __has_include() +#include "PiWebServer.h" +#include "NodeDB.h" +#include "PhoneAPI.h" +#include "PowerFSM.h" +#include "RadioLibInterface.h" +#include "airtime.h" +#include "graphics/Screen.h" +#include "main.h" +#include "mesh/wifi/WiFiAPClient.h" +#include "sleep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "PortduinoFS.h" +#include "platform/portduino/PortduinoGlue.h" + +#define DEFAULT_REALM "default_realm" +#define PREFIX "" + +struct _file_config configWeb; + +// We need to specify some content-type mapping, so the resources get delivered with the +// right content type and are displayed correctly in the browser +char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"}, + {".js", "text/javascript"}, {".png", "image/png"}, + {".jpg", "image/jpg"}, {".gz", "application/gzip"}, + {".gif", "image/gif"}, {".json", "application/json"}, + {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"}, + {".svg", "image/svg+xml"}, {".ts", "text/javascript"}, + {".tsx", "text/javascript"}, {"", ""}}; + +#undef str + +volatile bool isWebServerReady; +volatile bool isCertReady; + +HttpAPI webAPI; + +PiWebServerThread *piwebServerThread; + +/** + * Return the filename extension + */ +const char *get_filename_ext(const char *path) +{ + const char *dot = strrchr(path, '.'); + if (!dot || dot == path) + return "*"; + if (strchr(dot, '?') != NULL) { + //*strchr(dot, '?') = '\0'; + const char *empty = "\0"; + return empty; + } + return dot; +} + +/** + * Streaming callback function to ease sending large files + */ +static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max) +{ + (void)(pos); + if (cls != NULL) { + return fread(buf, 1, max, (FILE *)cls); + } else { + return U_STREAM_END; + } +} + +/** + * Cleanup FILE* structure when streaming is complete + */ +static void callback_static_file_stream_free(void *cls) +{ + if (cls != NULL) { + fclose((FILE *)cls); + } +} + +/** + * static file callback endpoint that delivers the content for WebServer calls + */ +int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data) +{ + size_t length; + FILE *f; + char *file_requested, *file_path, *url_dup_save, *real_path = NULL; + const char *content_type; + + /* + * Comment this if statement if you don't access static files url from root dir, like /app + */ + if (request->callback_position > 0) { + return U_CALLBACK_CONTINUE; + } else if (user_data != NULL && (configWeb.files_path != NULL)) { + file_requested = o_strdup(request->http_url); + url_dup_save = file_requested; + + while (file_requested[0] == '/') { + file_requested++; + } + file_requested += o_strlen(configWeb.url_prefix); + while (file_requested[0] == '/') { + file_requested++; + } + + if (strchr(file_requested, '#') != NULL) { + *strchr(file_requested, '#') = '\0'; + } + + if (strchr(file_requested, '?') != NULL) { + *strchr(file_requested, '?') = '\0'; + } + + if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) { + o_free(url_dup_save); + url_dup_save = file_requested = o_strdup("index.html"); + } + + file_path = msprintf("%s/%s", configWeb.files_path, file_requested); + real_path = realpath(file_path, NULL); + if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) { + if (access(file_path, F_OK) != -1) { + f = fopen(file_path, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + + content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested)); + if (content_type == NULL) { + content_type = u_map_get(&configWeb.mime_types, "*"); + LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested)); + } + u_map_put(response->map_header, "Content-Type", content_type); + u_map_copy_into(response->map_header, &configWeb.map_header); + + if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free, + length, STATIC_FILE_CHUNK, f) != U_OK) { + LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n "); + } + } + } else { + if (configWeb.redirect_on_404 == NULL) { + ulfius_set_string_body_response(response, 404, "File not found"); + } else { + ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404); + response->status = 302; + } + } + } else { + if (configWeb.redirect_on_404 == NULL) { + ulfius_set_string_body_response(response, 404, "File not found"); + } else { + ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404); + response->status = 302; + } + } + + o_free(file_path); + o_free(url_dup_save); + free(real_path); // realpath uses malloc + return U_CALLBACK_CONTINUE; + } else { + LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n"); + return U_CALLBACK_ERROR; + } +} + +static void handleWebResponse() {} + +/* + * Adapt the radioapi to the Webservice handleAPIv1ToRadio + * Trigger : WebGui(SAVE)->WebServcice->phoneApi + */ +int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data) +{ + LOG_DEBUG("handleAPIv1ToRadio web -> radio \n"); + + ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS"); + ulfius_add_header_to_response(res, "X-Protobuf-Schema", + "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + + if (req->http_verb == "OPTIONS") { + ulfius_set_response_properties(res, U_OPT_STATUS, 204); + return U_CALLBACK_CONTINUE; + } + + byte buffer[MAX_TO_FROM_RADIO_SIZE]; + size_t s = req->binary_body_length; + + memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE); + + // FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread + + portduinoVFS->mountpoint("/home/marc/.portduino/default"); + + LOG_DEBUG("Received %d bytes from PUT request\n", s); + webAPI.handleToRadio(buffer, s); + LOG_DEBUG("end web->radio \n"); + return U_CALLBACK_COMPLETE; +} + +/* + * Adapt the radioapi to the Webservice handleAPIv1FromRadio + * Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events + */ +int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data) +{ + + // LOG_DEBUG("handleAPIv1FromRadio radio -> web\n"); + std::string valueAll; + + // Status code is 200 OK by default. + ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET"); + ulfius_add_header_to_response(res, "X-Protobuf-Schema", + "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + + uint8_t txBuf[MAX_STREAM_BUF_SIZE]; + uint32_t len = 1; + + if (valueAll == "true") { + while (len) { + len = webAPI.getFromRadio(txBuf); + ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len); + const char *tmpa = (const char *)txBuf; + ulfius_set_string_body_response(res, 200, tmpa); + // LOG_DEBUG("\n----webAPI response all:----\n"); + LOG_DEBUG(tmpa); + LOG_DEBUG("\n"); + } + // Otherwise, just return one protobuf + } else { + len = webAPI.getFromRadio(txBuf); + const char *tmpa = (const char *)txBuf; + ulfius_set_binary_body_response(res, 200, tmpa, len); + // LOG_DEBUG("\n----webAPI response:\n"); + LOG_DEBUG(tmpa); + LOG_DEBUG("\n"); + } + + // LOG_DEBUG("end radio->web\n", len); + return U_CALLBACK_COMPLETE; +} + +/* +OpenSSL RSA Key Gen +*/ +int generate_rsa_key(EVP_PKEY **pkey) +{ + EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!pkey_ctx) + return -1; + if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) + return -1; + if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0) + return -1; + if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0) + return -1; + EVP_PKEY_CTX_free(pkey_ctx); + return 0; // SUCCESS +} + +int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509) +{ + *x509 = X509_new(); + if (!*x509) + return -1; + if (X509_set_version(*x509, 2) != 1) + return -1; + ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1); + X509_gmtime_adj(X509_get_notBefore(*x509), 0); + X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS + + X509_set_pubkey(*x509, pkey); + + // SET Subject Name + X509_NAME *name = X509_get_subject_name(*x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0); + // Selfsigned, Issuer = Subject + X509_set_issuer_name(*x509, name); + + // Certificate signed with our privte key + if (X509_sign(*x509, pkey, EVP_sha256()) <= 0) + return -1; + + return 0; +} + +char *read_file_into_string(const char *filename) +{ + FILE *file = fopen(filename, "rb"); + if (file == NULL) { + LOG_ERROR("Error reading File : %s \n", filename); + return NULL; + } + + // Size of file + fseek(file, 0, SEEK_END); + long filesize = ftell(file); + rewind(file); + + // reserve mem for file + 1 byte + char *buffer = (char *)malloc(filesize + 1); + if (buffer == NULL) { + LOG_ERROR("Malloc of mem failed for file : %s \n", filename); + fclose(file); + return NULL; + } + + // read content + size_t readSize = fread(buffer, 1, filesize, file); + if (readSize != filesize) { + LOG_ERROR("Error reading file into buffer\n"); + free(buffer); + fclose(file); + return NULL; + } + + // add terminator sign at the end + buffer[filesize] = '\0'; + fclose(file); + return buffer; // return pointer +} + +int PiWebServerThread::CheckSSLandLoad() +{ + // read certificate + cert_pem = read_file_into_string("certificate.pem"); + if (cert_pem == NULL) { + LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n"); + return 1; + } + // read private key + key_pem = read_file_into_string("private_key.pem"); + if (key_pem == NULL) { + LOG_ERROR("ERROR file private_key can't be loaded or is missing\n"); + return 2; + } + + return 0; +} + +int PiWebServerThread::CreateSSLCertificate() +{ + + EVP_PKEY *pkey = NULL; + X509 *x509 = NULL; + + if (generate_rsa_key(&pkey) != 0) { + LOG_ERROR("Error generating RSA-Key.\n"); + return 1; + } + + if (generate_self_signed_x509(pkey, &x509) != 0) { + LOG_ERROR("Error generating of X509-Certificat.\n"); + return 2; + } + + // Ope file to write private key file + FILE *pkey_file = fopen("private_key.pem", "wb"); + if (!pkey_file) { + LOG_ERROR("Error opening private key file.\n"); + return 3; + } + // write private key file + PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL); + fclose(pkey_file); + + // open Certificate file + FILE *x509_file = fopen("certificate.pem", "wb"); + if (!x509_file) { + LOG_ERROR("Error opening certificate.\n"); + return 4; + } + // write cirtificate + PEM_write_X509(x509_file, x509); + fclose(x509_file); + + EVP_PKEY_free(pkey); + X509_free(x509); + LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n"); + return 0; +} + +void initWebServer() {} + +PiWebServerThread::PiWebServerThread() +{ + int ret, retssl, webservport; + + if (CheckSSLandLoad() != 0) { + CreateSSLCertificate(); + if (CheckSSLandLoad() != 0) { + LOG_ERROR("Major Error Gen & Read SSL Certificate\n"); + } + } + + if (settingsMap[webserverport] != 0) { + webservport = settingsMap[webserverport]; + LOG_INFO("Using webserver port from yaml config. %i \n", webservport); + } else { + LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n"); + webservport = 443; + } + + // Web Content Service Instance + if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) { + LOG_ERROR("Webserver couldn't be started, abort execution\n"); + } else { + + LOG_INFO("Webserver started ....\n"); + u_map_init(&configWeb.mime_types); + u_map_put(&configWeb.mime_types, "*", "application/octet-stream"); + u_map_put(&configWeb.mime_types, ".html", "text/html"); + u_map_put(&configWeb.mime_types, ".htm", "text/html"); + u_map_put(&configWeb.mime_types, ".tsx", "application/javascript"); + u_map_put(&configWeb.mime_types, ".ts", "application/javascript"); + u_map_put(&configWeb.mime_types, ".css", "text/css"); + u_map_put(&configWeb.mime_types, ".js", "application/javascript"); + u_map_put(&configWeb.mime_types, ".json", "application/json"); + u_map_put(&configWeb.mime_types, ".png", "image/png"); + u_map_put(&configWeb.mime_types, ".gif", "image/gif"); + u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg"); + u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg"); + u_map_put(&configWeb.mime_types, ".ttf", "font/ttf"); + u_map_put(&configWeb.mime_types, ".woff", "font/woff"); + u_map_put(&configWeb.mime_types, ".ico", "image/x-icon"); + u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml"); + + webrootpath = settingsStrings[webserverrootpath]; + + configWeb.files_path = (char *)webrootpath.c_str(); + configWeb.url_prefix = ""; + configWeb.rootPath = strdup(portduinoVFS->mountpoint()); + + u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*"); + // Maximum body size sent by the client is 1 Kb + instanceWeb.max_post_body_size = 1024; + ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL); + ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath); + + // Add callback function to all endpoints for the Web Server + ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb); + + // thats for serving without SSL + // retssl = ulfius_start_framework(&instanceWeb); + + // thats for serving with SSL + retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem); + + if (retssl == U_OK) { + LOG_INFO("Web Server framework started on port: %i \n", webservport); + LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str()); + } else { + LOG_ERROR("Error starting Web Server framework\n"); + } + } +} + +PiWebServerThread::~PiWebServerThread() +{ + u_map_clean(&configWeb.mime_types); + + ulfius_stop_framework(&instanceWeb); + ulfius_stop_framework(&instanceWeb); + free(configWeb.rootPath); + ulfius_clean_instance(&instanceService); + ulfius_clean_instance(&instanceService); + free(cert_pem); + LOG_INFO("End framework"); +} + +#endif +#endif \ No newline at end of file diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h new file mode 100644 index 000000000..c4c49e919 --- /dev/null +++ b/src/mesh/raspihttp/PiWebServer.h @@ -0,0 +1,61 @@ +#pragma once +#ifdef PORTDUINO_LINUX_HARDWARE +#if __has_include() +#include "PhoneAPI.h" +#include "ulfius-cfg.h" +#include "ulfius.h" +#include +#include + +#define STATIC_FILE_CHUNK 256 + +void initWebServer(); +void createSSLCert(); +int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data); +const char *get_filename_ext(const char *path); + +struct _file_config { + char *files_path; + char *url_prefix; + struct _u_map mime_types; + struct _u_map map_header; + char *redirect_on_404; + char *rootPath; +}; + +class PiWebServerThread +{ + private: + char *key_pem = NULL; + char *cert_pem = NULL; + // struct _u_map mime_types; + std::string webrootpath; + + public: + PiWebServerThread(); + ~PiWebServerThread(); + int CreateSSLCertificate(); + int CheckSSLandLoad(); + uint32_t requestRestart = 0; + struct _u_instance instanceWeb; + struct _u_instance instanceService; +}; + +class HttpAPI : public PhoneAPI +{ + + public: + // Nothing here yet + + private: + // Nothing here yet + + protected: + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this +}; + +extern PiWebServerThread *piwebServerThread; + +#endif +#endif \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 046509fab..997058406 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -195,6 +195,11 @@ void portduinoSetup() settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as(""); } + if (yamlConfig["Webserver"]) { + settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as(-1); + settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as(""); + } + } catch (YAML::Exception e) { std::cout << "*** Exception " << e.what() << std::endl; exit(EXIT_FAILURE); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index f8da20e37..3fe5f74bf 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -33,7 +33,10 @@ enum configNames { displayOffsetY, displayInvert, keyboardDevice, - logoutputlevel + logoutputlevel, + webserver, + webserverport, + webserverrootpath }; enum { no_screen, st7789, st7735, st7735s, ili9341 }; enum { no_touchscreen, xpt2046, stmpe610 }; diff --git a/variants/portduino/platformio.ini b/variants/portduino/platformio.ini index d37c6be21..46417e388 100644 --- a/variants/portduino/platformio.ini +++ b/variants/portduino/platformio.ini @@ -1,6 +1,10 @@ [env:native] extends = portduino_base -build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino +; The pkg-config commands below optionally add link flags. +; the || : is just a "or run the null command" to avoid returning an error code +build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino -I /usr/include + !pkg-config --libs libulfius --silence-errors || : + !pkg-config --libs openssl --silence-errors || : board = cross_platform lib_deps = ${portduino_base.lib_deps} -build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file +build_src_filter = ${portduino_base.build_src_filter} From 46ad6237859e60eb1e54b3bb5c69baa895d95c3b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 6 Mar 2024 17:00:23 -0600 Subject: [PATCH 0046/1377] Add webroot to .deb --- .github/workflows/package_raspbian.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 2f9a99e58..ee1643fbf 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -23,6 +23,14 @@ jobs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + with: + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + - name: Get release version string run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version @@ -37,9 +45,12 @@ jobs: - name: build .debpkg run: | + mkdir -p .debpkg/usr/share/doc/meshtasticd/web mkdir -p .debpkg/usr/sbin mkdir -p .debpkg/etc/meshtasticd mkdir -p .debpkg/usr/lib/systemd/system/ + tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web + gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml chmod +x .debpkg/usr/sbin/meshtasticd From bfce3938d24b7190ec78e2faf64041b916550139 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 6 Mar 2024 18:54:09 -0600 Subject: [PATCH 0047/1377] Add openssl as dependency to meshtasticd .deb --- .github/workflows/package_raspbian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index ee1643fbf..377074e95 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -63,7 +63,7 @@ jobs: maintainer: Jonathan Bennett version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* arch: arm64 - depends: libyaml-cpp0.7 + depends: libyaml-cpp0.7, openssl desc: Native Linux Meshtastic binary. - uses: actions/upload-artifact@v3 From 2dd751e3391e9c676f78e7b329a0cdccb03f3c40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 07:06:47 -0600 Subject: [PATCH 0048/1377] [create-pull-request] automated change (#3346) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/portnums.pb.h | 10 +++++----- src/mesh/generated/meshtastic/telemetry.pb.h | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/protobufs b/protobufs index 62b7d8b88..5a97acb17 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 62b7d8b884d70aed5ff18c3b0e228095eeb48de2 +Subproject commit 5a97acb17543a10e114675a205e3274a83e721af diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 88342e5dc..3f3e9aaee 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -38,19 +38,19 @@ typedef enum _meshtastic_PortNum { ENCODING: Protobuf */ meshtastic_PortNum_REMOTE_HARDWARE_APP = 2, /* The built-in position messaging app. - Payload is a [Position](/docs/developers/protobufs/api#position) message + Payload is a Position message. ENCODING: Protobuf */ meshtastic_PortNum_POSITION_APP = 3, /* The built-in user info app. - Payload is a [User](/docs/developers/protobufs/api#user) message + Payload is a User message. ENCODING: Protobuf */ meshtastic_PortNum_NODEINFO_APP = 4, /* Protocol control packets for mesh protocol use. - Payload is a [Routing](/docs/developers/protobufs/api#routing) message + Payload is a Routing message. ENCODING: Protobuf */ meshtastic_PortNum_ROUTING_APP = 5, /* Admin control packets. - Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message + Payload is a AdminMessage message. ENCODING: Protobuf */ meshtastic_PortNum_ADMIN_APP = 6, /* Compressed TEXT_MESSAGE payloads. @@ -60,7 +60,7 @@ typedef enum _meshtastic_PortNum { any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP. */ meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP = 7, /* Waypoint payloads. - Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message + Payload is a Waypoint message. ENCODING: Protobuf */ meshtastic_PortNum_WAYPOINT_APP = 8, /* Audio Payloads. diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index fc2780a96..d73c6baa1 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -41,7 +41,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* PM2.5 air quality sensor */ meshtastic_TelemetrySensorType_PMSA003I = 13, /* INA3221 3 Channel Voltage / Current Sensor */ - meshtastic_TelemetrySensorType_INA3221 = 14 + meshtastic_TelemetrySensorType_INA3221 = 14, + /* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */ + meshtastic_TelemetrySensorType_BMP085 = 15 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -141,8 +143,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA3221 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA3221+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BMP085 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BMP085+1)) From c860493e68112d83f5c3707bd4bc4401d8d681de Mon Sep 17 00:00:00 2001 From: Steven Osborn Date: Thu, 7 Mar 2024 05:11:25 -0800 Subject: [PATCH 0049/1377] Add delay so GPS and Radio have time to power up (#3334) * Add delay so GPS and Radio have time to power up * reduce the delay a bit * make delay more generic / configurable * remove whitespace changes --- boards/canaryone.json | 2 +- src/main.cpp | 5 +++++ variants/canaryone/variant.h | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/boards/canaryone.json b/boards/canaryone.json index d8f966a47..da7c0986f 100644 --- a/boards/canaryone.json +++ b/boards/canaryone.json @@ -7,7 +7,7 @@ "cpu": "cortex-m4", "extra_flags": "-DARDUINO_NRF52840_CANARY -DNRF52840_XXAA", "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4405"]], + "hwids": [["0x239A", "0x4405"], ["0x239A", "0x009F"]], "usb_product": "CanaryOne", "mcu": "nrf52840", "variant": "canaryone", diff --git a/src/main.cpp b/src/main.cpp index 3619b0053..80706d044 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -680,6 +680,11 @@ void setup() digitalWrite(SX126X_ANT_SW, 1); #endif +#ifdef PIN_PWR_DELAY_MS + // This may be required to give the peripherals time to power up. + delay(PIN_PWR_DELAY_MS); +#endif + #ifdef ARCH_PORTDUINO if (settingsMap[use_sx1262]) { if (!rIf) { diff --git a/variants/canaryone/variant.h b/variants/canaryone/variant.h index e31ba3c58..21aa921ce 100644 --- a/variants/canaryone/variant.h +++ b/variants/canaryone/variant.h @@ -103,6 +103,9 @@ static const uint8_t A0 = PIN_A0; #define EXTERNAL_FLASH_DEVICES MX25R1635F #define EXTERNAL_FLASH_USE_QSPI +// Add a delay on startup to allow LoRa and GPS to power up +#define PIN_PWR_DELAY_MS 100 + /* * Lora radio */ From b4940b476daa6817d990f8a019bda741589c76ed Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 7 Mar 2024 15:51:28 -0600 Subject: [PATCH 0050/1377] Trunk --- boards/canaryone.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/boards/canaryone.json b/boards/canaryone.json index da7c0986f..f64a4a7c7 100644 --- a/boards/canaryone.json +++ b/boards/canaryone.json @@ -7,7 +7,10 @@ "cpu": "cortex-m4", "extra_flags": "-DARDUINO_NRF52840_CANARY -DNRF52840_XXAA", "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4405"], ["0x239A", "0x009F"]], + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x009F"] + ], "usb_product": "CanaryOne", "mcu": "nrf52840", "variant": "canaryone", From 7f1250571679131424952f01a0e44e489a332793 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 7 Mar 2024 15:52:08 -0600 Subject: [PATCH 0051/1377] Update trunk --- .trunk/trunk.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index af7d3d21d..0826b71d9 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,19 +4,19 @@ cli: plugins: sources: - id: trunk - ref: v1.4.3 + ref: v1.4.4 uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.68.2 + - trufflehog@3.68.5 - yamllint@1.35.1 - bandit@1.7.7 - - checkov@3.2.26 - - terrascan@1.18.11 + - checkov@3.2.32 + - terrascan@1.19.1 - trivy@0.49.1 #- trufflehog@3.63.2-rc0 - taplo@0.8.1 - - ruff@0.2.2 + - ruff@0.3.1 - isort@5.13.2 - markdownlint@0.39.0 - oxipng@9.0.0 From 763ae9f2e2e7db779c80021474162a212fa409e8 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Sat, 2 Mar 2024 22:14:34 +0100 Subject: [PATCH 0052/1377] add BMP085 (and BMP180) sensor (temperature and air pressure) --- platformio.ini | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 4 +++ src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 6 ++++ src/modules/Telemetry/Sensor/BMP085Sensor.cpp | 31 +++++++++++++++++++ src/modules/Telemetry/Sensor/BMP085Sensor.h | 17 ++++++++++ 7 files changed, 61 insertions(+) create mode 100644 src/modules/Telemetry/Sensor/BMP085Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/BMP085Sensor.h diff --git a/platformio.ini b/platformio.ini index 0033b6e46..b67ddc50a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -116,6 +116,7 @@ lib_deps = adafruit/Adafruit BusIO@^1.11.4 adafruit/Adafruit Unified Sensor@^1.1.11 adafruit/Adafruit BMP280 Library@^2.6.8 + adafruit/Adafruit BMP085 Library@^1.2.4 adafruit/Adafruit BME280 Library@^2.2.2 https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400 boschsensortec/BME68x Sensor Library@^1.1.40407 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 2b4b8a735..66e683982 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -23,6 +23,7 @@ class ScanI2C BME_680, BME_280, BMP_280, + BMP_085, INA260, INA219, INA3221, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 990fb36ea..b6eca5fa4 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -242,6 +242,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port) LOG_INFO("BME-280 sensor found at address 0x%x\n", (uint8_t)addr.address); type = BME_280; break; + case 0x55: + LOG_INFO("BMP-085 or BMP-180 sensor found at address 0x%x\n", (uint8_t)addr.address); + type = BMP_085; + break; default: LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address); type = BMP_280; diff --git a/src/main.cpp b/src/main.cpp index 80706d044..b62ccf986 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -500,6 +500,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index e501f17c2..d4f423e54 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -16,12 +16,14 @@ // Sensors #include "Sensor/BME280Sensor.h" #include "Sensor/BME680Sensor.h" +#include "Sensor/BMP085Sensor.h" #include "Sensor/BMP280Sensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHTC3Sensor.h" +BMP085Sensor bmp085Sensor; BMP280Sensor bmp280Sensor; BME280Sensor bme280Sensor; BME680Sensor bme680Sensor; @@ -67,6 +69,8 @@ int32_t EnvironmentTelemetryModule::runOnce() LOG_INFO("Environment Telemetry: Initializing\n"); // it's possible to have this module enabled, only for displaying values on the screen. // therefore, we should only enable the sensor loop if measurement is also enabled + if (bmp085Sensor.hasSensor()) + result = bmp085Sensor.runOnce(); if (bmp280Sensor.hasSensor()) result = bmp280Sensor.runOnce(); if (bme280Sensor.hasSensor()) @@ -219,6 +223,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = lps22hbSensor.getMetrics(&m); if (shtc3Sensor.hasSensor()) valid = shtc3Sensor.getMetrics(&m); + if (bmp085Sensor.hasSensor()) + valid = bmp085Sensor.getMetrics(&m); if (bmp280Sensor.hasSensor()) valid = bmp280Sensor.getMetrics(&m); if (bme280Sensor.hasSensor()) diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp new file mode 100644 index 000000000..b0991749b --- /dev/null +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -0,0 +1,31 @@ +#include "BMP085Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" +#include +#include + +BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {} + +int32_t BMP085Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + bmp085 = Adafruit_BMP085(); + status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + + return initI2CSensor(); +} + +void BMP085Sensor::setup() {} + +bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("BMP085Sensor::getMetrics\n"); + measurement->variant.environment_metrics.temperature = bmp085.readTemperature(); + measurement->variant.environment_metrics.barometric_pressure = bmp085.readPressure() / 100.0F; + + return true; +} diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h new file mode 100644 index 000000000..c4a9479b9 --- /dev/null +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class BMP085Sensor : public TelemetrySensor +{ + private: + Adafruit_BMP085 bmp085; + + protected: + virtual void setup() override; + + public: + BMP085Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file From 585805c3b96d58224ffd2327fd6b4e1712a84abd Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:13:57 +0100 Subject: [PATCH 0053/1377] Add original hop limit to header to determine hops used (#3321) * Set `hop_start` in header to determine how many hops each packet traveled * Set hopLimit of response according to hops used by request * Identify neighbors based on `hopStart` and `hopLimit` * NeighborInfo: get all packets and assume a default broadcast interval * Add fail-safe in case node in between is running modified firmware * Add `viaMQTT` and `hopsAway` to NodeInfo * Replace `HOP_RELIABLE` with hopStart for repeated packet --------- Co-authored-by: Ben Meadors --- src/mesh/MeshModule.cpp | 9 ++++++--- src/mesh/MeshModule.h | 3 ++- src/mesh/NodeDB.cpp | 6 ++++++ src/mesh/RadioInterface.cpp | 5 ++++- src/mesh/RadioInterface.h | 6 ++++-- src/mesh/RadioLibInterface.cpp | 7 ++++--- src/mesh/ReliableRouter.cpp | 13 +++++++------ src/mesh/Router.cpp | 9 +++++++-- src/mesh/Router.h | 3 ++- src/modules/NeighborInfoModule.cpp | 12 +++++++++--- src/modules/NeighborInfoModule.h | 3 +++ src/modules/RoutingModule.cpp | 19 +++++++++++++++++-- src/modules/RoutingModule.h | 6 +++++- 13 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 9c6ca78ee..ad0c78108 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -32,7 +32,8 @@ MeshModule::~MeshModule() assert(0); // FIXME - remove from list of modules once someone needs this feature } -meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, + uint8_t hopStart, uint8_t hopLimit) { meshtastic_Routing c = meshtastic_Routing_init_default; @@ -49,7 +50,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod p->priority = meshtastic_MeshPacket_Priority_ACK; - p->hop_limit = config.lora.hop_limit; // Flood ACK back to original sender + p->hop_limit = routingModule->getHopLimitForResponse(hopStart, hopLimit); // Flood ACK back to original sender p->to = to; p->decoded.request_id = idFrom; p->channel = chIndex; @@ -176,7 +177,8 @@ void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src) // SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded) // but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs // bad. - routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); + routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel, mp.hop_start, + mp.hop_limit); } } @@ -217,6 +219,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to) assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0 p->channel = to.channel; // Use the same channel that the request came in on + p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit); // No need for an ack if we are just delivering locally (it just generates an ignored ack) p->want_ack = (to.from != 0) ? to.want_ack : false; diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index ebe3af1a0..6c431adb4 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -153,7 +153,8 @@ class MeshModule virtual bool wantUIFrame() { return false; } virtual Observable *getUIFrameObservable() { return NULL; } - meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, + uint8_t hopStart = 0, uint8_t hopLimit = 0); /// Send an error response for the specified packet. meshtastic_MeshPacket *allocErrorResponse(meshtastic_Routing_Error err, const meshtastic_MeshPacket *p); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dc8d7540c..787c16a79 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -875,6 +875,12 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) if (mp.rx_snr) info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. + + info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT + + // If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway + if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) + info->hops_away = mp.hop_start - mp.hop_limit; } } diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index cea3968ce..c10eb26f6 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -302,6 +302,8 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p) out += DEBUG_PORT.mt_sprintf(" rxRSSI=%i", p->rx_rssi); if (p->via_mqtt != 0) out += DEBUG_PORT.mt_sprintf(" via MQTT"); + if (p->hop_start != 0) + out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start); if (p->priority != 0) out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority); @@ -561,6 +563,7 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) p->hop_limit = HOP_RELIABLE; } h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0); + h->flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK; // if the sender nodenum is zero, that means uninitialized assert(h->from); @@ -569,4 +572,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} +} \ No newline at end of file diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 83c5dae64..f85b3bfa5 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -10,9 +10,11 @@ #define MAX_RHPACKETLEN 256 -#define PACKET_FLAGS_HOP_MASK 0x07 +#define PACKET_FLAGS_HOP_LIMIT_MASK 0x07 #define PACKET_FLAGS_WANT_ACK_MASK 0x08 #define PACKET_FLAGS_VIA_MQTT_MASK 0x10 +#define PACKET_FLAGS_HOP_START_MASK 0xE0 +#define PACKET_FLAGS_HOP_START_SHIFT 5 /** * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility @@ -224,4 +226,4 @@ class RadioInterface }; /// Debug printing for packets -void printPacket(const char *prefix, const meshtastic_MeshPacket *p); +void printPacket(const char *prefix, const meshtastic_MeshPacket *p); \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 8a2bc53e5..9f42afa6d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -359,8 +359,9 @@ void RadioLibInterface::handleReceiveInterrupt() mp->to = h->to; mp->id = h->id; mp->channel = h->channel; - assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK; + assert(HOP_MAX <= PACKET_FLAGS_HOP_LIMIT_MASK); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK; + mp->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); mp->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK); @@ -407,4 +408,4 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) // bits enableInterrupt(isrTxLevel0); } -} +} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index a1e9f281d..167a248ab 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -71,12 +71,12 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) i->second.nextTxMsec += iface->getPacketTime(p); } - /* Resend implicit ACKs for repeated packets (assuming the original packet was sent with HOP_RELIABLE) + /* Resend implicit ACKs for repeated packets (hopStart equals hopLimit); * this way if an implicit ACK is dropped and a packet is resent we'll rebroadcast again. * Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and * flooding this ACK back to the original sender already adds redundancy. */ - if (wasSeenRecently(p, false) && p->hop_limit == HOP_RELIABLE && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) { - // retransmission on broadcast has hop_limit still equal to HOP_RELIABLE + bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit); + if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) { LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n"); meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); tosend->hop_limit--; // bump down the hop count @@ -107,10 +107,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas if (MeshModule::currentReply) { LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n"); } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel); + sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); } else { // Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded - sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex()); + sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start, + p->hop_limit); } } @@ -255,4 +256,4 @@ void ReliableRouter::setNextTx(PendingPacket *pending) LOG_DEBUG("Setting next retransmission in %u msecs: ", d); printPacket("", pending->packet); setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time -} +} \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1d6a2d96b..7657d2268 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -132,9 +132,10 @@ meshtastic_MeshPacket *Router::allocForSending() /** * Send an ack or a nak packet back towards whoever sent idFrom */ -void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart, + uint8_t hopLimit) { - routingModule->sendAckNak(err, to, idFrom, chIndex); + routingModule->sendAckNak(err, to, idFrom, chIndex, hopStart, hopLimit); } void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p) @@ -240,6 +241,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // the lora we need to make sure we have replaced it with our local address p->from = getFrom(p); + // If we are the original transmitter, set the hop limit with which we start + if (p->from == getNodeNum()) + p->hop_start = p->hop_limit; + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || diff --git a/src/mesh/Router.h b/src/mesh/Router.h index db810e42e..98486745b 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -104,7 +104,8 @@ class Router : protected concurrency::OSThread /** * Send an ack or a nak packet back towards whoever sent idFrom */ - void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0, + uint8_t hopLimit = 0); private: /** diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 4541958fa..2e0b04afa 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -95,6 +95,7 @@ NeighborInfoModule::NeighborInfoModule() ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP; if (moduleConfig.neighbor_info.enabled) { + isPromiscuous = true; // Update neighbors from all packets this->loadProtoForModule(); setIntervalFromNow(35 * 1000); } else { @@ -202,9 +203,12 @@ Pass it to an upper client; do not persist this data on the mesh */ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np) { - if (enabled) { + if (np) { printNeighborInfo("RECEIVED", np); updateNeighbors(mp, np); + } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) { + // If the hopLimit is the same as hopStart, then it is a neighbor + getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it } // Allow others to handle this packet return false; @@ -261,7 +265,7 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen nbr->snr = snr; nbr->last_rx_time = getTime(); // Only if this is the original sender, the broadcast interval corresponds to it - if (originalSender == n) + if (originalSender == n && node_broadcast_interval_secs != 0) nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; saveProtoForModule(); // Save the updated neighbor return nbr; @@ -277,8 +281,10 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen new_nbr->snr = snr; new_nbr->last_rx_time = getTime(); // Only if this is the original sender, the broadcast interval corresponds to it - if (originalSender == n) + if (originalSender == n && node_broadcast_interval_secs != 0) new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; + else // Assume the same broadcast interval as us for the neighbor if we don't know it + new_nbr->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; saveProtoForModule(); // Save the new neighbor return new_nbr; } diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index 0e3ec09ca..df5c2c948 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -75,6 +75,9 @@ class NeighborInfoModule : public ProtobufModule, priva /* Does our periodic broadcast */ int32_t runOnce() override; + // Override wantPacket to say we want to see all packets when enabled, not just those for our port number + virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return enabled; } + /* These are for debugging only */ void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); void printNodeDBNodes(const char *header); diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index edeb1fb86..37a7c3755 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -36,13 +36,28 @@ meshtastic_MeshPacket *RoutingModule::allocReply() return NULL; } -void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart, + uint8_t hopLimit) { - auto p = allocAckNak(err, to, idFrom, chIndex); + auto p = allocAckNak(err, to, idFrom, chIndex, hopStart, hopLimit); router->sendLocal(p); // we sometimes send directly to the local node } +uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit) +{ + if (hopStart != 0) { + // Hops used by the request. If somebody in between running modified firmware modified it, ignore it + uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit; + if (hopsUsed > config.lora.hop_limit) { + return hopsUsed; // If the request used more hops than the limit, use the same amount of hops + } else if (hopsUsed + 2 < config.lora.hop_limit) { + return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different + } + } + return config.lora.hop_limit; // Use the default hop limit +} + RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) { isPromiscuous = true; diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h index 06e76cfb4..f085b307b 100644 --- a/src/modules/RoutingModule.h +++ b/src/modules/RoutingModule.h @@ -13,7 +13,11 @@ class RoutingModule : public ProtobufModule */ RoutingModule(); - void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0, + uint8_t hopLimit = 0); + + // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response + uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit); protected: friend class Router; From 7da1153c2c913903cc0c8603a81ade22f8cf34a8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 8 Mar 2024 08:31:49 -0600 Subject: [PATCH 0054/1377] Fix known_only panic by short circuiting for NULL before checking has_user (#3352) --- src/mesh/Router.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 7657d2268..7c739b8f2 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -297,7 +297,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p) return false; if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY && - !nodeDB.getMeshNode(p->from)->has_user) { + (nodeDB.getMeshNode(p->from) == NULL || !nodeDB.getMeshNode(p->from)->has_user)) { LOG_DEBUG("Node 0x%x not in NodeDB. Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from); return false; } From 5d4d91f77512ca7aa2f0dd07cbb09385f397834c Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Tue, 5 Mar 2024 14:53:42 +1300 Subject: [PATCH 0055/1377] Move Wireless Paper V1.1 custom hibernate behavior to GxEPD2 --- src/graphics/EInkDisplay2.cpp | 6 +++--- variants/heltec_wireless_paper/variant.h | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index aee30c7f8..026a65e6d 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -84,10 +84,10 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) adafruitDisplay->nextPage(); #endif -#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory - // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display) + // Power off display hardware + // Most models: deep sleep. + // Wireless Paper V1.1: power off only. Deep sleep clears memory - problems with fast refresh adafruitDisplay->hibernate(); -#endif LOG_DEBUG("done\n"); return true; diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 28bc8628a..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -5,7 +5,6 @@ #define I2C_SCL SCL #define USE_EINK -#define EINK_NO_HIBERNATE /* * eink display pins From 07da13058684b0d5517d3bfd54a3e2d857e9e5a8 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 7 Mar 2024 03:26:31 +1300 Subject: [PATCH 0056/1377] Async full-refresh for EInkDynamicDisplay --- src/graphics/EInkDisplay2.cpp | 25 +++--- src/graphics/EInkDisplay2.h | 7 ++ src/graphics/EInkDynamicDisplay.cpp | 90 +++++++++++++++++-- src/graphics/EInkDynamicDisplay.h | 15 +++- variants/heltec_wireless_paper/platformio.ini | 3 +- .../heltec_wireless_paper_v1/platformio.ini | 3 +- 6 files changed, 117 insertions(+), 26 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 026a65e6d..a544833c1 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -71,28 +71,24 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) } } + // Trigger the refresh in GxEPD2 LOG_DEBUG("Updating E-Paper... "); - -#if false - // Currently unused; rescued from commented-out line during a refactor - // Use a meaningful macro here if variant doesn't want fast refresh - - // Full update mode (slow) - adafruitDisplay->display(false) -#else - // Fast update mode adafruitDisplay->nextPage(); -#endif - // Power off display hardware - // Most models: deep sleep. - // Wireless Paper V1.1: power off only. Deep sleep clears memory - problems with fast refresh - adafruitDisplay->hibernate(); + // End the update process + endUpdate(); LOG_DEBUG("done\n"); return true; } +// End the update process - virtual method, overriden in derived class +void EInkDisplay::endUpdate() +{ + // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep) + adafruitDisplay->hibernate(); +} + // Write the buffer to the display memory void EInkDisplay::display(void) { @@ -193,6 +189,7 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); + adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh) } #elif defined(PCA10059) { diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 75770a3bc..f74416494 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -45,6 +45,13 @@ class EInkDisplay : public OLEDDisplay */ virtual bool forceDisplay(uint32_t msecLimit = 1000); + /** + * Run any code needed to complete an update, after the physical refresh has completed. + * Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class. + * + */ + virtual void endUpdate(); + /** * shim to make the abstraction happy * diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index ae1e30fe1..75db0e33f 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -94,19 +94,29 @@ void EInkDynamicDisplay::adjustRefreshCounters() // Trigger the display update by calling base class bool EInkDynamicDisplay::update() { + // Detemine the refresh mode to use, and start the update bool refreshApproved = determineMode(); if (refreshApproved) EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system - return refreshApproved; // (Unutilized) Base class promises to return true if update ran + +#if defined(HAS_EINK_ASYNCFULL) + if (refreshApproved) + endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh) +#endif + + return refreshApproved; // (Unutilized) Base class promises to return true if update ran } // Assess situation, pick a refresh type bool EInkDynamicDisplay::determineMode() { - checkWasFlooded(); + checkForPromotion(); +#if defined(HAS_EINK_ASYNCFULL) + checkAsyncFullRefresh(); +#endif checkRateLimiting(); - // If too soon for a new time, abort here + // If too soon for a new frame, or display busy, abort early if (refresh == SKIPPED) { storeAndReset(); return false; // No refresh @@ -116,7 +126,7 @@ bool EInkDynamicDisplay::determineMode() resetRateLimiting(); // Once determineMode() ends, will have to wait again hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check - LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry + LOG_DEBUG("determineMode(): "); // Begin log entry // Once mode determined, any remaining checks will bypass checkCosmetic(); @@ -151,13 +161,25 @@ bool EInkDynamicDisplay::determineMode() } } -// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh? -void EInkDynamicDisplay::checkWasFlooded() +// Was a frame skipped (rate, display busy) that should have been a FAST refresh? +void EInkDynamicDisplay::checkForPromotion() { - if (previousReason == EXCEEDED_RATELIMIT_FAST) { - // If so, allow a BACKGROUND frame to draw as RESPONSIVE - // Because we DID want a RESPONSIVE frame last time, we just didn't get it + // If a frame was skipped (rate, display busy), then promote a BACKGROUND frame + // Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it + + switch (previousReason) { + case ASYNC_REFRESH_BLOCKED_DEMANDFAST: + setFrameFlag(DEMAND_FAST); + break; + case ASYNC_REFRESH_BLOCKED_COSMETIC: + setFrameFlag(COSMETIC); + break; + case ASYNC_REFRESH_BLOCKED_RESPONSIVE: + case EXCEEDED_RATELIMIT_FAST: setFrameFlag(RESPONSIVE); + break; + default: + break; } } @@ -381,4 +403,54 @@ void EInkDynamicDisplay::resetGhostPixelTracking() } #endif // EINK_LIMIT_GHOSTING_PX +#ifdef HAS_EINK_ASYNCFULL +// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready +void EInkDynamicDisplay::checkAsyncFullRefresh() +{ + // No refresh taking place, continue with determineMode() + if (!asyncRefreshRunning) + return; + + // Full refresh still running + if (adafruitDisplay->epd2.isBusy()) { + // No refresh + refresh = SKIPPED; + + // Set the reason, marking what type of frame we're skipping + if (frameFlags & DEMAND_FAST) + reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST; + else if (frameFlags & COSMETIC) + reason = ASYNC_REFRESH_BLOCKED_COSMETIC; + else if (frameFlags & RESPONSIVE) + reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE; + else + reason = ASYNC_REFRESH_BLOCKED_BACKGROUND; + + return; + } + + // If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag + LOG_DEBUG("Async full-refresh complete\n"); + + // Note: this code only works because of a modification to meshtastic/GxEPD2. + // It is only equipped to intercept calls to nextPage() +} + +// Figure out who runs the post-update code +void EInkDynamicDisplay::endOrDetach() +{ + if (previousRefresh == FULL) { // Note: previousRefresh is the refresh from this loop. + asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. + LOG_DEBUG("Async full-refresh begins\n"); + } + + // Fast Refresh + else + EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. +} +#endif // HAS_EINK_ASYNCFULL + #endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 2880c716b..3dc00ba7c 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -44,6 +44,11 @@ class EInkDynamicDisplay : public EInkDisplay }; enum reasonTypes : uint8_t { // How was the decision reached NO_OBJECTIONS, + ASYNC_REFRESH_BLOCKED_DEMANDFAST, + ASYNC_REFRESH_BLOCKED_COSMETIC, + ASYNC_REFRESH_BLOCKED_RESPONSIVE, + ASYNC_REFRESH_BLOCKED_BACKGROUND, + DISPLAY_NOT_READY_FOR_FULL, EXCEEDED_RATELIMIT_FAST, EXCEEDED_RATELIMIT_FULL, FLAGGED_COSMETIC, @@ -64,7 +69,7 @@ class EInkDynamicDisplay : public EInkDisplay bool update(); // Trigger the display update - determine mode, then call base class // Checks as part of determineMode() - void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC? + void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh? void checkRateLimiting(); // Is this frame too soon? void checkCosmetic(); // Was the COSMETIC flag set? void checkDemandingFast(); // Was the DEMAND_FAST flag set? @@ -99,6 +104,14 @@ class EInkDynamicDisplay : public EInkDisplay uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use #endif + + // Conditional - async full refresh - only with modified meshtastic/GxEPD2 +#if defined(HAS_EINK_ASYNCFULL) + void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready + void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() + void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() + bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() +#endif }; #endif \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 0abbe085e..7aebef014 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -16,7 +16,8 @@ build_flags = -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2 + ; https://github.com/meshtastic/GxEPD2/ + https://github.com/todd-herbert/meshtastic-GxEPD2#async ; Revert to meshtastic/firmware before submitting PR before final merge adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 4e5e291e0..8cd870353 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -16,7 +16,8 @@ build_flags = ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2/ + ; https://github.com/meshtastic/GxEPD2/ + https://github.com/todd-herbert/meshtastic-GxEPD2#async ; Revert to meshtastic/firmware before submitting PR before final merge adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file From ac89bb33871a37005a50ef2b7410b8102186c1d9 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Fri, 8 Mar 2024 13:16:06 +1300 Subject: [PATCH 0057/1377] initial config for T-Echo --- variants/t-echo/platformio.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 49ba3bb34..f894b1203 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -11,6 +11,12 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 -DEINK_HEIGHT=200 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From 7275c21f6b054e57a9c2c1b8a418cb1aa047bab5 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sat, 9 Mar 2024 09:34:53 +1300 Subject: [PATCH 0058/1377] formatting responds to https://github.com/meshtastic/firmware/pull/3339#discussion_r1518175434 --- variants/t-echo/platformio.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index f894b1203..076f1a747 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -11,12 +11,12 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 -DEINK_HEIGHT=200 - -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk - -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted - -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates - -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates - -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated - -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From 23926210d1bcd841b27e15a5119941621be94c72 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sat, 9 Mar 2024 09:57:30 +1300 Subject: [PATCH 0059/1377] increase fast-refresh limit for T-Echo https://github.com/meshtastic/firmware/pull/3339#issuecomment-1986245727 --- variants/t-echo/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 076f1a747..94b6ee087 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -12,7 +12,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_WIDTH=200 -DEINK_HEIGHT=200 -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk - -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated From 0f1bc98305c3c6fd56b6d394aaf3d0e1ba1b66b9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 8 Mar 2024 20:15:00 -0600 Subject: [PATCH 0060/1377] Update MQTT topic to match (#3353) --- src/mqtt/MQTT.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 2b803e3fc..e67958b25 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -80,10 +80,10 @@ class MQTT : private concurrency::OSThread private: std::string statusTopic = "/2/stat/"; - std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID + std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID - /** return true if we have a channel that wants uplink/downlink - */ + /** return true if we have a channel that wants uplink/downlink + */ bool wantsLink() const; /** Tell the server what subscriptions we want (based on channels.downlink_enabled) From 51df4fc7750b5affda52b82f901173195f201293 Mon Sep 17 00:00:00 2001 From: Andre K Date: Fri, 8 Mar 2024 23:15:37 -0300 Subject: [PATCH 0061/1377] fix: turn off T-Echo peripherals on deep sleep (#3162) Co-authored-by: Ben Meadors --- src/Power.cpp | 5 ----- src/graphics/EInkDisplay2.cpp | 5 ----- src/graphics/Screen.cpp | 3 +++ src/main.cpp | 9 +++++---- src/sleep.cpp | 7 +++++++ variants/rak10701/variant.h | 4 ---- variants/rak4631/variant.h | 4 ---- variants/rak4631_epaper/variant.h | 4 ---- variants/rak4631_epaper_onrxtx/variant.h | 4 ---- variants/t-echo/variant.h | 6 +++--- 10 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 8e44ddb98..3d1a1b9b2 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -473,11 +473,6 @@ bool Power::setup() void Power::shutdown() { - screen->setOn(false); -#if defined(USE_EINK) && defined(PIN_EINK_EN) - digitalWrite(PIN_EINK_EN, LOW); // power off backlight first -#endif - LOG_INFO("Shutting down\n"); #ifdef HAS_PMU diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index aee30c7f8..6ee4245b3 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -122,11 +122,6 @@ bool EInkDisplay::connect() { LOG_INFO("Doing EInk init\n"); -#ifdef PIN_EINK_PWR_ON - pinMode(PIN_EINK_PWR_ON, OUTPUT); - digitalWrite(PIN_EINK_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals -#endif - #ifdef PIN_EINK_EN // backlight power, HIGH is backlight on, LOW is off pinMode(PIN_EINK_EN, OUTPUT); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 33df78462..3ffea4a60 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -938,6 +938,9 @@ void Screen::doDeepSleep() static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); ui->setFrames(sleepFrames, sleepFrameCount); ui->update(); +#ifdef PIN_EINK_EN + digitalWrite(PIN_EINK_EN, LOW); // power off backlight +#endif #endif setOn(false); } diff --git a/src/main.cpp b/src/main.cpp index b62ccf986..ef1cd53c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -219,10 +219,11 @@ void setup() initDeepSleep(); - // Testing this fix für erratic T-Echo boot behaviour -#if defined(TTGO_T_ECHO) && defined(PIN_EINK_PWR_ON) - pinMode(PIN_EINK_PWR_ON, OUTPUT); - digitalWrite(PIN_EINK_PWR_ON, HIGH); + // power on peripherals +#if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN) + pinMode(PIN_POWER_EN, OUTPUT); + digitalWrite(PIN_POWER_EN, HIGH); + digitalWrite(PIN_POWER_EN1, INPUT); #endif #if defined(VEXT_ENABLE_V03) diff --git a/src/sleep.cpp b/src/sleep.cpp index 1afba1173..bfacffeb9 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -200,6 +200,13 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) nodeDB.saveToDisk(); +#ifdef TTGO_T_ECHO +#ifdef PIN_POWER_EN + pinMode(PIN_POWER_EN, INPUT); // power off peripherals + pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); +#endif +#endif + // Kill GPS power completely (even if previously we just had it in sleep mode) if (gps) gps->setGPSPower(false, false, 0); diff --git a/variants/rak10701/variant.h b/variants/rak10701/variant.h index 837d081ff..d6eeb71dc 100644 --- a/variants/rak10701/variant.h +++ b/variants/rak10701/variant.h @@ -133,10 +133,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 3) #define PIN_EINK_MOSI (0 + 30) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - // #define USE_EINK // RAKRGB diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 4ad99df44..0ccf3b1d7 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -133,10 +133,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 3) #define PIN_EINK_MOSI (0 + 30) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - // #define USE_EINK // RAKRGB diff --git a/variants/rak4631_epaper/variant.h b/variants/rak4631_epaper/variant.h index d8a5e5597..b1bd84d21 100644 --- a/variants/rak4631_epaper/variant.h +++ b/variants/rak4631_epaper/variant.h @@ -133,10 +133,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 3) #define PIN_EINK_MOSI (0 + 30) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - #define USE_EINK // RAKRGB diff --git a/variants/rak4631_epaper_onrxtx/variant.h b/variants/rak4631_epaper_onrxtx/variant.h index 411e3eb17..ec53ebd33 100644 --- a/variants/rak4631_epaper_onrxtx/variant.h +++ b/variants/rak4631_epaper_onrxtx/variant.h @@ -119,10 +119,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 14) // SCL #define PIN_EINK_MOSI (0 + 13) // SDA -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - // RAKRGB #define HAS_NCP5623 diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 1af68863e..19a66719f 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -156,9 +156,9 @@ External serial flash WP25R1635FZUIL0 #define PIN_EINK_SCLK (0 + 31) #define PIN_EINK_MOSI (0 + 29) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -#define PIN_EINK_PWR_ON (0 + 12) +// Controls power for all peripherals (eink + GPS + LoRa + Sensor) +#define PIN_POWER_EN (0 + 12) +#define PIN_POWER_EN1 (0 + 13) #define USE_EINK From 29335a18f58d10ca7f86a234c921f5b3b8e1e469 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Sat, 9 Mar 2024 14:55:02 +0200 Subject: [PATCH 0062/1377] Update variant.h (#3354) --- variants/heltec_esp32c3/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_esp32c3/variant.h b/variants/heltec_esp32c3/variant.h index de6462a38..6641f9d21 100644 --- a/variants/heltec_esp32c3/variant.h +++ b/variants/heltec_esp32c3/variant.h @@ -9,7 +9,7 @@ #define LED_PIN 18 // LED #define LED_INVERTED 1 -#define HAS_SCREEN 0 +#define HAS_SCREEN 1 #define HAS_GPS 0 #undef GPS_RX_PIN #undef GPS_TX_PIN From 3efd606ea7fcc7cda763eff3d5cbab0aa80c2447 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 9 Mar 2024 07:01:46 -0600 Subject: [PATCH 0063/1377] Bump to 2.3.0 --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 14d1884fb..8927d1781 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 -minor = 2 -build = 25 +minor = 3 +build = 0 From d5c11d18922301864617ff160468bb39751916e0 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 02:11:49 +1300 Subject: [PATCH 0064/1377] change dependency from private repo to meshtastic/GxEPD2 --- variants/heltec_wireless_paper/platformio.ini | 3 +-- variants/heltec_wireless_paper_v1/platformio.ini | 3 +-- variants/t-echo/platformio.ini | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 7aebef014..14275830a 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -16,8 +16,7 @@ build_flags = -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - ; https://github.com/meshtastic/GxEPD2/ - https://github.com/todd-herbert/meshtastic-GxEPD2#async ; Revert to meshtastic/firmware before submitting PR before final merge + https://github.com/meshtastic/GxEPD2/ adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 8cd870353..4e5e291e0 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -16,8 +16,7 @@ build_flags = ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - ; https://github.com/meshtastic/GxEPD2/ - https://github.com/todd-herbert/meshtastic-GxEPD2#async ; Revert to meshtastic/firmware before submitting PR before final merge + https://github.com/meshtastic/GxEPD2/ adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 94b6ee087..c97341a3b 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -20,7 +20,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} - https://github.com/meshtastic/GxEPD2#afce87a97dda1ac31d8a28dc8fa7c6f55dc96a61 + https://github.com/meshtastic/GxEPD2 adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs From 576f582cd9a41ac877f11129a834cac9d18e4b81 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 02:30:16 +1300 Subject: [PATCH 0065/1377] rename setFrameFlag() method --- src/graphics/EInkDynamicDisplay.cpp | 12 ++++++------ src/graphics/EInkDynamicDisplay.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 75db0e33f..c9bd5b22b 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -25,19 +25,19 @@ EInkDynamicDisplay::~EInkDynamicDisplay() // Screen requests a BACKGROUND frame void EInkDynamicDisplay::display() { - setFrameFlag(BACKGROUND); + addFrameFlag(BACKGROUND); update(); } // Screen requests a RESPONSIVE frame bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit) { - setFrameFlag(RESPONSIVE); + addFrameFlag(RESPONSIVE); return update(); // (Unutilized) Base class promises to return true if update ran } // Add flag for the next frame -void EInkDynamicDisplay::setFrameFlag(frameFlagTypes flag) +void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag) { // OR the new flag into the existing flags this->frameFlags = (frameFlagTypes)(this->frameFlags | flag); @@ -169,14 +169,14 @@ void EInkDynamicDisplay::checkForPromotion() switch (previousReason) { case ASYNC_REFRESH_BLOCKED_DEMANDFAST: - setFrameFlag(DEMAND_FAST); + addFrameFlag(DEMAND_FAST); break; case ASYNC_REFRESH_BLOCKED_COSMETIC: - setFrameFlag(COSMETIC); + addFrameFlag(COSMETIC); break; case ASYNC_REFRESH_BLOCKED_RESPONSIVE: case EXCEEDED_RATELIMIT_FAST: - setFrameFlag(RESPONSIVE); + addFrameFlag(RESPONSIVE); break; default: break; diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 3dc00ba7c..1eeb28f81 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -29,7 +29,7 @@ class EInkDynamicDisplay : public EInkDisplay COSMETIC = (1 << 2), // For splashes DEMAND_FAST = (1 << 3), // Special case only }; - void setFrameFlag(frameFlagTypes flag); + void addFrameFlag(frameFlagTypes flag); // Set the correct frame flag, then call universal "update()" method void display() override; From efd818fe903dc042aa92ae78ea940466689e67ae Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 03:07:13 +1300 Subject: [PATCH 0066/1377] move storeAndReset() to end of update() --- src/graphics/EInkDynamicDisplay.cpp | 14 +++++++------- src/graphics/EInkDynamicDisplay.h | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index c9bd5b22b..0a4d9691d 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -104,6 +104,7 @@ bool EInkDynamicDisplay::update() endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh) #endif + storeAndReset(); // Store the result of this loop for next time return refreshApproved; // (Unutilized) Base class promises to return true if update ran } @@ -117,10 +118,8 @@ bool EInkDynamicDisplay::determineMode() checkRateLimiting(); // If too soon for a new frame, or display busy, abort early - if (refresh == SKIPPED) { - storeAndReset(); + if (refresh == SKIPPED) return false; // No refresh - } // -- New frame is due -- @@ -152,12 +151,12 @@ bool EInkDynamicDisplay::determineMode() #endif // Return - call a refresh or not? - if (refresh == SKIPPED) { - storeAndReset(); + if (refresh == SKIPPED) return false; // Don't trigger a refresh - } else { - storeAndReset(); + else return true; // Do trigger a refresh +} + } } @@ -335,6 +334,7 @@ void EInkDynamicDisplay::hashImage() // Store the results of determineMode() for future use, and reset for next call void EInkDynamicDisplay::storeAndReset() { + previousFrameFlags = frameFlags; previousRefresh = refresh; previousReason = reason; diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 1eeb28f81..ad4d9bfd9 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -82,13 +82,14 @@ class EInkDynamicDisplay : public EInkDisplay void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call // What we are determining for this frame - frameFlagTypes frameFlags = BACKGROUND; // Frame type(s) - determineMode() input + frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used // What happened last time determineMode() ran - refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome - reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason + frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags + refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome + reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting) uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed! From 95b6f27d2a9dcd436070b81524fbe583e342aa6a Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 03:38:39 +1300 Subject: [PATCH 0067/1377] change order of determineMode() checks --- src/graphics/EInkDynamicDisplay.cpp | 34 ++++++++++++++--------------- src/graphics/EInkDynamicDisplay.h | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 0a4d9691d..7666820bd 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -130,11 +130,11 @@ bool EInkDynamicDisplay::determineMode() // Once mode determined, any remaining checks will bypass checkCosmetic(); checkDemandingFast(); - checkConsecutiveFastRefreshes(); #ifdef EINK_LIMIT_GHOSTING_PX checkExcessiveGhosting(); #endif checkFrameMatchesPrevious(); + checkConsecutiveFastRefreshes(); checkFastRequested(); if (refresh == UNSPECIFIED) @@ -244,21 +244,6 @@ void EInkDynamicDisplay::checkDemandingFast() } } -// Have too many fast-refreshes occured consecutively, since last full refresh? -void EInkDynamicDisplay::checkConsecutiveFastRefreshes() -{ - // If a decision was already reached, don't run the check - if (refresh != UNSPECIFIED) - return; - - // If too many FAST refreshes consecutively - force a FULL refresh - if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) { - refresh = FULL; - reason = EXCEEDED_LIMIT_FASTREFRESH; - LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n"); - } -} - // Does the new frame match the currently displayed image? void EInkDynamicDisplay::checkFrameMatchesPrevious() { @@ -283,7 +268,22 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious() // Not redrawn, not COSMETIC, not DEMAND_FAST refresh = SKIPPED; reason = FRAME_MATCHED_PREVIOUS; - LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS\n"); + LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags); +} + +// Have too many fast-refreshes occured consecutively, since last full refresh? +void EInkDynamicDisplay::checkConsecutiveFastRefreshes() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // If too many FAST refreshes consecutively - force a FULL refresh + if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) { + refresh = FULL; + reason = EXCEEDED_LIMIT_FASTREFRESH; + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n"); + } } // No objections, we can perform fast-refresh, if desired diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index ad4d9bfd9..b3e091fb2 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -73,8 +73,8 @@ class EInkDynamicDisplay : public EInkDisplay void checkRateLimiting(); // Is this frame too soon? void checkCosmetic(); // Was the COSMETIC flag set? void checkDemandingFast(); // Was the DEMAND_FAST flag set? - void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively? void checkFrameMatchesPrevious(); // Does the new frame match the existing display image? + void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively? void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND? void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting From 94794edd43392836ddbd88471f6575bc859ac676 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 03:43:07 +1300 Subject: [PATCH 0068/1377] add init code as a determineMode() check --- src/graphics/EInkDisplay2.cpp | 1 - src/graphics/EInkDynamicDisplay.cpp | 16 ++++++++++++++++ src/graphics/EInkDynamicDisplay.h | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 6f7885b45..0c5fab4fb 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -184,7 +184,6 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); - adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh) } #elif defined(PCA10059) { diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 7666820bd..8ff8dc4af 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -111,6 +111,7 @@ bool EInkDynamicDisplay::update() // Assess situation, pick a refresh type bool EInkDynamicDisplay::determineMode() { + checkInitialized(); checkForPromotion(); #if defined(HAS_EINK_ASYNCFULL) checkAsyncFullRefresh(); @@ -157,6 +158,21 @@ bool EInkDynamicDisplay::determineMode() return true; // Do trigger a refresh } +// Is this the very first frame? +void EInkDynamicDisplay::checkInitialized() +{ + if (!initialized) { + // Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect() + configForFullRefresh(); + + // Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write + adafruitDisplay->clearScreen(); + + LOG_DEBUG("initialized, "); + initialized = true; + + // Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep + addFrameFlag(DEMAND_FAST); } } diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index b3e091fb2..48540a132 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -69,6 +69,7 @@ class EInkDynamicDisplay : public EInkDisplay bool update(); // Trigger the display update - determine mode, then call base class // Checks as part of determineMode() + void checkInitialized(); // Is this the very first frame? void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh? void checkRateLimiting(); // Is this frame too soon? void checkCosmetic(); // Was the COSMETIC flag set? @@ -91,6 +92,7 @@ class EInkDynamicDisplay : public EInkDisplay refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason + bool initialized = false; // Have we drawn at least one frame yet? uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting) uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed! uint32_t previousImageHash = 0; // Hash of the previous update's frame From e232e3462c58bd67711a1706351f3d5a4adb61d6 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 03:48:59 +1300 Subject: [PATCH 0069/1377] add BLOCKING modifier to frameFlagTypes --- src/graphics/EInkDynamicDisplay.cpp | 21 +++++++++++++++++++-- src/graphics/EInkDynamicDisplay.h | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 8ff8dc4af..2b3659099 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -458,15 +458,32 @@ void EInkDynamicDisplay::checkAsyncFullRefresh() // Figure out who runs the post-update code void EInkDynamicDisplay::endOrDetach() { - if (previousRefresh == FULL) { // Note: previousRefresh is the refresh from this loop. + if (refresh == FULL) { asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. - LOG_DEBUG("Async full-refresh begins\n"); + + if (frameFlags & BLOCKING) + awaitRefresh(); + else + LOG_DEBUG("Async full-refresh begins\n"); } // Fast Refresh else EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. } + +// Hold control while an async refresh runs +void EInkDynamicDisplay::awaitRefresh() +{ + // Continually poll the BUSY pin + while (adafruitDisplay->epd2.isBusy()) + yield(); + + // End the full-refresh process + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag +} #endif // HAS_EINK_ASYNCFULL #endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 48540a132..ed5be70cd 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -28,6 +28,7 @@ class EInkDynamicDisplay : public EInkDisplay RESPONSIVE = (1 << 1), // For frames via forceDisplay() COSMETIC = (1 << 2), // For splashes DEMAND_FAST = (1 << 3), // Special case only + BLOCKING = (1 << 4), // Modifier - block while refresh runs }; void addFrameFlag(frameFlagTypes flag); @@ -112,6 +113,7 @@ class EInkDynamicDisplay : public EInkDisplay #if defined(HAS_EINK_ASYNCFULL) void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() + void awaitRefresh(); // Hold control while an async refresh runs void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() #endif From a9c07a4c016f330cf6a50c3dd8ed61ab0e535453 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 04:07:51 +1300 Subject: [PATCH 0070/1377] add frameFlags to LOG_DEBUG() messages --- src/graphics/EInkDynamicDisplay.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 2b3659099..d53969540 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -241,7 +241,7 @@ void EInkDynamicDisplay::checkCosmetic() if (frameFlags & COSMETIC) { refresh = FULL; reason = FLAGGED_COSMETIC; - LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC\n"); + LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags); } } @@ -256,7 +256,7 @@ void EInkDynamicDisplay::checkDemandingFast() if (frameFlags & DEMAND_FAST) { refresh = FAST; reason = FLAGGED_DEMAND_FAST; - LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST\n"); + LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags); } } @@ -276,7 +276,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious() if (frameFlags == BACKGROUND && fastRefreshCount > 0) { refresh = FULL; reason = REDRAW_WITH_FULL; - LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL\n"); + LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags); return; } #endif @@ -298,7 +298,7 @@ void EInkDynamicDisplay::checkConsecutiveFastRefreshes() if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) { refresh = FULL; reason = EXCEEDED_LIMIT_FASTREFRESH; - LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n"); + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags); } } @@ -313,7 +313,8 @@ void EInkDynamicDisplay::checkFastRequested() // If we want BACKGROUND to use fast. (FULL only when a limit is hit) refresh = FAST; reason = BACKGROUND_USES_FAST; - LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu\n", fastRefreshCount); + LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, + frameFlags); #else // If we do want to use FULL for BACKGROUND updates refresh = FULL; @@ -326,7 +327,7 @@ void EInkDynamicDisplay::checkFastRequested() if (frameFlags & RESPONSIVE) { refresh = FAST; reason = NO_OBJECTIONS; - LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu\n", fastRefreshCount); + LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags); } } @@ -407,7 +408,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting() if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) { refresh = FULL; reason = EXCEEDED_GHOSTINGLIMIT; - LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT\n"); + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags); } } From 94eb837ee8c506ef4838d720f343a51914cd0a94 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 04:14:45 +1300 Subject: [PATCH 0071/1377] function macro for tidier addFramFlag() calls --- src/graphics/EInkDynamicDisplay.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index ed5be70cd..495d20e7b 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -119,4 +119,10 @@ class EInkDynamicDisplay : public EInkDisplay #endif }; +// Tidier calls to addFrameFlag() from outside class +#define EINK_ADD_FRAMEFLAG(display, flag) static_cast(display)->addFrameFlag(EInkDynamicDisplay::flag) + +#else // !USE_EINK_DYNAMICDISPLAY +// Dummy-macro, removes the need for include guards +#define EINK_ADD_FRAMEFLAG(display, flag) #endif \ No newline at end of file From 7b703244351eb5bbee8542a638a04e12e965db28 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 05:00:51 +1300 Subject: [PATCH 0072/1377] handle special frames in Screen.cpp --- src/graphics/Screen.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3ffea4a60..7f20b5666 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -260,6 +260,10 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i /// Used on eink displays while in deep sleep static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { + // Next frame should use full-refresh, and block while running, else device will sleep before async callback + EINK_ADD_FRAMEFLAG(display, COSMETIC); + EINK_ADD_FRAMEFLAG(display, BLOCKING); + drawIconScreen("Sleeping...", display, state, x, y); } #endif @@ -1170,6 +1174,7 @@ int32_t Screen::runOnce() break; case Cmd::STOP_BLUETOOTH_PIN_SCREEN: case Cmd::STOP_BOOT_SCREEN: + EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame setFrames(); break; case Cmd::PRINT: @@ -1350,6 +1355,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin) { LOG_DEBUG("showing bluetooth screen\n"); showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame static FrameCallback frames[] = {drawFrameBluetooth}; snprintf(btPIN, sizeof(btPIN), "%06u", pin); @@ -1367,6 +1373,7 @@ void Screen::handleShutdownScreen() { LOG_DEBUG("showing shutdown screen\n"); showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Shutting down..."); @@ -1380,6 +1387,7 @@ void Screen::handleRebootScreen() { LOG_DEBUG("showing reboot screen\n"); showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Rebooting..."); @@ -1392,6 +1400,7 @@ void Screen::handleStartFirmwareUpdateScreen() { LOG_DEBUG("showing firmware screen\n"); showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame static FrameCallback frames[] = {drawFrameFirmware}; setFrameImmediateDraw(frames); From 3da7c0dba709c8b38a80371d59f1f693430cc65c Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sat, 9 Mar 2024 18:32:49 +0100 Subject: [PATCH 0073/1377] Add hops_away to JSON output (#3357) --- src/mqtt/MQTT.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 898607eca..b25075177 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -808,6 +808,8 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); if (mp->rx_snr != 0) jsonObj["snr"] = new JSONValue((float)mp->rx_snr); + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + jsonObj["hops_away"] = new JSONValue((uint)(mp->hop_start - mp->hop_limit)); // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); From 7167f1e04f3a7f76b4dc476bf36df581d6afeb9c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 9 Mar 2024 15:25:16 -0600 Subject: [PATCH 0074/1377] Add parens to macro (#3361) --- src/gps/GeoCoord.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index 9f911ed93..e811035db 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -11,7 +11,7 @@ #define PI 3.1415926535897932384626433832795 #define OLC_CODE_LEN 11 -#define DEG_CONVERT 180 / PI +#define DEG_CONVERT (180 / PI) // Helper functions // Raises a number to an exponent, handling negative exponents. From dced888492a915e84d74cbd3fc2afa5d07ff54d8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 9 Mar 2024 13:40:03 -0600 Subject: [PATCH 0075/1377] Add precision_bit sto json --- src/mqtt/MQTT.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index b25075177..619815e85 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -655,6 +655,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if ((int)decoded->VDOP) { msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP); } + if ((int)decoded->precision_bits) { + msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits); + } jsonObj["payload"] = new JSONValue(msgPayload); } else { LOG_ERROR("Error decoding protobuf for position message!\n"); From 3daae24d29d2962d38fa33f2b4577c09698b04db Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 13:43:57 +1300 Subject: [PATCH 0076/1377] fix fallback behavior for unmodified GxEPD2 Issues exposed by https://github.com/meshtastic/firmware/pull/3356#issuecomment-1986950317 --- src/graphics/EInkDynamicDisplay.cpp | 58 +++++++++++++++++------------ src/graphics/EInkDynamicDisplay.h | 2 +- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index d53969540..2f3c2fd0e 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -96,18 +96,45 @@ bool EInkDynamicDisplay::update() { // Detemine the refresh mode to use, and start the update bool refreshApproved = determineMode(); - if (refreshApproved) + if (refreshApproved) { EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system + storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach() + endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNC) + } else + storeAndReset(); // No update, no post-update code, just store the results -#if defined(HAS_EINK_ASYNCFULL) - if (refreshApproved) - endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh) -#endif - - storeAndReset(); // Store the result of this loop for next time return refreshApproved; // (Unutilized) Base class promises to return true if update ran } +// Figure out who runs the post-update code +void EInkDynamicDisplay::endOrDetach() +{ + // If the GxEPD2 version reports that it has the async modifications +#ifdef HAS_EINK_ASYNCFULL + if (previousRefresh == FULL) { + asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. + + if (previousFrameFlags & BLOCKING) + awaitRefresh(); + else + LOG_DEBUG("Async full-refresh begins\n"); + } + + // Fast Refresh + else if (previousRefresh == FAST) + EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. + + // Fallback - If using an unmodified version of GxEPD2 for some reason +#else + if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..) + LOG_WARN( + "GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in " + "variant's platformio.ini file\n"); + EInkDisplay::endUpdate(); + } +#endif +} + // Assess situation, pick a refresh type bool EInkDynamicDisplay::determineMode() { @@ -456,23 +483,6 @@ void EInkDynamicDisplay::checkAsyncFullRefresh() // It is only equipped to intercept calls to nextPage() } -// Figure out who runs the post-update code -void EInkDynamicDisplay::endOrDetach() -{ - if (refresh == FULL) { - asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. - - if (frameFlags & BLOCKING) - awaitRefresh(); - else - LOG_DEBUG("Async full-refresh begins\n"); - } - - // Fast Refresh - else - EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. -} - // Hold control while an async refresh runs void EInkDynamicDisplay::awaitRefresh() { diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 495d20e7b..dcae056c6 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -68,6 +68,7 @@ class EInkDynamicDisplay : public EInkDisplay void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type void adjustRefreshCounters(); // Update fastRefreshCount bool update(); // Trigger the display update - determine mode, then call base class + void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() // Checks as part of determineMode() void checkInitialized(); // Is this the very first frame? @@ -112,7 +113,6 @@ class EInkDynamicDisplay : public EInkDisplay // Conditional - async full refresh - only with modified meshtastic/GxEPD2 #if defined(HAS_EINK_ASYNCFULL) void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready - void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() void awaitRefresh(); // Hold control while an async refresh runs void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() From c0a3b20aa3eec657300b9c83b97dc1b3e8d8d00c Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 13:45:35 +1300 Subject: [PATCH 0077/1377] while drafting, build from todd-herbert/meshtastic-GxEPD2#async --- variants/heltec_wireless_paper/platformio.ini | 2 +- variants/heltec_wireless_paper_v1/platformio.ini | 2 +- variants/t-echo/platformio.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 14275830a..8ff475d06 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -16,7 +16,7 @@ build_flags = -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2/ + https://github.com/todd-herbert/meshtastic-GxEPD2#async adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 4e5e291e0..9327ed256 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -16,7 +16,7 @@ build_flags = ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2/ + https://github.com/todd-herbert/meshtastic-GxEPD2#async adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index c97341a3b..1a35f2f28 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -20,7 +20,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} - https://github.com/meshtastic/GxEPD2 + https://github.com/todd-herbert/meshtastic-GxEPD2#async adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs From 3da1b74a103df8cdead31f671eb46ac2ac0acf2a Mon Sep 17 00:00:00 2001 From: Andre K Date: Sun, 10 Mar 2024 05:39:37 -0300 Subject: [PATCH 0078/1377] refactor: always send range tests with zero hops --- src/modules/RangeTestModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index ecf4b70c7..b45068b45 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -113,7 +113,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) meshtastic_MeshPacket *p = allocDataPacket(); p->to = dest; p->decoded.want_response = wantReplies; - + p->hop_limit = 0; p->want_ack = true; packetSequence++; @@ -295,4 +295,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} \ No newline at end of file +} From 1032e16ea44d824fdfe49ffc0c4bf5c146bf712c Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Mon, 11 Mar 2024 01:02:03 +1300 Subject: [PATCH 0079/1377] reorder determineMode() checks --- src/graphics/EInkDynamicDisplay.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 2f3c2fd0e..732f6d3fb 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -99,7 +99,7 @@ bool EInkDynamicDisplay::update() if (refreshApproved) { EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach() - endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNC) + endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL) } else storeAndReset(); // No update, no post-update code, just store the results @@ -158,11 +158,11 @@ bool EInkDynamicDisplay::determineMode() // Once mode determined, any remaining checks will bypass checkCosmetic(); checkDemandingFast(); + checkFrameMatchesPrevious(); + checkConsecutiveFastRefreshes(); #ifdef EINK_LIMIT_GHOSTING_PX checkExcessiveGhosting(); #endif - checkFrameMatchesPrevious(); - checkConsecutiveFastRefreshes(); checkFastRequested(); if (refresh == UNSPECIFIED) From af9d14c370699c8a8cc618763052ecf12eccb461 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 10 Mar 2024 14:52:37 +0100 Subject: [PATCH 0080/1377] Periodic reporting of device information to a map via MQTT --- src/mesh/Channels.cpp | 20 +++++++ src/mesh/Channels.h | 3 + src/mesh/NodeDB.cpp | 7 ++- src/mesh/NodeDB.h | 8 ++- src/mesh/RadioInterface.cpp | 6 ++ src/mesh/RadioInterface.h | 3 + src/mqtt/MQTT.cpp | 107 +++++++++++++++++++++++++++++++----- src/mqtt/MQTT.h | 17 ++++-- 8 files changed, 149 insertions(+), 22 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 2d27c737d..b50ecf6ca 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -2,6 +2,7 @@ #include "CryptoEngine.h" #include "DisplayFormatters.h" #include "NodeDB.h" +#include "RadioInterface.h" #include "configuration.h" #include @@ -254,6 +255,25 @@ const char *Channels::getName(size_t chIndex) return channelName; } +bool Channels::hasDefaultChannel() +{ + // If we don't use a preset or we override the frequency, we don't have a default channel + if (!config.lora.use_preset || config.lora.override_frequency) + return false; + // Check if any of the channels are using the default name and PSK + for (size_t i = 0; i < getNumChannels(); i++) { + const auto &ch = getByIndex(i); + if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { + const char *name = getName(i); + const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + // Check if the name is the default derived from the modem preset and we use the default frequency slot + if (strcmp(name, presetName) == 0 && RadioInterface::uses_default_frequency_slot) + return true; + } + } + return false; +} + /** * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs. * The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 87a72e07b..0e11605c4 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -102,6 +102,9 @@ class Channels */ int16_t setActiveByIndex(ChannelIndex channelIndex); + // Returns true if we can be reached via a channel with the default settings given a region and modem preset + bool hasDefaultChannel(); + private: /** Given a channel index, change to use the crypto key specified by that index * diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 787c16a79..9d7647138 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -744,14 +744,17 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p) #define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline -size_t NodeDB::getNumOnlineMeshNodes() +size_t NodeDB::getNumOnlineMeshNodes(bool localOnly) { size_t numseen = 0; // FIXME this implementation is kinda expensive - for (int i = 0; i < *numMeshNodes; i++) + for (int i = 0; i < *numMeshNodes; i++) { + if (localOnly && meshNodes[i].via_mqtt) + continue; if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS) numseen++; + } return numseen; } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e24a971c1..8545b08d6 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -108,8 +108,10 @@ class NodeDB // get channel channel index we heard a nodeNum on, defaults to 0 if not found uint8_t getMeshNodeChannel(NodeNum n); - /// Return the number of nodes we've heard from recently (within the last 2 hrs?) - size_t getNumOnlineMeshNodes(); + /* Return the number of nodes we've heard from recently (within the last 2 hrs?) + * @param localOnly if true, ignore nodes heard via MQTT + */ + size_t getNumOnlineMeshNodes(bool localOnly = false); void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum); @@ -246,4 +248,4 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index c10eb26f6..7a2711251 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,5 +1,6 @@ #include "RadioInterface.h" #include "Channels.h" +#include "DisplayFormatters.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -143,6 +144,7 @@ const RegionInfo regions[] = { }; const RegionInfo *myRegion; +bool RadioInterface::uses_default_frequency_slot = true; static uint8_t bytes[MAX_RHPACKETLEN]; @@ -486,6 +488,10 @@ void RadioInterface::applyModemConfig() // channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1) int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; + // Check if we use the default frequency slot + RadioInterface::uses_default_frequency_slot = + channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels; + // Old frequency selection formula // float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index f85b3bfa5..ee4726d74 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -175,6 +175,9 @@ class RadioInterface /// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers virtual bool isIRQPending() { return false; } + // Whether we use the default frequency slot given our LoRa config (region and modem preset) + static bool uses_default_frequency_slot; + protected: int8_t power = 17; // Set by applyModemConfig() diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 898607eca..426934be8 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -77,8 +77,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); p->decoded.payload.size = jsonPayloadStr.length(); - meshtastic_MeshPacket *packet = packetPool.allocCopy(*p); - service.sendToMesh(packet, RX_SRC_LOCAL); + service.sendToMesh(p, RX_SRC_LOCAL); } else { LOG_WARN("Received MQTT json payload too long, dropping\n"); } @@ -192,6 +191,11 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) jsonTopic = "msh" + jsonTopic; } + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { + map_position_precision = moduleConfig.mqtt.map_report_settings.position_precision; + map_publish_interval_secs = moduleConfig.mqtt.map_report_settings.publish_interval_secs; + } + #ifdef HAS_NETWORKING if (!moduleConfig.mqtt.proxy_to_client_enabled) pubSub.setCallback(mqttCallback); @@ -365,27 +369,30 @@ void MQTT::sendSubscriptions() bool MQTT::wantsLink() const { - bool hasChannel = false; + bool hasChannelorMapReport = false; if (moduleConfig.mqtt.enabled) { - // No need for link if no channel needed it - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) { - const auto &ch = channels.getByIndex(i); - if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) { - hasChannel = true; - break; + hasChannelorMapReport = moduleConfig.mqtt.map_reporting_enabled; + if (!hasChannelorMapReport) { + // No need for link if no channel needed it + size_t numChan = channels.getNumChannels(); + for (size_t i = 0; i < numChan; i++) { + const auto &ch = channels.getByIndex(i); + if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) { + hasChannelorMapReport = true; + break; + } } } } - if (hasChannel && moduleConfig.mqtt.proxy_to_client_enabled) + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) return true; #if HAS_WIFI - return hasChannel && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannel && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif return false; } @@ -397,6 +404,8 @@ int32_t MQTT::runOnce() bool wantConnection = wantsLink(); + perhapsReportToMap(); + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server if (moduleConfig.mqtt.proxy_to_client_enabled) { publishQueuedMessages(); @@ -536,6 +545,78 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & } } +void MQTT::perhapsReportToMap() +{ + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; + + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); + return; + } + + if (millis() - last_report_to_map < map_publish_interval_secs * 1000) { + return; + } else { + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; + + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB.getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); + + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } else { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; + + mapReport.num_online_local_nodes = nodeDB.getNumOnlineMeshNodes(true); + + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; + + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + + LOG_INFO("MQTT Publish map report to %s\n", statusTopic.c_str()); + publish(statusTopic.c_str(), bytes, numBytes, false); + + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); + + // Update the last report time + last_report_to_map = millis(); + } +} + // converts a downstream packet into a json message std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) { diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 2b803e3fc..1599c7ae8 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -79,10 +79,16 @@ class MQTT : private concurrency::OSThread virtual int32_t runOnce() override; private: - std::string statusTopic = "/2/stat/"; - std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID - std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID - /** return true if we have a channel that wants uplink/downlink + std::string statusTopic = "/2/stat/"; // For "online"/"offline" message and MapReport + std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID + std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID + + // For map reporting (only applies when enabled) + uint32_t last_report_to_map = 0; + uint32_t map_position_precision = 32; // default to full precision + uint32_t map_publish_interval_secs = 60 * 15; // default to 15 minutes + + /** return true if we have a channel that wants uplink/downlink or map reporting is enabled */ bool wantsLink() const; @@ -102,6 +108,9 @@ class MQTT : private concurrency::OSThread void publishStatus(); void publishQueuedMessages(); + // Check if we should report unencrypted information about our node for consumption by a map + void perhapsReportToMap(); + // returns true if this is a valid JSON envelope which we accept on downlink bool isValidJsonEnvelope(JSONObject &json); From 69dcc948b9631e798f232c3669e114a8da0ab9a7 Mon Sep 17 00:00:00 2001 From: caveman99 Date: Sun, 10 Mar 2024 14:39:40 +0000 Subject: [PATCH 0081/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- .../generated/meshtastic/module_config.pb.c | 5 +- .../generated/meshtastic/module_config.pb.h | 42 ++++++++++-- src/mesh/generated/meshtastic/mqtt.pb.c | 3 + src/mesh/generated/meshtastic/mqtt.pb.h | 68 +++++++++++++++++++ src/mesh/generated/meshtastic/portnums.pb.h | 2 + 8 files changed, 117 insertions(+), 9 deletions(-) diff --git a/protobufs b/protobufs index 5a97acb17..00332412b 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5a97acb17543a10e114675a205e3274a83e721af +Subproject commit 00332412b238fe559175a6e83fdf8d31fa5e209a diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index ca4b2176b..556821e1c 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -324,7 +324,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_DeviceState_size 17571 #define meshtastic_NodeInfoLite_size 158 #define meshtastic_NodeRemoteHardwarePin_size 29 -#define meshtastic_OEMStore_size 3246 +#define meshtastic_OEMStore_size 3262 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 7d39da01f..2e22cb1e4 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define meshtastic_LocalConfig_size 469 -#define meshtastic_LocalModuleConfig_size 631 +#define meshtastic_LocalModuleConfig_size 647 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.c b/src/mesh/generated/meshtastic/module_config.pb.c index 38965f3e2..a75c3fb59 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.c +++ b/src/mesh/generated/meshtastic/module_config.pb.c @@ -6,12 +6,15 @@ #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, 2) PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_MapReportSettings, meshtastic_ModuleConfig_MapReportSettings, AUTO) + + PB_BIND(meshtastic_ModuleConfig_RemoteHardwareConfig, meshtastic_ModuleConfig_RemoteHardwareConfig, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index edfd56e4c..2e1c25c7f 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -84,6 +84,14 @@ typedef enum _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar { } meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar; /* Struct definitions */ +/* Settings for reporting unencrypted information about our node to a map via MQTT */ +typedef struct _meshtastic_ModuleConfig_MapReportSettings { + /* How often we should report our info to the map (in seconds) */ + uint32_t publish_interval_secs; + /* Bits of precision for the location sent (default of 32 is full precision). */ + uint32_t position_precision; +} meshtastic_ModuleConfig_MapReportSettings; + /* MQTT Client Config */ typedef struct _meshtastic_ModuleConfig_MQTTConfig { /* If a meshtastic node is able to reach the internet it will normally attempt to gateway any channels that are marked as @@ -114,6 +122,11 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { char root[16]; /* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection */ bool proxy_to_client_enabled; + /* If true, we will periodically report unencrypted information about our node to a map via MQTT */ + bool map_reporting_enabled; + /* Settings for reporting information about our node to a map via MQTT */ + bool has_map_report_settings; + meshtastic_ModuleConfig_MapReportSettings map_report_settings; } meshtastic_ModuleConfig_MQTTConfig; /* NeighborInfoModule Config */ @@ -427,6 +440,7 @@ extern "C" { + #define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud @@ -447,7 +461,8 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ModuleConfig_init_default {0, {meshtastic_ModuleConfig_MQTTConfig_init_default}} -#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0} +#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_default} +#define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0} @@ -462,7 +477,8 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} -#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0} +#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} +#define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0} @@ -478,6 +494,8 @@ extern "C" { #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_ModuleConfig_MapReportSettings_publish_interval_secs_tag 1 +#define meshtastic_ModuleConfig_MapReportSettings_position_precision_tag 2 #define meshtastic_ModuleConfig_MQTTConfig_enabled_tag 1 #define meshtastic_ModuleConfig_MQTTConfig_address_tag 2 #define meshtastic_ModuleConfig_MQTTConfig_username_tag 3 @@ -487,6 +505,8 @@ extern "C" { #define meshtastic_ModuleConfig_MQTTConfig_tls_enabled_tag 7 #define meshtastic_ModuleConfig_MQTTConfig_root_tag 8 #define meshtastic_ModuleConfig_MQTTConfig_proxy_to_client_enabled_tag 9 +#define meshtastic_ModuleConfig_MQTTConfig_map_reporting_enabled_tag 10 +#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_tag 11 #define meshtastic_ModuleConfig_NeighborInfoConfig_enabled_tag 1 #define meshtastic_ModuleConfig_NeighborInfoConfig_update_interval_tag 2 #define meshtastic_ModuleConfig_DetectionSensorConfig_enabled_tag 1 @@ -623,9 +643,18 @@ X(a, STATIC, SINGULAR, BOOL, encryption_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) \ X(a, STATIC, SINGULAR, BOOL, tls_enabled, 7) \ X(a, STATIC, SINGULAR, STRING, root, 8) \ -X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) +X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) \ +X(a, STATIC, SINGULAR, BOOL, map_reporting_enabled, 10) \ +X(a, STATIC, OPTIONAL, MESSAGE, map_report_settings, 11) #define meshtastic_ModuleConfig_MQTTConfig_CALLBACK NULL #define meshtastic_ModuleConfig_MQTTConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_MSGTYPE meshtastic_ModuleConfig_MapReportSettings + +#define meshtastic_ModuleConfig_MapReportSettings_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, publish_interval_secs, 1) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 2) +#define meshtastic_ModuleConfig_MapReportSettings_CALLBACK NULL +#define meshtastic_ModuleConfig_MapReportSettings_DEFAULT NULL #define meshtastic_ModuleConfig_RemoteHardwareConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -764,6 +793,7 @@ X(a, STATIC, SINGULAR, UENUM, type, 3) extern const pb_msgdesc_t meshtastic_ModuleConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_MQTTConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_MapReportSettings_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_RemoteHardwareConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg; @@ -781,6 +811,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ModuleConfig_fields &meshtastic_ModuleConfig_msg #define meshtastic_ModuleConfig_MQTTConfig_fields &meshtastic_ModuleConfig_MQTTConfig_msg +#define meshtastic_ModuleConfig_MapReportSettings_fields &meshtastic_ModuleConfig_MapReportSettings_msg #define meshtastic_ModuleConfig_RemoteHardwareConfig_fields &meshtastic_ModuleConfig_RemoteHardwareConfig_msg #define meshtastic_ModuleConfig_NeighborInfoConfig_fields &meshtastic_ModuleConfig_NeighborInfoConfig_msg #define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg @@ -801,7 +832,8 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_CannedMessageConfig_size 49 #define meshtastic_ModuleConfig_DetectionSensorConfig_size 44 #define meshtastic_ModuleConfig_ExternalNotificationConfig_size 42 -#define meshtastic_ModuleConfig_MQTTConfig_size 222 +#define meshtastic_ModuleConfig_MQTTConfig_size 238 +#define meshtastic_ModuleConfig_MapReportSettings_size 12 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 8 #define meshtastic_ModuleConfig_PaxcounterConfig_size 8 #define meshtastic_ModuleConfig_RangeTestConfig_size 10 @@ -809,7 +841,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 22 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 -#define meshtastic_ModuleConfig_size 225 +#define meshtastic_ModuleConfig_size 241 #define meshtastic_RemoteHardwarePin_size 21 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mqtt.pb.c b/src/mesh/generated/meshtastic/mqtt.pb.c index 3046e6109..a43f364e1 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.c +++ b/src/mesh/generated/meshtastic/mqtt.pb.c @@ -9,4 +9,7 @@ PB_BIND(meshtastic_ServiceEnvelope, meshtastic_ServiceEnvelope, AUTO) +PB_BIND(meshtastic_MapReport, meshtastic_MapReport, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h index 12e83c724..8ca570d78 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.h +++ b/src/mesh/generated/meshtastic/mqtt.pb.h @@ -5,6 +5,7 @@ #define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #include #include "meshtastic/mesh.pb.h" +#include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -23,6 +24,38 @@ typedef struct _meshtastic_ServiceEnvelope { char *gateway_id; } meshtastic_ServiceEnvelope; +/* Information about a node intended to be reported unencrypted to a map using MQTT. */ +typedef struct _meshtastic_MapReport { + /* A full name for this user, i.e. "Kevin Hester" */ + char long_name[40]; + /* A VERY short name, ideally two characters. + Suitable for a tiny OLED screen */ + char short_name[5]; + /* Role of the node that applies specific settings for a particular use-case */ + meshtastic_Config_DeviceConfig_Role role; + /* Hardware model of the node, i.e. T-Beam, Heltec V3, etc... */ + meshtastic_HardwareModel hw_model; + /* Device firmware version string */ + char firmware_version[18]; + /* The region code for the radio (US, CN, EU433, etc...) */ + meshtastic_Config_LoRaConfig_RegionCode region; + /* Modem preset used by the radio (LongFast, MediumSlow, etc...) */ + meshtastic_Config_LoRaConfig_ModemPreset modem_preset; + /* Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...) + and it uses the default frequency slot given the region and modem preset. */ + bool has_default_channel; + /* Latitude: multiply by 1e-7 to get degrees in floating point */ + int32_t latitude_i; + /* Longitude: multiply by 1e-7 to get degrees in floating point */ + int32_t longitude_i; + /* Altitude in meters above MSL */ + int32_t altitude; + /* Indicates the bits of precision for latitude and longitude set by the sending node */ + uint32_t position_precision; + /* Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT) */ + uint16_t num_online_local_nodes; +} meshtastic_MapReport; + #ifdef __cplusplus extern "C" { @@ -30,12 +63,27 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ServiceEnvelope_init_default {NULL, NULL, NULL} +#define meshtastic_MapReport_init_default {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0} #define meshtastic_ServiceEnvelope_init_zero {NULL, NULL, NULL} +#define meshtastic_MapReport_init_zero {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_ServiceEnvelope_packet_tag 1 #define meshtastic_ServiceEnvelope_channel_id_tag 2 #define meshtastic_ServiceEnvelope_gateway_id_tag 3 +#define meshtastic_MapReport_long_name_tag 1 +#define meshtastic_MapReport_short_name_tag 2 +#define meshtastic_MapReport_role_tag 3 +#define meshtastic_MapReport_hw_model_tag 4 +#define meshtastic_MapReport_firmware_version_tag 5 +#define meshtastic_MapReport_region_tag 6 +#define meshtastic_MapReport_modem_preset_tag 7 +#define meshtastic_MapReport_has_default_channel_tag 8 +#define meshtastic_MapReport_latitude_i_tag 9 +#define meshtastic_MapReport_longitude_i_tag 10 +#define meshtastic_MapReport_altitude_tag 11 +#define meshtastic_MapReport_position_precision_tag 12 +#define meshtastic_MapReport_num_online_local_nodes_tag 13 /* Struct field encoding specification for nanopb */ #define meshtastic_ServiceEnvelope_FIELDLIST(X, a) \ @@ -46,13 +94,33 @@ X(a, POINTER, SINGULAR, STRING, gateway_id, 3) #define meshtastic_ServiceEnvelope_DEFAULT NULL #define meshtastic_ServiceEnvelope_packet_MSGTYPE meshtastic_MeshPacket +#define meshtastic_MapReport_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, long_name, 1) \ +X(a, STATIC, SINGULAR, STRING, short_name, 2) \ +X(a, STATIC, SINGULAR, UENUM, role, 3) \ +X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \ +X(a, STATIC, SINGULAR, STRING, firmware_version, 5) \ +X(a, STATIC, SINGULAR, UENUM, region, 6) \ +X(a, STATIC, SINGULAR, UENUM, modem_preset, 7) \ +X(a, STATIC, SINGULAR, BOOL, has_default_channel, 8) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 9) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 10) \ +X(a, STATIC, SINGULAR, INT32, altitude, 11) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 12) \ +X(a, STATIC, SINGULAR, UINT32, num_online_local_nodes, 13) +#define meshtastic_MapReport_CALLBACK NULL +#define meshtastic_MapReport_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_ServiceEnvelope_msg; +extern const pb_msgdesc_t meshtastic_MapReport_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ServiceEnvelope_fields &meshtastic_ServiceEnvelope_msg +#define meshtastic_MapReport_fields &meshtastic_MapReport_msg /* Maximum encoded size of messages (where known) */ /* meshtastic_ServiceEnvelope_size depends on runtime parameters */ +#define meshtastic_MapReport_size 108 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 3f3e9aaee..f576c7893 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -122,6 +122,8 @@ typedef enum _meshtastic_PortNum { /* ATAK Plugin Portnum for payloads from the official Meshtastic ATAK plugin */ meshtastic_PortNum_ATAK_PLUGIN = 72, + /* Provides unencrypted information about a node for consumption by a map via MQTT */ + meshtastic_PortNum_MAP_REPORT_APP = 73, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ From b45a912409c067b9ea6c0c41799a1128800cab4b Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 10 Mar 2024 15:56:00 +0100 Subject: [PATCH 0082/1377] Use dedicated `map` topic --- src/mqtt/MQTT.cpp | 6 ++++-- src/mqtt/MQTT.h | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index b33132aa4..2de35971a 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -185,10 +185,12 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) statusTopic = moduleConfig.mqtt.root + statusTopic; cryptTopic = moduleConfig.mqtt.root + cryptTopic; jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + jsonTopic; } else { statusTopic = "msh" + statusTopic; cryptTopic = "msh" + cryptTopic; jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; } if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { @@ -605,8 +607,8 @@ void MQTT::perhapsReportToMap() static uint8_t bytes[meshtastic_MeshPacket_size + 64]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); - LOG_INFO("MQTT Publish map report to %s\n", statusTopic.c_str()); - publish(statusTopic.c_str(), bytes, numBytes, false); + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); // Release the allocated memory for ServiceEnvelope and MeshPacket mqttPool.release(se); diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index b665a6efc..eeeb00d92 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -79,9 +79,10 @@ class MQTT : private concurrency::OSThread virtual int32_t runOnce() override; private: - std::string statusTopic = "/2/stat/"; // For "online"/"offline" message and MapReport + std::string statusTopic = "/2/stat/"; // For "online"/"offline" message std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID + std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages // For map reporting (only applies when enabled) uint32_t last_report_to_map = 0; From cb7407e06ba88f8c9d1aec45e0ccae2101cc5d99 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 10 Mar 2024 16:04:59 +0100 Subject: [PATCH 0083/1377] Don't need to check all channels if not using default frequency slot --- src/mesh/Channels.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index b50ecf6ca..3e9c78241 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -257,8 +257,8 @@ const char *Channels::getName(size_t chIndex) bool Channels::hasDefaultChannel() { - // If we don't use a preset or we override the frequency, we don't have a default channel - if (!config.lora.use_preset || config.lora.override_frequency) + // If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel + if (!config.lora.use_preset || !RadioInterface::uses_default_frequency_slot || config.lora.override_frequency) return false; // Check if any of the channels are using the default name and PSK for (size_t i = 0; i < getNumChannels(); i++) { @@ -266,8 +266,8 @@ bool Channels::hasDefaultChannel() if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { const char *name = getName(i); const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); - // Check if the name is the default derived from the modem preset and we use the default frequency slot - if (strcmp(name, presetName) == 0 && RadioInterface::uses_default_frequency_slot) + // Check if the name is the default derived from the modem preset + if (strcmp(name, presetName) == 0) return true; } } From fb4faf790ba28fd09344c61a713728e647f6c711 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Tue, 5 Mar 2024 23:39:43 +0100 Subject: [PATCH 0084/1377] split query of paxcounter data from sending funcionality; don't cummulate (count mode != 1); use flag to signal changed count data --- src/modules/esp32/PaxcounterModule.cpp | 24 ++++++++++++++++++------ src/modules/esp32/PaxcounterModule.h | 3 +++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 2182ed124..29edb069d 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -7,8 +7,6 @@ PaxcounterModule *paxcounterModule; -void NullFunc(){}; - // paxcounterModule->sendInfo(NODENUM_BROADCAST); PaxcounterModule::PaxcounterModule() @@ -19,10 +17,14 @@ PaxcounterModule::PaxcounterModule() bool PaxcounterModule::sendInfo(NodeNum dest) { - libpax_counter_count(&count_from_libpax); - LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%d\n", count_from_libpax.wifi_count, count_from_libpax.ble_count, + if (paxcounterModule->reportedDataSent) + return false; + + LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = true; + meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; pl.ble = count_from_libpax.ble_count; @@ -55,6 +57,14 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply() return allocDataProtobuf(pl); } +void PaxcounterModule::handlePaxCounterReportRequest() +{ + // libpax_counter_count(&paxcounterModule->count_from_libpax); + LOG_INFO("(Reading): libPax reported new data: wifi=%d; ble=%d; uptime=%lu\n", paxcounterModule->count_from_libpax.wifi_count, + paxcounterModule->count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = false; +} + int32_t PaxcounterModule::runOnce() { if (isActive()) { @@ -76,12 +86,14 @@ int32_t PaxcounterModule::runOnce() libpax_update_config(&configuration); // internal processing initialization - libpax_counter_init(NullFunc, &count_from_libpax, UINT16_MAX, 1); + libpax_counter_init(handlePaxCounterReportRequest, &count_from_libpax, + moduleConfig.paxcounter.paxcounter_update_interval, 0); libpax_counter_start(); } else { sendInfo(NODENUM_BROADCAST); } - return getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs); + // we check every second if the counter had new data to send + return 1000; } else { return disable(); } diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h index e72f87450..67d47be56 100644 --- a/src/modules/esp32/PaxcounterModule.h +++ b/src/modules/esp32/PaxcounterModule.h @@ -13,10 +13,13 @@ class PaxcounterModule : private concurrency::OSThread, public ProtobufModule { bool firstTime = true; + bool reportedDataSent = true; public: PaxcounterModule(); + static void handlePaxCounterReportRequest(); + protected: struct count_payload_t count_from_libpax = {0, 0, 0}; virtual int32_t runOnce() override; From 73c77b663c0bff89aa28817d27faf667201088f6 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Tue, 5 Mar 2024 23:48:52 +0100 Subject: [PATCH 0085/1377] fix typo --- src/modules/esp32/PaxcounterModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 29edb069d..94fcca36f 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -77,7 +77,7 @@ int32_t PaxcounterModule::runOnce() libpax_default_config(&configuration); configuration.blecounter = 1; - configuration.blescantime = 0; // infinit + configuration.blescantime = 0; // infinite configuration.wificounter = 1; configuration.wifi_channel_map = WIFI_CHANNEL_ALL; configuration.wifi_channel_switch_interval = 50; From 09e08e0091dc2c4ef2dfc2debdf7bda7b4f6f5b2 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Wed, 6 Mar 2024 19:15:04 +0100 Subject: [PATCH 0086/1377] add some documentation, cleanup --- src/modules/esp32/PaxcounterModule.cpp | 39 +++++++++++++++++--------- src/modules/esp32/PaxcounterModule.h | 7 +++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 94fcca36f..e718d6261 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -7,7 +7,18 @@ PaxcounterModule *paxcounterModule; -// paxcounterModule->sendInfo(NODENUM_BROADCAST); +/** + * Callback function for libpax. + * We only clear our sent flag here, since this function is called from another thread, so we + * cannot send to the mesh directly. + */ +void PaxcounterModule::handlePaxCounterReportRequest() +{ + // The libpax library already updated our data structure, just before invoking this callback. + LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n", + paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = false; +} PaxcounterModule::PaxcounterModule() : concurrency::OSThread("PaxcounterModule"), @@ -15,15 +26,20 @@ PaxcounterModule::PaxcounterModule() { } +/** + * Send the Pax information to the mesh if we got new data from libpax. + * This is called periodically from our runOnce() method and will actually send the data to the mesh + * if libpax updated it since the last transmission through the callback. + * @param dest - destination node (usually NODENUM_BROADCAST) + * @return false if sending is unnecessary, true if information was sent + */ bool PaxcounterModule::sendInfo(NodeNum dest) { if (paxcounterModule->reportedDataSent) return false; - LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, count_from_libpax.ble_count, - millis() / 1000); - - paxcounterModule->reportedDataSent = true; + LOG_INFO("PaxcounterModule: sending pax info wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, + count_from_libpax.ble_count, millis() / 1000); meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; @@ -33,9 +49,12 @@ bool PaxcounterModule::sendInfo(NodeNum dest) meshtastic_MeshPacket *p = allocDataProtobuf(pl); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_DEFAULT; service.sendToMesh(p, RX_SRC_LOCAL, true); + + paxcounterModule->reportedDataSent = true; + return true; } @@ -57,14 +76,6 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply() return allocDataProtobuf(pl); } -void PaxcounterModule::handlePaxCounterReportRequest() -{ - // libpax_counter_count(&paxcounterModule->count_from_libpax); - LOG_INFO("(Reading): libPax reported new data: wifi=%d; ble=%d; uptime=%lu\n", paxcounterModule->count_from_libpax.wifi_count, - paxcounterModule->count_from_libpax.ble_count, millis() / 1000); - paxcounterModule->reportedDataSent = false; -} - int32_t PaxcounterModule::runOnce() { if (isActive()) { diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h index 67d47be56..ebd6e7191 100644 --- a/src/modules/esp32/PaxcounterModule.h +++ b/src/modules/esp32/PaxcounterModule.h @@ -8,18 +8,19 @@ #include /** - * A simple example module that just replies with "Message received" to any message it receives. + * Wrapper module for the estimate passenger (PAX) count library (https://github.com/dbinfrago/libpax) which + * implements the core functionality of the ESP32 Paxcounter project (https://github.com/cyberman54/ESP32-Paxcounter) */ class PaxcounterModule : private concurrency::OSThread, public ProtobufModule { bool firstTime = true; bool reportedDataSent = true; + static void handlePaxCounterReportRequest(); + public: PaxcounterModule(); - static void handlePaxCounterReportRequest(); - protected: struct count_payload_t count_from_libpax = {0, 0, 0}; virtual int32_t runOnce() override; From 26691c0be7145b726eafad236def154cad3122d5 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Fri, 8 Mar 2024 23:48:56 +0100 Subject: [PATCH 0087/1377] include requested change and suggestions on PR from @caveman99 --- src/modules/esp32/PaxcounterModule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index e718d6261..580fc46e1 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -18,6 +18,7 @@ void PaxcounterModule::handlePaxCounterReportRequest() LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n", paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000); paxcounterModule->reportedDataSent = false; + paxcounterModule->setIntervalFromNow(0); } PaxcounterModule::PaxcounterModule() @@ -49,7 +50,7 @@ bool PaxcounterModule::sendInfo(NodeNum dest) meshtastic_MeshPacket *p = allocDataProtobuf(pl); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_DEFAULT; + p->priority = meshtastic_MeshPacket_Priority_MIN; service.sendToMesh(p, RX_SRC_LOCAL, true); @@ -103,8 +104,7 @@ int32_t PaxcounterModule::runOnce() } else { sendInfo(NODENUM_BROADCAST); } - // we check every second if the counter had new data to send - return 1000; + return getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs); } else { return disable(); } From 766beefbc5796f700e5d6d94f7f9d7682cc0a4c8 Mon Sep 17 00:00:00 2001 From: Kevin Cai Date: Sun, 10 Mar 2024 18:24:32 -0400 Subject: [PATCH 0088/1377] Update AccelerometerThread.h to work with T-Watch S3 --- src/AccelerometerThread.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 744f0ad64..9898f4d49 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -103,12 +103,21 @@ class AccelerometerThread : public concurrency::OSThread #endif struct bma423_axes_remap remap_data; +#ifdef T_WATCH_S3 + remap_data.x_axis = 1; + remap_data.x_axis_sign = 0; + remap_data.y_axis = 0; + remap_data.y_axis_sign = 0; + remap_data.z_axis = 2; + remap_data.z_axis_sign = 1; +#else remap_data.x_axis = 0; remap_data.x_axis_sign = 1; remap_data.y_axis = 1; remap_data.y_axis_sign = 0; remap_data.z_axis = 2; remap_data.z_axis_sign = 1; +#endif // Need to raise the wrist function, need to set the correct axis bmaSensor.setRemapAxes(&remap_data); // sensor.enableFeature(BMA423_STEP_CNTR, true); @@ -171,4 +180,4 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LIS3DH lis; }; -} // namespace concurrency \ No newline at end of file +} // namespace concurrency From b65b9e5d659b6bd8f4eb3314d8cf24f4892e47f4 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Thu, 7 Mar 2024 16:03:01 +0200 Subject: [PATCH 0089/1377] Include esp32c3 build step --- .github/workflows/build_esp32.yml | 1 + .github/workflows/build_esp32_c3.yml | 60 ++++++++++++++++++++++++++++ .github/workflows/build_esp32_s3.yml | 1 + .github/workflows/main_matrix.yml | 14 ++++++- 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build_esp32_c3.yml diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index c9664152e..31f0dd5a0 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -35,6 +35,7 @@ jobs: sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - name: Build ESP32 run: bin/build-esp32.sh ${{ inputs.board }} diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml new file mode 100644 index 000000000..f9164b96a --- /dev/null +++ b/.github/workflows/build_esp32_c3.yml @@ -0,0 +1,60 @@ +name: Build ESP32-C3 + +on: + workflow_call: + inputs: + board: + required: true + type: string + +jobs: + build-esp32-c3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + with: + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + - name: Remove debug flags for release + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini + - name: Build ESP32 + run: bin/build-esp32.sh ${{ inputs.board }} + + - name: Pull OTA Firmware + uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + with: + repo: meshtastic/firmware-ota + file: firmware-c3.bin + target: release/bleota-c3.bin + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v3 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + path: | + release/*.bin + release/*.elf diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 9611dd5b8..f603a6a31 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -34,6 +34,7 @@ jobs: sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - name: Build ESP32 run: bin/build-esp32.sh ${{ inputs.board }} diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index e77b4a261..03d47f18e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -67,7 +67,6 @@ jobs: - board: tlora-v2-1-1_6-tcxo - board: tlora-v2-1-1_8 - board: tbeam - - board: heltec-ht62-esp32c3-sx1262 - board: heltec-v2_0 - board: heltec-v2_1 - board: tbeam0_7 @@ -105,6 +104,16 @@ jobs: with: board: ${{ matrix.board }} + build-esp32-c3: + strategy: + fail-fast: false + matrix: + include: + - board: heltec-ht62-esp32c3-sx1262 + uses: ./.github/workflows/build_esp32_c3.yml + with: + board: ${{ matrix.board }} + build-nrf52: strategy: fail-fast: false @@ -226,6 +235,7 @@ jobs: [ build-esp32, build-esp32-s3, + build-esp32-c3, build-nrf52, build-raspbian, build-native, @@ -251,7 +261,7 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml + run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml - name: Repackage in single firmware zip uses: actions/upload-artifact@v3 From b3ec3c20fbd80081328e44e7d4ce084afd42fa58 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Thu, 7 Mar 2024 16:31:20 +0200 Subject: [PATCH 0090/1377] Update device-install.sh files to account for bleota-c3.bin file --- bin/device-install.bat | 8 ++++++-- bin/device-install.sh | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index c7d8a10cf..cb652346f 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -31,9 +31,13 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% ( %PYTHON% -m esptool --baud 115200 erase_flash %PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME% - @REM Account for S3 board's different OTA partition + @REM Account for S3 and C3 board's different OTA partition IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% ( - %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin + IF x%FILENAME:esp32c3=%==x%FILENAME% ( + %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin + ) else ( + %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-c3.bin + ) ) else ( %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin ) diff --git a/bin/device-install.sh b/bin/device-install.sh index 35d99286d..52a27309a 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -51,7 +51,11 @@ if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then - "$PYTHON" -m esptool write_flash 0x260000 bleota.bin + if [ ! -z "${FILENAME##*"esp32c3"*}" ]; then + "$PYTHON" -m esptool write_flash 0x260000 bleota.bin + else + "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin + fi else "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin fi From a493ab526f82979890d97e9354a2753eec8c06a7 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Fri, 8 Mar 2024 10:50:03 +0200 Subject: [PATCH 0091/1377] Trunk fmt to correct failing PR check for device-install.sh --- bin/device-install.sh | 56 ++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/bin/device-install.sh b/bin/device-install.sh index 52a27309a..a4ee20c9c 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -1,12 +1,12 @@ #!/bin/sh -PYTHON=${PYTHON:-$(which python3 python|head -n 1)} +PYTHON=${PYTHON:-$(which python3 python | head -n 1)} set -e # Usage info show_help() { -cat << EOF + cat <&2 - exit 1 - ;; - esac + case "${opt}" in + h) + show_help + exit 0 + ;; + p) + export ESPTOOL_PORT=${OPTARG} + ;; + P) + PYTHON=${OPTARG} + ;; + f) + FILENAME=${OPTARG} + ;; + *) + echo "Invalid flag." + show_help >&2 + exit 1 + ;; + esac done -shift "$((OPTIND-1))" +shift "$((OPTIND - 1))" [ -z "$FILENAME" -a -n "$1" ] && { - FILENAME=$1 - shift + FILENAME=$1 + shift } if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - "$PYTHON" -m esptool erase_flash - "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} + "$PYTHON" -m esptool erase_flash + "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then if [ ! -z "${FILENAME##*"esp32c3"*}" ]; then @@ -57,9 +59,9 @@ if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin fi else - "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin + "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin fi - "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin + "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin else show_help From f09e5c96fcad715a7e3b3fe4bb285f1348d6c29d Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Fri, 8 Mar 2024 11:03:03 +0200 Subject: [PATCH 0092/1377] Add permission: read-all to silence CKV_GHA_1 check --- .github/workflows/build_esp32_c3.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index f9164b96a..a30cf33f1 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -7,6 +7,8 @@ on: required: true type: string +permissions: read-all + jobs: build-esp32-c3: runs-on: ubuntu-latest From 3a8f623f8adfc73e0c3b36453d8392394377713a Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Fri, 8 Mar 2024 11:27:31 +0200 Subject: [PATCH 0093/1377] Change '! -z' to '-n' to addresss shellcheck/SC2236 --- bin/device-install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/device-install.sh b/bin/device-install.sh index a4ee20c9c..0e7bd8ada 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -47,13 +47,13 @@ shift "$((OPTIND - 1))" shift } -if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then +if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then echo "Trying to flash ${FILENAME}, but first erasing and writing system information" "$PYTHON" -m esptool erase_flash "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition - if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then - if [ ! -z "${FILENAME##*"esp32c3"*}" ]; then + if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ]; then + if [ -n "${FILENAME##*"esp32c3"*}" ]; then "$PYTHON" -m esptool write_flash 0x260000 bleota.bin else "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin From 658ed6fd2874f8e7e73007a492ff81e40af1093b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 11 Mar 2024 13:51:26 +0100 Subject: [PATCH 0094/1377] tryfix SHT31 sensor on secondary bus --- src/mesh/NodeDB.h | 2 +- src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/SHT31Sensor.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 8545b08d6..e472f7151 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -248,4 +248,4 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 7ffb68254..7f2b7691e 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -12,7 +12,7 @@ int32_t SHT31Sensor::runOnce() if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = sht31.begin(); + status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first); return initI2CSensor(); } diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index 940361325..9700bdf2c 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -5,7 +5,7 @@ class SHT31Sensor : public TelemetrySensor { private: - Adafruit_SHT31 sht31 = Adafruit_SHT31(); + Adafruit_SHT31 sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second); protected: virtual void setup() override; From 892223a297c23b788ae879b3a6dab92a10e391ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 11 Mar 2024 13:52:46 +0100 Subject: [PATCH 0095/1377] fix typos and add 2 missing modules to the equasion (#3370) --- src/configuration.h | 2 ++ src/modules/Modules.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 03170c1c7..ac8f9435a 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -239,4 +239,6 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1 #define MESHTASTIC_EXCLUDE_TRACEROUTE 1 #define MESHTASTIC_EXCLUDE_WAYPOINT 1 +#define MESHTASTIC_EXCLUDE_INPUTBROKER 1 +#define MESHTASTIC_EXCLUDE_SERIAL 1 #endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 4f0b8f2b0..97ed90cf1 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -39,11 +39,11 @@ #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" #endif -#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" #endif #ifdef ARCH_ESP32 @@ -138,13 +138,13 @@ void setupModules() #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) new DeviceTelemetryModule(); #endif -#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new EnvironmentTelemetryModule(); if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY new PowerTelemetryModule(); #endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ From cf4753f7fd32ce43e6f11b5760f1e0366f7c07a5 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Tue, 12 Mar 2024 01:56:55 +1300 Subject: [PATCH 0096/1377] Async full-refresh for EInkDynamicDisplay (#3339) * Move Wireless Paper V1.1 custom hibernate behavior to GxEPD2 * Async full-refresh for EInkDynamicDisplay * initial config for T-Echo * formatting responds to https://github.com/meshtastic/firmware/pull/3339#discussion_r1518175434 * increase fast-refresh limit for T-Echo https://github.com/meshtastic/firmware/pull/3339#issuecomment-1986245727 * change dependency from private repo to meshtastic/GxEPD2 --------- Co-authored-by: Ben Meadors --- src/graphics/EInkDisplay2.cpp | 25 +++--- src/graphics/EInkDisplay2.h | 7 ++ src/graphics/EInkDynamicDisplay.cpp | 90 +++++++++++++++++-- src/graphics/EInkDynamicDisplay.h | 15 +++- variants/heltec_wireless_paper/platformio.ini | 2 +- variants/heltec_wireless_paper/variant.h | 1 - variants/t-echo/platformio.ini | 8 +- 7 files changed, 121 insertions(+), 27 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 6ee4245b3..6f7885b45 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -71,28 +71,24 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) } } + // Trigger the refresh in GxEPD2 LOG_DEBUG("Updating E-Paper... "); - -#if false - // Currently unused; rescued from commented-out line during a refactor - // Use a meaningful macro here if variant doesn't want fast refresh - - // Full update mode (slow) - adafruitDisplay->display(false) -#else - // Fast update mode adafruitDisplay->nextPage(); -#endif -#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory - // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display) - adafruitDisplay->hibernate(); -#endif + // End the update process + endUpdate(); LOG_DEBUG("done\n"); return true; } +// End the update process - virtual method, overriden in derived class +void EInkDisplay::endUpdate() +{ + // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep) + adafruitDisplay->hibernate(); +} + // Write the buffer to the display memory void EInkDisplay::display(void) { @@ -188,6 +184,7 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); + adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh) } #elif defined(PCA10059) { diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 75770a3bc..f74416494 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -45,6 +45,13 @@ class EInkDisplay : public OLEDDisplay */ virtual bool forceDisplay(uint32_t msecLimit = 1000); + /** + * Run any code needed to complete an update, after the physical refresh has completed. + * Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class. + * + */ + virtual void endUpdate(); + /** * shim to make the abstraction happy * diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index ae1e30fe1..75db0e33f 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -94,19 +94,29 @@ void EInkDynamicDisplay::adjustRefreshCounters() // Trigger the display update by calling base class bool EInkDynamicDisplay::update() { + // Detemine the refresh mode to use, and start the update bool refreshApproved = determineMode(); if (refreshApproved) EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system - return refreshApproved; // (Unutilized) Base class promises to return true if update ran + +#if defined(HAS_EINK_ASYNCFULL) + if (refreshApproved) + endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh) +#endif + + return refreshApproved; // (Unutilized) Base class promises to return true if update ran } // Assess situation, pick a refresh type bool EInkDynamicDisplay::determineMode() { - checkWasFlooded(); + checkForPromotion(); +#if defined(HAS_EINK_ASYNCFULL) + checkAsyncFullRefresh(); +#endif checkRateLimiting(); - // If too soon for a new time, abort here + // If too soon for a new frame, or display busy, abort early if (refresh == SKIPPED) { storeAndReset(); return false; // No refresh @@ -116,7 +126,7 @@ bool EInkDynamicDisplay::determineMode() resetRateLimiting(); // Once determineMode() ends, will have to wait again hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check - LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry + LOG_DEBUG("determineMode(): "); // Begin log entry // Once mode determined, any remaining checks will bypass checkCosmetic(); @@ -151,13 +161,25 @@ bool EInkDynamicDisplay::determineMode() } } -// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh? -void EInkDynamicDisplay::checkWasFlooded() +// Was a frame skipped (rate, display busy) that should have been a FAST refresh? +void EInkDynamicDisplay::checkForPromotion() { - if (previousReason == EXCEEDED_RATELIMIT_FAST) { - // If so, allow a BACKGROUND frame to draw as RESPONSIVE - // Because we DID want a RESPONSIVE frame last time, we just didn't get it + // If a frame was skipped (rate, display busy), then promote a BACKGROUND frame + // Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it + + switch (previousReason) { + case ASYNC_REFRESH_BLOCKED_DEMANDFAST: + setFrameFlag(DEMAND_FAST); + break; + case ASYNC_REFRESH_BLOCKED_COSMETIC: + setFrameFlag(COSMETIC); + break; + case ASYNC_REFRESH_BLOCKED_RESPONSIVE: + case EXCEEDED_RATELIMIT_FAST: setFrameFlag(RESPONSIVE); + break; + default: + break; } } @@ -381,4 +403,54 @@ void EInkDynamicDisplay::resetGhostPixelTracking() } #endif // EINK_LIMIT_GHOSTING_PX +#ifdef HAS_EINK_ASYNCFULL +// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready +void EInkDynamicDisplay::checkAsyncFullRefresh() +{ + // No refresh taking place, continue with determineMode() + if (!asyncRefreshRunning) + return; + + // Full refresh still running + if (adafruitDisplay->epd2.isBusy()) { + // No refresh + refresh = SKIPPED; + + // Set the reason, marking what type of frame we're skipping + if (frameFlags & DEMAND_FAST) + reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST; + else if (frameFlags & COSMETIC) + reason = ASYNC_REFRESH_BLOCKED_COSMETIC; + else if (frameFlags & RESPONSIVE) + reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE; + else + reason = ASYNC_REFRESH_BLOCKED_BACKGROUND; + + return; + } + + // If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag + LOG_DEBUG("Async full-refresh complete\n"); + + // Note: this code only works because of a modification to meshtastic/GxEPD2. + // It is only equipped to intercept calls to nextPage() +} + +// Figure out who runs the post-update code +void EInkDynamicDisplay::endOrDetach() +{ + if (previousRefresh == FULL) { // Note: previousRefresh is the refresh from this loop. + asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. + LOG_DEBUG("Async full-refresh begins\n"); + } + + // Fast Refresh + else + EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. +} +#endif // HAS_EINK_ASYNCFULL + #endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 2880c716b..3dc00ba7c 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -44,6 +44,11 @@ class EInkDynamicDisplay : public EInkDisplay }; enum reasonTypes : uint8_t { // How was the decision reached NO_OBJECTIONS, + ASYNC_REFRESH_BLOCKED_DEMANDFAST, + ASYNC_REFRESH_BLOCKED_COSMETIC, + ASYNC_REFRESH_BLOCKED_RESPONSIVE, + ASYNC_REFRESH_BLOCKED_BACKGROUND, + DISPLAY_NOT_READY_FOR_FULL, EXCEEDED_RATELIMIT_FAST, EXCEEDED_RATELIMIT_FULL, FLAGGED_COSMETIC, @@ -64,7 +69,7 @@ class EInkDynamicDisplay : public EInkDisplay bool update(); // Trigger the display update - determine mode, then call base class // Checks as part of determineMode() - void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC? + void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh? void checkRateLimiting(); // Is this frame too soon? void checkCosmetic(); // Was the COSMETIC flag set? void checkDemandingFast(); // Was the DEMAND_FAST flag set? @@ -99,6 +104,14 @@ class EInkDynamicDisplay : public EInkDisplay uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use #endif + + // Conditional - async full refresh - only with modified meshtastic/GxEPD2 +#if defined(HAS_EINK_ASYNCFULL) + void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready + void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() + void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() + bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() +#endif }; #endif \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 0abbe085e..14275830a 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -16,7 +16,7 @@ build_flags = -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2 + https://github.com/meshtastic/GxEPD2/ adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 28bc8628a..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -5,7 +5,6 @@ #define I2C_SCL SCL #define USE_EINK -#define EINK_NO_HIBERNATE /* * eink display pins diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 49ba3bb34..c97341a3b 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -11,10 +11,16 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 -DEINK_HEIGHT=200 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} - https://github.com/meshtastic/GxEPD2#afce87a97dda1ac31d8a28dc8fa7c6f55dc96a61 + https://github.com/meshtastic/GxEPD2 adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs From 1d31be939ff36dd2ad675a72893d502aa91808e2 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Tue, 12 Mar 2024 03:06:01 +1300 Subject: [PATCH 0097/1377] Swap Wireless Paper V1.0 dependency to meshtastic/GxEPD2 --- variants/heltec_wireless_paper_v1/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 9327ed256..de832d6d7 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -16,7 +16,7 @@ build_flags = ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/todd-herbert/meshtastic-GxEPD2#async + https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file From 1f766a04aa036c83094eb34c6048f8ab6774f0f4 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Tue, 12 Mar 2024 04:04:28 +1300 Subject: [PATCH 0098/1377] purge unused enum val --- src/graphics/EInkDynamicDisplay.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index dcae056c6..81963df58 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -49,7 +49,6 @@ class EInkDynamicDisplay : public EInkDisplay ASYNC_REFRESH_BLOCKED_COSMETIC, ASYNC_REFRESH_BLOCKED_RESPONSIVE, ASYNC_REFRESH_BLOCKED_BACKGROUND, - DISPLAY_NOT_READY_FOR_FULL, EXCEEDED_RATELIMIT_FAST, EXCEEDED_RATELIMIT_FULL, FLAGGED_COSMETIC, From c80098f517b9f69071227f96f96834e57282e9d8 Mon Sep 17 00:00:00 2001 From: Andre K Date: Mon, 11 Mar 2024 13:49:46 -0300 Subject: [PATCH 0099/1377] refactor: remove ACKs in range tests so zero hops is honored (#3374) --- src/modules/RangeTestModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index b45068b45..904fb25db 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -114,7 +114,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) p->to = dest; p->decoded.want_response = wantReplies; p->hop_limit = 0; - p->want_ack = true; + p->want_ack = false; packetSequence++; From e16689a0d6b3192c12fe5b7090992b61d540aeb8 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:45:59 +0100 Subject: [PATCH 0100/1377] fix heap use after delete (#3373) --- src/mesh/ReliableRouter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 167a248ab..2327cbfb7 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -167,8 +167,6 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key) auto old = findPendingPacket(key); if (old) { auto p = old->packet; - auto numErased = pending.erase(key); - assert(numErased == 1); /* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue to avoid canceling a transmission if it was ACKed super fast via MQTT */ if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) { @@ -177,6 +175,8 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key) // now free the pooled copy for retransmission too packetPool.release(p); } + auto numErased = pending.erase(key); + assert(numErased == 1); return true; } else return false; From c7839b469b5a3dee6711c4d892bb9a002d7d96d2 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:51:14 +0100 Subject: [PATCH 0101/1377] fix of tryfix SHT31 sensor (#3377) --- src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 1 + src/modules/Telemetry/Sensor/SHT31Sensor.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 7f2b7691e..35978d970 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -12,6 +12,7 @@ int32_t SHT31Sensor::runOnce() if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } + sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second); status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first); return initI2CSensor(); } diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index 9700bdf2c..c6f8f1596 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -5,7 +5,7 @@ class SHT31Sensor : public TelemetrySensor { private: - Adafruit_SHT31 sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second); + Adafruit_SHT31 sht31; protected: virtual void setup() override; From 5f47ca1f32dd283739c4d78a4e08f20a9bd60fa1 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:58:45 +0100 Subject: [PATCH 0102/1377] Don't spam logs if no position with map reporting (#3378) --- src/mqtt/MQTT.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2de35971a..760aa7210 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -552,14 +552,14 @@ void MQTT::perhapsReportToMap() if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) return; - if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { - LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); - return; - } - if (millis() - last_report_to_map < map_publish_interval_secs * 1000) { return; } else { + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); + return; + } + // Allocate ServiceEnvelope and fill it meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id From f9bf9e2dcc3e9ea11c4ae3fbe8916f54aa2df171 Mon Sep 17 00:00:00 2001 From: thebentern Date: Mon, 11 Mar 2024 21:43:46 +0000 Subject: [PATCH 0103/1377] [create-pull-request] automated change --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 8927d1781..07fadd0d8 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 0 +build = 1 From affbd7f2b91ffb397066df70e22e34d3e6ac3aeb Mon Sep 17 00:00:00 2001 From: AeroXuk Date: Tue, 12 Mar 2024 02:13:52 +0000 Subject: [PATCH 0104/1377] Update MQTT.cpp Bug fix for #3382 --- src/mqtt/MQTT.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 760aa7210..4250ad5cd 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -185,7 +185,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) statusTopic = moduleConfig.mqtt.root + statusTopic; cryptTopic = moduleConfig.mqtt.root + cryptTopic; jsonTopic = moduleConfig.mqtt.root + jsonTopic; - mapTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + mapTopic; } else { statusTopic = "msh" + statusTopic; cryptTopic = "msh" + cryptTopic; @@ -915,4 +915,4 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) (json["from"]->AsNumber() == nodeDB.getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type (json.find("payload") != json.end()); // should have a payload -} \ No newline at end of file +} From 7f063fbf811faf0c722cd377f5b7bde572872fbd Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Tue, 12 Mar 2024 17:55:31 +0100 Subject: [PATCH 0105/1377] Support external charge detection (#3386) * Support external charge detection * trunk fmt --- src/Power.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index 3d1a1b9b2..71554daa3 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -54,6 +54,19 @@ static const adc_atten_t atten = ADC_ATTENUATION; #endif #endif // BATTERY_PIN && ARCH_ESP32 +#ifdef EXT_CHRG_DETECT +#ifndef EXT_CHRG_DETECT_MODE +static const uint8_t ext_chrg_detect_mode = INPUT; +#else +static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE; +#endif +#ifndef EXT_CHRG_DETECT_VALUE +static const uint8_t ext_chrg_detect_value = HIGH; +#else +static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE; +#endif +#endif + #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) INA260Sensor ina260Sensor; INA219Sensor ina219Sensor; @@ -322,7 +335,14 @@ class AnalogBatteryLevel : public HasBatteryLevel /// Assume charging if we have a battery and external power is connected. /// we can't be smart enough to say 'full'? - virtual bool isCharging() override { return isBatteryConnect() && isVbusIn(); } + virtual bool isCharging() override + { +#ifdef EXT_CHRG_DETECT + return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; +#else + return isBatteryConnect() && isVbusIn(); +#endif + } private: /// If we see a battery voltage higher than physics allows - assume charger is pumping @@ -389,6 +409,9 @@ bool Power::analogInit() #ifdef EXT_PWR_DETECT pinMode(EXT_PWR_DETECT, INPUT); #endif +#ifdef EXT_CHRG_DETECT + pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode); +#endif #ifdef BATTERY_PIN LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN); From cf11807f9711eb4575ada4dce1fbb32c18f9e89a Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Tue, 12 Mar 2024 18:21:09 +0100 Subject: [PATCH 0106/1377] use priority background for low priority messages (#3381) --- src/modules/Telemetry/AirQualityTelemetry.cpp | 2 +- src/modules/Telemetry/DeviceTelemetry.cpp | 2 +- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 +- src/modules/Telemetry/PowerTelemetry.cpp | 2 +- src/modules/esp32/PaxcounterModule.cpp | 2 +- src/modules/esp32/StoreForwardModule.cpp | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index f87ea504b..ada1fdef8 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -112,7 +112,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index a6eecda80..55000e4c6 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -91,7 +91,7 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) meshtastic_MeshPacket *p = allocDataProtobuf(telemetry); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL); if (phoneOnly) { diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index d4f423e54..7b59c28a6 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -253,7 +253,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) packetPool.release(lastMeasurementPacket); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 30628bfd7..300ab1f62 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -195,7 +195,7 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) packetPool.release(lastMeasurementPacket); diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 580fc46e1..54c67fad7 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -50,7 +50,7 @@ bool PaxcounterModule::sendInfo(NodeNum dest) meshtastic_MeshPacket *p = allocDataProtobuf(pl); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; service.sendToMesh(p, RX_SRC_LOCAL, true); diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index 70d13afca..71d75750a 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -255,7 +255,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw p->to = dest; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume // everything is broadcast. @@ -334,7 +334,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m LOG_INFO("*** S&F - Busy. Try again shortly.\n"); meshtastic_MeshPacket *pr = allocReply(); pr->to = getFrom(&mp); - pr->priority = meshtastic_MeshPacket_Priority_MIN; + pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND; pr->want_ack = false; pr->decoded.want_response = false; pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; From ee685b4ed78edad0f06b5816b913f39af8e25fb4 Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Tue, 12 Mar 2024 19:03:04 +0100 Subject: [PATCH 0107/1377] Check AQ_SET_PIN instead of EINK dependency (#3387) --- src/main.cpp | 2 +- src/platform/nrf52/main-nrf52.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ef1cd53c3..535051811 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -342,7 +342,7 @@ void setup() pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); #endif -#ifndef USE_EINK +#ifdef AQ_SET_PIN // RAK-12039 set pin for Air quality sensor pinMode(AQ_SET_PIN, OUTPUT); digitalWrite(AQ_SET_PIN, HIGH); diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 9e8798e37..2f670dee3 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -177,7 +177,7 @@ void cpuDeepSleep(uint32_t msecToWake) #ifdef PIN_3V3_EN digitalWrite(PIN_3V3_EN, LOW); #endif -#ifndef USE_EINK +#ifdef AQ_SET_PIN // RAK-12039 set pin for Air quality sensor digitalWrite(AQ_SET_PIN, LOW); #endif From 38ea6814331c26cdb72af024e401151a6f466de5 Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Tue, 12 Mar 2024 22:42:21 +0100 Subject: [PATCH 0108/1377] Fix LTO discharge curve (#3385) * Fix LTO discharge curve * Remove duplicate info --- src/power.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/power.h b/src/power.h index 4dd35e710..b94ce8f98 100644 --- a/src/power.h +++ b/src/power.h @@ -10,13 +10,6 @@ #define NUM_OCV_POINTS 11 #endif -// 3400,3350,3320,3290,3270,3260,3250,3230,3200,3120,3000 //3.4 to 3.0 LiFePO4 -// 2120,2090,2070,2050,2030,2010,1990,1980,1970,1960,1950 //2.12 to 1.95 Lead Acid -// 4200,4050,3990,3890,3790,3700,3650,3550,3450,3300,3200 //4.2 to 3.2 LiIon/LiPo -// 4200,4050,3990,3890,3790,3700,3650,3550,3400,3300,3000 //4.2 to 3.0 LiIon/LiPo -// 4150,4050,3990,3890,3790,3690,3620,3520,3420,3300,3100 //4.15 to 3.1 LiIon/LiPo -// 2770,2650,2540,2420,2300,2180,2060,1940,1800,1680,1550 //2.8 to 1.5 Lithium Titanate - #ifndef OCV_ARRAY #ifdef CELL_TYPE_LIFEPO4 #define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 @@ -27,7 +20,7 @@ #elif defined(CELL_TYPE_NIMH) #define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 #elif defined(CELL_TYPE_LTO) -#define OCV_ARRAY 2770, 2650, 2540, 2420, 2300, 2180, 2060, 1940, 1800, 1680, 1550 +#define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 #else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif From 724fa38a552ddd952ceeca336e65ae187962bb59 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:42:34 +0100 Subject: [PATCH 0109/1377] Fix T-LoRa V2.1-6 with TCXO init (#3392) --- src/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 535051811..bb9b68631 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -226,6 +226,11 @@ void setup() digitalWrite(PIN_POWER_EN1, INPUT); #endif +#if defined(LORA_TCXO_GPIO) + pinMode(LORA_TCXO_GPIO, OUTPUT); + digitalWrite(LORA_TCXO_GPIO, HIGH); +#endif + #if defined(VEXT_ENABLE_V03) pinMode(VEXT_ENABLE_V03, OUTPUT); pinMode(ST7735_BL_V03, OUTPUT); From 333c3c1c9ebf71910c78e90d171db5a921b4d733 Mon Sep 17 00:00:00 2001 From: Tavis Date: Tue, 12 Mar 2024 21:42:08 -1000 Subject: [PATCH 0110/1377] fix off by one error buzzer is index 2, but loop was 0-1 so buzzer never got turned off. --- src/modules/ExternalNotificationModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 9af1f9e00..652965f6d 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -81,7 +81,7 @@ int32_t ExternalNotificationModule::runOnce() // let the song finish if we reach timeout nagCycleCutoff = UINT32_MAX; LOG_INFO("Turning off external notification: "); - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 3; i++) { setExternalOff(i); externalTurnedOn[i] = 0; LOG_INFO("%d ", i); From 2efe436102d097d535bf2d20b90f399e58f4a0ef Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 13 Mar 2024 07:20:51 -0500 Subject: [PATCH 0111/1377] Update nrf52 platform and consolidate Adafruit Bus IO (#3393) --- arch/nrf52/nrf52.ini | 2 +- platformio.ini | 2 +- variants/canaryone/platformio.ini | 1 - variants/heltec_wireless_paper/platformio.ini | 1 - variants/heltec_wireless_paper_v1/platformio.ini | 1 - variants/nano-g2-ultra/platformio.ini | 1 - variants/t-echo/platformio.ini | 1 - 7 files changed, 2 insertions(+), 7 deletions(-) diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 5155eaadc..2505fe315 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -1,6 +1,6 @@ [nrf52_base] ; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files -platform = platformio/nordicnrf52@^10.1.0 +platform = platformio/nordicnrf52@^10.4.0 extends = arduino_base build_type = debug ; I'm debugging with ICE a lot now diff --git a/platformio.ini b/platformio.ini index b67ddc50a..392b38fd7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -113,7 +113,7 @@ lib_deps = ; (not included in native / portduino) [environmental_base] lib_deps = - adafruit/Adafruit BusIO@^1.11.4 + adafruit/Adafruit BusIO@^1.15.0 adafruit/Adafruit Unified Sensor@^1.1.11 adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP085 Library@^1.2.4 diff --git a/variants/canaryone/platformio.ini b/variants/canaryone/platformio.ini index d52bbb24a..4917f52c7 100644 --- a/variants/canaryone/platformio.ini +++ b/variants/canaryone/platformio.ini @@ -10,6 +10,5 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/canaryone build_src_filter = ${nrf52_base.build_src_filter} +<../variants/canaryone> lib_deps = ${nrf52840_base.lib_deps} - adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 94ed15ed1..1e1bb9376 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -17,6 +17,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a - adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index de832d6d7..cae1940b3 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -17,6 +17,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a - adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/nano-g2-ultra/platformio.ini b/variants/nano-g2-ultra/platformio.ini index d5e5a6137..2b011e032 100644 --- a/variants/nano-g2-ultra/platformio.ini +++ b/variants/nano-g2-ultra/platformio.ini @@ -9,6 +9,5 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/nano-g2-ultra -D NANO_G2_U build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nano-g2-ultra> lib_deps = ${nrf52840_base.lib_deps} - adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index be8900e0f..9ff60be3f 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -21,6 +21,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a - adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs From 216f85ff221990a86b4ffd4548c90220b59fd255 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:02:48 -0500 Subject: [PATCH 0112/1377] [create-pull-request] automated change (#3397) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/clientonly.pb.c | 3 +++ src/mesh/generated/meshtastic/clientonly.pb.h | 16 ++++++++++++++++ src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/module_config.pb.c | 2 +- src/mesh/generated/meshtastic/module_config.pb.h | 6 +++--- 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 00332412b..7e3ee8cd9 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 00332412b238fe559175a6e83fdf8d31fa5e209a +Subproject commit 7e3ee8cd96740910d0611433cb9a05a7a692568c diff --git a/src/mesh/generated/meshtastic/clientonly.pb.c b/src/mesh/generated/meshtastic/clientonly.pb.c index ebc2ffabc..90e8e2d8a 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.c +++ b/src/mesh/generated/meshtastic/clientonly.pb.c @@ -9,4 +9,7 @@ PB_BIND(meshtastic_DeviceProfile, meshtastic_DeviceProfile, 2) +PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO) + + diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h index 0f70e09c6..19b0a0e5f 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.h +++ b/src/mesh/generated/meshtastic/clientonly.pb.h @@ -30,6 +30,12 @@ typedef struct _meshtastic_DeviceProfile { meshtastic_LocalModuleConfig module_config; } meshtastic_DeviceProfile; +/* A heartbeat message is sent by a node to indicate that it is still alive. + This is currently only needed to keep serial connections alive. */ +typedef struct _meshtastic_Heartbeat { + char dummy_field; +} meshtastic_Heartbeat; + #ifdef __cplusplus extern "C" { @@ -37,7 +43,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceProfile_init_default {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} +#define meshtastic_Heartbeat_init_default {0} #define meshtastic_DeviceProfile_init_zero {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} +#define meshtastic_Heartbeat_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceProfile_long_name_tag 1 @@ -58,13 +66,21 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_config, 5) #define meshtastic_DeviceProfile_config_MSGTYPE meshtastic_LocalConfig #define meshtastic_DeviceProfile_module_config_MSGTYPE meshtastic_LocalModuleConfig +#define meshtastic_Heartbeat_FIELDLIST(X, a) \ + +#define meshtastic_Heartbeat_CALLBACK NULL +#define meshtastic_Heartbeat_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_DeviceProfile_msg; +extern const pb_msgdesc_t meshtastic_Heartbeat_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceProfile_fields &meshtastic_DeviceProfile_msg +#define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceProfile_size depends on runtime parameters */ +#define meshtastic_Heartbeat_size 0 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 556821e1c..79800d4b4 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -324,7 +324,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_DeviceState_size 17571 #define meshtastic_NodeInfoLite_size 158 #define meshtastic_NodeRemoteHardwarePin_size 29 -#define meshtastic_OEMStore_size 3262 +#define meshtastic_OEMStore_size 3278 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2e22cb1e4..f27c119bd 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define meshtastic_LocalConfig_size 469 -#define meshtastic_LocalModuleConfig_size 647 +#define meshtastic_LocalModuleConfig_size 663 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.c b/src/mesh/generated/meshtastic/module_config.pb.c index a75c3fb59..594cf9628 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.c +++ b/src/mesh/generated/meshtastic/module_config.pb.c @@ -9,7 +9,7 @@ PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, 2) -PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, 2) PB_BIND(meshtastic_ModuleConfig_MapReportSettings, meshtastic_ModuleConfig_MapReportSettings, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 2e1c25c7f..a2adbc1b9 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -119,7 +119,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { bool tls_enabled; /* The root topic to use for MQTT messages. Default is "msh". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs */ - char root[16]; + char root[32]; /* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection */ bool proxy_to_client_enabled; /* If true, we will periodically report unencrypted information about our node to a map via MQTT */ @@ -832,7 +832,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_CannedMessageConfig_size 49 #define meshtastic_ModuleConfig_DetectionSensorConfig_size 44 #define meshtastic_ModuleConfig_ExternalNotificationConfig_size 42 -#define meshtastic_ModuleConfig_MQTTConfig_size 238 +#define meshtastic_ModuleConfig_MQTTConfig_size 254 #define meshtastic_ModuleConfig_MapReportSettings_size 12 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 8 #define meshtastic_ModuleConfig_PaxcounterConfig_size 8 @@ -841,7 +841,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 22 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 -#define meshtastic_ModuleConfig_size 241 +#define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 #ifdef __cplusplus From 3995e2f7084b5776b655a7027af4620ee7c21e02 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 13 Mar 2024 15:06:52 -0500 Subject: [PATCH 0113/1377] Remove bunk code --- src/platform/nrf52/NRF52Bluetooth.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 9a93f5cc6..e1914a184 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -202,8 +202,6 @@ void setupMeshService(void) toRadio.begin(); } -// FIXME, turn off soft device access for debugging -static bool isSoftDeviceAllowed = true; static uint32_t configuredPasskey; void NRF52Bluetooth::shutdown() @@ -281,14 +279,11 @@ void NRF52Bluetooth::setup() LOG_INFO("Configuring the Mesh bluetooth service\n"); setupMeshService(); - // Supposedly debugging works with soft device if you disable advertising - if (isSoftDeviceAllowed) { - // Setup the advertising packet(s) - LOG_INFO("Setting up the advertising payload(s)\n"); - startAdv(); + // Setup the advertising packet(s) + LOG_INFO("Setting up the advertising payload(s)\n"); + startAdv(); - LOG_INFO("Advertising\n"); - } + LOG_INFO("Advertising\n"); } void NRF52Bluetooth::resumeAdverising() From 9d2fcbe1e108d221824fc5a1ead1c4cfa08e909b Mon Sep 17 00:00:00 2001 From: Andre K Date: Wed, 13 Mar 2024 20:24:49 -0300 Subject: [PATCH 0114/1377] use decoded packets in public MQTT range test/detection sensor filter (#3404) Co-authored-by: Ben Meadors --- src/mqtt/MQTT.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4250ad5cd..c518bc4b5 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -486,9 +486,9 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & auto &ch = channels.getByIndex(chIndex); - if (&mp.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { + if (&mp_decoded.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); return; } From 9c37e57e750a72324ea58f6acda53168c4efae50 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 13 Mar 2024 20:27:26 -0500 Subject: [PATCH 0115/1377] Only allow phone to set time for fixed positions (#3403) --- src/mesh/NodeDB.h | 9 +++++++-- src/modules/PositionModule.cpp | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e472f7151..20cc5c25b 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -133,8 +133,13 @@ class NodeDB meshtastic_NodeInfoLite *getMeshNode(NodeNum n); size_t getNumMeshNodes() { return *numMeshNodes; } - void setLocalPosition(meshtastic_Position position) + void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { + if (timeOnly) { + LOG_DEBUG("Setting local position time only: time=%i\n", position.time); + localPosition.time = position.time; + return; + } LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i\n", position.latitude_i, position.longitude_i, position.time); localPosition = position; @@ -248,4 +253,4 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 4634f8fef..59f62bd5c 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -59,9 +59,15 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // to set fixed location, EUD-GPS location or just the time (see also issue #900) bool isLocal = false; if (nodeDB.getNodeNum() == getFrom(&mp)) { - LOG_DEBUG("Incoming update from MYSELF\n"); isLocal = true; - nodeDB.setLocalPosition(p); + if (config.position.fixed_position) { + LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n"); + nodeDB.setLocalPosition(p, true); + return false; + } else { + LOG_DEBUG("Incoming update from MYSELF\n"); + nodeDB.setLocalPosition(p); + } } // Log packet size and data fields From 58cdf360f862d7027f77148fc4f78d16d237365e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 14 Mar 2024 16:18:33 +0100 Subject: [PATCH 0116/1377] (1/3) Support L76B GNSS chip found on pico waveshare shield. Original work by @Mictronics --- platformio.ini | 4 ++-- src/gps/GPS.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++++++-- src/gps/GPS.h | 12 +++++------- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/platformio.ini b/platformio.ini index 392b38fd7..7680f2f20 100644 --- a/platformio.ini +++ b/platformio.ini @@ -77,7 +77,7 @@ lib_deps = https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 - https://github.com/meshtastic/TinyGPSPlus.git#2044b2c51e91ab4cd8cc93b15e40658cd808dd06 + https://github.com/meshtastic/TinyGPSPlus.git#f9f4fef2183514aa52be91d714c1455dd6f26e45 https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3 nanopb/Nanopb@^0.4.7 erriez/ErriezCRC32@^1.0.1 @@ -130,4 +130,4 @@ lib_deps = adafruit/Adafruit PM25 AQI Sensor@^1.0.6 adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 - https://github.com/lewisxhe/BMA423_Library@^0.0.1 + https://github.com/lewisxhe/BMA423_Library@^0.0.1 \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 849c38794..5b7d18bab 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -290,6 +290,26 @@ bool GPS::setup() // Switch to Vehicle Mode, since SoftRF enables Aviation < 2g _serial_gps->write("$PCAS11,3*1E\r\n"); delay(250); + } else if (gnssModel == GNSS_MODEL_MTK_L76B) { + // Waveshare Pico-GPS hat uses the L76B with 9600 baud + // Initialize the L76B Chip, use GPS + GLONASS + // See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29 + _serial_gps->write("$PMTK353,1,1,0,0,0*2B\r\n"); + // Above command will reset the GPS and takes longer before it will accept new commands + delay(1000); + // only ask for RMC and GGA (GNRMC and GNGGA) + // See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1 + _serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n"); + delay(250); + // Enable SBAS + _serial_gps->write("$PMTK301,2*2E\r\n"); + delay(250); + // Enable PPS for 2D/3D fix only + _serial_gps->write("$PMTK285,3,100*3F\r\n"); + delay(250); + // Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s) + _serial_gps->write("$PMTK886,1*29\r\n"); + delay(250); } else if (gnssModel == GNSS_MODEL_UC6580) { // The Unicore UC6580 can use a lot of sat systems, enable it to // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS @@ -625,17 +645,27 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) return; } #endif -#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones +#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones if (on) { LOG_INFO("Waking GPS\n"); pinMode(PIN_GPS_STANDBY, OUTPUT); + // Some PCB's use an inverse logic due to a transistor driver + // Example for this is the Pico-Waveshare Lora+GPS HAT +#ifdef PIN_GPS_STANDBY_INVERTED + digitalWrite(PIN_GPS_STANDBY, 0); +#else digitalWrite(PIN_GPS_STANDBY, 1); +#endif return; } else { LOG_INFO("GPS entering sleep\n"); // notifyGPSSleep.notifyObservers(NULL); pinMode(PIN_GPS_STANDBY, OUTPUT); +#ifdef PIN_GPS_STANDBY_INVERTED + digitalWrite(PIN_GPS_STANDBY, 1); +#else digitalWrite(PIN_GPS_STANDBY, 0); +#endif return; } #endif @@ -916,7 +946,7 @@ GnssModel_t GPS::probe(int serialSpeed) uint8_t buffer[768] = {0}; delay(100); - // Close all NMEA sentences , Only valid for MTK platform + // Close all NMEA sentences , Only valid for L76K MTK platform _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); delay(20); @@ -928,6 +958,18 @@ GnssModel_t GPS::probe(int serialSpeed) return GNSS_MODEL_MTK; } + // Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS) + _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); + delay(20); + + // Get version information + clearBuffer(); + _serial_gps->write("$PMTK605*31\r\n"); + if (getACK("Quectel-L76B", 500) == GNSS_RESPONSE_OK) { + LOG_INFO("L76B GNSS init succeeded, using L76B GNSS Module\n"); + return GNSS_MODEL_MTK_L76B; + } + uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; UBXChecksum(cfg_rate, sizeof(cfg_rate)); clearBuffer(); @@ -1111,6 +1153,7 @@ GPS *GPS::createGps() _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); #else + _serial_gps->setFIFOSize(256); _serial_gps->begin(GPS_BAUDRATE); #endif @@ -1168,6 +1211,9 @@ bool GPS::factoryReset() // byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B}; // _serial_gps->write(_message_CFG_RST_COLDSTART, sizeof(_message_CFG_RST_COLDSTART)); // delay(1000); + } else if (HW_VENDOR == meshtastic_HardwareModel_RPI_PICO) { + _serial_gps->write("$PMTK104*37\r\n"); + // No PMTK_ACK for this command. } else { // send the UBLOX Factory Reset Command regardless of detect state, something is very wrong, just assume it's UBLOX. // Factory Reset diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 77e1d8042..502763bb6 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -20,12 +20,7 @@ struct uBloxGnssModelInfo { char extension[10][30]; }; -typedef enum { - GNSS_MODEL_MTK, - GNSS_MODEL_UBLOX, - GNSS_MODEL_UC6580, - GNSS_MODEL_UNKNOWN, -} GnssModel_t; +typedef enum { GNSS_MODEL_MTK, GNSS_MODEL_UBLOX, GNSS_MODEL_UC6580, GNSS_MODEL_UNKNOWN, GNSS_MODEL_MTK_L76B } GnssModel_t; typedef enum { GNSS_RESPONSE_NONE, @@ -92,8 +87,11 @@ class GPS : private concurrency::OSThread public: /** If !NULL we will use this serial port to construct our GPS */ +#if defined(RPI_PICO_WAVESHARE) + static SerialUART *_serial_gps; +#else static HardwareSerial *_serial_gps; - +#endif static uint8_t _message_PMREQ[]; static uint8_t _message_PMREQ_10[]; static const uint8_t _message_CFG_RXM_PSM[]; From a085c3ddb334fb4977f68980ebd2ef79e625c861 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 14 Mar 2024 17:00:57 -0500 Subject: [PATCH 0117/1377] Try-fix router missed messages (#3405) --- src/PowerFSM.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index c359e4c12..f98b03077 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -358,10 +358,10 @@ void PowerFSM_setup() // Don't add power saving transitions if we are a power saving tracker or sensor. Sleep will be initiatiated through the // modules if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) { - powerFSM.add_timed_transition(&stateNB, isInfrastructureRole ? &stateSDS : &stateLS, + powerFSM.add_timed_transition(&stateNB, &stateLS, getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, "Min wake timeout"); - powerFSM.add_timed_transition(&stateDARK, isInfrastructureRole ? &stateSDS : &stateLS, + powerFSM.add_timed_transition(&stateDARK, &stateLS, getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, "Bluetooth timeout"); } From ec6bdeed8115be3904d413c1cd0ad79928d4f55e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 15 Mar 2024 07:12:03 -0500 Subject: [PATCH 0118/1377] NodeInfo broadcast ensure default on 0 and enforce 1 hour minimum (#3415) * NodeInfo broadcasts ensure defaults on 0 and enforce 1 hour minumum * Doh! * Hey that's not on config! --- src/mesh/NodeDB.cpp | 13 ++----------- src/mesh/NodeDB.h | 2 ++ src/modules/AdminModule.cpp | 4 ++++ src/modules/NodeInfoModule.cpp | 3 +-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 9d7647138..6898f7702 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -101,16 +101,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset) // devicestate.no_save = true; if (devicestate.no_save) { LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n"); - - // Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins - config.display.screen_on_secs = 10; - config.power.wait_bluetooth_secs = 10; - config.position.position_broadcast_secs = 6 * 60; - config.power.ls_secs = 60; - config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_TW; - - // Enter super deep sleep soon and stay there not very long - // radioConfig.preferences.sds_secs = 60; + // Put your development config changes here } // Update the global myRegion @@ -199,7 +190,7 @@ void NodeDB::installDefaultConfig() config.position.broadcast_smart_minimum_distance = 100; config.position.broadcast_smart_minimum_interval_secs = 30; if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER) - config.device.node_info_broadcast_secs = 3 * 60 * 60; + config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.device.serial_enabled = true; resetRadioConfig(); strncpy(config.network.ntp_server, "0.pool.ntp.org", 32); diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 20cc5c25b..b34059fb9 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -203,6 +203,8 @@ extern NodeDB nodeDB; #define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60) #define default_min_wake_secs 10 #define default_screen_on_secs IF_ROUTER(1, 60 * 10) +#define default_node_info_broadcast_secs 3 * 60 * 60 +#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour #define default_mqtt_address "mqtt.meshtastic.org" #define default_mqtt_username "meshdev" diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index abd7c2e54..06818dc88 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -302,6 +302,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) // If we're setting router role for the first time, install its intervals if (existingRole != c.payload_variant.device.role) nodeDB.installRoleDefaults(c.payload_variant.device.role); + if (config.device.node_info_broadcast_secs < min_node_info_broadcast_secs) { + LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs); + config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; + } break; case meshtastic_Config_position_tag: LOG_INFO("Setting config: Position\n"); diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index b0b4bbdcd..5177af33a 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -91,6 +91,5 @@ int32_t NodeInfoModule::runOnce() LOG_INFO("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies); sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies) } - - return getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_broadcast_interval_secs); + return getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs); } From 50cc4cfcf15a65997f72960c7f1ab762d796dba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 15 Mar 2024 14:07:54 +0100 Subject: [PATCH 0119/1377] We don't use Lorawan (#3417) #warning "Persistent storage not supported!" [-Wcpp] --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 392b38fd7..68cfa1753 100644 --- a/platformio.ini +++ b/platformio.ini @@ -69,6 +69,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_PAGER -DRADIOLIB_EXCLUDE_FSK4 -DRADIOLIB_EXCLUDE_APRS + -DRADIOLIB_EXCLUDE_LORAWAN monitor_speed = 115200 From 876a0520a99787f371d36e42db6475b4e80dcc34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:09:48 -0500 Subject: [PATCH 0120/1377] [create-pull-request] automated change (#3418) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/clientonly.pb.c | 3 - src/mesh/generated/meshtastic/clientonly.pb.h | 16 ---- src/mesh/generated/meshtastic/mesh.pb.c | 3 + src/mesh/generated/meshtastic/mesh.pb.h | 86 ++++++++++++------- 5 files changed, 58 insertions(+), 52 deletions(-) diff --git a/protobufs b/protobufs index 7e3ee8cd9..cf25b390d 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 7e3ee8cd96740910d0611433cb9a05a7a692568c +Subproject commit cf25b390d65113980b1a239e16faa79c7730a736 diff --git a/src/mesh/generated/meshtastic/clientonly.pb.c b/src/mesh/generated/meshtastic/clientonly.pb.c index 90e8e2d8a..ebc2ffabc 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.c +++ b/src/mesh/generated/meshtastic/clientonly.pb.c @@ -9,7 +9,4 @@ PB_BIND(meshtastic_DeviceProfile, meshtastic_DeviceProfile, 2) -PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO) - - diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h index 19b0a0e5f..0f70e09c6 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.h +++ b/src/mesh/generated/meshtastic/clientonly.pb.h @@ -30,12 +30,6 @@ typedef struct _meshtastic_DeviceProfile { meshtastic_LocalModuleConfig module_config; } meshtastic_DeviceProfile; -/* A heartbeat message is sent by a node to indicate that it is still alive. - This is currently only needed to keep serial connections alive. */ -typedef struct _meshtastic_Heartbeat { - char dummy_field; -} meshtastic_Heartbeat; - #ifdef __cplusplus extern "C" { @@ -43,9 +37,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceProfile_init_default {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} -#define meshtastic_Heartbeat_init_default {0} #define meshtastic_DeviceProfile_init_zero {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} -#define meshtastic_Heartbeat_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceProfile_long_name_tag 1 @@ -66,21 +58,13 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_config, 5) #define meshtastic_DeviceProfile_config_MSGTYPE meshtastic_LocalConfig #define meshtastic_DeviceProfile_module_config_MSGTYPE meshtastic_LocalModuleConfig -#define meshtastic_Heartbeat_FIELDLIST(X, a) \ - -#define meshtastic_Heartbeat_CALLBACK NULL -#define meshtastic_Heartbeat_DEFAULT NULL - extern const pb_msgdesc_t meshtastic_DeviceProfile_msg; -extern const pb_msgdesc_t meshtastic_Heartbeat_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceProfile_fields &meshtastic_DeviceProfile_msg -#define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceProfile_size depends on runtime parameters */ -#define meshtastic_Heartbeat_size 0 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.c b/src/mesh/generated/meshtastic/mesh.pb.c index 790f8be2d..97bb7e53b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.c +++ b/src/mesh/generated/meshtastic/mesh.pb.c @@ -60,6 +60,9 @@ PB_BIND(meshtastic_Neighbor, meshtastic_Neighbor, AUTO) PB_BIND(meshtastic_DeviceMetadata, meshtastic_DeviceMetadata, AUTO) +PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 04590210e..8f260589c 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -682,32 +682,6 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; -/* Packets/commands to the radio will be written (reliably) to the toRadio characteristic. - Once the write completes the phone can assume it is handled. */ -typedef struct _meshtastic_ToRadio { - pb_size_t which_payload_variant; - union { - /* Send this packet on the mesh */ - meshtastic_MeshPacket packet; - /* Phone wants radio to send full node db to the phone, This is - typically the first packet sent to the radio when the phone gets a - bluetooth connection. The radio will respond by sending back a - MyNodeInfo, a owner, a radio config and a series of - FromRadio.node_infos, and config_complete - the integer you write into this field will be reported back in the - config_complete_id response this allows clients to never be confused by - a stale old partially sent config. */ - uint32_t want_config_id; - /* Tell API server we are disconnecting now. - This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link. - (Sending this message is optional for clients) */ - bool disconnect; - meshtastic_XModem xmodemPacket; - /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */ - meshtastic_MqttClientProxyMessage mqttClientProxyMessage; - }; -} meshtastic_ToRadio; - typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t; /* Compressed message payload */ typedef struct _meshtastic_Compressed { @@ -815,6 +789,40 @@ typedef struct _meshtastic_FromRadio { }; } meshtastic_FromRadio; +/* A heartbeat message is sent to the node from the client to keep the connection alive. + This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI. */ +typedef struct _meshtastic_Heartbeat { + char dummy_field; +} meshtastic_Heartbeat; + +/* Packets/commands to the radio will be written (reliably) to the toRadio characteristic. + Once the write completes the phone can assume it is handled. */ +typedef struct _meshtastic_ToRadio { + pb_size_t which_payload_variant; + union { + /* Send this packet on the mesh */ + meshtastic_MeshPacket packet; + /* Phone wants radio to send full node db to the phone, This is + typically the first packet sent to the radio when the phone gets a + bluetooth connection. The radio will respond by sending back a + MyNodeInfo, a owner, a radio config and a series of + FromRadio.node_infos, and config_complete + the integer you write into this field will be reported back in the + config_complete_id response this allows clients to never be confused by + a stale old partially sent config. */ + uint32_t want_config_id; + /* Tell API server we are disconnecting now. + This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link. + (Sending this message is optional for clients) */ + bool disconnect; + meshtastic_XModem xmodemPacket; + /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */ + meshtastic_MqttClientProxyMessage mqttClientProxyMessage; + /* Heartbeat message (used to keep the device connection awake on serial) */ + meshtastic_Heartbeat hearbeat; + }; +} meshtastic_ToRadio; + #ifdef __cplusplus extern "C" { @@ -888,6 +896,7 @@ extern "C" { #define meshtastic_DeviceMetadata_hw_model_ENUMTYPE meshtastic_HardwareModel + /* Initializer values for message structs */ #define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} @@ -907,6 +916,7 @@ extern "C" { #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} #define meshtastic_Neighbor_init_default {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_Heartbeat_init_default {0} #define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} @@ -925,6 +935,7 @@ extern "C" { #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} #define meshtastic_Neighbor_init_zero {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_Heartbeat_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Position_latitude_i_tag 1 @@ -1016,11 +1027,6 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 -#define meshtastic_ToRadio_packet_tag 1 -#define meshtastic_ToRadio_want_config_id_tag 3 -#define meshtastic_ToRadio_disconnect_tag 4 -#define meshtastic_ToRadio_xmodemPacket_tag 5 -#define meshtastic_ToRadio_mqttClientProxyMessage_tag 6 #define meshtastic_Compressed_portnum_tag 1 #define meshtastic_Compressed_data_tag 2 #define meshtastic_Neighbor_node_id_tag 1 @@ -1055,6 +1061,12 @@ extern "C" { #define meshtastic_FromRadio_xmodemPacket_tag 12 #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 +#define meshtastic_ToRadio_packet_tag 1 +#define meshtastic_ToRadio_want_config_id_tag 3 +#define meshtastic_ToRadio_disconnect_tag 4 +#define meshtastic_ToRadio_xmodemPacket_tag 5 +#define meshtastic_ToRadio_mqttClientProxyMessage_tag 6 +#define meshtastic_ToRadio_hearbeat_tag 7 /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ @@ -1234,12 +1246,14 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \ X(a, STATIC, ONEOF, UINT32, (payload_variant,want_config_id,want_config_id), 3) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,disconnect,disconnect), 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 5) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,hearbeat,hearbeat), 7) #define meshtastic_ToRadio_CALLBACK NULL #define meshtastic_ToRadio_DEFAULT NULL #define meshtastic_ToRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket #define meshtastic_ToRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem #define meshtastic_ToRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage +#define meshtastic_ToRadio_payload_variant_hearbeat_MSGTYPE meshtastic_Heartbeat #define meshtastic_Compressed_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, portnum, 1) \ @@ -1278,6 +1292,11 @@ X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) #define meshtastic_DeviceMetadata_CALLBACK NULL #define meshtastic_DeviceMetadata_DEFAULT NULL +#define meshtastic_Heartbeat_FIELDLIST(X, a) \ + +#define meshtastic_Heartbeat_CALLBACK NULL +#define meshtastic_Heartbeat_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_Position_msg; extern const pb_msgdesc_t meshtastic_User_msg; extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; @@ -1296,6 +1315,7 @@ extern const pb_msgdesc_t meshtastic_Compressed_msg; extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; extern const pb_msgdesc_t meshtastic_Neighbor_msg; extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; +extern const pb_msgdesc_t meshtastic_Heartbeat_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Position_fields &meshtastic_Position_msg @@ -1316,12 +1336,14 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; #define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg #define meshtastic_Neighbor_fields &meshtastic_Neighbor_msg #define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg +#define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg /* Maximum encoded size of messages (where known) */ #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 #define meshtastic_FromRadio_size 510 +#define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 81 #define meshtastic_MeshPacket_size 326 #define meshtastic_MqttClientProxyMessage_size 501 From cbc0aa16c5851f4a822a37d24f2d1798a153ad80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 15 Mar 2024 16:37:47 +0100 Subject: [PATCH 0121/1377] fix compilation --- src/gps/GPS.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 5b7d18bab..7073e4eb0 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1151,10 +1151,11 @@ GPS *GPS::createGps() LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio); LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio); _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); - -#else +#elif defined(ARCH_RP2040) _serial_gps->setFIFOSize(256); _serial_gps->begin(GPS_BAUDRATE); +#else + _serial_gps->begin(GPS_BAUDRATE); #endif /* From b06c77d46fa2867a629dbc0c3d14e9400076005f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 15 Mar 2024 16:43:39 +0100 Subject: [PATCH 0122/1377] don't fix this to a hardware model. --- src/gps/GPS.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 7073e4eb0..4812786cb 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1212,10 +1212,11 @@ bool GPS::factoryReset() // byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B}; // _serial_gps->write(_message_CFG_RST_COLDSTART, sizeof(_message_CFG_RST_COLDSTART)); // delay(1000); - } else if (HW_VENDOR == meshtastic_HardwareModel_RPI_PICO) { + } else { + // fire this for good measure, if we have an L76B - won't harm other devices. _serial_gps->write("$PMTK104*37\r\n"); // No PMTK_ACK for this command. - } else { + delay(100); // send the UBLOX Factory Reset Command regardless of detect state, something is very wrong, just assume it's UBLOX. // Factory Reset byte _message_reset[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFB, 0x00, 0x00, 0x00, From da7cd5fc7fbe460969246e83bad981f68460a0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 15 Mar 2024 16:45:14 +0100 Subject: [PATCH 0123/1377] new Accelerometer lib (#3413) * new Accelerometer lib * Use our fork till upstreasm merges changes. * that PR escalated quickly * resurrect display flip --- platformio.ini | 2 +- src/AccelerometerThread.h | 70 ++++++++++++++------------------------- 2 files changed, 25 insertions(+), 47 deletions(-) diff --git a/platformio.ini b/platformio.ini index 68cfa1753..dbd15645f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -131,4 +131,4 @@ lib_deps = adafruit/Adafruit PM25 AQI Sensor@^1.0.6 adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 - https://github.com/lewisxhe/BMA423_Library@^0.0.1 + https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 9898f4d49..6827908b7 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -7,16 +7,16 @@ #include #include #include +#include #include -#include -BMA423 bmaSensor; +SensorBMA423 bmaSensor; bool BMA_IRQ = false; #define ACCELEROMETER_CHECK_INTERVAL_MS 100 #define ACCELEROMETER_CLICK_THRESHOLD 40 -uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len) +int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) { Wire.beginTransmission(address); Wire.write(reg); @@ -29,7 +29,7 @@ uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len) return 0; // Pass } -uint16_t writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len) +int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) { Wire.beginTransmission(address); Wire.write(reg); @@ -72,24 +72,14 @@ class AccelerometerThread : public concurrency::OSThread lis.setRange(LIS3DH_RANGE_2_G); // Adjust threshold, higher numbers are less sensitive lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD); - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.begin(readRegister, writeRegister, delay)) { + } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && + bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) { LOG_DEBUG("BMA423 initializing\n"); - Acfg cfg; - cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ; - cfg.range = BMA4_ACCEL_RANGE_2G; - cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4; - cfg.perf_mode = BMA4_CONTINUOUS_MODE; - bmaSensor.setAccelConfig(cfg); - bmaSensor.enableAccel(); - - struct bma4_int_pin_config pin_config; - pin_config.edge_ctrl = BMA4_LEVEL_TRIGGER; - pin_config.lvl = BMA4_ACTIVE_HIGH; - pin_config.od = BMA4_PUSH_PULL; - pin_config.output_en = BMA4_OUTPUT_ENABLE; - pin_config.input_en = BMA4_INPUT_DISABLE; - // The correct trigger interrupt needs to be configured as needed - bmaSensor.setINTPinConfig(pin_config, BMA4_INTR1_MAP); + bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4, + bmaSensor.PERF_CONTINUOUS_MODE); + bmaSensor.enableAccelerometer(); + bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE, + BMA4_INPUT_DISABLE); #ifdef BMA423_INT pinMode(BMA4XX_INT, INPUT); @@ -102,34 +92,22 @@ class AccelerometerThread : public concurrency::OSThread RISING); // Select the interrupt mode according to the actual circuit #endif - struct bma423_axes_remap remap_data; #ifdef T_WATCH_S3 - remap_data.x_axis = 1; - remap_data.x_axis_sign = 0; - remap_data.y_axis = 0; - remap_data.y_axis_sign = 0; - remap_data.z_axis = 2; - remap_data.z_axis_sign = 1; -#else - remap_data.x_axis = 0; - remap_data.x_axis_sign = 1; - remap_data.y_axis = 1; - remap_data.y_axis_sign = 0; - remap_data.z_axis = 2; - remap_data.z_axis_sign = 1; -#endif // Need to raise the wrist function, need to set the correct axis - bmaSensor.setRemapAxes(&remap_data); - // sensor.enableFeature(BMA423_STEP_CNTR, true); - bmaSensor.enableFeature(BMA423_TILT, true); - bmaSensor.enableFeature(BMA423_WAKEUP, true); - // sensor.resetStepCounter(); + bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER); +#else + bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); +#endif + // bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true); + bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true); + bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true); + // bmaSensor.resetPedometer(); // Turn on feature interrupt - bmaSensor.enableStepCountInterrupt(); - bmaSensor.enableTiltInterrupt(); + bmaSensor.enablePedometerIRQ(); + bmaSensor.enableTiltIRQ(); // It corresponds to isDoubleClick interrupt - bmaSensor.enableWakeupInterrupt(); + bmaSensor.enableWakeupIRQ(); } } @@ -150,8 +128,8 @@ class AccelerometerThread : public concurrency::OSThread buttonPress(); return 500; } - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.getINT()) { - if (bmaSensor.isTilt() || bmaSensor.isDoubleClick()) { + } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) { + if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) { wakeScreen(); return 500; } From b9004152189933d9e0de7184ec7687166d9415c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 15 Mar 2024 19:47:47 +0100 Subject: [PATCH 0124/1377] that should work now --- src/gps/GPS.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 4812786cb..2321ee246 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1151,9 +1151,6 @@ GPS *GPS::createGps() LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio); LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio); _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); -#elif defined(ARCH_RP2040) - _serial_gps->setFIFOSize(256); - _serial_gps->begin(GPS_BAUDRATE); #else _serial_gps->begin(GPS_BAUDRATE); #endif From 34bc22f94db27fb59d609b969c285644f30937a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 14 Mar 2024 16:29:28 +0100 Subject: [PATCH 0125/1377] (2/3) Add Slow Clock Support for RP2040 platform. This will disable USB Softserial. --- arch/rp2040/rp2040.ini | 4 +-- src/SerialConsole.cpp | 8 +++++ src/modules/SerialModule.cpp | 10 ++++++ src/platform/rp2040/main-rp2040.cpp | 52 ++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini index edc4373ad..dd3a4d7ff 100644 --- a/arch/rp2040/rp2040.ini +++ b/arch/rp2040/rp2040.ini @@ -1,8 +1,8 @@ ; Common settings for rp2040 Processor based targets [rp2040_base] -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#612de5399d68b359053f1307ed223d400aea975c +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2 extends = arduino_base -platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.6.2 +platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2 board_build.core = earlephilhower board_build.filesystem_size = 0.5m diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index ed217c3ed..485329ddc 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -3,7 +3,11 @@ #include "PowerFSM.h" #include "configuration.h" +#ifdef RP2040_SLOW_CLOCK +#define Port Serial2 +#else #define Port Serial +#endif // Defaulting to the formerly removed phone_timeout_secs value of 15 minutes #define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL @@ -31,6 +35,10 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con canWrite = false; // We don't send packets to our port until it has talked to us first // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks +#ifdef RP2040_SLOW_CLOCK + Port.setTX(SERIAL2_TX); + Port.setRX(SERIAL2_RX); +#endif Port.begin(SERIAL_BAUD); #if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040) time_t timeout = millis(); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 820e1fb62..1dee42a8d 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -126,8 +126,13 @@ int32_t SerialModule::runOnce() uint32_t baud = getBaudRate(); if (moduleConfig.serial.override_console_serial_port) { +#ifdef RP2040_SLOW_CLOCK + Serial2.flush(); + serialPrint = &Serial2; +#else Serial.flush(); serialPrint = &Serial; +#endif // Give it a chance to flush out 💩 delay(10); } @@ -151,8 +156,13 @@ int32_t SerialModule::runOnce() Serial2.begin(baud, SERIAL_8N1); Serial2.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } else { +#ifdef RP2040_SLOW_CLOCK + Serial2.begin(baud, SERIAL_8N1); + Serial2.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); +#else Serial.begin(baud, SERIAL_8N1); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); +#endif } #else Serial.begin(baud, SERIAL_8N1); diff --git a/src/platform/rp2040/main-rp2040.cpp b/src/platform/rp2040/main-rp2040.cpp index 283b801f1..af3aeadc3 100644 --- a/src/platform/rp2040/main-rp2040.cpp +++ b/src/platform/rp2040/main-rp2040.cpp @@ -1,4 +1,7 @@ #include "configuration.h" +#include +#include +#include #include #include @@ -35,9 +38,56 @@ void rp2040Setup() Taken from CPU cycle counter and ROSC oscillator, so should be pretty random. */ randomSeed(rp2040.hwrand32()); + +#ifdef RP2040_SLOW_CLOCK + uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY); + uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY); + uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC); + uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS); + uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI); + uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB); + uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC); + uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC); + + LOG_INFO("Clock speed:\n"); + LOG_INFO("pll_sys = %dkHz\n", f_pll_sys); + LOG_INFO("pll_usb = %dkHz\n", f_pll_usb); + LOG_INFO("rosc = %dkHz\n", f_rosc); + LOG_INFO("clk_sys = %dkHz\n", f_clk_sys); + LOG_INFO("clk_peri = %dkHz\n", f_clk_peri); + LOG_INFO("clk_usb = %dkHz\n", f_clk_usb); + LOG_INFO("clk_adc = %dkHz\n", f_clk_adc); + LOG_INFO("clk_rtc = %dkHz\n", f_clk_rtc); +#endif } void enterDfuMode() { reset_usb_boot(0, 0); -} \ No newline at end of file +} + +/* Init in early boot state. */ +#ifdef RP2040_SLOW_CLOCK +void initVariant() +{ + /* Set the system frequency to 18 MHz. */ + set_sys_clock_khz(18 * KHZ, false); + /* The previous line automatically detached clk_peri from clk_sys, and + attached it to pll_usb. We need to attach clk_peri back to system PLL to keep SPI + working at this low speed. + For details see https://github.com/jgromes/RadioLib/discussions/938 + */ + clock_configure(clk_peri, + 0, // No glitchless mux + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, // System PLL on AUX mux + 18 * MHZ, // Input frequency + 18 * MHZ // Output (must be same as no divider) + ); + /* Run also ADC on lower clk_sys. */ + clock_configure(clk_adc, 0, CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, 18 * MHZ, 18 * MHZ); + /* Run RTC from XOSC since USB clock is off */ + clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, 12 * MHZ, 47 * KHZ); + /* Turn off USB PLL */ + pll_deinit(pll_usb); +} +#endif \ No newline at end of file From 52cfec29fcf595aa3f069993a6b0dec293110647 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 15 Mar 2024 16:17:47 -0500 Subject: [PATCH 0126/1377] More comprehensive client proxy queue guards (#3414) * More comprehensive MQTT thread and queue guards * Consolidate logic * Remove channel check * Check for map_reporting_enabled as well * Update message * Remove channel check from here as well * One liner * Start the mqtt thread back up when channels change and we want mqtt --- src/mesh/Channels.cpp | 16 ++++++++++++++++ src/mesh/Channels.h | 3 +++ src/mesh/PhoneAPI.cpp | 6 +++++- src/mqtt/MQTT.cpp | 21 ++++----------------- src/mqtt/MQTT.h | 4 ++++ 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 3e9c78241..93dec7e7d 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -7,6 +7,8 @@ #include +#include "mqtt/MQTT.h" + /// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}; @@ -193,6 +195,10 @@ void Channels::onConfigChanged() if (ch.role == meshtastic_Channel_Role_PRIMARY) primaryIndex = i; } + if (channels.anyMqttEnabled() && mqtt && !mqtt->isEnabled()) { + LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately\n"); + mqtt->start(); + } } meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex) @@ -237,6 +243,16 @@ void Channels::setChannel(const meshtastic_Channel &c) old = c; // slam in the new settings/role } +bool Channels::anyMqttEnabled() +{ + for (int i = 0; i < getNumChannels(); i++) + if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings && + (channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled)) + return true; + + return false; +} + const char *Channels::getName(size_t chIndex) { // Convert the short "" representation for Default into a usable string diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 0e11605c4..a1c4ba171 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -105,6 +105,9 @@ class Channels // Returns true if we can be reached via a channel with the default settings given a region and modem preset bool hasDefaultChannel(); + // Returns true if any of our channels have enabled MQTT uplink or downlink + bool anyMqttEnabled(); + private: /** Given a channel index, change to use the crypto key specified by that index * diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 270bf613f..8e8d69156 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -105,8 +105,12 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) break; case meshtastic_ToRadio_mqttClientProxyMessage_tag: LOG_INFO("Got MqttClientProxy message\n"); - if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled) { + if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled && + (channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) { mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage); + } else { + LOG_WARN("MqttClientProxy received but proxy is not enabled, no channels have up/downlink, or map reporting " + "not enabled\n"); } break; default: diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index c518bc4b5..0d99a3cfd 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -371,22 +371,9 @@ void MQTT::sendSubscriptions() bool MQTT::wantsLink() const { - bool hasChannelorMapReport = false; + bool hasChannelorMapReport = + moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); - if (moduleConfig.mqtt.enabled) { - hasChannelorMapReport = moduleConfig.mqtt.map_reporting_enabled; - if (!hasChannelorMapReport) { - // No need for link if no channel needed it - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) { - const auto &ch = channels.getByIndex(i); - if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) { - hasChannelorMapReport = true; - break; - } - } - } - } if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) return true; @@ -401,7 +388,7 @@ bool MQTT::wantsLink() const int32_t MQTT::runOnce() { - if (!moduleConfig.mqtt.enabled) + if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); bool wantConnection = wantsLink(); @@ -915,4 +902,4 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) (json["from"]->AsNumber() == nodeDB.getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type (json.find("payload") != json.end()); // should have a payload -} +} \ No newline at end of file diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index eeeb00d92..dbc0c77b3 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -71,6 +71,10 @@ class MQTT : private concurrency::OSThread void onClientProxyReceive(meshtastic_MqttClientProxyMessage msg); + bool isEnabled() { return this->enabled; }; + + void start() { setIntervalFromNow(0); }; + protected: PointerQueue mqttQueue; From 0dda20bc353343d8e8a8edcfab7fed80d33b047a Mon Sep 17 00:00:00 2001 From: Andre K Date: Fri, 15 Mar 2024 19:12:30 -0300 Subject: [PATCH 0127/1377] fix for I2C scan getting stuck (#3375) * refactor: add delay for T-Echo peripherals setup * comment out `PIN_POWER_EN1` --- src/main.cpp | 4 ++-- src/sleep.cpp | 2 +- variants/t-echo/variant.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index bb9b68631..fe5d455f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -223,7 +223,7 @@ void setup() #if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN) pinMode(PIN_POWER_EN, OUTPUT); digitalWrite(PIN_POWER_EN, HIGH); - digitalWrite(PIN_POWER_EN1, INPUT); + // digitalWrite(PIN_POWER_EN1, INPUT); #endif #if defined(LORA_TCXO_GPIO) @@ -965,4 +965,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} diff --git a/src/sleep.cpp b/src/sleep.cpp index bfacffeb9..6d8e4f3cc 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -203,7 +203,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #ifdef TTGO_T_ECHO #ifdef PIN_POWER_EN pinMode(PIN_POWER_EN, INPUT); // power off peripherals - pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); + // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif #endif diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 19a66719f..13f74d303 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -158,7 +158,7 @@ External serial flash WP25R1635FZUIL0 // Controls power for all peripherals (eink + GPS + LoRa + Sensor) #define PIN_POWER_EN (0 + 12) -#define PIN_POWER_EN1 (0 + 13) +// #define PIN_POWER_EN1 (0 + 13) #define USE_EINK From 0de36fbfb0bd5a6cd773726de03b084ab96d7960 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:12:45 -0500 Subject: [PATCH 0128/1377] [create-pull-request] automated change (#3419) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index cf25b390d..b2b145e33 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit cf25b390d65113980b1a239e16faa79c7730a736 +Subproject commit b2b145e3321beab1441fa59290137ab42eb38dc8 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 8f260589c..2f57f1ae2 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -819,7 +819,7 @@ typedef struct _meshtastic_ToRadio { /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */ meshtastic_MqttClientProxyMessage mqttClientProxyMessage; /* Heartbeat message (used to keep the device connection awake on serial) */ - meshtastic_Heartbeat hearbeat; + meshtastic_Heartbeat heartbeat; }; } meshtastic_ToRadio; @@ -1066,7 +1066,7 @@ extern "C" { #define meshtastic_ToRadio_disconnect_tag 4 #define meshtastic_ToRadio_xmodemPacket_tag 5 #define meshtastic_ToRadio_mqttClientProxyMessage_tag 6 -#define meshtastic_ToRadio_hearbeat_tag 7 +#define meshtastic_ToRadio_heartbeat_tag 7 /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ @@ -1247,13 +1247,13 @@ X(a, STATIC, ONEOF, UINT32, (payload_variant,want_config_id,want_config_i X(a, STATIC, ONEOF, BOOL, (payload_variant,disconnect,disconnect), 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 5) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,hearbeat,hearbeat), 7) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,heartbeat,heartbeat), 7) #define meshtastic_ToRadio_CALLBACK NULL #define meshtastic_ToRadio_DEFAULT NULL #define meshtastic_ToRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket #define meshtastic_ToRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem #define meshtastic_ToRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage -#define meshtastic_ToRadio_payload_variant_hearbeat_MSGTYPE meshtastic_Heartbeat +#define meshtastic_ToRadio_payload_variant_heartbeat_MSGTYPE meshtastic_Heartbeat #define meshtastic_Compressed_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, portnum, 1) \ From 9586606229f59883938b263c9abc451ce6967b3a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 15 Mar 2024 18:40:48 -0500 Subject: [PATCH 0129/1377] Handle for heartbeat toradio packets (#3420) --- src/SerialConsole.cpp | 2 +- src/mesh/PhoneAPI.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 485329ddc..e17c8f99e 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -72,7 +72,7 @@ bool SerialConsole::checkIsConnected() /** * we override this to notice when we've received a protobuf over the serial - * stream. Then we shunt off debug serial output. + * stream. Then we shut off debug serial output. */ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 8e8d69156..e6b336d41 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -113,6 +113,9 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) "not enabled\n"); } break; + case meshtastic_ToRadio_heartbeat_tag: + LOG_DEBUG("Got client heartbeat\n"); + break; default: // Ignore nop messages // LOG_DEBUG("Error: unexpected ToRadio variant\n"); From 611f291d4d5e26e6d19c9f54ff0188b836209a4b Mon Sep 17 00:00:00 2001 From: David Ellefsen <93522+titan098@users.noreply.github.com> Date: Sat, 16 Mar 2024 02:19:50 +0200 Subject: [PATCH 0130/1377] Factory reset GNSS_MODEL_MTK GPS modules with PCAS10,3 (#3388) Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 2321ee246..18932e066 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1209,6 +1209,11 @@ bool GPS::factoryReset() // byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B}; // _serial_gps->write(_message_CFG_RST_COLDSTART, sizeof(_message_CFG_RST_COLDSTART)); // delay(1000); + } else if (gnssModel == GNSS_MODEL_MTK) { + // send the CAS10 to perform a factory restart of the device (and other device that support PCAS statements) + LOG_INFO("GNSS Factory Reset via PCAS10,3\n"); + _serial_gps->write("$PCAS10,3*1F\r\n"); + delay(100); } else { // fire this for good measure, if we have an L76B - won't harm other devices. _serial_gps->write("$PMTK104*37\r\n"); From 54a2a4bcc67793fc8e72621d487153d4eb801c3d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 07:39:28 -0500 Subject: [PATCH 0131/1377] [create-pull-request] automated change (#3422) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index b2b145e33..556e49ba6 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit b2b145e3321beab1441fa59290137ab42eb38dc8 +Subproject commit 556e49ba619e2f4d8fa3c2dee2a94129a43d5f08 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 79800d4b4..d6a2a0272 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -141,7 +141,8 @@ typedef struct _meshtastic_DeviceState { NodeDB.cpp in the device code. */ uint32_t version; /* Used only during development. - Indicates developer is testing and changes should never be saved to flash. */ + Indicates developer is testing and changes should never be saved to flash. + Deprecated in 2.3.1 */ bool no_save; /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */ bool did_gps_reset; From 13cc1b0252b5fdae9a195412039ea46b0288d634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 16 Mar 2024 16:01:43 +0100 Subject: [PATCH 0132/1377] (3/3) Add variant for pico with waveshare and GPS hat (#3412) * (3/3) Add variant for pico with waveshare and GPS hat, utilizing slow clock. * Not everybody has Serial2 * Trunk * Push it real gud * No init --------- Co-authored-by: Ben Meadors --- src/configuration.h | 1 + src/detect/ScanI2CTwoWire.cpp | 10 ++- src/modules/Telemetry/Sensor/INA219Sensor.cpp | 6 +- variants/rpipico-slowclock/platformio.ini | 28 ++++++ variants/rpipico-slowclock/variant.h | 87 +++++++++++++++++++ 5 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 variants/rpipico-slowclock/platformio.ini create mode 100644 variants/rpipico-slowclock/variant.h diff --git a/src/configuration.h b/src/configuration.h index ac8f9435a..ec32c72d1 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -111,6 +111,7 @@ along with this program. If not, see . #define MCP9808_ADDR 0x18 #define INA_ADDR 0x40 #define INA_ADDR_ALTERNATE 0x41 +#define INA_ADDR_WAVESHARE_UPS 0x43 #define INA3221_ADDR 0x42 #define QMC6310_ADDR 0x1C #define QMI8658_ADDR 0x6B diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index b6eca5fa4..146daa3dc 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -183,8 +183,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port) #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) case ATECC608B_ADDR: - type = ATECC608B; - if (atecc.begin(addr.address) == true) { +#ifdef RP2040_SLOW_CLOCK + if (atecc.begin(addr.address, Wire, Serial2) == true) +#else + if (atecc.begin(addr.address) == true) +#endif + + { LOG_INFO("ATECC608B initialized\n"); } else { LOG_WARN("ATECC608B initialization failed\n"); @@ -254,6 +259,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) case INA_ADDR: case INA_ADDR_ALTERNATE: + case INA_ADDR_WAVESHARE_UPS: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2); LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue); if (registerValue == 0x5449) { diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp index 5a1faa99f..ecb564368 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp @@ -4,6 +4,10 @@ #include "configuration.h" #include +#ifndef INA219_MULTIPLIER +#define INA219_MULTIPLIER 1.0f +#endif + INA219Sensor::INA219Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA219, "INA219") {} int32_t INA219Sensor::runOnce() @@ -26,7 +30,7 @@ void INA219Sensor::setup() {} bool INA219Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.voltage = ina219.getBusVoltage_V(); - measurement->variant.environment_metrics.current = ina219.getCurrent_mA(); + measurement->variant.environment_metrics.current = ina219.getCurrent_mA() * INA219_MULTIPLIER; return true; } diff --git a/variants/rpipico-slowclock/platformio.ini b/variants/rpipico-slowclock/platformio.ini new file mode 100644 index 000000000..e583c4b0d --- /dev/null +++ b/variants/rpipico-slowclock/platformio.ini @@ -0,0 +1,28 @@ +[env:pico_slowclock] +extends = rp2040_base +board = rpipico +upload_protocol = jlink +# debug settings for external openocd with RP2040 support (custom build) +debug_tool = custom +debug_init_cmds = + target extended-remote localhost:3333 + $INIT_BREAK + monitor reset halt + $LOAD_CMDS + monitor init + monitor reset halt + +# add our variants files to the include and src paths +build_flags = ${rp2040_base.build_flags} + -DRPI_PICO + -Ivariants/rpipico_slowclock + -DDEBUG_RP2040_PORT=Serial2 + -DHW_SPI1_DEVICE + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -g + -DNO_USB +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} + -g + -DNO_USB \ No newline at end of file diff --git a/variants/rpipico-slowclock/variant.h b/variants/rpipico-slowclock/variant.h new file mode 100644 index 000000000..fb97ec0fb --- /dev/null +++ b/variants/rpipico-slowclock/variant.h @@ -0,0 +1,87 @@ +#define ARDUINO_ARCH_AVR + +// Build with slow system clock enabled to reduce power consumption. +#define RP2040_SLOW_CLOCK + +#ifdef RP2040_SLOW_CLOCK +// Redefine UART1 serial log output to avoid collision with UART0 for GPS. +#define SERIAL2_TX 4 +#define SERIAL2_RX 5 +// Reroute log output in SensorLib when USB is not available +#define log_e(...) Serial2.printf(__VA_ARGS__) +#define log_i(...) Serial2.printf(__VA_ARGS__) +#define log_d(...) Serial2.printf(__VA_ARGS__) +#endif + +// Expecting the Waveshare Pico GPS hat +#define HAS_GPS 1 + +// Enable OLED Screen +#define HAS_SCREEN 1 +#define USE_SH1106 1 +#define RESET_OLED 13 + +// Redefine I2C0 pins to avoid collision with UART1/Serial2. +#define I2C_SDA 8 +#define I2C_SCL 9 + +// Redefine Waveshare UPS-A/B I2C_1 pins: +#define I2C_SDA1 6 +#define I2C_SCL1 7 +// Waveshare UPS-A/B uses a 0.01 Ohm shunt for the INA219 sensor +#define INA219_MULTIPLIER 10.0f + +// Waveshare Pico GPS L76B pins: +#define GPS_RX_PIN 1 +#define GPS_TX_PIN 0 + +// Wakeup from backup mode +// #define PIN_GPS_FORCE_ON 14 +// No GPS reset available +#undef PIN_GPS_RESET +/* + * For PPS output the resistor R20 needs to be populated with 0 Ohm + * on the Waveshare Pico GPS board. + */ +#define PIN_GPS_PPS 16 +/* + * For standby mode switching the resistor R18 needs to be populated + * with 0 Ohm on the Waveshare Pico GPS board. + */ +#define PIN_GPS_STANDBY 17 + +#define BUTTON_PIN 18 +#define EXT_NOTIFY_OUT 22 +#define LED_PIN PIN_LED + +#define BATTERY_PIN 26 +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +#define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION + +#define USE_SX1262 + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +#define LORA_SCK 10 +#define LORA_MISO 12 +#define LORA_MOSI 11 +#define LORA_CS 3 + +#define LORA_DIO0 RADIOLIB_NC +#define LORA_RESET 15 +#define LORA_DIO1 20 +#define LORA_DIO2 2 +#define LORA_DIO3 RADIOLIB_NC + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file From e27f029d09424698f6e8b00ac64efe73ee85749a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 16 Mar 2024 19:56:42 -0500 Subject: [PATCH 0133/1377] Bump minimum NodeInfo send to 5 minutes (#3423) * Bump minimum NodeInfo send to 3 minutes * 5 --- src/modules/NodeInfoModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 5177af33a..6b4289970 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -58,8 +58,8 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha meshtastic_MeshPacket *NodeInfoModule::allocReply() { uint32_t now = millis(); - // If we sent our NodeInfo less than 1 min. ago, don't send it again as it may be still underway. - if (lastSentToMesh && (now - lastSentToMesh) < 60 * 1000) { + // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. + if (lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) { LOG_DEBUG("Sending NodeInfo will be ignored since we just sent it.\n"); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; From bb57ccfc9eccc9520950c125debcf7bfe73b87ab Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 17 Mar 2024 08:16:22 -0500 Subject: [PATCH 0134/1377] Remove devicestate no_save (#3424) --- src/mesh/NodeDB.cpp | 74 ++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6898f7702..a9fffc335 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -97,13 +97,6 @@ bool NodeDB::resetRadioConfig(bool factory_reset) channels.onConfigChanged(); - // temp hack for quicker testing - // devicestate.no_save = true; - if (devicestate.no_save) { - LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n"); - // Put your development config changes here - } - // Update the global myRegion initRegion(); @@ -644,61 +637,52 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ void NodeDB::saveChannelsToDisk() { - if (!devicestate.no_save) { #ifdef FSCom - FSCom.mkdir("/prefs"); + FSCom.mkdir("/prefs"); #endif - saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); - } + saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); } void NodeDB::saveDeviceStateToDisk() { - if (!devicestate.no_save) { #ifdef FSCom - FSCom.mkdir("/prefs"); + FSCom.mkdir("/prefs"); #endif - saveProto(prefFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate); - } } void NodeDB::saveToDisk(int saveWhat) { - if (!devicestate.no_save) { #ifdef FSCom - FSCom.mkdir("/prefs"); + FSCom.mkdir("/prefs"); #endif - if (saveWhat & SEGMENT_DEVICESTATE) { - saveDeviceStateToDisk(); - } + if (saveWhat & SEGMENT_DEVICESTATE) { + saveDeviceStateToDisk(); + } - if (saveWhat & SEGMENT_CONFIG) { - config.has_device = true; - config.has_display = true; - config.has_lora = true; - config.has_position = true; - config.has_power = true; - config.has_network = true; - config.has_bluetooth = true; - saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); - } + if (saveWhat & SEGMENT_CONFIG) { + config.has_device = true; + config.has_display = true; + config.has_lora = true; + config.has_position = true; + config.has_power = true; + config.has_network = true; + config.has_bluetooth = true; + saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); + } - if (saveWhat & SEGMENT_MODULECONFIG) { - moduleConfig.has_canned_message = true; - moduleConfig.has_external_notification = true; - moduleConfig.has_mqtt = true; - moduleConfig.has_range_test = true; - moduleConfig.has_serial = true; - moduleConfig.has_store_forward = true; - moduleConfig.has_telemetry = true; - saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); - } + if (saveWhat & SEGMENT_MODULECONFIG) { + moduleConfig.has_canned_message = true; + moduleConfig.has_external_notification = true; + moduleConfig.has_mqtt = true; + moduleConfig.has_range_test = true; + moduleConfig.has_serial = true; + moduleConfig.has_store_forward = true; + moduleConfig.has_telemetry = true; + saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); + } - if (saveWhat & SEGMENT_CHANNELS) { - saveChannelsToDisk(); - } - } else { - LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n"); + if (saveWhat & SEGMENT_CHANNELS) { + saveChannelsToDisk(); } } From 0d1d79b6d15f5e7c4bcde12a1f59af3c204117c3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 17 Mar 2024 08:18:30 -0500 Subject: [PATCH 0135/1377] Extract default intervals and coalesce methods into their own file / static class methods (#3425) * Extract default intervals and coalesce methods into their own file / static class methods * Missed pax * Still managed to miss one --- src/PowerFSM.cpp | 18 ++++---- src/PowerFSMThread.h | 3 +- src/gps/GPS.cpp | 4 +- src/mesh/Default.cpp | 23 ++++++++++ src/mesh/Default.h | 30 +++++++++++++ src/mesh/NodeDB.cpp | 1 + src/mesh/NodeDB.h | 42 ------------------- src/mesh/PhoneAPI.cpp | 1 + src/modules/AdminModule.cpp | 1 + src/modules/AtakPluginModule.cpp | 1 + src/modules/DetectionSensorModule.cpp | 7 ++-- src/modules/NeighborInfoModule.cpp | 3 +- src/modules/NodeInfoModule.cpp | 3 +- src/modules/PositionModule.cpp | 11 +++-- src/modules/Telemetry/AirQualityTelemetry.cpp | 3 +- src/modules/Telemetry/DeviceTelemetry.cpp | 3 +- .../Telemetry/EnvironmentTelemetry.cpp | 5 ++- src/modules/Telemetry/PowerTelemetry.cpp | 5 ++- src/modules/esp32/PaxcounterModule.cpp | 11 ++--- src/mqtt/MQTT.cpp | 1 + 20 files changed, 103 insertions(+), 73 deletions(-) create mode 100644 src/mesh/Default.cpp create mode 100644 src/mesh/Default.h diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index f98b03077..5d86987df 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -8,6 +8,7 @@ * actions to be taken upon entering or exiting each state. */ #include "PowerFSM.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "configuration.h" @@ -45,7 +46,7 @@ static void sdsEnter() { LOG_DEBUG("Enter state: SDS\n"); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw - doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs), false); + doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false); } extern Power *power; @@ -343,13 +344,13 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); powerFSM.add_timed_transition(&stateON, &stateDARK, - getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout"); powerFSM.add_timed_transition(&statePOWER, &stateDARK, - getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout"); powerFSM.add_timed_transition(&stateDARK, &stateDARK, - getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout"); // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) @@ -359,11 +360,12 @@ void PowerFSM_setup() // modules if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) { powerFSM.add_timed_transition(&stateNB, &stateLS, - getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, + Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, "Min wake timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateLS, - getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), - NULL, "Bluetooth timeout"); + powerFSM.add_timed_transition( + &stateDARK, &stateLS, + Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, + "Bluetooth timeout"); } #endif diff --git a/src/PowerFSMThread.h b/src/PowerFSMThread.h index 584c955aa..fb640dd8b 100644 --- a/src/PowerFSMThread.h +++ b/src/PowerFSMThread.h @@ -1,3 +1,4 @@ +#include "Default.h" #include "NodeDB.h" #include "PowerFSM.h" #include "concurrency/OSThread.h" @@ -28,7 +29,7 @@ class PowerFSMThread : public OSThread timeLastPowered = millis(); } else if (config.power.on_battery_shutdown_after_secs > 0 && config.power.on_battery_shutdown_after_secs != UINT32_MAX && millis() > (timeLastPowered + - getConfiguredOrDefaultMs( + Default::getConfiguredOrDefaultMs( config.power.on_battery_shutdown_after_secs))) { // shutdown after 30 minutes unpowered powerFSM.trigger(EVENT_SHUTDOWN); } diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 18932e066..5595172dd 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1,4 +1,5 @@ #include "GPS.h" +#include "Default.h" #include "NodeDB.h" #include "RTC.h" #include "configuration.h" @@ -495,7 +496,6 @@ bool GPS::setup() } } } - } else { // LOG_INFO("u-blox M10 hardware found.\n"); delay(1000); @@ -759,7 +759,7 @@ uint32_t GPS::getWakeTime() const if (t == UINT32_MAX) return t; // already maxint - return getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); + return Default::Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); } /** Get how long we should sleep between aqusition attempts in msecs diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp new file mode 100644 index 000000000..db058c5b0 --- /dev/null +++ b/src/mesh/Default.cpp @@ -0,0 +1,23 @@ +#include "Default.h" + +uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval) +{ + if (configuredInterval > 0) + return configuredInterval * 1000; + return default_broadcast_interval_secs * 1000; +} + +uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) +{ + if (configuredInterval > 0) + return configuredInterval * 1000; + return defaultInterval * 1000; +} + +uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue) +{ + if (configured > 0) + return configured; + + return defaultValue; +} \ No newline at end of file diff --git a/src/mesh/Default.h b/src/mesh/Default.h new file mode 100644 index 000000000..734cdf519 --- /dev/null +++ b/src/mesh/Default.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#define ONE_DAY 24 * 60 * 60 + +#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) +#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) +#define default_wait_bluetooth_secs IF_ROUTER(1, 60) +#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep +#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60) +#define default_min_wake_secs 10 +#define default_screen_on_secs IF_ROUTER(1, 60 * 10) +#define default_node_info_broadcast_secs 3 * 60 * 60 +#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour + +#define default_mqtt_address "mqtt.meshtastic.org" +#define default_mqtt_username "meshdev" +#define default_mqtt_password "large4cats" +#define default_mqtt_root "msh" + +#define IF_ROUTER(routerVal, normalVal) \ + ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal)) + +class Default +{ + public: + static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval); + static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval); + static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); +}; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a9fffc335..04b6fe89d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -3,6 +3,7 @@ #include "../detect/ScanI2C.h" #include "Channels.h" #include "CryptoEngine.h" +#include "Default.h" #include "FSCommon.h" #include "GPS.h" #include "MeshRadio.h" diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index b34059fb9..930b3483e 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -191,48 +191,6 @@ extern NodeDB nodeDB; // Our delay functions check for this for times that should never expire #define NODE_DELAY_FOREVER 0xffffffff -#define IF_ROUTER(routerVal, normalVal) \ - ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal)) - -#define ONE_DAY 24 * 60 * 60 - -#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) -#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) -#define default_wait_bluetooth_secs IF_ROUTER(1, 60) -#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep -#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60) -#define default_min_wake_secs 10 -#define default_screen_on_secs IF_ROUTER(1, 60 * 10) -#define default_node_info_broadcast_secs 3 * 60 * 60 -#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour - -#define default_mqtt_address "mqtt.meshtastic.org" -#define default_mqtt_username "meshdev" -#define default_mqtt_password "large4cats" -#define default_mqtt_root "msh" - -inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval) -{ - if (configuredInterval > 0) - return configuredInterval * 1000; - return default_broadcast_interval_secs * 1000; -} - -inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) -{ - if (configuredInterval > 0) - return configuredInterval * 1000; - return defaultInterval * 1000; -} - -inline uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue) -{ - if (configured > 0) - return configured; - - return defaultValue; -} - /// Sometimes we will have Position objects that only have a time, so check for /// valid lat/lon static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index e6b336d41..d8e842149 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -1,5 +1,6 @@ #include "PhoneAPI.h" #include "Channels.h" +#include "Default.h" #include "GPS.h" #include "MeshService.h" #include "NodeDB.h" diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 06818dc88..6c4c80dbb 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -16,6 +16,7 @@ #ifdef ARCH_PORTDUINO #include "unistd.h" #endif +#include "Default.h" #include "mqtt/MQTT.h" diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index ffc4fe68a..64a85e2bf 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -1,4 +1,5 @@ #include "AtakPluginModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index 6c35e94ae..fd26749c1 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -1,10 +1,10 @@ #include "DetectionSensorModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" #include "configuration.h" #include "main.h" - DetectionSensorModule *detectionSensorModule; #define GPIO_POLLING_INTERVAL 100 @@ -49,7 +49,7 @@ int32_t DetectionSensorModule::runOnce() // LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin)); - if ((millis() - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && + if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && hasDetectionEvent()) { sendDetectionMessage(); return DELAYED_INTERVAL; @@ -58,7 +58,8 @@ int32_t DetectionSensorModule::runOnce() // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && - (millis() - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) { + (millis() - lastSentToMesh) >= + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) { sendCurrentStateMessage(); return DELAYED_INTERVAL; } diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 2e0b04afa..024f321e6 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -1,4 +1,5 @@ #include "NeighborInfoModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -194,7 +195,7 @@ int32_t NeighborInfoModule::runOnce() { bool requestReplies = false; sendNeighborInfo(NODENUM_BROADCAST, requestReplies); - return getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); + return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); } /* diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 6b4289970..370847b94 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -1,4 +1,5 @@ #include "NodeInfoModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -91,5 +92,5 @@ int32_t NodeInfoModule::runOnce() LOG_INFO("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies); sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies) } - return getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs); + return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs); } diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 59f62bd5c..853808f44 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -1,4 +1,5 @@ #include "PositionModule.h" +#include "Default.h" #include "GPS.h" #include "MeshService.h" #include "NodeDB.h" @@ -280,7 +281,7 @@ int32_t PositionModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send position again.\n", nightyNightMs); doDeepSleep(nightyNightMs, false); } @@ -291,7 +292,8 @@ int32_t PositionModule::runOnce() // We limit our GPS broadcasts to a max rate uint32_t now = millis(); - uint32_t intervalMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); + uint32_t intervalMs = + Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); uint32_t msSinceLastSend = now - lastGpsSend; // Only send packets if the channel util. is less than 25% utilized or we're a tracker with less than 40% utilized. if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && @@ -322,7 +324,7 @@ int32_t PositionModule::runOnce() if (hasValidPosition(node2)) { // The minimum time (in seconds) that would pass before we are able to send a new position packet. const uint32_t minimumTimeThreshold = - getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); + Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); auto smartPosition = getDistanceTraveledSinceLastSend(node->position); @@ -369,7 +371,8 @@ void PositionModule::sendLostAndFoundText() struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition) { // The minimum distance to travel before we are able to send a new position packet. - const uint32_t distanceTravelThreshold = getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100); + const uint32_t distanceTravelThreshold = + Default::getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100); // Determine the distance in meters between two points on the globe float distanceTraveledSinceLastSend = GeoCoord::latLongToMeter( diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index ada1fdef8..3e9b069c4 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,5 +1,6 @@ #include "AirQualityTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -43,7 +44,7 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 55000e4c6..3ed106d1c 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -1,5 +1,6 @@ #include "DeviceTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -16,7 +17,7 @@ int32_t DeviceTelemetryModule::runOnce() { uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && airTime->isTxAllowedChannelUtil() && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 7b59c28a6..203b632a7 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -1,5 +1,6 @@ #include "EnvironmentTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -41,7 +42,7 @@ int32_t EnvironmentTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -102,7 +103,7 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 300ab1f62..713f6aacb 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -1,5 +1,6 @@ #include "PowerTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -20,7 +21,7 @@ int32_t PowerTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -66,7 +67,7 @@ int32_t PowerTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 54c67fad7..aad7b5d63 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -1,8 +1,8 @@ #include "configuration.h" #if defined(ARCH_ESP32) +#include "Default.h" #include "MeshService.h" #include "PaxcounterModule.h" - #include PaxcounterModule *paxcounterModule; @@ -82,9 +82,9 @@ int32_t PaxcounterModule::runOnce() if (isActive()) { if (firstTime) { firstTime = false; - LOG_DEBUG( - "Paxcounter starting up with interval of %d seconds\n", - getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs)); + LOG_DEBUG("Paxcounter starting up with interval of %d seconds\n", + Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, + default_broadcast_interval_secs)); struct libpax_config_t configuration; libpax_default_config(&configuration); @@ -104,7 +104,8 @@ int32_t PaxcounterModule::runOnce() } else { sendInfo(NODENUM_BROADCAST); } - return getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs); + return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, + default_broadcast_interval_secs); } else { return disable(); } diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 0d99a3cfd..7e341a18c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -17,6 +17,7 @@ #include "mesh/wifi/WiFiAPClient.h" #include #endif +#include "Default.h" #include const int reconnectMax = 5; From aae49f5ecf06fb216a59b8ac4bad2e5cc008d641 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 17 Mar 2024 08:38:49 -0500 Subject: [PATCH 0136/1377] Remove confusing channel suffix (#3432) * Remove confusing channel suffix * Missed it --- src/graphics/Screen.cpp | 3 +-- src/mesh/Channels.cpp | 34 ---------------------------------- src/mesh/Channels.h | 19 ------------------- 3 files changed, 1 insertion(+), 55 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 7f20b5666..cfd8494d2 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1513,8 +1513,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 char channelStr[20]; { concurrency::LockGuard guard(&lock); - auto chName = channels.getPrimaryName(); - snprintf(channelStr, sizeof(channelStr), "%s", chName); + snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex())); } // Display power status diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 93dec7e7d..840e65bca 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -290,40 +290,6 @@ bool Channels::hasDefaultChannel() return false; } -/** -* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs. -* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they -their nodes -* aren't talking to each other. -* -* This string is of the form "#name-X". -* -* Where X is either: -* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together, -* -* This function will also need to be implemented in GUI apps that talk to the radio. -* -* https://github.com/meshtastic/firmware/issues/269 -*/ -const char *Channels::getPrimaryName() -{ - static char buf[32]; - - char suffix; - // auto channelSettings = getPrimary(); - // if (channelSettings.psk.size != 1) { - // We have a standard PSK, so generate a letter based hash. - uint8_t code = getHash(primaryIndex); - - suffix = 'A' + (code % 26); - /* } else { - suffix = '0' + channelSettings.psk.bytes[0]; - } */ - - snprintf(buf, sizeof(buf), "#%s-%c", getName(primaryIndex), suffix); - return buf; -} - /** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured) * * This method is called before decoding inbound packets diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index a1c4ba171..952445a1d 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -61,25 +61,6 @@ class Channels ChannelIndex getNumChannels() { return channelFile.channels_count; } - /** - * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different - PSKs. - * The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why - they their nodes - * aren't talking to each other. - * - * This string is of the form "#name-X". - * - * Where X is either: - * (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together, - * OR (for the standard minimially secure PSKs) a number from 0 to 9. - * - * This function will also need to be implemented in GUI apps that talk to the radio. - * - * https://github.com/meshtastic/firmware/issues/269 - */ - const char *getPrimaryName(); - /// Called by NodeDB on initial boot when the radio config settings are unset. Set a default single channel config. void initDefaults(); From b98176e73ee2eed034d435cf586f2c188120e129 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 07:33:01 -0500 Subject: [PATCH 0137/1377] [create-pull-request] automated change (#3434) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 07fadd0d8..12603eda7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 1 +build = 2 From 711b85cfe80c10ac79b16864c59956552ca8321e Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:42:44 +0100 Subject: [PATCH 0138/1377] fix WLAN crash (#3435) * fix WLAN crash * link to commit in arduinothread * revert usb mode --- platformio.ini | 2 +- variants/t-deck/platformio.ini | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c511587a6..2c373ab00 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,7 +79,7 @@ lib_deps = mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#f9f4fef2183514aa52be91d714c1455dd6f26e45 - https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3 + https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 nanopb/Nanopb@^0.4.7 erriez/ErriezCRC32@^1.0.1 diff --git a/variants/t-deck/platformio.ini b/variants/t-deck/platformio.ini index cb6033300..593fdae5e 100644 --- a/variants/t-deck/platformio.ini +++ b/variants/t-deck/platformio.ini @@ -8,6 +8,7 @@ upload_protocol = esptool build_flags = ${esp32_base.build_flags} -DT_DECK -DBOARD_HAS_PSRAM + -DMAX_THREADS=40 -DGPS_POWER_TOGGLE -Ivariants/t-deck From a6625998f5936e4503e75b31ac5a03054c933e73 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:22:45 +0100 Subject: [PATCH 0139/1377] fix compiler warnings in NodeDB.h (#3439) * fix warnings on arm * make trunk+compiler happy --- src/mesh/NodeDB.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 930b3483e..1d2086adb 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -213,4 +213,6 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) + +// Please do not remove this comment, it makes trunk and compiler happy at the same time. From 4fa7f5a748b833cdf20536153725af3ff4b4b912 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 19 Mar 2024 10:31:31 -0500 Subject: [PATCH 0140/1377] Fix devicestate persistence bug --- src/mesh/NodeDB.cpp | 1 + version.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 04b6fe89d..25d010f16 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -649,6 +649,7 @@ void NodeDB::saveDeviceStateToDisk() #ifdef FSCom FSCom.mkdir("/prefs"); #endif + saveProto(prefFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate); } void NodeDB::saveToDisk(int saveWhat) diff --git a/version.properties b/version.properties index 12603eda7..3cb488c16 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 2 +build = 1 \ No newline at end of file From 5e832e2fc6671a5eadbedd17f5f262c05498cf2e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:02:29 -0500 Subject: [PATCH 0141/1377] [create-pull-request] automated change (#3444) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 3cb488c16..12603eda7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 1 \ No newline at end of file +build = 2 From 7aa21f6e3fc550e8ed41958ef7bc5c308c18eba7 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Wed, 20 Mar 2024 16:58:48 +0100 Subject: [PATCH 0142/1377] Fixed double and missing Default class. (#3448) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. --------- Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 5595172dd..506f6d89c 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -759,7 +759,7 @@ uint32_t GPS::getWakeTime() const if (t == UINT32_MAX) return t; // already maxint - return Default::Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); + return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); } /** Get how long we should sleep between aqusition attempts in msecs @@ -775,7 +775,7 @@ uint32_t GPS::getSleepTime() const if (t == UINT32_MAX) return t; // already maxint - return t * 1000; + return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval); } void GPS::publishUpdate() From f4095ce00d7b26fcd94b8f3987e3fbdfa499067f Mon Sep 17 00:00:00 2001 From: Jim Whitelaw Date: Thu, 21 Mar 2024 05:34:34 -0600 Subject: [PATCH 0143/1377] Adds configuration option to exclude the webserver on esp32. (#3369) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds configuration option to not build/include the webserver. * Adds configuration option to not build/include the webserver. * Keep initApiServer when excluding webserver * fixes for failed formatting check * Once more with feeling! Fix for regression. * Fix includes for ARCH_ESP32 * Format changes from trunk * Merge updates from origin * Revert "Format changes from trunk" This reverts commit 436e6317744576ca6b00559937ca76235b7cd66b. * jeez! * tryfix proto conflict --------- Co-authored-by: Thomas Göttgens --- src/main.cpp | 4 +++- src/mesh/http/ContentHandler.cpp | 2 ++ src/mesh/http/WebServer.cpp | 6 +++--- src/mesh/wifi/WiFiAPClient.cpp | 12 +++++++----- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index fe5d455f8..4fc713b1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,9 @@ // #include #ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" +#endif #include "nimble/NimbleBluetooth.h" NimbleBluetooth *nimbleBluetooth; #endif @@ -864,7 +866,7 @@ void setup() #endif #endif -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER // Start web server thread. webServerThread = new WebServerThread(); #endif diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7640e879c..1557948d8 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -1,3 +1,4 @@ +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "NodeDB.h" #include "PowerFSM.h" #include "RadioLibInterface.h" @@ -855,3 +856,4 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) res->print(value->Stringify().c_str()); delete value; } +#endif \ No newline at end of file diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 7814f2c29..83fe20dd8 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -1,3 +1,4 @@ +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" #include "NodeDB.h" #include "graphics/Screen.h" @@ -92,7 +93,6 @@ static void taskCreateCert(void *parameter) LOG_DEBUG("Retrieved Private Key: %d Bytes\n", cert->getPKLength()); LOG_DEBUG("Retrieved Certificate: %d Bytes\n", cert->getCertLength()); - } else { LOG_INFO("Creating the certificate. This may take a while. Please wait...\n"); @@ -105,7 +105,6 @@ static void taskCreateCert(void *parameter) if (createCertResult != 0) { LOG_ERROR("Creating the certificate failed\n"); - } else { LOG_INFO("Creating the certificate was successful\n"); @@ -210,4 +209,5 @@ void initWebServer() } else { LOG_ERROR("Web Servers Failed! ;-( \n"); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index b0b033ba0..88764d2be 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -10,7 +10,9 @@ #include #include #ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" +#endif #include #include static void WiFiEvent(WiFiEvent_t event); @@ -92,11 +94,10 @@ static void onNetworkConnected() syslog.enable(); } -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER initWebServer(); #endif initApiServer(); - APStartupComplete = true; } @@ -146,7 +147,6 @@ static int32_t reconnectWiFi() perhapsSetRTC(RTCQualityNTP, &tv); lastrun_ntp = millis(); - } else { LOG_DEBUG("NTP Update failed\n"); } @@ -204,7 +204,9 @@ bool initWifi() const char *wifiPsw = config.network.wifi_psk; #ifndef ARCH_RP2040 - createSSLCert(); // For WebServer +#if !MESHTASTIC_EXCLUDE_WEBSERVER + createSSLCert(); // For WebServer +#endif esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials #endif if (!*wifiPsw) // Treat empty password as no password @@ -405,4 +407,4 @@ static void WiFiEvent(WiFiEvent_t event) uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; -} \ No newline at end of file +} From dfcd0d14f610a6510622e903d19cc08122da9407 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 09:06:37 -0500 Subject: [PATCH 0144/1377] Add MaxNodes to Native config (#3427) * Add MaxNodes to Native * It compiles... * Convert nodedb to use new * Closer but still broken. * Finally working * Remove unintended lines * Don't include a pointer * Capitalization matters. * avoid rename in protocol regen * When trimming the nodeDB, start with a cleanup * Remove extra cleanupMeshDB() call for now --------- Co-authored-by: Ben Meadors --- bin/config-dist.yaml | 3 + bin/regen-protos.bat | 2 +- bin/regen-protos.sh | 3 +- src/GPSStatus.h | 25 +--- src/gps/GPS.cpp | 4 +- src/graphics/Screen.cpp | 30 +++-- src/graphics/Screen.h | 2 + src/main.cpp | 8 +- src/mesh/MeshModule.cpp | 4 +- src/mesh/MeshService.cpp | 24 ++-- src/mesh/NodeDB.cpp | 125 +++++++++++------- src/mesh/NodeDB.h | 15 ++- src/mesh/PacketHistory.cpp | 4 + src/mesh/PhoneAPI.cpp | 2 +- src/mesh/ProtobufModule.h | 2 +- src/mesh/RadioInterface.cpp | 4 +- src/mesh/ReliableRouter.cpp | 2 +- src/mesh/Router.cpp | 20 +-- src/mesh/SX128xInterface.cpp | 10 +- .../meshtastic/{admin.pb.c => admin.pb.cpp} | 0 .../{apponly.pb.c => apponly.pb.cpp} | 0 .../meshtastic/{atak.pb.c => atak.pb.cpp} | 0 ...nedmessages.pb.c => cannedmessages.pb.cpp} | 0 .../{channel.pb.c => channel.pb.cpp} | 0 .../{clientonly.pb.c => clientonly.pb.cpp} | 0 .../meshtastic/{config.pb.c => config.pb.cpp} | 0 ...n_status.pb.c => connection_status.pb.cpp} | 0 .../{deviceonly.pb.c => deviceonly.pb.cpp} | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 15 ++- .../{localonly.pb.c => localonly.pb.cpp} | 0 .../meshtastic/{mesh.pb.c => mesh.pb.cpp} | 0 ...odule_config.pb.c => module_config.pb.cpp} | 0 .../meshtastic/{mqtt.pb.c => mqtt.pb.cpp} | 0 .../{paxcount.pb.c => paxcount.pb.cpp} | 0 .../{portnums.pb.c => portnums.pb.cpp} | 0 ...e_hardware.pb.c => remote_hardware.pb.cpp} | 0 .../meshtastic/{rtttl.pb.c => rtttl.pb.cpp} | 0 ...{storeforward.pb.c => storeforward.pb.cpp} | 0 .../{telemetry.pb.c => telemetry.pb.cpp} | 0 .../meshtastic/{xmodem.pb.c => xmodem.pb.cpp} | 0 src/mesh/mesh-pb-constants.h | 7 +- src/modules/AdminModule.cpp | 12 +- src/modules/CannedMessageModule.cpp | 32 ++--- src/modules/ExternalNotificationModule.cpp | 8 +- src/modules/NeighborInfoModule.cpp | 26 ++-- src/modules/NodeInfoModule.cpp | 4 +- src/modules/PositionModule.cpp | 18 +-- src/modules/RangeTestModule.cpp | 8 +- src/modules/RoutingModule.cpp | 4 +- src/modules/SerialModule.cpp | 12 +- src/modules/Telemetry/DeviceTelemetry.cpp | 4 +- src/modules/esp32/AudioModule.cpp | 3 +- src/modules/esp32/StoreForwardModule.cpp | 4 +- src/mqtt/MQTT.cpp | 10 +- src/platform/portduino/PortduinoGlue.cpp | 2 + src/platform/portduino/PortduinoGlue.h | 3 +- src/shutdown.h | 2 + src/sleep.cpp | 2 +- 58 files changed, 258 insertions(+), 209 deletions(-) rename src/mesh/generated/meshtastic/{admin.pb.c => admin.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{apponly.pb.c => apponly.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{atak.pb.c => atak.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{cannedmessages.pb.c => cannedmessages.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{channel.pb.c => channel.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{clientonly.pb.c => clientonly.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{config.pb.c => config.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{connection_status.pb.c => connection_status.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{deviceonly.pb.c => deviceonly.pb.cpp} (90%) rename src/mesh/generated/meshtastic/{localonly.pb.c => localonly.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{mesh.pb.c => mesh.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{module_config.pb.c => module_config.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{mqtt.pb.c => mqtt.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{paxcount.pb.c => paxcount.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{portnums.pb.c => portnums.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{remote_hardware.pb.c => remote_hardware.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{rtttl.pb.c => rtttl.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{storeforward.pb.c => storeforward.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{telemetry.pb.c => telemetry.pb.cpp} (100%) rename src/mesh/generated/meshtastic/{xmodem.pb.c => xmodem.pb.cpp} (100%) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index a241a929a..22ca3e7db 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -121,3 +121,6 @@ Logging: Webserver: # Port: 443 # Port for Webserver & Webservices # RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer + +General: + MaxNodes: 200 diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat index 1422f7914..1d55c2506 100644 --- a/bin/regen-protos.bat +++ b/bin/regen-protos.bat @@ -1 +1 @@ -cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --experimental_allow_proto3_optional --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs ..\protobufs\meshtastic\*.proto +cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated\" -I=..\protobufs ..\protobufs\meshtastic\*.proto diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index ad771ab45..7c751208a 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -8,9 +8,8 @@ echo "prebuilt binaries for your computer into nanopb-0.4.7" # the nanopb tool seems to require that the .options file be in the current directory! cd protobufs -../nanopb-0.4.7/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated/ -I=../protobufs meshtastic/*.proto --experimental_allow_proto3_optional +../nanopb-0.4.7/generator-bin/protoc "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto --experimental_allow_proto3_optional -# cd ../src/mesh/generated/meshtastic # sed -i 's/#include "meshtastic/#include "./g' -- * # sed -i 's/meshtastic_//g' -- * diff --git a/src/GPSStatus.h b/src/GPSStatus.h index bcfb5f2eb..1245d5e5d 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -4,8 +4,6 @@ #include "configuration.h" #include -extern NodeDB nodeDB; - namespace meshtastic { @@ -55,7 +53,7 @@ class GPSStatus : public Status #ifdef GPS_EXTRAVERBOSE LOG_WARN("Using fixed latitude\n"); #endif - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); return node->position.latitude_i; } else { return p.latitude_i; @@ -68,7 +66,7 @@ class GPSStatus : public Status #ifdef GPS_EXTRAVERBOSE LOG_WARN("Using fixed longitude\n"); #endif - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); return node->position.longitude_i; } else { return p.longitude_i; @@ -81,27 +79,18 @@ class GPSStatus : public Status #ifdef GPS_EXTRAVERBOSE LOG_WARN("Using fixed altitude\n"); #endif - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); return node->position.altitude; } else { return p.altitude; } } - uint32_t getDOP() const - { - return p.PDOP; - } + uint32_t getDOP() const { return p.PDOP; } - uint32_t getHeading() const - { - return p.ground_track; - } + uint32_t getHeading() const { return p.ground_track; } - uint32_t getNumSatellites() const - { - return p.sats_in_view; - } + uint32_t getNumSatellites() const { return p.sats_in_view; } bool matches(const GPSStatus *newStatus) const { @@ -149,4 +138,4 @@ class GPSStatus : public Status } // namespace meshtastic -extern meshtastic::GPSStatus *gpsStatus; \ No newline at end of file +extern meshtastic::GPSStatus *gpsStatus; diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 506f6d89c..7d4f41a55 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -815,7 +815,7 @@ int32_t GPS::runOnce() LOG_WARN("GPS FactoryReset requested\n"); if (gps->factoryReset()) { // If we don't succeed try again next time devicestate.did_gps_reset = true; - nodeDB.saveToDisk(SEGMENT_DEVICESTATE); + nodeDB->saveToDisk(SEGMENT_DEVICESTATE); } } GPSInitFinished = true; @@ -835,7 +835,7 @@ int32_t GPS::runOnce() if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); devicestate.did_gps_reset = false; - nodeDB.saveDeviceStateToDisk(); + nodeDB->saveDeviceStateToDisk(); return disable(); // Stop the GPS thread as it can do nothing useful until next reboot. } } diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index cfd8494d2..3c3777496 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -71,7 +71,7 @@ namespace graphics // #define SHOW_REDRAWS // A text message frame + debug frame + all the node infos -static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; +FrameCallback *normalFrames; static uint32_t targetFramerate = IDLE_FRAMERATE; static char btPIN[16] = "888888"; @@ -354,7 +354,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state static char tempBuf[237]; const meshtastic_MeshPacket &mp = devicestate.rx_text_message; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); // LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from, // mp.decoded.variant.data.decoded.bytes); @@ -392,7 +392,7 @@ static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, i static char tempBuf[237]; meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); @@ -780,16 +780,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (state->currentFrame != prevFrame) { prevFrame = state->currentFrame; - nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); - meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex); - if (n->num == nodeDB.getNodeNum()) { + nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes(); + meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(nodeIndex); + if (n->num == nodeDB->getNodeNum()) { // Don't show our node, just skip to next - nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes(); - n = nodeDB.getMeshNodeByIndex(nodeIndex); + nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes(); + n = nodeDB->getMeshNodeByIndex(nodeIndex); } } - meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(nodeIndex); display->setFont(FONT_SMALL); @@ -827,7 +827,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ } else { strncpy(distStr, "? km", sizeof(distStr)); } - meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; int16_t compassX = 0, compassY = 0; @@ -893,6 +893,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { + graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) dispdev = new SH1106Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -931,6 +932,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O cmdQueue.setReader(this); } +Screen::~Screen() +{ + delete[] graphics::normalFrames; +} + /** * Prepare the display for the unit going to the lowest power mode possible. Most screens will just * poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code @@ -1287,7 +1293,7 @@ void Screen::setFrames() #endif // We don't show the node info our our node (if we have it yet - we should) - size_t numMeshNodes = nodeDB.getNumMeshNodes(); + size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (numMeshNodes > 0) numMeshNodes--; @@ -1792,7 +1798,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { setFrames(); // Regen the list of screens } - nodeDB.updateGUI = false; + nodeDB->updateGUI = false; break; } diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 69e858dd2..a66cc44ec 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -125,6 +125,8 @@ class Screen : public concurrency::OSThread public: explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); + ~Screen(); + Screen(const Screen &) = delete; Screen &operator=(const Screen &) = delete; diff --git a/src/main.cpp b/src/main.cpp index 4fc713b1c..e09af0c9a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -556,7 +556,7 @@ void setup() // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu init (esp32setup), because we need the random seed set - nodeDB.init(); + nodeDB = NodeDB::init(); // If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { @@ -650,7 +650,7 @@ void setup() } else { LOG_DEBUG("Running without GPS.\n"); } - nodeStatus->observe(&nodeDB.newStatus); + nodeStatus->observe(&nodeDB->newStatus); #ifdef HAS_I2S LOG_DEBUG("Starting audio thread\n"); @@ -844,7 +844,7 @@ void setup() if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) { LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; - nodeDB.saveToDisk(SEGMENT_CONFIG); + nodeDB->saveToDisk(SEGMENT_CONFIG); if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting\n"); screen->startRebootScreen(); @@ -967,4 +967,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index ad0c78108..c8dd7f3d1 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -81,7 +81,7 @@ void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src) bool ignoreRequest = false; // No module asked to ignore the request yet // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets - auto ourNodeNum = nodeDB.getNodeNum(); + auto ourNodeNum = nodeDB->getNodeNum(); bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; for (auto i = modules->begin(); i != modules->end(); ++i) { @@ -279,4 +279,4 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const mesht } } return handled; -} +} \ No newline at end of file diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index db0dd88ec..31eb082ec 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -71,7 +71,7 @@ MeshService::MeshService() void MeshService::init() { // moved much earlier in boot (called from setup()) - // nodeDB.init(); + // nodeDB->init(); if (gps) gpsObserver.observe(&gps->newStatus); @@ -81,13 +81,13 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) { powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping - nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio + nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) { LOG_DEBUG( "Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our " "request for its NodeInfo.\n"); - } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB.getMeshNode(mp->from)->has_user && + } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && nodeInfoModule) { LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); @@ -120,10 +120,10 @@ bool MeshService::reloadConfig(int saveWhat) // If we can successfully set this radio to these settings, save them to disk // This will also update the region as needed - bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings + bool didReset = nodeDB->resetRadioConfig(); // Don't let the phone send us fatally bad settings configChanged.notifyObservers(NULL); // This will cause radio hardware to change freqs etc - nodeDB.saveToDisk(saveWhat); + nodeDB->saveToDisk(saveWhat); return didReset; } @@ -133,7 +133,7 @@ void MeshService::reloadOwner(bool shouldSave) { // LOG_DEBUG("reloadOwner()\n"); // update our local data directly - nodeDB.updateUser(nodeDB.getNodeNum(), owner); + nodeDB->updateUser(nodeDB->getNodeNum(), owner); assert(nodeInfoModule); // update everyone else and save to disk if (nodeInfoModule && shouldSave) { @@ -192,7 +192,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) LOG_WARN("phone tried to pick a nodenum, we don't allow that.\n"); p.from = 0; } else { - // p.from = nodeDB.getNodeNum(); + // p.from = nodeDB->getNodeNum(); } if (p.id == 0) @@ -217,7 +217,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) /** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */ bool MeshService::cancelSending(PacketId id) { - return router->cancelSending(nodeDB.getNodeNum(), id); + return router->cancelSending(nodeDB->getNodeNum(), id); } ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id) @@ -245,7 +245,7 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPhone) { uint32_t mesh_packet_id = p->id; - nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...) + nodeDB->updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...) // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it ErrorCode res = router->sendLocal(p, src); @@ -265,7 +265,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); assert(node); @@ -320,7 +320,7 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); assert(node); // We might not have a position yet for our local node, in that case, at least try to send the time @@ -373,7 +373,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) pos.longitude_i, pos.altitude); // Update our current position in the local DB - nodeDB.updatePosition(nodeDB.getNodeNum(), pos, RX_SRC_LOCAL); + nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL); return 0; } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 25d010f16..37232e6ed 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -18,8 +18,11 @@ #include "mesh-pb-constants.h" #include "modules/NeighborInfoModule.h" #include +#include +#include #include #include +#include #ifdef ARCH_ESP32 #include "mesh/wifi/WiFiAPClient.h" @@ -37,7 +40,7 @@ #include #endif -NodeDB nodeDB; +NodeDB *nodeDB = nullptr; // we have plenty of ram so statically alloc this tempbuf (for now) EXT_RAM_ATTR meshtastic_DeviceState devicestate; @@ -47,6 +50,26 @@ meshtastic_LocalModuleConfig moduleConfig; meshtastic_ChannelFile channelFile; meshtastic_OEMStore oemStore; +bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field) +{ + if (ostream) { + std::vector *vec = (std::vector *)field->pData; + for (auto item : *vec) { + if (!pb_encode_tag_for_field(ostream, field)) + return false; + pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item); + } + } + if (istream) { + meshtastic_NodeInfoLite node; // this gets good data + std::vector *vec = (std::vector *)field->pData; + + if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node)) + vec->push_back(node); + } + return true; +} + /** The current change # for radio settings. Starts at 0 on boot and any time the radio settings * might have changed is incremented. Allows others to detect they might now be on a new channel. */ @@ -69,7 +92,7 @@ uint32_t error_address = 0; static uint8_t ourMacAddr[6]; -NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestate.node_db_lite_count) {} +NodeDB::NodeDB() {} /** * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on @@ -77,7 +100,7 @@ NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestat */ NodeNum getFrom(const meshtastic_MeshPacket *p) { - return (p->from == 0) ? nodeDB.getNodeNum() : p->from; + return (p->from == 0) ? nodeDB->getNodeNum() : p->from; } bool NodeDB::resetRadioConfig(bool factory_reset) @@ -353,8 +376,8 @@ void NodeDB::installDefaultChannels() void NodeDB::resetNodes() { - devicestate.node_db_lite_count = 1; - std::fill(&devicestate.node_db_lite[1], &devicestate.node_db_lite[MAX_NUM_NODES - 1], meshtastic_NodeInfoLite()); + numMeshNodes = 1; + std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite()); saveDeviceStateToDisk(); if (neighborInfoModule && moduleConfig.neighbor_info.enabled) neighborInfoModule->resetNeighbors(); @@ -363,13 +386,15 @@ void NodeDB::resetNodes() void NodeDB::removeNodeByNum(uint nodeNum) { int newPos = 0, removed = 0; - for (int i = 0; i < *numMeshNodes; i++) { - if (meshNodes[i].num != nodeNum) - meshNodes[newPos++] = meshNodes[i]; + for (int i = 0; i < numMeshNodes; i++) { + if (meshNodes->at(i).num != nodeNum) + meshNodes->at(newPos++) = meshNodes->at(i); else removed++; } - *numMeshNodes -= removed; + numMeshNodes -= removed; + std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + 1, + meshtastic_NodeInfoLite()); LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Saving changes...\n", removed); saveDeviceStateToDisk(); } @@ -377,27 +402,30 @@ void NodeDB::removeNodeByNum(uint nodeNum) void NodeDB::cleanupMeshDB() { int newPos = 0, removed = 0; - for (int i = 0; i < *numMeshNodes; i++) { - if (meshNodes[i].has_user) - meshNodes[newPos++] = meshNodes[i]; + for (int i = 0; i < numMeshNodes; i++) { + if (meshNodes->at(i).has_user) + meshNodes->at(newPos++) = meshNodes->at(i); else removed++; } - *numMeshNodes -= removed; + numMeshNodes -= removed; + std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed, + meshtastic_NodeInfoLite()); LOG_DEBUG("cleanupMeshDB purged %d entries\n", removed); } void NodeDB::installDefaultDeviceState() { LOG_INFO("Installing default DeviceState\n"); - memset(&devicestate, 0, sizeof(meshtastic_DeviceState)); + // memset(&devicestate, 0, sizeof(meshtastic_DeviceState)); - *numMeshNodes = 0; + numMeshNodes = 0; + meshNodes = &devicestate.node_db_lite; // init our devicestate with valid flags so protobuf writing/reading will work devicestate.has_my_node = true; devicestate.has_owner = true; - devicestate.node_db_lite_count = 0; + // devicestate.node_db_lite_count = 0; devicestate.version = DEVICESTATE_CUR_VER; devicestate.receive_queue_count = 0; // Not yet implemented FIXME @@ -411,11 +439,12 @@ void NodeDB::installDefaultDeviceState() memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); } -void NodeDB::init() +NodeDB *NodeDB::init() { LOG_INFO("Initializing NodeDB\n"); - loadFromDisk(); - cleanupMeshDB(); + NodeDB *newnodeDB = new NodeDB; + newnodeDB->loadFromDisk(); + newnodeDB->cleanupMeshDB(); uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate)); uint32_t configCRC = crc32Buffer(&config, sizeof(config)); @@ -427,7 +456,7 @@ void NodeDB::init() myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) - pickNewNodeNum(); + newnodeDB->pickNewNodeNum(); // Set our board type so we can share it with others owner.hw_model = HW_VENDOR; @@ -435,7 +464,7 @@ void NodeDB::init() owner.role = config.device.role; // Include our owner in the node db under our nodenum - meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); + meshtastic_NodeInfoLite *info = newnodeDB->getOrCreateMeshNode(newnodeDB->getNodeNum()); info->user = owner; info->has_user = true; @@ -447,8 +476,8 @@ void NodeDB::init() LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count); #endif - resetRadioConfig(); // If bogus settings got saved, then fix them - LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, *numMeshNodes); + newnodeDB->resetRadioConfig(); // If bogus settings got saved, then fix them + // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) saveWhat |= SEGMENT_DEVICESTATE; @@ -466,8 +495,9 @@ void NodeDB::init() config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_enabled = 0; } + return newnodeDB; - saveToDisk(saveWhat); + nodeDB->saveToDisk(saveWhat); } // We reserve a few nodenums for future use @@ -537,17 +567,21 @@ bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, c void NodeDB::loadFromDisk() { // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM - if (!loadProto(prefFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, - &devicestate)) { + if (!loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), + sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate)) { installDefaultDeviceState(); // Our in RAM copy might now be corrupt } else { if (devicestate.version < DEVICESTATE_MIN_VER) { LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version); factoryReset(); } else { - LOG_INFO("Loaded saved devicestate version %d\n", devicestate.version); + LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version, + devicestate.node_db_lite.size()); + meshNodes = &devicestate.node_db_lite; + numMeshNodes = devicestate.node_db_lite.size(); } } + meshNodes->resize(MAX_NUM_NODES); if (!loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, &config)) { @@ -626,7 +660,7 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ if (failedCounter >= 2) { FSCom.format(); // After formatting, the device needs to be restarted - nodeDB.resetRadioConfig(true); + nodeDB->resetRadioConfig(true); } #endif } @@ -649,7 +683,8 @@ void NodeDB::saveDeviceStateToDisk() #ifdef FSCom FSCom.mkdir("/prefs"); #endif - saveProto(prefFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate); + saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg, + &devicestate); } void NodeDB::saveToDisk(int saveWhat) @@ -690,8 +725,8 @@ void NodeDB::saveToDisk(int saveWhat) const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) { - if (readIndex < *numMeshNodes) - return &meshNodes[readIndex++]; + if (readIndex < numMeshNodes) + return &meshNodes->at(readIndex++); else return NULL; } @@ -726,10 +761,10 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly) size_t numseen = 0; // FIXME this implementation is kinda expensive - for (int i = 0; i < *numMeshNodes; i++) { - if (localOnly && meshNodes[i].via_mqtt) + for (int i = 0; i < numMeshNodes; i++) { + if (localOnly && meshNodes->at(i).via_mqtt) continue; - if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS) + if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS) numseen++; } @@ -877,9 +912,9 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n) /// NOTE: This function might be called from an ISR meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n) { - for (int i = 0; i < *numMeshNodes; i++) - if (meshNodes[i].num == n) - return &meshNodes[i]; + for (int i = 0; i < numMeshNodes; i++) + if (meshNodes->at(i).num == n) + return &meshNodes->at(i); return NULL; } @@ -890,27 +925,27 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) meshtastic_NodeInfoLite *lite = getMeshNode(n); if (!lite) { - if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { + if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { if (screen) screen->print("Warn: node database full!\nErasing oldest entry\n"); LOG_INFO("Warn: node database full!\nErasing oldest entry\n"); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; - for (int i = 1; i < *numMeshNodes; i++) { - if (meshNodes[i].last_heard < oldest) { - oldest = meshNodes[i].last_heard; + for (int i = 1; i < numMeshNodes; i++) { + if (meshNodes->at(i).last_heard < oldest) { + oldest = meshNodes->at(i).last_heard; oldestIndex = i; } } // Shove the remaining nodes down the chain - for (int i = oldestIndex; i < *numMeshNodes - 1; i++) { - meshNodes[i] = meshNodes[i + 1]; + for (int i = oldestIndex; i < numMeshNodes - 1; i++) { + meshNodes->at(i) = meshNodes->at(i + 1); } - (*numMeshNodes)--; + (numMeshNodes)--; } // add the node at the end - lite = &meshNodes[(*numMeshNodes)++]; + lite = &meshNodes->at((numMeshNodes)++); // everything is missing except the nodenum memset(lite, 0, sizeof(*lite)); diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 1d2086adb..ea2019c37 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -3,6 +3,7 @@ #include "Observer.h" #include #include +#include #include "MeshTypes.h" #include "NodeStatus.h" @@ -45,20 +46,20 @@ class NodeDB // Eventually use a smarter datastructure // HashMap nodes; // Note: these two references just point into our static array we serialize to/from disk - meshtastic_NodeInfoLite *meshNodes; - pb_size_t *numMeshNodes; public: + std::vector *meshNodes; bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI Observable newStatus; + pb_size_t numMeshNodes; /// don't do mesh based algorithm for node id assignment (initially) /// instead just store in flash - possibly even in the initial alpha release do this hack NodeDB(); /// Called from service after app start, to do init which can only be done after OS load - void init(); + static NodeDB *init(); /// write to flash void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS), @@ -126,12 +127,12 @@ class NodeDB meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x) { - assert(x < *numMeshNodes); - return &meshNodes[x]; + assert(x < numMeshNodes); + return &meshNodes->at(x); } meshtastic_NodeInfoLite *getMeshNode(NodeNum n); - size_t getNumMeshNodes() { return *numMeshNodes; } + size_t getNumMeshNodes() { return numMeshNodes; } void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { @@ -167,7 +168,7 @@ class NodeDB void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig(); }; -extern NodeDB nodeDB; +extern NodeDB *nodeDB; /* If is_router is set, we use a number of different default values diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 9ecad47cd..26a73a3fe 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -2,6 +2,10 @@ #include "configuration.h" #include "mesh-pb-constants.h" +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + PacketHistory::PacketHistory() { recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index d8e842149..48f7eb940 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -421,7 +421,7 @@ bool PhoneAPI::available() case STATE_SEND_NODEINFO: if (nodeInfoForPhone.num == 0) { - auto nextNode = nodeDB.readNextMeshNode(readIndex); + auto nextNode = nodeDB->readNextMeshNode(readIndex); if (nextNode) { nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode); } diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index d87bb47c3..1067ee01e 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -56,7 +56,7 @@ template class ProtobufModule : protected SinglePortModule */ const char *getSenderShortName(const meshtastic_MeshPacket &mp) { - auto node = nodeDB.getMeshNode(getFrom(&mp)); + auto node = nodeDB->getMeshNode(getFrom(&mp)); const char *sender = (node) ? node->user.short_name : "???"; return sender; } diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 7a2711251..859e7bea4 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -334,8 +334,8 @@ bool RadioInterface::init() notifyDeepSleepObserver.observe(¬ifyDeepSleep); // we now expect interfaces to operate in promiscuous mode - // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor - // time. + // radioIf.setThisAddress(nodeDB->getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at + // constructor time. applyModemConfig(); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 2327cbfb7..d3246b48d 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) * Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and * flooding this ACK back to the original sender already adds redundancy. */ bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit); - if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) { + if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB->getNodeNum()) { LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n"); meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); tosend->hop_limit--; // bump down the hop count diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 7c739b8f2..7894b1b92 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -119,7 +119,7 @@ meshtastic_MeshPacket *Router::allocForSending() meshtastic_MeshPacket *p = packetPool.allocZeroed(); p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // Assume payload is decoded at start. - p->from = nodeDB.getNodeNum(); + p->from = nodeDB->getNodeNum(); p->to = NODENUM_BROADCAST; p->hop_limit = (config.lora.hop_limit >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit; p->id = generatePacketId(); @@ -165,7 +165,7 @@ meshtastic_QueueStatus Router::getQueueStatus() ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) { // No need to deliver externally if the destination is the local node - if (p->to == nodeDB.getNodeNum()) { + if (p->to == nodeDB->getNodeNum()) { printPacket("Enqueued local", p); enqueueReceivedMessage(p); return ERRNO_OK; @@ -182,7 +182,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } if (!p->channel) { // don't override if a channel was requested - p->channel = nodeDB.getMeshNodeChannel(p->to); + p->channel = nodeDB->getMeshNodeChannel(p->to); LOG_DEBUG("localSend to channel %d\n", p->channel); } @@ -205,7 +205,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes) */ ErrorCode Router::send(meshtastic_MeshPacket *p) { - if (p->to == nodeDB.getNodeNum()) { + if (p->to == nodeDB->getNodeNum()) { LOG_ERROR("BUG! send() called with packet destined for local node!\n"); packetPool.release(p); return meshtastic_Routing_Error_BAD_REQUEST; @@ -220,7 +220,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes); #endif meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; - if (getFrom(p) == nodeDB.getNodeNum()) { // only send NAK to API, not to the mesh + if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh abortSendAndNak(err, p); } else { packetPool.release(p); @@ -263,7 +263,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) } // Only publish to MQTT if we're the original transmitter of the packet - if (moduleConfig.mqtt.enabled && p->from == nodeDB.getNodeNum() && mqtt) { + if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) { mqtt->onSend(*p, *p_decoded, chIndex); } packetPool.release(p_decoded); @@ -297,8 +297,8 @@ bool perhapsDecode(meshtastic_MeshPacket *p) return false; if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY && - (nodeDB.getMeshNode(p->from) == NULL || !nodeDB.getMeshNode(p->from)->has_user)) { - LOG_DEBUG("Node 0x%x not in NodeDB. Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from); + (nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) { + LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from); return false; } @@ -431,7 +431,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) NodeNum Router::getNodeNum() { - return nodeDB.getNodeNum(); + return nodeDB->getNodeNum(); } /** @@ -467,7 +467,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) } // Publish received message to MQTT if we're not the original transmitter of the packet - if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt) + if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); } else { printPacket("packet decoding failed or skipped (no PSK?)", p); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index d0103ec29..f2220dbcf 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -71,7 +71,7 @@ template bool SX128xInterface::init() if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) { LOG_WARN("Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.\n"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24; - nodeDB.saveToDisk(SEGMENT_CONFIG); + nodeDB->saveToDisk(SEGMENT_CONFIG); delay(2000); #if defined(ARCH_ESP32) ESP.restart(); @@ -251,9 +251,9 @@ template void SX128xInterface::startReceive() #endif // We use the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving - int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | - RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED | - RADIOLIB_SX128X_IRQ_HEADER_VALID); + int err = + lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED | + RADIOLIB_SX128X_IRQ_HEADER_VALID); assert(err == RADIOLIB_ERR_NONE); @@ -327,4 +327,4 @@ template bool SX128xInterface::sleep() #endif return true; -} +} \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/admin.pb.c b/src/mesh/generated/meshtastic/admin.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/admin.pb.c rename to src/mesh/generated/meshtastic/admin.pb.cpp diff --git a/src/mesh/generated/meshtastic/apponly.pb.c b/src/mesh/generated/meshtastic/apponly.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/apponly.pb.c rename to src/mesh/generated/meshtastic/apponly.pb.cpp diff --git a/src/mesh/generated/meshtastic/atak.pb.c b/src/mesh/generated/meshtastic/atak.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/atak.pb.c rename to src/mesh/generated/meshtastic/atak.pb.cpp diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.c b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/cannedmessages.pb.c rename to src/mesh/generated/meshtastic/cannedmessages.pb.cpp diff --git a/src/mesh/generated/meshtastic/channel.pb.c b/src/mesh/generated/meshtastic/channel.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/channel.pb.c rename to src/mesh/generated/meshtastic/channel.pb.cpp diff --git a/src/mesh/generated/meshtastic/clientonly.pb.c b/src/mesh/generated/meshtastic/clientonly.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/clientonly.pb.c rename to src/mesh/generated/meshtastic/clientonly.pb.cpp diff --git a/src/mesh/generated/meshtastic/config.pb.c b/src/mesh/generated/meshtastic/config.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/config.pb.c rename to src/mesh/generated/meshtastic/config.pb.cpp diff --git a/src/mesh/generated/meshtastic/connection_status.pb.c b/src/mesh/generated/meshtastic/connection_status.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/connection_status.pb.c rename to src/mesh/generated/meshtastic/connection_status.pb.cpp diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.c b/src/mesh/generated/meshtastic/deviceonly.pb.cpp similarity index 90% rename from src/mesh/generated/meshtastic/deviceonly.pb.c rename to src/mesh/generated/meshtastic/deviceonly.pb.cpp index 82c3fc44c..93ab3dd98 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.c +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -6,7 +6,7 @@ #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 4) +PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2) PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index d6a2a0272..c286bd471 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -4,6 +4,7 @@ #ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED #include +#include #include "meshtastic/channel.pb.h" #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" @@ -155,8 +156,7 @@ typedef struct _meshtastic_DeviceState { pb_size_t node_remote_hardware_pins_count; meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; /* New lite version of NodeDB to decrease memory footprint */ - pb_size_t node_db_lite_count; - meshtastic_NodeInfoLite node_db_lite[100]; + std::vector node_db_lite; } meshtastic_DeviceState; @@ -179,13 +179,13 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, 0, {meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default}} +#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {{NULL}, NULL}} #define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} -#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, 0, {meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero}} +#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {{NULL}, NULL}} #define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} @@ -241,8 +241,9 @@ X(a, STATIC, SINGULAR, BOOL, no_save, 9) \ X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, rx_waypoint, 12) \ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13) \ -X(a, STATIC, REPEATED, MESSAGE, node_db_lite, 14) -#define meshtastic_DeviceState_CALLBACK NULL +X(a, CALLBACK, REPEATED, MESSAGE, node_db_lite, 14) +extern bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); +#define meshtastic_DeviceState_CALLBACK meshtastic_DeviceState_callback #define meshtastic_DeviceState_DEFAULT NULL #define meshtastic_DeviceState_my_node_MSGTYPE meshtastic_MyNodeInfo #define meshtastic_DeviceState_owner_MSGTYPE meshtastic_User @@ -321,8 +322,8 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ +/* meshtastic_DeviceState_size depends on runtime parameters */ #define meshtastic_ChannelFile_size 702 -#define meshtastic_DeviceState_size 17571 #define meshtastic_NodeInfoLite_size 158 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_OEMStore_size 3278 diff --git a/src/mesh/generated/meshtastic/localonly.pb.c b/src/mesh/generated/meshtastic/localonly.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/localonly.pb.c rename to src/mesh/generated/meshtastic/localonly.pb.cpp diff --git a/src/mesh/generated/meshtastic/mesh.pb.c b/src/mesh/generated/meshtastic/mesh.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/mesh.pb.c rename to src/mesh/generated/meshtastic/mesh.pb.cpp diff --git a/src/mesh/generated/meshtastic/module_config.pb.c b/src/mesh/generated/meshtastic/module_config.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/module_config.pb.c rename to src/mesh/generated/meshtastic/module_config.pb.cpp diff --git a/src/mesh/generated/meshtastic/mqtt.pb.c b/src/mesh/generated/meshtastic/mqtt.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/mqtt.pb.c rename to src/mesh/generated/meshtastic/mqtt.pb.cpp diff --git a/src/mesh/generated/meshtastic/paxcount.pb.c b/src/mesh/generated/meshtastic/paxcount.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/paxcount.pb.c rename to src/mesh/generated/meshtastic/paxcount.pb.cpp diff --git a/src/mesh/generated/meshtastic/portnums.pb.c b/src/mesh/generated/meshtastic/portnums.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/portnums.pb.c rename to src/mesh/generated/meshtastic/portnums.pb.cpp diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.c b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/remote_hardware.pb.c rename to src/mesh/generated/meshtastic/remote_hardware.pb.cpp diff --git a/src/mesh/generated/meshtastic/rtttl.pb.c b/src/mesh/generated/meshtastic/rtttl.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/rtttl.pb.c rename to src/mesh/generated/meshtastic/rtttl.pb.cpp diff --git a/src/mesh/generated/meshtastic/storeforward.pb.c b/src/mesh/generated/meshtastic/storeforward.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/storeforward.pb.c rename to src/mesh/generated/meshtastic/storeforward.pb.cpp diff --git a/src/mesh/generated/meshtastic/telemetry.pb.c b/src/mesh/generated/meshtastic/telemetry.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/telemetry.pb.c rename to src/mesh/generated/meshtastic/telemetry.pb.cpp diff --git a/src/mesh/generated/meshtastic/xmodem.pb.c b/src/mesh/generated/meshtastic/xmodem.pb.cpp similarity index 100% rename from src/mesh/generated/meshtastic/xmodem.pb.c rename to src/mesh/generated/meshtastic/xmodem.pb.cpp diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 22a80c8e3..9e747db1d 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -1,4 +1,5 @@ #pragma once +#include #include "mesh/generated/meshtastic/admin.pb.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" @@ -16,7 +17,11 @@ #define MAX_RX_TOPHONE 32 /// max number of nodes allowed in the mesh -#define MAX_NUM_NODES (member_size(meshtastic_DeviceState, node_db_lite) / member_size(meshtastic_DeviceState, node_db_lite[0])) +#if ARCH_PORTDUINO +#define MAX_NUM_NODES settingsMap[maxnodes] +#else +#define MAX_NUM_NODES 100 +#endif /// Max number of channels allowed #define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0])) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 6c4c80dbb..6d420ddb8 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -50,7 +50,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta // if handled == false, then let others look at this message also if they want bool handled = false; assert(r); - bool fromOthers = mp.from != 0 && mp.from != nodeDB.getNodeNum(); + bool fromOthers = mp.from != 0 && mp.from != nodeDB->getNodeNum(); switch (r->which_payload_variant) { @@ -150,13 +150,13 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_factory_reset_tag: { LOG_INFO("Initiating factory reset\n"); - nodeDB.factoryReset(); + nodeDB->factoryReset(); reboot(DEFAULT_REBOOT_SECONDS); break; } case meshtastic_AdminMessage_nodedb_reset_tag: { LOG_INFO("Initiating node-db reset\n"); - nodeDB.resetNodes(); + nodeDB->resetNodes(); reboot(DEFAULT_REBOOT_SECONDS); break; } @@ -186,7 +186,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_remove_by_nodenum_tag: { LOG_INFO("Client is receiving a remove_nodenum command.\n"); - nodeDB.removeNodeByNum(r->remove_by_nodenum); + nodeDB->removeNodeByNum(r->remove_by_nodenum); break; } case meshtastic_AdminMessage_enter_dfu_mode_request_tag: { @@ -302,7 +302,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.device = c.payload_variant.device; // If we're setting router role for the first time, install its intervals if (existingRole != c.payload_variant.device.role) - nodeDB.installRoleDefaults(c.payload_variant.device.role); + nodeDB->installRoleDefaults(c.payload_variant.device.role); if (config.device.node_info_broadcast_secs < min_node_info_broadcast_secs) { LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs); config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; @@ -608,7 +608,7 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r continue; } meshtastic_NodeRemoteHardwarePin nodePin = meshtastic_NodeRemoteHardwarePin_init_default; - nodePin.node_num = nodeDB.getNodeNum(); + nodePin.node_num = nodeDB->getNodeNum(); nodePin.pin = moduleConfig.remote_hardware.available_pins[i]; r.get_node_remote_hardware_pins_response.node_remote_hardware_pins[i + 12] = nodePin; } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index b2b52d1ab..3293e5d0d 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -311,18 +311,18 @@ int32_t CannedMessageModule::runOnce() switch (this->payload) { case 0xb4: // left if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { - size_t numMeshNodes = nodeDB.getNumMeshNodes(); + size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { - this->dest = nodeDB.getNodeNum(); + this->dest = nodeDB->getNodeNum(); } for (unsigned int i = 0; i < numMeshNodes; i++) { - if (nodeDB.getMeshNodeByIndex(i)->num == this->dest) { + if (nodeDB->getMeshNodeByIndex(i)->num == this->dest) { this->dest = - (i > 0) ? nodeDB.getMeshNodeByIndex(i - 1)->num : nodeDB.getMeshNodeByIndex(numMeshNodes - 1)->num; + (i > 0) ? nodeDB->getMeshNodeByIndex(i - 1)->num : nodeDB->getMeshNodeByIndex(numMeshNodes - 1)->num; break; } } - if (this->dest == nodeDB.getNodeNum()) { + if (this->dest == nodeDB->getNodeNum()) { this->dest = NODENUM_BROADCAST; } } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { @@ -346,18 +346,18 @@ int32_t CannedMessageModule::runOnce() break; case 0xb7: // right if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { - size_t numMeshNodes = nodeDB.getNumMeshNodes(); + size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { - this->dest = nodeDB.getNodeNum(); + this->dest = nodeDB->getNodeNum(); } for (unsigned int i = 0; i < numMeshNodes; i++) { - if (nodeDB.getMeshNodeByIndex(i)->num == this->dest) { + if (nodeDB->getMeshNodeByIndex(i)->num == this->dest) { this->dest = - (i < numMeshNodes - 1) ? nodeDB.getMeshNodeByIndex(i + 1)->num : nodeDB.getMeshNodeByIndex(0)->num; + (i < numMeshNodes - 1) ? nodeDB->getMeshNodeByIndex(i + 1)->num : nodeDB->getMeshNodeByIndex(0)->num; break; } } - if (this->dest == nodeDB.getNodeNum()) { + if (this->dest == nodeDB->getNodeNum()) { this->dest = NODENUM_BROADCAST; } } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { @@ -462,7 +462,7 @@ const char *CannedMessageModule::getNodeName(NodeNum node) if (node == NODENUM_BROADCAST) { return "Broadcast"; } else { - meshtastic_NodeInfoLite *info = nodeDB.getMeshNode(node); + meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node); if (info != NULL) { return info->user.long_name; } else { @@ -618,9 +618,9 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket & void CannedMessageModule::loadProtoForModule() { - if (!nodeDB.loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, - &cannedMessageModuleConfig)) { + if (!nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, + &cannedMessageModuleConfig)) { installDefaultCannedMessageModuleConfig(); } } @@ -639,8 +639,8 @@ bool CannedMessageModule::saveProtoForModule() FS.mkdir("/prefs"); #endif - okay &= nodeDB.saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); + okay &= nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); return okay; } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 652965f6d..2a4fdd0ae 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -284,8 +284,8 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.alert_message_buzzer = true; if (moduleConfig.external_notification.enabled) { - if (!nodeDB.loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), - &meshtastic_RTTTLConfig_msg, &rtttlConfig)) { + if (!nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), + &meshtastic_RTTTLConfig_msg, &rtttlConfig)) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); strncpy(rtttlConfig.ringtone, "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p", @@ -343,7 +343,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP drv.setWaveform(2, 0); drv.go(); #endif - if (getFrom(&mp) != nodeDB.getNodeNum()) { + if (getFrom(&mp) != nodeDB->getNodeNum()) { // Check if the message contains a bell character. Don't do this loop for every pin, just once. auto &p = mp.decoded; bool containsBell = false; @@ -506,6 +506,6 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) } if (changed) { - nodeDB.saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); + nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } } \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 024f321e6..4d68b4a16 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -17,7 +17,7 @@ NOTE: For debugging only void NeighborInfoModule::printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np) { LOG_DEBUG("%s NEIGHBORINFO PACKET from Node 0x%x to Node 0x%x (last sent by 0x%x)\n", header, np->node_id, - nodeDB.getNodeNum(), np->last_sent_by_id); + nodeDB->getNodeNum(), np->last_sent_by_id); LOG_DEBUG("----------------\n"); LOG_DEBUG("Packet contains %d neighbors\n", np->neighbors_count); for (int i = 0; i < np->neighbors_count; i++) { @@ -31,12 +31,12 @@ NOTE: for debugging only */ void NeighborInfoModule::printNodeDBNodes(const char *header) { - int num_nodes = nodeDB.getNumMeshNodes(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum()); + int num_nodes = nodeDB->getNumMeshNodes(); + LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB->getNodeNum()); LOG_DEBUG("----------------\n"); LOG_DEBUG("DB contains %d nodes\n", num_nodes); for (int i = 0; i < num_nodes; i++) { - const meshtastic_NodeInfoLite *dbEntry = nodeDB.getMeshNodeByIndex(i); + const meshtastic_NodeInfoLite *dbEntry = nodeDB->getMeshNodeByIndex(i); LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->num, dbEntry->snr); } LOG_DEBUG("----------------\n"); @@ -49,7 +49,7 @@ NOTE: for debugging only void NeighborInfoModule::printNodeDBNeighbors(const char *header) { int num_neighbors = getNumNeighbors(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum()); + LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB->getNodeNum()); LOG_DEBUG("----------------\n"); LOG_DEBUG("DB contains %d neighbors\n", num_neighbors); for (int i = 0; i < num_neighbors; i++) { @@ -67,7 +67,7 @@ NOTE: For debugging only void NeighborInfoModule::printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np) { int num_neighbors = getNumNeighbors(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum()); + LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB->getNodeNum()); LOG_DEBUG("----------------\n"); LOG_DEBUG("Selected %d neighbors of %d DB neighbors\n", np->neighbors_count, num_neighbors); for (int i = 0; i < num_neighbors; i++) { @@ -112,7 +112,7 @@ Assumes that the neighborInfo packet has been allocated */ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo) { - uint my_node_id = nodeDB.getNodeNum(); + uint my_node_id = nodeDB->getNodeNum(); neighborInfo->node_id = my_node_id; neighborInfo->last_sent_by_id = my_node_id; neighborInfo->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; @@ -143,7 +143,7 @@ size_t NeighborInfoModule::cleanUpNeighbors() { uint32_t now = getTime(); int num_neighbors = getNumNeighbors(); - NodeNum my_node_id = nodeDB.getNodeNum(); + NodeNum my_node_id = nodeDB->getNodeNum(); // Find neighbors to remove std::vector indices_to_remove; @@ -227,7 +227,7 @@ void NeighborInfoModule::updateLastSentById(meshtastic_MeshPacket *p) pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_NeighborInfo_msg, &scratch); updated = &scratch; - updated->last_sent_by_id = nodeDB.getNodeNum(); + updated->last_sent_by_id = nodeDB->getNodeNum(); // Set updated last_sent_by_id to the payload of the to be flooded packet p->decoded.payload.size = @@ -256,7 +256,7 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen { // our node and the phone are the same node (not neighbors) if (n == 0) { - n = nodeDB.getNodeNum(); + n = nodeDB->getNodeNum(); } // look for one in the existing list for (int i = 0; i < (*numNeighbors); i++) { @@ -292,8 +292,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen void NeighborInfoModule::loadProtoForModule() { - if (!nodeDB.loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), - &meshtastic_NeighborInfo_msg, &neighborState)) { + if (!nodeDB->loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), + &meshtastic_NeighborInfo_msg, &neighborState)) { neighborState = meshtastic_NeighborInfo_init_zero; } } @@ -312,7 +312,7 @@ bool NeighborInfoModule::saveProtoForModule() FS.mkdir("/prefs"); #endif - okay &= nodeDB.saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState); + okay &= nodeDB->saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState); return okay; } \ No newline at end of file diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 370847b94..f77026708 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -13,7 +13,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes { auto p = *pptr; - bool hasChanged = nodeDB.updateUser(getFrom(&mp), p, mp.channel); + bool hasChanged = nodeDB->updateUser(getFrom(&mp), p, mp.channel); bool wasBroadcast = mp.to == NODENUM_BROADCAST; @@ -25,7 +25,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } // if user has changed while packet was not for us, inform phone - if (hasChanged && !wasBroadcast && mp.to != nodeDB.getNodeNum()) + if (hasChanged && !wasBroadcast && mp.to != nodeDB->getNodeNum()) service.sendToPhone(packetPool.allocCopy(mp)); // LOG_DEBUG("did handleReceived\n"); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 853808f44..0bfc775da 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -41,12 +41,12 @@ PositionModule::PositionModule() void PositionModule::clearPosition() { LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n"); - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); node->position.latitude_i = 0; node->position.longitude_i = 0; node->position.altitude = 0; node->position.time = 0; - nodeDB.setLocalPosition(meshtastic_Position_init_default); + nodeDB->setLocalPosition(meshtastic_Position_init_default); } bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *pptr) @@ -59,15 +59,15 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // FIXME this can in fact happen with packets sent from EUD (src=RX_SRC_USER) // to set fixed location, EUD-GPS location or just the time (see also issue #900) bool isLocal = false; - if (nodeDB.getNodeNum() == getFrom(&mp)) { + if (nodeDB->getNodeNum() == getFrom(&mp)) { isLocal = true; if (config.position.fixed_position) { LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n"); - nodeDB.setLocalPosition(p, true); + nodeDB->setLocalPosition(p, true); return false; } else { LOG_DEBUG("Incoming update from MYSELF\n"); - nodeDB.setLocalPosition(p); + nodeDB->setLocalPosition(p); } } @@ -89,7 +89,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv); } - nodeDB.updatePosition(getFrom(&mp), p); + nodeDB->updatePosition(getFrom(&mp), p); if (channels.getByIndex(mp.channel).settings.has_module_settings) { precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; } else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { @@ -119,7 +119,7 @@ meshtastic_MeshPacket *PositionModule::allocReply() meshtastic_Position p = meshtastic_Position_init_default; // Start with an empty structure // if localPosition is totally empty, put our last saved position (lite) in there if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { - nodeDB.setLocalPosition(TypeConversions::ConvertToPosition(node->position)); + nodeDB->setLocalPosition(TypeConversions::ConvertToPosition(node->position)); } localPosition.seq_number++; @@ -286,7 +286,7 @@ int32_t PositionModule::runOnce() doDeepSleep(nightyNightMs, false); } - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); if (node == nullptr) return RUNONCE_INTERVAL; @@ -402,7 +402,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic void PositionModule::handleNewPosition() { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position // We limit our GPS broadcasts to a max rate uint32_t now = millis(); diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 904fb25db..a66a0513e 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -142,14 +142,14 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket LOG_INFO.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); */ - if (getFrom(&mp) != nodeDB.getNodeNum()) { + if (getFrom(&mp) != nodeDB->getNodeNum()) { if (moduleConfig.range_test.save) { appendFile(mp); } /* - NodeInfoLite *n = nodeDB.getMeshNode(getFrom(&mp)); + NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); LOG_DEBUG("-----------------------------------------\n"); LOG_DEBUG("p.payload.bytes \"%s\"\n", p.payload.bytes); @@ -188,7 +188,7 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #ifdef ARCH_ESP32 auto &p = mp.decoded; - meshtastic_NodeInfoLite *n = nodeDB.getMeshNode(getFrom(&mp)); + meshtastic_NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); /* LOG_DEBUG("-----------------------------------------\n"); LOG_DEBUG("p.payload.bytes \"%s\"\n", p.payload.bytes); @@ -295,4 +295,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} +} \ No newline at end of file diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index 37a7c3755..a52328ca4 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -14,7 +14,7 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh // FIXME - move this to a non promsicious PhoneAPI module? // Note: we are careful not to send back packets that started with the phone back to the phone - if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) && (mp.from != 0)) { + if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB->getNodeNum()) && (mp.from != 0)) { printPacket("Delivering rx packet", &mp); service.handleFromRadio(&mp); } @@ -63,4 +63,4 @@ RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_RO isPromiscuous = true; encryptedOk = config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY && config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY; -} +} \ No newline at end of file diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 1dee42a8d..663bc1d86 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -190,11 +190,11 @@ int32_t SerialModule::runOnce() if (millis() - lastNmeaTime > 10000) { lastNmeaTime = millis(); uint32_t readIndex = 0; - const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB.readNextMeshNode(readIndex); + const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex); while (tempNodeInfo != NULL && tempNodeInfo->has_user && hasValidPosition(tempNodeInfo)) { printWPL(outbuf, sizeof(outbuf), tempNodeInfo->position, tempNodeInfo->user.long_name, true); serialPrint->printf("%s", outbuf); - tempNodeInfo = nodeDB.readNextMeshNode(readIndex); + tempNodeInfo = nodeDB->readNextMeshNode(readIndex); } } } @@ -265,9 +265,9 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp auto &p = mp.decoded; // LOG_DEBUG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n", - // nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); + // nodeDB->getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); - if (getFrom(&mp) == nodeDB.getNodeNum()) { + if (getFrom(&mp) == nodeDB->getNodeNum()) { /* * If moduleConfig.serial.echo is true, then echo the packets that are sent out @@ -290,7 +290,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE) { serialPrint->write(p.payload.bytes, p.payload.size); } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); String sender = (node && node->has_user) ? node->user.short_name : "???"; serialPrint->println(); serialPrint->printf("%s: %s", sender, p.payload.bytes); @@ -306,7 +306,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp decoded = &scratch; } // send position packet as WPL to the serial port - printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB.getMeshNode(getFrom(&mp))->user.long_name, + printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB->getMeshNode(getFrom(&mp))->user.long_name, moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO); serialPrint->printf("%s", outbuf); } diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 3ed106d1c..7c02b57b4 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -45,7 +45,7 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & t->variant.device_metrics.air_util_tx, t->variant.device_metrics.channel_utilization, t->variant.device_metrics.battery_level, t->variant.device_metrics.voltage); #endif - nodeDB.updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO); + nodeDB->updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO); } return false; // Let others look at this message also if they want } @@ -94,7 +94,7 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) p->decoded.want_response = false; p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL); + nodeDB->updateTelemetry(nodeDB->getNodeNum(), telemetry, RX_SRC_LOCAL); if (phoneOnly) { LOG_INFO("Sending packet to phone\n"); service.sendToPhone(p); diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index a10cae954..4a7b1c2c6 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -1,4 +1,3 @@ - #include "configuration.h" #if defined(ARCH_ESP32) && defined(USE_SX1280) #include "AudioModule.h" @@ -274,7 +273,7 @@ ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp) { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; - if (getFrom(&mp) != nodeDB.getNodeNum()) { + if (getFrom(&mp) != nodeDB->getNodeNum()) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); radio_state = RadioState::rx; rx_encode_frame_index = p.payload.size; diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index 71d75750a..a60065e56 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -320,11 +320,11 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m if (moduleConfig.store_forward.enabled) { // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT - if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { + if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; - if (mp.to == nodeDB.getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && + if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) { LOG_DEBUG("*** Legacy Request to send\n"); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 7e341a18c..e29786dcb 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -135,7 +135,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node // receives it when we get our own packet back. Then we'll stop our retransmissions. - if (e.packet && getFrom(e.packet) == nodeDB.getNodeNum()) + if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); else LOG_INFO("Ignoring downlink message we originally sent.\n"); @@ -556,7 +556,7 @@ void MQTT::perhapsReportToMap() // Allocate MeshPacket and fill it meshtastic_MeshPacket *mp = packetPool.allocZeroed(); mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; - mp->from = nodeDB.getNodeNum(); + mp->from = nodeDB->getNodeNum(); mp->to = NODENUM_BROADCAST; mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; @@ -584,7 +584,7 @@ void MQTT::perhapsReportToMap() mapReport.altitude = localPosition.altitude; mapReport.position_precision = map_position_precision; - mapReport.num_online_local_nodes = nodeDB.getNumOnlineMeshNodes(true); + mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); // Encode MapReport message and set it to MeshPacket in ServiceEnvelope mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), @@ -794,7 +794,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) // Lambda function for adding a long name to the route auto addToRoute = [](JSONArray *route, NodeNum num) { char long_name[40] = "Unknown"; - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(num); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); bool name_known = node ? node->has_user : false; if (name_known) memcpy(long_name, node->user.long_name, sizeof(long_name)); @@ -900,7 +900,7 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) // if "sender" is provided, avoid processing packets we uplinked return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && (json.find("from") != json.end()) && json["from"]->IsNumber() && - (json["from"]->AsNumber() == nodeDB.getNodeNum()) && // only accept message if the "from" is us + (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type (json.find("payload") != json.end()); // should have a payload } \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 997058406..b255c0ce1 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -200,6 +200,8 @@ void portduinoSetup() settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as(""); } + settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as(200); + } catch (YAML::Exception e) { std::cout << "*** Exception " << e.what() << std::endl; exit(EXIT_FAILURE); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 3fe5f74bf..505c436d6 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -36,7 +36,8 @@ enum configNames { logoutputlevel, webserver, webserverport, - webserverrootpath + webserverrootpath, + maxnodes }; enum { no_screen, st7789, st7735, st7735s, ili9341 }; enum { no_touchscreen, xpt2046, stmpe610 }; diff --git a/src/shutdown.h b/src/shutdown.h index 6449b129e..21abba07e 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -26,6 +26,8 @@ void powerCommandsCheck() SPI.end(); Wire.end(); Serial1.end(); + if (screen) + delete screen; reboot(); #else rebootAtMsec = -1; diff --git a/src/sleep.cpp b/src/sleep.cpp index 6d8e4f3cc..f170e2ab7 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -198,7 +198,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) screen->doDeepSleep(); // datasheet says this will draw only 10ua - nodeDB.saveToDisk(); + nodeDB->saveToDisk(); #ifdef TTGO_T_ECHO #ifdef PIN_POWER_EN From fd26914d88be7fe8d5c00651659005b6049e0daa Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 13:14:02 -0500 Subject: [PATCH 0145/1377] move nodeDB::init code into nodeDB constructor (#3455) --- src/main.cpp | 2 +- src/mesh/NodeDB.cpp | 119 +++++++++++++++++++++----------------------- src/mesh/NodeDB.h | 3 -- 3 files changed, 58 insertions(+), 66 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index e09af0c9a..5f746f12a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -556,7 +556,7 @@ void setup() // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu init (esp32setup), because we need the random seed set - nodeDB = NodeDB::init(); + nodeDB = new NodeDB; // If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 37232e6ed..6db8fc50b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -92,7 +92,63 @@ uint32_t error_address = 0; static uint8_t ourMacAddr[6]; -NodeDB::NodeDB() {} +NodeDB::NodeDB() +{ + LOG_INFO("Initializing NodeDB\n"); + loadFromDisk(); + cleanupMeshDB(); + + uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate)); + uint32_t configCRC = crc32Buffer(&config, sizeof(config)); + uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile)); + + int saveWhat = 0; + + // likewise - we always want the app requirements to come from the running appload + myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 + // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't + // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) + pickNewNodeNum(); + + // Set our board type so we can share it with others + owner.hw_model = HW_VENDOR; + // Ensure user (nodeinfo) role is set to whatever we're configured to + owner.role = config.device.role; + + // Include our owner in the node db under our nodenum + meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); + info->user = owner; + info->has_user = true; + +#ifdef ARCH_ESP32 + Preferences preferences; + preferences.begin("meshtastic", false); + myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0); + preferences.end(); + LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count); +#endif + + resetRadioConfig(); // If bogus settings got saved, then fix them + // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); + + if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) + saveWhat |= SEGMENT_DEVICESTATE; + if (configCRC != crc32Buffer(&config, sizeof(config))) + saveWhat |= SEGMENT_CONFIG; + if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) + saveWhat |= SEGMENT_CHANNELS; + + if (!devicestate.node_remote_hardware_pins) { + meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; + memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); + } + + if (config.position.gps_enabled) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; + config.position.gps_enabled = 0; + } + saveToDisk(saveWhat); +} /** * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on @@ -439,67 +495,6 @@ void NodeDB::installDefaultDeviceState() memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); } -NodeDB *NodeDB::init() -{ - LOG_INFO("Initializing NodeDB\n"); - NodeDB *newnodeDB = new NodeDB; - newnodeDB->loadFromDisk(); - newnodeDB->cleanupMeshDB(); - - uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate)); - uint32_t configCRC = crc32Buffer(&config, sizeof(config)); - uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile)); - - int saveWhat = 0; - - // likewise - we always want the app requirements to come from the running appload - myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 - // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't - // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) - newnodeDB->pickNewNodeNum(); - - // Set our board type so we can share it with others - owner.hw_model = HW_VENDOR; - // Ensure user (nodeinfo) role is set to whatever we're configured to - owner.role = config.device.role; - - // Include our owner in the node db under our nodenum - meshtastic_NodeInfoLite *info = newnodeDB->getOrCreateMeshNode(newnodeDB->getNodeNum()); - info->user = owner; - info->has_user = true; - -#ifdef ARCH_ESP32 - Preferences preferences; - preferences.begin("meshtastic", false); - myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0); - preferences.end(); - LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count); -#endif - - newnodeDB->resetRadioConfig(); // If bogus settings got saved, then fix them - // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); - - if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) - saveWhat |= SEGMENT_DEVICESTATE; - if (configCRC != crc32Buffer(&config, sizeof(config))) - saveWhat |= SEGMENT_CONFIG; - if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) - saveWhat |= SEGMENT_CHANNELS; - - if (!devicestate.node_remote_hardware_pins) { - meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; - memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); - } - - if (config.position.gps_enabled) { - config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; - config.position.gps_enabled = 0; - } - return newnodeDB; - - nodeDB->saveToDisk(saveWhat); -} - // We reserve a few nodenums for future use #define NUM_RESERVED 4 diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index ea2019c37..23870db74 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -58,9 +58,6 @@ class NodeDB /// instead just store in flash - possibly even in the initial alpha release do this hack NodeDB(); - /// Called from service after app start, to do init which can only be done after OS load - static NodeDB *init(); - /// write to flash void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS), saveChannelsToDisk(), saveDeviceStateToDisk(); From 4debcd5ccd59eae3741789ce77d4c77f0567b17b Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:35:17 +0100 Subject: [PATCH 0146/1377] Set default position precision of mapReport to 14 (#3456) --- src/mqtt/MQTT.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index dbc0c77b3..41b1601e7 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -90,8 +90,8 @@ class MQTT : private concurrency::OSThread // For map reporting (only applies when enabled) uint32_t last_report_to_map = 0; - uint32_t map_position_precision = 32; // default to full precision - uint32_t map_publish_interval_secs = 60 * 15; // default to 15 minutes + uint32_t map_position_precision = 14; // defaults to max. offset of ~1459m + uint32_t map_publish_interval_secs = 60 * 15; // defaults to 15 minutes /** return true if we have a channel that wants uplink/downlink or map reporting is enabled */ From 0a7ddb7594d4a8c514ba29b592df3039079fb7cd Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:42:53 +0100 Subject: [PATCH 0147/1377] Let NeighborInfo Module ignore packets coming from MQTT (#3457) --- src/modules/NeighborInfoModule.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index df5c2c948..820e2d0d4 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -75,8 +75,9 @@ class NeighborInfoModule : public ProtobufModule, priva /* Does our periodic broadcast */ int32_t runOnce() override; - // Override wantPacket to say we want to see all packets when enabled, not just those for our port number - virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return enabled; } + /* Override wantPacket to say we want to see all packets when enabled, not just those for our port number. + Exception is when the packet came via MQTT */ + virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return enabled && !p->via_mqtt; } /* These are for debugging only */ void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); From 6dd337a651ffafd81f47a530a41ee23bfce286e1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 21 Mar 2024 14:43:10 -0500 Subject: [PATCH 0148/1377] Clear local position on nodedb-reset (#3451) * Clear local position on nodedb-reset * NodeDB pointer now, yo --------- Co-authored-by: Jonathan Bennett --- src/mesh/NodeDB.cpp | 11 +++++++++++ src/mesh/NodeDB.h | 2 ++ src/modules/PositionModule.cpp | 14 ++------------ src/modules/PositionModule.h | 3 --- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6db8fc50b..80b46a426 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -434,6 +434,7 @@ void NodeDB::resetNodes() { numMeshNodes = 1; std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite()); + clearLocalPosition(); saveDeviceStateToDisk(); if (neighborInfoModule && moduleConfig.neighbor_info.enabled) neighborInfoModule->resetNeighbors(); @@ -455,6 +456,16 @@ void NodeDB::removeNodeByNum(uint nodeNum) saveDeviceStateToDisk(); } +void NodeDB::clearLocalPosition() +{ + meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum()); + node->position.latitude_i = 0; + node->position.longitude_i = 0; + node->position.altitude = 0; + node->position.time = 0; + setLocalPosition(meshtastic_Position_init_default); +} + void NodeDB::cleanupMeshDB() { int newPos = 0, removed = 0; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 23870db74..4d24d7225 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -131,6 +131,8 @@ class NodeDB meshtastic_NodeInfoLite *getMeshNode(NodeNum n); size_t getNumMeshNodes() { return numMeshNodes; } + void clearLocalPosition(); + void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { if (timeOnly) { diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 0bfc775da..d22c6b699 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -34,21 +34,11 @@ PositionModule::PositionModule() if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && config.power.is_power_saving) { - clearPosition(); + LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n"); + nodeDB->clearLocalPosition(); } } -void PositionModule::clearPosition() -{ - LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n"); - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); - node->position.latitude_i = 0; - node->position.longitude_i = 0; - node->position.altitude = 0; - node->position.time = 0; - nodeDB->setLocalPosition(meshtastic_Position_init_default); -} - bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *pptr) { auto p = *pptr; diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index fddafef6f..68171ab0e 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -51,9 +51,6 @@ class PositionModule : public ProtobufModule, private concu struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition); meshtastic_MeshPacket *allocAtakPli(); uint32_t precision; - - /** Only used in power saving trackers for now */ - void clearPosition(); void sendLostAndFoundText(); }; From defeb8e52bab51eff5ab990119a0976f43730482 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 15:24:57 -0500 Subject: [PATCH 0149/1377] Bump actions to node 20 (#3461) --- .github/actions/setup-base/action.yml | 6 +++--- .github/workflows/build_esp32.yml | 4 ++-- .github/workflows/build_esp32_c3.yml | 4 ++-- .github/workflows/build_esp32_s3.yml | 4 ++-- .github/workflows/build_nrf52.yml | 4 ++-- .github/workflows/build_raspbian.yml | 4 ++-- .github/workflows/build_rpi2040.yml | 4 ++-- .github/workflows/main_matrix.yml | 18 +++++++++--------- .github/workflows/nightly.yml | 2 +- .github/workflows/package_raspbian.yml | 4 ++-- .github/workflows/sec_sast_flawfinder.yml | 4 ++-- .github/workflows/sec_sast_semgrep_cron.yml | 4 ++-- .github/workflows/sec_sast_semgrep_pull.yml | 2 +- .github/workflows/trunk-check.yml | 2 +- .github/workflows/update_protobufs.yml | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 7b97e1753..7e57f6a31 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -5,7 +5,7 @@ runs: using: "composite" steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" ref: ${{github.event.pull_request.head.ref}} @@ -30,12 +30,12 @@ runs: sudo apt-get install -y libyaml-cpp-dev - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.x - name: Cache python libs - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-pip # needed in if test with: path: ~/.cache/pip diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 31f0dd5a0..1a07d5f28 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -11,7 +11,7 @@ jobs: build-esp32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -54,7 +54,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index a30cf33f1..cdb8427c1 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -13,7 +13,7 @@ jobs: build-esp32-c3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -54,7 +54,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index f603a6a31..502a319c5 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -11,7 +11,7 @@ jobs: build-esp32-s3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -52,7 +52,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index 33ee4d00c..a37547973 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -11,7 +11,7 @@ jobs: build-nrf52: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -24,7 +24,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 7a25892bc..04aa2340b 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -11,7 +11,7 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} @@ -37,7 +37,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-raspbian-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index 76ca2c20e..aac70610f 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -11,7 +11,7 @@ jobs: build-rpi2040: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -24,7 +24,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 03d47f18e..d1c01a366 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -157,7 +157,7 @@ jobs: build-native: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -180,7 +180,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-native-${{ steps.version.outputs.version }}.zip path: | @@ -221,7 +221,7 @@ jobs: needs: [check] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -244,7 +244,7 @@ jobs: ] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -264,7 +264,7 @@ jobs: run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml - name: Repackage in single firmware zip - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ steps.version.outputs.version }} path: | @@ -295,7 +295,7 @@ jobs: run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output - name: Repackage in single elfs zip - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debug-elfs-${{ steps.version.outputs.version }}.zip path: ./*.elf @@ -319,10 +319,10 @@ jobs: needs: [gather-artifacts, after-checks] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.x diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index da59bc0fd..e249823a7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Trunk Check uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 377074e95..6c1ae5d60 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -17,7 +17,7 @@ jobs: needs: build-raspbian steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} @@ -66,7 +66,7 @@ jobs: depends: libyaml-cpp0.7, openssl desc: Native Linux Meshtastic binary. - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: artifact-deb path: | diff --git a/.github/workflows/sec_sast_flawfinder.yml b/.github/workflows/sec_sast_flawfinder.yml index 2c7e751af..59ff994ca 100644 --- a/.github/workflows/sec_sast_flawfinder.yml +++ b/.github/workflows/sec_sast_flawfinder.yml @@ -16,7 +16,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # step 2 - name: flawfinder_scan @@ -27,7 +27,7 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: flawfinder_report.sarif path: flawfinder_report.sarif diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index cdd2c3c37..a29e6ca02 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -17,7 +17,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # step 2 - name: full scan @@ -29,7 +29,7 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: report.sarif path: report.sarif diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml index 1697ffb1b..b6c288494 100644 --- a/.github/workflows/sec_sast_semgrep_pull.yml +++ b/.github/workflows/sec_sast_semgrep_pull.yml @@ -11,7 +11,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml index e35b91cb9..6ed905bc8 100644 --- a/.github/workflows/trunk-check.yml +++ b/.github/workflows/trunk-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Trunk Check uses: trunk-io/trunk-action@v1 diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 6944d827e..4c51c35c7 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -7,7 +7,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true From 9c88906acc1e910e49402bd474c7390be157a9f4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 16:14:45 -0500 Subject: [PATCH 0150/1377] Remove double run of build-raspbian --- .github/workflows/main_matrix.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index d1c01a366..c145feca2 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -145,11 +145,11 @@ jobs: with: board: ${{ matrix.board }} - build-raspbian: - strategy: - fail-fast: false - max-parallel: 1 - uses: ./.github/workflows/build_raspbian.yml + #build-raspbian: + # strategy: + # fail-fast: false + # max-parallel: 1 + # uses: ./.github/workflows/build_raspbian.yml package-raspbian: uses: ./.github/workflows/package_raspbian.yml From 907d075917209291338de769dedb667cfa6faf9e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 16:17:13 -0500 Subject: [PATCH 0151/1377] Revert previous attempt --- .github/workflows/main_matrix.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index c145feca2..d1c01a366 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -145,11 +145,11 @@ jobs: with: board: ${{ matrix.board }} - #build-raspbian: - # strategy: - # fail-fast: false - # max-parallel: 1 - # uses: ./.github/workflows/build_raspbian.yml + build-raspbian: + strategy: + fail-fast: false + max-parallel: 1 + uses: ./.github/workflows/build_raspbian.yml package-raspbian: uses: ./.github/workflows/package_raspbian.yml From 155df45d92fd23d86926d8ae46876b102b8b4b23 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Thu, 21 Mar 2024 22:20:20 +0100 Subject: [PATCH 0152/1377] Add sanity check for map report interval and position precision (#3459) * Add sanity check for map report interval and position precision * Use new `Default::` methods --- src/mqtt/MQTT.cpp | 8 +++++--- src/mqtt/MQTT.h | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index e29786dcb..390d0e206 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -195,8 +195,10 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) } if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { - map_position_precision = moduleConfig.mqtt.map_report_settings.position_precision; - map_publish_interval_secs = moduleConfig.mqtt.map_report_settings.publish_interval_secs; + map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, + default_map_position_precision); + map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( + moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); } #ifdef HAS_NETWORKING @@ -540,7 +542,7 @@ void MQTT::perhapsReportToMap() if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) return; - if (millis() - last_report_to_map < map_publish_interval_secs * 1000) { + if (millis() - last_report_to_map < map_publish_interval_msecs) { return; } else { if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 41b1601e7..f2eb6b120 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -89,9 +89,11 @@ class MQTT : private concurrency::OSThread std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages // For map reporting (only applies when enabled) + const uint32_t default_map_position_precision = 14; // defaults to max. offset of ~1459m + const uint32_t default_map_publish_interval_secs = 60 * 15; // defaults to 15 minutes uint32_t last_report_to_map = 0; - uint32_t map_position_precision = 14; // defaults to max. offset of ~1459m - uint32_t map_publish_interval_secs = 60 * 15; // defaults to 15 minutes + uint32_t map_position_precision = default_map_position_precision; + uint32_t map_publish_interval_msecs = default_map_publish_interval_secs * 1000; /** return true if we have a channel that wants uplink/downlink or map reporting is enabled */ From 79cfb1e8769bfd258f91dd9193c42d424e8c450e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 16:50:44 -0500 Subject: [PATCH 0153/1377] Revert "Bump actions to node 20 (#3461)" (#3462) This reverts commit defeb8e52bab51eff5ab990119a0976f43730482. As per https://github.com/actions/upload-artifact/issues/478 the new version of upload-artifact includes a breaking change. --- .github/actions/setup-base/action.yml | 6 +++--- .github/workflows/build_esp32.yml | 4 ++-- .github/workflows/build_esp32_c3.yml | 4 ++-- .github/workflows/build_esp32_s3.yml | 4 ++-- .github/workflows/build_nrf52.yml | 4 ++-- .github/workflows/build_raspbian.yml | 4 ++-- .github/workflows/build_rpi2040.yml | 4 ++-- .github/workflows/main_matrix.yml | 18 +++++++++--------- .github/workflows/nightly.yml | 2 +- .github/workflows/package_raspbian.yml | 4 ++-- .github/workflows/sec_sast_flawfinder.yml | 4 ++-- .github/workflows/sec_sast_semgrep_cron.yml | 4 ++-- .github/workflows/sec_sast_semgrep_pull.yml | 2 +- .github/workflows/trunk-check.yml | 2 +- .github/workflows/update_protobufs.yml | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 7e57f6a31..7b97e1753 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -5,7 +5,7 @@ runs: using: "composite" steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: submodules: "recursive" ref: ${{github.event.pull_request.head.ref}} @@ -30,12 +30,12 @@ runs: sudo apt-get install -y libyaml-cpp-dev - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: 3.x - name: Cache python libs - uses: actions/cache@v4 + uses: actions/cache@v3 id: cache-pip # needed in if test with: path: ~/.cache/pip diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 1a07d5f28..31f0dd5a0 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -11,7 +11,7 @@ jobs: build-esp32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -54,7 +54,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index cdb8427c1..a30cf33f1 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -13,7 +13,7 @@ jobs: build-esp32-c3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -54,7 +54,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 502a319c5..f603a6a31 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -11,7 +11,7 @@ jobs: build-esp32-s3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -52,7 +52,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index a37547973..33ee4d00c 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -11,7 +11,7 @@ jobs: build-nrf52: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -24,7 +24,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 04aa2340b..7a25892bc 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -11,7 +11,7 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} @@ -37,7 +37,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-raspbian-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index aac70610f..76ca2c20e 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -11,7 +11,7 @@ jobs: build-rpi2040: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -24,7 +24,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip path: | diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index d1c01a366..03d47f18e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -157,7 +157,7 @@ jobs: build-native: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build base id: base uses: ./.github/actions/setup-base @@ -180,7 +180,7 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-native-${{ steps.version.outputs.version }}.zip path: | @@ -221,7 +221,7 @@ jobs: needs: [check] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -244,7 +244,7 @@ jobs: ] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -264,7 +264,7 @@ jobs: run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml - name: Repackage in single firmware zip - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: firmware-${{ steps.version.outputs.version }} path: | @@ -295,7 +295,7 @@ jobs: run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output - name: Repackage in single elfs zip - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: debug-elfs-${{ steps.version.outputs.version }}.zip path: ./*.elf @@ -319,10 +319,10 @@ jobs: needs: [gather-artifacts, after-checks] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: 3.x diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e249823a7..da59bc0fd 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Trunk Check uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 6c1ae5d60..377074e95 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -17,7 +17,7 @@ jobs: needs: build-raspbian steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} @@ -66,7 +66,7 @@ jobs: depends: libyaml-cpp0.7, openssl desc: Native Linux Meshtastic binary. - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v3 with: name: artifact-deb path: | diff --git a/.github/workflows/sec_sast_flawfinder.yml b/.github/workflows/sec_sast_flawfinder.yml index 59ff994ca..2c7e751af 100644 --- a/.github/workflows/sec_sast_flawfinder.yml +++ b/.github/workflows/sec_sast_flawfinder.yml @@ -16,7 +16,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v4 + uses: actions/checkout@v3 # step 2 - name: flawfinder_scan @@ -27,7 +27,7 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: flawfinder_report.sarif path: flawfinder_report.sarif diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index a29e6ca02..cdd2c3c37 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -17,7 +17,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v4 + uses: actions/checkout@v3 # step 2 - name: full scan @@ -29,7 +29,7 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: report.sarif path: report.sarif diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml index b6c288494..1697ffb1b 100644 --- a/.github/workflows/sec_sast_semgrep_pull.yml +++ b/.github/workflows/sec_sast_semgrep_pull.yml @@ -11,7 +11,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml index 6ed905bc8..e35b91cb9 100644 --- a/.github/workflows/trunk-check.yml +++ b/.github/workflows/trunk-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Trunk Check uses: trunk-io/trunk-action@v1 diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 4c51c35c7..6944d827e 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -7,7 +7,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: submodules: true From 35754d661d01ade60a0f27031891264d676078f8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 21 Mar 2024 18:26:37 -0500 Subject: [PATCH 0154/1377] Make MAX_NUM_NODES configurable in variant.h (#3453) Co-authored-by: Ben Meadors --- src/mesh/mesh-pb-constants.h | 4 +--- variants/portduino/variant.h | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 9e747db1d..b8ef236c9 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -17,9 +17,7 @@ #define MAX_RX_TOPHONE 32 /// max number of nodes allowed in the mesh -#if ARCH_PORTDUINO -#define MAX_NUM_NODES settingsMap[maxnodes] -#else +#ifndef MAX_NUM_NODES #define MAX_NUM_NODES 100 #endif diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h index f47b58afc..5aad8dbfc 100644 --- a/variants/portduino/variant.h +++ b/variants/portduino/variant.h @@ -1,3 +1,4 @@ #define HAS_SCREEN 1 #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 +#define MAX_NUM_NODES settingsMap[maxnodes] From a57f7730eacc5fbe42315c638c550b724684a507 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:55:50 -0500 Subject: [PATCH 0155/1377] [create-pull-request] automated change (#3463) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 13 +++++++++---- src/mesh/generated/meshtastic/mesh.pb.h | 13 +++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/protobufs b/protobufs index 556e49ba6..bcfb49c49 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 556e49ba619e2f4d8fa3c2dee2a94129a43d5f08 +Subproject commit bcfb49c4988b1539fc35e568a58b9f2f5b60738a diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index c286bd471..c65a5764f 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -70,6 +70,9 @@ typedef struct _meshtastic_NodeInfoLite { bool via_mqtt; /* Number of hops away from us this node is (0 if adjacent) */ uint8_t hops_away; + /* True if node is in our favorites list + Persists between NodeDB internal clean ups */ + bool is_favorite; } meshtastic_NodeInfoLite; /* The on-disk saved channels */ @@ -180,13 +183,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {{NULL}, NULL}} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {{NULL}, NULL}} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -207,6 +210,7 @@ extern "C" { #define meshtastic_NodeInfoLite_channel_tag 7 #define meshtastic_NodeInfoLite_via_mqtt_tag 8 #define meshtastic_NodeInfoLite_hops_away_tag 9 +#define meshtastic_NodeInfoLite_is_favorite_tag 10 #define meshtastic_ChannelFile_channels_tag 1 #define meshtastic_ChannelFile_version_tag 2 #define meshtastic_OEMStore_oem_icon_width_tag 1 @@ -262,7 +266,8 @@ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ X(a, STATIC, SINGULAR, UINT32, channel, 7) \ X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ -X(a, STATIC, SINGULAR, UINT32, hops_away, 9) +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User @@ -324,7 +329,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceState_size depends on runtime parameters */ #define meshtastic_ChannelFile_size 702 -#define meshtastic_NodeInfoLite_size 158 +#define meshtastic_NodeInfoLite_size 160 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_OEMStore_size 3278 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 2f57f1ae2..5804dd42a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -638,6 +638,9 @@ typedef struct _meshtastic_NodeInfo { bool via_mqtt; /* Number of hops away from us this node is (0 if adjacent) */ uint8_t hops_away; + /* True if node is in our favorites list + Persists between NodeDB internal clean ups */ + bool is_favorite; } meshtastic_NodeInfo; /* Unique local debugging info for this node @@ -906,7 +909,7 @@ extern "C" { #define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -925,7 +928,7 @@ extern "C" { #define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -1016,6 +1019,7 @@ extern "C" { #define meshtastic_NodeInfo_channel_tag 7 #define meshtastic_NodeInfo_via_mqtt_tag 8 #define meshtastic_NodeInfo_hops_away_tag 9 +#define meshtastic_NodeInfo_is_favorite_tag 10 #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 @@ -1182,7 +1186,8 @@ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ X(a, STATIC, SINGULAR, UINT32, channel, 7) \ X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ -X(a, STATIC, SINGULAR, UINT32, hops_away, 9) +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL #define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User @@ -1350,7 +1355,7 @@ extern const pb_msgdesc_t meshtastic_Heartbeat_msg; #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 275 +#define meshtastic_NodeInfo_size 277 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 From 7aa013a716aa0ed89d3fdac7eecc310ca437330f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 21 Mar 2024 19:51:02 -0500 Subject: [PATCH 0156/1377] Skip favorite nodes when clearing out oldest in NodeDB (#3464) * Skip favorite nodes when clearing out oldest in NodeDB * We should actually map between the types --- src/mesh/NodeDB.cpp | 2 +- src/mesh/TypeConversions.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 80b46a426..f65fe0da3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -939,7 +939,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) uint32_t oldest = UINT32_MAX; int oldestIndex = -1; for (int i = 1; i < numMeshNodes; i++) { - if (meshNodes->at(i).last_heard < oldest) { + if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) { oldest = meshNodes->at(i).last_heard; oldestIndex = i; } diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 20b1cb31e..bcd600f24 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -12,6 +12,7 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo info.channel = lite->channel; info.via_mqtt = lite->via_mqtt; info.hops_away = lite->hops_away; + info.is_favorite = lite->is_favorite; if (lite->has_position) { info.has_position = true; From 794e99c2f992919e1dea57e3c9cf936ca4648aed Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 21 Mar 2024 20:45:48 -0500 Subject: [PATCH 0157/1377] Log warning cleanup and truth (#3466) --- src/gps/GPS.cpp | 2 +- src/main.cpp | 2 +- src/mesh/MeshService.cpp | 2 +- src/mesh/NodeDB.cpp | 6 +++--- src/mesh/RF95Interface.cpp | 2 +- src/mesh/SX126xInterface.cpp | 2 +- src/mesh/SX128xInterface.cpp | 2 +- src/platform/esp32/SimpleAllocator.cpp | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 7d4f41a55..df1d40fdf 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1278,7 +1278,7 @@ bool GPS::lookForLocation() #ifndef TINYGPS_OPTION_NO_STATISTICS if (reader.failedChecksum() > lastChecksumFailCount) { - LOG_WARN("Warning, %u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount, + LOG_WARN("%u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount, reader.failedChecksum()); lastChecksumFailCount = reader.failedChecksum(); } diff --git a/src/main.cpp b/src/main.cpp index 5f746f12a..f7d1a4bc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -631,7 +631,7 @@ void setup() #else // ESP32 SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); - LOG_WARN("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); + LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); SPI.setFrequency(4000000); #endif diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 31eb082ec..2df5d5797 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -359,7 +359,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) LOG_DEBUG("onGPSchanged() - lost validLocation\n"); #endif } - // Used fixed position if configured regalrdless of GPS lock + // Used fixed position if configured regardless of GPS lock if (config.position.fixed_position) { LOG_WARN("Using fixed position\n"); pos = TypeConversions::ConvertToPosition(node->position); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index f65fe0da3..3734309be 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -195,7 +195,7 @@ bool NodeDB::factoryReset() // first, remove the "/prefs" (this removes most prefs) rmDir("/prefs"); if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) { - LOG_WARN("Could not remove rangetest.csv file\n"); + LOG_ERROR("Could not remove rangetest.csv file\n"); } // second, install default state (this will deal with the duplicate mac address issue) installDefaultDeviceState(); @@ -527,7 +527,7 @@ void NodeDB::pickNewNodeNum() LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate); nodeNum = candidate; } - LOG_WARN("Using nodenum 0x%x \n", nodeNum); + LOG_DEBUG("Using nodenum 0x%x \n", nodeNum); myNodeInfo.my_node_num = nodeNum; } @@ -934,7 +934,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { if (screen) screen->print("Warn: node database full!\nErasing oldest entry\n"); - LOG_INFO("Warn: node database full!\nErasing oldest entry\n"); + LOG_WARN("Node database full! Erasing oldest entry\n"); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 72e0f823f..adc512ae2 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -19,7 +19,7 @@ RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIO RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy) { - LOG_WARN("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); + LOG_DEBUG("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /** Some boards require GPIO control of tx vs rx paths */ diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 7220dd3e5..104d0a5ed 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -17,7 +17,7 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module) { - LOG_WARN("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); + LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /// Initialise the Driver transport hardware and software. diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index f2220dbcf..45325f339 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -17,7 +17,7 @@ SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module) { - LOG_WARN("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); + LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /// Initialise the Driver transport hardware and software. diff --git a/src/platform/esp32/SimpleAllocator.cpp b/src/platform/esp32/SimpleAllocator.cpp index ed44722c5..63f3b02de 100644 --- a/src/platform/esp32/SimpleAllocator.cpp +++ b/src/platform/esp32/SimpleAllocator.cpp @@ -58,7 +58,7 @@ void *operator new(size_t sz) throw(std::bad_alloc) void operator delete(void *ptr) throw() { if (activeAllocator) - LOG_DEBUG("Warning: leaking an active allocator object\n"); // We don't properly handle this yet + LOG_WARN("Leaking an active allocator object\n"); // We don't properly handle this yet else free(ptr); } From c77c58d656e3c42ee69a793881e1670de92a79b2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:24:10 -0500 Subject: [PATCH 0158/1377] [create-pull-request] automated change (#3470) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index bcfb49c49..0fe69d73e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bcfb49c4988b1539fc35e568a58b9f2f5b60738a +Subproject commit 0fe69d73e639372128d9480ec8cf65b182d36d30 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 28bda429d..d2f40c7f0 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -154,6 +154,10 @@ typedef struct _meshtastic_AdminMessage { char set_ringtone_message[231]; /* Remove the node by the specified node-num from the NodeDB on the device */ uint32_t remove_by_nodenum; + /* Set specified node-num to be favorited on the NodeDB on the device */ + uint32_t set_favorite_node; + /* Set specified node-num to be un-favorited on the NodeDB on the device */ + uint32_t remove_favorite_node; /* Begins an edit transaction for config, module config, owner, and channel settings changes This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */ bool begin_edit_settings; @@ -238,6 +242,8 @@ extern "C" { #define meshtastic_AdminMessage_set_canned_message_module_messages_tag 36 #define meshtastic_AdminMessage_set_ringtone_message_tag 37 #define meshtastic_AdminMessage_remove_by_nodenum_tag 38 +#define meshtastic_AdminMessage_set_favorite_node_tag 39 +#define meshtastic_AdminMessage_remove_favorite_node_tag 40 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 @@ -277,6 +283,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_module_config,set_module X(a, STATIC, ONEOF, STRING, (payload_variant,set_canned_message_module_messages,set_canned_message_module_messages), 36) \ X(a, STATIC, ONEOF, STRING, (payload_variant,set_ringtone_message,set_ringtone_message), 37) \ X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_by_nodenum,remove_by_nodenum), 38) \ +X(a, STATIC, ONEOF, UINT32, (payload_variant,set_favorite_node,set_favorite_node), 39) \ +X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_favorite_node,remove_favorite_node), 40) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ From 54818b5f8d2d289fb031e4a5054e81dd4203e73a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 22 Mar 2024 07:25:00 -0500 Subject: [PATCH 0159/1377] Enforce consistent polite channel utilization limits except for Sensor role (#3467) --- src/modules/Telemetry/AirQualityTelemetry.cpp | 1 + src/modules/Telemetry/DeviceTelemetry.cpp | 4 ++-- src/modules/Telemetry/EnvironmentTelemetry.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 3e9b069c4..a51a7cea9 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -45,6 +45,7 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 7c02b57b4..2ae904b89 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -18,8 +18,8 @@ int32_t DeviceTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && - airTime->isTxAllowedChannelUtil() && airTime->isTxAllowedAirUtil() && - config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && + airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 203b632a7..908062a5b 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -104,6 +104,7 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; From 94e4301f2f5f224e0d1dc828aeca2846714d9e35 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 22 Mar 2024 10:53:18 -0500 Subject: [PATCH 0160/1377] Add set and remove favorite nodes admin commands (#3471) --- src/modules/AdminModule.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 6d420ddb8..ae0dac9ff 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -189,6 +189,22 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta nodeDB->removeNodeByNum(r->remove_by_nodenum); break; } + case meshtastic_AdminMessage_set_favorite_node_tag: { + LOG_INFO("Client is receiving a set_favorite_node command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node); + if (node != NULL) { + node->is_favorite = true; + } + break; + } + case meshtastic_AdminMessage_remove_favorite_node_tag: { + LOG_INFO("Client is receiving a remove_favorite_node command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node); + if (node != NULL) { + node->is_favorite = false; + } + break; + } case meshtastic_AdminMessage_enter_dfu_mode_request_tag: { LOG_INFO("Client is requesting to enter DFU mode.\n"); #if defined(ARCH_NRF52) || defined(ARCH_RP2040) From d30d6bd3eba7f0db00146449391a7c89f17df5fe Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sat, 23 Mar 2024 13:31:58 +0100 Subject: [PATCH 0161/1377] Fix #3452: only alter received packet if port number matches (#3474) * Use `alterReceivedProtobuf()` for NeighborInfo and Traceroute `alterReceived()` should never return NULL Traceroute should be promiscuous * Remove extensive logging from NeighborInfo module --- src/mesh/FloodingRouter.cpp | 9 ---- src/mesh/FloodingRouter.h | 2 - src/mesh/ProtobufModule.h | 4 +- src/modules/NeighborInfoModule.cpp | 72 +++--------------------------- src/modules/NeighborInfoModule.h | 9 +--- src/modules/TraceRouteModule.cpp | 24 ++++------ src/modules/TraceRouteModule.h | 7 +-- 7 files changed, 22 insertions(+), 105 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index db3f3f35e..4cfe982d8 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -49,15 +49,6 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas tosend->hop_limit--; // bump down the hop count - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - // If it is a traceRoute request, update the route that it went via me - if (traceRouteModule && traceRouteModule->wantPacket(p)) - traceRouteModule->updateRoute(tosend); - // If it is a neighborInfo packet, update last_sent_by_id - if (neighborInfoModule && neighborInfoModule->wantPacket(p)) - neighborInfoModule->updateLastSentById(tosend); - } - LOG_INFO("Rebroadcasting received floodmsg to neighbors\n"); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 309035cb3..a3adfe70c 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -2,8 +2,6 @@ #include "PacketHistory.h" #include "Router.h" -#include "modules/NeighborInfoModule.h" -#include "modules/TraceRouteModule.h" /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 1067ee01e..a2e89e98a 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -108,8 +108,8 @@ template class ProtobufModule : protected SinglePortModule // if we can't decode it, nobody can process it! return; } - } - return alterReceivedProtobuf(mp, decoded); + return alterReceivedProtobuf(mp, decoded); + } } }; \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 4d68b4a16..1e9652469 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -18,73 +18,24 @@ void NeighborInfoModule::printNeighborInfo(const char *header, const meshtastic_ { LOG_DEBUG("%s NEIGHBORINFO PACKET from Node 0x%x to Node 0x%x (last sent by 0x%x)\n", header, np->node_id, nodeDB->getNodeNum(), np->last_sent_by_id); - LOG_DEBUG("----------------\n"); LOG_DEBUG("Packet contains %d neighbors\n", np->neighbors_count); for (int i = 0; i < np->neighbors_count; i++) { LOG_DEBUG("Neighbor %d: node_id=0x%x, snr=%.2f\n", i, np->neighbors[i].node_id, np->neighbors[i].snr); } - LOG_DEBUG("----------------\n"); -} -/* -Prints the nodeDB nodes so we can see whose nodeInfo we have -NOTE: for debugging only -*/ -void NeighborInfoModule::printNodeDBNodes(const char *header) -{ - int num_nodes = nodeDB->getNumMeshNodes(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB->getNodeNum()); - LOG_DEBUG("----------------\n"); - LOG_DEBUG("DB contains %d nodes\n", num_nodes); - for (int i = 0; i < num_nodes; i++) { - const meshtastic_NodeInfoLite *dbEntry = nodeDB->getMeshNodeByIndex(i); - LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->num, dbEntry->snr); - } - LOG_DEBUG("----------------\n"); } /* Prints the nodeDB neighbors NOTE: for debugging only */ -void NeighborInfoModule::printNodeDBNeighbors(const char *header) +void NeighborInfoModule::printNodeDBNeighbors() { int num_neighbors = getNumNeighbors(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB->getNodeNum()); - LOG_DEBUG("----------------\n"); - LOG_DEBUG("DB contains %d neighbors\n", num_neighbors); + LOG_DEBUG("Our NodeDB contains %d neighbors\n", num_neighbors); for (int i = 0; i < num_neighbors; i++) { const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); } - LOG_DEBUG("----------------\n"); -} - -/* -Prints the nodeDB with selectors for the neighbors we've chosen to send (inefficiently) -Uses LOG_DEBUG, which equates to Console.log -NOTE: For debugging only -*/ -void NeighborInfoModule::printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np) -{ - int num_neighbors = getNumNeighbors(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB->getNodeNum()); - LOG_DEBUG("----------------\n"); - LOG_DEBUG("Selected %d neighbors of %d DB neighbors\n", np->neighbors_count, num_neighbors); - for (int i = 0; i < num_neighbors; i++) { - meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); - bool chosen = false; - for (int j = 0; j < np->neighbors_count; j++) { - if (np->neighbors[j].node_id == dbEntry->node_id) { - chosen = true; - } - } - if (!chosen) { - LOG_DEBUG(" Node %d: neighbor=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); - } else { - LOG_DEBUG("---> Node %d: neighbor=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); - } - } - LOG_DEBUG("----------------\n"); } /* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */ @@ -129,9 +80,7 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb neighborInfo->neighbors_count++; } } - printNodeDBNodes("DBSTATE"); - printNodeDBNeighbors("NEIGHBORS"); - printNodeDBSelection("COLLECTED", neighborInfo); + printNodeDBNeighbors(); return neighborInfo->neighbors_count; } @@ -218,20 +167,13 @@ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, /* Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum */ -void NeighborInfoModule::updateLastSentById(meshtastic_MeshPacket *p) +void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) { - auto &incoming = p->decoded; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *updated = NULL; - memset(&scratch, 0, sizeof(scratch)); - pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_NeighborInfo_msg, &scratch); - updated = &scratch; - - updated->last_sent_by_id = nodeDB->getNodeNum(); + n->last_sent_by_id = nodeDB->getNodeNum(); // Set updated last_sent_by_id to the payload of the to be flooded packet - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_NeighborInfo_msg, updated); + p.decoded.payload.size = + pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_NeighborInfo_msg, n); } void NeighborInfoModule::resetNeighbors() diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index 820e2d0d4..b4acb0f66 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -20,9 +20,6 @@ class NeighborInfoModule : public ProtobufModule, priva bool saveProtoForModule(); - // Let FloodingRouter call updateLastSentById upon rebroadcasting a NeighborInfo packet - friend class FloodingRouter; - protected: // Note: this holds our local info. meshtastic_NeighborInfo neighborState; @@ -68,7 +65,7 @@ class NeighborInfoModule : public ProtobufModule, priva void updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np); /* update a NeighborInfo packet with our NodeNum as last_sent_by_id */ - void updateLastSentById(meshtastic_MeshPacket *p); + void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) override; void loadProtoForModule(); @@ -81,8 +78,6 @@ class NeighborInfoModule : public ProtobufModule, priva /* These are for debugging only */ void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); - void printNodeDBNodes(const char *header); - void printNodeDBNeighbors(const char *header); - void printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np); + void printNodeDBNeighbors(); }; extern NeighborInfoModule *neighborInfoModule; \ No newline at end of file diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 311e211f3..aa0b6a1eb 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -1,5 +1,4 @@ #include "TraceRouteModule.h" -#include "FloodingRouter.h" #include "MeshService.h" TraceRouteModule *traceRouteModule; @@ -14,23 +13,17 @@ bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m return false; // let it be handled by RoutingModule } -void TraceRouteModule::updateRoute(meshtastic_MeshPacket *p) +void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { - auto &incoming = p->decoded; - // Only append an ID for the request (one way) - if (!incoming.request_id) { - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *updated = NULL; - memset(&scratch, 0, sizeof(scratch)); - pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_RouteDiscovery_msg, &scratch); - updated = &scratch; - - appendMyID(updated); - printRoute(updated, p->from, NODENUM_BROADCAST); + auto &incoming = p.decoded; + // Only append an ID for the request (one way) and if we are not the destination (the reply will have our NodeNum already) + if (!incoming.request_id && p.to != nodeDB->getNodeNum()) { + appendMyID(r); + printRoute(r, p.from, NODENUM_BROADCAST); // Set updated route to the payload of the to be flooded packet - p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), - &meshtastic_RouteDiscovery_msg, updated); + p.decoded.payload.size = + pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r); } } @@ -85,4 +78,5 @@ TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg) { ourPortNum = meshtastic_PortNum_TRACEROUTE_APP; + isPromiscuous = true; // We need to update the route even if it is not destined to us } \ No newline at end of file diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index 674846ef1..15e01debd 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -9,17 +9,14 @@ class TraceRouteModule : public ProtobufModule public: TraceRouteModule(); - // Let FloodingRouter call updateRoute upon rebroadcasting a TraceRoute request - friend class FloodingRouter; - protected: bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) override; virtual meshtastic_MeshPacket *allocReply() override; - /* Call before rebroadcasting a RouteDiscovery payload in order to update + /* Called before rebroadcasting a RouteDiscovery payload in order to update the route array containing the IDs of nodes this packet went through */ - void updateRoute(meshtastic_MeshPacket *p); + void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override; private: // Call to add your ID to the route array of a RouteDiscovery message From 9e8860d1888332b3fd8588e3aaf9f3867ced3415 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 23 Mar 2024 12:29:05 -0500 Subject: [PATCH 0162/1377] Crash fix and remove hard-coded path from PiWebServer (#3478) * Remove hard-coded path from PiWebServer * Bump portduino to pick up crash fix * Remove PiWebServer non-ASCII debug output * Trunk formatting --- arch/portduino/portduino.ini | 2 +- src/mesh/raspihttp/PiWebServer.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 368fb5d0e..ef8711f8a 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#a28dd5a9ccd5c48a9bede46037855ff83915d74b +platform = https://github.com/meshtastic/platform-native.git#1b8a32c60ab7495026033858d53c737f7d1cb34a framework = arduino build_src_filter = diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp index 41f6727a4..bffd6c340 100644 --- a/src/mesh/raspihttp/PiWebServer.cpp +++ b/src/mesh/raspihttp/PiWebServer.cpp @@ -244,7 +244,7 @@ int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, vo // FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread - portduinoVFS->mountpoint("/home/marc/.portduino/default"); + portduinoVFS->mountpoint(configWeb.rootPath); LOG_DEBUG("Received %d bytes from PUT request\n", s); webAPI.handleToRadio(buffer, s); @@ -279,8 +279,8 @@ int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, const char *tmpa = (const char *)txBuf; ulfius_set_string_body_response(res, 200, tmpa); // LOG_DEBUG("\n----webAPI response all:----\n"); - LOG_DEBUG(tmpa); - LOG_DEBUG("\n"); + // LOG_DEBUG(tmpa); + // LOG_DEBUG("\n"); } // Otherwise, just return one protobuf } else { @@ -288,8 +288,8 @@ int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, const char *tmpa = (const char *)txBuf; ulfius_set_binary_body_response(res, 200, tmpa, len); // LOG_DEBUG("\n----webAPI response:\n"); - LOG_DEBUG(tmpa); - LOG_DEBUG("\n"); + // LOG_DEBUG(tmpa); + // LOG_DEBUG("\n"); } // LOG_DEBUG("end radio->web\n", len); @@ -508,7 +508,7 @@ PiWebServerThread::PiWebServerThread() LOG_INFO("Web Server framework started on port: %i \n", webservport); LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str()); } else { - LOG_ERROR("Error starting Web Server framework\n"); + LOG_ERROR("Error starting Web Server framework, error number: %d\n", retssl); } } } From 4cce4c7c93a5c44cb7de5ce509f63a1db0587dbf Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sat, 23 Mar 2024 18:38:29 +0100 Subject: [PATCH 0163/1377] Set unused header bytes to zero for future use (#3479) --- src/mesh/RadioInterface.cpp | 2 ++ src/mesh/RadioInterface.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 859e7bea4..3aac9dfce 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -564,6 +564,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) h->to = p->to; h->id = p->id; h->channel = p->channel; + h->next_hop = 0; // *** For future use *** + h->relay_node = 0; // *** For future use *** if (p->hop_limit > HOP_MAX) { LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE); p->hop_limit = HOP_RELIABLE; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index ee4726d74..b965328e4 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -34,6 +34,12 @@ typedef struct { /** The channel hash - used as a hint for the decoder to limit which channels we consider */ uint8_t channel; + + // ***For future use*** Last byte of the NodeNum of the next-hop for this packet + uint8_t next_hop; + + // ***For future use*** Last byte of the NodeNum of the node that will relay/relayed this packet + uint8_t relay_node; } PacketHeader; /** From 71ca6f768f6cd1e06ff2aa8bc1ba7e53c00e50d4 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sat, 23 Mar 2024 19:35:12 +0100 Subject: [PATCH 0164/1377] Actually update last_report_to_map --- src/mqtt/MQTT.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 390d0e206..8e7c8f2cc 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -546,6 +546,7 @@ void MQTT::perhapsReportToMap() return; } else { if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + last_report_to_map = millis(); LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); return; } From c87fdfece73ce3a242a77e02218fd535299371c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 23 Mar 2024 19:47:43 -0500 Subject: [PATCH 0165/1377] [create-pull-request] automated change (#3483) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 12603eda7..58a6c19d7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 2 +build = 3 From 63df972d42b4aed34f7faea25174368a9a7f14cf Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 24 Mar 2024 08:11:47 -0500 Subject: [PATCH 0166/1377] Revert "[create-pull-request] automated change (#3483)" (#3484) This reverts commit c87fdfece73ce3a242a77e02218fd535299371c9. --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 58a6c19d7..12603eda7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 3 +build = 2 From b4dbc2b4bf7b7f3d0c0e3107f2128b127d6521ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 12:44:44 -0500 Subject: [PATCH 0167/1377] [create-pull-request] automated change (#3485) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 12603eda7..58a6c19d7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 2 +build = 3 From 5f529f7ca39ef8ca0e4a88dde3963d29422ea87c Mon Sep 17 00:00:00 2001 From: code8buster <20384924+code8buster@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:41:28 +0000 Subject: [PATCH 0168/1377] Remove unused defines from nrf52 variants (#3482) Co-authored-by: Ben Meadors --- variants/Dongle_nRF52840-pca10059-v1/variant.h | 13 ++----------- variants/MakePython_nRF52840_eink/variant.h | 13 ++----------- variants/MakePython_nRF52840_oled/variant.h | 13 ++----------- variants/canaryone/variant.h | 11 +---------- variants/monteops_hw1/variant.h | 13 ++----------- variants/nano-g2-ultra/variant.h | 13 ++----------- variants/rak10701/variant.h | 13 ++----------- variants/rak4631/variant.h | 11 +---------- variants/rak4631_epaper/variant.h | 11 +---------- variants/rak4631_epaper_onrxtx/variant.h | 11 +---------- variants/t-echo/variant.h | 11 +---------- variants/trackerd/variant.h | 17 +++-------------- 12 files changed, 20 insertions(+), 130 deletions(-) diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/Dongle_nRF52840-pca10059-v1/variant.h index 0f1bf15da..533367a30 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.h @@ -155,19 +155,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #ifdef __cplusplus } @@ -177,4 +168,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/MakePython_nRF52840_eink/variant.h b/variants/MakePython_nRF52840_eink/variant.h index 2ff9c76fd..16f803dd2 100644 --- a/variants/MakePython_nRF52840_eink/variant.h +++ b/variants/MakePython_nRF52840_eink/variant.h @@ -134,19 +134,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #ifdef __cplusplus } @@ -156,4 +147,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/MakePython_nRF52840_oled/variant.h b/variants/MakePython_nRF52840_oled/variant.h index e7375a610..a2a5fa80a 100644 --- a/variants/MakePython_nRF52840_oled/variant.h +++ b/variants/MakePython_nRF52840_oled/variant.h @@ -112,19 +112,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #ifdef __cplusplus } @@ -134,4 +125,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/canaryone/variant.h b/variants/canaryone/variant.h index 21aa921ce..d283f08f6 100644 --- a/variants/canaryone/variant.h +++ b/variants/canaryone/variant.h @@ -166,19 +166,10 @@ static const uint8_t A0 = PIN_A0; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (2.0F) #ifdef __cplusplus } diff --git a/variants/monteops_hw1/variant.h b/variants/monteops_hw1/variant.h index f7df0688b..97536b169 100644 --- a/variants/monteops_hw1/variant.h +++ b/variants/monteops_hw1/variant.h @@ -208,19 +208,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) // #define HAS_RTC 1 @@ -239,4 +230,4 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/nano-g2-ultra/variant.h b/variants/nano-g2-ultra/variant.h index c328d2271..4d8aa5784 100644 --- a/variants/nano-g2-ultra/variant.h +++ b/variants/nano-g2-ultra/variant.h @@ -165,19 +165,10 @@ External serial flash W25Q16JV_IQ // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0F) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (2.0F) #define HAS_RTC 1 @@ -195,4 +186,4 @@ External serial flash W25Q16JV_IQ * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/rak10701/variant.h b/variants/rak10701/variant.h index d6eeb71dc..076504c16 100644 --- a/variants/rak10701/variant.h +++ b/variants/rak10701/variant.h @@ -264,19 +264,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #define HAS_RTC 1 @@ -325,4 +316,4 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 0ccf3b1d7..bc5541336 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -246,19 +246,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73F) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER 1.73 #define HAS_RTC 1 diff --git a/variants/rak4631_epaper/variant.h b/variants/rak4631_epaper/variant.h index b1bd84d21..0bb97498c 100644 --- a/variants/rak4631_epaper/variant.h +++ b/variants/rak4631_epaper/variant.h @@ -214,19 +214,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73F) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER 1.73 #define HAS_RTC 1 diff --git a/variants/rak4631_epaper_onrxtx/variant.h b/variants/rak4631_epaper_onrxtx/variant.h index ec53ebd33..5888cff33 100644 --- a/variants/rak4631_epaper_onrxtx/variant.h +++ b/variants/rak4631_epaper_onrxtx/variant.h @@ -187,19 +187,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution // #define BATTERY_SENSE_RESOLUTION_BITS 12 // #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -// #define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -// #define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -// #define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -// #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) // #undef AREF_VOLTAGE // #define AREF_VOLTAGE 3.0 // #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -// #define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -// #define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +// #define ADC_MULTIPLIER 1.73 // #define HAS_RTC 1 diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 13f74d303..6a5146dc0 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -209,19 +209,10 @@ External serial flash WP25R1635FZUIL0 // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0F) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (2.0F) #define HAS_RTC 1 diff --git a/variants/trackerd/variant.h b/variants/trackerd/variant.h index b3fca367f..bd8017d8c 100644 --- a/variants/trackerd/variant.h +++ b/variants/trackerd/variant.h @@ -10,7 +10,7 @@ #define LED_PIN 13 // 13 red, 2 blue, 15 red -//#define HAS_BUTTON 0 +// #define HAS_BUTTON 0 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP @@ -26,21 +26,10 @@ // The battery sense is hooked to pin A0 (4) // it is defined in the anlaolgue pin section of this file // and has 12 bit resolution +// #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) - -//#define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. -//#define ADC_MULTIPLIER 3.3 \ No newline at end of file +#define ADC_MULTIPLIER (2.0F) \ No newline at end of file From b960dc1b415b76502cf71e6f9a902f0d80832a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 24 Mar 2024 19:41:45 +0100 Subject: [PATCH 0169/1377] Add Shutdown and reboot to CardKB and friends (#3487) * Add Shutdown and reboot to CardKB and friends * aw shucks --- src/configuration.h | 8 ++++++++ src/input/kbI2cBase.cpp | 6 ++++++ src/modules/AdminModule.cpp | 2 -- src/modules/CannedMessageModule.cpp | 10 ++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index ec32c72d1..c3dd2cb06 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -161,6 +161,14 @@ along with this program. If not, see . also enable HAS_ option not specifically disabled by variant.h */ #include "architecture.h" +#ifndef DEFAULT_REBOOT_SECONDS +#define DEFAULT_REBOOT_SECONDS 7 +#endif + +#ifndef DEFAULT_SHUTDOWN_SECONDS +#define DEFAULT_SHUTDOWN_SECONDS 2 +#endif + /* Step #3: mop up with disabled values for HAS_ options not handled by the above two */ // ----------------------------------------------------------------------------- diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 1dba4e34d..048f8bbdc 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -216,6 +216,12 @@ int32_t KbI2cBase::runOnce() e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; e.kbchar = 0xb7; break; + case 0x90: // fn+r + case 0x9b: // fn+s + // just pass those unmodified + e.inputEvent = ANYKEY; + e.kbchar = c; + break; case 0x0d: // Enter e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; break; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index ae0dac9ff..c44048fd2 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -20,8 +20,6 @@ #include "mqtt/MQTT.h" -#define DEFAULT_REBOOT_SECONDS 7 - AdminModule *adminModule; bool hasOpenEditTransaction; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 3293e5d0d..60334ca03 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -409,6 +409,16 @@ int32_t CannedMessageModule::runOnce() case 0xb7: // right // already handled above break; + // handle fn+s for shutdown + case 0x9b: + screen->startShutdownScreen(); + shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; + break; + // and fn+r for reboot + case 0x90: + screen->startRebootScreen(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; default: if (this->cursor == this->freetext.length()) { this->freetext += this->payload; From 77fb230baaa3557b984a50d79970be98d0fd6618 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 24 Mar 2024 19:42:32 +0100 Subject: [PATCH 0170/1377] Native: fail-safes for simulated node without config file (#3486) * LinuxInput: only close if file descriptor is assigned * Native: set some defaults if no configuration file found --- src/input/LinuxInput.cpp | 3 ++- src/input/LinuxInput.h | 2 +- src/platform/portduino/PortduinoGlue.cpp | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp index d2a94e94e..1ace2044c 100644 --- a/src/input/LinuxInput.cpp +++ b/src/input/LinuxInput.cpp @@ -24,7 +24,8 @@ LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name) void LinuxInput::deInit() { - close(fd); + if (fd >= 0) + close(fd); } int32_t LinuxInput::runOnce() diff --git a/src/input/LinuxInput.h b/src/input/LinuxInput.h index aa1e8e340..43d08493c 100644 --- a/src/input/LinuxInput.h +++ b/src/input/LinuxInput.h @@ -38,7 +38,7 @@ class LinuxInput : public Observable, public concurrency::OS int queue_progress = 0; struct epoll_event events[MAX_EVENTS]; - int fd; + int fd = -1; int ret; uint8_t report[8]; int epollfd; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index b255c0ce1..72b2a3bc7 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -106,6 +106,8 @@ void portduinoSetup() } } else { std::cout << "No 'config.yaml' found, running simulated." << std::endl; + settingsMap[maxnodes] = 200; // Default to 200 nodes + settingsMap[logoutputlevel] = level_debug; // Default to debug // Set the random seed equal to TCPPort to have a different seed per instance randomSeed(TCPPort); return; From 728b58fb94f813a11c6aaa3d889749e0df3aa47e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:50:26 -0500 Subject: [PATCH 0171/1377] [create-pull-request] automated change (#3489) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 0fe69d73e..95b0aa07b 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0fe69d73e639372128d9480ec8cf65b182d36d30 +Subproject commit 95b0aa07b2bf3d2ab777f86d6ae8e256e94ced84 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index d2f40c7f0..68e9c22a2 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -158,6 +158,10 @@ typedef struct _meshtastic_AdminMessage { uint32_t set_favorite_node; /* Set specified node-num to be un-favorited on the NodeDB on the device */ uint32_t remove_favorite_node; + /* Set fixed position data on the node and then set the position.fixed_position = true */ + meshtastic_Position set_fixed_position; + /* Clear fixed position coordinates and then set position.fixed_position = false */ + bool remove_fixed_position; /* Begins an edit transaction for config, module config, owner, and channel settings changes This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */ bool begin_edit_settings; @@ -244,6 +248,8 @@ extern "C" { #define meshtastic_AdminMessage_remove_by_nodenum_tag 38 #define meshtastic_AdminMessage_set_favorite_node_tag 39 #define meshtastic_AdminMessage_remove_favorite_node_tag 40 +#define meshtastic_AdminMessage_set_fixed_position_tag 41 +#define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 @@ -285,6 +291,8 @@ X(a, STATIC, ONEOF, STRING, (payload_variant,set_ringtone_message,set_rin X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_by_nodenum,remove_by_nodenum), 38) \ X(a, STATIC, ONEOF, UINT32, (payload_variant,set_favorite_node,set_favorite_node), 39) \ X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_favorite_node,remove_favorite_node), 40) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed_position), 41) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ @@ -307,6 +315,7 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), #define meshtastic_AdminMessage_payload_variant_set_channel_MSGTYPE meshtastic_Channel #define meshtastic_AdminMessage_payload_variant_set_config_MSGTYPE meshtastic_Config #define meshtastic_AdminMessage_payload_variant_set_module_config_MSGTYPE meshtastic_ModuleConfig +#define meshtastic_AdminMessage_payload_variant_set_fixed_position_MSGTYPE meshtastic_Position #define meshtastic_HamParameters_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, call_sign, 1) \ From acc32916c3c7b6ac36a0c9d6c23ff1a448ab69d8 Mon Sep 17 00:00:00 2001 From: Jim Whitelaw Date: Mon, 25 Mar 2024 05:33:57 -0600 Subject: [PATCH 0172/1377] Add multiple configuration options for a minimized build (GPS,WiFi,BT,MQTT,Screen). (#3469) Co-authored-by: Ben Meadors --- .vscode/extensions.json | 2 +- src/ButtonThread.cpp | 8 +++-- src/Power.cpp | 4 ++- src/configuration.h | 54 ++++++++++++++++++++++++------- src/gps/GPS.cpp | 9 ++++-- src/gps/GPS.h | 5 ++- src/gps/NMEAWPL.cpp | 5 ++- src/graphics/Screen.cpp | 13 ++++++-- src/main.cpp | 37 +++++++++++++++------ src/main.h | 1 + src/mesh/Channels.cpp | 6 ++++ src/mesh/MeshService.cpp | 21 ++++++++---- src/mesh/MeshService.h | 6 ++-- src/mesh/NodeDB.cpp | 9 ++++-- src/mesh/PhoneAPI.cpp | 14 +++++--- src/mesh/Router.cpp | 11 ++++--- src/mesh/eth/ethClient.cpp | 6 +++- src/mesh/http/ContentHandler.cpp | 2 ++ src/mesh/http/WebServer.cpp | 4 +-- src/mesh/wifi/WiFiAPClient.cpp | 11 +++++-- src/mesh/wifi/WiFiAPClient.h | 2 +- src/modules/AdminModule.cpp | 8 +++-- src/modules/AdminModule.h | 2 +- src/modules/Modules.cpp | 12 ++++--- src/modules/PositionModule.cpp | 5 ++- src/modules/SerialModule.cpp | 9 +++--- src/mqtt/MQTT.cpp | 3 +- src/nimble/NimbleBluetooth.cpp | 9 ++++-- src/platform/esp32/main-esp32.cpp | 14 +++++--- src/platform/nrf52/main-nrf52.cpp | 5 ++- src/sleep.cpp | 16 ++++++--- 31 files changed, 226 insertions(+), 87 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4fc84fa78..783791f0b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,4 +6,4 @@ "platformio.platformio-ide", "trunk.io" ], -} +} \ No newline at end of file diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 84d433285..a1f0170e8 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -1,10 +1,12 @@ #include "ButtonThread.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif #include "MeshService.h" #include "PowerFSM.h" #include "RadioLibInterface.h" #include "buzz.h" -#include "graphics/Screen.h" #include "main.h" #include "modules/ExternalNotificationModule.h" #include "power.h" @@ -145,7 +147,7 @@ int32_t ButtonThread::runOnce() screen->print("Sent ad-hoc ping\n"); break; } - +#if HAS_GPS case BUTTON_EVENT_MULTI_PRESSED: { LOG_BUTTON("Multi press!\n"); if (!config.device.disable_triple_click && (gps != nullptr)) { @@ -155,7 +157,7 @@ int32_t ButtonThread::runOnce() } break; } - +#endif case BUTTON_EVENT_LONG_PRESSED: { LOG_BUTTON("Long press!\n"); powerFSM.trigger(EVENT_PRESS); diff --git a/src/Power.cpp b/src/Power.cpp index 71554daa3..779e32ff5 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -24,11 +24,13 @@ #include "nrfx_power.h" #endif -#ifdef DEBUG_HEAP_MQTT +#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #include "target_specific.h" +#if !MESTASTIC_EXCLUDE_WIFI #include #endif +#endif #ifndef DELAY_FOREVER #define DELAY_FOREVER portMAX_DELAY diff --git a/src/configuration.h b/src/configuration.h index c3dd2cb06..7ce1a0b8b 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -171,17 +171,6 @@ along with this program. If not, see . /* Step #3: mop up with disabled values for HAS_ options not handled by the above two */ -// ----------------------------------------------------------------------------- -// GPS -// ----------------------------------------------------------------------------- - -#ifndef GPS_BAUDRATE -#define GPS_BAUDRATE 9600 -#endif -#ifndef GPS_THREAD_INTERVAL -#define GPS_THREAD_INTERVAL 100 -#endif - #ifndef HAS_WIFI #define HAS_WIFI 0 #endif @@ -232,7 +221,21 @@ along with this program. If not, see . #error HW_VENDOR must be defined #endif -// global switch to turn off all optional modules for a minimzed build +// ----------------------------------------------------------------------------- +// Global switches to turn off features for a minimized build +// ----------------------------------------------------------------------------- + +// #define MESHTASTIC_MINIMIZE_BUILD 1 +#ifdef MESHTASTIC_MINIMIZE_BUILD +#define MESHTASTIC_EXCLUDE_MODULES 1 +#define MESHTASTIC_EXCLUDE_WIFI 1 +#define MESHTASTIC_EXCLUDE_BLUETOOTH 1 +#define MESHTASTIC_EXCLUDE_GPS 1 +#define MESHTASTIC_EXCLUDE_SCREEN 1 +#define MESHTASTIC_EXCLUDE_MQTT 1 +#endif + +// Turn off all optional modules #ifdef MESHTASTIC_EXCLUDE_MODULES #define MESHTASTIC_EXCLUDE_AUDIO 1 #define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1 @@ -251,3 +254,30 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_INPUTBROKER 1 #define MESHTASTIC_EXCLUDE_SERIAL 1 #endif + +// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) +#ifdef MESHTASTIC_EXCLUDE_WIFI +#define MESHTASTIC_EXCLUDE_WEBSERVER 1 +#undef HAS_WIFI +#define HAS_WIFI 0 +#endif + +// // Turn off Bluetooth +#ifdef MESHTASTIC_EXCLUDE_BLUETOOTH +#undef HAS_BLUETOOTH +#define HAS_BLUETOOTH 0 +#endif + +// // Turn off GPS +#ifdef MESHTASTIC_EXCLUDE_GPS +#undef HAS_GPS +#define HAS_GPS 0 +#undef MESHTASTIC_EXCLUDE_RANGETEST +#define MESHTASTIC_EXCLUDE_RANGETEST 1 +#endif + +// Turn off Screen +#ifdef MESHTASTIC_EXCLUDE_SCREEN +#undef HAS_SCREEN +#define HAS_SCREEN 0 +#endif diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index df1d40fdf..a6f68f2ef 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1,8 +1,10 @@ -#include "GPS.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "Default.h" +#include "GPS.h" #include "NodeDB.h" #include "RTC.h" -#include "configuration.h" + #include "main.h" // pmu_found #include "sleep.h" #include "ubx.h" @@ -1481,4 +1483,5 @@ void GPS::toggleGpsMode() LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n"); enable(); } -} \ No newline at end of file +} +#endif // Exclude GPS \ No newline at end of file diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 502763bb6..49f27e29f 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -1,4 +1,6 @@ #pragma once +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "GPSStatus.h" #include "Observer.h" @@ -268,4 +270,5 @@ class GPS : private concurrency::OSThread GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN; }; -extern GPS *gps; \ No newline at end of file +extern GPS *gps; +#endif // Exclude GPS \ No newline at end of file diff --git a/src/gps/NMEAWPL.cpp b/src/gps/NMEAWPL.cpp index 9eff4d00e..cdac3bb27 100644 --- a/src/gps/NMEAWPL.cpp +++ b/src/gps/NMEAWPL.cpp @@ -1,3 +1,4 @@ +#if !MESHTASTIC_EXCLUDE_GPS #include "NMEAWPL.h" #include "GeoCoord.h" #include "RTC.h" @@ -93,4 +94,6 @@ uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos) } len += snprintf(buf + len, bufsz - len, "*%02X\r\n", chk); return len; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3c3777496..2453faec9 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -25,7 +25,9 @@ along with this program. If not, see . #include #include "DisplayFormatters.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif #include "MeshService.h" #include "NodeDB.h" #include "error.h" @@ -92,8 +94,10 @@ std::vector moduleFrames; // Stores the last 4 of our hardware ID, to make finding the device for pairing easier static char ourId[5]; +#if HAS_GPS // GeoCoord object for the screen GeoCoord geoCoord; +#endif #ifdef SHOW_REDRAWS static bool heartbeat = false; @@ -483,7 +487,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat if (config.display.heading_bold) display->drawString(x + 11, y - 2, usersString); } - +#if HAS_GPS // Draw GPS status summary static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) { @@ -625,7 +629,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } } } - +#endif namespace { @@ -1542,6 +1546,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } else { drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); } +#if HAS_GPS // Display GPS status if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { drawGPSpowerstat(display, x, y + 2, gpsStatus); @@ -1552,7 +1557,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); } } - +#endif display->setColor(WHITE); // Draw the channel name display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); @@ -1771,6 +1776,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat char chUtil[13]; snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent()); display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil); +#if HAS_GPS if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { // Line 3 if (config.display.gps_format != @@ -1782,6 +1788,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat } else { drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); } +#endif /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS if (heartbeat) diff --git a/src/main.cpp b/src/main.cpp index f7d1a4bc0..0c45e903a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,7 @@ +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -6,7 +9,7 @@ #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" -#include "configuration.h" + #include "error.h" #include "power.h" // #include "debug.h" @@ -36,9 +39,11 @@ #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" #endif +#if !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" NimbleBluetooth *nimbleBluetooth; #endif +#endif #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" @@ -54,16 +59,21 @@ NRF52Bluetooth *nrf52Bluetooth; #include "mesh/api/ethServerAPI.h" #include "mesh/eth/ethClient.h" #endif + +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif #include "LLCC68Interface.h" #include "RF95Interface.h" #include "SX1262Interface.h" #include "SX1268Interface.h" #include "SX1280Interface.h" + #ifdef ARCH_STM32WL #include "STM32WLE5JCInterface.h" #endif + #if !HAS_RADIO && defined(ARCH_PORTDUINO) #include "platform/portduino/SimRadio.h" #endif @@ -80,6 +90,7 @@ NRF52Bluetooth *nrf52Bluetooth; #if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "ButtonThread.h" #endif + #include "PowerFSMThread.h" #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) @@ -640,16 +651,21 @@ void setup() readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) +#if !MESHTASTIC_EXCLUDE_GPS // If we're taking on the repeater role, ignore GPS - if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && - config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { - gps = GPS::createGps(); - } - if (gps) { - gpsStatus->observe(&gps->newStatus); - } else { - LOG_DEBUG("Running without GPS.\n"); + if (HAS_GPS) { + if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { + gps = GPS::createGps(); + if (gps) { + gpsStatus->observe(&gps->newStatus); + } else { + LOG_DEBUG("Running without GPS.\n"); + } + } } +#endif + nodeStatus->observe(&nodeDB->newStatus); #ifdef HAS_I2S @@ -852,9 +868,12 @@ void setup() } } +#if !MESHTASTIC_EXCLUDE_MQTT mqttInit(); +#endif #ifndef ARCH_PORTDUINO + // Initialize Wifi #if HAS_WIFI initWifi(); diff --git a/src/main.h b/src/main.h index 5af0b4082..132fd190b 100644 --- a/src/main.h +++ b/src/main.h @@ -54,6 +54,7 @@ extern int TCPPort; // set by Portduino // Global Screen singleton. extern graphics::Screen *screen; + // extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class // extern meshtastic::PowerStatus *powerStatus; diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 840e65bca..079af4eca 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -7,7 +7,9 @@ #include +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif /// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, @@ -18,7 +20,9 @@ Channels channels; const char *Channels::adminChannel = "admin"; const char *Channels::gpioChannel = "gpio"; const char *Channels::serialChannel = "serial"; +#if !MESHTASTIC_EXCLUDE_MQTT const char *Channels::mqttChannel = "mqtt"; +#endif uint8_t xorHash(const uint8_t *p, size_t len) { @@ -195,10 +199,12 @@ void Channels::onConfigChanged() if (ch.role == meshtastic_Channel_Role_PRIMARY) primaryIndex = i; } +#if !MESHTASTIC_EXCLUDE_MQTT if (channels.anyMqttEnabled() && mqtt && !mqtt->isEnabled()) { LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately\n"); mqtt->start(); } +#endif } meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 2df5d5797..2c1969e30 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -1,10 +1,11 @@ #include "configuration.h" -#include -#include + +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif #include "../concurrency/Periodic.h" #include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here -#include "GPS.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -15,8 +16,10 @@ #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" #include "power.h" +#include +#include -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" #endif @@ -71,10 +74,11 @@ MeshService::MeshService() void MeshService::init() { // moved much earlier in boot (called from setup()) - // nodeDB->init(); - + // nodeDB.init(); +#if HAS_GPS if (gps) gpsObserver.observe(&gps->newStatus); +#endif } int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) @@ -270,11 +274,13 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) assert(node); if (hasValidPosition(node)) { +#if HAS_GPS if (positionModule) { LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); positionModule->sendOurPosition(dest, wantReplies, node->channel); } } else { +#endif if (nodeInfoModule) { LOG_INFO("Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel); @@ -344,6 +350,7 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() return node; } +#if HAS_GPS int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) { // Update our local node info with our position (even if we don't decide to update anyone else) @@ -377,7 +384,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) return 0; } - +#endif bool MeshService::isToPhoneQueueEmpty() { return toPhoneQueue.isEmpty(); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 68287efc2..8d1434030 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -23,9 +23,10 @@ extern Allocator &mqttClientProxyMessagePool; */ class MeshService { +#if HAS_GPS CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); - +#endif /// received packets waiting for the phone to process them /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure /// we never hang because android hasn't been there in a while @@ -132,10 +133,11 @@ class MeshService ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); private: +#if HAS_GPS /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh /// returns 0 to allow further processing int onGPSChanged(const meshtastic::GPSStatus *arg); - +#endif /// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it /// needs to keep the packet around it makes a copy int handleFromRadio(const meshtastic_MeshPacket *p); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 3734309be..262b0e039 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1,11 +1,12 @@ #include "configuration.h" - +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif #include "../detect/ScanI2C.h" #include "Channels.h" #include "CryptoEngine.h" #include "Default.h" #include "FSCommon.h" -#include "GPS.h" #include "MeshRadio.h" #include "NodeDB.h" #include "PacketHistory.h" @@ -25,7 +26,9 @@ #include #ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" +#endif #include "modules/esp32/StoreForwardModule.h" #include #include @@ -230,7 +233,7 @@ void NodeDB::installDefaultConfig() config.has_position = true; config.has_power = true; config.has_network = true; - config.has_bluetooth = true; + config.has_bluetooth = (HAS_BLUETOOTH ? true : false); config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; config.lora.sx126x_rx_boosted_gain = true; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 48f7eb940..f2d2a6e9d 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -1,13 +1,16 @@ -#include "PhoneAPI.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif + #include "Channels.h" #include "Default.h" -#include "GPS.h" #include "MeshService.h" #include "NodeDB.h" +#include "PhoneAPI.h" #include "PowerFSM.h" #include "RadioInterface.h" #include "TypeConversions.h" -#include "configuration.h" #include "main.h" #include "xmodem.h" @@ -18,8 +21,9 @@ #if ToRadio_size > MAX_TO_FROM_RADIO_SIZE #error ToRadio is too big #endif - +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif PhoneAPI::PhoneAPI() { @@ -104,6 +108,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) LOG_INFO("Got xmodem packet\n"); xModem.handlePacket(toRadioScratch.xmodemPacket); break; +#if !MESHTASTIC_EXCLUDE_MQTT case meshtastic_ToRadio_mqttClientProxyMessage_tag: LOG_INFO("Got MqttClientProxy message\n"); if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled && @@ -114,6 +119,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) "not enabled\n"); } break; +#endif case meshtastic_ToRadio_heartbeat_tag: LOG_DEBUG("Got client heartbeat\n"); break; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 7894b1b92..266c4f78d 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -11,9 +11,9 @@ extern "C" { #include "mesh/compression/unishox2.h" } - +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" - +#endif /** * Router todo * @@ -261,11 +261,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) abortSendAndNak(encodeResult, p); return encodeResult; // FIXME - this isn't a valid ErrorCode } - +#if !MESHTASTIC_EXCLUDE_MQTT // Only publish to MQTT if we're the original transmitter of the packet if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) { mqtt->onSend(*p, *p_decoded, chIndex); } +#endif packetPool.release(p_decoded); } @@ -465,10 +466,12 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) cancelSending(p->from, p->id); skipHandle = true; } - +#if !MESHTASTIC_EXCLUDE_MQTT // Publish received message to MQTT if we're not the original transmitter of the packet if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); +#endif + } else { printPacket("packet decoding failed or skipped (no PSK?)", p); } diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 97f5027bd..5373f243e 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -2,9 +2,12 @@ #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" +#include "configuration.h" #include "main.h" #include "mesh/api/ethServerAPI.h" +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif #include "target_specific.h" #include #include @@ -66,11 +69,12 @@ static int32_t reconnectETH() ethStartupComplete = true; } - +#if !MESHTASTIC_EXCLUDE_MQTT // FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected' if (mqtt && !moduleConfig.mqtt.proxy_to_client_enabled && !mqtt->isConnectedDirectly()) { mqtt->reconnect(); } +#endif } #ifndef DISABLE_NTP diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 1557948d8..7f9df058d 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -6,7 +6,9 @@ #include "main.h" #include "mesh/http/ContentHelper.h" #include "mesh/http/WebServer.h" +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" +#endif #include "mqtt/JSON.h" #include "power.h" #include "sleep.h" diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 83fe20dd8..fc8535257 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -1,14 +1,14 @@ +#include "configuration.h" #if !MESHTASTIC_EXCLUDE_WEBSERVER -#include "mesh/http/WebServer.h" #include "NodeDB.h" #include "graphics/Screen.h" #include "main.h" +#include "mesh/http/WebServer.h" #include "mesh/wifi/WiFiAPClient.h" #include "sleep.h" #include #include #include - #include #include diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 88764d2be..1de4d7669 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -1,18 +1,24 @@ -#include "mesh/wifi/WiFiAPClient.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_WIFI #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" -#include "configuration.h" +#include "mesh/wifi/WiFiAPClient.h" + #include "main.h" #include "mesh/api/WiFiServerAPI.h" +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif #include "target_specific.h" #include #include #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" #endif +#endif #include #include static void WiFiEvent(WiFiEvent_t event); @@ -408,3 +414,4 @@ uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; } +#endif \ No newline at end of file diff --git a/src/mesh/wifi/WiFiAPClient.h b/src/mesh/wifi/WiFiAPClient.h index 6625d3e46..5f4e2f5c9 100644 --- a/src/mesh/wifi/WiFiAPClient.h +++ b/src/mesh/wifi/WiFiAPClient.h @@ -5,7 +5,7 @@ #include #include -#if defined(HAS_WIFI) && !defined(ARCH_PORTDUINO) +#if HAS_WIFI && !defined(ARCH_PORTDUINO) #include #endif diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index c44048fd2..50cec824d 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -4,7 +4,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "BleOta.h" #endif #include "Router.h" @@ -18,7 +18,9 @@ #endif #include "Default.h" +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif AdminModule *adminModule; bool hasOpenEditTransaction; @@ -119,7 +121,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_reboot_ota_seconds_tag: { int32_t s = r->reboot_ota_seconds; -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH if (BleOta::getOtaAppVersion().isEmpty()) { LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); screen->startRebootScreen(); @@ -666,7 +668,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r if (Ethernet.linkStatus() == LinkON) { conn.ethernet.status.is_connected = true; conn.ethernet.status.ip_address = Ethernet.localIP(); +#if !MESHTASTIC_EXCLUDE_MQTT conn.ethernet.status.is_mqtt_connected = mqtt && mqtt->isConnectedDirectly(); +#endif conn.ethernet.status.is_syslog_connected = false; // FIXME wire this up } else { conn.ethernet.status.is_connected = false; diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 6ecc88829..32b32c253 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,6 +1,6 @@ #pragma once #include "ProtobufModule.h" -#if HAS_WIFI +#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 97ed90cf1..5ac45577e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -21,7 +21,9 @@ #include "modules/NeighborInfoModule.h" #endif #include "modules/NodeInfoModule.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "modules/PositionModule.h" +#endif #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE #include "modules/RemoteHardwareModule.h" #endif @@ -61,15 +63,13 @@ #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #include "modules/ExternalNotificationModule.h" #endif -#if !MESHTASTIC_EXCLUDE_RANGETEST +#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS #include "modules/RangeTestModule.h" #endif -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) -#if !MESHTASTIC_EXCLUDE_SERIAL +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL #include "modules/SerialModule.h" #endif #endif -#endif /** * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) */ @@ -81,7 +81,9 @@ void setupModules() #endif adminModule = new AdminModule(); nodeInfoModule = new NodeInfoModule(); +#if !MESHTASTIC_EXCLUDE_GPS positionModule = new PositionModule(); +#endif #if !MESHTASTIC_EXCLUDE_WAYPOINT waypointModule = new WaypointModule(); #endif @@ -169,7 +171,7 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION externalNotificationModule = new ExternalNotificationModule(); #endif -#if !MESHTASTIC_EXCLUDE_RANGETEST +#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS new RangeTestModule(); #endif #endif diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index d22c6b699..dcfe03f7f 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -1,3 +1,4 @@ +#if !MESHTASTIC_EXCLUDE_GPS #include "PositionModule.h" #include "Default.h" #include "GPS.h" @@ -418,4 +419,6 @@ void PositionModule::handleNewPosition() lastGpsSend = now; } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 663bc1d86..96a99b13e 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -179,14 +179,14 @@ int32_t SerialModule::runOnce() } else { if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO) { return runOncePart(); - } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) { + } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) && HAS_GPS) { // in NMEA mode send out GGA every 2 seconds, Don't read from Port if (millis() - lastNmeaTime > 2000) { lastNmeaTime = millis(); printGGA(outbuf, sizeof(outbuf), localPosition); serialPrint->printf("%s", outbuf); } - } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) { + } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && HAS_GPS) { if (millis() - lastNmeaTime > 10000) { lastNmeaTime = millis(); uint32_t readIndex = 0; @@ -295,8 +295,9 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp serialPrint->println(); serialPrint->printf("%s: %s", sender, p.payload.bytes); serialPrint->println(); - } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA || - moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) { + } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA || + moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && + HAS_GPS) { // Decode the Payload some more meshtastic_Position scratch; meshtastic_Position *decoded = NULL; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 8e7c8f2cc..05d5486b2 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -2,6 +2,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "configuration.h" #include "main.h" #include "mesh/Channels.h" #include "mesh/Router.h" @@ -13,7 +14,7 @@ #endif #include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "sleep.h" -#if HAS_WIFI +#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #include #endif diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 0b2a806c9..bc94abf6e 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -1,7 +1,9 @@ -#include "NimbleBluetooth.h" -#include "BluetoothCommon.h" -#include "PowerFSM.h" #include "configuration.h" +#if !MESHTASTIC_EXCLUDE_BLUETOOTH +#include "BluetoothCommon.h" +#include "NimbleBluetooth.h" +#include "PowerFSM.h" + #include "main.h" #include "mesh/PhoneAPI.h" #include "mesh/mesh-pb-constants.h" @@ -227,3 +229,4 @@ void clearNVS() ESP.restart(); #endif } +#endif \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index f97f6d121..3fb6e7774 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -3,11 +3,14 @@ #include "esp_task_wdt.h" #include "main.h" -#if !defined(CONFIG_IDF_TARGET_ESP32S2) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH +#include "BleOta.h" #include "nimble/NimbleBluetooth.h" #endif -#include "BleOta.h" + +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" +#endif #include "meshUtils.h" #include "sleep.h" @@ -18,8 +21,7 @@ #include #include -#if !defined(CONFIG_IDF_TARGET_ESP32S2) - +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { if (!isWifiAvailable() && config.bluetooth.enabled == true) { @@ -108,12 +110,16 @@ void esp32Setup() preferences.putUInt("rebootCounter", rebootCounter); preferences.end(); LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); +#if !MESHTASTIC_EXCLUDE_BLUETOOTH String BLEOTA = BleOta::getOtaAppVersion(); if (BLEOTA.isEmpty()) { LOG_DEBUG("No OTA firmware available\n"); } else { LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); } +#else + LOG_DEBUG("No OTA firmware available\n"); +#endif // enableModemSleep(); diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 2f670dee3..ecffb745d 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -65,6 +65,7 @@ static void initBrownout() static const bool useSoftDevice = true; // Set to false for easier debugging +#if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { if (enable && config.bluetooth.enabled) { @@ -88,7 +89,9 @@ void setBluetoothEnable(bool enable) } } } - +#else +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) */ diff --git a/src/sleep.cpp b/src/sleep.cpp index f170e2ab7..2f4bd09e1 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -1,17 +1,23 @@ -#include "sleep.h" +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif + #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" -#include "configuration.h" #include "error.h" #include "main.h" +#include "sleep.h" #include "target_specific.h" #ifdef ARCH_ESP32 #include "esp32/pm.h" #include "esp_pm.h" +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" +#endif #include "rom/rtc.h" #include #include @@ -48,7 +54,7 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI if (isWifiAvailable()) { /* @@ -206,11 +212,11 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif #endif - +#if HAS_GPS // Kill GPS power completely (even if previously we just had it in sleep mode) if (gps) gps->setGPSPower(false, false, 0); - +#endif setLed(false); #ifdef RESET_OLED From 1542afb84766e094031323d8cd37f773e047e53c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 26 Mar 2024 00:58:47 -0500 Subject: [PATCH 0173/1377] Add libulfius2.7 to .deb debendencies (#3494) --- .github/workflows/package_raspbian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 377074e95..dd4133dab 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -63,7 +63,7 @@ jobs: maintainer: Jonathan Bennett version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* arch: arm64 - depends: libyaml-cpp0.7, openssl + depends: libyaml-cpp0.7, openssl, libulfius2.7 desc: Native Linux Meshtastic binary. - uses: actions/upload-artifact@v3 From 5732eed86bc67f783c53ba02348b9fc3c831f16a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 26 Mar 2024 07:29:07 -0500 Subject: [PATCH 0174/1377] Fixed position admin messages (#3490) * Bespoke admin messages for setting and clearing fixed positions * Add guards against remote admin messages setting things * Flip ifs --- src/modules/AdminModule.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 50cec824d..2c04916dd 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -17,6 +17,7 @@ #include "unistd.h" #endif #include "Default.h" +#include "TypeConversions.h" #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" @@ -205,6 +206,31 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } break; } + case meshtastic_AdminMessage_set_fixed_position_tag: { + if (fromOthers) { + LOG_INFO("Ignoring set_fixed_position command from another node.\n"); + } else { + LOG_INFO("Client is receiving a set_fixed_position command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); + node->has_position = true; + node->position = TypeConversions::ConvertToPositionLite(r->set_fixed_position); + nodeDB->setLocalPosition(r->set_fixed_position); + config.position.fixed_position = true; + saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); + } + break; + } + case meshtastic_AdminMessage_remove_fixed_position_tag: { + if (fromOthers) { + LOG_INFO("Ignoring remove_fixed_position command from another node.\n"); + } else { + LOG_INFO("Client is receiving a remove_fixed_position command.\n"); + nodeDB->clearLocalPosition(); + config.position.fixed_position = false; + saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); + } + break; + } case meshtastic_AdminMessage_enter_dfu_mode_request_tag: { LOG_INFO("Client is requesting to enter DFU mode.\n"); #if defined(ARCH_NRF52) || defined(ARCH_RP2040) From b5ec35ec78419536d213df5d289f498234f80576 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 06:44:34 -0500 Subject: [PATCH 0175/1377] [create-pull-request] automated change (#3502) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 1 - .../generated/meshtastic/deviceonly.pb.cpp | 3 - src/mesh/generated/meshtastic/deviceonly.pb.h | 128 +++++++----------- src/mesh/generated/meshtastic/mesh.pb.cpp | 3 + src/mesh/generated/meshtastic/mesh.pb.h | 24 ++++ 6 files changed, 80 insertions(+), 81 deletions(-) diff --git a/protobufs b/protobufs index 95b0aa07b..dea3a82ef 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 95b0aa07b2bf3d2ab777f86d6ae8e256e94ced84 +Subproject commit dea3a82ef2accd25112b4ef1c6f8991b579740f4 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 68e9c22a2..f0d4e81b6 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -7,7 +7,6 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/config.pb.h" #include "meshtastic/connection_status.pb.h" -#include "meshtastic/deviceonly.pb.h" #include "meshtastic/mesh.pb.h" #include "meshtastic/module_config.pb.h" diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 93ab3dd98..a9925b517 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -21,8 +21,5 @@ PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2) PB_BIND(meshtastic_OEMStore, meshtastic_OEMStore, 2) -PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO) - - diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index c65a5764f..18617390a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -27,6 +27,48 @@ typedef enum _meshtastic_ScreenFonts { } meshtastic_ScreenFonts; /* Struct definitions */ +/* This message is never sent over the wire, but it is used for serializing DB + state to flash in the device code + FIXME, since we write this each time we enter deep sleep (and have infinite + flash) it would be better to use some sort of append only data structure for + the receive queue and use the preferences store for the other stuff */ +typedef struct _meshtastic_DeviceState { + /* Read only settings/info about this node */ + bool has_my_node; + meshtastic_MyNodeInfo my_node; + /* My owner info */ + bool has_owner; + meshtastic_User owner; + /* Received packets saved for delivery to the phone */ + pb_size_t receive_queue_count; + meshtastic_MeshPacket receive_queue[1]; + /* We keep the last received text message (only) stored in the device flash, + so we can show it on the screen. + Might be null */ + bool has_rx_text_message; + meshtastic_MeshPacket rx_text_message; + /* A version integer used to invalidate old save files when we make + incompatible changes This integer is set at build time and is private to + NodeDB.cpp in the device code. */ + uint32_t version; + /* Used only during development. + Indicates developer is testing and changes should never be saved to flash. + Deprecated in 2.3.1 */ + bool no_save; + /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */ + bool did_gps_reset; + /* We keep the last received waypoint stored in the device flash, + so we can show it on the screen. + Might be null */ + bool has_rx_waypoint; + meshtastic_MeshPacket rx_waypoint; + /* The mesh's nodes with their available gpio pins for RemoteHardware module */ + pb_size_t node_remote_hardware_pins_count; + meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; + /* New lite version of NodeDB to decrease memory footprint */ + std::vector node_db_lite; +} meshtastic_DeviceState; + /* Position with static location information only for NodeDBLite */ typedef struct _meshtastic_PositionLite { /* The new preferred location encoding, multiply by 1e-7 to get degrees @@ -111,57 +153,6 @@ typedef struct _meshtastic_OEMStore { meshtastic_LocalModuleConfig oem_local_module_config; } meshtastic_OEMStore; -/* RemoteHardwarePins associated with a node */ -typedef struct _meshtastic_NodeRemoteHardwarePin { - /* The node_num exposing the available gpio pin */ - uint32_t node_num; - /* The the available gpio pin for usage with RemoteHardware module */ - bool has_pin; - meshtastic_RemoteHardwarePin pin; -} meshtastic_NodeRemoteHardwarePin; - -/* This message is never sent over the wire, but it is used for serializing DB - state to flash in the device code - FIXME, since we write this each time we enter deep sleep (and have infinite - flash) it would be better to use some sort of append only data structure for - the receive queue and use the preferences store for the other stuff */ -typedef struct _meshtastic_DeviceState { - /* Read only settings/info about this node */ - bool has_my_node; - meshtastic_MyNodeInfo my_node; - /* My owner info */ - bool has_owner; - meshtastic_User owner; - /* Received packets saved for delivery to the phone */ - pb_size_t receive_queue_count; - meshtastic_MeshPacket receive_queue[1]; - /* We keep the last received text message (only) stored in the device flash, - so we can show it on the screen. - Might be null */ - bool has_rx_text_message; - meshtastic_MeshPacket rx_text_message; - /* A version integer used to invalidate old save files when we make - incompatible changes This integer is set at build time and is private to - NodeDB.cpp in the device code. */ - uint32_t version; - /* Used only during development. - Indicates developer is testing and changes should never be saved to flash. - Deprecated in 2.3.1 */ - bool no_save; - /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */ - bool did_gps_reset; - /* We keep the last received waypoint stored in the device flash, - so we can show it on the screen. - Might be null */ - bool has_rx_waypoint; - meshtastic_MeshPacket rx_waypoint; - /* The mesh's nodes with their available gpio pins for RemoteHardware module */ - pb_size_t node_remote_hardware_pins_count; - meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; - /* New lite version of NodeDB to decrease memory footprint */ - std::vector node_db_lite; -} meshtastic_DeviceState; - #ifdef __cplusplus extern "C" { @@ -180,22 +171,29 @@ extern "C" { #define meshtastic_OEMStore_oem_font_ENUMTYPE meshtastic_ScreenFonts - /* Initializer values for message structs */ #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {{NULL}, NULL}} #define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} -#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {{NULL}, NULL}} #define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} -#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero} /* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_DeviceState_my_node_tag 2 +#define meshtastic_DeviceState_owner_tag 3 +#define meshtastic_DeviceState_receive_queue_tag 5 +#define meshtastic_DeviceState_rx_text_message_tag 7 +#define meshtastic_DeviceState_version_tag 8 +#define meshtastic_DeviceState_no_save_tag 9 +#define meshtastic_DeviceState_did_gps_reset_tag 11 +#define meshtastic_DeviceState_rx_waypoint_tag 12 +#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 +#define meshtastic_DeviceState_node_db_lite_tag 14 #define meshtastic_PositionLite_latitude_i_tag 1 #define meshtastic_PositionLite_longitude_i_tag 2 #define meshtastic_PositionLite_altitude_tag 3 @@ -221,18 +219,6 @@ extern "C" { #define meshtastic_OEMStore_oem_aes_key_tag 6 #define meshtastic_OEMStore_oem_local_config_tag 7 #define meshtastic_OEMStore_oem_local_module_config_tag 8 -#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1 -#define meshtastic_NodeRemoteHardwarePin_pin_tag 2 -#define meshtastic_DeviceState_my_node_tag 2 -#define meshtastic_DeviceState_owner_tag 3 -#define meshtastic_DeviceState_receive_queue_tag 5 -#define meshtastic_DeviceState_rx_text_message_tag 7 -#define meshtastic_DeviceState_version_tag 8 -#define meshtastic_DeviceState_no_save_tag 9 -#define meshtastic_DeviceState_did_gps_reset_tag 11 -#define meshtastic_DeviceState_rx_waypoint_tag 12 -#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 -#define meshtastic_DeviceState_node_db_lite_tag 14 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceState_FIELDLIST(X, a) \ @@ -304,19 +290,11 @@ X(a, STATIC, OPTIONAL, MESSAGE, oem_local_module_config, 8) #define meshtastic_OEMStore_oem_local_config_MSGTYPE meshtastic_LocalConfig #define meshtastic_OEMStore_oem_local_module_config_MSGTYPE meshtastic_LocalModuleConfig -#define meshtastic_NodeRemoteHardwarePin_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, node_num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, pin, 2) -#define meshtastic_NodeRemoteHardwarePin_CALLBACK NULL -#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL -#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin - extern const pb_msgdesc_t meshtastic_DeviceState_msg; extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_PositionLite_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; extern const pb_msgdesc_t meshtastic_OEMStore_msg; -extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg @@ -324,13 +302,11 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_PositionLite_fields &meshtastic_PositionLite_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg #define meshtastic_OEMStore_fields &meshtastic_OEMStore_msg -#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceState_size depends on runtime parameters */ #define meshtastic_ChannelFile_size 702 #define meshtastic_NodeInfoLite_size 160 -#define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_OEMStore_size 3278 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 97bb7e53b..39713ae8d 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -63,6 +63,9 @@ PB_BIND(meshtastic_DeviceMetadata, meshtastic_DeviceMetadata, AUTO) PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO) +PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 5804dd42a..fcefe508b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -826,6 +826,15 @@ typedef struct _meshtastic_ToRadio { }; } meshtastic_ToRadio; +/* RemoteHardwarePins associated with a node */ +typedef struct _meshtastic_NodeRemoteHardwarePin { + /* The node_num exposing the available gpio pin */ + uint32_t node_num; + /* The the available gpio pin for usage with RemoteHardware module */ + bool has_pin; + meshtastic_RemoteHardwarePin pin; +} meshtastic_NodeRemoteHardwarePin; + #ifdef __cplusplus extern "C" { @@ -900,6 +909,7 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} @@ -920,6 +930,7 @@ extern "C" { #define meshtastic_Neighbor_init_default {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} #define meshtastic_Heartbeat_init_default {0} +#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} #define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} @@ -939,6 +950,7 @@ extern "C" { #define meshtastic_Neighbor_init_zero {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} #define meshtastic_Heartbeat_init_zero {0} +#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Position_latitude_i_tag 1 @@ -1071,6 +1083,8 @@ extern "C" { #define meshtastic_ToRadio_xmodemPacket_tag 5 #define meshtastic_ToRadio_mqttClientProxyMessage_tag 6 #define meshtastic_ToRadio_heartbeat_tag 7 +#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1 +#define meshtastic_NodeRemoteHardwarePin_pin_tag 2 /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ @@ -1302,6 +1316,13 @@ X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) #define meshtastic_Heartbeat_CALLBACK NULL #define meshtastic_Heartbeat_DEFAULT NULL +#define meshtastic_NodeRemoteHardwarePin_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, node_num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, pin, 2) +#define meshtastic_NodeRemoteHardwarePin_CALLBACK NULL +#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL +#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin + extern const pb_msgdesc_t meshtastic_Position_msg; extern const pb_msgdesc_t meshtastic_User_msg; extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; @@ -1321,6 +1342,7 @@ extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; extern const pb_msgdesc_t meshtastic_Neighbor_msg; extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; extern const pb_msgdesc_t meshtastic_Heartbeat_msg; +extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Position_fields &meshtastic_Position_msg @@ -1342,6 +1364,7 @@ extern const pb_msgdesc_t meshtastic_Heartbeat_msg; #define meshtastic_Neighbor_fields &meshtastic_Neighbor_msg #define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg #define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg +#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ #define meshtastic_Compressed_size 243 @@ -1356,6 +1379,7 @@ extern const pb_msgdesc_t meshtastic_Heartbeat_msg; #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 #define meshtastic_NodeInfo_size 277 +#define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 From 4c2d5c6a8956f9d7447503a407a18c4cd26c3b3f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 28 Mar 2024 07:16:07 -0500 Subject: [PATCH 0176/1377] Reorder structs to fix build --- src/mesh/generated/meshtastic/deviceonly.pb.h | 95 ++++++++++--------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 18617390a..c75f35c04 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -15,6 +15,54 @@ #error Regenerate this file with the current version of nanopb generator. #endif +/* Position with static location information only for NodeDBLite */ +typedef struct _meshtastic_PositionLite { + /* The new preferred location encoding, multiply by 1e-7 to get degrees + in floating point */ + int32_t latitude_i; + /* TODO: REPLACE */ + int32_t longitude_i; + /* In meters above MSL (but see issue #359) */ + int32_t altitude; + /* This is usually not sent over the mesh (to save space), but it is sent + from the phone so that the local device can set its RTC If it is sent over + the mesh (because there are devices on the mesh without GPS), it will only + be sent by devices which has a hardware GPS clock. + seconds since 1970 */ + uint32_t time; + /* TODO: REPLACE */ + meshtastic_Position_LocSource location_source; +} meshtastic_PositionLite; + +typedef struct _meshtastic_NodeInfoLite { + /* The node number */ + uint32_t num; + /* The user info for this node */ + bool has_user; + meshtastic_User user; + /* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. + Position.time now indicates the last time we received a POSITION from that node. */ + bool has_position; + meshtastic_PositionLite position; + /* Returns the Signal-to-noise ratio (SNR) of the last received message, + as measured by the receiver. Return SNR of the last received message in dB */ + float snr; + /* Set to indicate the last time we received a packet from this node */ + uint32_t last_heard; + /* The latest device metrics for the node. */ + bool has_device_metrics; + meshtastic_DeviceMetrics device_metrics; + /* local channel index we heard that node on. Only populated if its not the default channel. */ + uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; + /* True if node is in our favorites list + Persists between NodeDB internal clean ups */ + bool is_favorite; +} meshtastic_NodeInfoLite; + /* Enum definitions */ /* TODO: REPLACE */ typedef enum _meshtastic_ScreenFonts { @@ -69,53 +117,6 @@ typedef struct _meshtastic_DeviceState { std::vector node_db_lite; } meshtastic_DeviceState; -/* Position with static location information only for NodeDBLite */ -typedef struct _meshtastic_PositionLite { - /* The new preferred location encoding, multiply by 1e-7 to get degrees - in floating point */ - int32_t latitude_i; - /* TODO: REPLACE */ - int32_t longitude_i; - /* In meters above MSL (but see issue #359) */ - int32_t altitude; - /* This is usually not sent over the mesh (to save space), but it is sent - from the phone so that the local device can set its RTC If it is sent over - the mesh (because there are devices on the mesh without GPS), it will only - be sent by devices which has a hardware GPS clock. - seconds since 1970 */ - uint32_t time; - /* TODO: REPLACE */ - meshtastic_Position_LocSource location_source; -} meshtastic_PositionLite; - -typedef struct _meshtastic_NodeInfoLite { - /* The node number */ - uint32_t num; - /* The user info for this node */ - bool has_user; - meshtastic_User user; - /* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. - Position.time now indicates the last time we received a POSITION from that node. */ - bool has_position; - meshtastic_PositionLite position; - /* Returns the Signal-to-noise ratio (SNR) of the last received message, - as measured by the receiver. Return SNR of the last received message in dB */ - float snr; - /* Set to indicate the last time we received a packet from this node */ - uint32_t last_heard; - /* The latest device metrics for the node. */ - bool has_device_metrics; - meshtastic_DeviceMetrics device_metrics; - /* local channel index we heard that node on. Only populated if its not the default channel. */ - uint8_t channel; - /* True if we witnessed the node over MQTT instead of LoRA transport */ - bool via_mqtt; - /* Number of hops away from us this node is (0 if adjacent) */ - uint8_t hops_away; - /* True if node is in our favorites list - Persists between NodeDB internal clean ups */ - bool is_favorite; -} meshtastic_NodeInfoLite; /* The on-disk saved channels */ typedef struct _meshtastic_ChannelFile { From daa4d387c605295d1c161d7eab01e204095c9519 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 28 Mar 2024 18:14:15 -0500 Subject: [PATCH 0177/1377] Don't reboot for non-radio lora config changes (#3505) --- src/PowerFSM.cpp | 1 - src/modules/AdminModule.cpp | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 5d86987df..b6e267e28 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -246,7 +246,6 @@ Fsm powerFSM(&stateBOOT); void PowerFSM_setup() { bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); - bool isInfrastructureRole = isRouter || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER; bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 2c04916dd..b40633af0 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -336,6 +336,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) auto changes = SEGMENT_CONFIG; auto existingRole = config.device.role; bool isRegionUnset = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + bool requiresReboot = true; switch (c.which_payload_variant) { case meshtastic_Config_device_tag: @@ -375,7 +376,21 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_lora_tag: LOG_INFO("Setting config: LoRa\n"); config.has_lora = true; + // If no lora radio parameters change, don't need to reboot + if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region && + config.lora.modem_preset == c.payload_variant.lora.modem_preset && + config.lora.bandwidth == c.payload_variant.lora.bandwidth && + config.lora.spread_factor == c.payload_variant.lora.spread_factor && + config.lora.coding_rate == c.payload_variant.lora.coding_rate && + config.lora.tx_power == c.payload_variant.lora.tx_power && + config.lora.frequency_offset == c.payload_variant.lora.frequency_offset && + config.lora.override_frequency == c.payload_variant.lora.override_frequency && + config.lora.channel_num == c.payload_variant.lora.channel_num && + config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) { + requiresReboot = false; + } config.lora = c.payload_variant.lora; + // If we're setting region for the first time, init the region if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { config.lora.tx_enabled = true; initRegion(); @@ -395,7 +410,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) break; } - saveChanges(changes); + saveChanges(changes, requiresReboot); } void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) From 8187fa7115c928e32417a056bf10efd86f512395 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 29 Mar 2024 12:31:11 +1300 Subject: [PATCH 0178/1377] E-Ink Screensaver (#3477) * fix Wireless Paper double-clear screen at boot * log when flooded with "responsive" frames * show the "resuming" screen when waking from deep-sleep * rename drawDeepSleepScreen avoid future confusion with "Screen Paused" screen * show a screensaver frame when screen off The frame shown during deep sleep is now also passed through showScreensaverFrames() * Add macros for E-Ink color values. OLEDDISPLAY_COLOR is inverted. Result of light-mode on E-Ink vs dark-mode on OLED? * adapt drawDeepSleepScreen to new screensaver convention * Mark Wireless Paper V1.1 as having problems with ghosting Any other issues can be marked in a similar way, then handled in code where relevant * Change screensaver from fullscreen logo to overlay * identify "quirks" rather than "problems" * move async refresh polling from display() to a NotifiedWorkerThread * Prevent skipping of deep-sleep screen (Hopefully) * Redesign screensaver overlay Now displays short name * Optimize refresh for different displays * Support older EInkDisplay class * Don't assume text alignment * fix spelling of a quirk macro (No impact to code, but avoids future issues) * Handle impossibly unlikely millis() overflow error Should have just let it go, but here we are.. --------- Co-authored-by: Ben Meadors --- src/graphics/EInkDisplay2.cpp | 1 - src/graphics/EInkDynamicDisplay.cpp | 62 ++++++--- src/graphics/EInkDynamicDisplay.h | 35 +++-- src/graphics/Screen.cpp | 125 ++++++++++++++++-- src/graphics/Screen.h | 19 ++- variants/heltec_wireless_paper/platformio.ini | 2 + .../heltec_wireless_paper_v1/platformio.ini | 3 +- 7 files changed, 204 insertions(+), 43 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 6f7885b45..0c5fab4fb 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -184,7 +184,6 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); - adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh) } #elif defined(PCA10059) { diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 732f6d3fb..f61cf891e 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -5,7 +5,7 @@ // Constructor EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) - : EInkDisplay(address, sda, scl, geometry, i2cBus) + : EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay") { // If tracking ghost pixels, grab memory #ifdef EINK_LIMIT_GHOSTING_PX @@ -112,12 +112,15 @@ void EInkDynamicDisplay::endOrDetach() // If the GxEPD2 version reports that it has the async modifications #ifdef HAS_EINK_ASYNCFULL if (previousRefresh == FULL) { - asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. + asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify() if (previousFrameFlags & BLOCKING) awaitRefresh(); - else - LOG_DEBUG("Async full-refresh begins\n"); + else { + // Async begins + LOG_DEBUG("Async full-refresh begins (dropping frames)\n"); + notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread + } } // Fast Refresh @@ -141,7 +144,7 @@ bool EInkDynamicDisplay::determineMode() checkInitialized(); checkForPromotion(); #if defined(HAS_EINK_ASYNCFULL) - checkAsyncFullRefresh(); + checkBusyAsyncRefresh(); #endif checkRateLimiting(); @@ -252,6 +255,7 @@ void EInkDynamicDisplay::checkRateLimiting() if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) { refresh = SKIPPED; reason = EXCEEDED_RATELIMIT_FAST; + LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags); return; } } @@ -447,9 +451,44 @@ void EInkDynamicDisplay::resetGhostPixelTracking() } #endif // EINK_LIMIT_GHOSTING_PX +// Handle any asyc tasks +void EInkDynamicDisplay::onNotify(uint32_t notification) +{ + // Which task + switch (notification) { + case DUE_POLL_ASYNCREFRESH: + pollAsyncRefresh(); + break; + } +} + #ifdef HAS_EINK_ASYNCFULL -// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready -void EInkDynamicDisplay::checkAsyncFullRefresh() +// Run the post-update code if the hardware is ready +void EInkDynamicDisplay::pollAsyncRefresh() +{ + // We shouldn't be here.. + if (!asyncRefreshRunning) + return; + + // Still running, check back later + if (adafruitDisplay->epd2.isBusy()) { + // Schedule next call of pollAsyncRefresh() + NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); + return; + } + + // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag + LOG_DEBUG("Async full-refresh complete\n"); + + // Note: this code only works because of a modification to meshtastic/GxEPD2. + // It is only equipped to intercept calls to nextPage() +} + +// Check the status of "async full-refresh"; skip if running +void EInkDynamicDisplay::checkBusyAsyncRefresh() { // No refresh taking place, continue with determineMode() if (!asyncRefreshRunning) @@ -472,15 +511,6 @@ void EInkDynamicDisplay::checkAsyncFullRefresh() return; } - - // If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done - adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code - EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) - asyncRefreshRunning = false; // Unset the flag - LOG_DEBUG("Async full-refresh complete\n"); - - // Note: this code only works because of a modification to meshtastic/GxEPD2. - // It is only equipped to intercept calls to nextPage() } // Hold control while an async refresh runs diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 81963df58..39953b62a 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -6,6 +6,7 @@ #include "EInkDisplay2.h" #include "GxEPD2_BW.h" +#include "concurrency/NotifiedWorkerThread.h" /* Derives from the EInkDisplay adapter class. @@ -14,7 +15,7 @@ (Full, Fast, Skip) */ -class EInkDynamicDisplay : public EInkDisplay +class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread { public: // Constructor @@ -61,13 +62,20 @@ class EInkDynamicDisplay : public EInkDisplay REDRAW_WITH_FULL, }; - void configForFastRefresh(); // GxEPD2 code to set fast-refresh - void configForFullRefresh(); // GxEPD2 code to set full-refresh - bool determineMode(); // Assess situation, pick a refresh type - void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type - void adjustRefreshCounters(); // Update fastRefreshCount - bool update(); // Trigger the display update - determine mode, then call base class - void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() + enum notificationTypes : uint8_t { // What was onNotify() called for + NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class + DUE_POLL_ASYNCREFRESH = 1, + }; + const uint32_t intervalPollAsyncRefresh = 100; + + void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread + void configForFastRefresh(); // GxEPD2 code to set fast-refresh + void configForFullRefresh(); // GxEPD2 code to set full-refresh + bool determineMode(); // Assess situation, pick a refresh type + void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type + void adjustRefreshCounters(); // Update fastRefreshCount + bool update(); // Trigger the display update - determine mode, then call base class + void endOrDetach(); // Run the post-update code, or delegate it off to checkBusyAsyncRefresh() // Checks as part of determineMode() void checkInitialized(); // Is this the very first frame? @@ -111,10 +119,13 @@ class EInkDynamicDisplay : public EInkDisplay // Conditional - async full refresh - only with modified meshtastic/GxEPD2 #if defined(HAS_EINK_ASYNCFULL) - void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready - void awaitRefresh(); // Hold control while an async refresh runs - void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() - bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() + void pollAsyncRefresh(); // Run the post-update code if the hardware is ready + void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames) + void awaitRefresh(); // Hold control while an async refresh runs + void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() + bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh() +#else + void pollAsyncRefresh() {} // Dummy method. In theory, not reachable #endif }; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 2453faec9..52829d1f7 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -262,14 +262,65 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i #ifdef USE_EINK /// Used on eink displays while in deep sleep -static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // Next frame should use full-refresh, and block while running, else device will sleep before async callback EINK_ADD_FRAMEFLAG(display, COSMETIC); EINK_ADD_FRAMEFLAG(display, BLOCKING); + LOG_DEBUG("Drawing deep sleep screen\n"); drawIconScreen("Sleeping...", display, state, x, y); } + +/// Used on eink displays when screen updates are paused +static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + LOG_DEBUG("Drawing screensaver overlay\n"); + + EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh + + // Config + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *pauseText = "Screen Paused"; + const char *idText = owner.short_name; + constexpr uint16_t padding = 5; + constexpr uint8_t dividerGap = 1; + constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in. + + // Dimensions + const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText)); + const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText)); + const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding; + const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding; + + // Position + const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1); + // const int16_t boxRight = boxLeft + boxWidth - 1; + const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1)); + const int16_t boxBottom = boxTop + boxHeight - 1; + const int16_t idTextLeft = boxLeft + padding; + const int16_t idTextTop = boxTop + padding; + const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding; + const int16_t pauseTextTop = boxTop + padding; + const int16_t dividerX = boxLeft + padding + idTextWidth + padding; + const int16_t dividerTop = boxTop + 1 + dividerGap; + const int16_t dividerBottom = boxBottom - 1 - dividerGap; + + // Draw: box + display->setColor(EINK_WHITE); + display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box + display->setColor(EINK_BLACK); + display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); + + // Draw: Text + display->drawString(idTextLeft, idTextTop, idText); + display->drawString(pauseTextLeft, pauseTextTop, pauseText); + display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold + + // Draw: divider + display->drawLine(dividerX, dividerTop, dividerX, dividerBottom); +} #endif static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -948,18 +999,17 @@ Screen::~Screen() void Screen::doDeepSleep() { #ifdef USE_EINK - static FrameCallback sleepFrames[] = {drawSleepScreen}; - static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); - ui->setFrames(sleepFrames, sleepFrameCount); - ui->update(); + setOn(false, drawDeepSleepScreen); #ifdef PIN_EINK_EN digitalWrite(PIN_EINK_EN, LOW); // power off backlight #endif -#endif +#else + // Without E-Ink display: setOn(false); +#endif } -void Screen::handleSetOn(bool on) +void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) { if (!useDisplay) return; @@ -978,6 +1028,10 @@ void Screen::handleSetOn(bool on) setInterval(0); // Draw ASAP runASAP = true; } else { +#ifdef USE_EINK + // eInkScreensaver parameter is usually NULL (default argument), default frame used instead + setScreensaverFrames(einkScreensaver); +#endif LOG_INFO("Turning off screen\n"); dispdev->displayOff(); #ifdef T_WATCH_S3 @@ -1028,6 +1082,7 @@ void Screen::setup() logo_timeout *= 2; // Add frames. + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); static FrameCallback bootFrames[] = {drawBootScreen}; static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); ui->setFrames(bootFrames, bootFrameCount); @@ -1283,6 +1338,58 @@ void Screen::setWelcomeFrames() } } +#ifdef USE_EINK +/// Determine which screensaver frame to use, then set the FrameCallback +void Screen::setScreensaverFrames(FrameCallback einkScreensaver) +{ + // Remember current frame, restore position at power-on + uint8_t frameNumber = ui->getUiState()->currentFrame; + + // Retain specified frame / overlay callback beyond scope of this method + static FrameCallback screensaverFrame; + static OverlayCallback screensaverOverlay; + + // If: one-off screensaver frame passed as argument. Handles doDeepSleep() + if (einkScreensaver != NULL) { + screensaverFrame = einkScreensaver; + ui->setFrames(&screensaverFrame, 1); + } + + // Else, display the usual "overlay" screensaver + else { + screensaverOverlay = drawScreensaverOverlay; + ui->setOverlays(&screensaverOverlay, 1); + } + + // Request new frame, ASAP + setFastFramerate(); + uint64_t startUpdate; + do { + startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow.. + delay(1); + ui->update(); + } while (ui->getUiState()->lastUpdate < startUpdate); + +#ifndef USE_EINK_DYNAMICDISPLAY + // Retrofit to EInkDisplay class + delay(10); + screen->forceDisplay(); +#endif + + // Prepare now for next frame, shown when display wakes + ui->setOverlays(NULL, 0); // Clear overlay + setFrames(); // Return to normal display updates + ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on + + // Pick a refresh method, for when display wakes +#ifdef EINK_HASQUIRK_GHOSTING + EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused" +#else + EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh +#endif +} +#endif + // restore our regular frame list void Screen::setFrames() { @@ -1383,7 +1490,8 @@ void Screen::handleShutdownScreen() { LOG_DEBUG("showing shutdown screen\n"); showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Shutting down..."); @@ -1391,6 +1499,7 @@ void Screen::handleShutdownScreen() static FrameCallback frames[] = {frame}; setFrameImmediateDraw(frames); + forceDisplay(); } void Screen::handleRebootScreen() diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index a66cc44ec..971146012 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -73,6 +73,10 @@ class Screen #define MILES_TO_FEET 5280 #endif +// Intuitive colors. E-Ink display is inverted from OLED(?) +#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE +#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK + namespace graphics { @@ -139,12 +143,12 @@ class Screen : public concurrency::OSThread // Not thread safe - must be called before any other methods are called. void setup(); - /// Turns the screen on/off. - void setOn(bool on) + /// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink + void setOn(bool on, FrameCallback einkScreensaver = NULL) { if (!on) - handleSetOn( - false); // We handle off commands immediately, because they might be called because the CPU is shutting down + // We handle off commands immediately, because they might be called because the CPU is shutting down + handleSetOn(false, einkScreensaver); else enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); } @@ -321,6 +325,11 @@ class Screen : public concurrency::OSThread void setWelcomeFrames(); +#ifdef USE_EINK + /// Draw an image to remain on E-Ink display after screen off + void setScreensaverFrames(FrameCallback einkScreensaver = NULL); +#endif + protected: /// Updates the UI. // @@ -351,7 +360,7 @@ class Screen : public concurrency::OSThread } // Implementations of various commands, called from doTask(). - void handleSetOn(bool on); + void handleSetOn(bool on, FrameCallback einkScreensaver = NULL); void handleOnPress(); void handleShowNextFrame(); void handleShowPrevFrame(); diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 1e1bb9376..d7aac5e22 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -14,6 +14,8 @@ build_flags = -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index cae1940b3..999f1586a 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -13,7 +13,8 @@ build_flags = -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated - ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a From 7b391d1a9f36ee387cf6a81d748f47ab70f6cef3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 19:27:34 -0500 Subject: [PATCH 0179/1377] [create-pull-request] automated change (#3507) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 58a6c19d7..5f162b8ae 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 3 +build = 4 From 64fd8664942ca272104059c3976896ff417b2cd4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 29 Mar 2024 00:03:19 -0500 Subject: [PATCH 0180/1377] Make native honor HAS_SCREEN 0 (#3509) This allows easier building of the native target without the LovyanGFX libraries. --- src/graphics/TFTDisplay.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 9475e0296..79f521fbb 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -333,7 +333,7 @@ static LGFX *tft = nullptr; #include // Graphics and font library for ILI9341 driver chip static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h -#elif ARCH_PORTDUINO +#elif ARCH_PORTDUINO && HAS_SCREEN != 0 #include // Graphics and font library for ST7735 driver chip class LGFX : public lgfx::LGFX_Device @@ -404,7 +404,8 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; #endif -#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || ARCH_PORTDUINO +#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || \ + (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" #include From 3cf6c47bab231736292a57c0cb756f89a3ba960e Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 29 Mar 2024 07:01:40 +0100 Subject: [PATCH 0181/1377] replace arch with uname -m for arch linux (#3508) From the manpage: > arch - print machine hardware name (same as uname -m) Arch Linux does not have the `arch` alias, only `uname`, so use `uname` to fix this issue: > ``` > ./bin/build-native.sh: line 18: arch: command not found > ``` Co-authored-by: Jonathan Bennett --- bin/build-native.sh | 2 +- bin/native-install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/build-native.sh b/bin/build-native.sh index 7e9fcb632..7065ea54f 100755 --- a/bin/build-native.sh +++ b/bin/build-native.sh @@ -15,6 +15,6 @@ rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale platformio pkg update pio run --environment native -cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(arch)" +cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)" cp bin/device-install.* $OUTDIR cp bin/device-update.* $OUTDIR diff --git a/bin/native-install.sh b/bin/native-install.sh index cc6d968f9..ba71c4f46 100755 --- a/bin/native-install.sh +++ b/bin/native-install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -cp "release/meshtasticd_linux_$(arch)" /usr/sbin/meshtasticd +cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd mkdir /etc/meshtasticd if [[ -f "/etc/meshtasticd/config.yaml" ]]; then cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml From 279464f96d5139920b017d437501233737daf407 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 29 Mar 2024 13:42:20 +0100 Subject: [PATCH 0182/1377] linux-native: only install linux native deps (#3510) This is a couple times faster because platformio checks all environment sequentially. Co-authored-by: Ben Meadors --- bin/build-native.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/build-native.sh b/bin/build-native.sh index 7065ea54f..9d31d091a 100755 --- a/bin/build-native.sh +++ b/bin/build-native.sh @@ -13,7 +13,7 @@ mkdir -p $OUTDIR/ rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update --environment native pio run --environment native cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)" cp bin/device-install.* $OUTDIR From 46a63bf293b2f7d6e1a4306b6cdbdc08d3acb19c Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 1 Apr 2024 01:04:05 +1300 Subject: [PATCH 0183/1377] Handle edge cases for E-Ink screensaver (#3518) * remove redundant logic * Handle special screens for old EInkDisplayClass * Handle special screens for EInkDynamicDisplay class * Join an async refresh in progress to avoid skipping screensaver * attempt trunk fix --- src/graphics/EInkDynamicDisplay.cpp | 27 +++++++++++++++++++++++++-- src/graphics/EInkDynamicDisplay.h | 12 +++++++++++- src/graphics/Screen.cpp | 25 ++++++++++++++++++------- src/graphics/Screen.h | 2 +- src/platform/nrf52/NRF52Bluetooth.cpp | 2 ++ 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index f61cf891e..b396446fa 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -463,10 +463,33 @@ void EInkDynamicDisplay::onNotify(uint32_t notification) } #ifdef HAS_EINK_ASYNCFULL -// Run the post-update code if the hardware is ready +// Public: wait for an refresh already in progress, then run the post-update code. See Screen::setScreensaverFrames() +void EInkDynamicDisplay::joinAsyncRefresh() +{ + // If no async refresh running, nothing to do + if (!asyncRefreshRunning) + return; + + LOG_DEBUG("Joining an async refresh in progress\n"); + + // Continually poll the BUSY pin + while (adafruitDisplay->epd2.isBusy()) + yield(); + + // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag + LOG_DEBUG("Refresh complete\n"); + + // Note: this code only works because of a modification to meshtastic/GxEPD2. + // It is only equipped to intercept calls to nextPage() +} + +// Called from NotifiedWorkerThread. Run the post-update code if the hardware is ready void EInkDynamicDisplay::pollAsyncRefresh() { - // We shouldn't be here.. + // In theory, this condition should never be met if (!asyncRefreshRunning) return; diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 39953b62a..8f3ce205a 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -119,20 +119,30 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo // Conditional - async full refresh - only with modified meshtastic/GxEPD2 #if defined(HAS_EINK_ASYNCFULL) + public: + void joinAsyncRefresh(); // Main thread joins an async refresh already in progress. Blocks, then runs post-update code + + protected: void pollAsyncRefresh(); // Run the post-update code if the hardware is ready void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames) void awaitRefresh(); // Hold control while an async refresh runs void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh() #else + public: + void joinAsyncRefresh() {} // Dummy method + + protected: void pollAsyncRefresh() {} // Dummy method. In theory, not reachable #endif }; -// Tidier calls to addFrameFlag() from outside class +// Hide the ugly casts used in Screen.cpp #define EINK_ADD_FRAMEFLAG(display, flag) static_cast(display)->addFrameFlag(EInkDynamicDisplay::flag) +#define EINK_JOIN_ASYNCREFRESH(display) static_cast(display)->joinAsyncRefresh() #else // !USE_EINK_DYNAMICDISPLAY // Dummy-macro, removes the need for include guards #define EINK_ADD_FRAMEFLAG(display, flag) +#define EINK_JOIN_ASYNCREFRESH(display) #endif \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 52829d1f7..2087b8daf 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1349,6 +1349,12 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) static FrameCallback screensaverFrame; static OverlayCallback screensaverOverlay; +#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY) + // Join (await) a currently running async refresh, then run the post-update code. + // Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread. + EINK_JOIN_ASYNCREFRESH(dispdev); +#endif + // If: one-off screensaver frame passed as argument. Handles doDeepSleep() if (einkScreensaver != NULL) { screensaverFrame = einkScreensaver; @@ -1370,10 +1376,9 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) ui->update(); } while (ui->getUiState()->lastUpdate < startUpdate); -#ifndef USE_EINK_DYNAMICDISPLAY - // Retrofit to EInkDisplay class - delay(10); - screen->forceDisplay(); + // Old EInkDisplay class +#if !defined(USE_EINK_DYNAMICDISPLAY) + static_cast(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit #endif // Prepare now for next frame, shown when display wakes @@ -1490,8 +1495,11 @@ void Screen::handleShutdownScreen() { LOG_DEBUG("showing shutdown screen\n"); showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Use fast-refresh for next frame, no skip please +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) +#endif auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Shutting down..."); @@ -1499,14 +1507,17 @@ void Screen::handleShutdownScreen() static FrameCallback frames[] = {frame}; setFrameImmediateDraw(frames); - forceDisplay(); } void Screen::handleRebootScreen() { LOG_DEBUG("showing reboot screen\n"); showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?) +#endif auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Rebooting..."); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 971146012..d03ba4320 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -150,7 +150,7 @@ class Screen : public concurrency::OSThread // We handle off commands immediately, because they might be called because the CPU is shutting down handleSetOn(false, einkScreensaver); else - enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); + enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON}); } /** diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index e1914a184..759cbb404 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -1,5 +1,6 @@ #include "NRF52Bluetooth.h" #include "BluetoothCommon.h" +#include "PowerFSM.h" #include "configuration.h" #include "main.h" #include "mesh/PhoneAPI.h" @@ -318,6 +319,7 @@ void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); screen->startBluetoothPinScreen(configuredPasskey); if (match_request) { From a4c22321fca6fc8da7bab157c3812055603512ba Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:03:29 +0200 Subject: [PATCH 0184/1377] Don't save Neighbors to flash when receiving (#3519) * Don't save Neighbors to flash when receiving * Move `shouldSave` to `saveProtoForModule()` --------- Co-authored-by: Ben Meadors --- src/modules/NeighborInfoModule.cpp | 9 +++++---- src/modules/NeighborInfoModule.h | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 1e9652469..92395ffc5 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -114,8 +114,8 @@ size_t NeighborInfoModule::cleanUpNeighbors() (*numNeighbors)--; } - // Save the neighbor list if we removed any neighbors - if (indices_to_remove.size() > 0) { + // Save the neighbor list if we removed any neighbors or neighbors were already updated upon receiving a packet + if (indices_to_remove.size() > 0 || shouldSave) { saveProtoForModule(); } @@ -210,7 +210,6 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen // Only if this is the original sender, the broadcast interval corresponds to it if (originalSender == n && node_broadcast_interval_secs != 0) nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; - saveProtoForModule(); // Save the updated neighbor return nbr; } } @@ -228,7 +227,7 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; else // Assume the same broadcast interval as us for the neighbor if we don't know it new_nbr->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; - saveProtoForModule(); // Save the new neighbor + shouldSave = true; // Save the new neighbor upon next cleanup return new_nbr; } @@ -255,6 +254,8 @@ bool NeighborInfoModule::saveProtoForModule() #endif okay &= nodeDB->saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState); + if (okay) + shouldSave = false; return okay; } \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index b4acb0f66..d47004981 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -20,6 +20,9 @@ class NeighborInfoModule : public ProtobufModule, priva bool saveProtoForModule(); + private: + bool shouldSave = false; // Whether we should save the neighbor info to flash + protected: // Note: this holds our local info. meshtastic_NeighborInfo neighborState; From 15501e84ddd6d3fcc7a86d4419e7e1e260f7f728 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Mon, 1 Apr 2024 03:53:19 -0700 Subject: [PATCH 0185/1377] Add Station-G2 to install scripts (#3525) * add station g2 to device-install.bat * add station-g2 to device-install.sh * remove extra space --- bin/device-install.bat | 2 +- bin/device-install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index cb652346f..1fe1df52a 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -32,7 +32,7 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% ( %PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME% @REM Account for S3 and C3 board's different OTA partition - IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% ( + IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% ( IF x%FILENAME:esp32c3=%==x%FILENAME% ( %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin ) else ( diff --git a/bin/device-install.sh b/bin/device-install.sh index 0e7bd8ada..6ef7b1204 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -52,7 +52,7 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then "$PYTHON" -m esptool erase_flash "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition - if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ]; then + if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ]; then if [ -n "${FILENAME##*"esp32c3"*}" ]; then "$PYTHON" -m esptool write_flash 0x260000 bleota.bin else From 8bb562c5fa79c330b424ff49d61cc74a92828387 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 1 Apr 2024 18:31:36 -0500 Subject: [PATCH 0186/1377] Add spiTransfer function to Native to support Linux-managed CS (#3524) * Add spiTransfer function to Native to support Linux-managed CS * Trunk --------- Co-authored-by: Ben Meadors --- arch/portduino/portduino.ini | 2 +- src/mesh/RadioLibInterface.cpp | 6 ++++++ src/mesh/RadioLibInterface.h | 3 +++ variants/portduino/variant.h | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index ef8711f8a..077a49b3f 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#1b8a32c60ab7495026033858d53c737f7d1cb34a +platform = https://github.com/meshtastic/platform-native.git#117acc5e7fcc2047e9ba1dc11789daea26fc36d2 framework = arduino build_src_filter = diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 9f42afa6d..3ad2abe23 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -22,6 +22,12 @@ void LockingArduinoHal::spiEndTransaction() ArduinoHal::spiEndTransaction(); } +#if ARCH_PORTDUINO +void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in) +{ + spi->transfer(out, in, len); +} +#endif RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *_iface) diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 4634ca7ee..62720cfc9 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -25,6 +25,9 @@ class LockingArduinoHal : public ArduinoHal void spiBeginTransaction() override; void spiEndTransaction() override; +#if ARCH_PORTDUINO + void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override; +#endif }; #if defined(USE_STM32WLx) diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h index 5aad8dbfc..414a3fa56 100644 --- a/variants/portduino/variant.h +++ b/variants/portduino/variant.h @@ -2,3 +2,4 @@ #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_NUM_NODES settingsMap[maxnodes] +#define RADIOLIB_GODMODE 1 From f2ed0f7c8c0333d6dff91718a11d5e10062d5942 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 3 Apr 2024 08:55:48 +1300 Subject: [PATCH 0187/1377] Fix Light-sleep for ESP32 (#3521) * Change wakeup source from EXT0 to GPIO * Avoid ISR issue on wake * Detect press from wake reason, instead of digitalRead * Missing #ifdef Risky phone-typed commit * Fix PowerFSM timed transition preventing light sleep Addresses https://github.com/meshtastic/firmware/issues/3517 --------- Co-authored-by: Ben Meadors --- src/PowerFSM.cpp | 39 ++++++++++++++++++++++----------------- src/sleep.cpp | 18 +++++++++++------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index b6e267e28..0002a62b4 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -102,23 +102,18 @@ static void lsIdle() powerFSM.trigger(EVENT_SERIAL_CONNECTED); break; + case ESP_SLEEP_WAKEUP_GPIO: + // GPIO wakeup is now used for all ESP32 devices during light sleep + powerFSM.trigger(EVENT_PRESS); + break; + default: - // We woke for some other reason (button press, device interrupt) - // uint64_t status = esp_sleep_get_ext1_wakeup_status(); + // We woke for some other reason (device interrupt?) LOG_INFO("wakeCause2 %d\n", wakeCause2); -#ifdef BUTTON_PIN - bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); -#else - bool pressed = false; -#endif - if (pressed) { // If we woke because of press, instead generate a PRESS event. - powerFSM.trigger(EVENT_PRESS); - } else { - // Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) - // we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code - powerFSM.trigger(EVENT_WAKE_TIMER); - } + // Let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) + // we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code + powerFSM.trigger(EVENT_WAKE_TIMER); break; } } else { @@ -348,9 +343,6 @@ void PowerFSM_setup() powerFSM.add_timed_transition(&statePOWER, &stateDARK, Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateDARK, - Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, - "Screen-on timeout"); // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) #ifdef ARCH_ESP32 @@ -361,11 +353,24 @@ void PowerFSM_setup() powerFSM.add_timed_transition(&stateNB, &stateLS, Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, "Min wake timeout"); + + // If ESP32 and using power-saving, timer mover from DARK to light-sleep + // Also serves purpose of the old DARK to DARK transition(?) See https://github.com/meshtastic/firmware/issues/3517 powerFSM.add_timed_transition( &stateDARK, &stateLS, Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, "Bluetooth timeout"); + } else { + // If ESP32, but not using power-saving, check periodically if config has drifted out of stateDark + powerFSM.add_timed_transition(&stateDARK, &stateDARK, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), + NULL, "Screen-on timeout"); } +#else + // If not ESP32, light-sleep not used. Check periodically if config has drifted out of stateDark + powerFSM.add_timed_transition(&stateDARK, &stateDARK, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, + "Screen-on timeout"); #endif powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state diff --git a/src/sleep.cpp b/src/sleep.cpp index 2f4bd09e1..e91bda782 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -312,13 +312,11 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // assert(esp_sleep_enable_uart_wakeup(0) == ESP_OK); #endif #ifdef BUTTON_PIN -#if SOC_PM_SUPPORT_EXT_WAKEUP - esp_sleep_enable_ext0_wakeup((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN), - LOW); // when user presses, this button goes low -#else + // The enableLoraInterrupt() method is using ext0_wakeup, so we are forced to use GPIO wakeup + gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); + gpio_intr_disable(pin); + gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); esp_sleep_enable_gpio_wakeup(); - gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); -#endif #endif enableLoraInterrupt(); #ifdef PMU_IRQ @@ -342,6 +340,12 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r } assert(res == ESP_OK); +#ifdef BUTTON_PIN + gpio_wakeup_disable(pin); + // Would have thought that need gpio_intr_enable() here, but nope.. + // Works fine without it; crashes with it. +#endif + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); #ifdef BUTTON_PIN if (cause == ESP_SLEEP_WAKEUP_GPIO) { @@ -406,4 +410,4 @@ void enableLoraInterrupt() gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high #endif } -#endif \ No newline at end of file +#endif From 2caed6d29c68163673f934896122020bb78eabe3 Mon Sep 17 00:00:00 2001 From: AeroXuk Date: Tue, 2 Apr 2024 21:36:15 +0100 Subject: [PATCH 0188/1377] Feature parity between Pico and Pico W (#3538) --- variants/rpipicow/variant.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/variants/rpipicow/variant.h b/variants/rpipicow/variant.h index 27117680f..a17f05ee0 100644 --- a/variants/rpipicow/variant.h +++ b/variants/rpipicow/variant.h @@ -21,6 +21,8 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 +#define LED_PIN LED_BUILTIN + #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic @@ -51,4 +53,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#endif \ No newline at end of file +#endif From a570e50acad384a2754b6735891aeead3931ab9d Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Thu, 4 Apr 2024 00:59:53 +1300 Subject: [PATCH 0189/1377] Disable holds / isolations on RTC IO pads after deep sleep (#3539) * disable holds on RTC IO pads after deep sleep * Don't assume SOC_RTCIO_HOLD_SUPPORTED --- src/graphics/EInkDisplay2.cpp | 23 ++--------------------- src/sleep.cpp | 12 ++++++++++++ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 0c5fab4fb..04915fe07 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -151,31 +151,12 @@ bool EInkDisplay::connect() #elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) { - // Is this a normal boot, or a wake from deep sleep? - esp_sleep_wakeup_cause_t wakeReason = esp_sleep_get_wakeup_cause(); - - // If waking from sleep, need to reverse rtc_gpio_isolate(), called in cpuDeepSleep() - // Otherwise, SPI won't work - if (wakeReason != ESP_SLEEP_WAKEUP_UNDEFINED) { - // HSPI + other display pins - rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_SCLK); - rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_DC); - rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_RES); - rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_BUSY); - rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_CS); - rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_MOSI); - } - // Start HSPI hspi = new SPIClass(HSPI); hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS - // Enable VExt (ACTIVE LOW) - // Unsure if called elsewhere first? - delay(100); - pinMode(Vext, OUTPUT); - digitalWrite(Vext, LOW); - delay(100); + // VExt already enabled in setup() + // RTC GPIO hold disabled in setup() // Create GxEPD2 objects auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); diff --git a/src/sleep.cpp b/src/sleep.cpp index e91bda782..ddea9942c 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -147,6 +147,18 @@ void initDeepSleep() LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason); #endif + +#if SOC_RTCIO_HOLD_SUPPORTED + // If waking from sleep, release any and all RTC GPIOs + if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { + LOG_DEBUG("Disabling any holds on RTC IO pads\n"); + for (uint8_t i = 0; i <= 45; i++) { + if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) + rtc_gpio_hold_dis((gpio_num_t)i); + } + } +#endif + #endif } From 46ad4bf0e5bcfbb6c8b903b7436bec7b50ecdc9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 08:47:47 -0500 Subject: [PATCH 0190/1377] [create-pull-request] automated change (#3542) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 6 +- .../generated/meshtastic/deviceonly.pb.cpp | 4 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 119 +++++++++--------- src/mesh/generated/meshtastic/mesh.pb.h | 7 +- src/mesh/generated/meshtastic/mqtt.pb.h | 2 +- 6 files changed, 72 insertions(+), 68 deletions(-) diff --git a/protobufs b/protobufs index dea3a82ef..6157a5723 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit dea3a82ef2accd25112b4ef1c6f8991b579740f4 +Subproject commit 6157a5723745b3a750720b94676198a7f3839e2a diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index c56cf65a0..67c745214 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -30,12 +30,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role { meshtastic_Config_DeviceConfig_Role_REPEATER = 4, /* Description: Broadcasts GPS position packets as priority. Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default. - When used in conjunction with power.is_power_saving = true, nodes will wake up, + When used in conjunction with power.is_power_saving = true, nodes will wake up, send position, and then sleep for position.position_broadcast_secs seconds. */ meshtastic_Config_DeviceConfig_Role_TRACKER = 5, /* Description: Broadcasts telemetry packets as priority. Technical Details: Telemetry Mesh packets will be prioritized higher and sent more frequently by default. - When used in conjunction with power.is_power_saving = true, nodes will wake up, + When used in conjunction with power.is_power_saving = true, nodes will wake up, send environment telemetry, and then sleep for telemetry.environment_update_interval seconds. */ meshtastic_Config_DeviceConfig_Role_SENSOR = 6, /* Description: Optimized for ATAK system communication and reduces routine broadcasts. @@ -50,7 +50,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role { Can be used for clandestine operation or to dramatically reduce airtime / power consumption */ meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN = 8, /* Description: Broadcasts location as message to default channel regularly for to assist with device recovery. - Technical Details: Used to automatically send a text message to the mesh + Technical Details: Used to automatically send a text message to the mesh with the current position of the device on a frequent interval: "I'm lost! Position: lat / long" */ meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND = 9, diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index a9925b517..127319b14 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -6,13 +6,13 @@ #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2) +PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO) PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) -PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO) +PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2) PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index c75f35c04..cdd59d871 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -8,13 +8,25 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" -#include "meshtastic/telemetry.pb.h" #include "meshtastic/module_config.pb.h" +#include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. #endif +/* Enum definitions */ +/* Font sizes for the device screen */ +typedef enum _meshtastic_ScreenFonts { + /* TODO: REPLACE */ + meshtastic_ScreenFonts_FONT_SMALL = 0, + /* TODO: REPLACE */ + meshtastic_ScreenFonts_FONT_MEDIUM = 1, + /* TODO: REPLACE */ + meshtastic_ScreenFonts_FONT_LARGE = 2 +} meshtastic_ScreenFonts; + +/* Struct definitions */ /* Position with static location information only for NodeDBLite */ typedef struct _meshtastic_PositionLite { /* The new preferred location encoding, multiply by 1e-7 to get degrees @@ -63,18 +75,6 @@ typedef struct _meshtastic_NodeInfoLite { bool is_favorite; } meshtastic_NodeInfoLite; -/* Enum definitions */ -/* TODO: REPLACE */ -typedef enum _meshtastic_ScreenFonts { - /* TODO: REPLACE */ - meshtastic_ScreenFonts_FONT_SMALL = 0, - /* TODO: REPLACE */ - meshtastic_ScreenFonts_FONT_MEDIUM = 1, - /* TODO: REPLACE */ - meshtastic_ScreenFonts_FONT_LARGE = 2 -} meshtastic_ScreenFonts; - -/* Struct definitions */ /* This message is never sent over the wire, but it is used for serializing DB state to flash in the device code FIXME, since we write this each time we enter deep sleep (and have infinite @@ -117,7 +117,6 @@ typedef struct _meshtastic_DeviceState { std::vector node_db_lite; } meshtastic_DeviceState; - /* The on-disk saved channels */ typedef struct _meshtastic_ChannelFile { /* The channels our node knows about */ @@ -164,37 +163,27 @@ extern "C" { #define _meshtastic_ScreenFonts_MAX meshtastic_ScreenFonts_FONT_LARGE #define _meshtastic_ScreenFonts_ARRAYSIZE ((meshtastic_ScreenFonts)(meshtastic_ScreenFonts_FONT_LARGE+1)) - - #define meshtastic_PositionLite_location_source_ENUMTYPE meshtastic_Position_LocSource + + #define meshtastic_OEMStore_oem_font_ENUMTYPE meshtastic_ScreenFonts /* Initializer values for message structs */ -#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {{NULL}, NULL}} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} +#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {{NULL}, NULL}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} -#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {{NULL}, NULL}} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} +#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {{NULL}, NULL}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ -#define meshtastic_DeviceState_my_node_tag 2 -#define meshtastic_DeviceState_owner_tag 3 -#define meshtastic_DeviceState_receive_queue_tag 5 -#define meshtastic_DeviceState_rx_text_message_tag 7 -#define meshtastic_DeviceState_version_tag 8 -#define meshtastic_DeviceState_no_save_tag 9 -#define meshtastic_DeviceState_did_gps_reset_tag 11 -#define meshtastic_DeviceState_rx_waypoint_tag 12 -#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 -#define meshtastic_DeviceState_node_db_lite_tag 14 #define meshtastic_PositionLite_latitude_i_tag 1 #define meshtastic_PositionLite_longitude_i_tag 2 #define meshtastic_PositionLite_altitude_tag 3 @@ -210,6 +199,16 @@ extern "C" { #define meshtastic_NodeInfoLite_via_mqtt_tag 8 #define meshtastic_NodeInfoLite_hops_away_tag 9 #define meshtastic_NodeInfoLite_is_favorite_tag 10 +#define meshtastic_DeviceState_my_node_tag 2 +#define meshtastic_DeviceState_owner_tag 3 +#define meshtastic_DeviceState_receive_queue_tag 5 +#define meshtastic_DeviceState_rx_text_message_tag 7 +#define meshtastic_DeviceState_version_tag 8 +#define meshtastic_DeviceState_no_save_tag 9 +#define meshtastic_DeviceState_did_gps_reset_tag 11 +#define meshtastic_DeviceState_rx_waypoint_tag 12 +#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 +#define meshtastic_DeviceState_node_db_lite_tag 14 #define meshtastic_ChannelFile_channels_tag 1 #define meshtastic_ChannelFile_version_tag 2 #define meshtastic_OEMStore_oem_icon_width_tag 1 @@ -222,6 +221,32 @@ extern "C" { #define meshtastic_OEMStore_oem_local_module_config_tag 8 /* Struct field encoding specification for nanopb */ +#define meshtastic_PositionLite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ +X(a, STATIC, SINGULAR, INT32, altitude, 3) \ +X(a, STATIC, SINGULAR, FIXED32, time, 4) \ +X(a, STATIC, SINGULAR, UENUM, location_source, 5) +#define meshtastic_PositionLite_CALLBACK NULL +#define meshtastic_PositionLite_DEFAULT NULL + +#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ +X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ +X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ +X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) +#define meshtastic_NodeInfoLite_CALLBACK NULL +#define meshtastic_NodeInfoLite_DEFAULT NULL +#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User +#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite +#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics + #define meshtastic_DeviceState_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \ @@ -244,32 +269,6 @@ extern bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t #define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin #define meshtastic_DeviceState_node_db_lite_MSGTYPE meshtastic_NodeInfoLite -#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ -X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ -X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ -X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ -X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) \ -X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ -X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ -X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) -#define meshtastic_NodeInfoLite_CALLBACK NULL -#define meshtastic_NodeInfoLite_DEFAULT NULL -#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User -#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite -#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics - -#define meshtastic_PositionLite_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ -X(a, STATIC, SINGULAR, INT32, altitude, 3) \ -X(a, STATIC, SINGULAR, FIXED32, time, 4) \ -X(a, STATIC, SINGULAR, UENUM, location_source, 5) -#define meshtastic_PositionLite_CALLBACK NULL -#define meshtastic_PositionLite_DEFAULT NULL - #define meshtastic_ChannelFile_FIELDLIST(X, a) \ X(a, STATIC, REPEATED, MESSAGE, channels, 1) \ X(a, STATIC, SINGULAR, UINT32, version, 2) @@ -291,16 +290,16 @@ X(a, STATIC, OPTIONAL, MESSAGE, oem_local_module_config, 8) #define meshtastic_OEMStore_oem_local_config_MSGTYPE meshtastic_LocalConfig #define meshtastic_OEMStore_oem_local_module_config_MSGTYPE meshtastic_LocalModuleConfig -extern const pb_msgdesc_t meshtastic_DeviceState_msg; -extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_PositionLite_msg; +extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; +extern const pb_msgdesc_t meshtastic_DeviceState_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg -#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg #define meshtastic_PositionLite_fields &meshtastic_PositionLite_msg +#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg +#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg #define meshtastic_OEMStore_fields &meshtastic_OEMStore_msg diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index fcefe508b..d15f968d4 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -141,6 +141,11 @@ typedef enum _meshtastic_HardwareModel { /* Heltec Wireless Tracker with ESP32-S3 CPU, built-in GPS, and TFT Older "V1.0" Variant */ meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V1_0 = 58, + /* unPhone with ESP32-S3, TFT touchscreen, LSM6DS3TR-C accelerometer and gyroscope */ + meshtastic_HardwareModel_UNPHONE = 59, + /* Teledatics TD-LORAC NRF52840 based M.2 LoRA module + Compatible with the TD-WRLS development board */ + meshtastic_HardwareModel_TD_LORAC = 60, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -593,7 +598,7 @@ typedef struct _meshtastic_MeshPacket { meshtastic_MeshPacket_Delayed delayed; /* Describes whether this packet passed via MQTT somewhere along the path it currently took. */ bool via_mqtt; - /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. + /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */ uint8_t hop_start; } meshtastic_MeshPacket; diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h index 8ca570d78..9ec29d5b1 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.h +++ b/src/mesh/generated/meshtastic/mqtt.pb.h @@ -4,8 +4,8 @@ #ifndef PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #include -#include "meshtastic/mesh.pb.h" #include "meshtastic/config.pb.h" +#include "meshtastic/mesh.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. From eb0e705ba973a9596bf677c5ce185bbcfc279f7d Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 4 Apr 2024 17:04:10 +1300 Subject: [PATCH 0191/1377] de-init bluetooth --- src/nimble/NimbleBluetooth.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index bc94abf6e..0b91bf44f 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -112,6 +112,12 @@ void NimbleBluetooth::shutdown() NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->stop(); + +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) + // Saving of ~1mA + // Probably applicable to other ESP32 boards - unverified + NimBLEDevice::deinit(); +#endif } bool NimbleBluetooth::isActive() From d1db51830b90241e0f5f0b6259ae0d027c5e5fd1 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 4 Apr 2024 17:05:12 +1300 Subject: [PATCH 0192/1377] set GPIOs for sleep --- src/sleep.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sleep.cpp b/src/sleep.cpp index ddea9942c..67b7f5c7c 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -277,6 +277,17 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) if (shouldLoraWake(msecToWake)) { enableLoraInterrupt(); } + +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) // Applicable to most ESP32 boards? + // Avoid leakage through button pin + pinMode(0, INPUT); + rtc_gpio_hold_en((gpio_num_t)0); + + // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_CS); +#endif #endif cpuDeepSleep(msecToWake); } From 30ebb6ae46560a81eb33e102f14bc873ddc000a5 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 4 Apr 2024 17:18:40 +1300 Subject: [PATCH 0193/1377] use BUTTON_PIN macro --- src/sleep.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 67b7f5c7c..0d36112c1 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -280,8 +280,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) // Applicable to most ESP32 boards? // Avoid leakage through button pin - pinMode(0, INPUT); - rtc_gpio_hold_en((gpio_num_t)0); + pinMode(BUTTON_PIN, INPUT); + rtc_gpio_hold_en((gpio_num_t)BUTTON_PIN); // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep pinMode(LORA_CS, OUTPUT); From be889015f7792d8a15367d0ac471cb66b2f5d6cb Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Wed, 3 Apr 2024 20:17:13 +0100 Subject: [PATCH 0194/1377] New device unPhone using HX8357D LCD and XPT2046 touchscreen --- src/graphics/Screen.cpp | 15 ++-- src/graphics/ScreenFonts.h | 2 +- src/graphics/TFTDisplay.cpp | 113 +++++++++++++++++++++++++++++- src/graphics/images.h | 5 +- src/main.cpp | 18 ++++- src/platform/esp32/architecture.h | 4 +- variants/unphone/platformio.ini | 16 +++++ variants/unphone/variant.h | 61 ++++++++++++++++ 8 files changed, 218 insertions(+), 16 deletions(-) create mode 100644 variants/unphone/platformio.ini create mode 100644 variants/unphone/variant.h diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 2087b8daf..8510562c4 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -528,7 +528,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat { char usersString[20]; snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x, y + 3, 8, 8, imgUser); #else @@ -955,7 +955,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) +#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -1101,7 +1101,7 @@ void Screen::setup() // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically // flip it. If you have a headache now, you're welcome. if (!config.display.flip_screen) { -#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) +#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS) static_cast(dispdev)->flipScreenVertically(); #else dispdev->flipScreenVertically(); @@ -1686,7 +1686,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #ifdef ARCH_ESP32 if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -1697,7 +1697,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 imgQuestion); #endif } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -1711,7 +1711,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #endif } else { // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); @@ -1974,4 +1975,4 @@ int Screen::handleInputEvent(const InputEvent *event) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index d858add2c..4b34563f7 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -8,7 +8,7 @@ #include "graphics/fonts/OLEDDisplayFontsUA.h" #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL ArialMT_Plain_16 // Height: 19 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 79f521fbb..c1f482b84 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -402,9 +402,95 @@ class LGFX : public lgfx::LGFX_Device }; static LGFX *tft = nullptr; + +#elif defined(HX8357_CS) +#include // Graphics and font library for HX8357 driver chip + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_HX8357D _panel_instance; + lgfx::Bus_SPI _bus_instance; + #if defined(USE_XPT2046) + lgfx::ITouch *_touch_instance; + // lgfx::Touch_XPT2046 _touch_instance; + #endif + + public: + LGFX(void) + { + // Panel_HX8357D + { + // configure SPI + auto cfg = _bus_instance.config(); + + cfg.spi_host = HX8357_SPI_HOST; + cfg.spi_mode = 0; + cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing + // 80MHz by an integer) + cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving + cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin + cfg.use_lock = true; // Set to true to use transaction locking + cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch / + // SPI_DMA_CH_AUTO=auto setting) + cfg.pin_sclk = HX8357_SCK; // Set SPI SCLK pin number + cfg.pin_mosi = HX8357_MOSI; // Set SPI MOSI pin number + cfg.pin_miso = HX8357_MISO; // Set SPI MISO pin number (-1 = disable) + cfg.pin_dc = HX8357_RS; // Set SPI DC pin number (-1 = disable) + + _bus_instance.config(cfg); // applies the set value to the bus. + _panel_instance.setBus(&_bus_instance); // set the bus on the panel. + } + { + // Set the display panel control. + auto cfg = _panel_instance.config(); // Gets a structure for display panel settings. + + cfg.pin_cs = HX8357_CS; // Pin number where CS is connected (-1 = disable) + cfg.pin_rst = HX8357_RESET; // Pin number where RST is connected (-1 = disable) + cfg.pin_busy = HX8357_BUSY; // Pin number where BUSY is connected (-1 = disable) + + cfg.panel_width = TFT_WIDTH; // actual displayable width + cfg.panel_height = TFT_HEIGHT; // actual displayable height + cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction + cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction + cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is upside down) + cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout + cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read + cfg.readable = true; // Set to true if data can be read + cfg.invert = TFT_INVERT; // Set to true if the light/darkness of the panel is reversed + cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped + cfg.dlen_16bit = false; + cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.) + + _panel_instance.config(cfg); + } + #if defined(USE_XPT2046) + { + // Configure settings for touch control. + _touch_instance = new lgfx::Touch_XPT2046; + auto touch_cfg = _touch_instance->config(); + + touch_cfg.pin_cs = TOUCH_CS; + touch_cfg.x_min = 0; + touch_cfg.x_max = TFT_HEIGHT - 1; + touch_cfg.y_min = 0; + touch_cfg.y_max = TFT_WIDTH - 1; + touch_cfg.pin_int = -1; + touch_cfg.bus_shared = true; + touch_cfg.offset_rotation = 1; + + _touch_instance->config(touch_cfg); + //_panel_instance->setTouch(_touch_instance); + } + #endif + setPanel(&_panel_instance); + } +}; + +static LGFX *tft = nullptr; + #endif -#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || \ +#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || \ (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" @@ -487,7 +573,13 @@ void TFTDisplay::sendCommand(uint8_t com) #ifdef VTFT_CTRL digitalWrite(VTFT_CTRL, LOW); #endif - +#ifdef UNPHONE + Wire.beginTransmission(0x26); + Wire.write(0x02); + Wire.write(0x04); // Backlight on + Wire.write(0x22); // G&B LEDs off + Wire.endTransmission(); +#endif #ifdef RAK14014 #elif !defined(M5STACK) tft->setBrightness(172); @@ -514,6 +606,13 @@ void TFTDisplay::sendCommand(uint8_t com) #ifdef VTFT_CTRL digitalWrite(VTFT_CTRL, HIGH); #endif +#ifdef UNPHONE + Wire.beginTransmission(0x26); + Wire.write(0x02); + Wire.write(0x00); // Backlight off + Wire.write(0x22); // G&B LEDs off + Wire.endTransmission(); +#endif #ifdef RAK14014 #elif !defined(M5STACK) tft->setBrightness(0); @@ -585,6 +684,14 @@ bool TFTDisplay::connect() pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); #endif +#ifdef UNPHONE + Wire.beginTransmission(0x26); + Wire.write(0x02); + Wire.write(0x04); // Backlight on + Wire.write(0x22); // G&B LEDs off + Wire.endTransmission(); + LOG_INFO("Power to TFT Backlight\n"); +#endif tft->init(); @@ -606,4 +713,4 @@ bool TFTDisplay::connect() return true; } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/images.h b/src/graphics/images.h index 207fc3a86..5c6fb4275 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -14,7 +14,8 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3 const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF}; const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF}; -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; @@ -30,4 +31,4 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1, const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5}; #endif -#include "img/icon.xbm" \ No newline at end of file +#include "img/icon.xbm" diff --git a/src/main.cpp b/src/main.cpp index 0c45e903a..e93acf0ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -589,6 +589,20 @@ void setup() if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) screen_model = config.display.oled; +#ifdef UNPHONE + // initialise IO expander with pinmodes + Wire.beginTransmission(0x26); + Wire.write(0x06); + Wire.write(0x7A); + Wire.write(0xDD); + Wire.endTransmission(); + Wire.beginTransmission(0x26); + Wire.write(0x02); + Wire.write(0x04); // Backlight on + Wire.write(0x22); // G&B LEDs off + Wire.endTransmission(); +#endif + #if defined(USE_SH1107) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128 display_geometry = GEOMETRY_128_128; @@ -686,7 +700,7 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { @@ -986,4 +1000,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 703bcefc9..6855265ac 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -135,6 +135,8 @@ #define HW_VENDOR meshtastic_HardwareModel_CHATTER_2 #elif defined(STATION_G2) #define HW_VENDOR meshtastic_HardwareModel_STATION_G2 +#elif defined(UNPHONE) +#define HW_VENDOR meshtastic_HardwareModel_UNPHONE #endif // ----------------------------------------------------------------------------- @@ -157,4 +159,4 @@ #define LORA_CS 18 #endif -#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. \ No newline at end of file +#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini new file mode 100644 index 000000000..9a4a63807 --- /dev/null +++ b/variants/unphone/platformio.ini @@ -0,0 +1,16 @@ +[env:unphone] +;build_type = debug ; to make it possible to step through our jtag debugger +extends = esp32s3_base +board_level = extra +board = unphone9 +upload_speed = 921600 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder + +build_flags = ${esp32_base.build_flags} + -D UNPHONE + -D BOARD_HAS_PSRAM + -I variants/unphone + +lib_deps = ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h new file mode 100644 index 000000000..3032156f4 --- /dev/null +++ b/variants/unphone/variant.h @@ -0,0 +1,61 @@ +#define SPI_SCK 39 +#define SPI_MOSI 40 +#define SPI_MISO 41 + +// We use the RFM95W LoRa module +#define USE_RF95 +#define LORA_SCK SPI_SCK +#define LORA_MOSI SPI_MOSI +#define LORA_MISO SPI_MISO +#define LORA_CS 44 +#define LORA_DIO0 10 // AKA LORA_IRQ +#define LORA_RESET 42 +#define LORA_DIO1 11 +#define LORA_DIO2 RADIOLIB_NC // Not really used + +// HX8357 TFT LCD +#define HX8357_CS 48 +#define HX8357_RS 47 // AKA DC +#define HX8357_RESET 46 +#define HX8357_SCK SPI_SCK +#define HX8357_MOSI SPI_MOSI +#define HX8357_MISO SPI_MISO +#define HX8357_BUSY -1 +#define HX8357_SPI_HOST SPI2_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 480 +#define TFT_WIDTH 320 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 6 // the unPhone's screen is wired unusually, 0 is typical value here +#define TFT_INVERT false +#define SCREEN_ROTATE true +#define SCREEN_TRANSITION_FRAMERATE 5 + +#define HAS_TOUCHSCREEN 1 +#define USE_XPT2046 1 +#define TOUCH_CS 38 + +#define HAS_GPS 0 // the unphone doesn't have a gps module +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define HAS_SDCARD 1 +#define SDCARD_CS 43 + +#define LED_PIN 13 // the red part of the RGB LED +#define LED_INVERTED 1 + +#define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode +#define BUTTON_NEED_PULLUP // we do need a helping hand up + +#define I2C_SDA 3 // I2C pins for this board +#define I2C_SCL 4 + +// ratio of voltage divider = 3.20 (R1=100k, R2=220k) +// #define ADC_MULTIPLIER 3.2 + +// #define BATTERY_PIN 13 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +// #define ADC_CHANNEL ADC2_GPIO13_CHANNEL +// #define BAT_MEASURE_ADC_UNIT 2 \ No newline at end of file From 4cdfae71cfe2a47b6265adb6bc2041384dcc6114 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Wed, 3 Apr 2024 20:34:31 +0100 Subject: [PATCH 0195/1377] first attempt at getting trunk to do linting --- src/graphics/TFTDisplay.cpp | 10 +++++----- variants/unphone/variant.h | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index c1f482b84..8de415185 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -410,10 +410,10 @@ class LGFX : public lgfx::LGFX_Device { lgfx::Panel_HX8357D _panel_instance; lgfx::Bus_SPI _bus_instance; - #if defined(USE_XPT2046) +#if defined(USE_XPT2046) lgfx::ITouch *_touch_instance; - // lgfx::Touch_XPT2046 _touch_instance; - #endif +// lgfx::Touch_XPT2046 _touch_instance; +#endif public: LGFX(void) @@ -463,7 +463,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance.config(cfg); } - #if defined(USE_XPT2046) +#if defined(USE_XPT2046) { // Configure settings for touch control. _touch_instance = new lgfx::Touch_XPT2046; @@ -481,7 +481,7 @@ class LGFX : public lgfx::LGFX_Device _touch_instance->config(touch_cfg); //_panel_instance->setTouch(_touch_instance); } - #endif +#endif setPanel(&_panel_instance); } }; diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index 3032156f4..dff03b8d5 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -28,7 +28,7 @@ #define TFT_WIDTH 320 #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 -#define TFT_OFFSET_ROTATION 6 // the unPhone's screen is wired unusually, 0 is typical value here +#define TFT_OFFSET_ROTATION 6 // the unPhone's screen is wired unusually, 0 is typical value here #define TFT_INVERT false #define SCREEN_ROTATE true #define SCREEN_TRANSITION_FRAMERATE 5 @@ -37,20 +37,20 @@ #define USE_XPT2046 1 #define TOUCH_CS 38 -#define HAS_GPS 0 // the unphone doesn't have a gps module +#define HAS_GPS 0 // the unphone doesn't have a gps module #undef GPS_RX_PIN #undef GPS_TX_PIN #define HAS_SDCARD 1 #define SDCARD_CS 43 -#define LED_PIN 13 // the red part of the RGB LED +#define LED_PIN 13 // the red part of the RGB LED #define LED_INVERTED 1 -#define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode +#define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode #define BUTTON_NEED_PULLUP // we do need a helping hand up -#define I2C_SDA 3 // I2C pins for this board +#define I2C_SDA 3 // I2C pins for this board #define I2C_SCL 4 // ratio of voltage divider = 3.20 (R1=100k, R2=220k) From 902f38238daefe5b6acb13ca86d73de9ea4dcd8d Mon Sep 17 00:00:00 2001 From: Gareth Coleman <30833824+garethhcoleman@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:20:22 +0100 Subject: [PATCH 0196/1377] This change to the I2C Scan is to distinguish between two devices (#3554) sharing the same I2C address, the QMI8658 IMU and BQ24295 PMU. --- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 66e683982..ecb6db225 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -38,6 +38,7 @@ class ScanI2C MPU6050, LIS3DH, BMA423, + BQ24295, #ifdef HAS_NCP5623 NCP5623, #endif diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 146daa3dc..ea6e692df 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -293,7 +293,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n") SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310 Highrate 3-Axis magnetic sensor found\n") - SCAN_SIMPLE_CASE(QMI8658_ADDR, QMI8658, "QMI8658 Highrate 6-Axis inertial measurement sensor found\n") + + case QMI8658_ADDR: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID + if (registerValue == 0xC0) { + type = BQ24295; + LOG_INFO("BQ24295 PMU found\n"); + } else { + type = QMI8658; + LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found\n"); + } + break; + SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n") SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") From f6e6f975c03920a46ae3be2ab23132c3fd3df7ae Mon Sep 17 00:00:00 2001 From: fuutott Date: Fri, 5 Apr 2024 14:58:00 +0100 Subject: [PATCH 0197/1377] Update platformio.ini should be dash instead of underscore --- variants/rpipico-slowclock/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rpipico-slowclock/platformio.ini b/variants/rpipico-slowclock/platformio.ini index e583c4b0d..eec76ca0f 100644 --- a/variants/rpipico-slowclock/platformio.ini +++ b/variants/rpipico-slowclock/platformio.ini @@ -15,7 +15,7 @@ debug_init_cmds = # add our variants files to the include and src paths build_flags = ${rp2040_base.build_flags} -DRPI_PICO - -Ivariants/rpipico_slowclock + -Ivariants/rpipico-slowclock -DDEBUG_RP2040_PORT=Serial2 -DHW_SPI1_DEVICE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" @@ -25,4 +25,4 @@ lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} -g - -DNO_USB \ No newline at end of file + -DNO_USB From 5b5f9c62b5d0afc691487da33eed4446fded04a2 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sun, 7 Apr 2024 02:04:26 +1300 Subject: [PATCH 0198/1377] Remap backlight toggle and touch button (#3560) * Update E-Ink display after sending adhoc ping or disable/enable GPS * Resume display updates when touch button pressed * Use touch hold as modifier; change double-click behavior for user button * Fix preprocessor exclusions * Purge backlight behavior * Distinguish between 3x and 4x multi-presses * Touch button considers "Wake screen on tap or motion" user-setting * Don't assume device has BUTTON_PIN * Rename misleading method --- src/ButtonThread.cpp | 84 ++++++++++++++++++++++++++++++++--------- src/ButtonThread.h | 10 +++-- src/graphics/Screen.cpp | 25 +++++++++++- src/graphics/Screen.h | 4 +- 4 files changed, 99 insertions(+), 24 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index a1f0170e8..069a92308 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -48,7 +48,7 @@ ButtonThread::ButtonThread() : OSThread("Button") userButton.setPressMs(c_longPressTime); userButton.setDebounceMs(1); userButton.attachDoubleClick(userButtonDoublePressed); - userButton.attachMultiClick(userButtonMultiPressed); + userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton #ifndef T_DECK // T-Deck immediately wakes up after shutdown, so disable this function userButton.attachLongPressStart(userButtonPressedLongStart); userButton.attachLongPressStop(userButtonPressedLongStop); @@ -86,7 +86,8 @@ ButtonThread::ButtonThread() : OSThread("Button") #ifdef BUTTON_PIN_TOUCH userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true); - userButtonTouch.attachClick(touchPressed); + userButtonTouch.setPressMs(400); + userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click? wakeOnIrq(BUTTON_PIN_TOUCH, FALLING); #endif } @@ -138,26 +139,42 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_DOUBLE_PRESSED: { LOG_BUTTON("Double press!\n"); -#if defined(USE_EINK) && defined(PIN_EINK_EN) - digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); -#endif service.refreshLocalMeshNode(); service.sendNetworkPing(NODENUM_BROADCAST, true); - if (screen) + if (screen) { screen->print("Sent ad-hoc ping\n"); - break; - } -#if HAS_GPS - case BUTTON_EVENT_MULTI_PRESSED: { - LOG_BUTTON("Multi press!\n"); - if (!config.device.disable_triple_click && (gps != nullptr)) { - gps->toggleGpsMode(); - if (screen) - screen->forceDisplay(); + screen->forceDisplay(true); // Force a new UI frame, then force an EInk update } break; } + + case BUTTON_EVENT_MULTI_PRESSED: { + LOG_BUTTON("Mulitipress! %hux\n", multipressClickCount); + switch (multipressClickCount) { +#if HAS_GPS + // 3 clicks: toggle GPS + case 3: + if (!config.device.disable_triple_click && (gps != nullptr)) { + gps->toggleGpsMode(); + if (screen) + screen->forceDisplay(true); // Force a new UI frame, then force an EInk update + } + break; #endif +#if defined(USE_EINK) && defined(PIN_EINK_EN) // i.e. T-Echo + // 4 clicks: toggle backlight + case 4: + digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); + break; +#endif + // No valid multipress action + default: + break; + } // end switch: click count + + break; + } // end multipress event + case BUTTON_EVENT_LONG_PRESSED: { LOG_BUTTON("Long press!\n"); powerFSM.trigger(EVENT_PRESS); @@ -176,12 +193,24 @@ int32_t ButtonThread::runOnce() power->shutdown(); break; } - case BUTTON_EVENT_TOUCH_PRESSED: { + +#ifdef BUTTON_PIN_TOUCH + case BUTTON_EVENT_TOUCH_LONG_PRESSED: { LOG_BUTTON("Touch press!\n"); - if (screen) - screen->forceDisplay(); + if (config.display.wake_on_tap_or_motion) { + if (screen) { + // Wake if asleep + if (powerFSM.getState() == &stateDARK) + powerFSM.trigger(EVENT_PRESS); + + // Update display (legacy behaviour) + screen->forceDisplay(); + } + } break; } +#endif // BUTTON_PIN_TOUCH + default: break; } @@ -206,6 +235,25 @@ void ButtonThread::wakeOnIrq(int irq, int mode) FALLING); } +// Static callback +void ButtonThread::userButtonMultiPressed(void *callerThread) +{ + // Grab click count from non-static button, while the info is still valid + ButtonThread *thread = (ButtonThread *)callerThread; + thread->storeClickCount(); + + // Then handle later, in the usual way + btnEvent = BUTTON_EVENT_MULTI_PRESSED; +} + +// Non-static method, runs during callback. Grabs info while still valid +void ButtonThread::storeClickCount() +{ +#ifdef BUTTON_PIN + multipressClickCount = userButton.getNumberClicks(); +#endif +} + void ButtonThread::userButtonPressedLongStart() { if (millis() > c_holdOffTime) { diff --git a/src/ButtonThread.h b/src/ButtonThread.h index 554c1f0c4..3f177302d 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -17,11 +17,12 @@ class ButtonThread : public concurrency::OSThread BUTTON_EVENT_MULTI_PRESSED, BUTTON_EVENT_LONG_PRESSED, BUTTON_EVENT_LONG_RELEASED, - BUTTON_EVENT_TOUCH_PRESSED + BUTTON_EVENT_TOUCH_LONG_PRESSED, }; ButtonThread(); int32_t runOnce() override; + void storeClickCount(); private: #ifdef BUTTON_PIN @@ -40,13 +41,16 @@ class ButtonThread : public concurrency::OSThread // set during IRQ static volatile ButtonEventType btnEvent; + // Store click count during callback, for later use + volatile int multipressClickCount = 0; + static void wakeOnIrq(int irq, int mode); // IRQ callbacks - static void touchPressed() { btnEvent = BUTTON_EVENT_TOUCH_PRESSED; } static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; } static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; } - static void userButtonMultiPressed() { btnEvent = BUTTON_EVENT_MULTI_PRESSED; } + static void userButtonMultiPressed(void *callerThread); // Retrieve click count from non-static Onebutton while still valid static void userButtonPressedLongStart(); static void userButtonPressedLongStop(); + static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; } }; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 8510562c4..11c0b7aa0 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1153,10 +1153,33 @@ void Screen::setup() MeshModule::observeUIEvents(&uiFrameEventObserver); } -void Screen::forceDisplay() +void Screen::forceDisplay(bool forceUiUpdate) { // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. #ifdef USE_EINK + // If requested, make sure queued commands are run, and UI has rendered a new frame + if (forceUiUpdate) { + // No delay between UI frame rendering + setFastFramerate(); + + // Make sure all CMDs have run first + while (!cmdQueue.isEmpty()) + runOnce(); + + // Ensure at least one frame has drawn + uint64_t startUpdate; + do { + startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow.. + delay(10); + ui->update(); + } while (ui->getUiState()->lastUpdate < startUpdate); + + // Return to normal frame rate + targetFramerate = IDLE_FRAMERATE; + ui->setTargetFPS(targetFramerate); + } + + // Tell EInk class to update the display static_cast(dispdev)->forceDisplay(); #endif } diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index d03ba4320..2cb1cd5a9 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -20,7 +20,7 @@ class Screen void setOn(bool) {} void print(const char *) {} void doDeepSleep() {} - void forceDisplay() {} + void forceDisplay(bool forceUiUpdate = false) {} void startBluetoothPinScreen(uint32_t pin) {} void stopBluetoothPinScreen() {} void startRebootScreen() {} @@ -318,7 +318,7 @@ class Screen : public concurrency::OSThread int handleInputEvent(const InputEvent *arg); /// Used to force (super slow) eink displays to draw critical frames - void forceDisplay(); + void forceDisplay(bool forceUiUpdate = false); /// Draws our SSL cert screen during boot (called from WebServer) void setSSLFrames(); From 03f60dcb49184bab4474b9a0e8cf1302ac278da8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 6 Apr 2024 08:04:49 -0500 Subject: [PATCH 0199/1377] Make instructions clearer in config.yaml comments (#3559) --- bin/config-dist.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 22ca3e7db..5a8e658cb 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -1,5 +1,6 @@ ### Define your devices here using Broadcom pin numbering ### Uncomment the block that corresponds to your hardware +### Including the "Module:" line! --- Lora: # Module: sx1262 # Waveshare SX126X XXXM From 0e9f1beb4055264d585c5cabf36c757b6f0929e6 Mon Sep 17 00:00:00 2001 From: Jared Quinn Date: Sun, 7 Apr 2024 02:32:15 +1100 Subject: [PATCH 0200/1377] Native Linux Build (ARM support and webserver deps) (#3506) * Added webserver libraries to build libs * Revert "Added webserver libraries to build libs" This reverts commit bcc72a06b9e1d26f57f46089ab96f502703bff3c. * Added piwebserver library dependencies to native build * Add webserver libraries to apt install for native build * Revert additional libraries added by mistake * Address trunk check issues on Dockerfile * Ignore linter checks for pinning build packages and apt-get --------- Co-authored-by: Jonathan Bennett Co-authored-by: Ben Meadors --- Dockerfile | 52 +++++++++++++++++++++++------------- arch/portduino/portduino.ini | 2 +- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 21e42ad87..76aa3e2a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim AS builder +FROM debian:bookworm-slim AS builder ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Etc/UTC @@ -11,31 +11,45 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Install build deps USER root -RUN apt-get update && \ - apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev -# create a non-priveleged user & group +# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue +# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain +RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \ + ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \ + libulfius-dev liborcania-dev libssl-dev pkg-config && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh - USER mesh -RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -qO /tmp/get-platformio.py && \ - chmod +x /tmp/get-platformio.py && \ - python3 /tmp/get-platformio.py && \ - git clone https://github.com/meshtastic/firmware --recurse-submodules /tmp/firmware && \ - cd /tmp/firmware && \ - chmod +x /tmp/firmware/bin/build-native.sh && \ - source ~/.platformio/penv/bin/activate && \ - ./bin/build-native.sh -FROM frolvlad/alpine-glibc:glibc-2.31 +WORKDIR /tmp/firmware +RUN python3 -m venv /tmp/firmware +RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 -RUN apk --update add --no-cache g++ shadow && \ - groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh +COPY . /tmp/firmware +RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh +RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" -COPY --from=builder /tmp/firmware/release/meshtasticd_linux_x86_64 /home/mesh/ +##### PRODUCTION BUILD ############# + +FROM debian:bookworm-slim +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Etc/UTC + +# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue +# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain +RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 liborcania2.3 libssl3 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh USER mesh + WORKDIR /home/mesh -CMD sh -cx "./meshtasticd_linux_x86_64 --hwid '${HWID:-$RANDOM}'" +COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/ -HEALTHCHECK NONE \ No newline at end of file +VOLUME /home/mesh/data + +CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ] + +HEALTHCHECK NONE diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 077a49b3f..3c996741c 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -34,4 +34,4 @@ build_flags = -DPORTDUINO_LINUX_HARDWARE -lbluetooth -lgpiod - -lyaml-cpp \ No newline at end of file + -lyaml-cpp From 1baad2875a122fcd876024f5145587b38e711efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 7 Apr 2024 02:12:57 +0200 Subject: [PATCH 0201/1377] Add keymappings for several utility functions (#3536) * - map fn+m to mute and unmute the external notification module - map fn+t to be an alternative for the TAB key * add whitelist to inputbroker * (maybe) sweet-talking t-deck into tabbing... * now for real - back in Kansas * More fancy mappings --------- Co-authored-by: Ben Meadors --- src/input/kbI2cBase.cpp | 4 +++ src/modules/CannedMessageModule.cpp | 42 ++++++++++++++++++++-- src/modules/ExternalNotificationModule.cpp | 4 +-- src/modules/ExternalNotificationModule.h | 5 +++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 048f8bbdc..74a6c718d 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -217,7 +217,11 @@ int32_t KbI2cBase::runOnce() e.kbchar = 0xb7; break; case 0x90: // fn+r + case 0x91: // fn+t case 0x9b: // fn+s + case 0xac: // fn+m + case 0x9e: // fn+g + case 0xaf: // fn+space // just pass those unmodified e.inputEvent = ANYKEY; e.kbchar = c; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 60334ca03..c1cf90325 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -12,7 +12,11 @@ #include "detect/ScanI2C.h" #include "mesh/generated/meshtastic/cannedmessages.pb.h" -#include "main.h" // for cardkb_found +#include "main.h" // for cardkb_found +#include "modules/ExternalNotificationModule.h" // for buzzer control +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif #ifndef INPUTBROKER_MATRIX_TYPE #define INPUTBROKER_MATRIX_TYPE 0 @@ -397,6 +401,7 @@ int32_t CannedMessageModule::runOnce() } break; case 0x09: // tab + case 0x91: // alt+t for T-Deck that doesn't have a tab key if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { @@ -411,13 +416,44 @@ int32_t CannedMessageModule::runOnce() break; // handle fn+s for shutdown case 0x9b: - screen->startShutdownScreen(); + if (screen) + screen->startShutdownScreen(); shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; // and fn+r for reboot case 0x90: - screen->startRebootScreen(); + if (screen) + screen->startRebootScreen(); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + break; + case 0x9e: // toggle GPS like triple press does + if (gps != nullptr) { + gps->toggleGpsMode(); + } + if (screen) + screen->forceDisplay(); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + break; + + // mute (switch off/toggle) external notifications on fn+m + case 0xac: + if (moduleConfig.external_notification.enabled == true) { + if (externalNotificationModule->getMute()) { + externalNotificationModule->setMute(false); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + } else { + externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop + externalNotificationModule->setMute(true); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + } + } + break; + case 0xaf: // fn+space send network ping like double press does + service.refreshLocalMeshNode(); + service.sendNetworkPing(NODENUM_BROADCAST, true); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; default: if (this->cursor == this->freetext.length()) { diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 2a4fdd0ae..617796544 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -336,7 +336,7 @@ ExternalNotificationModule::ExternalNotificationModule() ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) { - if (moduleConfig.external_notification.enabled) { + if (moduleConfig.external_notification.enabled && !isMuted) { #ifdef T_WATCH_S3 drv.setWaveform(0, 75); drv.setWaveform(1, 56); @@ -445,7 +445,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP setIntervalFromNow(0); // run once so we know if we should do something } } else { - LOG_INFO("External Notification Module Disabled\n"); + LOG_INFO("External Notification Module Disabled or muted\n"); } return ProcessMessage::CONTINUE; // Let others look at this message also if they want diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 3331ec428..08e72c35a 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -38,6 +38,9 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: void setExternalOff(uint8_t index = 0); bool getExternal(uint8_t index = 0); + void setMute(bool mute) { isMuted = mute; } + bool getMute() { return isMuted; } + void stopNow(); void handleGetRingtone(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response); @@ -56,6 +59,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: bool isNagging = false; + bool isMuted = false; + virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) override; From 2db061ded9df0a495f83d0d1c0bdc5d11f14569b Mon Sep 17 00:00:00 2001 From: caveman99 Date: Sun, 7 Apr 2024 13:58:58 +0000 Subject: [PATCH 0202/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 6157a5723..68720ed8d 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 6157a5723745b3a750720b94676198a7f3839e2a +Subproject commit 68720ed8dbcb2c055e3d1ecd4f78d60692f59493 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 67c745214..ed512f12f 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -281,6 +281,8 @@ typedef struct _meshtastic_Config_DeviceConfig { bool is_managed; /* Disables the triple-press of user button to enable or disable GPS */ bool disable_triple_click; + /* POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. */ + char tzdef[65]; } meshtastic_Config_DeviceConfig; /* Position Config */ @@ -583,7 +585,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, ""} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} @@ -592,7 +594,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, ""} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} @@ -612,6 +614,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_double_tap_as_button_press_tag 8 #define meshtastic_Config_DeviceConfig_is_managed_tag 9 #define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10 +#define meshtastic_Config_DeviceConfig_tzdef_tag 11 #define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1 #define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2 #define meshtastic_Config_PositionConfig_fixed_position_tag 3 @@ -711,7 +714,8 @@ X(a, STATIC, SINGULAR, UENUM, rebroadcast_mode, 6) \ X(a, STATIC, SINGULAR, UINT32, node_info_broadcast_secs, 7) \ X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \ -X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) +X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \ +X(a, STATIC, SINGULAR, STRING, tzdef, 11) #define meshtastic_Config_DeviceConfig_CALLBACK NULL #define meshtastic_Config_DeviceConfig_DEFAULT NULL @@ -829,7 +833,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; /* Maximum encoded size of messages (where known) */ #define meshtastic_Config_BluetoothConfig_size 10 -#define meshtastic_Config_DeviceConfig_size 32 +#define meshtastic_Config_DeviceConfig_size 98 #define meshtastic_Config_DisplayConfig_size 28 #define meshtastic_Config_LoRaConfig_size 80 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index cdd59d871..bec15eb90 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -307,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* meshtastic_DeviceState_size depends on runtime parameters */ #define meshtastic_ChannelFile_size 702 #define meshtastic_NodeInfoLite_size 160 -#define meshtastic_OEMStore_size 3278 +#define meshtastic_OEMStore_size 3344 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index f27c119bd..1f431d788 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -180,7 +180,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define meshtastic_LocalConfig_size 469 +#define meshtastic_LocalConfig_size 535 #define meshtastic_LocalModuleConfig_size 663 #ifdef __cplusplus From 40a7fd145a9f3e00712b33bd5c5223182e90fd0a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 7 Apr 2024 14:15:03 -0500 Subject: [PATCH 0203/1377] Update Dockerfile to remove sticky bit during build (#3567) * Update Dockerfile to remove sticky bit during build * no sudo? --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 76aa3e2a1..a6176c32b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN python3 -m venv /tmp/firmware RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 COPY . /tmp/firmware -RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh +RUN source ./bin/activate && chmod -t /tmp/firmware -R && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" From fde20db95f75aae36589474a1ad68997ddf1e9e9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 7 Apr 2024 14:22:39 -0500 Subject: [PATCH 0204/1377] move chmod -t to root section --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a6176c32b..230ddc787 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ USER root RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \ ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \ libulfius-dev liborcania-dev libssl-dev pkg-config && \ - apt-get clean && rm -rf /var/lib/apt/lists/* + apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware && chmod -t /tmp/firmware RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh USER mesh @@ -27,7 +27,7 @@ RUN python3 -m venv /tmp/firmware RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 COPY . /tmp/firmware -RUN source ./bin/activate && chmod -t /tmp/firmware -R && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh +RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" From 47b8f7b6c63f919ebdb3658d28cb0e09d0b8fcc1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 7 Apr 2024 14:34:19 -0500 Subject: [PATCH 0205/1377] Don't forget to change directory owner --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 230ddc787..cd848208d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y wget python3 py libulfius-dev liborcania-dev libssl-dev pkg-config && \ apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware && chmod -t /tmp/firmware -RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh +RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware USER mesh WORKDIR /tmp/firmware From 68e657fd07f416b855b7c4cd70d9c1b15b05e55a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 7 Apr 2024 15:52:43 -0500 Subject: [PATCH 0206/1377] Actually fix Docker - hopefully --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cd848208d..fc6648dec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ USER root RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \ ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \ libulfius-dev liborcania-dev libssl-dev pkg-config && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware && chmod -t /tmp/firmware + apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware USER mesh @@ -26,7 +26,7 @@ WORKDIR /tmp/firmware RUN python3 -m venv /tmp/firmware RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 -COPY . /tmp/firmware +COPY --chown=mesh:mesh . /tmp/firmware RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" From aa3280c18c500cc29b9aac3cd19b6be4e37cf39d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 7 Apr 2024 17:08:17 -0500 Subject: [PATCH 0207/1377] add trunk ignore for docker chmod (#3568) * add trunk ignore for docker chmod * Fix incorrect comment type --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fc6648dec..fee6c62d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ USER mesh WORKDIR /tmp/firmware RUN python3 -m venv /tmp/firmware RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 - +# trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm COPY --chown=mesh:mesh . /tmp/firmware RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" From 65e5bdc212e42f3a94f75d84e7ef9cc9c8cc6651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 8 Apr 2024 00:01:44 +0200 Subject: [PATCH 0208/1377] display log and onscreen times in local timezone --- src/RedirectablePrint.cpp | 2 +- src/gps/RTC.cpp | 39 +++++++++++++++++++++++++++++++++------ src/gps/RTC.h | 4 ++-- src/graphics/Screen.cpp | 2 +- src/main.cpp | 9 +++++++++ 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index d3f39c377..16906e2e0 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -99,7 +99,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) // If we are the first message on a report, include the header if (!isContinuationMessage) { - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; // hms += tz.tz_dsttime * SEC_PER_HOUR; diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 10e9e0331..58b267a6c 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -128,7 +128,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv) #else rtc.initI2C(); #endif - tm *t = localtime(&tv->tv_sec); + tm *t = gmtime(&tv->tv_sec); rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec); @@ -142,7 +142,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv) #else rtc.begin(); #endif - tm *t = localtime(&tv->tv_sec); + tm *t = gmtime(&tv->tv_sec); rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec); @@ -175,7 +175,15 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t) The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). */ + // horrible hack to make mktime TZ agnostic - best practise according to + // https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html + setenv("TZ", "GMT0", 1); time_t res = mktime(&t); + if (*config.device.tzdef) { + setenv("TZ", config.device.tzdef, 1); + } else { + setenv("TZ", "UTC0", 1); + } struct timeval tv; tv.tv_sec = res; tv.tv_usec = 0; // time.centisecond() * (10 / 1000); @@ -189,14 +197,33 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t) } } +/** + * Returns the timezone offset in seconds. + * + * @return The timezone offset in seconds. + */ +int32_t getTZOffset() +{ + time_t now; + struct tm *gmt; + now = time(NULL); + gmt = gmtime(&now); + gmt->tm_isdst = -1; + return (int16_t)difftime(now, mktime(gmt)); +} + /** * Returns the current time in seconds since the Unix epoch (January 1, 1970). * * @return The current time in seconds since the Unix epoch. */ -uint32_t getTime() +uint32_t getTime(bool local) { - return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs; + if (local) { + return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs + getTZOffset(); + } else { + return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs; + } } /** @@ -205,7 +232,7 @@ uint32_t getTime() * @param minQuality The minimum quality of the RTC time required for it to be considered valid. * @return The current time from the RTC if it meets the minimum quality requirement, or 0 if the time is not valid. */ -uint32_t getValidTime(RTCQuality minQuality) +uint32_t getValidTime(RTCQuality minQuality, bool local) { - return (currentQuality >= minQuality) ? getTime() : 0; + return (currentQuality >= minQuality) ? getTime(local) : 0; } diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 527b31f46..f74e17cd0 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -29,10 +29,10 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv); bool perhapsSetRTC(RTCQuality q, struct tm &t); /// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero -uint32_t getTime(); +uint32_t getTime(bool local = false); /// Return time since 1970 in secs. If quality is RTCQualityNone return zero -uint32_t getValidTime(RTCQuality minQuality); +uint32_t getValidTime(RTCQuality minQuality, bool local = false); void readFromRTC(); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 11c0b7aa0..e5f392036 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1896,7 +1896,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // Show uptime as days, hours, minutes OR seconds std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds); - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; // hms += tz.tz_dsttime * SEC_PER_HOUR; diff --git a/src/main.cpp b/src/main.cpp index e93acf0ff..47f3e2c22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -663,6 +663,15 @@ void setup() // Initialize the screen first so we can show the logo while we start up everything else. screen = new graphics::Screen(screen_found, screen_model, screen_geometry); + // setup TZ prior to time actions. + if (*config.device.tzdef) { + setenv("TZ", config.device.tzdef, 1); + } else { + setenv("TZ", "GMT0", 1); + } + tzset(); + LOG_DEBUG("Set Timezone to %s\n", getenv("TZ")); + readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) #if !MESHTASTIC_EXCLUDE_GPS From ea61808fd94a74e3a6c24a8cb7fb0df5a5133236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 9 Apr 2024 00:26:23 +0200 Subject: [PATCH 0209/1377] tryfix: use UTC on Phone API (#3576) --- src/gps/GPS.cpp | 2 +- src/gps/NMEAWPL.cpp | 4 ++-- src/gps/RTC.cpp | 24 +++++++++++++++--------- src/gps/RTC.h | 2 ++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index a6f68f2ef..6a0e3e44a 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1379,7 +1379,7 @@ bool GPS::lookForLocation() t.tm_mon = reader.date.month() - 1; t.tm_year = reader.date.year() - 1900; t.tm_isdst = false; - p.timestamp = mktime(&t); + p.timestamp = gm_mktime(&t); // Nice to have, if available if (reader.satellites.isUpdated()) { diff --git a/src/gps/NMEAWPL.cpp b/src/gps/NMEAWPL.cpp index cdac3bb27..71943b76c 100644 --- a/src/gps/NMEAWPL.cpp +++ b/src/gps/NMEAWPL.cpp @@ -75,10 +75,10 @@ uint32_t printWPL(char *buf, size_t bufsz, const meshtastic_Position &pos, const uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos) { GeoCoord geoCoord(pos.latitude_i, pos.longitude_i, pos.altitude); - tm *t = localtime((time_t *)&pos.timestamp); + tm *t = gmtime((time_t *)&pos.timestamp); if (getRTCQuality() > 0) { // use the device clock if we got time from somewhere. If not, use the GPS timestamp. uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); - t = localtime((time_t *)&rtc_sec); + t = gmtime((time_t *)&rtc_sec); } uint32_t len = snprintf( diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 58b267a6c..26af7cac2 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -40,7 +40,7 @@ void readFromRTC() t.tm_hour = rtc.getHour(); t.tm_min = rtc.getMinute(); t.tm_sec = rtc.getSecond(); - tv.tv_sec = mktime(&t); + tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; LOG_DEBUG("Read RTC time from RV3028 as %ld\n", tv.tv_sec); timeStartMsec = now; @@ -68,7 +68,7 @@ void readFromRTC() t.tm_hour = tc.hour; t.tm_min = tc.minute; t.tm_sec = tc.second; - tv.tv_sec = mktime(&t); + tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; LOG_DEBUG("Read RTC time from PCF8563 as %ld\n", tv.tv_sec); timeStartMsec = now; @@ -177,13 +177,7 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t) */ // horrible hack to make mktime TZ agnostic - best practise according to // https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html - setenv("TZ", "GMT0", 1); - time_t res = mktime(&t); - if (*config.device.tzdef) { - setenv("TZ", config.device.tzdef, 1); - } else { - setenv("TZ", "UTC0", 1); - } + time_t res = gm_mktime(&t); struct timeval tv; tv.tv_sec = res; tv.tv_usec = 0; // time.centisecond() * (10 / 1000); @@ -236,3 +230,15 @@ uint32_t getValidTime(RTCQuality minQuality, bool local) { return (currentQuality >= minQuality) ? getTime(local) : 0; } + +time_t gm_mktime(struct tm *tm) +{ + setenv("TZ", "GMT0", 1); + time_t res = mktime(tm); + if (*config.device.tzdef) { + setenv("TZ", config.device.tzdef, 1); + } else { + setenv("TZ", "UTC0", 1); + } + return res; +} diff --git a/src/gps/RTC.h b/src/gps/RTC.h index f74e17cd0..0561819bd 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -36,6 +36,8 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false); void readFromRTC(); +time_t gm_mktime(struct tm *tm); + #define SEC_PER_DAY 86400 #define SEC_PER_HOUR 3600 #define SEC_PER_MIN 60 \ No newline at end of file From e89575bfd173852ea60bd515b0735db9b1ac4580 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:43:10 -0500 Subject: [PATCH 0210/1377] [create-pull-request] automated change (#3577) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 5f162b8ae..6aedf1e72 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 4 +build = 5 From ec74fba2bd452cd40999158b322a628910210029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 9 Apr 2024 14:40:55 +0200 Subject: [PATCH 0211/1377] update to nanopb 0.4.8 and fix proto regen script (#3578) * update to nanopb 0.4.8 and fix proto regen script * trunk, damnit --- .github/workflows/update_protobufs.yml | 6 +++--- bin/regen-protos.bat | 2 +- bin/regen-protos.sh | 14 +++----------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 6944d827e..30f9b3578 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -17,9 +17,9 @@ jobs: - name: Download nanopb run: | - wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.7-linux-x86.tar.gz - tar xvzf nanopb-0.4.7-linux-x86.tar.gz - mv nanopb-0.4.7-linux-x86 nanopb-0.4.7 + wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz + tar xvzf nanopb-0.4.8-linux-x86.tar.gz + mv nanopb-0.4.8-linux-x86 nanopb-0.4.8 - name: Re-generate protocol buffers run: | diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat index 1d55c2506..f28ef0025 100644 --- a/bin/regen-protos.bat +++ b/bin/regen-protos.bat @@ -1 +1 @@ -cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated\" -I=..\protobufs ..\protobufs\meshtastic\*.proto +cd protobufs && ..\nanopb-0.4.8\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index 7c751208a..2e60784e3 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -2,18 +2,10 @@ set -e -echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.7 to be located in the" +echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.8 to be located in the" echo "firmware root directory if the following step fails, you should download the correct" -echo "prebuilt binaries for your computer into nanopb-0.4.7" +echo "prebuilt binaries for your computer into nanopb-0.4.8" # the nanopb tool seems to require that the .options file be in the current directory! cd protobufs -../nanopb-0.4.7/generator-bin/protoc "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto --experimental_allow_proto3_optional - -# sed -i 's/#include "meshtastic/#include "./g' -- * - -# sed -i 's/meshtastic_//g' -- * - -#echo "Regenerating protobuf documentation - if you see an error message" -#echo "you can ignore it unless doing a new protobuf release to github." -#bin/regen-docs.sh +../nanopb-0.4.8/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto From daa64b055a17a5060baf71e3259a9d323600b6e5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 08:00:19 -0500 Subject: [PATCH 0212/1377] [create-pull-request] automated change (#3579) Co-authored-by: caveman99 --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 3 ++- src/mesh/generated/meshtastic/apponly.pb.cpp | 2 +- src/mesh/generated/meshtastic/apponly.pb.h | 3 ++- src/mesh/generated/meshtastic/atak.pb.cpp | 2 +- src/mesh/generated/meshtastic/atak.pb.h | 3 ++- .../generated/meshtastic/cannedmessages.pb.cpp | 2 +- .../generated/meshtastic/cannedmessages.pb.h | 3 ++- src/mesh/generated/meshtastic/channel.pb.cpp | 2 +- src/mesh/generated/meshtastic/channel.pb.h | 3 ++- src/mesh/generated/meshtastic/clientonly.pb.cpp | 2 +- src/mesh/generated/meshtastic/clientonly.pb.h | 2 +- src/mesh/generated/meshtastic/config.pb.cpp | 2 +- src/mesh/generated/meshtastic/config.pb.h | 3 ++- .../meshtastic/connection_status.pb.cpp | 2 +- .../generated/meshtastic/connection_status.pb.h | 3 ++- src/mesh/generated/meshtastic/deviceonly.pb.cpp | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 7 ++++--- src/mesh/generated/meshtastic/localonly.pb.cpp | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 3 ++- src/mesh/generated/meshtastic/mesh.pb.cpp | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 3 ++- .../generated/meshtastic/module_config.pb.cpp | 2 +- src/mesh/generated/meshtastic/module_config.pb.h | 3 ++- src/mesh/generated/meshtastic/mqtt.pb.cpp | 2 +- src/mesh/generated/meshtastic/mqtt.pb.h | 3 ++- src/mesh/generated/meshtastic/paxcount.pb.cpp | 2 +- src/mesh/generated/meshtastic/paxcount.pb.h | 3 ++- src/mesh/generated/meshtastic/portnums.pb.cpp | 2 +- src/mesh/generated/meshtastic/portnums.pb.h | 2 +- .../generated/meshtastic/remote_hardware.pb.cpp | 2 +- .../generated/meshtastic/remote_hardware.pb.h | 3 ++- src/mesh/generated/meshtastic/rtttl.pb.cpp | 2 +- src/mesh/generated/meshtastic/rtttl.pb.h | 3 ++- .../generated/meshtastic/storeforward.pb.cpp | 2 +- src/mesh/generated/meshtastic/storeforward.pb.h | 3 ++- src/mesh/generated/meshtastic/telemetry.pb.cpp | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 16 +++++++++++----- src/mesh/generated/meshtastic/xmodem.pb.cpp | 2 +- src/mesh/generated/meshtastic/xmodem.pb.h | 3 ++- 41 files changed, 70 insertions(+), 47 deletions(-) diff --git a/protobufs b/protobufs index 68720ed8d..22cbd0d4c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 68720ed8dbcb2c055e3d1ecd4f78d60692f59493 +Subproject commit 22cbd0d4cfafa4b8c1e64517e06edc2d7a22cca9 diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index 92835c89c..339960302 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/admin.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index f0d4e81b6..d692a3f30 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED @@ -340,6 +340,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size #define meshtastic_AdminMessage_size 500 #define meshtastic_HamParameters_size 32 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 diff --git a/src/mesh/generated/meshtastic/apponly.pb.cpp b/src/mesh/generated/meshtastic/apponly.pb.cpp index 8c3801ed7..44b0ea3cc 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.cpp +++ b/src/mesh/generated/meshtastic/apponly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/apponly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index 253fdd8ef..54629f522 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED @@ -54,6 +54,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; #define meshtastic_ChannelSet_fields &meshtastic_ChannelSet_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size #define meshtastic_ChannelSet_size 658 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/atak.pb.cpp b/src/mesh/generated/meshtastic/atak.pb.cpp index 1413b748e..491336bcf 100644 --- a/src/mesh/generated/meshtastic/atak.pb.cpp +++ b/src/mesh/generated/meshtastic/atak.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/atak.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index 17d3cd3b9..c094727ed 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED @@ -260,6 +260,7 @@ extern const pb_msgdesc_t meshtastic_PLI_msg; #define meshtastic_PLI_fields &meshtastic_PLI_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size #define meshtastic_Contact_size 242 #define meshtastic_GeoChat_size 323 #define meshtastic_Group_size 4 diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.cpp b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp index fffa3fdf9..71e659be2 100644 --- a/src/mesh/generated/meshtastic/cannedmessages.pb.cpp +++ b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/cannedmessages.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.h b/src/mesh/generated/meshtastic/cannedmessages.pb.h index b81f65d0d..c3f9a8b9b 100644 --- a/src/mesh/generated/meshtastic/cannedmessages.pb.h +++ b/src/mesh/generated/meshtastic/cannedmessages.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED @@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_CannedMessageModuleConfig_msg; #define meshtastic_CannedMessageModuleConfig_fields &meshtastic_CannedMessageModuleConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_MAX_SIZE meshtastic_CannedMessageModuleConfig_size #define meshtastic_CannedMessageModuleConfig_size 203 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/channel.pb.cpp b/src/mesh/generated/meshtastic/channel.pb.cpp index f604f64e9..fe76d8140 100644 --- a/src/mesh/generated/meshtastic/channel.pb.cpp +++ b/src/mesh/generated/meshtastic/channel.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/channel.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index 1587483c0..185a47a98 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED @@ -181,6 +181,7 @@ extern const pb_msgdesc_t meshtastic_Channel_msg; #define meshtastic_Channel_fields &meshtastic_Channel_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size #define meshtastic_ChannelSettings_size 70 #define meshtastic_Channel_size 85 #define meshtastic_ModuleSettings_size 6 diff --git a/src/mesh/generated/meshtastic/clientonly.pb.cpp b/src/mesh/generated/meshtastic/clientonly.pb.cpp index ebc2ffabc..44c6f95ce 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.cpp +++ b/src/mesh/generated/meshtastic/clientonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/clientonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h index 0f70e09c6..dc323292a 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.h +++ b/src/mesh/generated/meshtastic/clientonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index 0fa8ba588..f05e47573 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index ed512f12f..fd040c57f 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED @@ -832,6 +832,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_BluetoothConfig_fields &meshtastic_Config_BluetoothConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_DeviceConfig_size 98 #define meshtastic_Config_DisplayConfig_size 28 diff --git a/src/mesh/generated/meshtastic/connection_status.pb.cpp b/src/mesh/generated/meshtastic/connection_status.pb.cpp index 0675bc815..fc5a364dd 100644 --- a/src/mesh/generated/meshtastic/connection_status.pb.cpp +++ b/src/mesh/generated/meshtastic/connection_status.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/connection_status.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/connection_status.pb.h b/src/mesh/generated/meshtastic/connection_status.pb.h index 19ed69455..1c618e4d4 100644 --- a/src/mesh/generated/meshtastic/connection_status.pb.h +++ b/src/mesh/generated/meshtastic/connection_status.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED @@ -175,6 +175,7 @@ extern const pb_msgdesc_t meshtastic_SerialConnectionStatus_msg; #define meshtastic_SerialConnectionStatus_fields &meshtastic_SerialConnectionStatus_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_MAX_SIZE meshtastic_DeviceConnectionStatus_size #define meshtastic_BluetoothConnectionStatus_size 19 #define meshtastic_DeviceConnectionStatus_size 106 #define meshtastic_EthernetConnectionStatus_size 13 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 127319b14..672192f67 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/deviceonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index bec15eb90..9b3767180 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED @@ -174,12 +174,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} -#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {{NULL}, NULL}} +#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} -#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {{NULL}, NULL}} +#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -305,6 +305,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceState_size depends on runtime parameters */ +#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 702 #define meshtastic_NodeInfoLite_size 160 #define meshtastic_OEMStore_size 3344 diff --git a/src/mesh/generated/meshtastic/localonly.pb.cpp b/src/mesh/generated/meshtastic/localonly.pb.cpp index 8fc3f1139..9bc98fb85 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.cpp +++ b/src/mesh/generated/meshtastic/localonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/localonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 1f431d788..fa7ebcfee 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED @@ -180,6 +180,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 535 #define meshtastic_LocalModuleConfig_size 663 diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 39713ae8d..4907affc6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/mesh.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index d15f968d4..14b6b4c1f 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED @@ -1372,6 +1372,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index 594cf9628..88a771d5b 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/module_config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index a2adbc1b9..ffda48704 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED @@ -827,6 +827,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_MAX_SIZE meshtastic_ModuleConfig_size #define meshtastic_ModuleConfig_AmbientLightingConfig_size 14 #define meshtastic_ModuleConfig_AudioConfig_size 19 #define meshtastic_ModuleConfig_CannedMessageConfig_size 49 diff --git a/src/mesh/generated/meshtastic/mqtt.pb.cpp b/src/mesh/generated/meshtastic/mqtt.pb.cpp index a43f364e1..f00dd823b 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.cpp +++ b/src/mesh/generated/meshtastic/mqtt.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/mqtt.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h index 9ec29d5b1..8ec9f98c3 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.h +++ b/src/mesh/generated/meshtastic/mqtt.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED @@ -120,6 +120,7 @@ extern const pb_msgdesc_t meshtastic_MapReport_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_ServiceEnvelope_size depends on runtime parameters */ +#define MESHTASTIC_MESHTASTIC_MQTT_PB_H_MAX_SIZE meshtastic_MapReport_size #define meshtastic_MapReport_size 108 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/paxcount.pb.cpp b/src/mesh/generated/meshtastic/paxcount.pb.cpp index 57d5f5be9..67f07a31b 100644 --- a/src/mesh/generated/meshtastic/paxcount.pb.cpp +++ b/src/mesh/generated/meshtastic/paxcount.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/paxcount.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/paxcount.pb.h b/src/mesh/generated/meshtastic/paxcount.pb.h index 4b643293c..09377d833 100644 --- a/src/mesh/generated/meshtastic/paxcount.pb.h +++ b/src/mesh/generated/meshtastic/paxcount.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED @@ -48,6 +48,7 @@ extern const pb_msgdesc_t meshtastic_Paxcount_msg; #define meshtastic_Paxcount_fields &meshtastic_Paxcount_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_MAX_SIZE meshtastic_Paxcount_size #define meshtastic_Paxcount_size 18 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/portnums.pb.cpp b/src/mesh/generated/meshtastic/portnums.pb.cpp index dd0d00e20..8f32c0851 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.cpp +++ b/src/mesh/generated/meshtastic/portnums.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/portnums.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index f576c7893..233e8d653 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.cpp b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp index f368ec1ef..4a23698b2 100644 --- a/src/mesh/generated/meshtastic/remote_hardware.pb.cpp +++ b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/remote_hardware.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.h b/src/mesh/generated/meshtastic/remote_hardware.pb.h index 26df97616..936034b62 100644 --- a/src/mesh/generated/meshtastic/remote_hardware.pb.h +++ b/src/mesh/generated/meshtastic/remote_hardware.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED @@ -84,6 +84,7 @@ extern const pb_msgdesc_t meshtastic_HardwareMessage_msg; #define meshtastic_HardwareMessage_fields &meshtastic_HardwareMessage_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_MAX_SIZE meshtastic_HardwareMessage_size #define meshtastic_HardwareMessage_size 24 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/rtttl.pb.cpp b/src/mesh/generated/meshtastic/rtttl.pb.cpp index 685bbde45..8367fdbce 100644 --- a/src/mesh/generated/meshtastic/rtttl.pb.cpp +++ b/src/mesh/generated/meshtastic/rtttl.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/rtttl.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/rtttl.pb.h b/src/mesh/generated/meshtastic/rtttl.pb.h index aa55d0b7d..452b0cf4b 100644 --- a/src/mesh/generated/meshtastic/rtttl.pb.h +++ b/src/mesh/generated/meshtastic/rtttl.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED @@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_RTTTLConfig_msg; #define meshtastic_RTTTLConfig_fields &meshtastic_RTTTLConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_RTTTL_PB_H_MAX_SIZE meshtastic_RTTTLConfig_size #define meshtastic_RTTTLConfig_size 232 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/storeforward.pb.cpp b/src/mesh/generated/meshtastic/storeforward.pb.cpp index 44a1c70c1..5b3fadd9a 100644 --- a/src/mesh/generated/meshtastic/storeforward.pb.cpp +++ b/src/mesh/generated/meshtastic/storeforward.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/storeforward.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/storeforward.pb.h b/src/mesh/generated/meshtastic/storeforward.pb.h index 55ab0b510..311596c7f 100644 --- a/src/mesh/generated/meshtastic/storeforward.pb.h +++ b/src/mesh/generated/meshtastic/storeforward.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED @@ -207,6 +207,7 @@ extern const pb_msgdesc_t meshtastic_StoreAndForward_Heartbeat_msg; #define meshtastic_StoreAndForward_Heartbeat_fields &meshtastic_StoreAndForward_Heartbeat_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_MAX_SIZE meshtastic_StoreAndForward_size #define meshtastic_StoreAndForward_Heartbeat_size 12 #define meshtastic_StoreAndForward_History_size 18 #define meshtastic_StoreAndForward_Statistics_size 50 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index 046998ae9..6388e37a0 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index d73c6baa1..6955ac4e9 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED @@ -73,6 +73,9 @@ typedef struct _meshtastic_EnvironmentMetrics { float voltage; /* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ float current; + /* relative scale IAQ value as measured by Bosch BME680 . value 0-500. + Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ + uint16_t iaq; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -154,12 +157,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -175,6 +178,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_gas_resistance_tag 4 #define meshtastic_EnvironmentMetrics_voltage_tag 5 #define meshtastic_EnvironmentMetrics_current_tag 6 +#define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -214,7 +218,8 @@ X(a, STATIC, SINGULAR, FLOAT, relative_humidity, 2) \ X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) \ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ -X(a, STATIC, SINGULAR, FLOAT, current, 6) +X(a, STATIC, SINGULAR, FLOAT, current, 6) \ +X(a, STATIC, SINGULAR, UINT32, iaq, 7) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -271,9 +276,10 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 21 -#define meshtastic_EnvironmentMetrics_size 30 +#define meshtastic_EnvironmentMetrics_size 34 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 diff --git a/src/mesh/generated/meshtastic/xmodem.pb.cpp b/src/mesh/generated/meshtastic/xmodem.pb.cpp index 9692a5eb4..8e5cde457 100644 --- a/src/mesh/generated/meshtastic/xmodem.pb.cpp +++ b/src/mesh/generated/meshtastic/xmodem.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/xmodem.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/xmodem.pb.h b/src/mesh/generated/meshtastic/xmodem.pb.h index 48d5aa5cd..67bd0869f 100644 --- a/src/mesh/generated/meshtastic/xmodem.pb.h +++ b/src/mesh/generated/meshtastic/xmodem.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED @@ -68,6 +68,7 @@ extern const pb_msgdesc_t meshtastic_XModem_msg; #define meshtastic_XModem_fields &meshtastic_XModem_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_XMODEM_PB_H_MAX_SIZE meshtastic_XModem_size #define meshtastic_XModem_size 141 #ifdef __cplusplus From 77082e35f5f53bf36db4403bd4bda19b1d92e6bb Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Tue, 9 Apr 2024 09:37:38 -0700 Subject: [PATCH 0213/1377] Add unPhone to S3 build scripts (#3583) * add unphone to s3 devices * add unphone --- bin/device-install.bat | 2 +- bin/device-install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index 1fe1df52a..6c880185e 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -32,7 +32,7 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% ( %PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME% @REM Account for S3 and C3 board's different OTA partition - IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% ( + IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% IF x%FILENAME:unphone=%==x%FILENAME% ( IF x%FILENAME:esp32c3=%==x%FILENAME% ( %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin ) else ( diff --git a/bin/device-install.sh b/bin/device-install.sh index 6ef7b1204..563a87af4 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -52,7 +52,7 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then "$PYTHON" -m esptool erase_flash "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition - if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ]; then + if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then if [ -n "${FILENAME##*"esp32c3"*}" ]; then "$PYTHON" -m esptool write_flash 0x260000 bleota.bin else From 6e7405e56b9ea31e324ad30a353898d537eb5ce8 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Tue, 9 Apr 2024 10:26:03 -0700 Subject: [PATCH 0214/1377] add unphone (#3584) --- .github/workflows/main_matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 03d47f18e..9ca0764b5 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -100,6 +100,7 @@ jobs: - board: t-deck - board: picomputer-s3 - board: station-g2 + - board: unphone uses: ./.github/workflows/build_esp32_s3.yml with: board: ${{ matrix.board }} From cfd98b2c918e6e14da8e839de4b95dc6e2e504d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 9 Apr 2024 15:15:21 +0200 Subject: [PATCH 0215/1377] add BME680 IAQ reading. Range is from 0 (clean) - 500 (extremely polluted) --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 ++ src/modules/Telemetry/Sensor/BME680Sensor.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 908062a5b..189ab7ed0 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -181,6 +181,8 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt display->drawString(x, y += fontHeight(FONT_SMALL), "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); + if (lastMeasurement.variant.environment_metrics.iaq != 0) + display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 323dce31f..217fb5737 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -57,6 +57,7 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal / 100.0F; measurement->variant.environment_metrics.gas_resistance = bme680.getData(BSEC_OUTPUT_RAW_GAS).signal / 1000.0; // Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms) + measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal; updateState(); return true; } From 2d81c97b9816de793677e6a640190389d2c29acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 9 Apr 2024 15:50:15 +0200 Subject: [PATCH 0216/1377] fix #2586 (lower IAQ quality for saving to 2 and rework save logic) --- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 217fb5737..e1222bba4 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -86,17 +86,17 @@ void BME680Sensor::updateState() if (stateUpdateCounter == 0) { /* First state update when IAQ accuracy is >= 3 */ accuracy = bme680.getData(BSEC_OUTPUT_IAQ).accuracy; - if (accuracy >= 3) { - LOG_DEBUG("%s state update IAQ accuracy %u >= 3\n", sensorName, accuracy); + if (accuracy >= 2) { + LOG_DEBUG("%s state update IAQ accuracy %u >= 2\n", sensorName, accuracy); update = true; stateUpdateCounter++; } else { - LOG_DEBUG("%s not updated, IAQ accuracy is %u >= 3\n", sensorName, accuracy); + LOG_DEBUG("%s not updated, IAQ accuracy is %u < 2\n", sensorName, accuracy); } } else { /* Update every STATE_SAVE_PERIOD minutes */ if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) { - LOG_DEBUG("%s state update every %d minutes\n", sensorName, STATE_SAVE_PERIOD); + LOG_DEBUG("%s state update every %d minutes\n", sensorName, STATE_SAVE_PERIOD / 60000); update = true; stateUpdateCounter++; } @@ -104,22 +104,15 @@ void BME680Sensor::updateState() if (update) { bme680.getState(bsecState); - std::string filenameTmp = bsecConfigFileName; - filenameTmp += ".tmp"; + if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) { + LOG_WARN("Can't remove old state file\n"); + } auto file = FSCom.open(bsecConfigFileName, FILE_O_WRITE); if (file) { LOG_INFO("%s state write to %s.\n", sensorName, bsecConfigFileName); file.write((uint8_t *)&bsecState, BSEC_MAX_STATE_BLOB_SIZE); file.flush(); file.close(); - // brief window of risk here ;-) - if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) { - LOG_WARN("Can't remove old state file\n"); - } - if (!renameFile(filenameTmp.c_str(), bsecConfigFileName)) { - LOG_ERROR("Error: can't rename new state file\n"); - } - } else { LOG_INFO("Can't write %s state (File: %s).\n", sensorName, bsecConfigFileName); } From 3bee6ce9c33faf7b84a964d49024d3324fb04843 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:29:29 +0200 Subject: [PATCH 0217/1377] Only set NodeNum based on MAC if it's still zero (#3585) * Only set NodeNum based on MAC if it's still zero * Already declared --- src/mesh/NodeDB.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 262b0e039..fb2b79048 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -517,11 +517,12 @@ void NodeDB::installDefaultDeviceState() */ void NodeDB::pickNewNodeNum() { - - getMacAddr(ourMacAddr); // Make sure ourMacAddr is set - - // Pick an initial nodenum based on the macaddr - NodeNum nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]; + NodeNum nodeNum = myNodeInfo.my_node_num; + if (nodeNum == 0) { + getMacAddr(ourMacAddr); // Make sure ourMacAddr is set + // Pick an initial nodenum based on the macaddr + nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]; + } meshtastic_NodeInfoLite *found; while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) || From 8e29efcb5021201cd4c1ed0cd5600a066085a85d Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 12 Apr 2024 00:02:50 +1200 Subject: [PATCH 0218/1377] Fix button interrupt after light sleep (#3587) * Make ButtonThread instance extern Previously was a static local instance in setup(). Now declared in ButtonThread.cpp, accessible via extern declaration in ButtonThread. * Extract attachInterrupt() calls to public method; create matching method for detachInterrupt() * Change suspension of button interrupts for light-sleep * Fix declaration for ARCH_PORTDUINO * Remove LOG_DEBUG used during testing * Don't assume device has a button.. * Guard entire constructor code * Don't use BUTTON_PIN with ARCH_PORTDUINO --------- Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com> --- src/ButtonThread.cpp | 86 ++++++++++++++++++++++++++++++++++---------- src/ButtonThread.h | 11 +++--- src/main.cpp | 3 -- src/sleep.cpp | 10 ++++-- 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 069a92308..206bb7239 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -23,18 +23,24 @@ using namespace concurrency; +ButtonThread *buttonThread; // Declared extern in header volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE; +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) +OneButton ButtonThread::userButton; // Get reference to static member +#endif + ButtonThread::ButtonThread() : OSThread("Button") { -#if defined(ARCH_PORTDUINO) || defined(BUTTON_PIN) +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) + #if defined(ARCH_PORTDUINO) if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) { - userButton = OneButton(settingsMap[user], true, true); + this->userButton = OneButton(settingsMap[user], true, true); LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]); } #elif defined(BUTTON_PIN) - int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; + int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin this->userButton = OneButton(pin, true, true); LOG_DEBUG("Using GPIO%02d for button\n", pin); #endif @@ -43,6 +49,8 @@ ButtonThread::ButtonThread() : OSThread("Button") // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did pinMode(pin, INPUT_PULLUP_SENSE); #endif + +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) userButton.attachClick(userButtonPressed); userButton.setClickMs(250); userButton.setPressMs(c_longPressTime); @@ -53,21 +61,8 @@ ButtonThread::ButtonThread() : OSThread("Button") userButton.attachLongPressStart(userButtonPressedLongStart); userButton.attachLongPressStop(userButtonPressedLongStop); #endif -#if defined(ARCH_PORTDUINO) - if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) - wakeOnIrq(settingsMap[user], FALLING); -#else - static OneButton *pBtn = &userButton; // only one instance of ButtonThread is created, so static is safe - attachInterrupt( - pin, - []() { - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - pBtn->tick(); - }, - CHANGE); -#endif #endif + #ifdef BUTTON_PIN_ALT userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true); #ifdef INPUT_PULLUP_SENSE @@ -81,14 +76,15 @@ ButtonThread::ButtonThread() : OSThread("Button") userButtonAlt.attachDoubleClick(userButtonDoublePressed); userButtonAlt.attachLongPressStart(userButtonPressedLongStart); userButtonAlt.attachLongPressStop(userButtonPressedLongStop); - wakeOnIrq(BUTTON_PIN_ALT, FALLING); #endif #ifdef BUTTON_PIN_TOUCH userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true); userButtonTouch.setPressMs(400); userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click? - wakeOnIrq(BUTTON_PIN_TOUCH, FALLING); +#endif + + attachButtonInterrupts(); #endif } @@ -220,6 +216,58 @@ int32_t ButtonThread::runOnce() return 50; } +/* + * Attach (or re-attach) hardware interrupts for buttons + * Public method. Used outside class when waking from MCU sleep + */ +void ButtonThread::attachButtonInterrupts() +{ +#if defined(ARCH_PORTDUINO) + if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) + wakeOnIrq(settingsMap[user], FALLING); +#elif defined(BUTTON_PIN) + // Interrupt for user button, during normal use. Improves responsiveness. + attachInterrupt( + config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, + []() { + BaseType_t higherWake = 0; + mainDelay.interruptFromISR(&higherWake); + ButtonThread::userButton.tick(); + }, + CHANGE); +#endif + +#ifdef BUTTON_PIN_ALT + wakeOnIrq(BUTTON_PIN_ALT, FALLING); +#endif + +#ifdef BUTTON_PIN_TOUCH + wakeOnIrq(BUTTON_PIN_TOUCH, FALLING); +#endif +} + +/* + * Detach the "normal" button interrupts. + * Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep + */ +void ButtonThread::detachButtonInterrupts() +{ +#if defined(ARCH_PORTDUINO) + if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) + detachInterrupt(settingsMap[user]); +#elif defined(BUTTON_PIN) + detachInterrupt(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); +#endif + +#ifdef BUTTON_PIN_ALT + detachInterrupt(BUTTON_PIN_ALT); +#endif + +#ifdef BUTTON_PIN_TOUCH + detachInterrupt(BUTTON_PIN_TOUCH); +#endif +} + /** * Watch a GPIO and if we get an IRQ, wake the main thread. * Use to add wake on button press diff --git a/src/ButtonThread.h b/src/ButtonThread.h index 3f177302d..07c7ccff7 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -22,11 +22,13 @@ class ButtonThread : public concurrency::OSThread ButtonThread(); int32_t runOnce() override; + void attachButtonInterrupts(); + void detachButtonInterrupts(); void storeClickCount(); private: -#ifdef BUTTON_PIN - OneButton userButton; +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) + static OneButton userButton; // Static - accessed from an interrupt #endif #ifdef BUTTON_PIN_ALT OneButton userButtonAlt; @@ -34,9 +36,6 @@ class ButtonThread : public concurrency::OSThread #ifdef BUTTON_PIN_TOUCH OneButton userButtonTouch; #endif -#if defined(ARCH_PORTDUINO) - OneButton userButton; -#endif // set during IRQ static volatile ButtonEventType btnEvent; @@ -54,3 +53,5 @@ class ButtonThread : public concurrency::OSThread static void userButtonPressedLongStop(); static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; } }; + +extern ButtonThread *buttonThread; diff --git a/src/main.cpp b/src/main.cpp index 47f3e2c22..0f2ef7e67 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -188,9 +188,6 @@ uint32_t timeLastPowered = 0; static Periodic *ledPeriodic; static OSThread *powerFSMthread; -#if HAS_BUTTON || defined(ARCH_PORTDUINO) -static OSThread *buttonThread; -#endif static OSThread *accelerometerThread; static OSThread *ambientLightingThread; SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); diff --git a/src/sleep.cpp b/src/sleep.cpp index 0d36112c1..5fbb733e6 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -4,6 +4,7 @@ #include "GPS.h" #endif +#include "ButtonThread.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -337,7 +338,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r #ifdef BUTTON_PIN // The enableLoraInterrupt() method is using ext0_wakeup, so we are forced to use GPIO wakeup gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); - gpio_intr_disable(pin); + + // Have to *fully* detach the normal button-interrupts first + buttonThread->detachButtonInterrupts(); + gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); esp_sleep_enable_gpio_wakeup(); #endif @@ -364,9 +368,9 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r assert(res == ESP_OK); #ifdef BUTTON_PIN + // Disable wake-on-button interrupt. Re-attach normal button-interrupts gpio_wakeup_disable(pin); - // Would have thought that need gpio_intr_enable() here, but nope.. - // Works fine without it; crashes with it. + buttonThread->attachButtonInterrupts(); #endif esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); From a4a8556aa2ad2fee6aa5556313f87088d90ff826 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 07:41:37 -0500 Subject: [PATCH 0219/1377] [create-pull-request] automated change (#3595) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 12 ++++++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 22cbd0d4c..f92900c5f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 22cbd0d4cfafa4b8c1e64517e06edc2d7a22cca9 +Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 9b3767180..856eb6f4a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -307,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 702 -#define meshtastic_NodeInfoLite_size 160 +#define meshtastic_NodeInfoLite_size 166 #define meshtastic_OEMStore_size 3344 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 14b6b4c1f..e674e28bb 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -1384,7 +1384,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 277 +#define meshtastic_NodeInfo_size 283 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 6955ac4e9..b6d3811a4 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -57,6 +57,8 @@ typedef struct _meshtastic_DeviceMetrics { float channel_utilization; /* Percent of airtime for transmission used within the last hour. */ float air_util_tx; + /* How long the device has been running since the last reboot (in seconds) */ + uint32_t uptime_seconds; } meshtastic_DeviceMetrics; /* Weather station or other environmental metrics */ @@ -156,12 +158,12 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} #define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} -#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} #define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -172,6 +174,7 @@ extern "C" { #define meshtastic_DeviceMetrics_voltage_tag 2 #define meshtastic_DeviceMetrics_channel_utilization_tag 3 #define meshtastic_DeviceMetrics_air_util_tx_tag 4 +#define meshtastic_DeviceMetrics_uptime_seconds_tag 5 #define meshtastic_EnvironmentMetrics_temperature_tag 1 #define meshtastic_EnvironmentMetrics_relative_humidity_tag 2 #define meshtastic_EnvironmentMetrics_barometric_pressure_tag 3 @@ -208,7 +211,8 @@ extern "C" { X(a, STATIC, SINGULAR, UINT32, battery_level, 1) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 2) \ X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 3) \ -X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) +X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) \ +X(a, STATIC, SINGULAR, UINT32, uptime_seconds, 5) #define meshtastic_DeviceMetrics_CALLBACK NULL #define meshtastic_DeviceMetrics_DEFAULT NULL @@ -278,7 +282,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 -#define meshtastic_DeviceMetrics_size 21 +#define meshtastic_DeviceMetrics_size 27 #define meshtastic_EnvironmentMetrics_size 34 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From 927d07e2c6bb1a60f3b3e89ed65402843cc84d0d Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Fri, 12 Apr 2024 02:39:07 +0200 Subject: [PATCH 0220/1377] fix: device PMU shutdown (part 2) (#3596) * fix: device PMU shutdown (part 2) * fix error + enable nimble deinit --- src/Power.cpp | 7 +----- src/nimble/NimbleBluetooth.cpp | 4 ++-- src/sleep.cpp | 39 ++++++++++++++++++++-------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 779e32ff5..d13fd6891 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -500,12 +500,7 @@ void Power::shutdown() { LOG_INFO("Shutting down\n"); -#ifdef HAS_PMU - if (pmu_found == true) { - PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF); - PMU->shutdown(); - } -#elif defined(ARCH_NRF52) || defined(ARCH_ESP32) +#if defined(ARCH_NRF52) || defined(ARCH_ESP32) #ifdef PIN_LED1 ledOff(PIN_LED1); #endif diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 0b91bf44f..42b296e45 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -113,8 +113,8 @@ void NimbleBluetooth::shutdown() pAdvertising->reset(); pAdvertising->stop(); -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) - // Saving of ~1mA +#if defined(ARCH_ESP32) + // Saving of ~1mA for esp32-s3 and 0.1mA for esp32 // Probably applicable to other ESP32 boards - unverified NimBLEDevice::deinit(); #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 5fbb733e6..7ed264183 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -245,6 +245,22 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + enableLoraInterrupt(); + } +#ifdef BUTTON_PIN + // Avoid leakage through button pin + pinMode(BUTTON_PIN, INPUT); + gpio_hold_en((gpio_num_t)BUTTON_PIN); +#endif + + // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + gpio_hold_en((gpio_num_t)LORA_CS); +#endif + #ifdef HAS_PMU if (pmu_found && PMU) { // Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep. @@ -257,6 +273,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // If we want to leave the radio receiving in would be 11.5mA current draw, but most of the time it is just waiting // in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets // all the time. + PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF); uint8_t model = PMU->getChipModel(); if (model == XPOWERS_AXP2101) { @@ -271,25 +288,15 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // t-beam v1.1 radio power channel PMU->disablePowerOutput(XPOWERS_LDO2); // lora radio power channel } + if (msecToWake == portMAX_DELAY) { + LOG_INFO("PMU shutdown.\n"); + console->flush(); + PMU->shutdown(); + } } #endif -#ifdef ARCH_ESP32 - if (shouldLoraWake(msecToWake)) { - enableLoraInterrupt(); - } - -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) // Applicable to most ESP32 boards? - // Avoid leakage through button pin - pinMode(BUTTON_PIN, INPUT); - rtc_gpio_hold_en((gpio_num_t)BUTTON_PIN); - - // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - rtc_gpio_hold_en((gpio_num_t)LORA_CS); -#endif -#endif + console->flush(); cpuDeepSleep(msecToWake); } From f4a2023dbad5e50ca6e0d4aa197a9bf92fb78041 Mon Sep 17 00:00:00 2001 From: Gareth Coleman <30833824+garethhcoleman@users.noreply.github.com> Date: Fri, 12 Apr 2024 01:40:14 +0100 Subject: [PATCH 0221/1377] LSM6DS3TR-C support (#3593) * started work on pulling in the unphone library and dependencies, to do e.g. power switch management and etc.; currently failing at Adafruit_ImageReader * now compiles with unphoneLibrary included * successfully pulled in unphone library to manage power switch and init vibe motor and etc. doesnt print to serial tho... * simplified the build a bit; when doing meshtastic do not depend on the MCCI lora libs etc., then also no need to config them via build flags * version that doesnt trigger brownout * cleaned up initVariant a little * note re. GPS * back to mesh upstream version * this time we're back to mesh upstream version * getting LSM6DS3TRC driver installed * shake to wake works, set threshold quite low may need increasing * whats the crack with these end of file changes? * paramatize the wake threshold * try to get the PR to just include real changes * got the right config item and also not giving compiler messages * moved the lib_deps for the LSM6DS3TRC driver from our variant platformio.ini to the main one in root so all boards have it * stuupid error #define-ing --------- Co-authored-by: Hamish Cunningham Co-authored-by: Ben Meadors --- platformio.ini | 1 + src/AccelerometerThread.h | 16 +++++++++++++++- src/configuration.h | 3 ++- src/detect/ScanI2C.cpp | 4 ++-- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 1 + variants/unphone/variant.h | 2 ++ 7 files changed, 24 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 2c373ab00..d54a06d3e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -132,3 +132,4 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 + adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 6827908b7..fa5acdaae 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -5,6 +5,7 @@ #include "power.h" #include +#include #include #include #include @@ -108,6 +109,15 @@ class AccelerometerThread : public concurrency::OSThread bmaSensor.enableTiltIRQ(); // It corresponds to isDoubleClick interrupt bmaSensor.enableWakeupIRQ(); + } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) { + LOG_DEBUG("LSM6DS3 initializing\n"); + // Default threshold of 2G, less sensitive options are 4, 8 or 16G + lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G); +#ifndef LSM6DS3_WAKE_THRESH +#define LSM6DS3_WAKE_THRESH 20 +#endif + lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH); + // Duration is number of occurances needed to trigger, higher threshold is less sensitive } } @@ -133,6 +143,9 @@ class AccelerometerThread : public concurrency::OSThread wakeScreen(); return 500; } + } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { + wakeScreen(); + return 500; } return ACCELEROMETER_CHECK_INTERVAL_MS; @@ -156,6 +169,7 @@ class AccelerometerThread : public concurrency::OSThread ScanI2C::DeviceType acceleremoter_type; Adafruit_MPU6050 mpu; Adafruit_LIS3DH lis; + Adafruit_LSM6DS3TRC lsm; }; -} // namespace concurrency +} // namespace concurrency \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 7ce1a0b8b..66ec607ff 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -128,6 +128,7 @@ along with this program. If not, see . #define MPU6050_ADDR 0x68 #define LIS3DH_ADR 0x18 #define BMA423_ADDR 0x19 +#define LSM6DS3_ADDR 0x6A // ----------------------------------------------------------------------------- // LED @@ -280,4 +281,4 @@ along with this program. If not, see . #ifdef MESHTASTIC_EXCLUDE_SCREEN #undef HAS_SCREEN #define HAS_SCREEN 0 -#endif +#endif \ No newline at end of file diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index bf206c190..149bb95f0 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -36,8 +36,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const { - ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423}; - return firstOfOrNONE(3, types); + ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3}; + return firstOfOrNONE(4, types); } ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index ecb6db225..e87ede0a4 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -39,6 +39,7 @@ class ScanI2C LIS3DH, BMA423, BQ24295, + LSM6DS3, #ifdef HAS_NCP5623 NCP5623, #endif diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index ea6e692df..335892131 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -310,6 +310,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n"); SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); + SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index dff03b8d5..9306537f2 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -53,6 +53,8 @@ #define I2C_SDA 3 // I2C pins for this board #define I2C_SCL 4 +#define LSM6DS3_WAKE_THRESH 5 // higher values reduce the sensitivity of the wake threshold + // ratio of voltage divider = 3.20 (R1=100k, R2=220k) // #define ADC_MULTIPLIER 3.2 From 6de0363eea1ff91eeca219ef6a47a122a8de706e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 12 Apr 2024 07:17:43 -0500 Subject: [PATCH 0222/1377] Pin RadioLib to 6.5.x (#3601) --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index d54a06d3e..27f9cf546 100644 --- a/platformio.ini +++ b/platformio.ini @@ -74,7 +74,7 @@ build_flags = -Wno-missing-field-initializers monitor_speed = 115200 lib_deps = - jgromes/RadioLib@^6.4.0 + jgromes/RadioLib@~6.5.0 https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 @@ -132,4 +132,4 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file + adafruit/Adafruit LSM6DS@^4.7.2 From 178877f2d9eccd7a046c142b6abee5d08ba9a817 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 13 Apr 2024 00:18:36 +1200 Subject: [PATCH 0223/1377] Enable T-Echo touch button by default (#3604) Co-authored-by: Ben Meadors --- src/mesh/NodeDB.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fb2b79048..ea0a27992 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -346,6 +346,9 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.active = true; +#endif +#ifdef TTGO_T_ECHO + config.display.wake_on_tap_or_motion = true; // Enable touch button for screen-on / refresh #endif moduleConfig.has_canned_message = true; From 8fd32f34525613448f17f962cd6e00f84b53a59f Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Sat, 13 Apr 2024 00:19:48 +1200 Subject: [PATCH 0224/1377] enable USB CDC (#3597) --- boards/tbeam-s3-core.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 7bda2e5a0..8d2c3eed6 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -8,7 +8,7 @@ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], From 4c9646f7d97a063d366061adff9c9686da51f3e7 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Fri, 12 Apr 2024 17:01:24 +0200 Subject: [PATCH 0225/1377] fix: device sleep (part 1) (#3590) * fix sleep part 1 * always show wakeup reason in debug log * fix screen turn on issue * avoid unnecessary reboot when entering light sleep * set DIO1 based on radio type --------- Co-authored-by: Ben Meadors --- src/PowerFSM.cpp | 23 +++++++------- src/detect/LoRaRadioType.h | 5 ++++ src/main.cpp | 11 +++++++ src/sleep.cpp | 61 +++++++++++++++++++++++++++++--------- 4 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 src/detect/LoRaRadioType.h diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 0002a62b4..a6b2aea27 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -102,18 +102,21 @@ static void lsIdle() powerFSM.trigger(EVENT_SERIAL_CONNECTED); break; - case ESP_SLEEP_WAKEUP_GPIO: - // GPIO wakeup is now used for all ESP32 devices during light sleep - powerFSM.trigger(EVENT_PRESS); - break; - default: - // We woke for some other reason (device interrupt?) - LOG_INFO("wakeCause2 %d\n", wakeCause2); + // We woke for some other reason (button press, device IRQ interrupt) - // Let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) - // we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code - powerFSM.trigger(EVENT_WAKE_TIMER); +#ifdef BUTTON_PIN + bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); +#else + bool pressed = false; +#endif + if (pressed) { // If we woke because of press, instead generate a PRESS event. + powerFSM.trigger(EVENT_PRESS); + } else { + // Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) + // we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code + powerFSM.trigger(EVENT_WAKE_TIMER); + } break; } } else { diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h new file mode 100644 index 000000000..eadd92e64 --- /dev/null +++ b/src/detect/LoRaRadioType.h @@ -0,0 +1,5 @@ +#pragma once + +enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO }; + +extern LoRaRadioType radioType; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0f2ef7e67..32ac91412 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,6 +69,7 @@ NRF52Bluetooth *nrf52Bluetooth; #include "SX1262Interface.h" #include "SX1268Interface.h" #include "SX1280Interface.h" +#include "detect/LoRaRadioType.h" #ifdef ARCH_STM32WL #include "STM32WLE5JCInterface.h" @@ -142,6 +143,9 @@ ATECCX08A atecc; Adafruit_DRV2605 drv; #endif +// Global LoRa radio type +LoRaRadioType radioType = NO_RADIO; + bool isVibrating = false; bool eink_found = true; @@ -793,6 +797,7 @@ void setup() rIf = NULL; } else { LOG_INFO("STM32WL Radio init succeeded, using STM32WL radio\n"); + radioType = STM32WLx_RADIO; } } #endif @@ -806,6 +811,7 @@ void setup() rIf = NULL; } else { LOG_INFO("Using SIMULATED radio!\n"); + radioType = SIM_RADIO; } } #endif @@ -819,6 +825,7 @@ void setup() rIf = NULL; } else { LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n"); + radioType = RF95_RADIO; } } #endif @@ -832,6 +839,7 @@ void setup() rIf = NULL; } else { LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n"); + radioType = SX1262_RADIO; } } #endif @@ -845,6 +853,7 @@ void setup() rIf = NULL; } else { LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n"); + radioType = SX1268_RADIO; } } #endif @@ -858,6 +867,7 @@ void setup() rIf = NULL; } else { LOG_INFO("LLCC68 Radio init succeeded, using LLCC68 radio\n"); + radioType = LLCC68_RADIO; } } #endif @@ -871,6 +881,7 @@ void setup() rIf = NULL; } else { LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n"); + radioType = SX1280_RADIO; } } #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 7ed264183..fdfaf5e35 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -8,6 +8,7 @@ #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" #include "sleep.h" @@ -153,7 +154,7 @@ void initDeepSleep() // If waking from sleep, release any and all RTC GPIOs if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { LOG_DEBUG("Disabling any holds on RTC IO pads\n"); - for (uint8_t i = 0; i <= 45; i++) { + for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) rtc_gpio_hold_dis((gpio_num_t)i); } @@ -360,19 +361,23 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r #endif auto res = esp_sleep_enable_gpio_wakeup(); if (res != ESP_OK) { - LOG_DEBUG("esp_sleep_enable_gpio_wakeup result %d\n", res); + LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d\n", res); } assert(res == ESP_OK); res = esp_sleep_enable_timer_wakeup(sleepUsec); if (res != ESP_OK) { - LOG_DEBUG("esp_sleep_enable_timer_wakeup result %d\n", res); + LOG_ERROR("esp_sleep_enable_timer_wakeup result %d\n", res); } assert(res == ESP_OK); + + console->flush(); res = esp_light_sleep_start(); if (res != ESP_OK) { - LOG_DEBUG("esp_light_sleep_start result %d\n", res); + LOG_ERROR("esp_light_sleep_start result %d\n", res); } - assert(res == ESP_OK); + // commented out because it's not that crucial; + // if it sporadically happens the node will go into light sleep during the next round + // assert(res == ESP_OK); #ifdef BUTTON_PIN // Disable wake-on-button interrupt. Re-attach normal button-interrupts @@ -380,13 +385,27 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r buttonThread->attachButtonInterrupts(); #endif +#if !defined(SOC_PM_SUPPORT_EXT_WAKEUP) && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + if (radioType != RF95_RADIO) { + gpio_wakeup_disable((gpio_num_t)LORA_DIO1); + } +#endif +#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) + if (radioType == RF95_RADIO) { + gpio_wakeup_disable((gpio_num_t)RF95_IRQ); + } +#endif + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); #ifdef BUTTON_PIN if (cause == ESP_SLEEP_WAKEUP_GPIO) { LOG_INFO("Exit light sleep gpio: btn=%d\n", !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); - } + } else #endif + { + LOG_INFO("Exit light sleep cause: %d\n", cause); + } return cause; } @@ -428,20 +447,34 @@ bool shouldLoraWake(uint32_t msecToWake) void enableLoraInterrupt() { #if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - rtc_gpio_pulldown_en((gpio_num_t)LORA_DIO1); + gpio_pulldown_en((gpio_num_t)LORA_DIO1); #if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) - rtc_gpio_pullup_en((gpio_num_t)LORA_RESET); + gpio_pullup_en((gpio_num_t)LORA_RESET); #endif #if defined(LORA_CS) && (LORA_CS != RADIOLIB_NC) - rtc_gpio_pullup_en((gpio_num_t)LORA_CS); + gpio_pullup_en((gpio_num_t)LORA_CS); #endif - // Setup deep sleep with wakeup by external source - esp_sleep_enable_ext0_wakeup((gpio_num_t)LORA_DIO1, RISING); + + if (rtc_gpio_is_valid_gpio((gpio_num_t)LORA_DIO1)) { + // Setup light/deep sleep with wakeup by external source + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by external source\n", LORA_DIO1); + esp_sleep_enable_ext0_wakeup((gpio_num_t)LORA_DIO1, HIGH); + } else { + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt\n", LORA_DIO1); + gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); + } + #elif defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high + if (radioType != RF95_RADIO) { + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt\n", LORA_DIO1); + gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high + } #endif -#ifdef RF95_IRQ - gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high +#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) + if (radioType == RF95_RADIO) { + LOG_INFO("setup RF95_IRQ (GPIO%02d) with wakeup by gpio interrupt\n", RF95_IRQ); + gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high + } #endif } #endif From 2c4db163364893becd37d6be48f40773eb2b51c0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Apr 2024 10:49:14 -0500 Subject: [PATCH 0226/1377] TinyGPSAltitude support for negative altitude (#3605) --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 27f9cf546..c5cbaf908 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ lib_deps = https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 - https://github.com/meshtastic/TinyGPSPlus.git#f9f4fef2183514aa52be91d714c1455dd6f26e45 + https://github.com/meshtastic/TinyGPSPlus.git#bc14cc3146b33e295fc845026289a472004d48dc https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 nanopb/Nanopb@^0.4.7 erriez/ErriezCRC32@^1.0.1 @@ -132,4 +132,4 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 + adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file From 917b739e6294cb9c081877fde9d2379703021ad7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 12 Apr 2024 11:29:08 -0500 Subject: [PATCH 0227/1377] Update TinyGPS version to un-derped commit --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index c5cbaf908..e28b7424d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ lib_deps = https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 - https://github.com/meshtastic/TinyGPSPlus.git#bc14cc3146b33e295fc845026289a472004d48dc + https://github.com/meshtastic/TinyGPSPlus.git#f5b67909745c44590048594727865b3e9b055014 https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 nanopb/Nanopb@^0.4.7 erriez/ErriezCRC32@^1.0.1 @@ -132,4 +132,4 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file + adafruit/Adafruit LSM6DS@^4.7.2 From b4009f9f2f6245cbdb5e93cb1163403ade3d3693 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Apr 2024 11:49:35 -0500 Subject: [PATCH 0228/1377] New fixed copy-pasted more corrector hash --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index e28b7424d..a1082a84a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ lib_deps = https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 - https://github.com/meshtastic/TinyGPSPlus.git#f5b67909745c44590048594727865b3e9b055014 + https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 nanopb/Nanopb@^0.4.7 erriez/ErriezCRC32@^1.0.1 @@ -132,4 +132,4 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 + adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file From 11adfe05cea06ed21b781e53095cc498aaa5253e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 12 Apr 2024 14:06:05 -0500 Subject: [PATCH 0229/1377] Drop unishox2 functions from Router (#3606) --- src/mesh/Router.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 266c4f78d..3fa933bb1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -8,9 +8,6 @@ #include "main.h" #include "mesh-pb-constants.h" #include "modules/RoutingModule.h" -extern "C" { -#include "mesh/compression/unishox2.h" -} #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #endif @@ -332,6 +329,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p) p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded p->channel = chIndex; // change to store the index instead of the hash + /* Not actually ever used. // Decompress if needed. jm if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) { // Decompress the payload @@ -349,7 +347,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p) // Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - } + } */ printPacket("decoded message", p); return true; @@ -371,6 +369,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); + /* Not actually used, so save the cycles // Only allow encryption on the text message app. // TODO: Allow modules to opt into compression. if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) { @@ -404,7 +403,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP; } - } + } */ if (numbytes > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; @@ -500,4 +499,4 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) handleReceived(p); packetPool.release(p); -} \ No newline at end of file +} From 3f45c2d4f06b51c5d02f70d37524bf3bb2d1236f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 12 Apr 2024 14:14:56 -0500 Subject: [PATCH 0230/1377] Fix another LOG_DEBUG message that should be LOG_ERROR (#3607) --- src/mesh/RadioLibInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 3ad2abe23..fc1563ee3 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -319,7 +319,7 @@ void RadioLibInterface::handleReceiveInterrupt() // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race // Condition? if (!isReceiving) { - LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode\n"); + LOG_ERROR("handleReceiveInterrupt called when not in receive mode, which shouldn't happen.\n"); return; } @@ -414,4 +414,4 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) // bits enableInterrupt(isrTxLevel0); } -} \ No newline at end of file +} From 2a6e26620e3976b23aba66da28e9d6034ae1362a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Apr 2024 20:17:25 -0500 Subject: [PATCH 0231/1377] Auto-favorite our node (#3609) --- src/mesh/PhoneAPI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index f2d2a6e9d..efbcc9558 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -430,6 +430,8 @@ bool PhoneAPI::available() auto nextNode = nodeDB->readNextMeshNode(readIndex); if (nextNode) { nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode); + nodeInfoForPhone.is_favorite = + nodeInfoForPhone.is_favorite || nodeInfoForPhone.num == nodeDB->getNodeNum(); // Our node is always a favorite } } return true; // Always say we have something, because we might need to advance our state machine From f1a1834ee2c5572dc6ae97997d401567138ef875 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 13 Apr 2024 16:14:15 -0500 Subject: [PATCH 0232/1377] Update portduino to include SPI and setSetial fixes (#3611) --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 3c996741c..07151c4a3 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#117acc5e7fcc2047e9ba1dc11789daea26fc36d2 +platform = https://github.com/meshtastic/platform-native.git#c95616208ffff4c8a36d48df810a3f072cce3521 framework = arduino build_src_filter = From 0a246bfe9b93de6e7bd644ea8613b5af482de420 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 14 Apr 2024 00:29:42 -0500 Subject: [PATCH 0233/1377] Add more useful error output in radio interfaces (#3615) * Add more useful error output in radio interfaces * trunk --- src/mesh/RF95Interface.cpp | 12 ++++++++++++ src/mesh/SX126xInterface.cpp | 17 +++++++++++++---- src/mesh/SX128xInterface.cpp | 15 +++++++++++---- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index adc512ae2..b658a8ff6 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -128,12 +128,18 @@ bool RF95Interface::reconfigure() RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora->setSyncWord(syncWord); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora->setCurrentLimit(currentLimit); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 setCurrentLimit!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora->setPreambleLength(preambleLength); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora->setFrequency(getFreq()); @@ -164,6 +170,8 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp) void RF95Interface::setStandby() { int err = lora->standby(); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 standby!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more @@ -185,6 +193,8 @@ void RF95Interface::startReceive() setTransmitEnable(false); setStandby(); int err = lora->startReceive(); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -205,6 +215,8 @@ bool RF95Interface::isChannelActive() // LOG_DEBUG("Channel is busy!\n"); return true; } + if (result != RADIOLIB_ERR_WRONG_MODEM) + LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); // LOG_DEBUG("Channel is free!\n"); diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 104d0a5ed..0690f9e96 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -181,12 +181,18 @@ template bool SX126xInterface::reconfigure() RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora.setSyncWord(syncWord); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setCurrentLimit(currentLimit); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); @@ -197,6 +203,8 @@ template bool SX126xInterface::reconfigure() power = SX126X_MAX_POWER; err = lora.setOutputPower(power); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving @@ -215,10 +223,8 @@ template void SX126xInterface::setStandby() int err = lora.standby(); - if (err != RADIOLIB_ERR_NONE) { + if (err != RADIOLIB_ERR_NONE) LOG_DEBUG("SX126x standby failed with error %d\n", err); - } - assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more @@ -260,6 +266,8 @@ template void SX126xInterface::startReceive() int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, RADIOLIB_SX126X_IRQ_RX_DEFAULT | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -279,7 +287,8 @@ template bool SX126xInterface::isChannelActive() result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; - + if (result != RADIOLIB_ERR_WRONG_MODEM) + LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 45325f339..564b80494 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -126,9 +126,13 @@ template bool SX128xInterface::reconfigure() RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora.setSyncWord(syncWord); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); @@ -139,6 +143,8 @@ template bool SX128xInterface::reconfigure() power = SX128X_MAX_POWER; err = lora.setOutputPower(power); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X setOutputPower!\n", err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving @@ -162,10 +168,8 @@ template void SX128xInterface::setStandby() int err = lora.standby(); - if (err != RADIOLIB_ERR_NONE) { + if (err != RADIOLIB_ERR_NONE) LOG_ERROR("SX128x standby failed with error %d\n", err); - } - assert(err == RADIOLIB_ERR_NONE); #if ARCH_PORTDUINO if (settingsMap[rxen] != RADIOLIB_NC) { @@ -255,6 +259,8 @@ template void SX128xInterface::startReceive() lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX128X_IRQ_HEADER_VALID); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -274,7 +280,8 @@ template bool SX128xInterface::isChannelActive() result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; - + if (result != RADIOLIB_ERR_WRONG_MODEM) + LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; From ec3971bce5a3b65a5c2593dae8c8ab5b25d0f451 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 14 Apr 2024 15:11:22 +0200 Subject: [PATCH 0234/1377] fix upDown ISR (#3612) --- src/input/UpDownInterruptBase.cpp | 49 +++++++++++++++++++++---------- src/input/UpDownInterruptBase.h | 9 +++++- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index ecc3b944a..b1f83c56b 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -1,7 +1,7 @@ #include "UpDownInterruptBase.h" #include "configuration.h" -UpDownInterruptBase::UpDownInterruptBase(const char *name) +UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThread(name) { this->_originName = name; } @@ -24,31 +24,48 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, attachInterrupt(this->_pinUp, onIntUp, RISING); LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)\n", this->_pinUp, this->_pinDown, pinPress); + + this->setInterval(100); +} + +int32_t UpDownInterruptBase::runOnce() +{ + InputEvent e; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + + if (this->action == UPDOWN_ACTION_PRESSED) { + LOG_DEBUG("GPIO event Press\n"); + e.inputEvent = this->_eventPressed; + } else if (this->action == UPDOWN_ACTION_UP) { + LOG_DEBUG("GPIO event Up\n"); + e.inputEvent = this->_eventUp; + } else if (this->action == UPDOWN_ACTION_DOWN) { + LOG_DEBUG("GPIO event Down\n"); + e.inputEvent = this->_eventDown; + } + + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + e.source = this->_originName; + e.kbchar = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + this->notifyObservers(&e); + } + + this->action = UPDOWN_ACTION_NONE; + + return 100; } void UpDownInterruptBase::intPressHandler() { - InputEvent e; - e.source = this->_originName; - LOG_DEBUG("GPIO event Press\n"); - e.inputEvent = this->_eventPressed; - this->notifyObservers(&e); + this->action = UPDOWN_ACTION_PRESSED; } void UpDownInterruptBase::intDownHandler() { - InputEvent e; - e.source = this->_originName; - LOG_DEBUG("GPIO event Down\n"); - e.inputEvent = this->_eventDown; - this->notifyObservers(&e); + this->action = UPDOWN_ACTION_DOWN; } void UpDownInterruptBase::intUpHandler() { - InputEvent e; - e.source = this->_originName; - LOG_DEBUG("GPIO event Up\n"); - e.inputEvent = this->_eventUp; - this->notifyObservers(&e); + this->action = UPDOWN_ACTION_UP; } diff --git a/src/input/UpDownInterruptBase.h b/src/input/UpDownInterruptBase.h index afa64d28d..7060a0d80 100644 --- a/src/input/UpDownInterruptBase.h +++ b/src/input/UpDownInterruptBase.h @@ -3,7 +3,7 @@ #include "InputBroker.h" #include "mesh/NodeDB.h" -class UpDownInterruptBase : public Observable +class UpDownInterruptBase : public Observable, public concurrency::OSThread { public: explicit UpDownInterruptBase(const char *name); @@ -13,6 +13,13 @@ class UpDownInterruptBase : public Observable void intDownHandler(); void intUpHandler(); + int32_t runOnce() override; + + protected: + enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN }; + + volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE; + private: uint8_t _pinDown = 0; uint8_t _pinUp = 0; From 5047468d9fee8aad3913514369f904ce6ef6fa85 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 14 Apr 2024 16:11:27 +0200 Subject: [PATCH 0235/1377] fix/enhancement: TFT device powersave (part 3) (#3600) * fix: device TFT powersave (part 3) * trunk fmt * trunk fmt * undo bluetooth deinit from #3596 * revert code for heltec tracker --------- Co-authored-by: Ben Meadors --- src/PowerFSM.cpp | 6 +++++- src/graphics/TFTDisplay.cpp | 13 +++++++++---- src/mesh/NodeDB.cpp | 6 ++++++ src/nimble/NimbleBluetooth.cpp | 4 ++-- src/sleep.cpp | 7 +++++++ variants/t-deck/variant.h | 2 ++ variants/t-watch-s3/platformio.ini | 2 -- variants/t-watch-s3/variant.h | 2 ++ 8 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index a6b2aea27..ac48e664c 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -17,6 +17,10 @@ #include "sleep.h" #include "target_specific.h" +#ifndef SLEEP_TIME +#define SLEEP_TIME 30 +#endif + /// Should we behave as if we have AC power now? static bool isPowered() { @@ -81,7 +85,7 @@ static void lsIdle() // If some other service would stall sleep, don't let sleep happen yet if (doPreflightSleep()) { // Briefly come out of sleep long enough to blink the led once every few seconds - uint32_t sleepTime = 30; + uint32_t sleepTime = SLEEP_TIME; setLed(false); // Never leave led on while in light sleep esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 8de415185..fb64553ef 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -561,8 +561,10 @@ void TFTDisplay::sendCommand(uint8_t com) #elif defined(ST7735_BL_V05) pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); -#endif -#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) +#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) + tft->wakeup(); + tft->powerSaveOff(); +#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); #endif @@ -596,10 +598,13 @@ void TFTDisplay::sendCommand(uint8_t com) #elif defined(ST7735_BL_V05) pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON); -#endif -#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) +#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) + tft->sleep(); + tft->powerSaveOn(); +#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); #endif + #ifdef VTFT_CTRL_V03 digitalWrite(VTFT_CTRL_V03, HIGH); #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ea0a27992..921935593 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -311,6 +311,12 @@ void NodeDB::initConfigIntervals() config.power.wait_bluetooth_secs = default_wait_bluetooth_secs; config.display.screen_on_secs = default_screen_on_secs; + +#if defined(T_WATCH_S3) || defined(T_DECK) + config.power.is_power_saving = true; + config.display.screen_on_secs = 30; + config.power.wait_bluetooth_secs = 30; +#endif } void NodeDB::installDefaultModuleConfig() diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 42b296e45..0b91bf44f 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -113,8 +113,8 @@ void NimbleBluetooth::shutdown() pAdvertising->reset(); pAdvertising->stop(); -#if defined(ARCH_ESP32) - // Saving of ~1mA for esp32-s3 and 0.1mA for esp32 +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) + // Saving of ~1mA // Probably applicable to other ESP32 boards - unverified NimBLEDevice::deinit(); #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index fdfaf5e35..6abe535d7 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -352,6 +352,9 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); esp_sleep_enable_gpio_wakeup(); +#endif +#ifdef T_WATCH_S3 + gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL); #endif enableLoraInterrupt(); #ifdef PMU_IRQ @@ -385,6 +388,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r buttonThread->attachButtonInterrupts(); #endif +#ifdef T_WATCH_S3 + gpio_wakeup_disable((gpio_num_t)SCREEN_TOUCH_INT); +#endif + #if !defined(SOC_PM_SUPPORT_EXT_WAKEUP) && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) if (radioType != RF95_RADIO) { gpio_wakeup_disable((gpio_num_t)LORA_DIO1); diff --git a/variants/t-deck/variant.h b/variants/t-deck/variant.h index 62ac0a373..09db198ec 100644 --- a/variants/t-deck/variant.h +++ b/variants/t-deck/variant.h @@ -24,6 +24,8 @@ #define TOUCH_I2C_PORT 0 #define TOUCH_SLAVE_ADDRESS 0x5D // GT911 +#define SLEEP_TIME 120 + #define BUTTON_PIN 0 // #define BUTTON_NEED_PULLUP diff --git a/variants/t-watch-s3/platformio.ini b/variants/t-watch-s3/platformio.ini index d03273ed4..5d5904b30 100644 --- a/variants/t-watch-s3/platformio.ini +++ b/variants/t-watch-s3/platformio.ini @@ -3,8 +3,6 @@ extends = esp32s3_base board = t-watch-s3 upload_protocol = esptool -upload_speed = 115200 -upload_port = /dev/tty.usbmodem3485188D636C1 build_flags = ${esp32_base.build_flags} -DT_WATCH_S3 diff --git a/variants/t-watch-s3/variant.h b/variants/t-watch-s3/variant.h index c66fac5ef..ad7e6b56b 100644 --- a/variants/t-watch-s3/variant.h +++ b/variants/t-watch-s3/variant.h @@ -25,6 +25,8 @@ #define TOUCH_I2C_PORT 1 #define TOUCH_SLAVE_ADDRESS 0x38 +#define SLEEP_TIME 180 + #define I2C_SDA1 39 // Used for capacitive touch #define I2C_SCL1 40 // Used for capacitive touch From 4f205718f01b9de456388a9fe84d80668de65a6a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 14 Apr 2024 10:27:01 -0500 Subject: [PATCH 0236/1377] Device telemetry uptime in seconds (#3614) --- src/modules/Telemetry/DeviceTelemetry.cpp | 21 +++++++++------------ src/modules/Telemetry/DeviceTelemetry.h | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 2ae904b89..3529267cb 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -15,14 +15,14 @@ int32_t DeviceTelemetryModule::runOnce() { - uint32_t now = millis(); + refreshUptime(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && + ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { sendTelemetry(); - lastSentToMesh = now; + lastSentToMesh = uptimeLastMs; } else if (service.isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) @@ -68,16 +68,12 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() t.time = getTime(); t.which_variant = meshtastic_Telemetry_device_metrics_tag; - t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent(); - if (powerStatus->getIsCharging()) { - t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL; - } else { - t.variant.device_metrics.battery_level = powerStatus->getBatteryChargePercent(); - } - + t.variant.device_metrics.battery_level = + powerStatus->getIsCharging() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent(); t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent(); t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0; + t.variant.device_metrics.uptime_seconds = getUptimeSeconds(); return t; } @@ -85,9 +81,10 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry telemetry = getDeviceTelemetry(); - LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f\n", + LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f, uptime=%i\n", telemetry.variant.device_metrics.air_util_tx, telemetry.variant.device_metrics.channel_utilization, - telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage); + telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage, + telemetry.variant.device_metrics.uptime_seconds); meshtastic_MeshPacket *p = allocDataProtobuf(telemetry); p->to = dest; diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index 81f83ce0a..5f4e761f9 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -12,6 +12,8 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu : concurrency::OSThread("DeviceTelemetryModule"), ProtobufModule("DeviceTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { + uptimeWrapCount = 0; + uptimeLastMs = millis(); setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent } virtual bool wantUIFrame() { return false; } @@ -28,8 +30,27 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu */ bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool phoneOnly = false); + /** + * Get the uptime in seconds + * Loses some accuracy after 49 days, but that's fine + */ + uint32_t getUptimeSeconds() { return (0xFFFFFFFF / 1000) * uptimeWrapCount + (uptimeLastMs / 1000); } + private: meshtastic_Telemetry getDeviceTelemetry(); uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; + + void refreshUptime() + { + auto now = millis(); + // If we wrapped around (~49 days), increment the wrap count + if (now < uptimeLastMs) + uptimeWrapCount++; + + uptimeLastMs = now; + } + + uint32_t uptimeWrapCount; + uint32_t uptimeLastMs; }; From 1447148811ff49119dc01e588d60380c4c6809f4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 14 Apr 2024 14:15:06 -0500 Subject: [PATCH 0237/1377] Make sure settingsStrings get initialized --- src/platform/portduino/PortduinoGlue.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 72b2a3bc7..f686ef3dc 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -77,6 +77,10 @@ void portduinoSetup() gpioInit(); std::string gpioChipName = "gpiochip"; + settingsStrings[i2cdev] = ""; + settingsStrings[keyboardDevice] = ""; + settingsStrings[webserverrootpath] = ""; + settingsStrings[spidev] = ""; YAML::Node yamlConfig; @@ -280,4 +284,4 @@ int initGPIOPin(int pinNum, std::string gpioChipName) std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; return ERRNO_DISABLED; } -} \ No newline at end of file +} From 00d4c011c7d901dea144ae452f0d419312bda164 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 14 Apr 2024 14:36:11 -0500 Subject: [PATCH 0238/1377] Fix sx126x error log logic --- src/mesh/SX126xInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 0690f9e96..afaa13b7f 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -287,7 +287,7 @@ template bool SX126xInterface::isChannelActive() result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; - if (result != RADIOLIB_ERR_WRONG_MODEM) + if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); From 5b52c31a76762eda5c1e7d61221e39c59cecdccc Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 14 Apr 2024 14:36:39 -0500 Subject: [PATCH 0239/1377] Fix HAS_WIRE logic in main --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 32ac91412..587bcb56e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -387,7 +387,7 @@ void setup() // We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to // accessories auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); -#ifdef HAS_WIRE +#if HAS_WIRE LOG_INFO("Scanning for i2c devices...\n"); #endif From 1d9754404163b9bed92d79da92d6019053f91127 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 15 Apr 2024 23:50:42 +1200 Subject: [PATCH 0240/1377] Wireless Paper: Fix BLE after Lightsleep (#3629) * NimBLE deinit for deep-sleep only * Optionally disable blink during light-sleep * Advised to revert "blink disable" This reverts commit 66347ce19bb7e6ade64827a01b0ba834bff1e361. --- src/nimble/NimbleBluetooth.cpp | 8 ++++---- src/nimble/NimbleBluetooth.h | 1 + src/sleep.cpp | 6 ++++++ variants/heltec_wireless_paper/variant.h | 3 +++ variants/heltec_wireless_paper_v1/variant.h | 3 +++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 0b91bf44f..092aef470 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -112,12 +112,12 @@ void NimbleBluetooth::shutdown() NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->stop(); +} -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) - // Saving of ~1mA - // Probably applicable to other ESP32 boards - unverified +// Extra power-saving on some devices +void NimbleBluetooth::deinit() +{ NimBLEDevice::deinit(); -#endif } bool NimbleBluetooth::isActive() diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index df2d3e45a..d1e347830 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -6,6 +6,7 @@ class NimbleBluetooth : BluetoothApi public: void setup(); void shutdown(); + void deinit(); void clearBonds(); bool isActive(); bool isConnected(); diff --git a/src/sleep.cpp b/src/sleep.cpp index 6abe535d7..860a676df 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -206,6 +206,12 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(skipPreflight); + +#ifdef NIMBLE_DEINIT_FOR_DEEPSLEEP + // Extra power saving on some devices + nimbleBluetooth->deinit(); +#endif + #ifdef ARCH_ESP32 if (shouldLoraWake(msecToWake)) { notifySleep.notifyObservers(NULL); diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 29b8bbbd1..466925a2e 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -55,3 +55,6 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Power management +#define NIMBLE_DEINIT_FOR_DEEPSLEEP // Required to reach manufacturers claim of 18uA diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 29b8bbbd1..466925a2e 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -55,3 +55,6 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Power management +#define NIMBLE_DEINIT_FOR_DEEPSLEEP // Required to reach manufacturers claim of 18uA From 2803fa964e337b58da26fec4f29f62c7338481a1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 15 Apr 2024 07:22:05 -0500 Subject: [PATCH 0241/1377] Add LoadFileState to differentiate types of success / failures (#3625) --- src/mesh/NodeDB.cpp | 49 +++++++++++++--------- src/mesh/NodeDB.h | 16 ++++++- src/modules/CannedMessageModule.cpp | 6 +-- src/modules/ExternalNotificationModule.cpp | 4 +- src/modules/NeighborInfoModule.cpp | 4 +- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 921935593..dce7e47af 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -551,12 +551,17 @@ static const char *moduleConfigFileName = "/prefs/module.proto"; static const char *channelFileName = "/prefs/channels.proto"; static const char *oemConfigFile = "/oem/oem.proto"; -/** Load a protobuf from a file, return true for success */ -bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct) +/** Load a protobuf from a file, return LoadFileResult */ +LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, + void *dest_struct) { - bool okay = false; + LoadFileResult state = LoadFileResult::OTHER_FAILURE; #ifdef FSCom - // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM + + if (!FSCom.exists(filename)) { + LOG_INFO("File %s not found\n", filename); + return LoadFileResult::NOT_FOUND; + } auto f = FSCom.open(filename, FILE_O_READ); @@ -564,30 +569,32 @@ bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, c LOG_INFO("Loading %s\n", filename); pb_istream_t stream = {&readcb, &f, protoSize}; - // LOG_DEBUG("Preload channel name=%s\n", channelSettings.name); - memset(dest_struct, 0, objSize); if (!pb_decode(&stream, fields, dest_struct)) { LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream)); + state = LoadFileResult::DECODE_FAILED; } else { - okay = true; + LOG_INFO("Loaded %s successfully\n", filename); + state = LoadFileResult::SUCCESS; } - f.close(); } else { - LOG_INFO("No %s preferences found\n", filename); + LOG_ERROR("Could not open / read %s\n", filename); } #else LOG_ERROR("ERROR: Filesystem not implemented\n"); + state = LoadFileState::NO_FILESYSTEM; #endif - return okay; + return state; } void NodeDB::loadFromDisk() { // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM - if (!loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), - sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate)) { + auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), + sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate); + + if (state != LoadFileResult::SUCCESS) { installDefaultDeviceState(); // Our in RAM copy might now be corrupt } else { if (devicestate.version < DEVICESTATE_MIN_VER) { @@ -602,8 +609,9 @@ void NodeDB::loadFromDisk() } meshNodes->resize(MAX_NUM_NODES); - if (!loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, - &config)) { + state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, + &config); + if (state != LoadFileResult::SUCCESS) { installDefaultConfig(); // Our in RAM copy might now be corrupt } else { if (config.version < DEVICESTATE_MIN_VER) { @@ -614,8 +622,9 @@ void NodeDB::loadFromDisk() } } - if (!loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), - &meshtastic_LocalModuleConfig_msg, &moduleConfig)) { + state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), + &meshtastic_LocalModuleConfig_msg, &moduleConfig); + if (state != LoadFileResult::SUCCESS) { installDefaultModuleConfig(); // Our in RAM copy might now be corrupt } else { if (moduleConfig.version < DEVICESTATE_MIN_VER) { @@ -626,8 +635,9 @@ void NodeDB::loadFromDisk() } } - if (!loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg, - &channelFile)) { + state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg, + &channelFile); + if (state != LoadFileResult::SUCCESS) { installDefaultChannels(); // Our in RAM copy might now be corrupt } else { if (channelFile.version < DEVICESTATE_MIN_VER) { @@ -638,7 +648,8 @@ void NodeDB::loadFromDisk() } } - if (loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore)) { + state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore); + if (state == LoadFileResult::SUCCESS) { LOG_INFO("Loaded OEMStore\n"); } } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 4d24d7225..1c1736f78 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -38,6 +38,19 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n); /// Given a packet, return how many seconds in the past (vs now) it was received uint32_t sinceReceived(const meshtastic_MeshPacket *p); +enum LoadFileResult { + // Successfully opened the file + SUCCESS = 1, + // File does not exist + NOT_FOUND = 2, + // Device does not have a filesystem + NO_FILESYSTEM = 3, + // File exists, but could not decode protobufs + DECODE_FAILED = 4, + // File exists, but open failed for some reason + OTHER_FAILURE = 5 +}; + class NodeDB { // NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt @@ -115,7 +128,8 @@ class NodeDB bool factoryReset(); - bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct); + LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, + void *dest_struct); bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct); void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index c1cf90325..cbd6fee72 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -664,9 +664,9 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket & void CannedMessageModule::loadProtoForModule() { - if (!nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, - &cannedMessageModuleConfig)) { + if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, + &cannedMessageModuleConfig) != LoadFileResult::SUCCESS) { installDefaultCannedMessageModuleConfig(); } } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 617796544..a38b231af 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -284,8 +284,8 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.alert_message_buzzer = true; if (moduleConfig.external_notification.enabled) { - if (!nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), - &meshtastic_RTTTLConfig_msg, &rtttlConfig)) { + if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), + &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); strncpy(rtttlConfig.ringtone, "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p", diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 92395ffc5..470234047 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -233,8 +233,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen void NeighborInfoModule::loadProtoForModule() { - if (!nodeDB->loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), - &meshtastic_NeighborInfo_msg, &neighborState)) { + if (nodeDB->loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), + &meshtastic_NeighborInfo_msg, &neighborState) != LoadFileResult::SUCCESS) { neighborState = meshtastic_NeighborInfo_init_zero; } } From 1291da746b1e052793327105b7d0e6d46d37ce19 Mon Sep 17 00:00:00 2001 From: Gareth Coleman <30833824+garethhcoleman@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:30:45 +0100 Subject: [PATCH 0242/1377] Support for alt I2C address for LSM6DS3 sensor, identification of TCA9555 IO Expander, resolve serial hang issue (#3622) * basic identification of TCA9555 * recognise LSM6DS3 on alt address * keep variant.h changes out of this PR * 2nd attempt to keep variant.h changes out of this PR --------- Co-authored-by: Ben Meadors --- src/configuration.h | 6 +++++- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 9 ++++++++- variants/unphone/platformio.ini | 5 ++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 66ec607ff..37b67f666 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -138,9 +138,13 @@ along with this program. If not, see . // ----------------------------------------------------------------------------- // Security // ----------------------------------------------------------------------------- - #define ATECC608B_ADDR 0x35 +// ----------------------------------------------------------------------------- +// IO Expander +// ----------------------------------------------------------------------------- +#define TCA9555_ADDR 0x26 + // ----------------------------------------------------------------------------- // GPS // ----------------------------------------------------------------------------- diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index e87ede0a4..c8fcfee10 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -40,6 +40,7 @@ class ScanI2C BMA423, BQ24295, LSM6DS3, + TCA9555, #ifdef HAS_NCP5623 NCP5623, #endif diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 335892131..13c2f4609 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -299,6 +299,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port) if (registerValue == 0xC0) { type = BQ24295; LOG_INFO("BQ24295 PMU found\n"); + break; + } + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID + if (registerValue == 0x6A) { + type = LSM6DS3; + LOG_INFO("LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); } else { type = QMI8658; LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found\n"); @@ -310,7 +316,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n"); SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); - SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found\n"); + SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index 9a4a63807..06314eaa3 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -7,10 +7,13 @@ upload_speed = 921600 monitor_speed = 115200 monitor_filters = esp32_exception_decoder +build_unflags = + -D ARDUINO_USB_MODE + build_flags = ${esp32_base.build_flags} -D UNPHONE - -D BOARD_HAS_PSRAM -I variants/unphone + -D ARDUINO_USB_MODE=0 lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file From d1cd686644b2c36b8c51276ab51c72e1b49003f4 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Mon, 15 Apr 2024 17:24:08 +0100 Subject: [PATCH 0243/1377] Fixed XPT2046 syntax and using unPhone library to clean up main and TFTDisplay. --- src/graphics/TFTDisplay.cpp | 48 ++++++++++++--------------------- src/main.cpp | 16 +---------- variants/unphone/platformio.ini | 15 +++++++++-- variants/unphone/variant.cpp | 20 ++++++++++++++ variants/unphone/variant.h | 14 +++++++--- 5 files changed, 62 insertions(+), 51 deletions(-) create mode 100644 variants/unphone/variant.cpp diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index fb64553ef..ddc4df2b3 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -411,8 +411,7 @@ class LGFX : public lgfx::LGFX_Device lgfx::Panel_HX8357D _panel_instance; lgfx::Bus_SPI _bus_instance; #if defined(USE_XPT2046) - lgfx::ITouch *_touch_instance; -// lgfx::Touch_XPT2046 _touch_instance; + lgfx::Touch_XPT2046 _touch_instance; #endif public: @@ -466,8 +465,7 @@ class LGFX : public lgfx::LGFX_Device #if defined(USE_XPT2046) { // Configure settings for touch control. - _touch_instance = new lgfx::Touch_XPT2046; - auto touch_cfg = _touch_instance->config(); + auto touch_cfg = _touch_instance.config(); touch_cfg.pin_cs = TOUCH_CS; touch_cfg.x_min = 0; @@ -478,8 +476,8 @@ class LGFX : public lgfx::LGFX_Device touch_cfg.bus_shared = true; touch_cfg.offset_rotation = 1; - _touch_instance->config(touch_cfg); - //_panel_instance->setTouch(_touch_instance); + _touch_instance.config(touch_cfg); + _panel_instance.setTouch(&_touch_instance); } #endif setPanel(&_panel_instance); @@ -496,6 +494,11 @@ static LGFX *tft = nullptr; #include "TFTDisplay.h" #include +#ifdef UNPHONE +#include "unPhone.h" +extern unPhone unphone; +#endif + TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { LOG_DEBUG("TFTDisplay!\n"); @@ -561,10 +564,8 @@ void TFTDisplay::sendCommand(uint8_t com) #elif defined(ST7735_BL_V05) pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); -#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) - tft->wakeup(); - tft->powerSaveOff(); -#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) +#endif +#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); #endif @@ -576,11 +577,7 @@ void TFTDisplay::sendCommand(uint8_t com) digitalWrite(VTFT_CTRL, LOW); #endif #ifdef UNPHONE - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x04); // Backlight on - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); + unphone.backlight(true); // using unPhone library #endif #ifdef RAK14014 #elif !defined(M5STACK) @@ -598,13 +595,10 @@ void TFTDisplay::sendCommand(uint8_t com) #elif defined(ST7735_BL_V05) pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON); -#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) - tft->sleep(); - tft->powerSaveOn(); -#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) +#endif +#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); #endif - #ifdef VTFT_CTRL_V03 digitalWrite(VTFT_CTRL_V03, HIGH); #endif @@ -612,11 +606,7 @@ void TFTDisplay::sendCommand(uint8_t com) digitalWrite(VTFT_CTRL, HIGH); #endif #ifdef UNPHONE - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x00); // Backlight off - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); + unphone.backlight(false); // using unPhone library #endif #ifdef RAK14014 #elif !defined(M5STACK) @@ -690,11 +680,7 @@ bool TFTDisplay::connect() digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); #endif #ifdef UNPHONE - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x04); // Backlight on - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); + unphone.backlight(true); // using unPhone library LOG_INFO("Power to TFT Backlight\n"); #endif @@ -718,4 +704,4 @@ bool TFTDisplay::connect() return true; } -#endif +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 587bcb56e..744fda4de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -590,20 +590,6 @@ void setup() if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) screen_model = config.display.oled; -#ifdef UNPHONE - // initialise IO expander with pinmodes - Wire.beginTransmission(0x26); - Wire.write(0x06); - Wire.write(0x7A); - Wire.write(0xDD); - Wire.endTransmission(); - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x04); // Backlight on - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); -#endif - #if defined(USE_SH1107) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128 display_geometry = GEOMETRY_128_128; @@ -1017,4 +1003,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index 06314eaa3..dad9a7177 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -1,5 +1,7 @@ +; platformio.ini for unphone meshtastic + [env:unphone] -;build_type = debug ; to make it possible to step through our jtag debugger + extends = esp32s3_base board_level = extra board = unphone9 @@ -14,6 +16,15 @@ build_flags = ${esp32_base.build_flags} -D UNPHONE -I variants/unphone -D ARDUINO_USB_MODE=0 + -D UNPHONE_ACCEL=0 + -D UNPHONE_TOUCHS=0 + -D UNPHONE_SDCARD=0 + -D UNPHONE_UI0=0 + -D UNPHONE_LORA=0 + -D UNPHONE_FACTORY_MODE=0 + +build_src_filter = ${esp32_base.build_src_filter} +<../variants/unphone> lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX @ ^1.1.8 + https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic @ ^9.0.0 \ No newline at end of file diff --git a/variants/unphone/variant.cpp b/variants/unphone/variant.cpp new file mode 100644 index 000000000..3f6d1c54d --- /dev/null +++ b/variants/unphone/variant.cpp @@ -0,0 +1,20 @@ +// meshtastic/firmware/variants/unphone/variant.cpp + +#include "unPhone.h" +unPhone unphone = unPhone("meshtastic_unphone"); + +void initVariant() +{ + unphone.begin(); // initialise hardware etc. + unphone.store(unphone.buildTime); + unphone.printWakeupReason(); // what woke us up? (stored, not printed :|) + unphone.checkPowerSwitch(); // if power switch is off, shutdown + unphone.backlight(false); // setup backlight and make sure its off + + for (int i = 0; i < 3; i++) { // buzz a bit + unphone.vibe(true); + delay(150); + unphone.vibe(false); + delay(150); + } +} \ No newline at end of file diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index 9306537f2..180fdfe2c 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -1,3 +1,7 @@ +// meshtastic/firmware/variants/unphone/variant.h + +#pragma once + #define SPI_SCK 39 #define SPI_MOSI 40 #define SPI_MISO 41 @@ -28,7 +32,7 @@ #define TFT_WIDTH 320 #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 -#define TFT_OFFSET_ROTATION 6 // the unPhone's screen is wired unusually, 0 is typical value here +#define TFT_OFFSET_ROTATION 6 // unPhone's screen wired unusually, 0 typical #define TFT_INVERT false #define SCREEN_ROTATE true #define SCREEN_TRANSITION_FRAMERATE 5 @@ -37,7 +41,10 @@ #define USE_XPT2046 1 #define TOUCH_CS 38 -#define HAS_GPS 0 // the unphone doesn't have a gps module +#define HAS_GPS \ + 0 // the unphone doesn't have a gps module by default (though + // GPS featherwing -- https://www.adafruit.com/product/3133 + // -- can be added) #undef GPS_RX_PIN #undef GPS_TX_PIN @@ -49,6 +56,7 @@ #define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode #define BUTTON_NEED_PULLUP // we do need a helping hand up +#define BUTTON_PIN_ALT 45 // Button 1 - triangle - bottom button in landscape mode #define I2C_SDA 3 // I2C pins for this board #define I2C_SCL 4 @@ -58,6 +66,6 @@ // ratio of voltage divider = 3.20 (R1=100k, R2=220k) // #define ADC_MULTIPLIER 3.2 -// #define BATTERY_PIN 13 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +// #define BATTERY_PIN 13 // battery V measurement pin; vbat divider is here // #define ADC_CHANNEL ADC2_GPIO13_CHANNEL // #define BAT_MEASURE_ADC_UNIT 2 \ No newline at end of file From 385d7296fee2ee4a0ad2260dbe44591cf3836381 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Mon, 15 Apr 2024 17:37:39 +0100 Subject: [PATCH 0244/1377] strange extra edits removed wtf --- src/graphics/TFTDisplay.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index ddc4df2b3..b561f3b56 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -564,8 +564,10 @@ void TFTDisplay::sendCommand(uint8_t com) #elif defined(ST7735_BL_V05) pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); -#endif -#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) +#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) + tft->wakeup(); + tft->powerSaveOff(); +#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); #endif @@ -595,10 +597,13 @@ void TFTDisplay::sendCommand(uint8_t com) #elif defined(ST7735_BL_V05) pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON); -#endif -#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) +#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) + tft->sleep(); + tft->powerSaveOn(); +#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); #endif + #ifdef VTFT_CTRL_V03 digitalWrite(VTFT_CTRL_V03, HIGH); #endif @@ -704,4 +709,4 @@ bool TFTDisplay::connect() return true; } -#endif \ No newline at end of file +#endif From 27ae4399bc3f8c0049d3061fe454981e5e27fa73 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 15 Apr 2024 16:35:52 -0500 Subject: [PATCH 0245/1377] Zero hop always for connected node (#3634) --- src/mesh/PhoneAPI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index efbcc9558..2a69d6d56 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -430,6 +430,7 @@ bool PhoneAPI::available() auto nextNode = nodeDB->readNextMeshNode(readIndex); if (nextNode) { nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode); + nodeInfoForPhone.hops_away = nodeInfoForPhone.num == nodeDB->getNodeNum() ? 0 : nodeInfoForPhone.hops_away; nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || nodeInfoForPhone.num == nodeDB->getNodeNum(); // Our node is always a favorite } From 2f9b68e08b2ff5fd4c6aa40b9c84041d2f3351ac Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 15 Apr 2024 16:36:22 -0500 Subject: [PATCH 0246/1377] File management changes (Part 2 - Reboot instead of reformat NRF52 after two failed file saves) (#3630) * Add LoadFileState to differentiate types of success / failures * Try rebooting NRF52s with multiple failed saves * Trunkate --- src/mesh/NodeDB.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dce7e47af..73aa29bbf 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -688,9 +688,13 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ static uint8_t failedCounter = 0; failedCounter++; if (failedCounter >= 2) { - FSCom.format(); - // After formatting, the device needs to be restarted - nodeDB->resetRadioConfig(true); + LOG_ERROR("Failed to save file twice. Rebooting...\n"); + delay(100); + NVIC_SystemReset(); + // We used to blow away the filesystem here, but that's a bit extreme + // FSCom.format(); + // // After formatting, the device needs to be restarted + // nodeDB->resetRadioConfig(true); } #endif } @@ -734,6 +738,7 @@ void NodeDB::saveToDisk(int saveWhat) config.has_power = true; config.has_network = true; config.has_bluetooth = true; + saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); } @@ -745,6 +750,12 @@ void NodeDB::saveToDisk(int saveWhat) moduleConfig.has_serial = true; moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; + moduleConfig.has_neighbor_info = true; + moduleConfig.has_detection_sensor = true; + moduleConfig.has_ambient_lighting = true; + moduleConfig.has_audio = true; + moduleConfig.has_paxcounter = true; + saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); } From 441638c2eba017e842ab5b96b18f6f05dbcc2828 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 20:23:49 -0500 Subject: [PATCH 0247/1377] [create-pull-request] automated change (#3636) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 6aedf1e72..f5ad818a2 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 5 +build = 6 From 7d3175dc833f19d78754075c8fae2d3afb3686b5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 16 Apr 2024 07:22:31 -0500 Subject: [PATCH 0248/1377] More useful default input device for Pi 400 (#3639) --- bin/config-dist.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 5a8e658cb..d8cb5a9dd 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -112,7 +112,7 @@ Touchscreen: ### Configure device for direct keyboard input Input: -# KeyboardDevice: /dev/input/event0 +# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd ### From 3413b9da412fb0f197096d34de2b3fd4b90ff5ee Mon Sep 17 00:00:00 2001 From: Gareth Coleman <30833824+garethhcoleman@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:29:08 +0100 Subject: [PATCH 0249/1377] Fixed XPT2046 syntax and using unPhone library to clean up support (#3631) * Fixed XPT2046 syntax and using unPhone library to clean up main and TFTDisplay. * strange extra edits removed wtf --- src/graphics/TFTDisplay.cpp | 33 ++++++++++++--------------------- src/main.cpp | 16 +--------------- variants/unphone/platformio.ini | 15 +++++++++++++-- variants/unphone/variant.cpp | 20 ++++++++++++++++++++ variants/unphone/variant.h | 14 +++++++++++--- 5 files changed, 57 insertions(+), 41 deletions(-) create mode 100644 variants/unphone/variant.cpp diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index fb64553ef..b561f3b56 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -411,8 +411,7 @@ class LGFX : public lgfx::LGFX_Device lgfx::Panel_HX8357D _panel_instance; lgfx::Bus_SPI _bus_instance; #if defined(USE_XPT2046) - lgfx::ITouch *_touch_instance; -// lgfx::Touch_XPT2046 _touch_instance; + lgfx::Touch_XPT2046 _touch_instance; #endif public: @@ -466,8 +465,7 @@ class LGFX : public lgfx::LGFX_Device #if defined(USE_XPT2046) { // Configure settings for touch control. - _touch_instance = new lgfx::Touch_XPT2046; - auto touch_cfg = _touch_instance->config(); + auto touch_cfg = _touch_instance.config(); touch_cfg.pin_cs = TOUCH_CS; touch_cfg.x_min = 0; @@ -478,8 +476,8 @@ class LGFX : public lgfx::LGFX_Device touch_cfg.bus_shared = true; touch_cfg.offset_rotation = 1; - _touch_instance->config(touch_cfg); - //_panel_instance->setTouch(_touch_instance); + _touch_instance.config(touch_cfg); + _panel_instance.setTouch(&_touch_instance); } #endif setPanel(&_panel_instance); @@ -496,6 +494,11 @@ static LGFX *tft = nullptr; #include "TFTDisplay.h" #include +#ifdef UNPHONE +#include "unPhone.h" +extern unPhone unphone; +#endif + TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { LOG_DEBUG("TFTDisplay!\n"); @@ -576,11 +579,7 @@ void TFTDisplay::sendCommand(uint8_t com) digitalWrite(VTFT_CTRL, LOW); #endif #ifdef UNPHONE - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x04); // Backlight on - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); + unphone.backlight(true); // using unPhone library #endif #ifdef RAK14014 #elif !defined(M5STACK) @@ -612,11 +611,7 @@ void TFTDisplay::sendCommand(uint8_t com) digitalWrite(VTFT_CTRL, HIGH); #endif #ifdef UNPHONE - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x00); // Backlight off - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); + unphone.backlight(false); // using unPhone library #endif #ifdef RAK14014 #elif !defined(M5STACK) @@ -690,11 +685,7 @@ bool TFTDisplay::connect() digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); #endif #ifdef UNPHONE - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x04); // Backlight on - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); + unphone.backlight(true); // using unPhone library LOG_INFO("Power to TFT Backlight\n"); #endif diff --git a/src/main.cpp b/src/main.cpp index 587bcb56e..744fda4de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -590,20 +590,6 @@ void setup() if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) screen_model = config.display.oled; -#ifdef UNPHONE - // initialise IO expander with pinmodes - Wire.beginTransmission(0x26); - Wire.write(0x06); - Wire.write(0x7A); - Wire.write(0xDD); - Wire.endTransmission(); - Wire.beginTransmission(0x26); - Wire.write(0x02); - Wire.write(0x04); // Backlight on - Wire.write(0x22); // G&B LEDs off - Wire.endTransmission(); -#endif - #if defined(USE_SH1107) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128 display_geometry = GEOMETRY_128_128; @@ -1017,4 +1003,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index 06314eaa3..dad9a7177 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -1,5 +1,7 @@ +; platformio.ini for unphone meshtastic + [env:unphone] -;build_type = debug ; to make it possible to step through our jtag debugger + extends = esp32s3_base board_level = extra board = unphone9 @@ -14,6 +16,15 @@ build_flags = ${esp32_base.build_flags} -D UNPHONE -I variants/unphone -D ARDUINO_USB_MODE=0 + -D UNPHONE_ACCEL=0 + -D UNPHONE_TOUCHS=0 + -D UNPHONE_SDCARD=0 + -D UNPHONE_UI0=0 + -D UNPHONE_LORA=0 + -D UNPHONE_FACTORY_MODE=0 + +build_src_filter = ${esp32_base.build_src_filter} +<../variants/unphone> lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX @ ^1.1.8 + https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic @ ^9.0.0 \ No newline at end of file diff --git a/variants/unphone/variant.cpp b/variants/unphone/variant.cpp new file mode 100644 index 000000000..3f6d1c54d --- /dev/null +++ b/variants/unphone/variant.cpp @@ -0,0 +1,20 @@ +// meshtastic/firmware/variants/unphone/variant.cpp + +#include "unPhone.h" +unPhone unphone = unPhone("meshtastic_unphone"); + +void initVariant() +{ + unphone.begin(); // initialise hardware etc. + unphone.store(unphone.buildTime); + unphone.printWakeupReason(); // what woke us up? (stored, not printed :|) + unphone.checkPowerSwitch(); // if power switch is off, shutdown + unphone.backlight(false); // setup backlight and make sure its off + + for (int i = 0; i < 3; i++) { // buzz a bit + unphone.vibe(true); + delay(150); + unphone.vibe(false); + delay(150); + } +} \ No newline at end of file diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index 9306537f2..180fdfe2c 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -1,3 +1,7 @@ +// meshtastic/firmware/variants/unphone/variant.h + +#pragma once + #define SPI_SCK 39 #define SPI_MOSI 40 #define SPI_MISO 41 @@ -28,7 +32,7 @@ #define TFT_WIDTH 320 #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 -#define TFT_OFFSET_ROTATION 6 // the unPhone's screen is wired unusually, 0 is typical value here +#define TFT_OFFSET_ROTATION 6 // unPhone's screen wired unusually, 0 typical #define TFT_INVERT false #define SCREEN_ROTATE true #define SCREEN_TRANSITION_FRAMERATE 5 @@ -37,7 +41,10 @@ #define USE_XPT2046 1 #define TOUCH_CS 38 -#define HAS_GPS 0 // the unphone doesn't have a gps module +#define HAS_GPS \ + 0 // the unphone doesn't have a gps module by default (though + // GPS featherwing -- https://www.adafruit.com/product/3133 + // -- can be added) #undef GPS_RX_PIN #undef GPS_TX_PIN @@ -49,6 +56,7 @@ #define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode #define BUTTON_NEED_PULLUP // we do need a helping hand up +#define BUTTON_PIN_ALT 45 // Button 1 - triangle - bottom button in landscape mode #define I2C_SDA 3 // I2C pins for this board #define I2C_SCL 4 @@ -58,6 +66,6 @@ // ratio of voltage divider = 3.20 (R1=100k, R2=220k) // #define ADC_MULTIPLIER 3.2 -// #define BATTERY_PIN 13 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +// #define BATTERY_PIN 13 // battery V measurement pin; vbat divider is here // #define ADC_CHANNEL ADC2_GPIO13_CHANNEL // #define BAT_MEASURE_ADC_UNIT 2 \ No newline at end of file From a01069a549584937ced04ab741f57cd35c110837 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 17 Apr 2024 00:36:14 +1200 Subject: [PATCH 0250/1377] No more printing power-state changes to screen (#3640) --- src/PowerFSM.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ac48e664c..4f42b36b5 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -185,10 +185,12 @@ static void powerEnter() screen->setOn(true); setBluetoothEnable(true); // within enter() the function getState() returns the state we came from - if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 && + + // Mothballed: print change of power-state to device screen + /* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 && strcmp(powerFSM.getState()->name, "DARK") != 0) { screen->print("Powered...\n"); - } + }*/ } } @@ -205,8 +207,10 @@ static void powerExit() { screen->setOn(true); setBluetoothEnable(true); - if (!isPowered()) - screen->print("Unpowered...\n"); + + // Mothballed: print change of power-state to device screen + /*if (!isPowered()) + screen->print("Unpowered...\n");*/ } static void onEnter() From 699ea7467299c8c5e19711111f0ad98f1c14b894 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 08:01:32 -0500 Subject: [PATCH 0251/1377] [create-pull-request] automated change (#3642) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index f92900c5f..ecf105f66 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4 +Subproject commit ecf105f66d182531423b73f4408c53701313c4eb diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index e674e28bb..67b2edd15 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -146,6 +146,8 @@ typedef enum _meshtastic_HardwareModel { /* Teledatics TD-LORAC NRF52840 based M.2 LoRA module Compatible with the TD-WRLS development board */ meshtastic_HardwareModel_TD_LORAC = 60, + /* CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3 */ + meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From e813703bf5298b60eb5357608b6e5009ebd87def Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:00:16 +0100 Subject: [PATCH 0252/1377] Add support for CDEBYTE_EoRa-S3 (#3613) * Create CDEBYTE_EoRa-S3.json * Update CDEBYTE_EoRa-S3.json * Update architecture.h * Create variant.h * Create platformio.ini * Create pins_arduino.h * Update variant.h * Update variant.h * Update variant.h * Trunk format * update variant.h --------- Co-authored-by: Ben Meadors Co-authored-by: S5NC <> --- boards/CDEBYTE_EoRa-S3.json | 38 +++++++++++++++ src/platform/esp32/architecture.h | 2 + variants/CDEBYTE_EoRa-S3/pins_arduino.h | 37 +++++++++++++++ variants/CDEBYTE_EoRa-S3/platformio.ini | 8 ++++ variants/CDEBYTE_EoRa-S3/variant.h | 63 +++++++++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 boards/CDEBYTE_EoRa-S3.json create mode 100644 variants/CDEBYTE_EoRa-S3/pins_arduino.h create mode 100644 variants/CDEBYTE_EoRa-S3/platformio.ini create mode 100644 variants/CDEBYTE_EoRa-S3/variant.h diff --git a/boards/CDEBYTE_EoRa-S3.json b/boards/CDEBYTE_EoRa-S3.json new file mode 100644 index 000000000..9ecee3c9f --- /dev/null +++ b/boards/CDEBYTE_EoRa-S3.json @@ -0,0 +1,38 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D CDEBYTE_EORA_S3", + "-D ARDUINO_USB_CDC_ON_BOOT=1", + "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1", + "-D BOARD_HAS_PSRAM" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "dio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "CDEBYTE_EoRa-S3" + }, + "connectivity": ["wifi"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "CDEBYTE EoRa-S3", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.cdebyte.com/Module-Testkits-EoRaPI", + "vendor": "CDEBYTE" +} diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 6855265ac..15e437bb5 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -117,6 +117,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER #elif defined(TLORA_T3S3_V1) #define HW_VENDOR meshtastic_HardwareModel_TLORA_T3_S3 +#elif defined(CDEBYTE_ELORA_S3) +#define HW_VENDOR meshtastic_HardwareModel_CDEBYTE_ELORA_S3 #elif defined(BETAFPV_2400_TX) #define HW_VENDOR meshtastic_HardwareModel_BETAFPV_2400_TX #elif defined(NANO_G1_EXPLORER) diff --git a/variants/CDEBYTE_EoRa-S3/pins_arduino.h b/variants/CDEBYTE_EoRa-S3/pins_arduino.h new file mode 100644 index 000000000..38a9103f0 --- /dev/null +++ b/variants/CDEBYTE_EoRa-S3/pins_arduino.h @@ -0,0 +1,37 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) \ + (((p) < 48) ? (p) : -1) // Maybe it should be <= 48 but this is from a trustworthy source so it is likely correct +#define digitalPinHasPWM(p) (p < 46) + +// Serial +static const uint8_t TX = UART_TX; +static const uint8_t RX = UART_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = LORA_CS; +static const uint8_t SCK = LORA_SCK; +static const uint8_t MOSI = LORA_MOSI; +static const uint8_t MISO = LORA_MISO; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = I2C_SCL; +static const uint8_t SDA = I2C_SDA; + +#endif /* Pins_Arduino_h */ diff --git a/variants/CDEBYTE_EoRa-S3/platformio.ini b/variants/CDEBYTE_EoRa-S3/platformio.ini new file mode 100644 index 000000000..1ff54de88 --- /dev/null +++ b/variants/CDEBYTE_EoRa-S3/platformio.ini @@ -0,0 +1,8 @@ +[env:CDEBYTE_EoRa-S3] +extends = esp32s3_base +board = CDEBYTE_EoRa-S3 +build_flags = + ${esp32s3_base.build_flags} + -D CDEBYTE_EORA_S3 + -I variants/CDEBYTE_EoRa-S3 + -D GPS_POWER_TOGGLE diff --git a/variants/CDEBYTE_EoRa-S3/variant.h b/variants/CDEBYTE_EoRa-S3/variant.h new file mode 100644 index 000000000..5da99667b --- /dev/null +++ b/variants/CDEBYTE_EoRa-S3/variant.h @@ -0,0 +1,63 @@ +// LED - status indication +#define LED_PIN 37 + +// Button - user interface +#define BUTTON_PIN 0 // This is the BOOT button, and it has its own pull-up resistor + +// SD card - TODO: test, currently untested, copied from T3S3 variant +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +// TODO: rename this to make this SD-card specific +#define SPI_CS 13 +#define SPI_SCK 14 +#define SPI_MOSI 11 +#define SPI_MISO 2 +// FIXME: there are two other SPI pins that are not defined here +// Compatibility +#define SDCARD_CS SPI_CS + +// Battery voltage monitoring - TODO: test, currently untested, copied from T3S3 variant +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_MULTIPLIER \ + 2.11 // ratio of voltage divider = 2.0 (R10=1M, R13=1M), plus some undervoltage correction - TODO: this was carried over from + // the T3S3, test to see if the undervoltage correction is needed. + +// Display - OLED connected via I2C by the default hardware configuration +#define HAS_SCREEN 1 +#define USE_SSD1306 +#define I2C_SCL 17 +#define I2C_SDA 18 + +// UART - The 1mm JST SH connector closest to the USB-C port +#define UART_TX 43 +#define UART_RX 44 + +// Peripheral I2C - The 1mm JST SH connector furthest from the USB-C port which follows Adafruit connection standard. There are no +// pull-up resistors on these lines, the downstream device needs to include them. TODO: test, currently untested +#define I2C_SCL1 21 +#define I2C_SDA1 10 + +// Radio +#define USE_SX1262 // CDEBYTE EoRa-S3-900TB <- CDEBYTE E22-900MM22S <- Semtech SX1262 +#define USE_SX1268 // CDEBYTE EoRa-S3-400TB <- CDEBYTE E22-400MM22S <- Semtech SX1268 + +#define SX126X_CS 7 +#define LORA_SCK 5 +#define LORA_MOSI 6 +#define LORA_MISO 3 +#define SX126X_RESET 8 +#define SX126X_BUSY 34 +#define SX126X_DIO1 33 + +#define SX126X_DIO2_AS_RF_SWITCH // All switching is performed with DIO2, it is automatically inverted using circuitry. +// CDEBYTE EoRa-S3 uses an XTAL, thus we do not need DIO3 as TCXO voltage reference. Don't define SX126X_DIO3_TCXO_VOLTAGE for +// simplicity rather than defining it as 0. +#define SX126X_MAX_POWER \ + 22 // E22-900MM22S and E22-400MM22S have a raw SX1262 or SX1268 respsectively, they are rated to output up and including 22 + // dBm out of their SX126x IC. + +// Compatibility with old variant.h file structure - FIXME: this should be done in the respective radio interface modules to clean +// up all variants. +#define LORA_CS SX126X_CS +#define LORA_DIO1 SX126X_DIO1 \ No newline at end of file From 9599549477d015a747ae1a828ea75ee73bc6eb77 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Tue, 16 Apr 2024 22:03:36 +0800 Subject: [PATCH 0253/1377] Add configuration option for LoRa Region Code override for region-locked builds/variants (#3540) The main use case for this will be to create a custom Heltec WiFi LoRa 32 V3 SG_923 variant, which will be pre-flashed and sent for regulatory approval for retail sale. Signed-off-by: Andrew Yong Co-authored-by: Ben Meadors --- src/configuration.h | 7 +++++++ src/mesh/RadioInterface.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/configuration.h b/src/configuration.h index 37b67f666..701e07a32 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -74,6 +74,13 @@ along with this program. If not, see . #define RTC_DATA_ATTR #endif +// ----------------------------------------------------------------------------- +// Regulatory overrides for producing regional builds +// ----------------------------------------------------------------------------- + +// Define if region should override user saved region +// #define LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923 + // ----------------------------------------------------------------------------- // Feature toggles // ----------------------------------------------------------------------------- diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 3aac9dfce..63912a03e 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -151,10 +151,16 @@ static uint8_t bytes[MAX_RHPACKETLEN]; void initRegion() { const RegionInfo *r = regions; +#ifdef LORA_REGIONCODE + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != LORA_REGIONCODE; r++) + ; + LOG_INFO("Wanted region %d, regulatory override to %s\n", config.lora.region, r->name); +#else for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != config.lora.region; r++) ; - myRegion = r; LOG_INFO("Wanted region %d, using %s\n", config.lora.region, r->name); +#endif + myRegion = r; } /** From 55c9c3b29843a3301c5982ad6a46561d80d0fd94 Mon Sep 17 00:00:00 2001 From: David Ellefsen <93522+titan098@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:03:51 +0200 Subject: [PATCH 0254/1377] Support for the ATGM336H series of GPS modules (#3610) Co-authored-by: Ben Meadors --- src/RedirectablePrint.cpp | 8 +- src/gps/GPS.cpp | 163 +++++++++++++++++++++++++++++++++++++- src/gps/GPS.h | 18 ++++- src/gps/cas.h | 63 +++++++++++++++ 4 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 src/gps/cas.h diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 16906e2e0..e09e5fe30 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -182,11 +182,11 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len) { const char alphabet[17] = "0123456789abcdef"; - log(logLevel, " +------------------------------------------------+ +----------------+\n"); - log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n"); + log(logLevel, " +------------------------------------------------+ +----------------+\n"); + log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n"); for (uint16_t i = 0; i < len; i += 16) { if (i % 128 == 0) - log(logLevel, " +------------------------------------------------+ +----------------+\n"); + log(logLevel, " +------------------------------------------------+ +----------------+\n"); char s[] = "| | | |\n"; uint8_t ix = 1, iy = 52; for (uint8_t j = 0; j < 16; j++) { @@ -208,7 +208,7 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16 log(logLevel, "."); log(logLevel, s); } - log(logLevel, " +------------------------------------------------+ +----------------+\n"); + log(logLevel, " +------------------------------------------------+ +----------------+\n"); } std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 6a0e3e44a..0d0bfd9a2 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -7,6 +7,8 @@ #include "main.h" // pmu_found #include "sleep.h" + +#include "cas.h" #include "ubx.h" #ifdef ARCH_PORTDUINO @@ -51,6 +53,28 @@ void GPS::UBXChecksum(uint8_t *message, size_t length) message[length - 1] = CK_B; } +// Calculate the checksum for a CAS packet +void GPS::CASChecksum(uint8_t *message, size_t length) +{ + uint32_t cksum = ((uint32_t)message[5] << 24); // Message ID + cksum += ((uint32_t)message[4]) << 16; // Class + cksum += message[2]; // Payload Len + + // Iterate over the payload as a series of uint32_t's and + // accumulate the cksum + uint32_t *payload = (uint32_t *)(message + 6); + for (size_t i = 0; i < (length - 10) / 4; i++) { + uint32_t p = payload[i]; + cksum += p; + } + + // Place the checksum values in the message + message[length - 4] = (cksum & 0xFF); + message[length - 3] = (cksum & (0xFF << 8)) >> 8; + message[length - 2] = (cksum & (0xFF << 16)) >> 16; + message[length - 1] = (cksum & (0xFF << 24)) >> 24; +} + // Function to create a ublox packet for editing in memory uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg) { @@ -72,6 +96,41 @@ uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_siz return (payload_size + 8); } +// Function to create a CAS packet for editing in memory +uint8_t GPS::makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg) +{ + // General CAS structure + // | H1 | H2 | payload_len | cls | msg | Payload ... | Checksum | + // Size: | 1 | 1 | 2 | 1 | 1 | payload_len | 4 | + // Pos: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ... | 6 + payload_len ... | + // |------|------|-------------|------|------|------|--------------|---------------------------| + // | 0xBA | 0xCE | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX ... | 0xXX | 0xXX | 0xXX | 0xXX | + + // Construct the CAS packet + UBXscratch[0] = 0xBA; // header 1 (0xBA) + UBXscratch[1] = 0xCE; // header 2 (0xCE) + UBXscratch[2] = payload_size; // length 1 + UBXscratch[3] = 0; // length 2 + UBXscratch[4] = class_id; // class + UBXscratch[5] = msg_id; // id + + UBXscratch[6 + payload_size] = 0x00; // Checksum + UBXscratch[7 + payload_size] = 0x00; + UBXscratch[8 + payload_size] = 0x00; + UBXscratch[9 + payload_size] = 0x00; + + for (int i = 0; i < payload_size; i++) { + UBXscratch[6 + i] = pgm_read_byte(&msg[i]); + } + CASChecksum(UBXscratch, (payload_size + 10)); + +#if defined(GPS_DEBUG) && defined(DEBUG_PORT) + LOG_DEBUG("Constructed CAS packet: \n"); + DEBUG_PORT.hexDump(MESHTASTIC_LOG_LEVEL_DEBUG, UBXscratch, payload_size + 10); +#endif + return (payload_size + 10); +} + GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis) { uint8_t buffer[768] = {0}; @@ -81,6 +140,7 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis) while (millis() < startTimeout) { if (_serial_gps->available()) { b = _serial_gps->read(); + #ifdef GPS_DEBUG LOG_DEBUG("%02X", (char *)buffer); #endif @@ -104,6 +164,67 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis) return GNSS_RESPONSE_NONE; } +GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) +{ + uint32_t startTime = millis(); + uint8_t buffer[CAS_ACK_NACK_MSG_SIZE] = {0}; + uint8_t bufferPos = 0; + + // CAS-ACK-(N)ACK structure + // | H1 | H2 | Payload Len | cls | msg | Payload | Checksum (4) | + // | | | | | | Cls | Msg | Reserved | | + // |------|------|-------------|------|------|------|------|-------------|---------------------------| + // ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX | + // ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX | + + while (millis() - startTime < waitMillis) { + if (_serial_gps->available()) { + buffer[bufferPos++] = _serial_gps->read(); + + // keep looking at the first two bytes of buffer until + // we have found the CAS frame header (0xBA, 0xCE), if not + // keep reading bytes until we find a frame header or we run + // out of time. + if ((bufferPos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) { + buffer[0] = buffer[1]; + buffer[1] = 0; + bufferPos = 1; + } + } + + // we have read all the bytes required for the Ack/Nack (14-bytes) + // and we must have found a frame to get this far + if (bufferPos == sizeof(buffer) - 1) { + uint8_t msg_cls = buffer[4]; // message class should be 0x05 + uint8_t msg_msg_id = buffer[5]; // message id should be 0x00 or 0x01 + uint8_t payload_cls = buffer[6]; // payload class id + uint8_t payload_msg = buffer[7]; // payload message id + + // Check for an ACK-ACK for the specified class and message id + if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) { +#ifdef GPS_DEBUG + LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime); +#endif + return GNSS_RESPONSE_OK; + } + + // Check for an ACK-NACK for the specified class and message id + if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) { +#ifdef GPS_DEBUG + LOG_WARN("Got NACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime); +#endif + return GNSS_RESPONSE_NAK; + } + + // This isn't the frame we are looking for, clear the buffer + // and try again until we run out of time. + memset(buffer, 0x0, sizeof(buffer)); + bufferPos = 0; + } + } + return GNSS_RESPONSE_NONE; +} + GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) { uint8_t b; @@ -313,6 +434,33 @@ bool GPS::setup() // Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s) _serial_gps->write("$PMTK886,1*29\r\n"); delay(250); + } else if (gnssModel == GNSS_MODEL_ATGM336H) { + // Set the intial configuration of the device - these _should_ work for most AT6558 devices + msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF); + _serial_gps->write(UBXscratch, msglen); + if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) { + LOG_WARN("ATGM336H - Could not set Configuration"); + } + + // Set the update frequence to 1Hz + msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ); + _serial_gps->write(UBXscratch, msglen); + if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) { + LOG_WARN("ATGM336H - Could not set Update Frequency"); + } + + // Set the NEMA output messages + // Ask for only RMC and GGA + uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA}; + for (int i = 0; i < sizeof(fields); i++) { + // Construct a CAS-CFG-MSG packet + uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00}; + msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet); + _serial_gps->write(UBXscratch, msglen); + if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) { + LOG_WARN("ATGM336H - Could not enable NMEA MSG: %d\n", fields[i]); + } + } } else if (gnssModel == GNSS_MODEL_UC6580) { // The Unicore UC6580 can use a lot of sat systems, enable it to // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS @@ -948,10 +1096,18 @@ GnssModel_t GPS::probe(int serialSpeed) uint8_t buffer[768] = {0}; delay(100); - // Close all NMEA sentences , Only valid for L76K MTK platform + // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); delay(20); + // Get version information + clearBuffer(); + _serial_gps->write("$PCAS06,1*1A\r\n"); + if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) { + LOG_INFO("ATGM336H GNSS init succeeded, using ATGM336H Module\n"); + return GNSS_MODEL_ATGM336H; + } + // Get version information clearBuffer(); _serial_gps->write("$PCAS06,0*1B\r\n"); @@ -1216,6 +1372,11 @@ bool GPS::factoryReset() LOG_INFO("GNSS Factory Reset via PCAS10,3\n"); _serial_gps->write("$PCAS10,3*1F\r\n"); delay(100); + } else if (gnssModel == GNSS_MODEL_ATGM336H) { + LOG_INFO("Factory Reset via CAS-CFG-RST\n"); + uint8_t msglen = makeCASPacket(0x06, 0x02, sizeof(_message_CAS_CFG_RST_FACTORY), _message_CAS_CFG_RST_FACTORY); + _serial_gps->write(UBXscratch, msglen); + delay(100); } else { // fire this for good measure, if we have an L76B - won't harm other devices. _serial_gps->write("$PMTK104*37\r\n"); diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 49f27e29f..77c6c0269 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -22,7 +22,14 @@ struct uBloxGnssModelInfo { char extension[10][30]; }; -typedef enum { GNSS_MODEL_MTK, GNSS_MODEL_UBLOX, GNSS_MODEL_UC6580, GNSS_MODEL_UNKNOWN, GNSS_MODEL_MTK_L76B } GnssModel_t; +typedef enum { + GNSS_MODEL_ATGM336H, + GNSS_MODEL_MTK, + GNSS_MODEL_UBLOX, + GNSS_MODEL_UC6580, + GNSS_MODEL_UNKNOWN, + GNSS_MODEL_MTK_L76B +} GnssModel_t; typedef enum { GNSS_RESPONSE_NONE, @@ -133,6 +140,11 @@ class GPS : private concurrency::OSThread static const uint8_t _message_VALSET_DISABLE_SBAS_RAM[]; static const uint8_t _message_VALSET_DISABLE_SBAS_BBR[]; + // CASIC commands for ATGM336H + static const uint8_t _message_CAS_CFG_RST_FACTORY[]; + static const uint8_t _message_CAS_CFG_NAVX_CONF[]; + static const uint8_t _message_CAS_CFG_RATE_1HZ[]; + meshtastic_Position p = meshtastic_Position_init_default; GPS() : concurrency::OSThread("GPS") {} @@ -174,6 +186,7 @@ class GPS : private concurrency::OSThread // Create a ublox packet for editing in memory uint8_t makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg); + uint8_t makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg); // scratch space for creating ublox packets uint8_t UBXscratch[250] = {0}; @@ -184,6 +197,8 @@ class GPS : private concurrency::OSThread GPS_RESPONSE getACK(uint8_t c, uint8_t i, uint32_t waitMillis); GPS_RESPONSE getACK(const char *message, uint32_t waitMillis); + GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis); + /** * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode * @@ -243,6 +258,7 @@ class GPS : private concurrency::OSThread // Calculate checksum void UBXChecksum(uint8_t *message, size_t length); + void CASChecksum(uint8_t *message, size_t length); /** Get how long we should stay looking for each aquisition */ diff --git a/src/gps/cas.h b/src/gps/cas.h new file mode 100644 index 000000000..53d75cda9 --- /dev/null +++ b/src/gps/cas.h @@ -0,0 +1,63 @@ +#pragma once + +// CASIC binary message definitions +// Reference: https://www.icofchina.com/d/file/xiazai/2020-09-22/20f1b42b3a11ac52089caf3603b43fb5.pdf +// ATGM33H-5N: https://www.icofchina.com/pro/mokuai/2016-08-01/4.html +// (https://www.icofchina.com/d/file/xiazai/2016-12-05/b5c57074f4b1fcc62ba8c7868548d18a.pdf) + +// NEMA (Class ID - 0x4e) message IDs +#define CAS_NEMA_GGA 0x00 +#define CAS_NEMA_GLL 0x01 +#define CAS_NEMA_GSA 0x02 +#define CAS_NEMA_GSV 0x03 +#define CAS_NEMA_RMC 0x04 +#define CAS_NEMA_VTG 0x05 +#define CAS_NEMA_GST 0x07 +#define CAS_NEMA_ZDA 0x08 +#define CAS_NEMA_DHV 0x0D + +// Size of a CAS-ACK-(N)ACK message (14 bytes) +#define CAS_ACK_NACK_MSG_SIZE 0x0E + +// CFG-RST (0x06, 0x02) +// Factory reset +const uint8_t GPS::_message_CAS_CFG_RST_FACTORY[] = { + 0xFF, 0x03, // Fields to clear + 0x01, // Reset Mode: Controlled Software reset + 0x03 // Startup Mode: Factory +}; + +// CFG_RATE (0x06, 0x01) +// 1HZ update rate, this should always be the case after +// factory reset but update it regardless +const uint8_t GPS::_message_CAS_CFG_RATE_1HZ[] = { + 0xE8, 0x03, // Update Rate: 0x03E8 = 1000ms + 0x00, 0x00 // Reserved +}; + +// CFG-NAVX (0x06, 0x07) +// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system +// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable +// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used. +const uint8_t GPS::_message_CAS_CFG_NAVX_CONF[] = { + 0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings + 0x03, // Dynamic Mode: Automotive + 0x03, // Fix Mode: Auto 2D/3D + 0x00, // Min SV + 0x00, // Max SVs + 0x00, // Min CNO + 0x00, // Reserved1 + 0x00, // Init 3D fix + 0x00, // Min Elevation + 0x00, // Dr Limit + 0x07, // Nav System: 2^0 = GPS, 2^1 = BDS 2^2 = GLONASS: 2^3 + // 3=GPS+BDS, 7=GPS+BDS+GLONASS + 0x00, 0x00, // Rollover Week + 0x00, 0x00, 0x00, 0x00, // Fix Altitude + 0x00, 0x00, 0x00, 0x00, // Fix Height Error + 0x00, 0x00, 0x00, 0x00, // PDOP Maximum + 0x00, 0x00, 0x00, 0x00, // TDOP Maximum + 0x00, 0x00, 0x00, 0x00, // Position Accuracy Max + 0x00, 0x00, 0x00, 0x00, // Time Accuracy Max + 0x00, 0x00, 0x00, 0x00 // Static Hold Threshold +}; \ No newline at end of file From 8a3322fbcbb470df00e71dbe0b861241e860a29f Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Tue, 16 Apr 2024 21:28:12 +0100 Subject: [PATCH 0255/1377] rgb led support for unPhone --- src/AmbientLightingThread.h | 25 ++++++++++-- src/detect/ScanI2C.h | 3 +- src/main.cpp | 20 +++++++++- src/modules/ExternalNotificationModule.cpp | 45 +++++++++++++++++++++- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 98ccedde4..fd3c66cda 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -5,6 +5,11 @@ NCP5623 rgb; #endif +#ifdef UNPHONE +#include "unPhone.h" +extern unPhone unphone; +#endif + namespace concurrency { class AmbientLightingThread : public concurrency::OSThread @@ -20,8 +25,8 @@ class AmbientLightingThread : public concurrency::OSThread // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; -#ifdef HAS_NCP5623 _type = type; +#ifdef HAS_NCP5623 if (_type == ScanI2C::DeviceType::NONE) { LOG_DEBUG("AmbientLightingThread disabling due to no RGB leds found on I2C bus\n"); disable(); @@ -37,14 +42,23 @@ class AmbientLightingThread : public concurrency::OSThread rgb.begin(); setLighting(); } +#endif +#ifdef UNPHONE + if (!moduleConfig.ambient_lighting.led_state) { + LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n"); + disable(); + return; + } + LOG_DEBUG("AmbientLightingThread initializing\n"); + setLighting(); #endif } protected: int32_t runOnce() override { -#ifdef HAS_NCP5623 - if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) { +#if defined(HAS_NCP5623) || defined(UNPHONE) + if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::RGBLED_CA) && moduleConfig.ambient_lighting.led_state) { setLighting(); return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification } else { @@ -68,6 +82,11 @@ class AmbientLightingThread : public concurrency::OSThread LOG_DEBUG("Initializing Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); +#endif +#ifdef UNPHONE + unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + LOG_DEBUG("Initializing Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, + moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif } }; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c8fcfee10..b4341bcc0 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,9 +41,8 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, -#ifdef HAS_NCP5623 + RGBLED_CA, NCP5623, -#endif } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/main.cpp b/src/main.cpp index 744fda4de..4b3212f5f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -494,11 +494,15 @@ void setup() * "found". */ -// Only one supported RGB LED currently +// Only one supported I2C RGB LED currently (plus common anode RGB LED used by the unPhone) #ifdef HAS_NCP5623 rgb_found = i2cScanner->find(ScanI2C::DeviceType::NCP5623); #endif +#ifdef UNPHONE + rgb_found.type = ScanI2C::DeviceType::RGBLED_CA; +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) auto acc_info = i2cScanner->firstAccelerometer(); accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found; @@ -590,6 +594,20 @@ void setup() if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) screen_model = config.display.oled; +#ifdef UNPHONE + // initialise IO expander with pinmodes + Wire.beginTransmission(0x26); + Wire.write(0x06); + Wire.write(0x7A); + Wire.write(0xDD); + Wire.endTransmission(); + Wire.beginTransmission(0x26); + Wire.write(0x02); + Wire.write(0x04); // Backlight on + Wire.write(0x22); // G&B LEDs off + Wire.endTransmission(); +#endif + #if defined(USE_SH1107) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128 display_geometry = GEOMETRY_128_128; diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index a38b231af..be8fd2be2 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -36,6 +36,18 @@ uint8_t brightnessValues[] = {0, 10, 20, 30, 50, 90, 160, 170}; // blue gets mul bool ascending = true; #endif +#ifdef UNPHONE +#include "unPhone.h" +extern unPhone unphone; + +uint8_t red = 0; +uint8_t green = 0; +uint8_t blue = 0; +uint8_t colorState = 1; +const uint8_t duration = 15; +uint8_t counter = 0; +#endif + #ifndef PIN_BUZZER #define PIN_BUZZER false #endif @@ -72,7 +84,6 @@ int32_t ExternalNotificationModule::runOnce() if (!moduleConfig.external_notification.enabled) { return INT32_MAX; // we don't need this thread here... } else { - bool isPlaying = rtttl::isPlaying(); #ifdef HAS_I2S isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); @@ -133,6 +144,25 @@ int32_t ExternalNotificationModule::runOnce() } #endif +#ifdef UNPHONE + if (rgb_found.type == ScanI2C::RGBLED_CA) { + red = colorState & 4; // Red enabled on colorState = 4,5,6,7 + green = colorState & 2; // Green enabled on colorState = 2,3,6,7 + blue = colorState & 1; // Blue enabled on colorState = 1,3,5,7 + unphone.rgb(red, green, blue); + LOG_DEBUG("RGB runOnce: %i, %i, %i\n", red, green, blue); + + counter++; // tick on + if (counter > duration) { + counter = 0; + colorState++; // next color + if (colorState > 7) { + colorState = 1; + } + } + } +#endif + #ifdef T_WATCH_S3 drv.go(); #endif @@ -197,6 +227,11 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) rgb.setColor(red, green, blue); } #endif +#ifdef UNPHONE + if (rgb_found.type == ScanI2C::RGBLED_CA) { + unphone.rgb(red, green, blue); + } +#endif #ifdef T_WATCH_S3 drv.go(); #endif @@ -230,6 +265,14 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) rgb.setColor(red, green, blue); } #endif +#ifdef UNPHONE + if (rgb_found.type == ScanI2C::RGBLED_CA) { + red = 0; + green = 0; + blue = 0; + unphone.rgb(red, green, blue); + } +#endif #ifdef T_WATCH_S3 drv.stop(); #endif From 0632b96fcbeda61f4b21d236c4047429e7057a58 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Tue, 16 Apr 2024 21:40:13 +0100 Subject: [PATCH 0256/1377] just tiny tweak to minimise changes --- src/modules/ExternalNotificationModule.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index be8fd2be2..304b93389 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -84,7 +84,10 @@ int32_t ExternalNotificationModule::runOnce() if (!moduleConfig.external_notification.enabled) { return INT32_MAX; // we don't need this thread here... } else { - bool isPlaying = rtttl::isPlaying(); + + bool isPlaying = rtttl::isPlaying();This PR just tidies up support for the unPhone by using its [library](https://gitlab.com/hamishcunningham/unphonelibrary) + +Also fixes incomplete syntax for the touchscreen driver invocation for XPT2046 when attached to a HX8357. #ifdef HAS_I2S isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); #endif @@ -551,4 +554,4 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) if (changed) { nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } -} \ No newline at end of file +} From afb4de21d9331a435281766b02af3c07080cee0a Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Tue, 16 Apr 2024 22:37:57 +0100 Subject: [PATCH 0257/1377] yet another random edit, think i'm brushing the touchpad or perhaps my computer is possessed by the devil determined to make me look foolish --- src/modules/ExternalNotificationModule.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 304b93389..be8fd2be2 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -84,10 +84,7 @@ int32_t ExternalNotificationModule::runOnce() if (!moduleConfig.external_notification.enabled) { return INT32_MAX; // we don't need this thread here... } else { - - bool isPlaying = rtttl::isPlaying();This PR just tidies up support for the unPhone by using its [library](https://gitlab.com/hamishcunningham/unphonelibrary) - -Also fixes incomplete syntax for the touchscreen driver invocation for XPT2046 when attached to a HX8357. + bool isPlaying = rtttl::isPlaying(); #ifdef HAS_I2S isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); #endif @@ -554,4 +551,4 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) if (changed) { nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } -} +} \ No newline at end of file From c34956e9d8912f28c6ae65137298804d81d669ec Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Wed, 17 Apr 2024 00:47:56 +0200 Subject: [PATCH 0258/1377] =?UTF-8?q?Cosmetics:=20rename=20remaining=20plu?= =?UTF-8?q?gins=20=E2=86=92=20modules=20and=20less=20errors=20(#3645)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mesh/MeshModule.cpp | 11 ++++++----- src/mesh/MeshModule.h | 6 +++--- src/mesh/RF95Interface.cpp | 2 +- src/mesh/Router.cpp | 4 ++-- src/mesh/SX128xInterface.cpp | 2 +- src/modules/AdminModule.cpp | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index c8dd7f3d1..2ef46e4db 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -12,7 +12,7 @@ const meshtastic_MeshPacket *MeshModule::currentRequest; /** * If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow - * the RoutingPlugin to avoid sending redundant acks + * the RoutingModule to avoid sending redundant acks */ meshtastic_MeshPacket *MeshModule::currentReply; @@ -40,7 +40,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod c.error_reason = err; c.which_variant = meshtastic_Routing_error_reason_tag; - // Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingPlugin + // Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingModule // So we manually call pb_encode_to_bytes and specify routing port number // auto p = allocDataProtobuf(c); meshtastic_MeshPacket *p = router->allocForSending(); @@ -54,7 +54,8 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod p->to = to; p->decoded.request_id = idFrom; p->channel = chIndex; - LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); + if (err != meshtastic_Routing_Error_NONE) + LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); return p; } @@ -68,7 +69,7 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e return r; } -void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src) +void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) { // LOG_DEBUG("In call modules\n"); bool moduleFound = false; @@ -258,7 +259,7 @@ void MeshModule::observeUIEvents(Observer *observer) } } -AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp, +AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) { diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 6c431adb4..2e2af33e0 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -64,11 +64,11 @@ class MeshModule /** For use only by MeshService */ - static void callPlugins(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); + static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshModulesWithUIFrames(); static void observeUIEvents(Observer *observer); - static AdminMessageHandleResult handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp, + static AdminMessageHandleResult handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response); #if HAS_SCREEN @@ -195,4 +195,4 @@ class MeshModule /** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet * This ensures that if the request packet was sent reliably, the reply is sent that way as well. */ -void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to); +void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to); \ No newline at end of file diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index b658a8ff6..8c6c349fd 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -215,7 +215,7 @@ bool RF95Interface::isChannelActive() // LOG_DEBUG("Channel is busy!\n"); return true; } - if (result != RADIOLIB_ERR_WRONG_MODEM) + if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3fa933bb1..e4d67f019 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -479,7 +479,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) // call modules here if (!skipHandle) - MeshModule::callPlugins(*p, src); + MeshModule::callModules(*p, src); } void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) @@ -499,4 +499,4 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) handleReceived(p); packetPool.release(p); -} +} \ No newline at end of file diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 564b80494..9e4fbfa77 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -280,7 +280,7 @@ template bool SX128xInterface::isChannelActive() result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; - if (result != RADIOLIB_ERR_WRONG_MODEM) + if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index b40633af0..54eb577f7 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -256,7 +256,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta default: meshtastic_AdminMessage res = meshtastic_AdminMessage_init_default; - AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllPlugins(mp, r, &res); + AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllModules(mp, r, &res); if (handleResult == AdminMessageHandleResult::HANDLED_WITH_RESPONSE) { myReply = allocDataProtobuf(res); From 2450031b1bfd189c7e9723432922f5f6dd9d7245 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 17 Apr 2024 07:00:18 -0500 Subject: [PATCH 0259/1377] Add device metrics uptime to MQTT JSON (#3643) * Add device metrics uptime to MQTT JSON * Cast a spell --- src/mqtt/MQTT.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 05d5486b2..da1c204b8 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -656,6 +656,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage); msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization); msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx); + msgPayload["uptime_seconds"] = new JSONValue((uint)decoded->variant.device_metrics.uptime_seconds); } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature); msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity); From bc085ab840af3ab00dca88eb34b30db74e83d61e Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:07:40 +0200 Subject: [PATCH 0260/1377] Fix #3641: Always set MAC when picking new NodeNum (#3651) Co-authored-by: Ben Meadors --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 73aa29bbf..710b21593 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -527,8 +527,8 @@ void NodeDB::installDefaultDeviceState() void NodeDB::pickNewNodeNum() { NodeNum nodeNum = myNodeInfo.my_node_num; + getMacAddr(ourMacAddr); // Make sure ourMacAddr is set if (nodeNum == 0) { - getMacAddr(ourMacAddr); // Make sure ourMacAddr is set // Pick an initial nodenum based on the macaddr nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]; } From d47e9bed196ae05c4dc1a2abd3bc783b9cbb965c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 17 Apr 2024 14:25:52 -0500 Subject: [PATCH 0261/1377] Add multiple SPI devices for Radio, Display, and Touchscreen (#3638) This changeset gives us the ability to specify a separate SPI device for the LoRa, Display, and Touchscreen. The changes in Portduino also add support for specifying a new SPI speed for each transaction. All together, this means that we can let the Linux OS manage the CS lines, and also get much faster SPI speeds, leading to better framerates. * Add multiple SPI devices to put Radio, Display, and Touchscreen on each their own --------- Co-authored-by: Ben Meadors --- arch/portduino/portduino.ini | 2 +- bin/config-dist.yaml | 4 +- src/graphics/TFTDisplay.cpp | 4 +- src/graphics/mesh_bus_spi.cpp | 188 +++++++++++++++++++++++ src/graphics/mesh_bus_spi.h | 100 ++++++++++++ src/input/TouchScreenImpl1.cpp | 2 +- src/main.cpp | 12 +- src/main.h | 5 + src/platform/portduino/PortduinoGlue.cpp | 29 ++++ src/platform/portduino/PortduinoGlue.h | 6 +- 10 files changed, 340 insertions(+), 12 deletions(-) create mode 100644 src/graphics/mesh_bus_spi.cpp create mode 100644 src/graphics/mesh_bus_spi.h diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 07151c4a3..53f06c9f3 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#c95616208ffff4c8a36d48df810a3f072cce3521 +platform = https://github.com/meshtastic/platform-native.git#6fb39b6f94ece9c042141edb4afb91aca94dcaab framework = arduino build_src_filter = diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index d8cb5a9dd..f729f1ac7 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -101,11 +101,13 @@ Display: # Height: 240 Touchscreen: +### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching. + # Module: STMPE610 # CS: 7 # IRQ: 24 -# Module: XPT2046 +# Module: XPT2046 # Waveshare 2.8inch # CS: 7 # IRQ: 17 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b561f3b56..12e549424 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1,6 +1,7 @@ #include "configuration.h" #include "main.h" #if ARCH_PORTDUINO +#include "mesh_bus_spi.h" #include "platform/portduino/PortduinoGlue.h" #endif @@ -339,7 +340,7 @@ static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h class LGFX : public lgfx::LGFX_Device { lgfx::Panel_LCD *_panel_instance; - lgfx::Bus_SPI _bus_instance; + lgfx::Mesh_Bus_SPI _bus_instance; lgfx::ITouch *_touch_instance; @@ -356,6 +357,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ILI9341; auto buscfg = _bus_instance.config(); buscfg.spi_mode = 0; + _bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]); buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) diff --git a/src/graphics/mesh_bus_spi.cpp b/src/graphics/mesh_bus_spi.cpp new file mode 100644 index 000000000..a9536d490 --- /dev/null +++ b/src/graphics/mesh_bus_spi.cpp @@ -0,0 +1,188 @@ +// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens. +// Ideally this could eventually be an inherited class from BUS_SPI, +// but currently too many internal objects are set private. + +#include "configuration.h" +#if ARCH_PORTDUINO +#include "lgfx/v1/misc/pixelcopy.hpp" +#include "main.h" +#include "mesh_bus_spi.h" +#include +#include + +namespace lgfx +{ +inline namespace v1 +{ +//---------------------------------------------------------------------------- + +void Mesh_Bus_SPI::config(const config_t &config) +{ + _cfg = config; + + if (_cfg.pin_dc >= 0) { + pinMode(_cfg.pin_dc, pin_mode_t::output); + gpio_hi(_cfg.pin_dc); + } +} + +bool Mesh_Bus_SPI::init(void) +{ + dc_h(); + pinMode(_cfg.pin_dc, pin_mode_t::output); + if (SPIName != "") + PrivateSPI->begin(SPIName.c_str()); + else + PrivateSPI->begin(); + return true; +} + +void Mesh_Bus_SPI::release(void) +{ + PrivateSPI->end(); +} + +void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName) +{ + PrivateSPI = newSPI; + SPIName = newSPIName; +} +void Mesh_Bus_SPI::beginTransaction(void) +{ + dc_h(); + SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode); + PrivateSPI->beginTransaction(setting); +} + +void Mesh_Bus_SPI::endTransaction(void) +{ + PrivateSPI->endTransaction(); + dc_h(); +} + +void Mesh_Bus_SPI::beginRead(void) +{ + PrivateSPI->endTransaction(); + // SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false); + SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode); + PrivateSPI->beginTransaction(setting); +} + +void Mesh_Bus_SPI::endRead(void) +{ + PrivateSPI->endTransaction(); + beginTransaction(); +} + +void Mesh_Bus_SPI::wait(void) {} + +bool Mesh_Bus_SPI::busy(void) const +{ + return false; +} + +bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length) +{ + dc_l(); + PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3); + dc_h(); + return true; +} + +void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length) +{ + PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3); +} + +void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length) +{ + const uint8_t dst_bytes = bit_length >> 3; + uint32_t limit = (dst_bytes == 3) ? 12 : 16; + auto buf = _flip_buffer.getBuffer(512); + size_t fillpos = 0; + reinterpret_cast(buf)[0] = data; + fillpos += dst_bytes; + uint32_t len; + do { + len = ((length - 1) % limit) + 1; + if (limit <= 64) + limit <<= 1; + + while (fillpos < len * dst_bytes) { + memcpy(&buf[fillpos], buf, fillpos); + fillpos += fillpos; + } + + PrivateSPI->transfer(buf, len * dst_bytes); + } while (length -= len); +} + +void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length) +{ + const uint8_t dst_bytes = param->dst_bits >> 3; + uint32_t limit = (dst_bytes == 3) ? 12 : 16; + uint32_t len; + do { + len = ((length - 1) % limit) + 1; + if (limit <= 32) + limit <<= 1; + auto buf = _flip_buffer.getBuffer(len * dst_bytes); + param->fp_copy(buf, 0, len, param); + PrivateSPI->transfer(buf, len * dst_bytes); + } while (length -= len); +} + +void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) +{ + if (dc) + dc_h(); + else + dc_l(); + PrivateSPI->transfer(const_cast(data), length); + if (!dc) + dc_h(); +} + +uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length) +{ + uint32_t res = 0; + bit_length >>= 3; + if (!bit_length) + return res; + int idx = 0; + do { + res |= PrivateSPI->transfer(0) << idx; + idx += 8; + } while (--bit_length); + return res; +} + +bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma) +{ + do { + dst[0] = PrivateSPI->transfer(0); + ++dst; + } while (--length); + return true; +} + +void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length) +{ + uint32_t bytes = param->src_bits >> 3; + uint32_t dstindex = 0; + uint32_t len = 4; + uint8_t buf[24]; + param->src_data = buf; + do { + if (len > length) + len = length; + readBytes((uint8_t *)buf, len * bytes, true); + param->src_x = 0; + dstindex = param->fp_copy(dst, dstindex, dstindex + len, param); + length -= len; + } while (length); +} + +} // namespace v1 +} // namespace lgfx +#endif \ No newline at end of file diff --git a/src/graphics/mesh_bus_spi.h b/src/graphics/mesh_bus_spi.h new file mode 100644 index 000000000..903f7ad9d --- /dev/null +++ b/src/graphics/mesh_bus_spi.h @@ -0,0 +1,100 @@ +#if ARCH_PORTDUINO +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#pragma once + +#include + +#include "lgfx/v1/Bus.hpp" +#include "lgfx/v1/platforms/common.hpp" + +namespace lgfx +{ +inline namespace v1 +{ +//---------------------------------------------------------------------------- + +class Mesh_Bus_SPI : public IBus +{ + public: + struct config_t { + uint32_t freq_write = 16000000; + uint32_t freq_read = 8000000; + // bool spi_3wire = true; + // bool use_lock = true; + int16_t pin_sclk = -1; + int16_t pin_miso = -1; + int16_t pin_mosi = -1; + int16_t pin_dc = -1; + uint8_t spi_mode = 0; + }; + + const config_t &config(void) const { return _cfg; } + + void config(const config_t &config); + + bus_type_t busType(void) const override { return bus_type_t::bus_spi; } + + bool init(void) override; + void release(void) override; + void spi_device(HardwareSPI *newSPI, std::string newSPIName); + + void beginTransaction(void) override; + void endTransaction(void) override; + void wait(void) override; + bool busy(void) const override; + + bool writeCommand(uint32_t data, uint_fast8_t bit_length) override; + void writeData(uint32_t data, uint_fast8_t bit_length) override; + void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override; + void writePixels(pixelcopy_t *param, uint32_t length) override; + void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override; + + void initDMA(void) {} + void flush(void) {} + void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); } + void execDMAQueue(void) {} + uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); } + + void beginRead(void) override; + void endRead(void) override; + uint32_t readData(uint_fast8_t bit_length) override; + bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override; + void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override; + + private: + HardwareSPI *PrivateSPI; + std::string SPIName; + __attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); } + __attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); } + + config_t _cfg; + FlipBuffer _flip_buffer; + bool _need_wait; + uint32_t _mask_reg_dc; + uint32_t _last_apb_freq = -1; + uint32_t _clkdiv_write; + uint32_t _clkdiv_read; + volatile uint32_t *_gpio_reg_dc_h; + volatile uint32_t *_gpio_reg_dc_l; +}; + +//---------------------------------------------------------------------------- +} // namespace v1 +} // namespace lgfx +#endif \ No newline at end of file diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index 3e4ed4163..c863ead69 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -4,7 +4,7 @@ #include "configuration.h" #include "modules/ExternalNotificationModule.h" -#ifdef ARCH_PORTDUINO +#if ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif diff --git a/src/main.cpp b/src/main.cpp index 744fda4de..b1a15634f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -630,14 +630,12 @@ void setup() pinMode(LORA_CS, OUTPUT); digitalWrite(LORA_CS, HIGH); SPI1.begin(false); -#else // HW_SPI1_DEVICE +#else // HW_SPI1_DEVICE SPI.setSCK(LORA_SCK); SPI.setTX(LORA_MOSI); SPI.setRX(LORA_MISO); SPI.begin(false); -#endif // HW_SPI1_DEVICE -#elif ARCH_PORTDUINO - SPI.begin(settingsStrings[spidev].c_str()); +#endif // HW_SPI1_DEVICE #elif !defined(ARCH_ESP32) // ARCH_RP2040 SPI.begin(); #else @@ -724,7 +722,7 @@ void setup() if (settingsMap[use_sx1262]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -738,7 +736,7 @@ void setup() } else if (settingsMap[use_rf95]) { if (!rIf) { LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -753,7 +751,7 @@ void setup() } else if (settingsMap[use_sx1280]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { diff --git a/src/main.h b/src/main.h index 132fd190b..bb812b7b6 100644 --- a/src/main.h +++ b/src/main.h @@ -22,6 +22,11 @@ extern NimbleBluetooth *nimbleBluetooth; extern NRF52Bluetooth *nrf52Bluetooth; #endif +#if ARCH_PORTDUINO +extern HardwareSPI *DisplaySPI; +extern HardwareSPI *LoraSPI; + +#endif extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index f686ef3dc..f3415aaee 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -15,6 +15,8 @@ #include #include +HardwareSPI *DisplaySPI; +HardwareSPI *LoraSPI; std::map settingsMap; std::map settingsStrings; char *configPath = nullptr; @@ -81,6 +83,7 @@ void portduinoSetup() settingsStrings[keyboardDevice] = ""; settingsStrings[webserverrootpath] = ""; settingsStrings[spidev] = ""; + settingsStrings[displayspidev] = ""; YAML::Node yamlConfig; @@ -187,6 +190,9 @@ void portduinoSetup() settingsMap[displayOffsetY] = yamlConfig["Display"]["OffsetY"].as(0); settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as(false); settingsMap[displayInvert] = yamlConfig["Display"]["Invert"].as(false); + if (yamlConfig["Display"]["spidev"]) { + settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); + } } settingsMap[touchscreenModule] = no_touchscreen; if (yamlConfig["Touchscreen"]) { @@ -196,6 +202,9 @@ void portduinoSetup() settingsMap[touchscreenModule] = stmpe610; settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as(-1); settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); + if (yamlConfig["Touchscreen"]["spidev"]) { + settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); + } } if (yamlConfig["Input"]) { settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as(""); @@ -267,6 +276,26 @@ void portduinoSetup() initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); } + // if we specify a touchscreen dev, that is SPI. + // else if we specify a screen dev, that is SPI + // else if we specify a LoRa dev, that is SPI. + if (settingsStrings[touchscreenspidev] != "") { + SPI.begin(settingsStrings[touchscreenspidev].c_str()); + DisplaySPI = new HardwareSPI; + DisplaySPI->begin(settingsStrings[displayspidev].c_str()); + LoraSPI = new HardwareSPI; + LoraSPI->begin(settingsStrings[spidev].c_str()); + } else if (settingsStrings[displayspidev] != "") { + SPI.begin(settingsStrings[displayspidev].c_str()); + DisplaySPI = &SPI; + LoraSPI = new HardwareSPI; + LoraSPI->begin(settingsStrings[spidev].c_str()); + } else { + SPI.begin(settingsStrings[spidev].c_str()); + LoraSPI = &SPI; + DisplaySPI = &SPI; + } + return; } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 505c436d6..980fc63b8 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -21,6 +21,8 @@ enum configNames { touchscreenModule, touchscreenCS, touchscreenIRQ, + touchscreenspidev, + displayspidev, displayPanel, displayWidth, displayHeight, @@ -45,4 +47,6 @@ enum { level_error, level_warn, level_info, level_debug }; extern std::map settingsMap; extern std::map settingsStrings; -int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file +int initGPIOPin(int pinNum, std::string gpioChipname); +extern HardwareSPI *DisplaySPI; +extern HardwareSPI *LoraSPI; \ No newline at end of file From 2e14234b77ed98ec63368fb5fb5b8cf6192f7018 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Thu, 18 Apr 2024 09:55:47 +1200 Subject: [PATCH 0262/1377] don't enable the CDC interface already at boot (#3652) Co-authored-by: Ben Meadors --- boards/tbeam-s3-core.json | 1 - 1 file changed, 1 deletion(-) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 8d2c3eed6..4c82a2789 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -7,7 +7,6 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", - "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" From 4b5549be8fa48cfe80a7de77ac6f9099fea7915b Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Thu, 18 Apr 2024 09:22:31 +0100 Subject: [PATCH 0263/1377] added vibration notifications --- src/modules/ExternalNotificationModule.cpp | 44 +++++++++++----------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index be8fd2be2..d7997b849 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -145,20 +145,16 @@ int32_t ExternalNotificationModule::runOnce() #endif #ifdef UNPHONE - if (rgb_found.type == ScanI2C::RGBLED_CA) { - red = colorState & 4; // Red enabled on colorState = 4,5,6,7 - green = colorState & 2; // Green enabled on colorState = 2,3,6,7 - blue = colorState & 1; // Blue enabled on colorState = 1,3,5,7 - unphone.rgb(red, green, blue); - LOG_DEBUG("RGB runOnce: %i, %i, %i\n", red, green, blue); - - counter++; // tick on - if (counter > duration) { - counter = 0; - colorState++; // next color - if (colorState > 7) { - colorState = 1; - } + red = colorState & 4; // Red enabled on colorState = 4,5,6,7 + green = colorState & 2; // Green enabled on colorState = 2,3,6,7 + blue = colorState & 1; // Blue enabled on colorState = 1,3,5,7 + unphone.rgb(red, green, blue); + counter++; // tick on + if (counter > duration) { + counter = 0; + colorState++; // next color + if (colorState > 7) { + colorState = 1; } } #endif @@ -209,6 +205,9 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) switch (index) { case 1: +#ifdef UNPHONE + unphone.vibe(true); // the unPhone's vibration motor is on a i2c GPIO expander +#endif if (moduleConfig.external_notification.output_vibra) digitalWrite(moduleConfig.external_notification.output_vibra, true); break; @@ -228,9 +227,7 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) } #endif #ifdef UNPHONE - if (rgb_found.type == ScanI2C::RGBLED_CA) { - unphone.rgb(red, green, blue); - } + unphone.rgb(red, green, blue); #endif #ifdef T_WATCH_S3 drv.go(); @@ -244,6 +241,9 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) switch (index) { case 1: +#ifdef UNPHONE + unphone.vibe(false); // the unPhone's vibration motor is on a i2c GPIO expander +#endif if (moduleConfig.external_notification.output_vibra) digitalWrite(moduleConfig.external_notification.output_vibra, false); break; @@ -266,12 +266,10 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) } #endif #ifdef UNPHONE - if (rgb_found.type == ScanI2C::RGBLED_CA) { - red = 0; - green = 0; - blue = 0; - unphone.rgb(red, green, blue); - } + red = 0; + green = 0; + blue = 0; + unphone.rgb(red, green, blue); #endif #ifdef T_WATCH_S3 drv.stop(); From 747c713ba925fd77f6f4a8bb000db3cedd9d45ab Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 19 Apr 2024 00:27:18 +1200 Subject: [PATCH 0264/1377] (ESP32) Fix bluetooth after light-sleep; de-init for deep sleep (#3655) --- src/nimble/NimbleBluetooth.cpp | 10 ++++++++-- src/platform/esp32/main-esp32.cpp | 5 +++-- src/sleep.cpp | 4 ++-- variants/heltec_wireless_paper/variant.h | 3 --- variants/heltec_wireless_paper_v1/variant.h | 3 --- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 092aef470..8f7e00461 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -106,20 +106,26 @@ static NimbleBluetoothFromRadioCallback *fromRadioCallbacks; void NimbleBluetooth::shutdown() { + // No measurable power saving for ESP32 during light-sleep(?) +#ifndef ARCH_ESP32 // Shutdown bluetooth for minimum power draw LOG_INFO("Disable bluetooth\n"); - // Bluefruit.Advertising.stop(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->stop(); +#endif } -// Extra power-saving on some devices +// Proper shutdown for ESP32. Needs reboot to reverse. void NimbleBluetooth::deinit() { +#ifdef ARCH_ESP32 + LOG_INFO("Disable bluetooth until reboot\n"); NimBLEDevice::deinit(); +#endif } +// Has initial setup been completed bool NimbleBluetooth::isActive() { return bleServer; diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 3fb6e7774..2894a49fc 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -30,9 +30,10 @@ void setBluetoothEnable(bool enable) } if (enable && !nimbleBluetooth->isActive()) { nimbleBluetooth->setup(); - } else if (!enable) { - nimbleBluetooth->shutdown(); } + // For ESP32, no way to recover from bluetooth shutdown without reboot + // BLE advertising automatically stops when MCU enters light-sleep(?) + // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse } } #else diff --git a/src/sleep.cpp b/src/sleep.cpp index 860a676df..a2a221d79 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -207,8 +207,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // esp_wifi_stop(); waitEnterSleep(skipPreflight); -#ifdef NIMBLE_DEINIT_FOR_DEEPSLEEP - // Extra power saving on some devices +#ifdef ARCH_ESP32 + // Full shutdown of bluetooth hardware nimbleBluetooth->deinit(); #endif diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 466925a2e..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -55,6 +55,3 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// Power management -#define NIMBLE_DEINIT_FOR_DEEPSLEEP // Required to reach manufacturers claim of 18uA diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 466925a2e..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -55,6 +55,3 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// Power management -#define NIMBLE_DEINIT_FOR_DEEPSLEEP // Required to reach manufacturers claim of 18uA From 425a71599595b1d4937f5d9030886a0f7e5c98fd Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 18 Apr 2024 14:20:39 -0500 Subject: [PATCH 0265/1377] Added one minute throttling to NodeDB save to disk (#3648) * Added one minute throttling to NodeDB * Derp --- src/mesh/Default.h | 3 ++- src/mesh/NodeDB.cpp | 7 +++++-- src/mesh/NodeDB.h | 3 ++- src/mesh/Throttle.cpp | 27 +++++++++++++++++++++++++++ src/mesh/Throttle.h | 9 +++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/mesh/Throttle.cpp create mode 100644 src/mesh/Throttle.h diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 734cdf519..95723744b 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -2,6 +2,7 @@ #include #include #define ONE_DAY 24 * 60 * 60 +#define ONE_MINUTE_MS 60 * 1000 #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) #define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) @@ -27,4 +28,4 @@ class Default static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval); static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval); static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); -}; +}; \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 710b21593..39422b454 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -813,6 +813,7 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly) } #include "MeshModule.h" +#include "Throttle.h" /** Update position info for this node based on received position data */ @@ -907,8 +908,10 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t chann powerFSM.trigger(EVENT_NODEDB_UPDATED); notifyObservers(true); // Force an update whether or not our node counts have changed - // We just changed something important about the user, store our DB - saveToDisk(SEGMENT_DEVICESTATE); + // We just changed something about the user, store our DB + Throttle::execute( + &lastNodeDbSave, ONE_MINUTE_MS, []() { nodeDB->saveToDisk(SEGMENT_DEVICESTATE); }, + []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now, since we saved less than a minute ago\n"); }); } return changed; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 1c1736f78..57040fbd6 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -160,6 +160,7 @@ class NodeDB } private: + uint32_t lastNodeDbSave = 0; // when we last saved our db to flash /// Find a node in our DB, create an empty NodeInfoLite if missing meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n); @@ -229,4 +230,4 @@ extern uint32_t error_address; ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ ModuleConfig_TelemetryConfig_size + ModuleConfig_size) -// Please do not remove this comment, it makes trunk and compiler happy at the same time. +// Please do not remove this comment, it makes trunk and compiler happy at the same time. \ No newline at end of file diff --git a/src/mesh/Throttle.cpp b/src/mesh/Throttle.cpp new file mode 100644 index 000000000..d8f23f9dc --- /dev/null +++ b/src/mesh/Throttle.cpp @@ -0,0 +1,27 @@ +#include "Throttle.h" +#include + +/// @brief Execute a function throttled to a minimum interval +/// @param lastExecutionMs Pointer to the last execution time in milliseconds +/// @param minumumIntervalMs Minimum execution interval in milliseconds +/// @param throttleFunc Function to execute if the execution is not deferred +/// @param onDefer Default to NULL, execute the function if the execution is deferred +/// @return true if the function was executed, false if it was deferred +bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*throttleFunc)(void), void (*onDefer)(void)) +{ + if (*lastExecutionMs == 0) { + *lastExecutionMs = millis(); + throttleFunc(); + return true; + } + uint32_t now = millis(); + + if ((now - *lastExecutionMs) >= minumumIntervalMs) { + throttleFunc(); + *lastExecutionMs = now; + return true; + } else if (onDefer != NULL) { + onDefer(); + } + return false; +} \ No newline at end of file diff --git a/src/mesh/Throttle.h b/src/mesh/Throttle.h new file mode 100644 index 000000000..8115595a4 --- /dev/null +++ b/src/mesh/Throttle.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +class Throttle +{ + public: + static bool execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*func)(void), void (*onDefer)(void) = NULL); +}; \ No newline at end of file From 4c0b7ea409a9ea372e202ca95bff1463ee13717c Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Thu, 18 Apr 2024 21:28:11 +0200 Subject: [PATCH 0266/1377] StoreForward: Remove assert when receiving unhandled case (#3661) --- src/modules/esp32/StoreForwardModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index a60065e56..12cddc520 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -506,7 +506,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, break; default: - assert(0); // unexpected state + break; // no need to do anything } return true; // There's no need for others to look at this message. } From a149999ceca1248d7cb4097384a61356d2b4f10b Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Thu, 18 Apr 2024 20:57:03 +0100 Subject: [PATCH 0267/1377] tidy up first --- src/detect/ScanI2C.h | 3 ++- src/main.cpp | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index b4341bcc0..c8fcfee10 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,8 +41,9 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, - RGBLED_CA, +#ifdef HAS_NCP5623 NCP5623, +#endif } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/main.cpp b/src/main.cpp index 1699344a9..b1a15634f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -494,15 +494,11 @@ void setup() * "found". */ -// Only one supported I2C RGB LED currently (plus common anode RGB LED used by the unPhone) +// Only one supported RGB LED currently #ifdef HAS_NCP5623 rgb_found = i2cScanner->find(ScanI2C::DeviceType::NCP5623); #endif -#ifdef UNPHONE - rgb_found.type = ScanI2C::DeviceType::RGBLED_CA; -#endif - #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) auto acc_info = i2cScanner->firstAccelerometer(); accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found; From 7d77b23eb6a3383e4950f2f06ac7524b6a8102da Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Thu, 18 Apr 2024 22:00:33 +0100 Subject: [PATCH 0268/1377] support for generic 4 pin CC and CA RGB LEDS --- src/AmbientLightingThread.h | 48 ++++++++++++++---- src/main.cpp | 2 +- src/modules/ExternalNotificationModule.cpp | 59 ++++++++++++++++++++++ 3 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index fd3c66cda..1425d3266 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -25,8 +25,8 @@ class AmbientLightingThread : public concurrency::OSThread // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; - _type = type; #ifdef HAS_NCP5623 + _type = type; if (_type == ScanI2C::DeviceType::NONE) { LOG_DEBUG("AmbientLightingThread disabling due to no RGB leds found on I2C bus\n"); disable(); @@ -51,22 +51,39 @@ class AmbientLightingThread : public concurrency::OSThread } LOG_DEBUG("AmbientLightingThread initializing\n"); setLighting(); +#endif +#ifdef RGBLED_RED + if (!moduleConfig.ambient_lighting.led_state) { + LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n"); + disable(); + return; + } + LOG_DEBUG("AmbientLightingThread initializing\n"); + pinMode(RGBLED_RED, output); + pinMode(RGBLED_GREEN, output); + pinMode(RGBLED_BLUE, output); + setLighting(); #endif } protected: int32_t runOnce() override { -#if defined(HAS_NCP5623) || defined(UNPHONE) - if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::RGBLED_CA) && moduleConfig.ambient_lighting.led_state) { +#ifdef HAS_NCP5623 + if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) { setLighting(); return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification - } else { - return disable(); } -#else - return disable(); #endif +#ifdef UNPHONE + setLighting(); + return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification +#endif +#ifdef RGBLED_RED + setLighting(); + return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification +#endif + return disable(); } private: @@ -79,14 +96,27 @@ class AmbientLightingThread : public concurrency::OSThread rgb.setRed(moduleConfig.ambient_lighting.red); rgb.setGreen(moduleConfig.ambient_lighting.green); rgb.setBlue(moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Initializing Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n", + LOG_DEBUG("Initializing NCP5623 Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif #ifdef UNPHONE unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Initializing Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, + LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); +#endif +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); + analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green); + analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue); + LOG_DEBUG("Initializing Ambient lighting RGB Common Anode w/ red=%d, green=%d, blue=%d\n", + moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); +#elifdef RGBLED_RED + analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red); + analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green); + analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue); + LOG_DEBUG("Initializing Ambient lighting RGB Common Cathode w/ red=%d, green=%d, blue=%d\n", + moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif } }; diff --git a/src/main.cpp b/src/main.cpp index b1a15634f..d8640bb59 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -608,7 +608,7 @@ void setup() #endif #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) - if (rgb_found.type != ScanI2C::DeviceType::NONE) { + if (rgb_found.type != ScanI2C::DeviceType::NONE || UNPHONE) { ambientLightingThread = new AmbientLightingThread(rgb_found.type); } #endif diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index d7997b849..ee3b73efd 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -48,6 +48,16 @@ const uint8_t duration = 15; uint8_t counter = 0; #endif +#ifdef RGBLED_RED +uint8_t red = 0; +uint8_t green = 0; +uint8_t blue = 0; +uint8_t colorState = 1; +uint8_t brightnessIndex = 0; +uint8_t brightnessValues[] = {0, 10, 20, 30, 50, 90, 160, 170}; // blue gets multiplied by 1.5 +bool ascending = true; +#endif + #ifndef PIN_BUZZER #define PIN_BUZZER false #endif @@ -84,6 +94,7 @@ int32_t ExternalNotificationModule::runOnce() if (!moduleConfig.external_notification.enabled) { return INT32_MAX; // we don't need this thread here... } else { + bool isPlaying = rtttl::isPlaying(); #ifdef HAS_I2S isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); @@ -159,6 +170,30 @@ int32_t ExternalNotificationModule::runOnce() } #endif +#ifdef RGBLED_RED + red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0; // Red enabled on colorState = 4,5,6,7 + green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7 + blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7 + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); + if (ascending) { // fade in + brightnessIndex++; + if (brightnessIndex == (sizeof(brightnessValues) - 1)) { + ascending = false; + } + } else { + brightnessIndex--; // fade out + } + if (brightnessIndex == 0) { + ascending = true; + colorState++; // next color + if (colorState > 7) { + colorState = 1; + } + } +#endif + #ifdef T_WATCH_S3 drv.go(); #endif @@ -229,6 +264,15 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) #ifdef UNPHONE unphone.rgb(red, green, blue); #endif +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255 - red); + analogWrite(RGBLED_GREEN, 255 - green); + analogWrite(RGBLED_BLUE, 255 - blue); +#elifdef RGBLED_RED + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); +#endif #ifdef T_WATCH_S3 drv.go(); #endif @@ -271,6 +315,21 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) blue = 0; unphone.rgb(red, green, blue); #endif +#ifdef RGBLED_CA + red = 0; + green = 0; + blue = 0; + analogWrite(RGBLED_RED, 255 - red); + analogWrite(RGBLED_GREEN, 255 - green); + analogWrite(RGBLED_BLUE, 255 - blue); +#elifdef RGBLED_RED + red = 0; + green = 0; + blue = 0; + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); +#endif #ifdef T_WATCH_S3 drv.stop(); #endif From e4b5f2ce14fe118eb1455a38e7768871490b75fe Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Thu, 18 Apr 2024 23:16:50 +0200 Subject: [PATCH 0269/1377] NeighborInfo: Only keep neighbors in RAM (#3660) * NeighborInfo: Only keep neighbors in RAM It fills up quickly when nodes are running >=2.3 * Defer first transmission as it's usually empty --------- Co-authored-by: Ben Meadors --- src/modules/NeighborInfoModule.cpp | 144 +++++++++-------------------- src/modules/NeighborInfoModule.h | 27 +----- 2 files changed, 50 insertions(+), 121 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 470234047..8c8135deb 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -4,11 +4,8 @@ #include "NodeDB.h" #include "RTC.h" -#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options NeighborInfoModule *neighborInfoModule; -static const char *neighborInfoConfigFile = "/prefs/neighbors.proto"; - /* Prints a single neighbor info packet and associated neighbors Uses LOG_DEBUG, which equates to Console.log @@ -30,26 +27,23 @@ NOTE: for debugging only */ void NeighborInfoModule::printNodeDBNeighbors() { - int num_neighbors = getNumNeighbors(); - LOG_DEBUG("Our NodeDB contains %d neighbors\n", num_neighbors); - for (int i = 0; i < num_neighbors; i++) { - const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); - LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); + LOG_DEBUG("Our NodeDB contains %d neighbors\n", neighbors.size()); + for (size_t i = 0; i < neighbors.size(); i++) { + LOG_DEBUG("Node %d: node_id=0x%x, snr=%.2f\n", i, neighbors[i].node_id, neighbors[i].snr); } } /* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */ NeighborInfoModule::NeighborInfoModule() : ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg), - concurrency::OSThread("NeighborInfoModule"), neighbors(neighborState.neighbors), - numNeighbors(&neighborState.neighbors_count) + concurrency::OSThread("NeighborInfoModule") { ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP; if (moduleConfig.neighbor_info.enabled) { isPromiscuous = true; // Update neighbors from all packets - this->loadProtoForModule(); - setIntervalFromNow(35 * 1000); + setIntervalFromNow( + Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs)); } else { LOG_DEBUG("NeighborInfoModule is disabled\n"); disable(); @@ -63,18 +57,17 @@ Assumes that the neighborInfo packet has been allocated */ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo) { - uint my_node_id = nodeDB->getNodeNum(); + NodeNum my_node_id = nodeDB->getNodeNum(); neighborInfo->node_id = my_node_id; neighborInfo->last_sent_by_id = my_node_id; neighborInfo->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; - int num_neighbors = cleanUpNeighbors(); + cleanUpNeighbors(); - for (int i = 0; i < num_neighbors; i++) { - const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); - if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (dbEntry->node_id != my_node_id)) { - neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = dbEntry->node_id; - neighborInfo->neighbors[neighborInfo->neighbors_count].snr = dbEntry->snr; + for (auto nbr : neighbors) { + if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) { + neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id; + neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr; // Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over // the mesh neighborInfo->neighbors_count++; @@ -85,41 +78,22 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb } /* -Remove neighbors from the database that we haven't heard from in a while -@returns new number of neighbors + Remove neighbors from the database that we haven't heard from in a while */ -size_t NeighborInfoModule::cleanUpNeighbors() +void NeighborInfoModule::cleanUpNeighbors() { uint32_t now = getTime(); - int num_neighbors = getNumNeighbors(); NodeNum my_node_id = nodeDB->getNodeNum(); - - // Find neighbors to remove - std::vector indices_to_remove; - for (int i = 0; i < num_neighbors; i++) { - const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); + for (auto it = neighbors.rbegin(); it != neighbors.rend();) { // We will remove a neighbor if we haven't heard from them in twice the broadcast interval - if ((now - dbEntry->last_rx_time > dbEntry->node_broadcast_interval_secs * 2) && (dbEntry->node_id != my_node_id)) { - indices_to_remove.push_back(i); + if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) { + LOG_DEBUG("Removing neighbor with node ID 0x%x\n", it->node_id); + it = std::vector::reverse_iterator( + neighbors.erase(std::next(it).base())); // Erase the element and update the iterator + } else { + ++it; } } - - // Update the neighbor list - for (uint i = 0; i < indices_to_remove.size(); i++) { - int index = indices_to_remove[i]; - LOG_DEBUG("Removing neighbor with node ID 0x%x\n", neighbors[index].node_id); - for (int j = index; j < num_neighbors - 1; j++) { - neighbors[j] = neighbors[j + 1]; - } - (*numNeighbors)--; - } - - // Save the neighbor list if we removed any neighbors or neighbors were already updated upon receiving a packet - if (indices_to_remove.size() > 0 || shouldSave) { - saveProtoForModule(); - } - - return *numNeighbors; } /* Send neighbor info to the mesh */ @@ -143,7 +117,9 @@ Will be used for broadcast. int32_t NeighborInfoModule::runOnce() { bool requestReplies = false; - sendNeighborInfo(NODENUM_BROADCAST, requestReplies); + if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { + sendNeighborInfo(NODENUM_BROADCAST, requestReplies); + } return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); } @@ -178,10 +154,7 @@ void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtas void NeighborInfoModule::resetNeighbors() { - *numNeighbors = 0; - neighborState.neighbors_count = 0; - memset(neighborState.neighbors, 0, sizeof(neighborState.neighbors)); - saveProtoForModule(); + neighbors.clear(); } void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np) @@ -201,61 +174,36 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen n = nodeDB->getNodeNum(); } // look for one in the existing list - for (int i = 0; i < (*numNeighbors); i++) { - meshtastic_Neighbor *nbr = &neighbors[i]; - if (nbr->node_id == n) { + for (size_t i = 0; i < neighbors.size(); i++) { + if (neighbors[i].node_id == n) { // if found, update it - nbr->snr = snr; - nbr->last_rx_time = getTime(); + neighbors[i].snr = snr; + neighbors[i].last_rx_time = getTime(); // Only if this is the original sender, the broadcast interval corresponds to it if (originalSender == n && node_broadcast_interval_secs != 0) - nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; - return nbr; + neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs; + return &neighbors[i]; } } // otherwise, allocate one and assign data to it - // TODO: max memory for the database should take neighbors into account, but currently doesn't - if (*numNeighbors < MAX_NUM_NEIGHBORS) { - (*numNeighbors)++; - } - meshtastic_Neighbor *new_nbr = &neighbors[((*numNeighbors) - 1)]; - new_nbr->node_id = n; - new_nbr->snr = snr; - new_nbr->last_rx_time = getTime(); + + meshtastic_Neighbor new_nbr = meshtastic_Neighbor_init_zero; + new_nbr.node_id = n; + new_nbr.snr = snr; + new_nbr.last_rx_time = getTime(); // Only if this is the original sender, the broadcast interval corresponds to it if (originalSender == n && node_broadcast_interval_secs != 0) - new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; + new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs; else // Assume the same broadcast interval as us for the neighbor if we don't know it - new_nbr->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; - shouldSave = true; // Save the new neighbor upon next cleanup - return new_nbr; -} + new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; -void NeighborInfoModule::loadProtoForModule() -{ - if (nodeDB->loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), - &meshtastic_NeighborInfo_msg, &neighborState) != LoadFileResult::SUCCESS) { - neighborState = meshtastic_NeighborInfo_init_zero; + if (neighbors.size() < MAX_NUM_NEIGHBORS) { + neighbors.push_back(new_nbr); + } else { + // If we have too many neighbors, replace the oldest one + LOG_WARN("Neighbor DB is full, replacing oldest neighbor\n"); + neighbors.erase(neighbors.begin()); + neighbors.push_back(new_nbr); } -} - -/** - * @brief Save the module config to file. - * - * @return true On success. - * @return false On error. - */ -bool NeighborInfoModule::saveProtoForModule() -{ - bool okay = true; - -#ifdef FS - FS.mkdir("/prefs"); -#endif - - okay &= nodeDB->saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState); - if (okay) - shouldSave = false; - - return okay; + return &neighbors.back(); } \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index d47004981..496fdece5 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -1,13 +1,13 @@ #pragma once #include "ProtobufModule.h" +#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options /* * Neighborinfo module for sending info on each node's 0-hop neighbors to the mesh */ class NeighborInfoModule : public ProtobufModule, private concurrency::OSThread { - meshtastic_Neighbor *neighbors; - pb_size_t *numNeighbors; + std::vector neighbors; public: /* @@ -18,15 +18,7 @@ class NeighborInfoModule : public ProtobufModule, priva /* Reset neighbor info after clearing nodeDB*/ void resetNeighbors(); - bool saveProtoForModule(); - - private: - bool shouldSave = false; // Whether we should save the neighbor info to flash - protected: - // Note: this holds our local info. - meshtastic_NeighborInfo neighborState; - /* * Called to handle a particular incoming message * @return true if you've guaranteed you've handled this message and no other handlers should be considered for it @@ -40,10 +32,9 @@ class NeighborInfoModule : public ProtobufModule, priva uint32_t collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo); /* - Remove neighbors from the database that we haven't heard from in a while - @returns new number of neighbors + Remove neighbors from the database that we haven't heard from in a while */ - size_t cleanUpNeighbors(); + void cleanUpNeighbors(); /* Allocate a new NeighborInfo packet */ meshtastic_NeighborInfo *allocateNeighborInfoPacket(); @@ -56,22 +47,12 @@ class NeighborInfoModule : public ProtobufModule, priva */ void sendNeighborInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); - size_t getNumNeighbors() { return *numNeighbors; } - - meshtastic_Neighbor *getNeighborByIndex(size_t x) - { - assert(x < *numNeighbors); - return &neighbors[x]; - } - /* update neighbors with subpacket sniffed from network */ void updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np); /* update a NeighborInfo packet with our NodeNum as last_sent_by_id */ void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) override; - void loadProtoForModule(); - /* Does our periodic broadcast */ int32_t runOnce() override; From 0ae76749820d6bc9a069ddfb8b8313ffbc1bbe7d Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Thu, 18 Apr 2024 22:18:50 +0100 Subject: [PATCH 0270/1377] I'm sure there's a cleverer way to do this, but I'm stupid and I didn't find it after a few minutes of searching stack overflow --- src/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d8640bb59..a1f2ebea1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -608,11 +608,15 @@ void setup() #endif #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) - if (rgb_found.type != ScanI2C::DeviceType::NONE || UNPHONE) { + if (rgb_found.type != ScanI2C::DeviceType::NONE) { ambientLightingThread = new AmbientLightingThread(rgb_found.type); } #endif +#ifdef UNPHONE + ambientLightingThread = new AmbientLightingThread(rgb_found.type); +#endif + #ifdef T_WATCH_S3 drv.begin(); drv.selectLibrary(1); From 64edfb76e0ad1d7d434df27a9d9edac1e7003533 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Fri, 19 Apr 2024 00:44:13 +0200 Subject: [PATCH 0271/1377] Uplink to MQTT after potentially altering content (#3646) Mainly for traceroute module now Co-authored-by: Ben Meadors --- src/mesh/Router.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index e4d67f019..4189bca66 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -465,21 +465,22 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) cancelSending(p->from, p->id); skipHandle = true; } -#if !MESHTASTIC_EXCLUDE_MQTT - // Publish received message to MQTT if we're not the original transmitter of the packet - if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt) - mqtt->onSend(*p_encrypted, *p, p->channel); -#endif - } else { printPacket("packet decoding failed or skipped (no PSK?)", p); } - packetPool.release(p_encrypted); // Release the encrypted packet - // call modules here - if (!skipHandle) + if (!skipHandle) { MeshModule::callModules(*p, src); + +#if !MESHTASTIC_EXCLUDE_MQTT + // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet + if (decoded && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt) + mqtt->onSend(*p_encrypted, *p, p->channel); +#endif + } + + packetPool.release(p_encrypted); // Release the encrypted packet } void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) From eea85d26ca4328fb7a321e9a98815daf0f62864e Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Fri, 19 Apr 2024 00:28:20 +0100 Subject: [PATCH 0272/1377] oh god the bugs, they are everywhere, I feel so dirty... --- src/AmbientLightingThread.h | 8 ++++---- src/main.cpp | 6 ++++-- src/modules/ExternalNotificationModule.cpp | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 1425d3266..81c9c85c7 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -59,9 +59,9 @@ class AmbientLightingThread : public concurrency::OSThread return; } LOG_DEBUG("AmbientLightingThread initializing\n"); - pinMode(RGBLED_RED, output); - pinMode(RGBLED_GREEN, output); - pinMode(RGBLED_BLUE, output); + pinMode(RGBLED_RED, OUTPUT); + pinMode(RGBLED_GREEN, OUTPUT); + pinMode(RGBLED_BLUE, OUTPUT); setLighting(); #endif } @@ -111,7 +111,7 @@ class AmbientLightingThread : public concurrency::OSThread analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue); LOG_DEBUG("Initializing Ambient lighting RGB Common Anode w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); -#elifdef RGBLED_RED +#elif defined(RGBLED_RED) analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red); analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green); analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue); diff --git a/src/main.cpp b/src/main.cpp index a1f2ebea1..fd06e8ae9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -611,11 +611,13 @@ void setup() if (rgb_found.type != ScanI2C::DeviceType::NONE) { ambientLightingThread = new AmbientLightingThread(rgb_found.type); } -#endif - #ifdef UNPHONE ambientLightingThread = new AmbientLightingThread(rgb_found.type); #endif +#ifdef RGBLED_RED + ambientLightingThread = new AmbientLightingThread(rgb_found.type); +#endif +#endif #ifdef T_WATCH_S3 drv.begin(); diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index ee3b73efd..4bab90527 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -268,7 +268,7 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) analogWrite(RGBLED_RED, 255 - red); analogWrite(RGBLED_GREEN, 255 - green); analogWrite(RGBLED_BLUE, 255 - blue); -#elifdef RGBLED_RED +#elif defined(RGBLED_RED) analogWrite(RGBLED_RED, red); analogWrite(RGBLED_GREEN, green); analogWrite(RGBLED_BLUE, blue); @@ -322,7 +322,7 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) analogWrite(RGBLED_RED, 255 - red); analogWrite(RGBLED_GREEN, 255 - green); analogWrite(RGBLED_BLUE, 255 - blue); -#elifdef RGBLED_RED +#elif defined(RGBLED_RED) red = 0; green = 0; blue = 0; From 7a3570aecf119825dba67784ecedb0338e1285bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:29:50 -0500 Subject: [PATCH 0273/1377] [create-pull-request] automated change (#3662) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 29 ++++++++++------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/protobufs b/protobufs index ecf105f66..0d08acd9c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit ecf105f66d182531423b73f4408c53701313c4eb +Subproject commit 0d08acd9c51c4e5575f3ea42368834ec990b2278 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index fd040c57f..2abe040a6 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -324,35 +324,30 @@ typedef struct _meshtastic_Config_PositionConfig { /* Power Config\ See [Power Config](/docs/settings/config/power) for additional power config details. */ typedef struct _meshtastic_Config_PowerConfig { - /* If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in - we should try to minimize power consumption as much as possible. - YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case). - Advanced Option */ + /* Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. + Don't use this setting if you want to use your device with the phone apps or are using a device without a user button. + Technical Details: Works for ESP32 devices and NRF52 devices in the Sensor or Tracker roles */ bool is_power_saving; - /* If non-zero, the device will fully power off this many seconds after external power is removed. */ + /* Description: If non-zero, the device will fully power off this many seconds after external power is removed. */ uint32_t on_battery_shutdown_after_secs; /* Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k) Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation. - Should be set to floating point value between 2 and 4 - Fixes issues on Heltec v2 */ + https://meshtastic.org/docs/configuration/radio/power/#adc-multiplier-override + Should be set to floating point value between 2 and 6 */ float adc_multiplier_override; - /* Wait Bluetooth Seconds - The number of seconds for to wait before turning off BLE in No Bluetooth states - 0 for default of 1 minute */ + /* Description: The number of seconds for to wait before turning off BLE in No Bluetooth states + Technical Details: ESP32 Only 0 for default of 1 minute */ uint32_t wait_bluetooth_secs; /* Super Deep Sleep Seconds While in Light Sleep if mesh_sds_timeout_secs is exceeded we will lower into super deep sleep for this value (default 1 year) or a button press 0 for default of one year */ uint32_t sds_secs; - /* Light Sleep Seconds - In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on - ESP32 Only - 0 for default of 300 */ + /* Description: In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on + Technical Details: ESP32 Only 0 for default of 300 */ uint32_t ls_secs; - /* Minimum Wake Seconds - While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value - 0 for default of 10 seconds */ + /* Description: While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value + Technical Details: ESP32 Only 0 for default of 10 seconds */ uint32_t min_wake_secs; /* I2C address of INA_2XX to use for reading device battery voltage */ uint8_t device_battery_ina_address; From 65bde8538fae649939273d2f93a526acc6aa37f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 20:33:23 -0500 Subject: [PATCH 0274/1377] [create-pull-request] automated change (#3663) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index f5ad818a2..485f55130 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 6 +build = 7 From 2100f3135e417d8999a8ba9688534644ef82a836 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Fri, 19 Apr 2024 09:25:38 +0100 Subject: [PATCH 0275/1377] minor edit to have another go at CI --- src/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index fd06e8ae9..991fa3648 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -613,8 +613,7 @@ void setup() } #ifdef UNPHONE ambientLightingThread = new AmbientLightingThread(rgb_found.type); -#endif -#ifdef RGBLED_RED +#elifdef RGBLED_RED ambientLightingThread = new AmbientLightingThread(rgb_found.type); #endif #endif From e0513d4078e93b3361145d58d151e706350536b6 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Fri, 19 Apr 2024 09:27:10 +0100 Subject: [PATCH 0276/1377] ahem, another minor edit to have another go at CI --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 991fa3648..430fa3a6b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -613,7 +613,7 @@ void setup() } #ifdef UNPHONE ambientLightingThread = new AmbientLightingThread(rgb_found.type); -#elifdef RGBLED_RED +#elif defined(RGBLED_RED) ambientLightingThread = new AmbientLightingThread(rgb_found.type); #endif #endif From 44aa248099fdcf2a27ae06285ceabc29d6479fd8 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sat, 20 Apr 2024 02:27:13 +0200 Subject: [PATCH 0277/1377] added new display parameters (#3670) --- src/platform/portduino/PortduinoGlue.cpp | 19 +++++++++++++++++++ src/platform/portduino/PortduinoGlue.h | 11 +++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index f3415aaee..a04c9c12c 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -178,18 +178,31 @@ void portduinoSetup() settingsMap[displayPanel] = st7735; else if (yamlConfig["Display"]["Panel"].as("") == "ST7735S") settingsMap[displayPanel] = st7735s; + else if (yamlConfig["Display"]["Panel"].as("") == "ST7796") + settingsMap[displayPanel] = st7796; else if (yamlConfig["Display"]["Panel"].as("") == "ILI9341") settingsMap[displayPanel] = ili9341; + else if (yamlConfig["Display"]["Panel"].as("") == "ILI9488") + settingsMap[displayPanel] = ili9488; + else if (yamlConfig["Display"]["Panel"].as("") == "HX8357D") + settingsMap[displayPanel] = hx8357d; + else if (yamlConfig["Display"]["Panel"].as("") == "X11") + settingsMap[displayPanel] = x11; settingsMap[displayHeight] = yamlConfig["Display"]["Height"].as(0); settingsMap[displayWidth] = yamlConfig["Display"]["Width"].as(0); settingsMap[displayDC] = yamlConfig["Display"]["DC"].as(-1); settingsMap[displayCS] = yamlConfig["Display"]["CS"].as(-1); + settingsMap[displayRGBOrder] = yamlConfig["Display"]["RGBOrder"].as(false); settingsMap[displayBacklight] = yamlConfig["Display"]["Backlight"].as(-1); + settingsMap[displayBacklightInvert] = yamlConfig["Display"]["BacklightInvert"].as(false); + settingsMap[displayBacklightPWMChannel] = yamlConfig["Display"]["BacklightPWMChannel"].as(-1); settingsMap[displayReset] = yamlConfig["Display"]["Reset"].as(-1); settingsMap[displayOffsetX] = yamlConfig["Display"]["OffsetX"].as(0); settingsMap[displayOffsetY] = yamlConfig["Display"]["OffsetY"].as(0); settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as(false); + settingsMap[displayOffsetRotate] = yamlConfig["Display"]["OffsetRotate"].as(1); settingsMap[displayInvert] = yamlConfig["Display"]["Invert"].as(false); + settingsMap[displayBusFrequency] = yamlConfig["Display"]["BusFrequency"].as(40000000); if (yamlConfig["Display"]["spidev"]) { settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); } @@ -200,8 +213,14 @@ void portduinoSetup() settingsMap[touchscreenModule] = xpt2046; else if (yamlConfig["Touchscreen"]["Module"].as("") == "STMPE610") settingsMap[touchscreenModule] = stmpe610; + else if (yamlConfig["Touchscreen"]["Module"].as("") == "GT911") + settingsMap[touchscreenModule] = gt911; + else if (yamlConfig["Touchscreen"]["Module"].as("") == "FT5x06") + settingsMap[touchscreenModule] = ft5x06; settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as(-1); settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); + settingsMap[touchscreenBusFrequency] = yamlConfig["Touchscreen"]["BusFrequency"].as(1000000); + settingsMap[touchscreenRotate] = yamlConfig["Touchscreen"]["Rotate"].as(-1); if (yamlConfig["Touchscreen"]["spidev"]) { settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 980fc63b8..ed2954eef 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -21,16 +21,23 @@ enum configNames { touchscreenModule, touchscreenCS, touchscreenIRQ, + touchscreenBusFrequency, + touchscreenRotate, touchscreenspidev, displayspidev, + displayBusFrequency, displayPanel, displayWidth, displayHeight, displayCS, displayDC, + displayRGBOrder, displayBacklight, + displayBacklightPWMChannel, + displayBacklightInvert, displayReset, displayRotate, + displayOffsetRotate, displayOffsetX, displayOffsetY, displayInvert, @@ -41,8 +48,8 @@ enum configNames { webserverrootpath, maxnodes }; -enum { no_screen, st7789, st7735, st7735s, ili9341 }; -enum { no_touchscreen, xpt2046, stmpe610 }; +enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; +enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; enum { level_error, level_warn, level_info, level_debug }; extern std::map settingsMap; From e7828c4c64e306f1ec281633bc60650be8d9102a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 07:36:53 -0500 Subject: [PATCH 0278/1377] [create-pull-request] automated change (#3676) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 0d08acd9c..f4be94a7f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0d08acd9c51c4e5575f3ea42368834ec990b2278 +Subproject commit f4be94a7fc92d5db4a25b26886496934939dc8bd diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 2abe040a6..105380044 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -283,6 +283,8 @@ typedef struct _meshtastic_Config_DeviceConfig { bool disable_triple_click; /* POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. */ char tzdef[65]; + /* If true, inhibit blinking LED at LED_PIN regularly */ + bool status_led_off; } meshtastic_Config_DeviceConfig; /* Position Config */ @@ -580,7 +582,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, ""} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} @@ -589,7 +591,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, ""} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} @@ -610,6 +612,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_is_managed_tag 9 #define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10 #define meshtastic_Config_DeviceConfig_tzdef_tag 11 +#define meshtastic_Config_DeviceConfig_status_led_off_tag 12 #define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1 #define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2 #define meshtastic_Config_PositionConfig_fixed_position_tag 3 @@ -710,7 +713,8 @@ X(a, STATIC, SINGULAR, UINT32, node_info_broadcast_secs, 7) \ X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \ X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \ -X(a, STATIC, SINGULAR, STRING, tzdef, 11) +X(a, STATIC, SINGULAR, STRING, tzdef, 11) \ +X(a, STATIC, SINGULAR, BOOL, status_led_off, 12) #define meshtastic_Config_DeviceConfig_CALLBACK NULL #define meshtastic_Config_DeviceConfig_DEFAULT NULL @@ -829,7 +833,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 10 -#define meshtastic_Config_DeviceConfig_size 98 +#define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 28 #define meshtastic_Config_LoRaConfig_size 80 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 856eb6f4a..2506ec647 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -308,7 +308,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 702 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3344 +#define meshtastic_OEMStore_size 3346 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index fa7ebcfee..1799f49da 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 535 +#define meshtastic_LocalConfig_size 537 #define meshtastic_LocalModuleConfig_size 663 #ifdef __cplusplus From 419eb1396808497d837ff498588252b2f9009cd3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 09:56:55 -0500 Subject: [PATCH 0279/1377] [create-pull-request] automated change (#3679) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index f4be94a7f..ea127fcbd 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit f4be94a7fc92d5db4a25b26886496934939dc8bd +Subproject commit ea127fcbd894458ecfe0eccaea6528afbf9e3275 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index b6d3811a4..07a6bcae0 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -43,7 +43,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* INA3221 3 Channel Voltage / Current Sensor */ meshtastic_TelemetrySensorType_INA3221 = 14, /* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */ - meshtastic_TelemetrySensorType_BMP085 = 15 + meshtastic_TelemetrySensorType_BMP085 = 15, + /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */ + meshtastic_TelemetrySensorType_RCWL9620 = 16 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -78,6 +80,8 @@ typedef struct _meshtastic_EnvironmentMetrics { /* relative scale IAQ value as measured by Bosch BME680 . value 0-500. Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ uint16_t iaq; + /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ + float water_level; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -148,8 +152,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BMP085 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BMP085+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_RCWL9620 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_RCWL9620+1)) @@ -159,12 +163,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -182,6 +186,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_voltage_tag 5 #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 +#define meshtastic_EnvironmentMetrics_water_level_tag 8 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -223,7 +228,8 @@ X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) \ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ -X(a, STATIC, SINGULAR, UINT32, iaq, 7) +X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ +X(a, STATIC, SINGULAR, FLOAT, water_level, 8) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -283,7 +289,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 34 +#define meshtastic_EnvironmentMetrics_size 39 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From 0972a8dccb40ec9fc202b2810fda10f163a84133 Mon Sep 17 00:00:00 2001 From: caveman99 Date: Sat, 20 Apr 2024 18:24:40 +0000 Subject: [PATCH 0280/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index ea127fcbd..6e30bbb48 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit ea127fcbd894458ecfe0eccaea6528afbf9e3275 +Subproject commit 6e30bbb482c97a7de6efee168fb121c5af7b261b diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 07a6bcae0..e670dd340 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -81,7 +81,7 @@ typedef struct _meshtastic_EnvironmentMetrics { Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ - float water_level; + float distance; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -186,7 +186,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_voltage_tag 5 #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 -#define meshtastic_EnvironmentMetrics_water_level_tag 8 +#define meshtastic_EnvironmentMetrics_distance_tag 8 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -229,7 +229,7 @@ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, water_level, 8) +X(a, STATIC, SINGULAR, FLOAT, distance, 8) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL From 9170fe0580f7eccd440b446100edca0e604bc16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 20 Apr 2024 16:16:20 +0200 Subject: [PATCH 0281/1377] Support radar sensor RCWL-9620 on i2c --- platformio.ini | 3 ++- src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 1 + src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 25 ++++++++++++++---- .../Telemetry/Sensor/RCWL9620Sensor.cpp | 26 +++++++++++++++++++ src/modules/Telemetry/Sensor/RCWL9620Sensor.h | 17 ++++++++++++ 8 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/RCWL9620Sensor.h diff --git a/platformio.ini b/platformio.ini index a1082a84a..01924e290 100644 --- a/platformio.ini +++ b/platformio.ini @@ -132,4 +132,5 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file + adafruit/Adafruit LSM6DS@^4.7.2 + m5stack/M5Unit-Sonic@^0.0.2 diff --git a/src/configuration.h b/src/configuration.h index 701e07a32..493449764 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -128,6 +128,7 @@ along with this program. If not, see . #define LPS22HB_ADDR_ALT 0x5D #define SHT31_ADDR 0x44 #define PMSA0031_ADDR 0x12 +#define RCWL9620_ADDR 0x57 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c8fcfee10..6fb2057b2 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,6 +41,7 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, + RCWL9620, #ifdef HAS_NCP5623 NCP5623, #endif diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 13c2f4609..53050d39b 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -288,6 +288,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n") SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n") + SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n") case LPS22HB_ADDR_ALT: SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n") diff --git a/src/main.cpp b/src/main.cpp index b1a15634f..3fe9ba185 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -533,6 +533,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) i2cScanner.reset(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 189ab7ed0..a8c2f0a8d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -21,6 +21,7 @@ #include "Sensor/BMP280Sensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" +#include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHTC3Sensor.h" @@ -32,6 +33,7 @@ MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; +RCWL9620Sensor rcwl9620Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -90,6 +92,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); + if (rcwl9620Sensor.hasSensor()) + result = rcwl9620Sensor.runOnce(); } return result; } else { @@ -183,6 +187,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); if (lastMeasurement.variant.environment_metrics.iaq != 0) display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + if (lastMeasurement.variant.environment_metrics.water_level != 0) + display->drawString(x, y += fontHeight(FONT_SMALL), + "Water Level: " + String(lastMeasurement.variant.environment_metrics.water_level, 0) + "mm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -192,10 +199,13 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, " - "temperature=%f, voltage=%f\n", + "temperature=%f\n", sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, - t->variant.environment_metrics.temperature, t->variant.environment_metrics.voltage); + t->variant.environment_metrics.temperature); + LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, water_level=%f\n", sender, t->variant.environment_metrics.voltage, + t->variant.environment_metrics.iaq, t->variant.environment_metrics.water_level); + #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -220,6 +230,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.relative_humidity = 0; m.variant.environment_metrics.temperature = 0; m.variant.environment_metrics.voltage = 0; + m.variant.environment_metrics.iaq = 0; + m.variant.environment_metrics.water_level = 0; if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); @@ -241,13 +253,16 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = ina219Sensor.getMetrics(&m); if (ina260Sensor.hasSensor()) valid = ina260Sensor.getMetrics(&m); + if (rcwl9620Sensor.hasSensor()) + valid = rcwl9620Sensor.getMetrics(&m); if (valid) { - LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f, " - "voltage=%f\n", + LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, - m.variant.environment_metrics.temperature, m.variant.environment_metrics.voltage); + m.variant.environment_metrics.temperature); + LOG_INFO("(Sending): voltage=%f, IAQ=%d, water_level=%f\n", m.variant.environment_metrics.voltage, + m.variant.environment_metrics.iaq, m.variant.environment_metrics.water_level); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp new file mode 100644 index 000000000..d27dd459e --- /dev/null +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -0,0 +1,26 @@ +#include "RCWL9620Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" + +RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {} + +int32_t RCWL9620Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + status = 1; + rcwl9620.begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first, -1, -1); + return initI2CSensor(); +} + +void RCWL9620Sensor::setup() {} + +bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("RCWL9620Sensor::getMetrics\n"); + measurement->variant.environment_metrics.water_level = rcwl9620.getDistance(); + return true; +} \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h new file mode 100644 index 000000000..d3efe0ef5 --- /dev/null +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class RCWL9620Sensor : public TelemetrySensor +{ + private: + SONIC_I2C rcwl9620; + + protected: + virtual void setup() override; + + public: + RCWL9620Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file From 94e1f016e57021a891f0cbd45bac0c910db11303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 20 Apr 2024 20:49:57 +0200 Subject: [PATCH 0282/1377] Change name --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 14 +++++++------- src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index a8c2f0a8d..bbd734b5a 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -187,9 +187,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); if (lastMeasurement.variant.environment_metrics.iaq != 0) display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); - if (lastMeasurement.variant.environment_metrics.water_level != 0) + if (lastMeasurement.variant.environment_metrics.distance != 0) display->drawString(x, y += fontHeight(FONT_SMALL), - "Water Level: " + String(lastMeasurement.variant.environment_metrics.water_level, 0) + "mm"); + "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -203,8 +203,8 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, t->variant.environment_metrics.temperature); - LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, water_level=%f\n", sender, t->variant.environment_metrics.voltage, - t->variant.environment_metrics.iaq, t->variant.environment_metrics.water_level); + LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f\n", sender, t->variant.environment_metrics.voltage, + t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance); #endif // release previous packet before occupying a new spot @@ -231,7 +231,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.temperature = 0; m.variant.environment_metrics.voltage = 0; m.variant.environment_metrics.iaq = 0; - m.variant.environment_metrics.water_level = 0; + m.variant.environment_metrics.distance = 0; if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); @@ -261,8 +261,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, m.variant.environment_metrics.temperature); - LOG_INFO("(Sending): voltage=%f, IAQ=%d, water_level=%f\n", m.variant.environment_metrics.voltage, - m.variant.environment_metrics.iaq, m.variant.environment_metrics.water_level); + LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f\n", m.variant.environment_metrics.voltage, + m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index d27dd459e..96e9a7445 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -21,6 +21,6 @@ void RCWL9620Sensor::setup() {} bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) { LOG_DEBUG("RCWL9620Sensor::getMetrics\n"); - measurement->variant.environment_metrics.water_level = rcwl9620.getDistance(); + measurement->variant.environment_metrics.distance = rcwl9620.getDistance(); return true; } \ No newline at end of file From ec39e1136a5e5f0d949c94fcaeea58875975d0d2 Mon Sep 17 00:00:00 2001 From: Ric In New Mexico <78682404+RicInNewMexico@users.noreply.github.com> Date: Sat, 20 Apr 2024 14:58:21 -0600 Subject: [PATCH 0283/1377] INA3221 Mis-identification fix (#3681) --- src/detect/ScanI2CTwoWire.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 13c2f4609..e2e2188b6 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -271,9 +271,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port) } break; case INA3221_ADDR: - LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address); - type = INA3221; - break; + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2); + LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue); + if (registerValue == 0x5449) { + LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address); + type = INA3221; + } else { // Unknown device + LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address); + } case MCP9808_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2); if (registerValue == 0x0400) { From e72792afc846ed0559fda6586a3e7753baf95848 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 15:58:42 -0500 Subject: [PATCH 0284/1377] [create-pull-request] automated change (#3683) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index 6e30bbb48..eade2c6be 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 6e30bbb482c97a7de6efee168fb121c5af7b261b +Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 105380044..0830ed851 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -283,8 +283,8 @@ typedef struct _meshtastic_Config_DeviceConfig { bool disable_triple_click; /* POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. */ char tzdef[65]; - /* If true, inhibit blinking LED at LED_PIN regularly */ - bool status_led_off; + /* If true, disable the default blinking LED (LED_PIN) behavior on the device */ + bool led_heartbeat_disabled; } meshtastic_Config_DeviceConfig; /* Position Config */ @@ -612,7 +612,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_is_managed_tag 9 #define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10 #define meshtastic_Config_DeviceConfig_tzdef_tag 11 -#define meshtastic_Config_DeviceConfig_status_led_off_tag 12 +#define meshtastic_Config_DeviceConfig_led_heartbeat_disabled_tag 12 #define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1 #define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2 #define meshtastic_Config_PositionConfig_fixed_position_tag 3 @@ -714,7 +714,7 @@ X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \ X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \ X(a, STATIC, SINGULAR, STRING, tzdef, 11) \ -X(a, STATIC, SINGULAR, BOOL, status_led_off, 12) +X(a, STATIC, SINGULAR, BOOL, led_heartbeat_disabled, 12) #define meshtastic_Config_DeviceConfig_CALLBACK NULL #define meshtastic_Config_DeviceConfig_DEFAULT NULL From fb7a878d94874c116191c51784e89e002d5dca65 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Sun, 21 Apr 2024 08:24:51 +0100 Subject: [PATCH 0285/1377] tweaked guards to allow various combinations of RGB leds --- src/detect/ScanI2C.h | 2 +- src/main.cpp | 11 +- src/modules/ExternalNotificationModule.cpp | 155 +++++++++------------ 3 files changed, 68 insertions(+), 100 deletions(-) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c8fcfee10..f2069cd09 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,7 +41,7 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, -#ifdef HAS_NCP5623 +#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) NCP5623, #endif } DeviceType; diff --git a/src/main.cpp b/src/main.cpp index 430fa3a6b..4d741d32e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -607,15 +607,14 @@ void setup() } #endif -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#ifdef UNPHONE + ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE); +#elif defined(RGBLED_RED) + ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE); +#elif !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) if (rgb_found.type != ScanI2C::DeviceType::NONE) { ambientLightingThread = new AmbientLightingThread(rgb_found.type); } -#ifdef UNPHONE - ambientLightingThread = new AmbientLightingThread(rgb_found.type); -#elif defined(RGBLED_RED) - ambientLightingThread = new AmbientLightingThread(rgb_found.type); -#endif #endif #ifdef T_WATCH_S3 diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 4bab90527..bc48e419b 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -26,29 +26,14 @@ #ifdef HAS_NCP5623 #include - -uint8_t red = 0; -uint8_t green = 0; -uint8_t blue = 0; -uint8_t colorState = 1; -uint8_t brightnessIndex = 0; -uint8_t brightnessValues[] = {0, 10, 20, 30, 50, 90, 160, 170}; // blue gets multiplied by 1.5 -bool ascending = true; #endif #ifdef UNPHONE #include "unPhone.h" extern unPhone unphone; - -uint8_t red = 0; -uint8_t green = 0; -uint8_t blue = 0; -uint8_t colorState = 1; -const uint8_t duration = 15; -uint8_t counter = 0; #endif -#ifdef RGBLED_RED +#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) uint8_t red = 0; uint8_t green = 0; uint8_t blue = 0; @@ -130,53 +115,27 @@ int32_t ExternalNotificationModule::runOnce() millis()) { getExternal(2) ? setExternalOff(2) : setExternalOn(2); } -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0; // Red enabled on colorState = 4,5,6,7 - green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7 - blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7 - rgb.setColor(red, green, blue); - - if (ascending) { // fade in - brightnessIndex++; - if (brightnessIndex == (sizeof(brightnessValues) - 1)) { - ascending = false; - } - } else { - brightnessIndex--; // fade out - } - if (brightnessIndex == 0) { - ascending = true; - colorState++; // next color - if (colorState > 7) { - colorState = 1; - } - } - } -#endif - -#ifdef UNPHONE - red = colorState & 4; // Red enabled on colorState = 4,5,6,7 - green = colorState & 2; // Green enabled on colorState = 2,3,6,7 - blue = colorState & 1; // Blue enabled on colorState = 1,3,5,7 - unphone.rgb(red, green, blue); - counter++; // tick on - if (counter > duration) { - counter = 0; - colorState++; // next color - if (colorState > 7) { - colorState = 1; - } - } -#endif - -#ifdef RGBLED_RED +#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0; // Red enabled on colorState = 4,5,6,7 green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7 blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7 +#ifdef HAS_NCP5623 + if (rgb_found.type == ScanI2C::NCP5623) { + rgb.setColor(red, green, blue); + } +#endif +#ifdef UNPHONE + unphone.rgb(red, green, blue); +#endif +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic + analogWrite(RGBLED_GREEN, 255 - green); + analogWrite(RGBLED_BLUE, 255 - blue); +#elif defined(RGBLED_RED) analogWrite(RGBLED_RED, red); analogWrite(RGBLED_GREEN, green); analogWrite(RGBLED_BLUE, blue); +#endif if (ascending) { // fade in brightnessIndex++; if (brightnessIndex == (sizeof(brightnessValues) - 1)) { @@ -192,35 +151,35 @@ int32_t ExternalNotificationModule::runOnce() colorState = 1; } } + } #endif #ifdef T_WATCH_S3 - drv.go(); + drv.go(); #endif - } - - // Play RTTTL over i2s audio interface if enabled as buzzer -#ifdef HAS_I2S - if (moduleConfig.external_notification.use_i2s_as_buzzer) { - if (audioThread->isPlaying()) { - // Continue playing - } else if (isNagging && (nagCycleCutoff >= millis())) { - audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); - } - } -#endif - // now let the PWM buzzer play - if (moduleConfig.external_notification.use_pwm) { - if (rtttl::isPlaying()) { - rtttl::play(); - } else if (isNagging && (nagCycleCutoff >= millis())) { - // start the song again if we have time left - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); - } - } - - return EXT_NOTIFICATION_DEFAULT_THREAD_MS; } + + // Play RTTTL over i2s audio interface if enabled as buzzer +#ifdef HAS_I2S + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + if (audioThread->isPlaying()) { + // Continue playing + } else if (isNagging && (nagCycleCutoff >= millis())) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } + } +#endif + // now let the PWM buzzer play + if (moduleConfig.external_notification.use_pwm) { + if (rtttl::isPlaying()) { + rtttl::play(); + } else if (isNagging && (nagCycleCutoff >= millis())) { + // start the song again if we have time left + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } + } + + return EXT_NOTIFICATION_DEFAULT_THREAD_MS; } bool ExternalNotificationModule::wantPacket(const meshtastic_MeshPacket *p) @@ -265,13 +224,13 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) unphone.rgb(red, green, blue); #endif #ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - red); + analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic analogWrite(RGBLED_GREEN, 255 - green); analogWrite(RGBLED_BLUE, 255 - blue); #elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, red); - analogWrite(RGBLED_GREEN, green); - analogWrite(RGBLED_BLUE, blue); + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); #endif #ifdef T_WATCH_S3 drv.go(); @@ -315,21 +274,21 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) blue = 0; unphone.rgb(red, green, blue); #endif -#ifdef RGBLED_CA +#ifdef RGBLED_RED red = 0; green = 0; blue = 0; - analogWrite(RGBLED_RED, 255 - red); +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic analogWrite(RGBLED_GREEN, 255 - green); analogWrite(RGBLED_BLUE, 255 - blue); -#elif defined(RGBLED_RED) - red = 0; - green = 0; - blue = 0; +#else analogWrite(RGBLED_RED, red); analogWrite(RGBLED_GREEN, green); analogWrite(RGBLED_BLUE, blue); #endif +#endif + #ifdef T_WATCH_S3 drv.stop(); #endif @@ -427,6 +386,16 @@ ExternalNotificationModule::ExternalNotificationModule() rgb.begin(); rgb.setCurrent(10); } +#endif +#ifdef RGBLED_RED + pinMode(RGBLED_RED, OUTPUT); // set up the RGB led pins + pinMode(RGBLED_GREEN, OUTPUT); + pinMode(RGBLED_BLUE, OUTPUT); +#endif +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255); // with a common anode type, logic is reversed + analogWrite(RGBLED_GREEN, 255); // so we want to initialise with lights off + analogWrite(RGBLED_BLUE, 255); #endif } else { LOG_INFO("External Notification Module Disabled\n"); @@ -489,7 +458,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP #ifdef HAS_I2S audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); #else - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); #endif } if (moduleConfig.external_notification.nag_timeout) { @@ -533,7 +502,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); } #else - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); #endif } if (moduleConfig.external_notification.nag_timeout) { From cf65661c7ca9bce3e470d7f744df219c6af0d525 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Sun, 21 Apr 2024 08:59:40 +0100 Subject: [PATCH 0286/1377] another silly error --- src/AmbientLightingThread.h | 40 ++++++------------ src/modules/ExternalNotificationModule.cpp | 48 +++++++++++----------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 81c9c85c7..2febc3d8c 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -32,56 +32,42 @@ class AmbientLightingThread : public concurrency::OSThread disable(); return; } +#endif +#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) if (!moduleConfig.ambient_lighting.led_state) { LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n"); disable(); return; } LOG_DEBUG("AmbientLightingThread initializing\n"); +#ifdef HAS_NCP5623 if (_type == ScanI2C::NCP5623) { rgb.begin(); - setLighting(); - } -#endif -#ifdef UNPHONE - if (!moduleConfig.ambient_lighting.led_state) { - LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n"); - disable(); - return; - } - LOG_DEBUG("AmbientLightingThread initializing\n"); - setLighting(); #endif #ifdef RGBLED_RED - if (!moduleConfig.ambient_lighting.led_state) { - LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n"); - disable(); - return; + pinMode(RGBLED_RED, OUTPUT); + pinMode(RGBLED_GREEN, OUTPUT); + pinMode(RGBLED_BLUE, OUTPUT); +#endif + setLighting(); +#endif +#ifdef HAS_NCP5623 } - LOG_DEBUG("AmbientLightingThread initializing\n"); - pinMode(RGBLED_RED, OUTPUT); - pinMode(RGBLED_GREEN, OUTPUT); - pinMode(RGBLED_BLUE, OUTPUT); - setLighting(); #endif } protected: int32_t runOnce() override { +#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) #ifdef HAS_NCP5623 if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) { +#endif setLighting(); return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification +#ifdef HAS_NCP5623 } #endif -#ifdef UNPHONE - setLighting(); - return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification -#endif -#ifdef RGBLED_RED - setLighting(); - return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification #endif return disable(); } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index bc48e419b..236178498 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -151,35 +151,35 @@ int32_t ExternalNotificationModule::runOnce() colorState = 1; } } - } #endif #ifdef T_WATCH_S3 - drv.go(); + drv.go(); #endif - } + } - // Play RTTTL over i2s audio interface if enabled as buzzer + // Play RTTTL over i2s audio interface if enabled as buzzer #ifdef HAS_I2S - if (moduleConfig.external_notification.use_i2s_as_buzzer) { - if (audioThread->isPlaying()) { - // Continue playing - } else if (isNagging && (nagCycleCutoff >= millis())) { - audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + if (audioThread->isPlaying()) { + // Continue playing + } else if (isNagging && (nagCycleCutoff >= millis())) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } } - } #endif - // now let the PWM buzzer play - if (moduleConfig.external_notification.use_pwm) { - if (rtttl::isPlaying()) { - rtttl::play(); - } else if (isNagging && (nagCycleCutoff >= millis())) { - // start the song again if we have time left - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + // now let the PWM buzzer play + if (moduleConfig.external_notification.use_pwm) { + if (rtttl::isPlaying()) { + rtttl::play(); + } else if (isNagging && (nagCycleCutoff >= millis())) { + // start the song again if we have time left + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } } - } - return EXT_NOTIFICATION_DEFAULT_THREAD_MS; + return EXT_NOTIFICATION_DEFAULT_THREAD_MS; + } } bool ExternalNotificationModule::wantPacket(const meshtastic_MeshPacket *p) @@ -228,9 +228,9 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) analogWrite(RGBLED_GREEN, 255 - green); analogWrite(RGBLED_BLUE, 255 - blue); #elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, red); - analogWrite(RGBLED_GREEN, green); - analogWrite(RGBLED_BLUE, blue); + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); #endif #ifdef T_WATCH_S3 drv.go(); @@ -458,7 +458,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP #ifdef HAS_I2S audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); #else - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); #endif } if (moduleConfig.external_notification.nag_timeout) { @@ -502,7 +502,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); } #else - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); #endif } if (moduleConfig.external_notification.nag_timeout) { From 9e4ef92e6d9343d7209529799708c24a1f9eb8a0 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Sun, 21 Apr 2024 09:16:50 +0100 Subject: [PATCH 0287/1377] lets just define it without guards! --- src/detect/ScanI2C.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index f2069cd09..05a5cb2ea 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,9 +41,7 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, -#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) NCP5623, -#endif } DeviceType; // typedef uint8_t DeviceAddress; From c480f0870cee559f61ba99c8c23bb7fd6a4df937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 20 Apr 2024 16:16:20 +0200 Subject: [PATCH 0288/1377] Support radar sensor RCWL-9620 on i2c --- platformio.ini | 3 ++- src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 1 + src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 25 ++++++++++++++---- .../Telemetry/Sensor/RCWL9620Sensor.cpp | 26 +++++++++++++++++++ src/modules/Telemetry/Sensor/RCWL9620Sensor.h | 17 ++++++++++++ 8 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/RCWL9620Sensor.h diff --git a/platformio.ini b/platformio.ini index a1082a84a..01924e290 100644 --- a/platformio.ini +++ b/platformio.ini @@ -132,4 +132,5 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file + adafruit/Adafruit LSM6DS@^4.7.2 + m5stack/M5Unit-Sonic@^0.0.2 diff --git a/src/configuration.h b/src/configuration.h index 701e07a32..493449764 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -128,6 +128,7 @@ along with this program. If not, see . #define LPS22HB_ADDR_ALT 0x5D #define SHT31_ADDR 0x44 #define PMSA0031_ADDR 0x12 +#define RCWL9620_ADDR 0x57 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c8fcfee10..6fb2057b2 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,6 +41,7 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, + RCWL9620, #ifdef HAS_NCP5623 NCP5623, #endif diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index e2e2188b6..8ab4e4c7e 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -293,6 +293,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n") SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n") + SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n") case LPS22HB_ADDR_ALT: SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n") diff --git a/src/main.cpp b/src/main.cpp index b1a15634f..3fe9ba185 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -533,6 +533,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) i2cScanner.reset(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 189ab7ed0..a8c2f0a8d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -21,6 +21,7 @@ #include "Sensor/BMP280Sensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" +#include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHTC3Sensor.h" @@ -32,6 +33,7 @@ MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; +RCWL9620Sensor rcwl9620Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -90,6 +92,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); + if (rcwl9620Sensor.hasSensor()) + result = rcwl9620Sensor.runOnce(); } return result; } else { @@ -183,6 +187,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); if (lastMeasurement.variant.environment_metrics.iaq != 0) display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + if (lastMeasurement.variant.environment_metrics.water_level != 0) + display->drawString(x, y += fontHeight(FONT_SMALL), + "Water Level: " + String(lastMeasurement.variant.environment_metrics.water_level, 0) + "mm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -192,10 +199,13 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, " - "temperature=%f, voltage=%f\n", + "temperature=%f\n", sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, - t->variant.environment_metrics.temperature, t->variant.environment_metrics.voltage); + t->variant.environment_metrics.temperature); + LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, water_level=%f\n", sender, t->variant.environment_metrics.voltage, + t->variant.environment_metrics.iaq, t->variant.environment_metrics.water_level); + #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -220,6 +230,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.relative_humidity = 0; m.variant.environment_metrics.temperature = 0; m.variant.environment_metrics.voltage = 0; + m.variant.environment_metrics.iaq = 0; + m.variant.environment_metrics.water_level = 0; if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); @@ -241,13 +253,16 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = ina219Sensor.getMetrics(&m); if (ina260Sensor.hasSensor()) valid = ina260Sensor.getMetrics(&m); + if (rcwl9620Sensor.hasSensor()) + valid = rcwl9620Sensor.getMetrics(&m); if (valid) { - LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f, " - "voltage=%f\n", + LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, - m.variant.environment_metrics.temperature, m.variant.environment_metrics.voltage); + m.variant.environment_metrics.temperature); + LOG_INFO("(Sending): voltage=%f, IAQ=%d, water_level=%f\n", m.variant.environment_metrics.voltage, + m.variant.environment_metrics.iaq, m.variant.environment_metrics.water_level); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp new file mode 100644 index 000000000..d27dd459e --- /dev/null +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -0,0 +1,26 @@ +#include "RCWL9620Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" + +RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {} + +int32_t RCWL9620Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + status = 1; + rcwl9620.begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first, -1, -1); + return initI2CSensor(); +} + +void RCWL9620Sensor::setup() {} + +bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("RCWL9620Sensor::getMetrics\n"); + measurement->variant.environment_metrics.water_level = rcwl9620.getDistance(); + return true; +} \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h new file mode 100644 index 000000000..d3efe0ef5 --- /dev/null +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class RCWL9620Sensor : public TelemetrySensor +{ + private: + SONIC_I2C rcwl9620; + + protected: + virtual void setup() override; + + public: + RCWL9620Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file From 5218aaafcf039c6edbbc470cf8065b97cfc6e5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 20 Apr 2024 20:49:57 +0200 Subject: [PATCH 0289/1377] Change name --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 14 +++++++------- src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index a8c2f0a8d..bbd734b5a 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -187,9 +187,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); if (lastMeasurement.variant.environment_metrics.iaq != 0) display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); - if (lastMeasurement.variant.environment_metrics.water_level != 0) + if (lastMeasurement.variant.environment_metrics.distance != 0) display->drawString(x, y += fontHeight(FONT_SMALL), - "Water Level: " + String(lastMeasurement.variant.environment_metrics.water_level, 0) + "mm"); + "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -203,8 +203,8 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, t->variant.environment_metrics.temperature); - LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, water_level=%f\n", sender, t->variant.environment_metrics.voltage, - t->variant.environment_metrics.iaq, t->variant.environment_metrics.water_level); + LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f\n", sender, t->variant.environment_metrics.voltage, + t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance); #endif // release previous packet before occupying a new spot @@ -231,7 +231,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.temperature = 0; m.variant.environment_metrics.voltage = 0; m.variant.environment_metrics.iaq = 0; - m.variant.environment_metrics.water_level = 0; + m.variant.environment_metrics.distance = 0; if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); @@ -261,8 +261,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, m.variant.environment_metrics.temperature); - LOG_INFO("(Sending): voltage=%f, IAQ=%d, water_level=%f\n", m.variant.environment_metrics.voltage, - m.variant.environment_metrics.iaq, m.variant.environment_metrics.water_level); + LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f\n", m.variant.environment_metrics.voltage, + m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index d27dd459e..96e9a7445 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -21,6 +21,6 @@ void RCWL9620Sensor::setup() {} bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) { LOG_DEBUG("RCWL9620Sensor::getMetrics\n"); - measurement->variant.environment_metrics.water_level = rcwl9620.getDistance(); + measurement->variant.environment_metrics.distance = rcwl9620.getDistance(); return true; } \ No newline at end of file From 820c5dc8c5a9cd300e15a2fd9aa9e3a5c35d09af Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Sun, 21 Apr 2024 13:24:39 +0100 Subject: [PATCH 0290/1377] Update architecture.h (#3688) --- src/platform/esp32/architecture.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 15e437bb5..c6d90970f 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -117,8 +117,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER #elif defined(TLORA_T3S3_V1) #define HW_VENDOR meshtastic_HardwareModel_TLORA_T3_S3 -#elif defined(CDEBYTE_ELORA_S3) -#define HW_VENDOR meshtastic_HardwareModel_CDEBYTE_ELORA_S3 +#elif defined(CDEBYTE_EORA_S3) +#define HW_VENDOR meshtastic_HardwareModel_CDEBYTE_EORA_S3 #elif defined(BETAFPV_2400_TX) #define HW_VENDOR meshtastic_HardwareModel_BETAFPV_2400_TX #elif defined(NANO_G1_EXPLORER) From f6cfdfe881871ccae66017d9421cd8ea1a730f48 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 22 Apr 2024 00:25:12 +1200 Subject: [PATCH 0291/1377] (ESP-32S) Fix "critical error 3" after deep-sleep (#3685) --- src/sleep.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index a2a221d79..548ef2c54 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -157,6 +157,10 @@ void initDeepSleep() for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) rtc_gpio_hold_dis((gpio_num_t)i); + + // ESP32 (original) + else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) + gpio_hold_dis((gpio_num_t)i); } } #endif @@ -258,14 +262,17 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) } #ifdef BUTTON_PIN // Avoid leakage through button pin - pinMode(BUTTON_PIN, INPUT); - gpio_hold_en((gpio_num_t)BUTTON_PIN); + if (GPIO_IS_VALID_OUTPUT_GPIO(BUTTON_PIN)) { + pinMode(BUTTON_PIN, INPUT); + gpio_hold_en((gpio_num_t)BUTTON_PIN); + } #endif - - // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - gpio_hold_en((gpio_num_t)LORA_CS); + if (GPIO_IS_VALID_OUTPUT_GPIO(LORA_CS)) { + // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + gpio_hold_en((gpio_num_t)LORA_CS); + } #endif #ifdef HAS_PMU From dfc43bae1859d677c0f1580ce1b44f6d0ae2f435 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 22 Apr 2024 00:25:58 +1200 Subject: [PATCH 0292/1377] Fix crash on shutdown, if Bluetooth not enabled (#3686) Previously attempted to call deinit method for a nullptr --- src/sleep.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 548ef2c54..e58c3872a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -213,7 +213,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #ifdef ARCH_ESP32 // Full shutdown of bluetooth hardware - nimbleBluetooth->deinit(); + if (nimbleBluetooth) + nimbleBluetooth->deinit(); #endif #ifdef ARCH_ESP32 From 402b0d7e0bb41c2437f94e346ab1f35b2d597345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 21 Apr 2024 14:39:55 +0200 Subject: [PATCH 0293/1377] ditch that no-good m5 dependancy and do it ourself --- platformio.ini | 1 - .../Telemetry/Sensor/RCWL9620Sensor.cpp | 41 +++++++++++++++++-- src/modules/Telemetry/Sensor/RCWL9620Sensor.h | 8 +++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 01924e290..89c69013d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -133,4 +133,3 @@ lib_deps = adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 adafruit/Adafruit LSM6DS@^4.7.2 - m5stack/M5Unit-Sonic@^0.0.2 diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index 96e9a7445..84003188e 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -11,8 +11,7 @@ int32_t RCWL9620Sensor::runOnce() if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = 1; - rcwl9620.begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first, -1, -1); + status = begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); return initI2CSensor(); } @@ -21,6 +20,42 @@ void RCWL9620Sensor::setup() {} bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) { LOG_DEBUG("RCWL9620Sensor::getMetrics\n"); - measurement->variant.environment_metrics.distance = rcwl9620.getDistance(); + measurement->variant.environment_metrics.distance = getDistance(); return true; +} + +bool RCWL9620Sensor::begin(uint8_t addr, TwoWire *wire) +{ + _wire = wire; + _addr = addr; + if (i2c_dev) + delete i2c_dev; + i2c_dev = new Adafruit_I2CDevice(_addr, _wire); + if (!i2c_dev->begin()) + return false; + return true; +} + +float RCWL9620Sensor::getDistance() +{ + uint32_t data; + _wire->beginTransmission(_addr); // Transfer data to addr. + _wire->write(0x01); + _wire->endTransmission(); // Stop data transmission with the Ultrasonic + // Unit. + + _wire->requestFrom(_addr, + (uint8_t)3); // Request 3 bytes from Ultrasonic Unit. + + data = _wire->read(); + data <<= 8; + data |= _wire->read(); + data <<= 8; + data |= _wire->read(); + float Distance = float(data) / 1000; + if (Distance > 4500.00) { + return 4500.00; + } else { + return Distance; + } } \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h index d3efe0ef5..4120e19d9 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -1,14 +1,18 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include +#include class RCWL9620Sensor : public TelemetrySensor { private: - SONIC_I2C rcwl9620; + uint8_t _addr; + TwoWire *_wire; protected: virtual void setup() override; + bool begin(uint8_t addr = 0x57, TwoWire *wire = &Wire); + Adafruit_I2CDevice *i2c_dev = NULL; + float getDistance(); public: RCWL9620Sensor(); From 41f355749145bd40ee0228c151c233e7af90b76b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 21 Apr 2024 07:42:36 -0500 Subject: [PATCH 0294/1377] Refactor smart position to use throttle helper (#3671) * Added one minute throttling to NodeDB * Derp * Refactor smart-position to use throttle --- src/modules/PositionModule.cpp | 76 ++++++++++++++++------------------ src/modules/PositionModule.h | 7 +++- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index dcfe03f7f..250daec57 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -17,6 +17,7 @@ extern "C" { #include "mesh/compression/unishox2.h" +#include } PositionModule *positionModule; @@ -63,11 +64,11 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } // Log packet size and data fields - LOG_INFO("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d " - "time=%d\n", - getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae, - p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp, - p.time); + LOG_DEBUG("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d " + "time=%d\n", + getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae, + p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp, + p.time); if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { struct timeval tv; @@ -222,6 +223,16 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() return mp; } +void PositionModule::sendOurPosition() +{ + bool requestReplies = currentGeneration != radioGeneration; + currentGeneration = radioGeneration; + + // If we changed channels, ask everyone else for their latest info + LOG_INFO("Sending pos@%x:6 to mesh (wantReplies=%d)\n", localPosition.timestamp, requestReplies); + sendOurPosition(NODENUM_BROADCAST, requestReplies); +} + void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t channel) { // cancel any not yet sent (now stale) position packets @@ -299,12 +310,7 @@ int32_t PositionModule::runOnce() lastGpsLatitude = node->position.latitude_i; lastGpsLongitude = node->position.longitude_i; - // If we changed channels, ask everyone else for their latest info - bool requestReplies = currentGeneration != radioGeneration; - currentGeneration = radioGeneration; - - LOG_INFO("Sending pos@%x:6 to mesh (wantReplies=%d)\n", localPosition.timestamp, requestReplies); - sendOurPosition(NODENUM_BROADCAST, requestReplies); + sendOurPosition(); if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) { sendLostAndFoundText(); } @@ -314,29 +320,23 @@ int32_t PositionModule::runOnce() if (hasValidPosition(node2)) { // The minimum time (in seconds) that would pass before we are able to send a new position packet. - const uint32_t minimumTimeThreshold = - Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); auto smartPosition = getDistanceTraveledSinceLastSend(node->position); + uint32_t msSinceLastSend = now - lastGpsSend; - if (smartPosition.hasTraveledOverThreshold && msSinceLastSend >= minimumTimeThreshold) { - bool requestReplies = currentGeneration != radioGeneration; - currentGeneration = radioGeneration; + if (smartPosition.hasTraveledOverThreshold && + Throttle::execute( + &lastGpsSend, minimumTimeThreshold, []() { positionModule->sendOurPosition(); }, + []() { LOG_DEBUG("Skipping send smart broadcast due to time throttling\n"); })) { - LOG_INFO("Sending smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims, " - "minTimeInterval=%ims)\n", - localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold, - msSinceLastSend, minimumTimeThreshold); - sendOurPosition(NODENUM_BROADCAST, requestReplies); + LOG_DEBUG("Sent smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims, " + "minTimeInterval=%ims)\n", + localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold, + msSinceLastSend, minimumTimeThreshold); // Set the current coords as our last ones, after we've compared distance with current and decided to send lastGpsLatitude = node->position.latitude_i; lastGpsLongitude = node->position.longitude_i; - - /* Update lastGpsSend to now. This means if the device is stationary, then - getPref_position_broadcast_secs will still apply. - */ - lastGpsSend = now; } } } @@ -396,27 +396,21 @@ void PositionModule::handleNewPosition() meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position // We limit our GPS broadcasts to a max rate - uint32_t now = millis(); - uint32_t msSinceLastSend = now - lastGpsSend; - if (hasValidPosition(node2)) { auto smartPosition = getDistanceTraveledSinceLastSend(node->position); - if (smartPosition.hasTraveledOverThreshold) { - bool requestReplies = currentGeneration != radioGeneration; - currentGeneration = radioGeneration; - - LOG_INFO("Sending smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims)\n", - localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold, msSinceLastSend); - sendOurPosition(NODENUM_BROADCAST, requestReplies); + uint32_t msSinceLastSend = millis() - lastGpsSend; + if (smartPosition.hasTraveledOverThreshold && + Throttle::execute( + &lastGpsSend, minimumTimeThreshold, []() { positionModule->sendOurPosition(); }, + []() { LOG_DEBUG("Skipping send smart broadcast due to time throttling\n"); })) { + LOG_DEBUG("Sent smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims, " + "minTimeInterval=%ims)\n", + localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold, msSinceLastSend, + minimumTimeThreshold); // Set the current coords as our last ones, after we've compared distance with current and decided to send lastGpsLatitude = node->position.latitude_i; lastGpsLongitude = node->position.longitude_i; - - /* Update lastGpsSend to now. This means if the device is stationary, then - getPref_position_broadcast_secs will still apply. - */ - lastGpsSend = now; } } } diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 68171ab0e..0e24e3a9e 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -1,4 +1,5 @@ #pragma once +#include "Default.h" #include "ProtobufModule.h" #include "concurrency/OSThread.h" @@ -29,7 +30,8 @@ class PositionModule : public ProtobufModule, private concu /** * Send our position into the mesh */ - void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false, uint8_t channel = 0); + void sendOurPosition(NodeNum dest, bool wantReplies = false, uint8_t channel = 0); + void sendOurPosition(); void handleNewPosition(); @@ -52,6 +54,9 @@ class PositionModule : public ProtobufModule, private concu meshtastic_MeshPacket *allocAtakPli(); uint32_t precision; void sendLostAndFoundText(); + + const uint32_t minimumTimeThreshold = + Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); }; struct SmartPosition { From 9822a8527487f377c38dd4143288299faf2d8c8a Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Sun, 21 Apr 2024 14:40:23 +0100 Subject: [PATCH 0295/1377] Add board and variant definitions for EBYTE_ESP32-S3 (#2882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create ESP32-S3-WROOM-1-N4.json * Create pins_arduino.h * Create platformio.ini * Create variant.h * Update mesh.pb.h * Update architecture.h * Update mesh.pb.h * Update variant.h * Update variant.h Add example schematic * Update architecture.h * Revert update architecture.h * Create variant.h * Create pins_arduino.h * Create platformio.ini * Delete variants/E22-900M_S3 directory * Update architecture.h * Update variant.h * Update platformio.ini * Update variant.h * Update variant.h * Update architecture.h * Update platformio.ini * Update architecture.h * Update ESP32-S3-WROOM-1-N4.json * Update platformio.ini * Update ESP32-S3-WROOM-1-N4.json * Update variant.h * Update variant.h * Update variant.h * Update variant.h * Update pins_arduino.h * Update architecture.h * add SX1268 allow * GPS * Commit * Whitespace * Update variant.h * Update variant.h * trunk --------- Co-authored-by: Ben Meadors Co-authored-by: Thomas Göttgens Co-authored-by: S5NC <> --- boards/ESP32-S3-WROOM-1-N4.json | 39 +++++ src/platform/esp32/architecture.h | 4 + variants/EBYTE_ESP32-S3/pins_arduino.h | 37 +++++ variants/EBYTE_ESP32-S3/platformio.ini | 9 ++ variants/EBYTE_ESP32-S3/variant.h | 193 +++++++++++++++++++++++++ 5 files changed, 282 insertions(+) create mode 100644 boards/ESP32-S3-WROOM-1-N4.json create mode 100644 variants/EBYTE_ESP32-S3/pins_arduino.h create mode 100644 variants/EBYTE_ESP32-S3/platformio.ini create mode 100644 variants/EBYTE_ESP32-S3/variant.h diff --git a/boards/ESP32-S3-WROOM-1-N4.json b/boards/ESP32-S3-WROOM-1-N4.json new file mode 100644 index 000000000..3620a711d --- /dev/null +++ b/boards/ESP32-S3-WROOM-1-N4.json @@ -0,0 +1,39 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D ARDUINO_USB_CDC_ON_BOOT=0", + "-D ARDUINO_USB_MSC_ON_BOOT=0", + "-D ARDUINO_USB_DFU_ON_BOOT=0", + "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "ESP32-S3-WROOM-1-N4" + }, + "connectivity": ["wifi"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 524288, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf", + "vendor": "Espressif" +} diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index c6d90970f..27088f86f 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -127,6 +127,10 @@ #define HW_VENDOR meshtastic_HardwareModel_BETAFPV_900_NANO_TX #elif defined(PICOMPUTER_S3) #define HW_VENDOR meshtastic_HardwareModel_PICOMPUTER_S3 +#elif defined(HELTEC_HT62) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_HT62 +#elif defined(EBYTE_ESP32_S3) +#define HW_VENDOR meshtastic_HardwareModel_EBYTE_ESP32_S3 #elif defined(ESP32_S3_PICO) #define HW_VENDOR meshtastic_HardwareModel_ESP32_S3_PICO #elif defined(SENSELORA_S3) diff --git a/variants/EBYTE_ESP32-S3/pins_arduino.h b/variants/EBYTE_ESP32-S3/pins_arduino.h new file mode 100644 index 000000000..38a9103f0 --- /dev/null +++ b/variants/EBYTE_ESP32-S3/pins_arduino.h @@ -0,0 +1,37 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) \ + (((p) < 48) ? (p) : -1) // Maybe it should be <= 48 but this is from a trustworthy source so it is likely correct +#define digitalPinHasPWM(p) (p < 46) + +// Serial +static const uint8_t TX = UART_TX; +static const uint8_t RX = UART_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = LORA_CS; +static const uint8_t SCK = LORA_SCK; +static const uint8_t MOSI = LORA_MOSI; +static const uint8_t MISO = LORA_MISO; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = I2C_SCL; +static const uint8_t SDA = I2C_SDA; + +#endif /* Pins_Arduino_h */ diff --git a/variants/EBYTE_ESP32-S3/platformio.ini b/variants/EBYTE_ESP32-S3/platformio.ini new file mode 100644 index 000000000..10de91386 --- /dev/null +++ b/variants/EBYTE_ESP32-S3/platformio.ini @@ -0,0 +1,9 @@ +[env:EBYTE_ESP32-S3] +extends = esp32s3_base +; board assumes the lowest spec WROOM module: 4 MB (Quad SPI) Flash, No PSRAM +board = ESP32-S3-WROOM-1-N4 +board_level = extra +build_flags = + ${esp32s3_base.build_flags} + -D EBYTE_ESP32_S3 + -I variants/EBYTE_ESP32-S3 diff --git a/variants/EBYTE_ESP32-S3/variant.h b/variants/EBYTE_ESP32-S3/variant.h new file mode 100644 index 000000000..10b39617b --- /dev/null +++ b/variants/EBYTE_ESP32-S3/variant.h @@ -0,0 +1,193 @@ +// Supporting information: https://github.com/S5NC/EBYTE_ESP32-S3/ + +// Originally developed for E22-900M30S with ESP32-S3-WROOM-1-N4 +// NOTE: Uses ESP32-S3-WROOM-1-N4.json in boards folder (via platformio.ini board field), assumes 4 MB (quad SPI) flash, no PSRAM + +// FIXME: implement SX12 module type autodetection and have setup for each case (add E32 support) +// E32 has same pinout except having extra pins. I assume that the GND on it is connected internally to other GNDs so it is not a +// problem to NC the extra GND pins. + +// For each EBYTE module pin in this section, provide the pin number of the ESP32-S3 you connected it to +// The ESP32-S3 is great because YOU CAN USE PRACTICALLY ANY PINS for the connections, but avoid some pins (such as on the WROOM +// modules the following): strapping pins (except 0 as a user button input as it already has a pulldown resistor in typical +// application schematic) (0, 3, 45, 46), USB-reserved (19, 20), and pins which aren't present on the WROOM-2 module for +// compatiblity as it uses octal SPI, or are likely connected internally in either WROOM version (26-37), and avoid pins whose +// voltages are set by the SPI voltage (47, 48), and pins that don't exist (22-25) You can ALSO set the SPI pins (SX126X_CS, +// SX126X_SCK, SX126X_MISO, SX126X_MOSI) to any pin with the ESP32-S3 due to \ GPIO Matrix / IO MUX / RTC IO MUX \, and also the +// serial pins, but this isn't recommended for Serial0 as the WROOM modules have a 499 Ohm resistor on U0TXD (to reduce harmonics +// but also acting as a sort of protection) + +// We have many free pins on the ESP32-S3-WROOM-X-Y module, perhaps it is best to use one of its pins to control TXEN, and use +// DIO2 as an extra interrupt, but right now Meshtastic does not benefit from having another interrupt pin available. + +// Adding two 0-ohm links on your PCB design so that you can choose between the two modes for controlling the E22's TXEN would +// enable future software to make the most of an extra available interrupt pin + +// Possible improvement: can add extremely low resistance MOSFET to physically toggle power to E22 module when in full sleep (not +// waiting for interrupt)? + +// PA stands for Power Amplifier, used when transmitting to increase output power +// LNA stands for Low Noise Amplifier, used when \ listening for / receiving \ data to increase sensitivity + +////////////////////////////////////////////////////////////////////////////////// +// // +// Have custom connections or functionality? Configure them in this section // +// // +////////////////////////////////////////////////////////////////////////////////// + +#define SX126X_CS 14 // EBYTE module's NSS pin // FIXME: rename to SX126X_SS +#define LORA_SCK 21 // EBYTE module's SCK pin +#define LORA_MOSI 38 // EBYTE module's MOSI pin +#define LORA_MISO 39 // EBYTE module's MISO pin +#define SX126X_RESET 40 // EBYTE module's NRST pin +#define SX126X_BUSY 41 // EBYTE module's BUSY pin +#define SX126X_DIO1 42 // EBYTE module's DIO1 pin +// We don't define a pin for SX126X_DIO2 as Meshtastic doesn't use it as an interrupt output, so it is never connected to an MCU +// pin! Also E22 module datasheets say not to connect it to an MCU pin. +// We don't define a pin for SX126X_DIO3 as Meshtastic doesn't use it as an interrupt output, so it is never connected to an MCU +// pin! Also E22 module datasheets say to use it as the TCXO's reference voltage. +// E32 module (which uses SX1276) may not have ability to set TCXO voltage using a DIO pin. + +// The radio module needs to be told whether to enable RX mode or TX mode. Each radio module takes different actions based on +// these values, but generally the path from the antenna to SX1262 is changed from signal output to signal input. Also, if there +// are LNAs (Low-Noise Amplifiers) or PAs (Power Amplifiers) in the output or input paths, their power is also controlled by +// these pins. You should never have both TXEN and RXEN set high, this can cause problems for some radio modules, and is +// commonly referred to as 'undefined behaviour' in datasheets. For the SX1262, you shouldn't connect DIO2 to the MCU. DIO2 is +// an output only, and can be controlled via SPI instructions, the use for this is to save an MCU pin by using the DIO2 pin to +// control the RF switching mode. + +// Choose ONLY ONE option from below, comment in/out the '/*'s and '*/'s +// SX126X_TXEN is the E22's [SX1262's] TXEN pin, SX126X_RXEN is the E22's [SX1262's] RXEN pin + +// Option 1: E22's TXEN pin connected to E22's DIO2 pin, E22's RXEN pin connected to NEGATED output of E22's DIO2 pin (more +// expensive option hardware-wise, is the 'most proper' way, removes need for routing one/two traces from MCU to RF switching +// pins), however you can't have E22 in low-power 'sleep' mode (TXEN and RXEN both low cannot be achieved this this option). +/* +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_TXEN RADIOLIB_NC +#define SX126X_RXEN RADIOLIB_NC +*/ + +// Option 2: E22's TXEN pin connected to E22's DIO2 pin, E22's RXEN pin connected to MCU pin (cheaper option hardware-wise, +// removes need for routing another trace from MCU to an RF switching pin). +// /* +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_TXEN RADIOLIB_NC +#define SX126X_RXEN 10 +// */ + +// Option 3: E22's TXEN pin connected to MCU pin, E22's RXEN pin connected to MCU pin (cheaper option hardware-wise, allows for +// ramping up PA before transmission (add/expand on feature yourself in RadioLib) if PA takes a while to stabilise) +// Don't define DIO2_AS_RF_SWITCH because we only use DIO2 or an MCU pin mutually exclusively to connect to E22's TXEN (to prevent +// a short if they are both connected at the same time (suboptimal PCB design) and there's a slight non-neglibible delay and/or +// voltage difference between DIO2 and TXEN). Can use DIO2 as an IRQ (but not in Meshtastic at the moment). +/* +#define SX126X_TXEN 9 +#define SX126X_RXEN 10 +*/ + +// (NOT RECOMMENDED, if need to ramp up PA before transmission, better to use option 3) +// Option 4: E22's TXEN pin connected to MCU pin, E22's RXEN pin connected to NEGATED output of E22's DIO2 pin (more expensive +// option hardware-wise, allows for ramping up PA before transmission (add/expand on feature yourself in RadioLib) if PA takes +// a while to stabilise, removes need for routing another trace from MCU to an RF switching pin, however may mean if in +// RadioLib you don't tell DIO2 to go high to indicate transmission (so the negated output goes to RXEN to turn the LNA off) +// then you may end up enabling E22's TXEN and RXEN pins at the same time whilst you ramp up the PA which is not ideal, +// changing DIO2's switching advance in RadioLib may not even be possible, may be baked into the SX126x). +/* +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_TXEN 9 +#define SX126X_RXEN RADIOLIB_NC +*/ + +// Status +#define LED_PIN 1 +#define LED_INVERTED 0 +// External notification +// FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the +// app/preferences +#define EXT_NOTIFY_OUT 2 // The GPIO pin that acts as the external notification output (here we connect an LED to it) +// Buzzer +#define PIN_BUZZER 11 +// Buttons +#define BUTTON_PIN 0 // Use the BOOT button as the user button +// I2C +#define I2C_SCL 18 +#define I2C_SDA 8 +// UART +#define UART_TX 43 +#define UART_RX 44 + +// Power +// Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) +// Respect local regulations! If your E22-900M30S outputs the advertised 30 dBm and you use a 6 dBi antenna, you are at the +// equivalent of 36 EIRP (Effective Isotropic Radiated Power), which in this case is the limit for non-HAM users in the US (4W +// EIRP, at SPECIFIC frequencies). +// In the EU (and UK), as of now, you are allowed 27 dBm ERP which is 29.15 EIRP. +// https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32022D0180 +// https://www.legislation.gov.uk/uksi/1999/930/schedule/6/made +// To respect the 29.15 dBm EIRP (at SPECIFIC frequencies, others are lower) EU limit with a 2.5 dBi gain antenna, consulting +// https://github.com/S5NC/EBYTE_ESP32-S3/blob/main/power%20testing.txt, assuming 0.1 dBm insertion loss, output 20 dBm from the +// E22-900M30S's SX1262. It is worth noting that if you are in this situation and don't have a HAM license, you may be better off +// with a lower gain antenna, and output the difference as a higher total power input into the antenna, as your EIRP would be the +// same, but you would get a wider angle of coverage. Also take insertion loss and possibly VSWR into account +// (https://www.everythingrf.com/tech-resources/vswr). Please check regulations yourself and check airtime, usage (for example +// whether you are airborne), frequency, and power laws. +#define SX126X_MAX_POWER 22 // SX126xInterface.cpp defaults to 22 if not defined, but here we define it for good practice + +// Display +// FIXME: change behavior in src to default to not having screen if is undefined +// FIXME: remove 0/1 option for HAS_SCREEN in src, change to being defined or not +// FIXME: check if it actually causes a crash when not specifiying that a display isn't present +#define HAS_SCREEN 0 // Assume no screen present by default to prevent crash... + +// GPS +// FIXME: unsure what to define HAS_GPS as if GPS isn't always present +#define HAS_GPS 1 // Don't need to set this to 0 to prevent a crash as it doesn't crash if GPS not found, will probe by default +#define PIN_GPS_EN 15 +#define GPS_EN_ACTIVE 1 +#define GPS_TX_PIN 16 +#define GPS_RX_PIN 17 + +///////////////////////////////////////////////////////////////////////////////// +// // +// You should have no need to modify the code below, nor in pins_arduino.h // +// // +///////////////////////////////////////////////////////////////////////////////// + +#define USE_SX1262 // E22-900M30S, E22-900M22S, and E22-900MM22S (not E220!) use SX1262 +#define USE_SX1268 // E22-400M30S, E22-400M33S, E22-400M22S, and E22-400MM22S use SX1268 + +// The below isn't needed as we directly define SX126X_TXEN and SX126X_RXEN instead of using proxies E22_TXEN and E22_RXEN +/* +// FALLBACK: If somehow E22_TXEN isn't defined or clearly isn't a valid pin number, set it to RADIOLIB_NC to avoid SX126X_TXEN +being defined but having no value #if (!defined(E22_TXEN) || !(0 <= E22_TXEN && E22_TXEN <= 48)) #define E22_TXEN RADIOLIB_NC +#endif +// FALLBACK: If somehow E22_RXEN isn't defined or clearly isn't a valid pin number, set it to RADIOLIB_NC to avoid SX126X_RXEN +being defined but having no value #if (!defined(E22_RXEN) || !(0 <= E22_RXEN && E22_RXEN <= 48)) #define E22_RXEN RADIOLIB_NC +#endif +#define SX126X_TXEN E22_TXEN +#define SX126X_RXEN E22_RXEN +*/ + +// E22 series TCXO voltage is 1.8V per https://www.ebyte.com/en/pdf-down.aspx?id=781 (source +// https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575), so set it as such +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define LORA_CS SX126X_CS // FIXME: for some reason both are used in /src + +// Many of the below values would only be used if USE_RF95 was defined, but it's not as we aren't actually using an RF95, just +// that the 4 pins above are named like it If they aren't used they don't need to be defined and doing so cause confusion to those +// adapting this file LORA_RESET value is never used in src (as we are not using RF95), so no need to define LORA_DIO0 is not used +// in src (as we are not using RF95) as SX1262 does not have it per SX1262 datasheet, so no need to define +// FIXME: confirm that the linked lines below are actually only called when using the SX126x or SX128x and no other modules +// then use SX126X_DIO1 and SX128X_DIO1 respectively for that purpose, removing the need for RF95-style LORA_* definitions when +// the RF95 isn't used +#define LORA_DIO1 \ + SX126X_DIO1 // The old name is used in + // https://github.com/meshtastic/firmware/blob/7eff5e7bcb2084499b723c5e3846c15ee089e36d/src/sleep.cpp#L298, so + // must also define the old name +// LORA_DIO2 value is never used in src (as we are not using RF95), so no need to define, and if DIO2_AS_RF_SWITCH is set then it +// cannot serve any extra function even if requested to LORA_DIO3 value is never used in src (as we are not using RF95), so no +// need to define, and DIO3_AS_TCXO_AT_1V8 is set so it cannot serve any extra function even if requested to (from 13.3.2.1 +// DioxMask in SX1262 datasheet: Note that if DIO2 or DIO3 are used to control the RF Switch or the TCXO, the IRQ will not be +// generated even if it is mapped to the pins.) \ No newline at end of file From ac87c0065fa3c95bf2ab5aad39be331777782091 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 21 Apr 2024 08:45:36 -0500 Subject: [PATCH 0296/1377] Also refresh timestamp for "timeonly" fixed position nodes (#3689) --- src/mesh/NodeDB.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 57040fbd6..8e3784e58 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -150,12 +150,13 @@ class NodeDB void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { if (timeOnly) { - LOG_DEBUG("Setting local position time only: time=%i\n", position.time); + LOG_DEBUG("Setting local position time only: time=%i timestamp=%i\n", position.time, position.timestamp); localPosition.time = position.time; + localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; return; } - LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i\n", position.latitude_i, position.longitude_i, - position.time); + LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i, timeestamp=%i\n", position.latitude_i, + position.longitude_i, position.time, position.timestamp); localPosition = position; } From a231cd2ad0463d8a89cba6bb74753dc4a216dba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 21 Apr 2024 16:35:41 +0200 Subject: [PATCH 0297/1377] derp... --- src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp | 15 +++++++-------- src/modules/Telemetry/Sensor/RCWL9620Sensor.h | 8 +++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index 84003188e..03df57efd 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -11,7 +11,8 @@ int32_t RCWL9620Sensor::runOnce() if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + status = 1; + begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first); return initI2CSensor(); } @@ -24,16 +25,14 @@ bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) return true; } -bool RCWL9620Sensor::begin(uint8_t addr, TwoWire *wire) +void RCWL9620Sensor::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed) { _wire = wire; _addr = addr; - if (i2c_dev) - delete i2c_dev; - i2c_dev = new Adafruit_I2CDevice(_addr, _wire); - if (!i2c_dev->begin()) - return false; - return true; + _sda = sda; + _scl = scl; + _speed = speed; + _wire->begin(); } float RCWL9620Sensor::getDistance() diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h index 4120e19d9..4fb2aec2d 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -1,17 +1,19 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include +#include class RCWL9620Sensor : public TelemetrySensor { private: uint8_t _addr; TwoWire *_wire; + uint8_t _scl; + uint8_t _sda; + uint8_t _speed; protected: virtual void setup() override; - bool begin(uint8_t addr = 0x57, TwoWire *wire = &Wire); - Adafruit_I2CDevice *i2c_dev = NULL; + void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000L); float getDistance(); public: From 679e068e19519b274b26c9cd9e5deb950236944c Mon Sep 17 00:00:00 2001 From: Ric In New Mexico <78682404+RicInNewMexico@users.noreply.github.com> Date: Sun, 21 Apr 2024 11:35:42 -0600 Subject: [PATCH 0298/1377] Missing break in INA3221 i2c scan (#3692) * INA3221 Mis-identification fix * Missing break in INA3221 i2c scan --- src/detect/ScanI2CTwoWire.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index e2e2188b6..ba2820a77 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -279,6 +279,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) } else { // Unknown device LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address); } + break; case MCP9808_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2); if (registerValue == 0x0400) { From 0406be82d22f1e546b90d3aa5145409f2ec84d39 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Sun, 21 Apr 2024 19:36:37 +0200 Subject: [PATCH 0299/1377] Use correct format specifier and fixed typo. (#3696) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. --------- Co-authored-by: Ben Meadors --- src/mesh/NodeDB.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 8e3784e58..4946672ec 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -150,12 +150,12 @@ class NodeDB void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { if (timeOnly) { - LOG_DEBUG("Setting local position time only: time=%i timestamp=%i\n", position.time, position.timestamp); + LOG_DEBUG("Setting local position time only: time=%u timestamp=%u\n", position.time, position.timestamp); localPosition.time = position.time; localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; return; } - LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i, timeestamp=%i\n", position.latitude_i, + LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%u, timestamp=%u\n", position.latitude_i, position.longitude_i, position.time, position.timestamp); localPosition = position; } From 39bbf0d352fca0735643b53518646341b7fef67c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 21 Apr 2024 14:40:47 -0500 Subject: [PATCH 0300/1377] Added more clear RTC handling and quality logging (#3691) * Also refresh timestamp for "timeonly" fixed position nodes * Added more clear RTC quality handling * Fix clock drift from Phone GPS / NTP too --- src/gps/RTC.cpp | 28 ++++++++++++++++++++++++---- src/gps/RTC.h | 3 +++ src/modules/PositionModule.cpp | 18 +++++++++++------- src/modules/PositionModule.h | 1 + 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 26af7cac2..85931900f 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -104,13 +104,15 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv) bool shouldSet; if (q > currentQuality) { shouldSet = true; - LOG_DEBUG("Upgrading time to quality %d\n", q); - } else if (q == RTCQualityGPS && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { - // Every 12 hrs we will slam in a new GPS time, to correct for local RTC clock drift + LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q)); + } else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { + // Every 12 hrs we will slam in a new GPS or Phone GPS / NTP time, to correct for local RTC clock drift shouldSet = true; LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", tv->tv_sec); - } else + } else { shouldSet = false; + LOG_DEBUG("Current RTC quality: %s. Ignoring time of RTC quality of %s\n", RtcName(currentQuality), RtcName(q)); + } if (shouldSet) { currentQuality = q; @@ -162,6 +164,24 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv) } } +const char *RtcName(RTCQuality quality) +{ + switch (quality) { + case RTCQualityNone: + return "None"; + case RTCQualityDevice: + return "Device"; + case RTCQualityFromNet: + return "Net"; + case RTCQualityNTP: + return "NTP"; + case RTCQualityGPS: + return "GPS"; + default: + return "Unknown"; + } +} + /** * Sets the RTC time if the provided time is of higher quality than the current RTC time. * diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 0561819bd..1d609f136 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -28,6 +28,9 @@ RTCQuality getRTCQuality(); bool perhapsSetRTC(RTCQuality q, const struct timeval *tv); bool perhapsSetRTC(RTCQuality q, struct tm &t); +/// Return a string name for the quality +const char *RtcName(RTCQuality quality); + /// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero uint32_t getTime(bool local = false); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 250daec57..658b8b5a7 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -71,14 +71,8 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes p.time); if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { - struct timeval tv; - uint32_t secs = p.time; - - tv.tv_sec = secs; - tv.tv_usec = 0; - // Set from phone RTC Quality to RTCQualityNTP since it should be approximately so - perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv); + trySetRtc(p, isLocal); } nodeDB->updatePosition(getFrom(&mp), p); @@ -93,6 +87,16 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes return false; // Let others look at this message also if they want } +void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal) +{ + struct timeval tv; + uint32_t secs = p.time; + + tv.tv_sec = secs; + tv.tv_usec = 0; + perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv); +} + meshtastic_MeshPacket *PositionModule::allocReply() { if (precision == 0) { diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 0e24e3a9e..89ff50c64 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -52,6 +52,7 @@ class PositionModule : public ProtobufModule, private concu private: struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition); meshtastic_MeshPacket *allocAtakPli(); + void trySetRtc(meshtastic_Position p, bool isLocal); uint32_t precision; void sendLostAndFoundText(); From 4a48a3fb52c35f38d77954b9c8d013c323c160a9 Mon Sep 17 00:00:00 2001 From: Nicholas Baddorf <42445164+nbaddorf@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:41:22 -0400 Subject: [PATCH 0301/1377] Fixed bug making t-deck reboot when muted (#3694) --- src/modules/ExternalNotificationModule.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index a38b231af..b898e72ee 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -244,7 +244,8 @@ void ExternalNotificationModule::stopNow() { rtttl::stop(); #ifdef HAS_I2S - audioThread->stop(); + if (audioThread->isPlaying()) + audioThread->stop(); #endif nagCycleCutoff = 1; // small value isNagging = false; From fd9461505f2322027955ae8d38f6e04350888243 Mon Sep 17 00:00:00 2001 From: quimnut Date: Mon, 22 Apr 2024 10:51:02 +1000 Subject: [PATCH 0302/1377] adjust adc for rak11310 devices (#3698) --- variants/rak11310/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak11310/variant.h b/variants/rak11310/variant.h index ba3d4fed7..f9dcbd91a 100644 --- a/variants/rak11310/variant.h +++ b/variants/rak11310/variant.h @@ -14,7 +14,7 @@ #define BATTERY_PIN 26 #define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION // ratio of voltage divider = 3.0 (R17=200k, R18=100k) -#define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define ADC_MULTIPLIER 1.84 #define DETECTION_SENSOR_EN 28 @@ -47,4 +47,4 @@ // DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#endif \ No newline at end of file +#endif From f47b40cf6880f9560b8ece533f2a9e1c6a1bdf70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 21 Apr 2024 22:29:17 +0200 Subject: [PATCH 0303/1377] fix signedness warnings of NRF52 toolchain --- src/gps/GPS.cpp | 4 ++-- src/mesh/RadioInterface.cpp | 2 +- src/modules/RoutingModule.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0d0bfd9a2..17e35a4b3 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -452,7 +452,7 @@ bool GPS::setup() // Set the NEMA output messages // Ask for only RMC and GGA uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA}; - for (int i = 0; i < sizeof(fields); i++) { + for (uint i = 0; i < sizeof(fields); i++) { // Construct a CAS-CFG-MSG packet uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00}; msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet); @@ -1584,7 +1584,7 @@ bool GPS::hasFlow() bool GPS::whileIdle() { - int charsInBuf = 0; + uint charsInBuf = 0; bool isValid = false; if (!isAwake) { clearBuffer(); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 63912a03e..4fa0bef7a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -492,7 +492,7 @@ void RadioInterface::applyModemConfig() // If user has manually specified a channel num, then use that, otherwise generate one by hashing the name const char *channelName = channels.getName(channels.getPrimaryIndex()); // channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1) - int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; + uint channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; // Check if we use the default frequency slot RadioInterface::uses_default_frequency_slot = diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index a52328ca4..fe1abab05 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -51,7 +51,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit; if (hopsUsed > config.lora.hop_limit) { return hopsUsed; // If the request used more hops than the limit, use the same amount of hops - } else if (hopsUsed + 2 < config.lora.hop_limit) { + } else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) { return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different } } From 30d4c3a94587e6b8bdb91a2801a68ef7bda84f50 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Mon, 22 Apr 2024 10:47:11 +0200 Subject: [PATCH 0304/1377] Updates for esp32s2 build --- arch/esp32/esp32s2.ini | 7 +++++-- src/modules/esp32/PaxcounterModule.cpp | 2 +- src/sleep.cpp | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini index 5de0fa549..df66de2ed 100644 --- a/arch/esp32/esp32s2.ini +++ b/arch/esp32/esp32s2.ini @@ -2,14 +2,17 @@ extends = esp32_base build_src_filter = - ${esp32_base.build_src_filter} - - + ${esp32_base.build_src_filter} - - - monitor_speed = 115200 build_flags = ${esp32_base.build_flags} -DHAS_BLUETOOTH=0 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER + -DMESHTASTIC_EXCLUDE_BLUETOOTH lib_ignore = ${esp32_base.lib_ignore} - NimBLE-Arduino \ No newline at end of file + NimBLE-Arduino + libpax \ No newline at end of file diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index aad7b5d63..b9fdfcb63 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -1,5 +1,5 @@ #include "configuration.h" -#if defined(ARCH_ESP32) +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER #include "Default.h" #include "MeshService.h" #include "PaxcounterModule.h" diff --git a/src/sleep.cpp b/src/sleep.cpp index e58c3872a..fe73a755c 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -211,7 +211,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // esp_wifi_stop(); waitEnterSleep(skipPreflight); -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH // Full shutdown of bluetooth hardware if (nimbleBluetooth) nimbleBluetooth->deinit(); From 250cf16bf8793aefed95b9cedb9c20b2f2e7a2a7 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Mon, 22 Apr 2024 21:21:50 +0800 Subject: [PATCH 0305/1377] Add ability to turn off heartbeat LED blinking (#3674) * Add ability to turn off status LED blinking Fixes #3635 and depends on [protobufs PR #485](https://github.com/meshtastic/protobufs/pull/485) Signed-off-by: Andrew Yong * led_heartbeat_disabled * trunk --------- Signed-off-by: Andrew Yong Co-authored-by: Ben Meadors --- src/main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index b1a15634f..f40fd0789 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -179,6 +179,11 @@ const char *getDeviceName() static int32_t ledBlinker() { + // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if + // config.device.led_heartbeat_disabled is changed + if (config.device.led_heartbeat_disabled) + return 1000; + static bool ledOn; ledOn ^= 1; @@ -1001,4 +1006,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} From 5dd08e95330f009f79f04963711a73092289dece Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Mon, 22 Apr 2024 14:42:52 +0100 Subject: [PATCH 0306/1377] added NeoPixel support using Adafruit library --- src/AmbientLightingThread.h | 31 ++++++++--- src/main.cpp | 4 +- src/modules/ExternalNotificationModule.cpp | 61 ++++++++++++++-------- 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 2febc3d8c..6b3360b1f 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -5,6 +5,11 @@ NCP5623 rgb; #endif +#ifdef HAS_NEOPIXEL +#include +Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); +#endif + #ifdef UNPHONE #include "unPhone.h" extern unPhone unphone; @@ -33,7 +38,7 @@ class AmbientLightingThread : public concurrency::OSThread return; } #endif -#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) if (!moduleConfig.ambient_lighting.led_state) { LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n"); disable(); @@ -48,6 +53,11 @@ class AmbientLightingThread : public concurrency::OSThread pinMode(RGBLED_RED, OUTPUT); pinMode(RGBLED_GREEN, OUTPUT); pinMode(RGBLED_BLUE, OUTPUT); +#endif +#ifdef HAS_NEOPIXEL + pixels.begin(); // Initialise the pixel(s) + pixels.clear(); // Set all pixel colors to 'off' + pixels.setBrightness(moduleConfig.ambient_lighting.current); #endif setLighting(); #endif @@ -59,7 +69,7 @@ class AmbientLightingThread : public concurrency::OSThread protected: int32_t runOnce() override { -#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) #ifdef HAS_NCP5623 if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) { #endif @@ -86,10 +96,14 @@ class AmbientLightingThread : public concurrency::OSThread moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif -#ifdef UNPHONE - unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); +#ifdef HAS_NEOPIXEL + pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, + moduleConfig.ambient_lighting.blue), + 0, NEOPIXEL_COUNT); + pixels.show(); + LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n", + moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, + moduleConfig.ambient_lighting.blue); #endif #ifdef RGBLED_CA analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); @@ -103,6 +117,11 @@ class AmbientLightingThread : public concurrency::OSThread analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue); LOG_DEBUG("Initializing Ambient lighting RGB Common Cathode w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); +#endif +#ifdef UNPHONE + unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red, + moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif } }; diff --git a/src/main.cpp b/src/main.cpp index 4d741d32e..c142ae15a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -607,9 +607,7 @@ void setup() } #endif -#ifdef UNPHONE - ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE); -#elif defined(RGBLED_RED) +#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED) ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE); #elif !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) if (rgb_found.type != ScanI2C::DeviceType::NONE) { diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 94e0aeadc..c02559240 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -28,12 +28,16 @@ #include #endif +#ifdef HAS_NEOPIXEL +#include +#endif + #ifdef UNPHONE #include "unPhone.h" extern unPhone unphone; #endif -#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) uint8_t red = 0; uint8_t green = 0; uint8_t blue = 0; @@ -115,7 +119,7 @@ int32_t ExternalNotificationModule::runOnce() millis()) { getExternal(2) ? setExternalOff(2) : setExternalOn(2); } -#if defined(HAS_NCP5623) || defined(UNPHONE) || defined(RGBLED_RED) +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0; // Red enabled on colorState = 4,5,6,7 green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7 blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7 @@ -124,9 +128,6 @@ int32_t ExternalNotificationModule::runOnce() rgb.setColor(red, green, blue); } #endif -#ifdef UNPHONE - unphone.rgb(red, green, blue); -#endif #ifdef RGBLED_CA analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic analogWrite(RGBLED_GREEN, 255 - green); @@ -135,6 +136,13 @@ int32_t ExternalNotificationModule::runOnce() analogWrite(RGBLED_RED, red); analogWrite(RGBLED_GREEN, green); analogWrite(RGBLED_BLUE, blue); +#endif +#ifdef HAS_NEOPIXEL + pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); + pixels.show(); +#endif +#ifdef UNPHONE + unphone.rgb(red, green, blue); #endif if (ascending) { // fade in brightnessIndex++; @@ -220,9 +228,6 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) rgb.setColor(red, green, blue); } #endif -#ifdef UNPHONE - unphone.rgb(red, green, blue); -#endif #ifdef RGBLED_CA analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic analogWrite(RGBLED_GREEN, 255 - green); @@ -232,6 +237,13 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) analogWrite(RGBLED_GREEN, green); analogWrite(RGBLED_BLUE, blue); #endif +#ifdef HAS_NEOPIXEL + pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); + pixels.show(); +#endif +#ifdef UNPHONE + unphone.rgb(red, green, blue); +#endif #ifdef T_WATCH_S3 drv.go(); #endif @@ -260,33 +272,31 @@ void ExternalNotificationModule::setExternalOff(uint8_t index) break; } +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) + red = 0; + green = 0; + blue = 0; #ifdef HAS_NCP5623 if (rgb_found.type == ScanI2C::NCP5623) { - red = 0; - green = 0; - blue = 0; rgb.setColor(red, green, blue); } #endif -#ifdef UNPHONE - red = 0; - green = 0; - blue = 0; - unphone.rgb(red, green, blue); -#endif -#ifdef RGBLED_RED - red = 0; - green = 0; - blue = 0; #ifdef RGBLED_CA analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic analogWrite(RGBLED_GREEN, 255 - green); analogWrite(RGBLED_BLUE, 255 - blue); -#else +#elif defined(RGBLED_RED) analogWrite(RGBLED_RED, red); analogWrite(RGBLED_GREEN, green); analogWrite(RGBLED_BLUE, blue); #endif +#ifdef HAS_NEOPIXEL + pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); + pixels.show(); +#endif +#ifdef UNPHONE + unphone.rgb(red, green, blue); +#endif #endif #ifdef T_WATCH_S3 @@ -397,6 +407,11 @@ ExternalNotificationModule::ExternalNotificationModule() analogWrite(RGBLED_RED, 255); // with a common anode type, logic is reversed analogWrite(RGBLED_GREEN, 255); // so we want to initialise with lights off analogWrite(RGBLED_BLUE, 255); +#endif +#ifdef HAS_NEOPIXEL + pixels.begin(); // Initialise the pixel(s) + pixels.clear(); // Set all pixel colors to 'off' + pixels.setBrightness(moduleConfig.ambient_lighting.current); #endif } else { LOG_INFO("External Notification Module Disabled\n"); @@ -578,4 +593,4 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) if (changed) { nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } -} \ No newline at end of file +} From ec2b854ea28bfc91f33b1ae92678b544afea1872 Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Mon, 22 Apr 2024 14:44:59 +0100 Subject: [PATCH 0307/1377] oops missed the extern enabling little chap --- src/graphics/NeoPixel.h | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/graphics/NeoPixel.h diff --git a/src/graphics/NeoPixel.h b/src/graphics/NeoPixel.h new file mode 100644 index 000000000..dde74366e --- /dev/null +++ b/src/graphics/NeoPixel.h @@ -0,0 +1,4 @@ +#ifdef HAS_NEOPIXEL +#include +extern Adafruit_NeoPixel pixels; +#endif \ No newline at end of file From 6669b22db386dd0dd40c7315fbf4c1a45d0e4d5c Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Mon, 22 Apr 2024 16:37:05 +0100 Subject: [PATCH 0308/1377] tidied up, prob broke everything --- .../Dongle_nRF52840-pca10059-v1/variant.cpp | 7 ++----- .../Dongle_nRF52840-pca10059-v1/variant.h | 21 +++++-------------- variants/betafpv_2400_tx_micro/platformio.ini | 2 +- variants/betafpv_2400_tx_micro/variant.h | 9 +++++--- variants/esp32-s3-pico/platformio.ini | 2 +- variants/esp32-s3-pico/variant.h | 4 ++++ variants/lora_relay_v1/platformio.ini | 3 ++- variants/lora_relay_v1/variant.h | 12 +++++++---- variants/lora_relay_v2/platformio.ini | 1 + variants/lora_relay_v2/variant.h | 13 ++++++++---- variants/my_esp32s3_diy_eink/platformio.ini | 4 ++-- variants/my_esp32s3_diy_eink/variant.h | 4 ++++ variants/my_esp32s3_diy_oled/platformio.ini | 4 ++-- variants/my_esp32s3_diy_oled/variant.h | 6 +++++- variants/unphone/platformio.ini | 4 +++- variants/unphone/variant.cpp | 1 + variants/unphone/variant.h | 13 ++++++++++-- 17 files changed, 67 insertions(+), 43 deletions(-) diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.cpp b/variants/Dongle_nRF52840-pca10059-v1/variant.cpp index 8c6bf039c..2fc87c718 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.cpp +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.cpp @@ -29,10 +29,7 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 + // LED1 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); -} +} \ No newline at end of file diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/Dongle_nRF52840-pca10059-v1/variant.h index 533367a30..aef702c7c 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.h @@ -43,24 +43,13 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (0 + 12) // Blue LED P1.12 -#define PIN_LED2 (0 + 6) // Built in Green P0.06 - -// Green Built in LED1 -// #define PIN_LED1 (0 + 6) // LED1 P1.15 - -// RGB NeoPixel LED2 -// #define PIN_LED1 (0 + 8) Red -// #define PIN_LED1 (32 + 9) Green -// #define PIN_LED1 (0 + 12) Blue +#define PIN_LED1 (0 + 6) // Built in Green P0.06 +#define RGBLED_RED (0 + 8) // Red of RGB P0.08 +#define RGBLED_GREEN (32 + 9) // Green of RGB P1.09 +#define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 +#define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic #define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 0 // State when LED is litted /* * Buttons diff --git a/variants/betafpv_2400_tx_micro/platformio.ini b/variants/betafpv_2400_tx_micro/platformio.ini index 82fe2a9e4..531e8532d 100644 --- a/variants/betafpv_2400_tx_micro/platformio.ini +++ b/variants/betafpv_2400_tx_micro/platformio.ini @@ -15,4 +15,4 @@ upload_protocol = esptool upload_speed = 460800 lib_deps = ${esp32_base.lib_deps} - makuna/NeoPixelBus@^2.7.1 + adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/betafpv_2400_tx_micro/variant.h b/variants/betafpv_2400_tx_micro/variant.h index 8c615d168..fd06183ee 100644 --- a/variants/betafpv_2400_tx_micro/variant.h +++ b/variants/betafpv_2400_tx_micro/variant.h @@ -1,5 +1,4 @@ // https://betafpv.com/products/elrs-micro-tx-module -#include // 0.96" OLED #define I2C_SDA 22 @@ -15,7 +14,11 @@ #define LORA_CS 5 #define RF95_FAN_EN 17 -#define LED_PIN 16 // This is a LED_WS2812 not a standard LED +// #define LED_PIN 16 // This is a LED_WS2812 not a standard LED +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 16 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use #define BUTTON_PIN 25 #define BUTTON_NEED_PULLUP @@ -31,4 +34,4 @@ #define SX128X_TXEN 26 #define SX128X_RXEN 27 #define SX128X_RESET LORA_RESET -#define SX128X_MAX_POWER 13 +#define SX128X_MAX_POWER 13 \ No newline at end of file diff --git a/variants/esp32-s3-pico/platformio.ini b/variants/esp32-s3-pico/platformio.ini index ef737d98a..ff77c30e0 100644 --- a/variants/esp32-s3-pico/platformio.ini +++ b/variants/esp32-s3-pico/platformio.ini @@ -22,4 +22,4 @@ build_flags = ${esp32_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} zinggjm/GxEPD2@^1.5.3 - ;adafruit/Adafruit NeoPixel@^1.10.7 + adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/esp32-s3-pico/variant.h b/variants/esp32-s3-pico/variant.h index 87378d378..bfcb6059d 100644 --- a/variants/esp32-s3-pico/variant.h +++ b/variants/esp32-s3-pico/variant.h @@ -10,6 +10,10 @@ // #define LED_PIN PIN_LED // Board has RGB LED 21 +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 21 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use // The usbPower state is revered ? // DEBUG | ??:??:?? 365 [Power] Battery: usbPower=0, isCharging=0, batMv=4116, batPct=90 diff --git a/variants/lora_relay_v1/platformio.ini b/variants/lora_relay_v1/platformio.ini index 77402aadc..8660bf64a 100644 --- a/variants/lora_relay_v1/platformio.ini +++ b/variants/lora_relay_v1/platformio.ini @@ -20,4 +20,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/lora_relay_v1> lib_deps = ${nrf52840_base.lib_deps} sparkfun/SparkFun BQ27441 LiPo Fuel Gauge Arduino Library@^1.1.0 - bodmer/TFT_eSPI@^2.4.76 \ No newline at end of file + bodmer/TFT_eSPI@^2.4.76 + adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/lora_relay_v1/variant.h b/variants/lora_relay_v1/variant.h index 9cfb69337..29fb4cb84 100644 --- a/variants/lora_relay_v1/variant.h +++ b/variants/lora_relay_v1/variant.h @@ -44,15 +44,19 @@ extern "C" { // LEDs #define PIN_LED1 (3) #define PIN_LED2 (4) -#define PIN_NEOPIXEL (8) +// #define PIN_NEOPIXEL (8) +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 8 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use #define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define PIN_LED2 #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define 1 // State when LED is litted /* * Buttons @@ -154,4 +158,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/lora_relay_v2/platformio.ini b/variants/lora_relay_v2/platformio.ini index 4439d8a46..cd2109f00 100644 --- a/variants/lora_relay_v2/platformio.ini +++ b/variants/lora_relay_v2/platformio.ini @@ -23,3 +23,4 @@ lib_deps = ${nrf52840_base.lib_deps} sparkfun/SparkFun BQ27441 LiPo Fuel Gauge Arduino Library@^1.1.0 bodmer/TFT_eSPI@^2.4.76 + adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/lora_relay_v2/variant.h b/variants/lora_relay_v2/variant.h index 3afe8620e..98881a496 100644 --- a/variants/lora_relay_v2/variant.h +++ b/variants/lora_relay_v2/variant.h @@ -61,16 +61,21 @@ extern "C" { // LEDs #define PIN_LED1 (3) #define PIN_LED2 (4) -#define PIN_NEOPIXEL (8) +// #define PIN_NEOPIXEL (8) +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 8 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use + #define PIN_BUZZER (40) #define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define PIN_LED2 #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define 1 // State when LED is litted /* * Buttons @@ -180,4 +185,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/my_esp32s3_diy_eink/platformio.ini b/variants/my_esp32s3_diy_eink/platformio.ini index 966bc580e..e81f2c1ab 100644 --- a/variants/my_esp32s3_diy_eink/platformio.ini +++ b/variants/my_esp32s3_diy_eink/platformio.ini @@ -13,7 +13,7 @@ platform_packages = lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2@^1.5.1 - adafruit/Adafruit NeoPixel@^1.10.7 + adafruit/Adafruit NeoPixel @ ^1.12.0 build_unflags = -DARDUINO_USB_MODE=1 build_flags = ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink @@ -24,4 +24,4 @@ build_flags = -DEINK_HEIGHT=128 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -DARDUINO_USB_MODE=0 + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/my_esp32s3_diy_eink/variant.h b/variants/my_esp32s3_diy_eink/variant.h index 516fa7f34..024f912dd 100644 --- a/variants/my_esp32s3_diy_eink/variant.h +++ b/variants/my_esp32s3_diy_eink/variant.h @@ -12,6 +12,10 @@ #define I2C_SCL 17 // 2 // #define LED_PIN 38 // This is a RGB LED not a standard LED +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use #define BUTTON_PIN 0 // This is the BOOT button #define BUTTON_NEED_PULLUP diff --git a/variants/my_esp32s3_diy_oled/platformio.ini b/variants/my_esp32s3_diy_oled/platformio.ini index 9b8b09f7f..2d7a5cd91 100644 --- a/variants/my_esp32s3_diy_oled/platformio.ini +++ b/variants/my_esp32s3_diy_oled/platformio.ini @@ -12,11 +12,11 @@ platform_packages = tool-esptoolpy@^1.40500.0 lib_deps = ${esp32_base.lib_deps} - adafruit/Adafruit NeoPixel@^1.10.7 + adafruit/Adafruit NeoPixel @ ^1.12.0 build_unflags = -DARDUINO_USB_MODE=1 build_flags = ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_oled ${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_oled -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -DARDUINO_USB_MODE=0 + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/my_esp32s3_diy_oled/variant.h b/variants/my_esp32s3_diy_oled/variant.h index 6dd18c236..8a3a39003 100644 --- a/variants/my_esp32s3_diy_oled/variant.h +++ b/variants/my_esp32s3_diy_oled/variant.h @@ -12,6 +12,10 @@ #define I2C_SCL 17 // 2 // #define LED_PIN 38 // This is a RGB LED not a standard LED +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use #define BUTTON_PIN 0 // This is the BOOT button #define BUTTON_NEED_PULLUP @@ -53,4 +57,4 @@ // #define PIN_EINK_DC 1 // #define PIN_EINK_RES (-1) // #define PIN_EINK_SCLK 5 -// #define PIN_EINK_MOSI 6 +// #define PIN_EINK_MOSI 6 \ No newline at end of file diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index dad9a7177..407976c48 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -13,6 +13,7 @@ build_unflags = -D ARDUINO_USB_MODE build_flags = ${esp32_base.build_flags} + -D BOARD_HAS_PSRAM -D UNPHONE -I variants/unphone -D ARDUINO_USB_MODE=0 @@ -27,4 +28,5 @@ build_src_filter = ${esp32_base.build_src_filter} +<../variants/unphone> lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX @ ^1.1.8 - https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic @ ^9.0.0 \ No newline at end of file + https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic @ ^9.0.0 + adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/unphone/variant.cpp b/variants/unphone/variant.cpp index 3f6d1c54d..7884f82e3 100644 --- a/variants/unphone/variant.cpp +++ b/variants/unphone/variant.cpp @@ -10,6 +10,7 @@ void initVariant() unphone.printWakeupReason(); // what woke us up? (stored, not printed :|) unphone.checkPowerSwitch(); // if power switch is off, shutdown unphone.backlight(false); // setup backlight and make sure its off + unphone.expanderPower(true); // enable power to expander / hat / sheild for (int i = 0; i < 3; i++) { // buzz a bit unphone.vibe(true); diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index 180fdfe2c..a3f2fce8c 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -1,7 +1,16 @@ // meshtastic/firmware/variants/unphone/variant.h #pragma once - +// RGB LED configuration +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA A0 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) #define SPI_SCK 39 #define SPI_MOSI 40 #define SPI_MISO 41 @@ -38,7 +47,7 @@ #define SCREEN_TRANSITION_FRAMERATE 5 #define HAS_TOUCHSCREEN 1 -#define USE_XPT2046 1 +#define USE_XPT2046 #define TOUCH_CS 38 #define HAS_GPS \ From ccbf635eef944d7d3fea9a9bb19440b94bafbfed Mon Sep 17 00:00:00 2001 From: Gareth Coleman Date: Mon, 22 Apr 2024 17:21:41 +0100 Subject: [PATCH 0309/1377] corrected a bit of overzealous tidying --- variants/Dongle_nRF52840-pca10059-v1/variant.h | 9 ++++++++- variants/lora_relay_v1/variant.h | 6 +++--- variants/lora_relay_v2/variant.h | 6 +++--- variants/unphone/platformio.ini | 2 +- variants/unphone/variant.h | 13 ++----------- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/Dongle_nRF52840-pca10059-v1/variant.h index aef702c7c..2318450eb 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.h @@ -44,12 +44,19 @@ extern "C" { // LEDs #define PIN_LED1 (0 + 6) // Built in Green P0.06 +#define PIN_LED2 (0 + 6) // Just here for completeness #define RGBLED_RED (0 + 8) // Red of RGB P0.08 #define RGBLED_GREEN (32 + 9) // Green of RGB P1.09 #define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 #define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic #define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 0 // State when LED is litted /* * Buttons @@ -157,4 +164,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif diff --git a/variants/lora_relay_v1/variant.h b/variants/lora_relay_v1/variant.h index 29fb4cb84..54bc87b68 100644 --- a/variants/lora_relay_v1/variant.h +++ b/variants/lora_relay_v1/variant.h @@ -51,12 +51,12 @@ extern "C" { #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use #define LED_BUILTIN PIN_LED1 -#define PIN_LED2 +#define LED_CONN PIN_LED2 #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED2 -#define 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is litted /* * Buttons @@ -158,4 +158,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif diff --git a/variants/lora_relay_v2/variant.h b/variants/lora_relay_v2/variant.h index 98881a496..6ef7ad7d6 100644 --- a/variants/lora_relay_v2/variant.h +++ b/variants/lora_relay_v2/variant.h @@ -70,12 +70,12 @@ extern "C" { #define PIN_BUZZER (40) #define LED_BUILTIN PIN_LED1 -#define PIN_LED2 +#define LED_CONN PIN_LED2 #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED2 -#define 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is litted /* * Buttons @@ -185,4 +185,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index 407976c48..e4a92fe4c 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -13,7 +13,7 @@ build_unflags = -D ARDUINO_USB_MODE build_flags = ${esp32_base.build_flags} - -D BOARD_HAS_PSRAM + ;-D BOARD_HAS_PSRAM // what's up with this - doesn't seem to be recognised at boot -D UNPHONE -I variants/unphone -D ARDUINO_USB_MODE=0 diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index a3f2fce8c..180fdfe2c 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -1,16 +1,7 @@ // meshtastic/firmware/variants/unphone/variant.h #pragma once -// RGB LED configuration -#define HAS_NEOPIXEL // Enable the use of neopixels -#define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA A0 // gpio pin used to send data to the neopixels -#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use -// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) -// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) -// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) -// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) -// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + #define SPI_SCK 39 #define SPI_MOSI 40 #define SPI_MISO 41 @@ -47,7 +38,7 @@ #define SCREEN_TRANSITION_FRAMERATE 5 #define HAS_TOUCHSCREEN 1 -#define USE_XPT2046 +#define USE_XPT2046 1 #define TOUCH_CS 38 #define HAS_GPS \ From d0e81b915155fff05f86825e526ed0d7e57715b7 Mon Sep 17 00:00:00 2001 From: Nicholas Baddorf <42445164+nbaddorf@users.noreply.github.com> Date: Tue, 23 Apr 2024 08:09:26 -0400 Subject: [PATCH 0310/1377] Fixed node and channel selection for t-deck (#3695) This enables the node and channel selection to be accessed by pressing the tab shortcut and then swiping between nodes or pressing tab again to change channels. (To access the tab function look at my other pull request https://github.com/meshtastic/firmware/pull/3668) Co-authored-by: Ben Meadors --- src/modules/CannedMessageModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index cbd6fee72..c80ccc5e7 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -161,10 +161,10 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (!event->kbchar) { if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { this->payload = 0xb4; - this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; + // this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { this->payload = 0xb7; - this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; + // this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } } else { // pass the pressed key From 7acaec8ef5148749df167acc9e4ea1d07862a50f Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 23 Apr 2024 20:11:22 +0800 Subject: [PATCH 0311/1377] Add power limit for TW region (#3701) The TW region had now power limit set, so defaulted to 16dBm. The relevant regulation is section 5.8.1 of the Low-power Radio-frequency Devices Technical Regulations, which notes the limits of 0.5W (27dBM) indoor or coastal, 1.0W (30dBM) outdoor. This patch updates the power limit to 27dbM, using the the lower limit specified in the regulations to be conservative. Regulation references: https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF (latest English version) https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283 (latest Chinese version, February 2024) --- src/mesh/RadioInterface.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 4fa0bef7a..f5eb35cbe 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -76,9 +76,12 @@ const RegionInfo regions[] = { RDEF(KR, 920.0f, 923.0f, 100, 0, 0, true, false, false), /* - ??? + Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor. + 5.8.1 in the Low-power Radio-frequency Devices Technical Regulations + https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF + https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283 */ - RDEF(TW, 920.0f, 925.0f, 100, 0, 0, true, false, false), + RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf @@ -586,4 +589,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} \ No newline at end of file +} From 4599534616166a412db6c6a5f46f35d856356a59 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 24 Apr 2024 05:00:48 +1200 Subject: [PATCH 0312/1377] Terminate an async-full-refresh when caught by determineMode() instead of onNotify() (#3706) --- src/graphics/EInkDynamicDisplay.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index b396446fa..5b97b8d48 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -534,6 +534,10 @@ void EInkDynamicDisplay::checkBusyAsyncRefresh() return; } + + // Async refresh appears to have stopped, but wasn't caught by onNotify() + else + pollAsyncRefresh(); // Check (and terminate) the async refresh manually } // Hold control while an async refresh runs From 27f0e42d2fe2bcf33fbd1ab5b686f495d07ab423 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:32:33 -0500 Subject: [PATCH 0313/1377] [create-pull-request] automated change (#3708) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index eade2c6be..86640f20d 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 +Subproject commit 86640f20db7b9b5be42949d18e8d96ad10d47a68 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 67b2edd15..68d9b4707 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -148,6 +148,9 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TD_LORAC = 60, /* CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3 */ meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61, + /* TWC_MESH_V4 + Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS */ + meshtastic_HardwareModel_TWC_MESH_V4 = 62, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 9c9d126f6b2aac4dda2f82193f26bc7959ade2e2 Mon Sep 17 00:00:00 2001 From: SCWhite Date: Wed, 24 Apr 2024 03:09:28 +0800 Subject: [PATCH 0314/1377] [BOARD] Add new variant: TWC_Mesh (#3705) * add new variant: TWC_mesh_v4 * fix trunk format * fix format under wsl * change board to TWC_mesh_v4 * change platformio & variant.h properly --------- Co-authored-by: Ben Meadors --- src/platform/nrf52/architecture.h | 4 +- variants/TWC_mesh_v4/platformio.ini | 10 +++ variants/TWC_mesh_v4/variant.cpp | 38 ++++++++ variants/TWC_mesh_v4/variant.h | 133 ++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 variants/TWC_mesh_v4/platformio.ini create mode 100644 variants/TWC_mesh_v4/variant.cpp create mode 100644 variants/TWC_mesh_v4/variant.h diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 35cd4fd84..3be3e7e55 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -52,6 +52,8 @@ #define HW_VENDOR meshtastic_HardwareModel_CANARYONE #elif defined(NORDIC_PCA10059) #define HW_VENDOR meshtastic_HardwareModel_NRF52840_PCA10059 +#elif defined(TWC_MESH_V4) +#define HW_VENDOR meshtastic_HardwareModel_TWC_MESH_V4 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else @@ -110,4 +112,4 @@ #if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA) // No serial ports on this board - ONLY use segger in memory console #define USE_SEGGER -#endif +#endif \ No newline at end of file diff --git a/variants/TWC_mesh_v4/platformio.ini b/variants/TWC_mesh_v4/platformio.ini new file mode 100644 index 000000000..9cf25c685 --- /dev/null +++ b/variants/TWC_mesh_v4/platformio.ini @@ -0,0 +1,10 @@ +[env:TWC_mesh_v4] +extends = nrf52840_base +board = TWC_mesh_v4 +board_level = extra +build_flags = ${nrf52840_base.build_flags} -I variants/TWC_mesh_v4 -D TWC_mesh_v4 -L".pio\libdeps\TWC_mesh_v4\BSEC2 Software Library\src\cortex-m4\fpv4-sp-d16-hard" +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/TWC_mesh_v4> +lib_deps = + ${nrf52840_base.lib_deps} + zinggjm/GxEPD2@^1.4.9 +debug_tool = jlink \ No newline at end of file diff --git a/variants/TWC_mesh_v4/variant.cpp b/variants/TWC_mesh_v4/variant.cpp new file mode 100644 index 000000000..b3712346d --- /dev/null +++ b/variants/TWC_mesh_v4/variant.cpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); +} \ No newline at end of file diff --git a/variants/TWC_mesh_v4/variant.h b/variants/TWC_mesh_v4/variant.h new file mode 100644 index 000000000..6a6f541e6 --- /dev/null +++ b/variants/TWC_mesh_v4/variant.h @@ -0,0 +1,133 @@ +#ifndef _VARIANT_TWC_MESH_V4_ +#define _VARIANT_TWC_MESH_V4_ + +#define PCA10059 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (32 + 10) // Blue LED P1.10 +#define PIN_LED2 (32 + 15) // Built in Green P1.15 + +// RGB NeoPixel LED2 +// #define PIN_LED1 (0 + 8) Red +// #define PIN_LED1 (32 + 9) Green +// #define PIN_LED1 (0 + 12) Blue + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 0 // State when LED is litted + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 2) // BTN_DN P1.02 Built in button + +/* + * Analog pins + */ +#define PIN_A0 (0 + 29) // using VDIV (A6 / P0.29) + +static const uint8_t A0 = PIN_A0; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (-1) // AREF Not yet used + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 24) +#define PIN_SERIAL1_TX (0 + 25) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 15) // MISO P0.15 +#define PIN_SPI_MOSI (0 + 13) // MOSI P0.13 +#define PIN_SPI_SCK (0 + 14) // SCK P0.14 + +static const uint8_t SS = (0 + 6); // LORA_CS P0.6 +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +////#define USE_EINK +#define USE_SSD1306 + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (0 + 12) // SDA P0.12 +#define PIN_WIRE_SCL (0 + 11) // SCL P0.11 + +// NiceRF 868 LoRa module +#define USE_SX1262 +#define USE_LLCC68 + +#define SX126X_CS (0 + 6) // LORA_CS P0.06 +#define SX126X_DIO1 (0 + 7) // DIO1 P0.07 +#define SX126X_BUSY (0 + 26) // LORA_BUSY P0.26 +#define SX126X_RESET (0 + 27) // LORA_RESET P0.27 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define PIN_GPS_EN (-1) +#define PIN_GPS_PPS (-1) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Battery +// The battery sense is hooked to pin A6 (0.29) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (2.0F) + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file From 57d296e0db8757a4318f225094fdd21ef48f0198 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 23 Apr 2024 14:18:51 -0500 Subject: [PATCH 0315/1377] Add better support for the Adafruit PiTFT 2.8 for Native (#3704) * Add better support for the Adafruit PiTFT 2.8 for Native * native: Make touch i2c address configurable * Bump portduino to pick up I2C features --------- Co-authored-by: Ben Meadors --- arch/portduino/portduino.ini | 2 +- bin/config-dist.yaml | 12 ++++++++---- src/graphics/TFTDisplay.cpp | 7 ++++++- src/platform/portduino/PortduinoGlue.cpp | 3 ++- src/platform/portduino/PortduinoGlue.h | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 53f06c9f3..452c34337 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#6fb39b6f94ece9c042141edb4afb91aca94dcaab +platform = https://github.com/meshtastic/platform-native.git#659e49346aa33008b150dfb206b1817ddabc7132 framework = arduino build_src_filter = diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index f729f1ac7..f02c2a2c2 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -96,17 +96,21 @@ Display: # Panel: ILI9341 # CS: 8 # DC: 25 -# Backlight: 2 -# Width: 320 -# Height: 240 +# Width: 240 +# Height: 320 +# Rotate: true Touchscreen: ### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching. -# Module: STMPE610 +# Module: STMPE610 # Option 1 for Adafruit PiTFT 2.8 # CS: 7 # IRQ: 24 +# Module: FT5x06 # Option 2 for Adafruit PiTFT 2.8 +# IRQ: 24 +# I2CAddr: 0x38 + # Module: XPT2046 # Waveshare 2.8inch # CS: 7 # IRQ: 17 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 12e549424..3c28a9e42 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -383,6 +383,8 @@ class LGFX : public lgfx::LGFX_Device _touch_instance = new lgfx::Touch_XPT2046; } else if (settingsMap[touchscreenModule] == stmpe610) { _touch_instance = new lgfx::Touch_STMPE610; + } else if (settingsMap[touchscreenModule] == ft5x06) { + _touch_instance = new lgfx::Touch_FT5x06; } auto touch_cfg = _touch_instance->config(); @@ -394,6 +396,9 @@ class LGFX : public lgfx::LGFX_Device touch_cfg.pin_int = settingsMap[touchscreenIRQ]; touch_cfg.bus_shared = true; touch_cfg.offset_rotation = 1; + if (settingsMap[touchscreenI2CAddr] != -1) { + touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr]; + } _touch_instance->config(touch_cfg); _panel_instance->setTouch(_touch_instance); @@ -711,4 +716,4 @@ bool TFTDisplay::connect() return true; } -#endif +#endif \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index a04c9c12c..a8c473887 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -221,6 +221,7 @@ void portduinoSetup() settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); settingsMap[touchscreenBusFrequency] = yamlConfig["Touchscreen"]["BusFrequency"].as(1000000); settingsMap[touchscreenRotate] = yamlConfig["Touchscreen"]["Rotate"].as(-1); + settingsMap[touchscreenI2CAddr] = yamlConfig["Touchscreen"]["I2CAddr"].as(-1); if (yamlConfig["Touchscreen"]["spidev"]) { settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); } @@ -332,4 +333,4 @@ int initGPIOPin(int pinNum, std::string gpioChipName) std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; return ERRNO_DISABLED; } -} +} \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index ed2954eef..4d2bcc262 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -21,6 +21,7 @@ enum configNames { touchscreenModule, touchscreenCS, touchscreenIRQ, + touchscreenI2CAddr, touchscreenBusFrequency, touchscreenRotate, touchscreenspidev, From ac16ccf40c2389b1cd1de8e366a540f44523f292 Mon Sep 17 00:00:00 2001 From: Gareth Coleman <30833824+garethhcoleman@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:41:05 +0100 Subject: [PATCH 0316/1377] fix for unPhone hangs during boot without sd card present (#3709) * work around sd card hang if not present * comment out the define for HAS_SDCARD --- variants/unphone/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index 180fdfe2c..7d5c30f79 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -48,7 +48,7 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN -#define HAS_SDCARD 1 +// #define HAS_SDCARD 1 // causes hang if defined #define SDCARD_CS 43 #define LED_PIN 13 // the red part of the RGB LED From 9baccc80d8b06466d76a3a48ac0e1325af91abb7 Mon Sep 17 00:00:00 2001 From: Oleksandr Podolchak Date: Wed, 24 Apr 2024 16:41:01 +0300 Subject: [PATCH 0317/1377] Add SX1268 modules support for linux-native (#3702) * Add portduino Ebyte E22 XXXM30S/XXXM33S (sx1268) module support * Add Ebyte E22 XXXM3XS module config * Update comment for sx1268 module * Address review comments --------- Co-authored-by: Ben Meadors --- bin/config-dist.yaml | 9 +++++++++ src/main.cpp | 15 +++++++++++++++ src/platform/portduino/PortduinoGlue.cpp | 3 +++ src/platform/portduino/PortduinoGlue.h | 1 + 4 files changed, 28 insertions(+) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index f02c2a2c2..05b4a7b0a 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -38,6 +38,15 @@ Lora: # Busy: 20 # Reset: 18 +# Module: sx1268 # SX1268-based modules, tested with Ebyte E22 400M33S +# CS: 21 +# IRQ: 16 +# Busy: 20 +# Reset: 18 +# TXen: 6 +# RXen: 12 +# DIO3_TCXO_VOLTAGE: true + # DIO3_TCXO_VOLTAGE: true # the Waveshare Core1262 and others are known to need this setting # TXen: x # TX and RX enable pins diff --git a/src/main.cpp b/src/main.cpp index deaa60f3a..4a663a8a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -770,6 +770,21 @@ void setup() LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n"); } } + } else if (settingsMap[use_sx1268]) { + if (!rIf) { + LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s\n", settingsStrings[spidev].c_str()); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); + rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], + settingsMap[busy]); + if (!rIf->init()) { + LOG_ERROR("Failed to find SX1268 radio\n"); + delete rIf; + rIf = NULL; + exit(EXIT_FAILURE); + } else { + LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n"); + } + } } #elif defined(HW_SPI1_DEVICE) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index a8c473887..d86ac6677 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -136,6 +136,7 @@ void portduinoSetup() settingsMap[use_sx1262] = false; settingsMap[use_rf95] = false; settingsMap[use_sx1280] = false; + settingsMap[use_sx1268] = false; if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1262") { settingsMap[use_sx1262] = true; @@ -143,6 +144,8 @@ void portduinoSetup() settingsMap[use_rf95] = true; } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1280") { settingsMap[use_sx1280] = true; + } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1268") { + settingsMap[use_sx1268] = true; } settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(false); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 4d2bcc262..94cdbf2f8 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -13,6 +13,7 @@ enum configNames { dio3_tcxo_voltage, use_rf95, use_sx1280, + use_sx1268, user, gpiochip, spidev, From e3610a2eb1e32350bb9b3adf1a7ced3394c0d024 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 24 Apr 2024 12:47:59 -0500 Subject: [PATCH 0318/1377] Move to lovyangfx develop for Native --- arch/portduino/portduino.ini | 4 +- src/graphics/TFTDisplay.cpp | 5 +- src/graphics/mesh_bus_spi.cpp | 188 ---------------------------------- src/graphics/mesh_bus_spi.h | 100 ------------------ 4 files changed, 4 insertions(+), 293 deletions(-) delete mode 100644 src/graphics/mesh_bus_spi.cpp delete mode 100644 src/graphics/mesh_bus_spi.h diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 452c34337..4857933ec 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -24,7 +24,7 @@ lib_deps = ${env.lib_deps} ${networking_base.lib_deps} rweather/Crypto@^0.4.0 - lovyan03/LovyanGFX@^1.1.12 + https://github.com/lovyan03/LovyanGFX.git#d35e60f269dfecbb18a8cb0fd07d594c2fb7e7a8 build_flags = ${arduino_base.build_flags} @@ -34,4 +34,4 @@ build_flags = -DPORTDUINO_LINUX_HARDWARE -lbluetooth -lgpiod - -lyaml-cpp + -lyaml-cpp \ No newline at end of file diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 3c28a9e42..b529bf0e4 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1,7 +1,6 @@ #include "configuration.h" #include "main.h" #if ARCH_PORTDUINO -#include "mesh_bus_spi.h" #include "platform/portduino/PortduinoGlue.h" #endif @@ -340,7 +339,7 @@ static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h class LGFX : public lgfx::LGFX_Device { lgfx::Panel_LCD *_panel_instance; - lgfx::Mesh_Bus_SPI _bus_instance; + lgfx::Bus_SPI _bus_instance; lgfx::ITouch *_touch_instance; @@ -357,7 +356,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ILI9341; auto buscfg = _bus_instance.config(); buscfg.spi_mode = 0; - _bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]); + _bus_instance.spi_device(DisplaySPI); buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) diff --git a/src/graphics/mesh_bus_spi.cpp b/src/graphics/mesh_bus_spi.cpp deleted file mode 100644 index a9536d490..000000000 --- a/src/graphics/mesh_bus_spi.cpp +++ /dev/null @@ -1,188 +0,0 @@ -// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens. -// Ideally this could eventually be an inherited class from BUS_SPI, -// but currently too many internal objects are set private. - -#include "configuration.h" -#if ARCH_PORTDUINO -#include "lgfx/v1/misc/pixelcopy.hpp" -#include "main.h" -#include "mesh_bus_spi.h" -#include -#include - -namespace lgfx -{ -inline namespace v1 -{ -//---------------------------------------------------------------------------- - -void Mesh_Bus_SPI::config(const config_t &config) -{ - _cfg = config; - - if (_cfg.pin_dc >= 0) { - pinMode(_cfg.pin_dc, pin_mode_t::output); - gpio_hi(_cfg.pin_dc); - } -} - -bool Mesh_Bus_SPI::init(void) -{ - dc_h(); - pinMode(_cfg.pin_dc, pin_mode_t::output); - if (SPIName != "") - PrivateSPI->begin(SPIName.c_str()); - else - PrivateSPI->begin(); - return true; -} - -void Mesh_Bus_SPI::release(void) -{ - PrivateSPI->end(); -} - -void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName) -{ - PrivateSPI = newSPI; - SPIName = newSPIName; -} -void Mesh_Bus_SPI::beginTransaction(void) -{ - dc_h(); - SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode); - PrivateSPI->beginTransaction(setting); -} - -void Mesh_Bus_SPI::endTransaction(void) -{ - PrivateSPI->endTransaction(); - dc_h(); -} - -void Mesh_Bus_SPI::beginRead(void) -{ - PrivateSPI->endTransaction(); - // SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false); - SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode); - PrivateSPI->beginTransaction(setting); -} - -void Mesh_Bus_SPI::endRead(void) -{ - PrivateSPI->endTransaction(); - beginTransaction(); -} - -void Mesh_Bus_SPI::wait(void) {} - -bool Mesh_Bus_SPI::busy(void) const -{ - return false; -} - -bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length) -{ - dc_l(); - PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3); - dc_h(); - return true; -} - -void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length) -{ - PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3); -} - -void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length) -{ - const uint8_t dst_bytes = bit_length >> 3; - uint32_t limit = (dst_bytes == 3) ? 12 : 16; - auto buf = _flip_buffer.getBuffer(512); - size_t fillpos = 0; - reinterpret_cast(buf)[0] = data; - fillpos += dst_bytes; - uint32_t len; - do { - len = ((length - 1) % limit) + 1; - if (limit <= 64) - limit <<= 1; - - while (fillpos < len * dst_bytes) { - memcpy(&buf[fillpos], buf, fillpos); - fillpos += fillpos; - } - - PrivateSPI->transfer(buf, len * dst_bytes); - } while (length -= len); -} - -void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length) -{ - const uint8_t dst_bytes = param->dst_bits >> 3; - uint32_t limit = (dst_bytes == 3) ? 12 : 16; - uint32_t len; - do { - len = ((length - 1) % limit) + 1; - if (limit <= 32) - limit <<= 1; - auto buf = _flip_buffer.getBuffer(len * dst_bytes); - param->fp_copy(buf, 0, len, param); - PrivateSPI->transfer(buf, len * dst_bytes); - } while (length -= len); -} - -void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) -{ - if (dc) - dc_h(); - else - dc_l(); - PrivateSPI->transfer(const_cast(data), length); - if (!dc) - dc_h(); -} - -uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length) -{ - uint32_t res = 0; - bit_length >>= 3; - if (!bit_length) - return res; - int idx = 0; - do { - res |= PrivateSPI->transfer(0) << idx; - idx += 8; - } while (--bit_length); - return res; -} - -bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma) -{ - do { - dst[0] = PrivateSPI->transfer(0); - ++dst; - } while (--length); - return true; -} - -void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length) -{ - uint32_t bytes = param->src_bits >> 3; - uint32_t dstindex = 0; - uint32_t len = 4; - uint8_t buf[24]; - param->src_data = buf; - do { - if (len > length) - len = length; - readBytes((uint8_t *)buf, len * bytes, true); - param->src_x = 0; - dstindex = param->fp_copy(dst, dstindex, dstindex + len, param); - length -= len; - } while (length); -} - -} // namespace v1 -} // namespace lgfx -#endif \ No newline at end of file diff --git a/src/graphics/mesh_bus_spi.h b/src/graphics/mesh_bus_spi.h deleted file mode 100644 index 903f7ad9d..000000000 --- a/src/graphics/mesh_bus_spi.h +++ /dev/null @@ -1,100 +0,0 @@ -#if ARCH_PORTDUINO -/*----------------------------------------------------------------------------/ - Lovyan GFX - Graphics library for embedded devices. - -Original Source: - https://github.com/lovyan03/LovyanGFX/ - -Licence: - [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) - -Author: - [lovyan03](https://twitter.com/lovyan03) - -Contributors: - [ciniml](https://github.com/ciniml) - [mongonta0716](https://github.com/mongonta0716) - [tobozo](https://github.com/tobozo) -/----------------------------------------------------------------------------*/ -#pragma once - -#include - -#include "lgfx/v1/Bus.hpp" -#include "lgfx/v1/platforms/common.hpp" - -namespace lgfx -{ -inline namespace v1 -{ -//---------------------------------------------------------------------------- - -class Mesh_Bus_SPI : public IBus -{ - public: - struct config_t { - uint32_t freq_write = 16000000; - uint32_t freq_read = 8000000; - // bool spi_3wire = true; - // bool use_lock = true; - int16_t pin_sclk = -1; - int16_t pin_miso = -1; - int16_t pin_mosi = -1; - int16_t pin_dc = -1; - uint8_t spi_mode = 0; - }; - - const config_t &config(void) const { return _cfg; } - - void config(const config_t &config); - - bus_type_t busType(void) const override { return bus_type_t::bus_spi; } - - bool init(void) override; - void release(void) override; - void spi_device(HardwareSPI *newSPI, std::string newSPIName); - - void beginTransaction(void) override; - void endTransaction(void) override; - void wait(void) override; - bool busy(void) const override; - - bool writeCommand(uint32_t data, uint_fast8_t bit_length) override; - void writeData(uint32_t data, uint_fast8_t bit_length) override; - void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override; - void writePixels(pixelcopy_t *param, uint32_t length) override; - void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override; - - void initDMA(void) {} - void flush(void) {} - void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); } - void execDMAQueue(void) {} - uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); } - - void beginRead(void) override; - void endRead(void) override; - uint32_t readData(uint_fast8_t bit_length) override; - bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override; - void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override; - - private: - HardwareSPI *PrivateSPI; - std::string SPIName; - __attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); } - __attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); } - - config_t _cfg; - FlipBuffer _flip_buffer; - bool _need_wait; - uint32_t _mask_reg_dc; - uint32_t _last_apb_freq = -1; - uint32_t _clkdiv_write; - uint32_t _clkdiv_read; - volatile uint32_t *_gpio_reg_dc_h; - volatile uint32_t *_gpio_reg_dc_l; -}; - -//---------------------------------------------------------------------------- -} // namespace v1 -} // namespace lgfx -#endif \ No newline at end of file From c14043f196f7d05060b17ff193a60c269c84a686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= Date: Thu, 25 Apr 2024 13:47:39 +0200 Subject: [PATCH 0319/1377] Split warning into two messages, so we know which one is the case. (#3710) --- src/mqtt/MQTT.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index da1c204b8..8a477d8ad 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -548,7 +548,10 @@ void MQTT::perhapsReportToMap() } else { if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { last_report_to_map = millis(); - LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); + if (map_position_precision == 0) + LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) + LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); return; } From 30fbcabf84b92e4fe84be8fb094c8e3c9be5252c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 25 Apr 2024 14:23:38 -0500 Subject: [PATCH 0320/1377] add conffiles to .deb packaging (#3722) --- .github/workflows/package_raspbian.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index dd4133dab..81ff6ee25 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -45,6 +45,7 @@ jobs: - name: build .debpkg run: | + mkdir -p .debpkg/debian mkdir -p .debpkg/usr/share/doc/meshtasticd/web mkdir -p .debpkg/usr/sbin mkdir -p .debpkg/etc/meshtasticd @@ -55,6 +56,7 @@ jobs: cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml chmod +x .debpkg/usr/sbin/meshtasticd cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service + echo "etc/meshtasticd/config.yaml" > .debpkg/debian/conffiles - uses: jiro4989/build-deb-action@v3 with: From 5806a266d38ff34a53ee5f7bf456b2f4fa344079 Mon Sep 17 00:00:00 2001 From: thebentern Date: Thu, 25 Apr 2024 20:19:54 +0000 Subject: [PATCH 0321/1377] [create-pull-request] automated change --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 485f55130..36607a956 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 7 +build = 8 From dfcabba0b2bda6b142108962053c7c36263dcaf3 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 27 Apr 2024 13:43:09 +1200 Subject: [PATCH 0322/1377] Prevent overflow when calculating timezones (#3730) --- src/gps/RTC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 85931900f..a2cdb5b30 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -223,7 +223,7 @@ int32_t getTZOffset() now = time(NULL); gmt = gmtime(&now); gmt->tm_isdst = -1; - return (int16_t)difftime(now, mktime(gmt)); + return (int32_t)difftime(now, mktime(gmt)); } /** From f8c3f43ea61d4b6987b31e6e170b3ef56049ac9e Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Sat, 27 Apr 2024 19:17:17 +0800 Subject: [PATCH 0323/1377] Fix xiao_ble variant build error due to undefined BATTERY_SENSE_RESOLUTION_BITS (#3732) Define BATTERY_SENSE_RESOLUTION_BITS based on [amoroz's snippet on the Meshtastic forum](https://meshtastic.discourse.group/t/new-1w-diy-variant-xiao-nrf52840-ebyte-e22-900m30s/7904/10). Fixes following build error: ``` src/Power.cpp: In member function 'virtual uint16_t AnalogBatteryLevel::getBattVoltage()': src/Power.cpp:224:79: error: 'BATTERY_SENSE_RESOLUTION_BITS' was not declared in this scope scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; ``` Signed-off-by: Andrew Yong --- variants/xiao_ble/variant.h | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_ble/variant.h b/variants/xiao_ble/variant.h index e2b8eb613..77af08278 100644 --- a/variants/xiao_ble/variant.h +++ b/variants/xiao_ble/variant.h @@ -181,6 +181,7 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define BAT_READ \ 14 // P0_14 = 14 Reads battery voltage from divider on signal board. (PIN_VBAT is reading voltage divider on XIAO and is // program pin 32 / or P0.31) +#define BATTERY_SENSE_RESOLUTION_BITS 10 #define CHARGE_LED 23 // P0_17 = 17 D23 YELLOW CHARGE LED #define HICHG 22 // P0_13 = 13 D22 Charge-select pin for Lipo for 100 mA instead of default 50mA charge From a06a01d25ea2f5cdc75e0e5266b542af5e789389 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Sat, 27 Apr 2024 11:35:44 +0000 Subject: [PATCH 0324/1377] fix #if HAS_TELEMETRY when set to 0 (#3733) --- src/Power.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index d13fd6891..2658b74a4 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -184,7 +184,7 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual uint16_t getBattVoltage() override { -#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) if (hasINA()) { LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address); return getINAVoltage(); @@ -360,7 +360,7 @@ class AnalogBatteryLevel : public HasBatteryLevel float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; -#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) uint16_t getINAVoltage() { if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) { From e66aec8223337e6580adf86028ea4f6a5c73e2cd Mon Sep 17 00:00:00 2001 From: jcyrio <50239349+jcyrio@users.noreply.github.com> Date: Sat, 27 Apr 2024 06:54:06 -0700 Subject: [PATCH 0325/1377] fix typo in comment (#3726) --- src/modules/CannedMessageModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index c80ccc5e7..24e5d28d1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -239,7 +239,7 @@ int32_t CannedMessageModule::runOnce() UIFrameEvent e = {false, true}; if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED)) { - // TODO: might have some feedback of sendig state + // TODO: might have some feedback of sending state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; e.frameChanged = true; this->currentMessageIndex = -1; From e683d8f5525b1b3efd445c296b2cce2b37f7deb5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 27 Apr 2024 08:55:04 -0500 Subject: [PATCH 0326/1377] Rebrand "send network ping" to more honest "try send position" with better output (#3737) --- src/ButtonThread.cpp | 7 +++++-- src/mesh/MeshService.cpp | 4 +++- src/mesh/MeshService.h | 5 +++-- src/modules/CannedMessageModule.cpp | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 206bb7239..e9717521a 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -136,9 +136,12 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_DOUBLE_PRESSED: { LOG_BUTTON("Double press!\n"); service.refreshLocalMeshNode(); - service.sendNetworkPing(NODENUM_BROADCAST, true); + auto sentPosition = service.trySendPosition(NODENUM_BROADCAST, true); if (screen) { - screen->print("Sent ad-hoc ping\n"); + if (sentPosition) + screen->print("Sent ad-hoc position\n"); + else + screen->print("Sent ad-hoc nodeinfo\n"); screen->forceDisplay(true); // Force a new UI frame, then force an EInk update } break; diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 2c1969e30..6b8d37975 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -267,7 +267,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh } } -void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) +bool MeshService::trySendPosition(NodeNum dest, bool wantReplies) { meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); @@ -278,6 +278,7 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) if (positionModule) { LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); positionModule->sendOurPosition(dest, wantReplies, node->channel); + return true; } } else { #endif @@ -286,6 +287,7 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel); } } + return false; } void MeshService::sendToPhone(meshtastic_MeshPacket *p) diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 8d1434030..d777b7a01 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -108,8 +108,9 @@ class MeshService void reloadOwner(bool shouldSave = true); /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least - /// sends our owner - void sendNetworkPing(NodeNum dest, bool wantReplies = false); + /// sends our nodeinfo + /// returns true if we sent a position + bool trySendPosition(NodeNum dest, bool wantReplies = false); /// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after /// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 24e5d28d1..ea7d5029a 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -452,7 +452,7 @@ int32_t CannedMessageModule::runOnce() break; case 0xaf: // fn+space send network ping like double press does service.refreshLocalMeshNode(); - service.sendNetworkPing(NODENUM_BROADCAST, true); + service.trySendPosition(NODENUM_BROADCAST, true); runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; default: From 38c4d35a7bd81e5b8ecbec479e4bf5df314e9735 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:08:25 -0400 Subject: [PATCH 0327/1377] Add Notification on device screen following feature toggle (#3627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update CannedMessageModule.h * Update CannedMessageModule.cpp * Update CannedMessageModule.cpp hopefully this fixes the errors on Trunk * Update CannedMessageModule.cpp Changed "Ping Broadcasted" with "Telemetry Update Sent" * tryfix: disable tempmessage again after 2 seconds * fix 2s showtime * Put spelling fix back * Fix build --------- Co-authored-by: Thomas Göttgens Co-authored-by: Ben Meadors --- src/modules/CannedMessageModule.cpp | 36 ++++++++++++++++++++++------- src/modules/CannedMessageModule.h | 6 ++++- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index ea7d5029a..b6267d985 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -233,14 +233,16 @@ int32_t CannedMessageModule::runOnce() { if (((!moduleConfig.canned_message.enabled) && !CANNED_MESSAGE_MODULE_ENABLE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) || (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) { + temporaryMessage = ""; return INT32_MAX; } // LOG_DEBUG("Check status\n"); UIFrameEvent e = {false, true}; if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || - (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED)) { + (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE)) { // TODO: might have some feedback of sending state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + temporaryMessage = ""; e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext @@ -434,7 +436,7 @@ int32_t CannedMessageModule::runOnce() } if (screen) screen->forceDisplay(); - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + showTemporaryMessage("GPS Toggled"); break; // mute (switch off/toggle) external notifications on fn+m @@ -442,18 +444,21 @@ int32_t CannedMessageModule::runOnce() if (moduleConfig.external_notification.enabled == true) { if (externalNotificationModule->getMute()) { externalNotificationModule->setMute(false); - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + showTemporaryMessage("Notifications \nEnabled"); } else { externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop externalNotificationModule->setMute(true); - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + showTemporaryMessage("Notifications \nDisabled"); } } break; case 0xaf: // fn+space send network ping like double press does service.refreshLocalMeshNode(); - service.trySendPosition(NODENUM_BROADCAST, true); - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + if (service.trySendPosition(NODENUM_BROADCAST, true)) { + showTemporaryMessage("Position \nUpdate Sent"); + } else { + showTemporaryMessage("Node Info \nUpdate Sent"); + } break; default: if (this->cursor == this->freetext.length()) { @@ -542,12 +547,27 @@ int CannedMessageModule::getPrevIndex() return this->currentMessageIndex - 1; } } +void CannedMessageModule::showTemporaryMessage(const String &message) +{ + temporaryMessage = message; + UIFrameEvent e = {false, true}; + e.frameChanged = true; + notifyObservers(&e); + runState = CANNED_MESSAGE_RUN_STATE_MESSAGE; + // run this loop again in 2 seconds, next iteration will clear the display + setIntervalFromNow(2000); +} void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { char buffer[50]; - if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { + if (temporaryMessage.length() != 0) { + LOG_DEBUG("Drawing temporary message: %s", temporaryMessage.c_str()); + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage); + } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); String displayString; @@ -766,4 +786,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor) return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 4802be078..faf1d80f3 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -10,6 +10,7 @@ enum cannedMessageModuleRunState { CANNED_MESSAGE_RUN_STATE_FREETEXT, CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE, CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED, + CANNED_MESSAGE_RUN_STATE_MESSAGE, CANNED_MESSAGE_RUN_STATE_ACTION_SELECT, CANNED_MESSAGE_RUN_STATE_ACTION_UP, CANNED_MESSAGE_RUN_STATE_ACTION_DOWN, @@ -51,6 +52,8 @@ class CannedMessageModule : public SinglePortModule, public Observable Date: Sat, 27 Apr 2024 11:12:52 -0500 Subject: [PATCH 0328/1377] Tradunkadunk --- src/modules/CannedMessageModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index b6267d985..a8b1b994b 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -455,9 +455,9 @@ int32_t CannedMessageModule::runOnce() case 0xaf: // fn+space send network ping like double press does service.refreshLocalMeshNode(); if (service.trySendPosition(NODENUM_BROADCAST, true)) { - showTemporaryMessage("Position \nUpdate Sent"); + showTemporaryMessage("Position \nUpdate Sent"); } else { - showTemporaryMessage("Node Info \nUpdate Sent"); + showTemporaryMessage("Node Info \nUpdate Sent"); } break; default: From ee4c4ae6c968502888fdb3adb256d1023b397a1b Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 28 Apr 2024 02:15:54 +0300 Subject: [PATCH 0329/1377] Allow setting hopLimit for MQTT json sendtext and sendposition (#3735) * Fix channel name extraction * Allow setting hopLimit for mqtt sendtext and sendposition --- src/mqtt/MQTT.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 8a477d8ad..508830203 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -54,9 +54,9 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) JSONObject json; json = json_value->AsObject(); - // parse the channel name from the topic string by looking for "json/" - const char *jsonSlash = "json/"; - char *ptr = strstr(topic, jsonSlash) + sizeof(jsonSlash) + 1; // set pointer to after "json/" + // parse the channel name from the topic string + // the topic has been checked above for having jsonTopic prefix, so just move past it + char *ptr = topic + jsonTopic.length(); ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character meshtastic_Channel sendChannel = channels.getByName(ptr); // We allow downlink JSON packets only on a channel named "mqtt" @@ -76,6 +76,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) p->channel = json["channel"]->AsNumber(); if (json.find("to") != json.end() && json["to"]->IsNumber()) p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); p->decoded.payload.size = jsonPayloadStr.length(); @@ -105,6 +107,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) p->channel = json["channel"]->AsNumber(); if (json.find("to") != json.end() && json["to"]->IsNumber()) p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Position_msg, &pos); // make the Data protobuf from position @@ -907,6 +911,7 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) { // if "sender" is provided, avoid processing packets we uplinked return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number (json.find("from") != json.end()) && json["from"]->IsNumber() && (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type From 93f77ea7d23a1612887cef09019e1c0ed5d8cd93 Mon Sep 17 00:00:00 2001 From: David Ellefsen <93522+titan098@users.noreply.github.com> Date: Sun, 28 Apr 2024 02:50:15 +0200 Subject: [PATCH 0330/1377] Update TinyGPSPlus version (#3727) Co-authored-by: Ben Meadors --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a1082a84a..e178fdb16 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ lib_deps = https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 - https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d + https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 nanopb/Nanopb@^0.4.7 erriez/ErriezCRC32@^1.0.1 From 18e69a0906e173cffa1049ca0954728e1b66b367 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Sun, 28 Apr 2024 19:49:26 +0800 Subject: [PATCH 0331/1377] Update Heltec HT-C62 variant based on HT-DEV-ESP board (#3731) * I2C is not defined in the reference schematic nor HT-DEV-ESP board, remove definition accordingly * There is no screen in the the reference schematic nor HT-DEV-ESP board, change definition accordingly * BUTTON_PIN has a 10 kOhm pullup resistor on HT-DEV-ESP, turning off redundant internal pullup should save some power * LED is connected to GPIO2 on HT-DEV-ESP and is not inverted, update definition accordingly * Remove redundant undef lines for LoRa pins Above changes were built and flashed to my [HT-DEV-ESP_V2 board purchased from Heltec's Taobao store](https://item.taobao.com/item.htm?id=521590063077). Signed-off-by: Andrew Yong --- variants/heltec_esp32c3/variant.h | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/variants/heltec_esp32c3/variant.h b/variants/heltec_esp32c3/variant.h index 6641f9d21..360d9bf1f 100644 --- a/variants/heltec_esp32c3/variant.h +++ b/variants/heltec_esp32c3/variant.h @@ -1,24 +1,16 @@ -#define I2C_SDA 1 -#define I2C_SCL 0 - #define BUTTON_PIN 9 -#define BUTTON_NEED_PULLUP -// LED flashes brighter +// LED pin on HT-DEV-ESP_V2 and HT-DEV-ESP_V3 // https://resource.heltec.cn/download/HT-CT62/HT-CT62_Reference_Design.pdf -#define LED_PIN 18 // LED -#define LED_INVERTED 1 +// https://resource.heltec.cn/download/HT-DEV-ESP/HT-DEV-ESP_V3_Sch.pdf +#define LED_PIN 2 // LED +#define LED_INVERTED 0 -#define HAS_SCREEN 1 +#define HAS_SCREEN 0 #define HAS_GPS 0 #undef GPS_RX_PIN #undef GPS_TX_PIN -#undef LORA_SCK -#undef LORA_MISO -#undef LORA_MOSI -#undef LORA_CS - #define USE_SX1262 #define LORA_SCK 10 #define LORA_MISO 6 From 21311bbeda61ec7ca9f052fdad37757727ef3707 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Tue, 30 Apr 2024 01:54:57 +1200 Subject: [PATCH 0332/1377] T-Echo touch button no longer requires "wake on tap or motion" (#3745) --- src/ButtonThread.cpp | 14 ++++++-------- src/mesh/NodeDB.cpp | 3 --- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index e9717521a..4566de924 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -196,15 +196,13 @@ int32_t ButtonThread::runOnce() #ifdef BUTTON_PIN_TOUCH case BUTTON_EVENT_TOUCH_LONG_PRESSED: { LOG_BUTTON("Touch press!\n"); - if (config.display.wake_on_tap_or_motion) { - if (screen) { - // Wake if asleep - if (powerFSM.getState() == &stateDARK) - powerFSM.trigger(EVENT_PRESS); + if (screen) { + // Wake if asleep + if (powerFSM.getState() == &stateDARK) + powerFSM.trigger(EVENT_PRESS); - // Update display (legacy behaviour) - screen->forceDisplay(); - } + // Update display (legacy behaviour) + screen->forceDisplay(); } break; } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 39422b454..249db627e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -352,9 +352,6 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.active = true; -#endif -#ifdef TTGO_T_ECHO - config.display.wake_on_tap_or_motion = true; // Enable touch button for screen-on / refresh #endif moduleConfig.has_canned_message = true; From e51ee91c39903bc13e14f06fe4743d755f834580 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Wed, 1 May 2024 03:07:15 +0200 Subject: [PATCH 0333/1377] Optimization: stop relaying when reply is received (#3753) --- src/mesh/FloodingRouter.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 4cfe982d8..dd547a6f1 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -35,11 +35,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) { - bool isAck = - ((c && c->error_reason == meshtastic_Routing_Error_NONE)); // consider only ROUTING_APP message without error as ACK - if (isAck && p->to != getNodeNum()) { - // do not flood direct message that is ACKed - LOG_DEBUG("Receiving an ACK not for me, but don't need to rebroadcast this direct message anymore.\n"); + bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0); + if (isAckorReply && p->to != getNodeNum() && p->to != NODENUM_BROADCAST) { + // do not flood direct message that is ACKed or replied to + LOG_DEBUG("Receiving an ACK or reply not for me, but don't need to rebroadcast this direct message anymore.\n"); Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM } if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) { From 3619ac87b8a78205d16de856f711abb49d83fa96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:14:38 -0500 Subject: [PATCH 0334/1377] [create-pull-request] automated change (#3754) Co-authored-by: thebentern --- protobufs | 2 +- src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/channel.pb.h | 17 +++++++++++------ src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/protobufs b/protobufs index 86640f20d..e21899aa6 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 86640f20db7b9b5be42949d18e8d96ad10d47a68 +Subproject commit e21899aa6b2b49863cfa2758e5e3b6faacf04bba diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index 54629f522..ba9f90873 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 658 +#define meshtastic_ChannelSet_size 674 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index 185a47a98..d9c7d4ffa 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -34,6 +34,9 @@ typedef enum _meshtastic_Channel_Role { typedef struct _meshtastic_ModuleSettings { /* Bits of precision for the location sent in position packets. */ uint32_t position_precision; + /* Controls whether or not the phone / clients should mute the current channel + Useful for noisy public channels you don't necessarily want to disable */ + bool is_client_muted; } meshtastic_ModuleSettings; typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t; @@ -126,14 +129,15 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default} -#define meshtastic_ModuleSettings_init_default {0} +#define meshtastic_ModuleSettings_init_default {0, 0} #define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN} #define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero} -#define meshtastic_ModuleSettings_init_zero {0} +#define meshtastic_ModuleSettings_init_zero {0, 0} #define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_ModuleSettings_position_precision_tag 1 +#define meshtastic_ModuleSettings_is_client_muted_tag 2 #define meshtastic_ChannelSettings_channel_num_tag 1 #define meshtastic_ChannelSettings_psk_tag 2 #define meshtastic_ChannelSettings_name_tag 3 @@ -159,7 +163,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings #define meshtastic_ModuleSettings_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, position_precision, 1) +X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \ +X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2) #define meshtastic_ModuleSettings_CALLBACK NULL #define meshtastic_ModuleSettings_DEFAULT NULL @@ -182,9 +187,9 @@ extern const pb_msgdesc_t meshtastic_Channel_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size -#define meshtastic_ChannelSettings_size 70 -#define meshtastic_Channel_size 85 -#define meshtastic_ModuleSettings_size 6 +#define meshtastic_ChannelSettings_size 72 +#define meshtastic_Channel_size 87 +#define meshtastic_ModuleSettings_size 8 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 2506ec647..b8cc80633 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -306,7 +306,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size -#define meshtastic_ChannelFile_size 702 +#define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 #define meshtastic_OEMStore_size 3346 #define meshtastic_PositionLite_size 28 From 57da37cfbca4ef5b47c346e4f2de02d00f33a726 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 1 May 2024 08:05:26 -0500 Subject: [PATCH 0335/1377] Position module should enforce precision for phone originated position packets (#3752) --- src/modules/PositionModule.cpp | 17 +++++++++++++++++ src/modules/PositionModule.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 658b8b5a7..7c459dc35 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -87,6 +87,23 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes return false; // Let others look at this message also if they want } +void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p) +{ + // Phone position packets need to be truncated to the channel precision + if (nodeDB->getNodeNum() == getFrom(&mp) && (precision < 32 && precision > 0)) { + LOG_DEBUG("Truncating phone position to channel precision %i\n", precision); + p->latitude_i = p->latitude_i & (UINT32_MAX << (32 - precision)); + p->longitude_i = p->longitude_i & (UINT32_MAX << (32 - precision)); + + // We want the imprecise position to be the middle of the possible location, not + p->latitude_i += (1 << (31 - precision)); + p->longitude_i += (1 << (31 - precision)); + + mp.decoded.payload.size = + pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_Position_msg, p); + } +} + void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal) { struct timeval tv; diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 89ff50c64..1161159f7 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -42,6 +42,8 @@ class PositionModule : public ProtobufModule, private concu */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *p) override; + virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p) override; + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked * so that subclasses can (optionally) send a response back to the original sender. */ virtual meshtastic_MeshPacket *allocReply() override; From ec92f7a5a396657e6b475988d1df520ab9753cd5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 1 May 2024 08:27:43 -0500 Subject: [PATCH 0336/1377] Remove phone nodenum warning and empty else clause (#3756) --- src/mesh/MeshService.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 6b8d37975..66a2e6952 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -193,10 +193,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) } #endif if (p.from != 0) { // We don't let phones assign nodenums to their sent messages - LOG_WARN("phone tried to pick a nodenum, we don't allow that.\n"); p.from = 0; - } else { - // p.from = nodeDB->getNodeNum(); } if (p.id == 0) From 5095efc55fc17f1c67abc4b64e715c9c2b4acf58 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 1 May 2024 13:06:42 -0500 Subject: [PATCH 0337/1377] Pick up support for more than 64 GPIO lines under Portduino --- arch/portduino/portduino.ini | 2 +- src/platform/portduino/PortduinoGlue.cpp | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 4857933ec..ef99c7870 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#659e49346aa33008b150dfb206b1817ddabc7132 +platform = https://github.com/meshtastic/platform-native.git#784007630ca43b4811c6637606440588bb5acf39 framework = arduino build_src_filter = diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index d86ac6677..8572f4cf2 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -76,7 +76,21 @@ void portduinoCustomInit() void portduinoSetup() { printf("Setting up Meshtastic on Portduino...\n"); - gpioInit(); + int max_GPIO = 0; + int GPIO_lines[] = {cs, + irq, + busy, + reset, + txen, + rxen, + displayDC, + displayCS, + displayBacklight, + displayBacklightPWMChannel, + displayReset, + touchscreenCS, + touchscreenIRQ, + user}; std::string gpioChipName = "gpiochip"; settingsStrings[i2cdev] = ""; @@ -245,6 +259,13 @@ void portduinoSetup() exit(EXIT_FAILURE); } + for (int i : GPIO_lines) { + if (i > max_GPIO) + max_GPIO = i; + } + + gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. + // Need to bind all the configured GPIO pins so they're not simulated if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) { if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) { From 45c1b46bd0f03350106009bec03e4c3a6050895b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 1 May 2024 13:20:26 -0500 Subject: [PATCH 0338/1377] Move native to spi_host to indicate spidev for LovyanGFX --- arch/portduino/portduino.ini | 2 +- src/graphics/TFTDisplay.cpp | 4 ++- src/main.cpp | 8 ++--- src/platform/portduino/PortduinoGlue.cpp | 46 +++++++++++++----------- src/platform/portduino/PortduinoGlue.h | 4 +-- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index ef99c7870..162411972 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -24,7 +24,7 @@ lib_deps = ${env.lib_deps} ${networking_base.lib_deps} rweather/Crypto@^0.4.0 - https://github.com/lovyan03/LovyanGFX.git#d35e60f269dfecbb18a8cb0fd07d594c2fb7e7a8 + https://github.com/lovyan03/LovyanGFX.git#5a39989aa2c9492572255b22f033843ec8900233 build_flags = ${arduino_base.build_flags} diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b529bf0e4..fac9a5796 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -356,7 +356,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ILI9341; auto buscfg = _bus_instance.config(); buscfg.spi_mode = 0; - _bus_instance.spi_device(DisplaySPI); + buscfg.spi_host = settingsMap[displayspidev]; buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) @@ -397,6 +397,8 @@ class LGFX : public lgfx::LGFX_Device touch_cfg.offset_rotation = 1; if (settingsMap[touchscreenI2CAddr] != -1) { touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr]; + } else { + touch_cfg.spi_host = settingsMap[touchscreenspidev]; } _touch_instance->config(touch_cfg); diff --git a/src/main.cpp b/src/main.cpp index 4a663a8a0..063e9b355 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -729,7 +729,7 @@ void setup() if (settingsMap[use_sx1262]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -743,7 +743,7 @@ void setup() } else if (settingsMap[use_rf95]) { if (!rIf) { LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -758,7 +758,7 @@ void setup() } else if (settingsMap[use_sx1280]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -773,7 +773,7 @@ void setup() } else if (settingsMap[use_sx1268]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 8572f4cf2..6151da227 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -15,8 +15,6 @@ #include #include -HardwareSPI *DisplaySPI; -HardwareSPI *LoraSPI; std::map settingsMap; std::map settingsStrings; char *configPath = nullptr; @@ -173,6 +171,15 @@ void portduinoSetup() gpioChipName += std::to_string(settingsMap[gpiochip]); settingsStrings[spidev] = "/dev/" + yamlConfig["Lora"]["spidev"].as("spidev0.0"); + if (settingsStrings[spidev].length() == 14) { + int x = settingsStrings[spidev].at(11) - '0'; + int y = settingsStrings[spidev].at(13) - '0'; + if (x >= 0 && x < 10 && y >= 0 && y < 10) { + settingsMap[spidev] = x + y << 4; + settingsMap[displayspidev] = settingsMap[spidev]; + settingsMap[touchscreenspidev] = settingsMap[spidev]; + } + } } if (yamlConfig["GPIO"]) { settingsMap[user] = yamlConfig["GPIO"]["User"].as(RADIOLIB_NC); @@ -222,6 +229,14 @@ void portduinoSetup() settingsMap[displayBusFrequency] = yamlConfig["Display"]["BusFrequency"].as(40000000); if (yamlConfig["Display"]["spidev"]) { settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); + if (settingsStrings[displayspidev].length() == 14) { + int x = settingsStrings[displayspidev].at(11) - '0'; + int y = settingsStrings[displayspidev].at(13) - '0'; + if (x >= 0 && x < 10 && y >= 0 && y < 10) { + settingsMap[displayspidev] = x + y << 4; + settingsMap[touchscreenspidev] = settingsMap[displayspidev]; + } + } } } settingsMap[touchscreenModule] = no_touchscreen; @@ -241,6 +256,13 @@ void portduinoSetup() settingsMap[touchscreenI2CAddr] = yamlConfig["Touchscreen"]["I2CAddr"].as(-1); if (yamlConfig["Touchscreen"]["spidev"]) { settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); + if (settingsStrings[touchscreenspidev].length() == 14) { + int x = settingsStrings[touchscreenspidev].at(11) - '0'; + int y = settingsStrings[touchscreenspidev].at(13) - '0'; + if (x >= 0 && x < 10 && y >= 0 && y < 10) { + settingsMap[touchscreenspidev] = x + y << 4; + } + } } } if (yamlConfig["Input"]) { @@ -319,27 +341,9 @@ void portduinoSetup() if (settingsMap[touchscreenIRQ] > 0) initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); } - - // if we specify a touchscreen dev, that is SPI. - // else if we specify a screen dev, that is SPI - // else if we specify a LoRa dev, that is SPI. - if (settingsStrings[touchscreenspidev] != "") { - SPI.begin(settingsStrings[touchscreenspidev].c_str()); - DisplaySPI = new HardwareSPI; - DisplaySPI->begin(settingsStrings[displayspidev].c_str()); - LoraSPI = new HardwareSPI; - LoraSPI->begin(settingsStrings[spidev].c_str()); - } else if (settingsStrings[displayspidev] != "") { - SPI.begin(settingsStrings[displayspidev].c_str()); - DisplaySPI = &SPI; - LoraSPI = new HardwareSPI; - LoraSPI->begin(settingsStrings[spidev].c_str()); - } else { + if (settingsStrings[spidev] != "") { SPI.begin(settingsStrings[spidev].c_str()); - LoraSPI = &SPI; - DisplaySPI = &SPI; } - return; } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 94cdbf2f8..995793a21 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -56,6 +56,4 @@ enum { level_error, level_warn, level_info, level_debug }; extern std::map settingsMap; extern std::map settingsStrings; -int initGPIOPin(int pinNum, std::string gpioChipname); -extern HardwareSPI *DisplaySPI; -extern HardwareSPI *LoraSPI; \ No newline at end of file +int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file From 0f4ac945591545cefb6c645573b1ef0a880cc745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 2 May 2024 12:48:50 +0200 Subject: [PATCH 0339/1377] fix building new TWC_mesh_v4 board --- variants/TWC_mesh_v4/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/TWC_mesh_v4/platformio.ini b/variants/TWC_mesh_v4/platformio.ini index 9cf25c685..481682143 100644 --- a/variants/TWC_mesh_v4/platformio.ini +++ b/variants/TWC_mesh_v4/platformio.ini @@ -1,6 +1,6 @@ [env:TWC_mesh_v4] extends = nrf52840_base -board = TWC_mesh_v4 +board = nordic_pca10059 board_level = extra build_flags = ${nrf52840_base.build_flags} -I variants/TWC_mesh_v4 -D TWC_mesh_v4 -L".pio\libdeps\TWC_mesh_v4\BSEC2 Software Library\src\cortex-m4\fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/TWC_mesh_v4> From 5f929a80244e7f5d4fda0df4571ecb0edbe55950 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 2 May 2024 20:13:36 +0800 Subject: [PATCH 0340/1377] Publish fixed position updates and consider changes in only altitude as an updated point (#3758) * AdminModule: Publish fixed position updates Enabled GPS thread when fixed position is updated, to let the GPS thread run once and publish the new fixed position. Signed-off-by: Andrew Yong * GPS: Consider changes in only altitude as an updated point Signed-off-by: Andrew Yong --------- Signed-off-by: Andrew Yong --- src/gps/GPS.cpp | 2 +- src/modules/AdminModule.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 17e35a4b3..1c1aac7ad 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1467,7 +1467,7 @@ bool GPS::lookForLocation() #endif // GPS_EXTRAVERBOSE // Is this a new point or are we re-reading the previous one? - if (!reader.location.isUpdated()) + if (!reader.location.isUpdated() && !reader.altitude.isUpdated()) return false; // check if a complete GPS solution set is available for reading diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 54eb577f7..37e798b3c 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -23,6 +23,10 @@ #include "mqtt/MQTT.h" #endif +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif + AdminModule *adminModule; bool hasOpenEditTransaction; @@ -217,6 +221,10 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta nodeDB->setLocalPosition(r->set_fixed_position); config.position.fixed_position = true; saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); +#if !MESHTASTIC_EXCLUDE_GPS + if (gps != nullptr) + gps->enable(); +#endif } break; } From 0527fb10ce28fb054d74c9c0848717cdb2cc15e0 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 3 May 2024 00:14:44 +1200 Subject: [PATCH 0341/1377] Init. battery voltage from ADC reading, instead of fixed value (#3761) --- src/Power.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index 2658b74a4..770bf4b5a 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -223,7 +223,17 @@ class AnalogBatteryLevel : public HasBatteryLevel raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; #endif - last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF + + if (!initial_read_done) { + // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct + if (scaled > last_read_value) + last_read_value = scaled; + initial_read_done = true; + } else { + // Already initialized - filter this reading + last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF + } + // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) // (last_read_value)); } @@ -357,6 +367,8 @@ class AnalogBatteryLevel : public HasBatteryLevel const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; // Start value from minimum voltage for the filter to not start from 0 // that could trigger some events. + // This value is over-written by the first ADC reading, it the voltage seems reasonable. + bool initial_read_done = false; float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; From d1b6f1142909efb9e82c1983c92670bc08712bce Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Thu, 2 May 2024 14:27:25 +0800 Subject: [PATCH 0342/1377] Fix t-echo gps failure --- variants/t-echo/variant.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 6a5146dc0..2abeed16d 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -131,7 +131,7 @@ External serial flash WP25R1635FZUIL0 // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching #define SX1262_DIO3 \ (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main - // CPU? +// CPU? #define SX126X_BUSY (0 + 17) #define SX126X_RESET (0 + 25) // Not really an E22 but TTGO seems to be trying to clone that @@ -177,13 +177,13 @@ External serial flash WP25R1635FZUIL0 #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define PIN_GPS_TX (32 + 9) // This is for bits going TOWARDS the CPU -#define PIN_GPS_RX (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 9) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 8) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 From 71400103b3e9f981cc1ac7f3dff4c24b663a93ab Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Thu, 2 May 2024 18:39:18 +0000 Subject: [PATCH 0343/1377] set USB_CDC_ON_BOOT, udate arduinoespressif32 to 2.0.15 (#3764) --- boards/tbeam-s3-core.json | 1 + variants/tbeam-s3-core/platformio.ini | 2 ++ 2 files changed, 3 insertions(+) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 4c82a2789..8d2c3eed6 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -7,6 +7,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", + "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/tbeam-s3-core/platformio.ini index 99d315a69..926f15a39 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/tbeam-s3-core/platformio.ini @@ -3,6 +3,8 @@ extends = esp32s3_base board = tbeam-s3-core +platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.15 + lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@1.0.1 From 06e7d2b8459f86e55cae1ab5a2bb5e2a21d7b118 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 2 May 2024 13:39:28 -0500 Subject: [PATCH 0344/1377] Track actual GPIO values, not just the enum values (#3768) * Track actual GPIO values, not just the enum values * trunk --- src/platform/portduino/PortduinoGlue.cpp | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 6151da227..35cee2d2f 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -75,20 +75,20 @@ void portduinoSetup() { printf("Setting up Meshtastic on Portduino...\n"); int max_GPIO = 0; - int GPIO_lines[] = {cs, - irq, - busy, - reset, - txen, - rxen, - displayDC, - displayCS, - displayBacklight, - displayBacklightPWMChannel, - displayReset, - touchscreenCS, - touchscreenIRQ, - user}; + configNames GPIO_lines[] = {cs, + irq, + busy, + reset, + txen, + rxen, + displayDC, + displayCS, + displayBacklight, + displayBacklightPWMChannel, + displayReset, + touchscreenCS, + touchscreenIRQ, + user}; std::string gpioChipName = "gpiochip"; settingsStrings[i2cdev] = ""; @@ -281,9 +281,9 @@ void portduinoSetup() exit(EXIT_FAILURE); } - for (int i : GPIO_lines) { - if (i > max_GPIO) - max_GPIO = i; + for (configNames i : GPIO_lines) { + if (settingsMap[i] > max_GPIO) + max_GPIO = settingsMap[i]; } gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. From b69a1cada916bec7bf39cd970f9412e5de464cb7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 2 May 2024 13:54:50 -0500 Subject: [PATCH 0345/1377] Portduino bump to pick up minor fix (#3770) --- arch/portduino/portduino.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 162411972..63f5576b6 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#784007630ca43b4811c6637606440588bb5acf39 +platform = https://github.com/meshtastic/platform-native.git#9881bf3721d610cccacf5ae8e3a07839cce75d63 framework = arduino build_src_filter = @@ -34,4 +34,4 @@ build_flags = -DPORTDUINO_LINUX_HARDWARE -lbluetooth -lgpiod - -lyaml-cpp \ No newline at end of file + -lyaml-cpp From 9501f3bda9de00db7c2804270f0f6fd366ac4ebf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 16:17:41 -0500 Subject: [PATCH 0346/1377] [create-pull-request] automated change (#3771) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 36607a956..a7a7fb1bd 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 8 +build = 9 From 5dfa4b837fe87e08ea71b4c8ec0b1d6d65729015 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 3 May 2024 12:11:13 +1200 Subject: [PATCH 0347/1377] Ensure LED is off when disabling heartbeat (#3772) --- src/main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 063e9b355..c2c824968 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -693,6 +693,12 @@ void setup() // Now that the mesh service is created, create any modules setupModules(); +#ifdef LED_PIN + // Turn LED off after boot, if heartbeat by config + if (config.device.led_heartbeat_disabled) + digitalWrite(LED_PIN, LOW ^ LED_INVERTED); +#endif + // Do this after service.init (because that clears error_code) #ifdef HAS_PMU if (!pmu_found) From d490a332a7f2e6978a24a0033a5ebb4c29b5117a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 2 May 2024 19:11:35 -0500 Subject: [PATCH 0348/1377] Update version.properties --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index a7a7fb1bd..36607a956 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 9 +build = 8 From 09080d76ad29dc40f450a9ba944ef4ed5f7a4f10 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 20:46:22 -0500 Subject: [PATCH 0349/1377] [create-pull-request] automated change (#3773) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 36607a956..a7a7fb1bd 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 8 +build = 9 From be0e882be1dc59e13c07e94bb0986c14846983a6 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Sat, 27 Apr 2024 18:05:39 +1200 Subject: [PATCH 0350/1377] exclude sensors when MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR set --- src/AccelerometerThread.h | 6 +++++- src/modules/Telemetry/AirQualityTelemetry.cpp | 4 ++++ src/modules/Telemetry/AirQualityTelemetry.h | 4 ++++ src/modules/Telemetry/EnvironmentTelemetry.cpp | 6 +++++- src/modules/Telemetry/EnvironmentTelemetry.h | 4 ++++ src/modules/Telemetry/Sensor/BME280Sensor.cpp | 5 ++++- src/modules/Telemetry/Sensor/BME280Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 4 ++++ src/modules/Telemetry/Sensor/BME680Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/BMP085Sensor.cpp | 4 ++++ src/modules/Telemetry/Sensor/BMP085Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/BMP280Sensor.cpp | 4 ++++ src/modules/Telemetry/Sensor/BMP280Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/INA219Sensor.cpp | 6 +++++- src/modules/Telemetry/Sensor/INA219Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/INA260Sensor.cpp | 6 +++++- src/modules/Telemetry/Sensor/INA260Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/INA3221Sensor.cpp | 6 +++++- src/modules/Telemetry/Sensor/INA3221Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/LPS22HBSensor.cpp | 6 +++++- src/modules/Telemetry/Sensor/LPS22HBSensor.h | 6 +++++- src/modules/Telemetry/Sensor/MCP9808Sensor.cpp | 6 +++++- src/modules/Telemetry/Sensor/MCP9808Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 4 ++++ src/modules/Telemetry/Sensor/SHT31Sensor.h | 4 ++++ src/modules/Telemetry/Sensor/SHTC3Sensor.cpp | 6 +++++- src/modules/Telemetry/Sensor/SHTC3Sensor.h | 6 +++++- src/modules/Telemetry/Sensor/TelemetrySensor.cpp | 4 ++++ src/modules/Telemetry/Sensor/TelemetrySensor.h | 6 +++++- src/modules/Telemetry/Sensor/VoltageSensor.h | 6 +++++- .../Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c | 4 ++++ .../Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h | 4 ++++ 32 files changed, 148 insertions(+), 21 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index fa5acdaae..6c76c74ca 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "PowerFSM.h" #include "concurrency/OSThread.h" #include "configuration.h" @@ -172,4 +174,6 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LSM6DS3TRC lsm; }; -} // namespace concurrency \ No newline at end of file +} // namespace concurrency + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index a51a7cea9..d8490c897 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "AirQualityTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" @@ -130,3 +132,5 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) } return true; } + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index ab77d61e7..882fca3a6 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Adafruit_PM25AQI.h" @@ -35,3 +37,5 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; }; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 189ab7ed0..42570d2fa 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "EnvironmentTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" @@ -278,4 +280,6 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) } } return valid; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index d6cd2137f..3e3055456 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "NodeDB.h" @@ -42,3 +44,5 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu uint32_t lastSentToPhone = 0; uint32_t sensor_read_error_count = 0; }; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp index a30614123..5f9b4cfb4 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "BME280Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -35,4 +37,5 @@ bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme280.readPressure() / 100.0F; return true; -} \ No newline at end of file + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.h b/src/modules/Telemetry/Sensor/BME280Sensor.h index 2034c0a82..086cb0fbc 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.h +++ b/src/modules/Telemetry/Sensor/BME280Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +16,6 @@ class BME280Sensor : public TelemetrySensor BME280Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index e1222bba4..4cd893656 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "BME680Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "FSCommon.h" @@ -134,3 +136,5 @@ void BME680Sensor::checkStatus(String functionName) else if (bme680.sensor.status > BME68X_OK) LOG_WARN("%s BME68X code: %s\n", functionName.c_str(), String(bme680.sensor.status).c_str()); } + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 4b7f84cf0..8855a01d2 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -35,4 +37,6 @@ class BME680Sensor : public TelemetrySensor int32_t runTrigger(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp index b0991749b..4aac00d5b 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "BMP085Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -29,3 +31,5 @@ bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement) return true; } + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h index c4a9479b9..0f8b651ff 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.h +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +16,6 @@ class BMP085Sensor : public TelemetrySensor BMP085Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp index 408532388..5cd40b9c7 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "BMP280Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -35,3 +37,5 @@ bool BMP280Sensor::getMetrics(meshtastic_Telemetry *measurement) return true; } + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.h b/src/modules/Telemetry/Sensor/BMP280Sensor.h index 48581df8f..738f2fd65 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.h +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +16,6 @@ class BMP280Sensor : public TelemetrySensor BMP280Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp index ecb564368..88eff39d8 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp @@ -1,3 +1,5 @@ +#if HAS_TELEMETRY + #include "INA219Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -37,4 +39,6 @@ bool INA219Sensor::getMetrics(meshtastic_Telemetry *measurement) uint16_t INA219Sensor::getBusVoltageMv() { return lround(ina219.getBusVoltage_V() * 1000); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.h b/src/modules/Telemetry/Sensor/INA219Sensor.h index 76f4613db..66107081c 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.h +++ b/src/modules/Telemetry/Sensor/INA219Sensor.h @@ -1,3 +1,5 @@ +#if HAS_TELEMETRY + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include "VoltageSensor.h" @@ -16,4 +18,6 @@ class INA219Sensor : public TelemetrySensor, VoltageSensor virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual uint16_t getBusVoltageMv() override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.cpp b/src/modules/Telemetry/Sensor/INA260Sensor.cpp index 89b7580d2..094cb4eee 100644 --- a/src/modules/Telemetry/Sensor/INA260Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA260Sensor.cpp @@ -1,3 +1,5 @@ +#if HAS_TELEMETRY + #include "INA260Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -32,4 +34,6 @@ bool INA260Sensor::getMetrics(meshtastic_Telemetry *measurement) uint16_t INA260Sensor::getBusVoltageMv() { return lround(ina260.readBusVoltage()); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.h b/src/modules/Telemetry/Sensor/INA260Sensor.h index 28e8944bf..b13d87966 100644 --- a/src/modules/Telemetry/Sensor/INA260Sensor.h +++ b/src/modules/Telemetry/Sensor/INA260Sensor.h @@ -1,3 +1,5 @@ +#if HAS_TELEMETRY + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include "VoltageSensor.h" @@ -16,4 +18,6 @@ class INA260Sensor : public TelemetrySensor, VoltageSensor virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual uint16_t getBusVoltageMv() override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index 3269ba47a..a12266b0a 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -1,3 +1,5 @@ +#if HAS_TELEMETRY + #include "INA3221Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -41,4 +43,6 @@ bool INA3221Sensor::getMetrics(meshtastic_Telemetry *measurement) uint16_t INA3221Sensor::getBusVoltageMv() { return lround(ina3221.getVoltage(INA3221_CH1) * 1000); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h index 4c82fc34d..4d894d1b7 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.h +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h @@ -1,3 +1,5 @@ +#if HAS_TELEMETRY + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include "VoltageSensor.h" @@ -16,4 +18,6 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor int32_t runOnce() override; bool getMetrics(meshtastic_Telemetry *measurement) override; virtual uint16_t getBusVoltageMv() override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp index 6e30113cd..5329e5264 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "LPS22HBSensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -32,4 +34,6 @@ bool LPS22HBSensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = pressure.pressure; return true; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.h b/src/modules/Telemetry/Sensor/LPS22HBSensor.h index 5b86539b1..10fa93bfc 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.h +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -15,4 +17,6 @@ class LPS22HBSensor : public TelemetrySensor LPS22HBSensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp index c1d9bfa71..f2ecce8fa 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "MCP9808Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -26,4 +28,6 @@ bool MCP9808Sensor::getMetrics(meshtastic_Telemetry *measurement) LOG_DEBUG("MCP9808Sensor::getMetrics\n"); measurement->variant.environment_metrics.temperature = mcp9808.readTempC(); return true; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.h b/src/modules/Telemetry/Sensor/MCP9808Sensor.h index c1029f8a7..f7d3311f0 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.h +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +16,6 @@ class MCP9808Sensor : public TelemetrySensor MCP9808Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 35978d970..3c7030f89 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "SHT31Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -29,3 +31,5 @@ bool SHT31Sensor::getMetrics(meshtastic_Telemetry *measurement) return true; } + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index c6f8f1596..b2f74a8b2 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -15,3 +17,5 @@ class SHT31Sensor : public TelemetrySensor virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp index b0b5d37dc..e1a6c5015 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "SHTC3Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -30,4 +32,6 @@ bool SHTC3Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity; return true; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.h b/src/modules/Telemetry/Sensor/SHTC3Sensor.h index e5db417f5..dc437b38f 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.h +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +16,6 @@ class SHTC3Sensor : public TelemetrySensor SHTC3Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp index cd8fe2566..d8df499f1 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp @@ -1,4 +1,8 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "TelemetrySensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "NodeDB.h" #include "main.h" + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 7282e6dfa..d48ff2b9a 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "NodeDB.h" @@ -45,4 +47,6 @@ class TelemetrySensor virtual bool isRunning() { return status > 0; } virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/VoltageSensor.h b/src/modules/Telemetry/Sensor/VoltageSensor.h index f2f28fb06..e3f79b833 100644 --- a/src/modules/Telemetry/Sensor/VoltageSensor.h +++ b/src/modules/Telemetry/Sensor/VoltageSensor.h @@ -1,7 +1,11 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #pragma once class VoltageSensor { public: virtual uint16_t getBusVoltageMv() = 0; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c index 1f27e6c69..0b5328306 100644 --- a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c +++ b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c @@ -1,3 +1,5 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "bsec_iaq.h" const uint8_t bsec_config_iaq[1974] = { @@ -80,3 +82,5 @@ const uint8_t bsec_config_iaq[1974] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 5, 10, 5, 0, 2, 0, 10, 0, 30, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 64, 1, 100, 0, 100, 0, 100, 0, 200, 0, 200, 0, 200, 0, 64, 1, 64, 1, 64, 1, 10, 0, 0, 0, 0, 0, 21, 122, 0, 0}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h index cdd209ae5..d693f1e6a 100644 --- a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h +++ b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h @@ -1,3 +1,7 @@ +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include extern const uint8_t bsec_config_iaq[1974]; + +#endif \ No newline at end of file From eaa7e21bc772d89b3ee722e6613948e82844bb28 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Mon, 29 Apr 2024 13:57:54 +1200 Subject: [PATCH 0351/1377] exclude AccelerometerThread when MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR is set --- src/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index c2c824968..dbd470378 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -197,7 +197,9 @@ uint32_t timeLastPowered = 0; static Periodic *ledPeriodic; static OSThread *powerFSMthread; +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR static OSThread *accelerometerThread; +#endif static OSThread *ambientLightingThread; SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); @@ -604,7 +606,7 @@ void setup() screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 #endif -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !(MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR) if (acc_info.type != ScanI2C::DeviceType::NONE) { config.display.wake_on_tap_or_motion = true; moduleConfig.external_notification.enabled = true; From 668b716119adfa787e114a9dddb7b31ddbf910b9 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Mon, 29 Apr 2024 13:58:21 +1200 Subject: [PATCH 0352/1377] move QMC5883LCompass dependency to environmental_base --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index e178fdb16..a6db1c76e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -96,7 +96,6 @@ check_flags = framework = arduino lib_deps = ${env.lib_deps} - mprograms/QMC5883LCompass@^1.2.0 end2endzone/NonBlockingRTTTL@^1.3.0 https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#5cf62b36c6f30bc72a07bdb2c11fc9a22d1e31da @@ -132,4 +131,5 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file + adafruit/Adafruit LSM6DS@^4.7.2 + mprograms/QMC5883LCompass@^1.2.0 From 077ca5919a800d638cbd3c4d6adcc9327fde5a44 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Mon, 29 Apr 2024 22:10:49 +1200 Subject: [PATCH 0353/1377] MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR refinements --- src/AccelerometerThread.h | 3 ++- src/Power.cpp | 6 +++--- src/main.cpp | 2 +- src/modules/CannedMessageModule.cpp | 3 ++- src/modules/Modules.cpp | 2 +- src/modules/Telemetry/AirQualityTelemetry.cpp | 3 ++- src/modules/Telemetry/AirQualityTelemetry.h | 2 ++ src/modules/Telemetry/EnvironmentTelemetry.cpp | 3 ++- src/modules/Telemetry/EnvironmentTelemetry.h | 2 ++ src/modules/Telemetry/PowerTelemetry.cpp | 9 +++++++-- src/modules/Telemetry/PowerTelemetry.h | 7 +++++++ src/modules/Telemetry/Sensor/BME280Sensor.cpp | 5 +++-- src/modules/Telemetry/Sensor/BME280Sensor.h | 2 ++ src/modules/Telemetry/Sensor/BME680Sensor.cpp | 3 ++- src/modules/Telemetry/Sensor/BME680Sensor.h | 2 ++ src/modules/Telemetry/Sensor/BMP085Sensor.cpp | 3 ++- src/modules/Telemetry/Sensor/BMP085Sensor.h | 2 ++ src/modules/Telemetry/Sensor/BMP280Sensor.cpp | 3 ++- src/modules/Telemetry/Sensor/BMP280Sensor.h | 2 ++ src/modules/Telemetry/Sensor/INA219Sensor.cpp | 5 +++-- src/modules/Telemetry/Sensor/INA219Sensor.h | 4 +++- src/modules/Telemetry/Sensor/INA260Sensor.cpp | 5 +++-- src/modules/Telemetry/Sensor/INA260Sensor.h | 4 +++- src/modules/Telemetry/Sensor/INA3221Sensor.cpp | 5 +++-- src/modules/Telemetry/Sensor/INA3221Sensor.h | 4 +++- src/modules/Telemetry/Sensor/LPS22HBSensor.cpp | 3 ++- src/modules/Telemetry/Sensor/LPS22HBSensor.h | 2 ++ src/modules/Telemetry/Sensor/MCP9808Sensor.cpp | 3 ++- src/modules/Telemetry/Sensor/MCP9808Sensor.h | 2 ++ src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 3 ++- src/modules/Telemetry/Sensor/SHT31Sensor.h | 2 ++ src/modules/Telemetry/Sensor/SHTC3Sensor.cpp | 3 ++- src/modules/Telemetry/Sensor/SHTC3Sensor.h | 2 ++ src/modules/Telemetry/Sensor/TelemetrySensor.cpp | 2 ++ src/modules/Telemetry/Sensor/TelemetrySensor.h | 2 ++ src/modules/Telemetry/Sensor/VoltageSensor.h | 2 ++ src/power.h | 3 ++- 37 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 6c76c74ca..66e5624f1 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -1,8 +1,9 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "PowerFSM.h" #include "concurrency/OSThread.h" -#include "configuration.h" #include "main.h" #include "power.h" diff --git a/src/Power.cpp b/src/Power.cpp index 770bf4b5a..64e310b68 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -69,7 +69,7 @@ static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE; #endif #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) INA260Sensor ina260Sensor; INA219Sensor ina219Sensor; INA3221Sensor ina3221Sensor; @@ -184,7 +184,7 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual uint16_t getBattVoltage() override { -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (hasINA()) { LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address); return getINAVoltage(); @@ -372,7 +372,7 @@ class AnalogBatteryLevel : public HasBatteryLevel float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) uint16_t getINAVoltage() { if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) { diff --git a/src/main.cpp b/src/main.cpp index dbd470378..81a129cc2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -606,7 +606,7 @@ void setup() screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 #endif -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !(MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (acc_info.type != ScanI2C::DeviceType::NONE) { config.display.wake_on_tap_or_motion = true; moduleConfig.external_notification.enabled = true; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index a8b1b994b..65e2c3ee1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -431,6 +431,7 @@ int32_t CannedMessageModule::runOnce() runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; case 0x9e: // toggle GPS like triple press does +#if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) { gps->toggleGpsMode(); } @@ -438,7 +439,7 @@ int32_t CannedMessageModule::runOnce() screen->forceDisplay(); showTemporaryMessage("GPS Toggled"); break; - +#endif // mute (switch off/toggle) external notifications on fn+m case 0xac: if (moduleConfig.external_notification.enabled == true) { diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 5ac45577e..15b356b05 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -146,7 +146,7 @@ void setupModules() new AirQualityTelemetryModule(); } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new PowerTelemetryModule(); #endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index d8490c897..86d296c34 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "AirQualityTelemetry.h" @@ -8,7 +10,6 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" -#include "configuration.h" #include "main.h" int32_t AirQualityTelemetryModule::runOnce() diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 882fca3a6..eb0355001 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #pragma once diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 42570d2fa..8e2801a49 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "EnvironmentTelemetry.h" @@ -8,7 +10,6 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" -#include "configuration.h" #include "main.h" #include "power.h" #include "sleep.h" diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index 3e3055456..cdd9491d4 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #pragma once diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 713f6aacb..94d47d08a 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -1,3 +1,7 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "PowerTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" @@ -6,7 +10,6 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" -#include "configuration.h" #include "main.h" #include "power.h" #include "sleep.h" @@ -217,4 +220,6 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) } } return valid; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index fc5b98875..3d6b686f2 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -1,4 +1,9 @@ #pragma once + +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "NodeDB.h" #include "ProtobufModule.h" @@ -41,3 +46,5 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul uint32_t lastSentToPhone = 0; uint32_t sensor_read_error_count = 0; }; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp index 5f9b4cfb4..396ba1242 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "BME280Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include #include @@ -37,5 +38,5 @@ bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme280.readPressure() / 100.0F; return true; - +} #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.h b/src/modules/Telemetry/Sensor/BME280Sensor.h index 086cb0fbc..eb78f79f7 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.h +++ b/src/modules/Telemetry/Sensor/BME280Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 4cd893656..77c78e587 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -1,10 +1,11 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "BME680Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "FSCommon.h" #include "TelemetrySensor.h" -#include "configuration.h" BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 8855a01d2..351db50ab 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp index 4aac00d5b..be17ddcc2 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "BMP085Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include #include diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h index 0f8b651ff..4ba8c5af1 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.h +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp index 5cd40b9c7..fbaa9faaa 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "BMP280Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include #include diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.h b/src/modules/Telemetry/Sensor/BMP280Sensor.h index 738f2fd65..da85fdc1d 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.h +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp index 88eff39d8..52aaec964 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp @@ -1,9 +1,10 @@ -#if HAS_TELEMETRY +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "INA219Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include #ifndef INA219_MULTIPLIER diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.h b/src/modules/Telemetry/Sensor/INA219Sensor.h index 66107081c..9dded067b 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.h +++ b/src/modules/Telemetry/Sensor/INA219Sensor.h @@ -1,4 +1,6 @@ -#if HAS_TELEMETRY +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.cpp b/src/modules/Telemetry/Sensor/INA260Sensor.cpp index 094cb4eee..4c51c7f0d 100644 --- a/src/modules/Telemetry/Sensor/INA260Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA260Sensor.cpp @@ -1,9 +1,10 @@ -#if HAS_TELEMETRY +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "INA260Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include INA260Sensor::INA260Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA260, "INA260") {} diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.h b/src/modules/Telemetry/Sensor/INA260Sensor.h index b13d87966..f436b8f38 100644 --- a/src/modules/Telemetry/Sensor/INA260Sensor.h +++ b/src/modules/Telemetry/Sensor/INA260Sensor.h @@ -1,4 +1,6 @@ -#if HAS_TELEMETRY +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index a12266b0a..a3e8af153 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -1,9 +1,10 @@ -#if HAS_TELEMETRY +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "INA3221Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include INA3221Sensor::INA3221Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA3221, "INA3221"){}; diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h index 4d894d1b7..3b8e382ee 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.h +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h @@ -1,4 +1,6 @@ -#if HAS_TELEMETRY +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp index 5329e5264..0bd7f145e 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "LPS22HBSensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include #include diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.h b/src/modules/Telemetry/Sensor/LPS22HBSensor.h index 10fa93bfc..955f2a1e5 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.h +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp index f2ecce8fa..9bb8947a2 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "MCP9808Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include MCP9808Sensor::MCP9808Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MCP9808, "MCP9808") {} diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.h b/src/modules/Telemetry/Sensor/MCP9808Sensor.h index f7d3311f0..05bdabf3f 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.h +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 3c7030f89..787372b0c 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "SHT31Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include SHT31Sensor::SHT31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT31, "SHT31") {} diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index b2f74a8b2..560b22436 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp index e1a6c5015..39b9570fa 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp @@ -1,9 +1,10 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "SHTC3Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" #include SHTC3Sensor::SHTC3Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHTC3, "SHTC3") {} diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.h b/src/modules/Telemetry/Sensor/SHTC3Sensor.h index dc437b38f..7a760292f 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.h +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp index d8df499f1..1c58b37e7 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "TelemetrySensor.h" diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index d48ff2b9a..35cb7965d 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #pragma once diff --git a/src/modules/Telemetry/Sensor/VoltageSensor.h b/src/modules/Telemetry/Sensor/VoltageSensor.h index e3f79b833..767ffd246 100644 --- a/src/modules/Telemetry/Sensor/VoltageSensor.h +++ b/src/modules/Telemetry/Sensor/VoltageSensor.h @@ -1,3 +1,5 @@ +#include "configuration.h" + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #pragma once diff --git a/src/power.h b/src/power.h index b94ce8f98..1fa683f8e 100644 --- a/src/power.h +++ b/src/power.h @@ -1,4 +1,5 @@ #pragma once +#include "configuration.h" #include "PowerStatus.h" #include "concurrency/OSThread.h" #ifdef ARCH_ESP32 @@ -36,7 +37,7 @@ extern RTC_NOINIT_ATTR uint64_t RTC_reg_b; #include "soc/sens_reg.h" // needed for adc pin reset #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) #include "modules/Telemetry/Sensor/INA219Sensor.h" #include "modules/Telemetry/Sensor/INA260Sensor.h" #include "modules/Telemetry/Sensor/INA3221Sensor.h" From 6c1377aa39862e6cc1726c83090c540f1087ab92 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Fri, 3 May 2024 18:59:33 +1200 Subject: [PATCH 0354/1377] fix case statement --- src/modules/CannedMessageModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 65e2c3ee1..f7e39fc8a 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -438,8 +438,8 @@ int32_t CannedMessageModule::runOnce() if (screen) screen->forceDisplay(); showTemporaryMessage("GPS Toggled"); - break; #endif + break; // mute (switch off/toggle) external notifications on fn+m case 0xac: if (moduleConfig.external_notification.enabled == true) { From 827dcfca4a419f386609d02d97932f559bbc091a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 3 May 2024 14:26:57 +0200 Subject: [PATCH 0355/1377] trunk fmt --- src/modules/CannedMessageModule.cpp | 2 +- src/modules/Telemetry/AirQualityTelemetry.cpp | 2 +- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 +- src/modules/Telemetry/PowerTelemetry.cpp | 2 +- src/modules/Telemetry/Sensor/BME280Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/BMP085Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/BMP280Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/INA219Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/INA260Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/INA3221Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/LPS22HBSensor.cpp | 2 +- src/modules/Telemetry/Sensor/MCP9808Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/SHTC3Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/TelemetrySensor.cpp | 2 +- src/power.h | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index f7e39fc8a..8cfea154e 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -431,7 +431,7 @@ int32_t CannedMessageModule::runOnce() runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; case 0x9e: // toggle GPS like triple press does -#if !MESHTASTIC_EXCLUDE_GPS +#if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) { gps->toggleGpsMode(); } diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 86d296c34..e4f31ff9f 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AirQualityTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "AirQualityTelemetry.h" #include "Default.h" #include "MeshService.h" #include "NodeDB.h" diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8e2801a49..d77a45f18 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -2,9 +2,9 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "EnvironmentTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" +#include "EnvironmentTelemetry.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 94d47d08a..e61a4e629 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -2,12 +2,12 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "PowerTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "PowerTelemetry.h" #include "RTC.h" #include "Router.h" #include "main.h" diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp index 396ba1242..aea6f2c3d 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "BME280Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BME280Sensor.h" #include "TelemetrySensor.h" #include #include diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 77c78e587..f2c3804f4 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "BME680Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BME680Sensor.h" #include "FSCommon.h" #include "TelemetrySensor.h" diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp index be17ddcc2..0c4d0b5ca 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "BMP085Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BMP085Sensor.h" #include "TelemetrySensor.h" #include #include diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp index fbaa9faaa..8d0e4c180 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "BMP280Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BMP280Sensor.h" #include "TelemetrySensor.h" #include #include diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp index 52aaec964..040e59575 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp @@ -2,8 +2,8 @@ #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "INA219Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "INA219Sensor.h" #include "TelemetrySensor.h" #include diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.cpp b/src/modules/Telemetry/Sensor/INA260Sensor.cpp index 4c51c7f0d..f156a9aba 100644 --- a/src/modules/Telemetry/Sensor/INA260Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA260Sensor.cpp @@ -2,8 +2,8 @@ #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "INA260Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "INA260Sensor.h" #include "TelemetrySensor.h" #include diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index a3e8af153..ea2cb4ea8 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -2,8 +2,8 @@ #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "INA3221Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "INA3221Sensor.h" #include "TelemetrySensor.h" #include diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp index 0bd7f145e..c3c994cfa 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "LPS22HBSensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "LPS22HBSensor.h" #include "TelemetrySensor.h" #include #include diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp index 9bb8947a2..b01a19291 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "MCP9808Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "MCP9808Sensor.h" #include "TelemetrySensor.h" #include diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 787372b0c..aa2b5dcfc 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "SHT31Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SHT31Sensor.h" #include "TelemetrySensor.h" #include diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp index 39b9570fa..37685fed7 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "SHTC3Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SHTC3Sensor.h" #include "TelemetrySensor.h" #include diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp index 1c58b37e7..d6e7d1fac 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp @@ -2,9 +2,9 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "TelemetrySensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "NodeDB.h" +#include "TelemetrySensor.h" #include "main.h" #endif \ No newline at end of file diff --git a/src/power.h b/src/power.h index 1fa683f8e..8d14ed7f8 100644 --- a/src/power.h +++ b/src/power.h @@ -1,7 +1,7 @@ #pragma once -#include "configuration.h" #include "PowerStatus.h" #include "concurrency/OSThread.h" +#include "configuration.h" #ifdef ARCH_ESP32 #include #include From dc0593c5a74927e77b61b30f155b3d7fb7b9cc51 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Fri, 3 May 2024 10:24:18 +0800 Subject: [PATCH 0356/1377] Fix the infinite restart caused by unformatted t-echo fs file system --- src/FSCommon.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index e7760c575..6a394c021 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -212,8 +212,23 @@ void fsInit() LOG_ERROR("Filesystem mount Failed.\n"); // assert(0); This auto-formats the partition, so no need to fail here. } -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes()); +#elif defined(ARCH_NRF52) + /* + * nRF52840 has a certain chance of automatic formatting failure. + * Try to create a file after initializing the file system. If the creation fails, + * it means that the file system is not working properly. Please format it manually again. + * */ + Adafruit_LittleFS_Namespace::File file(FSCom); + const char *filename = "/meshtastic.txt"; + if (! file.open(filename, FILE_O_WRITE) ) { + LOG_DEBUG("Format ...."); + FSCom.format(); + FSCom.begin(); + } else { + file.close(); + } #else LOG_DEBUG("Filesystem files:\n"); #endif From 5f90f45ac44e74f403d2c3670586c0897c7ae76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 3 May 2024 12:37:19 +0200 Subject: [PATCH 0357/1377] trunk fmt --- src/FSCommon.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 6a394c021..d5ca72142 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -212,17 +212,17 @@ void fsInit() LOG_ERROR("Filesystem mount Failed.\n"); // assert(0); This auto-formats the partition, so no need to fail here. } -#if defined(ARCH_ESP32) +#if defined(ARCH_ESP32) LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes()); #elif defined(ARCH_NRF52) /* - * nRF52840 has a certain chance of automatic formatting failure. - * Try to create a file after initializing the file system. If the creation fails, - * it means that the file system is not working properly. Please format it manually again. - * */ + * nRF52840 has a certain chance of automatic formatting failure. + * Try to create a file after initializing the file system. If the creation fails, + * it means that the file system is not working properly. Please format it manually again. + * */ Adafruit_LittleFS_Namespace::File file(FSCom); const char *filename = "/meshtastic.txt"; - if (! file.open(filename, FILE_O_WRITE) ) { + if (!file.open(filename, FILE_O_WRITE)) { LOG_DEBUG("Format ...."); FSCom.format(); FSCom.begin(); From 13ad5245381f936bea47d8b8bc0ace142b585108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 3 May 2024 15:10:57 +0200 Subject: [PATCH 0358/1377] make clang-format happy again. Also fix assorted variable shrouding and logic bleeps --- src/gps/GPS.cpp | 6 +++--- src/mesh/MeshService.cpp | 4 +--- src/mesh/NodeDB.cpp | 2 +- src/mesh/ProtobufModule.h | 3 +-- src/modules/NeighborInfoModule.cpp | 3 +-- src/modules/PositionModule.cpp | 2 +- src/modules/Telemetry/Sensor/RCWL9620Sensor.h | 12 ++++++------ src/platform/portduino/PortduinoGlue.cpp | 12 ++++++------ src/platform/stm32wl/LittleFS_File.cpp | 4 ++-- 9 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 1c1aac7ad..deea076b2 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -62,10 +62,10 @@ void GPS::CASChecksum(uint8_t *message, size_t length) // Iterate over the payload as a series of uint32_t's and // accumulate the cksum - uint32_t *payload = (uint32_t *)(message + 6); + uint32_t const *payload = (uint32_t *)(message + 6); for (size_t i = 0; i < (length - 10) / 4; i++) { - uint32_t p = payload[i]; - cksum += p; + uint32_t pl = payload[i]; + cksum += pl; } // Place the checksum values in the message diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 66a2e6952..ddad211a6 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -192,9 +192,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) return; } #endif - if (p.from != 0) { // We don't let phones assign nodenums to their sent messages - p.from = 0; - } + p.from = 0; // We don't let phones assign nodenums to their sent messages if (p.id == 0) p.id = generatePacketId(); // If the phone didn't supply one, then pick one diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 249db627e..906356e7c 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -56,7 +56,7 @@ meshtastic_OEMStore oemStore; bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field) { if (ostream) { - std::vector *vec = (std::vector *)field->pData; + std::vector const *vec = (std::vector *)field->pData; for (auto item : *vec) { if (!pb_encode_tag_for_field(ostream, field)) return false; diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index a2e89e98a..0d3da9568 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -95,12 +95,11 @@ template class ProtobufModule : protected SinglePortModule */ virtual void alterReceived(meshtastic_MeshPacket &mp) override { - auto &p = mp.decoded; - T scratch; T *decoded = NULL; if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) { memset(&scratch, 0, sizeof(scratch)); + auto &p = mp.decoded; if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) { decoded = &scratch; } else { diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 8c8135deb..3925bea9a 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -116,9 +116,8 @@ Will be used for broadcast. */ int32_t NeighborInfoModule::runOnce() { - bool requestReplies = false; if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { - sendNeighborInfo(NODENUM_BROADCAST, requestReplies); + sendNeighborInfo(NODENUM_BROADCAST, false); } return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); } diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 7c459dc35..9986f860d 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -343,7 +343,7 @@ int32_t PositionModule::runOnce() // The minimum time (in seconds) that would pass before we are able to send a new position packet. auto smartPosition = getDistanceTraveledSinceLastSend(node->position); - uint32_t msSinceLastSend = now - lastGpsSend; + msSinceLastSend = now - lastGpsSend; if (smartPosition.hasTraveledOverThreshold && Throttle::execute( diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h index 4fb2aec2d..b78066f5c 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -5,15 +5,15 @@ class RCWL9620Sensor : public TelemetrySensor { private: - uint8_t _addr; - TwoWire *_wire; - uint8_t _scl; - uint8_t _sda; - uint8_t _speed; + uint8_t _addr = 0x57; + TwoWire *_wire = &Wire; + uint8_t _scl = -1; + uint8_t _sda = -1; + uint32_t _speed = 200000UL; protected: virtual void setup() override; - void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000L); + void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000UL); float getDistance(); public: diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 35cee2d2f..edb81261c 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -75,7 +75,7 @@ void portduinoSetup() { printf("Setting up Meshtastic on Portduino...\n"); int max_GPIO = 0; - configNames GPIO_lines[] = {cs, + const configNames GPIO_lines[] = {cs, irq, busy, reset, @@ -103,7 +103,7 @@ void portduinoSetup() std::cout << "Using " << configPath << " as config file" << std::endl; try { yamlConfig = YAML::LoadFile(configPath); - } catch (YAML::Exception e) { + } catch (YAML::Exception &e) { std::cout << "Could not open " << configPath << " because of error: " << e.what() << std::endl; exit(EXIT_FAILURE); } @@ -111,7 +111,7 @@ void portduinoSetup() std::cout << "Using local config.yaml as config file" << std::endl; try { yamlConfig = YAML::LoadFile("config.yaml"); - } catch (YAML::Exception e) { + } catch (YAML::Exception &e) { std::cout << "*** Exception " << e.what() << std::endl; exit(EXIT_FAILURE); } @@ -119,7 +119,7 @@ void portduinoSetup() std::cout << "Using /etc/meshtasticd/config.yaml as config file" << std::endl; try { yamlConfig = YAML::LoadFile("/etc/meshtasticd/config.yaml"); - } catch (YAML::Exception e) { + } catch (YAML::Exception &e) { std::cout << "*** Exception " << e.what() << std::endl; exit(EXIT_FAILURE); } @@ -276,7 +276,7 @@ void portduinoSetup() settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as(200); - } catch (YAML::Exception e) { + } catch (YAML::Exception &e) { std::cout << "*** Exception " << e.what() << std::endl; exit(EXIT_FAILURE); } @@ -347,7 +347,7 @@ void portduinoSetup() return; } -int initGPIOPin(int pinNum, std::string gpioChipName) +int initGPIOPin(int pinNum, const std::string& gpioChipName) { std::string gpio_name = "GPIO" + std::to_string(pinNum); try { diff --git a/src/platform/stm32wl/LittleFS_File.cpp b/src/platform/stm32wl/LittleFS_File.cpp index cffb924e1..548a3d300 100644 --- a/src/platform/stm32wl/LittleFS_File.cpp +++ b/src/platform/stm32wl/LittleFS_File.cpp @@ -186,9 +186,9 @@ int File::available(void) _fs->_lockFS(); if (!this->_is_dir) { - uint32_t size = lfs_file_size(_fs->_getFS(), _file); + uint32_t fsize = lfs_file_size(_fs->_getFS(), _file); uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); - ret = size - pos; + ret = fsize - pos; } _fs->_unlockFS(); From 85e0372d26c0471fcb41db1ec470d83e7a736220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 3 May 2024 15:58:16 +0200 Subject: [PATCH 0359/1377] darn you, trunk. foiled my cunning plan. --- src/platform/portduino/PortduinoGlue.cpp | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index edb81261c..0b8b7e739 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -76,19 +76,19 @@ void portduinoSetup() printf("Setting up Meshtastic on Portduino...\n"); int max_GPIO = 0; const configNames GPIO_lines[] = {cs, - irq, - busy, - reset, - txen, - rxen, - displayDC, - displayCS, - displayBacklight, - displayBacklightPWMChannel, - displayReset, - touchscreenCS, - touchscreenIRQ, - user}; + irq, + busy, + reset, + txen, + rxen, + displayDC, + displayCS, + displayBacklight, + displayBacklightPWMChannel, + displayReset, + touchscreenCS, + touchscreenIRQ, + user}; std::string gpioChipName = "gpiochip"; settingsStrings[i2cdev] = ""; @@ -347,7 +347,7 @@ void portduinoSetup() return; } -int initGPIOPin(int pinNum, const std::string& gpioChipName) +int initGPIOPin(int pinNum, const std::string &gpioChipName) { std::string gpio_name = "GPIO" + std::to_string(pinNum); try { From 61216e579e4b53dd5b693e00438301b596225ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 3 May 2024 19:25:37 +0200 Subject: [PATCH 0360/1377] there --- src/platform/portduino/PortduinoGlue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 0b8b7e739..7c5086ac2 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -347,7 +347,7 @@ void portduinoSetup() return; } -int initGPIOPin(int pinNum, const std::string &gpioChipName) +int initGPIOPin(int pinNum, const std::string gpioChipName) { std::string gpio_name = "GPIO" + std::to_string(pinNum); try { From 70712d859cdc242b1873fa9b9622cf1c1711fb06 Mon Sep 17 00:00:00 2001 From: tuxphone <67556506+tuxphone@users.noreply.github.com> Date: Fri, 3 May 2024 22:49:22 +0200 Subject: [PATCH 0361/1377] Enable compiling with gccnoneeabi 12.3.1 for nRF52 targets, additional small fixes (#3778) * Fix type of nodeNum Type of nodeNum is NodeNum, not uint * typo fixed typo "resumeAdverising()" * fix missing #include "time.h" Missing include breaks compilation with gccnoneeabi 12.3.1 for nrf52 targets on windows hosts. * change type uint to unsigned int uint is not a standard type. Using uint breaks compilation with gccnoneeabi 12.3.1 for nRF52 targets on windows hosts. * fix type of channel_num Type of channel_num should be uint32_t (as this is the type of hash() and numChannels). Using uint non-standard type uint breaks compilation with gccnoneeabi 12.3.1 for nRF52 targets on windows hosts. * Update nrf52.ini Default build type should be "release" as this is the default of platformio. * Update GPS.cpp uint to unsigned int --- arch/nrf52/nrf52.ini | 2 +- src/SerialConsole.cpp | 1 + src/gps/GPS.cpp | 4 +-- src/input/TouchScreenBase.h | 1 + src/mesh/NodeDB.cpp | 2 +- src/mesh/NodeDB.h | 2 +- src/mesh/RadioInterface.cpp | 2 +- src/mqtt/JSONValue.cpp | 4 +-- src/mqtt/JSONValue.h | 2 +- src/mqtt/MQTT.cpp | 52 +++++++++++++-------------- src/platform/nrf52/NRF52Bluetooth.cpp | 2 +- src/platform/nrf52/NRF52Bluetooth.h | 2 +- src/platform/nrf52/main-nrf52.cpp | 2 +- 13 files changed, 40 insertions(+), 38 deletions(-) diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 2505fe315..0669a31e8 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -3,7 +3,7 @@ platform = platformio/nordicnrf52@^10.4.0 extends = arduino_base -build_type = debug ; I'm debugging with ICE a lot now +build_type = release build_flags = ${arduino_base.build_flags} -DSERIAL_BUFFER_SIZE=1024 diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index e17c8f99e..88a336ecc 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -2,6 +2,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "configuration.h" +#include "time.h" #ifdef RP2040_SLOW_CLOCK #define Port Serial2 diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index deea076b2..eaae049b5 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -452,7 +452,7 @@ bool GPS::setup() // Set the NEMA output messages // Ask for only RMC and GGA uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA}; - for (uint i = 0; i < sizeof(fields); i++) { + for (unsigned int i = 0; i < sizeof(fields); i++) { // Construct a CAS-CFG-MSG packet uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00}; msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet); @@ -1584,7 +1584,7 @@ bool GPS::hasFlow() bool GPS::whileIdle() { - uint charsInBuf = 0; + unsigned int charsInBuf = 0; bool isValid = false; if (!isAwake) { clearBuffer(); diff --git a/src/input/TouchScreenBase.h b/src/input/TouchScreenBase.h index a68c23e99..0b2002551 100644 --- a/src/input/TouchScreenBase.h +++ b/src/input/TouchScreenBase.h @@ -3,6 +3,7 @@ #include "InputBroker.h" #include "concurrency/OSThread.h" #include "mesh/NodeDB.h" +#include "time.h" typedef struct _TouchEvent { const char *source; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 906356e7c..b693a8a2b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -449,7 +449,7 @@ void NodeDB::resetNodes() neighborInfoModule->resetNeighbors(); } -void NodeDB::removeNodeByNum(uint nodeNum) +void NodeDB::removeNodeByNum(NodeNum nodeNum) { int newPos = 0, removed = 0; for (int i = 0; i < numMeshNodes; i++) { diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 4946672ec..e9e36cc61 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -124,7 +124,7 @@ class NodeDB */ size_t getNumOnlineMeshNodes(bool localOnly = false); - void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum); + void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum); bool factoryReset(); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index f5eb35cbe..cc6ccca07 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -495,7 +495,7 @@ void RadioInterface::applyModemConfig() // If user has manually specified a channel num, then use that, otherwise generate one by hashing the name const char *channelName = channels.getName(channels.getPrimaryIndex()); // channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1) - uint channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; + uint32_t channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; // Check if we use the default frequency slot RadioInterface::uses_default_frequency_slot = diff --git a/src/mqtt/JSONValue.cpp b/src/mqtt/JSONValue.cpp index a229666a9..51e0c1a3b 100644 --- a/src/mqtt/JSONValue.cpp +++ b/src/mqtt/JSONValue.cpp @@ -368,9 +368,9 @@ JSONValue::JSONValue(int m_integer_value) * * @access public * - * @param uint m_integer_value The number to use as the value + * @param unsigned int m_integer_value The number to use as the value */ -JSONValue::JSONValue(uint m_integer_value) +JSONValue::JSONValue(unsigned int m_integer_value) { type = JSONType_Number; number_value = (double)m_integer_value; diff --git a/src/mqtt/JSONValue.h b/src/mqtt/JSONValue.h index 3a50a831a..0380d324b 100644 --- a/src/mqtt/JSONValue.h +++ b/src/mqtt/JSONValue.h @@ -45,7 +45,7 @@ class JSONValue JSONValue(bool m_bool_value); JSONValue(double m_number_value); JSONValue(int m_integer_value); - JSONValue(uint m_integer_value); + JSONValue(unsigned int m_integer_value); JSONValue(const JSONArray &m_array_value); JSONValue(const JSONObject &m_object_value); JSONValue(const JSONValue &m_source); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 508830203..95b2daa99 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -659,11 +659,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { decoded = &scratch; if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { - msgPayload["battery_level"] = new JSONValue((uint)decoded->variant.device_metrics.battery_level); + msgPayload["battery_level"] = new JSONValue((unsigned int)decoded->variant.device_metrics.battery_level); msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage); msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization); msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx); - msgPayload["uptime_seconds"] = new JSONValue((uint)decoded->variant.device_metrics.uptime_seconds); + msgPayload["uptime_seconds"] = new JSONValue((unsigned int)decoded->variant.device_metrics.uptime_seconds); } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature); msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity); @@ -710,10 +710,10 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { decoded = &scratch; if ((int)decoded->time) { - msgPayload["time"] = new JSONValue((uint)decoded->time); + msgPayload["time"] = new JSONValue((unsigned int)decoded->time); } if ((int)decoded->timestamp) { - msgPayload["timestamp"] = new JSONValue((uint)decoded->timestamp); + msgPayload["timestamp"] = new JSONValue((unsigned int)decoded->timestamp); } msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); @@ -721,13 +721,13 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["altitude"] = new JSONValue((int)decoded->altitude); } if ((int)decoded->ground_speed) { - msgPayload["ground_speed"] = new JSONValue((uint)decoded->ground_speed); + msgPayload["ground_speed"] = new JSONValue((unsigned int)decoded->ground_speed); } if (int(decoded->ground_track)) { - msgPayload["ground_track"] = new JSONValue((uint)decoded->ground_track); + msgPayload["ground_track"] = new JSONValue((unsigned int)decoded->ground_track); } if (int(decoded->sats_in_view)) { - msgPayload["sats_in_view"] = new JSONValue((uint)decoded->sats_in_view); + msgPayload["sats_in_view"] = new JSONValue((unsigned int)decoded->sats_in_view); } if ((int)decoded->PDOP) { msgPayload["PDOP"] = new JSONValue((int)decoded->PDOP); @@ -754,11 +754,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { decoded = &scratch; - msgPayload["id"] = new JSONValue((uint)decoded->id); + msgPayload["id"] = new JSONValue((unsigned int)decoded->id); msgPayload["name"] = new JSONValue(decoded->name); msgPayload["description"] = new JSONValue(decoded->description); - msgPayload["expire"] = new JSONValue((uint)decoded->expire); - msgPayload["locked_to"] = new JSONValue((uint)decoded->locked_to); + msgPayload["expire"] = new JSONValue((unsigned int)decoded->expire); + msgPayload["locked_to"] = new JSONValue((unsigned int)decoded->locked_to); msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); jsonObj["payload"] = new JSONValue(msgPayload); @@ -775,14 +775,14 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, &scratch)) { decoded = &scratch; - msgPayload["node_id"] = new JSONValue((uint)decoded->node_id); - msgPayload["node_broadcast_interval_secs"] = new JSONValue((uint)decoded->node_broadcast_interval_secs); - msgPayload["last_sent_by_id"] = new JSONValue((uint)decoded->last_sent_by_id); + msgPayload["node_id"] = new JSONValue((unsigned int)decoded->node_id); + msgPayload["node_broadcast_interval_secs"] = new JSONValue((unsigned int)decoded->node_broadcast_interval_secs); + msgPayload["last_sent_by_id"] = new JSONValue((unsigned int)decoded->last_sent_by_id); msgPayload["neighbors_count"] = new JSONValue(decoded->neighbors_count); JSONArray neighbors; for (uint8_t i = 0; i < decoded->neighbors_count; i++) { JSONObject neighborObj; - neighborObj["node_id"] = new JSONValue((uint)decoded->neighbors[i].node_id); + neighborObj["node_id"] = new JSONValue((unsigned int)decoded->neighbors[i].node_id); neighborObj["snr"] = new JSONValue((int)decoded->neighbors[i].snr); neighbors.push_back(new JSONValue(neighborObj)); } @@ -843,9 +843,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Paxcount_msg, &scratch)) { decoded = &scratch; - msgPayload["wifi_count"] = new JSONValue((uint)decoded->wifi); - msgPayload["ble_count"] = new JSONValue((uint)decoded->ble); - msgPayload["uptime"] = new JSONValue((uint)decoded->uptime); + msgPayload["wifi_count"] = new JSONValue((unsigned int)decoded->wifi); + msgPayload["ble_count"] = new JSONValue((unsigned int)decoded->ble); + msgPayload["uptime"] = new JSONValue((unsigned int)decoded->uptime); jsonObj["payload"] = new JSONValue(msgPayload); } else { LOG_ERROR("Error decoding protobuf for Paxcount message!\n"); @@ -862,12 +862,12 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) decoded = &scratch; if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { msgType = "gpios_changed"; - msgPayload["gpio_value"] = new JSONValue((uint)decoded->gpio_value); + msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value); jsonObj["payload"] = new JSONValue(msgPayload); } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { msgType = "gpios_read_reply"; - msgPayload["gpio_value"] = new JSONValue((uint)decoded->gpio_value); - msgPayload["gpio_mask"] = new JSONValue((uint)decoded->gpio_mask); + msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value); + msgPayload["gpio_mask"] = new JSONValue((unsigned int)decoded->gpio_mask); jsonObj["payload"] = new JSONValue(msgPayload); } } else { @@ -883,11 +883,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); } - jsonObj["id"] = new JSONValue((uint)mp->id); - jsonObj["timestamp"] = new JSONValue((uint)mp->rx_time); - jsonObj["to"] = new JSONValue((uint)mp->to); - jsonObj["from"] = new JSONValue((uint)mp->from); - jsonObj["channel"] = new JSONValue((uint)mp->channel); + jsonObj["id"] = new JSONValue((unsigned int)mp->id); + jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time); + jsonObj["to"] = new JSONValue((unsigned int)mp->to); + jsonObj["from"] = new JSONValue((unsigned int)mp->from); + jsonObj["channel"] = new JSONValue((unsigned int)mp->channel); jsonObj["type"] = new JSONValue(msgType.c_str()); jsonObj["sender"] = new JSONValue(owner.id); if (mp->rx_rssi != 0) @@ -895,7 +895,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (mp->rx_snr != 0) jsonObj["snr"] = new JSONValue((float)mp->rx_snr); if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) - jsonObj["hops_away"] = new JSONValue((uint)(mp->hop_start - mp->hop_limit)); + jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 759cbb404..39898ab25 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -287,7 +287,7 @@ void NRF52Bluetooth::setup() LOG_INFO("Advertising\n"); } -void NRF52Bluetooth::resumeAdverising() +void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index fd27bbf04..11e18c127 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -8,7 +8,7 @@ class NRF52Bluetooth : BluetoothApi public: void setup(); void shutdown(); - void resumeAdverising(); + void resumeAdvertising(); void clearBonds(); bool isConnected(); int getRssi(); diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index ecffb745d..9cc52a7de 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -80,7 +80,7 @@ void setBluetoothEnable(bool enable) // We delay brownout init until after BLE because BLE starts soft device initBrownout(); } else { - nrf52Bluetooth->resumeAdverising(); + nrf52Bluetooth->resumeAdvertising(); } } } else { From 6fb7d7f2d7980a298d6d2b129a50a022058a76d2 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 5 May 2024 13:49:50 +0200 Subject: [PATCH 0362/1377] Check if packet is not released before CC to phone --- src/mesh/MeshService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index ddad211a6..2cfb4843c 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -257,7 +257,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh LOG_DEBUG("Can't send status to phone"); } - if (ccToPhone) { + if (res == ERRNO_OK && ccToPhone) { // Check if p is not released in case it couldn't be sent sendToPhone(packetPool.allocCopy(*p)); } } From 0b239e618dbb6b0d34c0c0d8cb84bdffa27f9da9 Mon Sep 17 00:00:00 2001 From: caveman99 Date: Sun, 5 May 2024 15:32:28 +0000 Subject: [PATCH 0363/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index e21899aa6..24776635e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e21899aa6b2b49863cfa2758e5e3b6faacf04bba +Subproject commit 24776635eea48316137b206ae8b1ddf1e218c10f diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 68d9b4707..ffc18c30b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -63,6 +63,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_NANO_G2_ULTRA = 18, /* LoRAType device: https://loratype.org/ */ meshtastic_HardwareModel_LORA_TYPE = 19, + /* wiphone https://www.wiphone.io/ */ + meshtastic_HardwareModel_WIPHONE = 20, /* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */ meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ @@ -151,6 +153,9 @@ typedef enum _meshtastic_HardwareModel { /* TWC_MESH_V4 Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS */ meshtastic_HardwareModel_TWC_MESH_V4 = 62, + /* NRF52_PROMICRO_DIY + Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS */ + meshtastic_HardwareModel_NRF52_PROMICRO_DIY = 63, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 2c30923e3e70641b61fdc7795ebacd4583ca2371 Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Mon, 6 May 2024 11:25:18 +1200 Subject: [PATCH 0364/1377] add MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR exclusion to RCWL9620 --- src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp | 9 +++++++-- src/modules/Telemetry/Sensor/RCWL9620Sensor.h | 8 +++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index 03df57efd..4f5ddf622 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -1,7 +1,10 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "RCWL9620Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {} @@ -57,4 +60,6 @@ float RCWL9620Sensor::getDistance() } else { return Distance; } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h index b78066f5c..7f9486d25 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -1,3 +1,7 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -20,4 +24,6 @@ class RCWL9620Sensor : public TelemetrySensor RCWL9620Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file From b155a5b6dccf479c1b5edbec8b3221141208588e Mon Sep 17 00:00:00 2001 From: Oliver Seiler Date: Mon, 6 May 2024 12:15:49 +1200 Subject: [PATCH 0365/1377] rearrange includes --- src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index 4f5ddf622..49a509d38 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -2,8 +2,8 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "RCWL9620Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "RCWL9620Sensor.h" #include "TelemetrySensor.h" RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {} From 5e9d48d0d7ba27f43106f74b15be276941b6b747 Mon Sep 17 00:00:00 2001 From: caveman99 Date: Mon, 6 May 2024 11:37:03 +0000 Subject: [PATCH 0366/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 24776635e..1bfe0354d 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 24776635eea48316137b206ae8b1ddf1e218c10f +Subproject commit 1bfe0354d101a6a71ea1354ea158e59193671a0b diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index e670dd340..b6b08f2e7 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -45,7 +45,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */ meshtastic_TelemetrySensorType_BMP085 = 15, /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */ - meshtastic_TelemetrySensorType_RCWL9620 = 16 + meshtastic_TelemetrySensorType_RCWL9620 = 16, + /* Sensirion High accuracy temperature and humidity */ + meshtastic_TelemetrySensorType_SHT4X = 17 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -152,8 +154,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_RCWL9620 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_RCWL9620+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHT4X +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHT4X+1)) From 77a66e1dce961c01f11f8b6f665ee22c2ca2b204 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Mon, 6 May 2024 07:47:34 -0400 Subject: [PATCH 0367/1377] Fix for EnvironmentTelemetry Screen (#3785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update EnvironmentTelemetry.cpp * Update EnvironmentTelemetry.cpp Corrected lines I deleted by mistake * trunk fmt --------- Co-authored-by: Thomas Göttgens --- .../Telemetry/EnvironmentTelemetry.cpp | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 7ae706b3c..93184069d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -151,45 +151,53 @@ uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp) void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - display->drawString(x, y, "Environment"); + display->setFont(FONT_SMALL); + if (lastMeasurementPacket == nullptr) { - display->setFont(FONT_SMALL); - display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement"); + // If there's no valid packet, display "Environment" + display->drawString(x, y, "Environment"); + display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement"); return; } + // Decode the last measurement packet meshtastic_Telemetry lastMeasurement; - uint32_t agoSecs = GetTimeSinceMeshPacket(lastMeasurementPacket); const char *lastSender = getSenderShortName(*lastMeasurementPacket); auto &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { - display->setFont(FONT_SMALL); - display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error"); + display->drawString(x, y, "Measurement Error"); LOG_ERROR("Unable to decode last packet"); return; } - display->setFont(FONT_SMALL); + // Display "Env. From: ..." on its own + display->drawString(x, y, "Env. From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; if (moduleConfig.telemetry.environment_display_fahrenheit) { last_temp = String(CelsiusToFahrenheit(lastMeasurement.variant.environment_metrics.temperature), 0) + "°F"; } - display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); - display->drawString(x, y += fontHeight(FONT_SMALL) - 2, + + // Continue with the remaining details + display->drawString(x, y += fontHeight(FONT_SMALL), "Temp/Hum: " + last_temp + " / " + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%"); - if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) + + if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { display->drawString(x, y += fontHeight(FONT_SMALL), "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); - if (lastMeasurement.variant.environment_metrics.voltage != 0) + } + + if (lastMeasurement.variant.environment_metrics.voltage != 0) { display->drawString(x, y += fontHeight(FONT_SMALL), "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); - if (lastMeasurement.variant.environment_metrics.iaq != 0) + } + if (lastMeasurement.variant.environment_metrics.iaq != 0) { display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + } if (lastMeasurement.variant.environment_metrics.distance != 0) display->drawString(x, y += fontHeight(FONT_SMALL), "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); From 353c7e07d16a73a63fde4bfeb8cce8f8875291e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 6 May 2024 13:48:57 +0200 Subject: [PATCH 0368/1377] Wiphone (#3793) * add wiphone, still WIP * (very preliminary) wiphone support * undo config changes * revert extensions.json * eh? --- boards/wiphone.json | 34 ++++++++++++++++++ src/graphics/TFTDisplay.cpp | 6 ++++ src/platform/esp32/architecture.h | 2 ++ variants/wiphone/pins_arduino.h | 52 +++++++++++++++++++++++++++ variants/wiphone/platformio.ini | 13 +++++++ variants/wiphone/variant.h | 58 +++++++++++++++++++++++++++++++ 6 files changed, 165 insertions(+) create mode 100644 boards/wiphone.json create mode 100644 variants/wiphone/pins_arduino.h create mode 100644 variants/wiphone/platformio.ini create mode 100644 variants/wiphone/variant.h diff --git a/boards/wiphone.json b/boards/wiphone.json new file mode 100644 index 000000000..bb01f425f --- /dev/null +++ b/boards/wiphone.json @@ -0,0 +1,34 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32_out.ld", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_WIPHONE14", + "-DBOARD_HAS_PSRAM", + "-mfix-esp32-psram-cache-issue", + "-mfix-esp32-psram-cache-strategy=memw" + ], + "f_cpu": "240000000L", + "f_flash": "40000000L", + "flash_mode": "dio", + "mcu": "esp32", + "variant": "wiphone", + "board": "WiPhone" + }, + "connectivity": ["wifi", "bluetooth"], + "frameworks": ["arduino", "espidf"], + "name": "WIPhone Integrated 1.4", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 532480, + "maximum_size": 6553600, + "maximum_data_size": 4521984, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.wiphone.io/", + "vendor": "HackEDA" +} diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index fac9a5796..36397a826 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -8,6 +8,12 @@ #define TFT_BACKLIGHT_ON HIGH #endif +#ifdef GPIO_EXTENDER +#include +#include +extern SX1509 gpioExtender; +#endif + #ifndef TFT_MESH #define TFT_MESH COLOR565(0x67, 0xEA, 0x94) #endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 27088f86f..45d533a76 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -143,6 +143,8 @@ #define HW_VENDOR meshtastic_HardwareModel_STATION_G2 #elif defined(UNPHONE) #define HW_VENDOR meshtastic_HardwareModel_UNPHONE +#elif defined(WIPHONE) +#define HW_VENDOR meshtastic_HardwareModel_WIPHONE #endif // ----------------------------------------------------------------------------- diff --git a/variants/wiphone/pins_arduino.h b/variants/wiphone/pins_arduino.h new file mode 100644 index 000000000..bca9c1173 --- /dev/null +++ b/variants/wiphone/pins_arduino.h @@ -0,0 +1,52 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define EXTERNAL_NUM_INTERRUPTS 16 +#define NUM_DIGITAL_PINS 20 +#define NUM_ANALOG_INPUTS 16 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (esp32_adc2gpio[(p)]) : -1) +#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 34) + +static const uint8_t TX = 1; +static const uint8_t RX = 3; + +static const uint8_t SDA = 21; +static const uint8_t SCL = 22; + +static const uint8_t SS = 5; +static const uint8_t MOSI = 23; +static const uint8_t MISO = 19; +static const uint8_t SCK = 18; + +static const uint8_t G23 = 23; +static const uint8_t G19 = 19; +static const uint8_t G18 = 18; +static const uint8_t G3 = 3; +static const uint8_t G16 = 16; +static const uint8_t G21 = 21; +static const uint8_t G2 = 2; +static const uint8_t G12 = 12; +static const uint8_t G15 = 15; +static const uint8_t G35 = 35; +static const uint8_t G36 = 36; +static const uint8_t G25 = 25; +static const uint8_t G26 = 26; +static const uint8_t G1 = 1; +static const uint8_t G17 = 17; +static const uint8_t G22 = 22; +static const uint8_t G5 = 5; +static const uint8_t G13 = 13; +static const uint8_t G0 = 0; +static const uint8_t G34 = 34; + +static const uint8_t DAC1 = 25; +static const uint8_t DAC2 = 26; + +static const uint8_t ADC1 = 35; +static const uint8_t ADC2 = 36; + +#endif /* Pins_Arduino_h */ diff --git a/variants/wiphone/platformio.ini b/variants/wiphone/platformio.ini new file mode 100644 index 000000000..10c0de55e --- /dev/null +++ b/variants/wiphone/platformio.ini @@ -0,0 +1,13 @@ +[env:wiphone] +extends = esp32_base +board = wiphone +monitor_filters = esp32_exception_decoder +board_build.partitions = default_16MB.csv +build_flags = + ${esp32_base.build_flags} -D WIPHONE -I variants/wiphone +lib_deps = + ${esp32_base.lib_deps} + lovyan03/LovyanGFX@^1.1.8 + sparkfun/SX1509 IO Expander@^3.0.5 + pololu/APA102@^3.0.0 + \ No newline at end of file diff --git a/variants/wiphone/variant.h b/variants/wiphone/variant.h new file mode 100644 index 000000000..b2b3ade78 --- /dev/null +++ b/variants/wiphone/variant.h @@ -0,0 +1,58 @@ +#define I2C_SDA 15 +#define I2C_SCL 25 + +#define GPIO_EXTENDER 1509 +#define EXTENDER_FLAG 0x40 +#define EXTENDER_PIN(x) (x + EXTENDER_FLAG) + +#undef RF95_SCK +#undef RF95_MISO +#undef RF95_MOSI +#undef RF95_NSS + +#define RF95_SCK 14 +#define RF95_MISO 12 +#define RF95_MOSI 13 +#define RF95_NSS 27 + +#define USE_RF95 +#define LORA_DIO0 38 +#define LORA_RESET RADIOLIB_NC +#define LORA_DIO1 RADIOLIB_NC +#define LORA_DIO2 RADIOLIB_NC + +// This board has no GPS or Screen for now +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define NO_GPS +#define HAS_GPS 0 +#define NO_SCREEN +#define HAS_SCREEN 0 + +// Default SPI1 will be mapped to the display +#define ST7789_SDA 23 +#define ST7789_SCK 18 +#define ST7789_CS 5 +#define ST7789_RS 26 +#define ST7789_BL -1 // EXTENDER_PIN(9) + +#define ST7789_RESET -1 +#define ST7789_MISO 19 +#define ST7789_BUSY -1 +#define ST7789_SPI_HOST SPI3_HOST +#define ST7789_BACKLIGHT_EN -1 // EXTENDER_PIN(9) +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 240 +#define TFT_WIDTH 320 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 + +#define I2S_MCLK_GPIO0 +#define I2S_BCK_PIN 4 // rev1.3 - 4 (wp05) +#define I2S_WS_PIN 33 +#define I2S_MOSI_PIN 21 +#define I2S_MISO_PIN 34 \ No newline at end of file From 0d57d29cbdba8d0f22646b29daeb76d7619cae92 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 6 May 2024 14:51:19 -0500 Subject: [PATCH 0369/1377] Send fixed position to mesh after setting it (#3803) --- src/modules/AdminModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 37e798b3c..adf5620ba 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -221,6 +221,8 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta nodeDB->setLocalPosition(r->set_fixed_position); config.position.fixed_position = true; saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); + // Send our new fixed position to the mesh for good measure + positionModule->sendOurPosition(); #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) gps->enable(); From 2c99f11073c3b7d6f150d4fbb955580dae7e4619 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 6 May 2024 17:35:38 -0500 Subject: [PATCH 0370/1377] Revert "set USB_CDC_ON_BOOT, udate arduinoespressif32 to 2.0.15 (#3764)" (#3809) This reverts commit 71400103b3e9f981cc1ac7f3dff4c24b663a93ab. --- boards/tbeam-s3-core.json | 1 - variants/tbeam-s3-core/platformio.ini | 2 -- 2 files changed, 3 deletions(-) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 8d2c3eed6..4c82a2789 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -7,7 +7,6 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", - "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/tbeam-s3-core/platformio.ini index 926f15a39..99d315a69 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/tbeam-s3-core/platformio.ini @@ -3,8 +3,6 @@ extends = esp32s3_base board = tbeam-s3-core -platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.15 - lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@1.0.1 From c009c0db1e7f79f5599c173961e1cddcbc05cc80 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 6 May 2024 20:56:59 -0500 Subject: [PATCH 0371/1377] Elimate non-text output for Portduino --- src/SerialConsole.cpp | 2 ++ src/main.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 88a336ecc..53ece0fa3 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -51,7 +51,9 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con } } #endif +#if !ARCH_PORTDUINO emitRebooted(); +#endif } int32_t SerialConsole::runOnce() diff --git a/src/main.cpp b/src/main.cpp index a6c1dd9fb..b7bc4892b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -344,7 +344,7 @@ void setup() Wire.begin(I2C_SDA, I2C_SCL); #elif defined(ARCH_PORTDUINO) if (settingsStrings[i2cdev] != "") { - LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev]); + LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev].c_str()); Wire.begin(settingsStrings[i2cdev].c_str()); } else { LOG_INFO("No I2C device configured, skipping.\n"); From 76adcbb46ba2c8c57158db37a73a6d75bb4511be Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Tue, 7 May 2024 11:57:49 +0800 Subject: [PATCH 0372/1377] Enhanced t-echo file system integrity check --- src/FSCommon.cpp | 86 ++++++++++++++++++++++++++++++---- variants/t-echo/platformio.ini | 1 + 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index d5ca72142..3d2885654 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -205,6 +205,63 @@ void rmDir(const char *dirname) #endif } +bool fsCheck() +{ +#if defined(ARCH_NRF52) + size_t write_size = 0; + size_t read_size = 0; + char buf[32] = {0}; + while (!Serial); + + Adafruit_LittleFS_Namespace::File file(FSCom); + const char *text = "meshtastic fs test"; + size_t text_length = strlen(text); + const char *filename = "/meshtastic.txt"; + + LOG_DEBUG("Try create file .\n"); + if (file.open(filename, FILE_O_WRITE)) { + write_size = file.write(text); + } else { + LOG_DEBUG("Open file failed .\n");; + goto FORMAT_FS; + } + + if (write_size != text_length) { + LOG_DEBUG("Text bytes do not match .\n"); + file.close(); + goto FORMAT_FS; + } + + file.close(); + + if (!file.open(filename, FILE_O_READ)) { + LOG_DEBUG("Open file failed .\n"); + goto FORMAT_FS; + } + + read_size = file.readBytes(buf, text_length); + if (read_size != text_length) { + LOG_DEBUG("Text bytes do not match .\n"); + file.close(); + goto FORMAT_FS; + } + + if (memcmp(buf, text, text_length) != 0) { + LOG_DEBUG("The written bytes do not match the read bytes .\n"); + file.close(); + goto FORMAT_FS; + } + return true; +FORMAT_FS: + LOG_DEBUG("Format FS ....\n"); + FSCom.format(); + FSCom.begin(); + return false; +#else + return true; +#endif +} + void fsInit() { #ifdef FSCom @@ -219,15 +276,28 @@ void fsInit() * nRF52840 has a certain chance of automatic formatting failure. * Try to create a file after initializing the file system. If the creation fails, * it means that the file system is not working properly. Please format it manually again. + * To check the normality of the file system, you need to disable the LFS_NO_ASSERT assertion. + * Otherwise, the assertion will be entered at the moment of reading or opening, and the FS will not be formatted. * */ - Adafruit_LittleFS_Namespace::File file(FSCom); - const char *filename = "/meshtastic.txt"; - if (!file.open(filename, FILE_O_WRITE)) { - LOG_DEBUG("Format ...."); - FSCom.format(); - FSCom.begin(); - } else { - file.close(); + bool ret = false; + uint8_t retry = 3; + + while (retry--) { + ret = fsCheck(); + if (ret) { + LOG_DEBUG("File system check is OK.\n"); + break; + } + delay(10); + } + + // It may not be possible to reach this step. + // Add a loop here to prevent unpredictable situations from happening. + // Can add a screen to display error status later. + if (!ret) { + while (1) { + Serial.println("The file system is damaged and cannot proceed to the next step.\n"); delay(1000); + } } #else LOG_DEBUG("Filesystem files:\n"); diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 9ff60be3f..900c7ffd8 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -17,6 +17,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DLFS_NO_ASSERT ; Disable LFS assertions build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From b63997b36f56e61ad2eefafaac80e66afd65dd18 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Tue, 7 May 2024 13:42:00 +0800 Subject: [PATCH 0373/1377] Remove debug wait --- src/FSCommon.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 3d2885654..28dd877ae 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -211,7 +211,6 @@ bool fsCheck() size_t write_size = 0; size_t read_size = 0; char buf[32] = {0}; - while (!Serial); Adafruit_LittleFS_Namespace::File file(FSCom); const char *text = "meshtastic fs test"; From 81ecd6d926f0dfe4a938ca0106975f326ca9f533 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Tue, 7 May 2024 14:33:16 +0800 Subject: [PATCH 0374/1377] trunk fmt --- src/FSCommon.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 28dd877ae..8e3b67fa0 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -208,8 +208,8 @@ void rmDir(const char *dirname) bool fsCheck() { #if defined(ARCH_NRF52) - size_t write_size = 0; - size_t read_size = 0; + size_t write_size = 0; + size_t read_size = 0; char buf[32] = {0}; Adafruit_LittleFS_Namespace::File file(FSCom); @@ -221,7 +221,7 @@ bool fsCheck() if (file.open(filename, FILE_O_WRITE)) { write_size = file.write(text); } else { - LOG_DEBUG("Open file failed .\n");; + LOG_DEBUG("Open file failed .\n"); goto FORMAT_FS; } @@ -295,7 +295,8 @@ void fsInit() // Can add a screen to display error status later. if (!ret) { while (1) { - Serial.println("The file system is damaged and cannot proceed to the next step.\n"); delay(1000); + Serial.println("The file system is damaged and cannot proceed to the next step.\n"); + delay(1000); } } #else From cbf20e4cee8f8d58eac2a6bfd7f43f8c52828ecb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 7 May 2024 07:57:30 -0500 Subject: [PATCH 0375/1377] Default to new vendor ntp pool (#3819) --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b693a8a2b..8cbeb8dd4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -269,7 +269,7 @@ void NodeDB::installDefaultConfig() config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.device.serial_enabled = true; resetRadioConfig(); - strncpy(config.network.ntp_server, "0.pool.ntp.org", 32); + strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; From 8886d2df551fd66dfe7886114f82000353ed1e47 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Tue, 7 May 2024 11:57:49 +0800 Subject: [PATCH 0376/1377] Enhanced t-echo file system integrity check --- src/FSCommon.cpp | 86 ++++++++++++++++++++++++++++++---- variants/t-echo/platformio.ini | 1 + 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index d5ca72142..3d2885654 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -205,6 +205,63 @@ void rmDir(const char *dirname) #endif } +bool fsCheck() +{ +#if defined(ARCH_NRF52) + size_t write_size = 0; + size_t read_size = 0; + char buf[32] = {0}; + while (!Serial); + + Adafruit_LittleFS_Namespace::File file(FSCom); + const char *text = "meshtastic fs test"; + size_t text_length = strlen(text); + const char *filename = "/meshtastic.txt"; + + LOG_DEBUG("Try create file .\n"); + if (file.open(filename, FILE_O_WRITE)) { + write_size = file.write(text); + } else { + LOG_DEBUG("Open file failed .\n");; + goto FORMAT_FS; + } + + if (write_size != text_length) { + LOG_DEBUG("Text bytes do not match .\n"); + file.close(); + goto FORMAT_FS; + } + + file.close(); + + if (!file.open(filename, FILE_O_READ)) { + LOG_DEBUG("Open file failed .\n"); + goto FORMAT_FS; + } + + read_size = file.readBytes(buf, text_length); + if (read_size != text_length) { + LOG_DEBUG("Text bytes do not match .\n"); + file.close(); + goto FORMAT_FS; + } + + if (memcmp(buf, text, text_length) != 0) { + LOG_DEBUG("The written bytes do not match the read bytes .\n"); + file.close(); + goto FORMAT_FS; + } + return true; +FORMAT_FS: + LOG_DEBUG("Format FS ....\n"); + FSCom.format(); + FSCom.begin(); + return false; +#else + return true; +#endif +} + void fsInit() { #ifdef FSCom @@ -219,15 +276,28 @@ void fsInit() * nRF52840 has a certain chance of automatic formatting failure. * Try to create a file after initializing the file system. If the creation fails, * it means that the file system is not working properly. Please format it manually again. + * To check the normality of the file system, you need to disable the LFS_NO_ASSERT assertion. + * Otherwise, the assertion will be entered at the moment of reading or opening, and the FS will not be formatted. * */ - Adafruit_LittleFS_Namespace::File file(FSCom); - const char *filename = "/meshtastic.txt"; - if (!file.open(filename, FILE_O_WRITE)) { - LOG_DEBUG("Format ...."); - FSCom.format(); - FSCom.begin(); - } else { - file.close(); + bool ret = false; + uint8_t retry = 3; + + while (retry--) { + ret = fsCheck(); + if (ret) { + LOG_DEBUG("File system check is OK.\n"); + break; + } + delay(10); + } + + // It may not be possible to reach this step. + // Add a loop here to prevent unpredictable situations from happening. + // Can add a screen to display error status later. + if (!ret) { + while (1) { + Serial.println("The file system is damaged and cannot proceed to the next step.\n"); delay(1000); + } } #else LOG_DEBUG("Filesystem files:\n"); diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 9ff60be3f..900c7ffd8 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -17,6 +17,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DLFS_NO_ASSERT ; Disable LFS assertions build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From 8e7ede16efe2c20ce3ab31dc358944b35468ef8c Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Tue, 7 May 2024 13:42:00 +0800 Subject: [PATCH 0377/1377] Remove debug wait --- src/FSCommon.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 3d2885654..28dd877ae 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -211,7 +211,6 @@ bool fsCheck() size_t write_size = 0; size_t read_size = 0; char buf[32] = {0}; - while (!Serial); Adafruit_LittleFS_Namespace::File file(FSCom); const char *text = "meshtastic fs test"; From 1d583341e49ba353bf2339c67a21c677dbf3e85c Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Tue, 7 May 2024 14:33:16 +0800 Subject: [PATCH 0378/1377] trunk fmt --- src/FSCommon.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 28dd877ae..8e3b67fa0 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -208,8 +208,8 @@ void rmDir(const char *dirname) bool fsCheck() { #if defined(ARCH_NRF52) - size_t write_size = 0; - size_t read_size = 0; + size_t write_size = 0; + size_t read_size = 0; char buf[32] = {0}; Adafruit_LittleFS_Namespace::File file(FSCom); @@ -221,7 +221,7 @@ bool fsCheck() if (file.open(filename, FILE_O_WRITE)) { write_size = file.write(text); } else { - LOG_DEBUG("Open file failed .\n");; + LOG_DEBUG("Open file failed .\n"); goto FORMAT_FS; } @@ -295,7 +295,8 @@ void fsInit() // Can add a screen to display error status later. if (!ret) { while (1) { - Serial.println("The file system is damaged and cannot proceed to the next step.\n"); delay(1000); + Serial.println("The file system is damaged and cannot proceed to the next step.\n"); + delay(1000); } } #else From f19aa49eb230fbac41192201210c79597861be4d Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Tue, 7 May 2024 16:11:41 -0400 Subject: [PATCH 0379/1377] add veml7700 --- platformio.ini | 5 +-- .../Telemetry/Sensor/VEML7700Sensor.cpp | 34 +++++++++++++++++++ src/modules/Telemetry/Sensor/VEML7700Sensor.h | 17 ++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/VEML7700Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/VEML7700Sensor.h diff --git a/platformio.ini b/platformio.ini index a1082a84a..d03f3243d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,7 +80,7 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 - nanopb/Nanopb@^0.4.7 + nanopb/Nanopb@^0.4.8 erriez/ErriezCRC32@^1.0.1 ; Used for the code analysis in PIO Home / Inspect @@ -132,4 +132,5 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 - adafruit/Adafruit LSM6DS@^4.7.2 \ No newline at end of file + adafruit/Adafruit LSM6DS@^4.7.2 + adafruit/Adafruit VEML7700 Library@^2.1.6 \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp new file mode 100644 index 000000000..cfce1c7f1 --- /dev/null +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -0,0 +1,34 @@ +#include "VEML7700Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" +#include +#include + +VEML7700Sensor::VEML7700Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_VEML7700, "VEML7700") {} + +int32_t VEML7700Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + status = veml7700.begin(nodeTelemetrySensorsMap[sensorType].second); + + veml7700.setLowThreshold(10000); + veml7700.setHighThreshold(20000); + veml7700.interruptEnable(true); + + return initI2CSensor(); +} + +void VEML7700Sensor::setup() {} + +bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("VEML7700Sensor::getMetrics\n"); + + measurement->variant.environment_metrics.lux = veml7700.readLux(VEML_LUX_AUTO); + + return true; +} \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h new file mode 100644 index 000000000..fac602672 --- /dev/null +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class VEML7700Sensor : public TelemetrySensor +{ + private: + Adafruit_VEML7700 veml7700; + + protected: + virtual void setup() override; + + public: + VEML7700Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file From 23466b5a5fb2cc646350d26443f9ec996ecb1d0c Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Tue, 7 May 2024 18:07:24 -0400 Subject: [PATCH 0380/1377] regenerated files --- src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/channel.pb.h | 17 +++++++++------ src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 8 +++++++ src/mesh/generated/meshtastic/telemetry.pb.h | 21 ++++++++++++------- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index 54629f522..ba9f90873 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 658 +#define meshtastic_ChannelSet_size 674 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index 185a47a98..d9c7d4ffa 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -34,6 +34,9 @@ typedef enum _meshtastic_Channel_Role { typedef struct _meshtastic_ModuleSettings { /* Bits of precision for the location sent in position packets. */ uint32_t position_precision; + /* Controls whether or not the phone / clients should mute the current channel + Useful for noisy public channels you don't necessarily want to disable */ + bool is_client_muted; } meshtastic_ModuleSettings; typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t; @@ -126,14 +129,15 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default} -#define meshtastic_ModuleSettings_init_default {0} +#define meshtastic_ModuleSettings_init_default {0, 0} #define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN} #define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero} -#define meshtastic_ModuleSettings_init_zero {0} +#define meshtastic_ModuleSettings_init_zero {0, 0} #define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_ModuleSettings_position_precision_tag 1 +#define meshtastic_ModuleSettings_is_client_muted_tag 2 #define meshtastic_ChannelSettings_channel_num_tag 1 #define meshtastic_ChannelSettings_psk_tag 2 #define meshtastic_ChannelSettings_name_tag 3 @@ -159,7 +163,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings #define meshtastic_ModuleSettings_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, position_precision, 1) +X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \ +X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2) #define meshtastic_ModuleSettings_CALLBACK NULL #define meshtastic_ModuleSettings_DEFAULT NULL @@ -182,9 +187,9 @@ extern const pb_msgdesc_t meshtastic_Channel_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size -#define meshtastic_ChannelSettings_size 70 -#define meshtastic_Channel_size 85 -#define meshtastic_ModuleSettings_size 6 +#define meshtastic_ChannelSettings_size 72 +#define meshtastic_Channel_size 87 +#define meshtastic_ModuleSettings_size 8 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 2506ec647..b8cc80633 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -306,7 +306,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size -#define meshtastic_ChannelFile_size 702 +#define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 #define meshtastic_OEMStore_size 3346 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 67b2edd15..ffc18c30b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -63,6 +63,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_NANO_G2_ULTRA = 18, /* LoRAType device: https://loratype.org/ */ meshtastic_HardwareModel_LORA_TYPE = 19, + /* wiphone https://www.wiphone.io/ */ + meshtastic_HardwareModel_WIPHONE = 20, /* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */ meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ @@ -148,6 +150,12 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TD_LORAC = 60, /* CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3 */ meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61, + /* TWC_MESH_V4 + Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS */ + meshtastic_HardwareModel_TWC_MESH_V4 = 62, + /* NRF52_PROMICRO_DIY + Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS */ + meshtastic_HardwareModel_NRF52_PROMICRO_DIY = 63, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index e670dd340..d4efad85d 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -45,7 +45,11 @@ typedef enum _meshtastic_TelemetrySensorType { /* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */ meshtastic_TelemetrySensorType_BMP085 = 15, /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */ - meshtastic_TelemetrySensorType_RCWL9620 = 16 + meshtastic_TelemetrySensorType_RCWL9620 = 16, + /* Sensirion High accuracy temperature and humidity */ + meshtastic_TelemetrySensorType_SHT4X = 17, + /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + meshtastic_TelemetrySensorType_VEML7700 = 18 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -82,6 +86,7 @@ typedef struct _meshtastic_EnvironmentMetrics { uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ float distance; + float lux; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -152,8 +157,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_RCWL9620 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_RCWL9620+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_VEML7700 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_VEML7700+1)) @@ -163,12 +168,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -187,6 +192,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 +#define meshtastic_EnvironmentMetrics_lux_tag 9 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -229,7 +235,8 @@ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, distance, 8) +X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ +X(a, STATIC, SINGULAR, FLOAT, lux, 9) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -289,7 +296,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 39 +#define meshtastic_EnvironmentMetrics_size 44 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From 73ab43c67a2a5aafbdf49eae951ffaf500a74c38 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Wed, 8 May 2024 08:45:24 +0800 Subject: [PATCH 0381/1377] Change to LOG_ERROR --- src/FSCommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 8e3b67fa0..96aad1a9a 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -295,7 +295,7 @@ void fsInit() // Can add a screen to display error status later. if (!ret) { while (1) { - Serial.println("The file system is damaged and cannot proceed to the next step.\n"); + LOG_ERROR("The file system is damaged and cannot proceed to the next step.\n"); delay(1000); } } From 8c3b9a61395d15f4b88101760f0eb94989e21b14 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Wed, 8 May 2024 08:46:08 +0800 Subject: [PATCH 0382/1377] Move LFS_NO_ASSERT to nrf52.ini --- arch/nrf52/nrf52.ini | 1 + variants/t-echo/platformio.ini | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 0669a31e8..314c73361 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -9,6 +9,7 @@ build_flags = -DSERIAL_BUFFER_SIZE=1024 -Wno-unused-variable -Isrc/platform/nrf52 + -DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818 build_src_filter = ${arduino_base.build_src_filter} - - - - - - - - - - diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 900c7ffd8..9ff60be3f 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -17,7 +17,6 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. - -DLFS_NO_ASSERT ; Disable LFS assertions build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From 6c75f2e627a6e67e3dc059797dd99af63a182c92 Mon Sep 17 00:00:00 2001 From: lewisxhe Date: Wed, 8 May 2024 08:51:24 +0800 Subject: [PATCH 0383/1377] Move LFS_NO_ASSERT to nrf52.ini --- variants/t-echo/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 900c7ffd8..1ba0afe25 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -17,7 +17,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. - -DLFS_NO_ASSERT ; Disable LFS assertions + build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} From 8105c0440a8ad943b924ba745538dac09688124a Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 8 May 2024 14:53:13 +0300 Subject: [PATCH 0384/1377] New variants PROMICRO_DIY (#3788) * New variants PROMICRO_DIY * Renaming and cleanup * Renaming - phase 2 * nrf52_promicro: Trunk formatting --------- Co-authored-by: Ben Meadors --- boards/promicro-nrf52840.json | 52 ++++++ src/platform/nrf52/architecture.h | 2 + .../diy/nrf52_promicro_diy_tcxo/variant.cpp | 31 ++++ .../diy/nrf52_promicro_diy_tcxo/variant.h | 154 ++++++++++++++++++ .../diy/nrf52_promicro_diy_xtal/variant.cpp | 31 ++++ .../diy/nrf52_promicro_diy_xtal/variant.h | 153 +++++++++++++++++ variants/diy/platformio.ini | 32 ++++ 7 files changed, 455 insertions(+) create mode 100644 boards/promicro-nrf52840.json create mode 100644 variants/diy/nrf52_promicro_diy_tcxo/variant.cpp create mode 100644 variants/diy/nrf52_promicro_diy_tcxo/variant.h create mode 100644 variants/diy/nrf52_promicro_diy_xtal/variant.cpp create mode 100644 variants/diy/nrf52_promicro_diy_xtal/variant.h diff --git a/boards/promicro-nrf52840.json b/boards/promicro-nrf52840.json new file mode 100644 index 000000000..99ae3f01e --- /dev/null +++ b/boards/promicro-nrf52840.json @@ -0,0 +1,52 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x00B3"], + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "ProMicro compatible nRF52840", + "mcu": "nrf52840", + "variant": "promicro_diy", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "ProMicro compatible nRF52840", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.nologo.tech/product/otherboard/NRF52840.html", + "vendor": "Nologo" +} diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 3be3e7e55..68bd87801 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -54,6 +54,8 @@ #define HW_VENDOR meshtastic_HardwareModel_NRF52840_PCA10059 #elif defined(TWC_MESH_V4) #define HW_VENDOR meshtastic_HardwareModel_TWC_MESH_V4 +#elif defined(NRF52_PROMICRO_DIY) +#define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp b/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp new file mode 100644 index 000000000..4030122e5 --- /dev/null +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp @@ -0,0 +1,31 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; \ No newline at end of file diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/diy/nrf52_promicro_diy_tcxo/variant.h new file mode 100644 index 000000000..8b957fe12 --- /dev/null +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.h @@ -0,0 +1,154 @@ +#ifndef _VARIANT_PROMICRO_DIY_ +#define _VARIANT_PROMICRO_DIY_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +// #define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF + +#define PROMICRO_DIY_TCXO + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/* +NRF52 PRO MICRO PIN ASSIGNMENT + +| Pin | Function | | Pin | Function | +|-------|------------|---|---------|-------------| +| Gnd | | | vbat | | +| P0.06 | Serial2 RX | | vbat | | +| P0.08 | Serial2 TX | | Gnd | | +| Gnd | | | reset | | +| Gnd | | | ext_vcc | *see 0.13 | +| P0.17 | RXEN | | P0.31 | BATTERY_PIN | +| P0.20 | GPS_RX | | P0.29 | BUSY | +| P0.22 | GPS_TX | | P0.02 | MISO | +| P0.24 | GPS_EN | | P1.15 | MOSI | +| P1.00 | BUTTON_PIN | | P1.13 | CS | +| P0.11 | SCL | | P1.11 | SCK | +| P1.04 | SDA | | P0.10 | DIO1/IRQ | +| P1.06 | Free pin | | P0.09 | RESET | +| | | | | | +| | Mid board | | | Internal | +| P1.01 | Free pin | | 0.15 | LED | +| P1.02 | Free pin | | 0.13 | 3V3_EN | +| P1.07 | Free pin | | | | +*/ + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// Pin 13 enables 3.3V periphery. If the Lora module is on this pin, then it should stay enabled at all times. +#define PIN_3V3_EN (0 + 13) // P0.13 + +// Analog pins +#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 +#define VBAT_MV_PER_LSB (0.73242188F) +// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) +#define VBAT_DIVIDER (0.6F) +// Compensation factor for the VBAT divider +#define VBAT_DIVIDER_COMP (1.73) +// Fixed calculation of milliVolt from compensation value +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB +#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) + +// WIRE IC AND IIC PINS +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) // P1.04 +#define PIN_WIRE_SCL (0 + 11) // P0.11 + +// LED +#define PIN_LED1 (0 + 15) // P0.15 +#define LED_BUILTIN PIN_LED1 +// Actually red +#define LED_BLUE PIN_LED1 +#define LED_STATE_ON 1 // State when LED is lit + +// Button +#define BUTTON_PIN (32 + 0) // P1.00 + +// GPS +#define PIN_GPS_TX (0 + 22) // P0.22 +#define PIN_GPS_RX (0 + 20) // P0.20 + +#define PIN_GPS_EN (0 + 24) // P0.24 +#define GPS_POWER_TOGGLE +#define GPS_UBLOX +// define GPS_DEBUG + +// UART interfaces +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +#define PIN_SERIAL2_RX (0 + 6) // P0.06 +#define PIN_SERIAL2_TX (0 + 8) // P0.08 + +// Serial interfaces +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 2) // P0.02 +#define PIN_SPI_MOSI (32 + 15) // P1.15 +#define PIN_SPI_SCK (32 + 11) // P1.11 + +// LORA MODULES +#define USE_LLCC68 +#define USE_SX1262 + +// LORA CONFIG +#define SX126X_CS (32 + 13) // P1.13 FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 (0 + 10) // P0.10 IRQ +#define SX126X_DIO2_AS_RF_SWITCH // Note for E22 modules: DIO2 is not attached internally to TXEN for automatic TX/RX switching, + // so it needs connecting externally if it is used in this way +#define SX126X_BUSY (0 + 29) // P0.29 +#define SX126X_RESET (0 + 9) // P0.09 +#define SX126X_RXEN (0 + 17) // P0.17 +#define SX126X_TXEN RADIOLIB_NC // Assuming that DIO2 is connected to TXEN pin. If not, TXEN must be connected. + +/* +On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, then this should not be used. + +Ebyte +e22-900mm22s has no TCXO +e22-900m22s has TCXO +e220-900mm22s has no TCXO, works with/without this definition, looks like DIO3 not connected at all + +AI-thinker +RA-01SH does not have TCXO + +Waveshare +Core1262 has TCXO + +*/ +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.cpp b/variants/diy/nrf52_promicro_diy_xtal/variant.cpp new file mode 100644 index 000000000..4030122e5 --- /dev/null +++ b/variants/diy/nrf52_promicro_diy_xtal/variant.cpp @@ -0,0 +1,31 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; \ No newline at end of file diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.h b/variants/diy/nrf52_promicro_diy_xtal/variant.h new file mode 100644 index 000000000..fd0b21681 --- /dev/null +++ b/variants/diy/nrf52_promicro_diy_xtal/variant.h @@ -0,0 +1,153 @@ +#ifndef _VARIANT_PROMICRO_DIY_ +#define _VARIANT_PROMICRO_DIY_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +// #define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF + +#define PROMICRO_DIY_XTAL +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/* +NRF52 PRO MICRO PIN ASSIGNMENT + +| Pin | Function | | Pin | Function | +|-------|------------|---|---------|-------------| +| Gnd | | | vbat | | +| P0.06 | Serial2 RX | | vbat | | +| P0.08 | Serial2 TX | | Gnd | | +| Gnd | | | reset | | +| Gnd | | | ext_vcc | *see 0.13 | +| P0.17 | RXEN | | P0.31 | BATTERY_PIN | +| P0.20 | GPS_RX | | P0.29 | BUSY | +| P0.22 | GPS_TX | | P0.02 | MISO | +| P0.24 | GPS_EN | | P1.15 | MOSI | +| P1.00 | BUTTON_PIN | | P1.13 | CS | +| P0.11 | SCL | | P1.11 | SCK | +| P1.04 | SDA | | P0.10 | DIO1/IRQ | +| P1.06 | Free pin | | P0.09 | RESET | +| | | | | | +| | Mid board | | | Internal | +| P1.01 | Free pin | | 0.15 | LED | +| P1.02 | Free pin | | 0.13 | 3V3_EN | +| P1.07 | Free pin | | | | +*/ + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// Pin 13 enables 3.3V periphery. If the Lora module is on this pin, then it should stay enabled at all times. +#define PIN_3V3_EN (0 + 13) // P0.13 + +// Analog pins +#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 +#define VBAT_MV_PER_LSB (0.73242188F) +// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) +#define VBAT_DIVIDER (0.6F) +// Compensation factor for the VBAT divider +#define VBAT_DIVIDER_COMP (1.73) +// Fixed calculation of milliVolt from compensation value +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB +#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) + +// WIRE IC AND IIC PINS +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) // P1.04 +#define PIN_WIRE_SCL (0 + 11) // P0.11 + +// LED +#define PIN_LED1 (0 + 15) // P0.15 +#define LED_BUILTIN PIN_LED1 +// Actually red +#define LED_BLUE PIN_LED1 +#define LED_STATE_ON 1 // State when LED is lit + +// Button +#define BUTTON_PIN (32 + 0) // P1.00 + +// GPS +#define PIN_GPS_TX (0 + 22) // P0.22 +#define PIN_GPS_RX (0 + 20) // P0.20 + +#define PIN_GPS_EN (0 + 24) // P0.24 +#define GPS_POWER_TOGGLE +#define GPS_UBLOX +// define GPS_DEBUG + +// UART interfaces +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +#define PIN_SERIAL2_RX (0 + 6) // P0.06 +#define PIN_SERIAL2_TX (0 + 8) // P0.08 + +// Serial interfaces +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 2) // P0.02 +#define PIN_SPI_MOSI (32 + 15) // P1.15 +#define PIN_SPI_SCK (32 + 11) // P1.11 + +// LORA MODULES +#define USE_LLCC68 +#define USE_SX1262 + +// LORA CONFIG +#define SX126X_CS (32 + 13) // P1.13 FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 (0 + 10) // P0.10 IRQ +#define SX126X_DIO2_AS_RF_SWITCH // Note for E22 modules: DIO2 is not attached internally to TXEN for automatic TX/RX switching, + // so it needs connecting externally if it is used in this way +#define SX126X_BUSY (0 + 29) // P0.29 +#define SX126X_RESET (0 + 9) // P0.09 +#define SX126X_RXEN (0 + 17) // P0.17 +#define SX126X_TXEN RADIOLIB_NC // Assuming that DIO2 is connected to TXEN pin. If not, TXEN must be connected. + +/* +On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, then this should not be used. + +Ebyte +e22-900mm22s has no TCXO +e22-900m22s has TCXO +e220-900mm22s has no TCXO, works with/without this definition, looks like DIO3 not connected at all + +AI-thinker +RA-01SH does not have TCXO + +Waveshare +Core1262 has TCXO + +*/ +// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/diy/platformio.ini b/variants/diy/platformio.ini index e7d72d13f..5fb0f6421 100644 --- a/variants/diy/platformio.ini +++ b/variants/diy/platformio.ini @@ -44,3 +44,35 @@ build_flags = ${esp32_base.build_flags} -D DIY_V1 -I variants/diy/hydra + + +; Promicro + E22(0)-xxxMM / RA-01SH modules board variant - DIY - without TCXO +[env:nrf52_promicro_diy_xtal] +extends = nrf52840_base +board = promicro-nrf52840 +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -I variants/diy/nrf52_promicro_diy_xtal + -D NRF52_PROMICRO_DIY + -D OLED_RU + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_xtal> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink + + +; Promicro + E22(0)-xxxM / HT-RA62 modules board variant - DIY - with TCXO +[env:nrf52_promicro_diy_tcxo] +extends = nrf52840_base +board = promicro-nrf52840 +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -I variants/diy/nrf52_promicro_diy_tcxo + -D NRF52_PROMICRO_DIY + -D OLED_RU + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_tcxo> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink \ No newline at end of file From 5371f134ba5ab84b1292d3b5bf8a560ea6b9187f Mon Sep 17 00:00:00 2001 From: pr000t <20946964+pr000t@users.noreply.github.com> Date: Wed, 8 May 2024 14:02:53 +0200 Subject: [PATCH 0385/1377] Add Sensirion SHT4X sensors (#3792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add Sensirion SHT4X sensors * Update platformio.ini Fix lib version * Delete src/mesh/generated/meshtastic/telemetry.pb.h * Revert "Delete src/mesh/generated/meshtastic/telemetry.pb.h" This reverts commit 8e5e6a9f6ff4e31ed32775741c03a855e663a5de. * remove modification on generated file * Update ScanI2CTwoWire.cpp Fix copy/paste issue --------- Co-authored-by: Thomas Göttgens --- platformio.ini | 1 + src/configuration.h | 2 +- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 15 ++++- src/main.cpp | 3 +- .../Telemetry/EnvironmentTelemetry.cpp | 6 ++ src/modules/Telemetry/Sensor/SHT4XSensor.cpp | 63 +++++++++++++++++++ src/modules/Telemetry/Sensor/SHT4XSensor.h | 23 +++++++ 8 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SHT4XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SHT4XSensor.h diff --git a/platformio.ini b/platformio.ini index a6db1c76e..9d7c76fbf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -133,3 +133,4 @@ lib_deps = https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 + https://github.com/Sensirion/arduino-i2c-sht4x#1.1.0 diff --git a/src/configuration.h b/src/configuration.h index 493449764..0d9ee5451 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -126,7 +126,7 @@ along with this program. If not, see . #define SHTC3_ADDR 0x70 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D -#define SHT31_ADDR 0x44 +#define SHT31_4x_ADDR 0x44 #define PMSA0031_ADDR 0x12 #define RCWL9620_ADDR 0x57 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 20f22040c..a53df11f3 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -29,6 +29,7 @@ class ScanI2C INA3221, MCP9808, SHT31, + SHT4X, SHTC3, LPS22HB, QMC6310, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 562a94c1f..58d46a58d 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -292,7 +292,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port) break; - SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n") + case SHT31_4x_ADDR: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); + if (registerValue == 0x11a2) { + type = SHT4X; + LOG_INFO("SHT4X sensor found\n"); + } else { + type = SHT31; + LOG_INFO("SHT31 sensor found\n"); + } + + break; + SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n") SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n") @@ -357,4 +368,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index b7bc4892b..1465cd084 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -541,6 +541,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) i2cScanner.reset(); @@ -1032,4 +1033,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 93184069d..62adc9a8c 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -26,6 +26,7 @@ #include "Sensor/MCP9808Sensor.h" #include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" +#include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" BMP085Sensor bmp085Sensor; @@ -36,6 +37,7 @@ MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; +SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 @@ -91,6 +93,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = lps22hbSensor.runOnce(); if (sht31Sensor.hasSensor()) result = sht31Sensor.runOnce(); + if (sht4xSensor.hasSensor()) + result = sht4xSensor.runOnce(); if (ina219Sensor.hasSensor()) result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) @@ -246,6 +250,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); + if (sht4xSensor.hasSensor()) + valid = sht4xSensor.getMetrics(&m); if (lps22hbSensor.hasSensor()) valid = lps22hbSensor.getMetrics(&m); if (shtc3Sensor.hasSensor()) diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp new file mode 100644 index 000000000..d324b7fd6 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp @@ -0,0 +1,63 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SHT4XSensor.h" +#include "TelemetrySensor.h" +#include + +// macro definitions +// make sure that we use the proper definition of NO_ERROR +#ifdef NO_ERROR +#undef NO_ERROR +#endif +#define NO_ERROR 0 + +static char errorMessage[64]; +static int16_t error; + +SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {} + +int32_t SHT4XSensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + uint32_t serialNumber = 0; + + sht4x.begin(*nodeTelemetrySensorsMap[sensorType].second, 0x44); + + error = sht4x.serialNumber(serialNumber); + LOG_DEBUG("serialNumber : %x\n", serialNumber); + if (error != NO_ERROR) { + LOG_DEBUG("Error trying to execute serialNumber(): "); + errorToString(error, errorMessage, sizeof errorMessage); + LOG_DEBUG(errorMessage); + status = 0; + } else { + status = 1; + } + + return initI2CSensor(); +} + +void SHT4XSensor::setup() +{ + // Set up oversampling and filter initialization +} + +bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + float aTemperature = 0.0; + float aHumidity = 0.0; + sht4x.measureLowestPrecision(aTemperature, aHumidity); + measurement->variant.environment_metrics.temperature = aTemperature; + measurement->variant.environment_metrics.relative_humidity = aHumidity; + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h new file mode 100644 index 000000000..67045eb2a --- /dev/null +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h @@ -0,0 +1,23 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class SHT4XSensor : public TelemetrySensor +{ + private: + SensirionI2cSht4x sht4x; + + protected: + virtual void setup() override; + + public: + SHT4XSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file From 147de75a0295e7c8e71605d7fd007dfa222a5c12 Mon Sep 17 00:00:00 2001 From: Nicholas Baddorf <42445164+nbaddorf@users.noreply.github.com> Date: Wed, 8 May 2024 08:37:50 -0400 Subject: [PATCH 0386/1377] Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors Co-authored-by: Thomas Göttgens --- src/graphics/Screen.cpp | 77 +++++++++++++++++- src/graphics/Screen.h | 13 +++- src/graphics/TFTDisplay.cpp | 8 +- src/graphics/TFTDisplay.h | 3 + src/input/kbI2cBase.cpp | 80 ++++++++++++++++++- src/modules/CannedMessageModule.cpp | 117 ++++++++++++++++++---------- variants/t-deck/variant.h | 3 +- 7 files changed, 253 insertions(+), 48 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index e5f392036..0899335e6 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -94,6 +94,11 @@ std::vector moduleFrames; // Stores the last 4 of our hardware ID, to make finding the device for pairing easier static char ourId[5]; +// vector where symbols (string) are displayed in bottom corner of display. +std::vector functionSymbals; +// string displayed in bottom right corner of display. Created from elements in functionSymbals vector +std::string functionSymbalString = ""; + #if HAS_GPS // GeoCoord object for the screen GeoCoord geoCoord; @@ -260,6 +265,18 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i #endif } +// draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active +static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + // LOG_DEBUG("Drawing function overlay\n"); + if (functionSymbals.begin() != functionSymbals.end()) { + char buf[64]; + display->setFont(FONT_SMALL); + snprintf(buf, sizeof(buf), "%s", functionSymbalString.c_str()); + display->drawString(SCREEN_WIDTH - display->getStringWidth(buf), SCREEN_HEIGHT - FONT_HEIGHT_SMALL, buf); + } +} + #ifdef USE_EINK /// Used on eink displays while in deep sleep static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -1023,7 +1040,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #if !ARCH_PORTDUINO dispdev->displayOn(); #endif + +#if defined(ST7789_CS) && \ + !defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here. + static_cast(dispdev)->setDisplayBrightness(brightness); +#endif + dispdev->displayOn(); + enabled = true; setInterval(0); // Draw ASAP runASAP = true; @@ -1490,6 +1514,11 @@ void Screen::setFrames() ui->setFrames(normalFrames, numframes); ui->enableAllIndicators(); + // Add function overlay here. This can show when notifications muted, modifier key is active etc + static OverlayCallback functionOverlay[] = {drawFunctionOverlay}; + static const int functionOverlayCount = sizeof(functionOverlay) / sizeof(functionOverlay[0]); + ui->setOverlays(functionOverlay, functionOverlayCount); + prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list // just changed) @@ -1573,9 +1602,55 @@ void Screen::blink() delay(50); count = count - 1; } + // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay. dispdev->setBrightness(brightness); } +void Screen::increaseBrightness() +{ + brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62); + +#if defined(ST7789_CS) + // run the setDisplayBrightness function. This works on t-decks + static_cast(dispdev)->setDisplayBrightness(brightness); +#endif + + /* TO DO: add little popup in center of screen saying what brightness level it is set to*/ +} + +void Screen::decreaseBrightness() +{ + brightness = (brightness < 70) ? brightness : (brightness - 62); + +#if defined(ST7789_CS) + static_cast(dispdev)->setDisplayBrightness(brightness); +#endif + + /* TO DO: add little popup in center of screen saying what brightness level it is set to*/ +} + +void Screen::setFunctionSymbal(std::string sym) +{ + if (std::find(functionSymbals.begin(), functionSymbals.end(), sym) == functionSymbals.end()) { + functionSymbals.push_back(sym); + functionSymbalString = ""; + for (auto symbol : functionSymbals) { + functionSymbalString = symbol + " " + functionSymbalString; + } + setFastFramerate(); + } +} + +void Screen::removeFunctionSymbal(std::string sym) +{ + functionSymbals.erase(std::remove(functionSymbals.begin(), functionSymbals.end(), sym), functionSymbals.end()); + functionSymbalString = ""; + for (auto symbol : functionSymbals) { + functionSymbalString = symbol + " " + functionSymbalString; + } + setFastFramerate(); +} + std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds) { std::string uptime; @@ -1998,4 +2073,4 @@ int Screen::handleInputEvent(const InputEvent *event) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN +#endif // HAS_SCREEN \ No newline at end of file diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 2cb1cd5a9..cfb08c0f4 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -166,9 +166,6 @@ class Screen : public concurrency::OSThread void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); } void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); } - // Implementation to Adjust Brightness - uint8_t brightness = BRIGHTNESS_DEFAULT; - /// Starts showing the Bluetooth PIN screen. // // Switches over to a static frame showing the Bluetooth pairing screen @@ -202,6 +199,13 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } + // functions for display brightness + void increaseBrightness(); + void decreaseBrightness(); + + void setFunctionSymbal(std::string sym); + void removeFunctionSymbal(std::string sym); + /// Stops showing the bluetooth PIN screen. void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); } @@ -395,6 +399,9 @@ class Screen : public concurrency::OSThread // Bluetooth PIN screen) bool showingNormalScreen = false; + // Implementation to Adjust Brightness + uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103 + /// Holds state for debug information DebugInfo debugInfo; diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 36397a826..b19e402b8 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -596,7 +596,7 @@ void TFTDisplay::sendCommand(uint8_t com) unphone.backlight(true); // using unPhone library #endif #ifdef RAK14014 -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function tft->setBrightness(172); #endif break; @@ -640,6 +640,12 @@ void TFTDisplay::sendCommand(uint8_t com) // Drop all other commands to device (we just update the buffer) } +void TFTDisplay::setDisplayBrightness(uint8_t _brightness) +{ + tft->setBrightness(_brightness); + LOG_DEBUG("Brightness is set to value: %i \n", _brightness); +} + void TFTDisplay::flipScreenVertically() { #if defined(T_WATCH_S3) diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 3d6ea6cc6..42aa3abff 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -30,6 +30,9 @@ class TFTDisplay : public OLEDDisplay static bool hasTouch(void); static bool getTouch(int16_t *x, int16_t *y); + // Functions for changing display brightness + void setDisplayBrightness(uint8_t); + /** * shim to make the abstraction happy * diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 74a6c718d..af7c96b20 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -1,5 +1,4 @@ #include "kbI2cBase.h" - #include "configuration.h" #include "detect/ScanI2C.h" @@ -138,6 +137,9 @@ int32_t KbI2cBase::runOnce() break; case 0x13: // Code scanner says the SYM key is 0x13 is_sym = !is_sym; + e.inputEvent = ANYKEY; + e.kbchar = + is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active break; case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; @@ -193,6 +195,75 @@ int32_t KbI2cBase::runOnce() e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; e.source = this->_originName; switch (c) { + case 0x71: // This is the button q. If modifier and q pressed, it cancels the input + if (is_sym) { + is_sym = false; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; + case 0x74: // letter t. if modifier and t pressed call 'tab' + if (is_sym) { + is_sym = false; + e.inputEvent = ANYKEY; + e.kbchar = 0x09; // TAB Scancode + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; + case 0x6d: // letter m. Modifier makes it mute notifications + if (is_sym) { + is_sym = false; + e.inputEvent = ANYKEY; + e.kbchar = 0xac; // mute notifications + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; + case 0x6f: // letter o(+). Modifier makes screen increase in brightness + if (is_sym) { + is_sym = false; + e.inputEvent = ANYKEY; + e.kbchar = 0x11; // Increase Brightness code + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; + case 0x69: // letter i(-). Modifier makes screen decrease in brightness + if (is_sym) { + is_sym = false; + e.inputEvent = ANYKEY; + e.kbchar = 0x12; // Decrease Brightness code + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; + case 0x20: // Space. Send network ping like double press does + if (is_sym) { + is_sym = false; + e.inputEvent = ANYKEY; + e.kbchar = 0xaf; // (fn + space) + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; + case 0x67: // letter g. toggle gps + if (is_sym) { + is_sym = false; + e.inputEvent = ANYKEY; + e.kbchar = 0x9e; + } else { + e.inputEvent = ANYKEY; + e.kbchar = c; + } + break; case 0x1b: // ESC e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; break; @@ -216,6 +287,12 @@ int32_t KbI2cBase::runOnce() e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; e.kbchar = 0xb7; break; + case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker)) + // toggle moddifiers button. + is_sym = !is_sym; + e.inputEvent = ANYKEY; + e.kbchar = is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active + break; case 0x90: // fn+r case 0x91: // fn+t case 0x9b: // fn+s @@ -239,6 +316,7 @@ int32_t KbI2cBase::runOnce() } e.inputEvent = ANYKEY; e.kbchar = c; + is_sym = false; break; } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 8cfea154e..0f17c268b 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -180,11 +180,75 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) { this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; } - // pass the pressed key - // LOG_DEBUG("Canned message ANYKEY (%x)\n", event->kbchar); - this->payload = event->kbchar; - this->lastTouchMillis = millis(); - validEvent = true; + + validEvent = false; // If key is normal than it will be set to true. + + // Run modifier key code below, (doesnt inturrupt typing or reset to start screen page) + switch (event->kbchar) { + case 0x11: // make screen brighter + if (screen) + screen->increaseBrightness(); + LOG_DEBUG("increasing Screen Brightness\n"); + break; + case 0x12: // make screen dimmer + if (screen) + screen->decreaseBrightness(); + LOG_DEBUG("Decreasing Screen Brightness\n"); + break; + case 0xf1: // draw modifier (function) symbal + if (screen) + screen->setFunctionSymbal("Fn"); + break; + case 0xf2: // remove modifier (function) symbal + if (screen) + screen->removeFunctionSymbal("Fn"); + break; + // mute (switch off/toggle) external notifications on fn+m + case 0xac: + if (moduleConfig.external_notification.enabled == true) { + if (externalNotificationModule->getMute()) { + externalNotificationModule->setMute(false); + showTemporaryMessage("Notifications \nEnabled"); + if (screen) + screen->removeFunctionSymbal("M"); // remove the mute symbol from the bottom right corner + } else { + externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop + externalNotificationModule->setMute(true); + showTemporaryMessage("Notifications \nDisabled"); + if (screen) + screen->setFunctionSymbal("M"); // add the mute symbol to the bottom right corner + } + } + break; + case 0x9e: // toggle GPS like triple press does +#if !MESHTASTIC_EXCLUDE_GPS + if (gps != nullptr) { + gps->toggleGpsMode(); + } + if (screen) + screen->forceDisplay(); + showTemporaryMessage("GPS Toggled"); +#endif + break; + case 0xaf: // fn+space send network ping like double press does + service.refreshLocalMeshNode(); + if (service.trySendPosition(NODENUM_BROADCAST, true)) { + showTemporaryMessage("Position \nUpdate Sent"); + } else { + showTemporaryMessage("Node Info \nUpdate Sent"); + } + break; + default: + // pass the pressed key + // LOG_DEBUG("Canned message ANYKEY (%x)\n", event->kbchar); + this->payload = event->kbchar; + this->lastTouchMillis = millis(); + validEvent = true; + break; + } + if (screen && (event->kbchar != 0xf1)) { + screen->removeFunctionSymbal("Fn"); // remove modifier (function) symbal + } } if (event->inputEvent == static_cast(MATRIXKEY)) { LOG_DEBUG("Canned message event Matrix key pressed\n"); @@ -390,8 +454,9 @@ int32_t CannedMessageModule::runOnce() } if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { e.frameChanged = true; - switch (this->payload) { - case 0x08: // backspace + switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the + // display back to the default window + case 0x08: // backspace if (this->freetext.length() > 0) { if (this->cursor == this->freetext.length()) { this->freetext = this->freetext.substring(0, this->freetext.length() - 1); @@ -403,7 +468,6 @@ int32_t CannedMessageModule::runOnce() } break; case 0x09: // tab - case 0x91: // alt+t for T-Deck that doesn't have a tab key if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { @@ -416,7 +480,7 @@ int32_t CannedMessageModule::runOnce() case 0xb7: // right // already handled above break; - // handle fn+s for shutdown + // handle fn+s for shutdown case 0x9b: if (screen) screen->startShutdownScreen(); @@ -430,37 +494,6 @@ int32_t CannedMessageModule::runOnce() rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; - case 0x9e: // toggle GPS like triple press does -#if !MESHTASTIC_EXCLUDE_GPS - if (gps != nullptr) { - gps->toggleGpsMode(); - } - if (screen) - screen->forceDisplay(); - showTemporaryMessage("GPS Toggled"); -#endif - break; - // mute (switch off/toggle) external notifications on fn+m - case 0xac: - if (moduleConfig.external_notification.enabled == true) { - if (externalNotificationModule->getMute()) { - externalNotificationModule->setMute(false); - showTemporaryMessage("Notifications \nEnabled"); - } else { - externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop - externalNotificationModule->setMute(true); - showTemporaryMessage("Notifications \nDisabled"); - } - } - break; - case 0xaf: // fn+space send network ping like double press does - service.refreshLocalMeshNode(); - if (service.trySendPosition(NODENUM_BROADCAST, true)) { - showTemporaryMessage("Position \nUpdate Sent"); - } else { - showTemporaryMessage("Node Info \nUpdate Sent"); - } - break; default: if (this->cursor == this->freetext.length()) { this->freetext += this->payload; @@ -476,6 +509,8 @@ int32_t CannedMessageModule::runOnce() } break; } + if (screen) + screen->removeFunctionSymbal("Fn"); } this->lastTouchMillis = millis(); @@ -787,4 +822,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor) return result; } -#endif +#endif \ No newline at end of file diff --git a/variants/t-deck/variant.h b/variants/t-deck/variant.h index 09db198ec..7efa00c82 100644 --- a/variants/t-deck/variant.h +++ b/variants/t-deck/variant.h @@ -18,6 +18,7 @@ #define TFT_OFFSET_ROTATION 0 #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness #define HAS_TOUCHSCREEN 1 #define SCREEN_TOUCH_INT 16 @@ -96,4 +97,4 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface -// code) +// code) \ No newline at end of file From 75dc8cccecd52da78f1d69eeb4eb017fbc68f26c Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Thu, 9 May 2024 09:08:24 +1200 Subject: [PATCH 0387/1377] Button ISR runs thread asap (#3801) --- src/ButtonThread.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 4566de924..97cce7bc2 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -214,6 +214,7 @@ int32_t ButtonThread::runOnce() btnEvent = BUTTON_EVENT_NONE; } + runASAP = false; return 50; } @@ -234,6 +235,7 @@ void ButtonThread::attachButtonInterrupts() BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); ButtonThread::userButton.tick(); + runASAP = true; }, CHANGE); #endif @@ -280,6 +282,7 @@ void ButtonThread::wakeOnIrq(int irq, int mode) [] { BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); + runASAP = true; }, FALLING); } From 39336847adf18cfe1557bc5bbf2f1b46c456917d Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Wed, 8 May 2024 22:14:55 -0400 Subject: [PATCH 0388/1377] add veml7700 readings to protobuf and to the mqtt json + fix the readigns validator code in env telemetry --- src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 1 + src/main.cpp | 3 +- src/mesh/generated/meshtastic/telemetry.pb.h | 17 +++- .../Telemetry/EnvironmentTelemetry.cpp | 88 ++++++++++++------- .../Telemetry/Sensor/VEML7700Sensor.cpp | 5 +- src/mqtt/MQTT.cpp | 4 + 8 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 701e07a32..37fda2b9d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -128,6 +128,7 @@ along with this program. If not, see . #define LPS22HB_ADDR_ALT 0x5D #define SHT31_ADDR 0x44 #define PMSA0031_ADDR 0x12 +#define VEML7700_ADDR 0x10 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c8fcfee10..5fad2f30a 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -41,6 +41,7 @@ class ScanI2C BQ24295, LSM6DS3, TCA9555, + VEML7700, #ifdef HAS_NCP5623 NCP5623, #endif diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index ba2820a77..0b76318ce 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -324,6 +324,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); + SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/main.cpp b/src/main.cpp index f40fd0789..0a26312eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -538,6 +538,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) i2cScanner.reset(); @@ -1006,4 +1007,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index d4efad85d..d85855437 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -86,7 +86,12 @@ typedef struct _meshtastic_EnvironmentMetrics { uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ float distance; + /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ float lux; + /* VEML7700 raw white light data digital 16-bit resolution sensor. */ + uint32_t white; + /* VEML7700 raw ALS data digital 16-bit resolution sensor. */ + uint32_t ALS; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -168,12 +173,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -193,6 +198,8 @@ extern "C" { #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 #define meshtastic_EnvironmentMetrics_lux_tag 9 +#define meshtastic_EnvironmentMetrics_white_tag 10 +#define meshtastic_EnvironmentMetrics_ALS_tag 11 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -236,7 +243,9 @@ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ -X(a, STATIC, SINGULAR, FLOAT, lux, 9) +X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ +X(a, STATIC, SINGULAR, UINT32, white, 10) \ +X(a, STATIC, SINGULAR, UINT32, ALS, 11) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -296,7 +305,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 44 +#define meshtastic_EnvironmentMetrics_size 56 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 189ab7ed0..657d309ba 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -23,6 +23,7 @@ #include "Sensor/MCP9808Sensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHTC3Sensor.h" +#include "Sensor/VEML7700Sensor.h" BMP085Sensor bmp085Sensor; BMP280Sensor bmp280Sensor; @@ -32,6 +33,7 @@ MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; +VEML7700Sensor veml7700Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -90,6 +92,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); + if (veml7700Sensor.hasSensor()) + result = veml7700Sensor.runOnce(); } return result; } else { @@ -192,10 +196,11 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, " - "temperature=%f, voltage=%f\n", + "temperature=%f, voltage=%f, lux=%f, iaq=%i\n", sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, - t->variant.environment_metrics.temperature, t->variant.environment_metrics.voltage); + t->variant.environment_metrics.temperature, t->variant.environment_metrics.voltage, + t->variant.environment_metrics.lux, t->variant.environment_metrics.iaq); #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -210,44 +215,65 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m; - bool valid = false; + bool valid = true; + bool hasSensor = false; m.time = getTime(); m.which_variant = meshtastic_Telemetry_environment_metrics_tag; - m.variant.environment_metrics.barometric_pressure = 0; - m.variant.environment_metrics.current = 0; - m.variant.environment_metrics.gas_resistance = 0; - m.variant.environment_metrics.relative_humidity = 0; - m.variant.environment_metrics.temperature = 0; - m.variant.environment_metrics.voltage = 0; - - if (sht31Sensor.hasSensor()) - valid = sht31Sensor.getMetrics(&m); - if (lps22hbSensor.hasSensor()) - valid = lps22hbSensor.getMetrics(&m); - if (shtc3Sensor.hasSensor()) - valid = shtc3Sensor.getMetrics(&m); - if (bmp085Sensor.hasSensor()) - valid = bmp085Sensor.getMetrics(&m); - if (bmp280Sensor.hasSensor()) - valid = bmp280Sensor.getMetrics(&m); - if (bme280Sensor.hasSensor()) - valid = bme280Sensor.getMetrics(&m); - if (bme680Sensor.hasSensor()) - valid = bme680Sensor.getMetrics(&m); - if (mcp9808Sensor.hasSensor()) - valid = mcp9808Sensor.getMetrics(&m); - if (ina219Sensor.hasSensor()) + if (sht31Sensor.hasSensor()) { + valid = valid && sht31Sensor.getMetrics(&m); + hasSensor = true; + } + if (lps22hbSensor.hasSensor()) { + valid = valid && lps22hbSensor.getMetrics(&m); + hasSensor = true; + } + if (shtc3Sensor.hasSensor()) { + valid = valid && shtc3Sensor.getMetrics(&m); + hasSensor = true; + } + if (bmp085Sensor.hasSensor()) { + valid = valid && bmp085Sensor.getMetrics(&m); + hasSensor = true; + } + if (bmp280Sensor.hasSensor()) { + valid = valid && bmp280Sensor.getMetrics(&m); + hasSensor = true; + } + if (bme280Sensor.hasSensor()) { + valid = valid && bme280Sensor.getMetrics(&m); + hasSensor = true; + } + if (bme680Sensor.hasSensor()) { + valid = valid && bme680Sensor.getMetrics(&m); + hasSensor = true; + } + if (mcp9808Sensor.hasSensor()) { + valid = valid && mcp9808Sensor.getMetrics(&m); + hasSensor = true; + } + if (ina219Sensor.hasSensor()) { valid = ina219Sensor.getMetrics(&m); - if (ina260Sensor.hasSensor()) - valid = ina260Sensor.getMetrics(&m); + hasSensor = true; + } + if (ina260Sensor.hasSensor()) { + valid = valid && ina260Sensor.getMetrics(&m); + hasSensor = true; + } + if (veml7700Sensor.hasSensor()) { + valid = valid && veml7700Sensor.getMetrics(&m); + hasSensor = true; + } + + valid = valid && hasSensor; if (valid) { LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f, " - "voltage=%f\n", + "voltage=%f, lux=%f, iaq=%i\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, - m.variant.environment_metrics.temperature, m.variant.environment_metrics.voltage); + m.variant.environment_metrics.temperature, m.variant.environment_metrics.voltage, + m.variant.environment_metrics.lux, m.variant.environment_metrics.iaq); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp index cfce1c7f1..5e1376849 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -26,9 +26,10 @@ void VEML7700Sensor::setup() {} bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) { - LOG_DEBUG("VEML7700Sensor::getMetrics\n"); - measurement->variant.environment_metrics.lux = veml7700.readLux(VEML_LUX_AUTO); + measurement->variant.environment_metrics.white = veml7700.readWhite(); + measurement->variant.environment_metrics.ALS = veml7700.readALS(); + return true; } \ No newline at end of file diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index da1c204b8..59f1002fc 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -664,6 +664,10 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["gas_resistance"] = new JSONValue(decoded->variant.environment_metrics.gas_resistance); msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage); msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current); + msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux); + msgPayload["white"] = new JSONValue((uint)decoded->variant.environment_metrics.white); + msgPayload["ALS"] = new JSONValue((uint)decoded->variant.environment_metrics.ALS); + msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); From 5e160b21c78927dd36134a4c6d5f7b08a0603565 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 10 May 2024 01:14:58 +1200 Subject: [PATCH 0389/1377] T-Echo screen and button performance (#3840) * Make button timing configurable per variant * Adjust button timing for T-Echo Easier multi-clicks for features like "toggle backlight" (4x click) * Fewer full-refreshes for T-Echo display Disables ghost pixel tracking: T-Echo ghost pixels are fairly faint. --- src/ButtonThread.cpp | 10 +++++----- src/ButtonThread.h | 15 +++++++++++++-- variants/t-echo/platformio.ini | 2 +- variants/t-echo/variant.h | 3 +++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 97cce7bc2..aaead62be 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -52,8 +52,8 @@ ButtonThread::ButtonThread() : OSThread("Button") #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) userButton.attachClick(userButtonPressed); - userButton.setClickMs(250); - userButton.setPressMs(c_longPressTime); + userButton.setClickMs(BUTTON_CLICK_MS); + userButton.setPressMs(BUTTON_LONGPRESS_MS); userButton.setDebounceMs(1); userButton.attachDoubleClick(userButtonDoublePressed); userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton @@ -70,8 +70,8 @@ ButtonThread::ButtonThread() : OSThread("Button") pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE); #endif userButtonAlt.attachClick(userButtonPressed); - userButtonAlt.setClickMs(250); - userButtonAlt.setPressMs(c_longPressTime); + userButtonAlt.setClickMs(BUTTON_CLICK_MS); + userButtonAlt.setPressMs(BUTTON_LONGPRESS_MS); userButtonAlt.setDebounceMs(1); userButtonAlt.attachDoubleClick(userButtonDoublePressed); userButtonAlt.attachLongPressStart(userButtonPressedLongStart); @@ -80,7 +80,7 @@ ButtonThread::ButtonThread() : OSThread("Button") #ifdef BUTTON_PIN_TOUCH userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true); - userButtonTouch.setPressMs(400); + userButtonTouch.setPressMs(BUTTON_TOUCH_MS); userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click? #endif diff --git a/src/ButtonThread.h b/src/ButtonThread.h index 07c7ccff7..d7a9201a3 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -4,11 +4,22 @@ #include "concurrency/OSThread.h" #include "configuration.h" +#ifndef BUTTON_CLICK_MS +#define BUTTON_CLICK_MS 250 +#endif + +#ifndef BUTTON_LONGPRESS_MS +#define BUTTON_LONGPRESS_MS 5000 +#endif + +#ifndef BUTTON_TOUCH_MS +#define BUTTON_TOCH_MS 400 +#endif + class ButtonThread : public concurrency::OSThread { public: - static const uint32_t c_longPressTime = 5000; // shutdown after 5s - static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot + static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot enum ButtonEventType { BUTTON_EVENT_NONE, diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 9ff60be3f..c036a39a2 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -15,7 +15,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates - -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated +; -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 2abeed16d..1c263a61a 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -65,6 +65,9 @@ extern "C" { #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular GPIO #define PIN_BUTTON_TOUCH (0 + 11) // 0.11 is the soft touch button on T-Echo +#define BUTTON_CLICK_MS 400 +#define BUTTON_TOUCH_MS 200 + /* * Analog pins */ From 0c89aff0f64d7d93321238222d27b2fb32ad4c3f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 9 May 2024 14:56:29 -0500 Subject: [PATCH 0390/1377] Enable telemetry and power telemetry on the native target --- src/modules/Modules.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 15b356b05..e6c44fae6 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -45,7 +45,7 @@ #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" #endif #ifdef ARCH_ESP32 @@ -137,7 +137,7 @@ void setupModules() #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES cannedMessageModule = new CannedMessageModule(); #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY new DeviceTelemetryModule(); #endif #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR @@ -146,7 +146,7 @@ void setupModules() new AirQualityTelemetryModule(); } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new PowerTelemetryModule(); #endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ From 5d9800b7c2c7754e91db228efd7fd73320883a14 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 9 May 2024 21:25:36 -0500 Subject: [PATCH 0391/1377] Revert "Add Sensirion SHT4X sensors (#3792)" (#3845) This reverts commit 5371f134ba5ab84b1292d3b5bf8a560ea6b9187f. --- platformio.ini | 1 - src/configuration.h | 2 +- src/detect/ScanI2C.h | 1 - src/detect/ScanI2CTwoWire.cpp | 15 +---- src/main.cpp | 3 +- .../Telemetry/EnvironmentTelemetry.cpp | 6 -- src/modules/Telemetry/Sensor/SHT4XSensor.cpp | 63 ------------------- src/modules/Telemetry/Sensor/SHT4XSensor.h | 23 ------- 8 files changed, 4 insertions(+), 110 deletions(-) delete mode 100644 src/modules/Telemetry/Sensor/SHT4XSensor.cpp delete mode 100644 src/modules/Telemetry/Sensor/SHT4XSensor.h diff --git a/platformio.ini b/platformio.ini index 9d7c76fbf..a6db1c76e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -133,4 +133,3 @@ lib_deps = https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 - https://github.com/Sensirion/arduino-i2c-sht4x#1.1.0 diff --git a/src/configuration.h b/src/configuration.h index 0d9ee5451..493449764 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -126,7 +126,7 @@ along with this program. If not, see . #define SHTC3_ADDR 0x70 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D -#define SHT31_4x_ADDR 0x44 +#define SHT31_ADDR 0x44 #define PMSA0031_ADDR 0x12 #define RCWL9620_ADDR 0x57 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index a53df11f3..20f22040c 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -29,7 +29,6 @@ class ScanI2C INA3221, MCP9808, SHT31, - SHT4X, SHTC3, LPS22HB, QMC6310, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 58d46a58d..562a94c1f 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -292,18 +292,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) break; - case SHT31_4x_ADDR: - registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2) { - type = SHT4X; - LOG_INFO("SHT4X sensor found\n"); - } else { - type = SHT31; - LOG_INFO("SHT31 sensor found\n"); - } - - break; - + SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n") SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n") SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n") @@ -368,4 +357,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1465cd084..b7bc4892b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -541,7 +541,6 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) - SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) i2cScanner.reset(); @@ -1033,4 +1032,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 62adc9a8c..93184069d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -26,7 +26,6 @@ #include "Sensor/MCP9808Sensor.h" #include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" -#include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" BMP085Sensor bmp085Sensor; @@ -37,7 +36,6 @@ MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; -SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 @@ -93,8 +91,6 @@ int32_t EnvironmentTelemetryModule::runOnce() result = lps22hbSensor.runOnce(); if (sht31Sensor.hasSensor()) result = sht31Sensor.runOnce(); - if (sht4xSensor.hasSensor()) - result = sht4xSensor.runOnce(); if (ina219Sensor.hasSensor()) result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) @@ -250,8 +246,6 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); - if (sht4xSensor.hasSensor()) - valid = sht4xSensor.getMetrics(&m); if (lps22hbSensor.hasSensor()) valid = lps22hbSensor.getMetrics(&m); if (shtc3Sensor.hasSensor()) diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp deleted file mode 100644 index d324b7fd6..000000000 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "configuration.h" - -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -#include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "SHT4XSensor.h" -#include "TelemetrySensor.h" -#include - -// macro definitions -// make sure that we use the proper definition of NO_ERROR -#ifdef NO_ERROR -#undef NO_ERROR -#endif -#define NO_ERROR 0 - -static char errorMessage[64]; -static int16_t error; - -SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {} - -int32_t SHT4XSensor::runOnce() -{ - LOG_INFO("Init sensor: %s\n", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - - uint32_t serialNumber = 0; - - sht4x.begin(*nodeTelemetrySensorsMap[sensorType].second, 0x44); - - error = sht4x.serialNumber(serialNumber); - LOG_DEBUG("serialNumber : %x\n", serialNumber); - if (error != NO_ERROR) { - LOG_DEBUG("Error trying to execute serialNumber(): "); - errorToString(error, errorMessage, sizeof errorMessage); - LOG_DEBUG(errorMessage); - status = 0; - } else { - status = 1; - } - - return initI2CSensor(); -} - -void SHT4XSensor::setup() -{ - // Set up oversampling and filter initialization -} - -bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement) -{ - float aTemperature = 0.0; - float aHumidity = 0.0; - sht4x.measureLowestPrecision(aTemperature, aHumidity); - measurement->variant.environment_metrics.temperature = aTemperature; - measurement->variant.environment_metrics.relative_humidity = aHumidity; - - return true; -} - -#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h deleted file mode 100644 index 67045eb2a..000000000 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.h +++ /dev/null @@ -1,23 +0,0 @@ -#include "configuration.h" - -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -#include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" -#include - -class SHT4XSensor : public TelemetrySensor -{ - private: - SensirionI2cSht4x sht4x; - - protected: - virtual void setup() override; - - public: - SHT4XSensor(); - virtual int32_t runOnce() override; - virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; - -#endif \ No newline at end of file From 676319a9ca6c989ba6bfa5cac0bd0cce58a483e4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 10 May 2024 04:36:20 -0500 Subject: [PATCH 0392/1377] Implement chunked SPI transfer for ch341 (#3847) This seems to fix the ch341 quirk where large packets fail to send. As it can be problematic for other radios, we gate it behind "ch341_quirk" in the config. --- bin/config-dist.yaml | 2 ++ src/main.cpp | 6 +++-- src/mesh/RadioLibInterface.cpp | 28 ++++++++++++++++++++++-- src/mesh/RadioLibInterface.h | 12 ++++++++-- src/platform/portduino/PortduinoGlue.cpp | 1 + src/platform/portduino/PortduinoGlue.h | 1 + 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 05b4a7b0a..333d6eadc 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -52,6 +52,8 @@ Lora: # TXen: x # TX and RX enable pins # RXen: x +# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341 + ### Set gpio chip to use in /dev/. Defaults to 0. ### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4 # gpiochip: 4 diff --git a/src/main.cpp b/src/main.cpp index b7bc4892b..7814ea596 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -738,7 +738,8 @@ void setup() if (settingsMap[use_sx1262]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = + new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC)); rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -752,7 +753,8 @@ void setup() } else if (settingsMap[use_rf95]) { if (!rIf) { LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = + new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC)); rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index fc1563ee3..a4ceac9f1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -25,7 +25,31 @@ void LockingArduinoHal::spiEndTransaction() #if ARCH_PORTDUINO void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in) { - spi->transfer(out, in, len); + if (busy == RADIOLIB_NC) { + spi->transfer(out, in, len); + } else { + uint16_t offset = 0; + + while (len) { + uint8_t block_size = (len < 20 ? len : 20); + spi->transfer((out != NULL ? out + offset : NULL), (in != NULL ? in + offset : NULL), block_size); + if (block_size == len) + return; + + // ensure GPIO is low + + uint32_t start = millis(); + while (digitalRead(busy)) { + if (millis() - start >= 2000) { + LOG_ERROR("GPIO mid-transfer timeout, is it connected?"); + return; + } + } + + offset += block_size; + len -= block_size; + } + } } #endif @@ -414,4 +438,4 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) // bits enableInterrupt(isrTxLevel0); } -} +} \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 62720cfc9..2c841a19e 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -21,12 +21,20 @@ class LockingArduinoHal : public ArduinoHal { public: - LockingArduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){}; + LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, RADIOLIB_PIN_TYPE _busy = RADIOLIB_NC) + : ArduinoHal(spi, spiSettings) + { +#if ARCH_PORTDUINO + busy = _busy; +#endif + }; void spiBeginTransaction() override; void spiEndTransaction() override; #if ARCH_PORTDUINO + RADIOLIB_PIN_TYPE busy; void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override; + #endif }; @@ -179,4 +187,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0; virtual void setStandby() = 0; -}; +}; \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 7c5086ac2..4077a27bc 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -168,6 +168,7 @@ void portduinoSetup() settingsMap[txen] = yamlConfig["Lora"]["TXen"].as(RADIOLIB_NC); settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC); settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); + settingsMap[ch341Quirk] = yamlConfig["Lora"]["ch341_quirk"].as(false); gpioChipName += std::to_string(settingsMap[gpiochip]); settingsStrings[spidev] = "/dev/" + yamlConfig["Lora"]["spidev"].as("spidev0.0"); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 995793a21..ca935ea3b 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -11,6 +11,7 @@ enum configNames { rxen, dio2_as_rf_switch, dio3_tcxo_voltage, + ch341Quirk, use_rf95, use_sx1280, use_sx1268, From ac22a503de4ace307d5fcf86681fd43a7fe809af Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 10 May 2024 07:13:12 -0500 Subject: [PATCH 0393/1377] Revert "Revert "Add Sensirion SHT4X sensors (#3792)" (#3845)" (#3850) This reverts commit 5d9800b7c2c7754e91db228efd7fd73320883a14. --- platformio.ini | 1 + src/configuration.h | 2 +- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 15 ++++- src/main.cpp | 3 +- .../Telemetry/EnvironmentTelemetry.cpp | 6 ++ src/modules/Telemetry/Sensor/SHT4XSensor.cpp | 63 +++++++++++++++++++ src/modules/Telemetry/Sensor/SHT4XSensor.h | 23 +++++++ 8 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SHT4XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SHT4XSensor.h diff --git a/platformio.ini b/platformio.ini index a6db1c76e..9d7c76fbf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -133,3 +133,4 @@ lib_deps = https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 + https://github.com/Sensirion/arduino-i2c-sht4x#1.1.0 diff --git a/src/configuration.h b/src/configuration.h index 493449764..0d9ee5451 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -126,7 +126,7 @@ along with this program. If not, see . #define SHTC3_ADDR 0x70 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D -#define SHT31_ADDR 0x44 +#define SHT31_4x_ADDR 0x44 #define PMSA0031_ADDR 0x12 #define RCWL9620_ADDR 0x57 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 20f22040c..a53df11f3 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -29,6 +29,7 @@ class ScanI2C INA3221, MCP9808, SHT31, + SHT4X, SHTC3, LPS22HB, QMC6310, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 562a94c1f..58d46a58d 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -292,7 +292,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port) break; - SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n") + case SHT31_4x_ADDR: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); + if (registerValue == 0x11a2) { + type = SHT4X; + LOG_INFO("SHT4X sensor found\n"); + } else { + type = SHT31; + LOG_INFO("SHT31 sensor found\n"); + } + + break; + SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n") SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n") @@ -357,4 +368,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index 7814ea596..93c6ef38f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -541,6 +541,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) i2cScanner.reset(); @@ -1034,4 +1035,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 93184069d..62adc9a8c 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -26,6 +26,7 @@ #include "Sensor/MCP9808Sensor.h" #include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" +#include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" BMP085Sensor bmp085Sensor; @@ -36,6 +37,7 @@ MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; +SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 @@ -91,6 +93,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = lps22hbSensor.runOnce(); if (sht31Sensor.hasSensor()) result = sht31Sensor.runOnce(); + if (sht4xSensor.hasSensor()) + result = sht4xSensor.runOnce(); if (ina219Sensor.hasSensor()) result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) @@ -246,6 +250,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (sht31Sensor.hasSensor()) valid = sht31Sensor.getMetrics(&m); + if (sht4xSensor.hasSensor()) + valid = sht4xSensor.getMetrics(&m); if (lps22hbSensor.hasSensor()) valid = lps22hbSensor.getMetrics(&m); if (shtc3Sensor.hasSensor()) diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp new file mode 100644 index 000000000..d324b7fd6 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp @@ -0,0 +1,63 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SHT4XSensor.h" +#include "TelemetrySensor.h" +#include + +// macro definitions +// make sure that we use the proper definition of NO_ERROR +#ifdef NO_ERROR +#undef NO_ERROR +#endif +#define NO_ERROR 0 + +static char errorMessage[64]; +static int16_t error; + +SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {} + +int32_t SHT4XSensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + uint32_t serialNumber = 0; + + sht4x.begin(*nodeTelemetrySensorsMap[sensorType].second, 0x44); + + error = sht4x.serialNumber(serialNumber); + LOG_DEBUG("serialNumber : %x\n", serialNumber); + if (error != NO_ERROR) { + LOG_DEBUG("Error trying to execute serialNumber(): "); + errorToString(error, errorMessage, sizeof errorMessage); + LOG_DEBUG(errorMessage); + status = 0; + } else { + status = 1; + } + + return initI2CSensor(); +} + +void SHT4XSensor::setup() +{ + // Set up oversampling and filter initialization +} + +bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + float aTemperature = 0.0; + float aHumidity = 0.0; + sht4x.measureLowestPrecision(aTemperature, aHumidity); + measurement->variant.environment_metrics.temperature = aTemperature; + measurement->variant.environment_metrics.relative_humidity = aHumidity; + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h new file mode 100644 index 000000000..67045eb2a --- /dev/null +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h @@ -0,0 +1,23 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class SHT4XSensor : public TelemetrySensor +{ + private: + SensirionI2cSht4x sht4x; + + protected: + virtual void setup() override; + + public: + SHT4XSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file From f06c56a51be7acf11eab4f3d4f59807e4936143e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 10 May 2024 07:14:28 -0500 Subject: [PATCH 0394/1377] Removing release build type due to huge amount of flash utilization --- arch/nrf52/nrf52.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 0669a31e8..6c6bd8738 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -3,7 +3,7 @@ platform = platformio/nordicnrf52@^10.4.0 extends = arduino_base -build_type = release +build_type = debug build_flags = ${arduino_base.build_flags} -DSERIAL_BUFFER_SIZE=1024 From 69d765622f74d8415eb0fe4b794ebf621a0ae384 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 08:16:49 -0500 Subject: [PATCH 0395/1377] [create-pull-request] automated change (#3846) Co-authored-by: thebentern --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index a7a7fb1bd..69761c0ac 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 9 +build = 10 From 58484d7fe5b6fe7e52a866670ef717f95b03a980 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 11 May 2024 02:10:23 +0200 Subject: [PATCH 0396/1377] .github: add Linux Native and other as platform to Feature Request --- .github/ISSUE_TEMPLATE/feature.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 2b6ffce0a..91f52860e 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -16,6 +16,8 @@ body: options: - NRF52 - ESP32 + - Linux Native + - other validations: required: true - type: textarea From 86b14793de79060a450a9571230942f3a7fa721a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 11 May 2024 09:46:39 +0200 Subject: [PATCH 0397/1377] add optional define DEBUG_MUTE This shaves roughly 60k from firmware builds by not including the Logging Ressource strings. Define in variant.h or architecture.h Stats from T-ECHO compiles: Before: Flash: [======== ] 81.5% (used 664700 bytes from 815104 bytes) After: Flash: [======= ] 74.5% (used 606924 bytes from 815104 bytes) --- src/DebugConfiguration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index f0686b811..ca908197e 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -36,7 +36,7 @@ #define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) #define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__) #define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__) #define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__) From 38347fa6db4a51338d8573a9aba028689d7e0d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 11 May 2024 10:03:13 +0200 Subject: [PATCH 0398/1377] exclude serial module for T-Echo, saves 3000 bytes --- variants/t-echo/variant.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 1c263a61a..a1166a19c 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -206,6 +206,9 @@ External serial flash WP25R1635FZUIL0 // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER +// T-Echo does not have a free serial port for this module +#define MESHTASTIC_EXCLUDE_SERIAL 1 + // Battery // The battery sense is hooked to pin A0 (4) // it is defined in the anlaolgue pin section of this file From 3b6ce29cca8362b50ad349a37761171efde6215b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 11 May 2024 10:05:03 +0200 Subject: [PATCH 0399/1377] add the now common RP2040 --- .github/ISSUE_TEMPLATE/feature.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 91f52860e..b027a36cc 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -16,6 +16,7 @@ body: options: - NRF52 - ESP32 + - RP2040 - Linux Native - other validations: From d8d831b27aee53e67cedb11cef177d37c2c4af2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 11 May 2024 14:19:53 +0200 Subject: [PATCH 0400/1377] Revert "exclude serial module for T-Echo, saves 3000 bytes" This reverts commit 38347fa6db4a51338d8573a9aba028689d7e0d54. --- variants/t-echo/variant.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index a1166a19c..1c263a61a 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -206,9 +206,6 @@ External serial flash WP25R1635FZUIL0 // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER -// T-Echo does not have a free serial port for this module -#define MESHTASTIC_EXCLUDE_SERIAL 1 - // Battery // The battery sense is hooked to pin A0 (4) // it is defined in the anlaolgue pin section of this file From 96b5bd2fd0d432c0c2412533c82cd47e10f3a4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 11 May 2024 15:20:11 +0200 Subject: [PATCH 0401/1377] unphone has a display, don't default BLE PIN to 123456 (#3865) fixes #3822 --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8cbeb8dd4..b79911a3e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -273,7 +273,7 @@ void NodeDB::installDefaultConfig() // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) bool hasScreen = true; #elif ARCH_PORTDUINO bool hasScreen = false; From 5de0c71a3e20ae3b8bc66563dd81a9bedc42bed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 12 May 2024 02:50:54 +0200 Subject: [PATCH 0402/1377] add bobricius tracksenger variants (#3866) --- variants/tracksenger/internal/pins_arduino.h | 80 +++++++++++++ variants/tracksenger/internal/variant.h | 90 +++++++++++++++ variants/tracksenger/lcd/pins_arduino.h | 80 +++++++++++++ variants/tracksenger/lcd/variant.h | 114 +++++++++++++++++++ variants/tracksenger/oled/pins_arduino.h | 80 +++++++++++++ variants/tracksenger/oled/variant.h | 92 +++++++++++++++ variants/tracksenger/platformio.ini | 40 +++++++ 7 files changed, 576 insertions(+) create mode 100644 variants/tracksenger/internal/pins_arduino.h create mode 100644 variants/tracksenger/internal/variant.h create mode 100644 variants/tracksenger/lcd/pins_arduino.h create mode 100644 variants/tracksenger/lcd/variant.h create mode 100644 variants/tracksenger/oled/pins_arduino.h create mode 100644 variants/tracksenger/oled/variant.h create mode 100644 variants/tracksenger/platformio.ini diff --git a/variants/tracksenger/internal/pins_arduino.h b/variants/tracksenger/internal/pins_arduino.h new file mode 100644 index 000000000..5c0b529b0 --- /dev/null +++ b/variants/tracksenger/internal/pins_arduino.h @@ -0,0 +1,80 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define WIFI_LoRa_32_V3 true +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 45; +static const uint8_t SCL = 46; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/tracksenger/internal/variant.h b/variants/tracksenger/internal/variant.h new file mode 100644 index 000000000..e63cecd7b --- /dev/null +++ b/variants/tracksenger/internal/variant.h @@ -0,0 +1,90 @@ +#define LED_PIN 18 + +#define HELTEC_TRACKER_V1_X + +// TRACKSENGER builtin LCD + +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + +// ST7735S TFT LCD +#define ST7735S 1 // there are different (sub-)versions of ST7735 +#define ST7735_CS 38 +#define ST7735_RS 40 // DC +#define ST7735_SDA 42 // MOSI +#define ST7735_SCK 41 +#define ST7735_RESET 39 +#define ST7735_MISO -1 +#define ST7735_BUSY -1 +#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +#define ST7735_SPI_HOST SPI3_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define SCREEN_ROTATE +#define TFT_HEIGHT DISPLAY_WIDTH +#define TFT_WIDTH DISPLAY_HEIGHT +#define TFT_OFFSET_X 26 +#define TFT_OFFSET_Y -1 +#define SCREEN_TRANSITION_FRAMERATE 3 // fps +#define DISPLAY_FORCE_SMALL_FONTS + +#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost +#define BUTTON_PIN 0 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 +#define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 +#define ADC_CTRL_ENABLED HIGH + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 33 +#define GPS_TX_PIN 34 +#define PIN_GPS_RESET 35 +#define PIN_GPS_PPS 36 + +#define GPS_RESET_MODE LOW +#define GPS_UC6580 + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Picomputer gets a white on black display +#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) + +// keyboard changes + +#define PIN_BUZZER 43 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +#define INPUTBROKER_MATRIX_TYPE 1 + +#define KEYS_COLS \ + { \ + 44, 45, 46, 4, 5, 6 \ + } +#define KEYS_ROWS \ + { \ + 26, 37, 17, 16, 15, 7 \ + } +// #end keyboard \ No newline at end of file diff --git a/variants/tracksenger/lcd/pins_arduino.h b/variants/tracksenger/lcd/pins_arduino.h new file mode 100644 index 000000000..5c0b529b0 --- /dev/null +++ b/variants/tracksenger/lcd/pins_arduino.h @@ -0,0 +1,80 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define WIFI_LoRa_32_V3 true +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 45; +static const uint8_t SCL = 46; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/tracksenger/lcd/variant.h b/variants/tracksenger/lcd/variant.h new file mode 100644 index 000000000..0f3423d52 --- /dev/null +++ b/variants/tracksenger/lcd/variant.h @@ -0,0 +1,114 @@ +#define LED_PIN 18 + +#define HELTEC_TRACKER_V1_X + +// TRACKSENGER 2.8" IPS 320x240 + +// I2C +// #define I2C_SDA 42 +// #define I2C_SCL 41 +// #define HAS_SCREEN 1 +// #define USE_SSD1306 + +// Default SPI1 will be mapped to the display +#define ST7789_SDA 42 +#define ST7789_SCK 41 +#define ST7789_CS 38 +#define ST7789_RS 40 +#define ST7789_BL 21 +// P#define ST7735_BL_V05 21 /* V1.1 PCB marking */ + +#define ST7789_RESET -1 +#define ST7789_MISO -1 +#define ST7789_BUSY -1 +#define ST7789_SPI_HOST SPI3_HOST +#define ST7789_BACKLIGHT_EN 21 +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 320 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 +#define SCREEN_ROTATE + +// ST7735S TFT LCD +// #define ST7735S 1 // there are different (sub-)versions of ST7735 +// #define ST7735_CS 38 +// #define ST7735_RS 40 // DC +// #define ST7735_SDA 42 // MOSI +// #define ST7735_SCK 41 +// #define ST7735_RESET 39 +// #define ST7735_MISO -1 +// #define ST7735_BUSY -1 +#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +// #define ST7735_SPI_HOST SPI3_HOST +// #define SPI_FREQUENCY 40000000 +// #define SPI_READ_FREQUENCY 16000000 +// #define SCREEN_ROTATE +// #define TFT_HEIGHT DISPLAY_WIDTH +// #define TFT_WIDTH DISPLAY_HEIGHT +// #define TFT_OFFSET_X 26 +// #define TFT_OFFSET_Y -1 +#define SCREEN_TRANSITION_FRAMERATE 3 // fps +// #define DISPLAY_FORCE_SMALL_FONTS + +#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost +#define BUTTON_PIN 0 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 +#define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 +#define ADC_CTRL_ENABLED HIGH + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 33 +#define GPS_TX_PIN 34 +#define PIN_GPS_RESET 35 +#define PIN_GPS_PPS 36 + +#define GPS_RESET_MODE LOW +#define GPS_UC6580 + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Picomputer gets a white on black display +#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) + +// keyboard changes + +#define PIN_BUZZER 43 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +#define INPUTBROKER_MATRIX_TYPE 1 + +#define KEYS_COLS \ + { \ + 44, 45, 46, 4, 5, 6 \ + } +#define KEYS_ROWS \ + { \ + 26, 37, 17, 16, 15, 7 \ + } +// #end keyboard \ No newline at end of file diff --git a/variants/tracksenger/oled/pins_arduino.h b/variants/tracksenger/oled/pins_arduino.h new file mode 100644 index 000000000..5c0b529b0 --- /dev/null +++ b/variants/tracksenger/oled/pins_arduino.h @@ -0,0 +1,80 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define WIFI_LoRa_32_V3 true +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 45; +static const uint8_t SCL = 46; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/tracksenger/oled/variant.h b/variants/tracksenger/oled/variant.h new file mode 100644 index 000000000..d6bacf139 --- /dev/null +++ b/variants/tracksenger/oled/variant.h @@ -0,0 +1,92 @@ +#define LED_PIN 18 + +#define HELTEC_TRACKER_V1_X + +// TRACKSENGER 2.42" I2C OLED + +// I2C +#define I2C_SDA 42 +#define I2C_SCL 41 +#define HAS_SCREEN 1 +#define USE_SSD1306 + +// ST7735S TFT LCD +// #define ST7735S 1 // there are different (sub-)versions of ST7735 +// #define ST7735_CS 38 +// #define ST7735_RS 40 // DC +// #define ST7735_SDA 42 // MOSI +// #define ST7735_SCK 41 +// #define ST7735_RESET 39 +// #define ST7735_MISO -1 +// #define ST7735_BUSY -1 +#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +// #define ST7735_SPI_HOST SPI3_HOST +// #define SPI_FREQUENCY 40000000 +// #define SPI_READ_FREQUENCY 16000000 +// #define SCREEN_ROTATE +// #define TFT_HEIGHT DISPLAY_WIDTH +// #define TFT_WIDTH DISPLAY_HEIGHT +// #define TFT_OFFSET_X 26 +// #define TFT_OFFSET_Y -1 +#define SCREEN_TRANSITION_FRAMERATE 3 // fps +// #define DISPLAY_FORCE_SMALL_FONTS + +#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost +#define BUTTON_PIN 0 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 +#define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 +#define ADC_CTRL_ENABLED HIGH + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 33 +#define GPS_TX_PIN 34 +#define PIN_GPS_RESET 35 +#define PIN_GPS_PPS 36 + +#define GPS_RESET_MODE LOW +#define GPS_UC6580 + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Picomputer gets a white on black display +#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) + +// keyboard changes + +#define PIN_BUZZER 43 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +#define INPUTBROKER_MATRIX_TYPE 1 + +#define KEYS_COLS \ + { \ + 44, 45, 46, 4, 5, 6 \ + } +#define KEYS_ROWS \ + { \ + 26, 37, 17, 16, 15, 7 \ + } +// #end keyboard \ No newline at end of file diff --git a/variants/tracksenger/platformio.ini b/variants/tracksenger/platformio.ini new file mode 100644 index 000000000..d3e31264f --- /dev/null +++ b/variants/tracksenger/platformio.ini @@ -0,0 +1,40 @@ +[env:tracksenger] +extends = esp32s3_base +board = heltec_wireless_tracker +upload_protocol = esp-builtin + +build_flags = + ${esp32s3_base.build_flags} -I variants/tracksenger/internal + -D HELTEC_TRACKER_V1_1 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output + +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.1.8 + +[env:tracksenger-lcd] +extends = esp32s3_base +board = heltec_wireless_tracker +upload_protocol = esp-builtin + +build_flags = + ${esp32s3_base.build_flags} -I variants/tracksenger/lcd + -D HELTEC_TRACKER_V1_1 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output + +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.1.8 + +[env:tracksenger-oled] +extends = esp32s3_base +board = heltec_wireless_tracker +upload_protocol = esp-builtin + +build_flags = + ${esp32s3_base.build_flags} -I variants/tracksenger/oled + -D HELTEC_TRACKER_V1_1 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output From 859fd7c25110c038bcb41aa7ef01d6b603b8ab9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 12 May 2024 22:43:47 +0200 Subject: [PATCH 0403/1377] Generate the build matrix from the variant files (#3870) --- .github/workflows/main_matrix.yml | 110 ++++++------------ bin/generate_ci_matrix.py | 7 +- variants/CDEBYTE_EoRa-S3/platformio.ini | 3 +- .../platformio.ini | 3 +- variants/betafpv_900_tx_nano/platformio.ini | 3 +- variants/chatter2/platformio.ini | 1 - variants/diy/platformio.ini | 4 +- variants/feather_diy/platformio.ini | 1 - variants/heltec_esp32c3/platformio.ini | 1 - variants/heltec_v2.1/platformio.ini | 1 - variants/heltec_v2/platformio.ini | 1 - variants/heltec_v3/platformio.ini | 1 + variants/m5stack_core/platformio.ini | 3 +- variants/m5stack_coreink/platformio.ini | 4 +- variants/rak10701/platformio.ini | 1 + variants/rak11200/platformio.ini | 4 +- variants/rak4631/platformio.ini | 3 +- variants/rpipico-slowclock/platformio.ini | 3 +- variants/rpipicow/platformio.ini | 1 - variants/station-g2/platformio.ini | 1 + variants/t-deck/platformio.ini | 1 + variants/t-echo/platformio.ini | 3 +- variants/t-watch-s3/platformio.ini | 1 + variants/tbeam-s3-core/platformio.ini | 1 + variants/tbeam/platformio.ini | 3 +- variants/tbeam_v07/platformio.ini | 1 - variants/tlora_t3s3_v1/platformio.ini | 1 + variants/tlora_v1_3/platformio.ini | 1 - variants/tlora_v2/platformio.ini | 1 - variants/tlora_v2_1_16/platformio.ini | 1 + variants/unphone/platformio.ini | 1 - variants/wiphone/platformio.ini | 4 +- 32 files changed, 71 insertions(+), 104 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 9ca0764b5..1d3d16c0c 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -8,7 +8,7 @@ on: branches: [master, develop] paths-ignore: - "**.md" - - "version.properties" + - version.properties # Note: This is different from "pull_request". Need to specify ref when doing checkouts. pull_request_target: @@ -20,25 +20,34 @@ on: workflow_dispatch: jobs: - check: + setup: strategy: fail-fast: false matrix: - include: - - board: rak11200 - - board: tlora-v2-1-1_6 - - board: tbeam - - board: heltec-v2_1 - - board: meshtastic-diy-v1 - - board: rak4631 - - board: t-echo - - board: station-g2 - - board: m5stack-coreink - - board: tbeam-s3-core - - board: tlora-t3s3-v1 - - board: t-watch-s3 - - board: t-deck - #- board: rak11310 + arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, check] + runs-on: ubuntu-latest + steps: + - id: checkout + uses: actions/checkout@v3 + name: Checkout base + - id: jsonStep + run: | + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) + echo "$TARGETS" + echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + outputs: + esp32: ${{ steps.jsonStep.outputs.esp32 }} + esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} + esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} + nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} + rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + check: ${{ steps.jsonStep.outputs.check }} + + check: + needs: setup + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest steps: @@ -55,93 +64,46 @@ jobs: run: bin/check-all.sh ${{ matrix.board }} build-esp32: + needs: setup strategy: fail-fast: false - matrix: - include: - - board: rak11200 - - board: tlora-v2 - - board: tlora-v1 - - board: tlora_v1_3 - - board: tlora-v2-1-1_6 - - board: tlora-v2-1-1_6-tcxo - - board: tlora-v2-1-1_8 - - board: tbeam - - board: heltec-v2_0 - - board: heltec-v2_1 - - board: tbeam0_7 - - board: meshtastic-diy-v1 - - board: hydra - - board: meshtastic-dr-dev - - board: nano-g1 - - board: station-g1 - - board: m5stack-core - - board: m5stack-coreink - - board: nano-g1-explorer - - board: chatter2 + matrix: ${{ fromJson(needs.setup.outputs.esp32) }} uses: ./.github/workflows/build_esp32.yml with: board: ${{ matrix.board }} build-esp32-s3: + needs: setup strategy: fail-fast: false - matrix: - include: - - board: heltec-v3 - - board: heltec-wsl-v3 - - board: heltec-wireless-tracker - - board: heltec-wireless-tracker-V1-0 - - board: heltec-wireless-paper-v1_0 - - board: heltec-wireless-paper #v1.1 - - board: tbeam-s3-core - - board: tlora-t3s3-v1 - - board: t-watch-s3 - - board: t-deck - - board: picomputer-s3 - - board: station-g2 - - board: unphone + matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} uses: ./.github/workflows/build_esp32_s3.yml with: board: ${{ matrix.board }} build-esp32-c3: + needs: setup strategy: fail-fast: false - matrix: - include: - - board: heltec-ht62-esp32c3-sx1262 + matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} uses: ./.github/workflows/build_esp32_c3.yml with: board: ${{ matrix.board }} build-nrf52: + needs: setup strategy: fail-fast: false - matrix: - include: - - board: rak4631 - - board: rak4631_eink - - board: monteops_hw1 - - board: t-echo - - board: canaryone - - board: pca10059_diy_eink - - board: feather_diy - - board: nano-g2-ultra + matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} uses: ./.github/workflows/build_nrf52.yml with: board: ${{ matrix.board }} build-rpi2040: + needs: setup strategy: fail-fast: false - matrix: - include: - - board: pico - - board: picow - - board: rak11310 - - board: senselora_rp2040 - - board: rp2040-lora + matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} uses: ./.github/workflows/build_rpi2040.yml with: board: ${{ matrix.board }} diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index 2501e83c1..46398dd59 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -"""Generate the CI matrix""" +"""Generate the CI matrix.""" import configparser import json @@ -34,5 +34,10 @@ for subdir, dirs, files in os.walk(rootdir): outlist.append(section) else: outlist.append(section) + if "board_check" in config[config[c].name]: + if (config[config[c].name]["board_check"] == "true") & ( + "check" in options + ): + outlist.append(section) print(json.dumps(outlist)) diff --git a/variants/CDEBYTE_EoRa-S3/platformio.ini b/variants/CDEBYTE_EoRa-S3/platformio.ini index 1ff54de88..88845a50c 100644 --- a/variants/CDEBYTE_EoRa-S3/platformio.ini +++ b/variants/CDEBYTE_EoRa-S3/platformio.ini @@ -1,8 +1,9 @@ [env:CDEBYTE_EoRa-S3] extends = esp32s3_base board = CDEBYTE_EoRa-S3 +board_level = extra build_flags = ${esp32s3_base.build_flags} -D CDEBYTE_EORA_S3 -I variants/CDEBYTE_EoRa-S3 - -D GPS_POWER_TOGGLE + -D GPS_POWER_TOGGLE \ No newline at end of file diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini index b1608770e..2d14f1ca1 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -1,7 +1,6 @@ [env:pca10059_diy_eink] extends = nrf52840_base board = nordic_pca10059 -board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DEINK_DISPLAY_MODEL=GxEPD2_420_M01 @@ -11,4 +10,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/Dongle_nRF52840- lib_deps = ${nrf52840_base.lib_deps} zinggjm/GxEPD2@^1.4.9 -debug_tool = jlink +debug_tool = jlink \ No newline at end of file diff --git a/variants/betafpv_900_tx_nano/platformio.ini b/variants/betafpv_900_tx_nano/platformio.ini index 68e1a469b..3bea16f6b 100644 --- a/variants/betafpv_900_tx_nano/platformio.ini +++ b/variants/betafpv_900_tx_nano/platformio.ini @@ -1,6 +1,7 @@ [env:betafpv_900_tx_nano] extends = esp32_base board = esp32doit-devkit-v1 +board_level = extra build_flags = ${esp32_base.build_flags} -D BETAFPV_900_TX_NANO @@ -13,4 +14,4 @@ upload_protocol = esptool ;upload_port = /dev/ttyUSB0 upload_speed = 460800 lib_deps = - ${esp32_base.lib_deps} + ${esp32_base.lib_deps} \ No newline at end of file diff --git a/variants/chatter2/platformio.ini b/variants/chatter2/platformio.ini index 0856debfc..1f086cf07 100644 --- a/variants/chatter2/platformio.ini +++ b/variants/chatter2/platformio.ini @@ -2,7 +2,6 @@ [env:chatter2] extends = esp32_base board = esp32doit-devkit-v1 -board_level = extra build_flags = ${esp32_base.build_flags} -D CHATTER_2 diff --git a/variants/diy/platformio.ini b/variants/diy/platformio.ini index 5fb0f6421..94d59553d 100644 --- a/variants/diy/platformio.ini +++ b/variants/diy/platformio.ini @@ -2,7 +2,7 @@ [env:meshtastic-diy-v1] extends = esp32_base board = esp32doit-devkit-v1 -board_level = extra +board_check = true build_flags = ${esp32_base.build_flags} -D DIY_V1 @@ -26,7 +26,6 @@ build_flags = [env:meshtastic-dr-dev] extends = esp32_base board = esp32doit-devkit-v1 -board_level = extra board_upload.maximum_size = 4194304 board_upload.maximum_ram_size = 532480 build_flags = @@ -39,7 +38,6 @@ build_flags = [env:hydra] extends = esp32_base board = esp32doit-devkit-v1 -board_level = extra build_flags = ${esp32_base.build_flags} -D DIY_V1 diff --git a/variants/feather_diy/platformio.ini b/variants/feather_diy/platformio.ini index 924f9098d..47c864b8e 100644 --- a/variants/feather_diy/platformio.ini +++ b/variants/feather_diy/platformio.ini @@ -2,7 +2,6 @@ [env:feather_diy] extends = nrf52840_base board = adafruit_feather_nrf52840 -board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/feather_diy -Dfeather_diy -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/feather_diy> diff --git a/variants/heltec_esp32c3/platformio.ini b/variants/heltec_esp32c3/platformio.ini index c9c80213e..6fe5c3c69 100644 --- a/variants/heltec_esp32c3/platformio.ini +++ b/variants/heltec_esp32c3/platformio.ini @@ -1,7 +1,6 @@ [env:heltec-ht62-esp32c3-sx1262] extends = esp32c3_base board = esp32-c3-devkitm-1 -board_level = extra build_flags = ${esp32_base.build_flags} -D HELTEC_HT62 diff --git a/variants/heltec_v2.1/platformio.ini b/variants/heltec_v2.1/platformio.ini index 7d4daecc9..5aa04fc58 100644 --- a/variants/heltec_v2.1/platformio.ini +++ b/variants/heltec_v2.1/platformio.ini @@ -2,7 +2,6 @@ ;build_type = debug ; to make it possible to step through our jtag debugger extends = esp32_base board = heltec_wifi_lora_32_V2 -board_level = extra build_flags = ${esp32_base.build_flags} -D HELTEC_V2_1 -I variants/heltec_v2.1 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. \ No newline at end of file diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 3289f4e68..cee1537d0 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -2,6 +2,5 @@ ;build_type = debug ; to make it possible to step through our jtag debugger extends = esp32_base board = heltec_wifi_lora_32_V2 -board_level = extra build_flags = ${esp32_base.build_flags} -D HELTEC_V2_0 -I variants/heltec_v2 \ No newline at end of file diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 58ee0b5ba..e8f73e1ef 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -1,6 +1,7 @@ [env:heltec-v3] extends = esp32s3_base board = heltec_wifi_lora_32_V3 +board_check = true # Temporary until espressif creates a release with this new target build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/heltec_v3 diff --git a/variants/m5stack_core/platformio.ini b/variants/m5stack_core/platformio.ini index 84fb9f251..95f5aea9f 100644 --- a/variants/m5stack_core/platformio.ini +++ b/variants/m5stack_core/platformio.ini @@ -1,7 +1,6 @@ [env:m5stack-core] extends = esp32_base board = m5stack-core-esp32 -board_level = extra monitor_filters = esp32_exception_decoder build_src_filter = ${esp32_base.build_src_filter} @@ -26,4 +25,4 @@ lib_ignore = m5stack-core lib_deps = ${esp32_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file diff --git a/variants/m5stack_coreink/platformio.ini b/variants/m5stack_coreink/platformio.ini index dfb078a0a..c0c8bd30e 100644 --- a/variants/m5stack_coreink/platformio.ini +++ b/variants/m5stack_coreink/platformio.ini @@ -1,7 +1,7 @@ [env:m5stack-coreink] extends = esp32_base board = m5stack-coreink -board_level = extra +board_check = true build_src_filter = ${esp32_base.build_src_filter} build_flags = @@ -24,4 +24,4 @@ lib_ignore = monitor_filters = esp32_exception_decoder board_build.f_cpu = 240000000L upload_protocol = esptool -upload_port = /dev/ttyACM0 +upload_port = /dev/ttyACM0 \ No newline at end of file diff --git a/variants/rak10701/platformio.ini b/variants/rak10701/platformio.ini index 37f785e84..ae43b1906 100644 --- a/variants/rak10701/platformio.ini +++ b/variants/rak10701/platformio.ini @@ -1,6 +1,7 @@ ; The very slick RAK wireless RAK10701 Field Tester device. Note you will have to flash to Arduino bootloader to use this firmware. Be aware touch is not currently working. [env:rak10701] extends = nrf52840_base +board_level = extra board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak10701 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" diff --git a/variants/rak11200/platformio.ini b/variants/rak11200/platformio.ini index f653adeb2..eddc3458e 100644 --- a/variants/rak11200/platformio.ini +++ b/variants/rak11200/platformio.ini @@ -1,7 +1,7 @@ [env:rak11200] extends = esp32_base -board_level = extra board = wiscore_rak11200 +board_check = true build_flags = ${esp32_base.build_flags} -D RAK_11200 -I variants/rak11200 -upload_speed = 115200 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b1bc2d9b5..115e96967 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -2,6 +2,7 @@ [env:rak4631] extends = nrf52840_base board = wiscore_rak4631 +board_check = true build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. @@ -17,4 +18,4 @@ lib_deps = rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/rpipico-slowclock/platformio.ini b/variants/rpipico-slowclock/platformio.ini index eec76ca0f..0b94eb9c6 100644 --- a/variants/rpipico-slowclock/platformio.ini +++ b/variants/rpipico-slowclock/platformio.ini @@ -1,6 +1,7 @@ [env:pico_slowclock] extends = rp2040_base board = rpipico +board_level = extra upload_protocol = jlink # debug settings for external openocd with RP2040 support (custom build) debug_tool = custom @@ -25,4 +26,4 @@ lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} -g - -DNO_USB + -DNO_USB \ No newline at end of file diff --git a/variants/rpipicow/platformio.ini b/variants/rpipicow/platformio.ini index 29b5c8bcb..91ec964d9 100644 --- a/variants/rpipicow/platformio.ini +++ b/variants/rpipicow/platformio.ini @@ -1,7 +1,6 @@ [env:picow] extends = rp2040_base board = rpipicow -board_level = extra upload_protocol = picotool # add our variants files to the include and src paths diff --git a/variants/station-g2/platformio.ini b/variants/station-g2/platformio.ini index b39136684..e96c0ab88 100755 --- a/variants/station-g2/platformio.ini +++ b/variants/station-g2/platformio.ini @@ -1,6 +1,7 @@ [env:station-g2] extends = esp32s3_base board = station-g2 +board_check = true board_build.mcu = esp32s3 upload_protocol = esptool ;upload_port = /dev/ttyACM0 diff --git a/variants/t-deck/platformio.ini b/variants/t-deck/platformio.ini index 593fdae5e..a63ff57a7 100644 --- a/variants/t-deck/platformio.ini +++ b/variants/t-deck/platformio.ini @@ -2,6 +2,7 @@ [env:t-deck] extends = esp32s3_base board = t-deck +board_check = true upload_protocol = esptool #upload_port = COM29 diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 5bd56598b..aa8177b33 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -2,6 +2,7 @@ [env:t-echo] extends = nrf52840_base board = t-echo +board_check = true debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. @@ -23,4 +24,4 @@ lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a lewisxhe/PCF8563_Library@^1.0.1 -;upload_protocol = fs +;upload_protocol = fs \ No newline at end of file diff --git a/variants/t-watch-s3/platformio.ini b/variants/t-watch-s3/platformio.ini index 5d5904b30..1f5fc278b 100644 --- a/variants/t-watch-s3/platformio.ini +++ b/variants/t-watch-s3/platformio.ini @@ -2,6 +2,7 @@ [env:t-watch-s3] extends = esp32s3_base board = t-watch-s3 +board_check = true upload_protocol = esptool build_flags = ${esp32_base.build_flags} diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/tbeam-s3-core/platformio.ini index 99d315a69..e50d506b9 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/tbeam-s3-core/platformio.ini @@ -2,6 +2,7 @@ [env:tbeam-s3-core] extends = esp32s3_base board = tbeam-s3-core +board_check = true lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/tbeam/platformio.ini b/variants/tbeam/platformio.ini index 76a03d126..85e66c2dd 100644 --- a/variants/tbeam/platformio.ini +++ b/variants/tbeam/platformio.ini @@ -2,9 +2,10 @@ [env:tbeam] extends = esp32_base board = ttgo-t-beam +board_check = true lib_deps = ${esp32_base.lib_deps} build_flags = ${esp32_base.build_flags} -D TBEAM_V10 -I variants/tbeam -DGPS_POWER_TOGGLE ; comment this line to disable double press function on the user button to turn off gps entirely. -upload_speed = 921600 +upload_speed = 921600 \ No newline at end of file diff --git a/variants/tbeam_v07/platformio.ini b/variants/tbeam_v07/platformio.ini index 5428b0e2a..105d65912 100644 --- a/variants/tbeam_v07/platformio.ini +++ b/variants/tbeam_v07/platformio.ini @@ -2,6 +2,5 @@ [env:tbeam0_7] extends = esp32_base board = ttgo-t-beam -board_level = extra build_flags = ${esp32_base.build_flags} -D TBEAM_V07 -I variants/tbeam_v07 \ No newline at end of file diff --git a/variants/tlora_t3s3_v1/platformio.ini b/variants/tlora_t3s3_v1/platformio.ini index fd3d393d9..002b2f224 100644 --- a/variants/tlora_t3s3_v1/platformio.ini +++ b/variants/tlora_t3s3_v1/platformio.ini @@ -1,6 +1,7 @@ [env:tlora-t3s3-v1] extends = esp32s3_base board = tlora-t3s3-v1 +board_check = true upload_protocol = esp-builtin build_flags = diff --git a/variants/tlora_v1_3/platformio.ini b/variants/tlora_v1_3/platformio.ini index 739f76268..9d9f41a7c 100644 --- a/variants/tlora_v1_3/platformio.ini +++ b/variants/tlora_v1_3/platformio.ini @@ -1,6 +1,5 @@ [env:tlora_v1_3] extends = esp32_base -board_level = extra board = ttgo-lora32-v1 build_flags = ${esp32_base.build_flags} -D TLORA_V1_3 -I variants/tlora_v1_3 \ No newline at end of file diff --git a/variants/tlora_v2/platformio.ini b/variants/tlora_v2/platformio.ini index 25ae3a360..8710068af 100644 --- a/variants/tlora_v2/platformio.ini +++ b/variants/tlora_v2/platformio.ini @@ -1,6 +1,5 @@ [env:tlora-v2] extends = esp32_base board = ttgo-lora32-v1 -board_level = extra build_flags = ${esp32_base.build_flags} -D TLORA_V2 -I variants/tlora_v2 \ No newline at end of file diff --git a/variants/tlora_v2_1_16/platformio.ini b/variants/tlora_v2_1_16/platformio.ini index 167f6c37c..351f71676 100644 --- a/variants/tlora_v2_1_16/platformio.ini +++ b/variants/tlora_v2_1_16/platformio.ini @@ -1,6 +1,7 @@ [env:tlora-v2-1-1_6] extends = esp32_base board = ttgo-lora32-v21 +board_check = true build_flags = ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/tlora_v2_1_16 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. \ No newline at end of file diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index e4a92fe4c..f66b5db49 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -3,7 +3,6 @@ [env:unphone] extends = esp32s3_base -board_level = extra board = unphone9 upload_speed = 921600 monitor_speed = 115200 diff --git a/variants/wiphone/platformio.ini b/variants/wiphone/platformio.ini index 10c0de55e..0218f8930 100644 --- a/variants/wiphone/platformio.ini +++ b/variants/wiphone/platformio.ini @@ -1,6 +1,7 @@ [env:wiphone] extends = esp32_base board = wiphone +board_level = extra monitor_filters = esp32_exception_decoder board_build.partitions = default_16MB.csv build_flags = @@ -9,5 +10,4 @@ lib_deps = ${esp32_base.lib_deps} lovyan03/LovyanGFX@^1.1.8 sparkfun/SX1509 IO Expander@^3.0.5 - pololu/APA102@^3.0.0 - \ No newline at end of file + pololu/APA102@^3.0.0 \ No newline at end of file From 4d8c98c23db3724347bf6b2283ed3cd66e266cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 13 May 2024 10:47:40 +0200 Subject: [PATCH 0404/1377] Update CI runner versions from Node 16 to 20. (#3872) --- .github/actions/setup-base/action.yml | 6 +-- .github/workflows/build_esp32.yml | 9 ++-- .github/workflows/build_esp32_c3.yml | 9 ++-- .github/workflows/build_esp32_s3.yml | 9 ++-- .github/workflows/build_nrf52.yml | 5 ++- .github/workflows/build_raspbian.yml | 5 ++- .github/workflows/build_rpi2040.yml | 5 ++- .github/workflows/main_matrix.yml | 49 ++++++++++++--------- .github/workflows/nightly.yml | 2 +- .github/workflows/package_raspbian.yml | 10 +++-- .github/workflows/sec_sast_flawfinder.yml | 7 +-- .github/workflows/sec_sast_semgrep_cron.yml | 7 +-- .github/workflows/sec_sast_semgrep_pull.yml | 2 +- .github/workflows/trunk-check.yml | 2 +- .github/workflows/update_protobufs.yml | 2 +- 15 files changed, 72 insertions(+), 57 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 7b97e1753..7e57f6a31 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -5,7 +5,7 @@ runs: using: "composite" steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" ref: ${{github.event.pull_request.head.ref}} @@ -30,12 +30,12 @@ runs: sudo apt-get install -y libyaml-cpp-dev - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.x - name: Cache python libs - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-pip # needed in if test with: path: ~/.cache/pip diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 31f0dd5a0..4cbb4c7a4 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -11,13 +11,13 @@ jobs: build-esp32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web file: build.tar @@ -41,7 +41,7 @@ jobs: run: bin/build-esp32.sh ${{ inputs.board }} - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/firmware-ota file: firmware.bin @@ -54,9 +54,10 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/*.bin release/*.elf diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index a30cf33f1..07727d711 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -13,13 +13,13 @@ jobs: build-esp32-c3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web file: build.tar @@ -41,7 +41,7 @@ jobs: run: bin/build-esp32.sh ${{ inputs.board }} - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/firmware-ota file: firmware-c3.bin @@ -54,9 +54,10 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/*.bin release/*.elf diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index f603a6a31..10773833e 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -11,13 +11,13 @@ jobs: build-esp32-s3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web file: build.tar @@ -39,7 +39,7 @@ jobs: run: bin/build-esp32.sh ${{ inputs.board }} - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/firmware-ota file: firmware-s3.bin @@ -52,9 +52,10 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/*.bin release/*.elf diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index 33ee4d00c..eb1779963 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -11,7 +11,7 @@ jobs: build-nrf52: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -24,9 +24,10 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/*.uf2 release/*.elf diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 7a25892bc..cef61bb21 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -11,7 +11,7 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} @@ -37,9 +37,10 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-raspbian-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/meshtasticd_linux_aarch64 bin/config-dist.yaml diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index 76ca2c20e..6e258fe2a 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -11,7 +11,7 @@ jobs: build-rpi2040: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -24,9 +24,10 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/*.uf2 release/*.elf diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 1d3d16c0c..095981087 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - id: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 name: Checkout base - id: jsonStep run: | @@ -51,14 +51,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base - name: Trunk Check if: ${{ github.event_name != 'workflow_dispatch' }} - uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b + uses: trunk-io/trunk-action@v1 - name: Check ${{ matrix.board }} run: bin/check-all.sh ${{ matrix.board }} @@ -120,7 +120,7 @@ jobs: build-native: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build base id: base uses: ./.github/actions/setup-base @@ -143,16 +143,17 @@ jobs: id: version - name: Store binaries as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-native-${{ steps.version.outputs.version }}.zip + overwrite: true path: | release/device-*.sh release/device-*.bat - name: Docker login if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: meshtastic password: ${{ secrets.DOCKER_TOKEN }} @@ -184,7 +185,7 @@ jobs: needs: [check] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -207,14 +208,15 @@ jobs: ] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: ./ + merge-multiple: true - name: Display structure of downloaded files run: ls -R @@ -223,16 +225,14 @@ jobs: run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - - name: Move files up - run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml - - name: Repackage in single firmware zip - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: firmware-${{ steps.version.outputs.version }} + overwrite: true path: | - ./*.bin - ./*.uf2 + ./firmware-*.bin + ./firmware-*.uf2 ./firmware-*-ota.zip ./device-*.sh ./device-*.bat @@ -240,9 +240,10 @@ jobs: ./config-dist.yaml retention-days: 90 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: firmware-${{ steps.version.outputs.version }} + merge-multiple: true path: ./output # For diagnostics @@ -258,9 +259,10 @@ jobs: run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output - name: Repackage in single elfs zip - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debug-elfs-${{ steps.version.outputs.version }}.zip + overwrite: true path: ./*.elf retention-days: 30 @@ -282,10 +284,10 @@ jobs: needs: [gather-artifacts, after-checks] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.x @@ -293,13 +295,15 @@ jobs: run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: firmware-${{ steps.version.outputs.version }} + merge-multiple: true path: ./output - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: + merge-multiple: true name: artifact-deb - name: Display structure of downloaded files @@ -313,9 +317,10 @@ jobs: - name: Zip firmware run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: debug-elfs-${{ steps.version.outputs.version }}.zip + merge-multiple: true path: ./elfs - name: Zip Elfs diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index da59bc0fd..e249823a7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Trunk Check uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 81ff6ee25..367f90c56 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -17,14 +17,14 @@ jobs: needs: build-raspbian steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web file: build.tar @@ -36,9 +36,10 @@ jobs: id: version - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: firmware-raspbian-${{ steps.version.outputs.version }}.zip + merge-multiple: true - name: Display structure of downloaded files run: ls -R @@ -68,8 +69,9 @@ jobs: depends: libyaml-cpp0.7, openssl, libulfius2.7 desc: Native Linux Meshtastic binary. - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: artifact-deb + overwrite: true path: | ./*.deb diff --git a/.github/workflows/sec_sast_flawfinder.yml b/.github/workflows/sec_sast_flawfinder.yml index 2c7e751af..99cc72190 100644 --- a/.github/workflows/sec_sast_flawfinder.yml +++ b/.github/workflows/sec_sast_flawfinder.yml @@ -16,7 +16,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # step 2 - name: flawfinder_scan @@ -27,14 +27,15 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: flawfinder_report.sarif + overwrite: true path: flawfinder_report.sarif # step 4 - name: publish code scanning alerts - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: flawfinder_report.sarif category: flawfinder diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index cdd2c3c37..2a0361f5e 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -17,7 +17,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # step 2 - name: full scan @@ -29,14 +29,15 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: report.sarif + overwrite: true path: report.sarif # step 4 - name: publish code scanning alerts - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: report.sarif category: semgrep diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml index 1697ffb1b..b6c288494 100644 --- a/.github/workflows/sec_sast_semgrep_pull.yml +++ b/.github/workflows/sec_sast_semgrep_pull.yml @@ -11,7 +11,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml index e35b91cb9..6ed905bc8 100644 --- a/.github/workflows/trunk-check.yml +++ b/.github/workflows/trunk-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Trunk Check uses: trunk-io/trunk-action@v1 diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 30f9b3578..30a5993c2 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -7,7 +7,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true From 078f33ff78f22d3ca53dc50b9c9b0e9cabd2f7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 13 May 2024 11:45:22 +0200 Subject: [PATCH 0405/1377] Re-add missing files (#3873) --- .github/workflows/main_matrix.yml | 17 +++++++++++------ .github/workflows/update_protobufs.yml | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 095981087..7affd8fc1 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -157,14 +157,13 @@ jobs: with: username: meshtastic password: ${{ secrets.DOCKER_TOKEN }} - - name: Docker setup if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Docker build and push tagged versions if: ${{ github.event_name == 'workflow_dispatch' }} - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile @@ -173,7 +172,7 @@ jobs: - name: Docker build and push if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile @@ -225,6 +224,9 @@ jobs: run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version + - name: Move files up + run: mv -b -t ./ ./release/meshtasticd_linux_aarch64 ./bin/config-dist.yaml + - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 with: @@ -236,8 +238,11 @@ jobs: ./firmware-*-ota.zip ./device-*.sh ./device-*.bat - ./meshtasticd_linux_arm64 + ./meshtasticd_linux_*64 ./config-dist.yaml + ./littlefs-*.bin + ./bleota*bin + ./Meshtastic_nRF52_factory_erase*.uf2 retention-days: 90 - uses: actions/download-artifact@v4 @@ -378,7 +383,7 @@ jobs: bin/bump_version.py - name: Create version.properties pull request - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v6 with: add-paths: | version.properties diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 30a5993c2..4402a280e 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -26,7 +26,7 @@ jobs: ./bin/regen-protos.sh - name: Create pull request - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v6 with: add-paths: | protobufs From a9a208de7336903fa466ebd3d749f317c0ae69ce Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 13 May 2024 23:42:41 +1200 Subject: [PATCH 0406/1377] Implement "Flip screen" setting for E-Ink displays (#3871) --- src/graphics/EInkDisplay2.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 04915fe07..bbc12521a 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -62,12 +62,19 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) return false; // FIXME - only draw bits have changed (use backbuf similar to the other displays) + const bool flipped = config.display.flip_screen; for (uint32_t y = 0; y < displayHeight; y++) { for (uint32_t x = 0; x < displayWidth; x++) { // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); - adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); + + // Handle flip here, rather than with setRotation(), + // Avoids issues when display width is not a multiple of 8 + if (flipped) + adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE); + else + adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); } } From 2388eb91ae6168ecf25c086abd6d856bfee2c8b3 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Tue, 14 May 2024 23:46:03 +1200 Subject: [PATCH 0407/1377] Fix immediate wake from deepsleep for some devices (#3884) Affects ESP32 boards without an external pull-up on the defined user-button pin. --- src/sleep.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sleep.cpp b/src/sleep.cpp index fe73a755c..590610e6c 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -264,7 +264,11 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #ifdef BUTTON_PIN // Avoid leakage through button pin if (GPIO_IS_VALID_OUTPUT_GPIO(BUTTON_PIN)) { +#ifdef BUTTON_NEED_PULLUP + pinMode(BUTTON_PIN, INPUT_PULLUP); +#else pinMode(BUTTON_PIN, INPUT); +#endif gpio_hold_en((gpio_num_t)BUTTON_PIN); } #endif From 3b5d4e92c5aa109056e89814434ab1a196ddc33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 14 May 2024 13:48:26 +0200 Subject: [PATCH 0408/1377] add psram flag on RAK11200 board definition (#3887) --- boards/wiscore_rak11200.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/wiscore_rak11200.json b/boards/wiscore_rak11200.json index 33d16ba77..54ee9b69e 100644 --- a/boards/wiscore_rak11200.json +++ b/boards/wiscore_rak11200.json @@ -4,7 +4,7 @@ "ldscript": "esp32_out.ld" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV", + "extra_flags": ["-DBOARD_HAS_PSRAM", "-DARDUINO_ESP32_DEV"], "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", From 1f9ff68f1d318de7bede8e4e0bed64ca0b990f2c Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Tue, 14 May 2024 19:04:31 +0200 Subject: [PATCH 0409/1377] RP2040: Add `getFreeHeap()` and `getHeapSize()` support --- src/memGet.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/memGet.cpp b/src/memGet.cpp index 0575da279..e982ef7ee 100644 --- a/src/memGet.cpp +++ b/src/memGet.cpp @@ -22,6 +22,8 @@ uint32_t MemGet::getFreeHeap() return ESP.getFreeHeap(); #elif defined(ARCH_NRF52) return dbgHeapFree(); +#elif defined(ARCH_RP2040) + return rp2040.getFreeHeap(); #else // this platform does not have heap management function implemented return UINT32_MAX; @@ -38,6 +40,8 @@ uint32_t MemGet::getHeapSize() return ESP.getHeapSize(); #elif defined(ARCH_NRF52) return dbgHeapTotal(); +#elif defined(ARCH_RP2040) + return rp2040.getTotalHeap(); #else // this platform does not have heap management function implemented return UINT32_MAX; From 15178da566c23709026e5cac262b35d5245f6d80 Mon Sep 17 00:00:00 2001 From: pr000t <20946964+pr000t@users.noreply.github.com> Date: Tue, 14 May 2024 21:07:44 +0200 Subject: [PATCH 0410/1377] Change SHT4X sensors library from Sensirion to Adafruit --- platformio.ini | 2 +- src/modules/Telemetry/Sensor/SHT4XSensor.cpp | 38 +++++++------------- src/modules/Telemetry/Sensor/SHT4XSensor.h | 4 +-- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/platformio.ini b/platformio.ini index 9d7c76fbf..5e53c495f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -133,4 +133,4 @@ lib_deps = https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 - https://github.com/Sensirion/arduino-i2c-sht4x#1.1.0 + https://github.com/adafruit/Adafruit_SHT4X#1.0.4 \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp index d324b7fd6..7f37327c6 100644 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp @@ -5,17 +5,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "SHT4XSensor.h" #include "TelemetrySensor.h" -#include - -// macro definitions -// make sure that we use the proper definition of NO_ERROR -#ifdef NO_ERROR -#undef NO_ERROR -#endif -#define NO_ERROR 0 - -static char errorMessage[64]; -static int16_t error; +#include SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {} @@ -28,17 +18,15 @@ int32_t SHT4XSensor::runOnce() uint32_t serialNumber = 0; - sht4x.begin(*nodeTelemetrySensorsMap[sensorType].second, 0x44); + sht4x.begin(nodeTelemetrySensorsMap[sensorType].second); - error = sht4x.serialNumber(serialNumber); - LOG_DEBUG("serialNumber : %x\n", serialNumber); - if (error != NO_ERROR) { - LOG_DEBUG("Error trying to execute serialNumber(): "); - errorToString(error, errorMessage, sizeof errorMessage); - LOG_DEBUG(errorMessage); - status = 0; - } else { + serialNumber = sht4x.readSerial(); + if (serialNumber != 0) { + LOG_DEBUG("serialNumber : %x\n", serialNumber); status = 1; + } else { + LOG_DEBUG("Error trying to execute readSerial(): "); + status = 0; } return initI2CSensor(); @@ -51,12 +39,10 @@ void SHT4XSensor::setup() bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement) { - float aTemperature = 0.0; - float aHumidity = 0.0; - sht4x.measureLowestPrecision(aTemperature, aHumidity); - measurement->variant.environment_metrics.temperature = aTemperature; - measurement->variant.environment_metrics.relative_humidity = aHumidity; - + sensors_event_t humidity, temp; + sht4x.getEvent(&humidity, &temp); + measurement->variant.environment_metrics.temperature = temp.temperature; + measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity; return true; } diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h index 67045eb2a..62a5cefeb 100644 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.h +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h @@ -4,12 +4,12 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include +#include class SHT4XSensor : public TelemetrySensor { private: - SensirionI2cSht4x sht4x; + Adafruit_SHT4x sht4x = Adafruit_SHT4x(); protected: virtual void setup() override; From 77e76bc92b8a7ea0816eea23f39b6e59a1e035d0 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Tue, 14 May 2024 16:42:23 -0400 Subject: [PATCH 0411/1377] Fix VEML7700Sensor readings and update protobuf and MQTT JSON --- src/mesh/generated/meshtastic/telemetry.pb.h | 18 +++++------ .../Telemetry/Sensor/VEML7700Sensor.cpp | 31 +++++++++++++++++-- src/modules/Telemetry/Sensor/VEML7700Sensor.h | 5 +++ src/mqtt/MQTT.cpp | 3 +- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index d85855437..fb1a84c44 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -88,10 +88,8 @@ typedef struct _meshtastic_EnvironmentMetrics { float distance; /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ float lux; - /* VEML7700 raw white light data digital 16-bit resolution sensor. */ - uint32_t white; - /* VEML7700 raw ALS data digital 16-bit resolution sensor. */ - uint32_t ALS; + /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ + float white_lux; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -173,12 +171,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -198,8 +196,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 #define meshtastic_EnvironmentMetrics_lux_tag 9 -#define meshtastic_EnvironmentMetrics_white_tag 10 -#define meshtastic_EnvironmentMetrics_ALS_tag 11 +#define meshtastic_EnvironmentMetrics_white_lux_tag 10 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -244,8 +241,7 @@ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ -X(a, STATIC, SINGULAR, UINT32, white, 10) \ -X(a, STATIC, SINGULAR, UINT32, ALS, 11) +X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -305,7 +301,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 56 +#define meshtastic_EnvironmentMetrics_size 49 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp index 5e1376849..1870cb3f9 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -24,11 +24,38 @@ int32_t VEML7700Sensor::runOnce() void VEML7700Sensor::setup() {} +/*! + * @brief Copmute lux from ALS reading. + * @param rawALS raw ALS register value + * @param corrected if true, apply non-linear correction + * @return lux value + */ +float VEML7700Sensor::computeLux(uint16_t rawALS, bool corrected) { + float lux = getResolution() * rawALS; + if (corrected) + lux = (((6.0135e-13 * lux - 9.3924e-9) * lux + 8.1488e-5) * lux + 1.0023) * + lux; + return lux; +} + +/*! + * @brief Determines resolution for current gain and integration time + * settings. + */ +float VEML7700Sensor::getResolution(void) { + return MAX_RES * (IT_MAX / veml7700.getIntegrationTimeValue()) * + (GAIN_MAX / veml7700.getGainValue()); +} + bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) { + int16_t white; measurement->variant.environment_metrics.lux = veml7700.readLux(VEML_LUX_AUTO); - measurement->variant.environment_metrics.white = veml7700.readWhite(); - measurement->variant.environment_metrics.ALS = veml7700.readALS(); + white = veml7700.readWhite(true); + measurement->variant.environment_metrics.white_lux = computeLux(white, white > 100); + LOG_INFO("white lux %f, als lux %f\n", + measurement->variant.environment_metrics.white_lux, + measurement->variant.environment_metrics.lux); return true; diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h index fac602672..9c7a584d9 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.h +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h @@ -5,7 +5,12 @@ class VEML7700Sensor : public TelemetrySensor { private: + const float MAX_RES = 0.0036; + const float GAIN_MAX = 2; + const float IT_MAX = 800; Adafruit_VEML7700 veml7700; + float computeLux(uint16_t rawALS, bool corrected); + float getResolution(void); protected: virtual void setup() override; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 59f1002fc..e91eade35 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -665,8 +665,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage); msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current); msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux); - msgPayload["white"] = new JSONValue((uint)decoded->variant.environment_metrics.white); - msgPayload["ALS"] = new JSONValue((uint)decoded->variant.environment_metrics.ALS); + msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux); msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); From 022e1f472d2690e68bc3a8246070acdee128d8b9 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Tue, 14 May 2024 17:00:33 -0400 Subject: [PATCH 0412/1377] Updated protobufs submodule --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index eade2c6be..71f3d68db 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 +Subproject commit 71f3d68db56fb335c78211443b6cff3d661adb03 From 0aa449bca943528e2fc00f9ddeeaf4631334b011 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Tue, 14 May 2024 18:47:20 -0400 Subject: [PATCH 0413/1377] Fix unnecessary code block removal in EnvironmentTelemetryModule --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 53a415af5..77d292849 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -220,8 +220,6 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac t->variant.environment_metrics.temperature, t->variant.environment_metrics.lux); LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f\n", sender, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance); - -#endif #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) From 49a9aa3e36f4010fbe71d451046efcdce9f4ea5e Mon Sep 17 00:00:00 2001 From: mverch67 Date: Wed, 15 May 2024 08:10:52 +0200 Subject: [PATCH 0414/1377] fix native compilation for linux PCs --- src/platform/portduino/PortduinoGlue.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 4077a27bc..89ac806dd 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -283,7 +283,7 @@ void portduinoSetup() } for (configNames i : GPIO_lines) { - if (settingsMap[i] > max_GPIO) + if (settingsMap.count(i) && settingsMap[i] > max_GPIO) max_GPIO = settingsMap[i]; } @@ -342,6 +342,7 @@ void portduinoSetup() if (settingsMap[touchscreenIRQ] > 0) initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); } + if (settingsStrings[spidev] != "") { SPI.begin(settingsStrings[spidev].c_str()); } @@ -350,6 +351,7 @@ void portduinoSetup() int initGPIOPin(int pinNum, const std::string gpioChipName) { +#ifdef PORTDUINO_LINUX_HARDWARE std::string gpio_name = "GPIO" + std::to_string(pinNum); try { GPIOPin *csPin; @@ -362,4 +364,7 @@ int initGPIOPin(int pinNum, const std::string gpioChipName) std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; return ERRNO_DISABLED; } +#else + return ERRNO_OK; +#endif } \ No newline at end of file From 78a1b6a9a827462b5486844f5ccd57ba0408db40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 15 May 2024 09:59:46 +0200 Subject: [PATCH 0415/1377] unrelated change, i just noticed this problem... --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 5641e1f63..ff704c931 100644 --- a/platformio.ini +++ b/platformio.ini @@ -134,5 +134,5 @@ lib_deps = adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 - https://github.com/adafruit/Adafruit_SHT4X#1.0.4 + adafruit/Adafruit SHT4x Library@^1.0.4 From 33812a208283aa192a85c5e3e98054b1c2de1c2d Mon Sep 17 00:00:00 2001 From: mverch67 Date: Wed, 15 May 2024 11:18:39 +0200 Subject: [PATCH 0416/1377] update portduino-framework --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 63f5576b6..be374df32 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#9881bf3721d610cccacf5ae8e3a07839cce75d63 +platform = https://github.com/meshtastic/platform-native.git#f5ec3c031b0fcd89c0523de9e43eef3a92d59292 framework = arduino build_src_filter = From 64dc6cc215a24707b7cd68770a1a589eddfe06e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 15 May 2024 12:24:42 +0200 Subject: [PATCH 0417/1377] trunk fmt --- src/main.cpp | 1 - .../Telemetry/EnvironmentTelemetry.cpp | 25 +++++++++---------- .../Telemetry/Sensor/VEML7700Sensor.cpp | 24 ++++++++---------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 03c116ede..f14d08188 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -544,7 +544,6 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) - i2cScanner.reset(); #ifdef HAS_SDCARD diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index e35c2a51a..d0a9890dd 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -246,54 +246,53 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (sht31Sensor.hasSensor()) { valid = valid && sht31Sensor.getMetrics(&m); hasSensor = true; - } + } if (lps22hbSensor.hasSensor()) { valid = valid && lps22hbSensor.getMetrics(&m); hasSensor = true; - } + } if (shtc3Sensor.hasSensor()) { valid = valid && shtc3Sensor.getMetrics(&m); hasSensor = true; - } + } if (bmp085Sensor.hasSensor()) { valid = valid && bmp085Sensor.getMetrics(&m); hasSensor = true; - } + } if (bmp280Sensor.hasSensor()) { valid = valid && bmp280Sensor.getMetrics(&m); hasSensor = true; - } + } if (bme280Sensor.hasSensor()) { valid = valid && bme280Sensor.getMetrics(&m); hasSensor = true; - } + } if (bme680Sensor.hasSensor()) { valid = valid && bme680Sensor.getMetrics(&m); hasSensor = true; - } + } if (mcp9808Sensor.hasSensor()) { valid = valid && mcp9808Sensor.getMetrics(&m); hasSensor = true; - } + } if (ina219Sensor.hasSensor()) { valid = valid && ina219Sensor.getMetrics(&m); hasSensor = true; - } + } if (ina260Sensor.hasSensor()) { valid = valid && ina260Sensor.getMetrics(&m); hasSensor = true; - } + } if (veml7700Sensor.hasSensor()) { valid = valid && veml7700Sensor.getMetrics(&m); hasSensor = true; - } - if (rcwl9620Sensor.hasSensor()){ + } + if (rcwl9620Sensor.hasSensor()) { valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; } valid = valid && hasSensor; - if (valid) { LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f, " "lux=%f\n", diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp index 1870cb3f9..1abe8339f 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -30,21 +30,21 @@ void VEML7700Sensor::setup() {} * @param corrected if true, apply non-linear correction * @return lux value */ -float VEML7700Sensor::computeLux(uint16_t rawALS, bool corrected) { - float lux = getResolution() * rawALS; - if (corrected) - lux = (((6.0135e-13 * lux - 9.3924e-9) * lux + 8.1488e-5) * lux + 1.0023) * - lux; - return lux; +float VEML7700Sensor::computeLux(uint16_t rawALS, bool corrected) +{ + float lux = getResolution() * rawALS; + if (corrected) + lux = (((6.0135e-13 * lux - 9.3924e-9) * lux + 8.1488e-5) * lux + 1.0023) * lux; + return lux; } /*! * @brief Determines resolution for current gain and integration time * settings. */ -float VEML7700Sensor::getResolution(void) { - return MAX_RES * (IT_MAX / veml7700.getIntegrationTimeValue()) * - (GAIN_MAX / veml7700.getGainValue()); +float VEML7700Sensor::getResolution(void) +{ + return MAX_RES * (IT_MAX / veml7700.getIntegrationTimeValue()) * (GAIN_MAX / veml7700.getGainValue()); } bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) @@ -53,10 +53,8 @@ bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.lux = veml7700.readLux(VEML_LUX_AUTO); white = veml7700.readWhite(true); measurement->variant.environment_metrics.white_lux = computeLux(white, white > 100); - LOG_INFO("white lux %f, als lux %f\n", - measurement->variant.environment_metrics.white_lux, - measurement->variant.environment_metrics.lux); - + LOG_INFO("white lux %f, als lux %f\n", measurement->variant.environment_metrics.white_lux, + measurement->variant.environment_metrics.lux); return true; } \ No newline at end of file From 419820f4836d4b26a38180c39e59fb100722133e Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Wed, 15 May 2024 12:47:31 +0000 Subject: [PATCH 0418/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 32 +++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index 1bfe0354d..bee9ddc71 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1bfe0354d101a6a71ea1354ea158e59193671a0b +Subproject commit bee9ddc71677e974568a5a95362d78acac6bb974 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index b6b08f2e7..7748dd6b3 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -47,7 +47,17 @@ typedef enum _meshtastic_TelemetrySensorType { /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */ meshtastic_TelemetrySensorType_RCWL9620 = 16, /* Sensirion High accuracy temperature and humidity */ - meshtastic_TelemetrySensorType_SHT4X = 17 + meshtastic_TelemetrySensorType_SHT4X = 17, + /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + meshtastic_TelemetrySensorType_VEML7700 = 18, + /* MLX90632 non-contact IR temperature sensor. */ + meshtastic_TelemetrySensorType_MLX90632 = 19, + /* TI OPT3001 Ambient Light Sensor */ + meshtastic_TelemetrySensorType_OPT3001 = 20, + /* Lite On LTR-390UV-01 UV Light Sensor */ + meshtastic_TelemetrySensorType_LTR390UV = 21, + /* AMS TSL25911FN RGB Light Sensor */ + meshtastic_TelemetrySensorType_TSL25911FN = 22 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -84,6 +94,10 @@ typedef struct _meshtastic_EnvironmentMetrics { uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ float distance; + /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + float lux; + /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ + float white_lux; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -154,8 +168,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHT4X -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHT4X+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL25911FN +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL25911FN+1)) @@ -165,12 +179,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -189,6 +203,8 @@ extern "C" { #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 +#define meshtastic_EnvironmentMetrics_lux_tag 9 +#define meshtastic_EnvironmentMetrics_white_lux_tag 10 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -231,7 +247,9 @@ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, distance, 8) +X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ +X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ +X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -291,7 +309,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 39 +#define meshtastic_EnvironmentMetrics_size 49 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From bd9156de24196ce99b6bdda8a0c615565c31cea0 Mon Sep 17 00:00:00 2001 From: Ken McGuire Date: Wed, 15 May 2024 11:12:46 -0600 Subject: [PATCH 0419/1377] GPS Chechsum failures (#3900) * Portduino multiple logging levels * Fixes based on GPSFan work * Fix derped logic * Correct size field for AID message * Reformat to add comments, beginning of GPS rework * Update PM2 message for Neo-6 * Correct ECO mode logic as ECO mode is only for Neo-6 * Cleanup ubx.h add a few more comments * GPS rework, changes for M8 and stub for M10 * Add VALSET commands for u-blox M10 receivers * Add VALSET commands for u-blox M10 receivers tweak M8 commands add comments for VALSET configuration commands * Add commands to init M10 receivers, tweak the M8 init sequence, this is a WIP as there are still some issues during init. Add M10 version of PMREQ. * Add wakeup source of uartrx to PMREQ_10 The M10 does not respond to commands when asleep, may need to do this for the M8 as well * Enable NMEA messages on USB port. Normally, it is a good idea to disable messages on unused ports. Native Linux needs to be able to use GNSS modules connected via via either serial or USB. In the future I2C connections may be required, but are not enabled for now. * Save the config for all u-blox receiver types. The M10 supports this command in addition to saving using the VALSET commands for the RAM & BBR layers. * Address Issue #3779 RAK12500 GPS Checksum failures Remove NMEA sentences that are not processed by TinyGPS++ or Meshtastic. --------- Co-authored-by: Jonathan Bennett Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 3 +++ src/gps/ubx.h | 31 ++++++++++++++++++------------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index eaae049b5..4335244f0 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -472,6 +472,9 @@ bool GPS::setup() // Turn off GSV messages, we don't really care about which and where the sats are, maybe someday. _serial_gps->write("$CFGMSG,0,3,0\r\n"); delay(250); + // Turn off GSA messages, TinyGPS++ doesn't use this message. + _serial_gps->write("$CFGMSG,0,2,0\r\n"); + delay(250); // Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care. _serial_gps->write("$CFGMSG,6,0,0\r\n"); delay(250); diff --git a/src/gps/ubx.h b/src/gps/ubx.h index 5b2cb24ce..0a382a8a3 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -206,14 +206,14 @@ const uint8_t GPS::_message_GLL[] = { 0x00 // Reserved }; -// Enable GSA. GSA - GPS DOP and active satellites, used for detailing the satellites used in the positioning and +// Disable GSA. GSA - GPS DOP and active satellites, used for detailing the satellites used in the positioning and // the DOP (Dilution of Precision) const uint8_t GPS::_message_GSA[] = { 0xF0, 0x02, // NMEA ID for GSA 0x00, // Rate for DDC - 0x01, // Rate for UART1 + 0x00, // Rate for UART1 0x00, // Rate for UART2 - 0x01, // Rate for USB usefull for native linux + 0x00, // Rate for USB usefull for native linux 0x00, // Rate for SPI 0x00 // Reserved }; @@ -402,23 +402,28 @@ const uint8_t GPS::_message_VALSET_DISABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, // BBR layer config message: // b5 62 06 8a 09 00 00 02 00 00 07 00 92 20 06 5a 58 -// Turn NMEA GSA, GGA, RMC messages on: -// Ram layer config message: -// b5 62 06 8a 13 00 00 01 00 00 c0 00 91 20 01 bb 00 91 20 01 ac 00 91 20 01 e1 3b - -// BBR layer config message: -// b5 62 06 8a 13 00 00 02 00 00 c0 00 91 20 01 bb 00 91 20 01 ac 00 91 20 01 e2 4d +// Turn NMEA GGA, RMC messages on: +// Layer config messages: +// RAM: +// b5 62 06 8a 0e 00 00 01 00 00 bb 00 91 20 01 ac 00 91 20 01 6a 8f +// BBR: +// b5 62 06 8a 0e 00 00 02 00 00 bb 00 91 20 01 ac 00 91 20 01 6b 9c +// FLASH: +// b5 62 06 8a 0e 00 00 04 00 00 bb 00 91 20 01 ac 00 91 20 01 6d b6 +// Doing this for the FLASH layer isn't really required since we save the config to flash later const uint8_t GPS::_message_VALSET_DISABLE_TXT_INFO_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03}; const uint8_t GPS::_message_VALSET_DISABLE_TXT_INFO_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03}; -const uint8_t GPS::_message_VALSET_ENABLE_NMEA_RAM[] = {0x00, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x01, 0xbb, - 0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01}; -const uint8_t GPS::_message_VALSET_ENABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x01, 0xbb, - 0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01}; + +const uint8_t GPS::_message_VALSET_ENABLE_NMEA_RAM[] = {0x00, 0x01, 0x00, 0x00, 0xbb, 0x00, 0x91, + 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01}; +const uint8_t GPS::_message_VALSET_ENABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xbb, 0x00, 0x91, + 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01}; const uint8_t GPS::_message_VALSET_DISABLE_SBAS_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x31, 0x10, 0x00, 0x05, 0x00, 0x31, 0x10, 0x00}; const uint8_t GPS::_message_VALSET_DISABLE_SBAS_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x31, 0x10, 0x00, 0x05, 0x00, 0x31, 0x10, 0x00}; + /* Operational issues with the M10: From 9e8ca69a11ee8ad2b53f16fb2f3eeeba7f76bef6 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 15 May 2024 22:24:25 +0200 Subject: [PATCH 0420/1377] bin: remove unused import in buildinfo.py This was originally added in b1c30f06509459e5afc5291de067b1946ae4ddd5 and it now do nothing since 361556a6a7913104c513c8f8029efe090267bb9f because it now use readprops. --- bin/buildinfo.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/buildinfo.py b/bin/buildinfo.py index 6b123c9cf..5aecad1bd 100755 --- a/bin/buildinfo.py +++ b/bin/buildinfo.py @@ -1,9 +1,8 @@ #!/usr/bin/env python3 -import configparser import sys + from readprops import readProps - -verObj = readProps('version.properties') +verObj = readProps("version.properties") propName = sys.argv[1] print(f"{verObj[propName]}") From e08c808c3fc7e16aab2e12904764bf0aa0c39137 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Wed, 15 May 2024 17:06:23 -0400 Subject: [PATCH 0421/1377] fix log line --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index d0a9890dd..a3f63b0aa 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -218,12 +218,12 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, " - "temperature=%f, lux=%f\n", + "temperature=%f\n", sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, - t->variant.environment_metrics.temperature, t->variant.environment_metrics.lux); - LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f\n", sender, t->variant.environment_metrics.voltage, - t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance); + t->variant.environment_metrics.temperature); + LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage, + t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -294,13 +294,12 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = valid && hasSensor; if (valid) { - LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f, " - "lux=%f\n", + LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, - m.variant.environment_metrics.temperature, m.variant.environment_metrics.lux); - LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f\n", m.variant.environment_metrics.voltage, - m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance); + m.variant.environment_metrics.temperature); + LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage, + m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); sensor_read_error_count = 0; From ce25381f678e5838a8ca80b74f4ccb5b8311cdb4 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Wed, 15 May 2024 17:13:28 -0400 Subject: [PATCH 0422/1377] fix unrelated change --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index ff704c931..0f2907dee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -134,5 +134,4 @@ lib_deps = adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 - adafruit/Adafruit SHT4x Library@^1.0.4 - + https://github.com/adafruit/Adafruit_SHT4X#1.0.4 \ No newline at end of file From eaa7fcf3dcdaa1f4b4a1a814052c509d34b314bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 00:23:03 +0200 Subject: [PATCH 0423/1377] Revert "Updated protobufs submodule" This reverts commit 022e1f472d2690e68bc3a8246070acdee128d8b9. --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 71f3d68db..eade2c6be 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 71f3d68db56fb335c78211443b6cff3d661adb03 +Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 From 79628c73cda35d798b854cc68dec245a82cc5481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 00:23:51 +0200 Subject: [PATCH 0424/1377] tryfix proto conflict --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index eade2c6be..71f3d68db 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 +Subproject commit 71f3d68db56fb335c78211443b6cff3d661adb03 From 6dbc85810209c16a60736da16b52bd42fdb95f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 00:26:01 +0200 Subject: [PATCH 0425/1377] Revert "tryfix proto conflict" This reverts commit 79628c73cda35d798b854cc68dec245a82cc5481. --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 71f3d68db..eade2c6be 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 71f3d68db56fb335c78211443b6cff3d661adb03 +Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 From 3342395a0b783fee59abbb5ef1291985ce5967e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 01:04:38 +0200 Subject: [PATCH 0426/1377] fix generated files --- src/mesh/generated/meshtastic/telemetry.pb.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index ecb353ff6..f4a79398f 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -49,7 +49,15 @@ typedef enum _meshtastic_TelemetrySensorType { /* Sensirion High accuracy temperature and humidity */ meshtastic_TelemetrySensorType_SHT4X = 17, /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ - meshtastic_TelemetrySensorType_VEML7700 = 18 + meshtastic_TelemetrySensorType_VEML7700 = 18, + /* MLX90632 non-contact IR temperature sensor. */ + meshtastic_TelemetrySensorType_MLX90632 = 19, + /* TI OPT3001 Ambient Light Sensor */ + meshtastic_TelemetrySensorType_OPT3001 = 20, + /* Lite On LTR-390UV-01 UV Light Sensor */ + meshtastic_TelemetrySensorType_LTR390UV = 21, + /* AMS TSL25911FN RGB Light Sensor */ + meshtastic_TelemetrySensorType_TSL25911FN = 22 } meshtastic_TelemetrySensorType; /* Struct definitions */ From 53dea44983bf1a2ee9bec096b2f3cf74167ffe3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 01:08:52 +0200 Subject: [PATCH 0427/1377] Revert "Merge pull request #3898 from meshtastic/create-pull-request/patch" This reverts commit 938aba481a9faed90aae4ddac636513dc7d594ff, reversing changes made to 7810e59b0c3fe392c75ed6222558d1d12a1dc201. --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 32 +++++--------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/protobufs b/protobufs index bee9ddc71..1bfe0354d 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bee9ddc71677e974568a5a95362d78acac6bb974 +Subproject commit 1bfe0354d101a6a71ea1354ea158e59193671a0b diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 7748dd6b3..b6b08f2e7 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -47,17 +47,7 @@ typedef enum _meshtastic_TelemetrySensorType { /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */ meshtastic_TelemetrySensorType_RCWL9620 = 16, /* Sensirion High accuracy temperature and humidity */ - meshtastic_TelemetrySensorType_SHT4X = 17, - /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ - meshtastic_TelemetrySensorType_VEML7700 = 18, - /* MLX90632 non-contact IR temperature sensor. */ - meshtastic_TelemetrySensorType_MLX90632 = 19, - /* TI OPT3001 Ambient Light Sensor */ - meshtastic_TelemetrySensorType_OPT3001 = 20, - /* Lite On LTR-390UV-01 UV Light Sensor */ - meshtastic_TelemetrySensorType_LTR390UV = 21, - /* AMS TSL25911FN RGB Light Sensor */ - meshtastic_TelemetrySensorType_TSL25911FN = 22 + meshtastic_TelemetrySensorType_SHT4X = 17 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -94,10 +84,6 @@ typedef struct _meshtastic_EnvironmentMetrics { uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ float distance; - /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ - float lux; - /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ - float white_lux; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -168,8 +154,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL25911FN -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL25911FN+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHT4X +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHT4X+1)) @@ -179,12 +165,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -203,8 +189,6 @@ extern "C" { #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 -#define meshtastic_EnvironmentMetrics_lux_tag 9 -#define meshtastic_EnvironmentMetrics_white_lux_tag 10 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -247,9 +231,7 @@ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ -X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ -X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) +X(a, STATIC, SINGULAR, FLOAT, distance, 8) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -309,7 +291,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 49 +#define meshtastic_EnvironmentMetrics_size 39 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From fe2356ae871afd3c8b2c5399e55cd37a423ed5c9 Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Wed, 15 May 2024 23:12:12 +0000 Subject: [PATCH 0428/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 32 +++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index 1bfe0354d..bee9ddc71 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1bfe0354d101a6a71ea1354ea158e59193671a0b +Subproject commit bee9ddc71677e974568a5a95362d78acac6bb974 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index b6b08f2e7..7748dd6b3 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -47,7 +47,17 @@ typedef enum _meshtastic_TelemetrySensorType { /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */ meshtastic_TelemetrySensorType_RCWL9620 = 16, /* Sensirion High accuracy temperature and humidity */ - meshtastic_TelemetrySensorType_SHT4X = 17 + meshtastic_TelemetrySensorType_SHT4X = 17, + /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + meshtastic_TelemetrySensorType_VEML7700 = 18, + /* MLX90632 non-contact IR temperature sensor. */ + meshtastic_TelemetrySensorType_MLX90632 = 19, + /* TI OPT3001 Ambient Light Sensor */ + meshtastic_TelemetrySensorType_OPT3001 = 20, + /* Lite On LTR-390UV-01 UV Light Sensor */ + meshtastic_TelemetrySensorType_LTR390UV = 21, + /* AMS TSL25911FN RGB Light Sensor */ + meshtastic_TelemetrySensorType_TSL25911FN = 22 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -84,6 +94,10 @@ typedef struct _meshtastic_EnvironmentMetrics { uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ float distance; + /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + float lux; + /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ + float white_lux; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -154,8 +168,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHT4X -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHT4X+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL25911FN +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL25911FN+1)) @@ -165,12 +179,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -189,6 +203,8 @@ extern "C" { #define meshtastic_EnvironmentMetrics_current_tag 6 #define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_EnvironmentMetrics_distance_tag 8 +#define meshtastic_EnvironmentMetrics_lux_tag 9 +#define meshtastic_EnvironmentMetrics_white_lux_tag 10 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -231,7 +247,9 @@ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, distance, 8) +X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ +X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ +X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -291,7 +309,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 39 +#define meshtastic_EnvironmentMetrics_size 49 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From 0c9eadc507de3790525cbaf895632ff957970a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 01:16:05 +0200 Subject: [PATCH 0429/1377] Resync --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index eade2c6be..bee9ddc71 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0 +Subproject commit bee9ddc71677e974568a5a95362d78acac6bb974 From c18b4920aedc9a91d39d477b57cefaa662c246ff Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Wed, 15 May 2024 23:28:03 +0000 Subject: [PATCH 0430/1377] [create-pull-request] automated change --- src/mesh/generated/meshtastic/telemetry.pb.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 6c7779710..7748dd6b3 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -168,7 +168,6 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET - #define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL25911FN #define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL25911FN+1)) @@ -178,7 +177,6 @@ extern "C" { - /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} #define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -319,4 +317,4 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; } /* extern "C" */ #endif -#endif \ No newline at end of file +#endif From 1d42a6c48fb471329feccf8628a76e26a5bcb018 Mon Sep 17 00:00:00 2001 From: Henrik Witt-Hansen Date: Thu, 16 May 2024 01:54:51 +0200 Subject: [PATCH 0431/1377] Fix static ip assignment on wifi for rp2040 (#3896) by rearranging the arguments to match the expected input order. The lwip library makes an internal reorder or the arguments depending on the netmask to work with both ESP and Arduino platforms. The input order was incorrect when running on an rp2040 device. Co-authored-by: Henrik Witt-Hansen Co-authored-by: Ben Meadors --- src/mesh/wifi/WiFiAPClient.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 1de4d7669..a259d161b 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -225,10 +225,16 @@ bool initWifi() WiFi.mode(WIFI_STA); WiFi.setHostname(ourHost); + if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC && config.network.ipv4_config.ip != 0) { +#ifndef ARCH_RP2040 WiFi.config(config.network.ipv4_config.ip, config.network.ipv4_config.gateway, config.network.ipv4_config.subnet, config.network.ipv4_config.dns); +#else + WiFi.config(config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.gateway, + config.network.ipv4_config.subnet); +#endif } #ifndef ARCH_RP2040 WiFi.onEvent(WiFiEvent); From 04837b33027be04dfd44f45128e7ad0c9396510e Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 16 May 2024 02:37:35 +0200 Subject: [PATCH 0432/1377] bin: remove unused imports from platformio-custom.py --- bin/platformio-custom.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index e03a35e3a..651677af2 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -1,13 +1,12 @@ -import subprocess -import configparser -import traceback import sys from os.path import join + from readprops import readProps Import("env") platform = env.PioPlatform() + def esp32_create_combined_bin(source, target, env): # this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3 # https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py @@ -20,8 +19,8 @@ def esp32_create_combined_bin(source, target, env): firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") chip = env.get("BOARD_MCU") flash_size = env.BoardConfig().get("upload.flash_size") - flash_freq = env.BoardConfig().get("build.f_flash", '40m') - flash_freq = flash_freq.replace('000000L', 'm') + flash_freq = env.BoardConfig().get("build.f_flash", "40m") + flash_freq = flash_freq.replace("000000L", "m") flash_mode = env.BoardConfig().get("build.flash_mode", "dio") memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi") if flash_mode == "qio" or flash_mode == "qout": @@ -51,23 +50,27 @@ def esp32_create_combined_bin(source, target, env): print(f" - {hex(app_offset)} | {firmware_name}") cmd += [hex(app_offset), firmware_name] - print('Using esptool.py arguments: %s' % ' '.join(cmd)) + print("Using esptool.py arguments: %s" % " ".join(cmd)) esptool.main(cmd) -if (platform.name == "espressif32"): + +if platform.name == "espressif32": sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) import esptool - env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) + + env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) Import("projenv") prefsLoc = projenv["PROJECT_DIR"] + "/version.properties" verObj = readProps(prefsLoc) -print("Using meshtastic platformio-custom.py, firmware version " + verObj['long']) +print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"]) # General options that are passed to the C and C++ compilers -projenv.Append(CCFLAGS=[ - "-DAPP_VERSION=" + verObj['long'], - "-DAPP_VERSION_SHORT=" + verObj['short'] -]) +projenv.Append( + CCFLAGS=[ + "-DAPP_VERSION=" + verObj["long"], + "-DAPP_VERSION_SHORT=" + verObj["short"], + ] +) From 51d2795b269df7d3150810017bca55b10b1c8969 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 16 May 2024 07:46:47 -0500 Subject: [PATCH 0433/1377] Fix TBeam Supreme woes (and upgrade platform) --- boards/tbeam-s3-core.json | 1 + variants/tbeam-s3-core/pins_arduino.h | 8 -------- variants/tbeam-s3-core/platformio.ini | 2 ++ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 4c82a2789..8d2c3eed6 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -7,6 +7,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", + "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" diff --git a/variants/tbeam-s3-core/pins_arduino.h b/variants/tbeam-s3-core/pins_arduino.h index 24edb7d9f..22ed814ff 100644 --- a/variants/tbeam-s3-core/pins_arduino.h +++ b/variants/tbeam-s3-core/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/tbeam-s3-core/platformio.ini index e50d506b9..c71802255 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/tbeam-s3-core/platformio.ini @@ -4,6 +4,8 @@ extends = esp32s3_base board = tbeam-s3-core board_check = true +platform = platformio/espressif32@6.7.0 + lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@1.0.1 From d95e3acab37660c545322df23f8a41eb378e8fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 15:52:22 +0200 Subject: [PATCH 0434/1377] implements #3885 --- src/modules/esp32/PaxcounterModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index b9fdfcb63..e6712871d 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -93,8 +93,8 @@ int32_t PaxcounterModule::runOnce() configuration.wificounter = 1; configuration.wifi_channel_map = WIFI_CHANNEL_ALL; configuration.wifi_channel_switch_interval = 50; - configuration.wifi_rssi_threshold = -80; - configuration.ble_rssi_threshold = -80; + configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80); + configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80); libpax_update_config(&configuration); // internal processing initialization From fce281f54cb89652a62a41ce51f7d6985208a60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 01:40:50 +0200 Subject: [PATCH 0435/1377] update sensor libs --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 0f2907dee..c6efc740d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,7 @@ lib_deps = adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP085 Library@^1.2.4 adafruit/Adafruit BME280 Library@^2.2.2 - https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400 + https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 adafruit/Adafruit MCP9808 Library@^2.0.0 https://github.com/KodinLanewave/INA3221@^1.0.0 @@ -130,8 +130,8 @@ lib_deps = adafruit/Adafruit PM25 AQI Sensor@^1.0.6 adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 - https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17 + lewisxhe/SensorLib@^0.2.0 adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 - https://github.com/adafruit/Adafruit_SHT4X#1.0.4 \ No newline at end of file + adafruit/Adafruit SHT4x Library@^1.0.4 \ No newline at end of file From d02e12a4245dafa9508c1b8a993dd9e9acdbe576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 15:08:59 +0200 Subject: [PATCH 0436/1377] fix include path --- variants/Dongle_nRF52840-pca10059-v1/platformio.ini | 2 +- variants/MakePython_nRF52840_eink/platformio.ini | 2 +- variants/MakePython_nRF52840_oled/platformio.ini | 2 +- variants/TWC_mesh_v4/platformio.ini | 2 +- variants/canaryone/platformio.ini | 2 +- variants/diy/platformio.ini | 4 ++-- variants/feather_diy/platformio.ini | 2 +- variants/lora_relay_v1/platformio.ini | 2 +- variants/lora_relay_v2/platformio.ini | 2 +- variants/monteops_hw1/platformio.ini | 2 +- variants/nano-g2-ultra/platformio.ini | 2 +- variants/pca10056-rc-clock/platformio.ini | 2 +- variants/rak10701/platformio.ini | 2 +- variants/rak11310/platformio.ini | 2 +- variants/rak4631/platformio.ini | 2 +- variants/rak4631_epaper/platformio.ini | 2 +- variants/rak4631_epaper_onrxtx/platformio.ini | 2 +- variants/rp2040-lora/platformio.ini | 2 +- variants/rpipico-slowclock/platformio.ini | 2 +- variants/rpipico/platformio.ini | 2 +- variants/rpipicow/platformio.ini | 2 +- variants/senselora_rp2040/platformio.ini | 2 +- variants/t-echo/platformio.ini | 2 +- variants/xiao_ble/platformio.ini | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini index 2d14f1ca1..c87f83d39 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -2,7 +2,7 @@ extends = nrf52840_base board = nordic_pca10059 build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DEINK_DISPLAY_MODEL=GxEPD2_420_M01 -DEINK_WIDTH=300 -DEINK_HEIGHT=400 diff --git a/variants/MakePython_nRF52840_eink/platformio.ini b/variants/MakePython_nRF52840_eink/platformio.ini index f421466ec..b11b54c7d 100644 --- a/variants/MakePython_nRF52840_eink/platformio.ini +++ b/variants/MakePython_nRF52840_eink/platformio.ini @@ -3,7 +3,7 @@ board_level = extra extends = nrf52840_base board = nordic_pca10059 build_flags = ${nrf52840_base.build_flags} -Ivariants/MakePython_nRF52840_eink -D PRIVATE_HW - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -D PIN_EINK_EN build_src_filter = ${nrf52_base.build_src_filter} +<../variants/MakePython_nRF52840_eink> lib_deps = diff --git a/variants/MakePython_nRF52840_oled/platformio.ini b/variants/MakePython_nRF52840_oled/platformio.ini index e0ddb1377..0146385e0 100644 --- a/variants/MakePython_nRF52840_oled/platformio.ini +++ b/variants/MakePython_nRF52840_oled/platformio.ini @@ -3,7 +3,7 @@ board_level = extra extends = nrf52840_base board = nordic_pca10059 build_flags = ${nrf52840_base.build_flags} -Ivariants/MakePython_nRF52840_oled -D PRIVATE_HW - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/MakePython_nRF52840_oled> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/TWC_mesh_v4/platformio.ini b/variants/TWC_mesh_v4/platformio.ini index 481682143..4fb382334 100644 --- a/variants/TWC_mesh_v4/platformio.ini +++ b/variants/TWC_mesh_v4/platformio.ini @@ -2,7 +2,7 @@ extends = nrf52840_base board = nordic_pca10059 board_level = extra -build_flags = ${nrf52840_base.build_flags} -I variants/TWC_mesh_v4 -D TWC_mesh_v4 -L".pio\libdeps\TWC_mesh_v4\BSEC2 Software Library\src\cortex-m4\fpv4-sp-d16-hard" +build_flags = ${nrf52840_base.build_flags} -I variants/TWC_mesh_v4 -D TWC_mesh_v4 -L".pio\libdeps\TWC_mesh_v4\bsec2\src\cortex-m4\fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/TWC_mesh_v4> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/canaryone/platformio.ini b/variants/canaryone/platformio.ini index 4917f52c7..5e01c3763 100644 --- a/variants/canaryone/platformio.ini +++ b/variants/canaryone/platformio.ini @@ -6,7 +6,7 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/canaryone - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/canaryone> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/diy/platformio.ini b/variants/diy/platformio.ini index 94d59553d..adc10de44 100644 --- a/variants/diy/platformio.ini +++ b/variants/diy/platformio.ini @@ -53,7 +53,7 @@ build_flags = ${nrf52840_base.build_flags} -I variants/diy/nrf52_promicro_diy_xtal -D NRF52_PROMICRO_DIY -D OLED_RU - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_xtal> lib_deps = ${nrf52840_base.lib_deps} @@ -69,7 +69,7 @@ build_flags = ${nrf52840_base.build_flags} -I variants/diy/nrf52_promicro_diy_tcxo -D NRF52_PROMICRO_DIY -D OLED_RU - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_tcxo> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/feather_diy/platformio.ini b/variants/feather_diy/platformio.ini index 47c864b8e..82dbb317c 100644 --- a/variants/feather_diy/platformio.ini +++ b/variants/feather_diy/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = adafruit_feather_nrf52840 build_flags = ${nrf52840_base.build_flags} -Ivariants/feather_diy -Dfeather_diy - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/feather_diy> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/lora_relay_v1/platformio.ini b/variants/lora_relay_v1/platformio.ini index 8660bf64a..435d256c5 100644 --- a/variants/lora_relay_v1/platformio.ini +++ b/variants/lora_relay_v1/platformio.ini @@ -15,7 +15,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/lora_relay_v1 -DTFT_DC=ST7735_RS -DTFT_RST=ST7735_RESET -DSPI_FREQUENCY=27000000 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/lora_relay_v1> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/lora_relay_v2/platformio.ini b/variants/lora_relay_v2/platformio.ini index cd2109f00..3598466d5 100644 --- a/variants/lora_relay_v2/platformio.ini +++ b/variants/lora_relay_v2/platformio.ini @@ -17,7 +17,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/lora_relay_v2 -DSPI_FREQUENCY=27000000 -DTFT_WR=ST7735_SDA -DTFT_SCLK=ST7735_SCK - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/lora_relay_v2> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/monteops_hw1/platformio.ini b/variants/monteops_hw1/platformio.ini index f9d260e74..551419abc 100644 --- a/variants/monteops_hw1/platformio.ini +++ b/variants/monteops_hw1/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/monteops_hw1 -D MONTEOPS_HW1 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/monteops_hw1> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/nano-g2-ultra/platformio.ini b/variants/nano-g2-ultra/platformio.ini index 2b011e032..913b38e3f 100644 --- a/variants/nano-g2-ultra/platformio.ini +++ b/variants/nano-g2-ultra/platformio.ini @@ -5,7 +5,7 @@ board = nano-g2-ultra debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/nano-g2-ultra -D NANO_G2_ULTRA - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nano-g2-ultra> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/pca10056-rc-clock/platformio.ini b/variants/pca10056-rc-clock/platformio.ini index 0467b1417..f8cff4d73 100644 --- a/variants/pca10056-rc-clock/platformio.ini +++ b/variants/pca10056-rc-clock/platformio.ini @@ -5,5 +5,5 @@ extends = nrf52840_base board = nrf52840_dk_modified # add our variants files to the include and src paths build_flags = ${nrf52_base.build_flags} -Ivariants/pca10056-rc-clock - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/pca10056-rc-clock> \ No newline at end of file diff --git a/variants/rak10701/platformio.ini b/variants/rak10701/platformio.ini index ae43b1906..75e29bca0 100644 --- a/variants/rak10701/platformio.ini +++ b/variants/rak10701/platformio.ini @@ -4,7 +4,7 @@ extends = nrf52840_base board_level = extra board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak10701 -D RAK_4631 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index 6495278bf..e1bd2b1b0 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -8,7 +8,7 @@ build_flags = ${rp2040_base.build_flags} -DRAK11310 -Ivariants/rak11310 -DDEBUG_RP2040_PORT=Serial - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 115e96967..f64811429 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -4,7 +4,7 @@ extends = nrf52840_base board = wiscore_rak4631 board_check = true build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 diff --git a/variants/rak4631_epaper/platformio.ini b/variants/rak4631_epaper/platformio.ini index ced732d94..1ca9a2157 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/rak4631_epaper/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini index c4a13ec82..e0a0a5a58 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -4,7 +4,7 @@ board_level = extra extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -D PIN_EINK_EN=34 -D EINK_DISPLAY_MODEL=GxEPD2_213_BN -D EINK_WIDTH=250 diff --git a/variants/rp2040-lora/platformio.ini b/variants/rp2040-lora/platformio.ini index a1d6bea9d..8499f6f3c 100644 --- a/variants/rp2040-lora/platformio.ini +++ b/variants/rp2040-lora/platformio.ini @@ -9,7 +9,7 @@ build_flags = ${rp2040_base.build_flags} -Ivariants/rp2040-lora -DDEBUG_RP2040_PORT=Serial -DHW_SPI1_DEVICE - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} diff --git a/variants/rpipico-slowclock/platformio.ini b/variants/rpipico-slowclock/platformio.ini index 0b94eb9c6..c21994249 100644 --- a/variants/rpipico-slowclock/platformio.ini +++ b/variants/rpipico-slowclock/platformio.ini @@ -19,7 +19,7 @@ build_flags = ${rp2040_base.build_flags} -Ivariants/rpipico-slowclock -DDEBUG_RP2040_PORT=Serial2 -DHW_SPI1_DEVICE - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" -g -DNO_USB lib_deps = diff --git a/variants/rpipico/platformio.ini b/variants/rpipico/platformio.ini index 9537694ec..e4b9e479f 100644 --- a/variants/rpipico/platformio.ini +++ b/variants/rpipico/platformio.ini @@ -9,7 +9,7 @@ build_flags = ${rp2040_base.build_flags} -Ivariants/rpipico -DDEBUG_RP2040_PORT=Serial -DHW_SPI1_DEVICE - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} diff --git a/variants/rpipicow/platformio.ini b/variants/rpipicow/platformio.ini index 91ec964d9..2600b4b38 100644 --- a/variants/rpipicow/platformio.ini +++ b/variants/rpipicow/platformio.ini @@ -8,7 +8,7 @@ build_flags = ${rp2040_base.build_flags} -DRPI_PICO -Ivariants/rpipicow -DHW_SPI1_DEVICE - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" -fexceptions # for exception handling in MQTT build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = diff --git a/variants/senselora_rp2040/platformio.ini b/variants/senselora_rp2040/platformio.ini index 3b3253ee8..c719bd261 100644 --- a/variants/senselora_rp2040/platformio.ini +++ b/variants/senselora_rp2040/platformio.ini @@ -8,6 +8,6 @@ build_flags = ${rp2040_base.build_flags} -DSENSELORA_RP2040 -Ivariants/senselora_rp2040 -DDEBUG_RP2040_PORT=Serial - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" lib_deps = ${rp2040_base.lib_deps} \ No newline at end of file diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index aa8177b33..5b295c96a 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -8,7 +8,7 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DGPS_POWER_TOGGLE - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 -DEINK_HEIGHT=200 diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index c52e2c644..8e9a663a9 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -4,7 +4,7 @@ extends = nrf52840_base board = xiao_ble_sense board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Dxiao_ble -D EBYTE_E22 - -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/xiao_ble> lib_deps = ${nrf52840_base.lib_deps} From 57575f8e491270655585d2428a9210ca9393b616 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Thu, 16 May 2024 08:11:46 -0700 Subject: [PATCH 0437/1377] remove has screen = 0 --- variants/heltec_wsl_v3/variant.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/heltec_wsl_v3/variant.h b/variants/heltec_wsl_v3/variant.h index d3a009ade..917ea7fb9 100644 --- a/variants/heltec_wsl_v3/variant.h +++ b/variants/heltec_wsl_v3/variant.h @@ -3,8 +3,6 @@ #define LED_PIN LED -#define HAS_SCREEN 0 - #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define BUTTON_PIN 0 From 4087bd93a91137f6a14c9561b5dc565be5cdd640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 16 May 2024 17:35:14 +0200 Subject: [PATCH 0438/1377] Axe trunk from check We run that anyway as a separate job No need to run it in the matrix. --- .github/workflows/main_matrix.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 7affd8fc1..d329e693a 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -55,11 +55,6 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - - name: Trunk Check - if: ${{ github.event_name != 'workflow_dispatch' }} - uses: trunk-io/trunk-action@v1 - - name: Check ${{ matrix.board }} run: bin/check-all.sh ${{ matrix.board }} From 0976705f2580edc50f2b8ae4a64fd5fc2611c8f2 Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Thu, 16 May 2024 17:43:23 +0000 Subject: [PATCH 0439/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/module_config.pb.h | 16 ++++++++++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index bee9ddc71..5cfadd148 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bee9ddc71677e974568a5a95362d78acac6bb974 +Subproject commit 5cfadd14890b7723a1fe6e7683f711911154b010 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index b8cc80633..a5cbc42a5 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -308,7 +308,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3346 +#define meshtastic_OEMStore_size 3368 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 1799f49da..1b8123ef5 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -182,7 +182,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 537 -#define meshtastic_LocalModuleConfig_size 663 +#define meshtastic_LocalModuleConfig_size 685 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index ffda48704..f3c48ee6d 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -188,6 +188,10 @@ typedef struct _meshtastic_ModuleConfig_PaxcounterConfig { /* Enable the Paxcounter Module */ bool enabled; uint32_t paxcounter_update_interval; + /* WiFi RSSI threshold. Defaults to -80 */ + int32_t wifi_threshold; + /* BLE RSSI threshold. Defaults to -80 */ + int32_t ble_threshold; } meshtastic_ModuleConfig_PaxcounterConfig; /* Serial Config */ @@ -467,7 +471,7 @@ extern "C" { #define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0} #define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0} +#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} @@ -483,7 +487,7 @@ extern "C" { #define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0} #define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0} +#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} @@ -526,6 +530,8 @@ extern "C" { #define meshtastic_ModuleConfig_AudioConfig_i2s_sck_tag 7 #define meshtastic_ModuleConfig_PaxcounterConfig_enabled_tag 1 #define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2 +#define meshtastic_ModuleConfig_PaxcounterConfig_wifi_threshold_tag 3 +#define meshtastic_ModuleConfig_PaxcounterConfig_ble_threshold_tag 4 #define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1 #define meshtastic_ModuleConfig_SerialConfig_echo_tag 2 #define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3 @@ -695,7 +701,9 @@ X(a, STATIC, SINGULAR, UINT32, i2s_sck, 7) #define meshtastic_ModuleConfig_PaxcounterConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ -X(a, STATIC, SINGULAR, UINT32, paxcounter_update_interval, 2) +X(a, STATIC, SINGULAR, UINT32, paxcounter_update_interval, 2) \ +X(a, STATIC, SINGULAR, INT32, wifi_threshold, 3) \ +X(a, STATIC, SINGULAR, INT32, ble_threshold, 4) #define meshtastic_ModuleConfig_PaxcounterConfig_CALLBACK NULL #define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL @@ -836,7 +844,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_MQTTConfig_size 254 #define meshtastic_ModuleConfig_MapReportSettings_size 12 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 8 -#define meshtastic_ModuleConfig_PaxcounterConfig_size 8 +#define meshtastic_ModuleConfig_PaxcounterConfig_size 30 #define meshtastic_ModuleConfig_RangeTestConfig_size 10 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 From f3cf9a5e719930408b35ac24b86b1065e8c5c180 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 17 May 2024 08:37:09 +1200 Subject: [PATCH 0440/1377] Adjust refresh for Heltec Wireless Paper V1.1 (#3913) --- src/graphics/EInkDynamicDisplay.h | 1 + variants/heltec_wireless_paper/platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 8f3ce205a..9e131dca7 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -109,6 +109,7 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for // Optional - track ghosting, pixel by pixel + // May 2024: no longer used by any display. Kept for possible future use. #ifdef EINK_LIMIT_GHOSTING_PX void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index d7aac5e22..afbbd8be9 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -12,12 +12,12 @@ build_flags = -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates - -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file From b4a7e78d182f72e4b9c1309ea55afd24d1f5edc9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 16 May 2024 17:27:36 -0500 Subject: [PATCH 0441/1377] Don't reboot for certain config prefs and make accelerometer thread re-entrant (#3889) * Don't reboot for certain config prefs and make accelerometer thread re-entrant * WHOOPS * Don't reboot for LED heartbeat and button press * Remove TZ --- src/AccelerometerThread.h | 89 ++++++++++++++++++++----------------- src/main.cpp | 6 +-- src/main.h | 5 +++ src/modules/AdminModule.cpp | 47 +++++++++++++++++++- 4 files changed, 99 insertions(+), 48 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 66e5624f1..ad40cd9bd 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -1,3 +1,4 @@ +#pragma once #include "configuration.h" #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR @@ -14,13 +15,10 @@ #include #include -SensorBMA423 bmaSensor; -bool BMA_IRQ = false; - #define ACCELEROMETER_CHECK_INTERVAL_MS 100 #define ACCELEROMETER_CLICK_THRESHOLD 40 -int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) +static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) { Wire.beginTransmission(address); Wire.write(reg); @@ -33,7 +31,7 @@ int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) return 0; // Pass } -int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) +static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) { Wire.beginTransmission(address); Wire.write(reg); @@ -41,8 +39,6 @@ int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) return (0 != Wire.endTransmission()); } -namespace concurrency -{ class AccelerometerThread : public concurrency::OSThread { public: @@ -53,14 +49,55 @@ class AccelerometerThread : public concurrency::OSThread disable(); return; } + acceleremoter_type = type; if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n"); disable(); return; } + init(); + } - acceleremoter_type = type; + void start() + { + init(); + setIntervalFromNow(0); + }; + + protected: + int32_t runOnce() override + { + canSleep = true; // Assume we should not keep the board awake + + if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) { + wakeScreen(); + } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) { + uint8_t click = lis.getClick(); + if (!config.device.double_tap_as_button_press) { + wakeScreen(); + } + + if (config.device.double_tap_as_button_press && (click & 0x20)) { + buttonPress(); + return 500; + } + } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) { + if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) { + wakeScreen(); + return 500; + } + } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { + wakeScreen(); + return 500; + } + + return ACCELEROMETER_CHECK_INTERVAL_MS; + } + + private: + void init() + { LOG_DEBUG("AccelerometerThread initializing\n"); if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.begin(accelerometer_found.address)) { @@ -123,38 +160,6 @@ class AccelerometerThread : public concurrency::OSThread // Duration is number of occurances needed to trigger, higher threshold is less sensitive } } - - protected: - int32_t runOnce() override - { - canSleep = true; // Assume we should not keep the board awake - - if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) { - wakeScreen(); - } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) { - uint8_t click = lis.getClick(); - if (!config.device.double_tap_as_button_press) { - wakeScreen(); - } - - if (config.device.double_tap_as_button_press && (click & 0x20)) { - buttonPress(); - return 500; - } - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) { - if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) { - wakeScreen(); - return 500; - } - } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { - wakeScreen(); - return 500; - } - - return ACCELEROMETER_CHECK_INTERVAL_MS; - } - - private: void wakeScreen() { if (powerFSM.getState() == &stateDARK) { @@ -173,8 +178,8 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_MPU6050 mpu; Adafruit_LIS3DH lis; Adafruit_LSM6DS3TRC lsm; + SensorBMA423 bmaSensor; + bool BMA_IRQ = false; }; -} // namespace concurrency - #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f14d08188..64ff92dd7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,9 +94,10 @@ NRF52Bluetooth *nrf52Bluetooth; #include "PowerFSMThread.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "AccelerometerThread.h" #include "AmbientLightingThread.h" +AccelerometerThread *accelerometerThread; #endif #ifdef HAS_I2S @@ -197,9 +198,6 @@ uint32_t timeLastPowered = 0; static Periodic *ledPeriodic; static OSThread *powerFSMthread; -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -static OSThread *accelerometerThread; -#endif static OSThread *ambientLightingThread; SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); diff --git a/src/main.h b/src/main.h index bb812b7b6..db05a4734 100644 --- a/src/main.h +++ b/src/main.h @@ -53,6 +53,11 @@ extern Adafruit_DRV2605 drv; extern AudioThread *audioThread; #endif +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#include "AccelerometerThread.h" +extern AccelerometerThread *accelerometerThread; +#endif + extern bool isVibrating; extern int TCPPort; // set by Portduino diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index adf5620ba..c9416a9b5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -26,6 +26,9 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" #endif +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#include "AccelerometerThread.h" +#endif AdminModule *adminModule; bool hasOpenEditTransaction; @@ -221,12 +224,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta nodeDB->setLocalPosition(r->set_fixed_position); config.position.fixed_position = true; saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); - // Send our new fixed position to the mesh for good measure - positionModule->sendOurPosition(); #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) gps->enable(); #endif + // Send our new fixed position to the mesh for good measure + positionModule->sendOurPosition(); } break; } @@ -352,6 +355,26 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_device_tag: LOG_INFO("Setting config: Device\n"); config.has_device = true; +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true) { + accelerometerThread->start(); + } +#endif +#ifdef LED_PIN + // Turn LED off if heartbeat by config + if (c.payload_variant.device.led_heartbeat_disabled) { + digitalWrite(LED_PIN, LOW ^ LED_INVERTED); + } +#endif + if (config.device.button_gpio == c.payload_variant.device.button_gpio && + config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio && + config.device.debug_log_enabled == c.payload_variant.device.debug_log_enabled && + config.device.serial_enabled == c.payload_variant.device.serial_enabled && + config.device.role == c.payload_variant.device.role && + config.device.disable_triple_click == c.payload_variant.device.disable_triple_click && + config.device.rebroadcast_mode == c.payload_variant.device.rebroadcast_mode) { + requiresReboot = false; + } config.device = c.payload_variant.device; // If we're setting router role for the first time, install its intervals if (existingRole != c.payload_variant.device.role) @@ -371,6 +394,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_power_tag: LOG_INFO("Setting config: Power\n"); config.has_power = true; + // Really just the adc override is the only thing that can change without a reboot + if (config.power.device_battery_ina_address == c.payload_variant.power.device_battery_ina_address && + config.power.is_power_saving == c.payload_variant.power.is_power_saving && + config.power.ls_secs == c.payload_variant.power.ls_secs && + config.power.min_wake_secs == c.payload_variant.power.min_wake_secs && + config.power.on_battery_shutdown_after_secs == c.payload_variant.power.on_battery_shutdown_after_secs && + config.power.sds_secs == c.payload_variant.power.sds_secs && + config.power.wait_bluetooth_secs == c.payload_variant.power.wait_bluetooth_secs) { + requiresReboot = false; + } config.power = c.payload_variant.power; break; case meshtastic_Config_network_tag: @@ -381,6 +414,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_display_tag: LOG_INFO("Setting config: Display\n"); config.has_display = true; + if (config.display.screen_on_secs == c.payload_variant.display.screen_on_secs && + config.display.flip_screen == c.payload_variant.display.flip_screen && + config.display.oled == c.payload_variant.display.oled) { + requiresReboot = false; + } +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true) { + accelerometerThread->start(); + } +#endif config.display = c.payload_variant.display; break; case meshtastic_Config_lora_tag: From 314d2e2da191246754be6df0867237c0656b2e27 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 17 May 2024 02:34:18 -0500 Subject: [PATCH 0442/1377] debconf expects absolute paths. --- .github/workflows/package_raspbian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 367f90c56..43c79a8c2 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -57,7 +57,7 @@ jobs: cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml chmod +x .debpkg/usr/sbin/meshtasticd cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service - echo "etc/meshtasticd/config.yaml" > .debpkg/debian/conffiles + echo "/etc/meshtasticd/config.yaml" > .debpkg/debian/conffiles - uses: jiro4989/build-deb-action@v3 with: From a2284e3d5242d9e5cb484a6dc349b8129598e2cc Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 17 May 2024 03:32:11 -0500 Subject: [PATCH 0443/1377] DEBIAN is case sensitive --- .github/workflows/package_raspbian.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 43c79a8c2..f6e40052e 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -46,7 +46,7 @@ jobs: - name: build .debpkg run: | - mkdir -p .debpkg/debian + mkdir -p .debpkg/DEBIAN mkdir -p .debpkg/usr/share/doc/meshtasticd/web mkdir -p .debpkg/usr/sbin mkdir -p .debpkg/etc/meshtasticd @@ -57,7 +57,8 @@ jobs: cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml chmod +x .debpkg/usr/sbin/meshtasticd cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service - echo "/etc/meshtasticd/config.yaml" > .debpkg/debian/conffiles + echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles + chmod +x .debpkg/DEBIAN/conffiles - uses: jiro4989/build-deb-action@v3 with: From b161649989d3fea8e2ca080f25d0b88d1169e401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 18 May 2024 10:22:07 +0200 Subject: [PATCH 0444/1377] remove screen pinning for pico targets --- variants/rpipico/variant.h | 2 -- variants/rpipicow/variant.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/variants/rpipico/variant.h b/variants/rpipico/variant.h index ad6d0b211..7efaeaf7a 100644 --- a/variants/rpipico/variant.h +++ b/variants/rpipico/variant.h @@ -4,8 +4,6 @@ #define ARDUINO_ARCH_AVR -#define USE_SH1106 1 - // default I2C pins: // SDA = 4 // SCL = 5 diff --git a/variants/rpipicow/variant.h b/variants/rpipicow/variant.h index a17f05ee0..24da8f932 100644 --- a/variants/rpipicow/variant.h +++ b/variants/rpipicow/variant.h @@ -8,8 +8,6 @@ #define HAS_WIFI 1 #endif -#define USE_SH1106 1 - // default I2C pins: // SDA = 4 // SCL = 5 From 108dfdc2ecd38ed6e3007036da49a10ec4e4b7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 18 May 2024 10:41:32 +0200 Subject: [PATCH 0445/1377] update trunk --- .trunk/trunk.yaml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 0826b71d9..8a2f18ad5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,32 +1,32 @@ version: 0.1 cli: - version: 1.20.1 + version: 1.22.1 plugins: sources: - id: trunk - ref: v1.4.4 + ref: v1.5.0 uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.68.5 + - trufflehog@3.76.3 - yamllint@1.35.1 - - bandit@1.7.7 - - checkov@3.2.32 + - bandit@1.7.8 + - checkov@3.2.95 - terrascan@1.19.1 - - trivy@0.49.1 + - trivy@0.51.1 #- trufflehog@3.63.2-rc0 - taplo@0.8.1 - - ruff@0.3.1 + - ruff@0.4.4 - isort@5.13.2 - - markdownlint@0.39.0 - - oxipng@9.0.0 - - svgo@3.2.0 - - actionlint@1.6.27 + - markdownlint@0.40.0 + - oxipng@9.1.1 + - svgo@3.3.2 + - actionlint@1.7.0 - flake8@7.0.0 - hadolint@2.12.0 - shfmt@3.6.0 - - shellcheck@0.9.0 - - black@24.2.0 + - shellcheck@0.10.0 + - black@24.4.2 - git-diff-check - gitleaks@8.18.2 - clang-format@16.0.3 From 7ef9fec446b6a214d3c5ee39f8c1cb66695c7102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 17 May 2024 10:21:56 +0200 Subject: [PATCH 0446/1377] PLEASE TEST move the power rail init earlier in the startup process on 4630 --- src/main.cpp | 11 ----------- variants/monteops_hw1/variant.cpp | 4 ++++ variants/rak10701/variant.cpp | 4 ++++ variants/rak4631/variant.cpp | 4 ++++ variants/rak4631_epaper/variant.cpp | 4 ++++ variants/rak4631_epaper_onrxtx/variant.cpp | 4 ++++ 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 64ff92dd7..4a9fef5d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -360,18 +360,11 @@ void setup() delay(1); #endif -#ifdef RAK4630 -#ifdef PIN_3V3_EN - // We need to enable 3.3V periphery in order to scan it - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); -#endif #ifdef AQ_SET_PIN // RAK-12039 set pin for Air quality sensor pinMode(AQ_SET_PIN, OUTPUT); digitalWrite(AQ_SET_PIN, HIGH); #endif -#endif #ifdef T_DECK // enable keyboard @@ -548,10 +541,6 @@ void setup() setupSDCard(); #endif -#ifdef RAK4630 - // scanEInkDevice(); -#endif - // LED init #ifdef LED_PIN diff --git a/variants/monteops_hw1/variant.cpp b/variants/monteops_hw1/variant.cpp index 75cca1dc3..e84b60b3b 100644 --- a/variants/monteops_hw1/variant.cpp +++ b/variants/monteops_hw1/variant.cpp @@ -38,4 +38,8 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } diff --git a/variants/rak10701/variant.cpp b/variants/rak10701/variant.cpp index 2b4bd39a6..5a3587982 100644 --- a/variants/rak10701/variant.cpp +++ b/variants/rak10701/variant.cpp @@ -38,4 +38,8 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } \ No newline at end of file diff --git a/variants/rak4631/variant.cpp b/variants/rak4631/variant.cpp index 75cca1dc3..e84b60b3b 100644 --- a/variants/rak4631/variant.cpp +++ b/variants/rak4631/variant.cpp @@ -38,4 +38,8 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } diff --git a/variants/rak4631_epaper/variant.cpp b/variants/rak4631_epaper/variant.cpp index 75cca1dc3..e84b60b3b 100644 --- a/variants/rak4631_epaper/variant.cpp +++ b/variants/rak4631_epaper/variant.cpp @@ -38,4 +38,8 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } diff --git a/variants/rak4631_epaper_onrxtx/variant.cpp b/variants/rak4631_epaper_onrxtx/variant.cpp index 75cca1dc3..e84b60b3b 100644 --- a/variants/rak4631_epaper_onrxtx/variant.cpp +++ b/variants/rak4631_epaper_onrxtx/variant.cpp @@ -38,4 +38,8 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } From cf0424922ac79c4e092fbd06d3443be5f372dd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 17 May 2024 10:42:23 +0200 Subject: [PATCH 0447/1377] target does not use the powerrail --- variants/monteops_hw1/variant.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/variants/monteops_hw1/variant.cpp b/variants/monteops_hw1/variant.cpp index e84b60b3b..75cca1dc3 100644 --- a/variants/monteops_hw1/variant.cpp +++ b/variants/monteops_hw1/variant.cpp @@ -38,8 +38,4 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2); - - // 3V3 Power Rail - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); } From 84d3117a7aa930d31721929613c17eb1097974ea Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 18 May 2024 12:21:35 -0500 Subject: [PATCH 0448/1377] Lock Portduino to MAGIC_USB_BATTERY_LEVEL for now (#3894) --- src/modules/Telemetry/DeviceTelemetry.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 3529267cb..002ce62a9 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -69,8 +69,12 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() t.time = getTime(); t.which_variant = meshtastic_Telemetry_device_metrics_tag; t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent(); +#if ARCH_PORTDUINO + t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL; +#else t.variant.device_metrics.battery_level = powerStatus->getIsCharging() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent(); +#endif t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent(); t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0; t.variant.device_metrics.uptime_seconds = getUptimeSeconds(); @@ -100,4 +104,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) service.sendToMesh(p, RX_SRC_LOCAL, true); } return true; -} \ No newline at end of file +} From 1ec0e750a3296eadb28bb0e57a4a5e3bab439a94 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sat, 18 May 2024 22:14:22 -0400 Subject: [PATCH 0449/1377] Added fix for ESP32 --- src/detect/ScanI2C.cpp | 28 ++++++++++++++++++++++++++++ src/detect/ScanI2C.h | 1 + src/main.cpp | 3 +++ src/main.h | 3 ++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 149bb95f0..941fdf3e8 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,4 +1,6 @@ #include "ScanI2C.h" +#include "main.h" +#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -27,7 +29,33 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } +bool performScanForCardKB() { + // Example I2C scan code for CardKB (adjust as needed) + Wire.beginTransmission(CARDKB_I2C_ADDRESS); + if (Wire.endTransmission() == 0) { + return true; // CardKB detected + } + return false; // CardKB not detected +} +void scanForCardKB() { + const int maxRetries = 10; // Maximum number of retries + const int retryDelay = 100; // Delay between retries in milliseconds + for (int i = 0; i < maxRetries; ++i) { + // Perform the scan (example scan code, adjust as needed) + cardKBDetected = performScanForCardKB(); + + if (cardKBDetected) { + Serial.println("CardKB Keyboard detected."); + break; + } + + delay(retryDelay); // Wait before the next retry + } + if (!cardKBDetected) { + Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); + } +} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 6c01b9100..15668aecb 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,6 +2,7 @@ #include #include +bool performScanForCardKB(); class ScanI2C { diff --git a/src/main.cpp b/src/main.cpp index 4a9fef5d0..a3e9258ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,8 @@ #include // #include +bool cardKBDetected = false; +void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -374,6 +376,7 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif +scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index db05a4734..737d424a7 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif - +extern bool cardKBDetected; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,6 +39,7 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; +#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From 4a05874dba4392264fb2a5ce4a8e299dc7c29b55 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 19 May 2024 07:24:10 -0500 Subject: [PATCH 0450/1377] Try-fix: Remove logging of actual payload strings (and compressed) for TAK packets (#3922) * Remove logging of actual payload strings (and compressed) for TAK packets * Don't assert / reboot. Log error and skip decode --- src/mesh/Router.cpp | 5 ++++- src/modules/AtakPluginModule.cpp | 31 ++++++++++--------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 4189bca66..3141d986b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -311,7 +311,10 @@ bool perhapsDecode(meshtastic_MeshPacket *p) if (channels.decryptForHash(chIndex, p->channel)) { // Try to decrypt the packet if we can size_t rawSize = p->encrypted.size; - assert(rawSize <= sizeof(bytes)); + if (rawSize > sizeof(bytes)) { + LOG_ERROR("Packet too large to attempt decription! (rawSize=%d > 256)\n", rawSize); + return false; + } memcpy(bytes, p->encrypted.bytes, rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf crypto->decrypt(p->from, p->id, rawSize, bytes); diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index 64a85e2bf..b460602ef 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -64,40 +64,33 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast { // From Phone (EUD) if (mp.from == 0) { - LOG_DEBUG("Received uncompressed TAK payload from phone with %d bytes\n", mp.decoded.payload.size); + LOG_DEBUG("Received uncompressed TAK payload from phone: %d bytes\n", mp.decoded.payload.size); // Compress for LoRA transport auto compressed = cloneTAKPacketData(t); compressed.is_compressed = true; if (t->has_contact) { auto length = unishox2_compress_simple(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign); - LOG_DEBUG("Uncompressed callsign '%s' - %d bytes\n", t->contact.callsign, strlen(t->contact.callsign)); - LOG_DEBUG("Compressed callsign '%s' - %d bytes\n", t->contact.callsign, length); + LOG_DEBUG("Compressed callsign: %d bytes\n", length); length = unishox2_compress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign), compressed.contact.device_callsign); - LOG_DEBUG("Uncompressed device_callsign '%s' - %d bytes\n", t->contact.device_callsign, - strlen(t->contact.device_callsign)); - LOG_DEBUG("Compressed device_callsign '%s' - %d bytes\n", compressed.contact.device_callsign, length); + LOG_DEBUG("Compressed device_callsign: %d bytes\n", length); } if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) { auto length = unishox2_compress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), compressed.payload_variant.chat.message); - LOG_DEBUG("Uncompressed chat message '%s' - %d bytes\n", t->payload_variant.chat.message, - strlen(t->payload_variant.chat.message)); - LOG_DEBUG("Compressed chat message '%s' - %d bytes\n", compressed.payload_variant.chat.message, length); + LOG_DEBUG("Compressed chat message: %d bytes\n", length); if (t->payload_variant.chat.has_to) { compressed.payload_variant.chat.has_to = true; length = unishox2_compress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), compressed.payload_variant.chat.to); - LOG_DEBUG("Uncompressed chat to '%s' - %d bytes\n", t->payload_variant.chat.to, - strlen(t->payload_variant.chat.to)); - LOG_DEBUG("Compressed chat to '%s' - %d bytes\n", compressed.payload_variant.chat.to, length); + LOG_DEBUG("Compressed chat to: %d bytes\n", length); } } mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), meshtastic_TAKPacket_fields, &compressed); - LOG_DEBUG("Final payload size of %d bytes\n", mp.decoded.payload.size); + LOG_DEBUG("Final payload: %d bytes\n", mp.decoded.payload.size); } else { if (!t->is_compressed) { // Not compressed. Something is wrong @@ -113,27 +106,23 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast auto length = unishox2_decompress_simple(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign); - LOG_DEBUG("Compressed callsign: %d bytes\n", strlen(t->contact.callsign)); - LOG_DEBUG("Decompressed callsign: '%s' @ %d bytes\n", uncompressed.contact.callsign, length); + LOG_DEBUG("Decompressed callsign: %d bytes\n", length); length = unishox2_decompress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign), uncompressed.contact.device_callsign); - LOG_DEBUG("Compressed device_callsign: %d bytes\n", strlen(t->contact.device_callsign)); - LOG_DEBUG("Decompressed device_callsign: '%s' @ %d bytes\n", uncompressed.contact.device_callsign, length); + LOG_DEBUG("Decompressed device_callsign: %d bytes\n", length); } if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) { auto length = unishox2_decompress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), uncompressed.payload_variant.chat.message); - LOG_DEBUG("Compressed chat message: %d bytes\n", strlen(t->payload_variant.chat.message)); - LOG_DEBUG("Decompressed chat message: '%s' @ %d bytes\n", uncompressed.payload_variant.chat.message, length); + LOG_DEBUG("Decompressed chat message: %d bytes\n", length); if (t->payload_variant.chat.has_to) { uncompressed.payload_variant.chat.has_to = true; length = unishox2_decompress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), uncompressed.payload_variant.chat.to); - LOG_DEBUG("Compressed chat to: %d bytes\n", strlen(t->payload_variant.chat.to)); - LOG_DEBUG("Decompressed chat to: '%s' @ %d bytes\n", uncompressed.payload_variant.chat.to, length); + LOG_DEBUG("Decompressed chat to: %d bytes\n", length); } } decompressedCopy->decoded.payload.size = From 3719ddac038947bdb3a6c46ef4b3563510ffe684 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sun, 19 May 2024 14:25:05 +0200 Subject: [PATCH 0451/1377] automatically propose to setup virtualenv if platformio is having issues in native build (#3923) --- bin/build-native.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bin/build-native.sh b/bin/build-native.sh index 9d31d091a..e8ed61bcf 100755 --- a/bin/build-native.sh +++ b/bin/build-native.sh @@ -2,6 +2,17 @@ set -e +platformioFailed() { + [[ $VIRTUAL_ENV != "" ]] && exit 1 # don't hint at virtualenv if it's already in use + echo -e "\nThere were issues running platformio and you are not using a virtual environment." \ + "\nYou may try setting up virtualenv and downloading the latest platformio from pip:" \ + "\n\tvirtualenv venv" \ + "\n\tsource venv/bin/activate" \ + "\n\tpip install platformio" \ + "\n\t./bin/build-native.sh # retry building" + exit 1 +} + VERSION=$(bin/buildinfo.py long) SHORT_VERSION=$(bin/buildinfo.py short) @@ -13,8 +24,8 @@ mkdir -p $OUTDIR/ rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update --environment native -pio run --environment native +platformio pkg update --environment native || platformioFailed +pio run --environment native || platformioFailed cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)" cp bin/device-install.* $OUTDIR cp bin/device-update.* $OUTDIR From a5fdb663e25055292bef3042110e9b8b77d9bf01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 19 May 2024 19:32:08 +0200 Subject: [PATCH 0452/1377] change the main scan class so they scan only for wanted bits - UNTESTED --- src/detect/ScanI2C.cpp | 29 +-------------------------- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 13 ++++++++++-- src/detect/ScanI2CTwoWire.h | 4 +++- src/input/kbI2cBase.cpp | 37 +++++++++++++++++++++++++++++++++-- src/main.cpp | 3 --- src/main.h | 3 +-- 7 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 941fdf3e8..7d0a83653 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,6 +1,4 @@ #include "ScanI2C.h" -#include "main.h" -#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -8,6 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, int *address) {} void ScanI2C::setSuppressScreen() { @@ -29,33 +28,7 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } -bool performScanForCardKB() { - // Example I2C scan code for CardKB (adjust as needed) - Wire.beginTransmission(CARDKB_I2C_ADDRESS); - if (Wire.endTransmission() == 0) { - return true; // CardKB detected - } - return false; // CardKB not detected -} -void scanForCardKB() { - const int maxRetries = 10; // Maximum number of retries - const int retryDelay = 100; // Delay between retries in milliseconds - for (int i = 0; i < maxRetries; ++i) { - // Perform the scan (example scan code, adjust as needed) - cardKBDetected = performScanForCardKB(); - - if (cardKBDetected) { - Serial.println("CardKB Keyboard detected."); - break; - } - - delay(retryDelay); // Wait before the next retry - } - if (!cardKBDetected) { - Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); - } -} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 15668aecb..4aa4549cc 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,7 +2,6 @@ #include #include -bool performScanForCardKB(); class ScanI2C { @@ -82,6 +81,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); + virtual void scanPort(ScanI2C::I2CPort, int *); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 7828dfb58..f7068ee02 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -135,7 +135,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port) +void ScanI2CTwoWire::scanPort(I2CPort port, int *address) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,6 +163,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port) #endif for (addr.address = 1; addr.address < 127; addr.address++) { + // Skip the address if it is not requested oon a partial scan + if (sizeof(address) > 0 && addr.address != *address) { + continue; + } i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO if (i2cBus->read() != -1) @@ -353,6 +357,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port) } } +void ScanI2CTwoWire::scanPort(I2CPort port) +{ + scanPort(port, nullptr); +} + TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const { if (address.port == ScanI2C::I2CPort::WIRE) { @@ -369,4 +378,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} +} \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 9acd736d2..a7c19c779 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,6 +16,8 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; + void scanPort(ScanI2C::I2CPort, int *) override; + ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const; @@ -53,4 +55,4 @@ class ScanI2CTwoWire : public ScanI2C uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const; DeviceType probeOLED(ScanI2C::DeviceAddress) const; -}; +}; \ No newline at end of file diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index af7c96b20..55f435fda 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -1,6 +1,7 @@ #include "kbI2cBase.h" #include "configuration.h" #include "detect/ScanI2C.h" +#include "detect/ScanI2CTwoWire.h" extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -30,8 +31,40 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { if (cardkb_found.address == 0x00) { - // Input device is not detected. - return INT32_MAX; + // Input device is not detected. Rescan now. + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); + int i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; +#if defined(I2C_SDA1) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan); + auto kb_info = i2cScanner->firstKeyboard(); + + if (kb_info.type != ScanI2C::DeviceType::NONE) { + cardkb_found = kb_info.address; + switch (kb_info.type) { + case ScanI2C::DeviceType::RAK14004: + kb_model = 0x02; + break; + case ScanI2C::DeviceType::CARDKB: + kb_model = 0x00; + break; + case ScanI2C::DeviceType::TDECKKB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x10; + break; + case ScanI2C::DeviceType::BBQ10KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x11; + break; + default: + // use this as default since it's also just zero + LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); + kb_model = 0x00; + } + } + if (cardkb_found.address == 0x00) + return INT32_MAX; } if (!i2cBus) { diff --git a/src/main.cpp b/src/main.cpp index a3e9258ba..4a9fef5d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,8 +35,6 @@ #include // #include -bool cardKBDetected = false; -void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -376,7 +374,6 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif -scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index 737d424a7..db05a4734 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif -extern bool cardKBDetected; + #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,7 +39,6 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; -#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From 3a628047ef40f34060e8c8f7e5132d24be45bcad Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sat, 18 May 2024 22:14:22 -0400 Subject: [PATCH 0453/1377] Added fix for ESP32 --- src/detect/ScanI2C.cpp | 28 ++++++++++++++++++++++++++++ src/detect/ScanI2C.h | 1 + src/main.cpp | 3 +++ src/main.h | 3 ++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 149bb95f0..941fdf3e8 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,4 +1,6 @@ #include "ScanI2C.h" +#include "main.h" +#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -27,7 +29,33 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } +bool performScanForCardKB() { + // Example I2C scan code for CardKB (adjust as needed) + Wire.beginTransmission(CARDKB_I2C_ADDRESS); + if (Wire.endTransmission() == 0) { + return true; // CardKB detected + } + return false; // CardKB not detected +} +void scanForCardKB() { + const int maxRetries = 10; // Maximum number of retries + const int retryDelay = 100; // Delay between retries in milliseconds + for (int i = 0; i < maxRetries; ++i) { + // Perform the scan (example scan code, adjust as needed) + cardKBDetected = performScanForCardKB(); + + if (cardKBDetected) { + Serial.println("CardKB Keyboard detected."); + break; + } + + delay(retryDelay); // Wait before the next retry + } + if (!cardKBDetected) { + Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); + } +} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 6c01b9100..15668aecb 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,6 +2,7 @@ #include #include +bool performScanForCardKB(); class ScanI2C { diff --git a/src/main.cpp b/src/main.cpp index 4a9fef5d0..a3e9258ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,8 @@ #include // #include +bool cardKBDetected = false; +void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -374,6 +376,7 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif +scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index db05a4734..737d424a7 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif - +extern bool cardKBDetected; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,6 +39,7 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; +#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From a37f309c0379dfa161f2dae41f319acdc0b3d0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 19 May 2024 19:32:08 +0200 Subject: [PATCH 0454/1377] change the main scan class so they scan only for wanted bits - UNTESTED --- src/detect/ScanI2C.cpp | 29 +-------------------------- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 13 ++++++++++-- src/detect/ScanI2CTwoWire.h | 4 +++- src/input/kbI2cBase.cpp | 37 +++++++++++++++++++++++++++++++++-- src/main.cpp | 3 --- src/main.h | 3 +-- 7 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 941fdf3e8..7d0a83653 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,6 +1,4 @@ #include "ScanI2C.h" -#include "main.h" -#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -8,6 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, int *address) {} void ScanI2C::setSuppressScreen() { @@ -29,33 +28,7 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } -bool performScanForCardKB() { - // Example I2C scan code for CardKB (adjust as needed) - Wire.beginTransmission(CARDKB_I2C_ADDRESS); - if (Wire.endTransmission() == 0) { - return true; // CardKB detected - } - return false; // CardKB not detected -} -void scanForCardKB() { - const int maxRetries = 10; // Maximum number of retries - const int retryDelay = 100; // Delay between retries in milliseconds - for (int i = 0; i < maxRetries; ++i) { - // Perform the scan (example scan code, adjust as needed) - cardKBDetected = performScanForCardKB(); - - if (cardKBDetected) { - Serial.println("CardKB Keyboard detected."); - break; - } - - delay(retryDelay); // Wait before the next retry - } - if (!cardKBDetected) { - Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); - } -} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 15668aecb..4aa4549cc 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,7 +2,6 @@ #include #include -bool performScanForCardKB(); class ScanI2C { @@ -82,6 +81,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); + virtual void scanPort(ScanI2C::I2CPort, int *); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 7828dfb58..f7068ee02 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -135,7 +135,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port) +void ScanI2CTwoWire::scanPort(I2CPort port, int *address) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,6 +163,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port) #endif for (addr.address = 1; addr.address < 127; addr.address++) { + // Skip the address if it is not requested oon a partial scan + if (sizeof(address) > 0 && addr.address != *address) { + continue; + } i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO if (i2cBus->read() != -1) @@ -353,6 +357,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port) } } +void ScanI2CTwoWire::scanPort(I2CPort port) +{ + scanPort(port, nullptr); +} + TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const { if (address.port == ScanI2C::I2CPort::WIRE) { @@ -369,4 +378,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} +} \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 9acd736d2..a7c19c779 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,6 +16,8 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; + void scanPort(ScanI2C::I2CPort, int *) override; + ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const; @@ -53,4 +55,4 @@ class ScanI2CTwoWire : public ScanI2C uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const; DeviceType probeOLED(ScanI2C::DeviceAddress) const; -}; +}; \ No newline at end of file diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index af7c96b20..55f435fda 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -1,6 +1,7 @@ #include "kbI2cBase.h" #include "configuration.h" #include "detect/ScanI2C.h" +#include "detect/ScanI2CTwoWire.h" extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -30,8 +31,40 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { if (cardkb_found.address == 0x00) { - // Input device is not detected. - return INT32_MAX; + // Input device is not detected. Rescan now. + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); + int i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; +#if defined(I2C_SDA1) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan); + auto kb_info = i2cScanner->firstKeyboard(); + + if (kb_info.type != ScanI2C::DeviceType::NONE) { + cardkb_found = kb_info.address; + switch (kb_info.type) { + case ScanI2C::DeviceType::RAK14004: + kb_model = 0x02; + break; + case ScanI2C::DeviceType::CARDKB: + kb_model = 0x00; + break; + case ScanI2C::DeviceType::TDECKKB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x10; + break; + case ScanI2C::DeviceType::BBQ10KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x11; + break; + default: + // use this as default since it's also just zero + LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); + kb_model = 0x00; + } + } + if (cardkb_found.address == 0x00) + return INT32_MAX; } if (!i2cBus) { diff --git a/src/main.cpp b/src/main.cpp index a3e9258ba..4a9fef5d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,8 +35,6 @@ #include // #include -bool cardKBDetected = false; -void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -376,7 +374,6 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif -scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index 737d424a7..db05a4734 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif -extern bool cardKBDetected; + #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,7 +39,6 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; -#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From f7a4cd33b4735e4b73a8e7e9771c06a8bb7d8879 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 18:00:06 -0500 Subject: [PATCH 0455/1377] Add armv7 builds --- .github/workflows/build_raspbian_armv7l.yml | 46 +++++++++++ .github/workflows/main_matrix.yml | 16 +++- .github/workflows/package_raspbian_armv7l.yml | 78 +++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_raspbian_armv7l.yml create mode 100644 .github/workflows/package_raspbian_armv7l.yml diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml new file mode 100644 index 000000000..0160aa53b --- /dev/null +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -0,0 +1,46 @@ +name: Build Raspbian + +on: workflow_call + +permissions: + contents: write + packages: write + +jobs: + build-raspbian-armv7l: + runs-on: [self-hosted, linux, ARM] + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Upgrade python tools + shell: bash + run: | + python -m pip install --upgrade pip + pip install -U platformio adafruit-nrfutil + pip install -U meshtastic --pre + + - name: Upgrade platformio + shell: bash + run: | + pio upgrade + + - name: Build Raspbian + run: bin/build-native.sh + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | + release/meshtasticd_linux_armv7l + bin/config-dist.yaml diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index d329e693a..b60a65009 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -112,6 +112,9 @@ jobs: package-raspbian: uses: ./.github/workflows/package_raspbian.yml + package-raspbian-armv7l: + uses: ./.github/workflows/package_raspbian_armv7l.yml + build-native: runs-on: ubuntu-latest steps: @@ -199,6 +202,7 @@ jobs: build-native, build-rpi2040, package-raspbian, + package-raspbian-armv7l, ] steps: - name: Checkout code @@ -363,7 +367,7 @@ jobs: asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip asset_content_type: application/zip - - name: Add raspbian .deb + - name: Add raspbian aarch64 .deb uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ github.token }} @@ -373,6 +377,16 @@ jobs: asset_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb asset_content_type: application/vnd.debian.binary-package + - name: Add raspbian armv7l .deb + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./meshtasticd_${{ steps.version.outputs.version }}_armhf.deb + asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb + asset_content_type: application/vnd.debian.binary-package + - name: Bump version.properties run: >- bin/bump_version.py diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml new file mode 100644 index 000000000..d10456759 --- /dev/null +++ b/.github/workflows/package_raspbian_armv7l.yml @@ -0,0 +1,78 @@ +name: Package Raspbian + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: write + packages: write + +jobs: + build-raspbian_armv7l: + uses: ./.github/workflows/build_raspbian_armv7l.yml + + package-raspbian_armv7l: + runs-on: ubuntu-latest + needs: build-raspbian_armv7l + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: build .debpkg + run: | + mkdir -p .debpkg/DEBIAN + mkdir -p .debpkg/usr/share/doc/meshtasticd/web + mkdir -p .debpkg/usr/sbin + mkdir -p .debpkg/etc/meshtasticd + mkdir -p .debpkg/usr/lib/systemd/system/ + tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web + gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz + cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd + cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml + chmod +x .debpkg/usr/sbin/meshtasticd + cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service + echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles + chmod +x .debpkg/DEBIAN/conffiles + + - uses: jiro4989/build-deb-action@v3 + with: + package: meshtasticd + package_root: .debpkg + maintainer: Jonathan Bennett + version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* + arch: armhf + depends: libyaml-cpp0.7, openssl, libulfius2.7 + desc: Native Linux Meshtastic binary. + + - uses: actions/upload-artifact@v4 + with: + name: artifact-deb + overwrite: true + path: | + ./*.deb From 1c67f491d452d48843e67d77f7ebfef93154fa20 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 18:13:12 -0500 Subject: [PATCH 0456/1377] Add deps install for armv7l builds --- .github/workflows/build_raspbian_armv7l.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index 0160aa53b..aceb7ec93 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -10,6 +10,11 @@ jobs: build-raspbian-armv7l: runs-on: [self-hosted, linux, ARM] steps: + - name: Install libbluetooth + shell: bash + run: | + sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev + - name: Checkout code uses: actions/checkout@v4 with: From 72fb8a30a166ef39eea69b71f5c06b5dc24014f7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 18:18:03 -0500 Subject: [PATCH 0457/1377] No sudo on debian docker --- .github/workflows/build_raspbian_armv7l.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index aceb7ec93..b9116ceab 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev + apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code uses: actions/checkout@v4 From 7d1a9258925201658eb2efac5cf4844cb68b2745 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 20:16:33 -0500 Subject: [PATCH 0458/1377] Use the right arch name for armv7l builds --- .github/workflows/build_raspbian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index cef61bb21..0bf96077c 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -42,5 +42,5 @@ jobs: name: firmware-raspbian-${{ steps.version.outputs.version }}.zip overwrite: true path: | - release/meshtasticd_linux_aarch64 + release/meshtasticd_linux_armv7l bin/config-dist.yaml From 45b05c9896bd7bde3a8940d102a0542510f75ace Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 20:39:11 -0500 Subject: [PATCH 0459/1377] Revert "Use the right arch name for armv7l builds" This reverts commit 7d1a9258925201658eb2efac5cf4844cb68b2745. --- .github/workflows/build_raspbian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 0bf96077c..cef61bb21 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -42,5 +42,5 @@ jobs: name: firmware-raspbian-${{ steps.version.outputs.version }}.zip overwrite: true path: | - release/meshtasticd_linux_armv7l + release/meshtasticd_linux_aarch64 bin/config-dist.yaml From 85e238ca76f783f77edb4ba258f74fda9388369a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 21:12:47 -0500 Subject: [PATCH 0460/1377] Better names for .deb artifacts --- .github/workflows/package_raspbian.yml | 2 +- .github/workflows/package_raspbian_armv7l.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index f6e40052e..5471332c5 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -72,7 +72,7 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: artifact-deb + name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb overwrite: true path: | ./*.deb diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml index d10456759..5b9c9aa71 100644 --- a/.github/workflows/package_raspbian_armv7l.yml +++ b/.github/workflows/package_raspbian_armv7l.yml @@ -72,7 +72,7 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: artifact-deb + name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb overwrite: true path: | ./*.deb From afae3a488e5f8f4567534ac4203f3a42080dd50e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 23:29:43 -0500 Subject: [PATCH 0461/1377] Move Raspbian Arm64 to new build scheme --- .github/workflows/build_raspbian.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index cef61bb21..697d08727 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -10,6 +10,11 @@ jobs: build-raspbian: runs-on: [self-hosted, linux, ARM64] steps: + - name: Install libbluetooth + shell: bash + run: | + apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev + - name: Checkout code uses: actions/checkout@v4 with: From e8cdac8fa007082a42b733b535006ae723ddad04 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 23:31:33 -0500 Subject: [PATCH 0462/1377] No need to build Raspbian arm64 twice --- .github/workflows/main_matrix.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index b60a65009..461b91665 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -198,7 +198,6 @@ jobs: build-esp32-s3, build-esp32-c3, build-nrf52, - build-raspbian, build-native, build-rpi2040, package-raspbian, From 34aec70998382a66ea0d61bb9b5b585885aad32d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 19 May 2024 23:44:12 -0500 Subject: [PATCH 0463/1377] No need to build Raspbian arm64 twice: Part 2 --- .github/workflows/main_matrix.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 461b91665..13c2731ae 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -103,12 +103,6 @@ jobs: with: board: ${{ matrix.board }} - build-raspbian: - strategy: - fail-fast: false - max-parallel: 1 - uses: ./.github/workflows/build_raspbian.yml - package-raspbian: uses: ./.github/workflows/package_raspbian.yml From b68ef3d98ab579eecd335ec0afa3dbcce0adcb09 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 20 May 2024 19:34:51 -0500 Subject: [PATCH 0464/1377] Revert "Fix TBeam Supreme woes (and upgrade platform)" (#3943) --- boards/tbeam-s3-core.json | 1 - variants/tbeam-s3-core/pins_arduino.h | 8 ++++++++ variants/tbeam-s3-core/platformio.ini | 2 -- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 8d2c3eed6..4c82a2789 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -7,7 +7,6 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", - "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" diff --git a/variants/tbeam-s3-core/pins_arduino.h b/variants/tbeam-s3-core/pins_arduino.h index 22ed814ff..24edb7d9f 100644 --- a/variants/tbeam-s3-core/pins_arduino.h +++ b/variants/tbeam-s3-core/pins_arduino.h @@ -6,6 +6,14 @@ #define USB_VID 0x303a #define USB_PID 0x1001 +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/tbeam-s3-core/platformio.ini index c71802255..e50d506b9 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/tbeam-s3-core/platformio.ini @@ -4,8 +4,6 @@ extends = esp32s3_base board = tbeam-s3-core board_check = true -platform = platformio/espressif32@6.7.0 - lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@1.0.1 From 5f107569f319bc9af8f7d99d4dcadeb32c653c9a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 20 May 2024 19:48:10 -0500 Subject: [PATCH 0465/1377] Put T-Beam supreme back to the future (#3944) --- boards/tbeam-s3-core.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 4c82a2789..7bda2e5a0 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -7,7 +7,8 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", - "-DARDUINO_USB_MODE=1", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], From ed8abea11bc9fea9863968c663631c56a87fe700 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 21 May 2024 00:09:33 -0500 Subject: [PATCH 0466/1377] Add final debug call for Portduino reboot (#3945) --- src/shutdown.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shutdown.h b/src/shutdown.h index 21abba07e..54fb3071b 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -28,6 +28,7 @@ void powerCommandsCheck() Serial1.end(); if (screen) delete screen; + LOG_DEBUG("final reboot!\n"); reboot(); #else rebootAtMsec = -1; From 55d46bae924b244e3136e56e7aae42a06dda1602 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 21 May 2024 06:56:16 -0500 Subject: [PATCH 0467/1377] Update build_raspbian_armv7l.yml --- .github/workflows/build_raspbian_armv7l.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index b9116ceab..ee5eb66eb 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -1,4 +1,4 @@ -name: Build Raspbian +name: Build Raspbian Arm on: workflow_call From 8c5dee58815a0e960b3394aaa8c384fe53af5fa0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 21 May 2024 07:04:18 -0500 Subject: [PATCH 0468/1377] Remove download --- .github/workflows/main_matrix.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 13c2731ae..0a530b5ae 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -298,11 +298,6 @@ jobs: merge-multiple: true path: ./output - - uses: actions/download-artifact@v4 - with: - merge-multiple: true - name: artifact-deb - - name: Display structure of downloaded files run: ls -R From b9a6d21dffaa188a8d6fe0a29da5c86290b9437a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 21 May 2024 07:45:24 -0500 Subject: [PATCH 0469/1377] Add EoRA-S3 --- variants/CDEBYTE_EoRa-S3/platformio.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/CDEBYTE_EoRa-S3/platformio.ini b/variants/CDEBYTE_EoRa-S3/platformio.ini index 88845a50c..a1642ff97 100644 --- a/variants/CDEBYTE_EoRa-S3/platformio.ini +++ b/variants/CDEBYTE_EoRa-S3/platformio.ini @@ -1,7 +1,6 @@ [env:CDEBYTE_EoRa-S3] extends = esp32s3_base board = CDEBYTE_EoRa-S3 -board_level = extra build_flags = ${esp32s3_base.build_flags} -D CDEBYTE_EORA_S3 From 2fdc2e2c3c807077486c87031960b3677bb0fd62 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 21 May 2024 09:07:47 -0500 Subject: [PATCH 0470/1377] Perhaps a wildcard shall I seek --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 0a530b5ae..90e178a3e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -230,7 +230,7 @@ jobs: ./firmware-*-ota.zip ./device-*.sh ./device-*.bat - ./meshtasticd_linux_*64 + ./meshtasticd_linux_* ./config-dist.yaml ./littlefs-*.bin ./bleota*bin From b829ee795d17c400db72825791721f82ba52dd3d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 21 May 2024 10:37:26 -0500 Subject: [PATCH 0471/1377] re-add .deb download to build --- .github/workflows/main_matrix.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 90e178a3e..ceac4f0b8 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -217,7 +217,7 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./release/meshtasticd_linux_aarch64 ./bin/config-dist.yaml + run: mv -b -t ./ ./release/meshtasticd_linux_aarch64 ./release/meshtasticd_linux_armv7l ./bin/config-dist.yaml - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 @@ -298,6 +298,12 @@ jobs: merge-multiple: true path: ./output + - uses: actions/download-artifact@v4 + with: + pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb + merge-multiple: true + path: ./output + - name: Display structure of downloaded files run: ls -R From d19607ba98019df07ca334168fb57dd3e1afedf1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 21 May 2024 11:23:29 -0500 Subject: [PATCH 0472/1377] Look in the right place for .debs --- .github/workflows/main_matrix.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ceac4f0b8..a768e5fd9 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -367,7 +367,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./meshtasticd_${{ steps.version.outputs.version }}_arm64.deb + asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_arm64.deb asset_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb asset_content_type: application/vnd.debian.binary-package @@ -377,7 +377,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./meshtasticd_${{ steps.version.outputs.version }}_armhf.deb + asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_armhf.deb asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb asset_content_type: application/vnd.debian.binary-package From 040b8516154dadd9b32a5abecf317cb053bca173 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 14:40:31 -0500 Subject: [PATCH 0473/1377] [create-pull-request] automated change (#3950) Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 69761c0ac..aa4d2c207 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 10 +build = 11 From cdf86f4166758737ed6039baa01342d24af94531 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 22 May 2024 08:58:05 +1200 Subject: [PATCH 0474/1377] Change NRF_APM USB detection code (#3939) --- src/Power.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 64e310b68..8d0c8be62 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -555,14 +555,24 @@ void Power::readPowerStatus() #ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect // changes. + static nrfx_power_usb_state_t prev_nrf_usb_state = (nrfx_power_usb_state_t)-1; // -1 so that state detected at boot nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); - if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) { - powerFSM.trigger(EVENT_POWER_DISCONNECTED); - NRF_USB = OptFalse; - } else { - powerFSM.trigger(EVENT_POWER_CONNECTED); - NRF_USB = OptTrue; + // If state changed + if (nrf_usb_state != prev_nrf_usb_state) { + // If changed to DISCONNECTED + if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) { + powerFSM.trigger(EVENT_POWER_DISCONNECTED); + NRF_USB = OptFalse; + } + // If changed to CONNECTED / READY + else { + powerFSM.trigger(EVENT_POWER_CONNECTED); + NRF_USB = OptTrue; + } + + // Cache the current state + prev_nrf_usb_state = nrf_usb_state; } #endif // Notify any status instances that are observing us From a12b9922ed370766bf6538df45dbe27e0e935415 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 22 May 2024 09:00:04 +1200 Subject: [PATCH 0475/1377] Screen changes from time deltas to timestamps after 15 minutes (#3935) * E-Ink displays sometimes use timestamp instead of delta * Allow users to disable E-Ink screensaver * Clarify variable's purpose * Operator order oopsie * Picky print problem * Implement for all display, not just E-Ink * Align "unknown age" behavior with existing code * One more use of timestamp, if screen is wide enough --- src/PowerFSM.cpp | 18 +++-- src/graphics/Screen.cpp | 142 +++++++++++++++++++++++++++++++++++----- 2 files changed, 138 insertions(+), 22 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 4f42b36b5..a7bc18f1a 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -348,12 +348,18 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); - powerFSM.add_timed_transition(&stateON, &stateDARK, - Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, - "Screen-on timeout"); - powerFSM.add_timed_transition(&statePOWER, &stateDARK, - Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, - "Screen-on timeout"); +#ifdef USE_EINK + // Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0 + if (config.display.screen_on_secs > 0) +#endif + { + powerFSM.add_timed_transition(&stateON, &stateDARK, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), + NULL, "Screen-on timeout"); + powerFSM.add_timed_transition(&statePOWER, &stateDARK, + Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), + NULL, "Screen-on timeout"); + } // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) #ifdef ARCH_ESP32 diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 0899335e6..bbf2c5e7c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -419,6 +419,76 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) return packet->from != 0 && !moduleConfig.store_forward.enabled; } +// Get an absolute time from "seconds ago" info. Returns false if no valid timestamp possible +bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo) +{ + // Cache the result - avoid frequent recalculation + static uint8_t hoursCached = 0, minutesCached = 0; + static uint32_t daysAgoCached = 0; + static uint32_t secondsAgoCached = 0; + static bool validCached = false; + + // Abort: if timezone not set + if (strlen(config.device.tzdef) == 0) { + validCached = false; + return validCached; + } + + // Abort: if invalid pointers passed + if (hours == nullptr || minutes == nullptr || daysAgo == nullptr) { + validCached = false; + return validCached; + } + + // Abort: if time seems invalid.. (> 6 months ago, probably seen before RTC set) + if (secondsAgo > SEC_PER_DAY * 30UL * 6) { + validCached = false; + return validCached; + } + + // If repeated request, don't bother recalculating + if (secondsAgo - secondsAgoCached < 60 && secondsAgoCached != 0) { + if (validCached) { + *hours = hoursCached; + *minutes = minutesCached; + *daysAgo = daysAgoCached; + } + return validCached; + } + + // Get local time + uint32_t secondsRTC = getValidTime(RTCQuality::RTCQualityDevice, true); // Get local time + + // Abort: if RTC not set + if (!secondsRTC) { + validCached = false; + return validCached; + } + + // Get absolute time when last seen + uint32_t secondsSeenAt = secondsRTC - secondsAgo; + + // Calculate daysAgo + *daysAgo = (secondsRTC / SEC_PER_DAY) - (secondsSeenAt / SEC_PER_DAY); // How many "midnights" have passed + + // Get seconds since midnight + uint32_t hms = (secondsRTC - secondsAgo) % SEC_PER_DAY; + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into hours and minutes + *hours = hms / SEC_PER_HOUR; + *minutes = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + + // Cache the result + daysAgoCached = *daysAgo; + hoursCached = *hours; + minutesCached = *minutes; + secondsAgoCached = secondsAgo; + + validCached = true; + return validCached; +} + /// Draw the last text message we received static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -440,18 +510,36 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->setColor(BLACK); } + // For time delta uint32_t seconds = sinceReceived(&mp); uint32_t minutes = seconds / 60; uint32_t hours = minutes / 60; uint32_t days = hours / 24; - if (config.display.heading_bold) { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); + // For timestamp + uint8_t timestampHours, timestampMinutes; + int32_t daysAgo; + bool useTimestamp = deltaToTimestamp(seconds, ×tampHours, ×tampMinutes, &daysAgo); + + // If bold, draw twice, shifting right by one pixel + for (uint8_t xOff = 0; xOff <= (config.display.heading_bold ? 1 : 0); xOff++) { + // Show a timestamp if received today, but longer than 15 minutes ago + if (useTimestamp && minutes >= 15 && daysAgo == 0) { + display->drawStringf(xOff + x, 0 + y, tempBuf, "At %02hu:%02hu from %s", timestampHours, timestampMinutes, + (node && node->has_user) ? node->user.short_name : "???"); + } + // Timestamp yesterday (if display is wide enough) + else if (useTimestamp && daysAgo == 1 && display->width() >= 200) { + display->drawStringf(xOff + x, 0 + y, tempBuf, "Yesterday %02hu:%02hu from %s", timestampHours, timestampMinutes, + (node && node->has_user) ? node->user.short_name : "???"); + } + // Otherwise, show a time delta + else { + display->drawStringf(xOff + x, 0 + y, tempBuf, "%s ago from %s", + screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); + } } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); display->setColor(WHITE); snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); @@ -879,19 +967,32 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; + + // Use an absolute timestamp in some cases. + // Particularly useful with E-Ink displays. Static UI, fewer refreshes. + uint8_t timestampHours, timestampMinutes; + int32_t daysAgo; + bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); + if (agoSecs < 120) // last 2 mins? snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); + // -- if suitable for timestamp -- + else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); + else if (useTimestamp && daysAgo == 0) // Today + snprintf(lastStr, sizeof(lastStr), "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); + else if (useTimestamp && daysAgo == 1) // Yesterday + snprintf(lastStr, sizeof(lastStr), "Seen yesterday"); + else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) + snprintf(lastStr, sizeof(lastStr), "%li days ago", (long)daysAgo); + // -- if using time delta instead -- else if (agoSecs < 120 * 60) // last 2 hrs snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); - else { - // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad - // data. - if ((agoSecs / 60 / 60) < (hours_in_month * 6)) { - snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); - } else { - snprintf(lastStr, sizeof(lastStr), "unknown age"); - } - } + // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. + else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) + snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); + else + snprintf(lastStr, sizeof(lastStr), "unknown age"); static char distStr[20]; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { @@ -900,7 +1001,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ strncpy(distStr, "? km", sizeof(distStr)); } meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; + const char *fields[] = {username, lastStr, signalStr, distStr, NULL}; int16_t compassX = 0, compassY = 0; // coordinates for the center of the compass/circle @@ -1448,6 +1549,15 @@ void Screen::setFrames() LOG_DEBUG("showing standard frames\n"); showingNormalScreen = true; +#ifdef USE_EINK + // If user has disabled the screensaver, warn them after boot + static bool warnedScreensaverDisabled = false; + if (config.display.screen_on_secs == 0 && !warnedScreensaverDisabled) { + screen->print("Screensaver disabled\n"); + warnedScreensaverDisabled = true; + } +#endif + moduleFrames = MeshModule::GetMeshModulesWithUIFrames(); LOG_DEBUG("Showing %d module frames\n", moduleFrames.size()); #ifdef DEBUG_PORT From 0c9da9aec7ef6b01ab209f22cafb5538cdf58b6e Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 22 May 2024 05:02:09 +0300 Subject: [PATCH 0476/1377] Update platformio/espressif32 to the latest 6.7.0 (#3899) * Bump platfomio/espressif32 version to latest 6.7.0 * Fix deprecated constants * Remove pin defs already defined by the framework * ESP_EXT1_WAKEUP_ALL_LOW is deprecated for any target except esp32 * Enable LTO and use newlib nano flavor * Make trunk happy * Respect build_unflags of base env * Recover float printfing * Disable BLE_SM_PAIR_AUTHREQ_SC * Distribute BLE_SM_PAIR_KEY_DIST_ID too --------- Co-authored-by: Ben Meadors --- arch/esp32/esp32.ini | 4 +++- bin/platformio-custom.py | 3 +++ src/Power.cpp | 2 +- src/nimble/NimbleBluetooth.cpp | 4 +++- src/platform/esp32/main-esp32.cpp | 5 +++++ variants/CDEBYTE_EoRa-S3/pins_arduino.h | 9 --------- variants/EBYTE_ESP32-S3/pins_arduino.h | 9 --------- variants/bpi_picow_esp32_s3/pins_arduino.h | 8 -------- variants/esp32-s3-pico/pins_arduino.h | 8 -------- variants/heltec_esp32c3/pins_arduino.h | 8 -------- variants/heltec_wireless_paper/pins_arduino.h | 8 -------- variants/heltec_wireless_paper_v1/pins_arduino.h | 8 -------- variants/heltec_wireless_tracker/pins_arduino.h | 8 -------- variants/heltec_wireless_tracker_V1_0/pins_arduino.h | 8 -------- variants/m5stack-stamp-c3/pins_arduino.h | 8 -------- variants/m5stack_core/pins_arduino.h | 8 -------- variants/m5stack_coreink/pins_arduino.h | 8 -------- variants/my_esp32s3_diy_eink/pins_arduino.h | 8 -------- variants/my_esp32s3_diy_oled/pins_arduino.h | 8 -------- variants/picomputer-s3/pins_arduino.h | 8 -------- variants/rak11200/pins_arduino.h | 8 -------- variants/rak11200/variant.h | 8 -------- variants/station-g2/pins_arduino.h | 8 -------- variants/station-g2/platformio.ini | 4 +++- variants/t-deck/pins_arduino.h | 8 -------- variants/t-watch-s3/pins_arduino.h | 8 -------- variants/tlora_t3s3_v1/pins_arduino.h | 8 -------- variants/tracksenger/internal/pins_arduino.h | 8 -------- variants/tracksenger/lcd/pins_arduino.h | 8 -------- variants/tracksenger/oled/pins_arduino.h | 8 -------- variants/unphone/platformio.ini | 1 + variants/wiphone/pins_arduino.h | 8 -------- 32 files changed, 19 insertions(+), 206 deletions(-) diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 39935b849..7e55f0934 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -1,7 +1,7 @@ ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] extends = arduino_base -platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch. +platform = platformio/espressif32@6.7.0 build_src_filter = ${arduino_base.build_src_filter} - - - - - @@ -15,8 +15,10 @@ board_build.filesystem = littlefs # Remove -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL for low level BLE logging. # See library directory for BLE logging possible values: .pio/libdeps/tbeam/NimBLE-Arduino/src/log_common/log_common.h # This overrides the BLE logging default of LOG_LEVEL_INFO (1) from: .pio/libdeps/tbeam/NimBLE-Arduino/src/esp_nimble_cfg.h +build_unflags = -fno-lto build_flags = ${arduino_base.build_flags} + -flto -Wall -Wextra -Isrc/platform/esp32 diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 651677af2..3382ff891 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -1,3 +1,5 @@ +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports import sys from os.path import join @@ -60,6 +62,7 @@ if platform.name == "espressif32": import esptool env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) + env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) Import("projenv") diff --git a/src/Power.cpp b/src/Power.cpp index 8d0c8be62..b80d8a0d5 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -50,7 +50,7 @@ RTC_NOINIT_ATTR uint64_t RTC_reg_b; esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t)); #ifndef ADC_ATTENUATION -static const adc_atten_t atten = ADC_ATTEN_DB_11; +static const adc_atten_t atten = ADC_ATTEN_DB_12; #else static const adc_atten_t atten = ADC_ATTENUATION; #endif diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 8f7e00461..68aa9b465 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -157,7 +157,9 @@ void NimbleBluetooth::setup() NimBLEDevice::setPower(ESP_PWR_LVL_P9); if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - NimBLEDevice::setSecurityAuth(true, true, true); + NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC); + NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); + NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); } bleServer = NimBLEDevice::createServer(); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 2894a49fc..57f466594 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -218,7 +218,12 @@ void cpuDeepSleep(uint32_t msecToWake) // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); #if SOC_PM_SUPPORT_EXT_WAKEUP +#ifdef CONFIG_IDF_TARGET_ESP32 + // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target. esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); +#else + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW); +#endif #endif #endif diff --git a/variants/CDEBYTE_EoRa-S3/pins_arduino.h b/variants/CDEBYTE_EoRa-S3/pins_arduino.h index 38a9103f0..46415d30f 100644 --- a/variants/CDEBYTE_EoRa-S3/pins_arduino.h +++ b/variants/CDEBYTE_EoRa-S3/pins_arduino.h @@ -11,15 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) \ - (((p) < 48) ? (p) : -1) // Maybe it should be <= 48 but this is from a trustworthy source so it is likely correct -#define digitalPinHasPWM(p) (p < 46) - // Serial static const uint8_t TX = UART_TX; static const uint8_t RX = UART_RX; diff --git a/variants/EBYTE_ESP32-S3/pins_arduino.h b/variants/EBYTE_ESP32-S3/pins_arduino.h index 38a9103f0..46415d30f 100644 --- a/variants/EBYTE_ESP32-S3/pins_arduino.h +++ b/variants/EBYTE_ESP32-S3/pins_arduino.h @@ -11,15 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) \ - (((p) < 48) ? (p) : -1) // Maybe it should be <= 48 but this is from a trustworthy source so it is likely correct -#define digitalPinHasPWM(p) (p < 46) - // Serial static const uint8_t TX = UART_TX; static const uint8_t RX = UART_RX; diff --git a/variants/bpi_picow_esp32_s3/pins_arduino.h b/variants/bpi_picow_esp32_s3/pins_arduino.h index af03bf28a..dd7b3c518 100644 --- a/variants/bpi_picow_esp32_s3/pins_arduino.h +++ b/variants/bpi_picow_esp32_s3/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32-s3-pico/pins_arduino.h b/variants/esp32-s3-pico/pins_arduino.h index d24d98a2e..57a66fea2 100644 --- a/variants/esp32-s3-pico/pins_arduino.h +++ b/variants/esp32-s3-pico/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - // The default Wire will be mapped to PMU and RTC static const uint8_t SDA = 15; static const uint8_t SCL = 16; diff --git a/variants/heltec_esp32c3/pins_arduino.h b/variants/heltec_esp32c3/pins_arduino.h index db30a2f30..a717a3706 100644 --- a/variants/heltec_esp32c3/pins_arduino.h +++ b/variants/heltec_esp32c3/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 22 -#define NUM_DIGITAL_PINS 22 -#define NUM_ANALOG_INPUTS 6 - -#define analogInputToDigitalPin(p) (((p) < NUM_ANALOG_INPUTS) ? (esp32_adc2gpio[(p)]) : -1) -#define digitalPinToInterrupt(p) (((p) < NUM_DIGITAL_PINS) ? (p) : -1) -#define digitalPinHasPWM(p) (p < EXTERNAL_NUM_INTERRUPTS) - static const uint8_t TX = 21; static const uint8_t RX = 20; diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h index 66d091691..9e1d8a9a0 100644 --- a/variants/heltec_wireless_paper/pins_arduino.h +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -7,14 +7,6 @@ #define DISPLAY_HEIGHT 64 #define DISPLAY_WIDTH 128 -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 40 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN diff --git a/variants/heltec_wireless_paper_v1/pins_arduino.h b/variants/heltec_wireless_paper_v1/pins_arduino.h index 66d091691..9e1d8a9a0 100644 --- a/variants/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/heltec_wireless_paper_v1/pins_arduino.h @@ -7,14 +7,6 @@ #define DISPLAY_HEIGHT 64 #define DISPLAY_WIDTH 128 -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 40 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN diff --git a/variants/heltec_wireless_tracker/pins_arduino.h b/variants/heltec_wireless_tracker/pins_arduino.h index 5c0b529b0..1052af961 100644 --- a/variants/heltec_wireless_tracker/pins_arduino.h +++ b/variants/heltec_wireless_tracker/pins_arduino.h @@ -11,18 +11,10 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h index f72c7661a..28b982012 100644 --- a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -11,18 +11,10 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/m5stack-stamp-c3/pins_arduino.h b/variants/m5stack-stamp-c3/pins_arduino.h index 38ef9934e..22d2af51d 100644 --- a/variants/m5stack-stamp-c3/pins_arduino.h +++ b/variants/m5stack-stamp-c3/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 22 -#define NUM_DIGITAL_PINS 22 -#define NUM_ANALOG_INPUTS 6 - -#define analogInputToDigitalPin(p) (((p) < NUM_ANALOG_INPUTS) ? (esp32_adc2gpio[(p)]) : -1) -#define digitalPinToInterrupt(p) (((p) < NUM_DIGITAL_PINS) ? (p) : -1) -#define digitalPinHasPWM(p) (p < EXTERNAL_NUM_INTERRUPTS) - static const uint8_t TX = -1; // 21; static const uint8_t RX = -1; // 20; diff --git a/variants/m5stack_core/pins_arduino.h b/variants/m5stack_core/pins_arduino.h index 8f2a0041e..cf807aab4 100644 --- a/variants/m5stack_core/pins_arduino.h +++ b/variants/m5stack_core/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 20 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/m5stack_coreink/pins_arduino.h b/variants/m5stack_coreink/pins_arduino.h index 7f9a14785..c75283ab2 100644 --- a/variants/m5stack_coreink/pins_arduino.h +++ b/variants/m5stack_coreink/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 40 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (esp32_adc2gpio[(p)]) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - #define TX2 -1 #define RX2 -1 diff --git a/variants/my_esp32s3_diy_eink/pins_arduino.h b/variants/my_esp32s3_diy_eink/pins_arduino.h index 39e316624..b37a258c3 100644 --- a/variants/my_esp32s3_diy_eink/pins_arduino.h +++ b/variants/my_esp32s3_diy_eink/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - // The default Wire will be mapped to PMU and RTC static const uint8_t SDA = 18; static const uint8_t SCL = 17; diff --git a/variants/my_esp32s3_diy_oled/pins_arduino.h b/variants/my_esp32s3_diy_oled/pins_arduino.h index 39e316624..b37a258c3 100644 --- a/variants/my_esp32s3_diy_oled/pins_arduino.h +++ b/variants/my_esp32s3_diy_oled/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - // The default Wire will be mapped to PMU and RTC static const uint8_t SDA = 18; static const uint8_t SCL = 17; diff --git a/variants/picomputer-s3/pins_arduino.h b/variants/picomputer-s3/pins_arduino.h index c84601b1e..a3d40018c 100644 --- a/variants/picomputer-s3/pins_arduino.h +++ b/variants/picomputer-s3/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/rak11200/pins_arduino.h b/variants/rak11200/pins_arduino.h index 2dfe02614..f383d54a7 100644 --- a/variants/rak11200/pins_arduino.h +++ b/variants/rak11200/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 40 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (esp32_adc2gpio[(p)]) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - #define LED_GREEN 12 #define LED_BLUE 2 diff --git a/variants/rak11200/variant.h b/variants/rak11200/variant.h index 3399594e5..3cd601254 100644 --- a/variants/rak11200/variant.h +++ b/variants/rak11200/variant.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 40 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (esp32_adc2gpio[(p)]) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - #define LED_GREEN 12 #define LED_BLUE 2 diff --git a/variants/station-g2/pins_arduino.h b/variants/station-g2/pins_arduino.h index 98cbd46d3..6a803008d 100755 --- a/variants/station-g2/pins_arduino.h +++ b/variants/station-g2/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) <= 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - // GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600 // The default Wire will be mapped to Screen and Sensors diff --git a/variants/station-g2/platformio.ini b/variants/station-g2/platformio.ini index e96c0ab88..b674c8bae 100755 --- a/variants/station-g2/platformio.ini +++ b/variants/station-g2/platformio.ini @@ -8,7 +8,9 @@ upload_protocol = esptool upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} -build_unflags = -DARDUINO_USB_MODE=1 +build_unflags = + ${esp32s3_base.build_unflags} + -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D STATION_G2 -I variants/station-g2 -DBOARD_HAS_PSRAM diff --git a/variants/t-deck/pins_arduino.h b/variants/t-deck/pins_arduino.h index 0150935ed..cb429d776 100644 --- a/variants/t-deck/pins_arduino.h +++ b/variants/t-deck/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < NUM_ANALOG_INPUTS) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < NUM_DIGITAL_PINS) ? (p) : -1) -#define digitalPinHasPWM(p) (p < EXTERNAL_NUM_INTERRUPTS) - // static const uint8_t LED_BUILTIN = -1; static const uint8_t TX = 43; diff --git a/variants/t-watch-s3/pins_arduino.h b/variants/t-watch-s3/pins_arduino.h index d3dde6856..35f0e933e 100644 --- a/variants/t-watch-s3/pins_arduino.h +++ b/variants/t-watch-s3/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < NUM_ANALOG_INPUTS) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < NUM_DIGITAL_PINS) ? (p) : -1) -#define digitalPinHasPWM(p) (p < EXTERNAL_NUM_INTERRUPTS) - // static const uint8_t LED_BUILTIN = -1; // static const uint8_t TX = 43; diff --git a/variants/tlora_t3s3_v1/pins_arduino.h b/variants/tlora_t3s3_v1/pins_arduino.h index 627dad19d..4ced1b446 100644 --- a/variants/tlora_t3s3_v1/pins_arduino.h +++ b/variants/tlora_t3s3_v1/pins_arduino.h @@ -6,14 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - // The default Wire will be mapped to PMU and RTC static const uint8_t SDA = 18; static const uint8_t SCL = 17; diff --git a/variants/tracksenger/internal/pins_arduino.h b/variants/tracksenger/internal/pins_arduino.h index 5c0b529b0..1052af961 100644 --- a/variants/tracksenger/internal/pins_arduino.h +++ b/variants/tracksenger/internal/pins_arduino.h @@ -11,18 +11,10 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/tracksenger/lcd/pins_arduino.h b/variants/tracksenger/lcd/pins_arduino.h index 5c0b529b0..1052af961 100644 --- a/variants/tracksenger/lcd/pins_arduino.h +++ b/variants/tracksenger/lcd/pins_arduino.h @@ -11,18 +11,10 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/tracksenger/oled/pins_arduino.h b/variants/tracksenger/oled/pins_arduino.h index 5c0b529b0..1052af961 100644 --- a/variants/tracksenger/oled/pins_arduino.h +++ b/variants/tracksenger/oled/pins_arduino.h @@ -11,18 +11,10 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index f66b5db49..dbfa0599d 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -9,6 +9,7 @@ monitor_speed = 115200 monitor_filters = esp32_exception_decoder build_unflags = + ${esp32s3_base.build_unflags} -D ARDUINO_USB_MODE build_flags = ${esp32_base.build_flags} diff --git a/variants/wiphone/pins_arduino.h b/variants/wiphone/pins_arduino.h index bca9c1173..3759219d1 100644 --- a/variants/wiphone/pins_arduino.h +++ b/variants/wiphone/pins_arduino.h @@ -3,14 +3,6 @@ #include -#define EXTERNAL_NUM_INTERRUPTS 16 -#define NUM_DIGITAL_PINS 20 -#define NUM_ANALOG_INPUTS 16 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (esp32_adc2gpio[(p)]) : -1) -#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 34) - static const uint8_t TX = 1; static const uint8_t RX = 3; From 7bcb8f1fee51ee100c4883350d85a24ce406a692 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 22 May 2024 07:54:06 -0500 Subject: [PATCH 0477/1377] Portduino: Catch the keyboard power button and initiate poweroff (#3953) --- src/input/LinuxInput.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp index 1ace2044c..6194195ed 100644 --- a/src/input/LinuxInput.cpp +++ b/src/input/LinuxInput.cpp @@ -155,6 +155,9 @@ int32_t LinuxInput::runOnce() case KEY_ENTER: // Enter e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; break; + case KEY_POWER: + system("poweroff"); + break; default: // all other keys if (keymap[code]) { e.inputEvent = ANYKEY; From 1631462db12ad7ec46b6f2a127646abf44ab176d Mon Sep 17 00:00:00 2001 From: 868meshbot <167752149+868meshbot@users.noreply.github.com> Date: Thu, 23 May 2024 01:28:30 +0100 Subject: [PATCH 0478/1377] Oled screen emojis (#3940) * Update images.h Add some fun emojis * Update Screen.cpp Update Screen.cpp to display single emojis on the OLED of devices, if a single known emoji is detected * Update images.h add ? ! fog emojis * Update Screen.cpp add logic for new emojis * Update Screen.cpp correct formatting * Update images.h correct formatting * Update Screen.cpp change formatting via trunk application * Update images.h change formatting based on trunk application --------- Co-authored-by: Ben Meadors --- src/graphics/Screen.cpp | 59 +++++++++++++- src/graphics/images.h | 165 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 3 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index bbf2c5e7c..1e82eef7d 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -542,8 +542,61 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state } display->setColor(WHITE); - snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); - display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); + if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, + thumbup); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, + thumbdown); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"❓") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height, + question); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"‼️") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5, + bang_width, bang_height, bang); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5, + poo_width, poo_height, poo); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\xa4\xa3") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5, + haha_width, haha_height, haha); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width, + wave_icon_height, wave_icon); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F920") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height, + cowboy); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height, + deadmau5); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5, + sun_width, sun_height, sun); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\u2614") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10, + rain_width, rain_height, rain); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"☁️") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"🌫️") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5, + fog_width, fog_height, fog); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil); + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"♥️") == 0) { + display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart); + } else { + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); + } } /// Draw the last waypoint we received @@ -2183,4 +2236,4 @@ int Screen::handleInputEvent(const InputEvent *event) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN diff --git a/src/graphics/images.h b/src/graphics/images.h index 5c6fb4275..deaf08159 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -31,4 +31,169 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1, const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5}; #endif +#define thumbs_height 25 +#define thumbs_width 25 +static unsigned char thumbup[] PROGMEM = { + 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00, + 0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, + 0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01, + 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00, + 0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00, +}; + +static unsigned char thumbdown[] PROGMEM = { + 0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00, + 0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, + 0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00, + 0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00, + 0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, +}; + +#define question_height 25 +#define question_width 25 +static unsigned char question[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00, + 0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00, + 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00, + 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define bang_height 30 +#define bang_width 30 +static unsigned char bang[] PROGMEM = { + 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, + 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, + 0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, + 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F, + 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07, +}; + +#define haha_height 30 +#define haha_width 30 +static unsigned char haha[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, + 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x1F, 0x3E, 0x00, 0x80, 0x03, 0x70, 0x00, 0xC0, 0x01, 0xE0, 0x00, 0xC0, 0x00, 0xC2, 0x00, + 0x60, 0x00, 0x03, 0x00, 0x60, 0x00, 0xC1, 0x1F, 0x60, 0x80, 0x8F, 0x31, 0x30, 0x0E, 0x80, 0x31, 0x30, 0x10, 0x30, 0x1F, + 0x30, 0x08, 0x58, 0x00, 0x30, 0x04, 0x6C, 0x03, 0x60, 0x00, 0xF3, 0x01, 0x60, 0xC0, 0xFC, 0x01, 0x80, 0x38, 0xBF, 0x01, + 0xE0, 0xC5, 0xDF, 0x00, 0xB0, 0xF9, 0xEF, 0x00, 0x30, 0xF1, 0x73, 0x00, 0xB0, 0x1D, 0x3E, 0x00, 0xF0, 0xFD, 0x0F, 0x00, + 0xE0, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define wave_icon_height 30 +#define wave_icon_width 30 +static unsigned char wave_icon[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xC0, 0x00, + 0x00, 0x0C, 0x9C, 0x01, 0x80, 0x17, 0x20, 0x01, 0x80, 0x26, 0x46, 0x02, 0x80, 0x44, 0x88, 0x02, 0xC0, 0x89, 0x8A, 0x02, + 0x40, 0x93, 0x8B, 0x02, 0x40, 0x26, 0x13, 0x00, 0x80, 0x44, 0x16, 0x00, 0xC0, 0x89, 0x24, 0x00, 0x40, 0x93, 0x60, 0x00, + 0x40, 0x26, 0x40, 0x00, 0x80, 0x0C, 0x80, 0x00, 0x00, 0x09, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x40, 0x06, 0x80, 0x00, + 0x50, 0x0C, 0x80, 0x00, 0x50, 0x08, 0x40, 0x00, 0x90, 0x10, 0x20, 0x00, 0xB0, 0x21, 0x10, 0x00, 0x20, 0x47, 0x18, 0x00, + 0x40, 0x80, 0x0F, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define cowboy_height 30 +#define cowboy_width 30 +static unsigned char cowboy[] PROGMEM = { + 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x3C, 0xFE, 0x1F, 0x0F, + 0xFE, 0xFE, 0xDF, 0x1F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, + 0x3E, 0xC0, 0x00, 0x1F, 0x1E, 0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x0E, 0x1C, 0x04, 0x00, 0x0E, 0x1C, 0x00, + 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x04, 0x08, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x08, + 0x8C, 0x07, 0x70, 0x0C, 0x88, 0xFC, 0x4F, 0x04, 0x88, 0x01, 0x40, 0x04, 0x90, 0xFF, 0x7F, 0x02, 0x30, 0x03, 0x30, 0x03, + 0x60, 0x0E, 0x9C, 0x01, 0xC0, 0xF8, 0xC7, 0x00, 0x80, 0x01, 0x60, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0xF8, 0x07, 0x00, +}; + +#define deadmau5_height 30 +#define deadmau5_width 60 +static unsigned char deadmau5[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, + 0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00, + 0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07, + 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00, + 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC, + 0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00, + 0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF, + 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define sun_width 30 +#define sun_height 30 +static unsigned char sun[] PROGMEM = { + 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03, + 0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00, + 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E, + 0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, + 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03, + 0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, +}; + +#define rain_width 30 +#define rain_height 30 +static unsigned char rain[] PROGMEM = { + 0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00, + 0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20, + 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00, + 0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00, + 0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C, + 0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00, +}; + +#define cloud_height 30 +#define cloud_width 30 +static unsigned char cloud[] PROGMEM = { + 0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00, + 0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01, + 0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, + 0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, + 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10, + 0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, +}; + +#define fog_height 25 +#define fog_width 25 +static unsigned char fog[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, + 0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00, + 0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define devil_height 30 +#define devil_width 30 +static unsigned char devil[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x10, 0x03, 0xC0, 0x01, 0x38, 0x07, 0x7C, 0x0F, 0x38, 0x1F, 0x03, 0x30, 0x1E, + 0xFE, 0x01, 0xE0, 0x1F, 0x7E, 0x00, 0x80, 0x1F, 0x3C, 0x00, 0x00, 0x0F, 0x1C, 0x00, 0x00, 0x0E, 0x18, 0x00, 0x00, 0x06, + 0x08, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x0E, 0x1C, 0x0C, + 0x0C, 0x18, 0x06, 0x0C, 0x0C, 0x1C, 0x06, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x0C, 0x06, 0x0C, + 0x08, 0x00, 0x00, 0x06, 0x18, 0x02, 0x10, 0x06, 0x10, 0x0C, 0x0C, 0x03, 0x30, 0xF8, 0x07, 0x03, 0x60, 0xE0, 0x80, 0x01, + 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x70, 0x00, 0x00, 0x06, 0x1C, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define heart_height 30 +#define heart_width 30 +static unsigned char heart[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18, + 0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37, + 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F, + 0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03, + 0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00, + 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, +}; + +#define poo_width 30 +#define poo_height 30 +static unsigned char poo[] PROGMEM = { + 0x00, 0x1C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0xEC, 0x01, 0x00, 0x00, 0x8C, 0x07, 0x00, 0x00, 0x0C, 0x06, 0x00, + 0x00, 0x24, 0x0C, 0x00, 0x00, 0x34, 0x08, 0x00, 0x00, 0x1F, 0x08, 0x00, 0xC0, 0x0F, 0x08, 0x00, 0xC0, 0x00, 0x3C, 0x00, + 0x60, 0x00, 0x7C, 0x00, 0x60, 0x00, 0xC6, 0x00, 0x20, 0x00, 0xCB, 0x00, 0xA0, 0xC7, 0xFF, 0x00, 0xE0, 0x7F, 0xF7, 0x00, + 0xF0, 0x18, 0xE3, 0x03, 0x78, 0x18, 0x41, 0x03, 0x6C, 0x9B, 0x5D, 0x06, 0x64, 0x9B, 0x5D, 0x04, 0x44, 0x1A, 0x41, 0x04, + 0x4C, 0xD8, 0x63, 0x06, 0xF8, 0xFC, 0x36, 0x06, 0xFE, 0x0F, 0x9C, 0x1F, 0x07, 0x03, 0xC0, 0x30, 0x03, 0x00, 0x78, 0x20, + 0x01, 0x00, 0x1F, 0x20, 0x03, 0xE0, 0x03, 0x20, 0x07, 0x7E, 0x04, 0x30, 0xFE, 0x0F, 0xFC, 0x1F, 0xF0, 0x00, 0xF0, 0x0F, +}; + #include "img/icon.xbm" From 7d873eb06bae78926c4ce7008fe42f0e9fc0e6d8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 22 May 2024 20:40:26 -0500 Subject: [PATCH 0479/1377] Add exclude emoji macro --- src/graphics/Screen.cpp | 5 +++++ src/graphics/images.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 1e82eef7d..b469840d6 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -542,6 +542,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state } display->setColor(WHITE); +#ifndef EXCLUDE_EMOJI if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, @@ -597,6 +598,10 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); } +#else + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); +#endif } /// Draw the last waypoint we received diff --git a/src/graphics/images.h b/src/graphics/images.h index deaf08159..42b4b5b81 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -31,6 +31,7 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1, const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5}; #endif +#ifndef EXCLUDE_EMOJI #define thumbs_height 25 #define thumbs_width 25 static unsigned char thumbup[] PROGMEM = { @@ -195,5 +196,6 @@ static unsigned char poo[] PROGMEM = { 0x4C, 0xD8, 0x63, 0x06, 0xF8, 0xFC, 0x36, 0x06, 0xFE, 0x0F, 0x9C, 0x1F, 0x07, 0x03, 0xC0, 0x30, 0x03, 0x00, 0x78, 0x20, 0x01, 0x00, 0x1F, 0x20, 0x03, 0xE0, 0x03, 0x20, 0x07, 0x7E, 0x04, 0x30, 0xFE, 0x0F, 0xFC, 0x1F, 0xF0, 0x00, 0xF0, 0x0F, }; +#endif #include "img/icon.xbm" From 1a253dccc3aeb02290bfb09731c731bc5ed9b421 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 07:20:13 -0500 Subject: [PATCH 0480/1377] [create-pull-request] automated change (#3964) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/protobufs b/protobufs index 5cfadd148..b5dc871a1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5cfadd14890b7723a1fe6e7683f711911154b010 +Subproject commit b5dc871a1bfa2cc932126a4f490d9ef078476e4c diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index d692a3f30..2a209ad0a 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -75,7 +75,7 @@ typedef struct _meshtastic_HamParameters { Ensure your radio is capable of operating of the selected frequency before setting this. */ float frequency; /* Optional short name of user */ - char short_name[6]; + char short_name[5]; } meshtastic_HamParameters; /* Response envelope for node_remote_hardware_pins */ @@ -342,7 +342,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size #define meshtastic_AdminMessage_size 500 -#define meshtastic_HamParameters_size 32 +#define meshtastic_HamParameters_size 31 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 #ifdef __cplusplus From 2f9dc813d3c64b738777bfdba1efc9a4697f7ac2 Mon Sep 17 00:00:00 2001 From: andrew-moroz <67253360+andrew-moroz@users.noreply.github.com> Date: Thu, 23 May 2024 08:21:27 -0400 Subject: [PATCH 0481/1377] t-watch-updates: Add canned message free text via touch keyboard and watch face frames to T-Watch S3 (#3941) Co-authored-by: Ben Meadors --- src/graphics/PointStruct.h | 4 + src/graphics/Screen.cpp | 503 ++++++++++++++++++++++++++-- src/graphics/Screen.h | 26 ++ src/graphics/images.h | 6 + src/input/InputBroker.h | 2 + src/input/TouchScreenImpl1.cpp | 4 + src/modules/CannedMessageModule.cpp | 356 +++++++++++++++++++- src/modules/CannedMessageModule.h | 103 ++++++ 8 files changed, 967 insertions(+), 37 deletions(-) create mode 100644 src/graphics/PointStruct.h diff --git a/src/graphics/PointStruct.h b/src/graphics/PointStruct.h new file mode 100644 index 000000000..218731978 --- /dev/null +++ b/src/graphics/PointStruct.h @@ -0,0 +1,4 @@ +struct PointStruct { + int x; + int y; +}; \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b469840d6..9ffec9845 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -419,6 +419,466 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) return packet->from != 0 && !moduleConfig.store_forward.enabled; } +// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. +static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) +{ + static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD}; + static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85}; + // Clear the bar area on the battery image + for (int i = 1; i < 14; i++) { + imgBuffer[i] = 0x81; + } + // If charging, draw a charging indicator + if (powerStatus->getIsCharging()) { + memcpy(imgBuffer + 3, lightning, 8); + // If not charging, Draw power bars + } else { + for (int i = 0; i < 4; i++) { + if (powerStatus->getBatteryChargePercent() >= 25 * i) + memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); + } + } + display->drawFastImage(x, y, 16, 8, imgBuffer); +} + +#ifdef T_WATCH_S3 + +void Screen::drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale) +{ + uint16_t segmentWidth = SEGMENT_WIDTH * scale; + uint16_t segmentHeight = SEGMENT_HEIGHT * scale; + + if (digitalMode) { + uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2; + uint16_t centerX = (x + segmentHeight + 2) + (radius / 2); + uint16_t centerY = (y + segmentHeight + 2) + (radius / 2); + + display->drawCircle(centerX, centerY, radius); + display->drawCircle(centerX, centerY, radius + 1); + display->drawLine(centerX, centerY, centerX, centerY - radius + 3); + display->drawLine(centerX, centerY, centerX + radius - 3, centerY); + } else { + uint16_t segmentOneX = x + segmentHeight + 2; + uint16_t segmentOneY = y; + + uint16_t segmentTwoX = segmentOneX + segmentWidth + 2; + uint16_t segmentTwoY = segmentOneY + segmentHeight + 2; + + uint16_t segmentThreeX = segmentOneX; + uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2; + + uint16_t segmentFourX = x; + uint16_t segmentFourY = y + segmentHeight + 2; + + drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight); + drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight); + drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight); + drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight); + } +} + +// Draw a digital clock +void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_LEFT); + + drawBattery(display, x, y + 7, imgBattery, powerStatus); + + if (powerStatus->getHasBattery()) { + String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%"; + + display->setFont(FONT_SMALL); + + display->drawString(x + 20, y + 2, batteryPercent); + } + + if (nimbleBluetooth->isConnected()) { + drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2); + } + + drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1); + + display->setColor(OLEDDISPLAY_COLOR::WHITE); + + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + int hour = hms / SEC_PER_HOUR; + int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN + + hour = hour > 12 ? hour - 12 : hour; + + if (hour == 0) { + hour = 12; + } + + // hours string + String hourString = String(hour); + + // minutes string + String minuteString = minute < 10 ? "0" + String(minute) : String(minute); + + String timeString = hourString + ":" + minuteString; + + // seconds string + String secondString = second < 10 ? "0" + String(second) : String(second); + + float scale = 1.5; + + uint16_t segmentWidth = SEGMENT_WIDTH * scale; + uint16_t segmentHeight = SEGMENT_HEIGHT * scale; + + // calculate hours:minutes string width + uint16_t timeStringWidth = timeString.length() * 5; + + for (uint8_t i = 0; i < timeString.length(); i++) { + String character = String(timeString[i]); + + if (character == ":") { + timeStringWidth += segmentHeight; + } else { + timeStringWidth += segmentWidth + (segmentHeight * 2) + 4; + } + } + + // calculate seconds string width + uint16_t secondStringWidth = (secondString.length() * 12) + 4; + + // sum these to get total string width + uint16_t totalWidth = timeStringWidth + secondStringWidth; + + uint16_t hourMinuteTextX = (display->getWidth() / 2) - (totalWidth / 2); + + uint16_t startingHourMinuteTextX = hourMinuteTextX; + + uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2); + + // iterate over characters in hours:minutes string and draw segmented characters + for (uint8_t i = 0; i < timeString.length(); i++) { + String character = String(timeString[i]); + + if (character == ":") { + drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale); + + hourMinuteTextX += segmentHeight + 6; + } else { + drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character.toInt(), scale); + + hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4; + } + + hourMinuteTextX += 5; + } + + // draw seconds string + display->setFont(FONT_MEDIUM); + display->drawString(startingHourMinuteTextX + timeStringWidth + 4, + (display->getHeight() - hourMinuteTextY) - FONT_HEIGHT_MEDIUM + 6, secondString); + } +} + +void Screen::drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale) +{ + uint16_t segmentWidth = SEGMENT_WIDTH * scale; + uint16_t segmentHeight = SEGMENT_HEIGHT * scale; + + uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8; + + uint16_t topAndBottomX = x + (4 * scale); + + uint16_t quarterCellHeight = cellHeight / 4; + + uint16_t topY = y + quarterCellHeight; + uint16_t bottomY = y + (quarterCellHeight * 3); + + display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight); + display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight); +} + +void Screen::drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale) +{ + // the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of + // segment {innerIndex + 1} + // e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off. + uint8_t numbers[10][7] = { + {1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key + {0, 1, 1, 0, 0, 0, 0}, // 1 1 + {1, 1, 0, 1, 1, 0, 1}, // 2 ___ + {1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2 + {0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_| + {1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3 + {1, 0, 1, 1, 1, 1, 1}, // 6 |___| + {1, 1, 1, 0, 0, 1, 0}, // 7 + {1, 1, 1, 1, 1, 1, 1}, // 8 4 + {1, 1, 1, 1, 0, 1, 1}, // 9 + }; + + // the width and height of each segment's central rectangle: + // _____________________ + // ⋰| (only this part, |⋱ + // ⋰ | not including | ⋱ + // ⋱ | the triangles | ⋰ + // ⋱| on the ends) |⋰ + // ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + + uint16_t segmentWidth = SEGMENT_WIDTH * scale; + uint16_t segmentHeight = SEGMENT_HEIGHT * scale; + + // segment x and y coordinates + uint16_t segmentOneX = x + segmentHeight + 2; + uint16_t segmentOneY = y; + + uint16_t segmentTwoX = segmentOneX + segmentWidth + 2; + uint16_t segmentTwoY = segmentOneY + segmentHeight + 2; + + uint16_t segmentThreeX = segmentTwoX; + uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2; + + uint16_t segmentFourX = segmentOneX; + uint16_t segmentFourY = segmentThreeY + segmentWidth + 2; + + uint16_t segmentFiveX = x; + uint16_t segmentFiveY = segmentThreeY; + + uint16_t segmentSixX = x; + uint16_t segmentSixY = segmentTwoY; + + uint16_t segmentSevenX = segmentOneX; + uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2; + + if (numbers[number][0]) { + drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight); + } + + if (numbers[number][1]) { + drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight); + } + + if (numbers[number][2]) { + drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight); + } + + if (numbers[number][3]) { + drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight); + } + + if (numbers[number][4]) { + drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight); + } + + if (numbers[number][5]) { + drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight); + } + + if (numbers[number][6]) { + drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight); + } +} + +void Screen::drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height) +{ + int halfHeight = height / 2; + + // draw central rectangle + display->fillRect(x, y, width, height); + + // draw end triangles + display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight); + + display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1); +} + +void Screen::drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height) +{ + int halfHeight = height / 2; + + // draw central rectangle + display->fillRect(x, y, height, width); + + // draw end triangles + display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y); + + display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight); +} + +void Screen::drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y) +{ + display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon); +} + +// Draw an analog clock +void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_LEFT); + + drawBattery(display, x, y + 7, imgBattery, powerStatus); + + if (powerStatus->getHasBattery()) { + String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%"; + + display->setFont(FONT_SMALL); + + display->drawString(x + 20, y + 2, batteryPercent); + } + + if (nimbleBluetooth->isConnected()) { + drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2); + } + + drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1); + + // clock face center coordinates + int16_t centerX = display->getWidth() / 2; + int16_t centerY = display->getHeight() / 2; + + // clock face radius + int16_t radius = (display->getWidth() / 2) * 0.8; + + // noon (0 deg) coordinates (outermost circle) + int16_t noonX = centerX; + int16_t noonY = centerY - radius; + + // second hand radius and y coordinate (outermost circle) + int16_t secondHandNoonY = noonY + 1; + + // tick mark outer y coordinate; (first nested circle) + int16_t tickMarkOuterNoonY = secondHandNoonY; + + // seconds tick mark inner y coordinate; (second nested circle) + double secondsTickMarkInnerNoonY = (double)noonY + 8; + + // hours tick mark inner y coordinate; (third nested circle) + double hoursTickMarkInnerNoonY = (double)noonY + 16; + + // minute hand y coordinate + int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4; + + // hour string y coordinate + int16_t hourStringNoonY = minuteHandNoonY + 18; + + // hour hand radius and y coordinate + int16_t hourHandRadius = radius * 0.55; + int16_t hourHandNoonY = centerY - hourHandRadius; + + display->setColor(OLEDDISPLAY_COLOR::WHITE); + display->drawCircle(centerX, centerY, radius); + + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into h:m:s + int hour = hms / SEC_PER_HOUR; + int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN + + hour = hour > 12 ? hour - 12 : hour; + + int16_t degreesPerHour = 30; + int16_t degreesPerMinuteOrSecond = 6; + + double hourBaseAngle = hour * degreesPerHour; + double hourAngleOffset = ((double)minute / 60) * degreesPerHour; + double hourAngle = radians(hourBaseAngle + hourAngleOffset); + + double minuteBaseAngle = minute * degreesPerMinuteOrSecond; + double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond; + double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset); + + double secondAngle = radians(second * degreesPerMinuteOrSecond); + + double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX; + double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY; + + double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX; + double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY; + + double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX; + double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY; + + display->setFont(FONT_MEDIUM); + + // draw minute and hour tick marks and hour numbers + for (uint16_t angle = 0; angle < 360; angle += 6) { + double angleInRadians = radians(angle); + + double sineAngleInRadians = sin(-angleInRadians); + double cosineAngleInRadians = cos(-angleInRadians); + + double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX; + double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY; + + if (angle % degreesPerHour == 0) { + double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX; + double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY; + + // draw hour tick mark + display->drawLine(startX, startY, endX, endY); + + static char buffer[2]; + + uint8_t hourInt = (angle / 30); + + if (hourInt == 0) { + hourInt = 12; + } + + // hour number x offset needs to be adjusted for some cases + int8_t hourStringXOffset; + int8_t hourStringYOffset = 13; + + switch (hourInt) { + case 3: + hourStringXOffset = 5; + break; + case 9: + hourStringXOffset = 7; + break; + case 10: + case 11: + hourStringXOffset = 8; + break; + case 12: + hourStringXOffset = 13; + break; + default: + hourStringXOffset = 6; + break; + } + + double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset; + double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset; + + // draw hour number + display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt); + } + + if (angle % degreesPerMinuteOrSecond == 0) { + double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX; + double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY; + + // draw minute tick mark + display->drawLine(startX, startY, endX, endY); + } + } + + // draw hour hand + display->drawLine(centerX, centerY, hourX, hourY); + + // draw minute hand + display->drawLine(centerX, centerY, minuteX, minuteY); + + // draw second hand + display->drawLine(centerX, centerY, secondX, secondY); + } +} + +#endif + // Get an absolute time from "seconds ago" info. Returns false if no valid timestamp possible bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo) { @@ -664,28 +1124,6 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char * } } -// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. -static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) -{ - static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD}; - static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85}; - // Clear the bar area on the battery image - for (int i = 1; i < 14; i++) { - imgBuffer[i] = 0x81; - } - // If charging, draw a charging indicator - if (powerStatus->getIsCharging()) { - memcpy(imgBuffer + 3, lightning, 8); - // If not charging, Draw power bars - } else { - for (int i = 0; i < 4; i++) { - if (powerStatus->getBatteryChargePercent() >= 25 * i) - memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); - } - } - display->drawFastImage(x, y, 16, 8, imgBuffer); -} - // Draw nodes status static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus) { @@ -1377,6 +1815,10 @@ int32_t Screen::runOnce() return RUN_SAME; } + if (displayHeight == 0) { + displayHeight = dispdev->getHeight(); + } + // Show boot screen for first logo_timeout seconds, then switch to normal operation. // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup static bool showingBootScreen = true; @@ -1655,6 +2097,10 @@ void Screen::setFrames() normalFrames[numframes++] = drawWaypointFrame; } +#ifdef T_WATCH_S3 + normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame; +#endif + // then all the nodes // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens size_t numToShow = min(numMeshNodes, 4U); @@ -2226,6 +2672,19 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) int Screen::handleInputEvent(const InputEvent *event) { + +#ifdef T_WATCH_S3 + // For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button + if (this->ui->getUiState()->currentFrame == 0 && event->touchX >= 204 && event->touchX <= 240 && event->touchY >= 204 && + event->touchY <= 240) { + screen->digitalWatchFace = !screen->digitalWatchFace; + + setFrames(); + + return 0; + } +#endif + if (showingNormalScreen && moduleFrames.size() == 0) { // LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index cfb08c0f4..8c4650271 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -48,6 +48,7 @@ class Screen #include "EInkDisplay2.h" #include "EInkDynamicDisplay.h" +#include "PointStruct.h" #include "TFTDisplay.h" #include "TypedQueue.h" #include "commands.h" @@ -77,6 +78,10 @@ class Screen #define EINK_BLACK OLEDDISPLAY_COLOR::WHITE #define EINK_WHITE OLEDDISPLAY_COLOR::BLACK +// Base segment dimensions for T-Watch segmented display +#define SEGMENT_WIDTH 16 +#define SEGMENT_HEIGHT 4 + namespace graphics { @@ -389,6 +394,27 @@ class Screen : public concurrency::OSThread static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +#ifdef T_WATCH_S3 + static void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + + static void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + + static void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1); + + static void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height); + + static void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height); + + static void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1); + + static void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1); + + static void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y); + + // Whether we are showing the digital watch face or the analog one + bool digitalWatchFace = true; +#endif + /// Queue of commands to execute in doTask. TypedQueue cmdQueue; /// Whether we are using a display diff --git a/src/graphics/images.h b/src/graphics/images.h index 42b4b5b81..d4c738610 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -14,6 +14,12 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3 const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF}; const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF}; +#ifdef T_WATCH_S3 +const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0xe3, 0x1f, + 0xf3, 0x3f, 0x33, 0x30, 0x33, 0x33, 0x33, 0x33, 0x03, 0x33, 0xff, 0x33, + 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; +#endif + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index d73e6657a..57c25af4b 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -8,6 +8,8 @@ typedef struct _InputEvent { const char *source; char inputEvent; char kbchar; + uint16_t touchX; + uint16_t touchY; } InputEvent; class InputBroker : public Observable { diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index c863ead69..20196278d 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -49,6 +49,10 @@ void TouchScreenImpl1::onEvent(const TouchEvent &event) { InputEvent e; e.source = event.source; + + e.touchX = event.x; + e.touchY = event.y; + switch (event.touchEvent) { case TOUCH_ACTION_LEFT: { e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 0f17c268b..5292fec69 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -47,6 +47,12 @@ CannedMessageModule::CannedMessageModule() disable(); } else { LOG_INFO("CannedMessageModule is enabled\n"); + + // T-Watch interface currently has no way to select destination type, so default to 'node' +#ifdef T_WATCH_S3 + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; +#endif + this->inputObserver.observe(inputBroker); } } else { @@ -67,8 +73,14 @@ int CannedMessageModule::splitConfiguredMessages() int messageIndex = 0; int i = 0; + String messages = cannedMessageModuleConfig.messages; + + String separator = messages.length() ? "|" : ""; + + messages = "[---- Free Text ----]" + separator + messages; + // collect all the message parts - strncpy(this->messageStore, cannedMessageModuleConfig.messages, sizeof(this->messageStore)); + strncpy(this->messageStore, messages.c_str(), sizeof(this->messageStore)); // The first message points to the beginning of the store. this->messages[messageIndex++] = this->messageStore; @@ -78,7 +90,6 @@ int CannedMessageModule::splitConfiguredMessages() if (this->messageStore[i] == '|') { // Message ending found, replace it with string-end character. this->messageStore[i] = '\0'; - LOG_DEBUG("CannedMessage %d is: '%s'\n", messageIndex - 1, this->messages[messageIndex - 1]); // hit our max messages, bail if (messageIndex >= CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT) { @@ -119,20 +130,27 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) bool validEvent = false; if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP)) { if (this->messagesCount > 0) { - // LOG_DEBUG("Canned message event UP\n"); this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP; validEvent = true; } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN)) { if (this->messagesCount > 0) { - // LOG_DEBUG("Canned message event DOWN\n"); this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; validEvent = true; } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) { - LOG_DEBUG("Canned message event Select\n"); + if (this->currentMessageIndex == 0) { + this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; + + UIFrameEvent e = {false, true}; + e.frameChanged = true; + this->notifyObservers(&e); + + return 0; + } + // when inactive, call the onebutton shortpress instead. Activate Module only on up/down if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) { powerFSM.trigger(EVENT_PRESS); @@ -143,38 +161,47 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { - LOG_DEBUG("Canned message event Cancel\n"); UIFrameEvent e = {false, true}; e.frameChanged = true; this->currentMessageIndex = -1; + +#ifndef T_WATCH_S3 this->freetext = ""; // clear freetext this->cursor = 0; this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; +#endif + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; this->notifyObservers(&e); } if ((event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK)) || (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) || (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) { - // LOG_DEBUG("Canned message event (%x)\n", event->kbchar); + +#ifdef T_WATCH_S3 + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { + this->payload = 0xb4; + } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { + this->payload = 0xb7; + } +#else // tweak for left/right events generated via trackball/touch with empty kbchar if (!event->kbchar) { if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { this->payload = 0xb4; - // this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { this->payload = 0xb7; - // this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } } else { // pass the pressed key this->payload = event->kbchar; } +#endif + this->lastTouchMillis = millis(); validEvent = true; } if (event->inputEvent == static_cast(ANYKEY)) { - LOG_DEBUG("Canned message event any key pressed\n"); // when inactive, this will switch to the freetext mode if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) { @@ -250,8 +277,68 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) screen->removeFunctionSymbal("Fn"); // remove modifier (function) symbal } } + +#ifdef T_WATCH_S3 + if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + String keyTapped = keyForCoordinates(event->touchX, event->touchY); + + if (keyTapped == "⇧") { + this->highlight = -1; + + this->payload = 0x00; + + validEvent = true; + + this->shift = !this->shift; + } else if (keyTapped == "⌫") { + this->highlight = keyTapped[0]; + + this->payload = 0x08; + + validEvent = true; + + this->shift = false; + } else if (keyTapped == "123" || keyTapped == "ABC") { + this->highlight = -1; + + this->payload = 0x00; + + this->charSet = this->charSet == 0 ? 1 : 0; + + validEvent = true; + } else if (keyTapped == " ") { + this->highlight = keyTapped[0]; + + this->payload = keyTapped[0]; + + validEvent = true; + + this->shift = false; + } else if (keyTapped == "↵") { + this->highlight = 0x00; + + this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; + + this->payload = CANNED_MESSAGE_RUN_STATE_FREETEXT; + + this->currentMessageIndex = event->kbchar - 1; + + validEvent = true; + + this->shift = false; + } else if (keyTapped != "") { + this->highlight = keyTapped[0]; + + this->payload = this->shift ? keyTapped[0] : std::tolower(keyTapped[0]); + + validEvent = true; + + this->shift = false; + } + } +#endif + if (event->inputEvent == static_cast(MATRIXKEY)) { - LOG_DEBUG("Canned message event Matrix key pressed\n"); // this will send the text immediately on matrix press this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; this->payload = MATRIXKEY; @@ -311,17 +398,24 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; + +#ifndef T_WATCH_S3 this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; +#endif + this->notifyObservers(&e); } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { // Reset module - LOG_DEBUG("Reset due to lack of activity.\n"); e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; + +#ifndef T_WATCH_S3 this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; +#endif + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; this->notifyObservers(&e); } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { @@ -330,7 +424,6 @@ int32_t CannedMessageModule::runOnce() sendText(this->dest, indexChannels[this->channel], this->freetext.c_str(), true); this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; } else { - LOG_DEBUG("Reset message is empty.\n"); this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } else { @@ -339,11 +432,15 @@ int32_t CannedMessageModule::runOnce() powerFSM.trigger(EVENT_PRESS); return INT32_MAX; } else { +#ifdef T_WATCH_S3 + sendText(this->dest, indexChannels[this->channel], this->messages[this->currentMessageIndex], true); +#else sendText(NODENUM_BROADCAST, channels.getPrimaryIndex(), this->messages[this->currentMessageIndex], true); +#endif } this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; } else { - LOG_DEBUG("Reset message is empty.\n"); + // LOG_DEBUG("Reset message is empty.\n"); this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } @@ -351,7 +448,11 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; + +#ifndef T_WATCH_S3 this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; +#endif + this->notifyObservers(&e); return 2000; } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { @@ -364,7 +465,11 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = getPrevIndex(); this->freetext = ""; // clear freetext this->cursor = 0; + +#ifndef T_WATCH_S3 this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; +#endif + this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE UP (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } @@ -373,7 +478,11 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = this->getNextIndex(); this->freetext = ""; // clear freetext this->cursor = 0; + +#ifndef T_WATCH_S3 this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; +#endif + this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE DOWN (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } @@ -457,7 +566,7 @@ int32_t CannedMessageModule::runOnce() switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the // display back to the default window case 0x08: // backspace - if (this->freetext.length() > 0) { + if (this->freetext.length() > 0 && this->highlight == 0x00) { if (this->cursor == this->freetext.length()) { this->freetext = this->freetext.substring(0, this->freetext.length() - 1); } else { @@ -495,13 +604,19 @@ int32_t CannedMessageModule::runOnce() runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; default: + if (this->highlight != 0x00) { + break; + } + if (this->cursor == this->freetext.length()) { this->freetext += this->payload; } else { this->freetext = this->freetext.substring(0, this->cursor) + this->payload + this->freetext.substring(this->cursor); } + this->cursor += 1; + uint16_t maxChars = meshtastic_Constants_DATA_PAYLOAD_LEN - (moduleConfig.canned_message.send_bell ? 1 : 0); if (this->freetext.length() > maxChars) { this->cursor = maxChars; @@ -594,6 +709,201 @@ void CannedMessageModule::showTemporaryMessage(const String &message) setIntervalFromNow(2000); } +#ifdef T_WATCH_S3 + +String CannedMessageModule::keyForCoordinates(uint x, uint y) +{ + int outerSize = *(&this->keyboard[this->charSet] + 1) - this->keyboard[this->charSet]; + + for (int8_t outerIndex = 0; outerIndex < outerSize; outerIndex++) { + int innerSize = *(&this->keyboard[this->charSet][outerIndex] + 1) - this->keyboard[this->charSet][outerIndex]; + + for (int8_t innerIndex = 0; innerIndex < innerSize; innerIndex++) { + Letter letter = this->keyboard[this->charSet][outerIndex][innerIndex]; + + if (x > letter.rectX && x < (letter.rectX + letter.rectWidth) && y > letter.rectY && + y < (letter.rectY + letter.rectHeight)) { + return letter.character; + } + } + } + + return ""; +} + +void CannedMessageModule::drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + int outerSize = *(&this->keyboard[this->charSet] + 1) - this->keyboard[this->charSet]; + + int xOffset = 0; + + int yOffset = 56; + + display->setTextAlignment(TEXT_ALIGN_LEFT); + + display->setFont(FONT_SMALL); + + display->setColor(OLEDDISPLAY_COLOR::WHITE); + + display->drawStringMaxWidth(0, 0, display->getWidth(), + cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor)); + + display->setFont(FONT_MEDIUM); + + int cellHeight = round((display->height() - 64) / outerSize); + + int yCorrection = 8; + + for (int8_t outerIndex = 0; outerIndex < outerSize; outerIndex++) { + yOffset += outerIndex > 0 ? cellHeight : 0; + + int innerSizeBound = *(&this->keyboard[this->charSet][outerIndex] + 1) - this->keyboard[this->charSet][outerIndex]; + + int innerSize = 0; + + for (int8_t innerIndex = 0; innerIndex < innerSizeBound; innerIndex++) { + if (this->keyboard[this->charSet][outerIndex][innerIndex].character != "") { + innerSize++; + } + } + + int cellWidth = display->width() / innerSize; + + for (int8_t innerIndex = 0; innerIndex < innerSize; innerIndex++) { + xOffset += innerIndex > 0 ? cellWidth : 0; + + Letter letter = this->keyboard[this->charSet][outerIndex][innerIndex]; + + Letter updatedLetter = {letter.character, letter.width, xOffset, yOffset, cellWidth, cellHeight}; + + this->keyboard[this->charSet][outerIndex][innerIndex] = updatedLetter; + + float characterOffset = ((cellWidth / 2) - (letter.width / 2)); + + if (letter.character == "⇧") { + if (this->shift) { + display->fillRect(xOffset, yOffset, cellWidth, cellHeight); + + display->setColor(OLEDDISPLAY_COLOR::BLACK); + + drawShiftIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2); + + display->setColor(OLEDDISPLAY_COLOR::WHITE); + } else { + display->drawRect(xOffset, yOffset, cellWidth, cellHeight); + + drawShiftIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2); + } + } else if (letter.character == "⌫") { + if (this->highlight == letter.character[0]) { + display->fillRect(xOffset, yOffset, cellWidth, cellHeight); + + display->setColor(OLEDDISPLAY_COLOR::BLACK); + + drawBackspaceIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2); + + display->setColor(OLEDDISPLAY_COLOR::WHITE); + + setIntervalFromNow(0); + } else { + display->drawRect(xOffset, yOffset, cellWidth, cellHeight); + + drawBackspaceIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2); + } + } else if (letter.character == "↵") { + display->drawRect(xOffset, yOffset, cellWidth, cellHeight); + + drawEnterIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.7); + } else { + if (this->highlight == letter.character[0]) { + display->fillRect(xOffset, yOffset, cellWidth, cellHeight); + + display->setColor(OLEDDISPLAY_COLOR::BLACK); + + display->drawString(xOffset + characterOffset, yOffset + yCorrection, + letter.character == " " ? "space" : letter.character); + + display->setColor(OLEDDISPLAY_COLOR::WHITE); + + setIntervalFromNow(0); + } else { + display->drawRect(xOffset, yOffset, cellWidth, cellHeight); + + display->drawString(xOffset + characterOffset, yOffset + yCorrection, + letter.character == " " ? "space" : letter.character); + } + } + } + + xOffset = 0; + } + + this->highlight = 0x00; +} + +void CannedMessageModule::drawShiftIcon(OLEDDisplay *display, int x, int y, float scale) +{ + PointStruct shiftIcon[10] = {{8, 0}, {15, 7}, {15, 8}, {12, 8}, {12, 12}, {4, 12}, {4, 8}, {1, 8}, {1, 7}, {8, 0}}; + + int size = 10; + + for (int i = 0; i < size - 1; i++) { + int x0 = x + (shiftIcon[i].x * scale); + int y0 = y + (shiftIcon[i].y * scale); + int x1 = x + (shiftIcon[i + 1].x * scale); + int y1 = y + (shiftIcon[i + 1].y * scale); + + display->drawLine(x0, y0, x1, y1); + } +} + +void CannedMessageModule::drawBackspaceIcon(OLEDDisplay *display, int x, int y, float scale) +{ + PointStruct backspaceIcon[6] = {{0, 7}, {5, 2}, {15, 2}, {15, 12}, {5, 12}, {0, 7}}; + + int size = 6; + + for (int i = 0; i < size - 1; i++) { + int x0 = x + (backspaceIcon[i].x * scale); + int y0 = y + (backspaceIcon[i].y * scale); + int x1 = x + (backspaceIcon[i + 1].x * scale); + int y1 = y + (backspaceIcon[i + 1].y * scale); + + display->drawLine(x0, y0, x1, y1); + } + + PointStruct backspaceIconX[4] = {{7, 4}, {13, 10}, {7, 10}, {13, 4}}; + + size = 4; + + for (int i = 0; i < size - 1; i++) { + int x0 = x + (backspaceIconX[i].x * scale); + int y0 = y + (backspaceIconX[i].y * scale); + int x1 = x + (backspaceIconX[i + 1].x * scale); + int y1 = y + (backspaceIconX[i + 1].y * scale); + + display->drawLine(x0, y0, x1, y1); + } +} + +void CannedMessageModule::drawEnterIcon(OLEDDisplay *display, int x, int y, float scale) +{ + PointStruct enterIcon[6] = {{0, 7}, {4, 3}, {4, 11}, {0, 7}, {15, 7}, {15, 0}}; + + int size = 6; + + for (int i = 0; i < size - 1; i++) { + int x0 = x + (enterIcon[i].x * scale); + int y0 = y + (enterIcon[i].y * scale); + int x1 = x + (enterIcon[i + 1].x * scale); + int y1 = y + (enterIcon[i + 1].y * scale); + + display->drawLine(x0, y0, x1, y1); + } +} + +#endif + void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { char buffer[50]; @@ -614,6 +924,16 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, displayString, cannedMessageModule->getNodeName(this->incoming)); + + display->setFont(FONT_SMALL); + + String snrString = "Last Rx SNR: %f"; + String rssiString = "Last Rx RSSI: %d"; + + if (this->ack) { + display->drawStringf(display->getWidth() / 2 + x, y + 100, buffer, snrString, this->lastRxSnr); + display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi); + } } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); @@ -623,6 +943,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setFont(FONT_SMALL); display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled."); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + +#ifdef T_WATCH_S3 + drawKeyboard(display, state, 0, 0); +#else + display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); if (this->destSelect != CANNED_MESSAGE_DESTINATION_TYPE_NONE) { @@ -663,6 +988,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawStringMaxWidth( 0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor)); +#endif } else { if (this->messagesCount > 0) { display->setTextAlignment(TEXT_ALIGN_LEFT); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index faf1d80f3..43897e782 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -22,6 +22,17 @@ enum cannedMessageDestinationType { CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL }; +enum CannedMessageModuleIconType { shift, backspace, space, enter }; + +struct Letter { + String character; + float width; + int rectX; + int rectY; + int rectWidth; + int rectHeight; +}; + #define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50 /** * Sum of CannedMessageModuleConfig part sizes. @@ -61,6 +72,14 @@ class CannedMessageModule : public SinglePortModule, public Observablerx_rssi != 0) { + this->lastRxRssi = p->rx_rssi; + } + + if (p->rx_snr > 0) { + this->lastRxSnr = p->rx_snr; + } + switch (p->decoded.portnum) { case meshtastic_PortNum_TEXT_MESSAGE_APP: case meshtastic_PortNum_ROUTING_APP: @@ -79,6 +98,18 @@ class CannedMessageModule : public SinglePortModule, public ObservableshouldDraw(); } virtual Observable *getUIFrameObservable() override { return this; } @@ -110,12 +141,84 @@ class CannedMessageModule : public SinglePortModule, public Observable Date: Fri, 24 May 2024 00:02:03 -0400 Subject: [PATCH 0482/1377] t-watch-fix: Fully insulate T-Watch free text updates from other hardware platforms --- src/modules/CannedMessageModule.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 5292fec69..9b993ae5a 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -75,9 +75,11 @@ int CannedMessageModule::splitConfiguredMessages() String messages = cannedMessageModuleConfig.messages; +#ifdef T_WATCH_S3 String separator = messages.length() ? "|" : ""; messages = "[---- Free Text ----]" + separator + messages; +#endif // collect all the message parts strncpy(this->messageStore, messages.c_str(), sizeof(this->messageStore)); @@ -141,6 +143,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) { + +#ifdef T_WATCH_S3 if (this->currentMessageIndex == 0) { this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; @@ -150,6 +154,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) return 0; } +#endif // when inactive, call the onebutton shortpress instead. Activate Module only on up/down if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) { From 2233507667c6030cc86d9e66a3746b2ed828d8ba Mon Sep 17 00:00:00 2001 From: capslockapocalypse <140287230+capslockapocalypse@users.noreply.github.com> Date: Fri, 24 May 2024 23:08:21 +1000 Subject: [PATCH 0483/1377] Added "Hops away" on display (#3934) * Added "Hops away" on display Added in logic that displays "hops away" instead of signal strength when the node is more than 0 hops away. * Added comment * Update extensions.json to same as master * attempt 2 at reverting extensions JSON * Attempt 3 at getting extensions right * Take 4. should be reverting to before my edits --------- Co-authored-by: Ben Meadors --- src/graphics/Screen.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 9ffec9845..9758e97fd 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1459,7 +1459,18 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const char *username = node->has_user ? node->user.long_name : "Unknown Name"; static char signalStr[20]; - snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); + + //section here to choose whether to display hops away rather than signal strength if more than 0 hops away. + if(node->hops_away>0) + { + snprintf(signalStr, sizeof(signalStr), "Hops Away: %d", node->hops_away); + } + else + { + snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); + } + + uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; From dca8615eaade72d847866058ce75795a17b1b38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 24 May 2024 22:59:31 +0200 Subject: [PATCH 0484/1377] change type to 8 bit uint --- src/detect/ScanI2C.cpp | 2 +- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 6 +++--- src/detect/ScanI2CTwoWire.h | 2 +- src/input/kbI2cBase.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 7d0a83653..f3057c81a 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -6,7 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} -void ScanI2C::scanPort(ScanI2C::I2CPort port, int *address) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address) {} void ScanI2C::setSuppressScreen() { diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 4aa4549cc..64c9ddbc2 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -81,7 +81,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); - virtual void scanPort(ScanI2C::I2CPort, int *); + virtual void scanPort(ScanI2C::I2CPort, uint8_t *); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index f7068ee02..06b94d6af 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -135,7 +135,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port, int *address) +void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,8 +163,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, int *address) #endif for (addr.address = 1; addr.address < 127; addr.address++) { - // Skip the address if it is not requested oon a partial scan - if (sizeof(address) > 0 && addr.address != *address) { + // Skip the address if it is not requested on a partial scan + if (address != nullptr && *address != addr.address) { continue; } i2cBus->beginTransmission(addr.address); diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index a7c19c779..332afbf64 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,7 +16,7 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; - void scanPort(ScanI2C::I2CPort, int *) override; + void scanPort(ScanI2C::I2CPort, uint8_t *) override; ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 55f435fda..135de1716 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -33,7 +33,7 @@ int32_t KbI2cBase::runOnce() if (cardkb_found.address == 0x00) { // Input device is not detected. Rescan now. auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); - int i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; #if defined(I2C_SDA1) i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); #endif From c46c3427f0c6ae8809e0aec3a731792f724dcd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 25 May 2024 12:37:55 +0200 Subject: [PATCH 0485/1377] Iterate through uint array --- src/detect/ScanI2C.cpp | 2 +- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 21 +++++++++++++++------ src/detect/ScanI2CTwoWire.h | 2 +- src/input/kbI2cBase.cpp | 5 +++-- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index f3057c81a..525780af6 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -6,7 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} -void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address, uint8_t asize) {} void ScanI2C::setSuppressScreen() { diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 64c9ddbc2..4084c4479 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -81,7 +81,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); - virtual void scanPort(ScanI2C::I2CPort, uint8_t *); + virtual void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 06b94d6af..3376d23be 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -14,6 +14,15 @@ #define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 #endif +bool in_array(uint8_t *array, int size, uint8_t lookfor) +{ + int i; + for (i = 0; i < size; i++) + if (lookfor == array[i]) + return true; + return false; +} + ScanI2C::FoundDevice ScanI2CTwoWire::find(ScanI2C::DeviceType type) const { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -135,7 +144,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) +void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,10 +172,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) #endif for (addr.address = 1; addr.address < 127; addr.address++) { - // Skip the address if it is not requested on a partial scan - if (address != nullptr && *address != addr.address) { - continue; - } + if (asize != 0) + if (in_array(address, asize, addr.address)) + continue; + i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO if (i2cBus->read() != -1) @@ -359,7 +368,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) void ScanI2CTwoWire::scanPort(I2CPort port) { - scanPort(port, nullptr); + scanPort(port, nullptr, 0); } TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 332afbf64..82b48f6b4 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,7 +16,7 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; - void scanPort(ScanI2C::I2CPort, uint8_t *) override; + void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t) override; ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 135de1716..ce22edb93 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -34,10 +34,11 @@ int32_t KbI2cBase::runOnce() // Input device is not detected. Rescan now. auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; + uint8_t i2caddr_asize = 3; #if defined(I2C_SDA1) - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); #endif - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); auto kb_info = i2cScanner->firstKeyboard(); if (kb_info.type != ScanI2C::DeviceType::NONE) { From 34553c971432812062b6c73636fd20360703231a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 26 May 2024 06:42:23 -0500 Subject: [PATCH 0486/1377] Bump portduino to pick up improvements to reboot() (#3975) --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index be374df32..482b1f9c5 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#f5ec3c031b0fcd89c0523de9e43eef3a92d59292 +platform = https://github.com/meshtastic/platform-native.git#ad8112adf82ce1f5b917092cf32be07a077801a0 framework = arduino build_src_filter = From aa33ad1d5845e99ce15ec57cd5e6529f557883ed Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 26 May 2024 14:42:44 +0300 Subject: [PATCH 0487/1377] Fix memory leak when there's no display (#3972) --- src/graphics/Screen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 8c4650271..7f8d078e7 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -360,7 +360,7 @@ class Screen : public concurrency::OSThread bool enqueueCmd(const ScreenCmd &cmd) { if (!useDisplay) - return true; // claim success if our display is not in use + return false; // not enqueued if our display is not in use else { bool success = cmdQueue.enqueue(cmd, 0); enabled = true; // handle ASAP (we are the registered reader for cmdQueue, but might have been disabled) From 77cf5c62008a1e8e3c331fb52dbe9bf1985a35e9 Mon Sep 17 00:00:00 2001 From: andrew-moroz <67253360+andrew-moroz@users.noreply.github.com> Date: Sun, 26 May 2024 08:04:31 -0400 Subject: [PATCH 0488/1377] Fix time updates from client device and potentially incorrect UI frame receiving 'toggle watch face' button tap (#3974) --- src/gps/RTC.cpp | 8 ++++++-- src/gps/RTC.h | 2 +- src/graphics/Screen.cpp | 27 ++++++++++++--------------- src/modules/PositionModule.cpp | 24 +++++++++++++++++++++--- src/modules/PositionModule.h | 2 +- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index a2cdb5b30..864b246a3 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -96,13 +96,17 @@ void readFromRTC() * * If we haven't yet set our RTC this boot, set it from a GPS derived time */ -bool perhapsSetRTC(RTCQuality q, const struct timeval *tv) +bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) { static uint32_t lastSetMsec = 0; uint32_t now = millis(); bool shouldSet; - if (q > currentQuality) { + if (forceUpdate) { + shouldSet = true; + LOG_DEBUG("Overriding current RTC quality (%s) with incoming time of RTC quality of %s\n", RtcName(currentQuality), + RtcName(q)); + } else if (q > currentQuality) { shouldSet = true; LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q)); } else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 1d609f136..4b065b376 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -25,7 +25,7 @@ enum RTCQuality { RTCQuality getRTCQuality(); /// If we haven't yet set our RTC this boot, set it from a GPS derived time -bool perhapsSetRTC(RTCQuality q, const struct timeval *tv); +bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); bool perhapsSetRTC(RTCQuality q, struct tm &t); /// Return a string name for the quality diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 9758e97fd..15e69b1ed 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1459,18 +1459,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const char *username = node->has_user ? node->user.long_name : "Unknown Name"; static char signalStr[20]; - - //section here to choose whether to display hops away rather than signal strength if more than 0 hops away. - if(node->hops_away>0) - { + + // section here to choose whether to display hops away rather than signal strength if more than 0 hops away. + if (node->hops_away > 0) { snprintf(signalStr, sizeof(signalStr), "Hops Away: %d", node->hops_away); - } - else - { + } else { snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); } - - uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; @@ -2099,6 +2094,10 @@ void Screen::setFrames() if (error_code) normalFrames[numframes++] = drawCriticalFaultFrame; +#ifdef T_WATCH_S3 + normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame; +#endif + // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { normalFrames[numframes++] = drawTextMessageFrame; @@ -2108,10 +2107,6 @@ void Screen::setFrames() normalFrames[numframes++] = drawWaypointFrame; } -#ifdef T_WATCH_S3 - normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame; -#endif - // then all the nodes // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens size_t numToShow = min(numMeshNodes, 4U); @@ -2686,8 +2681,10 @@ int Screen::handleInputEvent(const InputEvent *event) #ifdef T_WATCH_S3 // For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button - if (this->ui->getUiState()->currentFrame == 0 && event->touchX >= 204 && event->touchX <= 240 && event->touchY >= 204 && - event->touchY <= 240) { + uint8_t watchFaceFrame = error_code ? 1 : 0; + + if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 && + event->touchY >= 204 && event->touchY <= 240) { screen->digitalWatchFace = !screen->digitalWatchFace; setFrames(); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 9986f860d..f3a70f4c5 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -55,6 +55,15 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes isLocal = true; if (config.position.fixed_position) { LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n"); + +#ifdef T_WATCH_S3 + // Since we return early if position.fixed_position is true, set the T-Watch's RTC to the time received from the + // client device here + if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { + trySetRtc(p, isLocal, true); + } +#endif + nodeDB->setLocalPosition(p, true); return false; } else { @@ -71,8 +80,17 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes p.time); if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { + bool force = false; + +#ifdef T_WATCH_S3 + // The T-Watch appears to "pause" its RTC when shut down, such that the time it reads upon powering on is the same as when + // it was shut down. So we need to force the update here, since otherwise RTC::perhapsSetRTC will ignore it because it + // will always be an equivalent or lesser RTCQuality (RTCQualityNTP or RTCQualityNet). + force = true; +#endif + // Set from phone RTC Quality to RTCQualityNTP since it should be approximately so - trySetRtc(p, isLocal); + trySetRtc(p, isLocal, force); } nodeDB->updatePosition(getFrom(&mp), p); @@ -104,14 +122,14 @@ void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic } } -void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal) +void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate) { struct timeval tv; uint32_t secs = p.time; tv.tv_sec = secs; tv.tv_usec = 0; - perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv); + perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv, forceUpdate); } meshtastic_MeshPacket *PositionModule::allocReply() diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 1161159f7..763b51e5c 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -54,7 +54,7 @@ class PositionModule : public ProtobufModule, private concu private: struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition); meshtastic_MeshPacket *allocAtakPli(); - void trySetRtc(meshtastic_Position p, bool isLocal); + void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false); uint32_t precision; void sendLostAndFoundText(); From 038413f46f397f2cd4ce73bcd56cea352d1fcd3c Mon Sep 17 00:00:00 2001 From: Neil Hao Date: Tue, 28 May 2024 19:30:15 +0800 Subject: [PATCH 0489/1377] User experience improvement - app battery icon (#3979) * 'app_battery_icon' * Undo VS automatic modifications to this file * 'app_battery_icon_2' --- .vscode/extensions.json | 2 +- src/PowerStatus.h | 11 ++++++++++- variants/station-g2/variant.h | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 783791f0b..4fc84fa78 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,4 +6,4 @@ "platformio.platformio-ide", "trunk.io" ], -} \ No newline at end of file +} diff --git a/src/PowerStatus.h b/src/PowerStatus.h index 56d19b758..592a03328 100644 --- a/src/PowerStatus.h +++ b/src/PowerStatus.h @@ -59,9 +59,18 @@ class PowerStatus : public Status int getBatteryVoltageMv() const { return batteryVoltageMv; } /** - * Note: 0% battery means 'unknown/this board doesn't have a battery installed' + * Note: for boards with battery pin or PMU, 0% battery means 'unknown/this board doesn't have a battery installed' */ +#if defined(HAS_PMU) || defined(BATTERY_PIN) uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 0; } +#endif + + /** + * Note: for boards without battery pin and PMU, 101% battery means 'the board is using external power' + */ +#if !defined(HAS_PMU) && !defined(BATTERY_PIN) + uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 101; } +#endif bool matches(const PowerStatus *newStatus) const { diff --git a/variants/station-g2/variant.h b/variants/station-g2/variant.h index f781ceb24..8f0b4b220 100755 --- a/variants/station-g2/variant.h +++ b/variants/station-g2/variant.h @@ -40,6 +40,7 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 #define SX126X_MAX_POWER 19 #endif +/* #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO4_CHANNEL #define ADC_MULTIPLIER 4 @@ -50,3 +51,4 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 #define BAT_NOBATVOLT 4460 #define CELL_TYPE_LION // same curve for liion/lipo #define NUM_CELLS 2 +*/ From af9d825266c7c6d937326f52c97585e8aee2853e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 28 May 2024 19:25:19 -0500 Subject: [PATCH 0490/1377] Send own node-info earlier and move others to the end of want-config flow (#3949) * Send own node-info earlier and move others to the end of want-config flow * Special nonce skips other nodeinfos * Missed it --- src/mesh/PhoneAPI.cpp | 81 ++++++++++++++++++++++++++----------------- src/mesh/PhoneAPI.h | 14 ++++---- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 2a69d6d56..26d0d9525 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -140,16 +140,18 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) * * We assume buf is at least FromRadio_size bytes long. * - * Our sending states progress in the following sequence (the client app ASSUMES THIS SEQUENCE, DO NOT CHANGE IT): - * STATE_SEND_MY_INFO, // send our my info record - * STATE_SEND_CHANNELS - * STATE_SEND_NODEINFO, // states progress in this order as the device sends to the client - STATE_SEND_CONFIG, - STATE_SEND_MODULE_CONFIG, - STATE_SEND_METADATA, - STATE_SEND_COMPLETE_ID, - STATE_SEND_PACKETS // send packets or debug strings + * Our sending states progress in the following sequence (the client apps ASSUME THIS SEQUENCE, DO NOT CHANGE IT): + STATE_SEND_MY_INFO, // send our my info record + STATE_SEND_OWN_NODEINFO, + STATE_SEND_METADATA, + STATE_SEND_CHANNELS + STATE_SEND_CONFIG, + STATE_SEND_MODULE_CONFIG, + STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to the client + STATE_SEND_COMPLETE_ID, + STATE_SEND_PACKETS // send packets or debug strings */ + size_t PhoneAPI::getFromRadio(uint8_t *buf) { if (!available()) { @@ -171,37 +173,32 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // app not to send locations on our behalf. fromRadioScratch.which_payload_variant = meshtastic_FromRadio_my_info_tag; fromRadioScratch.my_info = myNodeInfo; - state = STATE_SEND_METADATA; + state = STATE_SEND_OWN_NODEINFO; service.refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon. break; + case STATE_SEND_OWN_NODEINFO: { + LOG_INFO("getFromRadio=STATE_SEND_OWN_NODEINFO\n"); + auto us = nodeDB->readNextMeshNode(readIndex); + if (us) { + nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; + fromRadioScratch.node_info = nodeInfoForPhone; + // Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS + nodeInfoForPhone.num = 0; + } + state = STATE_SEND_METADATA; + break; + } + case STATE_SEND_METADATA: LOG_INFO("getFromRadio=STATE_SEND_METADATA\n"); fromRadioScratch.which_payload_variant = meshtastic_FromRadio_metadata_tag; fromRadioScratch.metadata = getDeviceMetadata(); - state = STATE_SEND_NODEINFO; + state = STATE_SEND_CHANNELS; break; - case STATE_SEND_NODEINFO: { - LOG_INFO("getFromRadio=STATE_SEND_NODEINFO\n"); - - if (nodeInfoForPhone.num != 0) { - LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", nodeInfoForPhone.num, nodeInfoForPhone.last_heard, - nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; - fromRadioScratch.node_info = nodeInfoForPhone; - // Stay in current state until done sending nodeinfos - nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time - } else { - LOG_INFO("Done sending nodeinfos\n"); - state = STATE_SEND_CHANNELS; - // Go ahead and send that ID right now - return getFromRadio(buf); - } - break; - } - case STATE_SEND_CHANNELS: LOG_INFO("getFromRadio=STATE_SEND_CHANNELS\n"); fromRadioScratch.which_payload_variant = meshtastic_FromRadio_channel_tag; @@ -325,11 +322,30 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) config_state++; // Advance when we have sent all of our ModuleConfig objects if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) { - state = STATE_SEND_COMPLETE_ID; + // Clients sending special nonce don't want to see other nodeinfos + state = config_nonce == SPECIAL_NONCE ? STATE_SEND_COMPLETE_ID : STATE_SEND_OTHER_NODEINFOS; config_state = 0; } break; + case STATE_SEND_OTHER_NODEINFOS: { + LOG_INFO("getFromRadio=STATE_SEND_OTHER_NODEINFOS\n"); + if (nodeInfoForPhone.num != 0) { + LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", nodeInfoForPhone.num, nodeInfoForPhone.last_heard, + nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; + fromRadioScratch.node_info = nodeInfoForPhone; + // Stay in current state until done sending nodeinfos + nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time + } else { + LOG_INFO("Done sending nodeinfos\n"); + state = STATE_SEND_COMPLETE_ID; + // Go ahead and send that ID right now + return getFromRadio(buf); + } + break; + } + case STATE_SEND_COMPLETE_ID: LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; @@ -422,10 +438,11 @@ bool PhoneAPI::available() case STATE_SEND_CONFIG: case STATE_SEND_MODULECONFIG: case STATE_SEND_METADATA: + case STATE_SEND_OWN_NODEINFO: case STATE_SEND_COMPLETE_ID: return true; - case STATE_SEND_NODEINFO: + case STATE_SEND_OTHER_NODEINFOS: if (nodeInfoForPhone.num == 0) { auto nextNode = nodeDB->readNextMeshNode(readIndex); if (nextNode) { diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 450649d7b..49bf0e292 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -6,6 +6,7 @@ // Make sure that we never let our packets grow too large for one BLE packet #define MAX_TO_FROM_RADIO_SIZE 512 +#define SPECIAL_NONCE 69420 /** * Provides our protobuf based API which phone/PC clients can use to talk to our device @@ -20,13 +21,14 @@ class PhoneAPI : public Observer // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member { enum State { - STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config - STATE_SEND_MY_INFO, // send our my info record - STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client - STATE_SEND_CHANNELS, // Send all channels - STATE_SEND_CONFIG, // Replacement for the old Radioconfig - STATE_SEND_MODULECONFIG, // Send Module specific config + STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config + STATE_SEND_MY_INFO, // send our my info record + STATE_SEND_OWN_NODEINFO, STATE_SEND_METADATA, + STATE_SEND_CHANNELS, // Send all channels + STATE_SEND_CONFIG, // Replacement for the old Radioconfig + STATE_SEND_MODULECONFIG, // Send Module specific config + STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings }; From 0b48663cbcfb964ee8803f930e8152482a492d98 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 30 May 2024 08:49:01 -0500 Subject: [PATCH 0491/1377] Don't alloc NodeInfo replies when channel utilization is > 40% (#3991) * Don't alloc NodeInfo replies when channel utilization is > 40% * Commit * Logs --- src/modules/NodeInfoModule.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index f77026708..78af7099a 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -58,10 +58,15 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha meshtastic_MeshPacket *NodeInfoModule::allocReply() { + if (!airTime->isTxAllowedChannelUtil(false)) { + ignoreRequest = true; // Mark it as ignored for MeshModule + LOG_DEBUG("Skip sending NodeInfo due to > 40 percent channel util.\n"); + return NULL; + } uint32_t now = millis(); // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. if (lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) { - LOG_DEBUG("Sending NodeInfo will be ignored since we just sent it.\n"); + LOG_DEBUG("Skip sending NodeInfo since we just sent it less than 5 minutes ago.\n"); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; } else { From cd8a7e44a8cac6dac8c7d8ed0b6f993edc109ad6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 09:08:32 -0500 Subject: [PATCH 0492/1377] [create-pull-request] automated change (#3992) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/atak.pb.h | 15 ++++++++++----- src/mesh/generated/meshtastic/mesh.pb.h | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/protobufs b/protobufs index b5dc871a1..9e61b8233 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit b5dc871a1bfa2cc932126a4f490d9ef078476e4c +Subproject commit 9e61b8233181715363aba2aa6c91e85a2d61fc73 diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index c094727ed..5fd18f963 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -73,6 +73,9 @@ typedef struct _meshtastic_GeoChat { /* Uid recipient of the message */ bool has_to; char to[120]; + /* Callsign of the recipient for the message */ + bool has_to_callsign; + char to_callsign[120]; } meshtastic_GeoChat; /* ATAK Group @@ -164,13 +167,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}} -#define meshtastic_GeoChat_init_default {"", false, ""} +#define meshtastic_GeoChat_init_default {"", false, "", false, ""} #define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_default {0} #define meshtastic_Contact_init_default {"", ""} #define meshtastic_PLI_init_default {0, 0, 0, 0, 0} #define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}} -#define meshtastic_GeoChat_init_zero {"", false, ""} +#define meshtastic_GeoChat_init_zero {"", false, "", false, ""} #define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_zero {0} #define meshtastic_Contact_init_zero {"", ""} @@ -179,6 +182,7 @@ extern "C" { /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_GeoChat_message_tag 1 #define meshtastic_GeoChat_to_tag 2 +#define meshtastic_GeoChat_to_callsign_tag 3 #define meshtastic_Group_role_tag 1 #define meshtastic_Group_team_tag 2 #define meshtastic_Status_battery_tag 1 @@ -214,7 +218,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), #define meshtastic_GeoChat_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, message, 1) \ -X(a, STATIC, OPTIONAL, STRING, to, 2) +X(a, STATIC, OPTIONAL, STRING, to, 2) \ +X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) #define meshtastic_GeoChat_CALLBACK NULL #define meshtastic_GeoChat_DEFAULT NULL @@ -262,11 +267,11 @@ extern const pb_msgdesc_t meshtastic_PLI_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size #define meshtastic_Contact_size 242 -#define meshtastic_GeoChat_size 323 +#define meshtastic_GeoChat_size 444 #define meshtastic_Group_size 4 #define meshtastic_PLI_size 31 #define meshtastic_Status_size 3 -#define meshtastic_TAKPacket_size 584 +#define meshtastic_TAKPacket_size 705 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index ffc18c30b..7b544d714 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -156,6 +156,9 @@ typedef enum _meshtastic_HardwareModel { /* NRF52_PROMICRO_DIY Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS */ meshtastic_HardwareModel_NRF52_PROMICRO_DIY = 63, + /* RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module + ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS */ + meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From f138eaa970339250dfff1b5608ce1463253312f9 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 30 May 2024 18:59:10 +0300 Subject: [PATCH 0493/1377] Fix original esp32 boot init panic (#3985) Co-authored-by: Ben Meadors --- arch/esp32/esp32.ini | 1 + arch/esp32/esp32c3.ini | 1 + arch/esp32/esp32s2.ini | 1 + arch/esp32/esp32s3.ini | 1 + bin/platformio-custom.py | 16 +++++++++++++++- src/platform/esp32/iram-quirk.c | 23 +++++++++++++++++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/platform/esp32/iram-quirk.c diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 7e55f0934..f3eb0cbc0 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -1,6 +1,7 @@ ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] extends = arduino_base +custom_esp32_kind = esp32 platform = platformio/espressif32@6.7.0 build_src_filter = diff --git a/arch/esp32/esp32c3.ini b/arch/esp32/esp32c3.ini index 619fdb28a..2ba3036d0 100644 --- a/arch/esp32/esp32c3.ini +++ b/arch/esp32/esp32c3.ini @@ -1,5 +1,6 @@ [esp32c3_base] extends = esp32_base +custom_esp32_kind = esp32c3 monitor_speed = 115200 monitor_filters = esp32_c3_exception_decoder diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini index df66de2ed..40fdc461a 100644 --- a/arch/esp32/esp32s2.ini +++ b/arch/esp32/esp32s2.ini @@ -1,5 +1,6 @@ [esp32s2_base] extends = esp32_base +custom_esp32_kind = esp32s2 build_src_filter = ${esp32_base.build_src_filter} - - - diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 6a1bdd3fd..1cd0e2033 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -1,5 +1,6 @@ [esp32s3_base] extends = esp32_base +custom_esp32_kind = esp32s3 monitor_speed = 115200 diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 3382ff891..3202a1e7a 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -62,7 +62,21 @@ if platform.name == "espressif32": import esptool env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) - env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) + + esp32_kind = env.GetProjectOption("custom_esp32_kind") + if esp32_kind == "esp32": + # Free up some IRAM by removing auxiliary SPI flash chip drivers. + # Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c. + env.Append( + LINKFLAGS=[ + "-Wl,--wrap=esp_flash_chip_gd", + "-Wl,--wrap=esp_flash_chip_issi", + "-Wl,--wrap=esp_flash_chip_winbond", + ] + ) + else: + # For newer ESP32 targets, using newlib nano works better. + env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) Import("projenv") diff --git a/src/platform/esp32/iram-quirk.c b/src/platform/esp32/iram-quirk.c new file mode 100644 index 000000000..813842138 --- /dev/null +++ b/src/platform/esp32/iram-quirk.c @@ -0,0 +1,23 @@ +// Free up some precious space in the iram0_0_seg memory segment + +#include + +#include +#include +#include + +#define IRAM_SECTION section(".iram1.stub") + +IRAM_ATTR esp_err_t stub_probe(esp_flash_t *chip, uint32_t flash_id) +{ + return ESP_ERR_NOT_FOUND; +} + +const spi_flash_chip_t stub_flash_chip __attribute__((IRAM_SECTION)) = { + .name = "stub", + .probe = stub_probe, +}; + +extern const spi_flash_chip_t __wrap_esp_flash_chip_gd __attribute__((IRAM_SECTION, alias("stub_flash_chip"))); +extern const spi_flash_chip_t __wrap_esp_flash_chip_issi __attribute__((IRAM_SECTION, alias("stub_flash_chip"))); +extern const spi_flash_chip_t __wrap_esp_flash_chip_winbond __attribute__((IRAM_SECTION, alias("stub_flash_chip"))); From 10e3040494bc50abce8c32f96aee2e2a71d3479b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 30 May 2024 18:49:08 -0500 Subject: [PATCH 0494/1377] Add support for to_callsign on GeoChats for ATAK (#3996) --- src/modules/AtakPluginModule.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index b460602ef..59263415c 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -87,6 +87,14 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast compressed.payload_variant.chat.to); LOG_DEBUG("Compressed chat to: %d bytes\n", length); } + + if (t->payload_variant.chat.has_to_callsign) { + compressed.payload_variant.chat.has_to_callsign = true; + length = + unishox2_compress_simple(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), + compressed.payload_variant.chat.to_callsign); + LOG_DEBUG("Compressed chat to_callsign: %d bytes\n", length); + } } mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), meshtastic_TAKPacket_fields, &compressed); @@ -124,6 +132,14 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast uncompressed.payload_variant.chat.to); LOG_DEBUG("Decompressed chat to: %d bytes\n", length); } + + if (t->payload_variant.chat.has_to_callsign) { + uncompressed.payload_variant.chat.has_to_callsign = true; + length = + unishox2_decompress_simple(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), + uncompressed.payload_variant.chat.to_callsign); + LOG_DEBUG("Decompressed chat to_callsign: %d bytes\n", length); + } } decompressedCopy->decoded.payload.size = pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload), From 8d90c496d0b8793354fb3f04ee0f634e8bfa000b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 31 May 2024 07:14:33 -0500 Subject: [PATCH 0495/1377] Don't send potentially bogus timestamps with fixed location (#4001) --- src/modules/PositionModule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f3a70f4c5..002111171 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -211,11 +211,11 @@ meshtastic_MeshPacket *PositionModule::allocReply() // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (getRTCQuality() < RTCQualityDevice) { + if (getRTCQuality() < RTCQualityGPS) { LOG_INFO("Stripping time %u from position send\n", p.time); p.time = 0; } else { - p.time = getValidTime(RTCQualityDevice); + p.time = getValidTime(RTCQualityGPS); LOG_INFO("Providing time to mesh %u\n", p.time); } @@ -454,4 +454,4 @@ void PositionModule::handleNewPosition() } } -#endif \ No newline at end of file +#endif From 54bccb898e4b8a1f4bdcb3670a94869ff5b9c4fb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 31 May 2024 07:15:16 -0500 Subject: [PATCH 0496/1377] Run tzset() and localtime() in getTZOffset() to ensure proper timezone offset (#3999) * Run tzset() and localtime() in getTZOffset() to ensure proper timezone offset * Try #2 to fix timezone/DST --- src/gps/RTC.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 864b246a3..d60e3825c 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -222,9 +222,8 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t) */ int32_t getTZOffset() { - time_t now; + time_t now = getTime(false); struct tm *gmt; - now = time(NULL); gmt = gmtime(&now); gmt->tm_isdst = -1; return (int32_t)difftime(now, mktime(gmt)); @@ -265,4 +264,4 @@ time_t gm_mktime(struct tm *tm) setenv("TZ", "UTC0", 1); } return res; -} +} \ No newline at end of file From c88278724cfaae38e1876880539b6a9b4cb6146d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 07:15:41 -0500 Subject: [PATCH 0497/1377] [create-pull-request] automated change (#4003) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 9e61b8233..a45a6154d 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 9e61b8233181715363aba2aa6c91e85a2d61fc73 +Subproject commit a45a6154d0721027bf63f85cfc5abd9f6fab2422 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 7748dd6b3..f0ff9bed0 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -57,7 +57,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* Lite On LTR-390UV-01 UV Light Sensor */ meshtastic_TelemetrySensorType_LTR390UV = 21, /* AMS TSL25911FN RGB Light Sensor */ - meshtastic_TelemetrySensorType_TSL25911FN = 22 + meshtastic_TelemetrySensorType_TSL25911FN = 22, + /* AHT10 Integrated temperature and humidity sensor */ + meshtastic_TelemetrySensorType_AHT10 = 23 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -168,8 +170,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL25911FN -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL25911FN+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_AHT10 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_AHT10+1)) From b9edc7563b55fb6c9fff8a0ae00e1539f5bf803d Mon Sep 17 00:00:00 2001 From: "Aaron.Lee" <32860565+Heltec-Aaron-Lee@users.noreply.github.com> Date: Fri, 31 May 2024 23:55:05 +0800 Subject: [PATCH 0498/1377] Update the Heltec board battery level read accuracy. (#3955) * Update variant.h Update the Heltec board battery voltage read parameter. * Update variant.h Update the Heltec board battery voltage read parameter. * Update variant.h Update the Heltec board battery voltage read parameter. * Update variant.h Update the Heltec board battery voltage read parameter. --- variants/heltec_v3/variant.h | 2 +- variants/heltec_wireless_tracker/variant.h | 2 +- variants/heltec_wireless_tracker_V1_0/variant.h | 2 +- variants/heltec_wsl_v3/variant.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/heltec_v3/variant.h b/variants/heltec_v3/variant.h index 70b122f58..0c22ea1a7 100644 --- a/variants/heltec_v3/variant.h +++ b/variants/heltec_v3/variant.h @@ -16,7 +16,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9 +#define ADC_MULTIPLIER 4.9*1.045 #define USE_SX1262 diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index 167345e1a..0f632a33c 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -33,7 +33,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9 +#define ADC_MULTIPLIER 4.9*1.045 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 #define ADC_CTRL_ENABLED HIGH diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 84e77a6b9..2b521f249 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -34,7 +34,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9 +#define ADC_MULTIPLIER 4.9*1.045 #undef GPS_RX_PIN #undef GPS_TX_PIN diff --git a/variants/heltec_wsl_v3/variant.h b/variants/heltec_wsl_v3/variant.h index 917ea7fb9..37cef09c6 100644 --- a/variants/heltec_wsl_v3/variant.h +++ b/variants/heltec_wsl_v3/variant.h @@ -11,7 +11,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9 +#define ADC_MULTIPLIER 4.9*1.045 #define USE_SX1262 From 953aa4d0915f8652b14e7734acc1e1f2ac5210fa Mon Sep 17 00:00:00 2001 From: jonagnew Date: Fri, 31 May 2024 11:55:20 -0400 Subject: [PATCH 0499/1377] Tracker v1.1 - fix pin 3 description in variant.h (#3990) Schematic shows the following: Pin 3 is Vext on v1.1 - HIGH enables LDO for Vext rail which goes to: GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR GPS LNA SW7125DE: VCC(4), pulls up SHDN(5) through 10kR OLED: VDD, LEDA (through diode) Co-authored-by: Ben Meadors --- variants/heltec_wireless_tracker/variant.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index 0f632a33c..d496a8d6f 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -27,7 +27,11 @@ #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS -#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost + // pin 3 is Vext on v1.1 - HIGH enables LDO for Vext rail which goes to: + // GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR + // GPS LNA SW7125DE: VCC(4), pulls up SHDN(5) through 10kR + // LED: VDD, LEDA (through diode) +#define VEXT_ENABLE_V05 3 // active HIGH - powers the GPS, GPS LNA and OLED VDD/anode #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage @@ -43,6 +47,7 @@ #define GPS_TX_PIN 34 #define PIN_GPS_RESET 35 #define PIN_GPS_PPS 36 +// #define PIN_GPS_EN 3 // Uncomment to power off the GPS with triple-click on Tracker v1.1, though we'll also lose the display. #define GPS_RESET_MODE LOW #define GPS_UC6580 From 17142f87782ad9bff0bc66e9b4937018adfb3bb1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 31 May 2024 10:56:04 -0500 Subject: [PATCH 0500/1377] Add support for RadioMaster Bandit Nano (#4005) * Add support for RadioMaster Bandit Nano * Add fan to init and sleep --- src/mesh/NodeDB.cpp | 3 + src/mesh/RF95Interface.cpp | 33 ++++++-- src/mesh/RadioLibRF95.cpp | 4 + src/platform/esp32/architecture.h | 2 + .../platformio.ini | 15 ++++ .../radiomaster_900_bandit_nano/variant.h | 84 +++++++++++++++++++ 6 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 variants/radiomaster_900_bandit_nano/platformio.ini create mode 100644 variants/radiomaster_900_bandit_nano/variant.h diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b79911a3e..9473df8c5 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -292,6 +292,9 @@ void NodeDB::installDefaultConfig() meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_DOP | meshtastic_Config_PositionConfig_PositionFlags_SATINVIEW); +#ifdef RADIOMASTER_900_BANDIT_NANO + config.display.flip_screen = true; +#endif #ifdef T_WATCH_S3 config.display.screen_on_secs = 30; config.display.wake_on_tap_or_motion = true; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 8c6c349fd..5677e6eda 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -8,7 +8,10 @@ #include "PortduinoGlue.h" #endif -#define MAX_POWER 20 +#ifndef RF95_MAX_POWER +#define RF95_MAX_POWER 20 +#endif + // if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17 // In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING // if you set power to something higher than 17 or 20 you might fry your board. @@ -49,8 +52,8 @@ bool RF95Interface::init() { RadioLibInterface::init(); - if (power > MAX_POWER) // This chip has lower power limits than some - power = MAX_POWER; + if (power > RF95_MAX_POWER) // This chip has lower power limits than some + power = RF95_MAX_POWER; limitPower(); @@ -61,6 +64,13 @@ bool RF95Interface::init() digitalWrite(RF95_TCXO, 1); #endif + // enable PA +#ifdef RF95_PA_EN +#if defined(RF95_PA_DAC_EN) + dacWrite(RF95_PA_EN, RF95_PA_LEVEL); +#endif +#endif + /* #define RF95_TXEN (22) // If defined, this pin should be set high prior to transmit (controls an external analog switch) #define RF95_RXEN (23) // If defined, this pin should be set high prior to receive (controls an external analog switch) @@ -71,6 +81,11 @@ bool RF95Interface::init() digitalWrite(RF95_TXEN, 0); #endif +#ifdef RF95_FAN_EN + pinMode(RF95_FAN_EN, OUTPUT); + digitalWrite(RF95_FAN_EN, 1); +#endif + #ifdef RF95_RXEN pinMode(RF95_RXEN, OUTPUT); digitalWrite(RF95_RXEN, 1); @@ -146,10 +161,14 @@ bool RF95Interface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - if (power > MAX_POWER) // This chip has lower power limits than some - power = MAX_POWER; + if (power > RF95_MAX_POWER) // This chip has lower power limits than some + power = RF95_MAX_POWER; +#ifdef USE_RF95_RFO + err = lora->setOutputPower(power, true); +#else err = lora->setOutputPower(power); +#endif if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); @@ -235,5 +254,9 @@ bool RF95Interface::sleep() setStandby(); // First cancel any active receiving/sending lora->sleep(); +#ifdef RF95_FAN_EN + digitalWrite(RF95_FAN_EN, 0); +#endif + return true; } \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp index 1fe7869a3..a202d4f4d 100644 --- a/src/mesh/RadioLibRF95.cpp +++ b/src/mesh/RadioLibRF95.cpp @@ -42,7 +42,11 @@ int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_ state = setCodingRate(cr); RADIOLIB_ASSERT(state); +#ifdef USE_RF95_RFO + state = setOutputPower(power, true); +#else state = setOutputPower(power); +#endif RADIOLIB_ASSERT(state); state = setGain(gain); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 45d533a76..824c11bdd 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -145,6 +145,8 @@ #define HW_VENDOR meshtastic_HardwareModel_UNPHONE #elif defined(WIPHONE) #define HW_VENDOR meshtastic_HardwareModel_WIPHONE +#elif defined(RADIOMASTER_900_BANDIT_NANO) +#define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO #endif // ----------------------------------------------------------------------------- diff --git a/variants/radiomaster_900_bandit_nano/platformio.ini b/variants/radiomaster_900_bandit_nano/platformio.ini new file mode 100644 index 000000000..d83c14de2 --- /dev/null +++ b/variants/radiomaster_900_bandit_nano/platformio.ini @@ -0,0 +1,15 @@ +[env:radiomaster_900_bandit_nano] +extends = esp32_base +board = esp32doit-devkit-v1 +board_level = extra +build_flags = + ${esp32_base.build_flags} + -DRADIOMASTER_900_BANDIT_NANO + -DVTABLES_IN_FLASH=1 + -DCONFIG_DISABLE_HAL_LOCKS=1 + -O2 + -Ivariants/radiomaster_900_bandit_nano +board_build.f_cpu = 240000000L +upload_protocol = esptool +lib_deps = + ${esp32_base.lib_deps} \ No newline at end of file diff --git a/variants/radiomaster_900_bandit_nano/variant.h b/variants/radiomaster_900_bandit_nano/variant.h new file mode 100644 index 000000000..bd6687733 --- /dev/null +++ b/variants/radiomaster_900_bandit_nano/variant.h @@ -0,0 +1,84 @@ +/* + Initial settings and work by https://github.com/uberhalit and re-work by https://github.com/gjelsoe + Unit provided by Radio Master RC + https://radiomasterrc.com/products/bandit-nano-expresslrs-rf-module with 0.96" OLED display +*/ + +/* + I2C SDA and SCL. +*/ +#define I2C_SDA 14 +#define I2C_SCL 12 + +/* + No GPS - but free solder pads are available inside the case. +*/ +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +/* + Pin connections from ESP32-D0WDQ6 to SX1276. +*/ +#define LORA_DIO0 22 +#define LORA_DIO1 21 +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 +#define LORA_CS 4 +#define LORA_RESET 5 +#define LORA_TXEN 33 + +/* + This unit has a FAN built-in. + FAN is active at 250mW on it's ExpressLRS Firmware. +*/ +#define RF95_FAN_EN 2 + +/* + LED PIN setup. +*/ +#define LED_PIN 15 + +/* + Five way button when using ADC. + 2.632V, 2.177V, 1.598V, 1.055V, 0V + + Possible ADC Values: + { UP, DOWN, LEFT, RIGHT, ENTER, IDLE } + 3227, 0 ,1961, 2668, 1290, 4095 +*/ +#define BUTTON_PIN 39 +#define BUTTON_NEED_PULLUP + +#define SCREEN_ROTATE + +/* + No External notification. +*/ +#undef EXT_NOTIFY_OUT + +/* + Remapping PIN Names. + Note, that this unit uses RFO +*/ +#define USE_RF95 +#define USE_RF95_RFO +#define RF95_CS LORA_CS +#define RF95_DIO1 LORA_DIO1 +#define RF95_TXEN LORA_TXEN +#define RF95_RESET LORA_RESET +#define RF95_MAX_POWER 12 + +/* + This module has Skyworks SKY66122 controlled by dacWrite + power rangeing from 100mW to 1000mW. + + Mapping of PA_LEVEL to Power output: GPIO26/dacWrite + 168 -> 100mW -> 2.11v + 148 -> 250mW -> 1.87v + 128 -> 500mW -> 1.63v + 90 -> 1000mW -> 1.16v +*/ +#define RF95_PA_EN 26 +#define RF95_PA_DAC_EN +#define RF95_PA_LEVEL 90 \ No newline at end of file From eddda3ca43304357b7f130afe980091176814c05 Mon Sep 17 00:00:00 2001 From: fzellini Date: Fri, 31 May 2024 18:17:53 +0200 Subject: [PATCH 0501/1377] added AHTx0 sensor (#3977) * added AHTx0 sensor * AHT10 definition in protobuf * AHT10 definition in protobuf * protobufs * Management of AHT20+BMP280 module * missing newline in log * missing newline in log * reverted * reverted .gitignore --------- Co-authored-by: Ben Meadors --- platformio.ini | 1 + src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 9 +++-- src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 17 +++++++++ src/modules/Telemetry/Sensor/AHT10.cpp | 35 +++++++++++++++++++ src/modules/Telemetry/Sensor/AHT10.h | 17 +++++++++ 8 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/AHT10.cpp create mode 100644 src/modules/Telemetry/Sensor/AHT10.h diff --git a/platformio.ini b/platformio.ini index c6efc740d..ca41a8021 100644 --- a/platformio.ini +++ b/platformio.ini @@ -130,6 +130,7 @@ lib_deps = adafruit/Adafruit PM25 AQI Sensor@^1.0.6 adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 + adafruit/Adafruit AHTX0@^2.0.5 lewisxhe/SensorLib@^0.2.0 adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 diff --git a/src/configuration.h b/src/configuration.h index 858f3167e..daaf1a720 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -128,6 +128,7 @@ along with this program. If not, see . #define LPS22HB_ADDR_ALT 0x5D #define SHT31_4x_ADDR 0x44 #define PMSA0031_ADDR 0x12 +#define AHT10_ADDR 0x38 #define RCWL9620_ADDR 0x57 #define VEML7700_ADDR 0x10 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 6c01b9100..67e228791 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -45,6 +45,7 @@ class ScanI2C VEML7700, RCWL9620, NCP5623, + AHT10 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 7828dfb58..d46497d09 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -256,7 +256,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port) type = BMP_280; } break; - +#ifndef HAS_NCP5623 + case AHT10_ADDR: + LOG_INFO("AHT10 sensor found at address 0x%x\n", (uint8_t)addr.address); + type = AHT10; + break; +#endif case INA_ADDR: case INA_ADDR_ALTERNATE: case INA_ADDR_WAVESHARE_UPS: @@ -369,4 +374,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4a9fef5d0..f0564ea36 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -534,6 +534,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) i2cScanner.reset(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index a3f63b0aa..6d58460f4 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -18,6 +18,7 @@ #include // Sensors +#include "Sensor/AHT10.h" #include "Sensor/BME280Sensor.h" #include "Sensor/BME680Sensor.h" #include "Sensor/BMP085Sensor.h" @@ -41,6 +42,7 @@ SHT31Sensor sht31Sensor; VEML7700Sensor veml7700Sensor; SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; +AHT10Sensor aht10Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -105,6 +107,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = veml7700Sensor.runOnce(); if (rcwl9620Sensor.hasSensor()) result = rcwl9620Sensor.runOnce(); + if (aht10Sensor.hasSensor()) + result = aht10Sensor.runOnce(); } return result; } else { @@ -291,6 +295,19 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; } + if (aht10Sensor.hasSensor()) { + if (!bmp280Sensor.hasSensor()) { + valid = valid && aht10Sensor.getMetrics(&m); + hasSensor = true; + } else { + // prefer bmp280 temp if both sensors are present, fetch only humidity + meshtastic_Telemetry m_ahtx; + LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n"); + aht10Sensor.getMetrics(&m_ahtx); + m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; + } + } + valid = valid && hasSensor; if (valid) { diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp new file mode 100644 index 000000000..985515bb6 --- /dev/null +++ b/src/modules/Telemetry/Sensor/AHT10.cpp @@ -0,0 +1,35 @@ +#include "AHT10.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" +#include +#include + +AHT10Sensor::AHT10Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_AHT10, "AHT10") {} + +int32_t AHT10Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + aht10 = Adafruit_AHTX0(); + status = aht10.begin(nodeTelemetrySensorsMap[sensorType].second, 0, nodeTelemetrySensorsMap[sensorType].first); + + return initI2CSensor(); +} + +void AHT10Sensor::setup() {} + +bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("AHT10Sensor::getMetrics\n"); + + sensors_event_t humidity, temp; + aht10.getEvent(&humidity, &temp); + + measurement->variant.environment_metrics.temperature = temp.temperature; + measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity; + + return true; +} diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h new file mode 100644 index 000000000..b2b7b47f3 --- /dev/null +++ b/src/modules/Telemetry/Sensor/AHT10.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class AHT10Sensor : public TelemetrySensor +{ + private: + Adafruit_AHTX0 aht10; + + protected: + virtual void setup() override; + + public: + AHT10Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; From 4fa2427b8cf91b0e68c2a724e57066a789bdc5de Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 31 May 2024 11:18:06 -0500 Subject: [PATCH 0502/1377] Trunk variants --- variants/heltec_v3/variant.h | 2 +- variants/heltec_wireless_tracker/variant.h | 13 +++++++------ variants/heltec_wireless_tracker_V1_0/variant.h | 2 +- variants/heltec_wsl_v3/variant.h | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/variants/heltec_v3/variant.h b/variants/heltec_v3/variant.h index 0c22ea1a7..2417b873d 100644 --- a/variants/heltec_v3/variant.h +++ b/variants/heltec_v3/variant.h @@ -16,7 +16,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9*1.045 +#define ADC_MULTIPLIER 4.9 * 1.045 #define USE_SX1262 diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index d496a8d6f..f0ee0631d 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -27,17 +27,17 @@ #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS - // pin 3 is Vext on v1.1 - HIGH enables LDO for Vext rail which goes to: - // GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR - // GPS LNA SW7125DE: VCC(4), pulls up SHDN(5) through 10kR - // LED: VDD, LEDA (through diode) +// pin 3 is Vext on v1.1 - HIGH enables LDO for Vext rail which goes to: +// GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR +// GPS LNA SW7125DE: VCC(4), pulls up SHDN(5) through 10kR +// LED: VDD, LEDA (through diode) #define VEXT_ENABLE_V05 3 // active HIGH - powers the GPS, GPS LNA and OLED VDD/anode #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9*1.045 +#define ADC_MULTIPLIER 4.9 * 1.045 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 #define ADC_CTRL_ENABLED HIGH @@ -47,7 +47,8 @@ #define GPS_TX_PIN 34 #define PIN_GPS_RESET 35 #define PIN_GPS_PPS 36 -// #define PIN_GPS_EN 3 // Uncomment to power off the GPS with triple-click on Tracker v1.1, though we'll also lose the display. +// #define PIN_GPS_EN 3 // Uncomment to power off the GPS with triple-click on Tracker v1.1, though we'll also lose the +// display. #define GPS_RESET_MODE LOW #define GPS_UC6580 diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 2b521f249..1b4751a57 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -34,7 +34,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9*1.045 +#define ADC_MULTIPLIER 4.9 * 1.045 #undef GPS_RX_PIN #undef GPS_TX_PIN diff --git a/variants/heltec_wsl_v3/variant.h b/variants/heltec_wsl_v3/variant.h index 37cef09c6..75cea538d 100644 --- a/variants/heltec_wsl_v3/variant.h +++ b/variants/heltec_wsl_v3/variant.h @@ -11,7 +11,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9*1.045 +#define ADC_MULTIPLIER 4.9 * 1.045 #define USE_SX1262 From ffff2a03fc0a96cf90c12cc59a564d9a34dcc9a0 Mon Sep 17 00:00:00 2001 From: fzellini Date: Fri, 31 May 2024 20:05:40 +0200 Subject: [PATCH 0503/1377] dragino trackerd (#4002) * added AHTx0 sensor * AHT10 definition in protobuf * AHT10 definition in protobuf * protobufs * Management of AHT20+BMP280 module * missing newline in log * missing newline in log * dragino trackerd support * dragino trackerd support * revert back .gitmodules * reverted gitignore * merged telemetry.pb.h * merged telemetry.pb.h * removed extra script, now bin version works * reverted --------- Co-authored-by: Ben Meadors --- variants/trackerd/variant.h | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/variants/trackerd/variant.h b/variants/trackerd/variant.h index bd8017d8c..c4dfb9e93 100644 --- a/variants/trackerd/variant.h +++ b/variants/trackerd/variant.h @@ -20,16 +20,34 @@ #define LORA_DIO1 33 #define LORA_DIO2 32 // Not really used -#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#undef BAT_MEASURE_ADC_UNIT +#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_MULTIPLIER 1.34 // tracked resistance divider is 100k+470k, so it can not fillfull well on esp32 adc +#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_12 // lower dB for high resistance voltage divider -// Battery -// The battery sense is hooked to pin A0 (4) -// it is defined in the anlaolgue pin section of this file -// and has 12 bit resolution -// #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. -#define BATTERY_SENSE_RESOLUTION_BITS 12 -#define BATTERY_SENSE_RESOLUTION 4096.0 -#undef AREF_VOLTAGE -#define AREF_VOLTAGE 3.0 -#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER (2.0F) \ No newline at end of file +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#undef PIN_GPS_PPS + +#define PIN_GPS_EN 12 +#define GPS_EN_ACTIVE 1 + +#define GPS_TX_PIN 10 +#define GPS_RX_PIN 9 + +#define PIN_GPS_RESET 25 +// #define PIN_GPS_REINIT 25 +#define GPS_RESET_MODE 1 + +#define GPS_L76K + +#undef PIN_LED1 +#undef PIN_LED2 +#undef PIN_LED3 + +#define PIN_LED1 13 +#define PIN_LED2 15 +#define PIN_LED3 2 + +#define ledOff(pin) pinMode(pin, INPUT) \ No newline at end of file From 2740a56944d6647e53320a7e235b61ae0207a143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 1 Jun 2024 02:46:42 +0200 Subject: [PATCH 0504/1377] tryfix: init change for BME680 (#3965) --- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/BME680Sensor.h | 4 +- .../Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c | 86 ------------------- .../Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h | 7 -- 4 files changed, 4 insertions(+), 95 deletions(-) delete mode 100644 src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c delete mode 100644 src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index f2c3804f4..71da39043 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -28,7 +28,7 @@ int32_t BME680Sensor::runOnce() if (bme680.status == BSEC_OK) { status = 1; - if (!bme680.setConfig(bsec_config_iaq)) { + if (!bme680.setConfig(bsec_config)) { checkStatus("setConfig"); status = 0; } diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 351db50ab..a5d2b5a48 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -8,7 +8,9 @@ #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis() -#include "bme680_iaq_33v_3s_4d/bsec_iaq.h" +const uint8_t bsec_config[] = { +#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt" +}; class BME680Sensor : public TelemetrySensor { diff --git a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c deleted file mode 100644 index 0b5328306..000000000 --- a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c +++ /dev/null @@ -1,86 +0,0 @@ -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -#include "bsec_iaq.h" - -const uint8_t bsec_config_iaq[1974] = { - 0, 0, 4, 2, 189, 1, 0, 0, 0, 0, 0, 0, 158, 7, 0, 0, 176, 0, 1, 0, 0, 192, 168, 71, 64, - 49, 119, 76, 0, 0, 97, 69, 0, 0, 97, 69, 137, 65, 0, 191, 205, 204, 204, 190, 0, 0, 64, 191, 225, 122, - 148, 190, 10, 0, 3, 0, 0, 0, 96, 64, 23, 183, 209, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 205, 204, 204, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 63, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 63, 82, 73, 157, 188, 95, 41, 203, 61, 118, 224, - 108, 63, 155, 230, 125, 63, 191, 14, 124, 63, 0, 0, 160, 65, 0, 0, 32, 66, 0, 0, 160, 65, 0, 0, 32, - 66, 0, 0, 32, 66, 0, 0, 160, 65, 0, 0, 32, 66, 0, 0, 160, 65, 8, 0, 2, 0, 236, 81, 133, 66, - 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, 215, 35, 59, 13, 0, 5, 0, 0, 0, 0, 0, 100, - 35, 41, 29, 86, 88, 0, 9, 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, 156, 62, 225, - 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62, - 160, 191, 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, 64, 65, 241, 99, 190, 0, 0, 0, - 0, 0, 0, 0, 0, 167, 121, 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 13, 5, 11, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, - 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 10, 10, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, - 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 88, 1, 254, - 0, 2, 1, 5, 48, 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, 144, 1, 64, 1, 64, - 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, - 100, 0, 48, 117, 48, 117, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 100, 0, 100, 0, 100, 0, 100, 0, 48, - 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, - 1, 44, 1, 44, 1, 44, 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 112, 23, 112, 23, 112, 23, 112, 23, - 8, 7, 8, 7, 8, 7, 8, 7, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, 255, 255, - 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 5, 10, 5, - 0, 2, 0, 10, 0, 30, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 64, 1, 100, 0, 100, 0, - 100, 0, 200, 0, 200, 0, 200, 0, 64, 1, 64, 1, 64, 1, 10, 0, 0, 0, 0, 0, 21, 122, 0, 0}; - -#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h deleted file mode 100644 index d693f1e6a..000000000 --- a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h +++ /dev/null @@ -1,7 +0,0 @@ -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -#include - -extern const uint8_t bsec_config_iaq[1974]; - -#endif \ No newline at end of file From 9a855c0b6f5647144a38f1bbc13770d07c4e3fff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:46:01 -0500 Subject: [PATCH 0505/1377] [create-pull-request] automated change (#4011) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index aa4d2c207..700044e6f 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 11 +build = 12 From 6ce2fdc1c886a519442749a1db1c2fe0c469b2cb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 1 Jun 2024 20:21:39 -0500 Subject: [PATCH 0506/1377] Add TSL2591 sensor support (#4014) --- platformio.ini | 3 +- src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 1 + src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 10 ++++- .../Telemetry/Sensor/TSL2591Sensor.cpp | 38 +++++++++++++++++++ src/modules/Telemetry/Sensor/TSL2591Sensor.h | 17 +++++++++ 8 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/TSL2591Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/TSL2591Sensor.h diff --git a/platformio.ini b/platformio.ini index ca41a8021..f4306c2ea 100644 --- a/platformio.ini +++ b/platformio.ini @@ -135,4 +135,5 @@ lib_deps = adafruit/Adafruit LSM6DS@^4.7.2 mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 - adafruit/Adafruit SHT4x Library@^1.0.4 \ No newline at end of file + adafruit/Adafruit SHT4x Library@^1.0.4 + adafruit/Adafruit TSL2591 Library@^1.4.5 \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index daaf1a720..2d0095e22 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -131,6 +131,7 @@ along with this program. If not, see . #define AHT10_ADDR 0x38 #define RCWL9620_ADDR 0x57 #define VEML7700_ADDR 0x10 +#define TSL25911_ADDR 0x29 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 67e228791..a2ccb18a8 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -45,7 +45,8 @@ class ScanI2C VEML7700, RCWL9620, NCP5623, - AHT10 + TSL2591, + AHT10, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index d46497d09..d2a6e7848 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -342,6 +342,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n"); + SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/main.cpp b/src/main.cpp index f0564ea36..d9b4d84ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -533,6 +533,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 6d58460f4..5ecfe7328 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -29,6 +29,7 @@ #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" +#include "Sensor/TSL2591Sensor.h" #include "Sensor/VEML7700Sensor.h" BMP085Sensor bmp085Sensor; @@ -40,6 +41,7 @@ SHTC3Sensor shtc3Sensor; LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; VEML7700Sensor veml7700Sensor; +TSL2591Sensor tsl2591Sensor; SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; AHT10Sensor aht10Sensor; @@ -65,7 +67,7 @@ int32_t EnvironmentTelemetryModule::runOnce() */ // moduleConfig.telemetry.environment_measurement_enabled = 1; - // moduleConfig.telemetry.environment_screen_enabled = 1; + // moduleConfig.telemetry.environment_screen_enabled = 1; // moduleConfig.telemetry.environment_update_interval = 45; if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) { @@ -105,6 +107,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina260Sensor.runOnce(); if (veml7700Sensor.hasSensor()) result = veml7700Sensor.runOnce(); + if (tsl2591Sensor.hasSensor()) + result = tsl2591Sensor.runOnce(); if (rcwl9620Sensor.hasSensor()) result = rcwl9620Sensor.runOnce(); if (aht10Sensor.hasSensor()) @@ -291,6 +295,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = valid && veml7700Sensor.getMetrics(&m); hasSensor = true; } + if (tsl2591Sensor.hasSensor()) { + valid = valid && tsl2591Sensor.getMetrics(&m); + hasSensor = true; + } if (rcwl9620Sensor.hasSensor()) { valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp new file mode 100644 index 000000000..0a3f5d685 --- /dev/null +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp @@ -0,0 +1,38 @@ +#include "TSL2591Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" +#include +#include + +TSL2591Sensor::TSL2591Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL25911FN, "TSL2591") {} + +int32_t TSL2591Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second); + + return initI2CSensor(); +} + +void TSL2591Sensor::setup() +{ + tsl.setGain(TSL2591_GAIN_MED); // 25x gain + tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS); +} + +bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + uint32_t lum = tsl.getFullLuminosity(); + uint16_t ir, full; + ir = lum >> 16; + full = lum & 0xFFFF; + + measurement->variant.environment_metrics.lux = tsl.calculateLux(full, ir); + LOG_INFO("Lux: %f\n", measurement->variant.environment_metrics.lux); + + return true; +} \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.h b/src/modules/Telemetry/Sensor/TSL2591Sensor.h new file mode 100644 index 000000000..a24d53975 --- /dev/null +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class TSL2591Sensor : public TelemetrySensor +{ + private: + Adafruit_TSL2591 tsl; + + protected: + virtual void setup() override; + + public: + TSL2591Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file From 2723ae6e9be95b7e924e940efe04cac393af6a44 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 2 Jun 2024 14:38:20 +0200 Subject: [PATCH 0507/1377] fix crash during reset nodedb (#4017) --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 9473df8c5..cf576e94f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -444,9 +444,9 @@ void NodeDB::installDefaultChannels() void NodeDB::resetNodes() { + clearLocalPosition(); numMeshNodes = 1; std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite()); - clearLocalPosition(); saveDeviceStateToDisk(); if (neighborInfoModule && moduleConfig.neighbor_info.enabled) neighborInfoModule->resetNeighbors(); From 97a5abbc82c8b55edd396202bc7977a2868f5380 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 2 Jun 2024 07:39:08 -0500 Subject: [PATCH 0508/1377] TI OPT3001 light sensor support (#4015) * TI OPT3001 light sensor support * Added register interrogation to deconflict with SHT sensors on same address --- platformio.ini | 3 +- src/configuration.h | 2 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 4 ++ src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 8 ++++ .../Telemetry/Sensor/OPT3001Sensor.cpp | 44 +++++++++++++++++++ src/modules/Telemetry/Sensor/OPT3001Sensor.h | 17 +++++++ 8 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/modules/Telemetry/Sensor/OPT3001Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/OPT3001Sensor.h diff --git a/platformio.ini b/platformio.ini index f4306c2ea..791c31a9b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -136,4 +136,5 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 adafruit/Adafruit SHT4x Library@^1.0.4 - adafruit/Adafruit TSL2591 Library@^1.4.5 \ No newline at end of file + adafruit/Adafruit TSL2591 Library@^1.4.5 + ClosedCube OPT3001@^1.1.2 \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 2d0095e22..b102746b3 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -132,6 +132,8 @@ along with this program. If not, see . #define RCWL9620_ADDR 0x57 #define VEML7700_ADDR 0x10 #define TSL25911_ADDR 0x29 +#define OPT3001_ADDRESS 0x45 +#define OPT3001_ADDRESS_ALT 0x44 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index a2ccb18a8..917335a14 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -46,6 +46,7 @@ class ScanI2C RCWL9620, NCP5623, TSL2591, + OPT3001, AHT10, } DeviceType; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index d2a6e7848..4597a70ca 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -302,6 +302,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port) if (registerValue == 0x11a2) { type = SHT4X; LOG_INFO("SHT4X sensor found\n"); + } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { + type = OPT3001; + LOG_INFO("OPT3001 light sensor found\n"); } else { type = SHT31; LOG_INFO("SHT31 sensor found\n"); @@ -343,6 +346,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n"); SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n"); + SCAN_SIMPLE_CASE(OPT3001_ADDRESS, OPT3001, "OPT3001 light sensor found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/main.cpp b/src/main.cpp index d9b4d84ab..8d870feba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -534,6 +534,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 5ecfe7328..1d45d3ee9 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -25,6 +25,7 @@ #include "Sensor/BMP280Sensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" +#include "Sensor/OPT3001Sensor.h" #include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" @@ -42,6 +43,7 @@ LPS22HBSensor lps22hbSensor; SHT31Sensor sht31Sensor; VEML7700Sensor veml7700Sensor; TSL2591Sensor tsl2591Sensor; +OPT3001Sensor opt3001Sensor; SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; AHT10Sensor aht10Sensor; @@ -109,6 +111,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = veml7700Sensor.runOnce(); if (tsl2591Sensor.hasSensor()) result = tsl2591Sensor.runOnce(); + if (opt3001Sensor.hasSensor()) + result = opt3001Sensor.runOnce(); if (rcwl9620Sensor.hasSensor()) result = rcwl9620Sensor.runOnce(); if (aht10Sensor.hasSensor()) @@ -299,6 +303,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = valid && tsl2591Sensor.getMetrics(&m); hasSensor = true; } + if (opt3001Sensor.hasSensor()) { + valid = valid && opt3001Sensor.getMetrics(&m); + hasSensor = true; + } if (rcwl9620Sensor.hasSensor()) { valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp new file mode 100644 index 000000000..0d76e2897 --- /dev/null +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp @@ -0,0 +1,44 @@ +#include "OPT3001Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" +#include + +OPT3001Sensor::OPT3001Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_OPT3001, "OPT3001") {} + +int32_t OPT3001Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + auto errorCode = opt3001.begin(nodeTelemetrySensorsMap[sensorType].first); + status = errorCode == NO_ERROR; + + return initI2CSensor(); +} + +void OPT3001Sensor::setup() +{ + OPT3001_Config newConfig; + + newConfig.RangeNumber = B1100; + newConfig.ConvertionTime = B0; + newConfig.Latch = B1; + newConfig.ModeOfConversionOperation = B11; + + OPT3001_ErrorCode errorConfig = opt3001.writeConfig(newConfig); + if (errorConfig != NO_ERROR) { + LOG_ERROR("OPT3001 configuration error #%d", errorConfig); + } +} + +bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + OPT3001 result = opt3001.readResult(); + + measurement->variant.environment_metrics.lux = result.lux; + LOG_INFO("Lux: %f\n", measurement->variant.environment_metrics.lux); + + return true; +} \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.h b/src/modules/Telemetry/Sensor/OPT3001Sensor.h new file mode 100644 index 000000000..4a8deef21 --- /dev/null +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class OPT3001Sensor : public TelemetrySensor +{ + private: + ClosedCube_OPT3001 opt3001; + + protected: + virtual void setup() override; + + public: + OPT3001Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file From 06c635eca0b489d90c82286eea679a0e190ead16 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 2 Jun 2024 09:38:28 -0500 Subject: [PATCH 0509/1377] MLX90632 IR temperature sensor support (#4019) --- platformio.ini | 3 +- src/configuration.h | 5 ++- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 3 +- src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 8 ++++ .../Telemetry/Sensor/MLX90632Sensor.cpp | 40 +++++++++++++++++++ src/modules/Telemetry/Sensor/MLX90632Sensor.h | 23 +++++++++++ 8 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/MLX90632Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/MLX90632Sensor.h diff --git a/platformio.ini b/platformio.ini index 791c31a9b..968a37d9b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -137,4 +137,5 @@ lib_deps = adafruit/Adafruit VEML7700 Library@^2.1.6 adafruit/Adafruit SHT4x Library@^1.0.4 adafruit/Adafruit TSL2591 Library@^1.4.5 - ClosedCube OPT3001@^1.1.2 \ No newline at end of file + ClosedCube OPT3001@^1.1.2 + emotibit/EmotiBit MLX90632@^1.0.8 \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index b102746b3..744406f18 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -132,8 +132,9 @@ along with this program. If not, see . #define RCWL9620_ADDR 0x57 #define VEML7700_ADDR 0x10 #define TSL25911_ADDR 0x29 -#define OPT3001_ADDRESS 0x45 -#define OPT3001_ADDRESS_ALT 0x44 +#define OPT3001_ADDR 0x45 +#define OPT3001_ADDR_ALT 0x44 +#define MLX90632_ADDR 0x3A // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 917335a14..a90d9218a 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -47,6 +47,7 @@ class ScanI2C NCP5623, TSL2591, OPT3001, + MLX90632, AHT10, } DeviceType; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 4597a70ca..3aeb8560e 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -346,7 +346,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n"); SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n"); - SCAN_SIMPLE_CASE(OPT3001_ADDRESS, OPT3001, "OPT3001 light sensor found\n"); + SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found\n"); + SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/main.cpp b/src/main.cpp index 8d870feba..3c1893690 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -535,6 +535,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 1d45d3ee9..9032389c5 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -25,6 +25,7 @@ #include "Sensor/BMP280Sensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" +#include "Sensor/MLX90632Sensor.h" #include "Sensor/OPT3001Sensor.h" #include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" @@ -47,6 +48,7 @@ OPT3001Sensor opt3001Sensor; SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; AHT10Sensor aht10Sensor; +MLX90632Sensor mlx90632Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -117,6 +119,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = rcwl9620Sensor.runOnce(); if (aht10Sensor.hasSensor()) result = aht10Sensor.runOnce(); + if (mlx90632Sensor.hasSensor()) + result = mlx90632Sensor.runOnce(); } return result; } else { @@ -307,6 +311,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = valid && opt3001Sensor.getMetrics(&m); hasSensor = true; } + if (mlx90632Sensor.hasSensor()) { + valid = valid && mlx90632Sensor.getMetrics(&m); + hasSensor = true; + } if (rcwl9620Sensor.hasSensor()) { valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp new file mode 100644 index 000000000..4c459c365 --- /dev/null +++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp @@ -0,0 +1,40 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "MLX90632Sensor.h" +#include "TelemetrySensor.h" + +MLX90632Sensor::MLX90632Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MLX90632, "MLX90632") {} + +int32_t MLX90632Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + MLX90632::status returnError; + if (mlx.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second, returnError) == + true) // MLX90632 init + { + LOG_DEBUG("MLX90632 Init Succeed\n"); + status = true; + } else { + LOG_ERROR("MLX90632 Init Failed\n"); + status = false; + } + return initI2CSensor(); +} + +void MLX90632Sensor::setup() {} + +bool MLX90632Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.temperature = mlx.getObjectTemp(); // Get the object temperature in Fahrenheit + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.h b/src/modules/Telemetry/Sensor/MLX90632Sensor.h new file mode 100644 index 000000000..7b36c44cd --- /dev/null +++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.h @@ -0,0 +1,23 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class MLX90632Sensor : public TelemetrySensor +{ + private: + MLX90632 mlx = MLX90632(); + + protected: + virtual void setup() override; + + public: + MLX90632Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file From b551c8b59222ef7010ab1aad454a77d0767456fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 3 Jun 2024 14:00:58 +0200 Subject: [PATCH 0510/1377] update Radiolib to 6.6.0 --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 968a37d9b..e8eff024d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -74,7 +74,7 @@ build_flags = -Wno-missing-field-initializers monitor_speed = 115200 lib_deps = - jgromes/RadioLib@~6.5.0 + jgromes/RadioLib@~6.6.0 https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 @@ -138,4 +138,4 @@ lib_deps = adafruit/Adafruit SHT4x Library@^1.0.4 adafruit/Adafruit TSL2591 Library@^1.4.5 ClosedCube OPT3001@^1.1.2 - emotibit/EmotiBit MLX90632@^1.0.8 \ No newline at end of file + emotibit/EmotiBit MLX90632@^1.0.8 From 79333c85a3372243a89f41266de7d7123894afd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 3 Jun 2024 16:34:19 +0200 Subject: [PATCH 0511/1377] tryfix bme some more --- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 71da39043..411cbbf69 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -57,7 +57,7 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.temperature = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE).signal; measurement->variant.environment_metrics.relative_humidity = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY).signal; - measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal / 100.0F; + measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal; measurement->variant.environment_metrics.gas_resistance = bme680.getData(BSEC_OUTPUT_RAW_GAS).signal / 1000.0; // Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms) measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal; From 7cbfe7aa541014cf882aa71ebe34c2fb7a78518a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:59:39 -0500 Subject: [PATCH 0512/1377] [create-pull-request] automated change (#4029) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 33 +++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index a45a6154d..bfbf4a65e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a45a6154d0721027bf63f85cfc5abd9f6fab2422 +Subproject commit bfbf4a65e220581f45c5ed949c659953ac4d080f diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index f0ff9bed0..02b0bdd6d 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -59,7 +59,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* AMS TSL25911FN RGB Light Sensor */ meshtastic_TelemetrySensorType_TSL25911FN = 22, /* AHT10 Integrated temperature and humidity sensor */ - meshtastic_TelemetrySensorType_AHT10 = 23 + meshtastic_TelemetrySensorType_AHT10 = 23, + /* DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) */ + meshtastic_TelemetrySensorType_DFROBOT_LARK = 24 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -100,6 +102,15 @@ typedef struct _meshtastic_EnvironmentMetrics { float lux; /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ float white_lux; + /* Infrared lux */ + float ir_lux; + /* Ultraviolet lux */ + float uv_lux; + /* Wind direction in degrees + 0 degrees = North, 90 = East, etc... */ + uint16_t wind_direction; + /* Wind speed in m/s */ + float wind_speed; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -170,8 +181,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_AHT10 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_AHT10+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DFROBOT_LARK +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DFROBOT_LARK+1)) @@ -181,12 +192,12 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -207,6 +218,10 @@ extern "C" { #define meshtastic_EnvironmentMetrics_distance_tag 8 #define meshtastic_EnvironmentMetrics_lux_tag 9 #define meshtastic_EnvironmentMetrics_white_lux_tag 10 +#define meshtastic_EnvironmentMetrics_ir_lux_tag 11 +#define meshtastic_EnvironmentMetrics_uv_lux_tag 12 +#define meshtastic_EnvironmentMetrics_wind_direction_tag 13 +#define meshtastic_EnvironmentMetrics_wind_speed_tag 14 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -251,7 +266,11 @@ X(a, STATIC, SINGULAR, FLOAT, current, 6) \ X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ -X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) +X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) \ +X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ +X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ +X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ +X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -311,7 +330,7 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 49 +#define meshtastic_EnvironmentMetrics_size 68 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From b43c7c0f23690a966c899bb91463861db9b54365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 3 Jun 2024 23:04:40 +0200 Subject: [PATCH 0513/1377] LR1110 support (#3013) * DOES NOT WORK * trunk * DOES NOT WORK * trunk * DOES NOT WORK * trunk * WIP: LR11x0 non functional interface code. Please don't expect a working firmware out of this! I don't know what i am doing! :-) * trunk fmt * use canon toolchain * update and fix radiolib dependency * Switch Radiolib back to GIT checkout * enable tcxo and fix startReceive * progress * Correct midjudgement on scope of build defines. * - enable peripheral power rail during startup init - fix portduino builds * add tracker pinout variant * update to radiolib 6.6.0 API (aka: godmode is not for mere mortals) * tracker is not so 'extra' any more --------- Co-authored-by: Ben Meadors --- boards/wio-sdk-wm1110.json | 58 ++++ boards/wio-tracker-wm1110.json | 58 ++++ src/detect/LoRaRadioType.h | 13 +- src/main.cpp | 28 ++ src/mesh/InterfacesTemplates.cpp | 4 + src/mesh/LR1110Interface.cpp | 9 + src/mesh/LR1110Interface.h | 13 + src/mesh/LR1120Interface.cpp | 9 + src/mesh/LR1120Interface.h | 13 + src/mesh/LR11x0Interface.cpp | 299 +++++++++++++++++++++ src/mesh/LR11x0Interface.h | 71 +++++ src/platform/nrf52/architecture.h | 2 + variants/wio-sdk-wm1110/platformio.ini | 15 ++ variants/wio-sdk-wm1110/variant.cpp | 45 ++++ variants/wio-sdk-wm1110/variant.h | 111 ++++++++ variants/wio-tracker-wm1110/platformio.ini | 14 + variants/wio-tracker-wm1110/variant.cpp | 45 ++++ variants/wio-tracker-wm1110/variant.h | 111 ++++++++ 18 files changed, 917 insertions(+), 1 deletion(-) create mode 100644 boards/wio-sdk-wm1110.json create mode 100644 boards/wio-tracker-wm1110.json create mode 100644 src/mesh/LR1110Interface.cpp create mode 100644 src/mesh/LR1110Interface.h create mode 100644 src/mesh/LR1120Interface.cpp create mode 100644 src/mesh/LR1120Interface.h create mode 100644 src/mesh/LR11x0Interface.cpp create mode 100644 src/mesh/LR11x0Interface.h create mode 100644 variants/wio-sdk-wm1110/platformio.ini create mode 100644 variants/wio-sdk-wm1110/variant.cpp create mode 100644 variants/wio-sdk-wm1110/variant.h create mode 100644 variants/wio-tracker-wm1110/platformio.ini create mode 100644 variants/wio-tracker-wm1110/variant.cpp create mode 100644 variants/wio-tracker-wm1110/variant.h diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json new file mode 100644 index 000000000..029c9c085 --- /dev/null +++ b/boards/wio-sdk-wm1110.json @@ -0,0 +1,58 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "WIO-BOOT", + "mcu": "nrf52840", + "variant": "Seeed_WIO_WM1110", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Seeed WIO WM1110", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html", + "vendor": "Seeed Studio" +} diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json new file mode 100644 index 000000000..029c9c085 --- /dev/null +++ b/boards/wio-tracker-wm1110.json @@ -0,0 +1,58 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "WIO-BOOT", + "mcu": "nrf52840", + "variant": "Seeed_WIO_WM1110", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Seeed WIO WM1110", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html", + "vendor": "Seeed Studio" +} diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h index eadd92e64..3975153b5 100644 --- a/src/detect/LoRaRadioType.h +++ b/src/detect/LoRaRadioType.h @@ -1,5 +1,16 @@ #pragma once -enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO }; +enum LoRaRadioType { + NO_RADIO, + STM32WLx_RADIO, + SIM_RADIO, + RF95_RADIO, + SX1262_RADIO, + SX1268_RADIO, + LLCC68_RADIO, + SX1280_RADIO, + LR1110_RADIO, + LR1120_RADIO +}; extern LoRaRadioType radioType; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3c1893690..52de93e83 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -65,6 +65,8 @@ NRF52Bluetooth *nrf52Bluetooth; #endif #include "LLCC68Interface.h" +#include "LR1110Interface.h" +#include "LR1120Interface.h" #include "RF95Interface.h" #include "SX1262Interface.h" #include "SX1268Interface.h" @@ -882,6 +884,32 @@ void setup() } #endif +#if defined(USE_LR1110) + if (!rIf) { + rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESER_PIN, LR1110_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("Failed to find LR1110 radio\n"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio\n"); + } + } +#endif + +#if defined(USE_LR1120) + if (!rIf) { + rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESER_PIN, LR1120_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("Failed to find LR1120 radio\n"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio\n"); + } + } +#endif + #if defined(USE_SX1280) if (!rIf) { rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index c732829e9..f2cac8028 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -1,3 +1,5 @@ +#include "LR11x0Interface.cpp" +#include "LR11x0Interface.h" #include "SX126xInterface.cpp" #include "SX126xInterface.h" #include "SX128xInterface.cpp" @@ -10,6 +12,8 @@ template class SX126xInterface; template class SX126xInterface; template class SX126xInterface; template class SX128xInterface; +template class LR11x0Interface; +template class LR11x0Interface; #ifdef ARCH_STM32WL template class SX126xInterface; #endif diff --git a/src/mesh/LR1110Interface.cpp b/src/mesh/LR1110Interface.cpp new file mode 100644 index 000000000..c000bd838 --- /dev/null +++ b/src/mesh/LR1110Interface.cpp @@ -0,0 +1,9 @@ +#include "LR1110Interface.h" +#include "configuration.h" +#include "error.h" + +LR1110Interface::LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) + : LR11x0Interface(hal, cs, irq, rst, busy) +{ +} \ No newline at end of file diff --git a/src/mesh/LR1110Interface.h b/src/mesh/LR1110Interface.h new file mode 100644 index 000000000..79e7c36ca --- /dev/null +++ b/src/mesh/LR1110Interface.h @@ -0,0 +1,13 @@ +#pragma once + +#include "LR11x0Interface.h" + +/** + * Our adapter for LR1110 radios + */ +class LR1110Interface : public LR11x0Interface +{ + public: + LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy); +}; \ No newline at end of file diff --git a/src/mesh/LR1120Interface.cpp b/src/mesh/LR1120Interface.cpp new file mode 100644 index 000000000..94f3568f7 --- /dev/null +++ b/src/mesh/LR1120Interface.cpp @@ -0,0 +1,9 @@ +#include "LR1120Interface.h" +#include "configuration.h" +#include "error.h" + +LR1120Interface::LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) + : LR11x0Interface(hal, cs, irq, rst, busy) +{ +} \ No newline at end of file diff --git a/src/mesh/LR1120Interface.h b/src/mesh/LR1120Interface.h new file mode 100644 index 000000000..fc59293ec --- /dev/null +++ b/src/mesh/LR1120Interface.h @@ -0,0 +1,13 @@ +#pragma once + +#include "LR11x0Interface.h" + +/** + * Our adapter for LR1120 wideband radios + */ +class LR1120Interface : public LR11x0Interface +{ + public: + LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy); +}; \ No newline at end of file diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp new file mode 100644 index 000000000..bffca0c44 --- /dev/null +++ b/src/mesh/LR11x0Interface.cpp @@ -0,0 +1,299 @@ +#include "LR11x0Interface.h" +#include "configuration.h" +#include "error.h" +#include "mesh/NodeDB.h" +#ifdef ARCH_PORTDUINO +#include "PortduinoGlue.h" +#endif + +// Particular boards might define a different max power based on what their hardware can do, default to max power output if not +// specified (may be dangerous if using external PA and LR11x0 power config forgotten) +#ifndef LR11X0_MAX_POWER +#define LR11X0_MAX_POWER 22 +#endif + +template +LR11x0Interface::LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) + : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module) +{ + LOG_WARN("LR11x0Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); +} + +/// Initialise the Driver transport hardware and software. +/// Make sure the Driver is properly configured before calling init(). +/// \return true if initialisation succeeded. +template bool LR11x0Interface::init() +{ +#ifdef LR11X0_POWER_EN + pinMode(LR11X0_POWER_EN, OUTPUT); + digitalWrite(LR11X0_POWER_EN, HIGH); +#endif + +// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE +#if !defined(LR11X0_DIO3_TCXO_VOLTAGE) + float tcxoVoltage = + 0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per + // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104 + // (DIO3 is free to be used as an IRQ) + LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n"); +#else + float tcxoVoltage = LR11X0_DIO3_TCXO_VOLTAGE; + LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", LR11X0_DIO3_TCXO_VOLTAGE); + // (DIO3 is not free to be used as an IRQ) +#endif + + RadioLibInterface::init(); + + if (power > LR11X0_MAX_POWER) // Clamp power to maximum defined level + power = LR11X0_MAX_POWER; + + limitPower(); + + // set RF switch configuration for Wio WM1110 + // Wio WM1110 uses DIO5 and DIO6 for RF switching + // NOTE: other boards may be different. If you are + // using a different board, you may need to wrap + // this in a conditional. + + static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, + RADIOLIB_NC}; + + static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, + }; + +// We need to do this before begin() call +#ifdef LR11X0_DIO_AS_RF_SWITCH + LOG_DEBUG("Setting DIO RF switch\n"); + bool dioAsRfSwitch = true; +#elif defined(ARCH_PORTDUINO) + bool dioAsRfSwitch = false; + if (settingsMap[dio2_as_rf_switch]) { + LOG_DEBUG("Setting DIO RF switch\n"); + dioAsRfSwitch = true; + } +#else + bool dioAsRfSwitch = false; +#endif + + if (dioAsRfSwitch) + lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); + + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + // \todo Display actual typename of the adapter, not just `LR11x0` + LOG_INFO("LR11x0 init result %d\n", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND) + return false; + + LOG_INFO("Frequency set to %f\n", getFreq()); + LOG_INFO("Bandwidth set to %f\n", bw); + LOG_INFO("Power output set to %d\n", power); + + if (res == RADIOLIB_ERR_NONE) + res = lora.setCRC(2); + + // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option + if (res == RADIOLIB_ERR_NONE) + res = lora.setRegulatorDCDC(); + + if (res == RADIOLIB_ERR_NONE) { + if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate + res = lora.setRxBoostedGainMode(true); + LOG_INFO("Set RX gain to boosted mode; result: %d\n", res); + } else { + res = lora.setRxBoostedGainMode(false); + LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d\n", res); + } + } + + if (res == RADIOLIB_ERR_NONE) + startReceive(); // start receiving + + return res == RADIOLIB_ERR_NONE; +} + +template bool LR11x0Interface::reconfigure() +{ + RadioLibInterface::reconfigure(); + + // set mode to standby + setStandby(); + + // configure publicly accessible settings + int err = lora.setSpreadingFactor(sf); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + err = lora.setBandwidth(bw); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + err = lora.setCodingRate(cr); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now... + // TODO: Confirm gain registers are okay now + // err = lora.setRxGain(true); + // assert(err == RADIOLIB_ERR_NONE); + + err = lora.setSyncWord(syncWord); + assert(err == RADIOLIB_ERR_NONE); + + err = lora.setPreambleLength(preambleLength); + assert(err == RADIOLIB_ERR_NONE); + + err = lora.setFrequency(getFreq()); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + if (power > LR11X0_MAX_POWER) // This chip has lower power limits than some + power = LR11X0_MAX_POWER; + + err = lora.setOutputPower(power); + assert(err == RADIOLIB_ERR_NONE); + + startReceive(); // restart receiving + + return RADIOLIB_ERR_NONE; +} + +template void INTERRUPT_ATTR LR11x0Interface::disableInterrupt() +{ + lora.clearIrqAction(); +} + +template void LR11x0Interface::setStandby() +{ + checkNotification(); // handle any pending interrupts before we force standby + + int err = lora.standby(); + + if (err != RADIOLIB_ERR_NONE) { + LOG_DEBUG("LR11x0 standby failed with error %d\n", err); + } + + assert(err == RADIOLIB_ERR_NONE); + + isReceiving = false; // If we were receiving, not any more + activeReceiveStart = 0; + disableInterrupt(); + completeSending(); // If we were sending, not anymore +} + +/** + * Add SNR data to received messages + */ +template void LR11x0Interface::addReceiveMetadata(meshtastic_MeshPacket *mp) +{ + // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus()); + mp->rx_snr = lora.getSNR(); + mp->rx_rssi = lround(lora.getRSSI()); +} + +/** We override to turn on transmitter power as needed. + */ +template void LR11x0Interface::configHardwareForSend() +{ + RadioLibInterface::configHardwareForSend(); +} + +// For power draw measurements, helpful to force radio to stay sleeping +// #define SLEEP_ONLY + +template void LR11x0Interface::startReceive() +{ +#ifdef SLEEP_ONLY + sleep(); +#else + + setStandby(); + + lora.setPreambleLength(preambleLength); // Solve RX ack fail after direct message sent. Not sure why this is needed. + + // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. + // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving + int err = lora.startReceive( + RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_LR11X0_IRQ_RX_DONE, + 0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving + assert(err == RADIOLIB_ERR_NONE); + + isReceiving = true; + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrRxLevel0); +#endif +} + +/** Is the channel currently active? */ +template bool LR11x0Interface::isChannelActive() +{ + // check if we can detect a LoRa preamble on the current channel + int16_t result; + + setStandby(); + result = lora.scanChannel(); + if (result == RADIOLIB_LORA_DETECTED) + return true; + + assert(result != RADIOLIB_ERR_WRONG_MODEM); + + return false; +} + +/** Could we send right now (i.e. either not actively receiving or transmitting)? */ +template bool LR11x0Interface::isActivelyReceiving() +{ + // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet + // received and handled the interrupt for reading the packet/handling errors. + + uint16_t irq = lora.getIrqStatus(); + bool detected = (irq & (RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); + // Handle false detections + if (detected) { + uint32_t now = millis(); + if (!activeReceiveStart) { + activeReceiveStart = now; + } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID)) { + // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag + activeReceiveStart = 0; + LOG_DEBUG("Ignore false preamble detection.\n"); + return false; + } else if (now - activeReceiveStart > maxPacketTimeMsec) { + // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag + activeReceiveStart = 0; + LOG_DEBUG("Ignore false header detection.\n"); + return false; + } + } + + // if (detected) LOG_DEBUG("rx detected\n"); + return detected; +} + +template bool LR11x0Interface::sleep() +{ + // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet + // \todo Display actual typename of the adapter, not just `LR11x0` + LOG_DEBUG("LR11x0 entering sleep mode (FIXME, don't keep config)\n"); + setStandby(); // Stop any pending operations + + // turn off TCXO if it was powered + // FIXME - this isn't correct + // lora.setTCXO(0); + + // put chipset into sleep mode (we've already disabled interrupts by now) + bool keepConfig = true; + lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed + +#ifdef LR11X0_POWER_EN + digitalWrite(LR11X0_POWER_EN, LOW); +#endif + + return true; +} \ No newline at end of file diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h new file mode 100644 index 000000000..11a389d25 --- /dev/null +++ b/src/mesh/LR11x0Interface.h @@ -0,0 +1,71 @@ +#pragma once + +#include "RadioLibInterface.h" + +/** + * \brief Adapter for LR11x0 radio family. Implements common logic for child classes. + * \tparam T RadioLib module type for LR11x0: SX1262, SX1268. + */ +template class LR11x0Interface : public RadioLibInterface +{ + public: + LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init() override; + + /// Apply any radio provisioning changes + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool reconfigure() override; + + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep() override; + + bool isIRQPending() override { return lora.getIrqStatus() != 0; } + + protected: + /** + * Specific module instance + */ + T lora; + + /** + * Glue functions called from ISR land + */ + virtual void disableInterrupt() override; + + /** + * Enable a particular ISR callback glue function + */ + virtual void enableInterrupt(void (*callback)()) { lora.setIrqAction(callback); } + + /** can we detect a LoRa preamble on the current channel? */ + virtual bool isChannelActive() override; + + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving() override; + + /** + * Start waiting to receive a message + */ + virtual void startReceive() override; + + /** + * We override to turn on transmitter power as needed. + */ + virtual void configHardwareForSend() override; + + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; + + virtual void setStandby() override; + + private: + uint32_t activeReceiveStart = 0; +}; diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 68bd87801..b91c57c5e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -56,6 +56,8 @@ #define HW_VENDOR meshtastic_HardwareModel_TWC_MESH_V4 #elif defined(NRF52_PROMICRO_DIY) #define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY +#elif defined(WIO_WM1110) +#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini new file mode 100644 index 000000000..8b1433dd1 --- /dev/null +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -0,0 +1,15 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:wio-sdk-wm1110] +extends = nrf52840_base +board = wio-sdk-wm1110 +board_level = extra +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -DWIO_WM1110 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = jlink \ No newline at end of file diff --git a/variants/wio-sdk-wm1110/variant.cpp b/variants/wio-sdk-wm1110/variant.cpp new file mode 100644 index 000000000..5a3587982 --- /dev/null +++ b/variants/wio-sdk-wm1110/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} \ No newline at end of file diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h new file mode 100644 index 000000000..f027b469f --- /dev/null +++ b/variants/wio-sdk-wm1110/variant.h @@ -0,0 +1,111 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_WIO_SDK_WM1110_ +#define _VARIANT_WIO_SDK_WM1110_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_3V3_EN (0 + 7) // P0.7, Power to Sensors + +#define PIN_WIRE_SDA (0 + 27) // P0.27 +#define PIN_WIRE_SCL (0 + 26) // P0.26 + +#define PIN_LED1 (0 + 13) // P0.13 +#define PIN_LED2 (0 + 14) // P0.14 + +#define LED_BUILTIN PIN_LED1 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 // Actually red + +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN (0 + 23) // P0.23 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 22) // P0.22 +#define PIN_SERIAL1_TX (0 + 24) // P0.24 + +#define PIN_SERIAL2_RX (0 + 6) // P0.06 +#define PIN_SERIAL2_TX (0 + 8) // P0.08 + +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (32 + 15) // P1.15 47 +#define PIN_SPI_MOSI (32 + 14) // P1.14 46 +#define PIN_SPI_SCK (32 + 13) // P1.13 45 +#define PIN_SPI_NSS (32 + 12) // P1.12 44 + +#define LORA_RESET (32 + 10) // P1.10 42 // RST +#define LORA_DIO1 (32 + 8) // P1.08 40 // IRQ +#define LORA_DIO2 (32 + 11) // P1.11 43 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +// supported modules list +#define USE_LR1110 + +#define LR1110_IRQ_PIN LORA_DIO1 +#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO2 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.6 +#define LR11X0_DIO_AS_RF_SWITCH + +#define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_WIO_SDK_WM1110_ diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini new file mode 100644 index 000000000..cba1b8741 --- /dev/null +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -0,0 +1,14 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:wio-tracker-wm1110] +extends = nrf52840_base +board = wio-tracker-wm1110 +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -DWIO_WM1110 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/wio-tracker-wm1110/variant.cpp b/variants/wio-tracker-wm1110/variant.cpp new file mode 100644 index 000000000..5a3587982 --- /dev/null +++ b/variants/wio-tracker-wm1110/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} \ No newline at end of file diff --git a/variants/wio-tracker-wm1110/variant.h b/variants/wio-tracker-wm1110/variant.h new file mode 100644 index 000000000..e929332e6 --- /dev/null +++ b/variants/wio-tracker-wm1110/variant.h @@ -0,0 +1,111 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_WIO_TRACKER_WM1110_ +#define _VARIANT_WIO_TRACKER_WM1110_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_3V3_EN (32 + 1) // P1.01, Power to Sensors + +#define PIN_WIRE_SDA (0 + 5) // P0.05 +#define PIN_WIRE_SCL (0 + 4) // P0.04 + +#define PIN_LED1 (0 + 6) // P0.06 +#define PIN_LED2 (PINS_COUNT) // P0.14 + +#define LED_BUILTIN PIN_LED1 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 0 + +#define BUTTON_PIN (32 + 2) // P1.02 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 24) // P0.24 +#define PIN_SERIAL1_TX (0 + 25) // P0.25 + +#define PIN_SERIAL2_RX (0 + 6) // P0.06 +#define PIN_SERIAL2_TX (0 + 8) // P0.08 + +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (32 + 15) // P1.15 47 +#define PIN_SPI_MOSI (32 + 14) // P1.14 46 +#define PIN_SPI_SCK (32 + 13) // P1.13 45 +#define PIN_SPI_NSS (32 + 12) // P1.12 44 + +#define LORA_RESET (0 + 18) // P0.18 18 // RST +#define LORA_DIO1 (0 + 2) // P0.02 2 // IRQ +#define LORA_DIO2 (32 + 11) // P1.11 43 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +// supported modules list +#define USE_LR1110 + +#define LR1110_IRQ_PIN LORA_DIO1 +#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO2 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.6 +#define LR11X0_DIO_AS_RF_SWITCH + +#define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_WIO_TRACKER_WM1110_ From a218c6fb4d42abc867bac2e588ae274462c658b0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Jun 2024 21:50:28 -0500 Subject: [PATCH 0514/1377] DFRobot Lark weather station support (#4032) * DF Robot Lark weather station support * Missed it * I am a man of const char sorrow... * Strang * Use our fork --- platformio.ini | 2 + src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 5 +- src/gps/GeoCoord.cpp | 88 +++++++++++++++++++ src/gps/GeoCoord.h | 2 + src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 17 +++- .../Telemetry/Sensor/DFRobotLarkSensor.cpp | 53 +++++++++++ .../Telemetry/Sensor/DFRobotLarkSensor.h | 24 +++++ 10 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/DFRobotLarkSensor.h diff --git a/platformio.ini b/platformio.ini index e8eff024d..85bce3279 100644 --- a/platformio.ini +++ b/platformio.ini @@ -139,3 +139,5 @@ lib_deps = adafruit/Adafruit TSL2591 Library@^1.4.5 ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 + dfrobot/DFRobot_RTU@^1.0.3 + https://github.com/meshtastic/DFRobot_LarkWeatherStation#0e884fc86b7a0b602c7ff3d26b893b997f15c6ac \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 744406f18..462210cf2 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -135,6 +135,7 @@ along with this program. If not, see . #define OPT3001_ADDR 0x45 #define OPT3001_ADDR_ALT 0x44 #define MLX90632_ADDR 0x3A +#define DFROBOT_LARK_ADDR 0x42 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index a90d9218a..13dd66763 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -49,6 +49,7 @@ class ScanI2C OPT3001, MLX90632, AHT10, + DFROBOT_LARK, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 3aeb8560e..86099ad19 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -281,8 +281,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port) if (registerValue == 0x5449) { LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address); type = INA3221; - } else { // Unknown device - LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address); + } else { + LOG_INFO("DFRobot Lark weather station found at address 0x%x\n", (uint8_t)addr.address); + type = DFROBOT_LARK; } break; case MCP9808_ADDR: diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index cb4e69ff2..2224bd281 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -486,3 +486,91 @@ std::shared_ptr GeoCoord::pointAtDistance(double bearing, double range return std::make_shared(double(lat), double(lon), this->getAltitude()); } + +/** + * Convert bearing to degrees + * @param bearing + * The bearing in string format + * @return Bearing in degrees + */ +uint GeoCoord::bearingToDegrees(const char *bearing) +{ + if (strcmp(bearing, "N") == 0) + return 0; + else if (strcmp(bearing, "NNE") == 0) + return 22; + else if (strcmp(bearing, "NE") == 0) + return 45; + else if (strcmp(bearing, "ENE") == 0) + return 67; + else if (strcmp(bearing, "E") == 0) + return 90; + else if (strcmp(bearing, "ESE") == 0) + return 112; + else if (strcmp(bearing, "SE") == 0) + return 135; + else if (strcmp(bearing, "SSE") == 0) + return 157; + else if (strcmp(bearing, "S") == 0) + return 180; + else if (strcmp(bearing, "SSW") == 0) + return 202; + else if (strcmp(bearing, "SW") == 0) + return 225; + else if (strcmp(bearing, "WSW") == 0) + return 247; + else if (strcmp(bearing, "W") == 0) + return 270; + else if (strcmp(bearing, "WNW") == 0) + return 292; + else if (strcmp(bearing, "NW") == 0) + return 315; + else if (strcmp(bearing, "NNW") == 0) + return 337; + else + return 0; +} + +/** + * Convert bearing to string + * @param degrees + * The bearing in degrees + * @return Bearing in string format + */ +const char *GeoCoord::degreesToBearing(uint degrees) +{ + if (degrees >= 348 || degrees < 11) + return "N"; + else if (degrees >= 11 && degrees < 34) + return "NNE"; + else if (degrees >= 34 && degrees < 56) + return "NE"; + else if (degrees >= 56 && degrees < 79) + return "ENE"; + else if (degrees >= 79 && degrees < 101) + return "E"; + else if (degrees >= 101 && degrees < 124) + return "ESE"; + else if (degrees >= 124 && degrees < 146) + return "SE"; + else if (degrees >= 146 && degrees < 169) + return "SSE"; + else if (degrees >= 169 && degrees < 191) + return "S"; + else if (degrees >= 191 && degrees < 214) + return "SSW"; + else if (degrees >= 214 && degrees < 236) + return "SW"; + else if (degrees >= 236 && degrees < 259) + return "WSW"; + else if (degrees >= 259 && degrees < 281) + return "W"; + else if (degrees >= 281 && degrees < 304) + return "WNW"; + else if (degrees >= 304 && degrees < 326) + return "NW"; + else if (degrees >= 326 && degrees < 348) + return "NNW"; + else + return "N"; +} diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index e811035db..b02d12afb 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -117,6 +117,8 @@ class GeoCoord static float bearing(double lat1, double lon1, double lat2, double lon2); static float rangeRadiansToMeters(double range_radians); static float rangeMetersToRadians(double range_meters); + static uint bearingToDegrees(const char *bearing); + static const char *degreesToBearing(uint degrees); // Point to point conversions int32_t distanceTo(const GeoCoord &pointB); diff --git a/src/main.cpp b/src/main.cpp index 52de93e83..6797c8375 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -540,6 +540,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) i2cScanner.reset(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 9032389c5..8972a8e3f 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -23,6 +23,7 @@ #include "Sensor/BME680Sensor.h" #include "Sensor/BMP085Sensor.h" #include "Sensor/BMP280Sensor.h" +#include "Sensor/DFRobotLarkSensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" #include "Sensor/MLX90632Sensor.h" @@ -49,6 +50,7 @@ SHT4XSensor sht4xSensor; RCWL9620Sensor rcwl9620Sensor; AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; +DFRobotLarkSensor dfRobotLarkSensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -72,7 +74,7 @@ int32_t EnvironmentTelemetryModule::runOnce() // moduleConfig.telemetry.environment_measurement_enabled = 1; // moduleConfig.telemetry.environment_screen_enabled = 1; - // moduleConfig.telemetry.environment_update_interval = 45; + // moduleConfig.telemetry.environment_update_interval = 15; if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) { // If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it @@ -87,6 +89,8 @@ int32_t EnvironmentTelemetryModule::runOnce() LOG_INFO("Environment Telemetry: Initializing\n"); // it's possible to have this module enabled, only for displaying values on the screen. // therefore, we should only enable the sensor loop if measurement is also enabled + if (dfRobotLarkSensor.hasSensor()) + result = dfRobotLarkSensor.runOnce(); if (bmp085Sensor.hasSensor()) result = bmp085Sensor.runOnce(); if (bmp280Sensor.hasSensor()) @@ -240,6 +244,10 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac t->variant.environment_metrics.temperature); LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); + + LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees\n", sender, + t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction); + #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -259,6 +267,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); m.which_variant = meshtastic_Telemetry_environment_metrics_tag; + if (dfRobotLarkSensor.hasSensor()) { + valid = valid && dfRobotLarkSensor.getMetrics(&m); + hasSensor = true; + } if (sht31Sensor.hasSensor()) { valid = valid && sht31Sensor.getMetrics(&m); hasSensor = true; @@ -342,6 +354,9 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage, m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); + LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees\n", m.variant.environment_metrics.wind_speed, + m.variant.environment_metrics.wind_direction); + sensor_read_error_count = 0; meshtastic_MeshPacket *p = allocDataProtobuf(m); diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp new file mode 100644 index 000000000..830552023 --- /dev/null +++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp @@ -0,0 +1,53 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "DFRobotLarkSensor.h" +#include "TelemetrySensor.h" +#include "gps/GeoCoord.h" +#include +#include + +DFRobotLarkSensor::DFRobotLarkSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_LARK, "DFROBOT_LARK") {} + +int32_t DFRobotLarkSensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + lark = DFRobot_LarkWeatherStation_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + + if (lark.begin() == 0) // DFRobotLarkSensor init + { + LOG_DEBUG("DFRobotLarkSensor Init Succeed\n"); + status = true; + } else { + LOG_ERROR("DFRobotLarkSensor Init Failed\n"); + status = false; + } + return initI2CSensor(); +} + +void DFRobotLarkSensor::setup() {} + +bool DFRobotLarkSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.temperature = lark.getValue("Temp").toFloat(); + measurement->variant.environment_metrics.relative_humidity = lark.getValue("Humi").toFloat(); + measurement->variant.environment_metrics.wind_speed = lark.getValue("Speed").toFloat(); + measurement->variant.environment_metrics.wind_direction = GeoCoord::bearingToDegrees(lark.getValue("Dir").c_str()); + measurement->variant.environment_metrics.barometric_pressure = lark.getValue("Pressure").toFloat(); + + LOG_INFO("Temperature: %f\n", measurement->variant.environment_metrics.temperature); + LOG_INFO("Humidity: %f\n", measurement->variant.environment_metrics.relative_humidity); + LOG_INFO("Wind Speed: %f\n", measurement->variant.environment_metrics.wind_speed); + LOG_INFO("Wind Direction: %d\n", measurement->variant.environment_metrics.wind_direction); + LOG_INFO("Barometric Pressure: %f\n", measurement->variant.environment_metrics.barometric_pressure); + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h new file mode 100644 index 000000000..b26d690b1 --- /dev/null +++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h @@ -0,0 +1,24 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include +#include + +class DFRobotLarkSensor : public TelemetrySensor +{ + private: + DFRobot_LarkWeatherStation_I2C lark = DFRobot_LarkWeatherStation_I2C(); + + protected: + virtual void setup() override; + + public: + DFRobotLarkSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file From 9632e4c405069cfccf23b4cf274af14c169c6ea0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 4 Jun 2024 07:04:25 -0500 Subject: [PATCH 0515/1377] Add missing excludes to environmental sensors (#4033) * DF Robot Lark weather station support * Missed it * I am a man of const char sorrow... * Strang * Use our fork * Add excludes --- src/modules/Telemetry/Sensor/AHT10.cpp | 12 +++++++++--- src/modules/Telemetry/Sensor/AHT10.h | 6 ++++++ src/modules/Telemetry/Sensor/TSL2591Sensor.cpp | 13 +++++++++---- src/modules/Telemetry/Sensor/TSL2591Sensor.h | 7 ++++++- src/modules/Telemetry/Sensor/VEML7700Sensor.cpp | 11 ++++++++--- src/modules/Telemetry/Sensor/VEML7700Sensor.h | 7 ++++++- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp index 985515bb6..a5212b39b 100644 --- a/src/modules/Telemetry/Sensor/AHT10.cpp +++ b/src/modules/Telemetry/Sensor/AHT10.cpp @@ -1,7 +1,11 @@ -#include "AHT10.h" -#include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" #include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "AHT10.h" +#include "TelemetrySensor.h" + #include #include @@ -33,3 +37,5 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) return true; } + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h index b2b7b47f3..d9a133402 100644 --- a/src/modules/Telemetry/Sensor/AHT10.h +++ b/src/modules/Telemetry/Sensor/AHT10.h @@ -1,3 +1,7 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -15,3 +19,5 @@ class AHT10Sensor : public TelemetrySensor virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp index 0a3f5d685..d20e48dce 100644 --- a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp @@ -1,7 +1,10 @@ -#include "TSL2591Sensor.h" -#include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" #include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TSL2591Sensor.h" +#include "TelemetrySensor.h" #include #include @@ -35,4 +38,6 @@ bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement) LOG_INFO("Lux: %f\n", measurement->variant.environment_metrics.lux); return true; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.h b/src/modules/Telemetry/Sensor/TSL2591Sensor.h index a24d53975..27bebdfe5 100644 --- a/src/modules/Telemetry/Sensor/TSL2591Sensor.h +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.h @@ -1,3 +1,7 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +18,5 @@ class TSL2591Sensor : public TelemetrySensor TSL2591Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp index 1abe8339f..cbeaf4c2e 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -1,7 +1,11 @@ -#include "VEML7700Sensor.h" +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "configuration.h" +#include "VEML7700Sensor.h" + #include #include @@ -57,4 +61,5 @@ bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.lux); return true; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h index 9c7a584d9..97e57334c 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.h +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h @@ -1,3 +1,7 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -19,4 +23,5 @@ class VEML7700Sensor : public TelemetrySensor VEML7700Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; +#endif \ No newline at end of file From 181f03cb9538fea30cf00ff155dc5298881190f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 4 Jun 2024 15:14:27 +0200 Subject: [PATCH 0516/1377] tryfix random values (#4034) --- src/modules/Telemetry/AirQualityTelemetry.cpp | 2 +- src/modules/Telemetry/DeviceTelemetry.cpp | 2 +- src/modules/Telemetry/EnvironmentTelemetry.cpp | 4 ++-- src/modules/Telemetry/PowerTelemetry.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index e4f31ff9f..4f5fbcd13 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -92,7 +92,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) return false; } - meshtastic_Telemetry m; + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; m.time = getTime(); m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; m.variant.air_quality_metrics.pm10_standard = data.pm10_standard; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 002ce62a9..b64e8d113 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -64,7 +64,7 @@ meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() { - meshtastic_Telemetry t; + meshtastic_Telemetry t = meshtastic_Telemetry_init_zero; t.time = getTime(); t.which_variant = meshtastic_Telemetry_device_metrics_tag; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8972a8e3f..46b8a1ad8 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -261,7 +261,7 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { - meshtastic_Telemetry m; + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; bool valid = true; bool hasSensor = false; m.time = getTime(); @@ -337,7 +337,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) hasSensor = true; } else { // prefer bmp280 temp if both sensors are present, fetch only humidity - meshtastic_Telemetry m_ahtx; + meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n"); aht10Sensor.getMetrics(&m_ahtx); m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index e61a4e629..826de8a4a 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -165,7 +165,7 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { - meshtastic_Telemetry m; + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; bool valid = false; m.time = getTime(); m.which_variant = meshtastic_Telemetry_power_metrics_tag; From 67b67a481f2756568feaa876006103fb8ba4dede Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 08:34:38 -0500 Subject: [PATCH 0517/1377] [create-pull-request] automated change (#4035) --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 9 +++ src/mesh/generated/meshtastic/mesh.pb.h | 83 +++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index bfbf4a65e..a641c5ce4 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bfbf4a65e220581f45c5ed949c659953ac4d080f +Subproject commit a641c5ce4fca158d18ca3cffc92ac7a10f9b6a04 diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 4907affc6..46d59d609 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -66,6 +66,15 @@ PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO) PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO) +PB_BIND(meshtastic_ChunkedPayload, meshtastic_ChunkedPayload, AUTO) + + +PB_BIND(meshtastic_resend_chunks, meshtastic_resend_chunks, AUTO) + + +PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 7b544d714..ad97cb80f 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -65,6 +65,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_LORA_TYPE = 19, /* wiphone https://www.wiphone.io/ */ meshtastic_HardwareModel_WIPHONE = 20, + /* WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk */ + meshtastic_HardwareModel_WIO_WM1110 = 21, /* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */ meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ @@ -853,6 +855,38 @@ typedef struct _meshtastic_NodeRemoteHardwarePin { meshtastic_RemoteHardwarePin pin; } meshtastic_NodeRemoteHardwarePin; +typedef PB_BYTES_ARRAY_T(228) meshtastic_ChunkedPayload_payload_chunk_t; +typedef struct _meshtastic_ChunkedPayload { + /* The ID of the entire payload */ + uint32_t payload_id; + /* The total number of chunks in the payload */ + uint16_t chunk_count; + /* The current chunk index in the total */ + uint16_t chunk_index; + /* The binary data of the current chunk */ + meshtastic_ChunkedPayload_payload_chunk_t payload_chunk; +} meshtastic_ChunkedPayload; + +/* Wrapper message for broken repeated oneof support */ +typedef struct _meshtastic_resend_chunks { + pb_callback_t chunks; +} meshtastic_resend_chunks; + +/* Responses to a ChunkedPayload request */ +typedef struct _meshtastic_ChunkedPayloadResponse { + /* The ID of the entire payload */ + uint32_t payload_id; + pb_size_t which_payload_variant; + union { + /* Request to transfer chunked payload */ + bool request_transfer; + /* Accept the transfer chunked payload */ + bool accept_transfer; + /* Request missing indexes in the chunked payload */ + meshtastic_resend_chunks resend_chunks; + } payload_variant; +} meshtastic_ChunkedPayloadResponse; + #ifdef __cplusplus extern "C" { @@ -928,6 +962,9 @@ extern "C" { + + + /* Initializer values for message structs */ #define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} @@ -949,6 +986,9 @@ extern "C" { #define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} #define meshtastic_Heartbeat_init_default {0} #define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} +#define meshtastic_ChunkedPayload_init_default {0, 0, 0, {0, {0}}} +#define meshtastic_resend_chunks_init_default {{{NULL}, NULL}} +#define meshtastic_ChunkedPayloadResponse_init_default {0, 0, {0}} #define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} @@ -969,6 +1009,9 @@ extern "C" { #define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} #define meshtastic_Heartbeat_init_zero {0} #define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero} +#define meshtastic_ChunkedPayload_init_zero {0, 0, 0, {0, {0}}} +#define meshtastic_resend_chunks_init_zero {{{NULL}, NULL}} +#define meshtastic_ChunkedPayloadResponse_init_zero {0, 0, {0}} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Position_latitude_i_tag 1 @@ -1103,6 +1146,15 @@ extern "C" { #define meshtastic_ToRadio_heartbeat_tag 7 #define meshtastic_NodeRemoteHardwarePin_node_num_tag 1 #define meshtastic_NodeRemoteHardwarePin_pin_tag 2 +#define meshtastic_ChunkedPayload_payload_id_tag 1 +#define meshtastic_ChunkedPayload_chunk_count_tag 2 +#define meshtastic_ChunkedPayload_chunk_index_tag 3 +#define meshtastic_ChunkedPayload_payload_chunk_tag 4 +#define meshtastic_resend_chunks_chunks_tag 1 +#define meshtastic_ChunkedPayloadResponse_payload_id_tag 1 +#define meshtastic_ChunkedPayloadResponse_request_transfer_tag 2 +#define meshtastic_ChunkedPayloadResponse_accept_transfer_tag 3 +#define meshtastic_ChunkedPayloadResponse_resend_chunks_tag 4 /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ @@ -1341,6 +1393,28 @@ X(a, STATIC, OPTIONAL, MESSAGE, pin, 2) #define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL #define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin +#define meshtastic_ChunkedPayload_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, payload_id, 1) \ +X(a, STATIC, SINGULAR, UINT32, chunk_count, 2) \ +X(a, STATIC, SINGULAR, UINT32, chunk_index, 3) \ +X(a, STATIC, SINGULAR, BYTES, payload_chunk, 4) +#define meshtastic_ChunkedPayload_CALLBACK NULL +#define meshtastic_ChunkedPayload_DEFAULT NULL + +#define meshtastic_resend_chunks_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, UINT32, chunks, 1) +#define meshtastic_resend_chunks_CALLBACK pb_default_field_callback +#define meshtastic_resend_chunks_DEFAULT NULL + +#define meshtastic_ChunkedPayloadResponse_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, payload_id, 1) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,request_transfer,payload_variant.request_transfer), 2) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,accept_transfer,payload_variant.accept_transfer), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,resend_chunks,payload_variant.resend_chunks), 4) +#define meshtastic_ChunkedPayloadResponse_CALLBACK NULL +#define meshtastic_ChunkedPayloadResponse_DEFAULT NULL +#define meshtastic_ChunkedPayloadResponse_payload_variant_resend_chunks_MSGTYPE meshtastic_resend_chunks + extern const pb_msgdesc_t meshtastic_Position_msg; extern const pb_msgdesc_t meshtastic_User_msg; extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; @@ -1361,6 +1435,9 @@ extern const pb_msgdesc_t meshtastic_Neighbor_msg; extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; extern const pb_msgdesc_t meshtastic_Heartbeat_msg; extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; +extern const pb_msgdesc_t meshtastic_ChunkedPayload_msg; +extern const pb_msgdesc_t meshtastic_resend_chunks_msg; +extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Position_fields &meshtastic_Position_msg @@ -1383,9 +1460,15 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg #define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg #define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg +#define meshtastic_ChunkedPayload_fields &meshtastic_ChunkedPayload_msg +#define meshtastic_resend_chunks_fields &meshtastic_resend_chunks_msg +#define meshtastic_ChunkedPayloadResponse_fields &meshtastic_ChunkedPayloadResponse_msg /* Maximum encoded size of messages (where known) */ +/* meshtastic_resend_chunks_size depends on runtime parameters */ +/* meshtastic_ChunkedPayloadResponse_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size +#define meshtastic_ChunkedPayload_size 245 #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 From c37316e7237c50f8442de3b6ead35ed3605c5688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 4 Jun 2024 15:44:48 +0200 Subject: [PATCH 0518/1377] use correct hardware tag for tracker and sdk (#4036) --- src/platform/nrf52/architecture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index b91c57c5e..18a4d75f5 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -57,7 +57,7 @@ #elif defined(NRF52_PROMICRO_DIY) #define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY #elif defined(WIO_WM1110) -#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW +#define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else From d0ca616c19028ae4609744d15f5fd340430265e5 Mon Sep 17 00:00:00 2001 From: jcyrio <50239349+jcyrio@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:02:43 -0700 Subject: [PATCH 0519/1377] typo: 'our our' to 'of our' (#4037) --- src/graphics/Screen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 15e69b1ed..1c9484f62 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -2071,7 +2071,7 @@ void Screen::setFrames() LOG_DEBUG("Total frame count: %d\n", totalFrameCount); #endif - // We don't show the node info our our node (if we have it yet - we should) + // We don't show the node info of our node (if we have it yet - we should) size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (numMeshNodes > 0) numMeshNodes--; @@ -2708,4 +2708,4 @@ int Screen::handleInputEvent(const InputEvent *event) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN +#endif // HAS_SCREEN \ No newline at end of file From fbc8f6c03b563a7673250fbdd942fe03c152e252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 15:08:23 +0200 Subject: [PATCH 0520/1377] package x86_64 meshtasticd --- .github/workflows/main_matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index a768e5fd9..d2338c959 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -142,6 +142,7 @@ jobs: path: | release/device-*.sh release/device-*.bat + release/meshtasticd_linux* - name: Docker login if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} @@ -217,7 +218,7 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./release/meshtasticd_linux_aarch64 ./release/meshtasticd_linux_armv7l ./bin/config-dist.yaml + run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 From 2cc5598f8992074d7ebf5602719d174473afb525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 16:27:46 +0200 Subject: [PATCH 0521/1377] Try building a deb for native --- .github/workflows/package_amd64.yml | 78 +++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/package_amd64.yml diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml new file mode 100644 index 000000000..ae7bf3242 --- /dev/null +++ b/.github/workflows/package_amd64.yml @@ -0,0 +1,78 @@ +name: Package Native + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: write + packages: write + +jobs: + build-native: + uses: ./.github/workflows/build_native.yml + + package-native: + runs-on: ubuntu-latest + needs: build-native + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: firmware-native-${{ steps.version.outputs.version }}.zip + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: build .debpkg + run: | + mkdir -p .debpkg/DEBIAN + mkdir -p .debpkg/usr/share/doc/meshtasticd/web + mkdir -p .debpkg/usr/sbin + mkdir -p .debpkg/etc/meshtasticd + mkdir -p .debpkg/usr/lib/systemd/system/ + tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web + gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz + cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd + cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml + chmod +x .debpkg/usr/sbin/meshtasticd + cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service + echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles + chmod +x .debpkg/DEBIAN/conffiles + + - uses: jiro4989/build-deb-action@v3 + with: + package: meshtasticd + package_root: .debpkg + maintainer: Jonathan Bennett + version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* + arch: amd64 + depends: libyaml-cpp0.7, openssl, libulfius2.7 + desc: Native Linux Meshtastic binary. + + - uses: actions/upload-artifact@v4 + with: + name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + overwrite: true + path: | + ./*.deb From d8775d94e371ecf24f41f9fe700fcc48218ec51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 16:29:40 +0200 Subject: [PATCH 0522/1377] try harder dude --- .github/workflows/main_matrix.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index d2338c959..ade018e1b 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -109,6 +109,9 @@ jobs: package-raspbian-armv7l: uses: ./.github/workflows/package_raspbian_armv7l.yml + package-native: + uses: ./.github/workflows/package_amd64.yml + build-native: runs-on: ubuntu-latest steps: From 14b7c5b6efd73c6da7361f323360a6d2e7679980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 16:34:30 +0200 Subject: [PATCH 0523/1377] Create build_native.yml --- .github/workflows/build_native.yml | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/build_native.yml diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml new file mode 100644 index 000000000..7c6cf6738 --- /dev/null +++ b/.github/workflows/build_native.yml @@ -0,0 +1,51 @@ +name: Build Native + +on: workflow_call + +permissions: + contents: write + packages: write + +jobs: + build-native: + runs-on: ubuntu-latest + steps: + - name: Install libbluetooth + shell: bash + run: | + apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev + + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Upgrade python tools + shell: bash + run: | + python -m pip install --upgrade pip + pip install -U platformio adafruit-nrfutil + pip install -U meshtastic --pre + + - name: Upgrade platformio + shell: bash + run: | + pio upgrade + + - name: Build Native + run: bin/build-native.sh + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-native-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | + release/meshtasticd_linux_x86_64 + bin/config-dist.yaml From f1906c38f1aa7b1e22f3e897453b774583d6895c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 16:37:12 +0200 Subject: [PATCH 0524/1377] sudo make me a sandwich --- .github/workflows/build_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 7c6cf6738..d8d57a672 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev + sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code uses: actions/checkout@v4 From fb3c1412314cb607425cb562157c6e4ede8301b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 17:04:22 +0200 Subject: [PATCH 0525/1377] update package index --- .github/workflows/build_native.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index d8d57a672..257bc4176 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -13,6 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | + sudo apt-get update sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code From 7874ebc568c45e3413e6acd611b79f2948e88219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 17:24:55 +0200 Subject: [PATCH 0526/1377] Update package_amd64.yml --- .github/workflows/package_amd64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml index ae7bf3242..7a5efd154 100644 --- a/.github/workflows/package_amd64.yml +++ b/.github/workflows/package_amd64.yml @@ -53,7 +53,7 @@ jobs: mkdir -p .debpkg/usr/lib/systemd/system/ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz - cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd + cp meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml chmod +x .debpkg/usr/sbin/meshtasticd cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service From 96b286cd48d1a8c738ddc6ce5db9be81618cf21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 5 Jun 2024 17:50:58 +0200 Subject: [PATCH 0527/1377] release x86_64 deb --- .github/workflows/main_matrix.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ade018e1b..800c18b89 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -385,6 +385,16 @@ jobs: asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb asset_content_type: application/vnd.debian.binary-package + - name: Add raspbian amd64 .deb + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + asset_content_type: application/vnd.debian.binary-package + - name: Bump version.properties run: >- bin/bump_version.py From d82d9f5ef12aa2db54d7b0ead3b445c3253347f5 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 5 Jun 2024 23:31:33 +0200 Subject: [PATCH 0528/1377] fix dead link in EU_868 documentation See https://discord.com/channels/867578229534359593/871553168369148024/1248026118276255745. --- src/mesh/RadioInterface.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index cc6ccca07..eb86f4267 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -31,18 +31,18 @@ const RegionInfo regions[] = { RDEF(EU_433, 433.0f, 434.0f, 10, 0, 12, true, false, false), /* - https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/ - https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/ - https://www.legislation.gov.uk/uksi/1999/930/schedule/6/part/III/made/data.xht?view=snippet&wrap=true + https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/ + https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/ + https://www.legislation.gov.uk/uksi/1999/930/schedule/6/part/III/made/data.xht?view=snippet&wrap=true - audio_permitted = false per regulation + audio_permitted = false per regulation - Special Note: - The link above describes LoRaWAN's band plan, stating a power limit of 16 dBm. This is their own suggested specification, - we do not need to follow it. The European Union regulations clearly state that the power limit for this frequency range is - 500 mW, or 27 dBm. It also states that we can use interference avoidance and spectrum access techniques to avoid a duty - cycle. (Please refer to section 4.21 in the following document) - https://ec.europa.eu/growth/tools-databases/tris/index.cfm/ro/search/?trisaction=search.detail&year=2021&num=528&dLang=EN + Special Note: + The link above describes LoRaWAN's band plan, stating a power limit of 16 dBm. This is their own suggested specification, + we do not need to follow it. The European Union regulations clearly state that the power limit for this frequency range is + 500 mW, or 27 dBm. It also states that we can use interference avoidance and spectrum access techniques (such as LBT + + AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.) + https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf */ RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false), From 5554cc46a72acb56bee55613a39bd1e44095ecba Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Wed, 8 May 2024 20:15:06 +0800 Subject: [PATCH 0529/1377] Add REGULATORY_ prefix to LORA_REGIONCODE Add REGULATORY_ prefix to LORA_REGIONCODE to prepare for more regulatory configuration options, and update comment block accordingly too. Signed-off-by: Andrew Yong --- src/configuration.h | 6 +++--- src/mesh/RadioInterface.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 462210cf2..6dcca72dc 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -75,11 +75,11 @@ along with this program. If not, see . #endif // ----------------------------------------------------------------------------- -// Regulatory overrides for producing regional builds +// Regulatory overrides // ----------------------------------------------------------------------------- -// Define if region should override user saved region -// #define LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923 +// Override user saved region, for producing region-locked builds +// #define REGULATORY_LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923 // ----------------------------------------------------------------------------- // Feature toggles diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index cc6ccca07..7a1fcfb94 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -154,8 +154,8 @@ static uint8_t bytes[MAX_RHPACKETLEN]; void initRegion() { const RegionInfo *r = regions; -#ifdef LORA_REGIONCODE - for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != LORA_REGIONCODE; r++) +#ifdef REGULATORY_LORA_REGIONCODE + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != REGULATORY_LORA_REGIONCODE; r++) ; LOG_INFO("Wanted region %d, regulatory override to %s\n", config.lora.region, r->name); #else From 3cda5986732593f23ba2c293c2b0d68ba87fe1ef Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 9 May 2024 02:27:08 +0800 Subject: [PATCH 0530/1377] Add REGULATORY_GAIN configuration to remain within regulatory ERP limit REGULATORY_GAIN is the total system gain in dBm to subtract from the configured Tx power, to remain within regulatory ERP limit for non-licensed operators. This value should be set in variant.h and is PA gain + antenna gain (if system ships with an antenna). This is similar to antenna_gain/NL80211_ATTR_WIPHY_ANTENNA_GAIN/NL80211_ATTR_REG_RULE_POWER_MAX_ANT_GAIN setting in Linux Regulatory/OpenWrt/mac80211/nl80211/iw. Signed-off-by: Andrew Yong --- src/configuration.h | 6 ++++++ src/mesh/RadioInterface.cpp | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 6dcca72dc..eab2d0120 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -81,6 +81,12 @@ along with this program. If not, see . // Override user saved region, for producing region-locked builds // #define REGULATORY_LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923 +// Total system gain in dBm to subtract from Tx power to remain within regulatory ERP limit for non-licensed operators +// This value should be set in variant.h and is PA gain + antenna gain (if system ships with an antenna) +#ifndef REGULATORY_GAIN +#define REGULATORY_GAIN 0 +#endif + // ----------------------------------------------------------------------------- // Feature toggles // ----------------------------------------------------------------------------- diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 7a1fcfb94..8e77beee4 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -478,8 +478,8 @@ void RadioInterface::applyModemConfig() power = loraConfig.tx_power; - if ((power == 0) || ((power > myRegion->powerLimit) && !devicestate.owner.is_licensed)) - power = myRegion->powerLimit; + if ((power == 0) || ((power + REGULATORY_GAIN > myRegion->powerLimit) && !devicestate.owner.is_licensed)) + power = myRegion->powerLimit - REGULATORY_GAIN; if (power == 0) power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of myRegion defaults From d1d49efc6e4f5fd5294a4a3f120b739c25f522e5 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 9 May 2024 02:27:29 +0800 Subject: [PATCH 0531/1377] Implement REGULATORY_GAIN and SX126X_MAX_POWER in XIAO BLE EBYTE E22 Specify REGULATORY_GAIN and SX126X_MAX_POWER to prevent exceeding regulatory and hardware limits (i.e. overloading the PA input) respectively. Also update the build flag to define EBYTE_E22_900M30S instead of just EBYTE_E22, since all the builds on the Discourse topic [New 1W DIY variant: Xiao nRF52840 + Ebyte E22-900M30S](https://meshtastic.discourse.group/t/new-1w-diy-variant-xiao-nrf52840-ebyte-e22-900m30s/7904) are using this module. That should make it clearer as well that the variant header file should be tweaked if DIY builds are using stronger (E22-900M33S, not commonly available at this time) or weaker (E22-900M22S, not popular for DIY builds due to lack of differentiation from ordinary SX1262 modules). Retain EBYTE_E22 flag alongside EBYTE_E22_900M30S build flag to prevent possible regressions in code paths generally intended for EBYTE E22 modules. Signed-off-by: Andrew Yong --- variants/xiao_ble/platformio.ini | 2 +- variants/xiao_ble/variant.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 8e9a663a9..165536cce 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = xiao_ble_sense board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Dxiao_ble -D EBYTE_E22 +build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Dxiao_ble -DEBYTE_E22 -DEBYTE_E22_900M30S -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/xiao_ble> lib_deps = diff --git a/variants/xiao_ble/variant.h b/variants/xiao_ble/variant.h index 77af08278..1869e4eee 100644 --- a/variants/xiao_ble/variant.h +++ b/variants/xiao_ble/variant.h @@ -142,6 +142,11 @@ static const uint8_t SCK = PIN_SPI_SCK; // (which is the default for the sx1262interface code) #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#ifdef EBYTE_E22_900M30S +// 10dB PA gain and 30dB rated output; based on PA output table from Ebyte Robin +#define REGULATORY_GAIN 10 +#define SX126X_MAX_POWER 20 +#endif #endif /* From 537814df588f1c42b2a7bd4b768f743ad4d046f9 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Wed, 22 May 2024 03:16:27 +0800 Subject: [PATCH 0532/1377] xiao_ble: Add EBYTE E22-900M33S PA gain and limits Signed-off-by: Andrew Yong --- variants/xiao_ble/variant.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/variants/xiao_ble/variant.h b/variants/xiao_ble/variant.h index 1869e4eee..4ca38f928 100644 --- a/variants/xiao_ble/variant.h +++ b/variants/xiao_ble/variant.h @@ -147,6 +147,11 @@ static const uint8_t SCK = PIN_SPI_SCK; #define REGULATORY_GAIN 10 #define SX126X_MAX_POWER 20 #endif +#ifdef EBYTE_E22_900M33S +// 25dB PA gain and 33dB rated output; based on TX Power Curve from E22-900M33S_UserManual_EN_v1.0.pdf +#define REGULATORY_GAIN 25 +#define SX126X_MAX_POWER 8 +#endif #endif /* From 08e1c2f68122e21030879cc3cfe34a5edef54444 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 23 May 2024 23:28:07 +0800 Subject: [PATCH 0533/1377] Rename REGULATORY_GAIN to REGULATORY_GAIN_LORA to allow for other RF gain controls For example, Wi-Fi or BLE gain control (#3962) Signed-off-by: Andrew Yong --- src/configuration.h | 4 ++-- src/mesh/RadioInterface.cpp | 4 ++-- variants/xiao_ble/variant.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index eab2d0120..a8b059d2c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -83,8 +83,8 @@ along with this program. If not, see . // Total system gain in dBm to subtract from Tx power to remain within regulatory ERP limit for non-licensed operators // This value should be set in variant.h and is PA gain + antenna gain (if system ships with an antenna) -#ifndef REGULATORY_GAIN -#define REGULATORY_GAIN 0 +#ifndef REGULATORY_GAIN_LORA +#define REGULATORY_GAIN_LORA 0 #endif // ----------------------------------------------------------------------------- diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 8e77beee4..ae05ed004 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -478,8 +478,8 @@ void RadioInterface::applyModemConfig() power = loraConfig.tx_power; - if ((power == 0) || ((power + REGULATORY_GAIN > myRegion->powerLimit) && !devicestate.owner.is_licensed)) - power = myRegion->powerLimit - REGULATORY_GAIN; + if ((power == 0) || ((power + REGULATORY_GAIN_LORA > myRegion->powerLimit) && !devicestate.owner.is_licensed)) + power = myRegion->powerLimit - REGULATORY_GAIN_LORA; if (power == 0) power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of myRegion defaults diff --git a/variants/xiao_ble/variant.h b/variants/xiao_ble/variant.h index 4ca38f928..a86ddfde2 100644 --- a/variants/xiao_ble/variant.h +++ b/variants/xiao_ble/variant.h @@ -144,12 +144,12 @@ static const uint8_t SCK = PIN_SPI_SCK; #define SX126X_DIO3_TCXO_VOLTAGE 1.8 #ifdef EBYTE_E22_900M30S // 10dB PA gain and 30dB rated output; based on PA output table from Ebyte Robin -#define REGULATORY_GAIN 10 +#define REGULATORY_GAIN_LORA 10 #define SX126X_MAX_POWER 20 #endif #ifdef EBYTE_E22_900M33S // 25dB PA gain and 33dB rated output; based on TX Power Curve from E22-900M33S_UserManual_EN_v1.0.pdf -#define REGULATORY_GAIN 25 +#define REGULATORY_GAIN_LORA 25 #define SX126X_MAX_POWER 8 #endif #endif From 646b25278614ade57fd0190cfd2196dd98761832 Mon Sep 17 00:00:00 2001 From: Talie5in Date: Thu, 6 Jun 2024 22:19:40 +0930 Subject: [PATCH 0534/1377] Include PositionModule if EXCLUDE_GPS defined (requied by AdminModule) --- src/modules/AdminModule.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index c9416a9b5..3c51be7c7 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -26,6 +26,11 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" #endif + +#if MESHTASTIC_EXCLUDE_GPS +#include "modules/PositionModule.h" +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "AccelerometerThread.h" #endif From 1f9f885acaa969aca912d4ffe234b3d715edf4da Mon Sep 17 00:00:00 2001 From: Talie5in Date: Thu, 6 Jun 2024 22:31:10 +0930 Subject: [PATCH 0535/1377] If EXCUDE_MQTT Defined, skip reconnecting MQTT in WiFiAPClient and dont check status of is_mqtt_connected --- src/mesh/wifi/WiFiAPClient.cpp | 2 ++ src/modules/AdminModule.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index a259d161b..ffb16bd3e 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -108,8 +108,10 @@ static void onNetworkConnected() } // FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected' +#ifndef MESHTASTIC_EXCLUDE_MQTT if (mqtt) mqtt->reconnect(); +#endif } static int32_t reconnectWiFi() diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 3c51be7c7..091586462 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -756,7 +756,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r if (conn.wifi.status.is_connected) { conn.wifi.rssi = WiFi.RSSI(); conn.wifi.status.ip_address = WiFi.localIP(); +#ifndef MESHTASTIC_EXCLUDE_MQTT conn.wifi.status.is_mqtt_connected = mqtt && mqtt->isConnectedDirectly(); +#endif conn.wifi.status.is_syslog_connected = false; // FIXME wire this up } #endif From a5c96a29d54fc1d224c48da26ade21508e5e54eb Mon Sep 17 00:00:00 2001 From: Talie5in Date: Thu, 6 Jun 2024 22:52:11 +0930 Subject: [PATCH 0536/1377] Fix missing IFNDEF and IFDEF in main-esp32.cpp when EXCLUDE_WIFI is defined. Moved IFDEF HAS_NETWORK to beginning of MQTT:runOnce (to catch when EXCLUDE_WIFI is defined) --- src/mqtt/MQTT.cpp | 3 +- src/platform/esp32/main-esp32.cpp | 247 +++++++++++++++--------------- 2 files changed, 128 insertions(+), 122 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2e367420a..f3eda6d7c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -396,6 +396,7 @@ bool MQTT::wantsLink() const int32_t MQTT::runOnce() { +#ifdef HAS_NETWORKING if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); @@ -408,7 +409,7 @@ int32_t MQTT::runOnce() publishQueuedMessages(); return 200; } -#ifdef HAS_NETWORKING + else if (!pubSub.loop()) { if (!wantConnection) return 5000; // If we don't want connection now, check again in 5 secs diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 57f466594..3d5eb059c 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -24,121 +24,126 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { +#ifndef MESHTASTIC_EXCLUDE_WIFI if (!isWifiAvailable() && config.bluetooth.enabled == true) { - if (!nimbleBluetooth) { - nimbleBluetooth = new NimbleBluetooth(); +#endif +#ifdef MESHTASTIC_EXCLUDE_WIFI + if (config.bluetooth.enabled == true) { +#endif + if (!nimbleBluetooth) { + nimbleBluetooth = new NimbleBluetooth(); + } + if (enable && !nimbleBluetooth->isActive()) { + nimbleBluetooth->setup(); + } + // For ESP32, no way to recover from bluetooth shutdown without reboot + // BLE advertising automatically stops when MCU enters light-sleep(?) + // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse } - if (enable && !nimbleBluetooth->isActive()) { - nimbleBluetooth->setup(); - } - // For ESP32, no way to recover from bluetooth shutdown without reboot - // BLE advertising automatically stops when MCU enters light-sleep(?) - // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse } -} #else void setBluetoothEnable(bool enable) {} void updateBatteryLevel(uint8_t level) {} #endif -void getMacAddr(uint8_t *dmac) -{ - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); -} + void getMacAddr(uint8_t * dmac) + { + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); + } #ifdef HAS_32768HZ #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) -static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name) -{ - const uint32_t cal_count = 1000; - // const float factor = (1 << 19) * 1000.0f; unused var? - uint32_t cali_val; - for (int i = 0; i < 5; ++i) { - cali_val = rtc_clk_cal(cal_clk, cal_count); + static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name) + { + const uint32_t cal_count = 1000; + // const float factor = (1 << 19) * 1000.0f; unused var? + uint32_t cali_val; + for (int i = 0; i < 5; ++i) { + cali_val = rtc_clk_cal(cal_clk, cal_count); + } + return cali_val; } - return cali_val; -} -void enableSlowCLK() -{ - rtc_clk_32k_enable(true); + void enableSlowCLK() + { + rtc_clk_32k_enable(true); - CALIBRATE_ONE(RTC_CAL_RTC_MUX); - uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL); + CALIBRATE_ONE(RTC_CAL_RTC_MUX); + uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL); - if (cal_32k == 0) { - LOG_DEBUG("32K XTAL OSC has not started up\n"); - } else { - rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL); - LOG_DEBUG("Switching RTC Source to 32.768Khz succeeded, using 32K XTAL\n"); + if (cal_32k == 0) { + LOG_DEBUG("32K XTAL OSC has not started up\n"); + } else { + rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL); + LOG_DEBUG("Switching RTC Source to 32.768Khz succeeded, using 32K XTAL\n"); + CALIBRATE_ONE(RTC_CAL_RTC_MUX); + CALIBRATE_ONE(RTC_CAL_32K_XTAL); + } CALIBRATE_ONE(RTC_CAL_RTC_MUX); CALIBRATE_ONE(RTC_CAL_32K_XTAL); + if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) { + LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768Khz !!! \n"); + return; + } } - CALIBRATE_ONE(RTC_CAL_RTC_MUX); - CALIBRATE_ONE(RTC_CAL_32K_XTAL); - if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) { - LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768Khz !!! \n"); - return; - } -} #endif -void esp32Setup() -{ - uint32_t seed = esp_random(); - LOG_DEBUG("Setting random seed %u\n", seed); + void esp32Setup() + { + uint32_t seed = esp_random(); + LOG_DEBUG("Setting random seed %u\n", seed); - LOG_DEBUG("Total heap: %d\n", ESP.getHeapSize()); - LOG_DEBUG("Free heap: %d\n", ESP.getFreeHeap()); - LOG_DEBUG("Total PSRAM: %d\n", ESP.getPsramSize()); - LOG_DEBUG("Free PSRAM: %d\n", ESP.getFreePsram()); + LOG_DEBUG("Total heap: %d\n", ESP.getHeapSize()); + LOG_DEBUG("Free heap: %d\n", ESP.getFreeHeap()); + LOG_DEBUG("Total PSRAM: %d\n", ESP.getPsramSize()); + LOG_DEBUG("Free PSRAM: %d\n", ESP.getFreePsram()); - nvs_stats_t nvs_stats; - auto res = nvs_get_stats(NULL, &nvs_stats); - assert(res == ESP_OK); - LOG_DEBUG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d, NameSpaces %d\n", nvs_stats.used_entries, - nvs_stats.free_entries, nvs_stats.total_entries, nvs_stats.namespace_count); + nvs_stats_t nvs_stats; + auto res = nvs_get_stats(NULL, &nvs_stats); + assert(res == ESP_OK); + LOG_DEBUG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d, NameSpaces %d\n", nvs_stats.used_entries, + nvs_stats.free_entries, nvs_stats.total_entries, nvs_stats.namespace_count); - LOG_DEBUG("Setup Preferences in Flash Storage\n"); + LOG_DEBUG("Setup Preferences in Flash Storage\n"); - // Create object to store our persistent data - Preferences preferences; - preferences.begin("meshtastic", false); + // Create object to store our persistent data + Preferences preferences; + preferences.begin("meshtastic", false); - uint32_t rebootCounter = preferences.getUInt("rebootCounter", 0); - rebootCounter++; - preferences.putUInt("rebootCounter", rebootCounter); - preferences.end(); - LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); + uint32_t rebootCounter = preferences.getUInt("rebootCounter", 0); + rebootCounter++; + preferences.putUInt("rebootCounter", rebootCounter); + preferences.end(); + LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); #if !MESHTASTIC_EXCLUDE_BLUETOOTH - String BLEOTA = BleOta::getOtaAppVersion(); - if (BLEOTA.isEmpty()) { - LOG_DEBUG("No OTA firmware available\n"); - } else { - LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); - } + String BLEOTA = BleOta::getOtaAppVersion(); + if (BLEOTA.isEmpty()) { + LOG_DEBUG("No OTA firmware available\n"); + } else { + LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); + } #else LOG_DEBUG("No OTA firmware available\n"); #endif - // enableModemSleep(); + // enableModemSleep(); // Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any // false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now. // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 - res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); - assert(res == ESP_OK); + res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); + assert(res == ESP_OK); - res = esp_task_wdt_add(NULL); - assert(res == ESP_OK); + res = esp_task_wdt_add(NULL); + assert(res == ESP_OK); #ifdef HAS_32768HZ - enableSlowCLK(); + enableSlowCLK(); #endif -} + } #if 0 // Turn off for now @@ -160,76 +165,76 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -/// loop code specific to ESP32 targets -void esp32Loop() -{ - esp_task_wdt_reset(); // service our app level watchdog + /// loop code specific to ESP32 targets + void esp32Loop() + { + esp_task_wdt_reset(); // service our app level watchdog - // for debug printing - // radio.radioIf.canSleep(); -} + // for debug printing + // radio.radioIf.canSleep(); + } -void cpuDeepSleep(uint32_t msecToWake) -{ - /* - Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. - If an external circuit drives this pin in deep sleep mode, current consumption may - increase due to current flowing through these pullups and pulldowns. + void cpuDeepSleep(uint32_t msecToWake) + { + /* + Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. + If an external circuit drives this pin in deep sleep mode, current consumption may + increase due to current flowing through these pullups and pulldowns. - To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. - For example, on ESP32-WROVER module, GPIO12 is pulled up externally. - GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, - some current will flow through these external and internal resistors, increasing deep - sleep current above the minimal possible value. + To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. + For example, on ESP32-WROVER module, GPIO12 is pulled up externally. + GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, + some current will flow through these external and internal resistors, increasing deep + sleep current above the minimal possible value. - Note: we don't isolate pins that are used for the LORA, LED, i2c, or ST7735 Display for the Chatter2, spi or the wake - button(s), maybe we should not include any other GPIOs... - */ + Note: we don't isolate pins that are used for the LORA, LED, i2c, or ST7735 Display for the Chatter2, spi or the wake + button(s), maybe we should not include any other GPIOs... + */ #if SOC_RTCIO_HOLD_SUPPORTED - static const uint8_t rtcGpios[] = {/* 0, */ 2, - /* 4, */ + static const uint8_t rtcGpios[] = {/* 0, */ 2, + /* 4, */ #ifndef USE_JTAG - 13, - /* 14, */ /* 15, */ + 13, + /* 14, */ /* 15, */ #endif - /* 25, */ /* 26, */ /* 27, */ - /* 32, */ /* 33, */ 34, 35, - /* 36, */ 37 - /* 38, 39 */}; + /* 25, */ /* 26, */ /* 27, */ + /* 32, */ /* 33, */ 34, 35, + /* 36, */ 37 + /* 38, 39 */}; - for (int i = 0; i < sizeof(rtcGpios); i++) - rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); + for (int i = 0; i < sizeof(rtcGpios); i++) + rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); #endif - // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using - // to detect wake and in normal operation the external part drives them hard. + // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using + // to detect wake and in normal operation the external part drives them hard. #ifdef BUTTON_PIN - // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. + // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. #if SOC_RTCIO_HOLD_SUPPORTED - uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); + uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif #ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)BUTTON_PIN); + gpio_pullup_en((gpio_num_t)BUTTON_PIN); #endif - // Not needed because both of the current boards have external pullups - // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of - // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); + // Not needed because both of the current boards have external pullups + // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead + // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); #if SOC_PM_SUPPORT_EXT_WAKEUP #ifdef CONFIG_IDF_TARGET_ESP32 - // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target. - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); + // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target. + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); #else - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW); + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW); #endif #endif #endif - // We want RTC peripherals to stay on - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + // We want RTC peripherals to stay on + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs - esp_deep_sleep_start(); // TBD mA sleep current (battery) -} \ No newline at end of file + esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs + esp_deep_sleep_start(); // TBD mA sleep current (battery) + } \ No newline at end of file From d09da9678092f56908ee3ebce8fb80b62b96ba2d Mon Sep 17 00:00:00 2001 From: Talie5in Date: Thu, 6 Jun 2024 23:57:44 +0930 Subject: [PATCH 0537/1377] Fix indentation oopsie --- src/platform/esp32/main-esp32.cpp | 231 +++++++++++++++--------------- 1 file changed, 116 insertions(+), 115 deletions(-) diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 3d5eb059c..1dd7a389a 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -25,11 +25,12 @@ void setBluetoothEnable(bool enable) { #ifndef MESHTASTIC_EXCLUDE_WIFI - if (!isWifiAvailable() && config.bluetooth.enabled == true) { + if (!isWifiAvailable() && config.bluetooth.enabled == true) #endif #ifdef MESHTASTIC_EXCLUDE_WIFI - if (config.bluetooth.enabled == true) { + if (config.bluetooth.enabled == true) #endif + { if (!nimbleBluetooth) { nimbleBluetooth = new NimbleBluetooth(); } @@ -40,110 +41,110 @@ void setBluetoothEnable(bool enable) // BLE advertising automatically stops when MCU enters light-sleep(?) // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse } - } +} #else void setBluetoothEnable(bool enable) {} void updateBatteryLevel(uint8_t level) {} #endif - void getMacAddr(uint8_t * dmac) - { - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); - } +void getMacAddr(uint8_t *dmac) +{ + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); +} #ifdef HAS_32768HZ #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) - static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name) - { - const uint32_t cal_count = 1000; - // const float factor = (1 << 19) * 1000.0f; unused var? - uint32_t cali_val; - for (int i = 0; i < 5; ++i) { - cali_val = rtc_clk_cal(cal_clk, cal_count); - } - return cali_val; +static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name) +{ + const uint32_t cal_count = 1000; + // const float factor = (1 << 19) * 1000.0f; unused var? + uint32_t cali_val; + for (int i = 0; i < 5; ++i) { + cali_val = rtc_clk_cal(cal_clk, cal_count); } + return cali_val; +} - void enableSlowCLK() - { - rtc_clk_32k_enable(true); +void enableSlowCLK() +{ + rtc_clk_32k_enable(true); - CALIBRATE_ONE(RTC_CAL_RTC_MUX); - uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL); + CALIBRATE_ONE(RTC_CAL_RTC_MUX); + uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL); - if (cal_32k == 0) { - LOG_DEBUG("32K XTAL OSC has not started up\n"); - } else { - rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL); - LOG_DEBUG("Switching RTC Source to 32.768Khz succeeded, using 32K XTAL\n"); - CALIBRATE_ONE(RTC_CAL_RTC_MUX); - CALIBRATE_ONE(RTC_CAL_32K_XTAL); - } + if (cal_32k == 0) { + LOG_DEBUG("32K XTAL OSC has not started up\n"); + } else { + rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL); + LOG_DEBUG("Switching RTC Source to 32.768Khz succeeded, using 32K XTAL\n"); CALIBRATE_ONE(RTC_CAL_RTC_MUX); CALIBRATE_ONE(RTC_CAL_32K_XTAL); - if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) { - LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768Khz !!! \n"); - return; - } } + CALIBRATE_ONE(RTC_CAL_RTC_MUX); + CALIBRATE_ONE(RTC_CAL_32K_XTAL); + if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) { + LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768Khz !!! \n"); + return; + } +} #endif - void esp32Setup() - { - uint32_t seed = esp_random(); - LOG_DEBUG("Setting random seed %u\n", seed); +void esp32Setup() +{ + uint32_t seed = esp_random(); + LOG_DEBUG("Setting random seed %u\n", seed); - LOG_DEBUG("Total heap: %d\n", ESP.getHeapSize()); - LOG_DEBUG("Free heap: %d\n", ESP.getFreeHeap()); - LOG_DEBUG("Total PSRAM: %d\n", ESP.getPsramSize()); - LOG_DEBUG("Free PSRAM: %d\n", ESP.getFreePsram()); + LOG_DEBUG("Total heap: %d\n", ESP.getHeapSize()); + LOG_DEBUG("Free heap: %d\n", ESP.getFreeHeap()); + LOG_DEBUG("Total PSRAM: %d\n", ESP.getPsramSize()); + LOG_DEBUG("Free PSRAM: %d\n", ESP.getFreePsram()); - nvs_stats_t nvs_stats; - auto res = nvs_get_stats(NULL, &nvs_stats); - assert(res == ESP_OK); - LOG_DEBUG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d, NameSpaces %d\n", nvs_stats.used_entries, - nvs_stats.free_entries, nvs_stats.total_entries, nvs_stats.namespace_count); + nvs_stats_t nvs_stats; + auto res = nvs_get_stats(NULL, &nvs_stats); + assert(res == ESP_OK); + LOG_DEBUG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d, NameSpaces %d\n", nvs_stats.used_entries, + nvs_stats.free_entries, nvs_stats.total_entries, nvs_stats.namespace_count); - LOG_DEBUG("Setup Preferences in Flash Storage\n"); + LOG_DEBUG("Setup Preferences in Flash Storage\n"); - // Create object to store our persistent data - Preferences preferences; - preferences.begin("meshtastic", false); + // Create object to store our persistent data + Preferences preferences; + preferences.begin("meshtastic", false); - uint32_t rebootCounter = preferences.getUInt("rebootCounter", 0); - rebootCounter++; - preferences.putUInt("rebootCounter", rebootCounter); - preferences.end(); - LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); + uint32_t rebootCounter = preferences.getUInt("rebootCounter", 0); + rebootCounter++; + preferences.putUInt("rebootCounter", rebootCounter); + preferences.end(); + LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); #if !MESHTASTIC_EXCLUDE_BLUETOOTH - String BLEOTA = BleOta::getOtaAppVersion(); - if (BLEOTA.isEmpty()) { - LOG_DEBUG("No OTA firmware available\n"); - } else { - LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); - } + String BLEOTA = BleOta::getOtaAppVersion(); + if (BLEOTA.isEmpty()) { + LOG_DEBUG("No OTA firmware available\n"); + } else { + LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); + } #else LOG_DEBUG("No OTA firmware available\n"); #endif - // enableModemSleep(); + // enableModemSleep(); // Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any // false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now. // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 - res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); - assert(res == ESP_OK); + res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); + assert(res == ESP_OK); - res = esp_task_wdt_add(NULL); - assert(res == ESP_OK); + res = esp_task_wdt_add(NULL); + assert(res == ESP_OK); #ifdef HAS_32768HZ - enableSlowCLK(); + enableSlowCLK(); #endif - } +} #if 0 // Turn off for now @@ -165,76 +166,76 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif - /// loop code specific to ESP32 targets - void esp32Loop() - { - esp_task_wdt_reset(); // service our app level watchdog +/// loop code specific to ESP32 targets +void esp32Loop() +{ + esp_task_wdt_reset(); // service our app level watchdog - // for debug printing - // radio.radioIf.canSleep(); - } + // for debug printing + // radio.radioIf.canSleep(); +} - void cpuDeepSleep(uint32_t msecToWake) - { - /* - Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. - If an external circuit drives this pin in deep sleep mode, current consumption may - increase due to current flowing through these pullups and pulldowns. +void cpuDeepSleep(uint32_t msecToWake) +{ + /* + Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. + If an external circuit drives this pin in deep sleep mode, current consumption may + increase due to current flowing through these pullups and pulldowns. - To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. - For example, on ESP32-WROVER module, GPIO12 is pulled up externally. - GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, - some current will flow through these external and internal resistors, increasing deep - sleep current above the minimal possible value. + To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. + For example, on ESP32-WROVER module, GPIO12 is pulled up externally. + GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, + some current will flow through these external and internal resistors, increasing deep + sleep current above the minimal possible value. - Note: we don't isolate pins that are used for the LORA, LED, i2c, or ST7735 Display for the Chatter2, spi or the wake - button(s), maybe we should not include any other GPIOs... - */ + Note: we don't isolate pins that are used for the LORA, LED, i2c, or ST7735 Display for the Chatter2, spi or the wake + button(s), maybe we should not include any other GPIOs... + */ #if SOC_RTCIO_HOLD_SUPPORTED - static const uint8_t rtcGpios[] = {/* 0, */ 2, - /* 4, */ + static const uint8_t rtcGpios[] = {/* 0, */ 2, + /* 4, */ #ifndef USE_JTAG - 13, - /* 14, */ /* 15, */ + 13, + /* 14, */ /* 15, */ #endif - /* 25, */ /* 26, */ /* 27, */ - /* 32, */ /* 33, */ 34, 35, - /* 36, */ 37 - /* 38, 39 */}; + /* 25, */ /* 26, */ /* 27, */ + /* 32, */ /* 33, */ 34, 35, + /* 36, */ 37 + /* 38, 39 */}; - for (int i = 0; i < sizeof(rtcGpios); i++) - rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); + for (int i = 0; i < sizeof(rtcGpios); i++) + rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); #endif - // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using - // to detect wake and in normal operation the external part drives them hard. + // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using + // to detect wake and in normal operation the external part drives them hard. #ifdef BUTTON_PIN - // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. + // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. #if SOC_RTCIO_HOLD_SUPPORTED - uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); + uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif #ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)BUTTON_PIN); + gpio_pullup_en((gpio_num_t)BUTTON_PIN); #endif - // Not needed because both of the current boards have external pullups - // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead - // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); + // Not needed because both of the current boards have external pullups + // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead + // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); #if SOC_PM_SUPPORT_EXT_WAKEUP #ifdef CONFIG_IDF_TARGET_ESP32 - // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target. - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); + // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target. + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); #else - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW); + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW); #endif #endif #endif - // We want RTC peripherals to stay on - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + // We want RTC peripherals to stay on + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs - esp_deep_sleep_start(); // TBD mA sleep current (battery) - } \ No newline at end of file + esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs + esp_deep_sleep_start(); // TBD mA sleep current (battery) +} \ No newline at end of file From 338244de32686c7d1f882e92d8785e76e99fa054 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 8 Jun 2024 00:28:29 +1200 Subject: [PATCH 0538/1377] Wake screen on first press (#4052) --- src/ButtonThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index aaead62be..7e678d69d 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -232,10 +232,10 @@ void ButtonThread::attachButtonInterrupts() attachInterrupt( config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, []() { - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); ButtonThread::userButton.tick(); runASAP = true; + BaseType_t higherWake = 0; + mainDelay.interruptFromISR(&higherWake); }, CHANGE); #endif From a52db85ebe0a2b7aec1da710083cabf0c3542b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 7 Jun 2024 15:36:31 +0200 Subject: [PATCH 0539/1377] fix base setup --- .github/actions/setup-base/action.yml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 7e57f6a31..b5b4cb6f3 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -11,23 +11,11 @@ runs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Install cppcheck + - name: Install dependencies shell: bash run: | - sudo apt-get install -y cppcheck - - - name: Install libbluetooth - shell: bash - run: | - sudo apt-get install -y libbluetooth-dev - - name: Install libgpiod - shell: bash - run: | - sudo apt-get install -y libgpiod-dev - - name: Install libyaml-cpp - shell: bash - run: | - sudo apt-get install -y libyaml-cpp-dev + sudo apt-get -y update + sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev - name: Setup Python uses: actions/setup-python@v5 From 355c6108247479dba405c8141f74b8fe69c0f426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 7 Jun 2024 15:31:53 +0200 Subject: [PATCH 0540/1377] Compile without toolchain patching --- variants/xiao_ble/nrf52840_s140_v7.ld | 38 + variants/xiao_ble/platformio.ini | 3 +- variants/xiao_ble/softdevice/ble.h | 652 ++++ variants/xiao_ble/softdevice/ble_err.h | 92 + variants/xiao_ble/softdevice/ble_gap.h | 2895 ++++++++++++++++++ variants/xiao_ble/softdevice/ble_gatt.h | 232 ++ variants/xiao_ble/softdevice/ble_gattc.h | 764 +++++ variants/xiao_ble/softdevice/ble_gatts.h | 904 ++++++ variants/xiao_ble/softdevice/ble_hci.h | 135 + variants/xiao_ble/softdevice/ble_l2cap.h | 495 +++ variants/xiao_ble/softdevice/ble_ranges.h | 149 + variants/xiao_ble/softdevice/ble_types.h | 217 ++ variants/xiao_ble/softdevice/nrf52/nrf_mbr.h | 259 ++ variants/xiao_ble/softdevice/nrf_error.h | 90 + variants/xiao_ble/softdevice/nrf_error_sdm.h | 73 + variants/xiao_ble/softdevice/nrf_error_soc.h | 85 + variants/xiao_ble/softdevice/nrf_nvic.h | 449 +++ variants/xiao_ble/softdevice/nrf_sdm.h | 380 +++ variants/xiao_ble/softdevice/nrf_soc.h | 1046 +++++++ variants/xiao_ble/softdevice/nrf_svc.h | 98 + 20 files changed, 9055 insertions(+), 1 deletion(-) create mode 100644 variants/xiao_ble/nrf52840_s140_v7.ld create mode 100644 variants/xiao_ble/softdevice/ble.h create mode 100644 variants/xiao_ble/softdevice/ble_err.h create mode 100644 variants/xiao_ble/softdevice/ble_gap.h create mode 100644 variants/xiao_ble/softdevice/ble_gatt.h create mode 100644 variants/xiao_ble/softdevice/ble_gattc.h create mode 100644 variants/xiao_ble/softdevice/ble_gatts.h create mode 100644 variants/xiao_ble/softdevice/ble_hci.h create mode 100644 variants/xiao_ble/softdevice/ble_l2cap.h create mode 100644 variants/xiao_ble/softdevice/ble_ranges.h create mode 100644 variants/xiao_ble/softdevice/ble_types.h create mode 100644 variants/xiao_ble/softdevice/nrf52/nrf_mbr.h create mode 100644 variants/xiao_ble/softdevice/nrf_error.h create mode 100644 variants/xiao_ble/softdevice/nrf_error_sdm.h create mode 100644 variants/xiao_ble/softdevice/nrf_error_soc.h create mode 100644 variants/xiao_ble/softdevice/nrf_nvic.h create mode 100644 variants/xiao_ble/softdevice/nrf_sdm.h create mode 100644 variants/xiao_ble/softdevice/nrf_soc.h create mode 100644 variants/xiao_ble/softdevice/nrf_svc.h diff --git a/variants/xiao_ble/nrf52840_s140_v7.ld b/variants/xiao_ble/nrf52840_s140_v7.ld new file mode 100644 index 000000000..6aaeb4034 --- /dev/null +++ b/variants/xiao_ble/nrf52840_s140_v7.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 8e9a663a9..60e7cecbd 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -3,8 +3,9 @@ extends = nrf52840_base board = xiao_ble_sense board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Dxiao_ble -D EBYTE_E22 +build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Ivariants/xiao_ble/softdevice -Ivariants/xiao_ble/softdevice/nrf52 -D EBYTE_E22 -DPRIVATE_HW -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" +board_build.ldscript = variants/xiao_ble/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/xiao_ble> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/xiao_ble/softdevice/ble.h b/variants/xiao_ble/softdevice/ble.h new file mode 100644 index 000000000..177b436ad --- /dev/null +++ b/variants/xiao_ble/softdevice/ble.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON BLE SoftDevice Common + @{ + @defgroup ble_api Events, type definitions and API calls + @{ + + @brief Module independent events, type definitions and API calls for the BLE SoftDevice. + + */ + +#ifndef BLE_H__ +#define BLE_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_l2cap.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief Common API SVC numbers. + */ +enum BLE_COMMON_SVCS { + SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ + SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ + SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ + SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ + SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ + SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ + SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ + SD_BLE_OPT_SET, /**< Set a BLE option. */ + SD_BLE_OPT_GET, /**< Get a BLE option. */ + SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ + SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ +}; + +/** + * @brief BLE Module Independent Event IDs. + */ +enum BLE_COMMON_EVTS { + BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t + \n Reply with @ref sd_ble_user_mem_reply. */ + BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ +}; + +/**@brief BLE Connection Configuration IDs. + * + * IDs that uniquely identify a connection configuration. + */ +enum BLE_CONN_CFGS { + BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ + BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ + BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ + BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ + BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ +}; + +/**@brief BLE Common Configuration IDs. + * + * IDs that uniquely identify a common configuration. + */ +enum BLE_COMMON_CFGS { + BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ +}; + +/**@brief Common Option IDs. + * IDs that uniquely identify a common option. + */ +enum BLE_COMMON_OPTS { + BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ + BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ + BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ +}; + +/** @} */ + +/** @addtogroup BLE_COMMON_DEFINES Defines + * @{ */ + +/** @brief Required pointer alignment for BLE Events. + */ +#define BLE_EVT_PTR_ALIGNMENT 4 + +/** @brief Leaves the maximum of the two arguments. + */ +#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +/** @brief Maximum possible length for BLE Events. + * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a + * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. + */ +#define BLE_EVT_LEN_MAX(ATT_MTU) \ + (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) + +/** @defgroup BLE_USER_MEM_TYPES User Memory Types + * @{ */ +#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ +#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ +/** @} */ + +/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts + * @{ + */ +#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ +#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ +/** @} */ + +/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. + * @{ + */ +#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ + +/** @} */ + +/** @} */ + +/** @addtogroup BLE_COMMON_STRUCTURES Structures + * @{ */ + +/**@brief User Memory Block. */ +typedef struct { + uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ + uint16_t len; /**< Length in bytes of the user memory block. */ +} ble_user_mem_block_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ +} ble_evt_user_mem_request_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ + ble_user_mem_block_t mem_block; /**< User memory block */ +} ble_evt_user_mem_release_t; + +/**@brief Event structure for events not associated with a specific function module. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ + union { + ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ + ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ + } params; /**< Event parameter union. */ +} ble_common_evt_t; + +/**@brief BLE Event header. */ +typedef struct { + uint16_t evt_id; /**< Value from a BLE__EVT series. */ + uint16_t evt_len; /**< Length in octets including this header. */ +} ble_evt_hdr_t; + +/**@brief Common BLE Event type, wrapping the module specific event reports. */ +typedef struct { + ble_evt_hdr_t header; /**< Event header. */ + union { + ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ + ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ + ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ + ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ + ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ + } evt; /**< Event union. */ +} ble_evt_t; + +/** + * @brief Version Information. + */ +typedef struct { + uint8_t version_number; /**< Link Layer Version number. See + https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ + uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) + (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ + uint16_t + subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ +} ble_version_t; + +/** + * @brief Configuration parameters for the PA and LNA. + */ +typedef struct { + uint8_t enable : 1; /**< Enable toggling for this amplifier */ + uint8_t active_high : 1; /**< Set the pin to be active high */ + uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ +} ble_pa_lna_cfg_t; + +/** + * @brief PA & LNA GPIO toggle configuration + * + * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or + * a low noise amplifier. + * + * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided + * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences + * and must be avoided by the application. + */ +typedef struct { + ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ + ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ + + uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ + uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ + uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ +} ble_common_opt_pa_lna_t; + +/** + * @brief Configuration of extended BLE connection events. + * + * When enabled the SoftDevice will dynamically extend the connection event when possible. + * + * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. + * The connection event can be extended if there is time to send another packet pair before the start of the next connection + * interval, and if there are no conflicts with other BLE roles requesting radio time. + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ +} ble_common_opt_conn_evt_ext_t; + +/** + * @brief Enable/disable extended RC calibration. + * + * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice + * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets + * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. + * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When + * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the + * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. + * + * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the + * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ +} ble_common_opt_extended_rc_cal_t; + +/**@brief Option structure for common options. */ +typedef union { + ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ + ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ + ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ +} ble_common_opt_t; + +/**@brief Common BLE Option type, wrapping the module specific options. */ +typedef union { + ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ + ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ + ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ +} ble_opt_t; + +/**@brief BLE connection configuration type, wrapping the module specific configurations, set with + * @ref sd_ble_cfg_set. + * + * @note Connection configurations don't have to be set. + * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, + * the default connection configuration will be automatically added for the remaining connections. + * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in + * place of @ref ble_conn_cfg_t::conn_cfg_tag. + * + * @sa sd_ble_gap_adv_start() + * @sa sd_ble_gap_connect() + * + * @mscs + * @mmsc{@ref BLE_CONN_CFG} + * @endmscs + + */ +typedef struct { + uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the + @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls + to select this configuration when creating a connection. + Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ + union { + ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ + ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ + ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ + ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ + ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ + } params; /**< Connection configuration union. */ +} ble_conn_cfg_t; + +/** + * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. + */ +typedef struct { + uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. + Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is + @ref BLE_UUID_VS_COUNT_MAX. */ +} ble_common_cfg_vs_uuid_t; + +/**@brief Common BLE Configuration type, wrapping the common configurations. */ +typedef union { + ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ +} ble_common_cfg_t; + +/**@brief BLE Configuration type, wrapping the module specific configurations. */ +typedef union { + ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ + ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ + ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ + ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ +} ble_cfg_t; + +/** @} */ + +/** @addtogroup BLE_COMMON_FUNCTIONS Functions + * @{ */ + +/**@brief Enable the BLE stack + * + * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the + * application RAM region (APP_RAM_BASE). On return, this will + * contain the minimum start address of the application RAM region + * required by the SoftDevice for this configuration. + * @warning After this call, the SoftDevice may generate several events. The list of events provided + * below require the application to initiate a SoftDevice API call. The corresponding API call + * is referenced in the event documentation. + * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop + * communicating with the peer device. + * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST + * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST + * - @ref BLE_GAP_EVT_SEC_REQUEST + * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST + * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST + * - @ref BLE_EVT_USER_MEM_REQUEST + * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located + * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between + * APP_RAM_BASE and the start of the call stack. + * + * @details This call initializes the BLE stack, no BLE related function other than @ref + * sd_ble_cfg_set can be called before this one. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: + * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not + * large enough to fit this configuration's memory requirement. Check *p_app_ram_base + * and set the start address of the application RAM region accordingly. + * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which + * is currently not supported. + * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. + */ +SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); + +/**@brief Add configurations for the BLE stack + * + * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref + * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. + * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. + * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). + * See @ref sd_ble_enable for details about APP_RAM_BASE. + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note If a configuration is set more than once, the last one set is the one that takes effect on + * @ref sd_ble_enable. + * + * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default + * configuration. + * + * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref + * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref + * sd_ble_enable). + * + * @note Error codes for the configurations are described in the configuration structs. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The configuration has been added successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. + * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not + * large enough to fit this configuration's memory requirement. + */ +SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); + +/**@brief Get an event from the pending events queue. + * + * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. + * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. + * The buffer should be interpreted as a @ref ble_evt_t struct. + * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. + * + * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that + * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. + * The application is free to choose whether to call this function from thread mode (main context) or directly from the + * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher + * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) + * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so + * could potentially leave events in the internal queue without the application being aware of this fact. + * + * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to + * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, + * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. + * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length + * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: + * + * \code + * uint16_t len; + * errcode = sd_ble_evt_get(NULL, &len); + * \endcode + * + * @mscs + * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} + * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. + * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. + */ +SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); + +/**@brief Add a Vendor Specific base UUID. + * + * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later + * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t + * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code + * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses + * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to + * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field + * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to + * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, + * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. + * + * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by + * the 16-bit uuid field in @ref ble_uuid_t. + * + * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in + * p_uuid_type along with an @ref NRF_SUCCESS error code. + * + * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding + * bytes 12 and 13. + * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be + * stored. + * + * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. + * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. + */ +SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); + +/**@brief Remove a Vendor Specific base UUID. + * + * @details This call removes a Vendor Specific base UUID. This function allows + * the application to reuse memory allocated for Vendor Specific base UUIDs. + * + * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID + * type. + * + * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. + * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific + * base UUID will be removed. If the function returns successfully, the UUID type that was removed will + * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that + * is in use by the ATT Server. + * + * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. + * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. + */ +SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); + +/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. + * + * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared + * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs + * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index + * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. + * + * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. + * + * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). + * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. + * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. + * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. + */ +SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); + +/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). + * + * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is + * computed. + * + * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. + * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). + * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. + * + * @retval ::NRF_SUCCESS Successfully encoded into the buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. + */ +SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); + +/**@brief Get Version Information. + * + * @details This call allows the application to get the BLE stack version information. + * + * @param[out] p_version Pointer to a ble_version_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Version information stored successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). + */ +SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); + +/**@brief Provide a user memory block. + * + * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. + */ +SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); + +/**@brief Set a BLE option. + * + * @details This call allows the application to set the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. + * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. + * + * @retval ::NRF_SUCCESS Option set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + */ +SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); + +/**@brief Get a BLE option. + * + * @details This call allows the application to retrieve the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. + * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Option retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. + * + */ +SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); + +/** @} */ +#ifdef __cplusplus +} +#endif +#endif /* BLE_H__ */ + +/** + @} + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_err.h b/variants/xiao_ble/softdevice/ble_err.h new file mode 100644 index 000000000..d20f6d141 --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_err.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @addtogroup nrf_error + @{ + @ingroup BLE_COMMON + @} + + @defgroup ble_err General error codes + @{ + + @brief General error code definitions for the BLE API. + + @ingroup BLE_COMMON +*/ +#ifndef NRF_BLE_ERR_H__ +#define NRF_BLE_ERR_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* @defgroup BLE_ERRORS Error Codes + * @{ */ +#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ +#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ +#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ +#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ +#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ +#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ + (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ +/** @} */ + +/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges + * @brief Assignment of subranges for module specific error codes. + * @note For specific error codes, see ble_.h or ble_error_.h. + * @{ */ +#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ +#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ +#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ +#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif + +/** + @} + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_gap.h b/variants/xiao_ble/softdevice/ble_gap.h new file mode 100644 index 000000000..8ebdfa82b --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_gap.h @@ -0,0 +1,2895 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GAP Generic Access Profile (GAP) + @{ + @brief Definitions and prototypes for the GAP interface. + */ + +#ifndef BLE_GAP_H__ +#define BLE_GAP_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GAP API SVC numbers. + */ +enum BLE_GAP_SVCS { + SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ + SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ + SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ + SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ + SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ + SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ + SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ + SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ + SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ + SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ + SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ + SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ + SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ + SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ + SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ + SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ + SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ + SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ + SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ + SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ + SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ + SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ + SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ + SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ + SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ + SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ + SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ + SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ + SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ + SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ + SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ + SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ + SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ + SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ + SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ + SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ + SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ + SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = + BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ +}; + +/**@brief GAP Event IDs. + * IDs that uniquely identify an event coming from the stack to the application. + */ +enum BLE_GAP_EVTS { + BLE_GAP_EVT_CONNECTED = + BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ + BLE_GAP_EVT_DISCONNECTED = + BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE = + BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ + BLE_GAP_EVT_SEC_PARAMS_REQUEST = + BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. + \n See @ref ble_gap_evt_sec_params_request_t. */ + BLE_GAP_EVT_SEC_INFO_REQUEST = + BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. + \n See @ref ble_gap_evt_sec_info_request_t. */ + BLE_GAP_EVT_PASSKEY_DISPLAY = + BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref + sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ + BLE_GAP_EVT_KEY_PRESSED = + BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ + BLE_GAP_EVT_AUTH_KEY_REQUEST = + BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. + \n See @ref ble_gap_evt_auth_key_request_t. */ + BLE_GAP_EVT_LESC_DHKEY_REQUEST = + BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref + sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ + BLE_GAP_EVT_AUTH_STATUS = + BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ + BLE_GAP_EVT_CONN_SEC_UPDATE = + BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ + BLE_GAP_EVT_TIMEOUT = + BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ + BLE_GAP_EVT_RSSI_CHANGED = + BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ + BLE_GAP_EVT_ADV_REPORT = + BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ + BLE_GAP_EVT_SEC_REQUEST = + BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate +\n or with @ref sd_ble_gap_encrypt if required security information is available +. \n See @ref ble_gap_evt_sec_request_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref + sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ + BLE_GAP_EVT_SCAN_REQ_REPORT = + BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ + BLE_GAP_EVT_PHY_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n + See @ref ble_gap_evt_phy_update_request_t. */ + BLE_GAP_EVT_PHY_UPDATE = + BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref + sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE = + BLE_GAP_EVT_BASE + + 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ + BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = + BLE_GAP_EVT_BASE + + 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ + BLE_GAP_EVT_ADV_SET_TERMINATED = + BLE_GAP_EVT_BASE + + 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ +}; + +/**@brief GAP Option IDs. + * IDs that uniquely identify a GAP option. + */ +enum BLE_GAP_OPTS { + BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ + BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ + BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ + BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ + BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = + BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ + BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = + BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ +}; + +/**@brief GAP Configuration IDs. + * + * IDs that uniquely identify a GAP configuration. + */ +enum BLE_GAP_CFGS { + BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ + BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ + BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic + inclusion configuration. */ + BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic + inclusion configuration. */ +}; + +/**@brief GAP TX Power roles. + */ +enum BLE_GAP_TX_POWER_ROLES { + BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ + BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ + BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ +}; + +/** @} */ + +/**@addtogroup BLE_GAP_DEFINES Defines + * @{ */ + +/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP + * @{ */ +#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ + (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ +#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ + (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ +#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ + (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ +#define BLE_ERROR_GAP_WHITELIST_IN_USE \ + (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ + (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ + (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLES GAP Roles + * @{ */ +#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ +#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ +#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ +/**@} */ + +/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources + * @{ */ +#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ +#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ +#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ +/**@} */ + +/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types + * @{ */ +#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ +#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ +#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ + 0x7F /**< An advertiser may advertise without its address. \ + This type of advertising is called anonymous. */ +/**@} */ + +/**@brief The default interval in seconds at which a private address is refreshed. */ +#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ +/**@brief The maximum interval in seconds at which a private address can be refreshed. */ +#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ + +/** @brief BLE address length. */ +#define BLE_GAP_ADDR_LEN (6) + +/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes + * @{ */ +#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ +#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ +#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ + 0x02 /**< Device will send and accept only private addresses for its own address, \ + and will not accept a peer using identity address as sender address when \ + the peer IRK is exchanged, non-zero and added to the identity list. */ +/**@} */ + +/** @brief Invalid power level. */ +#define BLE_GAP_POWER_LEVEL_INVALID 127 + +/** @brief Advertising set handle not set. */ +#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) + +/** @brief The default number of advertising sets. */ +#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) + +/** @brief The maximum number of advertising sets supported by this SoftDevice. */ +#define BLE_GAP_ADV_SET_COUNT_MAX (1) + +/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. + * @{ */ +#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ + (31) /**< Maximum data length for an advertising set. \ + If more advertising data is required, use extended advertising instead. */ +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for an extended advertising set. */ + +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ + (238) /**< Maximum supported data length for an extended connectable advertising set. */ +/**@}. */ + +/** @brief Set ID not available in advertising report. */ +#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF + +/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons + * @{ */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ +/**@} */ + +/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format + * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm + * @{ */ +#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ +#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ +#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ +#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ +#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ +#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ +#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ +#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ +#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ +#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ +#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ +#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ +#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ +#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ +#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ +#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags + * @{ */ +#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min + * @{ */ +#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ +#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ + /**@} */ + +/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min + * @{ */ +#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min + * @{ */ +#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min + * @{ */ +#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ +#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size + * + * Scan buffers are used for storing advertising data received from an advertiser. + * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. + * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. + * @{ */ +#define BLE_GAP_SCAN_BUFFER_MIN \ + (31) /**< Minimum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_MAX \ + (31) /**< Maximum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ + (255) /**< Minimum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ + (1650) /**< Maximum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for \ + an extended advertising set. */ +/** @} */ + +/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types + * + * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. + * + * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. + * The maximum supported data length for an extended advertiser is defined by + * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED + * Note that some of the advertising types do not support advertising data. Non-scannable types do not support + * scan response data. + * + * @{ */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x01 /**< Connectable and scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ + 0x02 /**< Connectable non-scannable directed advertising \ + events. Advertising interval is less that 3.75 ms. \ + Use this type for fast reconnections. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x03 /**< Connectable non-scannable directed advertising \ + events. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x04 /**< Non-connectable scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x05 /**< Non-connectable non-scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x06 /**< Connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x07 /**< Connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x08 /**< Non-connectable scannable undirected advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ + 0x09 /**< Non-connectable scannable directed advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x0A /**< Non-connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x0B /**< Non-connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies + * @{ */ +#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ +#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status + * @{ */ +#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ + 0x01 /**< More data to be received. \ + @note This value will only be used if \ + @ref ble_gap_scan_params_t::report_incomplete_evts and \ + @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ + 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ + 0x03 /**< Failed to receive the remaining data. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +/**@} */ + +/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies + * @{ */ +#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ + 0x00 /**< Accept all advertising packets except directed advertising packets \ + not addressed to this device. */ +#define BLE_GAP_SCAN_FP_WHITELIST \ + 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ + packets not addressed to this device. */ +#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ + 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ + 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units + * @{ */ +#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ + (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ + */ +#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ + (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ + mode. */ +#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ + (0) /**< Unlimited advertising in general discoverable mode. \ + For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ +/**@} */ + +/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes + * @{ */ +#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ +#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ +#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ +/**@} */ + +/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities + * @{ */ +#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ +#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ +#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ +/**@} */ + +/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types + * @{ */ +#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ +#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ +#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ +/**@} */ + +/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types + * @{ */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS GAP Security status + * @{ */ +#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ +#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ +#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ +#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ +#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ +#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ +#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ +#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ +#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ +#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ +#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ +#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ +#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ +#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ +#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ +#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ +#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources + * @{ */ +#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ +#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ +/**@} */ + +/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits + * @{ */ +#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ + 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ + 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ + 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ + 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ +/**@} */ + +/**@defgroup BLE_GAP_DEVNAME GAP device name defines. + * @{ */ +#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ +#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ +#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ +/**@} */ + +/**@brief Disable RSSI events for connections */ +#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF + +/**@defgroup BLE_GAP_PHYS GAP PHYs + * @{ */ +#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ +#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ +#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ +#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ +#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ + +/**@brief Supported PHYs in connections, for scanning, and for advertising. */ +#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ + +/**@} */ + +/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters + * + * See @ref ble_gap_conn_sec_mode_t. + * @{ */ +/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ + do { \ + (ptr)->sm = 0; \ + (ptr)->lv = 0; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 2; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 3; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 4; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 2; \ + } while (0) +/**@} */ + +/**@brief GAP Security Random Number Length. */ +#define BLE_GAP_SEC_RAND_LEN 8 + +/**@brief GAP Security Key Length. */ +#define BLE_GAP_SEC_KEY_LEN 16 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ +#define BLE_GAP_LESC_P256_PK_LEN 64 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ +#define BLE_GAP_LESC_DHKEY_LEN 32 + +/**@brief GAP Passkey Length. */ +#define BLE_GAP_PASSKEY_LEN 6 + +/**@brief Maximum amount of addresses in the whitelist. */ +#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) + +/**@brief Maximum amount of identities in the device identities list. */ +#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) + +/**@brief Default connection count for a configuration. */ +#define BLE_GAP_CONN_COUNT_DEFAULT (1) + +/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. + * @{ */ +#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ +#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ +#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. + * @{ */ +#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ + (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ + (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ + +/**@} */ + +/**@brief Automatic data length parameter. */ +#define BLE_GAP_DATA_LENGTH_AUTO 0 + +/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. + * @{ */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ +/**@} */ + +/**@defgroup GAP_SEC_MODES GAP Security Modes + * @{ */ +#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ +/**@} */ + +/**@brief The total number of channels in Bluetooth Low Energy. */ +#define BLE_GAP_CHANNEL_COUNT (40) + +/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines + * @{ */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ + /**@} */ + +/** @} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations + * @{ + */ +#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ + (1) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will reserve the attribute handles \ + which are otherwise used for this characteristic. \ + By reserving the attribute handles it will be possible \ + to upgrade the SoftDevice without changing handle of the \ + Service Changed characteristic. */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ + (2) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will not reserve the attribute handles \ + which are otherwise used for this characteristic. */ +/**@} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values + * @{ */ +#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +/**@} */ + +/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options + * @{ */ +#define BLE_GAP_SLAVE_LATENCY_ENABLE \ + (0) /**< Slave latency is enabled. When slave latency is enabled, \ + the slave will wake up every time it has data to send, \ + and/or every slave latency number of connection events. */ +#define BLE_GAP_SLAVE_LATENCY_DISABLE \ + (1) /**< Disable slave latency. The slave will wake up every connection event \ + regardless of the requested slave latency. \ + This option consumes the most power. */ +#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ + (2) /**< The slave will wake up every connection event if it has not received \ + an ACK from the master for at least slave latency events. This \ + configuration may increase the power consumption in environments \ + with a lot of radio activity. */ +/**@} */ + +/**@addtogroup BLE_GAP_STRUCTURES Structures + * @{ */ + +/**@brief Advertising event properties. */ +typedef struct { + uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ + uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. + @note Anonymous advertising is only available for + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ + uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ +} ble_gap_adv_properties_t; + +/**@brief Advertising report type. */ +typedef struct { + uint16_t connectable : 1; /**< Connectable advertising event type. */ + uint16_t scannable : 1; /**< Scannable advertising event type. */ + uint16_t directed : 1; /**< Directed advertising event type. */ + uint16_t scan_response : 1; /**< Received a scan response. */ + uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ + uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ + uint16_t reserved : 9; /**< Reserved for future use. */ +} ble_gap_adv_report_type_t; + +/**@brief Advertising Auxiliary Pointer. */ +typedef struct { + uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ + uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ +} ble_gap_aux_pointer_t; + +/**@brief Bluetooth Low Energy address. */ +typedef struct { + uint8_t + addr_id_peer : 1; /**< Only valid for peer addresses. + This bit is set by the SoftDevice to indicate whether the address has been resolved from + a Resolvable Private Address (when the peer is using privacy). + If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. + + This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. + */ + uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ + uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. + @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ +} ble_gap_addr_t; + +/**@brief GAP connection parameters. + * + * @note When ble_conn_params_t is received in an event, both min_conn_interval and + * max_conn_interval will be equal to the connection interval set by the central. + * + * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: + * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval + * that corresponds to the following Bluetooth Spec requirement: + * The Supervision_Timeout in milliseconds shall be larger than + * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. + */ +typedef struct { + uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ +} ble_gap_conn_params_t; + +/**@brief GAP connection security modes. + * + * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n + * Security Mode 1 Level 1: No security is needed (aka open link).\n + * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n + * Security Mode 1 Level 3: MITM protected encrypted link required.\n + * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n + * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n + * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n + */ +typedef struct { + uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ + uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ + +} ble_gap_conn_sec_mode_t; + +/**@brief GAP connection security status.*/ +typedef struct { + ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ + uint8_t + encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ +} ble_gap_conn_sec_t; + +/**@brief Identity Resolving Key. */ +typedef struct { + uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ +} ble_gap_irk_t; + +/**@brief Channel mask (40 bits). + * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, + * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents + * channel index 39. If a bit is set to 1, the channel is not used. + */ +typedef uint8_t ble_gap_ch_mask_t[5]; + +/**@brief GAP advertising parameters. */ +typedef struct { + ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ + ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. + @note ble_gap_addr_t::addr_type cannot be + @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. + - When privacy is enabled and the local device uses + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, + the device identity list is searched for a matching entry. If + the local IRK for that device identity is set, the local IRK + for that device will be used to generate the advertiser address + field in the advertising packet. + - If @ref ble_gap_adv_properties_t::type is directed, this must be + set to the targeted scanner or initiator. If the peer address is + in the device identity list, the peer IRK for that device will be + used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE + target addresses used in the advertising event PDUs. */ + uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE + advertising, this parameter is ignored. */ + uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, + an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + @sa BLE_GAP_ADV_TIMEOUT_VALUES. + @note The SoftDevice will always complete at least one advertising + event even if the duration is set too low. */ + uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling + advertising. Setting the value to 0 disables the limitation. When + the count of advertising events specified by this parameter + (if not 0) is reached, advertising will be automatically stopped + and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, + this parameter is ignored. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be used. + Masking away secondary advertising channels is not supported. */ + uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets + are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS + will be used. + Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. + @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets + are transmitted. + If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. + Valid values are + @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. + If @ref ble_gap_adv_properties_t::type is an extended advertising type + and connectable, this is the PHY that will be used to establish a + connection and send AUX_ADV_IND packets on. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other + advertising sets transmitted by this and other devices. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a + scan request is received and the scanner address is allowed + by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is a non-scannable + advertising type. */ +} ble_gap_adv_params_t; + +/**@brief GAP advertising data buffers. + * + * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and + * shall never be modified while advertising. The data shall be kept alive until either: + * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding + * advertising handle. + * - Advertising is stopped. + * - Advertising data is changed. + * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ +typedef struct { + ble_data_t adv_data; /**< Advertising data. + @note + Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type + that is allowed to contain advertising data. */ + ble_data_t scan_rsp_data; /**< Scan response data. + @note + Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type + that is scannable. */ +} ble_gap_adv_data_t; + +/**@brief GAP scanning parameters. */ +typedef struct { + uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. + If set to 0, the scanner will not receive advertising packets + on secondary advertising channels, and will not be able + to receive long advertising PDUs. */ + uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have + @ref ble_gap_adv_report_type_t::status set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. + This parameter is ignored when used with @ref sd_ble_gap_connect + @note This may be used to abort receiving more packets from an extended + advertising event, and is only available for extended + scanning, see @ref sd_ble_gap_scan_start. + @note This feature is not supported by this SoftDevice. */ + uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. + This parameter is ignored when used with @ref sd_ble_gap_connect. */ + uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. + @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and + @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with + @ref sd_ble_gap_connect */ + uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, + scan_phys will default to @ref BLE_GAP_PHY_1MBPS. + - If @ref ble_gap_scan_params_t::extended is set to 0, the only + supported PHY is @ref BLE_GAP_PHY_1MBPS. + - When used with @ref sd_ble_gap_scan_start, + the bitfield indicates the PHYs the scanner will use for scanning + on primary advertising channels. The scanner will accept + @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. + - When used with @ref sd_ble_gap_connect, the bitfield indicates + the PHYs the initiator will use for scanning on primary advertising + channels. The initiator will accept connections initiated on either + of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. + If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, + the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. + If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan + PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is + @ref BLE_GAP_PHY_CODED, the primary scan PHY is + @ref BLE_GAP_PHY_CODED only. */ + uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ + uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. + If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and + @ref BLE_GAP_PHY_CODED interval shall be larger than or + equal to twice the scan window. */ + uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be + set to 0. + Masking away secondary channels is not supported. */ +} ble_gap_scan_params_t; + +/**@brief Privacy. + * + * The privacy feature provides a way for the device to avoid being tracked over a period of time. + * The privacy feature, when enabled, hides the local device identity and replaces it with a private address + * that is automatically refreshed at a specified interval. + * + * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). + * With this key, a device can generate a random private address that can only be recognized by peers in possession of that + * key, and devices can establish connections without revealing their real identities. + * + * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref + * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. + * + * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all + * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. + * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is + * called. + */ +typedef struct { + uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ + uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ + uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use + the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ + ble_gap_irk_t + *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device + default IRK will be used. When used as output, pointer to IRK structure where the current default IRK + will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate + random private resolvable addresses for the local device unless instructed otherwise. */ +} ble_gap_privacy_params_t; + +/**@brief PHY preferences for TX and RX + * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each + * direction. + * @code + * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * @endcode + * + */ +typedef struct { + uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ +} ble_gap_phys_t; + +/** @brief Keys that can be exchanged during a bonding procedure. */ +typedef struct { + uint8_t enc : 1; /**< Long Term Key and Master Identification. */ + uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ + uint8_t sign : 1; /**< Connection Signature Resolving Key. */ + uint8_t link : 1; /**< Derive the Link Key from the LTK. */ +} ble_gap_sec_kdist_t; + +/**@brief GAP security parameters. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ + uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ + uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ + uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ + uint8_t oob : 1; /**< The OOB data flag. + - In LE legacy pairing, this flag is set if a device has out of band authentication data. + The OOB method is used if both of the devices have out of band authentication data. + - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band + authentication data. The OOB method is used if at least one device has the peer device's OOB data + available. */ + uint8_t + min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ + uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ + ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ + ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ +} ble_gap_sec_params_t; + +/**@brief GAP Encryption Information. */ +typedef struct { + uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ + uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ + uint8_t auth : 1; /**< Authenticated Key. */ + uint8_t ltk_len : 6; /**< LTK length in octets. */ +} ble_gap_enc_info_t; + +/**@brief GAP Master Identification. */ +typedef struct { + uint16_t ediv; /**< Encrypted Diversifier. */ + uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ +} ble_gap_master_id_t; + +/**@brief GAP Signing Information. */ +typedef struct { + uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ +} ble_gap_sign_info_t; + +/**@brief GAP LE Secure Connections P-256 Public Key. */ +typedef struct { + uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the + standard SMP protocol format: {X,Y} both in little-endian. */ +} ble_gap_lesc_p256_pk_t; + +/**@brief GAP LE Secure Connections DHKey. */ +typedef struct { + uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ +} ble_gap_lesc_dhkey_t; + +/**@brief GAP LE Secure Connections OOB data. */ +typedef struct { + ble_gap_addr_t addr; /**< Bluetooth address of the device. */ + uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ + uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ +} ble_gap_lesc_oob_data_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ +typedef struct { + ble_gap_addr_t + peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ + uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ +} ble_gap_evt_connected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ +typedef struct { + uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ +} ble_gap_evt_disconnected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ +typedef struct { + ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ +} ble_gap_evt_phy_update_request_t; + +/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ +typedef struct { + uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ + uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ +} ble_gap_evt_phy_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ +typedef struct { + ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ +} ble_gap_evt_sec_params_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ +typedef struct { + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ + ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ + uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ + uint8_t id_info : 1; /**< If 1, Identity Information required. */ + uint8_t sign_info : 1; /**< If 1, Signing Information required. */ +} ble_gap_evt_sec_info_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ +typedef struct { + uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ + uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply + with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or + @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ +} ble_gap_evt_passkey_display_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ +typedef struct { + uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ +} ble_gap_evt_key_pressed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ +typedef struct { + uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ +} ble_gap_evt_auth_key_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ +typedef struct { + ble_gap_lesc_p256_pk_t + *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory + inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ + uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the + procedure. */ +} ble_gap_evt_lesc_dhkey_request_t; + +/**@brief Security levels supported. + * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. + */ +typedef struct { + uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ + uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ + uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ + uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ +} ble_gap_sec_levels_t; + +/**@brief Encryption Key. */ +typedef struct { + ble_gap_enc_info_t enc_info; /**< Encryption Information. */ + ble_gap_master_id_t master_id; /**< Master Identification. */ +} ble_gap_enc_key_t; + +/**@brief Identity Key. */ +typedef struct { + ble_gap_irk_t id_info; /**< Identity Resolving Key. */ + ble_gap_addr_t id_addr_info; /**< Identity Address. */ +} ble_gap_id_key_t; + +/**@brief Security Keys. */ +typedef struct { + ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ + ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ + ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ + ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the + value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ +} ble_gap_sec_keys_t; + +/**@brief Security key set for both local and peer keys. */ +typedef struct { + ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be + generated locally and will always be stored if bonding. */ + ble_gap_sec_keys_t + keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ +} ble_gap_sec_keyset_t; + +/**@brief Data Length Update Procedure parameters. */ +typedef struct { + uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ + uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ +} ble_gap_data_length_params_t; + +/**@brief Data Length Update Procedure local limitation. */ +typedef struct { + uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ + uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ + uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many + microseconds. */ +} ble_gap_data_length_limitation_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ +typedef struct { + uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ + uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ + uint8_t bonded : 1; /**< Procedure resulted in a bond. */ + uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ + ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ + ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ + ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding + with LE Secure Connections, the enc bit will be always set. */ + ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding + with LE Secure Connections, the enc bit will never be set. */ +} ble_gap_evt_auth_status_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ +typedef struct { + ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ +} ble_gap_evt_conn_sec_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ + union { + ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released + scan buffer is contained in this field. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ +typedef struct { + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ +} ble_gap_evt_rssi_changed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ +typedef struct { + uint8_t reason; /**< Reason for why the advertising set terminated. See + @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ + uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, + this field indicates the number of completed advertising events. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. */ +} ble_gap_evt_adv_set_terminated_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. + * + * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * not all fields in the advertising report may be available. + * + * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. + */ +typedef struct { + ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the + peer's identity address. */ + ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if + @ref ble_gap_adv_report_type_t::directed is set to 1. If the + SoftDevice was able to resolve the address, + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr + contains the local identity address. If the target address of the + advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + and the SoftDevice was unable to resolve it, the application may try + to resolve this address to find out if the advertising event was + directed to us. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. + See @ref BLE_GAP_PHYS. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. + See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets + were received on a secondary advertising channel. */ + int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. + This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the + last received packet did not contain the Tx Power field. + @note TX Power is only included in extended advertising packets. */ + int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ + uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present + if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID + is not present if @ref ble_gap_evt_adv_report_t::set_id is set to + @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + ble_data_t data; /**< Received advertising or scan response data. If + @ref ble_gap_adv_report_type_t::status is not set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided + in @ref sd_ble_gap_scan_start is now released. */ + ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising + event. @note This field is only set if @ref ble_gap_adv_report_type_t::status + is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ +} ble_gap_evt_adv_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Man In The Middle protection requested. */ + uint8_t lesc : 1; /**< LE Secure Connections requested. */ + uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ +} ble_gap_evt_sec_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ +typedef struct { + uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ +} ble_gap_evt_scan_req_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ +typedef struct { + ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ +} ble_gap_evt_data_length_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. + * + * @note This event may also be raised after a PHY Update procedure. + */ +typedef struct { + ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ +} ble_gap_evt_data_length_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ +typedef struct { + int8_t + channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy + channels, in dBm, indexed by Channel Index. + If no measurement is available for the given channel, channel_energy is set to + @ref BLE_GAP_POWER_LEVEL_INVALID. */ +} ble_gap_evt_qos_channel_survey_report_t; + +/**@brief GAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + union /**< union alternative identified by evt_id in enclosing struct. */ + { + ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ + ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ + ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ + ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ + ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ + ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ + ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ + ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ + ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ + ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ + ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ + ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ + ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ + ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ + ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ + ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ + ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ + ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ + ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ + ble_gap_evt_qos_channel_survey_report_t + qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_t; + +/** + * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. + * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. + */ +typedef struct { + uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. + The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ + uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. + The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref + BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters + for setting the throughput of a connection. + See the SoftDevice Specification for details on throughput. */ +} ble_gap_conn_cfg_t; + +/** + * @brief Configuration of maximum concurrent connections in the different connected roles, set with + * @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too + * large. The maximum supported sum of concurrent connections is + * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. + * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. + * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum + * supported advertising handles is + * @ref BLE_GAP_ADV_SET_COUNT_MAX. + */ +typedef struct { + uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ + uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref + BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ + uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref + BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ + uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is + @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ + uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to + the application using @ref sd_ble_gap_qos_channel_survey_start. */ +} ble_gap_cfg_role_count_t; + +/** + * @brief Device name and its properties, set with @ref sd_ble_cfg_set. + * + * @note If the device name is not configured, the default device name will be + * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be + * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name + * will have no write access. + * + * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, + * the attribute table size must be increased to have room for the longer device name (see + * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). + * + * @note If vloc is @ref BLE_GATTS_VLOC_STACK : + * - p_value must point to non-volatile memory (flash) or be NULL. + * - If p_value is NULL, the device name will initially be empty. + * + * @note If vloc is @ref BLE_GATTS_VLOC_USER : + * - p_value cannot be NULL. + * - If the device name is writable, p_value must point to volatile memory (RAM). + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Invalid device name location (vloc). + * - Invalid device name security mode. + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). + * - The device name length is too long for the given Attribute Table. + * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. + */ +typedef struct { + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ + uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ + uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ +} ble_gap_cfg_device_name_t; + +/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_ppcp_incl_cfg_t; + +/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_car_incl_cfg_t; + +/**@brief Configuration structure for GAP configurations. */ +typedef union { + ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ + ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ + ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include + configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ + ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, + cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ +} ble_gap_cfg_t; + +/**@brief Channel Map option. + * + * @details Used with @ref sd_ble_opt_get to get the current channel map + * or @ref sd_ble_opt_set to set a new channel map. When setting the + * channel map, it applies to all current and future connections. When getting the + * current channel map, it applies to a single connection and the connection handle + * must be supplied. + * + * @note Setting the channel map may take some time, depending on connection parameters. + * The time taken may be different for each connection and the get operation will + * return the previous channel map until the new one has taken effect. + * + * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. + * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. + * + * @retval ::NRF_SUCCESS Get or set successful. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Less then two bits in @ref ch_map are set. + * - Bits for primary advertising channels (37-39) are set. + * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. + * + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ + uint8_t ch_map[5]; /**< Channel Map (37-bit). */ +} ble_gap_opt_ch_map_t; + +/**@brief Local connection latency option. + * + * @details Local connection latency is a feature which enables the slave to improve + * current consumption by ignoring the slave latency set by the peer. The + * local connection latency can only be set to a multiple of the slave latency, + * and cannot be longer than half of the supervision timeout. + * + * @details Used with @ref sd_ble_opt_set to set the local connection latency. The + * @ref sd_ble_opt_get is not supported for this option, but the actual + * local connection latency (unless set to NULL) is set as a return parameter + * when setting the option. + * + * @note The latency set will be truncated down to the closest slave latency event + * multiple, or the nearest multiple before half of the supervision timeout. + * + * @note The local connection latency is disabled by default, and needs to be enabled for new + * connections and whenever the connection is updated. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t requested_latency; /**< Requested local connection latency. */ + uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return + value). */ +} ble_gap_opt_local_conn_latency_t; + +/**@brief Disable slave latency + * + * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection + * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the + * peripheral will ignore the slave_latency set by the central. + * + * @note Shall only be called on peripheral links. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ +} ble_gap_opt_slave_latency_disable_t; + +/**@brief Passkey Option. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @endmscs + * + * @details Structure containing the passkey to be used during pairing. This can be used with @ref + * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication + * instead of generating a random one. + * + * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + */ +typedef struct { + uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used + during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ +} ble_gap_opt_passkey_t; + +/**@brief Compatibility mode 1 option. + * + * @details This can be used with @ref sd_ble_opt_set to enable and disable + * compatibility mode 1. Compatibility mode 1 is disabled by default. + * + * @note Compatibility mode 1 enables interoperability with devices that do not support a value of + * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a + * limited set of legacy peripheral devices from another vendor. Enabling this compatibility + * mode will only have an effect if the local device will act as a central device and + * initiate a connection to a peripheral device. In that case it may lead to the connection + * creation taking up to one connection interval longer to complete for all connections. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. + */ +typedef struct { + uint8_t enable : 1; /**< Enable compatibility mode 1.*/ +} ble_gap_opt_compat_mode_1_t; + +/**@brief Authenticated payload timeout option. + * + * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other + * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. + * + * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated + * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted + * link. + * + * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance + * to reset the timer. In addition the stack will try to prioritize running of LE ping over other + * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects + * on other activities, it is recommended to use high timeout values. + * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ +} ble_gap_opt_auth_payload_timeout_t; + +/**@brief Option structure for GAP options. */ +typedef union { + ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ + ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ + ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ + ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ + ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ + ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ +} ble_gap_opt_t; + +/**@brief Connection event triggering parameters. */ +typedef struct { + uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until + connection event PPI task triggering is stopped. + The PPI channel ID can not be one of the PPI channels reserved by + the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ + uint32_t task_endpoint; /**< Task Endpoint to trigger. */ + uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ + uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. + If the device is in slave role and slave latency is enabled, + this parameter should be set to a multiple of (slave latency + 1) + to ensure low power operation. */ +} ble_gap_conn_event_trigger_t; +/**@} */ + +/**@addtogroup BLE_GAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set the local Bluetooth identity address. + * + * The local Bluetooth identity address is the address that identifies this device to other peers. + * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @note The identity address cannot be changed while advertising, scanning or creating a connection. + * + * @note This address will be distributed to the peer during bonding. + * If the address changes, the address stored in the peer device will not be valid and the ability to + * reconnect using the old address will be lost. + * + * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being + * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged + * for the lifetime of each IC. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @endmscs + * + * @param[in] p_addr Pointer to address structure. + * + * @retval ::NRF_SUCCESS Address successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, + * scanning or creating a connection. + */ +SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); + +/**@brief Get local Bluetooth identity address. + * + * @note This will always return the identity address irrespective of the privacy settings, + * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + */ +SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); + +/**@brief Get the Bluetooth device address used by the advertiser. + * + * @note This function will return the local Bluetooth address used in advertising PDUs. When + * using privacy, the SoftDevice will generate a new private address every + * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using + * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the + * address returned may not be the latest address that is used in the advertising PDUs. + * + * @param[in] adv_handle The advertising handle to get the address from. + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. + * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); + +/**@brief Set the active whitelist in the SoftDevice. + * + * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. + * The whitelist cannot be set if a BLE role is using the whitelist. + * + * @note If an address is resolved using the information in the device identity list, then the whitelist + * filter policy applies to the peer identity address and not the resolvable address sent on air. + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @endmscs + * + * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. + * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * + * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. + * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when + * pp_wl_addrs is not NULL. + */ +SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); + +/**@brief Set device identity list. + * + * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. + * The device identity list cannot be set if a BLE role is using the list. + * + * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will + * be cleared. + * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the + * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. + * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. + * This code may be returned if the local IRK list also has an invalid entry. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity + * address. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can + * only return when pp_id_keys is not NULL. + */ +SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, + sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, + uint8_t len)); + +/**@brief Set privacy settings. + * + * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. + * + * @param[in] p_privacy_params Privacy settings. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. + * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution + characteristic is not configured to be included and the SoftDevice is configured + to support central roles. + See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning + * or creating a connection. + */ +SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); + +/**@brief Get privacy settings. + * + * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. + * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. + * + * @param[in,out] p_privacy_params Privacy settings. + * + * @retval ::NRF_SUCCESS Privacy settings read. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + */ +SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); + +/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. + * + * @note The format of the advertising data will be checked by this call to ensure interoperability. + * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and + * duplicating the local name in the advertising data and scan response data. + * + * @note In order to update advertising data while advertising, new advertising buffers must be provided. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the + * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. + * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See + * @ref ble_gap_adv_data_t. + * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising + * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. + * + * @retval ::NRF_SUCCESS Advertising set successfully configured. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid advertising data configuration specified. See @ref + * ble_gap_adv_data_t. + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, + * see @ref sd_ble_gap_whitelist_set. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - It is invalid to provide non-NULL advertising set parameters while + * advertising. + * - It is invalid to provide the same data buffers while advertising. To + * update advertising data, provide new advertising buffers. + * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format + * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an + * existing advertising handle instead. + * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. + */ +SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, + sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, + ble_gap_adv_params_t const *p_adv_params)); + +/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @note Only one advertiser may be active at any time. + * + * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. + * See @ref sd_ble_gap_privacy_set(). + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} + * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} + * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable + * advertising, this is ignored. + * + * @retval ::NRF_SUCCESS The BLE stack has started advertising. + * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration + * tag has been reached; connectable advertiser cannot be started. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref + sd_ble_gap_adv_set_configure. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, see @ref + sd_ble_gap_whitelist_set. + * @retval ::NRF_ERROR_RESOURCES Either: + * - adv_handle is configured with connectable advertising, but the event_length parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + * - Not enough BLE role slots available. + Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) + and try again. + * - p_adv_params is configured with connectable advertising, but the event_length + parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); + +/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle The advertising handle that should stop advertising. + * + * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. + * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); + +/**@brief Update connection parameters. + * + * @details In the central role this will initiate a Link Layer connection parameter update procedure, + * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for + * the central to perform the procedure. In both cases, and regardless of success or failure, the application + * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. + * + * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the + * procedure unrequested. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CPU_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, + * the parameters in the PPCP characteristic of the GAP service will be used instead. + * If NULL is provided on a central role and in response to a @ref + * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected + * + * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, + sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Disconnect (GAP Link Termination). + * + * @details This call initiates the disconnection procedure, and its completion will be communicated to the application + * with a @ref BLE_GAP_EVT_DISCONNECTED event. + * + * @events + * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CONN_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref + * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). + * + * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + */ +SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); + +/**@brief Set the radio's transmit power. + * + * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for + * possible roles. + * @param[in] handle The handle parameter is interpreted depending on role: + * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. + * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, + * will use the specified transmit power, and include it in the advertising packet headers if + * @ref ble_gap_adv_properties_t::include_tx_power set. + * - For all other roles handle is ignored. + * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). + * + * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. + * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. + * Setting these values on a chip that does not support them will result in undefined behaviour. + * @note The initiator will have the same transmit power as the scanner. + * @note When a connection is created it will inherit the transmit power from the initiator or + * advertiser leading to the connection. + * + * @retval ::NRF_SUCCESS Successfully changed the transmit power. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); + +/**@brief Set GAP Appearance value. + * + * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); + +/**@brief Get GAP Appearance value. + * + * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); + +/**@brief Set GAP Peripheral Preferred Connection Parameters. + * + * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Get GAP Peripheral Preferred Connection Parameters. + * + * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); + +/**@brief Set GAP device name. + * + * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), + * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. + * + * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. + * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. + * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or + * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). + * + * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, + sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); + +/**@brief Get GAP device name. + * + * @note If the device name is longer than the size of the supplied buffer, + * p_len will return the complete device name length, + * and not the number of bytes actually returned in p_dev_name. + * The application may use this information to allocate a suitable buffer size. + * + * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to + * NULL to obtain the complete device name length. + * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. + * + * @retval ::NRF_SUCCESS GAP device name retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); + +/**@brief Initiate the GAP Authentication procedure. + * + * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), + * otherwise in the peripheral role, an SMP Security Request will be sent. + * + * @events + * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be + * generated:} + * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} + * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} + * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} + * @event{@ref BLE_GAP_EVT_KEY_PRESSED} + * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} + * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} + * @event{@ref BLE_GAP_EVT_AUTH_STATUS} + * @event{@ref BLE_GAP_EVT_TIMEOUT} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the + * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. + * In the central role, this pointer may be NULL to reject a Security Request. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - An encryption is already executing or queued. + * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is + * reached. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. + */ +SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, + sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); + +/**@brief Reply with GAP security parameters. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. + * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be + * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. + * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or + * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this + * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. + * Note that the SoftDevice expects the application to provide memory for storing the + * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The + * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application + * upon reception of the + * @ref BLE_GAP_EVT_AUTH_STATUS event. + * + * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + */ +SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, + sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, + ble_gap_sec_keyset_t const *p_sec_keyset)); + +/**@brief Reply with an authentication key. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, + * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. + * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. + * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL + * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, + * then a 16-byte OOB key value in little-endian format. + * + * @retval ::NRF_SUCCESS Authentication key successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, + sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); + +/**@brief Reply with an LE Secure connections DHKey. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dhkey LE Secure Connections DHKey. + * + * @retval ::NRF_SUCCESS DHKey successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - The peer is not authenticated. + * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, + sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); + +/**@brief Notify the peer of a local keypress. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. + * + * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested. + * - Passkey has not been entered. + * - Keypresses have not been enabled by both peers. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. + */ +SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); + +/**@brief Generate a set of OOB data to send to a peer out of band. + * + * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has + * already been established, the one used during connection setup). The application may manually overwrite it with an updated + * value. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. + * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. + * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. + * + * @retval ::NRF_SUCCESS OOB data successfully generated. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, + sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, + ble_gap_lesc_oob_data_t *p_oobd_own)); + +/**@brief Provide the OOB data sent/received out of band. + * + * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. + * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this + * function. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. + * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. + * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. + * Must correspond to @ref ble_gap_sec_params_t::oob flag + * in @ref sd_ble_gap_authenticate in the central role or + * in @ref sd_ble_gap_sec_params_reply in the peripheral role. + * + * @retval ::NRF_SUCCESS OOB data accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested + * - Not expecting LESC OOB data + * - Have not actually exchanged passkeys. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, + sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, + ble_gap_lesc_oob_data_t const *p_oobd_peer)); + +/**@brief Initiate GAP Encryption procedure. + * + * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. + * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and + * retry. + */ +SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, + sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); + +/**@brief Reply with GAP security information. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in + * @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is + * available. + * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. + * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is + * available. + * + * @retval ::NRF_SUCCESS Successfully accepted security information. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. + * - Encryption information provided by the app without being requested. See @ref + * ble_gap_evt_sec_info_request_t::enc_info. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, + sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, + ble_gap_sign_info_t const *p_sign_info)); + +/**@brief Get the current connection security. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Current connection security successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); + +/**@brief Start reporting the received signal strength to the application. + * + * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. + * + * @events + * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is + * dependent on the settings of the threshold_dbm + * and skip_count input parameters.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are + * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. + * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref + * BLE_GAP_EVT_RSSI_CHANGED event. + * + * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); + +/**@brief Stop reporting the received signal strength. + * + * @note An RSSI change detected before the call but not yet received by the application + * may be reported after @ref sd_ble_gap_rssi_stop has been called. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); + +/**@brief Get the received signal strength for the last connection event. + * + * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND + * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. + * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. + * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. + * + * @retval ::NRF_SUCCESS Successfully read the RSSI. + * @retval ::NRF_ERROR_NOT_FOUND No sample is available. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + */ +SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); + +/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). + * + * @note A call to this function will require the application to keep the memory pointed by + * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped + * or when this function is called with another buffer. + * + * @note The scanner will automatically stop in the following cases: + * - @ref sd_ble_gap_scan_stop is called. + * - @ref sd_ble_gap_connect is called. + * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. + * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application + * access received data. The application must call this function to continue scanning, or call @ref + * sd_ble_gap_scan_stop to stop scanning. + * + * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will + * receive more reports from this advertising event. The following reports will include the old and new received data. + * + * @events + * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue + * scanning, this parameter must be NULL. + * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. + * The memory pointed to should be kept alive until the scanning is stopped. + * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. + * If the scanner receives advertising data larger than can be stored in the buffer, + * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status + * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. + * + * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Scanning is already ongoing and p_scan_params was not NULL + * - Scanning is not running and p_scan_params was NULL. + * - The scanner has timed out when this function is called to continue scanning. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. + * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again + */ +SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, + sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); + +/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). + * + * @note The buffer provided in @ref sd_ble_gap_scan_start is released. + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. + * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. + */ +SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); + +/**@brief Create a connection (GAP Link Establishment). + * + * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. + * The scanning procedure will be stopped even if the function returns an error. + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use + * whitelist, then p_peer_addr is ignored. + * @param[in] p_scan_params Pointer to scan parameters structure. + * @param[in] p_conn_params Pointer to desired connection parameters. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. + * + * @retval ::NRF_SUCCESS Successfully initiated connection procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * - Invalid parameter(s) in p_scan_params or p_conn_params. + * - Use of whitelist requested but whitelist has not been set, see @ref + * sd_ble_gap_whitelist_set. + * - Peer address was not present in the device identity list, see @ref + * sd_ble_gap_device_identities_set. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an + * existing locally initiated connect procedure, which must complete before initiating again. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::NRF_ERROR_RESOURCES Either: + * - Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. + * - The event_length parameter associated with conn_cfg_tag is too small to be able to + * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. + * Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_CONNECT, uint32_t, + sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, + ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); + +/**@brief Cancel a connection establishment. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. + * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection + * completed occurred. + */ +SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); + +/**@brief Initiate or respond to a PHY Update Procedure + * + * @details This function is used to initiate or respond to a PHY Update Procedure. It will always + * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. + * If this function is used to initiate a PHY Update procedure and the only option + * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the + * currently active PHYs in the respective directions, the SoftDevice will generate a + * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the + * procedure in the Link Layer. + * + * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, + * then the stack will select PHYs based on the peer's PHY preferences and the local link + * configuration. The PHY Update procedure will for this case result in a PHY combination + * that respects the time constraints configured with @ref sd_ble_cfg_set and the current + * link layer data length. + * + * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. + * + * If the peer does not support the PHY Update Procedure, then the resulting + * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to + * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. + * + * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status + * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or + * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. + * If the peer responds to the PHY Update procedure with invalid parameters, the status + * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. + * If the PHY Update procedure was rejected by the peer for a different reason, the status will + * contain the reason as specified by the peer. + * + * @events + * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} + * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} + * @endmscs + * + * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. + * @param[in] p_gap_phys Pointer to PHY structure. + * + * @retval ::NRF_SUCCESS Successfully requested a PHY Update. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of + * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref + * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. + * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the + * pending procedure to complete and retry. + * + */ +SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); + +/**@brief Initiate or respond to a Data Length Update Procedure. + * + * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of + * p_dl_params, the SoftDevice will choose the highest value supported in current + * configuration and connection parameters. + * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime + * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and + * MaxRxTime will be limited to maximum 2120 us. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update + * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let + * the SoftDevice automatically decide the value for that member. + * Set to NULL to use automatic values for all members. + * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not + * have enough resources or does not support the requested Data Length + * Update parameters. Ignored if NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect + * p_dl_limitation to see which parameter is not supported. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested + * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation + * to see where the limitation is. + * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the + * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. + */ +SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, + sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, + ble_gap_data_length_limitation_t *p_dl_limitation)); + +/**@brief Start the Quality of Service (QoS) channel survey module. + * + * @details The channel survey module provides measurements of the energy levels on + * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT + * events will periodically report the measured energy levels for each channel. + * + * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, + * Radio Timeslot API events and Flash API events. + * + * @note The channel survey module will attempt to do measurements so that the average interval + * between measurements will be interval_us. However due to the channel survey module + * having the lowest priority of all roles and modules, this may not be possible. In that + * case fewer than expected channel survey reports may be given. + * + * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available + * must be set. This is done using @ref sd_ble_cfg_set. + * + * @param[in] interval_us Requested average interval for the measurements and reports. See + * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set + * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel + * survey role will be scheduled at every available opportunity. + * + * @retval ::NRF_SUCCESS The module is successfully started. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the + * allowed range. + * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. + * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. + * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using + * @ref sd_ble_cfg_set. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); + +/**@brief Stop the Quality of Service (QoS) channel survey module. + * + * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this + * function is called. + * + * @retval ::NRF_SUCCESS The module is successfully stopped. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); + +/**@brief Obtain the next connection event counter value. + * + * @details The connection event counter is initialized to zero on the first connection event. The value is incremented + * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, + * Section 4.5.1. + * + * @note The connection event counter obtained through this API will be outdated if this API is called + * at the same time as the connection event counter is incremented. + * + * @note This API will always return the last connection event counter + 1. + * The actual connection event may be multiple connection events later if: + * - Slave latency is enabled and there is no data to transmit or receive. + * - Another role is scheduled with a higher priority at the same time as the next connection event. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. + * + * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, + sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); + +/**@brief Start triggering a given task on connection event start. + * + * @details When enabled, this feature will trigger a PPI task at the start of connection events. + * The application can configure the SoftDevice to trigger every N connection events starting from + * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_params Connection event trigger parameters. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. + * @retval ::NRF_ERROR_INVALID_STATE Either: + * - Trying to start connection event triggering when it is already ongoing. + * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. + * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value + to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, + sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); + +/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GAP_H__ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_gatt.h b/variants/xiao_ble/softdevice/ble_gatt.h new file mode 100644 index 000000000..df0d728fc --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_gatt.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common + @{ + @brief Common definitions and prototypes for the GATT interfaces. + */ + +#ifndef BLE_GATT_H__ +#define BLE_GATT_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATT_DEFINES Defines + * @{ */ + +/** @brief Default ATT MTU, in bytes. */ +#define BLE_GATT_ATT_MTU_DEFAULT 23 + +/**@brief Invalid Attribute Handle. */ +#define BLE_GATT_HANDLE_INVALID 0x0000 + +/**@brief First Attribute Handle. */ +#define BLE_GATT_HANDLE_START 0x0001 + +/**@brief Last Attribute Handle. */ +#define BLE_GATT_HANDLE_END 0xFFFF + +/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources + * @{ */ +#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ +/** @} */ + +/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations + * @{ */ +#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ +/** @} */ + +/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags + * @{ */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ +/** @} */ + +/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations + * @{ */ +#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ +#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ +/** @} */ + +/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes + * @{ */ +#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ +#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ +#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ +#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ +#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ +#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ +#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ + 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ +#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ +#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ + 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ +#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ +#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ +#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ + 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ + */ +#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ + 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ +#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ + 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ +#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats + * @note Found at + * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + * @{ */ +#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ +#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ +#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ +#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ +#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ +#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ +#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ +#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces + * @{ + */ +#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ +#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATT_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. + */ +typedef struct { + uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. + The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + @mscs + @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + @endmscs + */ +} ble_gatt_conn_cfg_t; + +/**@brief GATT Characteristic Properties. */ +typedef struct { + /* Standard properties */ + uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ + uint8_t read : 1; /**< Reading the value permitted. */ + uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ + uint8_t write : 1; /**< Writing the value with Write Request permitted. */ + uint8_t notify : 1; /**< Notification of the value permitted. */ + uint8_t indicate : 1; /**< Indications of the value permitted. */ + uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ +} ble_gatt_char_props_t; + +/**@brief GATT Characteristic Extended Properties. */ +typedef struct { + /* Extended properties */ + uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ + uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ +} ble_gatt_char_ext_props_t; + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATT_H__ + +/** @} */ diff --git a/variants/xiao_ble/softdevice/ble_gattc.h b/variants/xiao_ble/softdevice/ble_gattc.h new file mode 100644 index 000000000..f1df1782c --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_gattc.h @@ -0,0 +1,764 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client + @{ + @brief Definitions and prototypes for the GATT Client interface. + */ + +#ifndef BLE_GATTC_H__ +#define BLE_GATTC_H__ + +#include "ble_err.h" +#include "ble_gatt.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GATTC API SVC numbers. */ +enum BLE_GATTC_SVCS { + SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ + SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ + SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ + SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ + SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ + SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ + SD_BLE_GATTC_READ, /**< Generic read. */ + SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ + SD_BLE_GATTC_WRITE, /**< Generic write. */ + SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ + SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ +}; + +/** + * @brief GATT Client Event IDs. + */ +enum BLE_GATTC_EVTS { + BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref + ble_gattc_evt_prim_srvc_disc_rsp_t. */ + BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. + */ + BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref + ble_gattc_evt_char_disc_rsp_t. */ + BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref + ble_gattc_evt_desc_disc_rsp_t. */ + BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref + ble_gattc_evt_attr_info_disc_rsp_t. */ + BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ + BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ + BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref + ble_gattc_evt_char_vals_read_rsp_t. */ + BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ + BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref + sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ + BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref + ble_gattc_evt_exchange_mtu_rsp_t. */ + BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ + BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref + ble_gattc_evt_write_cmd_tx_complete_t. */ +}; + +/**@brief GATTC Option IDs. + * IDs that uniquely identify a GATTC option. + */ +enum BLE_GATTC_OPTS { + BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTC_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC + * @{ */ +#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ +/** @} */ + +/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats + * @{ */ +#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ +#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ +/** @} */ + +/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults + * @{ */ +#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Write without Response that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTC_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for + transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ +} ble_gattc_conn_cfg_t; + +/**@brief Operation Handle Range. */ +typedef struct { + uint16_t start_handle; /**< Start Handle. */ + uint16_t end_handle; /**< End Handle. */ +} ble_gattc_handle_range_t; + +/**@brief GATT service. */ +typedef struct { + ble_uuid_t uuid; /**< Service UUID. */ + ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ +} ble_gattc_service_t; + +/**@brief GATT include. */ +typedef struct { + uint16_t handle; /**< Include Handle. */ + ble_gattc_service_t included_srvc; /**< Handle of the included service. */ +} ble_gattc_include_t; + +/**@brief GATT characteristic. */ +typedef struct { + ble_uuid_t uuid; /**< Characteristic UUID. */ + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + uint8_t char_ext_props : 1; /**< Extended properties present. */ + uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ + uint16_t handle_value; /**< Handle of the Characteristic Value. */ +} ble_gattc_char_t; + +/**@brief GATT descriptor. */ +typedef struct { + uint16_t handle; /**< Descriptor Handle. */ + ble_uuid_t uuid; /**< Descriptor UUID. */ +} ble_gattc_desc_t; + +/**@brief Write Parameters. */ +typedef struct { + uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ + uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ + uint16_t handle; /**< Handle to the attribute to be written. */ + uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ + uint16_t len; /**< Length of data in bytes. */ + uint8_t const *p_value; /**< Pointer to the value data. */ +} ble_gattc_write_params_t; + +/**@brief Attribute Information for 16-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ +} ble_gattc_attr_info16_t; + +/**@brief Attribute Information for 128-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ +} ble_gattc_attr_info128_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Service count. */ + ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_prim_srvc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Include count. */ + ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_rel_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Characteristic count. */ + ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_char_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Descriptor count. */ + ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_desc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Attribute count. */ + uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ + union { + ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + } info; /**< Attribute information union. */ +} ble_gattc_evt_attr_info_disc_rsp_t; + +/**@brief GATT read by UUID handle value pair. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ +} ble_gattc_handle_value_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ +typedef struct { + uint16_t count; /**< Handle-Value Pair Count. */ + uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ + uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref + sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. + @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_val_by_uuid_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint16_t offset; /**< Offset of the attribute data. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ +typedef struct { + uint16_t len; /**< Concatenated Attribute values length. */ + uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder + for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_vals_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ + uint16_t offset; /**< Data offset. */ + uint16_t len; /**< Data length. */ + uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_write_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ +typedef struct { + uint16_t handle; /**< Handle to which the HVx operation applies. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_hvx_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ +typedef struct { + uint16_t server_rx_mtu; /**< Server RX MTU size. */ +} ble_gattc_evt_exchange_mtu_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gattc_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of write without response transmissions completed. */ +} ble_gattc_evt_write_cmd_tx_complete_t; + +/**@brief GATTC event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint16_t + error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ + union { + ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ + ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ + ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ + ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ + ble_gattc_evt_char_val_by_uuid_read_rsp_t + char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ + ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ + ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ + ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ + ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ + ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ + ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ + ble_gattc_evt_write_cmd_tx_complete_t + write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ + } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ +} ble_gattc_evt_t; + +/**@brief UUID discovery option. + * + * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the + * Vendor Specific UUID table. Disabled by default. + * - When disabled, if a procedure initiated by + * @ref sd_ble_gattc_primary_services_discover, + * @ref sd_ble_gattc_relationships_discover, + * @ref sd_ble_gattc_characteristics_discover, + * @ref sd_ble_gattc_descriptors_discover + * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set + * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use + * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding + * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will + * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + * @retval ::NRF_SUCCESS Set successfully. + * + */ +typedef struct { + uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ +} ble_gattc_opt_uuid_disc_t; + +/**@brief Option structure for GATTC options. */ +typedef union { + ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ +} ble_gattc_opt_t; + +/** @} */ + +/** @addtogroup BLE_GATTC_FUNCTIONS Functions + * @{ */ + +/**@brief Initiate or continue a GATT Primary Service Discovery procedure. + * + * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. + * If the last service has not been reached, this function must be called again with an updated start handle value to + * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] start_handle Handle to start searching from. + * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, + sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); + +/**@brief Initiate or continue a GATT Relationship Discovery procedure. + * + * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been + * reached, this must be called again with an updated handle range to continue the search. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, + sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Discovery procedure. + * + * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, + sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. + * + * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not + * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, + sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. + * + * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_uuid Pointer to a Characteristic value UUID to read. + * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, + sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, + ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. + * + * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or + * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read + * the complete value. + * + * @events + * @event{@ref BLE_GATTC_EVT_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute to be read. + * @param[in] offset Offset into the attribute value to be read. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); + +/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. + * + * @details This function initiates a GATT Read Multiple Characteristic Values procedure. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. + * @param[in] handle_count The number of handles in p_handles. + * + * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, + sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); + +/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) + * procedure. + * + * @details This function can perform all write procedures described in GATT. + * + * @note Only one write with response procedure can be ongoing per connection at a time. + * If the application tries to write with response while another write with response procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. + * + * @note The number of Write without Response that can be queued is configured by @ref + * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. + * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without + * response is complete. + * + * @note The application can keep track of the available queue element count for writes without responses by following the + * procedure below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} + * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_write_params A pointer to a write parameters structure. + * + * @retval ::NRF_SUCCESS Successfully started the Write procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event + * and retry. + * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. + * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); + +/**@brief Send a Handle Value Confirmation to the GATT Server. + * + * @mscs + * @mmsc{@ref BLE_GATTC_HVI_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute in the indication. + * + * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); + +/**@brief Discovers information about a range of attributes on a GATT server. + * + * @events + * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} + * @endevents + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range The range of handles to request information about. + * + * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, + sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value, and + * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @events + * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] client_rx_mtu Client RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + used for this connection. + * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent request to the server. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, + sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); + +/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * + * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * @note If the buffer contains different event, behavior is undefined. + * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with + * the next Handle-Value pair in each iteration. If the function returns other than + * @ref NRF_SUCCESS, it will not be changed. + * - To start iteration, initialize the structure to zero. + * - To continue, pass the value from previous iteration. + * + * \code + * ble_gattc_handle_value_t iter; + * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); + * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) + * { + * app_handle = iter.handle; + * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); + * } + * \endcode + * + * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. + * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. + */ +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter); + +/** @} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter) +{ + uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; + uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; + uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; + + if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { + p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; + p_iter->p_value = p_next + sizeof(uint16_t); + return NRF_SUCCESS; + } else { + return NRF_ERROR_NOT_FOUND; + } +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_GATTC_H__ */ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_gatts.h b/variants/xiao_ble/softdevice/ble_gatts.h new file mode 100644 index 000000000..dc94957cd --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_gatts.h @@ -0,0 +1,904 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server + @{ + @brief Definitions and prototypes for the GATTS interface. + */ + +#ifndef BLE_GATTS_H__ +#define BLE_GATTS_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief GATTS API SVC numbers. + */ +enum BLE_GATTS_SVCS { + SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ + SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ + SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ + SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ + SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ + SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ + SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ + SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ + SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more + attributes. */ + SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ + SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ + SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ + SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ + SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ +}; + +/** + * @brief GATT Server Event IDs. + */ +enum BLE_GATTS_EVTS { + BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See + @ref ble_gatts_evt_write_t. */ + BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with + @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. + */ + BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref + sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ + BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. + */ + BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event + structure applies. */ + BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with + @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. + */ + BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref + ble_gatts_evt_timeout_t. */ + BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref + ble_gatts_evt_hvn_tx_complete_t. */ +}; + +/**@brief GATTS Configuration IDs. + * + * IDs that uniquely identify a GATTS configuration. + */ +enum BLE_GATTS_CFGS { + BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ + BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTS_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS + * @{ */ +#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ +#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths + * @{ */ +#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ +#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ +/** @} */ + +/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types + * @{ */ +#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ +#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ +#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types + * @{ */ +#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ +#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ +#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ +#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ +/** @} */ + +/** @defgroup BLE_GATTS_OPS GATT Server Operations + * @{ */ +#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ +/** @} */ + +/** @defgroup BLE_GATTS_VLOCS GATT Value Locations + * @{ */ +#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ +#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ +#define BLE_GATTS_VLOC_USER \ + 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ + of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ + There are no alignment requirements for the buffer. */ +/** @} */ + +/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types + * @{ */ +#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ +#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ +#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ +/** @} */ + +/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags + * @{ */ +#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ +#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ +/** @} */ + +/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values + * @{ + */ +#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ + (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size + * @{ + */ +#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ +#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ +/** @} */ + +/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults + * @{ + */ +#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTS_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. + The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ +} ble_gatts_conn_cfg_t; + +/**@brief Attribute metadata. */ +typedef struct { + ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vlen : 1; /**< Variable length attribute. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ + uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not + Write Command). */ +} ble_gatts_attr_md_t; + +/**@brief GATT Attribute. */ +typedef struct { + ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ + ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ + uint16_t init_len; /**< Initial attribute value length in bytes. */ + uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the + attribute value will be left uninitialized. */ + uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ + uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is + selected in the attribute metadata, this will have to point to a buffer that remains valid through the + lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any + other temporary location. The stack may access that memory directly without the application's + knowledge. For writable characteristics, this value must not be a location in flash memory.*/ +} ble_gatts_attr_t; + +/**@brief GATT Attribute Value. */ +typedef struct { + uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ + uint16_t offset; /**< Attribute value offset. */ + uint8_t *p_value; /**< Pointer to where value is stored or will be stored. + If value is stored in user memory, only the attribute length is updated when p_value == NULL. + Set to NULL when reading to obtain the complete length of the attribute value */ +} ble_gatts_value_t; + +/**@brief GATT Characteristic Presentation Format. */ +typedef struct { + uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ + int8_t exponent; /**< Exponent for integer data types. */ + uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ + uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ + uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ +} ble_gatts_char_pf_t; + +/**@brief GATT Characteristic metadata. */ +typedef struct { + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ + uint8_t const * + p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ + uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ + uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ + ble_gatts_char_pf_t const + *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ + ble_gatts_attr_md_t const + *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ +} ble_gatts_char_md_t; + +/**@brief GATT Characteristic Definition Handles. */ +typedef struct { + uint16_t value_handle; /**< Handle to the characteristic value. */ + uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ + uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ + uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ +} ble_gatts_char_handles_t; + +/**@brief GATT HVx parameters. */ +typedef struct { + uint16_t handle; /**< Characteristic Value Handle. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t offset; /**< Offset within the attribute value. */ + uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ + uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ +} ble_gatts_hvx_params_t; + +/**@brief GATT Authorization parameters. */ +typedef struct { + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. + Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, + as the data to be written needs to be stored and later provided by the application. */ + uint16_t offset; /**< Offset of the attribute value being updated. */ + uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ + uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ +} ble_gatts_authorize_params_t; + +/**@brief GATT Read or Write Authorize Reply parameters. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ + ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ + } params; /**< Reply Parameters. */ +} ble_gatts_rw_authorize_reply_params_t; + +/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref + BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ +} ble_gatts_cfg_service_changed_t; + +/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - @ref ble_gatts_attr_md_t::write_perm is out of range. + * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is + * not allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is + * allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. + * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported + */ +typedef struct { + ble_gatts_attr_md_t + perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ +} ble_gatts_cfg_service_changed_cccd_perm_t; + +/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The specified Attribute Table size is too small. + * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. + * - The specified Attribute Table size is not a multiple of 4. + */ +typedef struct { + uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref + BLE_GATTS_ATTR_TAB_SIZE_MIN. */ +} ble_gatts_cfg_attr_tab_size_t; + +/**@brief Config structure for GATTS configurations. */ +typedef union { + ble_gatts_cfg_service_changed_t + service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ + ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ + ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ +} ble_gatts_cfg_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ + uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref + sd_ble_gatts_value_set to finalize the writing operation. */ + uint16_t offset; /**< Offset for the write operation. */ + uint16_t len; /**< Length of the received data. */ + uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gatts_evt_write_t; + +/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint16_t offset; /**< Offset for the read operation. */ +} ble_gatts_evt_read_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ + ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ + } request; /**< Request Parameters. */ +} ble_gatts_evt_rw_authorize_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ +typedef struct { + uint8_t hint; /**< Hint (currently unused). */ +} ble_gatts_evt_sys_attr_missing_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ +} ble_gatts_evt_hvc_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ +typedef struct { + uint16_t client_rx_mtu; /**< Client RX MTU size. */ +} ble_gatts_evt_exchange_mtu_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gatts_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of notification transmissions completed. */ +} ble_gatts_evt_hvn_tx_complete_t; + +/**@brief GATTS event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ + union { + ble_gatts_evt_write_t write; /**< Write Event Parameters. */ + ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ + ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ + ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ + ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ + ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ + ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_gatts_evt_t; + +/** @} */ + +/** @addtogroup BLE_GATTS_FUNCTIONS Functions + * @{ */ + +/**@brief Add a service declaration to the Attribute Table. + * + * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to + * add a secondary service declaration that is not referenced by another service later in the Attribute Table. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. + * @param[in] p_uuid Pointer to service UUID. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a service declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); + +/**@brief Add an include declaration to the Attribute Table. + * + * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note The included service must already be present in the Attribute Table prior to this call. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID + * is used, it will be placed sequentially. + * @param[in] inc_srvc_handle Handle of the included service. + * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added an include declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + */ +SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, + sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); + +/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations + * to the Attribute Table. + * + * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and + * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format + * values. + * + * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic + * permissions. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_char_md Characteristic metadata. + * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. + * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a characteristic. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, + sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); + +/**@brief Add a descriptor to the Attribute Table. + * + * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is + * supported at this time). + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_attr Pointer to the attribute structure. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a descriptor. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, + sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); + +/**@brief Set the value of a given attribute. + * + * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully set the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + */ +SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, + sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Get the value of a given attribute. + * + * @note If the attribute value is longer than the size of the supplied buffer, + * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), + * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. + * The application may use this information to allocate a suitable buffer size. + * + * @note When retrieving system attribute values with this function, the connection handle + * may refer to an already disconnected connection. Refer to the documentation of + * @ref sd_ble_gatts_sys_attr_get for further information. + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + */ +SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, + sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Notify or Indicate an attribute value. + * + * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant + * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before + * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single + * API call. + * + * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during + * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, + * @ref NRF_ERROR_BUSY, + * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. + * The caller can check whether the value has been updated by looking at the contents of *(@ref + * ble_gatts_hvx_params_t::p_len). + * + * @note Only one indication procedure can be ongoing per connection at a time. + * If the application tries to indicate an attribute value while another indication procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. + * + * @note The number of Handle Value Notifications that can be queued is configured by @ref + * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. + * + * @note The application can keep track of the available queue element count for notifications by following the procedure + * below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} + * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_HVN_MSC} + * @mmsc{@ref BLE_GATTS_HVI_MSC} + * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data + * contains a non-NULL pointer the attribute value will be updated with the contents + * pointed by it before sending the notification or indication. If the attribute value + * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to + * contain the number of actual bytes written, else it will be set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute + * value. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application + * are available to notify and indicate. + * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and + * indicated. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions + * of the CCCD associated with this characteristic. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC + * event and retry. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. + * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); + +/**@brief Indicate the Service Changed attribute value. + * + * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute + * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will + * be issued. + * + * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. + * + * @events + * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_SC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] start_handle Start of affected attribute handle range. + * @param[in] end_handle End of affected attribute handle range. + * + * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref + * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the + * application. + * @retval ::NRF_ERROR_BUSY Procedure already in progress. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, + sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); + +/**@brief Respond to a Read/Write authorization request. + * + * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. + * + * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond + * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update + * is set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute + * Table updated. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, + * handle supplied does not match requested handle, + * or invalid data to be written provided by the application. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, + sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); + +/**@brief Update persistent system attribute information. + * + * @details Supply information about persistent system attributes to the stack, + * previously obtained using @ref sd_ble_gatts_sys_attr_get. + * This call is only allowed for active connections, and is usually + * made immediately after a connection is established with an known bonded device, + * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. + * + * p_sysattrs may point directly to the application's stored copy of the system attributes + * obtained using @ref sd_ble_gatts_sys_attr_get. + * If the pointer is NULL, the system attribute info is initialized, assuming that + * the application does not have any previously saved system attribute data for this device. + * + * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. + * + * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may + * have been completed only partially. This means that the state of the attribute table is undefined, and the application should + * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be modified. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be modified. + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. + * @param[in] len Size of data pointed by p_sys_attr_data, in octets. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully set the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref + * sd_ble_gatts_sys_attr_get. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, + sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); + +/**@brief Retrieve persistent system attribute information from the stack. + * + * @details This call is used to retrieve information about values to be stored persistently by the application + * during the lifetime of a connection or after it has been terminated. When a new connection is established with the + * same bonded device, the system attribute information retrieved with this function should be restored using using @ref + * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The + * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API + * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the + * system attributes may be written to at any time by the peer during a connection's lifetime. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be returned. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be returned. + * + * @mscs + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle of the recently terminated connection. + * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The + * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. + * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual + * length of system attribute data. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. + * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, + sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); + +/**@brief Retrieve the first valid user attribute handle. + * + * @param[out] p_handle Pointer to an integer where the handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully retrieved the handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); + +/**@brief Retrieve the attribute UUID and/or metadata. + * + * @param[in] handle Attribute handle + * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. + * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. + * + * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. + * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. + */ +SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); + +/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. + * + * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and + * - The Server RX MTU value. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @mscs + * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] server_rx_mtu Server RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + * used for this connection. + * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent response to the client. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATTS_H__ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_hci.h b/variants/xiao_ble/softdevice/ble_hci.h new file mode 100644 index 000000000..27f85d52e --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_hci.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ +*/ + +#ifndef BLE_HCI_H__ +#define BLE_HCI_H__ +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes + * @{ */ + +#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ +/*0x03 Hardware Failure +0x04 Page Timeout +*/ +#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ +#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ +#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ +#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ +/*0x09 Connection Limit Exceeded +0x0A Synchronous Connection Limit To A Device Exceeded +0x0B ACL Connection Already Exists*/ +#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ +/*0x0D Connection Rejected due to Limited Resources +0x0E Connection Rejected Due To Security Reasons +0x0F Connection Rejected due to Unacceptable BD_ADDR +0x10 Connection Accept Timeout Exceeded +0x11 Unsupported Feature or Parameter Value*/ +#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ +#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ + 0x14 /**< Remote Device Terminated Connection due to low \ + resources.*/ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ +#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ +/* +0x17 Repeated Attempts +0x18 Pairing Not Allowed +0x19 Unknown LMP PDU +*/ +#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ +/* +0x1B SCO Offset Rejected +0x1C SCO Interval Rejected +0x1D SCO Air Mode Rejected*/ +#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ +#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ +/*0x20 Unsupported LMP Parameter Value +0x21 Role Change Not Allowed +*/ +#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ +#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ +#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ +/*0x25 Encryption Mode Not Acceptable +0x26 Link Key Can Not be Changed +0x27 Requested QoS Not Supported +*/ +#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ +#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ +#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ +/* +0x2B Reserved +0x2C QoS Unacceptable Parameter +0x2D QoS Rejected +0x2E Channel Classification Not Supported +0x2F Insufficient Security +*/ +#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ +/* +0x31 Reserved +0x32 Role Switch Pending +0x33 Reserved +0x34 Reserved Slot Violation +0x35 Role Switch Failed +0x36 Extended Inquiry Response Too Large +0x37 Secure Simple Pairing Not Supported By Host. +0x38 Host Busy - Pairing +0x39 Connection Rejected due to No Suitable Channel Found*/ +#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ +#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ +#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ +#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ +#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_HCI_H__ + +/** @} */ diff --git a/variants/xiao_ble/softdevice/ble_l2cap.h b/variants/xiao_ble/softdevice/ble_l2cap.h new file mode 100644 index 000000000..5f4bd277d --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_l2cap.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) + @{ + @brief Definitions and prototypes for the L2CAP interface. + */ + +#ifndef BLE_L2CAP_H__ +#define BLE_L2CAP_H__ + +#include "ble_err.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology + * @{ + * @details + * + * L2CAP SDU + * - A data unit that the application can send/receive to/from a peer. + * + * L2CAP PDU + * - A data unit that is exchanged between local and remote L2CAP entities. + * It consists of L2CAP protocol control information and payload fields. + * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. + * + * L2CAP MTU + * - The maximum length of an L2CAP SDU. + * + * L2CAP MPS + * - The maximum length of an L2CAP PDU payload field. + * + * Credits + * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. + * @} */ + +/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief L2CAP API SVC numbers. */ +enum BLE_L2CAP_SVCS { + SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ + SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ + SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ +}; + +/**@brief L2CAP Event IDs. */ +enum BLE_L2CAP_EVTS { + BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. + \n Reply with @ref sd_ble_l2cap_ch_setup. + \n See @ref ble_l2cap_evt_ch_setup_request_t. */ + BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. + \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ + BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. + \n See @ref ble_l2cap_evt_ch_setup_t. */ + BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. + \n No additional event structure applies. */ + BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. + \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ + BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. + \n See @ref ble_l2cap_evt_ch_credit_t. */ + BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. + \n See @ref ble_l2cap_evt_ch_rx_t. */ + BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. + \n See @ref ble_l2cap_evt_ch_tx_t. */ +}; + +/** @} */ + +/**@addtogroup BLE_L2CAP_DEFINES Defines + * @{ */ + +/**@brief Maximum number of L2CAP channels per connection. */ +#define BLE_L2CAP_CH_COUNT_MAX (64) + +/**@brief Minimum L2CAP MTU, in bytes. */ +#define BLE_L2CAP_MTU_MIN (23) + +/**@brief Minimum L2CAP MPS, in bytes. */ +#define BLE_L2CAP_MPS_MIN (23) + +/**@brief Invalid CID. */ +#define BLE_L2CAP_CID_INVALID (0x0000) + +/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ +#define BLE_L2CAP_CREDITS_DEFAULT (1) + +/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources + * @{ */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ + /** @} */ + +/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes + * @{ */ +#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ +#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ +#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ +#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ +#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ +#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ +#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ + (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ +#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ +/** @} */ + +/** @} */ + +/**@addtogroup BLE_L2CAP_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note These parameters are set per connection, so all L2CAP channels created on this connection + * will have the same parameters. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. + * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. + */ +typedef struct { + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to receive on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to transmit on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per + L2CAP channel. The minimum value is one. */ + uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission + per L2CAP channel. The minimum value is one. */ + uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection + with this configuration. The default value is zero, the maximum + value is @ref BLE_L2CAP_CH_COUNT_MAX. + @note if this parameter is set to zero, all other parameters in + @ref ble_l2cap_conn_cfg_t are ignored. */ +} ble_l2cap_conn_cfg_t; + +/**@brief L2CAP channel RX parameters. */ +typedef struct { + uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to + receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be + able to receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. + - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ + ble_data_t sdu_buf; /**< SDU data buffer for reception. + - If @ref ble_data_t::p_data is non-NULL, initial credits are + issued to the peer. + - If @ref ble_data_t::p_data is NULL, no initial credits are + issued to the peer. */ +} ble_l2cap_ch_rx_params_t; + +/**@brief L2CAP channel setup parameters. */ +typedef struct { + ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting + setup of an L2CAP channel, ignored otherwise. */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. + Used when replying to a setup request of an L2CAP + channel, ignored otherwise. */ +} ble_l2cap_ch_setup_params_t; + +/**@brief L2CAP channel TX parameters. */ +typedef struct { + uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to + transmit on this L2CAP channel. */ + uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is + able to receive on this L2CAP channel. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able + to transmit on this L2CAP channel. This is effective tx_mps, + selected by the SoftDevice as + MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ + uint16_t credits; /**< Initial credits given by the peer. */ +} ble_l2cap_ch_tx_params_t; + +/**@brief L2CAP Channel Setup Request event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ +} ble_l2cap_evt_ch_setup_request_t; + +/**@brief L2CAP Channel Setup Refused event. */ +typedef struct { + uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ +} ble_l2cap_evt_ch_setup_refused_t; + +/**@brief L2CAP Channel Setup Completed event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ +} ble_l2cap_evt_ch_setup_t; + +/**@brief L2CAP Channel SDU Data Buffer Released event. */ +typedef struct { + ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice + returns SDU data buffers supplied by the application, which have + not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or + @ref BLE_L2CAP_EVT_CH_TX event. */ +} ble_l2cap_evt_ch_sdu_buf_released_t; + +/**@brief L2CAP Channel Credit received event. */ +typedef struct { + uint16_t credits; /**< Additional credits given by the peer. */ +} ble_l2cap_evt_ch_credit_t; + +/**@brief L2CAP Channel received SDU event. */ +typedef struct { + uint16_t sdu_len; /**< Total SDU length, in bytes. */ + ble_data_t sdu_buf; /**< SDU data buffer. + @note If there is not enough space in the buffer + (sdu_buf.len < sdu_len) then the rest of the SDU will be + silently discarded by the SoftDevice. */ +} ble_l2cap_evt_ch_rx_t; + +/**@brief L2CAP Channel transmitted SDU event. */ +typedef struct { + ble_data_t sdu_buf; /**< SDU data buffer. */ +} ble_l2cap_evt_ch_tx_t; + +/**@brief L2CAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occured. */ + uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or + @ref BLE_L2CAP_CID_INVALID if not present. */ + union { + ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ + ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ + ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ + ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ + ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ + ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ + ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_l2cap_evt_t; + +/** @} */ + +/**@addtogroup BLE_L2CAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set up an L2CAP channel. + * + * @details This function is used to: + * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. + * - Reply to a setup request of an L2CAP channel (if called in response to a + * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection + * Response packet to a peer. + * + * @note A call to this function will require the application to keep the SDU data buffer alive + * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or + * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} + * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: + * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP + * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * event when replying to a setup request of an L2CAP channel. + * - As output: local_cid for this channel. + * @param[in] p_params L2CAP channel parameters. + * + * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, + * see @ref ble_l2cap_conn_cfg_t::ch_count. + */ +SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, + sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); + +/**@brief Release an L2CAP channel. + * + * @details This sends a Disconnection Request packet to a peer. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * + * @retval ::NRF_SUCCESS Successfully queued request for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); + +/**@brief Receive an SDU on an L2CAP channel. + * + * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers + * for reception per L2CAP channel. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Buffer accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a + * @ref BLE_L2CAP_EVT_CH_RX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Transmit an SDU on an L2CAP channel. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for + * transmission per L2CAP channel. + * + * @note The application can keep track of the available credits for transmission by following + * the procedure below: + * - Store initial credits given by the peer in a variable. + * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Decrement the variable, which stores the currently available credits, by + * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns + * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Increment the variable, which stores the currently available credits, by additional + * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than + * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in + * @ref BLE_L2CAP_EVT_CH_SETUP event. + * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a + * @ref BLE_L2CAP_EVT_CH_TX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Advanced SDU reception flow control. + * + * @details Adjust the way the SoftDevice issues credits to the peer. + * This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set + * the value that will be used for newly created channels. + * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every + * time it starts using a new reception buffer. + * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will + * use if this function is not called. + * - If set to zero, the SoftDevice will stop issuing credits for new reception + * buffers the application provides or has provided. SDU reception that is + * currently ongoing will be allowed to complete. + * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be + * written by the SoftDevice with the number of credits that is or will be + * available to the peer. If the value written by the SoftDevice is 0 when + * credits parameter was set to 0, the peer will not be able to send more + * data until more credits are provided by calling this function again with + * credits > 0. This parameter is ignored when local_cid is set to + * @ref BLE_L2CAP_CID_INVALID. + * + * @note Application should take care when setting number of credits higher than default value. In + * this case the application must make sure that the SoftDevice always has reception buffers + * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have + * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic + * on the connection handle may be stalled until the SoftDevice again has an available + * reception buffer. This applies even if the application has used this call to set the + * credits back to default, or zero. + * + * @retval ::NRF_SUCCESS Flow control parameters accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, + sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_L2CAP_H__ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_ranges.h b/variants/xiao_ble/softdevice/ble_ranges.h new file mode 100644 index 000000000..2768e4996 --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_ranges.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_ranges Module specific SVC, event and option number subranges + @{ + + @brief Definition of SVC, event and option number subranges for each API module. + + @note + SVCs, event and option numbers are split into subranges for each API module. + Each module receives its entire allocated range of SVC calls, whether implemented or not, + but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. + + Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, + rather than the last SVC function call actually defined and implemented. + + Specific SVC, event and option values are defined in each module's ble_.h file, + which defines names of each individual SVC code based on the range start value. +*/ + +#ifndef BLE_RANGES_H__ +#define BLE_RANGES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ +#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ + +#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ +#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ + +#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ +#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ + +#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ +#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ + +#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ +#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ + +#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ + +#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ +#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ + +#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ +#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ + +#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ +#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ + +#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ +#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ + +#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ +#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ + +#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ + +#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ +#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ + +#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ +#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ + +#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ +#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ + +#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ +#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ + +#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ +#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ + +#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ +#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ + +#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ + +#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ +#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ + +#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ +#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ + +#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ +#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ + +#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ +#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ + +#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ +#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ + +#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ +#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ + +#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ +#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_RANGES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/xiao_ble/softdevice/ble_types.h b/variants/xiao_ble/softdevice/ble_types.h new file mode 100644 index 000000000..db3656cfd --- /dev/null +++ b/variants/xiao_ble/softdevice/ble_types.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_types Common types and macro definitions + @{ + + @brief Common types and macro definitions for the BLE SoftDevice. + */ + +#ifndef BLE_TYPES_H__ +#define BLE_TYPES_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_TYPES_DEFINES Defines + * @{ */ + +/** @defgroup BLE_CONN_HANDLES BLE Connection Handles + * @{ */ +#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ +#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ +/** @} */ + +/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs + * @{ */ +/* Generic UUIDs, applicable to all services */ +#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ +#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ +#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ +#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ +#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ +#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ +/* GATT specific UUIDs */ +#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ +#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ +/* GAP specific UUIDs */ +#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ +#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ +/** @} */ + +/** @defgroup BLE_UUID_TYPES Types of UUID + * @{ */ +#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ +#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ +#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ +/** @} */ + +/** @defgroup BLE_APPEARANCES Bluetooth Appearance values + * @note Retrieved from + * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * @{ */ +#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ +#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ +#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ +#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ +#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ +#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ +#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ +#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ +#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ +#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ +#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ +#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ +#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ +#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ +#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ +#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ +#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ +#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ +#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ +#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ +#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ +#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ +#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ +#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ +#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ +#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ +#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ +#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ +#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ +#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ +#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ +#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ +#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ +#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ +#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ +#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ + 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ + 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ +/** @} */ + +/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ +#define BLE_UUID_BLE_ASSIGN(instance, value) \ + do { \ + instance.type = BLE_UUID_TYPE_BLE; \ + instance.uuid = value; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ +#define BLE_UUID_COPY_PTR(dst, src) \ + do { \ + (dst)->type = (src)->type; \ + (dst)->uuid = (src)->uuid; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ +#define BLE_UUID_COPY_INST(dst, src) \ + do { \ + (dst).type = (src).type; \ + (dst).uuid = (src).uuid; \ + } while (0) + +/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) + +/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) + +/** @} */ + +/** @addtogroup BLE_TYPES_STRUCTURES Structures + * @{ */ + +/** @brief 128 bit UUID values. */ +typedef struct { + uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ +} ble_uuid128_t; + +/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ +typedef struct { + uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ + uint8_t + type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ +} ble_uuid_t; + +/**@brief Data structure. */ +typedef struct { + uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ + uint16_t len; /**< Length of the data buffer, in bytes. */ +} ble_data_t; + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* BLE_TYPES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/xiao_ble/softdevice/nrf52/nrf_mbr.h b/variants/xiao_ble/softdevice/nrf52/nrf_mbr.h new file mode 100644 index 000000000..4e0bd752a --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf52/nrf_mbr.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_mbr_api Master Boot Record API + @{ + + @brief APIs for updating SoftDevice and BootLoader + +*/ + +#ifndef NRF_MBR_H__ +#define NRF_MBR_H__ + +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_MBR_DEFINES Defines + * @{ */ + +/**@brief MBR SVC Base number. */ +#define MBR_SVC_BASE (0x18) + +/**@brief Page size in words. */ +#define MBR_PAGE_SIZE_IN_WORDS (1024) + +/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. +This is the offset where the first byte of the SoftDevice hex file is written. */ +#define MBR_SIZE (0x1000) + +/** @brief Location (in the flash memory) of the bootloader address. */ +#define MBR_BOOTLOADER_ADDR (0xFF8) + +/** @brief Location (in UICR) of the bootloader address. */ +#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) + +/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ +#define MBR_PARAM_PAGE_ADDR (0xFFC) + +/** @brief Location (in UICR) of the address of the MBR parameter page. */ +#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) + +/** @} */ + +/** @addtogroup NRF_MBR_ENUMS Enumerations + * @{ */ + +/**@brief nRF Master Boot Record API SVC numbers. */ +enum NRF_MBR_SVCS { + SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ +}; + +/**@brief Possible values for ::sd_mbr_command_t.command */ +enum NRF_MBR_COMMANDS { + SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ + SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ + SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any + parameters in ::sd_mbr_command_t params.*/ + SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ + SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see + ::sd_mbr_command_vector_table_base_set_t*/ + SD_MBR_COMMAND_RESERVED, + SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see + ::sd_mbr_command_irq_forward_address_set_t*/ +}; + +/** @} */ + +/** @addtogroup NRF_MBR_TYPES Types + * @{ */ + +/**@brief This command copies part of a new SoftDevice + * + * The destination area is erased before copying. + * If dst is in the middle of a flash page, that whole flash page will be erased. + * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. + * + * The user of this function is responsible for setting the BPROT registers. + * + * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. + * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. + */ +typedef struct { + uint32_t *src; /**< Pointer to the source of data to be copied.*/ + uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ + uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ +} sd_mbr_command_copy_sd_t; + +/**@brief This command works like memcmp, but takes the length in words. + * + * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. + * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. + */ +typedef struct { + uint32_t *ptr1; /**< Pointer to block of memory. */ + uint32_t *ptr2; /**< Pointer to block of memory. */ + uint32_t len; /**< Number of 32 bit words to compare.*/ +} sd_mbr_command_compare_t; + +/**@brief This command copies a new BootLoader. + * + * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to + * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * The bootloader destination is erased by this function. + * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is + * not intended to be written. + * + * On success, this function will not return. It will start the new bootloader from reset-vector as normal. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. + * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ + uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ +} sd_mbr_command_copy_bl_t; + +/**@brief Change the address the MBR starts after a reset + * + * Once this function has been called, this address is where the MBR will start to forward + * interrupts to after a reset. + * + * To restore default forwarding, this function should be called with @ref address set to 0. If a + * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will + * be forwarded to the SoftDevice. + * + * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or + * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * On success, this function will not return. It will reset the device. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_vector_table_base_set_t; + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR + * + * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not + * change where the MBR starts after reset. + * + * @retval ::NRF_SUCCESS + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_irq_forward_address_set_t; + +/**@brief Input structure containing data used when calling ::sd_mbr_command + * + * Depending on what command value that is set, the corresponding params value type must also be + * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command + * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. + */ +typedef struct { + uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ + union { + sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ + sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ + sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ + sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ + sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ + } params; /**< Command parameters. */ +} sd_mbr_command_t; + +/** @} */ + +/** @addtogroup NRF_MBR_FUNCTIONS Functions + * @{ */ + +/**@brief Issue Master Boot Record commands + * + * Commands used when updating a SoftDevice and bootloader. + * + * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires + * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash + * page. The location of the flash page should be provided by the application in either + * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR + * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to + * store the command before reset. When an address is specified, the page it refers to must not be + * used by the application. If no address is provided by the application, i.e. both + * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use + * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. + * + * @param[in] param Pointer to a struct describing the command. + * + * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, + * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, + * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t + * + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided + * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. + */ +SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_MBR_H__ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/nrf_error.h b/variants/xiao_ble/softdevice/nrf_error.h new file mode 100644 index 000000000..fb2831e19 --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_error.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_error SoftDevice Global Error Codes + @{ + + @brief Global Error definitions +*/ + +/* Header guard */ +#ifndef NRF_ERROR_H__ +#define NRF_ERROR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions + * @{ */ +#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base +#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base +#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base +#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base +/** @} */ + +#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command +#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing +#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled +#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error +#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation +#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found +#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported +#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter +#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state +#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length +#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags +#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data +#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size +#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out +#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer +#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation +#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address +#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy +#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. +#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_H__ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/nrf_error_sdm.h b/variants/xiao_ble/softdevice/nrf_error_sdm.h new file mode 100644 index 000000000..2fd621057 --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_error_sdm.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_sdm_api + @{ + @defgroup nrf_sdm_error SoftDevice Manager Error Codes + @{ + + @brief Error definitions for the SDM API +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SDM_H__ +#define NRF_ERROR_SDM_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. +#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ + (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having + ///< enabled SoftDevice interrupts). +#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ + (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SDM_H__ + +/** + @} + @} +*/ diff --git a/variants/xiao_ble/softdevice/nrf_error_soc.h b/variants/xiao_ble/softdevice/nrf_error_soc.h new file mode 100644 index 000000000..cbd0ba8ac --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_error_soc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_soc_api + @{ + @defgroup nrf_soc_error SoC Library Error Codes + @{ + + @brief Error definitions for the SoC library + +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SOC_H__ +#define NRF_ERROR_SOC_H__ + +#include "nrf_error.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* Mutex Errors */ +#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken + +/* NVIC errors */ +#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available +#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed +#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return + +/* Power errors */ +#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown +#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown +#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return + +/* Rand errors */ +#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values + +/* PPI errors */ +#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel +#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SOC_H__ +/** + @} + @} +*/ diff --git a/variants/xiao_ble/softdevice/nrf_nvic.h b/variants/xiao_ble/softdevice/nrf_nvic.h new file mode 100644 index 000000000..d4ab204d9 --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_nvic.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_nvic_api SoftDevice NVIC API + * @{ + * + * @note In order to use this module, the following code has to be added to a .c file: + * \code + * nrf_nvic_state_t nrf_nvic_state = {0}; + * \endcode + * + * @note Definitions and declarations starting with __ (double underscore) in this header file are + * not intended for direct use by the application. + * + * @brief APIs for the accessing NVIC when using a SoftDevice. + * + */ + +#ifndef NRF_NVIC_H__ +#define NRF_NVIC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_NVIC_DEFINES Defines + * @{ */ + +/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions + * @{ */ + +#define __NRF_NVIC_NVMC_IRQn \ + (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ + number in the MDK. */ + +#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ + +/**@brief Interrupt priority levels used by the SoftDevice. */ +#define __NRF_NVIC_SD_IRQ_PRIOS \ + ((uint8_t)((1U << 0) /**< Priority level high .*/ \ + | (1U << 1) /**< Priority level medium. */ \ + | (1U << 4) /**< Priority level low. */ \ + )) + +/**@brief Interrupt priority levels available to the application. */ +#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ +#define __NRF_NVIC_SD_IRQS_0 \ + ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ + (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ + (1U << (uint32_t)SWI5_IRQn))) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ +#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) + +/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ +#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) + +/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ +#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) + +/**@} */ + +/**@} */ + +/**@addtogroup NRF_NVIC_VARIABLES Variables + * @{ */ + +/**@brief Type representing the state struct for the SoftDevice NVIC module. */ +typedef struct { + uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ + uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ +} nrf_nvic_state_t; + +/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an + * application source file. */ +extern nrf_nvic_state_t nrf_nvic_state; + +/**@} */ + +/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions + * @{ */ + +/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. + * + * @retval The value of PRIMASK prior to disabling the interrupts. + */ +__STATIC_INLINE int __sd_nvic_irq_disable(void); + +/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. + */ +__STATIC_INLINE void __sd_nvic_irq_enable(void); + +/**@brief Checks if IRQn is available to application + * @param[in] IRQn IRQ to check + * + * @retval 1 (true) if the IRQ to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); + +/**@brief Checks if priority is available to application + * @param[in] priority priority to check + * + * @retval 1 (true) if the priority to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); + +/**@} */ + +/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions + * @{ */ + +/**@brief Enable External Interrupt. + * @note Corresponds to NVIC_EnableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was enabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); + +/**@brief Disable External Interrupt. + * @note Corresponds to NVIC_DisableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was disabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); + +/**@brief Get Pending Interrupt. + * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. + * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. + * + * @retval ::NRF_SUCCESS The interrupt is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); + +/**@brief Set Pending Interrupt. + * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt is set pending. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); + +/**@brief Clear Pending Interrupt. + * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); + +/**@brief Set Interrupt Priority. + * @note Corresponds to NVIC_SetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * @pre Priority is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. + * @param[in] priority A valid IRQ priority for use by the application. + * + * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); + +/**@brief Get Interrupt Priority. + * @note Corresponds to NVIC_GetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. + * @param[out] p_priority Return value from NVIC_GetPriority. + * + * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); + +/**@brief System Reset. + * @note Corresponds to NVIC_SystemReset in CMSIS. + * + * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN + */ +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); + +/**@brief Enter critical region. + * + * @post Application interrupts will be disabled. + * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each + * execution context + * @sa sd_nvic_critical_region_exit + * + * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); + +/**@brief Exit critical region. + * + * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. + * @post If not in a nested critical region, the application interrupts will restored to the state before + * ::sd_nvic_critical_region_enter was called. + * + * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa + * sd_nvic_critical_region_enter. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); + +/**@} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE int __sd_nvic_irq_disable(void) +{ + int pm = __get_PRIMASK(); + __disable_irq(); + return pm; +} + +__STATIC_INLINE void __sd_nvic_irq_enable(void) +{ + __enable_irq(); +} + +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) +{ + if (IRQn < 32) { + return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; + } else if (IRQn < 64) { + return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; + } else { + return 1; + } +} + +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) +{ + if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { + return 0; + } + return 1; +} + +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= + (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); + } else { + NVIC_EnableIRQ(IRQn); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); + } else { + NVIC_DisableIRQ(IRQn); + } + + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_pending_irq = NVIC_GetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_SetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_ClearPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (!__sd_nvic_is_app_accessible_priority(priority)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + NVIC_SetPriority(IRQn, (uint32_t)priority); + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) +{ + NVIC_SystemReset(); + return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) +{ + int was_masked = __sd_nvic_irq_disable(); + if (!nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__cr_flag = 1; + nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); + NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; + nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); + NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; + *p_is_nested_critical_region = 0; + } else { + *p_is_nested_critical_region = 1; + } + if (!was_masked) { + __sd_nvic_irq_enable(); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) +{ + if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { + int was_masked = __sd_nvic_irq_disable(); + NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; + NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; + nrf_nvic_state.__cr_flag = 0; + if (!was_masked) { + __sd_nvic_irq_enable(); + } + } + + return NRF_SUCCESS; +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif + +#endif // NRF_NVIC_H__ + +/**@} */ diff --git a/variants/xiao_ble/softdevice/nrf_sdm.h b/variants/xiao_ble/softdevice/nrf_sdm.h new file mode 100644 index 000000000..2786a86a4 --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_sdm.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_sdm_api SoftDevice Manager API + @{ + + @brief APIs for SoftDevice management. + +*/ + +#ifndef NRF_SDM_H__ +#define NRF_SDM_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_sdm.h" +#include "nrf_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ +#ifdef NRFSOC_DOXYGEN +/// Declared in nrf_mbr.h +#define MBR_SIZE 0 +#warning test +#endif + +/** @brief The major version for the SoftDevice binary distributed with this header file. */ +#define SD_MAJOR_VERSION (7) + +/** @brief The minor version for the SoftDevice binary distributed with this header file. */ +#define SD_MINOR_VERSION (3) + +/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ +#define SD_BUGFIX_VERSION (0) + +/** @brief The SoftDevice variant of this firmware. */ +#define SD_VARIANT_ID 140 + +/** @brief The full version number for the SoftDevice binary this header file was distributed + * with, as a decimal number in the form Mmmmbbb, where: + * - M is major version (one or more digits) + * - mmm is minor version (three digits) + * - bbb is bugfix version (three digits). */ +#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) + +/** @brief SoftDevice Manager SVC Base number. */ +#define SDM_SVC_BASE 0x10 + +/** @brief SoftDevice unique string size in bytes. */ +#define SD_UNIQUE_STR_SIZE 20 + +/** @brief Invalid info field. Returned when an info field does not exist. */ +#define SDM_INFO_FIELD_INVALID (0) + +/** @brief Defines the SoftDevice Information Structure location (address) as an offset from +the start of the SoftDevice (without MBR)*/ +#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) + +/** @brief Defines the absolute SoftDevice Information Structure location (address) when the + * SoftDevice is installed just above the MBR (the usual case). */ +#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) + +/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the + * SoftDevice base address. The size value is of type uint8_t. */ +#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) + +/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. + * The size value is of type uint32_t. */ +#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) + +/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value + * is of type uint16_t. */ +#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) + +/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID + * is of type uint32_t. */ +#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) + +/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in + * the same format as @ref SD_VERSION, stored as an uint32_t. */ +#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) + +/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. + * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. + */ +#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) + +/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value + * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is + * installed just above the MBR (the usual case). */ +#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base + * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above + * the MBR (the usual case). */ +#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) + +/** @brief Defines the amount of flash that is used by the SoftDevice. + * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed + * just above the MBR (the usual case). + */ +#define SD_FLASH_SIZE 0x26000 + +/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual + * case). */ +#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the + * usual case). */ +#define SD_ID_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_VERSION_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges + * @{ */ +#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ +#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ +/**@} */ + +/**@defgroup NRF_FAULT_IDS Fault ID types + * @{ */ +#define NRF_FAULT_ID_SD_ASSERT \ + (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ +#define NRF_FAULT_ID_APP_MEMACC \ + (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ + in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ + register violation the info parameter will contain the sub-region number of \ + PREGION[0], on whose address range the disallowed write access caused the \ + memory access fault. */ +/**@} */ + +/** @} */ + +/** @addtogroup NRF_SDM_ENUMS Enumerations + * @{ */ + +/**@brief nRF SoftDevice Manager API SVC numbers. */ +enum NRF_SD_SVCS { + SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ + SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ + SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ + SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ + SVC_SDM_LAST /**< Placeholder for last SDM SVC */ +}; + +/** @} */ + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ + +/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy + * @{ */ + +#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ +#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ +#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ +#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ +#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ +#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ +#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ +#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ +#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ +#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ +#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ +#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ + +/** @} */ + +/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources + * @{ */ + +#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ +#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ +#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ + +/** @} */ + +/** @} */ + +/** @addtogroup NRF_SDM_TYPES Types + * @{ */ + +/**@brief Type representing LFCLK oscillator source. */ +typedef struct { + uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ + uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second + units (nRF52: 1-32). + @note To avoid excessive clock drift, 0.5 degrees Celsius is the + maximum temperature change allowed in one calibration timer + interval. The interval should be selected to ensure this. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ + uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration + intervals) the RC oscillator shall be calibrated if the temperature + hasn't changed. + 0: Always calibrate even if the temperature hasn't changed. + 1: Only calibrate if the temperature has changed (legacy - nRF51 only). + 2-33: Check the temperature and only calibrate if it has changed, + however calibration will take place every rc_temp_ctiv + intervals in any case. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. + + @note For nRF52, the application must ensure calibration at least once + every 8 seconds to ensure +/-500 ppm clock stability. The + recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is + rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at + least once every 8 seconds and for temperature changes of 0.5 + degrees Celsius every 4 seconds. See the Product Specification + for the nRF52 device being used for more information.*/ + uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing + windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ +} nrf_clock_lf_cfg_t; + +/**@brief Fault Handler type. + * + * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. + * The protocol stack will be in an undefined state when this happens and the only way to recover will be to + * perform a reset, using e.g. CMSIS NVIC_SystemReset(). + * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). + * + * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. + * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may + * continously transmit packets. + * + * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. + * + * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. + * @param[in] pc The program counter of the instruction that triggered the fault. + * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. + * + * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time + * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the + * one that triggered the fault. + */ +typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); + +/** @} */ + +/** @addtogroup NRF_SDM_FUNCTIONS Functions + * @{ */ + +/**@brief Enables the SoftDevice and by extension the protocol stack. + * + * @note Some care must be taken if a low frequency clock source is already running when calling this function: + * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new + * clock source will be started. + * + * @note This function has no effect when returning with an error. + * + * @post If return code is ::NRF_SUCCESS + * - SoC library and protocol stack APIs are made available. + * - A portion of RAM will be unavailable (see relevant SDS documentation). + * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). + * - Interrupts will not arrive from protected peripherals or interrupts. + * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. + * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). + * - Chosen low frequency clock source will be running. + * + * @param p_clock_lf_cfg Low frequency clock source and accuracy. + If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 + In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to + the actual characteristics of your XTAL clock. + * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. + * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has + an illegal priority level. + * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. + */ +SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, + sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); + +/**@brief Disables the SoftDevice and by extension the protocol stack. + * + * Idempotent function to disable the SoftDevice. + * + * @post SoC library and protocol stack APIs are made unavailable. + * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). + * @post All peripherals used by the SoftDevice will be reset to default values. + * @post All of RAM become available. + * @post All interrupts are forwarded to the application. + * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); + +/**@brief Check if the SoftDevice is enabled. + * + * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice + * + * This function is only intended to be called when a bootloader is enabled. + * + * @param[in] address The base address of the interrupt vector table for forwarded interrupts. + + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SDM_H__ + +/** + @} +*/ diff --git a/variants/xiao_ble/softdevice/nrf_soc.h b/variants/xiao_ble/softdevice/nrf_soc.h new file mode 100644 index 000000000..c649ca836 --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_soc.h @@ -0,0 +1,1046 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_soc_api SoC Library API + * @{ + * + * @brief APIs for the SoC library. + * + */ + +#ifndef NRF_SOC_H__ +#define NRF_SOC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_SOC_DEFINES Defines + * @{ */ + +/**@brief The number of the lowest SVC number reserved for the SoC library. */ +#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ +#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ + +/**@brief Guaranteed time for application to process radio inactive notification. */ +#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) + +/**@brief The minimum allowed timeslot extension time. */ +#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) + +/**@brief The maximum processing time to handle a timeslot extension. */ +#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) + +/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ +#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) + +#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ +#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ +#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ + +#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ +#define SD_EVT_IRQHandler \ + (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ + The default interrupt priority for this handler is set to 6 */ +#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ +#define RADIO_NOTIFICATION_IRQHandler \ + (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ + The default interrupt priority for this handler is set to 6 */ +#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ +#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ + +#define NRF_RADIO_DISTANCE_MAX_US \ + (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ + nrf_radio_request_normal_t) in the request. */ + +#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ + (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ + +#define NRF_RADIO_START_JITTER_US \ + (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ + ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ + (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) + +/**@} */ + +/**@addtogroup NRF_SOC_ENUMS Enumerations + * @{ */ + +/**@brief The SVC numbers used by the SVC functions in the SoC library. */ +enum NRF_SOC_SVCS { + SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, + SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, + SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, + SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, + SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, + SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, + SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, + SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, + SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, + SD_FLASH_WRITE = SOC_SVC_BASE + 9, + SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, + SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, + SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, + SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, + SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, + SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, + SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, + SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, + SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, + SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, + SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, + SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, + SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, + SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, + SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, + SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, + SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, + SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, + SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, + SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, + SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, + SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, + SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, + SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, + SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, + SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, + SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, + SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, + SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, + SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, + SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, + SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, + SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, + SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, + SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, + SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, + SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, + SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, + SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 +}; + +/**@brief Possible values of a ::nrf_mutex_t. */ +enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; + +/**@brief Power modes. */ +enum NRF_POWER_MODES { + NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ + NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ +}; + +/**@brief Power failure thresholds */ +enum NRF_POWER_THRESHOLDS { + NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ +}; + +/**@brief Power failure thresholds for high voltage */ +enum NRF_POWER_THRESHOLDVDDHS { + NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ +}; + +/**@brief DC/DC converter modes. */ +enum NRF_POWER_DCDC_MODES { + NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ + NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ +}; + +/**@brief Radio notification distances. */ +enum NRF_RADIO_NOTIFICATION_DISTANCES { + NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ + NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ +}; + +/**@brief Radio notification types. */ +enum NRF_RADIO_NOTIFICATION_TYPES { + NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and + disabled. */ +}; + +/**@brief The Radio signal callback types. */ +enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { + NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ +}; + +/**@brief The actions requested by the signal callback. + * + * This code gives the SOC instructions about what action to take when the signal callback has + * returned. + */ +enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { + NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current + timeslot. Maximum execution time for this action: + @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. + This action must be started at least + @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before + the end of the timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ +}; + +/**@brief Radio timeslot high frequency clock source configuration. */ +enum NRF_RADIO_HFCLK_CFG { + NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the + external crystal for the whole duration of the timeslot. This should be the + preferred option for events that use the radio or require high timing accuracy. + @note The SoftDevice will automatically turn on and off the external crystal, + at the beginning and end of the timeslot, respectively. The crystal may also + intentionally be left running after the timeslot, in cases where it is needed + by the SoftDevice shortly after the end of the timeslot. */ + NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. + The RC oscillator may be the clock source in part or for the whole duration of the + timeslot. The RC oscillator's accuracy must therefore be taken into consideration. + @note If the application will use the radio peripheral in timeslots with this + configuration, it must make sure that the crystal is running and stable before + starting the radio. */ +}; + +/**@brief Radio timeslot priorities. */ +enum NRF_RADIO_PRIORITY { + NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ + NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ +}; + +/**@brief Radio timeslot request type. */ +enum NRF_RADIO_REQUEST_TYPE { + NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first + request in a session. */ + NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ +}; + +/**@brief SoC Events. */ +enum NRF_SOC_EVTS { + NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ + NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ + NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ + NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ + NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ + NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ + NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was + invalid. */ + NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ + NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ + NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ + NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ + NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ + NRF_EVT_NUMBER_OF_EVTS +}; + +/**@} */ + +/**@addtogroup NRF_SOC_STRUCTURES Structures + * @{ */ + +/**@brief Represents a mutex for use with the nrf_mutex functions. + * @note Accessing the value directly is not safe, use the mutex functions! + */ +typedef volatile uint8_t nrf_mutex_t; + +/**@brief Parameters for a request for a timeslot as early as possible. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ + uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref + NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ +} nrf_radio_request_earliest_t; + +/**@brief Parameters for a normal radio timeslot request. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US + microseconds). */ + uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ +} nrf_radio_request_normal_t; + +/**@brief Radio timeslot request parameters. */ +typedef struct { + uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ + union { + nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ + nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ + } params; /**< Parameter union. */ +} nrf_radio_request_t; + +/**@brief Return parameters of the radio timeslot signal callback. */ +typedef struct { + uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref + NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ + union { + struct { + nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ + } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ + struct { + uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref + NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ + } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ + } params; /**< Parameter union. */ +} nrf_radio_signal_callback_return_param_t; + +/**@brief The radio timeslot signal callback type. + * + * @note In case of invalid return parameters, the radio timeslot will automatically end + * immediately after returning from the signal callback and the + * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. + * @note The returned struct pointer must remain valid after the signal callback + * function returns. For instance, this means that it must not point to a stack variable. + * + * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. + * + * @return Pointer to structure containing action requested by the application. + */ +typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); + +/**@brief AES ECB parameter typedefs */ +typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ +typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ +typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ + +/**@brief AES ECB data structure */ +typedef struct { + soc_ecb_key_t key; /**< Encryption key. */ + soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ + soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ +} nrf_ecb_hal_data_t; + +/**@brief AES ECB block. Used to provide multiple blocks in a single call + to @ref sd_ecb_blocks_encrypt.*/ +typedef struct { + soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ + soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ + soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ +} nrf_ecb_hal_data_block_t; + +/**@} */ + +/**@addtogroup NRF_SOC_FUNCTIONS Functions + * @{ */ + +/**@brief Initialize a mutex. + * + * @param[in] p_mutex Pointer to the mutex to initialize. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); + +/**@brief Attempt to acquire a mutex. + * + * @param[in] p_mutex Pointer to the mutex to acquire. + * + * @retval ::NRF_SUCCESS The mutex was successfully acquired. + * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. + */ +SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); + +/**@brief Release a mutex. + * + * @param[in] p_mutex Pointer to the mutex to release. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); + +/**@brief Query the capacity of the application random pool. + * + * @param[out] p_pool_capacity The capacity of the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); + +/**@brief Get number of random bytes available to the application. + * + * @param[out] p_bytes_available The number of bytes currently available in the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); + +/**@brief Get random bytes from the application pool. + * + * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. + * @param[in] length Number of bytes to take from pool and place in p_buff. + * + * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. + * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes + * available. + */ +SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); + +/**@brief Gets the reset reason register. + * + * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); + +/**@brief Clears the bits of the reset reason register. + * + * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); + +/**@brief Sets the power mode when in CPU sleep. + * + * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait + * + * @retval ::NRF_SUCCESS The power mode was set. + * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. + */ +SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); + +/**@brief Puts the chip in System OFF mode. + * + * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN + */ +SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); + +/**@brief Enables or disables the power-fail comparator. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); + +/**@brief Enables or disables the USB power ready event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); + +/**@brief Enables or disables the power USB-detected event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); + +/**@brief Enables or disables the power USB-removed event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); + +/**@brief Get USB supply status register content. + * + * @param[out] usbregstatus The content of USBREGSTATUS register. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); + +/**@brief Sets the power failure comparator threshold value. + * + * @note: Power failure comparator threshold setting. This setting applies both for normal voltage + * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to + * VDDH only). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); + +/**@brief Sets the power failure comparator threshold value for high voltage. + * + * @note: Power failure comparator threshold setting for high voltage mode (supply connected to + * VDDH only). This setting does not apply for normal voltage mode (supply connected to both + * VDD and VDDH). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. + * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. + * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); + +/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. + * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); + +/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be set in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[out] p_gpregret Contents of the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). + * + * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. + */ +SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). + * + * For more details on the REG0 stage, please see product specification. + * + * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. + */ +SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); + +/**@brief Request the high frequency crystal oscillator. + * + * Will start the high frequency crystal oscillator, the startup time of the crystal varies + * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_release + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); + +/**@brief Releases the high frequency crystal oscillator. + * + * Will stop the high frequency crystal oscillator, this happens immediately. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_request + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); + +/**@brief Checks if the high frequency crystal oscillator is running. + * + * @see sd_clock_hfclk_request + * @see sd_clock_hfclk_release + * + * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); + +/**@brief Waits for an application event. + * + * An application event is either an application interrupt or a pended interrupt when the interrupt + * is disabled. + * + * When the application waits for an application event by calling this function, an interrupt that + * is enabled will be taken immediately on pending since this function will wait in thread mode, + * then the execution will return in the application's main thread. + * + * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M + * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets + * pended, this function will return to the application's main thread. + * + * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ + * in order to sleep using this function. This is only necessary for disabled interrupts, as + * the interrupt handler will clear the pending flag automatically for enabled interrupts. + * + * @note If an application interrupt has happened since the last time sd_app_evt_wait was + * called this function will return immediately and not go to sleep. This is to avoid race + * conditions that can occur when a flag is updated in the interrupt handler and processed + * in the main loop. + * + * @post An application interrupt has happened or a interrupt pending flag is set. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); + +/**@brief Get PPI channel enable register contents. + * + * @param[out] p_channel_enable The contents of the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); + +/**@brief Set PPI channel enable register. + * + * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); + +/**@brief Clear PPI channel enable register. + * + * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); + +/**@brief Assign endpoints to a PPI channel. + * + * @param[in] channel_num Number of the PPI channel to assign. + * @param[in] evt_endpoint Event endpoint of the PPI channel. + * @param[in] task_endpoint Task endpoint of the PPI channel. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, + sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); + +/**@brief Task to enable a channel group. + * + * @param[in] group_num Number of the channel group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); + +/**@brief Task to disable a channel group. + * + * @param[in] group_num Number of the PPI group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); + +/**@brief Assign PPI channels to a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[in] channel_msk Mask of the channels to assign to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); + +/**@brief Gets the PPI channels of a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[out] p_channel_msk Mask of the channels assigned to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); + +/**@brief Configures the Radio Notification signal. + * + * @note + * - The notification signal latency depends on the interrupt priority settings of SWI used + * for notification signal. + * - To ensure that the radio notification signal behaves in a consistent way, the radio + * notifications must be configured when there is no protocol stack or other SoftDevice + * activity in progress. It is recommended that the radio notification signal is + * configured directly after the SoftDevice has been enabled. + * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice + * will interrupt the application to do Radio Event preparation. + * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have + * to shorten the connection events to have time for the Radio Notification signals. + * + * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio + * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is + * recommended (but not required) to be used with + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. + * + * @param[in] distance Distance between the notification signal and start of radio activity, see @ref + * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or + * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. + * + * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. + * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all + * running activities and retry. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); + +/**@brief Encrypts a block according to the specified parameters. + * + * 128-bit AES encryption. + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input + * parameters and one output parameter). + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); + +/**@brief Encrypts multiple data blocks provided as an array of data block structures. + * + * @details: Performs 128-bit AES encryption on multiple data blocks + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in] block_count Count of blocks in the p_data_blocks array. + * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of + * @ref nrf_ecb_hal_data_block_t structures. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); + +/**@brief Gets any pending events generated by the SoC API. + * + * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. + * + * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. + * + * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. + * @retval ::NRF_ERROR_NOT_FOUND No pending events. + */ +SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); + +/**@brief Get the temperature measured on the chip + * + * This function will block until the temperature measurement is done. + * It takes around 50 us from call to return. + * + * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. + * + * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp + */ +SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); + +/**@brief Flash Write + * + * Commands to write a buffer to flash + * + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * write has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS + * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. + * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is + * protected. + * + * + * @param[in] p_dst Pointer to start of flash location to be written. + * @param[in] p_src Pointer to buffer with data to be written. + * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one + * flash page. See the device's Product Specification for details. + * + * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. + * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); + +/**@brief Flash Erase page + * + * Commands to erase a flash page + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * erase has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is + * protected. + * + * + * @param[in] page_number Page number of the page to erase + * + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); + +/**@brief Opens a session for radio timeslot requests. + * + * @note Only one session can be open at a time. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot + * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed + * by the application. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 + * interrupt occurs. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO + * interrupt occurs. + * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This + * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). + * + * @param[in] p_radio_signal_callback The signal callback. + * + * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. + * @retval ::NRF_ERROR_BUSY If session cannot be opened. + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); + +/**@brief Closes a session for radio timeslot requests. + * + * @note Any current radio timeslot will be finished before the session is closed. + * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. + * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED + * event is received. + * + * @retval ::NRF_ERROR_FORBIDDEN If session not opened. + * @retval ::NRF_ERROR_BUSY If session is currently being closed. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); + +/**@brief Requests a radio timeslot. + * + * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST + * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref + * NRF_RADIO_REQ_TYPE_EARLIEST. + * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by + * p_request->distance_us and is given relative to the start of the previous timeslot. + * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. + * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this + * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. + * The application may then try to schedule the first radio timeslot again. + * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). + * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. + * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. + * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the + * specified radio timeslot start, but this does not affect the actual start time of the timeslot. + * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency + * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is + * guaranteed to be clocked from the external crystal. + * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral + * during the radio timeslot. + * + * @param[in] p_request Pointer to the request parameters. + * + * @retval ::NRF_ERROR_FORBIDDEN Either: + * - The session is not open. + * - The session is not IDLE. + * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. + * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a + * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. + * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); + +/**@brief Write register protected by the SoftDevice + * + * This function writes to a register that is write-protected by the SoftDevice. Please refer to your + * SoftDevice Specification for more details about which registers that are protected by SoftDevice. + * This function can write to the following protected peripheral: + * - ACL + * + * @note Protected registers may be read directly. + * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in + * the register has not changed. See the Product Specification for more details about register + * properties. + * + * @param[in] p_register Pointer to register to be written. + * @param[in] value Value to be written to the register. + * + * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. + * @retval ::NRF_SUCCESS Value successfully written to register. + * + */ +SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); + +/**@} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SOC_H__ + +/**@} */ diff --git a/variants/xiao_ble/softdevice/nrf_svc.h b/variants/xiao_ble/softdevice/nrf_svc.h new file mode 100644 index 000000000..1de44656f --- /dev/null +++ b/variants/xiao_ble/softdevice/nrf_svc.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NRF_SVC__ +#define NRF_SVC__ + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Supervisor call declaration. + * + * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. + * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. + * + * @param[in] number The SVC number to be used. + * @param[in] return_type The return type of the SVC function. + * @param[in] signature Function signature. The function can have at most four arguments. + */ + +#ifdef SVCALL_AS_NORMAL_FUNCTION +#define SVCALL(number, return_type, signature) return_type signature +#else + +#ifndef SVCALL +#if defined(__CC_ARM) +#define SVCALL(number, return_type, signature) return_type __svc(number) signature +#elif defined(__GNUC__) +#ifdef __cplusplus +#define GCC_CAST_CPP (uint16_t) +#else +#define GCC_CAST_CPP +#endif +#define SVCALL(number, return_type, signature) \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ + __attribute__((unused)) static return_type signature \ + { \ + __asm("svc %0\n" \ + "bx r14" \ + : \ + : "I"(GCC_CAST_CPP number) \ + : "r0"); \ + } \ + _Pragma("GCC diagnostic pop") + +#elif defined(__ICCARM__) +#define PRAGMA(x) _Pragma(#x) +#define SVCALL(number, return_type, signature) \ + PRAGMA(swi_number = (number)) \ + __swi return_type signature; +#else +#define SVCALL(number, return_type, signature) return_type signature +#endif +#endif // SVCALL + +#endif // SVCALL_AS_NORMAL_FUNCTION + +#ifdef __cplusplus +} +#endif +#endif // NRF_SVC__ From da5bca31edcd091b33d1e6b9b35ea6f807850eb0 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 8 Jun 2024 02:41:46 +1200 Subject: [PATCH 0541/1377] Triple-press not disabling GPS (#4041) * Replace (bool) isAwake with an enum, to track standby states * Tidy-up, extra logging * Rename enum values * Reorder GPSPowerState enum Possibly more intuitive when reading logs * Avoid lego comments https://github.com/meshtastic/firmware/pull/4041/files/de22c57298535f8b94154bffbef64a44af09648c#r1627334779 --- src/gps/GPS.cpp | 74 ++++++++++++++++++++++++++++++++++--------------- src/gps/GPS.h | 10 +++++-- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 4335244f0..17088910a 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -21,6 +21,13 @@ #define GPS_RESET_MODE HIGH #endif +// How many minutes of sleep make it worthwhile to power-off the GPS +// Shorter than this, and GPS will only enter standby +// Affected by lock-time, and config.position.gps_update_interval +#ifndef GPS_STANDBY_THRESHOLD_MINUTES +#define GPS_STANDBY_THRESHOLD_MINUTES 15 +#endif + #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) HardwareSerial *GPS::_serial_gps = &Serial1; #else @@ -767,7 +774,16 @@ GPS::~GPS() void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) { - LOG_INFO("Setting GPS power=%d\n", on); + // Record the current powerState + if (on) + powerState = GPS_AWAKE; + else if (!on && standbyOnly) + powerState = GPS_STANDBY; + else + powerState = GPS_OFF; + + LOG_DEBUG("GPS::powerState=%d\n", powerState); + if (on) { clearBuffer(); // drop any old data waiting in the buffer before re-enabling if (en_gpio) @@ -861,17 +877,21 @@ void GPS::setConnected() * * calls sleep/wake */ -void GPS::setAwake(bool on) +void GPS::setAwake(bool wantAwake) { - if (isAwake != on) { - LOG_DEBUG("WANT GPS=%d\n", on); - isAwake = on; - if (!enabled) { // short circuit if the user has disabled GPS - setGPSPower(false, false, 0); - return; - } - if (on) { + // If user has disabled GPS, make sure it is off, not just in standby + if (!wantAwake && !enabled && powerState != GPS_OFF) { + setGPSPower(false, false, 0); + return; + } + + // If GPS power state needs to change + if ((wantAwake && powerState != GPS_AWAKE) || (!wantAwake && powerState == GPS_AWAKE)) { + LOG_DEBUG("WANT GPS=%d\n", wantAwake); + + // Calculate how long it takes to get a GPS lock + if (wantAwake) { lastWakeStartMsec = millis(); } else { lastSleepStartMsec = millis(); @@ -883,23 +903,31 @@ void GPS::setAwake(bool on) GPSCycles++; LOG_DEBUG("GPS Lock took %d, average %d\n", (lastSleepStartMsec - lastWakeStartMsec) / 1000, averageLockTime / 1000); } - if ((int32_t)getSleepTime() - averageLockTime > - 15 * 60 * 1000) { // 15 minutes is probably long enough to make a complete poweroff worth it. - setGPSPower(on, false, getSleepTime() - averageLockTime); + + // If long interval between updates: power off between updates + if ((int32_t)getSleepTime() - averageLockTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) { + setGPSPower(wantAwake, false, getSleepTime() - averageLockTime); return; - } else if ((int32_t)getSleepTime() - averageLockTime > 10000) { // 10 seconds is enough for standby + } + + // If waking frequently: standby only. Would use more power trying to reacquire lock each time + else if ((int32_t)getSleepTime() - averageLockTime > 10000) { // 10 seconds is enough for standby #ifdef GPS_UC6580 - setGPSPower(on, false, getSleepTime() - averageLockTime); + setGPSPower(wantAwake, false, getSleepTime() - averageLockTime); #else - setGPSPower(on, true, getSleepTime() - averageLockTime); + setGPSPower(wantAwake, true, getSleepTime() - averageLockTime); #endif return; } + + // Gradually recover from an abnormally long "time to get lock" if (averageLockTime > 20000) { averageLockTime -= 1000; // eventually want to sleep again. } - if (on) - setGPSPower(true, true, 0); // make sure we don't have a fallthrough where GPS is stuck off + + // Make sure we don't have a fallthrough where GPS is stuck off + if (wantAwake) + setGPSPower(true, true, 0); } } @@ -1005,14 +1033,14 @@ int32_t GPS::runOnce() uint32_t timeAsleep = now - lastSleepStartMsec; auto sleepTime = getSleepTime(); - if (!isAwake && (sleepTime != UINT32_MAX) && + if (powerState != GPS_AWAKE && (sleepTime != UINT32_MAX) && ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - averageLockTime)))) { // We now want to be awake - so wake up the GPS setAwake(true); } // While we are awake - if (isAwake) { + if (powerState == GPS_AWAKE) { // LOG_DEBUG("looking for location\n"); // If we've already set time from the GPS, no need to ask the GPS bool gotTime = (getRTCQuality() >= RTCQualityGPS); @@ -1058,7 +1086,7 @@ int32_t GPS::runOnce() // 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms // if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake. - return isAwake ? GPS_THREAD_INTERVAL : 5000; + return (powerState == GPS_AWAKE) ? GPS_THREAD_INTERVAL : 5000; } // clear the GPS rx buffer as quickly as possible @@ -1589,9 +1617,9 @@ bool GPS::whileIdle() { unsigned int charsInBuf = 0; bool isValid = false; - if (!isAwake) { + if (powerState != GPS_AWAKE) { clearBuffer(); - return isAwake; + return (powerState == GPS_AWAKE); } #ifdef SERIAL_BUFFER_SIZE if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) { diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 77c6c0269..e9ec111a7 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -38,6 +38,12 @@ typedef enum { GNSS_RESPONSE_OK, } GPS_RESPONSE; +enum GPSPowerState : uint8_t { + GPS_OFF = 0, + GPS_AWAKE = 1, + GPS_STANDBY = 2, +}; + // Generate a string representation of DOP const char *getDOPString(uint32_t dop); @@ -78,8 +84,6 @@ class GPS : private concurrency::OSThread */ bool hasValidLocation = false; // default to false, until we complete our first read - bool isAwake = false; // true if we want a location right now - bool isInPowersave = false; bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop() @@ -89,6 +93,8 @@ class GPS : private concurrency::OSThread bool GPSInitFinished = false; // Init thread finished? bool GPSInitStarted = false; // Init thread finished? + GPSPowerState powerState = GPS_OFF; // GPS_AWAKE if we want a location right now + uint8_t numSatellites = 0; CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); From 2fa55b7b6f799fbb294fece500c7c73d2af3d4cc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 8 Jun 2024 09:44:13 -0500 Subject: [PATCH 0542/1377] Remove bandit from extra --- variants/radiomaster_900_bandit_nano/platformio.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/radiomaster_900_bandit_nano/platformio.ini b/variants/radiomaster_900_bandit_nano/platformio.ini index d83c14de2..0d43b8665 100644 --- a/variants/radiomaster_900_bandit_nano/platformio.ini +++ b/variants/radiomaster_900_bandit_nano/platformio.ini @@ -1,7 +1,6 @@ [env:radiomaster_900_bandit_nano] extends = esp32_base board = esp32doit-devkit-v1 -board_level = extra build_flags = ${esp32_base.build_flags} -DRADIOMASTER_900_BANDIT_NANO From 2335352fbe874e212cfebfd71f76745eb53512c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 9 Jun 2024 12:30:47 +0200 Subject: [PATCH 0543/1377] fix native build --- .github/workflows/main_matrix.yml | 75 +------------------------------ 1 file changed, 1 insertion(+), 74 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 800c18b89..ed7f02b3b 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -112,79 +112,6 @@ jobs: package-native: uses: ./.github/workflows/package_amd64.yml - build-native: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - # We now run integration test before other build steps (to quickly see runtime failures) - #- name: Build for native - # run: platformio run -e native - #- name: Integration test - # run: | - #.pio/build/native/program - #& sleep 20 # 5 seconds was not enough - #echo "Simulator started, launching python test..." - #python3 -c 'from meshtastic.test import testSimulator; testSimulator()' - - - name: Build Native - run: bin/build-native.sh - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-native-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | - release/device-*.sh - release/device-*.bat - release/meshtasticd_linux* - - - name: Docker login - if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/login-action@v3 - with: - username: meshtastic - password: ${{ secrets.DOCKER_TOKEN }} - - name: Docker setup - if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/setup-buildx-action@v3 - - - name: Docker build and push tagged versions - if: ${{ github.event_name == 'workflow_dispatch' }} - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - push: true - tags: meshtastic/device-simulator:${{ steps.version.outputs.version }} - - - name: Docker build and push - if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - push: true - tags: meshtastic/device-simulator:latest - - after-checks: - runs-on: ubuntu-latest - needs: [check] - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - gather-artifacts: permissions: contents: write @@ -196,10 +123,10 @@ jobs: build-esp32-s3, build-esp32-c3, build-nrf52, - build-native, build-rpi2040, package-raspbian, package-raspbian-armv7l, + package-native, ] steps: - name: Checkout code From 27ad3da0ac0157907259ca3ab70f81b6a17bd463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 9 Jun 2024 12:37:23 +0200 Subject: [PATCH 0544/1377] reinstate after checks and hope the coffee kicks in --- .github/workflows/main_matrix.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ed7f02b3b..702a7875e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -112,6 +112,16 @@ jobs: package-native: uses: ./.github/workflows/package_amd64.yml + after-checks: + runs-on: ubuntu-latest + needs: [check] + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + gather-artifacts: permissions: contents: write @@ -126,7 +136,7 @@ jobs: build-rpi2040, package-raspbian, package-raspbian-armv7l, - package-native, + package-native ] steps: - name: Checkout code From 46b8e2a850bbfcfa804efd0f5ff292e59ecec47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 9 Jun 2024 12:56:44 +0200 Subject: [PATCH 0545/1377] fix binary location --- .github/workflows/package_amd64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml index 7a5efd154..ae7bf3242 100644 --- a/.github/workflows/package_amd64.yml +++ b/.github/workflows/package_amd64.yml @@ -53,7 +53,7 @@ jobs: mkdir -p .debpkg/usr/lib/systemd/system/ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz - cp meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd + cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml chmod +x .debpkg/usr/sbin/meshtasticd cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service From 095887de4043eaae3ab12f2875446b1d5f1dcba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 9 Jun 2024 13:00:06 +0200 Subject: [PATCH 0546/1377] do the docker dance --- .github/workflows/build_native.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 257bc4176..b92e77ef2 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -50,3 +50,32 @@ jobs: path: | release/meshtasticd_linux_x86_64 bin/config-dist.yaml + + - name: Docker login + if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} + uses: docker/login-action@v3 + with: + username: meshtastic + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Docker setup + if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} + uses: docker/setup-buildx-action@v3 + + - name: Docker build and push tagged versions + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: meshtastic/device-simulator:${{ steps.version.outputs.version }} + + - name: Docker build and push + if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: meshtastic/device-simulator:latest From f59cbc8ffbb9d32e992d4d357fe245ed36d3b4a4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 9 Jun 2024 07:19:25 -0500 Subject: [PATCH 0547/1377] Add firmware repo level secret --- .github/workflows/build_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index b92e77ef2..ff0c2eaf6 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -56,7 +56,7 @@ jobs: uses: docker/login-action@v3 with: username: meshtastic - password: ${{ secrets.DOCKER_TOKEN }} + password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }} - name: Docker setup if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} From 2f99a8dbb8c578987d3bf279db84dd0a065b26c9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 9 Jun 2024 07:40:57 -0500 Subject: [PATCH 0548/1377] Try latest version --- .github/workflows/build_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index ff0c2eaf6..f7c83eddb 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -53,7 +53,7 @@ jobs: - name: Docker login if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/login-action@v3 + uses: docker/login-action@v3.2.0 with: username: meshtastic password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }} From 4da3f202e516ba0b1c3d772957c36a783e3876ef Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 9 Jun 2024 07:55:45 -0500 Subject: [PATCH 0549/1377] Roll-back to v2 --- .github/workflows/build_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index f7c83eddb..115f85bbe 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -53,7 +53,7 @@ jobs: - name: Docker login if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/login-action@v3.2.0 + uses: docker/login-action@v2.2.0 with: username: meshtastic password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }} From 01a214aa5967924af814ca76c4bdcdf2ce41b1b3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 9 Jun 2024 08:32:31 -0500 Subject: [PATCH 0550/1377] Add continues on failing docker steps --- .github/workflows/build_native.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 115f85bbe..8fe8e6c31 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -53,17 +53,20 @@ jobs: - name: Docker login if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} - uses: docker/login-action@v2.2.0 + uses: docker/login-action@v3 + continue-on-error: true # FIXME: Failing docker login auth with: username: meshtastic password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }} - name: Docker setup if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} + continue-on-error: true # FIXME: Failing docker login auth uses: docker/setup-buildx-action@v3 - name: Docker build and push tagged versions if: ${{ github.event_name == 'workflow_dispatch' }} + continue-on-error: true # FIXME: Failing docker login auth uses: docker/build-push-action@v5 with: context: . @@ -73,6 +76,7 @@ jobs: - name: Docker build and push if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} + continue-on-error: true # FIXME: Failing docker login auth uses: docker/build-push-action@v5 with: context: . From 1d98e48bab8d47b3ec4f3f5547ec9d565a523d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 9 Jun 2024 16:33:50 +0200 Subject: [PATCH 0551/1377] Update main_matrix.yml --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 702a7875e..89b71acb8 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -158,7 +158,7 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml + run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml ./bin/device-*.sh ./bin/device-*.bat - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 From 237944aaf05e0b48369d6c9577864ddbd1662f7d Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:02:52 +0200 Subject: [PATCH 0552/1377] Avoid assert on receiving undecryptable packet (#4059) * Send NAK on primary if original packet couldn't be decoded * Add checks for `isDecoded` when accessing `decoded` * Channel index should be of original packet, not of newly allocated NAK --- src/mesh/MeshModule.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 2ef46e4db..04fa250bf 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -62,7 +62,10 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error err, const meshtastic_MeshPacket *p) { - auto r = allocAckNak(err, getFrom(p), p->id, p->channel); + // If the original packet couldn't be decoded, use the primary channel + uint8_t channelIndex = + p->which_payload_variant == meshtastic_MeshPacket_decoded_tag ? p->channel : channels.getPrimaryIndex(); + auto r = allocAckNak(err, getFrom(p), p->id, channelIndex); setReplyTo(r, *p); @@ -114,13 +117,13 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) /// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and /// it needs to to be able to fetch the initial admin packets without yet knowing any channels. - bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (strcasecmp(ch->settings.name, pi.boundChannel) == 0); + bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && strcasecmp(ch->settings.name, pi.boundChannel) == 0); if (!rxChannelOk) { // no one should have already replied! assert(!currentReply); - if (mp.decoded.want_response) { + if (isDecoded && mp.decoded.want_response) { printPacket("packet on wrong channel, returning error", &mp); currentReply = pi.allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp); } else @@ -138,7 +141,8 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) // because currently when the phone sends things, it sends things using the local node ID as the from address. A // better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like // any other node. - if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) { + if (isDecoded && mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && + !currentReply) { pi.sendResponse(mp); ignoreRequest = ignoreRequest || pi.ignoreRequest; // If at least one module asks it, we may ignore a request LOG_INFO("Asked module '%s' to send a response\n", pi.name); @@ -163,7 +167,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) pi.currentRequest = NULL; } - if (mp.decoded.want_response && toUs) { + if (isDecoded && mp.decoded.want_response && toUs) { if (currentReply) { printPacket("Sending response", currentReply); service.sendToMesh(currentReply); @@ -183,7 +187,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) } } - if (!moduleFound) { + if (!moduleFound && isDecoded) { LOG_DEBUG("No modules interested in portnum=%d, src=%s\n", mp.decoded.portnum, (src == RX_SRC_LOCAL) ? "LOCAL" : "REMOTE"); } From a2fb3d23a1545b80d480208d5df917bbf179bb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:03:39 +0200 Subject: [PATCH 0553/1377] Radio Master 900 Bandit Nano Power output interpolation (#4057) * DAC and DB values based on dBm using interpolation * Moved getDACandDB funtion Moved getDACandDB funtion up so it won't conflict with RF95_MAX_POWER * Added DAC output to LOG_INFO Added DAC output to LOG_INFO * Make Trunk Happy --- src/mesh/RF95Interface.cpp | 64 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 5677e6eda..c5356ad3b 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -17,6 +17,48 @@ // if you set power to something higher than 17 or 20 you might fry your board. #define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level +#ifdef RADIOMASTER_900_BANDIT_NANO +// Structure to hold DAC and DB values +typedef struct { + uint8_t dac; + uint8_t db; +} DACDB; + +// Interpolation function +DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) { + DACDB result; + double fraction = (double)(dbm - dbm1) / (dbm2 - dbm1); + result.dac = (uint8_t)(val1.dac + fraction * (val2.dac - val1.dac)); + result.db = (uint8_t)(val1.db + fraction * (val2.db - val1.db)); + return result; +} + +// Function to find the correct DAC and DB values based on dBm using interpolation +DACDB getDACandDB(uint8_t dbm) { + // Predefined values + static const struct { + uint8_t dbm; + DACDB values; + } dbmToDACDB[] = { + {20, {168, 2}}, // 100mW + {24, {148, 6}}, // 250mW + {27, {128, 9}}, // 500mW + {30, {90, 12}} // 1000mW + }; + const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); + + // Find the interval dbm falls within and interpolate + for (int i = 0; i < numValues - 1; i++) { + if (dbm >= dbmToDACDB[i].dbm && dbm <= dbmToDACDB[i + 1].dbm) { + return interpolate(dbm, dbmToDACDB[i].dbm, dbmToDACDB[i + 1].dbm, dbmToDACDB[i].values, dbmToDACDB[i + 1].values); + } + } + + // Return a default value if no match is found and default to 100mW + DACDB defaultValue = {168, 2}; + return defaultValue; +} +#endif RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) @@ -52,9 +94,16 @@ bool RF95Interface::init() { RadioLibInterface::init(); +#ifdef RADIOMASTER_900_BANDIT_NANO + // DAC and DB values based on dBm using interpolation + DACDB dacDbValues = getDACandDB(power); + int8_t powerDAC = dacDbValues.dac; + power = dacDbValues.db; +#endif + if (power > RF95_MAX_POWER) // This chip has lower power limits than some power = RF95_MAX_POWER; - + limitPower(); iface = lora = new RadioLibRF95(&module); @@ -67,7 +116,13 @@ bool RF95Interface::init() // enable PA #ifdef RF95_PA_EN #if defined(RF95_PA_DAC_EN) - dacWrite(RF95_PA_EN, RF95_PA_LEVEL); + #ifdef RADIOMASTER_900_BANDIT_NANO + // Use calculated DAC value + dacWrite(RF95_PA_EN, powerDAC); + #else + // Use Value set in /*/variant.h + dacWrite(RF95_PA_EN, RF95_PA_LEVEL); + #endif #endif #endif @@ -107,6 +162,9 @@ bool RF95Interface::init() LOG_INFO("Frequency set to %f\n", getFreq()); LOG_INFO("Bandwidth set to %f\n", bw); LOG_INFO("Power output set to %d\n", power); +#ifdef RADIOMASTER_900_BANDIT_NANO + LOG_INFO("DAC output set to %d\n", powerDAC); +#endif if (res == RADIOLIB_ERR_NONE) res = lora->setCRC(RADIOLIB_SX126X_LORA_CRC_ON); @@ -259,4 +317,4 @@ bool RF95Interface::sleep() #endif return true; -} \ No newline at end of file +} From 24458a73d6cbdba9f4d731eb0be3f07ffd0973f1 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:03:53 +0200 Subject: [PATCH 0554/1377] Add missing hops in traceroute as "unkown" (#4056) E.g. in case a node couldn't decrypt the packet --- src/modules/TraceRouteModule.cpp | 28 ++++++++++++++++++++++++---- src/modules/TraceRouteModule.h | 3 +++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index aa0b6a1eb..f390aafcd 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -16,17 +16,37 @@ bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { auto &incoming = p.decoded; - // Only append an ID for the request (one way) and if we are not the destination (the reply will have our NodeNum already) - if (!incoming.request_id && p.to != nodeDB->getNodeNum()) { - appendMyID(r); - printRoute(r, p.from, NODENUM_BROADCAST); + // Only append IDs for the request (one way) + if (!incoming.request_id) { + // Insert unknown hops if necessary + insertUnknownHops(p, r); + // Don't add ourselves if we are the destination (the reply will have our NodeNum already) + if (p.to != nodeDB->getNodeNum()) { + appendMyID(r); + printRoute(r, p.from, NODENUM_BROADCAST); + } // Set updated route to the payload of the to be flooded packet p.decoded.payload.size = pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r); } } +void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) +{ + // Only insert unknown hops if hop_start is valid + if (p.hop_start != 0 && p.hop_limit <= p.hop_start) { + uint8_t hopsTaken = p.hop_start - p.hop_limit; + int8_t diff = hopsTaken - r->route_count; + for (uint8_t i = 0; i < diff; i++) { + if (r->route_count < sizeof(r->route) / sizeof(r->route[0])) { + r->route[r->route_count] = NODENUM_BROADCAST; // This will represent an unknown hop + r->route_count += 1; + } + } + } +} + void TraceRouteModule::appendMyID(meshtastic_RouteDiscovery *updated) { // Length of route array can normally not be exceeded due to the max. hop_limit of 7 diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index 15e01debd..18a5ac0cb 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -19,6 +19,9 @@ class TraceRouteModule : public ProtobufModule void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override; private: + // Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit + void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); + // Call to add your ID to the route array of a RouteDiscovery message void appendMyID(meshtastic_RouteDiscovery *r); From 4f906ae3ae49a2f9a2e6f9ed5bc9a1921c53882c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:52:37 -0500 Subject: [PATCH 0555/1377] [create-pull-request] automated change (#4064) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 700044e6f..a26da1996 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 12 +build = 13 From 62b310ac5c868075ef08e4670e2c53a4726cfd43 Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Mon, 10 Jun 2024 15:10:17 +0200 Subject: [PATCH 0556/1377] Relax changes from #4001 to allow GPS and NTP as trusted sources (#4068) --- src/modules/PositionModule.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 002111171..49f2b808b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -209,13 +209,13 @@ meshtastic_MeshPacket *PositionModule::allocReply() p.ground_speed = localPosition.ground_speed; // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other - // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless - // devices can get time. - if (getRTCQuality() < RTCQualityGPS) { + // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS or NTP to include the time, so that devices + // without can get time. + if (getRTCQuality() < RTCQualityNTP) { LOG_INFO("Stripping time %u from position send\n", p.time); p.time = 0; } else { - p.time = getValidTime(RTCQualityGPS); + p.time = getValidTime(RTCQualityNTP); LOG_INFO("Providing time to mesh %u\n", p.time); } From 8b1b6faf896df177f585b2cb5f7799341815911d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Tue, 11 Jun 2024 21:51:39 +0200 Subject: [PATCH 0557/1377] Added Radiomaster Bandit Nano and Radiomaster Bandit Micro to default_envs. (#4077) Added Radiomaster Bandit Micro, it shares the same code and settings as Bandit Nano --- platformio.ini | 2 ++ .../platformio.ini | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 variants/radiomaster_900_bandit_micro/platformio.ini diff --git a/platformio.ini b/platformio.ini index 85bce3279..7ed794a6e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,6 +31,8 @@ default_envs = tbeam ;default_envs = rak4631 ;default_envs = rak10701 ;default_envs = wio-e5 +;default_envs = radiomaster_900_bandit_nano +;default_envs = radiomaster_900_bandit_micro extra_configs = arch/*/*.ini diff --git a/variants/radiomaster_900_bandit_micro/platformio.ini b/variants/radiomaster_900_bandit_micro/platformio.ini new file mode 100644 index 000000000..9e54f5859 --- /dev/null +++ b/variants/radiomaster_900_bandit_micro/platformio.ini @@ -0,0 +1,19 @@ +; +; This uses the same code and settings as the Radio Master Bandit Nano (https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module) +; +; Link to the unit : https://www.radiomasterrc.com/products/bandit-micro-expresslrs-rf-module +; +[env:radiomaster_900_bandit_micro] +extends = esp32_base +board = esp32doit-devkit-v1 +build_flags = + ${esp32_base.build_flags} + -DRADIOMASTER_900_BANDIT_NANO + -DVTABLES_IN_FLASH=1 + -DCONFIG_DISABLE_HAL_LOCKS=1 + -O2 + -Ivariants/radiomaster_900_bandit_nano +board_build.f_cpu = 240000000L +upload_protocol = esptool +lib_deps = + ${esp32_base.lib_deps} \ No newline at end of file From 7f2647abb18303c1e1af8325fbd53e3129e221dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:52:02 -0500 Subject: [PATCH 0558/1377] [create-pull-request] automated change (#4078) Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.cpp | 1 + src/mesh/generated/meshtastic/config.pb.h | 36 ++++++++++++++++--- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index a641c5ce4..8c8048798 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a641c5ce4fca158d18ca3cffc92ac7a10f9b6a04 +Subproject commit 8c8048798c1b1773b9d8f2c32eb3f4c9e72f8218 diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index f05e47573..bb82198c0 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -46,3 +46,4 @@ PB_BIND(meshtastic_Config_BluetoothConfig, meshtastic_Config_BluetoothConfig, AU + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 0830ed851..781538d11 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -182,6 +182,25 @@ typedef enum _meshtastic_Config_DisplayConfig_DisplayMode { meshtastic_Config_DisplayConfig_DisplayMode_COLOR = 3 } meshtastic_Config_DisplayConfig_DisplayMode; +typedef enum _meshtastic_Config_DisplayConfig_CompassOrientation { + /* The compass and the display are in the same orientation. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0 = 0, + /* Rotate the compass by 90 degrees. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90 = 1, + /* Rotate the compass by 180 degrees. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180 = 2, + /* Rotate the compass by 270 degrees. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 = 3, + /* Don't rotate the compass, but invert the result. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED = 4, + /* Rotate the compass by 90 degrees and invert. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED = 5, + /* Rotate the compass by 180 degrees and invert. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED = 6, + /* Rotate the compass by 270 degrees and invert. */ + meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED = 7 +} meshtastic_Config_DisplayConfig_CompassOrientation; + typedef enum _meshtastic_Config_LoRaConfig_RegionCode { /* Region is not set */ meshtastic_Config_LoRaConfig_RegionCode_UNSET = 0, @@ -413,6 +432,8 @@ typedef struct _meshtastic_Config_DisplayConfig { bool heading_bold; /* Should we wake the screen up on accelerometer detected motion or tap */ bool wake_on_tap_or_motion; + /* Indicates how to rotate or invert the compass output to accurate display on the display. */ + meshtastic_Config_DisplayConfig_CompassOrientation compass_orientation; } meshtastic_Config_DisplayConfig; /* Lora Config */ @@ -547,6 +568,10 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR #define _meshtastic_Config_DisplayConfig_DisplayMode_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayMode)(meshtastic_Config_DisplayConfig_DisplayMode_COLOR+1)) +#define _meshtastic_Config_DisplayConfig_CompassOrientation_MIN meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0 +#define _meshtastic_Config_DisplayConfig_CompassOrientation_MAX meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED +#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1)) + #define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET #define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_SG_923 #define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_SG_923+1)) @@ -573,6 +598,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode +#define meshtastic_Config_DisplayConfig_compass_orientation_ENUMTYPE meshtastic_Config_DisplayConfig_CompassOrientation #define meshtastic_Config_LoRaConfig_modem_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset #define meshtastic_Config_LoRaConfig_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode @@ -587,7 +613,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} -#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0} +#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} @@ -596,7 +622,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} -#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0} +#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} @@ -656,6 +682,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_displaymode_tag 8 #define meshtastic_Config_DisplayConfig_heading_bold_tag 9 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10 +#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11 #define meshtastic_Config_LoRaConfig_use_preset_tag 1 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3 @@ -778,7 +805,8 @@ X(a, STATIC, SINGULAR, UENUM, units, 6) \ X(a, STATIC, SINGULAR, UENUM, oled, 7) \ X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \ X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \ -X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) +X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \ +X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) #define meshtastic_Config_DisplayConfig_CALLBACK NULL #define meshtastic_Config_DisplayConfig_DEFAULT NULL @@ -834,7 +862,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_DeviceConfig_size 100 -#define meshtastic_Config_DisplayConfig_size 28 +#define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 80 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index a5cbc42a5..0e3e28ba1 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -308,7 +308,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3368 +#define meshtastic_OEMStore_size 3370 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 1b8123ef5..160202d9b 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 537 +#define meshtastic_LocalConfig_size 539 #define meshtastic_LocalModuleConfig_size 685 #ifdef __cplusplus From 0852a170a3b6a10b69fc88963ef267faeda11a07 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 11 Jun 2024 17:47:45 -0500 Subject: [PATCH 0559/1377] Add support for BMX160/RAK12034 compass module (#4021) --- src/AccelerometerThread.h | 81 ++- src/Fusion/Fusion.h | 32 ++ src/Fusion/FusionAhrs.c | 542 ++++++++++++++++++ src/Fusion/FusionAhrs.h | 112 ++++ src/Fusion/FusionAxes.h | 188 ++++++ src/Fusion/FusionCalibration.h | 49 ++ src/Fusion/FusionCompass.c | 51 ++ src/Fusion/FusionCompass.h | 26 + src/Fusion/FusionConvention.h | 25 + src/Fusion/FusionMath.h | 503 ++++++++++++++++ src/Fusion/FusionOffset.c | 80 +++ src/Fusion/FusionOffset.h | 40 ++ src/configuration.h | 1 + src/detect/ScanI2C.cpp | 4 +- src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 1 + src/graphics/Screen.cpp | 8 +- src/graphics/Screen.h | 13 + src/main.h | 6 +- variants/rak4631/platformio.ini | 1 + variants/rak4631_epaper/platformio.ini | 1 + variants/rak4631_epaper_onrxtx/platformio.ini | 3 +- 22 files changed, 1760 insertions(+), 10 deletions(-) create mode 100644 src/Fusion/Fusion.h create mode 100644 src/Fusion/FusionAhrs.c create mode 100644 src/Fusion/FusionAhrs.h create mode 100644 src/Fusion/FusionAxes.h create mode 100644 src/Fusion/FusionCalibration.h create mode 100644 src/Fusion/FusionCompass.c create mode 100644 src/Fusion/FusionCompass.h create mode 100644 src/Fusion/FusionConvention.h create mode 100644 src/Fusion/FusionMath.h create mode 100644 src/Fusion/FusionOffset.c create mode 100644 src/Fusion/FusionOffset.h diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index ad40cd9bd..f03752cad 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -14,6 +14,10 @@ #include #include #include +#ifdef RAK_4631 +#include "Fusion/Fusion.h" +#include +#endif #define ACCELEROMETER_CHECK_INTERVAL_MS 100 #define ACCELEROMETER_CLICK_THRESHOLD 40 @@ -50,12 +54,13 @@ class AccelerometerThread : public concurrency::OSThread return; } acceleremoter_type = type; - +#ifndef RAK_4631 if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n"); disable(); return; } +#endif init(); } @@ -87,6 +92,71 @@ class AccelerometerThread : public concurrency::OSThread wakeScreen(); return 500; } +#ifdef RAK_4631 + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { + sBmx160SensorData_t magAccel; + sBmx160SensorData_t gAccel; + + /* Get a new sensor event */ + bmx160.getAllData(&magAccel, NULL, &gAccel); + + // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot + if (millis() > 10 * 1000 && millis() < 30 * 1000) { + if (magAccel.x > highestX) + highestX = magAccel.x; + if (magAccel.x < lowestX) + lowestX = magAccel.x; + if (magAccel.y > highestY) + highestY = magAccel.y; + if (magAccel.y < lowestY) + lowestY = magAccel.y; + if (magAccel.z > highestZ) + highestZ = magAccel.z; + if (magAccel.z < lowestZ) + lowestZ = magAccel.z; + } + + int highestRealX = highestX - (highestX + lowestX) / 2; + + magAccel.x -= (highestX + lowestX) / 2; + magAccel.y -= (highestY + lowestY) / 2; + magAccel.z -= (highestZ + lowestZ) / 2; + FusionVector ga, ma; + ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board + ga.axis.y = -gAccel.y; + ga.axis.z = gAccel.z; + ma.axis.x = -magAccel.x; + ma.axis.y = -magAccel.y; + ma.axis.z = magAccel.z * 3; + + // If we're set to one of the inverted positions + if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { + ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); + ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); + } + + float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); + + switch (config.display.compass_orientation) { + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0: + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED: + heading += 90; + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED: + heading += 180; + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED: + heading += 270; + break; + } + + screen->setHeading(heading); + +#endif } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { wakeScreen(); return 500; @@ -149,6 +219,11 @@ class AccelerometerThread : public concurrency::OSThread bmaSensor.enableTiltIRQ(); // It corresponds to isDoubleClick interrupt bmaSensor.enableWakeupIRQ(); +#ifdef RAK_4631 + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { + bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate + +#endif } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) { LOG_DEBUG("LSM6DS3 initializing\n"); // Default threshold of 2G, less sensitive options are 4, 8 or 16G @@ -179,6 +254,10 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LIS3DH lis; Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; +#ifdef RAK_4631 + RAK_BMX160 bmx160; + float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; +#endif bool BMA_IRQ = false; }; diff --git a/src/Fusion/Fusion.h b/src/Fusion/Fusion.h new file mode 100644 index 000000000..48f5198c5 --- /dev/null +++ b/src/Fusion/Fusion.h @@ -0,0 +1,32 @@ +/** + * @file Fusion.h + * @author Seb Madgwick + * @brief Main header file for the Fusion library. This is the only file that + * needs to be included when using the library. + */ + +#ifndef FUSION_H +#define FUSION_H + +//------------------------------------------------------------------------------ +// Includes + +#ifdef __cplusplus +extern "C" { +#endif + +#include "FusionAhrs.h" +#include "FusionAxes.h" +#include "FusionCalibration.h" +#include "FusionCompass.h" +#include "FusionConvention.h" +#include "FusionMath.h" +#include "FusionOffset.h" + +#ifdef __cplusplus +} +#endif + +#endif +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionAhrs.c b/src/Fusion/FusionAhrs.c new file mode 100644 index 000000000..d6c1d0215 --- /dev/null +++ b/src/Fusion/FusionAhrs.c @@ -0,0 +1,542 @@ +/** + * @file FusionAhrs.c + * @author Seb Madgwick + * @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer + * measurements into a single measurement of orientation relative to the Earth. + */ + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionAhrs.h" +#include // FLT_MAX +#include // atan2f, cosf, fabsf, powf, sinf + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief Initial gain used during the initialisation. + */ +#define INITIAL_GAIN (10.0f) + +/** + * @brief Initialisation period in seconds. + */ +#define INITIALISATION_PERIOD (3.0f) + +//------------------------------------------------------------------------------ +// Function declarations + +static inline FusionVector HalfGravity(const FusionAhrs *const ahrs); + +static inline FusionVector HalfMagnetic(const FusionAhrs *const ahrs); + +static inline FusionVector Feedback(const FusionVector sensor, const FusionVector reference); + +static inline int Clamp(const int value, const int min, const int max); + +//------------------------------------------------------------------------------ +// Functions + +/** + * @brief Initialises the AHRS algorithm structure. + * @param ahrs AHRS algorithm structure. + */ +void FusionAhrsInitialise(FusionAhrs *const ahrs) +{ + const FusionAhrsSettings settings = { + .convention = FusionConventionNwu, + .gain = 0.5f, + .gyroscopeRange = 0.0f, + .accelerationRejection = 90.0f, + .magneticRejection = 90.0f, + .recoveryTriggerPeriod = 0, + }; + FusionAhrsSetSettings(ahrs, &settings); + FusionAhrsReset(ahrs); +} + +/** + * @brief Resets the AHRS algorithm. This is equivalent to reinitialising the + * algorithm while maintaining the current settings. + * @param ahrs AHRS algorithm structure. + */ +void FusionAhrsReset(FusionAhrs *const ahrs) +{ + ahrs->quaternion = FUSION_IDENTITY_QUATERNION; + ahrs->accelerometer = FUSION_VECTOR_ZERO; + ahrs->initialising = true; + ahrs->rampedGain = INITIAL_GAIN; + ahrs->angularRateRecovery = false; + ahrs->halfAccelerometerFeedback = FUSION_VECTOR_ZERO; + ahrs->halfMagnetometerFeedback = FUSION_VECTOR_ZERO; + ahrs->accelerometerIgnored = false; + ahrs->accelerationRecoveryTrigger = 0; + ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod; + ahrs->magnetometerIgnored = false; + ahrs->magneticRecoveryTrigger = 0; + ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod; +} + +/** + * @brief Sets the AHRS algorithm settings. + * @param ahrs AHRS algorithm structure. + * @param settings Settings. + */ +void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings) +{ + ahrs->settings.convention = settings->convention; + ahrs->settings.gain = settings->gain; + ahrs->settings.gyroscopeRange = settings->gyroscopeRange == 0.0f ? FLT_MAX : 0.98f * settings->gyroscopeRange; + ahrs->settings.accelerationRejection = settings->accelerationRejection == 0.0f + ? FLT_MAX + : powf(0.5f * sinf(FusionDegreesToRadians(settings->accelerationRejection)), 2); + ahrs->settings.magneticRejection = + settings->magneticRejection == 0.0f ? FLT_MAX : powf(0.5f * sinf(FusionDegreesToRadians(settings->magneticRejection)), 2); + ahrs->settings.recoveryTriggerPeriod = settings->recoveryTriggerPeriod; + ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod; + ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod; + if ((settings->gain == 0.0f) || + (settings->recoveryTriggerPeriod == 0)) { // disable acceleration and magnetic rejection features if gain is zero + ahrs->settings.accelerationRejection = FLT_MAX; + ahrs->settings.magneticRejection = FLT_MAX; + } + if (ahrs->initialising == false) { + ahrs->rampedGain = ahrs->settings.gain; + } + ahrs->rampedGainStep = (INITIAL_GAIN - ahrs->settings.gain) / INITIALISATION_PERIOD; +} + +/** + * @brief Updates the AHRS algorithm using the gyroscope, accelerometer, and + * magnetometer measurements. + * @param ahrs AHRS algorithm structure. + * @param gyroscope Gyroscope measurement in degrees per second. + * @param accelerometer Accelerometer measurement in g. + * @param magnetometer Magnetometer measurement in arbitrary units. + * @param deltaTime Delta time in seconds. + */ +void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, + const FusionVector magnetometer, const float deltaTime) +{ +#define Q ahrs->quaternion.element + + // Store accelerometer + ahrs->accelerometer = accelerometer; + + // Reinitialise if gyroscope range exceeded + if ((fabsf(gyroscope.axis.x) > ahrs->settings.gyroscopeRange) || (fabsf(gyroscope.axis.y) > ahrs->settings.gyroscopeRange) || + (fabsf(gyroscope.axis.z) > ahrs->settings.gyroscopeRange)) { + const FusionQuaternion quaternion = ahrs->quaternion; + FusionAhrsReset(ahrs); + ahrs->quaternion = quaternion; + ahrs->angularRateRecovery = true; + } + + // Ramp down gain during initialisation + if (ahrs->initialising) { + ahrs->rampedGain -= ahrs->rampedGainStep * deltaTime; + if ((ahrs->rampedGain < ahrs->settings.gain) || (ahrs->settings.gain == 0.0f)) { + ahrs->rampedGain = ahrs->settings.gain; + ahrs->initialising = false; + ahrs->angularRateRecovery = false; + } + } + + // Calculate direction of gravity indicated by algorithm + const FusionVector halfGravity = HalfGravity(ahrs); + + // Calculate accelerometer feedback + FusionVector halfAccelerometerFeedback = FUSION_VECTOR_ZERO; + ahrs->accelerometerIgnored = true; + if (FusionVectorIsZero(accelerometer) == false) { + + // Calculate accelerometer feedback scaled by 0.5 + ahrs->halfAccelerometerFeedback = Feedback(FusionVectorNormalise(accelerometer), halfGravity); + + // Don't ignore accelerometer if acceleration error below threshold + if (ahrs->initialising || + ((FusionVectorMagnitudeSquared(ahrs->halfAccelerometerFeedback) <= ahrs->settings.accelerationRejection))) { + ahrs->accelerometerIgnored = false; + ahrs->accelerationRecoveryTrigger -= 9; + } else { + ahrs->accelerationRecoveryTrigger += 1; + } + + // Don't ignore accelerometer during acceleration recovery + if (ahrs->accelerationRecoveryTrigger > ahrs->accelerationRecoveryTimeout) { + ahrs->accelerationRecoveryTimeout = 0; + ahrs->accelerometerIgnored = false; + } else { + ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod; + } + ahrs->accelerationRecoveryTrigger = Clamp(ahrs->accelerationRecoveryTrigger, 0, ahrs->settings.recoveryTriggerPeriod); + + // Apply accelerometer feedback + if (ahrs->accelerometerIgnored == false) { + halfAccelerometerFeedback = ahrs->halfAccelerometerFeedback; + } + } + + // Calculate magnetometer feedback + FusionVector halfMagnetometerFeedback = FUSION_VECTOR_ZERO; + ahrs->magnetometerIgnored = true; + if (FusionVectorIsZero(magnetometer) == false) { + + // Calculate direction of magnetic field indicated by algorithm + const FusionVector halfMagnetic = HalfMagnetic(ahrs); + + // Calculate magnetometer feedback scaled by 0.5 + ahrs->halfMagnetometerFeedback = + Feedback(FusionVectorNormalise(FusionVectorCrossProduct(halfGravity, magnetometer)), halfMagnetic); + + // Don't ignore magnetometer if magnetic error below threshold + if (ahrs->initialising || + ((FusionVectorMagnitudeSquared(ahrs->halfMagnetometerFeedback) <= ahrs->settings.magneticRejection))) { + ahrs->magnetometerIgnored = false; + ahrs->magneticRecoveryTrigger -= 9; + } else { + ahrs->magneticRecoveryTrigger += 1; + } + + // Don't ignore magnetometer during magnetic recovery + if (ahrs->magneticRecoveryTrigger > ahrs->magneticRecoveryTimeout) { + ahrs->magneticRecoveryTimeout = 0; + ahrs->magnetometerIgnored = false; + } else { + ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod; + } + ahrs->magneticRecoveryTrigger = Clamp(ahrs->magneticRecoveryTrigger, 0, ahrs->settings.recoveryTriggerPeriod); + + // Apply magnetometer feedback + if (ahrs->magnetometerIgnored == false) { + halfMagnetometerFeedback = ahrs->halfMagnetometerFeedback; + } + } + + // Convert gyroscope to radians per second scaled by 0.5 + const FusionVector halfGyroscope = FusionVectorMultiplyScalar(gyroscope, FusionDegreesToRadians(0.5f)); + + // Apply feedback to gyroscope + const FusionVector adjustedHalfGyroscope = FusionVectorAdd( + halfGyroscope, + FusionVectorMultiplyScalar(FusionVectorAdd(halfAccelerometerFeedback, halfMagnetometerFeedback), ahrs->rampedGain)); + + // Integrate rate of change of quaternion + ahrs->quaternion = FusionQuaternionAdd( + ahrs->quaternion, + FusionQuaternionMultiplyVector(ahrs->quaternion, FusionVectorMultiplyScalar(adjustedHalfGyroscope, deltaTime))); + + // Normalise quaternion + ahrs->quaternion = FusionQuaternionNormalise(ahrs->quaternion); +#undef Q +} + +/** + * @brief Returns the direction of gravity scaled by 0.5. + * @param ahrs AHRS algorithm structure. + * @return Direction of gravity scaled by 0.5. + */ +static inline FusionVector HalfGravity(const FusionAhrs *const ahrs) +{ +#define Q ahrs->quaternion.element + switch (ahrs->settings.convention) { + case FusionConventionNwu: + case FusionConventionEnu: { + const FusionVector halfGravity = {.axis = { + .x = Q.x * Q.z - Q.w * Q.y, + .y = Q.y * Q.z + Q.w * Q.x, + .z = Q.w * Q.w - 0.5f + Q.z * Q.z, + }}; // third column of transposed rotation matrix scaled by 0.5 + return halfGravity; + } + case FusionConventionNed: { + const FusionVector halfGravity = {.axis = { + .x = Q.w * Q.y - Q.x * Q.z, + .y = -1.0f * (Q.y * Q.z + Q.w * Q.x), + .z = 0.5f - Q.w * Q.w - Q.z * Q.z, + }}; // third column of transposed rotation matrix scaled by -0.5 + return halfGravity; + } + } + return FUSION_VECTOR_ZERO; // avoid compiler warning +#undef Q +} + +/** + * @brief Returns the direction of the magnetic field scaled by 0.5. + * @param ahrs AHRS algorithm structure. + * @return Direction of the magnetic field scaled by 0.5. + */ +static inline FusionVector HalfMagnetic(const FusionAhrs *const ahrs) +{ +#define Q ahrs->quaternion.element + switch (ahrs->settings.convention) { + case FusionConventionNwu: { + const FusionVector halfMagnetic = {.axis = { + .x = Q.x * Q.y + Q.w * Q.z, + .y = Q.w * Q.w - 0.5f + Q.y * Q.y, + .z = Q.y * Q.z - Q.w * Q.x, + }}; // second column of transposed rotation matrix scaled by 0.5 + return halfMagnetic; + } + case FusionConventionEnu: { + const FusionVector halfMagnetic = {.axis = { + .x = 0.5f - Q.w * Q.w - Q.x * Q.x, + .y = Q.w * Q.z - Q.x * Q.y, + .z = -1.0f * (Q.x * Q.z + Q.w * Q.y), + }}; // first column of transposed rotation matrix scaled by -0.5 + return halfMagnetic; + } + case FusionConventionNed: { + const FusionVector halfMagnetic = {.axis = { + .x = -1.0f * (Q.x * Q.y + Q.w * Q.z), + .y = 0.5f - Q.w * Q.w - Q.y * Q.y, + .z = Q.w * Q.x - Q.y * Q.z, + }}; // second column of transposed rotation matrix scaled by -0.5 + return halfMagnetic; + } + } + return FUSION_VECTOR_ZERO; // avoid compiler warning +#undef Q +} + +/** + * @brief Returns the feedback. + * @param sensor Sensor. + * @param reference Reference. + * @return Feedback. + */ +static inline FusionVector Feedback(const FusionVector sensor, const FusionVector reference) +{ + if (FusionVectorDotProduct(sensor, reference) < 0.0f) { // if error is >90 degrees + return FusionVectorNormalise(FusionVectorCrossProduct(sensor, reference)); + } + return FusionVectorCrossProduct(sensor, reference); +} + +/** + * @brief Returns a value limited to maximum and minimum. + * @param value Value. + * @param min Minimum value. + * @param max Maximum value. + * @return Value limited to maximum and minimum. + */ +static inline int Clamp(const int value, const int min, const int max) +{ + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; +} + +/** + * @brief Updates the AHRS algorithm using the gyroscope and accelerometer + * measurements only. + * @param ahrs AHRS algorithm structure. + * @param gyroscope Gyroscope measurement in degrees per second. + * @param accelerometer Accelerometer measurement in g. + * @param deltaTime Delta time in seconds. + */ +void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, + const float deltaTime) +{ + + // Update AHRS algorithm + FusionAhrsUpdate(ahrs, gyroscope, accelerometer, FUSION_VECTOR_ZERO, deltaTime); + + // Zero heading during initialisation + if (ahrs->initialising) { + FusionAhrsSetHeading(ahrs, 0.0f); + } +} + +/** + * @brief Updates the AHRS algorithm using the gyroscope, accelerometer, and + * heading measurements. + * @param ahrs AHRS algorithm structure. + * @param gyroscope Gyroscope measurement in degrees per second. + * @param accelerometer Accelerometer measurement in g. + * @param heading Heading measurement in degrees. + * @param deltaTime Delta time in seconds. + */ +void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, + const float heading, const float deltaTime) +{ +#define Q ahrs->quaternion.element + + // Calculate roll + const float roll = atan2f(Q.w * Q.x + Q.y * Q.z, 0.5f - Q.y * Q.y - Q.x * Q.x); + + // Calculate magnetometer + const float headingRadians = FusionDegreesToRadians(heading); + const float sinHeadingRadians = sinf(headingRadians); + const FusionVector magnetometer = {.axis = { + .x = cosf(headingRadians), + .y = -1.0f * cosf(roll) * sinHeadingRadians, + .z = sinHeadingRadians * sinf(roll), + }}; + + // Update AHRS algorithm + FusionAhrsUpdate(ahrs, gyroscope, accelerometer, magnetometer, deltaTime); +#undef Q +} + +/** + * @brief Returns the quaternion describing the sensor relative to the Earth. + * @param ahrs AHRS algorithm structure. + * @return Quaternion describing the sensor relative to the Earth. + */ +FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs) +{ + return ahrs->quaternion; +} + +/** + * @brief Sets the quaternion describing the sensor relative to the Earth. + * @param ahrs AHRS algorithm structure. + * @param quaternion Quaternion describing the sensor relative to the Earth. + */ +void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion) +{ + ahrs->quaternion = quaternion; +} + +/** + * @brief Returns the linear acceleration measurement equal to the accelerometer + * measurement with the 1 g of gravity removed. + * @param ahrs AHRS algorithm structure. + * @return Linear acceleration measurement in g. + */ +FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs) +{ +#define Q ahrs->quaternion.element + + // Calculate gravity in the sensor coordinate frame + const FusionVector gravity = {.axis = { + .x = 2.0f * (Q.x * Q.z - Q.w * Q.y), + .y = 2.0f * (Q.y * Q.z + Q.w * Q.x), + .z = 2.0f * (Q.w * Q.w - 0.5f + Q.z * Q.z), + }}; // third column of transposed rotation matrix + + // Remove gravity from accelerometer measurement + switch (ahrs->settings.convention) { + case FusionConventionNwu: + case FusionConventionEnu: { + return FusionVectorSubtract(ahrs->accelerometer, gravity); + } + case FusionConventionNed: { + return FusionVectorAdd(ahrs->accelerometer, gravity); + } + } + return FUSION_VECTOR_ZERO; // avoid compiler warning +#undef Q +} + +/** + * @brief Returns the Earth acceleration measurement equal to accelerometer + * measurement in the Earth coordinate frame with the 1 g of gravity removed. + * @param ahrs AHRS algorithm structure. + * @return Earth acceleration measurement in g. + */ +FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs) +{ +#define Q ahrs->quaternion.element +#define A ahrs->accelerometer.axis + + // Calculate accelerometer measurement in the Earth coordinate frame + const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations + const float qwqx = Q.w * Q.x; + const float qwqy = Q.w * Q.y; + const float qwqz = Q.w * Q.z; + const float qxqy = Q.x * Q.y; + const float qxqz = Q.x * Q.z; + const float qyqz = Q.y * Q.z; + FusionVector accelerometer = {.axis = { + .x = 2.0f * ((qwqw - 0.5f + Q.x * Q.x) * A.x + (qxqy - qwqz) * A.y + (qxqz + qwqy) * A.z), + .y = 2.0f * ((qxqy + qwqz) * A.x + (qwqw - 0.5f + Q.y * Q.y) * A.y + (qyqz - qwqx) * A.z), + .z = 2.0f * ((qxqz - qwqy) * A.x + (qyqz + qwqx) * A.y + (qwqw - 0.5f + Q.z * Q.z) * A.z), + }}; // rotation matrix multiplied with the accelerometer + + // Remove gravity from accelerometer measurement + switch (ahrs->settings.convention) { + case FusionConventionNwu: + case FusionConventionEnu: + accelerometer.axis.z -= 1.0f; + break; + case FusionConventionNed: + accelerometer.axis.z += 1.0f; + break; + } + return accelerometer; +#undef Q +#undef A +} + +/** + * @brief Returns the AHRS algorithm internal states. + * @param ahrs AHRS algorithm structure. + * @return AHRS algorithm internal states. + */ +FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs) +{ + const FusionAhrsInternalStates internalStates = { + .accelerationError = FusionRadiansToDegrees(FusionAsin(2.0f * FusionVectorMagnitude(ahrs->halfAccelerometerFeedback))), + .accelerometerIgnored = ahrs->accelerometerIgnored, + .accelerationRecoveryTrigger = + ahrs->settings.recoveryTriggerPeriod == 0 + ? 0.0f + : (float)ahrs->accelerationRecoveryTrigger / (float)ahrs->settings.recoveryTriggerPeriod, + .magneticError = FusionRadiansToDegrees(FusionAsin(2.0f * FusionVectorMagnitude(ahrs->halfMagnetometerFeedback))), + .magnetometerIgnored = ahrs->magnetometerIgnored, + .magneticRecoveryTrigger = ahrs->settings.recoveryTriggerPeriod == 0 + ? 0.0f + : (float)ahrs->magneticRecoveryTrigger / (float)ahrs->settings.recoveryTriggerPeriod, + }; + return internalStates; +} + +/** + * @brief Returns the AHRS algorithm flags. + * @param ahrs AHRS algorithm structure. + * @return AHRS algorithm flags. + */ +FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs) +{ + const FusionAhrsFlags flags = { + .initialising = ahrs->initialising, + .angularRateRecovery = ahrs->angularRateRecovery, + .accelerationRecovery = ahrs->accelerationRecoveryTrigger > ahrs->accelerationRecoveryTimeout, + .magneticRecovery = ahrs->magneticRecoveryTrigger > ahrs->magneticRecoveryTimeout, + }; + return flags; +} + +/** + * @brief Sets the heading of the orientation measurement provided by the AHRS + * algorithm. This function can be used to reset drift in heading when the AHRS + * algorithm is being used without a magnetometer. + * @param ahrs AHRS algorithm structure. + * @param heading Heading angle in degrees. + */ +void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading) +{ +#define Q ahrs->quaternion.element + const float yaw = atan2f(Q.w * Q.z + Q.x * Q.y, 0.5f - Q.y * Q.y - Q.z * Q.z); + const float halfYawMinusHeading = 0.5f * (yaw - FusionDegreesToRadians(heading)); + const FusionQuaternion rotation = {.element = { + .w = cosf(halfYawMinusHeading), + .x = 0.0f, + .y = 0.0f, + .z = -1.0f * sinf(halfYawMinusHeading), + }}; + ahrs->quaternion = FusionQuaternionMultiply(rotation, ahrs->quaternion); +#undef Q +} + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionAhrs.h b/src/Fusion/FusionAhrs.h new file mode 100644 index 000000000..aa2326e43 --- /dev/null +++ b/src/Fusion/FusionAhrs.h @@ -0,0 +1,112 @@ +/** + * @file FusionAhrs.h + * @author Seb Madgwick + * @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer + * measurements into a single measurement of orientation relative to the Earth. + */ + +#ifndef FUSION_AHRS_H +#define FUSION_AHRS_H + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionConvention.h" +#include "FusionMath.h" +#include + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief AHRS algorithm settings. + */ +typedef struct { + FusionConvention convention; + float gain; + float gyroscopeRange; + float accelerationRejection; + float magneticRejection; + unsigned int recoveryTriggerPeriod; +} FusionAhrsSettings; + +/** + * @brief AHRS algorithm structure. Structure members are used internally and + * must not be accessed by the application. + */ +typedef struct { + FusionAhrsSettings settings; + FusionQuaternion quaternion; + FusionVector accelerometer; + bool initialising; + float rampedGain; + float rampedGainStep; + bool angularRateRecovery; + FusionVector halfAccelerometerFeedback; + FusionVector halfMagnetometerFeedback; + bool accelerometerIgnored; + int accelerationRecoveryTrigger; + int accelerationRecoveryTimeout; + bool magnetometerIgnored; + int magneticRecoveryTrigger; + int magneticRecoveryTimeout; +} FusionAhrs; + +/** + * @brief AHRS algorithm internal states. + */ +typedef struct { + float accelerationError; + bool accelerometerIgnored; + float accelerationRecoveryTrigger; + float magneticError; + bool magnetometerIgnored; + float magneticRecoveryTrigger; +} FusionAhrsInternalStates; + +/** + * @brief AHRS algorithm flags. + */ +typedef struct { + bool initialising; + bool angularRateRecovery; + bool accelerationRecovery; + bool magneticRecovery; +} FusionAhrsFlags; + +//------------------------------------------------------------------------------ +// Function declarations + +void FusionAhrsInitialise(FusionAhrs *const ahrs); + +void FusionAhrsReset(FusionAhrs *const ahrs); + +void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings); + +void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, + const FusionVector magnetometer, const float deltaTime); + +void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, + const float deltaTime); + +void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, + const float heading, const float deltaTime); + +FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs); + +void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion); + +FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs); + +FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs); + +FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs); + +FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs); + +void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading); + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionAxes.h b/src/Fusion/FusionAxes.h new file mode 100644 index 000000000..9673c88ff --- /dev/null +++ b/src/Fusion/FusionAxes.h @@ -0,0 +1,188 @@ +/** + * @file FusionAxes.h + * @author Seb Madgwick + * @brief Swaps sensor axes for alignment with the body axes. + */ + +#ifndef FUSION_AXES_H +#define FUSION_AXES_H + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionMath.h" + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief Axes alignment describing the sensor axes relative to the body axes. + * For example, if the body X axis is aligned with the sensor Y axis and the + * body Y axis is aligned with sensor X axis but pointing the opposite direction + * then alignment is +Y-X+Z. + */ +typedef enum { + FusionAxesAlignmentPXPYPZ, /* +X+Y+Z */ + FusionAxesAlignmentPXNZPY, /* +X-Z+Y */ + FusionAxesAlignmentPXNYNZ, /* +X-Y-Z */ + FusionAxesAlignmentPXPZNY, /* +X+Z-Y */ + FusionAxesAlignmentNXPYNZ, /* -X+Y-Z */ + FusionAxesAlignmentNXPZPY, /* -X+Z+Y */ + FusionAxesAlignmentNXNYPZ, /* -X-Y+Z */ + FusionAxesAlignmentNXNZNY, /* -X-Z-Y */ + FusionAxesAlignmentPYNXPZ, /* +Y-X+Z */ + FusionAxesAlignmentPYNZNX, /* +Y-Z-X */ + FusionAxesAlignmentPYPXNZ, /* +Y+X-Z */ + FusionAxesAlignmentPYPZPX, /* +Y+Z+X */ + FusionAxesAlignmentNYPXPZ, /* -Y+X+Z */ + FusionAxesAlignmentNYNZPX, /* -Y-Z+X */ + FusionAxesAlignmentNYNXNZ, /* -Y-X-Z */ + FusionAxesAlignmentNYPZNX, /* -Y+Z-X */ + FusionAxesAlignmentPZPYNX, /* +Z+Y-X */ + FusionAxesAlignmentPZPXPY, /* +Z+X+Y */ + FusionAxesAlignmentPZNYPX, /* +Z-Y+X */ + FusionAxesAlignmentPZNXNY, /* +Z-X-Y */ + FusionAxesAlignmentNZPYPX, /* -Z+Y+X */ + FusionAxesAlignmentNZNXPY, /* -Z-X+Y */ + FusionAxesAlignmentNZNYNX, /* -Z-Y-X */ + FusionAxesAlignmentNZPXNY, /* -Z+X-Y */ +} FusionAxesAlignment; + +//------------------------------------------------------------------------------ +// Inline functions + +/** + * @brief Swaps sensor axes for alignment with the body axes. + * @param sensor Sensor axes. + * @param alignment Axes alignment. + * @return Sensor axes aligned with the body axes. + */ +static inline FusionVector FusionAxesSwap(const FusionVector sensor, const FusionAxesAlignment alignment) +{ + FusionVector result; + switch (alignment) { + case FusionAxesAlignmentPXPYPZ: + break; + case FusionAxesAlignmentPXNZPY: + result.axis.x = +sensor.axis.x; + result.axis.y = -sensor.axis.z; + result.axis.z = +sensor.axis.y; + return result; + case FusionAxesAlignmentPXNYNZ: + result.axis.x = +sensor.axis.x; + result.axis.y = -sensor.axis.y; + result.axis.z = -sensor.axis.z; + return result; + case FusionAxesAlignmentPXPZNY: + result.axis.x = +sensor.axis.x; + result.axis.y = +sensor.axis.z; + result.axis.z = -sensor.axis.y; + return result; + case FusionAxesAlignmentNXPYNZ: + result.axis.x = -sensor.axis.x; + result.axis.y = +sensor.axis.y; + result.axis.z = -sensor.axis.z; + return result; + case FusionAxesAlignmentNXPZPY: + result.axis.x = -sensor.axis.x; + result.axis.y = +sensor.axis.z; + result.axis.z = +sensor.axis.y; + return result; + case FusionAxesAlignmentNXNYPZ: + result.axis.x = -sensor.axis.x; + result.axis.y = -sensor.axis.y; + result.axis.z = +sensor.axis.z; + return result; + case FusionAxesAlignmentNXNZNY: + result.axis.x = -sensor.axis.x; + result.axis.y = -sensor.axis.z; + result.axis.z = -sensor.axis.y; + return result; + case FusionAxesAlignmentPYNXPZ: + result.axis.x = +sensor.axis.y; + result.axis.y = -sensor.axis.x; + result.axis.z = +sensor.axis.z; + return result; + case FusionAxesAlignmentPYNZNX: + result.axis.x = +sensor.axis.y; + result.axis.y = -sensor.axis.z; + result.axis.z = -sensor.axis.x; + return result; + case FusionAxesAlignmentPYPXNZ: + result.axis.x = +sensor.axis.y; + result.axis.y = +sensor.axis.x; + result.axis.z = -sensor.axis.z; + return result; + case FusionAxesAlignmentPYPZPX: + result.axis.x = +sensor.axis.y; + result.axis.y = +sensor.axis.z; + result.axis.z = +sensor.axis.x; + return result; + case FusionAxesAlignmentNYPXPZ: + result.axis.x = -sensor.axis.y; + result.axis.y = +sensor.axis.x; + result.axis.z = +sensor.axis.z; + return result; + case FusionAxesAlignmentNYNZPX: + result.axis.x = -sensor.axis.y; + result.axis.y = -sensor.axis.z; + result.axis.z = +sensor.axis.x; + return result; + case FusionAxesAlignmentNYNXNZ: + result.axis.x = -sensor.axis.y; + result.axis.y = -sensor.axis.x; + result.axis.z = -sensor.axis.z; + return result; + case FusionAxesAlignmentNYPZNX: + result.axis.x = -sensor.axis.y; + result.axis.y = +sensor.axis.z; + result.axis.z = -sensor.axis.x; + return result; + case FusionAxesAlignmentPZPYNX: + result.axis.x = +sensor.axis.z; + result.axis.y = +sensor.axis.y; + result.axis.z = -sensor.axis.x; + return result; + case FusionAxesAlignmentPZPXPY: + result.axis.x = +sensor.axis.z; + result.axis.y = +sensor.axis.x; + result.axis.z = +sensor.axis.y; + return result; + case FusionAxesAlignmentPZNYPX: + result.axis.x = +sensor.axis.z; + result.axis.y = -sensor.axis.y; + result.axis.z = +sensor.axis.x; + return result; + case FusionAxesAlignmentPZNXNY: + result.axis.x = +sensor.axis.z; + result.axis.y = -sensor.axis.x; + result.axis.z = -sensor.axis.y; + return result; + case FusionAxesAlignmentNZPYPX: + result.axis.x = -sensor.axis.z; + result.axis.y = +sensor.axis.y; + result.axis.z = +sensor.axis.x; + return result; + case FusionAxesAlignmentNZNXPY: + result.axis.x = -sensor.axis.z; + result.axis.y = -sensor.axis.x; + result.axis.z = +sensor.axis.y; + return result; + case FusionAxesAlignmentNZNYNX: + result.axis.x = -sensor.axis.z; + result.axis.y = -sensor.axis.y; + result.axis.z = -sensor.axis.x; + return result; + case FusionAxesAlignmentNZPXNY: + result.axis.x = -sensor.axis.z; + result.axis.y = +sensor.axis.x; + result.axis.z = -sensor.axis.y; + return result; + } + return sensor; // avoid compiler warning +} + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionCalibration.h b/src/Fusion/FusionCalibration.h new file mode 100644 index 000000000..be7102b73 --- /dev/null +++ b/src/Fusion/FusionCalibration.h @@ -0,0 +1,49 @@ +/** + * @file FusionCalibration.h + * @author Seb Madgwick + * @brief Gyroscope, accelerometer, and magnetometer calibration models. + */ + +#ifndef FUSION_CALIBRATION_H +#define FUSION_CALIBRATION_H + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionMath.h" + +//------------------------------------------------------------------------------ +// Inline functions + +/** + * @brief Gyroscope and accelerometer calibration model. + * @param uncalibrated Uncalibrated measurement. + * @param misalignment Misalignment matrix. + * @param sensitivity Sensitivity. + * @param offset Offset. + * @return Calibrated measurement. + */ +static inline FusionVector FusionCalibrationInertial(const FusionVector uncalibrated, const FusionMatrix misalignment, + const FusionVector sensitivity, const FusionVector offset) +{ + return FusionMatrixMultiplyVector(misalignment, + FusionVectorHadamardProduct(FusionVectorSubtract(uncalibrated, offset), sensitivity)); +} + +/** + * @brief Magnetometer calibration model. + * @param uncalibrated Uncalibrated measurement. + * @param softIronMatrix Soft-iron matrix. + * @param hardIronOffset Hard-iron offset. + * @return Calibrated measurement. + */ +static inline FusionVector FusionCalibrationMagnetic(const FusionVector uncalibrated, const FusionMatrix softIronMatrix, + const FusionVector hardIronOffset) +{ + return FusionMatrixMultiplyVector(softIronMatrix, FusionVectorSubtract(uncalibrated, hardIronOffset)); +} + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionCompass.c b/src/Fusion/FusionCompass.c new file mode 100644 index 000000000..6a6f9591a --- /dev/null +++ b/src/Fusion/FusionCompass.c @@ -0,0 +1,51 @@ +/** + * @file FusionCompass.c + * @author Seb Madgwick + * @brief Tilt-compensated compass to calculate the magnetic heading using + * accelerometer and magnetometer measurements. + */ + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionCompass.h" +#include "FusionAxes.h" +#include // atan2f + +//------------------------------------------------------------------------------ +// Functions + +/** + * @brief Calculates the magnetic heading. + * @param convention Earth axes convention. + * @param accelerometer Accelerometer measurement in any calibrated units. + * @param magnetometer Magnetometer measurement in any calibrated units. + * @return Heading angle in degrees. + */ +float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer, + const FusionVector magnetometer) +{ + switch (convention) { + case FusionConventionNwu: { + const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer)); + const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer)); + return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x)); + } + case FusionConventionEnu: { + const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer)); + const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer)); + const FusionVector east = FusionVectorMultiplyScalar(west, -1.0f); + return FusionRadiansToDegrees(atan2f(north.axis.x, east.axis.x)); + } + case FusionConventionNed: { + const FusionVector up = FusionVectorMultiplyScalar(accelerometer, -1.0f); + const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(up, magnetometer)); + const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, up)); + return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x)); + } + } + return 0; // avoid compiler warning +} + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionCompass.h b/src/Fusion/FusionCompass.h new file mode 100644 index 000000000..a3d0b466a --- /dev/null +++ b/src/Fusion/FusionCompass.h @@ -0,0 +1,26 @@ +/** + * @file FusionCompass.h + * @author Seb Madgwick + * @brief Tilt-compensated compass to calculate the magnetic heading using + * accelerometer and magnetometer measurements. + */ + +#ifndef FUSION_COMPASS_H +#define FUSION_COMPASS_H + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionConvention.h" +#include "FusionMath.h" + +//------------------------------------------------------------------------------ +// Function declarations + +float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer, + const FusionVector magnetometer); + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionConvention.h b/src/Fusion/FusionConvention.h new file mode 100644 index 000000000..0b0d43adc --- /dev/null +++ b/src/Fusion/FusionConvention.h @@ -0,0 +1,25 @@ +/** + * @file FusionConvention.h + * @author Seb Madgwick + * @brief Earth axes convention. + */ + +#ifndef FUSION_CONVENTION_H +#define FUSION_CONVENTION_H + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief Earth axes convention. + */ +typedef enum { + FusionConventionNwu, /* North-West-Up */ + FusionConventionEnu, /* East-North-Up */ + FusionConventionNed, /* North-East-Down */ +} FusionConvention; + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionMath.h b/src/Fusion/FusionMath.h new file mode 100644 index 000000000..c3fc34b2d --- /dev/null +++ b/src/Fusion/FusionMath.h @@ -0,0 +1,503 @@ +/** + * @file FusionMath.h + * @author Seb Madgwick + * @brief Math library. + */ + +#ifndef FUSION_MATH_H +#define FUSION_MATH_H + +//------------------------------------------------------------------------------ +// Includes + +#include // M_PI, sqrtf, atan2f, asinf +#include +#include + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief 3D vector. + */ +typedef union { + float array[3]; + + struct { + float x; + float y; + float z; + } axis; +} FusionVector; + +/** + * @brief Quaternion. + */ +typedef union { + float array[4]; + + struct { + float w; + float x; + float y; + float z; + } element; +} FusionQuaternion; + +/** + * @brief 3x3 matrix in row-major order. + * See http://en.wikipedia.org/wiki/Row-major_order + */ +typedef union { + float array[3][3]; + + struct { + float xx; + float xy; + float xz; + float yx; + float yy; + float yz; + float zx; + float zy; + float zz; + } element; +} FusionMatrix; + +/** + * @brief Euler angles. Roll, pitch, and yaw correspond to rotations around + * X, Y, and Z respectively. + */ +typedef union { + float array[3]; + + struct { + float roll; + float pitch; + float yaw; + } angle; +} FusionEuler; + +/** + * @brief Vector of zeros. + */ +#define FUSION_VECTOR_ZERO ((FusionVector){.array = {0.0f, 0.0f, 0.0f}}) + +/** + * @brief Vector of ones. + */ +#define FUSION_VECTOR_ONES ((FusionVector){.array = {1.0f, 1.0f, 1.0f}}) + +/** + * @brief Identity quaternion. + */ +#define FUSION_IDENTITY_QUATERNION ((FusionQuaternion){.array = {1.0f, 0.0f, 0.0f, 0.0f}}) + +/** + * @brief Identity matrix. + */ +#define FUSION_IDENTITY_MATRIX ((FusionMatrix){.array = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}}) + +/** + * @brief Euler angles of zero. + */ +#define FUSION_EULER_ZERO ((FusionEuler){.array = {0.0f, 0.0f, 0.0f}}) + +/** + * @brief Pi. May not be defined in math.h. + */ +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +/** + * @brief Include this definition or add as a preprocessor definition to use + * normal square root operations. + */ +// #define FUSION_USE_NORMAL_SQRT + +//------------------------------------------------------------------------------ +// Inline functions - Degrees and radians conversion + +/** + * @brief Converts degrees to radians. + * @param degrees Degrees. + * @return Radians. + */ +static inline float FusionDegreesToRadians(const float degrees) +{ + return degrees * ((float)M_PI / 180.0f); +} + +/** + * @brief Converts radians to degrees. + * @param radians Radians. + * @return Degrees. + */ +static inline float FusionRadiansToDegrees(const float radians) +{ + return radians * (180.0f / (float)M_PI); +} + +//------------------------------------------------------------------------------ +// Inline functions - Arc sine + +/** + * @brief Returns the arc sine of the value. + * @param value Value. + * @return Arc sine of the value. + */ +static inline float FusionAsin(const float value) +{ + if (value <= -1.0f) { + return (float)M_PI / -2.0f; + } + if (value >= 1.0f) { + return (float)M_PI / 2.0f; + } + return asinf(value); +} + +//------------------------------------------------------------------------------ +// Inline functions - Fast inverse square root + +#ifndef FUSION_USE_NORMAL_SQRT + +/** + * @brief Calculates the reciprocal of the square root. + * See https://pizer.wordpress.com/2008/10/12/fast-inverse-square-root/ + * @param x Operand. + * @return Reciprocal of the square root of x. + */ +static inline float FusionFastInverseSqrt(const float x) +{ + + typedef union { + float f; + int32_t i; + } Union32; + + Union32 union32 = {.f = x}; + union32.i = 0x5F1F1412 - (union32.i >> 1); + return union32.f * (1.69000231f - 0.714158168f * x * union32.f * union32.f); +} + +#endif + +//------------------------------------------------------------------------------ +// Inline functions - Vector operations + +/** + * @brief Returns true if the vector is zero. + * @param vector Vector. + * @return True if the vector is zero. + */ +static inline bool FusionVectorIsZero(const FusionVector vector) +{ + return (vector.axis.x == 0.0f) && (vector.axis.y == 0.0f) && (vector.axis.z == 0.0f); +} + +/** + * @brief Returns the sum of two vectors. + * @param vectorA Vector A. + * @param vectorB Vector B. + * @return Sum of two vectors. + */ +static inline FusionVector FusionVectorAdd(const FusionVector vectorA, const FusionVector vectorB) +{ + const FusionVector result = {.axis = { + .x = vectorA.axis.x + vectorB.axis.x, + .y = vectorA.axis.y + vectorB.axis.y, + .z = vectorA.axis.z + vectorB.axis.z, + }}; + return result; +} + +/** + * @brief Returns vector B subtracted from vector A. + * @param vectorA Vector A. + * @param vectorB Vector B. + * @return Vector B subtracted from vector A. + */ +static inline FusionVector FusionVectorSubtract(const FusionVector vectorA, const FusionVector vectorB) +{ + const FusionVector result = {.axis = { + .x = vectorA.axis.x - vectorB.axis.x, + .y = vectorA.axis.y - vectorB.axis.y, + .z = vectorA.axis.z - vectorB.axis.z, + }}; + return result; +} + +/** + * @brief Returns the sum of the elements. + * @param vector Vector. + * @return Sum of the elements. + */ +static inline float FusionVectorSum(const FusionVector vector) +{ + return vector.axis.x + vector.axis.y + vector.axis.z; +} + +/** + * @brief Returns the multiplication of a vector by a scalar. + * @param vector Vector. + * @param scalar Scalar. + * @return Multiplication of a vector by a scalar. + */ +static inline FusionVector FusionVectorMultiplyScalar(const FusionVector vector, const float scalar) +{ + const FusionVector result = {.axis = { + .x = vector.axis.x * scalar, + .y = vector.axis.y * scalar, + .z = vector.axis.z * scalar, + }}; + return result; +} + +/** + * @brief Calculates the Hadamard product (element-wise multiplication). + * @param vectorA Vector A. + * @param vectorB Vector B. + * @return Hadamard product. + */ +static inline FusionVector FusionVectorHadamardProduct(const FusionVector vectorA, const FusionVector vectorB) +{ + const FusionVector result = {.axis = { + .x = vectorA.axis.x * vectorB.axis.x, + .y = vectorA.axis.y * vectorB.axis.y, + .z = vectorA.axis.z * vectorB.axis.z, + }}; + return result; +} + +/** + * @brief Returns the cross product. + * @param vectorA Vector A. + * @param vectorB Vector B. + * @return Cross product. + */ +static inline FusionVector FusionVectorCrossProduct(const FusionVector vectorA, const FusionVector vectorB) +{ +#define A vectorA.axis +#define B vectorB.axis + const FusionVector result = {.axis = { + .x = A.y * B.z - A.z * B.y, + .y = A.z * B.x - A.x * B.z, + .z = A.x * B.y - A.y * B.x, + }}; + return result; +#undef A +#undef B +} + +/** + * @brief Returns the dot product. + * @param vectorA Vector A. + * @param vectorB Vector B. + * @return Dot product. + */ +static inline float FusionVectorDotProduct(const FusionVector vectorA, const FusionVector vectorB) +{ + return FusionVectorSum(FusionVectorHadamardProduct(vectorA, vectorB)); +} + +/** + * @brief Returns the vector magnitude squared. + * @param vector Vector. + * @return Vector magnitude squared. + */ +static inline float FusionVectorMagnitudeSquared(const FusionVector vector) +{ + return FusionVectorSum(FusionVectorHadamardProduct(vector, vector)); +} + +/** + * @brief Returns the vector magnitude. + * @param vector Vector. + * @return Vector magnitude. + */ +static inline float FusionVectorMagnitude(const FusionVector vector) +{ + return sqrtf(FusionVectorMagnitudeSquared(vector)); +} + +/** + * @brief Returns the normalised vector. + * @param vector Vector. + * @return Normalised vector. + */ +static inline FusionVector FusionVectorNormalise(const FusionVector vector) +{ +#ifdef FUSION_USE_NORMAL_SQRT + const float magnitudeReciprocal = 1.0f / sqrtf(FusionVectorMagnitudeSquared(vector)); +#else + const float magnitudeReciprocal = FusionFastInverseSqrt(FusionVectorMagnitudeSquared(vector)); +#endif + return FusionVectorMultiplyScalar(vector, magnitudeReciprocal); +} + +//------------------------------------------------------------------------------ +// Inline functions - Quaternion operations + +/** + * @brief Returns the sum of two quaternions. + * @param quaternionA Quaternion A. + * @param quaternionB Quaternion B. + * @return Sum of two quaternions. + */ +static inline FusionQuaternion FusionQuaternionAdd(const FusionQuaternion quaternionA, const FusionQuaternion quaternionB) +{ + const FusionQuaternion result = {.element = { + .w = quaternionA.element.w + quaternionB.element.w, + .x = quaternionA.element.x + quaternionB.element.x, + .y = quaternionA.element.y + quaternionB.element.y, + .z = quaternionA.element.z + quaternionB.element.z, + }}; + return result; +} + +/** + * @brief Returns the multiplication of two quaternions. + * @param quaternionA Quaternion A (to be post-multiplied). + * @param quaternionB Quaternion B (to be pre-multiplied). + * @return Multiplication of two quaternions. + */ +static inline FusionQuaternion FusionQuaternionMultiply(const FusionQuaternion quaternionA, const FusionQuaternion quaternionB) +{ +#define A quaternionA.element +#define B quaternionB.element + const FusionQuaternion result = {.element = { + .w = A.w * B.w - A.x * B.x - A.y * B.y - A.z * B.z, + .x = A.w * B.x + A.x * B.w + A.y * B.z - A.z * B.y, + .y = A.w * B.y - A.x * B.z + A.y * B.w + A.z * B.x, + .z = A.w * B.z + A.x * B.y - A.y * B.x + A.z * B.w, + }}; + return result; +#undef A +#undef B +} + +/** + * @brief Returns the multiplication of a quaternion with a vector. This is a + * normal quaternion multiplication where the vector is treated a + * quaternion with a W element value of zero. The quaternion is post- + * multiplied by the vector. + * @param quaternion Quaternion. + * @param vector Vector. + * @return Multiplication of a quaternion with a vector. + */ +static inline FusionQuaternion FusionQuaternionMultiplyVector(const FusionQuaternion quaternion, const FusionVector vector) +{ +#define Q quaternion.element +#define V vector.axis + const FusionQuaternion result = {.element = { + .w = -Q.x * V.x - Q.y * V.y - Q.z * V.z, + .x = Q.w * V.x + Q.y * V.z - Q.z * V.y, + .y = Q.w * V.y - Q.x * V.z + Q.z * V.x, + .z = Q.w * V.z + Q.x * V.y - Q.y * V.x, + }}; + return result; +#undef Q +#undef V +} + +/** + * @brief Returns the normalised quaternion. + * @param quaternion Quaternion. + * @return Normalised quaternion. + */ +static inline FusionQuaternion FusionQuaternionNormalise(const FusionQuaternion quaternion) +{ +#define Q quaternion.element +#ifdef FUSION_USE_NORMAL_SQRT + const float magnitudeReciprocal = 1.0f / sqrtf(Q.w * Q.w + Q.x * Q.x + Q.y * Q.y + Q.z * Q.z); +#else + const float magnitudeReciprocal = FusionFastInverseSqrt(Q.w * Q.w + Q.x * Q.x + Q.y * Q.y + Q.z * Q.z); +#endif + const FusionQuaternion result = {.element = { + .w = Q.w * magnitudeReciprocal, + .x = Q.x * magnitudeReciprocal, + .y = Q.y * magnitudeReciprocal, + .z = Q.z * magnitudeReciprocal, + }}; + return result; +#undef Q +} + +//------------------------------------------------------------------------------ +// Inline functions - Matrix operations + +/** + * @brief Returns the multiplication of a matrix with a vector. + * @param matrix Matrix. + * @param vector Vector. + * @return Multiplication of a matrix with a vector. + */ +static inline FusionVector FusionMatrixMultiplyVector(const FusionMatrix matrix, const FusionVector vector) +{ +#define R matrix.element + const FusionVector result = {.axis = { + .x = R.xx * vector.axis.x + R.xy * vector.axis.y + R.xz * vector.axis.z, + .y = R.yx * vector.axis.x + R.yy * vector.axis.y + R.yz * vector.axis.z, + .z = R.zx * vector.axis.x + R.zy * vector.axis.y + R.zz * vector.axis.z, + }}; + return result; +#undef R +} + +//------------------------------------------------------------------------------ +// Inline functions - Conversion operations + +/** + * @brief Converts a quaternion to a rotation matrix. + * @param quaternion Quaternion. + * @return Rotation matrix. + */ +static inline FusionMatrix FusionQuaternionToMatrix(const FusionQuaternion quaternion) +{ +#define Q quaternion.element + const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations + const float qwqx = Q.w * Q.x; + const float qwqy = Q.w * Q.y; + const float qwqz = Q.w * Q.z; + const float qxqy = Q.x * Q.y; + const float qxqz = Q.x * Q.z; + const float qyqz = Q.y * Q.z; + const FusionMatrix matrix = {.element = { + .xx = 2.0f * (qwqw - 0.5f + Q.x * Q.x), + .xy = 2.0f * (qxqy - qwqz), + .xz = 2.0f * (qxqz + qwqy), + .yx = 2.0f * (qxqy + qwqz), + .yy = 2.0f * (qwqw - 0.5f + Q.y * Q.y), + .yz = 2.0f * (qyqz - qwqx), + .zx = 2.0f * (qxqz - qwqy), + .zy = 2.0f * (qyqz + qwqx), + .zz = 2.0f * (qwqw - 0.5f + Q.z * Q.z), + }}; + return matrix; +#undef Q +} + +/** + * @brief Converts a quaternion to ZYX Euler angles in degrees. + * @param quaternion Quaternion. + * @return Euler angles in degrees. + */ +static inline FusionEuler FusionQuaternionToEuler(const FusionQuaternion quaternion) +{ +#define Q quaternion.element + const float halfMinusQySquared = 0.5f - Q.y * Q.y; // calculate common terms to avoid repeated operations + const FusionEuler euler = {.angle = { + .roll = FusionRadiansToDegrees(atan2f(Q.w * Q.x + Q.y * Q.z, halfMinusQySquared - Q.x * Q.x)), + .pitch = FusionRadiansToDegrees(FusionAsin(2.0f * (Q.w * Q.y - Q.z * Q.x))), + .yaw = FusionRadiansToDegrees(atan2f(Q.w * Q.z + Q.x * Q.y, halfMinusQySquared - Q.z * Q.z)), + }}; + return euler; +#undef Q +} + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionOffset.c b/src/Fusion/FusionOffset.c new file mode 100644 index 000000000..d4334c874 --- /dev/null +++ b/src/Fusion/FusionOffset.c @@ -0,0 +1,80 @@ +/** + * @file FusionOffset.c + * @author Seb Madgwick + * @brief Gyroscope offset correction algorithm for run-time calibration of the + * gyroscope offset. + */ + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionOffset.h" +#include // fabsf + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief Cutoff frequency in Hz. + */ +#define CUTOFF_FREQUENCY (0.02f) + +/** + * @brief Timeout in seconds. + */ +#define TIMEOUT (5) + +/** + * @brief Threshold in degrees per second. + */ +#define THRESHOLD (3.0f) + +//------------------------------------------------------------------------------ +// Functions + +/** + * @brief Initialises the gyroscope offset algorithm. + * @param offset Gyroscope offset algorithm structure. + * @param sampleRate Sample rate in Hz. + */ +void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate) +{ + offset->filterCoefficient = 2.0f * (float)M_PI * CUTOFF_FREQUENCY * (1.0f / (float)sampleRate); + offset->timeout = TIMEOUT * sampleRate; + offset->timer = 0; + offset->gyroscopeOffset = FUSION_VECTOR_ZERO; +} + +/** + * @brief Updates the gyroscope offset algorithm and returns the corrected + * gyroscope measurement. + * @param offset Gyroscope offset algorithm structure. + * @param gyroscope Gyroscope measurement in degrees per second. + * @return Corrected gyroscope measurement in degrees per second. + */ +FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope) +{ + + // Subtract offset from gyroscope measurement + gyroscope = FusionVectorSubtract(gyroscope, offset->gyroscopeOffset); + + // Reset timer if gyroscope not stationary + if ((fabsf(gyroscope.axis.x) > THRESHOLD) || (fabsf(gyroscope.axis.y) > THRESHOLD) || (fabsf(gyroscope.axis.z) > THRESHOLD)) { + offset->timer = 0; + return gyroscope; + } + + // Increment timer while gyroscope stationary + if (offset->timer < offset->timeout) { + offset->timer++; + return gyroscope; + } + + // Adjust offset if timer has elapsed + offset->gyroscopeOffset = + FusionVectorAdd(offset->gyroscopeOffset, FusionVectorMultiplyScalar(gyroscope, offset->filterCoefficient)); + return gyroscope; +} + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/Fusion/FusionOffset.h b/src/Fusion/FusionOffset.h new file mode 100644 index 000000000..51ae4a896 --- /dev/null +++ b/src/Fusion/FusionOffset.h @@ -0,0 +1,40 @@ +/** + * @file FusionOffset.h + * @author Seb Madgwick + * @brief Gyroscope offset correction algorithm for run-time calibration of the + * gyroscope offset. + */ + +#ifndef FUSION_OFFSET_H +#define FUSION_OFFSET_H + +//------------------------------------------------------------------------------ +// Includes + +#include "FusionMath.h" + +//------------------------------------------------------------------------------ +// Definitions + +/** + * @brief Gyroscope offset algorithm structure. Structure members are used + * internally and must not be accessed by the application. + */ +typedef struct { + float filterCoefficient; + unsigned int timeout; + unsigned int timer; + FusionVector gyroscopeOffset; +} FusionOffset; + +//------------------------------------------------------------------------------ +// Function declarations + +void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate); + +FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope); + +#endif + +//------------------------------------------------------------------------------ +// End of file diff --git a/src/configuration.h b/src/configuration.h index 462210cf2..62c48a205 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -144,6 +144,7 @@ along with this program. If not, see . #define LIS3DH_ADR 0x18 #define BMA423_ADDR 0x19 #define LSM6DS3_ADDR 0x6A +#define BMX160_ADDR 0x69 // ----------------------------------------------------------------------------- // LED diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 149bb95f0..3231f7054 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -36,8 +36,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const { - ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3}; - return firstOfOrNONE(4, types); + ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160}; + return firstOfOrNONE(5, types); } ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 13dd66763..20994ede1 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -49,7 +49,8 @@ class ScanI2C OPT3001, MLX90632, AHT10, - DFROBOT_LARK, + BMX160, + DFROBOT_LARK } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 86099ad19..f800a9963 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -342,6 +342,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n"); + SCAN_SIMPLE_CASE(BMX160_ADDR, BMX160, "BMX160 accelerometer found\n"); SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 1c9484f62..5a892bbfb 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1516,9 +1516,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ } bool hasNodeHeading = false; - if (ourNode && hasValidPosition(ourNode)) { + if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) { const meshtastic_PositionLite &op = ourNode->position; - float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + float myHeading; + if (screen->hasHeading()) + myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians + else + myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); drawCompassNorth(display, compassX, compassY, myHeading); if (hasValidPosition(node)) { diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 7f8d078e7..f4d719715 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -204,6 +204,17 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } + // Function to allow the AccelerometerThread to set the heading if a sensor provides it + // Mutex needed? + void setHeading(long _heading) + { + hasCompass = true; + compassHeading = _heading; + } + + bool hasHeading() { return hasCompass; } + + long getHeading() { return compassHeading; } // functions for display brightness void increaseBrightness(); void decreaseBrightness(); @@ -428,6 +439,8 @@ class Screen : public concurrency::OSThread // Implementation to Adjust Brightness uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103 + bool hasCompass = false; + float compassHeading; /// Holds state for debug information DebugInfo debugInfo; diff --git a/src/main.h b/src/main.h index db05a4734..2ef7edb3a 100644 --- a/src/main.h +++ b/src/main.h @@ -53,6 +53,9 @@ extern Adafruit_DRV2605 drv; extern AudioThread *audioThread; #endif +// Global Screen singleton. +extern graphics::Screen *screen; + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "AccelerometerThread.h" extern AccelerometerThread *accelerometerThread; @@ -62,9 +65,6 @@ extern bool isVibrating; extern int TCPPort; // set by Portduino -// Global Screen singleton. -extern graphics::Screen *screen; - // extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class // extern meshtastic::PowerStatus *powerStatus; diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index f64811429..24f209b01 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -16,6 +16,7 @@ lib_deps = melopero/Melopero RV3028@^1.1.0 https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + beegee-tokyo/RAKwireless RAK12034@^1.0.0 debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink \ No newline at end of file diff --git a/variants/rak4631_epaper/platformio.ini b/variants/rak4631_epaper/platformio.ini index 1ca9a2157..08342dcf7 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/rak4631_epaper/platformio.ini @@ -13,6 +13,7 @@ lib_deps = zinggjm/GxEPD2@^1.4.9 melopero/Melopero RV3028@^1.1.0 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + beegee-tokyo/RAKwireless RAK12034@^1.0.0 debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink \ No newline at end of file diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini index e0a0a5a58..f7035a1b1 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -15,7 +15,8 @@ lib_deps = zinggjm/GxEPD2@^1.5.1 melopero/Melopero RV3028@^1.1.0 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + beegee-tokyo/RAKwireless RAK12034@^1.0.0 debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink -;upload_port = /dev/ttyACM3 +;upload_port = /dev/ttyACM3 \ No newline at end of file From e63278cf431f9d403c79d8006a750a967208a3d0 Mon Sep 17 00:00:00 2001 From: Tavis Date: Tue, 11 Jun 2024 15:13:17 -1000 Subject: [PATCH 0560/1377] add wind speed and direction to json --- src/mqtt/MQTT.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index f3eda6d7c..566eb352d 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -675,6 +675,8 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux); msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux); msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); + msgPayload["wind_speed"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_speed); + msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction); } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); From d60d1d74477a5ad3f5d6785b8157f255bb0bbfd4 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 12 Jun 2024 23:34:00 +1200 Subject: [PATCH 0561/1377] Workaround to disable bluetooth on NRF52 (#4055) * Workaround to allow bluetooth disable on NRF52 * Use miminum tx power for bluetooth * Reorganize * Instantiate nrf52Bluetooth correctly.. * Change log message --- src/platform/nrf52/NRF52Bluetooth.cpp | 12 ++++++ src/platform/nrf52/NRF52Bluetooth.h | 1 + src/platform/nrf52/main-nrf52.cpp | 55 ++++++++++++++++++--------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 39898ab25..4c25f38ea 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -215,6 +215,18 @@ void NRF52Bluetooth::shutdown() 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("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); +} + bool NRF52Bluetooth::isConnected() { return Bluefruit.connected(connectionHandle); diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 11e18c127..450af47f9 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -8,6 +8,7 @@ class NRF52Bluetooth : BluetoothApi public: void setup(); void shutdown(); + void startDisabled(); void resumeAdvertising(); void clearBonds(); bool isConnected(); diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 9cc52a7de..1f2c6867d 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -68,28 +68,47 @@ static const bool useSoftDevice = true; // Set to false for easier debugging #if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { - if (enable && config.bluetooth.enabled) { - if (!useSoftDevice) { + // For debugging use: don't use bluetooth + if (!useSoftDevice) { + if (enable) LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); - } else { - if (!nrf52Bluetooth) { - LOG_DEBUG("Initializing NRF52 Bluetooth\n"); - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); - - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); - } else { - nrf52Bluetooth->resumeAdvertising(); - } - } - } else { - if (nrf52Bluetooth) { - nrf52Bluetooth->shutdown(); - } + 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) { + // If not yet set-up + if (!nrf52Bluetooth) { + LOG_DEBUG("Initializing NRF52 Bluetooth\n"); + 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) + nrf52Bluetooth->shutdown(); } #else +#warning NRF52 "Bluetooth disable" workaround does not apply to builds with MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) {} #endif /** From 992d1c42e6d73f46e818fb8178d8e067d48bdec0 Mon Sep 17 00:00:00 2001 From: Jan Veeh <33117982+craft4tnt@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:43:50 +0200 Subject: [PATCH 0562/1377] changed CFG-PM config message to use external signal (#4062) --- src/gps/ubx.h | 61 ++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/gps/ubx.h b/src/gps/ubx.h index 0a382a8a3..df03c1634 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -319,6 +319,7 @@ const uint8_t GPS::_message_SAVE[] = { // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. +// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after sleep // VALSET Commands for M10 // Please refer to the M10 Protocol Specification: @@ -327,40 +328,44 @@ const uint8_t GPS::_message_SAVE[] = { // and: // https://content.u-blox.com/sites/default/files/u-blox-M10-ROM-5.10_ReleaseNotes_UBX-22001426.pdf // for interesting insights. +// +// Integration manual: +// https://content.u-blox.com/sites/default/files/documents/SAM-M10Q_IntegrationManual_UBX-22020019.pdf +// has details on low-power modes + /* CFG-PM2 has been replaced by many CFG-PM commands -OPERATEMODE E1 2 (0 | 1 | 2) -POSUPDATEPERIOD U4 1000ms for M10 must be >= 5s try 5 -ACQPERIOD U4 10 seems ok for M10 def ok -GRIDOFFSET U4 0 seems ok for M10 def ok -ONTIME U2 1 will try 1 -MINACQTIME U1 0 will try 0 def ok -MAXACQTIME U1 stick with default of 0 def ok -DONOTENTEROFF L 1 stay at 1 -WAITTIMEFIX L 1 stay with 1 -UPDATEEPH L 1 changed to 1 for gps rework default is 1 -EXTINTWAKE L 0 no ext ints -EXTINTBACKUP L 0 no ext ints -EXTINTINACTIVE L 0 no ext ints -EXTINTACTIVITY U4 0 no ext ints -LIMITPEAKCURRENT L 1 stay with 1 -*/ -// CFG-PMS has been removed +CFG-PMS has been removed + +CFG-PM-OPERATEMODE E1 (0 | 1 | 2) -> 1 (PSMOO), because sporadic position updates are required instead of continous tracking <10s (PSMCT) +CFG-PM-POSUPDATEPERIOD U4 -> 0ms, no self-timed wakup because receiver power mode is controlled via "software standby mode" by legacy UBX-RXM-PMREQ request +CFG-PM-ACQPERIOD U4 -> 0ms, because receiver power mode is controlled via "software standby mode" by legacy UBX-RXM-PMREQ request +CFG-PM-ONTIME U4 -> 0ms, optional I guess +CFG-PM-EXTINTBACKUP L -> 1, force receiver into BACKUP mode when EXTINT (should be connected to GPS_EN_PIN) pin is "low" + +This is required because the receiver never enters low power mode if microcontroller is in deep-sleep. +Maybe the changing UART_RX levels trigger a wakeup but even with UBX-RXM-PMREQ[12] = 0x00 (all external wakeup sources disabled) the receivcer remains +in aquisition state -> potentially a bug + +Workaround: Control the EXTINT pin by the GPS_EN_PIN signal + +As mentioned in the M10 operational issues down below, power save won't allow the use of BDS B1C. +CFG-SIGNAL-BDS_B1C_ENA L -> 0 // Ram layer config message: -// b5 62 06 8a 26 00 00 01 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0 -// 10 01 8b de +// 01 01 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01 // BBR layer config message: -// b5 62 06 8a 26 00 00 02 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0 -// 10 01 8c 03 - -const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40, - 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0, - 0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01}; -const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40, - 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0, - 0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01}; +// 01 02 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01 +*/ +const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x01, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, + 0x01, 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, + 0x10, 0x01}; +const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x01, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, + 0x01, 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, + 0x10, 0x01}; /* CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR From b09cee118c5f17c9d9e38473896b041776a3faea Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 12 Jun 2024 06:57:11 -0500 Subject: [PATCH 0563/1377] Trunk --- src/gps/ubx.h | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/gps/ubx.h b/src/gps/ubx.h index df03c1634..0852c331d 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -319,7 +319,8 @@ const uint8_t GPS::_message_SAVE[] = { // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. -// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after sleep +// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after +// sleep // VALSET Commands for M10 // Please refer to the M10 Protocol Specification: @@ -337,15 +338,15 @@ const uint8_t GPS::_message_SAVE[] = { CFG-PM2 has been replaced by many CFG-PM commands CFG-PMS has been removed -CFG-PM-OPERATEMODE E1 (0 | 1 | 2) -> 1 (PSMOO), because sporadic position updates are required instead of continous tracking <10s (PSMCT) -CFG-PM-POSUPDATEPERIOD U4 -> 0ms, no self-timed wakup because receiver power mode is controlled via "software standby mode" by legacy UBX-RXM-PMREQ request -CFG-PM-ACQPERIOD U4 -> 0ms, because receiver power mode is controlled via "software standby mode" by legacy UBX-RXM-PMREQ request -CFG-PM-ONTIME U4 -> 0ms, optional I guess -CFG-PM-EXTINTBACKUP L -> 1, force receiver into BACKUP mode when EXTINT (should be connected to GPS_EN_PIN) pin is "low" +CFG-PM-OPERATEMODE E1 (0 | 1 | 2) -> 1 (PSMOO), because sporadic position updates are required instead of continous tracking <10s +(PSMCT) CFG-PM-POSUPDATEPERIOD U4 -> 0ms, no self-timed wakup because receiver power mode is controlled via "software standby +mode" by legacy UBX-RXM-PMREQ request CFG-PM-ACQPERIOD U4 -> 0ms, because receiver power mode is controlled via "software standby +mode" by legacy UBX-RXM-PMREQ request CFG-PM-ONTIME U4 -> 0ms, optional I guess CFG-PM-EXTINTBACKUP L -> 1, force receiver into +BACKUP mode when EXTINT (should be connected to GPS_EN_PIN) pin is "low" This is required because the receiver never enters low power mode if microcontroller is in deep-sleep. -Maybe the changing UART_RX levels trigger a wakeup but even with UBX-RXM-PMREQ[12] = 0x00 (all external wakeup sources disabled) the receivcer remains -in aquisition state -> potentially a bug +Maybe the changing UART_RX levels trigger a wakeup but even with UBX-RXM-PMREQ[12] = 0x00 (all external wakeup sources disabled) +the receivcer remains in aquisition state -> potentially a bug Workaround: Control the EXTINT pin by the GPS_EN_PIN signal @@ -358,14 +359,12 @@ CFG-SIGNAL-BDS_B1C_ENA L -> 0 // BBR layer config message: // 01 02 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01 */ -const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x01, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, - 0x01, 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, - 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, - 0x10, 0x01}; -const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x01, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, - 0x01, 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, - 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, - 0x10, 0x01}; +const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x01, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01, + 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01}; +const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x01, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01, + 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01}; /* CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR From 5b1d3ed173fc1eddd8dc8e494095e4353223ccfe Mon Sep 17 00:00:00 2001 From: Heltec-Aaron-Lee Date: Wed, 12 Jun 2024 20:21:26 +0800 Subject: [PATCH 0564/1377] Add Heltec Capsule Sensor V3 to source code --- platformio.ini | 1 + src/ButtonThread.cpp | 4 ++ src/Power.cpp | 25 +++++++---- src/platform/esp32/architecture.h | 2 + .../heltec_capsule_sensor_v3/platformio.ini | 11 +++++ variants/heltec_capsule_sensor_v3/variant.h | 42 +++++++++++++++++++ 6 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 variants/heltec_capsule_sensor_v3/platformio.ini create mode 100644 variants/heltec_capsule_sensor_v3/variant.h diff --git a/platformio.ini b/platformio.ini index 7ed794a6e..9c29d2d67 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,6 +33,7 @@ default_envs = tbeam ;default_envs = wio-e5 ;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_micro +;default_envs = heltec_capsule_sensor_v3 extra_configs = arch/*/*.ini diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 7e678d69d..4b3bb3fbc 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -41,7 +41,11 @@ ButtonThread::ButtonThread() : OSThread("Button") } #elif defined(BUTTON_PIN) int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin +#if defined(HELTEC_CAPSULE_SENSOR_V3) + this->userButton = OneButton(pin, false, false); +#else this->userButton = OneButton(pin, true, true); +#endif LOG_DEBUG("Using GPIO%02d for button\n", pin); #endif diff --git a/src/Power.cpp b/src/Power.cpp index b80d8a0d5..d80bfd55c 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -335,13 +335,20 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual bool isVbusIn() override { #ifdef EXT_PWR_DETECT - // if external powered that pin will be pulled up - if (digitalRead(EXT_PWR_DETECT) == HIGH) { - return true; - } - // if it's not HIGH - check the battery + #ifdef HELTEC_CAPSULE_SENSOR_V3 + // if external powered that pin will be pulled down + if (digitalRead(EXT_PWR_DETECT) == LOW) { + return true; + } + // if it's not LOW - check the battery + #else + // if external powered that pin will be pulled up + if (digitalRead(EXT_PWR_DETECT) == HIGH) { + return true; + } + // if it's not HIGH - check the battery + #endif #endif - return getBattVoltage() > chargingVolt; } @@ -421,7 +428,11 @@ Power::Power() : OSThread("Power") bool Power::analogInit() { #ifdef EXT_PWR_DETECT - pinMode(EXT_PWR_DETECT, INPUT); + #ifdef HELTEC_CAPSULE_SENSOR_V3 + pinMode(EXT_PWR_DETECT, INPUT_PULLUP); + #else + pinMode(EXT_PWR_DETECT, INPUT); + #endif #endif #ifdef EXT_CHRG_DETECT pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 824c11bdd..c979d016c 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -147,6 +147,8 @@ #define HW_VENDOR meshtastic_HardwareModel_WIPHONE #elif defined(RADIOMASTER_900_BANDIT_NANO) #define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO +#elif defined(HELTEC_CAPSULE_SENSOR_V3) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 #endif // ----------------------------------------------------------------------------- diff --git a/variants/heltec_capsule_sensor_v3/platformio.ini b/variants/heltec_capsule_sensor_v3/platformio.ini new file mode 100644 index 000000000..f1aef925d --- /dev/null +++ b/variants/heltec_capsule_sensor_v3/platformio.ini @@ -0,0 +1,11 @@ +[env:heltec_capsule_sensor_v3] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +board_check = true + +build_flags = + ${esp32s3_base.build_flags} -I variants/heltec_capsule_sensor_v3 + -D HELTEC_CAPSULE_SENSOR_V3 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output + diff --git a/variants/heltec_capsule_sensor_v3/variant.h b/variants/heltec_capsule_sensor_v3/variant.h new file mode 100644 index 000000000..0d5ab73cf --- /dev/null +++ b/variants/heltec_capsule_sensor_v3/variant.h @@ -0,0 +1,42 @@ +#define LED_PIN 33 +#define LED_PIN2 34 +#define EXT_PWR_DETECT 35 + +#define BUTTON_PIN 18 + +#define BATTERY_PIN 7 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER (4.9 * 1.045) +#define ADC_CTRL 36 // active HIGH, powers the voltage divider. Only on 1.1 +#define ADC_CTRL_ENABLED HIGH + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 5 +#define GPS_TX_PIN 4 +#define PIN_GPS_RESET 3 +#define GPS_RESET_MODE LOW +#define PIN_GPS_PPS 1 +#define PIN_GPS_EN 21 +#define GPS_EN_ACTIVE HIGH + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 From c7769274dd417017bf06a01e5215e61054567cfd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 07:23:54 -0500 Subject: [PATCH 0565/1377] [create-pull-request] automated change (#4085) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 8c8048798..8f4faf76e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8c8048798c1b1773b9d8f2c32eb3f4c9e72f8218 +Subproject commit 8f4faf76e52c2ef63c582c26642d312d9781b7c0 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index ad97cb80f..0e9e6a28d 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -161,6 +161,8 @@ typedef enum _meshtastic_HardwareModel { /* RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS */ meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64, + /* Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors */ + meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 871f6854b5751b88fbb3b3905970125f6ffb4285 Mon Sep 17 00:00:00 2001 From: John Gorkos - AB0OO Date: Wed, 12 Jun 2024 08:22:01 -0700 Subject: [PATCH 0566/1377] feature-mqtt: add hop_start and hop_limit to MQTT uplink --- src/mqtt/MQTT.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 566eb352d..905c2b7d3 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -902,7 +902,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) jsonObj["snr"] = new JSONValue((float)mp->rx_snr); if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); - + jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); + jsonObj["hop_limit"] = new JSONValue((unsigned int)(mp->hop_limit)); + // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); std::string jsonStr = value->Stringify(); From d80bcd7d67df2876b9536cd1418bec64171269e9 Mon Sep 17 00:00:00 2001 From: John Gorkos - AB0OO Date: Wed, 12 Jun 2024 12:59:52 -0700 Subject: [PATCH 0567/1377] adding only hop_start, per @GUVWAF --- src/mqtt/MQTT.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 905c2b7d3..4f685cd7a 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -903,7 +903,6 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); - jsonObj["hop_limit"] = new JSONValue((unsigned int)(mp->hop_limit)); // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); From b42185c722c27e17523bf2474079915f8eca7f04 Mon Sep 17 00:00:00 2001 From: John Gorkos - AB0OO Date: Wed, 12 Jun 2024 13:02:01 -0700 Subject: [PATCH 0568/1377] included hop_start in conditional for hop_away --- src/mqtt/MQTT.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4f685cd7a..d93166ce9 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -900,9 +900,10 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); if (mp->rx_snr != 0) jsonObj["snr"] = new JSONValue((float)mp->rx_snr); - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); - jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); + jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); + } // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); From f7433eb4ee772477493545800da88eafc1455e96 Mon Sep 17 00:00:00 2001 From: John Gorkos - AB0OO Date: Wed, 12 Jun 2024 14:36:38 -0700 Subject: [PATCH 0569/1377] trunk formatting --- src/mqtt/MQTT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index d93166ce9..9f9ac5c24 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -904,7 +904,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); } - + // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); std::string jsonStr = value->Stringify(); From 26d4d06e2a2359d35f349eaf15b6a703058bacff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 08:39:38 -0500 Subject: [PATCH 0570/1377] [create-pull-request] automated change (#4093) Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com> --- protobufs | 2 +- .../generated/meshtastic/telemetry.pb.cpp | 3 ++ src/mesh/generated/meshtastic/telemetry.pb.h | 30 +++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 8f4faf76e..260d24318 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8f4faf76e52c2ef63c582c26642d312d9781b7c0 +Subproject commit 260d24318d811518171ed2916c94c0cfd94eb9d4 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index 6388e37a0..c93483a15 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -21,5 +21,8 @@ PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO) PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, AUTO) +PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO) + + diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 02b0bdd6d..47961cd7d 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -61,7 +61,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* AHT10 Integrated temperature and humidity sensor */ meshtastic_TelemetrySensorType_AHT10 = 23, /* DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) */ - meshtastic_TelemetrySensorType_DFROBOT_LARK = 24 + meshtastic_TelemetrySensorType_DFROBOT_LARK = 24, + /* NAU7802 Scale Chip or compatible */ + meshtastic_TelemetrySensorType_NAU7802 = 25 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -174,6 +176,14 @@ typedef struct _meshtastic_Telemetry { } variant; } meshtastic_Telemetry; +/* NAU7802 Telemetry configuration, for saving to flash */ +typedef struct _meshtastic_Nau7802Config { + /* The offset setting for the NAU7802 */ + int32_t zeroOffset; + /* The calibration factor for the NAU7802 */ + float calibrationFactor; +} meshtastic_Nau7802Config; + #ifdef __cplusplus extern "C" { @@ -181,8 +191,9 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DFROBOT_LARK -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DFROBOT_LARK+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_NAU7802 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_NAU7802+1)) + @@ -196,11 +207,13 @@ extern "C" { #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} +#define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} #define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} +#define meshtastic_Nau7802Config_init_zero {0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceMetrics_battery_level_tag 1 @@ -245,6 +258,8 @@ extern "C" { #define meshtastic_Telemetry_environment_metrics_tag 3 #define meshtastic_Telemetry_air_quality_metrics_tag 4 #define meshtastic_Telemetry_power_metrics_tag 5 +#define meshtastic_Nau7802Config_zeroOffset_tag 1 +#define meshtastic_Nau7802Config_calibrationFactor_tag 2 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceMetrics_FIELDLIST(X, a) \ @@ -313,11 +328,18 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics) #define meshtastic_Telemetry_variant_air_quality_metrics_MSGTYPE meshtastic_AirQualityMetrics #define meshtastic_Telemetry_variant_power_metrics_MSGTYPE meshtastic_PowerMetrics +#define meshtastic_Nau7802Config_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \ +X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2) +#define meshtastic_Nau7802Config_CALLBACK NULL +#define meshtastic_Nau7802Config_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg; extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; +extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg @@ -325,12 +347,14 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg #define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg +#define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 #define meshtastic_EnvironmentMetrics_size 68 +#define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 From 75d5cd2c356eaa2ff76b1b360325091a92b44d89 Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:50:21 +0000 Subject: [PATCH 0571/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/protobufs b/protobufs index 260d24318..ab576a4a1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 260d24318d811518171ed2916c94c0cfd94eb9d4 +Subproject commit ab576a4a122c1a1d0a3c2235b0a0cf3bd4a83c65 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 47961cd7d..28d368754 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -113,6 +113,8 @@ typedef struct _meshtastic_EnvironmentMetrics { uint16_t wind_direction; /* Wind speed in m/s */ float wind_speed; + /* Weight in KG */ + float weight; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -203,13 +205,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -235,6 +237,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_uv_lux_tag 12 #define meshtastic_EnvironmentMetrics_wind_direction_tag 13 #define meshtastic_EnvironmentMetrics_wind_speed_tag 14 +#define meshtastic_EnvironmentMetrics_weight_tag 15 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -285,7 +288,8 @@ X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) \ X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ -X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) +X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \ +X(a, STATIC, SINGULAR, FLOAT, weight, 15) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -353,10 +357,10 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 68 +#define meshtastic_EnvironmentMetrics_size 73 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 -#define meshtastic_Telemetry_size 79 +#define meshtastic_Telemetry_size 80 #ifdef __cplusplus } /* extern "C" */ From 85bca8a32a09d75d786a16d8908c0582389a1d7d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 13 Jun 2024 11:13:18 -0500 Subject: [PATCH 0572/1377] Update lark to ref to clear C++ warning --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9c29d2d67..2064bac48 100644 --- a/platformio.ini +++ b/platformio.ini @@ -143,4 +143,4 @@ lib_deps = ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#0e884fc86b7a0b602c7ff3d26b893b997f15c6ac \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file From 16b41b51af7ea2ae3b0c9ad4e3b0afb4e0051797 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 13 Jun 2024 12:05:14 -0500 Subject: [PATCH 0573/1377] Update OLED ref --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 2064bac48..34471fc54 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ monitor_speed = 115200 lib_deps = jgromes/RadioLib@~6.6.0 - https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306 + https://github.com/meshtastic/esp8266-oled-ssd1306.git#69ba98fa30e67b12d4577b121f210f3eb7049d6b ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 From 39c9f92c6e7ee1639051a967d761ed5d642f4fcd Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 15 Jun 2024 01:28:01 +1200 Subject: [PATCH 0574/1377] GPS: short update intervals, lock-time prediction (#4070) * Refactor GPSPowerState enum Identifies a case where the GPS hardware is awake, but an update is not yet desired * Change terminology * Clear old lock-time prediction on triple press * Use exponential smoothing to predict lock time * Rename averageLockTime to predictedLockTime * Attempt: Send PMREQ with duration 0 on MCU deep-sleep * Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep * Revert "Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit 8b697cd2a445355dcfab5b33e0ce7a3128cab151. * Revert "Attempt: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit 9d29ec7603a88056b9115796b29b5023165a93bb. --- src/gps/GPS.cpp | 97 +++++++++++++++++++++++++++++++++---------------- src/gps/GPS.h | 11 +++--- 2 files changed, 71 insertions(+), 37 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 17088910a..8d46742ba 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -28,6 +28,12 @@ #define GPS_STANDBY_THRESHOLD_MINUTES 15 #endif +// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby +// Shorter than this, and we'll just wait instead +#ifndef GPS_IDLE_THRESHOLD_SECONDS +#define GPS_IDLE_THRESHOLD_SECONDS 10 +#endif + #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) HardwareSerial *GPS::_serial_gps = &Serial1; #else @@ -776,14 +782,22 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) { // Record the current powerState if (on) - powerState = GPS_AWAKE; - else if (!on && standbyOnly) + powerState = GPS_ACTIVE; + else if (!enabled) // User has disabled with triple press + powerState = GPS_OFF; + else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL) + powerState = GPS_IDLE; + else if (standbyOnly) powerState = GPS_STANDBY; else powerState = GPS_OFF; LOG_DEBUG("GPS::powerState=%d\n", powerState); + // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out. + if (!on && powerState == GPS_IDLE) + return; + if (on) { clearBuffer(); // drop any old data waiting in the buffer before re-enabling if (en_gpio) @@ -880,54 +894,69 @@ void GPS::setConnected() void GPS::setAwake(bool wantAwake) { - // If user has disabled GPS, make sure it is off, not just in standby + // If user has disabled GPS, make sure it is off, not just in standby or idle if (!wantAwake && !enabled && powerState != GPS_OFF) { setGPSPower(false, false, 0); return; } // If GPS power state needs to change - if ((wantAwake && powerState != GPS_AWAKE) || (!wantAwake && powerState == GPS_AWAKE)) { + if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) { LOG_DEBUG("WANT GPS=%d\n", wantAwake); // Calculate how long it takes to get a GPS lock if (wantAwake) { + // Record the time we start looking for a lock lastWakeStartMsec = millis(); } else { + // Record by how much we missed our ideal target postion.gps_update_interval (for logging only) + // Need to calculate this before we update lastSleepStartMsec, to make the new prediction + int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime(); + + // Record the time we finish looking for a lock lastSleepStartMsec = millis(); - if (GPSCycles == 1) { // Skipping initial lock time, as it will likely be much longer than average - averageLockTime = lastSleepStartMsec - lastWakeStartMsec; - } else if (GPSCycles > 1) { - averageLockTime += ((int32_t)(lastSleepStartMsec - lastWakeStartMsec) - averageLockTime) / (int32_t)GPSCycles; + + // How long did it take to get GPS lock this time? + uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec; + + // Update the lock-time prediction + // Used pre-emptively, attempting to hit target of gps.position_update_interval + switch (GPSCycles) { + case 0: + LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000); + break; + case 1: + predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value + LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000); + break; + default: + // Predict lock-time using exponential smoothing: respond slowly to changes + predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction + LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000, + (lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000); } GPSCycles++; - LOG_DEBUG("GPS Lock took %d, average %d\n", (lastSleepStartMsec - lastWakeStartMsec) / 1000, averageLockTime / 1000); } + // How long to wait before attempting next GPS update + // Aims to hit position.gps_update_interval by using the lock-time prediction + uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0; + // If long interval between updates: power off between updates - if ((int32_t)getSleepTime() - averageLockTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) { - setGPSPower(wantAwake, false, getSleepTime() - averageLockTime); - return; + if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) { + setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime); } - // If waking frequently: standby only. Would use more power trying to reacquire lock each time - else if ((int32_t)getSleepTime() - averageLockTime > 10000) { // 10 seconds is enough for standby + // If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time + // We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due + // Will decide which inside setGPSPower method + else { #ifdef GPS_UC6580 - setGPSPower(wantAwake, false, getSleepTime() - averageLockTime); + setGPSPower(wantAwake, false, compensatedSleepTime); #else - setGPSPower(wantAwake, true, getSleepTime() - averageLockTime); + setGPSPower(wantAwake, true, compensatedSleepTime); #endif - return; } - - // Gradually recover from an abnormally long "time to get lock" - if (averageLockTime > 20000) { - averageLockTime -= 1000; // eventually want to sleep again. - } - - // Make sure we don't have a fallthrough where GPS is stuck off - if (wantAwake) - setGPSPower(true, true, 0); } } @@ -1033,14 +1062,14 @@ int32_t GPS::runOnce() uint32_t timeAsleep = now - lastSleepStartMsec; auto sleepTime = getSleepTime(); - if (powerState != GPS_AWAKE && (sleepTime != UINT32_MAX) && - ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - averageLockTime)))) { + if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) && + ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) { // We now want to be awake - so wake up the GPS setAwake(true); } // While we are awake - if (powerState == GPS_AWAKE) { + if (powerState == GPS_ACTIVE) { // LOG_DEBUG("looking for location\n"); // If we've already set time from the GPS, no need to ask the GPS bool gotTime = (getRTCQuality() >= RTCQualityGPS); @@ -1086,7 +1115,7 @@ int32_t GPS::runOnce() // 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms // if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake. - return (powerState == GPS_AWAKE) ? GPS_THREAD_INTERVAL : 5000; + return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000; } // clear the GPS rx buffer as quickly as possible @@ -1617,9 +1646,9 @@ bool GPS::whileIdle() { unsigned int charsInBuf = 0; bool isValid = false; - if (powerState != GPS_AWAKE) { + if (powerState != GPS_ACTIVE) { clearBuffer(); - return (powerState == GPS_AWAKE); + return (powerState == GPS_ACTIVE); } #ifdef SERIAL_BUFFER_SIZE if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) { @@ -1650,6 +1679,10 @@ bool GPS::whileIdle() } void GPS::enable() { + // Clear the old lock-time prediction + GPSCycles = 0; + predictedLockTime = 0; + enabled = true; setInterval(GPS_THREAD_INTERVAL); setAwake(true); diff --git a/src/gps/GPS.h b/src/gps/GPS.h index e9ec111a7..34e1844c3 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -39,9 +39,10 @@ typedef enum { } GPS_RESPONSE; enum GPSPowerState : uint8_t { - GPS_OFF = 0, - GPS_AWAKE = 1, - GPS_STANDBY = 2, + GPS_OFF = 0, // Physically powered off + GPS_ACTIVE = 1, // Awake and want a position + GPS_STANDBY = 2, // Physically powered on, but soft-sleeping + GPS_IDLE = 3, // Awake, but not wanting another position yet }; // Generate a string representation of DOP @@ -72,7 +73,7 @@ class GPS : private concurrency::OSThread uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; uint32_t en_gpio = 0; - int32_t averageLockTime = 0; + int32_t predictedLockTime = 0; uint32_t GPSCycles = 0; int speedSelect = 0; @@ -93,7 +94,7 @@ class GPS : private concurrency::OSThread bool GPSInitFinished = false; // Init thread finished? bool GPSInitStarted = false; // Init thread finished? - GPSPowerState powerState = GPS_OFF; // GPS_AWAKE if we want a location right now + GPSPowerState powerState = GPS_OFF; // GPS_ACTIVE if we want a location right now uint8_t numSatellites = 0; From 1a5227c8266ee0eaac5f6b4388f62706648db72a Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Fri, 14 Jun 2024 17:45:16 +0200 Subject: [PATCH 0575/1377] Ensure data directory ownership is with mesh user (#4097) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index fee6c62d4..08cb3925d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,7 @@ USER mesh WORKDIR /home/mesh COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/ +RUN mkdir data VOLUME /home/mesh/data CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ] From 8b8e056b7bcce9f4ee3e4256c6c94a724e1d190f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 14 Jun 2024 16:27:49 -0500 Subject: [PATCH 0576/1377] Added (excluded) Dropzone Module for more comprehensive module example (#4098) * DropzoneModule hello world * Buttoning things up * Exclude by default * Upstream refs * Cleanup * Add modules folder to path * Case and path matters * Exclude from header * Guard --- platformio.ini | 1 + src/modules/DropzoneModule.cpp | 95 +++++++++++++++++++ src/modules/DropzoneModule.h | 37 ++++++++ src/modules/Modules.cpp | 9 ++ .../Telemetry/Sensor/DFRobotLarkSensor.h | 5 + src/modules/Telemetry/UnitConversions.cpp | 21 ++++ src/modules/Telemetry/UnitConversions.h | 10 ++ 7 files changed, 178 insertions(+) create mode 100644 src/modules/DropzoneModule.cpp create mode 100644 src/modules/DropzoneModule.h create mode 100644 src/modules/Telemetry/UnitConversions.cpp create mode 100644 src/modules/Telemetry/UnitConversions.h diff --git a/platformio.ini b/platformio.ini index 34471fc54..0de3e25c9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -73,6 +73,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_FSK4 -DRADIOLIB_EXCLUDE_APRS -DRADIOLIB_EXCLUDE_LORAWAN + -DMESHTASTIC_EXCLUDE_DROPZONE=1 monitor_speed = 115200 diff --git a/src/modules/DropzoneModule.cpp b/src/modules/DropzoneModule.cpp new file mode 100644 index 000000000..8c5b5dcdd --- /dev/null +++ b/src/modules/DropzoneModule.cpp @@ -0,0 +1,95 @@ +#if !MESHTASTIC_EXCLUDE_DROPZONE + +#include "DropzoneModule.h" +#include "MeshService.h" +#include "configuration.h" +#include "gps/GeoCoord.h" +#include "gps/RTC.h" +#include "main.h" + +#include + +#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h" +#include "modules/Telemetry/UnitConversions.h" + +#include + +DropzoneModule *dropzoneModule; + +int32_t DropzoneModule::runOnce() +{ + // Send on a 5 second delay from receiving the matching request + if (startSendConditions != 0 && (startSendConditions + 5000U) < millis()) { + service.sendToMesh(sendConditions(), RX_SRC_LOCAL); + startSendConditions = 0; + } + // Run every second to check if we need to send conditions + return 1000; +} + +ProcessMessage DropzoneModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + auto &p = mp.decoded; + char matchCompare[54]; + auto incomingMessage = reinterpret_cast(p.payload.bytes); + sprintf(matchCompare, "%s conditions", owner.short_name); + if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) { + LOG_DEBUG("Received dropzone conditions request\n"); + startSendConditions = millis(); + } + + sprintf(matchCompare, "%s conditions", owner.long_name); + if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) { + LOG_DEBUG("Received dropzone conditions request\n"); + startSendConditions = millis(); + } + return ProcessMessage::CONTINUE; +} + +meshtastic_MeshPacket *DropzoneModule::sendConditions() +{ + char replyStr[200]; + /* + CLOSED @ {HH:MM:SS}z + Wind 2 kts @ 125° + 29.25 inHg 72°C + */ + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); + int hour = 0, min = 0, sec = 0; + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + hour = hms / SEC_PER_HOUR; + min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; + } + + // Check if the dropzone is open or closed by reading the analog pin + // If pin is connected to GND (below 100 should be lower than floating voltage), + // the dropzone is open + auto dropzoneStatus = analogRead(A1) < 100 ? "OPEN" : "CLOSED"; + auto reply = allocDataPacket(); + + auto node = nodeDB->getMeshNode(nodeDB->getNodeNum()); + if (sensor.hasSensor()) { + meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero; + sensor.getMetrics(&telemetry); + auto windSpeed = UnitConversions::MetersPerSecondToKnots(telemetry.variant.environment_metrics.wind_speed); + auto windDirection = telemetry.variant.environment_metrics.wind_direction; + auto temp = telemetry.variant.environment_metrics.temperature; + auto baro = UnitConversions::HectoPascalToInchesOfMercury(telemetry.variant.environment_metrics.barometric_pressure); + sprintf(replyStr, "%s @ %02d:%02d:%02dz\nWind %.2f kts @ %d°\nBaro %.2f inHg %.2f°C", dropzoneStatus, hour, min, sec, + windSpeed, windDirection, baro, temp); + } else { + LOG_ERROR("No sensor found\n"); + sprintf(replyStr, "%s @ %02d:%02d:%02d\nNo sensor found", dropzoneStatus, hour, min, sec); + } + LOG_DEBUG("Conditions reply: %s\n", replyStr); + reply->decoded.payload.size = strlen(replyStr); // You must specify how many bytes are in the reply + memcpy(reply->decoded.payload.bytes, replyStr, reply->decoded.payload.size); + + return reply; +} + +#endif \ No newline at end of file diff --git a/src/modules/DropzoneModule.h b/src/modules/DropzoneModule.h new file mode 100644 index 000000000..28f54ee0f --- /dev/null +++ b/src/modules/DropzoneModule.h @@ -0,0 +1,37 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_DROPZONE +#include "SinglePortModule.h" +#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h" + +/** + * An example module that replies to a message with the current conditions + * and status at the dropzone when it receives a text message mentioning it's name followed by "conditions" + */ +class DropzoneModule : public SinglePortModule, private concurrency::OSThread +{ + DFRobotLarkSensor sensor; + + public: + /** Constructor + * name is for debugging output + */ + DropzoneModule() : SinglePortModule("dropzone", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("DropzoneModule") + { + // Set up the analog pin for reading the dropzone status + pinMode(PIN_A1, INPUT); + } + + virtual int32_t runOnce() override; + + protected: + /** Called to handle a particular incoming message + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + private: + meshtastic_MeshPacket *sendConditions(); + uint32_t startSendConditions = 0; +}; + +extern DropzoneModule *dropzoneModule; +#endif \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e6c44fae6..1b4bbc3b4 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -70,6 +70,11 @@ #include "modules/SerialModule.h" #endif #endif + +#if !MESHTASTIC_EXCLUDE_DROPZONE +#include "modules/DropzoneModule.h" +#endif + /** * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) */ @@ -100,6 +105,10 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_ATAK atakPluginModule = new AtakPluginModule(); #endif + +#if !MESHTASTIC_EXCLUDE_DROPZONE + dropzoneModule = new DropzoneModule(); +#endif // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable. diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h index b26d690b1..7a988e84a 100644 --- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h +++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h @@ -1,3 +1,7 @@ +#pragma once + +#ifndef _MT_DFROBOTLARKSENSOR_H +#define _MT_DFROBOTLARKSENSOR_H #include "configuration.h" #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR @@ -21,4 +25,5 @@ class DFRobotLarkSensor : public TelemetrySensor virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; +#endif #endif \ No newline at end of file diff --git a/src/modules/Telemetry/UnitConversions.cpp b/src/modules/Telemetry/UnitConversions.cpp new file mode 100644 index 000000000..9f40de40f --- /dev/null +++ b/src/modules/Telemetry/UnitConversions.cpp @@ -0,0 +1,21 @@ +#include "UnitConversions.h" + +float UnitConversions::CelsiusToFahrenheit(float celcius) +{ + return (celcius * 9) / 5 + 32; +} + +float UnitConversions::MetersPerSecondToKnots(float metersPerSecond) +{ + return metersPerSecond * 1.94384; +} + +float UnitConversions::MetersPerSecondToMilesPerHour(float metersPerSecond) +{ + return metersPerSecond * 2.23694; +} + +float UnitConversions::HectoPascalToInchesOfMercury(float hectoPascal) +{ + return hectoPascal * 0.029529983071445; +} diff --git a/src/modules/Telemetry/UnitConversions.h b/src/modules/Telemetry/UnitConversions.h new file mode 100644 index 000000000..60f9b664a --- /dev/null +++ b/src/modules/Telemetry/UnitConversions.h @@ -0,0 +1,10 @@ +#pragma once + +class UnitConversions +{ + public: + static float CelsiusToFahrenheit(float celcius); + static float MetersPerSecondToKnots(float metersPerSecond); + static float MetersPerSecondToMilesPerHour(float metersPerSecond); + static float HectoPascalToInchesOfMercury(float hectoPascal); +}; From e55604b8e5e233548c9e160ed008be9d16b647bc Mon Sep 17 00:00:00 2001 From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com> Date: Sat, 15 Jun 2024 08:36:20 +0800 Subject: [PATCH 0577/1377] rak10701: support touchscreen (#4104) * Add the touch screen driver RAK10701 platform, lib_deps https://github.com/RAKWireless/RAK14014-FT6336U * Added RAK10701 touch screen virtual keyboard, supporting cannedMessageModule free text --- src/graphics/TFTDisplay.cpp | 25 +++++++++++++++++++++++++ src/modules/CannedMessageModule.cpp | 28 ++++++++++++++-------------- src/modules/CannedMessageModule.h | 4 ++-- variants/rak10701/platformio.ini | 2 ++ variants/rak10701/variant.h | 8 ++++---- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b19e402b8..39099bd73 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -118,7 +118,15 @@ static LGFX *tft = nullptr; #elif defined(RAK14014) #include +#include TFT_eSPI *tft = nullptr; +FT6336U ft6336u; + +static uint8_t _rak14014_touch_int = false; // TP interrupt generation flag. +static void rak14014_tpIntHandle(void) +{ + _rak14014_touch_int = true; +} #elif defined(ST7789_CS) #include // Graphics and font library for ST7735 driver chip @@ -642,8 +650,12 @@ void TFTDisplay::sendCommand(uint8_t com) void TFTDisplay::setDisplayBrightness(uint8_t _brightness) { +#ifdef RAK14014 + //todo +#else tft->setBrightness(_brightness); LOG_DEBUG("Brightness is set to value: %i \n", _brightness); +#endif } void TFTDisplay::flipScreenVertically() @@ -657,6 +669,7 @@ void TFTDisplay::flipScreenVertically() bool TFTDisplay::hasTouch(void) { #ifdef RAK14014 + return true; #elif !defined(M5STACK) return tft->touch() != nullptr; #else @@ -667,6 +680,15 @@ bool TFTDisplay::hasTouch(void) bool TFTDisplay::getTouch(int16_t *x, int16_t *y) { #ifdef RAK14014 + if(_rak14014_touch_int) { + _rak14014_touch_int = false; + /* The X and Y axes have to be switched */ + *y = ft6336u.read_touch1_x(); + *x = TFT_HEIGHT - ft6336u.read_touch1_y(); + return true; + } else { + return false; + } #elif !defined(M5STACK) return tft->getTouch(x, y); #else @@ -717,6 +739,9 @@ bool TFTDisplay::connect() tft->setRotation(1); tft->setSwapBytes(true); // tft->fillScreen(TFT_BLACK); + ft6336u.begin(); + pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING); #elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2) tft->setRotation(1); // T-Deck has the TFT in landscape #elif defined(T_WATCH_S3) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 9b993ae5a..f513e045f 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -49,7 +49,7 @@ CannedMessageModule::CannedMessageModule() LOG_INFO("CannedMessageModule is enabled\n"); // T-Watch interface currently has no way to select destination type, so default to 'node' -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; #endif @@ -75,7 +75,7 @@ int CannedMessageModule::splitConfiguredMessages() String messages = cannedMessageModuleConfig.messages; -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) String separator = messages.length() ? "|" : ""; messages = "[---- Free Text ----]" + separator + messages; @@ -144,7 +144,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) { -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) if (this->currentMessageIndex == 0) { this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; @@ -170,7 +170,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) e.frameChanged = true; this->currentMessageIndex = -1; -#ifndef T_WATCH_S3 +#if !defined(T_WATCH_S3) && !defined(RAK14014) this->freetext = ""; // clear freetext this->cursor = 0; this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; @@ -183,7 +183,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) || (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) { -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { this->payload = 0xb4; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { @@ -283,7 +283,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { String keyTapped = keyForCoordinates(event->touchX, event->touchY); @@ -404,7 +404,7 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; // clear freetext this->cursor = 0; -#ifndef T_WATCH_S3 +#if !defined(T_WATCH_S3) && !defined(RAK14014) this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; #endif @@ -417,7 +417,7 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; // clear freetext this->cursor = 0; -#ifndef T_WATCH_S3 +#if !defined(T_WATCH_S3) && !defined(RAK14014) this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; #endif @@ -437,7 +437,7 @@ int32_t CannedMessageModule::runOnce() powerFSM.trigger(EVENT_PRESS); return INT32_MAX; } else { -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) sendText(this->dest, indexChannels[this->channel], this->messages[this->currentMessageIndex], true); #else sendText(NODENUM_BROADCAST, channels.getPrimaryIndex(), this->messages[this->currentMessageIndex], true); @@ -454,7 +454,7 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; // clear freetext this->cursor = 0; -#ifndef T_WATCH_S3 +#if !defined(T_WATCH_S3) && !defined(RAK14014) this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; #endif @@ -471,7 +471,7 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; // clear freetext this->cursor = 0; -#ifndef T_WATCH_S3 +#if !defined(T_WATCH_S3) && !defined(RAK14014) this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; #endif @@ -484,7 +484,7 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; // clear freetext this->cursor = 0; -#ifndef T_WATCH_S3 +#if !defined(T_WATCH_S3) && !defined(RAK14014) this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; #endif @@ -714,7 +714,7 @@ void CannedMessageModule::showTemporaryMessage(const String &message) setIntervalFromNow(2000); } -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) String CannedMessageModule::keyForCoordinates(uint x, uint y) { @@ -949,7 +949,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled."); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(RAK14014) drawKeyboard(display, state, 0, 0); #else diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 43897e782..00e8c2bf9 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -98,7 +98,7 @@ class CannedMessageModule : public SinglePortModule, public Observable Date: Fri, 14 Jun 2024 19:53:47 -0500 Subject: [PATCH 0578/1377] Trunk --- src/graphics/TFTDisplay.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 39099bd73..8ea90c523 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -117,8 +117,8 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; #elif defined(RAK14014) -#include #include +#include TFT_eSPI *tft = nullptr; FT6336U ft6336u; @@ -651,7 +651,7 @@ void TFTDisplay::sendCommand(uint8_t com) void TFTDisplay::setDisplayBrightness(uint8_t _brightness) { #ifdef RAK14014 - //todo + // todo #else tft->setBrightness(_brightness); LOG_DEBUG("Brightness is set to value: %i \n", _brightness); @@ -680,7 +680,7 @@ bool TFTDisplay::hasTouch(void) bool TFTDisplay::getTouch(int16_t *x, int16_t *y) { #ifdef RAK14014 - if(_rak14014_touch_int) { + if (_rak14014_touch_int) { _rak14014_touch_int = false; /* The X and Y axes have to be switched */ *y = ft6336u.read_touch1_x(); @@ -738,7 +738,7 @@ bool TFTDisplay::connect() #elif defined(RAK14014) tft->setRotation(1); tft->setSwapBytes(true); -// tft->fillScreen(TFT_BLACK); + // tft->fillScreen(TFT_BLACK); ft6336u.begin(); pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING); From 21d47adb8d23be4e1f5054b3b89b12b13205149f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 15 Jun 2024 09:45:33 -0500 Subject: [PATCH 0579/1377] [create-pull-request] automated change (#4114) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index ab576a4a1..dc066c89f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit ab576a4a122c1a1d0a3c2235b0a0cf3bd4a83c65 +Subproject commit dc066c89f73fce882e5a47648cba18a1967a7f56 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 2a209ad0a..d0e643dff 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -135,6 +135,8 @@ typedef struct _meshtastic_AdminMessage { bool enter_dfu_mode_request; /* Delete the file by the specified path from the device */ char delete_file_request[201]; + /* Set zero and offset for scale chips */ + uint32_t set_scale; /* Set the owner for this node */ meshtastic_User set_owner; /* Set channels (using the new API). @@ -238,6 +240,7 @@ extern "C" { #define meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag 20 #define meshtastic_AdminMessage_enter_dfu_mode_request_tag 21 #define meshtastic_AdminMessage_delete_file_request_tag 22 +#define meshtastic_AdminMessage_set_scale_tag 23 #define meshtastic_AdminMessage_set_owner_tag 32 #define meshtastic_AdminMessage_set_channel_tag 33 #define meshtastic_AdminMessage_set_config_tag 34 @@ -281,6 +284,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,get_node_remote_hardware_pin X(a, STATIC, ONEOF, MESSAGE, (payload_variant,get_node_remote_hardware_pins_response,get_node_remote_hardware_pins_response), 20) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,enter_dfu_mode_request,enter_dfu_mode_request), 21) \ X(a, STATIC, ONEOF, STRING, (payload_variant,delete_file_request,delete_file_request), 22) \ +X(a, STATIC, ONEOF, UINT32, (payload_variant,set_scale,set_scale), 23) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_owner,set_owner), 32) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_channel,set_channel), 33) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_config,set_config), 34) \ From 32702e2750cd8b85d2855fb443218beff920e039 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 15 Jun 2024 09:46:15 -0500 Subject: [PATCH 0580/1377] Fix compiler warnings (#4112) --- src/AccelerometerThread.h | 1 + src/gps/GPS.h | 2 +- variants/rak4631/platformio.ini | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index f03752cad..f45511cca 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -138,6 +138,7 @@ class AccelerometerThread : public concurrency::OSThread float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); switch (config.display.compass_orientation) { + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED: case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0: break; case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90: diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 34e1844c3..55bd42d0f 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -73,7 +73,7 @@ class GPS : private concurrency::OSThread uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; uint32_t en_gpio = 0; - int32_t predictedLockTime = 0; + uint32_t predictedLockTime = 0; uint32_t GPSCycles = 0; int speedSelect = 0; diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 24f209b01..4870d4b68 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -16,7 +16,7 @@ lib_deps = melopero/Melopero RV3028@^1.1.0 https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 - beegee-tokyo/RAKwireless RAK12034@^1.0.0 + https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink \ No newline at end of file From b1cf5778b4bc07ba5132125d491c8f817651781c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 15 Jun 2024 09:46:31 -0500 Subject: [PATCH 0581/1377] Update nrf52 platform to 10.5.0 (#4113) --- arch/nrf52/nrf52.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index f41ef0edc..1a371e920 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -1,6 +1,6 @@ [nrf52_base] ; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files -platform = platformio/nordicnrf52@^10.4.0 +platform = platformio/nordicnrf52@^10.5.0 extends = arduino_base build_type = debug From 96be051bff6d18c45218d39d1a668e6a56cef8a0 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sun, 16 Jun 2024 07:58:46 +1200 Subject: [PATCH 0582/1377] Screensaver validates short name (#4115) --- src/graphics/Screen.cpp | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 5a892bbfb..60168cffc 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -277,6 +277,30 @@ static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) } } +/// Check if the display can render a string (detect special chars; emoji) +static bool haveGlyphs(const char *str) +{ +#if defined(OLED_UA) || defined(OLED_RU) + // Don't want to make any assumptions about custom language support + return true; +#endif + + // Check each character with the lookup function for the OLED library + // We're not really meant to use this directly.. + bool have = true; + for (uint16_t i = 0; i < strlen(str); i++) { + uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); + // If font doesn't support a character, it is substituted for ¿ + if (result == 191 && (uint8_t)str[i] != 191) { + have = false; + break; + } + } + + LOG_DEBUG("haveGlyphs=%d\n", have); + return have; +} + #ifdef USE_EINK /// Used on eink displays while in deep sleep static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -301,14 +325,15 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta display->setTextAlignment(TEXT_ALIGN_LEFT); const char *pauseText = "Screen Paused"; const char *idText = owner.short_name; + const bool useId = haveGlyphs(idText); // This bool is used to hide the idText box if we can't render the short name constexpr uint16_t padding = 5; constexpr uint8_t dividerGap = 1; constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in. // Dimensions - const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText)); + const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText), true); // "true": handle utf8 chars const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText)); - const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding; + const uint16_t boxWidth = padding + (useId ? idTextWidth + padding + padding : 0) + pauseTextWidth + padding; const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding; // Position @@ -318,7 +343,7 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta const int16_t boxBottom = boxTop + boxHeight - 1; const int16_t idTextLeft = boxLeft + padding; const int16_t idTextTop = boxTop + padding; - const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding; + const int16_t pauseTextLeft = boxLeft + (useId ? padding + idTextWidth + padding : 0) + padding; const int16_t pauseTextTop = boxTop + padding; const int16_t dividerX = boxLeft + padding + idTextWidth + padding; const int16_t dividerTop = boxTop + 1 + dividerGap; @@ -331,12 +356,14 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); // Draw: Text - display->drawString(idTextLeft, idTextTop, idText); + if (useId) + display->drawString(idTextLeft, idTextTop, idText); display->drawString(pauseTextLeft, pauseTextTop, pauseText); display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold // Draw: divider - display->drawLine(dividerX, dividerTop, dividerX, dividerBottom); + if (useId) + display->drawLine(dividerX, dividerTop, dividerX, dividerBottom); } #endif From a38a18da0d1b8b69b092b04e2de5daf205d89f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 02:59:22 +0200 Subject: [PATCH 0583/1377] WIP: add NAU7802 based scale controller. (#4092) * WIP: add NAU7802 based scale controller. Needs proto commit * WIP: add NAU7802 based scale controller. Needs proto commit * telemetry uses kg, scale internally g * add sensor calibration setters --- platformio.ini | 18 ++- src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 121 ++++++++++++++- src/modules/Telemetry/EnvironmentTelemetry.h | 4 + .../Telemetry/Sensor/NAU7802Sensor.cpp | 143 ++++++++++++++++++ src/modules/Telemetry/Sensor/NAU7802Sensor.h | 31 ++++ .../Telemetry/Sensor/TelemetrySensor.h | 7 + 9 files changed, 318 insertions(+), 11 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/NAU7802Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/NAU7802Sensor.h diff --git a/platformio.ini b/platformio.ini index 0de3e25c9..d8d398775 100644 --- a/platformio.ini +++ b/platformio.ini @@ -122,10 +122,7 @@ lib_deps = adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP085 Library@^1.2.4 adafruit/Adafruit BME280 Library@^2.2.2 - https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 - boschsensortec/BME68x Sensor Library@^1.1.40407 adafruit/Adafruit MCP9808 Library@^2.0.0 - https://github.com/KodinLanewave/INA3221@^1.0.0 adafruit/Adafruit INA260 Library@^1.5.0 adafruit/Adafruit INA219@^1.2.0 adafruit/Adafruit SHTC3 Library@^1.0.0 @@ -135,13 +132,22 @@ lib_deps = adafruit/Adafruit MPU6050@^2.2.4 adafruit/Adafruit LIS3DH@^1.2.4 adafruit/Adafruit AHTX0@^2.0.5 - lewisxhe/SensorLib@^0.2.0 adafruit/Adafruit LSM6DS@^4.7.2 - mprograms/QMC5883LCompass@^1.2.0 adafruit/Adafruit VEML7700 Library@^2.1.6 adafruit/Adafruit SHT4x Library@^1.0.4 adafruit/Adafruit TSL2591 Library@^1.4.5 + sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5 ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + + + https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 + boschsensortec/BME68x Sensor Library@^1.1.40407 + https://github.com/KodinLanewave/INA3221@^1.0.0 + lewisxhe/SensorLib@^0.2.0 + mprograms/QMC5883LCompass@^1.2.0 + + + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + diff --git a/src/configuration.h b/src/configuration.h index 62c48a205..1149f344c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -136,6 +136,7 @@ along with this program. If not, see . #define OPT3001_ADDR_ALT 0x44 #define MLX90632_ADDR 0x3A #define DFROBOT_LARK_ADDR 0x42 +#define NAU7802_ADDR 0x2A // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 20994ede1..dcc1f40ae 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -50,7 +50,8 @@ class ScanI2C MLX90632, AHT10, BMX160, - DFROBOT_LARK + DFROBOT_LARK, + NAU7802 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index f800a9963..6766db014 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -350,6 +350,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n"); SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found\n"); SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); + SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 46b8a1ad8..ff3202067 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -27,6 +27,7 @@ #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" #include "Sensor/MLX90632Sensor.h" +#include "Sensor/NAU7802Sensor.h" #include "Sensor/OPT3001Sensor.h" #include "Sensor/RCWL9620Sensor.h" #include "Sensor/SHT31Sensor.h" @@ -51,6 +52,7 @@ RCWL9620Sensor rcwl9620Sensor; AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; +NAU7802Sensor nau7802Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -125,6 +127,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = aht10Sensor.runOnce(); if (mlx90632Sensor.hasSensor()) result = mlx90632Sensor.runOnce(); + if (nau7802Sensor.hasSensor()) + result = nau7802Sensor.runOnce(); } return result; } else { @@ -223,12 +227,18 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); } + if (lastMeasurement.variant.environment_metrics.iaq != 0) { display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); } + if (lastMeasurement.variant.environment_metrics.distance != 0) display->drawString(x, y += fontHeight(FONT_SMALL), "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); + + if (lastMeasurement.variant.environment_metrics.weight != 0) + display->drawString(x, y += fontHeight(FONT_SMALL), + "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -245,8 +255,9 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); - LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees\n", sender, - t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction); + LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", sender, + t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction, + t->variant.environment_metrics.weight); #endif // release previous packet before occupying a new spot @@ -331,6 +342,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; } + if (nau7802Sensor.hasSensor()) { + valid = valid && nau7802Sensor.getMetrics(&m); + hasSensor = true; + } if (aht10Sensor.hasSensor()) { if (!bmp280Sensor.hasSensor()) { valid = valid && aht10Sensor.getMetrics(&m); @@ -354,8 +369,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage, m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); - LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees\n", m.variant.environment_metrics.wind_speed, - m.variant.environment_metrics.wind_direction); + LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", m.variant.environment_metrics.wind_speed, + m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight); sensor_read_error_count = 0; @@ -388,4 +403,102 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) return valid; } +AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp, + meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED; + if (dfRobotLarkSensor.hasSensor()) { + result = dfRobotLarkSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (sht31Sensor.hasSensor()) { + result = sht31Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (lps22hbSensor.hasSensor()) { + result = lps22hbSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (shtc3Sensor.hasSensor()) { + result = shtc3Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (bmp085Sensor.hasSensor()) { + result = bmp085Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (bmp280Sensor.hasSensor()) { + result = bmp280Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (bme280Sensor.hasSensor()) { + result = bme280Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (bme680Sensor.hasSensor()) { + result = bme680Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (mcp9808Sensor.hasSensor()) { + result = mcp9808Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (ina219Sensor.hasSensor()) { + result = ina219Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (ina260Sensor.hasSensor()) { + result = ina260Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (veml7700Sensor.hasSensor()) { + result = veml7700Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (tsl2591Sensor.hasSensor()) { + result = tsl2591Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (opt3001Sensor.hasSensor()) { + result = opt3001Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (mlx90632Sensor.hasSensor()) { + result = mlx90632Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (rcwl9620Sensor.hasSensor()) { + result = rcwl9620Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (nau7802Sensor.hasSensor()) { + result = nau7802Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + if (aht10Sensor.hasSensor()) { + result = aht10Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + return result; +} + #endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index cdd9491d4..ca150347e 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -37,6 +37,10 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu */ bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp, + meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; + private: float CelsiusToFahrenheit(float c); bool firstTime = 1; diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp new file mode 100644 index 000000000..39ac4b08b --- /dev/null +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp @@ -0,0 +1,143 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "FSCommon.h" +#include "NAU7802Sensor.h" +#include "TelemetrySensor.h" +#include +#include + +meshtastic_Nau7802Config nau7802config = meshtastic_Nau7802Config_init_zero; + +NAU7802Sensor::NAU7802Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_NAU7802, "NAU7802") {} + +int32_t NAU7802Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + status = nau7802.begin(*nodeTelemetrySensorsMap[sensorType].second); + nau7802.setSampleRate(NAU7802_SPS_320); + if (!loadCalibrationData()) { + LOG_ERROR("Failed to load calibration data\n"); + } + nau7802.calibrateAFE(); + LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor()); + return initI2CSensor(); +} + +void NAU7802Sensor::setup() {} + +bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("NAU7802Sensor::getMetrics\n"); + nau7802.powerUp(); + // Wait for the sensor to become ready for one second max + uint32_t start = millis(); + while (!nau7802.available()) { + delay(100); + if (millis() - start > 1000) { + nau7802.powerDown(); + return false; + } + } + // Check if we have correct calibration values after powerup + LOG_DEBUG("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor()); + measurement->variant.environment_metrics.weight = nau7802.getWeight() / 1000; // sample is in kg + nau7802.powerDown(); + return true; +} + +void NAU7802Sensor::calibrate(float weight) +{ + nau7802.calculateCalibrationFactor(weight * 1000, 64); // internal sample is in grams + if (!saveCalibrationData()) { + LOG_WARN("Failed to save calibration data\n"); + } + LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor()); +} + +AdminMessageHandleResult NAU7802Sensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result; + + switch (request->which_payload_variant) { + case meshtastic_AdminMessage_set_scale_tag: + if (request->set_scale == 0) { + this->tare(); + LOG_DEBUG("Client requested to tare scale\n"); + } else { + this->calibrate(request->set_scale); + LOG_DEBUG("Client requested to calibrate to %d kg\n", request->set_scale); + } + result = AdminMessageHandleResult::HANDLED; + break; + + default: + result = AdminMessageHandleResult::NOT_HANDLED; + } + + return result; +} + +void NAU7802Sensor::tare() +{ + nau7802.calculateZeroOffset(64); + if (!saveCalibrationData()) { + LOG_WARN("Failed to save calibration data\n"); + } + LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor()); +} + +bool NAU7802Sensor::saveCalibrationData() +{ + if (FSCom.exists(nau7802ConfigFileName) && !FSCom.remove(nau7802ConfigFileName)) { + LOG_WARN("Can't remove old state file\n"); + } + auto file = FSCom.open(nau7802ConfigFileName, FILE_O_WRITE); + nau7802config.zeroOffset = nau7802.getZeroOffset(); + nau7802config.calibrationFactor = nau7802.getCalibrationFactor(); + bool okay = false; + if (file) { + LOG_INFO("%s state write to %s.\n", sensorName, nau7802ConfigFileName); + pb_ostream_t stream = {&writecb, &file, meshtastic_Nau7802Config_size}; + + if (!pb_encode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) { + LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); + } else { + okay = true; + } + file.flush(); + file.close(); + } else { + LOG_INFO("Can't write %s state (File: %s).\n", sensorName, nau7802ConfigFileName); + } + return okay; +} + +bool NAU7802Sensor::loadCalibrationData() +{ + auto file = FSCom.open(nau7802ConfigFileName, FILE_O_READ); + bool okay = false; + if (file) { + LOG_INFO("%s state read from %s.\n", sensorName, nau7802ConfigFileName); + pb_istream_t stream = {&readcb, &file, meshtastic_Nau7802Config_size}; + if (!pb_decode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) { + LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream)); + } else { + nau7802.setZeroOffset(nau7802config.zeroOffset); + nau7802.setCalibrationFactor(nau7802config.calibrationFactor); + okay = true; + } + file.close(); + } else { + LOG_INFO("No %s state found (File: %s).\n", sensorName, nau7802ConfigFileName); + } + return okay; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.h b/src/modules/Telemetry/Sensor/NAU7802Sensor.h new file mode 100644 index 000000000..c53a3b31a --- /dev/null +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.h @@ -0,0 +1,31 @@ +#include "MeshModule.h" +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class NAU7802Sensor : public TelemetrySensor +{ + private: + NAU7802 nau7802; + + protected: + virtual void setup() override; + const char *nau7802ConfigFileName = "/prefs/nau7802.dat"; + bool saveCalibrationData(); + bool loadCalibrationData(); + + public: + NAU7802Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + void tare(); + void calibrate(float weight); + AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 35cb7965d..da376ad31 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -4,6 +4,7 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "MeshModule.h" #include "NodeDB.h" #include @@ -42,6 +43,12 @@ class TelemetrySensor virtual void setup(); public: + virtual AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) + { + return AdminMessageHandleResult::NOT_HANDLED; + } + bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; } virtual int32_t runOnce() = 0; From d7c52c33b93101c1ce15cdb6471f8513516b1cbe Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Sun, 16 Jun 2024 14:24:36 +0800 Subject: [PATCH 0584/1377] Add RAK2560/RAK9154 --- .vscode/extensions.json | 7 +- platformio.ini | 1 + src/Power.cpp | 33 +++ .../Telemetry/Sensor/RAK9154Sensor.cpp | 189 ++++++++++++++++++ src/modules/Telemetry/Sensor/RAK9154Sensor.h | 18 ++ src/power.h | 5 + variants/rak2560/create_uf2.py | 105 ++++++++++ variants/rak2560/platformio.ini | 27 +++ variants/rak4631/variant.h | 32 ++- 9 files changed, 410 insertions(+), 7 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/RAK9154Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/RAK9154Sensor.h create mode 100644 variants/rak2560/create_uf2.py create mode 100644 variants/rak2560/platformio.ini diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4fc84fa78..080e70d08 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,9 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", - "platformio.platformio-ide", - "trunk.io" + "platformio.platformio-ide" ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] } diff --git a/platformio.ini b/platformio.ini index d8d398775..a1beb8e7c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,6 +29,7 @@ default_envs = tbeam ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 +;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 ;default_envs = radiomaster_900_bandit_nano diff --git a/src/Power.cpp b/src/Power.cpp index d80bfd55c..18dbfebe4 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -75,6 +75,10 @@ INA219Sensor ina219Sensor; INA3221Sensor ina3221Sensor; #endif +#if HAS_RAKPROT && !defined(ARCH_PORTDUINO) +RAK9154Sensor rak9154Sensor; +#endif + #ifdef HAS_PMU #include "XPowersAXP192.tpp" #include "XPowersAXP2101.tpp" @@ -145,6 +149,12 @@ class AnalogBatteryLevel : public HasBatteryLevel */ virtual int getBatteryPercent() override { +#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) + if (hasRAK()) { + return rak9154Sensor.getBusBatteryPercent(); + } +#endif + float v = getBattVoltage(); if (v < noBatVolt) @@ -184,6 +194,12 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual uint16_t getBattVoltage() override { +#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) + if (hasRAK()) { + return getRAKVoltage(); + } +#endif + #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (hasINA()) { LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address); @@ -356,6 +372,11 @@ class AnalogBatteryLevel : public HasBatteryLevel /// we can't be smart enough to say 'full'? virtual bool isCharging() override { +#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) + if (hasRAK()) { + return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse; + } +#endif #ifdef EXT_CHRG_DETECT return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; #else @@ -379,6 +400,18 @@ class AnalogBatteryLevel : public HasBatteryLevel float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; +#if defined(HAS_RAKPROT) + + uint16_t getRAKVoltage() { return rak9154Sensor.getBusVoltageMv(); } + + bool hasRAK() + { + if (!rak9154Sensor.isInitialized()) + return rak9154Sensor.runOnce() > 0; + return rak9154Sensor.isRunning(); + } +#endif + #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) uint16_t getINAVoltage() { diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp b/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp new file mode 100644 index 000000000..52d819007 --- /dev/null +++ b/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp @@ -0,0 +1,189 @@ +#include "RAK9154Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" + +#include +#include "concurrency/Periodic.h" + +using namespace concurrency; + +#define BOOT_DATA_REQ + +RAK9154Sensor::RAK9154Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR_UNSET, "RAK1954") {} + +static Periodic *onewirePeriodic; + +static SoftwareHalfSerial mySerial(HALF_UART_PIN); // Wire pin P0.15 + +static uint8_t buff[0x100]; +static uint16_t bufflen = 0; + +static int16_t dc_cur = 0; +static uint16_t dc_vol = 0; +static uint8_t dc_prec = 0; +static uint8_t provision = 0; + +static void onewire_evt(const uint8_t pid, const uint8_t sid, const SNHUBAPI_EVT_E eid, uint8_t *msg, uint16_t len) +{ + switch (eid) + { + case SNHUBAPI_EVT_RECV_REQ: + case SNHUBAPI_EVT_RECV_RSP: + break; + + case SNHUBAPI_EVT_QSEND: + mySerial.write(msg, len); + break; + + case SNHUBAPI_EVT_ADD_SID: + // LOG_INFO("+ADD:SID:[%02x]\r\n", msg[0]); + break; + + case SNHUBAPI_EVT_ADD_PID: + // LOG_INFO("+ADD:PID:[%02x]\r\n", msg[0]); +#ifdef BOOT_DATA_REQ + provision = msg[0]; +#endif + break; + + case SNHUBAPI_EVT_GET_INTV: + break; + + case SNHUBAPI_EVT_GET_ENABLE: + break; + + case SNHUBAPI_EVT_SDATA_REQ: + + // LOG_INFO("+EVT:PID[%02x],IPSO[%02x]\r\n",pid,msg[0]); + // for( uint16_t i=1; i 100) + { + dc_prec = 100; + } + break; + case RAK_IPSO_DC_CURRENT: + dc_cur = (msg[2] << 8) + msg[1]; + break; + case RAK_IPSO_DC_VOLTAGE: + dc_vol = (msg[2] << 8) + msg[1]; + dc_vol *= 10; + break; + default: + break; + } + + break; + case SNHUBAPI_EVT_REPORT: + + // LOG_INFO("+EVT:PID[%02x],IPSO[%02x]\r\n",pid,msg[0]); + // for( uint16_t i=1; i 100) + { + dc_prec = 100; + } + break; + case RAK_IPSO_DC_CURRENT: + dc_cur = (msg[1] << 8) + msg[2]; + break; + case RAK_IPSO_DC_VOLTAGE: + dc_vol = (msg[1] << 8) + msg[2]; + dc_vol *= 10; + break; + default: + break; + } + + break; + + case SNHUBAPI_EVT_CHKSUM_ERR: + LOG_INFO("+ERR:CHKSUM\r\n"); + break; + + case SNHUBAPI_EVT_SEQ_ERR: + LOG_INFO("+ERR:SEQUCE\r\n"); + break; + + default: + break; + } +} + +static int32_t onewireHandle() +{ + if (provision != 0) + { + RakSNHub_Protocl_API.get.data(provision); + provision = 0; + } + + while (mySerial.available()) + { + char a = mySerial.read(); + buff[bufflen++] = a; + delay(2); // continue data, timeout=2ms + } + + if (bufflen != 0) + { + RakSNHub_Protocl_API.process((uint8_t *)buff, bufflen); + bufflen = 0; + } + + return 50; +} + +int32_t RAK9154Sensor::runOnce() +{ + onewirePeriodic = new Periodic("onewireHandle", onewireHandle); + + mySerial.begin(9600); + + RakSNHub_Protocl_API.init(onewire_evt); + + status = true; + initialized = true; + return 0; +} + +void RAK9154Sensor::setup() +{ + // Set up oversampling and filter initialization +} + +bool RAK9154Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + return true; +} + +uint16_t RAK9154Sensor::getBusVoltageMv() +{ + return dc_vol; +} + +int RAK9154Sensor::getBusBatteryPercent() +{ + return (int)dc_prec; +} + +bool RAK9154Sensor::isCharging() +{ + return (dc_cur > 0) ? true : false; +} diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.h b/src/modules/Telemetry/Sensor/RAK9154Sensor.h new file mode 100644 index 000000000..cfe31780a --- /dev/null +++ b/src/modules/Telemetry/Sensor/RAK9154Sensor.h @@ -0,0 +1,18 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "VoltageSensor.h" + +class RAK9154Sensor : public TelemetrySensor, VoltageSensor +{ + private: + protected: + virtual void setup() override; + + public: + RAK9154Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual uint16_t getBusVoltageMv() override; + int getBusBatteryPercent(); + bool isCharging(); +}; \ No newline at end of file diff --git a/src/power.h b/src/power.h index 8d14ed7f8..94bf21cc2 100644 --- a/src/power.h +++ b/src/power.h @@ -46,6 +46,11 @@ extern INA219Sensor ina219Sensor; extern INA3221Sensor ina3221Sensor; #endif +#if HAS_RAKPROT && !defined(ARCH_PORTDUINO) +#include "modules/Telemetry/Sensor/RAK9154Sensor.h" +extern RAK9154Sensor rak9154Sensor; +#endif + class Power : private concurrency::OSThread { diff --git a/variants/rak2560/create_uf2.py b/variants/rak2560/create_uf2.py new file mode 100644 index 000000000..d14eaea02 --- /dev/null +++ b/variants/rak2560/create_uf2.py @@ -0,0 +1,105 @@ +import sys +import struct + +Import("env") + +# Parse input and create UF2 file +def create_uf2(source, target, env): + # source_hex = target[0].get_abspath() + source_hex = target[0].get_string(False) + source_hex = '.\\'+source_hex + print("#########################################################") + print("Create UF2 from "+source_hex) + print("#########################################################") + # print("Source: " + source_hex) + target = source_hex.replace(".hex", "") + target = target + ".uf2" + # print("Target: " + target) + + with open(source_hex, mode='rb') as f: + inpbuf = f.read() + + outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) + + write_file(target, outbuf) + print("#########################################################") + print(target + " is ready to flash to target device") + print("#########################################################") + + +# Add callback after .hex file was created +env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", create_uf2) + +# UF2 creation taken from uf2conv.py +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +familyid = 0xADA52840 + + +class Block: + def __init__(self, addr): + self.addr = addr + self.bytes = bytearray(256) + + def encode(self, blockno, numblocks): + global familyid + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack(" + + + +lib_deps = + ${nrf52840_base.lib_deps} + ${networking_base.lib_deps} + melopero/Melopero RV3028@^1.1.0 + https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 + rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + beegee-tokyo/RAKwireless RAK12034@^1.0.0 + https://github.com/beegee-tokyo/RAK-OneWireSerial.git +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink +extra_scripts = + ${env.extra_scripts} + ../firmware/variants/rak2560/create_uf2.py \ No newline at end of file diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index bc5541336..e9f1a1865 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -100,9 +100,9 @@ static const uint8_t AREF = PIN_AREF; #define PIN_SERIAL1_RX (15) #define PIN_SERIAL1_TX (16) -// Connected to Jlink CDC -#define PIN_SERIAL2_RX (8) -#define PIN_SERIAL2_TX (6) +// Connected to Serial 2 +#define PIN_SERIAL2_RX (19) +#define PIN_SERIAL2_TX (20) /* * SPI Interfaces @@ -228,9 +228,18 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // #define PIN_GPS_EN PIN_3V3_EN #define PIN_GPS_PPS (17) // Pulse per second input from the GPS +// On RAK2560 the GPS is be on a different UART +#ifdef HAS_RAKPROT +// #define GPS_RX_PIN PIN_SERIAL2_RX +// #define GPS_TX_PIN PIN_SERIAL2_TX +// #define PIN_GPS_EN PIN_3V3_EN +// Disable GPS +#define MESHTASTIC_EXCLUDE_GPS 1 +#else + // Enable GPS #define GPS_RX_PIN PIN_SERIAL1_RX #define GPS_TX_PIN PIN_SERIAL1_TX - +#endif // Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press // RAK12002 RTC Module @@ -257,6 +266,21 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RAK_4631 1 +#ifdef HAS_RAKPROT + +#define HALF_UART_PIN PIN_SERIAL1_RX + +#if defined(GPS_RX_PIN) && (GPS_RX_PIN == HALF_UART_PIN) +#error pin 15 collision + +#endif + +#if defined(GPS_TX_PIN) && (GPS_RX_PIN == HALF_UART_PIN) +#error pin 15 collision +#endif + +#endif + #define PIN_ETHERNET_RESET 21 #define PIN_ETHERNET_SS PIN_EINK_CS #define ETH_SPI_PORT SPI1 From 5e01b4251fd8848deb9b5896a841eaa0e6f29585 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Sun, 16 Jun 2024 15:46:37 +0800 Subject: [PATCH 0585/1377] Fix build error for none RAK2560 devices --- src/modules/Telemetry/Sensor/RAK9154Sensor.cpp | 2 ++ src/modules/Telemetry/Sensor/RAK9154Sensor.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp b/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp index 52d819007..4a317045b 100644 --- a/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp @@ -1,3 +1,4 @@ +#ifdef HAS_RAKPROT #include "RAK9154Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" @@ -187,3 +188,4 @@ bool RAK9154Sensor::isCharging() { return (dc_cur > 0) ? true : false; } +#endif // HAS_RAKPROT \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.h b/src/modules/Telemetry/Sensor/RAK9154Sensor.h index cfe31780a..5f5a92741 100644 --- a/src/modules/Telemetry/Sensor/RAK9154Sensor.h +++ b/src/modules/Telemetry/Sensor/RAK9154Sensor.h @@ -1,3 +1,4 @@ +#ifdef HAS_RAKPROT #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include "VoltageSensor.h" @@ -15,4 +16,5 @@ class RAK9154Sensor : public TelemetrySensor, VoltageSensor virtual uint16_t getBusVoltageMv() override; int getBusBatteryPercent(); bool isCharging(); -}; \ No newline at end of file +}; +#endif // HAS_RAKPROT \ No newline at end of file From 27bb3506d390c2a77cd52a8ceae7f04c60cafce4 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sat, 18 May 2024 22:14:22 -0400 Subject: [PATCH 0586/1377] Added fix for ESP32 --- src/detect/ScanI2C.cpp | 28 ++++++++++++++++++++++++++++ src/detect/ScanI2C.h | 1 + src/main.cpp | 3 +++ src/main.h | 3 ++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 3231f7054..ad0171118 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,4 +1,6 @@ #include "ScanI2C.h" +#include "main.h" +#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -27,7 +29,33 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } +bool performScanForCardKB() { + // Example I2C scan code for CardKB (adjust as needed) + Wire.beginTransmission(CARDKB_I2C_ADDRESS); + if (Wire.endTransmission() == 0) { + return true; // CardKB detected + } + return false; // CardKB not detected +} +void scanForCardKB() { + const int maxRetries = 10; // Maximum number of retries + const int retryDelay = 100; // Delay between retries in milliseconds + for (int i = 0; i < maxRetries; ++i) { + // Perform the scan (example scan code, adjust as needed) + cardKBDetected = performScanForCardKB(); + + if (cardKBDetected) { + Serial.println("CardKB Keyboard detected."); + break; + } + + delay(retryDelay); // Wait before the next retry + } + if (!cardKBDetected) { + Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); + } +} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index dcc1f40ae..ee691e864 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,6 +2,7 @@ #include #include +bool performScanForCardKB(); class ScanI2C { diff --git a/src/main.cpp b/src/main.cpp index 6797c8375..9529ca7b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,8 @@ #include // #include +bool cardKBDetected = false; +void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -376,6 +378,7 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif +scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index 2ef7edb3a..b1e1ac464 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif - +extern bool cardKBDetected; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,6 +39,7 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; +#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From dbb254ba7a5c442aab440af19c2ea1a20327a48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 19 May 2024 19:32:08 +0200 Subject: [PATCH 0587/1377] change the main scan class so they scan only for wanted bits - UNTESTED --- src/detect/ScanI2C.cpp | 29 +-------------------------- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 11 ++++++++++- src/detect/ScanI2CTwoWire.h | 4 +++- src/input/kbI2cBase.cpp | 37 +++++++++++++++++++++++++++++++++-- src/main.cpp | 3 --- src/main.h | 3 +-- 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index ad0171118..9c3a18644 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,6 +1,4 @@ #include "ScanI2C.h" -#include "main.h" -#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -8,6 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, int *address) {} void ScanI2C::setSuppressScreen() { @@ -29,33 +28,7 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } -bool performScanForCardKB() { - // Example I2C scan code for CardKB (adjust as needed) - Wire.beginTransmission(CARDKB_I2C_ADDRESS); - if (Wire.endTransmission() == 0) { - return true; // CardKB detected - } - return false; // CardKB not detected -} -void scanForCardKB() { - const int maxRetries = 10; // Maximum number of retries - const int retryDelay = 100; // Delay between retries in milliseconds - for (int i = 0; i < maxRetries; ++i) { - // Perform the scan (example scan code, adjust as needed) - cardKBDetected = performScanForCardKB(); - - if (cardKBDetected) { - Serial.println("CardKB Keyboard detected."); - break; - } - - delay(retryDelay); // Wait before the next retry - } - if (!cardKBDetected) { - Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); - } -} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index ee691e864..1facb897a 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,7 +2,6 @@ #include #include -bool performScanForCardKB(); class ScanI2C { @@ -89,6 +88,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); + virtual void scanPort(ScanI2C::I2CPort, int *); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 6766db014..dd70db8b7 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -135,7 +135,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port) +void ScanI2CTwoWire::scanPort(I2CPort port, int *address) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,6 +163,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port) #endif for (addr.address = 1; addr.address < 127; addr.address++) { + // Skip the address if it is not requested oon a partial scan + if (sizeof(address) > 0 && addr.address != *address) { + continue; + } i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO if (i2cBus->read() != -1) @@ -367,6 +371,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port) } } +void ScanI2CTwoWire::scanPort(I2CPort port) +{ + scanPort(port, nullptr); +} + TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const { if (address.port == ScanI2C::I2CPort::WIRE) { diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 9acd736d2..a7c19c779 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,6 +16,8 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; + void scanPort(ScanI2C::I2CPort, int *) override; + ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const; @@ -53,4 +55,4 @@ class ScanI2CTwoWire : public ScanI2C uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const; DeviceType probeOLED(ScanI2C::DeviceAddress) const; -}; +}; \ No newline at end of file diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index af7c96b20..55f435fda 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -1,6 +1,7 @@ #include "kbI2cBase.h" #include "configuration.h" #include "detect/ScanI2C.h" +#include "detect/ScanI2CTwoWire.h" extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -30,8 +31,40 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { if (cardkb_found.address == 0x00) { - // Input device is not detected. - return INT32_MAX; + // Input device is not detected. Rescan now. + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); + int i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; +#if defined(I2C_SDA1) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan); + auto kb_info = i2cScanner->firstKeyboard(); + + if (kb_info.type != ScanI2C::DeviceType::NONE) { + cardkb_found = kb_info.address; + switch (kb_info.type) { + case ScanI2C::DeviceType::RAK14004: + kb_model = 0x02; + break; + case ScanI2C::DeviceType::CARDKB: + kb_model = 0x00; + break; + case ScanI2C::DeviceType::TDECKKB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x10; + break; + case ScanI2C::DeviceType::BBQ10KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x11; + break; + default: + // use this as default since it's also just zero + LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); + kb_model = 0x00; + } + } + if (cardkb_found.address == 0x00) + return INT32_MAX; } if (!i2cBus) { diff --git a/src/main.cpp b/src/main.cpp index 9529ca7b3..6797c8375 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,8 +35,6 @@ #include // #include -bool cardKBDetected = false; -void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -378,7 +376,6 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif -scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index b1e1ac464..2ef7edb3a 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif -extern bool cardKBDetected; + #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,7 +39,6 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; -#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From ce9e63a2cb21ad943d708ae69fc6a0cbb62e5807 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sat, 18 May 2024 22:14:22 -0400 Subject: [PATCH 0588/1377] Added fix for ESP32 --- src/detect/ScanI2C.cpp | 28 ++++++++++++++++++++++++++++ src/detect/ScanI2C.h | 1 + src/main.cpp | 3 +++ src/main.h | 3 ++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 9c3a18644..51718b15f 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,4 +1,6 @@ #include "ScanI2C.h" +#include "main.h" +#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -28,7 +30,33 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } +bool performScanForCardKB() { + // Example I2C scan code for CardKB (adjust as needed) + Wire.beginTransmission(CARDKB_I2C_ADDRESS); + if (Wire.endTransmission() == 0) { + return true; // CardKB detected + } + return false; // CardKB not detected +} +void scanForCardKB() { + const int maxRetries = 10; // Maximum number of retries + const int retryDelay = 100; // Delay between retries in milliseconds + for (int i = 0; i < maxRetries; ++i) { + // Perform the scan (example scan code, adjust as needed) + cardKBDetected = performScanForCardKB(); + + if (cardKBDetected) { + Serial.println("CardKB Keyboard detected."); + break; + } + + delay(retryDelay); // Wait before the next retry + } + if (!cardKBDetected) { + Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); + } +} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 1facb897a..1c2fe73c6 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,6 +2,7 @@ #include #include +bool performScanForCardKB(); class ScanI2C { diff --git a/src/main.cpp b/src/main.cpp index 6797c8375..9529ca7b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,8 @@ #include // #include +bool cardKBDetected = false; +void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -376,6 +378,7 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif +scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index 2ef7edb3a..b1e1ac464 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif - +extern bool cardKBDetected; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,6 +39,7 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; +#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From 2eb3cfd5e033afb27c687bba0b194b94036d3cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 19 May 2024 19:32:08 +0200 Subject: [PATCH 0589/1377] change the main scan class so they scan only for wanted bits - UNTESTED --- src/detect/ScanI2C.cpp | 28 ---------------------------- src/detect/ScanI2C.h | 1 - src/main.cpp | 3 --- src/main.h | 3 +-- 4 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 51718b15f..9c3a18644 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -1,6 +1,4 @@ #include "ScanI2C.h" -#include "main.h" -#include const ScanI2C::DeviceAddress ScanI2C::ADDRESS_NONE = ScanI2C::DeviceAddress(); const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ADDRESS_NONE); @@ -30,33 +28,7 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; return firstOfOrNONE(2, types); } -bool performScanForCardKB() { - // Example I2C scan code for CardKB (adjust as needed) - Wire.beginTransmission(CARDKB_I2C_ADDRESS); - if (Wire.endTransmission() == 0) { - return true; // CardKB detected - } - return false; // CardKB not detected -} -void scanForCardKB() { - const int maxRetries = 10; // Maximum number of retries - const int retryDelay = 100; // Delay between retries in milliseconds - for (int i = 0; i < maxRetries; ++i) { - // Perform the scan (example scan code, adjust as needed) - cardKBDetected = performScanForCardKB(); - - if (cardKBDetected) { - Serial.println("CardKB Keyboard detected."); - break; - } - - delay(retryDelay); // Wait before the next retry - } - if (!cardKBDetected) { - Serial.println("CardKB Keyboard not detected. Canned Message Module Disabled."); - } -} ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 1c2fe73c6..1facb897a 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -2,7 +2,6 @@ #include #include -bool performScanForCardKB(); class ScanI2C { diff --git a/src/main.cpp b/src/main.cpp index 9529ca7b3..6797c8375 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,8 +35,6 @@ #include // #include -bool cardKBDetected = false; -void scanForCardKB(); #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" @@ -378,7 +376,6 @@ void setup() // otherwise keyboard and touch screen will not work delay(800); #endif -scanForCardKB(); // Initial scan for CardKB // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning diff --git a/src/main.h b/src/main.h index b1e1ac464..2ef7edb3a 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif -extern bool cardKBDetected; + #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; @@ -39,7 +39,6 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; -#define CARDKB_I2C_ADDRESS 0x5F // Replace 0x5F with the actual address if different #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif From ba14ffb8d393662f59481489af7c252a28dd60ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 24 May 2024 22:59:31 +0200 Subject: [PATCH 0590/1377] change type to 8 bit uint --- src/detect/ScanI2C.cpp | 2 +- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 6 +++--- src/detect/ScanI2CTwoWire.h | 2 +- src/input/kbI2cBase.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 9c3a18644..03b93e068 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -6,7 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} -void ScanI2C::scanPort(ScanI2C::I2CPort port, int *address) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address) {} void ScanI2C::setSuppressScreen() { diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 1facb897a..5c75a9dee 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -88,7 +88,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); - virtual void scanPort(ScanI2C::I2CPort, int *); + virtual void scanPort(ScanI2C::I2CPort, uint8_t *); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index dd70db8b7..95e273b85 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -135,7 +135,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port, int *address) +void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,8 +163,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, int *address) #endif for (addr.address = 1; addr.address < 127; addr.address++) { - // Skip the address if it is not requested oon a partial scan - if (sizeof(address) > 0 && addr.address != *address) { + // Skip the address if it is not requested on a partial scan + if (address != nullptr && *address != addr.address) { continue; } i2cBus->beginTransmission(addr.address); diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index a7c19c779..332afbf64 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,7 +16,7 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; - void scanPort(ScanI2C::I2CPort, int *) override; + void scanPort(ScanI2C::I2CPort, uint8_t *) override; ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 55f435fda..135de1716 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -33,7 +33,7 @@ int32_t KbI2cBase::runOnce() if (cardkb_found.address == 0x00) { // Input device is not detected. Rescan now. auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); - int i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; #if defined(I2C_SDA1) i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); #endif From a453d7f52c80ea6887714b2415c1cac8f138806e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 25 May 2024 12:37:55 +0200 Subject: [PATCH 0591/1377] Iterate through uint array --- src/detect/ScanI2C.cpp | 2 +- src/detect/ScanI2C.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 21 +++++++++++++++------ src/detect/ScanI2CTwoWire.h | 2 +- src/input/kbI2cBase.cpp | 5 +++-- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 03b93e068..73bdf973b 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -6,7 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C:: ScanI2C::ScanI2C() = default; void ScanI2C::scanPort(ScanI2C::I2CPort port) {} -void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address) {} +void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address, uint8_t asize) {} void ScanI2C::setSuppressScreen() { diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 5c75a9dee..711e8bee5 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -88,7 +88,7 @@ class ScanI2C ScanI2C(); virtual void scanPort(ScanI2C::I2CPort); - virtual void scanPort(ScanI2C::I2CPort, uint8_t *); + virtual void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t); /* * A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it. diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 95e273b85..b04590509 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -14,6 +14,15 @@ #define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 #endif +bool in_array(uint8_t *array, int size, uint8_t lookfor) +{ + int i; + for (i = 0; i < size; i++) + if (lookfor == array[i]) + return true; + return false; +} + ScanI2C::FoundDevice ScanI2CTwoWire::find(ScanI2C::DeviceType type) const { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -135,7 +144,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation type = T; \ break; -void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) +void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) { concurrency::LockGuard guard((concurrency::Lock *)&lock); @@ -163,10 +172,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) #endif for (addr.address = 1; addr.address < 127; addr.address++) { - // Skip the address if it is not requested on a partial scan - if (address != nullptr && *address != addr.address) { - continue; - } + if (asize != 0) + if (in_array(address, asize, addr.address)) + continue; + i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO if (i2cBus->read() != -1) @@ -373,7 +382,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address) void ScanI2CTwoWire::scanPort(I2CPort port) { - scanPort(port, nullptr); + scanPort(port, nullptr, 0); } TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 332afbf64..82b48f6b4 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -16,7 +16,7 @@ class ScanI2CTwoWire : public ScanI2C public: void scanPort(ScanI2C::I2CPort) override; - void scanPort(ScanI2C::I2CPort, uint8_t *) override; + void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t) override; ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 135de1716..ce22edb93 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -34,10 +34,11 @@ int32_t KbI2cBase::runOnce() // Input device is not detected. Rescan now. auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; + uint8_t i2caddr_asize = 3; #if defined(I2C_SDA1) - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); #endif - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); auto kb_info = i2cScanner->firstKeyboard(); if (kb_info.type != ScanI2C::DeviceType::NONE) { From 85d621d9c6d2c54117559e24a2c7c33c21fe80fa Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Sun, 16 Jun 2024 19:45:17 +0800 Subject: [PATCH 0592/1377] Move RAK9154 to variants, fix json --- .vscode/extensions.json | 7 ++-- platformio.ini | 4 +- src/power.h | 42 ++++++++++--------- .../rak2560}/RAK9154Sensor.cpp | 6 +-- .../rak2560}/RAK9154Sensor.h | 7 +++- variants/rak2560/platformio.ini | 2 +- 6 files changed, 36 insertions(+), 32 deletions(-) rename {src/modules/Telemetry/Sensor => variants/rak2560}/RAK9154Sensor.cpp (96%) rename {src/modules/Telemetry/Sensor => variants/rak2560}/RAK9154Sensor.h (71%) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 080e70d08..b50c95349 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,9 +2,8 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "platformio.platformio-ide" + "ms-vscode.cpptools", + "platformio.platformio-ide", + "trunk.io" ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] } diff --git a/platformio.ini b/platformio.ini index a1beb8e7c..55329e578 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +;default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,7 +29,7 @@ default_envs = tbeam ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -;default_envs = rak2560 +default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 ;default_envs = radiomaster_900_bandit_nano diff --git a/src/power.h b/src/power.h index 94bf21cc2..c343a45eb 100644 --- a/src/power.h +++ b/src/power.h @@ -2,6 +2,8 @@ #include "PowerStatus.h" #include "concurrency/OSThread.h" #include "configuration.h" +#include "../variants/rak2560/RAK9154Sensor.h" + #ifdef ARCH_ESP32 #include #include @@ -47,38 +49,38 @@ extern INA3221Sensor ina3221Sensor; #endif #if HAS_RAKPROT && !defined(ARCH_PORTDUINO) -#include "modules/Telemetry/Sensor/RAK9154Sensor.h" +#include "../variants/rak2560/RAK9154Sensor.h" extern RAK9154Sensor rak9154Sensor; #endif class Power : private concurrency::OSThread { - public: - Observable newStatus; +public: + Observable newStatus; - Power(); + Power(); - void shutdown(); - void readPowerStatus(); - virtual bool setup(); - virtual int32_t runOnce() override; - void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; } - const uint16_t OCV[11] = {OCV_ARRAY}; + void shutdown(); + void readPowerStatus(); + virtual bool setup(); + virtual int32_t runOnce() override; + void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; } + const uint16_t OCV[11] = {OCV_ARRAY}; - protected: - meshtastic::PowerStatus *statusHandler; +protected: + meshtastic::PowerStatus *statusHandler; - /// Setup a xpowers chip axp192/axp2101, return true if found - bool axpChipInit(); - /// Setup a simple ADC input based battery sensor - bool analogInit(); + /// Setup a xpowers chip axp192/axp2101, return true if found + bool axpChipInit(); + /// Setup a simple ADC input based battery sensor + bool analogInit(); - private: - // open circuit voltage lookup table - uint8_t low_voltage_counter; +private: + // open circuit voltage lookup table + uint8_t low_voltage_counter; #ifdef DEBUG_HEAP - uint32_t lastheap; + uint32_t lastheap; #endif }; diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp b/variants/rak2560/RAK9154Sensor.cpp similarity index 96% rename from src/modules/Telemetry/Sensor/RAK9154Sensor.cpp rename to variants/rak2560/RAK9154Sensor.cpp index 4a317045b..15a172c18 100644 --- a/src/modules/Telemetry/Sensor/RAK9154Sensor.cpp +++ b/variants/rak2560/RAK9154Sensor.cpp @@ -1,7 +1,7 @@ -#ifdef HAS_RAKPROT -#include "RAK9154Sensor.h" +#ifdef HAS_RAKPROT +#include "../variants/rak2560/RAK9154Sensor.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" +#include "../modules/Telemetry/Sensor/TelemetrySensor.h" #include "configuration.h" #include diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.h b/variants/rak2560/RAK9154Sensor.h similarity index 71% rename from src/modules/Telemetry/Sensor/RAK9154Sensor.h rename to variants/rak2560/RAK9154Sensor.h index 5f5a92741..6c6f304d6 100644 --- a/src/modules/Telemetry/Sensor/RAK9154Sensor.h +++ b/variants/rak2560/RAK9154Sensor.h @@ -1,7 +1,9 @@ #ifdef HAS_RAKPROT +#ifndef _RAK9154SENSOR_H +#define _RAK9154SENSOR_H 1 #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" -#include "VoltageSensor.h" +#include "../modules/Telemetry/Sensor/TelemetrySensor.h" +#include "../modules/Telemetry/Sensor/VoltageSensor.h" class RAK9154Sensor : public TelemetrySensor, VoltageSensor { @@ -17,4 +19,5 @@ class RAK9154Sensor : public TelemetrySensor, VoltageSensor int getBusBatteryPercent(); bool isCharging(); }; +#endif // _RAK9154SENSOR_H #endif // HAS_RAKPROT \ No newline at end of file diff --git a/variants/rak2560/platformio.ini b/variants/rak2560/platformio.ini index b33f7dcef..1734bc75c 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/rak2560/platformio.ini @@ -10,7 +10,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -DEINK_WIDTH=250 -DEINK_HEIGHT=122 -DHAS_RAKPROT=1 ; Define if RAk OneWireSerial is used (disables GPS) -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> +<../variants/rak2560> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} From 471ee78a5e86fa8b5b61705d61606ce9216f7f06 Mon Sep 17 00:00:00 2001 From: thebentern <9000580+thebentern@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:25:52 +0000 Subject: [PATCH 0593/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index dc066c89f..0c90a6814 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit dc066c89f73fce882e5a47648cba18a1967a7f56 +Subproject commit 0c90a6814fdd959a35bb6cf8e958e74d48e8a601 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 0e9e6a28d..f5fc8661a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -67,6 +67,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_WIPHONE = 20, /* WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk */ meshtastic_HardwareModel_WIO_WM1110 = 21, + /* RAK2560 Solar base station based on RAK4630 */ + meshtastic_HardwareModel_RAK2560 = 22, /* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */ meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ From f50073ed9f351c4b59f8756125efda40452e9562 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Sun, 16 Jun 2024 21:06:38 +0800 Subject: [PATCH 0594/1377] Separate RAK4631 and RAK2560 variants --- platformio.ini | 4 +- variants/rak2560/platformio.ini | 7 +- variants/rak2560/variant.cpp | 45 +++++ variants/rak2560/variant.h | 280 ++++++++++++++++++++++++++++++++ variants/rak4631/variant.h | 135 +++++++-------- 5 files changed, 385 insertions(+), 86 deletions(-) create mode 100644 variants/rak2560/variant.cpp create mode 100644 variants/rak2560/variant.h diff --git a/platformio.ini b/platformio.ini index 55329e578..a1beb8e7c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -;default_envs = tbeam +default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,7 +29,7 @@ ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -default_envs = rak2560 +;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 ;default_envs = radiomaster_900_bandit_nano diff --git a/variants/rak2560/platformio.ini b/variants/rak2560/platformio.ini index 1734bc75c..71b235aac 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/rak2560/platformio.ini @@ -3,14 +3,11 @@ extends = nrf52840_base board = wiscore_rak4631 board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} -Ivariants/rak2560 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. - -DEINK_DISPLAY_MODEL=GxEPD2_213_BN - -DEINK_WIDTH=250 - -DEINK_HEIGHT=122 -DHAS_RAKPROT=1 ; Define if RAk OneWireSerial is used (disables GPS) -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> +<../variants/rak2560> + + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak2560> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} diff --git a/variants/rak2560/variant.cpp b/variants/rak2560/variant.cpp new file mode 100644 index 000000000..e84b60b3b --- /dev/null +++ b/variants/rak2560/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/rak2560/variant.h b/variants/rak2560/variant.h new file mode 100644 index 000000000..7187c8a50 --- /dev/null +++ b/variants/rak2560/variant.h @@ -0,0 +1,280 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ + +#define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion +#define BUTTON_NEED_PULLUP +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Serial 2 +#define PIN_SERIAL2_RX (19) +#define PIN_SERIAL2_TX (20) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ + +#define PIN_EINK_CS (0 + 26) +#define PIN_EINK_BUSY (0 + 4) +#define PIN_EINK_DC (0 + 17) +#define PIN_EINK_RES (-1) +#define PIN_EINK_SCLK (0 + 3) +#define PIN_EINK_MOSI (0 + 30) // also called SDI + +// #define USE_EINK + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ + +// RAK4630 LoRa module + +/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) + +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) + +Important for successful SX1262 initialization: + +* Setup DIO2 to control the antenna switch +* Setup DIO3 to control the TCXO power supply +* Setup the SX1262 to use it's DCDC regulator and not the LDO +* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the +control of the antenna switch + +SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG + +*/ + +#define DETECTION_SENSOR_EN 4 + +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +// #define SX126X_TXEN (39) +// #define SX126X_RXEN (37) +#define SX126X_POWER_EN (37) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +// #define PIN_GPS_RESET (34) +// #define PIN_GPS_EN PIN_3V3_EN +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +// On RAK2560 the GPS is be on a different UART +// #define GPS_RX_PIN PIN_SERIAL2_RX +// #define GPS_TX_PIN PIN_SERIAL2_TX +// #define PIN_GPS_EN PIN_3V3_EN +// Disable GPS +#define MESHTASTIC_EXCLUDE_GPS 1 +// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press + +// RAK12002 RTC Module +#define RV3028_RTC (uint8_t)0b1010010 + +// RAK18001 Buzzer in Slot C +// #define PIN_BUZZER 21 // IO3 is PWM2 +// NEW: set this via protobuf instead! + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define HAS_ETHERNET 1 + +#define RAK_4631 1 + +#define HALF_UART_PIN PIN_SERIAL1_RX + +#if defined(GPS_RX_PIN) && (GPS_RX_PIN == HALF_UART_PIN) +#error pin 15 collision + +#endif + +#if defined(GPS_TX_PIN) && (GPS_RX_PIN == HALF_UART_PIN) +#error pin 15 collision +#endif + +#define PIN_ETHERNET_RESET 21 +#define PIN_ETHERNET_SS PIN_EINK_CS +#define ETH_SPI_PORT SPI1 +#define AQ_SET_PIN 10 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index e9f1a1865..2ce1b960a 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -34,7 +34,8 @@ #include "WVariant.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif // __cplusplus // Number of pins defined in PinDescription array @@ -55,9 +56,9 @@ extern "C" { #define LED_STATE_ON 1 // State when LED is litted -/* - * Buttons - */ + /* + * Buttons + */ #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP @@ -77,14 +78,14 @@ extern "C" { #define PIN_A6 (0xff) #define PIN_A7 (0xff) -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; + static const uint8_t A0 = PIN_A0; + static const uint8_t A1 = PIN_A1; + static const uint8_t A2 = PIN_A2; + static const uint8_t A3 = PIN_A3; + static const uint8_t A4 = PIN_A4; + static const uint8_t A5 = PIN_A5; + static const uint8_t A6 = PIN_A6; + static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 // Other pins @@ -92,7 +93,7 @@ static const uint8_t A7 = PIN_A7; #define PIN_NFC1 (9) #define PIN_NFC2 (10) -static const uint8_t AREF = PIN_AREF; + static const uint8_t AREF = PIN_AREF; /* * Serial interfaces @@ -100,9 +101,9 @@ static const uint8_t AREF = PIN_AREF; #define PIN_SERIAL1_RX (15) #define PIN_SERIAL1_TX (16) -// Connected to Serial 2 -#define PIN_SERIAL2_RX (19) -#define PIN_SERIAL2_TX (20) +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) /* * SPI Interfaces @@ -117,14 +118,14 @@ static const uint8_t AREF = PIN_AREF; #define PIN_SPI1_MOSI (30) // (0 + 30) #define PIN_SPI1_SCK (3) // (0 + 3) -static const uint8_t SS = 42; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; + static const uint8_t SS = 42; + static const uint8_t MOSI = PIN_SPI_MOSI; + static const uint8_t MISO = PIN_SPI_MISO; + static const uint8_t SCK = PIN_SPI_SCK; -/* - * eink display pins - */ + /* + * eink display pins + */ #define PIN_EINK_CS (0 + 26) #define PIN_EINK_BUSY (0 + 4) @@ -158,44 +159,44 @@ static const uint8_t SCK = PIN_SPI_SCK; #define EXTERNAL_FLASH_DEVICES IS25LP080D #define EXTERNAL_FLASH_USE_QSPI -/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports - RAK5005-O <-> nRF52840 - IO1 <-> P0.17 (Arduino GPIO number 17) - IO2 <-> P1.02 (Arduino GPIO number 34) - IO3 <-> P0.21 (Arduino GPIO number 21) - IO4 <-> P0.04 (Arduino GPIO number 4) - IO5 <-> P0.09 (Arduino GPIO number 9) - IO6 <-> P0.10 (Arduino GPIO number 10) - IO7 <-> P0.28 (Arduino GPIO number 28) - SW1 <-> P0.01 (Arduino GPIO number 1) - A0 <-> P0.04/AIN2 (Arduino Analog A2 - A1 <-> P0.31/AIN7 (Arduino Analog A7 - SPI_CS <-> P0.26 (Arduino GPIO number 26) - */ + /* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ -// RAK4630 LoRa module + // RAK4630 LoRa module -/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) + /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) + P1.10 NSS SPI NSS (Arduino GPIO number 42) + P1.11 SCK SPI CLK (Arduino GPIO number 43) + P1.12 MOSI SPI MOSI (Arduino GPIO number 44) + P1.13 MISO SPI MISO (Arduino GPIO number 45) + P1.14 BUSY BUSY signal (Arduino GPIO number 46) + P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) + P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) -Important for successful SX1262 initialization: + Important for successful SX1262 initialization: -* Setup DIO2 to control the antenna switch -* Setup DIO3 to control the TCXO power supply -* Setup the SX1262 to use it's DCDC regulator and not the LDO -* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the -control of the antenna switch + * Setup DIO2 to control the antenna switch + * Setup DIO3 to control the TCXO power supply + * Setup the SX1262 to use it's DCDC regulator and not the LDO + * RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the + control of the antenna switch -SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG + SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG -*/ + */ #define DETECTION_SENSOR_EN 4 @@ -228,18 +229,9 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // #define PIN_GPS_EN PIN_3V3_EN #define PIN_GPS_PPS (17) // Pulse per second input from the GPS -// On RAK2560 the GPS is be on a different UART -#ifdef HAS_RAKPROT -// #define GPS_RX_PIN PIN_SERIAL2_RX -// #define GPS_TX_PIN PIN_SERIAL2_TX -// #define PIN_GPS_EN PIN_3V3_EN -// Disable GPS -#define MESHTASTIC_EXCLUDE_GPS 1 -#else - // Enable GPS #define GPS_RX_PIN PIN_SERIAL1_RX #define GPS_TX_PIN PIN_SERIAL1_TX -#endif + // Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press // RAK12002 RTC Module @@ -266,21 +258,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RAK_4631 1 -#ifdef HAS_RAKPROT - -#define HALF_UART_PIN PIN_SERIAL1_RX - -#if defined(GPS_RX_PIN) && (GPS_RX_PIN == HALF_UART_PIN) -#error pin 15 collision - -#endif - -#if defined(GPS_TX_PIN) && (GPS_RX_PIN == HALF_UART_PIN) -#error pin 15 collision -#endif - -#endif - #define PIN_ETHERNET_RESET 21 #define PIN_ETHERNET_SS PIN_EINK_CS #define ETH_SPI_PORT SPI1 From ceb884cf1827ac13205b1bc605c42cced4d5a95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 16:29:45 +0200 Subject: [PATCH 0595/1377] trunk fmt --- src/Power.cpp | 36 ++--- src/power.h | 40 +++--- variants/rak2560/RAK9154Sensor.cpp | 214 ++++++++++++++--------------- variants/rak2560/create_uf2.py | 35 +++-- variants/rak4631/variant.h | 103 +++++++------- 5 files changed, 214 insertions(+), 214 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 18dbfebe4..18a527cee 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -351,19 +351,19 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual bool isVbusIn() override { #ifdef EXT_PWR_DETECT - #ifdef HELTEC_CAPSULE_SENSOR_V3 - // if external powered that pin will be pulled down - if (digitalRead(EXT_PWR_DETECT) == LOW) { - return true; - } - // if it's not LOW - check the battery - #else - // if external powered that pin will be pulled up - if (digitalRead(EXT_PWR_DETECT) == HIGH) { - return true; - } - // if it's not HIGH - check the battery - #endif +#ifdef HELTEC_CAPSULE_SENSOR_V3 + // if external powered that pin will be pulled down + if (digitalRead(EXT_PWR_DETECT) == LOW) { + return true; + } + // if it's not LOW - check the battery +#else + // if external powered that pin will be pulled up + if (digitalRead(EXT_PWR_DETECT) == HIGH) { + return true; + } + // if it's not HIGH - check the battery +#endif #endif return getBattVoltage() > chargingVolt; } @@ -461,11 +461,11 @@ Power::Power() : OSThread("Power") bool Power::analogInit() { #ifdef EXT_PWR_DETECT - #ifdef HELTEC_CAPSULE_SENSOR_V3 - pinMode(EXT_PWR_DETECT, INPUT_PULLUP); - #else - pinMode(EXT_PWR_DETECT, INPUT); - #endif +#ifdef HELTEC_CAPSULE_SENSOR_V3 + pinMode(EXT_PWR_DETECT, INPUT_PULLUP); +#else + pinMode(EXT_PWR_DETECT, INPUT); +#endif #endif #ifdef EXT_CHRG_DETECT pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode); diff --git a/src/power.h b/src/power.h index c343a45eb..b970dfeaf 100644 --- a/src/power.h +++ b/src/power.h @@ -1,8 +1,8 @@ #pragma once +#include "../variants/rak2560/RAK9154Sensor.h" #include "PowerStatus.h" #include "concurrency/OSThread.h" #include "configuration.h" -#include "../variants/rak2560/RAK9154Sensor.h" #ifdef ARCH_ESP32 #include @@ -56,31 +56,31 @@ extern RAK9154Sensor rak9154Sensor; class Power : private concurrency::OSThread { -public: - Observable newStatus; + public: + Observable newStatus; - Power(); + Power(); - void shutdown(); - void readPowerStatus(); - virtual bool setup(); - virtual int32_t runOnce() override; - void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; } - const uint16_t OCV[11] = {OCV_ARRAY}; + void shutdown(); + void readPowerStatus(); + virtual bool setup(); + virtual int32_t runOnce() override; + void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; } + const uint16_t OCV[11] = {OCV_ARRAY}; -protected: - meshtastic::PowerStatus *statusHandler; + protected: + meshtastic::PowerStatus *statusHandler; - /// Setup a xpowers chip axp192/axp2101, return true if found - bool axpChipInit(); - /// Setup a simple ADC input based battery sensor - bool analogInit(); + /// Setup a xpowers chip axp192/axp2101, return true if found + bool axpChipInit(); + /// Setup a simple ADC input based battery sensor + bool analogInit(); -private: - // open circuit voltage lookup table - uint8_t low_voltage_counter; + private: + // open circuit voltage lookup table + uint8_t low_voltage_counter; #ifdef DEBUG_HEAP - uint32_t lastheap; + uint32_t lastheap; #endif }; diff --git a/variants/rak2560/RAK9154Sensor.cpp b/variants/rak2560/RAK9154Sensor.cpp index 15a172c18..9f660947e 100644 --- a/variants/rak2560/RAK9154Sensor.cpp +++ b/variants/rak2560/RAK9154Sensor.cpp @@ -4,8 +4,8 @@ #include "../modules/Telemetry/Sensor/TelemetrySensor.h" #include "configuration.h" -#include #include "concurrency/Periodic.h" +#include using namespace concurrency; @@ -27,165 +27,157 @@ static uint8_t provision = 0; static void onewire_evt(const uint8_t pid, const uint8_t sid, const SNHUBAPI_EVT_E eid, uint8_t *msg, uint16_t len) { - switch (eid) - { - case SNHUBAPI_EVT_RECV_REQ: - case SNHUBAPI_EVT_RECV_RSP: - break; + switch (eid) { + case SNHUBAPI_EVT_RECV_REQ: + case SNHUBAPI_EVT_RECV_RSP: + break; - case SNHUBAPI_EVT_QSEND: - mySerial.write(msg, len); - break; + case SNHUBAPI_EVT_QSEND: + mySerial.write(msg, len); + break; - case SNHUBAPI_EVT_ADD_SID: - // LOG_INFO("+ADD:SID:[%02x]\r\n", msg[0]); - break; + case SNHUBAPI_EVT_ADD_SID: + // LOG_INFO("+ADD:SID:[%02x]\r\n", msg[0]); + break; - case SNHUBAPI_EVT_ADD_PID: - // LOG_INFO("+ADD:PID:[%02x]\r\n", msg[0]); + case SNHUBAPI_EVT_ADD_PID: + // LOG_INFO("+ADD:PID:[%02x]\r\n", msg[0]); #ifdef BOOT_DATA_REQ - provision = msg[0]; + provision = msg[0]; #endif - break; + break; - case SNHUBAPI_EVT_GET_INTV: - break; + case SNHUBAPI_EVT_GET_INTV: + break; - case SNHUBAPI_EVT_GET_ENABLE: - break; + case SNHUBAPI_EVT_GET_ENABLE: + break; - case SNHUBAPI_EVT_SDATA_REQ: + case SNHUBAPI_EVT_SDATA_REQ: - // LOG_INFO("+EVT:PID[%02x],IPSO[%02x]\r\n",pid,msg[0]); - // for( uint16_t i=1; i 100) - { - dc_prec = 100; - } - break; - case RAK_IPSO_DC_CURRENT: - dc_cur = (msg[2] << 8) + msg[1]; - break; - case RAK_IPSO_DC_VOLTAGE: - dc_vol = (msg[2] << 8) + msg[1]; - dc_vol *= 10; - break; - default: - break; - } + // LOG_INFO("+EVT:PID[%02x],IPSO[%02x]\r\n",pid,msg[0]); + // for( uint16_t i=1; i 100) { + dc_prec = 100; + } + break; + case RAK_IPSO_DC_CURRENT: + dc_cur = (msg[2] << 8) + msg[1]; + break; + case RAK_IPSO_DC_VOLTAGE: + dc_vol = (msg[2] << 8) + msg[1]; + dc_vol *= 10; + break; + default: + break; + } - break; - case SNHUBAPI_EVT_REPORT: + break; + case SNHUBAPI_EVT_REPORT: - // LOG_INFO("+EVT:PID[%02x],IPSO[%02x]\r\n",pid,msg[0]); - // for( uint16_t i=1; i 100) - { - dc_prec = 100; - } - break; - case RAK_IPSO_DC_CURRENT: - dc_cur = (msg[1] << 8) + msg[2]; - break; - case RAK_IPSO_DC_VOLTAGE: - dc_vol = (msg[1] << 8) + msg[2]; - dc_vol *= 10; - break; - default: - break; - } + switch (msg[0]) { + case RAK_IPSO_CAPACITY: + dc_prec = msg[1]; + if (dc_prec > 100) { + dc_prec = 100; + } + break; + case RAK_IPSO_DC_CURRENT: + dc_cur = (msg[1] << 8) + msg[2]; + break; + case RAK_IPSO_DC_VOLTAGE: + dc_vol = (msg[1] << 8) + msg[2]; + dc_vol *= 10; + break; + default: + break; + } - break; + break; - case SNHUBAPI_EVT_CHKSUM_ERR: - LOG_INFO("+ERR:CHKSUM\r\n"); - break; + case SNHUBAPI_EVT_CHKSUM_ERR: + LOG_INFO("+ERR:CHKSUM\r\n"); + break; - case SNHUBAPI_EVT_SEQ_ERR: - LOG_INFO("+ERR:SEQUCE\r\n"); - break; + case SNHUBAPI_EVT_SEQ_ERR: + LOG_INFO("+ERR:SEQUCE\r\n"); + break; - default: - break; - } + default: + break; + } } static int32_t onewireHandle() { - if (provision != 0) - { - RakSNHub_Protocl_API.get.data(provision); - provision = 0; - } + if (provision != 0) { + RakSNHub_Protocl_API.get.data(provision); + provision = 0; + } - while (mySerial.available()) - { - char a = mySerial.read(); - buff[bufflen++] = a; - delay(2); // continue data, timeout=2ms - } + while (mySerial.available()) { + char a = mySerial.read(); + buff[bufflen++] = a; + delay(2); // continue data, timeout=2ms + } - if (bufflen != 0) - { - RakSNHub_Protocl_API.process((uint8_t *)buff, bufflen); - bufflen = 0; - } + if (bufflen != 0) { + RakSNHub_Protocl_API.process((uint8_t *)buff, bufflen); + bufflen = 0; + } - return 50; + return 50; } int32_t RAK9154Sensor::runOnce() { - onewirePeriodic = new Periodic("onewireHandle", onewireHandle); + onewirePeriodic = new Periodic("onewireHandle", onewireHandle); - mySerial.begin(9600); + mySerial.begin(9600); - RakSNHub_Protocl_API.init(onewire_evt); + RakSNHub_Protocl_API.init(onewire_evt); - status = true; - initialized = true; - return 0; + status = true; + initialized = true; + return 0; } void RAK9154Sensor::setup() { - // Set up oversampling and filter initialization + // Set up oversampling and filter initialization } bool RAK9154Sensor::getMetrics(meshtastic_Telemetry *measurement) { - return true; + return true; } uint16_t RAK9154Sensor::getBusVoltageMv() { - return dc_vol; + return dc_vol; } int RAK9154Sensor::getBusBatteryPercent() { - return (int)dc_prec; + return (int)dc_prec; } bool RAK9154Sensor::isCharging() { - return (dc_cur > 0) ? true : false; + return (dc_cur > 0) ? true : false; } #endif // HAS_RAKPROT \ No newline at end of file diff --git a/variants/rak2560/create_uf2.py b/variants/rak2560/create_uf2.py index d14eaea02..cf6b11606 100644 --- a/variants/rak2560/create_uf2.py +++ b/variants/rak2560/create_uf2.py @@ -1,22 +1,23 @@ -import sys import struct +import sys Import("env") + # Parse input and create UF2 file def create_uf2(source, target, env): # source_hex = target[0].get_abspath() source_hex = target[0].get_string(False) - source_hex = '.\\'+source_hex + source_hex = ".\\" + source_hex print("#########################################################") - print("Create UF2 from "+source_hex) + print("Create UF2 from " + source_hex) print("#########################################################") # print("Source: " + source_hex) target = source_hex.replace(".hex", "") target = target + ".uf2" # print("Target: " + target) - with open(source_hex, mode='rb') as f: + with open(source_hex, mode="rb") as f: inpbuf = f.read() outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) @@ -48,9 +49,17 @@ class Block: flags = 0x0 if familyid: flags |= 0x2000 - hd = struct.pack(" nRF52840 - IO1 <-> P0.17 (Arduino GPIO number 17) - IO2 <-> P1.02 (Arduino GPIO number 34) - IO3 <-> P0.21 (Arduino GPIO number 21) - IO4 <-> P0.04 (Arduino GPIO number 4) - IO5 <-> P0.09 (Arduino GPIO number 9) - IO6 <-> P0.10 (Arduino GPIO number 10) - IO7 <-> P0.28 (Arduino GPIO number 28) - SW1 <-> P0.01 (Arduino GPIO number 1) - A0 <-> P0.04/AIN2 (Arduino Analog A2 - A1 <-> P0.31/AIN7 (Arduino Analog A7 - SPI_CS <-> P0.26 (Arduino GPIO number 26) - */ +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ - // RAK4630 LoRa module +// RAK4630 LoRa module - /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) +/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) - P1.10 NSS SPI NSS (Arduino GPIO number 42) - P1.11 SCK SPI CLK (Arduino GPIO number 43) - P1.12 MOSI SPI MOSI (Arduino GPIO number 44) - P1.13 MISO SPI MISO (Arduino GPIO number 45) - P1.14 BUSY BUSY signal (Arduino GPIO number 46) - P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) - P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) - Important for successful SX1262 initialization: +Important for successful SX1262 initialization: - * Setup DIO2 to control the antenna switch - * Setup DIO3 to control the TCXO power supply - * Setup the SX1262 to use it's DCDC regulator and not the LDO - * RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the - control of the antenna switch +* Setup DIO2 to control the antenna switch +* Setup DIO3 to control the TCXO power supply +* Setup the SX1262 to use it's DCDC regulator and not the LDO +* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the +control of the antenna switch - SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG +SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG - */ +*/ #define DETECTION_SENSOR_EN 4 From 11c3ca541fbf8bc7fcbddd97ac2e408a2e16886e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 20:03:45 +0200 Subject: [PATCH 0596/1377] add proper RAK variant and change pathspec --- src/platform/nrf52/architecture.h | 2 ++ variants/rak2560/platformio.ini | 2 +- variants/rak2560/variant.h | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 18a4d75f5..b66552a28 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -42,6 +42,8 @@ #define HW_VENDOR meshtastic_HardwareModel_NRF52840DK #elif defined(ARDUINO_NRF52840_PPR) #define HW_VENDOR meshtastic_HardwareModel_PPR +#elif defined(RAK2560) +#define HW_VENDOR meshtastic_HardwareModel_RAK2560 #elif defined(RAK4630) #define HW_VENDOR meshtastic_HardwareModel_RAK4631 #elif defined(TTGO_T_ECHO) diff --git a/variants/rak2560/platformio.ini b/variants/rak2560/platformio.ini index 71b235aac..ff667aadf 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/rak2560/platformio.ini @@ -21,4 +21,4 @@ debug_tool = jlink ;upload_protocol = jlink extra_scripts = ${env.extra_scripts} - ../firmware/variants/rak2560/create_uf2.py \ No newline at end of file + ./variants/rak2560/create_uf2.py \ No newline at end of file diff --git a/variants/rak2560/variant.h b/variants/rak2560/variant.h index 7187c8a50..0c1c9aed9 100644 --- a/variants/rak2560/variant.h +++ b/variants/rak2560/variant.h @@ -16,10 +16,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _VARIANT_RAK4630_ -#define _VARIANT_RAK4630_ +#ifndef _VARIANT_RAK2560_ +#define _VARIANT_RAK2560_ #define RAK4630 +#define RAK2560 /** Master clock frequency */ #define VARIANT_MCK (64000000ul) From 4fe281cf7fb07aec5e7a0f0e20e13651a721c3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 20:11:58 +0200 Subject: [PATCH 0597/1377] tryfix linter error --- variants/rak2560/create_uf2.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/variants/rak2560/create_uf2.py b/variants/rak2560/create_uf2.py index cf6b11606..af78f3e09 100644 --- a/variants/rak2560/create_uf2.py +++ b/variants/rak2560/create_uf2.py @@ -1,7 +1,6 @@ import struct -import sys -Import("env") +Import("env") # noqa: F821 # Parse input and create UF2 file @@ -29,7 +28,7 @@ def create_uf2(source, target, env): # Add callback after .hex file was created -env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", create_uf2) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", create_uf2) # noqa: F821 # UF2 creation taken from uf2conv.py UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" @@ -97,7 +96,7 @@ def convert_from_hex_to_uf2(buf): break elif tp == 0: addr = upper | (rec[1] << 8) | rec[2] - if appstartaddr == None: + if appstartaddr is None: appstartaddr = addr i = 4 while i < len(rec) - 1: From aca0807acfbd286473fa66c603fd34b1bd0f7de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 20:41:23 +0200 Subject: [PATCH 0598/1377] more try more fix --- .trunk/configs/.bandit | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .trunk/configs/.bandit diff --git a/.trunk/configs/.bandit b/.trunk/configs/.bandit new file mode 100644 index 000000000..d286ded89 --- /dev/null +++ b/.trunk/configs/.bandit @@ -0,0 +1,2 @@ +[bandit] +skips = B101 \ No newline at end of file From 369d3797200ba27cbb93929d05bb2368e913d8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 21:08:34 +0200 Subject: [PATCH 0599/1377] CI is creating the uf2 file during build --- variants/rak2560/platformio.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/variants/rak2560/platformio.ini b/variants/rak2560/platformio.ini index ff667aadf..96c1bfa92 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/rak2560/platformio.ini @@ -19,6 +19,3 @@ lib_deps = debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink -extra_scripts = - ${env.extra_scripts} - ./variants/rak2560/create_uf2.py \ No newline at end of file From 7aea056ac0bdaf0e101202549796a749f6589273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 16 Jun 2024 22:43:16 +0200 Subject: [PATCH 0600/1377] exclude debs from release zip --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 89b71acb8..25a0fbad2 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -254,7 +254,7 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output + run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x *.deb - uses: actions/download-artifact@v4 with: From ea69b999f9b602c7baba6508d63c27985872e18d Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 16 Jun 2024 13:59:38 -0700 Subject: [PATCH 0601/1377] Add rak4631_dap variant for debugging with NanoDAP debug probe device. --- variants/rak4631/platformio.ini | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 4870d4b68..b9bf42655 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -18,5 +18,33 @@ lib_deps = rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 debug_tool = jlink + ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink \ No newline at end of file +; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds +;upload_protocol = jlink + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" +[env:rak4631_dap] +extends = env:rak4631 +; pyocd pack --i nrf52840 +; eventually use platformio/tool-pyocd@^2.3600.0 instad +upload_protocol = custom +upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE + +; Only reprogram the board if the code has changed +debug_load_mode = modified +;debug_load_mode = manual +debug_tool = custom +; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) +debug_server = + pyocd + gdbserver + -t + nrf52840 + --elf + ${platformio.build_dir}/${this.__env__}/firmware.elf +; The following is not needed because it automatically tries do this +;debug_server_ready_pattern = -.*GDB server started on port \d+.* +;debug_port = localhost:3333 \ No newline at end of file From 163a732ddc5f904685091fd571328ee54a6f74b0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 16 Jun 2024 17:17:19 -0700 Subject: [PATCH 0602/1377] Turn off vscode cmake prompt - we don't use cmake on meshtastic (#4122) --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e86d31c7d..07e198f0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "editor.defaultFormatter": "trunk.io", "trunk.enableWindows": true, "files.insertFinalNewline": false, - "files.trimFinalNewlines": false + "files.trimFinalNewlines": false, + "cmake.configureOnOpen": false } From 7afa8107ae5f8f5c8d9a9fba5173d1450c54b8b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:22:51 -0500 Subject: [PATCH 0603/1377] [create-pull-request] automated change (#4121) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index a26da1996..268987418 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 13 +build = 14 From 15250a566a3bce90a3449481f5a1d0ff513441de Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 16 Jun 2024 17:17:19 -0700 Subject: [PATCH 0604/1377] Turn off vscode cmake prompt - we don't use cmake on meshtastic (#4122) --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e86d31c7d..07e198f0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "editor.defaultFormatter": "trunk.io", "trunk.enableWindows": true, "files.insertFinalNewline": false, - "files.trimFinalNewlines": false + "files.trimFinalNewlines": false, + "cmake.configureOnOpen": false } From c593e7ce56a804b4526f4778e8c7c400c5511235 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 16 Jun 2024 13:59:38 -0700 Subject: [PATCH 0605/1377] Add rak4631_dap variant for debugging with NanoDAP debug probe device. use board_level = extra --- variants/rak4631/platformio.ini | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 4870d4b68..58a8eb5e4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -18,5 +18,34 @@ lib_deps = rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 debug_tool = jlink + ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink \ No newline at end of file +; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds +;upload_protocol = jlink + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" +[env:rak4631_dap] +extends = env:rak4631 +board_level = extra +; pyocd pack --i nrf52840 +; eventually use platformio/tool-pyocd@^2.3600.0 instad +upload_protocol = custom +upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE + +; Only reprogram the board if the code has changed +debug_load_mode = modified +;debug_load_mode = manual +debug_tool = custom +; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) +debug_server = + pyocd + gdbserver + -t + nrf52840 + --elf + ${platformio.build_dir}/${this.__env__}/firmware.elf +; The following is not needed because it automatically tries do this +;debug_server_ready_pattern = -.*GDB server started on port \d+.* +;debug_port = localhost:3333 \ No newline at end of file From 12b8dc1918592baaaf72111d57cb98558eb24f3f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 17 Jun 2024 06:09:50 -0500 Subject: [PATCH 0606/1377] Revert "[create-pull-request] automated change (#4121)" (#4124) This reverts commit 7afa8107ae5f8f5c8d9a9fba5173d1450c54b8b1. --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 268987418..a26da1996 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 14 +build = 13 From 83f5ba0161725a597591c530ff509f2a5da27461 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 17 Jun 2024 23:14:20 +1200 Subject: [PATCH 0607/1377] Update OLED ref (#4125) Upstream changes to the library temporarily reverted to restore debug info frame --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a1beb8e7c..83c4924c4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,7 +80,7 @@ monitor_speed = 115200 lib_deps = jgromes/RadioLib@~6.6.0 - https://github.com/meshtastic/esp8266-oled-ssd1306.git#69ba98fa30e67b12d4577b121f210f3eb7049d6b ; ESP8266_SSD1306 + https://github.com/meshtastic/esp8266-oled-ssd1306.git#dcacac5d2c7942376bc17f7079cced6a73cb659f ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 From 5d2f7d1962de15b811c34f34909e50f571b030da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 07:17:36 -0500 Subject: [PATCH 0608/1377] [create-pull-request] automated change (#4127) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index a26da1996..268987418 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 13 +build = 14 From b6066a78c1983ffd3be6034115261916b2232dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 17 Jun 2024 15:09:38 +0200 Subject: [PATCH 0609/1377] WIP --- src/detect/ScanI2CTwoWire.cpp | 11 ++++----- src/input/cardKbI2cImpl.cpp | 42 +++++++++++++++++++++++++++++++++-- src/input/kbI2cBase.cpp | 38 ------------------------------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index b04590509..86408b8d2 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -148,7 +148,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) { concurrency::LockGuard guard((concurrency::Lock *)&lock); - LOG_DEBUG("Scanning for i2c devices on port %d\n", port); + LOG_DEBUG("Scanning for I2C devices on port %d\n", port); uint8_t err; @@ -172,10 +172,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) #endif for (addr.address = 1; addr.address < 127; addr.address++) { - if (asize != 0) - if (in_array(address, asize, addr.address)) + if (asize != 0) { + if (!in_array(address, asize, addr.address)) continue; - + LOG_DEBUG("Scanning address 0x%x\n", addr.address); + } i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO if (i2cBus->read() != -1) @@ -369,7 +370,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); } } else if (err == 4) { - LOG_ERROR("Unknown error at address 0x%x\n", addr); + LOG_ERROR("Unknown error at address 0x%x\n", addr.address); } // Check if a type was found for the enumerated device - save, if so diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index e000f36eb..d10f1118f 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -1,5 +1,7 @@ #include "cardKbI2cImpl.h" #include "InputBroker.h" +#include "detect/ScanI2C.h" +#include "detect/ScanI2CTwoWire.h" CardKbI2cImpl *cardKbI2cImpl; @@ -8,8 +10,44 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {} void CardKbI2cImpl::init() { if (cardkb_found.address == 0x00) { - disable(); - return; + LOG_DEBUG("Rescanning for I2C keyboard\n"); + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; + uint8_t i2caddr_asize = 3; + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); + +#if defined(I2C_SDA1) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); + auto kb_info = i2cScanner->firstKeyboard(); + + if (kb_info.type != ScanI2C::DeviceType::NONE) { + cardkb_found = kb_info.address; + switch (kb_info.type) { + case ScanI2C::DeviceType::RAK14004: + kb_model = 0x02; + break; + case ScanI2C::DeviceType::CARDKB: + kb_model = 0x00; + break; + case ScanI2C::DeviceType::TDECKKB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x10; + break; + case ScanI2C::DeviceType::BBQ10KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x11; + break; + default: + // use this as default since it's also just zero + LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); + kb_model = 0x00; + } + } + if (cardkb_found.address == 0x00) { + disable(); + return; + } } inputBroker->registerSource(this); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index ce22edb93..024b16b9e 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -30,44 +30,6 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { - if (cardkb_found.address == 0x00) { - // Input device is not detected. Rescan now. - auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); - uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; - uint8_t i2caddr_asize = 3; -#if defined(I2C_SDA1) - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); -#endif - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); - auto kb_info = i2cScanner->firstKeyboard(); - - if (kb_info.type != ScanI2C::DeviceType::NONE) { - cardkb_found = kb_info.address; - switch (kb_info.type) { - case ScanI2C::DeviceType::RAK14004: - kb_model = 0x02; - break; - case ScanI2C::DeviceType::CARDKB: - kb_model = 0x00; - break; - case ScanI2C::DeviceType::TDECKKB: - // assign an arbitrary value to distinguish from other models - kb_model = 0x10; - break; - case ScanI2C::DeviceType::BBQ10KB: - // assign an arbitrary value to distinguish from other models - kb_model = 0x11; - break; - default: - // use this as default since it's also just zero - LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); - kb_model = 0x00; - } - } - if (cardkb_found.address == 0x00) - return INT32_MAX; - } - if (!i2cBus) { switch (cardkb_found.port) { case ScanI2C::WIRE1: From 7a25e0b69ae571555605b9b9fd1a620a083889e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 17 Jun 2024 17:03:32 +0200 Subject: [PATCH 0610/1377] don't close the wire when we didn't find anything. We might rescan later. --- src/main.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6797c8375..3b009a179 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -421,10 +421,6 @@ void setup() auto i2cCount = i2cScanner->countDevices(); if (i2cCount == 0) { LOG_INFO("No I2C devices found\n"); - Wire.end(); -#ifdef I2C_SDA1 - Wire1.end(); -#endif } else { LOG_INFO("%i I2C devices found\n", i2cCount); } From 9d8a5221a97f13b64c5f3fe585eb8284c3224fef Mon Sep 17 00:00:00 2001 From: mverch67 Date: Mon, 17 Jun 2024 20:17:56 +0200 Subject: [PATCH 0611/1377] fix for MESHTASTIC_EXCLUDE_INPUTBROKER --- src/input/InputBroker.cpp | 2 +- src/input/cardKbI2cImpl.cpp | 1 + src/input/cardKbI2cImpl.h | 1 - src/modules/Modules.cpp | 1 + src/modules/Telemetry/Sensor/OPT3001Sensor.cpp | 13 +++++++++---- src/modules/Telemetry/Sensor/OPT3001Sensor.h | 9 ++++++++- 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index b06c7400f..cb73e32ba 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -1,7 +1,7 @@ #include "InputBroker.h" #include "PowerFSM.h" // needed for event trigger -InputBroker *inputBroker; +InputBroker *inputBroker = nullptr; InputBroker::InputBroker(){}; diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index e000f36eb..3cc70fa15 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -1,5 +1,6 @@ #include "cardKbI2cImpl.h" #include "InputBroker.h" +#include "main.h" CardKbI2cImpl *cardKbI2cImpl; diff --git a/src/input/cardKbI2cImpl.h b/src/input/cardKbI2cImpl.h index 1e6e87dfd..811a0558c 100644 --- a/src/input/cardKbI2cImpl.h +++ b/src/input/cardKbI2cImpl.h @@ -1,6 +1,5 @@ #pragma once #include "kbI2cBase.h" -#include "main.h" /** * @brief The idea behind this class to have static methods for the event handlers. diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 1b4bbc3b4..ba1f5c11e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -42,6 +42,7 @@ #include "modules/Telemetry/DeviceTelemetry.h" #endif #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#include "main.h" #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #endif diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp index 0d76e2897..d0e38bf88 100644 --- a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp @@ -1,7 +1,10 @@ -#include "OPT3001Sensor.h" -#include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" #include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "OPT3001Sensor.h" +#include "TelemetrySensor.h" #include OPT3001Sensor::OPT3001Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_OPT3001, "OPT3001") {} @@ -41,4 +44,6 @@ bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement) LOG_INFO("Lux: %f\n", measurement->variant.environment_metrics.lux); return true; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.h b/src/modules/Telemetry/Sensor/OPT3001Sensor.h index 4a8deef21..2ac149319 100644 --- a/src/modules/Telemetry/Sensor/OPT3001Sensor.h +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.h @@ -1,3 +1,8 @@ +#pragma once +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include @@ -14,4 +19,6 @@ class OPT3001Sensor : public TelemetrySensor OPT3001Sensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file From cd60ee80bdd0f6ab73f3ce9b26ce251819c8b9a4 Mon Sep 17 00:00:00 2001 From: mverch67 Date: Mon, 17 Jun 2024 21:17:25 +0200 Subject: [PATCH 0612/1377] fix wrong include file exclusion --- src/main.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6797c8375..57009ae46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,13 +41,14 @@ #endif #if !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" -NimbleBluetooth *nimbleBluetooth; +NimbleBluetooth *nimbleBluetooth = nullptr; #endif #endif #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" -NRF52Bluetooth *nrf52Bluetooth; +NRF52Bluetooth *nrf52Bluetooth = nullptr; +; #endif #if HAS_WIFI @@ -94,23 +95,26 @@ NRF52Bluetooth *nrf52Bluetooth; #include "ButtonThread.h" #endif +#include "AmbientLightingThread.h" #include "PowerFSMThread.h" #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "AccelerometerThread.h" -#include "AmbientLightingThread.h" -AccelerometerThread *accelerometerThread; +AccelerometerThread *accelerometerThread = nullptr; +; #endif #ifdef HAS_I2S #include "AudioThread.h" -AudioThread *audioThread; +AudioThread *audioThread = nullptr; +; #endif using namespace concurrency; // We always create a screen object, but we only init it if we find the hardware -graphics::Screen *screen; +graphics::Screen *screen = nullptr; +; // Global power status meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus(); From 5e92136ed043cd7b647523ff593a982ce52f033c Mon Sep 17 00:00:00 2001 From: mverch67 Date: Mon, 17 Jun 2024 21:19:36 +0200 Subject: [PATCH 0613/1377] semi colon --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 57009ae46..5ee895c9c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -101,7 +101,6 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "AccelerometerThread.h" AccelerometerThread *accelerometerThread = nullptr; -; #endif #ifdef HAS_I2S From e822525ce5d2252b8a9fa2eb562044be4fb1a948 Mon Sep 17 00:00:00 2001 From: mverch67 Date: Mon, 17 Jun 2024 21:23:07 +0200 Subject: [PATCH 0614/1377] more semi colons >_< --- src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5ee895c9c..a81f9206c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -106,14 +106,12 @@ AccelerometerThread *accelerometerThread = nullptr; #ifdef HAS_I2S #include "AudioThread.h" AudioThread *audioThread = nullptr; -; #endif using namespace concurrency; // We always create a screen object, but we only init it if we find the hardware graphics::Screen *screen = nullptr; -; // Global power status meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus(); From 5cebe4a0a7a80e540a3568ea45c99b8d7f47f3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 17 Jun 2024 23:24:27 +0200 Subject: [PATCH 0615/1377] trunk fmt --- src/input/cardKbI2cImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index af0958f94..4bc5ee4ea 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -1,7 +1,7 @@ #include "cardKbI2cImpl.h" #include "InputBroker.h" -#include "main.h" #include "detect/ScanI2CTwoWire.h" +#include "main.h" CardKbI2cImpl *cardKbI2cImpl; From 8fa0911ec8b4c4a48083c23f2fefdb4bcedc2dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 18 Jun 2024 22:59:47 +0200 Subject: [PATCH 0616/1377] speed up OLED Display by transferring bigger chunks (#4138) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 83c4924c4..23ff53a10 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,7 +80,7 @@ monitor_speed = 115200 lib_deps = jgromes/RadioLib@~6.6.0 - https://github.com/meshtastic/esp8266-oled-ssd1306.git#dcacac5d2c7942376bc17f7079cced6a73cb659f ; ESP8266_SSD1306 + https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 From 3c4fa2101f1d2739a555e4e41040960a1fe151bc Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:31:05 +0000 Subject: [PATCH 0617/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 0c90a6814..005d7231e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0c90a6814fdd959a35bb6cf8e958e74d48e8a601 +Subproject commit 005d7231e59fdbadd74065230132b9ee176f2c87 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index f5fc8661a..064115815 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -69,6 +69,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_WIO_WM1110 = 21, /* RAK2560 Solar base station based on RAK4630 */ meshtastic_HardwareModel_RAK2560 = 22, + /* Heltec HRU-3601: https://heltec.org/project/hru-3601/ */ + meshtastic_HardwareModel_HELTEC_HRU_3601 = 23, /* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */ meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ From f79039fe579d9f373f73447b973940c673db0d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 19 Jun 2024 21:46:29 +0200 Subject: [PATCH 0618/1377] mask the rescan for portduino --- src/input/cardKbI2cImpl.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 4bc5ee4ea..1bff49475 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -9,6 +9,7 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {} void CardKbI2cImpl::init() { +#ifndef ARCH_PORTDUINO if (cardkb_found.address == 0x00) { LOG_DEBUG("Rescanning for I2C keyboard\n"); uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; @@ -49,6 +50,11 @@ void CardKbI2cImpl::init() return; } } - +#else + if (cardkb_found.address == 0x00) { + disable(); + return; + } +#endif inputBroker->registerSource(this); } From c59cb3c29217d3e33197ee4f6ab78df70ed244ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 14:48:46 -0500 Subject: [PATCH 0619/1377] [create-pull-request] automated change (#4145) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 005d7231e..1c3029f28 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 005d7231e59fdbadd74065230132b9ee176f2c87 +Subproject commit 1c3029f2868e5fc49809fd378f6c0c66aee0eaf4 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 781538d11..5a78f1366 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -511,6 +511,8 @@ typedef struct _meshtastic_Config_BluetoothConfig { meshtastic_Config_BluetoothConfig_PairingMode mode; /* Specified PIN for PairingMode.FixedPin */ uint32_t fixed_pin; + /* Enables device (serial style logs) over Bluetooth */ + bool device_logging_enabled; } meshtastic_Config_BluetoothConfig; typedef struct _meshtastic_Config { @@ -615,7 +617,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} -#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} +#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} @@ -624,7 +626,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} -#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} +#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Config_DeviceConfig_role_tag 1 @@ -702,6 +704,7 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_enabled_tag 1 #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 +#define meshtastic_Config_BluetoothConfig_device_logging_enabled_tag 4 #define meshtastic_Config_device_tag 1 #define meshtastic_Config_position_tag 2 #define meshtastic_Config_power_tag 3 @@ -833,7 +836,8 @@ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_BluetoothConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, UENUM, mode, 2) \ -X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) +X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) \ +X(a, STATIC, SINGULAR, BOOL, device_logging_enabled, 4) #define meshtastic_Config_BluetoothConfig_CALLBACK NULL #define meshtastic_Config_BluetoothConfig_DEFAULT NULL @@ -860,7 +864,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size -#define meshtastic_Config_BluetoothConfig_size 10 +#define meshtastic_Config_BluetoothConfig_size 12 #define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 80 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 0e3e28ba1..5e291ee94 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -308,7 +308,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3370 +#define meshtastic_OEMStore_size 3372 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 160202d9b..96a9976f0 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 539 +#define meshtastic_LocalConfig_size 541 #define meshtastic_LocalModuleConfig_size 685 #ifdef __cplusplus From 3e9e0fdd49a0e52090d3136406eda5423cfa485f Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Tue, 18 Jun 2024 21:26:45 +0800 Subject: [PATCH 0620/1377] Remove TTGO_T_ECHO gating for PIN_POWER_EN Signed-off-by: Andrew Yong --- src/main.cpp | 2 +- src/sleep.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b5707c8de..ddb99568d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -242,7 +242,7 @@ void setup() initDeepSleep(); // power on peripherals -#if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN) +#if defined(PIN_POWER_EN) pinMode(PIN_POWER_EN, OUTPUT); digitalWrite(PIN_POWER_EN, HIGH); // digitalWrite(PIN_POWER_EN1, INPUT); diff --git a/src/sleep.cpp b/src/sleep.cpp index 590610e6c..c9c45bf67 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -231,12 +231,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) nodeDB->saveToDisk(); -#ifdef TTGO_T_ECHO #ifdef PIN_POWER_EN pinMode(PIN_POWER_EN, INPUT); // power off peripherals // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif -#endif #if HAS_GPS // Kill GPS power completely (even if previously we just had it in sleep mode) if (gps) From 1515c8e76300621e7240f5144bb74833f5ae4ae6 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Tue, 18 Jun 2024 22:07:32 +0800 Subject: [PATCH 0621/1377] Add support for Heltec HRU-3601 Board is very similar to the Heltec HT-C62 based boards (Heltec HT62 variant) but due to wiring of SK6812 Neopixel LED to GPIO2 it becomes incompatible due to the regular HT-C62 dev board using a simple LED on the same GPIO. Depends on [protobufs#521](https://github.com/meshtastic/protobufs/pull/521). Works: * SK6812 Neopixel on GPIO2 * [GXCAS GXHTV3](https://www.lcsc.com/product-detail/Temperature-Sensors_GXCAS-GXHTV3C_C5441730.html) (SHTC3 compatible) Won't fix: * Battery reading - Board has no voltage divider on VBAT (board has a 1.25mm pitch "JST" style connector and a TP4054 charge IC) * Main thread LED - Board has no LED on simple GPIO Board schematic: [HRU3601.pdf](https://github.com/user-attachments/files/15874850/HRU3601.pdf) Signed-off-by: Andrew Yong --- src/platform/esp32/architecture.h | 2 ++ variants/heltec_hru_3601/pins_arduino.h | 25 ++++++++++++++++ variants/heltec_hru_3601/platformio.ini | 9 ++++++ variants/heltec_hru_3601/variant.h | 40 +++++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 variants/heltec_hru_3601/pins_arduino.h create mode 100644 variants/heltec_hru_3601/platformio.ini create mode 100644 variants/heltec_hru_3601/variant.h diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index c979d016c..5565b6468 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -101,6 +101,8 @@ #define HW_VENDOR meshtastic_HardwareModel_STATION_G1 #elif defined(DR_DEV) #define HW_VENDOR meshtastic_HardwareModel_DR_DEV +#elif defined(HELTEC_HRU_3601) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_HRU_3601 #elif defined(HELTEC_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V3 #elif defined(HELTEC_WSL_V3) diff --git a/variants/heltec_hru_3601/pins_arduino.h b/variants/heltec_hru_3601/pins_arduino.h new file mode 100644 index 000000000..625c57ced --- /dev/null +++ b/variants/heltec_hru_3601/pins_arduino.h @@ -0,0 +1,25 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +static const uint8_t TX = UART_TX; +static const uint8_t RX = UART_RX; + +static const uint8_t SDA = I2C_SDA; +static const uint8_t SCL = I2C_SCL; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 7; +static const uint8_t MISO = 6; +static const uint8_t SCK = 10; + +static const uint8_t A0 = 0; +static const uint8_t A1 = 1; +static const uint8_t A2 = 2; +static const uint8_t A3 = 3; +static const uint8_t A4 = 4; +static const uint8_t A5 = 5; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_hru_3601/platformio.ini b/variants/heltec_hru_3601/platformio.ini new file mode 100644 index 000000000..3668e72b7 --- /dev/null +++ b/variants/heltec_hru_3601/platformio.ini @@ -0,0 +1,9 @@ +[env:heltec-hru-3601] +extends = esp32c3_base +board = adafruit_qtpy_esp32c3 +build_flags = + ${esp32_base.build_flags} + -D HELTEC_HRU_3601 + -I variants/heltec_hru_3601 +lib_deps = ${esp32c3_base.lib_deps} + adafruit/Adafruit NeoPixel @ ^1.12.0 diff --git a/variants/heltec_hru_3601/variant.h b/variants/heltec_hru_3601/variant.h new file mode 100644 index 000000000..31783ec35 --- /dev/null +++ b/variants/heltec_hru_3601/variant.h @@ -0,0 +1,40 @@ +#define BUTTON_PIN 9 + +#define HAS_SCREEN 0 +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define USE_SX1262 +#define LORA_SCK 10 +#define LORA_MISO 6 +#define LORA_MOSI 7 +#define LORA_CS 8 +#define LORA_DIO0 RADIOLIB_NC +#define LORA_RESET 5 +#define LORA_DIO1 3 +#define LORA_DIO2 RADIOLIB_NC +#define LORA_BUSY 4 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_BUSY +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Vext_Ctrl pin controls 3.3V LDO (U2) which provides power to I2C peripheral +#define PIN_POWER_EN 1 + +// Board has I2C connected to UART0 pins, and no other hardware serial port +#define UART_TX -1 +#define UART_RX -1 + +// Board has I2C connected to U0RXD and U0TXD +#define I2C_SDA 21 +#define I2C_SCL 20 + +// Board has RGB LED on GPIO2 +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 2 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use From ecf5519b56cbfcd7fd7e70bba5fca99d4b726883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 20 Jun 2024 16:26:04 +0200 Subject: [PATCH 0622/1377] Moar LR1110 Targets --- boards/wio-sdk-wm1110.json | 6 +- boards/wio-t1000-s.json | 58 +++++++ boards/wio-tracker-wm1110.json | 6 +- src/ButtonThread.cpp | 8 +- src/SerialConsole.cpp | 4 + src/gps/GPS.cpp | 69 +++++++- src/gps/GPS.h | 2 +- .../Telemetry/EnvironmentTelemetry.cpp | 16 +- src/modules/Telemetry/Sensor/T1000xSensor.cpp | 116 +++++++++++++ src/modules/Telemetry/Sensor/T1000xSensor.h | 22 +++ src/platform/nrf52/BLEDfuScure.cpp | 144 ++++++++++++++++ src/platform/nrf52/BLEDfuSecure.h | 55 ++++++ src/platform/nrf52/NRF52Bluetooth.cpp | 14 +- .../platform/nrf52}/nrf52840_s140_v7.ld | 0 .../platform/nrf52}/softdevice/ble.h | 0 .../platform/nrf52}/softdevice/ble_err.h | 0 .../platform/nrf52}/softdevice/ble_gap.h | 0 .../platform/nrf52}/softdevice/ble_gatt.h | 0 .../platform/nrf52}/softdevice/ble_gattc.h | 0 .../platform/nrf52}/softdevice/ble_gatts.h | 0 .../platform/nrf52}/softdevice/ble_hci.h | 0 .../platform/nrf52}/softdevice/ble_l2cap.h | 0 .../platform/nrf52}/softdevice/ble_ranges.h | 0 .../platform/nrf52}/softdevice/ble_types.h | 0 .../nrf52}/softdevice/nrf52/nrf_mbr.h | 0 .../platform/nrf52}/softdevice/nrf_error.h | 0 .../nrf52}/softdevice/nrf_error_sdm.h | 0 .../nrf52}/softdevice/nrf_error_soc.h | 0 .../platform/nrf52}/softdevice/nrf_nvic.h | 0 .../platform/nrf52}/softdevice/nrf_sdm.h | 0 .../platform/nrf52}/softdevice/nrf_soc.h | 0 .../platform/nrf52}/softdevice/nrf_svc.h | 0 src/sleep.cpp | 18 ++ variants/wio-sdk-wm1110/platformio.ini | 6 +- variants/wio-t1000-s/platformio.ini | 15 ++ variants/wio-t1000-s/variant.cpp | 64 +++++++ variants/wio-t1000-s/variant.h | 161 ++++++++++++++++++ variants/wio-tracker-wm1110/platformio.ini | 6 +- variants/xiao_ble/platformio.ini | 4 +- 39 files changed, 772 insertions(+), 22 deletions(-) create mode 100644 boards/wio-t1000-s.json create mode 100644 src/modules/Telemetry/Sensor/T1000xSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/T1000xSensor.h create mode 100644 src/platform/nrf52/BLEDfuScure.cpp create mode 100644 src/platform/nrf52/BLEDfuSecure.h rename {variants/xiao_ble => src/platform/nrf52}/nrf52840_s140_v7.ld (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_err.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gap.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gatt.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gattc.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gatts.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_hci.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_l2cap.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_ranges.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_types.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf52/nrf_mbr.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_error.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_error_sdm.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_error_soc.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_nvic.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_sdm.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_soc.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_svc.h (100%) create mode 100644 variants/wio-t1000-s/platformio.ini create mode 100644 variants/wio-t1000-s/variant.cpp create mode 100644 variants/wio-t1000-s/variant.h diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index 029c9c085..04db52518 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -1,7 +1,7 @@ { "build": { "arduino": { - "ldscript": "nrf52840_s140_v6.ld" + "ldscript": "nrf52840_s140_v7.ld" }, "core": "nRF5", "cpu": "cortex-m4", @@ -22,8 +22,8 @@ "softdevice": { "sd_flags": "-DS140", "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" + "sd_version": "7.3.0", + "sd_fwid": "0x0123" }, "bootloader": { "settings_addr": "0xFF000" diff --git a/boards/wio-t1000-s.json b/boards/wio-t1000-s.json new file mode 100644 index 000000000..654a8f73d --- /dev/null +++ b/boards/wio-t1000-s.json @@ -0,0 +1,58 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v7.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "WIO-BOOT", + "mcu": "nrf52840", + "variant": "Seeed_WIO_WM1110", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "7.3.0", + "sd_fwid": "0x0123" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Seeed WIO WM1110", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.seeedstudio.com/LoRaWAN-Tracker-c-1938.html", + "vendor": "Seeed Studio" +} diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index 029c9c085..b4ab8db11 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -1,7 +1,7 @@ { "build": { "arduino": { - "ldscript": "nrf52840_s140_v6.ld" + "ldscript": "nrf52840_s140_v7.ld" }, "core": "nRF5", "cpu": "cortex-m4", @@ -22,7 +22,7 @@ "softdevice": { "sd_flags": "-DS140", "sd_name": "s140", - "sd_version": "6.1.1", + "sd_version": "7.3.0", "sd_fwid": "0x00B6" }, "bootloader": { @@ -53,6 +53,6 @@ "require_upload_port": true, "wait_for_upload_port": true }, - "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html", + "url": "https://www.seeedstudio.com/Wio-Tracker-1110-Dev-Board-p-5799.html", "vendor": "Seeed Studio" } diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 4b3bb3fbc..dc062fce4 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -43,6 +43,8 @@ ButtonThread::ButtonThread() : OSThread("Button") int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin #if defined(HELTEC_CAPSULE_SENSOR_V3) this->userButton = OneButton(pin, false, false); +#elif defined(BUTTON_ACTIVE_LOW) // change by WayenWeng + this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP); #else this->userButton = OneButton(pin, true, true); #endif @@ -51,8 +53,12 @@ ButtonThread::ButtonThread() : OSThread("Button") #ifdef INPUT_PULLUP_SENSE // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did +#ifdef BUTTON_SENSE_TYPE // change by WayenWeng + pinMode(pin, BUTTON_SENSE_TYPE); +#else pinMode(pin, INPUT_PULLUP_SENSE); #endif +#endif #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) userButton.attachClick(userButtonPressed); @@ -322,4 +328,4 @@ void ButtonThread::userButtonPressedLongStop() if (millis() > c_holdOffTime) { btnEvent = BUTTON_EVENT_LONG_RELEASED; } -} +} \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 53ece0fa3..cf6375585 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -7,8 +7,12 @@ #ifdef RP2040_SLOW_CLOCK #define Port Serial2 #else +#ifdef USER_DEBUG_PORT // change by WayenWeng +#define Port USER_DEBUG_PORT +#else #define Port Serial #endif +#endif // Defaulting to the formerly removed phone_timeout_secs value of 15 minutes #define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 8d46742ba..40ee4ea03 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -390,8 +390,13 @@ bool GPS::setup() int msglen = 0; if (!didSerialInit) { +#ifdef GNSS_Airoha // change by WayenWeng + if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { + probe(GPS_BAUDRATE); + LOG_INFO("GPS setting to %d.\n", GPS_BAUDRATE); + } +#else #if !defined(GPS_UC6580) - if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]); gnssModel = probe(serialSpeeds[speedSelect]); @@ -762,6 +767,7 @@ bool GPS::setup() LOG_INFO("GNSS module configuration saved!\n"); } } +#endif // !GNSS_Airoha didSerialInit = true; } @@ -869,6 +875,14 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) } gps->_serial_gps->write(gps->UBXscratch, msglen); } +#ifdef GNSS_Airoha // add by WayenWeng + else { + if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { + // TODO, send rtc mode command + digitalWrite(PIN_GPS_EN, LOW); + } + } +#endif } else { if (gnssModel == GNSS_MODEL_UBLOX) { gps->_serial_gps->write(0xFF); @@ -908,6 +922,9 @@ void GPS::setAwake(bool wantAwake) if (wantAwake) { // Record the time we start looking for a lock lastWakeStartMsec = millis(); +#ifdef GNSS_Airoha + lastFixStartMsec = 0; +#endif } else { // Record by how much we missed our ideal target postion.gps_update_interval (for logging only) // Need to calculate this before we update lastSleepStartMsec, to make the new prediction @@ -1147,6 +1164,9 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->updateBaudRate(serialSpeed); } #endif +#ifdef GNSS_Airoha // add by WayenWeng + return GNSS_MODEL_UNKNOWN; +#else #ifdef GPS_DEBUG for (int i = 0; i < 20; i++) { getACK("$GP", 200); @@ -1293,6 +1313,7 @@ GnssModel_t GPS::probe(int serialSpeed) } return GNSS_MODEL_UBLOX; +#endif // !GNSS_Airoha } GPS *GPS::createGps() @@ -1460,6 +1481,25 @@ bool GPS::factoryReset() */ bool GPS::lookForTime() { +#ifdef GNSS_Airoha // add by WayenWeng + uint8_t fix = reader.fixQuality(); + uint32_t now = millis(); + if (fix > 0) { + if (lastFixStartMsec > 0) { + if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { + return false; + } else { + clearBuffer(); + } + } else { + lastFixStartMsec = now; + return false; + } + } else { + return false; + } +#endif + auto ti = reader.time; auto d = reader.date; if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed @@ -1494,6 +1534,27 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s */ bool GPS::lookForLocation() { +#ifdef GNSS_Airoha // add by WayenWeng + if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { + uint8_t fix = reader.fixQuality(); + uint32_t now = millis(); + if (fix > 0) { + if (lastFixStartMsec > 0) { + if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { + return false; + } else { + clearBuffer(); + } + } else { + lastFixStartMsec = now; + return false; + } + } else { + return false; + } + } +#endif + // By default, TinyGPS++ does not parse GPGSA lines, which give us // the 2D/3D fixType (see NMEAGPS.h) // At a minimum, use the fixQuality indicator in GPGGA (FIXME?) @@ -1702,6 +1763,12 @@ void GPS::toggleGpsMode() if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; LOG_DEBUG("Flag set to false for gps power. GpsMode: DISABLED\n"); +#ifdef GNSS_Airoha + if (powerState != GPS_ACTIVE) { + LOG_DEBUG("User power Off GPS\n"); + digitalWrite(PIN_GPS_EN, LOW); + } +#endif disable(); } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 55bd42d0f..91548ce2c 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -67,7 +67,7 @@ class GPS : private concurrency::OSThread uint8_t fixType = 0; // fix type from GPGSA #endif private: - uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0; + uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0; const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600}; uint32_t rx_gpio = 0; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ff3202067..b1149799b 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -33,6 +33,9 @@ #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" +#ifdef T1000X_SENSOR_EN +#include "Sensor/T1000xSensor.h" +#endif #include "Sensor/TSL2591Sensor.h" #include "Sensor/VEML7700Sensor.h" @@ -53,6 +56,9 @@ AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; NAU7802Sensor nau7802Sensor; +#ifdef T1000X_SENSOR_EN +T1000xSensor t1000xSensor; +#endif #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -91,6 +97,9 @@ int32_t EnvironmentTelemetryModule::runOnce() LOG_INFO("Environment Telemetry: Initializing\n"); // it's possible to have this module enabled, only for displaying values on the screen. // therefore, we should only enable the sensor loop if measurement is also enabled +#ifdef T1000X_SENSOR_EN // add by WayenWeng + result = t1000xSensor.runOnce(); +#else if (dfRobotLarkSensor.hasSensor()) result = dfRobotLarkSensor.runOnce(); if (bmp085Sensor.hasSensor()) @@ -129,6 +138,7 @@ int32_t EnvironmentTelemetryModule::runOnce() result = mlx90632Sensor.runOnce(); if (nau7802Sensor.hasSensor()) result = nau7802Sensor.runOnce(); +#endif } return result; } else { @@ -278,6 +288,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); m.which_variant = meshtastic_Telemetry_environment_metrics_tag; +#ifdef T1000X_SENSOR_EN // add by WayenWeng + valid = valid && t1000xSensor.getMetrics(&m); + hasSensor = true; +#else if (dfRobotLarkSensor.hasSensor()) { valid = valid && dfRobotLarkSensor.getMetrics(&m); hasSensor = true; @@ -358,7 +372,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } - +#endif valid = valid && hasSensor; if (valid) { diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp new file mode 100644 index 000000000..e544d0dc5 --- /dev/null +++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp @@ -0,0 +1,116 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(T1000X_SENSOR_EN) + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "T1000xSensor.h" +#include "TelemetrySensor.h" +#include + +#define T1000X_SENSE_SAMPLES 15 +#define T1000X_LIGHT_REF_VCC 2400 + +#define HEATER_NTC_BX 4250 // thermistor coefficient B +#define HEATER_NTC_RP 8250 // ohm, series resistance to thermistor +#define HEATER_NTC_KA 273.15 // 25 Celsius at Kelvin +#define NTC_REF_VCC 3000 // mV, output voltage of LDO + +// ntc res table +uint32_t ntc_res2[136] = { + 113347, 107565, 102116, 96978, 92132, 87559, 83242, 79166, 75316, 71677, 68237, 64991, 61919, 59011, 56258, 53650, 51178, + 48835, 46613, 44506, 42506, 40600, 38791, 37073, 35442, 33892, 32420, 31020, 29689, 28423, 27219, 26076, 24988, 23951, + 22963, 22021, 21123, 20267, 19450, 18670, 17926, 17214, 16534, 15886, 15266, 14674, 14108, 13566, 13049, 12554, 12081, + 11628, 11195, 10780, 10382, 10000, 9634, 9284, 8947, 8624, 8315, 8018, 7734, 7461, 7199, 6948, 6707, 6475, + 6253, 6039, 5834, 5636, 5445, 5262, 5086, 4917, 4754, 4597, 4446, 4301, 4161, 4026, 3896, 3771, 3651, + 3535, 3423, 3315, 3211, 3111, 3014, 2922, 2834, 2748, 2666, 2586, 2509, 2435, 2364, 2294, 2228, 2163, + 2100, 2040, 1981, 1925, 1870, 1817, 1766, 1716, 1669, 1622, 1578, 1535, 1493, 1452, 1413, 1375, 1338, + 1303, 1268, 1234, 1202, 1170, 1139, 1110, 1081, 1053, 1026, 999, 974, 949, 925, 902, 880, 858, +}; + +int8_t ntc_temp2[136] = { + -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, + -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, +}; + +T1000xSensor::T1000xSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR_UNSET, "T1000x") {} + +int32_t T1000xSensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; +} + +void T1000xSensor::setup() +{ + // Set up oversampling and filter initialization +} + +float T1000xSensor::getLux() +{ + uint32_t lux_vot = 0; + float lux_level = 0; + + for (uint32_t i = 0; i < T1000X_SENSE_SAMPLES; i++) { + lux_vot += analogRead(T1000X_LUX_PIN); + } + lux_vot = lux_vot / T1000X_SENSE_SAMPLES; + lux_vot = ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * lux_vot; + + if (lux_vot <= 80) + lux_level = 0; + else if (lux_vot >= 2480) + lux_level = 100; + else + lux_level = 100 * (lux_vot - 80) / T1000X_LIGHT_REF_VCC; + + return lux_level; +} + +float T1000xSensor::getTemp() +{ + uint32_t vcc_vot = 0, ntc_vot = 0; + + uint8_t u8i = 0; + float Vout = 0, Rt = 0, temp = 0; + float Temp = 0; + + for (uint32_t i = 0; i < T1000X_SENSE_SAMPLES; i++) { + vcc_vot += analogRead(T1000X_VCC_PIN); + } + vcc_vot = vcc_vot / T1000X_SENSE_SAMPLES; + vcc_vot = 2 * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vcc_vot; + + for (uint32_t i = 0; i < T1000X_SENSE_SAMPLES; i++) { + ntc_vot += analogRead(T1000X_NTC_PIN); + } + ntc_vot = ntc_vot / T1000X_SENSE_SAMPLES; + ntc_vot = ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * ntc_vot; + + Vout = ntc_vot; + Rt = (HEATER_NTC_RP * vcc_vot) / Vout - HEATER_NTC_RP; + for (u8i = 0; u8i < 136; u8i++) { + if (Rt >= ntc_res2[u8i]) { + break; + } + } + temp = ntc_temp2[u8i - 1] + 1 * (ntc_res2[u8i - 1] - Rt) / (float)(ntc_res2[u8i - 1] - ntc_res2[u8i]); + Temp = (temp * 100 + 5) / 100; // half adjust + + return Temp; +} + +bool T1000xSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.temperature = getTemp(); + measurement->variant.environment_metrics.lux = getLux(); + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.h b/src/modules/Telemetry/Sensor/T1000xSensor.h new file mode 100644 index 000000000..127d2630c --- /dev/null +++ b/src/modules/Telemetry/Sensor/T1000xSensor.h @@ -0,0 +1,22 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" + +class T1000xSensor : public TelemetrySensor +{ + private: + protected: + virtual void setup() override; + + public: + T1000xSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual float getLux(); + virtual float getTemp(); +}; + +#endif \ No newline at end of file diff --git a/src/platform/nrf52/BLEDfuScure.cpp b/src/platform/nrf52/BLEDfuScure.cpp new file mode 100644 index 000000000..8f7a327c4 --- /dev/null +++ b/src/platform/nrf52/BLEDfuScure.cpp @@ -0,0 +1,144 @@ +/**************************************************************************/ +/*! + @file BLEDfu.cpp + @author hathach (tinyusb.org) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2018, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include "BLEDfuSecure.h" +#include "bluefruit.h" + +#define DFU_REV_APPMODE 0x0001 + +const uint16_t UUID16_SVC_DFU_OTA = 0xFE59; + +const uint8_t UUID128_CHR_DFU_CONTROL[16] = {0x50, 0xEA, 0xDA, 0x30, 0x88, 0x83, 0xB8, 0x9F, + 0x60, 0x4F, 0x15, 0xF3, 0x03, 0x00, 0xC9, 0x8E}; + +extern "C" void bootloader_util_app_start(uint32_t start_addr); + +static uint16_t crc16(const uint8_t *data_p, uint8_t length) +{ + uint8_t x; + uint16_t crc = 0xFFFF; + + while (length--) { + x = crc >> 8 ^ *data_p++; + x ^= x >> 4; + crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x << 5)) ^ ((uint16_t)x); + } + return crc; +} + +static void bledfu_control_wr_authorize_cb(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_write_t *request) +{ + if ((request->handle == chr->handles().value_handle) && (request->op != BLE_GATTS_OP_PREP_WRITE_REQ) && + (request->op != BLE_GATTS_OP_EXEC_WRITE_REQ_NOW) && (request->op != BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL)) { + BLEConnection *conn = Bluefruit.Connection(conn_hdl); + + ble_gatts_rw_authorize_reply_params_t reply = {.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE}; + + if (!chr->indicateEnabled(conn_hdl)) { + reply.params.write.gatt_status = BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR; + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); + return; + } + + reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); + + enum { START_DFU = 1 }; + if (request->data[0] == START_DFU) { + // Peer data information so that bootloader could re-connect after reboot + typedef struct { + ble_gap_addr_t addr; + ble_gap_irk_t irk; + ble_gap_enc_key_t enc_key; + uint8_t sys_attr[8]; + uint16_t crc16; + } peer_data_t; + + VERIFY_STATIC(offsetof(peer_data_t, crc16) == 60); + + /* Save Peer data + * Peer data address is defined in bootloader linker @0x20007F80 + * - If bonded : save Security information + * - Otherwise : save Address for direct advertising + * + * TODO may force bonded only for security reason + */ + peer_data_t *peer_data = (peer_data_t *)(0x20007F80UL); + varclr(peer_data); + + // Get CCCD + uint16_t sysattr_len = sizeof(peer_data->sys_attr); + sd_ble_gatts_sys_attr_get(conn_hdl, peer_data->sys_attr, &sysattr_len, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS); + + // Get Bond Data or using Address if not bonded + peer_data->addr = conn->getPeerAddr(); + + if (conn->secured()) { + bond_keys_t bkeys; + if (conn->loadBondKey(&bkeys)) { + peer_data->addr = bkeys.peer_id.id_addr_info; + peer_data->irk = bkeys.peer_id.id_info; + peer_data->enc_key = bkeys.own_enc; + } + } + + // Calculate crc + peer_data->crc16 = crc16((uint8_t *)peer_data, offsetof(peer_data_t, crc16)); + + // Initiate DFU Sequence and reboot into DFU OTA mode + Bluefruit.Advertising.restartOnDisconnect(false); + conn->disconnect(); + + NRF_POWER->GPREGRET = 0xB1; + NVIC_SystemReset(); + } + } +} + +BLEDfuSecure::BLEDfuSecure(void) : BLEService(UUID16_SVC_DFU_OTA), _chr_control(UUID128_CHR_DFU_CONTROL) {} + +err_t BLEDfuSecure::begin(void) +{ + // Invoke base class begin() + VERIFY_STATUS(BLEService::begin()); + + _chr_control.setProperties(CHR_PROPS_WRITE | CHR_PROPS_INDICATE); + _chr_control.setMaxLen(23); + _chr_control.setWriteAuthorizeCallback(bledfu_control_wr_authorize_cb); + VERIFY_STATUS(_chr_control.begin()); + + return ERROR_NONE; +} diff --git a/src/platform/nrf52/BLEDfuSecure.h b/src/platform/nrf52/BLEDfuSecure.h new file mode 100644 index 000000000..bd5d910e8 --- /dev/null +++ b/src/platform/nrf52/BLEDfuSecure.h @@ -0,0 +1,55 @@ +/**************************************************************************/ +/*! + @file BLEDfu.h + @author hathach (tinyusb.org) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2018, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ +#ifndef BLEDFUSECURE_H_ +#define BLEDFUSECURE_H_ + +#include "bluefruit_common.h" + +#include "BLECharacteristic.h" +#include "BLEService.h" + +class BLEDfuSecure : public BLEService +{ + protected: + BLECharacteristic _chr_control; + + public: + BLEDfuSecure(void); + + virtual err_t begin(void); +}; + +#endif /* BLEDFUSECURE_H_ */ diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4c25f38ea..8b817f51b 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -1,4 +1,5 @@ #include "NRF52Bluetooth.h" +#include "BLEDfuSecure.h" #include "BluetoothCommon.h" #include "PowerFSM.h" #include "configuration.h" @@ -13,9 +14,10 @@ static BLECharacteristic fromNum = BLECharacteristic(BLEUuid(FROMNUM_UUID_16)); static BLECharacteristic fromRadio = BLECharacteristic(BLEUuid(FROMRADIO_UUID_16)); static BLECharacteristic toRadio = BLECharacteristic(BLEUuid(TORADIO_UUID_16)); -static BLEDis bledis; // DIS (Device Information Service) helper class instance -static BLEBas blebas; // BAS (Battery Service) helper class instance -static BLEDfu bledfu; // DFU software update helper service +static BLEDis bledis; // DIS (Device Information Service) helper class instance +static BLEBas blebas; // BAS (Battery Service) helper class instance +static BLEDfu bledfu; // DFU software update helper service +static BLEDfuSecure bledfusecure; // DFU software update helper service // 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 @@ -273,9 +275,13 @@ void NRF52Bluetooth::setup() Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); +#ifndef BLE_DFU_SECURE 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 +#endif // Configure and Start the Device Information Service LOG_INFO("Configuring the Device Information Service\n"); bledis.setModel(optstr(HW_VERSION)); diff --git a/variants/xiao_ble/nrf52840_s140_v7.ld b/src/platform/nrf52/nrf52840_s140_v7.ld similarity index 100% rename from variants/xiao_ble/nrf52840_s140_v7.ld rename to src/platform/nrf52/nrf52840_s140_v7.ld diff --git a/variants/xiao_ble/softdevice/ble.h b/src/platform/nrf52/softdevice/ble.h similarity index 100% rename from variants/xiao_ble/softdevice/ble.h rename to src/platform/nrf52/softdevice/ble.h diff --git a/variants/xiao_ble/softdevice/ble_err.h b/src/platform/nrf52/softdevice/ble_err.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_err.h rename to src/platform/nrf52/softdevice/ble_err.h diff --git a/variants/xiao_ble/softdevice/ble_gap.h b/src/platform/nrf52/softdevice/ble_gap.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gap.h rename to src/platform/nrf52/softdevice/ble_gap.h diff --git a/variants/xiao_ble/softdevice/ble_gatt.h b/src/platform/nrf52/softdevice/ble_gatt.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gatt.h rename to src/platform/nrf52/softdevice/ble_gatt.h diff --git a/variants/xiao_ble/softdevice/ble_gattc.h b/src/platform/nrf52/softdevice/ble_gattc.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gattc.h rename to src/platform/nrf52/softdevice/ble_gattc.h diff --git a/variants/xiao_ble/softdevice/ble_gatts.h b/src/platform/nrf52/softdevice/ble_gatts.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gatts.h rename to src/platform/nrf52/softdevice/ble_gatts.h diff --git a/variants/xiao_ble/softdevice/ble_hci.h b/src/platform/nrf52/softdevice/ble_hci.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_hci.h rename to src/platform/nrf52/softdevice/ble_hci.h diff --git a/variants/xiao_ble/softdevice/ble_l2cap.h b/src/platform/nrf52/softdevice/ble_l2cap.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_l2cap.h rename to src/platform/nrf52/softdevice/ble_l2cap.h diff --git a/variants/xiao_ble/softdevice/ble_ranges.h b/src/platform/nrf52/softdevice/ble_ranges.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_ranges.h rename to src/platform/nrf52/softdevice/ble_ranges.h diff --git a/variants/xiao_ble/softdevice/ble_types.h b/src/platform/nrf52/softdevice/ble_types.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_types.h rename to src/platform/nrf52/softdevice/ble_types.h diff --git a/variants/xiao_ble/softdevice/nrf52/nrf_mbr.h b/src/platform/nrf52/softdevice/nrf52/nrf_mbr.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf52/nrf_mbr.h rename to src/platform/nrf52/softdevice/nrf52/nrf_mbr.h diff --git a/variants/xiao_ble/softdevice/nrf_error.h b/src/platform/nrf52/softdevice/nrf_error.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_error.h rename to src/platform/nrf52/softdevice/nrf_error.h diff --git a/variants/xiao_ble/softdevice/nrf_error_sdm.h b/src/platform/nrf52/softdevice/nrf_error_sdm.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_error_sdm.h rename to src/platform/nrf52/softdevice/nrf_error_sdm.h diff --git a/variants/xiao_ble/softdevice/nrf_error_soc.h b/src/platform/nrf52/softdevice/nrf_error_soc.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_error_soc.h rename to src/platform/nrf52/softdevice/nrf_error_soc.h diff --git a/variants/xiao_ble/softdevice/nrf_nvic.h b/src/platform/nrf52/softdevice/nrf_nvic.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_nvic.h rename to src/platform/nrf52/softdevice/nrf_nvic.h diff --git a/variants/xiao_ble/softdevice/nrf_sdm.h b/src/platform/nrf52/softdevice/nrf_sdm.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_sdm.h rename to src/platform/nrf52/softdevice/nrf_sdm.h diff --git a/variants/xiao_ble/softdevice/nrf_soc.h b/src/platform/nrf52/softdevice/nrf_soc.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_soc.h rename to src/platform/nrf52/softdevice/nrf_soc.h diff --git a/variants/xiao_ble/softdevice/nrf_svc.h b/src/platform/nrf52/softdevice/nrf_svc.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_svc.h rename to src/platform/nrf52/softdevice/nrf_svc.h diff --git a/src/sleep.cpp b/src/sleep.cpp index 590610e6c..0e7045323 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -242,6 +242,24 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) if (gps) gps->setGPSPower(false, false, 0); #endif + +#ifdef GNSS_Airoha // add by WayenWeng + digitalWrite(GPS_VRTC_EN, LOW); + digitalWrite(PIN_GPS_RESET, LOW); + digitalWrite(GPS_SLEEP_INT, LOW); + digitalWrite(GPS_RTC_INT, LOW); + pinMode(GPS_RESETB_OUT, OUTPUT); + digitalWrite(GPS_RESETB_OUT, LOW); +#endif + +#ifdef BUZZER_EN_PIN // add by WayenWeng + digitalWrite(BUZZER_EN_PIN, LOW); +#endif + +#ifdef PIN_3V3_EN // add by WayenWeng + digitalWrite(PIN_3V3_EN, LOW); +#endif + setLed(false); #ifdef RESET_OLED diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 8b1433dd1..9d9ea4c29 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -1,12 +1,12 @@ -; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +; The black Wio-WM1110 Dev Kit with sensors and the WM1110 module [env:wio-sdk-wm1110] extends = nrf52840_base board = wio-sdk-wm1110 board_level = extra -; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/wio-t1000-s/platformio.ini b/variants/wio-t1000-s/platformio.ini new file mode 100644 index 000000000..cb1cf86f7 --- /dev/null +++ b/variants/wio-t1000-s/platformio.ini @@ -0,0 +1,15 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:wio-t1000-s] +extends = nrf52840_base +board = wio-t1000-s +board_level = extra +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-t1000-s -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-t1000-s> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = jlink \ No newline at end of file diff --git a/variants/wio-t1000-s/variant.cpp b/variants/wio-t1000-s/variant.cpp new file mode 100644 index 000000000..85e0c44f3 --- /dev/null +++ b/variants/wio-t1000-s/variant.cpp @@ -0,0 +1,64 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); + + pinMode(PIN_3V3_ACC_EN, OUTPUT); + digitalWrite(PIN_3V3_ACC_EN, LOW); + + pinMode(BUZZER_EN_PIN, OUTPUT); + digitalWrite(BUZZER_EN_PIN, HIGH); + + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, LOW); + + pinMode(GPS_VRTC_EN, OUTPUT); + digitalWrite(GPS_VRTC_EN, HIGH); + + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, LOW); + + pinMode(GPS_SLEEP_INT, OUTPUT); + digitalWrite(GPS_SLEEP_INT, HIGH); + + pinMode(GPS_RTC_INT, OUTPUT); + digitalWrite(GPS_RTC_INT, LOW); + + pinMode(GPS_RESETB_OUT, INPUT); +} \ No newline at end of file diff --git a/variants/wio-t1000-s/variant.h b/variants/wio-t1000-s/variant.h new file mode 100644 index 000000000..86bd34f62 --- /dev/null +++ b/variants/wio-t1000-s/variant.h @@ -0,0 +1,161 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_WIO_SDK_WM1110_ +#define _VARIANT_WIO_SDK_WM1110_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +#define BLE_DFU_SECURE // we use the 7.x softdevice signing keys + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +#define PIN_3V3_EN (32 + 6) // P1.6, Power to Sensors +#define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc + +#define PIN_LED1 (0 + 24) // P0.24 +#define LED_PIN PIN_LED1 +#define LED_BUILTIN -1 +#define LED_BLUE -1 // Actually green +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN (0 + 6) // P0.6 +#define BUTTON_ACTIVE_LOW false +#define BUTTON_ACTIVE_PULLUP false +#define BUTTON_SENSE_TYPE INPUT_SENSE_HIGH + +#define HAS_WIRE 0 + +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (0 + 26) // P0.26 +#define PIN_WIRE_SCL (0 + 27) // P0.27 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 14) // P0.14 +#define PIN_SERIAL1_TX (0 + 13) // P0.13 + +#define PIN_SERIAL2_RX (0 + 17) // P0.17 +#define PIN_SERIAL2_TX (0 + 16) // P0.16 + +#define USER_DEBUG_PORT Serial2 + +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (32 + 8) // P1.08 +#define PIN_SPI_MOSI (32 + 9) // P1.09 +#define PIN_SPI_SCK (0 + 11) // P0.11 +#define PIN_SPI_NSS (0 + 12) // P0.12 + +#define LORA_RESET (32 + 10) // P1.10 // RST +#define LORA_DIO1 (32 + 1) // P1.01 // IRQ +#define LORA_DIO2 (0 + 7) // P0.07 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +// supported modules list +#define USE_LR1110 + +#define LR1110_IRQ_PIN LORA_DIO1 +#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO2 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.6 +#define LR11X0_DIO_AS_RF_SWITCH +#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 + +#define HAS_GPS 1 +#define GNSS_Airoha +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +#define GPS_BAUDRATE 115200 + +#define PIN_GPS_EN (32 + 11) // P1.11 +#define GPS_EN_ACTIVE HIGH + +#define PIN_GPS_RESET (32 + 15) // P1.15 +#define GPS_RESET_MODE HIGH + +#define GPS_VRTC_EN (0 + 8) // P0.8, awlays high +#define GPS_SLEEP_INT (32 + 12) // P1.12, awlays high +#define GPS_RTC_INT (0 + 15) // P0.15, normal is LOW, wake by HIGH +#define GPS_RESETB_OUT (32 + 14) // P1.14, awlays input pull_up + +// #define GPS_THREAD_INTERVAL 50 +#define GPS_FIX_HOLD_TIME 15000 // ms + +#define BATTERY_PIN 2 +// #define ADC_CHANNEL ADC1_GPIO2_CHANNEL +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +// #define BATTERY_SENSE_RESOLUTION 4096.0 + +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 + +// #define ADC_CTRL (32 + 6) +// #define ADC_CTRL_ENABLED HIGH + +// Buzzer +#define BUZZER_EN_PIN (32 + 5) // P1.05, awlays high +#define PIN_BUZZER (0 + 25) // P0.25, pwm output + +#define T1000X_SENSOR_EN +#define T1000X_VCC_PIN (0 + 4) // P0.4 +#define T1000X_NTC_PIN (0 + 31) // P0.31 +#define T1000X_LUX_PIN (0 + 29) // P0.29 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_WIO_SDK_WM1110_ diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index cba1b8741..03d7d047a 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -1,11 +1,11 @@ -; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +; The red tracker Dev Board with the WM1110 module [env:wio-tracker-wm1110] extends = nrf52840_base board = wio-tracker-wm1110 -; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 60e7cecbd..156eba528 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -3,9 +3,9 @@ extends = nrf52840_base board = xiao_ble_sense board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Ivariants/xiao_ble/softdevice -Ivariants/xiao_ble/softdevice/nrf52 -D EBYTE_E22 -DPRIVATE_HW +build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -D EBYTE_E22 -DPRIVATE_HW -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -board_build.ldscript = variants/xiao_ble/nrf52840_s140_v7.ld +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/xiao_ble> lib_deps = ${nrf52840_base.lib_deps} From 9266a53f4ef3bdf787d0985a76e7f3969741bac1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 20 Jun 2024 09:47:07 -0700 Subject: [PATCH 0623/1377] Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB --- boards/wio-sdk-wm1110.json | 7 ------- extra_scripts/README.md | 3 +++ extra_scripts/disable_adafruit_usb.py | 17 +++++++++++++++++ variants/wio-sdk-wm1110/platformio.ini | 6 +++++- variants/wio-sdk-wm1110/variant.h | 7 +++++++ 5 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 extra_scripts/README.md create mode 100644 extra_scripts/disable_adafruit_usb.py diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index 029c9c085..882f4443e 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -7,13 +7,6 @@ "cpu": "cortex-m4", "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", "f_cpu": "64000000L", - "hwids": [ - ["0x239A", "0x8029"], - ["0x239A", "0x0029"], - ["0x239A", "0x002A"], - ["0x239A", "0x802A"] - ], - "usb_product": "WIO-BOOT", "mcu": "nrf52840", "variant": "Seeed_WIO_WM1110", "bsp": { diff --git a/extra_scripts/README.md b/extra_scripts/README.md new file mode 100644 index 000000000..4c797f49f --- /dev/null +++ b/extra_scripts/README.md @@ -0,0 +1,3 @@ +# extra_scripts + +This directory contains special [scripts](https://docs.platformio.org/en/latest/scripting/index.html) that are used to modify the platformio environment in rare cases. diff --git a/extra_scripts/disable_adafruit_usb.py b/extra_scripts/disable_adafruit_usb.py new file mode 100644 index 000000000..109d1f3d4 --- /dev/null +++ b/extra_scripts/disable_adafruit_usb.py @@ -0,0 +1,17 @@ +Import("env") + +# NOTE: This is not currently used, but can serve as an example on how to write extra_scripts + +print("Current CLI targets", COMMAND_LINE_TARGETS) +print("Current Build targets", BUILD_TARGETS) +print("CPP defs", env.get("CPPDEFINES")) + +# Adafruit.py in the platformio build tree is a bit naive and always enables their USB stack for building. We don't want this. +# So come in after that python script has run and disable it. This hack avoids us having to fork that big project and send in a PR +# which might not be accepted. -@geeksville + +env["CPPDEFINES"].remove("USBCON") +env["CPPDEFINES"].remove("USE_TINYUSB") + +# Custom actions when building program/firmware +# env.AddPreAction("buildprog", callback...) diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 8b1433dd1..7ca82e4c6 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -2,6 +2,10 @@ [env:wio-sdk-wm1110] extends = nrf52840_base board = wio-sdk-wm1110 + +# Remove adafruit USB serial from the build (it is incompatible with using the ch340 serial chip on this board) +build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB + board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -DWIO_WM1110 @@ -12,4 +16,4 @@ lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -upload_protocol = jlink \ No newline at end of file +upload_protocol = jlink diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h index f027b469f..8ad8c769a 100644 --- a/variants/wio-sdk-wm1110/variant.h +++ b/variants/wio-sdk-wm1110/variant.h @@ -31,6 +31,13 @@ #include "WVariant.h" +#ifdef USE_TINYUSB +#error TinyUSB must be disabled by platformio before using this variant +#endif + +// We use the hardware serial port for the serial console +#define Serial Serial1 + #ifdef __cplusplus extern "C" { #endif // __cplusplus From e050888b26e4c9327e0bb7bdb839958bc1915814 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 20 Jun 2024 12:28:43 -0700 Subject: [PATCH 0624/1377] Don't complain about wierd platformio python --- extra_scripts/disable_adafruit_usb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extra_scripts/disable_adafruit_usb.py b/extra_scripts/disable_adafruit_usb.py index 109d1f3d4..fb391715c 100644 --- a/extra_scripts/disable_adafruit_usb.py +++ b/extra_scripts/disable_adafruit_usb.py @@ -1,3 +1,6 @@ +# trunk-ignore-all(flake8/F821) +# trunk-ignore-all(ruff/F821) + Import("env") # NOTE: This is not currently used, but can serve as an example on how to write extra_scripts From 2d39911f91f03dfc686fe542f6b34df828251973 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 21 Jun 2024 00:14:34 +0300 Subject: [PATCH 0625/1377] Fix protobuf structs handling (#4140) * Fix protobuf structs handling * Log instead of assert --------- Co-authored-by: Ben Meadors --- src/main.cpp | 2 +- src/mesh/NodeDB.cpp | 5 ----- src/modules/AdminModule.cpp | 4 ++-- src/mqtt/MQTT.cpp | 7 ++++++- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ddb99568d..931c2a3fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -173,7 +173,7 @@ const char *getDeviceName() static char name[20]; snprintf(name, sizeof(name), "%02x%02x", dmac[4], dmac[5]); // if the shortname exists and is NOT the new default of ab3c, use it for BLE name. - if ((owner.short_name != NULL) && (strcmp(owner.short_name, name) != 0)) { + if (strcmp(owner.short_name, name) != 0) { snprintf(name, sizeof(name), "%s_%02x%02x", owner.short_name, dmac[4], dmac[5]); } else { snprintf(name, sizeof(name), "Meshtastic_%02x%02x", dmac[4], dmac[5]); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index cf576e94f..209e0cbfa 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -141,11 +141,6 @@ NodeDB::NodeDB() if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) saveWhat |= SEGMENT_CHANNELS; - if (!devicestate.node_remote_hardware_pins) { - meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; - memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); - } - if (config.position.gps_enabled) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_enabled = 0; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 091586462..814686609 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -299,8 +299,8 @@ void AdminModule::handleGetModuleConfigResponse(const meshtastic_MeshPacket &mp, { // Skip if it's disabled or no pins are exposed if (!r->get_module_config_response.payload_variant.remote_hardware.enabled || - !r->get_module_config_response.payload_variant.remote_hardware.available_pins) { - LOG_DEBUG("Remote hardware module disabled or no vailable_pins. Skipping...\n"); + r->get_module_config_response.payload_variant.remote_hardware.available_pins_count == 0) { + LOG_DEBUG("Remote hardware module disabled or no available_pins. Skipping...\n"); return; } for (uint8_t i = 0; i < devicestate.node_remote_hardware_pins_count; i++) { diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 9f9ac5c24..74f3ca5a3 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -482,7 +482,12 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & auto &ch = channels.getByIndex(chIndex); - if (&mp_decoded.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } + + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); From 0bcc60d53561cb04b65deaada4f4483d1150c47c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 20 Jun 2024 16:14:55 -0500 Subject: [PATCH 0626/1377] BLE based logging (#4146) * WIP log characteristic * Bluetooth logging plumbing * Characteristic * Callback * Check for nullptr * Esp32 bluetooth impl * Formatting * Add thread name and log level * Add settings guard * Remove comments * Field name * Fixes esp32 * Open it up * Whoops * Move va_end past our logic --- src/BluetoothCommon.cpp | 4 +- src/BluetoothCommon.h | 4 +- src/RedirectablePrint.cpp | 39 +++++++++++++- src/main.cpp | 1 - src/nimble/NimbleBluetooth.cpp | 14 ++++- src/nimble/NimbleBluetooth.h | 1 + src/platform/nrf52/NRF52Bluetooth.cpp | 74 +++++++-------------------- src/platform/nrf52/NRF52Bluetooth.h | 2 +- 8 files changed, 76 insertions(+), 63 deletions(-) diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp index 53faae997..7ef1c39b4 100644 --- a/src/BluetoothCommon.cpp +++ b/src/BluetoothCommon.cpp @@ -10,4 +10,6 @@ const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, const uint8_t FROMRADIO_UUID_16[16u] = {0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c}; const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, - 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; \ No newline at end of file + 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; +const uint8_t LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa, + 0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c}; \ No newline at end of file diff --git a/src/BluetoothCommon.h b/src/BluetoothCommon.h index 586ffaa3c..5497e1d6d 100644 --- a/src/BluetoothCommon.h +++ b/src/BluetoothCommon.h @@ -11,10 +11,11 @@ #define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7" #define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002" #define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453" +#define LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2" // NRF52 wants these constants as byte arrays // Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER -extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[]; +extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[], LOGRADIO_UUID_16[]; /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level); @@ -27,4 +28,5 @@ class BluetoothApi virtual void clearBonds(); virtual bool isConnected(); virtual int getRssi() = 0; + virtual void sendLog(const char *logMessage); }; \ No newline at end of file diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index e09e5fe30..2110761ff 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -3,6 +3,7 @@ #include "RTC.h" #include "concurrency/OSThread.h" #include "configuration.h" +#include "main.h" #include #include #include @@ -166,9 +167,43 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) } #endif - va_end(arg); - isContinuationMessage = !hasNewline; + + if (config.bluetooth.device_logging_enabled) { + bool isBleConnected = false; +#ifdef ARCH_ESP32 + isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); +#elif defined(ARCH_NRF52) + isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); +#endif + if (isBleConnected) { + char *message; + size_t initialLen; + size_t len; + initialLen = strlen(format); + message = new char[initialLen + 1]; + len = vsnprintf(message, initialLen + 1, format, arg); + if (len > initialLen) { + delete[] message; + message = new char[len + 1]; + vsnprintf(message, len + 1, format, arg); + } + auto thread = concurrency::OSThread::currentThread; +#ifdef ARCH_ESP32 + if (thread) + nimbleBluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); + else + nimbleBluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); +#elif defined(ARCH_NRF52) + if (thread) + nrf52Bluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); + else + nrf52Bluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); +#endif + delete[] message; + } + } + va_end(arg); #ifdef HAS_FREE_RTOS xSemaphoreGive(inDebugPrint); #else diff --git a/src/main.cpp b/src/main.cpp index 931c2a3fd..725e3499d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,6 @@ NimbleBluetooth *nimbleBluetooth = nullptr; #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" NRF52Bluetooth *nrf52Bluetooth = nullptr; -; #endif #if HAS_WIFI diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 68aa9b465..6ed4a49ae 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -12,6 +12,7 @@ NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; +NimBLECharacteristic *logRadioCharacteristic; NimBLEServer *bleServer; static bool passkeyShowing; @@ -58,7 +59,6 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks { virtual void onRead(NimBLECharacteristic *pCharacteristic) { - LOG_INFO("From Radio onread\n"); uint8_t fromRadioBytes[meshtastic_FromRadio_size]; size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); @@ -180,6 +180,7 @@ void NimbleBluetooth::setupService() ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); + logRadioCharacteristic = bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); } else { ToRadioCharacteristic = bleService->createCharacteristic( TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); @@ -188,6 +189,9 @@ void NimbleBluetooth::setupService() fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + logRadioCharacteristic = + bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -236,6 +240,14 @@ void NimbleBluetooth::clearBonds() NimBLEDevice::deleteAllBonds(); } +void NimbleBluetooth::sendLog(const char *logMessage) +{ + if (!bleServer || !isConnected() || strlen(logMessage) > 512) { + return; + } + logRadioCharacteristic->notify((uint8_t *)logMessage, strlen(logMessage)); +} + void clearNVS() { NimBLEDevice::deleteAllBonds(); diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index d1e347830..39794779b 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -11,6 +11,7 @@ class NimbleBluetooth : BluetoothApi bool isActive(); bool isConnected(); int getRssi(); + void sendLog(const char *logMessage); private: void setupService(); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4c25f38ea..a14829285 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -7,16 +7,16 @@ #include "mesh/mesh-pb-constants.h" #include #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 toRadio = BLECharacteristic(BLEUuid(TORADIO_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 -static BLEDfu bledfu; // DFU software update helper service +static BLEDfu bledfu; // DFU software update helper service // 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)]; @@ -50,16 +50,14 @@ 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\n", central_name); } - /** * Callback invoked when a connection is dropped * @param conn_handle connection where this event happens @@ -70,15 +68,13 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason) // FIXME - we currently assume only one active connection LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } - void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { // Display the raw request packet LOG_INFO("CCCD Updated: %u\n", cccd_value); - // Check the characteristic this CCCD update is associated with in case // this handler is used for multiple CCCD records. - if (chr->uuid == fromNum.uuid) { + if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) { if (chr->notifyEnabled(conn_hdl)) { LOG_INFO("fromNum 'Notify' enabled\n"); } else { @@ -86,21 +82,17 @@ void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) } } } - 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 = 152.5 ms @@ -115,7 +107,6 @@ void startAdv(void) 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) { @@ -123,7 +114,6 @@ static void authorizeRead(uint16_t conn_hdl) 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 */ @@ -132,7 +122,6 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e 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); @@ -141,37 +130,22 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e } authorizeRead(conn_hdl); } - void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); } -/** - * client is starting read, pull the bytes from our API class - */ -void onFromNumAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_read_t *request) -{ - LOG_INFO("fromNumAuthorizeCb\n"); - - authorizeRead(conn_hdl); -} - 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( @@ -201,10 +175,15 @@ void setupMeshService(void) // 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_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 @@ -214,29 +193,23 @@ void NRF52Bluetooth::shutdown() } 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("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } - bool NRF52Bluetooth::isConnected() { return Bluefruit.connected(connectionHandle); } - int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } - void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -244,12 +217,10 @@ void NRF52Bluetooth::setup() 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 @@ -268,37 +239,29 @@ void NRF52Bluetooth::setup() } // 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); - bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.begin(); // Install the DFU helper - // Configure and Start the Device Information Service LOG_INFO("Configuring the Device Information Service\n"); bledis.setModel(optstr(HW_VERSION)); bledis.setFirmwareRev(optstr(APP_VERSION)); bledis.begin(); - // Start the BLE Battery Service and set it to 100% LOG_INFO("Configuring the Battery Service\n"); blebas.begin(); blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using // BLEService and BLECharacteristic classes LOG_INFO("Configuring the Mesh bluetooth service\n"); setupMeshService(); - // Setup the advertising packet(s) LOG_INFO("Setting up the advertising payload(s)\n"); startAdv(); - LOG_INFO("Advertising\n"); } - void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); @@ -306,34 +269,28 @@ void NRF52Bluetooth::resumeAdvertising() 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 NRF52Bluetooth::clearBonds() { LOG_INFO("Clearing bluetooth bonds!\n"); 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\n"); } - bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); screen->startBluetoothPinScreen(configuredPasskey); - if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { @@ -344,13 +301,18 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); return true; } - void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) LOG_INFO("BLE pairing success\n"); else LOG_INFO("BLE pairing failed\n"); - screen->stopBluetoothPinScreen(); +} + +void NRF52Bluetooth::sendLog(const char *logMessage) +{ + if (!isConnected() || strlen(logMessage) > 512) + return; + logRadio.notify(logMessage); } \ No newline at end of file diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 450af47f9..0d621dda2 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -13,10 +13,10 @@ class NRF52Bluetooth : BluetoothApi void clearBonds(); bool isConnected(); int getRssi(); + void sendLog(const char *logMessage); private: static void onConnectionSecured(uint16_t conn_handle); - void convertToUint8(uint8_t target[4], uint32_t source); static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request); static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status); }; \ No newline at end of file From 0dd363fa9842bdd00c703cd8edb1c9c2a7b66eea Mon Sep 17 00:00:00 2001 From: Mike G Date: Fri, 21 Jun 2024 04:01:36 +0300 Subject: [PATCH 0627/1377] Use `upload_protocol = esptool` as with the other heltec devices instead of `esp-builtin` (#4151) --- variants/heltec_wireless_tracker/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_wireless_tracker/platformio.ini b/variants/heltec_wireless_tracker/platformio.ini index 3259d563c..c7ecce8ea 100644 --- a/variants/heltec_wireless_tracker/platformio.ini +++ b/variants/heltec_wireless_tracker/platformio.ini @@ -1,7 +1,7 @@ [env:heltec-wireless-tracker] extends = esp32s3_base board = heltec_wireless_tracker -upload_protocol = esp-builtin +upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker @@ -11,4 +11,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX@^1.1.8 From 02d8715ca0de6a468dce1dc3b03fe458540df291 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 21 Jun 2024 17:25:54 -0500 Subject: [PATCH 0628/1377] Standardize lat/lon position logs (#4156) * Standardize lat/lon position logs * Missed sone and condensed logs --- src/GPSStatus.h | 2 +- src/gps/GPS.cpp | 18 +++++++++++++++++- src/gps/GPS.h | 2 ++ src/mesh/FloodingRouter.cpp | 2 +- src/mesh/MeshService.cpp | 4 ++-- src/mesh/NodeDB.cpp | 6 +++--- src/mesh/NodeDB.h | 4 ++-- src/modules/PositionModule.cpp | 4 ++-- 8 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/GPSStatus.h b/src/GPSStatus.h index 1245d5e5d..c2ab16c86 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -124,7 +124,7 @@ class GPSStatus : public Status if (isDirty) { if (hasLock) { // In debug logs, identify position by @timestamp:stage (stage 3 = notify) - LOG_DEBUG("New GPS pos@%x:3 lat=%f, lon=%f, alt=%d, pdop=%.2f, track=%.2f, speed=%.2f, sats=%d\n", p.timestamp, + LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d\n", p.timestamp, p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5, p.ground_speed * 1e-2, p.sats_in_view); } else { diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 8d46742ba..5efe96251 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -778,6 +778,22 @@ GPS::~GPS() notifyGPSSleepObserver.observe(¬ifyGPSSleep); } +const char *GPS::powerStateToString() +{ + switch (powerState) { + case GPS_OFF: + return "OFF"; + case GPS_IDLE: + return "IDLE"; + case GPS_STANDBY: + return "STANDBY"; + case GPS_ACTIVE: + return "ACTIVE"; + default: + return "UNKNOWN"; + } +} + void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) { // Record the current powerState @@ -792,7 +808,7 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) else powerState = GPS_OFF; - LOG_DEBUG("GPS::powerState=%d\n", powerState); + LOG_DEBUG("GPS::powerState=%s\n", powerStateToString()); // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out. if (!on && powerState == GPS_IDLE) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 55bd42d0f..6afbd4fab 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -289,6 +289,8 @@ class GPS : private concurrency::OSThread // delay counter to allow more sats before fixed position stops GPS thread uint8_t fixeddelayCtr = 0; + const char *powerStateToString(); + protected: GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN; }; diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index dd547a6f1..7866fa444 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -20,7 +20,7 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p) bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record - printPacket("Ignoring incoming msg, because we've already seen it", p); + printPacket("Ignoring incoming msg we've already seen", p); if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 2cfb4843c..c92b89eb4 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -373,8 +373,8 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) pos.time = getValidTime(RTCQualityFromNet); // In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB) - LOG_DEBUG("onGPSChanged() pos@%x, time=%u, lat=%d, lon=%d, alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, - pos.longitude_i, pos.altitude); + LOG_DEBUG("onGPSChanged() pos@%x time=%u lat=%d lon=%d alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, pos.longitude_i, + pos.altitude); // Update our current position in the local DB nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 209e0cbfa..31fb983f4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -821,8 +821,8 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou if (src == RX_SRC_LOCAL) { // Local packet, fully authoritative - LOG_INFO("updatePosition LOCAL pos@%x, time=%u, latI=%d, lonI=%d, alt=%d\n", p.timestamp, p.time, p.latitude_i, - p.longitude_i, p.altitude); + LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d\n", p.timestamp, p.time, p.latitude_i, p.longitude_i, + p.altitude); setLocalPosition(p); info->position = TypeConversions::ConvertToPositionLite(p); @@ -837,7 +837,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou // recorded based on the packet rxTime // // FIXME perhaps handle RX_SRC_USER separately? - LOG_INFO("updatePosition REMOTE node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); + LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); // First, back up fields that we want to protect from overwrite uint32_t tmp_time = info->position.time; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e9e36cc61..61bf90d4d 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -155,8 +155,8 @@ class NodeDB localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; return; } - LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%u, timestamp=%u\n", position.latitude_i, - position.longitude_i, position.time, position.timestamp); + LOG_DEBUG("Setting local position: lat=%i lon=%i time=%u timestamp=%u\n", position.latitude_i, position.longitude_i, + position.time, position.timestamp); localPosition = position; } diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 49f2b808b..61616841b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -73,7 +73,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } // Log packet size and data fields - LOG_DEBUG("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d " + LOG_DEBUG("POSITION node=%08x l=%d lat=%d lon=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d " "time=%d\n", getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae, p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp, @@ -219,7 +219,7 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_INFO("Providing time to mesh %u\n", p.time); } - LOG_INFO("Position reply: time=%i, latI=%i, lonI=%i\n", p.time, p.latitude_i, p.longitude_i); + LOG_INFO("Position reply: time=%i lat=%i lon=%i\n", p.time, p.latitude_i, p.longitude_i); // TAK Tracker devices should send their position in a TAK packet over the ATAK port if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) From f8db38cf992efd2b44bcd2804a6f28684239b0ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:02:16 -0500 Subject: [PATCH 0629/1377] [create-pull-request] automated change (#4157) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 13 ++- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/powermon.pb.cpp | 13 +++ src/mesh/generated/meshtastic/powermon.pb.h | 85 +++++++++++++++++++ 6 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 src/mesh/generated/meshtastic/powermon.pb.cpp create mode 100644 src/mesh/generated/meshtastic/powermon.pb.h diff --git a/protobufs b/protobufs index 1c3029f28..4da558d0f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1c3029f2868e5fc49809fd378f6c0c66aee0eaf4 +Subproject commit 4da558d0f73c46ef91b74431facee73c09affbfc diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 5a78f1366..2da4b86e6 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -372,6 +372,9 @@ typedef struct _meshtastic_Config_PowerConfig { uint32_t min_wake_secs; /* I2C address of INA_2XX to use for reading device battery voltage */ uint8_t device_battery_ina_address; + /* If non-zero, we want powermon log outputs. With the particular (bitfield) sources enabled. + Note: we picked an ID of 32 so that lower more efficient IDs can be used for more frequently used options. */ + uint64_t powermon_enables; } meshtastic_Config_PowerConfig; typedef struct _meshtastic_Config_NetworkConfig_IpV4Config { @@ -612,7 +615,7 @@ extern "C" { #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} -#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} @@ -621,7 +624,7 @@ extern "C" { #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} -#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} @@ -662,6 +665,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_ls_secs_tag 7 #define meshtastic_Config_PowerConfig_min_wake_secs_tag 8 #define meshtastic_Config_PowerConfig_device_battery_ina_address_tag 9 +#define meshtastic_Config_PowerConfig_powermon_enables_tag 32 #define meshtastic_Config_NetworkConfig_IpV4Config_ip_tag 1 #define meshtastic_Config_NetworkConfig_IpV4Config_gateway_tag 2 #define meshtastic_Config_NetworkConfig_IpV4Config_subnet_tag 3 @@ -773,7 +777,8 @@ X(a, STATIC, SINGULAR, UINT32, wait_bluetooth_secs, 4) \ X(a, STATIC, SINGULAR, UINT32, sds_secs, 6) \ X(a, STATIC, SINGULAR, UINT32, ls_secs, 7) \ X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 8) \ -X(a, STATIC, SINGULAR, UINT32, device_battery_ina_address, 9) +X(a, STATIC, SINGULAR, UINT32, device_battery_ina_address, 9) \ +X(a, STATIC, SINGULAR, UINT64, powermon_enables, 32) #define meshtastic_Config_PowerConfig_CALLBACK NULL #define meshtastic_Config_PowerConfig_DEFAULT NULL @@ -871,7 +876,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 -#define meshtastic_Config_PowerConfig_size 40 +#define meshtastic_Config_PowerConfig_size 52 #define meshtastic_Config_size 199 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 5e291ee94..100972c1e 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -308,7 +308,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3372 +#define meshtastic_OEMStore_size 3384 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 96a9976f0..c1d2a4ae3 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 541 +#define meshtastic_LocalConfig_size 553 #define meshtastic_LocalModuleConfig_size 685 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/powermon.pb.cpp b/src/mesh/generated/meshtastic/powermon.pb.cpp new file mode 100644 index 000000000..4d798e9a3 --- /dev/null +++ b/src/mesh/generated/meshtastic/powermon.pb.cpp @@ -0,0 +1,13 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "meshtastic/powermon.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(meshtastic_PowerMon, meshtastic_PowerMon, AUTO) + + + + diff --git a/src/mesh/generated/meshtastic/powermon.pb.h b/src/mesh/generated/meshtastic/powermon.pb.h new file mode 100644 index 000000000..88e80bb55 --- /dev/null +++ b/src/mesh/generated/meshtastic/powermon.pb.h @@ -0,0 +1,85 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED +#define PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* Any significant power changing event in meshtastic should be tagged with a powermon state transition. +If you are making new meshtastic features feel free to add new entries at the end of this definition. */ +typedef enum _meshtastic_PowerMon_State { + meshtastic_PowerMon_State_None = 0, + meshtastic_PowerMon_State_CPU_DeepSleep = 1, + meshtastic_PowerMon_State_CPU_LightSleep = 2, + /* The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only +occasionally. In cases where that rail has multiple devices on it we usually want to have logging on +the state of that rail as an independent record. +For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen. + +The log messages will be short and complete (see PowerMon.Event in the protobufs for details). +something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states. +(We use a bitmask for states so that if a log message gets lost it won't be fatal) */ + meshtastic_PowerMon_State_Vext1_On = 4, + meshtastic_PowerMon_State_Lora_RXOn = 8, + meshtastic_PowerMon_State_Lora_TXOn = 16, + meshtastic_PowerMon_State_Lora_RXActive = 32, + meshtastic_PowerMon_State_BT_On = 64, + meshtastic_PowerMon_State_LED_On = 128, + meshtastic_PowerMon_State_Screen_On = 256, + meshtastic_PowerMon_State_Screen_Drawing = 512, + meshtastic_PowerMon_State_Wifi_On = 1024, + /* GPS is actively trying to find our location +See GPSPowerState for more details */ + meshtastic_PowerMon_State_GPS_Active = 2048 +} meshtastic_PowerMon_State; + +/* Struct definitions */ +/* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). +But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */ +typedef struct _meshtastic_PowerMon { + char dummy_field; +} meshtastic_PowerMon; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _meshtastic_PowerMon_State_MIN meshtastic_PowerMon_State_None +#define _meshtastic_PowerMon_State_MAX meshtastic_PowerMon_State_GPS_Active +#define _meshtastic_PowerMon_State_ARRAYSIZE ((meshtastic_PowerMon_State)(meshtastic_PowerMon_State_GPS_Active+1)) + + + +/* Initializer values for message structs */ +#define meshtastic_PowerMon_init_default {0} +#define meshtastic_PowerMon_init_zero {0} + +/* Field tags (for use in manual encoding/decoding) */ + +/* Struct field encoding specification for nanopb */ +#define meshtastic_PowerMon_FIELDLIST(X, a) \ + +#define meshtastic_PowerMon_CALLBACK NULL +#define meshtastic_PowerMon_DEFAULT NULL + +extern const pb_msgdesc_t meshtastic_PowerMon_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define meshtastic_PowerMon_fields &meshtastic_PowerMon_msg + +/* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerMon_size +#define meshtastic_PowerMon_size 0 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif From d32cdecc06b973d58f87eb34f75399774592a0ce Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 22 Jun 2024 07:00:48 -0500 Subject: [PATCH 0630/1377] Pause BLE logging during want_config flow (#4162) --- src/RedirectablePrint.cpp | 2 +- src/main.cpp | 1 + src/main.h | 2 ++ src/mesh/PhoneAPI.cpp | 4 ++++ src/mesh/PhoneAPI.h | 2 -- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 2110761ff..b77720d85 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -169,7 +169,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) isContinuationMessage = !hasNewline; - if (config.bluetooth.device_logging_enabled) { + if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); diff --git a/src/main.cpp b/src/main.cpp index 725e3499d..9ec4fa82d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -154,6 +154,7 @@ bool isVibrating = false; bool eink_found = true; uint32_t serialSinceMsec; +bool pauseBluetoothLogging = false; bool pmu_found; diff --git a/src/main.h b/src/main.h index 2ef7edb3a..ea2d80f94 100644 --- a/src/main.h +++ b/src/main.h @@ -85,6 +85,8 @@ extern uint32_t serialSinceMsec; // This will suppress the current delay and instead try to run ASAP. extern bool runASAP; +extern bool pauseBluetoothLogging; + void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearBonds(), enterDfuMode(); meshtastic_DeviceMetadata getDeviceMetadata(); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 26d0d9525..404666877 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -46,6 +46,7 @@ void PhoneAPI::handleStartConfig() // even if we were already connected - restart our state machine state = STATE_SEND_MY_INFO; + pauseBluetoothLogging = true; LOG_INFO("Starting API client config\n"); nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos @@ -352,9 +353,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.config_complete_id = config_nonce; config_nonce = 0; state = STATE_SEND_PACKETS; + pauseBluetoothLogging = false; break; case STATE_SEND_PACKETS: + pauseBluetoothLogging = false; // Do we have a message from the mesh or packet from the local device? LOG_INFO("getFromRadio=STATE_SEND_PACKETS\n"); if (queueStatusPacketForPhone) { @@ -398,6 +401,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) void PhoneAPI::handleDisconnect() { + pauseBluetoothLogging = false; LOG_INFO("PhoneAPI disconnect\n"); } diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 49bf0e292..668f9c1f3 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -98,8 +98,6 @@ class PhoneAPI bool isConnected() { return state != STATE_SEND_NOTHING; } - void setInitialState() { state = STATE_SEND_MY_INFO; } - protected: /// Our fromradio packet while it is being assembled meshtastic_FromRadio fromRadioScratch = {}; From eb6bd3a06fb74f47dd335e0812bd35ed76f837ae Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 22 Jun 2024 08:49:55 -0500 Subject: [PATCH 0631/1377] Update NimBLE to 1.4.2 (#4163) --- arch/esp32/esp32.ini | 2 +- src/nimble/NimbleBluetooth.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index f3eb0cbc0..58c1302da 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -44,7 +44,7 @@ lib_deps = ${networking_base.lib_deps} ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 - h2zero/NimBLE-Arduino@^1.4.1 + h2zero/NimBLE-Arduino@^1.4.2 https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 6ed4a49ae..b70420c8a 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -245,7 +245,7 @@ void NimbleBluetooth::sendLog(const char *logMessage) if (!bleServer || !isConnected() || strlen(logMessage) > 512) { return; } - logRadioCharacteristic->notify((uint8_t *)logMessage, strlen(logMessage)); + logRadioCharacteristic->notify(reinterpret_cast(logMessage), strlen(logMessage)); } void clearNVS() From 8078e03f5fea2af1e6dbc217c9fac5d3eb4d1029 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:13:59 +0200 Subject: [PATCH 0632/1377] Implement replies for all telemetry types based on variant tag (#4164) * Implement replies for all telemetry types based on variant tag * Remove check for `ignoreRequest`: modules can set this, don't need to check --------- Co-authored-by: Ben Meadors --- src/modules/Telemetry/AirQualityTelemetry.cpp | 105 ++++++++++++------ src/modules/Telemetry/AirQualityTelemetry.h | 5 + src/modules/Telemetry/DeviceTelemetry.cpp | 29 +++-- .../Telemetry/EnvironmentTelemetry.cpp | 84 +++++++++----- src/modules/Telemetry/EnvironmentTelemetry.h | 5 + src/modules/Telemetry/PowerTelemetry.cpp | 77 +++++++++---- src/modules/Telemetry/PowerTelemetry.h | 5 + src/modules/esp32/PaxcounterModule.cpp | 6 +- 8 files changed, 222 insertions(+), 94 deletions(-) diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 4f5fbcd13..ba043feab 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -85,53 +85,90 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack return false; // Let others look at this message also if they want } -bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) { if (!aqi.read(&data)) { LOG_WARN("Skipping send measurements. Could not read AQIn\n"); return false; } - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - m.time = getTime(); - m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; - m.variant.air_quality_metrics.pm10_standard = data.pm10_standard; - m.variant.air_quality_metrics.pm25_standard = data.pm25_standard; - m.variant.air_quality_metrics.pm100_standard = data.pm100_standard; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; + m->variant.air_quality_metrics.pm10_standard = data.pm10_standard; + m->variant.air_quality_metrics.pm25_standard = data.pm25_standard; + m->variant.air_quality_metrics.pm100_standard = data.pm100_standard; - m.variant.air_quality_metrics.pm10_environmental = data.pm10_env; - m.variant.air_quality_metrics.pm25_environmental = data.pm25_env; - m.variant.air_quality_metrics.pm100_environmental = data.pm100_env; + m->variant.air_quality_metrics.pm10_environmental = data.pm10_env; + m->variant.air_quality_metrics.pm25_environmental = data.pm25_env; + m->variant.air_quality_metrics.pm100_environmental = data.pm100_env; LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i\n", - m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, - m.variant.air_quality_metrics.pm100_standard); + m->variant.air_quality_metrics.pm10_standard, m->variant.air_quality_metrics.pm25_standard, + m->variant.air_quality_metrics.pm100_standard); LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i\n", - m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental, - m.variant.air_quality_metrics.pm100_environmental); + m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental, + m->variant.air_quality_metrics.pm100_environmental); - meshtastic_MeshPacket *p = allocDataProtobuf(m); - p->to = dest; - p->decoded.want_response = false; - if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) - p->priority = meshtastic_MeshPacket_Priority_RELIABLE; - else - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - - // release previous packet before occupying a new spot - if (lastMeasurementPacket != nullptr) - packetPool.release(lastMeasurementPacket); - - lastMeasurementPacket = packetPool.allocCopy(*p); - if (phoneOnly) { - LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); - } else { - LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); - } return true; } +meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding AirQualityTelemetry module!\n"); + return NULL; + } + // Check for a request for air quality metrics + if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getAirQualityTelemetry(&m)) { + LOG_INFO("Air quality telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + return NULL; +} + +bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +{ + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getAirQualityTelemetry(&m)) { + meshtastic_MeshPacket *p = allocDataProtobuf(m); + p->to = dest; + p->decoded.want_response = false; + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + else + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + + // release previous packet before occupying a new spot + if (lastMeasurementPacket != nullptr) + packetPool.release(lastMeasurementPacket); + + lastMeasurementPacket = packetPool.allocCopy(*p); + if (phoneOnly) { + LOG_INFO("Sending packet to phone\n"); + service.sendToPhone(p); + } else { + LOG_INFO("Sending packet to mesh\n"); + service.sendToMesh(p, RX_SRC_LOCAL, true); + } + return true; + } + + return false; +} + #endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index eb0355001..9d09078b1 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -26,6 +26,11 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; + /** Called to get current Air Quality data + @return true if it contains valid data + */ + bool getAirQualityTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index b64e8d113..9cc4bf6ea 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -52,14 +52,27 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() { - if (ignoreRequest) { - return NULL; + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding DeviceTelemetry module!\n"); + return NULL; + } + // Check for a request for device metrics + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + LOG_INFO("Device telemetry replying to request\n"); + + meshtastic_Telemetry telemetry = getDeviceTelemetry(); + return allocDataProtobuf(telemetry); + } } - - LOG_INFO("Device telemetry replying to request\n"); - - meshtastic_Telemetry telemetry = getDeviceTelemetry(); - return allocDataProtobuf(telemetry); + return NULL; } meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() @@ -104,4 +117,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) service.sendToMesh(p, RX_SRC_LOCAL, true); } return true; -} +} \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ff3202067..8f899401b 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -270,98 +270,129 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac return false; // Let others look at this message also if they want } -bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m) { - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; bool valid = true; bool hasSensor = false; - m.time = getTime(); - m.which_variant = meshtastic_Telemetry_environment_metrics_tag; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_environment_metrics_tag; if (dfRobotLarkSensor.hasSensor()) { - valid = valid && dfRobotLarkSensor.getMetrics(&m); + valid = valid && dfRobotLarkSensor.getMetrics(m); hasSensor = true; } if (sht31Sensor.hasSensor()) { - valid = valid && sht31Sensor.getMetrics(&m); + valid = valid && sht31Sensor.getMetrics(m); hasSensor = true; } if (lps22hbSensor.hasSensor()) { - valid = valid && lps22hbSensor.getMetrics(&m); + valid = valid && lps22hbSensor.getMetrics(m); hasSensor = true; } if (shtc3Sensor.hasSensor()) { - valid = valid && shtc3Sensor.getMetrics(&m); + valid = valid && shtc3Sensor.getMetrics(m); hasSensor = true; } if (bmp085Sensor.hasSensor()) { - valid = valid && bmp085Sensor.getMetrics(&m); + valid = valid && bmp085Sensor.getMetrics(m); hasSensor = true; } if (bmp280Sensor.hasSensor()) { - valid = valid && bmp280Sensor.getMetrics(&m); + valid = valid && bmp280Sensor.getMetrics(m); hasSensor = true; } if (bme280Sensor.hasSensor()) { - valid = valid && bme280Sensor.getMetrics(&m); + valid = valid && bme280Sensor.getMetrics(m); hasSensor = true; } if (bme680Sensor.hasSensor()) { - valid = valid && bme680Sensor.getMetrics(&m); + valid = valid && bme680Sensor.getMetrics(m); hasSensor = true; } if (mcp9808Sensor.hasSensor()) { - valid = valid && mcp9808Sensor.getMetrics(&m); + valid = valid && mcp9808Sensor.getMetrics(m); hasSensor = true; } if (ina219Sensor.hasSensor()) { - valid = valid && ina219Sensor.getMetrics(&m); + valid = valid && ina219Sensor.getMetrics(m); hasSensor = true; } if (ina260Sensor.hasSensor()) { - valid = valid && ina260Sensor.getMetrics(&m); + valid = valid && ina260Sensor.getMetrics(m); hasSensor = true; } if (veml7700Sensor.hasSensor()) { - valid = valid && veml7700Sensor.getMetrics(&m); + valid = valid && veml7700Sensor.getMetrics(m); hasSensor = true; } if (tsl2591Sensor.hasSensor()) { - valid = valid && tsl2591Sensor.getMetrics(&m); + valid = valid && tsl2591Sensor.getMetrics(m); hasSensor = true; } if (opt3001Sensor.hasSensor()) { - valid = valid && opt3001Sensor.getMetrics(&m); + valid = valid && opt3001Sensor.getMetrics(m); hasSensor = true; } if (mlx90632Sensor.hasSensor()) { - valid = valid && mlx90632Sensor.getMetrics(&m); + valid = valid && mlx90632Sensor.getMetrics(m); hasSensor = true; } if (rcwl9620Sensor.hasSensor()) { - valid = valid && rcwl9620Sensor.getMetrics(&m); + valid = valid && rcwl9620Sensor.getMetrics(m); hasSensor = true; } if (nau7802Sensor.hasSensor()) { - valid = valid && nau7802Sensor.getMetrics(&m); + valid = valid && nau7802Sensor.getMetrics(m); hasSensor = true; } if (aht10Sensor.hasSensor()) { if (!bmp280Sensor.hasSensor()) { - valid = valid && aht10Sensor.getMetrics(&m); + valid = valid && aht10Sensor.getMetrics(m); hasSensor = true; } else { // prefer bmp280 temp if both sensors are present, fetch only humidity meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n"); aht10Sensor.getMetrics(&m_ahtx); - m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; + m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } - valid = valid && hasSensor; + return valid && hasSensor; +} - if (valid) { +meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding EnvironmentTelemetry module!\n"); + return NULL; + } + // Check for a request for environment metrics + if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getEnvironmentTelemetry(&m)) { + LOG_INFO("Environment telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + return NULL; +} + +bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +{ + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getEnvironmentTelemetry(&m)) { LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, @@ -399,8 +430,9 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) setIntervalFromNow(5000); } } + return true; } - return valid; + return false; } AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp, diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index ca150347e..ced617c2f 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -32,6 +32,11 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; + /** Called to get current Environment telemetry data + @return true if it contains valid data + */ + bool getEnvironmentTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 826de8a4a..cb864f4f3 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -163,29 +163,63 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m return false; // Let others look at this message also if they want } +bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) +{ + bool valid = false; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_power_metrics_tag; + + m->variant.power_metrics.ch1_voltage = 0; + m->variant.power_metrics.ch1_current = 0; + m->variant.power_metrics.ch2_voltage = 0; + m->variant.power_metrics.ch2_current = 0; + m->variant.power_metrics.ch3_voltage = 0; + m->variant.power_metrics.ch3_current = 0; +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) + if (ina219Sensor.hasSensor()) + valid = ina219Sensor.getMetrics(m); + if (ina260Sensor.hasSensor()) + valid = ina260Sensor.getMetrics(m); + if (ina3221Sensor.hasSensor()) + valid = ina3221Sensor.getMetrics(m); +#endif + + return valid; +} + +meshtastic_MeshPacket *PowerTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding PowerTelemetry module!\n"); + return NULL; + } + // Check for a request for power metrics + if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getPowerTelemetry(&m)) { + LOG_INFO("Power telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + + return NULL; +} + bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - bool valid = false; - m.time = getTime(); - m.which_variant = meshtastic_Telemetry_power_metrics_tag; - - m.variant.power_metrics.ch1_voltage = 0; - m.variant.power_metrics.ch1_current = 0; - m.variant.power_metrics.ch2_voltage = 0; - m.variant.power_metrics.ch2_current = 0; - m.variant.power_metrics.ch3_voltage = 0; - m.variant.power_metrics.ch3_current = 0; -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) - if (ina219Sensor.hasSensor()) - valid = ina219Sensor.getMetrics(&m); - if (ina260Sensor.hasSensor()) - valid = ina260Sensor.getMetrics(&m); - if (ina3221Sensor.hasSensor()) - valid = ina3221Sensor.getMetrics(&m); -#endif - - if (valid) { + if (getPowerTelemetry(&m)) { LOG_INFO("(Sending): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " "ch3_voltage=%f, ch3_current=%f\n", m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage, @@ -218,8 +252,9 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) setIntervalFromNow(5000); } } + return true; } - return valid; + return false; } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index 3d6b686f2..1b68847db 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -33,6 +33,11 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; + /** Called to get current Power telemetry data + @return true if it contains valid data + */ + bool getPowerTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index e6712871d..0bae515df 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -66,10 +66,6 @@ bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m meshtastic_MeshPacket *PaxcounterModule::allocReply() { - if (ignoreRequest) { - return NULL; - } - meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; pl.ble = count_from_libpax.ble_count; @@ -131,4 +127,4 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state } #endif // HAS_SCREEN -#endif +#endif \ No newline at end of file From 2e0d96cecebc9b2aa09238ed6d26bb1a66c1c5bb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 23 Jun 2024 07:54:13 -0500 Subject: [PATCH 0633/1377] Esptool is better --- variants/tlora_t3s3_v1/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/tlora_t3s3_v1/platformio.ini b/variants/tlora_t3s3_v1/platformio.ini index 002b2f224..0a5797280 100644 --- a/variants/tlora_t3s3_v1/platformio.ini +++ b/variants/tlora_t3s3_v1/platformio.ini @@ -2,7 +2,7 @@ extends = esp32s3_base board = tlora-t3s3-v1 board_check = true -upload_protocol = esp-builtin +upload_protocol = esptool build_flags = ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/tlora_t3s3_v1 From f5098dc6d82e1a582a810e225d4f0aa1ddee8617 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 23 Jun 2024 14:47:25 -0500 Subject: [PATCH 0634/1377] Explicitly set characteristic --- src/nimble/NimbleBluetooth.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index b70420c8a..48f945b0a 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -180,7 +180,8 @@ void NimbleBluetooth::setupService() ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); - logRadioCharacteristic = bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); + logRadioCharacteristic = + bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U); } else { ToRadioCharacteristic = bleService->createCharacteristic( TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); @@ -189,9 +190,10 @@ void NimbleBluetooth::setupService() fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); - logRadioCharacteristic = - bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + logRadioCharacteristic = bleService->createCharacteristic( + LOGRADIO_UUID, + NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U); + logRadioCharacteristic->setValue("Init"); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -245,7 +247,7 @@ void NimbleBluetooth::sendLog(const char *logMessage) if (!bleServer || !isConnected() || strlen(logMessage) > 512) { return; } - logRadioCharacteristic->notify(reinterpret_cast(logMessage), strlen(logMessage)); + logRadioCharacteristic->notify(reinterpret_cast(logMessage), strlen(logMessage), true); } void clearNVS() From 23ac6b65141914496ba108580cf0377f538db2e4 Mon Sep 17 00:00:00 2001 From: Warren Guy <5602790+warrenguy@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:40:13 +0100 Subject: [PATCH 0635/1377] fix INA3221 sensor (#4168) - pass wire to begin() - remove redundant setAddr() (already set in header) --- src/modules/Telemetry/Sensor/INA3221Sensor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index ea2cb4ea8..edd29682e 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -16,8 +16,7 @@ int32_t INA3221Sensor::runOnce() return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } if (!status) { - ina3221.setAddr(INA3221_ADDR42_SDA); // i2c address 0x42 - ina3221.begin(); + ina3221.begin(nodeTelemetrySensorsMap[sensorType].second); ina3221.setShuntRes(100, 100, 100); // 0.1 Ohm shunt resistors status = true; } else { From 64531fa1ae057f4128a65441766ad9eedaecb6b7 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 24 Jun 2024 19:04:46 +1200 Subject: [PATCH 0636/1377] Show compass on waypoint frame; clear when waypoint deleted (#4116) * Clear expired or deleted waypoint frame * Return 0 to CallbackObserver * Add a missing comment * Draw compass for waypoint frame * Display our own waypoints --- src/graphics/Screen.cpp | 251 +++++++++++++++++++++++++++++----------- src/graphics/Screen.h | 3 + 2 files changed, 186 insertions(+), 68 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 60168cffc..eb92d824e 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -43,6 +43,7 @@ along with this program. If not, see . #include "meshUtils.h" #include "modules/ExternalNotificationModule.h" #include "modules/TextMessageModule.h" +#include "modules/WaypointModule.h" #include "sleep.h" #include "target_specific.h" @@ -59,6 +60,9 @@ along with this program. If not, see . #include "platform/portduino/PortduinoGlue.h" #endif +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) + using namespace meshtastic; /** @todo remove */ namespace graphics @@ -446,6 +450,37 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) return packet->from != 0 && !moduleConfig.store_forward.enabled; } +// Determine whether the waypoint frame should be drawn (waypoint deleted? expired?) +static bool shouldDrawWaypoint(const meshtastic_MeshPacket *packet) +{ +#if !MESHTASTIC_EXCLUDE_WAYPOINT + // If no waypoint to show + if (!devicestate.has_rx_waypoint) + return false; + + // Decode the message, to find the expiration time (is waypoint still valid) + // This handles "deletion" as well as expiration + meshtastic_Waypoint wp; + memset(&wp, 0, sizeof(wp)); + if (pb_decode_from_bytes(packet->decoded.payload.bytes, packet->decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { + // Valid waypoint + if (wp.expire > getTime()) + return devicestate.has_rx_waypoint = true; + + // Expired, or deleted + else + return devicestate.has_rx_waypoint = false; + } + + // If decoding failed + LOG_ERROR("Failed to decode waypoint\n"); + devicestate.has_rx_waypoint = false; + return false; +#else + return false; +#endif +} + // Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) { @@ -1091,43 +1126,6 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state #endif } -/// Draw the last waypoint we received -static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - static char tempBuf[237]; - - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); - - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } - - uint32_t seconds = sinceReceived(&mp); - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; - - if (config.display.heading_bold) { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - - display->setColor(WHITE); - meshtastic_Waypoint scratch; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { - snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); - display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); - } -} - /// Draw a series of fields in a column, wrapping to multiple columns if needed static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) { @@ -1453,8 +1451,35 @@ static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t com drawLine(display, N1, N4); } -/// Convert an integer GPS coords to a floating point -#define DegD(i) (i * 1e-7) +// Get a string representation of the time passed since something happened +static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) +{ + // Use an absolute timestamp in some cases. + // Particularly useful with E-Ink displays. Static UI, fewer refreshes. + uint8_t timestampHours, timestampMinutes; + int32_t daysAgo; + bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); + + if (agoSecs < 120) // last 2 mins? + snprintf(timeStr, maxLength, "%u seconds ago", agoSecs); + // -- if suitable for timestamp -- + else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes + snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); + else if (useTimestamp && daysAgo == 0) // Today + snprintf(timeStr, maxLength, "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); + else if (useTimestamp && daysAgo == 1) // Yesterday + snprintf(timeStr, maxLength, "Seen yesterday"); + else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) + snprintf(timeStr, maxLength, "%li days ago", (long)daysAgo); + // -- if using time delta instead -- + else if (agoSecs < 120 * 60) // last 2 hrs + snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60); + // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. + else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) + snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60); + else + snprintf(timeStr, maxLength, "unknown age"); +} static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -1494,34 +1519,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); } - uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; - - // Use an absolute timestamp in some cases. - // Particularly useful with E-Ink displays. Static UI, fewer refreshes. - uint8_t timestampHours, timestampMinutes; - int32_t daysAgo; - bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); - - if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); - // -- if suitable for timestamp -- - else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); - else if (useTimestamp && daysAgo == 0) // Today - snprintf(lastStr, sizeof(lastStr), "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); - else if (useTimestamp && daysAgo == 1) // Yesterday - snprintf(lastStr, sizeof(lastStr), "Seen yesterday"); - else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) - snprintf(lastStr, sizeof(lastStr), "%li days ago", (long)daysAgo); - // -- if using time delta instead -- - else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); - // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. - else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) - snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); - else - snprintf(lastStr, sizeof(lastStr), "unknown age"); + getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr)); static char distStr[20]; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { @@ -1596,6 +1595,112 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ drawColumns(display, x, y, fields); } +/// Draw the last waypoint we received +static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // Prepare to draw + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + // Handle inverted display + // Unsure of expected behavior: for now, copy drawNodeInfo + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + + // Decode the waypoint + meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + meshtastic_Waypoint wp; + memset(&wp, 0, sizeof(wp)); + if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { + // This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case + display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint"); + devicestate.has_rx_waypoint = false; + return; + } + + // Get timestamp info. Will pass as a field to drawColumns + static char lastStr[20]; + getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr)); + + // Will contain distance information, passed as a field to drawColumns + static char distStr[20]; + + // Get our node, to use our own position + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + + // Text fields to draw (left of compass) + // Last element must be NULL. This signals the end of the char*[] to drawColumns + const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL}; + + // Co-ordinates for the center of the compass/circle + int16_t compassX = 0, compassY = 0; + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassY = y + SCREEN_HEIGHT / 2; + } else { + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; + } + + // If our node has a position: + if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) { + const meshtastic_PositionLite &op = ourNode->position; + float myHeading; + if (screen->hasHeading()) + myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians + else + myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + drawCompassNorth(display, compassX, compassY, myHeading); + + // Distance to Waypoint + float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + if (d < (2 * MILES_TO_FEET)) + snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); + else + snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); + } else { + if (d < 2000) + snprintf(distStr, sizeof(distStr), "%.0f m", d); + else + snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); + } + + // Compass bearing to waypoint + float bearingToOther = + GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i)); + // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly + // If the top of the compass is not a static north we need adjust bearingToOther based on heading + if (!config.display.compass_north_top) + bearingToOther -= myHeading; + drawNodeHeading(display, compassX, compassY, bearingToOther); + } + + // If our node doesn't have position + else { + // ? in the compass + display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); + + // ? in the distance field + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + strncpy(distStr, "? mi", sizeof(distStr)); + else + strncpy(distStr, "? km", sizeof(distStr)); + } + + // Undo color-inversion, if set prior to drawing header + // Unsure of expected behavior? For now: copy drawNodeInfo + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->setColor(BLACK); + } + + // Draw compass circle + display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + + // Must be after distStr is populated + drawColumns(display, x, y, fields); +} + Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { @@ -1806,6 +1911,8 @@ void Screen::setup() textMessageObserver.observe(textMessageModule); if (inputBroker) inputObserver.observe(inputBroker); + if (waypointModule) + waypointObserver.observe(waypointModule); // Modules can notify screen about refresh MeshModule::observeUIEvents(&uiFrameEventObserver); @@ -2133,8 +2240,9 @@ void Screen::setFrames() if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { normalFrames[numframes++] = drawTextMessageFrame; } - // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules - if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) { + + // If we have a waypoint (not expired, not deleted) + if (devicestate.has_rx_waypoint && shouldDrawWaypoint(&devicestate.rx_waypoint)) { normalFrames[numframes++] = drawWaypointFrame; } @@ -2736,6 +2844,13 @@ int Screen::handleInputEvent(const InputEvent *event) return 0; } +int Screen::handleWaypoint(const meshtastic_MeshPacket *arg) +{ + // TODO: move to appropriate frame when redrawing + setFrames(); + return 0; +} + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index f4d719715..b1bbffc3b 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -126,6 +126,8 @@ class Screen : public concurrency::OSThread CallbackObserver(this, &Screen::handleStatusUpdate); CallbackObserver textMessageObserver = CallbackObserver(this, &Screen::handleTextMessage); + CallbackObserver waypointObserver = + CallbackObserver(this, &Screen::handleWaypoint); CallbackObserver uiFrameEventObserver = CallbackObserver(this, &Screen::handleUIFrameEvent); CallbackObserver inputObserver = @@ -336,6 +338,7 @@ class Screen : public concurrency::OSThread int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); + int handleWaypoint(const meshtastic_MeshPacket *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(bool forceUiUpdate = false); From 58c00d044776d0d9fd5b71a78ce7b2dfbec68cd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 08:01:40 -0500 Subject: [PATCH 0637/1377] [create-pull-request] automated change (#4171) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 268987418..1cb93ac2b 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 14 +build = 15 From aa12e28568b28d471acb5b594c084eebaf0d6037 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 24 Jun 2024 08:27:37 -0700 Subject: [PATCH 0638/1377] Add semihosting support for nrf52 devices (#4137) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * fix my botched merge - keep board_level = extra flag for rak3631_dbg --------- Co-authored-by: Ben Meadors --- boards/wiscore_rak4631.json | 2 +- pyocd.yaml | 7 +++ src/DebugConfiguration.h | 8 +++ src/platform/nrf52/main-nrf52.cpp | 32 ++++++++++- variants/rak4631/platformio.ini | 91 +++++++++++++++++++++++++++---- 5 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 pyocd.yaml diff --git a/boards/wiscore_rak4631.json b/boards/wiscore_rak4631.json index 6dec3f7cb..c783f33a6 100644 --- a/boards/wiscore_rak4631.json +++ b/boards/wiscore_rak4631.json @@ -35,7 +35,7 @@ "svd_path": "nrf52840.svd", "openocd_target": "nrf52840-mdk-rs" }, - "frameworks": ["arduino"], + "frameworks": ["arduino", "freertos"], "name": "WisCore RAK4631 Board", "upload": { "maximum_ram_size": 248832, diff --git a/pyocd.yaml b/pyocd.yaml new file mode 100644 index 000000000..84bd9336b --- /dev/null +++ b/pyocd.yaml @@ -0,0 +1,7 @@ +# This is a config file to control pyocd ICE debugger probe options (only used for NRF52 targets with hardware debugging connections) +# for more info see FIXMEURL + +# console or telnet +semihost_console_type: telnet +enable_semihosting: True +telnet_port: 4444 diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index ca908197e..874d63bca 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -25,6 +25,14 @@ #include "SerialConsole.h" +// If defined we will include support for ARM ICE "semihosting" for a virtual +// console over the JTAG port (to replace the normal serial port) +// Note: Normally this flag is passed into the gcc commandline by platformio.ini. +// for an example see env:rak4631_dap. +// #ifndef USE_SEMIHOSTING +// #define USE_SEMIHOSTING +// #endif + #define DEBUG_PORT (*console) // Serial debug port #ifdef USE_SEGGER diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 1f2c6867d..86575bda6 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -149,13 +149,43 @@ void nrf52Loop() checkSDEvents(); } +#ifdef USE_SEMIHOSTING +#include + +/** + * 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. + */ +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); + } +} +#endif + void nrf52Setup() { - auto why = NRF_POWER->RESETREAS; + 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\n", why); +#ifdef USE_SEMIHOSTING + 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 diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index ef3e5a645..beffa7d3d 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -20,6 +20,7 @@ lib_deps = debug_tool = jlink + ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds ;upload_protocol = jlink @@ -27,26 +28,92 @@ debug_tool = jlink ; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) ; programming time is about the same as the bootloader version. ; For information on this see the meshtastic developers documentation for "Development on the NRF52" -[env:rak4631_dap] +[env:rak4631_dbg] extends = env:rak4631 board_level = extra -; pyocd pack --i nrf52840 + +; if the builtin version of openocd has a buggy version of semihosting, so use the external version +; platform_packages = platformio/tool-openocd@^3.1200.0 + +build_flags = + ${env:rak4631.build_flags} + -D USE_SEMIHOSTING + +lib_deps = + ${env:rak4631.lib_deps} + https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 + +; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. +; However the built in openocd version in platformio has buggy support for TCP to semihosting. +; +; So I'm now trying the external openocd - but the openocd scripts for nrf52.cfg assume you are using a DAP adapter not an STLINK adapter. +; In theory I could change those scripts. But for now I'm trying going back to a DAP adapter but with the external openocd. + +upload_protocol = stlink ; eventually use platformio/tool-pyocd@^2.3600.0 instad -upload_protocol = custom -upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE +;upload_protocol = custom +;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE + +; We want the initial breakpoint at setup() instead of main(). Also we want to enable semihosting at that point so instead of +; debug_init_break = tbreak setup +; we just turn off the platformio tbreak and do it in .gdbinit (where we have more flexibility for scripting) +; also we use a permanent breakpoint so it gets reused each time we restart the debugging session? +debug_init_break = tbreak setup + +; Note: add "monitor arm semihosting_redirect tcp 4444 all" if you want the stdout from the device to go to that port number instead +; (for use by meshtastic command line) +; monitor arm semihosting disable +; monitor debug_level 3 +; +; IMPORTANT: fileio must be disabled before using port 5555 - openocd ver 0.12 has a bug where if enabled it never properly parses the special :tt name +; for stdio access. +; monitor arm semihosting_redirect tcp 5555 stdio + +; Also note: it is _impossible_ to do non blocking reads on the semihost console port (an oversight when ARM specified the semihost API). +; So we'll neve be able to general purpose bi-directional communication with the device over semihosting. +debug_extra_cmds = + echo Running .gdbinit script + monitor arm semihosting enable + monitor arm semihosting_fileio enable + monitor arm semihosting_redirect disable + commands 1 + echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555" + set wantSemihost = true + end + ; Only reprogram the board if the code has changed debug_load_mode = modified ;debug_load_mode = manual -debug_tool = custom +debug_tool = stlink +;debug_tool = custom +; debug_server = +; openocd +; -f +; /usr/local/share/openocd/scripts/interface/stlink.cfg +; -f +; /usr/local/share/openocd/scripts/target/nrf52.cfg +; $PLATFORMIO_CORE_DIR/packages/tool-openocd/openocd/scripts/interface/cmsis-dap.cfg + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" ; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) -debug_server = - pyocd - gdbserver - -t - nrf52840 - --elf - ${platformio.build_dir}/${this.__env__}/firmware.elf +;debug_server = +; pyocd +; gdbserver +; -j +; ${platformio.workspace_dir}/.. +; -t +; nrf52840 +; --semihosting +; --elf +; ${platformio.build_dir}/${this.__env__}/firmware.elf + +; If you want to debug the semihosting support you can turn on extra logging in pyocd with +; -L +; pyocd.debug.semihost.trace=debug + ; The following is not needed because it automatically tries do this ;debug_server_ready_pattern = -.*GDB server started on port \d+.* ;debug_port = localhost:3333 \ No newline at end of file From 626aa762df18ea2deab5a1a8a350a65e5c5977b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 20:27:00 -0500 Subject: [PATCH 0639/1377] [create-pull-request] automated change (#4174) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 3 +++ src/mesh/generated/meshtastic/mesh.pb.h | 29 ++++++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index 4da558d0f..a3030d5ff 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4da558d0f73c46ef91b74431facee73c09affbfc +Subproject commit a3030d5ff187091c9fbbd08dd797cca5085736fe diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 46d59d609..d4ad9186a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -45,6 +45,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2) +PB_BIND(meshtastic_FileInfo, meshtastic_FileInfo, AUTO) + + PB_BIND(meshtastic_ToRadio, meshtastic_ToRadio, 2) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 064115815..e4e034cbd 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -711,6 +711,14 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; +/* Individual File info for the device */ +typedef struct _meshtastic_FileInfo { + /* The fully qualified path of the file */ + char file_name[228]; + /* The size of the file in bytes */ + uint32_t size_bytes; +} meshtastic_FileInfo; + typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t; /* Compressed message payload */ typedef struct _meshtastic_Compressed { @@ -815,6 +823,8 @@ typedef struct _meshtastic_FromRadio { meshtastic_DeviceMetadata metadata; /* MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT) */ meshtastic_MqttClientProxyMessage mqttClientProxyMessage; + /* File system manifest messages */ + meshtastic_FileInfo fileInfo; }; } meshtastic_FromRadio; @@ -958,6 +968,7 @@ extern "C" { + #define meshtastic_Compressed_portnum_ENUMTYPE meshtastic_PortNum @@ -985,6 +996,7 @@ extern "C" { #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} +#define meshtastic_FileInfo_init_default {"", 0} #define meshtastic_ToRadio_init_default {0, {meshtastic_MeshPacket_init_default}} #define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} @@ -1008,6 +1020,7 @@ extern "C" { #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} +#define meshtastic_FileInfo_init_zero {"", 0} #define meshtastic_ToRadio_init_zero {0, {meshtastic_MeshPacket_init_zero}} #define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} @@ -1110,6 +1123,8 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 +#define meshtastic_FileInfo_file_name_tag 1 +#define meshtastic_FileInfo_size_bytes_tag 2 #define meshtastic_Compressed_portnum_tag 1 #define meshtastic_Compressed_data_tag 2 #define meshtastic_Neighbor_node_id_tag 1 @@ -1144,6 +1159,7 @@ extern "C" { #define meshtastic_FromRadio_xmodemPacket_tag 12 #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 +#define meshtastic_FromRadio_fileInfo_tag 15 #define meshtastic_ToRadio_packet_tag 1 #define meshtastic_ToRadio_want_config_id_tag 3 #define meshtastic_ToRadio_disconnect_tag 4 @@ -1321,7 +1337,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,channel,channel), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,queueStatus,queueStatus), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) #define meshtastic_FromRadio_CALLBACK NULL #define meshtastic_FromRadio_DEFAULT NULL #define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket @@ -1335,6 +1352,13 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttC #define meshtastic_FromRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem #define meshtastic_FromRadio_payload_variant_metadata_MSGTYPE meshtastic_DeviceMetadata #define meshtastic_FromRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage +#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo + +#define meshtastic_FileInfo_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, file_name, 1) \ +X(a, STATIC, SINGULAR, UINT32, size_bytes, 2) +#define meshtastic_FileInfo_CALLBACK NULL +#define meshtastic_FileInfo_DEFAULT NULL #define meshtastic_ToRadio_FIELDLIST(X, a) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \ @@ -1434,6 +1458,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg; extern const pb_msgdesc_t meshtastic_LogRecord_msg; extern const pb_msgdesc_t meshtastic_QueueStatus_msg; extern const pb_msgdesc_t meshtastic_FromRadio_msg; +extern const pb_msgdesc_t meshtastic_FileInfo_msg; extern const pb_msgdesc_t meshtastic_ToRadio_msg; extern const pb_msgdesc_t meshtastic_Compressed_msg; extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; @@ -1459,6 +1484,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg #define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg #define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg +#define meshtastic_FileInfo_fields &meshtastic_FileInfo_msg #define meshtastic_ToRadio_fields &meshtastic_ToRadio_msg #define meshtastic_Compressed_fields &meshtastic_Compressed_msg #define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg @@ -1478,6 +1504,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 +#define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 81 From 042555134185b522c00c85f8590a7b1c280601fd Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 25 Jun 2024 11:26:02 -0500 Subject: [PATCH 0640/1377] Display alerts (#4170) * Move static functions into Screen.h, show compass during calibration * Move to _fontHeight macro to avoid collision * Move some alert functions to new alert handler * Catch missed reboot code * ESP32 fixes * Bump esp8266-oled-ssd1306 * Fixes for when a device has no screen * Use new startAlert(char*) helper class * Add EINK bits back to alert handling * Add noop class for no-display devices --------- Co-authored-by: Ben Meadors --- platformio.ini | 5 +- src/AccelerometerThread.h | 35 +++- src/ButtonThread.cpp | 7 +- src/commands.h | 6 +- src/graphics/Screen.cpp | 166 ++---------------- src/graphics/Screen.h | 148 ++++++++++++---- src/graphics/ScreenFonts.h | 8 +- src/main.cpp | 2 +- src/mesh/NodeDB.cpp | 2 +- src/modules/AdminModule.cpp | 6 +- src/modules/CannedMessageModule.cpp | 4 +- .../Telemetry/EnvironmentTelemetry.cpp | 14 +- src/modules/Telemetry/PowerTelemetry.cpp | 12 +- src/nimble/NimbleBluetooth.cpp | 30 +++- src/platform/nrf52/NRF52Bluetooth.cpp | 28 ++- src/shutdown.h | 2 +- 16 files changed, 252 insertions(+), 223 deletions(-) diff --git a/platformio.ini b/platformio.ini index 23ff53a10..720525f09 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,7 +80,7 @@ monitor_speed = 115200 lib_deps = jgromes/RadioLib@~6.6.0 - https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306 + https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 @@ -150,5 +150,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index f45511cca..0f04de057 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -16,6 +16,8 @@ #include #ifdef RAK_4631 #include "Fusion/Fusion.h" +#include "graphics/Screen.h" +#include "graphics/ScreenFonts.h" #include #endif @@ -101,7 +103,11 @@ class AccelerometerThread : public concurrency::OSThread bmx160.getAllData(&magAccel, NULL, &gAccel); // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 10 * 1000 && millis() < 30 * 1000) { + if (millis() > 12 * 1000 && millis() < 30 * 1000) { + if (!showingScreen) { + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameCalibration); + } if (magAccel.x > highestX) highestX = magAccel.x; if (magAccel.x < lowestX) @@ -114,6 +120,9 @@ class AccelerometerThread : public concurrency::OSThread highestZ = magAccel.z; if (magAccel.z < lowestZ) lowestZ = magAccel.z; + } else if (showingScreen && millis() >= 30 * 1000) { + showingScreen = false; + screen->endAlert(); } int highestRealX = highestX - (highestX + lowestX) / 2; @@ -255,11 +264,33 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LIS3DH lis; Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; + bool BMA_IRQ = false; #ifdef RAK_4631 + bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + + static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + display->drawString(x, y, "Calibrating\nCompass"); + int16_t compassX = 0, compassY = 0; + + // coordinates for the center of the compass/circle + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + display->getWidth() - getCompassDiam(display) / 2 - 5; + compassY = y + display->getHeight() / 2; + } else { + compassX = x + display->getWidth() - getCompassDiam(display) / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; + } + display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); + } #endif - bool BMA_IRQ = false; }; #endif \ No newline at end of file diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 4b3bb3fbc..1b85166d2 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -181,8 +181,9 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_LONG_PRESSED: { LOG_BUTTON("Long press!\n"); powerFSM.trigger(EVENT_PRESS); - if (screen) - screen->startShutdownScreen(); + if (screen) { + screen->startAlert("Shutting down..."); + } playBeep(); break; } @@ -322,4 +323,4 @@ void ButtonThread::userButtonPressedLongStop() if (millis() > c_holdOffTime) { btnEvent = BUTTON_EVENT_LONG_RELEASED; } -} +} \ No newline at end of file diff --git a/src/commands.h b/src/commands.h index 03ede5982..f2b783010 100644 --- a/src/commands.h +++ b/src/commands.h @@ -8,13 +8,11 @@ enum class Cmd { SET_ON, SET_OFF, ON_PRESS, - START_BLUETOOTH_PIN_SCREEN, + START_ALERT_FRAME, + STOP_ALERT_FRAME, START_FIRMWARE_UPDATE_SCREEN, - STOP_BLUETOOTH_PIN_SCREEN, STOP_BOOT_SCREEN, PRINT, - START_SHUTDOWN_SCREEN, - START_REBOOT_SCREEN, SHOW_PREV_FRAME, SHOW_NEXT_FRAME }; \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index eb92d824e..234381aa5 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -379,7 +379,7 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int // in the array of "drawScreen" functions; however, // the passed-state doesn't quite reflect the "current" // screen, so we have to detect it. - if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) { + if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == TransitionRelationship_INCOMING) { // if we're transitioning from the end of the frame list back around to the first // frame, then we want this to be `0` module_frame = state->transitionFrameTarget; @@ -393,31 +393,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int pi.drawFrame(display, state, x, y); } -static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - 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_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); -} - static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_CENTER); @@ -1307,49 +1282,6 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } } #endif -namespace -{ - -/// A basic 2D point class for drawing -class Point -{ - public: - float x, y; - - Point(float _x, float _y) : x(_x), y(_y) {} - - /// Apply a rotation around zero (standard rotation matrix math) - void rotate(float radian) - { - float cos = cosf(radian), sin = sinf(radian); - float rx = x * cos + y * sin, ry = -x * sin + y * cos; - - x = rx; - y = ry; - } - - void translate(int16_t dx, int dy) - { - x += dx; - y += dy; - } - - void scale(float f) - { - // We use -f here to counter the flip that happens - // on the y axis when drawing and rotating on screen - x *= f; - y *= -f; - } -}; - -} // namespace - -static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) -{ - d->drawLine(p1.x, p1.y, p2.x, p2.y); -} - /** * Given a recent lat/lon return a guess of the heading the user is walking on. * @@ -1380,31 +1312,6 @@ static float estimatedHeading(double lat, double lon) return b; } -static uint16_t getCompassDiam(OLEDDisplay *display) -{ - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (display->getWidth() > (display->getHeight() - offset)) { - diam = display->getHeight() - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (display->getWidth() * 2 / 3)) { - diam = display->getWidth() * 2 / 3; - } - } else { - diam = display->getWidth(); - if (diam > ((display->getHeight() - offset) * 2 / 3)) { - diam = (display->getHeight() - offset) * 2 / 3; - } - } - - return diam - 20; -}; - /// We will skip one node - the one for us, so we just blindly loop over all /// nodes static size_t nodeIndex; @@ -1428,7 +1335,7 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp drawLine(display, leftArrow, tip); drawLine(display, rightArrow, tip); } - +/* // Draw north static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) { @@ -1449,7 +1356,7 @@ static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t com drawLine(display, N1, N3); drawLine(display, N2, N4); drawLine(display, N1, N4); -} +}*/ // Get a string representation of the time passed since something happened static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) @@ -2023,13 +1930,22 @@ int32_t Screen::runOnce() case Cmd::SHOW_NEXT_FRAME: handleShowNextFrame(); break; - case Cmd::START_BLUETOOTH_PIN_SCREEN: - handleStartBluetoothPinScreen(cmd.bluetooth_pin); + case Cmd::START_ALERT_FRAME: { + showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away + showingNormalScreen = false; + alertFrames[0] = alertFrame; +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) +#endif + setFrameImmediateDraw(alertFrames); break; + } case Cmd::START_FIRMWARE_UPDATE_SCREEN: handleStartFirmwareUpdateScreen(); break; - case Cmd::STOP_BLUETOOTH_PIN_SCREEN: + case Cmd::STOP_ALERT_FRAME: case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame setFrames(); @@ -2038,12 +1954,6 @@ int32_t Screen::runOnce() handlePrint(cmd.print_text); free(cmd.print_text); break; - case Cmd::START_SHUTDOWN_SCREEN: - handleShutdownScreen(); - break; - case Cmd::START_REBOOT_SCREEN: - handleRebootScreen(); - break; default: LOG_ERROR("Invalid screen cmd\n"); } @@ -2284,17 +2194,6 @@ void Screen::setFrames() setFastFramerate(); // Draw ASAP } -void Screen::handleStartBluetoothPinScreen(uint32_t pin) -{ - LOG_DEBUG("showing bluetooth screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - - static FrameCallback frames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06u", pin); - setFrameImmediateDraw(frames); -} - void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) { ui->disableAllIndicators(); @@ -2302,41 +2201,6 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) setFastFramerate(); } -void Screen::handleShutdownScreen() -{ - LOG_DEBUG("showing shutdown screen\n"); - showingNormalScreen = false; -#ifdef USE_EINK - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please - EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update - handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) -#endif - - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - drawFrameText(display, state, x, y, "Shutting down..."); - }; - static FrameCallback frames[] = {frame}; - - setFrameImmediateDraw(frames); -} - -void Screen::handleRebootScreen() -{ - LOG_DEBUG("showing reboot screen\n"); - showingNormalScreen = false; -#ifdef USE_EINK - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please - EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update - handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?) -#endif - - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - drawFrameText(display, state, x, y, "Rebooting..."); - }; - static FrameCallback frames[] = {frame}; - setFrameImmediateDraw(frames); -} - void Screen::handleStartFirmwareUpdateScreen() { LOG_DEBUG("showing firmware screen\n"); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index b1bbffc3b..a8aca3657 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -21,11 +21,9 @@ class Screen void print(const char *) {} void doDeepSleep() {} void forceDisplay(bool forceUiUpdate = false) {} - void startBluetoothPinScreen(uint32_t pin) {} - void stopBluetoothPinScreen() {} - void startRebootScreen() {} - void startShutdownScreen() {} void startFirmwareUpdateScreen() {} + void startAlert(const char *) {} + void endAlert() {} }; } // namespace graphics #else @@ -34,6 +32,8 @@ class Screen #include #include "../configuration.h" +#include "gps/GeoCoord.h" +#include "graphics/ScreenFonts.h" #ifdef USE_ST7567 #include @@ -173,15 +173,29 @@ class Screen : public concurrency::OSThread void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); } void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); } - /// Starts showing the Bluetooth PIN screen. - // - // Switches over to a static frame showing the Bluetooth pairing screen - // with the PIN. - void startBluetoothPinScreen(uint32_t pin) + // generic alert start + void startAlert(FrameCallback _alertFrame) + { + alertFrame = _alertFrame; + ScreenCmd cmd; + cmd.cmd = Cmd::START_ALERT_FRAME; + enqueueCmd(cmd); + } + + void startAlert(const char *_alertMessage) + { + startAlert([_alertMessage](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + uint16_t x_offset = display->width() / 2; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, 26 + y, _alertMessage); + }); + } + + void endAlert() { ScreenCmd cmd; - cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN; - cmd.bluetooth_pin = pin; + cmd.cmd = Cmd::STOP_ALERT_FRAME; enqueueCmd(cmd); } @@ -192,20 +206,6 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } - void startShutdownScreen() - { - ScreenCmd cmd; - cmd.cmd = Cmd::START_SHUTDOWN_SCREEN; - enqueueCmd(cmd); - } - - void startRebootScreen() - { - ScreenCmd cmd; - cmd.cmd = Cmd::START_REBOOT_SCREEN; - enqueueCmd(cmd); - } - // Function to allow the AccelerometerThread to set the heading if a sensor provides it // Mutex needed? void setHeading(long _heading) @@ -224,9 +224,6 @@ class Screen : public concurrency::OSThread void setFunctionSymbal(std::string sym); void removeFunctionSymbal(std::string sym); - /// Stops showing the bluetooth PIN screen. - void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); } - /// Stops showing the boot screen. void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); } @@ -362,6 +359,7 @@ class Screen : public concurrency::OSThread bool isAUTOOled = false; private: + FrameCallback alertFrames[1]; struct ScreenCmd { Cmd cmd; union { @@ -387,11 +385,8 @@ class Screen : public concurrency::OSThread void handleOnPress(); void handleShowNextFrame(); void handleShowPrevFrame(); - void handleStartBluetoothPinScreen(uint32_t pin); void handlePrint(const char *text); void handleStartFirmwareUpdateScreen(); - void handleShutdownScreen(); - void handleRebootScreen(); /// Rebuilds our list of frames (screens) to default ones. void setFrames(); @@ -429,6 +424,9 @@ class Screen : public concurrency::OSThread bool digitalWatchFace = true; #endif + /// callback for current alert frame + FrameCallback alertFrame; + /// Queue of commands to execute in doTask. TypedQueue cmdQueue; /// Whether we are using a display @@ -455,4 +453,92 @@ class Screen : public concurrency::OSThread }; } // namespace graphics +namespace +{ +/// A basic 2D point class for drawing +class Point +{ + public: + float x, y; + + Point(float _x, float _y) : x(_x), y(_y) {} + + /// Apply a rotation around zero (standard rotation matrix math) + void rotate(float radian) + { + float cos = cosf(radian), sin = sinf(radian); + float rx = x * cos + y * sin, ry = -x * sin + y * cos; + + x = rx; + y = ry; + } + + void translate(int16_t dx, int dy) + { + x += dx; + y += dy; + } + + void scale(float f) + { + // We use -f here to counter the flip that happens + // on the y axis when drawing and rotating on screen + x *= f; + y *= -f; + } +}; + +} // namespace + +static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) +{ + d->drawLine(p1.x, p1.y, p2.x, p2.y); +} + +static uint16_t getCompassDiam(OLEDDisplay *display) +{ + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (display->getWidth() > (display->getHeight() - offset)) { + diam = display->getHeight() - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (display->getWidth() * 2 / 3)) { + diam = display->getWidth() * 2 / 3; + } + } else { + diam = display->getWidth(); + if (diam > ((display->getHeight() - offset) * 2 / 3)) { + diam = (display->getHeight() - offset) * 2 / 3; + } + } + + return diam - 20; +}; + +// Draw north +static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) +{ + // If north is supposed to be at the top of the compass we want rotation to be +0 + if (config.display.compass_north_top) + myHeading = -0; + + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); + Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); + Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + + for (int i = 0; i < 4; i++) { + // North on compass will be negative of heading + rosePoints[i]->rotate(-myHeading); + rosePoints[i]->scale(getCompassDiam(display)); + rosePoints[i]->translate(compassX, compassY); + } + drawLine(display, N1, N3); + drawLine(display, N2, N4); + drawLine(display, N1, N4); +} #endif \ No newline at end of file diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 4b34563f7..8a48d053e 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -28,8 +28,8 @@ #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #endif -#define fontHeight(font) ((font)[1] + 1) // height is position 1 +#define _fontHeight(font) ((font)[1] + 1) // height is position 1 -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9ec4fa82d..462eaa0f4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -930,7 +930,7 @@ void setup() nodeDB->saveToDisk(SEGMENT_CONFIG); if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting\n"); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = millis() + 5000; } } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 31fb983f4..1dc6d7883 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -180,7 +180,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset) if (didFactoryReset) { LOG_INFO("Rebooting due to factory reset"); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = millis() + (5 * 1000); } diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 814686609..3a3901433 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -137,7 +137,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH if (BleOta::getOtaAppVersion().isEmpty()) { LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); } else { screen->startFirmwareUpdateScreen(); BleOta::switchToOtaApp(); @@ -145,7 +145,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } #else LOG_INFO("Not on ESP32, scheduling regular reboot in %d seconds\n", s); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); #endif rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); break; @@ -811,7 +811,7 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch void AdminModule::reboot(int32_t seconds) { LOG_INFO("Rebooting in %d seconds\n", seconds); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000); } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index f513e045f..be414dce1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -597,14 +597,14 @@ int32_t CannedMessageModule::runOnce() // handle fn+s for shutdown case 0x9b: if (screen) - screen->startShutdownScreen(); + screen->startAlert("Shutting down..."); shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; // and fn+r for reboot case 0x90: if (screen) - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8f899401b..b69b2bfae 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -188,7 +188,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (lastMeasurementPacket == nullptr) { // If there's no valid packet, display "Environment" display->drawString(x, y, "Environment"); - display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement"); + display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement"); return; } @@ -213,31 +213,31 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt } // Continue with the remaining details - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Temp/Hum: " + last_temp + " / " + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%"); if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); } if (lastMeasurement.variant.environment_metrics.voltage != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); } if (lastMeasurement.variant.environment_metrics.iaq != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + display->drawString(x, y += _fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); } if (lastMeasurement.variant.environment_metrics.distance != 0) - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); if (lastMeasurement.variant.environment_metrics.weight != 0) - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); } diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index cb864f4f3..fb5aee375 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -108,7 +108,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s display->drawString(x, y, "Power Telemetry"); if (lastMeasurementPacket == nullptr) { display->setFont(FONT_SMALL); - display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement"); + display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement"); return; } @@ -120,22 +120,22 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s auto &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { display->setFont(FONT_SMALL); - display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error"); + display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error"); LOG_ERROR("Unable to decode last packet"); return; } display->setFont(FONT_SMALL); String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; - display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); } diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 48f945b0a..78ef5a1d3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -82,7 +82,33 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks LOG_INFO("*** Enter passkey %d on the peer side ***\n", passkey); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen->startBluetoothPinScreen(passkey); +#if HAS_SCREEN + screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + char btPIN[16] = "888888"; + snprintf(btPIN, sizeof(btPIN), "%06u", passkey); + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + 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_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); + }); +#endif passkeyShowing = true; return passkey; @@ -94,7 +120,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks if (passkeyShowing) { passkeyShowing = false; - screen->stopBluetoothPinScreen(); + screen->endAlert(); } } diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index a14829285..56d7ed167 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -290,7 +290,31 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen->startBluetoothPinScreen(configuredPasskey); + 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 : 32; + 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_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); + }); if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { @@ -307,7 +331,7 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu LOG_INFO("BLE pairing success\n"); else LOG_INFO("BLE pairing failed\n"); - screen->stopBluetoothPinScreen(); + screen->endAlert(); } void NRF52Bluetooth::sendLog(const char *logMessage) diff --git a/src/shutdown.h b/src/shutdown.h index 54fb3071b..3f191eea8 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -38,7 +38,7 @@ void powerCommandsCheck() #if defined(ARCH_ESP32) || defined(ARCH_NRF52) if (shutdownAtMsec) { - screen->startShutdownScreen(); + screen->startAlert("Shutting down..."); } #endif From a966d84e3d2409779c13894e68676680c9f2453f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 27 Jun 2024 07:07:27 -0500 Subject: [PATCH 0641/1377] Send file system manifest up on want_config (#4176) * Send file system manifest up on want_config * Platform specific methods * Helps to actually make the change * Clear --- src/FSCommon.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ src/FSCommon.h | 2 ++ src/mesh/PhoneAPI.cpp | 27 ++++++++++++++++++++--- src/mesh/PhoneAPI.h | 5 +++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 96aad1a9a..f9e9f1a82 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -84,6 +84,56 @@ bool renameFile(const char *pathFrom, const char *pathTo) #endif } +#include + +/** + * @brief Get the list of files in a directory. + * + * This function returns a list of files in a directory. The list includes the full path of each file. + * + * @param dirname The name of the directory. + * @param levels The number of levels of subdirectories to list. + * @return A vector of strings containing the full path of each file in the directory. + */ +std::vector getFiles(const char *dirname, uint8_t levels) +{ + std::vector filenames = {}; +#ifdef FSCom + File root = FSCom.open(dirname, FILE_O_READ); + if (!root) + return filenames; + if (!root.isDirectory()) + return filenames; + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory() && !String(file.name()).endsWith(".")) { + if (levels) { +#ifdef ARCH_ESP32 + std::vector subDirFilenames = getFiles(file.path(), levels - 1); +#else + std::vector subDirFilenames = getFiles(file.name(), levels - 1); +#endif + filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end()); + file.close(); + } + } else { + meshtastic_FileInfo fileInfo = {"", file.size()}; +#ifdef ARCH_ESP32 + strcpy(fileInfo.file_name, file.path()); +#else + strcpy(fileInfo.file_name, file.name()); +#endif + filenames.push_back(fileInfo); + file.close(); + } + file = root.openNextFile(); + } + root.close(); +#endif + return filenames; +} + /** * Lists the contents of a directory. * diff --git a/src/FSCommon.h b/src/FSCommon.h index ef1d3e4c1..8fbabd952 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -1,6 +1,7 @@ #pragma once #include "configuration.h" +#include // Cross platform filesystem API @@ -49,6 +50,7 @@ using namespace Adafruit_LittleFS_Namespace; void fsInit(); bool copyFile(const char *from, const char *to); bool renameFile(const char *pathFrom, const char *pathTo); +std::vector getFiles(const char *dirname, uint8_t levels); void listDir(const char *dirname, uint8_t levels, bool del); void rmDir(const char *dirname); void setupSDCard(); \ No newline at end of file diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 404666877..57a42651a 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -5,6 +5,7 @@ #include "Channels.h" #include "Default.h" +#include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" #include "PhoneAPI.h" @@ -47,6 +48,8 @@ void PhoneAPI::handleStartConfig() // even if we were already connected - restart our state machine state = STATE_SEND_MY_INFO; pauseBluetoothLogging = true; + filesManifest = getFiles("/", 10); + LOG_DEBUG("Got %d files in manifest\n", filesManifest.size()); LOG_INFO("Starting API client config\n"); nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos @@ -149,6 +152,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) STATE_SEND_CONFIG, STATE_SEND_MODULE_CONFIG, STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to the client + STATE_SEND_FILEMANIFEST, STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings */ @@ -324,8 +328,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Advance when we have sent all of our ModuleConfig objects if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) { // Clients sending special nonce don't want to see other nodeinfos - state = config_nonce == SPECIAL_NONCE ? STATE_SEND_COMPLETE_ID : STATE_SEND_OTHER_NODEINFOS; + state = config_nonce == SPECIAL_NONCE ? STATE_SEND_FILEMANIFEST : STATE_SEND_OTHER_NODEINFOS; config_state = 0; + filesManifest.clear(); } break; @@ -340,13 +345,28 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time } else { LOG_INFO("Done sending nodeinfos\n"); - state = STATE_SEND_COMPLETE_ID; + state = STATE_SEND_FILEMANIFEST; // Go ahead and send that ID right now return getFromRadio(buf); } break; } + case STATE_SEND_FILEMANIFEST: { + LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n"); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; + if (config_state < filesManifest.size()) { + fromRadioScratch.fileInfo = filesManifest.at(config_state); + config_state++; + // last element + if (config_state == filesManifest.size()) { + state = STATE_SEND_COMPLETE_ID; + config_state = 0; + } + } + break; + } + case STATE_SEND_COMPLETE_ID: LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; @@ -401,6 +421,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) void PhoneAPI::handleDisconnect() { + filesManifest.clear(); pauseBluetoothLogging = false; LOG_INFO("PhoneAPI disconnect\n"); } @@ -443,6 +464,7 @@ bool PhoneAPI::available() case STATE_SEND_MODULECONFIG: case STATE_SEND_METADATA: case STATE_SEND_OWN_NODEINFO: + case STATE_SEND_FILEMANIFEST: case STATE_SEND_COMPLETE_ID: return true; @@ -457,7 +479,6 @@ bool PhoneAPI::available() } } return true; // Always say we have something, because we might need to advance our state machine - case STATE_SEND_PACKETS: { if (!queueStatusPacketForPhone) queueStatusPacketForPhone = service.getQueueStatusForPhone(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 668f9c1f3..3d7bfbade 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -2,7 +2,9 @@ #include "Observer.h" #include "mesh-pb-constants.h" +#include #include +#include // Make sure that we never let our packets grow too large for one BLE packet #define MAX_TO_FROM_RADIO_SIZE 512 @@ -29,6 +31,7 @@ class PhoneAPI STATE_SEND_CONFIG, // Replacement for the old Radioconfig STATE_SEND_MODULECONFIG, // Send Module specific config STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client + STATE_SEND_FILEMANIFEST, // Send file manifest STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings }; @@ -65,6 +68,8 @@ class PhoneAPI uint32_t config_nonce = 0; uint32_t readIndex = 0; + std::vector filesManifest = {}; + void resetReadIndex() { readIndex = 0; } public: From 2cb6e7bd37f0ff705fc73ba042832237ed143f62 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 27 Jun 2024 11:14:16 -0700 Subject: [PATCH 0642/1377] tell vscode, if formatting, use whatever our trunk formatter wants (#4186) without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 07e198f0a..bf9b82111 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "trunk.enableWindows": true, "files.insertFinalNewline": false, "files.trimFinalNewlines": false, - "cmake.configureOnOpen": false + "cmake.configureOnOpen": false, + "[cpp]": { + "editor.defaultFormatter": "trunk.io" + } } From 41d633bfd84aec023922a7f34611a3715a6dd1cd Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 27 Jun 2024 18:43:08 -0700 Subject: [PATCH 0643/1377] fix the build - would loop forever if there were no files to send (#4188) --- src/mesh/PhoneAPI.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 57a42651a..399715f61 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -354,15 +354,14 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_FILEMANIFEST: { LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n"); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; - if (config_state < filesManifest.size()) { + // last element + if (config_state == filesManifest.size()) { // also handles an empty filesManifest + state = STATE_SEND_COMPLETE_ID; + config_state = 0; + } else { + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; fromRadioScratch.fileInfo = filesManifest.at(config_state); config_state++; - // last element - if (config_state == filesManifest.size()) { - state = STATE_SEND_COMPLETE_ID; - config_state = 0; - } } break; } From 51f3ce5e600b08dd96ce9a9aa3e37384456ba347 Mon Sep 17 00:00:00 2001 From: Alexander <156134901+Dorn8010@users.noreply.github.com> Date: Fri, 28 Jun 2024 08:55:54 +0200 Subject: [PATCH 0644/1377] Show owner.short_name on boot (and E-Ink sleep screen) (#4134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show owner.short_name on boot and sleep screen (on e-ink) * Update Screen.cpp - new line for short_name Boot screen short_name now below the region setting. Looks better on small screens. * Draw short_name on right --------- Co-authored-by: Thomas Göttgens Co-authored-by: todd-herbert Co-authored-by: Ben Meadors --- src/graphics/Screen.cpp | 83 ++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 234381aa5..924ca97c6 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -121,6 +121,30 @@ static uint16_t displayWidth, displayHeight; #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) +/// Check if the display can render a string (detect special chars; emoji) +static bool haveGlyphs(const char *str) +{ +#if defined(OLED_UA) || defined(OLED_RU) + // Don't want to make any assumptions about custom language support + return true; +#endif + + // Check each character with the lookup function for the OLED library + // We're not really meant to use this directly.. + bool have = true; + for (uint16_t i = 0; i < strlen(str); i++) { + uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); + // If font doesn't support a character, it is substituted for ¿ + if (result == 191 && (uint8_t)str[i] != 191) { + have = false; + break; + } + } + + LOG_DEBUG("haveGlyphs=%d\n", have); + return have; +} + /** * Draw the icon with extra info printed around the corners */ @@ -144,13 +168,15 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + // Draw version and short name in upper right + char buf[25]; + snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : ""); + + display->setTextAlignment(TEXT_ALIGN_RIGHT); + display->drawString(x + SCREEN_WIDTH, y + 0, buf); screen->forceDisplay(); - // FIXME - draw serial # somewhere? + + display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code } static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -185,14 +211,15 @@ static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDi if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + // Draw version and shortname in upper right + char buf[25]; + snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : ""); + + display->setTextAlignment(TEXT_ALIGN_RIGHT); + display->drawString(x + SCREEN_WIDTH, y + 0, buf); screen->forceDisplay(); - // FIXME - draw serial # somewhere? + display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code } static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -218,7 +245,6 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1 } else #endif { - // Draw region in upper left const char *region = myRegion ? myRegion->name : NULL; drawIconScreen(region, display, state, x, y); } @@ -281,40 +307,19 @@ static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) } } -/// Check if the display can render a string (detect special chars; emoji) -static bool haveGlyphs(const char *str) -{ -#if defined(OLED_UA) || defined(OLED_RU) - // Don't want to make any assumptions about custom language support - return true; -#endif - - // Check each character with the lookup function for the OLED library - // We're not really meant to use this directly.. - bool have = true; - for (uint16_t i = 0; i < strlen(str); i++) { - uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); - // If font doesn't support a character, it is substituted for ¿ - if (result == 191 && (uint8_t)str[i] != 191) { - have = false; - break; - } - } - - LOG_DEBUG("haveGlyphs=%d\n", have); - return have; -} - #ifdef USE_EINK /// Used on eink displays while in deep sleep static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { + // Next frame should use full-refresh, and block while running, else device will sleep before async callback EINK_ADD_FRAMEFLAG(display, COSMETIC); EINK_ADD_FRAMEFLAG(display, BLOCKING); LOG_DEBUG("Drawing deep sleep screen\n"); - drawIconScreen("Sleeping...", display, state, x, y); + + // Display displayStr on the screen + drawIconScreen("Sleeping", display, state, x, y); } /// Used on eink displays when screen updates are paused @@ -2718,4 +2723,4 @@ int Screen::handleWaypoint(const meshtastic_MeshPacket *arg) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN From f86a0e522853a90377e8e4fc9533f7212df11bcf Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 28 Jun 2024 04:48:55 -0700 Subject: [PATCH 0645/1377] nrf52 soft device will watchdog if you use ICE while BT on... (#4189) so have debugger disable bluetooth. --- src/platform/nrf52/main-nrf52.cpp | 3 ++- variants/rak4631/platformio.ini | 1 + variants/wio-sdk-wm1110/platformio.ini | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 86575bda6..b79f28f13 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -63,7 +63,8 @@ static void initBrownout() // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice } -static const bool useSoftDevice = true; // Set to false for easier debugging +// 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) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index beffa7d3d..6a67b0083 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -79,6 +79,7 @@ debug_extra_cmds = commands 1 echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555" set wantSemihost = true + set useSoftDevice = false end diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 7ca82e4c6..4a23e7a11 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -15,5 +15,19 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink +;debug_tool = stlink +;debug_speed = 4000 +; No need to reflash if the binary hasn't changed +debug_load_mode = modified ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = jlink +;upload_protocol = stlink +; we prefer to stop in setup() because we are an 'ardiuno' app +debug_init_break = tbreak setup + +; we need to turn off BLE/soft device if we are debugging otherwise it will watchdog reset us. +debug_extra_cmds = + echo Running .gdbinit script + commands 1 + set useSoftDevice = false + end \ No newline at end of file From c95b2c2d3c4312c8a925c68f066ec05b6ea5636b Mon Sep 17 00:00:00 2001 From: quimnut Date: Fri, 28 Jun 2024 21:49:38 +1000 Subject: [PATCH 0646/1377] correct xiao_ble build preventing sx1262 init (#4191) --- variants/xiao_ble/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 9d533c0ad..76e91e844 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = xiao_ble_sense board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Ivariants/xiao_ble/softdevice -Ivariants/xiao_ble/softdevice/nrf52 -D EBYTE_E22 -DEBYTE_E22_900M30S -DPRIVATE_HW +build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Ivariants/xiao_ble/softdevice -Ivariants/xiao_ble/softdevice/nrf52 -DEBYTE_E22 -DEBYTE_E22_900M30S -DPRIVATE_HW -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" board_build.ldscript = variants/xiao_ble/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/xiao_ble> @@ -11,4 +11,4 @@ lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink \ No newline at end of file +;upload_protocol = jlink From ce58a23f9baab06e5084fe81e32242f9d2e3d1f0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 28 Jun 2024 04:51:04 -0700 Subject: [PATCH 0647/1377] Force a compile time failur if FromRadio or ToRadio get larger than (#4190) a BLE packet size. We are actually very close to this threshold so important to make sure we don't accidentally pass it. --- src/mesh/PhoneAPI.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 3d7bfbade..1a2a065d3 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -8,6 +8,14 @@ // Make sure that we never let our packets grow too large for one BLE packet #define MAX_TO_FROM_RADIO_SIZE 512 + +#if meshtastic_FromRadio_size > MAX_TO_FROM_RADIO_SIZE +#error "meshtastic_FromRadio_size is too large for our BLE packets" +#endif +#if meshtastic_ToRadio_size > MAX_TO_FROM_RADIO_SIZE +#error "meshtastic_ToRadio_size is too large for our BLE packets" +#endif + #define SPECIAL_NONCE 69420 /** From 0016e747e912e2583a36ac7be6a6b9ed23073b3b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 28 Jun 2024 09:50:22 -0500 Subject: [PATCH 0648/1377] Clear vector after complete config state (#4194) * Clear after complete config * Don't collect . entries * Log file name and size --- src/FSCommon.cpp | 4 +++- src/mesh/PhoneAPI.cpp | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index f9e9f1a82..7d3788c4d 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -124,7 +124,9 @@ std::vector getFiles(const char *dirname, uint8_t levels) #else strcpy(fileInfo.file_name, file.name()); #endif - filenames.push_back(fileInfo); + if (!String(fileInfo.file_name).endsWith(".")) { + filenames.push_back(fileInfo); + } file.close(); } file = root.openNextFile(); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 399715f61..d6721b018 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -330,7 +330,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Clients sending special nonce don't want to see other nodeinfos state = config_nonce == SPECIAL_NONCE ? STATE_SEND_FILEMANIFEST : STATE_SEND_OTHER_NODEINFOS; config_state = 0; - filesManifest.clear(); } break; @@ -358,9 +357,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) if (config_state == filesManifest.size()) { // also handles an empty filesManifest state = STATE_SEND_COMPLETE_ID; config_state = 0; + filesManifest.clear(); } else { fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; fromRadioScratch.fileInfo = filesManifest.at(config_state); + LOG_DEBUG("File: %s (%d) bytes\n", fromRadioScratch.fileInfo.file_name, fromRadioScratch.fileInfo.size_bytes); config_state++; } break; From 9c232da00f73b3c8e97f6d6ede0993cb553e4bf8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:46:44 -0500 Subject: [PATCH 0649/1377] [create-pull-request] automated change (#4200) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/protobufs b/protobufs index a3030d5ff..57ddb288e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a3030d5ff187091c9fbbd08dd797cca5085736fe +Subproject commit 57ddb288e87438db3b5b99aa61f66a354c47bffb diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 2da4b86e6..e3037c910 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -22,7 +22,6 @@ typedef enum _meshtastic_Config_DeviceConfig_Role { The wifi radio and the oled screen will be put to sleep. This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. */ meshtastic_Config_DeviceConfig_Role_ROUTER = 2, - /* Description: Combination of both ROUTER and CLIENT. Not for mobile devices. */ meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3, /* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list. Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry From 5263c738f3af11c8284587623a16ac130c1ead98 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 28 Jun 2024 20:10:41 -0500 Subject: [PATCH 0650/1377] Make the logs Colorful! (#4199) --- src/RedirectablePrint.cpp | 61 +++++++++++++++++++++++++++++++-------- src/RedirectablePrint.h | 3 +- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index b77720d85..ee819643e 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -71,20 +71,49 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg) return len; } -size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) +size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) +{ + va_list copy; + static char printBuf[160]; + + va_copy(copy, arg); + size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); + va_end(copy); + + // If the resulting string is longer than sizeof(printBuf)-1 characters, the remaining characters are still counted for the + // return value + + if (len > sizeof(printBuf) - 1) { + len = sizeof(printBuf) - 1; + printBuf[sizeof(printBuf) - 2] = '\n'; + } + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + len = Print::write(printBuf, len); + Print::write("\u001b[0m", 5); + return len; +} + +void RedirectablePrint::log(const char *logLevel, const char *format, ...) { #ifdef ARCH_PORTDUINO if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - return 0; + return; else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - return 0; + return; else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - return 0; + return; #endif if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - return 0; + return; } - size_t r = 0; + #ifdef HAS_FREE_RTOS if (inDebugPrint != nullptr && xSemaphoreTake(inDebugPrint, portMAX_DELAY) == pdTRUE) { #else @@ -100,6 +129,14 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) // If we are the first message on a report, include the header if (!isContinuationMessage) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; @@ -113,15 +150,15 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN #ifdef ARCH_PORTDUINO - r += ::printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); + ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); #else - r += printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); + printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); #endif } else #ifdef ARCH_PORTDUINO - r += ::printf("%s | ??:??:?? %u ", logLevel, millis() / 1000); + ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); #else - r += printf("%s | ??:??:?? %u ", logLevel, millis() / 1000); + printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); #endif auto thread = concurrency::OSThread::currentThread; @@ -133,7 +170,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) print("] "); } } - r += vprintf(format, arg); + vprintf(logLevel, format, arg); #if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO) // if syslog is in use, collect the log messages and send them to syslog @@ -211,7 +248,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif } - return r; + return; } void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len) diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h index 31cc1b6ef..c997d3d4e 100644 --- a/src/RedirectablePrint.h +++ b/src/RedirectablePrint.h @@ -41,10 +41,11 @@ class RedirectablePrint : public Print * log message. Otherwise we assume more prints will come before the log message ends. This * allows you to call logDebug a few times to build up a single log message line if you wish. */ - size_t log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); + void log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); /** like printf but va_list based */ size_t vprintf(const char *format, va_list arg); + size_t vprintf(const char *logLevel, const char *format, va_list arg); void hexDump(const char *logLevel, unsigned char *buf, uint16_t len); From ca969e26a5c831b16881615c55524fd597e57355 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 28 Jun 2024 21:28:18 -0500 Subject: [PATCH 0651/1377] Squash needlessly static functions (#4183) --- src/AccelerometerThread.h | 2 +- src/graphics/Screen.cpp | 66 +++++---------- src/graphics/Screen.h | 173 +++++++++++++++++++------------------- 3 files changed, 107 insertions(+), 134 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 0f04de057..39ae90385 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -288,7 +288,7 @@ class AccelerometerThread : public concurrency::OSThread compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; } display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); - drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); + screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); } #endif }; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 924ca97c6..7c8bf40bb 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -79,7 +79,6 @@ namespace graphics // A text message frame + debug frame + all the node infos FrameCallback *normalFrames; static uint32_t targetFramerate = IDLE_FRAMERATE; -static char btPIN[16] = "888888"; uint32_t logo_timeout = 5000; // 4 seconds for EACH logo @@ -229,7 +228,7 @@ static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i drawOEMIconScreen(region, display, state, x, y); } -static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) +void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) { uint16_t x_offset = display->width() / 2; display->setTextAlignment(TEXT_ALIGN_CENTER); @@ -237,19 +236,6 @@ static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->drawString(x_offset + x, 26 + y, message); } -static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ -#ifdef ARCH_ESP32 - if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { - drawFrameText(display, state, x, y, "Resuming..."); - } else -#endif - { - const char *region = myRegion ? myRegion->name : NULL; - drawIconScreen(region, display, state, x, y); - } -} - // Used on boot when a certificate is being created static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -1336,32 +1322,10 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp arrowPoints[i]->scale(getCompassDiam(display) * 0.6); arrowPoints[i]->translate(compassX, compassY); } - drawLine(display, tip, tail); - drawLine(display, leftArrow, tip); - drawLine(display, rightArrow, tip); + display->drawLine(tip.x, tip.y, tail.x, tail.y); + display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y); + display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y); } -/* -// Draw north -static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) -{ - // If north is supposed to be at the top of the compass we want rotation to be +0 - if (config.display.compass_north_top) - myHeading = -0; - - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); - Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point *rosePoints[] = {&N1, &N2, &N3, &N4}; - - for (int i = 0; i < 4; i++) { - // North on compass will be negative of heading - rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(getCompassDiam(display)); - rosePoints[i]->translate(compassX, compassY); - } - drawLine(display, N1, N3); - drawLine(display, N2, N4); - drawLine(display, N1, N4); -}*/ // Get a string representation of the time passed since something happened static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) @@ -1461,7 +1425,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians else myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - drawCompassNorth(display, compassX, compassY, myHeading); + screen->drawCompassNorth(display, compassX, compassY, myHeading); if (hasValidPosition(node)) { // display direction toward node @@ -1562,7 +1526,7 @@ static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, i myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians else myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - drawCompassNorth(display, compassX, compassY, myHeading); + screen->drawCompassNorth(display, compassX, compassY, myHeading); // Distance to Waypoint float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); @@ -1758,9 +1722,19 @@ void Screen::setup() // Add frames. EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); - static FrameCallback bootFrames[] = {drawBootScreen}; - static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); - ui->setFrames(bootFrames, bootFrameCount); + alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { +#ifdef ARCH_ESP32 + if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { + drawFrameText(display, state, x, y, "Resuming..."); + } else +#endif + { + // Draw region in upper left + const char *region = myRegion ? myRegion->name : NULL; + drawIconScreen(region, display, state, x, y); + } + }; + ui->setFrames(alertFrames, 1); // No overlays. ui->setOverlays(nullptr, 0); @@ -2723,4 +2697,4 @@ int Screen::handleWaypoint(const meshtastic_MeshPacket *arg) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN +#endif // HAS_SCREEN \ No newline at end of file diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index a8aca3657..aa6e42893 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -82,6 +82,68 @@ class Screen #define SEGMENT_WIDTH 16 #define SEGMENT_HEIGHT 4 +namespace +{ +/// A basic 2D point class for drawing +class Point +{ + public: + float x, y; + + Point(float _x, float _y) : x(_x), y(_y) {} + + /// Apply a rotation around zero (standard rotation matrix math) + void rotate(float radian) + { + float cos = cosf(radian), sin = sinf(radian); + float rx = x * cos + y * sin, ry = -x * sin + y * cos; + + x = rx; + y = ry; + } + + void translate(int16_t dx, int dy) + { + x += dx; + y += dy; + } + + void scale(float f) + { + // We use -f here to counter the flip that happens + // on the y axis when drawing and rotating on screen + x *= f; + y *= -f; + } +}; + +} // namespace + +static uint16_t getCompassDiam(OLEDDisplay *display) +{ + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (display->getWidth() > (display->getHeight() - offset)) { + diam = display->getHeight() - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (display->getWidth() * 2 / 3)) { + diam = display->getWidth() * 2 / 3; + } + } else { + diam = display->getWidth(); + if (diam > ((display->getHeight() - offset) * 2 / 3)) { + diam = (display->getHeight() - offset) * 2 / 3; + } + } + + return diam - 20; +}; + namespace graphics { @@ -168,6 +230,30 @@ class Screen : public concurrency::OSThread void blink(); + void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *); + + // Draw north + void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) + { + // If north is supposed to be at the top of the compass we want rotation to be +0 + if (config.display.compass_north_top) + myHeading = -0; + + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); + Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); + Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + + for (int i = 0; i < 4; i++) { + // North on compass will be negative of heading + rosePoints[i]->rotate(-myHeading); + rosePoints[i]->scale(getCompassDiam(display)); + rosePoints[i]->translate(compassX, compassY); + } + display->drawLine(N1.x, N1.y, N3.x, N3.y); + display->drawLine(N2.x, N2.y, N4.x, N4.y); + display->drawLine(N1.x, N1.y, N4.x, N4.y); + } + /// Handle button press, trackball or swipe action) void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); } void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); } @@ -453,92 +539,5 @@ class Screen : public concurrency::OSThread }; } // namespace graphics -namespace -{ -/// A basic 2D point class for drawing -class Point -{ - public: - float x, y; - Point(float _x, float _y) : x(_x), y(_y) {} - - /// Apply a rotation around zero (standard rotation matrix math) - void rotate(float radian) - { - float cos = cosf(radian), sin = sinf(radian); - float rx = x * cos + y * sin, ry = -x * sin + y * cos; - - x = rx; - y = ry; - } - - void translate(int16_t dx, int dy) - { - x += dx; - y += dy; - } - - void scale(float f) - { - // We use -f here to counter the flip that happens - // on the y axis when drawing and rotating on screen - x *= f; - y *= -f; - } -}; - -} // namespace - -static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) -{ - d->drawLine(p1.x, p1.y, p2.x, p2.y); -} - -static uint16_t getCompassDiam(OLEDDisplay *display) -{ - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (display->getWidth() > (display->getHeight() - offset)) { - diam = display->getHeight() - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (display->getWidth() * 2 / 3)) { - diam = display->getWidth() * 2 / 3; - } - } else { - diam = display->getWidth(); - if (diam > ((display->getHeight() - offset) * 2 / 3)) { - diam = (display->getHeight() - offset) * 2 / 3; - } - } - - return diam - 20; -}; - -// Draw north -static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) -{ - // If north is supposed to be at the top of the compass we want rotation to be +0 - if (config.display.compass_north_top) - myHeading = -0; - - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); - Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point *rosePoints[] = {&N1, &N2, &N3, &N4}; - - for (int i = 0; i < 4; i++) { - // North on compass will be negative of heading - rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(getCompassDiam(display)); - rosePoints[i]->translate(compassX, compassY); - } - drawLine(display, N1, N3); - drawLine(display, N2, N4); - drawLine(display, N1, N4); -} #endif \ No newline at end of file From 6f3d7ca4d21c1bd1f05751c1e2777910f20424c3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 28 Jun 2024 23:30:39 -0500 Subject: [PATCH 0652/1377] Trim extra vprintf and filter for unprintable characters --- src/RedirectablePrint.cpp | 43 +++++++++++++-------------------------- src/RedirectablePrint.h | 1 - src/SerialConsole.cpp | 2 +- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index ee819643e..265bb42d6 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -50,27 +50,6 @@ size_t RedirectablePrint::write(uint8_t c) // serial port said (which could be zero) } -size_t RedirectablePrint::vprintf(const char *format, va_list arg) -{ - va_list copy; - static char printBuf[160]; - - va_copy(copy, arg); - size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); - va_end(copy); - - // If the resulting string is longer than sizeof(printBuf)-1 characters, the remaining characters are still counted for the - // return value - - if (len > sizeof(printBuf) - 1) { - len = sizeof(printBuf) - 1; - printBuf[sizeof(printBuf) - 2] = '\n'; - } - - len = Print::write(printBuf, len); - return len; -} - size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) { va_list copy; @@ -87,14 +66,20 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l len = sizeof(printBuf) - 1; printBuf[sizeof(printBuf) - 2] = '\n'; } - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - Print::write("\u001b[34m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - Print::write("\u001b[32m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - Print::write("\u001b[33m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) - Print::write("\u001b[31m", 6); + for (size_t f = 0; f < len; f++) { + if (!std::isprint(static_cast(printBuf[f])) && printBuf[f] != '\n') + printBuf[f] = '#'; + } + if (logLevel != nullptr) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + } len = Print::write(printBuf, len); Print::write("\u001b[0m", 5); return len; diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h index c997d3d4e..a29ad9c74 100644 --- a/src/RedirectablePrint.h +++ b/src/RedirectablePrint.h @@ -44,7 +44,6 @@ class RedirectablePrint : public Print void log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); /** like printf but va_list based */ - size_t vprintf(const char *format, va_list arg); size_t vprintf(const char *logLevel, const char *format, va_list arg); void hexDump(const char *logLevel, unsigned char *buf, uint16_t len); diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 53ece0fa3..41064f288 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -24,7 +24,7 @@ void consolePrintf(const char *format, ...) { va_list arg; va_start(arg, format); - console->vprintf(format, arg); + console->vprintf(nullptr, format, arg); va_end(arg); console->flush(); } From 20c1d71214aff27b6a63602fcd1f8ac922c15c87 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 29 Jun 2024 19:03:00 -0500 Subject: [PATCH 0653/1377] Deprecate Router Client role (and make it Client) (#4201) --- src/mesh/FloodingRouter.cpp | 1 - src/mesh/RadioInterface.cpp | 1 - src/modules/AdminModule.cpp | 4 ++++ src/modules/esp32/StoreForwardModule.cpp | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 7866fa444..0fdde5277 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -22,7 +22,6 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) if (wasSeenRecently(p)) { // Note: this will also add a recent packet record printPacket("Ignoring incoming msg we've already seen", p); if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && - config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! Router::cancelSending(p->from, p->id); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 78228c077..cdea33717 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -261,7 +261,6 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d\n", snr, CWsize); if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || - config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { delay = random(0, 2 * CWsize) * slotTimeMsec; LOG_DEBUG("rx_snr found in packet. As a router, setting tx delay:%d\n", delay); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 3a3901433..11821a0a3 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -388,6 +388,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs); config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; } + // Router Client is deprecated; Set it to client + if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) { + config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + } break; case meshtastic_Config_position_tag: LOG_INFO("Setting config: Position\n"); diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index 12cddc520..dc8650ad0 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -319,8 +319,8 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m #ifdef ARCH_ESP32 if (moduleConfig.store_forward.enabled) { - // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT - if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { + // The router node should not be sending messages as a client + if ((getFrom(&mp) != nodeDB->getNodeNum())) { if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; From 47a94d7a076e2cd9ba0e3260ca51a74a7c101f01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:04:08 -0500 Subject: [PATCH 0654/1377] [create-pull-request] automated change (#4205) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 1 - src/mesh/generated/meshtastic/mesh.pb.cpp | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 6 +- src/mesh/generated/meshtastic/portnums.pb.h | 2 + src/mesh/generated/meshtastic/powermon.pb.cpp | 4 ++ src/mesh/generated/meshtastic/powermon.pb.h | 55 ++++++++++++++++++- 7 files changed, 65 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 57ddb288e..e7327e76b 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 57ddb288e87438db3b5b99aa61f66a354c47bffb +Subproject commit e7327e76bdc0b3b77c50e214fae5beb1cb303e9c diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 100972c1e..fc7bea53a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -8,7 +8,6 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" -#include "meshtastic/module_config.pb.h" #include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index d4ad9186a..3fa81e131 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -36,7 +36,7 @@ PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, AUTO) PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO) -PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, AUTO) +PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, 2) PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index e4e034cbd..5e245a2b5 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -691,11 +691,11 @@ typedef struct _meshtastic_MyNodeInfo { and then extend as needed by emitting multiple records. */ typedef struct _meshtastic_LogRecord { /* Log levels, chosen to match python logging conventions. */ - char message[64]; + char message[384]; /* Seconds since 1970 - or 0 for unknown/unset */ uint32_t time; /* Usually based on thread name - if known */ - char source[8]; + char source[32]; /* Not yet set */ meshtastic_LogRecord_Level level; } meshtastic_LogRecord; @@ -1507,7 +1507,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 -#define meshtastic_LogRecord_size 81 +#define meshtastic_LogRecord_size 426 #define meshtastic_MeshPacket_size 326 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 233e8d653..6cc82352a 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -124,6 +124,8 @@ typedef enum _meshtastic_PortNum { meshtastic_PortNum_ATAK_PLUGIN = 72, /* Provides unencrypted information about a node for consumption by a map via MQTT */ meshtastic_PortNum_MAP_REPORT_APP = 73, + /* PowerStress based monitoring support (for automated power consumption testing) */ + meshtastic_PortNum_POWERSTRESS_APP = 74, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ diff --git a/src/mesh/generated/meshtastic/powermon.pb.cpp b/src/mesh/generated/meshtastic/powermon.pb.cpp index 4d798e9a3..ce41ea021 100644 --- a/src/mesh/generated/meshtastic/powermon.pb.cpp +++ b/src/mesh/generated/meshtastic/powermon.pb.cpp @@ -9,5 +9,9 @@ PB_BIND(meshtastic_PowerMon, meshtastic_PowerMon, AUTO) +PB_BIND(meshtastic_PowerStressMessage, meshtastic_PowerStressMessage, AUTO) + + + diff --git a/src/mesh/generated/meshtastic/powermon.pb.h b/src/mesh/generated/meshtastic/powermon.pb.h index 88e80bb55..7de0618e9 100644 --- a/src/mesh/generated/meshtastic/powermon.pb.h +++ b/src/mesh/generated/meshtastic/powermon.pb.h @@ -38,6 +38,33 @@ See GPSPowerState for more details */ meshtastic_PowerMon_State_GPS_Active = 2048 } meshtastic_PowerMon_State; +/* What operation would we like the UUT to perform. +note: senders should probably set want_response in their request packets, so that they can know when the state +machine has started processing their request */ +typedef enum _meshtastic_PowerStressMessage_Opcode { + /* Unset/unused */ + meshtastic_PowerStressMessage_Opcode_UNSET = 0, + meshtastic_PowerStressMessage_Opcode_PRINT_INFO = 1, /* Print board version slog and send an ack that we are alive and ready to process commands */ + meshtastic_PowerStressMessage_Opcode_FORCE_QUIET = 2, /* Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation) */ + meshtastic_PowerStressMessage_Opcode_END_QUIET = 3, /* Stop powerstress processing - probably by just rebooting the board */ + meshtastic_PowerStressMessage_Opcode_SCREEN_ON = 16, /* Turn the screen on */ + meshtastic_PowerStressMessage_Opcode_SCREEN_OFF = 17, /* Turn the screen off */ + meshtastic_PowerStressMessage_Opcode_CPU_IDLE = 32, /* Let the CPU run but we assume mostly idling for num_seconds */ + meshtastic_PowerStressMessage_Opcode_CPU_DEEPSLEEP = 33, /* Force deep sleep for FIXME seconds */ + meshtastic_PowerStressMessage_Opcode_CPU_FULLON = 34, /* Spin the CPU as fast as possible for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LED_ON = 48, /* Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes) */ + meshtastic_PowerStressMessage_Opcode_LED_OFF = 49, /* Force the LED off for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LORA_OFF = 64, /* Completely turn off the LORA radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LORA_TX = 65, /* Send Lora packets for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LORA_RX = 66, /* Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel) */ + meshtastic_PowerStressMessage_Opcode_BT_OFF = 80, /* Turn off the BT radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_BT_ON = 81, /* Turn on the BT radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_WIFI_OFF = 96, /* Turn off the WIFI radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_WIFI_ON = 97, /* Turn on the WIFI radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_GPS_OFF = 112, /* Turn off the GPS radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_GPS_ON = 113 /* Turn on the GPS radio for num_seconds */ +} meshtastic_PowerStressMessage_Opcode; + /* Struct definitions */ /* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */ @@ -45,6 +72,13 @@ typedef struct _meshtastic_PowerMon { char dummy_field; } meshtastic_PowerMon; +/* PowerStress testing support via the C++ PowerStress module */ +typedef struct _meshtastic_PowerStressMessage { + /* What type of HardwareMessage is this? */ + meshtastic_PowerStressMessage_Opcode cmd; + float num_seconds; +} meshtastic_PowerStressMessage; + #ifdef __cplusplus extern "C" { @@ -55,13 +89,23 @@ extern "C" { #define _meshtastic_PowerMon_State_MAX meshtastic_PowerMon_State_GPS_Active #define _meshtastic_PowerMon_State_ARRAYSIZE ((meshtastic_PowerMon_State)(meshtastic_PowerMon_State_GPS_Active+1)) +#define _meshtastic_PowerStressMessage_Opcode_MIN meshtastic_PowerStressMessage_Opcode_UNSET +#define _meshtastic_PowerStressMessage_Opcode_MAX meshtastic_PowerStressMessage_Opcode_GPS_ON +#define _meshtastic_PowerStressMessage_Opcode_ARRAYSIZE ((meshtastic_PowerStressMessage_Opcode)(meshtastic_PowerStressMessage_Opcode_GPS_ON+1)) + + +#define meshtastic_PowerStressMessage_cmd_ENUMTYPE meshtastic_PowerStressMessage_Opcode /* Initializer values for message structs */ #define meshtastic_PowerMon_init_default {0} +#define meshtastic_PowerStressMessage_init_default {_meshtastic_PowerStressMessage_Opcode_MIN, 0} #define meshtastic_PowerMon_init_zero {0} +#define meshtastic_PowerStressMessage_init_zero {_meshtastic_PowerStressMessage_Opcode_MIN, 0} /* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_PowerStressMessage_cmd_tag 1 +#define meshtastic_PowerStressMessage_num_seconds_tag 2 /* Struct field encoding specification for nanopb */ #define meshtastic_PowerMon_FIELDLIST(X, a) \ @@ -69,14 +113,23 @@ extern "C" { #define meshtastic_PowerMon_CALLBACK NULL #define meshtastic_PowerMon_DEFAULT NULL +#define meshtastic_PowerStressMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, cmd, 1) \ +X(a, STATIC, SINGULAR, FLOAT, num_seconds, 2) +#define meshtastic_PowerStressMessage_CALLBACK NULL +#define meshtastic_PowerStressMessage_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_PowerMon_msg; +extern const pb_msgdesc_t meshtastic_PowerStressMessage_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_PowerMon_fields &meshtastic_PowerMon_msg +#define meshtastic_PowerStressMessage_fields &meshtastic_PowerStressMessage_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerMon_size +#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerStressMessage_size #define meshtastic_PowerMon_size 0 +#define meshtastic_PowerStressMessage_size 7 #ifdef __cplusplus } /* extern "C" */ From b5d771831921bee1b59c969a04c246fd3fdae630 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 29 Jun 2024 21:16:07 -0500 Subject: [PATCH 0655/1377] Move waypoint (#4202) * Move waypoint screen draw into the waypoint module * Get the observer set up for the waypoint screen draw * Static squashing: screen dimensions Macros moved back to Screen.cpp, as a band-aid until we eventually move all those static functions into the Screen class. * Move getCompassDiam into Screen class (supress compiler warnings) At this stage, the method is still static, because it's used by drawNodeInfo, which has no tidy reference to our screen instance. This is probably just another band-aid until these static functions all move. * Use new getCompassDiam function in AccelerometerThread * Properly gate display code in WaypointModule --------- Co-authored-by: Todd Herbert --- src/AccelerometerThread.h | 7 +- src/graphics/Screen.cpp | 238 +++++++++------------------------ src/graphics/Screen.h | 63 +++------ src/modules/WaypointModule.cpp | 150 ++++++++++++++++++++- src/modules/WaypointModule.h | 14 +- 5 files changed, 246 insertions(+), 226 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 39ae90385..c2910007e 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -278,16 +278,17 @@ class AccelerometerThread : public concurrency::OSThread display->setFont(FONT_MEDIUM); display->drawString(x, y, "Calibrating\nCompass"); int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); // coordinates for the center of the compass/circle if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + display->getWidth() - getCompassDiam(display) / 2 - 5; + compassX = x + display->getWidth() - compassDiam / 2 - 5; compassY = y + display->getHeight() / 2; } else { - compassX = x + display->getWidth() - getCompassDiam(display) / 2 - 5; + compassX = x + display->getWidth() - compassDiam / 2 - 5; compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; } - display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + display->drawCircle(compassX, compassY, compassDiam / 2); screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); } #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 7c8bf40bb..f724ddd3d 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -43,7 +43,6 @@ along with this program. If not, see . #include "meshUtils.h" #include "modules/ExternalNotificationModule.h" #include "modules/TextMessageModule.h" -#include "modules/WaypointModule.h" #include "sleep.h" #include "target_specific.h" @@ -60,9 +59,6 @@ along with this program. If not, see . #include "platform/portduino/PortduinoGlue.h" #endif -/// Convert an integer GPS coords to a floating point -#define DegD(i) (i * 1e-7) - using namespace meshtastic; /** @todo remove */ namespace graphics @@ -111,10 +107,10 @@ GeoCoord geoCoord; static bool heartbeat = false; #endif -static uint16_t displayWidth, displayHeight; - -#define SCREEN_WIDTH displayWidth -#define SCREEN_HEIGHT displayHeight +// Quick access to screen dimensions from static drawing functions +// DEPRECATED. To-do: move static functions inside Screen class +#define SCREEN_WIDTH display->getWidth() +#define SCREEN_HEIGHT display->getHeight() #include "graphics/ScreenFonts.h" @@ -416,37 +412,6 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) return packet->from != 0 && !moduleConfig.store_forward.enabled; } -// Determine whether the waypoint frame should be drawn (waypoint deleted? expired?) -static bool shouldDrawWaypoint(const meshtastic_MeshPacket *packet) -{ -#if !MESHTASTIC_EXCLUDE_WAYPOINT - // If no waypoint to show - if (!devicestate.has_rx_waypoint) - return false; - - // Decode the message, to find the expiration time (is waypoint still valid) - // This handles "deletion" as well as expiration - meshtastic_Waypoint wp; - memset(&wp, 0, sizeof(wp)); - if (pb_decode_from_bytes(packet->decoded.payload.bytes, packet->decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { - // Valid waypoint - if (wp.expire > getTime()) - return devicestate.has_rx_waypoint = true; - - // Expired, or deleted - else - return devicestate.has_rx_waypoint = false; - } - - // If decoding failed - LOG_ERROR("Failed to decode waypoint\n"); - devicestate.has_rx_waypoint = false; - return false; -#else - return false; -#endif -} - // Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus) { @@ -1093,7 +1058,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state } /// Draw a series of fields in a column, wrapping to multiple columns if needed -static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) +void Screen::drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) { // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1279,7 +1244,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const * We keep a series of "after you've gone 10 meters, what is your heading since * the last reference point?" */ -static float estimatedHeading(double lat, double lon) +float Screen::estimatedHeading(double lat, double lon) { static double oldLat, oldLon; static float b; @@ -1309,7 +1274,7 @@ static size_t nodeIndex; static int8_t prevFrame = -1; // Draw the arrow pointing to a node's location -static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) +void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian) { Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; @@ -1319,7 +1284,7 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp for (int i = 0; i < 4; i++) { arrowPoints[i]->rotate(headingRadian); - arrowPoints[i]->scale(getCompassDiam(display) * 0.6); + arrowPoints[i]->scale(compassDiam * 0.6); arrowPoints[i]->translate(compassX, compassY); } display->drawLine(tip.x, tip.y, tail.x, tail.y); @@ -1328,7 +1293,7 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp } // Get a string representation of the time passed since something happened -static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) +void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) { // Use an absolute timestamp in some cases. // Particularly useful with E-Ink displays. Static UI, fewer refreshes. @@ -1357,6 +1322,54 @@ static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) snprintf(timeStr, maxLength, "unknown age"); } +void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) +{ + // If north is supposed to be at the top of the compass we want rotation to be +0 + if (config.display.compass_north_top) + myHeading = -0; + + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); + Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); + Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + + uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); + + for (int i = 0; i < 4; i++) { + // North on compass will be negative of heading + rosePoints[i]->rotate(-myHeading); + rosePoints[i]->scale(compassDiam); + rosePoints[i]->translate(compassX, compassY); + } + display->drawLine(N1.x, N1.y, N3.x, N3.y); + display->drawLine(N2.x, N2.y, N4.x, N4.y); + display->drawLine(N1.x, N1.y, N4.x, N4.y); +} + +uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight) +{ + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (displayWidth > (displayHeight - offset)) { + diam = displayHeight - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (displayWidth * 2 / 3)) { + diam = displayWidth * 2 / 3; + } + } else { + diam = displayWidth; + if (diam > ((displayHeight - offset) * 2 / 3)) { + diam = (displayHeight - offset) * 2 / 3; + } + } + + return diam - 20; +}; + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // We only advance our nodeIndex if the frame # has changed - because @@ -1396,7 +1409,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ } static char lastStr[20]; - getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr)); + screen->getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr)); static char distStr[20]; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { @@ -1407,13 +1420,14 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); const char *fields[] = {username, lastStr, signalStr, distStr, NULL}; int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); // coordinates for the center of the compass/circle if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5; compassY = y + SCREEN_HEIGHT / 2; } else { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5; compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; } bool hasNodeHeading = false; @@ -1424,7 +1438,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (screen->hasHeading()) myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians else - myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); screen->drawCompassNorth(display, compassX, compassY, myHeading); if (hasValidPosition(node)) { @@ -1452,7 +1466,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // If the top of the compass is not a static north we need adjust bearingToOther based on heading if (!config.display.compass_north_top) bearingToOther -= myHeading; - drawNodeHeading(display, compassX, compassY, bearingToOther); + screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); } } if (!hasNodeHeading) { @@ -1462,119 +1476,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // hasValidPosition(node)); display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); } - display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + display->drawCircle(compassX, compassY, compassDiam / 2); if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { display->setColor(BLACK); } // Must be after distStr is populated - drawColumns(display, x, y, fields); -} - -/// Draw the last waypoint we received -static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // Prepare to draw - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - - // Handle inverted display - // Unsure of expected behavior: for now, copy drawNodeInfo - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - - // Decode the waypoint - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_Waypoint wp; - memset(&wp, 0, sizeof(wp)); - if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { - // This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case - display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint"); - devicestate.has_rx_waypoint = false; - return; - } - - // Get timestamp info. Will pass as a field to drawColumns - static char lastStr[20]; - getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr)); - - // Will contain distance information, passed as a field to drawColumns - static char distStr[20]; - - // Get our node, to use our own position - meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - - // Text fields to draw (left of compass) - // Last element must be NULL. This signals the end of the char*[] to drawColumns - const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL}; - - // Co-ordinates for the center of the compass/circle - int16_t compassX = 0, compassY = 0; - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; - compassY = y + SCREEN_HEIGHT / 2; - } else { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; - } - - // If our node has a position: - if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) { - const meshtastic_PositionLite &op = ourNode->position; - float myHeading; - if (screen->hasHeading()) - myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians - else - myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - screen->drawCompassNorth(display, compassX, compassY, myHeading); - - // Distance to Waypoint - float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - if (d < (2 * MILES_TO_FEET)) - snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); - else - snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); - } else { - if (d < 2000) - snprintf(distStr, sizeof(distStr), "%.0f m", d); - else - snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); - } - - // Compass bearing to waypoint - float bearingToOther = - GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i)); - // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly - // If the top of the compass is not a static north we need adjust bearingToOther based on heading - if (!config.display.compass_north_top) - bearingToOther -= myHeading; - drawNodeHeading(display, compassX, compassY, bearingToOther); - } - - // If our node doesn't have position - else { - // ? in the compass - display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); - - // ? in the distance field - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - strncpy(distStr, "? mi", sizeof(distStr)); - else - strncpy(distStr, "? km", sizeof(distStr)); - } - - // Undo color-inversion, if set prior to drawing header - // Unsure of expected behavior? For now: copy drawNodeInfo - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->setColor(BLACK); - } - - // Draw compass circle - display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); - - // Must be after distStr is populated - drawColumns(display, x, y, fields); + screen->drawColumns(display, x, y, fields); } Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) @@ -1797,8 +1705,6 @@ void Screen::setup() textMessageObserver.observe(textMessageModule); if (inputBroker) inputObserver.observe(inputBroker); - if (waypointModule) - waypointObserver.observe(waypointModule); // Modules can notify screen about refresh MeshModule::observeUIEvents(&uiFrameEventObserver); @@ -2130,11 +2036,6 @@ void Screen::setFrames() normalFrames[numframes++] = drawTextMessageFrame; } - // If we have a waypoint (not expired, not deleted) - if (devicestate.has_rx_waypoint && shouldDrawWaypoint(&devicestate.rx_waypoint)) { - normalFrames[numframes++] = drawWaypointFrame; - } - // then all the nodes // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens size_t numToShow = min(numMeshNodes, 4U); @@ -2196,7 +2097,7 @@ void Screen::blink() uint8_t count = 10; dispdev->setBrightness(254); while (count > 0) { - dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight()); dispdev->display(); delay(50); dispdev->clear(); @@ -2687,13 +2588,6 @@ int Screen::handleInputEvent(const InputEvent *event) return 0; } -int Screen::handleWaypoint(const meshtastic_MeshPacket *arg) -{ - // TODO: move to appropriate frame when redrawing - setFrames(); - return 0; -} - } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index aa6e42893..e80581d6d 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -82,6 +82,9 @@ class Screen #define SEGMENT_WIDTH 16 #define SEGMENT_HEIGHT 4 +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) + namespace { /// A basic 2D point class for drawing @@ -119,31 +122,6 @@ class Point } // namespace -static uint16_t getCompassDiam(OLEDDisplay *display) -{ - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (display->getWidth() > (display->getHeight() - offset)) { - diam = display->getHeight() - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (display->getWidth() * 2 / 3)) { - diam = display->getWidth() * 2 / 3; - } - } else { - diam = display->getWidth(); - if (diam > ((display->getHeight() - offset) * 2 / 3)) { - diam = (display->getHeight() - offset) * 2 / 3; - } - } - - return diam - 20; -}; - namespace graphics { @@ -188,8 +166,6 @@ class Screen : public concurrency::OSThread CallbackObserver(this, &Screen::handleStatusUpdate); CallbackObserver textMessageObserver = CallbackObserver(this, &Screen::handleTextMessage); - CallbackObserver waypointObserver = - CallbackObserver(this, &Screen::handleWaypoint); CallbackObserver uiFrameEventObserver = CallbackObserver(this, &Screen::handleUIFrameEvent); CallbackObserver inputObserver = @@ -232,27 +208,18 @@ class Screen : public concurrency::OSThread void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *); + void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength); + // Draw north - void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) - { - // If north is supposed to be at the top of the compass we want rotation to be +0 - if (config.display.compass_north_top) - myHeading = -0; + void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading); - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); - Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight); - for (int i = 0; i < 4; i++) { - // North on compass will be negative of heading - rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(getCompassDiam(display)); - rosePoints[i]->translate(compassX, compassY); - } - display->drawLine(N1.x, N1.y, N3.x, N3.y); - display->drawLine(N2.x, N2.y, N4.x, N4.y); - display->drawLine(N1.x, N1.y, N4.x, N4.y); - } + float estimatedHeading(double lat, double lon); + + void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian); + + void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); /// Handle button press, trackball or swipe action) void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); } @@ -421,7 +388,6 @@ class Screen : public concurrency::OSThread int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); - int handleWaypoint(const meshtastic_MeshPacket *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(bool forceUiUpdate = false); @@ -444,6 +410,11 @@ class Screen : public concurrency::OSThread bool isAUTOOled = false; + // Screen dimensions (for convenience) + // Defined during Screen::setup + uint16_t displayWidth = 0; + uint16_t displayHeight = 0; + private: FrameCallback alertFrames[1]; struct ScreenCmd { diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index 83485c8ee..d5b7d29ee 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -2,6 +2,11 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "configuration.h" +#if HAS_SCREEN +#include "gps/RTC.h" +#include "graphics/Screen.h" +#include "main.h" +#endif WaypointModule *waypointModule; @@ -11,14 +16,155 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp) auto &p = mp.decoded; LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif - + UIFrameEvent e = {true, true}; // We only store/display messages destined for us. // Keep a copy of the most recent text message. devicestate.rx_waypoint = mp; devicestate.has_rx_waypoint = true; powerFSM.trigger(EVENT_RECEIVED_MSG); - notifyObservers(&mp); + notifyObservers(&e); return ProcessMessage::CONTINUE; // Let others look at this message also if they want } + +#if HAS_SCREEN +bool WaypointModule::shouldDraw() +{ +#if !MESHTASTIC_EXCLUDE_WAYPOINT + // If no waypoint to show + if (!devicestate.has_rx_waypoint) + return false; + + // Decode the message, to find the expiration time (is waypoint still valid) + // This handles "deletion" as well as expiration + meshtastic_Waypoint wp; + memset(&wp, 0, sizeof(wp)); + if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size, + &meshtastic_Waypoint_msg, &wp)) { + // Valid waypoint + if (wp.expire > getTime()) + return devicestate.has_rx_waypoint = true; + + // Expired, or deleted + else + return devicestate.has_rx_waypoint = false; + } + + // If decoding failed + LOG_ERROR("Failed to decode waypoint\n"); + devicestate.has_rx_waypoint = false; + return false; +#else + return false; +#endif +} + +/// Draw the last waypoint we received +void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // Prepare to draw + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + // Handle inverted display + // Unsure of expected behavior: for now, copy drawNodeInfo + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + + // Decode the waypoint + meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + meshtastic_Waypoint wp; + memset(&wp, 0, sizeof(wp)); + if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { + // This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case + display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint"); + devicestate.has_rx_waypoint = false; + return; + } + + // Get timestamp info. Will pass as a field to drawColumns + static char lastStr[20]; + screen->getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr)); + + // Will contain distance information, passed as a field to drawColumns + static char distStr[20]; + + // Get our node, to use our own position + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + + // Text fields to draw (left of compass) + // Last element must be NULL. This signals the end of the char*[] to drawColumns + const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL}; + + // Dimensions / co-ordinates for the compass/circle + int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); + + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + display->getHeight() / 2; + } else { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; + } + + // If our node has a position: + if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) { + const meshtastic_PositionLite &op = ourNode->position; + float myHeading; + if (screen->hasHeading()) + myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians + else + myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + screen->drawCompassNorth(display, compassX, compassY, myHeading); + + // Distance to Waypoint + float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + if (d < (2 * MILES_TO_FEET)) + snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); + else + snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); + } else { + if (d < 2000) + snprintf(distStr, sizeof(distStr), "%.0f m", d); + else + snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); + } + + // Compass bearing to waypoint + float bearingToOther = + GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i)); + // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly + // If the top of the compass is not a static north we need adjust bearingToOther based on heading + if (!config.display.compass_north_top) + bearingToOther -= myHeading; + screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); + } + + // If our node doesn't have position + else { + // ? in the compass + display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); + + // ? in the distance field + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + strncpy(distStr, "? mi", sizeof(distStr)); + else + strncpy(distStr, "? km", sizeof(distStr)); + } + + // Undo color-inversion, if set prior to drawing header + // Unsure of expected behavior? For now: copy drawNodeInfo + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->setColor(BLACK); + } + + // Draw compass circle + display->drawCircle(compassX, compassY, compassDiam / 2); + + // Must be after distStr is populated + screen->drawColumns(display, x, y, fields); +} +#endif \ No newline at end of file diff --git a/src/modules/WaypointModule.h b/src/modules/WaypointModule.h index ddbabf4de..4c9c7b86b 100644 --- a/src/modules/WaypointModule.h +++ b/src/modules/WaypointModule.h @@ -5,21 +5,29 @@ /** * Waypoint message handling for meshtastic */ -class WaypointModule : public SinglePortModule, public Observable +class WaypointModule : public SinglePortModule, public Observable { public: /** Constructor * name is for debugging output */ WaypointModule() : SinglePortModule("waypoint", meshtastic_PortNum_WAYPOINT_APP) {} - +#if HAS_SCREEN + bool shouldDraw(); +#endif protected: /** Called to handle a particular incoming message @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ + + virtual Observable *getUIFrameObservable() override { return this; } +#if HAS_SCREEN + virtual bool wantUIFrame() override { return this->shouldDraw(); } + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; }; -extern WaypointModule *waypointModule; +extern WaypointModule *waypointModule; \ No newline at end of file From 469ae0ff846944be12f293023954aad917e4ffcf Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 30 Jun 2024 08:22:24 -0500 Subject: [PATCH 0656/1377] Fix flakey phone api transition from file manifest to complete (#4209) * Try fix flakey phone api transition from file manifest to complete * Skip --- src/mesh/PhoneAPI.cpp | 20 +++++++++++++------- src/mesh/PhoneAPI.h | 2 ++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index d6721b018..322b0cf5e 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -355,9 +355,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n"); // last element if (config_state == filesManifest.size()) { // also handles an empty filesManifest - state = STATE_SEND_COMPLETE_ID; config_state = 0; filesManifest.clear(); + // Skip to complete packet + sendConfigComplete(); } else { fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; fromRadioScratch.fileInfo = filesManifest.at(config_state); @@ -368,12 +369,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) } case STATE_SEND_COMPLETE_ID: - LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; - fromRadioScratch.config_complete_id = config_nonce; - config_nonce = 0; - state = STATE_SEND_PACKETS; - pauseBluetoothLogging = false; + sendConfigComplete(); break; case STATE_SEND_PACKETS: @@ -419,6 +415,16 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) return 0; } +void PhoneAPI::sendConfigComplete() +{ + LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; + fromRadioScratch.config_complete_id = config_nonce; + config_nonce = 0; + state = STATE_SEND_PACKETS; + pauseBluetoothLogging = false; +} + void PhoneAPI::handleDisconnect() { filesManifest.clear(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 1a2a065d3..3c3668300 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -104,6 +104,8 @@ class PhoneAPI */ size_t getFromRadio(uint8_t *buf); + void sendConfigComplete(); + /** * Return true if we have data available to send to the phone */ From 8177329eac557c0bc366428fc8fc500014b5185c Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sun, 30 Jun 2024 23:01:28 +0200 Subject: [PATCH 0657/1377] enable colors in platformio serial monitor (#4217) --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 720525f09..bcdcc0034 100644 --- a/platformio.ini +++ b/platformio.ini @@ -77,6 +77,7 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_DROPZONE=1 monitor_speed = 115200 +monitor_filters = direct lib_deps = jgromes/RadioLib@~6.6.0 From 3219d65387876e9582a5bd8c99bb5c15843d4344 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 30 Jun 2024 16:41:27 -0700 Subject: [PATCH 0658/1377] When talking via serial, encapsulate log messages in protobufs if necessary (#4187) * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs --------- Co-authored-by: Ben Meadors --- src/RedirectablePrint.cpp | 253 ++++++++++++++++++++------------------ src/RedirectablePrint.h | 17 ++- src/SerialConsole.cpp | 34 ++++- src/SerialConsole.h | 10 +- src/main.cpp | 2 +- src/mesh/PhoneAPI.cpp | 4 +- src/mesh/StreamAPI.cpp | 23 +++- src/mesh/StreamAPI.h | 5 +- 8 files changed, 207 insertions(+), 141 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 265bb42d6..782febd75 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -15,11 +15,6 @@ #include "platform/portduino/PortduinoGlue.h" #endif -/** - * A printer that doesn't go anywhere - */ -NoopPrint noopPrint; - #if HAS_WIFI || HAS_ETHERNET extern Syslog syslog; #endif @@ -39,7 +34,7 @@ void RedirectablePrint::setDestination(Print *_dest) size_t RedirectablePrint::write(uint8_t c) { // Always send the characters to our segger JTAG debugger -#ifdef SEGGER_STDOUT_CH +#ifdef USE_SEGGER SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif @@ -85,6 +80,134 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l return len; } +void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, va_list arg) +{ + size_t r = 0; + + // Cope with 0 len format strings, but look for new line terminator + bool hasNewline = *format && format[strlen(format) - 1] == '\n'; + + // If we are the first message on a report, include the header + if (!isContinuationMessage) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + // hms += tz.tz_dsttime * SEC_PER_HOUR; + // hms -= tz.tz_minuteswest * SEC_PER_MIN; + // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into h:m:s + int hour = hms / SEC_PER_HOUR; + int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN +#ifdef ARCH_PORTDUINO + ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); +#else + printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); +#endif + } else +#ifdef ARCH_PORTDUINO + ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); +#else + printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); +#endif + + auto thread = concurrency::OSThread::currentThread; + if (thread) { + print("["); + // printf("%p ", thread); + // assert(thread->ThreadName.length()); + print(thread->ThreadName); + print("] "); + } + } + r += vprintf(logLevel, format, arg); + + isContinuationMessage = !hasNewline; +} + +void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg) +{ +#if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO) + // if syslog is in use, collect the log messages and send them to syslog + if (syslog.isEnabled()) { + int ll = 0; + switch (logLevel[0]) { + case 'D': + ll = SYSLOG_DEBUG; + break; + case 'I': + ll = SYSLOG_INFO; + break; + case 'W': + ll = SYSLOG_WARN; + break; + case 'E': + ll = SYSLOG_ERR; + break; + case 'C': + ll = SYSLOG_CRIT; + break; + default: + ll = 0; + } + auto thread = concurrency::OSThread::currentThread; + if (thread) { + syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg); + } else { + syslog.vlogf(ll, format, arg); + } + } +#endif +} + +void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) +{ + if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { + bool isBleConnected = false; +#ifdef ARCH_ESP32 + isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); +#elif defined(ARCH_NRF52) + isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); +#endif + if (isBleConnected) { + char *message; + size_t initialLen; + size_t len; + initialLen = strlen(format); + message = new char[initialLen + 1]; + len = vsnprintf(message, initialLen + 1, format, arg); + if (len > initialLen) { + delete[] message; + message = new char[len + 1]; + vsnprintf(message, len + 1, format, arg); + } + auto thread = concurrency::OSThread::currentThread; +#ifdef ARCH_ESP32 + if (thread) + nimbleBluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); + else + nimbleBluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); +#elif defined(ARCH_NRF52) + if (thread) + nrf52Bluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); + else + nrf52Bluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); +#endif + delete[] message; + } + } +} + void RedirectablePrint::log(const char *logLevel, const char *format, ...) { #ifdef ARCH_PORTDUINO @@ -109,122 +232,10 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) va_list arg; va_start(arg, format); - // Cope with 0 len format strings, but look for new line terminator - bool hasNewline = *format && format[strlen(format) - 1] == '\n'; + log_to_serial(logLevel, format, arg); + log_to_syslog(logLevel, format, arg); + log_to_ble(logLevel, format, arg); - // If we are the first message on a report, include the header - if (!isContinuationMessage) { - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - Print::write("\u001b[34m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - Print::write("\u001b[32m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - Print::write("\u001b[33m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) - Print::write("\u001b[31m", 6); - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile - if (rtc_sec > 0) { - long hms = rtc_sec % SEC_PER_DAY; - // hms += tz.tz_dsttime * SEC_PER_HOUR; - // hms -= tz.tz_minuteswest * SEC_PER_MIN; - // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) - hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; - - // Tear apart hms into h:m:s - int hour = hms / SEC_PER_HOUR; - int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; - int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN -#ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); -#else - printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); -#endif - } else -#ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); -#else - printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); -#endif - - auto thread = concurrency::OSThread::currentThread; - if (thread) { - print("["); - // printf("%p ", thread); - // assert(thread->ThreadName.length()); - print(thread->ThreadName); - print("] "); - } - } - vprintf(logLevel, format, arg); - -#if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO) - // if syslog is in use, collect the log messages and send them to syslog - if (syslog.isEnabled()) { - int ll = 0; - switch (logLevel[0]) { - case 'D': - ll = SYSLOG_DEBUG; - break; - case 'I': - ll = SYSLOG_INFO; - break; - case 'W': - ll = SYSLOG_WARN; - break; - case 'E': - ll = SYSLOG_ERR; - break; - case 'C': - ll = SYSLOG_CRIT; - break; - default: - ll = 0; - } - auto thread = concurrency::OSThread::currentThread; - if (thread) { - syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg); - } else { - syslog.vlogf(ll, format, arg); - } - } -#endif - - isContinuationMessage = !hasNewline; - - if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { - bool isBleConnected = false; -#ifdef ARCH_ESP32 - isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); -#elif defined(ARCH_NRF52) - isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); -#endif - if (isBleConnected) { - char *message; - size_t initialLen; - size_t len; - initialLen = strlen(format); - message = new char[initialLen + 1]; - len = vsnprintf(message, initialLen + 1, format, arg); - if (len > initialLen) { - delete[] message; - message = new char[len + 1]; - vsnprintf(message, len + 1, format, arg); - } - auto thread = concurrency::OSThread::currentThread; -#ifdef ARCH_ESP32 - if (thread) - nimbleBluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); - else - nimbleBluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); -#elif defined(ARCH_NRF52) - if (thread) - nrf52Bluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); - else - nrf52Bluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); -#endif - delete[] message; - } - } va_end(arg); #ifdef HAS_FREE_RTOS xSemaphoreGive(inDebugPrint); diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h index a29ad9c74..3f20c894c 100644 --- a/src/RedirectablePrint.h +++ b/src/RedirectablePrint.h @@ -49,15 +49,12 @@ class RedirectablePrint : public Print void hexDump(const char *logLevel, unsigned char *buf, uint16_t len); std::string mt_sprintf(const std::string fmt_str, ...); -}; -class NoopPrint : public Print -{ - public: - virtual size_t write(uint8_t c) { return 1; } -}; + protected: + /// Subclasses can override if they need to change how we format over the serial port + virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); -/** - * A printer that doesn't go anywhere - */ -extern NoopPrint noopPrint; \ No newline at end of file + private: + void log_to_syslog(const char *logLevel, const char *format, va_list arg); + void log_to_ble(const char *logLevel, const char *format, va_list arg); +}; \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 41064f288..12b9d2bd0 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -34,7 +34,6 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con assert(!console); console = this; canWrite = false; // We don't send packets to our port until it has talked to us first - // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks #ifdef RP2040_SLOW_CLOCK Port.setTX(SERIAL2_TX); @@ -81,13 +80,40 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled. if (config.has_lora && config.device.serial_enabled) { - // Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets - if (!config.device.debug_log_enabled) - setDestination(&noopPrint); + // Switch to protobufs for log messages + usingProtobufs = true; canWrite = true; return StreamAPI::handleToRadio(buf, len); } else { return false; } +} + +void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) +{ + if (usingProtobufs) { + meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset + switch (logLevel[0]) { + case 'D': + ll = meshtastic_LogRecord_Level_DEBUG; + break; + case 'I': + ll = meshtastic_LogRecord_Level_INFO; + break; + case 'W': + ll = meshtastic_LogRecord_Level_WARNING; + break; + case 'E': + ll = meshtastic_LogRecord_Level_ERROR; + break; + case 'C': + ll = meshtastic_LogRecord_Level_CRITICAL; + break; + } + + auto thread = concurrency::OSThread::currentThread; + emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg); + } else + RedirectablePrint::log_to_serial(logLevel, format, arg); } \ No newline at end of file diff --git a/src/SerialConsole.h b/src/SerialConsole.h index f8891ba14..f1e636c9d 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -8,6 +8,11 @@ */ class SerialConsole : public StreamAPI, public RedirectablePrint, private concurrency::OSThread { + /** + * If true we are talking to a smart host and all messages (including log messages) must be framed as protobufs. + */ + bool usingProtobufs = false; + public: SerialConsole(); @@ -31,10 +36,13 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur protected: /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() override; + + /// Possibly switch to protobufs if we see a valid protobuf message + virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); }; // A simple wrapper to allow non class aware code write to the console void consolePrintf(const char *format, ...); void consoleInit(); -extern SerialConsole *console; +extern SerialConsole *console; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 462eaa0f4..196eae525 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -221,7 +221,7 @@ void setup() meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; -#ifdef SEGGER_STDOUT_CH +#ifdef USE_SEGGER auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM; #ifdef NRF52840_XXAA auto buflen = 4096; // this board has a fair amount of ram diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 322b0cf5e..0f69b21f9 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -407,7 +407,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Encapsulate as a FromRadio packet size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch); - LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes); + // VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer + // for logging (when we are encapsulating with protobufs) + // LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes); return numbytes; } diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 4d04dffe4..9f59aa971 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -1,5 +1,6 @@ #include "StreamAPI.h" #include "PowerFSM.h" +#include "RTC.h" #include "configuration.h" #define START1 0x94 @@ -96,7 +97,6 @@ void StreamAPI::writeStream() void StreamAPI::emitTxBuffer(size_t len) { if (len != 0) { - // LOG_DEBUG("emit tx %d\n", len); txBuf[0] = START1; txBuf[1] = START2; txBuf[2] = (len >> 8) & 0xff; @@ -119,6 +119,25 @@ void StreamAPI::emitRebooted() emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch)); } +void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg) +{ + // In case we send a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_log_record_tag; + fromRadioScratch.log_record.level = level; + + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); + fromRadioScratch.log_record.time = rtc_sec; + strncpy(fromRadioScratch.log_record.source, src, sizeof(fromRadioScratch.log_record.source) - 1); + + auto num_printed = + vsnprintf(fromRadioScratch.log_record.message, sizeof(fromRadioScratch.log_record.message) - 1, format, arg); + if (num_printed > 0 && fromRadioScratch.log_record.message[num_printed - 1] == + '\n') // Strip any ending newline, because we have records for framing instead. + fromRadioScratch.log_record.message[num_printed - 1] = '\0'; + emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch)); +} + /// Hookable to find out when connection changes void StreamAPI::onConnectionChanged(bool connected) { @@ -131,4 +150,4 @@ void StreamAPI::onConnectionChanged(bool connected) // received a packet in a while powerFSM.trigger(EVENT_SERIAL_DISCONNECTED); } -} +} \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 3196e96f8..45cbb231c 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -82,4 +82,7 @@ class StreamAPI : public PhoneAPI /// Subclasses can use this scratch buffer if they wish uint8_t txBuf[MAX_STREAM_BUF_SIZE] = {0}; -}; + + /// Low level function to emit a protobuf encapsulated log record + void emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg); +}; \ No newline at end of file From 9701f35a83fc9d57a91626b97dc8113f5e106375 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 06:29:44 -0500 Subject: [PATCH 0659/1377] [create-pull-request] automated change (#4218) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index e7327e76b..1198b7dba 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e7327e76bdc0b3b77c50e214fae5beb1cb303e9c +Subproject commit 1198b7dbabf9768cb0143d2897707b4c7a51a5da diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 5e245a2b5..dbe9281ec 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -167,6 +167,15 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64, /* Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors */ meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65, + /* Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display */ + meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 = 66, + /* Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display */ + meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 = 67, + /* Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display */ + meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 = 68, + /* Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design, + specifically adapted for the Meshtatic project */ + meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From e65c309af60bd897c72ac9d49bd9ed52e13c327f Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Jul 2024 20:03:51 +0800 Subject: [PATCH 0660/1377] Fix SHT41 support (#4222) * Add SHT41 Serial to I2c Detection Code On the Seeed Wio-WM1110 Dev Kit board, the SHT41 chip was being incorrectly detected as SHT31. This patch adds the necessary serial number for the SHT41 chip to be correctly detected. fixes meshtastic/firmware#4221 * Add missing sensor read for SHT41 --- src/detect/ScanI2CTwoWire.cpp | 4 ++-- src/modules/Telemetry/EnvironmentTelemetry.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 86408b8d2..8738e2722 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -314,7 +314,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case SHT31_4x_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2) { + if (registerValue == 0x11a2 || registerValue == 0x11da) { type = SHT4X; LOG_INFO("SHT4X sensor found\n"); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { @@ -402,4 +402,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} \ No newline at end of file +} diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index b69b2bfae..d37bb754d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -285,6 +285,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && sht31Sensor.getMetrics(m); hasSensor = true; } + if (sht4xSensor.hasSensor()) { + valid = valid && sht4xSensor.getMetrics(m); + hasSensor = true; + } if (lps22hbSensor.hasSensor()) { valid = valid && lps22hbSensor.getMetrics(m); hasSensor = true; @@ -533,4 +537,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule return result; } -#endif \ No newline at end of file +#endif From 10b157a38d3fe353ece472f556b0cf8c97cea180 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 3 Jul 2024 22:04:39 +0800 Subject: [PATCH 0661/1377] Typo fix in logs - mhz - MHz (#4225) As reported by karamo, a few different places in our logs had incorrect capitalization of MHz. fixes meshtastic/firmware#4126 --- src/mesh/RadioInterface.cpp | 2 +- src/sleep.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index cdea33717..343b7f200 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -521,7 +521,7 @@ void RadioInterface::applyModemConfig() LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, loraConfig.frequency_offset); LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset, channel_num, power); - LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f mhz)\n", myRegion->freqStart, myRegion->freqEnd, + LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart); LOG_INFO("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw); LOG_INFO("Radio channel_num: %d\n", channel_num + 1); diff --git a/src/sleep.cpp b/src/sleep.cpp index c9c45bf67..1c1a6e9ac 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -62,14 +62,14 @@ void setCPUFast(bool on) /* * * There's a newly introduced bug in the espressif framework where WiFi is - * unstable when the frequency is less than 240mhz. + * unstable when the frequency is less than 240MHz. * * This mostly impacts WiFi AP mode but we'll bump the frequency for * all WiFi use cases. * (Added: Dec 23, 2021 by Jm Casler) */ #ifndef CONFIG_IDF_TARGET_ESP32C3 - LOG_DEBUG("Setting CPU to 240mhz because WiFi is in use.\n"); + LOG_DEBUG("Setting CPU to 240MHz because WiFi is in use.\n"); setCpuFrequencyMhz(240); #endif return; From 9c46bdad1abca401ab51fe7b865436c6756bd71c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 3 Jul 2024 16:29:07 -0500 Subject: [PATCH 0662/1377] New new BLE logging characteristic with LogRecord protos (#4220) * New UUID * New log radio characteristic with LogRecord protobuf * LogRecord * Merge derp * How did you get there * Trunk * Fix length * Remove assert --- src/BluetoothCommon.cpp | 6 +++-- src/BluetoothCommon.h | 4 ++-- src/RedirectablePrint.cpp | 43 +++++++++++++++++++++++++++------- src/RedirectablePrint.h | 2 ++ src/mesh/Router.cpp | 6 +++-- src/nimble/NimbleBluetooth.cpp | 7 +++--- src/nimble/NimbleBluetooth.h | 2 +- 7 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp index 7ef1c39b4..d9502e4f5 100644 --- a/src/BluetoothCommon.cpp +++ b/src/BluetoothCommon.cpp @@ -11,5 +11,7 @@ const uint8_t FROMRADIO_UUID_16[16u] = {0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78 0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c}; const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; -const uint8_t LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa, - 0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c}; \ No newline at end of file +const uint8_t LEGACY_LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa, + 0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c}; +const uint8_t LOGRADIO_UUID_16[16u] = {0x47, 0x95, 0xDF, 0x8C, 0xDE, 0xE9, 0x44, 0x99, + 0x23, 0x44, 0xE6, 0x06, 0x49, 0x6E, 0x3D, 0x5A}; \ No newline at end of file diff --git a/src/BluetoothCommon.h b/src/BluetoothCommon.h index 5497e1d6d..440d13844 100644 --- a/src/BluetoothCommon.h +++ b/src/BluetoothCommon.h @@ -11,7 +11,8 @@ #define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7" #define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002" #define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453" -#define LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2" +#define LEGACY_LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2" +#define LOGRADIO_UUID "5a3d6e49-06e6-4423-9944-e9de8cdf9547" // NRF52 wants these constants as byte arrays // Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER @@ -28,5 +29,4 @@ class BluetoothApi virtual void clearBonds(); virtual bool isConnected(); virtual int getRssi() = 0; - virtual void sendLog(const char *logMessage); }; \ No newline at end of file diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 782febd75..555e45401 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -4,6 +4,7 @@ #include "concurrency/OSThread.h" #include "configuration.h" #include "main.h" +#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -192,22 +193,48 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ vsnprintf(message, len + 1, format, arg); } auto thread = concurrency::OSThread::currentThread; + meshtastic_LogRecord logRecord = meshtastic_LogRecord_init_zero; + logRecord.level = getLogLevel(logLevel); + strcpy(logRecord.message, message); + if (thread) + strcpy(logRecord.source, thread->ThreadName.c_str()); + logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true); + + uint8_t *buffer = new uint8_t[meshtastic_LogRecord_size]; + size_t size = pb_encode_to_bytes(buffer, meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); #ifdef ARCH_ESP32 - if (thread) - nimbleBluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); - else - nimbleBluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); + nimbleBluetooth->sendLog(buffer, size); #elif defined(ARCH_NRF52) - if (thread) - nrf52Bluetooth->sendLog(mt_sprintf("%s | [%s] %s", logLevel, thread->ThreadName.c_str(), message).c_str()); - else - nrf52Bluetooth->sendLog(mt_sprintf("%s | %s", logLevel, message).c_str()); + nrf52Bluetooth->sendLog(reinterpret_cast(buffer)); #endif delete[] message; } } } +meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel) +{ + meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset + switch (logLevel[0]) { + case 'D': + ll = meshtastic_LogRecord_Level_DEBUG; + break; + case 'I': + ll = meshtastic_LogRecord_Level_INFO; + break; + case 'W': + ll = meshtastic_LogRecord_Level_WARNING; + break; + case 'E': + ll = meshtastic_LogRecord_Level_ERROR; + break; + case 'C': + ll = meshtastic_LogRecord_Level_CRITICAL; + break; + } + return ll; +} + void RedirectablePrint::log(const char *logLevel, const char *format, ...) { #ifdef ARCH_PORTDUINO diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h index 3f20c894c..23ae3c44d 100644 --- a/src/RedirectablePrint.h +++ b/src/RedirectablePrint.h @@ -1,6 +1,7 @@ #pragma once #include "../freertosinc.h" +#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -57,4 +58,5 @@ class RedirectablePrint : public Print private: void log_to_syslog(const char *logLevel, const char *format, va_list arg); void log_to_ble(const char *logLevel, const char *format, va_list arg); + meshtastic_LogRecord_Level getLogLevel(const char *logLevel); }; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3141d986b..c8c18ae6d 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -244,8 +244,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || - p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + if (!(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || + p->which_payload_variant == meshtastic_MeshPacket_decoded_tag)) { + return meshtastic_Routing_Error_BAD_REQUEST; + } // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 78ef5a1d3..d959553a4 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -219,7 +219,6 @@ void NimbleBluetooth::setupService() logRadioCharacteristic = bleService->createCharacteristic( LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U); - logRadioCharacteristic->setValue("Init"); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -268,12 +267,12 @@ void NimbleBluetooth::clearBonds() NimBLEDevice::deleteAllBonds(); } -void NimbleBluetooth::sendLog(const char *logMessage) +void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) { - if (!bleServer || !isConnected() || strlen(logMessage) > 512) { + if (!bleServer || !isConnected() || length > 512) { return; } - logRadioCharacteristic->notify(reinterpret_cast(logMessage), strlen(logMessage), true); + logRadioCharacteristic->notify(logMessage, length, true); } void clearNVS() diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 39794779b..45602e088 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -11,7 +11,7 @@ class NimbleBluetooth : BluetoothApi bool isActive(); bool isConnected(); int getRssi(); - void sendLog(const char *logMessage); + void sendLog(const uint8_t *logMessage, size_t length); private: void setupService(); From 8785adf6e4f59df40bc686cebfbb4946b7816135 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jul 2024 15:39:09 -0700 Subject: [PATCH 0663/1377] minor cleanup proposal (#4169) * MESHTASTIC_EXCLUDE_WIFI and HAS_WIFI cleanup... Our code was checking HAS_WIFI and the new MESHTASTIC_EXCLUDE_WIFI flags in various places (even though EXCLUDE_WIFI forces HAS_WIFI to 0). Instead just check HAS_WIFI, only use EXCLUDE_WIFI inside configuration.h * cleanup: use HAS_NETWORKING instead of HAS_WIFI || HAS_ETHERNET We already had HAS_NETWORKING as flag in MQTT to mean 'we have tcpip'. Generallize that and move it into configuration.h so that we can use it elsewhere. * Use #pragma once, because supported by gcc and all modern compilers instead of #ifdef DOTHFILE_H etc... --------- Co-authored-by: Jonathan Bennett --- src/DebugConfiguration.cpp | 2 +- src/DebugConfiguration.h | 11 +++++------ src/Power.cpp | 2 +- src/RedirectablePrint.cpp | 4 ++-- src/configuration.h | 11 +++++++---- src/mesh/NodeDB.cpp | 2 +- src/mesh/http/ContentHandler.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 2 +- src/modules/AdminModule.h | 2 +- src/mqtt/MQTT.cpp | 18 +++++++++--------- src/mqtt/MQTT.h | 6 ++---- src/platform/esp32/main-esp32.cpp | 29 ++++++++++++++--------------- src/sleep.cpp | 4 ++-- 13 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index 9df402e77..d9ecd9fe3 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,7 +26,7 @@ SOFTWARE.*/ #include "DebugConfiguration.h" -#if HAS_WIFI || HAS_ETHERNET +#if HAS_NETWORKING Syslog::Syslog(UDP &client) { diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index 874d63bca..ebe9da8d4 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -1,5 +1,6 @@ -#ifndef SYSLOG_H -#define SYSLOG_H +#pragma once + +#include "configuration.h" // DEBUG LED #ifndef LED_INVERTED @@ -125,7 +126,7 @@ #include #endif // HAS_WIFI -#if HAS_WIFI || HAS_ETHERNET +#if HAS_NETWORKING class Syslog { @@ -160,6 +161,4 @@ class Syslog bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0))); }; -#endif // HAS_ETHERNET || HAS_WIFI - -#endif // SYSLOG_H \ No newline at end of file +#endif // HAS_ETHERNET || HAS_WIFI \ No newline at end of file diff --git a/src/Power.cpp b/src/Power.cpp index 18a527cee..cea373806 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -27,7 +27,7 @@ #if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #include "target_specific.h" -#if !MESTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include #endif #endif diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 555e45401..14264ba21 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -16,7 +16,7 @@ #include "platform/portduino/PortduinoGlue.h" #endif -#if HAS_WIFI || HAS_ETHERNET +#if HAS_NETWORKING extern Syslog syslog; #endif void RedirectablePrint::rpInit() @@ -138,7 +138,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg) { -#if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO) +#if HAS_NETWORKING && !defined(ARCH_PORTDUINO) // if syslog is in use, collect the log messages and send them to syslog if (syslog.isEnabled()) { int ll = 0; diff --git a/src/configuration.h b/src/configuration.h index 3d10feeaa..854d3dadf 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -242,9 +242,6 @@ along with this program. If not, see . #define HAS_BLUETOOTH 0 #endif -#include "DebugConfiguration.h" -#include "RF95Configuration.h" - #ifndef HW_VENDOR #error HW_VENDOR must be defined #endif @@ -290,6 +287,9 @@ along with this program. If not, see . #define HAS_WIFI 0 #endif +// Allow code that needs internet to just check HAS_NETWORKING rather than HAS_WIFI || HAS_ETHERNET +#define HAS_NETWORKING (HAS_WIFI || HAS_ETHERNET) + // // Turn off Bluetooth #ifdef MESHTASTIC_EXCLUDE_BLUETOOTH #undef HAS_BLUETOOTH @@ -308,4 +308,7 @@ along with this program. If not, see . #ifdef MESHTASTIC_EXCLUDE_SCREEN #undef HAS_SCREEN #define HAS_SCREEN 0 -#endif \ No newline at end of file +#endif + +#include "DebugConfiguration.h" +#include "RF95Configuration.h" \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1dc6d7883..84872e471 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -26,7 +26,7 @@ #include #ifdef ARCH_ESP32 -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "modules/esp32/StoreForwardModule.h" diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7f9df058d..b309484e2 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -6,7 +6,7 @@ #include "main.h" #include "mesh/http/ContentHelper.h" #include "mesh/http/WebServer.h" -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "mqtt/JSON.h" diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index ffb16bd3e..e733d1801 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -1,5 +1,5 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 32b32c253..6ecc88829 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,6 +1,6 @@ #pragma once #include "ProtobufModule.h" -#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 74f3ca5a3..a64720c78 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -14,7 +14,7 @@ #endif #include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "sleep.h" -#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #include #endif @@ -175,7 +175,7 @@ void mqttInit() new MQTT(); } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_MQTT_QUEUE) #else MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) @@ -206,7 +206,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING if (!moduleConfig.mqtt.proxy_to_client_enabled) pubSub.setCallback(mqttCallback); #endif @@ -226,7 +226,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) bool MQTT::isConnectedDirectly() { -#ifdef HAS_NETWORKING +#if HAS_NETWORKING return pubSub.connected(); #else return false; @@ -244,7 +244,7 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) service.sendMqttMessageToClientProxy(msg); return true; } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING else if (isConnectedDirectly()) { return pubSub.publish(topic, payload, retained); } @@ -264,7 +264,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo service.sendMqttMessageToClientProxy(msg); return true; } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING else if (isConnectedDirectly()) { return pubSub.publish(topic, payload, length, retained); } @@ -284,7 +284,7 @@ void MQTT::reconnect() publishStatus(); return; // Don't try to connect directly to the server } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING // Defaults int serverPort = 1883; const char *serverAddr = default_mqtt_address; @@ -357,7 +357,7 @@ void MQTT::reconnect() void MQTT::sendSubscriptions() { -#ifdef HAS_NETWORKING +#if HAS_NETWORKING size_t numChan = channels.getNumChannels(); for (size_t i = 0; i < numChan; i++) { const auto &ch = channels.getByIndex(i); @@ -396,7 +396,7 @@ bool MQTT::wantsLink() const int32_t MQTT::runOnce() { -#ifdef HAS_NETWORKING +#if HAS_NETWORKING if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index f2eb6b120..1ebba4afe 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -8,17 +8,15 @@ #include "mqtt/JSON.h" #if HAS_WIFI #include -#define HAS_NETWORKING 1 #if !defined(ARCH_PORTDUINO) #include #endif #endif #if HAS_ETHERNET #include -#define HAS_NETWORKING 1 #endif -#ifdef HAS_NETWORKING +#if HAS_NETWORKING #include #endif @@ -43,7 +41,7 @@ class MQTT : private concurrency::OSThread #endif public: -#ifdef HAS_NETWORKING +#if HAS_NETWORKING PubSubClient pubSub; #endif MQTT(); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 1dd7a389a..aa51e810a 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -8,7 +8,7 @@ #include "nimble/NimbleBluetooth.h" #endif -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -24,23 +24,22 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { -#ifndef MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI if (!isWifiAvailable() && config.bluetooth.enabled == true) +#else + if (config.bluetooth.enabled == true) #endif -#ifdef MESHTASTIC_EXCLUDE_WIFI - if (config.bluetooth.enabled == true) -#endif - { - if (!nimbleBluetooth) { - nimbleBluetooth = new NimbleBluetooth(); - } - if (enable && !nimbleBluetooth->isActive()) { - nimbleBluetooth->setup(); - } - // For ESP32, no way to recover from bluetooth shutdown without reboot - // BLE advertising automatically stops when MCU enters light-sleep(?) - // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse + { + if (!nimbleBluetooth) { + nimbleBluetooth = new NimbleBluetooth(); } + if (enable && !nimbleBluetooth->isActive()) { + nimbleBluetooth->setup(); + } + // For ESP32, no way to recover from bluetooth shutdown without reboot + // BLE advertising automatically stops when MCU enters light-sleep(?) + // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse + } } #else void setBluetoothEnable(bool enable) {} diff --git a/src/sleep.cpp b/src/sleep.cpp index 1c1a6e9ac..735ebcf6a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -17,7 +17,7 @@ #ifdef ARCH_ESP32 #include "esp32/pm.h" #include "esp_pm.h" -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "rom/rtc.h" @@ -56,7 +56,7 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI +#if defined(ARCH_ESP32) && HAS_WIFI if (isWifiAvailable()) { /* From 8bca3e168d5f89ab3168937a1384c32bda27d0d9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jul 2024 16:02:20 -0700 Subject: [PATCH 0664/1377] Add PowerMon support (#4155) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * fix linter warning --- bin/setup-python-for-esp-debug.sh | 12 +++++ boards/wio-sdk-wm1110.json | 2 +- src/PowerFSM.cpp | 17 +++++++ src/PowerMon.cpp | 45 ++++++++++++++++++ src/PowerMon.h | 34 ++++++++++++++ src/configuration.h | 2 + src/gps/GPS.cpp | 4 ++ src/main.cpp | 12 ++++- src/mesh/LR11x0Interface.cpp | 3 +- src/mesh/RF95Interface.cpp | 31 +++++++------ src/mesh/RadioLibInterface.cpp | 21 +++++++++ src/mesh/RadioLibInterface.h | 13 ++++-- src/mesh/SX126xInterface.cpp | 3 +- src/mesh/SX128xInterface.cpp | 3 +- src/modules/Modules.cpp | 6 +++ src/modules/PowerStressModule.cpp | 77 +++++++++++++++++++++++++++++++ src/modules/PowerStressModule.h | 38 +++++++++++++++ src/sleep.cpp | 6 +++ 18 files changed, 306 insertions(+), 23 deletions(-) create mode 100644 bin/setup-python-for-esp-debug.sh create mode 100644 src/PowerMon.cpp create mode 100644 src/PowerMon.h create mode 100644 src/modules/PowerStressModule.cpp create mode 100644 src/modules/PowerStressModule.h diff --git a/bin/setup-python-for-esp-debug.sh b/bin/setup-python-for-esp-debug.sh new file mode 100644 index 000000000..edba43e72 --- /dev/null +++ b/bin/setup-python-for-esp-debug.sh @@ -0,0 +1,12 @@ +# shellcheck shell=bash +# (this minor script is actually shell agnostic, and is intended to be sourced rather than run in a subshell) + +# This is a little script you can source if you want to make ESP debugging work on a modern (24.04) ubuntu machine +# It assumes you have built and installed python 2.7 from source with: +# ./configure --enable-optimizations --enable-shared --enable-unicode=ucs4 +# sudo make clean +# make +# sudo make altinstall + +export LD_LIBRARY_PATH=$HOME/packages/python-2.7.18/ +export PYTHON_HOME=/usr/local/lib/python2.7/ diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index 882f4443e..18c87adde 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -27,7 +27,7 @@ "jlink_device": "nRF52840_xxAA", "svd_path": "nrf52840.svd" }, - "frameworks": ["arduino"], + "frameworks": ["arduino", "freertos"], "name": "Seeed WIO WM1110", "upload": { "maximum_ram_size": 248832, diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index a7bc18f1a..72e00810b 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -11,6 +11,7 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "PowerMon.h" #include "configuration.h" #include "graphics/Screen.h" #include "main.h" @@ -49,6 +50,7 @@ static bool isPowered() static void sdsEnter() { LOG_DEBUG("Enter state: SDS\n"); + powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false); } @@ -68,6 +70,7 @@ static uint32_t secsSlept; static void lsEnter() { LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs); + powerMon->clearState(meshtastic_PowerMon_State_Screen_On); screen->setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -87,8 +90,10 @@ static void lsIdle() // Briefly come out of sleep long enough to blink the led once every few seconds uint32_t sleepTime = SLEEP_TIME; + powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); setLed(false); // Never leave led on while in light sleep esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); + powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); switch (wakeCause2) { case ESP_SLEEP_WAKEUP_TIMER: @@ -144,6 +149,7 @@ static void lsExit() static void nbEnter() { LOG_DEBUG("Enter state: NB\n"); + powerMon->clearState(meshtastic_PowerMon_State_BT_On); screen->setOn(false); #ifdef ARCH_ESP32 // Only ESP32 should turn off bluetooth @@ -155,6 +161,8 @@ static void nbEnter() static void darkEnter() { + powerMon->clearState(meshtastic_PowerMon_State_BT_On); + powerMon->clearState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(true); screen->setOn(false); } @@ -162,6 +170,8 @@ static void darkEnter() static void serialEnter() { LOG_DEBUG("Enter state: SERIAL\n"); + powerMon->clearState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(false); screen->setOn(true); screen->print("Serial connected\n"); @@ -170,6 +180,7 @@ static void serialEnter() static void serialExit() { // Turn bluetooth back on when we leave serial stream API + powerMon->setState(meshtastic_PowerMon_State_BT_On); setBluetoothEnable(true); screen->print("Serial disconnected\n"); } @@ -182,6 +193,8 @@ static void powerEnter() LOG_INFO("Loss of power in Powered\n"); powerFSM.trigger(EVENT_POWER_DISCONNECTED); } else { + powerMon->setState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); // within enter() the function getState() returns the state we came from @@ -205,6 +218,8 @@ static void powerIdle() static void powerExit() { + powerMon->setState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); @@ -216,6 +231,8 @@ static void powerExit() static void onEnter() { LOG_DEBUG("Enter state: ON\n"); + powerMon->setState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); } diff --git a/src/PowerMon.cpp b/src/PowerMon.cpp new file mode 100644 index 000000000..3d28715e0 --- /dev/null +++ b/src/PowerMon.cpp @@ -0,0 +1,45 @@ +#include "PowerMon.h" +#include "NodeDB.h" + +// Use the 'live' config flag to figure out if we should be showing this message +static bool is_power_enabled(uint64_t m) +{ + return (m & config.power.powermon_enables) ? true : false; +} + +void PowerMon::setState(_meshtastic_PowerMon_State state, const char *reason) +{ +#ifdef USE_POWERMON + auto oldstates = states; + states |= state; + if (oldstates != states && is_power_enabled(state)) { + emitLog(reason); + } +#endif +} + +void PowerMon::clearState(_meshtastic_PowerMon_State state, const char *reason) +{ +#ifdef USE_POWERMON + auto oldstates = states; + states &= ~state; + if (oldstates != states && is_power_enabled(state)) { + emitLog(reason); + } +#endif +} + +void PowerMon::emitLog(const char *reason) +{ +#ifdef USE_POWERMON + // The nrf52 printf doesn't understand 64 bit ints, so if we ever reach that point this function will need to change. + LOG_INFO("S:PM:0x%08lx,%s\n", (uint32_t)states, reason); +#endif +} + +PowerMon *powerMon; + +void powerMonInit() +{ + powerMon = new PowerMon(); +} \ No newline at end of file diff --git a/src/PowerMon.h b/src/PowerMon.h new file mode 100644 index 000000000..e9f5dbd59 --- /dev/null +++ b/src/PowerMon.h @@ -0,0 +1,34 @@ +#pragma once +#include "configuration.h" + +#include "meshtastic/powermon.pb.h" + +#ifndef MESHTASTIC_EXCLUDE_POWERMON +#define USE_POWERMON // FIXME turn this only for certain builds +#endif + +/** + * The singleton class for monitoring power consumption of device + * subsystems/modes. + * + * For more information see the PowerMon docs. + */ +class PowerMon +{ + uint64_t states = 0UL; + + public: + PowerMon() {} + + // Mark entry/exit of a power consuming state + void setState(_meshtastic_PowerMon_State state, const char *reason = ""); + void clearState(_meshtastic_PowerMon_State state, const char *reason = ""); + + private: + // Emit the coded log message + void emitLog(const char *reason); +}; + +extern PowerMon *powerMon; + +void powerMonInit(); \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 854d3dadf..aad4ac457 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -258,6 +258,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_GPS 1 #define MESHTASTIC_EXCLUDE_SCREEN 1 #define MESHTASTIC_EXCLUDE_MQTT 1 +#define MESHTASTIC_EXCLUDE_POWERMON 1 #endif // Turn off all optional modules @@ -278,6 +279,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_WAYPOINT 1 #define MESHTASTIC_EXCLUDE_INPUTBROKER 1 #define MESHTASTIC_EXCLUDE_SERIAL 1 +#define MESHTASTIC_EXCLUDE_POWERSTRESS 1 #endif // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 5efe96251..ec7d725b8 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -3,6 +3,7 @@ #include "Default.h" #include "GPS.h" #include "NodeDB.h" +#include "PowerMon.h" #include "RTC.h" #include "main.h" // pmu_found @@ -815,9 +816,12 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) return; if (on) { + powerMon->setState(meshtastic_PowerMon_State_GPS_Active); clearBuffer(); // drop any old data waiting in the buffer before re-enabling if (en_gpio) digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time + } else { + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); } isInPowersave = !on; if (!standbyOnly && en_gpio != 0 && diff --git a/src/main.cpp b/src/main.cpp index 196eae525..1e0d998e1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "PowerMon.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" @@ -214,6 +215,14 @@ __attribute__((weak, noinline)) bool loopCanSleep() return true; } +/** + * Print info as a structured log message (for automated log processing) + */ +void printInfo() +{ + LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION)); +} + void setup() { concurrency::hasBeenSetup = true; @@ -234,6 +243,7 @@ void setup() #ifdef DEBUG_PORT consoleInit(); // Set serial baud rate and init our mesh console #endif + powerMonInit(); serialSinceMsec = millis(); @@ -553,7 +563,7 @@ void setup() #endif // Hello - LOG_INFO("Meshtastic hwvendor=%d, swver=%s\n", HW_VENDOR, optstr(APP_VERSION)); + printInfo(); #ifdef ARCH_ESP32 esp32Setup(); diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index bffca0c44..fc059ec16 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -184,6 +184,7 @@ template void LR11x0Interface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** @@ -223,7 +224,7 @@ template void LR11x0Interface::startReceive() 0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving assert(err == RADIOLIB_ERR_NONE); - isReceiving = true; + RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index c5356ad3b..bd1ebdb0e 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -25,7 +25,8 @@ typedef struct { } DACDB; // Interpolation function -DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) { +DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) +{ DACDB result; double fraction = (double)(dbm - dbm1) / (dbm2 - dbm1); result.dac = (uint8_t)(val1.dac + fraction * (val2.dac - val1.dac)); @@ -34,16 +35,17 @@ DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val } // Function to find the correct DAC and DB values based on dBm using interpolation -DACDB getDACandDB(uint8_t dbm) { +DACDB getDACandDB(uint8_t dbm) +{ // Predefined values static const struct { uint8_t dbm; DACDB values; } dbmToDACDB[] = { - {20, {168, 2}}, // 100mW - {24, {148, 6}}, // 250mW - {27, {128, 9}}, // 500mW - {30, {90, 12}} // 1000mW + {20, {168, 2}}, // 100mW + {24, {148, 6}}, // 250mW + {27, {128, 9}}, // 500mW + {30, {90, 12}} // 1000mW }; const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); @@ -103,7 +105,7 @@ bool RF95Interface::init() if (power > RF95_MAX_POWER) // This chip has lower power limits than some power = RF95_MAX_POWER; - + limitPower(); iface = lora = new RadioLibRF95(&module); @@ -116,13 +118,13 @@ bool RF95Interface::init() // enable PA #ifdef RF95_PA_EN #if defined(RF95_PA_DAC_EN) - #ifdef RADIOMASTER_900_BANDIT_NANO - // Use calculated DAC value - dacWrite(RF95_PA_EN, powerDAC); - #else - // Use Value set in /*/variant.h - dacWrite(RF95_PA_EN, RF95_PA_LEVEL); - #endif +#ifdef RADIOMASTER_900_BANDIT_NANO + // Use calculated DAC value + dacWrite(RF95_PA_EN, powerDAC); +#else + // Use Value set in /*/variant.h + dacWrite(RF95_PA_EN, RF95_PA_LEVEL); +#endif #endif #endif @@ -254,6 +256,7 @@ void RF95Interface::setStandby() isReceiving = false; // If we were receiving, not any more disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index a4ceac9f1..f299ebff2 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -1,6 +1,7 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" #include "NodeDB.h" +#include "PowerMon.h" #include "SPILock.h" #include "configuration.h" #include "error.h" @@ -317,6 +318,7 @@ void RadioLibInterface::handleTransmitInterrupt() // ignore the transmit interrupt if (sendingPacket) completeSending(); + powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // But our transmitter is deffinitely off now } void RadioLibInterface::completeSending() @@ -412,6 +414,24 @@ void RadioLibInterface::handleReceiveInterrupt() } } +void RadioLibInterface::startReceive() +{ + isReceiving = true; + powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); +} + +void RadioLibInterface::configHardwareForSend() +{ + powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); +} + +void RadioLibInterface::setStandby() +{ + // neither sending nor receiving + powerMon->clearState(meshtastic_PowerMon_State_Lora_RXOn); + powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); +} + /** start an immediate transmit */ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) { @@ -431,6 +451,7 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) // This send failed, but make sure to 'complete' it properly completeSending(); + powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // Transmitter off now startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode) } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 2c841a19e..dd01d2037 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -126,8 +126,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified * Start waiting to receive a message * * External functions can call this method to wake the device from sleep. + * Subclasses must override and call this base method */ - virtual void startReceive() = 0; + virtual void startReceive(); /** can we detect a LoRa preamble on the current channel? */ virtual bool isChannelActive() = 0; @@ -166,8 +167,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified meshtastic_QueueStatus getQueueStatus(); protected: - /** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */ - virtual void configHardwareForSend() {} + /** Do any hardware setup needed on entry into send configuration for the radio. + * Subclasses can customize, but must also call this base method */ + virtual void configHardwareForSend(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately(); @@ -186,5 +188,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0; - virtual void setStandby() = 0; + /** + * Subclasses must override, implement and then call into this base class implementation + */ + virtual void setStandby(); }; \ No newline at end of file diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index afaa13b7f..b564ba287 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -231,6 +231,7 @@ template void SX126xInterface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** @@ -270,7 +271,7 @@ template void SX126xInterface::startReceive() LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); - isReceiving = true; + RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 9e4fbfa77..fdb2b9a39 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -190,6 +190,7 @@ template void SX128xInterface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** @@ -263,7 +264,7 @@ template void SX128xInterface::startReceive() LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); - isReceiving = true; + RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index ba1f5c11e..300afc246 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -27,6 +27,9 @@ #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE #include "modules/RemoteHardwareModule.h" #endif +#if !MESHTASTIC_EXCLUDE_POWERSTRESS +#include "modules/PowerStressModule.h" +#endif #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" #if !MESHTASTIC_EXCLUDE_TRACEROUTE @@ -115,6 +118,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE new RemoteHardwareModule(); +#endif +#if !MESHTASTIC_EXCLUDE_POWERSTRESS + new PowerStressModule(); #endif // Example: Put your module here // new ReplyModule(); diff --git a/src/modules/PowerStressModule.cpp b/src/modules/PowerStressModule.cpp new file mode 100644 index 000000000..c86017ae2 --- /dev/null +++ b/src/modules/PowerStressModule.cpp @@ -0,0 +1,77 @@ +#include "PowerStressModule.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" +#include "Router.h" +#include "configuration.h" +#include "main.h" + +extern void printInfo(); + +PowerStressModule::PowerStressModule() + : ProtobufModule("powerstress", meshtastic_PortNum_POWERSTRESS_APP, &meshtastic_PowerStressMessage_msg), + concurrency::OSThread("PowerStressModule") +{ +} + +bool PowerStressModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_PowerStressMessage *pptr) +{ + // We only respond to messages if powermon debugging is already on + if (config.power.powermon_enables) { + auto p = *pptr; + LOG_INFO("Received PowerStress cmd=%d\n", p.cmd); + + // Some commands we can handle immediately, anything else gets deferred to be handled by our thread + switch (p.cmd) { + case meshtastic_PowerStressMessage_Opcode_UNSET: + LOG_ERROR("PowerStress operation unset\n"); + break; + + case meshtastic_PowerStressMessage_Opcode_PRINT_INFO: + printInfo(); + break; + + default: + if (currentMessage.cmd != meshtastic_PowerStressMessage_Opcode_UNSET) + LOG_ERROR("PowerStress operation %d already in progress! Can't start new command\n", currentMessage.cmd); + else + currentMessage = p; // copy for use by thread (the message provided to us will be getting freed) + break; + } + } + return true; +} + +int32_t PowerStressModule::runOnce() +{ + + if (!config.power.powermon_enables) { + // Powermon not enabled - stop using CPU/stop this thread + return disable(); + } + + int32_t sleep_msec = 10; // when not active check for new messages every 10ms + + auto &p = currentMessage; + + if (isRunningCommand) { + // Done with the previous command - our sleep must have finished + p.cmd = meshtastic_PowerStressMessage_Opcode_UNSET; + p.num_seconds = 0; + } else { + sleep_msec = (int32_t)(p.num_seconds * 1000); + isRunningCommand = !!sleep_msec; // if the command wants us to sleep, make sure to mark that we have something running + + switch (p.cmd) { + case meshtastic_PowerStressMessage_Opcode_UNSET: // No need to start a new command + break; + case meshtastic_PowerStressMessage_Opcode_LED_ON: + break; + default: + LOG_ERROR("PowerStress operation %d not yet implemented!\n", p.cmd); + sleep_msec = 0; // Don't do whatever sleep was requested... + break; + } + } + return sleep_msec; +} \ No newline at end of file diff --git a/src/modules/PowerStressModule.h b/src/modules/PowerStressModule.h new file mode 100644 index 000000000..2d449f690 --- /dev/null +++ b/src/modules/PowerStressModule.h @@ -0,0 +1,38 @@ +#pragma once +#include "ProtobufModule.h" +#include "concurrency/OSThread.h" +#include "mesh/generated/meshtastic/powermon.pb.h" + +/** + * A module that provides easy low-level remote access to device hardware. + */ +class PowerStressModule : public ProtobufModule, private concurrency::OSThread +{ + meshtastic_PowerStressMessage currentMessage = meshtastic_PowerStressMessage_init_default; + bool isRunningCommand = false; + + public: + /** Constructor + * name is for debugging output + */ + PowerStressModule(); + + protected: + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_PowerStressMessage *p) override; + + /** + * Periodically read the gpios we have been asked to WATCH, if they have changed, + * broadcast a message with the change information. + * + * The method that will be called each time our thread gets a chance to run + * + * Returns desired period for next invocation (or RUN_SAME for no change) + */ + virtual int32_t runOnce() override; +}; + +extern PowerStressModule powerStressModule; \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index 735ebcf6a..e2c9549f3 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -8,6 +8,7 @@ #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "PowerMon.h" #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" @@ -85,6 +86,11 @@ void setCPUFast(bool on) void setLed(bool ledOn) { + if (ledOn) + powerMon->setState(meshtastic_PowerMon_State_LED_On); + else + powerMon->clearState(meshtastic_PowerMon_State_LED_On); + #ifdef LED_PIN // toggle the led so we can get some rough sense of how often loop is pausing digitalWrite(LED_PIN, ledOn ^ LED_INVERTED); From 4b82634d1a7951a4ab8b0c32615640c340bb2b3e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 3 Jul 2024 22:19:01 -0500 Subject: [PATCH 0665/1377] Cleanup buffer --- src/RedirectablePrint.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 14264ba21..1851ffbaa 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -208,6 +208,7 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ nrf52Bluetooth->sendLog(reinterpret_cast(buffer)); #endif delete[] message; + delete[] buffer; } } } From fc63d956e7eee73c43bf60c81eee277ef11b43c3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 4 Jul 2024 08:10:40 -0500 Subject: [PATCH 0666/1377] Merge hex for wm1110 target(s) --- bin/build-nrf52.sh | 14 +- bin/mergehex | Bin 0 -> 2102544 bytes bin/s140_nrf52_7.3.0_softdevice.hex | 9726 +++++++++++++++++++++++++++ 3 files changed, 9737 insertions(+), 3 deletions(-) create mode 100644 bin/mergehex create mode 100644 bin/s140_nrf52_7.3.0_softdevice.hex diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index a9980f486..853adb488 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -2,8 +2,8 @@ set -e -VERSION=`bin/buildinfo.py long` -SHORT_VERSION=`bin/buildinfo.py short` +VERSION=$(bin/buildinfo.py long) +SHORT_VERSION=$(bin/buildinfo.py short) OUTDIR=release/ @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* @@ -29,6 +29,14 @@ cp $DFUPKG $OUTDIR/$basename-ota.zip echo "Generating NRF52 uf2 file" SRCHEX=.pio/build/$1/firmware.hex + +# if WM1110 target, merge hex with softdevice 7.3.0 +if (echo $1 | grep -q "wm1110"); then + echo "Merging with softdevice" + bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex + SRCHEX=.pio/build/$1/merged_fimware.hex +fi + bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 cp bin/device-install.* $OUTDIR diff --git a/bin/mergehex b/bin/mergehex new file mode 100644 index 0000000000000000000000000000000000000000..2a93c571003f60f7d94e7a588acbc00285af9354 GIT binary patch literal 2102544 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

$&*Qh7qS<%!@nUL*MI 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

jqWDRdU%3zsH=u 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�hF6bG}d+&NiOQLS&25sIl1%U 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

d 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

guyN)-&D4*RZyzGw7}F7F7xLMB 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

;hP?-a_*c@MfW;siN=Mr7&&B&U!y||w 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 literal 0 HcmV?d00001 diff --git a/bin/s140_nrf52_7.3.0_softdevice.hex b/bin/s140_nrf52_7.3.0_softdevice.hex new file mode 100644 index 000000000..639927f50 --- /dev/null +++ b/bin/s140_nrf52_7.3.0_softdevice.hex @@ -0,0 +1,9726 @@ +:020000040000FA +:1000000000040020810A000015070000610A0000BA +:100010001F07000029070000330700000000000050 +:10002000000000000000000000000000A50A000021 +:100030003D070000000000004707000051070000D6 +:100040005B070000650700006F07000079070000EC +:10005000830700008D07000097070000A10700003C +:10006000AB070000B5070000BF070000C90700008C +:10007000D3070000DD070000E7070000F1070000DC +:10008000FB070000050800000F0800001908000029 +:10009000230800002D080000370800004108000078 +:1000A0004B080000550800005F08000069080000C8 +:1000B000730800007D080000870800009108000018 +:1000C0009B080000A5080000AF080000B908000068 +:1000D000C3080000CD080000D7080000E1080000B8 +:1000E000EB080000F5080000FF0800000909000007 +:1000F000130900001D090000270900003109000054 +:100100003B0900001FB500F003F88DE80F001FBD8C +:1001100000F0ACBC40F6FC7108684FF01022401CA7 +:1001200008D00868401C09D00868401C04D0086842 +:1001300000F037BA9069F5E79069F9E7704770B554 +:100140000B46010B184400F6FF70040B4FF0805073 +:100150000022090303692403406943431D1B104621 +:1001600000F048FA29462046BDE8704000F042BA47 +:10017000F0B54FF6FF734FF4B4751A466E1E11E0DA +:10018000A94201D3344600E00C46091B30F8027B3B +:10019000641E3B441A44F9D19CB204EB134394B25D +:1001A00004EB12420029EBD198B200EB134002EBB2 +:1001B000124140EA0140F0BDF34992B00446D1E952 +:1001C0000001CDE91001FF224021684600F0F4FB58 +:1001D00094E80F008DE80F00684610A902E004C8FB +:1001E00041F8042D8842FAD110216846FFF7C0FF7C +:1001F0001090AA208DF8440000F099F9FFF78AFFCB +:1002000040F6FC7420684FF01025401C0FD0206889 +:1002100010226946803000F078F92068401C08D030 +:100220002068082210A900F070F900F061F9A869AF +:10023000EEE7A869F5E74FF080500369406940F6A2 +:10024000FC71434308684FF01022401C06D0086838 +:1002500000F58050834203D2092070479069F7E788 +:100260000868401C04D00868401C03D00020704778 +:100270009069F9E70420704770B504460068C34DE3 +:10028000072876D2DFE800F033041929631E250021 +:10029000D4E9026564682946304600F062F92A46CE +:1002A0002146304600F031F9AA002146304600F0E0 +:1002B00057FB002800D0032070BD00F009FC4FF46C +:1002C000805007E0201D00F040F90028F4D100F034 +:1002D000FFFB60682860002070BD241D94E80700C3 +:1002E000920000F03DFB0028F6D00E2070BDFFF715 +:1002F000A2FF0028FAD1D4E901034FF0805100EBAE +:10030000830208694D69684382420ED840F6F8704E +:1003100005684FF010226D1C09D0056805EB8305B8 +:100320000B6949694B439D4203D9092070BD55694A +:10033000F4E70168491C03D00068401C02D003E0C8 +:100340005069FAE70F2070BD2046FFF735FFFFF731 +:1003500072FF0028F7D1201D00F0F7F80028F2D135 +:1003600060680028F0D100F0E2F8FFF7D3FE00F05B +:10037000BFF8072070BD10B50C46182802D0012028 +:10038000086010BD2068FFF777FF206010BD41684E +:10039000054609B1012700E0002740F6F8742068FF +:1003A0004FF01026401C2BD02068AA68920000F065 +:1003B000D7FA38B3A86881002068401C27D020688D +:1003C000FFF7BDFED7B12068401C22D026684FF051 +:1003D0008050AC686D68016942695143A9420DD9EA +:1003E000016940694143A14208D92146304600F0E5 +:1003F000B8F822462946304600F087F800F078F831 +:100400007069D2E700F093F8FFF784FEF6E77069B1 +:10041000D6E77669DBE740F6FC7420684FF01026DB +:10042000401C23D02068401C0CD02068401C1FD0EA +:100430002568206805F18005401C1BD027683879A5 +:10044000AA2819D040F6F8700168491C42D001680A +:10045000491C45D00168491C3ED001680968491C07 +:100460003ED00168491C39D000683EE0B069DAE747 +:10047000B569DEE7B769E2E710212846FFF778FEA5 +:100480003968814222D12068401C05D0D4F8001080 +:1004900001F18002C03107E0B169F9E730B108CA63 +:1004A00051F8040D984201D1012000E000208A4259 +:1004B000F4D158B1286810B1042803D0FEE72846CB +:1004C000FFF765FF3149686808600EE0FFF722FE1C +:1004D00000F00EF87169BBE77169BFE7706904E06D +:1004E0004FF480500168491C01D000F0CBFAFEE7C0 +:1004F000BFF34F8F26480168264A01F4E06111439B +:100500000160BFF34F8F00BFFDE72DE9F0411746B3 +:100510000D460646002406E03046296800F054F8EF +:10052000641C2D1D361DBC42F6D3BDE8F08140F69B +:10053000FC700168491C04D0D0F800004FF48051D1 +:10054000FDE54FF010208069F8E74FF080510A690F +:10055000496900684A43824201D810207047002050 +:10056000704770B50C4605464FF4806608E0284693 +:1005700000F017F8B44205D3A4F5806405F5805562 +:10058000002CF4D170BD0000F40A0000000000202F +:100590000CED00E00400FA05144801680029FCD0C5 +:1005A0007047134A0221116010490B68002BFCD0E0 +:1005B0000F4B1B1D186008680028FCD0002010603D +:1005C00008680028FCD07047094B10B501221A605A +:1005D000064A1468002CFCD0016010680028FCD08A +:1005E0000020186010680028FCD010BD00E4014015 +:1005F00004E5014070B50C46054600F073F810B9EB +:1006000000F07EF828B121462846BDE8704000F091 +:1006100007B821462846BDE8704000F037B8000012 +:100620007FB5002200920192029203920A0B000B06 +:100630006946012302440AE0440900F01F0651F80C +:10064000245003FA06F6354341F82450401C8242F8 +:10065000F2D80D490868009A10430860081D016827 +:10066000019A1143016000F03DF800280AD00649C4 +:1006700010310868029A10430860091D0868039A3F +:10068000104308607FBD00000006004030B50F4CED +:10069000002200BF04EB0213D3F800582DB9D3F8A1 +:1006A000045815B9D3F808581DB1521C082AF1D3C3 +:1006B00030BD082AFCD204EB0212C2F80008C3F8CD +:1006C00004180220C3F8080830BD000000E0014013 +:1006D0004FF08050D0F83001082801D0002070473A +:1006E000012070474FF08050D0F83011062905D016 +:1006F000D0F83001401C01D0002070470120704725 +:100700004FF08050D0F830010A2801D00020704707 +:100710000120704708208F490968095808471020B0 +:100720008C4909680958084714208A4909680958FA +:100730000847182087490968095808473020854923 +:100740000968095808473820824909680958084744 +:100750003C20804909680958084740207D490968BC +:100760000958084744207B49096809580847482028 +:1007700078490968095808474C207649096809589A +:10078000084750207349096809580847542071499F +:1007900009680958084758206E49096809580847E8 +:1007A0005C206C4909680958084760206949096854 +:1007B00009580847642067490968095808476820AC +:1007C00064490968095808476C2062490968095852 +:1007D000084770205F4909680958084774205D4937 +:1007E00009680958084778205A490968095808478C +:1007F0007C205849096809580847802055490968EC +:10080000095808478420534909680958084788202F +:1008100050490968095808478C204E490968095809 +:10082000084790204B4909680958084794204949CE +:10083000096809580847982046490968095808472F +:100840009C204449096809580847A0204149096883 +:1008500009580847A4203F49096809580847A820B3 +:100860003C49096809580847AC203A4909680958C1 +:100870000847B0203749096809580847B420354966 +:10088000096809580847B8203249096809580847D3 +:10089000BC203049096809580847C0202D4909681B +:1008A00009580847C4202B49096809580847C82037 +:1008B0002849096809580847CC2026490968095879 +:1008C0000847D0202349096809580847D4202149FE +:1008D000096809580847D8201E4909680958084777 +:1008E000DC201C49096809580847E02019490968B3 +:1008F00009580847E4201749096809580847E820BB +:100900001449096809580847EC2012490968095830 +:100910000847F0200F49096809580847F4200D4995 +:10092000096809580847F8200A490968095808471A +:10093000FC2008490968095808475FF48070054998 +:10094000096809580847000003480449024A034B54 +:100950007047000000000020000B0000000B0000AA +:1009600040EA010310B59B070FD1042A0DD310C82C +:1009700008C9121F9C42F8D020BA19BA884201D97E +:10098000012010BD4FF0FF3010BD1AB1D30703D0C6 +:10099000521C07E0002010BD10F8013B11F8014B7C +:1009A0001B1B07D110F8013B11F8014B1B1B01D198 +:1009B000921EF1D1184610BD02F0FF0343EA032254 +:1009C00042EA024200F005B87047704770474FF0A6 +:1009D00000020429C0F0128010F0030C00F01B800C +:1009E000CCF1040CBCF1020F18BF00F8012BA8BF1A +:1009F00020F8022BA1EB0C0100F00DB85FEAC17CDE +:100A000024BF00F8012B00F8012B48BF00F8012B90 +:100A100070474FF0000200B51346944696462039C1 +:100A200022BFA0E80C50A0E80C50B1F12001BFF4A7 +:100A3000F7AF090728BFA0E80C5048BF0CC05DF80D +:100A400004EB890028BF40F8042B08BF704748BF5B +:100A500020F8022B11F0804F18BF00F8012B7047CF +:100A6000014B1B68DB6818470000002009480A4951 +:100A70007047FFF7FBFFFFF745FB00BD20BFFDE719 +:100A8000064B1847064A1060016881F308884068E1 +:100A900000470000000B0000000B000017040000DE +:100AA000000000201EF0040F0CBFEFF30881EFF3ED +:100AB0000981886902380078182803D100E0000015 +:100AC000074A1047074A12682C3212681047000084 +:100AD00000B5054B1B68054A9B58984700BD0000B0 +:100AE0007703000000000020F00A0000040000006E +:100AF000001000000000000000FFFFFF0090D00386 +:10100000C8130020395E020085C100009F5D020008 +:1010100085C1000085C1000085C1000000000000FE +:10102000000000000000000000000000C55E02009B +:1010300085C100000000000085C1000085C10000DE +:101040002D5F0200335F020085C1000085C10000F2 +:1010500085C1000085C1000085C1000085C1000078 +:10106000395F020085C1000085C100003F5F0200BA +:1010700085C10000455F02004B5F0200515F020026 +:1010800085C1000085C1000085C1000085C1000048 +:1010900085C1000085C1000085C1000085C1000038 +:1010A00085C10000575F020085C1000085C10000B6 +:1010B00085C1000085C1000085C1000085C1000018 +:1010C0005D5F020085C1000085C1000085C1000090 +:1010D00085C1000085C1000085C1000085C10000F8 +:1010E00085C1000085C1000085C1000085C10000E8 +:1010F00085C1000085C1000085C1000085C10000D8 +:1011000085C1000085C1000000F002F824F083FED4 +:101110000AA090E8000C82448344AAF10107DA4552 +:1011200001D124F078FEAFF2090EBAE80F0013F0F7 +:10113000010F18BFFB1A43F00103184718530200B0 +:10114000385302000A444FF0000C10F8013B13F032 +:10115000070408BF10F8014B1D1108BF10F8015B10 +:10116000641E05D010F8016B641E01F8016BF9D103 +:1011700013F0080F1EBF10F8014BAD1C0C1B09D15A +:101180006D1E58BF01F801CBFAD505E014F8016BCC +:1011900001F8016B6D1EF9D59142D6D3704700005E +:1011A0000023002400250026103A28BF78C1FBD870 +:1011B000520728BF30C148BF0B6070471FB500F011 +:1011C00003F88DE80F001FBD24F022BE70B51A4C45 +:1011D00005460A202070A01C00F0D5F85920A080F8 +:1011E00029462046BDE8704008F082B908F08BB966 +:1011F00070B50C461149097829B1A0F160015E294A +:1012000008D3012013E0602804D0692802D043F2FB +:1012100001000CE020CC0A4E94E80E0006EB8000A2 +:10122000A0F58050241FD0F8806E2846B04720607B +:1012300070BD012070470000080000201C00002045 +:10124000A05F02003249884201D20120704700208D +:10125000704770B50446A0F500002E4EB0F1786FCF +:1012600002D23444A4F500042948844201D2012565 +:1012700000E0002500F043F848B125B9B44204D39A +:101280002548006808E0012070BD002070BD002DD9 +:10129000F9D1B442F9D321488442F6D2F3E710B52C +:1012A0000446A0F50000B0F1786F03D21948044459 +:1012B000A4F5000400F023F84FF0804130B1164847 +:1012C000006804E08C4204D2012003E01348844209 +:1012D000F8D2002080F0010010BD10B520B1FFF75A +:1012E000DEFF08B1012010BD002010BD10B520B1F7 +:1012F000FFF7AFFF08B1012010BD002010BD084866 +:1013000008490068884201D10120704700207047D9 +:1013100000700200000000202000002008000020D3 +:101320005C000020BEBAFECA10B5044600210120B0 +:1013300000F042F800210B2000F03EF800210820C8 +:1013400000F03AF80421192000F036F804210D20AD +:1013500000F032F804210E2000F02EF804210F20B6 +:1013600000F02AF80421C84300F026F806211620D0 +:1013700000F022F80621152000F01EF82046FFF7A5 +:1013800025FF002010BD40F2231101807047FFF7B8 +:101390002DBF1148704710487047104A10B51468A7 +:1013A0000E4B0F4A08331A60FFF722FF0B48001D4F +:1013B000046010BD704770474907090E002804DB20 +:1013C00000F1E02080F80014704700F00F0000F1F9 +:1013D000E02080F8141D704703F900421005024018 +:1013E00001000001FD48002101604160018170475A +:1013F0002DE9FF4F93B09B46209F160004460DD069 +:101400001046FFF726FF18B1102017B0BDE8F08F87 +:101410003146012001F0D3FE0028F6D101258DF8D8 +:1014200042504FF4C050ADF84000002210A92846A9 +:1014300006F0C5FC0028E8D18DF84250A8464FF4CC +:1014400028500025ADF840001C2229466846079523 +:101450000DF01DF89DF81C000DF11C0A20F00F0086 +:10146000401C20F0F00010308DF81C0020788DF822 +:101470001D0061789DF81E000DF1400961F34200E6 +:1014800040F001008DF81E009DF8000008AA40F011 +:1014900002008DF800002089ADF83000ADF8325020 +:1014A0006089ADF83400CDF82CA060680E900AA9D0 +:1014B000CDF82890684606F090FA0028A5D160681B +:1014C000FFF70BFF40B16068FFF710FF20B96078AD +:1014D00000F00300022801D0012000E00020BF4CF2 +:1014E00008AA0AA92072BDF8200020808DF8428049 +:1014F00042F60120ADF840009DF81E0020F00600E5 +:10150000801C20F001008DF81E000220ADF8300094 +:10151000ADF8340014A80E90684606F05EFA002874 +:1015200089D1BDF82000608036B1211D304600F021 +:101530005FF90028C2D109E0BBF1000F05D00CF023 +:1015400021FDE8BB0CF01EFDD0BBA58017B1012F1B +:1015500043D04AE08DF8428042F6A620ADF8400024 +:1015600046461C220021684607950CF090FF9DF826 +:101570001C00ADF8346020F00F00401C20F0F0009B +:1015800010308DF81C009DF81D0020F0FF008DF834 +:101590001D009DF81E0020F0060040F00100801C98 +:1015A0008DF81E009DF800008DF8446040F00200A8 +:1015B0008DF80000CDE90A9AADF8306011A800E07E +:1015C00011E00E9008AA0AA9684606F006FA00285B +:1015D000A6D1BDF82000E08008E00CF0D3FC10B9E3 +:1015E0000CF0D0FC08B103200FE7E58000200CE7E9 +:1015F0003EB50446794D0820ADF80000A88828B112 +:101600002046FFF726FE18B110203EBD06203EBD45 +:101610002146012001F0D3FD0028F8D12088ADF843 +:1016200004006088ADF80600A088ADF80800E088E6 +:10163000ADF80A00A88801AB6A46002106F0AAFDB1 +:10164000BDF800100829E2D003203EBD7FB5634DF0 +:101650000446A88868B1002002900820ADF8080070 +:10166000CDF80CD02046FFF7F4FD20B1102004B0D7 +:1016700070BD0620FBE7A98802AA4FF6FF7006F0AE +:10168000CCFF0028F3D1BDF80810082901D00320B1 +:10169000EDE7BDF800102180BDF802106180BDF8B3 +:1016A0000410A180BDF80610E180E0E701B582B02A +:1016B0000220ADF80000494802AB6A46408800218C +:1016C00006F068FDBDF80010022900D003200EBD11 +:1016D0001CB5002100910221ADF800100190FFF728 +:1016E000DEFD08B110201CBD3C486A4641884FF61B +:1016F000FF7006F092FFBDF800100229F3D003201E +:101700001CBDFEB5354C06461546207A0F46C0076F +:1017100005D00846FFF79DFD18B11020FEBD0F2033 +:10172000FEBDF82D01D90C20FEBD3046FFF791FD1E +:1017300018BB208801A905F03AFE0028F4D13078C2 +:101740008DF80500208801A906F003FD0028EBD1E3 +:1017500000909DF800009DF8051040F002008DF803 +:101760000000090703D040F008008DF80000208831 +:10177000694606F08BFC0028D6D1ADF808502088C9 +:101780003B4602AA002106F005FDBDF80810A9425B +:10179000CAD00320FEBD7CB5054600200090019014 +:1017A0000888ADF800000C4628460195FFF795FD26 +:1017B00018B92046FFF773FD08B110207CBD15B1A4 +:1017C000BDF8000060B105486A4601884FF6FF7019 +:1017D00006F023FFBDF8001021807CBD240200200C +:1017E0000C20FAE72F48C088002800D0012070475D +:1017F00030B5044693B000200D46014600901422F7 +:1018000001A80CF044FE1C22002108A80CF03FFEA9 +:101810009DF80000CDF808D020F00F00401C20F00B +:10182000F00010308DF800009DF8010006AA20F0AD +:10183000FF008DF801009DF8200001A940F0020092 +:101840008DF8200001208DF8460042F60420ADF806 +:10185000440011A801902088ADF83C006088ADF8E4 +:101860003E00A088ADF84000E088ADF842009DF849 +:10187000020020F00600801C20F001008DF802001C +:101880000820ADF80C00ADF810000FA8059008A8CE +:1018900006F0A3F8002803D1BDF818002880002026 +:1018A00013B030BD24020020F0B5007B059F1E461A +:1018B00014460D46012800D0FFDF0C2030803A206E +:1018C0003880002C08D0287A032806D0287B0128ED +:1018D00000D0FFDF17206081F0BDA889FBE72DE96C +:1018E000F0470D4686B095F80C900E991446B9F164 +:1018F000010F0BD01022007B2E8A9046052807D0BE +:10190000062839D0FFDF06B0BDE8F0870222F2E7F3 +:10191000E8890C2200EB400002EB400018803320E5 +:101920000880002CEFD0E8896081002720E0009635 +:10193000688808F1020301AA696900F097FF06EBC5 +:101940000800801C07EB470186B204EB4102BDF89A +:1019500004009081F848007808B1012300E00023DA +:101960000DF1060140460E3214F029F87F1CBFB27B +:101970006089B842DBD8C6E734200880E889B9F12D +:10198000010F11D0122148430E301880002CBAD01C +:10199000E88960814846B9F1010F00D00220207328 +:1019A00000270DF1040A1FE00621ECE70096688885 +:1019B00008F1020301AA696900F058FF06EB08006C +:1019C000801C86B2B9F1010F12D007EBC70004EBFF +:1019D0004000BDF80410C18110220AF1020110304C +:1019E0000CF02BFD7F1CBFB26089B842DED88AE7BD +:1019F00007EB470104EB4102BDF80400D0810AF176 +:101A000002014046103213F0FCFFEBE72DE9F047EE +:101A10000E4688B090F80CC096F80C80378AF5898D +:101A20000C20DFF81493109902F10C04BCF1030FA1 +:101A300008D0BCF1040F3DD0BCF1070F75D0FFDF1B +:101A400008B061E705EB850C00EB4C0018803120F5 +:101A50000880002AF4D0A8F1060000F0FF0A5581A2 +:101A600024E01622002101A80CF011FD00977088D7 +:101A7000434601AA716900F0F9FEBDF80400208018 +:101A8000BDF80600E080BDF80800208199F800004C +:101A900008B1012300E00023A21C0DF10A01504609 +:101AA00013F08DFF07EB080087B20A346D1EADB24C +:101AB000D7D2C5E705EB850C00EB4C00188032202F +:101AC0000880002ABCD0A8F1050000F0FF0A55816B +:101AD00037E000977088434601AA716900F0C6FE9E +:101AE0009DF80600BDF80410E1802179420860F3FA +:101AF000000162F34101820862F38201C20862F3CD +:101B0000C301020962F30411420962F3451182091B +:101B100062F386112171C0096071BDF80700208150 +:101B200099F8000010B1012301E00EE000232246E5 +:101B30000DF10901504613F042FF07EB080087B290 +:101B40000A346D1EADB2C4D27AE7A8F1020084B2A5 +:101B500005FB08FC0CF10E00188035200880002AD7 +:101B6000A7D05581948100971FFA8CF370880E32AC +:101B7000716900F07BFE63E72DE9F84F1E460A9D70 +:101B80000C4681462AB1607A00F58070D080E089E9 +:101B9000108199F80C000C274FF000084FF00E0A46 +:101BA0000D2872D2DFE800F09D070E1B272F374566 +:101BB000546972727200214648460095FFF774FE20 +:101BC000BDE8F88F207B9146082802D0032800D07A +:101BD000FFDF3780302009E0A9F80A80F0E7207B9A +:101BE0009146042800D0FFDF378031202880B9F1EA +:101BF000000FF1D1E4E7207B9146042800D0FFDFFD +:101C000037803220F2E7207B9146022800D0FFDFA8 +:101C100037803320EAE7207B1746022800D0FFDF19 +:101C20003420A6F800A02880002FC9D0A7F80A8089 +:101C3000C6E7207B1746042800D0FFDF3520A6F832 +:101C400000A02880002FBBD04046A7F80A8012E0F1 +:101C5000207B1746052802D0062800D0FFDF102081 +:101C6000308036202880002FAAD0E0897881A7F81C +:101C70000E80B9F80E00B881A2E7207B91460728B4 +:101C800000D0FFDF37803720B0E72AE04FF01200A6 +:101C900018804FF038001700288091D0E0897881B3 +:101CA000A7F80E80A7F8108099F80C000A2805D034 +:101CB0000B2809D00C280DD0FFDF81E7207B0A28F4 +:101CC00000D0FFDF01200AE0207B0B2800D0FFDFDF +:101CD000042004E0207B0C2800D0FFDF05203873AF +:101CE0006EE7FFDF6CE770B50C46054601F0AAFB16 +:101CF00020B10078222804D2082070BD43F20200EF +:101D000070BD0521284612F0D1F8206008B10020EE +:101D100070BD032070BD30B44880087820F00F00FB +:101D2000C01C20F0F000903001F8080B1DCA81E8BB +:101D30001D0030BC07F05DBC100000202DE9FF47FE +:101D400084B0002782460297079890468946123051 +:101D50000AF069FA401D20F00306079828B907A980 +:101D60005046FFF7C0FF002854D1B9F1000F05D04D +:101D70000798017B19BB052504681BE098F8000053 +:101D8000092803D00D2812D0FFDF46E0079903256C +:101D90004868B0B3497B42887143914239D98AB2CD +:101DA000B3B2011D11F0F5FE0446078002E0079C66 +:101DB000042508340CB1208810B1032D29D02CE063 +:101DC0000798012112300AF060FAADF80C000246C3 +:101DD00002AB2946504608F0B8FA070001D1A01C12 +:101DE000029007983A461230C8F80400A8F802A0FA +:101DF00003A94046029B0AF055FAD8B10A2817D227 +:101E000000E006E0DFE800F007091414100B0D14E1 +:101E10001412132014E6002012E6112010E6082008 +:101E20000EE643F203000BE6072009E60D2007E665 +:101E3000032005E6BDF80C002346CDE900702A46D4 +:101E40005046079900F022FD57B9032D08D1079895 +:101E5000B3B2417B406871438AB2011D11F0ADFEFF +:101E6000B9F1000FD7D0079981F80C90D3E72DE98D +:101E7000FE4F91461A881C468A468046FAB102AB4C +:101E8000494608F062FA050019D04046A61C27888A +:101E900012F04FF93246072629463B46009611F0CC +:101EA0005EFD20882346CDE900504A465146404613 +:101EB00000F0ECFC002020800120BDE8FE8F002017 +:101EC000FBE710B586B01C46AAB104238DF800309C +:101ED0001388ADF808305288ADF80A208A788DF85A +:101EE0000E200988ADF80C1000236A462146FFF742 +:101EF00025FF06B010BD1020FBE770B50D4605218B +:101F000011F0D4FF040000D1FFDF294604F11200D4 +:101F1000BDE870400AF0A2B92DE9F8430D468046AD +:101F2000002607F063FB04462878102878D2DFE803 +:101F300000F0773B345331311231313108313131D6 +:101F400031312879001FC0B2022801D0102810D1E9 +:101F500014BBFFDF35E004B9FFDF0521404611F077 +:101F6000A5FF007B032806D004280BD0072828D023 +:101F7000FFDF072655E02879801FC0B2022820D055 +:101F800050B1F6E72879401FC0B2022819D01028B6 +:101F900017D0EEE704B9FFDF13E004B9FFDF2879BB +:101FA00001280ED1172137E00521404611F07EFFB0 +:101FB000070000D1FFDF07F1120140460AF02BF9BC +:101FC0002CB12A4621464046FFF7A5FE29E0132101 +:101FD000404602F01FFD24E004B9FFDF0521404622 +:101FE00011F064FF060000D1FFDF694606F1120020 +:101FF0000AF01BF9060000D0FFDFA988172901D2DB +:10200000172200E00A46BDF80000824202D90146CC +:1020100002E005E01729C5D3404600F047FCD0E7B1 +:10202000FFDF3046BDE8F883401D20F0030219B100 +:1020300002FB01F0001D00E000201044704713B5C2 +:10204000009858B10024684611F04DFD002C04D1D1 +:10205000F749009A4A6000220A701CBD0124002042 +:10206000F2E72DE9F0470C461546242200212046D0 +:102070000CF00DFA05B9FFDFA87860732888DFF847 +:10208000B0A3401D20F00301AF788946DAF80400C0 +:1020900011F047FD060000D1FFDF4FF00008266079 +:1020A000A6F8008077B109FB07F1091D0AD0DAF81C +:1020B000040011F036FD060000D1FFDF6660C6F8AF +:1020C000008001E0C4F80480298804F11200BDE812 +:1020D000F0470AF091B82DE9F047804601F112006F +:1020E0000D4681460AF09FF8401DD14F20F00302B3 +:1020F0006E7B14462968786811F03EFD3EB104FB02 +:1021000006F2121D03D06968786811F035FD0520CC +:1021100011F074FE0446052011F078FE201A012803 +:1021200002D1786811F0F2FC49464046BDE8F0471C +:102130000AF078B870B50546052111F0B7FE040025 +:1021400000D1FFDF04F112012846BDE870400AF01B +:1021500062B82DE9F04F91B04FF0000BADF828B008 +:10216000ADF804B047880C4605469246052138462E +:1021700011F09CFE060000D1FFDF24B1A780A4F877 +:1021800006B0A4F808B0297809220B20B2EB111F81 +:1021900073D12A7A04F1100138274FF00C084FF060 +:1021A00012090291102A69D2DFE802F068F2F1F018 +:1021B0008008D3898EA03DDCF3EEB7B7307B0228D0 +:1021C00000D0FFDFA88908EBC001ADF80410302172 +:1021D000ADF82810002C25D06081B5F80E800027BE +:1021E0001DE004EBC709317C89F80E10F189A9F8CC +:1021F0000C10CDF800806888042305AA296900F036 +:1022000035FBBDF81410A9F8101008F10400BDF852 +:1022100016107F1C1FFA80F8A9F81210BFB260894F +:10222000B842DED80CE1307B022800D0FFDFE9891C +:1022300008EBC100ADF804003020ADF8280095F897 +:102240000C90002CA9F10400C0B20F90EAD061817B +:10225000B5F81080002725E0CDF8008068884B464F +:1022600003AA696900F002FB08EB09001FFA80F875 +:102270006F48007818B1012302E0DDE0DAE00023C6 +:1022800004EBC702009204A90C320F9813F097FBDD +:10229000009ABDF80C007F1C1082009ABDF80E0059 +:1022A000BFB250826089B842D6D8C9E00AA800906F +:1022B00001AB224629463046FFF711FBC0E0307BD8 +:1022C000082805D0FFDF03E0307B082800D0FFDFBF +:1022D000E8891030ADF804003620ADF82800002C55 +:1022E0003FD0A9896181F189A18127E0307B09284C +:1022F00000D0FFDFA88901460C30ADF8040037207C +:10230000ADF82800002C2CD06181E8890090AB89C1 +:10231000688804F10C02296955E0E88939211030F8 +:1023200080B2ADF80400ADF82810002C72D0A98955 +:102330006181287A0E280AD002212173E989E1817E +:10234000288A0090EB8968886969029A3BE001213C +:10235000F3E70AA8009001AB224629463046FFF772 +:1023600055FB6DE0307B0A2800D0FFDFADF804900C +:10237000ADF828704CB3A9896181A4F810B0A4F815 +:102380000EB0012020735BE020E002E030E038E096 +:1023900041E0307B0B2800D0FFDF288AADF82870A1 +:1023A0001230ADF8040084B104212173A989618140 +:1023B000E989E181298A2182688A00902B8A6888CC +:1023C00004F11202696900F051FA39E0307B0C28FF +:1023D00000D0FFDFADF80490ADF828703CB30521C4 +:1023E0002173A4F80AB0A4F80EB0A4F810B027E046 +:1023F0000AA8009001AB224629463046FFF754FA5E +:102400001EE00AA8009001AB224629463046FFF79D +:10241000B3FB15E034E03B21ADF80400ADF8281023 +:1024200074B30120E080A4F808B084F80AB007E093 +:1024300010000020FFDF03E0297A012917D0FFDF19 +:10244000BDF80400AAF800006CB1BDF82800208097 +:10245000BDF804006080BDF82800392803D03C286E +:1024600001D086F80CB011B00020BDE8F08F3C21FF +:10247000ADF80400ADF8281014B1697AA172DFE755 +:10248000AAF80000EFE72DE9F84356880F4680468A +:1024900015460521304611F009FD040000D1FFDF8B +:1024A000123400943B46414630466A680AF02EF8E2 +:1024B000B8E570B50D46052111F0F8FC040000D117 +:1024C000FFDF294604F11200BDE8704009F0B8BEF4 +:1024D00070B50D46052111F0E9FC040000D1FFDFC5 +:1024E000294604F11200BDE8704009F0D6BE70B56F +:1024F0000546052111F0DAFC040000D1FFDF04F1EC +:10250000080321462846BDE870400422AFE470B5B8 +:102510000546052111F0CAFC040000D1FFDF214669 +:1025200028462368BDE870400522A0E470B5064641 +:10253000052111F0BBFC040000D1FFDF04F1120003 +:1025400009F071FE401D20F0030511E0011D008817 +:102550000322431821463046FFF789FC00280BD0A0 +:10256000607BABB2684382B26068011D11F05BFB17 +:10257000606841880029E9D170BD70B50E460546F6 +:1025800007F034F8040000D1FFDF012020726672EA +:102590006580207820F00F00C01C20F0F000303063 +:1025A0002070BDE8704007F024B8602801D00720F3 +:1025B00070470878C54900F0010008700020704796 +:1025C0002DE9F0438BB00D461446814606A9FFF76E +:1025D0008AFB002814D14FF6FF7601274FF42058CC +:1025E0008CB103208DF800001020ADF8100007A872 +:1025F000059007AA204604A913F005FA78B1072030 +:102600000BB0BDE8F0830820ADF808508DF80E70CF +:102610008DF80000ADF80A60ADF80C800CE006986B +:10262000A17801742188C1818DF80E70ADF8085031 +:10263000ADF80C80ADF80A606A4602214846069B58 +:10264000FFF77CFBDCE708B501228DF8022042F69B +:102650000202ADF800200A4603236946FFF731FC69 +:1026600008BD08B501228DF8022042F60302ADF83C +:1026700000200A4604236946FFF723FC08BD00B585 +:1026800087B079B102228DF800200A88ADF80820C1 +:102690004988ADF80A1000236A460521FFF74EFB72 +:1026A00007B000BD1020FBE709B1072309E40720AC +:1026B000704770B588B00D461446064606A9FFF768 +:1026C00012FB00280ED17CB10620ADF808508DF821 +:1026D0000000ADF80A40069B6A460821DC813046BE +:1026E000FFF72CFB08B070BD05208DF80000ADF899 +:1026F0000850F0E700B587B059B107238DF80030D6 +:10270000ADF80820039100236A460921FFF716FB64 +:10271000C6E71020C4E770B588B00C460646002511 +:1027200006A9FFF7E0FA0028DCD106980121123053 +:1027300009F0ABFD9CB12178062921D2DFE801F038 +:10274000200505160318801E80B2C01EE28880B2E4 +:102750000AB1A3681BB1824203D90C20C2E7102042 +:10276000C0E7042904D0A08850B901E00620B9E7E9 +:10277000012913D0022905D004291CD005292AD00B +:102780000720AFE709208DF800006088ADF8080049 +:10279000E088ADF80A00A068039023E00A208DF8D5 +:1027A00000006088ADF80800E088ADF80A00A06875 +:1027B0000A25039016E00B208DF800006088ADF824 +:1027C0000800A088ADF80A00E088ADF80C00A06809 +:1027D0000B25049006E00C208DF8000060788DF841 +:1027E00008000C256A4629463046069BFFF7A6FAE4 +:1027F00078E700B587B00D228DF80020ADF80810FD +:1028000000236A461946FFF799FA49E700B587B0F1 +:1028100071B102228DF800200A88ADF8082049889D +:10282000ADF80A1000236A460621FFF787FA37E75A +:10283000102035E770B586B0064601200D46ADF88C +:1028400008108DF80000014600236A463046FFF765 +:1028500075FA040008D12946304605F0B5FC002180 +:10286000304605F0CFFC204606B070BDF8B51C46DA +:1028700015460E46069F11F04AFC2346FF1DBCB2CA +:1028800031462A46009411F036F8F8BD30B41146AE +:10289000DDE902423CB1032903D0002330BC08F03B +:1028A00032BE0123FAE71A8030BC704770B50C467F +:1028B0000546FFF722FB2146284605F094FC2846F2 +:1028C000BDE87040012105F09DBC00001000002013 +:1028D0004FF0E0224FF400400021C2F88001BFF326 +:1028E0004F8FBFF36F8F1748016001601649900248 +:1028F00008607047134900B500220A600A60124B55 +:102900004FF060721A60002808BF00BD0F4A104BDC +:10291000DFF840C001280CD002281CBFFFDF00BD3B +:10292000032008601A604FF4000000BFCCF80000DC +:1029300000BD022008601A604FF04070F6E700B555 +:10294000FFDF00BD00F5004008F50140A4020020B3 +:1029500014F5004004F5014070B50B2000F0BDF9FE +:10296000082000F0BAF900210B2000F0D4F9002172 +:10297000082000F0D0F9F44C01256560A560002026 +:10298000C4F84001C4F84401C4F848010B2000F029 +:10299000B5F9082000F0B2F90B2000F091F925609C +:1029A00070BD10B50B2000F098F9082000F095F9E3 +:1029B000E548012141608160E4490A68002AFCD1B0 +:1029C0000021C0F84011C0F84411C0F848110B2094 +:1029D00000F094F9BDE81040082000F08FB910B560 +:1029E0000B2000F08BF9BDE81040082000F086B9FC +:1029F00000B530B1012806D0022806D0FFDF002044 +:102A000000BDD34800BDD34800BDD248001D00BD65 +:102A100070B5D1494FF000400860D04DC00BC5F8EB +:102A20000803CF4800240460C5F840410820C4359D +:102A300000F053F9C5F83C41CA48047070BD08B5B0 +:102A4000C14A002128B1012811D002281CD0FFDF83 +:102A500008BD4FF48030C2F80803C2F84803BB48F1 +:102A60003C300160C2F84011BDE80840D0E74FF4A7 +:102A70000030C2F80803C2F84803B448403001608F +:102A8000C2F84411B3480CE04FF48020C2F80803A8 +:102A9000C2F84803AD4844300160C2F84811AD485F +:102AA000001D0068009008BD70B516460D4604462E +:102AB000022800D9FFDF0022A348012304F11001FE +:102AC0008B4000EB8401C1F8405526B1C1F840218C +:102AD000C0F8043303E0C0F80833C1F84021C0F85F +:102AE000443370BD2DE9F0411D46144630B1012834 +:102AF00033D0022838D0FFDFBDE8F081891E0022E4 +:102B000021F07F411046FFF7CFFF012D23D0002099 +:102B1000944D924F012668703E61914900203C39E6 +:102B200008600220091D08608D49042030390860C2 +:102B30008B483D34046008206C6000F0DFF83004FE +:102B4000C7F80403082000F0BBF88349F007091F09 +:102B500008602E70D0E70120DAE7012B02D00022B6 +:102B6000012005E00122FBE7012B04D00022022016 +:102B7000BDE8F04198E70122F9E774480068704722 +:102B800070B500F0D8F8704C0546D4F84001002626 +:102B9000012809D1D4F80803C00305D54FF48030CB +:102BA000C4F80803C4F84061D4F8440101280CD1EA +:102BB000D4F80803800308D54FF40030C4F80803A4 +:102BC000C4F84461012013F0EEFED4F84801012856 +:102BD0000CD1D4F80803400308D54FF48020C4F882 +:102BE0000803C4F84861022013F0DDFE5E4805606A +:102BF00070BD70B500F09FF85A4D0446287850B16A +:102C0000FFF706FF687818B10020687013F0CBFE5C +:102C10005548046070BD0320F8E74FF0E0214FF401 +:102C20000010C1F800027047152000F067B84B494A +:102C300001200861082000F061B848494FF47C1079 +:102C4000C1F808030020024601EB8003C3F84025C9 +:102C5000C3F84021401CC0B20628F5D37047410A92 +:102C600043F609525143C0F3080010FB02F000F58F +:102C7000807001EB5020704710B5430B48F2376469 +:102C800063431B0C5C020C60384C03FB0400384BA4 +:102C90004CF2F72443435B0D13FB04F404EB402098 +:102CA00000F580704012107008681844086010BD6C +:102CB0002C484068704729490120C1F8000270473C +:102CC000002809DB00F01F0201219140400980002B +:102CD00000F1E020C0F80011704700280DDB00F083 +:102CE0001F02012191404009800000F1E020C0F85E +:102CF0008011BFF34F8FBFF36F8F7047002809DB40 +:102D000000F01F02012191404009800000F1E02005 +:102D1000C0F8801270474907090E002804DB00F153 +:102D2000E02080F80014704700F00F0000F1E02070 +:102D300080F8141D70470C48001F00680A4A0D49AE +:102D4000121D11607047000000B0004004B5004043 +:102D50004081004044B1004008F50140008000403F +:102D6000408500403C00002014050240F7C2FFFFF0 +:102D70006F0C0100010000010A4810B50468094900 +:102D800009480831086013F0A2FE0648001D0460DF +:102D900010BD0649002008604FF0E0210220C1F874 +:102DA000800270471005024001000001FC1F004036 +:102DB000374901200860704770B50D2000F049F8D0 +:102DC000344C0020C4F800010125C4F804530D2040 +:102DD00000F050F825604FF0E0216014C1F80001C8 +:102DE00070BD10B50D2000F034F82A480121416073 +:102DF0000021C0F80011BDE810400D2000F03AB8E5 +:102E0000254810B504682449244808310860214940 +:102E1000D1F80001012804D0FFDF1F48001D046025 +:102E200010BD1B48001D00680022C0B2C1F800217F +:102E300014F07FFBF1E710B5164800BFD0F8001181 +:102E40000029FBD0FFF7DCFFBDE810400D2000F0AB +:102E500011B800280DDB00F01F020121914040094C +:102E6000800000F1E020C0F88011BFF34F8FBFF366 +:102E70006F8F7047002809DB00F01F02012191408D +:102E80004009800000F1E020C0F880127047000087 +:102E900004D5004000D000401005024001000001B0 +:102EA0004FF0E0214FF00070C1F8800101F5C071D2 +:102EB000BFF34F8FBFF36F8FC1F80001394B8022F2 +:102EC00083F8002441F8800C704700B502460420C6 +:102ED000354903E001EBC0031B792BB1401EC0B2A2 +:102EE000F8D2FFDFFF2000BD41F8302001EBC00128 +:102EF00000224A718A7101220A7100BD2A4A00210A +:102F000002EBC0000171704710B50446042800D3DD +:102F1000FFDF254800EBC4042079012800D0FFDF43 +:102F20006079A179401CC0B2814200D060714FF03D +:102F3000E0214FF00070C1F8000210BD70B504250B +:102F4000194E1A4C16E0217806EBC1000279012ACD +:102F500008D1427983799A4204D04279827156F835 +:102F6000310080472078401CC0B22070042801D373 +:102F7000002020706D1EEDB2E5D270BD0C4810B57A +:102F800004680B490B4808310860064890F80004B3 +:102F90004009042800D0FFDFFFF7D0FF0448001DE0 +:102FA000046010BD19E000E0E0050020580000209A +:102FB00010050240010000010548064A01689142DF +:102FC00001D1002101600449012008607047000020 +:102FD0005C000020BEBAFECA40E5014070B50C4658 +:102FE000054609F02FFC21462846BDE870400AF04E +:102FF00010BD7047704770470021016081807047A5 +:103000002CFFFFFFDBE5B151007002002301FFFF41 +:103010008C00000078DB6A007A2E9AC67DB66CFAC6 +:10302000F35721CCC310D5E51471FB3C30B5FC4DF2 +:103030000446062CA9780ED2DFE804F0030E0E0E2B +:103040000509FFDF08E0022906D0FFDF04E00329BD +:1030500002D0FFDF00E0FFDFAC7030BD30B50446CA +:103060001038EF4D07280CD2DFE800F0040C060CF6 +:103070000C0C0C00FFDF05E0287E112802D0FFDFDA +:1030800000E0FFDF2C7630BD2DE9F04112F026FE86 +:10309000044614F063F8201AC5B2062010F0AEFE04 +:1030A0000446062010F0B2FE211ADD4C207E1228C4 +:1030B00018D000200F18072010F0A0FE06460720A9 +:1030C00010F0A4FE301A3918207E13280CD00020EE +:1030D0000144A078042809D000200844281AC0B26E +:1030E000BDE8F0810120E5E70120F1E70120F4E7E8 +:1030F000CB4810B590F825004108C94800F12600DA +:1031000005D00EF0F5FEBDE8104006F08CB80EF0CC +:10311000D0FEF8E730B50446A1F120000D460A289C +:103120004AD2DFE800F005070C1C2328353A3F445B +:10313000FFDF42E0207820283FD1FFDF3DE0B848A4 +:103140008178052939D0007E122836D020782428AD +:1031500033D0252831D023282FD0FFDF2DE0207851 +:1031600022282AD0232828D8FFDF26E0207822280A +:1031700023D0FFDF21E0207822281ED024281CD075 +:1031800026281AD0272818D0292816D0FFDF14E0C7 +:103190002078252811D0FFDF0FE0207825280CD0DB +:1031A000FFDF0AE02078252807D0FFDF05E0207840 +:1031B000282802D0FFDF00E0FFDF257030BD1FB5FB +:1031C00004466A46002001F0A5FEB4B1BDF8022015 +:1031D0004FF6FF700621824201D1ADF80210BDF812 +:1031E0000420824201D1ADF80410BDF808108142DC +:1031F00003D14FF44860ADF8080068460FF0E2FADA +:1032000006F011F804B010BD70B516460C46054620 +:10321000FEF71FF848B90CB1B44208D90C2070BDB4 +:1032200055F82400FEF715F808B1102070BD2046AF +:10323000641EE4B2F4D270BD2DE9F04105461F468C +:1032400090460E4600240068FEF750F830B9A98871 +:1032500028680844401EFEF749F808B110203FE7EF +:1032600028680028A88802D0B84202D850E0002878 +:10327000F5D0092034E72968085DB8B1671CCA5D3C +:10328000152A2ED03CDC152A3AD2DFE802F039129A +:10329000222228282A2A313139393939393939391C +:1032A00039392200085D30BB641CA4B2A242F9D8AF +:1032B00033E00228DDD1A01C085C88F80000072854 +:1032C00001D2400701D40A200AE7307840F001001B +:1032D00015E0C143C90707E0012807D010E0062028 +:1032E000FEE60107A1F180510029F5D01846F7E666 +:1032F0003078810701D50B20F2E640F002003070F3 +:103300002868005D384484B2A888A04202D2B0E7A1 +:103310004FF4485382B2A242ADD80020E0E610B587 +:10332000027843F2022354080122022C12D003DC5B +:103330003CB1012C16D106E0032C10D07F2C11D10A +:1033400012E0002011E080790324B4EB901F09D132 +:103350000A700BE08079B2EB901F03D1F8E7807917 +:103360008009F5D0184610BDFF200870002010BD60 +:1033700008B500208DF80000294890F82E1051B1B2 +:1033800090F82F0002280FD003280FD0FFDF00BFD6 +:103390009DF8000008BD22486946253001F009FE6D +:1033A0000028F5D0FFDFF3E7032000E001208DF8CF +:1033B0000000EDE738B50C460546694601F0F9FD19 +:1033C00000280DD19DF80010207861F3470020708F +:1033D00055F8010FC4F80100A888A4F805000020E2 +:1033E00038BD38B5137888B102280FD0FF281BD01C +:1033F0000CA46D46246800944C7905EB9414247851 +:1034000064F347031370032805D010E023F0FE0394 +:1034100013700228F7D1D8B240F001000AE0000092 +:10342000F00100200302FF0143F0FE00107010784D +:1034300020F0010010700868C2F801008888A2F826 +:10344000050038BD022110F031BD38B50C460978B1 +:10345000222901D2082038BDADF800008DF80220E5 +:1034600068460EF087FD05F0DEFE050003D1212140 +:103470002046FFF74FFE284638BD1CB500208DF8CA +:103480000000CDF80100ADF80500FB4890F82E00D3 +:10349000022801D0012000E000208DF807006846D6 +:1034A0000EF0F0FD002800D0FFDF1CBD00220A80D6 +:1034B000437892B263F3451222F040020A8000780A +:1034C0000C282BD2DFE800F02A06090E1116191C71 +:1034D0001F220C2742F0110009E042F01D00088075 +:1034E0000020704742F0110012E042F0100040F05E +:1034F0000200F4E742F01000F1E742F00100EEE7CD +:1035000042F0010004E042F00200E8E742F002006D +:1035100040F00400E3E742F00400E0E707207047D2 +:103520002DE9FF478AB00025BDF82C6082461C4675 +:1035300091468DF81C50700703D56068FDF789FE31 +:1035400068B9CD4F4FF0010897F82E0058B197F8A1 +:103550002F00022807D16068FDF7C8FE18B11020BF +:103560000EB0BDE8F087300702D5A08980283DD88D +:10357000700705D4B9F1000F02D097F8240098B372 +:10358000E07DC0F300108DF81B00627D0720032151 +:103590005AB3012A2CD0022AE2D0042AE0D18DF8B5 +:1035A0001710F00627D4A27D072022B3012A22D0CB +:1035B000022A23D0042AD3D18DF819108DF8159042 +:1035C000606810B307A9FFF7AAFE0028C8D19DF8CC +:1035D0001C00FF2816D0606850F8011FCDF80F10AE +:1035E0008088ADF8130014E000E001E00720B7E7A1 +:1035F0008DF81780D5E78DF81980DFE702208DF868 +:103600001900DBE743F20220AAE7CDF80F50ADF82E +:103610001350E07B40B9207C30B9607C20B9A07C9D +:1036200010B9E07CC00601D0062099E78DF800A013 +:10363000BDF82C00ADF80200A0680190A0680290CF +:1036400004F10F0001F0A9FC8DF80C00FFF790FECB +:103650008DF80D009DF81C008DF80E008DF81650A9 +:103660008DF81850E07D08A900F00F008DF81A00C1 +:1036700068460FF0E3F905F0D6FD71E7F0B58FB0BD +:1036800000258DF830508DF814508DF834500646D2 +:103690008DF82850019502950395049519B10FC92D +:1036A00001AC84E80F00744CA078052801D00428F0 +:1036B0000CD101986168884200D120B90398E16873 +:1036C000884203D110B108200FB0F0BD207DC006A4 +:1036D00001D51F2700E0FF273B460DAA05A903A837 +:1036E000FFF7AAFD0028EFD1A08AC10702D0C006CB +:1036F00000D4EE273B460AAA0CA901A8FFF79CFDBF +:103700000028E1D19DF81400C00701D00A20DBE7B2 +:10371000A08A410708D4A17D31B19DF828108907FE +:1037200002D043F20120CFE79DF82810C90709D045 +:10373000400707D4208818B144F25061884201D96B +:103740000720C1E78DF818508DF81960BDF8080002 +:10375000ADF81A000198079006A80FF07BF905F064 +:1037600062FD0028B0D18DF820508DF82160BDF8A1 +:103770001000ADF822000398099008A80FF08CF90A +:1037800005F051FD00289FD101AD241D95E80F00E3 +:1037900084E80F00002097E770B586B00D4604005E +:1037A00005D0FDF7A3FD20B1102006B070BD0820A4 +:1037B000FBE72078C107A98802D0FF2902D303E0E4 +:1037C0001F2901D20920F0E7800763D4FFF75CFCD2 +:1037D00038B12178C1F3C100012804D0032802D0F8 +:1037E00005E01320E1E7244890F82400C8B1C80799 +:1037F0004FF001064FF0000502D08DF80F6001E098 +:103800008DF80F50FFF7B4FD8DF800002078694661 +:10381000C0F3C1008DF8010060788DF80250C20835 +:1038200001D00720C1E730B3C20701D08DF8026094 +:10383000820705D59DF8022042F002028DF8022091 +:10384000400705D59DF8020040F004008DF8020005 +:10385000002022780B18C2F38002DA7001EB4002DC +:103860006388D380401CA388C0B253810228F0D360 +:10387000207A78B905E001E0F00100208DF80260BF +:10388000E6E7607A30B9A07A20B9E07A10B9207BF7 +:10389000C00601D0062088E704F1080001F07DFB96 +:1038A0008DF80E0068460EF0F6FC05F0BCFC002812 +:1038B00089D18DF810608DF81150E088ADF81200B4 +:1038C000ADF8145004A80EF039FD05F0ACFC00284A +:1038D00088D12078C00701D0152000E01320FFF721 +:1038E000BDFB002061E72DE9FF470220FD4E8DF86A +:1038F00004000027708EADF80600B84643F20209B6 +:103900004CE001A810F039FA050006D0708EA8B37B +:10391000A6F83280ADF806803EE0039CA07F010748 +:103920002DD504F124000090A28EBDF80800214698 +:1039300004F1360301F0BCFC050005D04D452AD04A +:10394000112D3CD0FFDF3AE0A07F20F00801E07F9E +:10395000420862F3C711A177810861F30000E077A4 +:1039600094F8210000F01F0084F820002078282817 +:1039700026D129212046FFF7CDFB21E014E04007A6 +:103980000AD5BDF8080004F10E0101F01CFB05008A +:103990000DD04D4510D100257F1CFFB2022010F044 +:1039A0002DFA401CB842ACD8052D11D008E0A07FFC +:1039B00020F00400A07703E0112D00D0FFDF0025E8 +:1039C000BDF806007086052D04D0284604B0C8E571 +:1039D000A6F832800020F9E770B50646FFF732FD01 +:1039E000054605F003FE040000D1FFDF6680207865 +:1039F00020F00F00801C20F0F00020302070032009 +:103A0000207295F83E006072BDE8704005F0F1BD8F +:103A10002DE9F04786B0040000D1FFDF2078B14DDA +:103A200020F00F00801C20F0F000703020706068E3 +:103A30000178491F1B2933D2DFE801F0FE32323210 +:103A400055FD320EFDFD42FC32323278FCFCFBFAB1 +:103A500032FCFCF9F8FCFC00C6883046FFF7F2FCAB +:103A60000546304607F045FCE0B16068007A85F80D +:103A70003E0021212846FFF74DFB3046FEF75AFB5A +:103A8000304603F0D7FE3146012014F017F8A87F26 +:103A900020F01000A877FFF726FF002800D0FFDFF6 +:103AA00006B05EE5207820F0F00020302070032082 +:103AB000207266806068007A607205F09AFDD8E72F +:103AC000C5882846FFF7BEFC00B9FFDF60680079B3 +:103AD000012800D0FFDF6068017A06B02846BDE803 +:103AE000F04707F0EBBDC6883046FFF7ABFC05009A +:103AF00000D1FFDF05F07DFD606831460089288137 +:103B000060684089688160688089A881012013F01D +:103B1000D5FF0020A875A87F00F003000228BFD1C0 +:103B2000FFF7E1FE0028BBD0FFDFB9E7007928B13D +:103B30000228B5D03C28B3D0FFDFB1E705F059FD2E +:103B40006668B6F806A0307A361D012806D0687E71 +:103B5000814605F0D4FA070003D101E0E878F7E7E1 +:103B6000FFDF00220221504610F097F9040000D137 +:103B7000FFDF22212046FFF7CDFA3079012800D05F +:103B80000220A17F804668F30101A177308B20815C +:103B9000708B6081B08BA08184F822908DF80880B2 +:103BA000B8680090F86801906A460321504610F00A +:103BB00074F900B9FFDFB888ADF81000B8788DF857 +:103BC000120004AA0521504610F067F900B9FFDF82 +:103BD000B888ADF80C00F8788DF80E0003AA04211F +:103BE000504610F05AF900B9FFDF062106F1120025 +:103BF0000DF00EF940B37079800700D5FFDF7179C1 +:103C0000E07D61F34700E075D6F80600A061708999 +:103C1000A083062106F10C000DF0FAF8F0B195F83A +:103C200025004108607861F3470006E041E039E093 +:103C300071E059E04EE02FE043E06070D5F82600D7 +:103C4000C4F80200688D12E0E07D20F0FE00801CC8 +:103C5000E075D6F81200A061F08AD9E7607820F00C +:103C6000FE00801C6070F068C4F80200308AE080BA +:103C7000B8F1010F04D0B8F1020F05D0FFDF0FE754 +:103C80000320FFF7D3F90BE7287E122800D0FFDFCF +:103C90001120FFF7E3F903E706B02046BDE8F0473F +:103CA00001F092BD05F0A5FC15F8300F40F00200C0 +:103CB00005E005F09EFC15F8300F40F00400287078 +:103CC000EEE6287E13280AD01528D8D15FF016001A +:103CD000FFF7C4F906B0BDE8F04705F08ABC142030 +:103CE000F6E70000F0010020A978052909D0042991 +:103CF000C5D105F07EFC022006B0BDE8F047FFF715 +:103D000095B900790028BAD0E87801F02DF905F0CE +:103D100070FC0320F0E7287E122802D1687E01F0B3 +:103D200023F91120D4E72DE9F05F054600784FF024 +:103D300000080009DFF8B8A891460C46464601285D +:103D40006ED002286DD007280BD00A286AD0FFDF7A +:103D5000A9F8006014B1A4F8008066800020BDE8D6 +:103D6000F09F6968012704F108000B784FF0020BFF +:103D70005B1F4FF6FF721B2B7ED2DFE803F0647DE2 +:103D80007D7D0E7D7D7D7D7D7D217D7D7D2BFDFC81 +:103D9000FBFA7D14D2F9E7F8F700C8884FF0120853 +:103DA000102621469AE14FF01C080A26BCB38888E9 +:103DB000A0806868807920726868C0796072C7E7FF +:103DC0004FF01B08142654B30320207268688088C3 +:103DD000A080BDE70A793C2ABAD00D1D4FF010082B +:103DE0002C26E4B16988A180298B6182298B2182EC +:103DF000698BA182A98BE1826B790246A91D1846C5 +:103E0000FFF7EFFA2979002001290CD084F80FB0D0 +:103E1000FF212176E06120626062A06298E70FE0F6 +:103E20003BE15EE199E1E77320760AF1040090E856 +:103E30000E00DAF81000C4E90930C4E9071287E778 +:103E4000A9F800608AE72C264FF01D08002CF7D057 +:103E5000A28005460F1D897B008861F30000288041 +:103E6000B97A490861F341002880B97A890861F379 +:103E700082002880B97A00E00CE1C90861F3C30030 +:103E80002880B97AAA1C0911491C61F3041000F0BA +:103E90007F0028807878B91CFFF7A3FA387D05F1F8 +:103EA000090207F11501FFF79CFA387B01F0A9F828 +:103EB0002874787B01F0A5F86874F87EA874787A85 +:103EC000E874387F2875B87B6875388AE882DAF834 +:103ED0001C10A961B97A504697F808A0C1F34111A6 +:103EE000012904D0008C504503D2824609E0FFDF4F +:103EF00010E0022903D0288820F0600009E0504536 +:103F000004D1288820F06000403002E0288840F08A +:103F100060002880A4F824A0524607F11D01A8697A +:103F20009BE011264FF02008002C89D0A280686801 +:103F300004F10A02007920726868007B6072696887 +:103F40008B1D48791946FFF74CFA01E70A264FF016 +:103F50002108002CE9D08888A080686880792072C8 +:103F60006868C07960729AF8301006E078E06BE01B +:103F700052E07FE019E003E03AE021F00401A6E01E +:103F80000B264FF02208002CCFD0C888A08068688C +:103F9000007920726868007A01F033F8607268680E +:103FA000407A01F02EF8A072D2E61C264FF02608C7 +:103FB000002CBAD0A2806868407960726868007A84 +:103FC000A0720AF1040090E80E00DAF81000C4E9CB +:103FD0000530C4E90312686800793C2803D04328FF +:103FE00003D0FFDFB4E62772B2E684F808B0AFE68C +:103FF00010264FF02408002C97D08888A08068688D +:10400000807920816868807A608168680089A081F1 +:1040100068688089E0819BE610264FF02308002C19 +:1040200098D08888A0806868C088208168680089E6 +:10403000608168684089A08168688089E0819AF819 +:10404000301021F0020142E030264FF02508002C0C +:104050009AD0A2806968282249680AF0EEF977E6CA +:104060002A264FF02F08002C8ED0A28069682222C9 +:10407000091DF2E714264FF01B08002C84D0A28003 +:10408000686800790128B0D02772DAE90710C4E91E +:1040900003105DE64A46214660E0287A012803D0F5 +:1040A000022817D0FFDF53E610264FF01F08002C20 +:1040B000A2D06888A080A8892081E8896081288AA8 +:1040C000A081688AE0819AF8301021F001018AF815 +:1040D00030103DE64FF012081026688800F07EFF91 +:1040E00036E6287AC8B3012838D0022836D003280B +:1040F00001D0FFDF2CE609264FF01108002C8FD0ED +:104100006F883846FFF79EF990F822A0A780687A5A +:104110002072042138460FF0DBFE052138460FF0EF +:10412000D7FE002138460FF0D3FE012138460FF0AC +:10413000CFFE032138460FF0CBFE022138460FF0A8 +:10414000C7FE062138460FF0C3FE072138460FF0A0 +:10415000BFFE504600F008FFFAE5FFE72846BDE83D +:10416000F05F01F0BBBC70B5012803D0052800D07A +:10417000FFDF70BD8DB22846FFF764F9040000D15F +:10418000FFDF20782128F4D005F030FA80B10178E3 +:1041900021F00F01891C21F0F00110310170022182 +:1041A000017245800020A075BDE8704005F021BA7D +:1041B00021462846BDE870401322FFF746B92DE995 +:1041C000F04116460C00804600D1FFDF307820F029 +:1041D0000F00801C20F0F000103030702078012893 +:1041E00004D0022818D0FFDFBDE8F0814046FFF779 +:1041F00029F9050000D1FFDF0320A87505F0F9F9C2 +:1042000094E80F00083686E80F00F94810F8301FD0 +:1042100041F001010170E7E74046FFF713F905009F +:1042200000D1FFDFA1884FF6FF700027814202D145 +:10423000E288824203D0814201D1E08840B105F09A +:10424000D8F994E80F00083686E80F00AF75CBE781 +:10425000A87D0128C8D178230022414613F084FBB1 +:104260000220A875C0E738B50C4624285CD008DCCD +:1042700020280FD0212825D022284BD0232806D152 +:104280004CE0252841D0262832D03F2851D00725A0 +:10429000284638BD0021052013F0E6FB08B11120A7 +:1042A00038BDA01C0EF0E1FA04F0BDFF0500EFD10F +:1042B000002208231146052013F056FB0528E7D0FD +:1042C000FFDFE5E76068FDF708F808B1102038BDAA +:1042D000618820886A460EF071FD04F0A4FF050095 +:1042E000D6D160680028D3D0BDF800100180CFE798 +:1042F000206820B1FCF7FAFF08B11025C8E7204676 +:104300000EF03BFE1DE00546C2E7A17820880EF0C6 +:1043100086FD16E0086801F08DFEF4E7087800F0ED +:1043200001000DF0B9FD0CE0618820880EF0C1FCA1 +:1043300007E0087800F001008DF8000068460EF0F4 +:10434000DFF804F070FFDEE770B505460C4608465E +:10435000FCF7A5FF08B1102070BD202D07D0212D3E +:104360000DD0222D0BD0252D09D0072070BD20881F +:10437000A11C0DF065FEBDE8704004F054BF06209E +:1043800070BD9B482530704708B5342200219848FD +:104390000AF07DF80120FEF749FE1120FEF75EFECF +:1043A00093496846263105F0B7F891489DF80020FA +:1043B00010F8251F62F3470121F00101017000216F +:1043C00041724FF46171A0F8071002218172FEF76B +:1043D0008FFE00B1FFDFFDF705F801F0BEF908BD63 +:1043E00010B50C464022002120460AF050F8A07F6C +:1043F00020F00300A077202020700020A07584F812 +:10440000230010BD70472DE9FC410746FCF721FF52 +:1044100010B11020BDE8FC81754E06F12501D6F8DB +:1044200025000090B6F82950ADF8045096F82B40BE +:104430008DF806403846FEF7BDFF0028EAD1FEF7AA +:1044400057FE0028E6D0009946F8251FB580B471C4 +:10445000E0E710B50446FCF722FF08B1102010BDBC +:1044600063486349224690F8250026314008FEF74C +:10447000B8FF002010BD3EB504460D460846FCF7C7 +:104480000EFF08B110203EBD14B143F204003EBD42 +:1044900057488078052803D0042801D008203EBD65 +:1044A000694602A80AF012FC2A4669469DF80800EF +:1044B000FEF797FF00203EBDFEB50D4604004FF00D +:1044C000000712D00822FEF79FFE002812D1002616 +:1044D00009E000BF54F826006946FEF720FF0028D7 +:1044E00008D1761CF6B2AE42F4D30DF01CFC10B12C +:1044F00043F20320FEBD3E4E86F824700CB3002725 +:104500001BE000BF54F8270002A9FEF708FF00B126 +:10451000FFDF9DF808008DF8000054F8270050F8E0 +:10452000011FCDF801108088ADF8050068460DF038 +:104530001FFC00B1FFDF7F1CFFB2AF42E2D386F861 +:1045400024500020FEBD2DE9F0418AB01546884672 +:1045500004001ED00F4608222946FEF755FE00280B +:1045600011D1002613E000BF54F826006946103030 +:1045700000F01FFD002806D13FB157F82600FCF7D8 +:1045800068FE10B110200AB02EE6761CF6B2AE42DC +:10459000EAD3681EC6B217E0701CC7B212E000BFB3 +:1045A00054F82600017C4A0854F827100B7CB2EB23 +:1045B000530F05D106221130113109F011FF50B10E +:1045C0007F1CFFB2AF42EBD3761EF6B2E4D2464672 +:1045D00024B1012003E043F20520D4E700200DF0D0 +:1045E000ECFB10B90DF0F5FB20B143F20420CAE753 +:1045F000F001002064B300270DF1170826E000BF8A +:1046000054F827006946103000F0D3FC00B1FFDFFA +:1046100054F82700102250F8111FCDF8011080889F +:10462000ADF8050054F827100DF1070009F005FF5B +:1046300096B156F827101022404609F0FEFE684653 +:104640000DF07BFB00B1FFDF7F1CFFB2AF42D7D381 +:10465000FEF713FF002096E7404601F0DFFCEEE78F +:1046600030B585B00446FDF7BDF830B906200FF02F +:10467000C5FB10B1062005B030BD2046FCF7E9FDB2 +:1046800018B96068FCF732FE08B11020F3E76088C3 +:104690004AF2B811884206D82078F94D28B101288D +:1046A00006D0022804D00720E5E7FEF721FD18E038 +:1046B0006078022804D0032802D043F20220DAE70F +:1046C00085F82F00C1B200200090ADF80400022947 +:1046D0002CD0032927D0FFDF68460DF009FC04F039 +:1046E000A2FD0028C7D1606801F08BFC207858B18A +:1046F00001208DF800000DF1010001F08FFC6846EB +:104700000EF0FDFB00B1FFDF207885F82E00FEF7EC +:10471000B4FE608860B1A88580B20DF046FB00B1A0 +:10472000FFDF0020A7E78DF80500D5E74020FAE776 +:104730004FF46170EFE710B50446FCF7B0FD20B907 +:10474000606838B1FCF7C9FD08B1102010BD606881 +:1047500001F064FCCA4830F82C1F6180C178617098 +:1047600080782070002010BD2DE9F843144689465A +:104770000646FCF794FDA0B94846FCF7B7FD80B9A2 +:104780002046FCF7B3FD60B9BD4DA878012800D1E3 +:104790003CB13178FF2906D049B143F20400BDE8AD +:1047A000F8831020FBE7012801D00420F7E7CCB301 +:1047B000052811D004280FD069462046FEF776FE62 +:1047C0000028ECD1217D49B1012909D0022909D065 +:1047D000032909D00720E2E70820E0E7024604E0C9 +:1047E000012202E0022200E003228046234617460F +:1047F00000200099FEF794FE0028D0D1A0892880DF +:10480000A07BE875BDF80000A882AF75BDF8001068 +:10481000090701D5A18931B1A1892980C00704D038 +:10482000032003E006E08021F7E70220FEF7FEFB0D +:1048300086F800804946BDE8F8430020FEF71EBF19 +:104840007CB58F4C05460E46A078022803D003287D +:1048500001D008207CBD15B143F204007CBD0720C7 +:104860000FF0D4FA10B9A078032806D0FEF70CFC9C +:1048700028B1A078032804D009E012207CBD1320C1 +:104880007CBD304600F053FB0028F9D1E670FEF7FE +:104890006FFD0AF058F901208DF800008DF8010035 +:1048A0008DF802502088ADF80400E07D8DF80600F8 +:1048B00068460EF0C1F904F0B6FC0028E0D1A078FB +:1048C000032805D05FF00400FEF7B0FB00207CBD9C +:1048D000E07800F03CFB0520F6E71CB510B143F290 +:1048E00004001CBD664CA078042803D0052801D024 +:1048F00008201CBD00208DF8000001218DF801105A +:104900008DF8020068460EF097F904F08CFC002840 +:10491000EFD1A078052805D05FF00200FEF786FBF6 +:1049200000201CBDE07800F01FFB0320F6E72DE916 +:10493000FC4180460E4603250846FCF7D7FC0028BC +:1049400066D14046FEF77EFD040004D02078222880 +:1049500004D208205EE543F202005BE5A07F00F090 +:1049600003073EB1012F0CD000203146FEF727FC93 +:104970000500EFD1012F06D0022F1AD0FFDF284605 +:1049800048E50120F1E7A07D3146022801D011B1B0 +:1049900007E011203EE56846FCF758FE0028D9D113 +:1049A0006946404606F04DFE0500E8D10120A0759D +:1049B000E5E7A07D032804D1314890F83000C00716 +:1049C00001D02EB30EE026B1A07F40071ED40021F7 +:1049D00000E00121404606F054FE0500CFD1A0754D +:1049E000002ECCD03146404600F0EDFA05461128A5 +:1049F000C5D1A07F4107C2D4316844F80E1F716849 +:104A0000616040F0040020740025B8E71125B6E786 +:104A10001020FFE470B50C460546FEF713FD0100BB +:104A200005D022462846BDE87040FEF70EBD43F291 +:104A3000020070BD10B5012807D1114B9B78012BE6 +:104A400000D011B143F2040010BD0DF0E0F9BDE853 +:104A5000104004F0E8BB012300F090BA00231A468E +:104A6000194600F08BBA70B506460C460846FCF7AE +:104A7000F0FB18B92068FCF712FC18B1102070BDCB +:104A8000F0010020F84D2A7E112A04D0132A00D309 +:104A90003EB10820F3E721463046FEF77DFE60B1C7 +:104AA000EDE70920132A0DD0142A0BD0A188FF2985 +:104AB000E5D31520FEF7D2FA0020D4E90012C5E9AB +:104AC0000712DCE7A1881F29D9D31320F2E71CB510 +:104AD000E548007E132801D208201CBD00208DF877 +:104AE000000068460DF02AFC04F09DFB0028F4D17C +:104AF0001120FEF7B3FA00201CBD2DE9F04FDFF8BE +:104B000068A3814691B09AF818009B4615460C465A +:104B1000132803D3FFF7DBFF00281FD12046FCF743 +:104B200098FBE8BB2846FCF794FBC8BB20784FF005 +:104B30000107C0074FF0000102D08DF83A7001E084 +:104B40008DF83A1020788846C0F3C1008DF8000037 +:104B500060788DF80910C10803D0072011B0BDE8B6 +:104B6000F08FB0B3C10701D08DF80970810705D56A +:104B70009DF8091041F002018DF80910400705D594 +:104B80009DF8090040F004008DF809009DF8090027 +:104B9000810703D540F001008DF80900002000E0F6 +:104BA00015E06E4606EB400162884A81401CA288EF +:104BB000C0B20A820328F5D32078C0F3C1000128CF +:104BC00025D0032823D04846FCF743FB28B110200A +:104BD000C4E7FFE78DF80970D8E799F800004008AE +:104BE00008D0012809D0022807D0032805D043F2B5 +:104BF0000220B3E78DF8028001E08DF8027048468C +:104C000050F8011FCDF803108088ADF80700FEF7BB +:104C1000AFFB8DF801000021424606EB41002B88D6 +:104C2000C3826B888383AB884384EB880385491CEC +:104C3000C285C9B282860329EFD3E088ADF83C0073 +:104C400068460DF053FC002887D19AF818005546A5 +:104C5000112801D0082081E706200FF0D7F838B1DD +:104C60002078C0F3C100012804D0032802D006E058 +:104C7000122073E795F8240000283FF46EAFFEF78A +:104C800003FA022801D2132068E7584600F04FF9D2 +:104C900000289DD185F819B068460DF06DFD04F02F +:104CA000C2FA040094D1687E00F051F91220FEF798 +:104CB000D5F9204652E770B56B4D287E122801D0F9 +:104CC0000820DCE60DF05BFD04F0ADFA040005D130 +:104CD000687E00F049F91120FEF7C0F92046CEE6C3 +:104CE00070B5064615460C460846FCF7D8FA18B9C2 +:104CF0002846FCF7D4FA08B11020C0E62A4621461F +:104D000030460EF03BF804F08EFA0028F5D12178F9 +:104D10007F29F2D10520B2E67CB505460C4608464F +:104D2000FCF797FA08B110207CBD2846FEF78AFBF5 +:104D300020B10078222804D208207CBD43F2020072 +:104D40007CBD494890F83000400701D511207CBD5A +:104D50002078C00802D16078C00801D007207CBD4F +:104D6000ADF8005020788DF8020060788DF80300CF +:104D70000220ADF8040068460CF03BFE04F053FA44 +:104D80007CBD70B586B014460D460646FEF75AFB4C +:104D900028B10078222805D2082006B06FE643F239 +:104DA0000200FAE72846FCF7A1FA20B944B12046F0 +:104DB000FCF793FA08B11020EFE700202060A080F4 +:104DC000294890F83000800701D51120E5E703A9B4 +:104DD00030460CF05EFE10B104F025FADDE7ADF8C8 +:104DE0000060BDF81400ADF80200BDF81600ADF883 +:104DF0000400BDF81000BDF81210ADF80600ADF8C3 +:104E000008107DB1298809B1ADF80610698809B18B +:104E1000ADF80210A98809B1ADF80810E98809B108 +:104E2000ADF80410DCB1BDF80610814201D9081AB2 +:104E30002080BDF80210BDF81400814201D9081A83 +:104E40006080BDF80800BDF80410BDF816200144CC +:104E5000BDF812001044814201D9081AA0806846AA +:104E60000CF0D5FEB8E70000F00100201CB56C493D +:104E70000968CDE9001068460DF03AFB04F0D3F95B +:104E80001CBD1CB500200090019068460DF030FB61 +:104E900004F0C9F91CBD70B505460C460846FCF780 +:104EA000FEF908B11020EAE5214628460DF012F976 +:104EB000BDE8704004F0B7B93EB505460C4608465B +:104EC000FCF7EDF908B110203EBD002000900190E4 +:104ED0000290ADF800502089ADF8080020788DF8D8 +:104EE0000200606801902089ADF808006089ADF883 +:104EF0000A0068460DF000F904F095F93EBD0EB5C4 +:104F0000ADF800000020019068460DF0F5F804F0BF +:104F10008AF90EBD10800888508048889080C88823 +:104F200010818888D080002050819081704710B512 +:104F3000044604F0E4F830B1407830B1204604F083 +:104F4000FCFB002010BD052010BD122010BD10B5C7 +:104F500004F0D5F8040000D1FFDF607800B9FFDF6E +:104F60006078401E607010BD10B504F0C8F80400F1 +:104F700000D1FFDF6078401C607010BD1CB5ADF83B +:104F800000008DF802308DF803108DF8042068467B +:104F90000DF0B7FE04F047F91CBD0CB521A2D2E913 +:104FA0000012CDE900120079694601EB501000783B +:104FB0000CBD0278520804D0012A02D043F202202C +:104FC0007047FEF7ACB91FB56A46FFF7A3FF684606 +:104FD0000DF008FC04F027F904B010BD70B50C000A +:104FE00006460DD0FEF72EFA050000D1FFDFA680A1 +:104FF00028892081288960816889A081A889E08129 +:105000003DE500B540B1012805D0022803D00328B2 +:1050100004D0FFDF002000BDFF2000BD042000BD44 +:1050200014610200070605040302010010B50446DE +:10503000FCF70FF908B1102010BD2078C0F3021062 +:10504000042807D86078072804D3A178102901D84C +:10505000814201D2072010BDE078410706D42179B2 +:105060004A0703D4000701D4080701D5062010BD64 +:10507000002010BD10B513785C08837F64F3C7135C +:10508000837713789C08C37F64F30003C377107899 +:10509000C309487863F34100487013781C090B7802 +:1050A00064F347130B701378DB0863F30000487058 +:1050B0005078487110BD10B5C4780B7864F30003C4 +:1050C0000B70C478640864F341030B70C478A408BF +:1050D00064F382030B70C478E40864F3C3030B70B9 +:1050E0000379117863F30001117003795B0863F3AE +:1050F0004101117003799B0863F3820111700079FB +:10510000C00860F3C301117010BD70B514460D46A0 +:10511000064604F06BFA80B10178182221F00F01E5 +:10512000891C21F0F001A03100F8081B214609F08C +:1051300084F9BDE8704004F05CBA29463046BDE809 +:1051400070401322FEF781B92DE9F047064608A802 +:10515000904690E8300489461F46142200212846D4 +:1051600009F095F90021CAF80010B8F1000F03D03A +:10517000B9F1000F03D114E03878C00711D02068CE +:10518000FCF78DF8C0BBB8F1000F07D120681230D2 +:1051900028602068143068602068A8602168CAF818 +:1051A00000103878800724D56068FCF796F818BBA3 +:1051B000B9F1000F21D0FFF7E4F80168C6F86811D3 +:1051C0008188A6F86C11807986F86E0101F013FDD4 +:1051D000F94FEF60626862B196F8680106F26911F2 +:1051E00040081032FEF7FDF810223946606809F0D9 +:1051F00024F90020BDE8F08706E0606820B1E8608F +:105200006068C6F86401F4E71020F3E730B505469E +:1052100008780C4620F00F00401C20F0F0011031FF +:1052200021700020607095F8230030B104280FD061 +:10523000052811D0062814D0FFDF20780121B1EB1A +:10524000101F04D295F8200000F01F00607030BDE0 +:1052500021F0F000203002E021F0F000303020702A +:10526000EBE721F0F0004030F9E7F0B591B002270C +:1052700015460C4606463A46ADF80870092103ABC0 +:1052800005F063F80490002810D004208DF8040085 +:105290008DF80170E034099605948DF818500AA92C +:1052A000684610F0FCFA00B1FFDF012011B0F0BD3C +:1052B00010B588B00C460A99ADF80000CBB118685B +:1052C000CDF80200D3F80400CDF80600ADF80A20AE +:1052D000102203A809F0B1F868460DF0F3FA03F0C4 +:1052E000A2FF002803D1A17F41F01001A17708B0EF +:1052F00010BD0020CDF80200E6E72DE9F84F064684 +:10530000808A0D4680B28246FEF79CF804463078CB +:10531000DFF8A48200274FF00209A8F120080F2827 +:1053200070D2DFE800F06FF23708387D8CC8F1F0FA +:10533000EFF35FF3F300A07F00F00300022809D031 +:105340005FF0000080F0010150460EF0AFFD050057 +:1053500003D101E00120F5E7FFDF98F85C10C907F1 +:1053600002D0D8F860000BE0032105F11D0012F017 +:10537000BEF8D5F81D009149B0FBF1F201FB120017 +:10538000C5F81D0070686867B068A8672078252890 +:1053900000D0FFDFCAE0A07F00F00300022809D0A0 +:1053A0005FF0000080F0010150460EF07FFD060026 +:1053B00003D101E00120F5E7FFDF3078810702D556 +:1053C0002178252904D040F001003070BDE8F88F25 +:1053D00085F80090307F287106F11D002D36C5E953 +:1053E0000206F3E7A07F00F00300022808D00020A7 +:1053F00080F0010150460EF059FD040004D102E096 +:105400000120F5E7A7E1FFDF2078C10604D50720DA +:1054100028703D346C60D9E740F008002070D5E773 +:10542000E07F000700D5FFDF307CB28800F0010389 +:1054300001B05046BDE8F04F092106F064B804B948 +:10544000FFDF716821B1102204F1240008F0F5FF9C +:1054500028212046FDF75EFEA07F00F00300022811 +:105460000ED104F12400002300901A462146504634 +:10547000FFF71EFF112807D029212046FDF74AFE1D +:10548000307A84F82000A1E7A07F000700D5FFDF75 +:1054900014F81E0F40F008002070E782A761E76152 +:1054A000C109607861F34100014660F382016170D7 +:1054B000307AE0708AE7A07F00F00300022809D06C +:1054C0005FF0000080F0010150460EF0EFFC040098 +:1054D00003D101E00120F5E7FFDF022104F185009F +:1054E00012F005F80420287004F5B4706860B4F870 +:1054F00085002882304810387C346C61C5E9028010 +:1055000064E703E024E15BE02DE015E0A07F00F01C +:105510000300022807D0002080F0010150460EF061 +:10552000C5FC18B901E00120F6E7FFDF324621464D +:105530005046BDE8F84FE8E504B9FFDF20782128A0 +:10554000A1D93079012803D1E07F40F00800E0774D +:10555000324621465046FFF7D8FD2046BDE8F84FB9 +:105560002321FDF7D7BD3279AA8005F1080309216F +:10557000504604F0EAFEE86010B10520287025E7E7 +:10558000A07F00F00300022808D0002080F0010175 +:1055900050460EF08BFC040003D101E00120F5E73A +:1055A000FFDF04F1620102231022081F0EF005FB49 +:1055B00007703179417009E75002002040420F0026 +:1055C000A07F00F00300022808D0002080F0010135 +:1055D00050460EF06BFC050003D101E00120F5E719 +:1055E000FFDF95F8840000F0030001287AD1A07F46 +:1055F00000F00307E07F10F0010602D0022F04D173 +:1056000033E095F8A000C0072BD0D5F8601121B386 +:1056100095F88320087C62F387000874A17FCA098B +:10562000D5F8601162F341000874D5F8601166F393 +:1056300000000874AEB1D5F86001102204F1240115 +:10564000883508F0FAFE287E40F001002876287898 +:1056500020F0010005F8880900E016B1022F04D0FF +:105660002DE095F88800C00727D0D5F85C1121B34C +:1056700095F88320087C62F387000874A17FCA092B +:10568000D5F85C1162F341000874D5F85C1166F33B +:10569000000008748EB1D5F85C01102204F12401D9 +:1056A000883508F0CAFE287840F0010005F8180B8C +:1056B000287820F0010005F8A009022F44D000202E +:1056C00000EB400005EBC00090F88800800709D58A +:1056D00095F87C00D5F86421400805F17D01103271 +:1056E000FDF77FFE8DF8009095F884006A4600F083 +:1056F00003008DF8010095F888108DF8021095F8D8 +:10570000A0008DF803002146504601F05DFA207894 +:10571000252805D0212807D0FFDF2078222803D9AB +:1057200022212046FDF7F6FCA07F00F003000228AE +:105730000CD0002080F0010150460EF0C9FB00287B +:105740003FF44FAEFFDF41E60120B9E70120F1E76A +:10575000706847703AE6FFDF38E670B5FE4C00250A +:1057600084F85C50256610F066F804F110012046BC +:1057700003F0F8FE84F8305070BD70B50D46FDF7AB +:1057800061FE040000D1FFDF4FF4B872002128460B +:1057900008F07DFE04F124002861A07F00F00300E2 +:1057A000022809D05FF0010105F1E00010F044F893 +:1057B000002800D0FFDF70BD0221F5E70A46014650 +:1057C00002F1E00010F059B870B50546406886B0A7 +:1057D00001780A2906D00D2933D00E292FD0FFDFFA +:1057E00006B070BD86883046FDF72CFE040000D15F +:1057F000FFDF20782128F3D028281BD168680221F8 +:105800000E3001F0D6F9A8B168680821801D01F0BA +:10581000D0F978B104F1240130460DF00FFA03F00D +:1058200002FD00B1FFDF06B02046BDE8704029212F +:10583000FDF770BC06B0BDE8704003F0DABE012190 +:1058400001726868C6883046FDF7FCFD040000D18F +:10585000FFDFA07F00F00301022902D120F0100039 +:10586000A077207821280AD06868017A09B10079E8 +:1058700080B1A07F00F00300022862D0FFDFA07F8C +:1058800000F003000228ABD1FEF72DF80028A7D0C6 +:10589000FFDFA5E703F0ADFEA17F08062BD5E07F73 +:1058A000C00705D094F8200000F01F00102820D079 +:1058B0005FF0050084F82300207829281DD02428D3 +:1058C000DDD13146042012F0F9F822212046FDF7FF +:1058D00021FCA07F00F00300022830D05FF0000020 +:1058E00080F0010130460EF0F3FA0028C7D0FFDF48 +:1058F000C5E70620DEE70420DCE701F0030002280C +:1059000008D0002080F0010130460EF0CFFA0500EB +:1059100003D101E00120F5E7FFDF25212046FDF757 +:10592000F9FB03208DF80000694605F1E0000FF057 +:105930009BFF0228A3D00028A1D0FFDF9FE7012012 +:10594000CEE703F056FE9AE72DE9F04387B099467B +:10595000164688460746FDF775FD04004BD02078B3 +:10596000222848D3232846D0E07F000743D4A07FD5 +:1059700000F00300022809D05FF0000080F0010170 +:1059800038460EF093FA050002D00CE00120F5E74E +:10599000A07F00F00300022805D001210022384634 +:1059A0000EF07BFA05466946284601F034F9009866 +:1059B00000B9FFDF45B10098E03505612078222865 +:1059C00006D0242804D007E000990020086103E0F5 +:1059D00025212046FDF79EFB00980121417047627A +:1059E000868001A9C0E902890FF059FF022802D080 +:1059F000002800D0FFDF07B0BDE8F08370B586B0A7 +:105A00000546FDF71FFD017822291ED9807F00F091 +:105A10000300022808D0002080F0010128460EF083 +:105A200045FA04002FD101E00120F5E7FFDF2AE06D +:105A3000B4F85E0004F1620630440178427829B17E +:105A400021462846FFF711FCB0B9C9E6ADF804209D +:105A50000921284602AB04F078FC03900028F4D01A +:105A600005208DF80000694604F1E0000FF0FCFE0F +:105A7000022801D000B1FFDF02231022314604F1D9 +:105A80005E000EF0D0F8B4F860000028D0D1A7E690 +:105A900010B586B00446FDF7D5FC017822291BD944 +:105AA000807F00F00300022808D0002080F0010170 +:105AB00020460EF0FBF9040003D101E00120F5E7D8 +:105AC000FFDF06208DF80000694604F1E0000FF0CA +:105AD000CBFE002800D0FFDF06B010BD2DE9F05F3F +:105AE00005460C4600270078904601093E4604F121 +:105AF000080BBA4602297DD0072902D00A2909D10C +:105B000046E0686801780A2905D00D2930D00E29B1 +:105B10002ED0FFDFBBE114271C26002C6BD0808821 +:105B2000A080FDF78FFC5FEA000900D1FFDF99F844 +:105B300017005A46400809F11801FDF752FC686841 +:105B4000C0892082696851F8060FC4F812004868BD +:105B5000C4F81600A07E01E03002002020F006000C +:105B600040F00100A07699F81E0040F020014DE0C1 +:105B70001A270A26002CD1D0C088A080FDF762FC2D +:105B8000050000D1FFDF59462846FFF73FFB7EE1C5 +:105B90000CB1A88BA080287A0B287DD006DC0128C8 +:105BA0007BD0022808D0032804D135E00D2875D019 +:105BB0000E2874D0FFDF6AE11E270926002CADD025 +:105BC000A088FDF73FFC5FEA000900D1FFDF287BDA +:105BD00000F003000128207A1BD020F00100207281 +:105BE000297B890861F341002072297BC90861F390 +:105BF000820001E041E1F2E02072297B090961F3B2 +:105C0000C300207299F81E0040F0400189F81E1070 +:105C10003DE140F00100E2E713270D26002CAAD059 +:105C2000A088FDF70FFC8146807F00F0030002286A +:105C300008D0002080F00101A0880EF037F905009F +:105C400003D101E00120F5E7FFDF99F81E0000F025 +:105C50000302022A50D0686F817801F00301012904 +:105C6000217A4BD021F00101217283789B0863F3E4 +:105C7000410121728378DB0863F38201217283780A +:105C80001B0963F3C3012172037863F306112172C8 +:105C9000437863F3C71103E061E0A9E090E0A1E07D +:105CA000217284F809A0C178A172022A29D0027950 +:105CB000E17A62F30001E1720279520862F3410174 +:105CC000E1720279920862F38201E1720279D208EC +:105CD00062F3C301E1724279217B62F30001217317 +:105CE0004279520862F3410121734279920862F3CA +:105CF00082012173407928E0A86FADE741F00101EE +:105D0000B2E74279E17A62F30001E1724279520826 +:105D100062F34101E1724279920862F38201E17219 +:105D20004279D20862F3C301E1720279217B62F306 +:105D3000000121730279520862F341012173027953 +:105D4000920862F3820121730079C00860F3C301F5 +:105D5000217399F80000232831D9262140E0182723 +:105D60001026E4B3A088FDF76DFB8346807F00F02A +:105D70000300022809D0002080F00101A0880EF065 +:105D800095F85FEA000903D101E00120F4E7FFDFA5 +:105D9000E868A06099F8000040F0040189F800105C +:105DA00099F80100800708D5012020739BF80000B6 +:105DB00023286CD92721584651E084F80CA066E0CE +:105DC00015270F265CB1A088FDF73CFB8146062213 +:105DD0005946E86808F0C7FB0120A073A0E041E045 +:105DE00048463CE016270926E4B3287B20724EE0A3 +:105DF000287B19270E26ACB3C4F808A0A4F80CA081 +:105E0000012807D0022805D0032805D0042803D094 +:105E1000FFDF0DE0207207E0697B042801F00F012D +:105E200041F0800121721ED0607A20F00300607280 +:105E3000A088FDF707FB05460078212827D02328F6 +:105E400000D0FFDFA87F00F00300022813D000205D +:105E500080F00101A0880EF03BF822212846FDF7D2 +:105E600059F914E004E0607A20F00300401CDEE7FA +:105E7000A8F8006010E00120EAE70CB16888A08073 +:105E8000287A68B301280AD002284FD0FFDFA8F88B +:105E900000600CB1278066800020BDE8F09F1527C8 +:105EA0000F26002CE4D0A088FDF7CCFA807F00F00C +:105EB0000300022808D0002080F00101A0880DF026 +:105EC000F5FF050003D101E00120F5E7FFDFD5F87C +:105ED0001D000622594608F046FB84F80EA0D6E7BE +:105EE00017270926002CC3D0A088FDF7ABFA8146FE +:105EF000807F00F00300022808D0002080F001011C +:105F0000A0880DF0D3FF050003D101E00120F5E7E3 +:105F1000FFDF6878800701D5022000E001202072B1 +:105F200099F800002328B2D9272159E719270E260E +:105F3000002C9DD0A088FDF785FA5FEA000900D10A +:105F4000FFDFC4F808A0A4F80CA084F808A0A07A89 +:105F500040F00300A07299F81E10C90961F3820095 +:105F6000A07299F81F2099F81E1012EAD11F05D0CF +:105F700099F8201001F01F0110292BD020F0080003 +:105F8000A07299F81F10607A61F3C3006072697A99 +:105F900001F003010129A2D140F00400607299F8D8 +:105FA0001E0000F003000228E87A16D0217B60F37F +:105FB00000012173AA7A607B62F300006073EA7AC1 +:105FC000520862F341012173A97A490861F3410043 +:105FD00060735CE740F00800D2E7617B60F300018A +:105FE0006173AA7A207B62F300002073EA7A520878 +:105FF00062F341016173A97A490861F3410020739A +:1060000045E710B5FE4C30B10146102204F12000E6 +:1060100008F013FA012084F8300010BD10B50446D2 +:1060200000F0E9FDF64920461022BDE8104020317D +:1060300008F003BA70B5F24D06004FF0000413D01B +:10604000FBF707F908B110240CE00621304608F0F0 +:1060500071FA411C05D028665FF0010085F85C00EC +:1060600000E00724204670BD0020F7E7007810F01C +:106070000F0204D0012A05D0022A0CD110E0000939 +:1060800009D10AE00009012807D0022805D0032819 +:1060900003D0042801D00720704708700020704703 +:1060A0000620704705282AD2DFE800F003070F1703 +:1060B0001F00087820F0FF001EE0087820F00F0095 +:1060C000401C20F0F000103016E0087820F00F009F +:1060D000401C20F0F00020300EE0087820F00F0087 +:1060E000401C20F0F000303006E0087820F00F006F +:1060F000401C20F0F000403008700020704707205E +:1061000070472DE9F041804688B00D4600270846CB +:10611000FBF7ECF8A8B94046FDF794F9040003D06A +:106120002078222815D104E043F2020008B0BDE82F +:10613000F08145B9A07F410603D500F00300022895 +:1061400001D01020F2E7A07FC10601D4010702D5DB +:106150000DB10820EAE7E17F090701D50D20E5E749 +:1061600000F0030002280DD165B12846FEF75EFF5E +:106170000700DBD1FBF736FB20B9E878800701D5B3 +:106180000620D3E7A07F00F00300022808D00020FB +:1061900080F0010140460DF089FE060002D00FE0BC +:1061A0000120F5E7A07F00F0030002280ED00020B8 +:1061B00080F00101002240460DF06FFE060007D07E +:1061C000A07F00F00300022804D009E00120EFE7DF +:1061D0000420ABE725B12A4631462046FEF74AFFA8 +:1061E0006946304600F017FD009800B9FFDF0099BE +:1061F000022006F1E0024870C1F824804A610022C2 +:106200000A81A27F02F00302022A1CD00120087139 +:10621000287800F00102087E62F3010008762A78EF +:10622000520862F3820008762A78920862F3C3006B +:1062300008762A78D20862F30410087624212046D2 +:10624000FCF768FF33E035B30871301D88613078A2 +:10625000400908777078C0F340004877287800F04C +:106260000102887F62F301008877A27FD20962F37E +:1062700082008877E27F62F3C3008877727862F3E6 +:1062800004108877A878C87701F1210228462031C8 +:10629000FEF711FF03E00320087105200876252191 +:1062A0002046FCF737FFA07F20F04000A07701A92F +:1062B00000980FF0F4FA022801D000B1FFDF384651 +:1062C00034E72DE9FF4F8DB09A4693460D460027DF +:1062D0000D98FDF7B7F8060006D03078262806D0CE +:1062E000082011B0BDE8F08F43F20200F9E7B07F5B +:1062F00000F00309B9F1020F11D04DB95846FEF76D +:1063000095FE0028EDD1B07F00F00300022806D0F2 +:10631000BBF1000F11D0FBF765FA20B10DE0BBF126 +:10632000000F50D109E006200DF068FD28B19BF860 +:106330000300800701D50620D3E7B07F00F00300FB +:10634000022809D05FF0000080F001010D980DF0E7 +:10635000ADFD040003D101E00120F5E7FFDF852D4D +:1063600027D007DCEDB1812D1DD0822D1DD0832DCE +:1063700008D11CE0862D1ED0882D1ED0892D1ED060 +:106380008A2D1ED00F2020710F281CD003F02EF96B +:10639000D8B101208DF81400201D06902079B0B1ED +:1063A00056E10020EFE70120EDE70220EBE70320B4 +:1063B000E9E70520E7E70620E5E70820E3E709200D +:1063C000E1E70A20DFE707208BE7112089E7B9F131 +:1063D000020F03D0A56F03D1A06F02E0656FFAE74B +:1063E000606F804631D04FF0010001904FF0020005 +:1063F00000905A4621463046FEF73CFE02E000007F +:10640000300200209BF8000000F00101A87861F341 +:106410000100A870B17FC90961F38200A870F17F03 +:1064200061F3C300A870617861F30410A87020784C +:10643000400928706078C0F3400068709BF8020043 +:10644000E87000206871287103E0022001900120AB +:106450000090A87898F80210C0F3C000C1F3C00102 +:10646000084003902CD05046FAF7F3FEC0BBDAF890 +:106470000C00FAF7EEFE98BBDAF81C00FAF7E9FE1A +:1064800070BBDAF80C00A060DAF81C00E0606078FD +:1064900098F8012042EA500161F34100607098F8D9 +:1064A0000210C0B200EA111161F300006070002018 +:1064B0002077009906F11700022907D0012106E094 +:1064C000607898F8012002EA5001E5E7002104EB2A +:1064D000810148610199701C022902D0012101E06B +:1064E00028E0002104EB81014861A87800F0030056 +:1064F000012857D198F8020000F00300012851D17B +:10650000B9F1020F04D02A1D691D5846FEF7D3FDCC +:10651000287998F8041008408DF82C00697998F8CB +:10652000052011408DF8301008433BD05046FAF753 +:1065300090FE08B11020D4E60AF110018B46B9F1A3 +:10654000020F17D00846002104F18C03CDE90003A7 +:1065500004F5AE7202920BAB2046039AFEF7F4FDEF +:106560000028E8D1B9F1020F08D0504608D14FF009 +:10657000010107E050464FF00101E5E75846F5E715 +:106580004FF0000104F1A403CDE9000304F5B0725B +:10659000029281F001010CAB2046039AFEF7D4FD74 +:1065A0000028C8D16078800733D4A87898F8021002 +:1065B000C0F38000C1F3800108432AD0297898F8FD +:1065C0000000F94AB9F1020F06D032F81120430059 +:1065D000DA4002F003070AE032F810204B00DA40FC +:1065E00012F0030705D0012F0AD0022F0AD0032F83 +:1065F00006D0039A6AB1012906D0042904D008E024 +:106600000227F6E70127F4E7012801D0042800D18A +:106610000427B07F40F08000B077F17F039860F3EB +:106620000001F1776078800705D50320A0710398F9 +:1066300070B9002029E00220022F18D0012F18D0B5 +:10664000042F2AD00020A071B07F20F08000B07706 +:1066500025213046FCF75EFD05A904F1E0000FF0AE +:1066600003F910B1022800D0FFDF002039E6A07145 +:10667000DFE7A0710D22002104F1200007F007FFE1 +:10668000207840F00200207001208DF8100004AA4C +:1066900031460D9800F098FADAE70120A071D7E7AB +:1066A0002DE9F04387B09046894604460025FCF763 +:1066B000C9FE060006D03078272806D0082007B08B +:1066C000BDE8F08343F20200F9E7B07F00F0030079 +:1066D000022809D05FF0000080F0010120460DF093 +:1066E000E5FB040003D101E00120F5E7FFDFA77916 +:1066F0005FEA090005D0012821D0B9F1020F26D1A7 +:1067000010E0B8F1000F22D1012F05D0022F05D0E3 +:10671000032F05D0FFDF2EE00C252CE001252AE019 +:10672000022528E04046FAF794FDB0B9032F0ED1B8 +:106730001022414604F11D0007F07FFE1BE0012FEF +:1067400002D0022F03D104E0B8F1000F13D00720CC +:10675000B5E74046FAF77DFD08B11020AFE71022FB +:10676000002104F11D0007F092FE0621404607F0CB +:10677000E1FEC4F81D002078252140F002002070C1 +:106780003046FCF7C7FC2078C10713D020F0010089 +:10679000207002208DF8000004F11D0002908DF899 +:1067A00004506946C3300FF05FF8022803D010B1DF +:1067B000FFDF00E02577002081E730B587B00D4688 +:1067C0000446FCF73FFE98B1807F00F003000228EA +:1067D00011D0002080F0010120460DF067FB04007D +:1067E0000ED02846FAF735FD38B1102007B030BD7D +:1067F00043F20200FAE70120ECE72078400701D4D9 +:106800000820F3E7294604F13D002022054607F061 +:1068100014FE207840F01000207001070FD520F002 +:106820000800207007208DF80000694604F1E000A0 +:1068300001950FF019F8022801D000B1FFDF002008 +:10684000D4E770B50D460646FCF7FCFD18B101789B +:10685000272921D102E043F2020070BD807F00F0C1 +:106860000300022808D0002080F0010130460DF01E +:106870001DFB040003D101E00120F5E7FFDFA07953 +:10688000022809D16078C00706D02A462146304642 +:10689000FEF7EBFC10B10FE0082070BDB4F860000B +:1068A0000E280BD204F1620102231022081F0DF002 +:1068B00084F9012101704570002070BD112070BD68 +:1068C00070B5064614460D460846FAF7C2FC18B9DC +:1068D0002046FAF7E4FC08B1102070BDA6F57F4011 +:1068E000FF380ED03046FCF7ADFD38B14178224676 +:1068F0004B08811C1846FCF774FD07E043F20200C8 +:1069000070BD2046FDF7A5FD0028F9D11021E01D3E +:1069100010F0EDFDE21D294604F1170000F08BF99F +:10692000002070BD2DE9F04104468AB01546884626 +:1069300000270846FAF7DAFC18B92846FAF7D6FC19 +:1069400018B110200AB0BDE8F0812046FCF77AFDAE +:10695000060003D0307827281BD102E043F2020062 +:10696000F0E7B07F00F00300022809D05FF00000DC +:1069700080F0010120460DF099FA040003D101E0F6 +:106980000120F5E7FFDF2078400702D56078800717 +:1069900001D40820D6E7B07F00F00300022805D01C +:1069A000A06F05D1A16F04E01C610200606FF8E7E1 +:1069B000616F407800B19DB1487810B1B8F1000F17 +:1069C0000ED0ADB1EA1D06A8E16800F034F910223E +:1069D00006A905F1170007F003FD18B1042707E029 +:1069E0000720AFE71022E91D04F12D0007F025FD77 +:1069F000B8F1000F06D0102208F1070104F11D00C4 +:106A000007F01BFD2078252140F002002070304661 +:106A1000FCF780FB2078C10715D020F00100207022 +:106A200002208DF8000004F11D0002901030039048 +:106A30008DF804706946B3300EF016FF022803D0BB +:106A400010B1FFDF00E0277700207BE7F8B515469F +:106A50000E460746FCF7F6FC040004D020782228F6 +:106A600004D00820F8BD43F20200F8BDA07F00F07A +:106A70000300022802D043F20500F8BD3046FAF7C1 +:106A8000E8FB18B92846FAF7E4FB08B11020F8BD76 +:106A900000953288B31C21463846FEF709FC1128C0 +:106AA00015D00028F3D1297C4A08A17F62F3C711D1 +:106AB000A177297CE27F61F30002E277297C8908D3 +:106AC00084F82010A17F21F04001A177F8BDA17FBB +:106AD0000907FBD4D6F80200C4F83600D6F8060041 +:106AE000C4F83A003088A0861022294604F1240018 +:106AF00007F0A3FC287C4108E07F61F34100E077C8 +:106B0000297C61F38200E077287C800884F82100EA +:106B1000A07F40F00800A0770020D3E770B50D46B5 +:106B200006460BB1072070BDFCF78CFC040007D0B3 +:106B30002078222802D3A07F800604D4082070BDCC +:106B400043F2020070BDADB1294630460CF076F834 +:106B500002F069FB297C4A08A17F62F3C711A17783 +:106B6000297CE27F61F30002E277297C890884F8BE +:106B7000201004E030460CF084F802F054FBA17FB2 +:106B800021F02001A17770BD70B50D46FCF75AFCCD +:106B9000040005D02846FAF782FB20B1102070BD12 +:106BA00043F2020070BD29462046FEF72FFB00206D +:106BB00070BD04E010F8012B0AB100207047491E97 +:106BC00089B2F7D20120704770B51546064602F02B +:106BD0000DFD040000D1FFDF207820F00F00801CA5 +:106BE00020F0F0002030207066802868A060BDE8AA +:106BF000704002F0FEBC10B5134C94F83000002831 +:106C000008D104F12001A1F110000EF06FFE012067 +:106C100084F8300010BD10B190F8B9202AB10A48AC +:106C200090F8350018B1002003E0B83001E00648C4 +:106C300034300860704708B50023009313460A46B5 +:106C40000DF031FB08BD00003002002018B1817842 +:106C5000012938D101E010207047018842F6011265 +:106C6000881A914231D018DC42F60102A1EB0200F1 +:106C700091422AD00CDC41B3B1F5C05F25D06FF44E +:106C8000C050081821D0A0F57060FF381BD11CE05F +:106C900001281AD002280AD117E0B0F5807F14D05D +:106CA00008DC012811D002280FD003280DD0FF28BE +:106CB00009D10AE0B0F5817F07D0A0F580700338D4 +:106CC00003D0012801D0002070470F2070470A2808 +:106CD0001FD008DC0A2818D2DFE800F0191B1F1F9C +:106CE000171F231D1F21102815D008DC0B2812D0D8 +:106CF0000C2810D00D2816D00F2806D10DE0112831 +:106D00000BD084280BD087280FD003207047002099 +:106D1000704705207047072070470F2070470420F8 +:106D20007047062070470C20704743F202007047FE +:106D300038B50C46050041D06946FFF797F90028A1 +:106D400019D19DF80010607861F302006070694607 +:106D5000681CFFF78BF900280DD19DF800106078B2 +:106D600061F3C5006070A978C1F34101012903D026 +:106D7000022905D0072038BD217821F0200102E04A +:106D8000217841F020012170410704D0A978C90879 +:106D900061F386106070607810F0380F07D0A97822 +:106DA000090961F3C710607010F0380F02D16078E4 +:106DB000400603D5207840F040002070002038BD08 +:106DC00070B504460020088015466068FFF7B0FFE4 +:106DD000002816D12089A189884211D8606880785E +:106DE000C0070AD0B1F5007F0AD840F20120B1FBFC +:106DF000F0F200FB1210288007E0B1F5FF7F01D907 +:106E00000C2070BD01F201212980002070BD10B559 +:106E10000478137864F3000313700478640864F34F +:106E2000410313700478A40864F382031370047898 +:106E3000E40864F3C30313700478240964F30413AF +:106E400013700478640964F34513137000788009A3 +:106E500060F38613137031B10878C10701D1800740 +:106E600001D5012000E0002060F3C713137010BDAE +:106E70004278530702D002F0070306E012F0380F01 +:106E800002D0C2F3C20300E001234A7863F3020296 +:106E90004A70407810F0380F02D0C0F3C20005E00D +:106EA000430702D000F0070000E0012060F3C502B4 +:106EB0004A7070472DE9F04F95B00D00824613D00F +:106EC00012220021284607F0E2FA4FF6FF7B05AABE +:106ED0000121584607F0A3F80024264637464FF410 +:106EE00020586FF4205972E0102015B0BDE8F08FE3 +:106EF0009DF81E0001280AD1BDF81C1041450BD099 +:106F000011EB09000AD001280CD002280CD0042C67 +:106F10000ED0052C0FD10DE0012400E00224BDF8B5 +:106F20001A6008E0032406E00424BDF81A7002E0A9 +:106F3000052400E00624BDF81A10514547D12C74F1 +:106F4000BEB34FF0000810AA4FF0070ACDE9028245 +:106F5000CDE900A80DF13C091023CDF81090424670 +:106F60003146584607F02BF908BBBDF83C002A46CD +:106F7000C0B210A90EF045FDC8B9AE81CFB1CDE9C0 +:106F800000A80DF1080C0AAE40468CE8410213231C +:106F900000223946584607F012F940B9BDF83C00C6 +:106FA000F11CC01EC0B22A1D0EF02BFD10B1032033 +:106FB0009BE70AE0BDF82900E881062C05D19DF881 +:106FC0001E00A872BDF81C00288100208DE705A8CE +:106FD00007F031F800288BD0FFF779FE85E72DE91F +:106FE000F0471C46DDE90978DDF8209015460E00D3 +:106FF000824600D1FFDF0CB1208818B1D5B1112035 +:10700000BDE8F087022D01D0012100E0002106F14A +:10701000140005F0CDFEA8F8000002463B462946C4 +:10702000504603F092F9C9F8000008B9A41C3C606E +:107030000020E5E71320E3E7F0B41446DDE904524D +:107040008DB1002314B1022C09D101E0012306E027 +:107050000D7CEE0703D025F0010501230D742146B8 +:10706000F0BC04F050BA1A80F0BC70472DE9FE4F16 +:1070700091461A881C468A468046FAB102AB4946B8 +:1070800003F063F9050019D04046A61C27880DF0CF +:1070900050F83246072629463B4600960CF05FFC26 +:1070A00020882346CDE900504A4651464046FFF726 +:1070B000C3FF002020800120BDE8FE8F0020FBE7F9 +:1070C0002DE9F04786B082460EA8904690E8B000C1 +:1070D000894604AA05A903A88DE807001E462A468A +:1070E00021465046FFF77BFF039901B1012139701A +:1070F000002818D1FA4904F1140204AB086003987F +:1071000005998DE8070042464946504606F003FAC5 +:10711000A8B1092811D2DFE800F005080510100A0F +:107120000C0C0E00002006B06AE71120FBE70720D8 +:10713000F9E70820F7E70D20F5E70320F3E7BDF8AE +:1071400010100398CDE9000133462A4621465046E7 +:10715000FFF772FFE6E72DE9F04389B01646DDE957 +:1071600010870D4681461C461422002103A807F013 +:107170008EF9012002218DF810108DF80C008DF889 +:107180001170ADF8146064B1A278D20709D08DF8FF +:107190001600E088ADF81A00A088ADF81800A068C5 +:1071A000079008A80095CDE90110424603A948467A +:1071B0006B68FFF785FF09B0BDE8F083F0B58BB0D1 +:1071C00000240646069407940727089405A8099406 +:1071D000019400970294CDE903400D461023224606 +:1071E000304606F0ECFF78B90AA806A9019400978A +:1071F0000294CDE90310BDF8143000222946304630 +:1072000006F07BFD002801D0FFF761FD0BB0F0BD5B +:1072100006F00CBC2DE9FC410C468046002602F02D +:10722000E5F9054620780D287ED2DFE800F0BC079E +:1072300013B325BD49496383AF959B00A8480068F7 +:1072400020B1417841F010014170ADE0404602F0BC +:10725000FDF9A9E0042140460CF028FE070000D10A +:10726000FFDF07F11401404605F037FDA5BB1321F0 +:107270004046FDF7CFFB97E0042140460CF016FE98 +:10728000070000D1FFDFE088ADF800000020B881E2 +:107290009DF80000010704D5C00602D5A088B8817A +:1072A00005E09DF8010040067ED5A088F88105B96B +:1072B000FFDF22462946404601F0ACFC022673E07F +:1072C000E188ADF800109DF8011009060FD50728D8 +:1072D00003D006280AD00AE024E0042140460CF03E +:1072E000E5FD060000D1FFDFA088F0810226CDB9C0 +:1072F000FFDF17E0042140460CF0D8FD070000D165 +:10730000FFDF07F1140006F0C8FB90F0010F02D177 +:10731000E079000648D5387C022640F00200387437 +:1073200005B9FFDF224600E03DE02946404601F076 +:1073300071FC39E0042140460CF0B8FD017C002DC1 +:1073400001F00206C1F340016171017C21F00201EC +:107350000174E7D1FFDFE5E702260121404602F094 +:10736000A7F921E0042140460CF0A0FD0546606825 +:1073700000902089ADF8040001226946404602F0E1 +:10738000B8F9287C20F0020028740DE0002DC9D146 +:10739000FFDFC7E7022600214046FBF799F8002DE2 +:1073A000C0D1FFDFBEE7FFDF3046BDE8FC813EB560 +:1073B0000C0009D001466B4601AA002006F084FFAC +:1073C00020B1FFF784FC3EBD10203EBD0020208090 +:1073D000A0709DF8050002A900F00700FEF762FE0C +:1073E00050B99DF8080020709DF8050002A9C0F36F +:1073F000C200FEF757FE08B103203EBD9DF808000D +:1074000060709DF80500C109A07861F30410A070B8 +:107410009DF80510890961F3C300A0709DF8041060 +:10742000890601D5022100E0012161F342009DF8A7 +:10743000001061F30000A07000203EBD70B514463E +:1074400006460D4651EA040005D075B10846F9F725 +:1074500044FF78B901E0072070BD2946304606F0A8 +:107460009AFF10B1BDE8704031E454B12046F9F7FD +:1074700034FF08B1102070BD21463046BDE8704091 +:1074800095E7002070BD2DE9FC5F0C46904605464F +:10749000002701780822007A3E46B2EB111F7DD109 +:1074A00004F10A0100910A31821E4FF0020A04F130 +:1074B000080B0191092A72D2DFE802F0EDE005F530 +:1074C00028287BAACE00688804210CF0EFFC060077 +:1074D00000D1FFDFB08928B152270726C3E00000A2 +:1074E0009402002051271026002C7DD06888A080AF +:1074F0000120A071A88900220099FFF79FFF0028B2 +:1075000073D1A8892081288AE081D1E0B5F8129052 +:10751000072824D1E87B000621D5512709F1140062 +:1075200086B2002CE1D0A88900220099FFF786FFDF +:1075300000285AD16888A08084F806A0A8892081F4 +:107540000120A073288A2082A4F81290A88A0090B3 +:1075500068884B46A969019A01F038FBA8E05027DA +:1075600009F1120086B2002C3ED0A88900225946AB +:10757000FFF764FF002838D16888A080A889E080E0 +:10758000287A072813D002202073288AE081E87B1C +:10759000C0096073A4F81090A88A01E085E082E039 +:1075A000009068884B4604F11202A969D4E70120D3 +:1075B000EAE7B5F81290512709F1140086B2002CC1 +:1075C00066D0688804210CF071FC83466888A0802E +:1075D000A88900220099FFF731FF00286ED184F8B6 +:1075E00006A0A889208101E052E067E00420A07392 +:1075F000288A2082A4F81290A88A009068884B46B6 +:10760000A969019A01F0E2FAA989ABF80E104FE0DE +:107610006888FBF717FF0746688804210CF046FCD2 +:10762000064607B9FFDF06B9FFDF687BC00702D057 +:107630005127142601E0502712264CB36888A080F9 +:10764000502F06D084F806A0287B594601F0CEFAC8 +:107650002EE0287BA11DF9E7FE49A88949898142CE +:1076600005D1542706269CB16888A08020E05327C6 +:107670000BE06888A080A889E08019E06888042170 +:107680000CF014FC00B9FFDF55270826002CF0D1C0 +:10769000A8F8006011E056270726002CF8D068886B +:1076A000A080002013E0FFDF02E0012808D0FFDF08 +:1076B000A8F800600CB1278066800020BDE8FC9F20 +:1076C00057270726002CE3D06888A080687AA0712D +:1076D000EEE7401D20F0030009B14143091D01EB15 +:1076E0004000704713B5DB4A00201071009848B184 +:1076F000002468460CF0F7F9002C02D1D64A009914 +:1077000011601CBD01240020F4E770B50D4614463D +:10771000064686B05C220021284606F0B8FE04B971 +:10772000FFDFA0786874A2782188284601F089FAE2 +:107730000020A881E881228805F11401304605F077 +:10774000B0FA6A460121304606F069FC1AE000BF33 +:107750009DF80300000715D5BDF806103046FFF769 +:107760002DFD9DF80300BDF8061040F010008DF8C7 +:107770000300BDF80300ADF81400FF233046059A5E +:1077800006F0D1FD684606F056FC0028E0D006B0B1 +:1077900070BD10B50C4601F1140005F0BAFA0146AF +:1077A000627C2046BDE8104001F080BA30B5044646 +:1077B000A84891B04FF6FF75C18905AA284606F082 +:1077C0002EFC30E09DF81E00A0422AD001282AD1CC +:1077D000BDF81C00B0F5205F03D042F601018842DD +:1077E00021D1002002AB0AAA0CA9019083E807006E +:1077F00007200090BDF81A1010230022284606F03A +:10780000DEFC38B9BDF828000BAAC0B20CA90EF0F6 +:10781000F8F810B1032011B030BD9DF82E00A04241 +:1078200001D10020F7E705A806F005FC0028C9D023 +:107830000520F0E770B5054604210CF037FB040085 +:1078400000D1FFDF04F114010C46284605F045FA8B +:1078500021462846BDE8704005F046BA70B58AB0AA +:107860000C460646FBF7EEFD050014D028782228CA +:1078700027D30CB1A08890B101208DF80C00032013 +:107880008DF8100000208DF8110054B1A088ADF8DB +:107890001800206807E043F202000AB070BD09201A +:1078A000FBE7ADF818000590042130460CF0FEFA15 +:1078B000040000D1FFDF04F1140005F040FA0007D6 +:1078C00001D40820E9E701F091FE60B108A8022187 +:1078D0000094CDE9011095F8232003A93046636890 +:1078E000FFF7EEFBD9E71120D7E72DE9F04FB2F80B +:1078F00002A0834689B0154689465046FBF7A2FD93 +:107900000746042150460CF0D1FA0026044605969D +:107910004FF002080696ADF81C6007B9FFDF04B906 +:10792000FFDF4146504603F055FF50B907AA06A9AC +:1079300005A88DE807004246214650466368FFF7D8 +:107940004EFB444807AB0660DDE9051204F1140064 +:10795000CDF80090CDE90320CDE9013197F823203F +:10796000594650466B6805F033FA06000AD0022EDD +:1079700004D0032E14D0042E00D0FFDF09B030460F +:10798000BDE8F08FBDF81C000028F7D00599CDE9BF +:1079900000104246214650466368FFF74DFBEDE775 +:1079A000687840F008006870E8E710B50C46FFF70B +:1079B000BFF900280BD1607800F00701012905D13B +:1079C00010F0380F02D02078810601D5072010BDB5 +:1079D00040F0C8002070002010BD2DE9F04F99B094 +:1079E00004464FF000081B48ADF81C80ADF820801D +:1079F000ADF82480A0F80880ADF81480ADF81880A8 +:107A0000ADF82880ADF82C80007916460D46474623 +:107A1000012808D0022806D0032804D0042802D068 +:107A2000082019B0ACE72046F9F713FCF0BB284654 +:107A3000F9F70FFCD0BB6068F9F758FCB0BB606881 +:107A400068B160892189884202D8B1F5007F05D9E3 +:107A50000C20E6E7940200201800002080460EAAC1 +:107A600006A92846FFF7ACF90028DAD168688078C3 +:107A7000C0F34100022808D19DF8190010F0380F1A +:107A800003D02869F9F729FC80B905A92069FFF717 +:107A90004FF90028C5D1206950B1607880079DF862 +:107AA000150000F0380002D5F0B301E011E0D8BBBA +:107AB0009DF8140080060ED59DF8150010F0380FC3 +:107AC00003D06068F9F709FC18B96068F9F70EFC93 +:107AD00008B11020A5E70BA906A8FFF7C9F99DF882 +:107AE0002D000BA920F00700401C8DF82D006069C7 +:107AF000FFF75BFF002894D10AA9A069FFF718F9E6 +:107B000000288ED19DF8280080062BD4A06940B1B2 +:107B10009DF8290000F00701012923D110F0380F4A +:107B200020D0E06828B100E01CE00078D0B11C282B +:107B300018D20FAA611C2046FFF769F901213846C7 +:107B400061F30F2082468DF85210B94642F60300C9 +:107B50000F46ADF850000DF13F0218A928680DF04E +:107B600052FF08B107205CE79DF8600015A9CDF829 +:107B70000090C01CCDE9019100F0FF0B00230BF237 +:107B80000122514614A806F075F9E8BBBDF854006F +:107B90000C90FB482A8929690092CDE901106B8974 +:107BA000BDF838202868069906F064F9010077D1FD +:107BB00020784FF0020AC10601D4800616D58DF850 +:107BC000527042F60210ADF85000CDF80C9008A9A2 +:107BD00003AACDF800A0CDE90121002340F2032241 +:107BE00014A80B9906F046F9010059D1E4484D4616 +:107BF00008380089ADF83D000FA8CDE90290CDF816 +:107C00000490CDF8109000E00CE04FF007095B46BF +:107C10000022CDF80090BDF854104FF6FF7006F02A +:107C20006CF810B1FFF753F8FBE69DF83C00000636 +:107C300024D52946012060F30F218DF852704FF4AE +:107C400024500395ADF8500062789DF80C00002395 +:107C500062F300008DF80C006278CDF800A05208A5 +:107C600062F341008DF80C0003AACDE9012540F232 +:107C7000032214A806F0FEF8010011D1606880B359 +:107C80002069A0B905A906A8FFF7F2F86078800777 +:107C900007D49DF8150020F038008DF8150006E097 +:107CA00077E09DF8140040F040008DF814008DF846 +:107CB000527042F60110ADF85000208940F20121C7 +:107CC000B0FBF1F201FB1202606809ABCDF8008055 +:107CD000CDE90103002314A8059906F0CBF80100B3 +:107CE00057D12078C00728D00395A06950B90AA9B8 +:107CF00006A8FFF7BDF89DF8290020F00700401CFA +:107D00008DF829009DF8280007A940F040008DF863 +:107D100028008DF8527042F60310ADF8500003AA07 +:107D2000CDF800A0CDE90121002340F2032214A8E0 +:107D30000A9906F09FF801002BD1E06868B3294644 +:107D4000012060F30F218DF8527042F60410ADF857 +:107D50005000E068002302788DF8582040788DF8B4 +:107D60005900E06816AA4088ADF85A00E06800792A +:107D70008DF85C00E068C088ADF85D00CDF800903B +:107D8000CDE901254FF4027214A806F073F8010042 +:107D900003D00C9800F0B6FF43E679480321083879 +:107DA000017156B100893080BDF824007080BDF8A3 +:107DB0002000B080BDF81C00F080002031E670B5D6 +:107DC00001258AB016460B46012802D0022816D19A +:107DD00004E08DF80E504FF4205003E08DF80E5063 +:107DE00042F60100ADF80C005BB10024601C60F3AA +:107DF0000F2404AA08A918460DF005FE18B10720A3 +:107E00004BE5102049E504A99DF820205C48CDE908 +:107E10000021801E02900023214603A802F20122C5 +:107E200006F028F810B1FEF752FF36E5544808383E +:107E30000EB1C1883180057100202EE5F0B593B0F8 +:107E4000044601268DF83E6041F601000F46ADF86C +:107E50003C0011AA0FA93046FFF7B1FF002837D127 +:107E60002000474C4FF00005A4F1080432D01C223A +:107E7000002102A806F00BFB9DF808008DF83E607B +:107E800040F020008DF8080042F60520ADF83C00D7 +:107E900004200797ADF82C00ADF8300039480A905F +:107EA0000EA80D900E950FA80990ADF82E506A46B9 +:107EB00009A902A8FFF791FD002809D1BDF800002B +:107EC0006081BDF80400A081401CE0812571002084 +:107ED00013B0F0BD6581A581BDF84400F4E72DE93C +:107EE000F74F2749A0B00024083917940A79A14612 +:107EF000012A04D0022A02D0082023B040E5CA8813 +:107F0000824201D00620F8E721988A46824201D1B8 +:107F10000720F2E70120214660F30F21ADF8480069 +:107F20004FF6FF788DF86E000691ADF84A8042F664 +:107F3000020B8DF872401CA9ADF86CB0ADF8704022 +:107F40001391ADF8508012A806F0A4F800252E4633 +:107F50002F460DAB072212A9404606F09EF898B1B5 +:107F60000A2861D1B5B3AEB3ADF86450ADF8666020 +:107F70009DF85E008DF8144019AC012868D06FE0C0 +:107F80009C020020266102009DF83A001FB30128E0 +:107F900059D1BDF8381059451FD118A809A9019425 +:107FA0000294CDE9031007200090BDF8361010238D +:107FB0000022404606F003F9B0BBBDF8600004287B +:107FC00001D006284AD1BDF82410219881423AD127 +:107FD0000F2092E73AE0012835D1BDF83800B0F51E +:107FE000205F03D042F6010188422CD1BAF8060086 +:107FF000BDF83610884201D1012700E0002705B105 +:108000009EB1219881421ED118A809AA0194029418 +:10801000CDE90320072000900D46102300224046A2 +:1080200006F0CDF800B902E02DE04E460BE0BDF8B9 +:108030006000022801D0102810D1C0B217AA09A9E7 +:108040000DF0DFFC50B9BDF8369082E7052054E70B +:1080500005A917A8221D0DF0D6FC08B103204CE796 +:108060009DF814000023001DC2B28DF81420229840 +:108070000092CDE901401BA8069905F0FBFE10B95E +:1080800002228AF80420FEF722FE36E710B50B46DE +:10809000401E88B084B205AA00211846FEF7B7FE3C +:1080A00000200DF1080C06AA05A901908CE8070034 +:1080B000072000900123002221464FF6FF7005F0B3 +:1080C0001CFE0446BDF81800012800D0FFDF204642 +:1080D000FEF7FDFD08B010BDF0B5FF4F044687B0B8 +:1080E00038790E46032804D0042802D0082007B0AF +:1080F000F0BD04AA03A92046FEF762FE0500F6D1F2 +:1081000060688078C0F3410002280AD19DF80D0014 +:1081100010F0380F05D02069F9F7DFF808B110200A +:10812000E5E7208905AA21698DE807006389BDF884 +:1081300010202068039905F09DFE10B1FEF7C7FDE1 +:10814000D5E716B1BDF814003080042038712846F8 +:10815000CDE7F8B50C0006460BD001464FF6FF758B +:1081600000236A46284606F0AFF820B1FEF7AFFDBF +:10817000F8BD1020F8BD69462046FEF7D9FD00285D +:10818000F8D1A078314600F001032846009A06F0A5 +:10819000CAF8EBE730B587B0144600220DF1080CA1 +:1081A00005AD01928CE82C00072200920A46014698 +:1081B00023884FF6FF7005F0A0FDBDF81410218054 +:1081C000FEF785FD07B030BD70B50D4604210BF0FC +:1081D0006DFE040000D1FFDF294604F11400BDE864 +:1081E000704004F0A5BD70B50D4604210BF05EFE95 +:1081F000040000D1FFDF294604F11400BDE87040FF +:1082000004F0B9BD70B50D4604210BF04FFE04001B +:1082100000D1FFDF294604F11400BDE8704004F0EE +:10822000D1BD70B5054604210BF040FE040000D11D +:10823000FFDF214628462368BDE870400122FEF793 +:1082400015BF70B5064604210BF030FE040000D1C6 +:10825000FFDF04F1140004F05CFD401D20F0030575 +:1082600011E0011D00880022431821463046FEF728 +:10827000FDFE00280BD0607CABB2684382B2A068E0 +:10828000011D0BF0D0FCA06841880029E9D170BD28 +:1082900070B5054604210BF009FE040000D1FFDF94 +:1082A000214628466368BDE870400222FEF7DEBE24 +:1082B00070B50E46054601F099F9040000D1FFDFC4 +:1082C0000120207266726580207820F00F00001D6A +:1082D00020F0F00040302070BDE8704001F089B916 +:1082E00010B50446012900D0FFDF2046BDE810404C +:1082F0000121FAF7EDB82DE9F04F97B04FF0000AE1 +:108300000C008346ADF814A0D04619D0E06830B117 +:10831000A068A8B10188ADF81410A0F800A05846D4 +:10832000FBF790F8070043F2020961D03878222861 +:108330005CD3042158460BF0B9FD050005D103E0DC +:10834000102017B0BDE8F08FFFDF05F1140004F036 +:10835000E0FC401D20F00306A078012803D002288D +:1083600001D00720EDE7218807AA584605F057FEFF +:1083700030BB07A805F05FFE10BB07A805F05BFE49 +:1083800048B99DF82600012805D1BDF82400A0F5C4 +:108390002451023902D04FF45050D2E7E068B0B116 +:1083A000CDE902A00720009005AACDF804A0049210 +:1083B000A2882188BDF81430584605F09EFC10B103 +:1083C000FEF785FCBDE7A168BDF8140008809DF8A4 +:1083D0001F00C00602D543F20140B2E70B9838B146 +:1083E000A1780078012905D080071AD40820A8E7D1 +:1083F0004846A6E7C007F9D002208DF83C00A868DF +:108400004FF00009A0B1697C4288714391420FD9B5 +:108410008AB2B3B2011D0BF0BCFB8046A0F800A0ED +:1084200006E003208DF83C00D5F800804FF00109EC +:108430009DF8200010F0380F00D1FFDF9DF82000DC +:108440002649C0F3C200084497F8231010F8010C25 +:10845000884201D90F2074E72088ADF8400014A9A4 +:108460000095CDE90191434607220FA95846FEF732 +:1084700027FE002891D19DF8500050B9A07801281E +:1084800007D1687CB3B2704382B2A868011D0BF0BB +:1084900094FB002055E770B5064615460C46084685 +:1084A000FEF7D4FB002805D12A4621463046BDE818 +:1084B000704084E470BD12E570B51E4614460D0090 +:1084C0000ED06CB1616859B160B10349C98881426D +:1084D00008D0072070BD000094020020296102002E +:1084E0001020F7E72068FEF7B1FB0028F2D13246F2 +:1084F00021462846BDE87040FFF76FBA70B51546B3 +:108500000C0006D038B1FE490989814203D007200A +:10851000E0E71020DEE72068FEF798FB0028D9D1BD +:1085200029462046BDE87040D6E570B5064686B0BF +:108530000D4614461046F8F7B2FED0BB6068F8F757 +:10854000D5FEB0BBA6F57F40FF3803D03046FAF722 +:1085500079FF80B128466946FEF7ACFC00280CD1B3 +:108560009DF810100F2008293DD2DFE801F0080621 +:108570000606060A0A0843F2020006B0AAE703202C +:10858000FBE79DF80210012908D1BDF80010B1F5F4 +:10859000C05FF2D06FF4C052D142EED09DF8061009 +:1085A00001290DD1BDF80410A1F52851062907D2E3 +:1085B00000E029E0DFE801F0030304030303DCE744 +:1085C0009DF80A1001290FD1BDF80810B1F5245FFC +:1085D000D3D0A1F60211B1F50051CED00129CCD0F3 +:1085E000022901D1C9E7FFDF606878B9002305AA35 +:1085F0002946304605F068FE10B1FEF768FBBCE77F +:108600009DF81400800601D41020B6E76188224648 +:1086100028466368FFF7BEFDAFE72DE9F0438146CA +:1086200087B0884614461046F8F739FE18B1102076 +:1086300007B0BDE8F083002306AA4146484605F08E +:1086400043FE10B1FEF743FBF2E79DF81800C006A9 +:1086500002D543F20140EBE70025072705A8019565 +:1086600000970295CDE9035062884FF6FF734146AB +:10867000484605F0A4FD060013D16068F8F70FFE28 +:1086800060B960680195CDE9025000970495238890 +:1086900062884146484605F092FD0646BDF8140042 +:1086A00020803046CEE739B1954B0A889B899A42A3 +:1086B00002D843F2030070471DE610B586B0904C17 +:1086C0000423ADF81430638943B1A4898C4201D2EC +:1086D000914205D943F2030006B010BD0620FBE726 +:1086E000ADF81010002100910191ADF80030022189 +:1086F0008DF8021005A9029104A90391ADF812208A +:108700006946FFF7F8FDE7E72DE9FC4781460D468E +:108710000846F8F79EFD88BB4846FAF793FE5FEAE5 +:1087200000080AD098F80000222829D304214846DE +:108730000BF0BCFB070005D103E043F20200BDE8EB +:10874000FC87FFDF07F1140004F0F9FA06462878E9 +:10875000012803D0022804D00720F0E7B0070FD586 +:1087600002E016F01C0F0BD0A8792C1DC00709D011 +:10877000E08838B1A068F8F76CFD18B11020DEE78A +:108780000820DCE721882A780720B1F5847F35D0DE +:108790001EDC40F20315A1F20313A94226D00EDC21 +:1087A000B1F5807FCBD003DCF9B1012926D1C6E732 +:1087B000A1F58073013BC2D0012B1FD113E0012B27 +:1087C000BDD0022B1AD0032BB9D0042B16D112E046 +:1087D000A1F20912082A11D2DFE802F00B040410FA +:1087E00010101004ABE7022AA9D007E0012AA6D096 +:1087F00004E0320700E0F206002AA0DACDB200F071 +:10880000F5FE50B198F82300CDE90005FA8923461A +:1088100039464846FEF79FFC91E711208FE72DE986 +:10882000F04F8BB01F4615460C4683460026FAF7DC +:1088300009FE28B10078222805D208200BB081E576 +:1088400043F20200FAE7B80801D00720F6E7032F49 +:1088500000D100274FF6FF79CCB1022D71D320460D +:10886000F8F744FD30B904EB0508A8F10100F8F76A +:108870003DFD08B11020E1E7AD1E38F8028CAAB228 +:108880002146484605F081FE40455AD1ADB21C490B +:10889000B80702D58889401C00E001201FFA80F843 +:1088A000F80701D08F8900E04F4605AA4146584697 +:1088B00005F0B5FB4FF0070A4FF00009FCB1204668 +:1088C00008E0408810283CD8361D304486B2AE42BD +:1088D00037D2A01902884245F3D352E09DF8170021 +:1088E00002074ED57CB304EB0608361DB8F80230FB +:1088F000B6B2102B25D89A19AA4222D802E040E03D +:1089000094020020B8F8002091421AD1C0061BD56D +:10891000CDE900A90DF1080C0AAAA11948468CE876 +:108920000700B8F800100022584605F0E6F910B12B +:10893000FEF7CDF982E7B8F80200BDF828108842AA +:1089400002D00B207AE704E0B8F80200304486B287 +:1089500006E0C00604D55846FEF730FC00288AD150 +:108960009DF81700BDF81A1020F010008DF81700C0 +:10897000BDF81700ADF80000FF235846009A05F037 +:10898000D2FC05A805F057FB18B9BDF81A10B9427A +:10899000A4D9042158460BF089FA040000D1FFDF66 +:1089A000A2895AB1CDE900A94D4600232146584677 +:1089B000FEF7D1FB0028BDD1A5813FE700203DE7B0 +:1089C0002DE9FF4F8BB01E4617000D464FF00004F7 +:1089D00012D0B00802D007200FB0B3E4032E00D1AC +:1089E00000265DB10846F8F778FC28B93888691E7A +:1089F0000844F8F772FC08B11020EDE7C74AB00749 +:108A000001D5D18900E00121F0074FF6FF7802D0AF +:108A1000D089401E00E0404686B206AA0B9805F0B9 +:108A2000FEFA4FF000094FF0070B0DF1140A38E081 +:108A30009DF81B00000734D5CDF80490CDF800B0A8 +:108A4000CDF80890CDE9039A434600220B9805F033 +:108A5000B6FB60BB05B3BDF814103A882144281951 +:108A6000091D8A4230D3BDF81E2020F8022BBDF824 +:108A7000142020F8022BCDE900B9CDE90290CDF801 +:108A800010A0BDF81E10BDF8143000220B9805F0A0 +:108A900096FB08B103209FE7BDF814002044001D99 +:108AA00084B206A805F0C7FA20B10A2806D0FEF75E +:108AB0000EF991E7BDF81E10B142B9D934B17DB1BC +:108AC0003888A11C884203D20C2085E7052083E763 +:108AD00022462946404605F058FD014628190180E6 +:108AE000A41C3C80002077E710B50446F8F7D7FBBC +:108AF00008B1102010BD8948C0892080002010BD19 +:108B0000F0B58BB00D4606461422002103A805F0EF +:108B1000BEFC01208DF80C008DF8100000208DF8AF +:108B20001100ADF814503046FAF78CFC48B10078CB +:108B3000222812D3042130460BF0B8F9040005D1E5 +:108B400003E043F202000BB0F0BDFFDF04F11400BC +:108B5000074604F0F4F8800601D40820F3E7207CEF +:108B6000022140F00100207409A80094CDE9011011 +:108B7000072203A930466368FEF7A2FA20B1217CE0 +:108B800021F001012174DEE729463046F9F791FC16 +:108B900008A9384604F0C2F800B1FFDFBDF8204054 +:108BA000172C01D2172000E02046A84201D92C46FC +:108BB00002E0172C00D2172421463046FFF713FBA2 +:108BC00021463046F9F799F90020BCE7F8B51C4674 +:108BD00015460E46069F0BF09AFA2346FF1DBCB2BF +:108BE00031462A4600940AF086FEF8BD70B50C4660 +:108BF00005460E220021204605F049FC0020208079 +:108C00002DB1012D01D0FFDF64E4062000E0052036 +:108C1000A0715FE410B548800878134620F00F007B +:108C2000001D20F0F00080300C4608701422194618 +:108C300004F1080005F001FC00F0DBFC374804609B +:108C400010BD2DE9F047DFF8D890491D064621F008 +:108C5000030117460C46D9F800000AF062FF050030 +:108C600000D1FFDF4FF000083560A5F800802146F5 +:108C7000D9F800000AF055FF050000D1FFDF75604C +:108C8000A5F800807FB104FB07F1091D0BD0D9F8CE +:108C900000000AF046FF040000D1FFDFB460C4F812 +:108CA0000080BDE8F087C6F80880FAE72DE9F041BA +:108CB0001746491D21F00302194D06460168144666 +:108CC00028680AF059FF2246716828680AF054FFA4 +:108CD0003FB104FB07F2121D03D0B16828680AF007 +:108CE0004BFF04200BF08AF8044604200BF08EF8AA +:108CF000201A012804D12868BDE8F0410AF006BF17 +:108D0000BDE8F08110B50C4605F058F900B1FFDF61 +:108D10002046BDE81040FDF7DABF000094020020B5 +:108D20001800002038B50C468288817B19B1418932 +:108D3000914200D90A462280C188121D90B26A462B +:108D40000AF0B2F8BDF80000032800D30320C1B236 +:108D5000208801F020F838BD38B50C468288817B28 +:108D600019B10189914200D90A462280C188121D99 +:108D700090B26A460AF098F8BDF80000022800D3C5 +:108D80000220C1B2208801F006F8401CC0B238BDF4 +:108D90002DE9FF5F82468B46F74814460BF103022C +:108DA000D0E90110CDE9021022F0030201A84FF42E +:108DB000907101920AF097FEF04E002C02D1F0491A +:108DC000019A8A60019901440191B57F05F101057D +:108DD00004D1E8B20CF098FD00B1FFDF019800EB80 +:108DE0000510C01C20F0030101915CB9707AB27AC1 +:108DF0001044C2B200200870B08C80B204F03DFF75 +:108E000000B1FFDF0198716A08440190214601A872 +:108E100000F084FF80460198C01C20F00300019000 +:108E2000B37AF27A717A04B100200AF052FF019904 +:108E300008440190214601A800F0B8FFCF48002760 +:108E40003D4690F801900CE0284600F04AFF0646A7 +:108E500081788088F9F7E8F871786D1C00FB01775C +:108E6000EDB24D45F0D10198C01C20F003000190F7 +:108E700004B100203946F9F7E2F8019900270844C7 +:108E80000190BE483D4690F801900CE0284600F065 +:108E900028FF0646C1788088FEF71BFC71786D1CA0 +:108EA00000FB0177EDB24D45F0D10198C01C20F0D8 +:108EB0000300019004B100203946FEF713FC01992C +:108EC0004FF0000908440190AC484D4647780EE049 +:108ED000284600F006FF0646807B30B106F1080008 +:108EE00002F09CF9727800FB02996D1CEDB2BD4254 +:108EF000EED10198C01C20F00300019004B10020C5 +:108F00009F494A78494602F08DF901990844019039 +:108F1000214601A800F0B8FE0198C01D20F007000E +:108F20000190DAF80010814204D3A0EB0B01B1F5F7 +:108F3000803F04DB4FF00408CAF8000004E0CAF8E0 +:108F40000000B8F1000F03D0404604B0BDE8F09F28 +:108F500084BB8C490020019A0EF044FEFBF714FA02 +:108F6000864C207F0090607F012825D0002328B305 +:108F70000022824800211030F8F73AFA00B1FFDFF2 +:108F80007E49E07F2031FEF759FF00B1FFDF7B48CB +:108F90004FF4F6720021443005F079FA7748042145 +:108FA000443080F8E91180F8EA11062180F8EB11CD +:108FB000032101710020C8E70123D8E702AAD8E7FE +:108FC00070B56E4C06464434207804EB4015E078CA +:108FD000083598B9A01990F8E80100280FD0A078BA +:108FE0000F2800D3FFDF20220021284605F04FFA8A +:108FF000687866F3020068700120E070284670BD52 +:109000002DE9F04105460C460027007805219046E1 +:109010003E46B1EB101F00D0FFDF287A50B1012887 +:109020000ED0FFDFA8F800600CB12780668000201A +:10903000BDE8F0810127092674B16888A08008E0A6 +:109040000227142644B16888A0802869E060A88AB5 +:109050002082287B2072E5E7A8F80060E7E730B5BA +:10906000464C012000212070617020726072032242 +:10907000A272E07261772177217321740521218327 +:109080001F216183607440A161610A21A177E077AB +:1090900039483B4DB0F801102184C07884F8220093 +:1090A0004FF4B06060626868C11C21F00301814226 +:1090B00000D0FFDF6868606030BD30B5304C1568A7 +:1090C000636810339D4202D20420136030BD2B4BE5 +:1090D0005D785A6802EB0512107051700320D08041 +:1090E000172090800120D0709070002090735878E5 +:1090F000401C5870606810306060002030BD70B552 +:1091000006461E480024457807E0204600F0E9FDA9 +:109110000178B14204D0641CE4B2AC42F5D1002025 +:1091200070BDF7B5074608780C4610B3FFF7E7FFA8 +:109130000546A7F12006202F06D0052E19D2DFE81C +:1091400006F00F383815270000F0D6FD0DB169780C +:1091500000E00021401AA17880B20844FF2808D816 +:10916000A07830B1A088022831D202E060881728A8 +:109170002DD20720FEBD000030610200B0030020A8 +:109180001C000020000000206E52463578000000D0 +:10919000207AE0B161881729EBD3A1881729E8D399 +:1091A000A1790029E5D0E1790029E2D0402804D94D +:1091B000DFE7242F0BD1207A48B161884FF6FB708E +:1091C000814202D8A188814201D90420D2E765B941 +:1091D000207802AA0121FFF770FF0028CAD1207869 +:1091E000FFF78DFF050000D1FFDF052E18D2DFE865 +:1091F00006F0030B0E081100A0786870A088E880C4 +:109200000FE06088A8800CE0A078A87009E0A07842 +:10921000E87006E054F8020FA8606068E86000E0BB +:10922000FFDF0020A6E71A2835D00DDC132832D244 +:10923000DFE800F01B31203131272723252D313184 +:1092400029313131312F0F00302802D003DC1E28A4 +:1092500021D1072070473A3809281CD2DFE800F0F6 +:10926000151B0F1B1B1B1B1B07000020704743F225 +:109270000400704743F202007047042070470D203D +:1092800070470F2070470820704711207047132047 +:109290007047062070470320704710B5007800F033 +:1092A000010009F0F3FDBDE81040BCE710B50078FF +:1092B00000F0010009F0F3FDBDE81040B3E70EB582 +:1092C000017801F001018DF80010417801F00101F1 +:1092D0008DF801100178C1F340018DF8021041783A +:1092E000C1F340018DF80310017889088DF804104E +:1092F000417889088DF8051081788DF80610C178BD +:109300008DF8071000798DF80800684608F0FDFD1B +:10931000FFF789FF0EBD2DE9FC5FDFF8F883FE4CF7 +:1093200000264FF490771FE0012000F082FD01201D +:10933000FFF746FE05463946D8F808000AF0F1FB6B +:10934000686000B9FFDF686808F0AAFCB0B1284681 +:10935000FAF75EFB284600F072FD28B93A466968C4 +:10936000D8F808000AF008FC94F9E9010428DBDACF +:1093700002200AF043FD07460025AAE03A46696844 +:10938000D8F808000AF0F8FBF2E7B8F802104046F7 +:10939000491C89B2A8F80210B94201D300214180CA +:1093A0000221B8F802000AF081FD002866D0B8F862 +:1093B0000200694609F0CFFCFFF735FF00B1FFDF7F +:1093C0009DF80000019078B1B8F802000AF0B1FEF3 +:1093D0005FEA000900D1FFDF48460AF020F918B122 +:1093E000B8F8020002F0E4F9B8F802000AF08FFEC3 +:1093F0005FEA000900D1FFDF48460AF008F9E8BB40 +:109400000321B8F802000AF051FD5FEA000B4BD1CE +:10941000FFDF49E0DBF8100010B10078FF284DD0E5 +:10942000022000F006FD0220FFF7CAFD82464846F2 +:109430000AF0F9F9CAF8040000B9FFDFDAF804000D +:109440000AF0C1FA002100900170B8F802105046ED +:10945000AAF8021002F0B2F848460AF0B6FA00B9CB +:10946000FFDF019800B10126504600F0E8FC18B972 +:109470009AF80100000705D5009800E027E0CBF836 +:10948000100011E0DBF8101039B10878401C10F022 +:10949000FF00087008D1FFDF06E0002211464846B1 +:1094A00000F0F0FB00B9FFDF94F9EA01022805DBC8 +:1094B000B8F8020002F049F80028ABD194F9E901AC +:1094C000042804DB48460AF0E4FA00B101266D1CCA +:1094D000EDB2BD4204D294F9EA010228BFF655AFBD +:1094E000002E7FF41DAFBDE8FC5F032000F0A1BC9F +:1094F00010B5884CE06008682061AFF2E510F9F71C +:10950000E4FC607010BD844800214438017081483B +:10951000017082494160704770B505464FF0805038 +:109520000C46D0F8A410491C05D1D0F8A810C943A6 +:109530000904090C0BD050F8A01F01F0010129709B +:10954000416821608068A080287830B970BD06210C +:1095500020460DF0CCFF01202870607940F0C0005B +:10956000607170BD70B54FF080540D46D4F8801016 +:10957000491C0BD1D4F88410491C07D1D4F88810A9 +:10958000491C03D1D4F88C10491C0CD0D4F880109D +:109590000160D4F884104160D4F888108160D4F858 +:1095A0008C10C16002E010210DF0A1FFD4F89000F2 +:1095B000401C0BD1D4F89400401C07D1D4F898007B +:1095C000401C03D1D4F89C00401C09D054F8900FE3 +:1095D000286060686860A068A860E068E86070BDA6 +:1095E0002846BDE8704010210DF081BF4A4800793F +:1095F000E6E470B5484CE07830B3207804EB4010D6 +:10960000407A00F00700204490F9E801002800DCCF +:10961000FFDF2078002504EB4010407A00F00700BF +:10962000011991F8E801401E81F8E8012078401CFA +:10963000C0B220700F2800D12570A078401CA07007 +:109640000DF0D4FDE57070BDFFDF70BD3EB5054681 +:1096500003210AF02BFC044628460AF058FD054673 +:1096600004B9FFDF206918B10078FF2800D1FFDFBF +:1096700001AA6946284600F005FB60B9FFDF0AE051 +:10968000002202A9284600F0FDFA00B9FFDF9DF88C +:10969000080000B1FFDF9DF80000411E8DF80010AA +:1096A000EED220690199884201D1002020613EBD9F +:1096B00070B50546A0F57F400C46FF3800D1FFDFAE +:1096C000012C01D0FFDF70BDFFF790FF040000D137 +:1096D000FFDF207820F00F00401D20F0F000503018 +:1096E000207065800020207201202073BDE870404A +:1096F0007FE72DE9F04116460D460746FFF776FF56 +:10970000040000D1FFDF207820F00F00401D20F082 +:10971000F00005E01C000020F403002048140020A5 +:109720005030207067800120207228682061A8884E +:10973000A0822673BDE8F0415BE77FB5FFF7DFFC51 +:10974000040000D1FFDF02A92046FFF7EBFA05462F +:1097500003A92046FFF700FB8DF800508DF80100AB +:10976000BDF80800001DADF80200BDF80C00001D9A +:10977000ADF80400E088ADF80600684609F070FB1B +:10978000002800D0FFDF7FBD2DE9F05FFC4E814651 +:10979000307810B10820BDE8F09F4846F7F77FFD0C +:1097A00008B11020F7E7F74C207808B9FFF757FC0D +:1097B000A17A607A4D460844C4B200F09DFAA042F6 +:1097C00007D2201AC1B22A460020FFF776FC0028F3 +:1097D000E1D17168EB48C91C002721F003017160D9 +:1097E000B3463E463D46BA463C4690F801800AE004 +:1097F000204600F076FA4178807B0E4410FB01553C +:10980000641CE4B27F1C4445F2D10AEB870000EBF4 +:10981000C600DC4E00EB85005C46F17A012200EBCD +:109820008100DBF80410451829464846FFF7B0FAD6 +:10983000070012D00020FFF762FC05000BD005F1F5 +:109840001300616820F00300884200D0FFDF7078C9 +:10985000401E7070656038469DE7002229464846E4 +:10986000FFF796FA00B1FFDFD9F8000060604FF60D +:10987000FF7060800120207000208CE72DE9F0410E +:109880000446BF4817460D46007810B10820BDE8D1 +:10989000F0810846F7F7DDFC08B11020F7E7B94E74 +:1098A000307808B9FFF7DBFB601E1E2807D8012CB3 +:1098B00023D12878FE2820D8B0770020E7E7A4F14C +:1098C00020001F2805D8E0B23A462946BDE8F041FD +:1098D00027E4A4F140001F2805D829462046BDE80A +:1098E000F04100F0D4BAA4F1A0001F2805D8294601 +:1098F0002046BDE8F04100F006BB0720C7E72DE990 +:10990000F05F81460F460846F7F7C9FC48B948465C +:10991000F7F7E3FC28B909F1030020F003014945FA +:1099200001D0102037E797484FF0000B4430817882 +:1099300069B14178804600EB411408343E883A46CC +:109940000021204600F089FA050004D027E0A7F89E +:1099500000B005201FE7B9F1000F24D03888B042CD +:1099600001D90C251FE0607800F00700824600F066 +:1099700060FA08EB0A063A4696F8E8014946401CA8 +:1099800086F8E801204600F068FA054696F8E801F6 +:10999000401E86F8E801032000F04BFA2DB10C2D93 +:1099A00001D0A7F800B02846F5E6754F5046BAF149 +:1099B000010F25D002280DD0BAF1030F35D0FFDFFB +:1099C00098F801104046491CC9B288F801100F29C7 +:1099D00037D038E0606828B16078000702D460882A +:1099E000FFF734FE98F8EA014446012802D178785E +:1099F000F9F78AFA94F9EA010428E1DBFFDFDFE7EF +:109A0000616821B14FF49072B8680AF0B5F898F81F +:109A1000E9014446032802D17878F9F775FA94F9F8 +:109A2000E9010428CCDBFFDFCAE76078C00602D575 +:109A30006088FFF70BFE98F9EB010628C0DBFFDF1B +:109A4000BEE780F801B08178491E88F8021096F8C8 +:109A5000E801401C86F8E801A5E770B50C4605460C +:109A6000F7F7F7FB18B92046F7F719FC08B11020F3 +:109A700070BD28460BF07FFF207008B1002070BD3C +:109A8000042070BD70B505460BF08EFFC4B22846A9 +:109A9000F7F723FC08B1102070BD35B128782C7081 +:109AA00018B1A04201D0072070BD2046FDF77EFE10 +:109AB000052805D10BF07BFF012801D0002070BDE7 +:109AC0000F2070BD70B5044615460E460846F7F7E0 +:109AD000C0FB18B92846F7F7E2FB08B1102070BDAB +:109AE000022C03D0102C01D0092070BD2A4631462B +:109AF00020460BF086FF0028F7D0052070BD70B51A +:109B000014460D460646F7F7A4FB38B92846F7F782 +:109B1000C6FB18B92046F7F7E0FB08B1102070BD6E +:109B20002246294630460BF06EFF0028F7D007206A +:109B300070BD3EB50446F7F7B2FB08B110203EBD3C +:109B4000684608F053F9FFF76EFB0028F7D19DF83F +:109B500006002070BDF808006080BDF80A00A080F3 +:109B600000203EBD70B505460C460846F7F7B5FB2C +:109B700020B95CB12068F7F792FB28B1102070BDC6 +:109B80001C000020B0030020A08828B121462846F0 +:109B9000BDE87040FDF762BE0920F0E770B50546EC +:109BA0000C460846F7F755FBA0BB681E1E280ED8CA +:109BB000032D01D90720E2E705B9FFDFFE4800EBDE +:109BC000850050F8041C2046BDE870400847A5F108 +:109BD00020001F2805D821462846BDE87040FAF726 +:109BE00042BBA5F160001F2805D821462846BDE8E4 +:109BF0007040F8F7DABCF02D0DD0F12D15D0BF2D47 +:109C0000D8D1A078218800F0010001F08DFB98B137 +:109C10000020B4E703E0A068F7F71BFB08B11020B1 +:109C2000ADE7204609F081F902E0207809F0A0F9BB +:109C3000BDE87040FFF7F7BA0820A0E770B504460A +:109C40000D460846F7F72BFB30B9601E1E280FD8CB +:109C50002846F7F7FEFA08B1102090E7012C03D050 +:109C6000022C01D0032C01D1062088E7072086E7CB +:109C7000A4F120001F28F9D829462046BDE87040ED +:109C8000FAF762BB09F092BC38B50446CB48007BBA +:109C900000F00105F9B904F01DFC0DB1226800E0E7 +:109CA0000022C7484178C06807F06DFDC4481030F5 +:109CB000C0788DF8000010B1012802D004E0012026 +:109CC00000E000208DF80000684608F0FFF8BA4870 +:109CD000243808F0B5FE002D02D02068283020601E +:109CE00038BD30B5B54D04466878A04200D8FFDFD6 +:109CF000686800EB041030BD70B5B04800252C46F4 +:109D0000467807E02046FFF7ECFF4078641C2844C3 +:109D1000C5B2E4B2B442F5D1284630E72DE9F041AE +:109D20000C4607464FF0000800F01FF90646FF28D2 +:109D300001D94FF013083868C01C20F003023A60C4 +:109D400054EA080421D19D48F3B2072128300DF0D0 +:109D5000DBFD09E0072C10D2DFE804F00604080858 +:109D60000A040600974804E0974802E0974800E09C +:109D700097480DF0E9FD054600E0FFDFA54200D061 +:109D8000FFDF641CE4B2072CE4D3386800EB061054 +:109D9000386040467BE5021D5143452900D24521EC +:109DA0000844C01CB0FBF2F0C0B270472DE9FC5F64 +:109DB000064682484FF000088B464746444690F8D6 +:109DC000019022E02046FFF78CFF050000D1FFDF65 +:109DD000687869463844C7B22846FEF7A3FF824632 +:109DE00001A92846FEF7B8FF0346BDF80400524615 +:109DF000001D81B2BDF80000001D80B20AF0D4F849 +:109E00006A78641C00FB0288E4B24C45DAD1306801 +:109E1000C01C20F003003060BBF1000F00D0002018 +:109E2000424639460AF0CEF8316808443060BDE851 +:109E3000FC9F6249443108710020C87070475F4937 +:109E40004431CA782AB10A7801EB421108318142C3 +:109E500001D001207047002070472DE9F0410646EF +:109E60000078154600F00F0400201080601E0F4699 +:109E7000052800D3FFDF50482A46183800EB84003D +:109E8000394650F8043C3046BDE8F04118472DE90A +:109E9000F0414A4E0C46402806D0412823D04228A3 +:109EA0002BD0432806D123E0A07861780D18E17803 +:109EB000814201D90720EAE42078012801D9132042 +:109EC000E5E4FF2D08D80BF009FF07460DF046F931 +:109ED000381A801EA84201DA1220D8E42068B06047 +:109EE000207930730DE0BDE8F041084600F078B805 +:109EF00008780228DED8307703E008780228D9D81D +:109F000070770020C3E4F8B500242C4DA02805D0BC +:109F1000A12815D0A22806D00720F8BD087800F0A7 +:109F20000100E8771FE00E4669463046FDF73DFD2B +:109F30000028F2D130882884B07885F8220012E019 +:109F400008680921F82801D3820701D00846F8BD26 +:109F50006A7C02F00302012A04D16A8BD73293B2E1 +:109F60008342F3D868622046F8BD2DE9F047DFF858 +:109F70004C900026344699F8090099F80A2099F87F +:109F800001700244D5B299F80B20104400F0FF088C +:109F900008E02046FFF7A5FE817B407811FB0066B4 +:109FA000641CE4B2BC42F4D199F8091099F80A0093 +:109FB0002944294441440DE054610200B0030020CB +:109FC0001C0000206741000045B30000DD2F0000A9 +:109FD000FB56010000B1012008443044BDE8F08781 +:109FE00038B50446407800F00300012803D0022869 +:109FF0000BD0072038BD606858B1F7F777F9D0B9B2 +:10A000006068F7F76AF920B915E06068F7F721F999 +:10A0100088B969462046FCF729F80028EAD160781B +:10A0200000F00300022808D19DF8000028B1606804 +:10A03000F7F753F908B1102038BD6189F8290DD818 +:10A04000208988420AD8607800F003020A48012A71 +:10A0500006D1D731426A89B28A4201D2092038BD7D +:10A0600094E80E0000F1100585E80E000AB9002101 +:10A070000183002038BD0000B00300202DE9F0412D +:10A08000074614468846084601F08AFD064608EB56 +:10A0900088001C22796802EBC0000D18688C58B14A +:10A0A0004146384601F08BFD014678680078C200D1 +:10A0B000082305F120000CE0E88CA8B141463846A1 +:10A0C00001F084FD0146786808234078C20005F15C +:10A0D000240009F0A8FD38B1062121726681D0E97B +:10A0E0000010C4E9031009E0287809280BD00520E6 +:10A0F000207266816868E060002028702046BDE814 +:10A10000F04101F02EBD072020726681F4E72DE9B1 +:10A11000F04116460D460746406801EB85011C22BA +:10A1200002EBC1014418204601F072FD40B100214C +:10A13000708865F30F2160F31F4106200DF0BEFC0F +:10A1400009202070324629463846BDE8F04195E79F +:10A150002DE9F0410E46074600241C21F07816E058 +:10A1600004EB8403726801EBC303D25C6AB1FFF7AE +:10A170003DFA050000D1FFDF6F802A4621463046B8 +:10A18000FFF7C5FF0120BDE8F081641CE4B2A042E6 +:10A19000E6D80020F7E770B5064600241C21C078F9 +:10A1A0000AE000BF04EB8403726801EBC303D51817 +:10A1B0002A782AB1641CE4B2A042F3D8402070BDD2 +:10A1C00028220021284604F062F9706880892881DD +:10A1D000204670BD70B5034600201C25DC780CE0DD +:10A1E00000EB80065A6805EBC6063244167816B1B5 +:10A1F000128A8A4204D0401CC0B28442F0D8402067 +:10A2000070BDF0B5044600201C26E5780EE000BFC6 +:10A2100000EB8007636806EBC7073B441F788F425B +:10A2200002D15B78934204D0401CC0B28542EFD883 +:10A230004020F0BD0078032801D0002070470120A5 +:10A2400070470078022801D0002070470120704735 +:10A250000078072801D000207047012070472DE9C1 +:10A26000F041064688461078F1781546884200D3BA +:10A27000FFDF2C781C27641CF078E4B2A04201D8E0 +:10A28000201AC4B204EB8401706807EBC1010844D2 +:10A29000017821B14146884708B12C7073E72878CE +:10A2A000A042E8D1402028706DE770B514460B88B5 +:10A2B0000122A240134207D113430B8001230A223B +:10A2C000011D09F07AFC047070BD2DE9FF4F81B0CB +:10A2D0000878DDE90E7B9A4691460E4640072CD45D +:10A2E000019809F026FF040000D1FFDF07F1040800 +:10A2F00020461FFA88F109F065F8050000D1FFDF5C +:10A30000204629466A4609F0B0FA0098A0F8037082 +:10A31000A0F805A0284609F056FB017869F306016C +:10A320006BF3C711017020461FFA88F109F08DF810 +:10A3300000B9FFDF019807F094F906EB0900017FEF +:10A34000491C017705B0BDE8F08F2DE9F84F0E46A6 +:10A350009A4691460746032109F0A8FD0446008D60 +:10A36000DFF8B885002518B198F80000B0421ED17A +:10A37000384609F0DEFE070000D1FFDF09F10401D5 +:10A38000384689B209F01EF8050010D03846294633 +:10A390006A4609F06AFA009800210A460180817035 +:10A3A00007F01CFA0098C01DCAF8000021E098F8D8 +:10A3B0000000B04216D104F1260734F8341F012002 +:10A3C00000FA06F911EA090F00D0FFDF2088012307 +:10A3D00040EA090020800A22391D384609F008FCAD +:10A3E000067006E0324604F1340104F12600FFF75E +:10A3F0005CFF0A2188F800102846BDE8F88FFEB5FA +:10A4000015460C46064602AB0C220621FFF79DFFBF +:10A41000002827D00299607812220A70801C4870A8 +:10A4200008224A80A07002982988052381806988C3 +:10A43000C180A9880181E988418100250C20CDE9EE +:10A440000005062221463046FFF73FFF294600223D +:10A4500066F31F41F02310460DF086FA6078801CE9 +:10A4600060700120FEBDFEB514460D46062206466C +:10A4700002AB1146FFF769FF002812D0029B1320A0 +:10A4800000211870A8785870022058809C800620FF +:10A49000CDE900010246052329463046FFF715FFA6 +:10A4A0000120FEBD2DE9FE430C46804644E002AB90 +:10A4B0000E2207214046FFF748FF002841D0606880 +:10A4C0001C2267788678BF1C06EB860102EBC1016F +:10A4D000451802981421017047700A214180698A49 +:10A4E0000181E98A4181A9888180A98981813046D9 +:10A4F00001F056FB029905230722C8806F700420E3 +:10A50000287000250E20CDE9000521464046FFF7C2 +:10A51000DCFE294666F30F2168F31F41F023002279 +:10A5200006200DF021FA6078FD49801C6070626899 +:10A530002046921CFFF793FE606880784028B6D1D1 +:10A540000120BDE8FE83FEB50D46064638E002ABAD +:10A550000E2207213046FFF7F8FE002835D0686844 +:10A560001C23C17801EB810203EBC202841802981C +:10A5700015220270627842700A224280A2894281CA +:10A58000A2888281084601F00BFB01460298818077 +:10A59000618AC180E18A0181A088B8B10020207061 +:10A5A00000210E20CDE9000105230722294630466F +:10A5B000FFF78BFE6A68DB492846D21CFFF74FFE87 +:10A5C0006868C0784028C2D10120FEBD0620E6E7B9 +:10A5D0002DE9FE430C46814644E0204601F002FB93 +:10A5E000D0B302AB082207214846FFF7AEFE002891 +:10A5F000A7D060681C2265780679AD1C06EB860141 +:10A6000002EBC10147180298B7F8108006210170CB +:10A61000457004214180304601F0C2FA014602989B +:10A6200005230722C180A0F804807D7008203870BF +:10A630000025CDE9000521464846FFF746FE29469C +:10A6400066F30F2169F31F41F023002206200DF06D +:10A650008BF96078801C60706268B3492046121DD7 +:10A66000FFF7FDFD606801794029B6D1012068E758 +:10A670002DE9F34F83B00D4691E0284601F0B2FA80 +:10A6800000287DD068681C2290F806A00AEB8A0199 +:10A6900002EBC10144185146284601F097FAA1780F +:10A6A000CB0069684978CA00014604F1240009F02A +:10A6B000D6FA07468188E08B4FF00009091A8EB25E +:10A6C00008B1C84607E04FF00108504601F053FAC0 +:10A6D00008B9B61CB6B2208BB04200D80646B346C5 +:10A6E00002AB324607210398FFF72FFE060007D082 +:10A6F000B8F1000F0BD0504601F03DFA10B106E062 +:10A7000000201FE60299B8884FF0020908800196E0 +:10A71000E28B3968ABEB09001FFA80F80A44039812 +:10A720004E46009209F005FDDDE90021F61D434685 +:10A73000009609F014F9E08B404480B2E083B988B8 +:10A74000884201D1012600E00026CDE900B6238A27 +:10A75000072229460398FFF7B8FD504601F00BFA8F +:10A7600010B9E089401EE08156B1A078401CA0706D +:10A770006868E978427811FB02F1CAB2012300E06F +:10A7800007E081690E3009F018FA80F800A0002077 +:10A79000E0836A6865492846921DFFF760FD686896 +:10A7A000817940297FF469AF0120CBE570B5064679 +:10A7B00048680D4614468179402910D104EB840184 +:10A7C0001C2202EBC101084401F043FA002806D024 +:10A7D0006868294684713046BDE8704048E770BD1E +:10A7E000FEB50C460746002645E0204601F0FAF982 +:10A7F000D8B360681C22417901EB810102EBC101F1 +:10A800004518688900B9FFDF02AB082207213846E6 +:10A81000FFF79BFD002833D00299607816220A705A +:10A82000801C4870042048806068407901F0B8F9C5 +:10A83000014602980523072281806989C18008208A +:10A84000CDE9000621463846FFF73FFD6078801CC1 +:10A850006070A88969890844B0F5803F00D3FFDFA4 +:10A86000A88969890844A8816E81626830492046B8 +:10A87000521DFFF7F4FC606841794029B5D10120F1 +:10A88000FEBD30B5438C458BC3F3C704002345B1EF +:10A89000838B641EED1AC38A6D1E1D4495FBF3F372 +:10A8A000E4B22CB1008918B1A04200D8204603447C +:10A8B0004FF6FF70834200D3034613800C7030BD07 +:10A8C0002DE9FC41074616460D46486802EB860115 +:10A8D0001C2202EBC10144186A4601A92046FFF779 +:10A8E000D0FFA089618901448AB2BDF8001091426D +:10A8F00012D0081A00D5002060816868407940288D +:10A900000AD1204601F09BF9002805D06868294645 +:10A9100046713846FFF764FFBDE8FC813000002037 +:10A9200035A2000043A2000051A2000053BC000069 +:10A930003FBC00002DE9FE4F0F468146154650886A +:10A94000032109F0B3FA0190B9F8020001F01BF9F4 +:10A9500082460146019801F045F9002824D001986B +:10A960001C2241680AEB8A0002EBC0000C1820464A +:10A9700001F04EF9002817D1B9F80000E18A8842A9 +:10A980000ED8A18961B1B8420ED100265146019876 +:10A9900001F015F9218C01EB0008608B30B114E057 +:10A9A000504601F0E8F8A0B3BDE8FE8F504601F034 +:10A9B000E2F808B1678308E0022FF5D3B9F8040084 +:10A9C0006083618A884224D80226B81B87B2B8F80F +:10A9D0000400A28B801A002814DD874200DA384672 +:10A9E0001FFA80FB688869680291D8F800100A4451 +:10A9F000009209F08CFBF61D009A5B4602990096C6 +:10AA000008F079FFA08B384480B2A083618B884224 +:10AA100007D96888019903B05246BDE8F04F01F0AC +:10AA200035B91FD14FF009002872B9F802006881CA +:10AA3000D8E90010C5E90410608BA881284601F010 +:10AA400090F85146019801F0BAF8014601980823A0 +:10AA500040680078C20004F1200009F0E4F800200A +:10AA6000A0836083504601F086F810B9A089401E8B +:10AA7000A0816888019903B00AF0FF02BDE8F04F99 +:10AA80001EE72DE9F041064615460F461C461846BE +:10AA9000F6F7DFFB18B92068F6F701FC10B11020BB +:10AAA000BDE8F0817168688C0978B0EBC10F01D303 +:10AAB0001320F5E73946304601F081F80146706809 +:10AAC00008230078C20005F1200009F076F8D4E9E7 +:10AAD0000012C0E900120020E2E710B5044603218D +:10AAE00009F0E4F90146007800F00300022805D0DF +:10AAF0002046BDE8104001F1140280E48A8A204615 +:10AB0000BDE81040AFE470B50446032109F0CEF96A +:10AB1000054601462046FFF75BFD002816D0294672 +:10AB20002046FFF75DFE002810D029462046FFF79B +:10AB30000AFD00280AD029462046FFF7B3FC00286A +:10AB400004D029462046BDE8704091E570BD2DE94E +:10AB5000F0410C4680461EE0E178427811FB02F19C +:10AB6000CAB2816901230E3009F05DF80778606888 +:10AB70001C22C179491EC17107EB8701606802EB95 +:10AB8000C10146183946204601F02CF818B130466C +:10AB900001F037F820B16068C1790029DCD17FE786 +:10ABA000FEF724FD050000D1FFDF0A202872384699 +:10ABB00000F0F6FF68813946204601F007F80146AB +:10ABC000606808234078C20006F1240009F02BF8E1 +:10ABD000D0E90010C5E90310A5F80280284600F06E +:10ABE000C0FFB07800B9FFDFB078401EB07057E703 +:10ABF00070B50C460546032109F058F90146406836 +:10AC0000C2792244C2712846BDE870409FE72DE911 +:10AC1000FE4F8246507814460F464FF00008002839 +:10AC20004FD0012807D0022822D0FFDF2068B8606B +:10AC30006068F860B8E602AB0E2208215046FFF7C4 +:10AC400084FB0028F2D00298152105230170217899 +:10AC500041700A214180C0F80480C0F80880A0F843 +:10AC60000C80628882810E20CDE90008082221E054 +:10AC7000A678304600F094FF054606EB86012C22AC +:10AC8000786802EBC1010822465A02AB11465046D1 +:10AC9000FFF75BFB0028C9D00298072101702178DB +:10ACA00041700421418008218580C680CDE90018CB +:10ACB00005230A4639465046FFF707FB87F8088008 +:10ACC00072E6A678022516B1022E13D0FFDF2A1DE8 +:10ACD000914602AB08215046FFF737FB0028A5D06C +:10ACE00002980121022E01702178417045808680F2 +:10ACF00002D005E00625EAE7A188C180E18801814C +:10AD0000CDE900980523082239465046D4E710B50E +:10AD10000446032109F0CAF8014600F10802204662 +:10AD2000BDE8104073E72DE9F04F0F4605468DB0A2 +:10AD300014465088032109F0B9F84FF000088DF847 +:10AD400014800646ADF81680042F7BD36A78002A5B +:10AD500078D028784FF6FF794FF01C0A132834D0AA +:10AD600008DC012871D006284AD007286ED01228A6 +:10AD70000ED106E014286AD0152869D0162807D10C +:10AD8000AAE10C2F04D1307800F00301022907D08A +:10AD9000CDF80880CDF80C8068788DF808004CE07C +:10ADA00040F0080030706878B07001208DF8140011 +:10ADB000A888ADF81800E888ADF81A002889ADF821 +:10ADC0001C006889ADF81E0011E1B078904239D1BD +:10ADD0003078010736D5062F34D120F008003070C6 +:10ADE0006088414660F31F4100200CF067FE02209E +:10ADF0008DF81400ADF81890A888ADF81A00F6E0A8 +:10AE0000082F1FD1A888EF88814600F0BCFE80463D +:10AE10000146304600F0E6FE18B1404600F0ABFEB9 +:10AE2000B8B1FC48D0E90010CDE902106878ADF85F +:10AE30000C908DF80800ADF80E70608802AA3146BB +:10AE4000FFF7E5FE0DB0BDE8F08FB6E01EE041E093 +:10AE5000ECE0716808EB88002C2202EBC000085A75 +:10AE6000B842EFD1EB4802AAD0E90210CDE90210B6 +:10AE700068788DF8080008F0FF058DF80A506088A2 +:10AE80003146FFF7C4FE224629461FE0082FD9D1DC +:10AE9000B5F80480E88800F076FE074601463046A3 +:10AEA00000F0A0FE0028CDD007EB870271680AEB06 +:10AEB000C2000844028A4245C4D101780829C1D1A0 +:10AEC000407869788842BDD1F9B222463046FFF712 +:10AED0001EF9B7E70E2F7FF45BAFE9886F898B46C9 +:10AEE000B5F808903046FFF775F9ABF140014029FD +:10AEF00001D309204AE0B9F1170F01D3172F01D26E +:10AF00000B2043E040280ED000EB800271680AEB72 +:10AF1000C20008440178012903D140786978884249 +:10AF200090D00A2032E03046FFF735F9014640283C +:10AF30002BD001EB810372680AEBC30002EB00081F +:10AF4000012288F800206A7888F801207068AA88B1 +:10AF50004089B84200D93846AD8903232372A282C2 +:10AF6000E7812082A4F80C906582084600F018FE64 +:10AF70006081A8F81490A8F81870A8F80E50A8F8E6 +:10AF800010B0204600F0EDFD5CE7042005212172A1 +:10AF9000A4F80A80E081012121739E49D1E90421AE +:10AFA000CDE9022169788DF80810ADF80A006088B3 +:10AFB00002AA3146FFF72BFEE3E7062F89D3B078CC +:10AFC00090421AD13078010717D520F00800307070 +:10AFD0006088414660F31F4100200CF06FFD0220A5 +:10AFE0008DF81400A888ADF81800ADF81A906088A4 +:10AFF000224605A9F9F7E3F824E704213046FFF7D4 +:10B0000000F905464028BFD0022083030090224665 +:10B010002946304600F003FE4146608865F30F2163 +:10B0200060F31F4106200CF049FD0BE70E2FABD15A +:10B0300004213046FFF7E5F881464028A4D0414678 +:10B04000608869F30F2160F31F4106200CF036FD84 +:10B05000A8890B906889099070682F894089B84247 +:10B0600000D938468346B5F80680A8880A90484635 +:10B0700000F096FD60810B9818B1022000900B9BA8 +:10B0800024E0B8F1170F1ED3172F1CD30420207211 +:10B0900009986082E781A4F810B0A4F80C8009EB4D +:10B0A000890271680AEBC2000D18DDE90913A5F8E1 +:10B0B0001480A5F818B0E9812B82204600F051FDDC +:10B0C00006202870BEE601200B2300902246494648 +:10B0D000304600F0A4FDB5E6082F8DD1A988304692 +:10B0E000FFF778F80746402886D000F044FD002896 +:10B0F0009BD107EB870271680AEBC20008448046C7 +:10B1000000F086FD002890D1ED88B8F80E002844A4 +:10B11000B0F5803F05D360883A46314600F0B6FD71 +:10B1200090E6002DCED0A8F80E0060883A46314651 +:10B13000FFF73CFB08202072384600F031FD6081AB +:10B14000A5811EE72DE9F05F0C4601281FD09579F7 +:10B1500092F8048092F8056005EB85011F2202EB4E +:10B16000C10121F0030B08EB060111FB05F14FF6BD +:10B17000FF7202EAC10909F1030115FB0611264F0E +:10B1800021F0031ABB6840B101283ED125E0616877 +:10B19000E57891F800804E78DEE75946184608F0C9 +:10B1A000C0FC606000B9FFDF5A460021606803F010 +:10B1B0006EF9E5705146B86808F0B3FC6168486103 +:10B1C00000B9FFDF6068426902EB090181616068D4 +:10B1D00080F800806068467017E0606852464169F8 +:10B1E000184608F0C9FC5A466168B86808F0C4FC03 +:10B1F000032008F003FE0446032008F007FE201A8F +:10B20000012802D1B86808F081FC0BEB0A00BDE808 +:10B21000F09F000060610200300000200246002123 +:10B2200002208FE7F7B5FF4C0A20164620700098E1 +:10B2300060B100254FEA0D0008F055FC0021A17017 +:10B240006670002D01D10099A160FEBD012500208E +:10B25000F2E770B50C46154638220021204603F06F +:10B2600016F9012666700A22002104F11C0003F081 +:10B270000EF905B9FFDF297A207861F3010020700B +:10B28000A87900282DD02A4621460020FFF75AFF32 +:10B2900061684020E34A88706168C870616808711D +:10B2A000616848716168887161682888088161688F +:10B2B00068884881606886819078002811D061682C +:10B2C0000620087761682888C885616828884886CC +:10B2D00060680685606869889288018681864685EF +:10B2E000828570BDC878002802D00022012029E79D +:10B2F000704770B50546002165F31F4100200CF032 +:10B30000DDFB0321284608F0D1FD040000D1FFDF5A +:10B3100021462846FEF71CFF002804D0207840F084 +:10B3200010002070012070BD70B505460C4603204A +:10B3300008F056FD08B1002070BDBA4885708480C1 +:10B34000012070BD2DE9FF4180460E460F0CFEF72F +:10B350004DF9050007D06F800321384608F0A6FD9F +:10B36000040008D106E004B03846BDE8F0411321DE +:10B37000F9F750BBFFDF5FEA080005D0B8F1060F10 +:10B3800018D0FFDFBDE8FF8120782A4620F00800B2 +:10B3900020700020ADF8020002208DF800004FF66A +:10B3A000FF70ADF80400ADF8060069463846F8F7BE +:10B3B00006FFE7E7C6F3072101EB81021C23606863 +:10B3C00003EBC202805C042803D008280AD0FFDF08 +:10B3D000D8E7012000904FF440432A46204600F071 +:10B3E0001EFCCFE704B02A462046BDE8F041FEF738 +:10B3F0008EBE2DE9F05F05464089002790460C4639 +:10B400003E46824600F0BFFB8146287AC01E0828CF +:10B410006BD2DFE800F00D04192058363C47722744 +:10B420001026002C6CD0D5E90301C4E902015CE0D0 +:10B4300070271226002C63D00A2205F10C0104F1BA +:10B44000080002F0FAFF50E071270C26002C57D0BC +:10B45000E868A06049E0742710269CB3D5E9030191 +:10B46000C4E902016888032108F020FD8346FEF745 +:10B47000BDF802466888508049465846FEF7FEFDF2 +:10B4800033E075270A26ECB1A88920812DE07627C4 +:10B490001426BCB105F10C0004F1080307C883E8C9 +:10B4A000070022E07727102664B1D5E90301C4E93B +:10B4B00002016888032108F0F9FC01466888FFF75B +:10B4C00046FB12E01CE073270826CCB168880321F4 +:10B4D00008F0ECFC01460078C00606D56888FEF747 +:10B4E00037FE10B96888F8F777FAA8F800602CB131 +:10B4F0002780A4F806A066806888A080002086E6E1 +:10B50000A8F80060FAE72DE9FC410C461E461746F4 +:10B510008046032108F0CAFC05460A2C0AD2DFE85F +:10B5200004F005050505050509090907042303E0DD +:10B53000062301E0FFDF0023CDE9007622462946FD +:10B540004046FEF7C2FEBDE8FC81F8B50546A0F511 +:10B550007F40FF382BD0284608F0D9FD040000D1E9 +:10B56000FFDF204608F05FF9002821D001466A4637 +:10B57000204608F07AF900980321B0F805602846C3 +:10B5800008F094FC0446052E13D0304600F0FBFA78 +:10B5900005460146204600F025FB40B1606805EBFA +:10B5A00085013E2202EBC101405A002800D0012053 +:10B5B000F8BD007A0028FAD00020F8BDF8B504469E +:10B5C000408808F0A4FD050000D1FFDF6A46284648 +:10B5D000616800F0C4FA01460098091F8BB230F888 +:10B5E000032F0280428842800188994205D1042AB3 +:10B5F00008D0052A20D0062A16D022461946FFF781 +:10B6000099F9F8BD001D0E46054601462246304612 +:10B61000F6F739FF0828F4D1224629463046FCF7D0 +:10B6200064F9F8BD30000020636864880A46011D93 +:10B630002046FAF789F9F4E72246001DFFF773FB6D +:10B64000EFE770B50D460646032108F02FFC040015 +:10B6500004D02078000704D5112070BD43F2020009 +:10B6600070BD2A4621463046FEF7C9FE18B9286843 +:10B6700060616868A061207840F0080020700020B8 +:10B6800070BD70B50D460646032108F00FFC04009E +:10B6900004D02078000704D4082070BD43F20200D3 +:10B6A00070BD2A4621463046FEF7DDFE00B9A58270 +:10B6B000207820F008002070002070BD2DE9F04FA8 +:10B6C0000E4691B08046032108F0F0FB0446404648 +:10B6D00008F02FFD07460020079008900990ADF86C +:10B6E00030000A9002900390049004B9FFDF0DF13E +:10B6F0000809FFB9FFDF1DE038460BA9002207F05B +:10B7000055FF9DF82C0000F07F050A2D00D3FFDFC8 +:10B710006019017F491E01779DF82C00000609D5AC +:10B720002A460CA907A8FEF7C0FD19F80510491C08 +:10B7300009F80510761EF6B2DED204F13400F84D99 +:10B7400004F1260BDFF8DCA304F12A07069010E0D1 +:10B750005846069900F08CFA064628700A2800D34D +:10B76000FFDF5AF8261040468847E08CC05DB042A3 +:10B7700002D0208D0028EBD10A202870E94D4E46DA +:10B7800028350EE00CA907A800F072FA0446375DD0 +:10B7900055F8240000B9FFDF55F82420394640460B +:10B7A0009047BDF81E000028ECD111B0BDE8F08F25 +:10B7B00010B5032108F07AFB040000D1FFDF0A2254 +:10B7C000002104F11C0002F062FE207840F0040029 +:10B7D000207010BD10B50C46032108F067FB204413 +:10B7E000007F002800D0012010BD2DE9F84F8946C8 +:10B7F00015468246032108F059FB070004D028466D +:10B80000F5F727FD40B903E043F20200BDE8F88FE9 +:10B810004846F5F744FD08B11020F7E7786828B1ED +:10B8200069880089814201D90920EFE7B9F8000051 +:10B830001C2488B100F0A7F980460146384600F084 +:10B84000D1F988B108EB8800796804EBC000085C86 +:10B8500001280BD00820D9E73846FEF79CFC80462B +:10B86000402807D11320D1E70520CFE7FDF7BEFE22 +:10B8700006000BD008EB8800796804EBC0000C18B8 +:10B88000B9F8000020B1E88910B113E01120BDE73C +:10B890002888172802D36888172801D20720B5E71F +:10B8A000686838B12B1D224641463846FFF7E9F853 +:10B8B0000028ABD104F10C0269462046FEF7E1FFF7 +:10B8C000288860826888E082B9F8000030B10220E0 +:10B8D0002070E889A080E889A0B12BE003202070C7 +:10B8E000A889A08078688178402905D180F80280F5 +:10B8F00039465046FEF7D6FD404600F051F9A9F80A +:10B90000000021E07868218B4089884200D90846F0 +:10B910002083A6F802A004203072B9F800007081DC +:10B92000E0897082F181208B3082A08AB08130461C +:10B9300000F017F97868C178402905D180F80380B4 +:10B9400039465046FEF7FFFD00205FE770B50D4613 +:10B950000646032108F0AAFA04000ED0284600F09B +:10B9600012F905460146204600F03CF918B1284678 +:10B9700000F001F920B1052070BD43F2020070BD56 +:10B9800005EB85011C22606802EBC101084400F050 +:10B990003FF908B1082070BD2A462146304600F024 +:10B9A00075F9002070BD2DE9F0410C461746804620 +:10B9B000032108F07BFA0546204600F0E4F804462F +:10B9C00095B10146284600F00DF980B104EB8401E1 +:10B9D0001C22686802EBC1014618304600F018F9D5 +:10B9E00038B10820BDE8F08143F20200FAE70520F3 +:10B9F000F8E73B46324621462846FFF742F8002842 +:10BA0000F0D1E2B229464046FEF75AFF708C083862 +:10BA1000082803D242484078F7F776FA0020E1E799 +:10BA20002DE9F0410D4617468046032108F03EFA05 +:10BA30000446284600F0A7F8064624B13846F5F734 +:10BA400008FC38B902E043F20200CBE73868F5F7AA +:10BA500000FC08B11020C5E73146204600F0C2F8CE +:10BA600060B106EB86011C22606802EBC10145183B +:10BA7000284600F0CDF818B10820B3E70520B1E75B +:10BA8000B888A98A884201D90C20ABE76168E88CA4 +:10BA90004978B0EBC10F01D31320A3E7314620460C +:10BAA00000F094F80146606808234078C20005F170 +:10BAB000240008F082F8D7E90012C0E90012F2B2BF +:10BAC00021464046FEF772FE00208BE72DE9F04745 +:10BAD0000D461F4690468146032108F0E7F90446CB +:10BAE000284600F050F806463CB14DB13846F5F70F +:10BAF000F4FB50B11020BDE8F08743F20200FAE7F2 +:10BB0000606858B1A0F80C8027E03146204600F06C +:10BB100069F818B1304600F02EF828B10520EAE7A0 +:10BB2000300000207861020006EB86011C2260686C +:10BB300002EBC1014518284600F06AF808B1082058 +:10BB4000D9E7A5F80880F2B221464846FEF7B8FECC +:10BB50001FB1A8896989084438800020CBE707F025 +:10BB600084BE017821F00F01491C21F0F001103151 +:10BB70000170FDF73EBD20B94E48807808B1012024 +:10BB80007047002070474B498988884201D10020C6 +:10BB90007047402801D2402000E0403880B2704712 +:10BBA00010B50446402800D9FFDF2046FFF7E3FF29 +:10BBB00010B14048808810BD4034A0B210BD40682C +:10BBC00042690078484302EBC0007047C278406881 +:10BBD000037812FB03F24378406901FB032100EB79 +:10BBE000C1007047C2788A4209D9406801EB8101DF +:10BBF0001C2202EBC101405C08B10120704700200B +:10BC000070470078062801D901207047002070474E +:10BC10000078062801D00120704700207047F0B45A +:10BC200001EB81061C27446807EBC6063444049DDB +:10BC300005262670E3802571F0BCFEF71FBA10B50B +:10BC4000418911B1FFF7DDFF08B1002010BD0120CF +:10BC500010BD10B5C18C8278B1EBC20F04D9C18977 +:10BC600011B1FFF7CEFF08B1002010BD012010BDBB +:10BC700010B50C4601230A22011D07F0D4FF0078FD +:10BC80002188012282409143218010BDF0B402EB53 +:10BC900082051C264C6806EBC505072363554B68D7 +:10BCA0001C79402C03D11A71F0BCFEF791BCF0BC9A +:10BCB000704700003000002010B5EFF3108000F056 +:10BCC000010472B6FC484178491C41704078012853 +:10BCD00001D10BF0B3FA002C00D162B610BD70B5E3 +:10BCE000F54CA07848B90125A570FFF7E5FF0BF0EA +:10BCF000B6FA20B100200BF080FA002070BD4FF0A2 +:10BD00008040E570C0F80453F7E770B5EFF310809A +:10BD100000F0010572B6E84C607800B9FFDF60788A +:10BD2000401E6070607808B90BF08CFA002D00D1CD +:10BD300062B670BDE04810B5817821B10021C170B4 +:10BD40008170FFF7E2FF002010BD10B504460BF034 +:10BD500086FAD9498978084000D001202060002067 +:10BD600010BD10B5FFF7A8FF0BF079FA02220123EE +:10BD7000D149540728B1D1480260236103200872D9 +:10BD800002E00A72C4F804330020887110BD2DE966 +:10BD9000F84FDFF824934278817889F80420002650 +:10BDA00089F80510074689F806600078DFF810B3B7 +:10BDB000354620B1012811D0022811D0FFDF0BF049 +:10BDC00060FA4FF0804498B10BF062FAB0420FD1A4 +:10BDD00030460BF061FA0028FAD042E00126EEE787 +:10BDE000FFF76AFF58460168C907FCD00226E6E75C +:10BDF0000120E060C4F80451B2490E600107D1F897 +:10BE00004412B04AC1F3423124321160AD49343199 +:10BE100008604FF0020AC4F804A3A060AA480168B1 +:10BE2000C94341F3001101F10108016841F010011B +:10BE3000016001E019F0A8FFD4F804010028F9D04E +:10BE400030460BF029FA0028FAD0B8F1000F04D1DF +:10BE50009D48016821F010010160C4F808A3C4F8EE +:10BE6000045199F805004E4680B1387870B90BF04E +:10BE7000F6F980460BF006FC6FF00042B8F1000FB7 +:10BE800002D0C6E9032001E0C6E90302DBF80000A6 +:10BE9000C00701D00BF0DFF9387810B13572BDE87A +:10BEA000F88F4FF01808C4F808830127A7614FF4F2 +:10BEB0002070ADF8000000BFBDF80000411EADF8D5 +:10BEC0000010F9D2C4F80C51C4F810517A48C01DC2 +:10BED0000BF06CFA3570FFF744FF676179493079F0 +:10BEE00020310860C4F80483D9E770B5050000D19B +:10BEF000FFDF4FF080424FF0FF30C2F8080300210F +:10BF0000C2F80011C2F80411C2F80C11C2F81011E5 +:10BF1000694C61700BF0AFF910B10120A070607036 +:10BF200067480068C00701D00BF095F92846BDE8C6 +:10BF300070402CE76048007A002800D0012070474C +:10BF40002DE9F04F61484FF0000A85B0D0F800B0FD +:10BF5000D14657465D4A5E49083211608406D4F8DE +:10BF6000080110B14FF0010801E04FF000080BF09C +:10BF7000F0F978B1D4F8240100B101208246D4F858 +:10BF80001C0100B101208146D4F8200108B101272D +:10BF900000E00027D4F8000100B101200490D4F89B +:10BFA000040100B101200390D4F80C0100B101207C +:10BFB0000290D4F8100100B101203F4D0190287883 +:10BFC00000260090B8F1000F04D0C4F808610120E9 +:10BFD0000BF013F9BAF1000F04D0C4F82461092062 +:10BFE0000BF00BF9B9F1000F04D0C4F81C610A2062 +:10BFF0000BF003F927B1C4F820610B200BF0FDF81A +:10C000002D48C01D0BF0DAF900B1FFDFDFF8AC807E +:10C010000498012780B1C4F80873E87818B1EE706D +:10C0200000200BF0EAF8287A022805D103202872B4 +:10C030000221C8F800102761039808B1C4F8046110 +:10C04000029850B1C4F80C61287A032800D0FFDFB1 +:10C05000C8F800602F72FFF758FE019838B1C4F895 +:10C060001061287A012801D100F05CF8676100981E +:10C0700038B12E70287A012801D1FFF772FEFFF740 +:10C0800044FE0D48C01D0BF0AFF91049091DC1F861 +:10C0900000B005B0BDE8F08F074810B5C01D0BF02B +:10C0A0008DF90549B0B1012008704FF0E021C1F8C9 +:10C0B0000002BDE81040FFE544000020340C0040C1 +:10C0C0000C0400401805004010ED00E0100502408F +:10C0D00001000001087A012801D1FFF742FEBDE806 +:10C0E000104024480BF080B970B5224CE41FA078B2 +:10C0F00008B90BF0A7F801208507A861207A00266F +:10C10000032809D1D5F80C0120B900200BF0C4F8A0 +:10C110000028F7D1C5F80C6126724FF0FF30C5F842 +:10C12000080370BD70B5134CE41F6079F0B10128AD +:10C1300003D0A179401E814218DA0BF090F8054631 +:10C140000BF0A0FA6179012902D9A179491CA171EA +:10C150000DB1216900E0E168411A022902DA11F10A +:10C16000020F06DC0DB1206100E0E060BDE8704028 +:10C17000F7E570BD4B0000200F4A12680D498A4256 +:10C180000CD118470C4A12680A4B9A4206D101B5E5 +:10C190000BF04AFA0BF01DFDBDE8014007490968A4 +:10C1A0000958084706480749054A064B70470000EA +:10C1B00000000000BEBAFECA5C000020040000209F +:10C1C000C8130020C8130020F8B51D46DDE9064756 +:10C1D0000E000AD007F0ADFF2346FF1DBCB231466A +:10C1E0002A46009407F0BBFBF8BDD0192246194639 +:10C1F00002F023F92046F8BD70B50D460446102222 +:10C20000002102F044F9258117206081A07B40F0D5 +:10C210000A00A07370BD4FF6FF720A80014602202B +:10C220000BF04CBC704700897047827BD30701D16B +:10C23000920703D48089088000207047052070474A +:10C24000827B920700D581817047014600200988D2 +:10C2500041F6FE52114200D00120704700B503465E +:10C26000807BC00701D0052000BD59811846FFF72B +:10C27000ECFFC00703D0987B40F004009873987BD4 +:10C2800040F001009873002000BD827B520700D56A +:10C2900009B14089704717207047827B61F3C30260 +:10C2A000827370472DE9FC5F0E460446017896467E +:10C2B000012000FA01F14DF6FF5201EA020962681D +:10C2C0004FF6FF7B1188594502D10920BDE8FC9F3C +:10C2D000B9F1000F05D041F6FE55294201D00120E9 +:10C2E000F4E741EA090111801D0014D000232B70EE +:10C2F00094F800C0052103221F464FF0020ABCF14A +:10C300000E0F76D2DFE80CF0F909252F47646B7722 +:10C31000479193B4D1D80420D8E7616820898B7BFA +:10C320009B0767D517284AD30B89834247D389894E +:10C33000172901D3814242D185F800A0A5F8010058 +:10C340003280616888816068817B21F0020181739D +:10C35000C6E0042028702089A5F801006089A5F8AE +:10C3600003003180BCE0208A3188C01D1FFA80F8AC +:10C37000414524D3062028702089A5F80100608952 +:10C38000A5F80300A089A5F805000721208ACDE9BA +:10C390000001636941E00CF0FF00082810D008207C +:10C3A00028702089A5F801006089A5F80300318074 +:10C3B0006A1D694604F10C0009F025FB10B15EE02E +:10C3C0001020EDE730889DF800100844308087E0A9 +:10C3D0000A2028702089A5F80100328044E00C2052 +:10C3E00028702089A5F801006089A5F80300318034 +:10C3F0003AE082E064E02189338800EB41021FFAD1 +:10C4000082F843453BD3B8F1050F38D30E222A708A +:10C410000BEA4101CDE90010E36860882A467146C5 +:10C42000FFF7D2FEA6F800805AE04020287060890D +:10C430003188C01C1FFA80F8414520D32878714606 +:10C4400020F03F00123028702089A5F80100608993 +:10C45000CDE9000260882A46E368FFF7B5FEA6F83A +:10C460000080287840063BD461682089888037E0C6 +:10C47000A0893288401D1FFA80F8424501D2042766 +:10C480003DE0162028702089A5F801006089A5F8F4 +:10C490000300A089CDE9000160882A46714623691E +:10C4A000FFF792FEA6F80080DEE718202870207AB9 +:10C4B0006870A6F800A013E061680A88920401D4AD +:10C4C00005271CE0C9882289914201D0062716E081 +:10C4D0001E21297030806068018821F4005101809C +:10C4E000B9F1000F0BD061887823002202200BF0F5 +:10C4F0003BFA61682078887006E033800327606823 +:10C50000018821EA090101803846DFE62DE9FF4F65 +:10C5100085B01746129C0D001E461CD03078C1070E +:10C5200003D000F03F00192801D9012100E00021CB +:10C530002046FFF7AAFEA8420DD32088A0F57F4130 +:10C54000FF3908D03078410601D4000605D508200F +:10C5500009B0BDE8F08F0720FAE700208DF8000051 +:10C560008DF8010030786B1E00F03F0C0121A81EF1 +:10C570004FF0050A4FF002094FF0030B9AB2BCF1DD +:10C58000200F75D2DFE80CF08B10745E7468748C29 +:10C59000749C74B574BA74C874D474E1747474F10E +:10C5A00074EF74EE74ED748B052D78D18DF80090D6 +:10C5B000A0788DF804007088ADF8060030798DF809 +:10C5C0000100707800F03F000C2829D00ADCA0F1AF +:10C5D0000200092863D2DFE800F0126215621A62D5 +:10C5E0001D622000122824D004DC0E281BD0102845 +:10C5F000DBD11BE016281FD01828D6D11FE02078E9 +:10C60000800701E020784007002848DAEEE0207833 +:10C610000007F9E72078C006F6E720788006F3E700 +:10C6200020784006F0E720780006EDE72088C00576 +:10C63000EAE720884005E7E720880005E4E720884E +:10C64000C004E1E72078800729D5032D27D18DF894 +:10C6500000B0B6F8010081E0217849071FD5062D0A +:10C660001DD381B27078012803D0022817D102E0CF +:10C67000C9E0022000E0102004228DF8002072782A +:10C680008DF80420801CB1FBF0F2ADF8062092B2C8 +:10C6900042438A4203D10397ADF80890A6E079E0BF +:10C6A0002078000776D598B282088DF800A0ADF802 +:10C6B0000420B0EB820F6DD10297ADF8061095E023 +:10C6C0002178C90666D5022D64D381B206208DF883 +:10C6D0000000707802285DD3B1FBF0F28DF8040001 +:10C6E000ADF8062092B242438A4253D1ADF8089089 +:10C6F0007BE0207880064DD5072003E020784006B7 +:10C700007FD508208DF80000A088ADF80400ADF8B2 +:10C710000620ADF8081068E02078000671D50920E1 +:10C72000ADF804208DF80000ADF8061002975DE02A +:10C730002188C90565D5022D63D381B20A208DF801 +:10C740000000707804285CD3C6E72088400558D5DF +:10C75000012D56D10B208DF80000A088ADF8040003 +:10C7600044E021E026E016E0FFE72088000548D5F8 +:10C77000052D46D30C208DF80000A088ADF80400EC +:10C78000B6F803006D1FADF80850ADF80600ADF81F +:10C790000AA02AE035E02088C00432D5012D30D12E +:10C7A0000D208DF8000021E02088800429D4B6F8FF +:10C7B0000100E080A07B000723D5032D21D3307832 +:10C7C00000F03F001B2818D00F208DF800002088B3 +:10C7D00040F40050A4F80000B6F80100ADF80400E1 +:10C7E000ED1EADF80650ADF808B003976946059800 +:10C7F000F5F792FB050008D016E00E208DF800003A +:10C80000EAE7072510E008250EE0307800F03F0049 +:10C810001B2809D01D2807D0022005990BF04EF9DE +:10C82000208800F400502080A07B400708D52046D7 +:10C83000FFF70BFDC00703D1A07B20F00400A0731D +:10C84000284685E61FB5022806D101208DF8000094 +:10C8500088B26946F5F760FB1FBD0000F8B51D46BC +:10C86000DDE906470E000AD007F063FC2346FF1DF2 +:10C87000BCB231462A46009407F071F8F8BDD019D1 +:10C880002246194601F0D9FD2046F8BD2DE9FF4F9B +:10C890008DB09B46DDE91B57DDF87CA00C46082BCC +:10C8A00005D0E06901F0FEF850B11020D2E02888F0 +:10C8B000092140F0100028808AF80010022617E0B5 +:10C8C000E16901208871E2694FF420519180E169AA +:10C8D0008872E06942F601010181E06900218173FB +:10C8E0002888112140F0200028808AF800100426B2 +:10C8F00038780A900A2038704FF0020904F11800C5 +:10C900004D460C9001F0C6FBB04681E0BBF1100F24 +:10C910000ED1022D0CD0A9EB0800801C80B20221A0 +:10C92000CDE9001005AB52461E990D98FFF796FF12 +:10C93000BDF816101A98814203D9F74800790F9074 +:10C9400004E003D10A9808B138702FE04FF00201DB +:10C95000CDE900190DF1160352461E990D98FFF707 +:10C960007DFF1D980088401B801B83B2C6F1FF002D +:10C97000984200D203461E990BA8D9B15FF000027D +:10C98000DDF878C0CDE9032009EB060189B2CDE9D5 +:10C9900001C10F980090BDF8161000220D9801F00B +:10C9A0000EFC387070B1C0B2832807D0BDF81600F5 +:10C9B00020833AE00AEB09018A19E1E7022011B06D +:10C9C000BDE8F08FBDF82C00811901F0FF08022DA1 +:10C9D0000DD09AF80120424506D1BDF820108142C1 +:10C9E00007D0B8F1FF0F04D09AF801801FE08AF851 +:10C9F0000180C94800680178052902D1BDF81610E8 +:10CA0000818009EB08001FFA80F905EB080085B268 +:10CA1000DDE90C1005AB0F9A01F03FFB28B91D981A +:10CA20000088411B4145BFF671AF022D13D0BBF109 +:10CA3000100F0CD1A9EB0800801C81B20220CDE9B7 +:10CA4000000105AB52461E990D98FFF707FF1D9890 +:10CA50000580002038700020B1E72DE9F8439C469E +:10CA6000089E13460027B26B9AB3491F8CB2F18F10 +:10CA7000A1F57F45FF3D05D05518AD882944891D96 +:10CA80008DB200E000252919B6F83C8008314145F7 +:10CA900020D82A44BCF8011022F8021BBCF803106D +:10CAA00022F8021B984622F8024B914607F02FFB12 +:10CAB0004FF00C0C41464A462346CDF800C006F024 +:10CAC0001AFFF587B16B00202944A41D214408807A +:10CAD00003E001E0092700E083273846BDE8F8833A +:10CAE00010B50B88848F9C420CD9846BE0180488A5 +:10CAF00044B1848824F40044A41D23440B801060B6 +:10CB0000002010BD0A2010BD2DE9F0478AB0002595 +:10CB1000904689468246ADF8185007274BE00598A5 +:10CB200006888088000446D4A8F8006007A801950C +:10CB300000970295CDE903504FF40073002231466F +:10CB4000504601F03CFB04003CD1BDF81800ADF8A4 +:10CB50002000059804888188B44216D10A0414D4B0 +:10CB600001950295039521F400410097049541F445 +:10CB7000804342882146504601F0BFF804000BD1A3 +:10CB80000598818841F40041818005AA08A948469A +:10CB9000FFF7A6FF0400DCD00097059802950195E9 +:10CBA000039504950188BDF81C300022504601F021 +:10CBB000A4F80A2C06D105AA06A94846FFF790FF5B +:10CBC0000400ACD0ADF8185004E00598818821F439 +:10CBD0000041818005AA06A94846FFF781FF002889 +:10CBE000F3D00A2C03D020460AB0BDE8F08700201D +:10CBF000FAE710B50C46896B86B051B10C218DF85F +:10CC00000010A18FADF80810A16B01916946FAF7E9 +:10CC100001FB00204FF6FF71A063E187A08706B0FB +:10CC200010BD2DE9F0410D460746896B0020069E98 +:10CC30001446002911D0012B0FD13246294638461F +:10CC4000FFF762FF002808D1002C06D032462946A3 +:10CC50003846BDE8F04100F034BFBDE8F0812DE971 +:10CC6000FC411446DDE9087C0E46DDE90A15521D3B +:10CC7000BCF800E092B2964502D20720BDE8FC81E4 +:10CC8000ACF8002017222A70A5F80160A5F803303F +:10CC90000522CDE900423B462A46FFF7DFFD002092 +:10CCA000ECE770B50C46154648220021204601F0FD +:10CCB000EEFB04F1080044F81C0F00204FF6FF7152 +:10CCC000E06161842084A5841720E08494F82A0020 +:10CCD00040F00A0084F82A0070BD4FF6FF720A8007 +:10CCE000014603200AF0EABE30B585B00C46054681 +:10CCF000FFF77FFFA18E284629B101218DF8001092 +:10CD00006946FAF787FA0020E0622063606305B0A5 +:10CD100030BDB0F8400070476000002090F8462019 +:10CD2000920703D4408808800020F4E70620F2E749 +:10CD300090F846209207EED5A0F84410EBE70146A4 +:10CD4000002009880A0700D5012011F0F00F01D05A +:10CD500040F00200CA0501D540F004008A0501D563 +:10CD600040F008004A0501D540F010000905D2D571 +:10CD700040F02000CFE700B5034690F84600C0071A +:10CD800001D0062000BDA3F842101846FFF7D7FFD8 +:10CD900010F03E0F05D093F8460040F0040083F8F1 +:10CDA000460013F8460F40F001001870002000BD47 +:10CDB00090F84620520700D511B1B0F84200AAE71A +:10CDC0001720A8E710F8462F61F3C3020270A2E70C +:10CDD0002DE9FF4F9BB00E00DDE92B34DDE929780A +:10CDE000289D24D02878C10703D000F03F001928DF +:10CDF00001D9012100E000212046FFF7D9FFB04210 +:10CE000015D32878410600F03F010CD41E290CD020 +:10CE1000218811F47F6F0AD13A8842B1A1F57F428F +:10CE2000FF3A04D001E0122901D1000602D5042006 +:10CE30001FB0C5E5FA491D984FF0000A08718DF83A +:10CE400018A08DF83CA00FAA0A60ADF81CA0ADF8A0 +:10CE500050A02978994601F03F02701F5B1C04F135 +:10CE6000180C4FF0060E4FF0040BCDF858C01F2AD7 +:10CE70007ED2DFE802F07D7D107D267DAC7DF47DE5 +:10CE8000F37DF27DF17DF47DF07D7D7DEF7DEE7DA6 +:10CE90007D7D7D7DED0094F84610B5F80100890791 +:10CEA00001D5032E02D08DF818B01EE34FF40061B7 +:10CEB000ADF85010608003218DF83C10ADF84000B3 +:10CEC000D4E2052EEFD1B5F801002083ADF81C00A7 +:10CED000B5F80310618308B1884201D9012079E1D6 +:10CEE0000020A07220814FF6FF702084169801F078 +:10CEF000D1F8052089F800000220029083460AAB91 +:10CF00001D9A16991B9801F0C8F890BB9DF82E0049 +:10CF1000012804D0022089F80100102003E001203C +:10CF200089F8010002200590002203A90BA808F04F +:10CF30006AFDE8BB9DF80C00059981423DD1398816 +:10CF4000801CA1EB0B01814237DB02990220CDE965 +:10CF500000010DF12A034A4641461B98FFF77EFC6B +:10CF600002980BF1020B801C81B217AA029101E01A +:10CF70009CE228E003A90BA808F045FD02999DF862 +:10CF80000C00CDE9000117AB4A4641461B98FFF75C +:10CF900065FC9DF80C000AAB0BEB00011FFA81FB4E +:10CFA00002991D9A084480B2029016991B9800E0DD +:10CFB00003E001F072F80028B6D0BBF1020F02D0F6 +:10CFC000A7F800B04FE20A208DF818004BE20021CC +:10CFD0000391072EFFF467AFB5F801002083ADF889 +:10CFE0001C00B5F80320628300283FF477AF90421D +:10CFF0003FF674AF0120A072B5F805002081002033 +:10D00000A073E06900F04EFD78B9E16901208871F4 +:10D01000E2694FF420519180E1698872E16942F63A +:10D0200001000881E06900218173F01F20841E98AF +:10D03000606207206084169801F02CF8072089F8B8 +:10D0400000000120049002900020ADF82A0028E0A2 +:10D0500019E29FE135E1E5E012E2A8E080E043E07B +:10D060000298012814D0E0698079012803D1BDF825 +:10D070002800ADF80E00049803ABCDE900B04A4695 +:10D0800041461B98FFF7EAFB0498001D80B204900C +:10D09000BDF82A00ADF80C00ADF80E00059880B27E +:10D0A00002900AAB1D9A16991B9800F0F6FF28B95A +:10D0B00002983988001D05908142D1D2029801283A +:10D0C00081D0E0698079012803D1BDF82800ADF84E +:10D0D0000E00049803ABCDE900B04A4641461B98C8 +:10D0E000FFF7BCFB0298BDE1072E02D0152E7FF49E +:10D0F000DAAEB5F801102183ADF81C10B5F80320A5 +:10D10000628300293FF4EAAE91423FF6E7AE012187 +:10D11000A1724FF0000BA4F808B084F80EB0052EF1 +:10D1200007D0C0B2691DE26908F06BFC00287FF4EB +:10D130004AAF4FF6FF70208401A906AA14A8CDF8C3 +:10D1400000B081E885032878214600F03F031D9A4E +:10D150001B98FFF79BFB8246208BADF81C0082E1F9 +:10D160000120032EC3D14021ADF85010B5F80110B5 +:10D170002183ADF81C100AAAB8F1000F00D00023DB +:10D18000CDE9020304921D98CDF804800090388800 +:10D190000022401E83B21B9801F011F88DF8180090 +:10D1A00090BB0B2089F80000BDF8280035E04FF057 +:10D1B000010C052E9BD18020ADF85000B5F8011070 +:10D1C0002183B5F803002084ADF81C10B0F5007F72 +:10D1D00003D907208DF8180087E140F47C422284AF +:10D1E0000CA8B8F1000F00D00023CDE90330CDE941 +:10D1F000018C1D9800903888401E83B21B9800F067 +:10D20000DEFF8DF8180018B18328A8D10220BFE0F6 +:10D210000D2189F80010BDF83000401C22E100000B +:10D2200060000020032E04D248067FF53CAE0020AB +:10D2300018E1B5F80110ADF81C102878400602D5A9 +:10D240008DF83CE002E007208DF83C004FF000082C +:10D250000320CDE902081E9BCDF810801D98019394 +:10D26000A6F1030B00901FFA8BF342461B9800F0C7 +:10D2700044FD8DF818008DF83C80297849060DD5BD +:10D280002088C00506D5208BBDF81C10884201D12E +:10D29000C4F8248040468DF81880E3E0832801D14B +:10D2A0004FF0020A4FF48070ADF85000BDF81C003A +:10D2B0002083A4F820B01E986062032060841321AC +:10D2C000CDE0052EFFF4EFADB5F80110ADF81C1060 +:10D2D000A28F6AB3A2F57F43FE3B29D008228DF8C6 +:10D2E0003C2000BF4FF0000B0523CDE9023BDDF8E9 +:10D2F00078C0CDF810B01D9A80B2CDF804C040F4CB +:10D3000000430092B5F803201B9800F0F6FC8DF85E +:10D310003CB04FF400718DF81800ADF85010832820 +:10D3200010D0F8B1A18FA1F57F40FE3807D0DCE026 +:10D330000B228DF83C204FF6FE72A287D2E7A4F8AC +:10D340003CB0D2E000942B4631461E9A1B98FFF762 +:10D3500084FB8DF8180008B183284BD1BDF81C0060 +:10D36000208353E700942B4631461E9A1B98FFF703 +:10D3700074FB8DF81800E8BBE18FA06B0844831D97 +:10D380008DE888034388828801881B98FFF767FC33 +:10D39000824668E095F80180022E70D15FEA0800AD +:10D3A00002D0B8F1010F6AD109208DF83C0007A81E +:10D3B00000908DF840804346002221461B98FFF7DD +:10D3C00030FC8DF842004FF0000B8DF843B050B99F +:10D3D000B8F1010F12D0B8F1000F04D1A18FA1F55F +:10D3E0007F40FF380AD0A08F40B18DF83CB04FF499 +:10D3F000806000E037E0ADF850000DE00FA91B9809 +:10D40000F9F708FF82468DF83CB04FF48060ADF824 +:10D410005000BAF1020F06D0FC480068C07928B16C +:10D420008DF8180027E0A4F8188044E0BAF1000F46 +:10D4300003D081208DF818003DE007A800904346F6 +:10D44000012221461B98FFF7ECFB8DF818002146BE +:10D450001B98FFF7CEFB9DF8180020B9192189F819 +:10D460000010012038809DF83C0020B10FA91B98C6 +:10D47000F9F7D0FE8246BAF1000F33D01BE018E076 +:10D480008DF818E031E02078000712D5012E10D178 +:10D490000A208DF83C00E088ADF8400003201B997D +:10D4A0000AF00CFB0820ADF85000C0E648067FF5F6 +:10D4B000FAAC4FF0040A2088BDF8501008432080D1 +:10D4C000BDF8500080050BD5A18FA1F57F40FE3837 +:10D4D00006D11E98E06228982063A6864FF0030AC2 +:10D4E0005046A5E49DF8180078B1012089F80000A5 +:10D4F000297889F80110BDF81C10A9F802109DF8D0 +:10D50000181089F80410052038802088BDF85010C4 +:10D5100088432080E4E72DE9FF4F8846087895B0DE +:10D52000012181404FF20900249C0140ADF82010F8 +:10D530002088DDF88890A0F57F424FF0000AFF3A7E +:10D5400006D039B1000705D5012019B0BDE8F08F2C +:10D550000820FAE7239E4FF0000B0EA886F800B0D3 +:10D5600018995D460988ADF83410A8498DF81CB0AB +:10D57000179A0A718DF838B0086098F800000128F1 +:10D580003BD0022809D003286FD1307820F03F002B +:10D590001D303070B8F80400E08098F800100320C7 +:10D5A000022904D1317821F03F011B31317094F808 +:10D5B0004610090759D505ABB9F1000F13D000216A +:10D5C00002AA82E80B000720CDE90009BDF834006B +:10D5D000B8F80410C01E83B20022159800F0EFFDC9 +:10D5E0000028D1D101E0F11CEAE7B8F80400A6F860 +:10D5F0000100BDF81400C01C04E198F805108DF876 +:10D600001C1098F80400012806D04FF4007A022874 +:10D610002CD00328B8D16CE12188B8F8080011F4A7 +:10D620000061ADF8201020D017281CD3B4F84010AA +:10D63000814218D3B4F84410172901D3814212D182 +:10D64000317821F03F01C91C3170A6F80100032197 +:10D65000ADF83410A4F8440094F8460020F002001D +:10D6600084F8460065E105257EE177E1208808F130 +:10D67000080700F4FE60ADF8200010F0F00F1BD09A +:10D6800010F0C00F03D03888228B9042EBD199B9AB +:10D69000B878C00710D0B9680720CDE902B1CDF83D +:10D6A00004B00090CDF810B0FB88BA88398815987E +:10D6B00000F023FB0028D6D12398BDF82010401C91 +:10D6C00080294ED006DC10290DD020290BD040290E +:10D6D00087D124E0B1F5807F6ED051457ED0B1F581 +:10D6E000806F97D1DEE0C80601D5082000E0102049 +:10D6F00082460DA907AA0520CDE902218DF8380040 +:10D70000ADF83CB0CDE9049608A93888CDE9000110 +:10D710005346072221461598FFF7B8F8A8E09DF870 +:10D720001C2001214FF00A0A002A9BD105ABB9F158 +:10D73000000F00D00020CDE902100720CDE900093C +:10D74000BDF834000493401E83B2218B002215984B +:10D7500000F035FD8DF81C000B203070BDF8140072 +:10D7600020E09DF81C2001214FF00C0A002A22D154 +:10D7700013ABB9F1000F00D00020CDE90210072053 +:10D78000CDE900090493BDF83400228C401E83B219 +:10D79000218B159800F013FD8DF81C000D203070C2 +:10D7A000BDF84C00401CADF8340005208DF8380061 +:10D7B000208BADF83C00BCE03888218B88427FF498 +:10D7C00052AF9DF81C004FF0120A00281CD1606A6D +:10D7D000A8B1B878C0073FF446AF00E018E0BA68D7 +:10D7E0000720CDE902B2CDF804B00090CDF810B01A +:10D7F000FB88BA88159800F080FA8DF81C00132079 +:10D8000030700120ADF8340093E00000600000208B +:10D810003988208B8142D2D19DF81C004FF0160A26 +:10D820000028A06B08D0E0B34FF6FF7000215F46E0 +:10D83000ADF808B0019027E068B1B978C907BED14A +:10D84000E18F0DAB0844821D03968DE80C024388DE +:10D850008288018809E0B878C007BCD0BA680DABEF +:10D8600003968DE80C02BB88FA881598FFF7F7F944 +:10D8700005005ED0072D72D076E0019005AA02A9BE +:10D880002046FFF72DF90146E28FBDF808008242DD +:10D8900001D00029F1D0E08FA16B084407800198E6 +:10D8A000E08746E09DF81C004FF0180A40B1208B3D +:10D8B000C8B13888208321461598FFF79AF938E0D7 +:10D8C00004F118000090237E012221461598FFF7ED +:10D8D000A8F98DF81C000028EDD119203070012026 +:10D8E000ADF83400E7E7052521461598FFF781F9E3 +:10D8F0003AE0208800F40070ADF8200050452DD1AA +:10D90000A08FA0F57F41FE3901D006252CE0D8F884 +:10D9100008004FF0160A48B1A063B8F80C10A187B0 +:10D920004FF6FF71E187A0F800B002E04FF6FF70FC +:10D93000A087BDF8200030F47F611AD07823002240 +:10D94000032015990AF010F898F80000207120883B +:10D95000BDF82010084320800EE000E00725208855 +:10D96000BDF8201088432080208810F47F6F1CD0E1 +:10D970003AE02188814321809DF8380020B10EA92A +:10D980001598F9F747FC05469DF81C000028EBD0D8 +:10D9900086F801A001203070208B70809DF81C005B +:10D9A00030710520ADF83400DEE7A18EE1B11898A2 +:10D9B0000DAB0088ADF834002398CDE90304CDE920 +:10D9C0000139206B0090E36A179A1598FFF700FA67 +:10D9D000054601208DF838000EA91598F9F71AFCB4 +:10D9E00000B10546A4F834B094F8460040070AD5C3 +:10D9F0002046FFF7A4F910F03E0F04D114F8460FAB +:10DA000020F0040020701898BDF8341001802846DA +:10DA10009BE500B585B0032806D102208DF80000F3 +:10DA200088B26946F9F7F6FB05B000BD10B5384C71 +:10DA30000B782268012B02D0022B2AD111E0137837 +:10DA40000BB1052B01D10423137023688A889A80B7 +:10DA50002268CB88D38022680B8913814989518140 +:10DA60000DE08B8893802268CB88D38022680B8955 +:10DA700013814B8953818B899381096911612168D5 +:10DA8000F9F7C8FB226800210228117003D0002892 +:10DA900000D0812010BD832010BD806B002800D0F5 +:10DAA000012070478178012909D10088B0F5205FF5 +:10DAB00003D042F60101884201D1002070470720BF +:10DAC0007047F0B587B0002415460E460746ADF8FE +:10DAD000184011E005980088288005980194811D60 +:10DAE000CDE902410721049400918388428801888E +:10DAF000384600F002F930B905AA06A93046FEF70B +:10DB0000EFFF0028E6D00A2800D1002007B0F0BDC2 +:10DB10006000002010B58B7883B102789A4205D15D +:10DB20000B885BB102E08B79091D4BB18B789A426F +:10DB3000F9D1B0F801300C88A342F4D1002010BD17 +:10DB4000812010BD072826D012B1012A27D103E079 +:10DB5000497801F0070102E04978C1F3C2010529C3 +:10DB60001DD2DFE801F00318080C12000AB10320EF +:10DB700070470220704704280DD250B10DE00528EF +:10DB800009D2801E022808D303E0062803D0032808 +:10DB900003D005207047002070470F207047812078 +:10DBA0007047C0B282060BD4000607D5FA48807AC7 +:10DBB0004143C01D01EBD00080B27047084670475A +:10DBC0000020704770B513880B800B781C0625D594 +:10DBD000F14CA47A844204D843F01000087000206D +:10DBE00070BD956800F0070605EBD0052D78F5406F +:10DBF00065F304130B701378D17803F0030341EA43 +:10DC0000032140F20123B1FBF3F503FB15119268E8 +:10DC1000E41D00FB012000EBD40070BD906870BDD6 +:10DC200037B51446BDF804101180117841F0040195 +:10DC300011709DF804100A061ED5D74AA368C1F3D7 +:10DC40000011927A824208D8FE2811D1D21DD20842 +:10DC50004942184600F01BFC0AE003EBD00200F03A +:10DC60000703012510789D40A84399400843107090 +:10DC7000207820F0100020703EBD2DE9F0410746CD +:10DC8000C81C0E4620F00300B04202D08620BDE83A +:10DC9000F081C14D002034462E60AF802881AA72E9 +:10DCA000E8801AE0E988491CE980810614D4E1780B +:10DCB00000F0030041EA002040F20121B0FBF1F244 +:10DCC00001FB12012068FFF76CFF2989084480B22C +:10DCD0002881381A3044A0600C3420784107E1D400 +:10DCE0000020D4E7AC4801220189C08800EB400045 +:10DCF00002EB8000084480B270472DE9FF4F89B0E5 +:10DD00001646DDE9168A0F46994623F44045084633 +:10DD100000F054FB040002D02078400703D4012017 +:10DD20000DB0BDE8F08F099806F086F802902078D3 +:10DD3000000606D59848817A0298814201D887204A +:10DD4000EEE7224601A90298FFF73CFF8346002038 +:10DD50008DF80C004046B8F1070F1AD00122214679 +:10DD6000FFF7F0FE0028DBD12078400611D5022015 +:10DD70008DF80C00ADF81070BDF80400ADF812007D +:10DD8000ADF814601898ADF81650CDF81CA0ADF899 +:10DD900018005FEA094004D500252E46A846012751 +:10DDA0000CE02178E07801F0030140EA012040F224 +:10DDB0000121B0FBF1F2804601FB12875FEA494086 +:10DDC00009D5B84507D1A178207901F0030140EACF +:10DDD0000120B04201D3BE4201D90720A0E7A81913 +:10DDE0001FFA80F9B94501D90D2099E79DF80C007B +:10DDF00028B103A90998F9F70BFA002890D1B84582 +:10DE000007D1A0784FEA192161F30100A07084F8CE +:10DE100004901A9800B10580199850EA0A0027D09A +:10DE2000199830B10BEB06002A46199900F005FB52 +:10DE30000EE00BEB06085746189E099806F067F9A6 +:10DE40002B46F61DB5B239464246009505F053FD06 +:10DE5000224601A90298FFF7B5FE9DF8040022466C +:10DE600020F010008DF80400DDE90110FFF7D8FE66 +:10DE7000002055E72DE9FF4FDFF81C91824685B061 +:10DE8000B9F80610D9F8000001EB410100EB81045C +:10DE900040F20120B2FBF0F1174600FB1175DDE9FD +:10DEA000138B4E4629460698FFF77BFE0346FFF785 +:10DEB00019FF1844B1880C30884202D9842009B077 +:10DEC0002FE70698C6B2300603D5B00601D5062066 +:10DED000F5E7B9F80620521C92B2A9F80620BBF16A +:10DEE000000F01D0ABF80020B00602D5C4F80880BE +:10DEF0000AE0B9F808201A4492B2A9F80820D9F823 +:10DF00000000891A0844A0602246FE200699FFF707 +:10DF100087FEE77025712078390A61F301002A0A2B +:10DF2000A17840F0040062F30101A17020709AF81A +:10DF300002006071BAF80000E08000252573300609 +:10DF400002D599F80A7000E00127B00601D54FF01C +:10DF500000084E4600244FF007090FE0CDE90258B3 +:10DF60000195CDF800900495F1882046129B089AFF +:10DF7000FFF7C3FE0028A2D1641CE4B2BC42EDD37B +:10DF800000209CE700B5FFF7ADFE03490C308A88FE +:10DF9000904203D9842000BD00060020CA8808688A +:10DFA00002EB420300EB8300521C037823F00403CE +:10DFB0000370CA80002101730846ECE72DE9F047A1 +:10DFC000804600F0FBF9070005D000264446F74DD7 +:10DFD00040F2012916E00120BDE8F087204600F05C +:10DFE000EDF90278C17802F0030241EA0222B2FBA5 +:10DFF000F9F309FB13210068FFF7D3FD3044641CDB +:10E0000086B2A4B2E988601E8142E7DCA8F1010073 +:10E01000E8802889801B288100203870DCE710B553 +:10E02000144631B1491E218005F006FFA070002082 +:10E0300010BD012010BD70B50446DC48C1880368DE +:10E0400001E0401C20802088884207D200EB40027B +:10E0500013EB820202D015786D07F2D580B28842A8 +:10E0600016D2AAB15079A072D08820819178107907 +:10E0700001F0030140EA0120A081A078E11CFFF734 +:10E08000A1FD20612088401C2080E080002070BD20 +:10E090000A2070BD0121018270472DE9FF4F85B034 +:10E0A0004FF6FF798246A3F8009048681E460D4659 +:10E0B00080788DF8060048680088ADF804000020DC +:10E0C0008DF80A00088A0C88A04200D304462C82EE +:10E0D00051E03878400708D4641C288AA4B2401C58 +:10E0E000288208F10100C0B246E0288A401C28823C +:10E0F000781D6968FFF70EFDD8BB3188494501D10D +:10E10000601E30803188A1EB080030806888A04212 +:10E1100038D3B878397900F0030041EA002801A922 +:10E12000781DFFF7F7FC20BB298949452ED0002236 +:10E1300039460798FFF706FDD8B92989414518D116 +:10E14000E9680391B5F80AC0D7F808B05046CDF891 +:10E1500000C005F0DCFFDDF800C05A460CF1070CEA +:10E160001FFA8CFC43460399CDF800C005F08DFBE7 +:10E1700060B1641CA4B200208046204600F01EF965 +:10E180000700A6D1641E2C820A2098E67480787954 +:10E19000B071F888B0803978F87801F0030140EA6E +:10E1A00001207081A6F80C80504605F045FE3A46E5 +:10E1B00006F10801FFF706FD306100207FE62DE93A +:10E1C000FF4F87B081461C469246DDF860B0DDF80F +:10E1D0005480089800F0F2F8050002D02878400733 +:10E1E00002D401200BB09CE5484605F025FE2978B5 +:10E1F000090605D56D49897A814201D88720F1E762 +:10E20000CAF309062A4601A9FFF7DCFC0746149861 +:10E2100007281CD000222946FFF794FC0028E1D1F2 +:10E220002878400613D501208DF808000898ADF82D +:10E230000C00BDF80400ADF80E00ADF81060ADF8AC +:10E24000124002A94846F8F7E3FF0028CAD129780E +:10E25000E87801F0030140EA0121AA78287902F068 +:10E26000030240EA0220564507D0B1F5007F04D9E9 +:10E27000611E814201DD0B20B4E7864201D90720EF +:10E28000B0E7801B85B2A54200D92546BBF1000F3F +:10E2900001D0ABF80050179818B1B9192A4600F010 +:10E2A000CCF8B8F1000F0DD03E4448464446169FC6 +:10E2B00005F03FFF2146FF1DBCB232462B460094BD +:10E2C00005F04DFB00208DE72DE9F04107461D4686 +:10E2D0001646084600F072F8040002D02078400785 +:10E2E00001D40120D3E4384605F0A6FD21780906C3 +:10E2F00005D52E49897A814201D88720C7E4224674 +:10E300003146FFF75FFC65B12178E07801F0030149 +:10E3100040EA0120B0F5007F01D8012000E0002094 +:10E3200028700020B3E42DE9F04107461D4616464B +:10E33000084600F043F8040002D02078400701D4DA +:10E340000120A4E4384605F077FD2178090605D5BB +:10E350001649897A814201D8872098E422463146BD +:10E36000FFF75EFCFF2D14D02178E07801F0030266 +:10E3700040EA022040F20122B0FBF2F302FB13005C +:10E3800015B900F2012080B2E070000A60F30101CB +:10E39000217000207BE410B50C4600F00FF810B19E +:10E3A0000178490704D4012010BD000000060020B8 +:10E3B000C18821804079A0700020F5E70749CA880C +:10E3C000824209D340B1096800EB40006FF00B02B4 +:10E3D00002EB8000084470470020704700060020D0 +:10E3E00070B504460D4621462B460AB9002070BD83 +:10E3F00001E0491C5B1C501E021E03D008781E78E9 +:10E40000B042F6D008781E78801BF0E730B50C4695 +:10E4100001462346051B954206D202E0521E9D5C32 +:10E420008D54002AFAD107E004E01D780D70491CD4 +:10E430005B1C521E002AF8D130BDF0B50E460146D5 +:10E44000334680EA030404F00304B4B906E002B9D9 +:10E45000F0BD13F8017B01F8017B521E01F00307A8 +:10E46000002FF4D10C461D4602E080CD80C4121F5F +:10E47000042AFAD221462B4600BF04E013F8014BD0 +:10E4800001F8014B521E002AF8D100BFE0E7F0B5B9 +:10E490000C460146E6B204E002B9F0BD01F8016B9A +:10E4A000521E01F00307002FF6D10B46E5B245EAF4 +:10E4B000052545EA054501E020C3121F042AFBD2C9 +:10E4C000194602E001F8016B521E002AFAD100BF82 +:10E4D000E3E7000010B509F0A0FDF4F7F9F909F041 +:10E4E000E7FBBDE8104009F0AFBC302834BF012085 +:10E4F00000207047202834BF4FF0A0420C4A01236F +:10E5000000F01F0003FA00F0002914BFC2F80C0548 +:10E51000C2F808057047202834BF4FF0A0410449D5 +:10E5200000F01F00012202FA00F0C1F81805704740 +:10E530000003005070B50346002002466FF02F051F +:10E540000EE09C5CA4F130060A2E02D34FF0FF309F +:10E5500070BD00EB800005EB4000521C2044D2B29D +:10E560008A42EED370BD30B50A230BE0B0FBF3F462 +:10E5700003FB1404B0FBF3F08D183034521E05F881 +:10E58000014CD2B2002AF1D130BD30B500234FF694 +:10E59000FF7510E0040A44EA002084B2C85C6040C1 +:10E5A000C0F30314604005EA00344440E0B25B1C51 +:10E5B00084EA40109BB29342ECD330BD2DE9F04188 +:10E5C000FE4B0026012793F864501C7893F868C02E +:10E5D000B8B183F89140A3F8921083F8902083F8A3 +:10E5E0008E70BCF1000F0CBF83F8946083F89450D8 +:10E5F000F3488068008805F08AFDBDE8F04105F029 +:10E6000021BA4FF6FF7083F89140A3F8920083F887 +:10E61000902083F88E70BCF1000F14BF83F89450E3 +:10E6200083F89460BDE8F0812DE9F041E44D29685C +:10E6300091F89C200024012A23D091F89620012AE9 +:10E6400030D091F86C301422DC4E0127012B32D0EF +:10E6500091F88E30012B4FD091F8A620012A1CBFD3 +:10E660000020BDE8F08144701F2200F8042B222214 +:10E67000A731FFF7E2FE286880F8A6400120BDE838 +:10E68000F08144701B220270D1F89D204260D1F8C5 +:10E69000A120826091F8A520027381F89C4001209E +:10E6A000BDE8F081447007220270D1F898204260E2 +:10E6B00081F89640E2E78046447000F8042B20225F +:10E6C0006E31FFF7BAFE88F80870286880F86C4051 +:10E6D00090F86E000028D1D1B6F87000A6F8980026 +:10E6E000A868417B86F89A1086F89670008805F035 +:10E6F0000EFD05F0B6F9C1E791F86C30012B0BD097 +:10E70000447017220270D1F890204260B1F8942032 +:10E71000028181F88E40B1E78046447000F8042BF6 +:10E7200020226E31FFF789FE88F80870286880F88B +:10E730006C4090F86E000028A0D1CDE7A04800689A +:10E7400090F86C10002914BFB0F870004FF6FF70FD +:10E75000704770B59A4C06462068002808BFFFDF56 +:10E760000025206845706660002808BFFFDF20682C +:10E77000417800291CBFFFDF70BDCC220021FFF7CC +:10E7800086FE2068FF2101707F2180F83810132158 +:10E790004184282180F86910012180F85C1080F8FC +:10E7A00061500AF0C1F9BDE8704009F0AEBA844981 +:10E7B0000968097881420CBF012000207047804819 +:10E7C000006890F82200C0F3400070477C48006861 +:10E7D00090F8220000F0010070477948006890F836 +:10E7E0002200C0F3001070472DE9F0437448002464 +:10E7F000036893F82400B3F822C0C0F38001C0F38B +:10E800004002114400F001000844CCF3001121B390 +:10E81000BCF1100F02BF6B4931F81000BDE8F08366 +:10E82000BCF1120F18BFBCF1130F0ED0BCF1150FC5 +:10E830001EBFFFDF2046BDE8F0830021624A32F8A8 +:10E84000102010FB0120BDE8F083604A002132F85F +:10E85000102010FB0120BDE8F08393F85E2093F8B0 +:10E860005F102E264FF47A774FF014084FF04009CE +:10E87000022A04BF4AF2D745B5FBF7F510D0012AAA +:10E8800004BF4AF22F75B5FBF7F510D04AF62315F1 +:10E89000B5FBF7F5082A08BF4E4613D0042A18D056 +:10E8A0002646082A0ED0042A13D0022A49D004F1A1 +:10E8B0002806042A0FD0082A1CBF4FF01908082286 +:10E8C00004D00AE04FF0140806F5A8764FF0400295 +:10E8D00003E006F5A8764FF0100218FB026212FB67 +:10E8E0000052C0EB00103A4D00EB800005EB8000B9 +:10E8F00010441CF0010F4FF4C8724FF4BF7504BFF1 +:10E90000CCF34006002E65D0CCF3400600F5A57090 +:10E91000EEB1082904BF174640260CD0042904BFD5 +:10E920002F46102607D0022907BF04F11807042636 +:10E9300004F12807082606EB860808EB86163E44F5 +:10E940001BE004F118064FF019080422C5E7082956 +:10E9500004BF164640270CD0042904BF2E461027BA +:10E9600007D0022907BF04F11806042704F128067E +:10E97000082707EB871706EB8706304400F19C0653 +:10E9800093F8690001F00C07002F08BF0020304405 +:10E9900018BF00F5416027D1082904BF164640275B +:10E9A0001BD0042904BF2E46102716D0022906BF0B +:10E9B00004F11806042704F128060CE00C060020D8 +:10E9C00068000020DC610200E4610200D461020002 +:10E9D000D4FEFFFF64E018BF0827C7EBC70707EBAB +:10E9E000470706EB4706304498301CF0010F17D05C +:10E9F000082908BF40210CD0042904BF2A46102151 +:10EA000007D0022907BF04F11802042104F12802EB +:10EA1000082101EB410303EB0111114408443BE0E1 +:10EA2000082904BF944640260CD0042904BFAC46F4 +:10EA3000102607D0022907BF04F1180C042604F1A0 +:10EA4000280C082606EB8616B3F840300CEB860C33 +:10EA50006044EB2B20D944F2552C0B3303FB0CF311 +:10EA60009B0D082907D0042902D0022905D008E00F +:10EA70002A46102108E0402106E004F11802042192 +:10EA800002E004F12802082101EB811102EB81016F +:10EA900001F5A57103FB010000F5B470BDE8F0833A +:10EAA00000F5A570082904BF944640260CD004291F +:10EAB00004BFAC46102607D0022907BF04F1180C8A +:10EAC000042604F1280C082606EB8616B3F8483015 +:10EAD0000CEB860C6044EB2BDED944F2552C0B3347 +:10EAE00003FB0CF39B0D0829C5D00429C0D00229D3 +:10EAF000C7D1C2E7FE4840F271210068806A4843EE +:10EB00007047FB48006890F83700002818BF0120C4 +:10EB1000704710B5F74C207B022818BF032808D196 +:10EB2000207D04F115010EF0E6FE08281CBF01202F +:10EB300010BD207B002816BF022800200120BDE860 +:10EB400010400AF021BDEB4908737047E849096895 +:10EB500081F8300070472DE9F047E54C2168087BCB +:10EB6000002816BF022800200120487301F10E0181 +:10EB70000AF0F4FC2168087B022816BF0328012252 +:10EB8000002281F82F204FF0080081F82D00487BEB +:10EB900001F10E034FF001064FF00007012804BFFA +:10EBA0005B7913F0C00F0AD001F10E03012804D1E4 +:10EBB000587900F0C000402801D0002000E001207A +:10EBC00081F82E00002A04BF91F8220010F0040FF3 +:10EBD00007D0087D01F115010EF08DFE216881F846 +:10EBE0002D002068476007F0BFFA2168C14D4FF043 +:10EBF0000009886095F82D000EF089FE804695F892 +:10EC00002F00002818BFB8F1000F04D095F82D0090 +:10EC10000EF0B1FC68B195F8300000281CBF95F8E3 +:10EC20002E0000281DD0697B05F10E0001290ED0B1 +:10EC300012E06E734A4605F10E0140460AF0E4FC0C +:10EC400095F82D1005F10E000EF063FF09E04079F4 +:10EC500000F0C000402831D0394605F10E000AF01E +:10EC60000BFD2068C77690F8220010F0040F08BF53 +:10EC7000BDE8F087002795F82D000EF017FD050080 +:10EC800008BFBDE8F087102102F0C2F8002818BFC5 +:10EC9000BDE8F08720683A4600F11C01C676284698 +:10ECA0000AF0B2FC206800F11C0160680FF08EF8D9 +:10ECB0006068BDE8F04701210FF0A3B80EF066FFD1 +:10ECC0004A4605F10E010AF09FFCCAE7884A12681D +:10ECD000137B0370D2F80E000860508A888070475A +:10ECE00078B584490446824E407B087332682078A8 +:10ECF00010706088ADF8000080B200F00101C0F330 +:10ED0000400341EA4301C0F3800341EA8301C0F3B9 +:10ED1000C00341EAC301C0F3001341EA0311C0F389 +:10ED2000401341EA4311C0F3801041EA801050843F +:10ED3000E07D012808BF012507D0022808BF022571 +:10ED400003D0032814BFFFDF0825306880F85E5029 +:10ED5000607E012808BF012507D0022808BF0225D0 +:10ED600003D0032814BFFFDF0825316881F85F5006 +:10ED700091F83500012829D0207B81F82400488CA7 +:10ED80001D280CBF002060688862607D81F8370014 +:10ED9000A07B002816BF0228002001200875D4F8A7 +:10EDA0000F00C1F81500B4F81300A1F81900A07EF7 +:10EDB00091F86B2060F3071281F86B20E07E012848 +:10EDC00018BF002081F83400002078BD91F85E2043 +:10EDD0000420082A08BF81F85E00082D08BF81F8CA +:10EDE0005F00C9E742480068408CC0F3001131B1B0 +:10EDF000C0F38000002804BF1F20704702E0C0F36A +:10EE0000400109B10020704710F0010F14BFEE203F +:10EE1000FF20704736480068408CC0F3001119B1DC +:10EE2000C0F3800028B102E0C0F3400008B1002028 +:10EE30007047012070472E49002209684A664B8CB2 +:10EE40001D2B0CBF81F8682081F8680070470023F3 +:10EE5000274A126882F85D30D164A2F85000012080 +:10EE600082F85D007047224A0023126882F85C3005 +:10EE7000A2F858000120516582F85C0070471C49D7 +:10EE8000096881F8360070471949096881F86100FE +:10EE900070471748006890F961007047144800688F +:10EEA00090F82200C0F3401070471148006890F8B5 +:10EEB0002200C0F3C0007047012070470C48006872 +:10EEC00090F85F00704770B509F018FE09F0F7FD83 +:10EED00009F0C0FC09F06CFD054C2068416E491C2E +:10EEE000416690F83300002558B109F01DFE03E09B +:10EEF000680000200C06002008F007FF206880F85A +:10EF000033502068457090F8391021B1BDE8704049 +:10EF100004200AF0AEBF90F86810D9B1406E81426B +:10EF200018D804200AF0A5FF206890F8220010F0FD +:10EF3000010F07D0A06843220188BDE8704001207E +:10EF4000FFF73CBBBDE8704043224FF6FF71002045 +:10EF5000FFF734BBBDE8704000200AF08ABF2DE9FE +:10EF6000F04782B00F468146FE4E4FF000083068F1 +:10EF7000458C15F0030F10D015F0010F05F00200BD +:10EF800005D0002808BF4FF0010806D004E0002893 +:10EF900018BF4FF0020800D1FFDF4FF0000A5446BF +:10EFA00015F0010F05F002000DD080B915F0040F27 +:10EFB0000DD04AF00800002F1CBF40F0010040F0C7 +:10EFC00002044DD09EE010B115F0040F0DD015F0E5 +:10EFD000070F10D015F0010F05F0020043D00028F4 +:10EFE00008BF15F0040F34D04AE0002F18BF4AF0D4 +:10EFF000090444D141E037B14AF00800044615F055 +:10F00000200F1BD07EE0316805F02002B1F84800E7 +:10F01000104308BF4AF0010474D04AF018000446B7 +:10F0200015F0200F6ED191F85E1011F00C0118BF91 +:10F030000121C94361F30000044663E0316891F89F +:10F040005E1011F00C0118BF012161F300000446AD +:10F0500058E04AF00800002F18BF40F0010451D1D9 +:10F0600040F010044EE0002818BF15F0040F07D040 +:10F07000002F18BF4AF00B0444D14AF0180441E0B5 +:10F0800015F0030F3DD115F0040F3AD077B1306879 +:10F090004AF0080490F85E0010F00C0118BF01213E +:10F0A00061F3410415F0200F24D02BE0306805F007 +:10F0B0002002B0F84810114308BF4AF0030421D0E1 +:10F0C0004AF0180415F0200F0AD000BF90F85E0037 +:10F0D00010F00C0018BF0120C04360F3410411E0A0 +:10F0E00090F85E1011F00C0118BF0121C94361F3C3 +:10F0F0000004EBE710F00C0018BF012060F30004DF +:10F1000000E0FFDF15F0400F1CD0CFB93168B1F837 +:10F110004800002804BF488C10F0010F0BD110F0FC +:10F12000020F08BF10F0200F05D115F0010F08BF26 +:10F1300015F0020F04D091F85E0010F00C0F01D111 +:10F1400044F040047068A0F800A0017821F020018C +:10F1500001704FF007010EF005FE414670680EF099 +:10F16000F8FF214670680FF000F814F0010F0CD082 +:10F170004FF006034FF000027B4970680EF0CFFF9E +:10F180003068417B70680EF02FFE14F0020F18D02B +:10F19000D6E90010B9F1000F4FF006034FF001025D +:10F1A00007D01C310EF0BBFF012170680EF029FE64 +:10F1B00007E015310EF0B3FF3068017D70680EF086 +:10F1C00020FE14F0040F18BFFFDF14F0080F19D051 +:10F1D000CDF800A03068BDF800200223B0F86A1016 +:10F1E00061F30B02ADF8002090F86B0003220109D7 +:10F1F0009DF8010061F307108DF801006946706801 +:10F200000EF08DFF012F62D13068B0F84810E1B3E5 +:10F2100090F82200C0F34000B8BB70680EF095FF74 +:10F22000401CC7B23068C7F1FF05B0F84820B0F8FD +:10F230005A10511AA942B8BF0D46AA423BD990F8BC +:10F24000220010F0010F36D144F0100421467068FE +:10F250000EF08BFFF81CC0B2ED1E284482B230685D +:10F26000B0F86A10436EC1F30B0151FA83F190F8C4 +:10F2700060303E4F1944BC460023E1FB07C31B0925 +:10F280006FF0240C03FB0C1100E020E080F860100C +:10F2900090F85F00012101F01FF90090BDF8000017 +:10F2A0009DF80210032340EA01400190042201A9C5 +:10F2B00070680EF034FF3068AAB2416C70680EF0CE +:10F2C00082FF3068B0F85A102944A0F85A1014F0A0 +:10F2D000400F06D0D6E900100123062261310EF05E +:10F2E0001EFF14F0200F18BFFFDF0020002818BFFA +:10F2F000FFDF02B0BDE8F0872DE9F043194C89B07B +:10F300002068002808BFFFDF20684178002944D129 +:10F310000178FF2941D0002680F83160A0F85A60BA +:10F32000867080F83960304609F062FB104802AD03 +:10F3300000F1240191E80E1085E80E10D0E90D10BF +:10F34000CDE9061002A809F041FB08F0BCFF2068D7 +:10F3500090F9610009F090F8064809F093F8064822 +:10F360000CE00000680000201A06002053E4B36E91 +:10F37000C8610200D0610200CD61020009F012FBF9 +:10F38000606809F038FB206890F8240010F0010F45 +:10F3900007D0252009F07EF80AE009B00C20BDE86E +:10F3A000F08310F0020F18BF262069D009F072F820 +:10F3B000206890F85E10252008F043FF206880F850 +:10F3C0002C6009F00FFB206890F85E10002009F017 +:10F3D00028F90F21052008F0F8FF206890F82E107A +:10F3E000002901BF90F82F10002990F8220010F09A +:10F3F000040F74D006F0B8FE0546206829468068E0 +:10F4000007F0AAFBDFF82884074690FBF8F008FB1A +:10F4100010704142284606F08EFB2168886097FBF9 +:10F42000F8F04A68104448600EF062FA014620681D +:10F43000426891426ED8C0E90165FE4D4FF0010867 +:10F4400095F82D000EF063FA814695F82F000127FC +:10F45000002818BFB9F1000F04D095F82D000EF068 +:10F460008AF8A0B195F8300000281CBF95F82E004E +:10F47000002824D0687B05F10E01012815D019E081 +:10F4800010F0040F14BF2720FFDF8FD190E73A461A +:10F490006F7305F10E0148460AF0B6F895F82D1085 +:10F4A00005F10E000EF035FB09E0487900F0C000D0 +:10F4B000402815D0414605F10E000AF0DDF820681D +:10F4C00090F8220010F0040F24D095F82D000EF0D3 +:10F4D000EDF805001ED0102101F09AFC40B119E0B2 +:10F4E0000EF054FB3A4605F10E010AF08DF8E6E7FE +:10F4F00020683A4600F11C01C77628460AF084F8D5 +:10F50000206800F11C0160680EF060FC0121606859 +:10F510000EF077FC2068417B0E3008F038FF206841 +:10F5200090F85C1061B3B0F85810A0F84810416D25 +:10F53000416490F82210C1F30011F1B9B0F86A00EB +:10F540000221C0F30B05ADF80050684607F0B0FF8C +:10F5500028B1BDF80000C0F30B00A84204D1BDF8EB +:10F560000000401CADF800002168BDF80000B1F8B3 +:10F570006A2060F30B02A1F86A20206880F85C60C2 +:10F58000206890F85D1039B1B0F85010A0F8401024 +:10F59000C16CC16380F85D60B0F86A10426EC1F35F +:10F5A0000B0151FA82F190F86020DFF88CC211440F +:10F5B00063460022E1FB0C3212096FF0240302FBC8 +:10F5C000031180F860100EF00CFA032160680EF051 +:10F5D00090FA216881F8330009B00020BDE8F0837B +:10F5E0009649886070472DE9F043944C83B02268B7 +:10F5F00092F831303BB1508C1D2808BFFFDF03B0BB +:10F60000BDE8F0435FE401260027F1B1054692F81A +:10F61000600008F03FFF206890F85F10FF2008F0BE +:10F6200010FE20684FF4A57190F85F20002009F0CB +:10F63000D4F8206890F8221011F0030F00F02C810C +:10F64000002D00F0238100F027B992F822108046A7 +:10F65000D07EC1F30011002956D0054660680780AE +:10F66000017821F020010170518C132937D01FDC63 +:10F67000102908BF022144D0122908BF062140D01A +:10F68000FFDF6C4D606805F10E010EF091FB697BA8 +:10F6900060680EF0A9FB2068418C1D2918BF152950 +:10F6A00063D0B0F84820416C60680EF0B6FB5CE0B7 +:10F6B000152918BF1D29E3D14FF001010EF052FBAF +:10F6C0006068017841F020010170216885B11C312A +:10F6D0000EF07CFB012160680EF093FBD1E7002166 +:10F6E0000EF040FB6068017841F020010170C8E72E +:10F6F00015310EF06BFB2068017D60680EF081FB18 +:10F70000BFE70EF02FFBBCE70021FFF728FC606885 +:10F71000C17811F03F0F28D0017911F0100F24D0DB +:10F720000EF01EFB2368024693F82410C1F38000FC +:10F73000C1F3400C604401F00101084493F82C101F +:10F74000C1F3800CC1F34005AC4401F001016144F8 +:10F75000401AC1B293F85E0000F0BEFE0090032391 +:10F760000422694660680EF0DAFC2068002590F8F3 +:10F77000241090F82C0021EA000212F0010F18BFAB +:10F7800001250ED111F0020F04D010F0020F08BFB6 +:10F79000022506D011F0040F03D010F0040F08BFAB +:10F7A0000425B8F1000F2BD0012D1BD0022D08BF6E +:10F7B00026201BD0042D14BFFFDF272016D0206881 +:10F7C00090F85E10252008F03CFD206890F822108B +:10F7D000C1F3001169B101224FF49671002008F0C5 +:10F7E000FCFF0DE0252008F055FEE8E708F052FE8A +:10F7F000E5E790F85E204FF49671002008F0EDFFE9 +:10F80000206890F82C10294380F82C1090F82420C0 +:10F8100032EA01011CD04670418C13292BD026DC22 +:10F82000102904BF03B0BDE8F083122923D007E0FC +:10F8300040420F000C06002053E4B36E6800002025 +:10F84000C1F30010002818BFFFDF03B0BDE8F0834C +:10F85000418C1D2908BF80F82C70DCD0C1F3001149 +:10F86000002914BF80F8316080F83170D3E7152982 +:10F8700018BF1D29DBD190F85E2003B04FF00101C5 +:10F88000BDE8F043084609F094B900BF90F85F2046 +:10F890000121084609F08DF92168002DC87E7CD031 +:10F8A0004A8C3D46C2F34000002808BF47F00805D7 +:10F8B00012F0400F18BF45F04005002819BFD1F8DD +:10F8C0003C90B1F84080D1F84490B1F8488060682D +:10F8D000072107800EF046FA002160680EF039FC1F +:10F8E000294660680EF041FC15F0080F17D020681B +:10F8F000BDF800100223B0F86A2062F30B01ADF8E6 +:10F90000001090F86B00032201099DF8010061F3DB +:10F9100007108DF80100694660680EF000FC606811 +:10F920000EF0DCFA2168C0F1FE00B1F85A20A8EB15 +:10F9300002018142A8BF0146CFB2D019404544D24E +:10F9400045F0100160680EF010FC60680EF0C6FA19 +:10F950002168C0F1FE00B1F85A10A8EB0101814204 +:10F96000A8BF0146CFB260680EF0EFFB3844421CDE +:10F970002068B0F86A10436EC1F30B0151FA83F1AD +:10F9800090F86030FE4D1944AC460023E1FB05C3FE +:10F990004FEA131C6FF0240300E03CE00CFB031162 +:10F9A00080F8601090F85F00012100F095FD009054 +:10F9B000BDF800009DF80210032340EA01400190C9 +:10F9C000042201A960680EF0AAFB216891F82200C8 +:10F9D00010F0400F05D001230622613160680EF05F +:10F9E0009EFB20683A46B0F85A0000EB09016068B7 +:10F9F0000EF0E9FB2068B0F85A103944A0F85A100C +:10FA000009F0BFFC002818BFFFDF20684670867031 +:10FA100003B0BDE8F0830121FFF7A1FAF0E7D94870 +:10FA200010B50068417841B90078FF2805D0002161 +:10FA30000846FFF7D8FD002010BD09F05FF809F077 +:10FA40003EF808F007FF08F0B3FF0C2010BD2DE9C9 +:10FA5000F041CC4D0446174628680E4690F86C00DD +:10FA6000002818BFFFDF2868002F80F86E7018BFCD +:10FA7000BDE8F0812188A0F870106188A0F8861098 +:10FA8000A188A0F88810E188A0F88A1094F888115D +:10FA900080F88C1090F82F10002749B1427B00F1BC +:10FAA0000E01012A04D1497901F0C001402935D065 +:10FAB00090F8301041B1427B00F10E01012A04BFE1 +:10FAC000497911F0C00F29D000F17A00F3F794FAC8 +:10FAD0006868FF2E0178C1F380116176D0F80310B9 +:10FAE000C4F81A10B0F80700E08328681ED0C0F8E8 +:10FAF0008010E18BA0F8841000F17402511E304692 +:10FB00000DF014FF002808BFFFDF286890F873107D +:10FB100041F0020180F87310BDE8F081D0F80E10BA +:10FB2000C0F87A10418AA0F87E10D1E7C0F8807042 +:10FB3000A0F88470617E80F87310D4F81A104167C1 +:10FB4000E18BA0F87810BDE8F08170B58D4C0125EF +:10FB5000206890F82200C0F3C00038B13C22FF2199 +:10FB6000A068FFF774FF206880F86C50206890F858 +:10FB7000220010F0010F1CBFA06801884FF03C026A +:10FB800012BF01204FF6FF710020FEF717FD20681D +:10FB900080F8395070BD7B49096881F832007047A0 +:10FBA0002DE9F041774C0026206841780127354641 +:10FBB000012906D0022901D003297DD0FFDFBDE84D +:10FBC000F081817802250029418C46D0C1F34002A2 +:10FBD000002A08BF11F0010F6FD090F85F204FF09E +:10FBE00001014FF0000008F0E4FF216891F82200C5 +:10FBF000C0F34000002814BF0C20222091F85F10B1 +:10FC000008F01FFB2068457090F8330058B108F0E9 +:10FC100068F8206890F85F0010F00C0F0CBF4020CF +:10FC2000452008F077FF206890F83400002818BFBE +:10FC300008F08FFF216891F85F0091F8691010F0CB +:10FC40000C0F08BF0021962008F0F6FE09F090FB8B +:10FC5000002818BFFFDFBDE8F081C1F3001282B1B8 +:10FC600010293FD090F8330020B108F03AF8402036 +:10FC700008F050FF206890F8221011F0040F36D0E1 +:10FC800043E090F8242090F82C309A422AD1B0F822 +:10FC90004800002808BF11F0010F05D111F0020F34 +:10FCA00008BF11F0200F6FD04FF001014FF000009E +:10FCB000FFF799FC206801E041E035E0418C11F04C +:10FCC000010F04BFC1F34001002907D1B0F85A1059 +:10FCD000B0F84820914218BFBDE8F08180F831703B +:10FCE000BDE8F081BDE8F041002101207BE490F8FF +:10FCF0003710012914BF0329102646F00E0190F891 +:10FD00005E204FF0000008F054FF206890F83400A7 +:10FD1000002818BF08F01DFF0021962008F08CFE77 +:10FD200020684570BDE8F081B0F85A10B0F848007E +:10FD3000814242D0BDE8F0410121084653E4817878 +:10FD4000D9B1418C11F0010F22D080F86C7090F87D +:10FD50006E20B0F870100120FEF730FC206845706E +:10FD600008F0CCFE08F0ABFE08F074FD08F020FEB1 +:10FD7000BDE8F04103200AF07CB88178012004E05E +:10FD800053E4B36E6800002017E0BDE8F0412AE4B8 +:10FD900011F0020F04BFFFDFBDE8F081B0F85A1088 +:10FDA000B0F84000814208D001210846FFF71BFC53 +:10FDB000216803204870BDE8F081BDE8F041FFF7FD +:10FDC00082B8FFF780B810B5FE4C206890F8341068 +:10FDD00049B1383008F0CCFE18B921687F2081F88D +:10FDE000380008F0ACFE206890F8330018B108F035 +:10FDF0009BFE07F08AFF0AF02EFCA8B1206890F85D +:10FE00002210C1F3001179B14078022818BFFFDF3A +:10FE100000210120FFF7E7FB2068417800291EBF81 +:10FE200040780128FFDF10BDBDE81040FFF74BB858 +:10FE30002DE9F047E34C0F4680462168B8F1030FE7 +:10FE4000488C08BFC0F3400508D000F0010591F8C8 +:10FE50003200002818BF4FF0010901D14FF000090E +:10FE600008F00CFB0646B8F1030F0CBF4FF0020878 +:10FE70004FF0010835EA090008BFBDE8F0872068A7 +:10FE800090F8330068B10DF08FFD38700146FF28FF +:10FE900007D06068C01C0DF060FD38780DF091FD52 +:10FEA000064360680178C1F3801221680B7D9A4295 +:10FEB00008D10622C01C1531FEF792FA002808BFAF +:10FEC000012000D000203978FF2906D0C8B9206869 +:10FED00090F82D00884216D113E0A0B1616811F8A6 +:10FEE000030BC0F380100DF006FD05460DF061FE1A +:10FEF00038B128460DF0DAFB18B1102100F088FF68 +:10FF000008B1012000E00020216891F8221011F0D2 +:10FF1000040F01D0F0B11AE0CEB9AB4890F8370029 +:10FF2000002818BF404515D1616811F8030BC0F3D4 +:10FF300080100DF0E0FC04460DF03BFE38B1204689 +:10FF40000DF0B4FB18B1102100F062FF10B10120D8 +:10FF5000BDE8F0870020BDE8F0872DE9F04F994D0E +:10FF6000044683B0286800264078022818BFFFDFC7 +:10FF700028684FF07F0B90F8341049B1383008F002 +:10FF8000F7FD002804BF286880F838B008F0D7FDD6 +:10FF900068680DF009FF8046002C00F0458208F0EB +:10FFA00010FA002800F04082012400274FF0FF09DA +:10FFB000B8F1050F1ED1686890F8240000F01F000A +:10FFC000102817D9286890F8360098B18DF800905D +:10FFD00069460520FFF72CFF002800F025822868DD +:10FFE00080F8A64069682222A730C91CFEF725FACE +:10FFF00000F01ABA68680EF062F8002800F0148267 +:020000040001F9 +:100000004046DFF8C4814FF0030A062880F02182C1 +:10001000DFE800F0FCFCFC03FCFB8DF80090694677 +:100020000320FFF705FF002800F0F180296891F810 +:10003000340010B191F89C00D8B12868817801296A +:100040004DD06868042107800DF08CFE08F10E0188 +:1000500068680DF0ADFE98F80D1068680DF0C4FEEC +:100060002868B0F84020C16B68680DF0FAFE00F017 +:1000700063B99DF8000081F89C400A7881F89D20C2 +:10008000FF280FD001F19F029E310DF04FFC002898 +:1000900008BFFFDF286890F89E1041F0020180F849 +:1000A0009E100DE068680278C2F3801281F89E20ED +:1000B000D0F80320C1F89F20B0F80700A1F8A300F2 +:1000C000286800F1A50490F838007F2808BFFFDFFA +:1000D000286890F83810217080F838B0ADE790F8B3 +:1000E00022000721C0F3801938480479686869F351 +:1000F000861407800DF036FE002168680EF029F89E +:10010000214668680EF031F80623002208F10E013E +:1001100068680EF004F82868417B68680DF064FE9A +:1001200068680DF0DBFE2968B1F84020C0F1FE01DF +:100130008A42B8BF1146CFB2BA423CD9F81EC7B204 +:1001400044F0100B594668680EF00FF868680DF01F +:10015000FCFF384400F101082868B0F86A10426ECC +:10016000C1F30B0151FA82F190F86020184C0A4457 +:10017000A4460023E2FB04C319096FF0240301FB2A +:10018000032180F8601090F85F004246012100F0E2 +:10019000A3F90190BDF804009DF80610032340EA7E +:1001A00001400290042202A968680DF0B8FF594688 +:1001B00068680DF0DAFFB9F1000F0FD0D5E9001033 +:1001C000012307E0680000200C060020C86102003F +:1001D00053E4B36E062261310DF0A1FF28683A4660 +:1001E000C16B68680DF0EFFF2868A0F85A70B0F88E +:1001F00040108F420CBF0121002180F8311009F01E +:10020000C0F8002818BFFFDF96E007E021E128686A +:100210008078002840F00A8100F006B98DF800903F +:1002200068680178C1F38019D0F803100191B0F823 +:100230000700ADF8080069460520FFF7F9FD002822 +:1002400028687DD0817800297CD090F85FB0D5E90E +:100250000104D0F80F10C4F80E10B0F8131061822A +:10026000417D2175817D6175B0F81710E182B0F88C +:1002700019106180B0F81B10A180B0F81D10E1804A +:1002800000F11F0104F1080015F085FE686890F880 +:10029000241001F01F01217690F82400400984F811 +:1002A000880184F864B084F865B01BF00C0F0CBFB3 +:1002B0000021012104F130000EF0ABF92868002282 +:1002C00090F8691084F8661090F8610084F867006F +:1002D0009DF80010A868FFF7BAFB022009F0C9FDDD +:1002E000B2480DF1040B08210468686807800DF01E +:1002F00039FD002168680DF02CFF214668680DF07B +:1003000034FF0623002208F10E0168680DF007FF94 +:100310002868417B68680DF067FD494668680DF004 +:1003200070FD06230122594668680DF0F8FE09F0B9 +:1003300028F8002818BFFFDF286880F801A077E0C0 +:100340006DE0FFE76868D5F808804FF00109027892 +:1003500098F80D10C2F34012114088F80D10D0F833 +:100360000F10C8F80E10B0F81310A8F81210417D45 +:1003700088F81410817D88F81510B0F81710A8F8C7 +:100380001610B0F81910A8F80210B0F81B10A8F851 +:100390000410B0F81D10A8F8061000F11F0108F1B4 +:1003A000080015F0F8FD686890F8241001F01F01AE +:1003B00088F8181090F824000021400988F8880176 +:1003C00088F8649088F8659008F130000EF021F903 +:1003D0002868002290F8691088F8661090F861008B +:1003E00088F867009DF80010A868FFF730FB2868C0 +:1003F00080F86C4090F86E20B0F870100120FEF785 +:10040000DDF82868477008F079FB08F058FB08F021 +:1004100021FA08F0CDFA012009F02BFD08E090F850 +:100420002200C0F3001008B1012601E0FEF74BFDE9 +:10043000286890F8330018B108F076FB07F065FCE7 +:1004400096B10AF008F960B100210120FFF7CBF85E +:1004500013E0286890F82200C0F300100028E5D0CF +:10046000E2E7FEF730FD08E028688178012904D131 +:1004700090F85F10FF2007F0E4FE2868417800291B +:1004800019BF4178012903B0BDE8F08F40780328F7 +:1004900018BFFFDF03B0BDE8F08F70B5444C0646CF +:1004A0000D462068807858B107F0F2FD21680346B8 +:1004B000304691F85F202946BDE870400AF085BAC1 +:1004C00007F0E6FD21680346304691F85E20294694 +:1004D000BDE870400AF079BA78B50C460021009169 +:1004E000082804BF4FF4C87040210DD0042804BF71 +:1004F0004FF4BF70102107D0022807BF01F1180088 +:10050000042101F128000821521D02FB01062848A0 +:100510009DF80010006890F8602062F3050141F03A +:1005200040058DF8005090F85F00012829D002287E +:100530002ED004281CBF0828FFDF2FD025F0800014 +:100540008DF80000C4EB041000EB80004FF01E019A +:1005500001EB800006FB04041648844228BFFFDF3D +:100560001548A0FB0410BDF80110000960F30C0150 +:10057000ADF80110BDF800009DF8021040EA0140FE +:1005800078BD9DF8020020F0E0008DF80200D5E76C +:100590009DF8020020F0E000203004E09DF8020009 +:1005A00020F0E00040308DF80200C7E7C86102008B +:1005B00068000020C4BF0300898888880023C383A3 +:1005C000428401EBC202521EB2FBF1F1018470477A +:1005D0002DE9F04104460026D9B3552333224FF4C8 +:1005E000FA4501297DD0022900F01481032918BFA2 +:1005F000BDE8F08104F17001207B00F01F00207342 +:1006000084F889605FF0000004EB000C9CF808C0DF +:1006100003EA5C05ACEB050C0CF0FF0C0CF03305A9 +:1006200002EA9C0CAC440D180CEB1C1C0CF00F0CDB +:1006300085F814C04D7E401CAC44C0B281F819C08E +:100640000528E1D30CF0FF00252898BFBDE8F08114 +:10065000DCE0FFE704F17005802200212846FDF769 +:1006600016FFAE71EE712E736E73EE732E746E7193 +:10067000AE76EE76212085F84000492085F84100CD +:10068000FE2085F874002588702200212046FDF7A1 +:10069000FEFE2580012584F8645084F865502820EA +:1006A00084F86600002104F130000DF0B2FF1B2237 +:1006B000A4F84E20A4F85020A4F85220A4F8542006 +:1006C0004FF4A470A4F85600A4F8580065734FF4D2 +:1006D00048606080A4F8F060A4F8F260A4F8F460C8 +:1006E00000E023E0A4F8F660A4F8F86084F8FA606B +:1006F00084F8FD60A4F8066184F80461A4F8186128 +:10070000A4F81A6184F8B66184F8B76184F8C0610E +:1007100084F8C16184F88C6184F88F6184F8A861E1 +:10072000C4F8A061C4F8A461BDE8F081A4F8066132 +:1007300084F8FB606088FE490144B1FBF0F1A4F845 +:1007400090104BF68031A4F89210B4F806C0A4F8CB +:100750009860B4F89C704FEACC0C4743BCFBF0FCAB +:1007600097FBF0F70CF1010CA4F89C701FFA8CFCBD +:100770000CFB00F704F17001A4F89AC0B7F5C84F5C +:10078000C4BFACF1010CA1F82AC0B5FBF0FC0CF120 +:10079000010CA1F830C000F5802C0CF5EE3CACF15A +:1007A0000105B5FBF0FCA1F820C0CD8B05FB00FCDA +:1007B000BCFBF0F0C8830846217B01F01F012173C8 +:1007C0004676002104EB010C9CF808C003EA5C05A6 +:1007D000ACEB050C0CF0FF0C0CF0330502EA9C0CA2 +:1007E000AC4445180CEB1C1C0CF00F0C85F814C025 +:1007F000457E491CAC44C9B280F819C00529E1D333 +:100800000CF0FF00252898BFBDE8F081FFDFBDE8B0 +:10081000F08100BFB4F8B011B4F8B4316288A4F824 +:100820009860B4F89CC0DB000CFB02FCB3FBF1F356 +:100830009CFBF1FC5B1CA4F89CC09BB203FB01FC7D +:1008400004F17000A4F89A30BCF5C84FC4BF5B1E19 +:100850004385B5FBF1F35B1C0386438C01EBC303BB +:100860005B1EB3FBF1F30384C38B5A43B2FBF1F17C +:10087000C183BDE8F0812DE9F04104460025A1B314 +:1008800055234FF4FA464FF0330C01297DD002294D +:1008900000F0E080032918BFBDE8F08104F170008A +:1008A000217B01F01F01217384F889500021621817 +:1008B000127A03EA5205521BD2B202F033050CEA57 +:1008C00092022A44451802EB121202F00F022A7516 +:1008D000457E491C2A44C9B242760529E7D3D0B2E5 +:1008E000252898BFBDE8F081B1E0FFE704F170066C +:1008F000802200213046FDF7CAFDB571F5713573D0 +:100900007573F57335747571B576F576212086F8B3 +:100910004000492086F84100FE2086F874002688B1 +:10092000702200212046FDF7B2FD2680012684F8C2 +:10093000646084F86560282084F86600002104F172 +:1009400030000DF066FE1B22A4F84E20A4F85020C3 +:10095000A4F85220A4F854204FF4A470A4F8560030 +:10096000A4F858006673A4F8F850202084F8FA0020 +:1009700084F8F050C4F8F45084F8245184F82551D8 +:1009800084F82E5184F82F5100E005E084F81451CA +:1009900084F82051BDE8F081618865480844B0FBC7 +:1009A000F1F0A4F890004BF68030A4F89200E288B1 +:1009B000A4F89850B4F89C70D2004F43B2FBF1F207 +:1009C00097FBF1F7521CA4F89C7092B202FB01F75E +:1009D00004F17000A4F89A20B7F5C84FC4BF521EA6 +:1009E0004285B6FBF1F2521C028601F5802202F527 +:1009F000EE32561EB6FBF1F20284C68B06FB01F204 +:100A0000B2FBF1F1C1830146207B00F01F0020738F +:100A10004D7600202218127A03EA5205521BD2B2F8 +:100A200002F033050CEA92022A440D1802EB12126E +:100A300002F00F022A754D7E401C2A44C0B24A764D +:100A40000528E7D3D0B2252898BFBDE8F081FFDFA5 +:100A5000BDE8F081D0F81811628804F1700348896C +:100A6000C989A4F89850B4F89CC0C9000CFB02FCDA +:100A7000B1FBF0F19CFBF0FC491CA4F89CC089B2CE +:100A800001FB00FCA4F89A10BCF5C84FC4BF491E76 +:100A90005985B6FBF0F1491C1986598C00EBC10150 +:100AA000491EB1FBF0F11984D98B5143B1FBF0F031 +:100AB000D883BDE8F0812DE9F003447E0CB1252CEC +:100AC00003D9BDE8F00312207047002A02BF0020BE +:100AD000BDE8F003704791F80DC01F260123154DA6 +:100AE0004FF00008BCF1000F7AD0BCF1010F1EBF1F +:100AF0001F20BDE8F0037047B0F800C00A7C8F7B70 +:100B000091F80F907A404F7C87EA090742EA072262 +:100B100082EA0C0C5FF000070CF0FF0999FAA9F9C2 +:100B20004FEA1C2C4FEA19699CFAACFC04E0000067 +:100B3000FFDB050053E4B36E4FEA1C6C49EA0C2C52 +:100B40000CEB0C1C7F1C9444FFB21FFA8CFC032F8F +:100B5000E2D38CEA020CFB4F0022ECFB0572120977 +:100B60006FF0240502FB05C2D2B201EBD2078276F8 +:100B700002F007053F7A03FA05F52F4218BFC27647 +:100B80007ED104FB0CF2120C521CD2B25FF00004B6 +:100B900000EB040C9CF814C094453CBFA2EB0C0283 +:100BA000D2B212D30D194FF0000C2D7A03FA0CF7C4 +:100BB0003D421CBF521ED2B2002A69D00CF1010C7A +:100BC0000CF0FF0CBCF1080FF0D304F1010C0CF099 +:100BD000FF04052CDCD33046BDE8F0037047FFE787 +:100BE00090F81AC00C7E474604FB02C2D54C4FF069 +:100BF000000CE2FB054C4FEA1C1C6FF024040CFBBC +:100C00000422D2B201EBD204827602F0070C247ADD +:100C100003FA0CFC14EA0C0F1FBFC2764046BDE875 +:100C2000F003704790F819C0B2FBFCF40CFB1422DF +:100C3000521CD2B25FF0000400EB040C9CF814C00C +:100C400094453CBFA2EB0C02D2B212D30D194FF067 +:100C5000000C2D7A03FA0CF815EA080F1CBF521E7F +:100C6000D2B272B10CF1010C0CF0FF0CBCF1080F08 +:100C7000F0D304F1010C0CF0FF04052CDCD3AAE73F +:100C800009E00CEBC401C1763846BDE8F0037047BB +:100C90000CEBC401C1764046BDE8F0037047AA4A98 +:100CA000016812681140A94A126811430160704737 +:100CB00030B4A749A44B00244FF0010C0A78521C11 +:100CC000D2B20A70202A08BF0C700D781A680CFA8C +:100CD00005F52A42F2D0097802680CFA01F1514078 +:100CE000016030BC704770B46FF01F02010C02EA63 +:100CF00090251F23A1F5AA4054381CBFA1F5AA4096 +:100D0000B0F1550009D0A1F52850AA381EBFA1F5B1 +:100D10002A40B0F1AA00012000D100204FF0000CC1 +:100D2000624601248CEA0106F6431643B6F1FF3F02 +:100D300011D005F001064FEA5C0C4CEAC63C03F00A +:100D4000010652086D085B08641C42EAC632162C84 +:100D5000E8DD70BC704770BC0020704790F804C09C +:100D60003CF01F011CBF0020704730B401785522B1 +:100D700002EA5103C91AC9B201F03304332303EA6A +:100D800091012144447801EB111102EA5405641BDE +:100D9000E4B204F0330503EA94042C4404EB141485 +:100DA00001F00F0104F00F040C448178C07802EACE +:100DB0005105491BC9B201F0330503EA91012944E9 +:100DC00001EB111101F00F01214402EA5004001B54 +:100DD000C0B200F0330403EA9000204400EB10108E +:100DE00000F00F00014402EA5C00ACEB0000C0B26E +:100DF00000F0330203EA9000104400EB101000F002 +:100E00000F00084401288CBF0120002030BC70472F +:100E10000A000ED00123012A0BDB491EC9B210F8CB +:100E200001C0BCF1000F01D0002070475B1C934251 +:100E3000F3DD01207047002A08BF70471144401EAF +:100E400012F0010F03D011F8013D00F8013F5208E4 +:100E500008BF704711F8013C437011F8023D00F8DB +:100E6000023F521EF6D1704770B58CB000F11004ED +:100E70001D4616460DF1FF3C5FF0080014F8012CEA +:100E80008CF8012014F8022D0CF8022F401EF5D129 +:100E900001F1100C6C460DF10F0108201CF8012C1B +:100EA0004A701CF8022D01F8022F401EF6D1204690 +:100EB00013F01CFB7EB16A1E04F130005FF00801E4 +:100EC00010F8013C537010F8023D02F8023F491E31 +:100ED000F6D10CB070BD08982860099868600A982F +:100EE000A8600B98E8600CB070BD38B505460C469C +:100EF000684607F03DFE002808BF38BD9DF9002078 +:100F00002272E07E607294F90A100020511A48BFE4 +:100F1000494295F82D308B42C8BF38BDFF2B08BF22 +:100F200038BDE17A491CC9B2E17295F82E30994278 +:100F300003D8A17A7F2918BF38BDA2720020E072C1 +:100F4000012038BD53E4B36E04620200086202005F +:100F5000740000200C2818BF0B2810D00D2818BFD3 +:100F60001F280CD0202818BF212808D0222818BFFD +:100F7000232804D024281EBF2628002070474FF0C5 +:100F8000010070470C2963D2DFE801F006090E1357 +:100F9000161B323C415C484E002A5BD058E0072AC1 +:100FA00018BF082A56D053E00C2A18BF0B2A51D07C +:100FB0004EE00D2A4ED04BE0A2F10F000C2849D98B +:100FC00046E023B1A2F110000B2843D940E0122AD9 +:100FD00018BF112A3ED090F8380020B1122A37D31A +:100FE0001A2A37D934E0162A32D31A2A32D92FE0F6 +:100FF000A2F10F0103292DD990F8380008B31B2A5C +:1010000028D925E0002B08BF042A21D122E013B102 +:10101000062A1FD01CE0012A1AD11BE01C2A1CBF83 +:101020001D2A1E2A16D013E01F2A18BF202A11D00D +:10103000212A18BF222A0DD0232A1CBF242A262A9F +:1010400008D005E013B10E2A04D001E0052A01D032 +:1010500000207047012070472DE9F0410D460446FD +:10106000866805F02FFA58B905F07EF840F236711F +:1010700004F061FDA060204605F024FA0028F3D0BA +:1010800095B13046A16805F067FD00280CDD2844C5 +:10109000401EB0FBF5F707FB05F1304604F04BFDB1 +:1010A000A0603846BDE8F0810020BDE8F08170B551 +:1010B0000446904228BF70BD101B64280BD325182E +:1010C0008D4206D8042105F07AFD00281CBF284671 +:1010D00070BD204670BD6420F1E711F00C0F13D0F5 +:1010E00001F0040100290DBF4022102296214FF487 +:1010F000167101F5BC71A0EB010388428CBF93FB14 +:10110000F2F0002080B27047022919BF6FF00D0184 +:1011100001EBD0006FF00E0101EB9000F2E7084404 +:1011200018449830002A14BF042100210844704755 +:1011300010B4002A14BF4FF429624FF4A472002B9C +:1011400019BF4FF429634FF0080C4FF4A4734FF00C +:10115000010C00280CBF0124002491F866001CF04B +:101160000C0F08BF0020D11808449830002C14BF81 +:1011700004210021084410BC704700280CBF012343 +:10118000002391F86600002BA0F6482000F50050DF +:1011900018BF04231844496A81422CBF0120002053 +:1011A00012F00C0118BF012131EA000014BF002029 +:1011B0000120704710B413680B66137813F00C030A +:1011C00018BF0123527812F00C0218BF012253EA13 +:1011D000020C04BF10BC7047002B0CBF4FF4A4736B +:1011E0004FF42963002A19BF4FF429624FF0080C0D +:1011F0004FF4A4724FF0010C00280CBF012400240E +:1012000091F866001CF00C0F08BF00201A4410442F +:101210009830002C14BF0422002210444A6A8242F3 +:1012200024BF10BC704791F860004FF0030230F00B +:101230000C0381F8603091F8610020F00C0081F817 +:10124000610008BF81F86020002808BF81F8612094 +:1012500010BC704710F0010F1CBF0120704710F048 +:10126000020F1CBF0220704710F0040018BF0820B6 +:1012700070472DE9F0470446174689464FF00108AC +:1012800008460DF0FAF8054648460DF0FAF810F059 +:10129000010F18BF012624D015F0010F18BF01233C +:1012A0002AD000BF56EA030108BF4FF0000810F033 +:1012B000070F08BF002615F0070F08BF002394F89A +:1012C0006400B0420CBF00203046387094F86510BE +:1012D000994208BF00237B70002808BF002B25D14E +:1012E00015E010F0020F18BF0226D5D110F0040F40 +:1012F00014BF08260026CFE715F0020F18BF0223FF +:10130000D0D115F0040F14BF08230023CAE74846C4 +:101310000DF0BDF8B4F87010401A00B247F6FE7137 +:10132000884201DC002801DC4FF0000816B1082ECD +:101330000CD018E094F86400012818BF022812D0DD +:1013400004281EBF0828FFDF032D0CD194F8C0012C +:1013500048B1B4F8C401012894F8640006D0082804 +:1013600001D0082038704046BDE8F087042818BF37 +:101370000420F7D1F5E7012814BF0228704710F0C8 +:101380000C0018BF0420704738B4CBB2C1F3072C4F +:10139000C1B2C0F30724012B07D0022B09D0042BC4 +:1013A00008BFBCF1040F2DD006E0BCF1010F03D142 +:1013B00028E0BCF1020F25D0012906D0022907D070 +:1013C000042908BF042C1DD004E0012C02D119E02F +:1013D000022C17D001EA0C0161F3070204EA0301B1 +:1013E00061F30F22D1B211F0020F18BF022310D007 +:1013F000C2F307218DF8003011F0020F18BF02214F +:101400001BD111E0214003EA0C03194061F30702EC +:10141000E6E711F0010F18BF0123E9D111F0040F25 +:1014200014BF08230023E3E711F0010F18BF0121C7 +:1014300003D111F0040118BF08218DF80110082B09 +:1014400001BF000C012804208DF80000BDF8000049 +:1014500038BC70474FF0000C082902D0042909D08D +:1014600011E001280FD10420907082F803C013808E +:1014700001207047012806D00820907082F803C030 +:1014800013800120704700207047162A10D12A22AD +:101490000C2818BF0D280FD04FF0230C1F280DD09B +:1014A00031B10878012818BF002805D0162805D0CA +:1014B00000207047012070471A70FBE783F800C0D6 +:1014C000F8E7012908D002290BD0042912BF082906 +:1014D00040F6A660704707E0002804BF40F2E240F3 +:1014E000704740F6C410704700B5FFDF40F2E2409D +:1014F00000BD00000178406829B190F82C1190F8E7 +:101500008C0038B901E001F0BDBD19B1042901D04A +:10151000012070470020704770B50C460546062133 +:1015200002F0C4FC606008B1002006E007212846F4 +:1015300002F0BCFC606018B101202070002070BD7A +:10154000022070BD2DE9FC470C4606466946FFF7B0 +:10155000E3FF00287DD19DF8000050B1FDF7EEF8C3 +:10156000B0427CD0214630460AF008FC002873D1F6 +:101570002DE00DF097F9B04271D02146304612F0BF +:10158000B6FA002868D1019D95F8F00022E001200C +:1015900000E00020804695F839004FF0010A4FF036 +:1015A0000009F0B195F83A0080071AD584F8019047 +:1015B00084F800A084F80490E68095F83B1021722E +:1015C000A98F6181E98FA18185F8399044E0019D5F +:1015D00095F82C0170350028DBD1287F0028D8D061 +:1015E000D5E7304602F0A5FD070000D1FFDF384601 +:1015F00001F0B5FF40B184F801900F212170E68021 +:10160000208184F804A027E0304602F080FD070026 +:1016100000D1FFDFB8F1000F21D0384601F0F7FF0D +:10162000B8B19DF8000038B90198D0F81801418888 +:10163000B14201D180F80090304607F00DFF84F8E8 +:1016400001900C21217084F80490E680697F21725A +:1016500000E004E085F81C900120BDE8FC87002034 +:10166000FBE71CB56946FFF757FF00B1FFDF68468F +:1016700001F014FDFE4900208968A1F8F2001CBDAC +:101680002DE9FC4104460E46062002F0B7FB054654 +:10169000072002F0B3FB2844C7B20025A8463E4409 +:1016A00017E02088401C80B22080B04202D3404620 +:1016B000A4F8008080B2B84204D3B04202D2002025 +:1016C000BDE8FC816946FFF727FF0028F8D06D1CB4 +:1016D000EDB2AE42E5D84FF6FF7020801220EFE762 +:1016E00038B54FF6FF70ADF800000DE00621BDF8EB +:1016F000000002F0EDFB04460721BDF8000002F0F7 +:10170000E7FB0CB100B1FFDF00216846FFF7B8FF2F +:101710000028EBD038BD70B507F00CFF0BF034FF9C +:10172000D44C4FF6FF76002526836683D2A0257021 +:1017300001680079A4F14002657042F8421FA11CC3 +:101740001071601C12F0EFFA1B2020814FF4A4717D +:101750006181A081E18107212177617703212174D3 +:10176000042262746082A082A4F13E00E1820570CE +:101770004680BF480C300570A4F11000057046800B +:1017800084F8205070BD70B5B94C16460D466060A7 +:10179000217007F047FEFFF7A3FFFFF7BCFF20789B +:1017A0000FF0BDFFB6480DF0D0F92178606812F057 +:1017B0005FFA20780BF0DCF8284608F0AFFEB0485E +:1017C000FCF7C7FF217860680AF0B2FB3146207849 +:1017D00012F024FDBDE870400BF0D6BE10B5012418 +:1017E0000AB1002010BD21B1012903D000242046F8 +:1017F00010BD02210CF024FDF9E710B50378044672 +:10180000002B406813460A46014609D05FF00100EC +:10181000FFF78EFC6168496A884203D9012010BD38 +:101820000020F5E7002010BD2DE9F04117468A7829 +:101830001E46804642B11546C87838B1044669074D +:1018400006D52AB1012104E00725F5E70724F6E7CC +:101850000021620702D508B1012000E0002001420A +:1018600006D0012211464046FFF7C7FF98B93DE078 +:1018700051B1002201214046FFF7BFFF58B9600770 +:1018800034D50122114620E060B1012200214046FA +:10189000FFF7B3FF10B10920BDE8F081680725D537 +:1018A000012206E068074FEA44700AD5002814DBDD +:1018B000002201214046FFF7A0FFB8B125F0040542 +:1018C00014E0002812DA012200214046FFF795FFBC +:1018D00060B100BF24F0040408E001221146404634 +:1018E000FFF78BFF10B125F00405F3E73D7034706E +:1018F0000020D1E770B58AB0044600886946FFF73A +:101900000BFE002806D1A08830B1012804D002289F +:1019100002D012200AB070BD04AB03AA214668466B +:10192000FFF782FF0500F5D19DF800100120002689 +:101930000029019906D081F8C101019991F80C1292 +:10194000B1BB2DE081F82F01019991F8561139B9F9 +:10195000019991F82E1119B9019991F8971009B1CF +:101960003A2519E00199059681F82E01019A9DF812 +:101970000C0082F83001019B9DF8102083F8312182 +:10198000A388019CA4F832318DF814008DF815203D +:1019900005AA0020FFF70EFC019880F82F6126E0D1 +:1019A000019991F8C01119B9019991F8971009B1ED +:1019B0003A2519E00199059681F8C00101989DF832 +:1019C0000C2080F8C221019B9DF8100083F8C30110 +:1019D000A388019CA4F8C4318DF814208DF815005B +:1019E00005AA0120FFF7E6FB019880F8C1612846AF +:1019F00090E710B504460020A17801B90120E278F3 +:101A00000AB940F0020001F058FB002803D120463B +:101A1000BDE810406EE710BD70B5044691F8650052 +:101A200091F866300D4610F00C0F00D1002321898B +:101A3000A088FFF774FB696A814229D2401A401CD2 +:101A4000A1884008091A8AB2A2802189081A208137 +:101A5000668895F864101046FFF73FFB864200D277 +:101A600030466080E68895F8651020890AE000001D +:101A70007800002018080020FFFFFFFF1F00000073 +:101A8000D8060020FFF729FB864200D23046E080CE +:101A900070BDF0B585B00D46064603A9FFF73CFDC5 +:101AA00000282DD19DF80C0060B300220499FB2082 +:101AB000B1F84E30FB2B00D30346B1F85040FB2069 +:101AC000FB2C00D30446DFF85CC59CE88110009035 +:101AD0000197CDF808C0ADF80230ADF80640684671 +:101AE000FFF79AFF6E80BDF80400E880BDF808009B +:101AF0006881BDF80200A880BDF80600288100209A +:101B000005B0F0BD0122D1E72DE9F04186B00446D1 +:101B100000886946FFF700FD002876D12189E0881A +:101B200001F0E4FA002870D1A188608801F0DEFAA3 +:101B300000286AD12189E08801F0CFFA002864D119 +:101B4000A188608801F0C9FA07005ED1208802A947 +:101B5000FFF79FFF00B1FFDFBDF81010628809207A +:101B6000914252D3BDF80C10E28891424DD3BDF89A +:101B70001210BDF80E2023891144A2881A44914204 +:101B800043D39DF80010019D4FF00008012640F658 +:101B9000480041B185F8B761019991F8F81105F550 +:101BA000DB7541B91AE085F82561019991F84A1170 +:101BB00005F5927509B13A2724E0E18869806188CA +:101BC000E9802189814200D30146A980A188814210 +:101BD00000D208462881012201990FE0E18869803E +:101BE0006188E9802189814200D30146A980A188CA +:101BF000814200D208462881019900222846FFF739 +:101C00000BFF2E7085F80180384606B044E67BE76E +:101C100070B504460CF0FCFDB0B12078182811D145 +:101C2000207901280ED1E088062102F03FF9040056 +:101C300008D0208807F010FC2088062102F048F91F +:101C400000B1FFDF012070BDF74D28780028FAD0E1 +:101C5000002666701420207020223146201DFCF7DB +:101C600016FC022020712E70ECE710B50446FCF73C +:101C7000DBFC002813D0207817280FD1207968B119 +:101C8000E088072102F012F940B1008807F0E4FB78 +:101C9000E088072102F01CF900B1FFDF012010BD30 +:101CA0002DE9F0475FEA000800D1FFDFDE4802219E +:101CB0001A308146FFF7E4FC00B1FFDFDA4C062062 +:101CC000678B02F09BF80546072002F097F828443E +:101CD000C5B2681CC6B2608BB04203D14046FFF764 +:101CE000C4FF58B9608BA84203D14046FFF790FF6C +:101CF00020B9608B4146FFF725FC38B1404601F022 +:101D000003FA0028E7D10120BDE8F0870221484608 +:101D1000FFF7B6FC10B9608BB842DCD14046BDE895 +:101D2000F04712F0C1BA10B501F053F908B10C2018 +:101D300010BD0BF07DFC002010BD10B504460078EE +:101D400018B1012801D0122010BD01F053F920B1C3 +:101D50000BF0C0FD08B10C2010BD207801F013F984 +:101D6000E21D04F11703611CBDE810400BF0DABC62 +:101D700010B5044601F02DF908B10C2010BD2078F3 +:101D800028B1012803D0FF280BD0122010BD01F08C +:101D9000FAF8611C0BF00CFC08B1002010BD072004 +:101DA00010BD01200BF03EFCF7E710B50BF095FDE0 +:101DB00008B1002010BD302010BD10B5044601F060 +:101DC00019F908B10C2010BD20460BF080FD002051 +:101DD00010BD10B501F00EF920B10BF07BFD08B17C +:101DE0000C2010BD0BF0F6FC002010BDFF2181700F +:101DF0004FF6FF7181808D4949680A7882718A881F +:101E000002814988418101214170002070477CB5E1 +:101E10000025022A19D015DC12F10C0F15D009DCAF +:101E200012F1280F11D012F1140F0ED012F1100F71 +:101E300011D10AE012F1080F07D012F1040F04D0FB +:101E40004AB902E0D31E052B05D8012806D0022886 +:101E500008D003280AD0122528467CBD1046FDF77D +:101E600013F8F9E710460CF06BFEF5E70846144648 +:101E70006946FFF751FB08B10225EDE79DF8000028 +:101E80000198002580F86740E6E710B51346012267 +:101E9000FEF7EAFF002010BD10B5044610F02FFA3F +:101EA000052804D020460FF029FC002010BD0C208E +:101EB00010BD10B5044601F09DF808B10C2010BD0E +:101EC0002146002007F037FB002010BD10B5044666 +:101ED0000FF0A3FC50B108F0A6FD38B1207808F04F +:101EE00029FB20780DF04DF9002010BD0C2010BD0D +:101EF00010B5044601F07EF808B10C2010BD214653 +:101F0000012007F018FB002010BD38B504464FF63D +:101F1000FF70ADF80000A079E179884216D02079F1 +:101F2000FCF7E3FA90B16079FCF7DFFA70B10022B8 +:101F3000A079114612F0A0FD40B90022E0791146C7 +:101F400012F09AFD10B9207A072801D9122038BD65 +:101F500008F076FD60B910F0D2F948B90021684662 +:101F6000FFF78EFB20B1204606F044F9002038BD73 +:101F70000C2038BD2DE9FC41817805461A2925D071 +:101F80000EDC16292ED2DFE801F02D2D2D2D2D216E +:101F90002D2D2D2D2D2D2D2D2D2D2D2D2D21212195 +:101FA0002A291FD00BDCA1F11E010C291AD2DFE86F +:101FB00001F019191919191919191919190D3A399D +:101FC00004290FD2DFE801F00E020E022888B0F5D6 +:101FD000706F07D201276946FFF79EFA20B10220F1 +:101FE000BDE8FC811220FBE79DF8000000F0D2FF65 +:101FF000019C10B104F58A7401E004F5C6749DF8E3 +:10200000000000F0C7FF019E10B106F2151601E0B6 +:1020100006F28D166846FFF76DFA08B1207838B1E0 +:102020000C20DDE70C620200180800207800002078 +:102030002770A8783070684601F030F80020CFE7AC +:102040007CB50D466946FFF767FA002618B12E6089 +:102050002E7102207CBD9DF8000000F09BFF019CCA +:102060009DF80000703400F095FF019884F84260FC +:1020700081682960017B297194F842100029F5D10B +:1020800000207CBD10B5044600F0B4FF20B10BF079 +:1020900021FC08B10C2010BD207800F074FFE2791B +:1020A000611C0BF093FD08B1002010BD022010BD93 +:1020B00010B5886E60B1002241F8682F0120CA7106 +:1020C0008979884012F0CCFC002800D01F2010BD78 +:1020D0000C2010BD1CB50C466946FFF71DFA002800 +:1020E00009D19DF8000000280198B0F8700000D0D8 +:1020F000401C208000201CBD1CB504460088694699 +:10210000FFF70AFA08B102201CBD606828B1DDE9BA +:102110000001224601F04CF81CBDDDE90001FFF78B +:10212000C7FF1CBD70B51C460D4618B1012801D073 +:10213000122070BD1946104601F078F830B12146E2 +:10214000284601F07DF808B1002070BD302070BD38 +:1021500070B5044600780E46012804D018B1022854 +:1021600001D0032840D1607828B1012803D002288B +:1021700001D0032838D1E07B10B9A078012833D1F1 +:10218000A07830F005012FD110F0050F2CD0628916 +:10219000E188E0783346FFF7C5FF002825D1A07815 +:1021A00005281DD16589A289218920793346FFF749 +:1021B000B9FF002819D1012004EB40014A891544D8 +:1021C0002218D378927893420ED1CA8889888A429D +:1021D0000AD1401CC0B20228EED3E088A84203D343 +:1021E000A07B08B1072801D9122070BD002070BD66 +:1021F00010B586B0044600F0E1FE10B10C2006B028 +:1022000010BD022104F10A0001F02FF8A0788DF82A +:102210000800A0788DF8000060788DF80400207820 +:102220008DF80300A07B8DF80500E07B00B1012054 +:102230008DF80600A078C10717D0E07801F00CF8FF +:102240008DF80100E088ADF80A006089ADF80C0057 +:10225000A078400716D5207900F0FEFF8DF8020027 +:102260002089ADF80E00A0890AE040070AD5E07881 +:1022700000F0F2FF8DF80200E088ADF80E006089F2 +:10228000ADF8100002A80FF0D4FA0028B7D16846C4 +:102290000CF07CFFB3E710B504460121FFF758FFAF +:1022A000002803D12046BDE81040A1E710BD027808 +:1022B000012A01D0BAB118E042783AB1012A05D01A +:1022C000022A12D189B1818879B100E059B14188DF +:1022D00049B1808838B101EB8101490000EB8000F1 +:1022E000B1EB002F01D2002070471220704770B56B +:1022F000044600780D46012809D010F000F80528A2 +:1023000003D00FF0A6F9002800D00C2070BD0CF00F +:102310000AFE88B10CF01CFE0CF018FF0028F5D165 +:1023200025B160780CF0ACFE0028EFD1A188608860 +:10233000BDE870400FF0A3BA122070BD10B504467E +:102340000121FFF7B4FF002804D12046BDE810406A +:102350000121CCE710BDF0B5871FDDE9056540F62A +:102360007B44A74213D28F1FA74210D288420ED8B7 +:10237000B2F5FA7F0BD2A3F10A00241FA04206D2C5 +:10238000521C4A43B2EB830F01DAAE4201D900205E +:10239000F0BD0120F0BD2DE9FC47477A894604468F +:1023A00017F0050F7ED0F8087CD194F83A0008B9F0 +:1023B000012F77D10025A8462E46F90789F0010A9A +:1023C00019D0208A514600F031FFE8B360895146A8 +:1023D00000F036FFC0B3208A6189884262D8A18E9E +:1023E000E08DCDE90001238D628CA18BE08AFFF79F +:1023F000B2FF48B30125B8070ED504EB4500828E25 +:10240000C18DCDE90012038D428C818BC08AFFF70C +:10241000A2FFC8B1A8466D1C78071ED504EB45067F +:102420005146308A00F002FF70B17089514600F0C9 +:1024300007FF48B1308A7189884253D8B18EF08D38 +:10244000CDE90001338D00E00BE0728CB18BF08A96 +:10245000FFF781FF28B12E466D1CB9F1000F03D0A4 +:1024600030E03020BDE8FC87F80707D0780705D5B5 +:1024700004EB460160894989884233D1228A0121CF +:102480001BE0414503D004EB4100008A024404EB09 +:102490004100C38A868AB34224D1838B468BB342E0 +:1024A00020D100E01EE0438C068CB3421AD1038D8C +:1024B000C08C834216D1491CC9B2A942E1D36089BC +:1024C00090420FD3207810B101280BD102E0A07800 +:1024D0000028F9D1607838B1012805D0022803D04E +:1024E000032801D01220BDE70020BBE7002152E7FE +:1024F0000178C90702D0406811F0A9BE11F076BE7C +:1025000010B50078012800D00020FCF7B8FC0020AE +:1025100010BD2DE9F0478EB00D46AFF6A422D2E9EA +:102520000092014690462846FFF735FF06000CD181 +:1025300000F044FD40B9FE4F387828B90CF0B2F9EC +:10254000A0F57F41FF3903D00C200EB0BDE8F08725 +:10255000032105F1100000F088FEF54809AA3E3875 +:102560000990F4480A90F248062110380B900CA804 +:1025700001F06AFC040037D00021FEF77CF904F179 +:1025800030017B8ABA8ACB830A84797C0091BA466F +:102590003B7CBA8A798A208801F044FD00B1FFDFD4 +:1025A000208806F058FF218804F10E0000F02CFD71 +:1025B000E1A004F1120700680590032105A804F0CA +:1025C0006DFF002005A90A5C3A54401CC0B20328E4 +:1025D000F9D3A88B6080688CA080288DE080687A11 +:1025E000410703D508270AE00920AEE7C10701D05B +:1025F000012704E0800701D5022700E000273A46C2 +:10260000BAF8160011460FF0CFF90146A062204635 +:102610000FF0D8F93A4621460020FEF7AEFD00B98A +:102620000926C34A21461C320020FEF7C3FD0027BD +:1026300084F8767084F87770A87800F0A4FC60764F +:10264000D5F80300C4F81A00B5F80700E083C4F811 +:10265000089084F80C80012084F8200101468DF850 +:102660000070684604F01AFF9DF8000000F00701B2 +:10267000C0F3C1021144C0F3401008448DF80000BB +:10268000401D2076092801D20830207601212046FD +:10269000FEF7F1F868780CF051FCEEBBA9782878C9 +:1026A000EA1C0CF01EFC48B10CF052FCA97828780A +:1026B000EA1C0CF0BFFC060002D052E0122650E0EB +:1026C000687A00F005010020CA0700D001208A07BF +:1026D00001D540F00200490701D540F008000CF098 +:1026E000E9FB06003DD1214603200CF0CDFC06009D +:1026F00037D10CF0D2FC060033D1697A01F0050124 +:102700008DF80810697AC90708D06889ADF80A0001 +:10271000288AADF80C0000E023E00120697A8A07DE +:1027200000D5401C490707D505EB40004189ADF8AD +:102730000E10008AADF8100002A80FF07AF80646D5 +:1027400095F83A0000B101200CF0C6FB4EB90CF030 +:10275000FDFC060005D1A98F20460FF00BF80600FE +:1027600008D0208806F078FE2088062101F0B0FB12 +:1027700000B1FFDF3046E8E601460020C9E638B583 +:102780006B48007878B90FF0BAFD052805D00CF039 +:1027900089F8A0F57F41FF3905D068460FF0B3F8FE +:1027A000040002D00CE00C2038BD0098008806F030 +:1027B00053FE00980621008801F08AFB00B1FFDF7C +:1027C000204638BD1CB582894189CDE900120389B4 +:1027D000C28881884088FFF7BEFD08B100201CBD7B +:1027E00030201CBD70B50546FFF7ECFF00280ED168 +:1027F0002888062101F05AFB040007D000F042FCB3 +:1028000020B1D4F81801017831B901E0022070BD7F +:10281000D4F86411097809B13A2070BD052181719D +:10282000D4F8181100200881D4F81811A88848811C +:10283000D4F81811E8888881D4F818112889C8813B +:10284000D4F81801028941898A4204D88279082A79 +:1028500001D88A4201D3122070BD29884180D4F862 +:10286000181102200870002070BD3EB50446FEF726 +:1028700075FAB0B12E480125A0F1400245702368D9 +:1028800042F8423F237900211371417069460620C6 +:1028900001F095FA00B1FFDF684601F06EFA10B161 +:1028A0000EE012203EBDBDF80440029880F8205191 +:1028B000684601F062FA18B9BDF80400A042F4D1EC +:1028C00000203EBD70B505460088062101F0EEFAF5 +:1028D000040007D000F0D6FB20B1D4F81811087816 +:1028E00030B901E0022070BDD4F86401007808B16D +:1028F0003A2070BDB020005D10F0010F22D0D5F855 +:1029000002004860D5F806008860D4F8180169898B +:1029100010228181D4F8180105F10C010E3004F564 +:102920008C74FBF78AFD216803200870288805E075 +:1029300018080020840000201122330021684880FC +:10294000002070BD0C2070BD38B504460078EF281B +:102950004DD86088ADF80000009800F097FC88B36F +:102960006188080708D4D4E9012082423FD8202A90 +:102970003DD3B0F5804F3AD8207B18B3072836D81E +:10298000607B28B1012803D0022801D003282ED172 +:102990004A0703D4022801D0032805D1A07B08B13F +:1029A000012824D1480707D4607D28B1012803D02D +:1029B000022801D003281AD1C806E07D03D50128DA +:1029C00015D110E013E0012801D003280FD1C8066B +:1029D00009D4607E012803D0022801D0032806D143 +:1029E000A07E0F2803D8E07E18B1012801D0122064 +:1029F00038BD002038BDF8B514460D46064608F02F +:102A00001FF808B10C20F8BD3046FFF79DFF0028E5 +:102A1000F9D1FCF73EFA2870B07554B9FF208DF853 +:102A2000000069460020FCF71EFA69460020FCF70A +:102A30000EFA3046BDE8F840FCF752B90022DAE75A +:102A40000078C10801D012207047FA4981F82000AF +:102A50000020704710B504460078C00704D1608894 +:102A600010B1FCF7D7F980B12078618800F001023D +:102A7000607800F02FFC002806D1FCF7B3F901467E +:102A80006088884203D9072010BD122010BD6168FC +:102A9000FCF7E9F9002010BD10B504460078C00726 +:102AA00004D1608810B1FBF78AFE70B1207861888C +:102AB00000F00102607800F00DFC002804D160886D +:102AC0006168FCF7C4F9002010BD122010BD7CB570 +:102AD000044640784225012808D8A078FBF767FE15 +:102AE00020B120781225012802D090B128467CBD63 +:102AF000FCF7DBF920B1A0880028F7D08028F5D8B2 +:102B0000FCF7DAF960B160780028EFD0207801286E +:102B100008D006F0C3FD044607F05DFC00287FD016 +:102B20000C207CBDFBF7F5FF10B9FCF7B7F990B3AB +:102B300007F086FF0028F3D1FBF700FEA0F57F41E8 +:102B4000FF39EDD1FCF707F8A68842F21070464332 +:102B5000A079FCF770F9FBF739FEF8B100220721E4 +:102B600001A801F071F9040058D0B3480021846035 +:102B70002046FDF72DFD2046FCF732FDAD4D04F15A +:102B800030006A8AA98AC2830184FBF726FE60B1FD +:102B9000E88A01210DE0FFE712207CBD31460020CC +:102BA00007F0CBFC88B3FFDF44E0FCF787F9014670 +:102BB000E88A07F091FD0146A0620022204606F057 +:102BC00070FDFBF70AFE38B9FCF778F9024621469A +:102BD0000120FEF7D2FAD0B1964A21461C320120DC +:102BE000FEF7E8FA687C00902B7CAA8A698A208824 +:102BF00001F018FA00B1FFDF208806F02CFC314606 +:102C0000204607F09AFC00B1FFDF13E008E007213F +:102C1000BDF8040001F05CF900B1FFDF09207CBDC4 +:102C200044B1208806F018FC2088072101F050F9F3 +:102C300000B1FFDF00207CBD002148E770B50D46E4 +:102C4000072101F033F9040003D094F88F0110B18B +:102C50000AE0022070BD94F87D00142801D01528E8 +:102C600002D194F8DC0108B10C2070BD1022294675 +:102C700004F5C870FBF7E1FB012084F88F01002008 +:102C800070BD10B5072101F011F918B190F88F113E +:102C900011B107E0022010BD90F87D10142903D077 +:102CA000152901D00C2010BD022180F88F110020C1 +:102CB00010BD2DE9FC410C464BF6803212219442A6 +:102CC0001DD8E4B16946FEF727FC002815D19DF810 +:102CD000000000F05FF9019E9DF80000703600F0E2 +:102CE00059F9019DAD1C2F88224639463046FDF723 +:102CF00065FC2888B842F6D10020BDE8FC81084672 +:102D0000FBE77CB5044600886946FEF705FC002811 +:102D100010D19DF8000000F03DF9019D9DF80000E4 +:102D2000703500F037F90198A27890F82C10914294 +:102D300001D10C207CBD7F212972A9720021E9728A +:102D4000E17880F82D10217980F82E10A17880F894 +:102D50002C1000207CBD1CB50C466946FEF7DCFB40 +:102D600000280AD19DF8000000F014F9019890F8AD +:102D70008C0000B10120207000201CBD7CB50D46E8 +:102D800014466946FEF7C8FB002809D19DF80000EB +:102D900000F000F9019890F82C00012801D00C20D7 +:102DA0007CBD9DF8000000F0F5F8019890F87810CF +:102DB000297090F87900207000207CBD70B50D4618 +:102DC0001646072101F072F818B381880124C388E0 +:102DD000428804EB4104AC4217D842F210746343BA +:102DE000A4106243B3FBF2F2521E94B24FF4FA7293 +:102DF000944200D91446A54200D22C46491C641CBA +:102E0000B4FBF1F24A43521E91B290F8C8211AB9AC +:102E100001E0022070BD01843180002070BD10B53A +:102E20000C46072101F042F840B1022C08D91220CB +:102E300010BD000018080020780000200220F7E7ED +:102E400014F0010180F8FD10C4F3400280F8FC206A +:102E500004D090F8FA1009B107F054FC0020E7E71D +:102E6000017889B1417879B141881B290CD38188D7 +:102E70001B2909D3C188022906D3F64902680A65CD +:102E800040684865002070471220704710B504461E +:102E90000EF086FD204607F0D8FB0020C8E710B5ED +:102EA00007F0D6FB0020C3E72DE9F04115460F4699 +:102EB00006460122114638460EF076FD04460121F1 +:102EC000384607F009FC844200D20446012130460E +:102ED00000F065F806460121002000F060F8311886 +:102EE000012096318C4206D901F19600611AB1FB9E +:102EF000F0F0401C80B228800020BDE8F08110B5C1 +:102F0000044600F077F808B10C2091E7601C0AF045 +:102F100038FE207800F00100FBF718FE207800F062 +:102F200001000CF010F8002082E710B504460720DD +:102F300000F056FF08B10C207AE72078C00711D0C6 +:102F400000226078114611F097FD08B112206FE75A +:102F5000A06809F01DFB6078D4F8041009F021FB8B +:102F6000002065E7002009F013FB00210846F5E783 +:102F700010B505F036FE00205AE710B5006805F0E0 +:102F800084F8002054E718B1022801D001207047CE +:102F90000020704708B1002070470120704710B52D +:102FA000012904D0022905D0FFDF204640E7C000F8 +:102FB000503001E080002C3084B2F6E710B50FF0FD +:102FC0009EF9042803D0052801D0002030E7012015 +:102FD0002EE710B5FFF7F2FF10B10CF07BF828B91F +:102FE00007F02EFD20B1FBF78CFD08B101201FE793 +:102FF00000201DE710B5FFF7E1FF18B907F020FD2D +:10300000002800D0012013E72DE9FE4300250F46DC +:1030100080460A260421404604F069FA4046FDF73E +:103020003EFE062000F0EAFE044616E06946062051 +:1030300000F0C5FE0BE000BFBDF80400B84206D0AA +:103040000298042241460E30FBF7CAF950B1684697 +:1030500000F093FE0500EFD0641E002C06DD002D6D +:10306000E4D005E04046FDF723FEF5E705B9FFDFB4 +:10307000D8F80000FDF737FE761E01D00028C9D031 +:10308000BDE8FE8390F8F01090F88C0020B919B1DB +:10309000042901D0012070470020704701780029E1 +:1030A0000AD0416891F8FA20002A05D0002281F860 +:1030B000FA20406807F026BB704770B514460546F5 +:1030C000012200F01BF9002806D121462846BDE860 +:1030D0007040002200F012B970BDFB2802D8B1F593 +:1030E000296F01D911207047002070471B38E12853 +:1030F00006D2B1F5A47F03D344F29020814201D9D6 +:1031000012207047002070471FB55249403191F896 +:103110002010CA0702D102781D2A0AD08A0702D4D9 +:1031200002781C2A28D049073DD40178152937D0C8 +:1031300039E08088ADF8000002A9FEF7EDF900B192 +:10314000FFDF9DF80800FFF725FF039810F8601FC8 +:103150008DF8021040788DF803000020ADF80400CF +:1031600001B9FFDF9DF8030000B9FFDF6846FEF7F5 +:1031700040FCD8B1FFDF19E08088ADF800004FF4C3 +:103180002961FB20ADF80410ADF80200ADF806008F +:10319000ADF808106846FEF73AFD38B1FFDF05E0EC +:1031A000807BC00702D0002004B041E60120FBE78D +:1031B000F8B50746508915460C4640B1B0F5004FAA +:1031C00005D20022A878114611F056FC08B1122051 +:1031D000F8BDA06E04F1700630B1A97894F86E00C5 +:1031E000814201D00C20F8BD012184F86F10A9782C +:1031F00084F86E106968A1666989A4F86C10288942 +:10320000B084002184F86F1028886946FEF762FFB9 +:10321000B08CBDF80010081A00B2002804DD214669 +:103220003846FEF745FFDDE70020F8BD042803D34C +:1032300021B9B0F5804F01D90020704701207047B7 +:10324000042803D321B9B0F5804F01D9002070477D +:1032500001207047D8070020012802D018B10020B3 +:103260007047022070470120704710B500224FF4CC +:10327000C84408E030F81230A34200D9234620F8B1 +:103280001230521CD2B28A42F4D3D1E580B2C106C8 +:103290000BD401071CD481064FEAC07101D5B9B91E +:1032A00000E099B1800713D410E0410610D48106E4 +:1032B0000ED4C1074FEA807104D0002902DB400719 +:1032C00004D405E0010703D4400701D4012070476E +:1032D0000020704770B50C460546FF2904D8FBF75F +:1032E0007CFA18B11F2C01D9122070BD2846FBF7BB +:1032F0005EFA08B1002070BD422070BD0AB1012203 +:1033000000E00222024202D1C80802D109B1002025 +:10331000704711207047000030B5058825F400443F +:1033200021448CB24FF4004194420AD2121B92B253 +:103330001B339A4201D2A94307E005F4004121431F +:1033400003E0A21A92B2A9431143018030BD0844A0 +:10335000083050434A31084480B2704770B51D466A +:1033600016460B46044629463046049AFFF7EFFFFF +:103370000646B34200D2FFDF282200212046FBF799 +:1033800086F84FF6FF70A082283EB0B26577608065 +:10339000B0F5004F00D9FFDF618805F13C008142A4 +:1033A00000D2FFDF60880835401B343880B22080AF +:1033B0001B2800D21B2020800020A07770BD8161D7 +:1033C000886170472DE9F05F0D46C188044600F121 +:1033D0002809008921F4004620F4004800F063FB2E +:1033E00010B10020BDE8F09F4FF0000A4FF0010B34 +:1033F000B0450CD9617FA8EB0600401A0838854219 +:1034000019DC09EB06000021058041801AE0608884 +:10341000617F801B471A083F0DD41B2F00DAFFDFA6 +:10342000BD4201DC294600E0B9B2681A0204120C60 +:1034300004D0424502DD84F817A0D2E709EB06006C +:103440000180428084F817B0CCE770B5044600F1E3 +:103450002802C088E37D20F400402BB1104402888C +:10346000438813448B4201D2002070BD00258A425C +:1034700002D30180458008E0891A0904090C4180C3 +:1034800003D0A01D00F01FFB08E0637F0088083315 +:10349000184481B26288A01DFFF73EFFE575012048 +:1034A00070BD70B5034600F12804C588808820F4FB +:1034B00000462644A84202D10020188270BD988997 +:1034C0003588A84206D3401B75882D1A2044ADB21A +:1034D000C01E05E02C1AA5B25C7F20443044401D7C +:1034E0000C88AC4200D90D809C8924B10024147052 +:1034F0000988198270BD0124F9E770B5044600F10E +:103500002801808820F400404518208A002825D012 +:10351000A189084480B2A08129886A881144814227 +:1035200000D2FFDF2888698800260844A1898842E4 +:1035300012D1A069807F2871698819B1201D00F01F +:10354000C2FA08E0637F28880833184481B2628891 +:10355000201DFFF7E1FEA6812682012070BD2DE926 +:10356000F041418987880026044600F12805B942C8 +:1035700019D004F10A0800BF21F400402844418812 +:1035800019B1404600F09FFA08E0637F00880833D5 +:10359000184481B262884046FFF7BEFE761C6189FE +:1035A000B6B2B942E8D13046BDE8F0812DE9F0412C +:1035B00004460B4627892830A68827F40041B4F832 +:1035C0000A8001440D46B74201D10020ECE70AB160 +:1035D000481D106023B1627F691D1846FAF72DFF60 +:1035E0002E88698804F1080021B18A1996B200F08A +:1035F0006AFA06E0637F62880833991989B2FFF797 +:103600008BFE474501D1208960813046CCE7818817 +:10361000C088814201D10120704700207047018994 +:103620008088814201D1012070470020704770B529 +:103630008588C38800F1280425F4004223F4004162 +:1036400014449D421AD08389058A5E1925886388AF +:10365000EC18A64214D313B18B4211D30EE0437F72 +:1036600008325C192244408892B2801A80B2233317 +:10367000984201D211B103E08A4201D1002070BD0D +:10368000012070BD2DE9F0478846C18804460089B5 +:1036900021F4004604F1280720F4004507EB060951 +:1036A00000F001FA002178BBB54204D9627FA81B63 +:1036B000801A002503E06088627F801B801A08382A +:1036C00023D4E28962B1B9F80020B9F802303BB1E5 +:1036D000E81A2177404518DBE0893844801A09E070 +:1036E000801A217740450ADB607FE1890830304449 +:1036F00039440844C01EA4F81280BDE8F08745454F +:1037000003DB01202077E7E7FFE761820020F4E791 +:103710002DE9F74F044600F12805C088884620F4BB +:10372000004A608A05EB0A0608B1404502D2002033 +:10373000BDE8FE8FE08978B13788B6F8029007EBD4 +:103740000901884200D0FFDF207F4FF0000B50EAD4 +:10375000090106D088B33BE00027A07FB94630714D +:10376000F2E7E18959B1607F2944083050440844A8 +:10377000B4F81F1020F8031D94F821108170E2891D +:1037800007EB080002EB0801E1813080A6F802B0E7 +:1037900002985F4650B1637F30880833184481B285 +:1037A0006288A01DFFF7B8FDE78121E0607FE18915 +:1037B00008305044294408442DE0FFE7E089B4F87C +:1037C0001F102844C01B20F8031D94F8211081709D +:1037D00009EB0800E28981B202EB0800E081378042 +:1037E00071800298A0B1A01D00F06DF9A4F80EB090 +:1037F000A07F401CA077A07D08B1E088A08284F85B +:1038000016B000BFA4F812B084F817B001208FE7FB +:10381000E0892844C01B30F8031DA4F81F108078ED +:1038200084F82100EEE710B5818800F1280321F427 +:1038300000442344848AC288A14212D0914210D00D +:10384000818971B9826972B11046FFF7E8FE50B9FB +:103850001089283220F400401044197900798842F8 +:1038600001D1002010BD184610BD00F12803407F93 +:1038700008300844C01E1060088808B9DB1E1360B9 +:1038800008884988084480B270472DE9F04100F16A +:103890002806407F1C4608309046431808884D880B +:1038A000069ADB1EA0B1C01C80B2904214D9801AC7 +:1038B000A04200DB204687B298183A464146FAF704 +:1038C0008FFD002816D1E01B84B2B844002005E02B +:1038D000ED1CADB2F61EE8E7101A80B20119A9423C +:1038E00006D8304422464146BDE8F041FAF778BD9B +:1038F0004FF0FF3058E62DE9F04100F12804407FF9 +:103900001E46083090464318002508884F88069ABE +:10391000DB1E90B1C01C80B2904212D9801AB04216 +:1039200000DB304685B299182A464046FAF785FDF5 +:10393000701B86B2A844002005E0FF1CBFB2E41E45 +:10394000EAE7101A80B28119B94206D82118324626 +:103950004046FAF772FDA81985B2284624E62DE9FB +:10396000F04100F12804407F1E460830904643187D +:10397000002508884F88069ADB1E90B1C01C80B2D3 +:10398000904212D9801AB04200DB304685B29818B6 +:103990002A464146FAF751FD701B86B2A844002022 +:1039A00005E0FF1CBFB2E41EEAE7101A80B28119DD +:1039B000B94206D8204432464146FAF73EFDA819DE +:1039C00085B22846F0E5401D704710B5044600F169 +:1039D0002801C288808820F400431944904206D010 +:1039E000A28922B9228A12B9A28A904201D100206A +:1039F00010BD0888498831B1201D00F064F800200E +:103A00002082012010BD637F62880833184481B290 +:103A1000201DFFF781FCF2E70021C181017741827F +:103A2000C1758175704703881380C28942B1C2880D +:103A300022F4004300F128021A440A60C08970474A +:103A40000020704710B50446808AA0F57F41FF39F9 +:103A500000D0FFDFE088A082E08900B10120A075DE +:103A600010BD4FF6FF71818200218175704710B53E +:103A70000446808AA0F57F41FF3900D1FFDFA07D99 +:103A800028B9A088A18A884201D1002010BD012058 +:103A900010BD8188828A914201D1807D08B10020C9 +:103AA00070470120704720F4004221F400439A42FD +:103AB00007D100F4004001F40041884201D0012008 +:103AC00070470020704730B5044600880D4620F44A +:103AD0000040A84200D2FFDF21884FF40040884315 +:103AE0002843208030BD70B50C00054609D0082C55 +:103AF00000D2FFDF1DB1A1B2286800F044F8201DFC +:103B000070BD0DB100202860002070BD002102684A +:103B100003E093881268194489B2002AF9D100F0B1 +:103B200032B870B500260D460446082900D2FFDFE2 +:103B3000206808B91EE0044620688188A94202D0A6 +:103B400001680029F7D181880646A94201D10068A1 +:103B50000DE005F1080293B20022994209D32844EE +:103B6000491B026081802168096821600160206032 +:103B700000E00026304670BD00230B608A8002689A +:103B80000A600160704700234360021D01810260EA +:103B90007047F0B50F460188408815460C181E4640 +:103BA000AC4200D3641B3044A84200D9FFDFA01907 +:103BB000A84200D9FFDF3819F0BD2DE9F041884651 +:103BC00006460188408815460C181F46AC4200D3B3 +:103BD000641B3844A84200D9FFDFE019A84200D98D +:103BE000FFDF70883844708008EB0400BDE8F08186 +:103BF0002DE9F041054600881E461746841B88467D +:103C0000BC4200D33C442C8068883044B84200D980 +:103C1000FFDFA019B84200D9FFDF68883044688010 +:103C200008EB0400E2E72DE9F04106881D46044652 +:103C3000701980B2174688462080B84201D3C01B55 +:103C400020806088A84200D2FFDF7019B84200D9F6 +:103C5000FFDF6088401B608008EB0600C6E730B5D8 +:103C60000D460188CC18944200D3A41A408898428B +:103C700000D8FFDF281930BD2DE9F041C84D0446BA +:103C80009046A8780E46A04200D8FFDF05EB8607D5 +:103C9000B86A50F8240000B1FFDFB868002816D0D9 +:103CA000304600F044F90146B868FFF73AFF0500D6 +:103CB0000CD0B86A082E40F8245000D3FFDFB94872 +:103CC0004246294650F82630204698472846BDE807 +:103CD000F0812DE9F8431E468C1991460F460546A2 +:103CE000FF2C00D9FFDFB14500D9FFDFE4B200951A +:103CF0004DB300208046E81C20F00300A84200D00D +:103D0000FFDF4946DFF89892684689F8001089F885 +:103D1000017089F8024089F8034089F8044089F865 +:103D2000054089F8066089F80770414600F008F9F7 +:103D3000002142460F464B460098C01C20F003006D +:103D4000009012B10EE00120D4E703EB8106B062CF +:103D5000002005E0D6F828C04CF82070401CC0B206 +:103D6000A042F7D30098491C00EB8400C9B2009030 +:103D70000829E1D3401BBDE8F88310B50446EDF7F0 +:103D80008EFA08B1102010BD2078854A618802EBB8 +:103D9000800092780EE0836A53F8213043B14A1CC8 +:103DA0006280A180806A50F82100A060002010BDD0 +:103DB000491C89B28A42EED86180052010BD70B5D9 +:103DC00005460C460846EDF76AFA08B1102070BDAA +:103DD000082D01D3072070BD25700020608070BDC4 +:103DE0000EB56946FFF7EBFF00B1FFDF6846FFF74E +:103DF000C4FF08B100200EBD01200EBD10B5044661 +:103E0000082800D3FFDF6648005D10BD3EB50546BB +:103E100000246946FFF7D3FF18B1FFDF01E0641CFF +:103E2000E4B26846FFF7A9FF0028F8D02846FFF75C +:103E3000E5FF001BC0B23EBD59498978814201D9D6 +:103E4000C0B27047FF2070472DE9F041544B06295E +:103E500003D007291CD19D7900E0002500244FF6EE +:103E6000FF7603EB810713F801C00AE06319D7F866 +:103E700028E09BB25EF823E0BEF1000F04D0641C82 +:103E8000A4B2A445F2D8334603801846B34201D108 +:103E900000201CE7BDE8F041EEE6A0F57F43FF3BC4 +:103EA00001D0082901D300207047E5E6A0F57F4244 +:103EB000FF3A0BD0082909D2394A9378834205D9B1 +:103EC00002EB8101896A51F8200070470020704799 +:103ED0002DE9F04104460D46A4F57F4143F202006E +:103EE000FF3902D0082D01D30720F0E62C494FF00E +:103EF00000088A78A242F8D901EB8506B26A52F826 +:103F00002470002FF1D027483946203050F8252062 +:103F100020469047B16A284641F8248000F007F80F +:103F200002463946B068FFF727FE0020CFE61D495C +:103F3000403131F810004FF6FC71C01C084070474A +:103F40002DE9F843164E8846054600242868C01C13 +:103F500020F0030028602046FFF7E9FF315D484369 +:103F6000B8F1000F01D0002200E02A68014600925B +:103F700032B100274FEA0D00FFF7B5FD1FB106E093 +:103F800001270020F8E706EB8401009A8A6029687F +:103F9000641C0844E4B22860082CD7D3EBE6000088 +:103FA0003C0800201862020070B50E461D461146FE +:103FB00000F0D3F804462946304600F0D7F82044F4 +:103FC000001D70BD2DE9F04190460D4604004FF0F4 +:103FD000000610D00027E01C20F00300A04200D013 +:103FE000FFDFE5B141460020FFF77DFD0C3000EB1F +:103FF000850617B113E00127EDE7614F04F10C00CE +:10400000AA003C602572606000EB85002060002102 +:104010006068FAF73CFA41463868FFF764FD3046BD +:10402000BDE8F0812DE9FF4F554C804681B02068F6 +:104030009A46934600B9FFDF2068027A424503D9C9 +:10404000416851F8280020B143F2020005B0BDE8F4 +:10405000F08F5146029800F080F886B258460E99CB +:1040600000F084F885B27019001D87B22068A1465F +:1040700039460068FFF755FD04001FD06780258092 +:104080002946201D0E9D07465A4601230095FFF73D +:1040900065F92088314638440123029ACDF800A002 +:1040A000FFF75CF92088C1193846FFF788F9D9F87D +:1040B00000004168002041F82840C7E70420C5E718 +:1040C00070B52F4C0546206800B9FFDF2068017AE3 +:1040D000A9420DD9426852F8251049B1002342F88F +:1040E00025304A880068FFF747FD2168087A06E016 +:1040F00043F2020070BD4A6852F820202AB9401EDF +:10410000C0B2F8D20868FFF701FD002070BD70B59D +:104110001B4E05460024306800B9FFDF3068017A85 +:10412000A94204D9406850F8250000B1041D20467A +:1041300070BD70B5124E05460024306800B9FFDF2F +:104140003068017AA94206D9406850F8251011B1AB +:1041500031F8040B4418204670BD10B50A46012101 +:10416000FFF7F5F8C01C20F0030010BD10B50A469B +:104170000121FFF7ECF8C01C20F0030010BD000087 +:104180008C00002070B50446C2F110052819FAF71A +:1041900054F915F0FF0109D0491ECAB28020A0547D +:1041A0002046BDE870400021FAF771B970BD30B506 +:1041B00005E05B1EDBB2CC5CD55C6C40C454002BCC +:1041C000F7D130BD10B5002409E00B78521E44EA47 +:1041D000430300F8013B11F8013BD2B2DC09002A8D +:1041E000F3D110BD2DE9F04389B01E46DDE9107909 +:1041F00090460D00044622D002460846F949FDF7D4 +:1042000044FE102221463846FFF7DCFFE07B000623 +:1042100006D5F44A3946102310320846FFF7C7FF87 +:10422000102239464846FFF7CDFFF87B000606D539 +:10423000EC4A4946102310320846FFF7B8FF102217 +:1042400000212046FAF723F90DE0103EB6B208EB44 +:104250000601102322466846FFF7A9FF224628469A +:104260006946FDF712FE102EEFD818D0F2B2414683 +:104270006846FFF787FF10234A46694604A8FFF700 +:1042800096FF1023224604A96846FFF790FF2246B6 +:1042900028466946FDF7F9FD09B0BDE8F083102313 +:1042A0003A464146EAE770B59CB01E4605461346BD +:1042B00020980C468DF80800202219460DF10900BF +:1042C000FAF7BBF8202221460DF12900FAF7B5F8DC +:1042D00017A913A8CDE90001412302AA31462846B7 +:1042E000FFF780FF1CB070BD2DE9FF4F9FB014AEEB +:1042F000DDE92D5410AFBB49CDE9007620232031F4 +:104300001AA8FFF76FFF4FF000088DF808804FF0F4 +:1043100001098DF8099054F8010FCDF80A00A08822 +:10432000ADF80E0014F8010C1022C0F340008DF817 +:10433000100055F8010FCDF81100A888ADF8150050 +:1043400015F8010C2C99C0F340008DF8170006A851 +:104350008246FAF772F80AA8834610222299FAF7E1 +:104360006CF8A0483523083802AA40688DF83C80D4 +:10437000CDE900760E901AA91F98FFF733FF8DF84C +:1043800008808DF809902068CDF80A00A088ADF863 +:104390000E0014F8010C1022C0F340008DF810003C +:1043A0002868CDF81100A888ADF8150015F8010CA3 +:1043B0002C99C0F340008DF817005046FAF73DF8ED +:1043C000584610222299FAF738F8864835230838DB +:1043D00002AA40688DF83C90CDE900760E901AA9AB +:1043E0002098FFF7FFFE23B0BDE8F08FF0B59BB03B +:1043F0000C460546DDE922101E461746DDE920324F +:10440000D0F801C0CDF808C0B0F805C0ADF80CC0B8 +:104410000078C0F340008DF80E00D1F80100CDF80F +:104420000F00B1F80500ADF8130008781946C0F385 +:1044300040008DF815001088ADF8160090788DF8C2 +:1044400018000DF119001022F9F7F7FF0DF12900FE +:1044500010223146F9F7F1FF0DF1390010223946EB +:10446000F9F7EBFF17A913A8CDE90001412302AA30 +:1044700021462846FFF7B6FE1BB0F0BDF0B5A3B04D +:1044800017460D4604461E46102202A82899F9F741 +:10449000D4FF06A820223946F9F7CFFF0EA8202224 +:1044A0002946F9F7CAFF1EA91AA8CDE90001502331 +:1044B00002AA314616A8FFF795FE1698206023B091 +:1044C000F0BDF0B589B00446DDE90E070D46397838 +:1044D000109EC1F340018DF8001031789446C1F36D +:1044E00040018DF801101968CDF802109988ADF8D7 +:1044F000061099798DF808100168CDF809108188A7 +:10450000ADF80D1080798DF80F0010236A466146D2 +:1045100004A8FFF74CFE2246284604A9FDF7B5FC87 +:10452000D6F801000090B6F80500ADF80400D7F801 +:104530000100CDF80600B7F80500ADF80A0000202C +:10454000039010236A46214604A8FFF730FE224656 +:10455000284604A9FDF799FC09B0F0BD1FB51C68F9 +:1045600000945B68019313680293526803920246B9 +:1045700008466946FDF789FC1FBD10B588B00446A2 +:104580001068049050680590002006900790084637 +:104590006A4604A9FDF779FCBDF80000208008B048 +:1045A00010BD1FB51288ADF800201A88ADF80220A2 +:1045B0000022019202920392024608466946FDF7E4 +:1045C00064FC1FBD7FB5074B14460546083B9A1C8B +:1045D0006846FFF7E6FF224669462846FFF7CDFF0B +:1045E0007FBD00007062020070B5044600780E4680 +:1045F000012813D0052802D0092813D10EE0A068A5 +:1046000061690578042003F059FA052D0AD0782352 +:1046100000220420616903F0A7F903E00420616926 +:1046200003F04CFA31462046BDE8704001F08AB8EC +:1046300010B500F12D03C2799C78411D144064F33C +:104640000102C271D2070DD04A795C7922404A71C9 +:104650000A791B791A400A718278C9788A4200D98E +:10466000817010BD00224A71F5E74178012900D020 +:104670000C21017070472DE9F04F93B04FF0000B03 +:104680000C690D468DF820B0097801260C201746DC +:104690004FF00D084FF0110A4FF008091B2975D291 +:1046A000DFE811F01B00C40207031F035E03710360 +:1046B000A303B803F9031A0462049504A204EF04E7 +:1046C0002D05370555056005F305360639066806DC +:1046D0008406FE062207EB06F00614B120781D289A +:1046E0002AD0D5F808805FEA08004FD001208DF865 +:1046F0002000686A02220D908DF824200A208DF88F +:104700002500A8690A90A8880028EED098F8001023 +:1047100091B10F2910D27DD2DFE801F07C1349DE80 +:10472000FCFBFAF9F8F738089CF6F50002282DD1C1 +:1047300024B120780C2801D00026F0E38DF8202049 +:10474000CBE10420696A03F0B9F9A8880728EED103 +:10475000204600F0F2FF022809D0204600F0EDFFCD +:10476000032807D9204600F0E8FF072802D20120DD +:10477000207004E0002CB8D020780128D7D198F818 +:104780000400C11F0A2902D30A2061E0C4E1A0701D +:10479000D8F80010E162B8F80410218698F80600F5 +:1047A00084F83200012028700320207044E007289C +:1047B000BDD1002C99D020780D28B8D198F80310DD +:1047C00094F82F20C1F3C000C2F3C002104201D000 +:1047D000062000E00720890707D198F8051001425C +:1047E000D2D198F806100142CED194F8312098F831 +:1047F000051020EA02021142C6D194F8322098F83E +:10480000061090430142BFD198F80400C11F0A2945 +:10481000BAD200E008E2617D81427CD8D8F800106D +:104820006160B8F80410218198F80600A072012098 +:1048300028700E20207003208DF82000686A0D90EB +:1048400004F12D000990601D0A900F300B9022E1B9 +:104850002875FCE3412891D1204600F06EFF042822 +:1048600002D1E078C00704D1204600F066FF0F288F +:1048700084D1A88CD5F80C8080B24FF0400BE6694B +:10488000FFF745FC324641465B464E46CDF8009068 +:10489000FFF731F80B208DF82000686A0D90E06971 +:1048A0000990002108A8FFF79FFE2078042806D071 +:1048B000A07D58B1012809D003280AD04AE3052079 +:1048C0002070032028708DF82060CEE184F800A0CD +:1048D00032E712202070EAE11128BCD1204600F016 +:1048E0002CFF042802D1E078C00719D0204600F040 +:1048F00024FF062805D1E078C00711D1A07D022849 +:104900000ED0204608E0CCE084E072E151E124E1E1 +:1049100003E1E9E019E0B0E100F00FFF11289AD1BE +:10492000102208F1010104F13C00F9F786FD6078DE +:1049300001286ED012202070E078C00760D0A07DE2 +:104940000028C8D00128C6D05AE0112890D12046AE +:1049500000F0F3FE082804D0204600F0EEFE1328F5 +:1049600086D104F16C00102208F101010646F9F726 +:1049700064FD207808280DD014202070E178C80745 +:104980000DD0A07D02280AD06278022A04D0032824 +:10499000A1D035E00920F0E708B1012837D1C807D8 +:1049A00013D0A07D02281DD000200090D4E906215C +:1049B00033460EA8FFF777FC10220EA904F13C0045 +:1049C000F9F70EFDC8B1042042E7D4E90912201D11 +:1049D0008DE8070004F12C0332460EA8616BFFF747 +:1049E00070FDE9E7606BC1F34401491E0068C840EF +:1049F00000F0010040F08000D7E72078092806D1B8 +:104A000085F800908DF8209036E32870EFE30920B8 +:104A1000FBE79EE1112899D1204600F08EFE0A287E +:104A200002D1E078C00704D1204600F086FE1528A8 +:104A30008CD104F13C00102208F101010646F9F77F +:104A4000FCFC20780A2816D016202070D4E9093200 +:104A5000606B611D8DE80F0004F15C0304F16C02D2 +:104A600047310EA8FFF7C2FC10220EA93046F9F715 +:104A7000B7FC18B1F9E20B20207073E22046FFF773 +:104A8000D7FDA078216AC0F110020B18002118464A +:104A9000F9F7FDFC26E3394608A8FFF7A5FD064611 +:104AA0003CE20228B7D1204600F047FE042804D398 +:104AB000204600F042FE082809D3204600F03DFEC3 +:104AC0000E2829D3204600F038FE122824D2A07DDB +:104AD0000228A0D10E208DF82000686A0D9098F869 +:104AE00001008DF82400F5E3022894D1204600F05F +:104AF00024FE002810D0204600F01FFE0128F9D027 +:104B0000204600F01AFE0C28F4D004208DF8240072 +:104B100098F801008DF8250060E21128FCD1002CE6 +:104B2000FAD020781728F7D16178606A022912D06C +:104B30005FF0000101EB4101182606EBC1011022D4 +:104B4000405808F10101F9F778FC0420696A00F087 +:104B5000E7FD2670F0E50121ECE70B28DCD1002C05 +:104B6000DAD020781828D7D16078616A02281CD062 +:104B70005FF0000000EB4002102000EBC20009587B +:104B8000B8F8010008806078616A02280FD0002020 +:104B900000EB4002142000EBC2000958404650F8D8 +:104BA000032F0A604068486039E00120E2E70120F5 +:104BB000EEE71128B0D1002CAED020781928ABD167 +:104BC0006178606A022912D05FF0000101EB4101B7 +:104BD0001C2202EBC1011022405808F10101F9F733 +:104BE0002CFC0420696A00F09BFD1A20B6E001212C +:104BF000ECE7082890D1002C8ED020781A288BD191 +:104C0000606A98F80120017862F347010170616AD7 +:104C1000D8F8022041F8012FB8F806008880042057 +:104C2000696A00F07DFD90E2072011E638780128DE +:104C300094D1182204F114007968F9F7FEFBE079A9 +:104C4000C10894F82F0001EAD001E07861F3000078 +:104C5000E070217D002974D12178032909D0C00793 +:104C600025D0032028708DF82090686A0D9041208F +:104C700008E3607DA178884201D90620E8E5022694 +:104C80002671E179204621F0E001E171617A21F09D +:104C9000F0016172A17A21F0F001A172FFF7C8FC66 +:104CA0002E708DF82090686A0D900720EAE20420AB +:104CB000ABE6387805289DD18DF82000686A0D9004 +:104CC000B8680A900720ADF824000A988DF830B033 +:104CD0006168016021898180A17A8171042020703E +:104CE000F8E23978052985D18DF82010696A0D918F +:104CF000391D09AE0EC986E80E004121ADF8241019 +:104D00008DF830B01070A88CD7F80C8080B2402697 +:104D1000A769FFF70EFA41463A463346C846CDF832 +:104D20000090FEF71CFE002108A8FFF75DFCE0786C +:104D300020F03E00801CE0702078052802D00F2073 +:104D40000CE04AE1A07D20B1012802D0032802D066 +:104D500002E10720BEE584F80080EDE42070EBE47A +:104D6000102104F15C0002F0C2FB606BB0BBA07DBF +:104D700018B1012801D00520FDE006202870F84870 +:104D80006063A063C2E23878022894D1387908B110 +:104D90002875B7E3A07D022802D0032805D022E0C1 +:104DA000B8680028F5D060631CE06078012806D060 +:104DB000A07994F82E10012805D0E94806E0A179E1 +:104DC00094F82E00F7E7B8680028E2D06063E07836 +:104DD000C00701D0012902D0E14803E003E0F868F0 +:104DE0000028D6D0A06306200FE68DF82090696ACF +:104DF0000D91E1784846C90709D06178022903D1AD +:104E0000A17D29B1012903D0A17D032900D007206C +:104E1000287033E138780528BBD1207807281ED0C8 +:104E200084F800A005208DF82000686A0D90B8680D +:104E30000A90ADF824A08DF830B003210170E1781C +:104E4000CA070FD0A27D022A1AD000210091D4E90E +:104E5000061204F15C03401CFFF725FA6BE384F8AB +:104E60000090DFE7D4E90923211D8DE80E0004F14D +:104E70002C0304F15C02401C616BFFF722FB5AE338 +:104E8000626BC1F34401491E1268CA4002F001017D +:104E900041F08001DAE738780528BDD18DF820008F +:104EA000686A0D90B8680A90ADF824A08DF830B00B +:104EB000042100F8011B102204F15C01F9F7BDFA8E +:104EC000002108A8FFF790FB2078092801D01320C3 +:104ED00044E70A2020709AE5E078C10742D0A17D1E +:104EE000012902D0022927D038E0617808A80129D9 +:104EF00016D004F16C010091D4E9061204F15C03B0 +:104F0000001DFFF7BBFA0A20287003268DF82080C9 +:104F1000686A0D90002108A8FFF766FBE1E2C7E28E +:104F200004F15C010091D4E9062104F16C03001D39 +:104F3000FFF7A4FA0026E9E7C0F3440114290DD2D3 +:104F40004FF0006101EBB0104FEAB060E0706078A4 +:104F5000012801D01020BDE40620FFE6607801287A +:104F60003FF4B6AC0A2050E5E178C90708D0A17D2E +:104F7000012903D10B202870042030E028702EE096 +:104F80000E2028706078616B012818D004F15C0352 +:104F900004F16C020EA8FFF7E1FA2046FFF748FB88 +:104FA000A0780EAEC0F1100230440021F9F76FFA7C +:104FB00006208DF82000686A09960D909BE004F1A8 +:104FC0006C0304F15C020EA8FFF7C8FAE8E7397831 +:104FD000022903D139790029D0D0297592E28DF8C0 +:104FE0002000686A0D9056E538780728F6D1D4E994 +:104FF00009216078012808D004F16C00CDE9000295 +:10500000029105D104F16C0304E004F15C00F5E7C2 +:1050100004F15C0304F14C007A680646216AFFF74C +:1050200063F96078012822D1A078216AC0F11002CA +:105030000B1800211846F9F72AFAD4E90923606B06 +:1050400004F12D018DE80F0004F15C0300E05BE248 +:1050500004F16C0231460EA8FFF7C8F910220EA920 +:1050600004F13C00F9F7BCF908B10B20ACE485F879 +:10507000008000BF8DF82090686A0D908DF824A004 +:1050800009E538780528A9D18DF82000686A0D90C7 +:10509000B8680A90ADF824A08DF830B080F8008090 +:1050A000617801291AD0D4E9092104F12D03A66BF6 +:1050B00003910096CDE9013204F16C0304F15C0226 +:1050C00004F14C01401CFFF791F9002108A8FFF7FB +:1050D0008BFA6078012805D015203FE6D4E9091243 +:1050E000631DE4E70E20287006208DF82000686A12 +:1050F000CDF824B00D90A0788DF82800CBE4387856 +:105100000328C0D1E079C00770D00F202870072095 +:1051100065E7387804286BD11422391D04F1140096 +:10512000F9F78BF9616A208CA1F80900616AA0780F +:10513000C871E179626A01F003011172616A627AF1 +:105140000A73616AA07A81F8240016205DE485F86C +:1051500000A08DF82090696A50460D9192E0000001 +:10516000706202003878052842D1B868A861617879 +:10517000606A022901D0012100E0002101EB410118 +:10518000142606EBC1014058082102F0B0F96178FD +:10519000606A022901D0012100E0002101EB4101F8 +:1051A00006EBC101425802A8E169FFF70BFA6078EB +:1051B000626A022801D0012000E0002000EB4001DB +:1051C000102000EBC1000223105802A90932FEF79B +:1051D000EEFF626AFD4B0EA80932A169FFF7E1F903 +:1051E0006178606A022904D0012103E044E18DE086 +:1051F000BFE0002101EB4101182606EBC101A278B6 +:1052000040580EA9F9F719F96178606A022901D0AE +:10521000012100E0002101EB410106EBC1014158F1 +:10522000A0780B18C0F1100200211846F9F72FF9E9 +:1052300005208DF82000686A0D90A8690A90ADF8E5 +:1052400024A08DF830B0062101706278616A022ACC +:1052500001D0012200E0002202EB420206EBC20272 +:10526000401C89581022F9F7E8F8002108A8FFF738 +:10527000BBF91220C5F818B028708DF82090686A24 +:105280000D900B208DF8240005E43878052870D1A6 +:105290008DF82000686A0D90B8680A900B20ADF870 +:1052A00024000A98072101706178626A022901D0FE +:1052B000012100E0002101EB4103102101EBC301BA +:1052C00051580988A0F801106178626A022902D059 +:1052D000012101E02FE1002101EB4103142101EB49 +:1052E000C30151580A6840F8032F4968416059E0EA +:1052F0001920287001208DF8300074E616202870DF +:105300008DF830B0002108A8FFF76EF9032617E1E9 +:1053100014202870AEE6387805282AD18DF82000B0 +:10532000686A0D90B8680A90ADF824A08DF830B086 +:1053300080F800906278616A4E46022A01D001220C +:1053400000E0002202EB42021C2303EBC202401CDD +:1053500089581022F9F771F8002108A8FFF744F9DD +:10536000152028708DF82060686A0D908DF82460F3 +:1053700039E680E0387805287DD18DF82000686A0C +:105380000D90B8680A90ADF8249009210170616908 +:10539000097849084170616951F8012FC0F802206D +:1053A0008988C18020781C28A8D1A1E7E078C007AF +:1053B00002D04FF0060C01E04FF0070C6078022895 +:1053C0000AD000BF4FF0000000EB040101F1090119 +:1053D00005D04FF0010004E04FF00100F4E74FF07A +:1053E00000000B78204413EA0C030B7010F8092F0F +:1053F00002EA0C02027004D14FF01B0C84F800C0CA +:10540000D2B394F801C0BCF1010F00D09BB990F861 +:1054100000C0E0465FEACC7C04D028F001060670AC +:10542000102606E05FEA887C05D528F002060670A3 +:1054300013262E70032694F801C0BCF1020F00D091 +:1054400092B991F800C05FEACC7804D02CF0010644 +:105450000E70172106E05FEA8C7805D52CF0020665 +:105460000E701921217000260078D0BBCAB3C3BBCF +:105470001C20207035E012E002E03878062841D187 +:105480001A2015E4207801283CD00C283AD0204678 +:10549000FFF7EBF809208DF82000686A0D9031E0E5 +:1054A0003878052805D00620387003261820287083 +:1054B00046E005208DF82000696A0D91B9680A91CF +:1054C0000221ADF8241001218DF830100A990870DE +:1054D000287D4870394608A8FFF786F80646182048 +:1054E0002870012E0ED02BE001208DF82000686A74 +:1054F0000D9003208DF82400287D8DF8250085F877 +:1055000014B012E0287D80B11D2020701720287073 +:105510008DF82090686A0D9002208DF8240039469D +:1055200008A8FFF761F806460AE00CB1FE202070DB +:105530009DF8200020B1002108A8FFF755F80CE4E1 +:1055400013B03046BDE8F08F2DE9F04387B00C462C +:105550004E6900218DF804100120257803460227AA +:105560004FF007094FF0050C85B1012D53D0022DE6 +:1055700039D1FE2030708DF80030606A059003202C +:105580008DF80400207E8DF8050063E02179012963 +:1055900025D002292DD0032928D0042923D1B17D7B +:1055A000022920D131780D1F042D04D30A3D032D8B +:1055B00001D31D2917D12189022914D38DF8047034 +:1055C000237020899DF80410884201E0686202007F +:1055D00018D208208DF80000606A059057E07078B6 +:1055E0000128EBD0052007B0BDE8F0831D20307006 +:1055F000E4E771780229F5D131780C29F3D18DF8DF +:105600000490DDE7083402F804CB94E80B0082E84C +:105610000B000320E7E71578052DE4D18DF800C0D5 +:10562000656A0595956802958DF8101094F80480C8 +:10563000B8F1010F13D0B8F1020F2DD0B8F1030F5C +:105640001CD0B8F1040FCED1ADF804700E20287034 +:10565000207E687000216846FEF7C6FF0CE0ADF8BA +:1056600004700B202870207E002100F01F0068705D +:105670006846FEF7B9FF37700020B4E7ADF8047054 +:105680008DF8103085F800C0207E687027701146B4 +:105690006846FEF7A9FFA6E7ADF804902B70207FBF +:1056A0006870607F00F00100A870A07F00F01F000C +:1056B000E870E27F2A71C0071CD094F8200000F047 +:1056C0000700687194F8210000F00700A87100211C +:1056D0006846FEF789FF2868F062A8883086A879B6 +:1056E00086F83200A069407870752879B0700D2076 +:1056F0003070C1E7A9716971E9E700B587B0042886 +:105700000CD101208DF800008DF8040000200591D7 +:105710008DF8050001466846FEF766FF07B000BD3C +:1057200070B50C46054602F0C9F921462846BDE889 +:1057300070407823002202F017B908B10078704752 +:105740000C20704770B50C0005784FF000010CD0AC +:1057500021702146EFF7D1FD69482178405D8842EC +:1057600001D1032070BD022070BDEFF7C6FD0020FF +:1057700070BD0279012A05D000220A704B78012BF6 +:1057800002D003E0042070470A758A610279930011 +:10579000521C0271C15003207047F0B587B00F460C +:1057A00005460124287905EB800050F8046C7078D8 +:1057B000411E02290AD252493A46083901EB8000BB +:1057C000314650F8043C2846984704460CB1012C59 +:1057D00011D12879401E10F0FF00287101D0032458 +:1057E000E0E70A208DF80000706A0590002101961C +:1057F0006846FFF7A7FF032CD4D007B02046F0BDC2 +:1058000070B515460A46044629461046FFF7C5FFFF +:10581000064674B12078FE280BD1207C30B10020E0 +:105820002870294604F10C00FFF7B7FF2046FEF769 +:105830001CFF304670BD704770B50E4604467C2292 +:105840000021F8F724FE0225012E03D0022E04D0F9 +:10585000052070BD0120607000E065702046FEF7F5 +:1058600004FFA575002070BD28B1027C1AB10A465C +:1058700000F10C01C4E70120704710B5044686B062 +:10588000042002F01BF92078FE2806D000208DF8B5 +:10589000000069462046FFF7E7FF06B010BD7CB563 +:1058A0000E4600218DF804104178012903D0022909 +:1058B00003D0002405E0046900E044690CB1217CB8 +:1058C00089B16D4601462846FFF753FF032809D1E9 +:1058D000324629462046FFF793FF9DF80410002921 +:1058E00000D004207CBD04F10C05EBE730B40C467D +:1058F0000146034A204630BC024B0C3AFEF751BE2B +:10590000AC6202006862020070B50D46040011D05E +:1059100085B1220100212846F8F7B9FD102250492F +:105920002846F8F78AFD4F48012101704470456010 +:10593000002070BD012070BD70B505460024494EA1 +:1059400011E07068AA7B00EB0410817B914208D1C2 +:10595000C17BEA7B914204D10C222946F8F740FD35 +:1059600030B1641CE4B230788442EAD3002070BDC8 +:10597000641CE0B270BD70B50546FFF7DDFF00287E +:1059800005D1384C20786178884201D3002070BD61 +:105990006168102201EB00102946F8F74EFD2078CF +:1059A000401CC0B2207070BD2E48007870472D4951 +:1059B0000878012802D0401E08700020704770B59A +:1059C0000D460021917014461180022802D0102843 +:1059D00015D105E0288890B10121A17010800CE05C +:1059E000284613B1FFF7C7FF01E0FFF7A5FFA0703E +:1059F00010F0FF0F03D0A8892080002070BD012087 +:105A000070BD0023DBE770B5054614460E0009D0D3 +:105A100000203070A878012806D003D911490A78EF +:105A200090420AD9012070BD24B1287820702888BE +:105A3000000A5070022008700FE064B1496810221B +:105A400001EB001120461039F8F7F7FC2878207395 +:105A50002888000A607310203070002070BD00009C +:105A6000BB620200900000202DE9F04190460C46F8 +:105A700007460025FE48072F00EB881607D2DFE80F +:105A800007F00707070704040400012500E0FFDF13 +:105A900006F81470002D13D0F548803000EB880113 +:105AA00091F82700202803D006EB4000447001E065 +:105AB00081F8264006EB44022020507081F82740F0 +:105AC000BDE8F081F0B51F4614460E460546202A73 +:105AD00000D1FFDFE649E648803100EB871C0CEB84 +:105AE000440001EB8702202E07D00CEB46014078E2 +:105AF0004B784870184620210AE092F8253040780B +:105B000082F82500F6E701460CEB4100057040786D +:105B1000A142F8D192F82740202C03D00CEB44048A +:105B2000637001E082F826300CEB4104202363709F +:105B300082F82710F0BD30B50D46CE4B4419002237 +:105B4000181A72EB020100D2FFDFCB48854200DD5C +:105B5000FFDFC9484042854200DAFFDFC548401CEC +:105B6000844207DA002C01DB204630BDC148401CCE +:105B7000201830BDBF48C043FAE710B5044601689D +:105B8000407ABE4A52F82020114450B10220084405 +:105B900020F07F40EDF763F894F90810BDE810405D +:105BA000C9E70420F3E72DE9F047B14E803696F8B7 +:105BB0002D50DFF8BC9206EB850090F8264034E0CB +:105BC00009EB85174FF0070817F81400012806D0D5 +:105BD00004282ED005282ED0062800D0FFDF01F0A3 +:105BE00025F9014607EB4400427806EB850080F872 +:105BF000262090F82720A24202D1202280F82720D8 +:105C0000084601F01EF92A4621460120FFF72CFF25 +:105C10009B48414600EB041002682046904796F8E6 +:105C20002D5006EB850090F82640202CC8D1BDE809 +:105C3000F087022000E003208046D0E710B58C4CAE +:105C40002021803484F8251084F8261084F8271049 +:105C5000002084F8280084F82D0084F82E10411EBE +:105C6000A16044F8100B2074607420736073A073FB +:105C70008449E07720750870487000217C4A103C08 +:105C800002F81100491CC9B22029F9D30120ECF710 +:105C9000D6FE0020ECF7D3FE012084F82200EDF7B9 +:105CA000FFF87948EDF711F9764CA41E207077487B +:105CB000EDF70BF96070BDE81040ECF74DBE10B584 +:105CC000ECF76FFE6F4CA41E2078EDF717F96078A3 +:105CD000EDF714F9BDE8104001F0E0B8202070475E +:105CE0000020ECF785BE70B5054601240E46AC4099 +:105CF0005AB1FFF7F5FF0146654800EBC500C0F853 +:105D00001015C0F81465634801E06248001D046086 +:105D100070BD2DE9F34F564C0025803404EB810A09 +:105D200089B09AF82500202821D0691E0291544993 +:105D3000009501EB0017391D03AB07C983E8070085 +:105D4000A18BADF81C10A07F8DF81E009DF81500EA +:105D5000A046C8B10226494951F820400399A2192A +:105D6000114421F07F41019184B102210FE0012013 +:105D7000ECF765FE0020ECF762FEECF730FE01F078 +:105D80008DF884F82F50A9E00426E4E700218DF86F +:105D90001810022801D0012820D103980119099870 +:105DA000081A801C9DF81C1020F07F4001B10221D0 +:105DB000353181420BD203208DF815000398C4F1D0 +:105DC0003201401A20F07F40322403900CE098F812 +:105DD000240018B901F043FA002863D0322C03D212 +:105DE00014B101F04FF801E001F058F8254A10789D +:105DF00018B393465278039B121B00219DF818405C +:105E0000994601281AD0032818D000208DF81E00CA +:105E1000002A04DD981A039001208DF818009DF8DF +:105E20001C0000B1022103981B4A20F07F40039020 +:105E300003AB099801F03EF810B110E00120E5E74E +:105E40009DF81D0018B99BF80000032829D08DF893 +:105E50001C50CDF80C908DF818408DF81E509DF810 +:105E6000180010B30398012381190022184615E089 +:105E7000840A0020FF7F841E0020A107CC6202005C +:105E8000840800209A00002017780100A75B010019 +:105E900000F0014004F50140FFFF3F00ECF722FE57 +:105EA00006E000200BB0BDE8F08F0120ECF7C7FD45 +:105EB00097F90C20012300200199ECF713FEF87BE1 +:105EC000C00701D0ECF7F7FE012188F82F108AF8FF +:105ED000285020226946FE48F8F7AFFA0120E1E792 +:105EE0002DE9F05FDFF8E883064608EB860090F8BE +:105EF0002550202D1FD0A8F180002C4600EB8617DE +:105F0000A0F50079DFF8CCB305E0A24607EB4A0024 +:105F10004478202C0AD0ECF730FE09EB04135A46E3 +:105F200001211B1D00F0C6FF0028EED0AC4202D0BC +:105F3000334652461EE0E84808B1AFF30080ECF764 +:105F40001CFE98F82F206AB1D8F80C20411C891A41 +:105F50000902CA1701EB12610912002902DD0020B3 +:105F6000BDE8F09F3146FFF7D4FE08B10120F7E706 +:105F700033462A4620210420FFF7A4FDEFE72DE950 +:105F8000F041D34C2569ECF7F8FD401B0002C11726 +:105F900000EB1160001200D4FFDF94F8220000B182 +:105FA000FFDF012784F8227094F82E00202800D10A +:105FB000FFDF94F82E60202084F82E00002584F85E +:105FC0002F5084F8205084F82150C4482560007870 +:105FD000022833D0032831D000202077A068401C4D +:105FE00005D04FF0FF30A0600120ECF728FD002025 +:105FF000ECF725FDECF721FEECF719FEECF7EFFCD2 +:106000000EF0D6FDB648056005604FF0E0214FF474 +:106010000040B846C1F88002ECF7BBFE94F82D7042 +:106020003846FFF75DFF0028FAD0A948803800EB1A +:10603000871010F81600022802D006E00120CCE7F5 +:106040003A4631460620FFF70FFD84F8238004EB23 +:10605000870090F82600202804D0A048801E4078B1 +:10606000ECF752FF207F002803D0ECF7D6FD257710 +:10607000657725E5964910B591F82D2000248039E3 +:1060800001EB821111F814302BB1641CE4B2202C06 +:10609000F8D3202010BD934901EB041108600020C3 +:1060A000C87321460120FFF7DFFC204610BD10B564 +:1060B000012801D0032800D171B3854A92F82D3010 +:1060C000834C0022803C04EB831300BF13F8124082 +:1060D0000CB1082010BD521CD2B2202AF6D37F4A40 +:1060E00048B1022807D0072916D2DFE801F01506CB +:1060F000080A0C0E100000210AE01B2108E03A21DA +:1061000006E0582104E0772102E0962100E0B52165 +:1061100051701070002010BD072010BD6F4810B5E1 +:106120004078ECF79CFD80B210BD10B5202811D24C +:10613000674991F82D30A1F1800202EB831414F825 +:1061400010303BB191F82D3002EB831212F8102081 +:10615000012A01D0002010BD91F82D200146002019 +:10616000FFF782FC012010BD10B5ECF706FDBDE87D +:106170001040ECF774BD2DE9F0410E46544F017804 +:106180002025803F0C4607EB831303E0254603EBF5 +:1061900045046478944202D0202CF7D108E0202CEA +:1061A00006D0A14206D103EB41014978017007E016 +:1061B000002085E403EB440003EB45014078487080 +:1061C000494F7EB127B1002140F22D40AFF300804E +:1061D0003078A04206D127B100214FF48660AFF39A +:1061E0000080357027B1002140F23540AFF30080C8 +:1061F000012065E410B542680B689A1A1202D417A0 +:1062000002EB1462121216D4497A91B1427A82B921 +:10621000364A006852F82110126819441044001DD3 +:10622000891C081A0002C11700EB11600012322805 +:1062300001DB012010BD002010BD2DE9F047294EE3 +:10624000814606F500709846144600EB811712E06F +:1062500006EB0415291D4846FFF7CCFF68B988F8FE +:106260000040A97B99F80A00814201D80020DEE4B1 +:1062700007EB44004478202CEAD10120D7E42DE933 +:10628000F047824612480E4600EB8600DFF8548045 +:1062900090F825402020107008F5007099461546AA +:1062A00000EB86170BE000BF08EB04105146001D01 +:1062B000FFF7A0FF28B107EB44002C704478202C96 +:1062C000F2D1297889F800104B46224631460FE07A +:1062D000040B0020FFFF3F00000000009A00002098 +:1062E00000F500408408002000000000CC6202009D +:1062F0005046BDE8F047A0E72DE9FC410F460446B3 +:106300000025FE4E10E000BF9DF80000256806EB5A +:1063100000108168204600F0E1FD2068A84202D10B +:106320000020BDE8FC8101256B4601AA39462046C4 +:10633000FFF7A5FF0028E7D02846F2E770B504462E +:10634000EF480125A54300EB841100EB85104022A6 +:10635000F8F773F8EB4E26B1002140F29D40AFF301 +:106360000080E748803000EB850100EB8400D0F826 +:106370002500C1F8250026B1002140F2A140AFF36D +:106380000080284670BD8A4203D003460520FFF7EF +:1063900099BB202906D0DA4A02EB801000EB4100BD +:1063A00040787047D649803101EB800090F8250095 +:1063B0007047D24901EB0010001DFFF7DEBB7CB532 +:1063C0001D46134604460E4600F1080221461846B3 +:1063D000ECF752FC94F908000F2804DD1F382072F6 +:1063E0002068401C206096B10220C74951F8261051 +:1063F000461820686946801B20F07F40206094F991 +:1064000008002844C01C1F2803DA012009E00420EA +:10641000EBE701AAECF730FC9DF8040010B10098FE +:10642000401C00900099206831440844C01C20F0B2 +:106430007F4060607CBDFEB50C46064609786079F9 +:10644000907220791F461546507279B12179002249 +:106450002846A368FFF7B3FFA9492846803191F881 +:106460002E20202A0AD00969491D0DE0D4E9022313 +:10647000217903B02846BDE8F040A0E7A349497858 +:10648000052900D20521314421F07F4100F026FD8D +:1064900039462846FFF730FFD4E9023221796846B1 +:1064A000FFF78DFF2B4600213046019A00F002FDD8 +:1064B000002806D103B031462846BDE8F04000F080 +:1064C0000DBDFEBD2DE9F14F84B000F0C3FCF0B16D +:1064D00000270498007800284FF000006DD1884D07 +:1064E000884C82468346803524B1002140F2045016 +:1064F000AFF3008095F82D8085F823B0002624B1F5 +:10650000002140F20950AFF3008017B105E00127E8 +:10651000DFE74046FFF712FF804624B1002140F23A +:106520001150AFF30080ECF728FB814643466A46E2 +:106530000499FFF780FF24B1002140F21750AFF318 +:10654000008095F82E0020280CD029690098401A68 +:106550000002C21700EB1260001203D5684600F07B +:10656000BDFC01264CB1002140F22150AFF3008068 +:10657000002140F22650AFF300806B46644A0021B0 +:10658000484600F097FC98B127B941466846FFF7A6 +:10659000B3FE064326B16846FFF7EFFA0499886018 +:1065A0004FF0010A24B1002140F23A50AFF30080CD +:1065B00095F82300002897D1504605B073E42DE9E3 +:1065C000F04F89B08B46824600F044FC4C4C80343E +:1065D00030B39BF80000002710B1012800D0FFDF86 +:1065E000484D25B1002140F2F950AFF300804349F6 +:1065F000012001EB0A18A946CDF81C005FEA090644 +:1066000004D0002140F20160AFF30080079800F051 +:1066100018FC94F82D50002084F8230067B119E08D +:1066200094F82E000127202800D1FFDF9BF80000FE +:106630000028D5D0FFDFD3E72846FFF77FFE0546C9 +:1066400026B1002140F20B60AFF3008094F82300E4 +:106650000028D3D126B1002140F21560AFF30080AD +:10666000ECF78BFA2B4602AA59460790FFF7E3FE98 +:1066700098F80F005FEA060900F001008DF813009A +:1066800004D0002140F21F60AFF300803B462A4651 +:1066900002A9CDF800A0079800F02BFC064604EBF9 +:1066A000850090F828000090B9F1000F04D0002177 +:1066B00040F22660AFF3008000F0B8FB0790B9F11C +:1066C000000F04D0002140F22C60AFF3008094F85A +:1066D0002300002892D1B9F1000F04D0002140F22C +:1066E0003460AFF300800DF1080C9CE80E00C8E99F +:1066F0000112C8F80C30BEB30CE000008408002082 +:10670000840A002000000000CC6202009A000020F1 +:10671000FFFF3F005FEA090604D0002140F241601C +:10672000AFF300800098B84312D094F82E002028D0 +:106730000ED126B1002140F24660AFF3008028461A +:10674000FFF7CEFB20B99BF80000D8B3012849D051 +:10675000B9F1000F04D0002140F26360AFF3008074 +:10676000284600F05CFB01265FEA090504D0002101 +:1067700040F26C60AFF30080079800F062FB25B137 +:1067800000214FF4CE60AFF300808EB194F82D005D +:1067900004EB800090F82600202809D025B10021C4 +:1067A00040F27760AFF30080F7484078ECF7ACFB3D +:1067B00025B1002140F27C60AFF3008009B0304683 +:1067C000BDE8F08FFFE7B9F1000F04D0002140F2DF +:1067D0004E60AFF3008094F82D2051460420FFF75F +:1067E00043F9C0E7002E3FF409AF002140F25960A1 +:1067F000AFF3008002E72DE9F84FE44D814695F8AC +:106800002D004FF00008E24C4FF0010B474624B139 +:10681000002140F28A60AFF30080584600F011FB7F +:1068200085F8237024B1002140F28F60AFF300801F +:1068300095F82D00FFF782FD064695F8230028B154 +:10684000002CE4D0002140F295604BE024B10021FF +:1068500040F29960AFF30080CC48803800EB86119D +:1068600011F81900032856D1334605EB830A4A462E +:106870009AF82500904201D1012000E0002000900C +:106880000AF125000021FFF776FC0146009801423D +:1068900003D001228AF82820AF77E1B324B1002188 +:1068A00040F29E60AFF30080324649460120FFF778 +:1068B000DBF89AF828A024B1002140F2A960AFF3D8 +:1068C000008000F0B3FA834624B1002140F2AE60AC +:1068D000AFF3008095F8230038B1002C97D0002149 +:1068E00040F2B260AFF3008091E7BAF1000F07D039 +:1068F00095F82E00202803D13046FFF7F1FAE0B1D9 +:1069000024B1002140F2C660AFF30080304600F0B1 +:1069100086FA4FF0010824B1002140F2CF60AFF3B6 +:106920000080584600F08DFA24B1002140F2D36077 +:10693000AFF300804046BDE8F88F002CF1D0002175 +:1069400040F2C160AFF30080E6E70120ECF750B8F9 +:106950008D48007870472DE9F0418C4C94F82E005A +:1069600020281FD194F82D6004EB860797F8255056 +:10697000202D00D1FFDF8549803901EB861000EB27 +:106980004500407807F8250F0120F87084F82300AF +:10699000294684F82E50324602202234FFF764F84C +:1069A0000020207005E42DE9F0417A4E774C012556 +:1069B00038B1012821D0022879D003287DD0FFDF0B +:1069C00017E400F05FFAFFF7C6FF207E00B1FFDF9B +:1069D00084F821500020ECF732F8A168481C04D05C +:1069E000012300221846ECF77DF814F82E0F2178C9 +:1069F00006EB01110A68012154E0FFF7ACFF01200A +:106A0000ECF71DF894F8210050B1A068401C07D0A5 +:106A100014F82E0F217806EB01110A68062141E0D7 +:106A2000207EDFF86481002708F10208012803D0E6 +:106A300002281ED0FFDFB5E7A777ECF7EEF898F84D +:106A40000000032801D165772577607D524951F810 +:106A5000200094F8201051B948B161680123091A47 +:106A600000221846ECF73EF8022020769AE72776B7 +:106A700098E784F8205000F005FAA07F50B198F80C +:106A8000010061680123091A00221846ECF72AF870 +:106A9000257600E0277614F82E0F217806EB0111F9 +:106AA0000A680021BDE8F041104700E005E03648E3 +:106AB0000078BDE8F041ECF727BAFFF74CFF14F877 +:106AC0002E0F217806EB01110A680521EAE710B5BF +:106AD0002E4C94F82E00202800D1FFDF14F82E0F42 +:106AE00021782C4A02EB01110A68BDE8104004210C +:106AF00010477CB5254C054694F82E00202800D17F +:106B0000FFDFA068401C00D0FFDF94F82E00214971 +:106B100001AA01EB0010694690F90C002844ECF73B +:106B2000ABF89DF904000F2801DD012000E00020F2 +:106B3000009908446168084420F07F41A16094F8FE +:106B40002100002807D002B00123BDE870400022D8 +:106B50001846EBF7C7BF7CBD30B5104A0B1A541C62 +:106B6000B3EB940F1ED3451AB5EB940F1AD393428F +:106B700003D9101A43185B1C14E0954210D9511A1E +:106B80000844401C43420DE098000020040B002004 +:106B90000000000084080020CC620200FF7F841EF9 +:106BA000FFDF0023184630BD0123002201460220EA +:106BB000EBF798BF0220EBF742BFEBF7DEBF2DE902 +:106BC000FE4FEE4C05468A4694F82E00202800D150 +:106BD000FFDFEA4E94F82E10A0462046A6F520725C +:106BE00002EB011420218DF8001090F82D10376968 +:106BF00000EB8101D8F8000091F82590284402AA02 +:106C000001A90C36ECF738F89DF90800002802DDE0 +:106C10000198401C0190A0680199642D084452D34A +:106C2000D74B00225B1B72EB02014CD36168411A07 +:106C300021F07F41B1F5800F45D220F07F40706098 +:106C400086F80AA098F82D1044466B464A4630460E +:106C5000FFF7F3FAB0B3A068401C10D0EBF78DFF3C +:106C6000A168081A0002C11700EB11600012022887 +:106C70002BDD0120EBF7E3FE4FF0FF30A06094F82E +:106C80002D009DF8002020210F34FFF77CFBA17F11 +:106C9000BA4A803A02EB8111E27F01EB420148706F +:106CA00054F80F0C284444F80F0C012020759DF86F +:106CB0000000202803D0B3484078ECF725F90120E4 +:106CC000BDE8FE8F01E00020FAE77760FBE72DE9E1 +:106CD000F047AA4C074694F82D00A4F1800606EB75 +:106CE000801010F8170000B9FFDF94F82D50A0466F +:106CF000A54C24B1002140F6EA00AFF3008040F635 +:106D0000F60940F6FF0A06EB851600BF16F81700D5 +:106D1000012819D0042811D005280FD006280DD03D +:106D20001CB100214846AFF300800FF02DF8002C75 +:106D3000ECD000215046AFF30080E7E72A46394601 +:106D40000120FEF791FEF2E74FF0010A4FF0000933 +:106D5000454624B1002140F60610AFF300805046AE +:106D600000F06FF885F8239024B1002140F60B1055 +:106D7000AFF3008095F82D00FFF7E0FA064695F88E +:106D8000230028B1002CE4D0002140F611101FE0B0 +:106D900024B1002140F61510AFF3008005EB86000A +:106DA00000F1270133463A462630FFF7E4F924B1D3 +:106DB000002140F61910AFF3008000F037F882464A +:106DC00095F8230038B1002CC3D0002140F61F10E5 +:106DD000AFF30080BDE785F82D60012085F8230022 +:106DE000504600F02EF8002C04D0002140F62C1064 +:106DF000AFF30080BDE8F08730B504465F480D462C +:106E000090F82D005D49803901EB801010F81400D6 +:106E100000B9FFDF5D4800EB0410C57330BD574972 +:106E200081F82D00012081F82300704710B55848E3 +:106E300008B1AFF30080EFF3108000F0010072B6EC +:106E400010BD10B5002804D1524808B1AFF300803E +:106E500062B610BD50480068C005C00D10D0103893 +:106E600040B2002804DB00F1E02090F8000405E0C7 +:106E700000F00F0000F1E02090F8140D4009704779 +:106E80000820704710B53D4C94F82400002804D128 +:106E9000F4F712FF012084F8240010BD10B5374C20 +:106EA00094F82400002804D0F4F72FFF002084F881 +:106EB000240010BD10B51C685B68241A181A24F051 +:106EC0007F4420F07F40A14206D8B4F5800F03D262 +:106ED000904201D8012010BD002010BDD0E9003241 +:106EE000D21A21F07F43114421F07F41C0E90031E3 +:106EF00070472DE9FC418446204815468038089C9F +:106F000000EB85160F4616F81400012804D002285D +:106F100002D00020BDE8FC810B46204A01216046DA +:106F2000FFF7C8FFF0B101AB6A4629463846FFF7C4 +:106F3000A6F9B8B19DF804209DF800102846FFF787 +:106F400022FA06EB440148709DF8000020280DD07D +:106F500006EB400044702A4621460320FEF784FDDC +:106F60000120D7E72A4621460420F7E703480121FC +:106F700000EB850000F8254FC170ECE7040B002002 +:106F8000FF1FA107980000200000000084080020D7 +:106F9000000000000000000004ED00E0FFFF3F00E3 +:106FA0002DE9F041044680074FF000054FF001063F +:106FB0000CD56B480560066000F0E8F920B169481F +:106FC000016841F48061016024F00204E0044FF0A4 +:106FD000FF3705D564484660C0F8087324F4805430 +:106FE000600003D56148056024F08044E0050FD5BA +:106FF0005F48C0F80052C0F808735E490D60091D73 +:107000000D605C4A04210C321160066124F4807426 +:10701000A00409D558484660C0F80052C0F808736B +:107020005648056024F40054C4F38030C4F3C031E2 +:10703000884200D0FFDF14F4404F14D0504846601F +:10704000C0F808734F488660C0F80052C0F8087353 +:107050004D490D600A1D16608660C0F808730D600A +:10706000166024F4404420050AD5484846608660EE +:10707000C0F80873C0F848734548056024F40064FC +:107080000DF070FD4348044200D0FFDFBDE8F08101 +:10709000F0B50022202501234FEA020420FA02F174 +:1070A000C9072DD051B2002910DB00BF4FEA51179C +:1070B0004FEA870701F01F0607F1E02703FA06F6FB +:1070C000C7F88061BFF34F8FBFF36F8F0CDB00BF3A +:1070D0004FEA51174FEA870701F01F0607F1E02733 +:1070E00003FA06F6C7F8806204DB01F1E02181F8BB +:1070F000004405E001F00F0101F1E02181F8144D99 +:1071000002F10102AA42C9D3F0BD10B5224C2060A1 +:107110000846F4F7EAFE2068FFF742FF2068FFF711 +:10712000B7FF0DF045F900F092F90DF01BFD0DF0E1 +:1071300058FCEBF7B5FEBDE810400DF0EDB910B509 +:10714000154C2068FFF72CFF2068FFF7A1FF0DF01A +:1071500009FDF4F7C9FF0020206010BD0A20704728 +:10716000FC1F00403C17004000C0004004E5014007 +:10717000008000400485004000D0004004D500405D +:1071800000E0004000F0004000F5004000B000408A +:1071900008B50040FEFF0FFD9C00002070B5264999 +:1071A0000A680AB30022154601244B685B1C4B6039 +:1071B0000C2B00D34D600E7904FA06F30E681E42C4 +:1071C0000FD0EFF3108212F0010272B600D001224C +:1071D0000C689C430C6002B962B6496801600020EB +:1071E00070BD521C0C2AE0D3052070BD4FF0E02189 +:1071F0004FF48000C1F800027047EFF3108111F0E6 +:10720000010F72B64FF0010202FA00F20A48036859 +:1072100042EA0302026000D162B6E7E706480021B5 +:1072200001604160704701218140034800680840C7 +:1072300000D0012070470000A0000020012081073D +:10724000086070470121880741600021C0F80011E3 +:1072500018480170704717490120087070474FF0B7 +:107260008040D0F80001012803D01248007800289F +:1072700000D00120704710480068C00700D00120EE +:1072800070470D480C300068C00700D001207047DF +:107290000948143000687047074910310A68D20362 +:1072A00006D5096801F00301814201D10120704730 +:1072B00000207047A8000020080400404FF08050D4 +:1072C000D0F830010A2801D0002070470120704713 +:1072D00000B5FFF7F3FF20B14FF08050D0F8340134 +:1072E00008B1002000BD012000BD4FF08050D0F853 +:1072F00030010E2801D000207047012070474FF068 +:107300008050D0F83001062803D0401C01D0002066 +:107310007047012070474FF08050D0F830010D28A1 +:1073200001D000207047012070474FF08050D0F806 +:107330003001082801D000207047012070474FF02D +:107340008050D0F83001102801D000207047012073 +:10735000704700B5FFF7F3FF30B9FFF7DCFF18B94E +:10736000FFF7E3FF002800D0012000BD00B5FFF7C4 +:10737000C6FF38B14FF08050D0F83401062803D34F +:10738000401C01D0002000BD012000BD00B5FFF76A +:10739000B6FF48B14FF08050D0F83401062803D32F +:1073A000401C01D0012000BD002000BD0021017063 +:1073B000084670470146002008707047EFF31081BF +:1073C00001F0010172B60278012A01D0012200E029 +:1073D00000220123037001B962B60AB10020704790 +:1073E0004FF400507047E9E7EFF3108111F0010FFF +:1073F00072B64FF00002027000D162B600207047F2 +:10740000F2E700002DE9F04115460E46044600273C +:1074100000F0EBF8A84215D3002341200FE000BF95 +:1074200094F84220A25CF25494F84210491CB1FB3B +:10743000F0F200FB12115B1C84F84210DBB2AB428D +:10744000EED3012700F0DDF83846BDE8F08172493F +:1074500010B5802081F800047049002081F84200B6 +:1074600081F84100433181F8420081F84100433105 +:1074700081F8420081F841006948FFF797FF6848AA +:10748000401CFFF793FFEBF793FCBDE8104000F0C2 +:10749000B8B840207047614800F0A7B80A460146D6 +:1074A0005E48AFE7402070475C48433000F09DB82D +:1074B0000A46014659484330A4E7402101700020A4 +:1074C000704710B504465548863000F08EF820709D +:1074D000002010BD0A460146504810B58630FFF71F +:1074E00091FF08B1002010BD42F2070010BD70B539 +:1074F0000C460646412900D9FFDF4A48006810388B +:1075000040B200F054F8C5B20D2000F050F8C0B2FF +:10751000854201D3012504E0002502E00DB1EBF71F +:107520008AFC224631463D48FFF76CFF0028F5D023 +:1075300070BD2DE9F0413A4F0025064617F10407CA +:1075400057F82540204600F041F810B36D1CEDB20D +:10755000032DF5D33148433000F038F8002825D00A +:107560002E4800F033F8002820D02C48863000F058 +:107570002DF800281AD0EBF734FC2948FFF71EFF3E +:10758000B0F5005F00D0FFDFBDE8F0412448FFF711 +:107590002BBF94F841004121265414F8410F401CA0 +:1075A000B0FBF1F201FB12002070D3E74DE7002899 +:1075B00004DB00F1E02090F8000405E000F00F008B +:1075C00000F1E02090F8140D4009704710F8411FB9 +:1075D0004122491CB1FBF2F302FB131140788142B6 +:1075E00001D1012070470020704710F8411F4078FA +:1075F000814201D3081A02E0C0F141000844C0B240 +:10760000704710B50648FFF7D9FE002803D1BDE842 +:107610001040EBF7D1BB10BD0DE000E0340B0020B3 +:10762000AC00002004ED00E070B5154D2878401C3A +:10763000C4B26878844202D000F0DBFA2C7070BDCE +:107640002DE9F0410E4C4FF0E02600BF00F0C6FAE5 +:107650000EF09AFB40BF20BF677820786070D6F8A4 +:107660000052E9F798FE854305D1D6F8040210B917 +:107670002078B842EAD000F0ACFA0020BDE8F081F2 +:10768000BC0000202DE9F04101264FF0E02231033B +:107690004FF000084046C2F88011BFF34F8FBFF390 +:1076A0006F8F204CC4F800010C2000F02EF81E4D06 +:1076B0002868C04340F30017286840F01000286095 +:1076C000C4F8046326607F1C02E000BF0EF05CFB80 +:1076D000D4F800010028F9D01FB9286820F0100064 +:1076E0002860124805686660C4F80863C4F8008121 +:1076F0000C2000F00AF82846BDE8F08110B50446D9 +:10770000FFF7C0FF2060002010BD002809DB00F05B +:107710001F02012191404009800000F1E020C0F8E3 +:107720008012704700C0004010ED00E008C5004026 +:107730002DE9F047FF4C0646FF21A06800EB06123A +:1077400011702178FF2910D04FF0080909EB0111C1 +:1077500009EB06174158C05900F0F4F9002807DD7D +:10776000A168207801EB061108702670BDE8F0874B +:1077700094F8008045460DE0A06809EB05114158DA +:10778000C05900F0DFF9002806DCA068A84600EB2D +:1077900008100578FF2DEFD1A06800EB061100EB73 +:1077A00008100D700670E1E7F0B5E24B04460020CA +:1077B00001259A680C269B780CE000BF05EB0017AA +:1077C000D75DA74204D106EB0017D7598F4204D0EA +:1077D000401CC0B28342F1D8FF20F0BD70B5FFF766 +:1077E000ECF9D44C08252278A16805EB02128958DF +:1077F00000F0A8F9012808DD2178A06805EB011147 +:107800004058BDE87040FFF7CFB9FFF7A1F8BDE8D9 +:107810007040EBF779BB2DE9F041C64C2578FFF7B6 +:10782000CCF9FF2D6ED04FF00808A26808EB0516C2 +:10783000915900F087F90228A06801DD80595DE0C8 +:1078400000EB051109782170022101EB0511425C62 +:107850005AB1521E4254815901F5800121F07F41F5 +:1078600081512846FFF764FF34E00423012203EB33 +:10787000051302EB051250F803C0875CBCF1000F42 +:1078800010D0BCF5007F10D9CCF3080250F806C028 +:107890000CEB423C2CF07F4C40F806C0C3589A1ABF +:1078A000520A09E0FF2181540AE0825902EB4C326E +:1078B00022F07F428251002242542846FFF738FFCF +:1078C0000C21A06801EB05114158E06850F8272011 +:1078D000384690472078FF2814D0FFF76EF92278B9 +:1078E000A16808EB02124546895800F02BF90128DF +:1078F00093DD2178A06805EB01114058BDE8F04107 +:10790000FFF752B9BDE8F081F0B51D4614460E46AA +:107910000746FF2B00D3FFDFA00700D0FFDF85481D +:10792000FF210022C0E90247C57006710170427054 +:1079300082701046012204E002EB0013401CE15467 +:10794000C0B2A842F8D3F0BD70B57A4C064665784F +:107950002079854200D3FFDFE06840F82560607839 +:10796000401C6070284670BD2DE9FF5F1D468B46A8 +:107970000746FF24FFF721F9DFF8B891064699F88A +:107980000100B84200D8FFDF00214FF001084FF09E +:107990000C0A99F80220D9F808000EE008EB011350 +:1079A000C35CFF2B0ED0BB4205D10AEB011350F88C +:1079B00003C0DC450CD0491CC9B28A42EED8FF2C6A +:1079C00002D00DE00C46F6E799F803108A4203D185 +:1079D000FF2004B0BDE8F09F1446521C89F8022035 +:1079E00008EB04110AEB0412475440F802B00421DA +:1079F000029B0022012B01EB04110CD040F8012066 +:107A00004FF4007808234FF0020C454513D9E905DF +:107A1000C90D02D002E04550F2E7414606EB413283 +:107A200003EB041322F07F42C250691A0CEB0412DC +:107A3000490A81540BE005B9012506EB453103EBFA +:107A4000041321F07F41C1500CEB0411425499F80A +:107A500000502046FFF76CFE99F80000A84201D0C4 +:107A6000FFF7BCFE3846B4E770B50C460546FFF795 +:107A7000A4F8064621462846FFF796FE0446FF284E +:107A80001AD02C4D082101EB0411A868415830464A +:107A900000F058F800F58050C11700EBD1404013BA +:107AA0000221AA6801EB0411515C09B100EB4120ED +:107AB000002800DC012070BD002070BD2DE9F047DA +:107AC00088468146FFF770FE0746FF281BD0194DF8 +:107AD0002E78A8683146344605E0BC4206D02646DA +:107AE00000EB06121478FF2CF7D10CE0FF2C0AD023 +:107AF000A6420CD100EB011000782870FF2804D0BA +:107B0000FFF76CFE03E0002030E6FFF753F8414634 +:107B10004846FFF7A9FF0123A968024603EB0413B7 +:107B2000FF20C854A878401EB84200D1A87001EBCD +:107B3000041001E0000C002001EB06110078087031 +:107B4000104613E6081A0002C11700EB116000127C +:107B50007047000010B5202000F07FF8202000F0D2 +:107B60008DF84D49202081F80004E9F712FC4B49BB +:107B700008604B48D0F8041341F00101C0F8041329 +:107B8000D0F8041341F08071C0F804134249012079 +:107B90001C39C1F8000110BD10B5202000F05DF8BF +:107BA0003E480021C8380160001D01603D4A481E62 +:107BB00010603B4AC2F80803384B1960C2F8000154 +:107BC000C2F8600138490860BDE81040202000F08C +:107BD00055B834493548091F086070473149334862 +:107BE000086070472D48C8380160001D521E0260B1 +:107BF00070472C4901200860BFF34F8F70472DE973 +:107C0000F0412849D0F8188028480860244CD4F85E +:107C100000010025244E6F1E28B14046E9F712FBF3 +:107C200040B9002111E0D4F8600198B14046E9F76D +:107C300009FB48B1C4F80051C4F860513760BDE891 +:107C4000F041202000F01AB831684046BDE8F0410C +:107C50000EF0A4B8FFDFBDE8F08100280DDB00F0D6 +:107C60001F02012191404009800000F1E020C0F88E +:107C70008011BFF34F8FBFF36F8F7047002809DB70 +:107C800000F01F02012191404009800000F1E02036 +:107C9000C0F880127047000020E000E0C8060240F3 +:107CA00000000240180502400004024001000001EB +:107CB0005E4800210170417010218170704770B5DD +:107CC000054616460C460220EAF714FE57490120E5 +:107CD00008705749F01E086056480560001F046090 +:107CE00070BD10B50220EAF705FE5049012008706A +:107CF00051480021C0F80011C0F80411C0F8081163 +:107D00004E494FF40000086010BD48480178D9B1D1 +:107D10004B4A4FF4000111604749D1F8003100226D +:107D2000002B1CBFD1F80431002B02D0D1F8081170 +:107D300019B142704FF0100104E04FF001014170A1 +:107D400040490968817002704FF00000EAF7D2BD27 +:107D500010B50220EAF7CEFD34480122002102705E +:107D60003548C0F80011C0F80411C0F808110260CD +:107D700010BD2E480178002904BF407870472E4876 +:107D8000D0F80011002904BF02207047D0F800117C +:107D900000291CBFD0F80411002905D0D0F8080133 +:107DA000002804BF01207047002070471F4800B51D +:107DB0000278214B4078C821491EC9B282B1D3F85C +:107DC00000C1BCF1000F10D0D3F8000100281CBF87 +:107DD000D3F8040100280BD0D3F8080150B107E014 +:107DE000022802D0012805D002E00029E4D1FFDFFB +:107DF000002000BD012000BD0C480178002904BF0F +:107E0000807870470C48D0F8001100291CBFD0F8CA +:107E10000411002902D0D0F8080110B14FF0100071 +:107E2000704708480068C0B270470000BE000020DC +:107E300010F5004008F5004000F0004004F5014056 +:107E400008F5014000F400405748002101704170DE +:107E5000704770B5064614460D460120EAF74AFD04 +:107E600052480660001D0460001D05605049002056 +:107E7000C1F850014F490320086050494E4808603E +:107E8000091D4F48086070BD2DE9F0410546464880 +:107E90000C46012606704B4945EA024040F08070CE +:107EA0000860FFF72CFA002804BF47480460002749 +:107EB000464CC4F80471474945480860002D02BF8C +:107EC000C4F800622660BDE8F081012D18BFFFDF15 +:107ED000C4F80072266041493F480860BDE8F0815F +:107EE0003148017871B13B4A394911603749D1F8BD +:107EF00004210021002A08BF417002D0384A1268CC +:107F0000427001700020EAF7F5BC2748017800298B +:107F100004BF407870472D48D0F80401002808BFFE +:107F200070472F480068C0B27047002808BF7047EC +:107F30002DE9F0471C480078002808BFFFDF234CDC +:107F4000D4F80401002818BFBDE8F0874FF00209FB +:107F5000C4F80493234F3868C0F30018386840F021 +:107F600010003860D4F80401002804BF4FF4004525 +:107F70004FF0E02608D100BFC6F880520DF004FF94 +:107F8000D4F804010028F7D0B8F1000F03D1386805 +:107F900020F010003860C4F80893BDE8F0870B4962 +:107FA0000120886070470000C100002008F50040F3 +:107FB000001000401CF500405011004098F50140B1 +:107FC0000CF0004004F5004018F5004000F00040BF +:107FD0000000020308F501400000020204F5014020 +:107FE00000F4004010ED00E0012804BF41F6A47049 +:107FF0007047022804BF41F288307047042804BF4C +:1080000046F218007047082804BF47F2A0307047B6 +:1080100000B5FFDF41F6A47000BD10B5FE48002496 +:1080200001214470047044728472C17280F825404A +:10803000C462846380F83C4080F83D40FF2180F8B2 +:108040003E105F2180F83F1018300DF09FFFF3497C +:10805000601E0860091D0860091D0C60091D08608C +:10806000091D0C60091D0860091D0860091D0860D4 +:10807000091D0860091D0860091D0860091D0860C8 +:10808000091D0860091D086010BDE549486070477A +:10809000E448016801F00F01032904BF0120704783 +:1080A000016801F00F01042904BF02207047016834 +:1080B00001F00F01052904D0006800F00F00062828 +:1080C00007D1D948006810F0060F0CBF0820042023 +:1080D000704700B5FFDF012000BD012812BF022854 +:1080E00000207047042812BF08284FF4C87070475A +:1080F00000B5FFDF002000BD012804BF2820704725 +:10810000022804BF18207047042812BF08284FF423 +:10811000A870704700B5FFDF282000BD70B5C148CA +:10812000016801F00F01032908BF012414D0016880 +:1081300001F00F01042904BF022418210DD00168A9 +:1081400001F00F0105294BD0006800F00F00062850 +:108150001CBFFFDF012443D02821AF48C26A806AD8 +:10816000101A0E18082C04BF4EF6981547F2A030CE +:108170002DD02046042C08BF4EF628350BD0012800 +:1081800008BF41F6A47506D0022C1ABFFFDF41F6E6 +:10819000A47541F28835012C08BF41F6A47016D0B1 +:1081A000022C08BF002005D0042C1ABFFFDF0020DE +:1081B0004FF4C8702D1A022C08BF41F2883006D047 +:1081C000042C1ABFFFDF41F6A47046F21800281AEB +:1081D0004FF47A7100F2E730B0FBF1F0304470BD3B +:1081E0009148006810F0060F0CBF082404244FF4D7 +:1081F000A871B2E710B58D49026801F118040A634D +:1082000042684A63007A81F83800207E48B1207FB6 +:10821000F6F781F9A07E011C18BF0121207FF6F737 +:1082200069F9607E002808BF10BD607FF6F773F91A +:10823000E07E011C18BF0121607FBDE81040F6F709 +:1082400059B930B50024054601290AD0022908BFD2 +:108250004FF0807405D0042916BF08294FF0C74499 +:10826000FFDF44F4847040F480107149086045F4E5 +:10827000403001F1040140F00070086030BD30B5BD +:108280000024054601290AD0022908BF4FF0807456 +:1082900005D0042916BF08294FF0C744FFDF44F476 +:1082A000847040F480106249086045F4403001F168 +:1082B000040140F0007008605E48D0F8000100281A +:1082C00018BFFFDF30BD2DE9F04102274FF0E02855 +:1082D00001250024C8F88071BFF34F8FBFF36F8F63 +:1082E000554804600560FFF751F8544E18B13068E6 +:1082F00040F480603060FFF702F838B1306820F059 +:10830000690040F0960040F0004030604D494C4814 +:1083100008604FF01020806CB0F1FF3F04D04A4954 +:108320000A6860F317420A60484940F25B600860DF +:10833000091F40F203100860081F05603949032037 +:10834000086043480560444A42491160444A434931 +:108350001160121F43491160016821F440710160EE +:10836000016841F480710160C8F8807231491020C1 +:10837000C1F80403284880F83140C46228484068A6 +:10838000002808BFBDE8F081BDE8F0410047274A5A +:108390000368C2F81A308088D08302F11800017295 +:1083A00070471D4B10B51A7A8A4208D10146062241 +:1083B000981CF6F715F8002804BF012010BD002016 +:1083C00010BD154890F825007047134A5170107081 +:1083D0007047F0B50546800000F1804000F5805000 +:1083E0008B88C0F820360B78D1F8011043EA0121C0 +:1083F000C0F8001605F10800012707FA00F61A4C2C +:10840000002A04BF2068B04304D0012A18BFFFDF50 +:1084100020683043206029E0280C0020000E004036 +:10842000C40000201015004014140040100C00205F +:108430001415004000100040FC1F00403C17004095 +:108440002C000089781700408C150040381500403A +:108450005016004000000E0408F501404080004026 +:10846000A4F501401011004040160040206807FAB2 +:1084700005F108432060F0BD0CF0C4BCFE4890F844 +:1084800032007047FD4AC17811600068FC49000263 +:1084900008607047252808BF02210ED0262808BF93 +:1084A0001A210AD0272808BF502106D00A2894BFD5 +:1084B0000422062202EB4001C9B2F24A1160F249DD +:1084C000086070472DE9F047EB4CA17A012956D09E +:1084D000022918BFBDE8F087627E002A08BFBDE808 +:1084E000F087012950D0E17E667F0D1C18BF012561 +:1084F0005FF02401DFF894934FF00108C9F84C8035 +:10850000DFF88CA34718DAF80000B84228BFFFDF75 +:108510000020C9F84C01CAF80070300285F0010152 +:1085200040EA015040F0031194F82000820002F16B +:10853000804202F5C042C2F81015D64901EB800115 +:10854000A07FC20002F1804202F5F832C2F8141591 +:10855000D14BC2F81035E27FD30003F1804303F51D +:10856000F833C3F81415CD49C3F8101508FA00F014 +:1085700008FA02F10843CA490860BDE8F087227E84 +:10858000002AAED1BDE8F087A17E267F002914BF66 +:10859000012500251121ADE72DE9F041C14E8046AE +:1085A00003200D46C6F80002BD49BF4808602846B2 +:1085B0000CF02CFCB04F0124B8F1000F04BFBC72CA +:1085C000346026D0B8F1010F23D1B848006860B9F3 +:1085D00015F00C0F09D0C6F80443012000F0DAFEB4 +:1085E000F463346487F83C4002E0002000F0D2FEDF +:1085F00028460CF0F3FC0220B872FEF7B7FE38B93B +:10860000FEF7C4FE20B9AA48016841F4C021016008 +:1086100074609E48C4649E4800682946BDE8F041E5 +:1086200050E72DE9F0479F4E814603200D46C6F8DE +:108630000002DFF86C829C48C8F8000008460CF085 +:10864000E5FB28460CF0CAFC01248B4FB9F1000F62 +:1086500003D0B9F1010F0AD031E0BC72B86B40F41D +:108660008010B8634FF48010C8F8000027E00220A3 +:10867000B872B86B40F40010B8634FF40010C8F83B +:1086800000008A48006860B915F00C0F09D0C6F8E0 +:108690000443012000F07EFEF463346487F83C401C +:1086A00002E0002000F076FEFEF760FE38B9FEF72B +:1086B0006DFE20B97E48016841F4C0210160EAF7EF +:1086C000F7FA2946BDE8F047FCE62DE9F84F754C6E +:1086D0008246032088461746C4F80002DFF8C0919E +:1086E0007148C9F8000010460CF090FBDFF8C4B1E7 +:1086F000614E0125BAF1000F04BFCBF80040B572FE +:1087000004D0BAF1010F18BFFFDF2FD06A48C0F8BC +:1087100000806B4969480860B06B40F40020B0638A +:10872000D4F800321021C4F808130020C4F8000265 +:10873000DFF890C18A03CCF80020C4F80001C4F827 +:108740000C01C4F81001C4F80401C4F81401C4F801 +:1087500018015D4800680090C4F80032C9F8002094 +:10876000C4F80413BAF1010F14D026E038460CF017 +:1087700035FCFEF7FBFD38B9FEF708FE20B94C4882 +:10878000016841F4C02101605048CBF8000002208C +:10879000B072BBE74548006860B917F00C0F09D00C +:1087A000C4F80453012000F0F5FDE563256486F864 +:1087B0003C5002E0002000F0EDFD4FF40020C9F82D +:1087C00000003248C56432480068404528BFFFDFDA +:1087D00039464046BDE8F84F74E62DE9F041264C95 +:1087E0000646002594F8310017468846002808BF41 +:1087F000FFDF16B1012E16D021E094F831000128D8 +:1088000008D094F83020394640460CF014FBE16A59 +:10881000451814E094F830103A4640460CF049FBF5 +:10882000E16A45180BE094F8310094F83010012803 +:108830003A46404609D00CF064FBE16A45183A46D6 +:1088400029463046BDE8F0413FE70CF014FBE16AF1 +:108850004518F4E72DE9F84F124CD4F8000220F047 +:108860000309D4F804034FF0100AC0F30018C4F849 +:1088700008A300262CE00000280C0020241500404E +:108880001C150040081500405415004000800040B1 +:108890004C850040006000404C81004010110040B9 +:1088A00004F5014000100040000004048817004057 +:1088B00068150040ACF50140488500404881004003 +:1088C000A8F5014008F501401811004004100040CF +:1088D000C4F80062FC48FB490160FC4D0127A97AFD +:1088E000012902D0022903D015E0297E11B912E036 +:1088F000697E81B1A97FEA7F07FA01F107FA02F2E6 +:108900001143016095F82000800000F1804000F5DF +:10891000C040C0F81065FF208DF80000C4F8106159 +:10892000276104E09DF80000401E8DF800009DF8CE +:10893000000018B1D4F810010028F3D09DF8000011 +:10894000002808BFFFDFC4F81061002000F022FDFE +:108950006E72AE72EF72C4F80092B8F1000F18BFD9 +:10896000C4F804A3BDE8F88FFF2008B58DF8000017 +:10897000D7480021C0F810110121016105E000BFB6 +:108980009DF80010491E8DF800109DF8001019B1D7 +:10899000D0F810110029F3D09DF80000002808BF7E +:1089A000FFDF08BD0068CB4920F07F4008607047BA +:1089B0004FF0E0200221C0F8801100F5C070BFF335 +:1089C0004F8FBFF36F8FC0F80011704710B490E85D +:1089D0001C10C14981E81C10D0E90420C1E9042021 +:1089E00010BC70474FF0E0210220C1F80001704731 +:1089F000BA4908707047BA490860704770B50546B3 +:108A0000EAF756F9B14C2844E16A884298BFFFDF83 +:108A100001202074EAF74CF9B24A28440021606131 +:108A2000C2F84411B0490860A06BB04940F480001E +:108A3000A063D001086070BD70B5A44C0546AC4A77 +:108A40000220207410680E4600F00F00032808BFB3 +:108A5000012213D0106800F00F00042808BF022282 +:108A60000CD0106800F00F0005281BD0106800F033 +:108A70000F0006281CBFFFDF012213D094F831003D +:108A800094F83010012815D028460CF081FA954949 +:108A900060610020C1F844016169E06A08449249BC +:108AA000086070BD9348006810F0060F0CBF0822E4 +:108AB0000422E3E7334628460CF038FAE7E7824918 +:108AC0004FF4800008608148816B21F4800181634C +:108AD000002101747047C20002F1804202F5F832B1 +:108AE000854BC2F81035C2F81415012181407F482A +:108AF00001607648826B1143816370477948012198 +:108B00004160C1600021C0F84411774801606F489E +:108B1000C1627047794908606D48D0F8001241F091 +:108B20004001C0F8001270476948D0F8001221F0E7 +:108B30004001C0F800127149002008607047644885 +:108B4000D0F8001221F01001C0F80012012181615B +:108B500070475E49FF2081F83E005D480021C0F863 +:108B60001C11D0F8001241F01001C0F8001270473B +:108B7000574981B0D1F81C21012A0DD0534991F8F1 +:108B80003E10FF290DBF00204942017001B008BF0F +:108B90007047012001B07047594A126802F07F0205 +:108BA000524202700020C1F81C0156480068009033 +:108BB000EFE7F0B517460C00064608BFFFDF434D50 +:108BC00014F0010F2F731CBF012CFFDF002E0CBF10 +:108BD000012002206872EC7201281CBF0228FFDF0E +:108BE000F0BD3A4981F83F007047384A136C036082 +:108BF000506C086070472DE9F84F38480078042819 +:108C000028BFFFDF314CDFF8C080314D94F83C00C5 +:108C100000260127E0B1D5F8040110F1000918BFC2 +:108C20004FF00109D5F81001002818BF012050EAC3 +:108C300009014FF4002B17D08021C5F80813C8F89C +:108C400000B084F83C6090F0010F18BFBDE8F88FC9 +:108C5000DFF89090D9F84C01002871D0A07A012853 +:108C60006FD002286ED0D1E0D5F80001DFF890A0D7 +:108C700030B3C5F800616F61FF20009002E0401E34 +:108C8000009005D0D5F81C0100280098F7D000B955 +:108C9000FFDFDAF8000000F07F0A94F83F0050454B +:108CA0003CBF002000F076FB84F83EA0C5F81C61B4 +:108CB000C5F808731348006800902F64AF6326E07E +:108CC00022E0000000000E0408F50140280C0020FE +:108CD000001000403C150040100C0020C400002093 +:108CE00004150040008000404485004004F5014028 +:108CF000101500401414004004110040601500409D +:108D0000481500401C110040B9F1000F03D0B9F123 +:108D1000000F2ED05CE0DAF8000000F07F0084F84D +:108D20003E00C5F81C6194F83D1061B194F83F1005 +:108D300081421BD2002000F02DFB2F64AF6315E0B1 +:108D400064E04CE04EE0FE49096894F83F308AB296 +:108D5000090C984203D30F2A06D9022904D2012014 +:108D600000F018FB2F6401E02F64AF63F548006842 +:108D700000908022C5F80423F3498F64F348036808 +:108D8000A0F1040CDCF800C043F698273B4463458F +:108D900015D2026842F210731A440260C1F84861A9 +:108DA000EC49EB480860091FEB480860EB48C0F845 +:108DB00000B0A06B40F40020A063BDE8F88F06600F +:108DC000C1F84861C5F80823C8F800B0C1F8486187 +:108DD0008020C5F80803C8F800B0BDE8F88F207EF1 +:108DE00010B913E0607E88B1A07FE17F07FA00F040 +:108DF00007FA01F10843C8F8000094F82000800049 +:108E000000F1804000F5C040C0F81065C9F84C7012 +:108E1000D34800682064D34800686064D248A16BDE +:108E20000160A663217C002019B1D9F84411012901 +:108E300000D00021A27A012A6ED0022A74D000BF8D +:108E4000D5F8101101290CBF1021002141EA0008BA +:108E5000C648016811F0FF0F03D0D5F8141101299D +:108E600000D0002184F83210006810F0FF0F03D00A +:108E7000D5F81801012800D0002084F83300BC4840 +:108E8000006884F83400FEF774FF012818BF002042 +:108E900084F83500C5F80061C5F80C61C5F81061AB +:108EA000C5F80461C5F81461C5F81861B1480068D7 +:108EB0000090A548C0F84461AF480068DFF8BC9254 +:108EC0000090D9F80000A062A9F104000068E062F7 +:108ED000AB48016801F00F01032908BF012013D03E +:108EE000016801F00F01042908BF02200CD00168BD +:108EF00001F00F01052926D0006800F00F000628B8 +:108F00001CBFFFDF01201ED084F83000A07A84F857 +:108F1000310002282CD11EE0D5F80C01012814BF25 +:108F2000002008208CE7FFE7D5F80C01012814BFCA +:108F300000200220934A1268012A14BF0422002252 +:108F4000104308437CE79048006810F0060F0CBF00 +:108F500008200420D8E7607850B18C490968097866 +:108F60000840217831EA000008BF84F8247001D05D +:108F700084F82460DFF818A218F0020F06D0E9F791 +:108F800097FEA16A081ADAF81010884718F0010F46 +:108F900018BF4FF0000B0DD0E9F78AFEE16ADAF84E +:108FA0001420081A594690477A48007810F0010FAB +:108FB0002FD10CE018F0020F18BF4FF0010BEBD1CE +:108FC00018F0080F18BF4FF0020BE5D1ECE7DFF8FF +:108FD000BCB1DBF80000007800F00F00072828BFC4 +:108FE00084F8256015D2DBF80000062200F10901A3 +:108FF000A01CF5F7F5F940B9207ADBF800100978E4 +:10900000B0EBD11F08BF012001D04FF0000084F861 +:109010002500E17A4FF0000011F0020F1CBF18F09C +:10902000020F18F0040F19D111F0100F1CBF94F8A3 +:109030003320002A02D094F835207AB111F0080FBD +:109040001CBF94F82420002A08D111F0040F02D08C +:1090500094F8251011B118F0010F01D04FF0010064 +:10906000617A19B168B1FFF7F5FB10E03E484A4953 +:109070000160D5F8000220F00300C5F80002E77295 +:1090800005E001290AD0022918BFFFDF0DD018F032 +:10909000010F14D0DAF80000804745E06672E772ED +:1090A000A7729621227B002006E06672E7720220FA +:1090B000A072227B96210120FFF78FFBE7E718F0D3 +:1090C000020F2AD018F0040F21D1FEF74FF9F0B9A2 +:1090D000FEF75CF9D8B931480168001F0068C0F399 +:1090E000006CC0F3425500F00F03C0F30312C0F34D +:1090F0000320BCF1000F0AD0002B1CBF002A00285F +:1091000005D1002918BF032D38BF48F0040827EA0D +:109110009800DAF80410884706E018F0080F18BF26 +:10912000DAF8080056D08047A07A022818BFBDE8B8 +:10913000F88F207C002808BFBDE8F88F02492FE097 +:10914000741500401C11004000800040488500401C +:1091500014100040ACF501404881004004F5014086 +:1091600004B500404C85004008F501404016004021 +:109170001014004018110040448100404485004014 +:109180001015004000140040141400400415004065 +:10919000100C0020C40000200000040454140040FF +:1091A000C1F8446102281DD0012818BFFFDFE16A21 +:1091B0006069884298BFFFDFD4F81400C9F8000046 +:1091C000A06B4FF4800140F48000A06382480160EE +:1091D000BDE8F88F18F0100F14BFDAF80C00FFDFAD +:1091E000A1D1A1E76169E06A0844E7E738B57B49A6 +:1091F00004460220887201212046FFF763F9784A6D +:1092000004F13D001060774B0020C3F8440176491B +:10921000C1F80001C1F80C01C1F81001C1F8040146 +:10922000C1F81401C1F818017048006800900120CD +:109230009864101D00681168884228BFFFDF38BDA0 +:109240002DE9F843654A88460024917A0125684F44 +:10925000012902D0022903D015E0117E11B912E0D4 +:10926000517E81B1917FD37F05FA01F105FA03F3B5 +:109270001943396092F82010890001F1804101F50D +:10928000C041C1F8104506460220907201213046C7 +:10929000FFF718F9524906F13D000860514AC2F83B +:1092A00044415148C0F80041C0F80C41C0F8104199 +:1092B000C0F80441C0F81441C0F818414B48006898 +:1092C00000909564081D00680968884228BFFFDF88 +:1092D000B8F1000F1CBF4FF400303860BDE8F883D0 +:1092E000022810B50DD0012804BF42F6CE3010BDC3 +:1092F000042817BF082843F6A440FFDF41F66A00A0 +:1093000010BDFDF7E5FF30B9FDF7F9FF002808BFF4 +:1093100041F6583001D041F2643041F29A010844DC +:1093200010BD314910B50020C1F800023049314864 +:109330000860324930480860091D31480860091D3D +:1093400030480860091D30480860091D2F48086032 +:10935000091D2F48086001200BF058FD1E494FF4ED +:109360003810086010BD22494FF43810086070476B +:109370002848016803291BBF00680228012000203B +:109380007047244801680B291BBF00680A28012088 +:109390000020704720490968C9B9204A204913684C +:1093A00070B123F0820343F07D0343F00043136068 +:1093B0000A6822F0100242F0600242F0004205E02A +:1093C00023F0004313600A6822F000420A60034958 +:1093D00081F83D007047000004F50140280C002092 +:1093E00044850040008000400010004018110040FB +:1093F00008F50140000004041011004098F50140F8 +:109400000410004044810040141000401C11004032 +:109410001010004050150040881700403C170040D5 +:109420007C17004010B5404822220021F5F72FF8A4 +:109430003D480024017821F010010170012104F061 +:10944000FFFE3A494FF6FF7081F822408884384980 +:109450000880488010BD704734498A8C824218BF0A +:109460007047002081F822004FF6FF708884704713 +:109470002D49016070472E49088070472B498A8C1E +:10948000A2F57F43FF3B03D00021016008467047EF +:1094900091F822202549012A1ABF016001200020ED +:1094A0007047224901F1220091F82220012A04BFCD +:1094B00000207047012202701D48008888841046F1 +:1094C00070471B49488070471849194B8A8C5B8844 +:1094D0009A4206D191F82220002A1EBF0160012085 +:1094E0007047002070471148114A818C5288914280 +:1094F00009D14FF6FF71818410F8221F19B10021A4 +:10950000017001207047002070470848084A818C8C +:109510005288914205D190F8220000281CBF0020FB +:109520007047012070470000960C0020700C00204E +:10953000CC0000207047584A012340B1012818BFD1 +:1095400070471370086890608888908170475370E6 +:109550000868C2F802008888D08070474E4A10B16F +:10956000012807D00EE0507860B1D2F80200086000 +:10957000D08804E0107828B19068086090898880CD +:109580000120704700207047434910B1012803D0E3 +:1095900006E0487810B903E0087808B10120704768 +:1095A0000020704730B58DB00C4605460D220021D5 +:1095B00004A8F4F76CFFE0788DF81F0020798DF88F +:1095C0001E0060798DF81D00286800906868019081 +:1095D000A8680290E868039068460AF087FF207840 +:1095E0009DF82F1088420CD160789DF82E1088428B +:1095F00007D1A0789DF82D10884202BF01200DB040 +:1096000030BD00200DB030BD30B50C4605468DB0E4 +:109610004FF0030104F1030012B1FDF749FF01E02F +:10962000FDF765FF60790D2220F0C00040F040009A +:109630006071002104A8F4F72AFFE0788DF81F007C +:1096400020798DF81E0060798DF81D002868009043 +:1096500068680190A8680290E868039068460AF07C +:1096600045FF9DF82F0020709DF82E0060709DF83A +:109670002D00A0700DB030BD10B5002904464FF08C +:10968000060102D0FDF714FF01E0FDF730FF60791D +:1096900020F0C000607110BDD0000020FE4840687E +:1096A00070472DE9F0410F46064601461446012059 +:1096B00005F06FF8054696F86500FEF795FC4AF24E +:1096C000B12108444FF47A71B0FBF1F0718840F297 +:1096D00071225143C0EB4100001BA0F55A7402F007 +:1096E0005AFF002818BF1E3CAF4234BF28463846F8 +:1096F000A04203D2AF422CBF3C462C467462BDE868 +:10970000F0812DE9FF4F8BB0044690F86500884644 +:109710000390DDE90D1008430A90E0480027057822 +:109720000C2D28BFFFDFDE4E36F8159094F88851D7 +:109730000C2D28BFFFDFDA4830F81500484480B20E +:10974000009094F87D000D280CBF012000200790A8 +:109750000D98002804BF94F82C0103282BD10798FA +:1097600048B3B4F8AA01404525D1D4F83401C4F86F +:109770002001608840F2E2414843C4F82401B4F873 +:109780007A01B4F806110844C4F82801204602F012 +:109790000CFFB4F8AE01E08294F8AC016075B4F847 +:1097A000B0016080B4F8B201A080B4F8B401E080E8 +:1097B000022084F82C01D4F884010990D4F88001A7 +:1097C0000690B4F80661B4F87801D4F874110191E8 +:1097D0000D9921B194F8401151B100F0D6B804F5BB +:1097E000807104917431059104F5B075091D07E08D +:1097F00004F5AA710491091D059104F5A275091DCE +:109800000891B4F87010A8EB0000A8EB01010FFA62 +:1098100080F90FFA81FBB9F1000F05DAD4F8700175 +:1098200001900120D9460A909C484FF0000A007927 +:10983000A8B3F2F77FFB90B3B4F8180102282ED337 +:1098400094F82C0102282AD094F8430138BB94F8EC +:10985000880100900C2828BFFFDF9148009930F85C +:10986000110000F5C86080B2009094F82C01012826 +:109870007ED0608840F2E2414843009901F0E6F86A +:10988000D4F8342180B206EB0B01A1EB0901821A56 +:1098900001FB02AAC4F83401012084F8430194F8C2 +:1098A0002C01002865D0012800F01482022800F065 +:1098B0007181032818BFFFDF00F04782A7EB0A0180 +:1098C0000198FCF738F90599012640F271220860E9 +:1098D0000898A0F80080002028702E710598006874 +:1098E000A8606188D4F834015143C0EB41006B4952 +:1098F000A0F54E70C8618969814287BF04990860EC +:10990000049801600498616A0068084400F5D47006 +:10991000E86002F040FE10B1E8681E30E8606E7149 +:10992000B4F8F000A0EB080000B20028C4BF032088 +:109930006871079800280E9800F06982E0B100BFB6 +:10994000B4F8181100290CBF0020B4F81A01A4F8CB +:109950001A0194F81C21401C504388420CD26879AB +:10996000401E002808DD6E71B4F81A01401C01E0A9 +:109970000FE05AE0A4F81A010D98002800F06A825E +:1099800094F84001002800F061820FB00220BDE889 +:10999000F08F94F8800003283DD03F4894F865107C +:1099A00090F82C00F7F78DFDE18A40F271225143C7 +:1099B00000EB4100CDF80800D4F82401009901F033 +:1099C00045F8D4F82021D4F82811821A01FB02AA04 +:1099D000C4F820010099029801F038F8D4F8301149 +:1099E000C4F83001411A8A44608840F2E241484399 +:1099F000009901F02BF806EB0B01D4F82821A1EB1C +:109A00000901891AD4F83421C4F83401821A491E94 +:109A100001FB02AA40E7E18A40F27122D4F8240156 +:109A2000514300EB41000290C6E70698002808BFAA +:109A3000FFDF94F86510184890F82C00F7F741FD07 +:109A40000990E08A40F271214143099800EB4100FE +:109A5000009900F0FBFFC4F83001608840F2E24159 +:109A60004843009900F0F2FFC4F8340103A902A8AA +:109A7000FFF7BBF8DDE90160039FE9F7F0F8014665 +:109A80003046FDF769F800F10F06E9F711F9381AC9 +:109A9000801B009006E00000B80C0020E0000020D1 +:109AA000E4620200B4F83401214686B20120D4F801 +:109AB000289004F06EFE074694F86500FEF794FACD +:109AC0004AF2B12108444FF47A7BB0FBFBF0618885 +:109AD00040F271225143C0EB4100801BA0F55A7641 +:109AE00002F059FD002818BF1E3EB94534BF384664 +:109AF0004846B04203D2B9452CBF4E463E46666248 +:109B000094F86500FEF7E9FA00F2E140B0FBFBF1E2 +:109B100006980F1894F86500FEF7DFFA064694F8E9 +:109B20006500FEF761FA30444AF2AB310844B0FBFD +:109B3000FBF1E08A40F2712242430998D4F8306187 +:109B400000EB4200401A801B384400993138471A14 +:109B5000607D40F2E24110FB01F994F8650000904D +:109B600010F00C0F0ABF00984EF62830FEF73CFAB2 +:109B70004AF2B1210844B0FBFBF000EB460000EBD9 +:109B800009060098FEF7B8FA304400F18401FE4857 +:109B9000816193E6E18A40F27122D4F824015143B5 +:109BA00000EB4100009900F051FFC4F830016188DA +:109BB00040F2E2404843009900F048FFC4F8340105 +:109BC00087B221460120D4F828B004F0E2FD814696 +:109BD00094F86500FEF708FA4AF2B12101444FF407 +:109BE0007A70B1FBF0F0618840F271225143C0EB12 +:109BF0004100C01BA0F55A7702F0CDFC002818BF29 +:109C00001E3FCB4534BF48465846B84203D2CB45E9 +:109C10002CBF5F464F4667621EBB0E9808B394F890 +:109C200065603046FEF7E0F94AF2B12101444FF495 +:109C30007A70B1FBF0F0D4F83011E28A084440F2B7 +:109C40007123D4F824115A4301EB42010F1A304614 +:109C5000FEF752FA01460998401A3844A0F120074D +:109C60000AE0E18A40F27122D4F82401514300EB6A +:109C70004100D4F83011471AD4F82821D4F8201123 +:109C8000D4F8300101FB0209607D40F2E24110FB93 +:109C900001FB94F8656016F00C0F0ABF30464EF6D3 +:109CA0002830FEF7A1F94AF2B12101444FF47A704D +:109CB000B1FBF0F000EB490000EB0B093046FEF77A +:109CC0001BFA484400F16001AF488161012084F82B +:109CD0002C01F3E5618840F271225143D4F834013C +:109CE000D4F82821C0EB410101FB09F706EB0B0179 +:109CF000891AD4F820C1D4F83031491E0CFB023245 +:109D000001FB0029607D40F2E24110FB01FB94F869 +:109D1000656016F00C0F0ABF30464EF62830FEF78D +:109D200063F94AF2B12101444FF47A70B1FBF0F0CB +:109D300000EB490000EB0B093046FEF7DDF9484423 +:109D400000F1600190488161B8E5618840F27122BC +:109D5000D4F834015143C0EB410000FB09F794F8FB +:109D60007C0024281CBF94F87D0024280BD1B4F873 +:109D7000AA01A8EB000000B2002804DB94F8AD01B2 +:109D8000002818BF03900A9800B3FEB9099800286C +:109D90001ABF06980028FFDF94F8650010F00C0F3A +:109DA00014BF4EF62830FEF71FF94AF2B1210144E4 +:109DB0004FF47A70B1FBF0F03F1A94F86500FEF7AB +:109DC0009BF90999081A3844A0F12007D4F83411F6 +:109DD00006EB0B0000FB01F6039810F00C0F0ABF16 +:109DE00003984EF62830FEF7FFF84AF2B1210144FD +:109DF0004FF47A70B1FBF0F000EB46060398FEF7E3 +:109E00007BF9304400F160015F48816156E500282C +:109E10007FF496AD94F82C0100283FF4ADAD618835 +:109E200040F27122D4F834015143C0EB410128467D +:109E3000F7F712F90004000C3FF49EAD18990029C1 +:109E400018BF088001200FB0BDE8F08F94F87C01A6 +:109E5000FCF7D1FC94F87C012946FCF7B0FB20B15B +:109E60000D9880F0010084F841010FB00020BDE89A +:109E7000F08F2DE9F843454C0246434F00266168B8 +:109E8000606A052A60D2DFE802F003464B4F5600B5 +:109E9000A07A002560B101216846FDF709FB9DF815 +:109EA000000042F210710002B0FBF1F201FB12055A +:109EB000F4F720FE4119A069FBF73DFEA06126746E +:109EC000032060754FF0010884F81480607AD0B9DF +:109ED000A06A80B1F4F70EFE0544F4F785FC411941 +:109EE000A06A884224BF401BA06204D2C4F8288024 +:109EF000F5F72BFE07E0207B04F11001FCF75FFB78 +:109F0000002808BFFFDF2684FCF739F87879BDE820 +:109F1000F843E8F7F9BFBDE8F843002100F0B3BD0E +:109F2000C1F88001BDE8F883D1F88001BDE8F843AD +:109F3000012100F0A8BD84F83060FCF720F87879A2 +:109F4000BDE8F843E8F7E0BFFFDFBDE8F8832DE99F +:109F5000F04F0E4C824683B020788B4601270025B7 +:109F6000094E4FF00209032804BF207B50457DD1E4 +:109F7000606870612078032818BFFFDF4FF0030886 +:109F8000BBF1080F73D203E0E0000020B80C002002 +:109F9000DFE80BF0040F32322D9999926562F5F7E4 +:109FA000ABF9002818BFFFDF86F8028003B0BDE8D8 +:109FB000F08FF4F77AFF68B9F4F716FC0546E0690C +:109FC000A84228BFE56105D2281A0421FCF7F7FD55 +:109FD000E56138B1F5F723FD002818BFFFDF03B0B6 +:109FE000BDE8F08F03B00020BDE8F04F41E703B0BB +:109FF000BDE8F04FFEF7FFBD2775257494F83000DB +:10A000004FF0010A58B14FF47A71A069FBF793FD44 +:10A01000A061002104F11000F7F71EF80EE0F4F73C +:10A0200069FD82465146A069FBF785FDA061514656 +:10A0300004F11000F7F710F800F1010A208C411C20 +:10A040000A293CBF50442084606830B1208C401CF9 +:10A050000A2828BF84F8159001D284F81580607A08 +:10A06000A8B9A06AE8B1F4F745FD01E02FE02AE0C5 +:10A070008046F4F7B9FB00EB0801A06A884224BFD0 +:10A08000A0EB0800A0620CD2A762F5F75EFD207B72 +:10A09000FCF74BF82570707903B0BDE8F04FE8F796 +:10A0A00033BF207B04F11001FCF789FA002808BFB8 +:10A0B000FFDF03B0BDE8F08F207BFCF736F825709A +:10A0C00003B0BDE8F08FFFDF03B0BDE8F08FBAF159 +:10A0D000200F28BFFFDFDFF8E886072138F81A00D5 +:10A0E000F9F7E4FE040008BFFFDFBAF1200F28BF34 +:10A0F000FFDF38F81A002188884218BFFFDF4FF0D1 +:10A10000200A7461BBF1080F80F06181DFE80BF079 +:10A110000496A0A099FEFDFCC4F88051F580C4F817 +:10A12000845194F8410138B9FCF71EF8D4F84C1169 +:10A13000FCF712FD00281BDCB4F83E11B4F87000E7 +:10A14000814206D1B4F8F410081AA4F8F6002046AB +:10A1500005E0081AA4F8F600B4F83E112046A4F869 +:10A160007010D4F86811C4F84C11C0F870111DE0DB +:10A17000B4F83C11B4F87000081AA4F8F600B4F86A +:10A180003C112046A4F87010D4F84C11C4F86811A2 +:10A19000C4F87011D4F85411C4F80011D4F858114F +:10A1A000C4F87411B4F85C11A4F8781102F008F93D +:10A1B000FBF7B4FF804694F86500FDF715FF4AF2FF +:10A1C000B12108444FF47A71B0FBF1F0D4F83411A6 +:10A1D00040F27122084461885143C0EB4100A0F174 +:10A1E000300AB8F1B70F98BF4FF0B70821460120E9 +:10A1F00004F0CFFA4044AAEB0000A0F21D38A246BA +:10A200002146012004F0C5FADAF824109C3081427E +:10A2100088BF0D1AC6F80C80454528BF4546B56075 +:10A22000D4F86C01A0F5D4703061FCF762FC84F8BE +:10A23000407186F8029003B0BDE8F08F02F0A6F9F5 +:10A2400001E0FEF7D8FC84F8407103B0BDE8F08F60 +:10A25000FBF78AFFD4F8702101461046FCF77CFC1E +:10A2600048B1628840F27123D4F834115A43C1EBEB +:10A270004201B0FBF1F094F87D100D290FD0B4F835 +:10A280007010B4F83E210B189A42AEBF501C401C0F +:10A290000844A4F83E0194F8420178B905E0B4F806 +:10A2A0003E01401CA4F83E0108E0B4F83E01B4F8B9 +:10A2B000F410884204BF401CA4F83E01B4F87A01AF +:10A2C0000DF1040B401CA4F87A01B4F89A00B4F81C +:10A2D0009810401AB4F87010401E08441FFA80F914 +:10A2E0000BE000231A462046CDF800B0FFF709FA2C +:10A2F00068B3012818BFFFDF48D0B4F83E11A9EBBE +:10A30000010000B2002802E053E047E05FE0E8DA35 +:10A31000082084F88D0084F88C70204601F012FE2D +:10A3200084F82C5194F87C514FF6FF77202D00D300 +:10A33000FFDF28F8157094F87C01FBF7F6FE84F82F +:10A340007CA1707903B0BDE8F04FE8F7DDBDA06EE9 +:10A35000002804BF03B0BDE8F08FB4F83E01B4F8A4 +:10A360009420801A01B20029DCBF03B0BDE8F08F51 +:10A37000B4F86C000144491E91FBF0F189B201FB75 +:10A380000020A4F8940003B0BDE8F08FB4F83E01BB +:10A39000BDF804100844A4F83E01AEE7FEF7E4FA65 +:10A3A000FEF729FC4FF0E020C0F8809203B0BDE832 +:10A3B000F08F94F82C01042818BFFFDF84F82C518B +:10A3C00094F87C514FF6FF77202DB2D3B0E7FFDF32 +:10A3D00003B0BDE8F08F10B5FA4C207850B10120E1 +:10A3E0006072F5F7D8FB2078032805D0207A002882 +:10A3F00008BF10BD0C2010BD207BFCF7FCF9207BB2 +:10A40000FCF765FC207BFBF790FE002808BFFFDF10 +:10A410000020207010BD2DE9F04FEA4F83B038784E +:10A4200001244FF0000840B17C720120F5F7B3FB26 +:10A430003878032818BF387A0DD0DFF88C9389F864 +:10A44000034069460720F9F7BAFC002818BFFFDF70 +:10A450004FF6FF7440E0387BFCF7CDF9387BFCF712 +:10A4600036FC387BFBF761FE002808BFFFDF87F86A +:10A470000080E2E7029800281CBF90F82C11002908 +:10A480002AD00088A0421CBFDFF834A34FF0200B75 +:10A490003AD00721F9F70AFD040008BFFFDF94F85E +:10A4A0007C01FCF714FC84F82C8194F87C514FF665 +:10A4B000FF76202D28BFFFDF2AF8156094F87C0175 +:10A4C000FBF733FE84F87CB169460720F9F777FC87 +:10A4D000002818BFFFDF12E06846F9F74EFC00289D +:10A4E000C8D011E0029800281CBF90F82C11002958 +:10A4F00005D00088A0F57F41FF39CAD104E0684645 +:10A50000F9F73BFC0028EDD089F8038087F830800C +:10A5100087F80B8003B00020BDE8F08FAA4948718E +:10A520000020887001220A7048700A71C870A5491D +:10A53000087070E7A449087070472DE9F84FA14CE6 +:10A5400006460F462078002862D1A048FBF792FD0E +:10A55000207320285CD04FF00308666084F80080E8 +:10A56000002565722572AEB1012106F58E70FCF7EB +:10A57000BEFF0620F9F742FC81460720F9F73EFCB2 +:10A5800096F81C114844B1FBF0F200FB1210401C7D +:10A5900086F81C01FBF7C2FD40F2F651884238BF35 +:10A5A00040F2F65000F5A0701FFA80F9F4F7A2FA15 +:10A5B000012680B3A672F4F717F9E061FBF7D4FD2A +:10A5C000824601216846FCF769FF9DF8000042F2CF +:10A5D00010710002B0FBF1F201FB120000EB090167 +:10A5E0005046FBF7A8FAA762A061267584F815808B +:10A5F0002574207B04F11001FBF7E1FF002808BF60 +:10A60000FFDF25840020F5F7C6FA0020BDE8F88FAB +:10A610000C20BDE8F88FFFE7E761FBF7A5FD494691 +:10A62000FBF789FAA061A57284F830600120FDF77C +:10A6300054FD4FF47A7100F2E140B0FBF1F0381AAA +:10A64000A0F5AB60A5626063CFE75F4948707047D3 +:10A650005D49087170475B4810B5417A00291CBFFD +:10A66000002010BD816A51B990F8301039B1416AAB +:10A67000406B814203D9F5F768FA002010BD012034 +:10A6800010BD2DE9F041504C0646E088401CE080AA +:10A69000D4E902516078D6F8807120B13A46284654 +:10A6A000F6F705FD0546A068854205D02169281A00 +:10A6B00008442061FCF71DFAA560AF4209D896F85E +:10A6C0002C01012805D0E078002804BF0120BDE856 +:10A6D000F0810020BDE8F08110B504460846FDF782 +:10A6E00083FC4AF2B12108444FF47A71B0FBF1F0D7 +:10A6F00040F2E241614300F54E7081428CBF081A7E +:10A70000002010BD70B5044682B0002084F84001DE +:10A7100094F8FB00002807BF94F82C01032802B02E +:10A7200070BDFBF721FDD4F8702101461046FCF7FF +:10A7300013FA0028DCBF02B070BD628840F27123BA +:10A74000D4F834115A43C1EB4201B0FBF1F0B4F834 +:10A750007010401C0844A4F83C01B4F8F400B4F8AC +:10A760003C21801A00B20028DCBF02B070BD01207D +:10A7700084F84201B4F89A00B4F8982001AE801A27 +:10A78000401E084485B212E00096B4F83C11002344 +:10A7900001222046FEF7B5FF002804BF02B070BDBD +:10A7A000012815D0022812BFFFDF02B070BDB4F837 +:10A7B0003C01281A00B20028BCBF02B070BDE3E71C +:10A7C000F00C0020B80C0020E00000204F9F01009A +:10A7D000B4F83C01BDF804100844A4F83C01E6E7D5 +:10A7E000F8B50422002506295BD2DFE801F0072630 +:10A7F0000319192A044680F82C2107E00446C948A9 +:10A80000C078002818BF84F82C210AD0FBF7B7FBCA +:10A81000A4F87A51B4F87000A4F83E0184F84251CB +:10A82000F8BD0095B4F8F410012300222046FEF78D +:10A8300068FF002818BFFFDFE8E7032180F82C112C +:10A84000F8BD0646876AB0F83401314685B201206A +:10A8500003F09FFF044696F86500FDF7C5FB4AF23A +:10A86000B12108444FF47A71B0FBF1F0718840F2E5 +:10A8700071225143C0EB4100401BA0F55A7501F015 +:10A880008AFE002818BF1E3DA74234BF2046384626 +:10A89000A84228BF2C4602D2A74228BF3C46746279 +:10A8A000F8BDFFDFF8BD2DE9F05F9E4EB1780229BB +:10A8B00006BFF1880029BDE8F09F7469C4F88401DF +:10A8C00094F86500FDF718FCD4F88411081AB168F3 +:10A8D0000144B160F1680844F060746994F8430180 +:10A8E000002808BFBDE8F09F94F82C01032818BF8A +:10A8F000BDE8F09F94F8655037780C2F28BFFFDF34 +:10A90000894E36F8178094F888710C2F28BFFFDF26 +:10A9100036F81700404494F8888187B2B8F10C0FDC +:10A9200028BFFFDF36F8180000F5C8601FFA80F86E +:10A930002846FDF7E1FBD4F884114FF0000A0E1A07 +:10A9400015F00C0F0ABF28464EF62830FDF74CFBD9 +:10A950004FF47A7900F2E730B0FBF9F0361A284666 +:10A96000FDF7CAFBD4F8001115F00C0FA1EB000B9A +:10A970000ABF28464EF62830FDF736FB4AF2B121D1 +:10A980000844B0FBF9F0ABEB0000A0F160017943A3 +:10A99000B1FBF8F1292202EB50006031A0EB51022B +:10A9A00000EB5100B24201D8B04201D8F1F774FB7C +:10A9B000608840F2E2414843394600F047F8C4F865 +:10A9C000340184F843A1BDE8F09F70B505465548B1 +:10A9D00090F802C0BCF1020F07BF406900F5C074D7 +:10A9E000524800F12404002904BF256070BD4FF4D3 +:10A9F0007A7601290DD002291CBFFFDF70BD1046F9 +:10AA0000FEF76EFC00F2E140B0FBF6F0281A206081 +:10AA100070BD1846FDF761FB00F2E140B0FBF6F0B7 +:10AA2000281A206070BD4148007800281CBF002013 +:10AA3000704710B50720F9F7D3F980F0010010BD79 +:10AA40003A480078002818BF0120704730B5024608 +:10AA50000020002908BF30BDA2FB0110490A41EACD +:10AA6000C051400A4C1C40F100000022D4F1FF31DB +:10AA700040F2A17572EB000038BFFFDF04F5F4600F +:10AA8000B0FBF5F030BD2DE9F843284C0025814698 +:10AA900084F83050D4F8188084F82C10E5722570B2 +:10AAA0000127277239466068F5F792FD6168C1F8A1 +:10AAB0007081267B81F87C61C1F88091C1F8748136 +:10AAC000B1F80080202E28BFFFDF194820F816803B +:10AAD000646884F82C510023A4F878511A4619466A +:10AAE00020460095FEF70DFE002818BFFFDFC4F8D2 +:10AAF0002851C4F8205184F82C71A4F83E51A4F8D0 +:10AB00003C5184F84251B4F87000401EA4F8700023 +:10AB1000A4F87A51FBF733FA02484079BDE8F843CC +:10AB2000E8F7F2B9E0000020E4620200B80C00206F +:10AB3000F00C0020012804D0022805D0032808D1F9 +:10AB400005E0012907D004E0022904D001E004292E +:10AB500001D000207047012070472DE9F0410E46DA +:10AB6000044603F08AFC0546204603F08AFC0446AE +:10AB7000F6F770FBFB4F010015D0386990F86420A0 +:10AB80008A4210D090F8C0311BB190F8C2312342F4 +:10AB90001FD02EB990F85D30234201D18A4218D8D7 +:10ABA00090F8C001A8B12846F6F754FB70B1396996 +:10ABB00091F86520824209D091F8C00118B191F84E +:10ABC000C301284205D091F8C00110B10120BDE8B1 +:10ABD000F0810020FBE730B5E24C85B0E069002849 +:10ABE0005FD0142200216846F3F751FC206990F8E9 +:10ABF0006500FDF7F9F94FF47A7100F5FA70B0FBD2 +:10AC0000F1F5206990F86500FDF776FA2844ADF873 +:10AC1000060020690188ADF80010B0F87010ADF89A +:10AC200004104188ADF8021090F8A20130B1A0697B +:10AC3000C11C039103F002FB8DF81000206990F80D +:10AC4000A1018DF80800E169684688472069002164 +:10AC500080F8A21180F8A1110399002921D090F861 +:10AC6000A01100291DD190F87C10272919D09DF83A +:10AC70001010039A002914D013780124FF2B12D04E +:10AC8000072B0ED102290CD15178FF2909D100BF21 +:10AC900080F8A0410399C0F8A4119DF8101080F825 +:10ACA000A31105B030BD1B29F2D9FAE770B5AD4C40 +:10ACB000206990F87D001B2800D0FFDF2069002567 +:10ACC00080F8A75090F8D40100B1FFDF206990F818 +:10ACD000A81041B180F8A8500188A0F8D81180F8D8 +:10ACE000D6510E2108E00188A0F8D81180F8D6517D +:10ACF000012180F8DA110D2180F8D4110088F9F7CC +:10AD000006FAF8F79FFE2079E8F7FEF8206980F848 +:10AD10007D5070BD70B5934CA07980072CD5A0787C +:10AD2000002829D162692046D37801690D2B01F1F1 +:10AD300070005FD00DDCA3F102034FF001050B2B77 +:10AD400019D2DFE803F01A1844506127182C183A7A +:10AD50006400152B6FD008DC112B4BD0122B5AD06E +:10AD6000132B62D0142B06D166E0162B71D0172B53 +:10AD700070D0FF2B6FD0FFDF70BD91F87F200123D3 +:10AD80001946F6F7FFF80028F6D12169082081F866 +:10AD90007F0070BD1079BDE8704001F090BC91F863 +:10ADA0007E00C00700D1FFDF01F048FC206910F8E9 +:10ADB0007E1F21F00101017070BD91F87D00102807 +:10ADC00000D0FFDF2069112180F8A75008E091F83A +:10ADD0007D00142800D0FFDF2069152180F8A750DE +:10ADE00080F87D1070BD91F87D00152800D0FFDF40 +:10ADF000172005E091F87D00152800D0FFDF19200D +:10AE0000216981F87D0070BDBDE870404EE7BDE866 +:10AE1000704001F028BC91F87C2001230021F6F756 +:10AE2000B1F800B9FFDF0E200FE011F87E0F20F01F +:10AE3000040008701DE00FE091F87C200123002140 +:10AE4000F6F7A0F800B9FFDF1C20216981F87C002B +:10AE500070BD12E01BE022E091F87E00C0F301100B +:10AE6000012800D0FFDF206910F87E1F21F01001BB +:10AE70000170BDE8704001F0E1BB91F87C20012336 +:10AE80000021F6F77FF800B9FFDF1F20DDE791F81A +:10AE90007D00212801D000B1FFDF2220B0E7BDE80E +:10AEA000704001F0D7BB2F48016991F87E2013074D +:10AEB00002D501218170704742F0080281F87E209E +:10AEC0008069C07881F8E10001F0AFBB10B5254C76 +:10AED00021690A88A1F8162281F8140291F8640009 +:10AEE00001F091FB216981F8180291F8650001F0E9 +:10AEF0008AFB216981F81902012081F812020020E1 +:10AF000081F8C0012079BDE81040E7F7FDBF10B51A +:10AF1000144C05212069FFF763FC206990F85A1052 +:10AF2000012908D000F5F57103F001FC2079BDE896 +:10AF30001040E7F7E9BF022180F85A1010BD10B5A4 +:10AF4000084C01230921206990F87C207030F6F725 +:10AF500019F848B12169002001F8960F087301F82B +:10AF60001A0C10BD000100200120A070F9E770B597 +:10AF7000F74D012329462869896990F87C200979D1 +:10AF80000E2A01D1122903D000241C2A03D004E088 +:10AF9000BDE87040D3E7142902D0202A07D008E08A +:10AFA00080F87C4080F8A240BDE87040AFE71629E9 +:10AFB00006D0262A01D1162902D0172909D00CE083 +:10AFC00000F87C4F80F82640407821280CD01A20C9 +:10AFD00017E090F87D20222A07D0EA69002A03D0E2 +:10AFE000FF2901D180F8A23132E780F87D4001F0DD +:10AFF00025FB286980F8974090F8C0010028F3D01D +:10B000000020BDE8704061E710B5D14C216991F88E +:10B010007C10202902D0262902D0A2E7FFF756FF94 +:10B020002169002081F87C0081F8A20099E72DE9D0 +:10B03000F843C74C206990F87C10202908D00027DD +:10B0400090F87D10222905D07FB300F17C0503E044 +:10B050000127F5E700F17D0510F8B01F41F004016C +:10B060000170A06903F015FA4FF00108002608B33B +:10B070003946A069FFF771FDE0B16A46A169206910 +:10B08000F6F7F7F890B3A06903F001FA2169A1F887 +:10B09000AA01B1F8701001F0AAFA40B32069282182 +:10B0A00080F88D1080F88C8058E0FFE70220A070B7 +:10B0B000BDE8F883206990F8C00110B11E20FFF7A9 +:10B0C00005FFAFB1A0692169C07881F8E20008FAF4 +:10B0D00000F1C1F3006000B9FFDF20690A2180F8A8 +:10B0E0007C1090F8A20040B9FFDF06E009E02AE0FA +:10B0F0002E7001F0A3FAFFF7D6FE206980F8976062 +:10B10000D6E7226992F8C00170B1B2F8703092F8B7 +:10B110006410B2F8C40102F5D572F6F79BF968B174 +:10B120002169252081F87C00206900F17D0180F8EB +:10B1300097608D4212D180F87D600FE00020FFF70C +:10B14000C5FE2E70F0E720699DF8001080F8AC1164 +:10B150009DF8011080F8AD1124202870206900F1BD +:10B160007D018D4203D1BDE8F84301F067BA80F854 +:10B17000A2609DE770B5764C01230B21206990F801 +:10B180007D207030F5F7FEFE202650BB206901239C +:10B19000002190F87D207030F5F7F4FE0125F0B124 +:10B1A000206990F87C0024281BD0A06903F04FF997 +:10B1B000C8B1206990F8B01041F0040180F8B010D7 +:10B1C000A1694A7902F0070280F85D20097901F04F +:10B1D000070180F85C1090F8C1311BBB06E0A57038 +:10B1E00036E6A67034E6BDE870405CE690F8C03103 +:10B1F000C3B900F164035E788E4205D1197891429B +:10B2000002D180F897500DE000F503710D700288AF +:10B210004A8090F85C200A7190F85D0048712079AE +:10B22000E7F772FE2169212081F87D00BDE87040BA +:10B2300001F0FBB9F8B5464C206990F87E0010F09B +:10B24000300F04D0A07840F00100A070F8BDA069D4 +:10B2500003F0E2F850B3A06903F0D8F80746A069FC +:10B2600003F0D8F80646A06903F0CEF80546A069B9 +:10B2700003F0CEF801460097206933462A46303065 +:10B2800003F0BFF9A079800703D56069C07814285E +:10B290000FD0216991F87C001C280AD091F85A003F +:10B2A00001280ED091F8B70158B907E0BDE8F84081 +:10B2B000F9E52169012081F85A0002E091F8B60110 +:10B2C00030B1206910F87E1F41F0100101700EE0CE +:10B2D00091F87E0001F5FC7240F0200081F87E00BC +:10B2E00031F8300B03F017FA2079E7F70DFEBDE8CF +:10B2F000F84001F09AB970B5154C206990F87E10AD +:10B30000890707D590F87C20012308217030F5F7D4 +:10B3100039FEF8B1206990F8AA00800712D4A0691C +:10B3200003F056F8216981F8AB00A06930F8052FC9 +:10B33000A1F8AC204088A1F8AE0011F8AA0F40F0A7 +:10B3400002000870206990F8AA10C90705D011E022 +:10B35000000100200120A0707AE590F87E008007AF +:10B3600000D5FFDF206910F87E1F41F00201017057 +:10B3700001F05BF92069002590F87C10062906D1C0 +:10B3800080F87C5080F8A2502079E7F7BDFD206955 +:10B3900090F8A8110429DFD180F8A8512079E7F7A7 +:10B3A000B3FD206990F87C100029D5D180F8A25017 +:10B3B0004EE570B5FB4C01230021206990F87D20FB +:10B3C0007030F5F7DFFD012578B9206990F87D2010 +:10B3D000122A0AD0012305217030F5F7D3FD10B1F0 +:10B3E0000820A07034E5A57032E5206990F8A80027 +:10B3F00008B901F01AF92169A06901F5847102F018 +:10B40000C8FF2169A069D83102F0CEFF206990F809 +:10B41000DC0100B1FFDF21690888A1F8DE0101F538 +:10B42000F071A06902F0A3FF2169A06901F5F47130 +:10B4300002F0A5FF206980F8DC51142180F87D100E +:10B440002079BDE87040E7F75FBD70B5D54C0123AA +:10B450000021206990F87D207030F5F793FD0125DB +:10B46000A8B1A06902F04FFF98B1A0692169B0F8B6 +:10B470000D00A1F8AA01B1F8701001F0B8F858B1A8 +:10B480002069282180F88D1080F88C50E0E4A570A8 +:10B49000DEE4BDE8704006E5A0692169027981F823 +:10B4A000AC21B0F80520A1F8AE2102F01FFF216900 +:10B4B000A1F8B001A06902F01CFF2169A1F8B20156 +:10B4C000A06902F01DFF2169A1F8B4010D2081F8E7 +:10B4D0007D00BDE47CB5B34CA079C00738D0A0692D +:10B4E00001230521C578206990F87D207030F5F79B +:10B4F00049FD68B1AD1E0A2D06D2DFE805F0090945 +:10B500000505090905050909A07840F00800A070A3 +:10B51000A07800281CD1A06902F0BEFE00286ED0E1 +:10B52000A0690226C5781DB1012D01D0162D18D1B4 +:10B53000206990F87C00F5F70DFD90B1216991F834 +:10B540007C001F280DD0202803D0162D16D0A67001 +:10B550007CBD262081F87C00162D02D02A20FFF722 +:10B56000B5FC0C2D5BD00CDC0C2D48D2DFE805F0CF +:10B5700036331F48BEBE4BB55ABE393C2020A070A2 +:10B580007CBD0120142D6ED008DC0D2D6CD0112D4A +:10B590006BD0122D6ED0132D31D168E0152D7FD0D8 +:10B5A000162D6FD0182D6ED0FF2D28D198E0206970 +:10B5B0000123194690F87F207030F5F7E3FC00284E +:10B5C00008D1A06902F0CCFE216981F88E01072024 +:10B5D00081F87F008CE001F0EDF889E0FFF735FF9E +:10B5E00086E001F0C7F883E0206990F87D1011290A +:10B5F00001D0A6707CE0122180F87D1078E075E023 +:10B60000FFF7D7FE74E0206990F87D001728F0D18D +:10B6100001F014F821691B2081F87D0068E0FFF734 +:10B620006AFE65E0206990F87E00C00703D0A0782C +:10B6300040F0010023E06946A06902F0D0FE9DF8C9 +:10B64000000000F02501206900F8B01F9DF80110EE +:10B6500001F04901417000F0E8FF206910F87E1FF9 +:10B6600041F0010117E018E023E025E002E0FFF7D8 +:10B6700066FC3DE0216991F87E10490704D5A07071 +:10B6800036E00DE00FE011E000F0CFFF206910F888 +:10B690007E1F41F0040101702AE0FFF7CBFD27E097 +:10B6A00001F030F824E0FFF765FD21E0FFF7BFFC73 +:10B6B0001EE0A06900790DE0206910F8B01F41F08C +:10B6C00004010170A06902F0F7FE162810D1A069EC +:10B6D00002F0F6FEFFF798FC0AE0FFF748FC07E0EF +:10B6E000E16919B1216981F8A20101E0FFF7DBFBF3 +:10B6F0002169F1E93002401C42F10002C1E9000277 +:10B700007CBD70B5274CA07900074AD5A0780028E9 +:10B7100047D1206990F8E400FE2800D1FFDF2069BE +:10B72000FE21002580F8E41090F87D10192906D13B +:10B7300080F8A75000F082FF206980F87D502069D2 +:10B7400090F87C101F2902D0272921D119E090F808 +:10B750007D00F5F7FFFB78B120692621012380F8F1 +:10B760007C1090F87D200B217030F5F70BFC78B938 +:10B770002A20FFF7ABFB0BE02169202081F87C0039 +:10B7800006E0012180F8A11180F87C5080F8A250D9 +:10B79000206990F87F10082903D10221217080F8D8 +:10B7A000E41021E40001002010B5FD4C216991F85E +:10B7B000AC210AB991F8642081F8642091F8AD2198 +:10B7C0000AB991F8652081F8652010B10020FFF7D3 +:10B7D0007DFB206902F041FF002806D02069BDE80A +:10B7E000104000F5F57102F0A2BF16E470B5EC4C04 +:10B7F00006460D46206990F8E400FE2800D0FFDFE1 +:10B800002269002082F8E46015B1A2F8A400E7E400 +:10B8100022F89E0F01201071E2E470B5E04C012384 +:10B820000021206990F87C207030F5F7ABFB0028F0 +:10B830007BD0206990F8B61111B190F8B71139B1E9 +:10B8400090F8C01100296FD090F8C11119B36BE0C6 +:10B8500090F87D1024291CD090F87C10242918D051 +:10B860005FF0000300F5D67200F5DB7102F096FE82 +:10B870002169002081F8B60101461420FFF7B6FFC8 +:10B88000216901F13000C28A21F8E62F408B4880FF +:10B8900050E00123E6E790F87D2001230B21703072 +:10B8A000F5F770FB68BB206990F8640000F0ABFE10 +:10B8B0000646206990F8650000F0A5FE054620695F +:10B8C00090F8C2113046FFF735F9D8B1206990F8E9 +:10B8D000C3112846FFF72EF9A0B12269B2F87030E3 +:10B8E00092F86410B2F8C40102F5D572F5F7B2FD12 +:10B8F00020B12169252081F87C001BE00020FFF7A2 +:10B90000E5FA11E020690123032190F87D207030D1 +:10B91000F5F738FB40B920690123022190F87D201A +:10B920007030F5F72FFB08B1002059E400211620F4 +:10B93000FFF75CFF012053E410B5E8BB984C206989 +:10B9400090F87E10CA0702D00121092052E08A0730 +:10B950000AD501210C20FFF749FF206910F8AA1F22 +:10B9600041F00101017047E04A0702D5012113208F +:10B9700040E00A0705D510F8E11F417101210720B9 +:10B9800038E011F0300F3BD090F8B711A1B990F822 +:10B99000B611E1B190F87D1024292FD090F87C10D9 +:10B9A00024292BD05FF0000300F5D67200F5DB717F +:10B9B00002F0F4FD216900E022E011F87E0F20F092 +:10B9C000200040F010000870002081F83801206944 +:10B9D00090F87E10C90613D502F03FFEFFF797FAE4 +:10B9E000216901F13000C28A21F8E62F408B48809E +:10B9F00001211520FFF7FAFE0120F6E60123D3E727 +:10BA00000020F2E670B5664C206990F8E410FE293B +:10BA100078D1A178002975D190F87F2001231946AB +:10BA20007030F5F7AFFA00286CD1206990F88C11CE +:10BA300049B10021A0F89C1090F88D1180F8E61013 +:10BA4000002102205BE090F87D200123042170306A +:10BA5000F5F798FA0546FFF76FFF002852D1284600 +:10BA600000F00CFF00284DD120690123002190F83F +:10BA70007C207030F5F786FA78B120690123042123 +:10BA800090F87D207030F5F77DFA30B9206990F894 +:10BA9000960010B10021122031E0206990F87C203E +:10BAA0000A2A0DD0002D2DD1012300217030F5F789 +:10BAB00069FA78B1206990F8A81104290AD105E043 +:10BAC00010F8E21F01710021072018E090F8AA0089 +:10BAD000800718D0FFF7A1FE002813D120690123A9 +:10BAE000002190F87C207030F5F74CFA002809D03E +:10BAF000206990F8A001002804D00021FF20BDE8B3 +:10BB0000704073E609E000210C20FFF76FFE20690A +:10BB100010F8AA1F41F0010101701DE43EB5054671 +:10BB20006846FDF7ABFC00B9FFDF22220021009838 +:10BB3000F2F7ADFC0321009802F096FB0098017823 +:10BB400021F010010170294602F0B3FB144C0D2DB9 +:10BB500043D00BDCA5F102050B2D19D2DFE805F06F +:10BB600022184B191922185718192700152D5FD0C4 +:10BB700008DC112D28D0122D0BD0132D09D0142D37 +:10BB800006D155E0162D2CD0172D6AD0FF2D74D07C +:10BB9000FFDFFDF786FC002800D1FFDF3EBD00007F +:10BBA000000100202169009891F8E61017E0E26892 +:10BBB00000981178017191884171090A8171518849 +:10BBC000C171090A0172E4E70321009802F072FCD6 +:10BBD0000621009802F072FCDBE700980621017153 +:10BBE000D7E70098D4F8101091F8C221027191F8AB +:10BBF000C3114171CDE72169009801F5887102F008 +:10BC0000D7FB21690098DC3102F0DCFBC1E7FA497F +:10BC1000D1E90001CDE90101206901A990F8B00046 +:10BC200000F025008DF80400009802F006FCB0E753 +:10BC30002069B0F84810009802F0D6FB2069B0F8EF +:10BC4000E810009802F0D4FB2069B0F84410009886 +:10BC500002F0D2FB2069B0F8E610009802F0D0FBA9 +:10BC600097E7216991F8C00100280098BCD111F82C +:10BC7000642F02714978BCE7FFE7206990F8A3219F +:10BC8000D0F8A411009802F022FB82E7DB4810B53F +:10BC9000006990F8821041B990F87D2001230621B7 +:10BCA0007030F5F76FF9002800D001209DE570B5E0 +:10BCB000D24D286990F8801039B1012905D00229A8 +:10BCC00006D0032904D0FFDF03E4B0F8F41037E016 +:10BCD00090F87F10082936D0B0F89810B0F89A2064 +:10BCE00000248B1C9A4206D3511A891E0C04240C82 +:10BCF00001D0641EA4B290F8961039B190F87C205F +:10BD0000012309217030F5F73DF940B3FFF7BEFF7D +:10BD100078B129690020B1F89020B1F88E108B1C01 +:10BD20009A4203D3501A801E00D0401EA04200D277 +:10BD300084B20CB1641EA4B22869B0F8F410214496 +:10BD4000A0F8F0102DE5B0F898100329BDD330F815 +:10BD5000701F428D1144491CA0F8801021E5002479 +:10BD6000EAE770B50C4605464FF4087200212046FC +:10BD7000F2F78DFB258014E5F8F7A2B92DE9F04123 +:10BD80000D4607460721F8F791F8041E3CD094F8B9 +:10BD9000C8010026A8B16E70092028700BE0268427 +:10BDA00084F8C861D4F8CA016860D4F8CE01A860EC +:10BDB000B4F8D201A88194F8C8010028EFD12E71FF +:10BDC000AEE094F8D40190B394F8D4010D2813D0C8 +:10BDD0000E2801D0FFDFA3E02088F8F798F9074686 +:10BDE000F7F745FE78B96E700E20287094F8D601EA +:10BDF00028712088E88014E02088F8F788F9074641 +:10BE0000F7F735FE10B10020BDE8F0816E700D200F +:10BE1000287094F8D60128712088E88094F8DA0117 +:10BE2000287284F8D4613846F7F71BFE78E0FFE704 +:10BE300094F80A0230B16E701020287084F80A62FB +:10BE4000AF806DE094F8DC0190B16E700A2028702C +:10BE50002088A880D4F8E011C5F80610D4F8E411C1 +:10BE6000C5F80A10B4F8E801E88184F8DC6157E00D +:10BE700094F8040270B16E701A20287005E000BFBB +:10BE800084F80462D4F80602686094F8040200287A +:10BE9000F6D145E094F8EA0188B16E70152028705B +:10BEA00008E000BF84F8EA6104F5F6702B1D07C8AE +:10BEB00083E8070094F8EA010028F3D130E094F811 +:10BEC000F80170B16E701C20287084F8F861D4F805 +:10BED000FA016860D4F8FE01A860B4F80202A881F3 +:10BEE0001EE094F80C0238B11D20287084F80C6212 +:10BEF000D4F80E02686013E094F81202002883D090 +:10BF00006E701620287007E084F81262D4F81402CC +:10BF10006860B4F81802288194F812020028F3D15E +:10BF2000012071E735480021C16101620846704770 +:10BF300030B5324D0C46E860FFF7F4FF00B1FFDF8B +:10BF40002C7130BD002180F87C1080F87D1080F8C5 +:10BF5000801090F8FB1009B1022100E00321FEF7E8 +:10BF60003FBC2DE9F041254C0546206909B100216F +:10BF700004E0B0F80611B0F8F6201144A0F806115C +:10BF800090F88C1139B990F87F2001231946703050 +:10BF9000F4F7F8FF30B1206930F89C1FB0F85A2050 +:10BFA00011440180206990F8A23033B1B0F89E109E +:10BFB000B0F8F6201144A0F89E1090F9A670002F5A +:10BFC00006DDB0F8A410B0F8F6201144A0F8A410D3 +:10BFD00001213D2615B180F88D6017E02278022AF4 +:10BFE0000ED0012A15D0A2784AB380F88C1012F036 +:10BFF000140F11D01E2117E0FC6202000001002086 +:10C0000090F8E620062A3CD016223AE080F88C1000 +:10C0100044E090F88E2134E0110702D580F88D605D +:10C020003CE0910603D5232180F88D1036E090077F +:10C0300000D1FFDF21692A2081F88D002AE02BB191 +:10C04000B0F89E20B0F8A0309A4210D2002F05DD43 +:10C05000B0F8A420B0F8A0309A4208D2B0F89C30D2 +:10C06000B0F89A20934204D390F88C310BB122227D +:10C0700007E090F880303BB1B0F89830934209D394 +:10C08000082280F88D20C1E7B0F89820062A01D355 +:10C090003E22F6E7206990F88C1019B12069BDE8BE +:10C0A000F0414FE7BDE8F0410021FEF799BB2DE9D3 +:10C0B000F047FF4C81460D4620690088F8F739F8B3 +:10C0C000060000D1FFDFA0782843A070A0794FF0D0 +:10C0D00000058006206904D5A0F8985080F8045126 +:10C0E00003E030F8981F491C0180FFF7CFFD4FF0A7 +:10C0F000010830B3E088000506D5206990F8821069 +:10C1000011B1A0F88E501CE02069B0F88E10491CC7 +:10C1100089B2A0F88E10B0F890208A4201D3531A49 +:10C1200000E0002327897F1DBB4201D880F896805C +:10C13000914206D3A0F88E5080F80A822079E6F763 +:10C14000E3FEA0794FF0020710F0600F0ED02069D7 +:10C1500090F8801011B1032908D102E080F88080A6 +:10C1600001E080F880700121FEF73AFB206990F829 +:10C170008010012904D1E188C90501D580F88070BB +:10C18000B9F1000F72D1E188890502D5A0F81851E4 +:10C1900004E0B0F81811491CA0F8181100F035FBA4 +:10C1A000FEF719FDFFF72EFC2769B7F8F800401CD1 +:10C1B000A7F8F80097F8FC0028B100F01BFFA8B121 +:10C1C000A7F8F85012E000F012FF08B1A7F8F850F5 +:10C1D00000F015FF50B197F80401401CC0B287F879 +:10C1E0000401022802D927F8F85F3D732069012372 +:10C1F000002190F87D207030F4F7C4FE20B920694A +:10C2000090F87D000C2859D120690123002190F875 +:10C210007C207030F4F7B6FE48B32069012300217A +:10C2200090F87F207030F4F7ADFE00B3206990F8ED +:10C230008010022942D190F80401C0B93046F7F7C6 +:10C24000E6F9A0B1216991F8E400FE2836D1B1F8F1 +:10C25000F200012832D981F8FA80B1F89A00B1F8D9 +:10C260009820831E9A4203DB012004E032E025E09F +:10C27000801A401E80B2B1F8F82023899A4201D377 +:10C28000012202E09A1A521C92B2904200D9104642 +:10C29000012801D181F8FA5091F86F2092B98A6E85 +:10C2A00082B1B1F89420B1F87010511A09B2002986 +:10C2B00008DD884200DB084680B203E021690120E6 +:10C2C00081F8FA502169B1F870201044A1F8F40007 +:10C2D000FFF7EDFCE088C0F340214846FFF741FE40 +:10C2E000206980F8FB50BDE8F047FDF7FCB87049C5 +:10C2F00002468878CB78184312D10846006942B1CB +:10C300008979090703D590F87F00082808D0012013 +:10C310007047B0F84C10028E914201D8FEF7B1B9C7 +:10C320000020704770B5624C05460E46E0882843F1 +:10C33000E080A80703D5E80700D0FFDF6661EA07C1 +:10C340004FF000014FF001001AD0A661F278062AE2 +:10C3500002D00B2A14D10AE0226992F87D30172B03 +:10C360000ED10023E2E92E3302F8370C08E02269EF +:10C3700092F87D30112B03D182F8811082F8A80049 +:10C38000AA0718D56269D278052A02D00B2A12D1E1 +:10C390000AE0216991F87D20152A0CD10022E1E9FB +:10C3A000302201F83E0C06E0206990F87D20102A2A +:10C3B00001D180F88210280601D50820E07083E4BE +:10C3C0002DE9F84301273A4C002567F30701E58082 +:10C3D000A570E570257020618946804680F8FB7065 +:10C3E0000088F7F7A6FE00B9FFDF20690088FDF797 +:10C3F00042F820690088FDF764F82069B0F8F2106F +:10C4000071B190F8E410FE290FD190F88C1189B128 +:10C4100090F87F20012319467030F4F7B3FD78B10E +:10C42000206990F8E400FE2804D0206990F8E40028 +:10C43000FFF774FB206990F8FD1089B1258118E0A1 +:10C440002069A0F89C5090F88D1180F8E61000212A +:10C450000220FFF7CBF9206980F8FA500220E7E7C5 +:10C4600090F8C81119B9018C8288914200D881884E +:10C47000218130F8F61F491E8EB230F8021F314478 +:10C4800020F86019018831440180FFF7FFFB20B1DB +:10C49000206930F88E1F314401802069B0F8F21015 +:10C4A000012902D8491CA0F8F2102EB102E00000C8 +:10C4B0000001002080F8045180F8FA5090F87D10B7 +:10C4C0000B2901D00C2916D1B0F87020B0F8AA3190 +:10C4D000D21A12B2002A0EDBD0F8AC11816090F8AB +:10C4E000B01101730321F4F773F8206980F87D50CF +:10C4F00080F8B27026E0242910D1B0F87010B0F89E +:10C50000AA21891A09B2002908DB90F8C001FFF7B7 +:10C510004BF9206900F87D5F857613E090F87C1078 +:10C52000242901D025290DD1B0F87010B0F8AA0146 +:10C53000081A00B2002805DB0120FFF735F9206951 +:10C5400080F87C5020690146B0F8F6207030F4F78E +:10C55000B2FAFC480090FC4BFC4A4146484600F0C9 +:10C560007DFC216A11B16078FCF7B5FA20690123DE +:10C57000052190F87D207030F4F704FD002803D0E9 +:10C58000BDE8F84300F0FDB9BDE8F88300F015BD43 +:10C59000EF49C8617047EE48C069002800D001200B +:10C5A0007047EB4A50701162704710B5044600881E +:10C5B000A4F8CC01B4F8B001A4F8CE01B4F8B201EB +:10C5C000A4F8D001B4F8B401A4F8D201012084F891 +:10C5D000C801DF480079E6F797FC02212046F3F70F +:10C5E000F7FF002004F87D0F0320E07010BD401A13 +:10C5F00000B247F6FE71884201DC002801DC012010 +:10C6000070470020704710B5012808D0022808D0D4 +:10C61000042808D0082806D0FFDF204610BD0124DA +:10C62000FBE70224F9E70324F7E7C9480021006982 +:10C6300020F8A41F8178491C81707047C44800B558 +:10C64000016911F8A60F401E40B20870002800DAF8 +:10C65000FFDF00BDBE482721006980F87C10002163 +:10C6600080F8A011704710B5B94C206990F8A81156 +:10C67000042916D190F87C20012300217030F4F7B2 +:10C6800081FC00B9FFDF206990F8AA10890703D464 +:10C69000062180F87C1004E0002180F8A21080F8C8 +:10C6A000A811206990F87E00800707D5FFF7C6FF24 +:10C6B000206910F87E1F21F00201017010BDA4490D +:10C6C00010B5096991F87C200A2A09D191F8E22075 +:10C6D000824205D1002081F87C0081F8A20010BDC3 +:10C6E00091F87E20130706D522F0080081F87E001D +:10C6F000BDE81040A2E7FF2801D0FFDF10BDBDE874 +:10C700001040A7E7F8B5924C01230A21206990F860 +:10C710007C207030F4F736FC38B3A06901F07CFE61 +:10C72000C8B1A06901F072FE0746A06901F072FE6F +:10C730000646A06901F068FE0546A06901F068FEA2 +:10C7400001460097206933462A46303001F059FFF0 +:10C75000206901F082FF2169002081F8A20081F8A0 +:10C760007C00BDE8F840FEF7D2BBA07840F00100A5 +:10C77000A070F8BD10B5764C01230021206990F817 +:10C780007D207030F4F7FEFB30B1FFF74EFF2169DA +:10C79000102081F87D0010BD20690123052190F84B +:10C7A0007D207030F4F7EEFB08B1082000E0012096 +:10C7B000A07010BD70B5664C01230021206990F86F +:10C7C0007D207030F4F7DEFB012588B1A06901F00F +:10C7D000C4FD2169A1F8AA01B1F87010FFF707FFA5 +:10C7E00040B12069282180F88D1080F88C50E6E552 +:10C7F000A570E4E52169A06901F5D67101F0A8FDF5 +:10C8000021690B2081F87D00D9E510B5FEF779FF8D +:10C81000FEF760FE4E4CA079400708D5A07830B9ED +:10C82000206990F87F00072801D101202070FEF7D1 +:10C8300071FAA079C00609D5A07838B9206990F8B6 +:10C840007D100B2902D10C2180F87D10E0780007C3 +:10C850000ED520690123052190F87D207030F4F772 +:10C8600091FB30B10820A0702169002081F8D4012B +:10C8700010BDBDE81040002000F0C4BB10B5344C22 +:10C88000216991F87D2048B3102A06D0142A07D0D8 +:10C89000152A1AD01B2A2CD11AE001210B2019E0ED +:10C8A000FAF702FE0C2817D32069082100F58870DA +:10C8B000FAF7FEFD28B120690421DC30FAF7F8FD13 +:10C8C00000B9FFDF0121042004E000F017F803E0C5 +:10C8D00001210620FEF78AFF012010BD212A08D180 +:10C8E00091F8970038B991F8C00110B191F8C101E1 +:10C8F00008B1002010BD01211720EBE770B5144CE2 +:10C900000025206990F88F1101290AD002292ED123 +:10C9100090F8A810F1B1062180F8E610012102205C +:10C9200020E090F8D411002921D100F1C80300F5CE +:10C930008471002200F5C870F4F796FA01210520F1 +:10C9400010E00000AFC00100EFC2010025C30100EC +:10C950000001002090F8B000400701D5112000E050 +:10C960000D200121FEF742FF206980F88F5126E556 +:10C9700030B5FB4C05462078002818BFFFDFE57175 +:10C9800030BDF7490120887170472DE9F14FF54D11 +:10C990002846446804F1700794F86510608F94F895 +:10C9A0008280268F082978D0F4F797FBB8F1000F22 +:10C9B00004BF001D80B2864238BF304600F0FF0839 +:10C9C000DFF89C93E848C9F8240009F134006E6848 +:10C9D000406800F1700A90F882B096F86510358FC3 +:10C9E000708F08295DD0F4F778FB00BFBBF1000F12 +:10C9F00004BF001D80B2854238BF2846C0B29AF8F5 +:10CA00001210002918BF04210844C0B296F865101E +:10CA1000FBF735FCB87C002847D007F15801D24815 +:10CA200091E80E1000F5027585E80E10B96EC0F899 +:10CA30002112F96EC0F8251200F58170FBF7DBFFBB +:10CA4000C848007800280CBF0120002080F00101B8 +:10CA5000C6480176D7E91412C0E90412A0F5837222 +:10CA6000D9F82410FBF7F5F994F86500012808BF00 +:10CA700000220CD0022808BF012208D0042808BFD9 +:10CA8000032204D008281ABFFFDF002202224146F9 +:10CA90000120FBF7F9F90EE0FFE70421F4F71DFB95 +:10CAA00084E70421F4F719FBA0E7D9F82400FBF789 +:10CAB000A2FFFBF715FA009850B994F8650094F8B6 +:10CAC000661010F00C0F08BF00219620FBF7B4FF92 +:10CAD00094F8642001210020FCF76BF894F82C00F6 +:10CAE000012808BFFCF735F8022089F80000FCF7A0 +:10CAF0003FFC002818BFFFDFBDE8F88F2DE9F04F9D +:10CB0000DFF860A28BB050469AF800204068AAF186 +:10CB10001401059190F8751000F1700504464FF06E +:10CB200008080127AAF13406A1B3012900F0068103 +:10CB3000022900F00781032918BFFFDF00F01881E8 +:10CB4000306A0423017821F008010170AA7908EA0B +:10CB5000C202114321F004010170EA7903EA820262 +:10CB6000114321F01001017095F80590F06AF6F775 +:10CB70005EFD8046FCF7C9FCB9F1020F00F00081B0 +:10CB8000B9F1010F00F00081B9F1030F00F000814D +:10CB900000F003B9FFE795F80CC04FF002094FF021 +:10CBA000000BBCF1240F1CBF6B7B242B08D0BCF105 +:10CBB0001F0F18BFBCF1200F2AD0222B4DD077E0D9 +:10CBC00094F864109AB190F8AC01002874D0082948 +:10CBD00018BF042969D0082818BF042865D0012986 +:10CBE00018BF012853D000BF4FF0020164E090F855 +:10CBF0001201002860D0082918BF042955D0082840 +:10CC000018BF042851D0012918BF01283FD0EBE7F5 +:10CC1000222B22D0002A4BD090F8C20194F8641045 +:10CC200010F0040F18BF40460CD0082918BF042983 +:10CC30003BD0082818BF042837D0012918BF012885 +:10CC400025D0D1E710F0010F18BF3846EDD110F014 +:10CC5000020F18BF4846E8D12EE04AB390F8C2212F +:10CC600090F85D0094F8641002EA000010F0040FE0 +:10CC700018BF40460ED0082918BF042915D008282F +:10CC800018BF042811D0012918BF0128ACD14FF0DA +:10CC9000010111E010F0010F18BF3846EBD110F080 +:10CCA000020F18BF4846E6D106E04FF0080103E046 +:10CCB00094F864100429F8D0A08E11F00C0F18BF5E +:10CCC0004FF42960F4F709FA218E814238BF0846F3 +:10CCD000ADF80400A4F84C000598FCF7F5FB60B132 +:10CCE0007289316A42F48062728172694FF48060A5 +:10CCF000904703206871EF7022E709AA01A9F06A42 +:10CD0000F6F7CFFB306210B195F8371021B10598D6 +:10CD1000FCF7AEFB6F7113E79DF8241031B9A0F852 +:10CD200000B080F802B0012101F09EFABDF80410B5 +:10CD3000306A01F0C7FB85F8059001E70598FCF71C +:10CD400097FBFDE6B4F84C00ADF8040009AA01A970 +:10CD5000F06AF6F7A6FB3062002808BFFFDFEFE6B7 +:10CD60002401002058010020300D0020380F002041 +:10CD70000598FCF7A9FB002808BFFFDFE0E600BF2D +:10CD800030EA080009D106E030EA080005D102E0E7 +:10CD9000B8F1000F01D0012100E00021306A0278D3 +:10CDA00042EA01110170697C00291CBF69790129DF +:10CDB0003BD005F15801FD4891E80E1000F50278CE +:10CDC00088E80E10A96EC0F82112E96EC0F825128D +:10CDD00000F58170FBF70FFE9AF8000000280CBFE9 +:10CDE00001210021F2480176D5E91212C0E90412AE +:10CDF000A0F58371326AFBF72CF894F864000128DF +:10CE000008BF00220CD0022808BF012208D0042845 +:10CE100008BF032204D008281ABFFFDF0022022225 +:10CE2000FB210020FBF730F803E0FBF7E4FDFBF704 +:10CE300057F8012194F865200846FBF7BAFE3771D0 +:10CE4000306A0188F181807830743770FCF799FA84 +:10CE5000002818BFFFDF0BB0BDE8F08F2DE9F043CD +:10CE6000D44D87B081462878DDF838801E461746B5 +:10CE70000C4628B9002F1CBF002EB8F1000F00D1BE +:10CE8000FFDFC5F81C80C5E90D94C5E905764FF0B4 +:10CE90000000A8716871E870A8702871C64E68819A +:10CEA000A881307804F170072088F7F742F9E8622A +:10CEB0002088F7F72CF92863FBF705FA94F9670047 +:10CEC000FBF7DAFA04F11200FBF76CFD04F10E0037 +:10CED000FBF7D8FA307800280CBF03200120FBF7BD +:10CEE00087FDB64890E80E108DE80E10D0E90410CA +:10CEF000CDE90410307800280CBFB148B148049047 +:10CF00006846FBF763FDF87EFBF7C4FAFBF76AFDA2 +:10CF100094F86F0078B9A06E68B1B88C39888842EF +:10CF200009D1B4F86C1001220844B88494F86E005A +:10CF3000A16EF8F7D8FE3078002804BFFF2094F8DF +:10CF400064401AD094F8651097F81280258F608F8E +:10CF5000082926D0F4F7C1F8B8F1000F04BF001D6E +:10CF600080B2854238BF2846C0B2B97C002918BFBC +:10CF70000421084494F86540C0B22146FBF77FF9CC +:10CF80003078214688B10120FBF74BFB7068D0F860 +:10CF90000001FBF733FD0120FFF7F7FC07B0BDE808 +:10CFA000F0830421F4F799F8D6E70020FBF739FB6A +:10CFB000FFF7A4FD07B0BDE8F0837F4800B5017816 +:10CFC0003438007819B1022818BFFFDF00BD0128EE +:10CFD00018BFFFDF00BD774810B50078022818BFE2 +:10CFE000FFDFBDE8104000F070BA00F06EBA714883 +:10CFF000007970476F488089C0F3002070476D4802 +:10D00000C07870472DE9F04706006B48694D4068CD +:10D0100000F17004686A90F8019018BF012E03D1E6 +:10D02000296B07F0F1FF6870687800274FF001085E +:10D03000A0B101283CD0022860D003281CBFFFDF2C +:10D04000BDE8F087012E08BFBDE8F087286BF6F732 +:10D05000E3FCE879BDE8F047E5F756BF012E14D0B0 +:10D06000A86A002808BFFFDF2889C21CD5E909107B +:10D07000F1F7E3F9A86A686201224946286BF6F7DE +:10D0800047FB022E08BFBDE8F087D4E91401401C1D +:10D0900041F10001C4E91401E079012801D1E771EF +:10D0A00001E084F80780E879BDE8F047E5F72CBF98 +:10D0B000012E14D0A86A002808BFFFDF2889C21CEF +:10D0C000D5E90910F1F7B9F9A86A68620022494662 +:10D0D000286BF6F71DFB022E08BFBDE8F087D4E9E8 +:10D0E0001410491C40F10000C4E91410E079012833 +:10D0F0000CBFE77184F80780BDE8F087012E06D0E9 +:10D10000286BF6F789FC022E08BFBDE8F087D4E94A +:10D110001410491C40F10000C4E91410E079012802 +:10D12000BFD1BCE72DE9F041234D2846A5F13404D9 +:10D13000406800F170062078012818BFFFDFB07842 +:10D140000127002158B1B1706289042042F0040225 +:10D150006281626990472878002818BF3771216A78 +:10D160000322087832EA000009D1628912F4806F44 +:10D1700005D042F002026281626902209047A169F3 +:10D180000020884760B3607950BB287818B30E48F8 +:10D19000007810F0100F04D10449097811F0100F35 +:10D1A0001ED06189E1B9A16AA9B90FE0300D002054 +:10D1B000380F0020240100205801002004630200E1 +:10D1C000BB220200A7A8010032010020218911B171 +:10D1D00010F0100F04D0BDE8F0410020FFF7D5BBE0 +:10D1E000BDE8F04100F071B92DE9F05FCC4E044686 +:10D1F0003046A6F134054068002700F1700A28780F +:10D20000B846022818BFFFDFA889FF2240F400704B +:10D21000A881706890F864101046FBF730F89AF80F +:10D2200012004FF00109002C00F0F080FAF77DFEAB +:10D23000FAF76BFE90B99AF8120078B1686A4178F3 +:10D2400061B100789AF80710C0F3C000884205D198 +:10D2500085F80290BDE8F05F00F037B9686A417860 +:10D260002981002908BFAF6203D0286BF6F70AFABC +:10D27000A862A88940F02000A881EF70706800F1D2 +:10D28000700B044690F82C0001281BD1FBF757FCCB +:10D2900059462046F3F729FEA0B13078002870687F +:10D2A0000CBF00F59A7000F50170218841809BF851 +:10D2B000081001719BF80910417180F80090E8791D +:10D2C000E5F722FE686A9AF806100078C0F380003D +:10D2D00088423AD0706800F1700490F87500002818 +:10D2E0002FD002284AD06771307800281CBF2079DF +:10D2F000002809D027716A89394642F010026A81F4 +:10D300006A694FF010009047E078A0B1E770FCF731 +:10D31000EAF8002808BFFFDF08206A89002142F0F0 +:10D3200008026A816A699047D4E91210491C40F1E9 +:10D330000000C4E91210A07901280CBFA77184F87D +:10D340000690A88940F48070A881696A9AF807302D +:10D350000878C0F3C0029A424DD1726800F0030011 +:10D3600002F17004012818BF02282DD003281CBF29 +:10D37000687940F0040012D068713CE0E86AF6F782 +:10D38000BCF8002808BFFFDFD4E91210491C40F1A7 +:10D390000000C4E91210E879E5F7B6FDA3E784F8C8 +:10D3A0000290AA89484642F40062AA816A8942F042 +:10D3B00001026A816A699047E079012801D1E77129 +:10D3C00019E084F8079016E04878D8B1A98941F4AB +:10D3D0000061A981A96A71B1FB2884BF687940F016 +:10D3E0001000C9D8A879002808BFC84603D08020FB +:10D3F0006A69002190470120A9698847E0B36879EC +:10D40000A0B13AE0E0790128DBD1D8E7002818BFC5 +:10D41000FAF7C5FDA88940F04000A881E97801200D +:10D42000491CC9B2E97001292DD8E5E7307890B9D7 +:10D430003C48007810F0100F04D13B49097811F0F6 +:10D44000100F1AD06989B9B9A96A21B9298911B10E +:10D4500010F0100F11D0B8F1000F1CBF0120FFF722 +:10D46000D1FDFFF74BFBB8F1000F08BFBDE8F09FFF +:10D470000220BDE8F05FC5E5FFE7B8F1000F1CBF73 +:10D480000020FFF7BFFDBDE8F05F00F01EB870B5EB +:10D490000D4606462248224900784C6850B1FAF7FA +:10D4A000F7FD034694F8642029463046BDE87040F5 +:10D4B000FDF78BBAFAF7ECFD034694F86420294691 +:10D4C0003046BDE8704004F0FCBE154910B54C680C +:10D4D000FBF714FBFBF7F3FAFBF7BCF9FBF768FA71 +:10D4E000FAF7FEFC94F82C00012808BFFBF727FB95 +:10D4F00094F86F0038B9A06E28B1002294F86E003D +:10D500001146F8F7F0FB094C00216269A0899047A9 +:10D51000E2696179A07890470020207010BD00007A +:10D520005801002032010020300D0020240100208D +:10D530002DE9F047FA4F894680463D782C0014D0FB +:10D540000126012D11DB601EC4B207EBC40090F868 +:10D550005311414506D10622494600F5AA70F0F75D +:10D560003FFF28B1761CAE42EDDD1020BDE8F0870C +:10D570002046BDE8F087EA498A78824286BF08449F +:10D5800090F843010020704710B540F2D3120021FB +:10D59000E348F0F77CFF0822FF21E248F0F777FF2D +:10D5A000E1480021417081704FF46171818010BDAC +:10D5B0002DE9F0410E460546FFF7BAFFD84C10287A +:10D5C00016D004EBC00191F85A0110F0010F1CBFF6 +:10D5D0000120BDE8F081607808283CBF012081F877 +:10D5E0005A011CD26078401C60700120BDE8F081B7 +:10D5F0006078082813D222780127501C207004EB91 +:10D60000C2083068C8F85401B088A8F85801102A38 +:10D6100028BFFFDF88F8535188F85A71E2E70020ED +:10D62000BDE8F081C04988707047BF488078704776 +:10D630002DE9F041BA4D00272878401E44B2002C55 +:10D6400030DB00BF05EBC40090F85A0110F0010F69 +:10D6500024D06878E6B2401E687005EBC6083046F4 +:10D6600088F85A7100F0E8FA102817D12878401E7F +:10D67000C0B22870B04211D005EBC001D1F85301FF +:10D68000C8F85301D1F85701C8F85701287800F0BD +:10D69000D3FA10281CBF284480F80361601E44B2EE +:10D6A000002CCFDAA0488770BDE8F0819C498A78C9 +:10D6B000824286BF01EB0010C01C002070472DE99C +:10D6C000F0470127994690463D460026FFF730FF78 +:10D6D000102820D0924C04EBC00191F85A1101F0AF +:10D6E000010600F0A9FA102815D0B9F1000F18BFF3 +:10D6F00089F80000A17881420DD904EB001111F1E5 +:10D70000030F08D0204490F84B5190F83B010128BA +:10D710000CBF0127002748EA060047EA0501084038 +:10D72000BDE8F0872DE9F05F1F4690468946064622 +:10D73000FFF7FEFE7A4C054610282ED000F07CFA4A +:10D7400010281CBF1220BDE8F09FA07808283ED208 +:10D75000A6781022701CA07004EB061909F10300D2 +:10D760004146F3F768FB09F1830010223946F3F7CD +:10D7700062FB10213846F3F74BFB3444102184F848 +:10D7800043014046F3F744FB84F84B0184F803510E +:10D79000002084F83B01BDE8F09FA078082816D24D +:10D7A00025784FF0000A681C207004EBC50BD9F8EF +:10D7B0000000CBF85401B9F80400ABF85801102D63 +:10D7C00028BFFFDF8BF853618BF85AA1C0E7072011 +:10D7D000BDE8F09F2DE9F041514CA078401E45B2C4 +:10D7E000002DB8BFBDE8F081EAB2A078401EC1B2FA +:10D7F000A17054FA85F090F803618A423DD004EBA1 +:10D80000011004EB0213D0F803C0C3F803C0D0F832 +:10D8100007C0C3F807C0D0F80BC0C3F80BC0D0F8DE +:10D820000FC0C3F80FC0D0F883C0C3F883C0D0F8CE +:10D8300087C0C3F887C0D0F88BC0C3F88BC0D0F8BE +:10D840008F00C3F88F006318A01801EB410193F813 +:10D8500003C102EB420204EB410180F803C104EB77 +:10D860004202D1F80BC1C2F80BC1B1F80F11A2F8F6 +:10D870000F1193F83B1180F83B1104EBC60797F8A2 +:10D880005A0110F0010F1CD1304600F0D5F91028D4 +:10D8900017D12078401EC0B22070B04211D004EBE6 +:10D8A000C000D0F85311C7F85311D0F85701C7F88A +:10D8B0005701207800F0C0F910281CBF204480F8E0 +:10D8C0000361681E45B2002D8EDABDE8F08116496D +:10D8D0004870704714484078704738B14AF2B81120 +:10D8E000884203D810498880012070470020704783 +:10D8F0000D488088704710B5FFF71AFE102804D035 +:10D9000000F09AF9102818BF10BD082010BD044976 +:10D910008A78824286BF01EB001083300020704776 +:10D92000600F00206C01002060010020FE4B93F886 +:10D9300002C084459CBF00207047184490F8030142 +:10D9400003EBC00090F853310B70D0F85411116004 +:10D95000B0F85801908001207047F34A114491F8C3 +:10D960000321F2490A700268C1F8062080884881C4 +:10D97000704770B516460C460546FBF7D5F8FAF722 +:10D98000C4F9EA48407868B1E748817851B12A196A +:10D99000002E0CBF8330C01CFAF791F9FAF7D8F9C2 +:10D9A000012070BD002070BD10B5FAF7FFF9002806 +:10D9B00004BFFF2010BDBDE81040FAF71DBAFAF70A +:10D9C000F5B9D9498A7882429CBF00207047084443 +:10D9D00090F8030101EBC00090F85A0100F001003B +:10D9E00070472DE9F047D04D00273E4628780028A3 +:10D9F00086BF4FF01009DFF83883BDE8F087AC78B8 +:10DA000021000CD00122012909DB601EC4B22819B3 +:10DA100090F80331B34203D0521C8A42F5DD4C46E4 +:10DA2000A14286BF05EB0410C01C002005EBC60A0E +:10DA30009AF85A1111F0010F16D050B1102C04D0E1 +:10DA4000291991F83B11012903D01021F3F7E0F9CE +:10DA500050B108F8074038467B1C9AF853210AF564 +:10DA6000AA71DFB2FAF7B5FC701CC6B22878B042D2 +:10DA7000C5D8BDE8F0872DE9F041AB4C002635460E +:10DA8000A07800288CBFAA4FBDE8F0816119C0B210 +:10DA900091F80381A84286BF04EB0510C01C00204A +:10DAA00091F83B11012903D01021F3F7B1F958B1D6 +:10DAB00004EBC800BD5590F8532100F5AA7130461B +:10DAC000731CDEB2FAF785FC681CC5B2A078A842C8 +:10DAD000DCD8BDE8F0810144934810B500EB02109A +:10DAE0000A4601218330FAF7EAF8BDE81040FAF758 +:10DAF0002FB90A468D4910B5497841B18A4B9978BA +:10DB000029B10244D81CFAF7DAF8012010BD002030 +:10DB100010BD854A01EB410102EB41010268C1F8E9 +:10DB20000B218088A1F80F0170472DE9F0417E4D4F +:10DB300007460024A878002898BFBDE8F081C0B24D +:10DB4000A04217D905EB041010F1830612D0102162 +:10DB50003046F3F75DF968B904EB440005EB400883 +:10DB600008F20B113A463046FBF74EFDB8F80F01AC +:10DB7000A8F80F01601CC4B2A878A042DFD8BDE8A5 +:10DB8000F081014610226B48F3F755B96948704798 +:10DB900065498A78824203D90A1892F843210AB16A +:10DBA0000020704700EB400001EB400000F20B103A +:10DBB00070475D498A78824206D9084490F83B0153 +:10DBC000002804BF01207047002070472DE9F04174 +:10DBD0000E460746144606213046F3F719F9524D12 +:10DBE00098B1A97871B105F59D7011F0010F18BFBA +:10DBF00000F8014FA978490804D0447000F8024F9A +:10DC0000491EFAD10120BDE8F08138463146FFF7C0 +:10DC10008FFC10280CD000F00FF8102818BF08282F +:10DC200006D0284480F83B414FF00100BDE8F08168 +:10DC30004FF00000BDE8F0813B4B10B4844698786B +:10DC400001000ED0012201290BDB401EC0B21C18BE +:10DC500094F80341644504BF10BC7047521C8A42CB +:10DC6000F3DD10BC1020704770B52F4C01466218D0 +:10DC7000A078401EC0B2A07092F8035181423CD0FF +:10DC800004EB011304EB001C01EB4101DCF8036021 +:10DC9000C3F80360DCF80760C3F80760DCF80B60CA +:10DCA000C3F80B60DCF80F60C3F80F60DCF883602A +:10DCB000C3F88360DCF88760C3F88760DCF88B60AA +:10DCC000C3F88B60DCF88FC0C3F88FC0231800EB5B +:10DCD000400093F803C104EB400082F803C104EB59 +:10DCE0004101D0F80BC1C1F80BC1B0F80F01A1F888 +:10DCF0000F0193F83B0182F83B0104EBC50696F84F +:10DD00005A0110F0010F18BF70BD2846FFF794FFAD +:10DD1000102818BF70BD2078401EC0B22070A842E5 +:10DD200008BF70BD08E00000600F00206001002007 +:10DD30006C0100203311002004EBC000D0F8531117 +:10DD4000C6F85311D0F85701C6F857012078FFF7ED +:10DD500073FF10281CBF204480F8035170BD0000E1 +:10DD60004078704730B50546007801F00F0220F08A +:10DD70000F0010432870092912D2DFE801F00507CF +:10DD800005070509050B0F0006240BE00C2409E02C +:10DD9000222407E001240020E87003E00E2401E0C3 +:10DDA0000024FFDF6C7030BD007800F00F0070477A +:10DDB0000A68C0F803208988A0F807107047D0F8D7 +:10DDC00003200A60B0F80700888070470A68C0F82E +:10DDD00009208988A0F80D107047D0F809200A6042 +:10DDE000B0F80D00888070470278402322F040028E +:10DDF00003EA81111143017070470078C0F380106D +:10DE000070470278802322F0800203EAC111114397 +:10DE1000017070470078C009704770B514460E460F +:10DE200005461F2A88BFFFDF2246314605F109005B +:10DE3000F0F703FBA01D687070BD70B544780E4606 +:10DE40000546062C38BFFFDFA01F84B21F2C88BFF9 +:10DE50001F24224605F109013046F0F7EEFA20466C +:10DE600070BD70B514460E4605461F2A88BFFFDFF9 +:10DE70002246314605F10900F0F7DFFAA01D68706F +:10DE800070BD0968C0F80F1070470A88A0F8132009 +:10DE900089784175704790F8242001F01F0122F025 +:10DEA0001F02114380F824107047072988BF0721FB +:10DEB00090F82420E02322F0E00203EA411111430C +:10DEC00080F8241070471F3008F065B810B504467C +:10DED00000F000FB002818BF204410BDC17811F0ED +:10DEE0003F0F1BBF027912F0010F0022012211F037 +:10DEF0003F0F1BBF037913F0020F002301231A44C5 +:10DF000002EB4202530011F03F0F1BBF027912F0E7 +:10DF1000080F0022012203EB420311F03F0F1BBF49 +:10DF2000027912F0040F00220122134411F03F0F76 +:10DF30001BBF027912F0200F0022012202EBC20265 +:10DF400003EB420311F03F0F1BBF027912F0100FD9 +:10DF50000022012202EB42021A4411F03F0F1BBFC4 +:10DF6000007910F0400F00200120104410F0FF0055 +:10DF700014BF012100210844C0B2704770B5027877 +:10DF8000417802F00F02082A4DD2DFE802F00408BF +:10DF90000B4C4C4C0F14881F1F280AD943E00C2946 +:10DFA00007D040E0881F1F2803D93CE0881F1F28A6 +:10DFB00039D8012070BD4A1EFE2A34D88446C07864 +:10DFC00000258209032A09D000F03F04601C884222 +:10DFD00004D86046FFF782FFA04201D9284670BDF1 +:10DFE0009CF803004FF0010610F03F0F1EBF1CF11C +:10DFF0000400007810F0100F13D06446042160462E +:10E0000000F068FA002818BF14EB0000E6D0017891 +:10E0100001F03F012529E1D280780221B1EB501FA8 +:10E02000DCD3304670BD002070BD70B5017801258D +:10E0300001F00F01002404290AD007290DD0082976 +:10E040001CBF002070BD40780E2836D0204670BD21 +:10E050004078801F1F2830D9F8E7844640789CF824 +:10E0600003108A09032AF1D001F03F06711C814296 +:10E07000ECD86046FFF732FFB042E7D89CF80300C7 +:10E0800010F03F0F1EBF1CF10400007810F0100FBD +:10E0900013D066460421604600F01CFA002818BF21 +:10E0A00016EB0000D2D0017801F03F012529CDD236 +:10E0B00080780221B1EB501FC8D3284670BD10B440 +:10E0C000017801F00F01032920D0052921D14478DE +:10E0D000B0F81910B0F81BC0B0F81730827D222CB0 +:10E0E00017D1062915D3B1F5486F98BFBCF5FA7F53 +:10E0F0000FD272B1082A98BF8A420AD28B429CBFC3 +:10E10000B0F81D00B0F5486F03D805E040780C2842 +:10E1100002D010BC0020704710BC012070472DE9D0 +:10E12000F0411F4614460D00064608BFFFDF21469A +:10E13000304600F0CFF9040008BFFFDF30193A463F +:10E140002946BDE8F041F0F778B9C07800F03F000B +:10E150007047C02202EA8111C27802F03F021143E7 +:10E16000C1707047C07880097047C9B201F00102E0 +:10E17000C1F340031A4402EB4202C1F3800303EBF4 +:10E180004202C1F3C00302EB4302C1F3001303EBED +:10E1900043031A44C1F3401303EBC30302EB4302EE +:10E1A000C1F380131A4412F0FF0202D0521CD2B203 +:10E1B0000171C37802F03F0103F0C0031943C1703D +:10E1C000511C417070472DE9F0410546C078164654 +:10E1D00000F03F041019401C0F46FF2888BFFFDFE6 +:10E1E000281932463946001DF0F727F9A019401CBE +:10E1F0006870BDE8F081C178407801F03F01401AB5 +:10E20000401E80B2704710B590F803C00B460CF06A +:10E210003F0144780CF03F0CA4EB0C0CACF1010C6A +:10E220001FFA8CF4944288BF14462BB10844011D98 +:10E2300022461846F0F701F9204610BD4078704795 +:10E2400000B5027801F0030322F003021A430270C2 +:10E25000012914BF0229002104D0032916BFFFDFC2 +:10E26000012100BD417000BD00B5027801F003033B +:10E2700022F003021A430270012914BF022900216F +:10E2800004D0032916BFFFDF012100BD417000BD8E +:10E29000007800F003007047417841B1C078192838 +:10E2A00003D2BC4A105C884201D101207047002093 +:10E2B000704730B501240546C17019293CBFB548E7 +:10E2C000445C02D3FF2918BFFFDF6C7030BD70B50E +:10E2D00015460E4604461B2A88BFFFDF65702A4696 +:10E2E0003146E01CBDE87040F0F7A7B8B0F8070071 +:10E2F0007047B0F809007047C172090A017370478E +:10E30000B0F80B00704730B4B0F80720B0F809C07F +:10E31000B0F805300179941F40F67A45AC4298BFB9 +:10E32000BCF5FA7F0ED269B1082998BF914209D293 +:10E3300093429FBFB0F80B00B0F5486F012030BC8E +:10E3400098BF7047002030BC7047001D07F023BE07 +:10E35000021D0846114607F01EBEB0F809007047BE +:10E36000007970470A684260496881607047426876 +:10E370000A60806848607047098881817047808999 +:10E38000088070470A68C0F80E204968C0F812106B +:10E390007047D0F80E200A60D0F81200486070472D +:10E3A0000968C0F816107047D0F81600086070476A +:10E3B0000A68426049688160704742680A60806804 +:10E3C000486070470968C1607047C068086070475E +:10E3D000007970470A684260496881607047426806 +:10E3E0000A608068486070470171090A417170478E +:10E3F0008171090AC17170470172090A417270473F +:10E400008172090AC172704780887047C08870475E +:10E41000008970474089704701891B2924BF4189C1 +:10E42000B1F5A47F07D381881B2921BFC088B0F52F +:10E43000A47F01207047002070470A684260496845 +:10E440008160704742680A6080684860704701795F +:10E4500011F0070F1BBF407910F0070F00200120BB +:10E460007047017911F0070F1BBF407910F0070FBB +:10E470000020012070470171704700797047417199 +:10E480007047407970478171090AC1717047C0882F +:10E4900070470179407901F007023F498A5C012AFF +:10E4A00006D800F00700085C01289CBF01207047D7 +:10E4B00000207047017170470079704741717047C3 +:10E4C0004079704730B50C460546FB2988BFFFDF11 +:10E4D0006C7030BDC378024613F03F0008BF704730 +:10E4E0000520127903F03F0312F0010F37D0002905 +:10E4F00014BF0B20704700BF12F0020F32D0012969 +:10E5000014BF801D704700BF12F0040F2DD00229E8 +:10E5100014BF401C704700BF12F0080F28D0032919 +:10E5200014BF801C704700BF12F0100F23D00429C5 +:10E5300014BFC01C704700BF12F0200F1ED0052969 +:10E540001ABF1230C0B2704712F0400F19D006291E +:10E550001ABF401CC0B27047072918D114E0002927 +:10E56000CAD114E00129CFD111E00229D4D10EE0A3 +:10E570000329D9D10BE00429DED108E00529E3D134 +:10E5800005E00629E8D102E0834288BF70470020F9 +:10E5900070470000246302001C63020030B490F84E +:10E5A00064508C88B1F808C015F00C0F1BD000BF68 +:10E5B000B4F5296F98BF4FF4296490F8655015F0B1 +:10E5C0000C0F17D0BCF5296F98BF4FF4296C4A88FF +:10E5D000C988A0F84420A0F84810A0F84640A0F848 +:10E5E0004AC030BC7047002B1CBF157815F00C0FCB +:10E5F000DED1E2E7002B1CBF527812F00C0FE1D104 +:10E60000E5E7DDF800C08181C2810382A0F812C075 +:10E6100070471B2202838282C281828142800281F2 +:10E62000028042848284828359B14FF429614183FC +:10E63000C18241820182C18041818180C184018582 +:10E6400070474FF4A4714183C18241820182C1802D +:10E6500041818180C18401857047F0B4B0F84820C1 +:10E66000818F468EC58E8A4228BF0A4690F8651073 +:10E670004FF0000311F00C0F18BF4FF4296106D1C1 +:10E68000B0F84AC0B0F840108C4538BF61464286A9 +:10E69000C186048FB0F83AC0944238BF14468C4506 +:10E6A00038BF8C460487A0F83AC0B2420ABFA942DC +:10E6B0004FF0010C4FF0000C058EB0F84410C28FE3 +:10E6C000848E914228BF114690F8642012F00C0FFE +:10E6D00018BF4FF4296206D1B0F84660B0F8422066 +:10E6E000964238BF324690F85A60022E0AD0018610 +:10E6F0008286A9420ABFA2420120002040EA0C0003 +:10E70000F0BC70478D4238BF2946944238BF22463C +:10E7100080F85A30EBE7508088899080C889D08093 +:10E72000088A1081488A508101201070704730B4E7 +:10E7300002884A80B0F830C0A1F804C0838ECB8034 +:10E74000428E0A81C48E4C81B0F85650A54204BF57 +:10E75000B0F85240944208D1B0F858409C4202BFF1 +:10E76000B0F854306345002301D04FF001030B7320 +:10E7700000F13003A0F852201A464B89D3848B88CD +:10E780009384CA88A0F858204FF00100087030BC6C +:10E79000704730B404460A46088E91F864104FF46E +:10E7A000747311F00C0F1CBF03EB801080B21ED0ED +:10E7B000918E814238BF0846118F92F865C01CF0D7 +:10E7C0000C0F1CBF03EB811189B218D0538F8B4201 +:10E7D00038BF194692F866301CF00C0F08BF0023B2 +:10E7E000002C0CBF0122002230BCF2F798BC022999 +:10E7F00007BF80003C30C000703080B2D8E7BCF169 +:10E80000020F07BF89003C31C900703189B2DDE7D2 +:10E810002DE9F041044606F099FCC8B9FE4F78682E +:10E8200090F8221001260025012914D00178012931 +:10E830001BD090F8281001291CBF0020BDE8F081F2 +:10E84000657018212170D0F82A10616080F8285076 +:10E850000120BDE8F081657007212170416A616087 +:10E8600080F822500120BDE8F081657014212170EC +:10E87000811C2022201DEFF7E0FD257279680D70C4 +:10E8800081F82850E54882888284C26B527B80F8E8 +:10E89000262080F82260C86B0088F5F738FCF5F771 +:10E8A000E0F8D5E7DC4840680178002914BF80888B +:10E8B0004FF6FF70704730B5D74C83B00D462078C7 +:10E8C0007F2808BFFFDF94F900307F202070D4F844 +:10E8D00004C09CF85000062808BF002205D09CF810 +:10E8E000500008280CBF022201229CF85400CDE9F8 +:10E8F000000302929CF873309CF880200CF13201E6 +:10E90000284606F08FFC03B0BDE8304006F01FBE7D +:10E910002DE9F04106F05FFC002818BF06F0E4FB8B +:10E92000BD4C606800F1840290F87610895C80F834 +:10E930008010002003F07EF828B3FAF753F86068DF +:10E94000B74990F855000D5C2846F9F7A3FD6068BB +:10E950004FF0000680F8735090F8801011F00C0F03 +:10E960000CBF25200F20F9F76CFC606890F8801030 +:10E970000120F9F711FE606890F84010032918BFD4 +:10E9800002290FD103E0BDE8F04101F02FB990F862 +:10E9900076108430085C012804D101221146002041 +:10E9A000FAF707F9FAF7D5F8606890F88050012D6A +:10E9B00007BF0127032100270521A068FFF799F869 +:10E9C000616881F8520040B1002F18BF402521D066 +:10E9D000F9F787F92846FAF79DF86068806DFAF72D +:10E9E0000DF8606890F85410FF291CBF6D30FEF7D9 +:10E9F000B4FFFF21606880F8531080F8541080F84D +:10EA0000626080F8616080F87D60062180F85010B7 +:10EA1000BDE8F08115F00C0F14BF55255025D7E740 +:10EA200070B57D4C0646606800F150052046806850 +:10EA300041B1D0F80510C5F81D10B0F80900A5F8CF +:10EA4000210003E005F11D01FFF7B9F9A068FFF708 +:10EA5000D4F985F82400A0680021032E018002D09B +:10EA6000052E04D03DE00321FFF77CF939E00521B4 +:10EA7000FFF778F96068C06B00F10E01A068FFF73E +:10EA800000FA6068C06B00F11201A068FFF7FDF9A1 +:10EA9000D4E90110CA6B527D8275CA6BD28AC275E5 +:10EAA000120A0276CA6B52884276120A8276CA6BC2 +:10EAB0009288C276120A0277CA6BD2884277120A0B +:10EAC0008277C96B0831FFF7FEF96068C06B017E81 +:10EAD000A068FFF7E0F9606890F88610A068FFF77B +:10EAE000E4F905F11D01A068FFF770F995F824100D +:10EAF000A068FFF786F9606800F1320590F8316090 +:10EB000090F8511091B190F84010032906D190F877 +:10EB10003910002918BF90F8560001D190F8530021 +:10EB2000FFF736F800281CBF012605462946A068D5 +:10EB3000FFF73EF93146A068BDE87040FFF754B9D1 +:10EB40003549496881F84B00704770B5324D002453 +:10EB50000126A8606968A1F8814081F8834081F8A6 +:10EB6000506091F85020022A1FBF91F850100129DF +:10EB7000FFDF70BD06F0CDFA6868047080F82240AF +:10EB800080F8284090F8520030B1F9F7CDFFF9F73E +:10EB9000BCF8686880F852406868072180F84A40ED +:10EBA00080F8396080F8404080F8554080F84B404C +:10EBB00080F87D4080F8381070BD2DE9F041164C8A +:10EBC000054686B0606890F85000012818BF0228FA +:10EBD00005D003281EBF0C2006B0BDE8F081687A7E +:10EBE000022839D0F9F76FFB0220F9F701FF0D4930 +:10EBF00001F10C0090E80D108DE80D10D1E907012E +:10EC0000CDE904016846F9F7E1FE606890F94B0030 +:10EC1000F9F732FCA06807E07401002044110020DD +:10EC20004363020040630200F9F7E5FEFC48F9F790 +:10EC3000B9FEFC48F9F726FC606890F831103230D4 +:10EC4000F9F7A5FB0F210720F9F7BFFB606890F8E3 +:10EC50003900E0B1FEF70FFF6168287A01F1840204 +:10EC600081F87600287A805C81F880006868886581 +:10EC70002A68CA65687A68B1012824D00525022867 +:10EC800008BF81F850506FD0032878D080E0FEF79D +:10EC9000A8FEE1E7E44B91F83850002291F85500C6 +:10ECA000401CA3FB006C4FEA5C0CACEB8C0C60448A +:10ECB00081F8550025FA00F010F0010F03D1501C27 +:10ECC000C2B2032AEAD3002681F87D6091F8490098 +:10ECD000002804BF91F85100002841D0F7F744FA0A +:10ECE000074660683946406CF7F736FFDFF83C832B +:10ECF000054690FBF8F008FB105041423846F6F705 +:10ED00001AFF6168486495FBF8F08A6F10448867C1 +:10ED1000FEF7EEFD01466068826F914220D847649D +:10ED2000866790F8510000281CBF0120FEF7FDFE09 +:10ED30000121606890F84A20002A1CBF90F8492001 +:10ED4000002A0DD090F8313000F13202012B04D1AD +:10ED5000527902F0C002402A08D03230FAF78CFC17 +:10ED60006168042081F8500012E008E00125FEF7F8 +:10ED70000DFF61682A463231FAF746FCF0E7002AB7 +:10ED800018BFFFDF012000F089FF606880F8505055 +:10ED900006B00020BDE8F08170B5A54D686890F818 +:10EDA000501004292ED005291CBF0C2070BD90F8EE +:10EDB0007D100026002990F883104FEA511124D0CD +:10EDC000002908BF012407D0012908BF022403D06D +:10EDD000022914BF00240824C06D00281CBF002095 +:10EDE00000F05CFF6868806DF9F708FE686890F8CD +:10EDF0004010022943D0032904BF90F86C10012968 +:10EE000041D04DE0FFF784FD52E0002908BF012406 +:10EE100007D0012908BF022403D0022914BF00240F +:10EE20000824C06D00281CBF002000F037FF686870 +:10EE3000806DF9F7E3FD686890F84010022906D06C +:10EE4000032904BF90F86C10012904D010E090F859 +:10EE50006C1002290CD1224614F00C0F04D090F84B +:10EE60004C00012808BF042201210020F9F7A1FE6F +:10EE70006868072180F8804080F8616016E090F8AB +:10EE80006C1002290CD1224614F00C0F04D090F81B +:10EE90004C00012808BF042201210020F9F789FE57 +:10EEA0006868082180F8804080F8616080F8501020 +:10EEB000002070BD5E49002210F0010F496802D0A9 +:10EEC000012281F8842010F0080F03D0114408209B +:10EED00081F88400002070475549496881F848004E +:10EEE000704710B5524C636893F83030022B14BF52 +:10EEF000032B00280BD100291ABF02290120002072 +:10EF00001146FEF7F8FC08281CBF012010BD606800 +:10EF100090F83000002816BF022800200120BDE82C +:10EF20001040FAF731BB4248406890F830000028A2 +:10EF300016BF022800200120FAF726BB3C49496889 +:10EF400081F8300070473A49496881F84A007047B3 +:10EF500070B5374C616891F83000002816BF022860 +:10EF60000020012081F8310001F13201FAF7F6FAB0 +:10EF7000606890F83010022916BF03290121002192 +:10EF800080F8511090F8312000F132034FF0000565 +:10EF9000012A04BF5B7913F0C00F0AD000F13203DD +:10EFA000012A04D15A7902F0C002402A01D000227D +:10EFB00000E0012280F84920002A04BF002970BD2A +:10EFC0008567F7F7D1F86168486491F85100002827 +:10EFD0001CBF0020FEF7A9FD0026606890F84A10CB +:10EFE00000291ABF90F84910002970BD90F831200F +:10EFF00000F13201012A04D1497901F0C001402910 +:10F0000005D02946BDE870403230FAF735BBFEF72F +:10F01000BDFD61683246BDE870403231FAF7F4BA9E +:10F020004063020046630200ABAAAAAA40420F0056 +:10F030007401002070B5FF4D0C4600280CBF012361 +:10F040000023696881F8393081F842004FF00800E8 +:10F0500081F856000CD1002C1ABF022C0120002090 +:10F060001146FEF748FC6968082881F8560001D06F +:10F07000002070BD022C14BF032C1220F8D170BDEB +:10F08000002818BF112070470328EA4A526808BFB9 +:10F09000D16382F840000020704710B5E54C6068ED +:10F0A00090F8401003291CBF002180F8601001D0A7 +:10F0B000002010BD0123C16B1A460020F2F738F87A +:10F0C0006168CA6B526A904294BF0120002081F8A7 +:10F0D0006000EDE7D748416891F84000032804D06C +:10F0E000012818BF022807D004E091F84200012847 +:10F0F00008BF70470020704791F84100012814BFF5 +:10F1000003280120F6D1704770B5F9F7F7FCF9F73D +:10F11000D6FCF9F79FFBF9F74BFCC64C002560685D +:10F1200090F8520030B1F9F7FFFCF8F7EEFD606897 +:10F1300080F8525060680121A0F8815080F8835017 +:10F1400080F8501080F82850002070BDB94810B5E4 +:10F150004068643006F0B1FB002010BDB5480121C5 +:10F16000406890F84020032A03BF80F82A10C26B41 +:10F170001288002218BF80F82A20828580F8281083 +:10F180007047AC49496881F88600704701780023D0 +:10F1900011F0010FA749496809D04278032A08BF36 +:10F1A000CB6381F84020012281F884201346027845 +:10F1B00012F0040F0CD082784FF0000C032A08BF25 +:10F1C000C1F83CC081F840200B44082283F8842019 +:10F1D000C27881F830200279002A16BF022A012362 +:10F1E000002381F8393081F84120427981F83820B4 +:10F1F000807981F848004FF0000070478D484068E2 +:10F200008030704770B58B4C06460D46606890F8AC +:10F210005000032818BFFFDF022E1EBF032EFFDFA2 +:10F2200070BD002D18BF06F0A1F900216068A0F89C +:10F23000811080F88310012180F8501070BD00F01B +:10F24000D5BC2DE9F0477B4C0646894660684FF0F7 +:10F250000108072E90F8397038BF032540D3082ED7 +:10F2600084BF0020BDE8F08790F85010062908BF41 +:10F27000002105D090F8501008290CBF022101216F +:10F2800090F8800005F0AEFF002873D1A068C17827 +:10F2900011F03F0F12D0027912F0010F0ED0616809 +:10F2A0004FF0050591F85220002A18BFB9F1000F60 +:10F2B00016D091F88010012909D011E011F03F0F0C +:10F2C0001ABF007910F0100F002F53D14CE04FF00F +:10F2D00001024FF00501FEF74CFB616881F8520016 +:10F2E000A16808782944C0F3801030B1487900F053 +:10F2F000C000402808BF012000D00020616891F8BC +:10F300005210002918BF002807D0FEF74DFB014618 +:10F31000606880F8531080F86180606890F853103E +:10F32000FF292AD080F854100846FEF74AFB40EA2D +:10F330000705606890F85320FF2A18BF002D10D0F1 +:10F34000072E0ED3A068C17811F03F0F09D00179C4 +:10F3500011F0020F05D00B21FEF7BDFB606880F8AD +:10F3600062802846BDE8F087FEF75FF9002808BFF5 +:10F37000BDE8F0870120BDE8F087A36890F8392048 +:10F3800059191B78C3F3801C00F153036046FEF744 +:10F3900096F90546CDE72DE9F043264C87B0A068E5 +:10F3A000FEF7E0FE7F264FF00108002558B1022746 +:10F3B00001287DD0022800F0EF80F9F74BFA07B062 +:10F3C0000620BDE8F083F9F745FA616891F840003E +:10F3D000032800F01581A068C27812F03F0F05D015 +:10F3E000037913F0100F18BF012700D10027002F59 +:10F3F00014BF0823012312F03F0F00F001810079B0 +:10F4000033EA000240F0FC8010F0020F08D091F8BF +:10F410008000002105F064FE002808BF012000D014 +:10F4200000208DF80C508DF810508DF814504FF0CE +:10F43000FF0801E074010020D0B105AA03A904A8C7 +:10F4400000F07AFC606890F831809DF80C0000288C +:10F4500018BF48F002080BD1A068FEF7DBFC81461C +:10F460000121A068FEF732FD4946F8F79AFF28B35C +:10F47000FFB1012000F0DDFB002852D020787F286A +:10F4800008BFFFDF94F900102670606890F85420E0 +:10F49000CDE90021029590F8733090F8802000F1BA +:10F4A0003201404605F0BEFE606880F86C50A3E073 +:10F4B00038E041460020FFF7FEF9A1E0606890F8CF +:10F4C0004100032818BF02282BD19DF81000002806 +:10F4D00027D09DF80C00002823D1F7B1012000F0BF +:10F4E000A8FB00281DD020787F2808BFFFDF94F9F3 +:10F4F00000102670606890F85420CDE90021029534 +:10F5000090F8733090F8802000F13201FE2005F071 +:10F5100089FE606880F86C506EE0FE210020FFF7E5 +:10F52000CAF96DE0F9F796F9A0681821C27812F0CF +:10F530003F0F65D00279914362D10421FEF7C6FCEA +:10F54000616891F84020032A01BF8078B7EB501F13 +:10F5500091F86000002853D04FF0010000F069FBE3 +:10F56000E8B320787F2808BFFFDF94F900102670E9 +:10F57000606890F85420CDE90021029590F873302E +:10F5800090F8802000F13201FF2005F04BFE60680A +:10F5900080F86C8030E000BFF9F75CF9606890F8A3 +:10F5A000400003282CD0A0681821C27812F03F0F29 +:10F5B00026D0007931EA000022D1012000F039FB89 +:10F5C00068B120787F2808BFFFDF94F9001026700B +:10F5D000606890F85420CDE90021029500E00FE02A +:10F5E00090F8733090F8802000F13201FF2005F090 +:10F5F00019FE606880F86C7007B00320BDE8F083E6 +:10F6000007B00620BDE8F083F0B5FE4C074683B096 +:10F6100060686D460078002818BFFFDF002661682B +:10F620008E70C86B02888A8042884A8382888A8367 +:10F63000C088C88381F8206047B10121A068FEF727 +:10F6400045FC0546A0680078C10907E06946A06846 +:10F65000FEF7B5FBA0680078C0F380116068012751 +:10F6600090F85120002A18BF002904D06A7902F0CE +:10F67000C002402A26D090F84A20002A18BF00294C +:10F6800003D0697911F0C00F1CD000F10E00E3F730 +:10F69000B3FC616891F85400FF2819D001F1080209 +:10F6A000C91DFEF743F9002808BFFFDF6068C17974 +:10F6B00041F00201C171D0F86D104161B0F87110D4 +:10F6C00001830FE02968C0F80E10A9884182E0E7A5 +:10F6D000C86B427ECA71D0F81A208A60C08B8881BC +:10F6E0004E610E8360680770C26B90F84B1082F811 +:10F6F0006710C06B0088F4F70AFDF4F7A3F903B0B4 +:10F70000F0BD2DE9F041BF4C0546002760684FF081 +:10F7100001083E4690F84000012818BF022802D098 +:10F72000032818BFFFDF5DB1A068FEF727FC18B9FA +:10F73000A068FEF77AFC18B100F08FFB074645E0A1 +:10F74000606890F850007F25801F06283ED2DFE8D1 +:10F7500000F003191924352FAA48F9F709FA0028EF +:10F7600008BF2570F9F7EBF9606890F8520030B1E6 +:10F77000F9F7DAF9F8F7C9FA606880F85260F9F732 +:10F7800069F830E09F48F9F7F3F9002808BF2570C1 +:10F79000F9F7D5F905F0EAFEC3E09A48F9F7E8F978 +:10F7A000002808BF2570F9F7CAF9F9F753F81AE0ED +:10F7B0009448F9F7DDF930B9257004E09148F9F77C +:10F7C000D7F90028F8D0F9F7BAF9AAE0102F80F09D +:10F7D0003881DFE807F01E9DA6AAF1F108B3F2F127 +:10F7E000F1F10C832051BDE8F041FFF791B80320FF +:10F7F00002F020F9002870D000210320FFF710F953 +:10F80000012211461046F9F7D4F961680C2081F8FD +:10F810005000BDE8F081606800F15005042002F05E +:10F8200009F900287DD00E202870012002F0FDFC8F +:10F83000A06861680078C0F3401081F8750000216D +:10F840000520FFF7EDF87048A1684FF0200CC26B5F +:10F850000B78527B23F020030CEA42121A430A7001 +:10F86000C16B95F825304A7B1A404A73C06B28213A +:10F8700080F86610BDE8F081062002F0DBF8002871 +:10F880004FD0614D0F2085F85000022002F0CDFCD2 +:10F890006068012190F880200846F9F78AF9A0688D +:10F8A00061680078C0F3401081F8750001210520DF +:10F8B000FFF7B6F8E86B80F80D80A068017821F0BA +:10F8C00020010170F9F75DFD002818BFFFDF282037 +:10F8D000E96B81F86600BDE8F08122E0052002F0C6 +:10F8E000A9F8F0B101210320FFF79AF8F9F749FDD3 +:10F8F000002818BFFFDF6068012190F880200846CB +:10F90000F9F757F961680D2081F85000BDE8F081E2 +:10F910006068A0F8816080F8836080F85080BDE85E +:10F92000F081BDE8F04100F061B96168032081F821 +:10F930005000BDE8F041082002F077BC606890F804 +:10F940008310490908BF012507D0012908BF0225F6 +:10F9500003D0022914BF00250825C06D00281CBF54 +:10F96000002000F09BF96068806DF9F747F8606847 +:10F9700090F84010022906D0032904BF90F86C10BB +:10F98000012904D010E090F86C1002290CD12A460D +:10F9900015F00C0F04D090F84C00012808BF042289 +:10F9A00001210020F9F705F96068072180F88050EF +:10F9B00080F8616041E000E043E0606890F8831007 +:10F9C000490908BF012507D0012908BF022503D036 +:10F9D000022914BF00250825C06D00281CBF002087 +:10F9E00000F05CF96068806DF9F708F8606890F8DD +:10F9F000401002290AD0032904BF90F86C10012995 +:10FA000008D014E0740100204411002090F86C101C +:10FA100002290CD12A4615F00C0F04D090F84C00A6 +:10FA2000012808BF042201210020F9F7C2F860680C +:10FA3000082180F8805080F8616080F85010BDE89F +:10FA4000F081FFDFBDE8F08170B5FE4C606890F892 +:10FA5000503000210C2B38D001220D2B40D00E2B22 +:10FA600055D00F2B1CBFFFDF70BD042002F0DDFB63 +:10FA7000606890F880100E20F8F7E3FB606890F85B +:10FA8000800010F00C0F14BF282100219620F8F7F9 +:10FA9000D3FFF9F75EF86068052190F88050A06800 +:10FAA000FEF727F8616881F8520048B115F00C0F95 +:10FAB0000CBF50255525F8F714F92846F9F72AF810 +:10FAC00061680B2081F8500070BDF9F742F8002101 +:10FAD0009620F8F7B1FF6168092081F8500070BDE9 +:10FAE00090F88010FF20F8F7ACFB606890F8800079 +:10FAF00010F00C0F14BF282100219620F8F79CFF6E +:10FB0000F9F727F861680A2081F8500070BDA0F865 +:10FB1000811080F8831080F850200020FFF774FDDA +:10FB2000BDE87040032002F080BB70B5C54C606832 +:10FB300090F850007F25801F062828BF70BDDFE8A1 +:10FB400000F0171F1D032A11BE48F9F711F800280D +:10FB500008BF2570F8F7F3FFF8F77CFEBDE87040AA +:10FB6000FEF7D6BEB748F9F703F8C8B9257017E015 +:10FB7000B448F8F7FDFF40B9257006E005F0F6FC43 +:10FB8000B048F8F7F5FF0028F6D0F8F7D8FFBDE841 +:10FB9000704000F02BB8AB48F8F7EAFF0028E5D03A +:10FBA000F8F7CDFF60680021643005F037FEBDE84E +:10FBB000704000F01BB870B5A24C06460D460129F6 +:10FBC00008D0606890F880203046BDE87040134649 +:10FBD00002F077BBF8F75CFA61680346304691F8AB +:10FBE00080202946BDE8704002F06BBB70B5F8F785 +:10FBF00085FFF8F764FFF8F72DFEF8F7D9FE914C72 +:10FC00000025606890F8520030B1F8F78DFFF8F7E2 +:10FC10007CF8606880F852506068022180F85010CB +:10FC2000A0F8815080F88350BDE87040002002F0B9 +:10FC3000FCBA70B5834D06460421A868FEF746F964 +:10FC4000044605F0C8FA002808BF70BD207800F00F +:10FC50003F00252814D2F8F761FA217811F0800FBF +:10FC60000CBF1E214FF49671B4F80120C2F30C02B0 +:10FC700012FB01F10A1AB2F5877F28BF814201D237 +:10FC8000002070BD68682188A0F88110A17880F8F4 +:10FC900083103046BDE8704001F0CCBE2DE9F04144 +:10FCA000684C0746606800F1810690F883004009BF +:10FCB00008BF012507D0012808BF022503D002286C +:10FCC00014BF00250825F8F78DFE307800F03F06B8 +:10FCD0003046F8F7DFFB606880F8736090F86C00DE +:10FCE00002280CBF4020FF202946F8F7AAFA27B1C6 +:10FCF00029460120F8F795FC05E060682A46C16DA9 +:10FD00000120F8F7E2FCF8F724FF0521A068FDF7D1 +:10FD1000F0FE6168002881F8520008BFBDE8F0815C +:10FD200015F00C0F0CBF50245524F7F7DAFF2046CE +:10FD3000BDE8F041F8F7EEBE2DE9F74F414C002544 +:10FD4000914660688A4690F8510000280CBF4FF039 +:10FD500001084FF00008A0680178CE090121FEF7E4 +:10FD6000B5F836B1407900F0C000402808BF012640 +:10FD700000D00026606890F85210002961D090F8F9 +:10FD800040104FF0000B032906D190F839100029DC +:10FD900018BF90F856700ED1A068C17811F03F0FCF +:10FDA0001CBF007910F0010F02D105F061F940B3DA +:10FDB000606890F85370FF2F18BF082F21D0384685 +:10FDC000FDF7D9FB002818BF4FF00108002E38D0EE +:10FDD000606890F8620030B1FDF7F1FD054660689B +:10FDE00080F862B02DE03846FDF791FD054601210F +:10FDF000A068FEF76BF801462846F9F7D3FB0546E5 +:10FE00001FE0F6B1606890F86100D0B9A068C178D1 +:10FE100011F03F0F05D0017911F0010F18BF0B2130 +:10FE200000D105210022FDF7A4FD616881F8520090 +:10FE300038B1FDF7B9FDFF2803D06168012581F8CD +:10FE4000530001E0740100208AF800500098067009 +:10FE500089F8008003B0BDE8F08F2DE9F04FFF4C2A +:10FE600087B00025606890F850002E46801F4FF044 +:10FE70007F08062880F0D581DFE800F00308088BB2 +:10FE8000FDDB00F0F8FB054600F0CCB9F348F8F7CD +:10FE90006FFE002808BF84F80080F8F750FEA068C5 +:10FEA000FDF782FF0546072861D1A068FEF75AF9E1 +:10FEB0000146606890F86C208A4258D190F8501042 +:10FEC000062908BF002005D090F8500008280CBF74 +:10FED0000220012005F08AF970B90321A068FDF71E +:10FEE000F5FF002843D001884078C1F30B010009D9 +:10FEF00005F07BFC00283AD000212846FFF7A1F945 +:10FF0000A0B38DF80C608DF808608DF8046062680D +:10FF1000FF2592F8500008280CBF02210121A0689B +:10FF2000C37813F03F0F1CBF007910F0020F12D0FE +:10FF300092F8800005F0D4F868B901AA03A902A8D4 +:10FF4000FFF7FAFE606890F831509DF80C00002829 +:10FF500018BF45F002052B469DF804209DF80810B7 +:10FF60009DF80C0000F0D5F9054603E0FFE705F029 +:10FF7000FDFA0225606890F85200002800F05281D6 +:10FF8000F8F7D2FDF7F7C1FE606880F8526000F024 +:10FF900049B9A068FDF708FF0646A1686068CA78FD +:10FFA00090F86D309A4221D10A7990F86E309A42D9 +:10FFB0001CD14A7990F86F309A4217D18A7990F81B +:10FFC00070309A4212D1CA7990F871309A420DD1AC +:10FFD0000A7A90F872309A4208D1097890F8740041 +:10FFE000C1F38011814208BF012500D00025F8F738 +:10FFF00031FC9A48F8F7BCFD002808BF84F800805F +:020000040002F8 +:10000000F8F79DFD042E11D185B120787F2808BF17 +:10001000FFDF94F9003084F80080606890F8732066 +:1000200090F87D1090F8540005F06EFB062500F066 +:10003000F9B802278948F8F79BFD002808BF84F823 +:100040000080F8F77CFDA068FDF7AEFE0546A068CD +:10005000FEF788F8082D08BF00287CD1A0684FF073 +:100060000301C27812F03F0F75D0007931EA000029 +:1000700071D1606800E095E000F1500890F8390017 +:10008000002814BF98F8066098F803604FF0000944 +:1000900098F8020078B1FDF787FC0546FF280AD0E2 +:1000A0000146A068401DFDF758FCB5420CBF4FF05B +:1000B00001094FF000090021A068FDF707FF0622A3 +:1000C00008F11D01EEF78CF940B9A068FDF795FE27 +:1000D00098F82410884208BF012000D0002059EA77 +:1000E00000095DD0606800F1320590F831A098F801 +:1000F000010038B13046FDF74BFD00281CBF054616 +:100100004FF0010A4FF00008A06801784FEAD11BB8 +:100110000121FDF7DBFEBBF1000F07D0407900F0B5 +:10012000C000402808BF4FF0010B01D04FF0000B7A +:100130000121A068FDF7CAFE06222946EEF750F914 +:1001400030B9A068FDF766FE504508BF012501D013 +:100150004FF0000500E023E03BEA050018BFFF2E4A +:100160000DD03046FDF7D3FB060008D00121A06872 +:10017000FDF7ACFE01463046F9F714FA804645EA31 +:10018000080019EA000F0BD060680121643005F007 +:1001900045FB01273846FFF737FA052002F045F8FE +:1001A0003D463FE002252D48F8F7E2FC002808BF55 +:1001B00084F80080F8F7C3FCA068FDF7F5FD06465B +:1001C000A068FDF7CFFF072E08BF00282AD1A0683E +:1001D0004FF00101C27812F03F0F23D00279914312 +:1001E00020D1616801F150060021FDF76FFE062263 +:1001F00006F11D01EEF7F4F8A0B9A068FDF7FDFDCA +:1002000096F8241088420DD160680121643005F011 +:1002100005FBFF21022000F009F8002818BF032584 +:1002200000E0FFDF07B02846BDE8F08F2DE9F0437E +:100230000A4C0F4601466068002683B090F87D2086 +:10024000002A35D090F8500008280CBF022501255F +:10025000A168C87810F03F0F02E000007401002090 +:10026000FD484FF000084FF07F0990F900001CBFD7 +:10027000097911F0100F22D07F2808BFFFDF94F911 +:10028000001084F80090606890F85420CDE90021B7 +:10029000029590F8733090F8802000F132013846D2 +:1002A00004F0C0FF05F0AAFA10B305F050F92CE0F5 +:1002B000002914BF0221012180F87D10C2E77F28A8 +:1002C00008BFFFDF94F9001084F80090606890F890 +:1002D0005420CDE90021029590F8733090F88020E9 +:1002E00000F13201384604F09DFF05F030F90CE0D2 +:1002F0000220FFF79EFC30B16068012680F86C8018 +:10030000F8F7A8FA01E005F031F903B03046BDE88E +:10031000F0832DE9F047D04C054684B09A46174645 +:100320000E46A068FDF71EFF4FF00109002800F0FF +:10033000CF804FF00208012808D0022800F00E817B +:1003400005F014F904B04046BDE8F087A068092123 +:10035000C27812F03F0F00F059810279914340F0CA +:100360005581616891F84010032906D012F0020F00 +:1003700008BFFF2118D05DB115E00021FDF7A6FDF3 +:1003800061680622C96B1A31EEF72AF848BB1EE0F5 +:10039000FDF740FD05460121A068FDF797FD2946C0 +:1003A000F7F7FFFF18B15146012000F051B960681E +:1003B00090F84100032818BF022840F02781002E42 +:1003C0001CBFFE21012040F0438100F01FB9A0684E +:1003D000FDF713FD6168C96B497E884208BF01269D +:1003E00000D00026A068C17811F03F0F05D0017938 +:1003F00011F0020F01D06DB338E0616891F842202E +:10040000012A01D096B11BE0D6B90021FDF75EFDAF +:1004100061680268C96BC1F81A208088C883A06827 +:10042000FDF7EBFC6168C96B487609E091F8530071 +:1004300091F85610884203D004B04046BDE8F087DA +:100440006068643005F02EFA002840D004B00F2018 +:10045000BDE8F08767B1FDF7DDFC05460121A06826 +:10046000FDF734FD2946F7F79CFF08B1012200E0B3 +:100470000022616891F84200012807D040B92EB9E6 +:1004800091F8533091F856108B4201D1012100E0D0 +:1004900000210A421BD0012808BF002E11D14FF0C5 +:1004A0000001A068FDF712FD61680268C96BC1F820 +:1004B0001A208088C883A068FDF79FFC6168C96B1B +:1004C00048766068643005F0EDF90028BED19DE003 +:1004D00060682F46554690F840104FF002080329F7 +:1004E000AAD0A168CA7812F03F0F1BBF097911F09A +:1004F000020F002201224FF0FF0A90F85010082945 +:100500000CBF0221012192B190F8800004F0E8FDB7 +:1005100068B95FB9A068FDF77DFC07460121A068B6 +:10052000FDF7D4FC3946F7F73CFF48B1AA465146DF +:100530000020FFF77BFE002818BF4FF003087BE781 +:10054000606890F84100032818BF02287FF474AF58 +:10055000002E18BF4FF0FE0AE9D16DE7616891F8EF +:100560004030032B52D0A0684FF0090CC27812F033 +:100570003F0F4BD002793CEA020C47D1022B06D048 +:1005800012F0020F08BFFF2161D0E5B35EE012F068 +:10059000020F4FF07F0801D04DB114E001F164006B +:1005A00005F080F980B320787F2842D013E067B34C +:1005B000FDF730FC05460121A068FDF787FC2946C0 +:1005C000F7F7EFFE08B36068643005F06BF9D8B157 +:1005D00020787F282DD094F9001084F8008060687E +:1005E00090F85420CDE90021CDF8089090F87330B0 +:1005F00090F8802000F13201504604F013FE0D20E7 +:1006000004B0BDE8F08716E000E001E00220F7E763 +:10061000606890F84100032818BF0228F6D1002E28 +:10062000F4D04FF0FE014FF00200FEF744F9022033 +:10063000E6E7FFDFCFE7FDF7EDFB05460121A06808 +:10064000FDF744FC2946F7F7ACFE38B151460220CD +:10065000FEF731F9DAE7000074010020606890F8D5 +:100660004100032818BF0228D0D1002E1CBFFE2154 +:100670000220EDD1CAE72DE9F84F4FF00008F74806 +:10068000F8F776FA7F27F54C002808BF2770F8F7AF +:1006900056FAA068FDF788FB81460121FEF7D1FDDF +:1006A000616891F88020012A14D0042A1CBF082A0E +:1006B000FFDF00F0D781606890F8520038B1F8F79A +:1006C00033FAF7F722FB6168002081F852004046B8 +:1006D000BDE8F88F0125E24EB9F1080F3AD2DFE804 +:1006E00009F03EC00439393914FC0546F8F7B2F870 +:1006F000002D72D0606890F84000012818BF0228D1 +:100700006BD120787F2869D122E018B391F840009E +:10071000022802D0012818D01CE020787F2808BFCA +:10072000FFDF94F90000277000906068FF2190F8C7 +:10073000733090F85420323004F02FFF61680020AD +:100740004FF00C0881F87D00B5E720787F2860D154 +:10075000FFDF5EE0F8F77EF84FF00608ABE74FF0FA +:100760000008002800F0508191F84000022836D09F +:1007700001284BD003289ED1A068CA6BC37892F899 +:100780001AC0634521D1037992F81BC063451CD17F +:10079000437992F81CC0634517D1837992F81DC044 +:1007A000634512D1C37992F81EC063450DD1037A17 +:1007B00092F81FC0634508D1037892F819C0C3F3BB +:1007C0008013634508BF012300D0002391F8421035 +:1007D00001292CD0C3B300F013B93FE019E0207811 +:1007E0007F2808BFFFDF94F9000027700090606841 +:1007F000FF2190F8733090F85420323004F0CDFE91 +:1008000060684FF00C0880F87D5054E720787F280E +:100810009ED094F90000277000906068FF2190F846 +:10082000733090F85420323004F0B7FE16E0002BFD +:100830007ED102F11A01FDF7C2FAA068FDF7DDFAD8 +:100840006168C96B4876DBE0FFE796F85600082838 +:1008500070D096F8531081426AD0D5E04FF0060868 +:1008600029E7054691F8510000280CBF4FF0010B15 +:100870004FF0000B4FF00008A06810F8092BD209C8 +:1008800007D0407900F0C000402808BF4FF0010AAF +:1008900001D04FF0000A91F84000032806D191F8EA +:1008A0003900002818BF91F8569001D191F8539063 +:1008B0004846FDF72CF80090D8B34846FCF75BFE9D +:1008C000002818BF4FF0010BBAF1000F37D0A06815 +:1008D000A14600F10901009800E0B6E0F8F762FED9 +:1008E0005FEA0008D9F8040090F8319018BF49F089 +:1008F0000209606890F84010032924D0F7F7AAFF96 +:10090000002DABD0F7F75DFD002808BFB8F1000F50 +:100910007DD020787F2808BFFFDF94F90000277082 +:1009200000906068494690F8733090F8542002E0D7 +:1009300066E004E068E0323004F02FFE8EE7606885 +:1009400090F83190D5E7A168C06BCA78837E9A424F +:100950001BD10A79C37E9A4217D14A79037F9A4202 +:1009600013D18A79437F9A420FD1CA79837F9A4201 +:100970000BD10A7AC37F9A4207D10978407EC1F32E +:100980008011814208BF012700D0002796F853004C +:10099000082806D096F85610884208BF4FF0010983 +:1009A00001D04FF00009B8F1000F05D1BBF1000FE5 +:1009B00004D0F7F706FD08B1012000E000204DB19A +:1009C00096F84210012903D021B957EA090101D054 +:1009D000012100E00021084216D0606890F8421022 +:1009E000012908BF002F0BD1C06B00F11A01A068CC +:1009F000FDF7E5F9A068FDF700FA6168C96B487674 +:100A00004FF00E0857E602E0F7F724FF26E760688C +:100A100090F84100032818BF02287FF41FAFBAF1F5 +:100A2000000F3FF41BAF20787F2808BFFFDF94F949 +:100A30000000277000906068FE2190F8733090F8F5 +:100A40005420323004F0A9FD08E791F8481000293D +:100A500018BF00283FF47EAE0BE0000074010020B8 +:100A600044110020B9F1070F7FF474AE00283FF461 +:100A700071AEFEF790FC80461DE60000D0F8001134 +:100A800049B1D0E941231A448B691A448A61D0E9FB +:100A90003F12D16003E0FE4AD0F8FC101162D0E9A9 +:100AA0003F1009B1086170470028FCD00021816126 +:100AB00070472DE9FF4F06460C46488883B040F248 +:100AC000E24148430190E08A002500FB01FA94F8D6 +:100AD0007C0090460D2822D00C2820D024281ED03F +:100AE00094F87D0024281AD000208346069818B177 +:100AF0000121204603F0C0F894F8641094F86500D2 +:100B0000009094F8F0200F464FF47A794AB1012A08 +:100B100061D0022A44D0032A5DD0FFDFB5E0012076 +:100B2000E3E7B8F1000F00D1FFDFD94814F8641FE4 +:100B3000243090F83400F0F7C4FC01902078F8F7E6 +:100B4000CFFB4D4600F2E730B0FBF5F1DFF8409304 +:100B5000D9F80C0001EB00082078F8F7C1FB01463A +:100B600014F86409022816D0012816D040F6340083 +:100B700008444AF2EF010844B0FBF5F10198D9F8B6 +:100B80001C20411A514402EB08000D18012084F882 +:100B9000F0002D1D78E02846EAE74FF4C860E7E74B +:100BA000DFF8EC92A8F10100D9F80810014300D158 +:100BB000FFDFB848B8F1000F016801EB0A0506D065 +:100BC000D9F8080000F22630A84200D9FFDF032040 +:100BD00084F8F00058E094F87C20019D242A05D088 +:100BE00094F87D30242B01D0252A3AD1B4F8702016 +:100BF000B4F81031D21A521C12B2002A31DB94F828 +:100C0000122172B3174694F8132102B110460090D6 +:100C1000022916D0012916D040F6340049F60852B0 +:100C20008118022F12D0012F12D040F63400104448 +:100C3000814210D9081A00F5FA70B0FBF9F00544AA +:100C40000FE04846EAE74FF4C860E7E74846EEE7BA +:100C50004FF4C860EBE7401A00F5FA70B0FBF9F00A +:100C60002D1AB8F1000F0FD0DFF82482D8F8080051 +:100C700018B9B8F8020000B1FFDFD8F8080000F298 +:100C80002630A84200D9FFDF05B9FFDF2946D4F896 +:100C9000F400F4F750FFC4F8F400B06000203070A6 +:100CA0004FF0010886F80480204603F040F8ABF1CD +:100CB0000101084202D186F8058005E094F8F000B1 +:100CC000012844D003207071606A3946009A01F00F +:100CD00042FBF060069830EA0B0035D029463046DA +:100CE000F0F7BAF987B2204603F021F8B8420FD8DE +:100CF000074686F8058005FB07F1D4F8F400F4F701 +:100D00001AFFB06029463046F0F7A6F9384487B29A +:100D10003946204602F0B0FFB068C4F8F400A06E77 +:100D2000002811D0B4F87000B4F89420801A01B2F1 +:100D3000002909DD34F86C0F0144491E91FBF0F1E4 +:100D400089B201FB0020208507B0BDE8F08F0220AA +:100D5000B9E72DE9F04106460C46012001F0DBFA27 +:100D6000C5B20B2001F0D7FAC0B2854200D0FFDF38 +:100D70000025082C7ED2DFE804F00461696965C6AD +:100D80008293304601F0DDFA0621F3F78FF8040074 +:100D900000D1FFDF304601F0D4FA2188884200D02C +:100DA000FFDF94F8F00000B9FFDF204602F00FFEED +:100DB000374E21460020B5607580F561FDF7E9FCEE +:100DC00000F19807606AB84217D994F86500F7F700 +:100DD0000BF9014694F864004FF47A72022828D087 +:100DE000012828D040F6340008444AF2473108442C +:100DF000B0FBF2F1606A0844C51B21460020356152 +:100E0000FDF7C7FC618840F2E24251439830081A6E +:100E1000A0F22630706194F8652094F86410606A3E +:100E200001F099FAA0F5CB70B061BDE8F041F5F79B +:100E300060BE1046D8E74FF4C860D5E7BDE8F04182 +:100E400002F02FBEBDE8F041F7F7D5BE304601F005 +:100E500078FA0621F3F72AF8040000D1FFDF3046C4 +:100E600001F06FFA2188884200D0FFDF01220021C3 +:100E7000204600E047E0BDE8F04101F089BAF7F70D +:100E800073FDF7F7B8FE02204FF0E02104E0000008 +:100E9000CC11002084010020C1F88002BDE8F0815F +:100EA000304601F04EFA0621F3F700F8040000D1B5 +:100EB000FFDF304601F045FA2188884200D0FFDF8D +:100EC00094F8F000042800D0FFDF84F8F05094F884 +:100ED000FA504FF6FF76202D00D3FFDFFA4820F8B6 +:100EE000156094F8FA00F5F720F900B9FFDF20202B +:100EF00084F8FA002046FFF7C1FDF4480078BDE809 +:100F0000F041E2F701B8FFDFC8E770B5EE4C00250D +:100F1000443C84F82850E07868B1E570FEF71EF98B +:100F20002078042803D0606AFFF7A8FD6562E748CF +:100F30000078E1F7E9FFBDE8704001F03ABA70B51A +:100F4000E14C0146443CE069F5F706FE6568A2788D +:100F500090FBF5F172B140F27122B5FBF2F292B260 +:100F6000A36B01FB02F6B34202D901FB123200E08F +:100F70000022A2634D43002800DAFFDF2946E06922 +:100F8000F4F7D9FDE06170BD2DE9F05FFEF736F9A9 +:100F90008246CD48683800F1240881684646D8F872 +:100FA0001800F4F7C8FD0146F069F5F7D5FD4FF0DC +:100FB0000009074686F835903C4640F28F254E469C +:100FC0001EE000BF0AEB06000079F7F70DF80146B6 +:100FD0004AF2B12001444FF47A70B1FBF0F008EB13 +:100FE0008602414692681044844207D3241A91F83D +:100FF0003500A4F28F24401C88F83500761CF6B228 +:1010000098F83600B042DDD8002C10DD98F8351085 +:10101000404608EB81018968A14208D24168C91B9A +:10102000B1F5247F00D30D466C4288F8359098F8CE +:101030003560C3460AEB060898F80400F6F7D4FFBB +:101040004AF2B12101444FF47A7AB1FBFAF298F8EE +:101050000410082909D0042909D0002013180429F4 +:101060000AD0082908D0252207E0082000E0022045 +:1010700000EB40002830F1E70F22521D4FF4A8701A +:10108000082914D0042915D0022916D04FF0080CD5 +:101090005FF0280012FB0C00184462190BEB86036A +:1010A00010449A68D84690420BD8791925E04FF041 +:1010B000400CEFE74FF0100CECE74FF0040C182059 +:1010C000E8E798F8352098F836604046B24210D2EA +:1010D000521C88F835203C1B986862198418084611 +:1010E000F6F782FF4AF2B1210144B1FBFAF001198F +:1010F00003E080F83590D8F80410D8F81C00BDE85B +:10110000F05FF4F718BD2DE9FE4F14460546FEF7D3 +:1011100075F8DFF8B4A10290AAF1440A50469AF893 +:1011200035604FF0000B0AEB86018968CAF83C1065 +:10113000F4B3044600780027042825D005283ED0C3 +:10114000FFDFA04639466069F4F7F5FC0746F5F77E +:101150000BF881463946D8F80440F5F7FDFC401EEF +:1011600090FBF4F0C14361433846F4F7E4FC0146D8 +:10117000C8F81C004846F5F7EFFC002800DDFFDF4B +:10118000012188F813108DE0D4F81490D4F804806D +:1011900001F07AF9070010D0387800B9FFDF7969DB +:1011A00078684A460844414601F05AF9074600E08B +:1011B0000BE04045C5D9FFDFC3E75F46C1E7606A82 +:1011C00001F004F940F6B837BBE7C1690AEB460005 +:1011D0000191408D10B35446DAF81400FFF7AFFECA +:1011E0006168E069F4F7A7FC074684F835B0019C14 +:1011F000D0462046DAF81410F5F7AEFC81463946A1 +:101200002046F5F7A9FCD8F804200146B9FBF2F016 +:10121000B1FBF2F1884242D0012041E0F4F7A4FF93 +:10122000FFF78DFEFFF7B0FE9AF83510DAF804905C +:101230000AEB81010746896800913946DAF81C00FB +:10124000F5F78AFC00248046484504DB98FBF9F456 +:1012500004FB09F41AE0002052469AF8351007E022 +:1012600002EB800304F28F249B68401C1C44C0B234 +:101270008142F5D851B10120F6F7B6FE4AF2B1210C +:1012800001444FF47A70B1FBF0F004440099A8EBEC +:1012900004000C1A00D5FFDFCAF83C40A7E7002085 +:1012A00088F813009AF802005446B8B13946E0694C +:1012B000F5F752FC0146A26B40F2712042438A428C +:1012C00006D2C4F83CB009E03412002080010020AE +:1012D000E06B511A884200D30846E063AF6085F89E +:1012E00000B001202871029F94F835003F1DC05DB9 +:1012F000F6F77AFE4AF23B5101444FF47A70B1FBA3 +:10130000F0F0E16BFE300844E8602078042808D152 +:1013100094F8350004EB4000408D0A2801D20320E8 +:1013200000E00220687104EB4600408DC0B1284601 +:101330006168EFF791FE82B20020761C0CE000BFDE +:1013400004EB4001B0424B8D13449BB24B8501D35B +:101350005B1C4B85401CC0B294F836108142EFD222 +:10136000A8686061A06194F8350004EB4001488DE5 +:10137000401C488594F83500C05D082803D0042837 +:1013800003D000210BE0082100E0022101EB410124 +:1013900028314FF4A872082804D0042802D002286B +:1013A00007D028220A44042805D0082803D0252184 +:1013B00002E01822F6E70F21491D08280CD0042866 +:1013C0000CD002280CD0082011FB0020E16B8842D1 +:1013D00008D20120BDE8FE8F4020F5E71020F3E79A +:1013E0000420F1E70020F5E770B5FE4C061D14F867 +:1013F000352F905DF6F7F8FD4FF47A7100F2E73083 +:10140000B0FBF1F0D4F8071045182078805DF6F7AE +:1014100073FE2178895D082903D0042903D00022B6 +:101420000BE0082200E0022202EB420228324FF4D5 +:10143000A873082904D0042902D0022907D0282340 +:101440001344042905D0082903D0252202E01823DB +:10145000F6E70F22521D08290AD004290AD00229D2 +:101460000AD0082112FB0131081A281A293070BD50 +:101470004021F7E71021F5E70421F3E72DE9FF41CB +:1014800007460C46012000F046FFC5B20B2000F0D5 +:1014900042FFC0B2854200D0FFDF20460126002572 +:1014A000D04C082869D2DFE800F004304646426894 +:1014B0006865667426746078002819D1FDF79EFE71 +:1014C000009594F835108DF808104188C90411D0A2 +:1014D000206C019003208DF80900C24824388560F3 +:1014E000C56125746846FDF768FB002800D0FFDF62 +:1014F000BDE8FF81FFF778FF0190E07C10B18DF827 +:101500000950EAE78DF80960E7E7607840B1207C90 +:1015100008B9FDF7F9FD6574BDE8FF41F4F72FBD8B +:10152000A674FDF739FC0028E2D0FFDFE0E7BDE854 +:10153000FF41F7F760BBFDF761FE4088C00407D0AC +:1015400001210320FDF75EFEA7480078E1F7DCFCEF +:10155000002239466846FFF7D6FD38B1694638465D +:1015600000F0EDFE0028C3D1FFDFC1E7E670FFF712 +:10157000CCFCBDE7BDE8FF41C7E4FFDFB8E7994910 +:1015800050B101228A704A6840F27123B2FBF3F233 +:1015900002EB0010886370470020887070472DE9C7 +:1015A000F05F894640F271218E4E48430025044683 +:1015B000706090462F46D0074AF2B12A4FF47A7BEA +:1015C0000FD0B9F800004843B0600120F6F70CFDD9 +:1015D00000EB0A01B1FBFBF0241AB7680125A4F265 +:1015E0008F245FEA087016D539F8151040F2712083 +:1015F000414306EB85080820C8F80810F6F7F4FC0C +:1016000000EB0A01B1FBFBF0241AD8F80800A4F2A1 +:101610008F2407446D1CA74219D9002D17D0391B00 +:10162000B1FBF5F0B268101AB1FBF5F205FB12122E +:10163000801AB060012008E0B1FBF5F306EB8002F0 +:101640009468E31A401CC0B29360A842F4D3BDE88A +:10165000F09F2DE9F041634C00262078042804D047 +:101660002078052801D00C2018E401206070607CEF +:10167000002538B1EFF3108010F0010F72B610D0D2 +:1016800001270FE0FDF7BAFD074694F82000F5F7B3 +:10169000B2F87888C00411D000210320FDF7B2FD14 +:1016A0000CE00027607C38B1A07C28B1FDF72CFD50 +:1016B0006574A574F4F763FC07B962B694F820006A +:1016C000F5F705FB94F8280030B184F8285020780D +:1016D000052800D0FFDF0C26657000F06AFE30465A +:1016E00012E4404810B5007808B1FFF7B2FF00F0EF +:1016F000D4FE3C4900202439086210BD10B53A4C94 +:1017000058B1012807D0FFDFA06841F66A0188427E +:1017100000D3FFDF10BD40F6C410A060F4E73249EB +:1017200008B508702F4900200870487081F828001B +:10173000C8700874487488742022486281F8202098 +:10174000243948704FF6FF7211F1680121F810201A +:10175000401CC0B22028F9D30020FFF7CFFFFFF7CD +:10176000C0FF1020ADF80000012269460420FFF7F9 +:1017700016FF08BD7FB51B4C05460E46207810B1FC +:101780000C2004B070BD95F8652095F86410686A67 +:1017900000F0C5FEC5F80401656295F8F00000B1DF +:1017A000FFDF104900202439C861052121706070D5 +:1017B00084F82800014604E004EB4102491C5085EE +:1017C000C9B294F836208A42F6D284F83500304601 +:1017D000FFF7D5FE0548F4F74DFC84F820002028DB +:1017E00007D105E0F0110020800100207D140200E7 +:1017F000FFDFF4F7B9FC606194F82010012268461D +:10180000FFF781FC00B9FFDF94F82000694600F083 +:1018100096FD00B9FFDF0020B3E7F94810B5007866 +:1018200008B1002010BD0620F2F7DAFA80F00100BE +:1018300010BDF8B5F24D0446287800B1FFDF002056 +:10184000009023780246DE0701466B4605D060888B +:10185000A188ADF80010012211462678760706D53A +:10186000E088248923F8114042F00802491C491EEF +:1018700085F836101946FFF792FE0020F8BD1FB517 +:1018800011B1112004B010BDDD4C217809B10C203C +:10189000F8E70022627004212170114605E000BFC4 +:1018A00004EB4103491C5A85C9B294F836308B4287 +:1018B000F6D284F83520FFF762FED248F4F7DAFB5F +:1018C00084F82000202800D1FFDF00F0DDFD10B1FA +:1018D000F4F74AFC05E0F4F747FC40F6B831F4F7BA +:1018E0002AF9606194F8201001226846FFF70BFC8A +:1018F00000B9FFDF94F82000694600F020FD00B930 +:10190000FFDF0020BEE770B5BD4C616A0160FFF7E4 +:10191000A0FE050002D1606AFFF7B0F80020606207 +:10192000284670BD7FB5B64C2178052901D00C2022 +:1019300027E7B3492439C860606A00B9FFDF606AED +:1019400090F8F00000B1FFDF606A90F8FA002028FC +:1019500000D0FFDFAC48F4F78DFB616A0546202814 +:1019600081F8FA000E8800D3FFDFA548443020F844 +:101970001560606A90F8FA00202800D1FFDF00238C +:1019800001226846616AFFF794F8606A694690F838 +:10199000FA0000F0D4FC00B9FFDF00206062F0E63E +:1019A000974924394870704710B540F2E24300FB74 +:1019B00003F4002000F0B3FD844201D9201A10BDC9 +:1019C000002010BD70B50D46064601460020FCF70C +:1019D000E0FE044696F86500F6F706FB014696F829 +:1019E00064004FF47A72022815D0012815D040F611 +:1019F000340008444AF247310844B0FBF2F17088E1 +:101A000040F271225043C1EB4000A0F22630A542C3 +:101A100006D2214605E01046EBE74FF4C860E8E740 +:101A20002946814204D2A54201D2204600E0284640 +:101A3000706270BD70B50546FDF7E0FB7049007837 +:101A400024398C689834072D30D2DFE805F004344F +:101A500034252C34340014214FF4A873042810D0FA +:101A60000822082809D02A2102280FD011FB0240A1 +:101A700000222823D118441819E0402211FB02400B +:101A8000F8E7102211FB02402E22F3E7042211FB9B +:101A9000024000221823EDE7282100F04BFC04440B +:101AA00004F5317403E004F5B07400E0FFDF54483E +:101AB000C06BA04201D9012070BD002070BD70B57F +:101AC0004F4C243C607870B1D4E904512846A26898 +:101AD000EFF7EDFA2061A84205D0A169401B084448 +:101AE000A061F5F706F82169A068884201D820783E +:101AF00008B1002070BD012070BD2DE9F04F0546F2 +:101B000085B016460F461C461846F6F7F5FA05EB63 +:101B100047014718204600F0F5FB4AF2C5714FF423 +:101B20007A7908444D46B0FBF5F0384400F160087E +:101B30003348761C24388068304404902046F6F7F9 +:101B4000DBFAA8EB0007204600F0DCFB0646204647 +:101B5000F6F74AFA301AB0FBF5F03A1A18252820A1 +:101B60004FF4C8764FF4BF774FF0020B082C30D0FB +:101B7000042C2BD00021022C2ED0082311F1280197 +:101B800003EB830C0CEB831319440A444FF0000A57 +:101B9000082C29D0042C22D00021022C29D0054663 +:101BA000082001F5B07100BF00EB0010284481420D +:101BB00032D2082C2AD0042C1ED00020022C28D08F +:101BC0000821283001EB0111084434E03946102384 +:101BD000D6E731464023D3E704231831D0E73D460A +:101BE00040F2EE311020DFE735464FF435614020FA +:101BF000DAE70420B431D7E738461021E2E70000E5 +:101C0000F01100207D140200530D020030464021E7 +:101C1000D8E704211830D5E7082C4FD0042C4AD03F +:101C20000021022C4DD0082311F12801C3EBC30081 +:101C300000EB4310084415182821204600F07AFBD9 +:101C400005EB4001082C42D0042C3DD00026022C8C +:101C50003FD0082016F1280600EB801006EB80002C +:101C60000E180120FA4D8DF804008DF800A08DF8B3 +:101C700005B0A86906F22A260499F3F75CFFCDE9BE +:101C800002062046F6F7B0F94AF23B510144B1FB97 +:101C9000F9F0301AFE38E8630298C5F84080A86170 +:101CA00095F82000694600F04AFB002800D1FFDFCC +:101CB00005B0BDE8F08F39461023B7E73146402321 +:101CC000B4E704231831B1E73E461020C4E74020B2 +:101CD000C2E704201836BFE72DE9FE4F06461C4632 +:101CE000174688464FF0010A1846F6F705FAD84D10 +:101CF000243DA9688A1907EB48011144471820467A +:101D000000F000FB4FF47A7BD84600F6FB00B0FBF6 +:101D1000F8F0384400F120092046F6F7EDF9A968FB +:101D20000246A9EB0100801B871A204600F0EAFA60 +:101D300005462046F6F758F9281AB0FBF8F03A1A8B +:101D4000182528204FF4C8774FF4BF78082C2DD0E1 +:101D5000042C28D00021022C2BD0082311F12801BB +:101D600003EB830C0CEB831319440A44082C28D092 +:101D7000042C21D00021022C28D00546082001F592 +:101D8000B07100BF00EB0010284481422AD2082C19 +:101D900022D0042C1DD00020022C20D00821283075 +:101DA00001EB01112CE041461023D9E739464023CD +:101DB000D6E704231831D3E7454640F2EE31102030 +:101DC000E0E73D464FF435614020DBE70420B431C5 +:101DD000D8E740461021E3E738464021E0E70421F8 +:101DE0001830DDE7082C48D0042C43D00020022C0A +:101DF00046D0082110F12800C1EBC10303EB4111CB +:101E0000084415182821204600F094FA05EB4001FB +:101E1000082C3BD0042C36D00027022C38D00820C8 +:101E200017F1280700EB801007EB80000C1804F571 +:101E300096740C98F6F7D8F84AF23B510144B1FB7E +:101E4000FBF0834DFE30A5F12407E96B06F1FE029D +:101E50000844B9680B191A44824224D93219114432 +:101E60000C1AFE342044B0F1807F37D2642C12D299 +:101E7000642011E040461021BEE738464021BBE710 +:101E800004211830B8E747461020CBE74020C9E7C7 +:101E900004201837C6E720460421F4F790FEE8B185 +:101EA000E86B2044E863E0F703FFB9682938314460 +:101EB0000844CDE9000995F835008DF808000220A6 +:101EC0008DF809006846FCF778FE00B1FFDFFCF7EB +:101ED00063FF00B1FFDF5046BDE8FE8F4FF0000A00 +:101EE000F9E71FB500F021FB594C607880B994F8F0 +:101EF000201000226846FFF706F938B194F8200058 +:101F0000694600F01CFA18B9FFDF01E00120E0701B +:101F1000F4F735F800206074A0741FBD2DE9F84F68 +:101F2000FDF76CF90646451CC07840090CD0012825 +:101F30000CD002280CD000202978824608064FF4E5 +:101F4000967407D41E2006E00120F5E70220F3E78F +:101F50000820F1E72046B5F80120C2F30C0212FB7D +:101F600000F7C80901D010B103E01E2401E0FFDF33 +:101F70000024F6F7D3F8A7EB00092878B77909EB26 +:101F80000408C0F3801010B120B1322504E04FF4F2 +:101F9000FA7501E0FFDF00250C2F00D3FFDF2D488D +:101FA0002D4A30F81700291801FB0821501CB1FBFD +:101FB000F0F5F6F76DF8F6F717F84FF47A7100F2CE +:101FC0007160B0FBF1F1A9EB0100471BA7F15900CB +:101FD000103FB0F5247F11D31D4E717829B9024608 +:101FE000534629462046FFF788FD00F09EFAF3F796 +:101FF000C6FF00207074B074BDE8F88F3078009090 +:102000005346224629463846FFF766FE0028F3D19C +:1020100001210220FDF7F6F8BDE8F84F61E710B5A1 +:102020000446012903D10A482438007830B104203D +:1020300084F8F000BDE81040F3F7A1BF00220121B1 +:10204000204600F0A5F934F8700F401C2080F1E71D +:10205000F0110020646302003F420F002DE9F041BF +:102060000746FDF7CBF8050000D1FFDF287810F018 +:102070000C0F01D0012100E00021F74C606A3030E4 +:10208000FCF7C7FA29783846EFF71BFAA4F12406C3 +:102090000146A069B26802446FB32878082803D0CB +:1020A000042803D000230BE0082300E0022303EB05 +:1020B000430328334FF4A877082804D0042802D01B +:1020C000022810D028273B4408280ED004280ED020 +:1020D00002280ED05FF00800C0EBC00707EB4010ED +:1020E0001844983009E01827EDE74020F4E7102065 +:1020F000F2E70420F0E74FF4FC701044471828780A +:102100003F1DF5F771FF014628784FF47A720228D7 +:102110001DD001281DD040F6340008444AF2EF01DA +:102120000844B0FBF2F03A1A606A40F2E241B0466D +:102130004788F0304F43316A81420DD03946206BD9 +:1021400000F08EF90646B84207D9FFDF05E01046D9 +:10215000E3E74FF4C860E0E70026C04880688642A5 +:1021600007D2616A40F271224888424306EB420678 +:1021700004E040F2E240B6FBF0F0616AC882606AB7 +:10218000297880F86410297880F865100521417558 +:10219000C08A6FF41C71484306EB400040F635419D +:1021A000C8F81C00B0EB410F00D3FFDFBDE8F081A1 +:1021B00010B5052937D2DFE801F00509030D31001C +:1021C000002100E00121BDE8104028E7032180F84C +:1021D000F01010BD0446408840F2E24148439F4958 +:1021E000091D0860D4F818010089E082D4F81801AC +:1021F00080796075D4F8180140896080D4F818019E +:102200008089A080D4F81801C089E0802046A16AA6 +:10221000FFF7D8FB022084F8F00010BD816ABDE80A +:102220001040FFF7CFBBFFDF10BD70B58A4C243CD8 +:102230000928A1683FD2DFE800F0050B0B15131544 +:1022400038380800BDE870404BE6BDE8704065E6F0 +:10225000022803D00020BDE87040FFE60120FAE725 +:10226000E16070BD032802D005281CD000E0E160C9 +:102270005FF0000600F059F9774D012085F828003D +:1022800085F83460686AA9690026C0F8F41080F8FF +:10229000F060E068FFF746FB00B1FFDFF3F76FFE89 +:1022A0006E74AE7470BD0126E4E76C480078BDE83A +:1022B0007040E0F729BEFFDF70BD674924394860F0 +:1022C000704770B5644D0446243DB1B14FF47A7641 +:1022D000012903D0022905D0FFDF70BD1846F5F7AC +:1022E000FCFE05E06888401C68801046F6F7F8FFA1 +:1022F00000F2E730B0FBF6F0201AA86070BD564837 +:1023000000787047082803D0042801D0F5F76CBE88 +:102310004EF628307047002804DB00F1E02090F8EA +:10232000000405E000F00F0000F1E02090F8140D2B +:102330004009704710F00C0000D008467047F4F7D1 +:102340003EB910B50446202800D3FFDF4248443090 +:1023500030F8140010BD70B505460C461046F5F770 +:1023600043FE4FF47A71022C0DD0012C0DD040F6B3 +:10237000340210444AF247321044B0FBF1F02844D2 +:1023800000F5CB7070BD0A46F3E74FF4C862F0E782 +:102390001FB513460A46044601466846FEF789FB08 +:1023A00094F8FA006946FFF7CAFF002800D1FFDF62 +:1023B0001FBD70B5284C0025257094F82000F3F758 +:1023C000B4FE00B9FFDF84F8205070BD2DE9F04164 +:1023D000050000D1FFDF204A0024243AD5F804612B +:1023E0002046631E116A08E08869B04203D3984210 +:1023F00001D203460C460846C9680029F4D104B945 +:1024000004460021C5F80041F035C4B1E068E5603C +:10241000E86000B105612E698846A96156B1B069CE +:1024200030B16F69B84200D2FFDFB069C01BA8614C +:10243000C6F81880084D5CB1207820B902E0E96048 +:102440001562E8E7FFDF6169606808442863ADE66C +:10245000C5F83080AAE60000F011002080010020BD +:1024600010B50C4601461046F4F776FB002806DA54 +:10247000211A491EB1FBF4F101FB040010BD90FBD1 +:10248000F4F101FB140010BD2E48016A002001E0A8 +:102490000846C9680029FBD170472DE9FE43294D44 +:1024A0000120287000264FF6FF7420E00621F1F786 +:1024B000FDFC070000D1FFDF97F8FA00F037F4F7D2 +:1024C00006FC07F80A6BA14617F8FA89B8F1200F45 +:1024D00000D3FFDF1B4A683222F8189097F8FA0001 +:1024E000F3F723FE00B9FFDF202087F8FA006946E2 +:1024F0000620F1F764FC50B1FFDF08E0029830B12C +:1025000090F8F01019B10088A042CFD104E06846DD +:10251000F1F733FC0028F1D02E70BDE8FE8310B532 +:10252000FFF719FF00F5C87010BD064800212430E0 +:1025300090F8352000EB4200418503480078E0F731 +:10254000E3BC0000CC11002080010020012804D051 +:10255000022805D0032808D105E0012907D004E0AE +:10256000022904D001E0042901D000207047012095 +:102570007047F748806890F8A21029B1B0F89E1013 +:10258000B0F8A020914215D290F8A61029B1B0F869 +:10259000A410B0F8A02091420CD2B0F89C20B0F862 +:1025A0009A108A4206D290F88020B0F898001AB1AA +:1025B000884203D3012070470628FBD200207047D1 +:1025C0002DE9F041E24D0746A86800F1700490F84B +:1025D000140130B9E27B002301212046EEF7D2FC42 +:1025E00010B1A08D401CA08501263D21AFB92878EF +:1025F000022808D001280AD06878C8B110F0140F5A +:1026000009D01E2039E0162037E026773EE0A86882 +:1026100090F8160131E0020701D56177F5E78107EF +:1026200001D02A2029E0800600D4FFDF232024E007 +:1026300094F8320028B1E08D411CE185218E88425A +:1026400013D294F8360028B1A08E411CA186218EA9 +:1026500088420AD2A18D608D814203D3AA6892F884 +:10266000142112B9228E914201D3222005E0217C4F +:1026700029B1218D814207D308206077C5E7208DDD +:10268000062801D33E20F8E7207FB0B10020207358 +:10269000607320740221A868FFF78AFDA86890F88B +:1026A000E410012904D1D0F81C110878401E0870EC +:1026B000E878BDE8F041E0F727BCA868BDE8F04144 +:1026C0000021FFF775BDA2490C28896881F8E40054 +:1026D00014D0132812D0182810D0002211280ED0A0 +:1026E00007280BD015280AD0012807D0002805D0CC +:1026F000022803D021F89E2F012008717047A1F80D +:10270000A420704710B5924CA1680A88A1F86021F6 +:1027100081F85E0191F8640001F046FBA16881F840 +:10272000620191F8650001F03FFBA16881F8630147 +:10273000012081F85C01002081F82E01E078BDE8DD +:102740001040E0F7E1BB70B5814C00231946A0684A +:1027500090F87C207030EEF715FC00283DD0A06882 +:1027600090F820110025C9B3A1690978B1BB90F890 +:102770007D00EEF7EFFB88BBA168B1F870000A2876 +:102780002DD905220831E069EBF72AFE10B3A068C5 +:10279000D0F81C11087858B10522491CE069EBF704 +:1027A0001FFE002819D1A068D0F81C01007840B99C +:1027B000A068E169D0F81C010A68C0F80120097915 +:1027C0004171A068D0F81C110878401C08700120E5 +:1027D000FFF779FFA06880F8205170BDFFE7A0687F +:1027E00090F8241111B190F82511C1B390F82E1171 +:1027F0000029F2D090F82F110029EED190F87D0039 +:10280000EEF7A8FB0028E8D1A06890F8640001F07A +:10281000CBFA0646A06890F8650001F0C5FA0546B7 +:10282000A06890F830113046FFF790FEA0B3A06882 +:1028300090F831112846FFF789FE68B3A268B2F814 +:10284000703092F86410B2F8320102F58872EEF737 +:1028500001FE20B3A168252081F87C00BDE7FFE7D9 +:1028600090F87D10242918D090F87C10242914D0D9 +:102870005FF0000300F5897200F59271FBF78EFEA0 +:10288000A16881F8245101F13000C28A21F8E62FB5 +:10289000408B4880142007E005E00123EAE7BDE80B +:1028A000704000202EE71620BDE870400BE710B501 +:1028B000F4F7FAFD0C2813D3254C0821A068D0F8B2 +:1028C00018011E30F4F7F4FD28B1A0680421D830B7 +:1028D000F4F7EEFD00B9FFDFBDE810400320F2E69B +:1028E00010BD10B51A4CA068D0F818110A78002A4B +:1028F0001FD04988028891421BD190F87C20002388 +:1029000019467030EEF73EFB002812D0A068D0F8D0 +:1029100018110978022907D003290BD0042919D0EE +:10292000052906D108200DE090F87D00EEF712FB96 +:1029300040B110BD90F8811039B190F8820000B913 +:10294000FFDF0A20BDE81040BDE6BDE81040AEE75D +:102950008C01002090F8AA008007EAD10C20FFF734 +:10296000B2FEA068002120F89E1F01210171017BA9 +:1029700041F00101017310BD70B5F74CA268556EAE +:10298000EEF702FDEBB2C1B200228B4203D0A36886 +:1029900083F8121102E0A16881F81221C5F3072122 +:1029A000C0F30720814203D0A16881F8130114E726 +:1029B000A06880F8132110E710B5E74C0421A06847 +:1029C000FFF7F6FBA06890F85A10012908D000F52F +:1029D0009E71FBF7ACFEE078BDE81040E0F794BADA +:1029E000022180F85A1010BD70B5DB4CA06890F839 +:1029F000E410FE2955D16178002952D190F87F204A +:102A0000002301217030EEF7BDFA002849D1A068FB +:102A100090F8141109B1022037E090F87C200023CF +:102A200019467030EEF7AEFA28B1A06890F896001B +:102A300008B1122029E0A068002590F87C20122A15 +:102A40001DD004DC032A23D0112A04D119E0182A4E +:102A50001AD0232A26D0002304217030EEF792FAF0 +:102A600000281ED1A06890F87D10192971D020DCB3 +:102A700001292AD0022935D0032932D120E00B20A8 +:102A800003E0BDE8704012E70620BDE870401AE69A +:102A900010F8E21F01710720FFF715FEA06880F80B +:102AA0007C509AE61820FFF70EFEA068A0F89E5012 +:102AB00093E61D2918D01E2916D0212966D149E098 +:102AC00010F8E11F4171072070E00C20FFF7FBFDBB +:102AD000A06820F8A45F817941F00101817100F8BC +:102AE000275C53E013202CE090F8252182BB90F85E +:102AF0002421B2B1242912D090F87C1024290ED0C0 +:102B00005FF0000300F5897200F59271FBF746FD56 +:102B1000A0681E2180F87D1080F8245103E0012375 +:102B2000F0E71E2932D1A068FBF797FDFFF744FFBD +:102B3000A16801F13000C28A21F8E62F408B48805D +:102B40001520FFF7C0FDA068A0F8A45080F87D50C4 +:102B50001CE02AE090F8971051B180F8125180F8EB +:102B600013511820FFF7AFFDA068A0F8A4500DE0A6 +:102B700090F82F1151B990F82E1139B1C16DD0F8DC +:102B80003001FFF7F9FE1820FFF79DFDA06890F8CF +:102B9000E400FE2885D1FFF7A4FEA06890F8E400C9 +:102BA000FE2885D1BDE87040CDE51120FFF78BFDF3 +:102BB000A068CBE7684A0129926819D0002302294E +:102BC0000FD003291ED010B301282BD0032807D122 +:102BD00092F87C00132803D0162801D0182804D1BD +:102BE000704792F8E4000028FAD0D2F8180117E0F4 +:102BF00092F8E4000128F3D0D2F81C110878401EA6 +:102C00000870704792F8E4000328EED17047D2F8BC +:102C10001801B2F870108288891A09B20029F5DB10 +:102C200003707047B2F87000B2F82211401A00B277 +:102C30000028F6DBD2F81C010178491E01707047AC +:102C400070B5044690F87C0000250C2810D00D28A3 +:102C50002ED1D4F81811B4F870008988401C88422D +:102C600026D1D4F864013C4E017811B3FFDF42E075 +:102C7000B4F87000B4F82211401C884218D1D4F87E +:102C80001C01D0F80110A160407920730321204677 +:102C9000EDF7F1FDD4F81C01007800B9FFDF012148 +:102CA000FE20FFF787FF84F87C50012084F8B200F3 +:102CB00093E52188C180D4F81801D4F864114089C3 +:102CC0000881D4F81801D4F8641180894881D4F8B7 +:102CD0001801D4F86411C0898881D4F864010571A1 +:102CE000D4F8641109200870D4F864112088488051 +:102CF000F078E0F709F902212046EDF7BCFD032149 +:102D00002046FFF755FAB068D0F81801007802287D +:102D100000D0FFDF0221FE20FFF74CFF84F87C503B +:102D20005BE52DE9F0410C4C00260327D4F808C0E0 +:102D3000012598B12069C0788CF8E20005FA00F00E +:102D4000C0F3C05000B9FFDFA06800F87C7F468464 +:102D500080F82650BDE8F0818C01002000239CF80B +:102D60007D2019460CF17000EEF70CF970B1607817 +:102D70000028EFD12069C178A06880F8E11080F8C0 +:102D80007D70A0F8A46080F8A650E3E76570E1E7E5 +:102D9000F0B5F74C002385B0A068194690F87D2067 +:102DA0007030EEF7EFF8012580B1A06890F87C0054 +:102DB00023280ED024280CD06846F6F785FB68B18E +:102DC000009801A9C0788DF8040008E0657005B08E +:102DD000F0BD607840F020006070F8E70021A06846 +:102DE00003AB162290F87C00EEF74FFB002648B1AB +:102DF000A0689DF80C20162180F80C2180F80D1198 +:102E0000192136E02069FBF722FB78B121690879A6 +:102E100000F00702A06880F85C20497901F0070102 +:102E200080F85D1090F82F310BBB03E00020FFF716 +:102E300078FFCCE790F82E31CBB900F164035F78CE +:102E4000974205D11A788A4202D180F897500EE055 +:102E500000F5AC71028821F8022990F85C200A7113 +:102E600090F85D0048710D70E078E0F74DF8A068CB +:102E7000212180F87D1080F8A650A0F8A460A6E774 +:102E8000F8B5BB4C00231946A06890F87D2070303F +:102E9000EEF778F840B32069FBF7BEFA48B3206933 +:102EA000FBF7B4FA07462069FBF7B4FA0646206937 +:102EB000FBF7AAFA05462069FBF7AAFA0146009734 +:102EC000A06833462A463030FBF79BFBA1680125FA +:102ED00091F87C001C2810D091F85A00012812D0DB +:102EE00091F8250178B90BE0607840F0010060703E +:102EF000F8BDBDE8F840002013E781F85A5002E021 +:102F000091F8240118B11E2081F87D000BE01D20EE +:102F100081F87D0001F5A57231F8300BFBF7FBFB62 +:102F2000E078DFF7F1FFA068002120F8A41F85708A +:102F3000F8BD10B58E4C00230921A06890F87C20C4 +:102F40007030EEF71FF848B16078002805D1A1680D +:102F500001F8960F087301F81A0C10BD012060707B +:102F600010BD7CB5824C00230721A06890F87C201E +:102F70007030EEF707F838B36078002826D169463C +:102F80002069FBF75FFA9DF80000002500F025019D +:102F9000A06880F8B0109DF8011001F0490180F898 +:102FA000B11080F8A250D0F81811008849888142E9 +:102FB00000D0FFDFA068D0F818110D70D0F86411B0 +:102FC0000A7822B1FFDF16E0012060707CBD30F886 +:102FD000E82BCA80C16F0D71C16F009A8A60019A97 +:102FE000CA60C26F0821117030F8E81CC06F4180C0 +:102FF000E078DFF789FFA06880F87C507CBD70B571 +:103000005B4C00231946A06890F87D207030EDF7E6 +:10301000B9FF012540B9A0680023082190F87C2061 +:103020007030EDF7AFFF10B36078002820D1A068B2 +:1030300090F8AA00800712D42069FBF7C9F9A168AB +:1030400081F8AB00206930F8052FA1F8AC2040884A +:10305000A1F8AE0011F8AA0F40F002000870A068B5 +:103060004FF0000690F8AA10C90702D011E0657071 +:103070009DE490F87D20002319467030EDF782FF23 +:1030800000B9FFDFA06880F87D5080F8A650A0F856 +:10309000A460A06890F87C10012906D180F87C60BB +:1030A00080F8A260E078DFF72FFFA168D1F818015F +:1030B000098842888A42DBD101780429D8D1067078 +:1030C000E078DFF721FFA06890F87C100029CFD1CD +:1030D00080F8A2606BE470B5254DA86890F87C106C +:1030E0001A2902D00220687061E469780029FBD1B6 +:1030F000002480F8A74080F8A240D0F8181100887A +:103100004988814200D0FFDFA868D0F818110C7000 +:10311000D0F864110A780AB1FFDF25E090F8A82002 +:1031200072B180F8A8400288CA80D0F864110C718E +:10313000D0F864210E2111700188D0F864010DE0EF +:1031400030F8E82BCA80C16F0C71C26F0121117277 +:10315000C26F0D21117030F8E81CC06F418000F083 +:10316000A1FEE878DFF7D0FEA86880F87C401EE476 +:103170008C01002070B5FA4CA16891F87C20162AC9 +:1031800001D0132A02D191F8A82012B10220607058 +:103190000DE46278002AFBD181F8E000002581F877 +:1031A000A75081F8A250D1F81801098840888842B8 +:1031B00000D0FFDFA068D0F818010078032800D005 +:1031C000FFDF0321FE20FFF7F5FCA068D0F86411B3 +:1031D0000A780AB1FFDF14E030F8E02BCA8010F85B +:1031E000081BC26F1171C16F0D72C26F0D2111707A +:1031F00030F8E81CC06F418000F054FEE078DFF743 +:1032000083FEA06880F87C504BE470B5D44C092153 +:103210000023A06890F87C207030EDF7B3FE002505 +:1032200018B12069007912281ED0A0680A21002355 +:1032300090F87C207030EDF7A5FE18B12069007978 +:10324000142814D02069007916281AD1A06890F8A3 +:103250007C101F2915D180F87C5080F8A250BDE861 +:1032600070401A20FFF74EBABDE8704061E6A068D2 +:1032700000F87C5F458480F82650BDE87040FFF779 +:103280009BBB0EE470B5B64C2079C00773D02069A3 +:1032900000230521C578A06890F87C207030EDF7F8 +:1032A00071FE98B1062D11D006DC022D0ED0042D32 +:1032B0000CD0052D06D109E00B2D07D00D2D05D022 +:1032C000112D03D0607840F008006070607800280D +:1032D00051D12069FAF7E0FF00287ED0206900254F +:1032E0000226C178891E162977D2DFE801F00B7615 +:1032F00034374722764D76254A457676763A5350CE +:103300006A6D7073A0680023012190F87F207030EF +:10331000EDF738FE08BB2069FBF722F8A16881F8B9 +:103320001601072081F87F0081F8A65081F8A2508D +:1033300056E0FFF76AFF53E0A06890F87C100F2971 +:1033400001D066704CE0617839B980F88150122163 +:1033500080F87C1044E000F0D0FD41E000F0ACFDCE +:103360003EE0FBF7A9F803283AD12069FBF7A8F85B +:10337000FFF700FF34E03BE00079F9E7FFF7ABFE31 +:103380002EE0FFF73CFE2BE0FFF7EBFD28E0FFF718 +:10339000D0FD25E0A0680023194690F87D2070300C +:1033A000EDF7F0FD012110B16078C8B901E061705E +:1033B00016E0A06820F8A45F817000F8276C0FE089 +:1033C0000BE0FFF75DFD0BE000F034FD08E0FFF7D8 +:1033D000DFFC05E000F0FAFC02E00020FFF7A1FCB2 +:1033E000A268F2E93001401C41F10001C2E900018C +:1033F0005EE42DE9F0415A4C2079800741D5607890 +:1034000000283ED1E06801270026C178204619290E +:10341000856805F170006FD2DFE801F04B3E0D6F5B +:10342000C1C1801C34C1556287C1C1C1C1BE8B9569 +:1034300098A4B0C1BA0095F87F2000230121EDF7D0 +:10344000A1FD00281DD1A068082180F87F1080F818 +:10345000A26090E0002395F87D201946EDF792FDDB +:1034600010B1A06880F8A660A0680023194690F803 +:103470007C207030EDF786FD002802D0A06880F82F +:10348000A26067E4002395F87C201946EDF77AFDE9 +:1034900000B9FFDF042008E0002395F87C201946DE +:1034A000EDF770FD00B9FFDF0C20A16881F87C000A +:1034B00050E4002395F87C201946EDF763FD00B930 +:1034C000FFDF0D20F1E7002395F87C201946EDF78A +:1034D00059FD00B9FFDFA0680F2180F8A77008E050 +:1034E00095F87C00122800D0FFDFA068112180F839 +:1034F000A87080F87C102DE451E0002395F87C2022 +:103500001946EDF73FFD20B9A06890F8A80000B972 +:10351000FFDFA068132180F8A770EAE795F87C0028 +:10352000182800D0FFDF1A20BFE7BDE8F04100F007 +:1035300063BD002395F87C201946EDF723FD00B903 +:10354000FFDF0520B1E785F8A66003E4002395F8C6 +:103550007C201946EDF716FD00B9FFDF1C20A4E71B +:103560008C010020002395F87D201946EDF70AFD17 +:1035700000B9FFDFA06880F8A66006E4002395F894 +:103580007C201946EDF7FEFC00B9FFDF1F208CE719 +:10359000BDE8F04100F0F8BC85F87D60D3E7FFDFBF +:1035A0006FE710B5F74C6078002837D120794007D5 +:1035B0000FD5A06890F87C00032800D1FFDFA06839 +:1035C00090F87F10072904D101212170002180F893 +:1035D0007F10FFF70EFF00F0B5FCFFF753FEA07859 +:1035E000000716D5A0680023052190F87C207030D4 +:1035F000EDF7C8FC50B108206070A068D0F86411E5 +:1036000008780D2800D10020087002E00020F9F7AA +:10361000F9FCA068BDE81040FFF712BB10BD2DE912 +:10362000F041D84C07464FF00005607808436070C1 +:10363000207981062046806802D5A0F8985004E0E1 +:10364000B0F89810491CA0F8981000F018FD012659 +:10365000F8B1A088000506D5A06890F8821011B1D5 +:10366000A0F88E5015E0A068B0F88E10491CA0F8A4 +:103670008E1000F0F3FCA068B0F88E10B0F8902027 +:10368000914206D3A0F88E5080F83A61E078DFF7D7 +:103690003BFC207910F0600F08D0A06890F88010F3 +:1036A00021B980F880600121FEF782FD1FB9FFF784 +:1036B00078FFFFF799F93846FEF782FFBDE8F04141 +:1036C000F5F711BFAF4A51789378194313D11146DA +:1036D0000128896808D01079400703D591F87F0048 +:1036E000072808D001207047B1F84C00098E8842A5 +:1036F00001D8FEF7E4B900207047A249C278896872 +:10370000012A06D05AB1182A08D1B1F81011FAF7D7 +:10371000BABEB1F822114172090A81727047D1F81C +:10372000181189884173090A8173704770B5954CE7 +:1037300005460E46A0882843A080A80703D5E807C1 +:1037400000D0FFDFE660E80700D02661A80719D5A2 +:10375000F078062802D00B2814D10BE0A06890F86E +:103760007C1018290ED10021E0E93011012100F868 +:103770003E1C07E0A06890F87C10122902D10021BD +:1037800080F88210280601D50820A07068050AD5A7 +:10379000A0688288B0F87010304600F07FFC304698 +:1037A000BDE87040A9E763E43EB505466846F5F715 +:1037B00065FE00B9FFDF222200210098EAF767FECC +:1037C00003210098FAF750FD0098017821F01001CC +:1037D00001702946FAF76DFD6A4C192D71D2DFE8A8 +:1037E00005F020180D3EC8C8C91266C8C9C959C815 +:1037F000C8C8C8BBC9C971718AC89300A1680098BC +:1038000091F8151103E0A168009891F8E610017194 +:10381000B0E0A068D0F81C110098491CFAF795FD9B +:10382000A8E0A1680098D1F8182192790271D1F826 +:10383000182112894271120A8271D1F81821528915 +:10384000C271120A0272D1F8182192894272120AC8 +:103850008272D1F81811C989FAF74EFD8AE0A06882 +:10386000D0F818110098091DFAF77CFDA068D0F86F +:10387000181100980C31FAF77FFDA068D0F81811E4 +:1038800000981E31FAF77EFDA1680098D831FAF74A +:1038900087FD6FE06269009811780171918841712C +:1038A000090A81715188C171090A017262E03649C1 +:1038B000D1E90001CDE9010101A90098FAF78AFDDB +:1038C00058E056E0A068B0F844100098FAF794FD6C +:1038D000A068B0F8E6100098FAF792FDA068B0F87A +:1038E00048100098FAF780FDA068B0F8E81000983A +:1038F000FAF77EFD3EE0A168009891F83021027150 +:1039000091F83111417135E0A06890F81301EDF79D +:1039100032FD01460098FAF7B2FDA06890F8120156 +:1039200000F03DFA70B1A06890F8640000F037FA3A +:1039300040B1A06890F8121190F86400814201D063 +:10394000002002E0A06890F81201EDF714FD014696 +:103950000098FAF790FD0DE0A06890F80D1100981E +:10396000FAF7A8FDA06890F80C110098FAF7A6FDE8 +:1039700000E0FFDFF5F795FD00B9FFDF0098FFF7E6 +:10398000BCFE3EBD8C0100207C63020010B5F94CEA +:10399000A06890F8121109B990F8641080F86410CA +:1039A00090F8131109B990F8651080F8651000209F +:1039B000FEF7A8FEA068FAF750FE002806D0A0681F +:1039C000BDE8104000F59E71FAF7B1BE10BDF8B524 +:1039D000E84E00250446B060B5807570B57035704E +:1039E0000088F5F748FDB0680088F5F76AFDB4F87F +:1039F000F800B168401C82B201F17000EDF75BF88D +:103A000000B1FFDF94F87D00242809D1B4F87010CC +:103A1000B4F81001081A00B2002801DB707830B148 +:103A200094F87C0024280AD0252808D015E0FFF758 +:103A3000ADFF84F87D50B16881F897500DE0B4F87F +:103A40007010B4F81001081A00B2002805DB707875 +:103A500018B9FFF79BFF84F87C50A4F8F850FEF7E4 +:103A600088FD00281CD1B06890F8E400FE2801D041 +:103A7000FFF79AFEC0480090C04BC14A2146284635 +:103A8000F9F7ECF9B0680023052190F87C2070303C +:103A9000EDF778FA002803D0BDE8F840F8F771BFD9 +:103AA000F8BD10B5FEF765FD20B10020BDE810405F +:103AB0000146B4E5BDE81040F9F77FBA70B50C4691 +:103AC000154606464FF4B47200212046EAF7DFFCA3 +:103AD000268005B9FFDF2868C4F818016868C4F8B3 +:103AE0001C01A868C4F8640182E4F0F7E9BA2DE982 +:103AF000F0410D4607460621F0F7D8F9041E3DD0E7 +:103B0000D4F864110026087858B14A8821888A427E +:103B100007D109280FD00E2819D00D2826D0082843 +:103B20003ED094F83A01D0B36E701020287084F81B +:103B30003A61AF809AE06E7009202870D4F8640171 +:103B4000416869608168A9608089A88133E008467E +:103B5000F0F7DDFA0746EFF78AFF70B96E700E20B6 +:103B60002870D4F864014068686011E00846F0F7F6 +:103B7000CEFA0746EFF77BFF08B1002081E46E70B4 +:103B80000D202870D4F8640141686960008928819B +:103B9000D4F8640106703846EFF763FF66E00EE084 +:103BA0006E7008202870D4F86401416869608168EB +:103BB000A960C068E860D4F86401067056E094F823 +:103BC0003C0198B16E70152028700AE084F83C61C1 +:103BD000D4F83E016860D4F84201A860D4F84601E8 +:103BE000E86094F83C010028F0D13FE094F84A01E5 +:103BF00058B16E701C20287084F84A610A2204F5BE +:103C0000A671281DEAF719FC30E094F8560140B17E +:103C10006E701D20287084F85661D4F858016860D1 +:103C200024E094F8340168B16E701A20287004E022 +:103C300084F83461D4F83601686094F834010028BF +:103C4000F6D113E094F85C01002897D06E7016202E +:103C5000287007E084F85C61D4F85E016860B4F80D +:103C60006201288194F85C010028F3D1012008E466 +:103C7000404A5061D170704770B50D4604464EE021 +:103C8000B4F8F800401CA4F8F800B4F89800401C00 +:103C9000A4F89800204600F0F2F9B8B1B4F88E000C +:103CA000401CA4F88E00204600F0D8F9B4F88E002D +:103CB000B4F89010884209D30020A4F88E000120A7 +:103CC00084F83A012B48C078DFF71EF994F8A20077 +:103CD00020B1B4F89E00401CA4F89E0094F8A60001 +:103CE00020B1B4F8A400401CA4F8A40094F8140176 +:103CF00040B994F87F200023012104F17000EDF712 +:103D000041F920B1B4F89C00401CA4F89C00204666 +:103D1000FEF796FFB4F87000401CA4F870006D1E0A +:103D2000ADB2ADD23FE5134AC2E90601704770B5A6 +:103D30000446B0F8980094F88010D1B1B4F89A1005 +:103D40000D1A2D1F94F8960040B194F87C200023A2 +:103D5000092104F17000EDF715F9B8B1B4F88E60DF +:103D6000204600F08CF980B1B4F89000801B001F51 +:103D70000CE007E08C0100201F360200C53602006F +:103D80002D370200C0F10205DCE72846A84200DA20 +:103D90000546002D01DC002005E5A8B203E510F082 +:103DA0000C0000D00120704710B5012808D002286F +:103DB00008D0042808D0082806D0FFDF204610BD10 +:103DC0000124FBE70224F9E70324F7E770B5CC4CA4 +:103DD000A06890F87C001F2804D0607840F00100B3 +:103DE0006070E0E42069FAF73CFBD8B12069012259 +:103DF0000179407901F0070161F30705294600F0D8 +:103E0000070060F30F21A06880F8A2200022A0F82C +:103E10009E20232200F87C2FD0F8B400BDE870402B +:103E2000FEF7AABD0120FEF77CFFBDE870401E2012 +:103E3000FEF768BCF8B5B24C00230A21A06890F8E0 +:103E40007C207030EDF79EF838B32069FAF7E4FA79 +:103E5000C8B12069FAF7DAFA07462069FAF7DAFA00 +:103E600006462069FAF7D0FA05462069FAF7D0FA33 +:103E700001460097A06833462A463030FAF7C1FB66 +:103E8000A068FAF7EAFBA168002081F8A20081F897 +:103E90007C00BDE8F840FEF78FBD607840F001007F +:103EA0006070F8BD964810B580680088F0F72FF96B +:103EB000BDE81040EFF7C6BD10B5914CA36893F86C +:103EC0007C00162802D00220607010BD60780028A7 +:103ED000FBD1D3F81801002200F11E010E30C833C7 +:103EE000ECF7C2FFA0680021C0E92E11012180F883 +:103EF0008110182180F87C1010BD10B5804CA0688E +:103F000090F87C10132902D00220607010BD6178F7 +:103F10000029FBD1D0F8181100884988814200D0CF +:103F2000FFDFA068D0F8181120692631FAF745FAAA +:103F3000A1682069DC31FAF748FAA168162081F8F7 +:103F40007C0010BD10B56E4C207900071BD5607841 +:103F5000002818D1A068002190F8E400FEF72AFE9E +:103F6000A06890F8E400FE2800D1FFDFA068FE21E1 +:103F700080F8E41090F87F10082904D10221217004 +:103F8000002180F87F1010BD70B55D4D2421002404 +:103F9000A86890F87D20212A05D090F87C20232A5B +:103FA00018D0FFDFA0E590F8122112B990F8132184 +:103FB0002AB180F87D10A86880F8A64094E500F842 +:103FC0007D4F847690F8B1000028F4D00020FEF7F1 +:103FD00099FBF0E790F8122112B990F813212AB159 +:103FE00080F87C10A86880F8A2407DE580F87C40CD +:103FF0000020FEF787FBF5E770B5414C0025A0686F +:10400000D0F8181103884A889A4219D109780429EE +:1040100016D190F87C20002319467030ECF7B2FFDF +:1040200000B9FFDFA06890F8AA10890703D4012126 +:1040300080F87C1004E080F8A250D0F818010570D8 +:10404000A0680023194690F87D207030ECF79AFFA5 +:10405000002802D0A06880F8A65045E5B0F890206E +:10406000B0F88E108A4201D3511A00E000218288F4 +:10407000521D8A4202D3012180F89610704710B574 +:1040800090F8821041B990F87C200023062170300E +:10409000ECF778FF002800D0012010BD70B5114466 +:1040A000174D891D8CB2C078A968012806D040B18F +:1040B000182805D191F8120138B109E0A1F8224180 +:1040C00012E5D1F8180184800EE591F8131191B131 +:1040D000FFF765FE80B1A86890F86400FFF75FFE07 +:1040E00050B1A86890F8121190F86420914203D062 +:1040F00090F8130100B90024A868A0F81041F3E477 +:104100008C01002070B58F4C0829207A6CD2DFE832 +:1041100001F004176464276B6B6458B1F4F7D3F8AB +:10412000F5F7FFF80020A072F4F7B4F9BDE870408D +:10413000F4F758BCF5F717F9BDE87040F1F71FBF69 +:10414000DEF7B6FDF5F752F8D4E90001F1F7F3FC1C +:104150002060A07A401CC0B2A072282824D370BD71 +:10416000A07A0025401EC6B2E0683044F4F700FD96 +:1041700010B9E1687F208855A07A272828BF01253B +:10418000DEF796FDA17A01EB4102C2EB81110844F2 +:104190002946F5F755F8A07A282809D2401CC0B264 +:1041A000A072282828BF70BDBDE87040F4F772B92E +:1041B000207A002818BF00F086F8F4F74BFBF4F7DC +:1041C000F7FBF5F7D0F80120E0725F480078DEF7E2 +:1041D0009BFEBDE87040F1F7D2BE002808BF70BD5D +:1041E000BDE8704000F06FB8FFDF70BD10B5554CF2 +:1041F000207A002804BF0C2010BD00202072E0723D +:10420000607AF2F7F8FA607AF2F761FD607AF1F716 +:104210008CFF00280CBF1F20002010BD002270B5AD +:10422000484C06460D46207A68B12272E272607AE6 +:10423000F2F7E1FA607AF2F74AFD607AF1F775FF7A +:10424000002808BFFFDF4048E560067070BD70B50C +:10425000050007D0A5F5E8503C494C3881429CBF89 +:10426000122070BD374CE068002804BF092070BDE3 +:10427000207A00281CBF0C2070BD3548F1F7FAFEEB +:104280006072202804BF1F2070BDF1F76DFF206011 +:104290001DB12946F1F74FFC2060012065602072B6 +:1042A00000F011F8002070BD2649CA7A002A04BF28 +:1042B000002070471E22027000224270CB684360CB +:1042C000CA7201207047F0B585B0F1F74DFF1D4D62 +:1042D0000746394668682C6800EB80004600204697 +:1042E000F2F73AFCB04206DB6868811B3846F1F70A +:1042F00022FC0446286040F2367621463846F2F722 +:104300002BFCB04204DA31463846F1F714FC04467F +:1043100000208DF8000040F6E210039004208DF894 +:10432000050001208DF8040068460294F2F7CAF8EF +:10433000687A6946F2F743F9002808BFFFDF05B045 +:10434000F0BD000074120020AC010020B5EB3C0071 +:10435000054102002DE9F0410C4612490D68114A51 +:10436000114908321160A0F12001312901D3012047 +:104370000CE0412810D040CC0C4F94E80E0007EB25 +:104380008000241F50F8807C3046B84720600548E4 +:10439000001D0560BDE8F081204601F0EBFCF5E76B +:1043A00006207047100502400100000184630200EE +:1043B00010B55548F2F7FAFF00B1FFDF5248401C34 +:1043C000F2F7F4FF002800D0FFDF10BD2DE9F14F18 +:1043D0004E4E82B0D6F800B001274B48F2F7EEFF00 +:1043E000DFF8248120B9002708F10100F2F7FCFF73 +:1043F000474C00254FF0030901206060C4F80051CC +:10440000C4F80451029931602060DFF808A11BE074 +:10441000DAF80000C00617D50E2000F068F8EFF3B8 +:10442000108010F0010072B600D001200090C4F896 +:104430000493D4F8000120B9D4F8040108B901F0BC +:10444000A3FC009800B962B6D4F8000118B9D4F8FA +:1044500004010028DCD0D4F804010028CCD137B105 +:10446000C6F800B008F10100F2F7A8FF11E008F16A +:104470000100F2F7A3FF0028B6D1C4F80893C4F8EE +:104480000451C4F800510E2000F031F81E48F2F734 +:10449000ABFF0020BDE8FE8F2DE9F0438DB00D4647 +:1044A000064600240DF110090DF1200818E000BFA8 +:1044B00004EB4407102255F827106846E9F7BDFFC2 +:1044C00005EB8707102248467968E9F7B6FF68468A +:1044D000FFF77CFF10224146B868E9F7AEFF641C85 +:1044E000B442E5DB0DB00020BDE8F0836EE70028A4 +:1044F00009DB00F01F02012191404009800000F11A +:10450000E020C0F880127047AD01002004E50040B3 +:1045100000E0004010ED00E0B54900200870704751 +:1045200070B5B44D01232B60B34B1C68002CFCD03C +:10453000002407E00E6806601E68002EFCD0001DF7 +:10454000091D641C9442F5D30020286018680028D7 +:10455000FCD070BD70B5A64E0446A84D3078022838 +:1045600000D0FFDFAC4200D3FFDF7169A44801290E +:1045700003D847F23052944201DD03224271491CB4 +:104580007161291BC1609E49707800F02EF90028E6 +:1045900000D1FFDF70BD70B5954C0D466178884243 +:1045A00000D0FFDF954E082D4BD2DFE805F04A041E +:1045B0001E2D4A4A4A382078022800D0FFDF032007 +:1045C0002070A078012801D020B108E0A06801F097 +:1045D00085F904E004F1080007C8FFF7A1FF0520F2 +:1045E0002070BDE87040F1F7CABCF1F7BDFD01468F +:1045F0006068F2F7B1FAB04202D2616902290BD3C6 +:104600000320F2F7FAFD12E0F1F7AEFD0146606813 +:10461000F2F7A2FAB042F3D2BDE870409AE72078F0 +:1046200002280AD0052806D0FFDF04202070BDE84C +:10463000704000F0D0B8022000E00320F2F7DDFD6A +:10464000F3E7FFDF70BD70B50546F1F78DFD684CEF +:1046500060602078012800D0FFDF694901200870E0 +:104660000020087104208D6048716448C8600220F1 +:104670002070607800F0B9F8002800D1FFDF70BD2D +:1046800010B55B4C207838B90220F2F7CCFD18B990 +:104690000320F2F7C8FD08B1112010BD5948F1F709 +:1046A000E9FC6070202804D00120207000206061A7 +:1046B00010BD032010BD2DE9F0471446054600EB60 +:1046C00084000E46A0F1040801F01BF907464FF0E4 +:1046D000805001694F4306EB8401091FB14201D2AA +:1046E000012100E0002189461CB10069B4EB900F64 +:1046F00002D90920BDE8F0872846DCF7A3FD90B970 +:10470000A84510D3BD4205D2B84503D245EA0600FC +:10471000800701D01020EDE73046DCF793FD10B99B +:10472000B9F1000F01D00F20E4E73748374900689E +:10473000884205D0224631462846FFF7F1FE1AE0AE +:10474000FFF79EFF0028D5D1294800218560C0E9E8 +:1047500003648170F2F7D3FD08B12D4801E04AF2FD +:10476000F87060434FF47A7100F2E730B0FBF1F07B +:104770001830FFF768FF0020BCE770B505464FF022 +:10478000805004696C432046DCF75CFD08B10F20C3 +:1047900070BD01F0B6F8A84201D8102070BD1A48CB +:1047A0001A490068884203D0204601F097F810E0CB +:1047B000FFF766FF0028F1D10D4801218460817068 +:1047C000F2F79DFD08B1134800E013481830FFF7D9 +:1047D0003AFF002070BD10B5054C6078F1F7A5FCDC +:1047E00000B9FFDF0020207010BDF1F7E8BE000027 +:1047F000B001002004E5014000E40140105C0C0021 +:1048000084120020974502005C000020BEBAFECA58 +:1048100050280500645E0100A85B01007E4909681C +:104820000160002070477C4908600020704701212A +:104830008A0720B1012804D042F204007047916732 +:1048400000E0D1670020704774490120086042F2FF +:104850000600704708B50423704A1907103230B1BA +:10486000C1F80433106840F0010010600BE01068DC +:1048700020F001001060C1F808330020C1F80801E1 +:10488000674800680090002008BD011F0B2909D867 +:10489000624910310A6822F01E0242EA40000860B4 +:1048A0000020704742F2050070470F2809D85B4985 +:1048B00010310A6822F4706242EA00200860002089 +:1048C000704742F205007047000100F18040C0F8D7 +:1048D000041900207047000100F18040C0F8081959 +:1048E00000207047000100F18040D0F80009086006 +:1048F00000207047012801D907207047494A52F823 +:10490000200002680A43026000207047012801D994 +:1049100007207047434A52F8200002688A43026029 +:1049200000207047012801D9072070473D4A52F8FE +:104930002000006808600020704702003A494FF0EC +:10494000000003D0012A01D0072070470A60704799 +:10495000020036494FF0000003D0012A01D00720A1 +:1049600070470A60704708B54FF40072510510B1E6 +:10497000C1F8042308E0C1F808230020C1F824018D +:1049800027481C3000680090002008BD08B5802230 +:10499000D10510B1C1F8042308E0C1F808230020B4 +:1049A000C1F81C011E48143000680090002008BDAA +:1049B00008B54FF48072910510B1C1F8042308E0E6 +:1049C000C1F808230020C1F82001154818300068FC +:1049D0000090002008BD10493831096801600020AE +:1049E000704770B54FF080450024C5F80841F2F7D4 +:1049F00092FC10B9F2F799FC28B1C5F82441C5F82A +:104A00001C41C5F820414FF0E020802180F80014BF +:104A10000121C0F8001170BD0004004000050040F5 +:104A2000080100404864020078050040800500400D +:104A30006249634B0A6863499A42096801D1C1F32C +:104A400010010160002070475C495D4B0A685D49B8 +:104A5000091D9A4201D1C0F3100008600020704780 +:104A60005649574B0A68574908319A4201D1C0F359 +:104A7000100008600020704730B5504B504D1C6846 +:104A800042F20803AC4202D0142802D203E01128FB +:104A900001D3184630BDC3004B481844C0F8101568 +:104AA000C0F81425002030BD4449454B0A6842F245 +:104AB00009019A4202D0062802D203E0042801D359 +:104AC00008467047404A012142F8301000207047E4 +:104AD0003A493B4B0A6842F209019A4202D0062841 +:104AE00002D203E0042801D308467047364A012168 +:104AF00002EBC00041600020704770B52F4A304E75 +:104B0000314C156842F2090304EB8002B54204D02F +:104B1000062804D2C2F8001807E0042801D318467A +:104B200070BDC1F31000C2F80008002070BD70B560 +:104B3000224A234E244C156842F2090304EB8002FA +:104B4000B54204D0062804D2D2F8000807E00428B1 +:104B500001D3184670BDD2F80008C0F310000860F9 +:104B6000002070BD174910B50831184808601120A1 +:104B7000154A002102EBC003C3F81015C3F8141541 +:104B8000401C1428F6D3002006E0042804D302EBCE +:104B90008003C3F8001807E002EB8003D3F8004855 +:104BA000C4F31004C3F80048401C0628EDD310BD20 +:104BB0000449064808310860704700005C00002086 +:104BC000BEBAFECA00F5014000F001400000FEFF41 +:104BD000814B1B6803B19847BFF34F8F7F48016833 +:104BE0007F4A01F4E06111430160BFF34F8F00BFC2 +:104BF000FDE710B5EFF3108010F0010F72B601D091 +:104C0000012400E0002400F0DDF850B1DCF7BFFB28 +:104C1000F1F755F8F2F793FAF2F7BEFF7149002069 +:104C2000086004B962B6002010BD2DE9F0410C46C1 +:104C30000546EFF3108010F0010F72B601D0012687 +:104C400000E0002600F0BEF820B106B962B60820E8 +:104C5000BDE8F08101F01EF9DCF79DFB0246002063 +:104C600001234709BF0007F1E02700F01F01D7F833 +:104C70000071CF40F9071BD0202803D222FA00F19F +:104C8000C90727D141B2002904DB01F1E02191F8E5 +:104C9000001405E001F00F0101F1E02191F8141D6D +:104CA0004909082916D203FA01F717F0EC0F11D0C1 +:104CB000401C6428D5D3F2F74DFF4B4A4B490020E6 +:104CC000F2F790FF47494A4808602046DCF7C1FAEE +:104CD00060B904E006B962B641F20100B8E73E48A7 +:104CE00004602DB12846DCF701FB18B1102428E040 +:104CF000404D19E02878022802D94FF4805420E072 +:104D000007240028687801D0D8B908E0C8B1202865 +:104D100017D8A878212814D8012812D001E0A87843 +:104D200078B9E8780B280CD8DCF735FB2946F2F780 +:104D3000ECF9F0F783FF00F017FE2846DCF7F4FAF1 +:104D4000044606B962B61CB1FFF753FF20467FE761 +:104D500000207DE710B5044600F034F800B10120D2 +:104D60002070002010BD244908600020704770B5F5 +:104D70000C4622490D682149214E08310E60102849 +:104D800007D011280CD012280FD0132811D00120E1 +:104D900013E0D4E90001FFF748FF354620600DE03D +:104DA000FFF727FF0025206008E02068FFF7D2FF0B +:104DB00003E0114920680860002020600F48001DB2 +:104DC000056070BD07480A490068884201D101208A +:104DD0007047002070470000C80100200CED00E083 +:104DE0000400FA055C0000204814002000000020A8 +:104DF000BEBAFECA50640200040000201005024042 +:104E0000010000017D49C0B20860704700B57C49CF +:104E1000012808BF03200CD0022808BF042008D0B6 +:104E2000042808BF062004D0082816BFFFDF05208D +:104E300000BD086000BD70B505460C46164610461C +:104E4000F3F7D2F8022C08BF4FF47A7105D0012C89 +:104E50000CBF4FF4C86140F6340144183046F3F7F4 +:104E60003CF9204449F6797108444FF47A71B0FB5B +:104E7000F1F0281A70BD70B505460C460846F4F7E7 +:104E80002FFA022C08BF40F24C4105D0012C0CBF78 +:104E900040F634014FF4AF5149F6CA62511A084442 +:104EA0004FF47A7100F2E140B0FBF1F0281A801E55 +:104EB00070BD70B5064615460C460846F4F710FA64 +:104EC000022D08BF4FF47A7105D0012D0CBF4FF4AD +:104ED000C86140F63401022C08BF40F24C4205D0B4 +:104EE000012C0CBF40F634024FF4AF52891A08442B +:104EF00049F6FC6108444FF47A71B0FBF1F0301AC6 +:104F000070BD70B504460E460846F3F76DF80546C9 +:104F10003046F3F7E2F828444AF2AB3108444FF444 +:104F20007A71B0FBF1F0201A801E70BD2DE9F041BE +:104F300007461E460D4614461046082A16BF04288A +:104F40004EF62830F3F750F807EB4701C1EBC711D5 +:104F500000EBC100022D08BF40F24C4105D0012DED +:104F60000CBF40F634014FF4AF5147182846F4F710 +:104F7000B7F9381A4FF47A7100F6B730B0FBF1F593 +:104F80002046F3F7B9F828443044401DBDE8F081CD +:104F900070B5054614460E460846F3F725F805EBAE +:104FA0004502C2EBC512C0EBC2053046F3F795F8D7 +:104FB0002D1A2046082C16BF04284EF62830F3F789 +:104FC00013F828444FF47A7100F6B730B0FBF1F5CE +:104FD0002046F3F791F82844401D70BD0949082880 +:104FE00018BF0428086803BF20F46C5040F4444004 +:104FF00040F0004020F00040086070470C15004071 +:105000001015004040170040F0B585B00C4605462D +:10501000F9F73EF907466E78204603A96A46EEF78F +:1050200002FD81198EB258B1012F02D0032005B0C4 +:10503000F0BD204604AA0399EEF717FC049D01E099 +:10504000022F0FD1ED1C042E0FD32888BDF80010BD +:10505000001D80B2884201D8864202D14FF0000084 +:10506000E5E702D34FF00200E1E74FF00100DEE791 +:10507000FA48C078FF2814BF0120002070472DE9AE +:10508000F041F74C0746160060680D4603D0F9F76B +:1050900069F8A0B121E0F9F765F8D8B96068F9F7C7 +:1050A00061F8D0B915F00C0F17D06068C17811F015 +:1050B0003F0F1CBF007910F0100F0ED00AE0022E37 +:1050C00008D0E6481FB1807DFF2806D002E0C078F6 +:1050D000FF2802D00120BDE8F0810020BDE8F0816A +:1050E0000A4601460120CAE710B5DC4C1D2200210A +:1050F000A01CE9F7CCF97F206077FF202074E070D6 +:10510000A075A08920F060002030A08100202070D0 +:1051100010BD70B5D249486001200870D248D1490D +:10512000002541600570CD4C1D222946A01CE9F7E1 +:10513000AEF97F206077FF202074E070A075A08911 +:1051400020F060002030A081257070BD2DE9F0476F +:10515000C24C06462078C24F4FF0010907F10808FB +:10516000002520B13878D0B998F80000B8B198F887 +:10517000000068B387F80090D8F804103C2239B3D7 +:105180007570301DE9F759F90520307086F80490E4 +:105190003878002818BF88F8005005D015E03D7019 +:1051A000A11C4FF48E72EAE71D220021A01CE9F732 +:1051B0006EF97F206077FF202074E070A075A089D1 +:1051C00020F060002030A08125700120BDE8F0872C +:1051D0000020BDE8F087A148007800280CBF01201E +:1051E000002070470A460146002048E710B510B17C +:1051F000022810D014E09A4C6068F8F7B3FF78B931 +:105200006068C17811F03F0F1CBF007910F0100FDB +:1052100006D1012010BD9148007B10F0080FF8D195 +:10522000002010BD2DE9FF4F81B08C4D8346DDE994 +:105230000F042978DDF838A09846164600291CBFCF +:1052400005B0BDE8F08F8849097800291CBF05B07A +:10525000BDE8F08FE872B4B1012E08BF012708D075 +:10526000022E08BF022704D0042E16BF082E0327E3 +:10527000FFDFEF7385F81E804FF00008784F8CB188 +:10528000022C1DD020E0012E08BF012708D0022EDD +:1052900008BF022704D0042E16BF082E0327FFDF05 +:1052A000AF73E7E77868F8F75DFF68B97868C178A9 +:1052B00011F03F0F1CBF007910F0100F04D110E067 +:1052C000287B10F0080F0CD14FF003017868F8F735 +:1052D000FDFD30B14178090929740088C0F30B0045 +:1052E0006882CDF800807868F8F73CFF0146012815 +:1052F000BDF8000005F102090CBF40F0010020F0EC +:105300000100ADF8000099F80A2012F0020F4ED10A +:10531000022918BF20F0020049D000BFADF80000FC +:1053200010F0020F04D0002908BF40F0080801D097 +:1053300020F00808ADF800807868C17811F03F0FC0 +:105340001CBF007910F0020F0CD0314622464FF0FE +:105350000100FFF794FE002804BF48F00400ADF8F8 +:10536000000006D099F80A00800860F38208ADF8C2 +:10537000008099F80A004109BDF8000061F3461069 +:10538000ADF8000080B20090BDF80000A8810421B3 +:105390007868F8F79BFD002804BFA88920F060001A +:1053A0000CD0B0F80100C004C00C03D007E040F0FE +:1053B0000200B3E7A88920F060004030A8815CB902 +:1053C00016F00C0F08D07868C17811F03F0F1CBFA1 +:1053D000007910F0100F0DD17868C17811F03F0FEF +:1053E00008D0017911F0400F04D00621F8F76EFDC6 +:1053F00000786877314622460020FFF740FE60BB08 +:105400007968C87810F03F0F3FD0087910F0010F8D +:105410003BD0504605F1040905F10308BAF1FF0F2E +:105420000DD04A464146F8F781FA002808BFFFDF51 +:1054300098F8000040F0020088F8000025E00846D7 +:10544000F8F7DBFC88F800007868F8F7ADFC07286F +:105450000CD249467868F8F7B2FC16E094120020A6 +:10546000CC010020D2120020D40100207868F8F787 +:105470009BFC072809D100217868F8F727FD01680F +:10548000C9F800108088A9F804003146224601209E +:10549000FFF7F5FD80BB7868C17811F03F0F2BD086 +:1054A000017911F0020F27D005F1170605F1160852 +:1054B000BBF1020F18BFBBF1030F08D0F8F774FC63 +:1054C00007280AD231467868F8F787FC12E002987C +:1054D000016831608088B0800CE07868F8F764FC7F +:1054E000072807D101217868F8F7F0FC01683160DE +:1054F0008088B08088F800B0002C04BF05B0BDE8FB +:10550000F08F7868F8F72EFE022804BF05B0BDE8DA +:10551000F08F05F11F047868F8F76DFEAB7AC3F1E0 +:10552000FF01884228BF084605D9A98921F06001FA +:1055300001F14001A981C2B203EB04017868F8F7D8 +:1055400062FEA97A0844A87205B0BDE8F08FB048A1 +:105550000178002918BF704701220270007B10F00B +:10556000080F14BF07200620FCF75FBEA848C17BC8 +:10557000002908BF70470122818921F06001403174 +:1055800081810378002B18BF7047027011F0080F5B +:1055900014BF07200620FCF748BE2DE9FF5F9C4F93 +:1055A000DDF838B0914638780E4600281CBF04B0AC +:1055B000BDE8F09FBC1C1D2200212046E8F767FFD4 +:1055C000944D4FF0010A84F800A06868F8F7ECFBEE +:1055D00018B3012826D0022829D0062818BFFFDFDB +:1055E0002AD000BF04F11D016868F8F726FC20727C +:1055F000484604F1020904F10108FF2821D04A4677 +:105600004146F8F793F9002808BFFFDF98F800003B +:1056100040F0020088F8000031E0608940F013009B +:105620006081DFE7608940F015006081E0E7608914 +:1056300040F010006081D5E7608940F01200608181 +:10564000D0E76868F8F7D9FB88F800006868F8F7D1 +:10565000ABFB072804D249466868F8F7B0FB0EE0B8 +:105660006868F8F7A1FB072809D100216868F8F7F6 +:105670002DFC0168C9F800108088A9F8040084F89E +:1056800009B084F80CA000206073FF20A073A17AF9 +:1056900011F0040F08BF20752AD004F1150804F199 +:1056A0001409022E18BF032E09D06868F8F77CFB96 +:1056B00007280CD241466868F8F78FFB16E000987F +:1056C0000168C8F800108088A8F804000EE0686837 +:1056D000F8F76AFB072809D101216868F8F7F6FB9B +:1056E0000168C8F800108088A8F8040089F80060F4 +:1056F0007F20E0760398207787F800A004B006208A +:10570000BDE8F05FFCF791BD2DE9FF5F424F814698 +:105710009A4638788B4600281CBF04B0BDE8F09F3D +:105720003B48017831B1007B10F0100F04BF04B08A +:10573000BDE8F09F1D227C6800212046E8F7A7FE07 +:1057400048464FF00108661C324D84F8008004F191 +:105750000209FF280BD04A463146F8F7E7F800283F +:1057600008BFFFDF307840F0020030701CE068684E +:10577000F8F743FB30706868F8F716FB072804D287 +:1057800049466868F8F71BFB0EE06868F8F70CFB01 +:10579000072809D100216868F8F798FB0168C9F863 +:1057A00000108088A9F8040004F11D016868F8F76A +:1057B00044FB207284F809A060896BF3000040F07C +:1057C0001A00608184F80C8000206073FF20A073B1 +:1057D00020757F20E0760298207787F8008004B05B +:1057E0000720BDE8F05FFCF720BD094A137C834227 +:1057F00005BF508A88420020012070470448007B82 +:10580000C0F3411002280CBF0120002070470000A7 +:1058100094120020CC010020D4010020C2790D2375 +:1058200041B342BB8188012904D94908818004BF62 +:10583000012282800168012918BF002930D0016847 +:105840006FEA0101C1EBC10202EB011281796FEA3B +:10585000010101EB8103C3EB811111444FEA914235 +:1058600001608188B2FBF1F301FB132181714FF0DC +:10587000010102E01AB14FF00001C1717047818847 +:10588000FF2908D24FF6FF7202EA41018180FF2909 +:1058900084BFFF2282800168012918BF0029CED170 +:1058A0000360CCE7817931B1491E11F0FF018171AC +:1058B0001CBF002070470120704710B50121C17145 +:1058C0008171818004460421F1F7E8FD002818BFAA +:1058D00010BD2068401C206010BD00000B4A022152 +:1058E00011600B490B68002BFCD0084B1B1D186086 +:1058F00008680028FCD00020106008680028FCD050 +:1059000070474FF0805040697047000004E5014047 +:1059100000E4014002000B464FF00000014620D099 +:10592000012A04D0022A04D0032A0DD103E0012069 +:1059300002E0022015E00320072B05D2DFE803F088 +:105940000406080A0C0E100007207047012108E029 +:10595000022106E0032104E0042102E0052100E029 +:105960000621F0F7A4BB0000E24805218170002168 +:10597000017041707047E0490A78012A05D0CA6871 +:105980001044C8604038F1F7B4B88A6810448860A1 +:10599000F8E7002819D00378D849D94A13B1012B68 +:1059A0000ED011E00379012B00D06BB943790BB114 +:1059B000012B09D18368643B8B4205D2C0680EE09D +:1059C0000379012B02D00BB10020704743790BB152 +:1059D000012BF9D1C368643B8B42F5D280689042B9 +:1059E000F2D801207047C44901220A70027972B1CD +:1059F00000220A71427962B104224A7182685232ED +:105A00008A60C068C860BB49022088707047032262 +:105A1000EFE70322F1E770B5B74D04460020287088 +:105A2000207988B100202871607978B10420B14EC6 +:105A30006871A168F068F0F77EF8A860E0685230FD +:105A4000E8600320B07070BD0120ECE70320EEE7B2 +:105A50002DE9F04105460226F0F777FF006800B116 +:105A6000FFDFA44C01273DB12878B8B1012805D04B +:105A7000022811D0032814D027710DE06868C828C7 +:105A800008D30421F1F79BF820B16868FFF773FF92 +:105A9000012603E0002601E000F014F93046BDE8DD +:105AA000F08120780028F7D16868FFF772FF00289E +:105AB000E2D06868017879B1A078042800D0FFDFCF +:105AC00001216868FFF7A7FF8B49E07800F003F930 +:105AD0000028E1D1FFDFDFE7FFF785FF6770DBE735 +:105AE0002DE9F041834C0F46E178884200D0FFDF7A +:105AF00000250126082F7DD2DFE807F0040B2828B7 +:105B00003D434F57A0780328C9D00228C7D0FFDFF4 +:105B1000C5E7A078032802D0022800D0FFDF0420C8 +:105B2000A07025712078B8BB0020FFF724FF7248D1 +:105B30000178012906D08068E06000F0EDF820616E +:105B4000002023E0E078F0F734FCF5E7A0780328A4 +:105B500002D0022800D0FFDF207880BB022F08D0BF +:105B60005FF00500F1F749FBA078032840D0A5704D +:105B700095E70420F6E7A078042800D0FFDF022094 +:105B800004E0A078042800D0FFDF0120A168884746 +:105B9000FFF75EFF054633E003E0A078042800D05D +:105BA000FFDFBDE8F04100F08DB8A078042804D0F4 +:105BB000617809B1022800D0FFDF207818B1BDE874 +:105BC000F04100F08AB8207920B10620F1F715FBEA +:105BD00025710DE0607840B14749E07800F07BF82E +:105BE00000B9FFDF65705AE704E00720F1F705FB15 +:105BF000A67054E7FFDF52E73DB1012D03D0FFDF70 +:105C0000022DF9D14BE70420C0E70320BEE770B5B1 +:105C1000050004D0374CA078052806D101E01020FB +:105C200070BD0820F1F7FFFA08B1112070BD3548AA +:105C3000F0F720FAE070202806D00121F1F7DCF817 +:105C40000020A560A07070BD032070BD294810B56C +:105C5000017809B1112010BD8178052906D00129EC +:105C600006D029B101210170002010BD0F2010BD08 +:105C700000F033F8F8E770B51E4C0546A07808B17F +:105C8000012809D155B12846FFF783FE40B1287895 +:105C900040B1A078012809D00F2070BD102070BD40 +:105CA000072070BD2846FFF79EFE03E0002128462E +:105CB000FFF7B1FE1049E07800F00DF800B9FFDF02 +:105CC000002070BD0B4810B5006900F01DF8BDE85C +:105CD0001040F0F754B9F0F772BC064810B5C07820 +:105CE000F0F723FA00B9FFDF0820F1F786FABDE8E4 +:105CF000104039E6DC010020B41300203D8601008D +:105D0000FF1FA107E15A02000C490A6848F202137A +:105D10009A4302430A607047084A116848F2021326 +:105D200001EA03009943116070470246044B1020BA +:105D30001344FC2B01D8116000207047C8060240B4 +:105D40000018FEBF1EF0040F0CBFEFF30880EFF346 +:105D50000980014A10470000FF7B010001B41EB416 +:105D600000B5F1F76DFC01B40198864601BC01B0A5 +:105D70001EBD00008269034981614FF0010010449B +:105D8000704700005D5D02000FF20C0000F10000A2 +:105D9000694641F8080C20BF70470000FEDF184933 +:105DA0000978F9B90420714608421BD10699154AB1 +:105DB000914217DC0699022914DB02394878DF2862 +:105DC00010D10878FE2807D0FF280BD14FF0010032 +:105DD0004FF000020C4B184741F201000099019A64 +:105DE000094B1847094B002B02D01B68DB6818478A +:105DF0004FF0FF3071464FF00002034B1847000090 +:105E000028ED00E000700200D14B020004000020E9 +:105E1000174818497047FFF7FBFFDBF7CFF900BDC4 +:105E2000154816490968884203D1154A13605B6812 +:105E3000184700BD20BFFDE70F4810490968884298 +:105E400010D1104B18684FF0FF318842F2D080F328 +:105E500008884FF02021884204DD0B4802680321A6 +:105E60000A4302600948804709488047FFDF000075 +:105E7000C8130020C81300200010000000000020FC +:105E8000040000200070020014090040B92F000037 +:105E9000215E0200F0B44046494652465B460FB4CC +:105EA00002A0013001B50648004700BF01BC86468C +:105EB0000FBC8046894692469B46F0BC7047000066 +:105EC0000911000004207146084202D0EFF3098155 +:105ED00001E0EFF30881886902380078102813DBAD +:105EE00020280FDB2C280BDB0A4A12680A4B9A4247 +:105EF00003D1602804DB094A10470220086070477C +:105F0000074A1047074A1047074A12682C3212689E +:105F1000104700005C000020BEBAFECA9B130000C0 +:105F2000554302006F4D0200040000200D4B0E4946 +:105F300008470E4B0C4908470D4B0B4908470D4BC2 +:105F4000094908470C4B084908470C4B06490847C4 +:105F50000B4B054908470B4B034908470A4B0249BD +:105F60000847000041BF000079C10000792D000002 +:105F7000F32B0000812B0000012E0000B71300005E +:105F80003F2900007D2F0000455D020000210160D7 +:105F90004160017270470A6802600B7903717047B3 +:105FA00089970000FF9800005B9A0000C59A0000E6 +:105FB000FF9A0000339B0000659B00009D9B000042 +:105FC0003D9C00007D980000859A0000331200007F +:105FD0000744000053440000B94400004745000056 +:105FE0006146000037470000694700004148000053 +:105FF000DB4800002F490000154A0000354A000028 +:10600000AD160000D1160000F11500004D1600007D +:10601000031700009717000003610000C36200002F +:10602000A1660000BB67000043680000C168000073 +:10603000256900004D6A00001D6B0000896B00009F +:10604000574A00005D4A0000674A0000CF4A00003E +:10605000FB4A0000B74C0000E14C0000194D000065 +:10606000834D00006D4E0000834E00007744000019 +:10607000974E0000B94E0000FF4E000033120000A2 +:10608000331200003312000033120000C12500005B +:1060900047260000632600007F2600000D28000030 +:1060A000A9260000B3260000F526000017270000EF +:1060B000F3270000352800003312000033120000DF +:1060C00097840000B7840000B9840000FD840000BC +:1060D0002B8500001B860000A7860000BB86000001 +:1060E000098700001F880000C1890000E98A0000BC +:1060F0003D740000018B00003312000033120000D9 +:10610000EBB700004DB90000A7B9000021BA0000AC +:10611000CDBA0000010000000000000010011001D5 +:106120003A0200001A020000020004050600000006 +:1061300007111102FFFFFFFF0000FFFFF3B3000094 +:10614000273D0000532100008774000001900000EB +:1061500000000000BF9200009B920000AD92000082 +:10616000000002000000000000020000000000002B +:1061700000010000000000004382000023820000B4 +:10618000918200002D250000EF2400000F25000063 +:10619000DBAA000007AB00000FAD0000FD590000B6 +:1061A000B182000000000000E18200007B250000B9 +:1061B000000000000000000000000000F1AB000043 +:1061C00000000000915A00000300000001555555E1 +:1061D000D6BE898E00006606660C661200000A03B1 +:1061E000AE055208000056044608360CC7FD0000F4 +:1061F0005BFF0000A1FB0000C3FD0000A7A8010099 +:106200009B040100AAAED7AB15412010000000008E +:10621000900A0000900A00007B5700007B570000A6 +:10622000E143000053B200000B7700006320000040 +:10623000BD3A020063BD0100BD570000BD5700001C +:1062400005440000E5B2000093770000D72000006D +:10625000EB3A020079BD0100700170014000380086 +:106260005C0024006801200200000300656C746279 +:10627000000000000000000000000000000000001E +:106280008700000000000000000000000000000087 +:10629000BE83605ADB0B376038A5F5AA9183886C02 +:1062A000010000007746010049550100000000018F +:1062B0000206030405000000070000FB349B5F801A +:1062C000000080001000000000000000000000003E +:1062D000060000000A000000320000007300000009 +:1062E000B4000000F401FA00960064004B00320094 +:1062F0001E0014000A000500020001000049000011 +:1063000000000000D7CF0100E9D1010025D1010034 +:10631000EBCF0100000000008FD40100000101025A +:10632000010202030C0802170D0101020909010113 +:1063300006020918180301010909030305000000FA +:10634000555555252627D6BE898E00002BFB01000A +:1063500003F7010049FA01003FF20100BB220200ED +:10636000B7FB0100F401FA00960064004B00320014 +:106370001E0014000A00050002000100254900006B +:1063800000000000314A0200494A0200614A02004E +:10639000794A0200A94A0200D14A0200FB4A0200DF +:1063A0002F4B02007B470200B7460200A1430200C8 +:1063B0002B5D0200AD730100BD730100E9730100A4 +:1063C000BB740100C3740100D57401002F480200A2 +:1063D000494802001D4802002748020055480200B3 +:1063E0008B480200AB480200C9480200D7480200AF +:1063F000E5480200F54802000D4902002549020067 +:106400003B4902005149020000000000DFBC0000CF +:1064100035BD00004BBD000015590200CD43020000 +:10642000994402000F5C02004D5C0200775C0200A0 +:106430009D710100FD760100674902008D4902004F +:10644000B1490200D74902001C0500402005004068 +:10645000001002007464020008000020E80100003F +:106460004411000098640200F0010020D8110000DF +:10647000A011000001181348140244200B440C061C +:106480004813770B1B2034041ABA0401A40213101A +:08649000327F0B744411C000BF +:00000001FF From 2ba88d305f123e5fa5f6ca1ad72937c6497882e4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 4 Jul 2024 08:29:49 -0500 Subject: [PATCH 0667/1377] Only sdk --- bin/build-nrf52.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 853adb488..a09b73cb8 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -31,7 +31,7 @@ echo "Generating NRF52 uf2 file" SRCHEX=.pio/build/$1/firmware.hex # if WM1110 target, merge hex with softdevice 7.3.0 -if (echo $1 | grep -q "wm1110"); then +if (echo $1 | grep -q "wio-sdk-wm1110"); then echo "Merging with softdevice" bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex SRCHEX=.pio/build/$1/merged_fimware.hex From c1df621711a800748d3bc3916910154aaff442d0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 4 Jul 2024 08:32:59 -0500 Subject: [PATCH 0668/1377] Sudo --- bin/build-nrf52.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index a09b73cb8..29b483520 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -33,6 +33,7 @@ SRCHEX=.pio/build/$1/firmware.hex # if WM1110 target, merge hex with softdevice 7.3.0 if (echo $1 | grep -q "wio-sdk-wm1110"); then echo "Merging with softdevice" + sudo chmod+x ./bin/mergehex bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex SRCHEX=.pio/build/$1/merged_fimware.hex fi From ae420dcd21dd0e34bf743169d3d6a69daa60ff70 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:58:16 +0200 Subject: [PATCH 0669/1377] Fix exclude macros (#4233) * fix MESHTASTIC_EXCLUDE_BLUETOOTH * fix HAS_SCREEN=0 * fix MESHTASTIC_EXCLUDE_GPS --- src/RedirectablePrint.cpp | 6 ++++++ src/graphics/Screen.h | 4 ++++ src/mesh/MeshService.cpp | 2 +- src/modules/AdminModule.cpp | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 1851ffbaa..01e5a34a7 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -173,6 +173,7 @@ void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) { +#if !MESHTASTIC_EXCLUDE_BLUETOOTH if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 @@ -211,6 +212,11 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ delete[] buffer; } } +#else + (void)logLevel; + (void)format; + (void)arg; +#endif } meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index e80581d6d..83c9a7a94 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -22,6 +22,10 @@ class Screen void doDeepSleep() {} void forceDisplay(bool forceUiUpdate = false) {} void startFirmwareUpdateScreen() {} + void increaseBrightness() {} + void decreaseBrightness() {} + void setFunctionSymbal(std::string) {} + void removeFunctionSymbal(std::string) {} void startAlert(const char *) {} void endAlert() {} }; diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index c92b89eb4..e5f33e8e7 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -269,7 +269,7 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies) assert(node); if (hasValidPosition(node)) { -#if HAS_GPS +#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS if (positionModule) { LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); positionModule->sendOurPosition(dest, wantReplies, node->channel); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 11821a0a3..e24c62712 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -232,9 +232,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) gps->enable(); -#endif // Send our new fixed position to the mesh for good measure positionModule->sendOurPosition(); +#endif } break; } From 2df8093fef22034d99df783e5ede1a1ca660a52d Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 5 Jul 2024 22:02:55 +0800 Subject: [PATCH 0670/1377] update SD_FLASH_SIZE to 0x27000 (#4232) The 7.3.0 softdevice needs the extra 1000 :) --- src/platform/nrf52/softdevice/nrf_sdm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/nrf52/softdevice/nrf_sdm.h b/src/platform/nrf52/softdevice/nrf_sdm.h index 2786a86a4..33b6cc342 100644 --- a/src/platform/nrf52/softdevice/nrf_sdm.h +++ b/src/platform/nrf52/softdevice/nrf_sdm.h @@ -141,7 +141,7 @@ the start of the SoftDevice (without MBR)*/ * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed * just above the MBR (the usual case). */ -#define SD_FLASH_SIZE 0x26000 +#define SD_FLASH_SIZE 0x27000 /** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual From 8be378c227841cd138f6f3dc3add2ef5f8e51e61 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 5 Jul 2024 22:03:45 +0800 Subject: [PATCH 0671/1377] fix typo in build-nrf52.sh (#4231) chmod is the command, '+x' is the argument. --- bin/build-nrf52.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 29b483520..fa6eacd23 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -33,7 +33,7 @@ SRCHEX=.pio/build/$1/firmware.hex # if WM1110 target, merge hex with softdevice 7.3.0 if (echo $1 | grep -q "wio-sdk-wm1110"); then echo "Merging with softdevice" - sudo chmod+x ./bin/mergehex + sudo chmod +x ./bin/mergehex bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex SRCHEX=.pio/build/$1/merged_fimware.hex fi From c3d3dfa8c8ea103c4a82eb8d44be5e2a4bc22729 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sun, 7 Jul 2024 05:41:29 +1200 Subject: [PATCH 0672/1377] Tidy Wireless Paper variant files (#4238) * Quick tidy of pins_arduino.h Matches requests made at https://github.com/meshtastic/firmware/pull/4226#discussion_r1664183480) * Tidy variant.h * Change deprecated ADC attenuation parameter From 11dB to 12dB. Resolves compiler warning. Allegly, no impact on function: `This is deprecated, it behaves the same as `ADC_ATTEN_DB_12` --- variants/heltec_wireless_paper/pins_arduino.h | 8 ++---- variants/heltec_wireless_paper/variant.h | 28 ++++++++----------- .../heltec_wireless_paper_v1/pins_arduino.h | 8 ++---- variants/heltec_wireless_paper_v1/variant.h | 28 ++++++++----------- 4 files changed, 26 insertions(+), 46 deletions(-) diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h index 9e1d8a9a0..2bb44161a 100644 --- a/variants/heltec_wireless_paper/pins_arduino.h +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -3,11 +3,7 @@ #include -#define WIFI_Kit_32 true -#define DISPLAY_HEIGHT 64 -#define DISPLAY_WIDTH 128 - -static const uint8_t LED_BUILTIN = 35; +static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN @@ -65,6 +61,6 @@ static const uint8_t LED = 18; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; +static const uint8_t DIO1 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 29b8bbbd1..c41d6d9df 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -1,14 +1,12 @@ #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals +// I2C #define I2C_SDA SDA #define I2C_SCL SCL +// Display (E-Ink) #define USE_EINK - -/* - * eink display pins - */ #define PIN_EINK_CS 4 #define PIN_EINK_BUSY 7 #define PIN_EINK_DC 5 @@ -16,32 +14,28 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 45 // Active low, powers the E-Ink display #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 -#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 diff --git a/variants/heltec_wireless_paper_v1/pins_arduino.h b/variants/heltec_wireless_paper_v1/pins_arduino.h index 9e1d8a9a0..2bb44161a 100644 --- a/variants/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/heltec_wireless_paper_v1/pins_arduino.h @@ -3,11 +3,7 @@ #include -#define WIFI_Kit_32 true -#define DISPLAY_HEIGHT 64 -#define DISPLAY_WIDTH 128 - -static const uint8_t LED_BUILTIN = 35; +static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN @@ -65,6 +61,6 @@ static const uint8_t LED = 18; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; +static const uint8_t DIO1 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 29b8bbbd1..c41d6d9df 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -1,14 +1,12 @@ #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals +// I2C #define I2C_SDA SDA #define I2C_SCL SCL +// Display (E-Ink) #define USE_EINK - -/* - * eink display pins - */ #define PIN_EINK_CS 4 #define PIN_EINK_BUSY 7 #define PIN_EINK_DC 5 @@ -16,32 +14,28 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 45 // Active low, powers the E-Ink display #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 -#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 From 7b838d388d7df7c10553639d7a4507182367b0c1 Mon Sep 17 00:00:00 2001 From: "Agent Blu, 006" Date: Sat, 6 Jul 2024 17:45:58 -0700 Subject: [PATCH 0673/1377] Updated raspbian CI to update apt repository ahead of libbluetooth. (#4243) --- .github/workflows/build_raspbian.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 697d08727..d262d8739 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -13,6 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | + apt-get update -y apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code From 27dfe10689aad1b70c533d0cefb999d10b38d9ed Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 7 Jul 2024 04:50:47 -0700 Subject: [PATCH 0674/1377] Fix BLE logging on nrf52 (#4244) * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. --------- Co-authored-by: Ben Meadors --- src/RedirectablePrint.cpp | 2 +- src/platform/nrf52/NRF52Bluetooth.cpp | 22 +++++++++++++++------- src/platform/nrf52/NRF52Bluetooth.h | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 01e5a34a7..9c3dcdc98 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -206,7 +206,7 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ #ifdef ARCH_ESP32 nimbleBluetooth->sendLog(buffer, size); #elif defined(ARCH_NRF52) - nrf52Bluetooth->sendLog(reinterpret_cast(buffer)); + nrf52Bluetooth->sendLog(buffer, size); #endif delete[] message; delete[] buffer; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 56d7ed167..57035a7c3 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -74,11 +74,16 @@ void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) LOG_INFO("CCCD Updated: %u\n", 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 + if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) { - if (chr->notifyEnabled(conn_hdl)) { - LOG_INFO("fromNum 'Notify' enabled\n"); + auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl); + if (result) { + LOG_INFO("Notify/Indicate enabled\n"); } else { - LOG_INFO("fromNum 'Notify' disabled\n"); + LOG_INFO("Notify/Indicate disabled\n"); } } } @@ -176,7 +181,7 @@ void setupMeshService(void) toRadio.setWriteCallback(onToRadioWrite, false); toRadio.begin(); - logRadio.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); + logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ); logRadio.setPermission(secMode, SECMODE_NO_ACCESS); logRadio.setMaxLen(512); logRadio.setCccdWriteCallback(onCccd); @@ -334,9 +339,12 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu screen->endAlert(); } -void NRF52Bluetooth::sendLog(const char *logMessage) +void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) { - if (!isConnected() || strlen(logMessage) > 512) + if (!isConnected() || length > 512) return; - logRadio.notify(logMessage); + 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/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 0d621dda2..2229163f8 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -13,7 +13,7 @@ class NRF52Bluetooth : BluetoothApi void clearBonds(); bool isConnected(); int getRssi(); - void sendLog(const char *logMessage); + void sendLog(const uint8_t *logMessage, size_t length); private: static void onConnectionSecured(uint16_t conn_handle); From f59d98482faa7f8a37bb29bbeb34f0b362923db9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 7 Jul 2024 05:08:49 -0700 Subject: [PATCH 0675/1377] Fix build when HAS_NETWORKING is false on nrf52 (#4237) (tested on a rak4631 by setting HAS_ETHERNET false when shrinking image) --- src/mesh/eth/ethClient.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 5373f243e..9f3bb8ab7 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -12,6 +12,8 @@ #include #include +#if HAS_NETWORKING + #ifndef DISABLE_NTP #include @@ -183,3 +185,5 @@ bool isEthernetAvailable() return true; } } + +#endif \ No newline at end of file From 86ca81b555b6b881cf14997a1cd1d862bc07198f Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:06:42 +0200 Subject: [PATCH 0676/1377] If `toPhoneQueue` is full, still increment `fromNum` to avoid client never getting packets (#4246) --- src/mesh/MeshService.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index e5f33e8e7..9e2a5b110 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -299,6 +299,7 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) } else { LOG_WARN("ToPhone queue is full, dropping packet.\n"); releaseToPool(p); + fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets return; } } From e1bf4c32f3bade256667e24f706010505a115a9c Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Sun, 7 Jul 2024 19:14:18 +0200 Subject: [PATCH 0677/1377] Update to SoftDevice 7.3.0 for wio-sdk-wm1110 and wio-tracker-wm1110 (#4248) * Update variant.h * Update wio-tracker-wm1110.json * Update wio-sdk-wm1110.json * Update platformio.ini * Update platformio.ini * Add files via upload * Add files via upload * Update variant.h --- boards/wio-sdk-wm1110.json | 6 +- boards/wio-tracker-wm1110.json | 6 +- variants/wio-sdk-wm1110/nrf52840_s140_v7.ld | 38 + variants/wio-sdk-wm1110/platformio.ini | 3 +- variants/wio-sdk-wm1110/softdevice/ble.h | 652 ++++ variants/wio-sdk-wm1110/softdevice/ble_err.h | 92 + variants/wio-sdk-wm1110/softdevice/ble_gap.h | 2895 +++++++++++++++++ variants/wio-sdk-wm1110/softdevice/ble_gatt.h | 232 ++ .../wio-sdk-wm1110/softdevice/ble_gattc.h | 764 +++++ .../wio-sdk-wm1110/softdevice/ble_gatts.h | 904 +++++ variants/wio-sdk-wm1110/softdevice/ble_hci.h | 135 + .../wio-sdk-wm1110/softdevice/ble_l2cap.h | 495 +++ .../wio-sdk-wm1110/softdevice/ble_ranges.h | 149 + .../wio-sdk-wm1110/softdevice/ble_types.h | 217 ++ .../wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h | 259 ++ .../wio-sdk-wm1110/softdevice/nrf_error.h | 90 + .../wio-sdk-wm1110/softdevice/nrf_error_sdm.h | 73 + .../wio-sdk-wm1110/softdevice/nrf_error_soc.h | 85 + variants/wio-sdk-wm1110/softdevice/nrf_nvic.h | 449 +++ variants/wio-sdk-wm1110/softdevice/nrf_sdm.h | 380 +++ variants/wio-sdk-wm1110/softdevice/nrf_soc.h | 1046 ++++++ variants/wio-sdk-wm1110/softdevice/nrf_svc.h | 98 + .../wio-tracker-wm1110/nrf52840_s140_v7.ld | 38 + variants/wio-tracker-wm1110/platformio.ini | 3 +- variants/wio-tracker-wm1110/softdevice/ble.h | 652 ++++ .../wio-tracker-wm1110/softdevice/ble_err.h | 92 + .../wio-tracker-wm1110/softdevice/ble_gap.h | 2895 +++++++++++++++++ .../wio-tracker-wm1110/softdevice/ble_gatt.h | 232 ++ .../wio-tracker-wm1110/softdevice/ble_gattc.h | 764 +++++ .../wio-tracker-wm1110/softdevice/ble_gatts.h | 904 +++++ .../wio-tracker-wm1110/softdevice/ble_hci.h | 135 + .../wio-tracker-wm1110/softdevice/ble_l2cap.h | 495 +++ .../softdevice/ble_ranges.h | 149 + .../wio-tracker-wm1110/softdevice/ble_types.h | 217 ++ .../softdevice/nrf52/nrf_mbr.h | 259 ++ .../wio-tracker-wm1110/softdevice/nrf_error.h | 90 + .../softdevice/nrf_error_sdm.h | 73 + .../softdevice/nrf_error_soc.h | 85 + .../wio-tracker-wm1110/softdevice/nrf_nvic.h | 449 +++ .../wio-tracker-wm1110/softdevice/nrf_sdm.h | 380 +++ .../wio-tracker-wm1110/softdevice/nrf_soc.h | 1046 ++++++ .../wio-tracker-wm1110/softdevice/nrf_svc.h | 98 + 42 files changed, 18116 insertions(+), 8 deletions(-) create mode 100644 variants/wio-sdk-wm1110/nrf52840_s140_v7.ld create mode 100644 variants/wio-sdk-wm1110/softdevice/ble.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_err.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gap.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatt.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gattc.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatts.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_hci.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_l2cap.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_ranges.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_types.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_nvic.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_sdm.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_soc.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_svc.h create mode 100644 variants/wio-tracker-wm1110/nrf52840_s140_v7.ld create mode 100644 variants/wio-tracker-wm1110/softdevice/ble.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_err.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gap.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatt.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gattc.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatts.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_hci.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_l2cap.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_ranges.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_types.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_nvic.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_sdm.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_soc.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_svc.h diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index 18c87adde..f45b030d1 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -1,7 +1,7 @@ { "build": { "arduino": { - "ldscript": "nrf52840_s140_v6.ld" + "ldscript": "nrf52840_s140_v7.ld" }, "core": "nRF5", "cpu": "cortex-m4", @@ -15,8 +15,8 @@ "softdevice": { "sd_flags": "-DS140", "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" + "sd_version": "7.3.0", + "sd_fwid": "0x0123" }, "bootloader": { "settings_addr": "0xFF000" diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index 029c9c085..04db52518 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -1,7 +1,7 @@ { "build": { "arduino": { - "ldscript": "nrf52840_s140_v6.ld" + "ldscript": "nrf52840_s140_v7.ld" }, "core": "nRF5", "cpu": "cortex-m4", @@ -22,8 +22,8 @@ "softdevice": { "sd_flags": "-DS140", "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" + "sd_version": "7.3.0", + "sd_fwid": "0x0123" }, "bootloader": { "settings_addr": "0xFF000" diff --git a/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld b/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld new file mode 100644 index 000000000..6aaeb4034 --- /dev/null +++ b/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 4a23e7a11..619f86e1e 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -11,6 +11,7 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = variants/wio-sdk-wm1110/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} @@ -30,4 +31,4 @@ debug_extra_cmds = echo Running .gdbinit script commands 1 set useSoftDevice = false - end \ No newline at end of file + end diff --git a/variants/wio-sdk-wm1110/softdevice/ble.h b/variants/wio-sdk-wm1110/softdevice/ble.h new file mode 100644 index 000000000..177b436ad --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON BLE SoftDevice Common + @{ + @defgroup ble_api Events, type definitions and API calls + @{ + + @brief Module independent events, type definitions and API calls for the BLE SoftDevice. + + */ + +#ifndef BLE_H__ +#define BLE_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_l2cap.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief Common API SVC numbers. + */ +enum BLE_COMMON_SVCS { + SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ + SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ + SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ + SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ + SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ + SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ + SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ + SD_BLE_OPT_SET, /**< Set a BLE option. */ + SD_BLE_OPT_GET, /**< Get a BLE option. */ + SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ + SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ +}; + +/** + * @brief BLE Module Independent Event IDs. + */ +enum BLE_COMMON_EVTS { + BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t + \n Reply with @ref sd_ble_user_mem_reply. */ + BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ +}; + +/**@brief BLE Connection Configuration IDs. + * + * IDs that uniquely identify a connection configuration. + */ +enum BLE_CONN_CFGS { + BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ + BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ + BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ + BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ + BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ +}; + +/**@brief BLE Common Configuration IDs. + * + * IDs that uniquely identify a common configuration. + */ +enum BLE_COMMON_CFGS { + BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ +}; + +/**@brief Common Option IDs. + * IDs that uniquely identify a common option. + */ +enum BLE_COMMON_OPTS { + BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ + BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ + BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ +}; + +/** @} */ + +/** @addtogroup BLE_COMMON_DEFINES Defines + * @{ */ + +/** @brief Required pointer alignment for BLE Events. + */ +#define BLE_EVT_PTR_ALIGNMENT 4 + +/** @brief Leaves the maximum of the two arguments. + */ +#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +/** @brief Maximum possible length for BLE Events. + * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a + * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. + */ +#define BLE_EVT_LEN_MAX(ATT_MTU) \ + (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) + +/** @defgroup BLE_USER_MEM_TYPES User Memory Types + * @{ */ +#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ +#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ +/** @} */ + +/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts + * @{ + */ +#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ +#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ +/** @} */ + +/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. + * @{ + */ +#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ + +/** @} */ + +/** @} */ + +/** @addtogroup BLE_COMMON_STRUCTURES Structures + * @{ */ + +/**@brief User Memory Block. */ +typedef struct { + uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ + uint16_t len; /**< Length in bytes of the user memory block. */ +} ble_user_mem_block_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ +} ble_evt_user_mem_request_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ + ble_user_mem_block_t mem_block; /**< User memory block */ +} ble_evt_user_mem_release_t; + +/**@brief Event structure for events not associated with a specific function module. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ + union { + ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ + ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ + } params; /**< Event parameter union. */ +} ble_common_evt_t; + +/**@brief BLE Event header. */ +typedef struct { + uint16_t evt_id; /**< Value from a BLE__EVT series. */ + uint16_t evt_len; /**< Length in octets including this header. */ +} ble_evt_hdr_t; + +/**@brief Common BLE Event type, wrapping the module specific event reports. */ +typedef struct { + ble_evt_hdr_t header; /**< Event header. */ + union { + ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ + ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ + ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ + ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ + ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ + } evt; /**< Event union. */ +} ble_evt_t; + +/** + * @brief Version Information. + */ +typedef struct { + uint8_t version_number; /**< Link Layer Version number. See + https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ + uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) + (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ + uint16_t + subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ +} ble_version_t; + +/** + * @brief Configuration parameters for the PA and LNA. + */ +typedef struct { + uint8_t enable : 1; /**< Enable toggling for this amplifier */ + uint8_t active_high : 1; /**< Set the pin to be active high */ + uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ +} ble_pa_lna_cfg_t; + +/** + * @brief PA & LNA GPIO toggle configuration + * + * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or + * a low noise amplifier. + * + * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided + * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences + * and must be avoided by the application. + */ +typedef struct { + ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ + ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ + + uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ + uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ + uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ +} ble_common_opt_pa_lna_t; + +/** + * @brief Configuration of extended BLE connection events. + * + * When enabled the SoftDevice will dynamically extend the connection event when possible. + * + * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. + * The connection event can be extended if there is time to send another packet pair before the start of the next connection + * interval, and if there are no conflicts with other BLE roles requesting radio time. + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ +} ble_common_opt_conn_evt_ext_t; + +/** + * @brief Enable/disable extended RC calibration. + * + * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice + * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets + * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. + * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When + * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the + * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. + * + * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the + * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ +} ble_common_opt_extended_rc_cal_t; + +/**@brief Option structure for common options. */ +typedef union { + ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ + ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ + ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ +} ble_common_opt_t; + +/**@brief Common BLE Option type, wrapping the module specific options. */ +typedef union { + ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ + ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ + ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ +} ble_opt_t; + +/**@brief BLE connection configuration type, wrapping the module specific configurations, set with + * @ref sd_ble_cfg_set. + * + * @note Connection configurations don't have to be set. + * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, + * the default connection configuration will be automatically added for the remaining connections. + * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in + * place of @ref ble_conn_cfg_t::conn_cfg_tag. + * + * @sa sd_ble_gap_adv_start() + * @sa sd_ble_gap_connect() + * + * @mscs + * @mmsc{@ref BLE_CONN_CFG} + * @endmscs + + */ +typedef struct { + uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the + @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls + to select this configuration when creating a connection. + Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ + union { + ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ + ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ + ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ + ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ + ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ + } params; /**< Connection configuration union. */ +} ble_conn_cfg_t; + +/** + * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. + */ +typedef struct { + uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. + Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is + @ref BLE_UUID_VS_COUNT_MAX. */ +} ble_common_cfg_vs_uuid_t; + +/**@brief Common BLE Configuration type, wrapping the common configurations. */ +typedef union { + ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ +} ble_common_cfg_t; + +/**@brief BLE Configuration type, wrapping the module specific configurations. */ +typedef union { + ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ + ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ + ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ + ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ +} ble_cfg_t; + +/** @} */ + +/** @addtogroup BLE_COMMON_FUNCTIONS Functions + * @{ */ + +/**@brief Enable the BLE stack + * + * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the + * application RAM region (APP_RAM_BASE). On return, this will + * contain the minimum start address of the application RAM region + * required by the SoftDevice for this configuration. + * @warning After this call, the SoftDevice may generate several events. The list of events provided + * below require the application to initiate a SoftDevice API call. The corresponding API call + * is referenced in the event documentation. + * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop + * communicating with the peer device. + * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST + * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST + * - @ref BLE_GAP_EVT_SEC_REQUEST + * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST + * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST + * - @ref BLE_EVT_USER_MEM_REQUEST + * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located + * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between + * APP_RAM_BASE and the start of the call stack. + * + * @details This call initializes the BLE stack, no BLE related function other than @ref + * sd_ble_cfg_set can be called before this one. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: + * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not + * large enough to fit this configuration's memory requirement. Check *p_app_ram_base + * and set the start address of the application RAM region accordingly. + * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which + * is currently not supported. + * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. + */ +SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); + +/**@brief Add configurations for the BLE stack + * + * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref + * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. + * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. + * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). + * See @ref sd_ble_enable for details about APP_RAM_BASE. + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note If a configuration is set more than once, the last one set is the one that takes effect on + * @ref sd_ble_enable. + * + * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default + * configuration. + * + * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref + * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref + * sd_ble_enable). + * + * @note Error codes for the configurations are described in the configuration structs. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The configuration has been added successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. + * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not + * large enough to fit this configuration's memory requirement. + */ +SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); + +/**@brief Get an event from the pending events queue. + * + * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. + * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. + * The buffer should be interpreted as a @ref ble_evt_t struct. + * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. + * + * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that + * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. + * The application is free to choose whether to call this function from thread mode (main context) or directly from the + * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher + * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) + * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so + * could potentially leave events in the internal queue without the application being aware of this fact. + * + * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to + * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, + * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. + * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length + * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: + * + * \code + * uint16_t len; + * errcode = sd_ble_evt_get(NULL, &len); + * \endcode + * + * @mscs + * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} + * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. + * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. + */ +SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); + +/**@brief Add a Vendor Specific base UUID. + * + * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later + * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t + * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code + * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses + * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to + * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field + * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to + * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, + * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. + * + * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by + * the 16-bit uuid field in @ref ble_uuid_t. + * + * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in + * p_uuid_type along with an @ref NRF_SUCCESS error code. + * + * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding + * bytes 12 and 13. + * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be + * stored. + * + * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. + * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. + */ +SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); + +/**@brief Remove a Vendor Specific base UUID. + * + * @details This call removes a Vendor Specific base UUID. This function allows + * the application to reuse memory allocated for Vendor Specific base UUIDs. + * + * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID + * type. + * + * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. + * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific + * base UUID will be removed. If the function returns successfully, the UUID type that was removed will + * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that + * is in use by the ATT Server. + * + * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. + * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. + */ +SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); + +/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. + * + * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared + * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs + * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index + * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. + * + * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. + * + * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). + * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. + * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. + * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. + */ +SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); + +/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). + * + * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is + * computed. + * + * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. + * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). + * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. + * + * @retval ::NRF_SUCCESS Successfully encoded into the buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. + */ +SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); + +/**@brief Get Version Information. + * + * @details This call allows the application to get the BLE stack version information. + * + * @param[out] p_version Pointer to a ble_version_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Version information stored successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). + */ +SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); + +/**@brief Provide a user memory block. + * + * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. + */ +SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); + +/**@brief Set a BLE option. + * + * @details This call allows the application to set the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. + * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. + * + * @retval ::NRF_SUCCESS Option set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + */ +SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); + +/**@brief Get a BLE option. + * + * @details This call allows the application to retrieve the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. + * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Option retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. + * + */ +SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); + +/** @} */ +#ifdef __cplusplus +} +#endif +#endif /* BLE_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_err.h b/variants/wio-sdk-wm1110/softdevice/ble_err.h new file mode 100644 index 000000000..d20f6d141 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_err.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @addtogroup nrf_error + @{ + @ingroup BLE_COMMON + @} + + @defgroup ble_err General error codes + @{ + + @brief General error code definitions for the BLE API. + + @ingroup BLE_COMMON +*/ +#ifndef NRF_BLE_ERR_H__ +#define NRF_BLE_ERR_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* @defgroup BLE_ERRORS Error Codes + * @{ */ +#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ +#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ +#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ +#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ +#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ +#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ + (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ +/** @} */ + +/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges + * @brief Assignment of subranges for module specific error codes. + * @note For specific error codes, see ble_.h or ble_error_.h. + * @{ */ +#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ +#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ +#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ +#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gap.h b/variants/wio-sdk-wm1110/softdevice/ble_gap.h new file mode 100644 index 000000000..8ebdfa82b --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gap.h @@ -0,0 +1,2895 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GAP Generic Access Profile (GAP) + @{ + @brief Definitions and prototypes for the GAP interface. + */ + +#ifndef BLE_GAP_H__ +#define BLE_GAP_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GAP API SVC numbers. + */ +enum BLE_GAP_SVCS { + SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ + SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ + SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ + SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ + SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ + SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ + SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ + SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ + SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ + SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ + SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ + SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ + SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ + SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ + SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ + SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ + SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ + SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ + SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ + SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ + SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ + SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ + SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ + SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ + SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ + SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ + SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ + SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ + SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ + SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ + SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ + SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ + SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ + SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ + SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ + SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ + SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ + SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = + BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ +}; + +/**@brief GAP Event IDs. + * IDs that uniquely identify an event coming from the stack to the application. + */ +enum BLE_GAP_EVTS { + BLE_GAP_EVT_CONNECTED = + BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ + BLE_GAP_EVT_DISCONNECTED = + BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE = + BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ + BLE_GAP_EVT_SEC_PARAMS_REQUEST = + BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. + \n See @ref ble_gap_evt_sec_params_request_t. */ + BLE_GAP_EVT_SEC_INFO_REQUEST = + BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. + \n See @ref ble_gap_evt_sec_info_request_t. */ + BLE_GAP_EVT_PASSKEY_DISPLAY = + BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref + sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ + BLE_GAP_EVT_KEY_PRESSED = + BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ + BLE_GAP_EVT_AUTH_KEY_REQUEST = + BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. + \n See @ref ble_gap_evt_auth_key_request_t. */ + BLE_GAP_EVT_LESC_DHKEY_REQUEST = + BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref + sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ + BLE_GAP_EVT_AUTH_STATUS = + BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ + BLE_GAP_EVT_CONN_SEC_UPDATE = + BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ + BLE_GAP_EVT_TIMEOUT = + BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ + BLE_GAP_EVT_RSSI_CHANGED = + BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ + BLE_GAP_EVT_ADV_REPORT = + BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ + BLE_GAP_EVT_SEC_REQUEST = + BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate +\n or with @ref sd_ble_gap_encrypt if required security information is available +. \n See @ref ble_gap_evt_sec_request_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref + sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ + BLE_GAP_EVT_SCAN_REQ_REPORT = + BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ + BLE_GAP_EVT_PHY_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n + See @ref ble_gap_evt_phy_update_request_t. */ + BLE_GAP_EVT_PHY_UPDATE = + BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref + sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE = + BLE_GAP_EVT_BASE + + 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ + BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = + BLE_GAP_EVT_BASE + + 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ + BLE_GAP_EVT_ADV_SET_TERMINATED = + BLE_GAP_EVT_BASE + + 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ +}; + +/**@brief GAP Option IDs. + * IDs that uniquely identify a GAP option. + */ +enum BLE_GAP_OPTS { + BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ + BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ + BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ + BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ + BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = + BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ + BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = + BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ +}; + +/**@brief GAP Configuration IDs. + * + * IDs that uniquely identify a GAP configuration. + */ +enum BLE_GAP_CFGS { + BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ + BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ + BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic + inclusion configuration. */ + BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic + inclusion configuration. */ +}; + +/**@brief GAP TX Power roles. + */ +enum BLE_GAP_TX_POWER_ROLES { + BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ + BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ + BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ +}; + +/** @} */ + +/**@addtogroup BLE_GAP_DEFINES Defines + * @{ */ + +/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP + * @{ */ +#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ + (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ +#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ + (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ +#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ + (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ +#define BLE_ERROR_GAP_WHITELIST_IN_USE \ + (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ + (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ + (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLES GAP Roles + * @{ */ +#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ +#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ +#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ +/**@} */ + +/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources + * @{ */ +#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ +#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ +#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ +/**@} */ + +/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types + * @{ */ +#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ +#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ +#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ + 0x7F /**< An advertiser may advertise without its address. \ + This type of advertising is called anonymous. */ +/**@} */ + +/**@brief The default interval in seconds at which a private address is refreshed. */ +#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ +/**@brief The maximum interval in seconds at which a private address can be refreshed. */ +#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ + +/** @brief BLE address length. */ +#define BLE_GAP_ADDR_LEN (6) + +/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes + * @{ */ +#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ +#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ +#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ + 0x02 /**< Device will send and accept only private addresses for its own address, \ + and will not accept a peer using identity address as sender address when \ + the peer IRK is exchanged, non-zero and added to the identity list. */ +/**@} */ + +/** @brief Invalid power level. */ +#define BLE_GAP_POWER_LEVEL_INVALID 127 + +/** @brief Advertising set handle not set. */ +#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) + +/** @brief The default number of advertising sets. */ +#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) + +/** @brief The maximum number of advertising sets supported by this SoftDevice. */ +#define BLE_GAP_ADV_SET_COUNT_MAX (1) + +/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. + * @{ */ +#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ + (31) /**< Maximum data length for an advertising set. \ + If more advertising data is required, use extended advertising instead. */ +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for an extended advertising set. */ + +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ + (238) /**< Maximum supported data length for an extended connectable advertising set. */ +/**@}. */ + +/** @brief Set ID not available in advertising report. */ +#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF + +/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons + * @{ */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ +/**@} */ + +/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format + * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm + * @{ */ +#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ +#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ +#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ +#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ +#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ +#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ +#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ +#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ +#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ +#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ +#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ +#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ +#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ +#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ +#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ +#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags + * @{ */ +#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min + * @{ */ +#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ +#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ + /**@} */ + +/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min + * @{ */ +#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min + * @{ */ +#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min + * @{ */ +#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ +#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size + * + * Scan buffers are used for storing advertising data received from an advertiser. + * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. + * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. + * @{ */ +#define BLE_GAP_SCAN_BUFFER_MIN \ + (31) /**< Minimum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_MAX \ + (31) /**< Maximum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ + (255) /**< Minimum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ + (1650) /**< Maximum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for \ + an extended advertising set. */ +/** @} */ + +/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types + * + * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. + * + * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. + * The maximum supported data length for an extended advertiser is defined by + * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED + * Note that some of the advertising types do not support advertising data. Non-scannable types do not support + * scan response data. + * + * @{ */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x01 /**< Connectable and scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ + 0x02 /**< Connectable non-scannable directed advertising \ + events. Advertising interval is less that 3.75 ms. \ + Use this type for fast reconnections. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x03 /**< Connectable non-scannable directed advertising \ + events. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x04 /**< Non-connectable scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x05 /**< Non-connectable non-scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x06 /**< Connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x07 /**< Connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x08 /**< Non-connectable scannable undirected advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ + 0x09 /**< Non-connectable scannable directed advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x0A /**< Non-connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x0B /**< Non-connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies + * @{ */ +#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ +#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status + * @{ */ +#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ + 0x01 /**< More data to be received. \ + @note This value will only be used if \ + @ref ble_gap_scan_params_t::report_incomplete_evts and \ + @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ + 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ + 0x03 /**< Failed to receive the remaining data. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +/**@} */ + +/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies + * @{ */ +#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ + 0x00 /**< Accept all advertising packets except directed advertising packets \ + not addressed to this device. */ +#define BLE_GAP_SCAN_FP_WHITELIST \ + 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ + packets not addressed to this device. */ +#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ + 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ + 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units + * @{ */ +#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ + (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ + */ +#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ + (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ + mode. */ +#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ + (0) /**< Unlimited advertising in general discoverable mode. \ + For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ +/**@} */ + +/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes + * @{ */ +#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ +#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ +#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ +/**@} */ + +/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities + * @{ */ +#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ +#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ +#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ +/**@} */ + +/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types + * @{ */ +#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ +#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ +#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ +/**@} */ + +/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types + * @{ */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS GAP Security status + * @{ */ +#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ +#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ +#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ +#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ +#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ +#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ +#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ +#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ +#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ +#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ +#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ +#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ +#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ +#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ +#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ +#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ +#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources + * @{ */ +#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ +#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ +/**@} */ + +/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits + * @{ */ +#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ + 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ + 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ + 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ + 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ +/**@} */ + +/**@defgroup BLE_GAP_DEVNAME GAP device name defines. + * @{ */ +#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ +#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ +#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ +/**@} */ + +/**@brief Disable RSSI events for connections */ +#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF + +/**@defgroup BLE_GAP_PHYS GAP PHYs + * @{ */ +#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ +#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ +#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ +#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ +#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ + +/**@brief Supported PHYs in connections, for scanning, and for advertising. */ +#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ + +/**@} */ + +/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters + * + * See @ref ble_gap_conn_sec_mode_t. + * @{ */ +/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ + do { \ + (ptr)->sm = 0; \ + (ptr)->lv = 0; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 2; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 3; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 4; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 2; \ + } while (0) +/**@} */ + +/**@brief GAP Security Random Number Length. */ +#define BLE_GAP_SEC_RAND_LEN 8 + +/**@brief GAP Security Key Length. */ +#define BLE_GAP_SEC_KEY_LEN 16 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ +#define BLE_GAP_LESC_P256_PK_LEN 64 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ +#define BLE_GAP_LESC_DHKEY_LEN 32 + +/**@brief GAP Passkey Length. */ +#define BLE_GAP_PASSKEY_LEN 6 + +/**@brief Maximum amount of addresses in the whitelist. */ +#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) + +/**@brief Maximum amount of identities in the device identities list. */ +#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) + +/**@brief Default connection count for a configuration. */ +#define BLE_GAP_CONN_COUNT_DEFAULT (1) + +/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. + * @{ */ +#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ +#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ +#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. + * @{ */ +#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ + (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ + (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ + +/**@} */ + +/**@brief Automatic data length parameter. */ +#define BLE_GAP_DATA_LENGTH_AUTO 0 + +/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. + * @{ */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ +/**@} */ + +/**@defgroup GAP_SEC_MODES GAP Security Modes + * @{ */ +#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ +/**@} */ + +/**@brief The total number of channels in Bluetooth Low Energy. */ +#define BLE_GAP_CHANNEL_COUNT (40) + +/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines + * @{ */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ + /**@} */ + +/** @} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations + * @{ + */ +#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ + (1) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will reserve the attribute handles \ + which are otherwise used for this characteristic. \ + By reserving the attribute handles it will be possible \ + to upgrade the SoftDevice without changing handle of the \ + Service Changed characteristic. */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ + (2) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will not reserve the attribute handles \ + which are otherwise used for this characteristic. */ +/**@} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values + * @{ */ +#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +/**@} */ + +/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options + * @{ */ +#define BLE_GAP_SLAVE_LATENCY_ENABLE \ + (0) /**< Slave latency is enabled. When slave latency is enabled, \ + the slave will wake up every time it has data to send, \ + and/or every slave latency number of connection events. */ +#define BLE_GAP_SLAVE_LATENCY_DISABLE \ + (1) /**< Disable slave latency. The slave will wake up every connection event \ + regardless of the requested slave latency. \ + This option consumes the most power. */ +#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ + (2) /**< The slave will wake up every connection event if it has not received \ + an ACK from the master for at least slave latency events. This \ + configuration may increase the power consumption in environments \ + with a lot of radio activity. */ +/**@} */ + +/**@addtogroup BLE_GAP_STRUCTURES Structures + * @{ */ + +/**@brief Advertising event properties. */ +typedef struct { + uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ + uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. + @note Anonymous advertising is only available for + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ + uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ +} ble_gap_adv_properties_t; + +/**@brief Advertising report type. */ +typedef struct { + uint16_t connectable : 1; /**< Connectable advertising event type. */ + uint16_t scannable : 1; /**< Scannable advertising event type. */ + uint16_t directed : 1; /**< Directed advertising event type. */ + uint16_t scan_response : 1; /**< Received a scan response. */ + uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ + uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ + uint16_t reserved : 9; /**< Reserved for future use. */ +} ble_gap_adv_report_type_t; + +/**@brief Advertising Auxiliary Pointer. */ +typedef struct { + uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ + uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ +} ble_gap_aux_pointer_t; + +/**@brief Bluetooth Low Energy address. */ +typedef struct { + uint8_t + addr_id_peer : 1; /**< Only valid for peer addresses. + This bit is set by the SoftDevice to indicate whether the address has been resolved from + a Resolvable Private Address (when the peer is using privacy). + If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. + + This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. + */ + uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ + uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. + @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ +} ble_gap_addr_t; + +/**@brief GAP connection parameters. + * + * @note When ble_conn_params_t is received in an event, both min_conn_interval and + * max_conn_interval will be equal to the connection interval set by the central. + * + * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: + * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval + * that corresponds to the following Bluetooth Spec requirement: + * The Supervision_Timeout in milliseconds shall be larger than + * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. + */ +typedef struct { + uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ +} ble_gap_conn_params_t; + +/**@brief GAP connection security modes. + * + * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n + * Security Mode 1 Level 1: No security is needed (aka open link).\n + * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n + * Security Mode 1 Level 3: MITM protected encrypted link required.\n + * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n + * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n + * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n + */ +typedef struct { + uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ + uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ + +} ble_gap_conn_sec_mode_t; + +/**@brief GAP connection security status.*/ +typedef struct { + ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ + uint8_t + encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ +} ble_gap_conn_sec_t; + +/**@brief Identity Resolving Key. */ +typedef struct { + uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ +} ble_gap_irk_t; + +/**@brief Channel mask (40 bits). + * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, + * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents + * channel index 39. If a bit is set to 1, the channel is not used. + */ +typedef uint8_t ble_gap_ch_mask_t[5]; + +/**@brief GAP advertising parameters. */ +typedef struct { + ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ + ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. + @note ble_gap_addr_t::addr_type cannot be + @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. + - When privacy is enabled and the local device uses + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, + the device identity list is searched for a matching entry. If + the local IRK for that device identity is set, the local IRK + for that device will be used to generate the advertiser address + field in the advertising packet. + - If @ref ble_gap_adv_properties_t::type is directed, this must be + set to the targeted scanner or initiator. If the peer address is + in the device identity list, the peer IRK for that device will be + used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE + target addresses used in the advertising event PDUs. */ + uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE + advertising, this parameter is ignored. */ + uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, + an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + @sa BLE_GAP_ADV_TIMEOUT_VALUES. + @note The SoftDevice will always complete at least one advertising + event even if the duration is set too low. */ + uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling + advertising. Setting the value to 0 disables the limitation. When + the count of advertising events specified by this parameter + (if not 0) is reached, advertising will be automatically stopped + and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, + this parameter is ignored. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be used. + Masking away secondary advertising channels is not supported. */ + uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets + are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS + will be used. + Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. + @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets + are transmitted. + If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. + Valid values are + @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. + If @ref ble_gap_adv_properties_t::type is an extended advertising type + and connectable, this is the PHY that will be used to establish a + connection and send AUX_ADV_IND packets on. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other + advertising sets transmitted by this and other devices. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a + scan request is received and the scanner address is allowed + by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is a non-scannable + advertising type. */ +} ble_gap_adv_params_t; + +/**@brief GAP advertising data buffers. + * + * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and + * shall never be modified while advertising. The data shall be kept alive until either: + * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding + * advertising handle. + * - Advertising is stopped. + * - Advertising data is changed. + * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ +typedef struct { + ble_data_t adv_data; /**< Advertising data. + @note + Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type + that is allowed to contain advertising data. */ + ble_data_t scan_rsp_data; /**< Scan response data. + @note + Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type + that is scannable. */ +} ble_gap_adv_data_t; + +/**@brief GAP scanning parameters. */ +typedef struct { + uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. + If set to 0, the scanner will not receive advertising packets + on secondary advertising channels, and will not be able + to receive long advertising PDUs. */ + uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have + @ref ble_gap_adv_report_type_t::status set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. + This parameter is ignored when used with @ref sd_ble_gap_connect + @note This may be used to abort receiving more packets from an extended + advertising event, and is only available for extended + scanning, see @ref sd_ble_gap_scan_start. + @note This feature is not supported by this SoftDevice. */ + uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. + This parameter is ignored when used with @ref sd_ble_gap_connect. */ + uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. + @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and + @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with + @ref sd_ble_gap_connect */ + uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, + scan_phys will default to @ref BLE_GAP_PHY_1MBPS. + - If @ref ble_gap_scan_params_t::extended is set to 0, the only + supported PHY is @ref BLE_GAP_PHY_1MBPS. + - When used with @ref sd_ble_gap_scan_start, + the bitfield indicates the PHYs the scanner will use for scanning + on primary advertising channels. The scanner will accept + @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. + - When used with @ref sd_ble_gap_connect, the bitfield indicates + the PHYs the initiator will use for scanning on primary advertising + channels. The initiator will accept connections initiated on either + of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. + If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, + the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. + If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan + PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is + @ref BLE_GAP_PHY_CODED, the primary scan PHY is + @ref BLE_GAP_PHY_CODED only. */ + uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ + uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. + If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and + @ref BLE_GAP_PHY_CODED interval shall be larger than or + equal to twice the scan window. */ + uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be + set to 0. + Masking away secondary channels is not supported. */ +} ble_gap_scan_params_t; + +/**@brief Privacy. + * + * The privacy feature provides a way for the device to avoid being tracked over a period of time. + * The privacy feature, when enabled, hides the local device identity and replaces it with a private address + * that is automatically refreshed at a specified interval. + * + * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). + * With this key, a device can generate a random private address that can only be recognized by peers in possession of that + * key, and devices can establish connections without revealing their real identities. + * + * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref + * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. + * + * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all + * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. + * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is + * called. + */ +typedef struct { + uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ + uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ + uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use + the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ + ble_gap_irk_t + *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device + default IRK will be used. When used as output, pointer to IRK structure where the current default IRK + will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate + random private resolvable addresses for the local device unless instructed otherwise. */ +} ble_gap_privacy_params_t; + +/**@brief PHY preferences for TX and RX + * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each + * direction. + * @code + * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * @endcode + * + */ +typedef struct { + uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ +} ble_gap_phys_t; + +/** @brief Keys that can be exchanged during a bonding procedure. */ +typedef struct { + uint8_t enc : 1; /**< Long Term Key and Master Identification. */ + uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ + uint8_t sign : 1; /**< Connection Signature Resolving Key. */ + uint8_t link : 1; /**< Derive the Link Key from the LTK. */ +} ble_gap_sec_kdist_t; + +/**@brief GAP security parameters. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ + uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ + uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ + uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ + uint8_t oob : 1; /**< The OOB data flag. + - In LE legacy pairing, this flag is set if a device has out of band authentication data. + The OOB method is used if both of the devices have out of band authentication data. + - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band + authentication data. The OOB method is used if at least one device has the peer device's OOB data + available. */ + uint8_t + min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ + uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ + ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ + ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ +} ble_gap_sec_params_t; + +/**@brief GAP Encryption Information. */ +typedef struct { + uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ + uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ + uint8_t auth : 1; /**< Authenticated Key. */ + uint8_t ltk_len : 6; /**< LTK length in octets. */ +} ble_gap_enc_info_t; + +/**@brief GAP Master Identification. */ +typedef struct { + uint16_t ediv; /**< Encrypted Diversifier. */ + uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ +} ble_gap_master_id_t; + +/**@brief GAP Signing Information. */ +typedef struct { + uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ +} ble_gap_sign_info_t; + +/**@brief GAP LE Secure Connections P-256 Public Key. */ +typedef struct { + uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the + standard SMP protocol format: {X,Y} both in little-endian. */ +} ble_gap_lesc_p256_pk_t; + +/**@brief GAP LE Secure Connections DHKey. */ +typedef struct { + uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ +} ble_gap_lesc_dhkey_t; + +/**@brief GAP LE Secure Connections OOB data. */ +typedef struct { + ble_gap_addr_t addr; /**< Bluetooth address of the device. */ + uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ + uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ +} ble_gap_lesc_oob_data_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ +typedef struct { + ble_gap_addr_t + peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ + uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ +} ble_gap_evt_connected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ +typedef struct { + uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ +} ble_gap_evt_disconnected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ +typedef struct { + ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ +} ble_gap_evt_phy_update_request_t; + +/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ +typedef struct { + uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ + uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ +} ble_gap_evt_phy_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ +typedef struct { + ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ +} ble_gap_evt_sec_params_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ +typedef struct { + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ + ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ + uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ + uint8_t id_info : 1; /**< If 1, Identity Information required. */ + uint8_t sign_info : 1; /**< If 1, Signing Information required. */ +} ble_gap_evt_sec_info_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ +typedef struct { + uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ + uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply + with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or + @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ +} ble_gap_evt_passkey_display_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ +typedef struct { + uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ +} ble_gap_evt_key_pressed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ +typedef struct { + uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ +} ble_gap_evt_auth_key_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ +typedef struct { + ble_gap_lesc_p256_pk_t + *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory + inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ + uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the + procedure. */ +} ble_gap_evt_lesc_dhkey_request_t; + +/**@brief Security levels supported. + * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. + */ +typedef struct { + uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ + uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ + uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ + uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ +} ble_gap_sec_levels_t; + +/**@brief Encryption Key. */ +typedef struct { + ble_gap_enc_info_t enc_info; /**< Encryption Information. */ + ble_gap_master_id_t master_id; /**< Master Identification. */ +} ble_gap_enc_key_t; + +/**@brief Identity Key. */ +typedef struct { + ble_gap_irk_t id_info; /**< Identity Resolving Key. */ + ble_gap_addr_t id_addr_info; /**< Identity Address. */ +} ble_gap_id_key_t; + +/**@brief Security Keys. */ +typedef struct { + ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ + ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ + ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ + ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the + value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ +} ble_gap_sec_keys_t; + +/**@brief Security key set for both local and peer keys. */ +typedef struct { + ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be + generated locally and will always be stored if bonding. */ + ble_gap_sec_keys_t + keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ +} ble_gap_sec_keyset_t; + +/**@brief Data Length Update Procedure parameters. */ +typedef struct { + uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ + uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ +} ble_gap_data_length_params_t; + +/**@brief Data Length Update Procedure local limitation. */ +typedef struct { + uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ + uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ + uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many + microseconds. */ +} ble_gap_data_length_limitation_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ +typedef struct { + uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ + uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ + uint8_t bonded : 1; /**< Procedure resulted in a bond. */ + uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ + ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ + ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ + ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding + with LE Secure Connections, the enc bit will be always set. */ + ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding + with LE Secure Connections, the enc bit will never be set. */ +} ble_gap_evt_auth_status_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ +typedef struct { + ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ +} ble_gap_evt_conn_sec_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ + union { + ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released + scan buffer is contained in this field. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ +typedef struct { + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ +} ble_gap_evt_rssi_changed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ +typedef struct { + uint8_t reason; /**< Reason for why the advertising set terminated. See + @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ + uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, + this field indicates the number of completed advertising events. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. */ +} ble_gap_evt_adv_set_terminated_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. + * + * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * not all fields in the advertising report may be available. + * + * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. + */ +typedef struct { + ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the + peer's identity address. */ + ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if + @ref ble_gap_adv_report_type_t::directed is set to 1. If the + SoftDevice was able to resolve the address, + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr + contains the local identity address. If the target address of the + advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + and the SoftDevice was unable to resolve it, the application may try + to resolve this address to find out if the advertising event was + directed to us. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. + See @ref BLE_GAP_PHYS. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. + See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets + were received on a secondary advertising channel. */ + int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. + This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the + last received packet did not contain the Tx Power field. + @note TX Power is only included in extended advertising packets. */ + int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ + uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present + if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID + is not present if @ref ble_gap_evt_adv_report_t::set_id is set to + @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + ble_data_t data; /**< Received advertising or scan response data. If + @ref ble_gap_adv_report_type_t::status is not set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided + in @ref sd_ble_gap_scan_start is now released. */ + ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising + event. @note This field is only set if @ref ble_gap_adv_report_type_t::status + is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ +} ble_gap_evt_adv_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Man In The Middle protection requested. */ + uint8_t lesc : 1; /**< LE Secure Connections requested. */ + uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ +} ble_gap_evt_sec_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ +typedef struct { + uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ +} ble_gap_evt_scan_req_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ +typedef struct { + ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ +} ble_gap_evt_data_length_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. + * + * @note This event may also be raised after a PHY Update procedure. + */ +typedef struct { + ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ +} ble_gap_evt_data_length_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ +typedef struct { + int8_t + channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy + channels, in dBm, indexed by Channel Index. + If no measurement is available for the given channel, channel_energy is set to + @ref BLE_GAP_POWER_LEVEL_INVALID. */ +} ble_gap_evt_qos_channel_survey_report_t; + +/**@brief GAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + union /**< union alternative identified by evt_id in enclosing struct. */ + { + ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ + ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ + ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ + ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ + ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ + ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ + ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ + ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ + ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ + ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ + ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ + ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ + ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ + ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ + ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ + ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ + ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ + ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ + ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ + ble_gap_evt_qos_channel_survey_report_t + qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_t; + +/** + * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. + * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. + */ +typedef struct { + uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. + The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ + uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. + The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref + BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters + for setting the throughput of a connection. + See the SoftDevice Specification for details on throughput. */ +} ble_gap_conn_cfg_t; + +/** + * @brief Configuration of maximum concurrent connections in the different connected roles, set with + * @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too + * large. The maximum supported sum of concurrent connections is + * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. + * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. + * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum + * supported advertising handles is + * @ref BLE_GAP_ADV_SET_COUNT_MAX. + */ +typedef struct { + uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ + uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref + BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ + uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref + BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ + uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is + @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ + uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to + the application using @ref sd_ble_gap_qos_channel_survey_start. */ +} ble_gap_cfg_role_count_t; + +/** + * @brief Device name and its properties, set with @ref sd_ble_cfg_set. + * + * @note If the device name is not configured, the default device name will be + * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be + * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name + * will have no write access. + * + * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, + * the attribute table size must be increased to have room for the longer device name (see + * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). + * + * @note If vloc is @ref BLE_GATTS_VLOC_STACK : + * - p_value must point to non-volatile memory (flash) or be NULL. + * - If p_value is NULL, the device name will initially be empty. + * + * @note If vloc is @ref BLE_GATTS_VLOC_USER : + * - p_value cannot be NULL. + * - If the device name is writable, p_value must point to volatile memory (RAM). + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Invalid device name location (vloc). + * - Invalid device name security mode. + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). + * - The device name length is too long for the given Attribute Table. + * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. + */ +typedef struct { + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ + uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ + uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ +} ble_gap_cfg_device_name_t; + +/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_ppcp_incl_cfg_t; + +/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_car_incl_cfg_t; + +/**@brief Configuration structure for GAP configurations. */ +typedef union { + ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ + ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ + ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include + configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ + ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, + cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ +} ble_gap_cfg_t; + +/**@brief Channel Map option. + * + * @details Used with @ref sd_ble_opt_get to get the current channel map + * or @ref sd_ble_opt_set to set a new channel map. When setting the + * channel map, it applies to all current and future connections. When getting the + * current channel map, it applies to a single connection and the connection handle + * must be supplied. + * + * @note Setting the channel map may take some time, depending on connection parameters. + * The time taken may be different for each connection and the get operation will + * return the previous channel map until the new one has taken effect. + * + * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. + * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. + * + * @retval ::NRF_SUCCESS Get or set successful. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Less then two bits in @ref ch_map are set. + * - Bits for primary advertising channels (37-39) are set. + * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. + * + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ + uint8_t ch_map[5]; /**< Channel Map (37-bit). */ +} ble_gap_opt_ch_map_t; + +/**@brief Local connection latency option. + * + * @details Local connection latency is a feature which enables the slave to improve + * current consumption by ignoring the slave latency set by the peer. The + * local connection latency can only be set to a multiple of the slave latency, + * and cannot be longer than half of the supervision timeout. + * + * @details Used with @ref sd_ble_opt_set to set the local connection latency. The + * @ref sd_ble_opt_get is not supported for this option, but the actual + * local connection latency (unless set to NULL) is set as a return parameter + * when setting the option. + * + * @note The latency set will be truncated down to the closest slave latency event + * multiple, or the nearest multiple before half of the supervision timeout. + * + * @note The local connection latency is disabled by default, and needs to be enabled for new + * connections and whenever the connection is updated. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t requested_latency; /**< Requested local connection latency. */ + uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return + value). */ +} ble_gap_opt_local_conn_latency_t; + +/**@brief Disable slave latency + * + * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection + * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the + * peripheral will ignore the slave_latency set by the central. + * + * @note Shall only be called on peripheral links. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ +} ble_gap_opt_slave_latency_disable_t; + +/**@brief Passkey Option. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @endmscs + * + * @details Structure containing the passkey to be used during pairing. This can be used with @ref + * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication + * instead of generating a random one. + * + * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + */ +typedef struct { + uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used + during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ +} ble_gap_opt_passkey_t; + +/**@brief Compatibility mode 1 option. + * + * @details This can be used with @ref sd_ble_opt_set to enable and disable + * compatibility mode 1. Compatibility mode 1 is disabled by default. + * + * @note Compatibility mode 1 enables interoperability with devices that do not support a value of + * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a + * limited set of legacy peripheral devices from another vendor. Enabling this compatibility + * mode will only have an effect if the local device will act as a central device and + * initiate a connection to a peripheral device. In that case it may lead to the connection + * creation taking up to one connection interval longer to complete for all connections. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. + */ +typedef struct { + uint8_t enable : 1; /**< Enable compatibility mode 1.*/ +} ble_gap_opt_compat_mode_1_t; + +/**@brief Authenticated payload timeout option. + * + * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other + * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. + * + * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated + * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted + * link. + * + * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance + * to reset the timer. In addition the stack will try to prioritize running of LE ping over other + * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects + * on other activities, it is recommended to use high timeout values. + * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ +} ble_gap_opt_auth_payload_timeout_t; + +/**@brief Option structure for GAP options. */ +typedef union { + ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ + ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ + ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ + ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ + ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ + ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ +} ble_gap_opt_t; + +/**@brief Connection event triggering parameters. */ +typedef struct { + uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until + connection event PPI task triggering is stopped. + The PPI channel ID can not be one of the PPI channels reserved by + the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ + uint32_t task_endpoint; /**< Task Endpoint to trigger. */ + uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ + uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. + If the device is in slave role and slave latency is enabled, + this parameter should be set to a multiple of (slave latency + 1) + to ensure low power operation. */ +} ble_gap_conn_event_trigger_t; +/**@} */ + +/**@addtogroup BLE_GAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set the local Bluetooth identity address. + * + * The local Bluetooth identity address is the address that identifies this device to other peers. + * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @note The identity address cannot be changed while advertising, scanning or creating a connection. + * + * @note This address will be distributed to the peer during bonding. + * If the address changes, the address stored in the peer device will not be valid and the ability to + * reconnect using the old address will be lost. + * + * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being + * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged + * for the lifetime of each IC. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @endmscs + * + * @param[in] p_addr Pointer to address structure. + * + * @retval ::NRF_SUCCESS Address successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, + * scanning or creating a connection. + */ +SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); + +/**@brief Get local Bluetooth identity address. + * + * @note This will always return the identity address irrespective of the privacy settings, + * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + */ +SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); + +/**@brief Get the Bluetooth device address used by the advertiser. + * + * @note This function will return the local Bluetooth address used in advertising PDUs. When + * using privacy, the SoftDevice will generate a new private address every + * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using + * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the + * address returned may not be the latest address that is used in the advertising PDUs. + * + * @param[in] adv_handle The advertising handle to get the address from. + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. + * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); + +/**@brief Set the active whitelist in the SoftDevice. + * + * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. + * The whitelist cannot be set if a BLE role is using the whitelist. + * + * @note If an address is resolved using the information in the device identity list, then the whitelist + * filter policy applies to the peer identity address and not the resolvable address sent on air. + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @endmscs + * + * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. + * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * + * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. + * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when + * pp_wl_addrs is not NULL. + */ +SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); + +/**@brief Set device identity list. + * + * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. + * The device identity list cannot be set if a BLE role is using the list. + * + * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will + * be cleared. + * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the + * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. + * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. + * This code may be returned if the local IRK list also has an invalid entry. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity + * address. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can + * only return when pp_id_keys is not NULL. + */ +SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, + sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, + uint8_t len)); + +/**@brief Set privacy settings. + * + * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. + * + * @param[in] p_privacy_params Privacy settings. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. + * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution + characteristic is not configured to be included and the SoftDevice is configured + to support central roles. + See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning + * or creating a connection. + */ +SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); + +/**@brief Get privacy settings. + * + * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. + * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. + * + * @param[in,out] p_privacy_params Privacy settings. + * + * @retval ::NRF_SUCCESS Privacy settings read. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + */ +SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); + +/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. + * + * @note The format of the advertising data will be checked by this call to ensure interoperability. + * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and + * duplicating the local name in the advertising data and scan response data. + * + * @note In order to update advertising data while advertising, new advertising buffers must be provided. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the + * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. + * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See + * @ref ble_gap_adv_data_t. + * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising + * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. + * + * @retval ::NRF_SUCCESS Advertising set successfully configured. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid advertising data configuration specified. See @ref + * ble_gap_adv_data_t. + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, + * see @ref sd_ble_gap_whitelist_set. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - It is invalid to provide non-NULL advertising set parameters while + * advertising. + * - It is invalid to provide the same data buffers while advertising. To + * update advertising data, provide new advertising buffers. + * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format + * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an + * existing advertising handle instead. + * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. + */ +SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, + sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, + ble_gap_adv_params_t const *p_adv_params)); + +/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @note Only one advertiser may be active at any time. + * + * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. + * See @ref sd_ble_gap_privacy_set(). + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} + * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} + * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable + * advertising, this is ignored. + * + * @retval ::NRF_SUCCESS The BLE stack has started advertising. + * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration + * tag has been reached; connectable advertiser cannot be started. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref + sd_ble_gap_adv_set_configure. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, see @ref + sd_ble_gap_whitelist_set. + * @retval ::NRF_ERROR_RESOURCES Either: + * - adv_handle is configured with connectable advertising, but the event_length parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + * - Not enough BLE role slots available. + Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) + and try again. + * - p_adv_params is configured with connectable advertising, but the event_length + parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); + +/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle The advertising handle that should stop advertising. + * + * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. + * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); + +/**@brief Update connection parameters. + * + * @details In the central role this will initiate a Link Layer connection parameter update procedure, + * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for + * the central to perform the procedure. In both cases, and regardless of success or failure, the application + * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. + * + * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the + * procedure unrequested. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CPU_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, + * the parameters in the PPCP characteristic of the GAP service will be used instead. + * If NULL is provided on a central role and in response to a @ref + * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected + * + * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, + sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Disconnect (GAP Link Termination). + * + * @details This call initiates the disconnection procedure, and its completion will be communicated to the application + * with a @ref BLE_GAP_EVT_DISCONNECTED event. + * + * @events + * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CONN_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref + * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). + * + * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + */ +SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); + +/**@brief Set the radio's transmit power. + * + * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for + * possible roles. + * @param[in] handle The handle parameter is interpreted depending on role: + * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. + * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, + * will use the specified transmit power, and include it in the advertising packet headers if + * @ref ble_gap_adv_properties_t::include_tx_power set. + * - For all other roles handle is ignored. + * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). + * + * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. + * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. + * Setting these values on a chip that does not support them will result in undefined behaviour. + * @note The initiator will have the same transmit power as the scanner. + * @note When a connection is created it will inherit the transmit power from the initiator or + * advertiser leading to the connection. + * + * @retval ::NRF_SUCCESS Successfully changed the transmit power. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); + +/**@brief Set GAP Appearance value. + * + * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); + +/**@brief Get GAP Appearance value. + * + * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); + +/**@brief Set GAP Peripheral Preferred Connection Parameters. + * + * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Get GAP Peripheral Preferred Connection Parameters. + * + * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); + +/**@brief Set GAP device name. + * + * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), + * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. + * + * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. + * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. + * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or + * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). + * + * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, + sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); + +/**@brief Get GAP device name. + * + * @note If the device name is longer than the size of the supplied buffer, + * p_len will return the complete device name length, + * and not the number of bytes actually returned in p_dev_name. + * The application may use this information to allocate a suitable buffer size. + * + * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to + * NULL to obtain the complete device name length. + * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. + * + * @retval ::NRF_SUCCESS GAP device name retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); + +/**@brief Initiate the GAP Authentication procedure. + * + * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), + * otherwise in the peripheral role, an SMP Security Request will be sent. + * + * @events + * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be + * generated:} + * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} + * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} + * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} + * @event{@ref BLE_GAP_EVT_KEY_PRESSED} + * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} + * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} + * @event{@ref BLE_GAP_EVT_AUTH_STATUS} + * @event{@ref BLE_GAP_EVT_TIMEOUT} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the + * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. + * In the central role, this pointer may be NULL to reject a Security Request. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - An encryption is already executing or queued. + * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is + * reached. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. + */ +SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, + sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); + +/**@brief Reply with GAP security parameters. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. + * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be + * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. + * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or + * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this + * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. + * Note that the SoftDevice expects the application to provide memory for storing the + * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The + * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application + * upon reception of the + * @ref BLE_GAP_EVT_AUTH_STATUS event. + * + * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + */ +SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, + sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, + ble_gap_sec_keyset_t const *p_sec_keyset)); + +/**@brief Reply with an authentication key. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, + * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. + * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. + * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL + * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, + * then a 16-byte OOB key value in little-endian format. + * + * @retval ::NRF_SUCCESS Authentication key successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, + sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); + +/**@brief Reply with an LE Secure connections DHKey. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dhkey LE Secure Connections DHKey. + * + * @retval ::NRF_SUCCESS DHKey successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - The peer is not authenticated. + * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, + sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); + +/**@brief Notify the peer of a local keypress. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. + * + * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested. + * - Passkey has not been entered. + * - Keypresses have not been enabled by both peers. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. + */ +SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); + +/**@brief Generate a set of OOB data to send to a peer out of band. + * + * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has + * already been established, the one used during connection setup). The application may manually overwrite it with an updated + * value. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. + * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. + * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. + * + * @retval ::NRF_SUCCESS OOB data successfully generated. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, + sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, + ble_gap_lesc_oob_data_t *p_oobd_own)); + +/**@brief Provide the OOB data sent/received out of band. + * + * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. + * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this + * function. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. + * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. + * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. + * Must correspond to @ref ble_gap_sec_params_t::oob flag + * in @ref sd_ble_gap_authenticate in the central role or + * in @ref sd_ble_gap_sec_params_reply in the peripheral role. + * + * @retval ::NRF_SUCCESS OOB data accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested + * - Not expecting LESC OOB data + * - Have not actually exchanged passkeys. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, + sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, + ble_gap_lesc_oob_data_t const *p_oobd_peer)); + +/**@brief Initiate GAP Encryption procedure. + * + * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. + * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and + * retry. + */ +SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, + sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); + +/**@brief Reply with GAP security information. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in + * @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is + * available. + * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. + * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is + * available. + * + * @retval ::NRF_SUCCESS Successfully accepted security information. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. + * - Encryption information provided by the app without being requested. See @ref + * ble_gap_evt_sec_info_request_t::enc_info. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, + sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, + ble_gap_sign_info_t const *p_sign_info)); + +/**@brief Get the current connection security. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Current connection security successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); + +/**@brief Start reporting the received signal strength to the application. + * + * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. + * + * @events + * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is + * dependent on the settings of the threshold_dbm + * and skip_count input parameters.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are + * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. + * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref + * BLE_GAP_EVT_RSSI_CHANGED event. + * + * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); + +/**@brief Stop reporting the received signal strength. + * + * @note An RSSI change detected before the call but not yet received by the application + * may be reported after @ref sd_ble_gap_rssi_stop has been called. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); + +/**@brief Get the received signal strength for the last connection event. + * + * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND + * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. + * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. + * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. + * + * @retval ::NRF_SUCCESS Successfully read the RSSI. + * @retval ::NRF_ERROR_NOT_FOUND No sample is available. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + */ +SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); + +/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). + * + * @note A call to this function will require the application to keep the memory pointed by + * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped + * or when this function is called with another buffer. + * + * @note The scanner will automatically stop in the following cases: + * - @ref sd_ble_gap_scan_stop is called. + * - @ref sd_ble_gap_connect is called. + * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. + * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application + * access received data. The application must call this function to continue scanning, or call @ref + * sd_ble_gap_scan_stop to stop scanning. + * + * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will + * receive more reports from this advertising event. The following reports will include the old and new received data. + * + * @events + * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue + * scanning, this parameter must be NULL. + * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. + * The memory pointed to should be kept alive until the scanning is stopped. + * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. + * If the scanner receives advertising data larger than can be stored in the buffer, + * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status + * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. + * + * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Scanning is already ongoing and p_scan_params was not NULL + * - Scanning is not running and p_scan_params was NULL. + * - The scanner has timed out when this function is called to continue scanning. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. + * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again + */ +SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, + sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); + +/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). + * + * @note The buffer provided in @ref sd_ble_gap_scan_start is released. + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. + * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. + */ +SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); + +/**@brief Create a connection (GAP Link Establishment). + * + * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. + * The scanning procedure will be stopped even if the function returns an error. + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use + * whitelist, then p_peer_addr is ignored. + * @param[in] p_scan_params Pointer to scan parameters structure. + * @param[in] p_conn_params Pointer to desired connection parameters. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. + * + * @retval ::NRF_SUCCESS Successfully initiated connection procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * - Invalid parameter(s) in p_scan_params or p_conn_params. + * - Use of whitelist requested but whitelist has not been set, see @ref + * sd_ble_gap_whitelist_set. + * - Peer address was not present in the device identity list, see @ref + * sd_ble_gap_device_identities_set. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an + * existing locally initiated connect procedure, which must complete before initiating again. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::NRF_ERROR_RESOURCES Either: + * - Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. + * - The event_length parameter associated with conn_cfg_tag is too small to be able to + * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. + * Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_CONNECT, uint32_t, + sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, + ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); + +/**@brief Cancel a connection establishment. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. + * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection + * completed occurred. + */ +SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); + +/**@brief Initiate or respond to a PHY Update Procedure + * + * @details This function is used to initiate or respond to a PHY Update Procedure. It will always + * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. + * If this function is used to initiate a PHY Update procedure and the only option + * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the + * currently active PHYs in the respective directions, the SoftDevice will generate a + * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the + * procedure in the Link Layer. + * + * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, + * then the stack will select PHYs based on the peer's PHY preferences and the local link + * configuration. The PHY Update procedure will for this case result in a PHY combination + * that respects the time constraints configured with @ref sd_ble_cfg_set and the current + * link layer data length. + * + * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. + * + * If the peer does not support the PHY Update Procedure, then the resulting + * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to + * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. + * + * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status + * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or + * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. + * If the peer responds to the PHY Update procedure with invalid parameters, the status + * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. + * If the PHY Update procedure was rejected by the peer for a different reason, the status will + * contain the reason as specified by the peer. + * + * @events + * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} + * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} + * @endmscs + * + * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. + * @param[in] p_gap_phys Pointer to PHY structure. + * + * @retval ::NRF_SUCCESS Successfully requested a PHY Update. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of + * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref + * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. + * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the + * pending procedure to complete and retry. + * + */ +SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); + +/**@brief Initiate or respond to a Data Length Update Procedure. + * + * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of + * p_dl_params, the SoftDevice will choose the highest value supported in current + * configuration and connection parameters. + * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime + * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and + * MaxRxTime will be limited to maximum 2120 us. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update + * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let + * the SoftDevice automatically decide the value for that member. + * Set to NULL to use automatic values for all members. + * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not + * have enough resources or does not support the requested Data Length + * Update parameters. Ignored if NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect + * p_dl_limitation to see which parameter is not supported. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested + * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation + * to see where the limitation is. + * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the + * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. + */ +SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, + sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, + ble_gap_data_length_limitation_t *p_dl_limitation)); + +/**@brief Start the Quality of Service (QoS) channel survey module. + * + * @details The channel survey module provides measurements of the energy levels on + * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT + * events will periodically report the measured energy levels for each channel. + * + * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, + * Radio Timeslot API events and Flash API events. + * + * @note The channel survey module will attempt to do measurements so that the average interval + * between measurements will be interval_us. However due to the channel survey module + * having the lowest priority of all roles and modules, this may not be possible. In that + * case fewer than expected channel survey reports may be given. + * + * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available + * must be set. This is done using @ref sd_ble_cfg_set. + * + * @param[in] interval_us Requested average interval for the measurements and reports. See + * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set + * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel + * survey role will be scheduled at every available opportunity. + * + * @retval ::NRF_SUCCESS The module is successfully started. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the + * allowed range. + * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. + * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. + * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using + * @ref sd_ble_cfg_set. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); + +/**@brief Stop the Quality of Service (QoS) channel survey module. + * + * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this + * function is called. + * + * @retval ::NRF_SUCCESS The module is successfully stopped. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); + +/**@brief Obtain the next connection event counter value. + * + * @details The connection event counter is initialized to zero on the first connection event. The value is incremented + * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, + * Section 4.5.1. + * + * @note The connection event counter obtained through this API will be outdated if this API is called + * at the same time as the connection event counter is incremented. + * + * @note This API will always return the last connection event counter + 1. + * The actual connection event may be multiple connection events later if: + * - Slave latency is enabled and there is no data to transmit or receive. + * - Another role is scheduled with a higher priority at the same time as the next connection event. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. + * + * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, + sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); + +/**@brief Start triggering a given task on connection event start. + * + * @details When enabled, this feature will trigger a PPI task at the start of connection events. + * The application can configure the SoftDevice to trigger every N connection events starting from + * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_params Connection event trigger parameters. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. + * @retval ::NRF_ERROR_INVALID_STATE Either: + * - Trying to start connection event triggering when it is already ongoing. + * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. + * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value + to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, + sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); + +/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GAP_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatt.h b/variants/wio-sdk-wm1110/softdevice/ble_gatt.h new file mode 100644 index 000000000..df0d728fc --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gatt.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common + @{ + @brief Common definitions and prototypes for the GATT interfaces. + */ + +#ifndef BLE_GATT_H__ +#define BLE_GATT_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATT_DEFINES Defines + * @{ */ + +/** @brief Default ATT MTU, in bytes. */ +#define BLE_GATT_ATT_MTU_DEFAULT 23 + +/**@brief Invalid Attribute Handle. */ +#define BLE_GATT_HANDLE_INVALID 0x0000 + +/**@brief First Attribute Handle. */ +#define BLE_GATT_HANDLE_START 0x0001 + +/**@brief Last Attribute Handle. */ +#define BLE_GATT_HANDLE_END 0xFFFF + +/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources + * @{ */ +#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ +/** @} */ + +/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations + * @{ */ +#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ +/** @} */ + +/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags + * @{ */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ +/** @} */ + +/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations + * @{ */ +#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ +#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ +/** @} */ + +/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes + * @{ */ +#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ +#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ +#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ +#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ +#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ +#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ +#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ + 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ +#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ +#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ + 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ +#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ +#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ +#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ + 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ + */ +#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ + 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ +#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ + 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ +#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats + * @note Found at + * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + * @{ */ +#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ +#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ +#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ +#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ +#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ +#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ +#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ +#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces + * @{ + */ +#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ +#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATT_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. + */ +typedef struct { + uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. + The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + @mscs + @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + @endmscs + */ +} ble_gatt_conn_cfg_t; + +/**@brief GATT Characteristic Properties. */ +typedef struct { + /* Standard properties */ + uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ + uint8_t read : 1; /**< Reading the value permitted. */ + uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ + uint8_t write : 1; /**< Writing the value with Write Request permitted. */ + uint8_t notify : 1; /**< Notification of the value permitted. */ + uint8_t indicate : 1; /**< Indications of the value permitted. */ + uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ +} ble_gatt_char_props_t; + +/**@brief GATT Characteristic Extended Properties. */ +typedef struct { + /* Extended properties */ + uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ + uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ +} ble_gatt_char_ext_props_t; + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATT_H__ + +/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gattc.h b/variants/wio-sdk-wm1110/softdevice/ble_gattc.h new file mode 100644 index 000000000..f1df1782c --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gattc.h @@ -0,0 +1,764 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client + @{ + @brief Definitions and prototypes for the GATT Client interface. + */ + +#ifndef BLE_GATTC_H__ +#define BLE_GATTC_H__ + +#include "ble_err.h" +#include "ble_gatt.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GATTC API SVC numbers. */ +enum BLE_GATTC_SVCS { + SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ + SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ + SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ + SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ + SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ + SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ + SD_BLE_GATTC_READ, /**< Generic read. */ + SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ + SD_BLE_GATTC_WRITE, /**< Generic write. */ + SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ + SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ +}; + +/** + * @brief GATT Client Event IDs. + */ +enum BLE_GATTC_EVTS { + BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref + ble_gattc_evt_prim_srvc_disc_rsp_t. */ + BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. + */ + BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref + ble_gattc_evt_char_disc_rsp_t. */ + BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref + ble_gattc_evt_desc_disc_rsp_t. */ + BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref + ble_gattc_evt_attr_info_disc_rsp_t. */ + BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ + BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ + BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref + ble_gattc_evt_char_vals_read_rsp_t. */ + BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ + BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref + sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ + BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref + ble_gattc_evt_exchange_mtu_rsp_t. */ + BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ + BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref + ble_gattc_evt_write_cmd_tx_complete_t. */ +}; + +/**@brief GATTC Option IDs. + * IDs that uniquely identify a GATTC option. + */ +enum BLE_GATTC_OPTS { + BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTC_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC + * @{ */ +#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ +/** @} */ + +/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats + * @{ */ +#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ +#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ +/** @} */ + +/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults + * @{ */ +#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Write without Response that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTC_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for + transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ +} ble_gattc_conn_cfg_t; + +/**@brief Operation Handle Range. */ +typedef struct { + uint16_t start_handle; /**< Start Handle. */ + uint16_t end_handle; /**< End Handle. */ +} ble_gattc_handle_range_t; + +/**@brief GATT service. */ +typedef struct { + ble_uuid_t uuid; /**< Service UUID. */ + ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ +} ble_gattc_service_t; + +/**@brief GATT include. */ +typedef struct { + uint16_t handle; /**< Include Handle. */ + ble_gattc_service_t included_srvc; /**< Handle of the included service. */ +} ble_gattc_include_t; + +/**@brief GATT characteristic. */ +typedef struct { + ble_uuid_t uuid; /**< Characteristic UUID. */ + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + uint8_t char_ext_props : 1; /**< Extended properties present. */ + uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ + uint16_t handle_value; /**< Handle of the Characteristic Value. */ +} ble_gattc_char_t; + +/**@brief GATT descriptor. */ +typedef struct { + uint16_t handle; /**< Descriptor Handle. */ + ble_uuid_t uuid; /**< Descriptor UUID. */ +} ble_gattc_desc_t; + +/**@brief Write Parameters. */ +typedef struct { + uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ + uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ + uint16_t handle; /**< Handle to the attribute to be written. */ + uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ + uint16_t len; /**< Length of data in bytes. */ + uint8_t const *p_value; /**< Pointer to the value data. */ +} ble_gattc_write_params_t; + +/**@brief Attribute Information for 16-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ +} ble_gattc_attr_info16_t; + +/**@brief Attribute Information for 128-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ +} ble_gattc_attr_info128_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Service count. */ + ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_prim_srvc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Include count. */ + ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_rel_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Characteristic count. */ + ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_char_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Descriptor count. */ + ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_desc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Attribute count. */ + uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ + union { + ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + } info; /**< Attribute information union. */ +} ble_gattc_evt_attr_info_disc_rsp_t; + +/**@brief GATT read by UUID handle value pair. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ +} ble_gattc_handle_value_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ +typedef struct { + uint16_t count; /**< Handle-Value Pair Count. */ + uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ + uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref + sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. + @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_val_by_uuid_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint16_t offset; /**< Offset of the attribute data. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ +typedef struct { + uint16_t len; /**< Concatenated Attribute values length. */ + uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder + for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_vals_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ + uint16_t offset; /**< Data offset. */ + uint16_t len; /**< Data length. */ + uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_write_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ +typedef struct { + uint16_t handle; /**< Handle to which the HVx operation applies. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_hvx_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ +typedef struct { + uint16_t server_rx_mtu; /**< Server RX MTU size. */ +} ble_gattc_evt_exchange_mtu_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gattc_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of write without response transmissions completed. */ +} ble_gattc_evt_write_cmd_tx_complete_t; + +/**@brief GATTC event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint16_t + error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ + union { + ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ + ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ + ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ + ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ + ble_gattc_evt_char_val_by_uuid_read_rsp_t + char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ + ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ + ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ + ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ + ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ + ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ + ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ + ble_gattc_evt_write_cmd_tx_complete_t + write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ + } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ +} ble_gattc_evt_t; + +/**@brief UUID discovery option. + * + * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the + * Vendor Specific UUID table. Disabled by default. + * - When disabled, if a procedure initiated by + * @ref sd_ble_gattc_primary_services_discover, + * @ref sd_ble_gattc_relationships_discover, + * @ref sd_ble_gattc_characteristics_discover, + * @ref sd_ble_gattc_descriptors_discover + * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set + * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use + * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding + * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will + * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + * @retval ::NRF_SUCCESS Set successfully. + * + */ +typedef struct { + uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ +} ble_gattc_opt_uuid_disc_t; + +/**@brief Option structure for GATTC options. */ +typedef union { + ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ +} ble_gattc_opt_t; + +/** @} */ + +/** @addtogroup BLE_GATTC_FUNCTIONS Functions + * @{ */ + +/**@brief Initiate or continue a GATT Primary Service Discovery procedure. + * + * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. + * If the last service has not been reached, this function must be called again with an updated start handle value to + * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] start_handle Handle to start searching from. + * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, + sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); + +/**@brief Initiate or continue a GATT Relationship Discovery procedure. + * + * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been + * reached, this must be called again with an updated handle range to continue the search. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, + sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Discovery procedure. + * + * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, + sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. + * + * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not + * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, + sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. + * + * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_uuid Pointer to a Characteristic value UUID to read. + * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, + sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, + ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. + * + * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or + * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read + * the complete value. + * + * @events + * @event{@ref BLE_GATTC_EVT_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute to be read. + * @param[in] offset Offset into the attribute value to be read. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); + +/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. + * + * @details This function initiates a GATT Read Multiple Characteristic Values procedure. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. + * @param[in] handle_count The number of handles in p_handles. + * + * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, + sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); + +/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) + * procedure. + * + * @details This function can perform all write procedures described in GATT. + * + * @note Only one write with response procedure can be ongoing per connection at a time. + * If the application tries to write with response while another write with response procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. + * + * @note The number of Write without Response that can be queued is configured by @ref + * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. + * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without + * response is complete. + * + * @note The application can keep track of the available queue element count for writes without responses by following the + * procedure below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} + * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_write_params A pointer to a write parameters structure. + * + * @retval ::NRF_SUCCESS Successfully started the Write procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event + * and retry. + * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. + * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); + +/**@brief Send a Handle Value Confirmation to the GATT Server. + * + * @mscs + * @mmsc{@ref BLE_GATTC_HVI_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute in the indication. + * + * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); + +/**@brief Discovers information about a range of attributes on a GATT server. + * + * @events + * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} + * @endevents + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range The range of handles to request information about. + * + * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, + sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value, and + * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @events + * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] client_rx_mtu Client RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + used for this connection. + * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent request to the server. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, + sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); + +/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * + * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * @note If the buffer contains different event, behavior is undefined. + * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with + * the next Handle-Value pair in each iteration. If the function returns other than + * @ref NRF_SUCCESS, it will not be changed. + * - To start iteration, initialize the structure to zero. + * - To continue, pass the value from previous iteration. + * + * \code + * ble_gattc_handle_value_t iter; + * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); + * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) + * { + * app_handle = iter.handle; + * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); + * } + * \endcode + * + * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. + * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. + */ +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter); + +/** @} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter) +{ + uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; + uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; + uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; + + if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { + p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; + p_iter->p_value = p_next + sizeof(uint16_t); + return NRF_SUCCESS; + } else { + return NRF_ERROR_NOT_FOUND; + } +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_GATTC_H__ */ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatts.h b/variants/wio-sdk-wm1110/softdevice/ble_gatts.h new file mode 100644 index 000000000..dc94957cd --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gatts.h @@ -0,0 +1,904 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server + @{ + @brief Definitions and prototypes for the GATTS interface. + */ + +#ifndef BLE_GATTS_H__ +#define BLE_GATTS_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief GATTS API SVC numbers. + */ +enum BLE_GATTS_SVCS { + SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ + SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ + SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ + SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ + SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ + SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ + SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ + SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ + SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more + attributes. */ + SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ + SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ + SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ + SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ + SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ +}; + +/** + * @brief GATT Server Event IDs. + */ +enum BLE_GATTS_EVTS { + BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See + @ref ble_gatts_evt_write_t. */ + BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with + @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. + */ + BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref + sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ + BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. + */ + BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event + structure applies. */ + BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with + @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. + */ + BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref + ble_gatts_evt_timeout_t. */ + BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref + ble_gatts_evt_hvn_tx_complete_t. */ +}; + +/**@brief GATTS Configuration IDs. + * + * IDs that uniquely identify a GATTS configuration. + */ +enum BLE_GATTS_CFGS { + BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ + BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTS_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS + * @{ */ +#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ +#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths + * @{ */ +#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ +#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ +/** @} */ + +/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types + * @{ */ +#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ +#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ +#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types + * @{ */ +#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ +#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ +#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ +#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ +/** @} */ + +/** @defgroup BLE_GATTS_OPS GATT Server Operations + * @{ */ +#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ +/** @} */ + +/** @defgroup BLE_GATTS_VLOCS GATT Value Locations + * @{ */ +#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ +#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ +#define BLE_GATTS_VLOC_USER \ + 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ + of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ + There are no alignment requirements for the buffer. */ +/** @} */ + +/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types + * @{ */ +#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ +#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ +#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ +/** @} */ + +/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags + * @{ */ +#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ +#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ +/** @} */ + +/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values + * @{ + */ +#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ + (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size + * @{ + */ +#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ +#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ +/** @} */ + +/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults + * @{ + */ +#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTS_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. + The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ +} ble_gatts_conn_cfg_t; + +/**@brief Attribute metadata. */ +typedef struct { + ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vlen : 1; /**< Variable length attribute. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ + uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not + Write Command). */ +} ble_gatts_attr_md_t; + +/**@brief GATT Attribute. */ +typedef struct { + ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ + ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ + uint16_t init_len; /**< Initial attribute value length in bytes. */ + uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the + attribute value will be left uninitialized. */ + uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ + uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is + selected in the attribute metadata, this will have to point to a buffer that remains valid through the + lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any + other temporary location. The stack may access that memory directly without the application's + knowledge. For writable characteristics, this value must not be a location in flash memory.*/ +} ble_gatts_attr_t; + +/**@brief GATT Attribute Value. */ +typedef struct { + uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ + uint16_t offset; /**< Attribute value offset. */ + uint8_t *p_value; /**< Pointer to where value is stored or will be stored. + If value is stored in user memory, only the attribute length is updated when p_value == NULL. + Set to NULL when reading to obtain the complete length of the attribute value */ +} ble_gatts_value_t; + +/**@brief GATT Characteristic Presentation Format. */ +typedef struct { + uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ + int8_t exponent; /**< Exponent for integer data types. */ + uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ + uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ + uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ +} ble_gatts_char_pf_t; + +/**@brief GATT Characteristic metadata. */ +typedef struct { + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ + uint8_t const * + p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ + uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ + uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ + ble_gatts_char_pf_t const + *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ + ble_gatts_attr_md_t const + *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ +} ble_gatts_char_md_t; + +/**@brief GATT Characteristic Definition Handles. */ +typedef struct { + uint16_t value_handle; /**< Handle to the characteristic value. */ + uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ + uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ + uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ +} ble_gatts_char_handles_t; + +/**@brief GATT HVx parameters. */ +typedef struct { + uint16_t handle; /**< Characteristic Value Handle. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t offset; /**< Offset within the attribute value. */ + uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ + uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ +} ble_gatts_hvx_params_t; + +/**@brief GATT Authorization parameters. */ +typedef struct { + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. + Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, + as the data to be written needs to be stored and later provided by the application. */ + uint16_t offset; /**< Offset of the attribute value being updated. */ + uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ + uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ +} ble_gatts_authorize_params_t; + +/**@brief GATT Read or Write Authorize Reply parameters. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ + ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ + } params; /**< Reply Parameters. */ +} ble_gatts_rw_authorize_reply_params_t; + +/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref + BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ +} ble_gatts_cfg_service_changed_t; + +/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - @ref ble_gatts_attr_md_t::write_perm is out of range. + * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is + * not allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is + * allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. + * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported + */ +typedef struct { + ble_gatts_attr_md_t + perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ +} ble_gatts_cfg_service_changed_cccd_perm_t; + +/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The specified Attribute Table size is too small. + * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. + * - The specified Attribute Table size is not a multiple of 4. + */ +typedef struct { + uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref + BLE_GATTS_ATTR_TAB_SIZE_MIN. */ +} ble_gatts_cfg_attr_tab_size_t; + +/**@brief Config structure for GATTS configurations. */ +typedef union { + ble_gatts_cfg_service_changed_t + service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ + ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ + ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ +} ble_gatts_cfg_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ + uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref + sd_ble_gatts_value_set to finalize the writing operation. */ + uint16_t offset; /**< Offset for the write operation. */ + uint16_t len; /**< Length of the received data. */ + uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gatts_evt_write_t; + +/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint16_t offset; /**< Offset for the read operation. */ +} ble_gatts_evt_read_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ + ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ + } request; /**< Request Parameters. */ +} ble_gatts_evt_rw_authorize_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ +typedef struct { + uint8_t hint; /**< Hint (currently unused). */ +} ble_gatts_evt_sys_attr_missing_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ +} ble_gatts_evt_hvc_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ +typedef struct { + uint16_t client_rx_mtu; /**< Client RX MTU size. */ +} ble_gatts_evt_exchange_mtu_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gatts_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of notification transmissions completed. */ +} ble_gatts_evt_hvn_tx_complete_t; + +/**@brief GATTS event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ + union { + ble_gatts_evt_write_t write; /**< Write Event Parameters. */ + ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ + ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ + ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ + ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ + ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ + ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_gatts_evt_t; + +/** @} */ + +/** @addtogroup BLE_GATTS_FUNCTIONS Functions + * @{ */ + +/**@brief Add a service declaration to the Attribute Table. + * + * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to + * add a secondary service declaration that is not referenced by another service later in the Attribute Table. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. + * @param[in] p_uuid Pointer to service UUID. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a service declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); + +/**@brief Add an include declaration to the Attribute Table. + * + * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note The included service must already be present in the Attribute Table prior to this call. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID + * is used, it will be placed sequentially. + * @param[in] inc_srvc_handle Handle of the included service. + * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added an include declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + */ +SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, + sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); + +/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations + * to the Attribute Table. + * + * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and + * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format + * values. + * + * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic + * permissions. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_char_md Characteristic metadata. + * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. + * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a characteristic. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, + sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); + +/**@brief Add a descriptor to the Attribute Table. + * + * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is + * supported at this time). + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_attr Pointer to the attribute structure. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a descriptor. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, + sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); + +/**@brief Set the value of a given attribute. + * + * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully set the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + */ +SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, + sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Get the value of a given attribute. + * + * @note If the attribute value is longer than the size of the supplied buffer, + * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), + * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. + * The application may use this information to allocate a suitable buffer size. + * + * @note When retrieving system attribute values with this function, the connection handle + * may refer to an already disconnected connection. Refer to the documentation of + * @ref sd_ble_gatts_sys_attr_get for further information. + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + */ +SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, + sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Notify or Indicate an attribute value. + * + * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant + * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before + * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single + * API call. + * + * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during + * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, + * @ref NRF_ERROR_BUSY, + * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. + * The caller can check whether the value has been updated by looking at the contents of *(@ref + * ble_gatts_hvx_params_t::p_len). + * + * @note Only one indication procedure can be ongoing per connection at a time. + * If the application tries to indicate an attribute value while another indication procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. + * + * @note The number of Handle Value Notifications that can be queued is configured by @ref + * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. + * + * @note The application can keep track of the available queue element count for notifications by following the procedure + * below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} + * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_HVN_MSC} + * @mmsc{@ref BLE_GATTS_HVI_MSC} + * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data + * contains a non-NULL pointer the attribute value will be updated with the contents + * pointed by it before sending the notification or indication. If the attribute value + * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to + * contain the number of actual bytes written, else it will be set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute + * value. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application + * are available to notify and indicate. + * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and + * indicated. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions + * of the CCCD associated with this characteristic. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC + * event and retry. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. + * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); + +/**@brief Indicate the Service Changed attribute value. + * + * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute + * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will + * be issued. + * + * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. + * + * @events + * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_SC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] start_handle Start of affected attribute handle range. + * @param[in] end_handle End of affected attribute handle range. + * + * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref + * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the + * application. + * @retval ::NRF_ERROR_BUSY Procedure already in progress. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, + sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); + +/**@brief Respond to a Read/Write authorization request. + * + * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. + * + * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond + * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update + * is set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute + * Table updated. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, + * handle supplied does not match requested handle, + * or invalid data to be written provided by the application. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, + sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); + +/**@brief Update persistent system attribute information. + * + * @details Supply information about persistent system attributes to the stack, + * previously obtained using @ref sd_ble_gatts_sys_attr_get. + * This call is only allowed for active connections, and is usually + * made immediately after a connection is established with an known bonded device, + * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. + * + * p_sysattrs may point directly to the application's stored copy of the system attributes + * obtained using @ref sd_ble_gatts_sys_attr_get. + * If the pointer is NULL, the system attribute info is initialized, assuming that + * the application does not have any previously saved system attribute data for this device. + * + * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. + * + * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may + * have been completed only partially. This means that the state of the attribute table is undefined, and the application should + * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be modified. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be modified. + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. + * @param[in] len Size of data pointed by p_sys_attr_data, in octets. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully set the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref + * sd_ble_gatts_sys_attr_get. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, + sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); + +/**@brief Retrieve persistent system attribute information from the stack. + * + * @details This call is used to retrieve information about values to be stored persistently by the application + * during the lifetime of a connection or after it has been terminated. When a new connection is established with the + * same bonded device, the system attribute information retrieved with this function should be restored using using @ref + * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The + * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API + * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the + * system attributes may be written to at any time by the peer during a connection's lifetime. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be returned. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be returned. + * + * @mscs + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle of the recently terminated connection. + * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The + * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. + * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual + * length of system attribute data. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. + * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, + sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); + +/**@brief Retrieve the first valid user attribute handle. + * + * @param[out] p_handle Pointer to an integer where the handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully retrieved the handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); + +/**@brief Retrieve the attribute UUID and/or metadata. + * + * @param[in] handle Attribute handle + * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. + * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. + * + * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. + * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. + */ +SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); + +/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. + * + * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and + * - The Server RX MTU value. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @mscs + * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] server_rx_mtu Server RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + * used for this connection. + * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent response to the client. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATTS_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_hci.h b/variants/wio-sdk-wm1110/softdevice/ble_hci.h new file mode 100644 index 000000000..27f85d52e --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_hci.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ +*/ + +#ifndef BLE_HCI_H__ +#define BLE_HCI_H__ +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes + * @{ */ + +#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ +/*0x03 Hardware Failure +0x04 Page Timeout +*/ +#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ +#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ +#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ +#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ +/*0x09 Connection Limit Exceeded +0x0A Synchronous Connection Limit To A Device Exceeded +0x0B ACL Connection Already Exists*/ +#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ +/*0x0D Connection Rejected due to Limited Resources +0x0E Connection Rejected Due To Security Reasons +0x0F Connection Rejected due to Unacceptable BD_ADDR +0x10 Connection Accept Timeout Exceeded +0x11 Unsupported Feature or Parameter Value*/ +#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ +#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ + 0x14 /**< Remote Device Terminated Connection due to low \ + resources.*/ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ +#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ +/* +0x17 Repeated Attempts +0x18 Pairing Not Allowed +0x19 Unknown LMP PDU +*/ +#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ +/* +0x1B SCO Offset Rejected +0x1C SCO Interval Rejected +0x1D SCO Air Mode Rejected*/ +#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ +#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ +/*0x20 Unsupported LMP Parameter Value +0x21 Role Change Not Allowed +*/ +#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ +#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ +#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ +/*0x25 Encryption Mode Not Acceptable +0x26 Link Key Can Not be Changed +0x27 Requested QoS Not Supported +*/ +#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ +#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ +#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ +/* +0x2B Reserved +0x2C QoS Unacceptable Parameter +0x2D QoS Rejected +0x2E Channel Classification Not Supported +0x2F Insufficient Security +*/ +#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ +/* +0x31 Reserved +0x32 Role Switch Pending +0x33 Reserved +0x34 Reserved Slot Violation +0x35 Role Switch Failed +0x36 Extended Inquiry Response Too Large +0x37 Secure Simple Pairing Not Supported By Host. +0x38 Host Busy - Pairing +0x39 Connection Rejected due to No Suitable Channel Found*/ +#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ +#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ +#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ +#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ +#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_HCI_H__ + +/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h b/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h new file mode 100644 index 000000000..5f4bd277d --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) + @{ + @brief Definitions and prototypes for the L2CAP interface. + */ + +#ifndef BLE_L2CAP_H__ +#define BLE_L2CAP_H__ + +#include "ble_err.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology + * @{ + * @details + * + * L2CAP SDU + * - A data unit that the application can send/receive to/from a peer. + * + * L2CAP PDU + * - A data unit that is exchanged between local and remote L2CAP entities. + * It consists of L2CAP protocol control information and payload fields. + * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. + * + * L2CAP MTU + * - The maximum length of an L2CAP SDU. + * + * L2CAP MPS + * - The maximum length of an L2CAP PDU payload field. + * + * Credits + * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. + * @} */ + +/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief L2CAP API SVC numbers. */ +enum BLE_L2CAP_SVCS { + SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ + SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ + SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ +}; + +/**@brief L2CAP Event IDs. */ +enum BLE_L2CAP_EVTS { + BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. + \n Reply with @ref sd_ble_l2cap_ch_setup. + \n See @ref ble_l2cap_evt_ch_setup_request_t. */ + BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. + \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ + BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. + \n See @ref ble_l2cap_evt_ch_setup_t. */ + BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. + \n No additional event structure applies. */ + BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. + \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ + BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. + \n See @ref ble_l2cap_evt_ch_credit_t. */ + BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. + \n See @ref ble_l2cap_evt_ch_rx_t. */ + BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. + \n See @ref ble_l2cap_evt_ch_tx_t. */ +}; + +/** @} */ + +/**@addtogroup BLE_L2CAP_DEFINES Defines + * @{ */ + +/**@brief Maximum number of L2CAP channels per connection. */ +#define BLE_L2CAP_CH_COUNT_MAX (64) + +/**@brief Minimum L2CAP MTU, in bytes. */ +#define BLE_L2CAP_MTU_MIN (23) + +/**@brief Minimum L2CAP MPS, in bytes. */ +#define BLE_L2CAP_MPS_MIN (23) + +/**@brief Invalid CID. */ +#define BLE_L2CAP_CID_INVALID (0x0000) + +/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ +#define BLE_L2CAP_CREDITS_DEFAULT (1) + +/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources + * @{ */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ + /** @} */ + +/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes + * @{ */ +#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ +#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ +#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ +#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ +#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ +#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ +#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ + (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ +#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ +/** @} */ + +/** @} */ + +/**@addtogroup BLE_L2CAP_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note These parameters are set per connection, so all L2CAP channels created on this connection + * will have the same parameters. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. + * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. + */ +typedef struct { + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to receive on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to transmit on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per + L2CAP channel. The minimum value is one. */ + uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission + per L2CAP channel. The minimum value is one. */ + uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection + with this configuration. The default value is zero, the maximum + value is @ref BLE_L2CAP_CH_COUNT_MAX. + @note if this parameter is set to zero, all other parameters in + @ref ble_l2cap_conn_cfg_t are ignored. */ +} ble_l2cap_conn_cfg_t; + +/**@brief L2CAP channel RX parameters. */ +typedef struct { + uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to + receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be + able to receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. + - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ + ble_data_t sdu_buf; /**< SDU data buffer for reception. + - If @ref ble_data_t::p_data is non-NULL, initial credits are + issued to the peer. + - If @ref ble_data_t::p_data is NULL, no initial credits are + issued to the peer. */ +} ble_l2cap_ch_rx_params_t; + +/**@brief L2CAP channel setup parameters. */ +typedef struct { + ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting + setup of an L2CAP channel, ignored otherwise. */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. + Used when replying to a setup request of an L2CAP + channel, ignored otherwise. */ +} ble_l2cap_ch_setup_params_t; + +/**@brief L2CAP channel TX parameters. */ +typedef struct { + uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to + transmit on this L2CAP channel. */ + uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is + able to receive on this L2CAP channel. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able + to transmit on this L2CAP channel. This is effective tx_mps, + selected by the SoftDevice as + MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ + uint16_t credits; /**< Initial credits given by the peer. */ +} ble_l2cap_ch_tx_params_t; + +/**@brief L2CAP Channel Setup Request event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ +} ble_l2cap_evt_ch_setup_request_t; + +/**@brief L2CAP Channel Setup Refused event. */ +typedef struct { + uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ +} ble_l2cap_evt_ch_setup_refused_t; + +/**@brief L2CAP Channel Setup Completed event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ +} ble_l2cap_evt_ch_setup_t; + +/**@brief L2CAP Channel SDU Data Buffer Released event. */ +typedef struct { + ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice + returns SDU data buffers supplied by the application, which have + not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or + @ref BLE_L2CAP_EVT_CH_TX event. */ +} ble_l2cap_evt_ch_sdu_buf_released_t; + +/**@brief L2CAP Channel Credit received event. */ +typedef struct { + uint16_t credits; /**< Additional credits given by the peer. */ +} ble_l2cap_evt_ch_credit_t; + +/**@brief L2CAP Channel received SDU event. */ +typedef struct { + uint16_t sdu_len; /**< Total SDU length, in bytes. */ + ble_data_t sdu_buf; /**< SDU data buffer. + @note If there is not enough space in the buffer + (sdu_buf.len < sdu_len) then the rest of the SDU will be + silently discarded by the SoftDevice. */ +} ble_l2cap_evt_ch_rx_t; + +/**@brief L2CAP Channel transmitted SDU event. */ +typedef struct { + ble_data_t sdu_buf; /**< SDU data buffer. */ +} ble_l2cap_evt_ch_tx_t; + +/**@brief L2CAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occured. */ + uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or + @ref BLE_L2CAP_CID_INVALID if not present. */ + union { + ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ + ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ + ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ + ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ + ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ + ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ + ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_l2cap_evt_t; + +/** @} */ + +/**@addtogroup BLE_L2CAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set up an L2CAP channel. + * + * @details This function is used to: + * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. + * - Reply to a setup request of an L2CAP channel (if called in response to a + * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection + * Response packet to a peer. + * + * @note A call to this function will require the application to keep the SDU data buffer alive + * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or + * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} + * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: + * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP + * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * event when replying to a setup request of an L2CAP channel. + * - As output: local_cid for this channel. + * @param[in] p_params L2CAP channel parameters. + * + * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, + * see @ref ble_l2cap_conn_cfg_t::ch_count. + */ +SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, + sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); + +/**@brief Release an L2CAP channel. + * + * @details This sends a Disconnection Request packet to a peer. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * + * @retval ::NRF_SUCCESS Successfully queued request for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); + +/**@brief Receive an SDU on an L2CAP channel. + * + * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers + * for reception per L2CAP channel. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Buffer accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a + * @ref BLE_L2CAP_EVT_CH_RX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Transmit an SDU on an L2CAP channel. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for + * transmission per L2CAP channel. + * + * @note The application can keep track of the available credits for transmission by following + * the procedure below: + * - Store initial credits given by the peer in a variable. + * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Decrement the variable, which stores the currently available credits, by + * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns + * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Increment the variable, which stores the currently available credits, by additional + * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than + * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in + * @ref BLE_L2CAP_EVT_CH_SETUP event. + * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a + * @ref BLE_L2CAP_EVT_CH_TX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Advanced SDU reception flow control. + * + * @details Adjust the way the SoftDevice issues credits to the peer. + * This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set + * the value that will be used for newly created channels. + * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every + * time it starts using a new reception buffer. + * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will + * use if this function is not called. + * - If set to zero, the SoftDevice will stop issuing credits for new reception + * buffers the application provides or has provided. SDU reception that is + * currently ongoing will be allowed to complete. + * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be + * written by the SoftDevice with the number of credits that is or will be + * available to the peer. If the value written by the SoftDevice is 0 when + * credits parameter was set to 0, the peer will not be able to send more + * data until more credits are provided by calling this function again with + * credits > 0. This parameter is ignored when local_cid is set to + * @ref BLE_L2CAP_CID_INVALID. + * + * @note Application should take care when setting number of credits higher than default value. In + * this case the application must make sure that the SoftDevice always has reception buffers + * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have + * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic + * on the connection handle may be stalled until the SoftDevice again has an available + * reception buffer. This applies even if the application has used this call to set the + * credits back to default, or zero. + * + * @retval ::NRF_SUCCESS Flow control parameters accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, + sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_L2CAP_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_ranges.h b/variants/wio-sdk-wm1110/softdevice/ble_ranges.h new file mode 100644 index 000000000..2768e4996 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_ranges.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_ranges Module specific SVC, event and option number subranges + @{ + + @brief Definition of SVC, event and option number subranges for each API module. + + @note + SVCs, event and option numbers are split into subranges for each API module. + Each module receives its entire allocated range of SVC calls, whether implemented or not, + but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. + + Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, + rather than the last SVC function call actually defined and implemented. + + Specific SVC, event and option values are defined in each module's ble_.h file, + which defines names of each individual SVC code based on the range start value. +*/ + +#ifndef BLE_RANGES_H__ +#define BLE_RANGES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ +#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ + +#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ +#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ + +#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ +#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ + +#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ +#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ + +#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ +#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ + +#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ + +#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ +#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ + +#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ +#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ + +#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ +#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ + +#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ +#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ + +#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ +#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ + +#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ + +#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ +#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ + +#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ +#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ + +#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ +#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ + +#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ +#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ + +#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ +#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ + +#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ +#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ + +#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ + +#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ +#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ + +#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ +#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ + +#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ +#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ + +#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ +#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ + +#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ +#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ + +#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ +#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ + +#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ +#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_RANGES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_types.h b/variants/wio-sdk-wm1110/softdevice/ble_types.h new file mode 100644 index 000000000..db3656cfd --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_types.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_types Common types and macro definitions + @{ + + @brief Common types and macro definitions for the BLE SoftDevice. + */ + +#ifndef BLE_TYPES_H__ +#define BLE_TYPES_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_TYPES_DEFINES Defines + * @{ */ + +/** @defgroup BLE_CONN_HANDLES BLE Connection Handles + * @{ */ +#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ +#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ +/** @} */ + +/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs + * @{ */ +/* Generic UUIDs, applicable to all services */ +#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ +#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ +#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ +#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ +#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ +#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ +/* GATT specific UUIDs */ +#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ +#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ +/* GAP specific UUIDs */ +#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ +#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ +/** @} */ + +/** @defgroup BLE_UUID_TYPES Types of UUID + * @{ */ +#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ +#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ +#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ +/** @} */ + +/** @defgroup BLE_APPEARANCES Bluetooth Appearance values + * @note Retrieved from + * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * @{ */ +#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ +#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ +#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ +#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ +#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ +#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ +#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ +#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ +#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ +#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ +#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ +#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ +#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ +#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ +#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ +#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ +#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ +#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ +#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ +#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ +#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ +#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ +#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ +#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ +#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ +#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ +#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ +#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ +#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ +#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ +#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ +#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ +#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ +#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ +#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ +#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ + 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ + 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ +/** @} */ + +/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ +#define BLE_UUID_BLE_ASSIGN(instance, value) \ + do { \ + instance.type = BLE_UUID_TYPE_BLE; \ + instance.uuid = value; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ +#define BLE_UUID_COPY_PTR(dst, src) \ + do { \ + (dst)->type = (src)->type; \ + (dst)->uuid = (src)->uuid; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ +#define BLE_UUID_COPY_INST(dst, src) \ + do { \ + (dst).type = (src).type; \ + (dst).uuid = (src).uuid; \ + } while (0) + +/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) + +/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) + +/** @} */ + +/** @addtogroup BLE_TYPES_STRUCTURES Structures + * @{ */ + +/** @brief 128 bit UUID values. */ +typedef struct { + uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ +} ble_uuid128_t; + +/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ +typedef struct { + uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ + uint8_t + type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ +} ble_uuid_t; + +/**@brief Data structure. */ +typedef struct { + uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ + uint16_t len; /**< Length of the data buffer, in bytes. */ +} ble_data_t; + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* BLE_TYPES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h new file mode 100644 index 000000000..4e0bd752a --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_mbr_api Master Boot Record API + @{ + + @brief APIs for updating SoftDevice and BootLoader + +*/ + +#ifndef NRF_MBR_H__ +#define NRF_MBR_H__ + +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_MBR_DEFINES Defines + * @{ */ + +/**@brief MBR SVC Base number. */ +#define MBR_SVC_BASE (0x18) + +/**@brief Page size in words. */ +#define MBR_PAGE_SIZE_IN_WORDS (1024) + +/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. +This is the offset where the first byte of the SoftDevice hex file is written. */ +#define MBR_SIZE (0x1000) + +/** @brief Location (in the flash memory) of the bootloader address. */ +#define MBR_BOOTLOADER_ADDR (0xFF8) + +/** @brief Location (in UICR) of the bootloader address. */ +#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) + +/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ +#define MBR_PARAM_PAGE_ADDR (0xFFC) + +/** @brief Location (in UICR) of the address of the MBR parameter page. */ +#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) + +/** @} */ + +/** @addtogroup NRF_MBR_ENUMS Enumerations + * @{ */ + +/**@brief nRF Master Boot Record API SVC numbers. */ +enum NRF_MBR_SVCS { + SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ +}; + +/**@brief Possible values for ::sd_mbr_command_t.command */ +enum NRF_MBR_COMMANDS { + SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ + SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ + SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any + parameters in ::sd_mbr_command_t params.*/ + SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ + SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see + ::sd_mbr_command_vector_table_base_set_t*/ + SD_MBR_COMMAND_RESERVED, + SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see + ::sd_mbr_command_irq_forward_address_set_t*/ +}; + +/** @} */ + +/** @addtogroup NRF_MBR_TYPES Types + * @{ */ + +/**@brief This command copies part of a new SoftDevice + * + * The destination area is erased before copying. + * If dst is in the middle of a flash page, that whole flash page will be erased. + * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. + * + * The user of this function is responsible for setting the BPROT registers. + * + * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. + * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. + */ +typedef struct { + uint32_t *src; /**< Pointer to the source of data to be copied.*/ + uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ + uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ +} sd_mbr_command_copy_sd_t; + +/**@brief This command works like memcmp, but takes the length in words. + * + * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. + * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. + */ +typedef struct { + uint32_t *ptr1; /**< Pointer to block of memory. */ + uint32_t *ptr2; /**< Pointer to block of memory. */ + uint32_t len; /**< Number of 32 bit words to compare.*/ +} sd_mbr_command_compare_t; + +/**@brief This command copies a new BootLoader. + * + * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to + * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * The bootloader destination is erased by this function. + * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is + * not intended to be written. + * + * On success, this function will not return. It will start the new bootloader from reset-vector as normal. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. + * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ + uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ +} sd_mbr_command_copy_bl_t; + +/**@brief Change the address the MBR starts after a reset + * + * Once this function has been called, this address is where the MBR will start to forward + * interrupts to after a reset. + * + * To restore default forwarding, this function should be called with @ref address set to 0. If a + * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will + * be forwarded to the SoftDevice. + * + * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or + * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * On success, this function will not return. It will reset the device. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_vector_table_base_set_t; + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR + * + * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not + * change where the MBR starts after reset. + * + * @retval ::NRF_SUCCESS + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_irq_forward_address_set_t; + +/**@brief Input structure containing data used when calling ::sd_mbr_command + * + * Depending on what command value that is set, the corresponding params value type must also be + * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command + * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. + */ +typedef struct { + uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ + union { + sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ + sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ + sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ + sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ + sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ + } params; /**< Command parameters. */ +} sd_mbr_command_t; + +/** @} */ + +/** @addtogroup NRF_MBR_FUNCTIONS Functions + * @{ */ + +/**@brief Issue Master Boot Record commands + * + * Commands used when updating a SoftDevice and bootloader. + * + * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires + * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash + * page. The location of the flash page should be provided by the application in either + * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR + * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to + * store the command before reset. When an address is specified, the page it refers to must not be + * used by the application. If no address is provided by the application, i.e. both + * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use + * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. + * + * @param[in] param Pointer to a struct describing the command. + * + * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, + * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, + * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t + * + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided + * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. + */ +SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_MBR_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error.h b/variants/wio-sdk-wm1110/softdevice/nrf_error.h new file mode 100644 index 000000000..fb2831e19 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_error.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_error SoftDevice Global Error Codes + @{ + + @brief Global Error definitions +*/ + +/* Header guard */ +#ifndef NRF_ERROR_H__ +#define NRF_ERROR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions + * @{ */ +#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base +#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base +#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base +#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base +/** @} */ + +#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command +#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing +#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled +#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error +#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation +#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found +#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported +#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter +#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state +#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length +#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags +#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data +#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size +#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out +#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer +#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation +#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address +#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy +#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. +#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h new file mode 100644 index 000000000..2fd621057 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_sdm_api + @{ + @defgroup nrf_sdm_error SoftDevice Manager Error Codes + @{ + + @brief Error definitions for the SDM API +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SDM_H__ +#define NRF_ERROR_SDM_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. +#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ + (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having + ///< enabled SoftDevice interrupts). +#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ + (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SDM_H__ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h new file mode 100644 index 000000000..cbd0ba8ac --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_soc_api + @{ + @defgroup nrf_soc_error SoC Library Error Codes + @{ + + @brief Error definitions for the SoC library + +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SOC_H__ +#define NRF_ERROR_SOC_H__ + +#include "nrf_error.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* Mutex Errors */ +#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken + +/* NVIC errors */ +#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available +#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed +#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return + +/* Power errors */ +#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown +#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown +#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return + +/* Rand errors */ +#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values + +/* PPI errors */ +#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel +#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SOC_H__ +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h b/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h new file mode 100644 index 000000000..d4ab204d9 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_nvic_api SoftDevice NVIC API + * @{ + * + * @note In order to use this module, the following code has to be added to a .c file: + * \code + * nrf_nvic_state_t nrf_nvic_state = {0}; + * \endcode + * + * @note Definitions and declarations starting with __ (double underscore) in this header file are + * not intended for direct use by the application. + * + * @brief APIs for the accessing NVIC when using a SoftDevice. + * + */ + +#ifndef NRF_NVIC_H__ +#define NRF_NVIC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_NVIC_DEFINES Defines + * @{ */ + +/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions + * @{ */ + +#define __NRF_NVIC_NVMC_IRQn \ + (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ + number in the MDK. */ + +#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ + +/**@brief Interrupt priority levels used by the SoftDevice. */ +#define __NRF_NVIC_SD_IRQ_PRIOS \ + ((uint8_t)((1U << 0) /**< Priority level high .*/ \ + | (1U << 1) /**< Priority level medium. */ \ + | (1U << 4) /**< Priority level low. */ \ + )) + +/**@brief Interrupt priority levels available to the application. */ +#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ +#define __NRF_NVIC_SD_IRQS_0 \ + ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ + (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ + (1U << (uint32_t)SWI5_IRQn))) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ +#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) + +/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ +#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) + +/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ +#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) + +/**@} */ + +/**@} */ + +/**@addtogroup NRF_NVIC_VARIABLES Variables + * @{ */ + +/**@brief Type representing the state struct for the SoftDevice NVIC module. */ +typedef struct { + uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ + uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ +} nrf_nvic_state_t; + +/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an + * application source file. */ +extern nrf_nvic_state_t nrf_nvic_state; + +/**@} */ + +/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions + * @{ */ + +/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. + * + * @retval The value of PRIMASK prior to disabling the interrupts. + */ +__STATIC_INLINE int __sd_nvic_irq_disable(void); + +/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. + */ +__STATIC_INLINE void __sd_nvic_irq_enable(void); + +/**@brief Checks if IRQn is available to application + * @param[in] IRQn IRQ to check + * + * @retval 1 (true) if the IRQ to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); + +/**@brief Checks if priority is available to application + * @param[in] priority priority to check + * + * @retval 1 (true) if the priority to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); + +/**@} */ + +/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions + * @{ */ + +/**@brief Enable External Interrupt. + * @note Corresponds to NVIC_EnableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was enabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); + +/**@brief Disable External Interrupt. + * @note Corresponds to NVIC_DisableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was disabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); + +/**@brief Get Pending Interrupt. + * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. + * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. + * + * @retval ::NRF_SUCCESS The interrupt is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); + +/**@brief Set Pending Interrupt. + * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt is set pending. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); + +/**@brief Clear Pending Interrupt. + * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); + +/**@brief Set Interrupt Priority. + * @note Corresponds to NVIC_SetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * @pre Priority is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. + * @param[in] priority A valid IRQ priority for use by the application. + * + * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); + +/**@brief Get Interrupt Priority. + * @note Corresponds to NVIC_GetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. + * @param[out] p_priority Return value from NVIC_GetPriority. + * + * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); + +/**@brief System Reset. + * @note Corresponds to NVIC_SystemReset in CMSIS. + * + * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN + */ +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); + +/**@brief Enter critical region. + * + * @post Application interrupts will be disabled. + * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each + * execution context + * @sa sd_nvic_critical_region_exit + * + * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); + +/**@brief Exit critical region. + * + * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. + * @post If not in a nested critical region, the application interrupts will restored to the state before + * ::sd_nvic_critical_region_enter was called. + * + * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa + * sd_nvic_critical_region_enter. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); + +/**@} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE int __sd_nvic_irq_disable(void) +{ + int pm = __get_PRIMASK(); + __disable_irq(); + return pm; +} + +__STATIC_INLINE void __sd_nvic_irq_enable(void) +{ + __enable_irq(); +} + +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) +{ + if (IRQn < 32) { + return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; + } else if (IRQn < 64) { + return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; + } else { + return 1; + } +} + +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) +{ + if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { + return 0; + } + return 1; +} + +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= + (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); + } else { + NVIC_EnableIRQ(IRQn); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); + } else { + NVIC_DisableIRQ(IRQn); + } + + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_pending_irq = NVIC_GetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_SetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_ClearPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (!__sd_nvic_is_app_accessible_priority(priority)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + NVIC_SetPriority(IRQn, (uint32_t)priority); + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) +{ + NVIC_SystemReset(); + return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) +{ + int was_masked = __sd_nvic_irq_disable(); + if (!nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__cr_flag = 1; + nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); + NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; + nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); + NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; + *p_is_nested_critical_region = 0; + } else { + *p_is_nested_critical_region = 1; + } + if (!was_masked) { + __sd_nvic_irq_enable(); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) +{ + if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { + int was_masked = __sd_nvic_irq_disable(); + NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; + NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; + nrf_nvic_state.__cr_flag = 0; + if (!was_masked) { + __sd_nvic_irq_enable(); + } + } + + return NRF_SUCCESS; +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif + +#endif // NRF_NVIC_H__ + +/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h new file mode 100644 index 000000000..2786a86a4 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_sdm_api SoftDevice Manager API + @{ + + @brief APIs for SoftDevice management. + +*/ + +#ifndef NRF_SDM_H__ +#define NRF_SDM_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_sdm.h" +#include "nrf_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ +#ifdef NRFSOC_DOXYGEN +/// Declared in nrf_mbr.h +#define MBR_SIZE 0 +#warning test +#endif + +/** @brief The major version for the SoftDevice binary distributed with this header file. */ +#define SD_MAJOR_VERSION (7) + +/** @brief The minor version for the SoftDevice binary distributed with this header file. */ +#define SD_MINOR_VERSION (3) + +/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ +#define SD_BUGFIX_VERSION (0) + +/** @brief The SoftDevice variant of this firmware. */ +#define SD_VARIANT_ID 140 + +/** @brief The full version number for the SoftDevice binary this header file was distributed + * with, as a decimal number in the form Mmmmbbb, where: + * - M is major version (one or more digits) + * - mmm is minor version (three digits) + * - bbb is bugfix version (three digits). */ +#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) + +/** @brief SoftDevice Manager SVC Base number. */ +#define SDM_SVC_BASE 0x10 + +/** @brief SoftDevice unique string size in bytes. */ +#define SD_UNIQUE_STR_SIZE 20 + +/** @brief Invalid info field. Returned when an info field does not exist. */ +#define SDM_INFO_FIELD_INVALID (0) + +/** @brief Defines the SoftDevice Information Structure location (address) as an offset from +the start of the SoftDevice (without MBR)*/ +#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) + +/** @brief Defines the absolute SoftDevice Information Structure location (address) when the + * SoftDevice is installed just above the MBR (the usual case). */ +#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) + +/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the + * SoftDevice base address. The size value is of type uint8_t. */ +#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) + +/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. + * The size value is of type uint32_t. */ +#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) + +/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value + * is of type uint16_t. */ +#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) + +/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID + * is of type uint32_t. */ +#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) + +/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in + * the same format as @ref SD_VERSION, stored as an uint32_t. */ +#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) + +/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. + * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. + */ +#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) + +/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value + * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is + * installed just above the MBR (the usual case). */ +#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base + * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above + * the MBR (the usual case). */ +#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) + +/** @brief Defines the amount of flash that is used by the SoftDevice. + * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed + * just above the MBR (the usual case). + */ +#define SD_FLASH_SIZE 0x26000 + +/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual + * case). */ +#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the + * usual case). */ +#define SD_ID_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_VERSION_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges + * @{ */ +#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ +#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ +/**@} */ + +/**@defgroup NRF_FAULT_IDS Fault ID types + * @{ */ +#define NRF_FAULT_ID_SD_ASSERT \ + (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ +#define NRF_FAULT_ID_APP_MEMACC \ + (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ + in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ + register violation the info parameter will contain the sub-region number of \ + PREGION[0], on whose address range the disallowed write access caused the \ + memory access fault. */ +/**@} */ + +/** @} */ + +/** @addtogroup NRF_SDM_ENUMS Enumerations + * @{ */ + +/**@brief nRF SoftDevice Manager API SVC numbers. */ +enum NRF_SD_SVCS { + SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ + SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ + SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ + SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ + SVC_SDM_LAST /**< Placeholder for last SDM SVC */ +}; + +/** @} */ + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ + +/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy + * @{ */ + +#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ +#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ +#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ +#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ +#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ +#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ +#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ +#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ +#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ +#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ +#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ +#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ + +/** @} */ + +/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources + * @{ */ + +#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ +#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ +#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ + +/** @} */ + +/** @} */ + +/** @addtogroup NRF_SDM_TYPES Types + * @{ */ + +/**@brief Type representing LFCLK oscillator source. */ +typedef struct { + uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ + uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second + units (nRF52: 1-32). + @note To avoid excessive clock drift, 0.5 degrees Celsius is the + maximum temperature change allowed in one calibration timer + interval. The interval should be selected to ensure this. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ + uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration + intervals) the RC oscillator shall be calibrated if the temperature + hasn't changed. + 0: Always calibrate even if the temperature hasn't changed. + 1: Only calibrate if the temperature has changed (legacy - nRF51 only). + 2-33: Check the temperature and only calibrate if it has changed, + however calibration will take place every rc_temp_ctiv + intervals in any case. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. + + @note For nRF52, the application must ensure calibration at least once + every 8 seconds to ensure +/-500 ppm clock stability. The + recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is + rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at + least once every 8 seconds and for temperature changes of 0.5 + degrees Celsius every 4 seconds. See the Product Specification + for the nRF52 device being used for more information.*/ + uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing + windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ +} nrf_clock_lf_cfg_t; + +/**@brief Fault Handler type. + * + * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. + * The protocol stack will be in an undefined state when this happens and the only way to recover will be to + * perform a reset, using e.g. CMSIS NVIC_SystemReset(). + * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). + * + * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. + * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may + * continously transmit packets. + * + * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. + * + * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. + * @param[in] pc The program counter of the instruction that triggered the fault. + * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. + * + * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time + * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the + * one that triggered the fault. + */ +typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); + +/** @} */ + +/** @addtogroup NRF_SDM_FUNCTIONS Functions + * @{ */ + +/**@brief Enables the SoftDevice and by extension the protocol stack. + * + * @note Some care must be taken if a low frequency clock source is already running when calling this function: + * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new + * clock source will be started. + * + * @note This function has no effect when returning with an error. + * + * @post If return code is ::NRF_SUCCESS + * - SoC library and protocol stack APIs are made available. + * - A portion of RAM will be unavailable (see relevant SDS documentation). + * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). + * - Interrupts will not arrive from protected peripherals or interrupts. + * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. + * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). + * - Chosen low frequency clock source will be running. + * + * @param p_clock_lf_cfg Low frequency clock source and accuracy. + If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 + In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to + the actual characteristics of your XTAL clock. + * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. + * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has + an illegal priority level. + * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. + */ +SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, + sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); + +/**@brief Disables the SoftDevice and by extension the protocol stack. + * + * Idempotent function to disable the SoftDevice. + * + * @post SoC library and protocol stack APIs are made unavailable. + * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). + * @post All peripherals used by the SoftDevice will be reset to default values. + * @post All of RAM become available. + * @post All interrupts are forwarded to the application. + * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); + +/**@brief Check if the SoftDevice is enabled. + * + * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice + * + * This function is only intended to be called when a bootloader is enabled. + * + * @param[in] address The base address of the interrupt vector table for forwarded interrupts. + + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SDM_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_soc.h new file mode 100644 index 000000000..c649ca836 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_soc.h @@ -0,0 +1,1046 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_soc_api SoC Library API + * @{ + * + * @brief APIs for the SoC library. + * + */ + +#ifndef NRF_SOC_H__ +#define NRF_SOC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_SOC_DEFINES Defines + * @{ */ + +/**@brief The number of the lowest SVC number reserved for the SoC library. */ +#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ +#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ + +/**@brief Guaranteed time for application to process radio inactive notification. */ +#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) + +/**@brief The minimum allowed timeslot extension time. */ +#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) + +/**@brief The maximum processing time to handle a timeslot extension. */ +#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) + +/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ +#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) + +#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ +#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ +#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ + +#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ +#define SD_EVT_IRQHandler \ + (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ + The default interrupt priority for this handler is set to 6 */ +#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ +#define RADIO_NOTIFICATION_IRQHandler \ + (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ + The default interrupt priority for this handler is set to 6 */ +#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ +#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ + +#define NRF_RADIO_DISTANCE_MAX_US \ + (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ + nrf_radio_request_normal_t) in the request. */ + +#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ + (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ + +#define NRF_RADIO_START_JITTER_US \ + (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ + ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ + (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) + +/**@} */ + +/**@addtogroup NRF_SOC_ENUMS Enumerations + * @{ */ + +/**@brief The SVC numbers used by the SVC functions in the SoC library. */ +enum NRF_SOC_SVCS { + SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, + SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, + SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, + SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, + SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, + SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, + SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, + SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, + SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, + SD_FLASH_WRITE = SOC_SVC_BASE + 9, + SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, + SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, + SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, + SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, + SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, + SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, + SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, + SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, + SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, + SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, + SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, + SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, + SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, + SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, + SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, + SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, + SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, + SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, + SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, + SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, + SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, + SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, + SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, + SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, + SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, + SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, + SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, + SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, + SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, + SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, + SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, + SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, + SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, + SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, + SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, + SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, + SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, + SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, + SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 +}; + +/**@brief Possible values of a ::nrf_mutex_t. */ +enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; + +/**@brief Power modes. */ +enum NRF_POWER_MODES { + NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ + NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ +}; + +/**@brief Power failure thresholds */ +enum NRF_POWER_THRESHOLDS { + NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ +}; + +/**@brief Power failure thresholds for high voltage */ +enum NRF_POWER_THRESHOLDVDDHS { + NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ +}; + +/**@brief DC/DC converter modes. */ +enum NRF_POWER_DCDC_MODES { + NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ + NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ +}; + +/**@brief Radio notification distances. */ +enum NRF_RADIO_NOTIFICATION_DISTANCES { + NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ + NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ +}; + +/**@brief Radio notification types. */ +enum NRF_RADIO_NOTIFICATION_TYPES { + NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and + disabled. */ +}; + +/**@brief The Radio signal callback types. */ +enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { + NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ +}; + +/**@brief The actions requested by the signal callback. + * + * This code gives the SOC instructions about what action to take when the signal callback has + * returned. + */ +enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { + NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current + timeslot. Maximum execution time for this action: + @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. + This action must be started at least + @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before + the end of the timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ +}; + +/**@brief Radio timeslot high frequency clock source configuration. */ +enum NRF_RADIO_HFCLK_CFG { + NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the + external crystal for the whole duration of the timeslot. This should be the + preferred option for events that use the radio or require high timing accuracy. + @note The SoftDevice will automatically turn on and off the external crystal, + at the beginning and end of the timeslot, respectively. The crystal may also + intentionally be left running after the timeslot, in cases where it is needed + by the SoftDevice shortly after the end of the timeslot. */ + NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. + The RC oscillator may be the clock source in part or for the whole duration of the + timeslot. The RC oscillator's accuracy must therefore be taken into consideration. + @note If the application will use the radio peripheral in timeslots with this + configuration, it must make sure that the crystal is running and stable before + starting the radio. */ +}; + +/**@brief Radio timeslot priorities. */ +enum NRF_RADIO_PRIORITY { + NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ + NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ +}; + +/**@brief Radio timeslot request type. */ +enum NRF_RADIO_REQUEST_TYPE { + NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first + request in a session. */ + NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ +}; + +/**@brief SoC Events. */ +enum NRF_SOC_EVTS { + NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ + NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ + NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ + NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ + NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ + NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ + NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was + invalid. */ + NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ + NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ + NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ + NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ + NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ + NRF_EVT_NUMBER_OF_EVTS +}; + +/**@} */ + +/**@addtogroup NRF_SOC_STRUCTURES Structures + * @{ */ + +/**@brief Represents a mutex for use with the nrf_mutex functions. + * @note Accessing the value directly is not safe, use the mutex functions! + */ +typedef volatile uint8_t nrf_mutex_t; + +/**@brief Parameters for a request for a timeslot as early as possible. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ + uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref + NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ +} nrf_radio_request_earliest_t; + +/**@brief Parameters for a normal radio timeslot request. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US + microseconds). */ + uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ +} nrf_radio_request_normal_t; + +/**@brief Radio timeslot request parameters. */ +typedef struct { + uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ + union { + nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ + nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ + } params; /**< Parameter union. */ +} nrf_radio_request_t; + +/**@brief Return parameters of the radio timeslot signal callback. */ +typedef struct { + uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref + NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ + union { + struct { + nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ + } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ + struct { + uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref + NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ + } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ + } params; /**< Parameter union. */ +} nrf_radio_signal_callback_return_param_t; + +/**@brief The radio timeslot signal callback type. + * + * @note In case of invalid return parameters, the radio timeslot will automatically end + * immediately after returning from the signal callback and the + * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. + * @note The returned struct pointer must remain valid after the signal callback + * function returns. For instance, this means that it must not point to a stack variable. + * + * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. + * + * @return Pointer to structure containing action requested by the application. + */ +typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); + +/**@brief AES ECB parameter typedefs */ +typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ +typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ +typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ + +/**@brief AES ECB data structure */ +typedef struct { + soc_ecb_key_t key; /**< Encryption key. */ + soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ + soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ +} nrf_ecb_hal_data_t; + +/**@brief AES ECB block. Used to provide multiple blocks in a single call + to @ref sd_ecb_blocks_encrypt.*/ +typedef struct { + soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ + soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ + soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ +} nrf_ecb_hal_data_block_t; + +/**@} */ + +/**@addtogroup NRF_SOC_FUNCTIONS Functions + * @{ */ + +/**@brief Initialize a mutex. + * + * @param[in] p_mutex Pointer to the mutex to initialize. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); + +/**@brief Attempt to acquire a mutex. + * + * @param[in] p_mutex Pointer to the mutex to acquire. + * + * @retval ::NRF_SUCCESS The mutex was successfully acquired. + * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. + */ +SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); + +/**@brief Release a mutex. + * + * @param[in] p_mutex Pointer to the mutex to release. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); + +/**@brief Query the capacity of the application random pool. + * + * @param[out] p_pool_capacity The capacity of the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); + +/**@brief Get number of random bytes available to the application. + * + * @param[out] p_bytes_available The number of bytes currently available in the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); + +/**@brief Get random bytes from the application pool. + * + * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. + * @param[in] length Number of bytes to take from pool and place in p_buff. + * + * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. + * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes + * available. + */ +SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); + +/**@brief Gets the reset reason register. + * + * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); + +/**@brief Clears the bits of the reset reason register. + * + * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); + +/**@brief Sets the power mode when in CPU sleep. + * + * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait + * + * @retval ::NRF_SUCCESS The power mode was set. + * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. + */ +SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); + +/**@brief Puts the chip in System OFF mode. + * + * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN + */ +SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); + +/**@brief Enables or disables the power-fail comparator. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); + +/**@brief Enables or disables the USB power ready event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); + +/**@brief Enables or disables the power USB-detected event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); + +/**@brief Enables or disables the power USB-removed event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); + +/**@brief Get USB supply status register content. + * + * @param[out] usbregstatus The content of USBREGSTATUS register. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); + +/**@brief Sets the power failure comparator threshold value. + * + * @note: Power failure comparator threshold setting. This setting applies both for normal voltage + * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to + * VDDH only). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); + +/**@brief Sets the power failure comparator threshold value for high voltage. + * + * @note: Power failure comparator threshold setting for high voltage mode (supply connected to + * VDDH only). This setting does not apply for normal voltage mode (supply connected to both + * VDD and VDDH). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. + * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. + * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); + +/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. + * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); + +/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be set in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[out] p_gpregret Contents of the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). + * + * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. + */ +SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). + * + * For more details on the REG0 stage, please see product specification. + * + * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. + */ +SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); + +/**@brief Request the high frequency crystal oscillator. + * + * Will start the high frequency crystal oscillator, the startup time of the crystal varies + * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_release + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); + +/**@brief Releases the high frequency crystal oscillator. + * + * Will stop the high frequency crystal oscillator, this happens immediately. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_request + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); + +/**@brief Checks if the high frequency crystal oscillator is running. + * + * @see sd_clock_hfclk_request + * @see sd_clock_hfclk_release + * + * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); + +/**@brief Waits for an application event. + * + * An application event is either an application interrupt or a pended interrupt when the interrupt + * is disabled. + * + * When the application waits for an application event by calling this function, an interrupt that + * is enabled will be taken immediately on pending since this function will wait in thread mode, + * then the execution will return in the application's main thread. + * + * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M + * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets + * pended, this function will return to the application's main thread. + * + * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ + * in order to sleep using this function. This is only necessary for disabled interrupts, as + * the interrupt handler will clear the pending flag automatically for enabled interrupts. + * + * @note If an application interrupt has happened since the last time sd_app_evt_wait was + * called this function will return immediately and not go to sleep. This is to avoid race + * conditions that can occur when a flag is updated in the interrupt handler and processed + * in the main loop. + * + * @post An application interrupt has happened or a interrupt pending flag is set. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); + +/**@brief Get PPI channel enable register contents. + * + * @param[out] p_channel_enable The contents of the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); + +/**@brief Set PPI channel enable register. + * + * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); + +/**@brief Clear PPI channel enable register. + * + * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); + +/**@brief Assign endpoints to a PPI channel. + * + * @param[in] channel_num Number of the PPI channel to assign. + * @param[in] evt_endpoint Event endpoint of the PPI channel. + * @param[in] task_endpoint Task endpoint of the PPI channel. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, + sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); + +/**@brief Task to enable a channel group. + * + * @param[in] group_num Number of the channel group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); + +/**@brief Task to disable a channel group. + * + * @param[in] group_num Number of the PPI group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); + +/**@brief Assign PPI channels to a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[in] channel_msk Mask of the channels to assign to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); + +/**@brief Gets the PPI channels of a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[out] p_channel_msk Mask of the channels assigned to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); + +/**@brief Configures the Radio Notification signal. + * + * @note + * - The notification signal latency depends on the interrupt priority settings of SWI used + * for notification signal. + * - To ensure that the radio notification signal behaves in a consistent way, the radio + * notifications must be configured when there is no protocol stack or other SoftDevice + * activity in progress. It is recommended that the radio notification signal is + * configured directly after the SoftDevice has been enabled. + * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice + * will interrupt the application to do Radio Event preparation. + * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have + * to shorten the connection events to have time for the Radio Notification signals. + * + * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio + * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is + * recommended (but not required) to be used with + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. + * + * @param[in] distance Distance between the notification signal and start of radio activity, see @ref + * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or + * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. + * + * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. + * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all + * running activities and retry. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); + +/**@brief Encrypts a block according to the specified parameters. + * + * 128-bit AES encryption. + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input + * parameters and one output parameter). + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); + +/**@brief Encrypts multiple data blocks provided as an array of data block structures. + * + * @details: Performs 128-bit AES encryption on multiple data blocks + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in] block_count Count of blocks in the p_data_blocks array. + * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of + * @ref nrf_ecb_hal_data_block_t structures. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); + +/**@brief Gets any pending events generated by the SoC API. + * + * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. + * + * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. + * + * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. + * @retval ::NRF_ERROR_NOT_FOUND No pending events. + */ +SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); + +/**@brief Get the temperature measured on the chip + * + * This function will block until the temperature measurement is done. + * It takes around 50 us from call to return. + * + * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. + * + * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp + */ +SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); + +/**@brief Flash Write + * + * Commands to write a buffer to flash + * + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * write has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS + * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. + * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is + * protected. + * + * + * @param[in] p_dst Pointer to start of flash location to be written. + * @param[in] p_src Pointer to buffer with data to be written. + * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one + * flash page. See the device's Product Specification for details. + * + * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. + * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); + +/**@brief Flash Erase page + * + * Commands to erase a flash page + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * erase has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is + * protected. + * + * + * @param[in] page_number Page number of the page to erase + * + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); + +/**@brief Opens a session for radio timeslot requests. + * + * @note Only one session can be open at a time. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot + * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed + * by the application. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 + * interrupt occurs. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO + * interrupt occurs. + * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This + * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). + * + * @param[in] p_radio_signal_callback The signal callback. + * + * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. + * @retval ::NRF_ERROR_BUSY If session cannot be opened. + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); + +/**@brief Closes a session for radio timeslot requests. + * + * @note Any current radio timeslot will be finished before the session is closed. + * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. + * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED + * event is received. + * + * @retval ::NRF_ERROR_FORBIDDEN If session not opened. + * @retval ::NRF_ERROR_BUSY If session is currently being closed. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); + +/**@brief Requests a radio timeslot. + * + * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST + * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref + * NRF_RADIO_REQ_TYPE_EARLIEST. + * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by + * p_request->distance_us and is given relative to the start of the previous timeslot. + * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. + * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this + * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. + * The application may then try to schedule the first radio timeslot again. + * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). + * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. + * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. + * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the + * specified radio timeslot start, but this does not affect the actual start time of the timeslot. + * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency + * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is + * guaranteed to be clocked from the external crystal. + * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral + * during the radio timeslot. + * + * @param[in] p_request Pointer to the request parameters. + * + * @retval ::NRF_ERROR_FORBIDDEN Either: + * - The session is not open. + * - The session is not IDLE. + * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. + * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a + * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. + * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); + +/**@brief Write register protected by the SoftDevice + * + * This function writes to a register that is write-protected by the SoftDevice. Please refer to your + * SoftDevice Specification for more details about which registers that are protected by SoftDevice. + * This function can write to the following protected peripheral: + * - ACL + * + * @note Protected registers may be read directly. + * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in + * the register has not changed. See the Product Specification for more details about register + * properties. + * + * @param[in] p_register Pointer to register to be written. + * @param[in] value Value to be written to the register. + * + * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. + * @retval ::NRF_SUCCESS Value successfully written to register. + * + */ +SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); + +/**@} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SOC_H__ + +/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_svc.h b/variants/wio-sdk-wm1110/softdevice/nrf_svc.h new file mode 100644 index 000000000..1de44656f --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_svc.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NRF_SVC__ +#define NRF_SVC__ + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Supervisor call declaration. + * + * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. + * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. + * + * @param[in] number The SVC number to be used. + * @param[in] return_type The return type of the SVC function. + * @param[in] signature Function signature. The function can have at most four arguments. + */ + +#ifdef SVCALL_AS_NORMAL_FUNCTION +#define SVCALL(number, return_type, signature) return_type signature +#else + +#ifndef SVCALL +#if defined(__CC_ARM) +#define SVCALL(number, return_type, signature) return_type __svc(number) signature +#elif defined(__GNUC__) +#ifdef __cplusplus +#define GCC_CAST_CPP (uint16_t) +#else +#define GCC_CAST_CPP +#endif +#define SVCALL(number, return_type, signature) \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ + __attribute__((unused)) static return_type signature \ + { \ + __asm("svc %0\n" \ + "bx r14" \ + : \ + : "I"(GCC_CAST_CPP number) \ + : "r0"); \ + } \ + _Pragma("GCC diagnostic pop") + +#elif defined(__ICCARM__) +#define PRAGMA(x) _Pragma(#x) +#define SVCALL(number, return_type, signature) \ + PRAGMA(swi_number = (number)) \ + __swi return_type signature; +#else +#define SVCALL(number, return_type, signature) return_type signature +#endif +#endif // SVCALL + +#endif // SVCALL_AS_NORMAL_FUNCTION + +#ifdef __cplusplus +} +#endif +#endif // NRF_SVC__ diff --git a/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld b/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld new file mode 100644 index 000000000..6aaeb4034 --- /dev/null +++ b/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index cba1b8741..e8c681186 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -6,9 +6,10 @@ board = wio-tracker-wm1110 build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = variants/wio-tracker-wm1110/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink \ No newline at end of file +;upload_protocol = jlink diff --git a/variants/wio-tracker-wm1110/softdevice/ble.h b/variants/wio-tracker-wm1110/softdevice/ble.h new file mode 100644 index 000000000..177b436ad --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON BLE SoftDevice Common + @{ + @defgroup ble_api Events, type definitions and API calls + @{ + + @brief Module independent events, type definitions and API calls for the BLE SoftDevice. + + */ + +#ifndef BLE_H__ +#define BLE_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_l2cap.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief Common API SVC numbers. + */ +enum BLE_COMMON_SVCS { + SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ + SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ + SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ + SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ + SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ + SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ + SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ + SD_BLE_OPT_SET, /**< Set a BLE option. */ + SD_BLE_OPT_GET, /**< Get a BLE option. */ + SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ + SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ +}; + +/** + * @brief BLE Module Independent Event IDs. + */ +enum BLE_COMMON_EVTS { + BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t + \n Reply with @ref sd_ble_user_mem_reply. */ + BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ +}; + +/**@brief BLE Connection Configuration IDs. + * + * IDs that uniquely identify a connection configuration. + */ +enum BLE_CONN_CFGS { + BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ + BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ + BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ + BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ + BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ +}; + +/**@brief BLE Common Configuration IDs. + * + * IDs that uniquely identify a common configuration. + */ +enum BLE_COMMON_CFGS { + BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ +}; + +/**@brief Common Option IDs. + * IDs that uniquely identify a common option. + */ +enum BLE_COMMON_OPTS { + BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ + BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ + BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ +}; + +/** @} */ + +/** @addtogroup BLE_COMMON_DEFINES Defines + * @{ */ + +/** @brief Required pointer alignment for BLE Events. + */ +#define BLE_EVT_PTR_ALIGNMENT 4 + +/** @brief Leaves the maximum of the two arguments. + */ +#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +/** @brief Maximum possible length for BLE Events. + * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a + * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. + */ +#define BLE_EVT_LEN_MAX(ATT_MTU) \ + (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) + +/** @defgroup BLE_USER_MEM_TYPES User Memory Types + * @{ */ +#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ +#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ +/** @} */ + +/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts + * @{ + */ +#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ +#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ +/** @} */ + +/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. + * @{ + */ +#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ + +/** @} */ + +/** @} */ + +/** @addtogroup BLE_COMMON_STRUCTURES Structures + * @{ */ + +/**@brief User Memory Block. */ +typedef struct { + uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ + uint16_t len; /**< Length in bytes of the user memory block. */ +} ble_user_mem_block_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ +} ble_evt_user_mem_request_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ + ble_user_mem_block_t mem_block; /**< User memory block */ +} ble_evt_user_mem_release_t; + +/**@brief Event structure for events not associated with a specific function module. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ + union { + ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ + ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ + } params; /**< Event parameter union. */ +} ble_common_evt_t; + +/**@brief BLE Event header. */ +typedef struct { + uint16_t evt_id; /**< Value from a BLE__EVT series. */ + uint16_t evt_len; /**< Length in octets including this header. */ +} ble_evt_hdr_t; + +/**@brief Common BLE Event type, wrapping the module specific event reports. */ +typedef struct { + ble_evt_hdr_t header; /**< Event header. */ + union { + ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ + ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ + ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ + ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ + ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ + } evt; /**< Event union. */ +} ble_evt_t; + +/** + * @brief Version Information. + */ +typedef struct { + uint8_t version_number; /**< Link Layer Version number. See + https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ + uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) + (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ + uint16_t + subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ +} ble_version_t; + +/** + * @brief Configuration parameters for the PA and LNA. + */ +typedef struct { + uint8_t enable : 1; /**< Enable toggling for this amplifier */ + uint8_t active_high : 1; /**< Set the pin to be active high */ + uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ +} ble_pa_lna_cfg_t; + +/** + * @brief PA & LNA GPIO toggle configuration + * + * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or + * a low noise amplifier. + * + * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided + * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences + * and must be avoided by the application. + */ +typedef struct { + ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ + ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ + + uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ + uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ + uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ +} ble_common_opt_pa_lna_t; + +/** + * @brief Configuration of extended BLE connection events. + * + * When enabled the SoftDevice will dynamically extend the connection event when possible. + * + * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. + * The connection event can be extended if there is time to send another packet pair before the start of the next connection + * interval, and if there are no conflicts with other BLE roles requesting radio time. + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ +} ble_common_opt_conn_evt_ext_t; + +/** + * @brief Enable/disable extended RC calibration. + * + * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice + * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets + * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. + * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When + * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the + * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. + * + * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the + * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ +} ble_common_opt_extended_rc_cal_t; + +/**@brief Option structure for common options. */ +typedef union { + ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ + ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ + ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ +} ble_common_opt_t; + +/**@brief Common BLE Option type, wrapping the module specific options. */ +typedef union { + ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ + ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ + ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ +} ble_opt_t; + +/**@brief BLE connection configuration type, wrapping the module specific configurations, set with + * @ref sd_ble_cfg_set. + * + * @note Connection configurations don't have to be set. + * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, + * the default connection configuration will be automatically added for the remaining connections. + * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in + * place of @ref ble_conn_cfg_t::conn_cfg_tag. + * + * @sa sd_ble_gap_adv_start() + * @sa sd_ble_gap_connect() + * + * @mscs + * @mmsc{@ref BLE_CONN_CFG} + * @endmscs + + */ +typedef struct { + uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the + @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls + to select this configuration when creating a connection. + Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ + union { + ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ + ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ + ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ + ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ + ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ + } params; /**< Connection configuration union. */ +} ble_conn_cfg_t; + +/** + * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. + */ +typedef struct { + uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. + Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is + @ref BLE_UUID_VS_COUNT_MAX. */ +} ble_common_cfg_vs_uuid_t; + +/**@brief Common BLE Configuration type, wrapping the common configurations. */ +typedef union { + ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ +} ble_common_cfg_t; + +/**@brief BLE Configuration type, wrapping the module specific configurations. */ +typedef union { + ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ + ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ + ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ + ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ +} ble_cfg_t; + +/** @} */ + +/** @addtogroup BLE_COMMON_FUNCTIONS Functions + * @{ */ + +/**@brief Enable the BLE stack + * + * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the + * application RAM region (APP_RAM_BASE). On return, this will + * contain the minimum start address of the application RAM region + * required by the SoftDevice for this configuration. + * @warning After this call, the SoftDevice may generate several events. The list of events provided + * below require the application to initiate a SoftDevice API call. The corresponding API call + * is referenced in the event documentation. + * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop + * communicating with the peer device. + * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST + * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST + * - @ref BLE_GAP_EVT_SEC_REQUEST + * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST + * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST + * - @ref BLE_EVT_USER_MEM_REQUEST + * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located + * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between + * APP_RAM_BASE and the start of the call stack. + * + * @details This call initializes the BLE stack, no BLE related function other than @ref + * sd_ble_cfg_set can be called before this one. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: + * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not + * large enough to fit this configuration's memory requirement. Check *p_app_ram_base + * and set the start address of the application RAM region accordingly. + * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which + * is currently not supported. + * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. + */ +SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); + +/**@brief Add configurations for the BLE stack + * + * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref + * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. + * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. + * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). + * See @ref sd_ble_enable for details about APP_RAM_BASE. + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note If a configuration is set more than once, the last one set is the one that takes effect on + * @ref sd_ble_enable. + * + * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default + * configuration. + * + * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref + * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref + * sd_ble_enable). + * + * @note Error codes for the configurations are described in the configuration structs. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The configuration has been added successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. + * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not + * large enough to fit this configuration's memory requirement. + */ +SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); + +/**@brief Get an event from the pending events queue. + * + * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. + * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. + * The buffer should be interpreted as a @ref ble_evt_t struct. + * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. + * + * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that + * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. + * The application is free to choose whether to call this function from thread mode (main context) or directly from the + * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher + * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) + * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so + * could potentially leave events in the internal queue without the application being aware of this fact. + * + * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to + * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, + * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. + * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length + * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: + * + * \code + * uint16_t len; + * errcode = sd_ble_evt_get(NULL, &len); + * \endcode + * + * @mscs + * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} + * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. + * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. + */ +SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); + +/**@brief Add a Vendor Specific base UUID. + * + * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later + * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t + * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code + * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses + * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to + * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field + * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to + * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, + * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. + * + * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by + * the 16-bit uuid field in @ref ble_uuid_t. + * + * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in + * p_uuid_type along with an @ref NRF_SUCCESS error code. + * + * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding + * bytes 12 and 13. + * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be + * stored. + * + * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. + * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. + */ +SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); + +/**@brief Remove a Vendor Specific base UUID. + * + * @details This call removes a Vendor Specific base UUID. This function allows + * the application to reuse memory allocated for Vendor Specific base UUIDs. + * + * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID + * type. + * + * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. + * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific + * base UUID will be removed. If the function returns successfully, the UUID type that was removed will + * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that + * is in use by the ATT Server. + * + * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. + * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. + */ +SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); + +/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. + * + * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared + * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs + * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index + * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. + * + * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. + * + * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). + * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. + * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. + * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. + */ +SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); + +/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). + * + * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is + * computed. + * + * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. + * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). + * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. + * + * @retval ::NRF_SUCCESS Successfully encoded into the buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. + */ +SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); + +/**@brief Get Version Information. + * + * @details This call allows the application to get the BLE stack version information. + * + * @param[out] p_version Pointer to a ble_version_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Version information stored successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). + */ +SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); + +/**@brief Provide a user memory block. + * + * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. + */ +SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); + +/**@brief Set a BLE option. + * + * @details This call allows the application to set the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. + * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. + * + * @retval ::NRF_SUCCESS Option set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + */ +SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); + +/**@brief Get a BLE option. + * + * @details This call allows the application to retrieve the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. + * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Option retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. + * + */ +SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); + +/** @} */ +#ifdef __cplusplus +} +#endif +#endif /* BLE_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_err.h b/variants/wio-tracker-wm1110/softdevice/ble_err.h new file mode 100644 index 000000000..d20f6d141 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_err.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @addtogroup nrf_error + @{ + @ingroup BLE_COMMON + @} + + @defgroup ble_err General error codes + @{ + + @brief General error code definitions for the BLE API. + + @ingroup BLE_COMMON +*/ +#ifndef NRF_BLE_ERR_H__ +#define NRF_BLE_ERR_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* @defgroup BLE_ERRORS Error Codes + * @{ */ +#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ +#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ +#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ +#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ +#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ +#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ + (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ +/** @} */ + +/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges + * @brief Assignment of subranges for module specific error codes. + * @note For specific error codes, see ble_.h or ble_error_.h. + * @{ */ +#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ +#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ +#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ +#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gap.h b/variants/wio-tracker-wm1110/softdevice/ble_gap.h new file mode 100644 index 000000000..8ebdfa82b --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gap.h @@ -0,0 +1,2895 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GAP Generic Access Profile (GAP) + @{ + @brief Definitions and prototypes for the GAP interface. + */ + +#ifndef BLE_GAP_H__ +#define BLE_GAP_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GAP API SVC numbers. + */ +enum BLE_GAP_SVCS { + SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ + SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ + SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ + SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ + SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ + SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ + SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ + SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ + SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ + SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ + SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ + SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ + SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ + SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ + SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ + SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ + SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ + SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ + SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ + SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ + SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ + SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ + SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ + SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ + SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ + SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ + SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ + SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ + SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ + SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ + SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ + SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ + SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ + SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ + SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ + SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ + SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ + SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = + BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ +}; + +/**@brief GAP Event IDs. + * IDs that uniquely identify an event coming from the stack to the application. + */ +enum BLE_GAP_EVTS { + BLE_GAP_EVT_CONNECTED = + BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ + BLE_GAP_EVT_DISCONNECTED = + BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE = + BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ + BLE_GAP_EVT_SEC_PARAMS_REQUEST = + BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. + \n See @ref ble_gap_evt_sec_params_request_t. */ + BLE_GAP_EVT_SEC_INFO_REQUEST = + BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. + \n See @ref ble_gap_evt_sec_info_request_t. */ + BLE_GAP_EVT_PASSKEY_DISPLAY = + BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref + sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ + BLE_GAP_EVT_KEY_PRESSED = + BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ + BLE_GAP_EVT_AUTH_KEY_REQUEST = + BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. + \n See @ref ble_gap_evt_auth_key_request_t. */ + BLE_GAP_EVT_LESC_DHKEY_REQUEST = + BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref + sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ + BLE_GAP_EVT_AUTH_STATUS = + BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ + BLE_GAP_EVT_CONN_SEC_UPDATE = + BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ + BLE_GAP_EVT_TIMEOUT = + BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ + BLE_GAP_EVT_RSSI_CHANGED = + BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ + BLE_GAP_EVT_ADV_REPORT = + BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ + BLE_GAP_EVT_SEC_REQUEST = + BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate +\n or with @ref sd_ble_gap_encrypt if required security information is available +. \n See @ref ble_gap_evt_sec_request_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref + sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ + BLE_GAP_EVT_SCAN_REQ_REPORT = + BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ + BLE_GAP_EVT_PHY_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n + See @ref ble_gap_evt_phy_update_request_t. */ + BLE_GAP_EVT_PHY_UPDATE = + BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref + sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE = + BLE_GAP_EVT_BASE + + 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ + BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = + BLE_GAP_EVT_BASE + + 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ + BLE_GAP_EVT_ADV_SET_TERMINATED = + BLE_GAP_EVT_BASE + + 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ +}; + +/**@brief GAP Option IDs. + * IDs that uniquely identify a GAP option. + */ +enum BLE_GAP_OPTS { + BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ + BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ + BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ + BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ + BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = + BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ + BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = + BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ +}; + +/**@brief GAP Configuration IDs. + * + * IDs that uniquely identify a GAP configuration. + */ +enum BLE_GAP_CFGS { + BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ + BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ + BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic + inclusion configuration. */ + BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic + inclusion configuration. */ +}; + +/**@brief GAP TX Power roles. + */ +enum BLE_GAP_TX_POWER_ROLES { + BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ + BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ + BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ +}; + +/** @} */ + +/**@addtogroup BLE_GAP_DEFINES Defines + * @{ */ + +/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP + * @{ */ +#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ + (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ +#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ + (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ +#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ + (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ +#define BLE_ERROR_GAP_WHITELIST_IN_USE \ + (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ + (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ + (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLES GAP Roles + * @{ */ +#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ +#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ +#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ +/**@} */ + +/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources + * @{ */ +#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ +#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ +#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ +/**@} */ + +/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types + * @{ */ +#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ +#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ +#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ + 0x7F /**< An advertiser may advertise without its address. \ + This type of advertising is called anonymous. */ +/**@} */ + +/**@brief The default interval in seconds at which a private address is refreshed. */ +#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ +/**@brief The maximum interval in seconds at which a private address can be refreshed. */ +#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ + +/** @brief BLE address length. */ +#define BLE_GAP_ADDR_LEN (6) + +/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes + * @{ */ +#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ +#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ +#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ + 0x02 /**< Device will send and accept only private addresses for its own address, \ + and will not accept a peer using identity address as sender address when \ + the peer IRK is exchanged, non-zero and added to the identity list. */ +/**@} */ + +/** @brief Invalid power level. */ +#define BLE_GAP_POWER_LEVEL_INVALID 127 + +/** @brief Advertising set handle not set. */ +#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) + +/** @brief The default number of advertising sets. */ +#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) + +/** @brief The maximum number of advertising sets supported by this SoftDevice. */ +#define BLE_GAP_ADV_SET_COUNT_MAX (1) + +/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. + * @{ */ +#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ + (31) /**< Maximum data length for an advertising set. \ + If more advertising data is required, use extended advertising instead. */ +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for an extended advertising set. */ + +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ + (238) /**< Maximum supported data length for an extended connectable advertising set. */ +/**@}. */ + +/** @brief Set ID not available in advertising report. */ +#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF + +/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons + * @{ */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ +/**@} */ + +/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format + * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm + * @{ */ +#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ +#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ +#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ +#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ +#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ +#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ +#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ +#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ +#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ +#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ +#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ +#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ +#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ +#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ +#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ +#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags + * @{ */ +#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min + * @{ */ +#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ +#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ + /**@} */ + +/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min + * @{ */ +#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min + * @{ */ +#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min + * @{ */ +#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ +#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size + * + * Scan buffers are used for storing advertising data received from an advertiser. + * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. + * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. + * @{ */ +#define BLE_GAP_SCAN_BUFFER_MIN \ + (31) /**< Minimum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_MAX \ + (31) /**< Maximum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ + (255) /**< Minimum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ + (1650) /**< Maximum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for \ + an extended advertising set. */ +/** @} */ + +/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types + * + * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. + * + * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. + * The maximum supported data length for an extended advertiser is defined by + * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED + * Note that some of the advertising types do not support advertising data. Non-scannable types do not support + * scan response data. + * + * @{ */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x01 /**< Connectable and scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ + 0x02 /**< Connectable non-scannable directed advertising \ + events. Advertising interval is less that 3.75 ms. \ + Use this type for fast reconnections. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x03 /**< Connectable non-scannable directed advertising \ + events. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x04 /**< Non-connectable scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x05 /**< Non-connectable non-scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x06 /**< Connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x07 /**< Connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x08 /**< Non-connectable scannable undirected advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ + 0x09 /**< Non-connectable scannable directed advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x0A /**< Non-connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x0B /**< Non-connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies + * @{ */ +#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ +#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status + * @{ */ +#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ + 0x01 /**< More data to be received. \ + @note This value will only be used if \ + @ref ble_gap_scan_params_t::report_incomplete_evts and \ + @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ + 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ + 0x03 /**< Failed to receive the remaining data. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +/**@} */ + +/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies + * @{ */ +#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ + 0x00 /**< Accept all advertising packets except directed advertising packets \ + not addressed to this device. */ +#define BLE_GAP_SCAN_FP_WHITELIST \ + 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ + packets not addressed to this device. */ +#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ + 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ + 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units + * @{ */ +#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ + (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ + */ +#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ + (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ + mode. */ +#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ + (0) /**< Unlimited advertising in general discoverable mode. \ + For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ +/**@} */ + +/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes + * @{ */ +#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ +#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ +#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ +/**@} */ + +/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities + * @{ */ +#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ +#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ +#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ +/**@} */ + +/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types + * @{ */ +#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ +#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ +#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ +/**@} */ + +/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types + * @{ */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS GAP Security status + * @{ */ +#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ +#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ +#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ +#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ +#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ +#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ +#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ +#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ +#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ +#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ +#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ +#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ +#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ +#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ +#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ +#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ +#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources + * @{ */ +#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ +#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ +/**@} */ + +/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits + * @{ */ +#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ + 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ + 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ + 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ + 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ +/**@} */ + +/**@defgroup BLE_GAP_DEVNAME GAP device name defines. + * @{ */ +#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ +#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ +#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ +/**@} */ + +/**@brief Disable RSSI events for connections */ +#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF + +/**@defgroup BLE_GAP_PHYS GAP PHYs + * @{ */ +#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ +#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ +#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ +#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ +#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ + +/**@brief Supported PHYs in connections, for scanning, and for advertising. */ +#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ + +/**@} */ + +/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters + * + * See @ref ble_gap_conn_sec_mode_t. + * @{ */ +/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ + do { \ + (ptr)->sm = 0; \ + (ptr)->lv = 0; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 2; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 3; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 4; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 2; \ + } while (0) +/**@} */ + +/**@brief GAP Security Random Number Length. */ +#define BLE_GAP_SEC_RAND_LEN 8 + +/**@brief GAP Security Key Length. */ +#define BLE_GAP_SEC_KEY_LEN 16 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ +#define BLE_GAP_LESC_P256_PK_LEN 64 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ +#define BLE_GAP_LESC_DHKEY_LEN 32 + +/**@brief GAP Passkey Length. */ +#define BLE_GAP_PASSKEY_LEN 6 + +/**@brief Maximum amount of addresses in the whitelist. */ +#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) + +/**@brief Maximum amount of identities in the device identities list. */ +#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) + +/**@brief Default connection count for a configuration. */ +#define BLE_GAP_CONN_COUNT_DEFAULT (1) + +/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. + * @{ */ +#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ +#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ +#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. + * @{ */ +#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ + (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ + (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ + +/**@} */ + +/**@brief Automatic data length parameter. */ +#define BLE_GAP_DATA_LENGTH_AUTO 0 + +/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. + * @{ */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ +/**@} */ + +/**@defgroup GAP_SEC_MODES GAP Security Modes + * @{ */ +#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ +/**@} */ + +/**@brief The total number of channels in Bluetooth Low Energy. */ +#define BLE_GAP_CHANNEL_COUNT (40) + +/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines + * @{ */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ + /**@} */ + +/** @} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations + * @{ + */ +#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ + (1) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will reserve the attribute handles \ + which are otherwise used for this characteristic. \ + By reserving the attribute handles it will be possible \ + to upgrade the SoftDevice without changing handle of the \ + Service Changed characteristic. */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ + (2) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will not reserve the attribute handles \ + which are otherwise used for this characteristic. */ +/**@} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values + * @{ */ +#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +/**@} */ + +/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options + * @{ */ +#define BLE_GAP_SLAVE_LATENCY_ENABLE \ + (0) /**< Slave latency is enabled. When slave latency is enabled, \ + the slave will wake up every time it has data to send, \ + and/or every slave latency number of connection events. */ +#define BLE_GAP_SLAVE_LATENCY_DISABLE \ + (1) /**< Disable slave latency. The slave will wake up every connection event \ + regardless of the requested slave latency. \ + This option consumes the most power. */ +#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ + (2) /**< The slave will wake up every connection event if it has not received \ + an ACK from the master for at least slave latency events. This \ + configuration may increase the power consumption in environments \ + with a lot of radio activity. */ +/**@} */ + +/**@addtogroup BLE_GAP_STRUCTURES Structures + * @{ */ + +/**@brief Advertising event properties. */ +typedef struct { + uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ + uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. + @note Anonymous advertising is only available for + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ + uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ +} ble_gap_adv_properties_t; + +/**@brief Advertising report type. */ +typedef struct { + uint16_t connectable : 1; /**< Connectable advertising event type. */ + uint16_t scannable : 1; /**< Scannable advertising event type. */ + uint16_t directed : 1; /**< Directed advertising event type. */ + uint16_t scan_response : 1; /**< Received a scan response. */ + uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ + uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ + uint16_t reserved : 9; /**< Reserved for future use. */ +} ble_gap_adv_report_type_t; + +/**@brief Advertising Auxiliary Pointer. */ +typedef struct { + uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ + uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ +} ble_gap_aux_pointer_t; + +/**@brief Bluetooth Low Energy address. */ +typedef struct { + uint8_t + addr_id_peer : 1; /**< Only valid for peer addresses. + This bit is set by the SoftDevice to indicate whether the address has been resolved from + a Resolvable Private Address (when the peer is using privacy). + If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. + + This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. + */ + uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ + uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. + @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ +} ble_gap_addr_t; + +/**@brief GAP connection parameters. + * + * @note When ble_conn_params_t is received in an event, both min_conn_interval and + * max_conn_interval will be equal to the connection interval set by the central. + * + * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: + * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval + * that corresponds to the following Bluetooth Spec requirement: + * The Supervision_Timeout in milliseconds shall be larger than + * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. + */ +typedef struct { + uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ +} ble_gap_conn_params_t; + +/**@brief GAP connection security modes. + * + * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n + * Security Mode 1 Level 1: No security is needed (aka open link).\n + * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n + * Security Mode 1 Level 3: MITM protected encrypted link required.\n + * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n + * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n + * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n + */ +typedef struct { + uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ + uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ + +} ble_gap_conn_sec_mode_t; + +/**@brief GAP connection security status.*/ +typedef struct { + ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ + uint8_t + encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ +} ble_gap_conn_sec_t; + +/**@brief Identity Resolving Key. */ +typedef struct { + uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ +} ble_gap_irk_t; + +/**@brief Channel mask (40 bits). + * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, + * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents + * channel index 39. If a bit is set to 1, the channel is not used. + */ +typedef uint8_t ble_gap_ch_mask_t[5]; + +/**@brief GAP advertising parameters. */ +typedef struct { + ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ + ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. + @note ble_gap_addr_t::addr_type cannot be + @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. + - When privacy is enabled and the local device uses + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, + the device identity list is searched for a matching entry. If + the local IRK for that device identity is set, the local IRK + for that device will be used to generate the advertiser address + field in the advertising packet. + - If @ref ble_gap_adv_properties_t::type is directed, this must be + set to the targeted scanner or initiator. If the peer address is + in the device identity list, the peer IRK for that device will be + used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE + target addresses used in the advertising event PDUs. */ + uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE + advertising, this parameter is ignored. */ + uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, + an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + @sa BLE_GAP_ADV_TIMEOUT_VALUES. + @note The SoftDevice will always complete at least one advertising + event even if the duration is set too low. */ + uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling + advertising. Setting the value to 0 disables the limitation. When + the count of advertising events specified by this parameter + (if not 0) is reached, advertising will be automatically stopped + and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, + this parameter is ignored. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be used. + Masking away secondary advertising channels is not supported. */ + uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets + are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS + will be used. + Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. + @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets + are transmitted. + If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. + Valid values are + @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. + If @ref ble_gap_adv_properties_t::type is an extended advertising type + and connectable, this is the PHY that will be used to establish a + connection and send AUX_ADV_IND packets on. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other + advertising sets transmitted by this and other devices. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a + scan request is received and the scanner address is allowed + by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is a non-scannable + advertising type. */ +} ble_gap_adv_params_t; + +/**@brief GAP advertising data buffers. + * + * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and + * shall never be modified while advertising. The data shall be kept alive until either: + * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding + * advertising handle. + * - Advertising is stopped. + * - Advertising data is changed. + * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ +typedef struct { + ble_data_t adv_data; /**< Advertising data. + @note + Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type + that is allowed to contain advertising data. */ + ble_data_t scan_rsp_data; /**< Scan response data. + @note + Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type + that is scannable. */ +} ble_gap_adv_data_t; + +/**@brief GAP scanning parameters. */ +typedef struct { + uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. + If set to 0, the scanner will not receive advertising packets + on secondary advertising channels, and will not be able + to receive long advertising PDUs. */ + uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have + @ref ble_gap_adv_report_type_t::status set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. + This parameter is ignored when used with @ref sd_ble_gap_connect + @note This may be used to abort receiving more packets from an extended + advertising event, and is only available for extended + scanning, see @ref sd_ble_gap_scan_start. + @note This feature is not supported by this SoftDevice. */ + uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. + This parameter is ignored when used with @ref sd_ble_gap_connect. */ + uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. + @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and + @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with + @ref sd_ble_gap_connect */ + uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, + scan_phys will default to @ref BLE_GAP_PHY_1MBPS. + - If @ref ble_gap_scan_params_t::extended is set to 0, the only + supported PHY is @ref BLE_GAP_PHY_1MBPS. + - When used with @ref sd_ble_gap_scan_start, + the bitfield indicates the PHYs the scanner will use for scanning + on primary advertising channels. The scanner will accept + @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. + - When used with @ref sd_ble_gap_connect, the bitfield indicates + the PHYs the initiator will use for scanning on primary advertising + channels. The initiator will accept connections initiated on either + of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. + If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, + the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. + If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan + PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is + @ref BLE_GAP_PHY_CODED, the primary scan PHY is + @ref BLE_GAP_PHY_CODED only. */ + uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ + uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. + If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and + @ref BLE_GAP_PHY_CODED interval shall be larger than or + equal to twice the scan window. */ + uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be + set to 0. + Masking away secondary channels is not supported. */ +} ble_gap_scan_params_t; + +/**@brief Privacy. + * + * The privacy feature provides a way for the device to avoid being tracked over a period of time. + * The privacy feature, when enabled, hides the local device identity and replaces it with a private address + * that is automatically refreshed at a specified interval. + * + * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). + * With this key, a device can generate a random private address that can only be recognized by peers in possession of that + * key, and devices can establish connections without revealing their real identities. + * + * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref + * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. + * + * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all + * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. + * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is + * called. + */ +typedef struct { + uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ + uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ + uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use + the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ + ble_gap_irk_t + *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device + default IRK will be used. When used as output, pointer to IRK structure where the current default IRK + will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate + random private resolvable addresses for the local device unless instructed otherwise. */ +} ble_gap_privacy_params_t; + +/**@brief PHY preferences for TX and RX + * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each + * direction. + * @code + * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * @endcode + * + */ +typedef struct { + uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ +} ble_gap_phys_t; + +/** @brief Keys that can be exchanged during a bonding procedure. */ +typedef struct { + uint8_t enc : 1; /**< Long Term Key and Master Identification. */ + uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ + uint8_t sign : 1; /**< Connection Signature Resolving Key. */ + uint8_t link : 1; /**< Derive the Link Key from the LTK. */ +} ble_gap_sec_kdist_t; + +/**@brief GAP security parameters. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ + uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ + uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ + uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ + uint8_t oob : 1; /**< The OOB data flag. + - In LE legacy pairing, this flag is set if a device has out of band authentication data. + The OOB method is used if both of the devices have out of band authentication data. + - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band + authentication data. The OOB method is used if at least one device has the peer device's OOB data + available. */ + uint8_t + min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ + uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ + ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ + ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ +} ble_gap_sec_params_t; + +/**@brief GAP Encryption Information. */ +typedef struct { + uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ + uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ + uint8_t auth : 1; /**< Authenticated Key. */ + uint8_t ltk_len : 6; /**< LTK length in octets. */ +} ble_gap_enc_info_t; + +/**@brief GAP Master Identification. */ +typedef struct { + uint16_t ediv; /**< Encrypted Diversifier. */ + uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ +} ble_gap_master_id_t; + +/**@brief GAP Signing Information. */ +typedef struct { + uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ +} ble_gap_sign_info_t; + +/**@brief GAP LE Secure Connections P-256 Public Key. */ +typedef struct { + uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the + standard SMP protocol format: {X,Y} both in little-endian. */ +} ble_gap_lesc_p256_pk_t; + +/**@brief GAP LE Secure Connections DHKey. */ +typedef struct { + uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ +} ble_gap_lesc_dhkey_t; + +/**@brief GAP LE Secure Connections OOB data. */ +typedef struct { + ble_gap_addr_t addr; /**< Bluetooth address of the device. */ + uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ + uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ +} ble_gap_lesc_oob_data_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ +typedef struct { + ble_gap_addr_t + peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ + uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ +} ble_gap_evt_connected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ +typedef struct { + uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ +} ble_gap_evt_disconnected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ +typedef struct { + ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ +} ble_gap_evt_phy_update_request_t; + +/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ +typedef struct { + uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ + uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ +} ble_gap_evt_phy_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ +typedef struct { + ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ +} ble_gap_evt_sec_params_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ +typedef struct { + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ + ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ + uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ + uint8_t id_info : 1; /**< If 1, Identity Information required. */ + uint8_t sign_info : 1; /**< If 1, Signing Information required. */ +} ble_gap_evt_sec_info_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ +typedef struct { + uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ + uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply + with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or + @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ +} ble_gap_evt_passkey_display_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ +typedef struct { + uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ +} ble_gap_evt_key_pressed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ +typedef struct { + uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ +} ble_gap_evt_auth_key_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ +typedef struct { + ble_gap_lesc_p256_pk_t + *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory + inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ + uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the + procedure. */ +} ble_gap_evt_lesc_dhkey_request_t; + +/**@brief Security levels supported. + * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. + */ +typedef struct { + uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ + uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ + uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ + uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ +} ble_gap_sec_levels_t; + +/**@brief Encryption Key. */ +typedef struct { + ble_gap_enc_info_t enc_info; /**< Encryption Information. */ + ble_gap_master_id_t master_id; /**< Master Identification. */ +} ble_gap_enc_key_t; + +/**@brief Identity Key. */ +typedef struct { + ble_gap_irk_t id_info; /**< Identity Resolving Key. */ + ble_gap_addr_t id_addr_info; /**< Identity Address. */ +} ble_gap_id_key_t; + +/**@brief Security Keys. */ +typedef struct { + ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ + ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ + ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ + ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the + value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ +} ble_gap_sec_keys_t; + +/**@brief Security key set for both local and peer keys. */ +typedef struct { + ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be + generated locally and will always be stored if bonding. */ + ble_gap_sec_keys_t + keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ +} ble_gap_sec_keyset_t; + +/**@brief Data Length Update Procedure parameters. */ +typedef struct { + uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ + uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ +} ble_gap_data_length_params_t; + +/**@brief Data Length Update Procedure local limitation. */ +typedef struct { + uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ + uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ + uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many + microseconds. */ +} ble_gap_data_length_limitation_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ +typedef struct { + uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ + uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ + uint8_t bonded : 1; /**< Procedure resulted in a bond. */ + uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ + ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ + ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ + ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding + with LE Secure Connections, the enc bit will be always set. */ + ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding + with LE Secure Connections, the enc bit will never be set. */ +} ble_gap_evt_auth_status_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ +typedef struct { + ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ +} ble_gap_evt_conn_sec_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ + union { + ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released + scan buffer is contained in this field. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ +typedef struct { + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ +} ble_gap_evt_rssi_changed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ +typedef struct { + uint8_t reason; /**< Reason for why the advertising set terminated. See + @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ + uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, + this field indicates the number of completed advertising events. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. */ +} ble_gap_evt_adv_set_terminated_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. + * + * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * not all fields in the advertising report may be available. + * + * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. + */ +typedef struct { + ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the + peer's identity address. */ + ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if + @ref ble_gap_adv_report_type_t::directed is set to 1. If the + SoftDevice was able to resolve the address, + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr + contains the local identity address. If the target address of the + advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + and the SoftDevice was unable to resolve it, the application may try + to resolve this address to find out if the advertising event was + directed to us. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. + See @ref BLE_GAP_PHYS. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. + See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets + were received on a secondary advertising channel. */ + int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. + This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the + last received packet did not contain the Tx Power field. + @note TX Power is only included in extended advertising packets. */ + int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ + uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present + if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID + is not present if @ref ble_gap_evt_adv_report_t::set_id is set to + @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + ble_data_t data; /**< Received advertising or scan response data. If + @ref ble_gap_adv_report_type_t::status is not set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided + in @ref sd_ble_gap_scan_start is now released. */ + ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising + event. @note This field is only set if @ref ble_gap_adv_report_type_t::status + is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ +} ble_gap_evt_adv_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Man In The Middle protection requested. */ + uint8_t lesc : 1; /**< LE Secure Connections requested. */ + uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ +} ble_gap_evt_sec_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ +typedef struct { + uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ +} ble_gap_evt_scan_req_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ +typedef struct { + ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ +} ble_gap_evt_data_length_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. + * + * @note This event may also be raised after a PHY Update procedure. + */ +typedef struct { + ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ +} ble_gap_evt_data_length_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ +typedef struct { + int8_t + channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy + channels, in dBm, indexed by Channel Index. + If no measurement is available for the given channel, channel_energy is set to + @ref BLE_GAP_POWER_LEVEL_INVALID. */ +} ble_gap_evt_qos_channel_survey_report_t; + +/**@brief GAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + union /**< union alternative identified by evt_id in enclosing struct. */ + { + ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ + ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ + ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ + ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ + ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ + ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ + ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ + ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ + ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ + ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ + ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ + ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ + ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ + ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ + ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ + ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ + ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ + ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ + ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ + ble_gap_evt_qos_channel_survey_report_t + qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_t; + +/** + * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. + * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. + */ +typedef struct { + uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. + The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ + uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. + The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref + BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters + for setting the throughput of a connection. + See the SoftDevice Specification for details on throughput. */ +} ble_gap_conn_cfg_t; + +/** + * @brief Configuration of maximum concurrent connections in the different connected roles, set with + * @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too + * large. The maximum supported sum of concurrent connections is + * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. + * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. + * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum + * supported advertising handles is + * @ref BLE_GAP_ADV_SET_COUNT_MAX. + */ +typedef struct { + uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ + uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref + BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ + uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref + BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ + uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is + @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ + uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to + the application using @ref sd_ble_gap_qos_channel_survey_start. */ +} ble_gap_cfg_role_count_t; + +/** + * @brief Device name and its properties, set with @ref sd_ble_cfg_set. + * + * @note If the device name is not configured, the default device name will be + * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be + * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name + * will have no write access. + * + * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, + * the attribute table size must be increased to have room for the longer device name (see + * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). + * + * @note If vloc is @ref BLE_GATTS_VLOC_STACK : + * - p_value must point to non-volatile memory (flash) or be NULL. + * - If p_value is NULL, the device name will initially be empty. + * + * @note If vloc is @ref BLE_GATTS_VLOC_USER : + * - p_value cannot be NULL. + * - If the device name is writable, p_value must point to volatile memory (RAM). + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Invalid device name location (vloc). + * - Invalid device name security mode. + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). + * - The device name length is too long for the given Attribute Table. + * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. + */ +typedef struct { + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ + uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ + uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ +} ble_gap_cfg_device_name_t; + +/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_ppcp_incl_cfg_t; + +/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_car_incl_cfg_t; + +/**@brief Configuration structure for GAP configurations. */ +typedef union { + ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ + ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ + ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include + configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ + ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, + cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ +} ble_gap_cfg_t; + +/**@brief Channel Map option. + * + * @details Used with @ref sd_ble_opt_get to get the current channel map + * or @ref sd_ble_opt_set to set a new channel map. When setting the + * channel map, it applies to all current and future connections. When getting the + * current channel map, it applies to a single connection and the connection handle + * must be supplied. + * + * @note Setting the channel map may take some time, depending on connection parameters. + * The time taken may be different for each connection and the get operation will + * return the previous channel map until the new one has taken effect. + * + * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. + * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. + * + * @retval ::NRF_SUCCESS Get or set successful. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Less then two bits in @ref ch_map are set. + * - Bits for primary advertising channels (37-39) are set. + * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. + * + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ + uint8_t ch_map[5]; /**< Channel Map (37-bit). */ +} ble_gap_opt_ch_map_t; + +/**@brief Local connection latency option. + * + * @details Local connection latency is a feature which enables the slave to improve + * current consumption by ignoring the slave latency set by the peer. The + * local connection latency can only be set to a multiple of the slave latency, + * and cannot be longer than half of the supervision timeout. + * + * @details Used with @ref sd_ble_opt_set to set the local connection latency. The + * @ref sd_ble_opt_get is not supported for this option, but the actual + * local connection latency (unless set to NULL) is set as a return parameter + * when setting the option. + * + * @note The latency set will be truncated down to the closest slave latency event + * multiple, or the nearest multiple before half of the supervision timeout. + * + * @note The local connection latency is disabled by default, and needs to be enabled for new + * connections and whenever the connection is updated. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t requested_latency; /**< Requested local connection latency. */ + uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return + value). */ +} ble_gap_opt_local_conn_latency_t; + +/**@brief Disable slave latency + * + * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection + * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the + * peripheral will ignore the slave_latency set by the central. + * + * @note Shall only be called on peripheral links. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ +} ble_gap_opt_slave_latency_disable_t; + +/**@brief Passkey Option. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @endmscs + * + * @details Structure containing the passkey to be used during pairing. This can be used with @ref + * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication + * instead of generating a random one. + * + * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + */ +typedef struct { + uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used + during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ +} ble_gap_opt_passkey_t; + +/**@brief Compatibility mode 1 option. + * + * @details This can be used with @ref sd_ble_opt_set to enable and disable + * compatibility mode 1. Compatibility mode 1 is disabled by default. + * + * @note Compatibility mode 1 enables interoperability with devices that do not support a value of + * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a + * limited set of legacy peripheral devices from another vendor. Enabling this compatibility + * mode will only have an effect if the local device will act as a central device and + * initiate a connection to a peripheral device. In that case it may lead to the connection + * creation taking up to one connection interval longer to complete for all connections. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. + */ +typedef struct { + uint8_t enable : 1; /**< Enable compatibility mode 1.*/ +} ble_gap_opt_compat_mode_1_t; + +/**@brief Authenticated payload timeout option. + * + * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other + * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. + * + * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated + * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted + * link. + * + * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance + * to reset the timer. In addition the stack will try to prioritize running of LE ping over other + * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects + * on other activities, it is recommended to use high timeout values. + * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ +} ble_gap_opt_auth_payload_timeout_t; + +/**@brief Option structure for GAP options. */ +typedef union { + ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ + ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ + ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ + ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ + ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ + ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ +} ble_gap_opt_t; + +/**@brief Connection event triggering parameters. */ +typedef struct { + uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until + connection event PPI task triggering is stopped. + The PPI channel ID can not be one of the PPI channels reserved by + the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ + uint32_t task_endpoint; /**< Task Endpoint to trigger. */ + uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ + uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. + If the device is in slave role and slave latency is enabled, + this parameter should be set to a multiple of (slave latency + 1) + to ensure low power operation. */ +} ble_gap_conn_event_trigger_t; +/**@} */ + +/**@addtogroup BLE_GAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set the local Bluetooth identity address. + * + * The local Bluetooth identity address is the address that identifies this device to other peers. + * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @note The identity address cannot be changed while advertising, scanning or creating a connection. + * + * @note This address will be distributed to the peer during bonding. + * If the address changes, the address stored in the peer device will not be valid and the ability to + * reconnect using the old address will be lost. + * + * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being + * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged + * for the lifetime of each IC. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @endmscs + * + * @param[in] p_addr Pointer to address structure. + * + * @retval ::NRF_SUCCESS Address successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, + * scanning or creating a connection. + */ +SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); + +/**@brief Get local Bluetooth identity address. + * + * @note This will always return the identity address irrespective of the privacy settings, + * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + */ +SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); + +/**@brief Get the Bluetooth device address used by the advertiser. + * + * @note This function will return the local Bluetooth address used in advertising PDUs. When + * using privacy, the SoftDevice will generate a new private address every + * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using + * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the + * address returned may not be the latest address that is used in the advertising PDUs. + * + * @param[in] adv_handle The advertising handle to get the address from. + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. + * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); + +/**@brief Set the active whitelist in the SoftDevice. + * + * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. + * The whitelist cannot be set if a BLE role is using the whitelist. + * + * @note If an address is resolved using the information in the device identity list, then the whitelist + * filter policy applies to the peer identity address and not the resolvable address sent on air. + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @endmscs + * + * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. + * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * + * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. + * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when + * pp_wl_addrs is not NULL. + */ +SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); + +/**@brief Set device identity list. + * + * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. + * The device identity list cannot be set if a BLE role is using the list. + * + * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will + * be cleared. + * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the + * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. + * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. + * This code may be returned if the local IRK list also has an invalid entry. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity + * address. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can + * only return when pp_id_keys is not NULL. + */ +SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, + sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, + uint8_t len)); + +/**@brief Set privacy settings. + * + * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. + * + * @param[in] p_privacy_params Privacy settings. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. + * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution + characteristic is not configured to be included and the SoftDevice is configured + to support central roles. + See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning + * or creating a connection. + */ +SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); + +/**@brief Get privacy settings. + * + * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. + * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. + * + * @param[in,out] p_privacy_params Privacy settings. + * + * @retval ::NRF_SUCCESS Privacy settings read. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + */ +SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); + +/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. + * + * @note The format of the advertising data will be checked by this call to ensure interoperability. + * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and + * duplicating the local name in the advertising data and scan response data. + * + * @note In order to update advertising data while advertising, new advertising buffers must be provided. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the + * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. + * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See + * @ref ble_gap_adv_data_t. + * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising + * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. + * + * @retval ::NRF_SUCCESS Advertising set successfully configured. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid advertising data configuration specified. See @ref + * ble_gap_adv_data_t. + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, + * see @ref sd_ble_gap_whitelist_set. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - It is invalid to provide non-NULL advertising set parameters while + * advertising. + * - It is invalid to provide the same data buffers while advertising. To + * update advertising data, provide new advertising buffers. + * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format + * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an + * existing advertising handle instead. + * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. + */ +SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, + sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, + ble_gap_adv_params_t const *p_adv_params)); + +/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @note Only one advertiser may be active at any time. + * + * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. + * See @ref sd_ble_gap_privacy_set(). + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} + * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} + * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable + * advertising, this is ignored. + * + * @retval ::NRF_SUCCESS The BLE stack has started advertising. + * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration + * tag has been reached; connectable advertiser cannot be started. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref + sd_ble_gap_adv_set_configure. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, see @ref + sd_ble_gap_whitelist_set. + * @retval ::NRF_ERROR_RESOURCES Either: + * - adv_handle is configured with connectable advertising, but the event_length parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + * - Not enough BLE role slots available. + Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) + and try again. + * - p_adv_params is configured with connectable advertising, but the event_length + parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); + +/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle The advertising handle that should stop advertising. + * + * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. + * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); + +/**@brief Update connection parameters. + * + * @details In the central role this will initiate a Link Layer connection parameter update procedure, + * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for + * the central to perform the procedure. In both cases, and regardless of success or failure, the application + * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. + * + * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the + * procedure unrequested. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CPU_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, + * the parameters in the PPCP characteristic of the GAP service will be used instead. + * If NULL is provided on a central role and in response to a @ref + * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected + * + * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, + sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Disconnect (GAP Link Termination). + * + * @details This call initiates the disconnection procedure, and its completion will be communicated to the application + * with a @ref BLE_GAP_EVT_DISCONNECTED event. + * + * @events + * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CONN_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref + * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). + * + * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + */ +SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); + +/**@brief Set the radio's transmit power. + * + * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for + * possible roles. + * @param[in] handle The handle parameter is interpreted depending on role: + * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. + * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, + * will use the specified transmit power, and include it in the advertising packet headers if + * @ref ble_gap_adv_properties_t::include_tx_power set. + * - For all other roles handle is ignored. + * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). + * + * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. + * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. + * Setting these values on a chip that does not support them will result in undefined behaviour. + * @note The initiator will have the same transmit power as the scanner. + * @note When a connection is created it will inherit the transmit power from the initiator or + * advertiser leading to the connection. + * + * @retval ::NRF_SUCCESS Successfully changed the transmit power. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); + +/**@brief Set GAP Appearance value. + * + * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); + +/**@brief Get GAP Appearance value. + * + * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); + +/**@brief Set GAP Peripheral Preferred Connection Parameters. + * + * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Get GAP Peripheral Preferred Connection Parameters. + * + * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); + +/**@brief Set GAP device name. + * + * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), + * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. + * + * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. + * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. + * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or + * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). + * + * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, + sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); + +/**@brief Get GAP device name. + * + * @note If the device name is longer than the size of the supplied buffer, + * p_len will return the complete device name length, + * and not the number of bytes actually returned in p_dev_name. + * The application may use this information to allocate a suitable buffer size. + * + * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to + * NULL to obtain the complete device name length. + * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. + * + * @retval ::NRF_SUCCESS GAP device name retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); + +/**@brief Initiate the GAP Authentication procedure. + * + * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), + * otherwise in the peripheral role, an SMP Security Request will be sent. + * + * @events + * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be + * generated:} + * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} + * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} + * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} + * @event{@ref BLE_GAP_EVT_KEY_PRESSED} + * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} + * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} + * @event{@ref BLE_GAP_EVT_AUTH_STATUS} + * @event{@ref BLE_GAP_EVT_TIMEOUT} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the + * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. + * In the central role, this pointer may be NULL to reject a Security Request. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - An encryption is already executing or queued. + * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is + * reached. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. + */ +SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, + sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); + +/**@brief Reply with GAP security parameters. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. + * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be + * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. + * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or + * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this + * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. + * Note that the SoftDevice expects the application to provide memory for storing the + * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The + * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application + * upon reception of the + * @ref BLE_GAP_EVT_AUTH_STATUS event. + * + * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + */ +SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, + sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, + ble_gap_sec_keyset_t const *p_sec_keyset)); + +/**@brief Reply with an authentication key. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, + * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. + * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. + * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL + * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, + * then a 16-byte OOB key value in little-endian format. + * + * @retval ::NRF_SUCCESS Authentication key successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, + sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); + +/**@brief Reply with an LE Secure connections DHKey. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dhkey LE Secure Connections DHKey. + * + * @retval ::NRF_SUCCESS DHKey successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - The peer is not authenticated. + * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, + sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); + +/**@brief Notify the peer of a local keypress. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. + * + * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested. + * - Passkey has not been entered. + * - Keypresses have not been enabled by both peers. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. + */ +SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); + +/**@brief Generate a set of OOB data to send to a peer out of band. + * + * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has + * already been established, the one used during connection setup). The application may manually overwrite it with an updated + * value. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. + * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. + * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. + * + * @retval ::NRF_SUCCESS OOB data successfully generated. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, + sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, + ble_gap_lesc_oob_data_t *p_oobd_own)); + +/**@brief Provide the OOB data sent/received out of band. + * + * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. + * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this + * function. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. + * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. + * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. + * Must correspond to @ref ble_gap_sec_params_t::oob flag + * in @ref sd_ble_gap_authenticate in the central role or + * in @ref sd_ble_gap_sec_params_reply in the peripheral role. + * + * @retval ::NRF_SUCCESS OOB data accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested + * - Not expecting LESC OOB data + * - Have not actually exchanged passkeys. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, + sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, + ble_gap_lesc_oob_data_t const *p_oobd_peer)); + +/**@brief Initiate GAP Encryption procedure. + * + * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. + * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and + * retry. + */ +SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, + sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); + +/**@brief Reply with GAP security information. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in + * @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is + * available. + * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. + * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is + * available. + * + * @retval ::NRF_SUCCESS Successfully accepted security information. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. + * - Encryption information provided by the app without being requested. See @ref + * ble_gap_evt_sec_info_request_t::enc_info. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, + sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, + ble_gap_sign_info_t const *p_sign_info)); + +/**@brief Get the current connection security. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Current connection security successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); + +/**@brief Start reporting the received signal strength to the application. + * + * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. + * + * @events + * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is + * dependent on the settings of the threshold_dbm + * and skip_count input parameters.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are + * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. + * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref + * BLE_GAP_EVT_RSSI_CHANGED event. + * + * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); + +/**@brief Stop reporting the received signal strength. + * + * @note An RSSI change detected before the call but not yet received by the application + * may be reported after @ref sd_ble_gap_rssi_stop has been called. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); + +/**@brief Get the received signal strength for the last connection event. + * + * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND + * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. + * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. + * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. + * + * @retval ::NRF_SUCCESS Successfully read the RSSI. + * @retval ::NRF_ERROR_NOT_FOUND No sample is available. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + */ +SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); + +/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). + * + * @note A call to this function will require the application to keep the memory pointed by + * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped + * or when this function is called with another buffer. + * + * @note The scanner will automatically stop in the following cases: + * - @ref sd_ble_gap_scan_stop is called. + * - @ref sd_ble_gap_connect is called. + * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. + * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application + * access received data. The application must call this function to continue scanning, or call @ref + * sd_ble_gap_scan_stop to stop scanning. + * + * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will + * receive more reports from this advertising event. The following reports will include the old and new received data. + * + * @events + * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue + * scanning, this parameter must be NULL. + * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. + * The memory pointed to should be kept alive until the scanning is stopped. + * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. + * If the scanner receives advertising data larger than can be stored in the buffer, + * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status + * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. + * + * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Scanning is already ongoing and p_scan_params was not NULL + * - Scanning is not running and p_scan_params was NULL. + * - The scanner has timed out when this function is called to continue scanning. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. + * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again + */ +SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, + sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); + +/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). + * + * @note The buffer provided in @ref sd_ble_gap_scan_start is released. + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. + * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. + */ +SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); + +/**@brief Create a connection (GAP Link Establishment). + * + * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. + * The scanning procedure will be stopped even if the function returns an error. + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use + * whitelist, then p_peer_addr is ignored. + * @param[in] p_scan_params Pointer to scan parameters structure. + * @param[in] p_conn_params Pointer to desired connection parameters. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. + * + * @retval ::NRF_SUCCESS Successfully initiated connection procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * - Invalid parameter(s) in p_scan_params or p_conn_params. + * - Use of whitelist requested but whitelist has not been set, see @ref + * sd_ble_gap_whitelist_set. + * - Peer address was not present in the device identity list, see @ref + * sd_ble_gap_device_identities_set. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an + * existing locally initiated connect procedure, which must complete before initiating again. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::NRF_ERROR_RESOURCES Either: + * - Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. + * - The event_length parameter associated with conn_cfg_tag is too small to be able to + * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. + * Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_CONNECT, uint32_t, + sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, + ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); + +/**@brief Cancel a connection establishment. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. + * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection + * completed occurred. + */ +SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); + +/**@brief Initiate or respond to a PHY Update Procedure + * + * @details This function is used to initiate or respond to a PHY Update Procedure. It will always + * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. + * If this function is used to initiate a PHY Update procedure and the only option + * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the + * currently active PHYs in the respective directions, the SoftDevice will generate a + * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the + * procedure in the Link Layer. + * + * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, + * then the stack will select PHYs based on the peer's PHY preferences and the local link + * configuration. The PHY Update procedure will for this case result in a PHY combination + * that respects the time constraints configured with @ref sd_ble_cfg_set and the current + * link layer data length. + * + * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. + * + * If the peer does not support the PHY Update Procedure, then the resulting + * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to + * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. + * + * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status + * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or + * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. + * If the peer responds to the PHY Update procedure with invalid parameters, the status + * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. + * If the PHY Update procedure was rejected by the peer for a different reason, the status will + * contain the reason as specified by the peer. + * + * @events + * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} + * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} + * @endmscs + * + * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. + * @param[in] p_gap_phys Pointer to PHY structure. + * + * @retval ::NRF_SUCCESS Successfully requested a PHY Update. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of + * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref + * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. + * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the + * pending procedure to complete and retry. + * + */ +SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); + +/**@brief Initiate or respond to a Data Length Update Procedure. + * + * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of + * p_dl_params, the SoftDevice will choose the highest value supported in current + * configuration and connection parameters. + * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime + * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and + * MaxRxTime will be limited to maximum 2120 us. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update + * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let + * the SoftDevice automatically decide the value for that member. + * Set to NULL to use automatic values for all members. + * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not + * have enough resources or does not support the requested Data Length + * Update parameters. Ignored if NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect + * p_dl_limitation to see which parameter is not supported. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested + * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation + * to see where the limitation is. + * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the + * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. + */ +SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, + sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, + ble_gap_data_length_limitation_t *p_dl_limitation)); + +/**@brief Start the Quality of Service (QoS) channel survey module. + * + * @details The channel survey module provides measurements of the energy levels on + * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT + * events will periodically report the measured energy levels for each channel. + * + * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, + * Radio Timeslot API events and Flash API events. + * + * @note The channel survey module will attempt to do measurements so that the average interval + * between measurements will be interval_us. However due to the channel survey module + * having the lowest priority of all roles and modules, this may not be possible. In that + * case fewer than expected channel survey reports may be given. + * + * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available + * must be set. This is done using @ref sd_ble_cfg_set. + * + * @param[in] interval_us Requested average interval for the measurements and reports. See + * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set + * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel + * survey role will be scheduled at every available opportunity. + * + * @retval ::NRF_SUCCESS The module is successfully started. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the + * allowed range. + * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. + * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. + * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using + * @ref sd_ble_cfg_set. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); + +/**@brief Stop the Quality of Service (QoS) channel survey module. + * + * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this + * function is called. + * + * @retval ::NRF_SUCCESS The module is successfully stopped. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); + +/**@brief Obtain the next connection event counter value. + * + * @details The connection event counter is initialized to zero on the first connection event. The value is incremented + * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, + * Section 4.5.1. + * + * @note The connection event counter obtained through this API will be outdated if this API is called + * at the same time as the connection event counter is incremented. + * + * @note This API will always return the last connection event counter + 1. + * The actual connection event may be multiple connection events later if: + * - Slave latency is enabled and there is no data to transmit or receive. + * - Another role is scheduled with a higher priority at the same time as the next connection event. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. + * + * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, + sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); + +/**@brief Start triggering a given task on connection event start. + * + * @details When enabled, this feature will trigger a PPI task at the start of connection events. + * The application can configure the SoftDevice to trigger every N connection events starting from + * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_params Connection event trigger parameters. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. + * @retval ::NRF_ERROR_INVALID_STATE Either: + * - Trying to start connection event triggering when it is already ongoing. + * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. + * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value + to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, + sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); + +/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GAP_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatt.h b/variants/wio-tracker-wm1110/softdevice/ble_gatt.h new file mode 100644 index 000000000..df0d728fc --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gatt.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common + @{ + @brief Common definitions and prototypes for the GATT interfaces. + */ + +#ifndef BLE_GATT_H__ +#define BLE_GATT_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATT_DEFINES Defines + * @{ */ + +/** @brief Default ATT MTU, in bytes. */ +#define BLE_GATT_ATT_MTU_DEFAULT 23 + +/**@brief Invalid Attribute Handle. */ +#define BLE_GATT_HANDLE_INVALID 0x0000 + +/**@brief First Attribute Handle. */ +#define BLE_GATT_HANDLE_START 0x0001 + +/**@brief Last Attribute Handle. */ +#define BLE_GATT_HANDLE_END 0xFFFF + +/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources + * @{ */ +#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ +/** @} */ + +/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations + * @{ */ +#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ +/** @} */ + +/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags + * @{ */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ +/** @} */ + +/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations + * @{ */ +#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ +#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ +/** @} */ + +/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes + * @{ */ +#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ +#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ +#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ +#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ +#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ +#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ +#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ + 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ +#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ +#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ + 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ +#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ +#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ +#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ + 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ + */ +#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ + 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ +#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ + 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ +#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats + * @note Found at + * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + * @{ */ +#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ +#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ +#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ +#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ +#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ +#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ +#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ +#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces + * @{ + */ +#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ +#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATT_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. + */ +typedef struct { + uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. + The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + @mscs + @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + @endmscs + */ +} ble_gatt_conn_cfg_t; + +/**@brief GATT Characteristic Properties. */ +typedef struct { + /* Standard properties */ + uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ + uint8_t read : 1; /**< Reading the value permitted. */ + uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ + uint8_t write : 1; /**< Writing the value with Write Request permitted. */ + uint8_t notify : 1; /**< Notification of the value permitted. */ + uint8_t indicate : 1; /**< Indications of the value permitted. */ + uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ +} ble_gatt_char_props_t; + +/**@brief GATT Characteristic Extended Properties. */ +typedef struct { + /* Extended properties */ + uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ + uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ +} ble_gatt_char_ext_props_t; + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATT_H__ + +/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gattc.h b/variants/wio-tracker-wm1110/softdevice/ble_gattc.h new file mode 100644 index 000000000..f1df1782c --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gattc.h @@ -0,0 +1,764 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client + @{ + @brief Definitions and prototypes for the GATT Client interface. + */ + +#ifndef BLE_GATTC_H__ +#define BLE_GATTC_H__ + +#include "ble_err.h" +#include "ble_gatt.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GATTC API SVC numbers. */ +enum BLE_GATTC_SVCS { + SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ + SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ + SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ + SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ + SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ + SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ + SD_BLE_GATTC_READ, /**< Generic read. */ + SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ + SD_BLE_GATTC_WRITE, /**< Generic write. */ + SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ + SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ +}; + +/** + * @brief GATT Client Event IDs. + */ +enum BLE_GATTC_EVTS { + BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref + ble_gattc_evt_prim_srvc_disc_rsp_t. */ + BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. + */ + BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref + ble_gattc_evt_char_disc_rsp_t. */ + BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref + ble_gattc_evt_desc_disc_rsp_t. */ + BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref + ble_gattc_evt_attr_info_disc_rsp_t. */ + BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ + BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ + BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref + ble_gattc_evt_char_vals_read_rsp_t. */ + BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ + BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref + sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ + BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref + ble_gattc_evt_exchange_mtu_rsp_t. */ + BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ + BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref + ble_gattc_evt_write_cmd_tx_complete_t. */ +}; + +/**@brief GATTC Option IDs. + * IDs that uniquely identify a GATTC option. + */ +enum BLE_GATTC_OPTS { + BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTC_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC + * @{ */ +#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ +/** @} */ + +/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats + * @{ */ +#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ +#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ +/** @} */ + +/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults + * @{ */ +#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Write without Response that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTC_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for + transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ +} ble_gattc_conn_cfg_t; + +/**@brief Operation Handle Range. */ +typedef struct { + uint16_t start_handle; /**< Start Handle. */ + uint16_t end_handle; /**< End Handle. */ +} ble_gattc_handle_range_t; + +/**@brief GATT service. */ +typedef struct { + ble_uuid_t uuid; /**< Service UUID. */ + ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ +} ble_gattc_service_t; + +/**@brief GATT include. */ +typedef struct { + uint16_t handle; /**< Include Handle. */ + ble_gattc_service_t included_srvc; /**< Handle of the included service. */ +} ble_gattc_include_t; + +/**@brief GATT characteristic. */ +typedef struct { + ble_uuid_t uuid; /**< Characteristic UUID. */ + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + uint8_t char_ext_props : 1; /**< Extended properties present. */ + uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ + uint16_t handle_value; /**< Handle of the Characteristic Value. */ +} ble_gattc_char_t; + +/**@brief GATT descriptor. */ +typedef struct { + uint16_t handle; /**< Descriptor Handle. */ + ble_uuid_t uuid; /**< Descriptor UUID. */ +} ble_gattc_desc_t; + +/**@brief Write Parameters. */ +typedef struct { + uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ + uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ + uint16_t handle; /**< Handle to the attribute to be written. */ + uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ + uint16_t len; /**< Length of data in bytes. */ + uint8_t const *p_value; /**< Pointer to the value data. */ +} ble_gattc_write_params_t; + +/**@brief Attribute Information for 16-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ +} ble_gattc_attr_info16_t; + +/**@brief Attribute Information for 128-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ +} ble_gattc_attr_info128_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Service count. */ + ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_prim_srvc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Include count. */ + ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_rel_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Characteristic count. */ + ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_char_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Descriptor count. */ + ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_desc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Attribute count. */ + uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ + union { + ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + } info; /**< Attribute information union. */ +} ble_gattc_evt_attr_info_disc_rsp_t; + +/**@brief GATT read by UUID handle value pair. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ +} ble_gattc_handle_value_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ +typedef struct { + uint16_t count; /**< Handle-Value Pair Count. */ + uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ + uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref + sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. + @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_val_by_uuid_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint16_t offset; /**< Offset of the attribute data. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ +typedef struct { + uint16_t len; /**< Concatenated Attribute values length. */ + uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder + for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_vals_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ + uint16_t offset; /**< Data offset. */ + uint16_t len; /**< Data length. */ + uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_write_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ +typedef struct { + uint16_t handle; /**< Handle to which the HVx operation applies. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_hvx_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ +typedef struct { + uint16_t server_rx_mtu; /**< Server RX MTU size. */ +} ble_gattc_evt_exchange_mtu_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gattc_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of write without response transmissions completed. */ +} ble_gattc_evt_write_cmd_tx_complete_t; + +/**@brief GATTC event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint16_t + error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ + union { + ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ + ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ + ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ + ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ + ble_gattc_evt_char_val_by_uuid_read_rsp_t + char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ + ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ + ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ + ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ + ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ + ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ + ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ + ble_gattc_evt_write_cmd_tx_complete_t + write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ + } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ +} ble_gattc_evt_t; + +/**@brief UUID discovery option. + * + * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the + * Vendor Specific UUID table. Disabled by default. + * - When disabled, if a procedure initiated by + * @ref sd_ble_gattc_primary_services_discover, + * @ref sd_ble_gattc_relationships_discover, + * @ref sd_ble_gattc_characteristics_discover, + * @ref sd_ble_gattc_descriptors_discover + * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set + * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use + * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding + * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will + * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + * @retval ::NRF_SUCCESS Set successfully. + * + */ +typedef struct { + uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ +} ble_gattc_opt_uuid_disc_t; + +/**@brief Option structure for GATTC options. */ +typedef union { + ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ +} ble_gattc_opt_t; + +/** @} */ + +/** @addtogroup BLE_GATTC_FUNCTIONS Functions + * @{ */ + +/**@brief Initiate or continue a GATT Primary Service Discovery procedure. + * + * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. + * If the last service has not been reached, this function must be called again with an updated start handle value to + * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] start_handle Handle to start searching from. + * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, + sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); + +/**@brief Initiate or continue a GATT Relationship Discovery procedure. + * + * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been + * reached, this must be called again with an updated handle range to continue the search. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, + sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Discovery procedure. + * + * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, + sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. + * + * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not + * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, + sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. + * + * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_uuid Pointer to a Characteristic value UUID to read. + * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, + sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, + ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. + * + * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or + * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read + * the complete value. + * + * @events + * @event{@ref BLE_GATTC_EVT_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute to be read. + * @param[in] offset Offset into the attribute value to be read. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); + +/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. + * + * @details This function initiates a GATT Read Multiple Characteristic Values procedure. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. + * @param[in] handle_count The number of handles in p_handles. + * + * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, + sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); + +/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) + * procedure. + * + * @details This function can perform all write procedures described in GATT. + * + * @note Only one write with response procedure can be ongoing per connection at a time. + * If the application tries to write with response while another write with response procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. + * + * @note The number of Write without Response that can be queued is configured by @ref + * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. + * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without + * response is complete. + * + * @note The application can keep track of the available queue element count for writes without responses by following the + * procedure below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} + * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_write_params A pointer to a write parameters structure. + * + * @retval ::NRF_SUCCESS Successfully started the Write procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event + * and retry. + * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. + * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); + +/**@brief Send a Handle Value Confirmation to the GATT Server. + * + * @mscs + * @mmsc{@ref BLE_GATTC_HVI_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute in the indication. + * + * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); + +/**@brief Discovers information about a range of attributes on a GATT server. + * + * @events + * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} + * @endevents + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range The range of handles to request information about. + * + * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, + sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value, and + * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @events + * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] client_rx_mtu Client RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + used for this connection. + * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent request to the server. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, + sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); + +/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * + * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * @note If the buffer contains different event, behavior is undefined. + * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with + * the next Handle-Value pair in each iteration. If the function returns other than + * @ref NRF_SUCCESS, it will not be changed. + * - To start iteration, initialize the structure to zero. + * - To continue, pass the value from previous iteration. + * + * \code + * ble_gattc_handle_value_t iter; + * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); + * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) + * { + * app_handle = iter.handle; + * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); + * } + * \endcode + * + * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. + * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. + */ +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter); + +/** @} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter) +{ + uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; + uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; + uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; + + if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { + p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; + p_iter->p_value = p_next + sizeof(uint16_t); + return NRF_SUCCESS; + } else { + return NRF_ERROR_NOT_FOUND; + } +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_GATTC_H__ */ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatts.h b/variants/wio-tracker-wm1110/softdevice/ble_gatts.h new file mode 100644 index 000000000..dc94957cd --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gatts.h @@ -0,0 +1,904 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server + @{ + @brief Definitions and prototypes for the GATTS interface. + */ + +#ifndef BLE_GATTS_H__ +#define BLE_GATTS_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief GATTS API SVC numbers. + */ +enum BLE_GATTS_SVCS { + SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ + SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ + SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ + SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ + SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ + SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ + SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ + SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ + SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more + attributes. */ + SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ + SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ + SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ + SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ + SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ +}; + +/** + * @brief GATT Server Event IDs. + */ +enum BLE_GATTS_EVTS { + BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See + @ref ble_gatts_evt_write_t. */ + BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with + @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. + */ + BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref + sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ + BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. + */ + BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event + structure applies. */ + BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with + @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. + */ + BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref + ble_gatts_evt_timeout_t. */ + BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref + ble_gatts_evt_hvn_tx_complete_t. */ +}; + +/**@brief GATTS Configuration IDs. + * + * IDs that uniquely identify a GATTS configuration. + */ +enum BLE_GATTS_CFGS { + BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ + BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTS_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS + * @{ */ +#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ +#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths + * @{ */ +#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ +#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ +/** @} */ + +/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types + * @{ */ +#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ +#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ +#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types + * @{ */ +#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ +#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ +#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ +#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ +/** @} */ + +/** @defgroup BLE_GATTS_OPS GATT Server Operations + * @{ */ +#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ +/** @} */ + +/** @defgroup BLE_GATTS_VLOCS GATT Value Locations + * @{ */ +#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ +#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ +#define BLE_GATTS_VLOC_USER \ + 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ + of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ + There are no alignment requirements for the buffer. */ +/** @} */ + +/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types + * @{ */ +#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ +#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ +#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ +/** @} */ + +/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags + * @{ */ +#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ +#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ +/** @} */ + +/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values + * @{ + */ +#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ + (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size + * @{ + */ +#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ +#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ +/** @} */ + +/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults + * @{ + */ +#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTS_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. + The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ +} ble_gatts_conn_cfg_t; + +/**@brief Attribute metadata. */ +typedef struct { + ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vlen : 1; /**< Variable length attribute. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ + uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not + Write Command). */ +} ble_gatts_attr_md_t; + +/**@brief GATT Attribute. */ +typedef struct { + ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ + ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ + uint16_t init_len; /**< Initial attribute value length in bytes. */ + uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the + attribute value will be left uninitialized. */ + uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ + uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is + selected in the attribute metadata, this will have to point to a buffer that remains valid through the + lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any + other temporary location. The stack may access that memory directly without the application's + knowledge. For writable characteristics, this value must not be a location in flash memory.*/ +} ble_gatts_attr_t; + +/**@brief GATT Attribute Value. */ +typedef struct { + uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ + uint16_t offset; /**< Attribute value offset. */ + uint8_t *p_value; /**< Pointer to where value is stored or will be stored. + If value is stored in user memory, only the attribute length is updated when p_value == NULL. + Set to NULL when reading to obtain the complete length of the attribute value */ +} ble_gatts_value_t; + +/**@brief GATT Characteristic Presentation Format. */ +typedef struct { + uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ + int8_t exponent; /**< Exponent for integer data types. */ + uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ + uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ + uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ +} ble_gatts_char_pf_t; + +/**@brief GATT Characteristic metadata. */ +typedef struct { + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ + uint8_t const * + p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ + uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ + uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ + ble_gatts_char_pf_t const + *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ + ble_gatts_attr_md_t const + *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ +} ble_gatts_char_md_t; + +/**@brief GATT Characteristic Definition Handles. */ +typedef struct { + uint16_t value_handle; /**< Handle to the characteristic value. */ + uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ + uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ + uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ +} ble_gatts_char_handles_t; + +/**@brief GATT HVx parameters. */ +typedef struct { + uint16_t handle; /**< Characteristic Value Handle. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t offset; /**< Offset within the attribute value. */ + uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ + uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ +} ble_gatts_hvx_params_t; + +/**@brief GATT Authorization parameters. */ +typedef struct { + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. + Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, + as the data to be written needs to be stored and later provided by the application. */ + uint16_t offset; /**< Offset of the attribute value being updated. */ + uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ + uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ +} ble_gatts_authorize_params_t; + +/**@brief GATT Read or Write Authorize Reply parameters. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ + ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ + } params; /**< Reply Parameters. */ +} ble_gatts_rw_authorize_reply_params_t; + +/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref + BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ +} ble_gatts_cfg_service_changed_t; + +/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - @ref ble_gatts_attr_md_t::write_perm is out of range. + * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is + * not allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is + * allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. + * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported + */ +typedef struct { + ble_gatts_attr_md_t + perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ +} ble_gatts_cfg_service_changed_cccd_perm_t; + +/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The specified Attribute Table size is too small. + * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. + * - The specified Attribute Table size is not a multiple of 4. + */ +typedef struct { + uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref + BLE_GATTS_ATTR_TAB_SIZE_MIN. */ +} ble_gatts_cfg_attr_tab_size_t; + +/**@brief Config structure for GATTS configurations. */ +typedef union { + ble_gatts_cfg_service_changed_t + service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ + ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ + ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ +} ble_gatts_cfg_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ + uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref + sd_ble_gatts_value_set to finalize the writing operation. */ + uint16_t offset; /**< Offset for the write operation. */ + uint16_t len; /**< Length of the received data. */ + uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gatts_evt_write_t; + +/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint16_t offset; /**< Offset for the read operation. */ +} ble_gatts_evt_read_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ + ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ + } request; /**< Request Parameters. */ +} ble_gatts_evt_rw_authorize_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ +typedef struct { + uint8_t hint; /**< Hint (currently unused). */ +} ble_gatts_evt_sys_attr_missing_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ +} ble_gatts_evt_hvc_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ +typedef struct { + uint16_t client_rx_mtu; /**< Client RX MTU size. */ +} ble_gatts_evt_exchange_mtu_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gatts_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of notification transmissions completed. */ +} ble_gatts_evt_hvn_tx_complete_t; + +/**@brief GATTS event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ + union { + ble_gatts_evt_write_t write; /**< Write Event Parameters. */ + ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ + ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ + ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ + ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ + ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ + ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_gatts_evt_t; + +/** @} */ + +/** @addtogroup BLE_GATTS_FUNCTIONS Functions + * @{ */ + +/**@brief Add a service declaration to the Attribute Table. + * + * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to + * add a secondary service declaration that is not referenced by another service later in the Attribute Table. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. + * @param[in] p_uuid Pointer to service UUID. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a service declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); + +/**@brief Add an include declaration to the Attribute Table. + * + * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note The included service must already be present in the Attribute Table prior to this call. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID + * is used, it will be placed sequentially. + * @param[in] inc_srvc_handle Handle of the included service. + * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added an include declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + */ +SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, + sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); + +/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations + * to the Attribute Table. + * + * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and + * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format + * values. + * + * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic + * permissions. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_char_md Characteristic metadata. + * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. + * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a characteristic. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, + sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); + +/**@brief Add a descriptor to the Attribute Table. + * + * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is + * supported at this time). + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_attr Pointer to the attribute structure. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a descriptor. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, + sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); + +/**@brief Set the value of a given attribute. + * + * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully set the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + */ +SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, + sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Get the value of a given attribute. + * + * @note If the attribute value is longer than the size of the supplied buffer, + * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), + * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. + * The application may use this information to allocate a suitable buffer size. + * + * @note When retrieving system attribute values with this function, the connection handle + * may refer to an already disconnected connection. Refer to the documentation of + * @ref sd_ble_gatts_sys_attr_get for further information. + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + */ +SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, + sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Notify or Indicate an attribute value. + * + * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant + * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before + * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single + * API call. + * + * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during + * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, + * @ref NRF_ERROR_BUSY, + * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. + * The caller can check whether the value has been updated by looking at the contents of *(@ref + * ble_gatts_hvx_params_t::p_len). + * + * @note Only one indication procedure can be ongoing per connection at a time. + * If the application tries to indicate an attribute value while another indication procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. + * + * @note The number of Handle Value Notifications that can be queued is configured by @ref + * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. + * + * @note The application can keep track of the available queue element count for notifications by following the procedure + * below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} + * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_HVN_MSC} + * @mmsc{@ref BLE_GATTS_HVI_MSC} + * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data + * contains a non-NULL pointer the attribute value will be updated with the contents + * pointed by it before sending the notification or indication. If the attribute value + * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to + * contain the number of actual bytes written, else it will be set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute + * value. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application + * are available to notify and indicate. + * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and + * indicated. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions + * of the CCCD associated with this characteristic. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC + * event and retry. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. + * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); + +/**@brief Indicate the Service Changed attribute value. + * + * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute + * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will + * be issued. + * + * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. + * + * @events + * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_SC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] start_handle Start of affected attribute handle range. + * @param[in] end_handle End of affected attribute handle range. + * + * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref + * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the + * application. + * @retval ::NRF_ERROR_BUSY Procedure already in progress. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, + sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); + +/**@brief Respond to a Read/Write authorization request. + * + * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. + * + * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond + * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update + * is set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute + * Table updated. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, + * handle supplied does not match requested handle, + * or invalid data to be written provided by the application. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, + sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); + +/**@brief Update persistent system attribute information. + * + * @details Supply information about persistent system attributes to the stack, + * previously obtained using @ref sd_ble_gatts_sys_attr_get. + * This call is only allowed for active connections, and is usually + * made immediately after a connection is established with an known bonded device, + * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. + * + * p_sysattrs may point directly to the application's stored copy of the system attributes + * obtained using @ref sd_ble_gatts_sys_attr_get. + * If the pointer is NULL, the system attribute info is initialized, assuming that + * the application does not have any previously saved system attribute data for this device. + * + * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. + * + * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may + * have been completed only partially. This means that the state of the attribute table is undefined, and the application should + * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be modified. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be modified. + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. + * @param[in] len Size of data pointed by p_sys_attr_data, in octets. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully set the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref + * sd_ble_gatts_sys_attr_get. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, + sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); + +/**@brief Retrieve persistent system attribute information from the stack. + * + * @details This call is used to retrieve information about values to be stored persistently by the application + * during the lifetime of a connection or after it has been terminated. When a new connection is established with the + * same bonded device, the system attribute information retrieved with this function should be restored using using @ref + * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The + * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API + * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the + * system attributes may be written to at any time by the peer during a connection's lifetime. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be returned. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be returned. + * + * @mscs + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle of the recently terminated connection. + * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The + * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. + * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual + * length of system attribute data. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. + * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, + sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); + +/**@brief Retrieve the first valid user attribute handle. + * + * @param[out] p_handle Pointer to an integer where the handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully retrieved the handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); + +/**@brief Retrieve the attribute UUID and/or metadata. + * + * @param[in] handle Attribute handle + * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. + * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. + * + * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. + * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. + */ +SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); + +/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. + * + * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and + * - The Server RX MTU value. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @mscs + * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] server_rx_mtu Server RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + * used for this connection. + * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent response to the client. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATTS_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_hci.h b/variants/wio-tracker-wm1110/softdevice/ble_hci.h new file mode 100644 index 000000000..27f85d52e --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_hci.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ +*/ + +#ifndef BLE_HCI_H__ +#define BLE_HCI_H__ +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes + * @{ */ + +#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ +/*0x03 Hardware Failure +0x04 Page Timeout +*/ +#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ +#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ +#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ +#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ +/*0x09 Connection Limit Exceeded +0x0A Synchronous Connection Limit To A Device Exceeded +0x0B ACL Connection Already Exists*/ +#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ +/*0x0D Connection Rejected due to Limited Resources +0x0E Connection Rejected Due To Security Reasons +0x0F Connection Rejected due to Unacceptable BD_ADDR +0x10 Connection Accept Timeout Exceeded +0x11 Unsupported Feature or Parameter Value*/ +#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ +#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ + 0x14 /**< Remote Device Terminated Connection due to low \ + resources.*/ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ +#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ +/* +0x17 Repeated Attempts +0x18 Pairing Not Allowed +0x19 Unknown LMP PDU +*/ +#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ +/* +0x1B SCO Offset Rejected +0x1C SCO Interval Rejected +0x1D SCO Air Mode Rejected*/ +#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ +#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ +/*0x20 Unsupported LMP Parameter Value +0x21 Role Change Not Allowed +*/ +#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ +#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ +#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ +/*0x25 Encryption Mode Not Acceptable +0x26 Link Key Can Not be Changed +0x27 Requested QoS Not Supported +*/ +#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ +#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ +#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ +/* +0x2B Reserved +0x2C QoS Unacceptable Parameter +0x2D QoS Rejected +0x2E Channel Classification Not Supported +0x2F Insufficient Security +*/ +#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ +/* +0x31 Reserved +0x32 Role Switch Pending +0x33 Reserved +0x34 Reserved Slot Violation +0x35 Role Switch Failed +0x36 Extended Inquiry Response Too Large +0x37 Secure Simple Pairing Not Supported By Host. +0x38 Host Busy - Pairing +0x39 Connection Rejected due to No Suitable Channel Found*/ +#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ +#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ +#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ +#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ +#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_HCI_H__ + +/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h b/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h new file mode 100644 index 000000000..5f4bd277d --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) + @{ + @brief Definitions and prototypes for the L2CAP interface. + */ + +#ifndef BLE_L2CAP_H__ +#define BLE_L2CAP_H__ + +#include "ble_err.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology + * @{ + * @details + * + * L2CAP SDU + * - A data unit that the application can send/receive to/from a peer. + * + * L2CAP PDU + * - A data unit that is exchanged between local and remote L2CAP entities. + * It consists of L2CAP protocol control information and payload fields. + * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. + * + * L2CAP MTU + * - The maximum length of an L2CAP SDU. + * + * L2CAP MPS + * - The maximum length of an L2CAP PDU payload field. + * + * Credits + * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. + * @} */ + +/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief L2CAP API SVC numbers. */ +enum BLE_L2CAP_SVCS { + SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ + SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ + SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ +}; + +/**@brief L2CAP Event IDs. */ +enum BLE_L2CAP_EVTS { + BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. + \n Reply with @ref sd_ble_l2cap_ch_setup. + \n See @ref ble_l2cap_evt_ch_setup_request_t. */ + BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. + \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ + BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. + \n See @ref ble_l2cap_evt_ch_setup_t. */ + BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. + \n No additional event structure applies. */ + BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. + \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ + BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. + \n See @ref ble_l2cap_evt_ch_credit_t. */ + BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. + \n See @ref ble_l2cap_evt_ch_rx_t. */ + BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. + \n See @ref ble_l2cap_evt_ch_tx_t. */ +}; + +/** @} */ + +/**@addtogroup BLE_L2CAP_DEFINES Defines + * @{ */ + +/**@brief Maximum number of L2CAP channels per connection. */ +#define BLE_L2CAP_CH_COUNT_MAX (64) + +/**@brief Minimum L2CAP MTU, in bytes. */ +#define BLE_L2CAP_MTU_MIN (23) + +/**@brief Minimum L2CAP MPS, in bytes. */ +#define BLE_L2CAP_MPS_MIN (23) + +/**@brief Invalid CID. */ +#define BLE_L2CAP_CID_INVALID (0x0000) + +/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ +#define BLE_L2CAP_CREDITS_DEFAULT (1) + +/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources + * @{ */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ + /** @} */ + +/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes + * @{ */ +#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ +#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ +#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ +#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ +#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ +#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ +#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ + (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ +#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ +/** @} */ + +/** @} */ + +/**@addtogroup BLE_L2CAP_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note These parameters are set per connection, so all L2CAP channels created on this connection + * will have the same parameters. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. + * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. + */ +typedef struct { + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to receive on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to transmit on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per + L2CAP channel. The minimum value is one. */ + uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission + per L2CAP channel. The minimum value is one. */ + uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection + with this configuration. The default value is zero, the maximum + value is @ref BLE_L2CAP_CH_COUNT_MAX. + @note if this parameter is set to zero, all other parameters in + @ref ble_l2cap_conn_cfg_t are ignored. */ +} ble_l2cap_conn_cfg_t; + +/**@brief L2CAP channel RX parameters. */ +typedef struct { + uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to + receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be + able to receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. + - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ + ble_data_t sdu_buf; /**< SDU data buffer for reception. + - If @ref ble_data_t::p_data is non-NULL, initial credits are + issued to the peer. + - If @ref ble_data_t::p_data is NULL, no initial credits are + issued to the peer. */ +} ble_l2cap_ch_rx_params_t; + +/**@brief L2CAP channel setup parameters. */ +typedef struct { + ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting + setup of an L2CAP channel, ignored otherwise. */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. + Used when replying to a setup request of an L2CAP + channel, ignored otherwise. */ +} ble_l2cap_ch_setup_params_t; + +/**@brief L2CAP channel TX parameters. */ +typedef struct { + uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to + transmit on this L2CAP channel. */ + uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is + able to receive on this L2CAP channel. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able + to transmit on this L2CAP channel. This is effective tx_mps, + selected by the SoftDevice as + MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ + uint16_t credits; /**< Initial credits given by the peer. */ +} ble_l2cap_ch_tx_params_t; + +/**@brief L2CAP Channel Setup Request event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ +} ble_l2cap_evt_ch_setup_request_t; + +/**@brief L2CAP Channel Setup Refused event. */ +typedef struct { + uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ +} ble_l2cap_evt_ch_setup_refused_t; + +/**@brief L2CAP Channel Setup Completed event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ +} ble_l2cap_evt_ch_setup_t; + +/**@brief L2CAP Channel SDU Data Buffer Released event. */ +typedef struct { + ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice + returns SDU data buffers supplied by the application, which have + not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or + @ref BLE_L2CAP_EVT_CH_TX event. */ +} ble_l2cap_evt_ch_sdu_buf_released_t; + +/**@brief L2CAP Channel Credit received event. */ +typedef struct { + uint16_t credits; /**< Additional credits given by the peer. */ +} ble_l2cap_evt_ch_credit_t; + +/**@brief L2CAP Channel received SDU event. */ +typedef struct { + uint16_t sdu_len; /**< Total SDU length, in bytes. */ + ble_data_t sdu_buf; /**< SDU data buffer. + @note If there is not enough space in the buffer + (sdu_buf.len < sdu_len) then the rest of the SDU will be + silently discarded by the SoftDevice. */ +} ble_l2cap_evt_ch_rx_t; + +/**@brief L2CAP Channel transmitted SDU event. */ +typedef struct { + ble_data_t sdu_buf; /**< SDU data buffer. */ +} ble_l2cap_evt_ch_tx_t; + +/**@brief L2CAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occured. */ + uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or + @ref BLE_L2CAP_CID_INVALID if not present. */ + union { + ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ + ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ + ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ + ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ + ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ + ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ + ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_l2cap_evt_t; + +/** @} */ + +/**@addtogroup BLE_L2CAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set up an L2CAP channel. + * + * @details This function is used to: + * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. + * - Reply to a setup request of an L2CAP channel (if called in response to a + * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection + * Response packet to a peer. + * + * @note A call to this function will require the application to keep the SDU data buffer alive + * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or + * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} + * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: + * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP + * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * event when replying to a setup request of an L2CAP channel. + * - As output: local_cid for this channel. + * @param[in] p_params L2CAP channel parameters. + * + * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, + * see @ref ble_l2cap_conn_cfg_t::ch_count. + */ +SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, + sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); + +/**@brief Release an L2CAP channel. + * + * @details This sends a Disconnection Request packet to a peer. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * + * @retval ::NRF_SUCCESS Successfully queued request for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); + +/**@brief Receive an SDU on an L2CAP channel. + * + * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers + * for reception per L2CAP channel. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Buffer accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a + * @ref BLE_L2CAP_EVT_CH_RX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Transmit an SDU on an L2CAP channel. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for + * transmission per L2CAP channel. + * + * @note The application can keep track of the available credits for transmission by following + * the procedure below: + * - Store initial credits given by the peer in a variable. + * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Decrement the variable, which stores the currently available credits, by + * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns + * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Increment the variable, which stores the currently available credits, by additional + * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than + * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in + * @ref BLE_L2CAP_EVT_CH_SETUP event. + * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a + * @ref BLE_L2CAP_EVT_CH_TX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Advanced SDU reception flow control. + * + * @details Adjust the way the SoftDevice issues credits to the peer. + * This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set + * the value that will be used for newly created channels. + * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every + * time it starts using a new reception buffer. + * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will + * use if this function is not called. + * - If set to zero, the SoftDevice will stop issuing credits for new reception + * buffers the application provides or has provided. SDU reception that is + * currently ongoing will be allowed to complete. + * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be + * written by the SoftDevice with the number of credits that is or will be + * available to the peer. If the value written by the SoftDevice is 0 when + * credits parameter was set to 0, the peer will not be able to send more + * data until more credits are provided by calling this function again with + * credits > 0. This parameter is ignored when local_cid is set to + * @ref BLE_L2CAP_CID_INVALID. + * + * @note Application should take care when setting number of credits higher than default value. In + * this case the application must make sure that the SoftDevice always has reception buffers + * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have + * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic + * on the connection handle may be stalled until the SoftDevice again has an available + * reception buffer. This applies even if the application has used this call to set the + * credits back to default, or zero. + * + * @retval ::NRF_SUCCESS Flow control parameters accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, + sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_L2CAP_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_ranges.h b/variants/wio-tracker-wm1110/softdevice/ble_ranges.h new file mode 100644 index 000000000..2768e4996 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_ranges.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_ranges Module specific SVC, event and option number subranges + @{ + + @brief Definition of SVC, event and option number subranges for each API module. + + @note + SVCs, event and option numbers are split into subranges for each API module. + Each module receives its entire allocated range of SVC calls, whether implemented or not, + but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. + + Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, + rather than the last SVC function call actually defined and implemented. + + Specific SVC, event and option values are defined in each module's ble_.h file, + which defines names of each individual SVC code based on the range start value. +*/ + +#ifndef BLE_RANGES_H__ +#define BLE_RANGES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ +#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ + +#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ +#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ + +#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ +#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ + +#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ +#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ + +#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ +#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ + +#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ + +#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ +#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ + +#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ +#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ + +#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ +#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ + +#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ +#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ + +#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ +#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ + +#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ + +#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ +#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ + +#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ +#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ + +#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ +#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ + +#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ +#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ + +#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ +#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ + +#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ +#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ + +#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ + +#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ +#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ + +#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ +#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ + +#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ +#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ + +#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ +#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ + +#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ +#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ + +#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ +#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ + +#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ +#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_RANGES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_types.h b/variants/wio-tracker-wm1110/softdevice/ble_types.h new file mode 100644 index 000000000..db3656cfd --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_types.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_types Common types and macro definitions + @{ + + @brief Common types and macro definitions for the BLE SoftDevice. + */ + +#ifndef BLE_TYPES_H__ +#define BLE_TYPES_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_TYPES_DEFINES Defines + * @{ */ + +/** @defgroup BLE_CONN_HANDLES BLE Connection Handles + * @{ */ +#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ +#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ +/** @} */ + +/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs + * @{ */ +/* Generic UUIDs, applicable to all services */ +#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ +#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ +#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ +#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ +#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ +#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ +/* GATT specific UUIDs */ +#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ +#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ +/* GAP specific UUIDs */ +#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ +#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ +/** @} */ + +/** @defgroup BLE_UUID_TYPES Types of UUID + * @{ */ +#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ +#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ +#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ +/** @} */ + +/** @defgroup BLE_APPEARANCES Bluetooth Appearance values + * @note Retrieved from + * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * @{ */ +#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ +#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ +#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ +#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ +#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ +#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ +#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ +#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ +#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ +#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ +#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ +#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ +#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ +#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ +#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ +#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ +#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ +#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ +#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ +#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ +#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ +#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ +#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ +#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ +#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ +#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ +#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ +#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ +#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ +#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ +#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ +#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ +#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ +#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ +#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ +#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ + 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ + 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ +/** @} */ + +/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ +#define BLE_UUID_BLE_ASSIGN(instance, value) \ + do { \ + instance.type = BLE_UUID_TYPE_BLE; \ + instance.uuid = value; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ +#define BLE_UUID_COPY_PTR(dst, src) \ + do { \ + (dst)->type = (src)->type; \ + (dst)->uuid = (src)->uuid; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ +#define BLE_UUID_COPY_INST(dst, src) \ + do { \ + (dst).type = (src).type; \ + (dst).uuid = (src).uuid; \ + } while (0) + +/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) + +/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) + +/** @} */ + +/** @addtogroup BLE_TYPES_STRUCTURES Structures + * @{ */ + +/** @brief 128 bit UUID values. */ +typedef struct { + uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ +} ble_uuid128_t; + +/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ +typedef struct { + uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ + uint8_t + type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ +} ble_uuid_t; + +/**@brief Data structure. */ +typedef struct { + uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ + uint16_t len; /**< Length of the data buffer, in bytes. */ +} ble_data_t; + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* BLE_TYPES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h new file mode 100644 index 000000000..4e0bd752a --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_mbr_api Master Boot Record API + @{ + + @brief APIs for updating SoftDevice and BootLoader + +*/ + +#ifndef NRF_MBR_H__ +#define NRF_MBR_H__ + +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_MBR_DEFINES Defines + * @{ */ + +/**@brief MBR SVC Base number. */ +#define MBR_SVC_BASE (0x18) + +/**@brief Page size in words. */ +#define MBR_PAGE_SIZE_IN_WORDS (1024) + +/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. +This is the offset where the first byte of the SoftDevice hex file is written. */ +#define MBR_SIZE (0x1000) + +/** @brief Location (in the flash memory) of the bootloader address. */ +#define MBR_BOOTLOADER_ADDR (0xFF8) + +/** @brief Location (in UICR) of the bootloader address. */ +#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) + +/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ +#define MBR_PARAM_PAGE_ADDR (0xFFC) + +/** @brief Location (in UICR) of the address of the MBR parameter page. */ +#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) + +/** @} */ + +/** @addtogroup NRF_MBR_ENUMS Enumerations + * @{ */ + +/**@brief nRF Master Boot Record API SVC numbers. */ +enum NRF_MBR_SVCS { + SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ +}; + +/**@brief Possible values for ::sd_mbr_command_t.command */ +enum NRF_MBR_COMMANDS { + SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ + SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ + SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any + parameters in ::sd_mbr_command_t params.*/ + SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ + SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see + ::sd_mbr_command_vector_table_base_set_t*/ + SD_MBR_COMMAND_RESERVED, + SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see + ::sd_mbr_command_irq_forward_address_set_t*/ +}; + +/** @} */ + +/** @addtogroup NRF_MBR_TYPES Types + * @{ */ + +/**@brief This command copies part of a new SoftDevice + * + * The destination area is erased before copying. + * If dst is in the middle of a flash page, that whole flash page will be erased. + * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. + * + * The user of this function is responsible for setting the BPROT registers. + * + * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. + * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. + */ +typedef struct { + uint32_t *src; /**< Pointer to the source of data to be copied.*/ + uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ + uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ +} sd_mbr_command_copy_sd_t; + +/**@brief This command works like memcmp, but takes the length in words. + * + * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. + * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. + */ +typedef struct { + uint32_t *ptr1; /**< Pointer to block of memory. */ + uint32_t *ptr2; /**< Pointer to block of memory. */ + uint32_t len; /**< Number of 32 bit words to compare.*/ +} sd_mbr_command_compare_t; + +/**@brief This command copies a new BootLoader. + * + * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to + * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * The bootloader destination is erased by this function. + * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is + * not intended to be written. + * + * On success, this function will not return. It will start the new bootloader from reset-vector as normal. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. + * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ + uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ +} sd_mbr_command_copy_bl_t; + +/**@brief Change the address the MBR starts after a reset + * + * Once this function has been called, this address is where the MBR will start to forward + * interrupts to after a reset. + * + * To restore default forwarding, this function should be called with @ref address set to 0. If a + * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will + * be forwarded to the SoftDevice. + * + * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or + * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * On success, this function will not return. It will reset the device. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_vector_table_base_set_t; + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR + * + * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not + * change where the MBR starts after reset. + * + * @retval ::NRF_SUCCESS + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_irq_forward_address_set_t; + +/**@brief Input structure containing data used when calling ::sd_mbr_command + * + * Depending on what command value that is set, the corresponding params value type must also be + * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command + * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. + */ +typedef struct { + uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ + union { + sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ + sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ + sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ + sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ + sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ + } params; /**< Command parameters. */ +} sd_mbr_command_t; + +/** @} */ + +/** @addtogroup NRF_MBR_FUNCTIONS Functions + * @{ */ + +/**@brief Issue Master Boot Record commands + * + * Commands used when updating a SoftDevice and bootloader. + * + * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires + * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash + * page. The location of the flash page should be provided by the application in either + * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR + * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to + * store the command before reset. When an address is specified, the page it refers to must not be + * used by the application. If no address is provided by the application, i.e. both + * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use + * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. + * + * @param[in] param Pointer to a struct describing the command. + * + * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, + * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, + * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t + * + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided + * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. + */ +SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_MBR_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error.h b/variants/wio-tracker-wm1110/softdevice/nrf_error.h new file mode 100644 index 000000000..fb2831e19 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_error.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_error SoftDevice Global Error Codes + @{ + + @brief Global Error definitions +*/ + +/* Header guard */ +#ifndef NRF_ERROR_H__ +#define NRF_ERROR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions + * @{ */ +#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base +#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base +#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base +#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base +/** @} */ + +#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command +#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing +#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled +#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error +#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation +#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found +#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported +#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter +#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state +#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length +#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags +#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data +#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size +#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out +#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer +#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation +#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address +#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy +#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. +#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h new file mode 100644 index 000000000..2fd621057 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_sdm_api + @{ + @defgroup nrf_sdm_error SoftDevice Manager Error Codes + @{ + + @brief Error definitions for the SDM API +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SDM_H__ +#define NRF_ERROR_SDM_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. +#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ + (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having + ///< enabled SoftDevice interrupts). +#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ + (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SDM_H__ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h new file mode 100644 index 000000000..cbd0ba8ac --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_soc_api + @{ + @defgroup nrf_soc_error SoC Library Error Codes + @{ + + @brief Error definitions for the SoC library + +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SOC_H__ +#define NRF_ERROR_SOC_H__ + +#include "nrf_error.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* Mutex Errors */ +#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken + +/* NVIC errors */ +#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available +#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed +#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return + +/* Power errors */ +#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown +#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown +#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return + +/* Rand errors */ +#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values + +/* PPI errors */ +#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel +#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SOC_H__ +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h b/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h new file mode 100644 index 000000000..d4ab204d9 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_nvic_api SoftDevice NVIC API + * @{ + * + * @note In order to use this module, the following code has to be added to a .c file: + * \code + * nrf_nvic_state_t nrf_nvic_state = {0}; + * \endcode + * + * @note Definitions and declarations starting with __ (double underscore) in this header file are + * not intended for direct use by the application. + * + * @brief APIs for the accessing NVIC when using a SoftDevice. + * + */ + +#ifndef NRF_NVIC_H__ +#define NRF_NVIC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_NVIC_DEFINES Defines + * @{ */ + +/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions + * @{ */ + +#define __NRF_NVIC_NVMC_IRQn \ + (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ + number in the MDK. */ + +#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ + +/**@brief Interrupt priority levels used by the SoftDevice. */ +#define __NRF_NVIC_SD_IRQ_PRIOS \ + ((uint8_t)((1U << 0) /**< Priority level high .*/ \ + | (1U << 1) /**< Priority level medium. */ \ + | (1U << 4) /**< Priority level low. */ \ + )) + +/**@brief Interrupt priority levels available to the application. */ +#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ +#define __NRF_NVIC_SD_IRQS_0 \ + ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ + (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ + (1U << (uint32_t)SWI5_IRQn))) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ +#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) + +/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ +#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) + +/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ +#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) + +/**@} */ + +/**@} */ + +/**@addtogroup NRF_NVIC_VARIABLES Variables + * @{ */ + +/**@brief Type representing the state struct for the SoftDevice NVIC module. */ +typedef struct { + uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ + uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ +} nrf_nvic_state_t; + +/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an + * application source file. */ +extern nrf_nvic_state_t nrf_nvic_state; + +/**@} */ + +/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions + * @{ */ + +/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. + * + * @retval The value of PRIMASK prior to disabling the interrupts. + */ +__STATIC_INLINE int __sd_nvic_irq_disable(void); + +/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. + */ +__STATIC_INLINE void __sd_nvic_irq_enable(void); + +/**@brief Checks if IRQn is available to application + * @param[in] IRQn IRQ to check + * + * @retval 1 (true) if the IRQ to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); + +/**@brief Checks if priority is available to application + * @param[in] priority priority to check + * + * @retval 1 (true) if the priority to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); + +/**@} */ + +/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions + * @{ */ + +/**@brief Enable External Interrupt. + * @note Corresponds to NVIC_EnableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was enabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); + +/**@brief Disable External Interrupt. + * @note Corresponds to NVIC_DisableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was disabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); + +/**@brief Get Pending Interrupt. + * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. + * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. + * + * @retval ::NRF_SUCCESS The interrupt is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); + +/**@brief Set Pending Interrupt. + * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt is set pending. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); + +/**@brief Clear Pending Interrupt. + * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); + +/**@brief Set Interrupt Priority. + * @note Corresponds to NVIC_SetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * @pre Priority is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. + * @param[in] priority A valid IRQ priority for use by the application. + * + * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); + +/**@brief Get Interrupt Priority. + * @note Corresponds to NVIC_GetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. + * @param[out] p_priority Return value from NVIC_GetPriority. + * + * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); + +/**@brief System Reset. + * @note Corresponds to NVIC_SystemReset in CMSIS. + * + * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN + */ +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); + +/**@brief Enter critical region. + * + * @post Application interrupts will be disabled. + * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each + * execution context + * @sa sd_nvic_critical_region_exit + * + * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); + +/**@brief Exit critical region. + * + * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. + * @post If not in a nested critical region, the application interrupts will restored to the state before + * ::sd_nvic_critical_region_enter was called. + * + * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa + * sd_nvic_critical_region_enter. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); + +/**@} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE int __sd_nvic_irq_disable(void) +{ + int pm = __get_PRIMASK(); + __disable_irq(); + return pm; +} + +__STATIC_INLINE void __sd_nvic_irq_enable(void) +{ + __enable_irq(); +} + +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) +{ + if (IRQn < 32) { + return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; + } else if (IRQn < 64) { + return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; + } else { + return 1; + } +} + +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) +{ + if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { + return 0; + } + return 1; +} + +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= + (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); + } else { + NVIC_EnableIRQ(IRQn); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); + } else { + NVIC_DisableIRQ(IRQn); + } + + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_pending_irq = NVIC_GetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_SetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_ClearPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (!__sd_nvic_is_app_accessible_priority(priority)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + NVIC_SetPriority(IRQn, (uint32_t)priority); + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) +{ + NVIC_SystemReset(); + return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) +{ + int was_masked = __sd_nvic_irq_disable(); + if (!nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__cr_flag = 1; + nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); + NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; + nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); + NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; + *p_is_nested_critical_region = 0; + } else { + *p_is_nested_critical_region = 1; + } + if (!was_masked) { + __sd_nvic_irq_enable(); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) +{ + if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { + int was_masked = __sd_nvic_irq_disable(); + NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; + NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; + nrf_nvic_state.__cr_flag = 0; + if (!was_masked) { + __sd_nvic_irq_enable(); + } + } + + return NRF_SUCCESS; +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif + +#endif // NRF_NVIC_H__ + +/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h new file mode 100644 index 000000000..2786a86a4 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_sdm_api SoftDevice Manager API + @{ + + @brief APIs for SoftDevice management. + +*/ + +#ifndef NRF_SDM_H__ +#define NRF_SDM_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_sdm.h" +#include "nrf_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ +#ifdef NRFSOC_DOXYGEN +/// Declared in nrf_mbr.h +#define MBR_SIZE 0 +#warning test +#endif + +/** @brief The major version for the SoftDevice binary distributed with this header file. */ +#define SD_MAJOR_VERSION (7) + +/** @brief The minor version for the SoftDevice binary distributed with this header file. */ +#define SD_MINOR_VERSION (3) + +/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ +#define SD_BUGFIX_VERSION (0) + +/** @brief The SoftDevice variant of this firmware. */ +#define SD_VARIANT_ID 140 + +/** @brief The full version number for the SoftDevice binary this header file was distributed + * with, as a decimal number in the form Mmmmbbb, where: + * - M is major version (one or more digits) + * - mmm is minor version (three digits) + * - bbb is bugfix version (three digits). */ +#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) + +/** @brief SoftDevice Manager SVC Base number. */ +#define SDM_SVC_BASE 0x10 + +/** @brief SoftDevice unique string size in bytes. */ +#define SD_UNIQUE_STR_SIZE 20 + +/** @brief Invalid info field. Returned when an info field does not exist. */ +#define SDM_INFO_FIELD_INVALID (0) + +/** @brief Defines the SoftDevice Information Structure location (address) as an offset from +the start of the SoftDevice (without MBR)*/ +#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) + +/** @brief Defines the absolute SoftDevice Information Structure location (address) when the + * SoftDevice is installed just above the MBR (the usual case). */ +#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) + +/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the + * SoftDevice base address. The size value is of type uint8_t. */ +#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) + +/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. + * The size value is of type uint32_t. */ +#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) + +/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value + * is of type uint16_t. */ +#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) + +/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID + * is of type uint32_t. */ +#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) + +/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in + * the same format as @ref SD_VERSION, stored as an uint32_t. */ +#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) + +/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. + * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. + */ +#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) + +/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value + * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is + * installed just above the MBR (the usual case). */ +#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base + * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above + * the MBR (the usual case). */ +#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) + +/** @brief Defines the amount of flash that is used by the SoftDevice. + * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed + * just above the MBR (the usual case). + */ +#define SD_FLASH_SIZE 0x26000 + +/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual + * case). */ +#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the + * usual case). */ +#define SD_ID_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_VERSION_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges + * @{ */ +#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ +#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ +/**@} */ + +/**@defgroup NRF_FAULT_IDS Fault ID types + * @{ */ +#define NRF_FAULT_ID_SD_ASSERT \ + (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ +#define NRF_FAULT_ID_APP_MEMACC \ + (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ + in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ + register violation the info parameter will contain the sub-region number of \ + PREGION[0], on whose address range the disallowed write access caused the \ + memory access fault. */ +/**@} */ + +/** @} */ + +/** @addtogroup NRF_SDM_ENUMS Enumerations + * @{ */ + +/**@brief nRF SoftDevice Manager API SVC numbers. */ +enum NRF_SD_SVCS { + SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ + SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ + SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ + SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ + SVC_SDM_LAST /**< Placeholder for last SDM SVC */ +}; + +/** @} */ + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ + +/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy + * @{ */ + +#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ +#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ +#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ +#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ +#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ +#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ +#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ +#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ +#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ +#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ +#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ +#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ + +/** @} */ + +/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources + * @{ */ + +#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ +#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ +#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ + +/** @} */ + +/** @} */ + +/** @addtogroup NRF_SDM_TYPES Types + * @{ */ + +/**@brief Type representing LFCLK oscillator source. */ +typedef struct { + uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ + uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second + units (nRF52: 1-32). + @note To avoid excessive clock drift, 0.5 degrees Celsius is the + maximum temperature change allowed in one calibration timer + interval. The interval should be selected to ensure this. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ + uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration + intervals) the RC oscillator shall be calibrated if the temperature + hasn't changed. + 0: Always calibrate even if the temperature hasn't changed. + 1: Only calibrate if the temperature has changed (legacy - nRF51 only). + 2-33: Check the temperature and only calibrate if it has changed, + however calibration will take place every rc_temp_ctiv + intervals in any case. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. + + @note For nRF52, the application must ensure calibration at least once + every 8 seconds to ensure +/-500 ppm clock stability. The + recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is + rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at + least once every 8 seconds and for temperature changes of 0.5 + degrees Celsius every 4 seconds. See the Product Specification + for the nRF52 device being used for more information.*/ + uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing + windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ +} nrf_clock_lf_cfg_t; + +/**@brief Fault Handler type. + * + * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. + * The protocol stack will be in an undefined state when this happens and the only way to recover will be to + * perform a reset, using e.g. CMSIS NVIC_SystemReset(). + * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). + * + * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. + * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may + * continously transmit packets. + * + * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. + * + * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. + * @param[in] pc The program counter of the instruction that triggered the fault. + * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. + * + * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time + * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the + * one that triggered the fault. + */ +typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); + +/** @} */ + +/** @addtogroup NRF_SDM_FUNCTIONS Functions + * @{ */ + +/**@brief Enables the SoftDevice and by extension the protocol stack. + * + * @note Some care must be taken if a low frequency clock source is already running when calling this function: + * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new + * clock source will be started. + * + * @note This function has no effect when returning with an error. + * + * @post If return code is ::NRF_SUCCESS + * - SoC library and protocol stack APIs are made available. + * - A portion of RAM will be unavailable (see relevant SDS documentation). + * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). + * - Interrupts will not arrive from protected peripherals or interrupts. + * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. + * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). + * - Chosen low frequency clock source will be running. + * + * @param p_clock_lf_cfg Low frequency clock source and accuracy. + If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 + In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to + the actual characteristics of your XTAL clock. + * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. + * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has + an illegal priority level. + * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. + */ +SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, + sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); + +/**@brief Disables the SoftDevice and by extension the protocol stack. + * + * Idempotent function to disable the SoftDevice. + * + * @post SoC library and protocol stack APIs are made unavailable. + * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). + * @post All peripherals used by the SoftDevice will be reset to default values. + * @post All of RAM become available. + * @post All interrupts are forwarded to the application. + * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); + +/**@brief Check if the SoftDevice is enabled. + * + * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice + * + * This function is only intended to be called when a bootloader is enabled. + * + * @param[in] address The base address of the interrupt vector table for forwarded interrupts. + + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SDM_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_soc.h new file mode 100644 index 000000000..c649ca836 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_soc.h @@ -0,0 +1,1046 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_soc_api SoC Library API + * @{ + * + * @brief APIs for the SoC library. + * + */ + +#ifndef NRF_SOC_H__ +#define NRF_SOC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_SOC_DEFINES Defines + * @{ */ + +/**@brief The number of the lowest SVC number reserved for the SoC library. */ +#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ +#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ + +/**@brief Guaranteed time for application to process radio inactive notification. */ +#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) + +/**@brief The minimum allowed timeslot extension time. */ +#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) + +/**@brief The maximum processing time to handle a timeslot extension. */ +#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) + +/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ +#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) + +#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ +#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ +#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ + +#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ +#define SD_EVT_IRQHandler \ + (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ + The default interrupt priority for this handler is set to 6 */ +#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ +#define RADIO_NOTIFICATION_IRQHandler \ + (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ + The default interrupt priority for this handler is set to 6 */ +#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ +#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ + +#define NRF_RADIO_DISTANCE_MAX_US \ + (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ + nrf_radio_request_normal_t) in the request. */ + +#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ + (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ + +#define NRF_RADIO_START_JITTER_US \ + (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ + ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ + (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) + +/**@} */ + +/**@addtogroup NRF_SOC_ENUMS Enumerations + * @{ */ + +/**@brief The SVC numbers used by the SVC functions in the SoC library. */ +enum NRF_SOC_SVCS { + SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, + SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, + SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, + SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, + SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, + SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, + SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, + SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, + SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, + SD_FLASH_WRITE = SOC_SVC_BASE + 9, + SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, + SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, + SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, + SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, + SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, + SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, + SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, + SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, + SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, + SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, + SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, + SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, + SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, + SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, + SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, + SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, + SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, + SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, + SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, + SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, + SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, + SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, + SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, + SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, + SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, + SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, + SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, + SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, + SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, + SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, + SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, + SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, + SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, + SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, + SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, + SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, + SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, + SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, + SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 +}; + +/**@brief Possible values of a ::nrf_mutex_t. */ +enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; + +/**@brief Power modes. */ +enum NRF_POWER_MODES { + NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ + NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ +}; + +/**@brief Power failure thresholds */ +enum NRF_POWER_THRESHOLDS { + NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ +}; + +/**@brief Power failure thresholds for high voltage */ +enum NRF_POWER_THRESHOLDVDDHS { + NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ +}; + +/**@brief DC/DC converter modes. */ +enum NRF_POWER_DCDC_MODES { + NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ + NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ +}; + +/**@brief Radio notification distances. */ +enum NRF_RADIO_NOTIFICATION_DISTANCES { + NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ + NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ +}; + +/**@brief Radio notification types. */ +enum NRF_RADIO_NOTIFICATION_TYPES { + NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and + disabled. */ +}; + +/**@brief The Radio signal callback types. */ +enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { + NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ +}; + +/**@brief The actions requested by the signal callback. + * + * This code gives the SOC instructions about what action to take when the signal callback has + * returned. + */ +enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { + NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current + timeslot. Maximum execution time for this action: + @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. + This action must be started at least + @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before + the end of the timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ +}; + +/**@brief Radio timeslot high frequency clock source configuration. */ +enum NRF_RADIO_HFCLK_CFG { + NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the + external crystal for the whole duration of the timeslot. This should be the + preferred option for events that use the radio or require high timing accuracy. + @note The SoftDevice will automatically turn on and off the external crystal, + at the beginning and end of the timeslot, respectively. The crystal may also + intentionally be left running after the timeslot, in cases where it is needed + by the SoftDevice shortly after the end of the timeslot. */ + NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. + The RC oscillator may be the clock source in part or for the whole duration of the + timeslot. The RC oscillator's accuracy must therefore be taken into consideration. + @note If the application will use the radio peripheral in timeslots with this + configuration, it must make sure that the crystal is running and stable before + starting the radio. */ +}; + +/**@brief Radio timeslot priorities. */ +enum NRF_RADIO_PRIORITY { + NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ + NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ +}; + +/**@brief Radio timeslot request type. */ +enum NRF_RADIO_REQUEST_TYPE { + NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first + request in a session. */ + NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ +}; + +/**@brief SoC Events. */ +enum NRF_SOC_EVTS { + NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ + NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ + NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ + NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ + NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ + NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ + NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was + invalid. */ + NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ + NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ + NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ + NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ + NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ + NRF_EVT_NUMBER_OF_EVTS +}; + +/**@} */ + +/**@addtogroup NRF_SOC_STRUCTURES Structures + * @{ */ + +/**@brief Represents a mutex for use with the nrf_mutex functions. + * @note Accessing the value directly is not safe, use the mutex functions! + */ +typedef volatile uint8_t nrf_mutex_t; + +/**@brief Parameters for a request for a timeslot as early as possible. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ + uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref + NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ +} nrf_radio_request_earliest_t; + +/**@brief Parameters for a normal radio timeslot request. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US + microseconds). */ + uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ +} nrf_radio_request_normal_t; + +/**@brief Radio timeslot request parameters. */ +typedef struct { + uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ + union { + nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ + nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ + } params; /**< Parameter union. */ +} nrf_radio_request_t; + +/**@brief Return parameters of the radio timeslot signal callback. */ +typedef struct { + uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref + NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ + union { + struct { + nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ + } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ + struct { + uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref + NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ + } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ + } params; /**< Parameter union. */ +} nrf_radio_signal_callback_return_param_t; + +/**@brief The radio timeslot signal callback type. + * + * @note In case of invalid return parameters, the radio timeslot will automatically end + * immediately after returning from the signal callback and the + * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. + * @note The returned struct pointer must remain valid after the signal callback + * function returns. For instance, this means that it must not point to a stack variable. + * + * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. + * + * @return Pointer to structure containing action requested by the application. + */ +typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); + +/**@brief AES ECB parameter typedefs */ +typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ +typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ +typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ + +/**@brief AES ECB data structure */ +typedef struct { + soc_ecb_key_t key; /**< Encryption key. */ + soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ + soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ +} nrf_ecb_hal_data_t; + +/**@brief AES ECB block. Used to provide multiple blocks in a single call + to @ref sd_ecb_blocks_encrypt.*/ +typedef struct { + soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ + soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ + soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ +} nrf_ecb_hal_data_block_t; + +/**@} */ + +/**@addtogroup NRF_SOC_FUNCTIONS Functions + * @{ */ + +/**@brief Initialize a mutex. + * + * @param[in] p_mutex Pointer to the mutex to initialize. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); + +/**@brief Attempt to acquire a mutex. + * + * @param[in] p_mutex Pointer to the mutex to acquire. + * + * @retval ::NRF_SUCCESS The mutex was successfully acquired. + * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. + */ +SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); + +/**@brief Release a mutex. + * + * @param[in] p_mutex Pointer to the mutex to release. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); + +/**@brief Query the capacity of the application random pool. + * + * @param[out] p_pool_capacity The capacity of the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); + +/**@brief Get number of random bytes available to the application. + * + * @param[out] p_bytes_available The number of bytes currently available in the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); + +/**@brief Get random bytes from the application pool. + * + * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. + * @param[in] length Number of bytes to take from pool and place in p_buff. + * + * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. + * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes + * available. + */ +SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); + +/**@brief Gets the reset reason register. + * + * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); + +/**@brief Clears the bits of the reset reason register. + * + * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); + +/**@brief Sets the power mode when in CPU sleep. + * + * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait + * + * @retval ::NRF_SUCCESS The power mode was set. + * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. + */ +SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); + +/**@brief Puts the chip in System OFF mode. + * + * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN + */ +SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); + +/**@brief Enables or disables the power-fail comparator. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); + +/**@brief Enables or disables the USB power ready event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); + +/**@brief Enables or disables the power USB-detected event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); + +/**@brief Enables or disables the power USB-removed event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); + +/**@brief Get USB supply status register content. + * + * @param[out] usbregstatus The content of USBREGSTATUS register. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); + +/**@brief Sets the power failure comparator threshold value. + * + * @note: Power failure comparator threshold setting. This setting applies both for normal voltage + * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to + * VDDH only). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); + +/**@brief Sets the power failure comparator threshold value for high voltage. + * + * @note: Power failure comparator threshold setting for high voltage mode (supply connected to + * VDDH only). This setting does not apply for normal voltage mode (supply connected to both + * VDD and VDDH). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. + * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. + * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); + +/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. + * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); + +/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be set in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[out] p_gpregret Contents of the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). + * + * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. + */ +SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). + * + * For more details on the REG0 stage, please see product specification. + * + * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. + */ +SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); + +/**@brief Request the high frequency crystal oscillator. + * + * Will start the high frequency crystal oscillator, the startup time of the crystal varies + * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_release + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); + +/**@brief Releases the high frequency crystal oscillator. + * + * Will stop the high frequency crystal oscillator, this happens immediately. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_request + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); + +/**@brief Checks if the high frequency crystal oscillator is running. + * + * @see sd_clock_hfclk_request + * @see sd_clock_hfclk_release + * + * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); + +/**@brief Waits for an application event. + * + * An application event is either an application interrupt or a pended interrupt when the interrupt + * is disabled. + * + * When the application waits for an application event by calling this function, an interrupt that + * is enabled will be taken immediately on pending since this function will wait in thread mode, + * then the execution will return in the application's main thread. + * + * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M + * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets + * pended, this function will return to the application's main thread. + * + * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ + * in order to sleep using this function. This is only necessary for disabled interrupts, as + * the interrupt handler will clear the pending flag automatically for enabled interrupts. + * + * @note If an application interrupt has happened since the last time sd_app_evt_wait was + * called this function will return immediately and not go to sleep. This is to avoid race + * conditions that can occur when a flag is updated in the interrupt handler and processed + * in the main loop. + * + * @post An application interrupt has happened or a interrupt pending flag is set. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); + +/**@brief Get PPI channel enable register contents. + * + * @param[out] p_channel_enable The contents of the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); + +/**@brief Set PPI channel enable register. + * + * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); + +/**@brief Clear PPI channel enable register. + * + * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); + +/**@brief Assign endpoints to a PPI channel. + * + * @param[in] channel_num Number of the PPI channel to assign. + * @param[in] evt_endpoint Event endpoint of the PPI channel. + * @param[in] task_endpoint Task endpoint of the PPI channel. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, + sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); + +/**@brief Task to enable a channel group. + * + * @param[in] group_num Number of the channel group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); + +/**@brief Task to disable a channel group. + * + * @param[in] group_num Number of the PPI group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); + +/**@brief Assign PPI channels to a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[in] channel_msk Mask of the channels to assign to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); + +/**@brief Gets the PPI channels of a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[out] p_channel_msk Mask of the channels assigned to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); + +/**@brief Configures the Radio Notification signal. + * + * @note + * - The notification signal latency depends on the interrupt priority settings of SWI used + * for notification signal. + * - To ensure that the radio notification signal behaves in a consistent way, the radio + * notifications must be configured when there is no protocol stack or other SoftDevice + * activity in progress. It is recommended that the radio notification signal is + * configured directly after the SoftDevice has been enabled. + * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice + * will interrupt the application to do Radio Event preparation. + * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have + * to shorten the connection events to have time for the Radio Notification signals. + * + * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio + * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is + * recommended (but not required) to be used with + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. + * + * @param[in] distance Distance between the notification signal and start of radio activity, see @ref + * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or + * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. + * + * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. + * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all + * running activities and retry. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); + +/**@brief Encrypts a block according to the specified parameters. + * + * 128-bit AES encryption. + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input + * parameters and one output parameter). + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); + +/**@brief Encrypts multiple data blocks provided as an array of data block structures. + * + * @details: Performs 128-bit AES encryption on multiple data blocks + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in] block_count Count of blocks in the p_data_blocks array. + * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of + * @ref nrf_ecb_hal_data_block_t structures. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); + +/**@brief Gets any pending events generated by the SoC API. + * + * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. + * + * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. + * + * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. + * @retval ::NRF_ERROR_NOT_FOUND No pending events. + */ +SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); + +/**@brief Get the temperature measured on the chip + * + * This function will block until the temperature measurement is done. + * It takes around 50 us from call to return. + * + * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. + * + * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp + */ +SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); + +/**@brief Flash Write + * + * Commands to write a buffer to flash + * + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * write has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS + * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. + * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is + * protected. + * + * + * @param[in] p_dst Pointer to start of flash location to be written. + * @param[in] p_src Pointer to buffer with data to be written. + * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one + * flash page. See the device's Product Specification for details. + * + * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. + * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); + +/**@brief Flash Erase page + * + * Commands to erase a flash page + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * erase has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is + * protected. + * + * + * @param[in] page_number Page number of the page to erase + * + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); + +/**@brief Opens a session for radio timeslot requests. + * + * @note Only one session can be open at a time. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot + * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed + * by the application. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 + * interrupt occurs. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO + * interrupt occurs. + * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This + * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). + * + * @param[in] p_radio_signal_callback The signal callback. + * + * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. + * @retval ::NRF_ERROR_BUSY If session cannot be opened. + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); + +/**@brief Closes a session for radio timeslot requests. + * + * @note Any current radio timeslot will be finished before the session is closed. + * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. + * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED + * event is received. + * + * @retval ::NRF_ERROR_FORBIDDEN If session not opened. + * @retval ::NRF_ERROR_BUSY If session is currently being closed. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); + +/**@brief Requests a radio timeslot. + * + * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST + * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref + * NRF_RADIO_REQ_TYPE_EARLIEST. + * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by + * p_request->distance_us and is given relative to the start of the previous timeslot. + * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. + * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this + * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. + * The application may then try to schedule the first radio timeslot again. + * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). + * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. + * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. + * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the + * specified radio timeslot start, but this does not affect the actual start time of the timeslot. + * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency + * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is + * guaranteed to be clocked from the external crystal. + * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral + * during the radio timeslot. + * + * @param[in] p_request Pointer to the request parameters. + * + * @retval ::NRF_ERROR_FORBIDDEN Either: + * - The session is not open. + * - The session is not IDLE. + * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. + * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a + * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. + * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); + +/**@brief Write register protected by the SoftDevice + * + * This function writes to a register that is write-protected by the SoftDevice. Please refer to your + * SoftDevice Specification for more details about which registers that are protected by SoftDevice. + * This function can write to the following protected peripheral: + * - ACL + * + * @note Protected registers may be read directly. + * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in + * the register has not changed. See the Product Specification for more details about register + * properties. + * + * @param[in] p_register Pointer to register to be written. + * @param[in] value Value to be written to the register. + * + * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. + * @retval ::NRF_SUCCESS Value successfully written to register. + * + */ +SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); + +/**@} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SOC_H__ + +/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_svc.h b/variants/wio-tracker-wm1110/softdevice/nrf_svc.h new file mode 100644 index 000000000..1de44656f --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_svc.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NRF_SVC__ +#define NRF_SVC__ + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Supervisor call declaration. + * + * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. + * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. + * + * @param[in] number The SVC number to be used. + * @param[in] return_type The return type of the SVC function. + * @param[in] signature Function signature. The function can have at most four arguments. + */ + +#ifdef SVCALL_AS_NORMAL_FUNCTION +#define SVCALL(number, return_type, signature) return_type signature +#else + +#ifndef SVCALL +#if defined(__CC_ARM) +#define SVCALL(number, return_type, signature) return_type __svc(number) signature +#elif defined(__GNUC__) +#ifdef __cplusplus +#define GCC_CAST_CPP (uint16_t) +#else +#define GCC_CAST_CPP +#endif +#define SVCALL(number, return_type, signature) \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ + __attribute__((unused)) static return_type signature \ + { \ + __asm("svc %0\n" \ + "bx r14" \ + : \ + : "I"(GCC_CAST_CPP number) \ + : "r0"); \ + } \ + _Pragma("GCC diagnostic pop") + +#elif defined(__ICCARM__) +#define PRAGMA(x) _Pragma(#x) +#define SVCALL(number, return_type, signature) \ + PRAGMA(swi_number = (number)) \ + __swi return_type signature; +#else +#define SVCALL(number, return_type, signature) return_type signature +#endif +#endif // SVCALL + +#endif // SVCALL_AS_NORMAL_FUNCTION + +#ifdef __cplusplus +} +#endif +#endif // NRF_SVC__ From deb7c274c4864812f4ebc936fefb8f2f5ccffaea Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 8 Jul 2024 19:02:05 +0800 Subject: [PATCH 0678/1377] Cleanup NRF s140 Softdevice variants (#4252) Note: This idea is originally from @caveman99 and should be credited as such. Submitting as a separate PR so the work in meshtastic/firmware#4148 can be a bit cleaner and Seeed boards can build while that work is ongoing. The nrf52 boards that depend on the v7 softdevice all use the same code and linker files. Rather than duplicate the code, keep it all together with the platform. --- boards/wio-tracker-wm1110.json | 2 +- .../platform/nrf52}/nrf52840_s140_v7.ld | 0 .../platform/nrf52}/softdevice/ble.h | 0 .../platform/nrf52}/softdevice/ble_err.h | 0 .../platform/nrf52}/softdevice/ble_gap.h | 0 .../platform/nrf52}/softdevice/ble_gatt.h | 0 .../platform/nrf52}/softdevice/ble_gattc.h | 0 .../platform/nrf52}/softdevice/ble_gatts.h | 0 .../platform/nrf52}/softdevice/ble_hci.h | 0 .../platform/nrf52}/softdevice/ble_l2cap.h | 0 .../platform/nrf52}/softdevice/ble_ranges.h | 0 .../platform/nrf52}/softdevice/ble_types.h | 0 .../nrf52}/softdevice/nrf52/nrf_mbr.h | 0 .../platform/nrf52}/softdevice/nrf_error.h | 0 .../nrf52}/softdevice/nrf_error_sdm.h | 0 .../nrf52}/softdevice/nrf_error_soc.h | 0 .../platform/nrf52}/softdevice/nrf_nvic.h | 0 .../platform/nrf52}/softdevice/nrf_sdm.h | 2 +- .../platform/nrf52}/softdevice/nrf_soc.h | 0 .../platform/nrf52}/softdevice/nrf_svc.h | 0 variants/wio-sdk-wm1110/platformio.ini | 6 +-- .../wio-tracker-wm1110/nrf52840_s140_v7.ld | 38 ------------------- variants/wio-tracker-wm1110/platformio.ini | 6 +-- variants/xiao_ble/nrf52840_s140_v7.ld | 38 ------------------- variants/xiao_ble/platformio.ini | 4 +- 25 files changed, 10 insertions(+), 86 deletions(-) rename {variants/wio-sdk-wm1110 => src/platform/nrf52}/nrf52840_s140_v7.ld (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_err.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gap.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gatt.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gattc.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_gatts.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_hci.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_l2cap.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_ranges.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/ble_types.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf52/nrf_mbr.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_error.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_error_sdm.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_error_soc.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_nvic.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_sdm.h (99%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_soc.h (100%) rename {variants/xiao_ble => src/platform/nrf52}/softdevice/nrf_svc.h (100%) delete mode 100644 variants/wio-tracker-wm1110/nrf52840_s140_v7.ld delete mode 100644 variants/xiao_ble/nrf52840_s140_v7.ld diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index 04db52518..37a9186ab 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -53,6 +53,6 @@ "require_upload_port": true, "wait_for_upload_port": true }, - "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html", + "url": "https://www.seeedstudio.com/Wio-Tracker-1110-Dev-Board-p-5799.html", "vendor": "Seeed Studio" } diff --git a/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld b/src/platform/nrf52/nrf52840_s140_v7.ld similarity index 100% rename from variants/wio-sdk-wm1110/nrf52840_s140_v7.ld rename to src/platform/nrf52/nrf52840_s140_v7.ld diff --git a/variants/xiao_ble/softdevice/ble.h b/src/platform/nrf52/softdevice/ble.h similarity index 100% rename from variants/xiao_ble/softdevice/ble.h rename to src/platform/nrf52/softdevice/ble.h diff --git a/variants/xiao_ble/softdevice/ble_err.h b/src/platform/nrf52/softdevice/ble_err.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_err.h rename to src/platform/nrf52/softdevice/ble_err.h diff --git a/variants/xiao_ble/softdevice/ble_gap.h b/src/platform/nrf52/softdevice/ble_gap.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gap.h rename to src/platform/nrf52/softdevice/ble_gap.h diff --git a/variants/xiao_ble/softdevice/ble_gatt.h b/src/platform/nrf52/softdevice/ble_gatt.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gatt.h rename to src/platform/nrf52/softdevice/ble_gatt.h diff --git a/variants/xiao_ble/softdevice/ble_gattc.h b/src/platform/nrf52/softdevice/ble_gattc.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gattc.h rename to src/platform/nrf52/softdevice/ble_gattc.h diff --git a/variants/xiao_ble/softdevice/ble_gatts.h b/src/platform/nrf52/softdevice/ble_gatts.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_gatts.h rename to src/platform/nrf52/softdevice/ble_gatts.h diff --git a/variants/xiao_ble/softdevice/ble_hci.h b/src/platform/nrf52/softdevice/ble_hci.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_hci.h rename to src/platform/nrf52/softdevice/ble_hci.h diff --git a/variants/xiao_ble/softdevice/ble_l2cap.h b/src/platform/nrf52/softdevice/ble_l2cap.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_l2cap.h rename to src/platform/nrf52/softdevice/ble_l2cap.h diff --git a/variants/xiao_ble/softdevice/ble_ranges.h b/src/platform/nrf52/softdevice/ble_ranges.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_ranges.h rename to src/platform/nrf52/softdevice/ble_ranges.h diff --git a/variants/xiao_ble/softdevice/ble_types.h b/src/platform/nrf52/softdevice/ble_types.h similarity index 100% rename from variants/xiao_ble/softdevice/ble_types.h rename to src/platform/nrf52/softdevice/ble_types.h diff --git a/variants/xiao_ble/softdevice/nrf52/nrf_mbr.h b/src/platform/nrf52/softdevice/nrf52/nrf_mbr.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf52/nrf_mbr.h rename to src/platform/nrf52/softdevice/nrf52/nrf_mbr.h diff --git a/variants/xiao_ble/softdevice/nrf_error.h b/src/platform/nrf52/softdevice/nrf_error.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_error.h rename to src/platform/nrf52/softdevice/nrf_error.h diff --git a/variants/xiao_ble/softdevice/nrf_error_sdm.h b/src/platform/nrf52/softdevice/nrf_error_sdm.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_error_sdm.h rename to src/platform/nrf52/softdevice/nrf_error_sdm.h diff --git a/variants/xiao_ble/softdevice/nrf_error_soc.h b/src/platform/nrf52/softdevice/nrf_error_soc.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_error_soc.h rename to src/platform/nrf52/softdevice/nrf_error_soc.h diff --git a/variants/xiao_ble/softdevice/nrf_nvic.h b/src/platform/nrf52/softdevice/nrf_nvic.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_nvic.h rename to src/platform/nrf52/softdevice/nrf_nvic.h diff --git a/variants/xiao_ble/softdevice/nrf_sdm.h b/src/platform/nrf52/softdevice/nrf_sdm.h similarity index 99% rename from variants/xiao_ble/softdevice/nrf_sdm.h rename to src/platform/nrf52/softdevice/nrf_sdm.h index 2786a86a4..33b6cc342 100644 --- a/variants/xiao_ble/softdevice/nrf_sdm.h +++ b/src/platform/nrf52/softdevice/nrf_sdm.h @@ -141,7 +141,7 @@ the start of the SoftDevice (without MBR)*/ * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed * just above the MBR (the usual case). */ -#define SD_FLASH_SIZE 0x26000 +#define SD_FLASH_SIZE 0x27000 /** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual diff --git a/variants/xiao_ble/softdevice/nrf_soc.h b/src/platform/nrf52/softdevice/nrf_soc.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_soc.h rename to src/platform/nrf52/softdevice/nrf_soc.h diff --git a/variants/xiao_ble/softdevice/nrf_svc.h b/src/platform/nrf52/softdevice/nrf_svc.h similarity index 100% rename from variants/xiao_ble/softdevice/nrf_svc.h rename to src/platform/nrf52/softdevice/nrf_svc.h diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 619f86e1e..aa10f525d 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -1,4 +1,4 @@ -; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +; The black Wio-WM1110 Dev Kit with sensors and the WM1110 module [env:wio-sdk-wm1110] extends = nrf52840_base board = wio-sdk-wm1110 @@ -8,10 +8,10 @@ build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -board_build.ldscript = variants/wio-sdk-wm1110/nrf52840_s140_v7.ld +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld b/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld deleted file mode 100644 index 6aaeb4034..000000000 --- a/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld +++ /dev/null @@ -1,38 +0,0 @@ -/* Linker script to configure memory regions. */ - -SEARCH_DIR(.) -GROUP(-lgcc -lc -lnosys) - -MEMORY -{ - FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 - - /* SRAM required by Softdevice depend on - * - Attribute Table Size (Number of Services and Characteristics) - * - Vendor UUID count - * - Max ATT MTU - * - Concurrent connection peripheral + central + secure links - * - Event Len, HVN queue, Write CMD queue - */ - RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 -} - -SECTIONS -{ - . = ALIGN(4); - .svc_data : - { - PROVIDE(__start_svc_data = .); - KEEP(*(.svc_data)) - PROVIDE(__stop_svc_data = .); - } > RAM - - .fs_data : - { - PROVIDE(__start_fs_data = .); - KEEP(*(.fs_data)) - PROVIDE(__stop_fs_data = .); - } > RAM -} INSERT AFTER .data; - -INCLUDE "nrf52_common.ld" diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index e8c681186..5ecc414ad 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -1,12 +1,12 @@ -; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +; The red tracker Dev Board with the WM1110 module [env:wio-tracker-wm1110] extends = nrf52840_base board = wio-tracker-wm1110 ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -board_build.ldscript = variants/wio-tracker-wm1110/nrf52840_s140_v7.ld +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/xiao_ble/nrf52840_s140_v7.ld b/variants/xiao_ble/nrf52840_s140_v7.ld deleted file mode 100644 index 6aaeb4034..000000000 --- a/variants/xiao_ble/nrf52840_s140_v7.ld +++ /dev/null @@ -1,38 +0,0 @@ -/* Linker script to configure memory regions. */ - -SEARCH_DIR(.) -GROUP(-lgcc -lc -lnosys) - -MEMORY -{ - FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 - - /* SRAM required by Softdevice depend on - * - Attribute Table Size (Number of Services and Characteristics) - * - Vendor UUID count - * - Max ATT MTU - * - Concurrent connection peripheral + central + secure links - * - Event Len, HVN queue, Write CMD queue - */ - RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 -} - -SECTIONS -{ - . = ALIGN(4); - .svc_data : - { - PROVIDE(__start_svc_data = .); - KEEP(*(.svc_data)) - PROVIDE(__stop_svc_data = .); - } > RAM - - .fs_data : - { - PROVIDE(__start_fs_data = .); - KEEP(*(.fs_data)) - PROVIDE(__stop_fs_data = .); - } > RAM -} INSERT AFTER .data; - -INCLUDE "nrf52_common.ld" diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 76e91e844..6c47780d5 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -3,9 +3,9 @@ extends = nrf52840_base board = xiao_ble_sense board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Ivariants/xiao_ble/softdevice -Ivariants/xiao_ble/softdevice/nrf52 -DEBYTE_E22 -DEBYTE_E22_900M30S -DPRIVATE_HW +build_flags = ${nrf52840_base.build_flags} -Ivariants/xiao_ble -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -D EBYTE_E22 -DEBYTE_E22_900M30S -DPRIVATE_HW -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -board_build.ldscript = variants/xiao_ble/nrf52840_s140_v7.ld +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/xiao_ble> lib_deps = ${nrf52840_base.lib_deps} From d97e6b86b8bc9be3b24589795c7abac07140fb48 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 8 Jul 2024 19:03:23 +0800 Subject: [PATCH 0679/1377] Sync Wio lr1110 refresh with master (#4251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix protobuf structs handling (#4140) * Fix protobuf structs handling * Log instead of assert --------- Co-authored-by: Ben Meadors * BLE based logging (#4146) * WIP log characteristic * Bluetooth logging plumbing * Characteristic * Callback * Check for nullptr * Esp32 bluetooth impl * Formatting * Add thread name and log level * Add settings guard * Remove comments * Field name * Fixes esp32 * Open it up * Whoops * Move va_end past our logic * Use `upload_protocol = esptool` as with the other heltec devices instead of `esp-builtin` (#4151) * Standardize lat/lon position logs (#4156) * Standardize lat/lon position logs * Missed sone and condensed logs * [create-pull-request] automated change (#4157) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Pause BLE logging during want_config flow (#4162) * Update NimBLE to 1.4.2 (#4163) * Implement replies for all telemetry types based on variant tag (#4164) * Implement replies for all telemetry types based on variant tag * Remove check for `ignoreRequest`: modules can set this, don't need to check --------- Co-authored-by: Ben Meadors * Esptool is better * Explicitly set characteristic * fix INA3221 sensor (#4168) - pass wire to begin() - remove redundant setAddr() (already set in header) * Show compass on waypoint frame; clear when waypoint deleted (#4116) * Clear expired or deleted waypoint frame * Return 0 to CallbackObserver * Add a missing comment * Draw compass for waypoint frame * Display our own waypoints * [create-pull-request] automated change (#4171) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Add semihosting support for nrf52 devices (#4137) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * fix my botched merge - keep board_level = extra flag for rak3631_dbg --------- Co-authored-by: Ben Meadors * [create-pull-request] automated change (#4174) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Display alerts (#4170) * Move static functions into Screen.h, show compass during calibration * Move to _fontHeight macro to avoid collision * Move some alert functions to new alert handler * Catch missed reboot code * ESP32 fixes * Bump esp8266-oled-ssd1306 * Fixes for when a device has no screen * Use new startAlert(char*) helper class * Add EINK bits back to alert handling * Add noop class for no-display devices --------- Co-authored-by: Ben Meadors * Send file system manifest up on want_config (#4176) * Send file system manifest up on want_config * Platform specific methods * Helps to actually make the change * Clear * tell vscode, if formatting, use whatever our trunk formatter wants (#4186) without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * fix the build - would loop forever if there were no files to send (#4188) * Show owner.short_name on boot (and E-Ink sleep screen) (#4134) * Show owner.short_name on boot and sleep screen (on e-ink) * Update Screen.cpp - new line for short_name Boot screen short_name now below the region setting. Looks better on small screens. * Draw short_name on right --------- Co-authored-by: Thomas Göttgens Co-authored-by: todd-herbert Co-authored-by: Ben Meadors * nrf52 soft device will watchdog if you use ICE while BT on... (#4189) so have debugger disable bluetooth. * correct xiao_ble build preventing sx1262 init (#4191) * Force a compile time failur if FromRadio or ToRadio get larger than (#4190) a BLE packet size. We are actually very close to this threshold so important to make sure we don't accidentally pass it. * Clear vector after complete config state (#4194) * Clear after complete config * Don't collect . entries * Log file name and size * [create-pull-request] automated change (#4200) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Make the logs Colorful! (#4199) * Squash needlessly static functions (#4183) * Trim extra vprintf and filter for unprintable characters * Deprecate Router Client role (and make it Client) (#4201) * [create-pull-request] automated change (#4205) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Move waypoint (#4202) * Move waypoint screen draw into the waypoint module * Get the observer set up for the waypoint screen draw * Static squashing: screen dimensions Macros moved back to Screen.cpp, as a band-aid until we eventually move all those static functions into the Screen class. * Move getCompassDiam into Screen class (supress compiler warnings) At this stage, the method is still static, because it's used by drawNodeInfo, which has no tidy reference to our screen instance. This is probably just another band-aid until these static functions all move. * Use new getCompassDiam function in AccelerometerThread * Properly gate display code in WaypointModule --------- Co-authored-by: Todd Herbert * Fix flakey phone api transition from file manifest to complete (#4209) * Try fix flakey phone api transition from file manifest to complete * Skip * enable colors in platformio serial monitor (#4217) * When talking via serial, encapsulate log messages in protobufs if necessary (#4187) * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs --------- Co-authored-by: Ben Meadors * [create-pull-request] automated change (#4218) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fix SHT41 support (#4222) * Add SHT41 Serial to I2c Detection Code On the Seeed Wio-WM1110 Dev Kit board, the SHT41 chip was being incorrectly detected as SHT31. This patch adds the necessary serial number for the SHT41 chip to be correctly detected. fixes meshtastic/firmware#4221 * Add missing sensor read for SHT41 * Typo fix in logs - mhz - MHz (#4225) As reported by karamo, a few different places in our logs had incorrect capitalization of MHz. fixes meshtastic/firmware#4126 * New new BLE logging characteristic with LogRecord protos (#4220) * New UUID * New log radio characteristic with LogRecord protobuf * LogRecord * Merge derp * How did you get there * Trunk * Fix length * Remove assert * minor cleanup proposal (#4169) * MESHTASTIC_EXCLUDE_WIFI and HAS_WIFI cleanup... Our code was checking HAS_WIFI and the new MESHTASTIC_EXCLUDE_WIFI flags in various places (even though EXCLUDE_WIFI forces HAS_WIFI to 0). Instead just check HAS_WIFI, only use EXCLUDE_WIFI inside configuration.h * cleanup: use HAS_NETWORKING instead of HAS_WIFI || HAS_ETHERNET We already had HAS_NETWORKING as flag in MQTT to mean 'we have tcpip'. Generallize that and move it into configuration.h so that we can use it elsewhere. * Use #pragma once, because supported by gcc and all modern compilers instead of #ifdef DOTHFILE_H etc... --------- Co-authored-by: Jonathan Bennett * Add PowerMon support (#4155) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * fix linter warning * Cleanup buffer * Merge hex for wm1110 target(s) * Only sdk * Sudo * Fix exclude macros (#4233) * fix MESHTASTIC_EXCLUDE_BLUETOOTH * fix HAS_SCREEN=0 * fix MESHTASTIC_EXCLUDE_GPS * fix typo in build-nrf52.sh (#4231) chmod is the command, '+x' is the argument. * Tidy Wireless Paper variant files (#4238) * Quick tidy of pins_arduino.h Matches requests made at https://github.com/meshtastic/firmware/pull/4226#discussion_r1664183480) * Tidy variant.h * Change deprecated ADC attenuation parameter From 11dB to 12dB. Resolves compiler warning. Allegly, no impact on function: `This is deprecated, it behaves the same as `ADC_ATTEN_DB_12` * Updated raspbian CI to update apt repository ahead of libbluetooth. (#4243) * Fix BLE logging on nrf52 (#4244) * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. --------- Co-authored-by: Ben Meadors * Fix build when HAS_NETWORKING is false on nrf52 (#4237) (tested on a rak4631 by setting HAS_ETHERNET false when shrinking image) * If `toPhoneQueue` is full, still increment `fromNum` to avoid client never getting packets (#4246) * Update to SoftDevice 7.3.0 for wio-sdk-wm1110 and wio-tracker-wm1110 (#4248) * Update variant.h * Update wio-tracker-wm1110.json * Update wio-sdk-wm1110.json * Update platformio.ini * Update platformio.ini * Add files via upload * Add files via upload * Update variant.h --------- Co-authored-by: Mike Co-authored-by: Ben Meadors Co-authored-by: Mike G Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Co-authored-by: Warren Guy <5602790+warrenguy@users.noreply.github.com> Co-authored-by: todd-herbert Co-authored-by: geeksville Co-authored-by: Jonathan Bennett Co-authored-by: Alexander <156134901+Dorn8010@users.noreply.github.com> Co-authored-by: Thomas Göttgens Co-authored-by: quimnut Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com> Co-authored-by: Agent Blu, 006 Co-authored-by: Mark Trevor Birss --- .github/workflows/build_raspbian.yml | 1 + .vscode/settings.json | 5 +- arch/esp32/esp32.ini | 2 +- bin/build-nrf52.sh | 15 +- bin/mergehex | Bin 0 -> 2102544 bytes bin/s140_nrf52_7.3.0_softdevice.hex | 9726 +++++++++++++++++ bin/setup-python-for-esp-debug.sh | 12 + boards/wio-sdk-wm1110.json | 2 +- boards/wio-tracker-wm1110.json | 2 +- boards/wiscore_rak4631.json | 2 +- platformio.ini | 6 +- protobufs | 2 +- pyocd.yaml | 7 + src/AccelerometerThread.h | 36 +- src/BluetoothCommon.cpp | 6 +- src/BluetoothCommon.h | 4 +- src/ButtonThread.cpp | 5 +- src/DebugConfiguration.cpp | 2 +- src/DebugConfiguration.h | 19 +- src/FSCommon.cpp | 52 + src/FSCommon.h | 2 + src/GPSStatus.h | 2 +- src/Power.cpp | 2 +- src/PowerFSM.cpp | 17 + src/PowerMon.cpp | 45 + src/PowerMon.h | 34 + src/RedirectablePrint.cpp | 280 +- src/RedirectablePrint.h | 23 +- src/SerialConsole.cpp | 36 +- src/SerialConsole.h | 10 +- src/commands.h | 6 +- src/configuration.h | 13 +- src/detect/ScanI2CTwoWire.cpp | 4 +- src/gps/GPS.cpp | 22 +- src/gps/GPS.h | 2 + src/graphics/Screen.cpp | 452 +- src/graphics/Screen.h | 125 +- src/graphics/ScreenFonts.h | 8 +- src/main.cpp | 20 +- src/main.h | 2 + src/mesh/FloodingRouter.cpp | 3 +- src/mesh/LR11x0Interface.cpp | 3 +- src/mesh/MeshService.cpp | 7 +- src/mesh/NodeDB.cpp | 15 +- src/mesh/NodeDB.h | 4 +- src/mesh/PhoneAPI.cpp | 51 +- src/mesh/PhoneAPI.h | 17 +- src/mesh/RF95Interface.cpp | 31 +- src/mesh/RadioInterface.cpp | 3 +- src/mesh/RadioLibInterface.cpp | 21 + src/mesh/RadioLibInterface.h | 13 +- src/mesh/Router.cpp | 6 +- src/mesh/SX126xInterface.cpp | 3 +- src/mesh/SX128xInterface.cpp | 3 +- src/mesh/StreamAPI.cpp | 23 +- src/mesh/StreamAPI.h | 5 +- src/mesh/eth/ethClient.cpp | 4 + src/mesh/generated/meshtastic/config.pb.h | 14 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 3 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 5 +- src/mesh/generated/meshtastic/mesh.pb.h | 44 +- src/mesh/generated/meshtastic/portnums.pb.h | 2 + src/mesh/generated/meshtastic/powermon.pb.cpp | 17 + src/mesh/generated/meshtastic/powermon.pb.h | 138 + src/mesh/http/ContentHandler.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 2 +- src/modules/AdminModule.cpp | 16 +- src/modules/AdminModule.h | 2 +- src/modules/CannedMessageModule.cpp | 4 +- src/modules/Modules.cpp | 6 + src/modules/PositionModule.cpp | 4 +- src/modules/PowerStressModule.cpp | 77 + src/modules/PowerStressModule.h | 38 + src/modules/Telemetry/AirQualityTelemetry.cpp | 105 +- src/modules/Telemetry/AirQualityTelemetry.h | 5 + src/modules/Telemetry/DeviceTelemetry.cpp | 29 +- .../Telemetry/EnvironmentTelemetry.cpp | 105 +- src/modules/Telemetry/EnvironmentTelemetry.h | 5 + src/modules/Telemetry/PowerTelemetry.cpp | 89 +- src/modules/Telemetry/PowerTelemetry.h | 5 + .../Telemetry/Sensor/INA3221Sensor.cpp | 3 +- src/modules/WaypointModule.cpp | 150 +- src/modules/WaypointModule.h | 14 +- src/modules/esp32/PaxcounterModule.cpp | 6 +- src/modules/esp32/StoreForwardModule.cpp | 4 +- src/mqtt/MQTT.cpp | 25 +- src/mqtt/MQTT.h | 6 +- src/nimble/NimbleBluetooth.cpp | 45 +- src/nimble/NimbleBluetooth.h | 1 + src/platform/esp32/main-esp32.cpp | 29 +- src/platform/nrf52/NRF52Bluetooth.cpp | 110 +- src/platform/nrf52/NRF52Bluetooth.h | 2 +- src/platform/nrf52/main-nrf52.cpp | 35 +- src/shutdown.h | 2 +- src/sleep.cpp | 14 +- variants/heltec_wireless_paper/pins_arduino.h | 8 +- variants/heltec_wireless_paper/variant.h | 28 +- .../heltec_wireless_paper_v1/pins_arduino.h | 8 +- variants/heltec_wireless_paper_v1/variant.h | 28 +- .../heltec_wireless_tracker/platformio.ini | 4 +- variants/rak4631/platformio.ini | 92 +- variants/tlora_t3s3_v1/platformio.ini | 2 +- variants/wio-sdk-wm1110/nrf52840_s140_v7.ld | 38 + variants/wio-sdk-wm1110/platformio.ini | 16 +- variants/wio-sdk-wm1110/softdevice/ble.h | 652 ++ variants/wio-sdk-wm1110/softdevice/ble_err.h | 92 + variants/wio-sdk-wm1110/softdevice/ble_gap.h | 2895 +++++ variants/wio-sdk-wm1110/softdevice/ble_gatt.h | 232 + .../wio-sdk-wm1110/softdevice/ble_gattc.h | 764 ++ .../wio-sdk-wm1110/softdevice/ble_gatts.h | 904 ++ variants/wio-sdk-wm1110/softdevice/ble_hci.h | 135 + .../wio-sdk-wm1110/softdevice/ble_l2cap.h | 495 + .../wio-sdk-wm1110/softdevice/ble_ranges.h | 149 + .../wio-sdk-wm1110/softdevice/ble_types.h | 217 + .../wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h | 259 + .../wio-sdk-wm1110/softdevice/nrf_error.h | 90 + .../wio-sdk-wm1110/softdevice/nrf_error_sdm.h | 73 + .../wio-sdk-wm1110/softdevice/nrf_error_soc.h | 85 + variants/wio-sdk-wm1110/softdevice/nrf_nvic.h | 449 + variants/wio-sdk-wm1110/softdevice/nrf_sdm.h | 380 + variants/wio-sdk-wm1110/softdevice/nrf_soc.h | 1046 ++ variants/wio-sdk-wm1110/softdevice/nrf_svc.h | 98 + .../wio-tracker-wm1110/nrf52840_s140_v7.ld | 38 + variants/wio-tracker-wm1110/platformio.ini | 4 +- variants/wio-tracker-wm1110/softdevice/ble.h | 652 ++ .../wio-tracker-wm1110/softdevice/ble_err.h | 92 + .../wio-tracker-wm1110/softdevice/ble_gap.h | 2895 +++++ .../wio-tracker-wm1110/softdevice/ble_gatt.h | 232 + .../wio-tracker-wm1110/softdevice/ble_gattc.h | 764 ++ .../wio-tracker-wm1110/softdevice/ble_gatts.h | 904 ++ .../wio-tracker-wm1110/softdevice/ble_hci.h | 135 + .../wio-tracker-wm1110/softdevice/ble_l2cap.h | 495 + .../softdevice/ble_ranges.h | 149 + .../wio-tracker-wm1110/softdevice/ble_types.h | 217 + .../softdevice/nrf52/nrf_mbr.h | 259 + .../wio-tracker-wm1110/softdevice/nrf_error.h | 90 + .../softdevice/nrf_error_sdm.h | 73 + .../softdevice/nrf_error_soc.h | 85 + .../wio-tracker-wm1110/softdevice/nrf_nvic.h | 449 + .../wio-tracker-wm1110/softdevice/nrf_sdm.h | 380 + .../wio-tracker-wm1110/softdevice/nrf_soc.h | 1046 ++ .../wio-tracker-wm1110/softdevice/nrf_svc.h | 98 + variants/xiao_ble/platformio.ini | 2 +- version.properties | 2 +- 145 files changed, 29841 insertions(+), 847 deletions(-) create mode 100644 bin/mergehex create mode 100644 bin/s140_nrf52_7.3.0_softdevice.hex create mode 100644 bin/setup-python-for-esp-debug.sh create mode 100644 pyocd.yaml create mode 100644 src/PowerMon.cpp create mode 100644 src/PowerMon.h create mode 100644 src/mesh/generated/meshtastic/powermon.pb.cpp create mode 100644 src/mesh/generated/meshtastic/powermon.pb.h create mode 100644 src/modules/PowerStressModule.cpp create mode 100644 src/modules/PowerStressModule.h create mode 100644 variants/wio-sdk-wm1110/nrf52840_s140_v7.ld create mode 100644 variants/wio-sdk-wm1110/softdevice/ble.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_err.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gap.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatt.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gattc.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatts.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_hci.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_l2cap.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_ranges.h create mode 100644 variants/wio-sdk-wm1110/softdevice/ble_types.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_nvic.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_sdm.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_soc.h create mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_svc.h create mode 100644 variants/wio-tracker-wm1110/nrf52840_s140_v7.ld create mode 100644 variants/wio-tracker-wm1110/softdevice/ble.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_err.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gap.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatt.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gattc.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatts.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_hci.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_l2cap.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_ranges.h create mode 100644 variants/wio-tracker-wm1110/softdevice/ble_types.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_nvic.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_sdm.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_soc.h create mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_svc.h diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 697d08727..d262d8739 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -13,6 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | + apt-get update -y apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.vscode/settings.json b/.vscode/settings.json index 07e198f0a..bf9b82111 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "trunk.enableWindows": true, "files.insertFinalNewline": false, "files.trimFinalNewlines": false, - "cmake.configureOnOpen": false + "cmake.configureOnOpen": false, + "[cpp]": { + "editor.defaultFormatter": "trunk.io" + } } diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index f3eb0cbc0..58c1302da 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -44,7 +44,7 @@ lib_deps = ${networking_base.lib_deps} ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 - h2zero/NimBLE-Arduino@^1.4.1 + h2zero/NimBLE-Arduino@^1.4.2 https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index a9980f486..fa6eacd23 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -2,8 +2,8 @@ set -e -VERSION=`bin/buildinfo.py long` -SHORT_VERSION=`bin/buildinfo.py short` +VERSION=$(bin/buildinfo.py long) +SHORT_VERSION=$(bin/buildinfo.py short) OUTDIR=release/ @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* @@ -29,6 +29,15 @@ cp $DFUPKG $OUTDIR/$basename-ota.zip echo "Generating NRF52 uf2 file" SRCHEX=.pio/build/$1/firmware.hex + +# if WM1110 target, merge hex with softdevice 7.3.0 +if (echo $1 | grep -q "wio-sdk-wm1110"); then + echo "Merging with softdevice" + sudo chmod +x ./bin/mergehex + bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex + SRCHEX=.pio/build/$1/merged_fimware.hex +fi + bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 cp bin/device-install.* $OUTDIR diff --git a/bin/mergehex b/bin/mergehex new file mode 100644 index 0000000000000000000000000000000000000000..2a93c571003f60f7d94e7a588acbc00285af9354 GIT binary patch literal 2102544 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

$&*Qh7qS<%!@nUL*MI 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

jqWDRdU%3zsH=u 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�hF6bG}d+&NiOQLS&25sIl1%U zD9%4g57Z!Fcui&}xCz7phf_?-v%fhb+o$gBZ!iTlC4=E+7o}n6#UNc$=lgdI0t`8emS@J1&cNf+K3K3if27kd0GeDt3k5+rCiDT;=) z*|Ak92_`^2c}XC?=Y4g+iB`fMwZzJRUm*y4?W^|N$Fo;UBA6~W)V&-_UB(?^>P3FY zuf|^^0=$!>`cNQ`b_0rRIr0A=!uh{ru9iHk=bIu z!6abi-X*4bl@IDa2Dl?L$4{li1Rr^q1$34#Kdi9)I1yD?UW$y9Evbq@&E*Xk`JDJN zm&qJTG#q<(CHR-5_|va}u!?Ab(BB1#xLb50p#4G5EUcR!O9|>=8c4~GHpIYr8LvE9 zT2w!iR{V`FnIToeLtZ{>Taq1*3dRzukz{9b(@ko1EM@kYiE!g=>dJTTNATGCMW`}? zg!{Qt>r|F1qB0cIU0Y~cc-2t55_KDx4I&KSR*kTUeYYeYpd&K=ND!75y`3S=y#5lR zRv6vIJ2;@*h&=IT8CpuwJ))DlOC*mJ>e2eb`O)#tQcy(x9u4*Zl{NCm%>SFBP!S+X zF+cN(FCX_uK8AB-vG*QJufckZrS2I#vGtf<=LcmZ^kE03j;nx4LjHK(2pY!3z5c^b zfvvmjaCcgWQaN8rHAD|Xp!<`W(5 zqxr_BY{3vU1d6W5wB@v)??NMAg-N_|{F&I%t897#L`R0xyTouwoA58$Xtc{#=J(0r z^s<>rg#Chh*DrlZ>bQ$bN)e1q?G6ByRmDzRi1o zbBfH5jyxe>1vFv2>Djy!Db))`f=%_AooB?OhFiUh2+@dqzpn^4TWPhvWKkAoxLt|E*m>yKbWJ}m?dX98F?9S9lL8T7uLB$<0$p6 zHe?^sX{-*Dc6jc6$Gi+1LoM}gROpJR!<6$$3ZwyN*CAelL7wlm-n?mC>z&!!4_wB-&xUCD!5+o)&`t>9DSe`X%pR-H_G zGNs&wzwP6%<8aczv<}!=s_(O{)7z)bG8=zzfZ2^dO|_2%8-Li^Y)=Mz`=>hTpbg~H z@g)(D;C=SdOvffW>9I!;|3ct5xtyZLUxBl&>nDNy1ZsG26g~|WOutj$%W3TRE1v*q z!?W6e>K#EpIvDh2*J{uU&pSWKkkOw_S3mdx!+sPGG(X0`T7KZ`z2 z{LSA(9}oQa--teL<-nQ^(Z^G{zk@!GD=VOnyRNbH@!CFr4}J81aO3IYG$?ZY^zn&_ zwg24ovHIlCqK}ea|2_0^^Q?a(`snz{hUw$>?B78j!!|9TkDpy_>0{Ae5&DQ9?fWN_ z8G>E%n_H!@Ya>>hesgzL{hZ&>QR(mJ!?I}B@}lf;IV>m3VL4R>$E1kE(r#SAXL;Ap zl^S&#bJc=)`x^(dcvv4x&7uGCyX3p!o)xquroB{EoB3@)EtF5woZE<|>0ZNhEidHZ zn@Ic<@!%efRIO`&k>VuzU={LPEoF7>uW}npKcc_49a5QE+Io83X}H{&t@-NCsy2Sz zyH{8(x7O4{P|{4ENUyj0zG9;yyw>AV_m|D-&Yo``m46uRwriDFl(UvE55 zt=?UCTC(*AUB`7exkx|aca>ABus463V|tVNI<4YUI-zooDI;A`RmSf!6}Mva*}k3z zVDvHs$G6{7OIrreW-KN9%5=1kE^ahNS4!(Yjj2~-spVYM-RM#=D7x`2ey!ujbn1$^ z+T?P@{L*Ey)ELIZ@2>^gM_FFK{R34+FqU^X1cvQ*aT?<8T524wJIaH|U`@v1I@Nc$ z`lg!ls@0MuM5z<7YQ`6XgS7CBztw`+%Vugw9!=HGOdW9I8PCf2j`eT}n*vvF`%>ksPy?Y@Wmc+u9$C8nktog9i4aDzbA7_A;j^v zkBeie6S*=WyT@`N|I{R>lXCXD+4=_Y$hMT~U9%Quv)5O(@jgj_fq28oeGhBtf~pCz zbRPqg7%jK<^q&0 zqIZFOoWfmd|yV)a0O(Zemk_IMNmcF%~TNj0gWAJYl#kl9MlI< z?sl+Px*)YEO4@VaVHN^(apeK{8eLFXbByIrp7s8c_Ma8r!(+Pr*1we4*C_v zE}tJK0Ym9gRmXcplr`&V0b85{pkZE&5}J`1nN0Pv3*R&5TLUG0eEXC`{w|J>R$(nY zD0^d`ma3qoY<0#p3l9@ag{i7GRmeZ@Fdoo;uJPLTwf$n3yvMWlyMIN9@!2YLI2c@MHrB>{;+O|4ssxHxsmP+RKP)C?Pnb$)Cw zk1vX)l5mD$PB|4OSyKW~+8kqh!Jx2b!#pRmiJ?48iJq)xf82#nnQJZKI%0rNr2-56 zbHZO9pRV_6fX`gxJ@kJ{dHeGHz47K@O6b8GA2?m?y`+t#u!KTAH@G-HScJgIjb+Yi zLFf=*dL7?Mnx;1u7v(#kfKP}*GG+;M_pKsd!bF6NnNLOovF6H}!ONj>A6HZLkjX-o zOK31MMtCE3RBLJMwvmK;nwhLy)I=bV(POBQNIxN`qM& zWg<<-0=LqA_xJwtWBN?vKQN`kx4-^_oyF9rSZXlPYVt}~b0PVZy2!{U6)P{rW^&VIT&;`H5@r;jXouwB|`W=fxV(kv8!T1Tt$^%BJ)#q!J%5 z^7$M;Gg&p>_xl{L<(2WcgwD3eu=F2=!|yD~^we4Vo=%Q0^4IXjQMv5j4uo_pt+j#d zh_yLqUFKk&ZTN#1_)fUJmKW}OZFKg2o=B;JuWS{D%M6yh-BcO+dR_?)k>k z9z4S-HkXc9toDp4omu3SAa>_K6|%KQto2Uks+T$_sO^{Xzl{&7GClNer*eUcd|=he zmY^N`@~1p;U;gwR|E%amQfw;S_@&aVSGcFJ@)6=o{u}RVJXpRQ(|xFK>r~508rgcy zua*XhgVm%v+YlrSDt)?e?SI_hx$)KUisIJuCFz>gNwfUlOnB{H@|E!gOT8xaocGM+ zwLBRo!8+la$Ji6#uY@_`GfS%CZB?wp@uprP!I4N97g_)NR6?nJ;iQ7azcxS%?SPz| z#cy0L>^9fyP$?yfPd{955V^gF(Yagp>rlxo4oQxUY0*TUK*u!O*dSB zc6D9JRSG3*AgE(_!RnMG+klBkQ_vq$?cYtZ~^^U(r}P2Rpc zsLUkmdWj45(LhEu{-U9xY|yD%*-vzKEj=UCjeyz=;7q5t~+=VrY z3#nH;b_w;m*WYB#gl6d4L^^w;xMKb58P1oZZ?V)CAa7!azUMr5`<1tw%9wgNlCAFW zns{b)Ja*OGhD_(KE4+2QM7{*rI~6#?jBzH)0UXxQHc9PF!mdhS?1<1DcRf$p#IVLw zcDe#RO6Q!0x$8^t$BHnl% z4xly-TmytxW+x{DPf8gA6DKTe$ed_ZS_E>xw{OQ3Fzc08Ta(*iQ`%r2DJr@qzUE`7 zGNSDtD*E{b@*6}qNkYcbhYCCfH1sA^16l>%gm=L)7BKFFVm@fVpHLmUY&Af}aVSh| zFuHsDoBlvdP=%41Z@>mo@@pW>3Wjpd6Gj-9_JS4GUlzqH34S|3c%i8E$5<6m#$KC< z$@&wf(i(jzL&-}o=DNP^6MPGI3I-n64_-YnQ==0a9qnebPs@Uq~H+3sdtqtk~ z#acFH1VQj$z@cc$1h?r#g?#;n%w)z~O*PDC@xZ~*SL2^q3(Hq)M4?mJMoWw zin@io0b*G|$0YFHQbZg2EE;ITu;&3>U{5vSvA8^-=i4Q%CuXq1LX!5XDQERH3}vi{ zW3ZvjeK-{D+X{a{{L}wL@dp;XYfPW()nDV7h|$;my={1E=~~gqY3>YEBdE&BwXdY# zR+3ns0C58&4PqS9yPc{HnY!cXC!ZhV+I|taM-bg=V`& zl8C>PIUX&kdA!p7A=^PIVLN4=0%m4Oe-->=2A9b(aIi+jZ=l}z228kfrMvqKk$@$0 zGkb+#UVIGS_rerjg6GoD4xI@Pc~Wnq`D-tMAt70xto`wuK#z;ON;7zEy0+59W`B3`vmr>i3gj&t0F5*zQr>`Hwk~HF)mEzVlaYx2o##WFzSaDdFNBO zA+y$4{D%=*--rtUc{@?bn6J*;{WFiMVlls}%(wHKWapQm5)KAx>5uXYJc|Wu={4X& zD-2!>;4yN_IFlu;-lRB|{_Aas&erJ#R{kg&mI5{1-STWYxj(mdDkU|*2@9<|TAeZT zs5eIy^^i}p@*fd~d%p1+fye?4ZFq_xMkWSk{rOxqX5RN50Ap;?Zo-dPkVW1MNREO5 z_oz<_GJ?w#6tg3j?pE-KABiD4s&vo3z}oZIY}MVK-=8>>h&>N@1^Tc~0^RdMqYrOx zN;dp6^kJshjn;=pu}QDTvRj+6eSvCJj50cGriieSNt6i|WIEw+_uW*}Fa*G-MzFQrlpl50|>nnF{`a z`fv*Uu!lZ;BM%|jFQgCKg(P1ezOWa~&tHY>%N(OsxTrqtA6(W$A2v&7y@%uW>1b z*w=@>_2|QWHA}45M7T20hfA$Kyw)h6g;qkS4-XPvU;*}dQfaUH@FZT)g%at*igC31 z@J)h2^0`w~Y%Xi;x<;rF*W?xA9j6v5#FrVEdMLzWH=qz3dA@P_@F)WtlOvHAzpn%V6g9Hsda^-{U>>-z|BYw;!VVZ;7w}R=8i8)=mGNo!5VTqQlWu66(K8 zyXn72(T34958$~Z{d+;=` z|6a-6hVOgV|u*H^v z{%iK>c7)%y7wEqS`r|i2-*`1<@Y;0k>%R*(r2kg9qgqi7-$2XiuK#MoWmNxV6cuM< z^xtZu|C%1bpXk3?3iqP_O8vdTsJ{&ovs&kp{#z2&e@nXQzi*+3_RxP30q+O!NQW&6 z_1_OEF8#N)r~Z3%UjLm)Cmmn^Em0QEi2jS|dtz0&w?q{cYPpKT?p^=Y8_!p2xs)Ee zLH+k6fbXSpeb=(Enb$XGJunE((b11F;`SwV!EaSLyaV9RU4 z6;=z*yM@cqf)!+@M2p5N{D{!VcB|NBOSv*ZX6&MAe^w+RoT#pdNB9#xUoT=>4PxMa z;}3qa7yUPkF4H*1NnD@a3glRGzR(-2Brxa(+H0Fe?eHVbe$JKAej@>9zCSn&FpKzu zcRHg`{hi#q`a5(3>aW!sy2qDEt?VVfY(E2@uK=4Mvyi4PHV)okeng)3sx_)!>%y7M zRbD?cV-dK62N-W~1`HZDR>;t^X6md*!GOf1_sX$s;L(0VqAP}^N)5G51nX_0^Mvf}Kl+gGb*^7`&< zz_IaZZ9J2|*YN|?D%_#xMD^V_-Sd8y*LQs?L*Hd3-mY74-b`VP+IOhkXqF0t^YvYA z_75ml+^cK9E-yi`fWFojhbAEs`g@f-M4hzway{w424B9rw^ZKVZ}1Q|?@j!K^4dW4 zb*gf5sVa5c$cPQaf-RwtyuN=n-d-CA_WDy+#r;Kh71t8h8+@YDmCh_}?oj~PnuqcM zU?{p!Q%^11A9lPyS`)wv zp`X$5@Z-B)-LQT;@l;g5zNmiP^;@Z|zJ6qPZi&fW742X6Fty06!&whh=Qs;48m<}gxz z+}8PAHG~e{i*3oMgV$)!i@aY#XOy!OsySNCD9+Zss~9_XUs$3*y}cZDUFz)}ANMKB z%WoC&PA=4M%|SJ0Z-m)wSKXNXwzkrhgc%nJ-#;^vXV^ubCmv%CL?P$0)|>v(`#%W8>*0w0y!$^c>9PM~ zW}za!@%umSTJQc38ktG_AL~{B$>wz3%^Li`oDixBuhB|7-g{jz^vTzp($~c#MY6-2ZVXo&CSH|Kq*_gMwWO-?RqB zDJwB30{@Y*C%Wzb=&rBVqOVsSXY}~i$=Wkz4uCbhpeetJ7{PxMgJKaaos zeGE50emd_jzx9GbHGSj!skjeaqI`R}M8s=fbzHqr6|LZR|{vhq=Zet!O^p|gf=Ps(yk0K#NpwR!oGy{ntLrU*286tw+CY08{J_9R9iDLBhk~m6+mhWF}f74Hr$*yY}U(CEq#Znh;-bE*(g|;k4QdlHVW>wgBCZ2KR?6z6l>g@c3Z=7CJUWxyV0iE z+<0(q#}|{?eVeo{wU}u9L~hjSiS`Q}(;(ZVo`wWicQ_dzLB9vs)+D!NyzRnD-do>f z%eD&IyN(9UrT~KW+@a0jGS}}~irZ&?fxx9XTm4}H(EQqj?v-vaAJMpSt)IlA4T~4- z-_DLAg$r>Re4ta#{^WnCUmj23$3%=&=zx`Y$V zH{bpHeT(u3$9LEje+47-?NJ~x^ghE5JDLUuAH<>FHG`#ZnJamb|W`*whqDO`%raSI5%jRVQbjBly}=tOMo5VdGZUf2mI^o2KBifNd6#BK3;7r3+ez zwtv8;$wQ$`!(#0p^r3h9_O?{jUjS-YNz2y5;JI?1(fN~ss~?vzSnc5DftDwk=Qug# zQst`J$@vK3roQVytq&%$V{7V4{wJ3H6?F!Kl&9>UDWIk&H8>_EGmHC*xX7MZRqs}@ zaOWIwI>G#xFCZyHCBuHUf`|(F90qC4!AC7K^G$q27ulyj-{b*5qZ{)C;H7Xl82!~r zbF9QKB*6m&YA+|p?RYDQ3E+GhU%%Cg^khL7utxi7b{*}nkPQp$R$`?N{fMA~LKwYk zz(nYu=}z2X-2bm=u9hQH&gGXdlBhJJCw^{+oBcS2ng9%Q){j}tE*A|CHSfE#pvyS+ zg7iWbw16OrzA2Q2p)@-p3qw(KP&aHgAd8f_!O&k2$9^yL8OEhFX$_u1Tj>wXa;1+Sx^zejk0dGVTg!7;hK2$#cS=}(z#*crS;^>*JGZ1dj){eKy3hZw7Y z9=Zwdop_XA|IcH3zP_u-JZXgfH@bc24dj7sr++Z^CQi(bwf9S+BuxAT6Z8Zk<1T9e zUL80%0b!#Fyx99TMJ5LM$DL5lXg?j}m+5MMw-^kUw$CkYU)wkKgVb8E%CTu|N+VK( z7scLCU51pgt=KWxZ#sg2rEr`TZ$Fp^g?Q0E9sm5MSbBd`K2_BQKK0N0ieKIexhv6c zyd8;w;k=zuge#X+c{|WJaE2t|^P{57s{@RKl^lDWM21@CR(RW9qEm3yU}YR z1X9~#eCWOgbhhv5UAQ#Kehcwu=qkYX!L%6Sn?uBm0&~ehc{uEo^q&pbdG0EXT`~gh z@-?W_%|zrIb{S5mwi06CdpRwk1|Gg$4ZCp53;Xh^$NrJ5%J&&a#uswxLRe0Pn;oCL}5 z?L#MEywoq4w*v`8?jyA37XmCg*(1;!4qoRd^IY9BCpK!?;H86CyW?Oi?-ag+7~PN9 zL%}RnU&s8ceJ?P86V9W^!3y`gB%g^=--*z1l#Fk;H8iV+pW7P71vP}An40z}tjToH zu!y&+SA1#T!0uLF6yVYEHeXNq1>RofP0>BqfhhO*H&qj( zXJfgZ&Jq7uhM&dg>e1W`oH!5ujBtV`@iG--wFC970@K}yEr{d zzi#Ue8YNUF$XI6*X6G!^;|F$Fvku$Ko4E=aH1gD>649;^hiQE_Wppr3acqVHEWI^g z*Xl~?MZm7rV{g-A4K1Sb71)yg%|NsDpg%%3aNCz|drBTLce(%+zpf-TeA2>hI% z810#TA+|nx(ms5K{oq5YD)``IBj{mN_jfA2oLfTW#!3Q{x$Gnl-By95jiy+HpB(QI z$)zZZiDaHM=!=%);rl%vdYir5%f}`ie)k&JLR1DzAzB*t1JDJA_Xtii46LTtVE-2u z{4xcDaquqWgE>N>&Dy4oXOY{vyw%)U=l=A-wJ_%!?iKth`tQz1`xLpIkHH4P!E&W;>1(UkxqswY*L!|#?zWGwHpO!k z&kIB3jtYeN-2VCgifIGDUvBu&^8$ax$DCsF#r+jKpSyAXiWvuitz5qq=X@T2#o>JP z&-Yhc^475 zap2K^kH2CWUrV|DOn=2_5|l!$$ZkrWe-q&-Yi{{8UeW#dq%iU+`Bf z+ET3E!(XxUrvLW-iW{r*DtR}5MgM&_!e23H_q@O2am9AV(i`wse6rhrh`-_r&O+TN zf5mAO{9^u!-(U9y{1tEQW&IVe@+*Hu*+Tpk+b=5cSB%5i<@V)S-d}MNcPjqef?oU; z!Fhu5G0EH+bK*z&hX@iC&{&?#o}|5v+xUAM$1GvtdMzoCIZm*WZ0l^e8Z)Pnw701r zVVLMJLeA`XI)0Sf@H`Bm12C46Vm{M2JKH!rky&Gp;V~Net{f8BdYv<1f%~P8fsKO! zX?(76_i^xur&9vmed_vP?1ZQMmF`8%$f$x7{#sAdk$&1Fn|b=!_;K;^@#Ev)8owa# zl8J!y?YD8oX>YLcL$?pFu7O%xca9v+IQ>`n8r@?m*RJcj#$Ug`L%`Z|8DDVM{X7^k z4&PhhPS{gp@Pa#YexES^viv_5pNY)6_}IqG1KKT`$h?<7nQ=@7rz^5Ex~VP6v4P3# z87%3K=}6>mmy0v9kF;?K+ekA_v)oy0KE>EJj<)>50=oJ@Kukl}u7fbz4qcK*;H)hryp@SR$^eoeW4z01to9xJFU_j^G#IDaWIN33?87jsk zJI+lEc+^Qh)3Tq=E@8cVgFo$yD zqA_;cQi#3g8F%5=#V=1VA|0IDIV*PCm|14iZ<9{5EHm6^LxsB?VVTJNS!Cnnrrna^ zs14kpr?PtnTF9C^G9dFdnXRu*o-nyahg~M?R!%Y3IZ6WNj~&f-jF z7UK$~)0CNmlUgZ7-N6snJd@1)TE|L&f`*xII+-iSiShP674UoOlH!xTqZLQ0!zKQvR%K0an71zocF00K|hlg z8_BHs@S>M)rS0SiAGUu`*>Wbe32zn^!z?O3OlBVNQIR!o6Lp=jv=F@!s8UZtR7u_l zRbR(fExE%1skbI`{1>jlI0=gl>`rF<9LV4dmLlGboz-|NY(tqhe86J^8`~g>nScd_ z+{y{7$qYxtnoquAKWVxZQGIJ{U`}U@#%{Y(>Z;(?=+LLAH?(WpxdtQZekLu!Iy_4C( zyXwK!{`!!U*^W8L$qh@F@Y0*ci)I|=JAbQhkz2je_BRPU?VaU`y0tCkNlsR%p0hf{ zR{?b3%itq>kXK^L*!}inGLOOPA0a@P4wZJI{nNfv4m46^B|1Ej5AADZ<77tC z>q`VCBBkQk@e5$~`Aj*h!MxN*R%#FEddkdCte=+-e%Sq(fpRi^l{whF&x|bZx-mb! z8T@!tgqz6tWY!jrPxt%mVWm!HrBnC7l;Q4#f3^L}$Vt(f(`g4%-d=u!Uht2R_Vky)8} zJfSfeklB8Lt+$DB;!yDU-f7n;kwQ@cJr7*v%=@s9qh?|Tto#62_%_)ve4qLK;va4Z zUgtBvpYeBoKk75SUn2^b?p!1~m_9wm1ti=yM}J~XosluI=Xs1Z88S4ZmPhm%$b0Zy zr*2Kle#z`U$=nol%Zbq8M^5Jbrn!|D|I@_e{ufE=pdBKr3R8{5PU>5%lV4XWr z_i0N%x9(5UKQ#jmf z1do*u@uL%&r5v8NosKn0=FTdQAJvpuh?elaFD9xTIIsXS${0c(kb%n*NciFE+368E2}`YuVh%?(4LFQcN=LO)|wz z>7^|d>c`dyz8i!N8}Ap-7H9e+Vu)RC*)Cc9+QGRY_at*Al=!r`^$a1bC{gzbm8pf@ zSP^$rJ$R?t&4e3WN0N|Spd=&#{^Q?b1P~)A2%QSDW@t;Q+_x?h;&ew<-U*=3jt6qq zoc>KiODO_SD$vzt3;-rMp8wuF=c|Ja(k*^1-rqqgx@Z6BoppxZ!0cmA@hTs=w1oJ{ z#>lIZ7`UvZ0xY!<{aN-yaPfP?^x6Ewsa2%eI#}{ebq~b{)BLbLY-78^eC%c)G z9a-A-c%IvR`KI})lV~)3m+9X*8kL55%+Q6AUmNp2)8CuR61&RBk4okymeY$*G!4;> zcToYM6`wxp&re zJs998UvvB7a%sA@g;5CU#Vz~VCPwm5mmrUk&Nor+H{qkmj-P4A-GW90Q=~Ls_w5vL z0ZQ}O)4ZnscAszO+`8fUR`%1!u2TA-(9q3%jsE#TR(}U=IRIDZvO_idW{+g1TG?`X z6TwTM?hqtPXUl<2qUS_rNL50o5hbA1az&^PaWV%aMdE(PX{wVLJ--`j)r@bq56#Tn z^|w4knh!QLGsU)=DNGxkC0Cn@GHbei;Xadn2SaKYYz1`LeY>8^e;eSZhkn&v-^w1% zr&wGi9d^F#aepZ9(RItX_3$3TyrtnUF(=^|2UG4 znwU$L|4u@aiAr25YHGI=6s&3Gmx{gVakX=6&f17#dh)|BI+})IubEnNOcXNKY?fCil+Fkf^>PDBIGawHS>_Uj!BB_~( zzy$`6T~8Y_$#lAR{Q*}}H8VliOP2mE{qzsSpXK!K^wo|TE$tn6gYvnOTe-FTZ)rDr z4?WjC&fplLz7@?fH~c62_SJ@*vh$lt0aufA)L5mG4fk-S2&VYUubl(v`eC z59oV6@8e^f+|f+(NU1TJ(%9lc;-aN@ZrxSxw4|*4Z#ilG+GK zM2($9q2IXCj0romj+Zyw8wqWa(b=^quXl9)iNUb+B>V>L$20E*R8Qk)Spd#305|~v zyEJ5~W+yYJ0l@4K0KavOPqh8F<=h%X`-t?ePG9be{<{8L*uIg^eF&iYEcqNvK3OK7 zynsHfKtK=o1$0R_0nOC3VFB&S=YsDvp{?nG>@bnXm%N5|As<{<>hL+()qjR*!)A_7l# zdut2pxIsN&MjTl0@C2*d@=?STfTk2?6jJ56-|x;I~G+OvQhb?U)h*oh&cvo^PC`+c%*ZZjM$sJ0Z=vu_3d6f>R2y&`k_# z$d&QqBRFizxPzC1Bgy4*l5>bPRnMZX+sycICRsyfwY!Iz<>A`jg?PFbmybJqre=Tp z$>m+`6FbaQE{*uKqNCuA@lQS9&moTTHv_b0!K*ivVRBdZ%l9~ z(_4x=tDF2$_o0S(W~Bj=3GI3cb(cmV{l@_YWjpgwue~{iPrLOd91eqT^(RsH&N*A3 zhxek;mDRx@fGtdj!4D?~F7N8ToY@W4~Ci$4&{xJ0v&chC_10E=u6uh^3WpScJ9un87mhm_Jp-Qp?NtAcH5^c=sI-2u(k(o3u2ZqsVrqH}+IaIP%P zeGx$2kjRy6;oy6{SX)I6IH|Ki9hJvYXL4c8HP(wBbYDrmv(6*PE3_})ffdE2r{aV5 zv`N$FPwYGM!4YkI@U{H&hjoM~{y+3X_bMt&%1$t*tG5D5_4)XAqksLB2X;P4)~$(M za*2U<$lVrSzvs@N%eycCU6Mh_*FUtd{(c6EHoil~0pEiLM6!QIKIzLRV9o&KgGPZ^ z`j52XosfrT;F^R;`;)}HPx=_SHr`^rX`q|P4H=Ng4I5~T;Jt-!?`sA^=+IzvNA4^D z?vUwVr_8)HaGCcX8>X7?Zm*XsMA{vaeb*a^K{j~BKOhy`$K#>x7LQDqdL}=BcP^cU z(f^b{-2CS0sH_Y4GimITO`0-GFyIq)OOmnC^OAMW0nu|9NF{zX+7VY^*dvpvx14_b4On&-C&I7as=B;=-xOR z#&dMeao(NOynzDxNs-+c>T0&$2)lRD$i4-+XR&_L>D1|S6VW7Q1Uw6mhv_2pSI8|Uob0j7?q zm{*11XQ{V+zPdX}hGo>68LDpYZq)_$0rN$61phgU7?`L6k@+wlFCV5UK_zij;*n6U za6UkXK!O@L_N8`vbV>E8zYNs?DP(q=$`zx2OQ#R_devpP2gH zZ6fd*oiM_L4+`JOfv*^p|Mh#K^(N@-b!Sj(k|+)>jv<( zQS~pyl*reApsoKzTmN&X^-{n1n?crC3-~;nPxosAzAE!Qge{b-8&ia>PJVg3$>p#)f|JCvh#)1%X9(bCUL<^ct6 zq=0)Q&wcvQ0;Ck?%)QC1gSo{dztkD{P_k}bGB(Q0Y+u&k07Hic=PO3unMR+-!=l>-_yjD>4mAjnsozo_e;j z;_G!Z4*dPzbSkCJ_n)B7V@;h#UPVNu)mN_G$olrN_5E7)ZI-Vu3P;eN62Cv+RoNGo z74)aK@{gh|n*OW&ErsPVcCsg9zb#4Dy)@;Z#K8L!b&sO+y{r{1a-Lx8yu(O9Gc4<| z{)!=DEj!JRX1(Y^H{F7wX%+|2$He5h{eHfSuNEd;np}uV6U?Wwv5bGA{jJLGt8iMM zbZAq{cE<0m!i{k({N-HDXDzoY0e^pHDTP3j~4GH(1fIZW@o>?GYIftQRTHs@{A@tU)794KlV7>+f@0 z4?+7v>{LJ0i7-Tk-S?t8>LK(*vqN!`MzCG1&D$fei}#h+-g_Q426V5;nr%vH5Z z8ps=jf9OX}-MlH^{Ht-g4g4EkHF&e-;D#(qbMJGSpgR-dOW=q?xb0=)a&Vk>x)0Mk zo{UZ8E-ZmGoq=nW=Z8R|DRRtFMeBk97QB9}{svTm8J(Bm3Zm$o2lCmKKJs4iNXGcp zlkmhKDYWxGK;K!?_1B0zag6;m)GOjEBVQu=@EW5J&p2D`@}{o^vw*D7NpBel_vKON zLN#dwx>-)Gn@^lgb{8Q<_(16XNW=o84Sd?Djp@0`SmVo9Joy4VFW(daX=^kt4F+3c zWZsm~&cG!OiM*JG>~?#@?2jgMdqQ{5v{V}{4vtvirU4jaZkJ#wj~KA!C6Wza)=EtJ znbs$RW|4_;c_y)E{r0+E&7(4$uLAriP^)+ROq|>sero*kIF5`{#-}7!U`VMj0y^K` z%parYG4Iz6xwvH$r`;_+_x;Fd`t-~4{0Z@~iOi>dY$#)LztL&4XO(vCV&Ny!$@nj_ zLuR>O6{En2yE*L(iW7CuO?l~9|41>@V+oS{e2LH32Es1z%k*sDrw>Jnt*8HI_16Dj zzVx0zm`CU>l%{rI8K=WFs`N4;3=r*+AK^si(XIjDC(-^^@j2U$%(na*If|~n!X3d7 zi-lh6KAMJ(^w1wu?57ulk@nY@w4OQCTYrD9I&*{l`6`4T?TuULGJUis)cEsGNP>+V|6(^G)48Q*F11J0k^w_rE@p$}< z-rx`QnT+ucz(eO&;>_}tcuWwC0dvyh48cM35E)L@FzpDe`NR= zP|Kr?pqTT^(XpEWy=d$<(XgKTV&MozIj~^7*5BXPPv3BVyYpXFA^&y7^85e+p3_g< z_`X?}5xxTYhXGdrWIXEQKg`0Q*w{hZy&PUDCpceZq%fW1l_(Z9(kQ z0(Di3EKz($>aTl#%F)iicb)7h2)suy)lFNp=r^}D5)X-btCJn<{(??_jwYp+XP!dQ zS+ZT=|9SyM=b7eA+>d%J%%^vWb{e_bbn%|Af|1Vo(EtJ9Ueu3=DM`7Zr>N*o4%`R6L2> zaGqd(#7cVbUeVpcAjEl*=)?v9)?TXkixuwN4u3y@AG9Ze%RBdikOxPe$;>`})R&W+ zT&K5}!`LNXLc^5Y#p z3ZDcz+@nmRnD8I>@grLbY!_lH72U{MbQz1%*@KL=G#IT~5r2fIm{2z@RA4oLzA%uo z^&ns}L6k;5O0Q!1;bl}qx8`K{i-6>NG@oQ%$2V~1!*s!qMmBYpcU|pzFj|59Gx6Ij zE(H3*{}%j{Cw7Pb=7)NLf8cuHmvxmS2rsuy8-mYcg9hszMjjpIJAF-jQTI_0-wiOQ z7HSm>FE9hAK?_IcC~#^85sapZ{-#T+OR>=ytHdnZx3R_*2zaFap*WtVJ#9r`y?1R1Hd&oWD z`<}bO_r1utZupk|9pIb|4fhhXAWzdkyo=jh=8@7zu9E$}f~$m)`#wJ`2=pBr@h7qi z?=Xf8Wd+Zb_C(w}g?QG7Sm48hI1e?00@QR5L!g?TecaSkZ)>`JjG0XG`ky@I8&%&~ zetlcowfdD*3uLGCs84;%ZF3bgROdTCfz? zA1+g{@*4NH?=UjCqtZ44!LIG#AG=3Ol;Pq{IDaCK=&=R?yzQ-dNtmBb3!=n%Xn;s> zi^L&U36bu3RD{?j3KVR9_c4RbRs7KV(~gQxh(HK;Ec$-=7XHc_TXllJFrGUzFYyGn4*SZJWvYe+mvN|YRZOXQpvgey{ z-hS?P=@9pI3w~nP3a^E(!nBCWR z=#5`#W0nubW9Cfb5&1w#p?7SLs{=G; z7fh~Y?H_L58l&%JMJ=V!Z`U7~|A^%-bI)r3bRCDLmI;^f%$TCCSsiNqa#P=>w!S@8 zUt3t8?dUij#%^OsEL=Fo{N+J`=G7vH0{f0+(=FL}`?}3rH!~2#(%<7-_x+1MUDrs^ zzFPKnQEd_XTJj*+E?`H&pKYDaQGOgh0nc`RH}mml&?7{&))0}CB>;)FjG!9)6;9AV z%*hZEMsx-?+)f9++nLeTbiC6fj=p*Cm_*9S_BjuCF@+7JA00 ziLLu*$CQ~bz=!}XUxOZE z#|QTZt1P%j;P}opufvm0AX1A4N4B<+XV!sdG zmpCVkU}VE7D3}k>wFrUV-gpts5&~#v95W(=uy8nENwLy9d06&$a?PdL`cf{da!&Ee z*zY%Oeb@L?d9s6jfz^leP(E?S)h9gQ!HO{g^O3|9B>n_Bq)x9Rn= zmH$!n`ofC;(yOxEhxGHWrPt{EYOI8d-ckfFQ*24ZII~&b(b(#y70$XW-MH@`XH@&h zq`S}^e5@wjvZ_X@g$g(5d6-2QvX~V<*6)c$gP5NxUt|>bwCNnFO#V@uD zFbAAGMd4)TM9v{^|2P)AWD7u&=^9I)2rf99VUuKzT(}&J&9|-!lIy#r3O(-J>Jf`e zD*1Me`=zali9;*A0x-^%Veb>KY)8}e-xJ68n+^+LP-(wyY0ixVl&R64Fxz> zCP@>EVt0BKy5i|otiR9o;i8}MLbz@N8Vgs_!bO7@R5b$^-x|2a5uIou%AKf#0^V>h z-cpUzGKy!7!@!KA_*BhksGu(Kt4mtaUPn)FVe@ zsaNzOLxb99G(ma4$7$3T_osM6)nq-8%C3(of@W(K-}9T@X;11YO$sC7cc^Jcc{=*R z0K~xtGe?foH@J@8a1S=0s4TJ{g!RE2_NIpdaDxpX%mvMX<5McUa zx|tM;?ZDBcsp>g^n?cO?5wMHg3!n144+TsG5e$S-e;5^7)E`CX!2dXde?ljOLLdK! zfd3;c{{N;IUkLx7JnjSFh+gv0F#-?2vAz*lVm|6Q0!33-uVbFgUTRKT_ za2ALNJiSKaZ9p<4t4iFzOhs?y!s@L}MP0Xt`YHaNSo(Cn7k`P@ysHW#OwG9M&m%i- zdzmYZ+kDn5yq#6n$Yn#|QdtN@B+fhax=@UdZ%2fPeJA8Y)Qd~D2g!8K`x9y}@x8SF zf+=j;Z}x63Xy3r3?Qs;q014P58 z;rD2hs-$zI65ep5kKq^s-WwEG49%FD7P}`!Yijm($oDhf#UFxhb+L}J>*?a*`R{Vu zen0>shaUAo43v0mBSVL;r-BHtOAPq}X@$3~zU{E$|7xN~Wk0ackL~b+yU8j!g{(+^ znR4EhDxmpMJUwA1`XBN}^zLy?32*#%e3~w3XD{(ys1%ZyJk>#svVmlQz<=pB?wMgq z$oFsTAD@oK6nSUzZI2eWV_e-KW92a!p1TP*RmDwQ>*y4XIw|MngtN59U>XlhSBdmO z4b7z^K3(Td!BH3S7s#GxltO#H+5OEUpk4%(8(AON=u8Sosb^b^JzpJ&z-sq9KmbZ z9lSP%$4|o76+s>%_n8)}ZEOcb;_q&7So$-$;)ylcFPkK=*Mp-JNI)9ebzkaPef-Jax7Hw$D6Q?B?D42(*q+#5EZl#Cac&{5Zupm8#TJdF z+Zm_!k)`a<#dJ<)_vp)GgkbCO1*UU32ec^sKUg?{+6(MwzL3l+Z9S~OeqLHw%*esg zu0H~2XfKy`T`Rn=G%x_B*)Ngj|FB$H*Uc(t=;L$y1NMH!($Bw#KOk5iWWAM03!`_A zVa6jl)|nD3@ny^7?XMMkw=yyv}<-;3ChkfW$R7ZY1dhSv)d7xHlX3DC?!+NV=45-BIkgqwXxJC zhSXMQGE)54RL(_6q5I6_u@rJ-box(Rb5cbjduC1IfHP~+myq*!8yaPWBCKln|d zZ+oq-pG_l9_Rq8R4wX=!>e?M(KNYlmIxF(2xz?vc&8K&%Wg|}C)0DC0;e`lEYg?3) zJq<4o`Y$Delo4@ypwy3VGV^n`9xP5>>s-_+?0F{vlb*-3B~`4v*97_&!lX?=x`%|-S&1C2j`?{# z{h335K6t@0&G7|&`FRMcnG%```f-t79!pP&SCThM`3cv(nD;q(F{mQEE%jai`uuIz zwc&MH*R6Kmvi_O=i|Y?BipaaA*+=m3S{S5l6@+LmZcRZtX>=H4G8>*bab5YXE z4Q_Th6XngW&##cSRpE|DwLp%QOSNAwr8_VTrN#)|EBFZy~?^f7+Ed71J76_p$c|oGr zd0oPM0YfOd{gYoMM-lZG0FlETSdZhnPBAvf_Z_s>)kh zEMH>k3N9yynR^v$3cc%t_nq2ZA3W8T?L{BF&Xo1_!FF&>IOGJUjy796l66&8Ef)|^ zjRkBP-ZUX~*4>fX63W4?$WeonEfUbMMap4Rs1FQQ%m>wO*An{yc#+M4P&FS|%92eXWFJ?cpXBIbg@Cmg@pLE{fL!0k(I%w&=U;xe+~kD_gb~ zygH_=kJmwm1bE%Cs`ZC{r_wq_&_WkL7S-3Iv z;gT-gfHO2QCBb6LfF`9K%?rdQH>ovLFfn!(gcIA6;(xNEZQWt$FN;)%4?i9;^1S*- z>q$V4oG6CzDaU@Y#(jRFg@|GBeQi^XG~Lb=X4tJDSR;a@G6*DkCgpyD?V^QG@-$FG z6x(n8F}Vf}9y@tDMat#w34Mx-yx)g@Hur>4@2qRcR>#{vv4*}5nb?-s?)N8tx~>5V zuF-e3-WHbcz)g)%unCOnz3|I$KMrJQ+iM?Q`ve9~>zQ#DiHyzKY?iLl+Gdf^o;0Q# zva)GKt$lcz${H6;>q&fIjofA#xv?~d71$3bTVeJf8NNR&Sv55t+tR7_jyE8s=yp-w zo~YyCMV-NMDC`b4yO@o2Y&ZF4D^<`bxxK}lXVS3*0=!~=&?^-Tw{1sa^)&xd_ zjR}QWMni+lZwlDN^D^*#!b{m4(KnGx#)=8C9pKb`bY9#U_&4XFE@$2=eNzvUXW%0z z*7y`0S(@m(BR+I?GqCsX`PhurKu%_~Sn4;rz@gLFz>aqv6q~I3NZF=iBNyN?@7pI5 zl&CBvk5Yob&*l7>6=`KGLEyQYoG;=kt3rIRIK9};IR&7U2$Ux^H8~grrO9GKb62rK z2S7BxcqB6o?`$@j+z==kG1GCvY7;$a5(;6eXe(W{Q{1IrCm{~CRG2qHtQZJDemN7?SmDU8<#^9yU{T&Em%TBKNxaJjjl87B`OLMKb^~3uKODfI9>z zu0;@n3tKKm4Q{Ac$I{bOSK;tjxq*j@Gl*gGt~xUw9C1liMeeI8>U7xg)*1td%8A$1 z>dX0i%OMLk>!yIon}7x7BiV#dRw}U z-!5G9yJwP+cGeUD!Gd1DJ?8Z+|^(`PODBs*zctvHAZo_a@+VRaO6g(t!qBya5VE5eO2X zVUU!uktv}GoC`M)g+gDUT183~kO~P@%aEFTq1VfWDy^b|R>k*4Kqv~e46P|0QU;-@ zNCkm{4EvClmZ_~w`G3A^pL6d?2gLXJ|9;Oe&(q$0&e_A-Yp=ET+H0@9Hdbr~x^2vi z)`@8w^S1F8i+@6psU%X|gj)VJEo=QsWssjnjYI0>u*&2y4SZpSWn|fCu->M-`{9N^ zKw_leh6$ous!YwRm@yVYnpR?nV<V^#+hXfYhSJUX^UCE z?)zFKk3oj~u+R`|`KfTUTFCW#Z=j>kv0Hu8U^;jX#Oi zfW_hVKV=5qjsGI~6J8Ug=~{S8;(ZtCvP7}@O6L#l@JRkm^BmQpxNt!O)?${AOM%}; z8K)O>kqe6WPkNRYTQYK|!Dq*@7JML;#lix)>1gk1e1vZmH`30p;*FReN4j+shOkH& z_}Hw=#@1B0?uRL;nWCtDHu$t~O0+#ftsfWT<;Uq|6Td*G-DOP+Y!>Iok#1#ka%?0z zJZBk49YC$Ad1J??(JX=)t^A?~BD*GPg2(d6grCa(z>^xwOE)nx)|A$x1e-suCbfNQ zP2}IsnK#V$7mNw+iKq5p(fdQ$d}LY<{oKvGT|9M@IlJki`xUyFM-Xw>cu!v|AYx4&07=pfaho()V+2h2e`1JAM_7zMgJw43OmZ` zHxs0_A)wkYqPNM2Zt8fN9e{MPbxvsqAYEh!;E+FnV0HlbvdXObXnw0~t@e%aGV(+_ z0OVcns(jv?$?JCj$UEQi9&LH!9e~=;N$X}R)fT`J+5-4M^igQ1yR3K{-$~_;LX;)V z&j;{qcs12WhZ*`#SM$dOnS`l7?M?k@Z|ZN0O#Qr-n2q~2A|09f|7)JQW~P3!^oa0f z>Q5(3{a0EeWm7*z&D6h}FDg?%MN6iBik3|MCtA_6sed>6I^00PRq;2dr_3^y^-rM z_I~_`Hew>a;z;Hrt=f(p-1)KQ?w8S7?xd;GFh+wHcG|+TiP4<0F;l!<{Fx-x>^Q#n z-NO97%jw*XHh5my?tRU6l0iQ0wpEwl2FHx$%7{_*sRaCoxB$>X~Fg`C&A8xkrfp)xQ4zC(SC$3nP5S`55o=zmE=ulJMhKZ z`%Q8I=y;;<(=)Ng_;Yr9?k;VbU@6hEdMq8cQWC(a{WBm;RHiUSu_#e6ClfevB1#UcKjJMmzTL4O zzEsWYkEopji(Bkd(zO$a4kp{AMKB49B8O`fk$;_gyrEb_5xW6OK z0qH(lZy*4MRrdX+B)_zsQqxh72Ek!mnoMzY=8v{qOTs*G#u}Ys)LzC_+I> z6ze`{O7U`@No9Ybku412)IY|`Jo$$QMTKvFJe9qRPeGn)PUK6#rxSr_M~zzdVz7M5 zM&9zKy%u$PqX6P}m426vb;QqNrNxF!$A4Mv-94kyf&neMTKhWykkspVFguPQi(w=; z0Or1o$VnbQF6(FP3RR`Mm!ZhssyV0chb2F%3&vKbwr{Eq)08de_v@F)zv-`>{P@!; z8B74vSI+?E0n9iRa|ri73?Hp?SRIGEueOUom%2|ius{VT=T^ydj-O?j#Lr%^^B6mT5ty#$aU@x$_eCi$X)^&+B2UfPd{$jfV#Z`oe&s?FV64B9%17US_qxO zkmv$KrbUF7_sVt$T6?%AD(aF=pzcht4`ngA=MK5n%d9bFgy{%M5QkTB>ptsOqnq|; zMt#(u=T7qd`4Jhn)}LF+Q=Z_r*q`u609B$B_VYh#|4z2{2lVf@a{r#N`&B^5*7{c+ z_3tCM_gtI$JD|OPFxN!*eEHwCHwQY<_z9oULj4$b;wVGr?(PRx`BHZRxi&*)g*%JS zo8z%Gf9c>Iq|mfAQmC`q6Op(^nYr5Y>seOQSn$=KHoMl^Z zR-fBHDk-_=4y0sDp|-jEy^$T46V|l*y+Iupw-V&}znZ(=@9#LV6=&Y;pVdZdM(W1d zRQ@6y3nO}HN|cbwb)q#R9t3<#d{A$3bN(L@9dOR1zkH4U-^|9j%QUC6+H`YIm z>roYR)IUrNzJJZc@ow&3IxJj+9vHRb&N?9;2eKLP;oh{-ykIDg;bP0#0;2746<=I) zsl=D#5^s^s>sj||G`(%X*0aqz;ZhM!E7dbZkJnR=&)>YjUYrqsZ(bl8Kw)Ds)?<)n#^Nh*65jGuk4wUD$G+dTH z{^HJ{um#4|XnV%}?cOGxzV6-vbWL5iA+BQSg#Xzwy@L(8QK&E`VI2=pc~~t1;w;!q zJisi+dh=E)>K(ij&sqyaA-wt?jMtZLntkW9eHU?+$*N;}BdLyi-PsXQP{S z`~Y!IFVN*S#au%fb0z$oOF<39o`4Y#M2y7+?x+F4Me7akrt+Rtg{|Uah*ew0M;)>x z!pE==$HzmE$UuGWKfp&@ozYj?%{tC^D?@FiZp{}N@W&``f*PDrd0$3PiJ#mNR5U5I zwkI)Hg52?M1gU*{CZ!G>dMo#iT)2m;BU|s_6IF@Gu+&oDEYI)3*Lx}+`pFT&VC~+> zc%Z*7Y`z_T(c|q+zyI^i|IHq5_!SW4mNBfp+()IqS*_}C8Q)9F^fxm9q4f7fwN*MV z5%V)p{eITp_IATRyFJ5<%9GFtN2z;X_+#dP)Bj5sToM+01E)CFu^D{~Tg;zQ(Cx23 zV7;wh_HA4qU#$$LFSBQi2&_~NAN}ATZ=joj)+tQqL8M>G;!*|N$GDgGv|&8R{i($d zV~%kx4I}s;RhoG_cSE-rBUxZ_*A*yH9;?1TyYEnRVE@Xl5I!`@u}RO z#OJI&SGb>U_I*C{x`bodUi}f|*Qr#Kq@R{Dzdm}^htkid=*K|(ieLW%)x_(SQ6{81 z>RdmEVI*t*@R`JD(qRKO>d(XE8Q5Qq?Ew1x$olma*4_a9I|#Dy_xyLC^vb?Du4VUx{v&+8Z|f)H>=>_yC1wTh?&y`g<)1eCZ*eJr6uD#4eJI>b>SJnS#E8O?J{>ObS+Fx@&htSGZmB%k0y${h(zeb>~ zE3MIlKT3-0~+=6(vKf!Df4eG>}UFQtzN3^;U3wgOsxmI4~{5N>lvea zBpuw-yxN3bO>9K#)M&W(;&?6ex)2(FKH1}syguomnPh(x^ZTmrerSI`M-R$Pmiqg) zOf_>vV;{cadzj+z?U&sHpE69icRq*Y!qOsp0pe7~7g<)k=!FQkUA*Dx4;8RIYBV8W zZQTEjTpd3^u71(W)w8@@JufL&GlFh&x#~|Hw^UR_a<#-yUcN3bKbw4=Uw$_EdUN^N zhB#ZM;W>*@Iw`2Vkx|GzHcX*So;{KH(FhZ5D?{o)|5Agf$H`azEM4Y4F1oE>|) zxs4*7fr?0C1EC8%VOM%``pcRnkTA{NuT*uvzF=NZe8pU-bk?G(<(*Z0orAKqoBJAKO<3{# zDVWG+@ltBpO=J$%=KK6E-UX?abwLsPktW#|`JT4a)w+iUebC>xf4RJlPc<&<{AMuy z0|V!(@6(x}a=niSv3f@k!4}adZL`<1-RizK5|U(tsdI_@;$Bd%rEonLm=Yp(AE}h~()y1D?dps&6$R`yN zq@&&ZC){u-)^H$<*!AUcM3AOXMz2je58!BCg5D$D9(%)gR6FWXyAO52VYnmf`tEcK zck~(}N_2QUm(*$YRr=sWdZ$EsN77~KG|+yj&Cs{Cfa_kpnU0yxza-zQ6hb082>fxs zJFmCt7e!Q}7{e>NW3798rf>}j&N-vFnibJa`ZK^+`)3?3oBRY&glu5YNJzFhVvlK?vfq)J$O~9G+M65VOxw#+L zaRsheXU~t91bQCkZg_^JKO`|xvgJ9bLKM^cb5RFG?xlnTgbvD0l%SJ%2pUaVNvqhJ zfOy0B?tdj9{_C2kbpe_b5Df-aBp}S~>a&(rt;FZmyl|LwAS9fB7OS7Tbu9yK?v>L4 zi51vBY3i&Jj^*?~mSrIB2_kp8+6JjIBMowFz#w39V(LOVX+1V|;Jmeoco1F<*({$z zt#LBNqsG24s3N@3#7`6Vv~1q^cpC4U>E)K>(+*W5yk7ng)$0u%eMwlT-$2+zoV%3jgsy}obu&)-J;e9n{Ii+T zC;o|b5ZEVDLxTK}I(H}oKYIP>es?L=n)jgMb1~D2mMyqjtqd0nhx1jMB6KevED{XP z{pKk@F*ZaKW0iY_+u}t3kKN!Y5AQ_Hj?q!{taKRBeQnTqWL+v-{~=_r&=20FiFvVB z)LSD;iz1^nl-PR<^Ws-Ic_k#`O=O|bd_l;v(V*(UM?9VvtK6ZMSzV0@zt&{;`TH{~ z1_zW?a#($AmHY3HZ8|`y+$>MeGRJv;81pT4yHHrD`^$*3&8M$ye<;&uD`MRJN$3}% zHk&=G!w;a(nFIO5MnMt@)oX3|Ohmx7e$uo$`tEgkT9%kkGf{qQYB{}4BJH>)>p!I5 zgZ3XtzZc3Z<7b{nsO{g;@0*a)X7pS3x{>Qckst<}?X}3HwtGy)u_k>n!CRlG}ULsJakz6-ww zbl7I2n82${u=kQt1db-~ZYmq9*jDtur#bjo^;^k{E$8}OPd>s0To_EsoBA(0i*&_LQ0JJ3f zw1cDx?e56B@MtXyHlfcT&+$M%TdxgwC4+RjYEPb4Mc;ky)osf%b=1WJ)q`li`J?JX zKd;|RAG)DTmH*T&hW|#YP5_vs!Wf-bP0d@D&cDOW>3*xlF7JJ7Tn1;l{9hLJRkP}w zjGbc!c$tJW)jl31SK1-W){W=u=$&sO_;u0BEODGOxBU_ z$~KmqC@+qKEva=|wu@TLWRuDsU~hY{QN|x% zCf%xVDc*ZW5GJ_f;9*eA^d;(5OMYEKo+<3X3W{d(f3qcP3%1P~I}pm=rF(X$d{;?v zxP$lV?^H(J?M$22*kbqau*6!eFWX#fsywnXbyW-4(1ElkG4QTr5SsJ7aw1&1u$dKZ z_UX+xH0-%f!4x+hkXfyjJb+^FUf8b5{G7pnx;!0W`nv@-O4^I^#NA_d)F3rf>YJ1$+ zv1Pgad`oPU2(^84BfF&!Yno?$M9319H_;Jy2Xz_xn$6p~P~H6wUPiN) zb-y!`<$80~vQ(}PsPZH0+>*Ts)i)ASV^4-aeb|}2{BG{j5&KW@nC3i0nrj2HUk6u= zM@L;%s}9k8k4KP6IDmFWH~TcF5O@>g{>-Y^`wi!Q08+UhQdNktG}3R{@*BeA;HkL( zZH4n2WWX5HlK%i?N+OM+D-2HP=0XkrUc=Ok4kP=eKK7MjQP!^`P*)~(vI~mYI%&dG za8E@oWS8%~cLX&A#T)9{fh>Td+)GEl!^pfGWq9shDV1&oFI9RurAxZU!_Dja&1jzO z(YN_lLodcM7K+FocWe1OFS#QWl*mM_^M|=FS?=fDl|Q9s4a;_*NRrca#Bbi!ARo4u z@en{%G~4-UW252$7mptXB+mtQ*f(q7yU(d4i|=YzJr;ReJmC9N$yB_*ZU&9vrq4TjOE4e!IEg}ZFgOx4QOO2=2Fa(TdTyHW#;Px0;J;@7BnX+O*L?HT7NHNYL$CHiXC z09J$t#Pv7NN77YDA^|4Tp!P(#>1 z?NgRV_@a1MT|m>a|E=5RUB!FVx{BB`2ZZMhtC$2Wj1tWe`lMD6^QUw}lxF<#YM zQ3dHHeH)X=*OW*Hahm*U(#?5CJvjvWy5A~ryRUkW7rr(vq6@mc=?y=VNbg0u44t=^bDMbn zjr`vxJ^t#XMQ#vh&Io;k-vk>V!J-$i7CzVtl`L9S#s0^(>(YgTf^8oNzlOcMWY0?K zM`$D{&0Fe9U$TkXlUgyd+j*kpwZ7%%Cocgko>T)w<78zgd7JX0hUNkBsE4;-Ap~mLN>k zRM7dLg$p|hI)zIGB|~7k5E}w(4ag+`NxUzDxcGQqtVStu945|x z8}IA6T>;|5<9*$)EJy60i1(H3-$_8X<^E+=&ic21UFSn_*siZZ7>xd?c=QKcE$roJ z+18;ewzhD>3tMV@UZQ_j;xd=%P8@c>g=R=)k7U53Y^ErlSyy#re@89$C9R9KM0w!v zZ249eI3d&htW8;?H3x!R>>T?IF;Y$Cmg!x$&V`kfR`2&E!#zXpk^h@~b_8a}0f0CJ5G%sR5^oRZ?Fily!`MoN zzvCnH3_pOTSKNzmg8Df08>qq^{1@-PN8gu3-=Ed@XU{EtPaG4JJ5*fV>?nJhoZ+_T zDSMV4B+j#r2rWM+d{=~T0gL|tY18bbMRz!V>gc_>`?Iw8_LJNF(cXS8)^PeXV@QPuvN77v~OA9jHQZYO{%T4_ks6 z^V>qw)0e1QNdaPbaEmD}#d8YdiwZKHOo@e7_C21Xb#Oh3RJ0C0%#ste_`S%I)wT|{ z;l5Z!+LE?(;*DASqk0=9OA5>mEwCi7k zJBjz~qtFG7C`v$;%8sID4zCoy)z8@vat^ws z-1^0}soZc96ir=u811_L8uV+(Ra>iu?{J=RVG9ytQ&2m`Md)~k%=dt_{)PH)^q;*a z$0xmq+z#H8V|Zj1SSYpc$5AU<@%vUd*%$M3{#mPQbdSNkesNRhxs$Xc`T_8*?`0`@ z1Bv{z`KKl=>Kioa@z+pIy8l^;j?(ondiZrPk?m>AZ)}>;QFp9n7Hl9*ZK)P!x6X>T z{PQ!;tZNoiG;!a&oBJB-*b?fi%>t?WY7u-QOv0+a|; zgHnMFoAb|dPQPjrVYs)M)QU;>mTXVw&gJd86IS}2ejP38daPPmCZ$?G&C>ZbGFLxvt-egNXnsh#M4ZOu@4DG~u!95lCs1&(F~3?p6YPqoy`#_^^fPVy8lrHK6H7feqRAQXB4vw^4)EDd@i(TTlG~;UpFE1NjctqxdTF;T#|< znbd3C3m2(X-=II6Zvhh}e@vs=VJ9>WRC+t~|1k~36nDFo;n=_SQ74x6Q72k~=SQ;4 zEqDv_Wl*Kz&ONMon_dtR03N&zGVw#Gp_^=w?b>x^G<3Iuhyi1hJI->Hrs5xVX>RRR zcuNeny~zq936sP=d|{wI~Ea=#)c({Z>yUQc%Y3&f9yblj(Q@+7V!IiCFGZeW?FR>uU*%tKKD#iH}E^W4ix|XTc)!K;ovKtaba~nh;K-)=4w( zWz;Jc($9)}^k2x*3|7aFP|Ng%&}YRx`Y(LlU}%cv%)OG4Nx8TOa4p0LSaFa33;!PX z%g%o-q_-9K=)dqcQ9bu6+d?&Rj~N8MAHA3KM}Lq)%g z9~5a|l=m?C$#d_+cb>6iMWPJrl$bqOa>`bB=-cbC%uz=D=@MYQ429vwMlf7WyQXn@ zov!V2QALLsPlWU~Js(I&eT>UMz3wMj75NH9L`VBu05{p4mnnCwX1t~w=KnJJhZw5~;P zbdg>2!%QcfY$iy4e1S@BU64Fjy}2Mc+sY&aNh*5=D`s`b3zJzC3IkrP>mF1YBYIGH zBILHtq`S=nUc{Sl!8$mSkKy6`XL|Cb5;+Rv{I9=i$$Tl(hT@F8wI1?*nOEVfq~Zt} z;V`JT^l+yJzZ)BD3AfpsO>kX`j4xo-h0?0sqBqv{hciX>(FllWn>^9J^pDMmwkams zzWb3*XwKi&#AT@iY0rIko$1rWAwjJcB7*Q-5pD6P<;M|W;*{fjFUtJVFNL;v)GhjY zC|{+zGBt%`zfw-0ok(AlNVg`^YbTcTpOtw3l|=d(@|5-C|G=JcTT0sK^ZBO|>q_?{ zoWY}sVn-TLXA@iLo7gkHu{OB`uuk4vid~jp*L+&A;e|+Bm#Q8tdUY^cliwkT*%vE= z?tVg<_dve{XsnV9{bLjVxzqN7BysFCr#&`U>Q@GjyG-$r2il5t&$czbe9>;1{F~Na zAHi&I%RiVH6Btkbp?KZ|Da_Pu#Jq${S`_V5mcMbeX^ic#GQkgv-xi0!T&( zn=qeo=-Hjf3d+EhN1cA%sy)i_J>OC(l#BDW-%@Yi`*VEkK??SOGiH%3~8-o9sn3iLkV1>E}dKXNj+f@Y4q++T+CdI59C!OpX&% z;>1*+@G3x@o}mPPt_Lxjfyy(|*NgEq80qZ-fXL68%5`kfE>W0HcJu`OX>4{$Z6D*+ z_NUQXyxRU_x?+)oqnb4SQq`}grXZ=R&((`qRqval>bQUF{<=U> zhpOr*l#UNB5gfr@cq+S_wviQJXI9-D70Z)}wDmxiS@W<^q{CT0)O!BvXrS6_<3RA* z;ZO(lr?krI&t1r1csQtOYI^>k9`S#wOhdw&0X=R-upe$`+DVvV5u=NjHoYHkHn)Dc z9@D8idWh{kU-e8>&i2w<^rfR5oljjA3I&airn0ZoPuXN@g|+V2MrjY{Jz@b0WA+X@ zYtT286%Dr_8Kq}A`#mfqfj-NC^DWq^fo@CsPONd}BlLiZIjTcC#V}>e-|riN0;~>j zq004V{vzHwq56y-_}mnImSSMA~H2w!U~$7QwDi4h-|wtUajN-fhF+Z*L2VQ&$3l z`;_CtWmke;>=hf?^CGV(n=Z5AaM?z;EA69JujG@n4FU4G zpnF-RJB2nq`Qhu`p344Sq)u{bCpW%$br}0@bg!8C%-eh)pu>%k4!73r@fWYd%~YTK z!efkvY7Wn=ITT`hq4UcQED~O=58p+`ct6#w@weUIc($1yFO?VAYwjC&dz4wV6kDh2 zp~Q{)^?vCaMA6VI5z7S$|ug z#e2?D))-KFA!;mD2NBHZ@O4?8F*WMJU2a7qVCV=dYvm;LKsR%qp$}OJy zh4%>uK2u~`mTtu2S`^%Ku>TvZF!}OET4LkRycpHcG$1(sBnZZ9s+WEW=-5Z~q`yU> z|4B_0|A&PBWfuy05qtfy)caC!{8;o`z+Cb2RL{`yJRme7S-WiDGE4=VqNW%9ct$Mw zb)RiexRA@imaT%^!hY6E@jeKC#h;O1F?R`r9Qnp=|I#{gMEhRwEw z%mRa5H{v+_>rG25{GT zxMd*pE6vki^pKl~*b3z0_i#r9jfc-{EdOIQpq512(Jcd2j~*7`GK}X~W=9IfpXU1U z>!G%Q>2igB;}*f)ay@IhB#t*9uJ#$ch+1KFT5=~?hPs1w^ zPh7+ikGnWO4$_=@v&`_}rNaG80XNft+(b`+kTLGW`dx20+1n{fafE->|F$(eLtThs z2?tQ8p3|jx!rEPuQ!#9lt?Tli=JT#JEh?(8GP%L@jHtr;Nb%*$)7Xo%D-@hA+-=XA zCikb=@UJGrRGsc`@rn&My;O%UB6dAJ>HeMuDv@vKKo$z;d!qC7y=8%X@q`4LizPLI zE4R()lR)%y--=-aOu27YL{8(*GN6}&BPM{(5u|wci--J&!);2pf{9-tl#Lb zOx*CIYR&e5VIVpuhAyIen@)!sPUgDclg9*Q#i0HT-Qi@qOzMuuf?^9AQDGe8&rJ(b zk#vu6amaLh&w}|S8eo_kS6+At*0|xpew`Ib>)Og-(FcQNUBj}L&OgB&OP3rKbiX_N z;{7d#3G%tn%Bu4zQhtr!{P~m&JJq*14Q7xF(973G&Tl#yA_6p%#b*JX<=w0c@R zp>X7?boY`FAe}C-IP{EH2UxwR z*4C8VQc>$3JWYIkL{JjGb2;Cdzv881FUw}TW%O1PpQbQz^2BM%-RBF97Enf%x4rYPKY>Z<2e1R2SOEGfxJ#AL?Ak&Km&es0*Z?F7zzjhOZbB4u0BmC_ z*COerJ^CAId3_VXulGsK%hc^Q2E1|mEHC9FSrzU}K2LYV!QQR1FAM(@bw&<{)g2%g zOxmf%GJcT9sqEnt(-C;|Ww@wriad)sSjNUbb~(2#!8UE@z}p0-kd8(0?JFgM!H|sU z;X2zh@+Ps8EtSiWeK<(A0pe)@w>@hIZurOkY|&XWU)>+6eg5Cp#X=@LIvw-K;&4~e zC3z8)Y-`P@bD^9FaVfoK4#QfSe=_w5 zEl^ipx~B6>(~I68OhqA{>20Z#UpujQp6yet0nY&YOi;O4AVV`MNc8GO?@Dt2T_bcY z2Nx?s`Na5fNGmc|<727pVqT^5ho=iCGanPFt1wQ#1iQydG|%c~J0jo4p{v#^9{r-A z7288=tsdofn$DvscvBF*r#qaYQHN5Z0u0$Q^x(d;c-_4er;FZEL3dwe$N7q*zDA)z zHXyKD$$L+&Qyk}8IRMQp!;9LuQ(+$7!!&4qqgObtZS=(O{=(TxgHe| z2-~hpfWYrq%fhzz5u_Q%l_(yB^d9I18q1$xezaG%Z?GZMKJ%iD#{`eRgD_V+aNH0k zijnLizf(?I;=44WRSC+XgibrxfcHngWNw)Ip*GX6sIbS3P-ImF4oCr`3tT7tU zm+e#^M0SmC8;RO2>7tqz(G+6C3m4Ypey;u?0BRQ40hU6Vt3mEmCo_#}G&HG`*9655 zXqy{)gZkICdy*efXXIS2)(D$n(cRSH&J;F2OczgsuDKM))AeD{C*Yk1O<&CKCHgJX zb~-O&Vi<>|dyn_jEx?Yi<*}A(NnZ?g9~V>i<+bjRcD*g-j}8-l{3{|Ih#HZ+77~AL zR9oYQjy37R;oSO6{YUGPOQY*_0Ul#<6w&rk!0jsjs$DB?CO#d>dYaL8I60OQ2&2><0l6oKHo*ij(62jSYw=&(sVC3o7(2pBrOYv=N=FB?)uZ&d7bjzZ}KO8lP++smY1&V`k)bwwK(0md}}IH8X&i zrg*B%Z`%a>9f!DLE2eO9H7X`Mba$_!Xi)t0OuXW2Ddc!;!$BVGwiQllXe)e)jqQ!B z?;cS8O#Z>3{+&$y-@R^%+>_ZeQ`}#hJC9|GBfpNb0Jq;oKnqXr}(<)Qk$HyFVD7y5_L$NX47%`)dEF9O2c3T~NpwiX`p| z>gh`BL$^pJ$lthJPx;&md+GP%gZgFA$~^utx#i~b#f0_tY6)STy5_5!w7J~cObFOg zn};Ms>^3FoI^WtZjsTz=ZD{kT;8?V|b1}$YRMXb@QtIlH5Dtd;!bvsdSso16rVE#k zQq;@(2h*Sj*|@x|^7+(^R)9(iylV&Jx^-f4$W7@ycgV8N8G-h)JX83sb-XY;>ap0J zX>blOSQiT(2l>03?1TMqE$a1|;`v>8GRz2yvnT3Hz8LU>zdp1#-d;c$6S^|_)|r`n zQCaxe=&>rI%H(Iuk(eKACcj{+J>A&Ok6ZpzYAobDeHR5_I{&5Wzyq1_Af@vsjS33m z;c&?+ez-%o@!dAb$b7L%l@+4)W@{8v+5M^+s6tV+n#n)P?2n2uTjZ1}7z{ySfdE6r z=pvhF*!owPZFG`vqaSo`-1V9KpWTnlDA);>+HOV}m8@^0_fjW@! z)n@WHsvg+`Td$A$Cc|XDZHBLx51lmJ5EOqXI&g$O0tZnO-G&PQwkqQmIjr7E(-E)x zPkEKNTRpa|T7g&e-4c|ug%}{1@1A&D)f=EefonpzJ*9xsP`C(QXq$_m7Fp)U-5lIo zve`>T$wL{bJ01eo`I&mi>r1JK`me3~IJ(WbFRsLZqFkwYCPw=2R^x!eXh^^?coI3O zc|{4Kb|9)1M1{M@@Z(lo-`_u;D>+-2m$YL}h^UF;*(=Y8gQ_VoaFe7$6MU!+W$MLb z5(&NBr&pP7T#~wKR~SVHM=Bpp&3I5deI^$E$lDJU3u%)FV;vQRZ>XQ85CnFB+BQd zMzh@NB9iE0CWVY?ON`>5jNI);9OE3@!q*izp7gm5rzX?*a_SnnIwDCL(uI@8lqCsg znwpR#0dXDD^?!=geAib0QfkKUC3?EwN0NLWF{`0Q(@f_dJE=HwZvv`$fdC1|0wE_> zHc3IQEAHl+bP6VNA`xOChW^}Eyj1>RNE0tRGWiLxdh7M>Q^tCm z36G9UrtoWZDAHR^GQ0wQ(Bzl|#+iVS7HJYBk`j+9ifSw+%7UnqsnP_2!L6n0rh>p| zN`l~OtD)#cmFH1$Yor-O?1{a{IMn#R&@2#c=F+BWevvB8HJ;5QuIA=Ck~bGwjXmsrFNxVm}Ku`X}Kj$VcMQw4M^Y zg-D4i)+mp*Uhl;H01-ZhXO@2L&kwMuEPb=*7pEYEy;0;eVRsjV*NrJ80R2qoa zt2U@LB}rmv@Y2%TKuYJi+v-;vB-yjrP(HCZ@@uU8z`q~({Omb2m6|tO(t5^hamtL@ zLNKTw)y8Uuv7TX^4~o(CYEb)XA-1kaeSqy}21T3xgW>}5E_8_?K=rtBqMizorZsLp zAt`t{*}wGE>9tSK`s7?myP5nr4W;A-taUT|G}ry)I#raD5P-2w>J*BS1(fkk=KRp# zD8gZsJ!urD+Ec6~2#=7>TwZ!RPc-f_D!s<6?uYqNkQ9;m7!w?+tgY6g%=t)X@js9Q zq@a7<3KOdaP{RhBonmTeq*6}}kD=}+r~$4MUq+_0X|?#V)Vlk=Q=&dk6yKl_V_!?o zfke@$E_rGZQHW}!>z0T@eWlCR^Kx%1+rS&||5%tVH~xIqKcRGvt& zBcRo@iZa_iE35FBjl^3D{#f<}enaamEwo-S$6B%sMcR4!igKV+{Q23V)Li{Ccb(I! z#V=R*DE&f<7?vx{9Ll66ON6ax0V3@p|4RT1C-FU5Z&3fJ8}y3!P-K2#s=e@cZ%=U|SU56aLS(A1=QFn31d zDV7C|HmrHCvY?OTrvPUXIc9_zfSgWp%w(5hCE{1(1i)&eG$ekF6pvLj&e0$mIcPzY zU;73{7M2I0)XvNCAiPBmkmc<~i2R>M6TUhVsQ@(X=_~HW3IGYWR~HHk1em8z0arv7mxX+Gnv)hxAEgx3L3Bv)o@;#hxdEn2rp zS&pF@2S3n&Y|`LuLEeT1+wxX_Jg18FNs8$?wq)gdD8Ko987e-q_GKi5R37fEwrp~LOY_0s>iSoY$mC5pcs%!M9&kh3A2(h43;y2u11!8)YOSC7l zXk`~qWyJ~TzJ892W;}&$$W5jO3Wyu+_??}f<%O4WxvBp14582q#V((eP@L+M5{grN zQcFi;43=0VSNgX%lN5tc!(5W$50dj;*VWdxXsV6P^+K%WU9SwD_*jD?nHwlI*It^M z#~XBH6CEU*NY3XY$sj0HdN54~1V+;V0kFB|l(PCK(LIwA?e2r`fxr3z{2ucoR+-Hf zLI?v^Q<3$V9t4C^j*ZWA{Zb6YQ){$5D_?LB&caQ>(^08jHX6`K%;LOY_;oz~JQ*^^1EGa-s85R>NDC z34gG{2R3+TDX?epsP{Q8;%1O$@nh2G=Zt{g#O>|-M3e{xDd*>0|Np!X z>iXR$R>?QYNY-|bVZGRTW7>pa8q*VbHu_N#sqGt75Saz4`y zTB|MO=iK+AMt}L7HEP2iwN_-kiIfxL_$QMtnhCI z%E}m&zpVn4NaYG`P(6)Fr%MT<0EbI)!iv)baOFI7RK?AIqsRP9$I@eWwLrv z!=IvIopcy^}W{wEfDl0Bf+U;tU8E%83=Xg#Z zR72aw{qov4fm(qrX=7~1HZB{ZVvHuujL13nFyqrwXlU;KpswRP?)|^LPZ+j?Wta~y zh2CbGj}Ti;SfO=Vm+6^47?Ty5j8S9lu{Z1iI@6b8hs0F&`}B&@RSuV%ul|g?*+LPi zx@cp0PI#3SWDM+B#xt`u5ii3_H+vAp-2~u?W18e9TU%qf#gqVN+1*rqu`DD-AT!j~ zkGs^apYV&e5-~E#7d0HraV92Y=#a^e2}_`tXhr9>wni(jDStjPmfmf}dQIqP^o(e{ zu<5lQy)+UUNIGX6T!yZ&KmZ4cO6c;wnti;DJ05z8F!vcN=vzO+NXy3Ez|gpV;jfH) zG1ov|oi($BU=Zu{=Em^ao_cdlWa419Wh{QXI>>VoID7vmV8`M>Y;KM! zsKn%Nf+q@H(IcaLOzXL7t5xUGk5*mX#>cn;$%lwsRs&=uun1&vthmHlXG6!J^FVT! zyb(P{4h{j;x<7wib>#%|s2D|^Q+abeg&M5ikxDgNm|+E?4?cV^E$9L8)vfSd>WJ3Q zErsE&^11&bdcHf56Ft3yk?arw41KHCH1k^L%30U5VmJkL#PmxP8EUEwuePe1M1dZ) z3v5`2K0!0y3TM5qNM*IMV!x-L-yAw==A z0U9MF*hFF|gHhIb1PYJgA&hIR*G0B^;{`h2RDSbfF4j95Jq27DG;*ph5n_F02^uE*V6 zDi|nR)<0IpZDWMnhp4NiFu0YuJ@=Z`Yx6V6v1$sRF#VzNft0Dxg!ekWI#ut@7r)Wq z?B2zY08PXzQzC@08{iH%aJG(+b38bbUS7{S&0gdrxXRwiv;AXqT0N)SjH8WA-jEsI zF#ryMZO5?YTTVk<+qJsaE0yi#mF{0kX2$<`?*2!?sep0gK0irVHRgk8-vEz3Gyq; ze8Fdk6`nSm#N=CrJC8-h_MRbTBe6*=T0}3fdV6hwr_n%(VL)t+*K0p|xrIsWhx=Pb zK@PDCX2iOniYjquA7xNkO_Bbhw3}cB&BO(hsyiJ!(V~H&Nv!y%x4k?{s!1@w=ovfOnrl|x)59I)FMA7;PMc3-5MA0?&XjFCs?ht3lPH67l25jLRDas%1;+)u^weMA3pO zs>EIQIfKe-iYQuXSFnN$s?|b7(NX{m3{mEN51Crl#P@+HImpg5L(qkyK7x{V{ghDR zm%AP%<2*_%gI`9ZvbWj0%TU7G=9nvkY)0bnpX&n>&T3m=WqNJl{^6&4u7{SFqPBUVibBLXVM$n-|Th}9KgL}~YJE9gyy35*yZ!pLtPgvaN#_88?$ z1f;SrOA-PP4_3R)3GB+wwh5HYFGLH<-U`uTZ*FCj&BSPse=<@bQ4nV;L%rnaRHgD8 zXB1Z{QF1ktBLyBFy;0uOJd;69WB$eMH_$FovVu=O*!}#VpgY78=<6%^pL}t=szRl_ zO5yJrwswiX{k-~`FShF|J9B!OFHW)c@dsRv3L<~N)U{KTI@f}5t<(XJ{A>f4rJI$U zEs!%sQ+`A#)Nk(>_^RxEd;i_)PygLQLBGdGes+y-vQ|HZ*+L?#luJ>-ljL zS13cWIlO3VH*3gno}Vq8(-G?=PES0A=U^#j!kR+c2-yUCUs+~veGJTS6wg*u^#)%_ z%~qc`MFF!?%-D*nw>8G*po9q7YRq5O#mK9wi2Q;=D-L<^F>COddN+`t-`Yz<6j`I= z87fiSEj~;cI%mcj=1jN9iY&0^Jlzr#&(MeYNrby@8I#PhdQ+Cb-?a`|SKXZ7$iGz6 zw$0E932f7Cc-=;dM%lc;@b6|2v1V6A?l>=r#~$TCe6QbnFacz4s0-Q!@41Uc=`5nx zNyAfFJo?%;tY9~5!+JKS``Dy@G}3sq$Gmnq#RdrNAdj1!7smBC?JO_*w?~qAQTThh z3&E&MCHOVeVKpQKZ*-O3Y7|4RSXB0Q%bpmc@O0w|@lZv0JC}lCeZS(J$_n=}0=q7= z38f+&N?YE9x;Xq2cU#8k3lr%tB+}nZq<@r1AC^e}ERmj`NZ(dU_lCbOr5A?>ZIUkU z)yaM)i2Mnvx1%erKjg};W9(#(qAs$?(~*=aH{#V&sIe>!=@?NW)YxXXcD?`0s71l*(XiA~jFo7m)#o4+VGRR)c(IL(qO(C<4rTjNh5sfB`$K6?< zQ6+2LxonwP*p<{(U8XJia08|AI+VhE6CElqMuB3qG-|198W3-12US>Tt-YCbEg|8w zsN&{@S)6+s{WMVQUa7XR6=Npi#-EAGb`=e#=ABx%`UssSruyeTXDoX5d34DbS_LGz7(2bv-n#qiP&pQ+U%A zdMs$MaF-p{&lGQ}Tg5xJO)bc&*69mRR~`3DRtyI_JIUB*xY8uA?EB6r5zqx1Kw_p(uISh|{06w6G*idocIQ~!ZXKO= z1ddbja?g#*yIs%3L!h;GaOaajBbN$rNhlW|cQ0<}dn&R=WQu3ks+A?X3ptBLVEG5S zUl=??6q7q1l9LMc7MT6UO-j{qzLjF8!Wd zD55-kgxSXH*>hJ1TuOA*Pt5UQipIyp7+zIsmkEDQzjzJ>SK*L4=YFo)K4PnQOtus? z;aRIi{j#`Q>-1Y5YAuA1VuDel_6e9PtPPDA^~;K#Xn^hb z^qu@>#ZKHie5d6Hppt;F$nt-9e=oy>#*hEfa2 z%l(bc)ISjqvT0>HC6^fJtX)|kEi7V?;th3O;wZGBsr({95T$3Lp?RszGehJaVZcC$ zG_$XuUlXeS{IteSc0dW&DTJOtzhd6=Z+u~`wXt$3oE3C}%u3X&Z&i{mGn1|R)j2{* zZ}sPnWo_HH1DC3tXTH%kCMP{7@p+doYE)%<&^}y9AjtqQ&-r<>iyDjw&-{Z1#dp3$ zSE<~=GNp1~AUGO$v8Ef&va-3iRUjA&#`}Z!v7#AipL6sTO!io=vi5m1&-w$2bM@=4 zHUjWkXxC5*#dhJ6I(itKez+;4?JA)QY^O*W{Txc9l+nXcMh{D6ca-W#q@zI1u2M#5 zbE*cZraZrCJiInKrmT%-s?J`!AU0(PxpB>PqY>`4*>z@6B9`CQ4V+G<_cb2b!SbjC!2WgF7ZC#L>f=1e7 zucA1R%+*$!`BlhN^fO`7=D9nRk$G-~eTdXVXh!hqFA9{kj-;rhN56d5v;InYK}#{m2hn}^;{lQ8_aL|VuPjsu)h9+vVdXm0LH~PD@E+R%$HH1Xdj^?}O!ng{Y zYO-sr0>+=USBa%HS--m{!@W7j9ztbX=2%tmzv0&(&`=5MWegW~(CdgK{CIzk>0d~n zE0~3^Kl~o0ppLhbxCQ$HFG8AA+ap85KWJ{=y@@Iilrdi#OQP3Lm0tfjdXiWpqu(En zet%fUq43pV7-2Sl{2 z#sYZE%0jHFRDTgIw_Age#N)G`^(P`RqKPqG?e>rtPXw#tb0jN+5Z>5BpYO67N?Mz$ zbFNxn?)Uvx(6>h*FV$o)X@GYA9RoB^`n~D@+X(hpW=Kd#6|D^)XRLx^#7yWDlS}$} zt$Xouua~bd*iA1-f%TG10%VNz@+)R}y?nA7SI+Qq8~zrWLobZt08Te-bJ2l2nf&4! zFP|bBiv&lkA6hj}QjOOS@hhtp3l9-lYRGK`x2XI1!Niz3ER{PJYKp&0Fk7Z}Tfu!5 zB~|q42NC+j;VFF3RM{pQk2%)2P2`qGPmGkP!!!)edc$K%a2c_NL$;&`i@J~^?km^% z-iksz4T`AZeu|F5`$}>_*A_tLrz+IzN{x#r^%hoAx^)m_WPz575mOZuWsbFTdmG%V zPx`^vTMcq`xj7<52;EhBBL^u(dl21Q7Sa6_dl9Wlw@mk6Ru*KqRS(@;Sx8lt>Mx@E z+Nh?Pyb>krS$|ug`}&Uy3a@xGbjn?-4C;Qz%m;V*D3oxEtyWZ>TeqDGMxd0%xIrYU zuf2o7OGHr7rkVU@LT*My7@ESMg`+fiof2OV^ z2T%wJ-_Rn$;_PgOy-YrmOpzEuS+`p^wjf_(np2fxNb6BdbJs@elqNoU=t~3@vTDb8 z>I?X9C0N6gwT4-9jn*(n9rqH3)5jHsliH1?=?)Z)SufNTrlRJGa8LAUudpsQttPyj zyIrFf;Yo>fCXwzST^8qde_HeD8S_oe6fdb(1Tc=Z%85 zyw`*J(LI^`;Yk-9O$Y03}zTFm4*zTllMkP>c|atxL)%bOZc znwnxP7U{;F=V*{6=p(h-8`>rGr5~TBT49tf%BCAXNM$>Dq1`FdqiyHG`%h|SI7gW= zr!r|ooD{3*5pRemqOGm6zC3VyYp3BcJemk14PWZO>Rt4qtU(($s6zL`$Mj{3O7tgg z^DCQ)rIp-7_xmV&Q&3bG@_6ajZWH>;xB3DNr2-A^yHSB^6{sy0sI>xjTY*hNL#1*B z04s%uT9vR)mE@Ng?Gv&WbhKfrb4Q6j5q(pI|HO_C-3sr9@Hh2Wm7)_58D87r_hbGO z7j)6OxFD*o?+*7`AzqFx4&SUS6Z5|k>0Z)h0v5T)_VS2s2>_HJ-7OI;b7zC7xz_E< z*%4%#LY!hOW!3;BSr%(6+(8VnT#hF7DL<6S9?Y=tx=4^g^4!V@bNt80)k>8cq+>lDN1|W z)Q#>145{pV;|duF5FFH_fHhJiOxM39;sO{^YjC<4Sl@>kpH0+@iFzT?+Mo)`J*{EE zBW7$8P^E&9_*&4X{7E|Q8>V;aOpF#!!h8Pa?MNit2u*l~FP^HK2iVx=L^;!|`|V?>twRpBI}D^k-r&BFW;=Cx&W#MG>bE zXPCf#Mmj>;ZrtsA;LcD}?&;1iGz-PW_WovxFYZg}s6GR#!MYmW^-Hd!V4WRJA^q(L zq^CPatAMWdRe=!{=zOe{p{7stJ|Bkd=Zcs`RNVL5Ni!4ABl;fo^enL4DdZD{Hb4~< z!ll28{5VC=<@xa~vP!ReS0|3h801HAt1Oo0$UKcz1v!(LGLepuTwzfD4TiqI9X6zZ zpD!DN!swvz`KqAu4Y#vIxPb-~$LQjeG3Fe7<@XV|c6BEVG34w)$fphN$5(U84gpPk zWb(~V2@Ynk@iLGyd9=4RhC!+o!ILhIz=r*=i}7UEu0-Ppyw)}WE?`%_NGVm8p|XHE zfVmcw(jCokObD#a9#3UIus+(0fUn9%l+%FupC<@i3odRZ=xt&~W8QD71|w*#Zs&V9 zGRbOc)qZGVGC%*9n#^@aXgr%Cm08M+i{3N08HACcexiLGqmRX*uW#H2kk6v!bCJ9v zAdXmr;4({o1%*Jvl2F8m4>0=AqdDW%@0zEptEXx@XP#6S^CL0z(`~w%+X>9$q@)!L7F6 ze)l53ke6Hi>-c2c(!M8DrthJ$w2kF+_sj2fZUgnA$3$KX#C0ovsVdiV`H9`GGAIsd zQW%y@@oU?RiR9kEkht>z716bMNZd7ZKxOyh28F+UBsi;eNN=k~PO|xo35vYed?vvZ zhv2qf6)qyLU*DV-6pybWEJXb>3yjMdym6-lXF*nH9u6_=Pc+Jx1k8bi`1)tZ1V29x z2+_Ou*5H{C!9&5$N3@pB)W3a3u;~3EUtcqhze=Z_6=HPS&1z<6%dhBrx{NV{?-T-7 zGpO%MmE=SMu|;+5Z=(M@fE^?W@$5apZy~9v$D{NjdvMO-#^{V%DGQj3P*!TfHc4^?E@4ESsES!3PKl}-8Mx>=0 z&AKPiLKMGKH;okz47y)>uQMfHN#syK*5U+FT6!R*4TaKxcOadegl3b#^L9ZY3cZ~B znWr-w*NDcfB@gfMwarqlE6g>4D_J{IaR;_3V9w~=wgs?#dkUo8-COJ4G-Ni_;&ppu zCHIxl-z8Pbwgvp5?+mDaPzM`zV8>TJg0@Qaj|++y)ItDj(G{xPHEKlK^WxOJrrMx* zN`o~Qq*|VGKj537Y+S4(kYf#yK?y*(mptO%IEHlO0cAl@gTj|F<$aTg)`w>l=ChB; zG+;mxF>CWHtd)*a*u5H_ns@9d|9B)Jmc8?aF0IX;G6WxWj;B6EvDVS+M?amK*FFl* z4nCrb6ebfXcxqMO9wmA3MP6J;FwjIDx%FD#H1_FEn$&TBkt(!fWL2xX++QEQH?;@# zxFdcxF>}U?1!DuI7;7$I#+#}G@B815abSR^{WN9` zFFf-g#HFsDPIdW}qMSu~&B(B`&v4A{*|0LaomY~7ZG<2f@-<;s&@U~KxxRZRB^ipK zk#MuiI(O*XC8l$#p^^^xrqx(-aOY~HF~}=KXN>VG&e?~@aG4pzJyHE!a1_q1QdF=^ z<4d}qB6ZvoqJvNCzV(<6s_31k1H79llBs_wK$kG)c%5G!d=G!lPu>wIH>V|^hCn_1 zJ3R~^<%Rm!l6QKOTgdZfS38jUWZM*%dfN>P)0u;jxdyD0p8Ci#J0_Eh@z55N;}eVl!>s$ zH&aL>?y^nKFP@d?#0e+t)VZ&`a$ET z>pISX%|;nLC-lIe@I@5Hm6mh4K#EcJsDI#pNK{ui8Hr}o3~%4=oDYSYcMb?TZlMDH zKib{|PKxT>|L$f02Ssb#2SptfbqFY+qBAn;3^N1W(4)wrxPlksZba!8;@aBXLZ#D6 zZZw))jpims6E%rRMCC?g*hY*CYNDu7h)bxVRZt_csJ!3bIaS>~U~c^1A=fP}9*V>`Fsjbj1{PV1(X}%p)o+S@iQO2!8z67Q3oa zB25?I>m|rSYe^g)ZgHeB$ow+z8#C>a4$<&j-)xYMfqongZgd|y8Cs3uh#8_}Pj*E@y4U0k>>f#L)I zT>eto?mtbP@M0BgaoRe!!Lak^s<-5?I@~V;$dQz@jWq8AAzo{}TdzPl>FQL&>JEfo zmLP?8ZUX{LXWU)fG4LG*N7VYy5E7d7eQnP%E4tb;UIl2HT6 zU|uIM;$ZCl%@j2I%&zK(lr8QPz`J62+_f>B8EcSoF6?TFg_4 zfgZ%lnSGxSv2i6BNM_bIHF1NI#ekurZwvG$H{~+o*(_e(0qgVMGiyTs$YcE(TSzXZ z^4>yaI>juJ@fR7s8BBD>=wPDX;lb-TcQg4hp7uwEMb-j~abb}QROA~JF<{cC;o+x* zJ~@0Uvl1hpTY3JpP~Mg#i{8McqzA>^6`@eSmxOwq$Tp&_`QGCEzzE@?J`iXERmtlm zsi&#~8GWv)`EhqC-NWf%iPQE{`uqBZh`y`HQYgUpkN_`w`aE9)8NN>}b4@j}=k1{) zVdZyo$+A@*oa_|R?-we6j&M5 zQ3t_!5fdMXq+ych8%w{&Z#=e3F{brBq~6s0?tsaB9JX9^9i{NS&z;<25n)kQoVaRa%npAc83N;QwO9s58=9D*sv^Ny){)1QWZ5H%VXC= zZNcuu$l3dWyo(EZMR@);19|rCfLgCh z4E)&hFM&$47z1AipxClEEB7e5_(}UbdnFxGoMN%YMUVwfS>ibwQ|NgxPPHLb`qdzH zX^2KORN)Wf010|0j2ly2yGQc(^>Zd|MDT$0^#c|n_&0N1RiwlHvbYQUBN%UyS6?;W zzh5=1V!Sf(!J0qzj@F>_{2*yleBi2Xe0gKS<-&lE1%{hG2MmVdt>-H1m@|6Od|p8G zo^x`i1CKcxx~3Eu6EPqzXjXxIi-9Z4ilM`8sY~|C56vuVmaQ;GmW@woC0OU=0N!Fr zoz^3?gW>$@Q61nHIkT&g|F0*d=Uy>)ee)c1JYZdHAV*fQ!HOwziwuw=K7a(EG2M7H zhr8QI6;EU#R%ZZ&b#W=0<6jxb)3H7)D7;>MtJcN<8?Zw}qKOgs22+LfwF8DQI1q%Z zJD|r!#=?5UlmU{!PS&Snp>%Su2t5hxE-;I~!#1FBV3GK!|5vkc1?%6!Ddp(Ypc?F*o7%O z2#qZqzZ+VH_vgtHDuPd|!%v^_;LYcgxRylEANhBD`5G+RUDclRlTWvH0Z>5dE^;UL zjJZ9z*XG@1pZ5IV0qwaDQs?R>9(V7Ud-MRAeDM&h@h7A9B6r!AEkhk&4?o8ws9vp89C zbfIbSoC^6sP7V<)46Au=SOfD7aYFlYTagIb*ORt<1-0}W zMCqg8Mha|{siZ&lXc|}R%w;Tll^K;GDS~53 zJ}C5!$2$Mo6Q9(M)y}+A)?2$^>Y-TX+bbi<`|y};Oz19A67$S{lPSjX$8u{Psj2oN z=IxY)6wj=U%3wPCPV@D0IK?FRzR6ljvuEuI)a>G&{wj;(Z-;-adh z9Fa@SkBtDe0iNp>=i$FLCfwq^p}Z>8prMM5WA!dv|B&l7USBa~7ZTlC*TTJGnfIzv z{df-t@OCwy%mTF5`6mEt=#R<0UEVI%Ulp7;3GD9)cPW7RBV^-3dHlN34GtH@$cmIr z8GdB_u_pWrIWbuKKy2`?T+PVfOh&4*X5vX~VMXTf&rpSO43#f;jLKowXBFhe&Al|H zko*R>pR|1{p{X!&DD1HP;|}>yTcDn}Of$=NV~QORol>Nf_xCfE;s$x7<#8f`Bl2Lb zi4}I<&W!HFD$mNK+HM>q|(7n9%E_1xR z}fxHRKk|?}|OE9J{bF!CER|BkxH~!g~{T^K`>sfoYyzd>v z+Eg!Vt0jCR_W}1he^t-lt>}4p&~xWyw&w;DGNrNnnm47MV`0yPlGvVO>RI8AVbAe$ z&(_Dhweq~zM;UEk!cboR4!p zuuIIBo8OR8t(L!V;q=;{jpEdJ2{wY>^{giLg5xtaXsJO>>(;u1p0eF!*N z@JVlw^NrN$+!R?Dv(@dWR)pqG6?_Z(cDN$zwvJb@iJ{7=WAl^xni_qnk$S#+F|*~X zi;pA!&)42u?{rS8F8)mPwe-7haannFEIgpw=DJ{ww%bg@;X1)^$0Gp{?BY%`u`zua zF4rEzIpND<<}5}K7t@$;S5%BU$;8CG8hk_$giou^*3}_BHk*@iwpSM))HqAO`xckz zx50t@4s;^v#Pa`G=EMjV55wQ>_tV0_s{BX|4exBo&%1x`8Jjkel`Uash!3nj+8PX3 zsS0}=RNx{*iw32_74{}j1r(9aajd-J`Q~aK2NYt__0A(ZrapoHnUDL}4_JE1#;Z9r zmylBG{|>~DX?z5p5z|ZM>R=&*-}D@ETo*p`o64HlGIa@A^>wRpgP; z+Mr<-tSmcV7H`GPh-`bzXv@HW%da3PwE)V=-e9-`GzRK&o(8LV8q?F9#DJ5f8acUN zsbYyt?IDpNV+w7DM3NlS|HN2L%(+i! zX=30zGPOQ*!`{*xcrlcD}6MPh?U=Mb2|)v7FB+-tg} z>Y|sG*a&Zb=*JM3$j9n7bRyuL-ZK)(EQ%^v^rRMdh1_TRvdk^=Ah>V2i?GDBxr@Ob z`2E)8mEeV4Tx+^Bt12z4rRyAbn@c` zFY&shJ&h;ChgmAS3|r`dX58gyX13!xufHj;t~m-!TvB#Gn&Kva=#*=Sx6&K!;D|IkG<|eRHVa56D=fhrV za|!3_sEcC)6EH6H=B~%np^NIvrZsF^C$ns_ujOtR0pV0*T_n|)8BbN4AA*3IK1+ve z*w}=3>o_)3d4IBaR#UN`Xp8&YjXbw57R6BbItVR0r)*BL6yE=`8BCpbPO#bqR`O1Y7?|!F{eKT$S8S#~`!6 zL3+eL?&P>3!5D5VQ6ZS4CZIIMg{2K@Ht>Bcr-+CDXVq|@He4ZlHx{X?8vh6U5fIsI zFb*Y^F*r6D0)T(vfNl;2ycJxyYXdSkF(evMK~zJUDJWMBeR?Vku*4|igRsmrGJI$DYE2b% zq8SX;in3XYrA=*e&DdZpmuL!qVzYL`fVBn#UYzhxFu)SoY3iW@1Jt&~YaIYK=r8=% z>w3}mu3GM02c4&^Z&@~)YdGbu-p9WU=t>?6S?*bfL{diu%y{NnU9emuE2wc&<8V(s zCUDo!Z>E|-Ie!MB3GkB+cYVMmR&@*nQK9LuiQY^_{!ruQSX)Nsn9~N@t}u)mQ|EP1 zltkk~BKG6X=!L~QXb*m9SfOQ#U&RRhO&crlPzTQwdNGYi)WCF!#@5V|UaRA(O1 zMGSqae`ruGpaCIf#k(o z0n=WE4i?k2p)y>{4$!f>6|xqUfs6k-*lN565>gsrdff;NHP*P^xe2=tOc0;Qhu-LOzS!!| z6cjKk?`uxAoB}!YZaqyHxee!qjRl$D;ds;iP6Jl4U<7v~w4YRC_;|?Ykr(~DC{%f3 zt)j`|gNerVS6;jw-%E`Tc%ID3?uTX0=l}3*g;sxrl>^(@ZS{ef`9U7MZ;h0hnNM=@ ze>?m%SDz;E$$NgHQFSN&7FBotgWzabwj(Tip2{8;HlrSn3_r#7sV|>UmUmMNv%(fO zQ*5WO*g-1xUW+wqOY(W`nNX)*>@Tf&{_Y>k?+X>rKQ28l_0v3;nQ2KMu*yK(4bCIn z`2L;ZdTvvtaSOELrO^nIz0$`GNlIIga>GiAj-oM*58lB4jc?e=J&cqFK9NdC=+ZQR zZ8WECzznaQyKfXKcsJjTn{^@&!n~MD$`1E^UTFUo>`}5Yg^7nSB9a??CPa`7B~kBQ z{dMmekmj#9?pZJn3Rw+%RjA~8IY418iTL}W>xSDA&(}}Rhk+q|Z zut%!!e7r2{k$hgV-eE@8`-ACGr`AkGq&l$|{TIvk9S{h7_v$B~^jH6N7x>&4OdRr= zD>O(uDow7Usb$P*u$e=Uz0SH2)_I4SI>8u_`!GRRvC_zWr(eNApHM&47p+@`{P&)f z|Fp#AT@dPv`6E%SlD@LsT04N&gr#614&UuC?8X)J0H+3*!z+W=O+QeZ`Tm#+-)51d z(zTu;+BFtFmOx3GeWNLG7Ay=b7{+Z_zVf-mjF7}_W$V39P8Mf88Dx43lW{zMB1Ym^ zBUdnTdJ^g6qeOJQ*!yH9$j(%Xob? zvoHZ9c(S zY`}x9c~b|6h!_=eH?nny`(8_CVN09a3BN}X-OnEmGz>ljAZZ#z)GeS3z%|orLAXPb zsij^NURy(GL1I9?|6gWUZ2{yl31k)I)s`pI1%JgJ4z!0Q|4L(G5tp7KaEh)}>{))6 zc!b4z-!%$KGyMJ10rd>tOvcWDX-SN&31i}{qhcr{i70xFjMo<}ln&eyT_|QWRdSTk zUW_{z?ZrrK;My$j`FAAfb)dnNoahL|Go-;p(O{-XgDJEj;%8=&YcqQ=5KrLzt#`7& z};hRZftrSt<;{1at@NS&&e3@opmE!)Ppx&znK4m$Zr6 zqi}*Slz@@0*BmtDjTUSr4|IJh!&qw=mg2cpYH~?isjZ+70q-1Yh%1>Plf5{^+m0K2 zEsaLv1K;vCE;A#k^M3uDMq=gOnhrNr-%ZTbqE#p_H_@x&;t?t z{%nwy_!dww++T@-A8}+{_*3KU92Yw9^wk?nfh`@V=Ql0XN;E#hUNRMj8Wa@B{e2Nb zj9eLNNb5QVk|7of#GdsYj!CfOsT4pHgojp2mY9I9F_IZG#QD%`|iY7ROb49Uqpj}Wf51MNo$ z;B7``H|;<2M~g;QS>;=O1;+W`r)n$@Gj%Z)aSG9eUj~f1#ITXvUNqv}^g6l2iIb9F zQ>OqmDR10zHDL|~YJ>tV6k5Vz4zhS#@OQovOIoAG$)Z3JK=Vz|i_u!rR*rU$VU9^|UH(=WPZV;zVi03pZ4I z7u{e&nX0=?fka_?bv(MN&AqT^%$1#IOLA<&{j^kHq%S`K{AH)LBhwtEJ+_Rf#8?3@ zzxLTc?csbY#BZ@V52P@zc;l+n5-n<+{nqBzhf(k*<(Or!Z`*^_!XKrQj| zG+z39^TWFidSusPvH!@z@VU$XP3d{5-^Fu@ej9%S{%LcMVch+~L_LxsJCQ$>BYE8b zO&F4b91%z2OwK}?xSv#$ywJJb5sz9j?^Y%HyoYST#IZ0`fc#v^%9$lG%;FV7BmQ{J zxqv0#h2yHB>~S>I*xu9m?l2zm9T6I;Aq!P?V<&s8-Z`n0k{p4$el*(*-!7-6*2f2~ zE1Uv=!mTlZj=0B2NuI*9IW<^INsVmusD*z3=2tZ>MiY7WXyW!yNWk+ZcVFO z+r^QwX*|Gb&GA-ZWO_=xl$5R0Oqpdvz30R?@~s=W?3N1|imBD{%r_&S`-+>k^t~KO zH?bm;j_9hdJimAgwDK#%>6U)e;2>juGxaRTaUvb84~x-aG2g$bByTffz{O!}tf`&v zg256QqJQyzdhMsg4o~M4=JWuQV*pYIZYgSN zN0@>=q8A31ww+lJq()7Jn1illV_ZjXWnpIP)_Et;AgVyT!0ls1(Ci$~k0#!d!?CIJ zxNS7k#j$U|lX^zZRR&)x4^s^iUeAjbn&ZAJ7`eZW0hRWPMpln~MMQ!cHKGy&pXU1R z6Cet)JBl-~7i;g(-xy!G6vi$r>>HT8=3!clm zf%uJ~2R37re87!g;$=j~Ad1!dsFBS>Iyjp&Ww8b)eR_@e00$%F$AvOE+{OKh1YkOYwh*U1}m_ zZs9@aH}QwJSA%hT7%DD&;01z#4KnYY-wkrSY(k#&4%a}ujH z*u|WhtWb81MRu_>`%)1Mb9PIG&KYaXx#n~qY1Q+oYHtkhVwrE&alZPxI_VXE^Jc%w zzR9ClOxqzOMrJS_h4Xemji?6%@j}}Up&DV%^Dy}qx2uI4N}SxU4PyiICxdpDNV0)y z*fFb;r&u~geK22)D}NMgn#f;LZ+K7iI=E!0 zLTu#u5F2|`>qxX!c@gC|P85dkzITtv|1i{uuj5ytzoQRlLXYKb3yxR+hb6ixBX_LN z%gNuGfeAj0Dj1P>|NL+`XKOfYfjVMuF#{m=iZR9ebakK*)oF-~$^?pr{h8%X`;J&eh#^?tZO+q}EPO6Y+ST zQnq17`R)ZGo*-8HF<4M<(~rXO8!(c2XWbD}fW`i`Pn4ghS3F-@@jRL5ZRi1HKxfjw zW89%;;~yYSP~P~Z@~08$9-0ItvfcQ;q7h`00KKkKm_Xk^eEY6RTFVg%i8F|q_(*Rc z@&RRNUn~a1P1FHwZmQ!Dq7J3ZNem!)nU(JV&fr9pGtJ09!$q0!q@lKrqaT5df134f zYQX&KO4dQI{wWJ^!=WyOzyMo3NJ|Osb8^zujO(bpSBU+rfzQaE2U0onCe*o`6xYci z8AY)V#PlGs&4kCLuiZ;-`^$JIG*RPFCoDwG8@g!y1H>Sm+E@ckxsm7QETYp!=B-&d zxpR0G7v>5z+5ExShk7}A{EQo?n=f>47^Lxq-VKD{9re0Vu};l#0MW?}kT0PIZeWlI~((OWtQ)Qor*rpnXesl>=l)vzfEP@?g1$#xBPt2x4c++c?y$A^qI{ zDEy#cjm|PupGJ4h&b$xodVwahWCp&|G^i#6a?- zL$0cZ`v1kBl026`*4&g)`mO!6l4x2ZL_(w$i!gCOgZFpr*0Q7E0k}@cj6B(;h-_i8 z-m339(>K2|--!A@GejS}S@XF$j^XdBZs!tz?qv67hjO%z^8kB?M54@>+xA|`fuZp5 z8@Z7wY)ObYNEz}mP706PfVg}m&4QmaH^8L833Q~)4&Uz}%K zD)m|#-3-JzBWDHH8?#T@o=v}F-rAm@2Wp2!!Vvm0;xuD> z*>)qCEr};7ZAP)m->3<6Pl9R2<6mOyr4WAeLiPG~@zhCKjvA^%{%4PajJU>D^LHwdDHBuWSFKouX z$bxN6o+O)CJl?n@dARa9RvN`&)EL2)%B%62nNv+FEctYIjl`V2vj3#exfOV%+TX^Ql&R$S&AY|6>3h%9BQ4Q$DQiMlAJh z`%w?>KI$IiyDN`+d>KeVf-3|Y1Q8?In2`UL$CV%fyC3^V?1a->akf#NmmCIymOvcT z7F`JDw*;2Oq_9fpjC8zk>QFP&gwyu1_cYDls-5X`|7&$qebv8-=*LW9x1f4sV z-ys0{7yzUhM0vf;8H<0mW=Cigm1|a)-rOU3Nd*XooXzr-tb*sy4c6^FLeaW?3E>~k z@7>=s2Rk`4vE->uJyO35Ccy27TZ=z8^11<~g`xqd0i}1$Pzhzg@$7C;tsF{#pSH!0 z!u3PFQ)pFq;;L(s4P{#ZQ9M6toCeUt$teKKWK&w>T}4sM#*qCKq6hH{l_n21>9}CpOEMigCzuvLiGX zP#ocl^|4%lCk_o!OL9xC&`|FY0#H8?+0jDkctZfr0(hT7XW(>Oy`P|~s*#~#*4qXm zO`PmnRfohi#~Qad*}i6=Nja;Z=a~kA48cZH4!e{~u^5VPUhsL{;G5Hve=xMZG7Fvb?)NFzJ?m+IrsRb_au9 z7P~;7`I{v-6KdRS^U|6&iu1j152H=a!WD+EyN64n+(lq*;rMCy!uHFAlnA`^6pBws*@qHKQ^Z$zPAE=-0@!dV5 zguMQ@ep!AVU4FJmePZw;xDx<7tb{b#V)4{ec+~2N^Kjpj@>JtJG)S8X85AWHM_OTG zFSH+F-of`XIVj5;fKuYn;y(yja$q$LkfMadMu^l0B1i03bOZbXCK7&rK%u^h!kN1t zTpEDD8RA+^;gG-?3r?M0bSH2@7Vq&7G5|=RsiKiEs%22PZq>GED=DuX|4nwrj|-)8cMcRj{S&g+ z=^mYGCgdMgF30CQ*EGIkexT4`Mw84*aThWKB3yFuWG7Xr_PL zB3>N;Z_n*y)x1GiApB2_q@ndS<8wPrY7Y#w$s;Y$av{(-#g{?kPq0r@(=eL6z=?JP zp0EaID0Kk;XrUm^BgSAX&ra?XG3F7Pzz8z|bF!1RQorZ0VEVWtoa}l!QF71X{pCF= zQ$>pSb(KiHT`YjL+<(PPojJ`6T67AX^@$x#D(zU|N^a4@Oy({93E#@r1uyl0w%TXj z*^i+~aFQeBKX<=NNJ-{{=nT#QU^?ZTCryKO$-xGvvODuLP+_j5s0wi={y2LrT2MCi zCu86lNf#U1!S&qfs)TdA+Q~jAxXA!p{DWO@gI2{KmgJH2)lpI)_GT?L4F8vg6>6W! zX0#3-VVm4nO>&`~!HR<`_@%Y5frZ{8F_aJ=>x`BbnEd-YxwjdIm!<^$?{6>?BMG+z zKWWTH^k)!9eI-){eX_3!x~^v<>vFfzrN7Joyi9Pw{8iq*_t|czDmA#1eGqgK&$o2u zq*3tvJM~I9QD<15!3ltPyg>$G`Ne8i^WFM$JKxk8JJ+moU8=QeKM91qRfn$MI_xE1 zas__|^N~LstS5D&zXN~Fwj@WP0e^ubX9d>uojjrN97G^Af_oiT04B?mZ8c2{k9rNwK9W^C+{ z9K*lS6uFj-!O6+We-rGjy_mjwpm~!IS&@UF$h<@&p>j@^^SO)&^So^-H7{r*wJ<#^ zdPHiz%vCc`73J9e9j%(*k63MCF{3r60jUcRNq%-a*))-y+J%w#CV&rGCUGv%S^cQ< z4zw+1CxbpB%k?@Ng4S{M13jFq;$=(CDjoGg1U8y*vQN@OB0uJ7V|I%-E_Y_1PCT(p zZygFd;OdF$pk~Dpm%^phC9C;i+6!hpTC{qNsLuO61Wo-LH5xC9xP-B!cOVtMkQ{`` zy{4j$k|ddW&yM73$nNzr^&BuAIagK=R&$Vnk2Lye?NFVbCR7Kxp%>%X#?@Nq9U-!6 zpimADWI+iIq(g5ieJPQOKgoz1S{s)bT^8;N%X`Ye_xEt!2WN>b#j4 zz^q)Y8>~xg;Vgg)AZNu$grd~SO{{52pdvb?hRbuzGV>UBabE(_>bMcDbyhN=;;aw? z)*l9FvJz5e@mHrswY}uIF`{ycoOz$nC5$h2ghWS*}0)6X^LSLzn|cmS9$z zK&%jWvm`bP>A_qO5$J`t^?C;zR6s4zW;^vwji5YMIKw(Gy3H^(pl-t)s$urxt@HvG zf;0ZyEc!aAfN6?e0}Pc$ubnNyP1`7aZH=6+*BGL()qCb%q0D(2?9ojBMny{Opd^1!m6nyhw{%}fnZ;OeY0>0 z9k25SFbXUoArbqFd=tDroY$h25p`Cpul@^sDsvRIT6VhwV~we@C#^}0ag*0W@MJk- zJ!r8mf(&L{ghpEcdxm;*@79O|mvg;U&6q0k{<0H3)zR5H5J6rOEm;+YY=4f%V&za~ z+RR=`{nG3sr184qJtX+F`)fBa#qO_ap8Qdo-Pa%Z#vfB>U&KFx%}^kW7!dDTs0Y81p(&Wa^b;(z*41YHvGDa~-f71_74vRsMPK?8 z)$f9G#Y0^~$OfpS1RsKa=AWT10ZFWDqe9D9`gVf8VVmnGfi*cvOH=P1P)BZq zK%bvgVVz^E`7Z#-ZHNj>U~qYBf9$4RD~wJLxVVMr&+YYfK-pNViT7KGssdnXV$< zKvRfqE5i(#o<7s2h2oi=+iHlY*|6-=ao~uq>H(uJR9#+8SHrw%wJ%zyN4%B0%7r@N z!g6XT&e-kz#ySkHXYW(#w~P&Hy%7K3%quwNMWmk;nzII|E`8`CHDincd)V=RI?h_+ zn#Ci6Fwkrwk&(|Gh1 zQk)+7{V!P=My#5J)V(qHwM5~`sZ>qmbLz@q&Z9A&PAc?$0MTo1%W1Lv?HbIugj-Na zZ@>TCy7in`{!#si4N9e_#5%vkWB93(@)*HXf^`f0GZ+Q^&gWv2V9v2Y1~#!lcMC*H zqYMf#A~$Cz-p-(M8o*OSnTxlAjn0~Sa}=nWe^MWG2(U2xf)Ao*H?I2O$6NgxBH=3d zSN`ldnx&j^TVtK?kc`qyPI(tx+pV^5sZ(#?KLE8tuRr{-cf_VkE0J5XX@ljZ6-`-T zT*l7Hgak6P9J2qbGdDMmSFE}>d!Zx)1PHs_dcEy(KdG0&iWiTl9KUJBG;HNH$sz8G z2@+S>g}&}uW4>F%ZAk)t_eH*JFF77%gRl$iV&HpJE65rf80T-d9f#{&AFqN~w4ACH zRyl9%Tory;&u4TD8a&{1v$Wsjr&(GZ)ifGn6+M9+rnN!059NETb8`>iVwSdZV-LkKjpqm@p=f(<0=MaS>-qd46bK0LSsg!7$g>SBuE*W}+OL-wt+?#1( zF4yJSP2rP)VXODWUS)1IiO~e+TYpXP?7tsAWX)Jh`II=a_Pm&n;XE7@!);~x4`(qd z2n{YjD9vZz1A2vCHFtF9JmX%k;5@&RHQREY0|KUl&xiz)+cKRorWNTDmg#s;>@Kb! z#50;rF8pY%Gu#F@(xzM0dn?&fke&nXQteH6*1U~{Y$j2-2G-Mx^?a+ik@vgX`L0$A z*N@{Etx3MR)_e0;+M`5UiI^il;D{L1^N@O0Lz&E%#41R*_me%#r24_{mtosg@w~X= zxs&I>{t$ORRw9~HTPDTb=MwIV<`zRFj}XK-;41tdF+hA9Xbp%TzP&q$KUi4;G4WSp zOCTO%;3O5iaLx80USnD*^L6jk-MWF8h3XFCAKbD%h-mVtW$$IOZXWTZ5ZnJ;~$tnRqxf1a8dz68F?SGb^ ze^c>1tK#`*70*{zJWmgw3%&OAqY(`9_Phab-ct}V__u@KXK01IrVXT3@?I9j;6B7^ zaN}(M(IYGFL!|zfUibETRkU|e<3Dfjo7=Rve4F+@y{$aHeqW=#J;U}gUyhqOHBs34 zE*flc*N>gn^G4E?o}h$xX=W0U%-zgEhUADg4tcbV7(Mv9Vex4TTa+=Yb9Jv+)#y5| zGZcB^s-DsHF0!8PMU2%3`3PmlURsfV-+eKETK$OA248nVY}z7Hac$d(ds@BxGj+M; z*Y}Uv6K!41f@vh?POmAqYnp5mwLF=$_0gtpVeg_)Vcd%J=047BrSE7T_EN^<1Y2j6 z{oL{d>vMy%VBv!2o_+RNUHg?)qY>V;A>mVKAb#jyze2{=gWDPxU3*w-VUKjoJ-N26 z&~qFe&{9*Mt6t=GgU?pB=U!@a+Nrm2G5IZN*Is&H^}NCUO!=$DKaIA z0W|OvnUrOtYrQRKRKeHO+n^{7jIK2_)Zni^IrD1Lo5v^nrZ=BV%3*gsA0ln}lVdH9 z*8zc+LYz49@kFXo**!T%juTxOFuIm}SN@wY4KpuF^=Ntg0Lm9%J-^J}=Z0N#*42(3 zHFiq!K<_+;g|skiVS8@jtR2haq3<+}Xfq+oF#Zt~q{u#*jeV}iBz78y_|wNtNxd2q zasbHUVy0Z~5ae*Oa7k{f_EpU^Ay$4u@ly)Q0WTmioG;m>G-ss2TLBLQ4)sELU0 z-%zWyL`&?IIdMqRTU^>tTT}ZM_b-hnw}E}4!XK6k7c2IgN$yyq$`;>^J_(oMAf?iw z7_C8(cqF2XM-SF5D}U9C0xjiL!CBgLD+V`ttDh1jF%O^02zb5mRL zK;eqrhmS|8lcregWiPWj8seQZ@3d%Pq}vqbK9$VdtJo5FNcyZG?GYu~Kr00tn5@vi zV|IjoV#jZ#mE@ro!N0Fp&2t7LP9LPLQ$)MmGdH)}_`2s4a9EvM4)=$pmE%zDXm4C0ZNFy@+1 zZFA1sGtxHaKFv1X_-69>HfP?!7AUC~k(kYNbLHu>5w_lLXPh%LHk*IgOEzC>1+v(6 z(tdu7;fAq&hyUo^Xhb_YGa@l_pmuCqE)t7+h9xya@0;1L3)vL_pvSOw3BKNmRPRRQN88#ov5D6MY` zCk8?Y7jM%3?Y^HaJ)D@jAf$(c%9CDwO7fia=5HlW@eq4~XBxM~e8j!Y*G?zJt$?y&bWP7>uXMwJI9j)H*%i>?bhvHv_Z~4)z$X)|iAnmxLV-k4Y)2IqN{|Z5?Lhg~g zvyGS&tLbX4-IEc3cJPnWYSYI}NL`M--QWnWr6?gF7a&G3Dei8$ePL?f%-^EAbx&f) zZke^Mr1LjqrcD>(6!v_2pWcxtl%gtD`0X5aadGD|e{<&T*zmNIeOO;Ji#T`maq4}I zzkS#n33ugUl+TfdPdf`nMy+r)^Q@*R$*IUT!(NhfGu+a#Lf@APi8(6>kH<#OI2QEC zF2rpY4thZkDz&=LSdr+e7Ynhc(wq9t{GqM3yx0}uQ_~bDr;yfR%NtK_aBfZ+?mC)7 z5B;Au<6&PE-($by0jy`c3GqynAGzOt`U{Hg_Bhdvr6x ziIaUqFY_a7^CRn`qhpO9IfV!HX4-4?74LkfSE6c4UA#+|5^~D#dxZM>@IZ5M(BB=VP;_YXM$(a-S#M*B2zH!?|oPPf!w9%LUbvug2Ud%^h5tcxZlf z!-&yM*Kxo5X+vYvo~C}n?f=Y~?r2ohm2CH{`q#RgsoxB^IzO_;9|M4R=L=A{;t%6b zVcb*c&HbF4PiH_TF=aP;fR34%wl*HsVdn`6ve0IHGct}ne^P7!-*VyymcBdL+x5*z zcDL&_yr5Ue3&bMe2hPpEpbTjf3|k=6PiAA)^-r{;uc@ie%$HT9 zNM>KryRX4`0#@svNGl=yLcKAhpM{b4(0|{;$X}>&{~C&_=bQfy@M1S^5?ga-ui*zY z+{xLiC=7(SAi)X-f#hr8Gv};ckvVsZQzqO`{MV_r%yPzHxJDk>jzK~*{wEw?DQ&fp zLEus|w~K&)m3zIQLa?lh0@#e1NnY=rZ+k0>JhZSZ0O7M37Cib$*8~Y9YAc$^Z5+nxy-Bsy^ zQOOHDIiTcV(3fB2s|%<66;F-mu@cZ3osik^S*8S6`3(50?RPcx@)H_HOlWcnT5p&$ z`2VpHvU`5yPH6DY;B5#WZ){)n-wts!c#v6pF6#B6`}dCI$JVoiFU30F?iGtJ8ZmaT zQ`mA7%xf9}RLi0zeu|wa&{th5z)ADt$v1HrFp5q<_nA@a7g@Fvz1NiI?5ZwFkqcck zL)P)jaLE6|bN3-@W*YWS^@ZxbI7a>wAk}j~`npIw^*{Lu9M1hjdhqEcLk zIJ1}Z=KK7uRkS{vC+DY&oXl@{$6;S;{XpKe`~*A1wbai$YPDkK(~O*;GZIU*P0|f4o4~y{ z&0yzRaA3KeWZ2GsZPU)aw8LEMP0>YOA}lw9atY>@2xbA}%!&bLRE1R*YhTV|*@2=2v-_(Of_fvLE3GO)yjlZbWSOukxT8HnvKgc<1UnSMxyt z>|%C$3qA;D|DNrxgd)RL*AkknqQunKf#z?g!T4Oz78q314+nL@3uqQAZM9b zN=Nq#^JN}UJKlk;j@8};>Q9V(r{^8fFUhRLju{`%z zxq;r!eaP3V-p7BO>gnryumz99ju9f-y}V6KTWz-x$&QdDry)sl;TmugvbAuR^hh4P zV~Q;KoeEt7kwh%me*ZyjbO^hp*nV$A2(5_(S+*_e@TP5LtB1Y%-hFQ6m^LceFERr)@^esonXNrj8TMyCxWe|KGF%nXc+a{S3eL&^Iasx7VTkej81D zL$<8jS|Qv^K9NOli_SGm!9XOhX(w1e`ztL;V4&sYxG4Wb^d)x}Ulz3U5022n&L`~B zJ7VGNel+k!_vudWfaudNZj7!#AyJO2c%f-I_wBYdpl)72(OrhN_zD(GYXdWIZ)-Ru zntInF({=B%js6h^9ci#+7Sc>`DNmLcGT=vxzd*l3y|u|C3*+Enr)etVqAlr-SEZ)4 z7tUDGlHP)qzZDtB+YIYR%Lc4y*-qgyiI5fDmi-gPwZPqFV=2+@EO-OG`zU%0+zV~) zWaGE+kKl7ldh=CI?p;I&x}4_ZB+2JbOAK3>zg&{rZ42Ge*AX2*Nf{Jr81ufUK!6~3^IV7KTNR)+iq%ac5Yin zXx43=&DGDRE^j<2c%kerJ~_M^@f{2%FT!!m>smNPjZ3B99+WkVGx%m0&*T{ zJ!tHNrX!NaHH}a9Y&tpBn`rs=dSsI7iK&Fck&_D7$XvAFJu=TzBB=woHoF1U; zu!W(W(9i>{WOrLU*LK-3dbggbL-Qw3ym3Sct;F9^81rO$Qxtudx9QCZC;MOKVUm;kJrCA+ zd8w2AGp(mLC!O4nRXd<0&b(MOy@{9P&DKk^QK`~Hx^h-`L1Xi@H{W>^Io$#1!6@U9 zs&?kQqmDMg=w4To!Lp#l8O`lpMIKeyamrd7b3<|Kio}HF(-uK$@mdP*ogyWb#ii zgXS_M?Rv>RogZry-h8Aman~K(39)p;rB3eKbb@c}A}4pbdFnx|=r;|PuJVozIy%iczTI(IlJC`oM<>ah#Vm)sDEAWpp zzU5~&%i+3Hei!U$jhRig$)nSoTRG=tmVZ{Xa54zjiz}@(w!-msE2JJRk4@`)Yb?6y zgsm7m(|n2KPpw6NX0+}z=}lC>)~s$@@((gjW$7{WZ|Ro%H~cGkW3x0AX7zwu#N2CG zKghQd>L;eX4%e`zw=>J8H%iG_kf%hvtBQ>pX$c%dE$&#Nm}Y?DF>r9BzlT~!7m5_` zeF7|u6o2@H97)Chd;;u<6!Y$j8pDU^1lTtNm#e{L*6B>`*w8t7j)Vdeb*$}U(ti%- zdwLUk#lb3QYD=a!Mt(xvHEkjNRl(Ap#mNid{jy*SI&)-RFnIsASu8DQYi+n5NlDGCaPf+Y z4o6h7%I=%YfSc6&T@lO_A(wP7gh@`X_3bgF*4pB1!JM82cHMiU;7AEA&Vq40Y}Ye6 z&9x9`->RTzuboiIyOF+(hAty68v0~j8~DZY z#FLl&4o>b~@Q9sN-~Hgu`E#4jaB?ewkL3gMwU3N8jdrp8q#^5lf6Y`5Z2dQkg}|Ampm%|sLbjwxGs%GTQW@p zs+{a34R4zvE0>!T2I0If3DD5DZ3;cD9++2X?Q5ChyC@T7`atiqcwvB~F=HH3S~4>e zCB(~5Y8WvI)1CMRm%<08yM#O7BX?2*SXnsbv0=+`NX*=wwFNZ@FK(~(mLk$>Lfeey zxM0Z4#*B*M6$2ahrVvCN_Ox2>9jQldCrLC$v_m&~3INkqXsfl?8yjGu*R?MY9#Az2 z^hb>4=*uuI!xBxA-{6U=fm8^+AClak>qA5{ZiJxkr8 zd~+=eHEg**fIk600DceH#rMC|7UXw5Lyx-)Nh6rt%M5{QixmjUr^^@!pVC_MHvT@-dG?Lu6Y&LGtEy}2Oha4KTiT#2V^_GZ z58Ke;ZcJ}F#JTwvK4FVBjt2puIlIBhIXsxD+|1YX7WUV3%ptvnec`esW>yo-thPGY z@uqNxlZ*3UaizW17r7PQl4nK8lUcp0X=3VP@6Tr63WQGX3DD7hS=54ZPsnjLzny=`?|E`KZRx5MR0PmzlQvY<)@ne`=CAV8Wb(c6>kgH(}+1{uUawZ z5zR;x|3}mi@GgGCQvAiv9AmdM_=K2qwZ5Az(2kvTop&SzEfCb4mV8yXsb|w(h}21* ztt$w}2$m`?YUpaw-iUzYUD45cIK4LEo?hesoz}bqK9FFYR)G$;34Ii>%HrEWG=@zi zU{Y?7?^vdIOMn8=AetcYEJy*!qcl~7QSJmmMj;3Z4IY4_a_a9J6_K=g^iBYnj@abK>p}i)WO2!r( zpm9g`i9DqVD5ZEI_HJg=(KDSG+y3RZW4Vu=To1kx|B^aJNBHa&q@9%BrgFZvFm}7D zSoA@6lLaF5YiWJa@o~cbt*Jf9X@=FN?{aJT>I`B*R{HV-J~POSW=? zy?|hTJfnUowaS(^``d&*2l%idvvGK{bNezwNzR{ZP`R127`7UTlZ8B2YsBUmZsrX%{((y&9l@Jd|Q&&5RRByqm#3mCMJ`}hx-{? zvIwin-n3GDe6Gi(qw>e>&QI(k%9@tuF_NBzLYI9WuErdZc3HkzJ-!9ks!AqkuNqk} z_Zxi$es_vo60@>=L7bZYR3TsqUVNWrzZYHhi-h{4DG+{_Y$+{9vgITEWsKj37Te~= zsJUG!?LCUxLHqbhHA-{Wy7v?NP6Si$Bwu?4-Z3P|mG@iD=Rv;aSO=uI1L${-sAIsi zys6S<+GD-sy4opdMNUrd0xV(l2Gut$->P@h=!bVY_@dsf;oX9(_~Gry5{OKSG1=V} z(WE$FXD0Or{xT^`e+461jr=B6zfjfxXos0yA|M%Cf~ii@NK?IFtAEpwZ&i0x{RrqdyK)@y=| z@C6IC`6A6X)48L*9Av-Xmo#6Ll)>A@zS}u?XO3uWUS(R?EqFJQa?*QD6kTzTR%c$% z9&t96!fToaBzKCtE4Z?_y>amk?u z-BNbH#ogt81204TntufS`dy$@NI!+Kdx^I(Q6=%W@8=IuxqC|G)Pq0N?3Q)jV-{ro z30h*R-yi(GEcks#@LSF4ySYz@=I`YDHvH96#)OlToEXE{X6)NUbUE|JV0?n&jp0Lj z(;3OjyoGNW7S2SI6EY2lIoV$ehWQz_El(WJ6R{FTQ1f|CZ$_ZbnkGj@iwBnJzf@0P zhNVqtZ;FXrb7AXyn54P=HLwGku3u35B@;liQT)XHg*{dLwa)d|Fzh1;Spz_Lk1j2z zer0!7D-s;dTDThf(YXF_yxt_l9!N++e&`bh7YfJ!W>GZq$jV)@^E#{rxyca5`=pSIprslICjfvS<*W6S6KtV` zEX0OB6yjeH|6-!LVT#h^F-krk0Cj`@q9D(s<)-tKBW*aDGh19=i1a)TF{{c1t81iO z^o|0g5{X5bK}_yXCLi#dtE1DM><`RH*Le%D(T4TgXcJv~BPP^adCY7)n#9bWp1;SySX9%y0d}XOD$KTBx*+Xb`Db0${KcxC-9Y3aV@3ZG|I4ES)nRAOeGtvaq z`$qkc0|s_PEE5MHl2;&q=O@*Vm^Aph^ApqFj@@Z2ZE{V1v%)nw$+#v74?&P+dVVLX z)N~PC3}f$~c*cfuDVaog()f|^i0G;1V^+BY5M+`&QX=Bye#I2Z@?Vg3HBGaTP^~v< z6u_&d%Tqt54tSdkTGgQa!}wwNb*cP+L9{8aMNqT*16AEk+{Bzz;~|cjk@R@M2=lwsyUYB3d%a+MAl{daX6_{a!zPA?)D-Fs&-)!8nnkU1c=2{-N_wXNW zvvv%fCc#!X{wl-5%(8M4Ip8Des#;TmS1~95DZXeN5{+}67Utd>BjI2pDUH{})~TGy zS9P1Xnr@R&zPjDho2Mr)OmDs{IgPWc;6!RHKp2;5Ck`=^I#C^t2D6#5GPFI&q51`l zc8=aU9Yq5YKd9gbnMGCjzl4iR>1D2}PqxD;J_Q~gHzAt3Q-eoZPyMGhn>i*e13{JI z!==oDr^tuVX-+l=T+$=Xce4M@gPPizrc7F1kQYDW^9+Q+vbUC)C#{(B5tdekw9stH zrDml+E{*$G#x3A9n0a*iPZ}Vm`32LfYN9{oe}P=&E@uv;Qj0dtWHX=34Kn;*FvV0` z=cVnSgzYJ`6P*z^S|r*J>}3>R0j-nW%RJ!7IEc#3i_zBdr0JYs7<;F`mfx1YWHz3c zx=u)#nrYyFg^C@9tFDZ=PVVq+5bJ(0Y8%9wxZG6ndr(yCZjl}u_ZQ&^n<)g5*-_#T zETsPoA2S=LCr7E*pFjcW)qhGfWged9yD`~awNu8slG7lwtuJPscg-ti=Drku)p`&S z4M_NkrZw~Uz2?Ckx(uZFfWe136!HJeN&*bPvXjsQGTUuL1A-ALd6tGNuQAq#PJUKM zJlr=%Vuu!UPcg@u@C#-CG$Fni`9N7ELa#ZI0-h_ z{$@~_Eg$xh8I7&$&+S6wz2^09F$>CLu(cfY`bpgVi>6KJs6t=c z`)yE8pq9+vbSo1v0K2qgmhI2S_I@9`I!StKYkVWF1s6nrpz%nr1pCgMcZo5g)ut*k_sK-qZ}B;N*7X zhk?X6nh+%Xe!L0gF`5>H{9fi=bEKOH(6(zd7Ph zL!kmb{w1h>{3T}lZ37>37n!yyRQ~ejuiCEi^JK4O)ZwOO{}u-aaAUqh_@tvtS1c** z*Zs9$jUAB6$wB(MJ2MV;=pVP+p(mSxvO_t>xDb55RQKz(DIpTO^kqXYd9Q;o+w9Z5 zRMG5HjY9j>U%QxH`sY_tmZRJ9zm|y%wwgLJzavQk zGMmQDWGcho;P!V8wN2~!1CHY;q}F}1o`M=zS58&<9~9j-Rw5#pM3JhiViLeqN^6l4EP_fbz4|@^<5{31(=%awbtjG4t`J10HiPv^mD& zm`=*W_aB5~&F#>H+VW2|P^%@++H6hrHDXaO608yal-YQ&ll5U4v3$Wy%zdGykP~SKnv(h8eG2m%@JM{EksPDG{DiEIqu9Jb`5z zZ}G@bp8tw-lb7n;4Ak{tffL2&+*E|EAgg`~CC&&HOw54fyx!!xjBsW&5xB=lz@cH}w10@oz$9 z|KGCxf8G7pxAtG}f6+q~^H19Tov%6nTHIv+D*YSs75(q}&-*v)|MI_q{{G~_?dRY3 zYxckUKL7gst19~s@PF^G*?%7txqlV@SO2nN{-@gY-{EWa|Il9ldjB<*{RjJR$FJG{ z$GGJFwfX1onT|YEmPiur{d$qe|JsPhT}BKCC=u@?grD@2lAfBluNUcS8>#DudS$#r zMtrK2x|*yQ)nsHNPjZ%R2$AZ(?Hl?9f@4KPlEY504V-`sY=-6DtwH*a6R)AmQ)?3W z$F>81 z82YEzn#h$7_mA?>`hhmUeFMZi4nQj0+%vUTpe&eZGkfhTw-Tb)mQ)_u z*Q95R=*gWHf$F8fpr#N7NwY15Dav}-D-kV{;Db2j{X==X(7IZu&$Ex1&KH}nDSw+K zxL7J(c?k%=udZB%It#@*?{5sE_)vM#kpd?X+}QHunHK$fc|DDQYi0f;(Emd@urg1| zHnQmVP%{lKnseeh5%K>Z?<=o$4SCTijPjN#f-0*_WBw^M<$S zk=UJ<0?s{3{>8-5w7JOveJRaURx_32?pA)){SWONa4!@%DBR{gtbk>ijzg8WKYCXi zS>gL$|K|{vg`MvfES{1UR%GsJ{+8!Yo{~@&zgGH=*$|}rPTtPk8v_~q#h9y8;AZ~e z8Tdde>z*q{r^Sc8WPRN~4TOMD9ohH5Zi}gHaJ^l?>toy74*D=$gK7U*^%%-gIwPKSv4p!G!$Pyb&_VvLGyDfp#R9mrVF(~USnBx9 zmMUlV{+vzv#JN1;zAilBWV*9!+a5!C6JviR_8>)JVoyM6G;e3u#2(FTH`~9A1&Y~| zIU%#QAN%U6pLO3jS2Dpi47JS((!EltU3V)#_933YmC76wk1ow@iOTvo$qQFEQ;fzl zAJloDQI(IWcLJrq5&?F?CgM-08M7N?*ZB{ip$<$d{}1Mg7Y7ND#{6*BfVzM{C|)SH zE6p#c1AH>Vd}kgAzWw@r$EddkG7K+N9JjLu`IihAR_u4)eHBbA5SsVJUJ&ln5J$J! zqb=bc{lDsq=eJ?65x2$laIdv6{eRdxQ4&p-mh<_!up?olHW6(tIq5s(Zd zaK}zG77<)fEETb8OJzoI0fI9lT*ra9P^nc*T`H}%+Ny}klCUIT60 zD%eYUZo)=b5%#&JBbVAaipg6m*!`iNNGDu{iySa${~B{%5VVv6-UWjl_^Rq*S{~hl z-Ig*{)C4U)&@M`g`|-B$S{uL4dX?=)=}u7~s$8=dnCZe1c9OPs5@~FFTs;vI?U;~* zJ;cjMa%&mo1-p7oN?Pm@ zNWx$mr=nGX`S$?XglU|82>7yE$C51`9{UHqcRq=WMiV%@1u-#% z>tsTSk)XA~_%1T{&fhA#htK7alYirINpo?4-+NQvzk-6;`GCcX`848D^phx>)A%DV zu~p58XWVH>rKsLDjk{?7S`Q6r5s^8Xo9IDd9G5c)3socTXQr$fYpcmEIS zzp#b+4^{s&3BVOz{d48%7Fft_&}kh=dU!*v0J8t&0$YlFn(ogzc$=PML~p3KKEiej z{RrL$;N(oKMrUbQWx;ARJ{BDi2TtZweGVXtJ$X1Qh5QZ=6) ziw>SM(Q?i`z>^I8M6DZ|~vjRPv~J-sonOe@_iXx{BJqD*St zcBV6b9DvTg|KAbdcMHJJg8JYf!`2NQY9VL$gXi2#-9+;NpMUV2|7khr@3Dh*kk(FU zIp@W8&I1V2m$aO7%)#p<5L@WyiwDp7Hj=cEb2fI$4${v`DI9!L^aKmN5D9s?A~gq4 z8T`5u^PRE2Bw6gmeIOC#L)_=Xw1edkg zlQ8H$Tz$|j$n&%1mKJc}Bj_Uzf&(9R;fOa|4F46u2g&&al2De+4MZn;a4fO{3%eFd zI*%1NV&69{2pm^BRXJD)99aCI&{I}m0X9B7W+BT^X!-vcJ^jIkv-DtXE2(?lf|GI^EB4umv@|#z#-L;U+fFvi4h;zH)&-X99qA^ zH?9}py&+m`6m}3`bek85HsUz_X^b-VT5oND*99JB7t7L95gJGiYQ*MIN9$HNp0KxU zEFU7GKeW~2zXZ*T#wO7d%DZ@9e8Qg^)X(ShZBIWfULahdIET(zd*W(7RNJ>uiytk) z%`olHY?Z2)il%_nJ-spbP*<7-a9H|d^w=G$<*iW`iv8=%q96Jq$9LLuD7 zfSUpdteq_1Hj5pIEF#{wNZM~1Zqj}dBu&-!&gavr?bgU|;~`1=r}nP3Jdg~&BsdZTOdS=;Fd#5jyQgr z43OFCA8Kf>`CAjN8@`#tQjlr?_>=9F`N7d0ALe{(>vleeGQ7#;n9|UExEjYKKebK} z5)`r8dLIOJIHJaBVkAKqC_JPUZMZRVEDrYkCem3BD2a~?Y(SL)|7qpw*JAhKmFLK= zMl!g2IvxZdXMp0|tCf4Ox0=2FOd6^+&zXe{Se%<`Mv*(YuCysK!>iFXPmmv^^{(IK zLm|p`-5e=Fh2tWZNV~8<-K@;>(q*=+T)pflX}K21j*^^S(&F>vjR(shDK^@ErMH?{ ze@g3-$t$}cXA3pS_^tY zXJ^(r8Yl!SdTWc6*KAy-LApE0JpU?iYCug1CSu&H1W?)ttgIn;gwVD~*J}mI!7M!1 zX4EE7vb&(ge}@ouWVE#7CvP57#_f)%J^yn8;Sdw&lDLw+%Wzhnt+d&O`P^e@3eJ-W z%LhZpdSp%1-omt3B>#C%@ssvXlb_UZXnvPs^Pe9oKl%Jf_(`(&GiVgidFLNFAK#v1 zy?mJKk|$&z^5BL$4sg%jLEv7Sn>Z#4PcgAKs1G+9VK}9Xt=uM74FTThS_j)E6Bbq4 z)D@p_Fy9WY-B1=^ti|5MXS5MUBp<&cMkK?x!EU6yuC{m^kpcX%4klTq$RV)P(mKPU zM4f+uFrsxHz72O9AjQ~qBBjhk8Hr<1AzFgU?tIL;7R9Ib$lJ_pA{3cj$b*}6oIg@| zp5kJ%f$q|BEqaulKMY`fd}!%~mI|xHT+O=ZJv1D2BjZ|z8m+q#ayZqVbC~J-K*ma$ zt9<$hYNXVa3R%IBAVLx|hGRm&-sg0=Z6WbkIX;sC4sw>BENPWfe-zG3F3!PCvRdq~ zz>H9zR*@~ZeOO02+{*{|mXU7O<5?N!ZTvOi?2!M9a96N6c%X!cb{7)p317%x9r;4s z_`~Dvx8^Ho1-(o##q)2IIx%N~pPs;xe~o5d5Gsvs#8=Gc353-XB;ZANj+Zh?z7Dj+9u1v>0++tmO(am%d z7v7ec=_>%Ng$7Q^$b^1HyJa+RBI=TLH4hx?)8T5D2aZSaz;O-Kd=5;_ozS?ZdjAVWx(5f< z%i#dQsofkd8sp6N1Z4Ty zBKDzlAMWXn0^>cChf-`eCKKU(siEk>?eym3Yhe2-J-)z_?8zBuxQlSbFh%dC zNz)x@0oIY0qU z>%^B}bRs0clN=C1ls4O*3hd3Z`*ariL=PQUu}U8L7!{#2v5ehmCcN<`N*k6f3)O!| z$%oQpG`AO|CmF!##en-fU~~``o4HYcK}ajvU_DQ?hc#OC;(~B{_eofn-CG~jX0^sd znTZHI*V;nkM{y=OkY4CPTVbtui%EYiFkmdk*<=wO03#2K@36YEZd=;e=ucjU0=4N^ z;hh&LC0^s|U)`mYrMHfAg2O%QtTb8$yeMvb~m0GGu4 zvl<(Kx{f3;@Ii-(l><%9F34kQ{AtghZ2oxo^TVI;vx`69@nxJR9FX6p9ic5GGdu7|z;LFzAxi2%uDWm4) zbW>Ii-5g07ZZAqw^@oyYRNU$Hywwd&;x;#q;%DdsgVCjq3H!9`y4rUjmiae_{|yhN zC+6Fzz2;Cvq)BzR$4Ehq>Iu8n`Bc-pI=v#N zK>CBLlZ5j*>Y;X^cxl%mDmecGqB)fGHe0fWsPy&)@3tP*b1 zNp_layVsMr17|h0cz*_B!3b59d0e#NXl?3A3>lR*MfWb33*E8P@dz25o`{k@D)#1K zrxTpc!H-uNCr3>lVZ3sv$(3A9R_pGe#0%XSZ>+D03YXMtqYU7m`3y@@r8?@}sCNS()Ut zYcyMs zsG4Xap<$o7T;KQzx2$SfXLKqWEk>XhMH9sMhqH%hK^K%Fh-{rDB&G1z9xr1RZXoH4 zX=$LKksAo{kOcj7^}1c5gSL4Jxg7d9^o)OGMK;H~R|bxPk-!4r&eM%m3fOtpKV5Rr zPnSQh0D2I6xJScr9!7i~MhR=%3yyd*cA6}}nHYs2^0SiRN!6kA&%L#0fdfX#z& z`^3zi$N=1bm^n9~5{8mJ-o1rWzD%iQ$%(CGv0>v6w1-i+K$AO??KwAnMxo6A zV;FbB&W+d%hWm;KpI>a&U;ht6Ih6PXf57;%nd0PRbLA!V)GbSBP_g!ra1jfw-#zX~-)@fk4rP%+g>YP!q&cl$H|C^ej@~8|9(~ad+8U&3G)Xe9a z_QKsNp;V>t^%|OLdwBacJxPIKn&Mp*)*WIV?Oji{;5O(wT>@RY|Vp z@qx^F=oXx&Zc@X)B)gj%j!@Z0fLiUjCU8Bd74uE8FIse#7Jr4n<}72=Anq@w{15Zc=(g_E!*6)x@$#*Wn4vgM)BPl#b zP8|!FaUUOU4j)rfiRkCL;3jOG&{sidVD#dw@Zc00z0EVz(YKh_gIll@aAImiB{#RY zT$9N+EL|z>()v1HBbq8|@=q02@fL>@lbwuk_zp)Z*q=!UPC<|k`YL)r78(NKK=|n> zTNQ&X&*{wPCfYFWgK?A7B`W(R)lThc8ckcu=%yBFFp{8EiS`!06T_-^=8*ciaa~EB=lhow6@_ z@%iDC$)RMRlu%(k01FH^J`CV7B8=H^AFbPeZ!aimqSlRYQuDu@KXj(fj5Ys}+PA0p zr_zt1?O#)d3mW^?(rCvJeadDZIlM8`1X+Q-^mh?b>GHy=Kt&_(t;q6t-hg=%_4V6-+;q$Ft^y$``C5RPYXOGl~;ba6p@b=IfAi z@qjB782Mqyk^}eC@BGwFcrQy%>JX@y)ZvDZv4C*GfILQ-KdJUs^ZTw0s84B6#Rft*|sCL7a7BX$5{?MTl6 zBa3$=dM-2iE(6FQ6fWR+7Ve7v06XMH-e^_0B3e^o_PLFMPz%zJ(fIkvk0w@Q`j#wH>K39%T!qG6syBtw$M@@V<-=MpaX}>l; z){a1$38~l83Z~U-OQ+e-O8ZF}hT*gA=N$X_vi+RLr+N188XKofj5We!V{+Tt(wIb! zDt$3AbiOQ{#o2jj6@#^xR>iAy?Xqg!_<}Yy#?bk=%tW8EQ81$5JKg_Ia}+>er2Y(r zV!Y!J*Kc8T<7?CeXl&Fj2L|CD3^CFHdQJI4mG-vY5<(#`qclO-EFi2AneA*c6Ntu5 zFPp2e`HLM>LQ{uV^TbR-5s@{q-h{QMaVEzgQCa~ajaLP5t9X4U#Tw%1ECNHuW=v1Z zT~p*Ke z6*HGam$b6p$0y@;g;!>5Ja(7?%tV~X4r>*w7HEOIw0PyX2jLvuMnWJ26Ojqwt}GZN z0i$>~2`{Xabp)~S%SyXI;WFCXHoJrYfzoVYLq^)u_gpPL0yWX+da_)drTZ4$=N%_s z;~6!3_HBpU+SH4XN;kIK^G)A#BfsHYDiS~bYP`16lb5zimanSV+YXAT@t>=CmTBnC zTiq0M=eGJ9UUV67p_AQ(4wv8Vuo%0=anW_iQzi|8EXNBRo*`1*jVHaK6-{CkdfRv!@5~UK^!GgYhUn{8v_;mP8}BLIla9^sxl~ zh7Vm%vw$4v#laU4&N}8PppAL!T^S2bdUVE`4F7!<{?S@IAC07Z0atL8FI?Bb4}d3+ z3HU!IClKuq4}|A~HVFe~8gdmDUDBT}0&7@>VbxcTE(TKq)3XMk0U zc`ygLKr2F++)KNjqmPx=OV?mozUW+lO^LtVO`gX}t2ox1J;c0LL{dFj=3cqOXSWhn zOQ!Y3`bz|M-T!X*G6+Z$9d*nht-oyla8Na$hW6AyD{@a^m6FHYjhVeuQL7Sq)|=a! zFU8?%)!k+E)4`?^LqJJsRHh1-ii>=+?;VlPnt<=2%(9w;FMPGv0bD2<$udWxJ?{2y zgF-p5fZ(-DcKI)JuV2pr{9fRKCj>`+u^|{{!0>hlhVa*Oa1?abu+Nv_E{U?lWL)ut zktF>jdn_OBT2FUpYm#NK-m%v$j(qJJ&(wNa_gmpXXv^WPr_97^XL`r_qLhm&6EvJVbCr~LOZe&1^+?@eD{@?R)%duA|MQR2dxh@GS^|8p-iO(a(b?ML~}_1Aw4????C`%L*mV$;Wd1 zo`B4{a^8F$qU4_XXYdk&(lK!dq`^M$=pUUTAzKuMZ#6gE$yEZbe*D%PhBValwy6aN zKz@|%*77D87F`Peho(aXAT~9Bhv;_-HRC2T6mViNy5AcK*0v_^fmL$-t9G$KF8O|z zdQkHZTVd$lE5AN5x)PRnk?*a^h_>-7(Kp|$MFM7gx0aVg%1H~6Kyd}_v-R`LqA)L~ zQ1ui3xQrou3c-3j!0$6}fd(5B)Nxg$-Dur@k&ouF1>omQKZF@h(2XC=r)KSF)aQ9V zcz^!N_3w$Si*9Ti?it`QGEwAp~ z9$#_SW@ed}s#<#q1xSggRDZMO@Fmh-)B|XC*WY*rOor2>?%i+8UAngLSTxYfGSEid zzdN#dNm^VI`0BW_c?UcoMyqfpp{=!Ih5$1?UR>{+YcU=)tRjOMVZ|RJgiAag*3UbE zJ_=k_X4Fva`mS8byVF*?=7#(@8}UbEDoKp$0_+K2LwUP;Q_B6C`W`$$*! zV)(t&aOg7JNss>cWB80qj?_p}pX26pD+6 zdeR?ho}PFjpNpyxKDtw^%E@3o!wai+4eXMGizEl<7z4Wm6Sw<9ec%tw-cbUPqlB?O z-M=M#e8oi1Sz7EbY%&OR(^%yaYzDrO420ju7zlL>4iKD@&Exu)C%aq}@?V*sQ&W=P z+k=%$vZTU`8+R!$mE;e1tyrEydTlOJ0g7WXK&bFRc#Yi*E!GC9r77`8v(OZ2qJIqT zm$(y968z9%<&+~UN2$8Z*o)^yxbA#+s9-xk^UlaoR@8Y;6xvAk4&k%fZ* zopOwO^RG0vGl9-uGI?X=azYe!jBI5YXaKC)g?ut z{0p_%D)t!{fd`|F`?c5|_=;}$F_^fwlTS~IpIKbR6*BgNTCQkBA-pB5X9zzKfQp11M1+8o+hya6df-uTnVgrZ-Gf zsXByf4G?~BeqIO^RD_O%Feu;!Af6IX5XhEXfG%~h$%hmHV`;!xk{;a|r?D-kejJGI zX+7y8UCou)TSWyPj;q&d@U?;PQDR>as@+n{X9mC;SO zn=+0M8T>fsKf!x#?!EaJ7U$iYe-WwaBBHrm0((5>HEU|RUgBB1fj(=lh;zj({UWbi zTCC<*8xMhkRJY;!?y7KK!p%D^l;2y6PiArM`+$kalLCQVOTCd^0EN@1ZcNt;M9FM& zEwJ5h8LJ({cqx|->nUJXP>8^0I&g+s-F%(Yf!%RC0b&`5%uq)!?c&HFy;;#Vl z9_FnBeA1ANFBWj^YC{^C+#&E56I=aJet@@!V{t42qmk3Jev1{TBY&v*H#PZS@};e` zJ^6xz;l`$nNu~(CXSz?@(FjVRma&Wjm?-VY`EE{*7Vm`02&UY`a4=%HOZW;2g2uWa z)OVL9`(kOqS%^~?U4oV%E-}=j_qj43c0ivYsmRIQ@q#kC2ilclsJG%~=6X>4<17hN z8~7P03dNvX6qPAZcyKGKQ4J61=P;%xL!w`Mfj#s?9?6bw^oHjauA%hMRR@nOj(r>U z1rylEO7L>+F5st}#c`(?G^C-#DS^fzJ*9);3LU2_y3;+#eI;re&&FsI0zGkh6O9ZSr8xnub19+ZV}_IGfMxxS1rbcU;Qw2TA1kw6 z1p+0s$TgpS#<1v8y~J4OnoZLJ1Q}mXNU=HL^Y!T7obWk%;%;<>)BJASn+&XgI1>Iv z$+Q#mP0uY`xpF)aYX~j&XCh2(DR(YB^+pOG0AX0=qxse`Xw>M+Ie@&#;&yA%_^T0- z8kRWk&7?ev?op{ZbBX0=MhCwjo)I^!O+dw9Z7I9xajLa6q%^M?n^F90RF}xg|DNNi``3lDD~def9;ses zdU;IyN``@MLyinJUM`MAoJDNa6Zt^&3=yYZ$w^K z@fajK{%_`aH@iV4hM~+-x5kcyi=K zJ(1n}M|9W#b~}w+Z1XdwGr=A1{&(ahr287L8R;H3e?1zVQSz_woP)Q&%7*{PRi8qG z1o$6K`1kv4U44fXzJ0dt<1L7F+Cw694aRk@zQm-{A7`JBrhUBPQ|q&j?>;_~``Nlr zd+7T-9#SlRP&_EC~Q&(xpC<1lm@}wmlB-z@(&S zg|zw&W!U`)bLda-P0VAkPT|G1L-fQ+Gj?W!bAKA{WOhP}$pw%53R;N7#3C$0uy)z2 z7eEcX3al5m)2^9(LVd5)cKkUu{FAr+SRyWRU4bH=MC zf33rMqg^Nw@s#?zh0kPtVAosCy=W<`eBsNjLfx77y$2JMeCEwJqBY4$Ip+0Cc0dQR z+uU{E4m59Qk)DizQEm@wQ1-LeM*%FsM5GAZth*lF;tLv)BDAc;Ul;Di3je|i4ZFg& z?g}0Flo~g6uYW(yp9Nl2!iYo~z!#Qcy}TE{xE zM&D{Kv#b4h1BM$Ykh+f+N{kBN2ybz(Uk$ux`?a~*$?k0+e!}lYGW!9%E=i9cIt1Lg z9f79ju^s3OZgxp#--K_LOS~$83^Q8WTBRqu&ZU|l*?TZzwSw$o^$0R;ZT4k&@DH!Q zOuy>s*V6Rs0Q=Sa_Ik8G*>B64sD28nN7dipU8+vehs);jEzCIl(=ZTFr;W#8oQLZ@ zLItKEr+=LmJ4VEiDySqx??+2at#no43eXgeyK_hC;p$MqH#%2y|6+SNeDC6=@KdN8 z%8c(q$&${#vVt#kBdhb}$-X`wiTsWs z@ivGQ=k+PJn1w(_-j70U8|b$T>;8#IHv;mIb3L=zbV)yfn3Czl3Nnz6`SmL?M3Apg zM`TX@{PcLe1=D~2O{R6R(IwwJo?xRagw0Uz5~~Pt)T{W{>Dpin--*jODD|rW<&ziD z&iZxt9AxV+l)>CiLGusnS+sj7(fiKtvfa1GLg3*{dT%~;3qbz$pi4sUuXPYi68Cfu zOp%spu@6yYAUW6@O8y2bHe-u1usgIq=H;*NXk0|qsCm;LcR;4bq7J=Y7E0cpV}5@F z)tdkwLP~vRK9VxaZHv&m=tQ3ojBRzEaLq_p&Gt^@X1J=VEpF$eyE`E0$pcOhy7i9I}NM`Qip_2fOE=M82U@=yI^ zuz!(6^$(e&&|VUkZY;6>w%&PPW_3WNY4#!TWgBvW2DqJX9u>B(IY8nOrNV7`Jy?3t zY>J9-*5MFL_Y_KTqDf8KrfG~+0Ihp$0iRsj>klK+a z$ZcKqZ>n?E{{RR8^^g0c#riwC>aY4-4a-m0|F3(~_5TN{o%#!_qy_9fv=}q13<%Z9 zY+tGwZO!-JAeLawV9&pV8~c1OOG%wxwe{B|njg@AtT5J~G#v{apDOaT^-j;)>Ph16 z=H2KWG;=5yH(4LJJ*(20&61yJPshV+&%|_*sy)}W)E?+N-{X?=L+U%hHF&@zWGtge z{BTJ8AktWVB@mq)jY`j1@(v=%kjiyE(S}Qv?q=KTNc$0_2Zw?3vdq6xi~b6HTlTRmLgENGYcF1rKv(jDtmfCyE!?h{Im%>ob!&`_UV`PzM4MLO++Twvu% zF-m?8`6He?yQ2xxM)0@9g=k|iT9ai?A#4F0$iGG{(~aLCRR!jA&B+QaS|FNNqYs(< z!`Tm5Y2yc6UV)z8l_L`hKT7h)>QMb)T5XCIXp3M)p7|Z>FEjdLViT;m0M@@+6rw$P zg*_}S`kf5*H)5UzB{9t3= ziTE@thAaMwH3B&y2-_TiAg(`pU&SY;>Luo-2m*tnQQo6?&|KX$=a4VD@}Feq-^pQ8 z`P(xH*{C2{BJ&||#kSED8;T(zV+k6d>&DP|f;~pr%PvMzx?C`(w92d+#zG;tl~%$G z@kzKJ1}QGql>uTwwI1ED?Wax7+-8kh{B{yKbX{5~F>xMXu1D*(q1;)b6N{tZP@jn} zkDII~OXneYcADdd61TrB7+F^EBlg72|NROp5;Ov0QtV|8rLhb#dLl9#D}`Cu)Y6TQ zkuGEmonwx~2!#?u=O`q6n$1s?&7+_iW0lxildKb8F72Gyr35>2`>*O+JusKc>_@#0*7u6aw&UJ74$cbdyzeiUx?wa|10qU?RnWM z6Xd8ssgPlZ&I=io;CMBXfEDv8ut%F) z)oztNUk2flMAE{pESS)9tWBujUKh1o-bRelVU%|>k{__VZim;t!3S&Kpu=llZDx6( zKM_ANHNjMh)eiP?(FS_MYYxY^c5rShynr^%8{dP7lYDEHB-mn}|B8}}Kj9M`#HE&@ z?~Yvh?zrqqxAf!4n}MrB$+;|s)4>N67xsa~chZ^_e8u#?Kv5qP^lo$If20mEk1+pm z%^qP4SldEYRB8d?54#5EWy`Wy*Gg(#yvC{TVDVT_Qho^-GCzGF%S6>G0P1^84`>!x zy8AGNf)xQovFKPcGGR|6HpY(iM6Sp3Zc;%_YwRFEe)B}S&&xt8rJTOo40$(o7@TL+ znE3*;!{$1Fpq=SQ-G1SSYzyC$JN!!TbkpcNRpcVl$X{ zG0Q}H=uxo#EybE*dzpWm7R|?;%sWVZ$N;s-AaBsWOq+sM9}KMTp&Yba|Flhoxjn9*{OjarK`Ne^cdA=0e|wqX7gd54SQtD98YZ zgwZF^#vs5F!oZ+PZ8|-5rSMRTam6a`x@4UgG}c?AP*%EpsTqTy`VVxO)D?XX-|E6F zs|sZhAn_mBRpUeHj8w)m#;M9ASc|tsYB8g*e%$A(shj{gJ(XCCeS%m?f{4j)N@nX5 zR3Nlrtw*ex4F-y)%HUaT5l6lakU%?TUANj}@7nR1ojSM3>yElIR z4T1zY>zU*X{L4)CfSoL@kg)#L&$a(dx}SC($Pct%mUvZYKbRPl6YMi6SBrK=ZrFZn z3qg_eROZ7i(cDE#_ZTLd+H~%;ieO8<40U8}faqn3LAX8hv5*-#U+@a6a3-*JJK- zN@)j&60MVvL-1RlC%Gm6GA%}>S81#;003}ktna=lt zCI^t2uSkEx373k{mhc;BN4%@xDHVRM3ai7N9eG8Er!>{s)F3(hD`*VLu|5Ee2%V?W z`5W%MmKIt9CVcb&bYv_op4dTSv{+xXC0(C%$?A?Ut^**-8N0#GR2H6YPX$>#*y4N% ze~Nt|@sn~m`1KFD@|RnEkl0Ly;3o}L#`htiY$3|m7JkuMWG+1qeJSnl`ji^V6z1Nim0YsD(_StqFn^dAL84$`YQlUXn-ivvolL#YKoCu?5RH$M z5_c_#AX;oR0t>(8{5-y=Hn)0L$sli@7AKE|@fl{?ft+b&8w9fGH(t(VoW~qm`2Yz+ z-o-&&?t<{~M_buMbMkXeT>iy(w#WUqyGkze=7qn7qBexp2%K+F1zpc7pzsNL-`BnG z>L8J5SLU7T)BD_pmOc1e40NC-D__7PJ*q9qhKL5G?uD-Kax++4(pAF2nj-MEUBNU9 zDUIj};{MhSivs906XUKB=in4&Q61+fV72BuMuGbC3Rt7hsc2uw~w zl`LK6W;-BDwLQeU6s(KU^fwUT5wg1kt?fI;S2=V3kVE$e&98+2-hv-s5&*c}~oT zZp@0-w8BBsG}J_y;pF8Qx$q-gDwTW+5=ntv>^U+^codCr_?gpGfds5pehFBhkno~( z0cvR6O&Ed#q$c`!N+$$vVhr60&qBOerxSF9+K^mp5<*<`82qm@NiKmwh;?J?Q|8Pb z_*CP|ERH*tpqkT$%c8-+WsUO4kRwwY9r_8@xyT6)ENM4Z@NPx-VlsBfZyV77< z!d9SFpDC&9b`&A}DlH!`Rxv{mla?o>omZfF($1$)2rX=UAS!fk(v8l5%UD|)aMGp+ ziAQZJ^+ICwB%hO-tB&{v5La|%mh}mq>H1RgDkdm2q*4l&&9NAYAe2(_JeCx8XJ)(V zrI_IKdfacptFlxRxSG(W$4Sv?B-LWW5MWVsPSJ6?eCv6HtK5^WcXCNrqywiSR+xfy zEYmp+D|Y}Sc-RF|PFB(EY@{&UI#sa1Y4X!js`VAAd~bQn+hRDAV*NKKff0cIswWtB-4JiK75UYvY`ygVauAztrAt7Olz-`ff%#jKfB*e6 z<=@9pCh6{{_N6pMYAg@fX4C(xRm^@idp5pX?OK`@Zd>C$ol69xcFHD-N>dv78x98MoqHCb zl>9@h(ItjAwkC23&X;;|MOC=OsGjnLr%z501=4>++&igQvBrY2|_C z2jn%~SQ%Rr?lJ8b*c-)+&ldDwa(v9!VVDUH%I zIRdaTFc4|lbCN$DfsvC_`VVbxR(oa*XBQW33x9_no=&)|v0~4}@xJ1s^R=fIp9;Nh zYDX2vtYy2q{$FYNg<3@VVM| ztMFj{EZ7_}^7DhyecJdEZq!Bhc_SBSb4xwb+Hy;d8)wES-+-E|Gcgrm3QQ^VBH;Te z!@**hCwvwDp#H>4vD(;JxW*iE_C73ExU~k&1Ga>?z}SF&N^@^9#R)08{ux+pTr(4k zoUb2+d*^MJC|)0c_VlBat;YB0elyK((D=&yL%Y3=i;hAku$sy-pYfA(VN0mSoYfzN zefcQ>MXf8`KP)p}n@Oea1!w{#noXZknBEjX#O5cPX?u@C$8f;LUMnmyueO_oeIe_S z#hDY1*e47%cA8;SXKaBDN3C_&$1eSsEq_8E6H5L*M`>ZW815RX}FDXdRXw}mCZYg?! z9V-sR!!YKUf_)%J*0+oZ#NvTLXe|ukwwSMBOc-32I48e8X3p)4&tyOC5unoJ54(p* z;D2cNwEEBcT=+EPBGw@Iqyrx2BN){*KHc<)8=oG-;M@50gdD@mAe5%~)DDeR)Vs_Y zQsr(e_zfeR{lQRje2%Thvk_*v3t38+2NMBbd=0UsD$<_^7;S7xcDdht7S2E*qAgF+ zD^6Hb6v3_;vBmAILB@%84#r33KiBarz<%P)hK~ei;E$SEmYe`3Sm|u@#m7O`wWU=Y zTTx{X#LxmTrE_3hhSLlfKWwSZ)<^L;9=7_D10Y^FPErbhI$vOqaz{&UF?PW9ia18m z3*MpR7{3wqhT!rqfhus`M}skBm3UT3mcFbfxuqAG7EE@GqGBvVhJ}o>*}n=IpKz-Y zI^$WoF|iU&Yb?k5v2-5y9kDhpgZEF^eFBq8=a^%G(pY@V#v)`6&`ef5wdQYolMra7 zHOIV+NROOMb@lZ@|7tDv0y+-%gcZje2M*@zz?C3e#dB{n*(;tUNvffFK9=lH_Gg0y zRqzo5uP{rS_Z@-g`zGZRWFPIp+rSM(-v-!K+?KELjG%XQW27<|{dya%JQAg|g2{02 zePxDq6*gL@HI`$uyI1~jsG+Vw6Xub6o?H1NR9K)ig%%tnp+BOA!8j`EmHtMYOmJmy zjs*)n40`#lwVVH*-YZ2fxqzBE>O(iZ^saQ%%gw+tn_h0?iPy}Dr769949?`x%LOn^ zPEVE&y@3Dj_#nl9_uwdJil0b-Qrhs4GSbMj(;?4YGogJD%gE>i(-Y~X$lUy5<<2He z)8ZC~7W1Bq<2rK5_W|Pr^L8|&MDg6G-&Z{M-`H@#6m<{KaE#B~2UOw4AQgGb%>C!v zxVFsvlKh4YHzC-w)%-Q^5Q}j#$N}Pgs1Uho&BW_FfosdKyoS2!2X7b`RB(k)rZ}5Z zAQU@&dYro~+Y>n_XmrdM9TngN^R9>XX5*pf)^8HI21fDIN^|R1D>(yeHqosGjS(Jj z@sn~r^;PB#Y*VsdPe|S7z$AqX;ZKJ+KTr9io1eBuS8aaUfMU&o1W#&y?mFF#AA^BA zHhzR)NO=(ac;mt}ew_YROZZ{iTLgxd@$@dXXSpSk$xEByK#^?NZZf~!PaMc#qhAX< zY|cj2dLv-KWLn7kov_J7DR@7$S1_wD5l#t4Sy7I2Q+arqHyr%^ z8a+AmC3A##7X~LQU-mj({IHh%a3olo%*Uc0)L|fxp%r2L>{E0o(Ry~nKvE3?c^q0W z+66iGJv(ia1jy}e^@3%3YZzGi7l%(3JN_-^t}~bur~6?|qxZ9vfWWboP1sTpUsAhkY!fgT`__aXG48IljG0>N$TOb<5j9(($Kx zY#OHSmwy7<-kk>(v(l$ae^J3=bXQc{>@yDaa|xn){pPvNdflXbA;=}6L}z*yDA>+6 zgwEDz^pqKg>33ahN~9bjbx)Uj?YM5AC>j!G8>(3*fDnqFo^|K?8ajJy7F4qfyl}liAzKRg-%Au3P`dgJ|$9kqGz#_0MkZRG4 z@3E0@{`nNto&u0PKHE#Rg;f|M>-HY*(X;t$Mt_6U<+Rl0^HCuHXLorj>OzDc*aPRum-a{pI-2;p6vJr<}4PZ``-?q zt`~qz*3(+oT>1pdby2jVUp2=0l#!zlyW4ayH9kOu0Em_3%%ZI#SUR}zHItr^0_W(_ zfD~Q(aC4bW*Z+jZTIhN_w!+O*PIBX3s=lECoLKq^@)jVy{#-L4x!a@Y8(z(}34;MA z0vrevF8K3deid7} zAcP*MHO*~O{KSFhA(j6I&@Wy7xvKmtRr&88f8^y~ol*Wu7%-##JN_)~zy1H9{5`IeYWpYbgN*$ZIxgKIEc)MG__W8?c{hG`ljw=+{`f&!q5ySD2?r+Gg zX@%{$&$TEC9?th@S6cvq;Ro-eB4}bUk3bS?EFYK4br1Hrs^_?Kr?m=32X%+P48$Up+;HUoeB33X2sIGs|NhQ@qt^Na z@m`y|%~Af2LCKNjff{U8UW;{0%GOZnQ&ZbGtbe^zdi}p0{*`EpZUCN)u5y}Zo$Bas zn!!W3CXT@q7JUX|zj7ylQIg<+y#O0B*a8Dc`=8)$g2<7}1VY~sJjpI`B*FILQr$ZQ z{W1wAduJRzJXj2!9bms6-cfj-5PShnN+!PoojQOusOPvVN(@M4P|3prIA{#&uKPb6 zcN0bcX8*cDk1F)H#GPdrOpJR5t*ifs-QQ+lvcu8361p?R_z|9A9A`w`XoDJu5AXP^ zns1$kme)Tn{yTIu*rMw@yBxxcA{C6yU?CC0YK_`-nDe#n20jEuPHPyF$zfWxo?!Zv zeXAawoCD1{zbvNfs2UWdkkyVO22$%_$T)C63N!zFljD!82r9x1^_D!9E09wWhb zq=GxB;IR^XZ7R5<3Z5XrC8^*}5*)7phDyv?sTg{B)Q$KwCnhHq<5e+YO+rxaI?aJX zD-|=#$@9NdOluW0+lg76ipf$j+*X$s%t^&$tC*LanCYpQHY#SG6BACw9Is-koS17; zF(;^)EW zS(u7BRmC(oG0&!APE#@BCXBFtAQf}EilL`Mf!o+rOumZAabm7Y#hjsHa-EpIshFcw zOr8^SS}I0UF?16m_4raTIVy(EJ|$*Lo&$rRWW0nn4(lnu8JA!#GLHJ@EZ6<$Enx} zh&8hqV?-;M5<3=LE6e<2-Z}*eo@A%Iqu_uc+4Wc)R3^s~=4|N2h-t?dL)Y=@Rjv#bnWGk9r-0*Inv$EM9l2*9mz2US6RGHp;Pa*>&+dtYcaQQ774Hql(N!WOqdF zhMa>)q10rj)heo653@G05V&L=dH>~xfh6O2lB zRVkTI=WzQu(teJypJVOk1of<_;1hp(ZN)V8#@EX9>#X$a?DXrL z^y|y%*LmsJs`Tsf^y{kh>$>!7UHa8bzivyvHl$xY03iR+NMefpnv;Gd9@wFI=~s@j z9m+wqUpa>MD~HT})zh!#>DS@u*OBR0;)|VaZ2FbhV24)NuQe6Z_y%s0W3AmKTLLBO zJb8=q*SM3xpdf$QK1UsYCbqn$PB**6uY8yI#p4Z1{5t9_Tl`uyLU7mRB(|wB_sG+ z6>GJ^Z4;BvEXMsQyhJls4ZBe(wKQqM$CmOh9qa+m=SZ4ikZ!r z!5J|{Dux7U_Q;6ws~FO!c}zyk#VUs6YVOH(_x>^!Q^lC|88Mfu7!tF&EF-2|#gMkm z7cye5P%$KR^P!BGU#XZn#*EL18KPpy2+R=~F;}V>@&$8XM$AwZLpEU+WW-#hVmzQd zQ_F}Mreb`I*?FuR2E$bhd5ZaIM$FYJhAhUc&WIVHV)7XCd`8Sb71NzD$&8p16+?bx zj?0KCRWW2w=G7T7gH%j0WBO;r3|28ZW6sEk(Nzq2nb{#DCa7Y_+RX2M;fBGbDrO{O zKFWysrHUEDm<1UzAr(VDXg-q>Q>J3b7EJ>&RB2(o8!{>?5QHfb&KH#YRX<+jFTJhE zUlX%AVQ0u+7c)l5Uwzao<*$%>9f{XJNgkc@*T2*&<*)y$SIS>~C9NZHeIgOlNT%sH zk|w?`Q7K7|_$pRkv(?uC^+mEnT#5Q3so`so`XYJZOZs9=t5RPNtNKVrh%drR2mv!Mp~dV4aE_s3P-hIq|F4kj0or(m+lHqjI;L*jg1$3PFM%^3_EC zDpqYFK_JOB>Wf5xuiMlY5g%Xot1luuzV_jG6Y3^{<7>bAB2weaqpBq$y5}h+6o{k*`XfUy!N!u#qc z{<`XNk-vsL`v1#c08nj3oxnz3$#(gNP$Com@Jh7dA6`9xsr^b!v0sTM_A7D3e$7k2 z5)Y3>UMkV@@^?RfWPn&)}_tL<9-9S6?dm>G0lw~J#xc~ER(DLc5%tm*&+As}{R zx7wv&1Ux?YlMOF-`bw4FTuxI$=9|$gvo>2kR#u8b^yCew=M?7{ObrguvsATm?xH*$xe;%LnDdqb0s3wX-@g*pEAugobz>b zzJtT-YzX&gKM0J z+QegP9rj?~6Be+3M}Q7+!38y5d^K2Eidq|n^SUfAeXQL~cE+5KrsctJN#PpI%7ET2 zQZ`(^sIqCsr;3)z4K_SZ$yh_El3`@WeMj2dy9*zA>254F`sRn=cw?f^fZkUqWOj4!;$7UyUM zbi~>b1K^^!-|Gm+$s+4AgjXs&NP~Hs2VfrGd0(SNle5v4a>Pgm3mdDAtK^uMx#{SX zMNfTK8&}dD`?xTpV{hA^cE{f0C+t-RUZ@s(K(qQfmQlac;WzLXxL{qj<1g^H|Hy9c zFYrmko2v$;`~{XG5dcAdfn6VZ7VpX4=QAkTug?##6ltSzSo__~T#pYH$R6Y5Nb?$} zenD#+rF|yPKZfYlWxcqCfJYZ|aB@Ef$DGMZ)u7N)1a2_JegIj&OPo)i7l~i_%;Qz! zx6+BRc1`VRv*(%VVZh146F;5a@kdBM7PORVW__)Lio!!3#(>=n8cP7H+sekfS#%+f z&wOehA8*U!<519-xQxfO_}3so05sQSNXIjol}*zvu+yb6(6UOf2kG$lAdGBy4#>-8dAe|FO`a{U3^PC&D=m46l1U8IkU^x-Q?V{oJ}4biG!?jzlc8ygy}*M!r9PXjNovjv#KJt&WTEtWHI zXcx~{A1$Y>tXx!p0>iJQE0HYqPp9!Cz5i&~=izw%BONapEZm7lkh5O*exVlYhX?^4 zfM`vUk5bM_+<;wdRiG&Ic+du0p*3j*nhZ3cF*uL5C@Pl|V5iK)q~MG%!;cgn=iPj8 zeEi}N@llMSMZ@TrP+Kb^hlG#6Lk;%1%?xz3;cDX}E`G%5!|@St62~HORtrYcbWr=Y z*bn%(11~>R{tE;zlN}!tub*Yg03U9XwELH%ghXfJ<-aoVON%!mN{%2sh?f>{Qn@|i zIH!&up`7rIf|r%Z(;P3qX^SIQ|7-y-_l&!PCbiYraxIfB*B2b(99Lii!pg?b3tkaJ zo=I1#@buvfehX< zAlc);?`Fea9Gu-hfXuXN2P6PE2pnclb`kRo1StU8zcn&|Uzoj8K8WsSh44Y7W)qAd zs_$_ZO2n07xwLt}j8UrWq93*CGTN{F&xHSlZumdXS~YWW0`UK{z`wcuv~>fTQ|TI) zT2pH;VnQAK3W+~hTfQF(q2g5_AB_>4qY(^9d7g4rqy!i>b9ze{)&DVIRB0288lJ+a zS?6(i6GPw0SzrYKi1ZS#OpyziXhdUOir)g?Mq;huiF9-K_gRH+GcZ8)vrN5R+8)N$ zzmFXGMy%zMy;twHFR9_JL#Gt--k~srOxRc+ivn;YTDjB1D1hI%=?NF>D(?}I&O!gS zJ4JKG-Iy{&M~65apZogE-fic44WDQy89-)-w3Fcw33A4K#W?HCPTEaTVMH@R_~ zVQEo@1fAvq86n)GFrX9c?Oe>zZsO4hK`d{PY z;M#hlI=HqDZ|&9w*IGbIWL>+FZtmXJ*8(`(g1f)RVObO!++Bw=BX^uog0Hok;52<) zHokG`xPh)v0apyf8`P-Y)%YknLv6^9f4D-ZDsn;)ogRd0f^6X`K*7Zy5bEDH{@Woz zZNWM{`h9=gN%l*GX!or_>x2G1>OM_YR)GMV(_7&J0*XWP(HQBwf&j{|e=3a+qm~2& z5XQ#=)5_BDQX(bSr)|!OI7kCY}s}dl5!_bXR93X*S3#2~`?*4*Ak1qK?yrXRd zlT+=ujhZ05Y*LRe)Z#7fWY>|%Rez? z14k=#1NURVl}fasv%2>xP%+<2y(DJbF4e(AFL)5hMmhw0Bsj7F%oW!z*W#?8HnX}o z_Fi})UB$nF)2ba?Mfdo|TgGw}$VUqB`6@ zQ1OQ9aob?@F5GxiL~&6n6Kb2MYRjqU*M&7`@y!VojbqejPZ1HNb;T;c z&AOBPcStdWP%ZWzQUrimpcWX5;&f@WZfu~Up{EwR2Ml4nAMbgQQK3ZT9OOin@~|Hl zCe>+uJ8DxOVd=@P$73Afg;5`sbsr?IOB(AG->+yWj$De2l0*_;Z;V`hSK~G>J zDn`FLyX?8NsU;|`cvsCxEq*cHN(Up2cJEfE?8vwhF6Q?v}MLmYuSho{VH_=VdX`FW^8B`XD&Imy4w_bH4=_ zxuIl_vCzm6y$G+ z)=dW2L0|QDi;H886TU)c#~&TtR1B8~wN>mSq7fI}GB&!uvo<4!V)aqG$`T!S2X;+W z=-dIcNway7eiuyDcP!8LRP^l$^c;r7Wr=n{?`q?7_#~|7eaf);^+LFh*0xjaiSF;O z%{Uhclh=DuFub+w$O-!QX;bOAmKPNrjX3x+)kl4bdH#Pn`jn-FPI_5v&s)(g<PQ~6X+SFT+LYuoLII3hO{6-P%HXgDogC#rQS0lM9_MWay<+>{RO`D3n zCuvj9aWZH9G34Ebyg=T;IP8CK+t!u|e!wzXBMrfk9M)PNwFHKcOLM?#|AkVJcrtFB z{K#5>q|)4vrlPq+8j}Oz1gBOXwFZ}~CqnO|wPA|fVZ0e=yahc9(xBPHQ0(QckP6;x zl8+-ZF14W3jQ6Cu;$A|^oy>9v?iaA>vs7d+yM3b&hxVaDtG69gF3B4+l4m0>G|EPW zY-<;!z!C>DvgJ~ukhaK37` zC6Fh&0gUWEyrgLygO-mM<5=^Gv=pn-;;#Y)!LF7p zVP!d}Yz{IAH?VF-P4tL?+ds9Zsxf>ktSenXPpF^(gS<>-UC7t9U-0#8dA0bOC$D?> z+Lf<`lD0ix?ZO{bCG7@kIYCdVps!p(e^EiJ8FZ?YQA1qB$uc19pY+|3!m6!T7+WB* zb5tz-3DiQsg-mVDWRUifL>o-mPF^SR)o#m7Rm-giDoqH!j%4g^9JH#nhRQ2M{aWi% zyxMhFH}nq{RtF2Jt===KR#Mc(C0HUP%X$$yd;|m7Y+O?;#v2UwLUa9uF)Vz`3 zzDQRTNy7OEpVm>)t&LVY{$$~Yy0M_KKQ(1?gAcm-w)lT}4t|E^;RpAW!DyUsHy7jQ z_vKn=+$xB-@gwmwd92nslwXdwsTG*K@SH(0O6z=O{z$F!b@^iv^4M$@^A}FmTIXl; zE9Cj2T!Wx>el>r#e9zBU@v;0W`F=2el{_EKuaoB!`P=Z+6H}}3K(Wu;&zG1C->=u< zscUc6UBWuBRe-u)%;#Pw>&U}%T>Fao`KRE;gL`~n{yqU*e-AiKz!AS8RNk+IO050! zg0+K+kk*r^C`KN7thk=bsD%VV8MeC?e-OFK=$!zig<8+zc5D4Lm>(N(J?qm*DiNenJu#1iV$Xjq zjK9rKs0b;>#m0pz%-z^74Ps4{%;Q|A;^{;qq904B&z!_ln4;uGN{FJ4Hr%UpY zb@bu4|Dl~=e5_qMVdD>cI-^nbd8zpb2Tvr8w=R|99HcwXTi7T+-mWnz4aQf2yTVv1sjLoj15$)8}oiz&`p$)vSYNLKt4 zA-gB`DWT8A{RD72M#tv8cLBY)qrFqMv!NCg&oxf_f{90k*7Zb!Rs^^pynnFuqv}Q! z|LSpNbcLH7<~3@f8>_I;DGr~E--_@U{KBt6-}AJoWoQME@qQ)hMeF**aY%F|%Yv5; z?zu!+aLB*)jz{RdB*S${vB#)Jj_4QtEB16AKT5|fp2$^O(i*)0J-&_oMQC}qvS0hdC)i{AgtrL8H3ehYDrOx<=TX=DU{<%mJ_18 zEL(0~^BqgAg|gb|tK2&&qLc%PXl<)BC@lKx(q4z8m5y%hie8QAL`H13EA|z{@*?8c z)os{@hO)#WBETC3Xg`}_e9pDL5oI7dOTD?CkWV|!dT6$gCS=UB)4&gmbsdrs7zgTq zw6XK}qvdM!WL6)*6(S+3$YHks$KJnyMOCeT;P_S`F|EPUGP~^x3saEcCYUA&V>2}< z36_;M4luwZFfUf{VgvKs)P6coiMK39f=n;&Ie6Yj7GQMdoYj?Yd6E-VM9fKI~<=AK%<-0xDK2HvQ~Z>J~^uAu%B zq@e11w{UT$q#jGoM;1hX`(+qMfvDM>kFpk&$Pa5 zgc5qX^~>O1&D1q2XOA2Y{O+N^OgGZqgMoZPj(lt6Dn#QH|9*s1=sz@C zxxYUW*01{|vLz>(wc$QoGQ4R(uce13XZN;{k?ngs6Axg`@@u2=yLrWtQS)!c27tsfPq?J4gljg=7t>|YA&3s6L zn;i_EH)Cm*SHI3X26IY1CGW~qnpoV3(;s+WO%A*gA#yz~s1xkX3qz4E(lBI`!E+}D zVmrN?TK~XI{OsNqkRE{zWo^g?W)o*eXVyy*n)~lXO=Z0gjn5PL{Q3cvy0MM*KSp=c zG{oZrM87`eUe|S$-RdTLJ(>~BO71rF#spmxwbKj}C;&koSmpNXROy!?*rukt@gG{r z`Sr(V;8_h=p!zj}abKfRgwBo`9o$;pn!@naAD^gcd;($WkLTh2ZuXI{X?&J_jMX$v zKnDs`6b<)uURHnH=-Pr^I1CNc{s$INVw$(|k$`JA(?bR!Mi!lp_p#d#(W8b7vnEH-;<=YJtn=g|nikD2#?g5!=BegD0ye@x19lMR}tBy;FJVdw;{^Wm6Mn zuLqci+e$~J>peGt#hS*oq@cM!_DzII@&;ZLpNVrmJGrrP+tfs(sUE&*aN+GJZDoWS z{43i==>75+$^cykOjCQHpK7Qt$sZpxdQC6c_e!eXzU&s#)>XF1tg znlg!X3%;HQdO1S?ODVlW%@lb;ZX*U<=jK>_sD}sEj-q8k38p|3Gp-+V~G_5iRxpK@U+o>PP3p3NrH%gI;%tZfoLeNxe2T1y)8W zZTDVuZ&WAo2MToQde}2zVZw;-bn?K@`y1V0;(nHqc<%STz5L8ymRs9yeBU<8^K<<;Dx#Sj3H|xiOy`4{_rGZrsO>S=^}RMim*pXw)-R z7f08>&zY2jya5Pc*QnmRmLYmIlfh{VZpWXAU_o6G^9rt^9`muPw_;h!Mz=A zKOWFw57A588jkkTG(LsE4M(G0P8~Hw3t;1^dh36qBpYLE35y$7AnlH9nALyaS(?UM zkV#s}e|Sy>L;=f9{Xb}9x58vPF@G7c9XJHnxmf5 z9gBT|a%8;^guRiFAQx;|W=QLIzVm@+9^We|&(^e0Vhqh!_=sy+@TT}c#!tOx3JG>( zEG)jr;?X+UUhgHou6RuQ{uz(Oa^kU} zxnLt^f#v!`Yq3$B%~*>)=ND|ot{>F{s1HtO%r>|eFlO^x)ln~k^D#)B59h^Mw3a2v zrI6!3s_uW#ZK1o2YyE+@KWsl8_BgSnmu5<=s13M}D2v1>{=gdXfbop3B+xaD(FJu2 zd(2hUgYEamd^rg-+iwztKT*by^C3WltHu*MCv%w|Yt*BVLIeeN_v0z}KpcAlI$1nsB6Jk z_L>&$+d~Jh&b_=p4(X9-sOg~&#BWCvjx9K>70)-|@_D+x?cP1LC^Nd0*TWYbuq4Cg zwqC`D(fuGF9uQgdLle%&2i^w{@;3ZUxHhbgO#~9?oUFaTYoBXdFH`iKmYj_uMP=H3 z!#dD@^gQRaxX=56`B4W}wyeWk}>DNRE5p2a;?!0CjbM{Q*&P}_xvu7W3 zUQh*RO}}UBx$gpjo6oYv<9W*Jt@2SQjQ~(E>Uh)U5G#PdATxj%y6f7v#Ek;W!UF zz2WaS&0VO0i`4`4Na`$CI`@aBLqi6H7rZ@rk}eM}-P&$! zw(P>wyI$NSybn+I9^H%rucu(F1&lAH7Es?je`6kEw>8njjjX4Mio=)SI;VbA1X_Tz zC>V7#3n&qGhu1u&Pq!}g_a`VHCKUSS1;i4ppT$rz=<(pwLUn@ARN#0;6JeJL+-pS)OkCVG5TUE& z(DQicrbO5^{XEN=v8G=3UBnVuu$H^dlU+q-eRdl&mh582vVF{0!3%y3FZd=Zcp^TN zd3s0yywjjkP{-6X12%Agc0cspP8?W44AeKTA&l5U>BUXQG^jZTJ%KX{t6B7B^Q3Lz z3Qpf#ff~EzHc8cA1nT1oLdX_dSdFBsios{nNLd9uN{R+mz;PHIgIc*DekH;wS1=b) zwqQO>`O3vXwHQ?CZQN~%oXKUBI|!TsPlOLd*cF-Fe@z}tloSQmdlt~t9(`P0tPA#W>wk@vX!?t7*vo50-+2OlvIkT>yjoOs%T4r672`e=Z z%f^e+ejH`&*E4yW1Q{k+j*B4PngpbPT z08J#GX&JHpS1BJV6QgIvHc(B^IV@v^#mi`KesBRTK{rCYqae8M8=qkdNaiwz!3Ysl zJaRD*mokwaOu2NFa$v4@_uncNSD`lzGbF60 zyi~}pHz&<)H(l`Eydby~069YS5px%SD4Ch%xg$pryXA~N}gG>D7gAIAVdKW3VY%%+D+5QD36)J0U z(LtWEtg-PK*T1mg6P+@;ml)om1or2v`*PCT)%`i!2Ljcfa>jdbWClI>v(q4jbYPS# zMZ^PdB(h8K<)O?13}w`cJ|E>^*ts)H`kMMY(k{5xU(dS(ng*ToJ!PJri06Z!)dMCY z#j{-Jp&vVnG!as0eU{~m^;7ewDg6{t)YfzjLi2tq9batQ$8-MO7`>84lKPDC(KS6U!dU^ozMFNcv1qfwjiv2u zkFj@;tVufeMz*lcPde(|DX}>j>t>a-&gT7puE!Y}orB{gec9t0m?A>k6B$cKZ>4Q) z>{SFmaLC{@X!QS{;7Qq{$5sFYq07gc)1+-pYwMEeZp-HLJ;r$Kg}4A`Y_l>xnSH6T z=o5X>I*+vnR+=LWo=YNgGJc=+u=|tNRnU8(3gePAC3AuM!TEUE4sFdp#$)HBu?=01 zw=LKvdyGg>mio_Hu>rA`o+1KY+~4Z3ZJtis(vvt|+KT{Rhc}1bxXpqxZYbJ`@$GEP z^45cM=w=GOhhD0S$#QR}{aO@@jz6;ZS90Km*xr8vOVf9Az6d2Y!0vfCHetZ+z3#6u zFP-PS8Al~(aKtxO|K&~;uxG$+AJc4T(8lUaq<;a9MzDR){oThJeyFNHMth&P;<9?$ zpDP-0sG7z~de2z(W@`*^xEw%mlprksnzspG&(?p)`D5rKTmPmnBKOGF$CZ3*-sb_E zINz|4AGALjlgn&_bguhfS(wo`unh#p-kc<1X-vYL4%XwiE1RzhBi0WAovK-<6$q#eXTOw1k8RDj=3ylSEDC8GRJ)qPr&55HyScN zbQWU_>ZQ4LRMXKcGmW@wMsRPWrH=cM6iz3M68U?Xnz_%xVN{~al8Y@xFby<{?1II&TCjQBKBD=UG|x##j*>8tAQAPkuBe+DQlT-VG%+v;L&y zoay%RwUnj{kUiuPvrd=ei~566d77przz5E>J_dQCd4IyM=W^-ar_bj7x8Clb_s?o; znWkx41BccPgeOz;)>kEidmUZ3JEHftk}Nlf_R*06yjVaRP9ZFOyh@ph_FWAhSstk@ zU_;A~4@j?kR*%hk`3tJ`2j4^_<;%st;PR&S-#>wfhG|a>cFKsHJ4Z)JPABdAsj5E? z?%s=RRom*1C%SIO@G3@5-*XEZQS5O0#Y_8IHE-V|5)Y}Hqz%v|>-I(p6mrv zxudxxmO2{H8$+#jpuHxczXEq3x4yvfzXUEVybWAx<#wbT-TD`w8&3ZIi~6H>y=QhL zD*ItJ9gS@0i^fIjhqQQ4P{N)`(VDlA9ZloQ$OQI4QTvE2ZGufRcM<;AA4}9U-H#6g zNsX#LyMOSOktPh;)O%(!lrJ%q`5dM5#sE5sUu^*@S0Y10{StA(do^L8_gll~9^vYb z<+*N!41KE9TiC~H+kCT7v?L$Iho>e7_&|0y*+1rdq4c8J6%s0Y>%Be;!uY1)pNyC4 zx4i$A?h28)6{i$BvB(Nd!M$GdmRBiwJsdQ1KSGT|;cJ>+#)q-l7Q^`8j7C$UM&d=} zo$|}-FqRTw&1xGbVq-hiN2BK^tOu{l#re8MI){yCcIkoLXzvtohV70YB|))3vW>3* zyN(!Bz<+~&%dT=%DV_owMLSWD(cLcA%!o1$(O}AU{}fY#N4`@{)h+cBOoeM><=5TW(=T zgQ0Jrb3}=*h8@|{X@94ejYOgqm5I?sG~x1-9T?GpqyQmJH3$VV&?eR4YgC@|J)a9` z{OO>9S`J!)(>DPviR@1DGakWOf93K5#$ppJF}Cq#gbHOv&I1J~EE5t(hl5Yw0fs7* z$v0~?wQcWH%gjm@X?c?omK7}ChID`G`(6$Y!HM?KISiQ&1YJY%d~d)_yOv$p;9D^X zLc1LQpuei+^-Gf1gJ`6JM?3eLUYUf7CywAzVPJe}g3OeVwFdbGhG)2(3G)okRPtVe6V<+dny<^8Xp|Dp1p9R>%^k;`@9 zbE*Th3xLUgO&IwvBKiN^H=Ve%Y9i!+1^ru&f2t0fkLR;))w_?;eOYXakJ5%uZk`yY zZ_dJsRSz6~#3QO5*etB0B5IH>(#*Yz6_%{Hp@Y@Dee=K)YPjhR8ZeuwcAx9C-{H}g zuc#!m*if35<30U9gr;`|EBAXl@_u#}^KCB;c)~=J^C2|Jbb735RC6Nkxp953$Md4b z=03(An{W=>4v8v@)Au-R$nY68V}A#<ZmPCWlvK<_XQzMt-iaNin7w_d=$%jnL;Kx;A<>hz+~ooIBIMd#9?x1GpF zZbWtrw%23j6VD;v$=I=BGV6v_!V+k&AxD#0qptELxzsn`Eb6Zt;4&lS=DXuGjn4qL z-aVk096SS38AYkTJWRIN>yeC{FMZ3{ZNyEO+nMQ}h#}qjM4an({fLNL5Cf&5Z;48K zgtmb&q%Xp;B|DDU6kXeQ6C|Sq;4fB<_5+43j4?&*>1$&7R##Q+w)CQh6R?&FFkLGNyB=KcgXrb%ER3vN1~Nub}r9UW-eyeIY{ z+FO&b(MQ7C?>!OMX&QYj7`3D9(yv*uA}O{aT)|cx^N3{r=TifV8h}?SaH1ofY>D1A zY}zbaM3bv&>5H=9!}2bE~Y&T@}#fNJOgpkS=!%k=S}R^_W8^ z?3K{UQe*3#2twHqhwlIM;f)sP0cg8=fOpW_=*$DfI5r+_*4_YrjF>}?_RYc20vez5 z4d1u$=kn!s=(?S$lBor*!0reO%M8$6NG`p>*ZPwaX_ z5pGuN@rOR61Dn3VVWTG#r)hK6Eqm@^4Lg2kKfr|KhHgkr5-SabjUr3K)mc# ze+;_Pm#}H(zKj2C`w5M@o1TUZ^BTlU5qr_JZ5ZN7e?T^gEc8mEp$A^tTdU8Aa6}s2 z(^0R%b@Z^7oBpP?<3OcDdJxdvHlpwaif8h?V0oC18L)C=KnDs*2jc=&oM;rMtO_Z#y4GWk$mCga4c*tWihv#0^vxA8QMriu0i zVx+1QOJ1h+llOsE-+WWFwrw8W-;sKT^L zsK$D#@SM*6n$Jj{(46~L!!f71M$B6GN1q&eLWOoPpj~Xb5K$X;_tZDr25K5Rm{4Ii zR#ThnQs1?R!{yP1(d0%HHeS@kr8FWo{n9M;4w5)L=;Hdf-V@d99gxtX6OZK%&E^p#`So3tW-Oxg>11}*TG~K{1 zE2Om-RB4*GiA~3ChemfR?En&U-R%Z?eHNwMdMV~VBG4e{AT6Q4hM>uPGLnL zu6`CN)AKi6A22r01ii>2HOhMTC9z;U-FFyGN1}32C`6ATV}s_t>p_pvUyO69byWC+#kr}R$SY!D4kSKHFM9$e^NSInXC|F(CyOv zk1;mA;YW@44cgWOOQw4#&&l)TKsh058j8VQ@QvM6MsJ};qUv!C6@{y5yqxGe0-g%O zE?7&^#Ae-V5hWBfog&0?NE6+clAB?{nPpl%#H7h2(a>Bm5Q42~x(Y53FiqnpkThHv zCkt08oRxmDS(Yynt_g6LpBnEAm5KD@owUUfeVfs}Pt_Bb%p-j=1=K*i`ymVSsLF3g ztDuxe+XrZL-3GJ<-*f&W$HbawAKlZ&TIhYCNxRwiC=#U>dI|;JtHdV`K|vh05jjWW zu-AKH%^!?0nXC)xgEaB>0h|3I-|uB#J<%Mk_~hbtx2!gu2nHNy!hHejKM&|>{uVcH z(m0uBi1!*AHbr}_XftV=9B-KWDEUk(2Q=?!0|_f0(<7BXLyr~dV!TK$Qv#{|C8rbHpZ$UueHlaSLXT>DCjp%^5)8EJI5B=hDqmo18`jZ5=9?t6|6v z!@71%&*#MU%x#WsF|5oTEHh89-g>(+L4pR8ooU@0nsKd<_SUJE$$Ies(wl#VxkUq_x9*5et2TJc=S}>XO z8GqRd7u4;Dp~YUXtJNdRKlBZnoP=7A4CT^uN&=Zdwf3H~8qyB8OhfY| zh#)lQT#?SB4)oy)QdBaUYet>i&G`|inZ5$jhNy$_ZKNL7{fF;*L}1d+R#DJG|KXj3 zR?T-2>K&TZM+q zJ>!+@9`&WBk!C^}JN2H)c!nLB1z*?s_Mo!ox{s60_y*$JSFJ;@d^i57MpM+!vGp%% zzZ+17InBMW-O)KynPvcc;v2?8NoY9>$sm6;*Et^g1eX5O3*r&Sl zJ>p)}a(wIEm%hx5?6~Hrhw%lxLn-hD;2=7&(fM$@W2&$3`&f>k@daG5=r9kfG4-HC zXoo!GR)XXj> zdQQ!3HCNkT|Xm=nl3-TTcQ#Wg3*Tyk^Jl1 zoTsF1Mtd{fCdQVb&d0xX6D5iBN3`uB(2=!dQf~(_&lJ_fF24`N_Tj|tz&D8fiWx-g zyZ8dJ!-?2IG85;(4aBB1V&(QZL_S)gW+M=zccVftTWz8`_$i3M^?mLW)IY5!ikjmi zh-A4`FGp1GjCWb7-h^-J9>-(5>KJ^tY8ZbEPG;|mw|J+o$% zD(g2maO8ayc$7=HRpYbf!9MuX#6to4tncyNdLgxY*In)`qFya7mwF`fPccw$l+~6K zEB0{h)MuP1soMS^Yn*8z_T1zYb4%&GDflZHL%%~8MAQ-Qi>(`DJ7 z(eu$%8{HTID)SHYAxjCg6D}^uIeowbI|3vbtp~J zt!O7EKG5o8BWJ?~?BrAL4>mT(_cIXxd9xZ1qP!2hLnVIc&G^Rf#dVSIMIIGj}W8*tms8wKN*%%F6QQFjy>OPTXs zhSD=apP3UygCe;;G++7%?2P0^3_&)AqC~OY9aRG#??;%GKo3i_H%ConVb`LDHTP+y zc@pRlaZIFB33`|I}b@>$mIJwNq}ecAG3k*Wt0kA88;N&5NYtt zVFUG_Kq!V5ECOl4cU(_18*E+{O0x5i??yNvJvJ7jn_hu!5n;Q7u+a)M8!OX&v;pBo zcrIVKOJnHjNP(7`zLs}hWZ`mbXsJ#(=NpI6#huY!+e;|ebtfZm+vRt1dGak6IqqMx zr2aeEa(Kq~uHQ(Q1ImVaPYK4@ z1N5>q{d5y2PMA1StgzTiEoGKkvBYdGw-k#dHoIu96kV0mD{VEEqTOP4*eVmmiLOe~ zS!NL(<_e2gWUHt!R~C!rQnR&k7=jlE@D zRwtrYI<1v1i$iqUSh5K!dd6`w;A1W?w-uS47LZtBv)75XO0mx7vQz3+E~mwwAda(J zK#>g;$w7!Hr@fBQDf!E)6s?uj=5i~@wU@doER{~N#_SNQ?6zuav86acoaC^G@$qGr z@+zKrvE1r#inbDw#ZvH0sI-X4fx}X5v75{NxE%OG@>Yk#T3H%|3poJukZPgL){7~Cai#s^3wa)_1yjWyOHiwa)>gd>n2f3PZx9THU`R@y`xz8M)^vq@EM zBy1tY7Mv3~ACxW=Ur4V*#WW_@#UhHO$U@u)f2pJ(dnL%Vld*!5Q}}V4%_WwZt1U!n zjok{d1G%hRA$L|T{3W*XVvAj@Vc3X3Igt{iEm|E6oq`YL19mwe^;B?q<)}v%(FHK0 zRDnQb!i_>u}EQ1XdSgdrFm$SHBl6ijP|GPgyj4uS`k6cxTbvdYuZ*+A zZmVFZMC8y?N!%pT#P|xLi_6|9mXxKk7(#Nr7;H;ONbn~(P7zsfhgTLw%mja!a39n< zqP0c4Z(*c3#0r_m4p$YLEz~K0xrd9dxP_LC$QU0Vj{+oysW~sk+bDmsPjHq*Lxs7N zQ-Vg_L7bC0&oUKO0u&O$nlc%3Mdsb9oTCDgY)1upKhaECMGH;x3`evc=-zCck)o5c z$XQ06Ahq!5BCUQh1MOP^Bz>5eU}c7-yaeCoTKuQJG6C^;QS-9ry&19CW=Frk%1T#j zuBbx$hSXKs0)&A?Kug%!S5g9Sv#(^rPuv76$(z*%L6G~iB6NyqTv3Tej;w~%p_I$f zZ=gX$uU9Enm}^ZA>kLcW5TzkyN=Aa;rgWK|TtYfJY8$W>nKcFitJDJHw!jNmnB+i- zjug2gWkL=MC(a8Y&y2{GrKsad-2zil6$Mm90o78-wzx!Paf~q4{{R2}o(FoK z3!&-t5PKW!6)*lF%?Df#co#4Zup2<4GG414%cPmrxgd)!jDI3A+GMfYZFc4z8b67b zW*|f;{wfr+8bOGE5?3vi_`{*u6b*Mk)`weaEkbZ-iTa8rQE=E~k!QwT<<`pSW_yLH z(q3Y!vDwS5h4{J)cuI@Q%M%>KZB@?U`MMi!&`lhEqtRM9snSwgg^Gc?4rJ8M*e#`y zPiP&LqQy~Uu7U`mW}Y$}X`Uo$q-xu3qDn;-gI(eDJVdZU>LDtqCV1gBzcGy6{ z_?cefPo1vrNx)s3L(ZaEes%LJ)IS5nYIvAlw-{CE?nkW8#L~F?HrWvxI~@ro;__edeq? zW65$YH~emA%Z{=-C5(4f6rxpz{EZ)%h4vb)wgbczSuDj443R3V&LFkcQAS;cT^3fZ zJ`STkoOE8+oxwAdXDmR4dAdb3-C#6~&7M5jG*NehZn7!AAge%UG8%FX1#>8r8*B2@bMf zNPLzYbb9KDWf6uYIDUel3^|EHutcU%SbWJoDfZHRQUPE!VCiN1q%D9$fccm2lbrBh z4U>fv;RpNjxP4OQkbTkvfL(xX*Y1Yi~@l*1L#=@MEA1jQXWEAoz=!N_Os5%4_3IyHwn}w|Gn*^{D zO+QJsqYBNXP-&?__hunQ%Lz@jJl<(8WcpKl3EBzBpxthTo`p*4blEF`R>(I9IYyn3 zldT0L0g?eJ!uYJ4g#6paBf+skb{5QBA%D_XL6@H`81hp9$$%t)RzTe?H)CvsMm-+6 zm=5jEhSte%tz->Xyu(^rDHM}n*!efc^F`new?LGxy!*(u|b4TRtF4~0=zS?xuxijs0@@R(izw~Ejj z2#}p>OT4*=SSj4a@uC$iuk)kz%L0=O&cfgjPAle7gdCj_@`TdD|Ee;xP*`rRoGuhW zVJW9ns>+0N8|KA?D(K8kp~_WR8q0?(|Ggpz;{Tt)-%S?&BmAdP1phI9Mt_Y^(?uIf z`_u^XEv0NR3{zJa5J6kSEESt=AjMUfP*UQ+;FCK$uuz7TFBU#z$^!!*Xip)I>RAzi zO#!m$M}ejY1)3ccXyQ?(Zvym%a?G5NVk)QM5c!NW238n`n2U>P3@^^ShYc78J4T8o z^xzILuBOad1l`#o4xZr}B3_RfM5`nIdOm#^;wuQ_Fi%uTtC!czyyqIR*oM(8>Bnr% z2LrjlT(>x>(p-qCJPd@03@id+>5on4IqRx0zn-s5kIEC~e9}~=4(mzEq&ZK>Kby|d zo_P;je58c!mLgkeC8j!&Nlf@ENV~<~B?@x@iA~h*FFw!U@*}w`o6mx$h4B=3ZAjAqCVdBeCXy(9=Nl7#i zUp3`c%$I3Q^2B0}u0WSvfI(L&h`>^1jkVZWCb0ihG9*<{l`&gr4}|d-1oUX7NJ7F2 zLf?e_((>W^rNw|*03+-hWRv{5gN5qjM*>kfghP4XYZF*0W>-Lr4E?C19kwe z0K@<;LHL`~_e-_FQx4DpKE57yiVJfU>@NdE;L`v)@clbr2jF8s3gEMp{ZbY16az*B zB-kTma|qHH2VC^AcC(0UPCY;v0exEw&&-wQ$}`(IE0*|`cq zI1it6tvaeE`;kc%l6IP4O4|}>ZfSM|lNWqtAImNYs#O#=6YpQl#~dRfqRlSFQYi*x ze6~o&Crhxr2qY z<(-weT)1F5*oRqWRcWeqR8jT|?*F@10{;8`Kg|@97A7w(%k%`g zOX^o?>8l2TNJ!b<6D2fj7AQ*0-TS5XS^K4ZfX#qQ>d+U;<~6hTOBVp50Eh0`FZ~Gk z9Iy_s9Iz7M=G*s6Tj0MI@D$*~nZPBRYv!O|1iV_mUwRtg2Gjyd0k;4iK)7W{|0m$t z4tNbvg!s|GO=c_1e*uOg++~1s0PV1E2CM`uLio{@=;x7!0QeUE(_w#7HcfCF57-EJ z6YzJyqkww>Re%o>E-^7to0ycCoS2fBnwXY2A~8KNLz}3@w3Rklo1#tCrfElL)3q5% ziAma|q@?7el%&+8w4@P9=}8&MiOJgJq~zq}l;qUpwB!-V>B$)>i7DEYq?F{8l$6wz zw3HDk=_whhiK*Juq}1fpl+@JJwA2x)>8TlMiD}xjq_pI;l(f{ew6qav>1i1w5=Ur9 zB#lTOkuoB6MB0cEBhp7?q$j3p)05JZ(^Jw@)6>#Nq^GB6WPrpBB%cA)8HknvCm~Ur zl$?^9HX=PEYixFoZk)=&Tv!B^Ob}+xgPuh282ao-I5a*A{Q)p$;i;i`KIGyb=yzX( zz5o~ncz-$iYyg?$*BuxUW)H&lN8EOpbL6;Wl3#Z)|0T%EtB4DTS_)kT<|VR8e%-?J zJgyOOQUFU}?=PG4@!cJigT^O8>w|!$fSvz@{v?~^*ByNMBJ?xRybxg<0hxd)vPpj3 z!JcK%iI84jz%YOb>F)xNNq*hI{O6GWS2&$HNOuO})XFCLbq8`2!;JCCB^A!3>hv@| z?VDEUD#3E0!P?b4lM-H=!H3!M{sbkOu_84wk>-Vgpv!4grO{QJc(_9%d#Je(?xe?s zngiP<4o-gHPb8` zdLz8hok?L-KV`kUSa$PLrtpiohMSA#axP_#<=Suk<8(Veem@+h>q7e zHi7PArTG+jH<((%C%uq)kSdWNBw&rE^D55i=?5~=VT?bkA1J8Cr(%DXVS+XhIWU!B zAB{!cSR@CK{YN;Un_|teQymNVv-z^F{I#h8{3a`Q*kSp_q)pV7m!zebbY;c%uBxv> zUzhQdlTIsslAM7~iRYgpaqvJ%KYV;7e){nlgF2<3ciQQPiI4O{;^*?y1OBN+w$+|g zI?}YQr7oLG*^$EwSeuT`!T~=W?JN^8={UG}Bqr);>k~>{J}og)9PAL3nKhTQBwg8c z!FQ0$%Ca!?o|*S3;w5w^-xz53kSD?7hl>7a{*ON^-)wLs3fSL>8;VvPkVdUMAYB4@ z6EGj(1QY=F0JZ@7z5{ssfRx^HK#B$o1c-obn+`}`0GZ_19rzVx;yK%?E(mXKIUqFx z<^vW2jDTXo9edh9`I3*Zub?}0fACdKIsb35Wa zX9y3=Z#XDr0`dTh09Ams0E#mYX5SkRN?+xMho5dbD3t>m0bc?Z0}cTw&IXuAZ{aX) z>OpDWBo5Iq3Hp|t8e(ocC@C|$*I@P>V_Jp1Y}87^SypyXda3-NR0cQ-hy!e&eo%T6 z;Jxdh^daCSKs{hGAPx`>$V5CoFvadIn!dy_A)1pdFUPh6eh5iPi*^LkY__n)2A}~SKOGD<XY`^W9Sv3kS^p46~Y6;^THZoyKq1_r^hut#`c)fqo&8BJ(l)p>G4C4_8u4X)b=#?Ea`b~ z&u4nR)pJ|V)}CiYh!NLCOp2(AXpUGCu{Ppg5r-o*k=OcH9RkdLpywsgi@tjJc=h|& zKR@S(f2^urIw?^+aY6HMsavPs=pOrN{B@Q|uPmSb^iJKN(Gi~x`sJfdPq$pyQT6JC z9jo7Kf2~I9`}Op!y^q}T-l+CD^Ojw{&C~GoByHT3o4;7G`GsW5nxo zDZD4PeCc;>|1zC3x#ZQqU%mf>e?N9~_raC*(km}5Td|_~##!Z2ro#L46W66olU8q@ zboHu79&8%__?p>6s$y%7?R_1?1j!C{t*c2BATYwRz^& zC)QR?`EBObq}dOb)%#vuoc{S&x+|Z)@ak(O{Oy_(8@|*&U-bJ+%RYQC-SY3{-&tNA z^iXt5!_$}EJ@(k8Q#VZh_2L(XS#ux#%~Lg~_>Ug{y0K>O(@*`q;`yfO_q;i?w)cUW zf#26X_fx@}>o-1g$D?!am^*sv;~#F!PyO(XMNb@`^5(S*el#t5YR%uq{d@3(8Q-O> ze`d*)zdiYP`_r4+w!Y^p$bWyw+q<)_JaFN`_6L^KPWj@Qt>TuQ`B%(5c>4uUj!eA# zi5)8z?SA}?AE)Gh_U%ifa$a2g?W|``#N;e~(P*rGchJJjxAs2tYV)whTVIITd-=8@ z+DCjpFOGTU*}e;(JMm1+uR{)9SNN?Mx3uZg7iZjgQ%-&S_e&!(f4buRhozi5hc7IB zC~M_|3!l9(`OB9t{_)L6zq{zK-`4D0Rr&tot<(S5_sn%Y@BgPF!OHkDWIT%PMm&V# zukEz6KG;7beod$NVaKmE{_!g!bkdzE1%LWs;}`kk)BY%R_z-&T3Xi=z{C62El9On7 zjrhUjDdVS5{Vgz^q|6?1K6Ul~|1W5lVA7;X{pcEP`sE3@ zhAC3$QP3k(h{}P_q)7!?H%CPl=qBY1=s#)F%>z*7g?=%@0D*2A5B~L!(nZE(W$AP` zPnwjMS&%oWN92Iatf+qd2lUG;=&$RS6_v>%if77$l*)+1k6`fT&56 zfE_;taTK!&!bP&FXz+KlLB1fQ(t;O$y+?C1Udv-D(*l-HE@d9adM;q*_viXp%$slY zoJ$T19P9OD`m(l6r4MRZO#5jvRz;bt&J9bU$Fk+sN1go&xO*|f2=OP`^aCvcxmqB-p_m=`}MNxsvkV-qCfuS`S!$<(Z~M!=x0BF`rG*8 zysOsybA8dF%Z`uneDv_bCpNyi_ps-Js7qt+I(zJvHLvV=_WpUd{e0K;cX-bIDf;ME zxnFqs#>I8#3%`E9?CB@3sV;hHK}ON{=QVGN&3|p!kyZ7==KTuY+|2uco8rD_S5yss zY1zhS-~1~7hHYsFdPd*%!QiSsKX=Tv-MQlGp2pv={`A$}cb@#^lS>B7eS2KPca1t} z`umAb<-hRy%eqyMUiFtgO8Xu8ule;iKR5K^#m}Aw{5IyFuWANFEq?SW?^i!%7e02~<*vI%UwHQN zUkfkz*UArDv-@6FKSJ|y-<>@t&X~vxUzLf&?b$aCdt>XpN$vgbJYpLA{Fv*WPCb0& z^N*B%&oFz}BX57^{OfnmPOct&)_I#IZv5kmf8780k^JjiIiK`@@9DU|Ji0)5x?eqXb-H+jE~ra&&|*h9=+nRF9ZzsKxx)Bj+mT)8se z&R#`!Wy+Y&$#gI ztH0SU%v)NSR6F;6;mupm9&I_s_|u^K-k3;7NDDeW7kb=glT zAFRHx-;gN}uRk0&NMeW}g^Ywe^9|U+;VO(&q2V zgiC+D%^Txta2igSHe z-qfqKWuSS(r3bGSgo=d;BgbyN;i2j0ABuVc6T61XzrOyQwihSNeD>rgZ-ZD@#v^^L z#eZmiZjuh)ik!T5ecq=(>?%x)zxSyp&>RYD#$>l08#ZHc@7$G*XeNc*UdYs1Yy%5O zwkhMfD>AEYxFEe|w5eAt*|QdXJ1gt6#Xl`snnKO3aE`d~=WmA;v=r>EVyv;AweI*M z*VU}6*=!(hVZhegEP3|VuKsBqYqo{Y*8h9i%RjomoPUsa1XDf3-0x2F&b#yp7F?*C zV7vJJx`ez8dLtgcpEW;Dzir^xJNn3*BPbDKJn@|vPW+~tLVQ;EtMFCfr^3hgzvlc? z_@?lS_!RmJo!=D$0qO&vj27D75&ElF9JrK7{xGs#>hQbp{BZZZ%#}DJNpt3;PGp-2 z87lIp4|ejm&|GX{rwM7@8;YFY2K`H$$Tqy23$4{!Ep2|s5e0{dc3)the2Goby8N5^ zaX69gDBwrAiYz9aQJl_?YqE5^4Cks03CV&Bs^bDlDP3V!h~QCz{;T7s1;tgJfb@kfVx%VE(0=Oqe|>!Z`(3Wev8d=|cF6vk~yeIh4u( zLY=VT;e(QK-a)B9AQnLOR~G*f1dVVX1sEp#Dq<^|@DXTV|#qUV}$jFC>&m`d<3u+Btn6S^6UGiWd&2qIt% zK>1<+bsEQ&$|CB7Z00W_0EJiL5+0@5#cc&b9PBVU{ZL;DOOO32F3 zHW-EvFPNA$KHq34;C7-H%^Eg+1?7e)<-f50H9`K`a$70xm|-gtgfFQW+iI<7f!JD$ zf=Go=UScsRYYFNg6rb<=>oR^4s-MYLVq!-))p6+?@ssU1?u%*Gun6FIPzqwfFmk| z$w^$r0WmqSA)I$8dW1g(e(S06hw(l${Ei$~Xa<7l@9ceM_^q80sPTvKrt(K8sPLN> z@f2QjnFYT{A(6`i{t$n5<}b$)5NZ{b2#-!z6+Yc`%1B+NZ|yF9Ie;o{`s^nCPLaAw zU#YTP=3fbLM(HEc-gn=3;m9jC6|%Oy7^gYRm38usQ-ZMar9;xTZeYKmW`(FU9WZ4=%ru=% zr}E>v!k;fcu1lPD{Lu0X&O!J2m*e}(?@Z%|lwWXkI-eP&-$9unA@|p@?&(WrxC!LC`3_0~CUO)PjhvI^96xpikLQeA0A~a6K zhqyvD=gnP+JMMSH4vb)=Dm~onstxg!uM!H5A*<~nK`{qF7dnInrNe%_Ug4cP`i)`# z@lRc&mmrt9IuYn;th(w zJQ(O9ME1pDm>7&NqA|$mDbRa(nQWLL;|+A%( z>#!tNAC|l@Z-G7XL4<2QEENED`~_iQubO{Y5&&x-J1pq|PvU!U!nMW{VQ|Ip!NtP| z*9n95BM0a5|04(Ig9^={HYxJzHfhg=ZPND3+N9zE0Z6;NO}g~rfSvp{huF!@aY>u> z8DPex!C|+BWI<(vySRq}@pGAu$k^+|^;}d*Kyr(oJ39Yr7yg zd^z$%=~Magj)a#lV?O>-@9S^iRbDh-Kw|)v6N`fOLb-tfiZ<(5JN2mwZZcKjwJAzZ zT*yU*x^k6&y3*0B5vu&PsY(Eo3V}MYFyHF^K5 zq0$r-3cI_YK!sl$KK`i>V5{P1_!R|15sEbf_x9p~lnQKxHsJzSTsCX7GY>6o`wF@@ zBMjdZr_T7%5pn#s|1$n`r6c(-lCPF8ZK{%hUw-~`HmUfdO;rTMj~@cwgHMZO*0f2J zyMee<_{XUHj#dY7Jqh>a-9Y^*{L55+d8%)v{7<9*_t(n*>F1pXDBbffRKE)z;B+59 z(0uL&`5W`kCv`!Qyk~@#XYm7Pm=9Uu=_hz0fAT z1emv^P1^Bxo3!EGHfiaqHpvN?@=lvnwXIEx0c`seZ4%%~eDB!aCd~qj0&MsOy4AOB zl5pn{=|h4^N2K3ON2ECg0r=_GBhmwt19tM;6JjT~dAA*rEYpri(dOW=kHSuPqYH!G z)oH}venhIj<%kqld_>xJ#}VoADS@!$t`1Az!>1mRz6ON4lYJcGtKHqOQ~K0qs`>-J zeiTxqO4G_rRrpT&5yeya2lOKqLY04K{YVuqC{dL~9bc^hZPGtQkgspz7YW-B3j>U4T&E%-7EHcrtYiPFN6KW=Y_6MkV z8CGIc1s?K=_$fTTwS?E_GeZ!FZ~L?H75e{7@@#w`od0lQ7m{g}H9)^V|7V0C5Z|Bw zGmRgT|FF?%3rGpwiHXx_SW?Dm8Tp6wM|Ad-OVs8VIkE_Ekky=Zw(U}PKYndhdkX8KDw|^drbNe@DktwKy=bE$(VjjiUmXhej0I1Dux*|`Zt4+>7%^l{T1@G>#3q4d>Azy+2UkVeGBi;x9@~W|4&a# z%Ji$$YBsGIPOT2tFe~d8Ty6fd0)qVVX+t3JPwT-kCqoje`5OBD<#5;o7L?$~ zAZr|3Ys4cDc$5cy61F#9jh&FLO8>2txc3QH{WE?9MI3oLcz7P!^@QeX+-yy^YYG)8 zv5e#qA#_2XRqpP>i&3N&%Y~Jh$uGa|yhRM$N6VbfD#ys-!%K^b5^#NZg3Vq!93dRz z1M5NBRIXNWmUS^W!&xyXO#y*Y$Cr<=k>rBsvNjN=YO8UCiBE#|r{brp=|8>XgU8S6 z_>})J`W|SfDFwM4gXW8rD{O-E-wlp#^M3~M!{xu5=v2Q0#m-lo&PYuT7e7pzx{#%A z8vmg_IJ;h7#}0Cm0;L8k3|H2@OD&aj%cWmn><{lk5S!=c1gn-o_Gt9I}1TS-$1r{GcBl`vhI6gJdPiLb~D>Q}Pq zmLzQoYij*Ful*Gm@|(nEDBSK>0rT&{+u4$mWXa>fJ0nyQmd#G_wd&M@qt9P;LMjIs z0mA@`0rS?KkjemhKpbEZprvQK^m;_Qbm-U#=~!gDwD0H%Kg1L@e7TAsM!mvwuNgC6#-O@e^`9# z{6ZJ~PaQwp_$NRiXbKwt1bn!F!e2f9;mZap8Sd=gdHfTKQx*M``^idE#xC-VHk+SqR)!L93*Z(5F!M2FjXNS#ANd$s`rON-Vf5U z2Qhhj6 zPdzqo71Pbo(-|j%{rt&^j=BmKCfGm3mEN@A2SuHDcoLWAE1~IrCH}~R>d8387gd$} zvE8O}6mp~lX~>HAsZ_4C+#^yGqBv5jog5(*I7Xxd!MknPOB%3IN_^c&jhMZ6&mLY= z=w?HLfIuk-*nLr=l|RT!Q|jzOX2qB2!8=;=>*fv&2^j0Bh@38m5{kA1`?*R(ajZjf z78m5^ivZpBuPXS;&Sg=k>QXL5!) z*XTNCHJSeJV0Kt7INZh_J!Fdvc&yGOzYyp!nVlwFSB`?~+Mk}eQ!q0UL4&Q5p1I>R z@`}0O>#Mze5_*hP4~0$DR!a>AV*GIw9_|$Hme3hi6nF4@CIV%f*JHj)h@1PwJ8)mR zeBOjD{DGhTH#?N2&5-sY)C&DRTpOWH3#7+2bw%L{L2!E)2vVq|Q%u>_(Cc)l^VlQK zNz~=tR6~!HrJ5$elj}EU)_!%SE!9M)JWsPV8LrB~(J_JZ=XSff&O}1N7nS8uX#Wz~ zf%H&sf5alQ1BWWnk5e#a@q5vJax0&+A~sV1#c9oj9a3!wkeeD-!cIWpl<(6u3D14q zKv#GvE~P{9?*ULaawAj0N&e(V?gV64fWn0W`6+J7ceuEUTR5DQHsMfcxb&h9DMtk< z_$D`kSQ*s*XZjtA|5WjvNRL1X6E1FlmA?{>Ood*x+v$E&TqP~SuLi~aRJb0xxI=m} z1XjXM?k~#_>VGEoaQII*ez>^d+z6jS@9E-JhoiWmcojFbyW+O`$`0vtVBr-VII9pI zZX3+q-;3>#?gdb|DwsO?J2c*}2v2Dag8$yDc=!!4>i{R{H-(IzZt= z!znn_ep=8M8jkFDz`aU5bBO#C@kU?G(>Z~-Gq358PQw14>^2AHcX6HR7%980RQZiU zTuQ$VZmaNpO=5@iG~hA7bASf{O@KY4JEQ@lI;2NslfGxm-`io{1sFVrhatbNpdZ2< zxt{xf4|B8p-BtKd_t{~>j|t-z8ZVq(K)mP5JESisaCqqU4r$A69a3LQhtyb%@1hQA z^3)E=2)8YhVFI??3KOv97MOr7lVAe26u<;*$%hHpG7<6!JUZamlN$ghzPo~j;}Hj- z_{UasNarH{*LR{mfR0h{Yd3K-7BmxVwIV&hhBCCrfKkBTFdc4yNaStDH11|8;U@W) z=W&QY9xs9Wj=~O{y+Rz&yya%pQKYfuE*{B~Ymh0c-pbx1Zq z1Ypa(9nxZ$qdef@U;g{B7&v2_kp_YK`$3c;AQI_*`3Ukt=>sAkf=LiUOXS1cZLw^I z`v3pR9y?E_17Q^$x-m1-=zD(tMM>4Mul`TT!^<;#tTd>#C1m8$!{b2LYzw2Wy>O)SWzy;ahBnH zIX%XMRI1H*V;gS|@i(?{-c#|q3#+YSHr<}YF_z->XWXkp!Tq!p81u2Gk*$F8y<|Kn zIoIF~W{eWCHmdgVPwa>^(6iaim2;egX*Ve50B}}U5LFpAIm{*SsI>9R ze&9zJ2MJeSEr_OUwy(4nUxo^EskIV36h@A`7IqVV{4Y*WE(8$b5)$xILP7$`CRXR= zU2nLOULJGMPCcRr4-YaI^|w6nDz79eHWo~t%#^(p8}m}Or76)APxE-;>d}HZUoe{m ztjnXUODl2lkzlTY5Sq+2c$FHK8A4GJ7kxf7D#Ab=#S154worVFFnqL7Y(~-1zS=vc zq7aLOV%nvHY_OqJak=aXFNctDtx$|1dbKcIuuKr{94%OE!kvQUZejFjp~NVZOcYAW zg%Z0^ieh&P<@v&>QKN-&t6zk!yiyd(9dH#Y3_|>9p~5I!i~m-kqFkt`5aQ!;eLTt5 z9aDuXMhjO6l{I7&Y!ii9qXnBym^DkNG73XS%=nXb!R~;+;K;+%?=vj65-L*1?Scb~vGj1goO1{0 z9F0&ch$uK_;PH4!G^9op3T?J>_RoUb?r9@4-bSY&_Ak-R$SJUoTPlE zr6^xSQJEP}zf$aU6Xwcb;uaPDk(`7N{HQrWb)xwnJL(-8V9{217bco{qtRL3M-iG~ zB9mE87ke0;IbSck`>P9g6|eW-KgV{sqOxG==#+>k3j48i_#}^9BhEOUQnOAkqMUB1 z5p=&BZB44K2zvoq(8VMCLIE~AW-6i^@^Hl#>kHYg9RFThN~?3catJ7OP=T1mt|}Qv zU_Z|3#6#T*if7C=Wf$C*rxWloep0S(q9L0;3k_~6=Y4kjl6Sp z`T1El=Yg(HV7ldPL-rvhpWQ)aB~N7m(GEgAc0?UoAN(=s#*9lp!QWvy?d|dQssVKzr z6%(M8kU1U;0L&rBQp7)il`sN^B(yv7HA!q=23V^WCcN zI(#cX@}n{x37~H(E9IMbru>N4%3W~|d=ox1K*2?Kuv%pIO8GlnIKoo~AY63ZAY2$Y zv+^SRPJj|NG))ReWlP~GO$tZfgjb+_i8{_IN&AZ$o_<@t8iKHqw=61)hXqh%7yYlc@E_Xg`xDr`4KND zA9REuG@cqKyjfZb9-cTsg%8%Hu6`}BVN-_ zu`4vI(df%MRK9ANKA)%#L(k6#dF{qp@c65Tj9OG=f5m%uG;- z(TGt9n(3hsrj5ozwjDIP5Cp+O5Cmnk5oEK_2!di_#7s~KvPU*$%O*y!Bt1zd=_Ezn zPo2X#`<~~X|L%RxKX;y|o=@I-e|_KY`&M;NC*A#D&q!1L$0rK^{V#r6Py74t!TFyb zY@0@Z|L=+ZB0~HB_PsP0UE!bC`q`=%UvT-%%mW|z=zp~Nul}$9zUqM&`3}SX&A;_( z`uTed{~o`Y;qQb0*TMdKz3(_w-&Ft4AJh9kTKzx8*910sc3lshQ`Z|f=W5C?-+K7} zcI|X;*0uTX^_BLkwP~&eKhdZ0Y3J7Uqg@xc?%_Jmbu(Afwz+~^*Yy)ze;%&u{jP7i z-sig9^?dFAG1d;_{jXOuM|1t-{(t@RKkltG&OiSAzj|+V+w}iGc}!YsiuI4%HZaMtluIv2yR(~(w zf%kANcRlp)_a4K`TpiaRo#)E+GkkiD`faW`*P~n?yj@-YyZbliD}%Z|v!C~t`}X;^yja=@9_U8R}=Gf#=o6;nwR#@6?MJp0`F$mh^r~z$K3z*nsI-* zYxM8+|Knx<_dhJT(9ka4%a(avS>W}a6PiQ)w=UJhwGX5 zwOU^|_i(+)OKyF*-o3@|Sia5Sdd>S=txvJf-M4jCKG15Nd*0!CnV+@)aW3%s5&f@h zwH|)^!}Y#T_}I!-+k9TcU3?egPqtd0XZ8iZfA$WC>-}H!c?aWbeExRF!*%{$zmJ70 zcKh9g%=mMz{GQ)$dna*R>+?TuVUN*ut=1jxe7FvN&}!ZHE{E&EAGTU|zx&}j;VQO^ zR_g{%usdcvo_`PisMWfdtA5;SJ%tDNii2B!+G@RrGy7VtFWvKSz32K?Yv%rk>(O7d zS|4Q2m!5yPp4{JRU3GzVGhQj5ziqYt%6&JsT7P-S;dQ|!OIphxYyR77$Ibj9lRGT&KO@%53%G?Z zVsNJ=t(S3%>p9O?bMu{-wBE#$4|?3;dg)!3v>wLoqf1)T?A~=r>jNzL>BhS+X`O$G z{N8g(>%zwy_g+g{7jrwG!llcWv<@-l<*{~6nXB%-q_xQ9_gT_9=LzEHN^ZIDlGYYR z;gZ%jxX7rAHu+PmLS!I3P%Q=QmTGCo`nf0;t4DoQ7V^3Yux+m8& z;sH)@ncp1M;a0w}xzDZ5{mYiLzQh?844<>4wJbSY&#twfoPMr6ap-b+;#$6vtEQH; z-onlN0K?}mX?=<*zslAXOIp`*4X@`u-oUXJENPwKcHZ<^&Iiw9IxTJ%ypYo`T+(_B zck=0sUL>#V@|7I+o1ospwfq2kA2u&XKVn`cyq+a*;Ev3^EUq-~vpvrrGatKr4EJBX zq?K@dmvhkE=i6EGDz5#`lGaXc=U&e92ClzmNvq<4AIi^Kc_}<+T;vSP^Ui90t|{}k zTzb2+TDNa`g!MAhe?aylM zVtBW+T7Tl~>(25&SmNb*TzLIitt+_h>a$vJV7BY5*6J(N-)x|DJy#D6v<|*Nd^aCx z?V7fKw-{)>=0&{aKx+^8aP^C=vpvvyJ&Uskd?D0xdCoxV4cyJMUedh(23jcxhX-1J z7pMB)NgQ0{)N7r)y9~5G&2=1moqZb} zXzk?K-3D4$zg`@?bc6NXeW3LPX8aWwc(XTnj>ZOBm$BjpIdzYL)@~lW&p>M*m*0P& zb>NNi&FoFq`+$MgA!gjL(YamjYm;wwj``KM`1tq0f!2?Ch&OP{`2(%LvEun}HU5L- zlkFAaf~ zwJyet;(=DejZ9gv%cV~kX!V$~V6tYQweFq9<9~AXlLuNe?{W?}`flrg%0TNq+`_9k z`qY8emw15Jurnckjy-Ll^;@o=9B3Wo=radeLz}HL8EAcn%lY2-*k=yDSKgjA(0U~g z@I4%S&Oqw}jQA1m;U_t<*1oai4sLnwKo{YcW{)+Oy-?imGak?3+7}J9Zo|!;fmYDy z^TOQc`NKXhtQc%IN;4|cw4pWf^K*X#@9o$8r>-8p3YYVS4cinyv--}meXJJ(s4&wIg0>+ zZ+@g+`4#cs;z&J{yU#t}vA(58>T&stImr$?>~aG$ZefqxSu$t)wnyr{jChb8E;42N zL;7=+8CS5+2?pmKsi&DRZR)w1CAYDCyCd~Z#w^(70Tx_n>PL>$L-sjlz%G}w;1-6r z*RQ$Hwe~sZCiXekl<#SZ4_KPMR7NgOP>m+764)WiNiE4%BFI%9DU>$4BR zJ;mSb17{iD%Y03FnS9%i{C-F3DftiXf27{Za=CavC7uT!saLV!TE^$AXYe5Xn=%iv zT46n(HqJ%r89vy3CJ)nZyZet22ZKl2w|~|CVtHipDDkuZX#2{1wRk>b-pAQ@R+k*9 z*D-j4ePsM3`^NSfdHk&PKUp4FvCHtON9x^8c|yKeTzaIAd)9NAd@_6Pk-B8>dGho* z<6VBFUd`eq^2Pp3<&o*Dj?`nH*M7$NV)CXVb&u&r`^r9dIH&2GkJN*6=6$O;7`#oK zOgA5?L+7{RJUj0{Qb%9XE;~~1VvqAIJ}w_$wvJDVkG)UHYvXo#*dZTZa;_QgaK0M9 zBCjlV9jVuQF3RtSgZ*oqv!?uA^L|yo-S(5&_pFQQwMXiGOs;dzzb1}7=4Jc)_JMuw zVDW<^b)VS}t$(NZ3;AdIQ+Z|Q7xMOXLg5N&ClQ(ffdjztauBrTtCj>kW+0p0D>c-g>@X_HFUqcD`QE z;*Rt69wv96uY;@A-+SKYm*#)Ke7(EzqWOAcm->gz*BwR=pRczzvvC*xP>=j$7NcG!KT_58@VGuFd`^Ne3JUr+v6 ze4PG?`{{f=v)4K{%-0qBZ#3^u)pPh~=6#1e%U8txOxS12mb~>Cu;hC2=H2;vlX$xC zG_N>m z4|g-WPI(`HFkf$G{G<7L{CfR;;w#&4`Q9kqX}Z>(1=nEyG( z(=OyzM(i=>E+*W^4i7P7#U6)CadC_VSF&WxKG(A1dWLJw%ZM3c&N1O`cDSD@`|Psy zYx8j_dtAnx5eu$i$tm_Z!-`$DpL=6H%YZu=at|Yxj5*H^2mix-9AS?kbFN~+36`8@ zpDEkVyRqKPklPq@Cp#?I7m*yGrLYR{O_ z7514aGZxGlyg!|kw=!XmDR(jBKIS~ck`*fsAJqTF*2#z~nJ{L`wamDlIXAIn#)@+cI?gvE z?q|Y2Q?`DuKbJD+GM0>3aSel)INyvo!-QR?oMpxx%(;gpOIDm`@KWdd2K_n0gdtO| zV#W#PoMy?C6*n_@ne)wvJDIRx$^*=}z??&W(4PS-E@!aL{xaeu6Ly$#12b-6&h0Fj zGyEs}&i2d2eaQJ{%<7-zm%)1ZWyIY~xSuKe%-H&)ak-Qwm$72R;1%-Ah*L~B!<1cS zoMp}(EV+jjO9ro$Uq&4Kll~lG%8(gXG3NwJPP1al;8pU=h})QOCsP*e@c;`hFqo0o zKYOpd+Bs(R8t0W+>fHC$zs@;lzQKL=-{`sci*ep8{>HaD&xg(THhE;wb-yXUTe~Bk z@Ars@o%dNE(=GBgZ=Cl#=L|loJ-b)RtB)h?t2`ISxXp9Me$PHI`kc5~?(lx{@g@AK zxQ~mMt66?cew#99+0E_83GrR+xjt#V9A&o4^U3Nvo=0}Bk>5peF=Uskm~(=CPBZ+j z{Ib1U|EA1+jCqJ9<5Svy&pOT9zScf6;)c`qfm@hzj?s16G2UbTGwQ!@K4#p)&JXMt zgCAN~rF`5u`>VKX^$c1^{W}F;$6j)@-gGNpPaiy5ulDyU!A*|VyV&RWxym;^T2HdN z#nE~n!?Tapg*ZzNnZJ9Ec1=9DI$9qvUo?EQ4$Rj*_h`M_Jkfba>-OKocaNj>R(9B9 z#)8E?kJf{=`gj)^=1YS5+D03j@Ho<@n3MX zUc>fO5-qkuzAk%b(!dBJ;8jHc^F-6JXVi7S|4QZ3C0^R?wX_Z zEW@W*2ZM>D^(QGoX=Trs zFQzY0&+3Io>;3HfvvF^x%o)b%(RvU2TsoxR>yOrJ8NI=L>~E6Sn~U#lN9$!Q-+r`S z&u&+|EIy>)EyQu<(R!NcRY&W*DbF3Phi_>fu3-8V`_J&ZN9(e2_tDxHFIwH7$`jM; z?Hl{QlpiL)Ia)72TO2nWtv9lF()!qG9jjM3Pwk;&b;k5;zXRAgDmdxy<)T~p9l-1j zAFF2>+}iJVW^tQi^*qzt9jixgD`XiJD70~bC&FLp6$CIs|S}F zpCjyZEsL>Z_131$9>aSat9LQtKE^!6gcVZ`-`2W0#*8bOGiJ%P>~lRUZeshM*2|D{ z>~MGEy^PC@OV6`zE@Q!neI`toStm1YVb1L=xtrm=t&=^*w=?d2%*T{F*k!?t2btg3 z@5UZ64_C3|1S?K62<-!7Ze_~7%=4X$)*nWU<8MEcj~WaR=MW?F&=( z89dN>?4f7L3_lb*#>tGWRlgq`WiYA`>pXtMM4J%T>%c!IIn9 zXU_J;;%3C*yIB`QcDaf@CafN1pP7zp&ywSJHx4IRvBUPG?H5C4>~IIO$JigHk2TMj z@j1qfD;ci#yS|$;b5>kn`*FwWp?g>-19mvhl#?vEnb9TA7n8?Z|2>V*h#A*3#`e2$ zLL7{qCa?FB?@R3~+t0M_W$r)Qd0_Tj?bze+z0Jc(R_ri+o_%DGv&^}J;T6Z~)%P*~ z3!G0DJiuVub8%nwT*}}@&KnEvZ^|#Wf2=z8DYTB4IHwF=ZvOjOHzW2q&3wJ_nZ8;6 z?r$8fVe}T~jNx0I3&yNiZnBRLF#bEm!~T1Z)l>eSzj&WGSaE^n7V|t%KHhIX*!!^e z3gcP*&lk_fojb-Hd64o|#$n7Jd)&i57n}Q^u-+B&!EyGvmf<%0*4$^01xu!%5-+pQ zI!70%pYyz3$S<0oIkz=_NjvsAev$f}-U|%B?VPOS)!x5Mm^bbc$Ah(F!r~hH#pt`n zVTa=nG5`0RTlRU7!L`oCL*3_U=A2=0opZv3yBU4o_>p!$bUqjq?lWflVcPvzo>(zw z_!H}TxPBaA$%qvv8SFJqQ_oFIm@(x}W-OTV5KC69IPwVfKQ$h6PO@Z&6*n>XneiBL zCleM-d5AfeuhNe(D^4@`xpTpYvrKu283!M!A4gd-Vz|$_WXh@LK4%#Ho9BlaXIXJa z<9_Fd;eR*}Ot`=fhb}e_19rKb8OPb~9axWtuWWq&u*nX7p zIm(PHm~%A?ChT(^D{f?Pz{g33>@nspCfvuAhZz0VKC$|p=XzYfgWiLTxRMEDrd-R6 z>zQ*COJ=M%$KdzQ86)mz!Ud)rezg7!nR7KuPO;(!1~(Xw5qC1-KBk;!#=*zv&rz0K z!HR1b{K0sPxPkE@`EJU8ly6qdnf}T8+5WTVu12=<_q;WGp=jOw^*pNrv8=-_3#so+cqvUPBS`t zq29vsoQ1k%exCmReNuTR^D!M=sF$wMpUc?eY8ISkpPSge>q5PqA@?w1$%>;-cK>b* z^(sc3V8Us3n6k@^J?1P}vd@a`yIbE=tcS~4ay{E);&1MAJ0s>z`5xB!RP(dT?4Aqt zBBOhWW5T$cW46qF2KTnkrzvwe`-~agN1V-lZe+z-CigWDbM9vlF4PN5IsA0-F=TW< z`C`T?w(q~-_m^qMjD7B8@&NnKg7XZQFVyWz_2V+OA7~t=oMxY!n4E9jELbplkp9d$ zJgL0GdYE%fQ@+5tXv*B&lrNNrrp&!f`6B(70j~6G?C)v-$xN8>bgwd1bgZWby>V8w6uz%0;{BaEv zPO-xortC6&+CsgHB^R3dOBd=D&$dsK&Ih~Cus*gE<2JrP+|O~JYnd};$&Br3?HRIQ z!b40sv{w8KSa4VO zWQPl^IR1S5{7;@c=IpWL9(G>7P>)<;oPQQ4LvCQqj3xU_*W2G07>5(gxsLHG%*#H9 zr;YPU`D4g&=G?;MRo;J0Sux|t3)OQu;~Dd^&%JEFT7DUF@I~5jg2`)~SEkIFaX-U< zG4G4zi8JhTGux^4GT<(T+{cKA7_(x+;f}m>j5$}bWX$$!oj-FnGQ5z>u?yxPvkGu)~rm=h@}pOO4MF_82nf zDi)kz$!WGXc%B$=Gb3(ek9%0MWW_}WZxH{>#KUEb7%}FW=KdSyqq)yHc33j!;5zr; zBo4-$V9E{5xuvP!XkVIoR!u#Z{gZVuV*AbV&VU(X?qR}`1(&^C+>F?Mi*+()%A7k` za1SdkFnX)^#Xrj%mowbtJ;y#bF?pNw!-}o-;(5EgGUF=tIKiCLESR$7X7;&_?RR*d z8L(i^KI5+adWC#(1-o3sj8p7!hUL4QFGlZ{Cnl_za^#iTaTNRc+{2vn zP5pbkA75o0u4KlwEV+rD_j+E~!8#eS{~6B@E6y?gtaHVT6?+_hgK;>@s7AK7-GD9^YhKE@g+ym@#6(HLN(r=nKvV zQ+AniwyEb1hI8`9h$R!wGv(k$<8p*OhRnH&B`4VDG%KcTf6=}$u11>%UN)oB_~<2!_JqC&y3rca~DgN>~o$K2j3!|FN>djPO;()!yWoFWx@7W z#PL@7Wx$xL*=53tb8LUrzA)lEV-9W-FW0c-6x&}DCj)L{m-Eax_%`jiob8>~$&@L( z+{~OgEAD0TbbD!Ip7e3Cg;vsf_B<}Yb z@5kN~%-CW46YZP(%o*-AZ{ttBSKcR2jM?FOrrg9H=a~G=`kFEax0shB41X?8X57R+ zGj{fQo>=f8^MA9x_bYRZ>Gjsd;1}An!=fqgcg{W_9izKgdT@=1^wc4A|jvW*leENtRsC?jdoo`lGxu_>*y%aP*_*#6SQ;CD|Up)_>w64F&H`}%O*ynB*t>bm;Q~EDCUPsK%I$qDP;&vtj$LsyfIrM4$ z7%&(-?%#Lo$Ar;Mj@O%5a0io{9d+2!GW5iwTa33qS z{?#})KVF9nZgISxWRDwJax2@n)SnS|F>W8P7nz(b&d(U{oa6O0(_0;{XPI#abM9fu zk`?C}3?HutKWjXWuswFX?y$Vb_^ehQ_wUKg^U&k<%AR#U%K90{^2P8;$Lsdz%+FEg zT*F|^@p=PO&aubcEV-YZC!6>4#^-W&pCZ4FPd#35X`DD-7ft=;#{GhRFF#(dXYUQx z!F03y&dKw~j@Rp$f8O{kxt|sLY=1#szbGCqWyocW7%}D=CY);Se_6cj?a=;9=K1FF zdYth!=4ZCs`0QS%|Cf#bTl?`9{eEZPziM3vjl=8)aWej+{$CT{(c^W_l6zV4Ad_R} z*(tt-~SY^7A$yxeJ-#%e!O1&b#a`KKL%XK9($};^zux zT+Pm+bH?bDeQ)Y{fF)bs6xV6{z?7?4a4mx~_L&{dGUqO~EAukpB70n#i-*e@|J8HB zjMMBhWAHcmZ|b?53HLMQA~TMB%RF4kl9Q}B!*+eV?y}(C=04|}`>hl8;J2-VBa9d_ zVa${rX6!O&k0lHCd4RnoC+e}Q-RC&_oMihf{aJ99y}=XpJd>N8sFPjlZ+fEM#_r8d z)C)|8PWb&_;<&|$dWL=OVs^_D_0To)**;N6jL#MiGj3;}yPNyxh=)B^Y~RYdzN;Ts zu;3bo!{TC>o7m?zCg+-uIZKAOK2a|+&6yUmGuIa9{$bDHte6ZJ;s+{W;>;$g;v zY@a93-_wuF*k{bn?ZnZP*=@=r^4pYIH09f$@cjeIY+tK<2m8Z5V|IfRb=G)id1c1I z>#Uz6?BB(DoAT(1dIO8Qov8OV_s34uqkFW!*NJ*P2Zk1>Y} z^KgE&b&W0K9{k})l4sOe%X1v^Uox=j|8bNW{tTS)M0G&$iD@STN-QW?W#-q5Z~Vz>>?Ezg)hVy~X%V`K|Ky@8Wu! z=at2KtiLILSiYEN_TiWE&6SKl>in_Ct!!WEJTqd+4(C}h_?0|=R8Xe|G|1V#&G4ydNm^^Y(My9y_F^RHT4fM4^u8ZBrdLC@KEb!hwGVh z3)|7jdIuBkV~_K!*#4t<9%h})IL@m97I+}X+nRBpj{f|0XhfF!ag6rAm zW=7*D>p3PYnDS6_|Iz0Ai}AUc?Z=#~w=v*OhAi0Q0xOOlw%*5{^zY}nT0bV-#+17l zJ{d`MJ&~gDKAgJ3Ppoi;S)?&w}>T_LIenPS!a~ z?rrYBSbo^!@Nx0HUc3x#1RvbFX8S^rE zjl41X7x`k$1I>Ldu*0E6wCTS4By~(w^3 zjJw(M$LhW6nZM6IH22?coWC0H1MR_zA@luBJC3l=kQG-o+7=Z9TRvUiodH}#xj{t5YD zvh8HOe92j@^poC)%s%Bjv-fH1XZv4I)+^7_?=#LZ%g>&yXPNix1JlombKoq0|Dc{J zXIOA6gD-eqn|khN&PBH8^cyrkS2F*ieP-}w=b=F+X?qtUOthm^ezhj=88}Ay=FH7!a z_q+1m+~4hdvd^jJ{`bt^+`ra3ZXqtNX6HKdG2G+1VxLQIDWBgzS!YfC51boDKNPR~ z?ZWz5vdiR0K8`W{u{^M#|M_ZleKQ%r(KXYEr*8b<_XMdk{u)N+rGXI6V zG28DPon!oeH!tH~s&C5N!yXT@;?S+s|H?UH#?|a|no()|=KinkKdb+6euniw;9N5K zt-P}Mop{gXK_BlJ-QfMw_=l7Anp^9C$Ud<9M<1tdWBfmP-?03%co_7>!-PXiwdXQc zT-}uaVjf0^Pu3ll9KEgajyUJa;k@~p``p3qjn0kw=%{tD;8u2ynUCRu_oJVu)aMj? z$GxAM`V-Efa&%I>Y%f|rOSb%cr0}$J!H7eCPEx{vDJPh5nmJSUxs|~gaWUi`Ml2a~ zo?Q;!L4S@gXUKx9SaO1WPP1alc4hqxxQ!uqGG@Vq2iRrB;IGzqN9$#W!QY%yrrgJp z^Xzjl(607gVfU=XdIQse#d*}mCgoipHG#_TiW@LkN$)eMFf>x2>4vBxfR z&N99EV!fZ$EyXix{I+=+pKZRT%-u}RF@96#$X%6hC05d4K!D-UFSWmINLOr_|$S2DS<%!Ws(Toe=MnPAe3ksL zdZcy?AGKJQ?2a$iOYd*oN81;Mk9FP{uU@P(rjIi(^GoFW0s23Fv5we|J*Nzxuvl+q z=ZW@*Dfh9;D?#vHTb3nC;ifKjYUq2kgGyyv*2sh3w7`(;)GT|zwoM6UjmTwg|`VjFE!*#LJ{WL2llRIOJDg|lefH}F=kxJ9%B|<%Rbk$;wH8~ATJC#$B4Vx;eK}6XOFE_#^F--xs2@( zdafCA4P#ER!x?tjWzOyFb2o#n-ZzX{G3Cf3&BGNexrP-x3_m1ZCfvp@cQI$lJ}br_ z7T3k*=Qwk&W63Tn&NBRn{bbC&Oxb77(MQQc=6tfl8TPo9eePuXQRj#4E9HG$z8Nw6 znDfS*TNuxZlO_A?eB3%8Ee?*edzJVYe8N63{-k!z{ZAY3G45~ozF?Og>4}@c=fu6*ellXkH4HzmKNHR{=XM5Pa2}X)=yB#@ zz>>?^o%8X8(HHF#``p3oOZJ%s7g%xV67zgne?~hzr;NWMP9|S zQ|@Dzhgh)vRP%C_y~2Jm{gM4)!JW+a%HM=}_c`xuUoYQGc!)g?J2QPL1uy$-8vF}W{nLW<2V!?P`Jj^&a zY5g2!bff2%T~4y(dWJ`h!<0Q1+{56Q`PpIXGV?QFbliTh&sl~iv}aby&ojg~aH^hW za??|F#xCdByZI^KzoH)}*t>;x?A-E{?`JW8`&7M`#jQ@&twelW%J%T7dKs%*pQDX4#t<9st-Qb{l}lGS3S@AV)3zjqV-%Z z?kAtBr`WsfRK17E3yeRd{|l{)>5EU*TiAVxJTQFqse0`B^39bjU#p(M>rU0%*?!xp zx@yWCzQTAMW3=T|J;Rt?b~(%NWAf6}bLa)e;~ECD;$r^UQ}s?3EZFYJCwpH#Rj-&< z&(*A$FuPXX8C@?AFEr0Dtee3vPt_ZlaIPu;+PsW8@gnUw#DZJd{tt08;xtpPWX_Zo zcQE*kys==#K8Ihdo?{FSn4b|7X57rqe_9ul--@HD|DC*dv_EKH8F7+*cG>>Dd@*It ziU*te8&1_DFA@JA%-Lgf_*A{0G5bu| zdYSPVv3Y*aGc?B{n%xXeI97;FP^HG|C2a4 z&JHJ;vBUDTb+gaCtay;^GpFiB#(%Z`my4STv)cIVaTohbPS?Z#tRKf151y{qGvOw7 zm@&WU>3Tm4_SwGq={j0(U$}-5r`YFKwr_E|?ltw?)zq_O!bRo`UtztsJY7#PZJ(|; zGdcToy_3;7r|SbvnG0;+>U6#AmGZ%eJx($jK3#8MeD3Lb2m3t8?yarsRo2CEc5ZXJ zPT9Wg={jfcywkq_!gwRbn=ua8FlC2bZeYeOY~TKLT`=SU##~^RL$5X-0~TD~c&F3# zM)sL8zVqpNFLN$196jy(EqFKUX2fmma3?bsjbo?lrT=1lE@RG!1=lp*Q@&ZU*SO61 z>~kpPeY9i6sm4${w(n98z25~>wI$1tM+>9gnVTU^zKFm2} zpQCS(pNBgSEI7l8UB-_%UC%ODWqfvcfCU%Wex!5qM(r4~%T+8m!Qf(XGhxaex3c06 zb{}Q^8?_s^zsecs-Xy+9i?3*Ix&f~3v71z6;#i#2%jGo}!zS(%(tlWK~=YZjpJjd>r>^1k-cpj9aC)@v~JzLD4 zBJYf!D$lG~GM(_evi&r1zeQY}X33Pn)6K{9QtM`CQorW@W!k^hJY2z)6U@1eooAY# z)w7)MP3oU*{S2R@9lKn|{PNTF?xuW&eR`WXr?q4LLeD3|7prI7u@7%||0SL)w%6IG zru=gGW%SSbzeD?1$}f{w%Lk*^sAupm_M@wwD_L*@gYq9{r9VXkNkh|biJ0vht1FKN4%feyHeiYtKY}%6N{_tCwrf8?wNCe$u{TeeeQFL z;U}$^Ikz+Zlz5o3YVLD*i+0=Phrz$<$CBGwF=zA{`_Gh%414zD{r2ti_JhF}oL5#X z*_qRh{T-+MyMBI6J4WBoj>$JYHy^Z)Ts`~Wk~bz-%Ljv9^14;L-w_WBc3EDdp3!dg zAJXo7^2v%_wy%|UW<0V!1Ko9ht~fQ>nfa2Wfa%Zd&qs~N)r|LfJ{eqZz07_gjw{Xk@9Nq8r8rpq%6gcW^3{}o zZT~)Iyx({ZnH=z5Wb~i*mobNC&Ck^gf9rk0h?^KQW5PLhxSJ{Wv&%j+wm$Bha4B;x zW5I|e*RaniR-9q`cj9KiS%%!fh>ji}mdqIb-Z^XPS+Tgm zdHjTae=skjL-N3c``G27rk)l19Ns3bKYHF7b0wocdEc_&4BLMeKLgIP!yU}Ir>XCY zzp3YZQ_sOq${$DAWyp-H*y98XPP1gnJ~y-CHn#sFeugZV@gRF#WX|@dv^y+MOc*gd z;(f^uyX>=Id*1pPv1;l${AuHIj0IP+eWT}#6+7%6bxxXkW-K|!;Fxp54)?RiK6AFV z8)v~eVZdb!88PA-#++h@GfdfKm$S^cgFWtH!IIH&=bABx{?)n}u*c=B7_&IxJ;wG) zaWmi?L+)n8{fyaX!q#WR!=+5Qj9o_TbArL5ePhU!Ip4HlDHnJ>&n?elh1(mh7?OE(U+|9I(SfELb)5 z9R8g8+Bs#PldRZbyLHC*BdF&VhTP7WIXm3Tln2@6A~Uu>Z(lgdf-6{ZHTz6haUI)B z&eR(jax3Gr&eSC{R?Ip41>~S~4!83JhPMHx4 zPBOcRI2hdYOx;$ETdbU@p}UFbacZdE#NlY38>(Q>QGrnI*Te&z;Tvku&w+*WBj_+qc)B2|LYw zZfBo4gFBq54>k3yntHC;sh(2|?r0u{+{PZ~nRD>#+HpB6jy$BN6osr{%tFubehkP){tX3mNp533gwX2g|DIKd95 znKEV0y)0R=&*ASFhhuDq`ZHk6kZT!nJ!5WS!i*ixG39P%+|QhS7HnN(J}za&Wo+Ni z^UH{9m~e_IXPB|eoU<&sgBABMxWD~i#Cawh{H{E6lsO~zImzGw;%CgwOxa_Pdsy%w zE4Fr9_j37U!jmU5h&>K|&v+bR#T5+B_kLi^DfT(r+<%aBz=Q{w zv30HSR*0YJ1@ghph1SoCt?SfZ(8YOA8CKsTGS;{isG@?QRdc3jD9-1-)g@EKcT6upd8? zmnS-J>~o6kC&@2Eb{TV)33sr=JuF!=T4TLGHa;U(>@a+?&li|*3;W!~>?xiD#!of> zPdH)z#;3_EJIt7Jj$Q6%#{KNE&z!Bj;^0zNT*mg(#le7U7;=gkw>0&a8jlr+eyZK1 zbIpj$nQ)vPPO{4mOKxCvnQ>TfJCkQP?~RG~2vZ(p#zp3A|4e_5vf>K1pJksIFk#4b zjJT08w=!XmDR(jBK2{w3`Ts@R`M^b1)%|~%xx1o+BPJy!)o7ShWTNKNBj6Ai1xLX+ zI0g39Q$NSZPcRG)f%Rbc67nCM0LQ`7OUb976CNx9V_*e11lEHiU^Ccr8RZ2=8z>)f z2lIY`ypNIpVCfaOgF{zRUaz3%RpbL00c*hluo(6@_I z^804=l;5Ai-pTJzW4C`v`oJ=90;~b^+VKZQz#(t|oB$JG0t|nKdiW~wfvdo2um-Hy zLb-!+umhX``@quAQVw7YoB&6_C9tT2bp49-gJs|l7zXohr@sLQ?w}okkvqvB`3)9K z;P3O~4>)o!?F)?ckZy1QOn~vP(jI?}JJ<{M+=o9f+Dkrxv9D2oe}jH~#0O4;^$+Lo}gY& z!h@^8$PnoT2f#Qu^fd8;;b)0g_+9AvJMekx16c7J;-4b?Z^>tHVv_nP_~1b!SgZ1_rZkn{2BRR0XPkofUyk; zzVkr3z)o-k>=XXNgjxbiuS%%!U-%tOs1C67nuHnwqu>%)aUJna6F*o3M!*Iz3bueT zFb>ASUa$w;0S;=Q%4ln}l0;Avr7z5|P9?+Oa53mp%0!zUWuo9dAYrzEA2sZo zd%)ZUaZuZ{8t!(bB_2fM)$Z~&YJhryzD@&$~5#@|Q>SO^Y) zrQpzK$zL$9gZu?c!Co*7?f~Q91ULfDfzx2#-|=@l`hZce8XN*6U;=CcBX=ZJ4>$sj zgGF~H)HE0aJ^z3Q3&3!k@&*UM1~6|c`Unrkg$H|u--bS592}M3cOegqf`v=`?xY;R zBCrlD1*2dLYy}6vE-(QOgW=B+Ufe;?KXC^Oz@pDn|G|m7DGzWO>;w~FADGug{g>Zh z;lKF(1@aNB0PDaw*a{YXF`Os;_mEz&6l?(_U>_I-2f=C3NDvQL z2$pt}KCl9;0|&r9a0na(r@<+(^j_=`*aMdB#UB^~C%`7K=u6lMFa{2Qd0(b{z!A{c zhx{JeAvgiX!01;JsvnGjLtq>n1uMQveksC%wP5T%>>(Hjd%%ib{DBd03M~B^>G1HK zUa%C5fR*3~*b4UCPx*oq;4ZNA0qPwX1LwpYG;;VZFjxqVfTdtjAMt}RunCNV?O+es z0}g=O!69%bI0BA?6W}yB4SKvjzQ>qQK`;!i0(-z(Fz+Gk9#{%?h&$K=M#1f19Na0` zPk#G|2MmD|U>(@=2>O5(Unl>-FgOZEz$q{WE`dE@o`HT~5G?uzdVm#RH5dgWU>s}$ z!vp96PJ?4$0^AMeeUo|ymV)^!@CO!yF|ZsQ0mI-lSPw?OOTL2>;F$2>ZgAv#gjKo zQ{G?9t6YKy- zen+{36~CuH$fvyi8@<6e*bSCW;STnI1xJE^puU57f5e`E)8G&o`4jH1BVMo;4DY60 zg9G3=SoA;WdlccpCNS^M{SzBo_8{Sh51=s>6zyYvl%U(4O4uOkc>Fwmt3H%0Iz{nlM1NMO9VCkKE)e=|% z=9SLmwNOM1mXoqCS9O&py=*mgekJ+rd2VKD85! z82i+2FtK8vDmk5U%HOB@!09951z)#M6}%t)kJ_igU|zvK)dUv3ai1Cl2j03*l~LvP`~u#fLV5Z^uf z_$~x;!2z%b+zF0=6JV@+pPB=QK%*Om~G(h-G zga^yPFc=1-U_BTEo548P0SlnPkA<@@3(OWi@rm=U>NKHBj4SpM!+~Y1rC5q-~^a|7Wn`cgQefw zrz*fOSOdnuMz9Brfkoe^{J|(V3C6)iZ~)A!0iPgVFz+dNaOj8R!`bl9?o+$Kyq}=o zIm8RrgR$rF4-S9>U~~kzV9^WY=egu7SPKrlNcn-$m(UxW{weuUi(W5NufQR&7t9+Y zeexTuIFEGwg7N}OUm?H1IJgTO04Km{a8BIEsZSq5AFvc00V~1qFG)8T1v|kZun)|8 zmGTE8;0QPXPJ+{5?)mV)B7QIi)_@~m1DH3lPsPC~*b5GUJHU!xqX!rPjXHR+5G?u) z`hYR85}W{Q!MxuhN8Bfg9}NGF{J#KyUz-h1o%=;VqgQZ{;jDdaN0JsyJ2B*O2-zl$)h<6FS!0b7!&j<)d%(%N{xXMB5iu?kr!B`FX z0G6JuIKPhnbMOa7!Q3eEfI%=(OZkA&^Az7R@OcV6=9P2vIi8%B{G4O*4$1Az@o2sX zr`|(n)O*BB=#YGYV*Xa~S8~lBm7mK^Ab)+JaN`mF8*^JdXT0h3cb-yw9IOr>0rPLb zpAgn1@ip_83-6Ke)+K)8{IwoCqYkk+9Y6PZhs~%>!mSSEZ}EE992Ur3?Oz|rErS~7 zZ|K%NY7OXF6UdKyR|g8W_(Fl;xrYS`39u%RTkStDdFv^`+aiCt$oxb`nCr|i>jJqO z{qikUk0(f8MDTmQmmg{Q>jObEA1ZO52+pWmMb>(Wf0M+&IuL9#ydlI@X%#m3&rYep zSr(6{g>c2BwaqQuS%eD(@~Z;5Vbt)H^Vg66Re3Y&XW}0{wtCkD3gf~gC7y6){Dd|mO3IuCu!JlADz z=J~(Pyd(8heyIy)70}^WmiDwBZ3ZM2tF1yK|e&o08fOVa=4S9SWg zO8Vnc&Q|_Q(r<9&Kl;`XzJYLqZ=X@0SwZ+~%cweBeJ{7jkaoHARn4TO`+R@R?7ELGNl zR$tp8TUN$bY&Ev|?vm;*ZB^p>>dkx9=X}KT#DvhAE9b6V?NRrN9=>1eJh{tzuB3^! zvDH|=JpNFim3RM0U@lwyIrjK-vc&%=?fc7wGoCTyFXVpE@Ob(>GwNg!SRKeepSmoU z*pV^Z#@{)^dxOkN%7CpE&m!F8E0J>-awz|dGN2A`q3-E2==Az7GAIK$M;WY>GH^-D zsfVWXveTO-EuSPUCrh5)W~N2jMLT(0c+!lzO87QYw{22CT-sNYcb99sNT$`6w-4r~ z^=WEbxn)J%*y`IxK1jKW?JWM}9`#PpC^mJGe99;966kVe9{kbCe4?Ff6I+u}S2qT> z`+UDXBzs+T$*03@dTq~E=e|uoy#-y3wWe;h=r(|EqaT=2j|#sjsoQ?q(O@tf7(Le4 zkmFj8SS8nTy!w!IzKtH6r5wv{+oR5uyqm&~aY^2VY2TA)%&31!TY1H_Zy9wg-Oe`# zTD`vOy{>tO<#yHWrz_LCwPvehzoT3`32(HSy5)1fn@%;fc19h>y2CQH4d&9E(%B0(wFGG1FjLyuFEdC1{)`1mkGFfxD68WOc|TdH&c4m*h-O;z`YZ9 zyI%QJkO4RubI2wBis8l`TsfR^>vDf#xH34qyn48>!(TI8i-YTc>vM2DaH9@xJ6r

d zk3$G9{bB`mzl=C8;NBdEtdsFXwT%5_^vMv8VMxe-Y4X+z*G#y&*BGvZumfTgJ~X2S z0dp*@+a~&Zt5OY;F-JAdq+R~FE zYmCMH~ zV4nLmj6=jeaIT5 zzBGz%dfaLo?`~%HrRy1O0OwKsCJw@{KA%U6!+35lJl33o3)Bc z9A>hR97^*pcIG7HaUWstldwmaVXZldeN0^DUFRqythgBZUC!oP{W)>gkax{Oh(0yVzl+=l?`{N4?&eUME5JVE&*KR?9y_j2xyO-Vg< zUECmbQFI9%L4Wb-8Fj3|54Uk;y3d!1%|EmB_&!6F%pCJe7pv>u0P!@@4$tAYSx47O z9X%@$Y)REovDahx>&M@_#2=T`$K7x{;jR_}9W%;`epvEi+5B;>pXR;RHlZ}_?J6P> zdvJ=#;u87Qa0PYgd9bVeGfnyAUUGiwD!&U^)yP__rJM5m;o9ML3+KpV$6O~AsPT4X zD^+Rhi^wX$K0Iv6ihI3vbc}2K7whivV=|Qx`(AXU$Fm)OAGZ9trfI|SER%4xgd2a2 z;ZVsFC!FWP8FiAx$0b}ZT&aWG0as_^B+gxM?QpL}SCQ{|9sLU7NV2|!D}d{V)9(Bh zEP?BTW4fs?;VR&^!`a6QHExa4C#2^=6ky*8w6 zt?oO_wFT*A&Gnph_BDbM!c`M)k#KSDoi_Talo{RVug19VxZVmHE6unh zy&lSUPeX}FEYl&NIiCnh0PqM{bpG>aAFX?)y*^2+m#KRTh zua)?t#Q!+=#+_#T_VJaj!`SAwl_=4YxwIw|MVEYkWGS5o_36=8I^R!E>TSk zfqoRZo3$Mgxw%Xp8*q1R_gU5kCC@4eH$b?TxHt9DV{-EB6DegF2;Y(%Jr&}u`SsX~ zxH;A|e)fXbb8#U596BAT51q)1;C8>*h8p{NHtWe)0&5Vq-tVpE2MvS4*=oNx#t(D4 zvDxowAm})9c5agn;gE#HkK|* zSfQkpOijqIE~3hPBlf#%MxA@${E{)8j4w!;V`Z?~zc~x<{h_Nv$S<=d7fCvQrLX8k zuc0rd*ShcWZb;4J7H+9|9XE! z-(SIhrjq9Tp9P|pd(nYla*3>R%4sLur=;B<^}N?sSM~gtd6w^K3M4(z+T@r1s!8fg z&y3n7;r{#+%U+x%X>wa95__>1F)mrOPTPxNbnE%*j2aci%rrGvs%t$bi7$K~b0^Yl zw3+c)dqlcVuL{&l<(K+k&IheMhC=eHy_fNh=y{=AxZWJEvIpGcFGqzM!bR_={z|xt z%y6MVXO8bs$(U4(sWtuhF|610&8Y86TAa46Bh~ho*)}PQorK#>xZ|xhXiuMa(iZND zWFpNnOb~AJ!Pg$&Dhz7lL$5u)C}mau@QfOfvN-)YUG~;~8EeZjH}A~x{=}X&YjmCI zBivH|jKWe<*UWI%dWKt_k@@tW-Rn%1E~7=M&)=9)c_MF?n&`-D(ZhGxvNB?{McMLK zMxKSgIisFp?bP_88Fs&Gyu&%ZqxQ4LD}6{iG6#3CE-CqP`!annkl6j_DZ>qxsmH&R z`QV!wCwqY;37yW;YKFO{M|Ds0=VG5p}QSG^e`SXu6#tn{f@W#Msj_*46d`#&vg3Oxd zEIpnyWv2Io$wwDGJmh1=Pv|ElKkhjQJ=hm|`~dk;i_HEpOOM+RtcR3m2LsctpU|9 z9!vHbj`jBf)D`^?yK;|u3^ZPRmR4ns4I)9>3u)RS1Z~V^9#Q6HuY1`%Vx6B|^JR+; zsjs{kw(drf) zHf9VzWYJ3I8DqyXzWnWs`Z)JyU9ydlS>v%PknVS+Pd0-1uboj}lzpp8bk=n!yh;=`wNH$12H$m)+a7F6T;Q zP9w8*hII!i=RfnXJ^5fi$DrrB+R~muc1k0n-k76gZDU17*>TKw=B>Tz=S>+_J0vgT z#wOWEF~|H2Axo|d1g}lXNR4TmiRS9+gxEefPbJ`JN!e37*( zvEP@O`dDLRJvOLvj*%z5zOh`_GEC`La6ENzA7^t?`Z?Nf+9t5p=vyr&A#*aY&M$4T z9$9fUqxNxcYm1w8zy3_BE$aN0w%AEMiFjt!EmU4(gcmF5x|+4zHwHR=a_q;YN^aC+ zok`-)qivrkX`w9b{o&QA=r{Pi&!&FJuwSpy7g0_%{#lhrSmSC_mkb-O2W`)p1~ye) z%(bKl;e&Zr_%mKxc`*Zgz~7a?5P>#T53AAGnj!i^J7 zr|%y3aL)8epD{w`({$9Vs!O%otnFqq)4p-nW=l^lcBSwH#^nXG>IQ>6KGxLXF4-H) z=*KNP#d(IEVt+FYfwsS*Ulf_!3uo2uS0HodDV>koWN*@b&PUr$eLQ02ip=Fcy)PxQ z29Z_!7E9JMNm*6dWcjkmlC&D++tS-+)k@K;+mv;e7g^cT%7cLC$=RqZX=NDhse32= zSIMkx-JUf>>&&6V*+QICxNoAzH`XQNbSp0_e;IrAxf@!YCt^FL{3qWztF9J%wIV4~ z+iun*wi+4ZnARL!2{Okq8}+=i=p6_xomDT3yq8!>)3#Wzzc}Wd*0`bGSK+SWdVf8l zqR8uf_pHj1{`X;1Ud9+!m%pqwv+qWBnB8xc-`YA$F(E$#xn(P0)mrCK;;1N_RWC>! zHzsv<vlKPl5~PH43) z+Vd~=W4lCV33-&hi zetLc=GMl76MP}7!rMzD`2$_`kf%0)DGRqrg)r{EhUnFJjKOZ03kNp-s3(3c^E3C9X zkd(Qf@;->PN0GVx+F3O(X}|LzWFACaA4X>6M(Vnxy)!BEf1mae(q3>9>#4C>^;_<3 zV|Mm*K9m|HOJkFKs(cUQ)LUoO8zoIAnek`XbUEqiWj|=yx>%;n=(I<6`E0B?h~{Ud~%{OqhMm-e*#i38Qwje%b8ZLYS78Gx?;WwfU| zXVs;LA@7N#y!2ei(Vyy>i${Q9OTT1 zbIGB~FKZgLr%-+`bIw}?TW8brxsAft@0wk9ma`Qu0>?J6zQkXbgtu{QVR-uCn&FNY zUtu|yTdPmJi`!1zI$YdpaGNAek?vWwPR3bBlP1U7PI?`|8pHJF_-=OZU$UJ^{=lp0 z{O-+mhX32=TYKLR{g=*fspl~=DE~{d>Z{xvUxe55Q^v#AIyMKlyqxh{HcuOo6YmoD#wljJ?rRYGc?{OOyf3=eYUY~~ z&mxOg`N??ZAJ^%VypK>Wqr{`vuzm-hHSeu>uy>!z)*&I!rLI3co=*GrY-{i;auhX)xmYaT_uEG zXQGYU&-O7c@UAw8u&HGOYM8G7gxy8hBPA@C*yMgH%oyDJxi?NA9mtH!GdTKOXGHHW z@Yt3oh`U_2`tg-l(s>k=+972oGKa~R4WRMcL7gwc<&!T1$#{MSpCulV5#{gMq>M*R z8TD|r$mmDLBitJgm@@43D{VJ*xvytGxzRO)!<@f!8Jmlqa@o@O_%G6FtaMvrZA8zj z31b{%>RHWw;(heT=s6?8^xQ_dWzLu1;_#47s{?}#|)y3+klm0jqYxyg~ zX$pTO_;b`nP62FT&#}_G$+R)%R0|y>Od*js5at?7wzck+3PabOUL7S2QFy|H=_Sl# z%fqmantDd8o+Gu(7FG%BslOiL-^&-()F7@Ci5KG-_PA+KA(6#adAnVoIw9F zbV%-}yTo6XJhh&(LF@e-_O~y6o8!C^^0p6Ibu!NMo9VH)X-EF@44>EcsLTa2=RbOV z)y0?dX9VJKK!z;yG72VwE5&H zg>?dphlGe+{MX|@4;jtk|5Nza_LSTfyWMnSYCJ82iy(gFvg!J}pQiJq$!@z#(f1+z z8mF52N?aqv6(p|D34h|UxWwkLgi>gp@xW$Dx|{gBBPnNPT229-OiNOZ`i@;^S2=^o z*`1X03jVpI{zX1O|35sNdd7FFJcHrv1KCfK>c*(Yv_I8y=9(sws*t9veqZ(; z)M@_T>o|yx|Dq4nI@YIk^qj%^%Qt4#qbVKfL!3I^k*Q-4{}qJorA$s@?q{AoVv6Vs zllAhHT$R5Jx*k76jLmDfH`@v&Eo~*pIcBMj_zc<3RJP!)i?SXk%su3!@zi5p&jqPx zRo(h#X?GuSn{;i^r<`A^4%FL61X<3AVfCAcJHL|sD)Qqw=|j#+JvZd2XI39l<2zsK zS?C#-51-{h3AV~Rz2Uy1z zoBQxG-Q4q}HHQPrE{P8LeLqN#?I}mnXSR5~_b2u!NV$Iu`~Ft$jf;qjOLXW%hr&l^ z)rjQJrYt(x)6|(G&6N?n<9s4IMBOt^uc3<#mCI&-FBwVO-_C6FiOHyq*#Epr`eN^| zvIrAfW2_~AsV_$`k8|3YblubQW4#`6!b;cN)#D~<=e4V8zrUGPXS^QSFMcb-c4f%i zzQXsT{j3{Ge)gl|&cDtsd**Wp&XYfvT01D@=O|nroO7K(WK820boiHrk5U)^k}pNL zRpK_xy=lKGFRTC3X9eV8I?kk*gl*jw$2?)|9heM5^!PZ6ynNE|l*psZ>}LPvZY)+#5f@KYH5t|79=} zcdW074Je?TI*y!EPe?fVsE;1w(Jz`~JT{E1j>{V3h45eax;b60#?49psWBllmbCw7 zWHzs-Jn?^n*nxE4mbRy9JFqe9+4Ob(3*1cHx@4cXY!+;e;iu8l^N(3oYLKp}Z`jvk zsKNIALEXQdB^BwT?(H($TG&ZHURIaf->32Wv-YTGWxeS>%cts2H)+7AE4lqjL=ntuha$5Gnu z>il-h^Q!{GD}3K`PqVgfE`7uYo=vBDc!hnaLHwed)Wx4M_F^5zV|*vATa0?pt7g>~ zSHPcO)=&GKDy^HfPc>}#u6EzI$aZ$|Yd^B-mTfIKaN!=cN_6{2TDQU~`dwpA{Z({( z|1#b57%XkWbzNb@ex>hd_k7E?PQAje+sZ8Y_E2PxwlT)h=)e`^Z^s7a8?@^umyJ6& zV`pSExlZq0jiY~iA#MK{^p~)Xa?jA8b@nRJpSG7VY9LOr!KE9q->c@-H@Fvh)_gj> zzCl3dbiPnr5@yDt6J2>P|Y zZ%)0-vdQ+k#Pj`ec}J1;eiwNkjjz*vETqdz+D;MqTz}e}Dij?LOX}#>cC7k2XPr^t z*>p!9>is{_v1t?eSTU#Wk;PJ7COXafojHy&k$IQzC8_V3Y1Z~fE89bBl0J{c_JV z^|R|z1Sk60bt!}EamWk7IrXc9bLtm`bL!U$=hUwY&Z%ENoHPH1;6y)r+D73d|Loip zT#JKSf@^kgd7G)9aCx@855h$q+$y+6N7!n(28X{0T)o3z6I{f>wZqjpxNf*wxHsB# z8L;HpxnVfb&(4j(In%ov&Y9i>oTS&DSNUg|^6XqOoTS$tM>(9xvvXlMDGxhW4=3eo z=bGW9zSy}AxIPEh1GnA5ZHF6ja6934Ik<7SF$XsdHwkA?n{4z>IJg41aR*ld=ag3g zC-Us+sDYbya1C%v4tXtbiw-UhH|OAbEqQjGcfjp-#IehgXV+x{&eOhJzd5)(2WOnk z_|?G`!WBEXQn)e)R|!|);A-Kj9b6+^t%Hlf)x!mBb-WWUV&jT&>w~Ls4^B1DAnH;%tGB9Fak^ZkP6 zv$f*hfP3y|vb(qAUcSuzQdY0e(a+{e_&(elv$*Huz7zLu+z;noF5$-Ex@?^EZPRd_ zaBIbr-b4R*Y7ag4T;ybNE5>aJHyHz&;ciTYtHiBzOQu^XUL&}5o@YK6Bjb7d84ejL z_1U(zMdvvF%aHX3iHA$LUbqSew*#&kPSPZo_}c|n>+m-LSMT8F;Gz!BsKs77r3)k=98sT<0gPxR8U3!qqsqR=5b9J>D+3Mu)$CxMn!J zeH(&nb#S9_aR)aA*X`hz;QHX~@#cMq{>I@i2sh;5R>AFZaMf^Q4lV*W3HJtFOU-)P z1UK#A+ToTQTsNH2u{`VmT)u-FhAVP#V{j!7Za3U22bX}Wba46S)5INIFENc}Mjf1|j{e`l6~OIw za3yey4z2>ubNgKC{efbKYT)v0ob+D}aD{M8RjmGIqx4_Ax9Gw^A#)5~<*V1(;`pn? z-_OKfdVa~-?Jb7b%Ldk}Z2M$7%rIf5Uqcv%{+>m`bl)+j{IdQ;JQ;iKM8^Bf?lxCi zQtO8yzr3=5otqTHtWMG#x`6s|=kk20gKL0$GwyPUzbIUj!(S_0%)xcRbvVNI!*w~h zA-G-#Hwri4;HKaP9o!P!PPjK|?M(ghE~I`qxFFnwgIfi++rd@CEjqXeoF~3KjwZM~ z2iFc)2xrf$Zn$CxHvm`W;D+HU9P-BCsvX>JxLQZp1YEs?%fEfw4ETr*rhoU{$Oq;7P;?Sy-aX3RbL9=K6BrfT+fQxgbYlxjCEIPJvW68_Tf z6Vht{>Gf27mP@Zu$h#}P>$-cf&M$|&SkZT^T!iI?5H36`2GW))BecJ*>A3BC+oX%-9&J;>xWZ#$P{n%T07m$8Oaiu)|=DN@4i6LtO zS=+xbr#Rtgtu^hZUex$r@4g(SAIL7xT+-q(n{o{>t&Ej&fwDwSlmUgx&q6 zIrVBPU$U<0QI0-OwtVrv;+lT_9ERk7dpzs)E_!Ce=FZHy%p7H$G=2THC5BFP7zwBC1S+!q< ztafB|Jiz=-%2Mw&=`#|nD|;VF?HyJ7FG$@}j-ta5;p+R+w#UBS>sU+O?|am0kd;eb zwLdtgj%7{VIPDA8dO3Su*89vI^7L~!XS=VZ>hoQqU*r<>8~D%l6Fb|FtoCotsoPkK zwyhgGY`k?YgSG9i$XdDE2>^midY4FVU)7v?4+|^CeO*?1-PYf&wQZj7xbP5HnR@+Q z=DTv)_EnD`NbkM$W;@SzY}Fn$N4XpCLkBL=t*f5;RYShP7#AgVOSjRCvS?l5`;&X) z$#$;c_HU$hYt6Q2^@-{|>L$_ckhE@PltbP*bLwi@6F4DLxAdNsE(`W83}3f-9ug(Ke}7zo7A_F%b0I{YwrK#x%$FODTnQI>g8PGxQaORUPb!+ zm7~84$#ZGP%DD*oK*tC|*N;NojDoM;il^D}DbAxYlufM#rU&uYM$5 zR+HKG@IF92`kU0FGty<%@=<;dt1L!5T>vOJJ(bJR=d?&JUpPp0CaBo}BlhYYD zVKaFz9NU77u*B~ieh2WY=UCd->a|4i%ddA&Vbn|+qN{tfyVm+MiQ)@=&is-I=; zMB40C`_oO^y0c`TY^$`{4HjaxnTZ@(P3 zmzJO7>4qzSJ6=55p7+V;=MLPucg>~Vch8X4vx{==gL{s9mXw z>bvXS@l_~Cub&#kv&$)Y*GJf;=digVj5Zwi^5zQmxg{@#aSQ*1F^;%#2{#5;4QF2u z-VGOm)A?!nOTg7Txcn=akHd-1a#iwI4A&3$iV!+}@-_Lct@Nqlb^dyDgu?R)ypl1j z*V1`Cqt>Yr8KLKw&xK=fH8!pq@tu@y1nze3je<_EXS0+e{qpiNsrudL>z-jf@?-GA*hcT z_JIkg_dAYEpjHsfvy*>?lxs+A3-8Ih%eyf(XV&8sJ!jT++52;IPRY2LJSMNHADkqE zWb$t(H~3d)l8j_(h+-A_)`9+;-$}Z^6Stoua~qFW1$qzlz2nWUEhXF7`G0!T_O$XB3|{|GWY1W%D=l6Lbf1N(zZrBHM>~-`YgX={Xwr+S@V<{?~hz-D6fX%K(CAO=e4mlsblKG|sw|bXeAKS#i*zJ@-x!dqv`lxPXh2DLTvg*AZfyOffO_4z3>Oj4Iz`;!} zk4!h76==FZ`yvi03lDjeD6`7q=GzatEXoPDc#>5X7n|X-l|>8DG+sx&ICI2n87 zeVpYYICtfZJ^H=TOs;&<7el4=hbc8%N6Et0IuwP^Qwz+ys4k#xemR~ZRyvW;~UDB ziI%(s@>)-yS3gh5OWR33k59LcketQ)sk=NqH7Q z=~`1{(LwTZ34Lle&MW=)1Iov4e=wJAD>kw6xt0VwMjbLfTSoEzY)M~rGvyV|p1v~t zMDbI7sPn8Sv2Ww8mi*@89TGEl5BXBzMU5<2# zy@?^CGeSRZ$*`~AI@ZzTT|E~{^K-N7L1gVhR>Oz0+Z2g!8b383nODEE>YqKn^m>-I zA3T%q%W;oylRt#4a`JtueqMb7-Ho<3-B#`U$c$&KZ<#Xu%VBAy%(0Wf-f<0bT9H$9 z*}S?;+I)>EXFqyzKL7Q;8{O>?EylFtV>hB-bY6Xy-_mBR=VbJE&eD3xgQxbbl`8)& z%L#2Kg5+5YSyj2{H~wkMCh57NUYB#&NfP^d@5Qd=v`#;p*VKZ%=6ThV(hd7&|Nbil z%-;K@C}$s=t@rbiRy!f(IfSfHWN|Xv>JQR-rPqjcTc#!Xj&?84kYCnMa_Jmb-8iql zVATz~J=9}U**Sg0)GD<;vChw!OVmm+)&AxbLn~*{$~aMUZA5-^%e?x!EL1S>$SC7< z-$faFy@+wC>+Ahu4|X7H{I+@ZKJGo|NZ(4&$*sgQidzD=Z!NFm?qy}p>)o1^F8iXJ zvDcr%?xgaB{I#zqiP3&0m0oi;B`Y5eN)D?_pA$vi@Tce1sj@J0pgfqQzm>i%Q#(Cp zlC~q|zx^(35aUgwx7Dhf=`n$$O|uri!gq@MnxpQ+MP@$vH*oj7I$LDkn3SpOReElR zPS&&0yxD1m?|%0_Sg-Gk%m!o*-ZQW6Cf&ySk}}i#W$C)AZBInraR`h)r<(!!B-FTvhYR$LN*Z6%GKz!sx!b^Lrq#P!m zn^#wejN47Uw#hfZt$kK)Z&`P3Td5zvkO{7{C(vaovf{|{ylBa~^nkJ+aFLa&$K%K< zetBL^tVFN(C-W;kzD?J;Dzlte2Pl+s%G~Ie=BwLp=_hG#q37reB#w%?OkrT)- zTVTE*Hdpt9q~HFoRqBhpQ>E1_Pt`e#g)ho1dIKG-p?C6bgbuzX-s<(eF3Fs2F_MlD zla$7P&a02ENTwrgFRGpIpGL?g_x_s*UrLJKWQo`7CVjWEwlY7b){`#d&Reaq2J6dP zebqjJo z(u$6I=ha!PRi|{!sW*Gv17K{d4*9yH{Q|!MH3nq;P#7 z&U5i}9*r8dy_y2#m+`j|9hgpfjOT9A`IFE6^rx6lp+gDxa!Gl5TIomOUKacHP5e6A z;l|W+bGkhB=WDVoPUvQeUq?=4w8uVqcWNkHj6rU+|yo$)KTeZf4x+ z{@bccj0wKz-gZ)Z>0R&lc-}$1y&w6;yO1gGU34*OVkm?7@w8DN{ynd*MOT?CdU-7e zeUA5IfIPG(ev0ukn3&h;kv^i$duAZWv`xAv=}g2=HGYhJ+2u%{NAXjm=G8+Im+ot@ z6Y`DOGI<}CH3?)CFKwb5zhj;S#aRGdms8`>&KzGUd0_vZium7!|5{@~ed!?mFX4aZ zAqOYF4EZJP*g5~f`Nz9n!-)`PIIxgfAI^twf*XN5Mm%u|*A6!h_YTdNTsPbz+#77% z0G#KDh14Eih@Zo7MR1RZKmCns#(y%eE|aZqbNVT-)G1u6N8?q`jL6<5FM*5V`V8EN z`CYkO7FFAkGcIzpP0(u-%$eE@z85T#&XZLH?^S_9qR|^YqI)IbO7j<%oujRVD~HqL zbNqdYKDau#x7fHrOP<}|h$YX?OMVJuYsR#_QMg|G{Y$uX9y5Px zGp;wQG>@Bx{PMy8mi4cZdy&~E@{e3d*)0h!aSXw?!siLWCEO@n4DM*nkOpuHt^-cj zIohC{o|I8TNr<4`Z4m>zUQsU%8FARf8F?9gc#(LjGtuL?MP%0@``a$E^;}n8inkh3_K>V7G6lCY5?b#whdQu7$V=AkBp-nv zcHx`h2OYe$~To?*D`KZ=4#$cP4&~ zvyjOpeZ!QLcfo>sQTR>pjNSD&(e?Mum(6U8_1Wp)CRe2B(JC7IdQdr zxYOggoV1)O?zDf&btxy28^o>E#Z7do!0lcaHxVOsDS~??IvpwmSCGFZxC*#ygwVF; z8kAY*XPBy0Du(XDPX~TJCw@}v*O%jCgP+f3;oEF?L-_N&!LsA{6RRd=Fb1Cs|9UHY z7+&<5hR=szAv~9Go;daj&fW$J;0odL#GOn0mB1C)ILVs|IFaYHb>dcM`FFaBtY+Lq zeo_zd-?1P6eJ=h7_v3%e;a}q1ZTU}@g>VTtXMW^wHS;6sPx7-EPV)1u+)H_|*QWEi z0$#$~`7*rJ!b|#M;$OEl{a#Yt&q>e1ctR|)IXI#I2JzNGmGO&)a{LdTwaZX zfs+*wv%CS?!UQpkYK*a3*D3TakN_ymS z$O$Ip_-t~j@fY02d=EJc7j;@Pp6`&pf+s{AQx>MA%e#saf=7`REL>2nB8&4fY$eH; zeffW;p9NnMS{2BJ7hUG~i@h_QrgDA{5Wb%93*1Y-TIbtHojf1Lo|R;7SB5US&y(^m ze&d3AfJ$fFa&5*uC%qn`*WhH{>YZWcz_gk!(S9;ytsavM20WhMyLQL^eqeOY~kN;#*?uxV9nK=@FV3hL_cvKvbIZ@qfJ>-raj1NMV3BGcMQB~oAenS z2`A6@9!(x;9sFjveE6}uu(yPJ2lsLbw;Qe)QVI;%5jMQWs5o>19#f ztn)*-&Ed8|-1K-skCAkJ(D{RV@U07~!3w*DQmWv75xxa}PJZ}@?FFyamyuK3)~`{*4^Ub zaAm=?EtYcVg{y#b+EHC^aI3`aDEuWakv#@q4WBG);daA?;PNbwB0FJ)KUp~Pzs<|| zkz|UST#hv2Zr0ZzZeu6xQ74Kg>k@s!pEvbCG)aO|4;nh@KZslAFUGY|-FI_5PiLrv z>tiwI+sHdo+&_Xlm*~)gynMnPFN9eKv5cnMAa2DjZaV$AmE)G|(?r&8I7z=VAM0^5 z?l$vF*4LySS?iGQdnWbzi_MuI%&GRXa^PYv!sb49BXO)ccES9rsM1(@|u+pHeKv=ZiA)Nb)B0=UMKRB{jTJdeEd2NcZl3` z30DBuVdEtKO5obzocSl^7qa}H#J%Sd-aSkMm2pgjJl{^3capEh-B(&`ZM3!2K5?0@ zAMN;i=J-8|VH$g>_!IkRnD6lyefp5!eVjEn=eKZ!a1C!;K3^SytA|V0aj_Yba1ppq zac`SbXd7Y8b;{%g;LG|iUL)%4zg7{v^b52{WL;&+vcJ#4%g3{=9(^s{n00H;dm9Kh zPPp@3!f~F*@{8R|u2SGn(%3^dPjNau!fl63APW3AlHd)1h7Qjti z#4<$j9=#@`!|_19)P)Jcc}`qVtb+0lRl;Fg+{YrQ{K?E+136G2c_o)^K6vI0>A7d6 zZQDTG>@xg*o-{~ZH7+;PAmPLOMalEI_bljdGa2Wa`eb}(jxk*7z0;JxoMOt{LNebw zT|?V`FL5E3?|7JTNjzf^<0mhux3Ly#oRN%2?|o^TuE%=mc-C{=k`ur#(J;}H{2jYN zY|S2Zi^OwuI-ZJqum`6us0$>Xw~W-@nMvw!4myZ^GQMo;AbHjN zWy-H&L4AOG&vk)($@31lDL6eIWP!QO%flq>F33uggz3k<-~(wJRs%Ogy~v0AlceP= zGc6hIo5s_&(mcsWr$-}T;=~9#?7z-TUy5qr{!%yFcMW~KEUM@|iTz8y3^Yw9U^njr4p$^})& zyTe;?s$IrW@O@@XhSJ)w*d%{MU2_=KPQD(N}j8XK{8BX901 zf?tAfhW{w{V&6DHb)jDG=Vuvzh2+^z-2a<<<55$`W#heDQvWW zB;I1$Qi3v(F`IFW8E>Q9lNT))uwE!@Bf?YWb~~M38?nyC*88StYc3-ko(cH2tAjK} zt%HZYU>qI3CGr0DQe7`ZhYoZo`0#@IgS0#053Iv4-F4u-6f$Vc9Db#AU_IO8CI7V! z51TsZG9mw4FI~{z;4y?phjgFn?MZd2l(^()^h)d;a>SMxThelBkyCh?)i>YZl#|&v z??6sjQqJbIoF3$OkTWLoKj@T`sZRnqOXRKA=gnz3Q^@H~>T|eLPNqH~COE@MIe)lB zmr)Hml=hN8$hlAI$t&;&Dx=mM-wEz#<78;5^RR$&Zy>G*C5_)t$JI?-I~uLHg#SN? ztChIEnvClnGcIZC#@Db%S1+jN(aUpBc~KOT6gb(b(eZO z%wHra>(}+Ve0BZ5pZR4{rx)Ot)&ESLqF0j_NjVRia>Vx6_Mt0sF6Q3s(|H&+*R*?0 z#4&-}X*d}-?LQ8Y)r+j@EV71>Rd%f<>$C&QnnqS7{_Bxz>81DaCGEKnu)dL$_0Ny& zH|@HAqr!G&kyVSV@uaNB_b*HOqIP8M#(zKd-JHM6x7K(8CO^eC_93t3I?F!aXv+Kl zWuJQ~r~0>8w(O};eKe`xwQ2qOkUN;v?;}aMdJN|}*E5*7RVMX& zpD9P|_cU^Pk)!SR38tL=)}w>4-(%FxsW)5p`;c^8bq_IbZnfeP{y_ERAmXZ|{NF;l zNQTGw)5W^{B;R)uSMHVt^^YV^T+8hDeJT50<(GP4+H%=O)%9X@g_-Ztu8pTnxl%8R z9>yLb_e9|zgFisMh<%)~W-{MzPwUr!obIH4H<@x?i+)Xvr5DL#t>3w6{f3ZR`B_W9 zwFlO(=z8)BIlA5+lh)7Zr~V`7406=)C-q~_V?S>qduz0>W$UEuOiF(eT*Ddya!-|f z`tL}pT}6eptaMg6 z^~+3WANq|WN0-Cf(sJU+@!V;p^C+jB%ydp8r#C6*FBj>2mpU@`4f+%GIfr|zpJTpl z_P=w)QA!-T|9y%u%gSVV93m??K%K_Fo2-hjV~>-vZri`CFtVb^>cszq^mD}PZhJP! zd361KhPS&9@b!MUl;;rf2e&5ctr@>-d1k4%<000gk4e_s3w8d9+ye41_pSwVUGcZ@ zI*&M4n0!YCi;`B~Qte-xx=-1Yynx5E8M(Tj*&?>25%7`RA;3=O*Q5PxnsNLF35L>7Kkmmy6sFe2el%&Y|4P736Oit_Y5$Fl!xO z)|ytEXZOxB*CM9y(~X}ur2OdNs9sm|Jjy-`Zbyq7m(=+Jxc)5u%WxZT_z%GiX7OK- z+mOS5Gu*B${yT9Sarp0p8+Y*^MAu>5CUDzt`teiu`7G(*F8g$j^usk~@n42p)Zsq_ z*OJA5J#I0F|7N%j7ynXMIw`9-Zl`l^OrLMJPn^Z&;Fh&pCJR5(XUyG18!oi!%1-=q zNg0k4M>}%fECl_1oA-P%f!b}+ir>vG|1mRuH@8yUI$dOn-qqxBS5oiLf%TT>nRXp* z>0Ol8yK_JB4`hj7(z^>ciQlPrK5kREb>p^~d%1*Lf*W&idEdtEZfkyI`3u5D9Na3n zez=GT;}Ut*aElHu0$16!pp-5Z(_a%@={=m~Wy~d)lwmtuby_{%YY`9R3>N+8zF4aIFr1op5ouCuEGkC3)HhSN_%I+#uZ4 zeQCQT{zl;D9NZ*a!oe-V<@PQQoBLhrqJt}f3p%(mxKalff-84$b#NgE7lo^_anf#E z;UaLljPzcK**A$>4{r5Y+y-%L$l^AJTVoctIozUI-15Fh`DSq|!L2!qTL`z7EN=C< z#j?1?aBFpOld|l_tsOUezdQifnZ^Gu+`1h8C*XRs_+P@U&*4As``DK({!4J%f!hjO zy{r)bUt3VWLtnXc`-h9)zo33#QhL9q0j~Uk1%=7C#wd(^`<2ZW4ggzz8EKZ|K7`*n{EmoU#;Dd9(0;b1#=FHamtpN%!Y&cEz0caC(_zzR z=k&Rl4QO{oX4oKMR}B)Lu;)u&=)RSgeESY(v@Qv$_E$pJ;b+xDoLLY*$hW_H82Q$o zoqT?GFmn%y)!`t)dXUli@Phg&_hvjY3FTG7W;C8k=wbYX`xn$H2aMM=qg+)lVp2vKe3zMQuxUTn^}ZbEV%j*P zTIu}@XMZ{QMVYgj%#K8mFztjH{6>1ew2d%j@&*7&CwU?Dq8~q@fd%uOY1&St>JTqO zwCV-&#P1k>cj5O(+)KV$&lSi!$*to?rd&z8=fAKA-&`=?G0#s)LjhbrT#1#Z_I)p@ zv`^a4FH@f+Of_NpzIE{FE+R~fFr$wysNZuh=c?Ph-uqeiT<^awkSn_N;NH8PyRdm5R()?l{fc{CA9*}p-YpU4=S~Ta z+a@Wm^m%iu&Xve6ZIHKYB-htnCwi`i$2LRCpC>Ap zonP1GPnd?m*A^y5m{Gz!C~4C1=x6RyZG|VWGUC}znDFDTEzA^Qb`gfI&ov%eS~?!y z*Cb_L_$1@A|1#GU&9mkX`zWTX#bF;U`%p{R!Y4R?B=KqccyVg((S%!R7PmNVm08^S zMIRTpVxriITO)3FNqijpp&f13?TGiOQqSe{D!T1V<8KsyH;cctU2yG(3hBU#hYsww zNutDW3x2P$>Yrunc{D9feM#DS?-$7}IP#IQ#`^IvC5tEWoIvUfO32Sso6FwwvW`=c z5-R0|_{0tj5@++1*0TiZv}~kQ8dLU=@({mM_}zuyHtucd7Qf_>Wjigu`A<+0r?Uc*XQw!?XOlpk64SLR-u{Oq;3+r6mX5M9U zp1f*1bBlY8K1bMa95(IHJ*pSk#tP(dNjy^iO+Q{xL5b(@)%JKe+hM&sF&$5scl6!6 z!ZjZAyyEm%7!wiCc8SNEj;EIVE_snP31T+>x-1^IGc>d$-*N8ezRc%86UQ@#C!TzX z=Ud3*lKY`y?CWTHZd3#}3KxM3a*v&)s?pt2qsn-YOx)*&_wSzn^#0w;4&Yw1A9vBY z$#PH5=PKda;p)(t>8WL(Xd|ij5!5#Z8abl4-h3t4Re^er_oRmqbOHK2%PwS$A>#y* zLArTDJ(X^WcQ@`+w!9K90XGSEw1nXjF8_zr6PrKDvtqd2a6#_f@-%b+_l5(ww;#a0 zZ$Iv$^G?g%o~Pq*lHUI$Pm7+Re*M(42w6@Zb`B5vJT z-146#J-9_u;dnBiXR;(*DQ@{ccXzACtroZ1R5+@E6<-5x-C4r5;x>`Rtp~T@FWhAg z;?{tho1SC1^=EOL!)-e0rl0*{5XWg)NnhTN8UMawJujy7j4t0wdkJpsS=>Unjc0MI z$E|eSU1kio)+}z_xQ*ea)92802X2MG%#>L~8b&GGsO4tluJM{>&$N%S5cfI44kg2C zxtJWu0Nlhqe;4`iswLaSy^Q>eSnkHa>SgkSgs;JU0QV^OMrYDp_CRH}V(!^A<7X-9 zrzz8q=+TWHp(In6D=DR$;KO5u3H% zkM+{_NY33h=Iqh$_ApK$9GA%3jl726TJnxQczGSjJ2@$DMotJ&NBArM3F(_mKhr37 zCQP1ghm)rZjGy3_ej#HV%b?YIM_U!+*ogZU?;5|z>p|Y=?-$fFBJci#lJ{kId2981 zA*Ru}<`2vF1U%29w~dp$Dxhqt;U>8^K5WKoUjy05Dgw{zr{|2E50G$S!p$Yal^eLIQ z@9)d&{Sw@wgUfr7`s?6=aMS;fw)26n?40xe&CQ((i8KfX!KGP(sL&t?GSfRVnVGar zE7i0OP204MHYzmQ(3CJSg0u|{4YCBGvn$B1Swd%t?JPldWr=P4%#tN`V+C1rlL^B8 zy+6-+&b{ZHdv4~owdeJ^Gbf+#pXd90pXWT!_xW?4f;|*Q z!_d0iv@Xj|YYM%hd(%1&HW-qYe7S_aA!z^0eXP{cm$go!iTQUsZ38o%&I?jJiy`BH z3tyfd2b4CruY1Yo)%^PLd709gdRf}%?JQoDJp5$Vy1e|*2dyjzZ4_F04%$&@J40y7 z?>uE(0quWfM-6tEx?}e8W!8mqkBHTDc>X6(ub2>4ri%YNX{gX1mPRt>HF5hv5{N{YV$tP4!~yt-5$ z+9;=PXqtD9eeESSEi=ch65yjCcARY2yQIQ1=KRG=J$21HPOkgCEuUM^$t54mlGbt3 zx|RFbbxvCLtQIyz+4E8KO@izrvrL<@d;@L&=Bjf>!z&%V%5!{yb_2bx?}gQZt#~ly zy%B5~>>1q4-+J1`+YY|v;T6^cw&}rY!3HVMB-kC?$9_;}>n-PU+V3FAVXtvwF6a5A zutT2Dzk7+-XMy)H&L)3KKh}?aE~Oix{7Q;Xn*CF;?(lS(P*$v$PPZJfK7PMBW%ccG z^a*eNA?1#~Cvfl9JJpj;@DA|D2{&^O&N{zj2B38YWMtbIwsnIWpZxO0dHF>8bbi`Z zu5DjKj!XJh(Km&@4|5;;e7L@_ymq4R>8`#*jy|PRuZ6ji?7X&s7l8-!DjT{N5P@d$ z^vZA>g5|8VR~(FJL5@p#>O|iF`b;@Zh3gB;(=z(b zaP_^*(I?+cFVg;po9l)crdTB zVG0!KE zHw~5m(|9G)>YN!j2i6YuOkr0z*b-PDn6VRB=_bJj!JZ~QE@4Ig!E*-o6oWWe1=v&o zs{uO(_8bpi0&Ee?#6DJED_Amsb%B*V%FoviRvo}bz#75+%9F+sunsV zHxJeY_5=^M4AvXKHo*qKynZfWfvO+On?@Y04@|MNF8Qh!Y!Ix}AP&|D7Sz`kqOUtd z-$01IQLE2u%VdbY8LRJ1k1Y#e%CA>;1*`{5_UqFBTVUN_-a1+KQ^$X2iHXz3#8(NX z{Ca&=2d4Z!N_<>#eobJ*U^fcj+=!tlXKXMTp+g)sHj%Dwcuv6cT=8(JeIEeZ1k>~8 z9YcsV4s9!hCSAv%(XjdKnz#I3TbHeV7i&ap6D)~dnn06g_&lw9E=%+-=BrplXD^2L zC`8`GksXO8{DU33*Hy)@b%}g|)d(>m(tg{ktwCPTn*5_{EMVU<*l3>i zmHph2uahlYiW4fqW|8j@knx|t-Z9DRb)Rf$LoQP8FUxMQtvqcRv+{18)3Y)KR*lX} zxcBT&BI)P>cYBiR%p!ap@Vz^mk7>iqJ|^+TmdPKyN3(gUycu5kq6Xf!$DTCndmdkS z=ay>t&Zh76$$I2EksCqoLrMc5ddrwi754ijTBMd}x{TFqnrGk$@=4?mNj|gROZjG2 zKVzf(z5s9QJj4sKx zTYc{QNd;Js)%OuG(NC~nPoJAG-`P=lj6k17&$~pwDt!hCd$bP4R3J{1=$z9z18?=? zZGUg(;$M}X%Q^wA2HKOP$Ivo!wwusup?ULJ@?Q?WYpdFhIGFT5Ey^F1oA+BUoCiOo z@6Ke_^P=AhUDsfJERJ{=y2_q#(wvQ}zV(o%z*xKF%b*ADgD!s!!_x%MW4YI*K5zo8 z73?VjE|i=zcT&lpGtkxO54jr-orhY&YP%-QJe zuCtAfYWVu$Gjq=z&g`|L5!wi}mt^VaNb8Wj-SADq_k8jB>hYCndsPQU;X4lBxoJK( zUspLbfQ-5GoyXx_fp?#Hjr~OUP?g>iw2CL5Or4X~WBOM5;f#O8SF}QXfbSLJ!^emD z=8@ya*QD)IeW-!23BKFK$444$A8h({n&U${i;JTKeQn{vVl~~~*3mhqzYkr*=+ZNx z%alLZ7}$#i5W~y139wlgGd^Q(#hjC$$%AC);9G)kw`8xg@jqC_ll(Dq608c$TgQri zN&dl}Az99j9E!G?=ctiCE1@+)>ktk9dcWJl{#JADG!L*Zhl%0?egM%be%w9nKbO4?Hav#tJIvm z=6pMvO&gibid0zH-h+@CoEt{vHuvrana z8}cnVl0ww8BpK9doIl?&NP)u&p{2Nv| zZ5ndb@YW$${tTyoj(y)JM~Q*lOO3o}EzqW+y;?L=H+W+RLD9OQZ9xmm z&k(do)q~5=EV4bw2KBa~a+!M53#}5L#$NOkQ^q!q&Ag9b;#h4Gq9?C8P+CQcmTtZ1 zm`9vb6}__`W)|8o)NedcaJ&R<VEtg; zx>2^Ct*;U+sILwzsILhusIT4X^X9k5>hofQR-gOK$o4TX<=4wM1=bbF+i|eY0N)~* z{HGWx@@gmcT?LbUUSCBvocun@`aaJ88noelR5SsEi0BsYR*RD~^@5Lsqeiv&*b_Q$}S*Ej1ns@Sf7`{av zXxG#8*dGj?$F_6CC8zMub2Ig(3YBrNKCl|A-@B(KIFH?A<@a0Op=kI#b`N|<(D{zE zP7~*4<`Wg?F%tBJVm2n9LUt=)<8iRfJZzM&*Q~r^Ctc0_Vw-fkXWKcwob%Xkz##8z zYhv4Z>{fIp(5W*Brbv@O~+qm+`0_H^VDmY{J`8 z^T6k^YyL<)j@*Zo20rwbaX$0d-N>&ZKag!B@8xG~l;6kTZGOJLU7P}I0`v9>$H5xG zyfMilSOV;kN{37OR>2wqvXL#~g#cC#RtI*Lk#uCMz*@lE`d$I89!&OqMtsgZHeW^7 zi51B6YlFT7Jun9KJ+F+Ee2=TfjysVZMYi$<;q%%@p;bY9B7Dx+MPHe8?3{;I4b8Pv z?Z&d@f1l;|jt>uL?Sc08&>4(>d;`RN#W(q%Q{%%LCN8=H`V(NCV9#fKp*nG==1y%N zl-c`lzGJ_Sz5m|`og-&75l4I&U2V17&ygR6XB3{tDnDHE*DTlsSlAr-GPEPmUMObl z%Qe4U@@Jl(96G9?9fxN6`^-ER<5%Wj`P-dmZ-#FUz85Ji=G?q<ExW4W8;0+I_?Z9C%#mZiGm~ffsTTOA;WN*8jyZC^aqO%2=j!bV zAL7iBkE3e~UCM(lJ;z7Eie6-W{90%3cNVM`?4@EdzUACuGe@4ugJjp>Yl80;;=9_3 z3uBRjNH5sqg>fmZonQlCCU!UT;rb$nlYT9TCcs|Dz0$(Ogm!AA1ng4ooo|oTi`2#Pv2{Bd_jG(XX)BFrNPYO@j`ieB;I5u z|9$=;z(=XIk0}P8e!CN?>a?rzI+qjfq>4Es3Tu5 zc{^Vo2b+Y?V8AN#TChd1w}_AE!8>2x46Xj9`OTO2!Mg;ndG4f_-!NFo%j`O@+g91L z5N>liNM_ac6u-eUXP`PWi(ChC-{#)awrJ*0jREruv`}`=JV_lWs<(6IFDuEKGcPYJ zh*UwJkS^m#6K9&boQ^Yt9T)G5s9c(n9Yfabf8@6guu-r%tIwW~!WhLjXO0_H{OPz* zU7_hmCuGmdPo^+Cw>|W{zRF|AG)u^?BJ0|tdYlAX0o$$~D=!s%h-@t&TLUJ2+sW1; z+X`P2zA5fKbllTl^~b*2--nw}9OBemM@KITXvt?P6`(s(?>6)NlY?$t(SH;R&!tu(wMW z?NxA|I}Y3l&jdV|i6>;fTy=N|o+WsGDIPa3>i0tC!l$LLAyclZXLFS6IM_Yh$8O>a zKIrhqI;WcpFTp2eyH2J?CBMg!_qHJ#mfE@S^t%w%@FtM!MNVtgv8Ot6c788wE?n(n z&lx-q@E_scV`JcXu&c=F$01Qnz;6dm&d2@OR$$trAHVF}t6G&84l zO=+3huOwL2Zo77B_&HL=&V83XtRT_`?LBE-2b?V?p1JQ@cxK^IJ$K3}GxyyLZ57%j zX?eXH>CAn1L9018+|EI0wa~nFj)Apw@wdvq16qydwC{b`Uwlq`XA$=7 z@%K5^U^QT-&*^sNv>U*>z#izFb}w=R$hk3xo~Wj z06PIT17`eZ^{s);dHB?Z#2!w626h4WDo^hmk7wSTxoTP}=yX6PPN_$34Y`+Fx$Wk( zJK>G(%`QvT#{pDQ&>x{^l}L>i#GWg^?+U`=3;2mDSomuOv#NVy^o%#{$ikW9^D9WLE=3Aro2r(0tIOQ|25Q+TkpH@x& zVhQ-U>_?9oKhW+RjuHU!El-V|R3<~DGYox*`&hY?j(2~}b@XRW!@IZmmh}A| zv#vJM8HwCMntEnpzbG_ma%<9EM(-?o70btd0FC~~oy%2ys+?z=nJasrBU>Zc5}qIA zGW`+bT6+e*`htJ-dW-R^WSa4D-+_~7LVy!AWsNa`C{uzst)Rj9n6 zeS-Vgx1*-qZ`XOALxmhGb~u(PkKM)E=#qIu(8*U7!Ns%Pe0>OcF4@~dn$zg*Rz2hr zHV9UJUSQ3a?@ZPa9}r zUeg9@A)M`czP$&DV&<%Th=wfYJA$p~JBq&dTYaaQm+uKX%}#CeIC@IXKk0mb!nDoV zvG^_i$k#mUi^$bJKsoub>`dwda=O>0|0}^-!Ok&=6O-5B+X1jE!D9DCY@0yaVV@7q z2eHfV`-<&GA=9Mjd8_W`eaf%f`hGXETq=VhbdIA_&3NoiN2m3hIiD{xmt?=gXHKB3 zBR*tQ;$4~M$x=GCZ;nL1^R?Afocq`%PCBaBNz!S&;ADz$k?)2&>DYG0w593tzL(PV z#^;Pkq_>Q~?E|jfXF7W2r=~I#Hv8us+rhR-%R3L*1GWiPf{ZT74uT~E*cjMa0Gk3^ z4d^=#wi3V=!IlHEt6)n3Eb^#=$cX?}4z>v9wiB|i3QYRESUs5R^X9D?ENEW`nC$b) z_JS$D50eA9l*W+N_bh`r*f>}TKK05T1=|_GX2B{0*a@&252pTn4Xhqa<3J}q$m|y` zc{J@~4q6qojvTZEwB8)Fc4$L6XnoMebI?Yi9SxzWA3aJr&O(!)Vn_c(fTF(Ci6PW( zEvH%EGk1MJ-(h_ZAySCJCdN_>(_Bvb z*>W=VBkrZoyFOyp!0cJtzL^f@Dop%?Y~|aaOrj-8^7$q$sR%W_=A*PMz-_vlg>H)CWgq& zO`G|={iXfU=vM-r-LB$ls&E`%RkS(d`Pj#QZ`%?#_SaFDnZbhio8fPQ|5e<_ZVmFY z<`CfThriF|zhrxUwMkPrGEd$%c*|aUdcDfSAiO>Bdh;~~HXOjFl)eX(&yQ2SvtXAfFHhY( zZ5buu--O?k(H~C*__NEX5?@wbVfpXho?m%sc>?hw{NB8Dg7tVX`MnQp7%ZPMIs$Jp zPu>Nk-=3e|$P-yV$%9wf1ZfSw6&? zwaWh%bej1O&fh)YoaMH6=nRfhYdpK`hO;Bl^PEH6_MctmtbZW8iR?keDF-~VyYDRA zb4P5SPQ%nmaCeU;4+(^rFi{pjoZICEOc+d)s>_Bwt!Uw$c^EO0#H)S;tKCjS3qW?!Y+ zz*+Rvf?dpgtkSV7Ge>oOR==|ee+T>*ia!$MXAg9spQ@@Th>XB*+RPKlZQD%M^VX-( zU&HTh1G~VMJebWCc-udfAMc{l&x2QOObmZkO`J@nbYJ4+E!Z~U!Au*& zEQjRkky~@+R!(}`v~*j+Yau>4JzqV@O@ErTCpLS=zU9b;$KCe4$hCsP{jtzz*37p^ z=PWuGj-E{Y8<7_j&z=KbrO$x_iUOmN5;-4h3ibWSI5!Wx@7(NFsJ+;sy}x=mHz6t*3QT0 zp%Z^@XQ!TtuBTC7{^ai?`oXpW*a%qBpHKR}U3Ua57Ql{yMFQBo)%OU})1`Em!8S?5 zi*14>Jy;`rB~N$k`$-{xn6aR#KW6S}x7x`}x$NiL5bw}Xkk=-dF7i%3?JjOdycJyo z1%H`$~InDD5xUT{_5bxL{A|DF2TaypgR^9if9wEsU$pJ4PDA z(PZj-XYhyHpLov(E&MCfZmL3ye--|khbL3Vz5Mhc`*fD4a|VCmT~W2P=bAiJoK+B+ zKQozXqHPZ5p~>%$1Ep&P(PZf+miMy*d5|4o!_k`s5^d)iFSnz%pEQm=Dw$f%Ck^`U z4;PfqlE6H_MG{#ek(p?YMD&1kp?8@y8q1TZK5dxGlq-d1zxlzrfc72k1Eo_t3TvaG zHt#7e{6s^`+4(n=L;c?Ruqbu#tr^s~?V+IJL& z&tbRkFYUavv^(K^-sJMqcAZBb^2D55Y;;edyZX6*dEM>UUGhu}K0BFuuK!tVNS9`|MClQp`>BFXffs~YZ}#%^l14Xa%snre`dm5<_J(Ek(Y9Sg-6uS0 z>>`b8+%&S|_r~JrOlaadSfYotSCzjPBvTy^=MU2kWcuAqn{3w>W(o>_6D^%Bpl*=C zMSe>Kt)(*s(RYTXvA4KzPVBp2-)Gq`hF$$nM-~0gOOw8J{a&!x%QBeS=ON0r3`}(> z_OM?EVoV|_PfV(HjAQUO!2fme|9sWPU(9o)&kQx^5i~^Ep5a@ivhQneVZ#jaZojB_ zwCq{5$6#jvDn9b=1-Q(t@R`qPMdI+*!#fXe1NZW$zV#Pp`amz*tItv1+u>RDq#>*a zYz3@Ha$Ld&!Ir_y9svGs<~K%pt${@;w%DWH{F(JH60&EyYDP!zZxeB*SG*5udDw^W z^f>9Pka3RhoS3@&bqjKGXcF0x?4_huxYSbumH)-}}#g6^L&hG`jSF``@I3EvXe6^?es2HIn9f)J|(qCJfOC3G(K@)n~ z_aswqCl|3lFK6aB?OdKS$N3eUV7~Hpu0Gl$eXFmJL_UK)W7oeU$E9)@dp_m0H<|jT z08ywuIp^^OfHhd6cN zSYtBvDehyJf&0tBdmNd`%Mj@nxp{d>Mo%YtD)%Q-Uyz<>xpsMde67#N1NdZd%=);< z(IcNsy@2|Po(JNSWzw!XkaWJkcl_s>axvfE+i%AHnm@kYEg9;R%4OgUk;wI~4Ie~~ zOZ`>tIn3XouSr1Q+iK0wR&&t0pv4a+L%&%z2(2RrZ35a6Xf5gVGT&C4QF=M#PC%<^ z3QunnS`V}^o#nOUFNa(Ww5=SpCTI=kF}_IKnfZQNC$tf0ZK9d|8kz5|4M1DWAvX@K z`uuR6$DsA+pe;gM$U#d&tGXauXW0ws51|F^RKHRUZN`-g`8HY;J}ql@^u}KQpKPep zz7!%#`|UNAf4fV+Fq=Mv{wYg$d}Lxo^KG^`=}#gbxiIN`TkWx~y#L#3^YApeJnKu= zh76DLktCf_czU?E>9Bh-^KG;87ttPWL$8J2dQooq7ULine=_%RvUrd zyAAy)^u=xH3()IY!u9K!Rr!xXA6Fas!=IWq&AU&>%ypXiNjnzqF08k6oq;8@UB%7l zOW@Cyi<7CO^nET5efjd-GyJwl|||6MPKqVt1t4<^r;MH(RaL!K3w)K|K#*#N*O#~^PH4(R-vL# zIxFyZ{0gh{#}8d+JvzJ5*>`0!HO77H`;JcU_|xpY)K^qBXFPWJ*m_?BzavW_~%a z{>=JnLG+(O^*Qgm4pWiGI{fv10&FyZO@oaDusN_{4^~5dm%xU=Cb)OToo}J33w))_ zLCn56$&|m0`2U*xWmGPWFJZj{nPSzy^Zp|f|9aOWh}Z~z_Y?faS=!)scX2cEm*fYL zZ@Dg+>XaX!mP@`(LXE|p$hSMUO;&zZ>31fby@%^3GBJ*`M!qK<Dl)=cu$A! z#52cK4$sar=$l9Ep6f|RztWAoocIDgPu9Jem$<~#1JNp=)#RYnK~wr+axKtgZ;&Rt zyQy3C(2U*Hr?-0^yF2lV{1A&}?4I5x{e>LqD^F|Clzz}Q=`VQ&@f)-f?sW-^gNfgZ z)q*L1UaS#J{_tXLVDg_A>jsm*z1RR)RR9|Ws|;Y1VDSJp16C2h7Ql9bxno-8W5w$8 zVq0KA`^sLKZC@pr?DNXjfhoV<{o74o()TPmk4x#cgEa+Yd%#))*dSO(02>4A_FyW% zDX>1U;QnLL=AaGdpshfg$U%!W5U=H+#i7mRpw&ZL%0X*|mJFd)U}q10FS^^Ys zILN<$dqm|2-OL|XKsRGfGxp8ezdb`b_3(!6-#!7Y0h(KGrv8I9fd&27gk2^0s2y69 z+Q1`=SusSQFH5K|`*rAQ$id>QL-O*x>Pv=qmp$=w{FN z6J2^{&-Xm^ie5|q^Zi!dq1Pe5hE4U*|BHM1HFSSAYZ$)$*~~3Swt6?~5f4(f6WNKk zJn(c?P7}zszBTE*H)rYs{fc?dCi8s=@y)^4p9fzOz9V_?Rh-LwZytOJ_%`$4>w+(S z>*@0`0$*bue8=GHJ`GN8x*`Vw2zg(AMkB+Mn42N8fDs03=vqEc}Xsccu=* zV&iIb*5SkBw6^{hx(o;ZzH@dZ_iI}54_WP@G1}E@K*Hu^K}%g zE`ZG{eGeudo>2V&yM+7LB@4D4&AoTM1mBFqZ_4PLAb)ll)x$sU@}Idqzw*+xmxlEn z{=D>qRd_I!(Fj-_SUzPm1MgU#ysJw8o%!kAxsU!h4_@Uf0q-ol-n!Qcwj98^z#@k- zd>X&^t9-!<)aTs)eN#rky@z4@qNkBdx^iFi$))!mhV6@9LvC)srgx`LZrgp)Rrqe= zT~_V}pIpxL`0>80>cQLr;@^Ktrq7#I8@QzU0%rC__c(TC`l;-F(PjIY zUxDB3i*63`XYY${fWPEE+h$e=`TcFCZ0tTr`~|o7-y3Ys=GHU2gwDPN>ZNo(DG#0O<82JrY0fp2&RTqUPW-u@ zoq8q)&S$>=iR-@oVWVIJ0c;X%7|h)VB-t6Tp#Zi3HWgx@ogRb>+1$nsz88IM+TuwRsDq zQTLSfl)h7%?oaF*G~1k6>MV@A8>7V^YsO!d>#5gz`2+i+7tOxty~XYD*TO&j?DbS@ zh(Ge)7ky=D7EE8OG)76|X!Uw(GoLha?u!mjL@!NrqIZilnxD6xI;jalQ?7ycB{KV> z_m>XuC_JR~g6x7#6i2&DM|Zrobezq*N7%$X#YWv}{-4Klt2b5KlL(Tkw&vX+?5(g#T@`eD#41ERFSqimHPXWy}7*NwTA z%VM3RH@f3eE1-PlYt_F>&}+aOi=#iy#yAwyZ~Zm~$+J2n+b?jN4k`C%>Nd`1v8nHI z{coJ|70`=9GIIm?9`Gi(_jn7FO}{%>mhr2{ zp0g*K)#VWL&HFpOq%lAmoqN_(dRM`D4%xh9_lpPKJJH-k^r$Bd?YlO6qF+rK*|9r& zqFHeWS>#h^-a~Sci`ieeZ#{LlCUP@vdtlGEY1{X)ow=|hbWe1DXa)}!7rsX9jh-#k z?w;sg(j7S8-{%d14SO)P&Eu5q7?{dA_Ly%6#_&ABo_Q0k!_31!2fx`9{o7d^v*o`h zy6n|V8D5a-V-<^5f;EEWvnRS4-Zpp}kdr^d_C)u<)9Xn?{rDhQ4_J}pxP*;?b%VL* z!8LMT%zf-1zGZz&UA6no zx7!olsq#kF%xS;Zkb>R}4Qdf|)(hfB5DD+7sP?Y}bWW_TPN6Cf0vDIm@*ty04W&yl6e=p6D^? zHQUgSL2uuNeggXNHuNp%GuzNBE@l328+twT_^Wc;-v+%o2Yn}g?}OeC{d|>IU{CZY zw4*s_N1?6cpv^<8XbIQ33avQ@t>`l9XAW8=wCNCl)rQ4e<{^+YIoJg2gV*=9jLc&~`#|*L)7q!K>X6Z5~<$w9NYEg@z_uR-whA zCAfF|t#dycIUCmbE@H{$_y@XoZYd7d2I%knzWtusicc_rN z559KzUM9Yd(lV8l@DcFA04}|g;8Wn5%W?Fw4VaA#P5hfEJ{M^x{RQ||PQ#~i+ERVE z#J0)L`6shhb|}i5ag?R@ z|8lP7?K;!Zp2uzctE0oe!&l#U0=#a+hfZ&~R)P(JdCRp9Y`}x5T${l9!A@7MD*r|JOu3$g z-I+R&Rj%XkZozBH^^afMwp`~(KYsZ`E7!6siI33X=3i}cCDJK5<0tVShkq3Q!(se< z_tNs4xkk0GN%%M6KS%r*m6qwWY~dwW6-45%@#8h%mEeuwuOM90H=pf3D|1$QlrL(f z?TNm(w1`hw*1^__Ov`K6Q-p`vG9OLL>@AMo;mYqr)Qo3GknO!dO@moXpSjT$$TmswI5nOxTr<``0R=n;s zt^11;AO}3VG0&rEHToK^WmL> zOE>=J{o+>S=aJv)Tu&{u?{4HzH@@{3l8(o)E zTFyg8W;rdFk4MMJRk8Wv=Fj0^3t8o9E%J?;{?~RY$%1 z`kSMNLQSTQs{O9Gj`sU)>#47DAGDcuFEtJaON%a2{ArJcZz@)Kwjei(+!L$~-f#Uz z|32OJnSjl%J2hMw;Jb#orLOlaUYMLDj5{fYb7pTF$w^Vx4b6wAbi z(XVDzeybyijwA0N-q$nrFRl*rOwkT{W1#4Im=2X^9ezIk&h^y&vg2cp4)3_Z-R~Qn z_BVaXS5GJNeTOoAtgu0_4lr+wF$UJ|!5ZM3BHcEy|KvXQJlA&GA^RO&qBR>IM~{Tr zE*(j96b(?Hl>a|}$=jv`^Z$!59VY*jP3&FksjtY6?>RcWV>Nf&6#Ym@{$EEtg{&K6 znEZncfcf$d*6+b&`xNQ+f$>^zB-ZZQZt@>2|IIMl)yE{!(Rp}1b%S&~&$XTM_wLo^ zTf~J0wna^MguBeMTuZr(zT1|4%$3Imue`>oA!XlvJ0lP!_d;LFn%Qq9_JAKBp^6fLKaMtpB)>Hq3&Oo1W zf3VMJf%nM!9$KFYN>wQ(QpGj3$ms_~xsjRpAX;0x#3HkenThX z*lvSGQ}r7~uV?;wWIYxC3-lW;=xY4Xdg>olPJzB|yMAK`xq0LsYQM37o+G3Feq)7l zulz7`+as`2L&V&gTQ3%x&C6aBLH{Ye%MD-f$guKbgB?(nZ^m)?p zzn?edWBq8pTg=||L!6IRqn8SUeyBoz=}pv!&)Bh^Z%(lA-<^J&fd+hfHcQ^fdeIRZ z*L=oJ%ZyVpbK&yYSN!wul5Iq`_HV50O%F_V0NF)k)nCLKe6nUwZaS9yxIbO>TQfHi z5C84D=Y0;}HjwqYPGCV8rB)+=td>i!gG!OG% z8J?0}o{xLiQy)`W`_gH#^qb+Sfv04eHbp$=z~jtIvH>c?(*n;TdOj_lr>1!Zy*$0} z#IX4u@f15ehojN=dU?j+X~5<$i09N1yG|7SfXkzK&>46tj? z7QHqy3*514+k|w$%|DlPjKH zLr3*D*HbKkL}I`Dj47Ycc-lrT-f@ZI=^7Sd+P;-vJl#e;iGSOU_wI4i3W=w`?2l^| zPmdyd+?DNlV6qFyc7De`7rT72{&@PY{pl*6E_*xAJF=PgqaS=c-G;8=@A~8EZm=P+ zT=DWS^kdu5r(~ax?vJPEp;yd#_50~-(3`fQmod2S^U?kBw90=Hx{0TMGvSR79&|iC zfX?ZEu{x)>)tPrZy@<}^|4JXEdj8IAot}95e*tHYWYvr@ZC%pyTO!>PpQItnD9gbpBu1K0!Kl z3)c2Me;M01(YcJy%jEZ>ylj6^c=*oJ}b@Ri>GJdIf~6Do<7sz3C7c_@HAkLiKqWK9vV-V;omuU zOg#PHE>AjM%!;QQR9^pK_4o@(io`$FV zCpI2@ds>e#o<0Fj&616$Z*XnP9Z#3Mi})LzCZ0a%@CW1R8hHBt)5g;;c6FxxlNC?5 ziys?IJpFi=-ycs8yqnl}|7z-mJRd55HZw3&>8$0AL;G9~+A(O~%t2d(_R|nroFtOi zvnvuy?zor_~~$zJuivd3x{IPzF*Ato&o&nccsq&E;$3{q1f$(W}lnJT2rL= z4E~S(mx5-{BO?0sM(Wno<*!zU}=4ts5<^8ku)HQxNXaDKB zX=bxeN$Diu?}h)h;?LN}Sc|cTolZHoSY5|H)3`}cdG14Q9J$w6IoevY5#E$Pv}I_n zkCe_N*byPERwy4t%2V%a-BbC9BSA0?YKJ=_nLKIwk#Ij zQSb=!JuB~qZ>BZTQH3vK|K*GeV?XRcoSyx*Fr#@rN4>W* zpMXBK!JfQOrcpX5X9}ts(m3g!fKO!@yUj`0p6@E&%&sx3cOJd<|Gl1iUAA7fCp&sI zj^9M@0DSM2-WQcPCvdxzEM@~Q6kT7ALiJq~RIn(}jydzH(f=&P81Yb<6fx7p*PXJHzi z0eIRi4}GC+6L>?}mh;}?&h)(|k!0T*a+AnCRdSa(?PYA3cnd5p?DEnw*|!tyIGAYz zj1AE%)F4SmEwr7#%;a14G=i0bJRv zHp|~nd$#KJZTh)5&-JaIO_l!?e0Osn+u@|^c{X{GNCiO?YoE<7^u~X^p6Y}@@Vs5@ z>aBvL^6!LiPv|N$n0;OeSfhpe9*>arY|#nlJ_m>a`=Z%^4q=YoS}F({9MY{Dz?s} z$MjQ~d^!Ev1(}SQ`hOSg{hECz&KaYa4?l@k3#}1aFy4}_&Cr^l6z(zfG2%}g5;9J-QSn3DCuZaSn~mL~9fj75t}wZIXp_)_a=XPQdpdvk`{PV-hZVAW8kS}FH^g7)*f7K=m3U6gzNP4=({;_* zT?gMP=}w(mPyK*<Or->yh2u=gvvoSkQ- zt@2O&em(UK?qfH*@@6f-KPUew|D3#JCy-r4_G^-T#rCpTo_4oUnYc@@4}K&Djr|DGcgTIQe4&cE#FqbuAWu7%xA6DiS_-+Z=UCQ%g-8_es`R}vyta58Z ze(lfpEQ)0saZ_hP%KQ`AWj=~*Pij4NOnIH$UN%_f3!}_$AZubtHeSN`;2Wv$Tp+zS(4>azQ-xIF0jAlKK3O7J!7Bg`<-@^sMm52@oJ?OGqQ z^T?WZ?T-xHur+iqFtXk{-b`B4$hPd*NbMkQj@{?V;!!bSAK!KLGmq!+>%|2}KQA^W;E#Qj`Hn|!q>jp$ z-*foy&@lXV^t1&&rJWQb*Ppv0{wz?PF*c#digKraoA|H$>$}XF>r#Y7E!}e{=bU6C>Q< z>NR5r<`fRankXzLEchs$ePT&Sas$XUK6b-di}TB|*Vx&Ey$d<&urr?G;49g847u*d zZKTeZeO|dkyvtvseSx-|*)OAfufjk4_>EMf_-}IU({u;hm-NwAtG66qPVd}s_G%o; z$~PxX*?bdO$&Lj4N1m{eI!k)r=klBSrYA7-f~9@0ut)q)+(^}n|4zpa?{hDqw>f@t zw&z;^P9it;qz5KfhJDLSmrqx0q~|kK-!{>86zmDI;a1lMQ)e>uO>gmPU$wXWz!v4X zjw$PrCvT))DEWhqym^MrGxp}R>opfC8#-C~n0QL2uMpM;HXgu+!NvmE1lXtt)3Y`W zHUj4MGj%$U0yBCp$xqJ$ zdRm|6*Rz727BHjdV19ZkSn_Rux?fKXzH0+BdS02Io(}YMp5@omi=GZJqvxFb^h~0s zyV9>`20dM1M$a?z)00F`?_c@#sQi1tjGiaur>Fi?tVcY2u6idDp-X3_pv22&h z2kh^m$9~7+QzkZbV%a`_K9%M;dMc_nQlH@7!%sU$K=uLGElw<}@*juynQC)B;ph*I zWq%ro0P(8Y)DrwL{M-%a>_9El<%&l`#%qnpmY==hjMwV6mkq|U1IUgd`y1I^=*nh% zOSE-}(?cInEW3!#+8P_no@AjjQ13fJV%ZXWJ^Xwd%l8 zXWg<)$F94bCk;On6etEQx(EJx8@qnN)qDEbwE?-+mv1=x_xy6{*p(qzCU#}8tN5uG zxyn~=q*`U)7k%j|c73l?56tqc$@dZXTN*Y}7mEMOF27<|wgGu!*LnDccG5Mpx<-s$R~E~L~bDQz~t19j{hzB+2fC0kD_Z3>}m4F zy{-)*vFncl0U(t~zE2|GyLTgXw&dUE$mfb(o8Yhf8~FEaq^>E1|4xVh&gj1M**E4) zBA%(kXV^1|x(n|t2wnW$SFF0$^I6)v0~@I;xp(Tjsb@5chhlbv6$O8;yLx8^!t%u= za`VV}jpPmKuG@kF*vx1%kn9=jy{Pa|Oj{g1v zzn&U=)&yqsd_6xs9q4Io_Uq|IPYamQ^Tqu1Orod#LcboBe;b(5b5DMHlIZEY$gige zpLKv4Js-_aPyOeK$6w{w(~O=jFr(+r{PYZU`EfO{PfJBr~hKVo+b43 zff+rwoL)~kb+`Nrv@fsD=m9cgziRM)aPK~Y2C%*W)&kZGRw6+zJ-eM?Jprr_tUG`W zgLMV;O@MUeF-qx=ap>* zQ-0mGHu<2->T_daVf|o30of6-(ExS?Y$AXi1Dgt9^I*pU*fQ8`0NVsx2w)|D$9hKq zi-WCtFpV2(!8XAZj|BI_HA5@8B%Ia-EuMom2(2~;Z30?j4%!T~wj8t*(7JQbHlYnb z3$9@+f8~Eq{m&s+18pn^tqIyx4w{Y}IUYi*AiV*~ViB6^MeNLX(ZKq@2RQ+K1-jPl zV2N|T-iQPIu)(i`BF@~ik>*M3#8`@K|MFSYRsC*&M(qmgy>P7t%cSI z&8_cBw-KxbELbPR-vO;Nz~2kj6T)8u{|K}}Xs_Tt!i=XqtBtdZn0wxNuGvGkub7y@ z*-mHT*I8snkm;2S15us3&U`vDhhp97VDeybM`p-q-d~O2uM@}_-+%m&&-W^uIP_KM z&*wgN$fak#H`xHazk z!`pgUem0c-1MNqiy!G(LFV9bJC%glB@{Yi}k|*yByiINS*{}-lRGz#$zeK$Fn$zo5 ze4N0)z3@IqW6tZ|VV+O#J|Odb$;{r!L}_>7qr>ClbIpFP5tY{!%q>gbb6kBq=DE(A z>@M7$OJ4)_Et8kVD>qWNbFVt+9j{;(?`&k2$MzO8T^~Q-PUh$n8>VhneVOMA9i3Lk zkeVH>z1DE{x1F5QLYkiU7I+uoy;Zzi!aBhcS7k7bL;6%6V3%+oyS3l8Yq`JAI0^p} z{I3%KB|(0>*UwotGvk~S@Rwa}`On#&UpAIZ6Mw`1bnbNti-Wa!Fg>TWV7*|U7L%F7 zGUFFL(^mzb>2`Rx^5h+Yx4$Dlz0>fs|gYZqKj0Okrae{NB7A2OIQYDx*cP39x+1 zDE3v>1Fy@Ew+7zHJbByTt?4|yUgc{L-lOn(^EC!GAHb%-Ry~-;)5j^xqSvu@s`31- zx0y2Xt_NxAu*>_9+x4_9wUPb~im++09$s1d_0~W=Wc55yuT=gm@M}MNEcKQEKOumJzX6_|_-O?GUyJ|8@E_EE zN&0+y&a63Giyn{jn3|o{6y_h(tM!gw+rCbJ`lgNaw=v}ZZm^~RHUQQdz(&D3z;+;` zOR|$-?E$_Sur4sOU)S<2fE@wTGpb9nD_}{mA%i&B7FhR9eynVU`T-V+I*gL71Y3Nw zzdY-}mcgFs(bohPx!KRx4ptJtdcbxDutBg&FmK%&1B(Z+DX@wFcHHXo$}WPH2l!UO z$^uyA8`<`igL&;!eXGJ(vagl<*l%wM)F1B-bZ0HD75*0Zzb^joI{dfm%x&+P*II(% zbq(KcEY2g9&quLg!DFl1ut~6auorRfiDMZ9AC6sSYMyzHNN{*Wt@9G5A7uuU*CR{++`Pvsv&HmW}N4>x<;GGmWfvr|Oa znw7s>0*LwMqtY27of&kNaIZ_)IM^`{RtI*Jw5P#_xsSa+J8f;2b{@sO#hGjE+`eXRbIG%OBL%d@fztIfeP2T#Tk3)Cs zZJhiJgN=bTaG$H(j>0qTk&{nmvF#|>ecZ=B`{oDq4X+qJZhVWCeWy>>^e=Bu`!nJ5 zr{)J5j??}lt2&{zOy1#Zu&*ga+JM@jm%Mc&_1|hw7%SLsr-prBPv7<{j2G~6zg?x{ z1#dF{4;S$2lfLUmee%8H$#67ZLU*4 z%{N3%{WRrj>bhBj)VHytbD=eHcd=~QL|4b%;c1D!bC&pL8+sk|<2mR%k#B{bgnmBv zwq0n{rgG7GpjC~A(}tn7<)BSM8_q$Sg?2m#Z5i5T4qD{fj88rsZc7EU_8hc2Xd^jj zEzo9j(7K^*<)95gtNBQ{Ek~ergwV|R7uqPaH`#h|yWRt``@ngo5A86YM$xugr&rWI zNY$q;9x!VhUd-aKL*^}bi?M$cY2Q=s1_#NiA#%#Z3_Ou+(e(Upi z2i%0#?6ff7%js76!uJL?X~llqZQCa^w@2rAd(6xg(m>m_^aFfzHTplH^_W9!Np}W) zeV_cx=&M0r89o_1!d?`u^~*k!7tWcm-_Jg1KCUumKCYtgZI>aF_%8kNXE#!R#l5ax zeyv~&VDWUnWb(*9FWZ-xXLX;>NaejgMt$m)0uAt`_@P zkaq9qnLm{=#{Pr0EYh!;8oRgP8-~w(-vn0WrEHG==?fW5ZDl28y8`w|#d@hXcw;>i zADB8uA5Gll3uSi~yJxee-#$?2X~y}zq%$$a`!Dj3>tnt-zUPkEp4)evXS{rw@v^g@ z?g;$Hzvy3!JO(xc=8pXpr_Y0(0Q-&N4UgV^cf@Yr!N<=J^6~R$d-O*Bh4TJVMz3^~ zgN=e+#J!%2^jt4%Df>%n-egAF&XSW;{_xMj@3o^9Y#A)y{0+h%`)bAxmBkoX1k9^< z3akPw-~27Xf8v;5ZxU<~%&WKPUumzw`n7q|^;<&U6WV+Gj;5hA_TjfL7HY?GquJkB z2Y=$ejg;)vCBHR+HGs8mqvO*?$6lkO5B~0NZ=~OquR+H!SRdHuq}2GHx?|3zIi&eG z#txe1NWTVP>icnIHjzn42HOusITwSCT|6a4yUo`uGMyG)#G`eqK>9?~}(Z5(R) z=49z65o3Rp2$DBnhNCyD!{+6PkkHXQGfHEUG`fGdky_0sjhSfShYLz)NnoDeB8e=K z$V@axA_}N`(OWi8|8)OG>OUUs=EKC(nHa;I?QGxY9^X;;85>Xsnyy4~VQqo^+Iqr% z*nUsxEFX-Y-x0lLXT20dh~tz{kbGe z(tQs|#7$(sO=LI5`YgC;+r%26?tMV^y*-yiRyN-Xr|ZML^5JFj?6nqk{hl`Vid=*X zCFWI@DUcs{l1QU(1kB3Q)qtTsYLoWsp|*s1P*kc~E5D)d6K}0-q@L}MZv%a%`M%t~ z()RtOotKt&CrW!048$%kZP&OnEKp|DlIP*}Hhl*%8iVrj?hlWFz>s zWQUdgzyp)5M|SB9D|=1;vhqVOvPX+frS8IxSlwy;pgyDUCmQq82?p9)y*_K=khvf7 z;FO^Wo&J93 zsg%Ybx|FYG>QEQh3X@u~_g%Y9`P%1!^1UZKU#8zHL+1qP6rGjHm#}HD7??NhInu5H z`!}V%C%?2m6P|YZ8+`HmnSXq)KkZtuX)tfvjre&P>@KC9x@Mbv+wnR1E>^wHE>BGr znerSXotnB#+KO+cz^cKVnB4pNo}4QGZK7uY zJ>EQ&;L|BEQzze;M;`L1lkJPtzjHHrP})6Un_%9w2T8j!Vg09%Zf;*!52#%@o&Qdd zPWSo#wAaA8z`SY4@Wm+D-5l-|`*FuMX*X)Tb{=6{;roknmt8Y?Y(?ktYpl-iK6IUG zt0vJ|^ZHZH`>Xe&({1PM*mR#pP0`(Fm)$@s_bRn=2a6A!T^4CTb_LnQji*urY9sIP z$U0-LeYfuzI-?Qkcko`!9>hQEgu)k`UDjgWPpm>m_5WkM*nP@>E|9*%8P{oY=~uSR zSfCZ&33zAJC%Sf~$9FWy2@*4JKsw{QLHK)~yXu@Dg^23O7+CzKQ>kZjuWKj2DX>1U z7Yi`^1g|w`6^MTh+ERdj39P;+oPP`2+M7<9y=_t_9c4de{uCYB@1{#>R)W=`!<-#q z^5UGKBU%HrYWRaR>1u=4fZUV0*Hy!>`$w$*fqjPi*b}cdbnrw=lU_6MN*4&-4VyeiTFUb6Z##-ier1MP? zYaXW(wmN(ld-JK(tmLo0%I1R^0Nz`mHkdW0I%w_iO~SV(z8l~({$mJd>W$W2`Q$B1 z&n<4`{z7xFHcDxz{M&Cnm0FbS^4KwC+-pOO*h#hXG`=i?bV7<0CQy%hfBUlHR)}xh2)^4p>@_Y5*~$c#(oi>?g%?U*_G ze%E|w47>(^{aa5t-}rbp{C<1h=e4KeC-evKcA?wD$MdeE&3N7^w}Zu3x*}$;LNh*I zMW$Q!JO}-+PVZl3U9iLD-GARV`Fz)*!ozAF>iG>KS9vS_n&y~JwVS;A_wX=}Nv{?i z4s>X2x>H*|i~K;}sZ_7zf9uHGv)JexHE+|Puc~Cu9-uUpZiKR#yX{o!tsXf$e`@yf z*txA{3Hv%y&Mr`$YWOMfWYX3(3fq zIM`}{uNG|6gEhm~NL`8bpGsX2!~Z|M!jzwP-N}p}GV2o>vlTXnZ`#zfsyU~3jC3Lc zr&3Sj?+DeUl?O`C+EMVG;Cn63!Re;G5p5n?QwXgF+A6euXzh|S1-KSE|(><4LDaTJ$V8)iaAG$3aq_Oioep`CM%E3xZs*Wu~ zU}a$Lvmjf>v84pe*mA*XZP`x?@n75dWsNkp-g_$jPK(lr;g?M?lg9H-o5lgs*w*(g zKWDytD3f2M(McN1U?z<-A9@^KaDxkm{F}h1vtZ{d-Fr#b^eKBen_F|MjS4N9c8|W{?6S*< zEMMmizfJw?N8gDLWYUnYM$oqiW_tQap?qwD9S8HqW+nJ^ z9_;hbW4Ai#`eHL3Ym(WnXzXwJ1?|lT{q1NASSy$}HtPgy0Sm@oYO@9`e~_l(7YJm%4J0vWeolYf`NR>0oK zz4AzGc7q$6MgE)e`|zpMVN0{`+7PQmZ*qebl|vQfa1_3%;)IvDw(xAZ=dXR+Z%Z{Y zZO9CN#Kv0Xu8b!(yVEt_i5rICk9^b_+s9Te55{Ki@!B(u-97Mj`R(2|Hd{si6f)h& z#D>tHXKYp}|H&@p{c_|2@rQlCKQuN|EYX2{<6je_O8!;Z@_U{00h*)l48&M0R7-vW z`6D0u|FoSCTwQhj|1bBRJ5d;tQBqPdMVm!NNkzGeZERx<6%`c?6_sihDH~p&pk}+_x+y7<8Fu7 z=l$>V{`~)YoIct*m*3m7^JdpsK0`tag47{ARt%5((f9v2C$CsUtu8CV4DLf(D-3De&^*g5Vt_$uJ@_s2GaRe`zrRyzB@>cIT{u|cp60lG1; z%|5IS-ks!+9yI_PNPn#KFN|lm?oSpYU;Qy%)?UVI6?ipxlkf%f ztBN6Oho=pmOT}aA9ThpQC(xjRzPaMiM;eVn)2w{+_&FvHH?x@K?olv%aGAekj{+EnbpzxR6iL0BzVH<)~) zM_3csHZZq;B^#9f+rf+t*Z%i5j3B$?zH}QVz&3#8$^ahOFa_2Q=Ju;(LjoJxz>Ezi z9n6Nc_+i()eyH5b{Kwb4{(xlHleSC2jO=3$Ci~z%?nCzEHZOjXJ&5dGU`BS%e=j?M z?4EC=%g)75-C#!c3k^#CZ8lKH+kSkh@dl@A|(|9tJ(x!q$UTfw}!A)gK$s zR|ytVzv)r*rTR@b$F^9jyW;D}*_YbArt{TnVy0iRhLBbGEltdXz|!bW+w z&!oEJEwh#%Wp>G1%xBhH^19*4{my>--g%!qjjOj2) zwAZD+DU(;X$?A%2A?*sFQG37Qk9*7>Y3o+>@hFX(NUKKdxSDtC2HOs2UV{2+qg90+ zxYP3kzLUJ&-=__7<(*^8lYa*P&UqK)nfXc1By42OH+$W~kxIhG2^%DAkaxQtHhm@2 z9?@N9(x#Zhg?5K&C7;RgtJP*t<*oc_(sS5L|01vwutKnpORmyH-8byiBc3Y4+X;8u zF4@@t)(I9oUoM}v5w@AI6M47ue2p{T$S!t!y3zQ)kMw@^ANH8N#nyRenKbl&r%3Fw zZ_LrZB9$)~d#vn=ZM5t(oFaVSd;62W6N4#ZYn|}um_5yH&q3w>gY|;%?>Be($bZgx z53?VRKFX%J-PSM*m$n~H+v$uSYT@ty!T!{F50!x?ux((Yya&@{9oyrX$Mn*kL=aC8 zJQX8;9ddiR-*ue|gR(9u z@#FBYLBdKO4wpGjSlQVAxXfjQ z~J!@}AK>qX{G9^WTizHY(}5WldnZG?4@4kiw!97nHr`L+|b z6rU*+A_s=={_|CvhE??wdq&Geb3o9b513PaOpUHo0-Eh*cg0T9ZhKJP?w)FA+ z=39Jreu>`Z^0g8+j?6E4GPk&4U4-=`Gc0U?u&MD(nW|662y6e@{^Zvr)A)rxi&?*k zcDwWRqT35yC3YQ*PIm#1k(14w z!ux3z&6x@HRT>B%n_wRV?{G1psb4WN?XPG~p(!fCfn9I<<2Of9Wr z9{rkX^8qcpb>#97gxI%#u<2JeZb!yGWVrKLvT+w!QfuPt=*B$9Tp~#3?vy&@DgC!T z$@)bUdD~BMQeA+7FT6>6|2M{E!;ivk*am;)?;IPBOtXQr16?|! zwJE;R%m*YxOfYL3)5vJ~{r+T~{Pw3>Ck@OTId|pw&v}`2U6Z%pm|ZQ?TDdE}|S>z<>1_>;4C=+y<99Qlx&rB&Yzqqk)D z{^TE|_X1n5KRwJ?mAtfZ(Hf4uEsv6wD3}ZdQd1cdlT-Cd-+xE7O-|NcdD%6K^-X zwSRMX$6Ve(K4s5TbU6L2fy(&!0m>J=?i`)`G>N>uV5VKT&X(t27hXuapmbUKAL_%u z@ArML!?dxqLm$X1q;93AF?qa>^lO1GqH=Q7JoYP(e>XgjEB#BS_a{#m|KDni@BHcN zm9ywfSRPk7+kuSgf9y|MlJP@ZhEvwl%h^!44F&kI@SlzipGmVJSkCr`*`RXKfQ<4N z=z}YNUTDj3&VJ5U&Q1z17xp*a29Q_sFZx@OSC%GkzH-(XF0a~r10nH0)SvtJCqIU* zcAv?#xhiKD`O8@`={^qKgVOu#+Klu_FK2HGDQ7E8Ia{y%Gs}2bzFJ)a1u+Cu4Hn#r&7H4J}&l}LV7`EqWJ@%Jri&z-Z+f&JR-myhWKiXqkZI@6A2 zTjnX?~e4AsUSo9GXae2U`Q>>n?x!>Ok){=w7aJ z@o`(PzrIT^U)P6}uhluK=eHxLGdGcZr{tWPDaX{Crai2oJ-i`A&dMCMKlxeF$cDoc zcH4EdEoY%^S1r87`3~>jSDQ2m%adVk*JdVx$Kdt1U46(K1v6!8z?S#ll&L9X7rrcU z&@!d*K&};yR6_T0-ktHTnLjY?A7NVw^VUgImGXjM7&m(#B!UU=?6ytPZ%iX^*DDjr3sufL zU3sS7@Y*5&IIK1H$?$T%!km+(eo(Zb&^JfuTCVJdAG8#N#iiSHmm+=KVdC|U275c=av3?E?}=W4tqX)9A<3L zIBXv>dtaMKMoe2gk4$eIwnUW$^$_~-lf67(&YqMatCF;(U4;k&pCXz+6A)01GdjD{qv0yoA*l?UDZ18>kWxy zlVt2!?)Z@Qe~a#H?a~;@krA`Pe5kf+1R2F|OxSB&Besmtw(7ef{XcleT;7{pUVQAI z570R4f%M*!>ge*rqLKBbUR$Mlzna5W&0sh4ZmqE8`P-_4)%)`OW@HY*|6={VLGJr4mLF>2jB3EN1qYB({1*CNibwLj zPTlH_{X@#n9S18vwYkyAH1ZBMrjqS#q;>UM6Ujfz_PcDoP8xW&(*{v~2E%NZjA3N# zLk2x(#y+l$u<{e#7bZjH=KwMy<%#4EWXB3yM(FtIi<#x8B8U15-tUU{@PqL7W|p6} z#nDLi+tSO=X5?95ru=Mwk4cM{Qhv50vmX8zD?iid*#O-J`RyaNo{aLNansA3oR^xN zvBynir18kx6Um3gzbcJCecbepaQ;^KYbzZ6#cBNMYhH%=DScNl6~@eXrBbEOPl7m;3LSQCktx&NRW>SLyIRxh%ZCo~NDZ%V#{Y zJiVL@B5xU(DJNaFyoHt%jR&TY*#-Z>$_ZqquUhmn#skpme7YXBePv+Vz_JZuV^v@S zV0k{Q0c>jkYXR#AJH$uV0oE75dcbhxJv&Kk7KzAC~i(!yzSvsamL>2D@{J>i+94UANL2OO{TNgLb@m6?OJKKwJ$-pIfE>IrT?<3gxNc79sf>Kes9(~p#L6J>W-lC zIzX*7YbqfBz^`|Ee0Z8kGnJ(cgmn;hw8D6Vb%XTT4(%iy&@Xf3C#_niPoEYGPS=+_7oFVyE>8jHn^~I zuy!ziUatmg16wH>Jfdp^8w4|Tjf1sF0U7?1We-`J)+wN)&%Cx z5eXXwYYbpJz#0PBZm@c=TnXb5?*XtnF#r9#`LE2BR{|E4R{ zEqVJ(E)#9YE?gU4Cb|hLBFtYVwt`gzuwk(J05%TR8o(yOy1?G0c<>bQ+Xprb_9p?# z?-UAcqCC$j4+Vv^7d44wryH)LBTjiJBkTZSZ&etN(yaALj%+W=MqRw*JL(Y1ip zf%(fr2Ur(ag`u+PdccMQ@&>?$!2IQ51Z*12p9d$v_6D#iu-ySH0X79DyY%S)+*dLF z2lJPQVz8jRa?<9`+G7K-fzu4@+MijZ~eSSsr9l6=CZM^YO zSOV-g={r3yU%Ufg1@LO@5NWir5wJ3_mkZ+&-2~V&Fn^q;z#70_Yp86x1Xyzb%Ppe* z0DGg4t{AKf%-G`SChGJv&$9RNGl(NW3&onVEhCXxq)&D%fIW!apn z32tNH9CkHq+mW#Y8SGRtGW=^{fjtbYg+>1yI*-Bbnx(K8yoUO{E@AI?H|g!}-HK}8 zRocFyN@$BtbLw(9`SaAZ2&vhAU3uYY&)2nrYhgV zU{heq-*&&s-m@$FcM!Ilu!&TdS=-j0IeLky>1d-b@>!0Ny@fa&@;J)&!N|H%JDBYA>(~_{Z`zUP zua_(~^((%9tPo7{+0T}=B5ogXU#n*j6IO=V!?0jvsaJD9)SYyjKg!_>aFfK7s3CB3E}#yG-R z?_O!20W9NtD`~?R^|{150B=csIIkH4;cT3nw$iUbjLOOsyzAi&D=U#Dj7JHZaBMQ^ z7qV|%^=>J&`DZ5VZ>-TL)F^OO&UyU1E{B3;`iG*chi*M|CrGBLKbRXbdq2-`x8s_A z=?3Upp{q~PxqDD9w2Nt#jfO_ik0P(d6&55~+Lsm4AxCy1-t=yB=ZXV7*}F0%(gA=W4Jq z7c+4_S#f4Q+l#Y!TcO(pommSdp9|{*%Rbx7-%7JyumUiXA5DM0QQdj7PBTQ9h z)aWwz+h*w61zja{#!h&pYZ|Nt%q=6;VAe6D7ua^1XZg)(JIwl`nFBX#fzH0vy66aP z2MD>&zamIxll(el?L8-9-$mMD$FCV|>A8ut^PcTs;kI;oNBQ0_c?jllx;IV$lBQ^ON-|u6Xvfndo0?ZT@P)G_ye?N zE|Rj`L?(q92f)cU%~X~Z_d$63;5B0i$G>A>JHb?cMlQ2$+zD3Dn2zlQs{`}%Mvf(a zgPkJYEA#SI*A#$l2Q%v!o_%J1+MIP}zJqIlWM67_+_kR`-rV!j^)`c52e5XqPO!I2 z@AV&!JZ|+ID*sc$pZ+9rYhoRj8?FP|O2hbBWK2q2Y!Ya_6>@TqrST!y?mK(sTz_P)XOXelE{~N%nz>XJoo*lmy zuqLpRg}J&-{?=(}wzE6wEIWIl?OXt@^28Xl13q2Cc7l!huwt;iV3S~GoMrT1g=bdg ztaIva@#Vjs{Toe*#r5ecQkW z7iiy3hu?4CUWdOGTa{P*>)zPso^RAVxPPPgnj8ZCo|XS9-a!2mu&)+uVuALxJN&9| z^{CD30*hR1?*p>)vhiPfzN10*yHB=^!B+vF-u0+l>;x+ZD-&>#av>h;jW++gMa!eI zQV7-z|JedeAA_83$_iJcIzvF|sf4Z%y3R~GHGB40kxr~=TcC|JCz9XHq@{}XXyuDu zXnUajh-f|gO&gTTOQ)yzchv`+fNvbW&pUknG&W_$Uh4?mQzIMmODVtaPx#JTrQ%I@ z*RxT6TLx`6wAs?dBdiXr%ZF8hHIo*b!M@JB_3|aod?{(`tYOloZsO`_t(VdeD0x$b zSlxfqw;e#nK4ff`j9;;OYkU$IKWM)(kcw!Ja+DJ>+b>bJyH9cp~dPIkIaM zEEi1gdXxutkO#8C)XrHyWHexWWy+GtKNKOeH<{0YrIjT)TecOPKzV@wrPx*t&klG@ zzy5=EAD|H|a)~nsdAW_Xf-MVRonY-?{_$-uSZ4s+2G$Mc##QBX6s!x(^gWCp=__xF zk*?h1W_KyfxM??ZL(nzAWBdC`)9Oo>^=A6Bg!$(p3c>aTuu`yTF#o(mCD`5oRu8rt z%v~QULdSZrDX=$6KmE0hT+pgs*-EERznO69Gw0L{+4=+4Tk>_TM$;i?P% zcF!JjUzK$mvUpTimz7aJpyQ7M@VCEyF=ym;(V~;FQl;1#8@0eY32$7yJc?rn*e)

RXOr}8R1o=$*;EVNj}QEbp^XB1Ag(^R*sWFBi-%DoO12rZ~43n ztn8UR_L>QKq{Yb_Tfuii>)Is#VXz$mY#eMNfK7sp2e5r$+XGnkTj?(cup+S009FPz z62Pj!h67jw*iZm#0UHE668rTi9vzN6Kh^^lv~K_`Xx|8!?DO+ZfGNI*7!9_LDMy|e zi#U1{VDi78H}^!!cK|B}Q~vSOm4juIZ~Ry_SU~`51S<|;tze}AtP`vvfc1h^`7q_> zZD4g^s`u=2afT@u3foRt^E_cwgtgBT7AdFxoF}Y^u>N_%DhL~zC#;^Z?em1S61I!5 zV0ly=x(J(`hi`zeee;Bk5tiK&?w3izibBFvt`Cq0$_P`wuztplY~P*|)qh3!t&;Hb z75+`aP5VNB$h0r|7_2>LlI_)RW4#U9V7w%!S^g*NRLS{BP|lUE7}HL5Bd0G!j>^h5 z!uknw^PTjLf^7o}<_Xd7B5X83KMgh>LSF%W?%VM{VV~hWLfh%xTf5P^j?SMQ+vPcR z9I$v~yiQ$}O)(k_`LY^6SAE3s<(4DUd}-Q0!s`it3$)hdZg`W)zY5<>c$*u3W_q}6 z8^E?6!hg@Z(!m*%dgn>CJk$~B(9mxZ?-ab%m;QIW#qXf}{};T~=xu~|@UleeP70^3 zGu_V?D;1?lO? zX~@UKkj4h0FN40(rGNdw>1AWnyJ%maKU%tYgtdVU_%P+!&0u3-H;Ks9vu5sFGfmB@ z{LQ#bv_sG?y*ja=Z@EuGyKRBAxg>tsH7_n#aj1ZH2wH!eyX?9bm#cW~f_4;If4ru_b_Fo&-SqE$nC4#! zNwZ>XhCt-?}tD(^iR(naT{l&G3zV!qNBo zG`@rC8-=g0!{KAoez0u}(-1jj53FcqNEe7ifVC7)F0p4n`tzhna zqvSP$^#`z4us*O03D=|Yp!83Goyog(Mt-0?@NLXM9W(^}(oZImr-}Z}LHZA5&`&|% z0KF+cIYIjL@*}$o-phOy^!|La6l|*xQ@&jWHVU?o@~|G-l8rB(ZzZ<}+JOboD!+}A zj-{Q6WRiF5q{IE`pUO9Q(93V^aS@-=f1k^DWEx*8pE{cr>}hHE#sXLi*k}OjaOC-Ud%%VRbOT^RVD8=v=@@b3`Sa@p*m!_<3M^<}0?cop z@?oyh6W&XBw~ovUr= z`oa1FbVFbR0c<u&Puy`XqeyEH&QC2=h(R? zwKnJ6=f4*lT0iZ@Np(%6iv18^Z`ZpSS9$C9+{Q`SUv0){#RQi^+W~DQ?{*)JX;!LF zXKK-;R~>uglDOrW3!iXhTN) zSjbJ=`omW+Y(10cY3xd$JKG1=;KMXV%*HqMVBNf1H)QH&!Nggg+AKuHBKH}Mfuv2NWO=6txIi;vUGo` zegBaBIzjj{!rgpZ3^oN;0anTTeCd|Gn)v(pjn}j8y;7x<+CPkad*|me8NP9|86iZ z9yJ&r`;MFf#+A(6b731I8? z1=!r575ifNeGz8uQ8tyVWju9fxXsd8g&%tfH#(pE-_hBN&V2*nI%U%^;U)LYAHI|D z`gy{Y*As+y5`IER{#>iM4hqSpMP|<%cN<#KCqr{$b&hPQs=+^Bo!^#b!g~)A-bwiO zdBWAt*-ChVaI;6tt`E55#opUFN?7UF!ozkE)-+F8g0P->!V1=5&pcsegdLbCtd_8{ zZQ-`8C#-p%uuj5y=Lzd4Y*Xe_uv2(I?3Axip z>pq$B_CwzJh<4;`1sec+I#pKbTV%{5uF*ZLYq*ECnkZ6wGiqSYE`-#~H965=5m1}K zaEiIdtN0Y^(;wOM_O|`zECgwBW-2Z6v9TJu66k))yB=YUV3R&fb!Dsa*N^craj}lE z{CzyrFED#su-@6AlFBM8a-uh8g9KhKCc$vts<=^YN;ityNp%CmSb!&^X!t zZt*_oH$RfT-#`0Q>O-)j5urzNi@^H9{zLlt<(jnP6Q+FBD)ufT*ZziSE%dEF_2f!M z6IctFsk!dYEs4)ozKeHOX;jOnKg!Q|N?#;3=<< zF2D7HZ3Wvozl={C8P2}L?aoni zuWH%yHbp7_KHAucQ4yQFshS|^hQ9F+j0u(JOn=MkH`b=FLFmrm=)SxJy~Pf0{@cu{ z?C$850xt}$emhYziIUOiJW5E?NIP;HPN)C! zXV$)SAjibR^c$Jy*kXD6Ud>&CTy`9fWrz10*W^UE=51zkST_UP-Z=f@_^P}C{^uU7 zi{Xp@gU~8MR}i|E&}g&J-XJR4Ccs)pkRd%sq*XrB)e_Js^y3b?#!zkzS&uh9iwD2fjOLp0rl;{5g*-gmqLiY3=M{ zZ~}E$hK|uJZl})Mp4G?~-D~o8W>w`)W;NuEW~sRkiHOF&b%-4%jXM|3Bu`?~o)tUb zuVc)3-K%3v-){{PV?!1qGsmVn8e#Scvwf|=w|)L+)0<^pZk>Me2F{Iyi?=bw;& z)d`X4?W(xt*b4Zo;jMqA!~6Z0#@h*RMWMsHW#PQ?!zjE(ubxSsjU84Kyk1=!@Pm`z zr?X;J@>AUFU2#JGhpFYxP%XDEr{;wGNF}^w_+a z8PhV}TB9Ct6?NowC*(&fVL#8VBx_H|KlKFIP5-&AQR}fY_MIQ(J?G3V>y_fzN%}dt&6c86|$6FBqjDfnF;7ix9u_anoFPZ$rM`)Q$ zh%Wa$+TQ?OG1%S!T{+n909Fk)6~G$7Cc)gj&C=28$aBw?7l3s-@|t*$a3724^y>pJ zyJ04E?qAp-SS?sC?|PQ<8w0Bb)4p%VN1898hIiJGm@wf8nUOgSZ6CD$cw6T){{+UA znt7zB5UeMFm4bDH`R}Eu1ltB?_8&U;Qq+TO2lK~!J=hMgVq1l=WdqnG*b*Ps4R#=a zZ3QdbG-ID7gjV(qgO!76KcOCB<6unzY!a+1fb9bt4q(|AkbYpt8fnyP{9gnXxp5}B zUl{rHW(Gj+nH6tOw-($k%Dk6?nqOncCS;5t2`MfA)fv1+YjH>qTG+*CRazU=?5& z2r%W7G3X|Jk7%RCsR?JqT$NKsa3!=Y(0)?1rhJ++h1@m4lAI{9X-&=t-B)8r8$8?L zanrpJ*3Dp3U@8x`PVYX8t%U6+?8m7v);#Sq`W$R<%%PQ6944Uc`c!&eo&wt(z!G4c zU~atif9{2>Zw9bpunl1DI;H5!!6eU*RfF|_(H%99=o-Pg16V6q(7sMbp5K;UFxltV zu`NX2s3Xr`o_9F%{Mc?so?Guo*8woa*IkpY1k1n3wvX9LCmqZ_Ja^6{dVTt&3Fo!E zITSToS`Vnlw+)}3vG23E_Ma>Or{8DMhK|jI`{TA5tOv}$f2a?v8_cciWb>fx3t(f8 zJW~hpO>5EZ1ltOoAKMEy3g)lpA{SFW16TprFqnT|$x^T}AEtJ68Q28aRlM7MsK9wN z%C_#LpkYhRV`>scyzTH7bcOS}bD-k&PnXNTDl0?qHozNJRwf8*BO3TV|5iik8ytzt2Kn3ey6Z=t`Q=2c37{f_tCE*`~SHe0&*nwa~RfccJ8& zy1=cU_+Y41a|1xNbDxEH6qgRtWE(uMVjSHci%jdcJIP8SYH6M-mmrKnbf@pigO{@4zPFdZpVdjvboQ~i?i}sC3JhC zJ4tlh#UQL6tnikZw0-*P!Aii67M-b6^%XYTznz3t5cW2O8Nbzp>e>cf9dsv(jz_vi z!8U*?&U&i(?Eq^Bi)udn#qP6cihh|khnyN%FQCI9`NbcgefaE*eP7{6?D&;~wS&Ea za6O`{2J7@;@?j%bH<;-oyZyY7`z$)48-ULBq4Gs9*bcB_-fe$*_gM@PwsQe;cR@D| zo$;4*pT#s-(dTB8D(cc)`~KJq#61;E za);hik$`uftE1i4k-I(`sl3(E>tMxTU10B$j*Fc12OAF1RfAc#dAv%)MzA_CV;|+( z?BbvjQ&>A;Lxf!+x$J|xg8D}Fn|$9xSmEt6$@eMDlm}DaP(!gQ$e>Oztb4_d+`gt={#R|1Y8b4`2mg!(jeAvJ`9|*zsa??z311mft(G zp!+P=LtDN8TG`YCZ3DEfE@1;;Z9c3RYy_+u%*}UQQD#~2B2$2-?yiJp5}IAm3_0!j ze0|tD@v07>!AaGmG*;4G@*(W`LVCMd0ahQtYQgG!SR=emV6|X><~?$;_Ud@+8G$)E zeG2=^u&QXCxwzqphP=9shiqEZ72jgr9J__4OSX+7e;4xgu19%d2iQ)qG68|I8@v-E z@?o2QtwTHCofu1@v+kHlUZ0YQm%7v^_T7oWQc+rCPdh8Sn&EAL*KcDxSUuQ_*(jc^ z4!@~K`MZwaFjy!2ZM^&Tkut9Ee>;O~1=4PAs5$7J7`ZLfCjmQ)!L}{X&RU26P}#&& z&9BMf_v$F)H*%TJZ)*d7s}8>tV;iz21NM!A?OLFHdmVnieUXpY{H-#Wyb@kMqN}jb zUoxu0?!>4?R-`XIotwZ8EYQ9#hhKHO9;J6bSSd0u7GUynNWPIQXjC$y{Lw7?a9har4 z0;l&!1+KE%0d46QXOa(O(tgxWD_?Abb`;u=h}J9DroA(LW_mHvkUKG^;7h>w1&7a{ z#<2um(+TjEn|!B=JwyI$1G4ZSiX=@2LA!8sV9Q=iTr}*4urjRAC19Ri5a=HVo7i_s0jUU7B#5e%mD0F5|zoBCu$(ctjyn^=bZf`zAc9eoee3-;b zZxwg~IdK6z!WzK#1+W&dX)ymBNe9^80M-Mx+lLh)Zvbox%(T;dZ@VkXn3B7@v=LDH zwi909vrRm^!1BSucocqsd{RkxEAQ43zcY0j-#Y)024<33^iljxxIYcb!1@AM71%%k zYXBSaVaj(c@;8{)cdf7d)})R9jHy|hHv4JKnil<`Sd7hfA%nB(oan~Tb%T>tXUX=R z=&!$LCiw>5^$6Pw)(YmvXQ=|(7(apy>D|=nW^PS2X;rHJrDI-Ba4EF;Uvbuq(&~R_ zR*-6ab&hj>k#~+G8@xbeOMP*E7+@iSU=bXFxO`B4uNe3TjHbJ z4z?BS6d$$=Y!d7!A2toP7tHS$>tn2s-RtR)Ukbqrz!u_{Drg6w?USCD;Fk^X}>qf33j#iQs({U zAbB&jmoj#%);u%U)6JTv%2y*Y2k&R@n|JH6U%Tb&61RM9xRUy0(2J}3J>6gj;Q1Br z>23T*>wGe#J;lSeAYDk{{sF1-=QzCm$mnQ4?gsTtDcIm*+T zORPJ8kNusWFBwmoyR8P1+k#wkF88)fxlTV2x$4f*vKO|c?;>tugE*_Y~`t;&gh+Qk`;rEuGStyj~2414q3 zb?ELycQ4ol7P|lXyz$j7I@gu4$JZ%)-O<>7Pq|--+3O(xsova1JQYLhK4kJ#^P5J0 z*9ddI#9wKWF&-y_I{W_B{%EszXc4c4eHrOK`XF;;^4SHh-9*?qA98AHzoO%_y4cC# z^48?6M_w25CLWqeu9hEGrpYsD;O*UVC+2loLww`Jv@_qwaVI&woU$X;3mJRkDVa9VPz`2)#R8Dr#e}i*)K{F4WiP! z4Vev(Fz3ApnZJ0>Ne9nw-X19@z1gF1hvG@vpwFM)ijLW<%zyqnz3yZGzI&(FL@-{; zAF3~pzF?1;yRy#11_#T(hVk&@_87}L&9==SUoNW9CGy?@06NI>ryk#(V#oXRw{Eh! zVq3@~n)fT^m%#5G{BO-X%fI%cy0mKp>5p!IKe8We9PBWMPW9^$*mkf{VYC6xxFt1C zpq)@U*c*HCknGzHPlWiIIbQO;$Me3_J#kgg9EpK z32(!-^jDE{obuNa;)$I%>l}msyGeYT)cHmnwx=ozvwoqrun$`%e&(&^_|M*%dF2{5 zSZTX|TK&1otjq3%xB2HY$+Hv>uPw9>Y|1sXg==%To7;Xhb!#p@>V~Hk7VGkzCSN-3 z3OPG9uheSpe)Z3Wgy(@{yS+Tvx+&geZHe6+Ro+t`_}Y1U%v_qa7(F~n*RGGV9{Cb% zR-PG!XB#~3oX#?^9mHe6jmLw}yrg)@zH!;&+IKztJd#_APe)z3SHE<*CFF(gxN_fZ z%T>HPKf!z@a!nsCEv@*fmXknLzO+)=9mbY@@O*%GE6cXU-@YnyI^%b{mlU2x*K>x_ zttXn?a_Xkhr?B-J$-n1m{*ij>%~B!Hl+7{%awvBENL< z*M-Q0f1&wHHqmsmf5yo-$J#cjuRexNg~(M~=gaFg+C}=|lm}Pl$ZylwFb=Kp+drNP zi9@E}q?<1dMm~ma&0|^*!p9@smDl4BFPey(>;Rj4&=>-=x$Ewt}*f6jqds#j_xH3*Ika!uX1(&b%!*WcvpOq@f-3?DtPfe zjXC`Olxlh-O7~{cXX#T;x_=KDUb=hp)oW;2PO+=9R82^pmG2uZJKc@%ud{8C+!1W3 zMDDe``+QFwr~dv*Q^!^3aL=amZt6R9Q}vnl?*Mjnz;E)x+ibhi+q&?)P)*)H&-F=` zZI8-v?MB8Q$U7^;9{QrGoRD7Uc94eSu5W(wq~A9$TIb3)?KGCBV9WKqTQ|YaBj4=A zmMP?3DZon~e8b`Slr3KU5^M_!@omY|PMR%GvuiWZ*NPp ze?fk$X8b+V=+rYazmR5Q{{XhMA$NJG{dL;*@0mhBvmCx%*wX#1lfNHC7I9ce{$7d? z|LNMW#kN86N;ff{N1oEg=MU5G;!Y!#GILXyQ}6L!ZR(z8e4lXbICUg=Zw`kO_;kIp7Q4zr#t@hkFNsb3U07v4yz(_VuM+?Z-)l3PO36j zOB&_xve)FSt0#<)GV-o&Bp|WUhTu-#t)dgt=EnEk@bk#;TajCV+>3d)-e$`UrX#&T zMLE^ht8SPkFWu?N%kt#yLtcX`Z{~^6d=VzEl0y1(SKd$HAa@^f z*C`I~SwI{*vSL@hNE|9j$0yNc@?|CcM8naogP-x z+i@uRH0|B=Op>KT?wLck8N;I|g-Vy)Dk>RoN`}a?w@jNJ&k;?ZlSDfu>yI5v!hn@8S+GCYq-YL)nz#6F6MLYM7A|y z+iTLF^cnk)lm4T&p8qQSDciqCzDfTMTYe?Fl>Sr5?;-uIYr)S@{$f9aGrvM^+7!o6 z%&&|ET3a(mT8)eYH#2@n{_o6%R+9#8k=f)wDT~&Tc6^<>!uU0?*NMAwVo^Q?6Ix}L zVw)aco_Y4;UY_ZW1?DMLPRjW$!%r*u-+JaT{)*jGj{lF@?9qV((IxD zZDO6o{zgUgJ9g^%`!=dGhsB>iYtHyu7ua?G zyo=N2tu}XgaCrphyBj1u*q+5F6i3fJ0b5KNcp{5$x$Q}E0~34o;Fpd3rjR?Z zYS!+j2`Aneb&=cu3u#|>A^arL$F#4qi${53*=J~9kvpI@(8I3&> z3$0Ae{SP7K{%>6cUG#!vi+_$D+e27zjY_Nc;xrS&yt=eJ2rggrP-kL+|2Jcq^FU4u`O4A zA42YK05^Q{gSIazxU5q-(H4or(=(mc$9zbJW=;K z)(;wHlMkqj?FKhxj6GR5$IMvB>${sgQKS0d_u;l_`-I8Y>raVB&P12#cim^}swHd$ zT^;AnCZAPb;q$I8(+~369O|)6i_G0X(iOcsw9T>G@(J|3(e?Lvww`?Sl-$Pn{k++< zvpE%D`@l#7^N6k%Y&w87f$atJuj#ab?FP&9@oom20;4K1kNP})j=bZA_`c0h#Gk{2 zS?AA&?(I>2+(~#j;iJ5(j`r4jLcXuW8jSo%77 zX*4T#dw2t2+8X8aY4q*7a5j01^zm(R|2$Z<`C+C8AK}W+@zi`=aWC_A7tNaSon3E| zm$zu&v$Gy#+WII*hn8|D6tfB$lw(iXr-(pl(ocBl#j|PqVTQn#g1t&SJfhnURt{ET5WDZQ3#<;TLl}1X@Bg9i zrCCr+U6;d^FV4GN-U>d?dOUn8+mW`s{ByBYa!bJ{!PO_yS&dEVZ<_D=UXwZp)kIJ| zbQR6B$zh8>O!@cH%&VK6u}NL*>F^}0)=rI(e)p=JLBtM|jy>yVlS`N*v;O?BqsQwT z1lk_X@khUoxYZde$jftdU2LeH=((XBYjT+P43!ic36F|%j-cfW^k*)eO;%#R9+j02 zuw`KP2;jS6o=-Ult&&1MsCxJjlUKGuI|^;NXiZ*XrjZ?Ie2>KBfpNko2{-fakSNV2 z!AdWiO>Rr^n2{4(T$aOoY7XDoHf?z}X*LAiYvs=`{x~DuJbyZCzx|72e+=&&rOKLA z`ugy%_^7Wpd zN$F)&Aa5V?+OM2VZj!v`f8^9V?!G_ovxTd2D61+Nq=7mpvZV}vj$cK6F5Urn1Nl3{ zPj&O0FQXr>I!@`|)jpfNN^MwUraZ5_IDM%8EM1uuHgHPqQQFbRe(P&z)Aq*g1{(u= z13LAn&Nu)z>7!GA%>N?o71)r7OnvP7C38RIGH5$C%)aD(e{JyXhA&$>cx20Fuqm)y zgV}t`O`cPZZ*ao1v zgmq>st&vyoCHl+I9qN;}6l^OP!&~#L=ig=cuOBRi(bf+h^4CFO`I2Rp#j$_S*Z+b~bso zY(D#7wz+MGx$800GS0gxf0V~rZ$^(~Td()@>_gA+=Vp^p+9&InG(BcqY08{epP0!l zRt-buDpr~~pq+QHHy%B`(v$ol|JrqLV?X+DXN)eu;~8Ax0koJ3G;7{lK%+x|8|KFU*>8 zhR;scO6?|yv^IIC^vjI*pwq|Ytk1c22I^sxcbqn2IxF^@0zvmG4XtdP0_AY7m}7n_4ycfC7>NpygrSL8m;qr=Rn$FaI-9Xx19G? z#wX+dlNCAZOlMBI!Pc+*Qb^j>-#we0k<4m4t~X2O7L8+^z8|HP^_H|{fvOz#<) zSAH%7_wuthKW*}fnP;hH=V4S`s`>J58|QRpsmo48w)G#?8g^Sef}Z?)XU*L>)?dLF zphtbZOg%LwFYH3kBd(tB*?N|ttK=^F3-`??e<|PJXX|n1+Q^U2Tpgv-O!uYjx#b&~ z@HfL>_%+%r^{qNwJI$EbYbT69oN>%VR&-COovV$V(Q_ z8~pF!#`j@yGIfC&dxzJdcHLBael+rz)AyKj_SSQ@9<}ZL_b^@?_S$x}VMFLnfaOQg z{eY|6tm`l?^RIunZ5a2mhxg1=bC6YZj0Vx)h>| zj&H4f;prM?a z)hTW~y!lAem!yty?mRT@&>Z<9v%$01rNE~*5Wih zCjXglyZQBWXYnPac@!Z}byMm4skhOkv4%DEeg3r{rCI&Gv?rtKeYy2uV_@$5MiF!y zz$UV&U1 z*4Xw`f-U>eY;v6ss|RZb`>qdL4_5NyS(Cp6D=r(rTEXglblqShU>|U>W&FPttnlGk z^BoG+`-JCK=cfdCc-^1eLZUv_fq;A+~d0*3F zi#*$|I9hM?XMdIU{*hVxj4N#NM-kWr*d*_^{?l}}l=k1N^R*);%^%b#icbT)ZI8|- ze=S~~Vty@PmD^{NRn%LmDl{9U;~xyexf!PFVWo99G%b(KCVMHX(&^08oB1Zvz&)$` zf~_^x$eZ@N5|#=0#~+_fzU8nqe#&{54-d$`eV6S zEAB?dfv0Ef{pX$xTF2;6ij*@$${!=pPCPSf_UOu2&ioc_vf4T(OzF6q$r@*87HlSd z``|0wIcuMXM#sh|`H^`P%8$9vvhdy06-AD*vS zE!t?sJ}f8LyWdDsnt6EysT6MYOPb-W{>yA~w`^d}m0aIp9Bzi+xUH3P$lBgU%0(w6 zJrWW81RvRJ;XBxn-47U-TAok=${2E|AA zSv(S=7~Ab!uw0*KDz0(=l;KNB?~?mTkAKf5v)*INI7l95t;ycomf1&Px$*%`esJqQ z)eec7s=QbhZO)x9Bk!pB zu{Y}YXPB0lCH2T|1Cr3lN0;$d`Y7Z@P?*Y z?27qV=2LR%JgH#z)xMN$5sOMSdmjQt>ExVPTa1v*Auc(2#eCW|FbmTjE0`~Wu^-M? zUSdlW!u1&e%U0zm3(kj!X&3!`c%~e_<3d#&$`JdhqLIl$c|1`eVI|cp6o&=G?hN-l zm8JSbb!=AId%~Q3pNr`?IP>R~(R=JhmCgZu#3pDrK-=-=x#YVv(M;X!Z|BVZ1k=v3 z2C>FBz_VXr!`^Jtx;;DrS(3#D)Vi=Npv(;%q7<{7b6`RZ5Vy%*eaqVd8tNh zMUL95qVF(1IcYB0^*bIzl5}X-WrE9Z&+z+lgvt= zPL+H-gsk<;=ghuDyKiQEtWND!DIar+PT`)@@AENpV~x;v*UTmF3F&)bZ0wD= zU0UtNcV$j9V!PqVt({8_%IDnW>G<56H}m=k5|N)}hA=<-`4iQ7mHs6)onQ}boDf^d zFM*!=Q|FTHvW>BxKkrd;opFXv(LI~Ft<#xbU-n(%S2vf`{v-A4PGO!{^Bwiz1K{rz z&bVHE5#cT16X5C->YmGsopsAj@ZI2p!g(})>ILgOZO*}!(*BCCjG5O*q077o;!Umd6m-M%yosya>@ep znLiFK49w7fH;XRbi*t*;~TJXE%DIZy7*lzL~skSM` zec|5Bl*V03zwsN`UrJnGC;D7Fu97o^9O+ZNYaO`9FNgUFTC+lX`0dImpi;?3j^b|Z zfSyNk4j@PVGWLw6$qBWm5jhj@b?oW)3-$qBV*D{=;qW9(V)$>~DQB=%_D z!fJ5kFlV?)cl6N<_mxHYXD@PEU3_RC**|bQTx+Kcp2eGrt0)Sn_=p7XYPlcsDBQv($|{4*Z{Ju zi>WJ9W%3}hTIc!9Bl9sO*tHK?o15qCZ(f^uck-aQ$M6#4Vya6+SM(738=!l$BAiCo zmDp!o^lQ8S-3I;U4>>gn^Gen6M>fxG)Xl*avF z+d^oE!N&r$<6sjZv{T@_1GEXSeIc~@qx8Q%=-Ht5p#-cD>1)Uex53U6?*^W%@gt;p>FYtydKHKCo^dCXu!LhDg6&@O|_VtuyX2dYpS~ zOy8y^{oa`xI&oZV7gDFvs7X)HG4!XG?MdFvyLBS+c$)bs{mVW)mu!@t_quvazr?H? znfXh99`498d+9Q}B`b0?#LVo_pJLWC3n{N%=)WAD)(dw^y=iOv(4T+lT=J{Zp9IIB z&bd{i-?Sa1LTdj%{US4p&oqG6!_Ir1==u!0Zs%|7H?}VMG#g*nUp|-oopk-()n&?< zr^}10PV0Au`jmE0KF#i@?}?rP>3PD|BcHaSXII-?@?7b8-qrKJ>r=IpE##5iu1{~V z^#hx{w(rNRk6bmE{J8)g)x+5j)4y$>v**P5TlHwsk0{Vr+kGwTGGx$a=jg76uO^O- z)QkT@hpEReuyvHdSC6a&vPOB=nDfQ<9;>gj8C{*|x|?^a*!GLF{^a#_G{R$QKdrCh zZTfXb4)+y0AZ7aU6}=7@|Kql-hPUe?ssL^ z%$MG>zZ#zwT>r~(L2d_05mLA6>pusm@iC9ePAAxQFf(4hFfU(ibT8OGuyRou8@R*Ne1E~SLA;~T<$ls>Z?3fOQ{4ep z4Yo$SEqVDB{I?sd6YOKc$m{;KU9*--yQGB+I$Eh=g!+%tQSvDE!TWOY@(8N{+W}T1 z;4)ipEm;1>bgT)iHh{H(ZT4Y`?`E(uu(wKHn$NxOO_UKd2;DB|E=hoTBtKfC#I%M-UusSfK*ZDrdC|F+r+X1#afb9k=-{kQYA@2Zr zq618Q94pqBQNJ(a?BoaD;gqKr%qqMor{zB-{_y3b$~x_&|J*698|RY8&&N+k!8y0esFpgJHpHyu!K->`2>y!AbII59uDr4_dZDS)Af zn41qw{J{pmUMYGfpQ=q0{RCm#2=nh09o zFX5CK|C|-y$Z$r0)j1r^aNg~`PaUP4%`e|$d}1}j%Tvd15}BpQY(%tGZ_CV>Q}y@j zxJ#m4b8DHMRkO!J@hBfB|9#OJ52WT#E;sQ|T~<$cFX8XyJrxJD58^|v(I&rbfNl`F z<3wljo9zpwaW7$GAz{UY4Uu;HPPD)AXMLsD(Lo!fy~UN}RcLlXa})mf1@G3aHqDkO zDdwc3AtX$FQj7H7jIJByqfgkh{y7@2pYP0X*6F-c$arGqyxn}$UmSZY`zx`@^z%nFOKz0@_x|7MuR4)@?dpHwcB5Zq#`-zs=}xcA6oVClZ3Qzq$&@k5yZSEr zh|{U|T+}jXitoxShw5`QkbWhEy+iW9hA!_MT&>pLYXbSV^3Bo6&Df@SZ0i==whG?+ z(NTn)cRO{ZfBuhodY#j*iw;oJ(o0S?+{{@k5A2eCch4oqdAI9{Q-}k*Toi|sb66oz zg|*I{!^?hx{08kiMN6L6T)rOV=Mutp6P6Ib<*rn?n#pA1c#T74&nMMFH@cO6lWe_O zWEXI7=HXVuz=<5{M*E|71k3LmM1Rkmy@!Fn3;A_}m40O|`DMwV_K&@S!pMk2^drlo zksHYOao+V5^BaY}@7}rOHqleLV;c8R(Cy;Gow87T6Yq{jPWSPtjZVN8`RPLMC}r+;ekhi6Op zn$L7J_6M4pkorm8HIrWa7qn$Mcy`*gWv$PaYWVVh!Fc1_bIH#t`Oh?a_7*R)zc;!z z^}W$g*^6AU&O_)m+aIS{p1UZY=tjF1ZHeD(-4YA;Nq|WD_!*zP#Y@)>V7=JU`GdLS z`HT3&_=Nh+xwpmJ7wgnVO|g%LyM$dDW(hlNmxehzk{Q5wvKW)TNy$};g~&tt2v2lsfA=u$!h8X zAFZ%SXqQ2o%ex-6-}}HSz|Ii>KUb-{$G?o67UY~0kh63L=TUreBzqZH6PRS`5mpD* z5}<1aTOYvM1M(EFE=S(c62{&Reeq2)w}Q6^_=myTe3<%e<6x~|Me@CAv&=r>Q}Vjw z(J9&+d+55BT3eg-sQMw3eCsLBYy4y`Rj*co6@t}+eTsQ1Tb4Vgd&ZVH*HAEMuDd?F z#`su2(NE)%w=6ErZW$ z84;&u9hSzcJ)E*@JHIo0c1dQ%)AYd}n@c{&d&)mXrl!14wk2MudJ>avO%rQmDvd0k zOzG)DX2;`msdJ1f>-}JTU}mgk($tMVpMSQ~A-Wqa%0F#1jNd8s{0C%oO(A3bxYJJo zQ~D;r+Q7amB2&Mba|d*C{C6Ow`g>c#8XU=~e1`Q@`3lhqJ~}7yWpc zoML!4Bd7jnbIBD-BibFhs@dn{<MIdjn6LS zpV_@YuyzqGj^Amv6*y_p!Oiv(PyMuSq z%XamH3b9>V_w~qc{m(JKw`(r>nUH>}SH7HbitVN0yZ=_^#5xtiEMLaHJdBw`G{Ha>BYK+tiEcwwbcd=l)XV zp2}SH#WZ|f@bzW#8JjZrD4XJ2x{LA*-?kK=nGjUbhCp3qQ-{ z*uzw9_x&m_4QmQaynB$-jvRNKhv-N$nRVFZ@xOAAF6%<`*Aau!_zo-bwOIUdEAmuq zEA==uhkr;kSH%A#K%9zThYn)r<+k;eGkF_F(?>Gkfc_-{)h6XyMQS@EYz zBDX|^Z;i%(S`zt%dH-WH{$NR@EA~3@Z^q)EFNyp*CYnFT;#*20w^@3>(~5tyB=U3f z{;UJn zi#%|sXnu5P{71(|{$}214vl~3*vMB76aLM^;?Eu%`L%if^I`Fy9UHkVNBEsN@%xUA z{M@`hn-l-yv5{oXGRet}f9BZ8zjH-%{o?oy$42g5d=c-rM{E58y-;as> zEAKeo?>s#I`(q+cA1?g2hsU2eCURr`k-QJ)#~(T-@`L>Nw~vW*z3i?mprIq;w;U7s z?GYl~dqn)+V$Tc924m-Q0N^6@r}ns9xo{1{SO84{YOV`eTCld ze?@%H(UIv_6!HGqSH@?SL>_;o=%0UOd~b2&fg_Jd6i5D2DCA#-@!uRB`QxkNcOSj@ zAFqn{9KCq()p~#U)$t!4z4*yj$Dd!a_^zYk<4YEQ^{DvhlEZf#mCyTMj#~VeC5tMdR1U zB2(slIx5^PQ!mRpG&jB_EAlQBxY*@c=eO|Z16lD~7e{`W6~Ae5M}C>oFPO2}o_c*dw1jYbnq+$3s9jB&UT z1Xk`#7*P`mVPHf?qn9Z9U&hO54b6W^_J7f-iFdYRN^x&qK@Akkb(0#i#NkqJL4V<# z@3thqQz%WSU6>-}IC&eeIa04H5zk5eO|h8i=rzS+xuf4J79VKcr$p@4dSkJC+YiQx z0X?onR0j0W645QFrsr$DS>!qIEO00JDq*me`M~`VG_W1NWEfrGkqn2uIwjR(*_0QSlENP|Y*aWdX?z)E!Es6`alacx~cxGg?QowJ*%a9wz+=3rP_R=p3qVa zJ874MrWZ~j73)sXP`CLMQuO{Q6hf~Sq+(eMa<6ScD&A_52e(^3DDppVL-A=Ub2`BC zt`biGeMl9m#{V?(Kj8l5oSTQwJx6;>F_Kt7J(LnWQJ0CIHKFo!wfq^%P?w;b;OMsk zYJ}4J0&))}{Q{rf6HrfU{a!#W50HCRfLJyJ;5jR(9}cN^g1TEsy&uwhLSjG|WJ-Io z7omp#4*YrfJM4f{CPsh(>RWX$*Gyj{LkLk>zE|VYDRD6|Oy5@MBlXmSRI;_XEFLP+ z^~$O0Nrd#ZPp7nifi=a6bkJX9gVL}0#42A06X-f09z!PjB&Q`-_uh3(*bQbX{jn0O zDYm;^XMZZ2U?iUkpIA>m=rrN{QW;&5?yB`vC6<#9+0yC=lC1YHfkol})~zbDoy)c$})HQB#c;nbn-M)&w6hrAi_iUSXGAe`QIn!#b7(?M*tLW%-sm95#HUI>UMN2GsVPOGw_oom5aax- zo%aev-_WU33dO4-HLFN`7SW(=$syLsx!1!z=J1AG4f9@;dO}cqt@OyC=?6A&^Sx$VIJvb-vdcAZiJr2L_B9cC-@v-I*Du|6BEy36*!=f^sN!9M;$*3jAZ)B0t z+VM_^?w5K*NbGR*%#c{A^yHA3ruD**nBXrQ7ZS5+5Z60IEQ3QtUK9GIt1)&fJYI41 z0KceGjfVTh7gQqp_=&KVnrD=uQ)Hua;JQtJC?#S&%u(okuu6bo+`%eEap*8Y>S+a{ zhhL8_5PKqePJtMo?TjxFALjmLP=R3fnIVpsf7O8ADhNSrO)!i3A(;uRgV zr7m~)^0t*ytUVg+;2QPM8phKw}^Ow+M`z@8f0Ra;xbHj8D<}jVVZN+*C8=g>scW&SGS!K5}QKM zurH)RhK49L%wNk-sfvb06I}DZXZ#B~q&q{@h-;X&zH*E&;oBE}PoPRv5WLGNC zz@9p0ExKxnaE}M=T4s6=EfZVmacSNN==ud(oZeh(0+a{zT3X-KaHvL|!#AB%bu? zM?zw{ue5hatO%Y3N}BQ)d0@gE<~wCyju=Q`?nRinqEBEq!rYB8bHzhpoX>qI65En1 zW=C+0Xm-|#aBj=$L1E?)N1t2`@h5UQ%;ECt=R@K-pPnB=dtN#{BsK)gKtWIHhS`zY z$3N+X5wSz67b2AFeY3<$M~}|Jv{OwFqsbTj-9ST?-)^I-+L8kW3(vxzeEO?^SU@HJb^mzTBPbpZLaAFH{~&KF z*YD(sep0`dCzdGvVIGYr7UjtmzKPDPJn;ot^JK2>moJ`<>KF6G%BbE{Al{0e2+xsu zM6b*vdhdLqKV5Jdh8YF=!vZ;@P|q!_RZmC#L|%aLkr2;deIj3c?$ays#j63mFJJ5p z>h1+%eHJ{w&eFT`^DzTA|jGx=hAJ_v}5nW@mYrfI&uPMmWB-Ufxwc&R5z zv^si^6yH!7HLR2jXAhK!?+U5E_KWTg3VFuSuLZ<71!`ZPt_p}1zMl#40)|8Z@tj}x z42Y3|vnYMgx8Hd`n4Bx-`NaDQYdav>VPNQF1awwQ%#>;o!Hzfo#jmnn3pe~ju2E&^oCkT&V z*Z{mY;TT2y6r5MFDFp6wS)sL)mU^d!INqc1k8 zz;m3I)8t%O@Hui&i!};Gc9>76wb(==Jv31VYSbS}I4klXycX+a{)#qY9M$Egu*d`V zm#yLM+eX9rY#TUTp~%OisJqr5Xwp$wtcN?|96bPsJM~g2Mo2RyIy9ROLn(u%-{^@_ zpaF1|=bQ)lV?999!ik~80e_|kNep%j1v7QyctJMN3v)$F{ZOu;anJo4b1)?KqmGzI zyc?xXqPvuORgM_$;203nq^lpThyFr~sY*}8iU7rBqK^hTGkp33pLoxQki?UIjmkL- zE0964gG%N@0i6tr!2!K0AeILZ!!1GGGfRxeFfAe~LwZU?^rd;~l(2q0BIbnk9#oAm zshXUnm!dn&q7lMQO4~l!x@V3UnXT7li`Chf5K$^d&WF!%xmbQ^3ZI)Xqkb)avRdlJ zL6LOywjdg_c5h%x;r!*JAPk=&X9dLujk%86tDA$g+OJm!QLi-Ip9M5n2L{PKI;g>k zV)+1y4}S4RRIhqV*||uM35c_FcPW0ZCk4cVgwCgwdmF1(0r8R44+m)dcdHV+s0#N} z`Wr1CRk~VX(m~{fKHXo7)jr)ri=-yXY^^`?Q;WRAosxi(^6PE^QHdojzj)VAJO^!= z*^q3xLSBlxye2FTD7`T(`uOyFVKLD6kB`G*j-DZBheax&_lMQ|z%~gQEiHw`mXPif z5hKDH-dM|po7Cc8!qxd}dAVQTg@TFFg%$}tKGw9A*dX;pg_I;R8oAL*%)!VP5B0f} z54mXX*J8ZZ7f=h;%P&6E8U$Cn&((dSyn2DuOCw^h)N?SPD?g5SQz?{HOH^JPX-X0Mj2eaYExRQLszlQs>u!ahh1^f<-I!CrE*0?!v zA*O6tr^k97TGRe`F_PkeK{w7qDq5m=PwDr3G(UkK%N~#?E%wdwKXJAgNd5ottasYL z-Q{e!$DXa>TtiNjxU8q0ZEA#{N{x#b|5`6V9xA;|i%-;i`H~ic*sJXNwDT~Ay~^p8 zEq17;a6j)GF2Bh_tL+RySq}6fWLYpDKI1|agv^R)AoH>`+-tMQ{f_J2O|xb;&} z+Ar7m`{s*TA?NXY(K|eo5E$2?wSHdWv0Kc(%k{m&op|h$dWsUCV^L3u84jqgJ9?3# znfELu#wZZnv}-F*#uR0|kLvwGpQx7Yp7Dt_j(*iA9#VRxPhyt>6q8&?>khIFwJ^WO zAJ&q$I%1gAGjQ-$>Z`;DQtx%#xeju`0fD7l`&Ldmw#7&}xviL)t1@lHp4!odF_Y*XD-#kHsb#ilZ&peUb%TFYC^@$oGJ$I6Z*wT~7YKVQ(nn)u~ z?I#iX%+m;Y`849))S+R4_G`dE{AON(w;_VJ7UPP?62ta1j+etz+^Pk*ZA8m&RH`Y?w1 z#X%)hGHy&PbO$rO63{{L(ZVDk26g9)j%M_tN6O#0M2z5mBRYcND*ac?-dsDt+Du<6 zE;Otp+J2y4Pr6FH6VkJ<5*s3qI%BR9Z${r!ORf?_8Z2l5tlNdeFymrkc;vEC*&vZE zb18yw1bV3hc|D8BSzN4fI;pL`6@y09!QaB+)ar4Gn~k# zQD%pnKZ_)5JQm)(ZN6z(N!p&T`xS`Z1twm3x9O!m(Ou1!&!Q?*;|vQGkjt8)p7nna5(BA22VQc$?ioTy)YVxY5~~7J<$#b_!V~J2(`6Ura&O;Vqo{Bp zyp3AJxtoS>)qZj=rol4QL@{K<;-v2fQ-fj^)8Y@QeMrR_LA|Y!SW7zlM(+Qxk$8p% z3WIYlCTi{%;5bllHQX;0Vf{;dUaY|~qU1}22E8qp%ZEW-=DT|>`h7BCq+i21m0EA? z&cOLO2Y1mZg%xM9DX6M{{lywA z!%Bvb9S7tUyBs=Ev(B$~HWUYFD3%VbkkcE9t<>Qx%Kjn9yK^uG6SMP91Nq7P8$o%r z;3~|9i_ZJFp%~i$w|zb;J{x|EOFlvXP}+Q-^UI+j@d+i{PXEbpP9d8ohBTauL#G&~ zH=G|%2~yt*RsJS$V*3D7t)PZ;MX2YNfNWn+Wkb7yq6 zz@Ch&z<=Wg`K%d*6&uQ#*8rBQ@qX&>(PgF`jRqd8!D#+p@o6knAv931R0cP8wZVfp z`KW^Z1M?udRF ztwxsKlq=R{>+QMXr5rsdmt;{S^Puje9Za68SKaQrt0BWuj!_6+hnP6eeRK~-9*2^7vzn<$T zlu$RCuBn+$oEKeD69=LNvvs~`4#RBT(%hyT_)3WhnS7fj`_F2P5!!S=rgUn9-&q}$ zSOCKOgeGq4tAK+lgK5Hnpn5LoJQ>2+#hDTE8c5@ilk&0DpZ%3;rVS7$+}&qPct+OZ+i4 zPW=IHtZ4UauCST|_ZYu^F{Wnv$&C{^Lz=56g2Mwq-izutV(P0X33SQVpU2dgd}8{t zK+lgUoMnM~SCO6)Q(YT89|Tg9b0*()2_~gv9A_T2BRXGil*EW1rFB+o-ATG_6JlEv z50r-%NKI^Lb*+gV^P9HpG8sRi7JplLVtee>ieY~2xX4G52_gArKyM4lVL|;+Sgr`3 zSd*Ki2bXtFy|hA#u~fA=mxmy6gap=+1K8VV5e%~RtInOI65}A?U(al+X(Eoho!sE{&_$Tib$*%^oq!-Axy62 zHX0Au2)VX3`SBxCqz;Yecclvv7$((oQj9xNJdchoNUcYElVnG9Q@Z@G%=-pUNd3OV zk~1QLuO@7k;tiWT4Y)YeX(e%};20&StbSK{_+9EUG5J86)jPy}i=;I?Qw4a#xo!_} z>=LNIky08tSNQY{pB+ND?MW~%+FX<(IIaY!c)_2k8^v{O!C@<*s}C1baST&;{SNdY z%)^%AW3xN&DjG}CprYLQxA+G~;BdvqZA&RljyeuUubWz~=KAzHEtm&C=a+g}F3q#C zggeU7U+0K!NmeKJ2dA}Z#i`6APFPF}HPR$jc1@+ooF_&{f=c0Wg?bp#r`*yu5 z8?EvmF`dGuL$=sS(=9Bt56;0V_lcPs+t%P*>`Lx`Mt@<2&* zXZ)w+;HhbS(By;2GcHR_?XI~{v74?~+@yh@6<$&&7V@gymp^`_zqRMC+>c%79Tv`aww(X7f8K7(t)afWMgaNGb+(Yg?h-j_al+U z%AaG&vImVb!Fd=`^iS{_E)~|idpKQ8(at5>V5zXs+{M8LI8j|B zmQmeZhZ$9o=#lj&A%}Y4RTEWD#^xg6|fC!#` z?$}=IuxMC|c0h)xyNEZ&bPJZ)R>2Tk!16Wh(m6B_zq}cS z=5{!l-5QkhWB;ZW)h#Po)Sx!er(wS2;&}t*YsldJILz%6A1m6)J{r9_r$YR~7%I=8 zBPt%@6W_Rs50+G4OMQu3G~7)-6e8*rZq830EH7k7i1xu7@N~!E{)6*i-A2E0kRP!R zQRjYh{*Z%2Bhy0v>aaSvOh@k4`=rE?<(+nB#bNvLb*$QRstfftDSxFuImpH~F&|8@ zwJL{F_eMS5p#$%3I$IyojlGZrZNy>f`lOGpn^gK_A7`Olr6jgbaD1TlF>VqJo~h&F zS4L6r2l_m$Y`9A6xQ~J-+DUJaVybNXvJ|^$V+OjvJ#ExQ@|=5M$QUdov=Ez}1tkkx zh=q;y3oYcLCVE2)xuFSsCp7IB0)0eFxF2t+=d~0o$Y~mt1K))X^zVuOa^JzTxb9NY zGjgvpjSmbW&2N_1Qlbr47fp`hyujOfW>j>|(m=4=5BDr=&E?A3+4|F5xgr~!UuT~N zPn@@fXSZBp9G}|^?iX`Ug?ngJkBf?B(GE5yl&}8-Vck1K% zkBd?g^o0~grnnWH(@K!JsfG&9H5A59*SVw-Mj_&{rg~~qvEa}!rcpe0M!tbwipQ5t zWK|)B^bW;iV1p47^j9d1Pm5c^nJz)l(;6x`H&PgTUFXV1xTGQ`HPy45ie-m}@jsCt z|Lu67O>3zya#v^321N$oNW#I>ExEMm@R&Rk)8MbUcgd!a{LT6Q5Pg{d@s2nb;Fj#% zRy14qM?R)P)3`7nhLm-aUaN3^;B^UrKz(*<(XP!^U@JSSm|)<8=WV$3N_pVCTMqN4;%m%5q=HLPh7J-I>J;3Wq)5 zT;u!^$D!vc4Faw!m;`huE3Zi$wV1{IRgOC@q@l!APVv&yFk9&jMPmgMRQk{iqL< zJ&n!UKDZ35ibQazeiV=zCk>PMjFm^56jEz^LPtKXe>*Gs2r7 z7sy>G(^Qy|?${6cB?iTV)TPNN4pENvJror~A{xl%$T@J2&(bTSa%#3tN9FT0j;hW+ z2cDT64Nn}^hr2SD+-q_h!M(?I?|13A4~)Jys&_@jesXe#{XYp4Uy|^aXMD(vcU~umst|z_EMdk*^G!e@Rb!8Jdy+}XV zL@q1BSfy)mHwpR>(mS!F5YE1&_tS>?aK6xp9x2H*(Yu<6{WO@bsY7(P(bYRM@_68z z#$tS-u4p0$7U^D1}Qa2Hs4$=D( z**hWfNZ`rF;_CuEud#d|RyUSk7lJ+=dO=@CdN&jo!a1Mxj&GO`=LZd|Vd2ik`qRb& z7qvb1{X7& zfZG~<@Q}IGgK$U83e2UxImv4eY4?aB2w*%2p)PxXO@8}(Pd(#e> zNFH0wSY@;K`0^%5l0@S@*Ad%ofSBC7rsmR66(gw^rPyjMM3lxl)OAgC#LHG-x)q=h zx;VsPhh%vZaUv4$S33H6oXE5SCIp=AC^}caEpbQ&a@8{YrNq%mI8BXp*L3RUW&U~a zy;lE2`~hb)#naZPAAtH(92&j>he<~=u~A2t^*Jd;9EeFQ?OR5J2)Z=&WZ;IuHVO7!L)wgzow@#Z7z+?{VM-FIFK6s~uV=$4lQfJ8<~b za2|9_vgVNGztKY-+L%Q{GDCLi?qEG0$1@OSqyOmJ6bD?pEAb?*_T!~%sb@JjrE=+% zgJtiC48@p%Jb~*QQe*HnUv@$fdW)l55O)n*CdHqAv_Mk%G6{6-5=foVD4K5JMf6aR zagE+pR&XZZ<$9JBiA$y&EbeHSMMg>Gn z(X=m>ozTp0vxU8$3U%xfhgQK?Z5Z`bsAH2j*nb{tovK@h+I#BA|DWm<#xyOk=5wAo z{&}qK2>O0>@{XQk)?M<Tp;68`KWD4h;cK2?kLZ6JS3dFZk3G z!h>|v1mR&Z_zN2XHjvQ2E%1a8{wv23DtPzV^w!TE#} zM$3lbB3ii>4zBBdMZwhwr|ZgoBh`B(i=G(H$DJ^qQeA?#SZGNBvhEnFMz*^v+d-a8 zTU%G5xl|aPU{P9zhtMjE9^>H76wGzgq(%zw$Jj8>0QVq?la&f>nS1(mwqr(n2-z-U z;JJ!g_C{UVb~1Jr%$bkfXQ{e7;pvWe9JfGdY>M#Ss3U>*zqgz8IrSoF8tbSjKDCxu zwmX-Q(--co>7KwN8ZU}qwa9(L#hROPWf9(a2(PzueOINPRAFpIy`c1Fh1b*&-dEIc zTLF~myIdLMBgb#9RPLpaw`904UIoVb3vHQ0aC%)s+q0dmN$!62rd00I*#b%TBo|W( zeI>&v#VHb-T32@8dlX0H1)Xa7t(q$FeO|cM$#Bx4TJR7~0!Reu)__{JrdbYU4)-%< zo44G1)ijU}H*Ck%4oXaMWssKbVB@{8Pd!6X zdq;*VDC6`9D%~&< zuj7!7ABU{(IAmA6`|Z=};p31kI}X{x6=;M%0kSx|od(r`O;npKUyJ8G{ zaH9k-E9owd7)K=%zV4qpHn>QZo~wtOzVLv&1g?yuUs62WbsB;&SA~~g98FE|PER|$ z_3c`5KE{4=VqMu|>KE^FWskjIZ12jUtvcgsYSUD;RD+!`J_v_?0w*o!P_4z2Pqk_- z63NO?f4E7i?Gn!8)YFg95t{nG;L!9$AI_d!WY4;-SY$YD2z7VhK)sn!yYAbWbzi`Hq4sIW* zCBpl^H8Qu;4Ik>v*2*vn$R2lv>Jy2mx~m>Fmf`KAEhbLK-2b_9=g3p3|HBU9ZmC|Q zMia>o=Oa#dkt3dVa90G+8X|h`Jj({#-IZ-8n|HvmEnF2>Z%Flx3}b4BV{lj}8AlRW z2CJPQWme`43=6R!rq)UIxeTM%8|rBExT8pBRb81THg3n*_if!wX>x3RU!^N^?0sM7 zZz&fiIW!0t(Apyw){!X2=2QRX=Ka%By&}Wt7jcUYgQtDe?LAdDdBj(5k<3rzSu}3K z0-Dy{ot8_q8bD?71*PEZ?^Bbs`ap-5_{9jnp63_1g@VlS=X2)R`#zPfE*?uCV;_3Y zamaQ&4%x5Xr1*b3o72Z3+x<9XI~|8?)^W&IZTj|UHTXDWuR9LeCM3(<;$dKJCzRyB z4pswwqMxssTYoypwpY^e#CveNlNLp*l>7{L$&|#~q;O;I0#CfFxnGIOgFC&@{l9ac z7j}JDTZa_K%k;0rtw=L+U!*S=33Ah$0=MH-uf%$HN(y)FZwuabsJUHg_>b)jPChi( zK7(_-hjPfyf5^LOp8rog{3NkuGdoO^%l#pR)t+WGSROz8% zHA(f9{lY5gcOaJXJ5$4IhCd&aO@8M&JdzS5b<2Z#MOdv5>aT*TPe^YMiO=|cm)krZ z7?qNEDVRck6)yl}OUx^u%~9CTc_s(P!1bmqg$05hIqDIm4`i#2cuX@#eWvi1v3eL! zYGy0kd)}I*Fx6h1t$OO)`{$@kz=58K~bBnFVLO1vHgI@Zs>z{M5no{eR0ooRpL z$vEEVOwaC*-EDd~VSC<9a1JiOxhOHEc&7yND4pe7+^{X2gBlHjFzROHrs>W{N_3+J z>WFB|Kaa-(;^3_AfiD|~H3hA3t-MIVxuOV)`!pbDf4-T2#%+%VVQX07I>9p$vLS04 zT`gFYkFmw41w>92g8XSAIp3vobO_!^Z49fqMR@HU-lHJ}_XLu9tANP8N$Sl)a;~b8 zdIn)-!s^RH@dQadnl)9!Jux5dIR!-CSODkah2-qdQpXgTV-5i21?Q0ejA(7cc3v}V zz;12ucrzRb(2JVMNsaWBW^yr|GGEzvk^}m?boTV~X1d!6V&DmI+A{YY=s+G{J6t~F zE>F6N9Ho8iv2vxe%O?(SzK8Flg=BX%%Ps4;+a6OtS2TC|OL(+<^J%;kLu3m&+y|l^ zo#SfOdML%)2aP(YJ5_}YW6c)3E?BdjNvl2|)M1|M^0t-DTB#e<6;RU!19?gNrPw$e zPs-QV;qQHn{MQjbPTc=h|Ep#>JJJu&-O+Jk1P8x^e>69vZRH!VSVQ;Dv2f$AHNEbH zdnmfm(;o*XU2;ui_pPJ2c91P;j_?A_DX=t(CwyrsjxM)_ahMpV0N!`#1tgHIV4CGx zl!EOlnsUA^HU1BA!k9kJ#1lw(7tPVD9PweDQFm`w?tJ-!vmv)uHd_`zt3T8M81A1c z0p1UUhX=(&K|Lu*_wXJIilsrY+HgC$@)yZ7+kyD7R5+rDHLm9r=1XnheoBS$|6LV+ z+DDHvyU)>0@QKkrVzD}W-+_+wT1q$ALGj1>AVS4r#If1&FRSA?;?nUu(D6wX$AvcN z@C}@$c&-4up*Xebw1In`3giD?6@HEeieSN|{MkM_5f2udrj=F)#pMyHmMDcC(lxFQ zxZTwj^|1;s@(J|mSo+0zdVGDvM=VyyUA1+5%e-wxI7|KVfgzk&D>m%5ujL!^H5;BoDm%I~&u-=hAnq4=WypN34qxGY6Sf3SR{o_E3( z3U^IF-iObzD7->So;I)2EO*TH-<3OGo=zuSUY9N3_v599ryPKk|EKsrAP`;{5YzCA zT|mqT=obQF2fb@xgB6`z`HN(`i?DUC{?zPs-07iqaIw^I;+PVsecZ(_%=U3U$UOqQ z&dFyL?r_h?I?klF5--;5aA?!x6Nhf3!oArE`U}thq5*PHQ2DVhV;gU z9x`B*I`?g41COoH@Clx1AB-1Z)wz%PL{GjSP;>|6(Si)q;u#;kCb^zH zim}zSQ`3{u8BI@6zplktnkpb#2hXF~(}#7_Gnb0V3pm#%>D5G6B>Fdc9~KAw9y@rB zE0^vQyeRRU3*OFTwtEg`YfKX>JW;+8kJUK(T|C>V&c*eWr)&&yA_1@Bcg5NVdO^H? zg(#?Vap4j+yFnM-0Jzh^gS+%|sxNc5=rLP&y$b1K>h+Yv9+4h6py1FEg7@&D zYIkQI`Q(bk$^h@yPhsfn=n6d8i*IeY`IqwHmEe{x&q+91xWPw{O|A6N$GBehi3Li( z?GyXexj3nfXM|lgH=7qK%Ega-l%x3Gmg`YG-OXmSp&<6q#4yPwLe2TZ!)J4-Q}e%| zl{R|va5&*B2b;sE;9|}sjzIQmxkEmW^Wq8vep~nQW@xp5-mFzKg5ncfiM~d{Y3y#O zRd`|mk7?pd0Bm|5jxG%K;d(w!+T+T3txAM-eSAg29S>7wL)v4H8Y}fP@l|i?9zk?# z(MIwXwM_uS&Gbe7{jdg}05j#V)R^eDx1q~77KAZ@`}6|0#}r>>B7&c9-E zF~Oljr#J<>iTMs5I>Q-H*QPq@=GK*!D2V*S;Q0{P~x4SKlgSYOc zJ#50cK_h?zg2quN7F-`-@Mw3VWRtBAI6{`e)|J#Box5ajv)T^wqc#0G%goq0i>ymA15pFTNbxq&D?J?M}NBn<6GU z?h6BF>z|5S3?4iQ>jr1w@p1%>Q|HF!AFE1qsK za$Z3ELo3qt)t*2$H7xW!HvQ#qYY|U1wpaX3)f>>3?N-`Fwv62kW$suQ5wt9&k znO3hy^pG6&ZbbKoP?jE+rFv!SaoGY-P4CZE=^Q;KSG}17n`^A4`Y}afhmsQQ|A@U; z$RA&UmL1nXm&?g`rAYVoA&$?5ngQ7pioyLTJy|5j<875}xgdg=y%AYStS{2K`W{LQ z`b3Rg^`kWSFX+U2bJ<||1%_o#>#mv(bHH8UD}ftd^QpbwyhcjwcDyd7+vRS<#)qU~ zVlDgdgzxoOZ=U6B&v~MWn3@N_Ou;|MS=7ykN1PfyErEPO_w0K$ZUE;zdS8WF$4FwN z!gd*+`aTM@<)86w9;`Qy4lHUQHWf6+%5G6pIM>m7^Zp`5PHvQ2-iF8c#olnQz?dTO zK|WXq6*h%)E-e>V7b-Y$z03{MuP{BE_4(rbF+y32! zVm`5MD`*O5A7WixsNn2kS&!A$BWp!DIJo}1IEFRk=S!ZBiQSFxw4?m2v7Q@~xQB^N zouM5x_9KcRoVVS8?5N4&T>ab~$z$FxYUD zKGgoyKhaz3E`nDg>5ZSCiQgIAcAi<7?&0G^^L%&? z40AZm+Q}B<2*H@_3#Eop3@cT~u0DmgmT)B2y>a<6mEoF}i`K!K#oP4`(gB)B@On8; zngr!@N{Er2Ys`+ z@?es=vQ6}_@u1Qz;;Mu6tfQy>O8fvx_lqOSUX3BtO`@z0-*-(rTs#g5gX4S%9dWKh z^PO$d&r3gA`J?4)xES9(35Y$;0NFo4-%Xkj5QBVrWPlX-s1F3Rf8LDoU1yTGsgflV}z$VD`j9hAP5;5-Q;E>NOSZQ&X9bm)KWCWio`%RLYmKH9k;TtFDA< zP6v?2ON~ZvVnp1%(e<+7HaFlqn(CcOoJsSQD^8*{c5Js$D}*W6N{LxIYA2qSoq{KV z@IeXZCse!j6Zkj^hTeYq;N&%8luy5+F}J1kTTe9|pZNSrivgOxE`YBBUL#)S$?%Wx zfiSG|2laeBr54hk7tx30J}43xnRRO*s=`EnBv%h;Al660_)(tTQY600)9Z`G3x(G% zFA}qh$de*>nOQ(MY$V-wqqRKYGU&J$$D^ft3VV`+CWB4{pqX%wV%Y`>>dqTQoi=7_ z?==>0;rlI(#Q?uv(^$M1)=L`G*RvUYA0;vH;&aa%gTotMpQhRa$haCG#Dn4j{rAd0 z><{VZPM2%)^o-MG-$MP$=_*yCSDdZ}HX{1IM%UmLKy%%%ja+b|?%qbOK7|;!wLLp?=;n?t)ACb&1|dpw-s--)ss#a<7th02mPxF-VOJz-|N|J=#$fH z+R(=`C$*smWCykp_`vj-HeyeZ{^K+0OWKGL5#8rh@j}*bhn_0F%pIjRwh=Q6^m}c@ z7eyNGo(=WKU}{YN`^NM)ZREBXxp%dq|6ST>h#_eDx$g!47^a{R7*|u1Oup7zE0IvA zCUoCiT7}%3gU`zA13BU`TwENTEA|D*V{GUNLbm2;{I69;a!L`@4>cBLenMR<`LqND zuzsIYc1xWqq7QFBQzZIny%HP#;qw<3;WO?}D}XNxHU5(-+xFb1$KVT5NIOl-4wFpV z#-33cCRAe!IzVGw{I6xhX+Ouf?KX|Ix`9}?@!^s!t>mEqzT%^+KX9YkgYI_ikR#a! z6MVnMxJuJkr9KN>kJGn7J<#oGeKeFO?X(&Sh>wsPhb#7LxG@e{>KDu9SH4w#^D#l> z$FJothsbv@OvZ;ZKQ9(t{dx<&;T?QL&M%h9u>PP}b&qJcF_BwetmfyC`}rKbycnMg zp5X(swQvS9_YE5Pjw&W!TgoV}BTf3q`4VfmXC5ql6z0!gqUwM(?p}p-r+I84KKRxF z1L|-W`EsEghld~w6~1*2_mnI>r%)}Mwn49Bj;`UiUJ`~XJ2P9hBb%EM*Tj*~_D})^}BzzD`26@82NOrgox4^LiIaNLpz#~Zb zZWf)8f_t-14-Y6j`40Em{uvr@zz{AJHQFq~(J$=eSUwPMIuA!Rs6cYJ>+dCR z#+{wol;1xu(VW(zG{i}wk2P&^^uc#B9QusdYrfGEBx0gA46p=+=SfX>qMk$N=ja*O zRq+v}7Eb|rIHfcrBp6iiphty)_~}43t*8 z-{bZ1$RyYtTCTx}lr;0y>g;WDt8Z;oyoNW1@svwQFN=yV!sH$n(et8WLl(JT$)W!{ zbM#wLF*8bTQbEs-07mKL`Au|CbE#8_FZ;QE*BZYQ$?qZOflWbbFkTLdluyI`p}u=- z(EZSaJ5^|uPg_=a!{3aPDrtg@n_u)e0Q5JeuJSs)9@hxmRZc8I?}8M`Xls2l-qxpcEBDF2 zU=seIDFAi(4)^_#JQ=x9uXOKc;yN^oxpgp0ps~0CdjEotVv+|;MMmHwM#gOWHA3$b z-$%oP=D(wx+H~59lA<>5#;>hx@*{j1w3kmU52)9DSI!O4$11i3)JORCML^ljO7M1m zn$iQ`(P$RFQ=G3K6c?XQd!JBzkm)Lyr=g5ox*To&YxSw7l0{1bdB=|*uO zPV(MWM_d%TJUGvP2I&7lbD{A$8JuMEwH_5v%e4C1uloBH+-{I&)RCD+cAX;gn}+Yk zD+t{%2`$CQrN%HjHks@4A-O2`VsnfJgbH)m)M2EAuS&b+Cl$XmAPEG zSJGhTK8?5c9+qa_8NE|K>&~;EqHYQ_$}^htY+cm|? zzYF6E?Pl4}K;QJK#J_Wu{ zf$vk``xN*-1-?&#?^ED^M+zjeFa^Lb7BDcuFv&2@Py~&CoMGDHAwy3yOoka}7-Ri5 zw#m_WGX=lQNgUqE27Yt{7R>2~t#JDND#3V`Sta@pq-5 zrFht#ex)oI<9JrQ%k+%~#@OHk zv44#9q!}jIztrZ>2PXW)ZUa;M*`99C1S6ajfRK`jU*N z*gs|EZMrdD#rBqAbD4h0_Ze0jSoW!b8Rn~Im}b4$Kz8M@$?U@DK7&U+>6Pa6s^an$ z=X5D$eJL)-g7qdDPjUPcTwcU!a`+XkOnlRfS1}YEuNar3sKpryd@P!NarSo+{JSG%`k(5g(QD~R zrdQAwR;}MerpK9oUtc4iVd(AOc33@MV<{NF(sPU*sUdzBk1@;)Hn?ECY^cG@7%yk& z3gKVp87AEL*nmr_7-wK=kb%+323r0~<`<6|dM0IHVz7b9VRiX;SpLz>$8ss=PmM72 z3dU_XVwSNhHpcjuJ!W7M9|ojf>L~*)e@Es|v;H*m$A>aML(5;z{AFxclEX49ggn+H_=CJ+Q9b)vPbW;g_?%a*k&i!%~J(hSi*2Ri1P! zSz^+yqAQM9;uq^~VEJDREWOu2*AxHBn7^tE(|a)AUkxnpT$jH$^NXaRM_I0%`O|+l z^aSHJoYYcdS2f#J*3Iyh{msDiKMl0}8<@Y6!>QnSMDH_T_JLdh?$mP_7FJgad^)1Y|g6Vnn;H%l6 zhuz|D%(s#05yt1`Oc^vLC~zm>a{ z<(hct;vSqC z^m`eSZu0v(~;0fPugY5!*yKVtevki|Fzs+5S`+-f$2Z>l+SKlu8KT*HZlKZhGaYGs$#w^ zOt;~_!}vCa)E-c{A2I%khkyNrrks7sbesO4GybKA|7*sD$4c4;ExUiz=N{&wc{v!n9K%s-7GjhZQL zNBet^d<8MO=XEaPM^{q6SnFY(~380XQwsEt#fw4D8I__cBJU(fzFzO`}k ze}nz0U!q@aocy=4zwO_>_)2}v8VFuaX z+E*JVnG@Kb!lR!TZ@~U`9OlK1)PL`PI}YFGj~8#n{&t+?#gAm!*(`6A{Ce^G*x$y# zHcn;Ye)hNV_u}2y-q8i(7*1q3gW+=w zUt;(Q!#5djXZQ(2b(_(fc+AY7QX5SUc7EL8X~wH28r;$|_0Vza&Nbi5AM?;{cp2`u zt={-HBVWzwVd+Vxi${$eR$jbk_${9B;AsyY+g_JH>A^D|JienYf69Z4opt>a9z5;A zWAE4HPkQi-2akVHmp{e0Eh8nI9#y?eE1F@5&x*M$)fVqvx0Rj9>6_y9qzuD~i);-r zA#7m2H2X)tF)h61v*}*3-`HvME6w;^-oLT-Rnb2Tw3=?MgCU%IlMsp00;3gsC5K4}ZKKdXnj79{zMa^q4gIZGExvuV(o&din&v za)vgYYT_R@@-`lE#;rcfm+7`7!^3@*sSj2=oocXNXamG_@Z@M14 z?<%ZocLn3E#+mwU_3dMQ(UE54Q_4^)mLz^F!9F7e)?FrZN zWteVp(ZJYk!?X6MA2)VIaaE3f)r?!dj+`%5OfO|VVIeb?dGgQ7m(MhMEuJ_6pN&_7 z?X`AV`6%aea)!~HVrcEQ@@bZ@DF@b0mak&Fsu|kyV4=5MWM*?Z%`z~-xJ}<3p7gcl zG0x?!+*9sq!eM&CLM{g}UKgr~_Y+({UST^JR$g4Jp zK3G2WZ;6^thtPcnWTr_aCb1?k?|_VJ29TI@{I3eyffp~j8`xow01Bq7>_f) zf%$Fv)CbcOOu8i*CK$#TrpFuq6hp5b8_)W}>&$l8^t0(^(`z65+xXdd+4y+F^Wwuf z-ZuX#Z2EIL*mNzk>EuyR#^G1hlU@^9Uy7kEM|ZHk1Vfv@9`zz<<7dNXd1dSyNIDi7U;6MWL-cV(d&??j90j(cM4U&i!GkAFw@ zuV#8#gSzr%>|eq3YKAr*@kXW{_Qo&ap<8=*u)btR6HbPq^%p!okDX)ulMHQmN!DxI zQA@92`gNZ1ePuoLs(R?v_0Xe@58qFgGTn|-tUd91=m`(q#@pJR;&E&IViSKmPO^OE z9KQtnC+p$s&3q~LPuIhjV!jOfi%Sk4?p)@Jv46ZCKAR2+_7|5PUf&L@kNxBI@QJ5P z`A)EZvL3z?=1Z}E+QVo2wIrv1<^Z>oj8pp3%)*vqJIfe8$?#d+=5H0tO=N$oPc%6= zE;$x`r7XA4qc8C-^;NK3r^&{CYhU_X>Z@kC;jGW5L$s-hZ-(u*^wN6hiF)W2OfU85 zudIijsfQkIX6%Uz9zXE-sI(q>!b7+9%;u}bEwtmmV}(}U=C8#qJXU>{-^$y3wz!3s z?^vOgxA|>x3y)Qw<+t*-yd){U`e~ZuQ#olVQEF z>kOZ`o#7P*M#~NK>dpMg(5pRqlUyznY-ft~R^P<>SZ~!Whu0ftKCj*?)|+I#X{+~U zqbJ6C%Wpiq-VCRgSMLt1m(w9_)AtUer z{si+U??1fW^xqA=+@rTM>&>t}@oU2uyUoA~%m1&#>y5JBl;yJpg7v029Wrcp^e&?( zZSDQT;q|8e#&nO~POLY^`Vwq+{BFZv&HPpO9$s(!uZAvu#gOf-uzETEafao8HheMW zFTd~bdNW+EOFeo^SZ|usL9o4*TrbM3-hUrnZ-VPv!lO6AdgGi9NvrpdMo*IY%Mypz zTfurOJbEXx-UP=#$uPy`p_2Ji^_1T<>rFGC?f;S|)a{>xo9p)P6ZssIW4aGv<lelb3enGTq6su+qqgI6$& ztv&iqL$U&!GW7?@x@&Ugj$r5IK+wDF0}H0hb*bFdjs z&)8VA<66p4OfYzQih*TQ?T)O`bRFvvEMGd_*qyfLG)EY`-0EWkDp)SY>6w~n_$tO2 z{V9f-Ck>u>is_RLEM+-ilL3?3L8(hE5_l(nXimtHOI4>!%0pu;Y2yS1oM?M zOft0fBX~;P`cc9-*>Sk}-HN$&>x-QawPLw_++JCFoaxbdM$X1>1M8`{!tB(R{>s4Q zuMMnVm}XdIA>X&EWd2l7GjpwA-0HXCRk8ju_K)3Y^j9$~zlm{%i5u7shG~WwhNZtX z{&9v$hSdyh_^mj;Q4Y8Ke<*yL?nzE}o6gp*)U%uq*PD367#?rB+w!!dg(*)K7cJ}J zC5&4;OBuK85|*A|dZ+ouUQ18b!=JKxncuee8KzVG4_BT`nBSBlQ8OMAt&H8rDi=F~ zTq{p_HePX-%Xs6<^obsNn(5wji?ud(Te?k$B-87QPse)f%CKDJg1Y$-Z)3tKVLIuh zpUtoMZKixx-fm#&?-|}dyvN-fdt-xq+q37iU<;(1v64x194c&9LeMv!9>3(7+@^ zD_6$lqLTGhbGm-l_T9nZRB<>l&bI{P885%eoajnjZD3`4KH+sX(@!z5>KdlA?j1ILSWlGm z?|Ai!g{D2ous*@{yS{Q^+o9O0b=y^2e{K6)-~2S;k+bd0k>pYyIa@!EB$x5X+4^=Q zx!7rS<7dmwA#$F1QNkl<%g>SIQXV;5Uk{OsnF3++A>)zjxWeS;A#$GiX{>GC_yvzr zF5!{8<0$1)9=X&}%4Ixq@s;(&&yx?a)9c3Xx}%g!c;q(JXQwA0QXVE0 zx$+~}$<2Z2xY(pan(uW9dfp$uYHlZmZEudyUzW3;ie5%O!+I*q4IbtDW!`>%BFmNa zF?>~h4g9X#>x>QOAr9wZw#)VtwtqOz{xQA8q-R-AqrZ}2^(=#n*#>47u>W&RPc!6< z*~fZfoSx;Jo)vsQFZD0OA7_0mCQ6r@@T)kUWgO20hhNUHdcKiQaQI~$eu`m|`6?Ku z7}{{|u;FmNm2tjBnJ>n$YM}`yy~w~QhgZ$vlrmqOVHrakPA9$}SZ?nt@;%1{-;0bh zj4>1pGhFV{488qelJ%A)ja?bm8|QnCF@}O+#@>^(>CdooggLov$L(dMroFJ^ZA(uw z-L{XGo~nmG&2-xi+jd&CtJ@ygaBTZf-~1)^E#=}zkZV=XJR!kywtcRNFVk&%Z|NDP z+xS>|{EWKkY11LabSftlcPnSpp}zTxIMe8}?V}AR!F1bB+wy1gD?ZnhlX9*%rSl9P zU(C=`4`bZk$C;zSV}Wx{}l983QY>;&o((@#%&ho5hgv4ErZ$ z8hWLbXIRBB&i*NeHvH%s6Mmfe5)5Nk^ZGTz3Jx#H;g>RA&i-PC3BR0ShW#rnZlNdq zRvdoWY$KmySh?Nc=|u)sZ8fm^Jp(J#2Bw}fuymDyRZ9)5zJ~2zZ1A#`23EXdppAc= z!_U}wariO6xv^Ku`s2#r32k6ChaY2D`U>k`V_^JR6Mp#;gQs6KFe8k7O*!gd%8R#r z#m+MH6>oaV5!3J3UYDL?`rJ3_(lbmyq@12@^jCZJGrhvA-}1A5n|`*OM~h6n#F+;E z*YnSF3Dl;OwW|gmFh9Iw?f&ZHi>)*1R=L)|4GcRm-FxY@oa=Gf*<2nvaQrzO8=mE> zX1>%}hHqbufyQC;(?b?P)@NaTd>OX0oZ)bW!SyCS-tc;}{Rz&mRC{BW4aY*uC)hvE z@$~Ae&n~OS!g~3RGC%iyV-m!|`tmdA(P!(0H+^k9syUtO<@1DFAK&m-OnO&wx+fV< zWLV1ZI)*Xk-^ch4#?uVF@knvLRh(<`D|%kt@?_&-`Krz_{#BOG6A#N*_Cv#$I^RH> zPPV_w#F_qE1OMCYqqn}PRR z@GM`&Eyll^`E0qecG~vT;uhL^VPSoIX|}VBVP}Rr7<$9oVe2>NS1Mubv*B21`2_pN zIlf+f_1R_hSXeKgr#{w~PqtoLSRbFaUQ~PfC7ZrB9=2Z8%jc;V_3?GyV9HPB>jowm z_GTDm*pZ=N{tb+;U_8ap8xO(#PQ~v{ensyv&>IiSS9P25ud;lecv!x&+YMjp4+hqZ zKe%7F_YZ8kZLoIUW%$z!D;Qe&SVuFC=*Fig#O>ckZvPm@8Qx(b^Tio@?Xdk`X>XIx z)eLR?Z9TJmmF!>fkm0v>T4?#onJ>c`^Xw%Wc`uM!@u=#2ASZM9Cus*&! zc-)-fbP6(F!T4MYEuG;ZcG&vB@lN$I>15+?)6=#`G4_x5J$!mvz6A4CGqm+#BA085 zFEz=wJ}Qo|S!7^kv4P(6q}5#iQp_LabE-#@v-KczKCdfZ zVBoQygN^Yy@p3+YS{XKW9;=>?tS1^W@)_2X`oQ2749oX(I{3|tMHP7l#+n%zZEaxs z4Z~NpiS@n3a?Ed19q)K@xTPF!G-CMDKR0?xyBa*v!@#nK3`{<3;QvS4d%!(atn1rD zQL#r(o^<=0g z&AjqY3;Y~b2QKw(HBOkzpJBFn%>sy0(r{YJUpOT*MaW` zl+cx0(b;-kL03grBA&{0`PLs%S-O$uH&fp>lms}0dTaPK{*QhI{IZO<{Qu)T7|ZJ{ zZ&vp^ zlaoG+@6cBK?K-4@zD{YrR+_HN-_twG-_8SSS;*k9&QMu@PGybv#VWimH-z_trs(hX{!sB=J zMXa-SH1KOpXZ2axnr`0L>W2(vo&0y;d5P!KD2wnRl!Ly}>(i8RJKPugyL(LikbR=L zKdc?5%Rj2WH_%!9R+_GiU-?PtEl-`PufRO5GC%G8=llwqx9R0omb$3S^S*O~*Jbt+ zFYaF7bYBshf21e6w}R;#04r(&oiqWh?uV>F*SOg?MLD4uab_Hs3+SUt-?oZC#Gm6Vs)b z{|$7p-~ToKq6J3TT0hyiw6axQ>}UJZ5w~{OyoirqCb#;mY)v<_p?*kFR>}Vy>j%mS zlzDXhDSPsH6h~RK04VeHTcfIRlzXaqJXt$TmqX{Cmd@h0(sZTA_4f)o+po`ZT({>% zo?BRUUQ)67Y@auYKL1kTd6m+R@Aml@JH9vgyB*&hes+9M@!aaS^OhPs&3S;WKN)z| zd?;=GDe(NS^~dzK-tgSkpR&DvlKMHlwa$C&JSfX^yYGhO7*7Nc;D$^5HI-aK}vy|pH0lzdpS;|H$es%udnxD19{H?U>28++0|99~Xwe}Ks zkup9Xc7v4fcpoqYx9{KE^+*OD;%jMtHQsNte#x~`Pabaf2QYoH6@9f8?zU~Y9qEW$ zJF@WB+R^Gh@&EF5qyL-NwYpFI-@LBXePUi0-zO1$~xbRPgnFj z{=R$tN5z{%U;Tf4oqZ2nwg2m1o4@~ml~$9@r)pOFih1rk_dmT)z@VSh{w((aC{9wI z{--{9eCqgA@JXG%q(Y{wjo0(qRVpht^7ngG)*n`x9j-EejLPD%Dyya^5u>TsmFaKl zt<~{aj@NflS?H#+(p_a^b(QtCRF=?Lo=fDIaBBlM^=36PFek%2) zn$yPqK5>FXo%!_>f1JZ$)S8e$n!Q zE>Aq=oa%4vtg_NyWojdprCH7O7OA(4U-kd!HvzvC<1Itk+B~r1S9J#+zwGw|jHh>O z=9aG^e`tM9Etg7An$mUF^DNJE#(80Z*Ex8GvbL^XSLS(z(&BKtC=NTnG@gaW=Nm=1 zwa4O~NB#MEs?W_=S)a>ugsFe3eVL!db5{Nz=WkDZQ~0GR-5;u_^ozhY^|xpx@B964 z-ygJbS;5bx74QG{IAHCl9oh1Ha7T&TbzM(xIG2HUXwx|vI(8nMg9jMi=gDzj(?s;Q zf6~0JdYIy`Q&uSLIx#m?fB)~U6D1z;pYUZCnngVa8vGoiEx(Jga(^ z=XDd#)cozr^E_o2%6a^K1)kgcZ*%|o=P?1zA_l%8|P~UwfigB=ie&a z=fUpVX!jee9-LeJ}0zLhSuI`<|=)PJ%5{c3(&P{gMjTgSKyC z_q!_dyIS^sf&E^JeZJ4`J87TutBh0o?7o*d;{5A=S=_J7;;{RB*ypU$4;_b0g3O|kt3h>J%79Jg?$dOh_8Jg-hAzIiw*K<@6Qy;W8U@)vUbGOD@%+k z+jp|xxv}?E(p=Zt@2A-P(yZPh`%-rQ5&K<~GWFT-epGpG_g!`5xxw$X+50hn-4BP) zZ)J(UM!W08Z@(vF_usVle~QeT>fRdv6<%k*mt*&>tWbZ!#wov3l43kGsJBA@+3&*G z=R)oGY3z4K%DmrY_W>>2_{GmYU)mrZ`@I|c{Tutepp^X{9ruGRGXCuSG{=5riSa&m zbM>eFzLD)?rZ3Pq{_FR1$V-mAI`%cQ%j)&E|7gEoGlX$krGEQe9lOtKhW6R-D2+vL z_r1%qznNit**;=}yxV;M)8r>jd}TiGn_(PhZJcnw)fu!mTVlTRJAU@P_#8g=drP(b zG!E?dYwSL-_P%A-@DYaGi>woNAEgHO5zOFcuP^gFk8jcD z2lqv^`vW$(FR$G%s6Zb6x=%6phiWh`?f!ap|KOSQgY942ew}?z)bpn z@>I$(l;==hKzRw}HI!FUPNJ+(PN#gC@;%BgDa*9;T7C!iYxr64%zTZPZ{e%M2l6{l z4fq83`Ly#F?hkw;{8Y+4a>{={p1(%DZZ+}hcdG9Pd{51b|HSj`JNo-0yl(7)`uorP zeG%$khO!ssdX$?|Zh>!kIlcZMo`1iup5L;n$~m-SYkqHj-^0Z}M!!9N|Df!>pZ@;+ z0V=OUKN|hRJb#MkxA1(YHqu{ukjhQqQ_$@~x%1Cz|M$do0DMR4Kalbm$}=g?qs%{` zjg4$Y>xAw1*zYy!Y~6pai}z)#e9oc9?-7>RSG=FHMrn&o=ff0#Ps;w3+f(jKc@*U- zlowD=q`ZT2I^~O$_PY0YZuMIE9`V#EKcW1B@*B$UDd$qor~HGm4gL#IE=*~!Tb$=( z+i0EUV`zVFv&9x0rhD#w(5RzFj!O5<^jW`8X05)XlBa!7TPM>evtBQ-Hf%QZfrlgU zaU%~b9Cye__xknfm;4Cj(b{#?u!D{pdC0+|N4tX$9C`3i*FW21ME`XE>{|UZJ$sBe zXml?x3>!cj`2YUyH|C!I&Tr(<5yza6_#HCL8}=A++%UZQM_yg;{qMX+9Qrr6(TUrE zqrJ<2{b=WRWOF(lX%e>cpMWW}$lJM6i@d#?(js5rul>KjSJ2M+_SnYQ-~Q0fE#z!} z@Nd}XwS%*Lvu3`qv;DSazKFAZy=K1XU;BT}+;1A%zSZCU(9SLH(k=2OT!!oEzsY@^ zOS)`}Jms=Qo8kX@MLW0DU;C~9qG25QwWg-^rkOA8;_(^&-p(!K4vEIC__D6r#&$yg z<&z>sWOn7-of!ERZ_7#VE$1qMVWZ~A z!83=5UxWStzwQK|+M(i)z`uc)4i_JT&)WX5ozy#2yb8Y_p5r?B8GIJ=OIy&_xqj?I zKYk3q{(K2}8^mqx--rnMd*L>MkA=S$@mJtKM7)nb2qo=0PH_%oeGNZ<5qQT7RPQ~+ zekGCgNYfQzhw#qwntD zjt@Sk!I#2655MSQfqC@rZSZq17q|EAeuUo$zZw7i`~V34gRhX@cm>{wcsqak6!ceI zDg9sL9DZ}e&+@#DKi*Y2&+kvY7omRuz4haC_*)VG3Z9>Nn5Bx|(NvrqQqH-sv^85a{h|H8%4g*^`QL;78hj4CUA&>fy#e3(PJt8Q zz5E-`A;KeBW%18N@4|u1Ki0v=j*rD?ok_LQt`$5F z@$7MrD*OPSW8v%FEB+GvnO1z>N8k27>HV2a@@V7VSP%Yb_-^pt@R9J*@I5?lDgSx& z_5QlfwEUk1FAor3oV+~;uWZ=N7xxQ7h^M%T_>LU-cY$v+MFz{_GZDV;{Q@@eng{E9hlzPtU{pQ)b*|@i__oWe-ZY1N=6)E6e9)_)Pdm zk$%yYRd1I0aw7WGJa1{d^+TVzN&csxKM-DlZ?3T1IQYvC38?p-n+A8c$maokX2V~P z^h>UyIIHN-M85+(H(5SK_{s2@kQ$$=~r= zbh_F#j(Yp_XgLo9JP+gf3iRz64|~9`<^+(*s@ z>RriqbZA#4igPn~nmA2A3|@=$=flfzi}QAPDdI1{-CE>X_@FLvuu+W-{zYy_N z;g!f|GkCtY>MaxhFnB7`pAXMO{7$$de)DK)8P3?KNFsd_&j(v;$8g$7{*T~;sZSo>&ax+lZn*3H~RcQ z<-yu@3Ooz%fPMmeY{YBua^$n%+NyWUCp4fA$7coj9q{eLeJ+x4q#XMSL>6^sM6dmnX^NdH5Z0i}P#v z^hm$Nx~g|(q|dN&fAOd^_~;gD0S{z`1Wv%C$*mH-8a&&10Nabck{fZ zyd8%AEcEM>hYI|m$mezV^AZ0U{%+)x^@gEeQqQYh7XQKUbi~i`yrnoNqR*iBPm{+7 z@WbHNkMrSUBL8Llc}0lllE`Nd_>9QsSokNAPv+Q`5Qc^YEhQEw%Rp z&%^pw$Z90mc=!`uig>qvtcMZb9G;8#VemBE`eiIU74h5PF5)l1>zgXVH2v~5yac!S z7w@k)D-mB4o{M-6UWoWecouH)UjffVd>T9*@ps@3Zt?%>c^D_l&|hWR)n|bC6nF)G zApDRQoBQ`<&s*x>3(?!}t}l+yEO_IRg%Xm6AG0Cz=+b81VI%QO#IJ+rBEIg%(ibBB zEW8A_c$U~i`ZD}p;+Y7~^E>DMv_E-t8Yq1UZa#a$%kbsU-vqbcOE>+G@EZCw`g1px ze2v1@-PLSy+ZZ+%lYKdXA9{IaPv6^ zUW8j79)XwPAK6e*|~@-o5G9+(tgrBK^tmGWwhGufl6lJc|sHPwr~PWBOt6&m#Rxa5qu< z?eSl5+pzw0akUE+xu53AW1#0@zQ_6#;JL`Bg3r(kn|&-{vLQW(tm}2f8sR%RefTEf2`jh zp1!s@&Iz7}_I8MTUTsCc=nnEP;$v}c39m=~r+Oaz8&N**Z$; zEa|U^9}3T0-<*eQJrDWp`9O30Z?&Rda%cIM@Ui%}g}WP?<2l3g;9n&_HXo<8qHn+p zkx#E(RBt`v2f=eUHrIQ#=b_#L?Xr4bZAHKEuH+4$qv)5-;F+7`Z~R1f89oyIG|xl5 z`Dx1MQ25WS=-2lHKJ-`mX8D`XNY8`MJo08f_qL+{9$t=oGP}!XGI=ncqdgDx#`-(q z>06rP`5K>86i@Fxh(FQ~gO}l_kcY{ppDcglpPN47tL=&ZtCw04gOuyD%;+!E)IeTVy+{TJUy{w^(lfBKg^wo7;ymyPu2q0dJ8XVH(2^v)k> zlK9bYfd7DmcX5?SKLUL<(%+7LPNe@Bz3bkbpRW5UpXu;Mvwv7VcTIRlmyPu2qaPCK zpFm$kZ~2^$z7*;E?XUJ$(OW)8CA^E95$W$k?^bTk|M%$K{mt?B_8UPVpIP*l&w~@* z#r2Q$6VVqV{hR0uk$&+!@uRo#wpGHrxM`98RP^apn)81@`bMPx27Nx#_c*ZS>z(yI z59`SVFKeQtoF2QvCr11@_#F|S1fLG?hR>VucO(7J@Gm1i_#nmeYs8O&FY-#8#M$C} z9Ns13pTm1cJbSSGH;ni}@WBzE3g0{8@4%0UcpraY4f#AN;yc5~Mf?`{)e)Zozb)b` z94h~ZBA$gmAMq>U??!w&{L6?hb6CscWChPdyM9H#68*a#yi2XQU-oRp=V z2^oGLeRa6)C*1{uuj6RLO?I%C^)-KzxD4!zz4fxo8 z#R}-HUArAEpWLa^-;MqS_( zi~3)r2<^Pc_8(&XU;8291GoNRKDHlH9^dTq*M3N(x9jd4+@H2354-NJJS!i6I-NZ1 zx;xg}K0_t)v3-UPv@3&;?K8xB+s8f3b{=l@S)bE4P+x|eTxBY?iSADVKeoS-KDRke+uz9HZ}ZglH)6f*SCk?j+plm> zH_ubsuZZ=wPvXWl$7A~y1-M@}lZWkZ)FW=!`?>SvGZMXB@0W?k_6_WMKi1nmMlJHO zeT=c>$9!xbBi7qKMs8elJhqR~KZ?ipF=D;#&(tCx+n;Gf@!0-MthfD&-1&;<6!K&H z619liK1S|>X1(o))FS>X$Avt{g?OK#|KrW$!S)%_YcyZS**-%d(%U{m9=%-`+djkO zNN@WLW%PDkZ2JuLNN@WL^+<2~44t27j^Fkf(rYSyyDqkUhFqk#eTICbw|$1Ok>2(h z%INL7*!CH!k>2(h>XF{|8RkWL+h<6xrT8tMw$G4xvbnvs&yYuN`Lun8p^@J98Oo8~ z_8H2N-u4;l=q;bN&oDF6+df0OPjmilpCQFMZ|m6NdF?ag(OW*ZN_ZDHDALe`zrnvA>3bjB@^#b3o`>_p>96U0FXi;O5dLDs>#g{Fi+<>v@>va^P9s{bcLmSG z`}HaABheoHCZ30QR()GOz2L{R;xih3Plen4`;E_mKaYP`^eg%UN~qU< zFW@uyUaj!UjPpAHOQV0>^RVyge$@QR!M|xm-({5Qwci^^qu&OekNih@9{ekOPu~2m zZ$)2&7x6LweYV}K@Evjcp2(l@LGOychyL)<@>!J&JHAff-hqGcp7cAQ&-gdWL%nNqfm?(h z>v>D@+dRs0f0!!znZ8Wo$EP3sB>dfWT4CnFKQO+%_-YEv?Rk=XKE~$^^jE?U`9MPB zAHqM4_=+dX=dcf@_p;=%2fPk9pA!7Qy7Z@_e;WQl#M}52n9$yRW;g3MfzOP14ZizF z&HA>!qTury+~Vm6-}U2WeF0vJ_$2rapET=d!=I1%vZtxuZ9Z+*?*e}+;y1%L`>a_% z6aHw#mp)zo8+|VQI^=U7_;C0c@GIfp!Y9CM@N?$Krzd>rqWtfIUk)Dx-{Onrde4H7 zf}8$c_#$6Q-`VLg7rrvw;_2lB4*m5Id?5M@;q&1BY4Ug*UOHXl{AevhE;B|xCw|o? z(XG`^`tk7U80ot%Dn18(^S9DZ5_TJ(DW7e=6F&of27Cs53-~MWMH|xl;gLL+I7>dM zn^f;U#94xmMSmIk<<6FVh41CFkP5dCe3c&rEN>-DY1eYmUmujKWi-^<*m+xUg>o*C|asopChz8?H}_|E8$gRk(b;^_jv5556>EBM#&Q{el;4>@1; z&V>H~zYYG@Z}J}u?{k6l>EFfofZy+VOUJt>(bu@I`B3z)z_Uj#m5}Y=v*0E8RQMh7{6D}e@ZHe2y-;zchD(1Kyd%5_e-ge1Ja@G8z2O5)54Yp;-ta2?CG@AlOUE>~ z>k4=segyg(;W@q-x)1y@cm;k9{1ww5D}RfB7Cb#d+{W|Q<^!+eGap_$PWlhv3;7eL zFi#63#kXV~>*RSjFDDgNU5(w~93@+XWMVT6J8!8 z{UqA;2=%5fR=p+s-$I`|OZs{E&o)l{b{_RJyl{^6i!Y!TEKK~VOT?d{-mdWarQ&Cx z?+q{AB<@d>lE(me;VN+s^KKA4f4%sp=y!!T$WI#%0EfZdr_w)w{y2CQzTv|9!|CQT zSNbpM$4kuTJMnGXNk18$Z{INi75FpoN?Y-p$irLk+;8%En0P*e*XN1bdBS|t{~&%D z{)?1YAAT3Vf`07nc}wfq2IyD&LxI@&&+hQ)@EwTfIM2g%`ULKC*#UkfybK=%e-NKV z{we>n;jBK z{6NoJYS(b|TSxk*TG7u$Z@*8m80}iX5BLy|{f@=b@IIc0b$B%Qt;@mpXhnZI+n6M*1wr@%7*fFQ9yyeg}B=9O*BigAar+jo#uv30@yB{r;@;7s5M7 z`kT!Ea_L9W-l?94d^++;nC@BhD@Hyaz^hltXD57qhp!yzSMmcOjEB?&=}$tx5q$MX zzb|~vh#v#*8}Tvl^hEhvelCM=5a}!MjUxUOJR9+k;af%gSI=7-w@dmHxum@}DV|P@ z^B(XFd@6iBcmsYTd<%H_X8CMR-tzFlQ9NVJ=N9Q#Mt>JY;cL|18v0(;yDEIINWUfA-6{WYzq#<(0lt5v-v?f~Tl#jK=Nt_`IMSa3 zuih*D?F@+PJrCnN$2hn3b85m@bkm}B^-=VB^hMhBGJH_v^QPya--i;9&Fi1xg=vcC zV%ClJ6GJ>5-4N~rY4I$T@NO;}`FHU=_>YL%yB7NTR-KbP_Dufux5I*W-df!Jw}p3r z+kD>`o{IE&&qKW@MD-r;c}wH}!dCbsd>R9ELVZdX1-kw}x!|^jrAA=qvm_pFfRE9;d-;lb1-I`|XS5@jg5?a7l6M9ag4Z-q)~t z$9mpUJ}*UIS(J&GxH)$@ydLq%@HF?&MD8B-Jj^e<&nMHxJ)iJ@zwhYx_~av>KN9`F z-@kN)NiD~}S}S~A&lmjbSET%HNu3`1wxU1W^Dxfq6&*-7LVsr~`iDIa?X7da9h+b8 zx1#?NfA^sLXX4-W`j*?(%kw0jU9@04+(s`r8eZP5nNKmkm-qtc=ff*|i_fOst^I{b z@GtKp{wVxqc ziTlgj>*pEWbbiZW!4*Akah!V|;;&`YQ#OzOnqLubZNB`F)Vr)Z zq=ozOwp6dXg*>xia|H2U1$Pq`Oxl@NbKPWk9=<62DR|>n9ne;UFEW{S-6HN!Q`|DE+`x5?e|FZ$UL9-S4IyAfV` zO7-&gqRyRVfV_X)>8(%(=VqQ zXI#;}?k0Hs9mP3{dO!5MO$+zOo{K&+LBh?ccj5bJ*D!G#w+F$q{p8<{c+P{DW-F0H z^k?@nyfH`oSoDick$$eh|N$ zdfPo9eYPlmJ^uaRDf)4LcnMxksowthyaq3hkk8ie9sLDVvYwC7IOlc-?v82VncfO~ z6#nnRvvFSP&axwJlz_VM+=Wgt$MSp+ygo?r3{x}Qau3PB z%*OVQ_z!^Bmy!O`#r1~7bqK`#>LtML3JwQD?i zs~g`+0iI5rnd$PceJcNRiE|{pLVxv#|HJePNN=A%xd&d~R>oK8&#v<$@-MEe2G#M& z!3)bs$lG#m0=#ji_$13CKNsWkhUs^bp4-K`RiBhkBbrAW!3&&+birpIc&4a$>^Qi_Q~3O;de20^9lRL% z?**^TP#$hX|C8}6#n*tZ_O$%VeU)HN$K4^ur$~Pf`I!hWyrX!&#pellW*zD6I5Hca ze^>gC(06%8^|}etcgKGuyg~o647h9I>Gh@WfzQk4bB*F>+PDGF%0F|J^k<_#9$w*w z(?`N5!pm%s*!xFsz;in+m?&)>+i-^b^XIis_?4_bPr%)}s&_BiH5;Decx2;eZJ(%Q z-5#a!*$4dqc==`dSU=8$r;boOQ#tUj^g;-u^S}L`MaJ{s?+VJ#&iIdqr>gRQSbugi z;hAFue0r0|e0XVn)%z2??~C#;Z6Ll29rhtS^OgGDjtiY%l0HX2+WMJ=7fzDTp$g0G z^Ro2yJ(On~SMR{TzE2I>hj`k(B7N~084RXf8^Pq`8u^LV zfoQ`LCU@cMev+juw}p8rgI8|LYA@Dk(SKTRHA!Lxrz ze6aDI=iwEuJ9c55RN*DAH}Ary^E>j%a$da#K5N7Ct7u-F!Z^POUU^aR z*gpBU@G9%7joTI9CC>MxKbZWV4$pljemi-43*JbHpHBSqjdMOQhd6VyjM-y{ScqZB=!4&@U|byC%cY(wuW!-d5Ax^ z`Vu1a*9GueT{YHhJ%N`N^>zEl!FrWjr!Jq$=hF9ppWu0DS3Rc$lV*22`eFz9=wIi) zL!Z4>Gp>S9YPS5-*DjI}Zd>Ng^E~9I+OxS`7hc!arCa!&rq{hb#M3xTc|M?xD%j;C z=k@>oIMVZw=L{P{3`2K);NGXy+m8wVIDnTv?s?MQP1MpW;BUjzUy6^1|An`F^1UC`_ji09o zzs0BD9DJg6V~poZwe9Bb)2byw>~>whAkI~kpBm$TufY8{FP^YK;_w7M zZ+jl%Pv0gVTmBaQQoPt*<6#T*$HFu7)!q#JB6zl;eq0s)JiI<-P`*6)x56pHX5H3(N_n^XE*ZsG2G44IJWs~S_lqXP`u7ERX(iQb^LK}Dq|fZ79`Wf-9%sRGTg%~e_#LM2F5U}1 z%kz+*>}=&>Jp32*^<|su9q_H@W9>`@)roR(?`Q#-M;X2g2%=hvsW)zQ~ zX~|=E&n;$9QXJv=r11XxW^%Uia4tTTX#T#M`1m|OykPQr(|?CPy@trfRMO)IwRghw z=Jp=rd1!BSW$Bkke+4{qirO`U@&BRcALs;n2tGe}9>#fz>zx7c_2$wq>@$zD zc;MNx;*pzkPkA1Ea{O+sW{q=m;HgiQf8NG+U4N#%+b^CtSBY~3yu|N;FG9Vyc^>@J zD|JkW`F{W}ZzZxJ`d{IxKGN^Y_*~={)tgyc@vp#q*~jy+zIC`xE$WHSDd^KV`Roe+ zGU$CoSedq+g0ev~G@zw+WrtzNQ3&T6i zSAOF2z)d_4{`Khm>g2$EK2wWooY?VVIy}wy?{u{C=kmWwpWa@17)-qd&zJIf=s#fT zgs2tHjYZ#hLj8L?`j_FQapG!)a|`??|JqyXmmK;GJb$jZtJv^^CSA&9*Soz*0&@5zzg*@SU+vvT?Ee@s`+U7 z`3zopUE`;O|FUi6Q&~}Y+Z^5to*Sw8rP`fa*YmLcXJ%_e4MM-Q=OO?3Yvga^ZGU*} z1GV==^vC00yiW~!i*b7kyl|y_Rz&|PK8+(a4p)P3@85_FdC2|NJl@8_i+`xztI^-$ z`BFaq^v#N}Km1wr`ID80^WgK~*=dSrPx!L!Rd4Zmahva(!*gef?})wvuXWHkvG3V^ z1g~$Z{>q_W+<#yu8CQoY^F!ckz)O3pUygzg^gKCk{+UY1G#1dKJP-X<{iC_PKP9}Q zt8b$9b|ZY&T~PIw-j~0f2cG45SO=<`Nxw1rr_g8DSG#V3{{~MNn)9>1{{m)cS2@~; z84a(6A5Qg;lZpRfc&bqP{a;~fAA)az)>5cF1dB{UO zIuASpUTi1jK2kcZ4p%CNHP+!daO`B-H>4njW_ zeJNVcpU1!Qm>dS9{|R0ktoC+=Z|mO(4)HX?5B~ax)mscajED2I-d@dl-C|2fpLtUa zeG;EF;f-^|ZJZqAd1zNQ%Ks(kQx{2Z>&%Bi@9S+GrFCzA{5M)s_10L&Y#lxW-Z)j` zzk&Wf&qKY9`_v*^CtWJ=e}Al)aG!s!LoE+k&y(ZSdzu%vj-BLrckfhYzh!kSZwmTQ zZ**PxHoU@lX$Jid@XSDs2aO`X@3NG9io+F24*f{aTZ(@i`r2X2r^Wvmyux|@3iy0y zd};NR{&nu?quM3ow&(R4x5oQ*R6M1}<=+jTli<1D;`V;lL!O8H7gt;=QCd9Tc^>kX z?V@$S^10~J@-MHi@wp}bnZW&caM5}DVerx$>K8jdxdESQch&d-<93y0RBwI-)w?$S zdw3rDtFoEuwKz}nJj7qEX#6W1&fl*@@uZ{u>eun9U!^>#7Uz2U4VmEY255ZR`>;2{ zOP^>x=}tUzjBl=Z{OL{dSf&$kE~jx~zvsRYytu#W-5#GK;We)JZCt(Xd1zN=XRU{d z%DLs1Q+|r<)2i2;ThsFpf9Wx;59<@p(7;2#eAt}-Ytc7GOK<+qdmiR>ZDU>ORETr2 z&WfkJjpErCzPab2-qe!nz!kYb8tD(XfoB#}ehxu@iRYnRsW;VMWY67c{Oaa@d>mfu zrrBiq`2k)UB8N}$U$~3vt-YZPff{qgX^p33kr_!!TFe{oOc)5h&B=*v0zY(YPE^%v$LpZOk&NB{co zcfji$FE+#fWY2?tuA+LY@GIcOtu=Bc!=DH~zTe~bvwEy3pZpjdLHV{ zc2%5q{cbtz z3n@a2vs-uh6gLzxpH1MYuQU!1z~>}*3(JMgfsKBZcEP}LLo7Y=A1hx6f?>%~_^-_3vC zv86aST9tl0Oh-mr_b&H*l7GeO+v`Y2`0ioPL!Rf|syz3^|5f-L&MS=1_q?Tgm*_!1 z{;Y8#H|I_a+_$$Hoexa$Jk&em7Ae!z`iZg3HvWgei_;Z}M!RztdmiGcJ*(cg5 z&KYpWd0H>{?BL_aNjX|?JM|`h&WA1j9Xt>1o%x*FYsZmcLGOKvA84JlJY0uQ>2S3u zPrYx$3-c7G`TPVgKBk3vZPw?F*HAq5hm@a3@j1)$ke@XBLUukm5q)-s{Kqlz_F7Xu zCEhRF2A^x;nKiVI9fZF9TGHo_RJ}8a=PY>oDb4p?@p;zske|{$%Fht^H}FDqox4CE z`4`y7vw4w*XQKVXT|E!&Egz*gRg3?aiuv$<)+*F{8@$*@fb)-dwL$`U5)oUPJkcedFYqagUXnV&k^{z!!^GwpO+cud|0jU(;WZQ z4E6h-_`jO?cXY+=Gy-kkBejm=EU+JbH2Rf2U&zri%YK^s8^6dgJ|~Q{a{J z)h`>Pe+*uT&KrOCJhZoVyCS#o*{z@abJwU|Tdy*n2YrY6(x>s?0eyXz^qZ0A%RCQx zOGVeSuc9w3q>+}vr=$O(N*JH@XnbzqdGKlEH19MTog3zPh{w%RJfra`;8TlyF84h2 zSMCty-^Ncp`1|pbaSHezMt=T@Go8~-V6Oy^zr+% zuLr%aw;CP47TZuh>9@7MjVGT+!gHHw{O^Q+(esd>I`0Fr%DE}%-P7{93H|HnOBv|;^%K0ZkMxJAaKkqV@%!t?UA0cy`g5x1A5}W2QnVk*A*on_jH>F?)$fPmBvF){CD#_sdsMk@#SLwdBTv- z;-cbfqo0aTk?%EFerDp6c~A}Ao^i71rn+8EMc2!#cpm)ITu)gZHupS?L-)DPqpZE7 z1NZv;wpv&F5YH_1^=RL~X zd!F>y=W3TftxX{rEQigW=hZ;`VuidyQYy-0yX`E2-X1@L6+^{Bvune;eB&U*7~T?IHbO z^v`=9`Zv$#`po}*&%67XT_3CwZs)x#3|76aoBTi3uy%)fp3Gm)i!{5Py9=J?I?IkP zt8J(8*7=d(rxcOWQ&)lteo@Bl+=NIbWljCzutJB_{;MHh7 zoDMJX`8OLUe|X-~`qpU&?Sswa{{NP@wLA~?rukfbhI&td7l&*9+Bm-jUXDH|@D4n` zqVzVN7u`|*S@sFdXQ1a{J(=@!b3TWlFWj#YX!`N!^U?cU51=o1)JV4ZKHq$(SF^^S zx9p^NQgy{So%|mJPe&BsUI!Ri|6RC!xd*+*0&AdjSh<3#?^`N)b*;@_6^2* zo}8!cq48k)JJ46UX+P}`;_SGy@-`^i=ULbD;L})HK0nisInR^+jXt+9F7fH;DjYX$ zy}bc{_jq%^%)meWxz1beW8wG^eJ+|8?RHVUmCrTu?YOq7=V3oK72SVtPtQaA)y);Z ztpmpe?$@Of-`m=fcAXLYJ+E_~Z0DI%0}npY=keCwRXp>C;@OFO9si5^EcNchVNA+(GetY0Po>cTb-v;{9eEIB%e$ejnuSD+`jDVN9UyAMjPlTuM zRetLDEVGC5lb)#y4mh`<)J(JE8*EE#BCqrPPpU!WB zThLzv&#$bN*7o_IhgV-#y|!L0wzqt$cPIgeF+R_R7dd~n_2F5#D`_Iy`R$VXsDDeF z$w#Bw@Aty9M=EdrbS`=9;CXl-ZOi4=UmPCY!HJ&6{H-|N^Uz-@J|Ae;n>TtM#zSgF z`K-Weo=Uj?T@v;OhQdDx{(ioscThtQX5(Y2edS+SxOx5D)$=fJ3-762c0DpY;eQK| z|1F}=a{gd(&hq!83CEpWbR4-9eRWyQpasa!GoB}U?*G{n|BuiYqxU2JfY;y9I=L45 zrTlK7uMdRJP-9&cTs!o_;Niw z#qZ!)o~xdRc2%R#pZth_YP{;@Fz7ZtQ1PU{(fnGTcnY2epZfZm7pv2*YdjC}epxLxONa)|O%SWqqff_hJf*N5nM z`WktD)$`C_CH8wQCtss?U6p6+M|Y_F^Vc=!xliCe&LaCH7U!Yp>o013xPcCsjlLFL zzqdV1{*5l0K_?Uc>TtKB47$V5fY)Boj93|dFFf}E=SA>2=ELW9R)hZlubd+N5DiCn z?BR+hx481~oYf1jEGusFalGlLXddl~zUq1C_d4U*&Rb@~M}MdhWqjeGs<+Jd)cWFc zDBNwQ`S>vWN_grj@yFoLc^>-LeWLZj&SySBU*$Z?^44vb>dmtcpw>CJA3Qrn`LQ_5 z@Eq^&TOQstJ)eKGd9l(F@^@D$!50wc9`M>i8lU%QnQ)zsRNnf3-#mY}@I17uzOKf@ z0UY3O_B^Z)`RF?Cb$F?*`ehp9f9a#7uimKfY45w84o{C$J_iuz6nJ(|`D_QD19!)0 z+#W;xx#99p&Cq=5gMMP*e!euK@9DgQK08DHcHXpKciZ z@X+DFQp z<>yy;hWnJRN<5tl@^5^v{67!h8=l|3dHp#bUTm*;rqGWs!0XRy1loB1&GQha<35ll z;lKH@8do#kRDRO%qdiZ?Pe0Xb<9q`8`ahb-;fJ0l{yQq3J@Dx@g8U5Byt97a)brrq zh|WVUgu9zrfAG0Ia6eAUUub~ab<-!FclR|rj^pOv={WhyI>H^I;K zJgiHTA8sBex1&$-ezA?iUXR6G0&56GDQo+I_RH9pCUiD&VK;UEv@!y z_3+=FIzjQ|KUIHa@Y%)l;2+;Vf4K3XgrU7N& z!>xE8eDYf=|H_hc&%@oL%KvZpe1lIdnvWZvB%j<+jr=u*;M`89$Upx4z{T+LZL0T4d>({Xzf_*>{Ow22 zLw@QxId8*y-tScLGW%4Pw_QC?<}aVeu>QIbePLPk*P+x~_B>qIOpdN=s`zC1T(S8t zwHdNGkm&w9==ze{!{+8kGCHBOtg<(foCUaygiQ3bk9S3OVRc9Tk!b)9;=+L zcyfz1&%3?gndimrJm(O2WpyR;Wc}IQ>3OKP%8hV-A)e>a=bw?odGJ3x57#L(zEWmv zK4y#ZaeJ!2eno$d=OJ&UBQ&oqo)Wyw^|eN+b3M*zl3cqY34M*p+qlR8HE z-;U$gF~+0M`H%BF98af3@84YqA6r-d+WA=3^AJyYrsnTgwD)_s<9pDm-S6|Ct$1?0 zue3bz5B5Cq;l3``@8<>{uFIm&A3ljsv7*HH!smU@!~40#?mF{H5zid_(|rD%Z7sLS zIn>MN99G8X$rBey@-UC@Nk00lyiOPfVVfdte(YR{Mx^%DSq2G&~SLp1; zxfjuAHq*GW_47A)^==t_NIcu0t30IN)p?X1Pp24faYU5B-&i z`s*WnT(oZYJ74QhedDDQXIp0u@H`pMYpQ?eQ}5HBhwmX4qwfXJ@jRUGWvf1X@T1XZwo`wd2cG~haK3jOd^Wtye!HUe_fHtFdNYT~XG`Mm?|D*hU)6gX z^JpYIznpjt|7rN-_EY?`;oUEie`#T@JQ?^=@XD(iw;H9+oeNL%zLKJH?itTRJZbhH zY+q8IONEf1(xoJ;imR&9Cn8%ra`w!L;iwc#U~s>&b8M+}eudX8hN=7=QRG z@KNwA>!+=^_jn$zm*+*-%P+zENAKf&gij^!ef5-ifY<<`oUgY}=>+_mB;rXX@Kw+ zil;PI+^*wxhNr9IZK(G|cqRIt&P|?&dWS}zgL=z+c2@p3C2xye!Tfqz=OI5b0s6t~ z`--1|z5uU%qIJp6i*60vuQNH;tA6O8@H~wF8lN}bQR#CFuF&#+^(^z}{&jcYle$tq z1wIe6Ieeh;P8x5Q!;gczscNsqKOJ7`q;}=e&kj7qAKjN@tqH2P$o<*W3(jo>ukt-p z^}chb8y_X5ts9TRE8A-vn!eXn%76NE#WS9Gw)Q++ca)>+j>A0<`7FLJpGoK^cpmz_ z;_s0f9z)>M&}W~Keqs2N@bo`b@7eI#o`-&{ZnQu`Y<_jPTJ<_U$709v9pLrOYVSt) zTmdi76W!1oM195--;$J6)WMLxf=9X?(DA^+mis`pR$ z(eT=H%D+FINFHlk%epa1`LXqXi08>X`dH^(){oc0^LNYN@;nQka?0n+)Vt<&_*~WO zb0s`|milWj`X$TK=Pp!@{%P_U3r~F@J-73A(>xF3C%)h8D}noQ8-L%h{Ur5o^$+!L zmU`C@+`nSpsyaike%#0N*3ykd!N*j z>X*03^ObOSweoDo@w(~xT$6g=fByDH<+&KWFS(}YVcyM*-j_KF-k<$a^FI}z)KMCr zAJMKEo`*QyW6J-v@D4YL*N#>`-+^xouk=^D?0u;FjPrdx+jsrh^DusD!#5X6Zt<4AB(=W znS6?jhxgD|c2j%xufGq~9m?nAdCF%VpJP2w*0&2)@A2?^;MIlYKLGxW=ixlO9-W7O zfWCO2&aYI9{|&c0ga5xjx_TbkmE(IZTAloTgnD^>+ojx0z6WZr+|TQib%60;$CqR9 zsehn(S|QJ0z{@)-Z+2bZ`!3a6;rp0}P{FpI2me9Q=S9b&Z@j8;vPOHo;01X4MR7k( zlE?aY%RkM0v3Yc;=ZQbxd+JZU6Fd+3tVQ{}AD`5ET9=kZ|DorhzX~5}eY1HqAAKhJ z{%K#Ikg#rNW-0JN_#EJQh-b#T>M4uou%Hk9!hW^oXEMC}wc7hOKHtGBOKK(G8Q$X_ z`DAm7zZ?8qcz!AA&x79y&kU7*pp9pE?K1V_PUsiC7k|!I^;hS%g6H3o50}U8S$OFy z^@yE^taG1yGA|N8^&So{t}dSp^e2be-JXXyOVNErUQIZ`{jK;CeYKY& zv~j!E{fe^|-5+lVJl-dnK6&(&W#qFT{A|y|=M1ya_m8d(+}}q!-Bl|_1O0T*ll;7^_3Cr#eck-|p2c|d zou-C({P$j?`;Knrc`_cpRy?*3yuatkJi15g(m9;yyn{affa)EEf4^y7@8@a$ZOv2j zIn(oyhsHa~gU!>sgWmUV`eluL8_y5HOY9HWIQ-4@A4~rZ?cM4@#b3)Qe*Np*NuCG) z_;*o%^L&zT@#~B?Y}_&rNuO<_`C{upZ+L2&B3zaBp5l4Pr(?g-+IyAf$v9bCeV@{7-m=`FxM91zzU9ly+YF9lU;%dfe9AgCFwIY${z6X&I#hjnHs*HauW+#Q~Wyj8hx zgIyPV1TXxi_I6VG+-grq-{APT3Gwe7xX)RQ`|$LFUxq%-zNlK~+-vac(#nIaSMEvq zr{^o5_Io1xz;n^}*{}6{l5f|+y>&#hdHpnc7kwYL`&05S@jbgeiF2Ump&!fK7sb}M zqtO?BQ=B%=-$9?>LIdh5e11S*8K(>nfRBAz_2w?rnQaFCIz0cN`f)S(QqO4ptaATt zTR%5|4|-nxI|%)*o+s;1^gi`)&qM#_qR)+A2(L3AZC|m5PyD&xZ_#J@UWT2YcY0R- zMZV8v>%)%lI^Q?6dHp^-!@RTgXW<$0sYLy|I=sw%R;^zyHGRkC@$a zQqRf1#C^{O!u!D;pA)n3a2UM&u=?du^iw?#$F=^SDF1ez@FDuf&N^;x!9d*gdBs!N zR^xd%J{Nf&`X#53I;Hhi0&fC8CbAkK#tDMj4tj6E3>?Qe6 zj`r;j@I3fe_`#xo>h!`r2Cql=1MK;-*0(~mzHR1t=&#Om6u+|Szjx_*l7BwmYwt%~ z?|HJGGp~(5gue8&^5&b9Jleg|##v5CO;Rl8dGJqhe=74?)AQip;QbE!T+JTnYa453 z?T7y@@HF2ywDXV}y!xa@f{oj`@ZxWZGtcqv{+jA7@_vxA;ok@MJjwHP&7K}q@w%6T>-Dmlm8H^uyki04YrL!9M#y1w`rJ{g{gen)98KDAjICmk3+dEd^kpV|1VUE;7B zJ~w(E`f+Sqtvo&9k9r=)PpP1I-huy==>2^#Hc}pxRp%C(Nx$$t)?Vm8n%=I(@z4`} zae@N0; zz4v(@{8NW%#W)!L1^WD_%DkfT`+>8hcYHq6*46Dj4|yxzqzfZ|x{^FjMqlST*Ypz- zPGx^9?l67y{k3O;zmK!b_db@#f9C&3*SUaAId%Vkr;r?)qNE(|QaY$iI;S*5XElUM zr>RIfF!Dx8OjJtBBqFcFM5z!%p-`qmMHo~P)i|Yu#-W4`|F!P#cm1#0uitZBzxVn1 zw4SxsUVH7e_r34E_bpuK+dHPfpJ*NVPQq8Tlh0q>Sw;)tVY>`LzgtBw4<| z^Flp`8OZ98}w(r{rLmQT`%ON zB<24@p5c9f)=#GkpDli_V-Ji1GK)%TP$7~)xmOSw0)z6XV zcy4zc?f*i!_B+RWC(z2y{XrhtiircPb*}8UC^y6NI2e7Ln@S$*j(E#)zHyK|`YHN5 zqTRXkzeBmne$bqxJ;Q~E{fp1PA0q9#6TSye@?KW7vU4rS6a21?*?BX0Fb#1ynEH$4 z=^00kM;C5~{_J>+7aj4-ZBZ`moAB<(t|AL=p`AI@s3Y}XP97hP2DJWut8nc{Nsc>~ zAJW3r|4U?i)*s8mFOtU=qNYQbXTGDJ9eK2r>51&1J#A2KecE#YdE#>Te=hahAzbs- z;3uJ{7UiF$e1YS5l;i1#d~cWE9t)6Mt33)V%r z=HZs!x$ojyo}ayu0MHdE*u=Cop+l2g^*l9 z{;YC|L;sxPYSZ%({9l6h>?KbRMn17~WPN{zKmBu>qlN3dB2gI;6=%6m2-kWQE5QE= z@|ENPST0ii}NAEwO$$TdG>kbQf__%+VOGL>o@Yu zZRnufj_K~%O?jT{v~{xso+JWqZzdE#iuFC+gzx%eTr^At}KJxF#GiRe-wk}#$CYGuv%l$= z+4ykSUxj(^58)cmfw$kj;U4HuF;7~%Od(JFiFWTo(-x6OxDWp#@_nYKi2Cl}fO^Yb z=#O#S>_qtmUYC*oSh9-h0yi!c{&t0(M&b+@l1JQt=R zK5cxNFTAQ)n)dD$Sws2!UKA+9U-Q6BOe zX#aNd?AyrC16bc)2cSPQ^T>R-l04$w2eq9%{x$qF9?j;O9fW^UeD7&-(p$LtC;b8J zxsdjcBKO~q{3JYV_d{rRi>pJFFY=tlS*%y=5X$xMcj|6@C&rOkl%G#t>_xnavjO?TjvsDr}WdJ_nn%l%EitC?^m(s>!sxWcXsv**SL-H`z)5vBS+B>KOxRn z(w@tNhk2${xsX)F@{r4r$G!I*%gIw*uP}QyQh%Cx)#_VH?#EaCIkw|zEH@CY^>WW5 zZoehJn7l9uer`)X%DDIah&jTwzKM#M|JXeC1IkDF9Cs1*oKOno#`qnjRpfPr2S=Uk z5+k68VdXBMe3JJ&#VJ2rxYldTXOL`6elO(%?w3oGzpC=mzVVrG@Fw!Fglm7<$M+qB z$q$73`5%Y@X|eq6m{ARl=1t3l+^>8P)rbNN!ZwpZ$6_}S(URb=C67!O=8twuXL30FTy zPenhP&xW~;JjU*%Ij+9l!L z^ER3CIo{i5dfpYTe)GRi^cCe(-aYDV%AnjL-}{)IIYw?{F4N}YiInCCs)c5b(ZaJ46L2?oY2^>igquSQ(WA^%&r+L`8fXMQ;GMCi#h zLR>vX`Ih8K@A+aTd9D`hFF`w(3)glm+zmaKkZ+@WGzR$}=+6t~VvsO?1|r_VV&9^l4yLV7tGwvX9YFk;{&$7zyglK~ z+qY7GFbIAgLH{3L4*pDbz_?ij>Re0V>i?qm9nYc4HLsox!^hH|sgzGlMf+ObeuVn{ z^JO1}oPT`SukF+m@%&k{JnV7Yw~x`^xdFm89&!O@s+JEYkmtSkLF>um!ywNz=e`a3 zVTSPkJs@23Lx%gf>XFw!8TMqj-`<`>ZzYeWkskuezf4}_ebF|5`&qfhi8o(5=M?Bq z@p%NTCd;to1%3x@4D}BZu71dtN5B4ne2U78{+M?z`(xo6SGgkMY82&9ssKHS6X2iQ zIFEWjco+}74{a^wmy$a^7ko|r4|&iAmfLp;Z>b3V@v6{&D&zkZ;bFe=&ddBj`4rpt zLh3(V{!r~t@VU(9=e>lho;3473C8)|%JsI!bl3U5MUVm9BT>a_4|NWKn$!`#EHXlB|GUTIk z(6II%^BHpgzR;z@)t(gh8P=zrU#YyrZDbP$M(fun%fgG=lbr?6TizQYTBM!m}Vhv42e@k3EPu zSx)^AlPBIozOsDssc`k1D}#Pom-4&G)10r`b6*8HuoJcm-?P}b(2+dByf=Y*7LrE} zA(M9_KRsZ4zJu}cY1X$Fd1^ZNZ!p)*5gyLpeuSO&ob^k{<-01o5f5?duU{2*28~ee zG`Q8hB3$d+)_V?mpWMIytzI?g$(#;*tUrzyuJ29uvD{HCcMIj??cjfT9T5JUB;%<1 zEzSE#SuNMbIPcrB=ad`B6Wmv6>kprjNA8A(i7dBdb?A?_gM~ZEqvT0`PsGOSF2*M# za4c{9Kpr$l++IOF$J7x0(oY@lk+ykqOXX6p0@pJyqx>l0+Fl8MXWZsjON58-Q@r=u zpHfe*BoyeBqE9v7~93S5UV zJ;TT|7r+npT(*FEA{~$qEf0K79-o9js6qY7bD=+X8}!(`E-PH?>qg_mmbs=9&!vofTM+sN^)15Kj&NB|@sl3k9y!Xi8Qjh-~x7zj4Uxs<}{ELOFodxb^ zp2K?mOdjxDyv?5*)`xtmJ?eD_+x;H$B){)q{p$nr!ft4=_$+fC^hCHm*^1?!AzbU5 z+YEo&I!|-T7nnaSerA#f-agfJ*|Ba-zIlFPh{ic$qgW%;W~!3 z%Vg!^|CINA=jFoH{=nPk^9A)Jx}qIFrQdSrLx0jcuYF!a=<(O>`;t3v{dSyiZO07X z>osS+(!w>~lHUAeHs#Yx(T>$9zm7ceAN13&$qxz-+r@it-lh@C4bDW%TK|2KyukP4 zHjeKRu6knLKG+i*LysGAq@GjBWBg9UxwNM@d6w@%?ES?%^U`2)%&J_GMQ%nv9Z<2YWM@;ij9Jz0K7%-%aR zZAL#F!aU^vzXu^t{)T>vXp#GNO#UC#*T&!Xg{%HFzh7znYnSjaKD~R%TyvD0$Ri$X zo_RHSb`Q#p(*D81Re$nE@Q=w~7G71x&0-mhn>L>2O+W8ZIfL>?wP3l=qeh*WZyypK z&O;hOkIiG(3fJeXmVD0Q^w52;@=|VQGv=A5|CkG)Kd1)}*mzN0csLK-c;x)^2H`r6 zN4;@;l5l9I;r#U^^(VZ0iq?|Hyn9xE5w80*QvUwWmasF{3~@e&ao&?W@;v&*G?sfW zd9)7vX7j-1 zPd&L?;H7oczn1c`{;+2rxw{bhi_f6JZQingJh~q8HEI85^2{p8ze+tdWFircr#x?t zXq0=ZjSoV7El*A-PyBXX-^S$qvU1j`JNCwn1 zj(YNZ9z^Y&d!0P#jdwLKLb)mKyRJ?>BgtdlI{)j!wLd1dVL_+|<@1!6C-snYBX4~% zy z>)o$g;}YnJFNFW6QBQmF#1m*Q+2#@cOeD`;iE@XK_uAyJ@47;zP%ePDTO;yXZo(_K z{H6ci-|Grj|M>eLtX_<sd!K4_K)KnI;OEz={~B_Co&QC0=k4p>Xq@MFEDszaPkZl+&+W)^z4J{o$dg6% zmvd;(%fhw)`sXHAQa+J~f9!eiXVcRhM!~uH$truiOcgFGdjOHh(Ax*LgyY^OJhiQ?840c@M~Y zEUms#;r|wg?S*T-;=E_?F6v2=$5z2_Hou)|^4y2nhw^WdCrTo54oA0l$*$1v-h`iR z-tu4J+KzFaH@ErfQsL@{!1Ke=apboc?=P}>LKER?fBFwJpw+iMd5+_|=^sR%e*pGG zY5!vKB=@Vs+s^GJFYrBhcgi=v9OcH&M0|cuK7`!yTuFj_rtxyxUCuX`VZ?=gp2+z|L#}M->)H4X=%BXGrH!0+6d zJ-sNOxe9)kVLJR7ZF(lc4;F{}gopF4SCN4&ZYx}katrH@jFSfB#dpwNhp7J=^31i6 zw|+XAJlP!b>)hn-PAS0>1h>jC}# zJ#VeZ<1fHJ-%!3kdC&*-nn!-S@X-IO5Xo}=a`=-acYJ=de!7x8JrH_qTsyHR^u*b( z;U(vqlgCPe1kC5t$P4?yEe@BFC&xm6HQM>J@NgV&fN{JndDCkUC+;RFHa*t~SNY3Z zL!Q%2H%z$J*S&yp7t@|Gsz-R5>sqy`{}JJuCzD>D{3g_2*%iF^iTkNPxDl3gp`K_j z=+Ari#a%|8>V?R>mGWuwXn?F`^Xf&y)&8I>;w?`36MEDC?;M%`>yQV0uIWtqYlLfk z&wB%jp)vUll=siSzd@c}3csDke6m`&_AkfzFtVs~hp0c2M2A>M{jK|;T~f8tE|-!| z60Y?s6d{j)b?!lOR{{QMMfr{7xs{N&JkY!^^h6rKKNToHRk+qS%Y7z2$kz$iIEisx zy%~AwexgU$XRgHn`xzG|?o=*%lHUE9D}}3m|GN*DBp@HBBfLe=YO`{(ejH=<%r6 zXqI~$d4&54pv}1%D;hG160~isk9}T5^a4GzAJ@r3A zUK|B(d44r{gy$(NzwIHWTo0Lm^v!^3JbfZ19 zjh93`n4f<2`J! z*131d3;cfQ{gnUK^!J1P$I|{|hoD^s^Bx<^w>^Z1<$C*VZY2-A?}jWTFEUSF%yJKr zN3TP>Oee2D6ytT=yPvSFaE;F%-o4F(gsXoN92ab!_Ox)dGgS}uno0eiP~QKp^e>dp zOo2U5Q$835JwZ+IC+MFE!gYR|{1G#g*D1e@@{aG_Y~FSJaJ65aLwPTO<<$-$m+yvn z=NfJiuJNDt?(do{T;GGZJmwXAd~{ji>W82mc#QL+pHz?f!CPvm#0O$MJg&mM5=NF7iq5p2H`N^L&~0i*@9MPf#w+cO`E|x&HTX&lIlu zBRm&x&yV+#2mBtHjr;Ek*SJl2`;E6!KGOvKcLV)gZ3N<^QcYwG)6-D7tXJf?UQvtk zU4^SXQGSQl;%z#4j^A-G|7;eH2%!T0`X%HM7vnuzHZIh>1@`B?_0k)KYq@c*3&pAD z8ROg!Y0nogk!N{NT~*50{}1i=_UHE{cf4=Y^5^5^S-u~%c&IQEdK~Yyu==(UuKv$> z@4tEo*SL!P7cJhI_ACfF|KqSXZWl=9=1Lua4$GBc?{cDNp5j))+M4gS}?^E7?A9Ty@ z(4X?okGw#h*@}KTi1}d)d7S&QV)R4lF~~Eyvnz$f)`iLoSNn79?>3&sgsXpo$*6CD zU+zY7_cMUaucn!v<%qP7EO(`Fjkl=RUrx9K`m;6QhtkZ?7Yh&V*$Ex4#qh zZO{8c1IphhTph-%p<5_wTI#UU!%J=YM}j30FN4uCG?3$ed7Kcq8by^Ln39 zKH&R>IOUH{i5`i=81Mb)M1Hw)(VriSnb?VptNVp(I}YY~oLZEhK_2z?M=e$^>&!8J zw+>c0x0d>|+&^Raa1VK$_ZZtad-6Dx+k@wq5xug{TzI&i#_xt$-WVx7j34iw##hNB zZyy<-yT~2iLov+ViFa$cvcC5NTA?lL+g-Tok9+O)HhE$(`eR-C?IYv72Ovp3CGVmA zGT+pnhUB${tN!R~h)=tBWifeS2;$%NT^}URltZR^oO*_gNB?qnp++{}TO?fD-FfGm zb}E;0-6YK4>^pQ9PXLc`9pfD8?@OL2z@DD0?^D7xZ)CY}X!&yod4cOub*QK0MCcFx zK?_>Hs81f}{SPt9&mb?1fuCg<3V+@cuJgda+n@OjB7RV)M5LD4)9v?O1~TS*~3CpXU9dM^n#k%E!+| zzq_3Lj7carPygdzC0@ug8Tifm(OB}>K-6>|^}L{5%FX%v_b8v_J}zsQ-NH5gqi3RC zwlA*iWZ2{1M^u;Gaoo9(<@OS;>y0t*`Q=H<$Gv+D){*D={rdB$=WpTRc)bPjY|n!g z??XF|`WhZ+Px%JIwY^fOAU=6rj$0&rwrIMiA?n3uahrv!e)k6E8{?>_;uOZo8_=I1 zuPMem%#oRIU65Bv2R^<=(7JKFoipUAWCBVSGC-<)+n%1yq7dRhOv znmohzH5TX7g=;%zxlhdU#v;?R8XD|*=Xdhh0%)*)eTaH;1K~Hz1GT4$JrY+bFP^Va zE`CUCfS$K${}kcc?}}XCuzdbL<)g#l2U`y;I}Lg=+&6CL3S+`m&-_2oE~clu$=`yR z9zVC~J|$0o41bnj!`FR){^U8nA82QP@*=<2vz`1w@)$29YePNT$Wuj>i(c>CnQ6pB zhWAz4b3|j|q2G8RXgMzEKSTLA_fgn*(O^3L$?xJ>yAL9dcpRz8xau!> z=cy+v7yaqk@UY3hPM)0w`)wcQc@INR%DZpmI^k+(gy+aCA3hdx8hzNW7gdkSzYYIe zd~P5Qx`MZ0yj7YB{pn4}SGJy6Pq@}A@dHL8%aenYi+=yy@jUX>N!i;J>glfNKlb>21D+sFHba4pxLhyOwO`QALOaR%k) z-$%LDudflV?HK$3|66~YOZhb4n_Iv5ROO}IWO@b__uj!j`At$vGzScUSR&OO$Swcn(g>5BI+gbzT|0dJ?$0p+)~KjP5Ix+6ZZJIy~=3fK8qf$Nz8@oB z`3v&&U8vAp>N)9I=!wsOJqe^&cbjmH+h*SWgm);PzY1|;?f!@9=>iY5q5fv`puZ^d zdi}Ba%tyjC&t%uZpHa$JnooQ99w#8bh&=Co5BDzNs%J3o3G6}nRg{l%K8DtJuF3-F z_s<G_QEX};fVLjBR_pvRwgO%Sf@Ed}oXw|V|N+lxO zL&;-lv~PEon-#A9NjHEe>yv*-`QSIq$A+@pgX9rj6v1hVyJ#WG&DDec;gr9d-0{4j z`9C8ZnTiGS*J6|R_Mew{@!#bu3fF!SkDc-oc$#_7Z0<(x-*Y~LJje3_@P>1{$qRGPkH*nIf0IYO@2S8J1CwJn8K-Y)u|H6&}8m@^=W=dL{XO z!Jdy6l1KUdulp&#lX{{jAfL}>ziYFIe!CBTi&6e|;X1wqT#u?oo*{Q^Rz0O$-I|J|ev4e2+Ps*DwjU~@{>yy6<*SwVe2a##}2O_URfALIki`%xsReyx{ zsF;7oQQozOoxNGFC6v#NM8D`p{u||^FQdJxl9zc+?UC=9{(NLU94K7N&0K~2R)_Jw zO}WIIn}vAmO+z=o4*BdG@c-GA-$9v-riXd;KI&PjTuthY0PJ; z(xD3|U-Z@|-xIEW+sE$@Hlv<>sz>zu-!rPTOw0Y>pO(UPUR24O&yN!xw$}@&m*uOa z##_KK8>zD8@A6GDDL20W6D}TKaaEQ>PcDzRHUHNnk9p_6E)}kNa^5`yH&Q;$eL<$@ zHS!Giw_itlwoy;k^V>ekC%KMf^O+OhhMfiOuSTzt`4xHmR^$Q8dtJyQJcrwrQao;o4p?u9KUdH06uD z$3Drt@w#$}x57hcuL{)jp>U0>X!gkd_!IT}-wltgVEbN&7P0l%TgcQ#_;S0Dk{I5kPQ+LQ3!Yb;hS?U?3$XviLNp9bY~+<$BPGfTb$J@HGBSEsRF?a0$y zm$NwkFL~rH%oAS4aOIA97kYx`=ohEbpXZai03848TtDI3FLE(>IAFQsD4+G_nJa{= z-y$4mZ5{Y4m6!S^s$;@(5A_@*4|u-M@H@`e(&4z^GNda_vl|1&#$T;ZQpOv?zaAR z^cv{T@O`w^>vZAaxVap92D984dD?q_(MPz}H+L>-I*IbrLizG;6wm#km7SYS`6BPT zuzdKHa2?k=d*fP>yphL`T8na{?NRPLmfM^>-wJ#*c^~86{)c(wktN6j<0xMgu70S+ zdvB~=qVKbPx&DAwb8Z58?k8l(&#C7P@?ve&)cSRe51^;O?>{c5{BYsgUP*6X@$yif zLWlj@O!@rgNJ!^%d@sLF?ALkYWVBQ8(7V+X6e)b~;E{qh9iVSeC#9*dI| zlrKIG{U}|&V?zBg&ciLwocba3|?eU&u!VbFq%BI5$%2{ zUsEohj-svYvJXjT^#+4sFu&33J=@m82HWj9O0oq zuK+RszfSqsGpN@>mYetldZLXHpZAl`AWy%Fe02@;@Uy~Ie{2G{>0c^bs`weoO|L?H+BnsMJi>j*!&vV1!vAgFJI3UB9=|r_Un7ru`>X#VPqV#j zJ@bt9u%}Q&UMj_U-AL}d_3D>}YyU012K~|I8y`~MzmKlW2Ken-@BC2};i{*Scb@1$ z$`|-uKa}F!V&P$XdG{_BsArP*-JBCPqTB-SF}RxksV`jh6yHOf4B>#0RW9RT%G+1J zo$}eL5VXA1>T~GHm4F_bhj${6diOZ(CC~neehP2P_c1r2+zj7K+PtxwaE-SF@7--q z`-iH$@Z9^z1GZi_Te!|omU!#h*4T5h}}N@>J$+k6TCWcePpKKWqb8YdZk z=hX7jT;-xC>Fsm)Hq^sEKJ3>4>dzcNUbXyl?pG}LLU{fp`fV_Ig6lko$e$Cg<>tNn z6H06W_upSPAa~r?Vfp8J;Tmtb-iT-O`2@=679(P9d|YYz&qsc4!n*At&+~nX>F=-= z_QWrP=Khp_Nw}7qDUTV;I0nQv%17=-o@__?&fB0T(Gca%Bp*Yb+zsB6dR7Y8ei8Nh z#W4lZBkdU9g9m~)=+FNMS9=`4Pi^aRb5ve<70=OjPyQ0h`{(aUehWSEUeIIh+nhY*otIxg9u+_7PX)Hi3E#o~!cvrL z^=&3x+pFNMKin-`?RUJ-{0aJPDfI;2cRI>^uloP@=d6%Ryj_d_HIgDX2v_}a-Wy|i z{tN zxt^`yBjVdGBCozNd}oV9p< zg1qnq%C-Kxggno4M%S?1zlG~K63~8&naGdOAN9UFJnhp_I%x5xVBe<=WA^KlOWG?9e5V?yd*sA*HzH&)tSfMA&>u#yxola z{GXsd`#s`+8ufG~ck9s~Z5?KiaMhoykCC)JFKzi>4M zmbK)4euY0{-no@|!c{)Od%x|uX^V34XR-w1q!;z{{0;t0wnUHnhq&$AsLp`91~2|dL(p~vRW z3&=A(?`HY==zZ|>s4eg_qQbeBzm*D~>vLRxuy&7%yygMNb@hPdwid4Sjd<_%Mk*Ko z$9NAe<$@E z{SV4bZ%5v%$ML!ed7kTv7C)oOlLLfql{Q;Yxc!nNEO&yCuAW<7a|&!IM7+Djhwz6Td60X>BZ z@ScsER|?nq7CGOrc)OE4^AFnB)>q#qcYmY3?q$6;P=B%qGSx=%5l7K}-lGyDe@3{L z8}-K1edHN0p3jr}-BdpC&MS2#&&@^Vw0<{7xcaSl1KP{t=N<9_zsG9*XdCrJHlRTs zrk%}8q1^NqNVaExpGThJ`CQ9mMdA9r@`(4n@>7q7p2Rfxb1(H*6|UoHd+sN+IO$Az z$8{2Gmx;nP&qvS4%yTXE%vCP+jd}ZmHd0TP>jM_gmmZ_?|NC=|aP@QYF2u9FFMf$U z#`6)i`KO!6i(L2H2eaHo$D&^IIiIw69z>q;=0#cIYJZ09V)Ncjluy5p7HrRQ4^TeG z`9(p2=z;AYr>0R=y zcV1vOdFuMop(PeSr<{N|DSQCSE&f{zS36VXq5lx=A50$SdhBWB%Y>^Ra*Q9$`keco z^2I-(rwirJK2ggp?_yU#GDd#AaGft@z3){$qg?bCc#pS@FN=)x{7W_J*=C&gfxk%J zNba)>^^8Iuu)NV=y&zhe6~+g{%FMBK&y{`3vN+ zny^34I4@lu^7&b4ueT`QiaeDBxB1mjljnYzu9P1qJhaC<7yLNoBNd>>3~*@;L7?Zccs^c~Bevx95(}$@3}n#}ce>gHxbC%6)DYpM!*}{e|+# z4-=U;J{BI1U%$Zrah6-Q0_5`-LjD=@VZzn_iNVM}ACW&z`OFjGmd|s-RsRy+8-Ui7 z{e;S8y*KBr_x?fsIljlYepII->`Cl{h9_C>HNw@NoEL`^$P+w2Vt#mmJhK<^WA>~e z&(?us&ZR#ao{E0$cwWQC+z!GupS$KqKA-j$K3n*2%u7_`rc+OX_Z?ZDe?hp~?k*VKvf z_lBJRaoDfNgsc6rCdjLs_-}tI7yrk-@4`1a9r95=M_Yb~3)lE5?m|0S`;Jk0(Ub7* zMPE$$@!om%FR4FO1^$1W_Ov?#^>UnNTEDnkxZ0oL`%r7grR3R0$TRh*XA60e_hro` zKjlp5akCK5_mg)b&-1;&)#Uw!YkL(>M?bRp|2@Lh|Ni&;o}iv+DOkw#=W3mWa&ybU z2T*?>@_^rcuzAZ=ljrwpQ9I{$kr%w@`k?{zCmN!qY(D%bdFDBeBaHL8!bAJH{%rG{ zcPJm_`zae|zcc+mLQfCssZ>?{Q`zNrAU>_!9>TS~0^TQM_ODW|{rv#q(B@Iwg@^r@ z=SHW|KP9VC59dXr$t#i5evI*?C(cw9)Vz50{K>mVAcJ-+&$yaOo~(iVvyJ+f3fFdwUWs;0lK(>a;sD5_6z9&U2l-TU zjE`+7e+hZujgRAmYu;E>0vWg#<)0R=?HIco`mG&*CXXLNiAyQps6NW|@9F9yg8Iv#$wlzhF(uQ@W#i{v@xlkSv1@jU2H^1cFC>s$}v>bK%6$S1b`KP2S* z0gNv$o#fW)mXX62i$*X z^VPU;)#EsyH$7v>GrZu(>a|9>=524k*@+D>PPO;OsYb$8-v4g+)xx#iH~os5rs#`% zR9@{YixSt8KSG{ZjePha^Jm}lA)n#=wl?JllSjGls4e+I;pzwf{+11t&)$SQ(1r3% z8bUth-N)WfxZ0VVhqziyds@rA389~P-=pQXLCVDs`Rmax4=``f6|Q=UIe>;NcO!Ys zI}dnVW9WBn@tkGj(K+N9p8K=@m>^FD$P5=!|HI_55zu4z+HDlB>mEy(SL2kgEc0)* zGr{xP7C$|eOTGN_7uQoh)ftvFqn>BTV_fI4c>74W&O!=}uKv$x_So#c@j=)Y&NKbC3<`NT4SlgY0j zkCcGlMspl|kUV`X_;Zv$BwYQLZiW0@m-aMi1^xc_Pdf;=v6!OauYQzw_dtI;{^K_C zJfE9vygT_q=#TAze0|z^GkJ>p%)3+1Yvcuf2f^ko`^fX$|KEx7Ok-kV)FPUsP!|H-$tH)5qTg-UN;8)(W8(7Y(6=hJTe|}_&W3C z4)U~jUa9s)(35QlJI7N05b`wd+qr>!yl}NY>wUleY2}i~++}ER+ef{NJd#7b7EsTz z7o%L)8TrTj-$c2NcL(sCScCFCDPQOad7JOuD_r|~vk8d&fbILb>F4-+5A_@n9`@gn z=t0xT+qHo`xoSt|!yAqBJ@QP--$Pz_2mPfQ`D^6)y>R@GwC4x%^oeLM>!;-}VYyAg zEpD$QkDLK+`tKqyP`)P1-AtbM?jtIFDe7C!`#yVB;ToS&eivXe^;{`jpO*{XIlFYo z&u|I5&xaddPHam(L(yL2nV6m=FYr6mQR@HMxc8l-v)gGq%DOA>2|1neZG@|x=|PxJ zE+)U3JjU}ECjWx!k$gMfyT|WC%14hv`!=SY&Ez?L{}JAHu55dhTj&aTYnSVVYy3oc zzm1KX6DVKw_DQ}YT=(I-lQ1qP84qW4fSy!ej1;?Q&-LVqF5pS>r-iE@;@t0Hd1jMv zeU6Tu1rnkDZ>cBx7!thAqyD6xg2bc#e1qwf>(`O(cr)@qRhBzTc$lxaFVX6|l03!t zViwOoQcrOm0^wHLGqV%)yQo-a1}y27>G*t_WO)?a#(XM3Z+ABW=I zm*mNd0POw#SzVwf;(d21PG01Cb&U18g*;jracJ#$zi_SBBz`A1p!^c@$R^Y`PX4WM zZO4G;?<~$6bcH<`ekU5GIM+eA%4fWD^uvW~ei-FFr!1kKH22kaXSo~5QceFO;>yc-R$j^4}<0kURt!RzlM-IuEW^;Z8~{|@9Qt6Js%0zaVO#32mUSPBjsSfOxMGobGoD4 zVA+xNy+pYBEw>E$whr}=q^(x%rg$--CZc`N&&{bIXTk_kjKg_obgp{r!b& zJEjLg-s0yG;o84yy@^OewsdYG<>M1kum4idZ^~ufmGSO1jrL@@r4gT_7+0-@Yq_}- zP_L=f(_46$Z`Y$-%Rl#0KE?0VHlh40{?Xe10qvh6T>TLD z&gZTrPn-n*%%l95p&lvMJ9mFbxy}>3_s(^DQ@?kw`$NJt-h%fLZ!+wLKYtp(6zz2x z+v~JG(Br&&!n={jSHRE6iq0(;uI(8A3hik5@JI3;d%&%q*69oRzLKCUUTLoT+5GJ_ z%4fMBo9W-xPf*VrX!jK~Xfe6ty>S0wxvoFt)BWJL>&Y7nSHHFQ?oWsd*XN`79w^4s znR6-Srt`4hGpIj5^2m0{3)l6`sP}$<2jx>WkeBTHN3{pQZ<&iwuURZ3COq^T?@vQ( zIya2+ZZ+a+2Ia?7KFM{b^T~6RkMLZx>G{&+-$Z?z@p+-fK$IIi01xnS-DQMpJjZT= zoq6itC0yGv;jL?(doA@i_~%K=-y~f1xbL9H=BxLGoI;2F%1}?1>vGWM+*ayIeFr~u z;op?Mj{cmFc>bLM)JnMeGj}EOu#Kk)@`yL?Pb7Cde|k6dA2&$!NSs8!fWs~(Zz^2< zkh=`|ryu!aDlg?G=E9$i$d9@nJjwevI+Aw~uJJR1=W~xDA3*uUp3fALqyU%`B}UAXq6ee6fgDDQ579~{4L z8<1BQ9{PVD?6)`^PG01?M{ml%OrALh<46gX`vrNv6C%OZ6WR`e{`@7-e;nmUk{1_1 zej4@979RR-HG21TR<3Yu#|-yHSiagt`GD)IX8%$0Tpjv{_xvoTo;%6?^F+^(7kKY9 zMjz*jTC0g>xJw4%B1%__q!;cZG?W&nSpgs zc-SBLe&5FB(}sil_hwuuT+7WKJhK0Gp?s0^D|>z!M)^WLl=}|4tJ_EU|%ZE z+z9>oiWuo`W1i2DXFH+2(94`Fa}(rqyf?$KUUkU*=Y<=EYx}zP(156rJWu%y*RLAT zo=u^=eD}&bhqzz3`Yq4zkXk&PlY~8y1JJyHdd8E-OM%;Z?0Vt4{xIZt*uymJ_ESE| zeKqj59PhnZ%l+SI$Ehm$Eb`D`)SQvpeGtYvpq+@MILb7&)Ve=@&dnu)`j-Z z_z(TR0{ZVG|5CW-)l0l_`A^D6SD>c0PFruJ_*wd0(fdwNd*SNm*c7y5l=>&Dyv9Qu zEoJSsobtJ@aKJH~uby)&;xOgy>qrV${e_R9KcM~($Q|!j>`#8ObPz2!wFMoE*8#ip zglpc2dEXbfR=Miu{&|brspOHe=(*ERmt-8`bkvpJ2 z>+LUHAYAij-kZ07K=}gqMfaheKgc6X&@Sfx)5kJyc@Li1Gf23`ZQ?5gn#IHO$~A6z zuj4t?zl%KA5+muQX#Bp7QR^n=4%N@T1;$(%+=K ze~;fb;c92Z`(9OaBFaqYD|28i=pn9}l|BQY*gZc3Kdqt0&w+c>2OBHF)c;TAgQrw3>g8Xmcs>gZzs4t#G zKaYl;-6%gnxcVpc6~^TS->>z@()Xj>fbZq4 zzceLJ@_8!8dUYU=Ux9vy)|Br^D%ZFwAg=7WX(r{fH$mR=Mut4&?PJ_xoc9cye@>W6 zJ9)0z*2k_Tcjv=n*U`>Lgll~>O_1R&udbwgrYGjZb`MXfY0w|%{`QBdr=@Vs13B-U z<~@{;dGEVl4CSTVQ&6sr@9TyCTYvn4dQzhh|MgjJrw0%xxo6;EWNEoqmOS+hW^}tK z|DbSWURZsR$*wcl4igIb+f_IPEnoy6tzxM8r z+Cn`=_80r!+(F^`e3#_&o%P?k(@}2WWb_ofZzo2cD6A>dCc-JvJ^3ATN5~w|aznier&CQq=!B<^B7zT0DgIO6^2{wEZo2DwlH8o8ixg zIBvc|9_4qb?x+6mg=_xw&($6vFZPE$pHM#bF!V>cza81txx2~J7o%P5{diXRzx9iE zDWB!JZ;O+XGpT{%56J_558Coug;~&3d>48SQT_(f{gh96 z<5aiDQEp*4{4oYmwYESWPlpCe~GEYL@@m_t4hZ@3loK1M&;p!k<<3D;3{oV5Rs8C+k$-VD! zJV^P>GiXPXUr8S4{p1PSbL>;FC&u?C)5vQH*K+e~puy&!gYNqG`%(r_-VH<^w!ATw zJaQ0mUKY(J7rQ{e>xX(-9M)GZ<71M~iPld$Qa-}>K31jM^lQ`qGWA6JAOm-19{AbhdA}cKYtGf1qxtzv7w0)IXmjpT;p)$?K81qDw10r` z&_DbRl*!K$uKg&x7d2`|`RB`eoCwioRWxV_1x+@p`spk;?(Q(k%Z$EHN;bH%}8kXDod;@aV6!}xGr44^x3HAJMySyh{ z^M4P1|1L$5qh5pl;zMYum&vCJSNUe%xto_LpW{9MNy>jLT-z(g_gt3$f25wMw_oc3 z^~eX6Lk-ri`@If({BxZzkVij(W^2dVOJL{x9P)n;+H<>b^;_gF=(qa^{vmhXzU4+s zA@6wqxwZRP@?uw%*p&M36|Qlc^5XUd%14et9GV}#6t4bBaGzLx>iL!ON$x+aL*Dxh z)TFME_vMRN5{O4_Hz8rh^-TzN1o+9lfP1b zM{@u9`gY-Aym3En}rvhwJ=Yf2hiG)8vI85v6U(H&aiN_aN0K|3|pa-x6cd z<9H2+Yq|>M#&ht4t(%P%uJ*hAup~-7%Yt+MV^IYGU zMg6Y}595dLgRC7tR4(@C9)bNBC7nAUT=!|GcwV3`_0)S8dXh0@w#ppe?*D?)Dsd z^lHdQnq$I>v<-oG_w63aKSdtp`#4)y zSwLQ7d_GC}ox=YuKOYdT{Uu<(XhZo+-iO~RaedYN)?IkmuY1DIh4jNZ^59JL_qo)w zhdjFig0GUtK7c>{?-4v8T-$4jcQ4Zi!ozZ(Mnsu^PFqL*E8@rIy*0=)JdbuE%k4~F zI2Hc4dv(T;NAE|!{)6$qmOR3JP&VG3{2}a2c+YE1g=?JTdH;zFXW>s@;i3KQpuzTQ zeM(-q7Q74XEb$TaXBfA&$SVrhyp-X(BBIo}hQd{U!2QpyDc@hX&O?&kJS0W=B;OZY zO!*n)#rxm~w6dI2Q!eF3c>kyI<3A?<5%YvIsi%!_wKM%C9AN7Mx2n9@S*VJP)PV9Y z3J>G33u-i;yh$G1algU2eCQGpV;}-vynW{eXT|E@2DrkeYrKr|291%&@Xu1v#a+R z^d}krQOdU#9`OG^{8v}X`}b!qAousHe`4J0r~AkYJlBk9bguq-*b{#PWHJlCO}Ms8 zmiG))B7cDL{{H8e$P?UGVEJJydEVPMb?ye}FAPMSG-Q6BY`hGjt})A?<8FH#CWfl^{=UvpXAMxpQC)h?@ioH{kw$!TfZp% zIrOI{AwDh6XOjo~ZmIS6wd9H6s1c&YxzEV+oM&RzA=fEvqJExhXi0n0!gXD}=Zf%W!QlwVI? z;C((e?Hs9-}TSg&KZLVxi!L~Iw@d8%-gk9-7q^XKKt<@syWX7D)m z^roJ`yU*pZ^y9D*zFI@eW^UgOtD_r9w zel*4}J16x4^`swz|81PwK%SWcI|oz$gI_~`+I#=?m2mY>{(bl}OFOH5gK{(7;s2fF zoygPPy=wi1tNoR{dljA#uJPu$zuNM{=j5rY5V1DC|3y7(yzj}@_!f3%IUlP>d%BYs z-+>=cYv%?CSHHO`$kK08{#Np+_dWD^|Q^VZXi>9>!H=%y0(t-~JJ< zen@XSGJa0~j`s7uZu3JE;c92VdmU}QI-2qso-aV_IQJ@fp6?@2igRC6Ph>9g$yv1L zci|f6+j&nfMkP7-{XOg{c;9dRliV#wd|I5JwHP$N;>;TXFfp$0h&mnh{Azy`x29X!}KHA2SC&_a|Kx|%7ekaSl5s_)_+m1ZX z`&+8fo_WH z0r?EdySvJU#PZ2O^1{jBSL3n7UHfxM(JtT7@b0xs3J=S@7XG*VJV&_tC&Bv*4p3_ahHBLY|-baZi!wx*~qMQU1i;(39YPOPj~m7q0D+;{5pn z$`7Y}hUZM{kna?(_Q(0WX6pkbi_o9{2l0Ok1hb?!Iba)E6RMI4teGhgwhh1mj^m^&j^;{2BA^SL`f2?7uD0AL~>8 zHu5Cz3AFb8jy$#k<54}!1AjnIj{Bq9QBRgU)faiV3Hi6ewS9|&kb(QLz616k-EStL%QIc}LVt|=+-%?V1LXd9JU=6k z@Lr~BwC9*Vp(p<|>U%W(Q<*%ia}@cxnDYIE>$$Di9OQ=x{g4u_ag`nh1$`;MnR*iM zpuHxOm)M7Lv)(+s6M149%6*9XXBp@Ix0%$l#`G+Q{w=h>&R-JGvi|IjK|E(D-(R@4 zOO*act(|+EJl7R*!fBBEl)T7wKm4oQ2S%ReefXAFe;2O)$?;rUdzO3Z-zYb+94-DZ zc^~0A|4(yYRxR?oDDOT)p0sjjlc#wOXNgn07p3&s*8ZU=_x|aMO^2n>uZ|xHN z13OcLj|(N!lpjZ)c^i4`PV(10@}AG^?yV8IQMaNC9iY<<+>y^bRoY?xax_ugdQ9BuNSWU zJ@DR7O`)FR@o16i)U!dk#DCOVx2$*&<;M5I|J5iT7q0WMz*{dJtn#8KQ5(;*pHY53 z^Vb&&Bp0=W^lNUQzG4V6MrpJ2HNM4xp!7-aGkd>gT-3)_JB159Qy6{9xL- zM0j{Ug8BJH@*?#F_al*8oD4e#`qP)f4?`%Q7OwF;lLzj5kiSfM$Mwbz?Gl8r+**jv!=&;ZvB)z z!i!;^ru~(VL%9*&<7exiDd9SQ>t6@`h-SJMg=@L~et@mQwOs~#_ivwB2Koy<(2qW) zq9l3FJ2&wZxuYL2I!N9i _+WWX_$3uT~8uZ`GdFdkZVrBG*I;?M*6Cj`W&hOVI zFMfjfG(X=YTmB(^}H5x{>NdzJ{7L5pjCUj{D?oeR!+za_$Tl<8xe+b{-dje)l|TbTQ+k zg>a3-uHO1#PvKgxly{Er0p(I|-1GB~!gbx=dF^t5@zd&YUs9->t4T`3R!d9K5kN4UGr!c~ud zZ~TAAa}{9!Jlg*}dA=0JDVxv#L|%9ge!hqD%}<8@gttHZ9^qQw#CkM{<-@s@PoIf+ z?oT^+s=UrOZU&e^{bf&q{tWNwg|*JL7OsBI@ZMK@&h1V4;z0Drm#Ak3d5q`RqU5W| z9rpp+I!yTrqF?6ii89Dz)~_1~5AzuJ|JXcjpzzSoRZ*{v)W3xCvGdSU@TzksR)n4y zzfWQFnRA7!p5y@}BpWv`G47rFevEoDXCT9rWC6cWKGGUsmHG2jjpy=izjwaxY~fmN z^gh^ce(tYa+NJO{@_^O%Ny_K@Lf-8BjNGjP@5XZf5w3pA@VhEsk+-PC`g-R;M+y() z$Gd;*G0OY*C%hqC)X8MfpSz#O-dDd-Q4W!#>XsRfMY_0)C%jBIPevE`CU^ zhaW87CR09L8TH*k`QON6-u>Z`%FyqBhq@1Wunu<4V;);={1x+|MZ$DdFxZ0Csf3*3}YVycn*t3jw?h&s2tHAF8btf+! zu-|$6#_N*Du0b9fMERcNfwwPotZ=obI0F8(e6mcq=ARS1b*fLOC+^+7~G@%NWD z5w7tW=X=juw5Kh3tu05!?PbE%o>(hbf?7B?M)m8sb1d2gf92dH%KPKOXDTmx@5t`f(i&q6n9jr>n*y^KVs6+HwEAdno;Ilj?sl%WWhc;Jr6j6F-Z1BlTm)m+t}>KcD4&eKx*& zi}V$IN6FG}v8R?h>g*TY6}Z$Z!aTqA4~GI5`JZ>!m&qsc%|ickANfqKQ~m5o`S;mN z`J^~Mok#pO;G(wx*LQ3`eZu1O=ev-8=lM#X{fD-f^-FQ!(vJq*^)=wv0)LG8bKCDO zwtSW=QcwO_;;B!n9~-4#nk4Qy_l1V`hCJvmjlU@z2Rj5uy;96u+VSE_;@*YYzihrS zun+aH$GeRrXR+Kfh-Y~Zs*N}9C7yELr`v8{_D5%*a5r#~$K!sayI9{Z5TE3EBJE{( zN0@lzAKD_nq&)B2kNkNKWj*=q0bJ&5$2j|W4gqfN5&E#UxYa`(xUt(8)K0AZ-%S3a z&OM?(050-RKBw}0i{R|L_m;Y2-SowTJmZ zm2-^svg1M@aO3}-eM;AozG9`;>mn*{w-DQFk;-HF90FYA8Go>_AAM4A=s$LY^0}9K z_!jw$@SQcgj`L5_kMX@)Td$8)DxU!NeeF#?Yl$~H_i=rRcgh)Kj`h1i0k)YPg=3`gXXt@(-sO8vs-=)A!`$kkA{a?IKO8Qu@3ciE7 zGq0*v=|^r=|CyznD~V6kDTgHSF5se{;cqJaDa3CfKH!g1#Y37erGBmjE_{ZQYPa)gCx0NG;`>21-?Hx_N0hqqGBZ;{VM#2a~T(c0B7 ziI455?QZ$+wS@X{_H}28yYK40H>~uHyidT&b0l!FlbB;C*8mqiH*TtWUO+j&4_xeR zoagRsdp%D6S>F3?wrtS-c_3z4)(sXQ-GT|b0_Uzw*QV=KCfxL ztl#)M@nPpaw*`l(oI%cKZ95(bTs;^C=U?5xrQF7lmTTj>Pm#}<^PcS8!~+95zFRr} zL_EfKG4xl@`*0KGaqhKP4P4^S9i8|y30(9O;CNx};T+--!Sv-T0(#8Oz;W+wnEhuK_OpeByDP$@a4yFAyAZR`8wEDCzGf9vD?ZTkihL)&7T^ zb;(7*g->>#YOIlbjs|Y@!1XxWjsq5dQRTdV^xg;j@1gn}`A-DYpVl9qcp%l)>{OC0;Gj(65y{T;X&cQ_AHuY&tX4_E#ZUsHMN=vSM8 z%X-jb2dhW2_A@B-;2%UBA%CN?<}hu{<1e{G!n6Y`0CT_tQLpErTaeDJVGb%qile#k17$7@vo z{2o^9yTFD2#F*Drxf zdks6|)bped{9QE`ru_RKq4LCjqw?5!@yWoAA9Lq0R#t|n!Falik*2sJFR#MKa<

guyN)-&D4*RZyzGw7}F7F7xLMB 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

;hP?-a_*c@MfW;siN=Mr7&&B&U!y||w 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 literal 0 HcmV?d00001 diff --git a/bin/s140_nrf52_7.3.0_softdevice.hex b/bin/s140_nrf52_7.3.0_softdevice.hex new file mode 100644 index 000000000..639927f50 --- /dev/null +++ b/bin/s140_nrf52_7.3.0_softdevice.hex @@ -0,0 +1,9726 @@ +:020000040000FA +:1000000000040020810A000015070000610A0000BA +:100010001F07000029070000330700000000000050 +:10002000000000000000000000000000A50A000021 +:100030003D070000000000004707000051070000D6 +:100040005B070000650700006F07000079070000EC +:10005000830700008D07000097070000A10700003C +:10006000AB070000B5070000BF070000C90700008C +:10007000D3070000DD070000E7070000F1070000DC +:10008000FB070000050800000F0800001908000029 +:10009000230800002D080000370800004108000078 +:1000A0004B080000550800005F08000069080000C8 +:1000B000730800007D080000870800009108000018 +:1000C0009B080000A5080000AF080000B908000068 +:1000D000C3080000CD080000D7080000E1080000B8 +:1000E000EB080000F5080000FF0800000909000007 +:1000F000130900001D090000270900003109000054 +:100100003B0900001FB500F003F88DE80F001FBD8C +:1001100000F0ACBC40F6FC7108684FF01022401CA7 +:1001200008D00868401C09D00868401C04D0086842 +:1001300000F037BA9069F5E79069F9E7704770B554 +:100140000B46010B184400F6FF70040B4FF0805073 +:100150000022090303692403406943431D1B104621 +:1001600000F048FA29462046BDE8704000F042BA47 +:10017000F0B54FF6FF734FF4B4751A466E1E11E0DA +:10018000A94201D3344600E00C46091B30F8027B3B +:10019000641E3B441A44F9D19CB204EB134394B25D +:1001A00004EB12420029EBD198B200EB134002EBB2 +:1001B000124140EA0140F0BDF34992B00446D1E952 +:1001C0000001CDE91001FF224021684600F0F4FB58 +:1001D00094E80F008DE80F00684610A902E004C8FB +:1001E00041F8042D8842FAD110216846FFF7C0FF7C +:1001F0001090AA208DF8440000F099F9FFF78AFFCB +:1002000040F6FC7420684FF01025401C0FD0206889 +:1002100010226946803000F078F92068401C08D030 +:100220002068082210A900F070F900F061F9A869AF +:10023000EEE7A869F5E74FF080500369406940F6A2 +:10024000FC71434308684FF01022401C06D0086838 +:1002500000F58050834203D2092070479069F7E788 +:100260000868401C04D00868401C03D00020704778 +:100270009069F9E70420704770B504460068C34DE3 +:10028000072876D2DFE800F033041929631E250021 +:10029000D4E9026564682946304600F062F92A46CE +:1002A0002146304600F031F9AA002146304600F0E0 +:1002B00057FB002800D0032070BD00F009FC4FF46C +:1002C000805007E0201D00F040F90028F4D100F034 +:1002D000FFFB60682860002070BD241D94E80700C3 +:1002E000920000F03DFB0028F6D00E2070BDFFF715 +:1002F000A2FF0028FAD1D4E901034FF0805100EBAE +:10030000830208694D69684382420ED840F6F8704E +:1003100005684FF010226D1C09D0056805EB8305B8 +:100320000B6949694B439D4203D9092070BD55694A +:10033000F4E70168491C03D00068401C02D003E0C8 +:100340005069FAE70F2070BD2046FFF735FFFFF731 +:1003500072FF0028F7D1201D00F0F7F80028F2D135 +:1003600060680028F0D100F0E2F8FFF7D3FE00F05B +:10037000BFF8072070BD10B50C46182802D0012028 +:10038000086010BD2068FFF777FF206010BD41684E +:10039000054609B1012700E0002740F6F8742068FF +:1003A0004FF01026401C2BD02068AA68920000F065 +:1003B000D7FA38B3A86881002068401C27D020688D +:1003C000FFF7BDFED7B12068401C22D026684FF051 +:1003D0008050AC686D68016942695143A9420DD9EA +:1003E000016940694143A14208D92146304600F0E5 +:1003F000B8F822462946304600F087F800F078F831 +:100400007069D2E700F093F8FFF784FEF6E77069B1 +:10041000D6E77669DBE740F6FC7420684FF01026DB +:10042000401C23D02068401C0CD02068401C1FD0EA +:100430002568206805F18005401C1BD027683879A5 +:10044000AA2819D040F6F8700168491C42D001680A +:10045000491C45D00168491C3ED001680968491C07 +:100460003ED00168491C39D000683EE0B069DAE747 +:10047000B569DEE7B769E2E710212846FFF778FEA5 +:100480003968814222D12068401C05D0D4F8001080 +:1004900001F18002C03107E0B169F9E730B108CA63 +:1004A00051F8040D984201D1012000E000208A4259 +:1004B000F4D158B1286810B1042803D0FEE72846CB +:1004C000FFF765FF3149686808600EE0FFF722FE1C +:1004D00000F00EF87169BBE77169BFE7706904E06D +:1004E0004FF480500168491C01D000F0CBFAFEE7C0 +:1004F000BFF34F8F26480168264A01F4E06111439B +:100500000160BFF34F8F00BFFDE72DE9F0411746B3 +:100510000D460646002406E03046296800F054F8EF +:10052000641C2D1D361DBC42F6D3BDE8F08140F69B +:10053000FC700168491C04D0D0F800004FF48051D1 +:10054000FDE54FF010208069F8E74FF080510A690F +:10055000496900684A43824201D810207047002050 +:10056000704770B50C4605464FF4806608E0284693 +:1005700000F017F8B44205D3A4F5806405F5805562 +:10058000002CF4D170BD0000F40A0000000000202F +:100590000CED00E00400FA05144801680029FCD0C5 +:1005A0007047134A0221116010490B68002BFCD0E0 +:1005B0000F4B1B1D186008680028FCD0002010603D +:1005C00008680028FCD07047094B10B501221A605A +:1005D000064A1468002CFCD0016010680028FCD08A +:1005E0000020186010680028FCD010BD00E4014015 +:1005F00004E5014070B50C46054600F073F810B9EB +:1006000000F07EF828B121462846BDE8704000F091 +:1006100007B821462846BDE8704000F037B8000012 +:100620007FB5002200920192029203920A0B000B06 +:100630006946012302440AE0440900F01F0651F80C +:10064000245003FA06F6354341F82450401C8242F8 +:10065000F2D80D490868009A10430860081D016827 +:10066000019A1143016000F03DF800280AD00649C4 +:1006700010310868029A10430860091D0868039A3F +:10068000104308607FBD00000006004030B50F4CED +:10069000002200BF04EB0213D3F800582DB9D3F8A1 +:1006A000045815B9D3F808581DB1521C082AF1D3C3 +:1006B00030BD082AFCD204EB0212C2F80008C3F8CD +:1006C00004180220C3F8080830BD000000E0014013 +:1006D0004FF08050D0F83001082801D0002070473A +:1006E000012070474FF08050D0F83011062905D016 +:1006F000D0F83001401C01D0002070470120704725 +:100700004FF08050D0F830010A2801D00020704707 +:100710000120704708208F490968095808471020B0 +:100720008C4909680958084714208A4909680958FA +:100730000847182087490968095808473020854923 +:100740000968095808473820824909680958084744 +:100750003C20804909680958084740207D490968BC +:100760000958084744207B49096809580847482028 +:1007700078490968095808474C207649096809589A +:10078000084750207349096809580847542071499F +:1007900009680958084758206E49096809580847E8 +:1007A0005C206C4909680958084760206949096854 +:1007B00009580847642067490968095808476820AC +:1007C00064490968095808476C2062490968095852 +:1007D000084770205F4909680958084774205D4937 +:1007E00009680958084778205A490968095808478C +:1007F0007C205849096809580847802055490968EC +:10080000095808478420534909680958084788202F +:1008100050490968095808478C204E490968095809 +:10082000084790204B4909680958084794204949CE +:10083000096809580847982046490968095808472F +:100840009C204449096809580847A0204149096883 +:1008500009580847A4203F49096809580847A820B3 +:100860003C49096809580847AC203A4909680958C1 +:100870000847B0203749096809580847B420354966 +:10088000096809580847B8203249096809580847D3 +:10089000BC203049096809580847C0202D4909681B +:1008A00009580847C4202B49096809580847C82037 +:1008B0002849096809580847CC2026490968095879 +:1008C0000847D0202349096809580847D4202149FE +:1008D000096809580847D8201E4909680958084777 +:1008E000DC201C49096809580847E02019490968B3 +:1008F00009580847E4201749096809580847E820BB +:100900001449096809580847EC2012490968095830 +:100910000847F0200F49096809580847F4200D4995 +:10092000096809580847F8200A490968095808471A +:10093000FC2008490968095808475FF48070054998 +:10094000096809580847000003480449024A034B54 +:100950007047000000000020000B0000000B0000AA +:1009600040EA010310B59B070FD1042A0DD310C82C +:1009700008C9121F9C42F8D020BA19BA884201D97E +:10098000012010BD4FF0FF3010BD1AB1D30703D0C6 +:10099000521C07E0002010BD10F8013B11F8014B7C +:1009A0001B1B07D110F8013B11F8014B1B1B01D198 +:1009B000921EF1D1184610BD02F0FF0343EA032254 +:1009C00042EA024200F005B87047704770474FF0A6 +:1009D00000020429C0F0128010F0030C00F01B800C +:1009E000CCF1040CBCF1020F18BF00F8012BA8BF1A +:1009F00020F8022BA1EB0C0100F00DB85FEAC17CDE +:100A000024BF00F8012B00F8012B48BF00F8012B90 +:100A100070474FF0000200B51346944696462039C1 +:100A200022BFA0E80C50A0E80C50B1F12001BFF4A7 +:100A3000F7AF090728BFA0E80C5048BF0CC05DF80D +:100A400004EB890028BF40F8042B08BF704748BF5B +:100A500020F8022B11F0804F18BF00F8012B7047CF +:100A6000014B1B68DB6818470000002009480A4951 +:100A70007047FFF7FBFFFFF745FB00BD20BFFDE719 +:100A8000064B1847064A1060016881F308884068E1 +:100A900000470000000B0000000B000017040000DE +:100AA000000000201EF0040F0CBFEFF30881EFF3ED +:100AB0000981886902380078182803D100E0000015 +:100AC000074A1047074A12682C3212681047000084 +:100AD00000B5054B1B68054A9B58984700BD0000B0 +:100AE0007703000000000020F00A0000040000006E +:100AF000001000000000000000FFFFFF0090D00386 +:10100000C8130020395E020085C100009F5D020008 +:1010100085C1000085C1000085C1000000000000FE +:10102000000000000000000000000000C55E02009B +:1010300085C100000000000085C1000085C10000DE +:101040002D5F0200335F020085C1000085C10000F2 +:1010500085C1000085C1000085C1000085C1000078 +:10106000395F020085C1000085C100003F5F0200BA +:1010700085C10000455F02004B5F0200515F020026 +:1010800085C1000085C1000085C1000085C1000048 +:1010900085C1000085C1000085C1000085C1000038 +:1010A00085C10000575F020085C1000085C10000B6 +:1010B00085C1000085C1000085C1000085C1000018 +:1010C0005D5F020085C1000085C1000085C1000090 +:1010D00085C1000085C1000085C1000085C10000F8 +:1010E00085C1000085C1000085C1000085C10000E8 +:1010F00085C1000085C1000085C1000085C10000D8 +:1011000085C1000085C1000000F002F824F083FED4 +:101110000AA090E8000C82448344AAF10107DA4552 +:1011200001D124F078FEAFF2090EBAE80F0013F0F7 +:10113000010F18BFFB1A43F00103184718530200B0 +:10114000385302000A444FF0000C10F8013B13F032 +:10115000070408BF10F8014B1D1108BF10F8015B10 +:10116000641E05D010F8016B641E01F8016BF9D103 +:1011700013F0080F1EBF10F8014BAD1C0C1B09D15A +:101180006D1E58BF01F801CBFAD505E014F8016BCC +:1011900001F8016B6D1EF9D59142D6D3704700005E +:1011A0000023002400250026103A28BF78C1FBD870 +:1011B000520728BF30C148BF0B6070471FB500F011 +:1011C00003F88DE80F001FBD24F022BE70B51A4C45 +:1011D00005460A202070A01C00F0D5F85920A080F8 +:1011E00029462046BDE8704008F082B908F08BB966 +:1011F00070B50C461149097829B1A0F160015E294A +:1012000008D3012013E0602804D0692802D043F2FB +:1012100001000CE020CC0A4E94E80E0006EB8000A2 +:10122000A0F58050241FD0F8806E2846B04720607B +:1012300070BD012070470000080000201C00002045 +:10124000A05F02003249884201D20120704700208D +:10125000704770B50446A0F500002E4EB0F1786FCF +:1012600002D23444A4F500042948844201D2012565 +:1012700000E0002500F043F848B125B9B44204D39A +:101280002548006808E0012070BD002070BD002DD9 +:10129000F9D1B442F9D321488442F6D2F3E710B52C +:1012A0000446A0F50000B0F1786F03D21948044459 +:1012B000A4F5000400F023F84FF0804130B1164847 +:1012C000006804E08C4204D2012003E01348844209 +:1012D000F8D2002080F0010010BD10B520B1FFF75A +:1012E000DEFF08B1012010BD002010BD10B520B1F7 +:1012F000FFF7AFFF08B1012010BD002010BD084866 +:1013000008490068884201D10120704700207047D9 +:1013100000700200000000202000002008000020D3 +:101320005C000020BEBAFECA10B5044600210120B0 +:1013300000F042F800210B2000F03EF800210820C8 +:1013400000F03AF80421192000F036F804210D20AD +:1013500000F032F804210E2000F02EF804210F20B6 +:1013600000F02AF80421C84300F026F806211620D0 +:1013700000F022F80621152000F01EF82046FFF7A5 +:1013800025FF002010BD40F2231101807047FFF7B8 +:101390002DBF1148704710487047104A10B51468A7 +:1013A0000E4B0F4A08331A60FFF722FF0B48001D4F +:1013B000046010BD704770474907090E002804DB20 +:1013C00000F1E02080F80014704700F00F0000F1F9 +:1013D000E02080F8141D704703F900421005024018 +:1013E00001000001FD48002101604160018170475A +:1013F0002DE9FF4F93B09B46209F160004460DD069 +:101400001046FFF726FF18B1102017B0BDE8F08F87 +:101410003146012001F0D3FE0028F6D101258DF8D8 +:1014200042504FF4C050ADF84000002210A92846A9 +:1014300006F0C5FC0028E8D18DF84250A8464FF4CC +:1014400028500025ADF840001C2229466846079523 +:101450000DF01DF89DF81C000DF11C0A20F00F0086 +:10146000401C20F0F00010308DF81C0020788DF822 +:101470001D0061789DF81E000DF1400961F34200E6 +:1014800040F001008DF81E009DF8000008AA40F011 +:1014900002008DF800002089ADF83000ADF8325020 +:1014A0006089ADF83400CDF82CA060680E900AA9D0 +:1014B000CDF82890684606F090FA0028A5D160681B +:1014C000FFF70BFF40B16068FFF710FF20B96078AD +:1014D00000F00300022801D0012000E00020BF4CF2 +:1014E00008AA0AA92072BDF8200020808DF8428049 +:1014F00042F60120ADF840009DF81E0020F00600E5 +:10150000801C20F001008DF81E000220ADF8300094 +:10151000ADF8340014A80E90684606F05EFA002874 +:1015200089D1BDF82000608036B1211D304600F021 +:101530005FF90028C2D109E0BBF1000F05D00CF023 +:1015400021FDE8BB0CF01EFDD0BBA58017B1012F1B +:1015500043D04AE08DF8428042F6A620ADF8400024 +:1015600046461C220021684607950CF090FF9DF826 +:101570001C00ADF8346020F00F00401C20F0F0009B +:1015800010308DF81C009DF81D0020F0FF008DF834 +:101590001D009DF81E0020F0060040F00100801C98 +:1015A0008DF81E009DF800008DF8446040F00200A8 +:1015B0008DF80000CDE90A9AADF8306011A800E07E +:1015C00011E00E9008AA0AA9684606F006FA00285B +:1015D000A6D1BDF82000E08008E00CF0D3FC10B9E3 +:1015E0000CF0D0FC08B103200FE7E58000200CE7E9 +:1015F0003EB50446794D0820ADF80000A88828B112 +:101600002046FFF726FE18B110203EBD06203EBD45 +:101610002146012001F0D3FD0028F8D12088ADF843 +:1016200004006088ADF80600A088ADF80800E088E6 +:10163000ADF80A00A88801AB6A46002106F0AAFDB1 +:10164000BDF800100829E2D003203EBD7FB5634DF0 +:101650000446A88868B1002002900820ADF8080070 +:10166000CDF80CD02046FFF7F4FD20B1102004B0D7 +:1016700070BD0620FBE7A98802AA4FF6FF7006F0AE +:10168000CCFF0028F3D1BDF80810082901D00320B1 +:10169000EDE7BDF800102180BDF802106180BDF8B3 +:1016A0000410A180BDF80610E180E0E701B582B02A +:1016B0000220ADF80000494802AB6A46408800218C +:1016C00006F068FDBDF80010022900D003200EBD11 +:1016D0001CB5002100910221ADF800100190FFF728 +:1016E000DEFD08B110201CBD3C486A4641884FF61B +:1016F000FF7006F092FFBDF800100229F3D003201E +:101700001CBDFEB5354C06461546207A0F46C0076F +:1017100005D00846FFF79DFD18B11020FEBD0F2033 +:10172000FEBDF82D01D90C20FEBD3046FFF791FD1E +:1017300018BB208801A905F03AFE0028F4D13078C2 +:101740008DF80500208801A906F003FD0028EBD1E3 +:1017500000909DF800009DF8051040F002008DF803 +:101760000000090703D040F008008DF80000208831 +:10177000694606F08BFC0028D6D1ADF808502088C9 +:101780003B4602AA002106F005FDBDF80810A9425B +:10179000CAD00320FEBD7CB5054600200090019014 +:1017A0000888ADF800000C4628460195FFF795FD26 +:1017B00018B92046FFF773FD08B110207CBD15B1A4 +:1017C000BDF8000060B105486A4601884FF6FF7019 +:1017D00006F023FFBDF8001021807CBD240200200C +:1017E0000C20FAE72F48C088002800D0012070475D +:1017F00030B5044693B000200D46014600901422F7 +:1018000001A80CF044FE1C22002108A80CF03FFEA9 +:101810009DF80000CDF808D020F00F00401C20F00B +:10182000F00010308DF800009DF8010006AA20F0AD +:10183000FF008DF801009DF8200001A940F0020092 +:101840008DF8200001208DF8460042F60420ADF806 +:10185000440011A801902088ADF83C006088ADF8E4 +:101860003E00A088ADF84000E088ADF842009DF849 +:10187000020020F00600801C20F001008DF802001C +:101880000820ADF80C00ADF810000FA8059008A8CE +:1018900006F0A3F8002803D1BDF818002880002026 +:1018A00013B030BD24020020F0B5007B059F1E461A +:1018B00014460D46012800D0FFDF0C2030803A206E +:1018C0003880002C08D0287A032806D0287B0128ED +:1018D00000D0FFDF17206081F0BDA889FBE72DE96C +:1018E000F0470D4686B095F80C900E991446B9F164 +:1018F000010F0BD01022007B2E8A9046052807D0BE +:10190000062839D0FFDF06B0BDE8F0870222F2E7F3 +:10191000E8890C2200EB400002EB400018803320E5 +:101920000880002CEFD0E8896081002720E0009635 +:10193000688808F1020301AA696900F097FF06EBC5 +:101940000800801C07EB470186B204EB4102BDF89A +:1019500004009081F848007808B1012300E00023DA +:101960000DF1060140460E3214F029F87F1CBFB27B +:101970006089B842DBD8C6E734200880E889B9F12D +:10198000010F11D0122148430E301880002CBAD01C +:10199000E88960814846B9F1010F00D00220207328 +:1019A00000270DF1040A1FE00621ECE70096688885 +:1019B00008F1020301AA696900F058FF06EB08006C +:1019C000801C86B2B9F1010F12D007EBC70004EBFF +:1019D0004000BDF80410C18110220AF1020110304C +:1019E0000CF02BFD7F1CBFB26089B842DED88AE7BD +:1019F00007EB470104EB4102BDF80400D0810AF176 +:101A000002014046103213F0FCFFEBE72DE9F047EE +:101A10000E4688B090F80CC096F80C80378AF5898D +:101A20000C20DFF81493109902F10C04BCF1030FA1 +:101A300008D0BCF1040F3DD0BCF1070F75D0FFDF1B +:101A400008B061E705EB850C00EB4C0018803120F5 +:101A50000880002AF4D0A8F1060000F0FF0A5581A2 +:101A600024E01622002101A80CF011FD00977088D7 +:101A7000434601AA716900F0F9FEBDF80400208018 +:101A8000BDF80600E080BDF80800208199F800004C +:101A900008B1012300E00023A21C0DF10A01504609 +:101AA00013F08DFF07EB080087B20A346D1EADB24C +:101AB000D7D2C5E705EB850C00EB4C00188032202F +:101AC0000880002ABCD0A8F1050000F0FF0A55816B +:101AD00037E000977088434601AA716900F0C6FE9E +:101AE0009DF80600BDF80410E1802179420860F3FA +:101AF000000162F34101820862F38201C20862F3CD +:101B0000C301020962F30411420962F3451182091B +:101B100062F386112171C0096071BDF80700208150 +:101B200099F8000010B1012301E00EE000232246E5 +:101B30000DF10901504613F042FF07EB080087B290 +:101B40000A346D1EADB2C4D27AE7A8F1020084B2A5 +:101B500005FB08FC0CF10E00188035200880002AD7 +:101B6000A7D05581948100971FFA8CF370880E32AC +:101B7000716900F07BFE63E72DE9F84F1E460A9D70 +:101B80000C4681462AB1607A00F58070D080E089E9 +:101B9000108199F80C000C274FF000084FF00E0A46 +:101BA0000D2872D2DFE800F09D070E1B272F374566 +:101BB000546972727200214648460095FFF774FE20 +:101BC000BDE8F88F207B9146082802D0032800D07A +:101BD000FFDF3780302009E0A9F80A80F0E7207B9A +:101BE0009146042800D0FFDF378031202880B9F1EA +:101BF000000FF1D1E4E7207B9146042800D0FFDFFD +:101C000037803220F2E7207B9146022800D0FFDFA8 +:101C100037803320EAE7207B1746022800D0FFDF19 +:101C20003420A6F800A02880002FC9D0A7F80A8089 +:101C3000C6E7207B1746042800D0FFDF3520A6F832 +:101C400000A02880002FBBD04046A7F80A8012E0F1 +:101C5000207B1746052802D0062800D0FFDF102081 +:101C6000308036202880002FAAD0E0897881A7F81C +:101C70000E80B9F80E00B881A2E7207B91460728B4 +:101C800000D0FFDF37803720B0E72AE04FF01200A6 +:101C900018804FF038001700288091D0E0897881B3 +:101CA000A7F80E80A7F8108099F80C000A2805D034 +:101CB0000B2809D00C280DD0FFDF81E7207B0A28F4 +:101CC00000D0FFDF01200AE0207B0B2800D0FFDFDF +:101CD000042004E0207B0C2800D0FFDF05203873AF +:101CE0006EE7FFDF6CE770B50C46054601F0AAFB16 +:101CF00020B10078222804D2082070BD43F20200EF +:101D000070BD0521284612F0D1F8206008B10020EE +:101D100070BD032070BD30B44880087820F00F00FB +:101D2000C01C20F0F000903001F8080B1DCA81E8BB +:101D30001D0030BC07F05DBC100000202DE9FF47FE +:101D400084B0002782460297079890468946123051 +:101D50000AF069FA401D20F00306079828B907A980 +:101D60005046FFF7C0FF002854D1B9F1000F05D04D +:101D70000798017B19BB052504681BE098F8000053 +:101D8000092803D00D2812D0FFDF46E0079903256C +:101D90004868B0B3497B42887143914239D98AB2CD +:101DA000B3B2011D11F0F5FE0446078002E0079C66 +:101DB000042508340CB1208810B1032D29D02CE063 +:101DC0000798012112300AF060FAADF80C000246C3 +:101DD00002AB2946504608F0B8FA070001D1A01C12 +:101DE000029007983A461230C8F80400A8F802A0FA +:101DF00003A94046029B0AF055FAD8B10A2817D227 +:101E000000E006E0DFE800F007091414100B0D14E1 +:101E10001412132014E6002012E6112010E6082008 +:101E20000EE643F203000BE6072009E60D2007E665 +:101E3000032005E6BDF80C002346CDE900702A46D4 +:101E40005046079900F022FD57B9032D08D1079895 +:101E5000B3B2417B406871438AB2011D11F0ADFEFF +:101E6000B9F1000FD7D0079981F80C90D3E72DE98D +:101E7000FE4F91461A881C468A468046FAB102AB4C +:101E8000494608F062FA050019D04046A61C27888A +:101E900012F04FF93246072629463B46009611F0CC +:101EA0005EFD20882346CDE900504A465146404613 +:101EB00000F0ECFC002020800120BDE8FE8F002017 +:101EC000FBE710B586B01C46AAB104238DF800309C +:101ED0001388ADF808305288ADF80A208A788DF85A +:101EE0000E200988ADF80C1000236A462146FFF742 +:101EF00025FF06B010BD1020FBE770B50D4605218B +:101F000011F0D4FF040000D1FFDF294604F11200D4 +:101F1000BDE870400AF0A2B92DE9F8430D468046AD +:101F2000002607F063FB04462878102878D2DFE803 +:101F300000F0773B345331311231313108313131D6 +:101F400031312879001FC0B2022801D0102810D1E9 +:101F500014BBFFDF35E004B9FFDF0521404611F077 +:101F6000A5FF007B032806D004280BD0072828D023 +:101F7000FFDF072655E02879801FC0B2022820D055 +:101F800050B1F6E72879401FC0B2022819D01028B6 +:101F900017D0EEE704B9FFDF13E004B9FFDF2879BB +:101FA00001280ED1172137E00521404611F07EFFB0 +:101FB000070000D1FFDF07F1120140460AF02BF9BC +:101FC0002CB12A4621464046FFF7A5FE29E0132101 +:101FD000404602F01FFD24E004B9FFDF0521404622 +:101FE00011F064FF060000D1FFDF694606F1120020 +:101FF0000AF01BF9060000D0FFDFA988172901D2DB +:10200000172200E00A46BDF80000824202D90146CC +:1020100002E005E01729C5D3404600F047FCD0E7B1 +:10202000FFDF3046BDE8F883401D20F0030219B100 +:1020300002FB01F0001D00E000201044704713B5C2 +:10204000009858B10024684611F04DFD002C04D1D1 +:10205000F749009A4A6000220A701CBD0124002042 +:10206000F2E72DE9F0470C461546242200212046D0 +:102070000CF00DFA05B9FFDFA87860732888DFF847 +:10208000B0A3401D20F00301AF788946DAF80400C0 +:1020900011F047FD060000D1FFDF4FF00008266079 +:1020A000A6F8008077B109FB07F1091D0AD0DAF81C +:1020B000040011F036FD060000D1FFDF6660C6F8AF +:1020C000008001E0C4F80480298804F11200BDE812 +:1020D000F0470AF091B82DE9F047804601F112006F +:1020E0000D4681460AF09FF8401DD14F20F00302B3 +:1020F0006E7B14462968786811F03EFD3EB104FB02 +:1021000006F2121D03D06968786811F035FD0520CC +:1021100011F074FE0446052011F078FE201A012803 +:1021200002D1786811F0F2FC49464046BDE8F0471C +:102130000AF078B870B50546052111F0B7FE040025 +:1021400000D1FFDF04F112012846BDE870400AF01B +:1021500062B82DE9F04F91B04FF0000BADF828B008 +:10216000ADF804B047880C4605469246052138462E +:1021700011F09CFE060000D1FFDF24B1A780A4F877 +:1021800006B0A4F808B0297809220B20B2EB111F81 +:1021900073D12A7A04F1100138274FF00C084FF060 +:1021A00012090291102A69D2DFE802F068F2F1F018 +:1021B0008008D3898EA03DDCF3EEB7B7307B0228D0 +:1021C00000D0FFDFA88908EBC001ADF80410302172 +:1021D000ADF82810002C25D06081B5F80E800027BE +:1021E0001DE004EBC709317C89F80E10F189A9F8CC +:1021F0000C10CDF800806888042305AA296900F036 +:1022000035FBBDF81410A9F8101008F10400BDF852 +:1022100016107F1C1FFA80F8A9F81210BFB260894F +:10222000B842DED80CE1307B022800D0FFDFE9891C +:1022300008EBC100ADF804003020ADF8280095F897 +:102240000C90002CA9F10400C0B20F90EAD061817B +:10225000B5F81080002725E0CDF8008068884B464F +:1022600003AA696900F002FB08EB09001FFA80F875 +:102270006F48007818B1012302E0DDE0DAE00023C6 +:1022800004EBC702009204A90C320F9813F097FBDD +:10229000009ABDF80C007F1C1082009ABDF80E0059 +:1022A000BFB250826089B842D6D8C9E00AA800906F +:1022B00001AB224629463046FFF711FBC0E0307BD8 +:1022C000082805D0FFDF03E0307B082800D0FFDFBF +:1022D000E8891030ADF804003620ADF82800002C55 +:1022E0003FD0A9896181F189A18127E0307B09284C +:1022F00000D0FFDFA88901460C30ADF8040037207C +:10230000ADF82800002C2CD06181E8890090AB89C1 +:10231000688804F10C02296955E0E88939211030F8 +:1023200080B2ADF80400ADF82810002C72D0A98955 +:102330006181287A0E280AD002212173E989E1817E +:10234000288A0090EB8968886969029A3BE001213C +:10235000F3E70AA8009001AB224629463046FFF772 +:1023600055FB6DE0307B0A2800D0FFDFADF804900C +:10237000ADF828704CB3A9896181A4F810B0A4F815 +:102380000EB0012020735BE020E002E030E038E096 +:1023900041E0307B0B2800D0FFDF288AADF82870A1 +:1023A0001230ADF8040084B104212173A989618140 +:1023B000E989E181298A2182688A00902B8A6888CC +:1023C00004F11202696900F051FA39E0307B0C28FF +:1023D00000D0FFDFADF80490ADF828703CB30521C4 +:1023E0002173A4F80AB0A4F80EB0A4F810B027E046 +:1023F0000AA8009001AB224629463046FFF754FA5E +:102400001EE00AA8009001AB224629463046FFF79D +:10241000B3FB15E034E03B21ADF80400ADF8281023 +:1024200074B30120E080A4F808B084F80AB007E093 +:1024300010000020FFDF03E0297A012917D0FFDF19 +:10244000BDF80400AAF800006CB1BDF82800208097 +:10245000BDF804006080BDF82800392803D03C286E +:1024600001D086F80CB011B00020BDE8F08F3C21FF +:10247000ADF80400ADF8281014B1697AA172DFE755 +:10248000AAF80000EFE72DE9F84356880F4680468A +:1024900015460521304611F009FD040000D1FFDF8B +:1024A000123400943B46414630466A680AF02EF8E2 +:1024B000B8E570B50D46052111F0F8FC040000D117 +:1024C000FFDF294604F11200BDE8704009F0B8BEF4 +:1024D00070B50D46052111F0E9FC040000D1FFDFC5 +:1024E000294604F11200BDE8704009F0D6BE70B56F +:1024F0000546052111F0DAFC040000D1FFDF04F1EC +:10250000080321462846BDE870400422AFE470B5B8 +:102510000546052111F0CAFC040000D1FFDF214669 +:1025200028462368BDE870400522A0E470B5064641 +:10253000052111F0BBFC040000D1FFDF04F1120003 +:1025400009F071FE401D20F0030511E0011D008817 +:102550000322431821463046FFF789FC00280BD0A0 +:10256000607BABB2684382B26068011D11F05BFB17 +:10257000606841880029E9D170BD70B50E460546F6 +:1025800007F034F8040000D1FFDF012020726672EA +:102590006580207820F00F00C01C20F0F000303063 +:1025A0002070BDE8704007F024B8602801D00720F3 +:1025B00070470878C54900F0010008700020704796 +:1025C0002DE9F0438BB00D461446814606A9FFF76E +:1025D0008AFB002814D14FF6FF7601274FF42058CC +:1025E0008CB103208DF800001020ADF8100007A872 +:1025F000059007AA204604A913F005FA78B1072030 +:102600000BB0BDE8F0830820ADF808508DF80E70CF +:102610008DF80000ADF80A60ADF80C800CE006986B +:10262000A17801742188C1818DF80E70ADF8085031 +:10263000ADF80C80ADF80A606A4602214846069B58 +:10264000FFF77CFBDCE708B501228DF8022042F69B +:102650000202ADF800200A4603236946FFF731FC69 +:1026600008BD08B501228DF8022042F60302ADF83C +:1026700000200A4604236946FFF723FC08BD00B585 +:1026800087B079B102228DF800200A88ADF80820C1 +:102690004988ADF80A1000236A460521FFF74EFB72 +:1026A00007B000BD1020FBE709B1072309E40720AC +:1026B000704770B588B00D461446064606A9FFF768 +:1026C00012FB00280ED17CB10620ADF808508DF821 +:1026D0000000ADF80A40069B6A460821DC813046BE +:1026E000FFF72CFB08B070BD05208DF80000ADF899 +:1026F0000850F0E700B587B059B107238DF80030D6 +:10270000ADF80820039100236A460921FFF716FB64 +:10271000C6E71020C4E770B588B00C460646002511 +:1027200006A9FFF7E0FA0028DCD106980121123053 +:1027300009F0ABFD9CB12178062921D2DFE801F038 +:10274000200505160318801E80B2C01EE28880B2E4 +:102750000AB1A3681BB1824203D90C20C2E7102042 +:10276000C0E7042904D0A08850B901E00620B9E7E9 +:10277000012913D0022905D004291CD005292AD00B +:102780000720AFE709208DF800006088ADF8080049 +:10279000E088ADF80A00A068039023E00A208DF8D5 +:1027A00000006088ADF80800E088ADF80A00A06875 +:1027B0000A25039016E00B208DF800006088ADF824 +:1027C0000800A088ADF80A00E088ADF80C00A06809 +:1027D0000B25049006E00C208DF8000060788DF841 +:1027E00008000C256A4629463046069BFFF7A6FAE4 +:1027F00078E700B587B00D228DF80020ADF80810FD +:1028000000236A461946FFF799FA49E700B587B0F1 +:1028100071B102228DF800200A88ADF8082049889D +:10282000ADF80A1000236A460621FFF787FA37E75A +:10283000102035E770B586B0064601200D46ADF88C +:1028400008108DF80000014600236A463046FFF765 +:1028500075FA040008D12946304605F0B5FC002180 +:10286000304605F0CFFC204606B070BDF8B51C46DA +:1028700015460E46069F11F04AFC2346FF1DBCB2CA +:1028800031462A46009411F036F8F8BD30B41146AE +:10289000DDE902423CB1032903D0002330BC08F03B +:1028A00032BE0123FAE71A8030BC704770B50C467F +:1028B0000546FFF722FB2146284605F094FC2846F2 +:1028C000BDE87040012105F09DBC00001000002013 +:1028D0004FF0E0224FF400400021C2F88001BFF326 +:1028E0004F8FBFF36F8F1748016001601649900248 +:1028F00008607047134900B500220A600A60124B55 +:102900004FF060721A60002808BF00BD0F4A104BDC +:10291000DFF840C001280CD002281CBFFFDF00BD3B +:10292000032008601A604FF4000000BFCCF80000DC +:1029300000BD022008601A604FF04070F6E700B555 +:10294000FFDF00BD00F5004008F50140A4020020B3 +:1029500014F5004004F5014070B50B2000F0BDF9FE +:10296000082000F0BAF900210B2000F0D4F9002172 +:10297000082000F0D0F9F44C01256560A560002026 +:10298000C4F84001C4F84401C4F848010B2000F029 +:10299000B5F9082000F0B2F90B2000F091F925609C +:1029A00070BD10B50B2000F098F9082000F095F9E3 +:1029B000E548012141608160E4490A68002AFCD1B0 +:1029C0000021C0F84011C0F84411C0F848110B2094 +:1029D00000F094F9BDE81040082000F08FB910B560 +:1029E0000B2000F08BF9BDE81040082000F086B9FC +:1029F00000B530B1012806D0022806D0FFDF002044 +:102A000000BDD34800BDD34800BDD248001D00BD65 +:102A100070B5D1494FF000400860D04DC00BC5F8EB +:102A20000803CF4800240460C5F840410820C4359D +:102A300000F053F9C5F83C41CA48047070BD08B5B0 +:102A4000C14A002128B1012811D002281CD0FFDF83 +:102A500008BD4FF48030C2F80803C2F84803BB48F1 +:102A60003C300160C2F84011BDE80840D0E74FF4A7 +:102A70000030C2F80803C2F84803B448403001608F +:102A8000C2F84411B3480CE04FF48020C2F80803A8 +:102A9000C2F84803AD4844300160C2F84811AD485F +:102AA000001D0068009008BD70B516460D4604462E +:102AB000022800D9FFDF0022A348012304F11001FE +:102AC0008B4000EB8401C1F8405526B1C1F840218C +:102AD000C0F8043303E0C0F80833C1F84021C0F85F +:102AE000443370BD2DE9F0411D46144630B1012834 +:102AF00033D0022838D0FFDFBDE8F081891E0022E4 +:102B000021F07F411046FFF7CFFF012D23D0002099 +:102B1000944D924F012668703E61914900203C39E6 +:102B200008600220091D08608D49042030390860C2 +:102B30008B483D34046008206C6000F0DFF83004FE +:102B4000C7F80403082000F0BBF88349F007091F09 +:102B500008602E70D0E70120DAE7012B02D00022B6 +:102B6000012005E00122FBE7012B04D00022022016 +:102B7000BDE8F04198E70122F9E774480068704722 +:102B800070B500F0D8F8704C0546D4F84001002626 +:102B9000012809D1D4F80803C00305D54FF48030CB +:102BA000C4F80803C4F84061D4F8440101280CD1EA +:102BB000D4F80803800308D54FF40030C4F80803A4 +:102BC000C4F84461012013F0EEFED4F84801012856 +:102BD0000CD1D4F80803400308D54FF48020C4F882 +:102BE0000803C4F84861022013F0DDFE5E4805606A +:102BF00070BD70B500F09FF85A4D0446287850B16A +:102C0000FFF706FF687818B10020687013F0CBFE5C +:102C10005548046070BD0320F8E74FF0E0214FF401 +:102C20000010C1F800027047152000F067B84B494A +:102C300001200861082000F061B848494FF47C1079 +:102C4000C1F808030020024601EB8003C3F84025C9 +:102C5000C3F84021401CC0B20628F5D37047410A92 +:102C600043F609525143C0F3080010FB02F000F58F +:102C7000807001EB5020704710B5430B48F2376469 +:102C800063431B0C5C020C60384C03FB0400384BA4 +:102C90004CF2F72443435B0D13FB04F404EB402098 +:102CA00000F580704012107008681844086010BD6C +:102CB0002C484068704729490120C1F8000270473C +:102CC000002809DB00F01F0201219140400980002B +:102CD00000F1E020C0F80011704700280DDB00F083 +:102CE0001F02012191404009800000F1E020C0F85E +:102CF0008011BFF34F8FBFF36F8F7047002809DB40 +:102D000000F01F02012191404009800000F1E02005 +:102D1000C0F8801270474907090E002804DB00F153 +:102D2000E02080F80014704700F00F0000F1E02070 +:102D300080F8141D70470C48001F00680A4A0D49AE +:102D4000121D11607047000000B0004004B5004043 +:102D50004081004044B1004008F50140008000403F +:102D6000408500403C00002014050240F7C2FFFFF0 +:102D70006F0C0100010000010A4810B50468094900 +:102D800009480831086013F0A2FE0648001D0460DF +:102D900010BD0649002008604FF0E0210220C1F874 +:102DA000800270471005024001000001FC1F004036 +:102DB000374901200860704770B50D2000F049F8D0 +:102DC000344C0020C4F800010125C4F804530D2040 +:102DD00000F050F825604FF0E0216014C1F80001C8 +:102DE00070BD10B50D2000F034F82A480121416073 +:102DF0000021C0F80011BDE810400D2000F03AB8E5 +:102E0000254810B504682449244808310860214940 +:102E1000D1F80001012804D0FFDF1F48001D046025 +:102E200010BD1B48001D00680022C0B2C1F800217F +:102E300014F07FFBF1E710B5164800BFD0F8001181 +:102E40000029FBD0FFF7DCFFBDE810400D2000F0AB +:102E500011B800280DDB00F01F020121914040094C +:102E6000800000F1E020C0F88011BFF34F8FBFF366 +:102E70006F8F7047002809DB00F01F02012191408D +:102E80004009800000F1E020C0F880127047000087 +:102E900004D5004000D000401005024001000001B0 +:102EA0004FF0E0214FF00070C1F8800101F5C071D2 +:102EB000BFF34F8FBFF36F8FC1F80001394B8022F2 +:102EC00083F8002441F8800C704700B502460420C6 +:102ED000354903E001EBC0031B792BB1401EC0B2A2 +:102EE000F8D2FFDFFF2000BD41F8302001EBC00128 +:102EF00000224A718A7101220A7100BD2A4A00210A +:102F000002EBC0000171704710B50446042800D3DD +:102F1000FFDF254800EBC4042079012800D0FFDF43 +:102F20006079A179401CC0B2814200D060714FF03D +:102F3000E0214FF00070C1F8000210BD70B504250B +:102F4000194E1A4C16E0217806EBC1000279012ACD +:102F500008D1427983799A4204D04279827156F835 +:102F6000310080472078401CC0B22070042801D373 +:102F7000002020706D1EEDB2E5D270BD0C4810B57A +:102F800004680B490B4808310860064890F80004B3 +:102F90004009042800D0FFDFFFF7D0FF0448001DE0 +:102FA000046010BD19E000E0E0050020580000209A +:102FB00010050240010000010548064A01689142DF +:102FC00001D1002101600449012008607047000020 +:102FD0005C000020BEBAFECA40E5014070B50C4658 +:102FE000054609F02FFC21462846BDE870400AF04E +:102FF00010BD7047704770470021016081807047A5 +:103000002CFFFFFFDBE5B151007002002301FFFF41 +:103010008C00000078DB6A007A2E9AC67DB66CFAC6 +:10302000F35721CCC310D5E51471FB3C30B5FC4DF2 +:103030000446062CA9780ED2DFE804F0030E0E0E2B +:103040000509FFDF08E0022906D0FFDF04E00329BD +:1030500002D0FFDF00E0FFDFAC7030BD30B50446CA +:103060001038EF4D07280CD2DFE800F0040C060CF6 +:103070000C0C0C00FFDF05E0287E112802D0FFDFDA +:1030800000E0FFDF2C7630BD2DE9F04112F026FE86 +:10309000044614F063F8201AC5B2062010F0AEFE04 +:1030A0000446062010F0B2FE211ADD4C207E1228C4 +:1030B00018D000200F18072010F0A0FE06460720A9 +:1030C00010F0A4FE301A3918207E13280CD00020EE +:1030D0000144A078042809D000200844281AC0B26E +:1030E000BDE8F0810120E5E70120F1E70120F4E7E8 +:1030F000CB4810B590F825004108C94800F12600DA +:1031000005D00EF0F5FEBDE8104006F08CB80EF0CC +:10311000D0FEF8E730B50446A1F120000D460A289C +:103120004AD2DFE800F005070C1C2328353A3F445B +:10313000FFDF42E0207820283FD1FFDF3DE0B848A4 +:103140008178052939D0007E122836D020782428AD +:1031500033D0252831D023282FD0FFDF2DE0207851 +:1031600022282AD0232828D8FFDF26E0207822280A +:1031700023D0FFDF21E0207822281ED024281CD075 +:1031800026281AD0272818D0292816D0FFDF14E0C7 +:103190002078252811D0FFDF0FE0207825280CD0DB +:1031A000FFDF0AE02078252807D0FFDF05E0207840 +:1031B000282802D0FFDF00E0FFDF257030BD1FB5FB +:1031C00004466A46002001F0A5FEB4B1BDF8022015 +:1031D0004FF6FF700621824201D1ADF80210BDF812 +:1031E0000420824201D1ADF80410BDF808108142DC +:1031F00003D14FF44860ADF8080068460FF0E2FADA +:1032000006F011F804B010BD70B516460C46054620 +:10321000FEF71FF848B90CB1B44208D90C2070BDB4 +:1032200055F82400FEF715F808B1102070BD2046AF +:10323000641EE4B2F4D270BD2DE9F04105461F468C +:1032400090460E4600240068FEF750F830B9A98871 +:1032500028680844401EFEF749F808B110203FE7EF +:1032600028680028A88802D0B84202D850E0002878 +:10327000F5D0092034E72968085DB8B1671CCA5D3C +:10328000152A2ED03CDC152A3AD2DFE802F039129A +:10329000222228282A2A313139393939393939391C +:1032A00039392200085D30BB641CA4B2A242F9D8AF +:1032B00033E00228DDD1A01C085C88F80000072854 +:1032C00001D2400701D40A200AE7307840F001001B +:1032D00015E0C143C90707E0012807D010E0062028 +:1032E000FEE60107A1F180510029F5D01846F7E666 +:1032F0003078810701D50B20F2E640F002003070F3 +:103300002868005D384484B2A888A04202D2B0E7A1 +:103310004FF4485382B2A242ADD80020E0E610B587 +:10332000027843F2022354080122022C12D003DC5B +:103330003CB1012C16D106E0032C10D07F2C11D10A +:1033400012E0002011E080790324B4EB901F09D132 +:103350000A700BE08079B2EB901F03D1F8E7807917 +:103360008009F5D0184610BDFF200870002010BD60 +:1033700008B500208DF80000294890F82E1051B1B2 +:1033800090F82F0002280FD003280FD0FFDF00BFD6 +:103390009DF8000008BD22486946253001F009FE6D +:1033A0000028F5D0FFDFF3E7032000E001208DF8CF +:1033B0000000EDE738B50C460546694601F0F9FD19 +:1033C00000280DD19DF80010207861F3470020708F +:1033D00055F8010FC4F80100A888A4F805000020E2 +:1033E00038BD38B5137888B102280FD0FF281BD01C +:1033F0000CA46D46246800944C7905EB9414247851 +:1034000064F347031370032805D010E023F0FE0394 +:1034100013700228F7D1D8B240F001000AE0000092 +:10342000F00100200302FF0143F0FE00107010784D +:1034300020F0010010700868C2F801008888A2F826 +:10344000050038BD022110F031BD38B50C460978B1 +:10345000222901D2082038BDADF800008DF80220E5 +:1034600068460EF087FD05F0DEFE050003D1212140 +:103470002046FFF74FFE284638BD1CB500208DF8CA +:103480000000CDF80100ADF80500FB4890F82E00D3 +:10349000022801D0012000E000208DF807006846D6 +:1034A0000EF0F0FD002800D0FFDF1CBD00220A80D6 +:1034B000437892B263F3451222F040020A8000780A +:1034C0000C282BD2DFE800F02A06090E1116191C71 +:1034D0001F220C2742F0110009E042F01D00088075 +:1034E0000020704742F0110012E042F0100040F05E +:1034F0000200F4E742F01000F1E742F00100EEE7CD +:1035000042F0010004E042F00200E8E742F002006D +:1035100040F00400E3E742F00400E0E707207047D2 +:103520002DE9FF478AB00025BDF82C6082461C4675 +:1035300091468DF81C50700703D56068FDF789FE31 +:1035400068B9CD4F4FF0010897F82E0058B197F8A1 +:103550002F00022807D16068FDF7C8FE18B11020BF +:103560000EB0BDE8F087300702D5A08980283DD88D +:10357000700705D4B9F1000F02D097F8240098B372 +:10358000E07DC0F300108DF81B00627D0720032151 +:103590005AB3012A2CD0022AE2D0042AE0D18DF8B5 +:1035A0001710F00627D4A27D072022B3012A22D0CB +:1035B000022A23D0042AD3D18DF819108DF8159042 +:1035C000606810B307A9FFF7AAFE0028C8D19DF8CC +:1035D0001C00FF2816D0606850F8011FCDF80F10AE +:1035E0008088ADF8130014E000E001E00720B7E7A1 +:1035F0008DF81780D5E78DF81980DFE702208DF868 +:103600001900DBE743F20220AAE7CDF80F50ADF82E +:103610001350E07B40B9207C30B9607C20B9A07C9D +:1036200010B9E07CC00601D0062099E78DF800A013 +:10363000BDF82C00ADF80200A0680190A0680290CF +:1036400004F10F0001F0A9FC8DF80C00FFF790FECB +:103650008DF80D009DF81C008DF80E008DF81650A9 +:103660008DF81850E07D08A900F00F008DF81A00C1 +:1036700068460FF0E3F905F0D6FD71E7F0B58FB0BD +:1036800000258DF830508DF814508DF834500646D2 +:103690008DF82850019502950395049519B10FC92D +:1036A00001AC84E80F00744CA078052801D00428F0 +:1036B0000CD101986168884200D120B90398E16873 +:1036C000884203D110B108200FB0F0BD207DC006A4 +:1036D00001D51F2700E0FF273B460DAA05A903A837 +:1036E000FFF7AAFD0028EFD1A08AC10702D0C006CB +:1036F00000D4EE273B460AAA0CA901A8FFF79CFDBF +:103700000028E1D19DF81400C00701D00A20DBE7B2 +:10371000A08A410708D4A17D31B19DF828108907FE +:1037200002D043F20120CFE79DF82810C90709D045 +:10373000400707D4208818B144F25061884201D96B +:103740000720C1E78DF818508DF81960BDF8080002 +:10375000ADF81A000198079006A80FF07BF905F064 +:1037600062FD0028B0D18DF820508DF82160BDF8A1 +:103770001000ADF822000398099008A80FF08CF90A +:1037800005F051FD00289FD101AD241D95E80F00E3 +:1037900084E80F00002097E770B586B00D4604005E +:1037A00005D0FDF7A3FD20B1102006B070BD0820A4 +:1037B000FBE72078C107A98802D0FF2902D303E0E4 +:1037C0001F2901D20920F0E7800763D4FFF75CFCD2 +:1037D00038B12178C1F3C100012804D0032802D0F8 +:1037E00005E01320E1E7244890F82400C8B1C80799 +:1037F0004FF001064FF0000502D08DF80F6001E098 +:103800008DF80F50FFF7B4FD8DF800002078694661 +:10381000C0F3C1008DF8010060788DF80250C20835 +:1038200001D00720C1E730B3C20701D08DF8026094 +:10383000820705D59DF8022042F002028DF8022091 +:10384000400705D59DF8020040F004008DF8020005 +:10385000002022780B18C2F38002DA7001EB4002DC +:103860006388D380401CA388C0B253810228F0D360 +:10387000207A78B905E001E0F00100208DF80260BF +:10388000E6E7607A30B9A07A20B9E07A10B9207BF7 +:10389000C00601D0062088E704F1080001F07DFB96 +:1038A0008DF80E0068460EF0F6FC05F0BCFC002812 +:1038B00089D18DF810608DF81150E088ADF81200B4 +:1038C000ADF8145004A80EF039FD05F0ACFC00284A +:1038D00088D12078C00701D0152000E01320FFF721 +:1038E000BDFB002061E72DE9FF470220FD4E8DF86A +:1038F00004000027708EADF80600B84643F20209B6 +:103900004CE001A810F039FA050006D0708EA8B37B +:10391000A6F83280ADF806803EE0039CA07F010748 +:103920002DD504F124000090A28EBDF80800214698 +:1039300004F1360301F0BCFC050005D04D452AD04A +:10394000112D3CD0FFDF3AE0A07F20F00801E07F9E +:10395000420862F3C711A177810861F30000E077A4 +:1039600094F8210000F01F0084F820002078282817 +:1039700026D129212046FFF7CDFB21E014E04007A6 +:103980000AD5BDF8080004F10E0101F01CFB05008A +:103990000DD04D4510D100257F1CFFB2022010F044 +:1039A0002DFA401CB842ACD8052D11D008E0A07FFC +:1039B00020F00400A07703E0112D00D0FFDF0025E8 +:1039C000BDF806007086052D04D0284604B0C8E571 +:1039D000A6F832800020F9E770B50646FFF732FD01 +:1039E000054605F003FE040000D1FFDF6680207865 +:1039F00020F00F00801C20F0F00020302070032009 +:103A0000207295F83E006072BDE8704005F0F1BD8F +:103A10002DE9F04786B0040000D1FFDF2078B14DDA +:103A200020F00F00801C20F0F000703020706068E3 +:103A30000178491F1B2933D2DFE801F0FE32323210 +:103A400055FD320EFDFD42FC32323278FCFCFBFAB1 +:103A500032FCFCF9F8FCFC00C6883046FFF7F2FCAB +:103A60000546304607F045FCE0B16068007A85F80D +:103A70003E0021212846FFF74DFB3046FEF75AFB5A +:103A8000304603F0D7FE3146012014F017F8A87F26 +:103A900020F01000A877FFF726FF002800D0FFDFF6 +:103AA00006B05EE5207820F0F00020302070032082 +:103AB000207266806068007A607205F09AFDD8E72F +:103AC000C5882846FFF7BEFC00B9FFDF60680079B3 +:103AD000012800D0FFDF6068017A06B02846BDE803 +:103AE000F04707F0EBBDC6883046FFF7ABFC05009A +:103AF00000D1FFDF05F07DFD606831460089288137 +:103B000060684089688160688089A881012013F01D +:103B1000D5FF0020A875A87F00F003000228BFD1C0 +:103B2000FFF7E1FE0028BBD0FFDFB9E7007928B13D +:103B30000228B5D03C28B3D0FFDFB1E705F059FD2E +:103B40006668B6F806A0307A361D012806D0687E71 +:103B5000814605F0D4FA070003D101E0E878F7E7E1 +:103B6000FFDF00220221504610F097F9040000D137 +:103B7000FFDF22212046FFF7CDFA3079012800D05F +:103B80000220A17F804668F30101A177308B20815C +:103B9000708B6081B08BA08184F822908DF80880B2 +:103BA000B8680090F86801906A460321504610F00A +:103BB00074F900B9FFDFB888ADF81000B8788DF857 +:103BC000120004AA0521504610F067F900B9FFDF82 +:103BD000B888ADF80C00F8788DF80E0003AA04211F +:103BE000504610F05AF900B9FFDF062106F1120025 +:103BF0000DF00EF940B37079800700D5FFDF7179C1 +:103C0000E07D61F34700E075D6F80600A061708999 +:103C1000A083062106F10C000DF0FAF8F0B195F83A +:103C200025004108607861F3470006E041E039E093 +:103C300071E059E04EE02FE043E06070D5F82600D7 +:103C4000C4F80200688D12E0E07D20F0FE00801CC8 +:103C5000E075D6F81200A061F08AD9E7607820F00C +:103C6000FE00801C6070F068C4F80200308AE080BA +:103C7000B8F1010F04D0B8F1020F05D0FFDF0FE754 +:103C80000320FFF7D3F90BE7287E122800D0FFDFCF +:103C90001120FFF7E3F903E706B02046BDE8F0473F +:103CA00001F092BD05F0A5FC15F8300F40F00200C0 +:103CB00005E005F09EFC15F8300F40F00400287078 +:103CC000EEE6287E13280AD01528D8D15FF016001A +:103CD000FFF7C4F906B0BDE8F04705F08ABC142030 +:103CE000F6E70000F0010020A978052909D0042991 +:103CF000C5D105F07EFC022006B0BDE8F047FFF715 +:103D000095B900790028BAD0E87801F02DF905F0CE +:103D100070FC0320F0E7287E122802D1687E01F0B3 +:103D200023F91120D4E72DE9F05F054600784FF024 +:103D300000080009DFF8B8A891460C46464601285D +:103D40006ED002286DD007280BD00A286AD0FFDF7A +:103D5000A9F8006014B1A4F8008066800020BDE8D6 +:103D6000F09F6968012704F108000B784FF0020BFF +:103D70005B1F4FF6FF721B2B7ED2DFE803F0647DE2 +:103D80007D7D0E7D7D7D7D7D7D217D7D7D2BFDFC81 +:103D9000FBFA7D14D2F9E7F8F700C8884FF0120853 +:103DA000102621469AE14FF01C080A26BCB38888E9 +:103DB000A0806868807920726868C0796072C7E7FF +:103DC0004FF01B08142654B30320207268688088C3 +:103DD000A080BDE70A793C2ABAD00D1D4FF010082B +:103DE0002C26E4B16988A180298B6182298B2182EC +:103DF000698BA182A98BE1826B790246A91D1846C5 +:103E0000FFF7EFFA2979002001290CD084F80FB0D0 +:103E1000FF212176E06120626062A06298E70FE0F6 +:103E20003BE15EE199E1E77320760AF1040090E856 +:103E30000E00DAF81000C4E90930C4E9071287E778 +:103E4000A9F800608AE72C264FF01D08002CF7D057 +:103E5000A28005460F1D897B008861F30000288041 +:103E6000B97A490861F341002880B97A890861F379 +:103E700082002880B97A00E00CE1C90861F3C30030 +:103E80002880B97AAA1C0911491C61F3041000F0BA +:103E90007F0028807878B91CFFF7A3FA387D05F1F8 +:103EA000090207F11501FFF79CFA387B01F0A9F828 +:103EB0002874787B01F0A5F86874F87EA874787A85 +:103EC000E874387F2875B87B6875388AE882DAF834 +:103ED0001C10A961B97A504697F808A0C1F34111A6 +:103EE000012904D0008C504503D2824609E0FFDF4F +:103EF00010E0022903D0288820F0600009E0504536 +:103F000004D1288820F06000403002E0288840F08A +:103F100060002880A4F824A0524607F11D01A8697A +:103F20009BE011264FF02008002C89D0A280686801 +:103F300004F10A02007920726868007B6072696887 +:103F40008B1D48791946FFF74CFA01E70A264FF016 +:103F50002108002CE9D08888A080686880792072C8 +:103F60006868C07960729AF8301006E078E06BE01B +:103F700052E07FE019E003E03AE021F00401A6E01E +:103F80000B264FF02208002CCFD0C888A08068688C +:103F9000007920726868007A01F033F8607268680E +:103FA000407A01F02EF8A072D2E61C264FF02608C7 +:103FB000002CBAD0A2806868407960726868007A84 +:103FC000A0720AF1040090E80E00DAF81000C4E9CB +:103FD0000530C4E90312686800793C2803D04328FF +:103FE00003D0FFDFB4E62772B2E684F808B0AFE68C +:103FF00010264FF02408002C97D08888A08068688D +:10400000807920816868807A608168680089A081F1 +:1040100068688089E0819BE610264FF02308002C19 +:1040200098D08888A0806868C088208168680089E6 +:10403000608168684089A08168688089E0819AF819 +:10404000301021F0020142E030264FF02508002C0C +:104050009AD0A2806968282249680AF0EEF977E6CA +:104060002A264FF02F08002C8ED0A28069682222C9 +:10407000091DF2E714264FF01B08002C84D0A28003 +:10408000686800790128B0D02772DAE90710C4E91E +:1040900003105DE64A46214660E0287A012803D0F5 +:1040A000022817D0FFDF53E610264FF01F08002C20 +:1040B000A2D06888A080A8892081E8896081288AA8 +:1040C000A081688AE0819AF8301021F001018AF815 +:1040D00030103DE64FF012081026688800F07EFF91 +:1040E00036E6287AC8B3012838D0022836D003280B +:1040F00001D0FFDF2CE609264FF01108002C8FD0ED +:104100006F883846FFF79EF990F822A0A780687A5A +:104110002072042138460FF0DBFE052138460FF0EF +:10412000D7FE002138460FF0D3FE012138460FF0AC +:10413000CFFE032138460FF0CBFE022138460FF0A8 +:10414000C7FE062138460FF0C3FE072138460FF0A0 +:10415000BFFE504600F008FFFAE5FFE72846BDE83D +:10416000F05F01F0BBBC70B5012803D0052800D07A +:10417000FFDF70BD8DB22846FFF764F9040000D15F +:10418000FFDF20782128F4D005F030FA80B10178E3 +:1041900021F00F01891C21F0F00110310170022182 +:1041A000017245800020A075BDE8704005F021BA7D +:1041B00021462846BDE870401322FFF746B92DE995 +:1041C000F04116460C00804600D1FFDF307820F029 +:1041D0000F00801C20F0F000103030702078012893 +:1041E00004D0022818D0FFDFBDE8F0814046FFF779 +:1041F00029F9050000D1FFDF0320A87505F0F9F9C2 +:1042000094E80F00083686E80F00F94810F8301FD0 +:1042100041F001010170E7E74046FFF713F905009F +:1042200000D1FFDFA1884FF6FF700027814202D145 +:10423000E288824203D0814201D1E08840B105F09A +:10424000D8F994E80F00083686E80F00AF75CBE781 +:10425000A87D0128C8D178230022414613F084FBB1 +:104260000220A875C0E738B50C4624285CD008DCCD +:1042700020280FD0212825D022284BD0232806D152 +:104280004CE0252841D0262832D03F2851D00725A0 +:10429000284638BD0021052013F0E6FB08B11120A7 +:1042A00038BDA01C0EF0E1FA04F0BDFF0500EFD10F +:1042B000002208231146052013F056FB0528E7D0FD +:1042C000FFDFE5E76068FDF708F808B1102038BDAA +:1042D000618820886A460EF071FD04F0A4FF050095 +:1042E000D6D160680028D3D0BDF800100180CFE798 +:1042F000206820B1FCF7FAFF08B11025C8E7204676 +:104300000EF03BFE1DE00546C2E7A17820880EF0C6 +:1043100086FD16E0086801F08DFEF4E7087800F0ED +:1043200001000DF0B9FD0CE0618820880EF0C1FCA1 +:1043300007E0087800F001008DF8000068460EF0F4 +:10434000DFF804F070FFDEE770B505460C4608465E +:10435000FCF7A5FF08B1102070BD202D07D0212D3E +:104360000DD0222D0BD0252D09D0072070BD20881F +:10437000A11C0DF065FEBDE8704004F054BF06209E +:1043800070BD9B482530704708B5342200219848FD +:104390000AF07DF80120FEF749FE1120FEF75EFECF +:1043A00093496846263105F0B7F891489DF80020FA +:1043B00010F8251F62F3470121F00101017000216F +:1043C00041724FF46171A0F8071002218172FEF76B +:1043D0008FFE00B1FFDFFDF705F801F0BEF908BD63 +:1043E00010B50C464022002120460AF050F8A07F6C +:1043F00020F00300A077202020700020A07584F812 +:10440000230010BD70472DE9FC410746FCF721FF52 +:1044100010B11020BDE8FC81754E06F12501D6F8DB +:1044200025000090B6F82950ADF8045096F82B40BE +:104430008DF806403846FEF7BDFF0028EAD1FEF7AA +:1044400057FE0028E6D0009946F8251FB580B471C4 +:10445000E0E710B50446FCF722FF08B1102010BDBC +:1044600063486349224690F8250026314008FEF74C +:10447000B8FF002010BD3EB504460D460846FCF7C7 +:104480000EFF08B110203EBD14B143F204003EBD42 +:1044900057488078052803D0042801D008203EBD65 +:1044A000694602A80AF012FC2A4669469DF80800EF +:1044B000FEF797FF00203EBDFEB50D4604004FF00D +:1044C000000712D00822FEF79FFE002812D1002616 +:1044D00009E000BF54F826006946FEF720FF0028D7 +:1044E00008D1761CF6B2AE42F4D30DF01CFC10B12C +:1044F00043F20320FEBD3E4E86F824700CB3002725 +:104500001BE000BF54F8270002A9FEF708FF00B126 +:10451000FFDF9DF808008DF8000054F8270050F8E0 +:10452000011FCDF801108088ADF8050068460DF038 +:104530001FFC00B1FFDF7F1CFFB2AF42E2D386F861 +:1045400024500020FEBD2DE9F0418AB01546884672 +:1045500004001ED00F4608222946FEF755FE00280B +:1045600011D1002613E000BF54F826006946103030 +:1045700000F01FFD002806D13FB157F82600FCF7D8 +:1045800068FE10B110200AB02EE6761CF6B2AE42DC +:10459000EAD3681EC6B217E0701CC7B212E000BFB3 +:1045A00054F82600017C4A0854F827100B7CB2EB23 +:1045B000530F05D106221130113109F011FF50B10E +:1045C0007F1CFFB2AF42EBD3761EF6B2E4D2464672 +:1045D00024B1012003E043F20520D4E700200DF0D0 +:1045E000ECFB10B90DF0F5FB20B143F20420CAE753 +:1045F000F001002064B300270DF1170826E000BF8A +:1046000054F827006946103000F0D3FC00B1FFDFFA +:1046100054F82700102250F8111FCDF8011080889F +:10462000ADF8050054F827100DF1070009F005FF5B +:1046300096B156F827101022404609F0FEFE684653 +:104640000DF07BFB00B1FFDF7F1CFFB2AF42D7D381 +:10465000FEF713FF002096E7404601F0DFFCEEE78F +:1046600030B585B00446FDF7BDF830B906200FF02F +:10467000C5FB10B1062005B030BD2046FCF7E9FDB2 +:1046800018B96068FCF732FE08B11020F3E76088C3 +:104690004AF2B811884206D82078F94D28B101288D +:1046A00006D0022804D00720E5E7FEF721FD18E038 +:1046B0006078022804D0032802D043F20220DAE70F +:1046C00085F82F00C1B200200090ADF80400022947 +:1046D0002CD0032927D0FFDF68460DF009FC04F039 +:1046E000A2FD0028C7D1606801F08BFC207858B18A +:1046F00001208DF800000DF1010001F08FFC6846EB +:104700000EF0FDFB00B1FFDF207885F82E00FEF7EC +:10471000B4FE608860B1A88580B20DF046FB00B1A0 +:10472000FFDF0020A7E78DF80500D5E74020FAE776 +:104730004FF46170EFE710B50446FCF7B0FD20B907 +:10474000606838B1FCF7C9FD08B1102010BD606881 +:1047500001F064FCCA4830F82C1F6180C178617098 +:1047600080782070002010BD2DE9F843144689465A +:104770000646FCF794FDA0B94846FCF7B7FD80B9A2 +:104780002046FCF7B3FD60B9BD4DA878012800D1E3 +:104790003CB13178FF2906D049B143F20400BDE8AD +:1047A000F8831020FBE7012801D00420F7E7CCB301 +:1047B000052811D004280FD069462046FEF776FE62 +:1047C0000028ECD1217D49B1012909D0022909D065 +:1047D000032909D00720E2E70820E0E7024604E0C9 +:1047E000012202E0022200E003228046234617460F +:1047F00000200099FEF794FE0028D0D1A0892880DF +:10480000A07BE875BDF80000A882AF75BDF8001068 +:10481000090701D5A18931B1A1892980C00704D038 +:10482000032003E006E08021F7E70220FEF7FEFB0D +:1048300086F800804946BDE8F8430020FEF71EBF19 +:104840007CB58F4C05460E46A078022803D003287D +:1048500001D008207CBD15B143F204007CBD0720C7 +:104860000FF0D4FA10B9A078032806D0FEF70CFC9C +:1048700028B1A078032804D009E012207CBD1320C1 +:104880007CBD304600F053FB0028F9D1E670FEF7FE +:104890006FFD0AF058F901208DF800008DF8010035 +:1048A0008DF802502088ADF80400E07D8DF80600F8 +:1048B00068460EF0C1F904F0B6FC0028E0D1A078FB +:1048C000032805D05FF00400FEF7B0FB00207CBD9C +:1048D000E07800F03CFB0520F6E71CB510B143F290 +:1048E00004001CBD664CA078042803D0052801D024 +:1048F00008201CBD00208DF8000001218DF801105A +:104900008DF8020068460EF097F904F08CFC002840 +:10491000EFD1A078052805D05FF00200FEF786FBF6 +:1049200000201CBDE07800F01FFB0320F6E72DE916 +:10493000FC4180460E4603250846FCF7D7FC0028BC +:1049400066D14046FEF77EFD040004D02078222880 +:1049500004D208205EE543F202005BE5A07F00F090 +:1049600003073EB1012F0CD000203146FEF727FC93 +:104970000500EFD1012F06D0022F1AD0FFDF284605 +:1049800048E50120F1E7A07D3146022801D011B1B0 +:1049900007E011203EE56846FCF758FE0028D9D113 +:1049A0006946404606F04DFE0500E8D10120A0759D +:1049B000E5E7A07D032804D1314890F83000C00716 +:1049C00001D02EB30EE026B1A07F40071ED40021F7 +:1049D00000E00121404606F054FE0500CFD1A0754D +:1049E000002ECCD03146404600F0EDFA05461128A5 +:1049F000C5D1A07F4107C2D4316844F80E1F716849 +:104A0000616040F0040020740025B8E71125B6E786 +:104A10001020FFE470B50C460546FEF713FD0100BB +:104A200005D022462846BDE87040FEF70EBD43F291 +:104A3000020070BD10B5012807D1114B9B78012BE6 +:104A400000D011B143F2040010BD0DF0E0F9BDE853 +:104A5000104004F0E8BB012300F090BA00231A468E +:104A6000194600F08BBA70B506460C460846FCF7AE +:104A7000F0FB18B92068FCF712FC18B1102070BDCB +:104A8000F0010020F84D2A7E112A04D0132A00D309 +:104A90003EB10820F3E721463046FEF77DFE60B1C7 +:104AA000EDE70920132A0DD0142A0BD0A188FF2985 +:104AB000E5D31520FEF7D2FA0020D4E90012C5E9AB +:104AC0000712DCE7A1881F29D9D31320F2E71CB510 +:104AD000E548007E132801D208201CBD00208DF877 +:104AE000000068460DF02AFC04F09DFB0028F4D17C +:104AF0001120FEF7B3FA00201CBD2DE9F04FDFF8BE +:104B000068A3814691B09AF818009B4615460C465A +:104B1000132803D3FFF7DBFF00281FD12046FCF743 +:104B200098FBE8BB2846FCF794FBC8BB20784FF005 +:104B30000107C0074FF0000102D08DF83A7001E084 +:104B40008DF83A1020788846C0F3C1008DF8000037 +:104B500060788DF80910C10803D0072011B0BDE8B6 +:104B6000F08FB0B3C10701D08DF80970810705D56A +:104B70009DF8091041F002018DF80910400705D594 +:104B80009DF8090040F004008DF809009DF8090027 +:104B9000810703D540F001008DF80900002000E0F6 +:104BA00015E06E4606EB400162884A81401CA288EF +:104BB000C0B20A820328F5D32078C0F3C1000128CF +:104BC00025D0032823D04846FCF743FB28B110200A +:104BD000C4E7FFE78DF80970D8E799F800004008AE +:104BE00008D0012809D0022807D0032805D043F2B5 +:104BF0000220B3E78DF8028001E08DF8027048468C +:104C000050F8011FCDF803108088ADF80700FEF7BB +:104C1000AFFB8DF801000021424606EB41002B88D6 +:104C2000C3826B888383AB884384EB880385491CEC +:104C3000C285C9B282860329EFD3E088ADF83C0073 +:104C400068460DF053FC002887D19AF818005546A5 +:104C5000112801D0082081E706200FF0D7F838B1DD +:104C60002078C0F3C100012804D0032802D006E058 +:104C7000122073E795F8240000283FF46EAFFEF78A +:104C800003FA022801D2132068E7584600F04FF9D2 +:104C900000289DD185F819B068460DF06DFD04F02F +:104CA000C2FA040094D1687E00F051F91220FEF798 +:104CB000D5F9204652E770B56B4D287E122801D0F9 +:104CC0000820DCE60DF05BFD04F0ADFA040005D130 +:104CD000687E00F049F91120FEF7C0F92046CEE6C3 +:104CE00070B5064615460C460846FCF7D8FA18B9C2 +:104CF0002846FCF7D4FA08B11020C0E62A4621461F +:104D000030460EF03BF804F08EFA0028F5D12178F9 +:104D10007F29F2D10520B2E67CB505460C4608464F +:104D2000FCF797FA08B110207CBD2846FEF78AFBF5 +:104D300020B10078222804D208207CBD43F2020072 +:104D40007CBD494890F83000400701D511207CBD5A +:104D50002078C00802D16078C00801D007207CBD4F +:104D6000ADF8005020788DF8020060788DF80300CF +:104D70000220ADF8040068460CF03BFE04F053FA44 +:104D80007CBD70B586B014460D460646FEF75AFB4C +:104D900028B10078222805D2082006B06FE643F239 +:104DA0000200FAE72846FCF7A1FA20B944B12046F0 +:104DB000FCF793FA08B11020EFE700202060A080F4 +:104DC000294890F83000800701D51120E5E703A9B4 +:104DD00030460CF05EFE10B104F025FADDE7ADF8C8 +:104DE0000060BDF81400ADF80200BDF81600ADF883 +:104DF0000400BDF81000BDF81210ADF80600ADF8C3 +:104E000008107DB1298809B1ADF80610698809B18B +:104E1000ADF80210A98809B1ADF80810E98809B108 +:104E2000ADF80410DCB1BDF80610814201D9081AB2 +:104E30002080BDF80210BDF81400814201D9081A83 +:104E40006080BDF80800BDF80410BDF816200144CC +:104E5000BDF812001044814201D9081AA0806846AA +:104E60000CF0D5FEB8E70000F00100201CB56C493D +:104E70000968CDE9001068460DF03AFB04F0D3F95B +:104E80001CBD1CB500200090019068460DF030FB61 +:104E900004F0C9F91CBD70B505460C460846FCF780 +:104EA000FEF908B11020EAE5214628460DF012F976 +:104EB000BDE8704004F0B7B93EB505460C4608465B +:104EC000FCF7EDF908B110203EBD002000900190E4 +:104ED0000290ADF800502089ADF8080020788DF8D8 +:104EE0000200606801902089ADF808006089ADF883 +:104EF0000A0068460DF000F904F095F93EBD0EB5C4 +:104F0000ADF800000020019068460DF0F5F804F0BF +:104F10008AF90EBD10800888508048889080C88823 +:104F200010818888D080002050819081704710B512 +:104F3000044604F0E4F830B1407830B1204604F083 +:104F4000FCFB002010BD052010BD122010BD10B5C7 +:104F500004F0D5F8040000D1FFDF607800B9FFDF6E +:104F60006078401E607010BD10B504F0C8F80400F1 +:104F700000D1FFDF6078401C607010BD1CB5ADF83B +:104F800000008DF802308DF803108DF8042068467B +:104F90000DF0B7FE04F047F91CBD0CB521A2D2E913 +:104FA0000012CDE900120079694601EB501000783B +:104FB0000CBD0278520804D0012A02D043F202202C +:104FC0007047FEF7ACB91FB56A46FFF7A3FF684606 +:104FD0000DF008FC04F027F904B010BD70B50C000A +:104FE00006460DD0FEF72EFA050000D1FFDFA680A1 +:104FF00028892081288960816889A081A889E08129 +:105000003DE500B540B1012805D0022803D00328B2 +:1050100004D0FFDF002000BDFF2000BD042000BD44 +:1050200014610200070605040302010010B50446DE +:10503000FCF70FF908B1102010BD2078C0F3021062 +:10504000042807D86078072804D3A178102901D84C +:10505000814201D2072010BDE078410706D42179B2 +:105060004A0703D4000701D4080701D5062010BD64 +:10507000002010BD10B513785C08837F64F3C7135C +:10508000837713789C08C37F64F30003C377107899 +:10509000C309487863F34100487013781C090B7802 +:1050A00064F347130B701378DB0863F30000487058 +:1050B0005078487110BD10B5C4780B7864F30003C4 +:1050C0000B70C478640864F341030B70C478A408BF +:1050D00064F382030B70C478E40864F3C3030B70B9 +:1050E0000379117863F30001117003795B0863F3AE +:1050F0004101117003799B0863F3820111700079FB +:10510000C00860F3C301117010BD70B514460D46A0 +:10511000064604F06BFA80B10178182221F00F01E5 +:10512000891C21F0F001A03100F8081B214609F08C +:1051300084F9BDE8704004F05CBA29463046BDE809 +:1051400070401322FEF781B92DE9F047064608A802 +:10515000904690E8300489461F46142200212846D4 +:1051600009F095F90021CAF80010B8F1000F03D03A +:10517000B9F1000F03D114E03878C00711D02068CE +:10518000FCF78DF8C0BBB8F1000F07D120681230D2 +:1051900028602068143068602068A8602168CAF818 +:1051A00000103878800724D56068FCF796F818BBA3 +:1051B000B9F1000F21D0FFF7E4F80168C6F86811D3 +:1051C0008188A6F86C11807986F86E0101F013FDD4 +:1051D000F94FEF60626862B196F8680106F26911F2 +:1051E00040081032FEF7FDF810223946606809F0D9 +:1051F00024F90020BDE8F08706E0606820B1E8608F +:105200006068C6F86401F4E71020F3E730B505469E +:1052100008780C4620F00F00401C20F0F0011031FF +:1052200021700020607095F8230030B104280FD061 +:10523000052811D0062814D0FFDF20780121B1EB1A +:10524000101F04D295F8200000F01F00607030BDE0 +:1052500021F0F000203002E021F0F000303020702A +:10526000EBE721F0F0004030F9E7F0B591B002270C +:1052700015460C4606463A46ADF80870092103ABC0 +:1052800005F063F80490002810D004208DF8040085 +:105290008DF80170E034099605948DF818500AA92C +:1052A000684610F0FCFA00B1FFDF012011B0F0BD3C +:1052B00010B588B00C460A99ADF80000CBB118685B +:1052C000CDF80200D3F80400CDF80600ADF80A20AE +:1052D000102203A809F0B1F868460DF0F3FA03F0C4 +:1052E000A2FF002803D1A17F41F01001A17708B0EF +:1052F00010BD0020CDF80200E6E72DE9F84F064684 +:10530000808A0D4680B28246FEF79CF804463078CB +:10531000DFF8A48200274FF00209A8F120080F2827 +:1053200070D2DFE800F06FF23708387D8CC8F1F0FA +:10533000EFF35FF3F300A07F00F00300022809D031 +:105340005FF0000080F0010150460EF0AFFD050057 +:1053500003D101E00120F5E7FFDF98F85C10C907F1 +:1053600002D0D8F860000BE0032105F11D0012F017 +:10537000BEF8D5F81D009149B0FBF1F201FB120017 +:10538000C5F81D0070686867B068A8672078252890 +:1053900000D0FFDFCAE0A07F00F00300022809D0A0 +:1053A0005FF0000080F0010150460EF07FFD060026 +:1053B00003D101E00120F5E7FFDF3078810702D556 +:1053C0002178252904D040F001003070BDE8F88F25 +:1053D00085F80090307F287106F11D002D36C5E953 +:1053E0000206F3E7A07F00F00300022808D00020A7 +:1053F00080F0010150460EF059FD040004D102E096 +:105400000120F5E7A7E1FFDF2078C10604D50720DA +:1054100028703D346C60D9E740F008002070D5E773 +:10542000E07F000700D5FFDF307CB28800F0010389 +:1054300001B05046BDE8F04F092106F064B804B948 +:10544000FFDF716821B1102204F1240008F0F5FF9C +:1054500028212046FDF75EFEA07F00F00300022811 +:105460000ED104F12400002300901A462146504634 +:10547000FFF71EFF112807D029212046FDF74AFE1D +:10548000307A84F82000A1E7A07F000700D5FFDF75 +:1054900014F81E0F40F008002070E782A761E76152 +:1054A000C109607861F34100014660F382016170D7 +:1054B000307AE0708AE7A07F00F00300022809D06C +:1054C0005FF0000080F0010150460EF0EFFC040098 +:1054D00003D101E00120F5E7FFDF022104F185009F +:1054E00012F005F80420287004F5B4706860B4F870 +:1054F00085002882304810387C346C61C5E9028010 +:1055000064E703E024E15BE02DE015E0A07F00F01C +:105510000300022807D0002080F0010150460EF061 +:10552000C5FC18B901E00120F6E7FFDF324621464D +:105530005046BDE8F84FE8E504B9FFDF20782128A0 +:10554000A1D93079012803D1E07F40F00800E0774D +:10555000324621465046FFF7D8FD2046BDE8F84FB9 +:105560002321FDF7D7BD3279AA8005F1080309216F +:10557000504604F0EAFEE86010B10520287025E7E7 +:10558000A07F00F00300022808D0002080F0010175 +:1055900050460EF08BFC040003D101E00120F5E73A +:1055A000FFDF04F1620102231022081F0EF005FB49 +:1055B00007703179417009E75002002040420F0026 +:1055C000A07F00F00300022808D0002080F0010135 +:1055D00050460EF06BFC050003D101E00120F5E719 +:1055E000FFDF95F8840000F0030001287AD1A07F46 +:1055F00000F00307E07F10F0010602D0022F04D173 +:1056000033E095F8A000C0072BD0D5F8601121B386 +:1056100095F88320087C62F387000874A17FCA098B +:10562000D5F8601162F341000874D5F8601166F393 +:1056300000000874AEB1D5F86001102204F1240115 +:10564000883508F0FAFE287E40F001002876287898 +:1056500020F0010005F8880900E016B1022F04D0FF +:105660002DE095F88800C00727D0D5F85C1121B34C +:1056700095F88320087C62F387000874A17FCA092B +:10568000D5F85C1162F341000874D5F85C1166F33B +:10569000000008748EB1D5F85C01102204F12401D9 +:1056A000883508F0CAFE287840F0010005F8180B8C +:1056B000287820F0010005F8A009022F44D000202E +:1056C00000EB400005EBC00090F88800800709D58A +:1056D00095F87C00D5F86421400805F17D01103271 +:1056E000FDF77FFE8DF8009095F884006A4600F083 +:1056F00003008DF8010095F888108DF8021095F8D8 +:10570000A0008DF803002146504601F05DFA207894 +:10571000252805D0212807D0FFDF2078222803D9AB +:1057200022212046FDF7F6FCA07F00F003000228AE +:105730000CD0002080F0010150460EF0C9FB00287B +:105740003FF44FAEFFDF41E60120B9E70120F1E76A +:10575000706847703AE6FFDF38E670B5FE4C00250A +:1057600084F85C50256610F066F804F110012046BC +:1057700003F0F8FE84F8305070BD70B50D46FDF7AB +:1057800061FE040000D1FFDF4FF4B872002128460B +:1057900008F07DFE04F124002861A07F00F00300E2 +:1057A000022809D05FF0010105F1E00010F044F893 +:1057B000002800D0FFDF70BD0221F5E70A46014650 +:1057C00002F1E00010F059B870B50546406886B0A7 +:1057D00001780A2906D00D2933D00E292FD0FFDFFA +:1057E00006B070BD86883046FDF72CFE040000D15F +:1057F000FFDF20782128F3D028281BD168680221F8 +:105800000E3001F0D6F9A8B168680821801D01F0BA +:10581000D0F978B104F1240130460DF00FFA03F00D +:1058200002FD00B1FFDF06B02046BDE8704029212F +:10583000FDF770BC06B0BDE8704003F0DABE012190 +:1058400001726868C6883046FDF7FCFD040000D18F +:10585000FFDFA07F00F00301022902D120F0100039 +:10586000A077207821280AD06868017A09B10079E8 +:1058700080B1A07F00F00300022862D0FFDFA07F8C +:1058800000F003000228ABD1FEF72DF80028A7D0C6 +:10589000FFDFA5E703F0ADFEA17F08062BD5E07F73 +:1058A000C00705D094F8200000F01F00102820D079 +:1058B0005FF0050084F82300207829281DD02428D3 +:1058C000DDD13146042012F0F9F822212046FDF7FF +:1058D00021FCA07F00F00300022830D05FF0000020 +:1058E00080F0010130460EF0F3FA0028C7D0FFDF48 +:1058F000C5E70620DEE70420DCE701F0030002280C +:1059000008D0002080F0010130460EF0CFFA0500EB +:1059100003D101E00120F5E7FFDF25212046FDF757 +:10592000F9FB03208DF80000694605F1E0000FF057 +:105930009BFF0228A3D00028A1D0FFDF9FE7012012 +:10594000CEE703F056FE9AE72DE9F04387B099467B +:10595000164688460746FDF775FD04004BD02078B3 +:10596000222848D3232846D0E07F000743D4A07FD5 +:1059700000F00300022809D05FF0000080F0010170 +:1059800038460EF093FA050002D00CE00120F5E74E +:10599000A07F00F00300022805D001210022384634 +:1059A0000EF07BFA05466946284601F034F9009866 +:1059B00000B9FFDF45B10098E03505612078222865 +:1059C00006D0242804D007E000990020086103E0F5 +:1059D00025212046FDF79EFB00980121417047627A +:1059E000868001A9C0E902890FF059FF022802D080 +:1059F000002800D0FFDF07B0BDE8F08370B586B0A7 +:105A00000546FDF71FFD017822291ED9807F00F091 +:105A10000300022808D0002080F0010128460EF083 +:105A200045FA04002FD101E00120F5E7FFDF2AE06D +:105A3000B4F85E0004F1620630440178427829B17E +:105A400021462846FFF711FCB0B9C9E6ADF804209D +:105A50000921284602AB04F078FC03900028F4D01A +:105A600005208DF80000694604F1E0000FF0FCFE0F +:105A7000022801D000B1FFDF02231022314604F1D9 +:105A80005E000EF0D0F8B4F860000028D0D1A7E690 +:105A900010B586B00446FDF7D5FC017822291BD944 +:105AA000807F00F00300022808D0002080F0010170 +:105AB00020460EF0FBF9040003D101E00120F5E7D8 +:105AC000FFDF06208DF80000694604F1E0000FF0CA +:105AD000CBFE002800D0FFDF06B010BD2DE9F05F3F +:105AE00005460C4600270078904601093E4604F121 +:105AF000080BBA4602297DD0072902D00A2909D10C +:105B000046E0686801780A2905D00D2930D00E29B1 +:105B10002ED0FFDFBBE114271C26002C6BD0808821 +:105B2000A080FDF78FFC5FEA000900D1FFDF99F844 +:105B300017005A46400809F11801FDF752FC686841 +:105B4000C0892082696851F8060FC4F812004868BD +:105B5000C4F81600A07E01E03002002020F006000C +:105B600040F00100A07699F81E0040F020014DE0C1 +:105B70001A270A26002CD1D0C088A080FDF762FC2D +:105B8000050000D1FFDF59462846FFF73FFB7EE1C5 +:105B90000CB1A88BA080287A0B287DD006DC0128C8 +:105BA0007BD0022808D0032804D135E00D2875D019 +:105BB0000E2874D0FFDF6AE11E270926002CADD025 +:105BC000A088FDF73FFC5FEA000900D1FFDF287BDA +:105BD00000F003000128207A1BD020F00100207281 +:105BE000297B890861F341002072297BC90861F390 +:105BF000820001E041E1F2E02072297B090961F3B2 +:105C0000C300207299F81E0040F0400189F81E1070 +:105C10003DE140F00100E2E713270D26002CAAD059 +:105C2000A088FDF70FFC8146807F00F0030002286A +:105C300008D0002080F00101A0880EF037F905009F +:105C400003D101E00120F5E7FFDF99F81E0000F025 +:105C50000302022A50D0686F817801F00301012904 +:105C6000217A4BD021F00101217283789B0863F3E4 +:105C7000410121728378DB0863F38201217283780A +:105C80001B0963F3C3012172037863F306112172C8 +:105C9000437863F3C71103E061E0A9E090E0A1E07D +:105CA000217284F809A0C178A172022A29D0027950 +:105CB000E17A62F30001E1720279520862F3410174 +:105CC000E1720279920862F38201E1720279D208EC +:105CD00062F3C301E1724279217B62F30001217317 +:105CE0004279520862F3410121734279920862F3CA +:105CF00082012173407928E0A86FADE741F00101EE +:105D0000B2E74279E17A62F30001E1724279520826 +:105D100062F34101E1724279920862F38201E17219 +:105D20004279D20862F3C301E1720279217B62F306 +:105D3000000121730279520862F341012173027953 +:105D4000920862F3820121730079C00860F3C301F5 +:105D5000217399F80000232831D9262140E0182723 +:105D60001026E4B3A088FDF76DFB8346807F00F02A +:105D70000300022809D0002080F00101A0880EF065 +:105D800095F85FEA000903D101E00120F4E7FFDFA5 +:105D9000E868A06099F8000040F0040189F800105C +:105DA00099F80100800708D5012020739BF80000B6 +:105DB00023286CD92721584651E084F80CA066E0CE +:105DC00015270F265CB1A088FDF73CFB8146062213 +:105DD0005946E86808F0C7FB0120A073A0E041E045 +:105DE00048463CE016270926E4B3287B20724EE0A3 +:105DF000287B19270E26ACB3C4F808A0A4F80CA081 +:105E0000012807D0022805D0032805D0042803D094 +:105E1000FFDF0DE0207207E0697B042801F00F012D +:105E200041F0800121721ED0607A20F00300607280 +:105E3000A088FDF707FB05460078212827D02328F6 +:105E400000D0FFDFA87F00F00300022813D000205D +:105E500080F00101A0880EF03BF822212846FDF7D2 +:105E600059F914E004E0607A20F00300401CDEE7FA +:105E7000A8F8006010E00120EAE70CB16888A08073 +:105E8000287A68B301280AD002284FD0FFDFA8F88B +:105E900000600CB1278066800020BDE8F09F1527C8 +:105EA0000F26002CE4D0A088FDF7CCFA807F00F00C +:105EB0000300022808D0002080F00101A0880DF026 +:105EC000F5FF050003D101E00120F5E7FFDFD5F87C +:105ED0001D000622594608F046FB84F80EA0D6E7BE +:105EE00017270926002CC3D0A088FDF7ABFA8146FE +:105EF000807F00F00300022808D0002080F001011C +:105F0000A0880DF0D3FF050003D101E00120F5E7E3 +:105F1000FFDF6878800701D5022000E001202072B1 +:105F200099F800002328B2D9272159E719270E260E +:105F3000002C9DD0A088FDF785FA5FEA000900D10A +:105F4000FFDFC4F808A0A4F80CA084F808A0A07A89 +:105F500040F00300A07299F81E10C90961F3820095 +:105F6000A07299F81F2099F81E1012EAD11F05D0CF +:105F700099F8201001F01F0110292BD020F0080003 +:105F8000A07299F81F10607A61F3C3006072697A99 +:105F900001F003010129A2D140F00400607299F8D8 +:105FA0001E0000F003000228E87A16D0217B60F37F +:105FB00000012173AA7A607B62F300006073EA7AC1 +:105FC000520862F341012173A97A490861F3410043 +:105FD00060735CE740F00800D2E7617B60F300018A +:105FE0006173AA7A207B62F300002073EA7A520878 +:105FF00062F341016173A97A490861F3410020739A +:1060000045E710B5FE4C30B10146102204F12000E6 +:1060100008F013FA012084F8300010BD10B50446D2 +:1060200000F0E9FDF64920461022BDE8104020317D +:1060300008F003BA70B5F24D06004FF0000413D01B +:10604000FBF707F908B110240CE00621304608F0F0 +:1060500071FA411C05D028665FF0010085F85C00EC +:1060600000E00724204670BD0020F7E7007810F01C +:106070000F0204D0012A05D0022A0CD110E0000939 +:1060800009D10AE00009012807D0022805D0032819 +:1060900003D0042801D00720704708700020704703 +:1060A0000620704705282AD2DFE800F003070F1703 +:1060B0001F00087820F0FF001EE0087820F00F0095 +:1060C000401C20F0F000103016E0087820F00F009F +:1060D000401C20F0F00020300EE0087820F00F0087 +:1060E000401C20F0F000303006E0087820F00F006F +:1060F000401C20F0F000403008700020704707205E +:1061000070472DE9F041804688B00D4600270846CB +:10611000FBF7ECF8A8B94046FDF794F9040003D06A +:106120002078222815D104E043F2020008B0BDE82F +:10613000F08145B9A07F410603D500F00300022895 +:1061400001D01020F2E7A07FC10601D4010702D5DB +:106150000DB10820EAE7E17F090701D50D20E5E749 +:1061600000F0030002280DD165B12846FEF75EFF5E +:106170000700DBD1FBF736FB20B9E878800701D5B3 +:106180000620D3E7A07F00F00300022808D00020FB +:1061900080F0010140460DF089FE060002D00FE0BC +:1061A0000120F5E7A07F00F0030002280ED00020B8 +:1061B00080F00101002240460DF06FFE060007D07E +:1061C000A07F00F00300022804D009E00120EFE7DF +:1061D0000420ABE725B12A4631462046FEF74AFFA8 +:1061E0006946304600F017FD009800B9FFDF0099BE +:1061F000022006F1E0024870C1F824804A610022C2 +:106200000A81A27F02F00302022A1CD00120087139 +:10621000287800F00102087E62F3010008762A78EF +:10622000520862F3820008762A78920862F3C3006B +:1062300008762A78D20862F30410087624212046D2 +:10624000FCF768FF33E035B30871301D88613078A2 +:10625000400908777078C0F340004877287800F04C +:106260000102887F62F301008877A27FD20962F37E +:1062700082008877E27F62F3C3008877727862F3E6 +:1062800004108877A878C87701F1210228462031C8 +:10629000FEF711FF03E00320087105200876252191 +:1062A0002046FCF737FFA07F20F04000A07701A92F +:1062B00000980FF0F4FA022801D000B1FFDF384651 +:1062C00034E72DE9FF4F8DB09A4693460D460027DF +:1062D0000D98FDF7B7F8060006D03078262806D0CE +:1062E000082011B0BDE8F08F43F20200F9E7B07F5B +:1062F00000F00309B9F1020F11D04DB95846FEF76D +:1063000095FE0028EDD1B07F00F00300022806D0F2 +:10631000BBF1000F11D0FBF765FA20B10DE0BBF126 +:10632000000F50D109E006200DF068FD28B19BF860 +:106330000300800701D50620D3E7B07F00F00300FB +:10634000022809D05FF0000080F001010D980DF0E7 +:10635000ADFD040003D101E00120F5E7FFDF852D4D +:1063600027D007DCEDB1812D1DD0822D1DD0832DCE +:1063700008D11CE0862D1ED0882D1ED0892D1ED060 +:106380008A2D1ED00F2020710F281CD003F02EF96B +:10639000D8B101208DF81400201D06902079B0B1ED +:1063A00056E10020EFE70120EDE70220EBE70320B4 +:1063B000E9E70520E7E70620E5E70820E3E709200D +:1063C000E1E70A20DFE707208BE7112089E7B9F131 +:1063D000020F03D0A56F03D1A06F02E0656FFAE74B +:1063E000606F804631D04FF0010001904FF0020005 +:1063F00000905A4621463046FEF73CFE02E000007F +:10640000300200209BF8000000F00101A87861F341 +:106410000100A870B17FC90961F38200A870F17F03 +:1064200061F3C300A870617861F30410A87020784C +:10643000400928706078C0F3400068709BF8020043 +:10644000E87000206871287103E0022001900120AB +:106450000090A87898F80210C0F3C000C1F3C00102 +:10646000084003902CD05046FAF7F3FEC0BBDAF890 +:106470000C00FAF7EEFE98BBDAF81C00FAF7E9FE1A +:1064800070BBDAF80C00A060DAF81C00E0606078FD +:1064900098F8012042EA500161F34100607098F8D9 +:1064A0000210C0B200EA111161F300006070002018 +:1064B0002077009906F11700022907D0012106E094 +:1064C000607898F8012002EA5001E5E7002104EB2A +:1064D000810148610199701C022902D0012101E06B +:1064E00028E0002104EB81014861A87800F0030056 +:1064F000012857D198F8020000F00300012851D17B +:10650000B9F1020F04D02A1D691D5846FEF7D3FDCC +:10651000287998F8041008408DF82C00697998F8CB +:10652000052011408DF8301008433BD05046FAF753 +:1065300090FE08B11020D4E60AF110018B46B9F1A3 +:10654000020F17D00846002104F18C03CDE90003A7 +:1065500004F5AE7202920BAB2046039AFEF7F4FDEF +:106560000028E8D1B9F1020F08D0504608D14FF009 +:10657000010107E050464FF00101E5E75846F5E715 +:106580004FF0000104F1A403CDE9000304F5B0725B +:10659000029281F001010CAB2046039AFEF7D4FD74 +:1065A0000028C8D16078800733D4A87898F8021002 +:1065B000C0F38000C1F3800108432AD0297898F8FD +:1065C0000000F94AB9F1020F06D032F81120430059 +:1065D000DA4002F003070AE032F810204B00DA40FC +:1065E00012F0030705D0012F0AD0022F0AD0032F83 +:1065F00006D0039A6AB1012906D0042904D008E024 +:106600000227F6E70127F4E7012801D0042800D18A +:106610000427B07F40F08000B077F17F039860F3EB +:106620000001F1776078800705D50320A0710398F9 +:1066300070B9002029E00220022F18D0012F18D0B5 +:10664000042F2AD00020A071B07F20F08000B07706 +:1066500025213046FCF75EFD05A904F1E0000FF0AE +:1066600003F910B1022800D0FFDF002039E6A07145 +:10667000DFE7A0710D22002104F1200007F007FFE1 +:10668000207840F00200207001208DF8100004AA4C +:1066900031460D9800F098FADAE70120A071D7E7AB +:1066A0002DE9F04387B09046894604460025FCF763 +:1066B000C9FE060006D03078272806D0082007B08B +:1066C000BDE8F08343F20200F9E7B07F00F0030079 +:1066D000022809D05FF0000080F0010120460DF093 +:1066E000E5FB040003D101E00120F5E7FFDFA77916 +:1066F0005FEA090005D0012821D0B9F1020F26D1A7 +:1067000010E0B8F1000F22D1012F05D0022F05D0E3 +:10671000032F05D0FFDF2EE00C252CE001252AE019 +:10672000022528E04046FAF794FDB0B9032F0ED1B8 +:106730001022414604F11D0007F07FFE1BE0012FEF +:1067400002D0022F03D104E0B8F1000F13D00720CC +:10675000B5E74046FAF77DFD08B11020AFE71022FB +:10676000002104F11D0007F092FE0621404607F0CB +:10677000E1FEC4F81D002078252140F002002070C1 +:106780003046FCF7C7FC2078C10713D020F0010089 +:10679000207002208DF8000004F11D0002908DF899 +:1067A00004506946C3300FF05FF8022803D010B1DF +:1067B000FFDF00E02577002081E730B587B00D4688 +:1067C0000446FCF73FFE98B1807F00F003000228EA +:1067D00011D0002080F0010120460DF067FB04007D +:1067E0000ED02846FAF735FD38B1102007B030BD7D +:1067F00043F20200FAE70120ECE72078400701D4D9 +:106800000820F3E7294604F13D002022054607F061 +:1068100014FE207840F01000207001070FD520F002 +:106820000800207007208DF80000694604F1E000A0 +:1068300001950FF019F8022801D000B1FFDF002008 +:10684000D4E770B50D460646FCF7FCFD18B101789B +:10685000272921D102E043F2020070BD807F00F0C1 +:106860000300022808D0002080F0010130460DF01E +:106870001DFB040003D101E00120F5E7FFDFA07953 +:10688000022809D16078C00706D02A462146304642 +:10689000FEF7EBFC10B10FE0082070BDB4F860000B +:1068A0000E280BD204F1620102231022081F0DF002 +:1068B00084F9012101704570002070BD112070BD68 +:1068C00070B5064614460D460846FAF7C2FC18B9DC +:1068D0002046FAF7E4FC08B1102070BDA6F57F4011 +:1068E000FF380ED03046FCF7ADFD38B14178224676 +:1068F0004B08811C1846FCF774FD07E043F20200C8 +:1069000070BD2046FDF7A5FD0028F9D11021E01D3E +:1069100010F0EDFDE21D294604F1170000F08BF99F +:10692000002070BD2DE9F04104468AB01546884626 +:1069300000270846FAF7DAFC18B92846FAF7D6FC19 +:1069400018B110200AB0BDE8F0812046FCF77AFDAE +:10695000060003D0307827281BD102E043F2020062 +:10696000F0E7B07F00F00300022809D05FF00000DC +:1069700080F0010120460DF099FA040003D101E0F6 +:106980000120F5E7FFDF2078400702D56078800717 +:1069900001D40820D6E7B07F00F00300022805D01C +:1069A000A06F05D1A16F04E01C610200606FF8E7E1 +:1069B000616F407800B19DB1487810B1B8F1000F17 +:1069C0000ED0ADB1EA1D06A8E16800F034F910223E +:1069D00006A905F1170007F003FD18B1042707E029 +:1069E0000720AFE71022E91D04F12D0007F025FD77 +:1069F000B8F1000F06D0102208F1070104F11D00C4 +:106A000007F01BFD2078252140F002002070304661 +:106A1000FCF780FB2078C10715D020F00100207022 +:106A200002208DF8000004F11D0002901030039048 +:106A30008DF804706946B3300EF016FF022803D0BB +:106A400010B1FFDF00E0277700207BE7F8B515469F +:106A50000E460746FCF7F6FC040004D020782228F6 +:106A600004D00820F8BD43F20200F8BDA07F00F07A +:106A70000300022802D043F20500F8BD3046FAF7C1 +:106A8000E8FB18B92846FAF7E4FB08B11020F8BD76 +:106A900000953288B31C21463846FEF709FC1128C0 +:106AA00015D00028F3D1297C4A08A17F62F3C711D1 +:106AB000A177297CE27F61F30002E277297C8908D3 +:106AC00084F82010A17F21F04001A177F8BDA17FBB +:106AD0000907FBD4D6F80200C4F83600D6F8060041 +:106AE000C4F83A003088A0861022294604F1240018 +:106AF00007F0A3FC287C4108E07F61F34100E077C8 +:106B0000297C61F38200E077287C800884F82100EA +:106B1000A07F40F00800A0770020D3E770B50D46B5 +:106B200006460BB1072070BDFCF78CFC040007D0B3 +:106B30002078222802D3A07F800604D4082070BDCC +:106B400043F2020070BDADB1294630460CF076F834 +:106B500002F069FB297C4A08A17F62F3C711A17783 +:106B6000297CE27F61F30002E277297C890884F8BE +:106B7000201004E030460CF084F802F054FBA17FB2 +:106B800021F02001A17770BD70B50D46FCF75AFCCD +:106B9000040005D02846FAF782FB20B1102070BD12 +:106BA00043F2020070BD29462046FEF72FFB00206D +:106BB00070BD04E010F8012B0AB100207047491E97 +:106BC00089B2F7D20120704770B51546064602F02B +:106BD0000DFD040000D1FFDF207820F00F00801CA5 +:106BE00020F0F0002030207066802868A060BDE8AA +:106BF000704002F0FEBC10B5134C94F83000002831 +:106C000008D104F12001A1F110000EF06FFE012067 +:106C100084F8300010BD10B190F8B9202AB10A48AC +:106C200090F8350018B1002003E0B83001E00648C4 +:106C300034300860704708B50023009313460A46B5 +:106C40000DF031FB08BD00003002002018B1817842 +:106C5000012938D101E010207047018842F6011265 +:106C6000881A914231D018DC42F60102A1EB0200F1 +:106C700091422AD00CDC41B3B1F5C05F25D06FF44E +:106C8000C050081821D0A0F57060FF381BD11CE05F +:106C900001281AD002280AD117E0B0F5807F14D05D +:106CA00008DC012811D002280FD003280DD0FF28BE +:106CB00009D10AE0B0F5817F07D0A0F580700338D4 +:106CC00003D0012801D0002070470F2070470A2808 +:106CD0001FD008DC0A2818D2DFE800F0191B1F1F9C +:106CE000171F231D1F21102815D008DC0B2812D0D8 +:106CF0000C2810D00D2816D00F2806D10DE0112831 +:106D00000BD084280BD087280FD003207047002099 +:106D1000704705207047072070470F2070470420F8 +:106D20007047062070470C20704743F202007047FE +:106D300038B50C46050041D06946FFF797F90028A1 +:106D400019D19DF80010607861F302006070694607 +:106D5000681CFFF78BF900280DD19DF800106078B2 +:106D600061F3C5006070A978C1F34101012903D026 +:106D7000022905D0072038BD217821F0200102E04A +:106D8000217841F020012170410704D0A978C90879 +:106D900061F386106070607810F0380F07D0A97822 +:106DA000090961F3C710607010F0380F02D16078E4 +:106DB000400603D5207840F040002070002038BD08 +:106DC00070B504460020088015466068FFF7B0FFE4 +:106DD000002816D12089A189884211D8606880785E +:106DE000C0070AD0B1F5007F0AD840F20120B1FBFC +:106DF000F0F200FB1210288007E0B1F5FF7F01D907 +:106E00000C2070BD01F201212980002070BD10B559 +:106E10000478137864F3000313700478640864F34F +:106E2000410313700478A40864F382031370047898 +:106E3000E40864F3C30313700478240964F30413AF +:106E400013700478640964F34513137000788009A3 +:106E500060F38613137031B10878C10701D1800740 +:106E600001D5012000E0002060F3C713137010BDAE +:106E70004278530702D002F0070306E012F0380F01 +:106E800002D0C2F3C20300E001234A7863F3020296 +:106E90004A70407810F0380F02D0C0F3C20005E00D +:106EA000430702D000F0070000E0012060F3C502B4 +:106EB0004A7070472DE9F04F95B00D00824613D00F +:106EC00012220021284607F0E2FA4FF6FF7B05AABE +:106ED0000121584607F0A3F80024264637464FF410 +:106EE00020586FF4205972E0102015B0BDE8F08FE3 +:106EF0009DF81E0001280AD1BDF81C1041450BD099 +:106F000011EB09000AD001280CD002280CD0042C67 +:106F10000ED0052C0FD10DE0012400E00224BDF8B5 +:106F20001A6008E0032406E00424BDF81A7002E0A9 +:106F3000052400E00624BDF81A10514547D12C74F1 +:106F4000BEB34FF0000810AA4FF0070ACDE9028245 +:106F5000CDE900A80DF13C091023CDF81090424670 +:106F60003146584607F02BF908BBBDF83C002A46CD +:106F7000C0B210A90EF045FDC8B9AE81CFB1CDE9C0 +:106F800000A80DF1080C0AAE40468CE8410213231C +:106F900000223946584607F012F940B9BDF83C00C6 +:106FA000F11CC01EC0B22A1D0EF02BFD10B1032033 +:106FB0009BE70AE0BDF82900E881062C05D19DF881 +:106FC0001E00A872BDF81C00288100208DE705A8CE +:106FD00007F031F800288BD0FFF779FE85E72DE91F +:106FE000F0471C46DDE90978DDF8209015460E00D3 +:106FF000824600D1FFDF0CB1208818B1D5B1112035 +:10700000BDE8F087022D01D0012100E0002106F14A +:10701000140005F0CDFEA8F8000002463B462946C4 +:10702000504603F092F9C9F8000008B9A41C3C606E +:107030000020E5E71320E3E7F0B41446DDE904524D +:107040008DB1002314B1022C09D101E0012306E027 +:107050000D7CEE0703D025F0010501230D742146B8 +:10706000F0BC04F050BA1A80F0BC70472DE9FE4F16 +:1070700091461A881C468A468046FAB102AB4946B8 +:1070800003F063F9050019D04046A61C27880DF0CF +:1070900050F83246072629463B4600960CF05FFC26 +:1070A00020882346CDE900504A4651464046FFF726 +:1070B000C3FF002020800120BDE8FE8F0020FBE7F9 +:1070C0002DE9F04786B082460EA8904690E8B000C1 +:1070D000894604AA05A903A88DE807001E462A468A +:1070E00021465046FFF77BFF039901B1012139701A +:1070F000002818D1FA4904F1140204AB086003987F +:1071000005998DE8070042464946504606F003FAC5 +:10711000A8B1092811D2DFE800F005080510100A0F +:107120000C0C0E00002006B06AE71120FBE70720D8 +:10713000F9E70820F7E70D20F5E70320F3E7BDF8AE +:1071400010100398CDE9000133462A4621465046E7 +:10715000FFF772FFE6E72DE9F04389B01646DDE957 +:1071600010870D4681461C461422002103A807F013 +:107170008EF9012002218DF810108DF80C008DF889 +:107180001170ADF8146064B1A278D20709D08DF8FF +:107190001600E088ADF81A00A088ADF81800A068C5 +:1071A000079008A80095CDE90110424603A948467A +:1071B0006B68FFF785FF09B0BDE8F083F0B58BB0D1 +:1071C00000240646069407940727089405A8099406 +:1071D000019400970294CDE903400D461023224606 +:1071E000304606F0ECFF78B90AA806A9019400978A +:1071F0000294CDE90310BDF8143000222946304630 +:1072000006F07BFD002801D0FFF761FD0BB0F0BD5B +:1072100006F00CBC2DE9FC410C468046002602F02D +:10722000E5F9054620780D287ED2DFE800F0BC079E +:1072300013B325BD49496383AF959B00A8480068F7 +:1072400020B1417841F010014170ADE0404602F0BC +:10725000FDF9A9E0042140460CF028FE070000D10A +:10726000FFDF07F11401404605F037FDA5BB1321F0 +:107270004046FDF7CFFB97E0042140460CF016FE98 +:10728000070000D1FFDFE088ADF800000020B881E2 +:107290009DF80000010704D5C00602D5A088B8817A +:1072A00005E09DF8010040067ED5A088F88105B96B +:1072B000FFDF22462946404601F0ACFC022673E07F +:1072C000E188ADF800109DF8011009060FD50728D8 +:1072D00003D006280AD00AE024E0042140460CF03E +:1072E000E5FD060000D1FFDFA088F0810226CDB9C0 +:1072F000FFDF17E0042140460CF0D8FD070000D165 +:10730000FFDF07F1140006F0C8FB90F0010F02D177 +:10731000E079000648D5387C022640F00200387437 +:1073200005B9FFDF224600E03DE02946404601F076 +:1073300071FC39E0042140460CF0B8FD017C002DC1 +:1073400001F00206C1F340016171017C21F00201EC +:107350000174E7D1FFDFE5E702260121404602F094 +:10736000A7F921E0042140460CF0A0FD0546606825 +:1073700000902089ADF8040001226946404602F0E1 +:10738000B8F9287C20F0020028740DE0002DC9D146 +:10739000FFDFC7E7022600214046FBF799F8002DE2 +:1073A000C0D1FFDFBEE7FFDF3046BDE8FC813EB560 +:1073B0000C0009D001466B4601AA002006F084FFAC +:1073C00020B1FFF784FC3EBD10203EBD0020208090 +:1073D000A0709DF8050002A900F00700FEF762FE0C +:1073E00050B99DF8080020709DF8050002A9C0F36F +:1073F000C200FEF757FE08B103203EBD9DF808000D +:1074000060709DF80500C109A07861F30410A070B8 +:107410009DF80510890961F3C300A0709DF8041060 +:10742000890601D5022100E0012161F342009DF8A7 +:10743000001061F30000A07000203EBD70B514463E +:1074400006460D4651EA040005D075B10846F9F725 +:1074500044FF78B901E0072070BD2946304606F0A8 +:107460009AFF10B1BDE8704031E454B12046F9F7FD +:1074700034FF08B1102070BD21463046BDE8704091 +:1074800095E7002070BD2DE9FC5F0C46904605464F +:10749000002701780822007A3E46B2EB111F7DD109 +:1074A00004F10A0100910A31821E4FF0020A04F130 +:1074B000080B0191092A72D2DFE802F0EDE005F530 +:1074C00028287BAACE00688804210CF0EFFC060077 +:1074D00000D1FFDFB08928B152270726C3E00000A2 +:1074E0009402002051271026002C7DD06888A080AF +:1074F0000120A071A88900220099FFF79FFF0028B2 +:1075000073D1A8892081288AE081D1E0B5F8129052 +:10751000072824D1E87B000621D5512709F1140062 +:1075200086B2002CE1D0A88900220099FFF786FFDF +:1075300000285AD16888A08084F806A0A8892081F4 +:107540000120A073288A2082A4F81290A88A0090B3 +:1075500068884B46A969019A01F038FBA8E05027DA +:1075600009F1120086B2002C3ED0A88900225946AB +:10757000FFF764FF002838D16888A080A889E080E0 +:10758000287A072813D002202073288AE081E87B1C +:10759000C0096073A4F81090A88A01E085E082E039 +:1075A000009068884B4604F11202A969D4E70120D3 +:1075B000EAE7B5F81290512709F1140086B2002CC1 +:1075C00066D0688804210CF071FC83466888A0802E +:1075D000A88900220099FFF731FF00286ED184F8B6 +:1075E00006A0A889208101E052E067E00420A07392 +:1075F000288A2082A4F81290A88A009068884B46B6 +:10760000A969019A01F0E2FAA989ABF80E104FE0DE +:107610006888FBF717FF0746688804210CF046FCD2 +:10762000064607B9FFDF06B9FFDF687BC00702D057 +:107630005127142601E0502712264CB36888A080F9 +:10764000502F06D084F806A0287B594601F0CEFAC8 +:107650002EE0287BA11DF9E7FE49A88949898142CE +:1076600005D1542706269CB16888A08020E05327C6 +:107670000BE06888A080A889E08019E06888042170 +:107680000CF014FC00B9FFDF55270826002CF0D1C0 +:10769000A8F8006011E056270726002CF8D068886B +:1076A000A080002013E0FFDF02E0012808D0FFDF08 +:1076B000A8F800600CB1278066800020BDE8FC9F20 +:1076C00057270726002CE3D06888A080687AA0712D +:1076D000EEE7401D20F0030009B14143091D01EB15 +:1076E0004000704713B5DB4A00201071009848B184 +:1076F000002468460CF0F7F9002C02D1D64A009914 +:1077000011601CBD01240020F4E770B50D4614463D +:10771000064686B05C220021284606F0B8FE04B971 +:10772000FFDFA0786874A2782188284601F089FAE2 +:107730000020A881E881228805F11401304605F077 +:10774000B0FA6A460121304606F069FC1AE000BF33 +:107750009DF80300000715D5BDF806103046FFF769 +:107760002DFD9DF80300BDF8061040F010008DF8C7 +:107770000300BDF80300ADF81400FF233046059A5E +:1077800006F0D1FD684606F056FC0028E0D006B0B1 +:1077900070BD10B50C4601F1140005F0BAFA0146AF +:1077A000627C2046BDE8104001F080BA30B5044646 +:1077B000A84891B04FF6FF75C18905AA284606F082 +:1077C0002EFC30E09DF81E00A0422AD001282AD1CC +:1077D000BDF81C00B0F5205F03D042F601018842DD +:1077E00021D1002002AB0AAA0CA9019083E807006E +:1077F00007200090BDF81A1010230022284606F03A +:10780000DEFC38B9BDF828000BAAC0B20CA90EF0F6 +:10781000F8F810B1032011B030BD9DF82E00A04241 +:1078200001D10020F7E705A806F005FC0028C9D023 +:107830000520F0E770B5054604210CF037FB040085 +:1078400000D1FFDF04F114010C46284605F045FA8B +:1078500021462846BDE8704005F046BA70B58AB0AA +:107860000C460646FBF7EEFD050014D028782228CA +:1078700027D30CB1A08890B101208DF80C00032013 +:107880008DF8100000208DF8110054B1A088ADF8DB +:107890001800206807E043F202000AB070BD09201A +:1078A000FBE7ADF818000590042130460CF0FEFA15 +:1078B000040000D1FFDF04F1140005F040FA0007D6 +:1078C00001D40820E9E701F091FE60B108A8022187 +:1078D0000094CDE9011095F8232003A93046636890 +:1078E000FFF7EEFBD9E71120D7E72DE9F04FB2F80B +:1078F00002A0834689B0154689465046FBF7A2FD93 +:107900000746042150460CF0D1FA0026044605969D +:107910004FF002080696ADF81C6007B9FFDF04B906 +:10792000FFDF4146504603F055FF50B907AA06A9AC +:1079300005A88DE807004246214650466368FFF7D8 +:107940004EFB444807AB0660DDE9051204F1140064 +:10795000CDF80090CDE90320CDE9013197F823203F +:10796000594650466B6805F033FA06000AD0022EDD +:1079700004D0032E14D0042E00D0FFDF09B030460F +:10798000BDE8F08FBDF81C000028F7D00599CDE9BF +:1079900000104246214650466368FFF74DFBEDE775 +:1079A000687840F008006870E8E710B50C46FFF70B +:1079B000BFF900280BD1607800F00701012905D13B +:1079C00010F0380F02D02078810601D5072010BDB5 +:1079D00040F0C8002070002010BD2DE9F04F99B094 +:1079E00004464FF000081B48ADF81C80ADF820801D +:1079F000ADF82480A0F80880ADF81480ADF81880A8 +:107A0000ADF82880ADF82C80007916460D46474623 +:107A1000012808D0022806D0032804D0042802D068 +:107A2000082019B0ACE72046F9F713FCF0BB284654 +:107A3000F9F70FFCD0BB6068F9F758FCB0BB606881 +:107A400068B160892189884202D8B1F5007F05D9E3 +:107A50000C20E6E7940200201800002080460EAAC1 +:107A600006A92846FFF7ACF90028DAD168688078C3 +:107A7000C0F34100022808D19DF8190010F0380F1A +:107A800003D02869F9F729FC80B905A92069FFF717 +:107A90004FF90028C5D1206950B1607880079DF862 +:107AA000150000F0380002D5F0B301E011E0D8BBBA +:107AB0009DF8140080060ED59DF8150010F0380FC3 +:107AC00003D06068F9F709FC18B96068F9F70EFC93 +:107AD00008B11020A5E70BA906A8FFF7C9F99DF882 +:107AE0002D000BA920F00700401C8DF82D006069C7 +:107AF000FFF75BFF002894D10AA9A069FFF718F9E6 +:107B000000288ED19DF8280080062BD4A06940B1B2 +:107B10009DF8290000F00701012923D110F0380F4A +:107B200020D0E06828B100E01CE00078D0B11C282B +:107B300018D20FAA611C2046FFF769F901213846C7 +:107B400061F30F2082468DF85210B94642F60300C9 +:107B50000F46ADF850000DF13F0218A928680DF04E +:107B600052FF08B107205CE79DF8600015A9CDF829 +:107B70000090C01CCDE9019100F0FF0B00230BF237 +:107B80000122514614A806F075F9E8BBBDF854006F +:107B90000C90FB482A8929690092CDE901106B8974 +:107BA000BDF838202868069906F064F9010077D1FD +:107BB00020784FF0020AC10601D4800616D58DF850 +:107BC000527042F60210ADF85000CDF80C9008A9A2 +:107BD00003AACDF800A0CDE90121002340F2032241 +:107BE00014A80B9906F046F9010059D1E4484D4616 +:107BF00008380089ADF83D000FA8CDE90290CDF816 +:107C00000490CDF8109000E00CE04FF007095B46BF +:107C10000022CDF80090BDF854104FF6FF7006F02A +:107C20006CF810B1FFF753F8FBE69DF83C00000636 +:107C300024D52946012060F30F218DF852704FF4AE +:107C400024500395ADF8500062789DF80C00002395 +:107C500062F300008DF80C006278CDF800A05208A5 +:107C600062F341008DF80C0003AACDE9012540F232 +:107C7000032214A806F0FEF8010011D1606880B359 +:107C80002069A0B905A906A8FFF7F2F86078800777 +:107C900007D49DF8150020F038008DF8150006E097 +:107CA00077E09DF8140040F040008DF814008DF846 +:107CB000527042F60110ADF85000208940F20121C7 +:107CC000B0FBF1F201FB1202606809ABCDF8008055 +:107CD000CDE90103002314A8059906F0CBF80100B3 +:107CE00057D12078C00728D00395A06950B90AA9B8 +:107CF00006A8FFF7BDF89DF8290020F00700401CFA +:107D00008DF829009DF8280007A940F040008DF863 +:107D100028008DF8527042F60310ADF8500003AA07 +:107D2000CDF800A0CDE90121002340F2032214A8E0 +:107D30000A9906F09FF801002BD1E06868B3294644 +:107D4000012060F30F218DF8527042F60410ADF857 +:107D50005000E068002302788DF8582040788DF8B4 +:107D60005900E06816AA4088ADF85A00E06800792A +:107D70008DF85C00E068C088ADF85D00CDF800903B +:107D8000CDE901254FF4027214A806F073F8010042 +:107D900003D00C9800F0B6FF43E679480321083879 +:107DA000017156B100893080BDF824007080BDF8A3 +:107DB0002000B080BDF81C00F080002031E670B5D6 +:107DC00001258AB016460B46012802D0022816D19A +:107DD00004E08DF80E504FF4205003E08DF80E5063 +:107DE00042F60100ADF80C005BB10024601C60F3AA +:107DF0000F2404AA08A918460DF005FE18B10720A3 +:107E00004BE5102049E504A99DF820205C48CDE908 +:107E10000021801E02900023214603A802F20122C5 +:107E200006F028F810B1FEF752FF36E5544808383E +:107E30000EB1C1883180057100202EE5F0B593B0F8 +:107E4000044601268DF83E6041F601000F46ADF86C +:107E50003C0011AA0FA93046FFF7B1FF002837D127 +:107E60002000474C4FF00005A4F1080432D01C223A +:107E7000002102A806F00BFB9DF808008DF83E607B +:107E800040F020008DF8080042F60520ADF83C00D7 +:107E900004200797ADF82C00ADF8300039480A905F +:107EA0000EA80D900E950FA80990ADF82E506A46B9 +:107EB00009A902A8FFF791FD002809D1BDF800002B +:107EC0006081BDF80400A081401CE0812571002084 +:107ED00013B0F0BD6581A581BDF84400F4E72DE93C +:107EE000F74F2749A0B00024083917940A79A14612 +:107EF000012A04D0022A02D0082023B040E5CA8813 +:107F0000824201D00620F8E721988A46824201D1B8 +:107F10000720F2E70120214660F30F21ADF8480069 +:107F20004FF6FF788DF86E000691ADF84A8042F664 +:107F3000020B8DF872401CA9ADF86CB0ADF8704022 +:107F40001391ADF8508012A806F0A4F800252E4633 +:107F50002F460DAB072212A9404606F09EF898B1B5 +:107F60000A2861D1B5B3AEB3ADF86450ADF8666020 +:107F70009DF85E008DF8144019AC012868D06FE0C0 +:107F80009C020020266102009DF83A001FB30128E0 +:107F900059D1BDF8381059451FD118A809A9019425 +:107FA0000294CDE9031007200090BDF8361010238D +:107FB0000022404606F003F9B0BBBDF8600004287B +:107FC00001D006284AD1BDF82410219881423AD127 +:107FD0000F2092E73AE0012835D1BDF83800B0F51E +:107FE000205F03D042F6010188422CD1BAF8060086 +:107FF000BDF83610884201D1012700E0002705B105 +:108000009EB1219881421ED118A809AA0194029418 +:10801000CDE90320072000900D46102300224046A2 +:1080200006F0CDF800B902E02DE04E460BE0BDF8B9 +:108030006000022801D0102810D1C0B217AA09A9E7 +:108040000DF0DFFC50B9BDF8369082E7052054E70B +:1080500005A917A8221D0DF0D6FC08B103204CE796 +:108060009DF814000023001DC2B28DF81420229840 +:108070000092CDE901401BA8069905F0FBFE10B95E +:1080800002228AF80420FEF722FE36E710B50B46DE +:10809000401E88B084B205AA00211846FEF7B7FE3C +:1080A00000200DF1080C06AA05A901908CE8070034 +:1080B000072000900123002221464FF6FF7005F0B3 +:1080C0001CFE0446BDF81800012800D0FFDF204642 +:1080D000FEF7FDFD08B010BDF0B5FF4F044687B0B8 +:1080E00038790E46032804D0042802D0082007B0AF +:1080F000F0BD04AA03A92046FEF762FE0500F6D1F2 +:1081000060688078C0F3410002280AD19DF80D0014 +:1081100010F0380F05D02069F9F7DFF808B110200A +:10812000E5E7208905AA21698DE807006389BDF884 +:1081300010202068039905F09DFE10B1FEF7C7FDE1 +:10814000D5E716B1BDF814003080042038712846F8 +:10815000CDE7F8B50C0006460BD001464FF6FF758B +:1081600000236A46284606F0AFF820B1FEF7AFFDBF +:10817000F8BD1020F8BD69462046FEF7D9FD00285D +:10818000F8D1A078314600F001032846009A06F0A5 +:10819000CAF8EBE730B587B0144600220DF1080CA1 +:1081A00005AD01928CE82C00072200920A46014698 +:1081B00023884FF6FF7005F0A0FDBDF81410218054 +:1081C000FEF785FD07B030BD70B50D4604210BF0FC +:1081D0006DFE040000D1FFDF294604F11400BDE864 +:1081E000704004F0A5BD70B50D4604210BF05EFE95 +:1081F000040000D1FFDF294604F11400BDE87040FF +:1082000004F0B9BD70B50D4604210BF04FFE04001B +:1082100000D1FFDF294604F11400BDE8704004F0EE +:10822000D1BD70B5054604210BF040FE040000D11D +:10823000FFDF214628462368BDE870400122FEF793 +:1082400015BF70B5064604210BF030FE040000D1C6 +:10825000FFDF04F1140004F05CFD401D20F0030575 +:1082600011E0011D00880022431821463046FEF728 +:10827000FDFE00280BD0607CABB2684382B2A068E0 +:10828000011D0BF0D0FCA06841880029E9D170BD28 +:1082900070B5054604210BF009FE040000D1FFDF94 +:1082A000214628466368BDE870400222FEF7DEBE24 +:1082B00070B50E46054601F099F9040000D1FFDFC4 +:1082C0000120207266726580207820F00F00001D6A +:1082D00020F0F00040302070BDE8704001F089B916 +:1082E00010B50446012900D0FFDF2046BDE810404C +:1082F0000121FAF7EDB82DE9F04F97B04FF0000AE1 +:108300000C008346ADF814A0D04619D0E06830B117 +:10831000A068A8B10188ADF81410A0F800A05846D4 +:10832000FBF790F8070043F2020961D03878222861 +:108330005CD3042158460BF0B9FD050005D103E0DC +:10834000102017B0BDE8F08FFFDF05F1140004F036 +:10835000E0FC401D20F00306A078012803D002288D +:1083600001D00720EDE7218807AA584605F057FEFF +:1083700030BB07A805F05FFE10BB07A805F05BFE49 +:1083800048B99DF82600012805D1BDF82400A0F5C4 +:108390002451023902D04FF45050D2E7E068B0B116 +:1083A000CDE902A00720009005AACDF804A0049210 +:1083B000A2882188BDF81430584605F09EFC10B103 +:1083C000FEF785FCBDE7A168BDF8140008809DF8A4 +:1083D0001F00C00602D543F20140B2E70B9838B146 +:1083E000A1780078012905D080071AD40820A8E7D1 +:1083F0004846A6E7C007F9D002208DF83C00A868DF +:108400004FF00009A0B1697C4288714391420FD9B5 +:108410008AB2B3B2011D0BF0BCFB8046A0F800A0ED +:1084200006E003208DF83C00D5F800804FF00109EC +:108430009DF8200010F0380F00D1FFDF9DF82000DC +:108440002649C0F3C200084497F8231010F8010C25 +:10845000884201D90F2074E72088ADF8400014A9A4 +:108460000095CDE90191434607220FA95846FEF732 +:1084700027FE002891D19DF8500050B9A07801281E +:1084800007D1687CB3B2704382B2A868011D0BF0BB +:1084900094FB002055E770B5064615460C46084685 +:1084A000FEF7D4FB002805D12A4621463046BDE818 +:1084B000704084E470BD12E570B51E4614460D0090 +:1084C0000ED06CB1616859B160B10349C98881426D +:1084D00008D0072070BD000094020020296102002E +:1084E0001020F7E72068FEF7B1FB0028F2D13246F2 +:1084F00021462846BDE87040FFF76FBA70B51546B3 +:108500000C0006D038B1FE490989814203D007200A +:10851000E0E71020DEE72068FEF798FB0028D9D1BD +:1085200029462046BDE87040D6E570B5064686B0BF +:108530000D4614461046F8F7B2FED0BB6068F8F757 +:10854000D5FEB0BBA6F57F40FF3803D03046FAF722 +:1085500079FF80B128466946FEF7ACFC00280CD1B3 +:108560009DF810100F2008293DD2DFE801F0080621 +:108570000606060A0A0843F2020006B0AAE703202C +:10858000FBE79DF80210012908D1BDF80010B1F5F4 +:10859000C05FF2D06FF4C052D142EED09DF8061009 +:1085A00001290DD1BDF80410A1F52851062907D2E3 +:1085B00000E029E0DFE801F0030304030303DCE744 +:1085C0009DF80A1001290FD1BDF80810B1F5245FFC +:1085D000D3D0A1F60211B1F50051CED00129CCD0F3 +:1085E000022901D1C9E7FFDF606878B9002305AA35 +:1085F0002946304605F068FE10B1FEF768FBBCE77F +:108600009DF81400800601D41020B6E76188224648 +:1086100028466368FFF7BEFDAFE72DE9F0438146CA +:1086200087B0884614461046F8F739FE18B1102076 +:1086300007B0BDE8F083002306AA4146484605F08E +:1086400043FE10B1FEF743FBF2E79DF81800C006A9 +:1086500002D543F20140EBE70025072705A8019565 +:1086600000970295CDE9035062884FF6FF734146AB +:10867000484605F0A4FD060013D16068F8F70FFE28 +:1086800060B960680195CDE9025000970495238890 +:1086900062884146484605F092FD0646BDF8140042 +:1086A00020803046CEE739B1954B0A889B899A42A3 +:1086B00002D843F2030070471DE610B586B0904C17 +:1086C0000423ADF81430638943B1A4898C4201D2EC +:1086D000914205D943F2030006B010BD0620FBE726 +:1086E000ADF81010002100910191ADF80030022189 +:1086F0008DF8021005A9029104A90391ADF812208A +:108700006946FFF7F8FDE7E72DE9FC4781460D468E +:108710000846F8F79EFD88BB4846FAF793FE5FEAE5 +:1087200000080AD098F80000222829D304214846DE +:108730000BF0BCFB070005D103E043F20200BDE8EB +:10874000FC87FFDF07F1140004F0F9FA06462878E9 +:10875000012803D0022804D00720F0E7B0070FD586 +:1087600002E016F01C0F0BD0A8792C1DC00709D011 +:10877000E08838B1A068F8F76CFD18B11020DEE78A +:108780000820DCE721882A780720B1F5847F35D0DE +:108790001EDC40F20315A1F20313A94226D00EDC21 +:1087A000B1F5807FCBD003DCF9B1012926D1C6E732 +:1087B000A1F58073013BC2D0012B1FD113E0012B27 +:1087C000BDD0022B1AD0032BB9D0042B16D112E046 +:1087D000A1F20912082A11D2DFE802F00B040410FA +:1087E00010101004ABE7022AA9D007E0012AA6D096 +:1087F00004E0320700E0F206002AA0DACDB200F071 +:10880000F5FE50B198F82300CDE90005FA8923461A +:1088100039464846FEF79FFC91E711208FE72DE986 +:10882000F04F8BB01F4615460C4683460026FAF7DC +:1088300009FE28B10078222805D208200BB081E576 +:1088400043F20200FAE7B80801D00720F6E7032F49 +:1088500000D100274FF6FF79CCB1022D71D320460D +:10886000F8F744FD30B904EB0508A8F10100F8F76A +:108870003DFD08B11020E1E7AD1E38F8028CAAB228 +:108880002146484605F081FE40455AD1ADB21C490B +:10889000B80702D58889401C00E001201FFA80F843 +:1088A000F80701D08F8900E04F4605AA4146584697 +:1088B00005F0B5FB4FF0070A4FF00009FCB1204668 +:1088C00008E0408810283CD8361D304486B2AE42BD +:1088D00037D2A01902884245F3D352E09DF8170021 +:1088E00002074ED57CB304EB0608361DB8F80230FB +:1088F000B6B2102B25D89A19AA4222D802E040E03D +:1089000094020020B8F8002091421AD1C0061BD56D +:10891000CDE900A90DF1080C0AAAA11948468CE876 +:108920000700B8F800100022584605F0E6F910B12B +:10893000FEF7CDF982E7B8F80200BDF828108842AA +:1089400002D00B207AE704E0B8F80200304486B287 +:1089500006E0C00604D55846FEF730FC00288AD150 +:108960009DF81700BDF81A1020F010008DF81700C0 +:10897000BDF81700ADF80000FF235846009A05F037 +:10898000D2FC05A805F057FB18B9BDF81A10B9427A +:10899000A4D9042158460BF089FA040000D1FFDF66 +:1089A000A2895AB1CDE900A94D4600232146584677 +:1089B000FEF7D1FB0028BDD1A5813FE700203DE7B0 +:1089C0002DE9FF4F8BB01E4617000D464FF00004F7 +:1089D00012D0B00802D007200FB0B3E4032E00D1AC +:1089E00000265DB10846F8F778FC28B93888691E7A +:1089F0000844F8F772FC08B11020EDE7C74AB00749 +:108A000001D5D18900E00121F0074FF6FF7802D0AF +:108A1000D089401E00E0404686B206AA0B9805F0B9 +:108A2000FEFA4FF000094FF0070B0DF1140A38E081 +:108A30009DF81B00000734D5CDF80490CDF800B0A8 +:108A4000CDF80890CDE9039A434600220B9805F033 +:108A5000B6FB60BB05B3BDF814103A882144281951 +:108A6000091D8A4230D3BDF81E2020F8022BBDF824 +:108A7000142020F8022BCDE900B9CDE90290CDF801 +:108A800010A0BDF81E10BDF8143000220B9805F0A0 +:108A900096FB08B103209FE7BDF814002044001D99 +:108AA00084B206A805F0C7FA20B10A2806D0FEF75E +:108AB0000EF991E7BDF81E10B142B9D934B17DB1BC +:108AC0003888A11C884203D20C2085E7052083E763 +:108AD00022462946404605F058FD014628190180E6 +:108AE000A41C3C80002077E710B50446F8F7D7FBBC +:108AF00008B1102010BD8948C0892080002010BD19 +:108B0000F0B58BB00D4606461422002103A805F0EF +:108B1000BEFC01208DF80C008DF8100000208DF8AF +:108B20001100ADF814503046FAF78CFC48B10078CB +:108B3000222812D3042130460BF0B8F9040005D1E5 +:108B400003E043F202000BB0F0BDFFDF04F11400BC +:108B5000074604F0F4F8800601D40820F3E7207CEF +:108B6000022140F00100207409A80094CDE9011011 +:108B7000072203A930466368FEF7A2FA20B1217CE0 +:108B800021F001012174DEE729463046F9F791FC16 +:108B900008A9384604F0C2F800B1FFDFBDF8204054 +:108BA000172C01D2172000E02046A84201D92C46FC +:108BB00002E0172C00D2172421463046FFF713FBA2 +:108BC00021463046F9F799F90020BCE7F8B51C4674 +:108BD00015460E46069F0BF09AFA2346FF1DBCB2BF +:108BE00031462A4600940AF086FEF8BD70B50C4660 +:108BF00005460E220021204605F049FC0020208079 +:108C00002DB1012D01D0FFDF64E4062000E0052036 +:108C1000A0715FE410B548800878134620F00F007B +:108C2000001D20F0F00080300C4608701422194618 +:108C300004F1080005F001FC00F0DBFC374804609B +:108C400010BD2DE9F047DFF8D890491D064621F008 +:108C5000030117460C46D9F800000AF062FF050030 +:108C600000D1FFDF4FF000083560A5F800802146F5 +:108C7000D9F800000AF055FF050000D1FFDF75604C +:108C8000A5F800807FB104FB07F1091D0BD0D9F8CE +:108C900000000AF046FF040000D1FFDFB460C4F812 +:108CA0000080BDE8F087C6F80880FAE72DE9F041BA +:108CB0001746491D21F00302194D06460168144666 +:108CC00028680AF059FF2246716828680AF054FFA4 +:108CD0003FB104FB07F2121D03D0B16828680AF007 +:108CE0004BFF04200BF08AF8044604200BF08EF8AA +:108CF000201A012804D12868BDE8F0410AF006BF17 +:108D0000BDE8F08110B50C4605F058F900B1FFDF61 +:108D10002046BDE81040FDF7DABF000094020020B5 +:108D20001800002038B50C468288817B19B1418932 +:108D3000914200D90A462280C188121D90B26A462B +:108D40000AF0B2F8BDF80000032800D30320C1B236 +:108D5000208801F020F838BD38B50C468288817B28 +:108D600019B10189914200D90A462280C188121D99 +:108D700090B26A460AF098F8BDF80000022800D3C5 +:108D80000220C1B2208801F006F8401CC0B238BDF4 +:108D90002DE9FF5F82468B46F74814460BF103022C +:108DA000D0E90110CDE9021022F0030201A84FF42E +:108DB000907101920AF097FEF04E002C02D1F0491A +:108DC000019A8A60019901440191B57F05F101057D +:108DD00004D1E8B20CF098FD00B1FFDF019800EB80 +:108DE0000510C01C20F0030101915CB9707AB27AC1 +:108DF0001044C2B200200870B08C80B204F03DFF75 +:108E000000B1FFDF0198716A08440190214601A872 +:108E100000F084FF80460198C01C20F00300019000 +:108E2000B37AF27A717A04B100200AF052FF019904 +:108E300008440190214601A800F0B8FFCF48002760 +:108E40003D4690F801900CE0284600F04AFF0646A7 +:108E500081788088F9F7E8F871786D1C00FB01775C +:108E6000EDB24D45F0D10198C01C20F003000190F7 +:108E700004B100203946F9F7E2F8019900270844C7 +:108E80000190BE483D4690F801900CE0284600F065 +:108E900028FF0646C1788088FEF71BFC71786D1CA0 +:108EA00000FB0177EDB24D45F0D10198C01C20F0D8 +:108EB0000300019004B100203946FEF713FC01992C +:108EC0004FF0000908440190AC484D4647780EE049 +:108ED000284600F006FF0646807B30B106F1080008 +:108EE00002F09CF9727800FB02996D1CEDB2BD4254 +:108EF000EED10198C01C20F00300019004B10020C5 +:108F00009F494A78494602F08DF901990844019039 +:108F1000214601A800F0B8FE0198C01D20F007000E +:108F20000190DAF80010814204D3A0EB0B01B1F5F7 +:108F3000803F04DB4FF00408CAF8000004E0CAF8E0 +:108F40000000B8F1000F03D0404604B0BDE8F09F28 +:108F500084BB8C490020019A0EF044FEFBF714FA02 +:108F6000864C207F0090607F012825D0002328B305 +:108F70000022824800211030F8F73AFA00B1FFDFF2 +:108F80007E49E07F2031FEF759FF00B1FFDF7B48CB +:108F90004FF4F6720021443005F079FA7748042145 +:108FA000443080F8E91180F8EA11062180F8EB11CD +:108FB000032101710020C8E70123D8E702AAD8E7FE +:108FC00070B56E4C06464434207804EB4015E078CA +:108FD000083598B9A01990F8E80100280FD0A078BA +:108FE0000F2800D3FFDF20220021284605F04FFA8A +:108FF000687866F3020068700120E070284670BD52 +:109000002DE9F04105460C460027007805219046E1 +:109010003E46B1EB101F00D0FFDF287A50B1012887 +:109020000ED0FFDFA8F800600CB12780668000201A +:10903000BDE8F0810127092674B16888A08008E0A6 +:109040000227142644B16888A0802869E060A88AB5 +:109050002082287B2072E5E7A8F80060E7E730B5BA +:10906000464C012000212070617020726072032242 +:10907000A272E07261772177217321740521218327 +:109080001F216183607440A161610A21A177E077AB +:1090900039483B4DB0F801102184C07884F8220093 +:1090A0004FF4B06060626868C11C21F00301814226 +:1090B00000D0FFDF6868606030BD30B5304C1568A7 +:1090C000636810339D4202D20420136030BD2B4BE5 +:1090D0005D785A6802EB0512107051700320D08041 +:1090E000172090800120D0709070002090735878E5 +:1090F000401C5870606810306060002030BD70B552 +:1091000006461E480024457807E0204600F0E9FDA9 +:109110000178B14204D0641CE4B2AC42F5D1002025 +:1091200070BDF7B5074608780C4610B3FFF7E7FFA8 +:109130000546A7F12006202F06D0052E19D2DFE81C +:1091400006F00F383815270000F0D6FD0DB169780C +:1091500000E00021401AA17880B20844FF2808D816 +:10916000A07830B1A088022831D202E060881728A8 +:109170002DD20720FEBD000030610200B0030020A8 +:109180001C000020000000206E52463578000000D0 +:10919000207AE0B161881729EBD3A1881729E8D399 +:1091A000A1790029E5D0E1790029E2D0402804D94D +:1091B000DFE7242F0BD1207A48B161884FF6FB708E +:1091C000814202D8A188814201D90420D2E765B941 +:1091D000207802AA0121FFF770FF0028CAD1207869 +:1091E000FFF78DFF050000D1FFDF052E18D2DFE865 +:1091F00006F0030B0E081100A0786870A088E880C4 +:109200000FE06088A8800CE0A078A87009E0A07842 +:10921000E87006E054F8020FA8606068E86000E0BB +:10922000FFDF0020A6E71A2835D00DDC132832D244 +:10923000DFE800F01B31203131272723252D313184 +:1092400029313131312F0F00302802D003DC1E28A4 +:1092500021D1072070473A3809281CD2DFE800F0F6 +:10926000151B0F1B1B1B1B1B07000020704743F225 +:109270000400704743F202007047042070470D203D +:1092800070470F2070470820704711207047132047 +:109290007047062070470320704710B5007800F033 +:1092A000010009F0F3FDBDE81040BCE710B50078FF +:1092B00000F0010009F0F3FDBDE81040B3E70EB582 +:1092C000017801F001018DF80010417801F00101F1 +:1092D0008DF801100178C1F340018DF8021041783A +:1092E000C1F340018DF80310017889088DF804104E +:1092F000417889088DF8051081788DF80610C178BD +:109300008DF8071000798DF80800684608F0FDFD1B +:10931000FFF789FF0EBD2DE9FC5FDFF8F883FE4CF7 +:1093200000264FF490771FE0012000F082FD01201D +:10933000FFF746FE05463946D8F808000AF0F1FB6B +:10934000686000B9FFDF686808F0AAFCB0B1284681 +:10935000FAF75EFB284600F072FD28B93A466968C4 +:10936000D8F808000AF008FC94F9E9010428DBDACF +:1093700002200AF043FD07460025AAE03A46696844 +:10938000D8F808000AF0F8FBF2E7B8F802104046F7 +:10939000491C89B2A8F80210B94201D300214180CA +:1093A0000221B8F802000AF081FD002866D0B8F862 +:1093B0000200694609F0CFFCFFF735FF00B1FFDF7F +:1093C0009DF80000019078B1B8F802000AF0B1FEF3 +:1093D0005FEA000900D1FFDF48460AF020F918B122 +:1093E000B8F8020002F0E4F9B8F802000AF08FFEC3 +:1093F0005FEA000900D1FFDF48460AF008F9E8BB40 +:109400000321B8F802000AF051FD5FEA000B4BD1CE +:10941000FFDF49E0DBF8100010B10078FF284DD0E5 +:10942000022000F006FD0220FFF7CAFD82464846F2 +:109430000AF0F9F9CAF8040000B9FFDFDAF804000D +:109440000AF0C1FA002100900170B8F802105046ED +:10945000AAF8021002F0B2F848460AF0B6FA00B9CB +:10946000FFDF019800B10126504600F0E8FC18B972 +:109470009AF80100000705D5009800E027E0CBF836 +:10948000100011E0DBF8101039B10878401C10F022 +:10949000FF00087008D1FFDF06E0002211464846B1 +:1094A00000F0F0FB00B9FFDF94F9EA01022805DBC8 +:1094B000B8F8020002F049F80028ABD194F9E901AC +:1094C000042804DB48460AF0E4FA00B101266D1CCA +:1094D000EDB2BD4204D294F9EA010228BFF655AFBD +:1094E000002E7FF41DAFBDE8FC5F032000F0A1BC9F +:1094F00010B5884CE06008682061AFF2E510F9F71C +:10950000E4FC607010BD844800214438017081483B +:10951000017082494160704770B505464FF0805038 +:109520000C46D0F8A410491C05D1D0F8A810C943A6 +:109530000904090C0BD050F8A01F01F0010129709B +:10954000416821608068A080287830B970BD06210C +:1095500020460DF0CCFF01202870607940F0C0005B +:10956000607170BD70B54FF080540D46D4F8801016 +:10957000491C0BD1D4F88410491C07D1D4F88810A9 +:10958000491C03D1D4F88C10491C0CD0D4F880109D +:109590000160D4F884104160D4F888108160D4F858 +:1095A0008C10C16002E010210DF0A1FFD4F89000F2 +:1095B000401C0BD1D4F89400401C07D1D4F898007B +:1095C000401C03D1D4F89C00401C09D054F8900FE3 +:1095D000286060686860A068A860E068E86070BDA6 +:1095E0002846BDE8704010210DF081BF4A4800793F +:1095F000E6E470B5484CE07830B3207804EB4010D6 +:10960000407A00F00700204490F9E801002800DCCF +:10961000FFDF2078002504EB4010407A00F00700BF +:10962000011991F8E801401E81F8E8012078401CFA +:10963000C0B220700F2800D12570A078401CA07007 +:109640000DF0D4FDE57070BDFFDF70BD3EB5054681 +:1096500003210AF02BFC044628460AF058FD054673 +:1096600004B9FFDF206918B10078FF2800D1FFDFBF +:1096700001AA6946284600F005FB60B9FFDF0AE051 +:10968000002202A9284600F0FDFA00B9FFDF9DF88C +:10969000080000B1FFDF9DF80000411E8DF80010AA +:1096A000EED220690199884201D1002020613EBD9F +:1096B00070B50546A0F57F400C46FF3800D1FFDFAE +:1096C000012C01D0FFDF70BDFFF790FF040000D137 +:1096D000FFDF207820F00F00401D20F0F000503018 +:1096E000207065800020207201202073BDE870404A +:1096F0007FE72DE9F04116460D460746FFF776FF56 +:10970000040000D1FFDF207820F00F00401D20F082 +:10971000F00005E01C000020F403002048140020A5 +:109720005030207067800120207228682061A8884E +:10973000A0822673BDE8F0415BE77FB5FFF7DFFC51 +:10974000040000D1FFDF02A92046FFF7EBFA05462F +:1097500003A92046FFF700FB8DF800508DF80100AB +:10976000BDF80800001DADF80200BDF80C00001D9A +:10977000ADF80400E088ADF80600684609F070FB1B +:10978000002800D0FFDF7FBD2DE9F05FFC4E814651 +:10979000307810B10820BDE8F09F4846F7F77FFD0C +:1097A00008B11020F7E7F74C207808B9FFF757FC0D +:1097B000A17A607A4D460844C4B200F09DFAA042F6 +:1097C00007D2201AC1B22A460020FFF776FC0028F3 +:1097D000E1D17168EB48C91C002721F003017160D9 +:1097E000B3463E463D46BA463C4690F801800AE004 +:1097F000204600F076FA4178807B0E4410FB01553C +:10980000641CE4B27F1C4445F2D10AEB870000EBF4 +:10981000C600DC4E00EB85005C46F17A012200EBCD +:109820008100DBF80410451829464846FFF7B0FAD6 +:10983000070012D00020FFF762FC05000BD005F1F5 +:109840001300616820F00300884200D0FFDF7078C9 +:10985000401E7070656038469DE7002229464846E4 +:10986000FFF796FA00B1FFDFD9F8000060604FF60D +:10987000FF7060800120207000208CE72DE9F0410E +:109880000446BF4817460D46007810B10820BDE8D1 +:10989000F0810846F7F7DDFC08B11020F7E7B94E74 +:1098A000307808B9FFF7DBFB601E1E2807D8012CB3 +:1098B00023D12878FE2820D8B0770020E7E7A4F14C +:1098C00020001F2805D8E0B23A462946BDE8F041FD +:1098D00027E4A4F140001F2805D829462046BDE80A +:1098E000F04100F0D4BAA4F1A0001F2805D8294601 +:1098F0002046BDE8F04100F006BB0720C7E72DE990 +:10990000F05F81460F460846F7F7C9FC48B948465C +:10991000F7F7E3FC28B909F1030020F003014945FA +:1099200001D0102037E797484FF0000B4430817882 +:1099300069B14178804600EB411408343E883A46CC +:109940000021204600F089FA050004D027E0A7F89E +:1099500000B005201FE7B9F1000F24D03888B042CD +:1099600001D90C251FE0607800F00700824600F066 +:1099700060FA08EB0A063A4696F8E8014946401CA8 +:1099800086F8E801204600F068FA054696F8E801F6 +:10999000401E86F8E801032000F04BFA2DB10C2D93 +:1099A00001D0A7F800B02846F5E6754F5046BAF149 +:1099B000010F25D002280DD0BAF1030F35D0FFDFFB +:1099C00098F801104046491CC9B288F801100F29C7 +:1099D00037D038E0606828B16078000702D460882A +:1099E000FFF734FE98F8EA014446012802D178785E +:1099F000F9F78AFA94F9EA010428E1DBFFDFDFE7EF +:109A0000616821B14FF49072B8680AF0B5F898F81F +:109A1000E9014446032802D17878F9F775FA94F9F8 +:109A2000E9010428CCDBFFDFCAE76078C00602D575 +:109A30006088FFF70BFE98F9EB010628C0DBFFDF1B +:109A4000BEE780F801B08178491E88F8021096F8C8 +:109A5000E801401C86F8E801A5E770B50C4605460C +:109A6000F7F7F7FB18B92046F7F719FC08B11020F3 +:109A700070BD28460BF07FFF207008B1002070BD3C +:109A8000042070BD70B505460BF08EFFC4B22846A9 +:109A9000F7F723FC08B1102070BD35B128782C7081 +:109AA00018B1A04201D0072070BD2046FDF77EFE10 +:109AB000052805D10BF07BFF012801D0002070BDE7 +:109AC0000F2070BD70B5044615460E460846F7F7E0 +:109AD000C0FB18B92846F7F7E2FB08B1102070BDAB +:109AE000022C03D0102C01D0092070BD2A4631462B +:109AF00020460BF086FF0028F7D0052070BD70B51A +:109B000014460D460646F7F7A4FB38B92846F7F782 +:109B1000C6FB18B92046F7F7E0FB08B1102070BD6E +:109B20002246294630460BF06EFF0028F7D007206A +:109B300070BD3EB50446F7F7B2FB08B110203EBD3C +:109B4000684608F053F9FFF76EFB0028F7D19DF83F +:109B500006002070BDF808006080BDF80A00A080F3 +:109B600000203EBD70B505460C460846F7F7B5FB2C +:109B700020B95CB12068F7F792FB28B1102070BDC6 +:109B80001C000020B0030020A08828B121462846F0 +:109B9000BDE87040FDF762BE0920F0E770B50546EC +:109BA0000C460846F7F755FBA0BB681E1E280ED8CA +:109BB000032D01D90720E2E705B9FFDFFE4800EBDE +:109BC000850050F8041C2046BDE870400847A5F108 +:109BD00020001F2805D821462846BDE87040FAF726 +:109BE00042BBA5F160001F2805D821462846BDE8E4 +:109BF0007040F8F7DABCF02D0DD0F12D15D0BF2D47 +:109C0000D8D1A078218800F0010001F08DFB98B137 +:109C10000020B4E703E0A068F7F71BFB08B11020B1 +:109C2000ADE7204609F081F902E0207809F0A0F9BB +:109C3000BDE87040FFF7F7BA0820A0E770B504460A +:109C40000D460846F7F72BFB30B9601E1E280FD8CB +:109C50002846F7F7FEFA08B1102090E7012C03D050 +:109C6000022C01D0032C01D1062088E7072086E7CB +:109C7000A4F120001F28F9D829462046BDE87040ED +:109C8000FAF762BB09F092BC38B50446CB48007BBA +:109C900000F00105F9B904F01DFC0DB1226800E0E7 +:109CA0000022C7484178C06807F06DFDC4481030F5 +:109CB000C0788DF8000010B1012802D004E0012026 +:109CC00000E000208DF80000684608F0FFF8BA4870 +:109CD000243808F0B5FE002D02D02068283020601E +:109CE00038BD30B5B54D04466878A04200D8FFDFD6 +:109CF000686800EB041030BD70B5B04800252C46F4 +:109D0000467807E02046FFF7ECFF4078641C2844C3 +:109D1000C5B2E4B2B442F5D1284630E72DE9F041AE +:109D20000C4607464FF0000800F01FF90646FF28D2 +:109D300001D94FF013083868C01C20F003023A60C4 +:109D400054EA080421D19D48F3B2072128300DF0D0 +:109D5000DBFD09E0072C10D2DFE804F00604080858 +:109D60000A040600974804E0974802E0974800E09C +:109D700097480DF0E9FD054600E0FFDFA54200D061 +:109D8000FFDF641CE4B2072CE4D3386800EB061054 +:109D9000386040467BE5021D5143452900D24521EC +:109DA0000844C01CB0FBF2F0C0B270472DE9FC5F64 +:109DB000064682484FF000088B464746444690F8D6 +:109DC000019022E02046FFF78CFF050000D1FFDF65 +:109DD000687869463844C7B22846FEF7A3FF824632 +:109DE00001A92846FEF7B8FF0346BDF80400524615 +:109DF000001D81B2BDF80000001D80B20AF0D4F849 +:109E00006A78641C00FB0288E4B24C45DAD1306801 +:109E1000C01C20F003003060BBF1000F00D0002018 +:109E2000424639460AF0CEF8316808443060BDE851 +:109E3000FC9F6249443108710020C87070475F4937 +:109E40004431CA782AB10A7801EB421108318142C3 +:109E500001D001207047002070472DE9F0410646EF +:109E60000078154600F00F0400201080601E0F4699 +:109E7000052800D3FFDF50482A46183800EB84003D +:109E8000394650F8043C3046BDE8F04118472DE90A +:109E9000F0414A4E0C46402806D0412823D04228A3 +:109EA0002BD0432806D123E0A07861780D18E17803 +:109EB000814201D90720EAE42078012801D9132042 +:109EC000E5E4FF2D08D80BF009FF07460DF046F931 +:109ED000381A801EA84201DA1220D8E42068B06047 +:109EE000207930730DE0BDE8F041084600F078B805 +:109EF00008780228DED8307703E008780228D9D81D +:109F000070770020C3E4F8B500242C4DA02805D0BC +:109F1000A12815D0A22806D00720F8BD087800F0A7 +:109F20000100E8771FE00E4669463046FDF73DFD2B +:109F30000028F2D130882884B07885F8220012E019 +:109F400008680921F82801D3820701D00846F8BD26 +:109F50006A7C02F00302012A04D16A8BD73293B2E1 +:109F60008342F3D868622046F8BD2DE9F047DFF858 +:109F70004C900026344699F8090099F80A2099F87F +:109F800001700244D5B299F80B20104400F0FF088C +:109F900008E02046FFF7A5FE817B407811FB0066B4 +:109FA000641CE4B2BC42F4D199F8091099F80A0093 +:109FB0002944294441440DE054610200B0030020CB +:109FC0001C0000206741000045B30000DD2F0000A9 +:109FD000FB56010000B1012008443044BDE8F08781 +:109FE00038B50446407800F00300012803D0022869 +:109FF0000BD0072038BD606858B1F7F777F9D0B9B2 +:10A000006068F7F76AF920B915E06068F7F721F999 +:10A0100088B969462046FCF729F80028EAD160781B +:10A0200000F00300022808D19DF8000028B1606804 +:10A03000F7F753F908B1102038BD6189F8290DD818 +:10A04000208988420AD8607800F003020A48012A71 +:10A0500006D1D731426A89B28A4201D2092038BD7D +:10A0600094E80E0000F1100585E80E000AB9002101 +:10A070000183002038BD0000B00300202DE9F0412D +:10A08000074614468846084601F08AFD064608EB56 +:10A0900088001C22796802EBC0000D18688C58B14A +:10A0A0004146384601F08BFD014678680078C200D1 +:10A0B000082305F120000CE0E88CA8B141463846A1 +:10A0C00001F084FD0146786808234078C20005F15C +:10A0D000240009F0A8FD38B1062121726681D0E97B +:10A0E0000010C4E9031009E0287809280BD00520E6 +:10A0F000207266816868E060002028702046BDE814 +:10A10000F04101F02EBD072020726681F4E72DE9B1 +:10A11000F04116460D460746406801EB85011C22BA +:10A1200002EBC1014418204601F072FD40B100214C +:10A13000708865F30F2160F31F4106200DF0BEFC0F +:10A1400009202070324629463846BDE8F04195E79F +:10A150002DE9F0410E46074600241C21F07816E058 +:10A1600004EB8403726801EBC303D25C6AB1FFF7AE +:10A170003DFA050000D1FFDF6F802A4621463046B8 +:10A18000FFF7C5FF0120BDE8F081641CE4B2A042E6 +:10A19000E6D80020F7E770B5064600241C21C078F9 +:10A1A0000AE000BF04EB8403726801EBC303D51817 +:10A1B0002A782AB1641CE4B2A042F3D8402070BDD2 +:10A1C00028220021284604F062F9706880892881DD +:10A1D000204670BD70B5034600201C25DC780CE0DD +:10A1E00000EB80065A6805EBC6063244167816B1B5 +:10A1F000128A8A4204D0401CC0B28442F0D8402067 +:10A2000070BDF0B5044600201C26E5780EE000BFC6 +:10A2100000EB8007636806EBC7073B441F788F425B +:10A2200002D15B78934204D0401CC0B28542EFD883 +:10A230004020F0BD0078032801D0002070470120A5 +:10A2400070470078022801D0002070470120704735 +:10A250000078072801D000207047012070472DE9C1 +:10A26000F041064688461078F1781546884200D3BA +:10A27000FFDF2C781C27641CF078E4B2A04201D8E0 +:10A28000201AC4B204EB8401706807EBC1010844D2 +:10A29000017821B14146884708B12C7073E72878CE +:10A2A000A042E8D1402028706DE770B514460B88B5 +:10A2B0000122A240134207D113430B8001230A223B +:10A2C000011D09F07AFC047070BD2DE9FF4F81B0CB +:10A2D0000878DDE90E7B9A4691460E4640072CD45D +:10A2E000019809F026FF040000D1FFDF07F1040800 +:10A2F00020461FFA88F109F065F8050000D1FFDF5C +:10A30000204629466A4609F0B0FA0098A0F8037082 +:10A31000A0F805A0284609F056FB017869F306016C +:10A320006BF3C711017020461FFA88F109F08DF810 +:10A3300000B9FFDF019807F094F906EB0900017FEF +:10A34000491C017705B0BDE8F08F2DE9F84F0E46A6 +:10A350009A4691460746032109F0A8FD0446008D60 +:10A36000DFF8B885002518B198F80000B0421ED17A +:10A37000384609F0DEFE070000D1FFDF09F10401D5 +:10A38000384689B209F01EF8050010D03846294633 +:10A390006A4609F06AFA009800210A460180817035 +:10A3A00007F01CFA0098C01DCAF8000021E098F8D8 +:10A3B0000000B04216D104F1260734F8341F012002 +:10A3C00000FA06F911EA090F00D0FFDF2088012307 +:10A3D00040EA090020800A22391D384609F008FCAD +:10A3E000067006E0324604F1340104F12600FFF75E +:10A3F0005CFF0A2188F800102846BDE8F88FFEB5FA +:10A4000015460C46064602AB0C220621FFF79DFFBF +:10A41000002827D00299607812220A70801C4870A8 +:10A4200008224A80A07002982988052381806988C3 +:10A43000C180A9880181E988418100250C20CDE9EE +:10A440000005062221463046FFF73FFF294600223D +:10A4500066F31F41F02310460DF086FA6078801CE9 +:10A4600060700120FEBDFEB514460D46062206466C +:10A4700002AB1146FFF769FF002812D0029B1320A0 +:10A4800000211870A8785870022058809C800620FF +:10A49000CDE900010246052329463046FFF715FFA6 +:10A4A0000120FEBD2DE9FE430C46804644E002AB90 +:10A4B0000E2207214046FFF748FF002841D0606880 +:10A4C0001C2267788678BF1C06EB860102EBC1016F +:10A4D000451802981421017047700A214180698A49 +:10A4E0000181E98A4181A9888180A98981813046D9 +:10A4F00001F056FB029905230722C8806F700420E3 +:10A50000287000250E20CDE9000521464046FFF7C2 +:10A51000DCFE294666F30F2168F31F41F023002279 +:10A5200006200DF021FA6078FD49801C6070626899 +:10A530002046921CFFF793FE606880784028B6D1D1 +:10A540000120BDE8FE83FEB50D46064638E002ABAD +:10A550000E2207213046FFF7F8FE002835D0686844 +:10A560001C23C17801EB810203EBC202841802981C +:10A5700015220270627842700A224280A2894281CA +:10A58000A2888281084601F00BFB01460298818077 +:10A59000618AC180E18A0181A088B8B10020207061 +:10A5A00000210E20CDE9000105230722294630466F +:10A5B000FFF78BFE6A68DB492846D21CFFF74FFE87 +:10A5C0006868C0784028C2D10120FEBD0620E6E7B9 +:10A5D0002DE9FE430C46814644E0204601F002FB93 +:10A5E000D0B302AB082207214846FFF7AEFE002891 +:10A5F000A7D060681C2265780679AD1C06EB860141 +:10A6000002EBC10147180298B7F8108006210170CB +:10A61000457004214180304601F0C2FA014602989B +:10A6200005230722C180A0F804807D7008203870BF +:10A630000025CDE9000521464846FFF746FE29469C +:10A6400066F30F2169F31F41F023002206200DF06D +:10A650008BF96078801C60706268B3492046121DD7 +:10A66000FFF7FDFD606801794029B6D1012068E758 +:10A670002DE9F34F83B00D4691E0284601F0B2FA80 +:10A6800000287DD068681C2290F806A00AEB8A0199 +:10A6900002EBC10144185146284601F097FAA1780F +:10A6A000CB0069684978CA00014604F1240009F02A +:10A6B000D6FA07468188E08B4FF00009091A8EB25E +:10A6C00008B1C84607E04FF00108504601F053FAC0 +:10A6D00008B9B61CB6B2208BB04200D80646B346C5 +:10A6E00002AB324607210398FFF72FFE060007D082 +:10A6F000B8F1000F0BD0504601F03DFA10B106E062 +:10A7000000201FE60299B8884FF0020908800196E0 +:10A71000E28B3968ABEB09001FFA80F80A44039812 +:10A720004E46009209F005FDDDE90021F61D434685 +:10A73000009609F014F9E08B404480B2E083B988B8 +:10A74000884201D1012600E00026CDE900B6238A27 +:10A75000072229460398FFF7B8FD504601F00BFA8F +:10A7600010B9E089401EE08156B1A078401CA0706D +:10A770006868E978427811FB02F1CAB2012300E06F +:10A7800007E081690E3009F018FA80F800A0002077 +:10A79000E0836A6865492846921DFFF760FD686896 +:10A7A000817940297FF469AF0120CBE570B5064679 +:10A7B00048680D4614468179402910D104EB840184 +:10A7C0001C2202EBC101084401F043FA002806D024 +:10A7D0006868294684713046BDE8704048E770BD1E +:10A7E000FEB50C460746002645E0204601F0FAF982 +:10A7F000D8B360681C22417901EB810102EBC101F1 +:10A800004518688900B9FFDF02AB082207213846E6 +:10A81000FFF79BFD002833D00299607816220A705A +:10A82000801C4870042048806068407901F0B8F9C5 +:10A83000014602980523072281806989C18008208A +:10A84000CDE9000621463846FFF73FFD6078801CC1 +:10A850006070A88969890844B0F5803F00D3FFDFA4 +:10A86000A88969890844A8816E81626830492046B8 +:10A87000521DFFF7F4FC606841794029B5D10120F1 +:10A88000FEBD30B5438C458BC3F3C704002345B1EF +:10A89000838B641EED1AC38A6D1E1D4495FBF3F372 +:10A8A000E4B22CB1008918B1A04200D8204603447C +:10A8B0004FF6FF70834200D3034613800C7030BD07 +:10A8C0002DE9FC41074616460D46486802EB860115 +:10A8D0001C2202EBC10144186A4601A92046FFF779 +:10A8E000D0FFA089618901448AB2BDF8001091426D +:10A8F00012D0081A00D5002060816868407940288D +:10A900000AD1204601F09BF9002805D06868294645 +:10A9100046713846FFF764FFBDE8FC813000002037 +:10A9200035A2000043A2000051A2000053BC000069 +:10A930003FBC00002DE9FE4F0F468146154650886A +:10A94000032109F0B3FA0190B9F8020001F01BF9F4 +:10A9500082460146019801F045F9002824D001986B +:10A960001C2241680AEB8A0002EBC0000C1820464A +:10A9700001F04EF9002817D1B9F80000E18A8842A9 +:10A980000ED8A18961B1B8420ED100265146019876 +:10A9900001F015F9218C01EB0008608B30B114E057 +:10A9A000504601F0E8F8A0B3BDE8FE8F504601F034 +:10A9B000E2F808B1678308E0022FF5D3B9F8040084 +:10A9C0006083618A884224D80226B81B87B2B8F80F +:10A9D0000400A28B801A002814DD874200DA384672 +:10A9E0001FFA80FB688869680291D8F800100A4451 +:10A9F000009209F08CFBF61D009A5B4602990096C6 +:10AA000008F079FFA08B384480B2A083618B884224 +:10AA100007D96888019903B05246BDE8F04F01F0AC +:10AA200035B91FD14FF009002872B9F802006881CA +:10AA3000D8E90010C5E90410608BA881284601F010 +:10AA400090F85146019801F0BAF8014601980823A0 +:10AA500040680078C20004F1200009F0E4F800200A +:10AA6000A0836083504601F086F810B9A089401E8B +:10AA7000A0816888019903B00AF0FF02BDE8F04F99 +:10AA80001EE72DE9F041064615460F461C461846BE +:10AA9000F6F7DFFB18B92068F6F701FC10B11020BB +:10AAA000BDE8F0817168688C0978B0EBC10F01D303 +:10AAB0001320F5E73946304601F081F80146706809 +:10AAC00008230078C20005F1200009F076F8D4E9E7 +:10AAD0000012C0E900120020E2E710B5044603218D +:10AAE00009F0E4F90146007800F00300022805D0DF +:10AAF0002046BDE8104001F1140280E48A8A204615 +:10AB0000BDE81040AFE470B50446032109F0CEF96A +:10AB1000054601462046FFF75BFD002816D0294672 +:10AB20002046FFF75DFE002810D029462046FFF79B +:10AB30000AFD00280AD029462046FFF7B3FC00286A +:10AB400004D029462046BDE8704091E570BD2DE94E +:10AB5000F0410C4680461EE0E178427811FB02F19C +:10AB6000CAB2816901230E3009F05DF80778606888 +:10AB70001C22C179491EC17107EB8701606802EB95 +:10AB8000C10146183946204601F02CF818B130466C +:10AB900001F037F820B16068C1790029DCD17FE786 +:10ABA000FEF724FD050000D1FFDF0A202872384699 +:10ABB00000F0F6FF68813946204601F007F80146AB +:10ABC000606808234078C20006F1240009F02BF8E1 +:10ABD000D0E90010C5E90310A5F80280284600F06E +:10ABE000C0FFB07800B9FFDFB078401EB07057E703 +:10ABF00070B50C460546032109F058F90146406836 +:10AC0000C2792244C2712846BDE870409FE72DE911 +:10AC1000FE4F8246507814460F464FF00008002839 +:10AC20004FD0012807D0022822D0FFDF2068B8606B +:10AC30006068F860B8E602AB0E2208215046FFF7C4 +:10AC400084FB0028F2D00298152105230170217899 +:10AC500041700A214180C0F80480C0F80880A0F843 +:10AC60000C80628882810E20CDE90008082221E054 +:10AC7000A678304600F094FF054606EB86012C22AC +:10AC8000786802EBC1010822465A02AB11465046D1 +:10AC9000FFF75BFB0028C9D00298072101702178DB +:10ACA00041700421418008218580C680CDE90018CB +:10ACB00005230A4639465046FFF707FB87F8088008 +:10ACC00072E6A678022516B1022E13D0FFDF2A1DE8 +:10ACD000914602AB08215046FFF737FB0028A5D06C +:10ACE00002980121022E01702178417045808680F2 +:10ACF00002D005E00625EAE7A188C180E18801814C +:10AD0000CDE900980523082239465046D4E710B50E +:10AD10000446032109F0CAF8014600F10802204662 +:10AD2000BDE8104073E72DE9F04F0F4605468DB0A2 +:10AD300014465088032109F0B9F84FF000088DF847 +:10AD400014800646ADF81680042F7BD36A78002A5B +:10AD500078D028784FF6FF794FF01C0A132834D0AA +:10AD600008DC012871D006284AD007286ED01228A6 +:10AD70000ED106E014286AD0152869D0162807D10C +:10AD8000AAE10C2F04D1307800F00301022907D08A +:10AD9000CDF80880CDF80C8068788DF808004CE07C +:10ADA00040F0080030706878B07001208DF8140011 +:10ADB000A888ADF81800E888ADF81A002889ADF821 +:10ADC0001C006889ADF81E0011E1B078904239D1BD +:10ADD0003078010736D5062F34D120F008003070C6 +:10ADE0006088414660F31F4100200CF067FE02209E +:10ADF0008DF81400ADF81890A888ADF81A00F6E0A8 +:10AE0000082F1FD1A888EF88814600F0BCFE80463D +:10AE10000146304600F0E6FE18B1404600F0ABFEB9 +:10AE2000B8B1FC48D0E90010CDE902106878ADF85F +:10AE30000C908DF80800ADF80E70608802AA3146BB +:10AE4000FFF7E5FE0DB0BDE8F08FB6E01EE041E093 +:10AE5000ECE0716808EB88002C2202EBC000085A75 +:10AE6000B842EFD1EB4802AAD0E90210CDE90210B6 +:10AE700068788DF8080008F0FF058DF80A506088A2 +:10AE80003146FFF7C4FE224629461FE0082FD9D1DC +:10AE9000B5F80480E88800F076FE074601463046A3 +:10AEA00000F0A0FE0028CDD007EB870271680AEB06 +:10AEB000C2000844028A4245C4D101780829C1D1A0 +:10AEC000407869788842BDD1F9B222463046FFF712 +:10AED0001EF9B7E70E2F7FF45BAFE9886F898B46C9 +:10AEE000B5F808903046FFF775F9ABF140014029FD +:10AEF00001D309204AE0B9F1170F01D3172F01D26E +:10AF00000B2043E040280ED000EB800271680AEB72 +:10AF1000C20008440178012903D140786978884249 +:10AF200090D00A2032E03046FFF735F9014640283C +:10AF30002BD001EB810372680AEBC30002EB00081F +:10AF4000012288F800206A7888F801207068AA88B1 +:10AF50004089B84200D93846AD8903232372A282C2 +:10AF6000E7812082A4F80C906582084600F018FE64 +:10AF70006081A8F81490A8F81870A8F80E50A8F8E6 +:10AF800010B0204600F0EDFD5CE7042005212172A1 +:10AF9000A4F80A80E081012121739E49D1E90421AE +:10AFA000CDE9022169788DF80810ADF80A006088B3 +:10AFB00002AA3146FFF72BFEE3E7062F89D3B078CC +:10AFC00090421AD13078010717D520F00800307070 +:10AFD0006088414660F31F4100200CF06FFD0220A5 +:10AFE0008DF81400A888ADF81800ADF81A906088A4 +:10AFF000224605A9F9F7E3F824E704213046FFF7D4 +:10B0000000F905464028BFD0022083030090224665 +:10B010002946304600F003FE4146608865F30F2163 +:10B0200060F31F4106200CF049FD0BE70E2FABD15A +:10B0300004213046FFF7E5F881464028A4D0414678 +:10B04000608869F30F2160F31F4106200CF036FD84 +:10B05000A8890B906889099070682F894089B84247 +:10B0600000D938468346B5F80680A8880A90484635 +:10B0700000F096FD60810B9818B1022000900B9BA8 +:10B0800024E0B8F1170F1ED3172F1CD30420207211 +:10B0900009986082E781A4F810B0A4F80C8009EB4D +:10B0A000890271680AEBC2000D18DDE90913A5F8E1 +:10B0B0001480A5F818B0E9812B82204600F051FDDC +:10B0C00006202870BEE601200B2300902246494648 +:10B0D000304600F0A4FDB5E6082F8DD1A988304692 +:10B0E000FFF778F80746402886D000F044FD002896 +:10B0F0009BD107EB870271680AEBC20008448046C7 +:10B1000000F086FD002890D1ED88B8F80E002844A4 +:10B11000B0F5803F05D360883A46314600F0B6FD71 +:10B1200090E6002DCED0A8F80E0060883A46314651 +:10B13000FFF73CFB08202072384600F031FD6081AB +:10B14000A5811EE72DE9F05F0C4601281FD09579F7 +:10B1500092F8048092F8056005EB85011F2202EB4E +:10B16000C10121F0030B08EB060111FB05F14FF6BD +:10B17000FF7202EAC10909F1030115FB0611264F0E +:10B1800021F0031ABB6840B101283ED125E0616877 +:10B19000E57891F800804E78DEE75946184608F0C9 +:10B1A000C0FC606000B9FFDF5A460021606803F010 +:10B1B0006EF9E5705146B86808F0B3FC6168486103 +:10B1C00000B9FFDF6068426902EB090181616068D4 +:10B1D00080F800806068467017E0606852464169F8 +:10B1E000184608F0C9FC5A466168B86808F0C4FC03 +:10B1F000032008F003FE0446032008F007FE201A8F +:10B20000012802D1B86808F081FC0BEB0A00BDE808 +:10B21000F09F000060610200300000200246002123 +:10B2200002208FE7F7B5FF4C0A20164620700098E1 +:10B2300060B100254FEA0D0008F055FC0021A17017 +:10B240006670002D01D10099A160FEBD012500208E +:10B25000F2E770B50C46154638220021204603F06F +:10B2600016F9012666700A22002104F11C0003F081 +:10B270000EF905B9FFDF297A207861F3010020700B +:10B28000A87900282DD02A4621460020FFF75AFF32 +:10B2900061684020E34A88706168C870616808711D +:10B2A000616848716168887161682888088161688F +:10B2B00068884881606886819078002811D061682C +:10B2C0000620087761682888C885616828884886CC +:10B2D00060680685606869889288018681864685EF +:10B2E000828570BDC878002802D00022012029E79D +:10B2F000704770B50546002165F31F4100200CF032 +:10B30000DDFB0321284608F0D1FD040000D1FFDF5A +:10B3100021462846FEF71CFF002804D0207840F084 +:10B3200010002070012070BD70B505460C4603204A +:10B3300008F056FD08B1002070BDBA4885708480C1 +:10B34000012070BD2DE9FF4180460E460F0CFEF72F +:10B350004DF9050007D06F800321384608F0A6FD9F +:10B36000040008D106E004B03846BDE8F0411321DE +:10B37000F9F750BBFFDF5FEA080005D0B8F1060F10 +:10B3800018D0FFDFBDE8FF8120782A4620F00800B2 +:10B3900020700020ADF8020002208DF800004FF66A +:10B3A000FF70ADF80400ADF8060069463846F8F7BE +:10B3B00006FFE7E7C6F3072101EB81021C23606863 +:10B3C00003EBC202805C042803D008280AD0FFDF08 +:10B3D000D8E7012000904FF440432A46204600F071 +:10B3E0001EFCCFE704B02A462046BDE8F041FEF738 +:10B3F0008EBE2DE9F05F05464089002790460C4639 +:10B400003E46824600F0BFFB8146287AC01E0828CF +:10B410006BD2DFE800F00D04192058363C47722744 +:10B420001026002C6CD0D5E90301C4E902015CE0D0 +:10B4300070271226002C63D00A2205F10C0104F1BA +:10B44000080002F0FAFF50E071270C26002C57D0BC +:10B45000E868A06049E0742710269CB3D5E9030191 +:10B46000C4E902016888032108F020FD8346FEF745 +:10B47000BDF802466888508049465846FEF7FEFDF2 +:10B4800033E075270A26ECB1A88920812DE07627C4 +:10B490001426BCB105F10C0004F1080307C883E8C9 +:10B4A000070022E07727102664B1D5E90301C4E93B +:10B4B00002016888032108F0F9FC01466888FFF75B +:10B4C00046FB12E01CE073270826CCB168880321F4 +:10B4D00008F0ECFC01460078C00606D56888FEF747 +:10B4E00037FE10B96888F8F777FAA8F800602CB131 +:10B4F0002780A4F806A066806888A080002086E6E1 +:10B50000A8F80060FAE72DE9FC410C461E461746F4 +:10B510008046032108F0CAFC05460A2C0AD2DFE85F +:10B5200004F005050505050509090907042303E0DD +:10B53000062301E0FFDF0023CDE9007622462946FD +:10B540004046FEF7C2FEBDE8FC81F8B50546A0F511 +:10B550007F40FF382BD0284608F0D9FD040000D1E9 +:10B56000FFDF204608F05FF9002821D001466A4637 +:10B57000204608F07AF900980321B0F805602846C3 +:10B5800008F094FC0446052E13D0304600F0FBFA78 +:10B5900005460146204600F025FB40B1606805EBFA +:10B5A00085013E2202EBC101405A002800D0012053 +:10B5B000F8BD007A0028FAD00020F8BDF8B504469E +:10B5C000408808F0A4FD050000D1FFDF6A46284648 +:10B5D000616800F0C4FA01460098091F8BB230F888 +:10B5E000032F0280428842800188994205D1042AB3 +:10B5F00008D0052A20D0062A16D022461946FFF781 +:10B6000099F9F8BD001D0E46054601462246304612 +:10B61000F6F739FF0828F4D1224629463046FCF7D0 +:10B6200064F9F8BD30000020636864880A46011D93 +:10B630002046FAF789F9F4E72246001DFFF773FB6D +:10B64000EFE770B50D460646032108F02FFC040015 +:10B6500004D02078000704D5112070BD43F2020009 +:10B6600070BD2A4621463046FEF7C9FE18B9286843 +:10B6700060616868A061207840F0080020700020B8 +:10B6800070BD70B50D460646032108F00FFC04009E +:10B6900004D02078000704D4082070BD43F20200D3 +:10B6A00070BD2A4621463046FEF7DDFE00B9A58270 +:10B6B000207820F008002070002070BD2DE9F04FA8 +:10B6C0000E4691B08046032108F0F0FB0446404648 +:10B6D00008F02FFD07460020079008900990ADF86C +:10B6E00030000A9002900390049004B9FFDF0DF13E +:10B6F0000809FFB9FFDF1DE038460BA9002207F05B +:10B7000055FF9DF82C0000F07F050A2D00D3FFDFC8 +:10B710006019017F491E01779DF82C00000609D5AC +:10B720002A460CA907A8FEF7C0FD19F80510491C08 +:10B7300009F80510761EF6B2DED204F13400F84D99 +:10B7400004F1260BDFF8DCA304F12A07069010E0D1 +:10B750005846069900F08CFA064628700A2800D34D +:10B76000FFDF5AF8261040468847E08CC05DB042A3 +:10B7700002D0208D0028EBD10A202870E94D4E46DA +:10B7800028350EE00CA907A800F072FA0446375DD0 +:10B7900055F8240000B9FFDF55F82420394640460B +:10B7A0009047BDF81E000028ECD111B0BDE8F08F25 +:10B7B00010B5032108F07AFB040000D1FFDF0A2254 +:10B7C000002104F11C0002F062FE207840F0040029 +:10B7D000207010BD10B50C46032108F067FB204413 +:10B7E000007F002800D0012010BD2DE9F84F8946C8 +:10B7F00015468246032108F059FB070004D028466D +:10B80000F5F727FD40B903E043F20200BDE8F88FE9 +:10B810004846F5F744FD08B11020F7E7786828B1ED +:10B8200069880089814201D90920EFE7B9F8000051 +:10B830001C2488B100F0A7F980460146384600F084 +:10B84000D1F988B108EB8800796804EBC000085C86 +:10B8500001280BD00820D9E73846FEF79CFC80462B +:10B86000402807D11320D1E70520CFE7FDF7BEFE22 +:10B8700006000BD008EB8800796804EBC0000C18B8 +:10B88000B9F8000020B1E88910B113E01120BDE73C +:10B890002888172802D36888172801D20720B5E71F +:10B8A000686838B12B1D224641463846FFF7E9F853 +:10B8B0000028ABD104F10C0269462046FEF7E1FFF7 +:10B8C000288860826888E082B9F8000030B10220E0 +:10B8D0002070E889A080E889A0B12BE003202070C7 +:10B8E000A889A08078688178402905D180F80280F5 +:10B8F00039465046FEF7D6FD404600F051F9A9F80A +:10B90000000021E07868218B4089884200D90846F0 +:10B910002083A6F802A004203072B9F800007081DC +:10B92000E0897082F181208B3082A08AB08130461C +:10B9300000F017F97868C178402905D180F80380B4 +:10B9400039465046FEF7FFFD00205FE770B50D4613 +:10B950000646032108F0AAFA04000ED0284600F09B +:10B9600012F905460146204600F03CF918B1284678 +:10B9700000F001F920B1052070BD43F2020070BD56 +:10B9800005EB85011C22606802EBC101084400F050 +:10B990003FF908B1082070BD2A462146304600F024 +:10B9A00075F9002070BD2DE9F0410C461746804620 +:10B9B000032108F07BFA0546204600F0E4F804462F +:10B9C00095B10146284600F00DF980B104EB8401E1 +:10B9D0001C22686802EBC1014618304600F018F9D5 +:10B9E00038B10820BDE8F08143F20200FAE70520F3 +:10B9F000F8E73B46324621462846FFF742F8002842 +:10BA0000F0D1E2B229464046FEF75AFF708C083862 +:10BA1000082803D242484078F7F776FA0020E1E799 +:10BA20002DE9F0410D4617468046032108F03EFA05 +:10BA30000446284600F0A7F8064624B13846F5F734 +:10BA400008FC38B902E043F20200CBE73868F5F7AA +:10BA500000FC08B11020C5E73146204600F0C2F8CE +:10BA600060B106EB86011C22606802EBC10145183B +:10BA7000284600F0CDF818B10820B3E70520B1E75B +:10BA8000B888A98A884201D90C20ABE76168E88CA4 +:10BA90004978B0EBC10F01D31320A3E7314620460C +:10BAA00000F094F80146606808234078C20005F170 +:10BAB000240008F082F8D7E90012C0E90012F2B2BF +:10BAC00021464046FEF772FE00208BE72DE9F04745 +:10BAD0000D461F4690468146032108F0E7F90446CB +:10BAE000284600F050F806463CB14DB13846F5F70F +:10BAF000F4FB50B11020BDE8F08743F20200FAE7F2 +:10BB0000606858B1A0F80C8027E03146204600F06C +:10BB100069F818B1304600F02EF828B10520EAE7A0 +:10BB2000300000207861020006EB86011C2260686C +:10BB300002EBC1014518284600F06AF808B1082058 +:10BB4000D9E7A5F80880F2B221464846FEF7B8FECC +:10BB50001FB1A8896989084438800020CBE707F025 +:10BB600084BE017821F00F01491C21F0F001103151 +:10BB70000170FDF73EBD20B94E48807808B1012024 +:10BB80007047002070474B498988884201D10020C6 +:10BB90007047402801D2402000E0403880B2704712 +:10BBA00010B50446402800D9FFDF2046FFF7E3FF29 +:10BBB00010B14048808810BD4034A0B210BD40682C +:10BBC00042690078484302EBC0007047C278406881 +:10BBD000037812FB03F24378406901FB032100EB79 +:10BBE000C1007047C2788A4209D9406801EB8101DF +:10BBF0001C2202EBC101405C08B10120704700200B +:10BC000070470078062801D901207047002070474E +:10BC10000078062801D00120704700207047F0B45A +:10BC200001EB81061C27446807EBC6063444049DDB +:10BC300005262670E3802571F0BCFEF71FBA10B50B +:10BC4000418911B1FFF7DDFF08B1002010BD0120CF +:10BC500010BD10B5C18C8278B1EBC20F04D9C18977 +:10BC600011B1FFF7CEFF08B1002010BD012010BDBB +:10BC700010B50C4601230A22011D07F0D4FF0078FD +:10BC80002188012282409143218010BDF0B402EB53 +:10BC900082051C264C6806EBC505072363554B68D7 +:10BCA0001C79402C03D11A71F0BCFEF791BCF0BC9A +:10BCB000704700003000002010B5EFF3108000F056 +:10BCC000010472B6FC484178491C41704078012853 +:10BCD00001D10BF0B3FA002C00D162B610BD70B5E3 +:10BCE000F54CA07848B90125A570FFF7E5FF0BF0EA +:10BCF000B6FA20B100200BF080FA002070BD4FF0A2 +:10BD00008040E570C0F80453F7E770B5EFF310809A +:10BD100000F0010572B6E84C607800B9FFDF60788A +:10BD2000401E6070607808B90BF08CFA002D00D1CD +:10BD300062B670BDE04810B5817821B10021C170B4 +:10BD40008170FFF7E2FF002010BD10B504460BF034 +:10BD500086FAD9498978084000D001202060002067 +:10BD600010BD10B5FFF7A8FF0BF079FA02220123EE +:10BD7000D149540728B1D1480260236103200872D9 +:10BD800002E00A72C4F804330020887110BD2DE966 +:10BD9000F84FDFF824934278817889F80420002650 +:10BDA00089F80510074689F806600078DFF810B3B7 +:10BDB000354620B1012811D0022811D0FFDF0BF049 +:10BDC00060FA4FF0804498B10BF062FAB0420FD1A4 +:10BDD00030460BF061FA0028FAD042E00126EEE787 +:10BDE000FFF76AFF58460168C907FCD00226E6E75C +:10BDF0000120E060C4F80451B2490E600107D1F897 +:10BE00004412B04AC1F3423124321160AD49343199 +:10BE100008604FF0020AC4F804A3A060AA480168B1 +:10BE2000C94341F3001101F10108016841F010011B +:10BE3000016001E019F0A8FFD4F804010028F9D04E +:10BE400030460BF029FA0028FAD0B8F1000F04D1DF +:10BE50009D48016821F010010160C4F808A3C4F8EE +:10BE6000045199F805004E4680B1387870B90BF04E +:10BE7000F6F980460BF006FC6FF00042B8F1000FB7 +:10BE800002D0C6E9032001E0C6E90302DBF80000A6 +:10BE9000C00701D00BF0DFF9387810B13572BDE87A +:10BEA000F88F4FF01808C4F808830127A7614FF4F2 +:10BEB0002070ADF8000000BFBDF80000411EADF8D5 +:10BEC0000010F9D2C4F80C51C4F810517A48C01DC2 +:10BED0000BF06CFA3570FFF744FF676179493079F0 +:10BEE00020310860C4F80483D9E770B5050000D19B +:10BEF000FFDF4FF080424FF0FF30C2F8080300210F +:10BF0000C2F80011C2F80411C2F80C11C2F81011E5 +:10BF1000694C61700BF0AFF910B10120A070607036 +:10BF200067480068C00701D00BF095F92846BDE8C6 +:10BF300070402CE76048007A002800D0012070474C +:10BF40002DE9F04F61484FF0000A85B0D0F800B0FD +:10BF5000D14657465D4A5E49083211608406D4F8DE +:10BF6000080110B14FF0010801E04FF000080BF09C +:10BF7000F0F978B1D4F8240100B101208246D4F858 +:10BF80001C0100B101208146D4F8200108B101272D +:10BF900000E00027D4F8000100B101200490D4F89B +:10BFA000040100B101200390D4F80C0100B101207C +:10BFB0000290D4F8100100B101203F4D0190287883 +:10BFC00000260090B8F1000F04D0C4F808610120E9 +:10BFD0000BF013F9BAF1000F04D0C4F82461092062 +:10BFE0000BF00BF9B9F1000F04D0C4F81C610A2062 +:10BFF0000BF003F927B1C4F820610B200BF0FDF81A +:10C000002D48C01D0BF0DAF900B1FFDFDFF8AC807E +:10C010000498012780B1C4F80873E87818B1EE706D +:10C0200000200BF0EAF8287A022805D103202872B4 +:10C030000221C8F800102761039808B1C4F8046110 +:10C04000029850B1C4F80C61287A032800D0FFDFB1 +:10C05000C8F800602F72FFF758FE019838B1C4F895 +:10C060001061287A012801D100F05CF8676100981E +:10C0700038B12E70287A012801D1FFF772FEFFF740 +:10C0800044FE0D48C01D0BF0AFF91049091DC1F861 +:10C0900000B005B0BDE8F08F074810B5C01D0BF02B +:10C0A0008DF90549B0B1012008704FF0E021C1F8C9 +:10C0B0000002BDE81040FFE544000020340C0040C1 +:10C0C0000C0400401805004010ED00E0100502408F +:10C0D00001000001087A012801D1FFF742FEBDE806 +:10C0E000104024480BF080B970B5224CE41FA078B2 +:10C0F00008B90BF0A7F801208507A861207A00266F +:10C10000032809D1D5F80C0120B900200BF0C4F8A0 +:10C110000028F7D1C5F80C6126724FF0FF30C5F842 +:10C12000080370BD70B5134CE41F6079F0B10128AD +:10C1300003D0A179401E814218DA0BF090F8054631 +:10C140000BF0A0FA6179012902D9A179491CA171EA +:10C150000DB1216900E0E168411A022902DA11F10A +:10C16000020F06DC0DB1206100E0E060BDE8704028 +:10C17000F7E570BD4B0000200F4A12680D498A4256 +:10C180000CD118470C4A12680A4B9A4206D101B5E5 +:10C190000BF04AFA0BF01DFDBDE8014007490968A4 +:10C1A0000958084706480749054A064B70470000EA +:10C1B00000000000BEBAFECA5C000020040000209F +:10C1C000C8130020C8130020F8B51D46DDE9064756 +:10C1D0000E000AD007F0ADFF2346FF1DBCB231466A +:10C1E0002A46009407F0BBFBF8BDD0192246194639 +:10C1F00002F023F92046F8BD70B50D460446102222 +:10C20000002102F044F9258117206081A07B40F0D5 +:10C210000A00A07370BD4FF6FF720A80014602202B +:10C220000BF04CBC704700897047827BD30701D16B +:10C23000920703D48089088000207047052070474A +:10C24000827B920700D581817047014600200988D2 +:10C2500041F6FE52114200D00120704700B503465E +:10C26000807BC00701D0052000BD59811846FFF72B +:10C27000ECFFC00703D0987B40F004009873987BD4 +:10C2800040F001009873002000BD827B520700D56A +:10C2900009B14089704717207047827B61F3C30260 +:10C2A000827370472DE9FC5F0E460446017896467E +:10C2B000012000FA01F14DF6FF5201EA020962681D +:10C2C0004FF6FF7B1188594502D10920BDE8FC9F3C +:10C2D000B9F1000F05D041F6FE55294201D00120E9 +:10C2E000F4E741EA090111801D0014D000232B70EE +:10C2F00094F800C0052103221F464FF0020ABCF14A +:10C300000E0F76D2DFE80CF0F909252F47646B7722 +:10C31000479193B4D1D80420D8E7616820898B7BFA +:10C320009B0767D517284AD30B89834247D389894E +:10C33000172901D3814242D185F800A0A5F8010058 +:10C340003280616888816068817B21F0020181739D +:10C35000C6E0042028702089A5F801006089A5F8AE +:10C3600003003180BCE0208A3188C01D1FFA80F8AC +:10C37000414524D3062028702089A5F80100608952 +:10C38000A5F80300A089A5F805000721208ACDE9BA +:10C390000001636941E00CF0FF00082810D008207C +:10C3A00028702089A5F801006089A5F80300318074 +:10C3B0006A1D694604F10C0009F025FB10B15EE02E +:10C3C0001020EDE730889DF800100844308087E0A9 +:10C3D0000A2028702089A5F80100328044E00C2052 +:10C3E00028702089A5F801006089A5F80300318034 +:10C3F0003AE082E064E02189338800EB41021FFAD1 +:10C4000082F843453BD3B8F1050F38D30E222A708A +:10C410000BEA4101CDE90010E36860882A467146C5 +:10C42000FFF7D2FEA6F800805AE04020287060890D +:10C430003188C01C1FFA80F8414520D32878714606 +:10C4400020F03F00123028702089A5F80100608993 +:10C45000CDE9000260882A46E368FFF7B5FEA6F83A +:10C460000080287840063BD461682089888037E0C6 +:10C47000A0893288401D1FFA80F8424501D2042766 +:10C480003DE0162028702089A5F801006089A5F8F4 +:10C490000300A089CDE9000160882A46714623691E +:10C4A000FFF792FEA6F80080DEE718202870207AB9 +:10C4B0006870A6F800A013E061680A88920401D4AD +:10C4C00005271CE0C9882289914201D0062716E081 +:10C4D0001E21297030806068018821F4005101809C +:10C4E000B9F1000F0BD061887823002202200BF0F5 +:10C4F0003BFA61682078887006E033800327606823 +:10C50000018821EA090101803846DFE62DE9FF4F65 +:10C5100085B01746129C0D001E461CD03078C1070E +:10C5200003D000F03F00192801D9012100E00021CB +:10C530002046FFF7AAFEA8420DD32088A0F57F4130 +:10C54000FF3908D03078410601D4000605D508200F +:10C5500009B0BDE8F08F0720FAE700208DF8000051 +:10C560008DF8010030786B1E00F03F0C0121A81EF1 +:10C570004FF0050A4FF002094FF0030B9AB2BCF1DD +:10C58000200F75D2DFE80CF08B10745E7468748C29 +:10C59000749C74B574BA74C874D474E1747474F10E +:10C5A00074EF74EE74ED748B052D78D18DF80090D6 +:10C5B000A0788DF804007088ADF8060030798DF809 +:10C5C0000100707800F03F000C2829D00ADCA0F1AF +:10C5D0000200092863D2DFE800F0126215621A62D5 +:10C5E0001D622000122824D004DC0E281BD0102845 +:10C5F000DBD11BE016281FD01828D6D11FE02078E9 +:10C60000800701E020784007002848DAEEE0207833 +:10C610000007F9E72078C006F6E720788006F3E700 +:10C6200020784006F0E720780006EDE72088C00576 +:10C63000EAE720884005E7E720880005E4E720884E +:10C64000C004E1E72078800729D5032D27D18DF894 +:10C6500000B0B6F8010081E0217849071FD5062D0A +:10C660001DD381B27078012803D0022817D102E0CF +:10C67000C9E0022000E0102004228DF8002072782A +:10C680008DF80420801CB1FBF0F2ADF8062092B2C8 +:10C6900042438A4203D10397ADF80890A6E079E0BF +:10C6A0002078000776D598B282088DF800A0ADF802 +:10C6B0000420B0EB820F6DD10297ADF8061095E023 +:10C6C0002178C90666D5022D64D381B206208DF883 +:10C6D0000000707802285DD3B1FBF0F28DF8040001 +:10C6E000ADF8062092B242438A4253D1ADF8089089 +:10C6F0007BE0207880064DD5072003E020784006B7 +:10C700007FD508208DF80000A088ADF80400ADF8B2 +:10C710000620ADF8081068E02078000671D50920E1 +:10C72000ADF804208DF80000ADF8061002975DE02A +:10C730002188C90565D5022D63D381B20A208DF801 +:10C740000000707804285CD3C6E72088400558D5DF +:10C75000012D56D10B208DF80000A088ADF8040003 +:10C7600044E021E026E016E0FFE72088000548D5F8 +:10C77000052D46D30C208DF80000A088ADF80400EC +:10C78000B6F803006D1FADF80850ADF80600ADF81F +:10C790000AA02AE035E02088C00432D5012D30D12E +:10C7A0000D208DF8000021E02088800429D4B6F8FF +:10C7B0000100E080A07B000723D5032D21D3307832 +:10C7C00000F03F001B2818D00F208DF800002088B3 +:10C7D00040F40050A4F80000B6F80100ADF80400E1 +:10C7E000ED1EADF80650ADF808B003976946059800 +:10C7F000F5F792FB050008D016E00E208DF800003A +:10C80000EAE7072510E008250EE0307800F03F0049 +:10C810001B2809D01D2807D0022005990BF04EF9DE +:10C82000208800F400502080A07B400708D52046D7 +:10C83000FFF70BFDC00703D1A07B20F00400A0731D +:10C84000284685E61FB5022806D101208DF8000094 +:10C8500088B26946F5F760FB1FBD0000F8B51D46BC +:10C86000DDE906470E000AD007F063FC2346FF1DF2 +:10C87000BCB231462A46009407F071F8F8BDD019D1 +:10C880002246194601F0D9FD2046F8BD2DE9FF4F9B +:10C890008DB09B46DDE91B57DDF87CA00C46082BCC +:10C8A00005D0E06901F0FEF850B11020D2E02888F0 +:10C8B000092140F0100028808AF80010022617E0B5 +:10C8C000E16901208871E2694FF420519180E169AA +:10C8D0008872E06942F601010181E06900218173FB +:10C8E0002888112140F0200028808AF800100426B2 +:10C8F00038780A900A2038704FF0020904F11800C5 +:10C900004D460C9001F0C6FBB04681E0BBF1100F24 +:10C910000ED1022D0CD0A9EB0800801C80B20221A0 +:10C92000CDE9001005AB52461E990D98FFF796FF12 +:10C93000BDF816101A98814203D9F74800790F9074 +:10C9400004E003D10A9808B138702FE04FF00201DB +:10C95000CDE900190DF1160352461E990D98FFF707 +:10C960007DFF1D980088401B801B83B2C6F1FF002D +:10C97000984200D203461E990BA8D9B15FF000027D +:10C98000DDF878C0CDE9032009EB060189B2CDE9D5 +:10C9900001C10F980090BDF8161000220D9801F00B +:10C9A0000EFC387070B1C0B2832807D0BDF81600F5 +:10C9B00020833AE00AEB09018A19E1E7022011B06D +:10C9C000BDE8F08FBDF82C00811901F0FF08022DA1 +:10C9D0000DD09AF80120424506D1BDF820108142C1 +:10C9E00007D0B8F1FF0F04D09AF801801FE08AF851 +:10C9F0000180C94800680178052902D1BDF81610E8 +:10CA0000818009EB08001FFA80F905EB080085B268 +:10CA1000DDE90C1005AB0F9A01F03FFB28B91D981A +:10CA20000088411B4145BFF671AF022D13D0BBF109 +:10CA3000100F0CD1A9EB0800801C81B20220CDE9B7 +:10CA4000000105AB52461E990D98FFF707FF1D9890 +:10CA50000580002038700020B1E72DE9F8439C469E +:10CA6000089E13460027B26B9AB3491F8CB2F18F10 +:10CA7000A1F57F45FF3D05D05518AD882944891D96 +:10CA80008DB200E000252919B6F83C8008314145F7 +:10CA900020D82A44BCF8011022F8021BBCF803106D +:10CAA00022F8021B984622F8024B914607F02FFB12 +:10CAB0004FF00C0C41464A462346CDF800C006F024 +:10CAC0001AFFF587B16B00202944A41D214408807A +:10CAD00003E001E0092700E083273846BDE8F8833A +:10CAE00010B50B88848F9C420CD9846BE0180488A5 +:10CAF00044B1848824F40044A41D23440B801060B6 +:10CB0000002010BD0A2010BD2DE9F0478AB0002595 +:10CB1000904689468246ADF8185007274BE00598A5 +:10CB200006888088000446D4A8F8006007A801950C +:10CB300000970295CDE903504FF40073002231466F +:10CB4000504601F03CFB04003CD1BDF81800ADF8A4 +:10CB50002000059804888188B44216D10A0414D4B0 +:10CB600001950295039521F400410097049541F445 +:10CB7000804342882146504601F0BFF804000BD1A3 +:10CB80000598818841F40041818005AA08A948469A +:10CB9000FFF7A6FF0400DCD00097059802950195E9 +:10CBA000039504950188BDF81C300022504601F021 +:10CBB000A4F80A2C06D105AA06A94846FFF790FF5B +:10CBC0000400ACD0ADF8185004E00598818821F439 +:10CBD0000041818005AA06A94846FFF781FF002889 +:10CBE000F3D00A2C03D020460AB0BDE8F08700201D +:10CBF000FAE710B50C46896B86B051B10C218DF85F +:10CC00000010A18FADF80810A16B01916946FAF7E9 +:10CC100001FB00204FF6FF71A063E187A08706B0FB +:10CC200010BD2DE9F0410D460746896B0020069E98 +:10CC30001446002911D0012B0FD13246294638461F +:10CC4000FFF762FF002808D1002C06D032462946A3 +:10CC50003846BDE8F04100F034BFBDE8F0812DE971 +:10CC6000FC411446DDE9087C0E46DDE90A15521D3B +:10CC7000BCF800E092B2964502D20720BDE8FC81E4 +:10CC8000ACF8002017222A70A5F80160A5F803303F +:10CC90000522CDE900423B462A46FFF7DFFD002092 +:10CCA000ECE770B50C46154648220021204601F0FD +:10CCB000EEFB04F1080044F81C0F00204FF6FF7152 +:10CCC000E06161842084A5841720E08494F82A0020 +:10CCD00040F00A0084F82A0070BD4FF6FF720A8007 +:10CCE000014603200AF0EABE30B585B00C46054681 +:10CCF000FFF77FFFA18E284629B101218DF8001092 +:10CD00006946FAF787FA0020E0622063606305B0A5 +:10CD100030BDB0F8400070476000002090F8462019 +:10CD2000920703D4408808800020F4E70620F2E749 +:10CD300090F846209207EED5A0F84410EBE70146A4 +:10CD4000002009880A0700D5012011F0F00F01D05A +:10CD500040F00200CA0501D540F004008A0501D563 +:10CD600040F008004A0501D540F010000905D2D571 +:10CD700040F02000CFE700B5034690F84600C0071A +:10CD800001D0062000BDA3F842101846FFF7D7FFD8 +:10CD900010F03E0F05D093F8460040F0040083F8F1 +:10CDA000460013F8460F40F001001870002000BD47 +:10CDB00090F84620520700D511B1B0F84200AAE71A +:10CDC0001720A8E710F8462F61F3C3020270A2E70C +:10CDD0002DE9FF4F9BB00E00DDE92B34DDE929780A +:10CDE000289D24D02878C10703D000F03F001928DF +:10CDF00001D9012100E000212046FFF7D9FFB04210 +:10CE000015D32878410600F03F010CD41E290CD020 +:10CE1000218811F47F6F0AD13A8842B1A1F57F428F +:10CE2000FF3A04D001E0122901D1000602D5042006 +:10CE30001FB0C5E5FA491D984FF0000A08718DF83A +:10CE400018A08DF83CA00FAA0A60ADF81CA0ADF8A0 +:10CE500050A02978994601F03F02701F5B1C04F135 +:10CE6000180C4FF0060E4FF0040BCDF858C01F2AD7 +:10CE70007ED2DFE802F07D7D107D267DAC7DF47DE5 +:10CE8000F37DF27DF17DF47DF07D7D7DEF7DEE7DA6 +:10CE90007D7D7D7DED0094F84610B5F80100890791 +:10CEA00001D5032E02D08DF818B01EE34FF40061B7 +:10CEB000ADF85010608003218DF83C10ADF84000B3 +:10CEC000D4E2052EEFD1B5F801002083ADF81C00A7 +:10CED000B5F80310618308B1884201D9012079E1D6 +:10CEE0000020A07220814FF6FF702084169801F078 +:10CEF000D1F8052089F800000220029083460AAB91 +:10CF00001D9A16991B9801F0C8F890BB9DF82E0049 +:10CF1000012804D0022089F80100102003E001203C +:10CF200089F8010002200590002203A90BA808F04F +:10CF30006AFDE8BB9DF80C00059981423DD1398816 +:10CF4000801CA1EB0B01814237DB02990220CDE965 +:10CF500000010DF12A034A4641461B98FFF77EFC6B +:10CF600002980BF1020B801C81B217AA029101E01A +:10CF70009CE228E003A90BA808F045FD02999DF862 +:10CF80000C00CDE9000117AB4A4641461B98FFF75C +:10CF900065FC9DF80C000AAB0BEB00011FFA81FB4E +:10CFA00002991D9A084480B2029016991B9800E0DD +:10CFB00003E001F072F80028B6D0BBF1020F02D0F6 +:10CFC000A7F800B04FE20A208DF818004BE20021CC +:10CFD0000391072EFFF467AFB5F801002083ADF889 +:10CFE0001C00B5F80320628300283FF477AF90421D +:10CFF0003FF674AF0120A072B5F805002081002033 +:10D00000A073E06900F04EFD78B9E16901208871F4 +:10D01000E2694FF420519180E1698872E16942F63A +:10D0200001000881E06900218173F01F20841E98AF +:10D03000606207206084169801F02CF8072089F8B8 +:10D0400000000120049002900020ADF82A0028E0A2 +:10D0500019E29FE135E1E5E012E2A8E080E043E07B +:10D060000298012814D0E0698079012803D1BDF825 +:10D070002800ADF80E00049803ABCDE900B04A4695 +:10D0800041461B98FFF7EAFB0498001D80B204900C +:10D09000BDF82A00ADF80C00ADF80E00059880B27E +:10D0A00002900AAB1D9A16991B9800F0F6FF28B95A +:10D0B00002983988001D05908142D1D2029801283A +:10D0C00081D0E0698079012803D1BDF82800ADF84E +:10D0D0000E00049803ABCDE900B04A4641461B98C8 +:10D0E000FFF7BCFB0298BDE1072E02D0152E7FF49E +:10D0F000DAAEB5F801102183ADF81C10B5F80320A5 +:10D10000628300293FF4EAAE91423FF6E7AE012187 +:10D11000A1724FF0000BA4F808B084F80EB0052EF1 +:10D1200007D0C0B2691DE26908F06BFC00287FF4EB +:10D130004AAF4FF6FF70208401A906AA14A8CDF8C3 +:10D1400000B081E885032878214600F03F031D9A4E +:10D150001B98FFF79BFB8246208BADF81C0082E1F9 +:10D160000120032EC3D14021ADF85010B5F80110B5 +:10D170002183ADF81C100AAAB8F1000F00D00023DB +:10D18000CDE9020304921D98CDF804800090388800 +:10D190000022401E83B21B9801F011F88DF8180090 +:10D1A00090BB0B2089F80000BDF8280035E04FF057 +:10D1B000010C052E9BD18020ADF85000B5F8011070 +:10D1C0002183B5F803002084ADF81C10B0F5007F72 +:10D1D00003D907208DF8180087E140F47C422284AF +:10D1E0000CA8B8F1000F00D00023CDE90330CDE941 +:10D1F000018C1D9800903888401E83B21B9800F067 +:10D20000DEFF8DF8180018B18328A8D10220BFE0F6 +:10D210000D2189F80010BDF83000401C22E100000B +:10D2200060000020032E04D248067FF53CAE0020AB +:10D2300018E1B5F80110ADF81C102878400602D5A9 +:10D240008DF83CE002E007208DF83C004FF000082C +:10D250000320CDE902081E9BCDF810801D98019394 +:10D26000A6F1030B00901FFA8BF342461B9800F0C7 +:10D2700044FD8DF818008DF83C80297849060DD5BD +:10D280002088C00506D5208BBDF81C10884201D12E +:10D29000C4F8248040468DF81880E3E0832801D14B +:10D2A0004FF0020A4FF48070ADF85000BDF81C003A +:10D2B0002083A4F820B01E986062032060841321AC +:10D2C000CDE0052EFFF4EFADB5F80110ADF81C1060 +:10D2D000A28F6AB3A2F57F43FE3B29D008228DF8C6 +:10D2E0003C2000BF4FF0000B0523CDE9023BDDF8E9 +:10D2F00078C0CDF810B01D9A80B2CDF804C040F4CB +:10D3000000430092B5F803201B9800F0F6FC8DF85E +:10D310003CB04FF400718DF81800ADF85010832820 +:10D3200010D0F8B1A18FA1F57F40FE3807D0DCE026 +:10D330000B228DF83C204FF6FE72A287D2E7A4F8AC +:10D340003CB0D2E000942B4631461E9A1B98FFF762 +:10D3500084FB8DF8180008B183284BD1BDF81C0060 +:10D36000208353E700942B4631461E9A1B98FFF703 +:10D3700074FB8DF81800E8BBE18FA06B0844831D97 +:10D380008DE888034388828801881B98FFF767FC33 +:10D39000824668E095F80180022E70D15FEA0800AD +:10D3A00002D0B8F1010F6AD109208DF83C0007A81E +:10D3B00000908DF840804346002221461B98FFF7DD +:10D3C00030FC8DF842004FF0000B8DF843B050B99F +:10D3D000B8F1010F12D0B8F1000F04D1A18FA1F55F +:10D3E0007F40FF380AD0A08F40B18DF83CB04FF499 +:10D3F000806000E037E0ADF850000DE00FA91B9809 +:10D40000F9F708FF82468DF83CB04FF48060ADF824 +:10D410005000BAF1020F06D0FC480068C07928B16C +:10D420008DF8180027E0A4F8188044E0BAF1000F46 +:10D4300003D081208DF818003DE007A800904346F6 +:10D44000012221461B98FFF7ECFB8DF818002146BE +:10D450001B98FFF7CEFB9DF8180020B9192189F819 +:10D460000010012038809DF83C0020B10FA91B98C6 +:10D47000F9F7D0FE8246BAF1000F33D01BE018E076 +:10D480008DF818E031E02078000712D5012E10D178 +:10D490000A208DF83C00E088ADF8400003201B997D +:10D4A0000AF00CFB0820ADF85000C0E648067FF5F6 +:10D4B000FAAC4FF0040A2088BDF8501008432080D1 +:10D4C000BDF8500080050BD5A18FA1F57F40FE3837 +:10D4D00006D11E98E06228982063A6864FF0030AC2 +:10D4E0005046A5E49DF8180078B1012089F80000A5 +:10D4F000297889F80110BDF81C10A9F802109DF8D0 +:10D50000181089F80410052038802088BDF85010C4 +:10D5100088432080E4E72DE9FF4F8846087895B0DE +:10D52000012181404FF20900249C0140ADF82010F8 +:10D530002088DDF88890A0F57F424FF0000AFF3A7E +:10D5400006D039B1000705D5012019B0BDE8F08F2C +:10D550000820FAE7239E4FF0000B0EA886F800B0D3 +:10D5600018995D460988ADF83410A8498DF81CB0AB +:10D57000179A0A718DF838B0086098F800000128F1 +:10D580003BD0022809D003286FD1307820F03F002B +:10D590001D303070B8F80400E08098F800100320C7 +:10D5A000022904D1317821F03F011B31317094F808 +:10D5B0004610090759D505ABB9F1000F13D000216A +:10D5C00002AA82E80B000720CDE90009BDF834006B +:10D5D000B8F80410C01E83B20022159800F0EFFDC9 +:10D5E0000028D1D101E0F11CEAE7B8F80400A6F860 +:10D5F0000100BDF81400C01C04E198F805108DF876 +:10D600001C1098F80400012806D04FF4007A022874 +:10D610002CD00328B8D16CE12188B8F8080011F4A7 +:10D620000061ADF8201020D017281CD3B4F84010AA +:10D63000814218D3B4F84410172901D3814212D182 +:10D64000317821F03F01C91C3170A6F80100032197 +:10D65000ADF83410A4F8440094F8460020F002001D +:10D6600084F8460065E105257EE177E1208808F130 +:10D67000080700F4FE60ADF8200010F0F00F1BD09A +:10D6800010F0C00F03D03888228B9042EBD199B9AB +:10D69000B878C00710D0B9680720CDE902B1CDF83D +:10D6A00004B00090CDF810B0FB88BA88398815987E +:10D6B00000F023FB0028D6D12398BDF82010401C91 +:10D6C00080294ED006DC10290DD020290BD040290E +:10D6D00087D124E0B1F5807F6ED051457ED0B1F581 +:10D6E000806F97D1DEE0C80601D5082000E0102049 +:10D6F00082460DA907AA0520CDE902218DF8380040 +:10D70000ADF83CB0CDE9049608A93888CDE9000110 +:10D710005346072221461598FFF7B8F8A8E09DF870 +:10D720001C2001214FF00A0A002A9BD105ABB9F158 +:10D73000000F00D00020CDE902100720CDE900093C +:10D74000BDF834000493401E83B2218B002215984B +:10D7500000F035FD8DF81C000B203070BDF8140072 +:10D7600020E09DF81C2001214FF00C0A002A22D154 +:10D7700013ABB9F1000F00D00020CDE90210072053 +:10D78000CDE900090493BDF83400228C401E83B219 +:10D79000218B159800F013FD8DF81C000D203070C2 +:10D7A000BDF84C00401CADF8340005208DF8380061 +:10D7B000208BADF83C00BCE03888218B88427FF498 +:10D7C00052AF9DF81C004FF0120A00281CD1606A6D +:10D7D000A8B1B878C0073FF446AF00E018E0BA68D7 +:10D7E0000720CDE902B2CDF804B00090CDF810B01A +:10D7F000FB88BA88159800F080FA8DF81C00132079 +:10D8000030700120ADF8340093E00000600000208B +:10D810003988208B8142D2D19DF81C004FF0160A26 +:10D820000028A06B08D0E0B34FF6FF7000215F46E0 +:10D83000ADF808B0019027E068B1B978C907BED14A +:10D84000E18F0DAB0844821D03968DE80C024388DE +:10D850008288018809E0B878C007BCD0BA680DABEF +:10D8600003968DE80C02BB88FA881598FFF7F7F944 +:10D8700005005ED0072D72D076E0019005AA02A9BE +:10D880002046FFF72DF90146E28FBDF808008242DD +:10D8900001D00029F1D0E08FA16B084407800198E6 +:10D8A000E08746E09DF81C004FF0180A40B1208B3D +:10D8B000C8B13888208321461598FFF79AF938E0D7 +:10D8C00004F118000090237E012221461598FFF7ED +:10D8D000A8F98DF81C000028EDD119203070012026 +:10D8E000ADF83400E7E7052521461598FFF781F9E3 +:10D8F0003AE0208800F40070ADF8200050452DD1AA +:10D90000A08FA0F57F41FE3901D006252CE0D8F884 +:10D9100008004FF0160A48B1A063B8F80C10A187B0 +:10D920004FF6FF71E187A0F800B002E04FF6FF70FC +:10D93000A087BDF8200030F47F611AD07823002240 +:10D94000032015990AF010F898F80000207120883B +:10D95000BDF82010084320800EE000E00725208855 +:10D96000BDF8201088432080208810F47F6F1CD0E1 +:10D970003AE02188814321809DF8380020B10EA92A +:10D980001598F9F747FC05469DF81C000028EBD0D8 +:10D9900086F801A001203070208B70809DF81C005B +:10D9A00030710520ADF83400DEE7A18EE1B11898A2 +:10D9B0000DAB0088ADF834002398CDE90304CDE920 +:10D9C0000139206B0090E36A179A1598FFF700FA67 +:10D9D000054601208DF838000EA91598F9F71AFCB4 +:10D9E00000B10546A4F834B094F8460040070AD5C3 +:10D9F0002046FFF7A4F910F03E0F04D114F8460FAB +:10DA000020F0040020701898BDF8341001802846DA +:10DA10009BE500B585B0032806D102208DF80000F3 +:10DA200088B26946F9F7F6FB05B000BD10B5384C71 +:10DA30000B782268012B02D0022B2AD111E0137837 +:10DA40000BB1052B01D10423137023688A889A80B7 +:10DA50002268CB88D38022680B8913814989518140 +:10DA60000DE08B8893802268CB88D38022680B8955 +:10DA700013814B8953818B899381096911612168D5 +:10DA8000F9F7C8FB226800210228117003D0002892 +:10DA900000D0812010BD832010BD806B002800D0F5 +:10DAA000012070478178012909D10088B0F5205FF5 +:10DAB00003D042F60101884201D1002070470720BF +:10DAC0007047F0B587B0002415460E460746ADF8FE +:10DAD000184011E005980088288005980194811D60 +:10DAE000CDE902410721049400918388428801888E +:10DAF000384600F002F930B905AA06A93046FEF70B +:10DB0000EFFF0028E6D00A2800D1002007B0F0BDC2 +:10DB10006000002010B58B7883B102789A4205D15D +:10DB20000B885BB102E08B79091D4BB18B789A426F +:10DB3000F9D1B0F801300C88A342F4D1002010BD17 +:10DB4000812010BD072826D012B1012A27D103E079 +:10DB5000497801F0070102E04978C1F3C2010529C3 +:10DB60001DD2DFE801F00318080C12000AB10320EF +:10DB700070470220704704280DD250B10DE00528EF +:10DB800009D2801E022808D303E0062803D0032808 +:10DB900003D005207047002070470F207047812078 +:10DBA0007047C0B282060BD4000607D5FA48807AC7 +:10DBB0004143C01D01EBD00080B27047084670475A +:10DBC0000020704770B513880B800B781C0625D594 +:10DBD000F14CA47A844204D843F01000087000206D +:10DBE00070BD956800F0070605EBD0052D78F5406F +:10DBF00065F304130B701378D17803F0030341EA43 +:10DC0000032140F20123B1FBF3F503FB15119268E8 +:10DC1000E41D00FB012000EBD40070BD906870BDD6 +:10DC200037B51446BDF804101180117841F0040195 +:10DC300011709DF804100A061ED5D74AA368C1F3D7 +:10DC40000011927A824208D8FE2811D1D21DD20842 +:10DC50004942184600F01BFC0AE003EBD00200F03A +:10DC60000703012510789D40A84399400843107090 +:10DC7000207820F0100020703EBD2DE9F0410746CD +:10DC8000C81C0E4620F00300B04202D08620BDE83A +:10DC9000F081C14D002034462E60AF802881AA72E9 +:10DCA000E8801AE0E988491CE980810614D4E1780B +:10DCB00000F0030041EA002040F20121B0FBF1F244 +:10DCC00001FB12012068FFF76CFF2989084480B22C +:10DCD0002881381A3044A0600C3420784107E1D400 +:10DCE0000020D4E7AC4801220189C08800EB400045 +:10DCF00002EB8000084480B270472DE9FF4F89B0E5 +:10DD00001646DDE9168A0F46994623F44045084633 +:10DD100000F054FB040002D02078400703D4012017 +:10DD20000DB0BDE8F08F099806F086F802902078D3 +:10DD3000000606D59848817A0298814201D887204A +:10DD4000EEE7224601A90298FFF73CFF8346002038 +:10DD50008DF80C004046B8F1070F1AD00122214679 +:10DD6000FFF7F0FE0028DBD12078400611D5022015 +:10DD70008DF80C00ADF81070BDF80400ADF812007D +:10DD8000ADF814601898ADF81650CDF81CA0ADF899 +:10DD900018005FEA094004D500252E46A846012751 +:10DDA0000CE02178E07801F0030140EA012040F224 +:10DDB0000121B0FBF1F2804601FB12875FEA494086 +:10DDC00009D5B84507D1A178207901F0030140EACF +:10DDD0000120B04201D3BE4201D90720A0E7A81913 +:10DDE0001FFA80F9B94501D90D2099E79DF80C007B +:10DDF00028B103A90998F9F70BFA002890D1B84582 +:10DE000007D1A0784FEA192161F30100A07084F8CE +:10DE100004901A9800B10580199850EA0A0027D09A +:10DE2000199830B10BEB06002A46199900F005FB52 +:10DE30000EE00BEB06085746189E099806F067F9A6 +:10DE40002B46F61DB5B239464246009505F053FD06 +:10DE5000224601A90298FFF7B5FE9DF8040022466C +:10DE600020F010008DF80400DDE90110FFF7D8FE66 +:10DE7000002055E72DE9FF4FDFF81C91824685B061 +:10DE8000B9F80610D9F8000001EB410100EB81045C +:10DE900040F20120B2FBF0F1174600FB1175DDE9FD +:10DEA000138B4E4629460698FFF77BFE0346FFF785 +:10DEB00019FF1844B1880C30884202D9842009B077 +:10DEC0002FE70698C6B2300603D5B00601D5062066 +:10DED000F5E7B9F80620521C92B2A9F80620BBF16A +:10DEE000000F01D0ABF80020B00602D5C4F80880BE +:10DEF0000AE0B9F808201A4492B2A9F80820D9F823 +:10DF00000000891A0844A0602246FE200699FFF707 +:10DF100087FEE77025712078390A61F301002A0A2B +:10DF2000A17840F0040062F30101A17020709AF81A +:10DF300002006071BAF80000E08000252573300609 +:10DF400002D599F80A7000E00127B00601D54FF01C +:10DF500000084E4600244FF007090FE0CDE90258B3 +:10DF60000195CDF800900495F1882046129B089AFF +:10DF7000FFF7C3FE0028A2D1641CE4B2BC42EDD37B +:10DF800000209CE700B5FFF7ADFE03490C308A88FE +:10DF9000904203D9842000BD00060020CA8808688A +:10DFA00002EB420300EB8300521C037823F00403CE +:10DFB0000370CA80002101730846ECE72DE9F047A1 +:10DFC000804600F0FBF9070005D000264446F74DD7 +:10DFD00040F2012916E00120BDE8F087204600F05C +:10DFE000EDF90278C17802F0030241EA0222B2FBA5 +:10DFF000F9F309FB13210068FFF7D3FD3044641CDB +:10E0000086B2A4B2E988601E8142E7DCA8F1010073 +:10E01000E8802889801B288100203870DCE710B553 +:10E02000144631B1491E218005F006FFA070002082 +:10E0300010BD012010BD70B50446DC48C1880368DE +:10E0400001E0401C20802088884207D200EB40027B +:10E0500013EB820202D015786D07F2D580B28842A8 +:10E0600016D2AAB15079A072D08820819178107907 +:10E0700001F0030140EA0120A081A078E11CFFF734 +:10E08000A1FD20612088401C2080E080002070BD20 +:10E090000A2070BD0121018270472DE9FF4F85B034 +:10E0A0004FF6FF798246A3F8009048681E460D4659 +:10E0B00080788DF8060048680088ADF804000020DC +:10E0C0008DF80A00088A0C88A04200D304462C82EE +:10E0D00051E03878400708D4641C288AA4B2401C58 +:10E0E000288208F10100C0B246E0288A401C28823C +:10E0F000781D6968FFF70EFDD8BB3188494501D10D +:10E10000601E30803188A1EB080030806888A04212 +:10E1100038D3B878397900F0030041EA002801A922 +:10E12000781DFFF7F7FC20BB298949452ED0002236 +:10E1300039460798FFF706FDD8B92989414518D116 +:10E14000E9680391B5F80AC0D7F808B05046CDF891 +:10E1500000C005F0DCFFDDF800C05A460CF1070CEA +:10E160001FFA8CFC43460399CDF800C005F08DFBE7 +:10E1700060B1641CA4B200208046204600F01EF965 +:10E180000700A6D1641E2C820A2098E67480787954 +:10E19000B071F888B0803978F87801F0030140EA6E +:10E1A00001207081A6F80C80504605F045FE3A46E5 +:10E1B00006F10801FFF706FD306100207FE62DE93A +:10E1C000FF4F87B081461C469246DDF860B0DDF80F +:10E1D0005480089800F0F2F8050002D02878400733 +:10E1E00002D401200BB09CE5484605F025FE2978B5 +:10E1F000090605D56D49897A814201D88720F1E762 +:10E20000CAF309062A4601A9FFF7DCFC0746149861 +:10E2100007281CD000222946FFF794FC0028E1D1F2 +:10E220002878400613D501208DF808000898ADF82D +:10E230000C00BDF80400ADF80E00ADF81060ADF8AC +:10E24000124002A94846F8F7E3FF0028CAD129780E +:10E25000E87801F0030140EA0121AA78287902F068 +:10E26000030240EA0220564507D0B1F5007F04D9E9 +:10E27000611E814201DD0B20B4E7864201D90720EF +:10E28000B0E7801B85B2A54200D92546BBF1000F3F +:10E2900001D0ABF80050179818B1B9192A4600F010 +:10E2A000CCF8B8F1000F0DD03E4448464446169FC6 +:10E2B00005F03FFF2146FF1DBCB232462B460094BD +:10E2C00005F04DFB00208DE72DE9F04107461D4686 +:10E2D0001646084600F072F8040002D02078400785 +:10E2E00001D40120D3E4384605F0A6FD21780906C3 +:10E2F00005D52E49897A814201D88720C7E4224674 +:10E300003146FFF75FFC65B12178E07801F0030149 +:10E3100040EA0120B0F5007F01D8012000E0002094 +:10E3200028700020B3E42DE9F04107461D4616464B +:10E33000084600F043F8040002D02078400701D4DA +:10E340000120A4E4384605F077FD2178090605D5BB +:10E350001649897A814201D8872098E422463146BD +:10E36000FFF75EFCFF2D14D02178E07801F0030266 +:10E3700040EA022040F20122B0FBF2F302FB13005C +:10E3800015B900F2012080B2E070000A60F30101CB +:10E39000217000207BE410B50C4600F00FF810B19E +:10E3A0000178490704D4012010BD000000060020B8 +:10E3B000C18821804079A0700020F5E70749CA880C +:10E3C000824209D340B1096800EB40006FF00B02B4 +:10E3D00002EB8000084470470020704700060020D0 +:10E3E00070B504460D4621462B460AB9002070BD83 +:10E3F00001E0491C5B1C501E021E03D008781E78E9 +:10E40000B042F6D008781E78801BF0E730B50C4695 +:10E4100001462346051B954206D202E0521E9D5C32 +:10E420008D54002AFAD107E004E01D780D70491CD4 +:10E430005B1C521E002AF8D130BDF0B50E460146D5 +:10E44000334680EA030404F00304B4B906E002B9D9 +:10E45000F0BD13F8017B01F8017B521E01F00307A8 +:10E46000002FF4D10C461D4602E080CD80C4121F5F +:10E47000042AFAD221462B4600BF04E013F8014BD0 +:10E4800001F8014B521E002AF8D100BFE0E7F0B5B9 +:10E490000C460146E6B204E002B9F0BD01F8016B9A +:10E4A000521E01F00307002FF6D10B46E5B245EAF4 +:10E4B000052545EA054501E020C3121F042AFBD2C9 +:10E4C000194602E001F8016B521E002AFAD100BF82 +:10E4D000E3E7000010B509F0A0FDF4F7F9F909F041 +:10E4E000E7FBBDE8104009F0AFBC302834BF012085 +:10E4F00000207047202834BF4FF0A0420C4A01236F +:10E5000000F01F0003FA00F0002914BFC2F80C0548 +:10E51000C2F808057047202834BF4FF0A0410449D5 +:10E5200000F01F00012202FA00F0C1F81805704740 +:10E530000003005070B50346002002466FF02F051F +:10E540000EE09C5CA4F130060A2E02D34FF0FF309F +:10E5500070BD00EB800005EB4000521C2044D2B29D +:10E560008A42EED370BD30B50A230BE0B0FBF3F462 +:10E5700003FB1404B0FBF3F08D183034521E05F881 +:10E58000014CD2B2002AF1D130BD30B500234FF694 +:10E59000FF7510E0040A44EA002084B2C85C6040C1 +:10E5A000C0F30314604005EA00344440E0B25B1C51 +:10E5B00084EA40109BB29342ECD330BD2DE9F04188 +:10E5C000FE4B0026012793F864501C7893F868C02E +:10E5D000B8B183F89140A3F8921083F8902083F8A3 +:10E5E0008E70BCF1000F0CBF83F8946083F89450D8 +:10E5F000F3488068008805F08AFDBDE8F04105F029 +:10E6000021BA4FF6FF7083F89140A3F8920083F887 +:10E61000902083F88E70BCF1000F14BF83F89450E3 +:10E6200083F89460BDE8F0812DE9F041E44D29685C +:10E6300091F89C200024012A23D091F89620012AE9 +:10E6400030D091F86C301422DC4E0127012B32D0EF +:10E6500091F88E30012B4FD091F8A620012A1CBFD3 +:10E660000020BDE8F08144701F2200F8042B222214 +:10E67000A731FFF7E2FE286880F8A6400120BDE838 +:10E68000F08144701B220270D1F89D204260D1F8C5 +:10E69000A120826091F8A520027381F89C4001209E +:10E6A000BDE8F081447007220270D1F898204260E2 +:10E6B00081F89640E2E78046447000F8042B20225F +:10E6C0006E31FFF7BAFE88F80870286880F86C4051 +:10E6D00090F86E000028D1D1B6F87000A6F8980026 +:10E6E000A868417B86F89A1086F89670008805F035 +:10E6F0000EFD05F0B6F9C1E791F86C30012B0BD097 +:10E70000447017220270D1F890204260B1F8942032 +:10E71000028181F88E40B1E78046447000F8042BF6 +:10E7200020226E31FFF789FE88F80870286880F88B +:10E730006C4090F86E000028A0D1CDE7A04800689A +:10E7400090F86C10002914BFB0F870004FF6FF70FD +:10E75000704770B59A4C06462068002808BFFFDF56 +:10E760000025206845706660002808BFFFDF20682C +:10E77000417800291CBFFFDF70BDCC220021FFF7CC +:10E7800086FE2068FF2101707F2180F83810132158 +:10E790004184282180F86910012180F85C1080F8FC +:10E7A00061500AF0C1F9BDE8704009F0AEBA844981 +:10E7B0000968097881420CBF012000207047804819 +:10E7C000006890F82200C0F3400070477C48006861 +:10E7D00090F8220000F0010070477948006890F836 +:10E7E0002200C0F3001070472DE9F0437448002464 +:10E7F000036893F82400B3F822C0C0F38001C0F38B +:10E800004002114400F001000844CCF3001121B390 +:10E81000BCF1100F02BF6B4931F81000BDE8F08366 +:10E82000BCF1120F18BFBCF1130F0ED0BCF1150FC5 +:10E830001EBFFFDF2046BDE8F0830021624A32F8A8 +:10E84000102010FB0120BDE8F083604A002132F85F +:10E85000102010FB0120BDE8F08393F85E2093F8B0 +:10E860005F102E264FF47A774FF014084FF04009CE +:10E87000022A04BF4AF2D745B5FBF7F510D0012AAA +:10E8800004BF4AF22F75B5FBF7F510D04AF62315F1 +:10E89000B5FBF7F5082A08BF4E4613D0042A18D056 +:10E8A0002646082A0ED0042A13D0022A49D004F1A1 +:10E8B0002806042A0FD0082A1CBF4FF01908082286 +:10E8C00004D00AE04FF0140806F5A8764FF0400295 +:10E8D00003E006F5A8764FF0100218FB026212FB67 +:10E8E0000052C0EB00103A4D00EB800005EB8000B9 +:10E8F00010441CF0010F4FF4C8724FF4BF7504BFF1 +:10E90000CCF34006002E65D0CCF3400600F5A57090 +:10E91000EEB1082904BF174640260CD0042904BFD5 +:10E920002F46102607D0022907BF04F11807042636 +:10E9300004F12807082606EB860808EB86163E44F5 +:10E940001BE004F118064FF019080422C5E7082956 +:10E9500004BF164640270CD0042904BF2E461027BA +:10E9600007D0022907BF04F11806042704F128067E +:10E97000082707EB871706EB8706304400F19C0653 +:10E9800093F8690001F00C07002F08BF0020304405 +:10E9900018BF00F5416027D1082904BF164640275B +:10E9A0001BD0042904BF2E46102716D0022906BF0B +:10E9B00004F11806042704F128060CE00C060020D8 +:10E9C00068000020DC610200E4610200D461020002 +:10E9D000D4FEFFFF64E018BF0827C7EBC70707EBAB +:10E9E000470706EB4706304498301CF0010F17D05C +:10E9F000082908BF40210CD0042904BF2A46102151 +:10EA000007D0022907BF04F11802042104F12802EB +:10EA1000082101EB410303EB0111114408443BE0E1 +:10EA2000082904BF944640260CD0042904BFAC46F4 +:10EA3000102607D0022907BF04F1180C042604F1A0 +:10EA4000280C082606EB8616B3F840300CEB860C33 +:10EA50006044EB2B20D944F2552C0B3303FB0CF311 +:10EA60009B0D082907D0042902D0022905D008E00F +:10EA70002A46102108E0402106E004F11802042192 +:10EA800002E004F12802082101EB811102EB81016F +:10EA900001F5A57103FB010000F5B470BDE8F0833A +:10EAA00000F5A570082904BF944640260CD004291F +:10EAB00004BFAC46102607D0022907BF04F1180C8A +:10EAC000042604F1280C082606EB8616B3F8483015 +:10EAD0000CEB860C6044EB2BDED944F2552C0B3347 +:10EAE00003FB0CF39B0D0829C5D00429C0D00229D3 +:10EAF000C7D1C2E7FE4840F271210068806A4843EE +:10EB00007047FB48006890F83700002818BF0120C4 +:10EB1000704710B5F74C207B022818BF032808D196 +:10EB2000207D04F115010EF0E6FE08281CBF01202F +:10EB300010BD207B002816BF022800200120BDE860 +:10EB400010400AF021BDEB4908737047E849096895 +:10EB500081F8300070472DE9F047E54C2168087BCB +:10EB6000002816BF022800200120487301F10E0181 +:10EB70000AF0F4FC2168087B022816BF0328012252 +:10EB8000002281F82F204FF0080081F82D00487BEB +:10EB900001F10E034FF001064FF00007012804BFFA +:10EBA0005B7913F0C00F0AD001F10E03012804D1E4 +:10EBB000587900F0C000402801D0002000E001207A +:10EBC00081F82E00002A04BF91F8220010F0040FF3 +:10EBD00007D0087D01F115010EF08DFE216881F846 +:10EBE0002D002068476007F0BFFA2168C14D4FF043 +:10EBF0000009886095F82D000EF089FE804695F892 +:10EC00002F00002818BFB8F1000F04D095F82D0090 +:10EC10000EF0B1FC68B195F8300000281CBF95F8E3 +:10EC20002E0000281DD0697B05F10E0001290ED0B1 +:10EC300012E06E734A4605F10E0140460AF0E4FC0C +:10EC400095F82D1005F10E000EF063FF09E04079F4 +:10EC500000F0C000402831D0394605F10E000AF01E +:10EC60000BFD2068C77690F8220010F0040F08BF53 +:10EC7000BDE8F087002795F82D000EF017FD050080 +:10EC800008BFBDE8F087102102F0C2F8002818BFC5 +:10EC9000BDE8F08720683A4600F11C01C676284698 +:10ECA0000AF0B2FC206800F11C0160680FF08EF8D9 +:10ECB0006068BDE8F04701210FF0A3B80EF066FFD1 +:10ECC0004A4605F10E010AF09FFCCAE7884A12681D +:10ECD000137B0370D2F80E000860508A888070475A +:10ECE00078B584490446824E407B087332682078A8 +:10ECF00010706088ADF8000080B200F00101C0F330 +:10ED0000400341EA4301C0F3800341EA8301C0F3B9 +:10ED1000C00341EAC301C0F3001341EA0311C0F389 +:10ED2000401341EA4311C0F3801041EA801050843F +:10ED3000E07D012808BF012507D0022808BF022571 +:10ED400003D0032814BFFFDF0825306880F85E5029 +:10ED5000607E012808BF012507D0022808BF0225D0 +:10ED600003D0032814BFFFDF0825316881F85F5006 +:10ED700091F83500012829D0207B81F82400488CA7 +:10ED80001D280CBF002060688862607D81F8370014 +:10ED9000A07B002816BF0228002001200875D4F8A7 +:10EDA0000F00C1F81500B4F81300A1F81900A07EF7 +:10EDB00091F86B2060F3071281F86B20E07E012848 +:10EDC00018BF002081F83400002078BD91F85E2043 +:10EDD0000420082A08BF81F85E00082D08BF81F8CA +:10EDE0005F00C9E742480068408CC0F3001131B1B0 +:10EDF000C0F38000002804BF1F20704702E0C0F36A +:10EE0000400109B10020704710F0010F14BFEE203F +:10EE1000FF20704736480068408CC0F3001119B1DC +:10EE2000C0F3800028B102E0C0F3400008B1002028 +:10EE30007047012070472E49002209684A664B8CB2 +:10EE40001D2B0CBF81F8682081F8680070470023F3 +:10EE5000274A126882F85D30D164A2F85000012080 +:10EE600082F85D007047224A0023126882F85C3005 +:10EE7000A2F858000120516582F85C0070471C49D7 +:10EE8000096881F8360070471949096881F86100FE +:10EE900070471748006890F961007047144800688F +:10EEA00090F82200C0F3401070471148006890F8B5 +:10EEB0002200C0F3C0007047012070470C48006872 +:10EEC00090F85F00704770B509F018FE09F0F7FD83 +:10EED00009F0C0FC09F06CFD054C2068416E491C2E +:10EEE000416690F83300002558B109F01DFE03E09B +:10EEF000680000200C06002008F007FF206880F85A +:10EF000033502068457090F8391021B1BDE8704049 +:10EF100004200AF0AEBF90F86810D9B1406E81426B +:10EF200018D804200AF0A5FF206890F8220010F0FD +:10EF3000010F07D0A06843220188BDE8704001207E +:10EF4000FFF73CBBBDE8704043224FF6FF71002045 +:10EF5000FFF734BBBDE8704000200AF08ABF2DE9FE +:10EF6000F04782B00F468146FE4E4FF000083068F1 +:10EF7000458C15F0030F10D015F0010F05F00200BD +:10EF800005D0002808BF4FF0010806D004E0002893 +:10EF900018BF4FF0020800D1FFDF4FF0000A5446BF +:10EFA00015F0010F05F002000DD080B915F0040F27 +:10EFB0000DD04AF00800002F1CBF40F0010040F0C7 +:10EFC00002044DD09EE010B115F0040F0DD015F0E5 +:10EFD000070F10D015F0010F05F0020043D00028F4 +:10EFE00008BF15F0040F34D04AE0002F18BF4AF0D4 +:10EFF000090444D141E037B14AF00800044615F055 +:10F00000200F1BD07EE0316805F02002B1F84800E7 +:10F01000104308BF4AF0010474D04AF018000446B7 +:10F0200015F0200F6ED191F85E1011F00C0118BF91 +:10F030000121C94361F30000044663E0316891F89F +:10F040005E1011F00C0118BF012161F300000446AD +:10F0500058E04AF00800002F18BF40F0010451D1D9 +:10F0600040F010044EE0002818BF15F0040F07D040 +:10F07000002F18BF4AF00B0444D14AF0180441E0B5 +:10F0800015F0030F3DD115F0040F3AD077B1306879 +:10F090004AF0080490F85E0010F00C0118BF01213E +:10F0A00061F3410415F0200F24D02BE0306805F007 +:10F0B0002002B0F84810114308BF4AF0030421D0E1 +:10F0C0004AF0180415F0200F0AD000BF90F85E0037 +:10F0D00010F00C0018BF0120C04360F3410411E0A0 +:10F0E00090F85E1011F00C0118BF0121C94361F3C3 +:10F0F0000004EBE710F00C0018BF012060F30004DF +:10F1000000E0FFDF15F0400F1CD0CFB93168B1F837 +:10F110004800002804BF488C10F0010F0BD110F0FC +:10F12000020F08BF10F0200F05D115F0010F08BF26 +:10F1300015F0020F04D091F85E0010F00C0F01D111 +:10F1400044F040047068A0F800A0017821F020018C +:10F1500001704FF007010EF005FE414670680EF099 +:10F16000F8FF214670680FF000F814F0010F0CD082 +:10F170004FF006034FF000027B4970680EF0CFFF9E +:10F180003068417B70680EF02FFE14F0020F18D02B +:10F19000D6E90010B9F1000F4FF006034FF001025D +:10F1A00007D01C310EF0BBFF012170680EF029FE64 +:10F1B00007E015310EF0B3FF3068017D70680EF086 +:10F1C00020FE14F0040F18BFFFDF14F0080F19D051 +:10F1D000CDF800A03068BDF800200223B0F86A1016 +:10F1E00061F30B02ADF8002090F86B0003220109D7 +:10F1F0009DF8010061F307108DF801006946706801 +:10F200000EF08DFF012F62D13068B0F84810E1B3E5 +:10F2100090F82200C0F34000B8BB70680EF095FF74 +:10F22000401CC7B23068C7F1FF05B0F84820B0F8FD +:10F230005A10511AA942B8BF0D46AA423BD990F8BC +:10F24000220010F0010F36D144F0100421467068FE +:10F250000EF08BFFF81CC0B2ED1E284482B230685D +:10F26000B0F86A10436EC1F30B0151FA83F190F8C4 +:10F2700060303E4F1944BC460023E1FB07C31B0925 +:10F280006FF0240C03FB0C1100E020E080F860100C +:10F2900090F85F00012101F01FF90090BDF8000017 +:10F2A0009DF80210032340EA01400190042201A9C5 +:10F2B00070680EF034FF3068AAB2416C70680EF0CE +:10F2C00082FF3068B0F85A102944A0F85A1014F0A0 +:10F2D000400F06D0D6E900100123062261310EF05E +:10F2E0001EFF14F0200F18BFFFDF0020002818BFFA +:10F2F000FFDF02B0BDE8F0872DE9F043194C89B07B +:10F300002068002808BFFFDF20684178002944D129 +:10F310000178FF2941D0002680F83160A0F85A60BA +:10F32000867080F83960304609F062FB104802AD03 +:10F3300000F1240191E80E1085E80E10D0E90D10BF +:10F34000CDE9061002A809F041FB08F0BCFF2068D7 +:10F3500090F9610009F090F8064809F093F8064822 +:10F360000CE00000680000201A06002053E4B36E91 +:10F37000C8610200D0610200CD61020009F012FBF9 +:10F38000606809F038FB206890F8240010F0010F45 +:10F3900007D0252009F07EF80AE009B00C20BDE86E +:10F3A000F08310F0020F18BF262069D009F072F820 +:10F3B000206890F85E10252008F043FF206880F850 +:10F3C0002C6009F00FFB206890F85E10002009F017 +:10F3D00028F90F21052008F0F8FF206890F82E107A +:10F3E000002901BF90F82F10002990F8220010F09A +:10F3F000040F74D006F0B8FE0546206829468068E0 +:10F4000007F0AAFBDFF82884074690FBF8F008FB1A +:10F4100010704142284606F08EFB2168886097FBF9 +:10F42000F8F04A68104448600EF062FA014620681D +:10F43000426891426ED8C0E90165FE4D4FF0010867 +:10F4400095F82D000EF063FA814695F82F000127FC +:10F45000002818BFB9F1000F04D095F82D000EF068 +:10F460008AF8A0B195F8300000281CBF95F82E004E +:10F47000002824D0687B05F10E01012815D019E081 +:10F4800010F0040F14BF2720FFDF8FD190E73A461A +:10F490006F7305F10E0148460AF0B6F895F82D1085 +:10F4A00005F10E000EF035FB09E0487900F0C000D0 +:10F4B000402815D0414605F10E000AF0DDF820681D +:10F4C00090F8220010F0040F24D095F82D000EF0D3 +:10F4D000EDF805001ED0102101F09AFC40B119E0B2 +:10F4E0000EF054FB3A4605F10E010AF08DF8E6E7FE +:10F4F00020683A4600F11C01C77628460AF084F8D5 +:10F50000206800F11C0160680EF060FC0121606859 +:10F510000EF077FC2068417B0E3008F038FF206841 +:10F5200090F85C1061B3B0F85810A0F84810416D25 +:10F53000416490F82210C1F30011F1B9B0F86A00EB +:10F540000221C0F30B05ADF80050684607F0B0FF8C +:10F5500028B1BDF80000C0F30B00A84204D1BDF8EB +:10F560000000401CADF800002168BDF80000B1F8B3 +:10F570006A2060F30B02A1F86A20206880F85C60C2 +:10F58000206890F85D1039B1B0F85010A0F8401024 +:10F59000C16CC16380F85D60B0F86A10426EC1F35F +:10F5A0000B0151FA82F190F86020DFF88CC211440F +:10F5B00063460022E1FB0C3212096FF0240302FBC8 +:10F5C000031180F860100EF00CFA032160680EF051 +:10F5D00090FA216881F8330009B00020BDE8F0837B +:10F5E0009649886070472DE9F043944C83B02268B7 +:10F5F00092F831303BB1508C1D2808BFFFDF03B0BB +:10F60000BDE8F0435FE401260027F1B1054692F81A +:10F61000600008F03FFF206890F85F10FF2008F0BE +:10F6200010FE20684FF4A57190F85F20002009F0CB +:10F63000D4F8206890F8221011F0030F00F02C810C +:10F64000002D00F0238100F027B992F822108046A7 +:10F65000D07EC1F30011002956D0054660680780AE +:10F66000017821F020010170518C132937D01FDC63 +:10F67000102908BF022144D0122908BF062140D01A +:10F68000FFDF6C4D606805F10E010EF091FB697BA8 +:10F6900060680EF0A9FB2068418C1D2918BF152950 +:10F6A00063D0B0F84820416C60680EF0B6FB5CE0B7 +:10F6B000152918BF1D29E3D14FF001010EF052FBAF +:10F6C0006068017841F020010170216885B11C312A +:10F6D0000EF07CFB012160680EF093FBD1E7002166 +:10F6E0000EF040FB6068017841F020010170C8E72E +:10F6F00015310EF06BFB2068017D60680EF081FB18 +:10F70000BFE70EF02FFBBCE70021FFF728FC606885 +:10F71000C17811F03F0F28D0017911F0100F24D0DB +:10F720000EF01EFB2368024693F82410C1F38000FC +:10F73000C1F3400C604401F00101084493F82C101F +:10F74000C1F3800CC1F34005AC4401F001016144F8 +:10F75000401AC1B293F85E0000F0BEFE0090032391 +:10F760000422694660680EF0DAFC2068002590F8F3 +:10F77000241090F82C0021EA000212F0010F18BFAB +:10F7800001250ED111F0020F04D010F0020F08BFB6 +:10F79000022506D011F0040F03D010F0040F08BFAB +:10F7A0000425B8F1000F2BD0012D1BD0022D08BF6E +:10F7B00026201BD0042D14BFFFDF272016D0206881 +:10F7C00090F85E10252008F03CFD206890F822108B +:10F7D000C1F3001169B101224FF49671002008F0C5 +:10F7E000FCFF0DE0252008F055FEE8E708F052FE8A +:10F7F000E5E790F85E204FF49671002008F0EDFFE9 +:10F80000206890F82C10294380F82C1090F82420C0 +:10F8100032EA01011CD04670418C13292BD026DC22 +:10F82000102904BF03B0BDE8F083122923D007E0FC +:10F8300040420F000C06002053E4B36E6800002025 +:10F84000C1F30010002818BFFFDF03B0BDE8F0834C +:10F85000418C1D2908BF80F82C70DCD0C1F3001149 +:10F86000002914BF80F8316080F83170D3E7152982 +:10F8700018BF1D29DBD190F85E2003B04FF00101C5 +:10F88000BDE8F043084609F094B900BF90F85F2046 +:10F890000121084609F08DF92168002DC87E7CD031 +:10F8A0004A8C3D46C2F34000002808BF47F00805D7 +:10F8B00012F0400F18BF45F04005002819BFD1F8DD +:10F8C0003C90B1F84080D1F84490B1F8488060682D +:10F8D000072107800EF046FA002160680EF039FC1F +:10F8E000294660680EF041FC15F0080F17D020681B +:10F8F000BDF800100223B0F86A2062F30B01ADF8E6 +:10F90000001090F86B00032201099DF8010061F3DB +:10F9100007108DF80100694660680EF000FC606811 +:10F920000EF0DCFA2168C0F1FE00B1F85A20A8EB15 +:10F9300002018142A8BF0146CFB2D019404544D24E +:10F9400045F0100160680EF010FC60680EF0C6FA19 +:10F950002168C0F1FE00B1F85A10A8EB0101814204 +:10F96000A8BF0146CFB260680EF0EFFB3844421CDE +:10F970002068B0F86A10436EC1F30B0151FA83F1AD +:10F9800090F86030FE4D1944AC460023E1FB05C3FE +:10F990004FEA131C6FF0240300E03CE00CFB031162 +:10F9A00080F8601090F85F00012100F095FD009054 +:10F9B000BDF800009DF80210032340EA01400190C9 +:10F9C000042201A960680EF0AAFB216891F82200C8 +:10F9D00010F0400F05D001230622613160680EF05F +:10F9E0009EFB20683A46B0F85A0000EB09016068B7 +:10F9F0000EF0E9FB2068B0F85A103944A0F85A100C +:10FA000009F0BFFC002818BFFFDF20684670867031 +:10FA100003B0BDE8F0830121FFF7A1FAF0E7D94870 +:10FA200010B50068417841B90078FF2805D0002161 +:10FA30000846FFF7D8FD002010BD09F05FF809F077 +:10FA40003EF808F007FF08F0B3FF0C2010BD2DE9C9 +:10FA5000F041CC4D0446174628680E4690F86C00DD +:10FA6000002818BFFFDF2868002F80F86E7018BFCD +:10FA7000BDE8F0812188A0F870106188A0F8861098 +:10FA8000A188A0F88810E188A0F88A1094F888115D +:10FA900080F88C1090F82F10002749B1427B00F1BC +:10FAA0000E01012A04D1497901F0C001402935D065 +:10FAB00090F8301041B1427B00F10E01012A04BFE1 +:10FAC000497911F0C00F29D000F17A00F3F794FAC8 +:10FAD0006868FF2E0178C1F380116176D0F80310B9 +:10FAE000C4F81A10B0F80700E08328681ED0C0F8E8 +:10FAF0008010E18BA0F8841000F17402511E304692 +:10FB00000DF014FF002808BFFFDF286890F873107D +:10FB100041F0020180F87310BDE8F081D0F80E10BA +:10FB2000C0F87A10418AA0F87E10D1E7C0F8807042 +:10FB3000A0F88470617E80F87310D4F81A104167C1 +:10FB4000E18BA0F87810BDE8F08170B58D4C0125EF +:10FB5000206890F82200C0F3C00038B13C22FF2199 +:10FB6000A068FFF774FF206880F86C50206890F858 +:10FB7000220010F0010F1CBFA06801884FF03C026A +:10FB800012BF01204FF6FF710020FEF717FD20681D +:10FB900080F8395070BD7B49096881F832007047A0 +:10FBA0002DE9F041774C0026206841780127354641 +:10FBB000012906D0022901D003297DD0FFDFBDE84D +:10FBC000F081817802250029418C46D0C1F34002A2 +:10FBD000002A08BF11F0010F6FD090F85F204FF09E +:10FBE00001014FF0000008F0E4FF216891F82200C5 +:10FBF000C0F34000002814BF0C20222091F85F10B1 +:10FC000008F01FFB2068457090F8330058B108F0E9 +:10FC100068F8206890F85F0010F00C0F0CBF4020CF +:10FC2000452008F077FF206890F83400002818BFBE +:10FC300008F08FFF216891F85F0091F8691010F0CB +:10FC40000C0F08BF0021962008F0F6FE09F090FB8B +:10FC5000002818BFFFDFBDE8F081C1F3001282B1B8 +:10FC600010293FD090F8330020B108F03AF8402036 +:10FC700008F050FF206890F8221011F0040F36D0E1 +:10FC800043E090F8242090F82C309A422AD1B0F822 +:10FC90004800002808BF11F0010F05D111F0020F34 +:10FCA00008BF11F0200F6FD04FF001014FF000009E +:10FCB000FFF799FC206801E041E035E0418C11F04C +:10FCC000010F04BFC1F34001002907D1B0F85A1059 +:10FCD000B0F84820914218BFBDE8F08180F831703B +:10FCE000BDE8F081BDE8F041002101207BE490F8FF +:10FCF0003710012914BF0329102646F00E0190F891 +:10FD00005E204FF0000008F054FF206890F83400A7 +:10FD1000002818BF08F01DFF0021962008F08CFE77 +:10FD200020684570BDE8F081B0F85A10B0F848007E +:10FD3000814242D0BDE8F0410121084653E4817878 +:10FD4000D9B1418C11F0010F22D080F86C7090F87D +:10FD50006E20B0F870100120FEF730FC206845706E +:10FD600008F0CCFE08F0ABFE08F074FD08F020FEB1 +:10FD7000BDE8F04103200AF07CB88178012004E05E +:10FD800053E4B36E6800002017E0BDE8F0412AE4B8 +:10FD900011F0020F04BFFFDFBDE8F081B0F85A1088 +:10FDA000B0F84000814208D001210846FFF71BFC53 +:10FDB000216803204870BDE8F081BDE8F041FFF7FD +:10FDC00082B8FFF780B810B5FE4C206890F8341068 +:10FDD00049B1383008F0CCFE18B921687F2081F88D +:10FDE000380008F0ACFE206890F8330018B108F035 +:10FDF0009BFE07F08AFF0AF02EFCA8B1206890F85D +:10FE00002210C1F3001179B14078022818BFFFDF3A +:10FE100000210120FFF7E7FB2068417800291EBF81 +:10FE200040780128FFDF10BDBDE81040FFF74BB858 +:10FE30002DE9F047E34C0F4680462168B8F1030FE7 +:10FE4000488C08BFC0F3400508D000F0010591F8C8 +:10FE50003200002818BF4FF0010901D14FF000090E +:10FE600008F00CFB0646B8F1030F0CBF4FF0020878 +:10FE70004FF0010835EA090008BFBDE8F0872068A7 +:10FE800090F8330068B10DF08FFD38700146FF28FF +:10FE900007D06068C01C0DF060FD38780DF091FD52 +:10FEA000064360680178C1F3801221680B7D9A4295 +:10FEB00008D10622C01C1531FEF792FA002808BFAF +:10FEC000012000D000203978FF2906D0C8B9206869 +:10FED00090F82D00884216D113E0A0B1616811F8A6 +:10FEE000030BC0F380100DF006FD05460DF061FE1A +:10FEF00038B128460DF0DAFB18B1102100F088FF68 +:10FF000008B1012000E00020216891F8221011F0D2 +:10FF1000040F01D0F0B11AE0CEB9AB4890F8370029 +:10FF2000002818BF404515D1616811F8030BC0F3D4 +:10FF300080100DF0E0FC04460DF03BFE38B1204689 +:10FF40000DF0B4FB18B1102100F062FF10B10120D8 +:10FF5000BDE8F0870020BDE8F0872DE9F04F994D0E +:10FF6000044683B0286800264078022818BFFFDFC7 +:10FF700028684FF07F0B90F8341049B1383008F002 +:10FF8000F7FD002804BF286880F838B008F0D7FDD6 +:10FF900068680DF009FF8046002C00F0458208F0EB +:10FFA00010FA002800F04082012400274FF0FF09DA +:10FFB000B8F1050F1ED1686890F8240000F01F000A +:10FFC000102817D9286890F8360098B18DF800905D +:10FFD00069460520FFF72CFF002800F025822868DD +:10FFE00080F8A64069682222A730C91CFEF725FACE +:10FFF00000F01ABA68680EF062F8002800F0148267 +:020000040001F9 +:100000004046DFF8C4814FF0030A062880F02182C1 +:10001000DFE800F0FCFCFC03FCFB8DF80090694677 +:100020000320FFF705FF002800F0F180296891F810 +:10003000340010B191F89C00D8B12868817801296A +:100040004DD06868042107800DF08CFE08F10E0188 +:1000500068680DF0ADFE98F80D1068680DF0C4FEEC +:100060002868B0F84020C16B68680DF0FAFE00F017 +:1000700063B99DF8000081F89C400A7881F89D20C2 +:10008000FF280FD001F19F029E310DF04FFC002898 +:1000900008BFFFDF286890F89E1041F0020180F849 +:1000A0009E100DE068680278C2F3801281F89E20ED +:1000B000D0F80320C1F89F20B0F80700A1F8A300F2 +:1000C000286800F1A50490F838007F2808BFFFDFFA +:1000D000286890F83810217080F838B0ADE790F8B3 +:1000E00022000721C0F3801938480479686869F351 +:1000F000861407800DF036FE002168680EF029F89E +:10010000214668680EF031F80623002208F10E013E +:1001100068680EF004F82868417B68680DF064FE9A +:1001200068680DF0DBFE2968B1F84020C0F1FE01DF +:100130008A42B8BF1146CFB2BA423CD9F81EC7B204 +:1001400044F0100B594668680EF00FF868680DF01F +:10015000FCFF384400F101082868B0F86A10426ECC +:10016000C1F30B0151FA82F190F86020184C0A4457 +:10017000A4460023E2FB04C319096FF0240301FB2A +:10018000032180F8601090F85F004246012100F0E2 +:10019000A3F90190BDF804009DF80610032340EA7E +:1001A00001400290042202A968680DF0B8FF594688 +:1001B00068680DF0DAFFB9F1000F0FD0D5E9001033 +:1001C000012307E0680000200C060020C86102003F +:1001D00053E4B36E062261310DF0A1FF28683A4660 +:1001E000C16B68680DF0EFFF2868A0F85A70B0F88E +:1001F00040108F420CBF0121002180F8311009F01E +:10020000C0F8002818BFFFDF96E007E021E128686A +:100210008078002840F00A8100F006B98DF800903F +:1002200068680178C1F38019D0F803100191B0F823 +:100230000700ADF8080069460520FFF7F9FD002822 +:1002400028687DD0817800297CD090F85FB0D5E90E +:100250000104D0F80F10C4F80E10B0F8131061822A +:10026000417D2175817D6175B0F81710E182B0F88C +:1002700019106180B0F81B10A180B0F81D10E1804A +:1002800000F11F0104F1080015F085FE686890F880 +:10029000241001F01F01217690F82400400984F811 +:1002A000880184F864B084F865B01BF00C0F0CBFB3 +:1002B0000021012104F130000EF0ABF92868002282 +:1002C00090F8691084F8661090F8610084F867006F +:1002D0009DF80010A868FFF7BAFB022009F0C9FDDD +:1002E000B2480DF1040B08210468686807800DF01E +:1002F00039FD002168680DF02CFF214668680DF07B +:1003000034FF0623002208F10E0168680DF007FF94 +:100310002868417B68680DF067FD494668680DF004 +:1003200070FD06230122594668680DF0F8FE09F0B9 +:1003300028F8002818BFFFDF286880F801A077E0C0 +:100340006DE0FFE76868D5F808804FF00109027892 +:1003500098F80D10C2F34012114088F80D10D0F833 +:100360000F10C8F80E10B0F81310A8F81210417D45 +:1003700088F81410817D88F81510B0F81710A8F8C7 +:100380001610B0F81910A8F80210B0F81B10A8F851 +:100390000410B0F81D10A8F8061000F11F0108F1B4 +:1003A000080015F0F8FD686890F8241001F01F01AE +:1003B00088F8181090F824000021400988F8880176 +:1003C00088F8649088F8659008F130000EF021F903 +:1003D0002868002290F8691088F8661090F861008B +:1003E00088F867009DF80010A868FFF730FB2868C0 +:1003F00080F86C4090F86E20B0F870100120FEF785 +:10040000DDF82868477008F079FB08F058FB08F021 +:1004100021FA08F0CDFA012009F02BFD08E090F850 +:100420002200C0F3001008B1012601E0FEF74BFDE9 +:10043000286890F8330018B108F076FB07F065FCE7 +:1004400096B10AF008F960B100210120FFF7CBF85E +:1004500013E0286890F82200C0F300100028E5D0CF +:10046000E2E7FEF730FD08E028688178012904D131 +:1004700090F85F10FF2007F0E4FE2868417800291B +:1004800019BF4178012903B0BDE8F08F40780328F7 +:1004900018BFFFDF03B0BDE8F08F70B5444C0646CF +:1004A0000D462068807858B107F0F2FD21680346B8 +:1004B000304691F85F202946BDE870400AF085BAC1 +:1004C00007F0E6FD21680346304691F85E20294694 +:1004D000BDE870400AF079BA78B50C460021009169 +:1004E000082804BF4FF4C87040210DD0042804BF71 +:1004F0004FF4BF70102107D0022807BF01F1180088 +:10050000042101F128000821521D02FB01062848A0 +:100510009DF80010006890F8602062F3050141F03A +:1005200040058DF8005090F85F00012829D002287E +:100530002ED004281CBF0828FFDF2FD025F0800014 +:100540008DF80000C4EB041000EB80004FF01E019A +:1005500001EB800006FB04041648844228BFFFDF3D +:100560001548A0FB0410BDF80110000960F30C0150 +:10057000ADF80110BDF800009DF8021040EA0140FE +:1005800078BD9DF8020020F0E0008DF80200D5E76C +:100590009DF8020020F0E000203004E09DF8020009 +:1005A00020F0E00040308DF80200C7E7C86102008B +:1005B00068000020C4BF0300898888880023C383A3 +:1005C000428401EBC202521EB2FBF1F1018470477A +:1005D0002DE9F04104460026D9B3552333224FF4C8 +:1005E000FA4501297DD0022900F01481032918BFA2 +:1005F000BDE8F08104F17001207B00F01F00207342 +:1006000084F889605FF0000004EB000C9CF808C0DF +:1006100003EA5C05ACEB050C0CF0FF0C0CF03305A9 +:1006200002EA9C0CAC440D180CEB1C1C0CF00F0CDB +:1006300085F814C04D7E401CAC44C0B281F819C08E +:100640000528E1D30CF0FF00252898BFBDE8F08114 +:10065000DCE0FFE704F17005802200212846FDF769 +:1006600016FFAE71EE712E736E73EE732E746E7193 +:10067000AE76EE76212085F84000492085F84100CD +:10068000FE2085F874002588702200212046FDF7A1 +:10069000FEFE2580012584F8645084F865502820EA +:1006A00084F86600002104F130000DF0B2FF1B2237 +:1006B000A4F84E20A4F85020A4F85220A4F8542006 +:1006C0004FF4A470A4F85600A4F8580065734FF4D2 +:1006D00048606080A4F8F060A4F8F260A4F8F460C8 +:1006E00000E023E0A4F8F660A4F8F86084F8FA606B +:1006F00084F8FD60A4F8066184F80461A4F8186128 +:10070000A4F81A6184F8B66184F8B76184F8C0610E +:1007100084F8C16184F88C6184F88F6184F8A861E1 +:10072000C4F8A061C4F8A461BDE8F081A4F8066132 +:1007300084F8FB606088FE490144B1FBF0F1A4F845 +:1007400090104BF68031A4F89210B4F806C0A4F8CB +:100750009860B4F89C704FEACC0C4743BCFBF0FCAB +:1007600097FBF0F70CF1010CA4F89C701FFA8CFCBD +:100770000CFB00F704F17001A4F89AC0B7F5C84F5C +:10078000C4BFACF1010CA1F82AC0B5FBF0FC0CF120 +:10079000010CA1F830C000F5802C0CF5EE3CACF15A +:1007A0000105B5FBF0FCA1F820C0CD8B05FB00FCDA +:1007B000BCFBF0F0C8830846217B01F01F012173C8 +:1007C0004676002104EB010C9CF808C003EA5C05A6 +:1007D000ACEB050C0CF0FF0C0CF0330502EA9C0CA2 +:1007E000AC4445180CEB1C1C0CF00F0C85F814C025 +:1007F000457E491CAC44C9B280F819C00529E1D333 +:100800000CF0FF00252898BFBDE8F081FFDFBDE8B0 +:10081000F08100BFB4F8B011B4F8B4316288A4F824 +:100820009860B4F89CC0DB000CFB02FCB3FBF1F356 +:100830009CFBF1FC5B1CA4F89CC09BB203FB01FC7D +:1008400004F17000A4F89A30BCF5C84FC4BF5B1E19 +:100850004385B5FBF1F35B1C0386438C01EBC303BB +:100860005B1EB3FBF1F30384C38B5A43B2FBF1F17C +:10087000C183BDE8F0812DE9F04104460025A1B314 +:1008800055234FF4FA464FF0330C01297DD002294D +:1008900000F0E080032918BFBDE8F08104F170008A +:1008A000217B01F01F01217384F889500021621817 +:1008B000127A03EA5205521BD2B202F033050CEA57 +:1008C00092022A44451802EB121202F00F022A7516 +:1008D000457E491C2A44C9B242760529E7D3D0B2E5 +:1008E000252898BFBDE8F081B1E0FFE704F170066C +:1008F000802200213046FDF7CAFDB571F5713573D0 +:100900007573F57335747571B576F576212086F8B3 +:100910004000492086F84100FE2086F874002688B1 +:10092000702200212046FDF7B2FD2680012684F8C2 +:10093000646084F86560282084F86600002104F172 +:1009400030000DF066FE1B22A4F84E20A4F85020C3 +:10095000A4F85220A4F854204FF4A470A4F8560030 +:10096000A4F858006673A4F8F850202084F8FA0020 +:1009700084F8F050C4F8F45084F8245184F82551D8 +:1009800084F82E5184F82F5100E005E084F81451CA +:1009900084F82051BDE8F081618865480844B0FBC7 +:1009A000F1F0A4F890004BF68030A4F89200E288B1 +:1009B000A4F89850B4F89C70D2004F43B2FBF1F207 +:1009C00097FBF1F7521CA4F89C7092B202FB01F75E +:1009D00004F17000A4F89A20B7F5C84FC4BF521EA6 +:1009E0004285B6FBF1F2521C028601F5802202F527 +:1009F000EE32561EB6FBF1F20284C68B06FB01F204 +:100A0000B2FBF1F1C1830146207B00F01F0020738F +:100A10004D7600202218127A03EA5205521BD2B2F8 +:100A200002F033050CEA92022A440D1802EB12126E +:100A300002F00F022A754D7E401C2A44C0B24A764D +:100A40000528E7D3D0B2252898BFBDE8F081FFDFA5 +:100A5000BDE8F081D0F81811628804F1700348896C +:100A6000C989A4F89850B4F89CC0C9000CFB02FCDA +:100A7000B1FBF0F19CFBF0FC491CA4F89CC089B2CE +:100A800001FB00FCA4F89A10BCF5C84FC4BF491E76 +:100A90005985B6FBF0F1491C1986598C00EBC10150 +:100AA000491EB1FBF0F11984D98B5143B1FBF0F031 +:100AB000D883BDE8F0812DE9F003447E0CB1252CEC +:100AC00003D9BDE8F00312207047002A02BF0020BE +:100AD000BDE8F003704791F80DC01F260123154DA6 +:100AE0004FF00008BCF1000F7AD0BCF1010F1EBF1F +:100AF0001F20BDE8F0037047B0F800C00A7C8F7B70 +:100B000091F80F907A404F7C87EA090742EA072262 +:100B100082EA0C0C5FF000070CF0FF0999FAA9F9C2 +:100B20004FEA1C2C4FEA19699CFAACFC04E0000067 +:100B3000FFDB050053E4B36E4FEA1C6C49EA0C2C52 +:100B40000CEB0C1C7F1C9444FFB21FFA8CFC032F8F +:100B5000E2D38CEA020CFB4F0022ECFB0572120977 +:100B60006FF0240502FB05C2D2B201EBD2078276F8 +:100B700002F007053F7A03FA05F52F4218BFC27647 +:100B80007ED104FB0CF2120C521CD2B25FF00004B6 +:100B900000EB040C9CF814C094453CBFA2EB0C0283 +:100BA000D2B212D30D194FF0000C2D7A03FA0CF7C4 +:100BB0003D421CBF521ED2B2002A69D00CF1010C7A +:100BC0000CF0FF0CBCF1080FF0D304F1010C0CF099 +:100BD000FF04052CDCD33046BDE8F0037047FFE787 +:100BE00090F81AC00C7E474604FB02C2D54C4FF069 +:100BF000000CE2FB054C4FEA1C1C6FF024040CFBBC +:100C00000422D2B201EBD204827602F0070C247ADD +:100C100003FA0CFC14EA0C0F1FBFC2764046BDE875 +:100C2000F003704790F819C0B2FBFCF40CFB1422DF +:100C3000521CD2B25FF0000400EB040C9CF814C00C +:100C400094453CBFA2EB0C02D2B212D30D194FF067 +:100C5000000C2D7A03FA0CF815EA080F1CBF521E7F +:100C6000D2B272B10CF1010C0CF0FF0CBCF1080F08 +:100C7000F0D304F1010C0CF0FF04052CDCD3AAE73F +:100C800009E00CEBC401C1763846BDE8F0037047BB +:100C90000CEBC401C1764046BDE8F0037047AA4A98 +:100CA000016812681140A94A126811430160704737 +:100CB00030B4A749A44B00244FF0010C0A78521C11 +:100CC000D2B20A70202A08BF0C700D781A680CFA8C +:100CD00005F52A42F2D0097802680CFA01F1514078 +:100CE000016030BC704770B46FF01F02010C02EA63 +:100CF00090251F23A1F5AA4054381CBFA1F5AA4096 +:100D0000B0F1550009D0A1F52850AA381EBFA1F5B1 +:100D10002A40B0F1AA00012000D100204FF0000CC1 +:100D2000624601248CEA0106F6431643B6F1FF3F02 +:100D300011D005F001064FEA5C0C4CEAC63C03F00A +:100D4000010652086D085B08641C42EAC632162C84 +:100D5000E8DD70BC704770BC0020704790F804C09C +:100D60003CF01F011CBF0020704730B401785522B1 +:100D700002EA5103C91AC9B201F03304332303EA6A +:100D800091012144447801EB111102EA5405641BDE +:100D9000E4B204F0330503EA94042C4404EB141485 +:100DA00001F00F0104F00F040C448178C07802EACE +:100DB0005105491BC9B201F0330503EA91012944E9 +:100DC00001EB111101F00F01214402EA5004001B54 +:100DD000C0B200F0330403EA9000204400EB10108E +:100DE00000F00F00014402EA5C00ACEB0000C0B26E +:100DF00000F0330203EA9000104400EB101000F002 +:100E00000F00084401288CBF0120002030BC70472F +:100E10000A000ED00123012A0BDB491EC9B210F8CB +:100E200001C0BCF1000F01D0002070475B1C934251 +:100E3000F3DD01207047002A08BF70471144401EAF +:100E400012F0010F03D011F8013D00F8013F5208E4 +:100E500008BF704711F8013C437011F8023D00F8DB +:100E6000023F521EF6D1704770B58CB000F11004ED +:100E70001D4616460DF1FF3C5FF0080014F8012CEA +:100E80008CF8012014F8022D0CF8022F401EF5D129 +:100E900001F1100C6C460DF10F0108201CF8012C1B +:100EA0004A701CF8022D01F8022F401EF6D1204690 +:100EB00013F01CFB7EB16A1E04F130005FF00801E4 +:100EC00010F8013C537010F8023D02F8023F491E31 +:100ED000F6D10CB070BD08982860099868600A982F +:100EE000A8600B98E8600CB070BD38B505460C469C +:100EF000684607F03DFE002808BF38BD9DF9002078 +:100F00002272E07E607294F90A100020511A48BFE4 +:100F1000494295F82D308B42C8BF38BDFF2B08BF22 +:100F200038BDE17A491CC9B2E17295F82E30994278 +:100F300003D8A17A7F2918BF38BDA2720020E072C1 +:100F4000012038BD53E4B36E04620200086202005F +:100F5000740000200C2818BF0B2810D00D2818BFD3 +:100F60001F280CD0202818BF212808D0222818BFFD +:100F7000232804D024281EBF2628002070474FF0C5 +:100F8000010070470C2963D2DFE801F006090E1357 +:100F9000161B323C415C484E002A5BD058E0072AC1 +:100FA00018BF082A56D053E00C2A18BF0B2A51D07C +:100FB0004EE00D2A4ED04BE0A2F10F000C2849D98B +:100FC00046E023B1A2F110000B2843D940E0122AD9 +:100FD00018BF112A3ED090F8380020B1122A37D31A +:100FE0001A2A37D934E0162A32D31A2A32D92FE0F6 +:100FF000A2F10F0103292DD990F8380008B31B2A5C +:1010000028D925E0002B08BF042A21D122E013B102 +:10101000062A1FD01CE0012A1AD11BE01C2A1CBF83 +:101020001D2A1E2A16D013E01F2A18BF202A11D00D +:10103000212A18BF222A0DD0232A1CBF242A262A9F +:1010400008D005E013B10E2A04D001E0052A01D032 +:1010500000207047012070472DE9F0410D460446FD +:10106000866805F02FFA58B905F07EF840F236711F +:1010700004F061FDA060204605F024FA0028F3D0BA +:1010800095B13046A16805F067FD00280CDD2844C5 +:10109000401EB0FBF5F707FB05F1304604F04BFDB1 +:1010A000A0603846BDE8F0810020BDE8F08170B551 +:1010B0000446904228BF70BD101B64280BD325182E +:1010C0008D4206D8042105F07AFD00281CBF284671 +:1010D00070BD204670BD6420F1E711F00C0F13D0F5 +:1010E00001F0040100290DBF4022102296214FF487 +:1010F000167101F5BC71A0EB010388428CBF93FB14 +:10110000F2F0002080B27047022919BF6FF00D0184 +:1011100001EBD0006FF00E0101EB9000F2E7084404 +:1011200018449830002A14BF042100210844704755 +:1011300010B4002A14BF4FF429624FF4A472002B9C +:1011400019BF4FF429634FF0080C4FF4A4734FF00C +:10115000010C00280CBF0124002491F866001CF04B +:101160000C0F08BF0020D11808449830002C14BF81 +:1011700004210021084410BC704700280CBF012343 +:10118000002391F86600002BA0F6482000F50050DF +:1011900018BF04231844496A81422CBF0120002053 +:1011A00012F00C0118BF012131EA000014BF002029 +:1011B0000120704710B413680B66137813F00C030A +:1011C00018BF0123527812F00C0218BF012253EA13 +:1011D000020C04BF10BC7047002B0CBF4FF4A4736B +:1011E0004FF42963002A19BF4FF429624FF0080C0D +:1011F0004FF4A4724FF0010C00280CBF012400240E +:1012000091F866001CF00C0F08BF00201A4410442F +:101210009830002C14BF0422002210444A6A8242F3 +:1012200024BF10BC704791F860004FF0030230F00B +:101230000C0381F8603091F8610020F00C0081F817 +:10124000610008BF81F86020002808BF81F8612094 +:1012500010BC704710F0010F1CBF0120704710F048 +:10126000020F1CBF0220704710F0040018BF0820B6 +:1012700070472DE9F0470446174689464FF00108AC +:1012800008460DF0FAF8054648460DF0FAF810F059 +:10129000010F18BF012624D015F0010F18BF01233C +:1012A0002AD000BF56EA030108BF4FF0000810F033 +:1012B000070F08BF002615F0070F08BF002394F89A +:1012C0006400B0420CBF00203046387094F86510BE +:1012D000994208BF00237B70002808BF002B25D14E +:1012E00015E010F0020F18BF0226D5D110F0040F40 +:1012F00014BF08260026CFE715F0020F18BF0223FF +:10130000D0D115F0040F14BF08230023CAE74846C4 +:101310000DF0BDF8B4F87010401A00B247F6FE7137 +:10132000884201DC002801DC4FF0000816B1082ECD +:101330000CD018E094F86400012818BF022812D0DD +:1013400004281EBF0828FFDF032D0CD194F8C0012C +:1013500048B1B4F8C401012894F8640006D0082804 +:1013600001D0082038704046BDE8F087042818BF37 +:101370000420F7D1F5E7012814BF0228704710F0C8 +:101380000C0018BF0420704738B4CBB2C1F3072C4F +:10139000C1B2C0F30724012B07D0022B09D0042BC4 +:1013A00008BFBCF1040F2DD006E0BCF1010F03D142 +:1013B00028E0BCF1020F25D0012906D0022907D070 +:1013C000042908BF042C1DD004E0012C02D119E02F +:1013D000022C17D001EA0C0161F3070204EA0301B1 +:1013E00061F30F22D1B211F0020F18BF022310D007 +:1013F000C2F307218DF8003011F0020F18BF02214F +:101400001BD111E0214003EA0C03194061F30702EC +:10141000E6E711F0010F18BF0123E9D111F0040F25 +:1014200014BF08230023E3E711F0010F18BF0121C7 +:1014300003D111F0040118BF08218DF80110082B09 +:1014400001BF000C012804208DF80000BDF8000049 +:1014500038BC70474FF0000C082902D0042909D08D +:1014600011E001280FD10420907082F803C013808E +:1014700001207047012806D00820907082F803C030 +:1014800013800120704700207047162A10D12A22AD +:101490000C2818BF0D280FD04FF0230C1F280DD09B +:1014A00031B10878012818BF002805D0162805D0CA +:1014B00000207047012070471A70FBE783F800C0D6 +:1014C000F8E7012908D002290BD0042912BF082906 +:1014D00040F6A660704707E0002804BF40F2E240F3 +:1014E000704740F6C410704700B5FFDF40F2E2409D +:1014F00000BD00000178406829B190F82C1190F8E7 +:101500008C0038B901E001F0BDBD19B1042901D04A +:10151000012070470020704770B50C460546062133 +:1015200002F0C4FC606008B1002006E007212846F4 +:1015300002F0BCFC606018B101202070002070BD7A +:10154000022070BD2DE9FC470C4606466946FFF7B0 +:10155000E3FF00287DD19DF8000050B1FDF7EEF8C3 +:10156000B0427CD0214630460AF008FC002873D1F6 +:101570002DE00DF097F9B04271D02146304612F0BF +:10158000B6FA002868D1019D95F8F00022E001200C +:1015900000E00020804695F839004FF0010A4FF036 +:1015A0000009F0B195F83A0080071AD584F8019047 +:1015B00084F800A084F80490E68095F83B1021722E +:1015C000A98F6181E98FA18185F8399044E0019D5F +:1015D00095F82C0170350028DBD1287F0028D8D061 +:1015E000D5E7304602F0A5FD070000D1FFDF384601 +:1015F00001F0B5FF40B184F801900F212170E68021 +:10160000208184F804A027E0304602F080FD070026 +:1016100000D1FFDFB8F1000F21D0384601F0F7FF0D +:10162000B8B19DF8000038B90198D0F81801418888 +:10163000B14201D180F80090304607F00DFF84F8E8 +:1016400001900C21217084F80490E680697F21725A +:1016500000E004E085F81C900120BDE8FC87002034 +:10166000FBE71CB56946FFF757FF00B1FFDF68468F +:1016700001F014FDFE4900208968A1F8F2001CBDAC +:101680002DE9FC4104460E46062002F0B7FB054654 +:10169000072002F0B3FB2844C7B20025A8463E4409 +:1016A00017E02088401C80B22080B04202D3404620 +:1016B000A4F8008080B2B84204D3B04202D2002025 +:1016C000BDE8FC816946FFF727FF0028F8D06D1CB4 +:1016D000EDB2AE42E5D84FF6FF7020801220EFE762 +:1016E00038B54FF6FF70ADF800000DE00621BDF8EB +:1016F000000002F0EDFB04460721BDF8000002F0F7 +:10170000E7FB0CB100B1FFDF00216846FFF7B8FF2F +:101710000028EBD038BD70B507F00CFF0BF034FF9C +:10172000D44C4FF6FF76002526836683D2A0257021 +:1017300001680079A4F14002657042F8421FA11CC3 +:101740001071601C12F0EFFA1B2020814FF4A4717D +:101750006181A081E18107212177617703212174D3 +:10176000042262746082A082A4F13E00E1820570CE +:101770004680BF480C300570A4F11000057046800B +:1017800084F8205070BD70B5B94C16460D466060A7 +:10179000217007F047FEFFF7A3FFFFF7BCFF20789B +:1017A0000FF0BDFFB6480DF0D0F92178606812F057 +:1017B0005FFA20780BF0DCF8284608F0AFFEB0485E +:1017C000FCF7C7FF217860680AF0B2FB3146207849 +:1017D00012F024FDBDE870400BF0D6BE10B5012418 +:1017E0000AB1002010BD21B1012903D000242046F8 +:1017F00010BD02210CF024FDF9E710B50378044672 +:10180000002B406813460A46014609D05FF00100EC +:10181000FFF78EFC6168496A884203D9012010BD38 +:101820000020F5E7002010BD2DE9F04117468A7829 +:101830001E46804642B11546C87838B1044669074D +:1018400006D52AB1012104E00725F5E70724F6E7CC +:101850000021620702D508B1012000E0002001420A +:1018600006D0012211464046FFF7C7FF98B93DE078 +:1018700051B1002201214046FFF7BFFF58B9600770 +:1018800034D50122114620E060B1012200214046FA +:10189000FFF7B3FF10B10920BDE8F081680725D537 +:1018A000012206E068074FEA44700AD5002814DBDD +:1018B000002201214046FFF7A0FFB8B125F0040542 +:1018C00014E0002812DA012200214046FFF795FFBC +:1018D00060B100BF24F0040408E001221146404634 +:1018E000FFF78BFF10B125F00405F3E73D7034706E +:1018F0000020D1E770B58AB0044600886946FFF73A +:101900000BFE002806D1A08830B1012804D002289F +:1019100002D012200AB070BD04AB03AA214668466B +:10192000FFF782FF0500F5D19DF800100120002689 +:101930000029019906D081F8C101019991F80C1292 +:10194000B1BB2DE081F82F01019991F8561139B9F9 +:10195000019991F82E1119B9019991F8971009B1CF +:101960003A2519E00199059681F82E01019A9DF812 +:101970000C0082F83001019B9DF8102083F8312182 +:10198000A388019CA4F832318DF814008DF815203D +:1019900005AA0020FFF70EFC019880F82F6126E0D1 +:1019A000019991F8C01119B9019991F8971009B1ED +:1019B0003A2519E00199059681F8C00101989DF832 +:1019C0000C2080F8C221019B9DF8100083F8C30110 +:1019D000A388019CA4F8C4318DF814208DF815005B +:1019E00005AA0120FFF7E6FB019880F8C1612846AF +:1019F00090E710B504460020A17801B90120E278F3 +:101A00000AB940F0020001F058FB002803D120463B +:101A1000BDE810406EE710BD70B5044691F8650052 +:101A200091F866300D4610F00C0F00D1002321898B +:101A3000A088FFF774FB696A814229D2401A401CD2 +:101A4000A1884008091A8AB2A2802189081A208137 +:101A5000668895F864101046FFF73FFB864200D277 +:101A600030466080E68895F8651020890AE000001D +:101A70007800002018080020FFFFFFFF1F00000073 +:101A8000D8060020FFF729FB864200D23046E080CE +:101A900070BDF0B585B00D46064603A9FFF73CFDC5 +:101AA00000282DD19DF80C0060B300220499FB2082 +:101AB000B1F84E30FB2B00D30346B1F85040FB2069 +:101AC000FB2C00D30446DFF85CC59CE88110009035 +:101AD0000197CDF808C0ADF80230ADF80640684671 +:101AE000FFF79AFF6E80BDF80400E880BDF808009B +:101AF0006881BDF80200A880BDF80600288100209A +:101B000005B0F0BD0122D1E72DE9F04186B00446D1 +:101B100000886946FFF700FD002876D12189E0881A +:101B200001F0E4FA002870D1A188608801F0DEFAA3 +:101B300000286AD12189E08801F0CFFA002864D119 +:101B4000A188608801F0C9FA07005ED1208802A947 +:101B5000FFF79FFF00B1FFDFBDF81010628809207A +:101B6000914252D3BDF80C10E28891424DD3BDF89A +:101B70001210BDF80E2023891144A2881A44914204 +:101B800043D39DF80010019D4FF00008012640F658 +:101B9000480041B185F8B761019991F8F81105F550 +:101BA000DB7541B91AE085F82561019991F84A1170 +:101BB00005F5927509B13A2724E0E18869806188CA +:101BC000E9802189814200D30146A980A188814210 +:101BD00000D208462881012201990FE0E18869803E +:101BE0006188E9802189814200D30146A980A188CA +:101BF000814200D208462881019900222846FFF739 +:101C00000BFF2E7085F80180384606B044E67BE76E +:101C100070B504460CF0FCFDB0B12078182811D145 +:101C2000207901280ED1E088062102F03FF9040056 +:101C300008D0208807F010FC2088062102F048F91F +:101C400000B1FFDF012070BDF74D28780028FAD0E1 +:101C5000002666701420207020223146201DFCF7DB +:101C600016FC022020712E70ECE710B50446FCF73C +:101C7000DBFC002813D0207817280FD1207968B119 +:101C8000E088072102F012F940B1008807F0E4FB78 +:101C9000E088072102F01CF900B1FFDF012010BD30 +:101CA0002DE9F0475FEA000800D1FFDFDE4802219E +:101CB0001A308146FFF7E4FC00B1FFDFDA4C062062 +:101CC000678B02F09BF80546072002F097F828443E +:101CD000C5B2681CC6B2608BB04203D14046FFF764 +:101CE000C4FF58B9608BA84203D14046FFF790FF6C +:101CF00020B9608B4146FFF725FC38B1404601F022 +:101D000003FA0028E7D10120BDE8F0870221484608 +:101D1000FFF7B6FC10B9608BB842DCD14046BDE895 +:101D2000F04712F0C1BA10B501F053F908B10C2018 +:101D300010BD0BF07DFC002010BD10B504460078EE +:101D400018B1012801D0122010BD01F053F920B1C3 +:101D50000BF0C0FD08B10C2010BD207801F013F984 +:101D6000E21D04F11703611CBDE810400BF0DABC62 +:101D700010B5044601F02DF908B10C2010BD2078F3 +:101D800028B1012803D0FF280BD0122010BD01F08C +:101D9000FAF8611C0BF00CFC08B1002010BD072004 +:101DA00010BD01200BF03EFCF7E710B50BF095FDE0 +:101DB00008B1002010BD302010BD10B5044601F060 +:101DC00019F908B10C2010BD20460BF080FD002051 +:101DD00010BD10B501F00EF920B10BF07BFD08B17C +:101DE0000C2010BD0BF0F6FC002010BDFF2181700F +:101DF0004FF6FF7181808D4949680A7882718A881F +:101E000002814988418101214170002070477CB5E1 +:101E10000025022A19D015DC12F10C0F15D009DCAF +:101E200012F1280F11D012F1140F0ED012F1100F71 +:101E300011D10AE012F1080F07D012F1040F04D0FB +:101E40004AB902E0D31E052B05D8012806D0022886 +:101E500008D003280AD0122528467CBD1046FDF77D +:101E600013F8F9E710460CF06BFEF5E70846144648 +:101E70006946FFF751FB08B10225EDE79DF8000028 +:101E80000198002580F86740E6E710B51346012267 +:101E9000FEF7EAFF002010BD10B5044610F02FFA3F +:101EA000052804D020460FF029FC002010BD0C208E +:101EB00010BD10B5044601F09DF808B10C2010BD0E +:101EC0002146002007F037FB002010BD10B5044666 +:101ED0000FF0A3FC50B108F0A6FD38B1207808F04F +:101EE00029FB20780DF04DF9002010BD0C2010BD0D +:101EF00010B5044601F07EF808B10C2010BD214653 +:101F0000012007F018FB002010BD38B504464FF63D +:101F1000FF70ADF80000A079E179884216D02079F1 +:101F2000FCF7E3FA90B16079FCF7DFFA70B10022B8 +:101F3000A079114612F0A0FD40B90022E0791146C7 +:101F400012F09AFD10B9207A072801D9122038BD65 +:101F500008F076FD60B910F0D2F948B90021684662 +:101F6000FFF78EFB20B1204606F044F9002038BD73 +:101F70000C2038BD2DE9FC41817805461A2925D071 +:101F80000EDC16292ED2DFE801F02D2D2D2D2D216E +:101F90002D2D2D2D2D2D2D2D2D2D2D2D2D21212195 +:101FA0002A291FD00BDCA1F11E010C291AD2DFE86F +:101FB00001F019191919191919191919190D3A399D +:101FC00004290FD2DFE801F00E020E022888B0F5D6 +:101FD000706F07D201276946FFF79EFA20B10220F1 +:101FE000BDE8FC811220FBE79DF8000000F0D2FF65 +:101FF000019C10B104F58A7401E004F5C6749DF8E3 +:10200000000000F0C7FF019E10B106F2151601E0B6 +:1020100006F28D166846FFF76DFA08B1207838B1E0 +:102020000C20DDE70C620200180800207800002078 +:102030002770A8783070684601F030F80020CFE7AC +:102040007CB50D466946FFF767FA002618B12E6089 +:102050002E7102207CBD9DF8000000F09BFF019CCA +:102060009DF80000703400F095FF019884F84260FC +:1020700081682960017B297194F842100029F5D10B +:1020800000207CBD10B5044600F0B4FF20B10BF079 +:1020900021FC08B10C2010BD207800F074FFE2791B +:1020A000611C0BF093FD08B1002010BD022010BD93 +:1020B00010B5886E60B1002241F8682F0120CA7106 +:1020C0008979884012F0CCFC002800D01F2010BD78 +:1020D0000C2010BD1CB50C466946FFF71DFA002800 +:1020E00009D19DF8000000280198B0F8700000D0D8 +:1020F000401C208000201CBD1CB504460088694699 +:10210000FFF70AFA08B102201CBD606828B1DDE9BA +:102110000001224601F04CF81CBDDDE90001FFF78B +:10212000C7FF1CBD70B51C460D4618B1012801D073 +:10213000122070BD1946104601F078F830B12146E2 +:10214000284601F07DF808B1002070BD302070BD38 +:1021500070B5044600780E46012804D018B1022854 +:1021600001D0032840D1607828B1012803D002288B +:1021700001D0032838D1E07B10B9A078012833D1F1 +:10218000A07830F005012FD110F0050F2CD0628916 +:10219000E188E0783346FFF7C5FF002825D1A07815 +:1021A00005281DD16589A289218920793346FFF749 +:1021B000B9FF002819D1012004EB40014A891544D8 +:1021C0002218D378927893420ED1CA8889888A429D +:1021D0000AD1401CC0B20228EED3E088A84203D343 +:1021E000A07B08B1072801D9122070BD002070BD66 +:1021F00010B586B0044600F0E1FE10B10C2006B028 +:1022000010BD022104F10A0001F02FF8A0788DF82A +:102210000800A0788DF8000060788DF80400207820 +:102220008DF80300A07B8DF80500E07B00B1012054 +:102230008DF80600A078C10717D0E07801F00CF8FF +:102240008DF80100E088ADF80A006089ADF80C0057 +:10225000A078400716D5207900F0FEFF8DF8020027 +:102260002089ADF80E00A0890AE040070AD5E07881 +:1022700000F0F2FF8DF80200E088ADF80E006089F2 +:10228000ADF8100002A80FF0D4FA0028B7D16846C4 +:102290000CF07CFFB3E710B504460121FFF758FFAF +:1022A000002803D12046BDE81040A1E710BD027808 +:1022B000012A01D0BAB118E042783AB1012A05D01A +:1022C000022A12D189B1818879B100E059B14188DF +:1022D00049B1808838B101EB8101490000EB8000F1 +:1022E000B1EB002F01D2002070471220704770B56B +:1022F000044600780D46012809D010F000F80528A2 +:1023000003D00FF0A6F9002800D00C2070BD0CF00F +:102310000AFE88B10CF01CFE0CF018FF0028F5D165 +:1023200025B160780CF0ACFE0028EFD1A188608860 +:10233000BDE870400FF0A3BA122070BD10B504467E +:102340000121FFF7B4FF002804D12046BDE810406A +:102350000121CCE710BDF0B5871FDDE9056540F62A +:102360007B44A74213D28F1FA74210D288420ED8B7 +:10237000B2F5FA7F0BD2A3F10A00241FA04206D2C5 +:10238000521C4A43B2EB830F01DAAE4201D900205E +:10239000F0BD0120F0BD2DE9FC47477A894604468F +:1023A00017F0050F7ED0F8087CD194F83A0008B9F0 +:1023B000012F77D10025A8462E46F90789F0010A9A +:1023C00019D0208A514600F031FFE8B360895146A8 +:1023D00000F036FFC0B3208A6189884262D8A18E9E +:1023E000E08DCDE90001238D628CA18BE08AFFF79F +:1023F000B2FF48B30125B8070ED504EB4500828E25 +:10240000C18DCDE90012038D428C818BC08AFFF70C +:10241000A2FFC8B1A8466D1C78071ED504EB45067F +:102420005146308A00F002FF70B17089514600F0C9 +:1024300007FF48B1308A7189884253D8B18EF08D38 +:10244000CDE90001338D00E00BE0728CB18BF08A96 +:10245000FFF781FF28B12E466D1CB9F1000F03D0A4 +:1024600030E03020BDE8FC87F80707D0780705D5B5 +:1024700004EB460160894989884233D1228A0121CF +:102480001BE0414503D004EB4100008A024404EB09 +:102490004100C38A868AB34224D1838B468BB342E0 +:1024A00020D100E01EE0438C068CB3421AD1038D8C +:1024B000C08C834216D1491CC9B2A942E1D36089BC +:1024C00090420FD3207810B101280BD102E0A07800 +:1024D0000028F9D1607838B1012805D0022803D04E +:1024E000032801D01220BDE70020BBE7002152E7FE +:1024F0000178C90702D0406811F0A9BE11F076BE7C +:1025000010B50078012800D00020FCF7B8FC0020AE +:1025100010BD2DE9F0478EB00D46AFF6A422D2E9EA +:102520000092014690462846FFF735FF06000CD181 +:1025300000F044FD40B9FE4F387828B90CF0B2F9EC +:10254000A0F57F41FF3903D00C200EB0BDE8F08725 +:10255000032105F1100000F088FEF54809AA3E3875 +:102560000990F4480A90F248062110380B900CA804 +:1025700001F06AFC040037D00021FEF77CF904F179 +:1025800030017B8ABA8ACB830A84797C0091BA466F +:102590003B7CBA8A798A208801F044FD00B1FFDFD4 +:1025A000208806F058FF218804F10E0000F02CFD71 +:1025B000E1A004F1120700680590032105A804F0CA +:1025C0006DFF002005A90A5C3A54401CC0B20328E4 +:1025D000F9D3A88B6080688CA080288DE080687A11 +:1025E000410703D508270AE00920AEE7C10701D05B +:1025F000012704E0800701D5022700E000273A46C2 +:10260000BAF8160011460FF0CFF90146A062204635 +:102610000FF0D8F93A4621460020FEF7AEFD00B98A +:102620000926C34A21461C320020FEF7C3FD0027BD +:1026300084F8767084F87770A87800F0A4FC60764F +:10264000D5F80300C4F81A00B5F80700E083C4F811 +:10265000089084F80C80012084F8200101468DF850 +:102660000070684604F01AFF9DF8000000F00701B2 +:10267000C0F3C1021144C0F3401008448DF80000BB +:10268000401D2076092801D20830207601212046FD +:10269000FEF7F1F868780CF051FCEEBBA9782878C9 +:1026A000EA1C0CF01EFC48B10CF052FCA97828780A +:1026B000EA1C0CF0BFFC060002D052E0122650E0EB +:1026C000687A00F005010020CA0700D001208A07BF +:1026D00001D540F00200490701D540F008000CF098 +:1026E000E9FB06003DD1214603200CF0CDFC06009D +:1026F00037D10CF0D2FC060033D1697A01F0050124 +:102700008DF80810697AC90708D06889ADF80A0001 +:10271000288AADF80C0000E023E00120697A8A07DE +:1027200000D5401C490707D505EB40004189ADF8AD +:102730000E10008AADF8100002A80FF07AF80646D5 +:1027400095F83A0000B101200CF0C6FB4EB90CF030 +:10275000FDFC060005D1A98F20460FF00BF80600FE +:1027600008D0208806F078FE2088062101F0B0FB12 +:1027700000B1FFDF3046E8E601460020C9E638B583 +:102780006B48007878B90FF0BAFD052805D00CF039 +:1027900089F8A0F57F41FF3905D068460FF0B3F8FE +:1027A000040002D00CE00C2038BD0098008806F030 +:1027B00053FE00980621008801F08AFB00B1FFDF7C +:1027C000204638BD1CB582894189CDE900120389B4 +:1027D000C28881884088FFF7BEFD08B100201CBD7B +:1027E00030201CBD70B50546FFF7ECFF00280ED168 +:1027F0002888062101F05AFB040007D000F042FCB3 +:1028000020B1D4F81801017831B901E0022070BD7F +:10281000D4F86411097809B13A2070BD052181719D +:10282000D4F8181100200881D4F81811A88848811C +:10283000D4F81811E8888881D4F818112889C8813B +:10284000D4F81801028941898A4204D88279082A79 +:1028500001D88A4201D3122070BD29884180D4F862 +:10286000181102200870002070BD3EB50446FEF726 +:1028700075FAB0B12E480125A0F1400245702368D9 +:1028800042F8423F237900211371417069460620C6 +:1028900001F095FA00B1FFDF684601F06EFA10B161 +:1028A0000EE012203EBDBDF80440029880F8205191 +:1028B000684601F062FA18B9BDF80400A042F4D1EC +:1028C00000203EBD70B505460088062101F0EEFAF5 +:1028D000040007D000F0D6FB20B1D4F81811087816 +:1028E00030B901E0022070BDD4F86401007808B16D +:1028F0003A2070BDB020005D10F0010F22D0D5F855 +:1029000002004860D5F806008860D4F8180169898B +:1029100010228181D4F8180105F10C010E3004F564 +:102920008C74FBF78AFD216803200870288805E075 +:1029300018080020840000201122330021684880FC +:10294000002070BD0C2070BD38B504460078EF281B +:102950004DD86088ADF80000009800F097FC88B36F +:102960006188080708D4D4E9012082423FD8202A90 +:102970003DD3B0F5804F3AD8207B18B3072836D81E +:10298000607B28B1012803D0022801D003282ED172 +:102990004A0703D4022801D0032805D1A07B08B13F +:1029A000012824D1480707D4607D28B1012803D02D +:1029B000022801D003281AD1C806E07D03D50128DA +:1029C00015D110E013E0012801D003280FD1C8066B +:1029D00009D4607E012803D0022801D0032806D143 +:1029E000A07E0F2803D8E07E18B1012801D0122064 +:1029F00038BD002038BDF8B514460D46064608F02F +:102A00001FF808B10C20F8BD3046FFF79DFF0028E5 +:102A1000F9D1FCF73EFA2870B07554B9FF208DF853 +:102A2000000069460020FCF71EFA69460020FCF70A +:102A30000EFA3046BDE8F840FCF752B90022DAE75A +:102A40000078C10801D012207047FA4981F82000AF +:102A50000020704710B504460078C00704D1608894 +:102A600010B1FCF7D7F980B12078618800F001023D +:102A7000607800F02FFC002806D1FCF7B3F901467E +:102A80006088884203D9072010BD122010BD6168FC +:102A9000FCF7E9F9002010BD10B504460078C00726 +:102AA00004D1608810B1FBF78AFE70B1207861888C +:102AB00000F00102607800F00DFC002804D160886D +:102AC0006168FCF7C4F9002010BD122010BD7CB570 +:102AD000044640784225012808D8A078FBF767FE15 +:102AE00020B120781225012802D090B128467CBD63 +:102AF000FCF7DBF920B1A0880028F7D08028F5D8B2 +:102B0000FCF7DAF960B160780028EFD0207801286E +:102B100008D006F0C3FD044607F05DFC00287FD016 +:102B20000C207CBDFBF7F5FF10B9FCF7B7F990B3AB +:102B300007F086FF0028F3D1FBF700FEA0F57F41E8 +:102B4000FF39EDD1FCF707F8A68842F21070464332 +:102B5000A079FCF770F9FBF739FEF8B100220721E4 +:102B600001A801F071F9040058D0B3480021846035 +:102B70002046FDF72DFD2046FCF732FDAD4D04F15A +:102B800030006A8AA98AC2830184FBF726FE60B1FD +:102B9000E88A01210DE0FFE712207CBD31460020CC +:102BA00007F0CBFC88B3FFDF44E0FCF787F9014670 +:102BB000E88A07F091FD0146A0620022204606F057 +:102BC00070FDFBF70AFE38B9FCF778F9024621469A +:102BD0000120FEF7D2FAD0B1964A21461C320120DC +:102BE000FEF7E8FA687C00902B7CAA8A698A208824 +:102BF00001F018FA00B1FFDF208806F02CFC314606 +:102C0000204607F09AFC00B1FFDF13E008E007213F +:102C1000BDF8040001F05CF900B1FFDF09207CBDC4 +:102C200044B1208806F018FC2088072101F050F9F3 +:102C300000B1FFDF00207CBD002148E770B50D46E4 +:102C4000072101F033F9040003D094F88F0110B18B +:102C50000AE0022070BD94F87D00142801D01528E8 +:102C600002D194F8DC0108B10C2070BD1022294675 +:102C700004F5C870FBF7E1FB012084F88F01002008 +:102C800070BD10B5072101F011F918B190F88F113E +:102C900011B107E0022010BD90F87D10142903D077 +:102CA000152901D00C2010BD022180F88F110020C1 +:102CB00010BD2DE9FC410C464BF6803212219442A6 +:102CC0001DD8E4B16946FEF727FC002815D19DF810 +:102CD000000000F05FF9019E9DF80000703600F0E2 +:102CE00059F9019DAD1C2F88224639463046FDF723 +:102CF00065FC2888B842F6D10020BDE8FC81084672 +:102D0000FBE77CB5044600886946FEF705FC002811 +:102D100010D19DF8000000F03DF9019D9DF80000E4 +:102D2000703500F037F90198A27890F82C10914294 +:102D300001D10C207CBD7F212972A9720021E9728A +:102D4000E17880F82D10217980F82E10A17880F894 +:102D50002C1000207CBD1CB50C466946FEF7DCFB40 +:102D600000280AD19DF8000000F014F9019890F8AD +:102D70008C0000B10120207000201CBD7CB50D46E8 +:102D800014466946FEF7C8FB002809D19DF80000EB +:102D900000F000F9019890F82C00012801D00C20D7 +:102DA0007CBD9DF8000000F0F5F8019890F87810CF +:102DB000297090F87900207000207CBD70B50D4618 +:102DC0001646072101F072F818B381880124C388E0 +:102DD000428804EB4104AC4217D842F210746343BA +:102DE000A4106243B3FBF2F2521E94B24FF4FA7293 +:102DF000944200D91446A54200D22C46491C641CBA +:102E0000B4FBF1F24A43521E91B290F8C8211AB9AC +:102E100001E0022070BD01843180002070BD10B53A +:102E20000C46072101F042F840B1022C08D91220CB +:102E300010BD000018080020780000200220F7E7ED +:102E400014F0010180F8FD10C4F3400280F8FC206A +:102E500004D090F8FA1009B107F054FC0020E7E71D +:102E6000017889B1417879B141881B290CD38188D7 +:102E70001B2909D3C188022906D3F64902680A65CD +:102E800040684865002070471220704710B504461E +:102E90000EF086FD204607F0D8FB0020C8E710B5ED +:102EA00007F0D6FB0020C3E72DE9F04115460F4699 +:102EB00006460122114638460EF076FD04460121F1 +:102EC000384607F009FC844200D20446012130460E +:102ED00000F065F806460121002000F060F8311886 +:102EE000012096318C4206D901F19600611AB1FB9E +:102EF000F0F0401C80B228800020BDE8F08110B5C1 +:102F0000044600F077F808B10C2091E7601C0AF045 +:102F100038FE207800F00100FBF718FE207800F062 +:102F200001000CF010F8002082E710B504460720DD +:102F300000F056FF08B10C207AE72078C00711D0C6 +:102F400000226078114611F097FD08B112206FE75A +:102F5000A06809F01DFB6078D4F8041009F021FB8B +:102F6000002065E7002009F013FB00210846F5E783 +:102F700010B505F036FE00205AE710B5006805F0E0 +:102F800084F8002054E718B1022801D001207047CE +:102F90000020704708B1002070470120704710B52D +:102FA000012904D0022905D0FFDF204640E7C000F8 +:102FB000503001E080002C3084B2F6E710B50FF0FD +:102FC0009EF9042803D0052801D0002030E7012015 +:102FD0002EE710B5FFF7F2FF10B10CF07BF828B91F +:102FE00007F02EFD20B1FBF78CFD08B101201FE793 +:102FF00000201DE710B5FFF7E1FF18B907F020FD2D +:10300000002800D0012013E72DE9FE4300250F46DC +:1030100080460A260421404604F069FA4046FDF73E +:103020003EFE062000F0EAFE044616E06946062051 +:1030300000F0C5FE0BE000BFBDF80400B84206D0AA +:103040000298042241460E30FBF7CAF950B1684697 +:1030500000F093FE0500EFD0641E002C06DD002D6D +:10306000E4D005E04046FDF723FEF5E705B9FFDFB4 +:10307000D8F80000FDF737FE761E01D00028C9D031 +:10308000BDE8FE8390F8F01090F88C0020B919B1DB +:10309000042901D0012070470020704701780029E1 +:1030A0000AD0416891F8FA20002A05D0002281F860 +:1030B000FA20406807F026BB704770B514460546F5 +:1030C000012200F01BF9002806D121462846BDE860 +:1030D0007040002200F012B970BDFB2802D8B1F593 +:1030E000296F01D911207047002070471B38E12853 +:1030F00006D2B1F5A47F03D344F29020814201D9D6 +:1031000012207047002070471FB55249403191F896 +:103110002010CA0702D102781D2A0AD08A0702D4D9 +:1031200002781C2A28D049073DD40178152937D0C8 +:1031300039E08088ADF8000002A9FEF7EDF900B192 +:10314000FFDF9DF80800FFF725FF039810F8601FC8 +:103150008DF8021040788DF803000020ADF80400CF +:1031600001B9FFDF9DF8030000B9FFDF6846FEF7F5 +:1031700040FCD8B1FFDF19E08088ADF800004FF4C3 +:103180002961FB20ADF80410ADF80200ADF806008F +:10319000ADF808106846FEF73AFD38B1FFDF05E0EC +:1031A000807BC00702D0002004B041E60120FBE78D +:1031B000F8B50746508915460C4640B1B0F5004FAA +:1031C00005D20022A878114611F056FC08B1122051 +:1031D000F8BDA06E04F1700630B1A97894F86E00C5 +:1031E000814201D00C20F8BD012184F86F10A9782C +:1031F00084F86E106968A1666989A4F86C10288942 +:10320000B084002184F86F1028886946FEF762FFB9 +:10321000B08CBDF80010081A00B2002804DD214669 +:103220003846FEF745FFDDE70020F8BD042803D34C +:1032300021B9B0F5804F01D90020704701207047B7 +:10324000042803D321B9B0F5804F01D9002070477D +:1032500001207047D8070020012802D018B10020B3 +:103260007047022070470120704710B500224FF4CC +:10327000C84408E030F81230A34200D9234620F8B1 +:103280001230521CD2B28A42F4D3D1E580B2C106C8 +:103290000BD401071CD481064FEAC07101D5B9B91E +:1032A00000E099B1800713D410E0410610D48106E4 +:1032B0000ED4C1074FEA807104D0002902DB400719 +:1032C00004D405E0010703D4400701D4012070476E +:1032D0000020704770B50C460546FF2904D8FBF75F +:1032E0007CFA18B11F2C01D9122070BD2846FBF7BB +:1032F0005EFA08B1002070BD422070BD0AB1012203 +:1033000000E00222024202D1C80802D109B1002025 +:10331000704711207047000030B5058825F400443F +:1033200021448CB24FF4004194420AD2121B92B253 +:103330001B339A4201D2A94307E005F4004121431F +:1033400003E0A21A92B2A9431143018030BD0844A0 +:10335000083050434A31084480B2704770B51D466A +:1033600016460B46044629463046049AFFF7EFFFFF +:103370000646B34200D2FFDF282200212046FBF799 +:1033800086F84FF6FF70A082283EB0B26577608065 +:10339000B0F5004F00D9FFDF618805F13C008142A4 +:1033A00000D2FFDF60880835401B343880B22080AF +:1033B0001B2800D21B2020800020A07770BD8161D7 +:1033C000886170472DE9F05F0D46C188044600F121 +:1033D0002809008921F4004620F4004800F063FB2E +:1033E00010B10020BDE8F09F4FF0000A4FF0010B34 +:1033F000B0450CD9617FA8EB0600401A0838854219 +:1034000019DC09EB06000021058041801AE0608884 +:10341000617F801B471A083F0DD41B2F00DAFFDFA6 +:10342000BD4201DC294600E0B9B2681A0204120C60 +:1034300004D0424502DD84F817A0D2E709EB06006C +:103440000180428084F817B0CCE770B5044600F1E3 +:103450002802C088E37D20F400402BB1104402888C +:10346000438813448B4201D2002070BD00258A425C +:1034700002D30180458008E0891A0904090C4180C3 +:1034800003D0A01D00F01FFB08E0637F0088083315 +:10349000184481B26288A01DFFF73EFFE575012048 +:1034A00070BD70B5034600F12804C588808820F4FB +:1034B00000462644A84202D10020188270BD988997 +:1034C0003588A84206D3401B75882D1A2044ADB21A +:1034D000C01E05E02C1AA5B25C7F20443044401D7C +:1034E0000C88AC4200D90D809C8924B10024147052 +:1034F0000988198270BD0124F9E770B5044600F10E +:103500002801808820F400404518208A002825D012 +:10351000A189084480B2A08129886A881144814227 +:1035200000D2FFDF2888698800260844A1898842E4 +:1035300012D1A069807F2871698819B1201D00F01F +:10354000C2FA08E0637F28880833184481B2628891 +:10355000201DFFF7E1FEA6812682012070BD2DE926 +:10356000F041418987880026044600F12805B942C8 +:1035700019D004F10A0800BF21F400402844418812 +:1035800019B1404600F09FFA08E0637F00880833D5 +:10359000184481B262884046FFF7BEFE761C6189FE +:1035A000B6B2B942E8D13046BDE8F0812DE9F0412C +:1035B00004460B4627892830A68827F40041B4F832 +:1035C0000A8001440D46B74201D10020ECE70AB160 +:1035D000481D106023B1627F691D1846FAF72DFF60 +:1035E0002E88698804F1080021B18A1996B200F08A +:1035F0006AFA06E0637F62880833991989B2FFF797 +:103600008BFE474501D1208960813046CCE7818817 +:10361000C088814201D10120704700207047018994 +:103620008088814201D1012070470020704770B529 +:103630008588C38800F1280425F4004223F4004162 +:1036400014449D421AD08389058A5E1925886388AF +:10365000EC18A64214D313B18B4211D30EE0437F72 +:1036600008325C192244408892B2801A80B2233317 +:10367000984201D211B103E08A4201D1002070BD0D +:10368000012070BD2DE9F0478846C18804460089B5 +:1036900021F4004604F1280720F4004507EB060951 +:1036A00000F001FA002178BBB54204D9627FA81B63 +:1036B000801A002503E06088627F801B801A08382A +:1036C00023D4E28962B1B9F80020B9F802303BB1E5 +:1036D000E81A2177404518DBE0893844801A09E070 +:1036E000801A217740450ADB607FE1890830304449 +:1036F00039440844C01EA4F81280BDE8F08745454F +:1037000003DB01202077E7E7FFE761820020F4E791 +:103710002DE9F74F044600F12805C088884620F4BB +:10372000004A608A05EB0A0608B1404502D2002033 +:10373000BDE8FE8FE08978B13788B6F8029007EBD4 +:103740000901884200D0FFDF207F4FF0000B50EAD4 +:10375000090106D088B33BE00027A07FB94630714D +:10376000F2E7E18959B1607F2944083050440844A8 +:10377000B4F81F1020F8031D94F821108170E2891D +:1037800007EB080002EB0801E1813080A6F802B0E7 +:1037900002985F4650B1637F30880833184481B285 +:1037A0006288A01DFFF7B8FDE78121E0607FE18915 +:1037B00008305044294408442DE0FFE7E089B4F87C +:1037C0001F102844C01B20F8031D94F8211081709D +:1037D00009EB0800E28981B202EB0800E081378042 +:1037E00071800298A0B1A01D00F06DF9A4F80EB090 +:1037F000A07F401CA077A07D08B1E088A08284F85B +:1038000016B000BFA4F812B084F817B001208FE7FB +:10381000E0892844C01B30F8031DA4F81F108078ED +:1038200084F82100EEE710B5818800F1280321F427 +:1038300000442344848AC288A14212D0914210D00D +:10384000818971B9826972B11046FFF7E8FE50B9FB +:103850001089283220F400401044197900798842F8 +:1038600001D1002010BD184610BD00F12803407F93 +:1038700008300844C01E1060088808B9DB1E1360B9 +:1038800008884988084480B270472DE9F04100F16A +:103890002806407F1C4608309046431808884D880B +:1038A000069ADB1EA0B1C01C80B2904214D9801AC7 +:1038B000A04200DB204687B298183A464146FAF704 +:1038C0008FFD002816D1E01B84B2B844002005E02B +:1038D000ED1CADB2F61EE8E7101A80B20119A9423C +:1038E00006D8304422464146BDE8F041FAF778BD9B +:1038F0004FF0FF3058E62DE9F04100F12804407FF9 +:103900001E46083090464318002508884F88069ABE +:10391000DB1E90B1C01C80B2904212D9801AB04216 +:1039200000DB304685B299182A464046FAF785FDF5 +:10393000701B86B2A844002005E0FF1CBFB2E41E45 +:10394000EAE7101A80B28119B94206D82118324626 +:103950004046FAF772FDA81985B2284624E62DE9FB +:10396000F04100F12804407F1E460830904643187D +:10397000002508884F88069ADB1E90B1C01C80B2D3 +:10398000904212D9801AB04200DB304685B29818B6 +:103990002A464146FAF751FD701B86B2A844002022 +:1039A00005E0FF1CBFB2E41EEAE7101A80B28119DD +:1039B000B94206D8204432464146FAF73EFDA819DE +:1039C00085B22846F0E5401D704710B5044600F169 +:1039D0002801C288808820F400431944904206D010 +:1039E000A28922B9228A12B9A28A904201D100206A +:1039F00010BD0888498831B1201D00F064F800200E +:103A00002082012010BD637F62880833184481B290 +:103A1000201DFFF781FCF2E70021C181017741827F +:103A2000C1758175704703881380C28942B1C2880D +:103A300022F4004300F128021A440A60C08970474A +:103A40000020704710B50446808AA0F57F41FF39F9 +:103A500000D0FFDFE088A082E08900B10120A075DE +:103A600010BD4FF6FF71818200218175704710B53E +:103A70000446808AA0F57F41FF3900D1FFDFA07D99 +:103A800028B9A088A18A884201D1002010BD012058 +:103A900010BD8188828A914201D1807D08B10020C9 +:103AA00070470120704720F4004221F400439A42FD +:103AB00007D100F4004001F40041884201D0012008 +:103AC00070470020704730B5044600880D4620F44A +:103AD0000040A84200D2FFDF21884FF40040884315 +:103AE0002843208030BD70B50C00054609D0082C55 +:103AF00000D2FFDF1DB1A1B2286800F044F8201DFC +:103B000070BD0DB100202860002070BD002102684A +:103B100003E093881268194489B2002AF9D100F0B1 +:103B200032B870B500260D460446082900D2FFDFE2 +:103B3000206808B91EE0044620688188A94202D0A6 +:103B400001680029F7D181880646A94201D10068A1 +:103B50000DE005F1080293B20022994209D32844EE +:103B6000491B026081802168096821600160206032 +:103B700000E00026304670BD00230B608A8002689A +:103B80000A600160704700234360021D01810260EA +:103B90007047F0B50F460188408815460C181E4640 +:103BA000AC4200D3641B3044A84200D9FFDFA01907 +:103BB000A84200D9FFDF3819F0BD2DE9F041884651 +:103BC00006460188408815460C181F46AC4200D3B3 +:103BD000641B3844A84200D9FFDFE019A84200D98D +:103BE000FFDF70883844708008EB0400BDE8F08186 +:103BF0002DE9F041054600881E461746841B88467D +:103C0000BC4200D33C442C8068883044B84200D980 +:103C1000FFDFA019B84200D9FFDF68883044688010 +:103C200008EB0400E2E72DE9F04106881D46044652 +:103C3000701980B2174688462080B84201D3C01B55 +:103C400020806088A84200D2FFDF7019B84200D9F6 +:103C5000FFDF6088401B608008EB0600C6E730B5D8 +:103C60000D460188CC18944200D3A41A408898428B +:103C700000D8FFDF281930BD2DE9F041C84D0446BA +:103C80009046A8780E46A04200D8FFDF05EB8607D5 +:103C9000B86A50F8240000B1FFDFB868002816D0D9 +:103CA000304600F044F90146B868FFF73AFF0500D6 +:103CB0000CD0B86A082E40F8245000D3FFDFB94872 +:103CC0004246294650F82630204698472846BDE807 +:103CD000F0812DE9F8431E468C1991460F460546A2 +:103CE000FF2C00D9FFDFB14500D9FFDFE4B200951A +:103CF0004DB300208046E81C20F00300A84200D00D +:103D0000FFDF4946DFF89892684689F8001089F885 +:103D1000017089F8024089F8034089F8044089F865 +:103D2000054089F8066089F80770414600F008F9F7 +:103D3000002142460F464B460098C01C20F003006D +:103D4000009012B10EE00120D4E703EB8106B062CF +:103D5000002005E0D6F828C04CF82070401CC0B206 +:103D6000A042F7D30098491C00EB8400C9B2009030 +:103D70000829E1D3401BBDE8F88310B50446EDF7F0 +:103D80008EFA08B1102010BD2078854A618802EBB8 +:103D9000800092780EE0836A53F8213043B14A1CC8 +:103DA0006280A180806A50F82100A060002010BDD0 +:103DB000491C89B28A42EED86180052010BD70B5D9 +:103DC00005460C460846EDF76AFA08B1102070BDAA +:103DD000082D01D3072070BD25700020608070BDC4 +:103DE0000EB56946FFF7EBFF00B1FFDF6846FFF74E +:103DF000C4FF08B100200EBD01200EBD10B5044661 +:103E0000082800D3FFDF6648005D10BD3EB50546BB +:103E100000246946FFF7D3FF18B1FFDF01E0641CFF +:103E2000E4B26846FFF7A9FF0028F8D02846FFF75C +:103E3000E5FF001BC0B23EBD59498978814201D9D6 +:103E4000C0B27047FF2070472DE9F041544B06295E +:103E500003D007291CD19D7900E0002500244FF6EE +:103E6000FF7603EB810713F801C00AE06319D7F866 +:103E700028E09BB25EF823E0BEF1000F04D0641C82 +:103E8000A4B2A445F2D8334603801846B34201D108 +:103E900000201CE7BDE8F041EEE6A0F57F43FF3BC4 +:103EA00001D0082901D300207047E5E6A0F57F4244 +:103EB000FF3A0BD0082909D2394A9378834205D9B1 +:103EC00002EB8101896A51F8200070470020704799 +:103ED0002DE9F04104460D46A4F57F4143F202006E +:103EE000FF3902D0082D01D30720F0E62C494FF00E +:103EF00000088A78A242F8D901EB8506B26A52F826 +:103F00002470002FF1D027483946203050F8252062 +:103F100020469047B16A284641F8248000F007F80F +:103F200002463946B068FFF727FE0020CFE61D495C +:103F3000403131F810004FF6FC71C01C084070474A +:103F40002DE9F843164E8846054600242868C01C13 +:103F500020F0030028602046FFF7E9FF315D484369 +:103F6000B8F1000F01D0002200E02A68014600925B +:103F700032B100274FEA0D00FFF7B5FD1FB106E093 +:103F800001270020F8E706EB8401009A8A6029687F +:103F9000641C0844E4B22860082CD7D3EBE6000088 +:103FA0003C0800201862020070B50E461D461146FE +:103FB00000F0D3F804462946304600F0D7F82044F4 +:103FC000001D70BD2DE9F04190460D4604004FF0F4 +:103FD000000610D00027E01C20F00300A04200D013 +:103FE000FFDFE5B141460020FFF77DFD0C3000EB1F +:103FF000850617B113E00127EDE7614F04F10C00CE +:10400000AA003C602572606000EB85002060002102 +:104010006068FAF73CFA41463868FFF764FD3046BD +:10402000BDE8F0812DE9FF4F554C804681B02068F6 +:104030009A46934600B9FFDF2068027A424503D9C9 +:10404000416851F8280020B143F2020005B0BDE8F4 +:10405000F08F5146029800F080F886B258460E99CB +:1040600000F084F885B27019001D87B22068A1465F +:1040700039460068FFF755FD04001FD06780258092 +:104080002946201D0E9D07465A4601230095FFF73D +:1040900065F92088314638440123029ACDF800A002 +:1040A000FFF75CF92088C1193846FFF788F9D9F87D +:1040B00000004168002041F82840C7E70420C5E718 +:1040C00070B52F4C0546206800B9FFDF2068017AE3 +:1040D000A9420DD9426852F8251049B1002342F88F +:1040E00025304A880068FFF747FD2168087A06E016 +:1040F00043F2020070BD4A6852F820202AB9401EDF +:10410000C0B2F8D20868FFF701FD002070BD70B59D +:104110001B4E05460024306800B9FFDF3068017A85 +:10412000A94204D9406850F8250000B1041D20467A +:1041300070BD70B5124E05460024306800B9FFDF2F +:104140003068017AA94206D9406850F8251011B1AB +:1041500031F8040B4418204670BD10B50A46012101 +:10416000FFF7F5F8C01C20F0030010BD10B50A469B +:104170000121FFF7ECF8C01C20F0030010BD000087 +:104180008C00002070B50446C2F110052819FAF71A +:1041900054F915F0FF0109D0491ECAB28020A0547D +:1041A0002046BDE870400021FAF771B970BD30B506 +:1041B00005E05B1EDBB2CC5CD55C6C40C454002BCC +:1041C000F7D130BD10B5002409E00B78521E44EA47 +:1041D000430300F8013B11F8013BD2B2DC09002A8D +:1041E000F3D110BD2DE9F04389B01E46DDE9107909 +:1041F00090460D00044622D002460846F949FDF7D4 +:1042000044FE102221463846FFF7DCFFE07B000623 +:1042100006D5F44A3946102310320846FFF7C7FF87 +:10422000102239464846FFF7CDFFF87B000606D539 +:10423000EC4A4946102310320846FFF7B8FF102217 +:1042400000212046FAF723F90DE0103EB6B208EB44 +:104250000601102322466846FFF7A9FF224628469A +:104260006946FDF712FE102EEFD818D0F2B2414683 +:104270006846FFF787FF10234A46694604A8FFF700 +:1042800096FF1023224604A96846FFF790FF2246B6 +:1042900028466946FDF7F9FD09B0BDE8F083102313 +:1042A0003A464146EAE770B59CB01E4605461346BD +:1042B00020980C468DF80800202219460DF10900BF +:1042C000FAF7BBF8202221460DF12900FAF7B5F8DC +:1042D00017A913A8CDE90001412302AA31462846B7 +:1042E000FFF780FF1CB070BD2DE9FF4F9FB014AEEB +:1042F000DDE92D5410AFBB49CDE9007620232031F4 +:104300001AA8FFF76FFF4FF000088DF808804FF0F4 +:1043100001098DF8099054F8010FCDF80A00A08822 +:10432000ADF80E0014F8010C1022C0F340008DF817 +:10433000100055F8010FCDF81100A888ADF8150050 +:1043400015F8010C2C99C0F340008DF8170006A851 +:104350008246FAF772F80AA8834610222299FAF7E1 +:104360006CF8A0483523083802AA40688DF83C80D4 +:10437000CDE900760E901AA91F98FFF733FF8DF84C +:1043800008808DF809902068CDF80A00A088ADF863 +:104390000E0014F8010C1022C0F340008DF810003C +:1043A0002868CDF81100A888ADF8150015F8010CA3 +:1043B0002C99C0F340008DF817005046FAF73DF8ED +:1043C000584610222299FAF738F8864835230838DB +:1043D00002AA40688DF83C90CDE900760E901AA9AB +:1043E0002098FFF7FFFE23B0BDE8F08FF0B59BB03B +:1043F0000C460546DDE922101E461746DDE920324F +:10440000D0F801C0CDF808C0B0F805C0ADF80CC0B8 +:104410000078C0F340008DF80E00D1F80100CDF80F +:104420000F00B1F80500ADF8130008781946C0F385 +:1044300040008DF815001088ADF8160090788DF8C2 +:1044400018000DF119001022F9F7F7FF0DF12900FE +:1044500010223146F9F7F1FF0DF1390010223946EB +:10446000F9F7EBFF17A913A8CDE90001412302AA30 +:1044700021462846FFF7B6FE1BB0F0BDF0B5A3B04D +:1044800017460D4604461E46102202A82899F9F741 +:10449000D4FF06A820223946F9F7CFFF0EA8202224 +:1044A0002946F9F7CAFF1EA91AA8CDE90001502331 +:1044B00002AA314616A8FFF795FE1698206023B091 +:1044C000F0BDF0B589B00446DDE90E070D46397838 +:1044D000109EC1F340018DF8001031789446C1F36D +:1044E00040018DF801101968CDF802109988ADF8D7 +:1044F000061099798DF808100168CDF809108188A7 +:10450000ADF80D1080798DF80F0010236A466146D2 +:1045100004A8FFF74CFE2246284604A9FDF7B5FC87 +:10452000D6F801000090B6F80500ADF80400D7F801 +:104530000100CDF80600B7F80500ADF80A0000202C +:10454000039010236A46214604A8FFF730FE224656 +:10455000284604A9FDF799FC09B0F0BD1FB51C68F9 +:1045600000945B68019313680293526803920246B9 +:1045700008466946FDF789FC1FBD10B588B00446A2 +:104580001068049050680590002006900790084637 +:104590006A4604A9FDF779FCBDF80000208008B048 +:1045A00010BD1FB51288ADF800201A88ADF80220A2 +:1045B0000022019202920392024608466946FDF7E4 +:1045C00064FC1FBD7FB5074B14460546083B9A1C8B +:1045D0006846FFF7E6FF224669462846FFF7CDFF0B +:1045E0007FBD00007062020070B5044600780E4680 +:1045F000012813D0052802D0092813D10EE0A068A5 +:1046000061690578042003F059FA052D0AD0782352 +:1046100000220420616903F0A7F903E00420616926 +:1046200003F04CFA31462046BDE8704001F08AB8EC +:1046300010B500F12D03C2799C78411D144064F33C +:104640000102C271D2070DD04A795C7922404A71C9 +:104650000A791B791A400A718278C9788A4200D98E +:10466000817010BD00224A71F5E74178012900D020 +:104670000C21017070472DE9F04F93B04FF0000B03 +:104680000C690D468DF820B0097801260C201746DC +:104690004FF00D084FF0110A4FF008091B2975D291 +:1046A000DFE811F01B00C40207031F035E03710360 +:1046B000A303B803F9031A0462049504A204EF04E7 +:1046C0002D05370555056005F305360639066806DC +:1046D0008406FE062207EB06F00614B120781D289A +:1046E0002AD0D5F808805FEA08004FD001208DF865 +:1046F0002000686A02220D908DF824200A208DF88F +:104700002500A8690A90A8880028EED098F8001023 +:1047100091B10F2910D27DD2DFE801F07C1349DE80 +:10472000FCFBFAF9F8F738089CF6F50002282DD1C1 +:1047300024B120780C2801D00026F0E38DF8202049 +:10474000CBE10420696A03F0B9F9A8880728EED103 +:10475000204600F0F2FF022809D0204600F0EDFFCD +:10476000032807D9204600F0E8FF072802D20120DD +:10477000207004E0002CB8D020780128D7D198F818 +:104780000400C11F0A2902D30A2061E0C4E1A0701D +:10479000D8F80010E162B8F80410218698F80600F5 +:1047A00084F83200012028700320207044E007289C +:1047B000BDD1002C99D020780D28B8D198F80310DD +:1047C00094F82F20C1F3C000C2F3C002104201D000 +:1047D000062000E00720890707D198F8051001425C +:1047E000D2D198F806100142CED194F8312098F831 +:1047F000051020EA02021142C6D194F8322098F83E +:10480000061090430142BFD198F80400C11F0A2945 +:10481000BAD200E008E2617D81427CD8D8F800106D +:104820006160B8F80410218198F80600A072012098 +:1048300028700E20207003208DF82000686A0D90EB +:1048400004F12D000990601D0A900F300B9022E1B9 +:104850002875FCE3412891D1204600F06EFF042822 +:1048600002D1E078C00704D1204600F066FF0F288F +:1048700084D1A88CD5F80C8080B24FF0400BE6694B +:10488000FFF745FC324641465B464E46CDF8009068 +:10489000FFF731F80B208DF82000686A0D90E06971 +:1048A0000990002108A8FFF79FFE2078042806D071 +:1048B000A07D58B1012809D003280AD04AE3052079 +:1048C0002070032028708DF82060CEE184F800A0CD +:1048D00032E712202070EAE11128BCD1204600F016 +:1048E0002CFF042802D1E078C00719D0204600F040 +:1048F00024FF062805D1E078C00711D1A07D022849 +:104900000ED0204608E0CCE084E072E151E124E1E1 +:1049100003E1E9E019E0B0E100F00FFF11289AD1BE +:10492000102208F1010104F13C00F9F786FD6078DE +:1049300001286ED012202070E078C00760D0A07DE2 +:104940000028C8D00128C6D05AE0112890D12046AE +:1049500000F0F3FE082804D0204600F0EEFE1328F5 +:1049600086D104F16C00102208F101010646F9F726 +:1049700064FD207808280DD014202070E178C80745 +:104980000DD0A07D02280AD06278022A04D0032824 +:10499000A1D035E00920F0E708B1012837D1C807D8 +:1049A00013D0A07D02281DD000200090D4E906215C +:1049B00033460EA8FFF777FC10220EA904F13C0045 +:1049C000F9F70EFDC8B1042042E7D4E90912201D11 +:1049D0008DE8070004F12C0332460EA8616BFFF747 +:1049E00070FDE9E7606BC1F34401491E0068C840EF +:1049F00000F0010040F08000D7E72078092806D1B8 +:104A000085F800908DF8209036E32870EFE30920B8 +:104A1000FBE79EE1112899D1204600F08EFE0A287E +:104A200002D1E078C00704D1204600F086FE1528A8 +:104A30008CD104F13C00102208F101010646F9F77F +:104A4000FCFC20780A2816D016202070D4E9093200 +:104A5000606B611D8DE80F0004F15C0304F16C02D2 +:104A600047310EA8FFF7C2FC10220EA93046F9F715 +:104A7000B7FC18B1F9E20B20207073E22046FFF773 +:104A8000D7FDA078216AC0F110020B18002118464A +:104A9000F9F7FDFC26E3394608A8FFF7A5FD064611 +:104AA0003CE20228B7D1204600F047FE042804D398 +:104AB000204600F042FE082809D3204600F03DFEC3 +:104AC0000E2829D3204600F038FE122824D2A07DDB +:104AD0000228A0D10E208DF82000686A0D9098F869 +:104AE00001008DF82400F5E3022894D1204600F05F +:104AF00024FE002810D0204600F01FFE0128F9D027 +:104B0000204600F01AFE0C28F4D004208DF8240072 +:104B100098F801008DF8250060E21128FCD1002CE6 +:104B2000FAD020781728F7D16178606A022912D06C +:104B30005FF0000101EB4101182606EBC1011022D4 +:104B4000405808F10101F9F778FC0420696A00F087 +:104B5000E7FD2670F0E50121ECE70B28DCD1002C05 +:104B6000DAD020781828D7D16078616A02281CD062 +:104B70005FF0000000EB4002102000EBC20009587B +:104B8000B8F8010008806078616A02280FD0002020 +:104B900000EB4002142000EBC2000958404650F8D8 +:104BA000032F0A604068486039E00120E2E70120F5 +:104BB000EEE71128B0D1002CAED020781928ABD167 +:104BC0006178606A022912D05FF0000101EB4101B7 +:104BD0001C2202EBC1011022405808F10101F9F733 +:104BE0002CFC0420696A00F09BFD1A20B6E001212C +:104BF000ECE7082890D1002C8ED020781A288BD191 +:104C0000606A98F80120017862F347010170616AD7 +:104C1000D8F8022041F8012FB8F806008880042057 +:104C2000696A00F07DFD90E2072011E638780128DE +:104C300094D1182204F114007968F9F7FEFBE079A9 +:104C4000C10894F82F0001EAD001E07861F3000078 +:104C5000E070217D002974D12178032909D0C00793 +:104C600025D0032028708DF82090686A0D9041208F +:104C700008E3607DA178884201D90620E8E5022694 +:104C80002671E179204621F0E001E171617A21F09D +:104C9000F0016172A17A21F0F001A172FFF7C8FC66 +:104CA0002E708DF82090686A0D900720EAE20420AB +:104CB000ABE6387805289DD18DF82000686A0D9004 +:104CC000B8680A900720ADF824000A988DF830B033 +:104CD0006168016021898180A17A8171042020703E +:104CE000F8E23978052985D18DF82010696A0D918F +:104CF000391D09AE0EC986E80E004121ADF8241019 +:104D00008DF830B01070A88CD7F80C8080B2402697 +:104D1000A769FFF70EFA41463A463346C846CDF832 +:104D20000090FEF71CFE002108A8FFF75DFCE0786C +:104D300020F03E00801CE0702078052802D00F2073 +:104D40000CE04AE1A07D20B1012802D0032802D066 +:104D500002E10720BEE584F80080EDE42070EBE47A +:104D6000102104F15C0002F0C2FB606BB0BBA07DBF +:104D700018B1012801D00520FDE006202870F84870 +:104D80006063A063C2E23878022894D1387908B110 +:104D90002875B7E3A07D022802D0032805D022E0C1 +:104DA000B8680028F5D060631CE06078012806D060 +:104DB000A07994F82E10012805D0E94806E0A179E1 +:104DC00094F82E00F7E7B8680028E2D06063E07836 +:104DD000C00701D0012902D0E14803E003E0F868F0 +:104DE0000028D6D0A06306200FE68DF82090696ACF +:104DF0000D91E1784846C90709D06178022903D1AD +:104E0000A17D29B1012903D0A17D032900D007206C +:104E1000287033E138780528BBD1207807281ED0C8 +:104E200084F800A005208DF82000686A0D90B8680D +:104E30000A90ADF824A08DF830B003210170E1781C +:104E4000CA070FD0A27D022A1AD000210091D4E90E +:104E5000061204F15C03401CFFF725FA6BE384F8AB +:104E60000090DFE7D4E90923211D8DE80E0004F14D +:104E70002C0304F15C02401C616BFFF722FB5AE338 +:104E8000626BC1F34401491E1268CA4002F001017D +:104E900041F08001DAE738780528BDD18DF820008F +:104EA000686A0D90B8680A90ADF824A08DF830B00B +:104EB000042100F8011B102204F15C01F9F7BDFA8E +:104EC000002108A8FFF790FB2078092801D01320C3 +:104ED00044E70A2020709AE5E078C10742D0A17D1E +:104EE000012902D0022927D038E0617808A80129D9 +:104EF00016D004F16C010091D4E9061204F15C03B0 +:104F0000001DFFF7BBFA0A20287003268DF82080C9 +:104F1000686A0D90002108A8FFF766FBE1E2C7E28E +:104F200004F15C010091D4E9062104F16C03001D39 +:104F3000FFF7A4FA0026E9E7C0F3440114290DD2D3 +:104F40004FF0006101EBB0104FEAB060E0706078A4 +:104F5000012801D01020BDE40620FFE6607801287A +:104F60003FF4B6AC0A2050E5E178C90708D0A17D2E +:104F7000012903D10B202870042030E028702EE096 +:104F80000E2028706078616B012818D004F15C0352 +:104F900004F16C020EA8FFF7E1FA2046FFF748FB88 +:104FA000A0780EAEC0F1100230440021F9F76FFA7C +:104FB00006208DF82000686A09960D909BE004F1A8 +:104FC0006C0304F15C020EA8FFF7C8FAE8E7397831 +:104FD000022903D139790029D0D0297592E28DF8C0 +:104FE0002000686A0D9056E538780728F6D1D4E994 +:104FF00009216078012808D004F16C00CDE9000295 +:10500000029105D104F16C0304E004F15C00F5E7C2 +:1050100004F15C0304F14C007A680646216AFFF74C +:1050200063F96078012822D1A078216AC0F11002CA +:105030000B1800211846F9F72AFAD4E90923606B06 +:1050400004F12D018DE80F0004F15C0300E05BE248 +:1050500004F16C0231460EA8FFF7C8F910220EA920 +:1050600004F13C00F9F7BCF908B10B20ACE485F879 +:10507000008000BF8DF82090686A0D908DF824A004 +:1050800009E538780528A9D18DF82000686A0D90C7 +:10509000B8680A90ADF824A08DF830B080F8008090 +:1050A000617801291AD0D4E9092104F12D03A66BF6 +:1050B00003910096CDE9013204F16C0304F15C0226 +:1050C00004F14C01401CFFF791F9002108A8FFF7FB +:1050D0008BFA6078012805D015203FE6D4E9091243 +:1050E000631DE4E70E20287006208DF82000686A12 +:1050F000CDF824B00D90A0788DF82800CBE4387856 +:105100000328C0D1E079C00770D00F202870072095 +:1051100065E7387804286BD11422391D04F1140096 +:10512000F9F78BF9616A208CA1F80900616AA0780F +:10513000C871E179626A01F003011172616A627AF1 +:105140000A73616AA07A81F8240016205DE485F86C +:1051500000A08DF82090696A50460D9192E0000001 +:10516000706202003878052842D1B868A861617879 +:10517000606A022901D0012100E0002101EB410118 +:10518000142606EBC1014058082102F0B0F96178FD +:10519000606A022901D0012100E0002101EB4101F8 +:1051A00006EBC101425802A8E169FFF70BFA6078EB +:1051B000626A022801D0012000E0002000EB4001DB +:1051C000102000EBC1000223105802A90932FEF79B +:1051D000EEFF626AFD4B0EA80932A169FFF7E1F903 +:1051E0006178606A022904D0012103E044E18DE086 +:1051F000BFE0002101EB4101182606EBC101A278B6 +:1052000040580EA9F9F719F96178606A022901D0AE +:10521000012100E0002101EB410106EBC1014158F1 +:10522000A0780B18C0F1100200211846F9F72FF9E9 +:1052300005208DF82000686A0D90A8690A90ADF8E5 +:1052400024A08DF830B0062101706278616A022ACC +:1052500001D0012200E0002202EB420206EBC20272 +:10526000401C89581022F9F7E8F8002108A8FFF738 +:10527000BBF91220C5F818B028708DF82090686A24 +:105280000D900B208DF8240005E43878052870D1A6 +:105290008DF82000686A0D90B8680A900B20ADF870 +:1052A00024000A98072101706178626A022901D0FE +:1052B000012100E0002101EB4103102101EBC301BA +:1052C00051580988A0F801106178626A022902D059 +:1052D000012101E02FE1002101EB4103142101EB49 +:1052E000C30151580A6840F8032F4968416059E0EA +:1052F0001920287001208DF8300074E616202870DF +:105300008DF830B0002108A8FFF76EF9032617E1E9 +:1053100014202870AEE6387805282AD18DF82000B0 +:10532000686A0D90B8680A90ADF824A08DF830B086 +:1053300080F800906278616A4E46022A01D001220C +:1053400000E0002202EB42021C2303EBC202401CDD +:1053500089581022F9F771F8002108A8FFF744F9DD +:10536000152028708DF82060686A0D908DF82460F3 +:1053700039E680E0387805287DD18DF82000686A0C +:105380000D90B8680A90ADF8249009210170616908 +:10539000097849084170616951F8012FC0F802206D +:1053A0008988C18020781C28A8D1A1E7E078C007AF +:1053B00002D04FF0060C01E04FF0070C6078022895 +:1053C0000AD000BF4FF0000000EB040101F1090119 +:1053D00005D04FF0010004E04FF00100F4E74FF07A +:1053E00000000B78204413EA0C030B7010F8092F0F +:1053F00002EA0C02027004D14FF01B0C84F800C0CA +:10540000D2B394F801C0BCF1010F00D09BB990F861 +:1054100000C0E0465FEACC7C04D028F001060670AC +:10542000102606E05FEA887C05D528F002060670A3 +:1054300013262E70032694F801C0BCF1020F00D091 +:1054400092B991F800C05FEACC7804D02CF0010644 +:105450000E70172106E05FEA8C7805D52CF0020665 +:105460000E701921217000260078D0BBCAB3C3BBCF +:105470001C20207035E012E002E03878062841D187 +:105480001A2015E4207801283CD00C283AD0204678 +:10549000FFF7EBF809208DF82000686A0D9031E0E5 +:1054A0003878052805D00620387003261820287083 +:1054B00046E005208DF82000696A0D91B9680A91CF +:1054C0000221ADF8241001218DF830100A990870DE +:1054D000287D4870394608A8FFF786F80646182048 +:1054E0002870012E0ED02BE001208DF82000686A74 +:1054F0000D9003208DF82400287D8DF8250085F877 +:1055000014B012E0287D80B11D2020701720287073 +:105510008DF82090686A0D9002208DF8240039469D +:1055200008A8FFF761F806460AE00CB1FE202070DB +:105530009DF8200020B1002108A8FFF755F80CE4E1 +:1055400013B03046BDE8F08F2DE9F04387B00C462C +:105550004E6900218DF804100120257803460227AA +:105560004FF007094FF0050C85B1012D53D0022DE6 +:1055700039D1FE2030708DF80030606A059003202C +:105580008DF80400207E8DF8050063E02179012963 +:1055900025D002292DD0032928D0042923D1B17D7B +:1055A000022920D131780D1F042D04D30A3D032D8B +:1055B00001D31D2917D12189022914D38DF8047034 +:1055C000237020899DF80410884201E0686202007F +:1055D00018D208208DF80000606A059057E07078B6 +:1055E0000128EBD0052007B0BDE8F0831D20307006 +:1055F000E4E771780229F5D131780C29F3D18DF8DF +:105600000490DDE7083402F804CB94E80B0082E84C +:105610000B000320E7E71578052DE4D18DF800C0D5 +:10562000656A0595956802958DF8101094F80480C8 +:10563000B8F1010F13D0B8F1020F2DD0B8F1030F5C +:105640001CD0B8F1040FCED1ADF804700E20287034 +:10565000207E687000216846FEF7C6FF0CE0ADF8BA +:1056600004700B202870207E002100F01F0068705D +:105670006846FEF7B9FF37700020B4E7ADF8047054 +:105680008DF8103085F800C0207E687027701146B4 +:105690006846FEF7A9FFA6E7ADF804902B70207FBF +:1056A0006870607F00F00100A870A07F00F01F000C +:1056B000E870E27F2A71C0071CD094F8200000F047 +:1056C0000700687194F8210000F00700A87100211C +:1056D0006846FEF789FF2868F062A8883086A879B6 +:1056E00086F83200A069407870752879B0700D2076 +:1056F0003070C1E7A9716971E9E700B587B0042886 +:105700000CD101208DF800008DF8040000200591D7 +:105710008DF8050001466846FEF766FF07B000BD3C +:1057200070B50C46054602F0C9F921462846BDE889 +:1057300070407823002202F017B908B10078704752 +:105740000C20704770B50C0005784FF000010CD0AC +:1057500021702146EFF7D1FD69482178405D8842EC +:1057600001D1032070BD022070BDEFF7C6FD0020FF +:1057700070BD0279012A05D000220A704B78012BF6 +:1057800002D003E0042070470A758A610279930011 +:10579000521C0271C15003207047F0B587B00F460C +:1057A00005460124287905EB800050F8046C7078D8 +:1057B000411E02290AD252493A46083901EB8000BB +:1057C000314650F8043C2846984704460CB1012C59 +:1057D00011D12879401E10F0FF00287101D0032458 +:1057E000E0E70A208DF80000706A0590002101961C +:1057F0006846FFF7A7FF032CD4D007B02046F0BDC2 +:1058000070B515460A46044629461046FFF7C5FFFF +:10581000064674B12078FE280BD1207C30B10020E0 +:105820002870294604F10C00FFF7B7FF2046FEF769 +:105830001CFF304670BD704770B50E4604467C2292 +:105840000021F8F724FE0225012E03D0022E04D0F9 +:10585000052070BD0120607000E065702046FEF7F5 +:1058600004FFA575002070BD28B1027C1AB10A465C +:1058700000F10C01C4E70120704710B5044686B062 +:10588000042002F01BF92078FE2806D000208DF8B5 +:10589000000069462046FFF7E7FF06B010BD7CB563 +:1058A0000E4600218DF804104178012903D0022909 +:1058B00003D0002405E0046900E044690CB1217CB8 +:1058C00089B16D4601462846FFF753FF032809D1E9 +:1058D000324629462046FFF793FF9DF80410002921 +:1058E00000D004207CBD04F10C05EBE730B40C467D +:1058F0000146034A204630BC024B0C3AFEF751BE2B +:10590000AC6202006862020070B50D46040011D05E +:1059100085B1220100212846F8F7B9FD102250492F +:105920002846F8F78AFD4F48012101704470456010 +:10593000002070BD012070BD70B505460024494EA1 +:1059400011E07068AA7B00EB0410817B914208D1C2 +:10595000C17BEA7B914204D10C222946F8F740FD35 +:1059600030B1641CE4B230788442EAD3002070BDC8 +:10597000641CE0B270BD70B50546FFF7DDFF00287E +:1059800005D1384C20786178884201D3002070BD61 +:105990006168102201EB00102946F8F74EFD2078CF +:1059A000401CC0B2207070BD2E48007870472D4951 +:1059B0000878012802D0401E08700020704770B59A +:1059C0000D460021917014461180022802D0102843 +:1059D00015D105E0288890B10121A17010800CE05C +:1059E000284613B1FFF7C7FF01E0FFF7A5FFA0703E +:1059F00010F0FF0F03D0A8892080002070BD012087 +:105A000070BD0023DBE770B5054614460E0009D0D3 +:105A100000203070A878012806D003D911490A78EF +:105A200090420AD9012070BD24B1287820702888BE +:105A3000000A5070022008700FE064B1496810221B +:105A400001EB001120461039F8F7F7FC2878207395 +:105A50002888000A607310203070002070BD00009C +:105A6000BB620200900000202DE9F04190460C46F8 +:105A700007460025FE48072F00EB881607D2DFE80F +:105A800007F00707070704040400012500E0FFDF13 +:105A900006F81470002D13D0F548803000EB880113 +:105AA00091F82700202803D006EB4000447001E065 +:105AB00081F8264006EB44022020507081F82740F0 +:105AC000BDE8F081F0B51F4614460E460546202A73 +:105AD00000D1FFDFE649E648803100EB871C0CEB84 +:105AE000440001EB8702202E07D00CEB46014078E2 +:105AF0004B784870184620210AE092F8253040780B +:105B000082F82500F6E701460CEB4100057040786D +:105B1000A142F8D192F82740202C03D00CEB44048A +:105B2000637001E082F826300CEB4104202363709F +:105B300082F82710F0BD30B50D46CE4B4419002237 +:105B4000181A72EB020100D2FFDFCB48854200DD5C +:105B5000FFDFC9484042854200DAFFDFC548401CEC +:105B6000844207DA002C01DB204630BDC148401CCE +:105B7000201830BDBF48C043FAE710B5044601689D +:105B8000407ABE4A52F82020114450B10220084405 +:105B900020F07F40EDF763F894F90810BDE810405D +:105BA000C9E70420F3E72DE9F047B14E803696F8B7 +:105BB0002D50DFF8BC9206EB850090F8264034E0CB +:105BC00009EB85174FF0070817F81400012806D0D5 +:105BD00004282ED005282ED0062800D0FFDF01F0A3 +:105BE00025F9014607EB4400427806EB850080F872 +:105BF000262090F82720A24202D1202280F82720D8 +:105C0000084601F01EF92A4621460120FFF72CFF25 +:105C10009B48414600EB041002682046904796F8E6 +:105C20002D5006EB850090F82640202CC8D1BDE809 +:105C3000F087022000E003208046D0E710B58C4CAE +:105C40002021803484F8251084F8261084F8271049 +:105C5000002084F8280084F82D0084F82E10411EBE +:105C6000A16044F8100B2074607420736073A073FB +:105C70008449E07720750870487000217C4A103C08 +:105C800002F81100491CC9B22029F9D30120ECF710 +:105C9000D6FE0020ECF7D3FE012084F82200EDF7B9 +:105CA000FFF87948EDF711F9764CA41E207077487B +:105CB000EDF70BF96070BDE81040ECF74DBE10B584 +:105CC000ECF76FFE6F4CA41E2078EDF717F96078A3 +:105CD000EDF714F9BDE8104001F0E0B8202070475E +:105CE0000020ECF785BE70B5054601240E46AC4099 +:105CF0005AB1FFF7F5FF0146654800EBC500C0F853 +:105D00001015C0F81465634801E06248001D046086 +:105D100070BD2DE9F34F564C0025803404EB810A09 +:105D200089B09AF82500202821D0691E0291544993 +:105D3000009501EB0017391D03AB07C983E8070085 +:105D4000A18BADF81C10A07F8DF81E009DF81500EA +:105D5000A046C8B10226494951F820400399A2192A +:105D6000114421F07F41019184B102210FE0012013 +:105D7000ECF765FE0020ECF762FEECF730FE01F078 +:105D80008DF884F82F50A9E00426E4E700218DF86F +:105D90001810022801D0012820D103980119099870 +:105DA000081A801C9DF81C1020F07F4001B10221D0 +:105DB000353181420BD203208DF815000398C4F1D0 +:105DC0003201401A20F07F40322403900CE098F812 +:105DD000240018B901F043FA002863D0322C03D212 +:105DE00014B101F04FF801E001F058F8254A10789D +:105DF00018B393465278039B121B00219DF818405C +:105E0000994601281AD0032818D000208DF81E00CA +:105E1000002A04DD981A039001208DF818009DF8DF +:105E20001C0000B1022103981B4A20F07F40039020 +:105E300003AB099801F03EF810B110E00120E5E74E +:105E40009DF81D0018B99BF80000032829D08DF893 +:105E50001C50CDF80C908DF818408DF81E509DF810 +:105E6000180010B30398012381190022184615E089 +:105E7000840A0020FF7F841E0020A107CC6202005C +:105E8000840800209A00002017780100A75B010019 +:105E900000F0014004F50140FFFF3F00ECF722FE57 +:105EA00006E000200BB0BDE8F08F0120ECF7C7FD45 +:105EB00097F90C20012300200199ECF713FEF87BE1 +:105EC000C00701D0ECF7F7FE012188F82F108AF8FF +:105ED000285020226946FE48F8F7AFFA0120E1E792 +:105EE0002DE9F05FDFF8E883064608EB860090F8BE +:105EF0002550202D1FD0A8F180002C4600EB8617DE +:105F0000A0F50079DFF8CCB305E0A24607EB4A0024 +:105F10004478202C0AD0ECF730FE09EB04135A46E3 +:105F200001211B1D00F0C6FF0028EED0AC4202D0BC +:105F3000334652461EE0E84808B1AFF30080ECF764 +:105F40001CFE98F82F206AB1D8F80C20411C891A41 +:105F50000902CA1701EB12610912002902DD0020B3 +:105F6000BDE8F09F3146FFF7D4FE08B10120F7E706 +:105F700033462A4620210420FFF7A4FDEFE72DE950 +:105F8000F041D34C2569ECF7F8FD401B0002C11726 +:105F900000EB1160001200D4FFDF94F8220000B182 +:105FA000FFDF012784F8227094F82E00202800D10A +:105FB000FFDF94F82E60202084F82E00002584F85E +:105FC0002F5084F8205084F82150C4482560007870 +:105FD000022833D0032831D000202077A068401C4D +:105FE00005D04FF0FF30A0600120ECF728FD002025 +:105FF000ECF725FDECF721FEECF719FEECF7EFFCD2 +:106000000EF0D6FDB648056005604FF0E0214FF474 +:106010000040B846C1F88002ECF7BBFE94F82D7042 +:106020003846FFF75DFF0028FAD0A948803800EB1A +:10603000871010F81600022802D006E00120CCE7F5 +:106040003A4631460620FFF70FFD84F8238004EB23 +:10605000870090F82600202804D0A048801E4078B1 +:10606000ECF752FF207F002803D0ECF7D6FD257710 +:10607000657725E5964910B591F82D2000248039E3 +:1060800001EB821111F814302BB1641CE4B2202C06 +:10609000F8D3202010BD934901EB041108600020C3 +:1060A000C87321460120FFF7DFFC204610BD10B564 +:1060B000012801D0032800D171B3854A92F82D3010 +:1060C000834C0022803C04EB831300BF13F8124082 +:1060D0000CB1082010BD521CD2B2202AF6D37F4A40 +:1060E00048B1022807D0072916D2DFE801F01506CB +:1060F000080A0C0E100000210AE01B2108E03A21DA +:1061000006E0582104E0772102E0962100E0B52165 +:1061100051701070002010BD072010BD6F4810B5E1 +:106120004078ECF79CFD80B210BD10B5202811D24C +:10613000674991F82D30A1F1800202EB831414F825 +:1061400010303BB191F82D3002EB831212F8102081 +:10615000012A01D0002010BD91F82D200146002019 +:10616000FFF782FC012010BD10B5ECF706FDBDE87D +:106170001040ECF774BD2DE9F0410E46544F017804 +:106180002025803F0C4607EB831303E0254603EBF5 +:1061900045046478944202D0202CF7D108E0202CEA +:1061A00006D0A14206D103EB41014978017007E016 +:1061B000002085E403EB440003EB45014078487080 +:1061C000494F7EB127B1002140F22D40AFF300804E +:1061D0003078A04206D127B100214FF48660AFF39A +:1061E0000080357027B1002140F23540AFF30080C8 +:1061F000012065E410B542680B689A1A1202D417A0 +:1062000002EB1462121216D4497A91B1427A82B921 +:10621000364A006852F82110126819441044001DD3 +:10622000891C081A0002C11700EB11600012322805 +:1062300001DB012010BD002010BD2DE9F047294EE3 +:10624000814606F500709846144600EB811712E06F +:1062500006EB0415291D4846FFF7CCFF68B988F8FE +:106260000040A97B99F80A00814201D80020DEE4B1 +:1062700007EB44004478202CEAD10120D7E42DE933 +:10628000F047824612480E4600EB8600DFF8548045 +:1062900090F825402020107008F5007099461546AA +:1062A00000EB86170BE000BF08EB04105146001D01 +:1062B000FFF7A0FF28B107EB44002C704478202C96 +:1062C000F2D1297889F800104B46224631460FE07A +:1062D000040B0020FFFF3F00000000009A00002098 +:1062E00000F500408408002000000000CC6202009D +:1062F0005046BDE8F047A0E72DE9FC410F460446B3 +:106300000025FE4E10E000BF9DF80000256806EB5A +:1063100000108168204600F0E1FD2068A84202D10B +:106320000020BDE8FC8101256B4601AA39462046C4 +:10633000FFF7A5FF0028E7D02846F2E770B504462E +:10634000EF480125A54300EB841100EB85104022A6 +:10635000F8F773F8EB4E26B1002140F29D40AFF301 +:106360000080E748803000EB850100EB8400D0F826 +:106370002500C1F8250026B1002140F2A140AFF36D +:106380000080284670BD8A4203D003460520FFF7EF +:1063900099BB202906D0DA4A02EB801000EB4100BD +:1063A00040787047D649803101EB800090F8250095 +:1063B0007047D24901EB0010001DFFF7DEBB7CB532 +:1063C0001D46134604460E4600F1080221461846B3 +:1063D000ECF752FC94F908000F2804DD1F382072F6 +:1063E0002068401C206096B10220C74951F8261051 +:1063F000461820686946801B20F07F40206094F991 +:1064000008002844C01C1F2803DA012009E00420EA +:10641000EBE701AAECF730FC9DF8040010B10098FE +:10642000401C00900099206831440844C01C20F0B2 +:106430007F4060607CBDFEB50C46064609786079F9 +:10644000907220791F461546507279B12179002249 +:106450002846A368FFF7B3FFA9492846803191F881 +:106460002E20202A0AD00969491D0DE0D4E9022313 +:10647000217903B02846BDE8F040A0E7A349497858 +:10648000052900D20521314421F07F4100F026FD8D +:1064900039462846FFF730FFD4E9023221796846B1 +:1064A000FFF78DFF2B4600213046019A00F002FDD8 +:1064B000002806D103B031462846BDE8F04000F080 +:1064C0000DBDFEBD2DE9F14F84B000F0C3FCF0B16D +:1064D00000270498007800284FF000006DD1884D07 +:1064E000884C82468346803524B1002140F2045016 +:1064F000AFF3008095F82D8085F823B0002624B1F5 +:10650000002140F20950AFF3008017B105E00127E8 +:10651000DFE74046FFF712FF804624B1002140F23A +:106520001150AFF30080ECF728FB814643466A46E2 +:106530000499FFF780FF24B1002140F21750AFF318 +:10654000008095F82E0020280CD029690098401A68 +:106550000002C21700EB1260001203D5684600F07B +:10656000BDFC01264CB1002140F22150AFF3008068 +:10657000002140F22650AFF300806B46644A0021B0 +:10658000484600F097FC98B127B941466846FFF7A6 +:10659000B3FE064326B16846FFF7EFFA0499886018 +:1065A0004FF0010A24B1002140F23A50AFF30080CD +:1065B00095F82300002897D1504605B073E42DE9E3 +:1065C000F04F89B08B46824600F044FC4C4C80343E +:1065D00030B39BF80000002710B1012800D0FFDF86 +:1065E000484D25B1002140F2F950AFF300804349F6 +:1065F000012001EB0A18A946CDF81C005FEA090644 +:1066000004D0002140F20160AFF30080079800F051 +:1066100018FC94F82D50002084F8230067B119E08D +:1066200094F82E000127202800D1FFDF9BF80000FE +:106630000028D5D0FFDFD3E72846FFF77FFE0546C9 +:1066400026B1002140F20B60AFF3008094F82300E4 +:106650000028D3D126B1002140F21560AFF30080AD +:10666000ECF78BFA2B4602AA59460790FFF7E3FE98 +:1066700098F80F005FEA060900F001008DF813009A +:1066800004D0002140F21F60AFF300803B462A4651 +:1066900002A9CDF800A0079800F02BFC064604EBF9 +:1066A000850090F828000090B9F1000F04D0002177 +:1066B00040F22660AFF3008000F0B8FB0790B9F11C +:1066C000000F04D0002140F22C60AFF3008094F85A +:1066D0002300002892D1B9F1000F04D0002140F22C +:1066E0003460AFF300800DF1080C9CE80E00C8E99F +:1066F0000112C8F80C30BEB30CE000008408002082 +:10670000840A002000000000CC6202009A000020F1 +:10671000FFFF3F005FEA090604D0002140F241601C +:10672000AFF300800098B84312D094F82E002028D0 +:106730000ED126B1002140F24660AFF3008028461A +:10674000FFF7CEFB20B99BF80000D8B3012849D051 +:10675000B9F1000F04D0002140F26360AFF3008074 +:10676000284600F05CFB01265FEA090504D0002101 +:1067700040F26C60AFF30080079800F062FB25B137 +:1067800000214FF4CE60AFF300808EB194F82D005D +:1067900004EB800090F82600202809D025B10021C4 +:1067A00040F27760AFF30080F7484078ECF7ACFB3D +:1067B00025B1002140F27C60AFF3008009B0304683 +:1067C000BDE8F08FFFE7B9F1000F04D0002140F2DF +:1067D0004E60AFF3008094F82D2051460420FFF75F +:1067E00043F9C0E7002E3FF409AF002140F25960A1 +:1067F000AFF3008002E72DE9F84FE44D814695F8AC +:106800002D004FF00008E24C4FF0010B474624B139 +:10681000002140F28A60AFF30080584600F011FB7F +:1068200085F8237024B1002140F28F60AFF300801F +:1068300095F82D00FFF782FD064695F8230028B154 +:10684000002CE4D0002140F295604BE024B10021FF +:1068500040F29960AFF30080CC48803800EB86119D +:1068600011F81900032856D1334605EB830A4A462E +:106870009AF82500904201D1012000E0002000900C +:106880000AF125000021FFF776FC0146009801423D +:1068900003D001228AF82820AF77E1B324B1002188 +:1068A00040F29E60AFF30080324649460120FFF778 +:1068B000DBF89AF828A024B1002140F2A960AFF3D8 +:1068C000008000F0B3FA834624B1002140F2AE60AC +:1068D000AFF3008095F8230038B1002C97D0002149 +:1068E00040F2B260AFF3008091E7BAF1000F07D039 +:1068F00095F82E00202803D13046FFF7F1FAE0B1D9 +:1069000024B1002140F2C660AFF30080304600F0B1 +:1069100086FA4FF0010824B1002140F2CF60AFF3B6 +:106920000080584600F08DFA24B1002140F2D36077 +:10693000AFF300804046BDE8F88F002CF1D0002175 +:1069400040F2C160AFF30080E6E70120ECF750B8F9 +:106950008D48007870472DE9F0418C4C94F82E005A +:1069600020281FD194F82D6004EB860797F8255056 +:10697000202D00D1FFDF8549803901EB861000EB27 +:106980004500407807F8250F0120F87084F82300AF +:10699000294684F82E50324602202234FFF764F84C +:1069A0000020207005E42DE9F0417A4E774C012556 +:1069B00038B1012821D0022879D003287DD0FFDF0B +:1069C00017E400F05FFAFFF7C6FF207E00B1FFDF9B +:1069D00084F821500020ECF732F8A168481C04D05C +:1069E000012300221846ECF77DF814F82E0F2178C9 +:1069F00006EB01110A68012154E0FFF7ACFF01200A +:106A0000ECF71DF894F8210050B1A068401C07D0A5 +:106A100014F82E0F217806EB01110A68062141E0D7 +:106A2000207EDFF86481002708F10208012803D0E6 +:106A300002281ED0FFDFB5E7A777ECF7EEF898F84D +:106A40000000032801D165772577607D524951F810 +:106A5000200094F8201051B948B161680123091A47 +:106A600000221846ECF73EF8022020769AE72776B7 +:106A700098E784F8205000F005FAA07F50B198F80C +:106A8000010061680123091A00221846ECF72AF870 +:106A9000257600E0277614F82E0F217806EB0111F9 +:106AA0000A680021BDE8F041104700E005E03648E3 +:106AB0000078BDE8F041ECF727BAFFF74CFF14F877 +:106AC0002E0F217806EB01110A680521EAE710B5BF +:106AD0002E4C94F82E00202800D1FFDF14F82E0F42 +:106AE00021782C4A02EB01110A68BDE8104004210C +:106AF00010477CB5254C054694F82E00202800D17F +:106B0000FFDFA068401C00D0FFDF94F82E00214971 +:106B100001AA01EB0010694690F90C002844ECF73B +:106B2000ABF89DF904000F2801DD012000E00020F2 +:106B3000009908446168084420F07F41A16094F8FE +:106B40002100002807D002B00123BDE870400022D8 +:106B50001846EBF7C7BF7CBD30B5104A0B1A541C62 +:106B6000B3EB940F1ED3451AB5EB940F1AD393428F +:106B700003D9101A43185B1C14E0954210D9511A1E +:106B80000844401C43420DE098000020040B002004 +:106B90000000000084080020CC620200FF7F841EF9 +:106BA000FFDF0023184630BD0123002201460220EA +:106BB000EBF798BF0220EBF742BFEBF7DEBF2DE902 +:106BC000FE4FEE4C05468A4694F82E00202800D150 +:106BD000FFDFEA4E94F82E10A0462046A6F520725C +:106BE00002EB011420218DF8001090F82D10376968 +:106BF00000EB8101D8F8000091F82590284402AA02 +:106C000001A90C36ECF738F89DF90800002802DDE0 +:106C10000198401C0190A0680199642D084452D34A +:106C2000D74B00225B1B72EB02014CD36168411A07 +:106C300021F07F41B1F5800F45D220F07F40706098 +:106C400086F80AA098F82D1044466B464A4630460E +:106C5000FFF7F3FAB0B3A068401C10D0EBF78DFF3C +:106C6000A168081A0002C11700EB11600012022887 +:106C70002BDD0120EBF7E3FE4FF0FF30A06094F82E +:106C80002D009DF8002020210F34FFF77CFBA17F11 +:106C9000BA4A803A02EB8111E27F01EB420148706F +:106CA00054F80F0C284444F80F0C012020759DF86F +:106CB0000000202803D0B3484078ECF725F90120E4 +:106CC000BDE8FE8F01E00020FAE77760FBE72DE9E1 +:106CD000F047AA4C074694F82D00A4F1800606EB75 +:106CE000801010F8170000B9FFDF94F82D50A0466F +:106CF000A54C24B1002140F6EA00AFF3008040F635 +:106D0000F60940F6FF0A06EB851600BF16F81700D5 +:106D1000012819D0042811D005280FD006280DD03D +:106D20001CB100214846AFF300800FF02DF8002C75 +:106D3000ECD000215046AFF30080E7E72A46394601 +:106D40000120FEF791FEF2E74FF0010A4FF0000933 +:106D5000454624B1002140F60610AFF300805046AE +:106D600000F06FF885F8239024B1002140F60B1055 +:106D7000AFF3008095F82D00FFF7E0FA064695F88E +:106D8000230028B1002CE4D0002140F611101FE0B0 +:106D900024B1002140F61510AFF3008005EB86000A +:106DA00000F1270133463A462630FFF7E4F924B1D3 +:106DB000002140F61910AFF3008000F037F882464A +:106DC00095F8230038B1002CC3D0002140F61F10E5 +:106DD000AFF30080BDE785F82D60012085F8230022 +:106DE000504600F02EF8002C04D0002140F62C1064 +:106DF000AFF30080BDE8F08730B504465F480D462C +:106E000090F82D005D49803901EB801010F81400D6 +:106E100000B9FFDF5D4800EB0410C57330BD574972 +:106E200081F82D00012081F82300704710B55848E3 +:106E300008B1AFF30080EFF3108000F0010072B6EC +:106E400010BD10B5002804D1524808B1AFF300803E +:106E500062B610BD50480068C005C00D10D0103893 +:106E600040B2002804DB00F1E02090F8000405E0C7 +:106E700000F00F0000F1E02090F8140D4009704779 +:106E80000820704710B53D4C94F82400002804D128 +:106E9000F4F712FF012084F8240010BD10B5374C20 +:106EA00094F82400002804D0F4F72FFF002084F881 +:106EB000240010BD10B51C685B68241A181A24F051 +:106EC0007F4420F07F40A14206D8B4F5800F03D262 +:106ED000904201D8012010BD002010BDD0E9003241 +:106EE000D21A21F07F43114421F07F41C0E90031E3 +:106EF00070472DE9FC418446204815468038089C9F +:106F000000EB85160F4616F81400012804D002285D +:106F100002D00020BDE8FC810B46204A01216046DA +:106F2000FFF7C8FFF0B101AB6A4629463846FFF7C4 +:106F3000A6F9B8B19DF804209DF800102846FFF787 +:106F400022FA06EB440148709DF8000020280DD07D +:106F500006EB400044702A4621460320FEF784FDDC +:106F60000120D7E72A4621460420F7E703480121FC +:106F700000EB850000F8254FC170ECE7040B002002 +:106F8000FF1FA107980000200000000084080020D7 +:106F9000000000000000000004ED00E0FFFF3F00E3 +:106FA0002DE9F041044680074FF000054FF001063F +:106FB0000CD56B480560066000F0E8F920B169481F +:106FC000016841F48061016024F00204E0044FF0A4 +:106FD000FF3705D564484660C0F8087324F4805430 +:106FE000600003D56148056024F08044E0050FD5BA +:106FF0005F48C0F80052C0F808735E490D60091D73 +:107000000D605C4A04210C321160066124F4807426 +:10701000A00409D558484660C0F80052C0F808736B +:107020005648056024F40054C4F38030C4F3C031E2 +:10703000884200D0FFDF14F4404F14D0504846601F +:10704000C0F808734F488660C0F80052C0F8087353 +:107050004D490D600A1D16608660C0F808730D600A +:10706000166024F4404420050AD5484846608660EE +:10707000C0F80873C0F848734548056024F40064FC +:107080000DF070FD4348044200D0FFDFBDE8F08101 +:10709000F0B50022202501234FEA020420FA02F174 +:1070A000C9072DD051B2002910DB00BF4FEA51179C +:1070B0004FEA870701F01F0607F1E02703FA06F6FB +:1070C000C7F88061BFF34F8FBFF36F8F0CDB00BF3A +:1070D0004FEA51174FEA870701F01F0607F1E02733 +:1070E00003FA06F6C7F8806204DB01F1E02181F8BB +:1070F000004405E001F00F0101F1E02181F8144D99 +:1071000002F10102AA42C9D3F0BD10B5224C2060A1 +:107110000846F4F7EAFE2068FFF742FF2068FFF711 +:10712000B7FF0DF045F900F092F90DF01BFD0DF0E1 +:1071300058FCEBF7B5FEBDE810400DF0EDB910B509 +:10714000154C2068FFF72CFF2068FFF7A1FF0DF01A +:1071500009FDF4F7C9FF0020206010BD0A20704728 +:10716000FC1F00403C17004000C0004004E5014007 +:10717000008000400485004000D0004004D500405D +:1071800000E0004000F0004000F5004000B000408A +:1071900008B50040FEFF0FFD9C00002070B5264999 +:1071A0000A680AB30022154601244B685B1C4B6039 +:1071B0000C2B00D34D600E7904FA06F30E681E42C4 +:1071C0000FD0EFF3108212F0010272B600D001224C +:1071D0000C689C430C6002B962B6496801600020EB +:1071E00070BD521C0C2AE0D3052070BD4FF0E02189 +:1071F0004FF48000C1F800027047EFF3108111F0E6 +:10720000010F72B64FF0010202FA00F20A48036859 +:1072100042EA0302026000D162B6E7E706480021B5 +:1072200001604160704701218140034800680840C7 +:1072300000D0012070470000A0000020012081073D +:10724000086070470121880741600021C0F80011E3 +:1072500018480170704717490120087070474FF0B7 +:107260008040D0F80001012803D01248007800289F +:1072700000D00120704710480068C00700D00120EE +:1072800070470D480C300068C00700D001207047DF +:107290000948143000687047074910310A68D20362 +:1072A00006D5096801F00301814201D10120704730 +:1072B00000207047A8000020080400404FF08050D4 +:1072C000D0F830010A2801D0002070470120704713 +:1072D00000B5FFF7F3FF20B14FF08050D0F8340134 +:1072E00008B1002000BD012000BD4FF08050D0F853 +:1072F00030010E2801D000207047012070474FF068 +:107300008050D0F83001062803D0401C01D0002066 +:107310007047012070474FF08050D0F830010D28A1 +:1073200001D000207047012070474FF08050D0F806 +:107330003001082801D000207047012070474FF02D +:107340008050D0F83001102801D000207047012073 +:10735000704700B5FFF7F3FF30B9FFF7DCFF18B94E +:10736000FFF7E3FF002800D0012000BD00B5FFF7C4 +:10737000C6FF38B14FF08050D0F83401062803D34F +:10738000401C01D0002000BD012000BD00B5FFF76A +:10739000B6FF48B14FF08050D0F83401062803D32F +:1073A000401C01D0012000BD002000BD0021017063 +:1073B000084670470146002008707047EFF31081BF +:1073C00001F0010172B60278012A01D0012200E029 +:1073D00000220123037001B962B60AB10020704790 +:1073E0004FF400507047E9E7EFF3108111F0010FFF +:1073F00072B64FF00002027000D162B600207047F2 +:10740000F2E700002DE9F04115460E46044600273C +:1074100000F0EBF8A84215D3002341200FE000BF95 +:1074200094F84220A25CF25494F84210491CB1FB3B +:10743000F0F200FB12115B1C84F84210DBB2AB428D +:10744000EED3012700F0DDF83846BDE8F08172493F +:1074500010B5802081F800047049002081F84200B6 +:1074600081F84100433181F8420081F84100433105 +:1074700081F8420081F841006948FFF797FF6848AA +:10748000401CFFF793FFEBF793FCBDE8104000F0C2 +:10749000B8B840207047614800F0A7B80A460146D6 +:1074A0005E48AFE7402070475C48433000F09DB82D +:1074B0000A46014659484330A4E7402101700020A4 +:1074C000704710B504465548863000F08EF820709D +:1074D000002010BD0A460146504810B58630FFF71F +:1074E00091FF08B1002010BD42F2070010BD70B539 +:1074F0000C460646412900D9FFDF4A48006810388B +:1075000040B200F054F8C5B20D2000F050F8C0B2FF +:10751000854201D3012504E0002502E00DB1EBF71F +:107520008AFC224631463D48FFF76CFF0028F5D023 +:1075300070BD2DE9F0413A4F0025064617F10407CA +:1075400057F82540204600F041F810B36D1CEDB20D +:10755000032DF5D33148433000F038F8002825D00A +:107560002E4800F033F8002820D02C48863000F058 +:107570002DF800281AD0EBF734FC2948FFF71EFF3E +:10758000B0F5005F00D0FFDFBDE8F0412448FFF711 +:107590002BBF94F841004121265414F8410F401CA0 +:1075A000B0FBF1F201FB12002070D3E74DE7002899 +:1075B00004DB00F1E02090F8000405E000F00F008B +:1075C00000F1E02090F8140D4009704710F8411FB9 +:1075D0004122491CB1FBF2F302FB131140788142B6 +:1075E00001D1012070470020704710F8411F4078FA +:1075F000814201D3081A02E0C0F141000844C0B240 +:10760000704710B50648FFF7D9FE002803D1BDE842 +:107610001040EBF7D1BB10BD0DE000E0340B0020B3 +:10762000AC00002004ED00E070B5154D2878401C3A +:10763000C4B26878844202D000F0DBFA2C7070BDCE +:107640002DE9F0410E4C4FF0E02600BF00F0C6FAE5 +:107650000EF09AFB40BF20BF677820786070D6F8A4 +:107660000052E9F798FE854305D1D6F8040210B917 +:107670002078B842EAD000F0ACFA0020BDE8F081F2 +:10768000BC0000202DE9F04101264FF0E02231033B +:107690004FF000084046C2F88011BFF34F8FBFF390 +:1076A0006F8F204CC4F800010C2000F02EF81E4D06 +:1076B0002868C04340F30017286840F01000286095 +:1076C000C4F8046326607F1C02E000BF0EF05CFB80 +:1076D000D4F800010028F9D01FB9286820F0100064 +:1076E0002860124805686660C4F80863C4F8008121 +:1076F0000C2000F00AF82846BDE8F08110B50446D9 +:10770000FFF7C0FF2060002010BD002809DB00F05B +:107710001F02012191404009800000F1E020C0F8E3 +:107720008012704700C0004010ED00E008C5004026 +:107730002DE9F047FF4C0646FF21A06800EB06123A +:1077400011702178FF2910D04FF0080909EB0111C1 +:1077500009EB06174158C05900F0F4F9002807DD7D +:10776000A168207801EB061108702670BDE8F0874B +:1077700094F8008045460DE0A06809EB05114158DA +:10778000C05900F0DFF9002806DCA068A84600EB2D +:1077900008100578FF2DEFD1A06800EB061100EB73 +:1077A00008100D700670E1E7F0B5E24B04460020CA +:1077B00001259A680C269B780CE000BF05EB0017AA +:1077C000D75DA74204D106EB0017D7598F4204D0EA +:1077D000401CC0B28342F1D8FF20F0BD70B5FFF766 +:1077E000ECF9D44C08252278A16805EB02128958DF +:1077F00000F0A8F9012808DD2178A06805EB011147 +:107800004058BDE87040FFF7CFB9FFF7A1F8BDE8D9 +:107810007040EBF779BB2DE9F041C64C2578FFF7B6 +:10782000CCF9FF2D6ED04FF00808A26808EB0516C2 +:10783000915900F087F90228A06801DD80595DE0C8 +:1078400000EB051109782170022101EB0511425C62 +:107850005AB1521E4254815901F5800121F07F41F5 +:1078600081512846FFF764FF34E00423012203EB33 +:10787000051302EB051250F803C0875CBCF1000F42 +:1078800010D0BCF5007F10D9CCF3080250F806C028 +:107890000CEB423C2CF07F4C40F806C0C3589A1ABF +:1078A000520A09E0FF2181540AE0825902EB4C326E +:1078B00022F07F428251002242542846FFF738FFCF +:1078C0000C21A06801EB05114158E06850F8272011 +:1078D000384690472078FF2814D0FFF76EF92278B9 +:1078E000A16808EB02124546895800F02BF90128DF +:1078F00093DD2178A06805EB01114058BDE8F04107 +:10790000FFF752B9BDE8F081F0B51D4614460E46AA +:107910000746FF2B00D3FFDFA00700D0FFDF85481D +:10792000FF210022C0E90247C57006710170427054 +:1079300082701046012204E002EB0013401CE15467 +:10794000C0B2A842F8D3F0BD70B57A4C064665784F +:107950002079854200D3FFDFE06840F82560607839 +:10796000401C6070284670BD2DE9FF5F1D468B46A8 +:107970000746FF24FFF721F9DFF8B891064699F88A +:107980000100B84200D8FFDF00214FF001084FF09E +:107990000C0A99F80220D9F808000EE008EB011350 +:1079A000C35CFF2B0ED0BB4205D10AEB011350F88C +:1079B00003C0DC450CD0491CC9B28A42EED8FF2C6A +:1079C00002D00DE00C46F6E799F803108A4203D185 +:1079D000FF2004B0BDE8F09F1446521C89F8022035 +:1079E00008EB04110AEB0412475440F802B00421DA +:1079F000029B0022012B01EB04110CD040F8012066 +:107A00004FF4007808234FF0020C454513D9E905DF +:107A1000C90D02D002E04550F2E7414606EB413283 +:107A200003EB041322F07F42C250691A0CEB0412DC +:107A3000490A81540BE005B9012506EB453103EBFA +:107A4000041321F07F41C1500CEB0411425499F80A +:107A500000502046FFF76CFE99F80000A84201D0C4 +:107A6000FFF7BCFE3846B4E770B50C460546FFF795 +:107A7000A4F8064621462846FFF796FE0446FF284E +:107A80001AD02C4D082101EB0411A868415830464A +:107A900000F058F800F58050C11700EBD1404013BA +:107AA0000221AA6801EB0411515C09B100EB4120ED +:107AB000002800DC012070BD002070BD2DE9F047DA +:107AC00088468146FFF770FE0746FF281BD0194DF8 +:107AD0002E78A8683146344605E0BC4206D02646DA +:107AE00000EB06121478FF2CF7D10CE0FF2C0AD023 +:107AF000A6420CD100EB011000782870FF2804D0BA +:107B0000FFF76CFE03E0002030E6FFF753F8414634 +:107B10004846FFF7A9FF0123A968024603EB0413B7 +:107B2000FF20C854A878401EB84200D1A87001EBCD +:107B3000041001E0000C002001EB06110078087031 +:107B4000104613E6081A0002C11700EB116000127C +:107B50007047000010B5202000F07FF8202000F0D2 +:107B60008DF84D49202081F80004E9F712FC4B49BB +:107B700008604B48D0F8041341F00101C0F8041329 +:107B8000D0F8041341F08071C0F804134249012079 +:107B90001C39C1F8000110BD10B5202000F05DF8BF +:107BA0003E480021C8380160001D01603D4A481E62 +:107BB00010603B4AC2F80803384B1960C2F8000154 +:107BC000C2F8600138490860BDE81040202000F08C +:107BD00055B834493548091F086070473149334862 +:107BE000086070472D48C8380160001D521E0260B1 +:107BF00070472C4901200860BFF34F8F70472DE973 +:107C0000F0412849D0F8188028480860244CD4F85E +:107C100000010025244E6F1E28B14046E9F712FBF3 +:107C200040B9002111E0D4F8600198B14046E9F76D +:107C300009FB48B1C4F80051C4F860513760BDE891 +:107C4000F041202000F01AB831684046BDE8F0410C +:107C50000EF0A4B8FFDFBDE8F08100280DDB00F0D6 +:107C60001F02012191404009800000F1E020C0F88E +:107C70008011BFF34F8FBFF36F8F7047002809DB70 +:107C800000F01F02012191404009800000F1E02036 +:107C9000C0F880127047000020E000E0C8060240F3 +:107CA00000000240180502400004024001000001EB +:107CB0005E4800210170417010218170704770B5DD +:107CC000054616460C460220EAF714FE57490120E5 +:107CD00008705749F01E086056480560001F046090 +:107CE00070BD10B50220EAF705FE5049012008706A +:107CF00051480021C0F80011C0F80411C0F8081163 +:107D00004E494FF40000086010BD48480178D9B1D1 +:107D10004B4A4FF4000111604749D1F8003100226D +:107D2000002B1CBFD1F80431002B02D0D1F8081170 +:107D300019B142704FF0100104E04FF001014170A1 +:107D400040490968817002704FF00000EAF7D2BD27 +:107D500010B50220EAF7CEFD34480122002102705E +:107D60003548C0F80011C0F80411C0F808110260CD +:107D700010BD2E480178002904BF407870472E4876 +:107D8000D0F80011002904BF02207047D0F800117C +:107D900000291CBFD0F80411002905D0D0F8080133 +:107DA000002804BF01207047002070471F4800B51D +:107DB0000278214B4078C821491EC9B282B1D3F85C +:107DC00000C1BCF1000F10D0D3F8000100281CBF87 +:107DD000D3F8040100280BD0D3F8080150B107E014 +:107DE000022802D0012805D002E00029E4D1FFDFFB +:107DF000002000BD012000BD0C480178002904BF0F +:107E0000807870470C48D0F8001100291CBFD0F8CA +:107E10000411002902D0D0F8080110B14FF0100071 +:107E2000704708480068C0B270470000BE000020DC +:107E300010F5004008F5004000F0004004F5014056 +:107E400008F5014000F400405748002101704170DE +:107E5000704770B5064614460D460120EAF74AFD04 +:107E600052480660001D0460001D05605049002056 +:107E7000C1F850014F490320086050494E4808603E +:107E8000091D4F48086070BD2DE9F0410546464880 +:107E90000C46012606704B4945EA024040F08070CE +:107EA0000860FFF72CFA002804BF47480460002749 +:107EB000464CC4F80471474945480860002D02BF8C +:107EC000C4F800622660BDE8F081012D18BFFFDF15 +:107ED000C4F80072266041493F480860BDE8F0815F +:107EE0003148017871B13B4A394911603749D1F8BD +:107EF00004210021002A08BF417002D0384A1268CC +:107F0000427001700020EAF7F5BC2748017800298B +:107F100004BF407870472D48D0F80401002808BFFE +:107F200070472F480068C0B27047002808BF7047EC +:107F30002DE9F0471C480078002808BFFFDF234CDC +:107F4000D4F80401002818BFBDE8F0874FF00209FB +:107F5000C4F80493234F3868C0F30018386840F021 +:107F600010003860D4F80401002804BF4FF4004525 +:107F70004FF0E02608D100BFC6F880520DF004FF94 +:107F8000D4F804010028F7D0B8F1000F03D1386805 +:107F900020F010003860C4F80893BDE8F0870B4962 +:107FA0000120886070470000C100002008F50040F3 +:107FB000001000401CF500405011004098F50140B1 +:107FC0000CF0004004F5004018F5004000F00040BF +:107FD0000000020308F501400000020204F5014020 +:107FE00000F4004010ED00E0012804BF41F6A47049 +:107FF0007047022804BF41F288307047042804BF4C +:1080000046F218007047082804BF47F2A0307047B6 +:1080100000B5FFDF41F6A47000BD10B5FE48002496 +:1080200001214470047044728472C17280F825404A +:10803000C462846380F83C4080F83D40FF2180F8B2 +:108040003E105F2180F83F1018300DF09FFFF3497C +:10805000601E0860091D0860091D0C60091D08608C +:10806000091D0C60091D0860091D0860091D0860D4 +:10807000091D0860091D0860091D0860091D0860C8 +:10808000091D0860091D086010BDE549486070477A +:10809000E448016801F00F01032904BF0120704783 +:1080A000016801F00F01042904BF02207047016834 +:1080B00001F00F01052904D0006800F00F00062828 +:1080C00007D1D948006810F0060F0CBF0820042023 +:1080D000704700B5FFDF012000BD012812BF022854 +:1080E00000207047042812BF08284FF4C87070475A +:1080F00000B5FFDF002000BD012804BF2820704725 +:10810000022804BF18207047042812BF08284FF423 +:10811000A870704700B5FFDF282000BD70B5C148CA +:10812000016801F00F01032908BF012414D0016880 +:1081300001F00F01042904BF022418210DD00168A9 +:1081400001F00F0105294BD0006800F00F00062850 +:108150001CBFFFDF012443D02821AF48C26A806AD8 +:10816000101A0E18082C04BF4EF6981547F2A030CE +:108170002DD02046042C08BF4EF628350BD0012800 +:1081800008BF41F6A47506D0022C1ABFFFDF41F6E6 +:10819000A47541F28835012C08BF41F6A47016D0B1 +:1081A000022C08BF002005D0042C1ABFFFDF0020DE +:1081B0004FF4C8702D1A022C08BF41F2883006D047 +:1081C000042C1ABFFFDF41F6A47046F21800281AEB +:1081D0004FF47A7100F2E730B0FBF1F0304470BD3B +:1081E0009148006810F0060F0CBF082404244FF4D7 +:1081F000A871B2E710B58D49026801F118040A634D +:1082000042684A63007A81F83800207E48B1207FB6 +:10821000F6F781F9A07E011C18BF0121207FF6F737 +:1082200069F9607E002808BF10BD607FF6F773F91A +:10823000E07E011C18BF0121607FBDE81040F6F709 +:1082400059B930B50024054601290AD0022908BFD2 +:108250004FF0807405D0042916BF08294FF0C74499 +:10826000FFDF44F4847040F480107149086045F4E5 +:10827000403001F1040140F00070086030BD30B5BD +:108280000024054601290AD0022908BF4FF0807456 +:1082900005D0042916BF08294FF0C744FFDF44F476 +:1082A000847040F480106249086045F4403001F168 +:1082B000040140F0007008605E48D0F8000100281A +:1082C00018BFFFDF30BD2DE9F04102274FF0E02855 +:1082D00001250024C8F88071BFF34F8FBFF36F8F63 +:1082E000554804600560FFF751F8544E18B13068E6 +:1082F00040F480603060FFF702F838B1306820F059 +:10830000690040F0960040F0004030604D494C4814 +:1083100008604FF01020806CB0F1FF3F04D04A4954 +:108320000A6860F317420A60484940F25B600860DF +:10833000091F40F203100860081F05603949032037 +:10834000086043480560444A42491160444A434931 +:108350001160121F43491160016821F440710160EE +:10836000016841F480710160C8F8807231491020C1 +:10837000C1F80403284880F83140C46228484068A6 +:10838000002808BFBDE8F081BDE8F0410047274A5A +:108390000368C2F81A308088D08302F11800017295 +:1083A00070471D4B10B51A7A8A4208D10146062241 +:1083B000981CF6F715F8002804BF012010BD002016 +:1083C00010BD154890F825007047134A5170107081 +:1083D0007047F0B50546800000F1804000F5805000 +:1083E0008B88C0F820360B78D1F8011043EA0121C0 +:1083F000C0F8001605F10800012707FA00F61A4C2C +:10840000002A04BF2068B04304D0012A18BFFFDF50 +:1084100020683043206029E0280C0020000E004036 +:10842000C40000201015004014140040100C00205F +:108430001415004000100040FC1F00403C17004095 +:108440002C000089781700408C150040381500403A +:108450005016004000000E0408F501404080004026 +:10846000A4F501401011004040160040206807FAB2 +:1084700005F108432060F0BD0CF0C4BCFE4890F844 +:1084800032007047FD4AC17811600068FC49000263 +:1084900008607047252808BF02210ED0262808BF93 +:1084A0001A210AD0272808BF502106D00A2894BFD5 +:1084B0000422062202EB4001C9B2F24A1160F249DD +:1084C000086070472DE9F047EB4CA17A012956D09E +:1084D000022918BFBDE8F087627E002A08BFBDE808 +:1084E000F087012950D0E17E667F0D1C18BF012561 +:1084F0005FF02401DFF894934FF00108C9F84C8035 +:10850000DFF88CA34718DAF80000B84228BFFFDF75 +:108510000020C9F84C01CAF80070300285F0010152 +:1085200040EA015040F0031194F82000820002F16B +:10853000804202F5C042C2F81015D64901EB800115 +:10854000A07FC20002F1804202F5F832C2F8141591 +:10855000D14BC2F81035E27FD30003F1804303F51D +:10856000F833C3F81415CD49C3F8101508FA00F014 +:1085700008FA02F10843CA490860BDE8F087227E84 +:10858000002AAED1BDE8F087A17E267F002914BF66 +:10859000012500251121ADE72DE9F041C14E8046AE +:1085A00003200D46C6F80002BD49BF4808602846B2 +:1085B0000CF02CFCB04F0124B8F1000F04BFBC72CA +:1085C000346026D0B8F1010F23D1B848006860B9F3 +:1085D00015F00C0F09D0C6F80443012000F0DAFEB4 +:1085E000F463346487F83C4002E0002000F0D2FEDF +:1085F00028460CF0F3FC0220B872FEF7B7FE38B93B +:10860000FEF7C4FE20B9AA48016841F4C021016008 +:1086100074609E48C4649E4800682946BDE8F041E5 +:1086200050E72DE9F0479F4E814603200D46C6F8DE +:108630000002DFF86C829C48C8F8000008460CF085 +:10864000E5FB28460CF0CAFC01248B4FB9F1000F62 +:1086500003D0B9F1010F0AD031E0BC72B86B40F41D +:108660008010B8634FF48010C8F8000027E00220A3 +:10867000B872B86B40F40010B8634FF40010C8F83B +:1086800000008A48006860B915F00C0F09D0C6F8E0 +:108690000443012000F07EFEF463346487F83C401C +:1086A00002E0002000F076FEFEF760FE38B9FEF72B +:1086B0006DFE20B97E48016841F4C0210160EAF7EF +:1086C000F7FA2946BDE8F047FCE62DE9F84F754C6E +:1086D0008246032088461746C4F80002DFF8C0919E +:1086E0007148C9F8000010460CF090FBDFF8C4B1E7 +:1086F000614E0125BAF1000F04BFCBF80040B572FE +:1087000004D0BAF1010F18BFFFDF2FD06A48C0F8BC +:1087100000806B4969480860B06B40F40020B0638A +:10872000D4F800321021C4F808130020C4F8000265 +:10873000DFF890C18A03CCF80020C4F80001C4F827 +:108740000C01C4F81001C4F80401C4F81401C4F801 +:1087500018015D4800680090C4F80032C9F8002094 +:10876000C4F80413BAF1010F14D026E038460CF017 +:1087700035FCFEF7FBFD38B9FEF708FE20B94C4882 +:10878000016841F4C02101605048CBF8000002208C +:10879000B072BBE74548006860B917F00C0F09D00C +:1087A000C4F80453012000F0F5FDE563256486F864 +:1087B0003C5002E0002000F0EDFD4FF40020C9F82D +:1087C00000003248C56432480068404528BFFFDFDA +:1087D00039464046BDE8F84F74E62DE9F041264C95 +:1087E0000646002594F8310017468846002808BF41 +:1087F000FFDF16B1012E16D021E094F831000128D8 +:1088000008D094F83020394640460CF014FBE16A59 +:10881000451814E094F830103A4640460CF049FBF5 +:10882000E16A45180BE094F8310094F83010012803 +:108830003A46404609D00CF064FBE16A45183A46D6 +:1088400029463046BDE8F0413FE70CF014FBE16AF1 +:108850004518F4E72DE9F84F124CD4F8000220F047 +:108860000309D4F804034FF0100AC0F30018C4F849 +:1088700008A300262CE00000280C0020241500404E +:108880001C150040081500405415004000800040B1 +:108890004C850040006000404C81004010110040B9 +:1088A00004F5014000100040000004048817004057 +:1088B00068150040ACF50140488500404881004003 +:1088C000A8F5014008F501401811004004100040CF +:1088D000C4F80062FC48FB490160FC4D0127A97AFD +:1088E000012902D0022903D015E0297E11B912E036 +:1088F000697E81B1A97FEA7F07FA01F107FA02F2E6 +:108900001143016095F82000800000F1804000F5DF +:10891000C040C0F81065FF208DF80000C4F8106159 +:10892000276104E09DF80000401E8DF800009DF8CE +:10893000000018B1D4F810010028F3D09DF8000011 +:10894000002808BFFFDFC4F81061002000F022FDFE +:108950006E72AE72EF72C4F80092B8F1000F18BFD9 +:10896000C4F804A3BDE8F88FFF2008B58DF8000017 +:10897000D7480021C0F810110121016105E000BFB6 +:108980009DF80010491E8DF800109DF8001019B1D7 +:10899000D0F810110029F3D09DF80000002808BF7E +:1089A000FFDF08BD0068CB4920F07F4008607047BA +:1089B0004FF0E0200221C0F8801100F5C070BFF335 +:1089C0004F8FBFF36F8FC0F80011704710B490E85D +:1089D0001C10C14981E81C10D0E90420C1E9042021 +:1089E00010BC70474FF0E0210220C1F80001704731 +:1089F000BA4908707047BA490860704770B50546B3 +:108A0000EAF756F9B14C2844E16A884298BFFFDF83 +:108A100001202074EAF74CF9B24A28440021606131 +:108A2000C2F84411B0490860A06BB04940F480001E +:108A3000A063D001086070BD70B5A44C0546AC4A77 +:108A40000220207410680E4600F00F00032808BFB3 +:108A5000012213D0106800F00F00042808BF022282 +:108A60000CD0106800F00F0005281BD0106800F033 +:108A70000F0006281CBFFFDF012213D094F831003D +:108A800094F83010012815D028460CF081FA954949 +:108A900060610020C1F844016169E06A08449249BC +:108AA000086070BD9348006810F0060F0CBF0822E4 +:108AB0000422E3E7334628460CF038FAE7E7824918 +:108AC0004FF4800008608148816B21F4800181634C +:108AD000002101747047C20002F1804202F5F832B1 +:108AE000854BC2F81035C2F81415012181407F482A +:108AF00001607648826B1143816370477948012198 +:108B00004160C1600021C0F84411774801606F489E +:108B1000C1627047794908606D48D0F8001241F091 +:108B20004001C0F8001270476948D0F8001221F0E7 +:108B30004001C0F800127149002008607047644885 +:108B4000D0F8001221F01001C0F80012012181615B +:108B500070475E49FF2081F83E005D480021C0F863 +:108B60001C11D0F8001241F01001C0F8001270473B +:108B7000574981B0D1F81C21012A0DD0534991F8F1 +:108B80003E10FF290DBF00204942017001B008BF0F +:108B90007047012001B07047594A126802F07F0205 +:108BA000524202700020C1F81C0156480068009033 +:108BB000EFE7F0B517460C00064608BFFFDF434D50 +:108BC00014F0010F2F731CBF012CFFDF002E0CBF10 +:108BD000012002206872EC7201281CBF0228FFDF0E +:108BE000F0BD3A4981F83F007047384A136C036082 +:108BF000506C086070472DE9F84F38480078042819 +:108C000028BFFFDF314CDFF8C080314D94F83C00C5 +:108C100000260127E0B1D5F8040110F1000918BFC2 +:108C20004FF00109D5F81001002818BF012050EAC3 +:108C300009014FF4002B17D08021C5F80813C8F89C +:108C400000B084F83C6090F0010F18BFBDE8F88FC9 +:108C5000DFF89090D9F84C01002871D0A07A012853 +:108C60006FD002286ED0D1E0D5F80001DFF890A0D7 +:108C700030B3C5F800616F61FF20009002E0401E34 +:108C8000009005D0D5F81C0100280098F7D000B955 +:108C9000FFDFDAF8000000F07F0A94F83F0050454B +:108CA0003CBF002000F076FB84F83EA0C5F81C61B4 +:108CB000C5F808731348006800902F64AF6326E07E +:108CC00022E0000000000E0408F50140280C0020FE +:108CD000001000403C150040100C0020C400002093 +:108CE00004150040008000404485004004F5014028 +:108CF000101500401414004004110040601500409D +:108D0000481500401C110040B9F1000F03D0B9F123 +:108D1000000F2ED05CE0DAF8000000F07F0084F84D +:108D20003E00C5F81C6194F83D1061B194F83F1005 +:108D300081421BD2002000F02DFB2F64AF6315E0B1 +:108D400064E04CE04EE0FE49096894F83F308AB296 +:108D5000090C984203D30F2A06D9022904D2012014 +:108D600000F018FB2F6401E02F64AF63F548006842 +:108D700000908022C5F80423F3498F64F348036808 +:108D8000A0F1040CDCF800C043F698273B4463458F +:108D900015D2026842F210731A440260C1F84861A9 +:108DA000EC49EB480860091FEB480860EB48C0F845 +:108DB00000B0A06B40F40020A063BDE8F88F06600F +:108DC000C1F84861C5F80823C8F800B0C1F8486187 +:108DD0008020C5F80803C8F800B0BDE8F88F207EF1 +:108DE00010B913E0607E88B1A07FE17F07FA00F040 +:108DF00007FA01F10843C8F8000094F82000800049 +:108E000000F1804000F5C040C0F81065C9F84C7012 +:108E1000D34800682064D34800686064D248A16BDE +:108E20000160A663217C002019B1D9F84411012901 +:108E300000D00021A27A012A6ED0022A74D000BF8D +:108E4000D5F8101101290CBF1021002141EA0008BA +:108E5000C648016811F0FF0F03D0D5F8141101299D +:108E600000D0002184F83210006810F0FF0F03D00A +:108E7000D5F81801012800D0002084F83300BC4840 +:108E8000006884F83400FEF774FF012818BF002042 +:108E900084F83500C5F80061C5F80C61C5F81061AB +:108EA000C5F80461C5F81461C5F81861B1480068D7 +:108EB0000090A548C0F84461AF480068DFF8BC9254 +:108EC0000090D9F80000A062A9F104000068E062F7 +:108ED000AB48016801F00F01032908BF012013D03E +:108EE000016801F00F01042908BF02200CD00168BD +:108EF00001F00F01052926D0006800F00F000628B8 +:108F00001CBFFFDF01201ED084F83000A07A84F857 +:108F1000310002282CD11EE0D5F80C01012814BF25 +:108F2000002008208CE7FFE7D5F80C01012814BFCA +:108F300000200220934A1268012A14BF0422002252 +:108F4000104308437CE79048006810F0060F0CBF00 +:108F500008200420D8E7607850B18C490968097866 +:108F60000840217831EA000008BF84F8247001D05D +:108F700084F82460DFF818A218F0020F06D0E9F791 +:108F800097FEA16A081ADAF81010884718F0010F46 +:108F900018BF4FF0000B0DD0E9F78AFEE16ADAF84E +:108FA0001420081A594690477A48007810F0010FAB +:108FB0002FD10CE018F0020F18BF4FF0010BEBD1CE +:108FC00018F0080F18BF4FF0020BE5D1ECE7DFF8FF +:108FD000BCB1DBF80000007800F00F00072828BFC4 +:108FE00084F8256015D2DBF80000062200F10901A3 +:108FF000A01CF5F7F5F940B9207ADBF800100978E4 +:10900000B0EBD11F08BF012001D04FF0000084F861 +:109010002500E17A4FF0000011F0020F1CBF18F09C +:10902000020F18F0040F19D111F0100F1CBF94F8A3 +:109030003320002A02D094F835207AB111F0080FBD +:109040001CBF94F82420002A08D111F0040F02D08C +:1090500094F8251011B118F0010F01D04FF0010064 +:10906000617A19B168B1FFF7F5FB10E03E484A4953 +:109070000160D5F8000220F00300C5F80002E77295 +:1090800005E001290AD0022918BFFFDF0DD018F032 +:10909000010F14D0DAF80000804745E06672E772ED +:1090A000A7729621227B002006E06672E7720220FA +:1090B000A072227B96210120FFF78FFBE7E718F0D3 +:1090C000020F2AD018F0040F21D1FEF74FF9F0B9A2 +:1090D000FEF75CF9D8B931480168001F0068C0F399 +:1090E000006CC0F3425500F00F03C0F30312C0F34D +:1090F0000320BCF1000F0AD0002B1CBF002A00285F +:1091000005D1002918BF032D38BF48F0040827EA0D +:109110009800DAF80410884706E018F0080F18BF26 +:10912000DAF8080056D08047A07A022818BFBDE8B8 +:10913000F88F207C002808BFBDE8F88F02492FE097 +:10914000741500401C11004000800040488500401C +:1091500014100040ACF501404881004004F5014086 +:1091600004B500404C85004008F501404016004021 +:109170001014004018110040448100404485004014 +:109180001015004000140040141400400415004065 +:10919000100C0020C40000200000040454140040FF +:1091A000C1F8446102281DD0012818BFFFDFE16A21 +:1091B0006069884298BFFFDFD4F81400C9F8000046 +:1091C000A06B4FF4800140F48000A06382480160EE +:1091D000BDE8F88F18F0100F14BFDAF80C00FFDFAD +:1091E000A1D1A1E76169E06A0844E7E738B57B49A6 +:1091F00004460220887201212046FFF763F9784A6D +:1092000004F13D001060774B0020C3F8440176491B +:10921000C1F80001C1F80C01C1F81001C1F8040146 +:10922000C1F81401C1F818017048006800900120CD +:109230009864101D00681168884228BFFFDF38BDA0 +:109240002DE9F843654A88460024917A0125684F44 +:10925000012902D0022903D015E0117E11B912E0D4 +:10926000517E81B1917FD37F05FA01F105FA03F3B5 +:109270001943396092F82010890001F1804101F50D +:10928000C041C1F8104506460220907201213046C7 +:10929000FFF718F9524906F13D000860514AC2F83B +:1092A00044415148C0F80041C0F80C41C0F8104199 +:1092B000C0F80441C0F81441C0F818414B48006898 +:1092C00000909564081D00680968884228BFFFDF88 +:1092D000B8F1000F1CBF4FF400303860BDE8F883D0 +:1092E000022810B50DD0012804BF42F6CE3010BDC3 +:1092F000042817BF082843F6A440FFDF41F66A00A0 +:1093000010BDFDF7E5FF30B9FDF7F9FF002808BFF4 +:1093100041F6583001D041F2643041F29A010844DC +:1093200010BD314910B50020C1F800023049314864 +:109330000860324930480860091D31480860091D3D +:1093400030480860091D30480860091D2F48086032 +:10935000091D2F48086001200BF058FD1E494FF4ED +:109360003810086010BD22494FF43810086070476B +:109370002848016803291BBF00680228012000203B +:109380007047244801680B291BBF00680A28012088 +:109390000020704720490968C9B9204A204913684C +:1093A00070B123F0820343F07D0343F00043136068 +:1093B0000A6822F0100242F0600242F0004205E02A +:1093C00023F0004313600A6822F000420A60034958 +:1093D00081F83D007047000004F50140280C002092 +:1093E00044850040008000400010004018110040FB +:1093F00008F50140000004041011004098F50140F8 +:109400000410004044810040141000401C11004032 +:109410001010004050150040881700403C170040D5 +:109420007C17004010B5404822220021F5F72FF8A4 +:109430003D480024017821F010010170012104F061 +:10944000FFFE3A494FF6FF7081F822408884384980 +:109450000880488010BD704734498A8C824218BF0A +:109460007047002081F822004FF6FF708884704713 +:109470002D49016070472E49088070472B498A8C1E +:10948000A2F57F43FF3B03D00021016008467047EF +:1094900091F822202549012A1ABF016001200020ED +:1094A0007047224901F1220091F82220012A04BFCD +:1094B00000207047012202701D48008888841046F1 +:1094C00070471B49488070471849194B8A8C5B8844 +:1094D0009A4206D191F82220002A1EBF0160012085 +:1094E0007047002070471148114A818C5288914280 +:1094F00009D14FF6FF71818410F8221F19B10021A4 +:10950000017001207047002070470848084A818C8C +:109510005288914205D190F8220000281CBF0020FB +:109520007047012070470000960C0020700C00204E +:10953000CC0000207047584A012340B1012818BFD1 +:1095400070471370086890608888908170475370E6 +:109550000868C2F802008888D08070474E4A10B16F +:10956000012807D00EE0507860B1D2F80200086000 +:10957000D08804E0107828B19068086090898880CD +:109580000120704700207047434910B1012803D0E3 +:1095900006E0487810B903E0087808B10120704768 +:1095A0000020704730B58DB00C4605460D220021D5 +:1095B00004A8F4F76CFFE0788DF81F0020798DF88F +:1095C0001E0060798DF81D00286800906868019081 +:1095D000A8680290E868039068460AF087FF207840 +:1095E0009DF82F1088420CD160789DF82E1088428B +:1095F00007D1A0789DF82D10884202BF01200DB040 +:1096000030BD00200DB030BD30B50C4605468DB0E4 +:109610004FF0030104F1030012B1FDF749FF01E02F +:10962000FDF765FF60790D2220F0C00040F040009A +:109630006071002104A8F4F72AFFE0788DF81F007C +:1096400020798DF81E0060798DF81D002868009043 +:1096500068680190A8680290E868039068460AF07C +:1096600045FF9DF82F0020709DF82E0060709DF83A +:109670002D00A0700DB030BD10B5002904464FF08C +:10968000060102D0FDF714FF01E0FDF730FF60791D +:1096900020F0C000607110BDD0000020FE4840687E +:1096A00070472DE9F0410F46064601461446012059 +:1096B00005F06FF8054696F86500FEF795FC4AF24E +:1096C000B12108444FF47A71B0FBF1F0718840F297 +:1096D00071225143C0EB4100001BA0F55A7402F007 +:1096E0005AFF002818BF1E3CAF4234BF28463846F8 +:1096F000A04203D2AF422CBF3C462C467462BDE868 +:10970000F0812DE9FF4F8BB0044690F86500884644 +:109710000390DDE90D1008430A90E0480027057822 +:109720000C2D28BFFFDFDE4E36F8159094F88851D7 +:109730000C2D28BFFFDFDA4830F81500484480B20E +:10974000009094F87D000D280CBF012000200790A8 +:109750000D98002804BF94F82C0103282BD10798FA +:1097600048B3B4F8AA01404525D1D4F83401C4F86F +:109770002001608840F2E2414843C4F82401B4F873 +:109780007A01B4F806110844C4F82801204602F012 +:109790000CFFB4F8AE01E08294F8AC016075B4F847 +:1097A000B0016080B4F8B201A080B4F8B401E080E8 +:1097B000022084F82C01D4F884010990D4F88001A7 +:1097C0000690B4F80661B4F87801D4F874110191E8 +:1097D0000D9921B194F8401151B100F0D6B804F5BB +:1097E000807104917431059104F5B075091D07E08D +:1097F00004F5AA710491091D059104F5A275091DCE +:109800000891B4F87010A8EB0000A8EB01010FFA62 +:1098100080F90FFA81FBB9F1000F05DAD4F8700175 +:1098200001900120D9460A909C484FF0000A007927 +:10983000A8B3F2F77FFB90B3B4F8180102282ED337 +:1098400094F82C0102282AD094F8430138BB94F8EC +:10985000880100900C2828BFFFDF9148009930F85C +:10986000110000F5C86080B2009094F82C01012826 +:109870007ED0608840F2E2414843009901F0E6F86A +:10988000D4F8342180B206EB0B01A1EB0901821A56 +:1098900001FB02AAC4F83401012084F8430194F8C2 +:1098A0002C01002865D0012800F01482022800F065 +:1098B0007181032818BFFFDF00F04782A7EB0A0180 +:1098C0000198FCF738F90599012640F271220860E9 +:1098D0000898A0F80080002028702E710598006874 +:1098E000A8606188D4F834015143C0EB41006B4952 +:1098F000A0F54E70C8618969814287BF04990860EC +:10990000049801600498616A0068084400F5D47006 +:10991000E86002F040FE10B1E8681E30E8606E7149 +:10992000B4F8F000A0EB080000B20028C4BF032088 +:109930006871079800280E9800F06982E0B100BFB6 +:10994000B4F8181100290CBF0020B4F81A01A4F8CB +:109950001A0194F81C21401C504388420CD26879AB +:10996000401E002808DD6E71B4F81A01401C01E0A9 +:109970000FE05AE0A4F81A010D98002800F06A825E +:1099800094F84001002800F061820FB00220BDE889 +:10999000F08F94F8800003283DD03F4894F865107C +:1099A00090F82C00F7F78DFDE18A40F271225143C7 +:1099B00000EB4100CDF80800D4F82401009901F033 +:1099C00045F8D4F82021D4F82811821A01FB02AA04 +:1099D000C4F820010099029801F038F8D4F8301149 +:1099E000C4F83001411A8A44608840F2E241484399 +:1099F000009901F02BF806EB0B01D4F82821A1EB1C +:109A00000901891AD4F83421C4F83401821A491E94 +:109A100001FB02AA40E7E18A40F27122D4F8240156 +:109A2000514300EB41000290C6E70698002808BFAA +:109A3000FFDF94F86510184890F82C00F7F741FD07 +:109A40000990E08A40F271214143099800EB4100FE +:109A5000009900F0FBFFC4F83001608840F2E24159 +:109A60004843009900F0F2FFC4F8340103A902A8AA +:109A7000FFF7BBF8DDE90160039FE9F7F0F8014665 +:109A80003046FDF769F800F10F06E9F711F9381AC9 +:109A9000801B009006E00000B80C0020E0000020D1 +:109AA000E4620200B4F83401214686B20120D4F801 +:109AB000289004F06EFE074694F86500FEF794FACD +:109AC0004AF2B12108444FF47A7BB0FBFBF0618885 +:109AD00040F271225143C0EB4100801BA0F55A7641 +:109AE00002F059FD002818BF1E3EB94534BF384664 +:109AF0004846B04203D2B9452CBF4E463E46666248 +:109B000094F86500FEF7E9FA00F2E140B0FBFBF1E2 +:109B100006980F1894F86500FEF7DFFA064694F8E9 +:109B20006500FEF761FA30444AF2AB310844B0FBFD +:109B3000FBF1E08A40F2712242430998D4F8306187 +:109B400000EB4200401A801B384400993138471A14 +:109B5000607D40F2E24110FB01F994F8650000904D +:109B600010F00C0F0ABF00984EF62830FEF73CFAB2 +:109B70004AF2B1210844B0FBFBF000EB460000EBD9 +:109B800009060098FEF7B8FA304400F18401FE4857 +:109B9000816193E6E18A40F27122D4F824015143B5 +:109BA00000EB4100009900F051FFC4F830016188DA +:109BB00040F2E2404843009900F048FFC4F8340105 +:109BC00087B221460120D4F828B004F0E2FD814696 +:109BD00094F86500FEF708FA4AF2B12101444FF407 +:109BE0007A70B1FBF0F0618840F271225143C0EB12 +:109BF0004100C01BA0F55A7702F0CDFC002818BF29 +:109C00001E3FCB4534BF48465846B84203D2CB45E9 +:109C10002CBF5F464F4667621EBB0E9808B394F890 +:109C200065603046FEF7E0F94AF2B12101444FF495 +:109C30007A70B1FBF0F0D4F83011E28A084440F2B7 +:109C40007123D4F824115A4301EB42010F1A304614 +:109C5000FEF752FA01460998401A3844A0F120074D +:109C60000AE0E18A40F27122D4F82401514300EB6A +:109C70004100D4F83011471AD4F82821D4F8201123 +:109C8000D4F8300101FB0209607D40F2E24110FB93 +:109C900001FB94F8656016F00C0F0ABF30464EF6D3 +:109CA0002830FEF7A1F94AF2B12101444FF47A704D +:109CB000B1FBF0F000EB490000EB0B093046FEF77A +:109CC0001BFA484400F16001AF488161012084F82B +:109CD0002C01F3E5618840F271225143D4F834013C +:109CE000D4F82821C0EB410101FB09F706EB0B0179 +:109CF000891AD4F820C1D4F83031491E0CFB023245 +:109D000001FB0029607D40F2E24110FB01FB94F869 +:109D1000656016F00C0F0ABF30464EF62830FEF78D +:109D200063F94AF2B12101444FF47A70B1FBF0F0CB +:109D300000EB490000EB0B093046FEF7DDF9484423 +:109D400000F1600190488161B8E5618840F27122BC +:109D5000D4F834015143C0EB410000FB09F794F8FB +:109D60007C0024281CBF94F87D0024280BD1B4F873 +:109D7000AA01A8EB000000B2002804DB94F8AD01B2 +:109D8000002818BF03900A9800B3FEB9099800286C +:109D90001ABF06980028FFDF94F8650010F00C0F3A +:109DA00014BF4EF62830FEF71FF94AF2B1210144E4 +:109DB0004FF47A70B1FBF0F03F1A94F86500FEF7AB +:109DC0009BF90999081A3844A0F12007D4F83411F6 +:109DD00006EB0B0000FB01F6039810F00C0F0ABF16 +:109DE00003984EF62830FEF7FFF84AF2B1210144FD +:109DF0004FF47A70B1FBF0F000EB46060398FEF7E3 +:109E00007BF9304400F160015F48816156E500282C +:109E10007FF496AD94F82C0100283FF4ADAD618835 +:109E200040F27122D4F834015143C0EB410128467D +:109E3000F7F712F90004000C3FF49EAD18990029C1 +:109E400018BF088001200FB0BDE8F08F94F87C01A6 +:109E5000FCF7D1FC94F87C012946FCF7B0FB20B15B +:109E60000D9880F0010084F841010FB00020BDE89A +:109E7000F08F2DE9F843454C0246434F00266168B8 +:109E8000606A052A60D2DFE802F003464B4F5600B5 +:109E9000A07A002560B101216846FDF709FB9DF815 +:109EA000000042F210710002B0FBF1F201FB12055A +:109EB000F4F720FE4119A069FBF73DFEA06126746E +:109EC000032060754FF0010884F81480607AD0B9DF +:109ED000A06A80B1F4F70EFE0544F4F785FC411941 +:109EE000A06A884224BF401BA06204D2C4F8288024 +:109EF000F5F72BFE07E0207B04F11001FCF75FFB78 +:109F0000002808BFFFDF2684FCF739F87879BDE820 +:109F1000F843E8F7F9BFBDE8F843002100F0B3BD0E +:109F2000C1F88001BDE8F883D1F88001BDE8F843AD +:109F3000012100F0A8BD84F83060FCF720F87879A2 +:109F4000BDE8F843E8F7E0BFFFDFBDE8F8832DE99F +:109F5000F04F0E4C824683B020788B4601270025B7 +:109F6000094E4FF00209032804BF207B50457DD1E4 +:109F7000606870612078032818BFFFDF4FF0030886 +:109F8000BBF1080F73D203E0E0000020B80C002002 +:109F9000DFE80BF0040F32322D9999926562F5F7E4 +:109FA000ABF9002818BFFFDF86F8028003B0BDE8D8 +:109FB000F08FF4F77AFF68B9F4F716FC0546E0690C +:109FC000A84228BFE56105D2281A0421FCF7F7FD55 +:109FD000E56138B1F5F723FD002818BFFFDF03B0B6 +:109FE000BDE8F08F03B00020BDE8F04F41E703B0BB +:109FF000BDE8F04FFEF7FFBD2775257494F83000DB +:10A000004FF0010A58B14FF47A71A069FBF793FD44 +:10A01000A061002104F11000F7F71EF80EE0F4F73C +:10A0200069FD82465146A069FBF785FDA061514656 +:10A0300004F11000F7F710F800F1010A208C411C20 +:10A040000A293CBF50442084606830B1208C401CF9 +:10A050000A2828BF84F8159001D284F81580607A08 +:10A06000A8B9A06AE8B1F4F745FD01E02FE02AE0C5 +:10A070008046F4F7B9FB00EB0801A06A884224BFD0 +:10A08000A0EB0800A0620CD2A762F5F75EFD207B72 +:10A09000FCF74BF82570707903B0BDE8F04FE8F796 +:10A0A00033BF207B04F11001FCF789FA002808BFB8 +:10A0B000FFDF03B0BDE8F08F207BFCF736F825709A +:10A0C00003B0BDE8F08FFFDF03B0BDE8F08FBAF159 +:10A0D000200F28BFFFDFDFF8E886072138F81A00D5 +:10A0E000F9F7E4FE040008BFFFDFBAF1200F28BF34 +:10A0F000FFDF38F81A002188884218BFFFDF4FF0D1 +:10A10000200A7461BBF1080F80F06181DFE80BF079 +:10A110000496A0A099FEFDFCC4F88051F580C4F817 +:10A12000845194F8410138B9FCF71EF8D4F84C1169 +:10A13000FCF712FD00281BDCB4F83E11B4F87000E7 +:10A14000814206D1B4F8F410081AA4F8F6002046AB +:10A1500005E0081AA4F8F600B4F83E112046A4F869 +:10A160007010D4F86811C4F84C11C0F870111DE0DB +:10A17000B4F83C11B4F87000081AA4F8F600B4F86A +:10A180003C112046A4F87010D4F84C11C4F86811A2 +:10A19000C4F87011D4F85411C4F80011D4F858114F +:10A1A000C4F87411B4F85C11A4F8781102F008F93D +:10A1B000FBF7B4FF804694F86500FDF715FF4AF2FF +:10A1C000B12108444FF47A71B0FBF1F0D4F83411A6 +:10A1D00040F27122084461885143C0EB4100A0F174 +:10A1E000300AB8F1B70F98BF4FF0B70821460120E9 +:10A1F00004F0CFFA4044AAEB0000A0F21D38A246BA +:10A200002146012004F0C5FADAF824109C3081427E +:10A2100088BF0D1AC6F80C80454528BF4546B56075 +:10A22000D4F86C01A0F5D4703061FCF762FC84F8BE +:10A23000407186F8029003B0BDE8F08F02F0A6F9F5 +:10A2400001E0FEF7D8FC84F8407103B0BDE8F08F60 +:10A25000FBF78AFFD4F8702101461046FCF77CFC1E +:10A2600048B1628840F27123D4F834115A43C1EBEB +:10A270004201B0FBF1F094F87D100D290FD0B4F835 +:10A280007010B4F83E210B189A42AEBF501C401C0F +:10A290000844A4F83E0194F8420178B905E0B4F806 +:10A2A0003E01401CA4F83E0108E0B4F83E01B4F8B9 +:10A2B000F410884204BF401CA4F83E01B4F87A01AF +:10A2C0000DF1040B401CA4F87A01B4F89A00B4F81C +:10A2D0009810401AB4F87010401E08441FFA80F914 +:10A2E0000BE000231A462046CDF800B0FFF709FA2C +:10A2F00068B3012818BFFFDF48D0B4F83E11A9EBBE +:10A30000010000B2002802E053E047E05FE0E8DA35 +:10A31000082084F88D0084F88C70204601F012FE2D +:10A3200084F82C5194F87C514FF6FF77202D00D300 +:10A33000FFDF28F8157094F87C01FBF7F6FE84F82F +:10A340007CA1707903B0BDE8F04FE8F7DDBDA06EE9 +:10A35000002804BF03B0BDE8F08FB4F83E01B4F8A4 +:10A360009420801A01B20029DCBF03B0BDE8F08F51 +:10A37000B4F86C000144491E91FBF0F189B201FB75 +:10A380000020A4F8940003B0BDE8F08FB4F83E01BB +:10A39000BDF804100844A4F83E01AEE7FEF7E4FA65 +:10A3A000FEF729FC4FF0E020C0F8809203B0BDE832 +:10A3B000F08F94F82C01042818BFFFDF84F82C518B +:10A3C00094F87C514FF6FF77202DB2D3B0E7FFDF32 +:10A3D00003B0BDE8F08F10B5FA4C207850B10120E1 +:10A3E0006072F5F7D8FB2078032805D0207A002882 +:10A3F00008BF10BD0C2010BD207BFCF7FCF9207BB2 +:10A40000FCF765FC207BFBF790FE002808BFFFDF10 +:10A410000020207010BD2DE9F04FEA4F83B038784E +:10A4200001244FF0000840B17C720120F5F7B3FB26 +:10A430003878032818BF387A0DD0DFF88C9389F864 +:10A44000034069460720F9F7BAFC002818BFFFDF70 +:10A450004FF6FF7440E0387BFCF7CDF9387BFCF712 +:10A4600036FC387BFBF761FE002808BFFFDF87F86A +:10A470000080E2E7029800281CBF90F82C11002908 +:10A480002AD00088A0421CBFDFF834A34FF0200B75 +:10A490003AD00721F9F70AFD040008BFFFDF94F85E +:10A4A0007C01FCF714FC84F82C8194F87C514FF665 +:10A4B000FF76202D28BFFFDF2AF8156094F87C0175 +:10A4C000FBF733FE84F87CB169460720F9F777FC87 +:10A4D000002818BFFFDF12E06846F9F74EFC00289D +:10A4E000C8D011E0029800281CBF90F82C11002958 +:10A4F00005D00088A0F57F41FF39CAD104E0684645 +:10A50000F9F73BFC0028EDD089F8038087F830800C +:10A5100087F80B8003B00020BDE8F08FAA4948718E +:10A520000020887001220A7048700A71C870A5491D +:10A53000087070E7A449087070472DE9F84FA14CE6 +:10A5400006460F462078002862D1A048FBF792FD0E +:10A55000207320285CD04FF00308666084F80080E8 +:10A56000002565722572AEB1012106F58E70FCF7EB +:10A57000BEFF0620F9F742FC81460720F9F73EFCB2 +:10A5800096F81C114844B1FBF0F200FB1210401C7D +:10A5900086F81C01FBF7C2FD40F2F651884238BF35 +:10A5A00040F2F65000F5A0701FFA80F9F4F7A2FA15 +:10A5B000012680B3A672F4F717F9E061FBF7D4FD2A +:10A5C000824601216846FCF769FF9DF8000042F2CF +:10A5D00010710002B0FBF1F201FB120000EB090167 +:10A5E0005046FBF7A8FAA762A061267584F815808B +:10A5F0002574207B04F11001FBF7E1FF002808BF60 +:10A60000FFDF25840020F5F7C6FA0020BDE8F88FAB +:10A610000C20BDE8F88FFFE7E761FBF7A5FD494691 +:10A62000FBF789FAA061A57284F830600120FDF77C +:10A6300054FD4FF47A7100F2E140B0FBF1F0381AAA +:10A64000A0F5AB60A5626063CFE75F4948707047D3 +:10A650005D49087170475B4810B5417A00291CBFFD +:10A66000002010BD816A51B990F8301039B1416AAB +:10A67000406B814203D9F5F768FA002010BD012034 +:10A6800010BD2DE9F041504C0646E088401CE080AA +:10A69000D4E902516078D6F8807120B13A46284654 +:10A6A000F6F705FD0546A068854205D02169281A00 +:10A6B00008442061FCF71DFAA560AF4209D896F85E +:10A6C0002C01012805D0E078002804BF0120BDE856 +:10A6D000F0810020BDE8F08110B504460846FDF782 +:10A6E00083FC4AF2B12108444FF47A71B0FBF1F0D7 +:10A6F00040F2E241614300F54E7081428CBF081A7E +:10A70000002010BD70B5044682B0002084F84001DE +:10A7100094F8FB00002807BF94F82C01032802B02E +:10A7200070BDFBF721FDD4F8702101461046FCF7FF +:10A7300013FA0028DCBF02B070BD628840F27123BA +:10A74000D4F834115A43C1EB4201B0FBF1F0B4F834 +:10A750007010401C0844A4F83C01B4F8F400B4F8AC +:10A760003C21801A00B20028DCBF02B070BD01207D +:10A7700084F84201B4F89A00B4F8982001AE801A27 +:10A78000401E084485B212E00096B4F83C11002344 +:10A7900001222046FEF7B5FF002804BF02B070BDBD +:10A7A000012815D0022812BFFFDF02B070BDB4F837 +:10A7B0003C01281A00B20028BCBF02B070BDE3E71C +:10A7C000F00C0020B80C0020E00000204F9F01009A +:10A7D000B4F83C01BDF804100844A4F83C01E6E7D5 +:10A7E000F8B50422002506295BD2DFE801F0072630 +:10A7F0000319192A044680F82C2107E00446C948A9 +:10A80000C078002818BF84F82C210AD0FBF7B7FBCA +:10A81000A4F87A51B4F87000A4F83E0184F84251CB +:10A82000F8BD0095B4F8F410012300222046FEF78D +:10A8300068FF002818BFFFDFE8E7032180F82C112C +:10A84000F8BD0646876AB0F83401314685B201206A +:10A8500003F09FFF044696F86500FDF7C5FB4AF23A +:10A86000B12108444FF47A71B0FBF1F0718840F2E5 +:10A8700071225143C0EB4100401BA0F55A7501F015 +:10A880008AFE002818BF1E3DA74234BF2046384626 +:10A89000A84228BF2C4602D2A74228BF3C46746279 +:10A8A000F8BDFFDFF8BD2DE9F05F9E4EB1780229BB +:10A8B00006BFF1880029BDE8F09F7469C4F88401DF +:10A8C00094F86500FDF718FCD4F88411081AB168F3 +:10A8D0000144B160F1680844F060746994F8430180 +:10A8E000002808BFBDE8F09F94F82C01032818BF8A +:10A8F000BDE8F09F94F8655037780C2F28BFFFDF34 +:10A90000894E36F8178094F888710C2F28BFFFDF26 +:10A9100036F81700404494F8888187B2B8F10C0FDC +:10A9200028BFFFDF36F8180000F5C8601FFA80F86E +:10A930002846FDF7E1FBD4F884114FF0000A0E1A07 +:10A9400015F00C0F0ABF28464EF62830FDF74CFBD9 +:10A950004FF47A7900F2E730B0FBF9F0361A284666 +:10A96000FDF7CAFBD4F8001115F00C0FA1EB000B9A +:10A970000ABF28464EF62830FDF736FB4AF2B121D1 +:10A980000844B0FBF9F0ABEB0000A0F160017943A3 +:10A99000B1FBF8F1292202EB50006031A0EB51022B +:10A9A00000EB5100B24201D8B04201D8F1F774FB7C +:10A9B000608840F2E2414843394600F047F8C4F865 +:10A9C000340184F843A1BDE8F09F70B505465548B1 +:10A9D00090F802C0BCF1020F07BF406900F5C074D7 +:10A9E000524800F12404002904BF256070BD4FF4D3 +:10A9F0007A7601290DD002291CBFFFDF70BD1046F9 +:10AA0000FEF76EFC00F2E140B0FBF6F0281A206081 +:10AA100070BD1846FDF761FB00F2E140B0FBF6F0B7 +:10AA2000281A206070BD4148007800281CBF002013 +:10AA3000704710B50720F9F7D3F980F0010010BD79 +:10AA40003A480078002818BF0120704730B5024608 +:10AA50000020002908BF30BDA2FB0110490A41EACD +:10AA6000C051400A4C1C40F100000022D4F1FF31DB +:10AA700040F2A17572EB000038BFFFDF04F5F4600F +:10AA8000B0FBF5F030BD2DE9F843284C0025814698 +:10AA900084F83050D4F8188084F82C10E5722570B2 +:10AAA0000127277239466068F5F792FD6168C1F8A1 +:10AAB0007081267B81F87C61C1F88091C1F8748136 +:10AAC000B1F80080202E28BFFFDF194820F816803B +:10AAD000646884F82C510023A4F878511A4619466A +:10AAE00020460095FEF70DFE002818BFFFDFC4F8D2 +:10AAF0002851C4F8205184F82C71A4F83E51A4F8D0 +:10AB00003C5184F84251B4F87000401EA4F8700023 +:10AB1000A4F87A51FBF733FA02484079BDE8F843CC +:10AB2000E8F7F2B9E0000020E4620200B80C00206F +:10AB3000F00C0020012804D0022805D0032808D1F9 +:10AB400005E0012907D004E0022904D001E004292E +:10AB500001D000207047012070472DE9F0410E46DA +:10AB6000044603F08AFC0546204603F08AFC0446AE +:10AB7000F6F770FBFB4F010015D0386990F86420A0 +:10AB80008A4210D090F8C0311BB190F8C2312342F4 +:10AB90001FD02EB990F85D30234201D18A4218D8D7 +:10ABA00090F8C001A8B12846F6F754FB70B1396996 +:10ABB00091F86520824209D091F8C00118B191F84E +:10ABC000C301284205D091F8C00110B10120BDE8B1 +:10ABD000F0810020FBE730B5E24C85B0E069002849 +:10ABE0005FD0142200216846F3F751FC206990F8E9 +:10ABF0006500FDF7F9F94FF47A7100F5FA70B0FBD2 +:10AC0000F1F5206990F86500FDF776FA2844ADF873 +:10AC1000060020690188ADF80010B0F87010ADF89A +:10AC200004104188ADF8021090F8A20130B1A0697B +:10AC3000C11C039103F002FB8DF81000206990F80D +:10AC4000A1018DF80800E169684688472069002164 +:10AC500080F8A21180F8A1110399002921D090F861 +:10AC6000A01100291DD190F87C10272919D09DF83A +:10AC70001010039A002914D013780124FF2B12D04E +:10AC8000072B0ED102290CD15178FF2909D100BF21 +:10AC900080F8A0410399C0F8A4119DF8101080F825 +:10ACA000A31105B030BD1B29F2D9FAE770B5AD4C40 +:10ACB000206990F87D001B2800D0FFDF2069002567 +:10ACC00080F8A75090F8D40100B1FFDF206990F818 +:10ACD000A81041B180F8A8500188A0F8D81180F8D8 +:10ACE000D6510E2108E00188A0F8D81180F8D6517D +:10ACF000012180F8DA110D2180F8D4110088F9F7CC +:10AD000006FAF8F79FFE2079E8F7FEF8206980F848 +:10AD10007D5070BD70B5934CA07980072CD5A0787C +:10AD2000002829D162692046D37801690D2B01F1F1 +:10AD300070005FD00DDCA3F102034FF001050B2B77 +:10AD400019D2DFE803F01A1844506127182C183A7A +:10AD50006400152B6FD008DC112B4BD0122B5AD06E +:10AD6000132B62D0142B06D166E0162B71D0172B53 +:10AD700070D0FF2B6FD0FFDF70BD91F87F200123D3 +:10AD80001946F6F7FFF80028F6D12169082081F866 +:10AD90007F0070BD1079BDE8704001F090BC91F863 +:10ADA0007E00C00700D1FFDF01F048FC206910F8E9 +:10ADB0007E1F21F00101017070BD91F87D00102807 +:10ADC00000D0FFDF2069112180F8A75008E091F83A +:10ADD0007D00142800D0FFDF2069152180F8A750DE +:10ADE00080F87D1070BD91F87D00152800D0FFDF40 +:10ADF000172005E091F87D00152800D0FFDF19200D +:10AE0000216981F87D0070BDBDE870404EE7BDE866 +:10AE1000704001F028BC91F87C2001230021F6F756 +:10AE2000B1F800B9FFDF0E200FE011F87E0F20F01F +:10AE3000040008701DE00FE091F87C200123002140 +:10AE4000F6F7A0F800B9FFDF1C20216981F87C002B +:10AE500070BD12E01BE022E091F87E00C0F301100B +:10AE6000012800D0FFDF206910F87E1F21F01001BB +:10AE70000170BDE8704001F0E1BB91F87C20012336 +:10AE80000021F6F77FF800B9FFDF1F20DDE791F81A +:10AE90007D00212801D000B1FFDF2220B0E7BDE80E +:10AEA000704001F0D7BB2F48016991F87E2013074D +:10AEB00002D501218170704742F0080281F87E209E +:10AEC0008069C07881F8E10001F0AFBB10B5254C76 +:10AED00021690A88A1F8162281F8140291F8640009 +:10AEE00001F091FB216981F8180291F8650001F0E9 +:10AEF0008AFB216981F81902012081F812020020E1 +:10AF000081F8C0012079BDE81040E7F7FDBF10B51A +:10AF1000144C05212069FFF763FC206990F85A1052 +:10AF2000012908D000F5F57103F001FC2079BDE896 +:10AF30001040E7F7E9BF022180F85A1010BD10B5A4 +:10AF4000084C01230921206990F87C207030F6F725 +:10AF500019F848B12169002001F8960F087301F82B +:10AF60001A0C10BD000100200120A070F9E770B597 +:10AF7000F74D012329462869896990F87C200979D1 +:10AF80000E2A01D1122903D000241C2A03D004E088 +:10AF9000BDE87040D3E7142902D0202A07D008E08A +:10AFA00080F87C4080F8A240BDE87040AFE71629E9 +:10AFB00006D0262A01D1162902D0172909D00CE083 +:10AFC00000F87C4F80F82640407821280CD01A20C9 +:10AFD00017E090F87D20222A07D0EA69002A03D0E2 +:10AFE000FF2901D180F8A23132E780F87D4001F0DD +:10AFF00025FB286980F8974090F8C0010028F3D01D +:10B000000020BDE8704061E710B5D14C216991F88E +:10B010007C10202902D0262902D0A2E7FFF756FF94 +:10B020002169002081F87C0081F8A20099E72DE9D0 +:10B03000F843C74C206990F87C10202908D00027DD +:10B0400090F87D10222905D07FB300F17C0503E044 +:10B050000127F5E700F17D0510F8B01F41F004016C +:10B060000170A06903F015FA4FF00108002608B33B +:10B070003946A069FFF771FDE0B16A46A169206910 +:10B08000F6F7F7F890B3A06903F001FA2169A1F887 +:10B09000AA01B1F8701001F0AAFA40B32069282182 +:10B0A00080F88D1080F88C8058E0FFE70220A070B7 +:10B0B000BDE8F883206990F8C00110B11E20FFF7A9 +:10B0C00005FFAFB1A0692169C07881F8E20008FAF4 +:10B0D00000F1C1F3006000B9FFDF20690A2180F8A8 +:10B0E0007C1090F8A20040B9FFDF06E009E02AE0FA +:10B0F0002E7001F0A3FAFFF7D6FE206980F8976062 +:10B10000D6E7226992F8C00170B1B2F8703092F8B7 +:10B110006410B2F8C40102F5D572F6F79BF968B174 +:10B120002169252081F87C00206900F17D0180F8EB +:10B1300097608D4212D180F87D600FE00020FFF70C +:10B14000C5FE2E70F0E720699DF8001080F8AC1164 +:10B150009DF8011080F8AD1124202870206900F1BD +:10B160007D018D4203D1BDE8F84301F067BA80F854 +:10B17000A2609DE770B5764C01230B21206990F801 +:10B180007D207030F5F7FEFE202650BB206901239C +:10B19000002190F87D207030F5F7F4FE0125F0B124 +:10B1A000206990F87C0024281BD0A06903F04FF997 +:10B1B000C8B1206990F8B01041F0040180F8B010D7 +:10B1C000A1694A7902F0070280F85D20097901F04F +:10B1D000070180F85C1090F8C1311BBB06E0A57038 +:10B1E00036E6A67034E6BDE870405CE690F8C03103 +:10B1F000C3B900F164035E788E4205D1197891429B +:10B2000002D180F897500DE000F503710D700288AF +:10B210004A8090F85C200A7190F85D0048712079AE +:10B22000E7F772FE2169212081F87D00BDE87040BA +:10B2300001F0FBB9F8B5464C206990F87E0010F09B +:10B24000300F04D0A07840F00100A070F8BDA069D4 +:10B2500003F0E2F850B3A06903F0D8F80746A069FC +:10B2600003F0D8F80646A06903F0CEF80546A069B9 +:10B2700003F0CEF801460097206933462A46303065 +:10B2800003F0BFF9A079800703D56069C07814285E +:10B290000FD0216991F87C001C280AD091F85A003F +:10B2A00001280ED091F8B70158B907E0BDE8F84081 +:10B2B000F9E52169012081F85A0002E091F8B60110 +:10B2C00030B1206910F87E1F41F0100101700EE0CE +:10B2D00091F87E0001F5FC7240F0200081F87E00BC +:10B2E00031F8300B03F017FA2079E7F70DFEBDE8CF +:10B2F000F84001F09AB970B5154C206990F87E10AD +:10B30000890707D590F87C20012308217030F5F7D4 +:10B3100039FEF8B1206990F8AA00800712D4A0691C +:10B3200003F056F8216981F8AB00A06930F8052FC9 +:10B33000A1F8AC204088A1F8AE0011F8AA0F40F0A7 +:10B3400002000870206990F8AA10C90705D011E022 +:10B35000000100200120A0707AE590F87E008007AF +:10B3600000D5FFDF206910F87E1F41F00201017057 +:10B3700001F05BF92069002590F87C10062906D1C0 +:10B3800080F87C5080F8A2502079E7F7BDFD206955 +:10B3900090F8A8110429DFD180F8A8512079E7F7A7 +:10B3A000B3FD206990F87C100029D5D180F8A25017 +:10B3B0004EE570B5FB4C01230021206990F87D20FB +:10B3C0007030F5F7DFFD012578B9206990F87D2010 +:10B3D000122A0AD0012305217030F5F7D3FD10B1F0 +:10B3E0000820A07034E5A57032E5206990F8A80027 +:10B3F00008B901F01AF92169A06901F5847102F018 +:10B40000C8FF2169A069D83102F0CEFF206990F809 +:10B41000DC0100B1FFDF21690888A1F8DE0101F538 +:10B42000F071A06902F0A3FF2169A06901F5F47130 +:10B4300002F0A5FF206980F8DC51142180F87D100E +:10B440002079BDE87040E7F75FBD70B5D54C0123AA +:10B450000021206990F87D207030F5F793FD0125DB +:10B46000A8B1A06902F04FFF98B1A0692169B0F8B6 +:10B470000D00A1F8AA01B1F8701001F0B8F858B1A8 +:10B480002069282180F88D1080F88C50E0E4A570A8 +:10B49000DEE4BDE8704006E5A0692169027981F823 +:10B4A000AC21B0F80520A1F8AE2102F01FFF216900 +:10B4B000A1F8B001A06902F01CFF2169A1F8B20156 +:10B4C000A06902F01DFF2169A1F8B4010D2081F8E7 +:10B4D0007D00BDE47CB5B34CA079C00738D0A0692D +:10B4E00001230521C578206990F87D207030F5F79B +:10B4F00049FD68B1AD1E0A2D06D2DFE805F0090945 +:10B500000505090905050909A07840F00800A070A3 +:10B51000A07800281CD1A06902F0BEFE00286ED0E1 +:10B52000A0690226C5781DB1012D01D0162D18D1B4 +:10B53000206990F87C00F5F70DFD90B1216991F834 +:10B540007C001F280DD0202803D0162D16D0A67001 +:10B550007CBD262081F87C00162D02D02A20FFF722 +:10B56000B5FC0C2D5BD00CDC0C2D48D2DFE805F0CF +:10B5700036331F48BEBE4BB55ABE393C2020A070A2 +:10B580007CBD0120142D6ED008DC0D2D6CD0112D4A +:10B590006BD0122D6ED0132D31D168E0152D7FD0D8 +:10B5A000162D6FD0182D6ED0FF2D28D198E0206970 +:10B5B0000123194690F87F207030F5F7E3FC00284E +:10B5C00008D1A06902F0CCFE216981F88E01072024 +:10B5D00081F87F008CE001F0EDF889E0FFF735FF9E +:10B5E00086E001F0C7F883E0206990F87D1011290A +:10B5F00001D0A6707CE0122180F87D1078E075E023 +:10B60000FFF7D7FE74E0206990F87D001728F0D18D +:10B6100001F014F821691B2081F87D0068E0FFF734 +:10B620006AFE65E0206990F87E00C00703D0A0782C +:10B6300040F0010023E06946A06902F0D0FE9DF8C9 +:10B64000000000F02501206900F8B01F9DF80110EE +:10B6500001F04901417000F0E8FF206910F87E1FF9 +:10B6600041F0010117E018E023E025E002E0FFF7D8 +:10B6700066FC3DE0216991F87E10490704D5A07071 +:10B6800036E00DE00FE011E000F0CFFF206910F888 +:10B690007E1F41F0040101702AE0FFF7CBFD27E097 +:10B6A00001F030F824E0FFF765FD21E0FFF7BFFC73 +:10B6B0001EE0A06900790DE0206910F8B01F41F08C +:10B6C00004010170A06902F0F7FE162810D1A069EC +:10B6D00002F0F6FEFFF798FC0AE0FFF748FC07E0EF +:10B6E000E16919B1216981F8A20101E0FFF7DBFBF3 +:10B6F0002169F1E93002401C42F10002C1E9000277 +:10B700007CBD70B5274CA07900074AD5A0780028E9 +:10B7100047D1206990F8E400FE2800D1FFDF2069BE +:10B72000FE21002580F8E41090F87D10192906D13B +:10B7300080F8A75000F082FF206980F87D502069D2 +:10B7400090F87C101F2902D0272921D119E090F808 +:10B750007D00F5F7FFFB78B120692621012380F8F1 +:10B760007C1090F87D200B217030F5F70BFC78B938 +:10B770002A20FFF7ABFB0BE02169202081F87C0039 +:10B7800006E0012180F8A11180F87C5080F8A250D9 +:10B79000206990F87F10082903D10221217080F8D8 +:10B7A000E41021E40001002010B5FD4C216991F85E +:10B7B000AC210AB991F8642081F8642091F8AD2198 +:10B7C0000AB991F8652081F8652010B10020FFF7D3 +:10B7D0007DFB206902F041FF002806D02069BDE80A +:10B7E000104000F5F57102F0A2BF16E470B5EC4C04 +:10B7F00006460D46206990F8E400FE2800D0FFDFE1 +:10B800002269002082F8E46015B1A2F8A400E7E400 +:10B8100022F89E0F01201071E2E470B5E04C012384 +:10B820000021206990F87C207030F5F7ABFB0028F0 +:10B830007BD0206990F8B61111B190F8B71139B1E9 +:10B8400090F8C01100296FD090F8C11119B36BE0C6 +:10B8500090F87D1024291CD090F87C10242918D051 +:10B860005FF0000300F5D67200F5DB7102F096FE82 +:10B870002169002081F8B60101461420FFF7B6FFC8 +:10B88000216901F13000C28A21F8E62F408B4880FF +:10B8900050E00123E6E790F87D2001230B21703072 +:10B8A000F5F770FB68BB206990F8640000F0ABFE10 +:10B8B0000646206990F8650000F0A5FE054620695F +:10B8C00090F8C2113046FFF735F9D8B1206990F8E9 +:10B8D000C3112846FFF72EF9A0B12269B2F87030E3 +:10B8E00092F86410B2F8C40102F5D572F5F7B2FD12 +:10B8F00020B12169252081F87C001BE00020FFF7A2 +:10B90000E5FA11E020690123032190F87D207030D1 +:10B91000F5F738FB40B920690123022190F87D201A +:10B920007030F5F72FFB08B1002059E400211620F4 +:10B93000FFF75CFF012053E410B5E8BB984C206989 +:10B9400090F87E10CA0702D00121092052E08A0730 +:10B950000AD501210C20FFF749FF206910F8AA1F22 +:10B9600041F00101017047E04A0702D5012113208F +:10B9700040E00A0705D510F8E11F417101210720B9 +:10B9800038E011F0300F3BD090F8B711A1B990F822 +:10B99000B611E1B190F87D1024292FD090F87C10D9 +:10B9A00024292BD05FF0000300F5D67200F5DB717F +:10B9B00002F0F4FD216900E022E011F87E0F20F092 +:10B9C000200040F010000870002081F83801206944 +:10B9D00090F87E10C90613D502F03FFEFFF797FAE4 +:10B9E000216901F13000C28A21F8E62F408B48809E +:10B9F00001211520FFF7FAFE0120F6E60123D3E727 +:10BA00000020F2E670B5664C206990F8E410FE293B +:10BA100078D1A178002975D190F87F2001231946AB +:10BA20007030F5F7AFFA00286CD1206990F88C11CE +:10BA300049B10021A0F89C1090F88D1180F8E61013 +:10BA4000002102205BE090F87D200123042170306A +:10BA5000F5F798FA0546FFF76FFF002852D1284600 +:10BA600000F00CFF00284DD120690123002190F83F +:10BA70007C207030F5F786FA78B120690123042123 +:10BA800090F87D207030F5F77DFA30B9206990F894 +:10BA9000960010B10021122031E0206990F87C203E +:10BAA0000A2A0DD0002D2DD1012300217030F5F789 +:10BAB00069FA78B1206990F8A81104290AD105E043 +:10BAC00010F8E21F01710021072018E090F8AA0089 +:10BAD000800718D0FFF7A1FE002813D120690123A9 +:10BAE000002190F87C207030F5F74CFA002809D03E +:10BAF000206990F8A001002804D00021FF20BDE8B3 +:10BB0000704073E609E000210C20FFF76FFE20690A +:10BB100010F8AA1F41F0010101701DE43EB5054671 +:10BB20006846FDF7ABFC00B9FFDF22220021009838 +:10BB3000F2F7ADFC0321009802F096FB0098017823 +:10BB400021F010010170294602F0B3FB144C0D2DB9 +:10BB500043D00BDCA5F102050B2D19D2DFE805F06F +:10BB600022184B191922185718192700152D5FD0C4 +:10BB700008DC112D28D0122D0BD0132D09D0142D37 +:10BB800006D155E0162D2CD0172D6AD0FF2D74D07C +:10BB9000FFDFFDF786FC002800D1FFDF3EBD00007F +:10BBA000000100202169009891F8E61017E0E26892 +:10BBB00000981178017191884171090A8171518849 +:10BBC000C171090A0172E4E70321009802F072FCD6 +:10BBD0000621009802F072FCDBE700980621017153 +:10BBE000D7E70098D4F8101091F8C221027191F8AB +:10BBF000C3114171CDE72169009801F5887102F008 +:10BC0000D7FB21690098DC3102F0DCFBC1E7FA497F +:10BC1000D1E90001CDE90101206901A990F8B00046 +:10BC200000F025008DF80400009802F006FCB0E753 +:10BC30002069B0F84810009802F0D6FB2069B0F8EF +:10BC4000E810009802F0D4FB2069B0F84410009886 +:10BC500002F0D2FB2069B0F8E610009802F0D0FBA9 +:10BC600097E7216991F8C00100280098BCD111F82C +:10BC7000642F02714978BCE7FFE7206990F8A3219F +:10BC8000D0F8A411009802F022FB82E7DB4810B53F +:10BC9000006990F8821041B990F87D2001230621B7 +:10BCA0007030F5F76FF9002800D001209DE570B5E0 +:10BCB000D24D286990F8801039B1012905D00229A8 +:10BCC00006D0032904D0FFDF03E4B0F8F41037E016 +:10BCD00090F87F10082936D0B0F89810B0F89A2064 +:10BCE00000248B1C9A4206D3511A891E0C04240C82 +:10BCF00001D0641EA4B290F8961039B190F87C205F +:10BD0000012309217030F5F73DF940B3FFF7BEFF7D +:10BD100078B129690020B1F89020B1F88E108B1C01 +:10BD20009A4203D3501A801E00D0401EA04200D277 +:10BD300084B20CB1641EA4B22869B0F8F410214496 +:10BD4000A0F8F0102DE5B0F898100329BDD330F815 +:10BD5000701F428D1144491CA0F8801021E5002479 +:10BD6000EAE770B50C4605464FF4087200212046FC +:10BD7000F2F78DFB258014E5F8F7A2B92DE9F04123 +:10BD80000D4607460721F8F791F8041E3CD094F8B9 +:10BD9000C8010026A8B16E70092028700BE0268427 +:10BDA00084F8C861D4F8CA016860D4F8CE01A860EC +:10BDB000B4F8D201A88194F8C8010028EFD12E71FF +:10BDC000AEE094F8D40190B394F8D4010D2813D0C8 +:10BDD0000E2801D0FFDFA3E02088F8F798F9074686 +:10BDE000F7F745FE78B96E700E20287094F8D601EA +:10BDF00028712088E88014E02088F8F788F9074641 +:10BE0000F7F735FE10B10020BDE8F0816E700D200F +:10BE1000287094F8D60128712088E88094F8DA0117 +:10BE2000287284F8D4613846F7F71BFE78E0FFE704 +:10BE300094F80A0230B16E701020287084F80A62FB +:10BE4000AF806DE094F8DC0190B16E700A2028702C +:10BE50002088A880D4F8E011C5F80610D4F8E411C1 +:10BE6000C5F80A10B4F8E801E88184F8DC6157E00D +:10BE700094F8040270B16E701A20287005E000BFBB +:10BE800084F80462D4F80602686094F8040200287A +:10BE9000F6D145E094F8EA0188B16E70152028705B +:10BEA00008E000BF84F8EA6104F5F6702B1D07C8AE +:10BEB00083E8070094F8EA010028F3D130E094F811 +:10BEC000F80170B16E701C20287084F8F861D4F805 +:10BED000FA016860D4F8FE01A860B4F80202A881F3 +:10BEE0001EE094F80C0238B11D20287084F80C6212 +:10BEF000D4F80E02686013E094F81202002883D090 +:10BF00006E701620287007E084F81262D4F81402CC +:10BF10006860B4F81802288194F812020028F3D15E +:10BF2000012071E735480021C16101620846704770 +:10BF300030B5324D0C46E860FFF7F4FF00B1FFDF8B +:10BF40002C7130BD002180F87C1080F87D1080F8C5 +:10BF5000801090F8FB1009B1022100E00321FEF7E8 +:10BF60003FBC2DE9F041254C0546206909B100216F +:10BF700004E0B0F80611B0F8F6201144A0F806115C +:10BF800090F88C1139B990F87F2001231946703050 +:10BF9000F4F7F8FF30B1206930F89C1FB0F85A2050 +:10BFA00011440180206990F8A23033B1B0F89E109E +:10BFB000B0F8F6201144A0F89E1090F9A670002F5A +:10BFC00006DDB0F8A410B0F8F6201144A0F8A410D3 +:10BFD00001213D2615B180F88D6017E02278022AF4 +:10BFE0000ED0012A15D0A2784AB380F88C1012F036 +:10BFF000140F11D01E2117E0FC6202000001002086 +:10C0000090F8E620062A3CD016223AE080F88C1000 +:10C0100044E090F88E2134E0110702D580F88D605D +:10C020003CE0910603D5232180F88D1036E090077F +:10C0300000D1FFDF21692A2081F88D002AE02BB191 +:10C04000B0F89E20B0F8A0309A4210D2002F05DD43 +:10C05000B0F8A420B0F8A0309A4208D2B0F89C30D2 +:10C06000B0F89A20934204D390F88C310BB122227D +:10C0700007E090F880303BB1B0F89830934209D394 +:10C08000082280F88D20C1E7B0F89820062A01D355 +:10C090003E22F6E7206990F88C1019B12069BDE8BE +:10C0A000F0414FE7BDE8F0410021FEF799BB2DE9D3 +:10C0B000F047FF4C81460D4620690088F8F739F8B3 +:10C0C000060000D1FFDFA0782843A070A0794FF0D0 +:10C0D00000058006206904D5A0F8985080F8045126 +:10C0E00003E030F8981F491C0180FFF7CFFD4FF0A7 +:10C0F000010830B3E088000506D5206990F8821069 +:10C1000011B1A0F88E501CE02069B0F88E10491CC7 +:10C1100089B2A0F88E10B0F890208A4201D3531A49 +:10C1200000E0002327897F1DBB4201D880F896805C +:10C13000914206D3A0F88E5080F80A822079E6F763 +:10C14000E3FEA0794FF0020710F0600F0ED02069D7 +:10C1500090F8801011B1032908D102E080F88080A6 +:10C1600001E080F880700121FEF73AFB206990F829 +:10C170008010012904D1E188C90501D580F88070BB +:10C18000B9F1000F72D1E188890502D5A0F81851E4 +:10C1900004E0B0F81811491CA0F8181100F035FBA4 +:10C1A000FEF719FDFFF72EFC2769B7F8F800401CD1 +:10C1B000A7F8F80097F8FC0028B100F01BFFA8B121 +:10C1C000A7F8F85012E000F012FF08B1A7F8F850F5 +:10C1D00000F015FF50B197F80401401CC0B287F879 +:10C1E0000401022802D927F8F85F3D732069012372 +:10C1F000002190F87D207030F4F7C4FE20B920694A +:10C2000090F87D000C2859D120690123002190F875 +:10C210007C207030F4F7B6FE48B32069012300217A +:10C2200090F87F207030F4F7ADFE00B3206990F8ED +:10C230008010022942D190F80401C0B93046F7F7C6 +:10C24000E6F9A0B1216991F8E400FE2836D1B1F8F1 +:10C25000F200012832D981F8FA80B1F89A00B1F8D9 +:10C260009820831E9A4203DB012004E032E025E09F +:10C27000801A401E80B2B1F8F82023899A4201D377 +:10C28000012202E09A1A521C92B2904200D9104642 +:10C29000012801D181F8FA5091F86F2092B98A6E85 +:10C2A00082B1B1F89420B1F87010511A09B2002986 +:10C2B00008DD884200DB084680B203E021690120E6 +:10C2C00081F8FA502169B1F870201044A1F8F40007 +:10C2D000FFF7EDFCE088C0F340214846FFF741FE40 +:10C2E000206980F8FB50BDE8F047FDF7FCB87049C5 +:10C2F00002468878CB78184312D10846006942B1CB +:10C300008979090703D590F87F00082808D0012013 +:10C310007047B0F84C10028E914201D8FEF7B1B9C7 +:10C320000020704770B5624C05460E46E0882843F1 +:10C33000E080A80703D5E80700D0FFDF6661EA07C1 +:10C340004FF000014FF001001AD0A661F278062AE2 +:10C3500002D00B2A14D10AE0226992F87D30172B03 +:10C360000ED10023E2E92E3302F8370C08E02269EF +:10C3700092F87D30112B03D182F8811082F8A80049 +:10C38000AA0718D56269D278052A02D00B2A12D1E1 +:10C390000AE0216991F87D20152A0CD10022E1E9FB +:10C3A000302201F83E0C06E0206990F87D20102A2A +:10C3B00001D180F88210280601D50820E07083E4BE +:10C3C0002DE9F84301273A4C002567F30701E58082 +:10C3D000A570E570257020618946804680F8FB7065 +:10C3E0000088F7F7A6FE00B9FFDF20690088FDF797 +:10C3F00042F820690088FDF764F82069B0F8F2106F +:10C4000071B190F8E410FE290FD190F88C1189B128 +:10C4100090F87F20012319467030F4F7B3FD78B10E +:10C42000206990F8E400FE2804D0206990F8E40028 +:10C43000FFF774FB206990F8FD1089B1258118E0A1 +:10C440002069A0F89C5090F88D1180F8E61000212A +:10C450000220FFF7CBF9206980F8FA500220E7E7C5 +:10C4600090F8C81119B9018C8288914200D881884E +:10C47000218130F8F61F491E8EB230F8021F314478 +:10C4800020F86019018831440180FFF7FFFB20B1DB +:10C49000206930F88E1F314401802069B0F8F21015 +:10C4A000012902D8491CA0F8F2102EB102E00000C8 +:10C4B0000001002080F8045180F8FA5090F87D10B7 +:10C4C0000B2901D00C2916D1B0F87020B0F8AA3190 +:10C4D000D21A12B2002A0EDBD0F8AC11816090F8AB +:10C4E000B01101730321F4F773F8206980F87D50CF +:10C4F00080F8B27026E0242910D1B0F87010B0F89E +:10C50000AA21891A09B2002908DB90F8C001FFF7B7 +:10C510004BF9206900F87D5F857613E090F87C1078 +:10C52000242901D025290DD1B0F87010B0F8AA0146 +:10C53000081A00B2002805DB0120FFF735F9206951 +:10C5400080F87C5020690146B0F8F6207030F4F78E +:10C55000B2FAFC480090FC4BFC4A4146484600F0C9 +:10C560007DFC216A11B16078FCF7B5FA20690123DE +:10C57000052190F87D207030F4F704FD002803D0E9 +:10C58000BDE8F84300F0FDB9BDE8F88300F015BD43 +:10C59000EF49C8617047EE48C069002800D001200B +:10C5A0007047EB4A50701162704710B5044600881E +:10C5B000A4F8CC01B4F8B001A4F8CE01B4F8B201EB +:10C5C000A4F8D001B4F8B401A4F8D201012084F891 +:10C5D000C801DF480079E6F797FC02212046F3F70F +:10C5E000F7FF002004F87D0F0320E07010BD401A13 +:10C5F00000B247F6FE71884201DC002801DC012010 +:10C6000070470020704710B5012808D0022808D0D4 +:10C61000042808D0082806D0FFDF204610BD0124DA +:10C62000FBE70224F9E70324F7E7C9480021006982 +:10C6300020F8A41F8178491C81707047C44800B558 +:10C64000016911F8A60F401E40B20870002800DAF8 +:10C65000FFDF00BDBE482721006980F87C10002163 +:10C6600080F8A011704710B5B94C206990F8A81156 +:10C67000042916D190F87C20012300217030F4F7B2 +:10C6800081FC00B9FFDF206990F8AA10890703D464 +:10C69000062180F87C1004E0002180F8A21080F8C8 +:10C6A000A811206990F87E00800707D5FFF7C6FF24 +:10C6B000206910F87E1F21F00201017010BDA4490D +:10C6C00010B5096991F87C200A2A09D191F8E22075 +:10C6D000824205D1002081F87C0081F8A20010BDC3 +:10C6E00091F87E20130706D522F0080081F87E001D +:10C6F000BDE81040A2E7FF2801D0FFDF10BDBDE874 +:10C700001040A7E7F8B5924C01230A21206990F860 +:10C710007C207030F4F736FC38B3A06901F07CFE61 +:10C72000C8B1A06901F072FE0746A06901F072FE6F +:10C730000646A06901F068FE0546A06901F068FEA2 +:10C7400001460097206933462A46303001F059FFF0 +:10C75000206901F082FF2169002081F8A20081F8A0 +:10C760007C00BDE8F840FEF7D2BBA07840F00100A5 +:10C77000A070F8BD10B5764C01230021206990F817 +:10C780007D207030F4F7FEFB30B1FFF74EFF2169DA +:10C79000102081F87D0010BD20690123052190F84B +:10C7A0007D207030F4F7EEFB08B1082000E0012096 +:10C7B000A07010BD70B5664C01230021206990F86F +:10C7C0007D207030F4F7DEFB012588B1A06901F00F +:10C7D000C4FD2169A1F8AA01B1F87010FFF707FFA5 +:10C7E00040B12069282180F88D1080F88C50E6E552 +:10C7F000A570E4E52169A06901F5D67101F0A8FDF5 +:10C8000021690B2081F87D00D9E510B5FEF779FF8D +:10C81000FEF760FE4E4CA079400708D5A07830B9ED +:10C82000206990F87F00072801D101202070FEF7D1 +:10C8300071FAA079C00609D5A07838B9206990F8B6 +:10C840007D100B2902D10C2180F87D10E0780007C3 +:10C850000ED520690123052190F87D207030F4F772 +:10C8600091FB30B10820A0702169002081F8D4012B +:10C8700010BDBDE81040002000F0C4BB10B5344C22 +:10C88000216991F87D2048B3102A06D0142A07D0D8 +:10C89000152A1AD01B2A2CD11AE001210B2019E0ED +:10C8A000FAF702FE0C2817D32069082100F58870DA +:10C8B000FAF7FEFD28B120690421DC30FAF7F8FD13 +:10C8C00000B9FFDF0121042004E000F017F803E0C5 +:10C8D00001210620FEF78AFF012010BD212A08D180 +:10C8E00091F8970038B991F8C00110B191F8C101E1 +:10C8F00008B1002010BD01211720EBE770B5144CE2 +:10C900000025206990F88F1101290AD002292ED123 +:10C9100090F8A810F1B1062180F8E610012102205C +:10C9200020E090F8D411002921D100F1C80300F5CE +:10C930008471002200F5C870F4F796FA01210520F1 +:10C9400010E00000AFC00100EFC2010025C30100EC +:10C950000001002090F8B000400701D5112000E050 +:10C960000D200121FEF742FF206980F88F5126E556 +:10C9700030B5FB4C05462078002818BFFFDFE57175 +:10C9800030BDF7490120887170472DE9F14FF54D11 +:10C990002846446804F1700794F86510608F94F895 +:10C9A0008280268F082978D0F4F797FBB8F1000F22 +:10C9B00004BF001D80B2864238BF304600F0FF0839 +:10C9C000DFF89C93E848C9F8240009F134006E6848 +:10C9D000406800F1700A90F882B096F86510358FC3 +:10C9E000708F08295DD0F4F778FB00BFBBF1000F12 +:10C9F00004BF001D80B2854238BF2846C0B29AF8F5 +:10CA00001210002918BF04210844C0B296F865101E +:10CA1000FBF735FCB87C002847D007F15801D24815 +:10CA200091E80E1000F5027585E80E10B96EC0F899 +:10CA30002112F96EC0F8251200F58170FBF7DBFFBB +:10CA4000C848007800280CBF0120002080F00101B8 +:10CA5000C6480176D7E91412C0E90412A0F5837222 +:10CA6000D9F82410FBF7F5F994F86500012808BF00 +:10CA700000220CD0022808BF012208D0042808BFD9 +:10CA8000032204D008281ABFFFDF002202224146F9 +:10CA90000120FBF7F9F90EE0FFE70421F4F71DFB95 +:10CAA00084E70421F4F719FBA0E7D9F82400FBF789 +:10CAB000A2FFFBF715FA009850B994F8650094F8B6 +:10CAC000661010F00C0F08BF00219620FBF7B4FF92 +:10CAD00094F8642001210020FCF76BF894F82C00F6 +:10CAE000012808BFFCF735F8022089F80000FCF7A0 +:10CAF0003FFC002818BFFFDFBDE8F88F2DE9F04F9D +:10CB0000DFF860A28BB050469AF800204068AAF186 +:10CB10001401059190F8751000F1700504464FF06E +:10CB200008080127AAF13406A1B3012900F0068103 +:10CB3000022900F00781032918BFFFDF00F01881E8 +:10CB4000306A0423017821F008010170AA7908EA0B +:10CB5000C202114321F004010170EA7903EA820262 +:10CB6000114321F01001017095F80590F06AF6F775 +:10CB70005EFD8046FCF7C9FCB9F1020F00F00081B0 +:10CB8000B9F1010F00F00081B9F1030F00F000814D +:10CB900000F003B9FFE795F80CC04FF002094FF021 +:10CBA000000BBCF1240F1CBF6B7B242B08D0BCF105 +:10CBB0001F0F18BFBCF1200F2AD0222B4DD077E0D9 +:10CBC00094F864109AB190F8AC01002874D0082948 +:10CBD00018BF042969D0082818BF042865D0012986 +:10CBE00018BF012853D000BF4FF0020164E090F855 +:10CBF0001201002860D0082918BF042955D0082840 +:10CC000018BF042851D0012918BF01283FD0EBE7F5 +:10CC1000222B22D0002A4BD090F8C20194F8641045 +:10CC200010F0040F18BF40460CD0082918BF042983 +:10CC30003BD0082818BF042837D0012918BF012885 +:10CC400025D0D1E710F0010F18BF3846EDD110F014 +:10CC5000020F18BF4846E8D12EE04AB390F8C2212F +:10CC600090F85D0094F8641002EA000010F0040FE0 +:10CC700018BF40460ED0082918BF042915D008282F +:10CC800018BF042811D0012918BF0128ACD14FF0DA +:10CC9000010111E010F0010F18BF3846EBD110F080 +:10CCA000020F18BF4846E6D106E04FF0080103E046 +:10CCB00094F864100429F8D0A08E11F00C0F18BF5E +:10CCC0004FF42960F4F709FA218E814238BF0846F3 +:10CCD000ADF80400A4F84C000598FCF7F5FB60B132 +:10CCE0007289316A42F48062728172694FF48060A5 +:10CCF000904703206871EF7022E709AA01A9F06A42 +:10CD0000F6F7CFFB306210B195F8371021B10598D6 +:10CD1000FCF7AEFB6F7113E79DF8241031B9A0F852 +:10CD200000B080F802B0012101F09EFABDF80410B5 +:10CD3000306A01F0C7FB85F8059001E70598FCF71C +:10CD400097FBFDE6B4F84C00ADF8040009AA01A970 +:10CD5000F06AF6F7A6FB3062002808BFFFDFEFE6B7 +:10CD60002401002058010020300D0020380F002041 +:10CD70000598FCF7A9FB002808BFFFDFE0E600BF2D +:10CD800030EA080009D106E030EA080005D102E0E7 +:10CD9000B8F1000F01D0012100E00021306A0278D3 +:10CDA00042EA01110170697C00291CBF69790129DF +:10CDB0003BD005F15801FD4891E80E1000F50278CE +:10CDC00088E80E10A96EC0F82112E96EC0F825128D +:10CDD00000F58170FBF70FFE9AF8000000280CBFE9 +:10CDE00001210021F2480176D5E91212C0E90412AE +:10CDF000A0F58371326AFBF72CF894F864000128DF +:10CE000008BF00220CD0022808BF012208D0042845 +:10CE100008BF032204D008281ABFFFDF0022022225 +:10CE2000FB210020FBF730F803E0FBF7E4FDFBF704 +:10CE300057F8012194F865200846FBF7BAFE3771D0 +:10CE4000306A0188F181807830743770FCF799FA84 +:10CE5000002818BFFFDF0BB0BDE8F08F2DE9F043CD +:10CE6000D44D87B081462878DDF838801E461746B5 +:10CE70000C4628B9002F1CBF002EB8F1000F00D1BE +:10CE8000FFDFC5F81C80C5E90D94C5E905764FF0B4 +:10CE90000000A8716871E870A8702871C64E68819A +:10CEA000A881307804F170072088F7F742F9E8622A +:10CEB0002088F7F72CF92863FBF705FA94F9670047 +:10CEC000FBF7DAFA04F11200FBF76CFD04F10E0037 +:10CED000FBF7D8FA307800280CBF03200120FBF7BD +:10CEE00087FDB64890E80E108DE80E10D0E90410CA +:10CEF000CDE90410307800280CBFB148B148049047 +:10CF00006846FBF763FDF87EFBF7C4FAFBF76AFDA2 +:10CF100094F86F0078B9A06E68B1B88C39888842EF +:10CF200009D1B4F86C1001220844B88494F86E005A +:10CF3000A16EF8F7D8FE3078002804BFFF2094F8DF +:10CF400064401AD094F8651097F81280258F608F8E +:10CF5000082926D0F4F7C1F8B8F1000F04BF001D6E +:10CF600080B2854238BF2846C0B2B97C002918BFBC +:10CF70000421084494F86540C0B22146FBF77FF9CC +:10CF80003078214688B10120FBF74BFB7068D0F860 +:10CF90000001FBF733FD0120FFF7F7FC07B0BDE808 +:10CFA000F0830421F4F799F8D6E70020FBF739FB6A +:10CFB000FFF7A4FD07B0BDE8F0837F4800B5017816 +:10CFC0003438007819B1022818BFFFDF00BD0128EE +:10CFD00018BFFFDF00BD774810B50078022818BFE2 +:10CFE000FFDFBDE8104000F070BA00F06EBA714883 +:10CFF000007970476F488089C0F3002070476D4802 +:10D00000C07870472DE9F04706006B48694D4068CD +:10D0100000F17004686A90F8019018BF012E03D1E6 +:10D02000296B07F0F1FF6870687800274FF001085E +:10D03000A0B101283CD0022860D003281CBFFFDF2C +:10D04000BDE8F087012E08BFBDE8F087286BF6F732 +:10D05000E3FCE879BDE8F047E5F756BF012E14D0B0 +:10D06000A86A002808BFFFDF2889C21CD5E909107B +:10D07000F1F7E3F9A86A686201224946286BF6F7DE +:10D0800047FB022E08BFBDE8F087D4E91401401C1D +:10D0900041F10001C4E91401E079012801D1E771EF +:10D0A00001E084F80780E879BDE8F047E5F72CBF98 +:10D0B000012E14D0A86A002808BFFFDF2889C21CEF +:10D0C000D5E90910F1F7B9F9A86A68620022494662 +:10D0D000286BF6F71DFB022E08BFBDE8F087D4E9E8 +:10D0E0001410491C40F10000C4E91410E079012833 +:10D0F0000CBFE77184F80780BDE8F087012E06D0E9 +:10D10000286BF6F789FC022E08BFBDE8F087D4E94A +:10D110001410491C40F10000C4E91410E079012802 +:10D12000BFD1BCE72DE9F041234D2846A5F13404D9 +:10D13000406800F170062078012818BFFFDFB07842 +:10D140000127002158B1B1706289042042F0040225 +:10D150006281626990472878002818BF3771216A78 +:10D160000322087832EA000009D1628912F4806F44 +:10D1700005D042F002026281626902209047A169F3 +:10D180000020884760B3607950BB287818B30E48F8 +:10D19000007810F0100F04D10449097811F0100F35 +:10D1A0001ED06189E1B9A16AA9B90FE0300D002054 +:10D1B000380F0020240100205801002004630200E1 +:10D1C000BB220200A7A8010032010020218911B171 +:10D1D00010F0100F04D0BDE8F0410020FFF7D5BBE0 +:10D1E000BDE8F04100F071B92DE9F05FCC4E044686 +:10D1F0003046A6F134054068002700F1700A28780F +:10D20000B846022818BFFFDFA889FF2240F400704B +:10D21000A881706890F864101046FBF730F89AF80F +:10D2200012004FF00109002C00F0F080FAF77DFEAB +:10D23000FAF76BFE90B99AF8120078B1686A4178F3 +:10D2400061B100789AF80710C0F3C000884205D198 +:10D2500085F80290BDE8F05F00F037B9686A417860 +:10D260002981002908BFAF6203D0286BF6F70AFABC +:10D27000A862A88940F02000A881EF70706800F1D2 +:10D28000700B044690F82C0001281BD1FBF757FCCB +:10D2900059462046F3F729FEA0B13078002870687F +:10D2A0000CBF00F59A7000F50170218841809BF851 +:10D2B000081001719BF80910417180F80090E8791D +:10D2C000E5F722FE686A9AF806100078C0F380003D +:10D2D00088423AD0706800F1700490F87500002818 +:10D2E0002FD002284AD06771307800281CBF2079DF +:10D2F000002809D027716A89394642F010026A81F4 +:10D300006A694FF010009047E078A0B1E770FCF731 +:10D31000EAF8002808BFFFDF08206A89002142F0F0 +:10D3200008026A816A699047D4E91210491C40F1E9 +:10D330000000C4E91210A07901280CBFA77184F87D +:10D340000690A88940F48070A881696A9AF807302D +:10D350000878C0F3C0029A424DD1726800F0030011 +:10D3600002F17004012818BF02282DD003281CBF29 +:10D37000687940F0040012D068713CE0E86AF6F782 +:10D38000BCF8002808BFFFDFD4E91210491C40F1A7 +:10D390000000C4E91210E879E5F7B6FDA3E784F8C8 +:10D3A0000290AA89484642F40062AA816A8942F042 +:10D3B00001026A816A699047E079012801D1E77129 +:10D3C00019E084F8079016E04878D8B1A98941F4AB +:10D3D0000061A981A96A71B1FB2884BF687940F016 +:10D3E0001000C9D8A879002808BFC84603D08020FB +:10D3F0006A69002190470120A9698847E0B36879EC +:10D40000A0B13AE0E0790128DBD1D8E7002818BFC5 +:10D41000FAF7C5FDA88940F04000A881E97801200D +:10D42000491CC9B2E97001292DD8E5E7307890B9D7 +:10D430003C48007810F0100F04D13B49097811F0F6 +:10D44000100F1AD06989B9B9A96A21B9298911B10E +:10D4500010F0100F11D0B8F1000F1CBF0120FFF722 +:10D46000D1FDFFF74BFBB8F1000F08BFBDE8F09FFF +:10D470000220BDE8F05FC5E5FFE7B8F1000F1CBF73 +:10D480000020FFF7BFFDBDE8F05F00F01EB870B5EB +:10D490000D4606462248224900784C6850B1FAF7FA +:10D4A000F7FD034694F8642029463046BDE87040F5 +:10D4B000FDF78BBAFAF7ECFD034694F86420294691 +:10D4C0003046BDE8704004F0FCBE154910B54C680C +:10D4D000FBF714FBFBF7F3FAFBF7BCF9FBF768FA71 +:10D4E000FAF7FEFC94F82C00012808BFFBF727FB95 +:10D4F00094F86F0038B9A06E28B1002294F86E003D +:10D500001146F8F7F0FB094C00216269A0899047A9 +:10D51000E2696179A07890470020207010BD00007A +:10D520005801002032010020300D0020240100208D +:10D530002DE9F047FA4F894680463D782C0014D0FB +:10D540000126012D11DB601EC4B207EBC40090F868 +:10D550005311414506D10622494600F5AA70F0F75D +:10D560003FFF28B1761CAE42EDDD1020BDE8F0870C +:10D570002046BDE8F087EA498A78824286BF08449F +:10D5800090F843010020704710B540F2D3120021FB +:10D59000E348F0F77CFF0822FF21E248F0F777FF2D +:10D5A000E1480021417081704FF46171818010BDAC +:10D5B0002DE9F0410E460546FFF7BAFFD84C10287A +:10D5C00016D004EBC00191F85A0110F0010F1CBFF6 +:10D5D0000120BDE8F081607808283CBF012081F877 +:10D5E0005A011CD26078401C60700120BDE8F081B7 +:10D5F0006078082813D222780127501C207004EB91 +:10D60000C2083068C8F85401B088A8F85801102A38 +:10D6100028BFFFDF88F8535188F85A71E2E70020ED +:10D62000BDE8F081C04988707047BF488078704776 +:10D630002DE9F041BA4D00272878401E44B2002C55 +:10D6400030DB00BF05EBC40090F85A0110F0010F69 +:10D6500024D06878E6B2401E687005EBC6083046F4 +:10D6600088F85A7100F0E8FA102817D12878401E7F +:10D67000C0B22870B04211D005EBC001D1F85301FF +:10D68000C8F85301D1F85701C8F85701287800F0BD +:10D69000D3FA10281CBF284480F80361601E44B2EE +:10D6A000002CCFDAA0488770BDE8F0819C498A78C9 +:10D6B000824286BF01EB0010C01C002070472DE99C +:10D6C000F0470127994690463D460026FFF730FF78 +:10D6D000102820D0924C04EBC00191F85A1101F0AF +:10D6E000010600F0A9FA102815D0B9F1000F18BFF3 +:10D6F00089F80000A17881420DD904EB001111F1E5 +:10D70000030F08D0204490F84B5190F83B010128BA +:10D710000CBF0127002748EA060047EA0501084038 +:10D72000BDE8F0872DE9F05F1F4690468946064622 +:10D73000FFF7FEFE7A4C054610282ED000F07CFA4A +:10D7400010281CBF1220BDE8F09FA07808283ED208 +:10D75000A6781022701CA07004EB061909F10300D2 +:10D760004146F3F768FB09F1830010223946F3F7CD +:10D7700062FB10213846F3F74BFB3444102184F848 +:10D7800043014046F3F744FB84F84B0184F803510E +:10D79000002084F83B01BDE8F09FA078082816D24D +:10D7A00025784FF0000A681C207004EBC50BD9F8EF +:10D7B0000000CBF85401B9F80400ABF85801102D63 +:10D7C00028BFFFDF8BF853618BF85AA1C0E7072011 +:10D7D000BDE8F09F2DE9F041514CA078401E45B2C4 +:10D7E000002DB8BFBDE8F081EAB2A078401EC1B2FA +:10D7F000A17054FA85F090F803618A423DD004EBA1 +:10D80000011004EB0213D0F803C0C3F803C0D0F832 +:10D8100007C0C3F807C0D0F80BC0C3F80BC0D0F8DE +:10D820000FC0C3F80FC0D0F883C0C3F883C0D0F8CE +:10D8300087C0C3F887C0D0F88BC0C3F88BC0D0F8BE +:10D840008F00C3F88F006318A01801EB410193F813 +:10D8500003C102EB420204EB410180F803C104EB77 +:10D860004202D1F80BC1C2F80BC1B1F80F11A2F8F6 +:10D870000F1193F83B1180F83B1104EBC60797F8A2 +:10D880005A0110F0010F1CD1304600F0D5F91028D4 +:10D8900017D12078401EC0B22070B04211D004EBE6 +:10D8A000C000D0F85311C7F85311D0F85701C7F88A +:10D8B0005701207800F0C0F910281CBF204480F8E0 +:10D8C0000361681E45B2002D8EDABDE8F08116496D +:10D8D0004870704714484078704738B14AF2B81120 +:10D8E000884203D810498880012070470020704783 +:10D8F0000D488088704710B5FFF71AFE102804D035 +:10D9000000F09AF9102818BF10BD082010BD044976 +:10D910008A78824286BF01EB001083300020704776 +:10D92000600F00206C01002060010020FE4B93F886 +:10D9300002C084459CBF00207047184490F8030142 +:10D9400003EBC00090F853310B70D0F85411116004 +:10D95000B0F85801908001207047F34A114491F8C3 +:10D960000321F2490A700268C1F8062080884881C4 +:10D97000704770B516460C460546FBF7D5F8FAF722 +:10D98000C4F9EA48407868B1E748817851B12A196A +:10D99000002E0CBF8330C01CFAF791F9FAF7D8F9C2 +:10D9A000012070BD002070BD10B5FAF7FFF9002806 +:10D9B00004BFFF2010BDBDE81040FAF71DBAFAF70A +:10D9C000F5B9D9498A7882429CBF00207047084443 +:10D9D00090F8030101EBC00090F85A0100F001003B +:10D9E00070472DE9F047D04D00273E4628780028A3 +:10D9F00086BF4FF01009DFF83883BDE8F087AC78B8 +:10DA000021000CD00122012909DB601EC4B22819B3 +:10DA100090F80331B34203D0521C8A42F5DD4C46E4 +:10DA2000A14286BF05EB0410C01C002005EBC60A0E +:10DA30009AF85A1111F0010F16D050B1102C04D0E1 +:10DA4000291991F83B11012903D01021F3F7E0F9CE +:10DA500050B108F8074038467B1C9AF853210AF564 +:10DA6000AA71DFB2FAF7B5FC701CC6B22878B042D2 +:10DA7000C5D8BDE8F0872DE9F041AB4C002635460E +:10DA8000A07800288CBFAA4FBDE8F0816119C0B210 +:10DA900091F80381A84286BF04EB0510C01C00204A +:10DAA00091F83B11012903D01021F3F7B1F958B1D6 +:10DAB00004EBC800BD5590F8532100F5AA7130461B +:10DAC000731CDEB2FAF785FC681CC5B2A078A842C8 +:10DAD000DCD8BDE8F0810144934810B500EB02109A +:10DAE0000A4601218330FAF7EAF8BDE81040FAF758 +:10DAF0002FB90A468D4910B5497841B18A4B9978BA +:10DB000029B10244D81CFAF7DAF8012010BD002030 +:10DB100010BD854A01EB410102EB41010268C1F8E9 +:10DB20000B218088A1F80F0170472DE9F0417E4D4F +:10DB300007460024A878002898BFBDE8F081C0B24D +:10DB4000A04217D905EB041010F1830612D0102162 +:10DB50003046F3F75DF968B904EB440005EB400883 +:10DB600008F20B113A463046FBF74EFDB8F80F01AC +:10DB7000A8F80F01601CC4B2A878A042DFD8BDE8A5 +:10DB8000F081014610226B48F3F755B96948704798 +:10DB900065498A78824203D90A1892F843210AB16A +:10DBA0000020704700EB400001EB400000F20B103A +:10DBB00070475D498A78824206D9084490F83B0153 +:10DBC000002804BF01207047002070472DE9F04174 +:10DBD0000E460746144606213046F3F719F9524D12 +:10DBE00098B1A97871B105F59D7011F0010F18BFBA +:10DBF00000F8014FA978490804D0447000F8024F9A +:10DC0000491EFAD10120BDE8F08138463146FFF7C0 +:10DC10008FFC10280CD000F00FF8102818BF08282F +:10DC200006D0284480F83B414FF00100BDE8F08168 +:10DC30004FF00000BDE8F0813B4B10B4844698786B +:10DC400001000ED0012201290BDB401EC0B21C18BE +:10DC500094F80341644504BF10BC7047521C8A42CB +:10DC6000F3DD10BC1020704770B52F4C01466218D0 +:10DC7000A078401EC0B2A07092F8035181423CD0FF +:10DC800004EB011304EB001C01EB4101DCF8036021 +:10DC9000C3F80360DCF80760C3F80760DCF80B60CA +:10DCA000C3F80B60DCF80F60C3F80F60DCF883602A +:10DCB000C3F88360DCF88760C3F88760DCF88B60AA +:10DCC000C3F88B60DCF88FC0C3F88FC0231800EB5B +:10DCD000400093F803C104EB400082F803C104EB59 +:10DCE0004101D0F80BC1C1F80BC1B0F80F01A1F888 +:10DCF0000F0193F83B0182F83B0104EBC50696F84F +:10DD00005A0110F0010F18BF70BD2846FFF794FFAD +:10DD1000102818BF70BD2078401EC0B22070A842E5 +:10DD200008BF70BD08E00000600F00206001002007 +:10DD30006C0100203311002004EBC000D0F8531117 +:10DD4000C6F85311D0F85701C6F857012078FFF7ED +:10DD500073FF10281CBF204480F8035170BD0000E1 +:10DD60004078704730B50546007801F00F0220F08A +:10DD70000F0010432870092912D2DFE801F00507CF +:10DD800005070509050B0F0006240BE00C2409E02C +:10DD9000222407E001240020E87003E00E2401E0C3 +:10DDA0000024FFDF6C7030BD007800F00F0070477A +:10DDB0000A68C0F803208988A0F807107047D0F8D7 +:10DDC00003200A60B0F80700888070470A68C0F82E +:10DDD00009208988A0F80D107047D0F809200A6042 +:10DDE000B0F80D00888070470278402322F040028E +:10DDF00003EA81111143017070470078C0F380106D +:10DE000070470278802322F0800203EAC111114397 +:10DE1000017070470078C009704770B514460E460F +:10DE200005461F2A88BFFFDF2246314605F109005B +:10DE3000F0F703FBA01D687070BD70B544780E4606 +:10DE40000546062C38BFFFDFA01F84B21F2C88BFF9 +:10DE50001F24224605F109013046F0F7EEFA20466C +:10DE600070BD70B514460E4605461F2A88BFFFDFF9 +:10DE70002246314605F10900F0F7DFFAA01D68706F +:10DE800070BD0968C0F80F1070470A88A0F8132009 +:10DE900089784175704790F8242001F01F0122F025 +:10DEA0001F02114380F824107047072988BF0721FB +:10DEB00090F82420E02322F0E00203EA411111430C +:10DEC00080F8241070471F3008F065B810B504467C +:10DED00000F000FB002818BF204410BDC17811F0ED +:10DEE0003F0F1BBF027912F0010F0022012211F037 +:10DEF0003F0F1BBF037913F0020F002301231A44C5 +:10DF000002EB4202530011F03F0F1BBF027912F0E7 +:10DF1000080F0022012203EB420311F03F0F1BBF49 +:10DF2000027912F0040F00220122134411F03F0F76 +:10DF30001BBF027912F0200F0022012202EBC20265 +:10DF400003EB420311F03F0F1BBF027912F0100FD9 +:10DF50000022012202EB42021A4411F03F0F1BBFC4 +:10DF6000007910F0400F00200120104410F0FF0055 +:10DF700014BF012100210844C0B2704770B5027877 +:10DF8000417802F00F02082A4DD2DFE802F00408BF +:10DF90000B4C4C4C0F14881F1F280AD943E00C2946 +:10DFA00007D040E0881F1F2803D93CE0881F1F28A6 +:10DFB00039D8012070BD4A1EFE2A34D88446C07864 +:10DFC00000258209032A09D000F03F04601C884222 +:10DFD00004D86046FFF782FFA04201D9284670BDF1 +:10DFE0009CF803004FF0010610F03F0F1EBF1CF11C +:10DFF0000400007810F0100F13D06446042160462E +:10E0000000F068FA002818BF14EB0000E6D0017891 +:10E0100001F03F012529E1D280780221B1EB501FA8 +:10E02000DCD3304670BD002070BD70B5017801258D +:10E0300001F00F01002404290AD007290DD0082976 +:10E040001CBF002070BD40780E2836D0204670BD21 +:10E050004078801F1F2830D9F8E7844640789CF824 +:10E0600003108A09032AF1D001F03F06711C814296 +:10E07000ECD86046FFF732FFB042E7D89CF80300C7 +:10E0800010F03F0F1EBF1CF10400007810F0100FBD +:10E0900013D066460421604600F01CFA002818BF21 +:10E0A00016EB0000D2D0017801F03F012529CDD236 +:10E0B00080780221B1EB501FC8D3284670BD10B440 +:10E0C000017801F00F01032920D0052921D14478DE +:10E0D000B0F81910B0F81BC0B0F81730827D222CB0 +:10E0E00017D1062915D3B1F5486F98BFBCF5FA7F53 +:10E0F0000FD272B1082A98BF8A420AD28B429CBFC3 +:10E10000B0F81D00B0F5486F03D805E040780C2842 +:10E1100002D010BC0020704710BC012070472DE9D0 +:10E12000F0411F4614460D00064608BFFFDF21469A +:10E13000304600F0CFF9040008BFFFDF30193A463F +:10E140002946BDE8F041F0F778B9C07800F03F000B +:10E150007047C02202EA8111C27802F03F021143E7 +:10E16000C1707047C07880097047C9B201F00102E0 +:10E17000C1F340031A4402EB4202C1F3800303EBF4 +:10E180004202C1F3C00302EB4302C1F3001303EBED +:10E1900043031A44C1F3401303EBC30302EB4302EE +:10E1A000C1F380131A4412F0FF0202D0521CD2B203 +:10E1B0000171C37802F03F0103F0C0031943C1703D +:10E1C000511C417070472DE9F0410546C078164654 +:10E1D00000F03F041019401C0F46FF2888BFFFDFE6 +:10E1E000281932463946001DF0F727F9A019401CBE +:10E1F0006870BDE8F081C178407801F03F01401AB5 +:10E20000401E80B2704710B590F803C00B460CF06A +:10E210003F0144780CF03F0CA4EB0C0CACF1010C6A +:10E220001FFA8CF4944288BF14462BB10844011D98 +:10E2300022461846F0F701F9204610BD4078704795 +:10E2400000B5027801F0030322F003021A430270C2 +:10E25000012914BF0229002104D0032916BFFFDFC2 +:10E26000012100BD417000BD00B5027801F003033B +:10E2700022F003021A430270012914BF022900216F +:10E2800004D0032916BFFFDF012100BD417000BD8E +:10E29000007800F003007047417841B1C078192838 +:10E2A00003D2BC4A105C884201D101207047002093 +:10E2B000704730B501240546C17019293CBFB548E7 +:10E2C000445C02D3FF2918BFFFDF6C7030BD70B50E +:10E2D00015460E4604461B2A88BFFFDF65702A4696 +:10E2E0003146E01CBDE87040F0F7A7B8B0F8070071 +:10E2F0007047B0F809007047C172090A017370478E +:10E30000B0F80B00704730B4B0F80720B0F809C07F +:10E31000B0F805300179941F40F67A45AC4298BFB9 +:10E32000BCF5FA7F0ED269B1082998BF914209D293 +:10E3300093429FBFB0F80B00B0F5486F012030BC8E +:10E3400098BF7047002030BC7047001D07F023BE07 +:10E35000021D0846114607F01EBEB0F809007047BE +:10E36000007970470A684260496881607047426876 +:10E370000A60806848607047098881817047808999 +:10E38000088070470A68C0F80E204968C0F812106B +:10E390007047D0F80E200A60D0F81200486070472D +:10E3A0000968C0F816107047D0F81600086070476A +:10E3B0000A68426049688160704742680A60806804 +:10E3C000486070470968C1607047C068086070475E +:10E3D000007970470A684260496881607047426806 +:10E3E0000A608068486070470171090A417170478E +:10E3F0008171090AC17170470172090A417270473F +:10E400008172090AC172704780887047C08870475E +:10E41000008970474089704701891B2924BF4189C1 +:10E42000B1F5A47F07D381881B2921BFC088B0F52F +:10E43000A47F01207047002070470A684260496845 +:10E440008160704742680A6080684860704701795F +:10E4500011F0070F1BBF407910F0070F00200120BB +:10E460007047017911F0070F1BBF407910F0070FBB +:10E470000020012070470171704700797047417199 +:10E480007047407970478171090AC1717047C0882F +:10E4900070470179407901F007023F498A5C012AFF +:10E4A00006D800F00700085C01289CBF01207047D7 +:10E4B00000207047017170470079704741717047C3 +:10E4C0004079704730B50C460546FB2988BFFFDF11 +:10E4D0006C7030BDC378024613F03F0008BF704730 +:10E4E0000520127903F03F0312F0010F37D0002905 +:10E4F00014BF0B20704700BF12F0020F32D0012969 +:10E5000014BF801D704700BF12F0040F2DD00229E8 +:10E5100014BF401C704700BF12F0080F28D0032919 +:10E5200014BF801C704700BF12F0100F23D00429C5 +:10E5300014BFC01C704700BF12F0200F1ED0052969 +:10E540001ABF1230C0B2704712F0400F19D006291E +:10E550001ABF401CC0B27047072918D114E0002927 +:10E56000CAD114E00129CFD111E00229D4D10EE0A3 +:10E570000329D9D10BE00429DED108E00529E3D134 +:10E5800005E00629E8D102E0834288BF70470020F9 +:10E5900070470000246302001C63020030B490F84E +:10E5A00064508C88B1F808C015F00C0F1BD000BF68 +:10E5B000B4F5296F98BF4FF4296490F8655015F0B1 +:10E5C0000C0F17D0BCF5296F98BF4FF4296C4A88FF +:10E5D000C988A0F84420A0F84810A0F84640A0F848 +:10E5E0004AC030BC7047002B1CBF157815F00C0FCB +:10E5F000DED1E2E7002B1CBF527812F00C0FE1D104 +:10E60000E5E7DDF800C08181C2810382A0F812C075 +:10E6100070471B2202838282C281828142800281F2 +:10E62000028042848284828359B14FF429614183FC +:10E63000C18241820182C18041818180C184018582 +:10E6400070474FF4A4714183C18241820182C1802D +:10E6500041818180C18401857047F0B4B0F84820C1 +:10E66000818F468EC58E8A4228BF0A4690F8651073 +:10E670004FF0000311F00C0F18BF4FF4296106D1C1 +:10E68000B0F84AC0B0F840108C4538BF61464286A9 +:10E69000C186048FB0F83AC0944238BF14468C4506 +:10E6A00038BF8C460487A0F83AC0B2420ABFA942DC +:10E6B0004FF0010C4FF0000C058EB0F84410C28FE3 +:10E6C000848E914228BF114690F8642012F00C0FFE +:10E6D00018BF4FF4296206D1B0F84660B0F8422066 +:10E6E000964238BF324690F85A60022E0AD0018610 +:10E6F0008286A9420ABFA2420120002040EA0C0003 +:10E70000F0BC70478D4238BF2946944238BF22463C +:10E7100080F85A30EBE7508088899080C889D08093 +:10E72000088A1081488A508101201070704730B4E7 +:10E7300002884A80B0F830C0A1F804C0838ECB8034 +:10E74000428E0A81C48E4C81B0F85650A54204BF57 +:10E75000B0F85240944208D1B0F858409C4202BFF1 +:10E76000B0F854306345002301D04FF001030B7320 +:10E7700000F13003A0F852201A464B89D3848B88CD +:10E780009384CA88A0F858204FF00100087030BC6C +:10E79000704730B404460A46088E91F864104FF46E +:10E7A000747311F00C0F1CBF03EB801080B21ED0ED +:10E7B000918E814238BF0846118F92F865C01CF0D7 +:10E7C0000C0F1CBF03EB811189B218D0538F8B4201 +:10E7D00038BF194692F866301CF00C0F08BF0023B2 +:10E7E000002C0CBF0122002230BCF2F798BC022999 +:10E7F00007BF80003C30C000703080B2D8E7BCF169 +:10E80000020F07BF89003C31C900703189B2DDE7D2 +:10E810002DE9F041044606F099FCC8B9FE4F78682E +:10E8200090F8221001260025012914D00178012931 +:10E830001BD090F8281001291CBF0020BDE8F081F2 +:10E84000657018212170D0F82A10616080F8285076 +:10E850000120BDE8F081657007212170416A616087 +:10E8600080F822500120BDE8F081657014212170EC +:10E87000811C2022201DEFF7E0FD257279680D70C4 +:10E8800081F82850E54882888284C26B527B80F8E8 +:10E89000262080F82260C86B0088F5F738FCF5F771 +:10E8A000E0F8D5E7DC4840680178002914BF80888B +:10E8B0004FF6FF70704730B5D74C83B00D462078C7 +:10E8C0007F2808BFFFDF94F900307F202070D4F844 +:10E8D00004C09CF85000062808BF002205D09CF810 +:10E8E000500008280CBF022201229CF85400CDE9F8 +:10E8F000000302929CF873309CF880200CF13201E6 +:10E90000284606F08FFC03B0BDE8304006F01FBE7D +:10E910002DE9F04106F05FFC002818BF06F0E4FB8B +:10E92000BD4C606800F1840290F87610895C80F834 +:10E930008010002003F07EF828B3FAF753F86068DF +:10E94000B74990F855000D5C2846F9F7A3FD6068BB +:10E950004FF0000680F8735090F8801011F00C0F03 +:10E960000CBF25200F20F9F76CFC606890F8801030 +:10E970000120F9F711FE606890F84010032918BFD4 +:10E9800002290FD103E0BDE8F04101F02FB990F862 +:10E9900076108430085C012804D101221146002041 +:10E9A000FAF707F9FAF7D5F8606890F88050012D6A +:10E9B00007BF0127032100270521A068FFF799F869 +:10E9C000616881F8520040B1002F18BF402521D066 +:10E9D000F9F787F92846FAF79DF86068806DFAF72D +:10E9E0000DF8606890F85410FF291CBF6D30FEF7D9 +:10E9F000B4FFFF21606880F8531080F8541080F84D +:10EA0000626080F8616080F87D60062180F85010B7 +:10EA1000BDE8F08115F00C0F14BF55255025D7E740 +:10EA200070B57D4C0646606800F150052046806850 +:10EA300041B1D0F80510C5F81D10B0F80900A5F8CF +:10EA4000210003E005F11D01FFF7B9F9A068FFF708 +:10EA5000D4F985F82400A0680021032E018002D09B +:10EA6000052E04D03DE00321FFF77CF939E00521B4 +:10EA7000FFF778F96068C06B00F10E01A068FFF73E +:10EA800000FA6068C06B00F11201A068FFF7FDF9A1 +:10EA9000D4E90110CA6B527D8275CA6BD28AC275E5 +:10EAA000120A0276CA6B52884276120A8276CA6BC2 +:10EAB0009288C276120A0277CA6BD2884277120A0B +:10EAC0008277C96B0831FFF7FEF96068C06B017E81 +:10EAD000A068FFF7E0F9606890F88610A068FFF77B +:10EAE000E4F905F11D01A068FFF770F995F824100D +:10EAF000A068FFF786F9606800F1320590F8316090 +:10EB000090F8511091B190F84010032906D190F877 +:10EB10003910002918BF90F8560001D190F8530021 +:10EB2000FFF736F800281CBF012605462946A068D5 +:10EB3000FFF73EF93146A068BDE87040FFF754B9D1 +:10EB40003549496881F84B00704770B5324D002453 +:10EB50000126A8606968A1F8814081F8834081F8A6 +:10EB6000506091F85020022A1FBF91F850100129DF +:10EB7000FFDF70BD06F0CDFA6868047080F82240AF +:10EB800080F8284090F8520030B1F9F7CDFFF9F73E +:10EB9000BCF8686880F852406868072180F84A40ED +:10EBA00080F8396080F8404080F8554080F84B404C +:10EBB00080F87D4080F8381070BD2DE9F041164C8A +:10EBC000054686B0606890F85000012818BF0228FA +:10EBD00005D003281EBF0C2006B0BDE8F081687A7E +:10EBE000022839D0F9F76FFB0220F9F701FF0D4930 +:10EBF00001F10C0090E80D108DE80D10D1E907012E +:10EC0000CDE904016846F9F7E1FE606890F94B0030 +:10EC1000F9F732FCA06807E07401002044110020DD +:10EC20004363020040630200F9F7E5FEFC48F9F790 +:10EC3000B9FEFC48F9F726FC606890F831103230D4 +:10EC4000F9F7A5FB0F210720F9F7BFFB606890F8E3 +:10EC50003900E0B1FEF70FFF6168287A01F1840204 +:10EC600081F87600287A805C81F880006868886581 +:10EC70002A68CA65687A68B1012824D00525022867 +:10EC800008BF81F850506FD0032878D080E0FEF79D +:10EC9000A8FEE1E7E44B91F83850002291F85500C6 +:10ECA000401CA3FB006C4FEA5C0CACEB8C0C60448A +:10ECB00081F8550025FA00F010F0010F03D1501C27 +:10ECC000C2B2032AEAD3002681F87D6091F8490098 +:10ECD000002804BF91F85100002841D0F7F744FA0A +:10ECE000074660683946406CF7F736FFDFF83C832B +:10ECF000054690FBF8F008FB105041423846F6F705 +:10ED00001AFF6168486495FBF8F08A6F10448867C1 +:10ED1000FEF7EEFD01466068826F914220D847649D +:10ED2000866790F8510000281CBF0120FEF7FDFE09 +:10ED30000121606890F84A20002A1CBF90F8492001 +:10ED4000002A0DD090F8313000F13202012B04D1AD +:10ED5000527902F0C002402A08D03230FAF78CFC17 +:10ED60006168042081F8500012E008E00125FEF7F8 +:10ED70000DFF61682A463231FAF746FCF0E7002AB7 +:10ED800018BFFFDF012000F089FF606880F8505055 +:10ED900006B00020BDE8F08170B5A54D686890F818 +:10EDA000501004292ED005291CBF0C2070BD90F8EE +:10EDB0007D100026002990F883104FEA511124D0CD +:10EDC000002908BF012407D0012908BF022403D06D +:10EDD000022914BF00240824C06D00281CBF002095 +:10EDE00000F05CFF6868806DF9F708FE686890F8CD +:10EDF0004010022943D0032904BF90F86C10012968 +:10EE000041D04DE0FFF784FD52E0002908BF012406 +:10EE100007D0012908BF022403D0022914BF00240F +:10EE20000824C06D00281CBF002000F037FF686870 +:10EE3000806DF9F7E3FD686890F84010022906D06C +:10EE4000032904BF90F86C10012904D010E090F859 +:10EE50006C1002290CD1224614F00C0F04D090F84B +:10EE60004C00012808BF042201210020F9F7A1FE6F +:10EE70006868072180F8804080F8616016E090F8AB +:10EE80006C1002290CD1224614F00C0F04D090F81B +:10EE90004C00012808BF042201210020F9F789FE57 +:10EEA0006868082180F8804080F8616080F8501020 +:10EEB000002070BD5E49002210F0010F496802D0A9 +:10EEC000012281F8842010F0080F03D0114408209B +:10EED00081F88400002070475549496881F848004E +:10EEE000704710B5524C636893F83030022B14BF52 +:10EEF000032B00280BD100291ABF02290120002072 +:10EF00001146FEF7F8FC08281CBF012010BD606800 +:10EF100090F83000002816BF022800200120BDE82C +:10EF20001040FAF731BB4248406890F830000028A2 +:10EF300016BF022800200120FAF726BB3C49496889 +:10EF400081F8300070473A49496881F84A007047B3 +:10EF500070B5374C616891F83000002816BF022860 +:10EF60000020012081F8310001F13201FAF7F6FAB0 +:10EF7000606890F83010022916BF03290121002192 +:10EF800080F8511090F8312000F132034FF0000565 +:10EF9000012A04BF5B7913F0C00F0AD000F13203DD +:10EFA000012A04D15A7902F0C002402A01D000227D +:10EFB00000E0012280F84920002A04BF002970BD2A +:10EFC0008567F7F7D1F86168486491F85100002827 +:10EFD0001CBF0020FEF7A9FD0026606890F84A10CB +:10EFE00000291ABF90F84910002970BD90F831200F +:10EFF00000F13201012A04D1497901F0C001402910 +:10F0000005D02946BDE870403230FAF735BBFEF72F +:10F01000BDFD61683246BDE870403231FAF7F4BA9E +:10F020004063020046630200ABAAAAAA40420F0056 +:10F030007401002070B5FF4D0C4600280CBF012361 +:10F040000023696881F8393081F842004FF00800E8 +:10F0500081F856000CD1002C1ABF022C0120002090 +:10F060001146FEF748FC6968082881F8560001D06F +:10F07000002070BD022C14BF032C1220F8D170BDEB +:10F08000002818BF112070470328EA4A526808BFB9 +:10F09000D16382F840000020704710B5E54C6068ED +:10F0A00090F8401003291CBF002180F8601001D0A7 +:10F0B000002010BD0123C16B1A460020F2F738F87A +:10F0C0006168CA6B526A904294BF0120002081F8A7 +:10F0D0006000EDE7D748416891F84000032804D06C +:10F0E000012818BF022807D004E091F84200012847 +:10F0F00008BF70470020704791F84100012814BFF5 +:10F1000003280120F6D1704770B5F9F7F7FCF9F73D +:10F11000D6FCF9F79FFBF9F74BFCC64C002560685D +:10F1200090F8520030B1F9F7FFFCF8F7EEFD606897 +:10F1300080F8525060680121A0F8815080F8835017 +:10F1400080F8501080F82850002070BDB94810B5E4 +:10F150004068643006F0B1FB002010BDB5480121C5 +:10F16000406890F84020032A03BF80F82A10C26B41 +:10F170001288002218BF80F82A20828580F8281083 +:10F180007047AC49496881F88600704701780023D0 +:10F1900011F0010FA749496809D04278032A08BF36 +:10F1A000CB6381F84020012281F884201346027845 +:10F1B00012F0040F0CD082784FF0000C032A08BF25 +:10F1C000C1F83CC081F840200B44082283F8842019 +:10F1D000C27881F830200279002A16BF022A012362 +:10F1E000002381F8393081F84120427981F83820B4 +:10F1F000807981F848004FF0000070478D484068E2 +:10F200008030704770B58B4C06460D46606890F8AC +:10F210005000032818BFFFDF022E1EBF032EFFDFA2 +:10F2200070BD002D18BF06F0A1F900216068A0F89C +:10F23000811080F88310012180F8501070BD00F01B +:10F24000D5BC2DE9F0477B4C0646894660684FF0F7 +:10F250000108072E90F8397038BF032540D3082ED7 +:10F2600084BF0020BDE8F08790F85010062908BF41 +:10F27000002105D090F8501008290CBF022101216F +:10F2800090F8800005F0AEFF002873D1A068C17827 +:10F2900011F03F0F12D0027912F0010F0ED0616809 +:10F2A0004FF0050591F85220002A18BFB9F1000F60 +:10F2B00016D091F88010012909D011E011F03F0F0C +:10F2C0001ABF007910F0100F002F53D14CE04FF00F +:10F2D00001024FF00501FEF74CFB616881F8520016 +:10F2E000A16808782944C0F3801030B1487900F053 +:10F2F000C000402808BF012000D00020616891F8BC +:10F300005210002918BF002807D0FEF74DFB014618 +:10F31000606880F8531080F86180606890F853103E +:10F32000FF292AD080F854100846FEF74AFB40EA2D +:10F330000705606890F85320FF2A18BF002D10D0F1 +:10F34000072E0ED3A068C17811F03F0F09D00179C4 +:10F3500011F0020F05D00B21FEF7BDFB606880F8AD +:10F3600062802846BDE8F087FEF75FF9002808BFF5 +:10F37000BDE8F0870120BDE8F087A36890F8392048 +:10F3800059191B78C3F3801C00F153036046FEF744 +:10F3900096F90546CDE72DE9F043264C87B0A068E5 +:10F3A000FEF7E0FE7F264FF00108002558B1022746 +:10F3B00001287DD0022800F0EF80F9F74BFA07B062 +:10F3C0000620BDE8F083F9F745FA616891F840003E +:10F3D000032800F01581A068C27812F03F0F05D015 +:10F3E000037913F0100F18BF012700D10027002F59 +:10F3F00014BF0823012312F03F0F00F001810079B0 +:10F4000033EA000240F0FC8010F0020F08D091F8BF +:10F410008000002105F064FE002808BF012000D014 +:10F4200000208DF80C508DF810508DF814504FF0CE +:10F43000FF0801E074010020D0B105AA03A904A8C7 +:10F4400000F07AFC606890F831809DF80C0000288C +:10F4500018BF48F002080BD1A068FEF7DBFC81461C +:10F460000121A068FEF732FD4946F8F79AFF28B35C +:10F47000FFB1012000F0DDFB002852D020787F286A +:10F4800008BFFFDF94F900102670606890F85420E0 +:10F49000CDE90021029590F8733090F8802000F1BA +:10F4A0003201404605F0BEFE606880F86C50A3E073 +:10F4B00038E041460020FFF7FEF9A1E0606890F8CF +:10F4C0004100032818BF02282BD19DF81000002806 +:10F4D00027D09DF80C00002823D1F7B1012000F0BF +:10F4E000A8FB00281DD020787F2808BFFFDF94F9F3 +:10F4F00000102670606890F85420CDE90021029534 +:10F5000090F8733090F8802000F13201FE2005F071 +:10F5100089FE606880F86C506EE0FE210020FFF7E5 +:10F52000CAF96DE0F9F796F9A0681821C27812F0CF +:10F530003F0F65D00279914362D10421FEF7C6FCEA +:10F54000616891F84020032A01BF8078B7EB501F13 +:10F5500091F86000002853D04FF0010000F069FBE3 +:10F56000E8B320787F2808BFFFDF94F900102670E9 +:10F57000606890F85420CDE90021029590F873302E +:10F5800090F8802000F13201FF2005F04BFE60680A +:10F5900080F86C8030E000BFF9F75CF9606890F8A3 +:10F5A000400003282CD0A0681821C27812F03F0F29 +:10F5B00026D0007931EA000022D1012000F039FB89 +:10F5C00068B120787F2808BFFFDF94F9001026700B +:10F5D000606890F85420CDE90021029500E00FE02A +:10F5E00090F8733090F8802000F13201FF2005F090 +:10F5F00019FE606880F86C7007B00320BDE8F083E6 +:10F6000007B00620BDE8F083F0B5FE4C074683B096 +:10F6100060686D460078002818BFFFDF002661682B +:10F620008E70C86B02888A8042884A8382888A8367 +:10F63000C088C88381F8206047B10121A068FEF727 +:10F6400045FC0546A0680078C10907E06946A06846 +:10F65000FEF7B5FBA0680078C0F380116068012751 +:10F6600090F85120002A18BF002904D06A7902F0CE +:10F67000C002402A26D090F84A20002A18BF00294C +:10F6800003D0697911F0C00F1CD000F10E00E3F730 +:10F69000B3FC616891F85400FF2819D001F1080209 +:10F6A000C91DFEF743F9002808BFFFDF6068C17974 +:10F6B00041F00201C171D0F86D104161B0F87110D4 +:10F6C00001830FE02968C0F80E10A9884182E0E7A5 +:10F6D000C86B427ECA71D0F81A208A60C08B8881BC +:10F6E0004E610E8360680770C26B90F84B1082F811 +:10F6F0006710C06B0088F4F70AFDF4F7A3F903B0B4 +:10F70000F0BD2DE9F041BF4C0546002760684FF081 +:10F7100001083E4690F84000012818BF022802D098 +:10F72000032818BFFFDF5DB1A068FEF727FC18B9FA +:10F73000A068FEF77AFC18B100F08FFB074645E0A1 +:10F74000606890F850007F25801F06283ED2DFE8D1 +:10F7500000F003191924352FAA48F9F709FA0028EF +:10F7600008BF2570F9F7EBF9606890F8520030B1E6 +:10F77000F9F7DAF9F8F7C9FA606880F85260F9F732 +:10F7800069F830E09F48F9F7F3F9002808BF2570C1 +:10F79000F9F7D5F905F0EAFEC3E09A48F9F7E8F978 +:10F7A000002808BF2570F9F7CAF9F9F753F81AE0ED +:10F7B0009448F9F7DDF930B9257004E09148F9F77C +:10F7C000D7F90028F8D0F9F7BAF9AAE0102F80F09D +:10F7D0003881DFE807F01E9DA6AAF1F108B3F2F127 +:10F7E000F1F10C832051BDE8F041FFF791B80320FF +:10F7F00002F020F9002870D000210320FFF710F953 +:10F80000012211461046F9F7D4F961680C2081F8FD +:10F810005000BDE8F081606800F15005042002F05E +:10F8200009F900287DD00E202870012002F0FDFC8F +:10F83000A06861680078C0F3401081F8750000216D +:10F840000520FFF7EDF87048A1684FF0200CC26B5F +:10F850000B78527B23F020030CEA42121A430A7001 +:10F86000C16B95F825304A7B1A404A73C06B28213A +:10F8700080F86610BDE8F081062002F0DBF8002871 +:10F880004FD0614D0F2085F85000022002F0CDFCD2 +:10F890006068012190F880200846F9F78AF9A0688D +:10F8A00061680078C0F3401081F8750001210520DF +:10F8B000FFF7B6F8E86B80F80D80A068017821F0BA +:10F8C00020010170F9F75DFD002818BFFFDF282037 +:10F8D000E96B81F86600BDE8F08122E0052002F0C6 +:10F8E000A9F8F0B101210320FFF79AF8F9F749FDD3 +:10F8F000002818BFFFDF6068012190F880200846CB +:10F90000F9F757F961680D2081F85000BDE8F081E2 +:10F910006068A0F8816080F8836080F85080BDE85E +:10F92000F081BDE8F04100F061B96168032081F821 +:10F930005000BDE8F041082002F077BC606890F804 +:10F940008310490908BF012507D0012908BF0225F6 +:10F9500003D0022914BF00250825C06D00281CBF54 +:10F96000002000F09BF96068806DF9F747F8606847 +:10F9700090F84010022906D0032904BF90F86C10BB +:10F98000012904D010E090F86C1002290CD12A460D +:10F9900015F00C0F04D090F84C00012808BF042289 +:10F9A00001210020F9F705F96068072180F88050EF +:10F9B00080F8616041E000E043E0606890F8831007 +:10F9C000490908BF012507D0012908BF022503D036 +:10F9D000022914BF00250825C06D00281CBF002087 +:10F9E00000F05CF96068806DF9F708F8606890F8DD +:10F9F000401002290AD0032904BF90F86C10012995 +:10FA000008D014E0740100204411002090F86C101C +:10FA100002290CD12A4615F00C0F04D090F84C00A6 +:10FA2000012808BF042201210020F9F7C2F860680C +:10FA3000082180F8805080F8616080F85010BDE89F +:10FA4000F081FFDFBDE8F08170B5FE4C606890F892 +:10FA5000503000210C2B38D001220D2B40D00E2B22 +:10FA600055D00F2B1CBFFFDF70BD042002F0DDFB63 +:10FA7000606890F880100E20F8F7E3FB606890F85B +:10FA8000800010F00C0F14BF282100219620F8F7F9 +:10FA9000D3FFF9F75EF86068052190F88050A06800 +:10FAA000FEF727F8616881F8520048B115F00C0F95 +:10FAB0000CBF50255525F8F714F92846F9F72AF810 +:10FAC00061680B2081F8500070BDF9F742F8002101 +:10FAD0009620F8F7B1FF6168092081F8500070BDE9 +:10FAE00090F88010FF20F8F7ACFB606890F8800079 +:10FAF00010F00C0F14BF282100219620F8F79CFF6E +:10FB0000F9F727F861680A2081F8500070BDA0F865 +:10FB1000811080F8831080F850200020FFF774FDDA +:10FB2000BDE87040032002F080BB70B5C54C606832 +:10FB300090F850007F25801F062828BF70BDDFE8A1 +:10FB400000F0171F1D032A11BE48F9F711F800280D +:10FB500008BF2570F8F7F3FFF8F77CFEBDE87040AA +:10FB6000FEF7D6BEB748F9F703F8C8B9257017E015 +:10FB7000B448F8F7FDFF40B9257006E005F0F6FC43 +:10FB8000B048F8F7F5FF0028F6D0F8F7D8FFBDE841 +:10FB9000704000F02BB8AB48F8F7EAFF0028E5D03A +:10FBA000F8F7CDFF60680021643005F037FEBDE84E +:10FBB000704000F01BB870B5A24C06460D460129F6 +:10FBC00008D0606890F880203046BDE87040134649 +:10FBD00002F077BBF8F75CFA61680346304691F8AB +:10FBE00080202946BDE8704002F06BBB70B5F8F785 +:10FBF00085FFF8F764FFF8F72DFEF8F7D9FE914C72 +:10FC00000025606890F8520030B1F8F78DFFF8F7E2 +:10FC10007CF8606880F852506068022180F85010CB +:10FC2000A0F8815080F88350BDE87040002002F0B9 +:10FC3000FCBA70B5834D06460421A868FEF746F964 +:10FC4000044605F0C8FA002808BF70BD207800F00F +:10FC50003F00252814D2F8F761FA217811F0800FBF +:10FC60000CBF1E214FF49671B4F80120C2F30C02B0 +:10FC700012FB01F10A1AB2F5877F28BF814201D237 +:10FC8000002070BD68682188A0F88110A17880F8F4 +:10FC900083103046BDE8704001F0CCBE2DE9F04144 +:10FCA000684C0746606800F1810690F883004009BF +:10FCB00008BF012507D0012808BF022503D002286C +:10FCC00014BF00250825F8F78DFE307800F03F06B8 +:10FCD0003046F8F7DFFB606880F8736090F86C00DE +:10FCE00002280CBF4020FF202946F8F7AAFA27B1C6 +:10FCF00029460120F8F795FC05E060682A46C16DA9 +:10FD00000120F8F7E2FCF8F724FF0521A068FDF7D1 +:10FD1000F0FE6168002881F8520008BFBDE8F0815C +:10FD200015F00C0F0CBF50245524F7F7DAFF2046CE +:10FD3000BDE8F041F8F7EEBE2DE9F74F414C002544 +:10FD4000914660688A4690F8510000280CBF4FF039 +:10FD500001084FF00008A0680178CE090121FEF7E4 +:10FD6000B5F836B1407900F0C000402808BF012640 +:10FD700000D00026606890F85210002961D090F8F9 +:10FD800040104FF0000B032906D190F839100029DC +:10FD900018BF90F856700ED1A068C17811F03F0FCF +:10FDA0001CBF007910F0010F02D105F061F940B3DA +:10FDB000606890F85370FF2F18BF082F21D0384685 +:10FDC000FDF7D9FB002818BF4FF00108002E38D0EE +:10FDD000606890F8620030B1FDF7F1FD054660689B +:10FDE00080F862B02DE03846FDF791FD054601210F +:10FDF000A068FEF76BF801462846F9F7D3FB0546E5 +:10FE00001FE0F6B1606890F86100D0B9A068C178D1 +:10FE100011F03F0F05D0017911F0010F18BF0B2130 +:10FE200000D105210022FDF7A4FD616881F8520090 +:10FE300038B1FDF7B9FDFF2803D06168012581F8CD +:10FE4000530001E0740100208AF800500098067009 +:10FE500089F8008003B0BDE8F08F2DE9F04FFF4C2A +:10FE600087B00025606890F850002E46801F4FF044 +:10FE70007F08062880F0D581DFE800F00308088BB2 +:10FE8000FDDB00F0F8FB054600F0CCB9F348F8F7CD +:10FE90006FFE002808BF84F80080F8F750FEA068C5 +:10FEA000FDF782FF0546072861D1A068FEF75AF9E1 +:10FEB0000146606890F86C208A4258D190F8501042 +:10FEC000062908BF002005D090F8500008280CBF74 +:10FED0000220012005F08AF970B90321A068FDF71E +:10FEE000F5FF002843D001884078C1F30B010009D9 +:10FEF00005F07BFC00283AD000212846FFF7A1F945 +:10FF0000A0B38DF80C608DF808608DF8046062680D +:10FF1000FF2592F8500008280CBF02210121A0689B +:10FF2000C37813F03F0F1CBF007910F0020F12D0FE +:10FF300092F8800005F0D4F868B901AA03A902A8D4 +:10FF4000FFF7FAFE606890F831509DF80C00002829 +:10FF500018BF45F002052B469DF804209DF80810B7 +:10FF60009DF80C0000F0D5F9054603E0FFE705F029 +:10FF7000FDFA0225606890F85200002800F05281D6 +:10FF8000F8F7D2FDF7F7C1FE606880F8526000F024 +:10FF900049B9A068FDF708FF0646A1686068CA78FD +:10FFA00090F86D309A4221D10A7990F86E309A42D9 +:10FFB0001CD14A7990F86F309A4217D18A7990F81B +:10FFC00070309A4212D1CA7990F871309A420DD1AC +:10FFD0000A7A90F872309A4208D1097890F8740041 +:10FFE000C1F38011814208BF012500D00025F8F738 +:10FFF00031FC9A48F8F7BCFD002808BF84F800805F +:020000040002F8 +:10000000F8F79DFD042E11D185B120787F2808BF17 +:10001000FFDF94F9003084F80080606890F8732066 +:1000200090F87D1090F8540005F06EFB062500F066 +:10003000F9B802278948F8F79BFD002808BF84F823 +:100040000080F8F77CFDA068FDF7AEFE0546A068CD +:10005000FEF788F8082D08BF00287CD1A0684FF073 +:100060000301C27812F03F0F75D0007931EA000029 +:1000700071D1606800E095E000F1500890F8390017 +:10008000002814BF98F8066098F803604FF0000944 +:1000900098F8020078B1FDF787FC0546FF280AD0E2 +:1000A0000146A068401DFDF758FCB5420CBF4FF05B +:1000B00001094FF000090021A068FDF707FF0622A3 +:1000C00008F11D01EEF78CF940B9A068FDF795FE27 +:1000D00098F82410884208BF012000D0002059EA77 +:1000E00000095DD0606800F1320590F831A098F801 +:1000F000010038B13046FDF74BFD00281CBF054616 +:100100004FF0010A4FF00008A06801784FEAD11BB8 +:100110000121FDF7DBFEBBF1000F07D0407900F0B5 +:10012000C000402808BF4FF0010B01D04FF0000B7A +:100130000121A068FDF7CAFE06222946EEF750F914 +:1001400030B9A068FDF766FE504508BF012501D013 +:100150004FF0000500E023E03BEA050018BFFF2E4A +:100160000DD03046FDF7D3FB060008D00121A06872 +:10017000FDF7ACFE01463046F9F714FA804645EA31 +:10018000080019EA000F0BD060680121643005F007 +:1001900045FB01273846FFF737FA052002F045F8FE +:1001A0003D463FE002252D48F8F7E2FC002808BF55 +:1001B00084F80080F8F7C3FCA068FDF7F5FD06465B +:1001C000A068FDF7CFFF072E08BF00282AD1A0683E +:1001D0004FF00101C27812F03F0F23D00279914312 +:1001E00020D1616801F150060021FDF76FFE062263 +:1001F00006F11D01EEF7F4F8A0B9A068FDF7FDFDCA +:1002000096F8241088420DD160680121643005F011 +:1002100005FBFF21022000F009F8002818BF032584 +:1002200000E0FFDF07B02846BDE8F08F2DE9F0437E +:100230000A4C0F4601466068002683B090F87D2086 +:10024000002A35D090F8500008280CBF022501255F +:10025000A168C87810F03F0F02E000007401002090 +:10026000FD484FF000084FF07F0990F900001CBFD7 +:10027000097911F0100F22D07F2808BFFFDF94F911 +:10028000001084F80090606890F85420CDE90021B7 +:10029000029590F8733090F8802000F132013846D2 +:1002A00004F0C0FF05F0AAFA10B305F050F92CE0F5 +:1002B000002914BF0221012180F87D10C2E77F28A8 +:1002C00008BFFFDF94F9001084F80090606890F890 +:1002D0005420CDE90021029590F8733090F88020E9 +:1002E00000F13201384604F09DFF05F030F90CE0D2 +:1002F0000220FFF79EFC30B16068012680F86C8018 +:10030000F8F7A8FA01E005F031F903B03046BDE88E +:10031000F0832DE9F047D04C054684B09A46174645 +:100320000E46A068FDF71EFF4FF00109002800F0FF +:10033000CF804FF00208012808D0022800F00E817B +:1003400005F014F904B04046BDE8F087A068092123 +:10035000C27812F03F0F00F059810279914340F0CA +:100360005581616891F84010032906D012F0020F00 +:1003700008BFFF2118D05DB115E00021FDF7A6FDF3 +:1003800061680622C96B1A31EEF72AF848BB1EE0F5 +:10039000FDF740FD05460121A068FDF797FD2946C0 +:1003A000F7F7FFFF18B15146012000F051B960681E +:1003B00090F84100032818BF022840F02781002E42 +:1003C0001CBFFE21012040F0438100F01FB9A0684E +:1003D000FDF713FD6168C96B497E884208BF01269D +:1003E00000D00026A068C17811F03F0F05D0017938 +:1003F00011F0020F01D06DB338E0616891F842202E +:10040000012A01D096B11BE0D6B90021FDF75EFDAF +:1004100061680268C96BC1F81A208088C883A06827 +:10042000FDF7EBFC6168C96B487609E091F8530071 +:1004300091F85610884203D004B04046BDE8F087DA +:100440006068643005F02EFA002840D004B00F2018 +:10045000BDE8F08767B1FDF7DDFC05460121A06826 +:10046000FDF734FD2946F7F79CFF08B1012200E0B3 +:100470000022616891F84200012807D040B92EB9E6 +:1004800091F8533091F856108B4201D1012100E0D0 +:1004900000210A421BD0012808BF002E11D14FF0C5 +:1004A0000001A068FDF712FD61680268C96BC1F820 +:1004B0001A208088C883A068FDF79FFC6168C96B1B +:1004C00048766068643005F0EDF90028BED19DE003 +:1004D00060682F46554690F840104FF002080329F7 +:1004E000AAD0A168CA7812F03F0F1BBF097911F09A +:1004F000020F002201224FF0FF0A90F85010082945 +:100500000CBF0221012192B190F8800004F0E8FDB7 +:1005100068B95FB9A068FDF77DFC07460121A068B6 +:10052000FDF7D4FC3946F7F73CFF48B1AA465146DF +:100530000020FFF77BFE002818BF4FF003087BE781 +:10054000606890F84100032818BF02287FF474AF58 +:10055000002E18BF4FF0FE0AE9D16DE7616891F8EF +:100560004030032B52D0A0684FF0090CC27812F033 +:100570003F0F4BD002793CEA020C47D1022B06D048 +:1005800012F0020F08BFFF2161D0E5B35EE012F068 +:10059000020F4FF07F0801D04DB114E001F164006B +:1005A00005F080F980B320787F2842D013E067B34C +:1005B000FDF730FC05460121A068FDF787FC2946C0 +:1005C000F7F7EFFE08B36068643005F06BF9D8B157 +:1005D00020787F282DD094F9001084F8008060687E +:1005E00090F85420CDE90021CDF8089090F87330B0 +:1005F00090F8802000F13201504604F013FE0D20E7 +:1006000004B0BDE8F08716E000E001E00220F7E763 +:10061000606890F84100032818BF0228F6D1002E28 +:10062000F4D04FF0FE014FF00200FEF744F9022033 +:10063000E6E7FFDFCFE7FDF7EDFB05460121A06808 +:10064000FDF744FC2946F7F7ACFE38B151460220CD +:10065000FEF731F9DAE7000074010020606890F8D5 +:100660004100032818BF0228D0D1002E1CBFFE2154 +:100670000220EDD1CAE72DE9F84F4FF00008F74806 +:10068000F8F776FA7F27F54C002808BF2770F8F7AF +:1006900056FAA068FDF788FB81460121FEF7D1FDDF +:1006A000616891F88020012A14D0042A1CBF082A0E +:1006B000FFDF00F0D781606890F8520038B1F8F79A +:1006C00033FAF7F722FB6168002081F852004046B8 +:1006D000BDE8F88F0125E24EB9F1080F3AD2DFE804 +:1006E00009F03EC00439393914FC0546F8F7B2F870 +:1006F000002D72D0606890F84000012818BF0228D1 +:100700006BD120787F2869D122E018B391F840009E +:10071000022802D0012818D01CE020787F2808BFCA +:10072000FFDF94F90000277000906068FF2190F8C7 +:10073000733090F85420323004F02FFF61680020AD +:100740004FF00C0881F87D00B5E720787F2860D154 +:10075000FFDF5EE0F8F77EF84FF00608ABE74FF0FA +:100760000008002800F0508191F84000022836D09F +:1007700001284BD003289ED1A068CA6BC37892F899 +:100780001AC0634521D1037992F81BC063451CD17F +:10079000437992F81CC0634517D1837992F81DC044 +:1007A000634512D1C37992F81EC063450DD1037A17 +:1007B00092F81FC0634508D1037892F819C0C3F3BB +:1007C0008013634508BF012300D0002391F8421035 +:1007D00001292CD0C3B300F013B93FE019E0207811 +:1007E0007F2808BFFFDF94F9000027700090606841 +:1007F000FF2190F8733090F85420323004F0CDFE91 +:1008000060684FF00C0880F87D5054E720787F280E +:100810009ED094F90000277000906068FF2190F846 +:10082000733090F85420323004F0B7FE16E0002BFD +:100830007ED102F11A01FDF7C2FAA068FDF7DDFAD8 +:100840006168C96B4876DBE0FFE796F85600082838 +:1008500070D096F8531081426AD0D5E04FF0060868 +:1008600029E7054691F8510000280CBF4FF0010B15 +:100870004FF0000B4FF00008A06810F8092BD209C8 +:1008800007D0407900F0C000402808BF4FF0010AAF +:1008900001D04FF0000A91F84000032806D191F8EA +:1008A0003900002818BF91F8569001D191F8539063 +:1008B0004846FDF72CF80090D8B34846FCF75BFE9D +:1008C000002818BF4FF0010BBAF1000F37D0A06815 +:1008D000A14600F10901009800E0B6E0F8F762FED9 +:1008E0005FEA0008D9F8040090F8319018BF49F089 +:1008F0000209606890F84010032924D0F7F7AAFF96 +:10090000002DABD0F7F75DFD002808BFB8F1000F50 +:100910007DD020787F2808BFFFDF94F90000277082 +:1009200000906068494690F8733090F8542002E0D7 +:1009300066E004E068E0323004F02FFE8EE7606885 +:1009400090F83190D5E7A168C06BCA78837E9A424F +:100950001BD10A79C37E9A4217D14A79037F9A4202 +:1009600013D18A79437F9A420FD1CA79837F9A4201 +:100970000BD10A7AC37F9A4207D10978407EC1F32E +:100980008011814208BF012700D0002796F853004C +:10099000082806D096F85610884208BF4FF0010983 +:1009A00001D04FF00009B8F1000F05D1BBF1000FE5 +:1009B00004D0F7F706FD08B1012000E000204DB19A +:1009C00096F84210012903D021B957EA090101D054 +:1009D000012100E00021084216D0606890F8421022 +:1009E000012908BF002F0BD1C06B00F11A01A068CC +:1009F000FDF7E5F9A068FDF700FA6168C96B487674 +:100A00004FF00E0857E602E0F7F724FF26E760688C +:100A100090F84100032818BF02287FF41FAFBAF1F5 +:100A2000000F3FF41BAF20787F2808BFFFDF94F949 +:100A30000000277000906068FE2190F8733090F8F5 +:100A40005420323004F0A9FD08E791F8481000293D +:100A500018BF00283FF47EAE0BE0000074010020B8 +:100A600044110020B9F1070F7FF474AE00283FF461 +:100A700071AEFEF790FC80461DE60000D0F8001134 +:100A800049B1D0E941231A448B691A448A61D0E9FB +:100A90003F12D16003E0FE4AD0F8FC101162D0E9A9 +:100AA0003F1009B1086170470028FCD00021816126 +:100AB00070472DE9FF4F06460C46488883B040F248 +:100AC000E24148430190E08A002500FB01FA94F8D6 +:100AD0007C0090460D2822D00C2820D024281ED03F +:100AE00094F87D0024281AD000208346069818B177 +:100AF0000121204603F0C0F894F8641094F86500D2 +:100B0000009094F8F0200F464FF47A794AB1012A08 +:100B100061D0022A44D0032A5DD0FFDFB5E0012076 +:100B2000E3E7B8F1000F00D1FFDFD94814F8641FE4 +:100B3000243090F83400F0F7C4FC01902078F8F7E6 +:100B4000CFFB4D4600F2E730B0FBF5F1DFF8409304 +:100B5000D9F80C0001EB00082078F8F7C1FB01463A +:100B600014F86409022816D0012816D040F6340083 +:100B700008444AF2EF010844B0FBF5F10198D9F8B6 +:100B80001C20411A514402EB08000D18012084F882 +:100B9000F0002D1D78E02846EAE74FF4C860E7E74B +:100BA000DFF8EC92A8F10100D9F80810014300D158 +:100BB000FFDFB848B8F1000F016801EB0A0506D065 +:100BC000D9F8080000F22630A84200D9FFDF032040 +:100BD00084F8F00058E094F87C20019D242A05D088 +:100BE00094F87D30242B01D0252A3AD1B4F8702016 +:100BF000B4F81031D21A521C12B2002A31DB94F828 +:100C0000122172B3174694F8132102B110460090D6 +:100C1000022916D0012916D040F6340049F60852B0 +:100C20008118022F12D0012F12D040F63400104448 +:100C3000814210D9081A00F5FA70B0FBF9F00544AA +:100C40000FE04846EAE74FF4C860E7E74846EEE7BA +:100C50004FF4C860EBE7401A00F5FA70B0FBF9F00A +:100C60002D1AB8F1000F0FD0DFF82482D8F8080051 +:100C700018B9B8F8020000B1FFDFD8F8080000F298 +:100C80002630A84200D9FFDF05B9FFDF2946D4F896 +:100C9000F400F4F750FFC4F8F400B06000203070A6 +:100CA0004FF0010886F80480204603F040F8ABF1CD +:100CB0000101084202D186F8058005E094F8F000B1 +:100CC000012844D003207071606A3946009A01F00F +:100CD00042FBF060069830EA0B0035D029463046DA +:100CE000F0F7BAF987B2204603F021F8B8420FD8DE +:100CF000074686F8058005FB07F1D4F8F400F4F701 +:100D00001AFFB06029463046F0F7A6F9384487B29A +:100D10003946204602F0B0FFB068C4F8F400A06E77 +:100D2000002811D0B4F87000B4F89420801A01B2F1 +:100D3000002909DD34F86C0F0144491E91FBF0F1E4 +:100D400089B201FB0020208507B0BDE8F08F0220AA +:100D5000B9E72DE9F04106460C46012001F0DBFA27 +:100D6000C5B20B2001F0D7FAC0B2854200D0FFDF38 +:100D70000025082C7ED2DFE804F00461696965C6AD +:100D80008293304601F0DDFA0621F3F78FF8040074 +:100D900000D1FFDF304601F0D4FA2188884200D02C +:100DA000FFDF94F8F00000B9FFDF204602F00FFEED +:100DB000374E21460020B5607580F561FDF7E9FCEE +:100DC00000F19807606AB84217D994F86500F7F700 +:100DD0000BF9014694F864004FF47A72022828D087 +:100DE000012828D040F6340008444AF2473108442C +:100DF000B0FBF2F1606A0844C51B21460020356152 +:100E0000FDF7C7FC618840F2E24251439830081A6E +:100E1000A0F22630706194F8652094F86410606A3E +:100E200001F099FAA0F5CB70B061BDE8F041F5F79B +:100E300060BE1046D8E74FF4C860D5E7BDE8F04182 +:100E400002F02FBEBDE8F041F7F7D5BE304601F005 +:100E500078FA0621F3F72AF8040000D1FFDF3046C4 +:100E600001F06FFA2188884200D0FFDF01220021C3 +:100E7000204600E047E0BDE8F04101F089BAF7F70D +:100E800073FDF7F7B8FE02204FF0E02104E0000008 +:100E9000CC11002084010020C1F88002BDE8F0815F +:100EA000304601F04EFA0621F3F700F8040000D1B5 +:100EB000FFDF304601F045FA2188884200D0FFDF8D +:100EC00094F8F000042800D0FFDF84F8F05094F884 +:100ED000FA504FF6FF76202D00D3FFDFFA4820F8B6 +:100EE000156094F8FA00F5F720F900B9FFDF20202B +:100EF00084F8FA002046FFF7C1FDF4480078BDE809 +:100F0000F041E2F701B8FFDFC8E770B5EE4C00250D +:100F1000443C84F82850E07868B1E570FEF71EF98B +:100F20002078042803D0606AFFF7A8FD6562E748CF +:100F30000078E1F7E9FFBDE8704001F03ABA70B51A +:100F4000E14C0146443CE069F5F706FE6568A2788D +:100F500090FBF5F172B140F27122B5FBF2F292B260 +:100F6000A36B01FB02F6B34202D901FB123200E08F +:100F70000022A2634D43002800DAFFDF2946E06922 +:100F8000F4F7D9FDE06170BD2DE9F05FFEF736F9A9 +:100F90008246CD48683800F1240881684646D8F872 +:100FA0001800F4F7C8FD0146F069F5F7D5FD4FF0DC +:100FB0000009074686F835903C4640F28F254E469C +:100FC0001EE000BF0AEB06000079F7F70DF80146B6 +:100FD0004AF2B12001444FF47A70B1FBF0F008EB13 +:100FE0008602414692681044844207D3241A91F83D +:100FF0003500A4F28F24401C88F83500761CF6B228 +:1010000098F83600B042DDD8002C10DD98F8351085 +:10101000404608EB81018968A14208D24168C91B9A +:10102000B1F5247F00D30D466C4288F8359098F8CE +:101030003560C3460AEB060898F80400F6F7D4FFBB +:101040004AF2B12101444FF47A7AB1FBFAF298F8EE +:101050000410082909D0042909D0002013180429F4 +:101060000AD0082908D0252207E0082000E0022045 +:1010700000EB40002830F1E70F22521D4FF4A8701A +:10108000082914D0042915D0022916D04FF0080CD5 +:101090005FF0280012FB0C00184462190BEB86036A +:1010A00010449A68D84690420BD8791925E04FF041 +:1010B000400CEFE74FF0100CECE74FF0040C182059 +:1010C000E8E798F8352098F836604046B24210D2EA +:1010D000521C88F835203C1B986862198418084611 +:1010E000F6F782FF4AF2B1210144B1FBFAF001198F +:1010F00003E080F83590D8F80410D8F81C00BDE85B +:10110000F05FF4F718BD2DE9FE4F14460546FEF7D3 +:1011100075F8DFF8B4A10290AAF1440A50469AF893 +:1011200035604FF0000B0AEB86018968CAF83C1065 +:10113000F4B3044600780027042825D005283ED0C3 +:10114000FFDFA04639466069F4F7F5FC0746F5F77E +:101150000BF881463946D8F80440F5F7FDFC401EEF +:1011600090FBF4F0C14361433846F4F7E4FC0146D8 +:10117000C8F81C004846F5F7EFFC002800DDFFDF4B +:10118000012188F813108DE0D4F81490D4F804806D +:1011900001F07AF9070010D0387800B9FFDF7969DB +:1011A00078684A460844414601F05AF9074600E08B +:1011B0000BE04045C5D9FFDFC3E75F46C1E7606A82 +:1011C00001F004F940F6B837BBE7C1690AEB460005 +:1011D0000191408D10B35446DAF81400FFF7AFFECA +:1011E0006168E069F4F7A7FC074684F835B0019C14 +:1011F000D0462046DAF81410F5F7AEFC81463946A1 +:101200002046F5F7A9FCD8F804200146B9FBF2F016 +:10121000B1FBF2F1884242D0012041E0F4F7A4FF93 +:10122000FFF78DFEFFF7B0FE9AF83510DAF804905C +:101230000AEB81010746896800913946DAF81C00FB +:10124000F5F78AFC00248046484504DB98FBF9F456 +:1012500004FB09F41AE0002052469AF8351007E022 +:1012600002EB800304F28F249B68401C1C44C0B234 +:101270008142F5D851B10120F6F7B6FE4AF2B1210C +:1012800001444FF47A70B1FBF0F004440099A8EBEC +:1012900004000C1A00D5FFDFCAF83C40A7E7002085 +:1012A00088F813009AF802005446B8B13946E0694C +:1012B000F5F752FC0146A26B40F2712042438A428C +:1012C00006D2C4F83CB009E03412002080010020AE +:1012D000E06B511A884200D30846E063AF6085F89E +:1012E00000B001202871029F94F835003F1DC05DB9 +:1012F000F6F77AFE4AF23B5101444FF47A70B1FBA3 +:10130000F0F0E16BFE300844E8602078042808D152 +:1013100094F8350004EB4000408D0A2801D20320E8 +:1013200000E00220687104EB4600408DC0B1284601 +:101330006168EFF791FE82B20020761C0CE000BFDE +:1013400004EB4001B0424B8D13449BB24B8501D35B +:101350005B1C4B85401CC0B294F836108142EFD222 +:10136000A8686061A06194F8350004EB4001488DE5 +:10137000401C488594F83500C05D082803D0042837 +:1013800003D000210BE0082100E0022101EB410124 +:1013900028314FF4A872082804D0042802D002286B +:1013A00007D028220A44042805D0082803D0252184 +:1013B00002E01822F6E70F21491D08280CD0042866 +:1013C0000CD002280CD0082011FB0020E16B8842D1 +:1013D00008D20120BDE8FE8F4020F5E71020F3E79A +:1013E0000420F1E70020F5E770B5FE4C061D14F867 +:1013F000352F905DF6F7F8FD4FF47A7100F2E73083 +:10140000B0FBF1F0D4F8071045182078805DF6F7AE +:1014100073FE2178895D082903D0042903D00022B6 +:101420000BE0082200E0022202EB420228324FF4D5 +:10143000A873082904D0042902D0022907D0282340 +:101440001344042905D0082903D0252202E01823DB +:10145000F6E70F22521D08290AD004290AD00229D2 +:101460000AD0082112FB0131081A281A293070BD50 +:101470004021F7E71021F5E70421F3E72DE9FF41CB +:1014800007460C46012000F046FFC5B20B2000F0D5 +:1014900042FFC0B2854200D0FFDF20460126002572 +:1014A000D04C082869D2DFE800F004304646426894 +:1014B0006865667426746078002819D1FDF79EFE71 +:1014C000009594F835108DF808104188C90411D0A2 +:1014D000206C019003208DF80900C24824388560F3 +:1014E000C56125746846FDF768FB002800D0FFDF62 +:1014F000BDE8FF81FFF778FF0190E07C10B18DF827 +:101500000950EAE78DF80960E7E7607840B1207C90 +:1015100008B9FDF7F9FD6574BDE8FF41F4F72FBD8B +:10152000A674FDF739FC0028E2D0FFDFE0E7BDE854 +:10153000FF41F7F760BBFDF761FE4088C00407D0AC +:1015400001210320FDF75EFEA7480078E1F7DCFCEF +:10155000002239466846FFF7D6FD38B1694638465D +:1015600000F0EDFE0028C3D1FFDFC1E7E670FFF712 +:10157000CCFCBDE7BDE8FF41C7E4FFDFB8E7994910 +:1015800050B101228A704A6840F27123B2FBF3F233 +:1015900002EB0010886370470020887070472DE9C7 +:1015A000F05F894640F271218E4E48430025044683 +:1015B000706090462F46D0074AF2B12A4FF47A7BEA +:1015C0000FD0B9F800004843B0600120F6F70CFDD9 +:1015D00000EB0A01B1FBFBF0241AB7680125A4F265 +:1015E0008F245FEA087016D539F8151040F2712083 +:1015F000414306EB85080820C8F80810F6F7F4FC0C +:1016000000EB0A01B1FBFBF0241AD8F80800A4F2A1 +:101610008F2407446D1CA74219D9002D17D0391B00 +:10162000B1FBF5F0B268101AB1FBF5F205FB12122E +:10163000801AB060012008E0B1FBF5F306EB8002F0 +:101640009468E31A401CC0B29360A842F4D3BDE88A +:10165000F09F2DE9F041634C00262078042804D047 +:101660002078052801D00C2018E401206070607CEF +:10167000002538B1EFF3108010F0010F72B610D0D2 +:1016800001270FE0FDF7BAFD074694F82000F5F7B3 +:10169000B2F87888C00411D000210320FDF7B2FD14 +:1016A0000CE00027607C38B1A07C28B1FDF72CFD50 +:1016B0006574A574F4F763FC07B962B694F820006A +:1016C000F5F705FB94F8280030B184F8285020780D +:1016D000052800D0FFDF0C26657000F06AFE30465A +:1016E00012E4404810B5007808B1FFF7B2FF00F0EF +:1016F000D4FE3C4900202439086210BD10B53A4C94 +:1017000058B1012807D0FFDFA06841F66A0188427E +:1017100000D3FFDF10BD40F6C410A060F4E73249EB +:1017200008B508702F4900200870487081F828001B +:10173000C8700874487488742022486281F8202098 +:10174000243948704FF6FF7211F1680121F810201A +:10175000401CC0B22028F9D30020FFF7CFFFFFF7CD +:10176000C0FF1020ADF80000012269460420FFF7F9 +:1017700016FF08BD7FB51B4C05460E46207810B1FC +:101780000C2004B070BD95F8652095F86410686A67 +:1017900000F0C5FEC5F80401656295F8F00000B1DF +:1017A000FFDF104900202439C861052121706070D5 +:1017B00084F82800014604E004EB4102491C5085EE +:1017C000C9B294F836208A42F6D284F83500304601 +:1017D000FFF7D5FE0548F4F74DFC84F820002028DB +:1017E00007D105E0F0110020800100207D140200E7 +:1017F000FFDFF4F7B9FC606194F82010012268461D +:10180000FFF781FC00B9FFDF94F82000694600F083 +:1018100096FD00B9FFDF0020B3E7F94810B5007866 +:1018200008B1002010BD0620F2F7DAFA80F00100BE +:1018300010BDF8B5F24D0446287800B1FFDF002056 +:10184000009023780246DE0701466B4605D060888B +:10185000A188ADF80010012211462678760706D53A +:10186000E088248923F8114042F00802491C491EEF +:1018700085F836101946FFF792FE0020F8BD1FB517 +:1018800011B1112004B010BDDD4C217809B10C203C +:10189000F8E70022627004212170114605E000BFC4 +:1018A00004EB4103491C5A85C9B294F836308B4287 +:1018B000F6D284F83520FFF762FED248F4F7DAFB5F +:1018C00084F82000202800D1FFDF00F0DDFD10B1FA +:1018D000F4F74AFC05E0F4F747FC40F6B831F4F7BA +:1018E0002AF9606194F8201001226846FFF70BFC8A +:1018F00000B9FFDF94F82000694600F020FD00B930 +:10190000FFDF0020BEE770B5BD4C616A0160FFF7E4 +:10191000A0FE050002D1606AFFF7B0F80020606207 +:10192000284670BD7FB5B64C2178052901D00C2022 +:1019300027E7B3492439C860606A00B9FFDF606AED +:1019400090F8F00000B1FFDF606A90F8FA002028FC +:1019500000D0FFDFAC48F4F78DFB616A0546202814 +:1019600081F8FA000E8800D3FFDFA548443020F844 +:101970001560606A90F8FA00202800D1FFDF00238C +:1019800001226846616AFFF794F8606A694690F838 +:10199000FA0000F0D4FC00B9FFDF00206062F0E63E +:1019A000974924394870704710B540F2E24300FB74 +:1019B00003F4002000F0B3FD844201D9201A10BDC9 +:1019C000002010BD70B50D46064601460020FCF70C +:1019D000E0FE044696F86500F6F706FB014696F829 +:1019E00064004FF47A72022815D0012815D040F611 +:1019F000340008444AF247310844B0FBF2F17088E1 +:101A000040F271225043C1EB4000A0F22630A542C3 +:101A100006D2214605E01046EBE74FF4C860E8E740 +:101A20002946814204D2A54201D2204600E0284640 +:101A3000706270BD70B50546FDF7E0FB7049007837 +:101A400024398C689834072D30D2DFE805F004344F +:101A500034252C34340014214FF4A873042810D0FA +:101A60000822082809D02A2102280FD011FB0240A1 +:101A700000222823D118441819E0402211FB02400B +:101A8000F8E7102211FB02402E22F3E7042211FB9B +:101A9000024000221823EDE7282100F04BFC04440B +:101AA00004F5317403E004F5B07400E0FFDF54483E +:101AB000C06BA04201D9012070BD002070BD70B57F +:101AC0004F4C243C607870B1D4E904512846A26898 +:101AD000EFF7EDFA2061A84205D0A169401B084448 +:101AE000A061F5F706F82169A068884201D820783E +:101AF00008B1002070BD012070BD2DE9F04F0546F2 +:101B000085B016460F461C461846F6F7F5FA05EB63 +:101B100047014718204600F0F5FB4AF2C5714FF423 +:101B20007A7908444D46B0FBF5F0384400F160087E +:101B30003348761C24388068304404902046F6F7F9 +:101B4000DBFAA8EB0007204600F0DCFB0646204647 +:101B5000F6F74AFA301AB0FBF5F03A1A18252820A1 +:101B60004FF4C8764FF4BF774FF0020B082C30D0FB +:101B7000042C2BD00021022C2ED0082311F1280197 +:101B800003EB830C0CEB831319440A444FF0000A57 +:101B9000082C29D0042C22D00021022C29D0054663 +:101BA000082001F5B07100BF00EB0010284481420D +:101BB00032D2082C2AD0042C1ED00020022C28D08F +:101BC0000821283001EB0111084434E03946102384 +:101BD000D6E731464023D3E704231831D0E73D460A +:101BE00040F2EE311020DFE735464FF435614020FA +:101BF000DAE70420B431D7E738461021E2E70000E5 +:101C0000F01100207D140200530D020030464021E7 +:101C1000D8E704211830D5E7082C4FD0042C4AD03F +:101C20000021022C4DD0082311F12801C3EBC30081 +:101C300000EB4310084415182821204600F07AFBD9 +:101C400005EB4001082C42D0042C3DD00026022C8C +:101C50003FD0082016F1280600EB801006EB80002C +:101C60000E180120FA4D8DF804008DF800A08DF8B3 +:101C700005B0A86906F22A260499F3F75CFFCDE9BE +:101C800002062046F6F7B0F94AF23B510144B1FB97 +:101C9000F9F0301AFE38E8630298C5F84080A86170 +:101CA00095F82000694600F04AFB002800D1FFDFCC +:101CB00005B0BDE8F08F39461023B7E73146402321 +:101CC000B4E704231831B1E73E461020C4E74020B2 +:101CD000C2E704201836BFE72DE9FE4F06461C4632 +:101CE000174688464FF0010A1846F6F705FAD84D10 +:101CF000243DA9688A1907EB48011144471820467A +:101D000000F000FB4FF47A7BD84600F6FB00B0FBF6 +:101D1000F8F0384400F120092046F6F7EDF9A968FB +:101D20000246A9EB0100801B871A204600F0EAFA60 +:101D300005462046F6F758F9281AB0FBF8F03A1A8B +:101D4000182528204FF4C8774FF4BF78082C2DD0E1 +:101D5000042C28D00021022C2BD0082311F12801BB +:101D600003EB830C0CEB831319440A44082C28D092 +:101D7000042C21D00021022C28D00546082001F592 +:101D8000B07100BF00EB0010284481422AD2082C19 +:101D900022D0042C1DD00020022C20D00821283075 +:101DA00001EB01112CE041461023D9E739464023CD +:101DB000D6E704231831D3E7454640F2EE31102030 +:101DC000E0E73D464FF435614020DBE70420B431C5 +:101DD000D8E740461021E3E738464021E0E70421F8 +:101DE0001830DDE7082C48D0042C43D00020022C0A +:101DF00046D0082110F12800C1EBC10303EB4111CB +:101E0000084415182821204600F094FA05EB4001FB +:101E1000082C3BD0042C36D00027022C38D00820C8 +:101E200017F1280700EB801007EB80000C1804F571 +:101E300096740C98F6F7D8F84AF23B510144B1FB7E +:101E4000FBF0834DFE30A5F12407E96B06F1FE029D +:101E50000844B9680B191A44824224D93219114432 +:101E60000C1AFE342044B0F1807F37D2642C12D299 +:101E7000642011E040461021BEE738464021BBE710 +:101E800004211830B8E747461020CBE74020C9E7C7 +:101E900004201837C6E720460421F4F790FEE8B185 +:101EA000E86B2044E863E0F703FFB9682938314460 +:101EB0000844CDE9000995F835008DF808000220A6 +:101EC0008DF809006846FCF778FE00B1FFDFFCF7EB +:101ED00063FF00B1FFDF5046BDE8FE8F4FF0000A00 +:101EE000F9E71FB500F021FB594C607880B994F8F0 +:101EF000201000226846FFF706F938B194F8200058 +:101F0000694600F01CFA18B9FFDF01E00120E0701B +:101F1000F4F735F800206074A0741FBD2DE9F84F68 +:101F2000FDF76CF90646451CC07840090CD0012825 +:101F30000CD002280CD000202978824608064FF4E5 +:101F4000967407D41E2006E00120F5E70220F3E78F +:101F50000820F1E72046B5F80120C2F30C0212FB7D +:101F600000F7C80901D010B103E01E2401E0FFDF33 +:101F70000024F6F7D3F8A7EB00092878B77909EB26 +:101F80000408C0F3801010B120B1322504E04FF4F2 +:101F9000FA7501E0FFDF00250C2F00D3FFDF2D488D +:101FA0002D4A30F81700291801FB0821501CB1FBFD +:101FB000F0F5F6F76DF8F6F717F84FF47A7100F2CE +:101FC0007160B0FBF1F1A9EB0100471BA7F15900CB +:101FD000103FB0F5247F11D31D4E717829B9024608 +:101FE000534629462046FFF788FD00F09EFAF3F796 +:101FF000C6FF00207074B074BDE8F88F3078009090 +:102000005346224629463846FFF766FE0028F3D19C +:1020100001210220FDF7F6F8BDE8F84F61E710B5A1 +:102020000446012903D10A482438007830B104203D +:1020300084F8F000BDE81040F3F7A1BF00220121B1 +:10204000204600F0A5F934F8700F401C2080F1E71D +:10205000F0110020646302003F420F002DE9F041BF +:102060000746FDF7CBF8050000D1FFDF287810F018 +:102070000C0F01D0012100E00021F74C606A3030E4 +:10208000FCF7C7FA29783846EFF71BFAA4F12406C3 +:102090000146A069B26802446FB32878082803D0CB +:1020A000042803D000230BE0082300E0022303EB05 +:1020B000430328334FF4A877082804D0042802D01B +:1020C000022810D028273B4408280ED004280ED020 +:1020D00002280ED05FF00800C0EBC00707EB4010ED +:1020E0001844983009E01827EDE74020F4E7102065 +:1020F000F2E70420F0E74FF4FC701044471828780A +:102100003F1DF5F771FF014628784FF47A720228D7 +:102110001DD001281DD040F6340008444AF2EF01DA +:102120000844B0FBF2F03A1A606A40F2E241B0466D +:102130004788F0304F43316A81420DD03946206BD9 +:1021400000F08EF90646B84207D9FFDF05E01046D9 +:10215000E3E74FF4C860E0E70026C04880688642A5 +:1021600007D2616A40F271224888424306EB420678 +:1021700004E040F2E240B6FBF0F0616AC882606AB7 +:10218000297880F86410297880F865100521417558 +:10219000C08A6FF41C71484306EB400040F635419D +:1021A000C8F81C00B0EB410F00D3FFDFBDE8F081A1 +:1021B00010B5052937D2DFE801F00509030D31001C +:1021C000002100E00121BDE8104028E7032180F84C +:1021D000F01010BD0446408840F2E24148439F4958 +:1021E000091D0860D4F818010089E082D4F81801AC +:1021F00080796075D4F8180140896080D4F818019E +:102200008089A080D4F81801C089E0802046A16AA6 +:10221000FFF7D8FB022084F8F00010BD816ABDE80A +:102220001040FFF7CFBBFFDF10BD70B58A4C243CD8 +:102230000928A1683FD2DFE800F0050B0B15131544 +:1022400038380800BDE870404BE6BDE8704065E6F0 +:10225000022803D00020BDE87040FFE60120FAE725 +:10226000E16070BD032802D005281CD000E0E160C9 +:102270005FF0000600F059F9774D012085F828003D +:1022800085F83460686AA9690026C0F8F41080F8FF +:10229000F060E068FFF746FB00B1FFDFF3F76FFE89 +:1022A0006E74AE7470BD0126E4E76C480078BDE83A +:1022B0007040E0F729BEFFDF70BD674924394860F0 +:1022C000704770B5644D0446243DB1B14FF47A7641 +:1022D000012903D0022905D0FFDF70BD1846F5F7AC +:1022E000FCFE05E06888401C68801046F6F7F8FFA1 +:1022F00000F2E730B0FBF6F0201AA86070BD564837 +:1023000000787047082803D0042801D0F5F76CBE88 +:102310004EF628307047002804DB00F1E02090F8EA +:10232000000405E000F00F0000F1E02090F8140D2B +:102330004009704710F00C0000D008467047F4F7D1 +:102340003EB910B50446202800D3FFDF4248443090 +:1023500030F8140010BD70B505460C461046F5F770 +:1023600043FE4FF47A71022C0DD0012C0DD040F6B3 +:10237000340210444AF247321044B0FBF1F02844D2 +:1023800000F5CB7070BD0A46F3E74FF4C862F0E782 +:102390001FB513460A46044601466846FEF789FB08 +:1023A00094F8FA006946FFF7CAFF002800D1FFDF62 +:1023B0001FBD70B5284C0025257094F82000F3F758 +:1023C000B4FE00B9FFDF84F8205070BD2DE9F04164 +:1023D000050000D1FFDF204A0024243AD5F804612B +:1023E0002046631E116A08E08869B04203D3984210 +:1023F00001D203460C460846C9680029F4D104B945 +:1024000004460021C5F80041F035C4B1E068E5603C +:10241000E86000B105612E698846A96156B1B069CE +:1024200030B16F69B84200D2FFDFB069C01BA8614C +:10243000C6F81880084D5CB1207820B902E0E96048 +:102440001562E8E7FFDF6169606808442863ADE66C +:10245000C5F83080AAE60000F011002080010020BD +:1024600010B50C4601461046F4F776FB002806DA54 +:10247000211A491EB1FBF4F101FB040010BD90FBD1 +:10248000F4F101FB140010BD2E48016A002001E0A8 +:102490000846C9680029FBD170472DE9FE43294D44 +:1024A0000120287000264FF6FF7420E00621F1F786 +:1024B000FDFC070000D1FFDF97F8FA00F037F4F7D2 +:1024C00006FC07F80A6BA14617F8FA89B8F1200F45 +:1024D00000D3FFDF1B4A683222F8189097F8FA0001 +:1024E000F3F723FE00B9FFDF202087F8FA006946E2 +:1024F0000620F1F764FC50B1FFDF08E0029830B12C +:1025000090F8F01019B10088A042CFD104E06846DD +:10251000F1F733FC0028F1D02E70BDE8FE8310B532 +:10252000FFF719FF00F5C87010BD064800212430E0 +:1025300090F8352000EB4200418503480078E0F731 +:10254000E3BC0000CC11002080010020012804D051 +:10255000022805D0032808D105E0012907D004E0AE +:10256000022904D001E0042901D000207047012095 +:102570007047F748806890F8A21029B1B0F89E1013 +:10258000B0F8A020914215D290F8A61029B1B0F869 +:10259000A410B0F8A02091420CD2B0F89C20B0F862 +:1025A0009A108A4206D290F88020B0F898001AB1AA +:1025B000884203D3012070470628FBD200207047D1 +:1025C0002DE9F041E24D0746A86800F1700490F84B +:1025D000140130B9E27B002301212046EEF7D2FC42 +:1025E00010B1A08D401CA08501263D21AFB92878EF +:1025F000022808D001280AD06878C8B110F0140F5A +:1026000009D01E2039E0162037E026773EE0A86882 +:1026100090F8160131E0020701D56177F5E78107EF +:1026200001D02A2029E0800600D4FFDF232024E007 +:1026300094F8320028B1E08D411CE185218E88425A +:1026400013D294F8360028B1A08E411CA186218EA9 +:1026500088420AD2A18D608D814203D3AA6892F884 +:10266000142112B9228E914201D3222005E0217C4F +:1026700029B1218D814207D308206077C5E7208DDD +:10268000062801D33E20F8E7207FB0B10020207358 +:10269000607320740221A868FFF78AFDA86890F88B +:1026A000E410012904D1D0F81C110878401E0870EC +:1026B000E878BDE8F041E0F727BCA868BDE8F04144 +:1026C0000021FFF775BDA2490C28896881F8E40054 +:1026D00014D0132812D0182810D0002211280ED0A0 +:1026E00007280BD015280AD0012807D0002805D0CC +:1026F000022803D021F89E2F012008717047A1F80D +:10270000A420704710B5924CA1680A88A1F86021F6 +:1027100081F85E0191F8640001F046FBA16881F840 +:10272000620191F8650001F03FFBA16881F8630147 +:10273000012081F85C01002081F82E01E078BDE8DD +:102740001040E0F7E1BB70B5814C00231946A0684A +:1027500090F87C207030EEF715FC00283DD0A06882 +:1027600090F820110025C9B3A1690978B1BB90F890 +:102770007D00EEF7EFFB88BBA168B1F870000A2876 +:102780002DD905220831E069EBF72AFE10B3A068C5 +:10279000D0F81C11087858B10522491CE069EBF704 +:1027A0001FFE002819D1A068D0F81C01007840B99C +:1027B000A068E169D0F81C010A68C0F80120097915 +:1027C0004171A068D0F81C110878401C08700120E5 +:1027D000FFF779FFA06880F8205170BDFFE7A0687F +:1027E00090F8241111B190F82511C1B390F82E1171 +:1027F0000029F2D090F82F110029EED190F87D0039 +:10280000EEF7A8FB0028E8D1A06890F8640001F07A +:10281000CBFA0646A06890F8650001F0C5FA0546B7 +:10282000A06890F830113046FFF790FEA0B3A06882 +:1028300090F831112846FFF789FE68B3A268B2F814 +:10284000703092F86410B2F8320102F58872EEF737 +:1028500001FE20B3A168252081F87C00BDE7FFE7D9 +:1028600090F87D10242918D090F87C10242914D0D9 +:102870005FF0000300F5897200F59271FBF78EFEA0 +:10288000A16881F8245101F13000C28A21F8E62FB5 +:10289000408B4880142007E005E00123EAE7BDE80B +:1028A000704000202EE71620BDE870400BE710B501 +:1028B000F4F7FAFD0C2813D3254C0821A068D0F8B2 +:1028C00018011E30F4F7F4FD28B1A0680421D830B7 +:1028D000F4F7EEFD00B9FFDFBDE810400320F2E69B +:1028E00010BD10B51A4CA068D0F818110A78002A4B +:1028F0001FD04988028891421BD190F87C20002388 +:1029000019467030EEF73EFB002812D0A068D0F8D0 +:1029100018110978022907D003290BD0042919D0EE +:10292000052906D108200DE090F87D00EEF712FB96 +:1029300040B110BD90F8811039B190F8820000B913 +:10294000FFDF0A20BDE81040BDE6BDE81040AEE75D +:102950008C01002090F8AA008007EAD10C20FFF734 +:10296000B2FEA068002120F89E1F01210171017BA9 +:1029700041F00101017310BD70B5F74CA268556EAE +:10298000EEF702FDEBB2C1B200228B4203D0A36886 +:1029900083F8121102E0A16881F81221C5F3072122 +:1029A000C0F30720814203D0A16881F8130114E726 +:1029B000A06880F8132110E710B5E74C0421A06847 +:1029C000FFF7F6FBA06890F85A10012908D000F52F +:1029D0009E71FBF7ACFEE078BDE81040E0F794BADA +:1029E000022180F85A1010BD70B5DB4CA06890F839 +:1029F000E410FE2955D16178002952D190F87F204A +:102A0000002301217030EEF7BDFA002849D1A068FB +:102A100090F8141109B1022037E090F87C200023CF +:102A200019467030EEF7AEFA28B1A06890F896001B +:102A300008B1122029E0A068002590F87C20122A15 +:102A40001DD004DC032A23D0112A04D119E0182A4E +:102A50001AD0232A26D0002304217030EEF792FAF0 +:102A600000281ED1A06890F87D10192971D020DCB3 +:102A700001292AD0022935D0032932D120E00B20A8 +:102A800003E0BDE8704012E70620BDE870401AE69A +:102A900010F8E21F01710720FFF715FEA06880F80B +:102AA0007C509AE61820FFF70EFEA068A0F89E5012 +:102AB00093E61D2918D01E2916D0212966D149E098 +:102AC00010F8E11F4171072070E00C20FFF7FBFDBB +:102AD000A06820F8A45F817941F00101817100F8BC +:102AE000275C53E013202CE090F8252182BB90F85E +:102AF0002421B2B1242912D090F87C1024290ED0C0 +:102B00005FF0000300F5897200F59271FBF746FD56 +:102B1000A0681E2180F87D1080F8245103E0012375 +:102B2000F0E71E2932D1A068FBF797FDFFF744FFBD +:102B3000A16801F13000C28A21F8E62F408B48805D +:102B40001520FFF7C0FDA068A0F8A45080F87D50C4 +:102B50001CE02AE090F8971051B180F8125180F8EB +:102B600013511820FFF7AFFDA068A0F8A4500DE0A6 +:102B700090F82F1151B990F82E1139B1C16DD0F8DC +:102B80003001FFF7F9FE1820FFF79DFDA06890F8CF +:102B9000E400FE2885D1FFF7A4FEA06890F8E400C9 +:102BA000FE2885D1BDE87040CDE51120FFF78BFDF3 +:102BB000A068CBE7684A0129926819D0002302294E +:102BC0000FD003291ED010B301282BD0032807D122 +:102BD00092F87C00132803D0162801D0182804D1BD +:102BE000704792F8E4000028FAD0D2F8180117E0F4 +:102BF00092F8E4000128F3D0D2F81C110878401EA6 +:102C00000870704792F8E4000328EED17047D2F8BC +:102C10001801B2F870108288891A09B20029F5DB10 +:102C200003707047B2F87000B2F82211401A00B277 +:102C30000028F6DBD2F81C010178491E01707047AC +:102C400070B5044690F87C0000250C2810D00D28A3 +:102C50002ED1D4F81811B4F870008988401C88422D +:102C600026D1D4F864013C4E017811B3FFDF42E075 +:102C7000B4F87000B4F82211401C884218D1D4F87E +:102C80001C01D0F80110A160407920730321204677 +:102C9000EDF7F1FDD4F81C01007800B9FFDF012148 +:102CA000FE20FFF787FF84F87C50012084F8B200F3 +:102CB00093E52188C180D4F81801D4F864114089C3 +:102CC0000881D4F81801D4F8641180894881D4F8B7 +:102CD0001801D4F86411C0898881D4F864010571A1 +:102CE000D4F8641109200870D4F864112088488051 +:102CF000F078E0F709F902212046EDF7BCFD032149 +:102D00002046FFF755FAB068D0F81801007802287D +:102D100000D0FFDF0221FE20FFF74CFF84F87C503B +:102D20005BE52DE9F0410C4C00260327D4F808C0E0 +:102D3000012598B12069C0788CF8E20005FA00F00E +:102D4000C0F3C05000B9FFDFA06800F87C7F468464 +:102D500080F82650BDE8F0818C01002000239CF80B +:102D60007D2019460CF17000EEF70CF970B1607817 +:102D70000028EFD12069C178A06880F8E11080F8C0 +:102D80007D70A0F8A46080F8A650E3E76570E1E7E5 +:102D9000F0B5F74C002385B0A068194690F87D2067 +:102DA0007030EEF7EFF8012580B1A06890F87C0054 +:102DB00023280ED024280CD06846F6F785FB68B18E +:102DC000009801A9C0788DF8040008E0657005B08E +:102DD000F0BD607840F020006070F8E70021A06846 +:102DE00003AB162290F87C00EEF74FFB002648B1AB +:102DF000A0689DF80C20162180F80C2180F80D1198 +:102E0000192136E02069FBF722FB78B121690879A6 +:102E100000F00702A06880F85C20497901F0070102 +:102E200080F85D1090F82F310BBB03E00020FFF716 +:102E300078FFCCE790F82E31CBB900F164035F78CE +:102E4000974205D11A788A4202D180F897500EE055 +:102E500000F5AC71028821F8022990F85C200A7113 +:102E600090F85D0048710D70E078E0F74DF8A068CB +:102E7000212180F87D1080F8A650A0F8A460A6E774 +:102E8000F8B5BB4C00231946A06890F87D2070303F +:102E9000EEF778F840B32069FBF7BEFA48B3206933 +:102EA000FBF7B4FA07462069FBF7B4FA0646206937 +:102EB000FBF7AAFA05462069FBF7AAFA0146009734 +:102EC000A06833462A463030FBF79BFBA1680125FA +:102ED00091F87C001C2810D091F85A00012812D0DB +:102EE00091F8250178B90BE0607840F0010060703E +:102EF000F8BDBDE8F840002013E781F85A5002E021 +:102F000091F8240118B11E2081F87D000BE01D20EE +:102F100081F87D0001F5A57231F8300BFBF7FBFB62 +:102F2000E078DFF7F1FFA068002120F8A41F85708A +:102F3000F8BD10B58E4C00230921A06890F87C20C4 +:102F40007030EEF71FF848B16078002805D1A1680D +:102F500001F8960F087301F81A0C10BD012060707B +:102F600010BD7CB5824C00230721A06890F87C201E +:102F70007030EEF707F838B36078002826D169463C +:102F80002069FBF75FFA9DF80000002500F025019D +:102F9000A06880F8B0109DF8011001F0490180F898 +:102FA000B11080F8A250D0F81811008849888142E9 +:102FB00000D0FFDFA068D0F818110D70D0F86411B0 +:102FC0000A7822B1FFDF16E0012060707CBD30F886 +:102FD000E82BCA80C16F0D71C16F009A8A60019A97 +:102FE000CA60C26F0821117030F8E81CC06F4180C0 +:102FF000E078DFF789FFA06880F87C507CBD70B571 +:103000005B4C00231946A06890F87D207030EDF7E6 +:10301000B9FF012540B9A0680023082190F87C2061 +:103020007030EDF7AFFF10B36078002820D1A068B2 +:1030300090F8AA00800712D42069FBF7C9F9A168AB +:1030400081F8AB00206930F8052FA1F8AC2040884A +:10305000A1F8AE0011F8AA0F40F002000870A068B5 +:103060004FF0000690F8AA10C90702D011E0657071 +:103070009DE490F87D20002319467030EDF782FF23 +:1030800000B9FFDFA06880F87D5080F8A650A0F856 +:10309000A460A06890F87C10012906D180F87C60BB +:1030A00080F8A260E078DFF72FFFA168D1F818015F +:1030B000098842888A42DBD101780429D8D1067078 +:1030C000E078DFF721FFA06890F87C100029CFD1CD +:1030D00080F8A2606BE470B5254DA86890F87C106C +:1030E0001A2902D00220687061E469780029FBD1B6 +:1030F000002480F8A74080F8A240D0F8181100887A +:103100004988814200D0FFDFA868D0F818110C7000 +:10311000D0F864110A780AB1FFDF25E090F8A82002 +:1031200072B180F8A8400288CA80D0F864110C718E +:10313000D0F864210E2111700188D0F864010DE0EF +:1031400030F8E82BCA80C16F0C71C26F0121117277 +:10315000C26F0D21117030F8E81CC06F418000F083 +:10316000A1FEE878DFF7D0FEA86880F87C401EE476 +:103170008C01002070B5FA4CA16891F87C20162AC9 +:1031800001D0132A02D191F8A82012B10220607058 +:103190000DE46278002AFBD181F8E000002581F877 +:1031A000A75081F8A250D1F81801098840888842B8 +:1031B00000D0FFDFA068D0F818010078032800D005 +:1031C000FFDF0321FE20FFF7F5FCA068D0F86411B3 +:1031D0000A780AB1FFDF14E030F8E02BCA8010F85B +:1031E000081BC26F1171C16F0D72C26F0D2111707A +:1031F00030F8E81CC06F418000F054FEE078DFF743 +:1032000083FEA06880F87C504BE470B5D44C092153 +:103210000023A06890F87C207030EDF7B3FE002505 +:1032200018B12069007912281ED0A0680A21002355 +:1032300090F87C207030EDF7A5FE18B12069007978 +:10324000142814D02069007916281AD1A06890F8A3 +:103250007C101F2915D180F87C5080F8A250BDE861 +:1032600070401A20FFF74EBABDE8704061E6A068D2 +:1032700000F87C5F458480F82650BDE87040FFF779 +:103280009BBB0EE470B5B64C2079C00773D02069A3 +:1032900000230521C578A06890F87C207030EDF7F8 +:1032A00071FE98B1062D11D006DC022D0ED0042D32 +:1032B0000CD0052D06D109E00B2D07D00D2D05D022 +:1032C000112D03D0607840F008006070607800280D +:1032D00051D12069FAF7E0FF00287ED0206900254F +:1032E0000226C178891E162977D2DFE801F00B7615 +:1032F00034374722764D76254A457676763A5350CE +:103300006A6D7073A0680023012190F87F207030EF +:10331000EDF738FE08BB2069FBF722F8A16881F8B9 +:103320001601072081F87F0081F8A65081F8A2508D +:1033300056E0FFF76AFF53E0A06890F87C100F2971 +:1033400001D066704CE0617839B980F88150122163 +:1033500080F87C1044E000F0D0FD41E000F0ACFDCE +:103360003EE0FBF7A9F803283AD12069FBF7A8F85B +:10337000FFF700FF34E03BE00079F9E7FFF7ABFE31 +:103380002EE0FFF73CFE2BE0FFF7EBFD28E0FFF718 +:10339000D0FD25E0A0680023194690F87D2070300C +:1033A000EDF7F0FD012110B16078C8B901E061705E +:1033B00016E0A06820F8A45F817000F8276C0FE089 +:1033C0000BE0FFF75DFD0BE000F034FD08E0FFF7D8 +:1033D000DFFC05E000F0FAFC02E00020FFF7A1FCB2 +:1033E000A268F2E93001401C41F10001C2E900018C +:1033F0005EE42DE9F0415A4C2079800741D5607890 +:1034000000283ED1E06801270026C178204619290E +:10341000856805F170006FD2DFE801F04B3E0D6F5B +:10342000C1C1801C34C1556287C1C1C1C1BE8B9569 +:1034300098A4B0C1BA0095F87F2000230121EDF7D0 +:10344000A1FD00281DD1A068082180F87F1080F818 +:10345000A26090E0002395F87D201946EDF792FDDB +:1034600010B1A06880F8A660A0680023194690F803 +:103470007C207030EDF786FD002802D0A06880F82F +:10348000A26067E4002395F87C201946EDF77AFDE9 +:1034900000B9FFDF042008E0002395F87C201946DE +:1034A000EDF770FD00B9FFDF0C20A16881F87C000A +:1034B00050E4002395F87C201946EDF763FD00B930 +:1034C000FFDF0D20F1E7002395F87C201946EDF78A +:1034D00059FD00B9FFDFA0680F2180F8A77008E050 +:1034E00095F87C00122800D0FFDFA068112180F839 +:1034F000A87080F87C102DE451E0002395F87C2022 +:103500001946EDF73FFD20B9A06890F8A80000B972 +:10351000FFDFA068132180F8A770EAE795F87C0028 +:10352000182800D0FFDF1A20BFE7BDE8F04100F007 +:1035300063BD002395F87C201946EDF723FD00B903 +:10354000FFDF0520B1E785F8A66003E4002395F8C6 +:103550007C201946EDF716FD00B9FFDF1C20A4E71B +:103560008C010020002395F87D201946EDF70AFD17 +:1035700000B9FFDFA06880F8A66006E4002395F894 +:103580007C201946EDF7FEFC00B9FFDF1F208CE719 +:10359000BDE8F04100F0F8BC85F87D60D3E7FFDFBF +:1035A0006FE710B5F74C6078002837D120794007D5 +:1035B0000FD5A06890F87C00032800D1FFDFA06839 +:1035C00090F87F10072904D101212170002180F893 +:1035D0007F10FFF70EFF00F0B5FCFFF753FEA07859 +:1035E000000716D5A0680023052190F87C207030D4 +:1035F000EDF7C8FC50B108206070A068D0F86411E5 +:1036000008780D2800D10020087002E00020F9F7AA +:10361000F9FCA068BDE81040FFF712BB10BD2DE912 +:10362000F041D84C07464FF00005607808436070C1 +:10363000207981062046806802D5A0F8985004E0E1 +:10364000B0F89810491CA0F8981000F018FD012659 +:10365000F8B1A088000506D5A06890F8821011B1D5 +:10366000A0F88E5015E0A068B0F88E10491CA0F8A4 +:103670008E1000F0F3FCA068B0F88E10B0F8902027 +:10368000914206D3A0F88E5080F83A61E078DFF7D7 +:103690003BFC207910F0600F08D0A06890F88010F3 +:1036A00021B980F880600121FEF782FD1FB9FFF784 +:1036B00078FFFFF799F93846FEF782FFBDE8F04141 +:1036C000F5F711BFAF4A51789378194313D11146DA +:1036D0000128896808D01079400703D591F87F0048 +:1036E000072808D001207047B1F84C00098E8842A5 +:1036F00001D8FEF7E4B900207047A249C278896872 +:10370000012A06D05AB1182A08D1B1F81011FAF7D7 +:10371000BABEB1F822114172090A81727047D1F81C +:10372000181189884173090A8173704770B5954CE7 +:1037300005460E46A0882843A080A80703D5E807C1 +:1037400000D0FFDFE660E80700D02661A80719D5A2 +:10375000F078062802D00B2814D10BE0A06890F86E +:103760007C1018290ED10021E0E93011012100F868 +:103770003E1C07E0A06890F87C10122902D10021BD +:1037800080F88210280601D50820A07068050AD5A7 +:10379000A0688288B0F87010304600F07FFC304698 +:1037A000BDE87040A9E763E43EB505466846F5F715 +:1037B00065FE00B9FFDF222200210098EAF767FECC +:1037C00003210098FAF750FD0098017821F01001CC +:1037D00001702946FAF76DFD6A4C192D71D2DFE8A8 +:1037E00005F020180D3EC8C8C91266C8C9C959C815 +:1037F000C8C8C8BBC9C971718AC89300A1680098BC +:1038000091F8151103E0A168009891F8E610017194 +:10381000B0E0A068D0F81C110098491CFAF795FD9B +:10382000A8E0A1680098D1F8182192790271D1F826 +:10383000182112894271120A8271D1F81821528915 +:10384000C271120A0272D1F8182192894272120AC8 +:103850008272D1F81811C989FAF74EFD8AE0A06882 +:10386000D0F818110098091DFAF77CFDA068D0F86F +:10387000181100980C31FAF77FFDA068D0F81811E4 +:1038800000981E31FAF77EFDA1680098D831FAF74A +:1038900087FD6FE06269009811780171918841712C +:1038A000090A81715188C171090A017262E03649C1 +:1038B000D1E90001CDE9010101A90098FAF78AFDDB +:1038C00058E056E0A068B0F844100098FAF794FD6C +:1038D000A068B0F8E6100098FAF792FDA068B0F87A +:1038E00048100098FAF780FDA068B0F8E81000983A +:1038F000FAF77EFD3EE0A168009891F83021027150 +:1039000091F83111417135E0A06890F81301EDF79D +:1039100032FD01460098FAF7B2FDA06890F8120156 +:1039200000F03DFA70B1A06890F8640000F037FA3A +:1039300040B1A06890F8121190F86400814201D063 +:10394000002002E0A06890F81201EDF714FD014696 +:103950000098FAF790FD0DE0A06890F80D1100981E +:10396000FAF7A8FDA06890F80C110098FAF7A6FDE8 +:1039700000E0FFDFF5F795FD00B9FFDF0098FFF7E6 +:10398000BCFE3EBD8C0100207C63020010B5F94CEA +:10399000A06890F8121109B990F8641080F86410CA +:1039A00090F8131109B990F8651080F8651000209F +:1039B000FEF7A8FEA068FAF750FE002806D0A0681F +:1039C000BDE8104000F59E71FAF7B1BE10BDF8B524 +:1039D000E84E00250446B060B5807570B57035704E +:1039E0000088F5F748FDB0680088F5F76AFDB4F87F +:1039F000F800B168401C82B201F17000EDF75BF88D +:103A000000B1FFDF94F87D00242809D1B4F87010CC +:103A1000B4F81001081A00B2002801DB707830B148 +:103A200094F87C0024280AD0252808D015E0FFF758 +:103A3000ADFF84F87D50B16881F897500DE0B4F87F +:103A40007010B4F81001081A00B2002805DB707875 +:103A500018B9FFF79BFF84F87C50A4F8F850FEF7E4 +:103A600088FD00281CD1B06890F8E400FE2801D041 +:103A7000FFF79AFEC0480090C04BC14A2146284635 +:103A8000F9F7ECF9B0680023052190F87C2070303C +:103A9000EDF778FA002803D0BDE8F840F8F771BFD9 +:103AA000F8BD10B5FEF765FD20B10020BDE810405F +:103AB0000146B4E5BDE81040F9F77FBA70B50C4691 +:103AC000154606464FF4B47200212046EAF7DFFCA3 +:103AD000268005B9FFDF2868C4F818016868C4F8B3 +:103AE0001C01A868C4F8640182E4F0F7E9BA2DE982 +:103AF000F0410D4607460621F0F7D8F9041E3DD0E7 +:103B0000D4F864110026087858B14A8821888A427E +:103B100007D109280FD00E2819D00D2826D0082843 +:103B20003ED094F83A01D0B36E701020287084F81B +:103B30003A61AF809AE06E7009202870D4F8640171 +:103B4000416869608168A9608089A88133E008467E +:103B5000F0F7DDFA0746EFF78AFF70B96E700E20B6 +:103B60002870D4F864014068686011E00846F0F7F6 +:103B7000CEFA0746EFF77BFF08B1002081E46E70B4 +:103B80000D202870D4F8640141686960008928819B +:103B9000D4F8640106703846EFF763FF66E00EE084 +:103BA0006E7008202870D4F86401416869608168EB +:103BB000A960C068E860D4F86401067056E094F823 +:103BC0003C0198B16E70152028700AE084F83C61C1 +:103BD000D4F83E016860D4F84201A860D4F84601E8 +:103BE000E86094F83C010028F0D13FE094F84A01E5 +:103BF00058B16E701C20287084F84A610A2204F5BE +:103C0000A671281DEAF719FC30E094F8560140B17E +:103C10006E701D20287084F85661D4F858016860D1 +:103C200024E094F8340168B16E701A20287004E022 +:103C300084F83461D4F83601686094F834010028BF +:103C4000F6D113E094F85C01002897D06E7016202E +:103C5000287007E084F85C61D4F85E016860B4F80D +:103C60006201288194F85C010028F3D1012008E466 +:103C7000404A5061D170704770B50D4604464EE021 +:103C8000B4F8F800401CA4F8F800B4F89800401C00 +:103C9000A4F89800204600F0F2F9B8B1B4F88E000C +:103CA000401CA4F88E00204600F0D8F9B4F88E002D +:103CB000B4F89010884209D30020A4F88E000120A7 +:103CC00084F83A012B48C078DFF71EF994F8A20077 +:103CD00020B1B4F89E00401CA4F89E0094F8A60001 +:103CE00020B1B4F8A400401CA4F8A40094F8140176 +:103CF00040B994F87F200023012104F17000EDF712 +:103D000041F920B1B4F89C00401CA4F89C00204666 +:103D1000FEF796FFB4F87000401CA4F870006D1E0A +:103D2000ADB2ADD23FE5134AC2E90601704770B5A6 +:103D30000446B0F8980094F88010D1B1B4F89A1005 +:103D40000D1A2D1F94F8960040B194F87C200023A2 +:103D5000092104F17000EDF715F9B8B1B4F88E60DF +:103D6000204600F08CF980B1B4F89000801B001F51 +:103D70000CE007E08C0100201F360200C53602006F +:103D80002D370200C0F10205DCE72846A84200DA20 +:103D90000546002D01DC002005E5A8B203E510F082 +:103DA0000C0000D00120704710B5012808D002286F +:103DB00008D0042808D0082806D0FFDF204610BD10 +:103DC0000124FBE70224F9E70324F7E770B5CC4CA4 +:103DD000A06890F87C001F2804D0607840F00100B3 +:103DE0006070E0E42069FAF73CFBD8B12069012259 +:103DF0000179407901F0070161F30705294600F0D8 +:103E0000070060F30F21A06880F8A2200022A0F82C +:103E10009E20232200F87C2FD0F8B400BDE870402B +:103E2000FEF7AABD0120FEF77CFFBDE870401E2012 +:103E3000FEF768BCF8B5B24C00230A21A06890F8E0 +:103E40007C207030EDF79EF838B32069FAF7E4FA79 +:103E5000C8B12069FAF7DAFA07462069FAF7DAFA00 +:103E600006462069FAF7D0FA05462069FAF7D0FA33 +:103E700001460097A06833462A463030FAF7C1FB66 +:103E8000A068FAF7EAFBA168002081F8A20081F897 +:103E90007C00BDE8F840FEF78FBD607840F001007F +:103EA0006070F8BD964810B580680088F0F72FF96B +:103EB000BDE81040EFF7C6BD10B5914CA36893F86C +:103EC0007C00162802D00220607010BD60780028A7 +:103ED000FBD1D3F81801002200F11E010E30C833C7 +:103EE000ECF7C2FFA0680021C0E92E11012180F883 +:103EF0008110182180F87C1010BD10B5804CA0688E +:103F000090F87C10132902D00220607010BD6178F7 +:103F10000029FBD1D0F8181100884988814200D0CF +:103F2000FFDFA068D0F8181120692631FAF745FAAA +:103F3000A1682069DC31FAF748FAA168162081F8F7 +:103F40007C0010BD10B56E4C207900071BD5607841 +:103F5000002818D1A068002190F8E400FEF72AFE9E +:103F6000A06890F8E400FE2800D1FFDFA068FE21E1 +:103F700080F8E41090F87F10082904D10221217004 +:103F8000002180F87F1010BD70B55D4D2421002404 +:103F9000A86890F87D20212A05D090F87C20232A5B +:103FA00018D0FFDFA0E590F8122112B990F8132184 +:103FB0002AB180F87D10A86880F8A64094E500F842 +:103FC0007D4F847690F8B1000028F4D00020FEF7F1 +:103FD00099FBF0E790F8122112B990F813212AB159 +:103FE00080F87C10A86880F8A2407DE580F87C40CD +:103FF0000020FEF787FBF5E770B5414C0025A0686F +:10400000D0F8181103884A889A4219D109780429EE +:1040100016D190F87C20002319467030ECF7B2FFDF +:1040200000B9FFDFA06890F8AA10890703D4012126 +:1040300080F87C1004E080F8A250D0F818010570D8 +:10404000A0680023194690F87D207030ECF79AFFA5 +:10405000002802D0A06880F8A65045E5B0F890206E +:10406000B0F88E108A4201D3511A00E000218288F4 +:10407000521D8A4202D3012180F89610704710B574 +:1040800090F8821041B990F87C200023062170300E +:10409000ECF778FF002800D0012010BD70B5114466 +:1040A000174D891D8CB2C078A968012806D040B18F +:1040B000182805D191F8120138B109E0A1F8224180 +:1040C00012E5D1F8180184800EE591F8131191B131 +:1040D000FFF765FE80B1A86890F86400FFF75FFE07 +:1040E00050B1A86890F8121190F86420914203D062 +:1040F00090F8130100B90024A868A0F81041F3E477 +:104100008C01002070B58F4C0829207A6CD2DFE832 +:1041100001F004176464276B6B6458B1F4F7D3F8AB +:10412000F5F7FFF80020A072F4F7B4F9BDE870408D +:10413000F4F758BCF5F717F9BDE87040F1F71FBF69 +:10414000DEF7B6FDF5F752F8D4E90001F1F7F3FC1C +:104150002060A07A401CC0B2A072282824D370BD71 +:10416000A07A0025401EC6B2E0683044F4F700FD96 +:1041700010B9E1687F208855A07A272828BF01253B +:10418000DEF796FDA17A01EB4102C2EB81110844F2 +:104190002946F5F755F8A07A282809D2401CC0B264 +:1041A000A072282828BF70BDBDE87040F4F772B92E +:1041B000207A002818BF00F086F8F4F74BFBF4F7DC +:1041C000F7FBF5F7D0F80120E0725F480078DEF7E2 +:1041D0009BFEBDE87040F1F7D2BE002808BF70BD5D +:1041E000BDE8704000F06FB8FFDF70BD10B5554CF2 +:1041F000207A002804BF0C2010BD00202072E0723D +:10420000607AF2F7F8FA607AF2F761FD607AF1F716 +:104210008CFF00280CBF1F20002010BD002270B5AD +:10422000484C06460D46207A68B12272E272607AE6 +:10423000F2F7E1FA607AF2F74AFD607AF1F775FF7A +:10424000002808BFFFDF4048E560067070BD70B50C +:10425000050007D0A5F5E8503C494C3881429CBF89 +:10426000122070BD374CE068002804BF092070BDE3 +:10427000207A00281CBF0C2070BD3548F1F7FAFEEB +:104280006072202804BF1F2070BDF1F76DFF206011 +:104290001DB12946F1F74FFC2060012065602072B6 +:1042A00000F011F8002070BD2649CA7A002A04BF28 +:1042B000002070471E22027000224270CB684360CB +:1042C000CA7201207047F0B585B0F1F74DFF1D4D62 +:1042D0000746394668682C6800EB80004600204697 +:1042E000F2F73AFCB04206DB6868811B3846F1F70A +:1042F00022FC0446286040F2367621463846F2F722 +:104300002BFCB04204DA31463846F1F714FC04467F +:1043100000208DF8000040F6E210039004208DF894 +:10432000050001208DF8040068460294F2F7CAF8EF +:10433000687A6946F2F743F9002808BFFFDF05B045 +:10434000F0BD000074120020AC010020B5EB3C0071 +:10435000054102002DE9F0410C4612490D68114A51 +:10436000114908321160A0F12001312901D3012047 +:104370000CE0412810D040CC0C4F94E80E0007EB25 +:104380008000241F50F8807C3046B84720600548E4 +:10439000001D0560BDE8F081204601F0EBFCF5E76B +:1043A00006207047100502400100000184630200EE +:1043B00010B55548F2F7FAFF00B1FFDF5248401C34 +:1043C000F2F7F4FF002800D0FFDF10BD2DE9F14F18 +:1043D0004E4E82B0D6F800B001274B48F2F7EEFF00 +:1043E000DFF8248120B9002708F10100F2F7FCFF73 +:1043F000474C00254FF0030901206060C4F80051CC +:10440000C4F80451029931602060DFF808A11BE074 +:10441000DAF80000C00617D50E2000F068F8EFF3B8 +:10442000108010F0010072B600D001200090C4F896 +:104430000493D4F8000120B9D4F8040108B901F0BC +:10444000A3FC009800B962B6D4F8000118B9D4F8FA +:1044500004010028DCD0D4F804010028CCD137B105 +:10446000C6F800B008F10100F2F7A8FF11E008F16A +:104470000100F2F7A3FF0028B6D1C4F80893C4F8EE +:104480000451C4F800510E2000F031F81E48F2F734 +:10449000ABFF0020BDE8FE8F2DE9F0438DB00D4647 +:1044A000064600240DF110090DF1200818E000BFA8 +:1044B00004EB4407102255F827106846E9F7BDFFC2 +:1044C00005EB8707102248467968E9F7B6FF68468A +:1044D000FFF77CFF10224146B868E9F7AEFF641C85 +:1044E000B442E5DB0DB00020BDE8F0836EE70028A4 +:1044F00009DB00F01F02012191404009800000F11A +:10450000E020C0F880127047AD01002004E50040B3 +:1045100000E0004010ED00E0B54900200870704751 +:1045200070B5B44D01232B60B34B1C68002CFCD03C +:10453000002407E00E6806601E68002EFCD0001DF7 +:10454000091D641C9442F5D30020286018680028D7 +:10455000FCD070BD70B5A64E0446A84D3078022838 +:1045600000D0FFDFAC4200D3FFDF7169A44801290E +:1045700003D847F23052944201DD03224271491CB4 +:104580007161291BC1609E49707800F02EF90028E6 +:1045900000D1FFDF70BD70B5954C0D466178884243 +:1045A00000D0FFDF954E082D4BD2DFE805F04A041E +:1045B0001E2D4A4A4A382078022800D0FFDF032007 +:1045C0002070A078012801D020B108E0A06801F097 +:1045D00085F904E004F1080007C8FFF7A1FF0520F2 +:1045E0002070BDE87040F1F7CABCF1F7BDFD01468F +:1045F0006068F2F7B1FAB04202D2616902290BD3C6 +:104600000320F2F7FAFD12E0F1F7AEFD0146606813 +:10461000F2F7A2FAB042F3D2BDE870409AE72078F0 +:1046200002280AD0052806D0FFDF04202070BDE84C +:10463000704000F0D0B8022000E00320F2F7DDFD6A +:10464000F3E7FFDF70BD70B50546F1F78DFD684CEF +:1046500060602078012800D0FFDF694901200870E0 +:104660000020087104208D6048716448C8600220F1 +:104670002070607800F0B9F8002800D1FFDF70BD2D +:1046800010B55B4C207838B90220F2F7CCFD18B990 +:104690000320F2F7C8FD08B1112010BD5948F1F709 +:1046A000E9FC6070202804D00120207000206061A7 +:1046B00010BD032010BD2DE9F0471446054600EB60 +:1046C00084000E46A0F1040801F01BF907464FF0E4 +:1046D000805001694F4306EB8401091FB14201D2AA +:1046E000012100E0002189461CB10069B4EB900F64 +:1046F00002D90920BDE8F0872846DCF7A3FD90B970 +:10470000A84510D3BD4205D2B84503D245EA0600FC +:10471000800701D01020EDE73046DCF793FD10B99B +:10472000B9F1000F01D00F20E4E73748374900689E +:10473000884205D0224631462846FFF7F1FE1AE0AE +:10474000FFF79EFF0028D5D1294800218560C0E9E8 +:1047500003648170F2F7D3FD08B12D4801E04AF2FD +:10476000F87060434FF47A7100F2E730B0FBF1F07B +:104770001830FFF768FF0020BCE770B505464FF022 +:10478000805004696C432046DCF75CFD08B10F20C3 +:1047900070BD01F0B6F8A84201D8102070BD1A48CB +:1047A0001A490068884203D0204601F097F810E0CB +:1047B000FFF766FF0028F1D10D4801218460817068 +:1047C000F2F79DFD08B1134800E013481830FFF7D9 +:1047D0003AFF002070BD10B5054C6078F1F7A5FCDC +:1047E00000B9FFDF0020207010BDF1F7E8BE000027 +:1047F000B001002004E5014000E40140105C0C0021 +:1048000084120020974502005C000020BEBAFECA58 +:1048100050280500645E0100A85B01007E4909681C +:104820000160002070477C4908600020704701212A +:104830008A0720B1012804D042F204007047916732 +:1048400000E0D1670020704774490120086042F2FF +:104850000600704708B50423704A1907103230B1BA +:10486000C1F80433106840F0010010600BE01068DC +:1048700020F001001060C1F808330020C1F80801E1 +:10488000674800680090002008BD011F0B2909D867 +:10489000624910310A6822F01E0242EA40000860B4 +:1048A0000020704742F2050070470F2809D85B4985 +:1048B00010310A6822F4706242EA00200860002089 +:1048C000704742F205007047000100F18040C0F8D7 +:1048D000041900207047000100F18040C0F8081959 +:1048E00000207047000100F18040D0F80009086006 +:1048F00000207047012801D907207047494A52F823 +:10490000200002680A43026000207047012801D994 +:1049100007207047434A52F8200002688A43026029 +:1049200000207047012801D9072070473D4A52F8FE +:104930002000006808600020704702003A494FF0EC +:10494000000003D0012A01D0072070470A60704799 +:10495000020036494FF0000003D0012A01D00720A1 +:1049600070470A60704708B54FF40072510510B1E6 +:10497000C1F8042308E0C1F808230020C1F824018D +:1049800027481C3000680090002008BD08B5802230 +:10499000D10510B1C1F8042308E0C1F808230020B4 +:1049A000C1F81C011E48143000680090002008BDAA +:1049B00008B54FF48072910510B1C1F8042308E0E6 +:1049C000C1F808230020C1F82001154818300068FC +:1049D0000090002008BD10493831096801600020AE +:1049E000704770B54FF080450024C5F80841F2F7D4 +:1049F00092FC10B9F2F799FC28B1C5F82441C5F82A +:104A00001C41C5F820414FF0E020802180F80014BF +:104A10000121C0F8001170BD0004004000050040F5 +:104A2000080100404864020078050040800500400D +:104A30006249634B0A6863499A42096801D1C1F32C +:104A400010010160002070475C495D4B0A685D49B8 +:104A5000091D9A4201D1C0F3100008600020704780 +:104A60005649574B0A68574908319A4201D1C0F359 +:104A7000100008600020704730B5504B504D1C6846 +:104A800042F20803AC4202D0142802D203E01128FB +:104A900001D3184630BDC3004B481844C0F8101568 +:104AA000C0F81425002030BD4449454B0A6842F245 +:104AB00009019A4202D0062802D203E0042801D359 +:104AC00008467047404A012142F8301000207047E4 +:104AD0003A493B4B0A6842F209019A4202D0062841 +:104AE00002D203E0042801D308467047364A012168 +:104AF00002EBC00041600020704770B52F4A304E75 +:104B0000314C156842F2090304EB8002B54204D02F +:104B1000062804D2C2F8001807E0042801D318467A +:104B200070BDC1F31000C2F80008002070BD70B560 +:104B3000224A234E244C156842F2090304EB8002FA +:104B4000B54204D0062804D2D2F8000807E00428B1 +:104B500001D3184670BDD2F80008C0F310000860F9 +:104B6000002070BD174910B50831184808601120A1 +:104B7000154A002102EBC003C3F81015C3F8141541 +:104B8000401C1428F6D3002006E0042804D302EBCE +:104B90008003C3F8001807E002EB8003D3F8004855 +:104BA000C4F31004C3F80048401C0628EDD310BD20 +:104BB0000449064808310860704700005C00002086 +:104BC000BEBAFECA00F5014000F001400000FEFF41 +:104BD000814B1B6803B19847BFF34F8F7F48016833 +:104BE0007F4A01F4E06111430160BFF34F8F00BFC2 +:104BF000FDE710B5EFF3108010F0010F72B601D091 +:104C0000012400E0002400F0DDF850B1DCF7BFFB28 +:104C1000F1F755F8F2F793FAF2F7BEFF7149002069 +:104C2000086004B962B6002010BD2DE9F0410C46C1 +:104C30000546EFF3108010F0010F72B601D0012687 +:104C400000E0002600F0BEF820B106B962B60820E8 +:104C5000BDE8F08101F01EF9DCF79DFB0246002063 +:104C600001234709BF0007F1E02700F01F01D7F833 +:104C70000071CF40F9071BD0202803D222FA00F19F +:104C8000C90727D141B2002904DB01F1E02191F8E5 +:104C9000001405E001F00F0101F1E02191F8141D6D +:104CA0004909082916D203FA01F717F0EC0F11D0C1 +:104CB000401C6428D5D3F2F74DFF4B4A4B490020E6 +:104CC000F2F790FF47494A4808602046DCF7C1FAEE +:104CD00060B904E006B962B641F20100B8E73E48A7 +:104CE00004602DB12846DCF701FB18B1102428E040 +:104CF000404D19E02878022802D94FF4805420E072 +:104D000007240028687801D0D8B908E0C8B1202865 +:104D100017D8A878212814D8012812D001E0A87843 +:104D200078B9E8780B280CD8DCF735FB2946F2F780 +:104D3000ECF9F0F783FF00F017FE2846DCF7F4FAF1 +:104D4000044606B962B61CB1FFF753FF20467FE761 +:104D500000207DE710B5044600F034F800B10120D2 +:104D60002070002010BD244908600020704770B5F5 +:104D70000C4622490D682149214E08310E60102849 +:104D800007D011280CD012280FD0132811D00120E1 +:104D900013E0D4E90001FFF748FF354620600DE03D +:104DA000FFF727FF0025206008E02068FFF7D2FF0B +:104DB00003E0114920680860002020600F48001DB2 +:104DC000056070BD07480A490068884201D101208A +:104DD0007047002070470000C80100200CED00E083 +:104DE0000400FA055C0000204814002000000020A8 +:104DF000BEBAFECA50640200040000201005024042 +:104E0000010000017D49C0B20860704700B57C49CF +:104E1000012808BF03200CD0022808BF042008D0B6 +:104E2000042808BF062004D0082816BFFFDF05208D +:104E300000BD086000BD70B505460C46164610461C +:104E4000F3F7D2F8022C08BF4FF47A7105D0012C89 +:104E50000CBF4FF4C86140F6340144183046F3F7F4 +:104E60003CF9204449F6797108444FF47A71B0FB5B +:104E7000F1F0281A70BD70B505460C460846F4F7E7 +:104E80002FFA022C08BF40F24C4105D0012C0CBF78 +:104E900040F634014FF4AF5149F6CA62511A084442 +:104EA0004FF47A7100F2E140B0FBF1F0281A801E55 +:104EB00070BD70B5064615460C460846F4F710FA64 +:104EC000022D08BF4FF47A7105D0012D0CBF4FF4AD +:104ED000C86140F63401022C08BF40F24C4205D0B4 +:104EE000012C0CBF40F634024FF4AF52891A08442B +:104EF00049F6FC6108444FF47A71B0FBF1F0301AC6 +:104F000070BD70B504460E460846F3F76DF80546C9 +:104F10003046F3F7E2F828444AF2AB3108444FF444 +:104F20007A71B0FBF1F0201A801E70BD2DE9F041BE +:104F300007461E460D4614461046082A16BF04288A +:104F40004EF62830F3F750F807EB4701C1EBC711D5 +:104F500000EBC100022D08BF40F24C4105D0012DED +:104F60000CBF40F634014FF4AF5147182846F4F710 +:104F7000B7F9381A4FF47A7100F6B730B0FBF1F593 +:104F80002046F3F7B9F828443044401DBDE8F081CD +:104F900070B5054614460E460846F3F725F805EBAE +:104FA0004502C2EBC512C0EBC2053046F3F795F8D7 +:104FB0002D1A2046082C16BF04284EF62830F3F789 +:104FC00013F828444FF47A7100F6B730B0FBF1F5CE +:104FD0002046F3F791F82844401D70BD0949082880 +:104FE00018BF0428086803BF20F46C5040F4444004 +:104FF00040F0004020F00040086070470C15004071 +:105000001015004040170040F0B585B00C4605462D +:10501000F9F73EF907466E78204603A96A46EEF78F +:1050200002FD81198EB258B1012F02D0032005B0C4 +:10503000F0BD204604AA0399EEF717FC049D01E099 +:10504000022F0FD1ED1C042E0FD32888BDF80010BD +:10505000001D80B2884201D8864202D14FF0000084 +:10506000E5E702D34FF00200E1E74FF00100DEE791 +:10507000FA48C078FF2814BF0120002070472DE9AE +:10508000F041F74C0746160060680D4603D0F9F76B +:1050900069F8A0B121E0F9F765F8D8B96068F9F7C7 +:1050A00061F8D0B915F00C0F17D06068C17811F015 +:1050B0003F0F1CBF007910F0100F0ED00AE0022E37 +:1050C00008D0E6481FB1807DFF2806D002E0C078F6 +:1050D000FF2802D00120BDE8F0810020BDE8F0816A +:1050E0000A4601460120CAE710B5DC4C1D2200210A +:1050F000A01CE9F7CCF97F206077FF202074E070D6 +:10510000A075A08920F060002030A08100202070D0 +:1051100010BD70B5D249486001200870D248D1490D +:10512000002541600570CD4C1D222946A01CE9F7E1 +:10513000AEF97F206077FF202074E070A075A08911 +:1051400020F060002030A081257070BD2DE9F0476F +:10515000C24C06462078C24F4FF0010907F10808FB +:10516000002520B13878D0B998F80000B8B198F887 +:10517000000068B387F80090D8F804103C2239B3D7 +:105180007570301DE9F759F90520307086F80490E4 +:105190003878002818BF88F8005005D015E03D7019 +:1051A000A11C4FF48E72EAE71D220021A01CE9F732 +:1051B0006EF97F206077FF202074E070A075A089D1 +:1051C00020F060002030A08125700120BDE8F0872C +:1051D0000020BDE8F087A148007800280CBF01201E +:1051E000002070470A460146002048E710B510B17C +:1051F000022810D014E09A4C6068F8F7B3FF78B931 +:105200006068C17811F03F0F1CBF007910F0100FDB +:1052100006D1012010BD9148007B10F0080FF8D195 +:10522000002010BD2DE9FF4F81B08C4D8346DDE994 +:105230000F042978DDF838A09846164600291CBFCF +:1052400005B0BDE8F08F8849097800291CBF05B07A +:10525000BDE8F08FE872B4B1012E08BF012708D075 +:10526000022E08BF022704D0042E16BF082E0327E3 +:10527000FFDFEF7385F81E804FF00008784F8CB188 +:10528000022C1DD020E0012E08BF012708D0022EDD +:1052900008BF022704D0042E16BF082E0327FFDF05 +:1052A000AF73E7E77868F8F75DFF68B97868C178A9 +:1052B00011F03F0F1CBF007910F0100F04D110E067 +:1052C000287B10F0080F0CD14FF003017868F8F735 +:1052D000FDFD30B14178090929740088C0F30B0045 +:1052E0006882CDF800807868F8F73CFF0146012815 +:1052F000BDF8000005F102090CBF40F0010020F0EC +:105300000100ADF8000099F80A2012F0020F4ED10A +:10531000022918BF20F0020049D000BFADF80000FC +:1053200010F0020F04D0002908BF40F0080801D097 +:1053300020F00808ADF800807868C17811F03F0FC0 +:105340001CBF007910F0020F0CD0314622464FF0FE +:105350000100FFF794FE002804BF48F00400ADF8F8 +:10536000000006D099F80A00800860F38208ADF8C2 +:10537000008099F80A004109BDF8000061F3461069 +:10538000ADF8000080B20090BDF80000A8810421B3 +:105390007868F8F79BFD002804BFA88920F060001A +:1053A0000CD0B0F80100C004C00C03D007E040F0FE +:1053B0000200B3E7A88920F060004030A8815CB902 +:1053C00016F00C0F08D07868C17811F03F0F1CBFA1 +:1053D000007910F0100F0DD17868C17811F03F0FEF +:1053E00008D0017911F0400F04D00621F8F76EFDC6 +:1053F00000786877314622460020FFF740FE60BB08 +:105400007968C87810F03F0F3FD0087910F0010F8D +:105410003BD0504605F1040905F10308BAF1FF0F2E +:105420000DD04A464146F8F781FA002808BFFFDF51 +:1054300098F8000040F0020088F8000025E00846D7 +:10544000F8F7DBFC88F800007868F8F7ADFC07286F +:105450000CD249467868F8F7B2FC16E094120020A6 +:10546000CC010020D2120020D40100207868F8F787 +:105470009BFC072809D100217868F8F727FD01680F +:10548000C9F800108088A9F804003146224601209E +:10549000FFF7F5FD80BB7868C17811F03F0F2BD086 +:1054A000017911F0020F27D005F1170605F1160852 +:1054B000BBF1020F18BFBBF1030F08D0F8F774FC63 +:1054C00007280AD231467868F8F787FC12E002987C +:1054D000016831608088B0800CE07868F8F764FC7F +:1054E000072807D101217868F8F7F0FC01683160DE +:1054F0008088B08088F800B0002C04BF05B0BDE8FB +:10550000F08F7868F8F72EFE022804BF05B0BDE8DA +:10551000F08F05F11F047868F8F76DFEAB7AC3F1E0 +:10552000FF01884228BF084605D9A98921F06001FA +:1055300001F14001A981C2B203EB04017868F8F7D8 +:1055400062FEA97A0844A87205B0BDE8F08FB048A1 +:105550000178002918BF704701220270007B10F00B +:10556000080F14BF07200620FCF75FBEA848C17BC8 +:10557000002908BF70470122818921F06001403174 +:1055800081810378002B18BF7047027011F0080F5B +:1055900014BF07200620FCF748BE2DE9FF5F9C4F93 +:1055A000DDF838B0914638780E4600281CBF04B0AC +:1055B000BDE8F09FBC1C1D2200212046E8F767FFD4 +:1055C000944D4FF0010A84F800A06868F8F7ECFBEE +:1055D00018B3012826D0022829D0062818BFFFDFDB +:1055E0002AD000BF04F11D016868F8F726FC20727C +:1055F000484604F1020904F10108FF2821D04A4677 +:105600004146F8F793F9002808BFFFDF98F800003B +:1056100040F0020088F8000031E0608940F013009B +:105620006081DFE7608940F015006081E0E7608914 +:1056300040F010006081D5E7608940F01200608181 +:10564000D0E76868F8F7D9FB88F800006868F8F7D1 +:10565000ABFB072804D249466868F8F7B0FB0EE0B8 +:105660006868F8F7A1FB072809D100216868F8F7F6 +:105670002DFC0168C9F800108088A9F8040084F89E +:1056800009B084F80CA000206073FF20A073A17AF9 +:1056900011F0040F08BF20752AD004F1150804F199 +:1056A0001409022E18BF032E09D06868F8F77CFB96 +:1056B00007280CD241466868F8F78FFB16E000987F +:1056C0000168C8F800108088A8F804000EE0686837 +:1056D000F8F76AFB072809D101216868F8F7F6FB9B +:1056E0000168C8F800108088A8F8040089F80060F4 +:1056F0007F20E0760398207787F800A004B006208A +:10570000BDE8F05FFCF791BD2DE9FF5F424F814698 +:105710009A4638788B4600281CBF04B0BDE8F09F3D +:105720003B48017831B1007B10F0100F04BF04B08A +:10573000BDE8F09F1D227C6800212046E8F7A7FE07 +:1057400048464FF00108661C324D84F8008004F191 +:105750000209FF280BD04A463146F8F7E7F800283F +:1057600008BFFFDF307840F0020030701CE068684E +:10577000F8F743FB30706868F8F716FB072804D287 +:1057800049466868F8F71BFB0EE06868F8F70CFB01 +:10579000072809D100216868F8F798FB0168C9F863 +:1057A00000108088A9F8040004F11D016868F8F76A +:1057B00044FB207284F809A060896BF3000040F07C +:1057C0001A00608184F80C8000206073FF20A073B1 +:1057D00020757F20E0760298207787F8008004B05B +:1057E0000720BDE8F05FFCF720BD094A137C834227 +:1057F00005BF508A88420020012070470448007B82 +:10580000C0F3411002280CBF0120002070470000A7 +:1058100094120020CC010020D4010020C2790D2375 +:1058200041B342BB8188012904D94908818004BF62 +:10583000012282800168012918BF002930D0016847 +:105840006FEA0101C1EBC10202EB011281796FEA3B +:10585000010101EB8103C3EB811111444FEA914235 +:1058600001608188B2FBF1F301FB132181714FF0DC +:10587000010102E01AB14FF00001C1717047818847 +:10588000FF2908D24FF6FF7202EA41018180FF2909 +:1058900084BFFF2282800168012918BF0029CED170 +:1058A0000360CCE7817931B1491E11F0FF018171AC +:1058B0001CBF002070470120704710B50121C17145 +:1058C0008171818004460421F1F7E8FD002818BFAA +:1058D00010BD2068401C206010BD00000B4A022152 +:1058E00011600B490B68002BFCD0084B1B1D186086 +:1058F00008680028FCD00020106008680028FCD050 +:1059000070474FF0805040697047000004E5014047 +:1059100000E4014002000B464FF00000014620D099 +:10592000012A04D0022A04D0032A0DD103E0012069 +:1059300002E0022015E00320072B05D2DFE803F088 +:105940000406080A0C0E100007207047012108E029 +:10595000022106E0032104E0042102E0052100E029 +:105960000621F0F7A4BB0000E24805218170002168 +:10597000017041707047E0490A78012A05D0CA6871 +:105980001044C8604038F1F7B4B88A6810448860A1 +:10599000F8E7002819D00378D849D94A13B1012B68 +:1059A0000ED011E00379012B00D06BB943790BB114 +:1059B000012B09D18368643B8B4205D2C0680EE09D +:1059C0000379012B02D00BB10020704743790BB152 +:1059D000012BF9D1C368643B8B42F5D280689042B9 +:1059E000F2D801207047C44901220A70027972B1CD +:1059F00000220A71427962B104224A7182685232ED +:105A00008A60C068C860BB49022088707047032262 +:105A1000EFE70322F1E770B5B74D04460020287088 +:105A2000207988B100202871607978B10420B14EC6 +:105A30006871A168F068F0F77EF8A860E0685230FD +:105A4000E8600320B07070BD0120ECE70320EEE7B2 +:105A50002DE9F04105460226F0F777FF006800B116 +:105A6000FFDFA44C01273DB12878B8B1012805D04B +:105A7000022811D0032814D027710DE06868C828C7 +:105A800008D30421F1F79BF820B16868FFF773FF92 +:105A9000012603E0002601E000F014F93046BDE8DD +:105AA000F08120780028F7D16868FFF772FF00289E +:105AB000E2D06868017879B1A078042800D0FFDFCF +:105AC00001216868FFF7A7FF8B49E07800F003F930 +:105AD0000028E1D1FFDFDFE7FFF785FF6770DBE735 +:105AE0002DE9F041834C0F46E178884200D0FFDF7A +:105AF00000250126082F7DD2DFE807F0040B2828B7 +:105B00003D434F57A0780328C9D00228C7D0FFDFF4 +:105B1000C5E7A078032802D0022800D0FFDF0420C8 +:105B2000A07025712078B8BB0020FFF724FF7248D1 +:105B30000178012906D08068E06000F0EDF820616E +:105B4000002023E0E078F0F734FCF5E7A0780328A4 +:105B500002D0022800D0FFDF207880BB022F08D0BF +:105B60005FF00500F1F749FBA078032840D0A5704D +:105B700095E70420F6E7A078042800D0FFDF022094 +:105B800004E0A078042800D0FFDF0120A168884746 +:105B9000FFF75EFF054633E003E0A078042800D05D +:105BA000FFDFBDE8F04100F08DB8A078042804D0F4 +:105BB000617809B1022800D0FFDF207818B1BDE874 +:105BC000F04100F08AB8207920B10620F1F715FBEA +:105BD00025710DE0607840B14749E07800F07BF82E +:105BE00000B9FFDF65705AE704E00720F1F705FB15 +:105BF000A67054E7FFDF52E73DB1012D03D0FFDF70 +:105C0000022DF9D14BE70420C0E70320BEE770B5B1 +:105C1000050004D0374CA078052806D101E01020FB +:105C200070BD0820F1F7FFFA08B1112070BD3548AA +:105C3000F0F720FAE070202806D00121F1F7DCF817 +:105C40000020A560A07070BD032070BD294810B56C +:105C5000017809B1112010BD8178052906D00129EC +:105C600006D029B101210170002010BD0F2010BD08 +:105C700000F033F8F8E770B51E4C0546A07808B17F +:105C8000012809D155B12846FFF783FE40B1287895 +:105C900040B1A078012809D00F2070BD102070BD40 +:105CA000072070BD2846FFF79EFE03E0002128462E +:105CB000FFF7B1FE1049E07800F00DF800B9FFDF02 +:105CC000002070BD0B4810B5006900F01DF8BDE85C +:105CD0001040F0F754B9F0F772BC064810B5C07820 +:105CE000F0F723FA00B9FFDF0820F1F786FABDE8E4 +:105CF000104039E6DC010020B41300203D8601008D +:105D0000FF1FA107E15A02000C490A6848F202137A +:105D10009A4302430A607047084A116848F2021326 +:105D200001EA03009943116070470246044B1020BA +:105D30001344FC2B01D8116000207047C8060240B4 +:105D40000018FEBF1EF0040F0CBFEFF30880EFF346 +:105D50000980014A10470000FF7B010001B41EB416 +:105D600000B5F1F76DFC01B40198864601BC01B0A5 +:105D70001EBD00008269034981614FF0010010449B +:105D8000704700005D5D02000FF20C0000F10000A2 +:105D9000694641F8080C20BF70470000FEDF184933 +:105DA0000978F9B90420714608421BD10699154AB1 +:105DB000914217DC0699022914DB02394878DF2862 +:105DC00010D10878FE2807D0FF280BD14FF0010032 +:105DD0004FF000020C4B184741F201000099019A64 +:105DE000094B1847094B002B02D01B68DB6818478A +:105DF0004FF0FF3071464FF00002034B1847000090 +:105E000028ED00E000700200D14B020004000020E9 +:105E1000174818497047FFF7FBFFDBF7CFF900BDC4 +:105E2000154816490968884203D1154A13605B6812 +:105E3000184700BD20BFFDE70F4810490968884298 +:105E400010D1104B18684FF0FF318842F2D080F328 +:105E500008884FF02021884204DD0B4802680321A6 +:105E60000A4302600948804709488047FFDF000075 +:105E7000C8130020C81300200010000000000020FC +:105E8000040000200070020014090040B92F000037 +:105E9000215E0200F0B44046494652465B460FB4CC +:105EA00002A0013001B50648004700BF01BC86468C +:105EB0000FBC8046894692469B46F0BC7047000066 +:105EC0000911000004207146084202D0EFF3098155 +:105ED00001E0EFF30881886902380078102813DBAD +:105EE00020280FDB2C280BDB0A4A12680A4B9A4247 +:105EF00003D1602804DB094A10470220086070477C +:105F0000074A1047074A1047074A12682C3212689E +:105F1000104700005C000020BEBAFECA9B130000C0 +:105F2000554302006F4D0200040000200D4B0E4946 +:105F300008470E4B0C4908470D4B0B4908470D4BC2 +:105F4000094908470C4B084908470C4B06490847C4 +:105F50000B4B054908470B4B034908470A4B0249BD +:105F60000847000041BF000079C10000792D000002 +:105F7000F32B0000812B0000012E0000B71300005E +:105F80003F2900007D2F0000455D020000210160D7 +:105F90004160017270470A6802600B7903717047B3 +:105FA00089970000FF9800005B9A0000C59A0000E6 +:105FB000FF9A0000339B0000659B00009D9B000042 +:105FC0003D9C00007D980000859A0000331200007F +:105FD0000744000053440000B94400004745000056 +:105FE0006146000037470000694700004148000053 +:105FF000DB4800002F490000154A0000354A000028 +:10600000AD160000D1160000F11500004D1600007D +:10601000031700009717000003610000C36200002F +:10602000A1660000BB67000043680000C168000073 +:10603000256900004D6A00001D6B0000896B00009F +:10604000574A00005D4A0000674A0000CF4A00003E +:10605000FB4A0000B74C0000E14C0000194D000065 +:10606000834D00006D4E0000834E00007744000019 +:10607000974E0000B94E0000FF4E000033120000A2 +:10608000331200003312000033120000C12500005B +:1060900047260000632600007F2600000D28000030 +:1060A000A9260000B3260000F526000017270000EF +:1060B000F3270000352800003312000033120000DF +:1060C00097840000B7840000B9840000FD840000BC +:1060D0002B8500001B860000A7860000BB86000001 +:1060E000098700001F880000C1890000E98A0000BC +:1060F0003D740000018B00003312000033120000D9 +:10610000EBB700004DB90000A7B9000021BA0000AC +:10611000CDBA0000010000000000000010011001D5 +:106120003A0200001A020000020004050600000006 +:1061300007111102FFFFFFFF0000FFFFF3B3000094 +:10614000273D0000532100008774000001900000EB +:1061500000000000BF9200009B920000AD92000082 +:10616000000002000000000000020000000000002B +:1061700000010000000000004382000023820000B4 +:10618000918200002D250000EF2400000F25000063 +:10619000DBAA000007AB00000FAD0000FD590000B6 +:1061A000B182000000000000E18200007B250000B9 +:1061B000000000000000000000000000F1AB000043 +:1061C00000000000915A00000300000001555555E1 +:1061D000D6BE898E00006606660C661200000A03B1 +:1061E000AE055208000056044608360CC7FD0000F4 +:1061F0005BFF0000A1FB0000C3FD0000A7A8010099 +:106200009B040100AAAED7AB15412010000000008E +:10621000900A0000900A00007B5700007B570000A6 +:10622000E143000053B200000B7700006320000040 +:10623000BD3A020063BD0100BD570000BD5700001C +:1062400005440000E5B2000093770000D72000006D +:10625000EB3A020079BD0100700170014000380086 +:106260005C0024006801200200000300656C746279 +:10627000000000000000000000000000000000001E +:106280008700000000000000000000000000000087 +:10629000BE83605ADB0B376038A5F5AA9183886C02 +:1062A000010000007746010049550100000000018F +:1062B0000206030405000000070000FB349B5F801A +:1062C000000080001000000000000000000000003E +:1062D000060000000A000000320000007300000009 +:1062E000B4000000F401FA00960064004B00320094 +:1062F0001E0014000A000500020001000049000011 +:1063000000000000D7CF0100E9D1010025D1010034 +:10631000EBCF0100000000008FD40100000101025A +:10632000010202030C0802170D0101020909010113 +:1063300006020918180301010909030305000000FA +:10634000555555252627D6BE898E00002BFB01000A +:1063500003F7010049FA01003FF20100BB220200ED +:10636000B7FB0100F401FA00960064004B00320014 +:106370001E0014000A00050002000100254900006B +:1063800000000000314A0200494A0200614A02004E +:10639000794A0200A94A0200D14A0200FB4A0200DF +:1063A0002F4B02007B470200B7460200A1430200C8 +:1063B0002B5D0200AD730100BD730100E9730100A4 +:1063C000BB740100C3740100D57401002F480200A2 +:1063D000494802001D4802002748020055480200B3 +:1063E0008B480200AB480200C9480200D7480200AF +:1063F000E5480200F54802000D4902002549020067 +:106400003B4902005149020000000000DFBC0000CF +:1064100035BD00004BBD000015590200CD43020000 +:10642000994402000F5C02004D5C0200775C0200A0 +:106430009D710100FD760100674902008D4902004F +:10644000B1490200D74902001C0500402005004068 +:10645000001002007464020008000020E80100003F +:106460004411000098640200F0010020D8110000DF +:10647000A011000001181348140244200B440C061C +:106480004813770B1B2034041ABA0401A40213101A +:08649000327F0B744411C000BF +:00000001FF diff --git a/bin/setup-python-for-esp-debug.sh b/bin/setup-python-for-esp-debug.sh new file mode 100644 index 000000000..edba43e72 --- /dev/null +++ b/bin/setup-python-for-esp-debug.sh @@ -0,0 +1,12 @@ +# shellcheck shell=bash +# (this minor script is actually shell agnostic, and is intended to be sourced rather than run in a subshell) + +# This is a little script you can source if you want to make ESP debugging work on a modern (24.04) ubuntu machine +# It assumes you have built and installed python 2.7 from source with: +# ./configure --enable-optimizations --enable-shared --enable-unicode=ucs4 +# sudo make clean +# make +# sudo make altinstall + +export LD_LIBRARY_PATH=$HOME/packages/python-2.7.18/ +export PYTHON_HOME=/usr/local/lib/python2.7/ diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index 9db60e203..f45b030d1 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -27,7 +27,7 @@ "jlink_device": "nRF52840_xxAA", "svd_path": "nrf52840.svd" }, - "frameworks": ["arduino"], + "frameworks": ["arduino", "freertos"], "name": "Seeed WIO WM1110", "upload": { "maximum_ram_size": 248832, diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index b4ab8db11..37a9186ab 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -23,7 +23,7 @@ "sd_flags": "-DS140", "sd_name": "s140", "sd_version": "7.3.0", - "sd_fwid": "0x00B6" + "sd_fwid": "0x0123" }, "bootloader": { "settings_addr": "0xFF000" diff --git a/boards/wiscore_rak4631.json b/boards/wiscore_rak4631.json index 6dec3f7cb..c783f33a6 100644 --- a/boards/wiscore_rak4631.json +++ b/boards/wiscore_rak4631.json @@ -35,7 +35,7 @@ "svd_path": "nrf52840.svd", "openocd_target": "nrf52840-mdk-rs" }, - "frameworks": ["arduino"], + "frameworks": ["arduino", "freertos"], "name": "WisCore RAK4631 Board", "upload": { "maximum_ram_size": 248832, diff --git a/platformio.ini b/platformio.ini index 23ff53a10..bcdcc0034 100644 --- a/platformio.ini +++ b/platformio.ini @@ -77,10 +77,11 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_DROPZONE=1 monitor_speed = 115200 +monitor_filters = direct lib_deps = jgromes/RadioLib@~6.6.0 - https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306 + https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 @@ -150,5 +151,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file diff --git a/protobufs b/protobufs index 1c3029f28..1198b7dba 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1c3029f2868e5fc49809fd378f6c0c66aee0eaf4 +Subproject commit 1198b7dbabf9768cb0143d2897707b4c7a51a5da diff --git a/pyocd.yaml b/pyocd.yaml new file mode 100644 index 000000000..84bd9336b --- /dev/null +++ b/pyocd.yaml @@ -0,0 +1,7 @@ +# This is a config file to control pyocd ICE debugger probe options (only used for NRF52 targets with hardware debugging connections) +# for more info see FIXMEURL + +# console or telnet +semihost_console_type: telnet +enable_semihosting: True +telnet_port: 4444 diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index f45511cca..c2910007e 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -16,6 +16,8 @@ #include #ifdef RAK_4631 #include "Fusion/Fusion.h" +#include "graphics/Screen.h" +#include "graphics/ScreenFonts.h" #include #endif @@ -101,7 +103,11 @@ class AccelerometerThread : public concurrency::OSThread bmx160.getAllData(&magAccel, NULL, &gAccel); // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 10 * 1000 && millis() < 30 * 1000) { + if (millis() > 12 * 1000 && millis() < 30 * 1000) { + if (!showingScreen) { + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameCalibration); + } if (magAccel.x > highestX) highestX = magAccel.x; if (magAccel.x < lowestX) @@ -114,6 +120,9 @@ class AccelerometerThread : public concurrency::OSThread highestZ = magAccel.z; if (magAccel.z < lowestZ) lowestZ = magAccel.z; + } else if (showingScreen && millis() >= 30 * 1000) { + showingScreen = false; + screen->endAlert(); } int highestRealX = highestX - (highestX + lowestX) / 2; @@ -255,11 +264,34 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LIS3DH lis; Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; + bool BMA_IRQ = false; #ifdef RAK_4631 + bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + + static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) + { + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + display->drawString(x, y, "Calibrating\nCompass"); + int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); + + // coordinates for the center of the compass/circle + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + display->getHeight() / 2; + } else { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; + } + display->drawCircle(compassX, compassY, compassDiam / 2); + screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); + } #endif - bool BMA_IRQ = false; }; #endif \ No newline at end of file diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp index 53faae997..d9502e4f5 100644 --- a/src/BluetoothCommon.cpp +++ b/src/BluetoothCommon.cpp @@ -10,4 +10,8 @@ const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, const uint8_t FROMRADIO_UUID_16[16u] = {0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c}; const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, - 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; \ No newline at end of file + 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; +const uint8_t LEGACY_LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa, + 0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c}; +const uint8_t LOGRADIO_UUID_16[16u] = {0x47, 0x95, 0xDF, 0x8C, 0xDE, 0xE9, 0x44, 0x99, + 0x23, 0x44, 0xE6, 0x06, 0x49, 0x6E, 0x3D, 0x5A}; \ No newline at end of file diff --git a/src/BluetoothCommon.h b/src/BluetoothCommon.h index 586ffaa3c..440d13844 100644 --- a/src/BluetoothCommon.h +++ b/src/BluetoothCommon.h @@ -11,10 +11,12 @@ #define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7" #define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002" #define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453" +#define LEGACY_LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2" +#define LOGRADIO_UUID "5a3d6e49-06e6-4423-9944-e9de8cdf9547" // NRF52 wants these constants as byte arrays // Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER -extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[]; +extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[], LOGRADIO_UUID_16[]; /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level); diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index dc062fce4..a81518f31 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -187,8 +187,9 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_LONG_PRESSED: { LOG_BUTTON("Long press!\n"); powerFSM.trigger(EVENT_PRESS); - if (screen) - screen->startShutdownScreen(); + if (screen) { + screen->startAlert("Shutting down..."); + } playBeep(); break; } diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index 9df402e77..d9ecd9fe3 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,7 +26,7 @@ SOFTWARE.*/ #include "DebugConfiguration.h" -#if HAS_WIFI || HAS_ETHERNET +#if HAS_NETWORKING Syslog::Syslog(UDP &client) { diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index ca908197e..ebe9da8d4 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -1,5 +1,6 @@ -#ifndef SYSLOG_H -#define SYSLOG_H +#pragma once + +#include "configuration.h" // DEBUG LED #ifndef LED_INVERTED @@ -25,6 +26,14 @@ #include "SerialConsole.h" +// If defined we will include support for ARM ICE "semihosting" for a virtual +// console over the JTAG port (to replace the normal serial port) +// Note: Normally this flag is passed into the gcc commandline by platformio.ini. +// for an example see env:rak4631_dap. +// #ifndef USE_SEMIHOSTING +// #define USE_SEMIHOSTING +// #endif + #define DEBUG_PORT (*console) // Serial debug port #ifdef USE_SEGGER @@ -117,7 +126,7 @@ #include #endif // HAS_WIFI -#if HAS_WIFI || HAS_ETHERNET +#if HAS_NETWORKING class Syslog { @@ -152,6 +161,4 @@ class Syslog bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0))); }; -#endif // HAS_ETHERNET || HAS_WIFI - -#endif // SYSLOG_H \ No newline at end of file +#endif // HAS_ETHERNET || HAS_WIFI \ No newline at end of file diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 96aad1a9a..7d3788c4d 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -84,6 +84,58 @@ bool renameFile(const char *pathFrom, const char *pathTo) #endif } +#include + +/** + * @brief Get the list of files in a directory. + * + * This function returns a list of files in a directory. The list includes the full path of each file. + * + * @param dirname The name of the directory. + * @param levels The number of levels of subdirectories to list. + * @return A vector of strings containing the full path of each file in the directory. + */ +std::vector getFiles(const char *dirname, uint8_t levels) +{ + std::vector filenames = {}; +#ifdef FSCom + File root = FSCom.open(dirname, FILE_O_READ); + if (!root) + return filenames; + if (!root.isDirectory()) + return filenames; + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory() && !String(file.name()).endsWith(".")) { + if (levels) { +#ifdef ARCH_ESP32 + std::vector subDirFilenames = getFiles(file.path(), levels - 1); +#else + std::vector subDirFilenames = getFiles(file.name(), levels - 1); +#endif + filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end()); + file.close(); + } + } else { + meshtastic_FileInfo fileInfo = {"", file.size()}; +#ifdef ARCH_ESP32 + strcpy(fileInfo.file_name, file.path()); +#else + strcpy(fileInfo.file_name, file.name()); +#endif + if (!String(fileInfo.file_name).endsWith(".")) { + filenames.push_back(fileInfo); + } + file.close(); + } + file = root.openNextFile(); + } + root.close(); +#endif + return filenames; +} + /** * Lists the contents of a directory. * diff --git a/src/FSCommon.h b/src/FSCommon.h index ef1d3e4c1..8fbabd952 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -1,6 +1,7 @@ #pragma once #include "configuration.h" +#include // Cross platform filesystem API @@ -49,6 +50,7 @@ using namespace Adafruit_LittleFS_Namespace; void fsInit(); bool copyFile(const char *from, const char *to); bool renameFile(const char *pathFrom, const char *pathTo); +std::vector getFiles(const char *dirname, uint8_t levels); void listDir(const char *dirname, uint8_t levels, bool del); void rmDir(const char *dirname); void setupSDCard(); \ No newline at end of file diff --git a/src/GPSStatus.h b/src/GPSStatus.h index 1245d5e5d..c2ab16c86 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -124,7 +124,7 @@ class GPSStatus : public Status if (isDirty) { if (hasLock) { // In debug logs, identify position by @timestamp:stage (stage 3 = notify) - LOG_DEBUG("New GPS pos@%x:3 lat=%f, lon=%f, alt=%d, pdop=%.2f, track=%.2f, speed=%.2f, sats=%d\n", p.timestamp, + LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d\n", p.timestamp, p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5, p.ground_speed * 1e-2, p.sats_in_view); } else { diff --git a/src/Power.cpp b/src/Power.cpp index 18a527cee..cea373806 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -27,7 +27,7 @@ #if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #include "target_specific.h" -#if !MESTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include #endif #endif diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index a7bc18f1a..72e00810b 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -11,6 +11,7 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "PowerMon.h" #include "configuration.h" #include "graphics/Screen.h" #include "main.h" @@ -49,6 +50,7 @@ static bool isPowered() static void sdsEnter() { LOG_DEBUG("Enter state: SDS\n"); + powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false); } @@ -68,6 +70,7 @@ static uint32_t secsSlept; static void lsEnter() { LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs); + powerMon->clearState(meshtastic_PowerMon_State_Screen_On); screen->setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -87,8 +90,10 @@ static void lsIdle() // Briefly come out of sleep long enough to blink the led once every few seconds uint32_t sleepTime = SLEEP_TIME; + powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); setLed(false); // Never leave led on while in light sleep esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); + powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); switch (wakeCause2) { case ESP_SLEEP_WAKEUP_TIMER: @@ -144,6 +149,7 @@ static void lsExit() static void nbEnter() { LOG_DEBUG("Enter state: NB\n"); + powerMon->clearState(meshtastic_PowerMon_State_BT_On); screen->setOn(false); #ifdef ARCH_ESP32 // Only ESP32 should turn off bluetooth @@ -155,6 +161,8 @@ static void nbEnter() static void darkEnter() { + powerMon->clearState(meshtastic_PowerMon_State_BT_On); + powerMon->clearState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(true); screen->setOn(false); } @@ -162,6 +170,8 @@ static void darkEnter() static void serialEnter() { LOG_DEBUG("Enter state: SERIAL\n"); + powerMon->clearState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(false); screen->setOn(true); screen->print("Serial connected\n"); @@ -170,6 +180,7 @@ static void serialEnter() static void serialExit() { // Turn bluetooth back on when we leave serial stream API + powerMon->setState(meshtastic_PowerMon_State_BT_On); setBluetoothEnable(true); screen->print("Serial disconnected\n"); } @@ -182,6 +193,8 @@ static void powerEnter() LOG_INFO("Loss of power in Powered\n"); powerFSM.trigger(EVENT_POWER_DISCONNECTED); } else { + powerMon->setState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); // within enter() the function getState() returns the state we came from @@ -205,6 +218,8 @@ static void powerIdle() static void powerExit() { + powerMon->setState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); @@ -216,6 +231,8 @@ static void powerExit() static void onEnter() { LOG_DEBUG("Enter state: ON\n"); + powerMon->setState(meshtastic_PowerMon_State_BT_On); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); } diff --git a/src/PowerMon.cpp b/src/PowerMon.cpp new file mode 100644 index 000000000..3d28715e0 --- /dev/null +++ b/src/PowerMon.cpp @@ -0,0 +1,45 @@ +#include "PowerMon.h" +#include "NodeDB.h" + +// Use the 'live' config flag to figure out if we should be showing this message +static bool is_power_enabled(uint64_t m) +{ + return (m & config.power.powermon_enables) ? true : false; +} + +void PowerMon::setState(_meshtastic_PowerMon_State state, const char *reason) +{ +#ifdef USE_POWERMON + auto oldstates = states; + states |= state; + if (oldstates != states && is_power_enabled(state)) { + emitLog(reason); + } +#endif +} + +void PowerMon::clearState(_meshtastic_PowerMon_State state, const char *reason) +{ +#ifdef USE_POWERMON + auto oldstates = states; + states &= ~state; + if (oldstates != states && is_power_enabled(state)) { + emitLog(reason); + } +#endif +} + +void PowerMon::emitLog(const char *reason) +{ +#ifdef USE_POWERMON + // The nrf52 printf doesn't understand 64 bit ints, so if we ever reach that point this function will need to change. + LOG_INFO("S:PM:0x%08lx,%s\n", (uint32_t)states, reason); +#endif +} + +PowerMon *powerMon; + +void powerMonInit() +{ + powerMon = new PowerMon(); +} \ No newline at end of file diff --git a/src/PowerMon.h b/src/PowerMon.h new file mode 100644 index 000000000..e9f5dbd59 --- /dev/null +++ b/src/PowerMon.h @@ -0,0 +1,34 @@ +#pragma once +#include "configuration.h" + +#include "meshtastic/powermon.pb.h" + +#ifndef MESHTASTIC_EXCLUDE_POWERMON +#define USE_POWERMON // FIXME turn this only for certain builds +#endif + +/** + * The singleton class for monitoring power consumption of device + * subsystems/modes. + * + * For more information see the PowerMon docs. + */ +class PowerMon +{ + uint64_t states = 0UL; + + public: + PowerMon() {} + + // Mark entry/exit of a power consuming state + void setState(_meshtastic_PowerMon_State state, const char *reason = ""); + void clearState(_meshtastic_PowerMon_State state, const char *reason = ""); + + private: + // Emit the coded log message + void emitLog(const char *reason); +}; + +extern PowerMon *powerMon; + +void powerMonInit(); \ No newline at end of file diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index e09e5fe30..9c3dcdc98 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -3,6 +3,8 @@ #include "RTC.h" #include "concurrency/OSThread.h" #include "configuration.h" +#include "main.h" +#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -14,12 +16,7 @@ #include "platform/portduino/PortduinoGlue.h" #endif -/** - * A printer that doesn't go anywhere - */ -NoopPrint noopPrint; - -#if HAS_WIFI || HAS_ETHERNET +#if HAS_NETWORKING extern Syslog syslog; #endif void RedirectablePrint::rpInit() @@ -38,7 +35,7 @@ void RedirectablePrint::setDestination(Print *_dest) size_t RedirectablePrint::write(uint8_t c) { // Always send the characters to our segger JTAG debugger -#ifdef SEGGER_STDOUT_CH +#ifdef USE_SEGGER SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif @@ -49,7 +46,7 @@ size_t RedirectablePrint::write(uint8_t c) // serial port said (which could be zero) } -size_t RedirectablePrint::vprintf(const char *format, va_list arg) +size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) { va_list copy; static char printBuf[160]; @@ -65,25 +62,200 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg) len = sizeof(printBuf) - 1; printBuf[sizeof(printBuf) - 2] = '\n'; } - + for (size_t f = 0; f < len; f++) { + if (!std::isprint(static_cast(printBuf[f])) && printBuf[f] != '\n') + printBuf[f] = '#'; + } + if (logLevel != nullptr) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + } len = Print::write(printBuf, len); + Print::write("\u001b[0m", 5); return len; } -size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) +void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, va_list arg) +{ + size_t r = 0; + + // Cope with 0 len format strings, but look for new line terminator + bool hasNewline = *format && format[strlen(format) - 1] == '\n'; + + // If we are the first message on a report, include the header + if (!isContinuationMessage) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + // hms += tz.tz_dsttime * SEC_PER_HOUR; + // hms -= tz.tz_minuteswest * SEC_PER_MIN; + // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into h:m:s + int hour = hms / SEC_PER_HOUR; + int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN +#ifdef ARCH_PORTDUINO + ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); +#else + printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); +#endif + } else +#ifdef ARCH_PORTDUINO + ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); +#else + printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); +#endif + + auto thread = concurrency::OSThread::currentThread; + if (thread) { + print("["); + // printf("%p ", thread); + // assert(thread->ThreadName.length()); + print(thread->ThreadName); + print("] "); + } + } + r += vprintf(logLevel, format, arg); + + isContinuationMessage = !hasNewline; +} + +void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg) +{ +#if HAS_NETWORKING && !defined(ARCH_PORTDUINO) + // if syslog is in use, collect the log messages and send them to syslog + if (syslog.isEnabled()) { + int ll = 0; + switch (logLevel[0]) { + case 'D': + ll = SYSLOG_DEBUG; + break; + case 'I': + ll = SYSLOG_INFO; + break; + case 'W': + ll = SYSLOG_WARN; + break; + case 'E': + ll = SYSLOG_ERR; + break; + case 'C': + ll = SYSLOG_CRIT; + break; + default: + ll = 0; + } + auto thread = concurrency::OSThread::currentThread; + if (thread) { + syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg); + } else { + syslog.vlogf(ll, format, arg); + } + } +#endif +} + +void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) +{ +#if !MESHTASTIC_EXCLUDE_BLUETOOTH + if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { + bool isBleConnected = false; +#ifdef ARCH_ESP32 + isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); +#elif defined(ARCH_NRF52) + isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); +#endif + if (isBleConnected) { + char *message; + size_t initialLen; + size_t len; + initialLen = strlen(format); + message = new char[initialLen + 1]; + len = vsnprintf(message, initialLen + 1, format, arg); + if (len > initialLen) { + delete[] message; + message = new char[len + 1]; + vsnprintf(message, len + 1, format, arg); + } + auto thread = concurrency::OSThread::currentThread; + meshtastic_LogRecord logRecord = meshtastic_LogRecord_init_zero; + logRecord.level = getLogLevel(logLevel); + strcpy(logRecord.message, message); + if (thread) + strcpy(logRecord.source, thread->ThreadName.c_str()); + logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true); + + uint8_t *buffer = new uint8_t[meshtastic_LogRecord_size]; + size_t size = pb_encode_to_bytes(buffer, meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); +#ifdef ARCH_ESP32 + nimbleBluetooth->sendLog(buffer, size); +#elif defined(ARCH_NRF52) + nrf52Bluetooth->sendLog(buffer, size); +#endif + delete[] message; + delete[] buffer; + } + } +#else + (void)logLevel; + (void)format; + (void)arg; +#endif +} + +meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel) +{ + meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset + switch (logLevel[0]) { + case 'D': + ll = meshtastic_LogRecord_Level_DEBUG; + break; + case 'I': + ll = meshtastic_LogRecord_Level_INFO; + break; + case 'W': + ll = meshtastic_LogRecord_Level_WARNING; + break; + case 'E': + ll = meshtastic_LogRecord_Level_ERROR; + break; + case 'C': + ll = meshtastic_LogRecord_Level_CRITICAL; + break; + } + return ll; +} + +void RedirectablePrint::log(const char *logLevel, const char *format, ...) { #ifdef ARCH_PORTDUINO if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - return 0; + return; else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - return 0; + return; else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - return 0; + return; #endif if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - return 0; + return; } - size_t r = 0; + #ifdef HAS_FREE_RTOS if (inDebugPrint != nullptr && xSemaphoreTake(inDebugPrint, portMAX_DELAY) == pdTRUE) { #else @@ -94,81 +266,11 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) va_list arg; va_start(arg, format); - // Cope with 0 len format strings, but look for new line terminator - bool hasNewline = *format && format[strlen(format) - 1] == '\n'; - - // If we are the first message on a report, include the header - if (!isContinuationMessage) { - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile - if (rtc_sec > 0) { - long hms = rtc_sec % SEC_PER_DAY; - // hms += tz.tz_dsttime * SEC_PER_HOUR; - // hms -= tz.tz_minuteswest * SEC_PER_MIN; - // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) - hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; - - // Tear apart hms into h:m:s - int hour = hms / SEC_PER_HOUR; - int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; - int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN -#ifdef ARCH_PORTDUINO - r += ::printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); -#else - r += printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); -#endif - } else -#ifdef ARCH_PORTDUINO - r += ::printf("%s | ??:??:?? %u ", logLevel, millis() / 1000); -#else - r += printf("%s | ??:??:?? %u ", logLevel, millis() / 1000); -#endif - - auto thread = concurrency::OSThread::currentThread; - if (thread) { - print("["); - // printf("%p ", thread); - // assert(thread->ThreadName.length()); - print(thread->ThreadName); - print("] "); - } - } - r += vprintf(format, arg); - -#if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO) - // if syslog is in use, collect the log messages and send them to syslog - if (syslog.isEnabled()) { - int ll = 0; - switch (logLevel[0]) { - case 'D': - ll = SYSLOG_DEBUG; - break; - case 'I': - ll = SYSLOG_INFO; - break; - case 'W': - ll = SYSLOG_WARN; - break; - case 'E': - ll = SYSLOG_ERR; - break; - case 'C': - ll = SYSLOG_CRIT; - break; - default: - ll = 0; - } - auto thread = concurrency::OSThread::currentThread; - if (thread) { - syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg); - } else { - syslog.vlogf(ll, format, arg); - } - } -#endif + log_to_serial(logLevel, format, arg); + log_to_syslog(logLevel, format, arg); + log_to_ble(logLevel, format, arg); va_end(arg); - - isContinuationMessage = !hasNewline; #ifdef HAS_FREE_RTOS xSemaphoreGive(inDebugPrint); #else @@ -176,7 +278,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif } - return r; + return; } void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len) diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h index 31cc1b6ef..23ae3c44d 100644 --- a/src/RedirectablePrint.h +++ b/src/RedirectablePrint.h @@ -1,6 +1,7 @@ #pragma once #include "../freertosinc.h" +#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -41,23 +42,21 @@ class RedirectablePrint : public Print * log message. Otherwise we assume more prints will come before the log message ends. This * allows you to call logDebug a few times to build up a single log message line if you wish. */ - size_t log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); + void log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); /** like printf but va_list based */ - size_t vprintf(const char *format, va_list arg); + size_t vprintf(const char *logLevel, const char *format, va_list arg); void hexDump(const char *logLevel, unsigned char *buf, uint16_t len); std::string mt_sprintf(const std::string fmt_str, ...); -}; -class NoopPrint : public Print -{ - public: - virtual size_t write(uint8_t c) { return 1; } -}; + protected: + /// Subclasses can override if they need to change how we format over the serial port + virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); -/** - * A printer that doesn't go anywhere - */ -extern NoopPrint noopPrint; \ No newline at end of file + private: + void log_to_syslog(const char *logLevel, const char *format, va_list arg); + void log_to_ble(const char *logLevel, const char *format, va_list arg); + meshtastic_LogRecord_Level getLogLevel(const char *logLevel); +}; \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index cf6375585..9a9331e47 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -28,7 +28,7 @@ void consolePrintf(const char *format, ...) { va_list arg; va_start(arg, format); - console->vprintf(format, arg); + console->vprintf(nullptr, format, arg); va_end(arg); console->flush(); } @@ -38,7 +38,6 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con assert(!console); console = this; canWrite = false; // We don't send packets to our port until it has talked to us first - // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks #ifdef RP2040_SLOW_CLOCK Port.setTX(SERIAL2_TX); @@ -85,13 +84,40 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled. if (config.has_lora && config.device.serial_enabled) { - // Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets - if (!config.device.debug_log_enabled) - setDestination(&noopPrint); + // Switch to protobufs for log messages + usingProtobufs = true; canWrite = true; return StreamAPI::handleToRadio(buf, len); } else { return false; } +} + +void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) +{ + if (usingProtobufs) { + meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset + switch (logLevel[0]) { + case 'D': + ll = meshtastic_LogRecord_Level_DEBUG; + break; + case 'I': + ll = meshtastic_LogRecord_Level_INFO; + break; + case 'W': + ll = meshtastic_LogRecord_Level_WARNING; + break; + case 'E': + ll = meshtastic_LogRecord_Level_ERROR; + break; + case 'C': + ll = meshtastic_LogRecord_Level_CRITICAL; + break; + } + + auto thread = concurrency::OSThread::currentThread; + emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg); + } else + RedirectablePrint::log_to_serial(logLevel, format, arg); } \ No newline at end of file diff --git a/src/SerialConsole.h b/src/SerialConsole.h index f8891ba14..f1e636c9d 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -8,6 +8,11 @@ */ class SerialConsole : public StreamAPI, public RedirectablePrint, private concurrency::OSThread { + /** + * If true we are talking to a smart host and all messages (including log messages) must be framed as protobufs. + */ + bool usingProtobufs = false; + public: SerialConsole(); @@ -31,10 +36,13 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur protected: /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() override; + + /// Possibly switch to protobufs if we see a valid protobuf message + virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); }; // A simple wrapper to allow non class aware code write to the console void consolePrintf(const char *format, ...); void consoleInit(); -extern SerialConsole *console; +extern SerialConsole *console; \ No newline at end of file diff --git a/src/commands.h b/src/commands.h index 03ede5982..f2b783010 100644 --- a/src/commands.h +++ b/src/commands.h @@ -8,13 +8,11 @@ enum class Cmd { SET_ON, SET_OFF, ON_PRESS, - START_BLUETOOTH_PIN_SCREEN, + START_ALERT_FRAME, + STOP_ALERT_FRAME, START_FIRMWARE_UPDATE_SCREEN, - STOP_BLUETOOTH_PIN_SCREEN, STOP_BOOT_SCREEN, PRINT, - START_SHUTDOWN_SCREEN, - START_REBOOT_SCREEN, SHOW_PREV_FRAME, SHOW_NEXT_FRAME }; \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 3d10feeaa..aad4ac457 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -242,9 +242,6 @@ along with this program. If not, see . #define HAS_BLUETOOTH 0 #endif -#include "DebugConfiguration.h" -#include "RF95Configuration.h" - #ifndef HW_VENDOR #error HW_VENDOR must be defined #endif @@ -261,6 +258,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_GPS 1 #define MESHTASTIC_EXCLUDE_SCREEN 1 #define MESHTASTIC_EXCLUDE_MQTT 1 +#define MESHTASTIC_EXCLUDE_POWERMON 1 #endif // Turn off all optional modules @@ -281,6 +279,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_WAYPOINT 1 #define MESHTASTIC_EXCLUDE_INPUTBROKER 1 #define MESHTASTIC_EXCLUDE_SERIAL 1 +#define MESHTASTIC_EXCLUDE_POWERSTRESS 1 #endif // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) @@ -290,6 +289,9 @@ along with this program. If not, see . #define HAS_WIFI 0 #endif +// Allow code that needs internet to just check HAS_NETWORKING rather than HAS_WIFI || HAS_ETHERNET +#define HAS_NETWORKING (HAS_WIFI || HAS_ETHERNET) + // // Turn off Bluetooth #ifdef MESHTASTIC_EXCLUDE_BLUETOOTH #undef HAS_BLUETOOTH @@ -308,4 +310,7 @@ along with this program. If not, see . #ifdef MESHTASTIC_EXCLUDE_SCREEN #undef HAS_SCREEN #define HAS_SCREEN 0 -#endif \ No newline at end of file +#endif + +#include "DebugConfiguration.h" +#include "RF95Configuration.h" \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 86408b8d2..8738e2722 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -314,7 +314,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case SHT31_4x_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2) { + if (registerValue == 0x11a2 || registerValue == 0x11da) { type = SHT4X; LOG_INFO("SHT4X sensor found\n"); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { @@ -402,4 +402,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} \ No newline at end of file +} diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 40ee4ea03..fe70bcdcd 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -3,6 +3,7 @@ #include "Default.h" #include "GPS.h" #include "NodeDB.h" +#include "PowerMon.h" #include "RTC.h" #include "main.h" // pmu_found @@ -784,6 +785,22 @@ GPS::~GPS() notifyGPSSleepObserver.observe(¬ifyGPSSleep); } +const char *GPS::powerStateToString() +{ + switch (powerState) { + case GPS_OFF: + return "OFF"; + case GPS_IDLE: + return "IDLE"; + case GPS_STANDBY: + return "STANDBY"; + case GPS_ACTIVE: + return "ACTIVE"; + default: + return "UNKNOWN"; + } +} + void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) { // Record the current powerState @@ -798,16 +815,19 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) else powerState = GPS_OFF; - LOG_DEBUG("GPS::powerState=%d\n", powerState); + LOG_DEBUG("GPS::powerState=%s\n", powerStateToString()); // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out. if (!on && powerState == GPS_IDLE) return; if (on) { + powerMon->setState(meshtastic_PowerMon_State_GPS_Active); clearBuffer(); // drop any old data waiting in the buffer before re-enabling if (en_gpio) digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time + } else { + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); } isInPowersave = !on; if (!standbyOnly && en_gpio != 0 && diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 91548ce2c..7fa37cb7a 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -289,6 +289,8 @@ class GPS : private concurrency::OSThread // delay counter to allow more sats before fixed position stops GPS thread uint8_t fixeddelayCtr = 0; + const char *powerStateToString(); + protected: GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN; }; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 60168cffc..f724ddd3d 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -75,7 +75,6 @@ namespace graphics // A text message frame + debug frame + all the node infos FrameCallback *normalFrames; static uint32_t targetFramerate = IDLE_FRAMERATE; -static char btPIN[16] = "888888"; uint32_t logo_timeout = 5000; // 4 seconds for EACH logo @@ -108,15 +107,39 @@ GeoCoord geoCoord; static bool heartbeat = false; #endif -static uint16_t displayWidth, displayHeight; - -#define SCREEN_WIDTH displayWidth -#define SCREEN_HEIGHT displayHeight +// Quick access to screen dimensions from static drawing functions +// DEPRECATED. To-do: move static functions inside Screen class +#define SCREEN_WIDTH display->getWidth() +#define SCREEN_HEIGHT display->getHeight() #include "graphics/ScreenFonts.h" #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) +/// Check if the display can render a string (detect special chars; emoji) +static bool haveGlyphs(const char *str) +{ +#if defined(OLED_UA) || defined(OLED_RU) + // Don't want to make any assumptions about custom language support + return true; +#endif + + // Check each character with the lookup function for the OLED library + // We're not really meant to use this directly.. + bool have = true; + for (uint16_t i = 0; i < strlen(str); i++) { + uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); + // If font doesn't support a character, it is substituted for ¿ + if (result == 191 && (uint8_t)str[i] != 191) { + have = false; + break; + } + } + + LOG_DEBUG("haveGlyphs=%d\n", have); + return have; +} + /** * Draw the icon with extra info printed around the corners */ @@ -140,13 +163,15 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + // Draw version and short name in upper right + char buf[25]; + snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : ""); + + display->setTextAlignment(TEXT_ALIGN_RIGHT); + display->drawString(x + SCREEN_WIDTH, y + 0, buf); screen->forceDisplay(); - // FIXME - draw serial # somewhere? + + display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code } static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -181,14 +206,15 @@ static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDi if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); - // Draw version in upper right - char buf[16]; - snprintf(buf, sizeof(buf), "%s", - xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long - display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); + // Draw version and shortname in upper right + char buf[25]; + snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : ""); + + display->setTextAlignment(TEXT_ALIGN_RIGHT); + display->drawString(x + SCREEN_WIDTH, y + 0, buf); screen->forceDisplay(); - // FIXME - draw serial # somewhere? + display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code } static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -198,7 +224,7 @@ static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i drawOEMIconScreen(region, display, state, x, y); } -static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) +void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) { uint16_t x_offset = display->width() / 2; display->setTextAlignment(TEXT_ALIGN_CENTER); @@ -206,20 +232,6 @@ static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->drawString(x_offset + x, 26 + y, message); } -static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ -#ifdef ARCH_ESP32 - if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { - drawFrameText(display, state, x, y, "Resuming..."); - } else -#endif - { - // Draw region in upper left - const char *region = myRegion ? myRegion->name : NULL; - drawIconScreen(region, display, state, x, y); - } -} - // Used on boot when a certificate is being created static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -277,40 +289,19 @@ static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) } } -/// Check if the display can render a string (detect special chars; emoji) -static bool haveGlyphs(const char *str) -{ -#if defined(OLED_UA) || defined(OLED_RU) - // Don't want to make any assumptions about custom language support - return true; -#endif - - // Check each character with the lookup function for the OLED library - // We're not really meant to use this directly.. - bool have = true; - for (uint16_t i = 0; i < strlen(str); i++) { - uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); - // If font doesn't support a character, it is substituted for ¿ - if (result == 191 && (uint8_t)str[i] != 191) { - have = false; - break; - } - } - - LOG_DEBUG("haveGlyphs=%d\n", have); - return have; -} - #ifdef USE_EINK /// Used on eink displays while in deep sleep static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { + // Next frame should use full-refresh, and block while running, else device will sleep before async callback EINK_ADD_FRAMEFLAG(display, COSMETIC); EINK_ADD_FRAMEFLAG(display, BLOCKING); LOG_DEBUG("Drawing deep sleep screen\n"); - drawIconScreen("Sleeping...", display, state, x, y); + + // Display displayStr on the screen + drawIconScreen("Sleeping", display, state, x, y); } /// Used on eink displays when screen updates are paused @@ -375,7 +366,7 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int // in the array of "drawScreen" functions; however, // the passed-state doesn't quite reflect the "current" // screen, so we have to detect it. - if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) { + if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == TransitionRelationship_INCOMING) { // if we're transitioning from the end of the frame list back around to the first // frame, then we want this to be `0` module_frame = state->transitionFrameTarget; @@ -389,31 +380,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int pi.drawFrame(display, state, x, y); } -static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - 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_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); -} - static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_CENTER); @@ -1091,45 +1057,8 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state #endif } -/// Draw the last waypoint we received -static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - static char tempBuf[237]; - - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); - - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_SMALL); - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - } - - uint32_t seconds = sinceReceived(&mp); - uint32_t minutes = seconds / 60; - uint32_t hours = minutes / 60; - uint32_t days = hours / 24; - - if (config.display.heading_bold) { - display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", - screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - } - display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), - (node && node->has_user) ? node->user.short_name : "???"); - - display->setColor(WHITE); - meshtastic_Waypoint scratch; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { - snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); - display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); - } -} - /// Draw a series of fields in a column, wrapping to multiple columns if needed -static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) +void Screen::drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) { // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1309,56 +1238,13 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } } #endif -namespace -{ - -/// A basic 2D point class for drawing -class Point -{ - public: - float x, y; - - Point(float _x, float _y) : x(_x), y(_y) {} - - /// Apply a rotation around zero (standard rotation matrix math) - void rotate(float radian) - { - float cos = cosf(radian), sin = sinf(radian); - float rx = x * cos + y * sin, ry = -x * sin + y * cos; - - x = rx; - y = ry; - } - - void translate(int16_t dx, int dy) - { - x += dx; - y += dy; - } - - void scale(float f) - { - // We use -f here to counter the flip that happens - // on the y axis when drawing and rotating on screen - x *= f; - y *= -f; - } -}; - -} // namespace - -static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) -{ - d->drawLine(p1.x, p1.y, p2.x, p2.y); -} - /** * Given a recent lat/lon return a guess of the heading the user is walking on. * * We keep a series of "after you've gone 10 meters, what is your heading since * the last reference point?" */ -static float estimatedHeading(double lat, double lon) +float Screen::estimatedHeading(double lat, double lon) { static double oldLat, oldLon; static float b; @@ -1382,38 +1268,13 @@ static float estimatedHeading(double lat, double lon) return b; } -static uint16_t getCompassDiam(OLEDDisplay *display) -{ - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (display->getWidth() > (display->getHeight() - offset)) { - diam = display->getHeight() - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (display->getWidth() * 2 / 3)) { - diam = display->getWidth() * 2 / 3; - } - } else { - diam = display->getWidth(); - if (diam > ((display->getHeight() - offset) * 2 / 3)) { - diam = (display->getHeight() - offset) * 2 / 3; - } - } - - return diam - 20; -}; - /// We will skip one node - the one for us, so we just blindly loop over all /// nodes static size_t nodeIndex; static int8_t prevFrame = -1; // Draw the arrow pointing to a node's location -static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) +void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian) { Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; @@ -1423,16 +1284,45 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp for (int i = 0; i < 4; i++) { arrowPoints[i]->rotate(headingRadian); - arrowPoints[i]->scale(getCompassDiam(display) * 0.6); + arrowPoints[i]->scale(compassDiam * 0.6); arrowPoints[i]->translate(compassX, compassY); } - drawLine(display, tip, tail); - drawLine(display, leftArrow, tip); - drawLine(display, rightArrow, tip); + display->drawLine(tip.x, tip.y, tail.x, tail.y); + display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y); + display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y); } -// Draw north -static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) +// Get a string representation of the time passed since something happened +void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) +{ + // Use an absolute timestamp in some cases. + // Particularly useful with E-Ink displays. Static UI, fewer refreshes. + uint8_t timestampHours, timestampMinutes; + int32_t daysAgo; + bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); + + if (agoSecs < 120) // last 2 mins? + snprintf(timeStr, maxLength, "%u seconds ago", agoSecs); + // -- if suitable for timestamp -- + else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes + snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); + else if (useTimestamp && daysAgo == 0) // Today + snprintf(timeStr, maxLength, "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); + else if (useTimestamp && daysAgo == 1) // Yesterday + snprintf(timeStr, maxLength, "Seen yesterday"); + else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) + snprintf(timeStr, maxLength, "%li days ago", (long)daysAgo); + // -- if using time delta instead -- + else if (agoSecs < 120 * 60) // last 2 hrs + snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60); + // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. + else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) + snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60); + else + snprintf(timeStr, maxLength, "unknown age"); +} + +void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) { // If north is supposed to be at the top of the compass we want rotation to be +0 if (config.display.compass_north_top) @@ -1442,19 +1332,43 @@ static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t com Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); + for (int i = 0; i < 4; i++) { // North on compass will be negative of heading rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(getCompassDiam(display)); + rosePoints[i]->scale(compassDiam); rosePoints[i]->translate(compassX, compassY); } - drawLine(display, N1, N3); - drawLine(display, N2, N4); - drawLine(display, N1, N4); + display->drawLine(N1.x, N1.y, N3.x, N3.y); + display->drawLine(N2.x, N2.y, N4.x, N4.y); + display->drawLine(N1.x, N1.y, N4.x, N4.y); } -/// Convert an integer GPS coords to a floating point -#define DegD(i) (i * 1e-7) +uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight) +{ + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (displayWidth > (displayHeight - offset)) { + diam = displayHeight - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (displayWidth * 2 / 3)) { + diam = displayWidth * 2 / 3; + } + } else { + diam = displayWidth; + if (diam > ((displayHeight - offset) * 2 / 3)) { + diam = (displayHeight - offset) * 2 / 3; + } + } + + return diam - 20; +}; static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -1494,34 +1408,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); } - uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; - - // Use an absolute timestamp in some cases. - // Particularly useful with E-Ink displays. Static UI, fewer refreshes. - uint8_t timestampHours, timestampMinutes; - int32_t daysAgo; - bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); - - if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); - // -- if suitable for timestamp -- - else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); - else if (useTimestamp && daysAgo == 0) // Today - snprintf(lastStr, sizeof(lastStr), "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); - else if (useTimestamp && daysAgo == 1) // Yesterday - snprintf(lastStr, sizeof(lastStr), "Seen yesterday"); - else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) - snprintf(lastStr, sizeof(lastStr), "%li days ago", (long)daysAgo); - // -- if using time delta instead -- - else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); - // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. - else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) - snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); - else - snprintf(lastStr, sizeof(lastStr), "unknown age"); + screen->getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr)); static char distStr[20]; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { @@ -1532,13 +1420,14 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); const char *fields[] = {username, lastStr, signalStr, distStr, NULL}; int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); // coordinates for the center of the compass/circle if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5; compassY = y + SCREEN_HEIGHT / 2; } else { - compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; + compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5; compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; } bool hasNodeHeading = false; @@ -1549,8 +1438,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (screen->hasHeading()) myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians else - myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - drawCompassNorth(display, compassX, compassY, myHeading); + myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + screen->drawCompassNorth(display, compassX, compassY, myHeading); if (hasValidPosition(node)) { // display direction toward node @@ -1577,7 +1466,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // If the top of the compass is not a static north we need adjust bearingToOther based on heading if (!config.display.compass_north_top) bearingToOther -= myHeading; - drawNodeHeading(display, compassX, compassY, bearingToOther); + screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); } } if (!hasNodeHeading) { @@ -1587,13 +1476,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // hasValidPosition(node)); display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); } - display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + display->drawCircle(compassX, compassY, compassDiam / 2); if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { display->setColor(BLACK); } // Must be after distStr is populated - drawColumns(display, x, y, fields); + screen->drawColumns(display, x, y, fields); } Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) @@ -1741,9 +1630,19 @@ void Screen::setup() // Add frames. EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); - static FrameCallback bootFrames[] = {drawBootScreen}; - static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); - ui->setFrames(bootFrames, bootFrameCount); + alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { +#ifdef ARCH_ESP32 + if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { + drawFrameText(display, state, x, y, "Resuming..."); + } else +#endif + { + // Draw region in upper left + const char *region = myRegion ? myRegion->name : NULL; + drawIconScreen(region, display, state, x, y); + } + }; + ui->setFrames(alertFrames, 1); // No overlays. ui->setOverlays(nullptr, 0); @@ -1916,13 +1815,22 @@ int32_t Screen::runOnce() case Cmd::SHOW_NEXT_FRAME: handleShowNextFrame(); break; - case Cmd::START_BLUETOOTH_PIN_SCREEN: - handleStartBluetoothPinScreen(cmd.bluetooth_pin); + case Cmd::START_ALERT_FRAME: { + showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away + showingNormalScreen = false; + alertFrames[0] = alertFrame; +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) +#endif + setFrameImmediateDraw(alertFrames); break; + } case Cmd::START_FIRMWARE_UPDATE_SCREEN: handleStartFirmwareUpdateScreen(); break; - case Cmd::STOP_BLUETOOTH_PIN_SCREEN: + case Cmd::STOP_ALERT_FRAME: case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame setFrames(); @@ -1931,12 +1839,6 @@ int32_t Screen::runOnce() handlePrint(cmd.print_text); free(cmd.print_text); break; - case Cmd::START_SHUTDOWN_SCREEN: - handleShutdownScreen(); - break; - case Cmd::START_REBOOT_SCREEN: - handleRebootScreen(); - break; default: LOG_ERROR("Invalid screen cmd\n"); } @@ -2133,10 +2035,6 @@ void Screen::setFrames() if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { normalFrames[numframes++] = drawTextMessageFrame; } - // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules - if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) { - normalFrames[numframes++] = drawWaypointFrame; - } // then all the nodes // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens @@ -2176,17 +2074,6 @@ void Screen::setFrames() setFastFramerate(); // Draw ASAP } -void Screen::handleStartBluetoothPinScreen(uint32_t pin) -{ - LOG_DEBUG("showing bluetooth screen\n"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - - static FrameCallback frames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06u", pin); - setFrameImmediateDraw(frames); -} - void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) { ui->disableAllIndicators(); @@ -2194,41 +2081,6 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) setFastFramerate(); } -void Screen::handleShutdownScreen() -{ - LOG_DEBUG("showing shutdown screen\n"); - showingNormalScreen = false; -#ifdef USE_EINK - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please - EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update - handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) -#endif - - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - drawFrameText(display, state, x, y, "Shutting down..."); - }; - static FrameCallback frames[] = {frame}; - - setFrameImmediateDraw(frames); -} - -void Screen::handleRebootScreen() -{ - LOG_DEBUG("showing reboot screen\n"); - showingNormalScreen = false; -#ifdef USE_EINK - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please - EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update - handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?) -#endif - - auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - drawFrameText(display, state, x, y, "Rebooting..."); - }; - static FrameCallback frames[] = {frame}; - setFrameImmediateDraw(frames); -} - void Screen::handleStartFirmwareUpdateScreen() { LOG_DEBUG("showing firmware screen\n"); @@ -2245,7 +2097,7 @@ void Screen::blink() uint8_t count = 10; dispdev->setBrightness(254); while (count > 0) { - dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight()); dispdev->display(); delay(50); dispdev->clear(); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index f4d719715..83c9a7a94 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -21,11 +21,13 @@ class Screen void print(const char *) {} void doDeepSleep() {} void forceDisplay(bool forceUiUpdate = false) {} - void startBluetoothPinScreen(uint32_t pin) {} - void stopBluetoothPinScreen() {} - void startRebootScreen() {} - void startShutdownScreen() {} void startFirmwareUpdateScreen() {} + void increaseBrightness() {} + void decreaseBrightness() {} + void setFunctionSymbal(std::string) {} + void removeFunctionSymbal(std::string) {} + void startAlert(const char *) {} + void endAlert() {} }; } // namespace graphics #else @@ -34,6 +36,8 @@ class Screen #include #include "../configuration.h" +#include "gps/GeoCoord.h" +#include "graphics/ScreenFonts.h" #ifdef USE_ST7567 #include @@ -82,6 +86,46 @@ class Screen #define SEGMENT_WIDTH 16 #define SEGMENT_HEIGHT 4 +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) + +namespace +{ +/// A basic 2D point class for drawing +class Point +{ + public: + float x, y; + + Point(float _x, float _y) : x(_x), y(_y) {} + + /// Apply a rotation around zero (standard rotation matrix math) + void rotate(float radian) + { + float cos = cosf(radian), sin = sinf(radian); + float rx = x * cos + y * sin, ry = -x * sin + y * cos; + + x = rx; + y = ry; + } + + void translate(int16_t dx, int dy) + { + x += dx; + y += dy; + } + + void scale(float f) + { + // We use -f here to counter the flip that happens + // on the y axis when drawing and rotating on screen + x *= f; + y *= -f; + } +}; + +} // namespace + namespace graphics { @@ -166,20 +210,49 @@ class Screen : public concurrency::OSThread void blink(); + void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *); + + void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength); + + // Draw north + void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading); + + static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight); + + float estimatedHeading(double lat, double lon); + + void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian); + + void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); + /// Handle button press, trackball or swipe action) void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); } void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); } void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); } - /// Starts showing the Bluetooth PIN screen. - // - // Switches over to a static frame showing the Bluetooth pairing screen - // with the PIN. - void startBluetoothPinScreen(uint32_t pin) + // generic alert start + void startAlert(FrameCallback _alertFrame) + { + alertFrame = _alertFrame; + ScreenCmd cmd; + cmd.cmd = Cmd::START_ALERT_FRAME; + enqueueCmd(cmd); + } + + void startAlert(const char *_alertMessage) + { + startAlert([_alertMessage](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + uint16_t x_offset = display->width() / 2; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, 26 + y, _alertMessage); + }); + } + + void endAlert() { ScreenCmd cmd; - cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN; - cmd.bluetooth_pin = pin; + cmd.cmd = Cmd::STOP_ALERT_FRAME; enqueueCmd(cmd); } @@ -190,20 +263,6 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } - void startShutdownScreen() - { - ScreenCmd cmd; - cmd.cmd = Cmd::START_SHUTDOWN_SCREEN; - enqueueCmd(cmd); - } - - void startRebootScreen() - { - ScreenCmd cmd; - cmd.cmd = Cmd::START_REBOOT_SCREEN; - enqueueCmd(cmd); - } - // Function to allow the AccelerometerThread to set the heading if a sensor provides it // Mutex needed? void setHeading(long _heading) @@ -222,9 +281,6 @@ class Screen : public concurrency::OSThread void setFunctionSymbal(std::string sym); void removeFunctionSymbal(std::string sym); - /// Stops showing the bluetooth PIN screen. - void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); } - /// Stops showing the boot screen. void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); } @@ -358,7 +414,13 @@ class Screen : public concurrency::OSThread bool isAUTOOled = false; + // Screen dimensions (for convenience) + // Defined during Screen::setup + uint16_t displayWidth = 0; + uint16_t displayHeight = 0; + private: + FrameCallback alertFrames[1]; struct ScreenCmd { Cmd cmd; union { @@ -384,11 +446,8 @@ class Screen : public concurrency::OSThread void handleOnPress(); void handleShowNextFrame(); void handleShowPrevFrame(); - void handleStartBluetoothPinScreen(uint32_t pin); void handlePrint(const char *text); void handleStartFirmwareUpdateScreen(); - void handleShutdownScreen(); - void handleRebootScreen(); /// Rebuilds our list of frames (screens) to default ones. void setFrames(); @@ -426,6 +485,9 @@ class Screen : public concurrency::OSThread bool digitalWatchFace = true; #endif + /// callback for current alert frame + FrameCallback alertFrame; + /// Queue of commands to execute in doTask. TypedQueue cmdQueue; /// Whether we are using a display @@ -452,4 +514,5 @@ class Screen : public concurrency::OSThread }; } // namespace graphics + #endif \ No newline at end of file diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 4b34563f7..8a48d053e 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -28,8 +28,8 @@ #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #endif -#define fontHeight(font) ((font)[1] + 1) // height is position 1 +#define _fontHeight(font) ((font)[1] + 1) // height is position 1 -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ddb99568d..1e0d998e1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "PowerMon.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" @@ -48,7 +49,6 @@ NimbleBluetooth *nimbleBluetooth = nullptr; #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" NRF52Bluetooth *nrf52Bluetooth = nullptr; -; #endif #if HAS_WIFI @@ -155,6 +155,7 @@ bool isVibrating = false; bool eink_found = true; uint32_t serialSinceMsec; +bool pauseBluetoothLogging = false; bool pmu_found; @@ -173,7 +174,7 @@ const char *getDeviceName() static char name[20]; snprintf(name, sizeof(name), "%02x%02x", dmac[4], dmac[5]); // if the shortname exists and is NOT the new default of ab3c, use it for BLE name. - if ((owner.short_name != NULL) && (strcmp(owner.short_name, name) != 0)) { + if (strcmp(owner.short_name, name) != 0) { snprintf(name, sizeof(name), "%s_%02x%02x", owner.short_name, dmac[4], dmac[5]); } else { snprintf(name, sizeof(name), "Meshtastic_%02x%02x", dmac[4], dmac[5]); @@ -214,6 +215,14 @@ __attribute__((weak, noinline)) bool loopCanSleep() return true; } +/** + * Print info as a structured log message (for automated log processing) + */ +void printInfo() +{ + LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION)); +} + void setup() { concurrency::hasBeenSetup = true; @@ -221,7 +230,7 @@ void setup() meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; -#ifdef SEGGER_STDOUT_CH +#ifdef USE_SEGGER auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM; #ifdef NRF52840_XXAA auto buflen = 4096; // this board has a fair amount of ram @@ -234,6 +243,7 @@ void setup() #ifdef DEBUG_PORT consoleInit(); // Set serial baud rate and init our mesh console #endif + powerMonInit(); serialSinceMsec = millis(); @@ -553,7 +563,7 @@ void setup() #endif // Hello - LOG_INFO("Meshtastic hwvendor=%d, swver=%s\n", HW_VENDOR, optstr(APP_VERSION)); + printInfo(); #ifdef ARCH_ESP32 esp32Setup(); @@ -930,7 +940,7 @@ void setup() nodeDB->saveToDisk(SEGMENT_CONFIG); if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting\n"); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = millis() + 5000; } } diff --git a/src/main.h b/src/main.h index 2ef7edb3a..ea2d80f94 100644 --- a/src/main.h +++ b/src/main.h @@ -85,6 +85,8 @@ extern uint32_t serialSinceMsec; // This will suppress the current delay and instead try to run ASAP. extern bool runASAP; +extern bool pauseBluetoothLogging; + void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearBonds(), enterDfuMode(); meshtastic_DeviceMetadata getDeviceMetadata(); diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index dd547a6f1..0fdde5277 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -20,9 +20,8 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p) bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record - printPacket("Ignoring incoming msg, because we've already seen it", p); + printPacket("Ignoring incoming msg we've already seen", p); if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && - config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! Router::cancelSending(p->from, p->id); diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index bffca0c44..fc059ec16 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -184,6 +184,7 @@ template void LR11x0Interface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** @@ -223,7 +224,7 @@ template void LR11x0Interface::startReceive() 0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving assert(err == RADIOLIB_ERR_NONE); - isReceiving = true; + RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 2cfb4843c..9e2a5b110 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -269,7 +269,7 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies) assert(node); if (hasValidPosition(node)) { -#if HAS_GPS +#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS if (positionModule) { LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); positionModule->sendOurPosition(dest, wantReplies, node->channel); @@ -299,6 +299,7 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) } else { LOG_WARN("ToPhone queue is full, dropping packet.\n"); releaseToPool(p); + fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets return; } } @@ -373,8 +374,8 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) pos.time = getValidTime(RTCQualityFromNet); // In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB) - LOG_DEBUG("onGPSChanged() pos@%x, time=%u, lat=%d, lon=%d, alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, - pos.longitude_i, pos.altitude); + LOG_DEBUG("onGPSChanged() pos@%x time=%u lat=%d lon=%d alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, pos.longitude_i, + pos.altitude); // Update our current position in the local DB nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index cf576e94f..84872e471 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -26,7 +26,7 @@ #include #ifdef ARCH_ESP32 -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "modules/esp32/StoreForwardModule.h" @@ -141,11 +141,6 @@ NodeDB::NodeDB() if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) saveWhat |= SEGMENT_CHANNELS; - if (!devicestate.node_remote_hardware_pins) { - meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; - memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); - } - if (config.position.gps_enabled) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_enabled = 0; @@ -185,7 +180,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset) if (didFactoryReset) { LOG_INFO("Rebooting due to factory reset"); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = millis() + (5 * 1000); } @@ -826,8 +821,8 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou if (src == RX_SRC_LOCAL) { // Local packet, fully authoritative - LOG_INFO("updatePosition LOCAL pos@%x, time=%u, latI=%d, lonI=%d, alt=%d\n", p.timestamp, p.time, p.latitude_i, - p.longitude_i, p.altitude); + LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d\n", p.timestamp, p.time, p.latitude_i, p.longitude_i, + p.altitude); setLocalPosition(p); info->position = TypeConversions::ConvertToPositionLite(p); @@ -842,7 +837,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou // recorded based on the packet rxTime // // FIXME perhaps handle RX_SRC_USER separately? - LOG_INFO("updatePosition REMOTE node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); + LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); // First, back up fields that we want to protect from overwrite uint32_t tmp_time = info->position.time; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e9e36cc61..61bf90d4d 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -155,8 +155,8 @@ class NodeDB localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; return; } - LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%u, timestamp=%u\n", position.latitude_i, - position.longitude_i, position.time, position.timestamp); + LOG_DEBUG("Setting local position: lat=%i lon=%i time=%u timestamp=%u\n", position.latitude_i, position.longitude_i, + position.time, position.timestamp); localPosition = position; } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 26d0d9525..0f69b21f9 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -5,6 +5,7 @@ #include "Channels.h" #include "Default.h" +#include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" #include "PhoneAPI.h" @@ -46,6 +47,9 @@ void PhoneAPI::handleStartConfig() // even if we were already connected - restart our state machine state = STATE_SEND_MY_INFO; + pauseBluetoothLogging = true; + filesManifest = getFiles("/", 10); + LOG_DEBUG("Got %d files in manifest\n", filesManifest.size()); LOG_INFO("Starting API client config\n"); nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos @@ -148,6 +152,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) STATE_SEND_CONFIG, STATE_SEND_MODULE_CONFIG, STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to the client + STATE_SEND_FILEMANIFEST, STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings */ @@ -323,7 +328,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Advance when we have sent all of our ModuleConfig objects if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) { // Clients sending special nonce don't want to see other nodeinfos - state = config_nonce == SPECIAL_NONCE ? STATE_SEND_COMPLETE_ID : STATE_SEND_OTHER_NODEINFOS; + state = config_nonce == SPECIAL_NONCE ? STATE_SEND_FILEMANIFEST : STATE_SEND_OTHER_NODEINFOS; config_state = 0; } break; @@ -339,22 +344,36 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time } else { LOG_INFO("Done sending nodeinfos\n"); - state = STATE_SEND_COMPLETE_ID; + state = STATE_SEND_FILEMANIFEST; // Go ahead and send that ID right now return getFromRadio(buf); } break; } + case STATE_SEND_FILEMANIFEST: { + LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n"); + // last element + if (config_state == filesManifest.size()) { // also handles an empty filesManifest + config_state = 0; + filesManifest.clear(); + // Skip to complete packet + sendConfigComplete(); + } else { + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; + fromRadioScratch.fileInfo = filesManifest.at(config_state); + LOG_DEBUG("File: %s (%d) bytes\n", fromRadioScratch.fileInfo.file_name, fromRadioScratch.fileInfo.size_bytes); + config_state++; + } + break; + } + case STATE_SEND_COMPLETE_ID: - LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; - fromRadioScratch.config_complete_id = config_nonce; - config_nonce = 0; - state = STATE_SEND_PACKETS; + sendConfigComplete(); break; case STATE_SEND_PACKETS: + pauseBluetoothLogging = false; // Do we have a message from the mesh or packet from the local device? LOG_INFO("getFromRadio=STATE_SEND_PACKETS\n"); if (queueStatusPacketForPhone) { @@ -388,7 +407,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Encapsulate as a FromRadio packet size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch); - LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes); + // VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer + // for logging (when we are encapsulating with protobufs) + // LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes); return numbytes; } @@ -396,8 +417,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) return 0; } +void PhoneAPI::sendConfigComplete() +{ + LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; + fromRadioScratch.config_complete_id = config_nonce; + config_nonce = 0; + state = STATE_SEND_PACKETS; + pauseBluetoothLogging = false; +} + void PhoneAPI::handleDisconnect() { + filesManifest.clear(); + pauseBluetoothLogging = false; LOG_INFO("PhoneAPI disconnect\n"); } @@ -439,6 +472,7 @@ bool PhoneAPI::available() case STATE_SEND_MODULECONFIG: case STATE_SEND_METADATA: case STATE_SEND_OWN_NODEINFO: + case STATE_SEND_FILEMANIFEST: case STATE_SEND_COMPLETE_ID: return true; @@ -453,7 +487,6 @@ bool PhoneAPI::available() } } return true; // Always say we have something, because we might need to advance our state machine - case STATE_SEND_PACKETS: { if (!queueStatusPacketForPhone) queueStatusPacketForPhone = service.getQueueStatusForPhone(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 49bf0e292..3c3668300 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -2,10 +2,20 @@ #include "Observer.h" #include "mesh-pb-constants.h" +#include #include +#include // Make sure that we never let our packets grow too large for one BLE packet #define MAX_TO_FROM_RADIO_SIZE 512 + +#if meshtastic_FromRadio_size > MAX_TO_FROM_RADIO_SIZE +#error "meshtastic_FromRadio_size is too large for our BLE packets" +#endif +#if meshtastic_ToRadio_size > MAX_TO_FROM_RADIO_SIZE +#error "meshtastic_ToRadio_size is too large for our BLE packets" +#endif + #define SPECIAL_NONCE 69420 /** @@ -29,6 +39,7 @@ class PhoneAPI STATE_SEND_CONFIG, // Replacement for the old Radioconfig STATE_SEND_MODULECONFIG, // Send Module specific config STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client + STATE_SEND_FILEMANIFEST, // Send file manifest STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings }; @@ -65,6 +76,8 @@ class PhoneAPI uint32_t config_nonce = 0; uint32_t readIndex = 0; + std::vector filesManifest = {}; + void resetReadIndex() { readIndex = 0; } public: @@ -91,6 +104,8 @@ class PhoneAPI */ size_t getFromRadio(uint8_t *buf); + void sendConfigComplete(); + /** * Return true if we have data available to send to the phone */ @@ -98,8 +113,6 @@ class PhoneAPI bool isConnected() { return state != STATE_SEND_NOTHING; } - void setInitialState() { state = STATE_SEND_MY_INFO; } - protected: /// Our fromradio packet while it is being assembled meshtastic_FromRadio fromRadioScratch = {}; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index c5356ad3b..bd1ebdb0e 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -25,7 +25,8 @@ typedef struct { } DACDB; // Interpolation function -DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) { +DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) +{ DACDB result; double fraction = (double)(dbm - dbm1) / (dbm2 - dbm1); result.dac = (uint8_t)(val1.dac + fraction * (val2.dac - val1.dac)); @@ -34,16 +35,17 @@ DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val } // Function to find the correct DAC and DB values based on dBm using interpolation -DACDB getDACandDB(uint8_t dbm) { +DACDB getDACandDB(uint8_t dbm) +{ // Predefined values static const struct { uint8_t dbm; DACDB values; } dbmToDACDB[] = { - {20, {168, 2}}, // 100mW - {24, {148, 6}}, // 250mW - {27, {128, 9}}, // 500mW - {30, {90, 12}} // 1000mW + {20, {168, 2}}, // 100mW + {24, {148, 6}}, // 250mW + {27, {128, 9}}, // 500mW + {30, {90, 12}} // 1000mW }; const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); @@ -103,7 +105,7 @@ bool RF95Interface::init() if (power > RF95_MAX_POWER) // This chip has lower power limits than some power = RF95_MAX_POWER; - + limitPower(); iface = lora = new RadioLibRF95(&module); @@ -116,13 +118,13 @@ bool RF95Interface::init() // enable PA #ifdef RF95_PA_EN #if defined(RF95_PA_DAC_EN) - #ifdef RADIOMASTER_900_BANDIT_NANO - // Use calculated DAC value - dacWrite(RF95_PA_EN, powerDAC); - #else - // Use Value set in /*/variant.h - dacWrite(RF95_PA_EN, RF95_PA_LEVEL); - #endif +#ifdef RADIOMASTER_900_BANDIT_NANO + // Use calculated DAC value + dacWrite(RF95_PA_EN, powerDAC); +#else + // Use Value set in /*/variant.h + dacWrite(RF95_PA_EN, RF95_PA_LEVEL); +#endif #endif #endif @@ -254,6 +256,7 @@ void RF95Interface::setStandby() isReceiving = false; // If we were receiving, not any more disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 78228c077..343b7f200 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -261,7 +261,6 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d\n", snr, CWsize); if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || - config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { delay = random(0, 2 * CWsize) * slotTimeMsec; LOG_DEBUG("rx_snr found in packet. As a router, setting tx delay:%d\n", delay); @@ -522,7 +521,7 @@ void RadioInterface::applyModemConfig() LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, loraConfig.frequency_offset); LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset, channel_num, power); - LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f mhz)\n", myRegion->freqStart, myRegion->freqEnd, + LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart); LOG_INFO("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw); LOG_INFO("Radio channel_num: %d\n", channel_num + 1); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index a4ceac9f1..f299ebff2 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -1,6 +1,7 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" #include "NodeDB.h" +#include "PowerMon.h" #include "SPILock.h" #include "configuration.h" #include "error.h" @@ -317,6 +318,7 @@ void RadioLibInterface::handleTransmitInterrupt() // ignore the transmit interrupt if (sendingPacket) completeSending(); + powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // But our transmitter is deffinitely off now } void RadioLibInterface::completeSending() @@ -412,6 +414,24 @@ void RadioLibInterface::handleReceiveInterrupt() } } +void RadioLibInterface::startReceive() +{ + isReceiving = true; + powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); +} + +void RadioLibInterface::configHardwareForSend() +{ + powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); +} + +void RadioLibInterface::setStandby() +{ + // neither sending nor receiving + powerMon->clearState(meshtastic_PowerMon_State_Lora_RXOn); + powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); +} + /** start an immediate transmit */ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) { @@ -431,6 +451,7 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) // This send failed, but make sure to 'complete' it properly completeSending(); + powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // Transmitter off now startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode) } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 2c841a19e..dd01d2037 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -126,8 +126,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified * Start waiting to receive a message * * External functions can call this method to wake the device from sleep. + * Subclasses must override and call this base method */ - virtual void startReceive() = 0; + virtual void startReceive(); /** can we detect a LoRa preamble on the current channel? */ virtual bool isChannelActive() = 0; @@ -166,8 +167,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified meshtastic_QueueStatus getQueueStatus(); protected: - /** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */ - virtual void configHardwareForSend() {} + /** Do any hardware setup needed on entry into send configuration for the radio. + * Subclasses can customize, but must also call this base method */ + virtual void configHardwareForSend(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately(); @@ -186,5 +188,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0; - virtual void setStandby() = 0; + /** + * Subclasses must override, implement and then call into this base class implementation + */ + virtual void setStandby(); }; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3141d986b..c8c18ae6d 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -244,8 +244,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || - p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + if (!(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || + p->which_payload_variant == meshtastic_MeshPacket_decoded_tag)) { + return meshtastic_Routing_Error_BAD_REQUEST; + } // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index afaa13b7f..b564ba287 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -231,6 +231,7 @@ template void SX126xInterface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** @@ -270,7 +271,7 @@ template void SX126xInterface::startReceive() LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); - isReceiving = true; + RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 9e4fbfa77..fdb2b9a39 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -190,6 +190,7 @@ template void SX128xInterface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore + RadioLibInterface::setStandby(); } /** @@ -263,7 +264,7 @@ template void SX128xInterface::startReceive() LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); - isReceiving = true; + RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 4d04dffe4..9f59aa971 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -1,5 +1,6 @@ #include "StreamAPI.h" #include "PowerFSM.h" +#include "RTC.h" #include "configuration.h" #define START1 0x94 @@ -96,7 +97,6 @@ void StreamAPI::writeStream() void StreamAPI::emitTxBuffer(size_t len) { if (len != 0) { - // LOG_DEBUG("emit tx %d\n", len); txBuf[0] = START1; txBuf[1] = START2; txBuf[2] = (len >> 8) & 0xff; @@ -119,6 +119,25 @@ void StreamAPI::emitRebooted() emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch)); } +void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg) +{ + // In case we send a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_log_record_tag; + fromRadioScratch.log_record.level = level; + + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); + fromRadioScratch.log_record.time = rtc_sec; + strncpy(fromRadioScratch.log_record.source, src, sizeof(fromRadioScratch.log_record.source) - 1); + + auto num_printed = + vsnprintf(fromRadioScratch.log_record.message, sizeof(fromRadioScratch.log_record.message) - 1, format, arg); + if (num_printed > 0 && fromRadioScratch.log_record.message[num_printed - 1] == + '\n') // Strip any ending newline, because we have records for framing instead. + fromRadioScratch.log_record.message[num_printed - 1] = '\0'; + emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch)); +} + /// Hookable to find out when connection changes void StreamAPI::onConnectionChanged(bool connected) { @@ -131,4 +150,4 @@ void StreamAPI::onConnectionChanged(bool connected) // received a packet in a while powerFSM.trigger(EVENT_SERIAL_DISCONNECTED); } -} +} \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 3196e96f8..45cbb231c 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -82,4 +82,7 @@ class StreamAPI : public PhoneAPI /// Subclasses can use this scratch buffer if they wish uint8_t txBuf[MAX_STREAM_BUF_SIZE] = {0}; -}; + + /// Low level function to emit a protobuf encapsulated log record + void emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg); +}; \ No newline at end of file diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 5373f243e..9f3bb8ab7 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -12,6 +12,8 @@ #include #include +#if HAS_NETWORKING + #ifndef DISABLE_NTP #include @@ -183,3 +185,5 @@ bool isEthernetAvailable() return true; } } + +#endif \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 5a78f1366..e3037c910 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -22,7 +22,6 @@ typedef enum _meshtastic_Config_DeviceConfig_Role { The wifi radio and the oled screen will be put to sleep. This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. */ meshtastic_Config_DeviceConfig_Role_ROUTER = 2, - /* Description: Combination of both ROUTER and CLIENT. Not for mobile devices. */ meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3, /* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list. Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry @@ -372,6 +371,9 @@ typedef struct _meshtastic_Config_PowerConfig { uint32_t min_wake_secs; /* I2C address of INA_2XX to use for reading device battery voltage */ uint8_t device_battery_ina_address; + /* If non-zero, we want powermon log outputs. With the particular (bitfield) sources enabled. + Note: we picked an ID of 32 so that lower more efficient IDs can be used for more frequently used options. */ + uint64_t powermon_enables; } meshtastic_Config_PowerConfig; typedef struct _meshtastic_Config_NetworkConfig_IpV4Config { @@ -612,7 +614,7 @@ extern "C" { #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} -#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} @@ -621,7 +623,7 @@ extern "C" { #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} -#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} @@ -662,6 +664,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_ls_secs_tag 7 #define meshtastic_Config_PowerConfig_min_wake_secs_tag 8 #define meshtastic_Config_PowerConfig_device_battery_ina_address_tag 9 +#define meshtastic_Config_PowerConfig_powermon_enables_tag 32 #define meshtastic_Config_NetworkConfig_IpV4Config_ip_tag 1 #define meshtastic_Config_NetworkConfig_IpV4Config_gateway_tag 2 #define meshtastic_Config_NetworkConfig_IpV4Config_subnet_tag 3 @@ -773,7 +776,8 @@ X(a, STATIC, SINGULAR, UINT32, wait_bluetooth_secs, 4) \ X(a, STATIC, SINGULAR, UINT32, sds_secs, 6) \ X(a, STATIC, SINGULAR, UINT32, ls_secs, 7) \ X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 8) \ -X(a, STATIC, SINGULAR, UINT32, device_battery_ina_address, 9) +X(a, STATIC, SINGULAR, UINT32, device_battery_ina_address, 9) \ +X(a, STATIC, SINGULAR, UINT64, powermon_enables, 32) #define meshtastic_Config_PowerConfig_CALLBACK NULL #define meshtastic_Config_PowerConfig_DEFAULT NULL @@ -871,7 +875,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 -#define meshtastic_Config_PowerConfig_size 40 +#define meshtastic_Config_PowerConfig_size 52 #define meshtastic_Config_size 199 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 5e291ee94..fc7bea53a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -8,7 +8,6 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" -#include "meshtastic/module_config.pb.h" #include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -308,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3372 +#define meshtastic_OEMStore_size 3384 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 96a9976f0..c1d2a4ae3 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 541 +#define meshtastic_LocalConfig_size 553 #define meshtastic_LocalModuleConfig_size 685 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 46d59d609..3fa81e131 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -36,7 +36,7 @@ PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, AUTO) PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO) -PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, AUTO) +PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, 2) PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) @@ -45,6 +45,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2) +PB_BIND(meshtastic_FileInfo, meshtastic_FileInfo, AUTO) + + PB_BIND(meshtastic_ToRadio, meshtastic_ToRadio, 2) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 064115815..dbe9281ec 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -167,6 +167,15 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64, /* Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors */ meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65, + /* Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display */ + meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 = 66, + /* Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display */ + meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 = 67, + /* Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display */ + meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 = 68, + /* Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design, + specifically adapted for the Meshtatic project */ + meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -691,11 +700,11 @@ typedef struct _meshtastic_MyNodeInfo { and then extend as needed by emitting multiple records. */ typedef struct _meshtastic_LogRecord { /* Log levels, chosen to match python logging conventions. */ - char message[64]; + char message[384]; /* Seconds since 1970 - or 0 for unknown/unset */ uint32_t time; /* Usually based on thread name - if known */ - char source[8]; + char source[32]; /* Not yet set */ meshtastic_LogRecord_Level level; } meshtastic_LogRecord; @@ -711,6 +720,14 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; +/* Individual File info for the device */ +typedef struct _meshtastic_FileInfo { + /* The fully qualified path of the file */ + char file_name[228]; + /* The size of the file in bytes */ + uint32_t size_bytes; +} meshtastic_FileInfo; + typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t; /* Compressed message payload */ typedef struct _meshtastic_Compressed { @@ -815,6 +832,8 @@ typedef struct _meshtastic_FromRadio { meshtastic_DeviceMetadata metadata; /* MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT) */ meshtastic_MqttClientProxyMessage mqttClientProxyMessage; + /* File system manifest messages */ + meshtastic_FileInfo fileInfo; }; } meshtastic_FromRadio; @@ -958,6 +977,7 @@ extern "C" { + #define meshtastic_Compressed_portnum_ENUMTYPE meshtastic_PortNum @@ -985,6 +1005,7 @@ extern "C" { #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} +#define meshtastic_FileInfo_init_default {"", 0} #define meshtastic_ToRadio_init_default {0, {meshtastic_MeshPacket_init_default}} #define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} @@ -1008,6 +1029,7 @@ extern "C" { #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} +#define meshtastic_FileInfo_init_zero {"", 0} #define meshtastic_ToRadio_init_zero {0, {meshtastic_MeshPacket_init_zero}} #define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} @@ -1110,6 +1132,8 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 +#define meshtastic_FileInfo_file_name_tag 1 +#define meshtastic_FileInfo_size_bytes_tag 2 #define meshtastic_Compressed_portnum_tag 1 #define meshtastic_Compressed_data_tag 2 #define meshtastic_Neighbor_node_id_tag 1 @@ -1144,6 +1168,7 @@ extern "C" { #define meshtastic_FromRadio_xmodemPacket_tag 12 #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 +#define meshtastic_FromRadio_fileInfo_tag 15 #define meshtastic_ToRadio_packet_tag 1 #define meshtastic_ToRadio_want_config_id_tag 3 #define meshtastic_ToRadio_disconnect_tag 4 @@ -1321,7 +1346,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,channel,channel), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,queueStatus,queueStatus), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) #define meshtastic_FromRadio_CALLBACK NULL #define meshtastic_FromRadio_DEFAULT NULL #define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket @@ -1335,6 +1361,13 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttC #define meshtastic_FromRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem #define meshtastic_FromRadio_payload_variant_metadata_MSGTYPE meshtastic_DeviceMetadata #define meshtastic_FromRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage +#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo + +#define meshtastic_FileInfo_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, file_name, 1) \ +X(a, STATIC, SINGULAR, UINT32, size_bytes, 2) +#define meshtastic_FileInfo_CALLBACK NULL +#define meshtastic_FileInfo_DEFAULT NULL #define meshtastic_ToRadio_FIELDLIST(X, a) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \ @@ -1434,6 +1467,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg; extern const pb_msgdesc_t meshtastic_LogRecord_msg; extern const pb_msgdesc_t meshtastic_QueueStatus_msg; extern const pb_msgdesc_t meshtastic_FromRadio_msg; +extern const pb_msgdesc_t meshtastic_FileInfo_msg; extern const pb_msgdesc_t meshtastic_ToRadio_msg; extern const pb_msgdesc_t meshtastic_Compressed_msg; extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; @@ -1459,6 +1493,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg #define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg #define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg +#define meshtastic_FileInfo_fields &meshtastic_FileInfo_msg #define meshtastic_ToRadio_fields &meshtastic_ToRadio_msg #define meshtastic_Compressed_fields &meshtastic_Compressed_msg #define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg @@ -1478,9 +1513,10 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 +#define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 -#define meshtastic_LogRecord_size 81 +#define meshtastic_LogRecord_size 426 #define meshtastic_MeshPacket_size 326 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 233e8d653..6cc82352a 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -124,6 +124,8 @@ typedef enum _meshtastic_PortNum { meshtastic_PortNum_ATAK_PLUGIN = 72, /* Provides unencrypted information about a node for consumption by a map via MQTT */ meshtastic_PortNum_MAP_REPORT_APP = 73, + /* PowerStress based monitoring support (for automated power consumption testing) */ + meshtastic_PortNum_POWERSTRESS_APP = 74, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ diff --git a/src/mesh/generated/meshtastic/powermon.pb.cpp b/src/mesh/generated/meshtastic/powermon.pb.cpp new file mode 100644 index 000000000..ce41ea021 --- /dev/null +++ b/src/mesh/generated/meshtastic/powermon.pb.cpp @@ -0,0 +1,17 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "meshtastic/powermon.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(meshtastic_PowerMon, meshtastic_PowerMon, AUTO) + + +PB_BIND(meshtastic_PowerStressMessage, meshtastic_PowerStressMessage, AUTO) + + + + + diff --git a/src/mesh/generated/meshtastic/powermon.pb.h b/src/mesh/generated/meshtastic/powermon.pb.h new file mode 100644 index 000000000..7de0618e9 --- /dev/null +++ b/src/mesh/generated/meshtastic/powermon.pb.h @@ -0,0 +1,138 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED +#define PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* Any significant power changing event in meshtastic should be tagged with a powermon state transition. +If you are making new meshtastic features feel free to add new entries at the end of this definition. */ +typedef enum _meshtastic_PowerMon_State { + meshtastic_PowerMon_State_None = 0, + meshtastic_PowerMon_State_CPU_DeepSleep = 1, + meshtastic_PowerMon_State_CPU_LightSleep = 2, + /* The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only +occasionally. In cases where that rail has multiple devices on it we usually want to have logging on +the state of that rail as an independent record. +For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen. + +The log messages will be short and complete (see PowerMon.Event in the protobufs for details). +something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states. +(We use a bitmask for states so that if a log message gets lost it won't be fatal) */ + meshtastic_PowerMon_State_Vext1_On = 4, + meshtastic_PowerMon_State_Lora_RXOn = 8, + meshtastic_PowerMon_State_Lora_TXOn = 16, + meshtastic_PowerMon_State_Lora_RXActive = 32, + meshtastic_PowerMon_State_BT_On = 64, + meshtastic_PowerMon_State_LED_On = 128, + meshtastic_PowerMon_State_Screen_On = 256, + meshtastic_PowerMon_State_Screen_Drawing = 512, + meshtastic_PowerMon_State_Wifi_On = 1024, + /* GPS is actively trying to find our location +See GPSPowerState for more details */ + meshtastic_PowerMon_State_GPS_Active = 2048 +} meshtastic_PowerMon_State; + +/* What operation would we like the UUT to perform. +note: senders should probably set want_response in their request packets, so that they can know when the state +machine has started processing their request */ +typedef enum _meshtastic_PowerStressMessage_Opcode { + /* Unset/unused */ + meshtastic_PowerStressMessage_Opcode_UNSET = 0, + meshtastic_PowerStressMessage_Opcode_PRINT_INFO = 1, /* Print board version slog and send an ack that we are alive and ready to process commands */ + meshtastic_PowerStressMessage_Opcode_FORCE_QUIET = 2, /* Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation) */ + meshtastic_PowerStressMessage_Opcode_END_QUIET = 3, /* Stop powerstress processing - probably by just rebooting the board */ + meshtastic_PowerStressMessage_Opcode_SCREEN_ON = 16, /* Turn the screen on */ + meshtastic_PowerStressMessage_Opcode_SCREEN_OFF = 17, /* Turn the screen off */ + meshtastic_PowerStressMessage_Opcode_CPU_IDLE = 32, /* Let the CPU run but we assume mostly idling for num_seconds */ + meshtastic_PowerStressMessage_Opcode_CPU_DEEPSLEEP = 33, /* Force deep sleep for FIXME seconds */ + meshtastic_PowerStressMessage_Opcode_CPU_FULLON = 34, /* Spin the CPU as fast as possible for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LED_ON = 48, /* Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes) */ + meshtastic_PowerStressMessage_Opcode_LED_OFF = 49, /* Force the LED off for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LORA_OFF = 64, /* Completely turn off the LORA radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LORA_TX = 65, /* Send Lora packets for num_seconds */ + meshtastic_PowerStressMessage_Opcode_LORA_RX = 66, /* Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel) */ + meshtastic_PowerStressMessage_Opcode_BT_OFF = 80, /* Turn off the BT radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_BT_ON = 81, /* Turn on the BT radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_WIFI_OFF = 96, /* Turn off the WIFI radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_WIFI_ON = 97, /* Turn on the WIFI radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_GPS_OFF = 112, /* Turn off the GPS radio for num_seconds */ + meshtastic_PowerStressMessage_Opcode_GPS_ON = 113 /* Turn on the GPS radio for num_seconds */ +} meshtastic_PowerStressMessage_Opcode; + +/* Struct definitions */ +/* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). +But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */ +typedef struct _meshtastic_PowerMon { + char dummy_field; +} meshtastic_PowerMon; + +/* PowerStress testing support via the C++ PowerStress module */ +typedef struct _meshtastic_PowerStressMessage { + /* What type of HardwareMessage is this? */ + meshtastic_PowerStressMessage_Opcode cmd; + float num_seconds; +} meshtastic_PowerStressMessage; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _meshtastic_PowerMon_State_MIN meshtastic_PowerMon_State_None +#define _meshtastic_PowerMon_State_MAX meshtastic_PowerMon_State_GPS_Active +#define _meshtastic_PowerMon_State_ARRAYSIZE ((meshtastic_PowerMon_State)(meshtastic_PowerMon_State_GPS_Active+1)) + +#define _meshtastic_PowerStressMessage_Opcode_MIN meshtastic_PowerStressMessage_Opcode_UNSET +#define _meshtastic_PowerStressMessage_Opcode_MAX meshtastic_PowerStressMessage_Opcode_GPS_ON +#define _meshtastic_PowerStressMessage_Opcode_ARRAYSIZE ((meshtastic_PowerStressMessage_Opcode)(meshtastic_PowerStressMessage_Opcode_GPS_ON+1)) + + +#define meshtastic_PowerStressMessage_cmd_ENUMTYPE meshtastic_PowerStressMessage_Opcode + + +/* Initializer values for message structs */ +#define meshtastic_PowerMon_init_default {0} +#define meshtastic_PowerStressMessage_init_default {_meshtastic_PowerStressMessage_Opcode_MIN, 0} +#define meshtastic_PowerMon_init_zero {0} +#define meshtastic_PowerStressMessage_init_zero {_meshtastic_PowerStressMessage_Opcode_MIN, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_PowerStressMessage_cmd_tag 1 +#define meshtastic_PowerStressMessage_num_seconds_tag 2 + +/* Struct field encoding specification for nanopb */ +#define meshtastic_PowerMon_FIELDLIST(X, a) \ + +#define meshtastic_PowerMon_CALLBACK NULL +#define meshtastic_PowerMon_DEFAULT NULL + +#define meshtastic_PowerStressMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, cmd, 1) \ +X(a, STATIC, SINGULAR, FLOAT, num_seconds, 2) +#define meshtastic_PowerStressMessage_CALLBACK NULL +#define meshtastic_PowerStressMessage_DEFAULT NULL + +extern const pb_msgdesc_t meshtastic_PowerMon_msg; +extern const pb_msgdesc_t meshtastic_PowerStressMessage_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define meshtastic_PowerMon_fields &meshtastic_PowerMon_msg +#define meshtastic_PowerStressMessage_fields &meshtastic_PowerStressMessage_msg + +/* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerStressMessage_size +#define meshtastic_PowerMon_size 0 +#define meshtastic_PowerStressMessage_size 7 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7f9df058d..b309484e2 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -6,7 +6,7 @@ #include "main.h" #include "mesh/http/ContentHelper.h" #include "mesh/http/WebServer.h" -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "mqtt/JSON.h" diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index ffb16bd3e..e733d1801 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -1,5 +1,5 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 091586462..e24c62712 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -137,7 +137,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH if (BleOta::getOtaAppVersion().isEmpty()) { LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); } else { screen->startFirmwareUpdateScreen(); BleOta::switchToOtaApp(); @@ -145,7 +145,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } #else LOG_INFO("Not on ESP32, scheduling regular reboot in %d seconds\n", s); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); #endif rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); break; @@ -232,9 +232,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) gps->enable(); -#endif // Send our new fixed position to the mesh for good measure positionModule->sendOurPosition(); +#endif } break; } @@ -299,8 +299,8 @@ void AdminModule::handleGetModuleConfigResponse(const meshtastic_MeshPacket &mp, { // Skip if it's disabled or no pins are exposed if (!r->get_module_config_response.payload_variant.remote_hardware.enabled || - !r->get_module_config_response.payload_variant.remote_hardware.available_pins) { - LOG_DEBUG("Remote hardware module disabled or no vailable_pins. Skipping...\n"); + r->get_module_config_response.payload_variant.remote_hardware.available_pins_count == 0) { + LOG_DEBUG("Remote hardware module disabled or no available_pins. Skipping...\n"); return; } for (uint8_t i = 0; i < devicestate.node_remote_hardware_pins_count; i++) { @@ -388,6 +388,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs); config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; } + // Router Client is deprecated; Set it to client + if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) { + config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + } break; case meshtastic_Config_position_tag: LOG_INFO("Setting config: Position\n"); @@ -811,7 +815,7 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch void AdminModule::reboot(int32_t seconds) { LOG_INFO("Rebooting in %d seconds\n", seconds); - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000); } diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 32b32c253..6ecc88829 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,6 +1,6 @@ #pragma once #include "ProtobufModule.h" -#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index f513e045f..be414dce1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -597,14 +597,14 @@ int32_t CannedMessageModule::runOnce() // handle fn+s for shutdown case 0x9b: if (screen) - screen->startShutdownScreen(); + screen->startAlert("Shutting down..."); shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; // and fn+r for reboot case 0x90: if (screen) - screen->startRebootScreen(); + screen->startAlert("Rebooting..."); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index ba1f5c11e..300afc246 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -27,6 +27,9 @@ #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE #include "modules/RemoteHardwareModule.h" #endif +#if !MESHTASTIC_EXCLUDE_POWERSTRESS +#include "modules/PowerStressModule.h" +#endif #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" #if !MESHTASTIC_EXCLUDE_TRACEROUTE @@ -115,6 +118,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE new RemoteHardwareModule(); +#endif +#if !MESHTASTIC_EXCLUDE_POWERSTRESS + new PowerStressModule(); #endif // Example: Put your module here // new ReplyModule(); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 49f2b808b..61616841b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -73,7 +73,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } // Log packet size and data fields - LOG_DEBUG("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d " + LOG_DEBUG("POSITION node=%08x l=%d lat=%d lon=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d " "time=%d\n", getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae, p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp, @@ -219,7 +219,7 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_INFO("Providing time to mesh %u\n", p.time); } - LOG_INFO("Position reply: time=%i, latI=%i, lonI=%i\n", p.time, p.latitude_i, p.longitude_i); + LOG_INFO("Position reply: time=%i lat=%i lon=%i\n", p.time, p.latitude_i, p.longitude_i); // TAK Tracker devices should send their position in a TAK packet over the ATAK port if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) diff --git a/src/modules/PowerStressModule.cpp b/src/modules/PowerStressModule.cpp new file mode 100644 index 000000000..c86017ae2 --- /dev/null +++ b/src/modules/PowerStressModule.cpp @@ -0,0 +1,77 @@ +#include "PowerStressModule.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" +#include "Router.h" +#include "configuration.h" +#include "main.h" + +extern void printInfo(); + +PowerStressModule::PowerStressModule() + : ProtobufModule("powerstress", meshtastic_PortNum_POWERSTRESS_APP, &meshtastic_PowerStressMessage_msg), + concurrency::OSThread("PowerStressModule") +{ +} + +bool PowerStressModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_PowerStressMessage *pptr) +{ + // We only respond to messages if powermon debugging is already on + if (config.power.powermon_enables) { + auto p = *pptr; + LOG_INFO("Received PowerStress cmd=%d\n", p.cmd); + + // Some commands we can handle immediately, anything else gets deferred to be handled by our thread + switch (p.cmd) { + case meshtastic_PowerStressMessage_Opcode_UNSET: + LOG_ERROR("PowerStress operation unset\n"); + break; + + case meshtastic_PowerStressMessage_Opcode_PRINT_INFO: + printInfo(); + break; + + default: + if (currentMessage.cmd != meshtastic_PowerStressMessage_Opcode_UNSET) + LOG_ERROR("PowerStress operation %d already in progress! Can't start new command\n", currentMessage.cmd); + else + currentMessage = p; // copy for use by thread (the message provided to us will be getting freed) + break; + } + } + return true; +} + +int32_t PowerStressModule::runOnce() +{ + + if (!config.power.powermon_enables) { + // Powermon not enabled - stop using CPU/stop this thread + return disable(); + } + + int32_t sleep_msec = 10; // when not active check for new messages every 10ms + + auto &p = currentMessage; + + if (isRunningCommand) { + // Done with the previous command - our sleep must have finished + p.cmd = meshtastic_PowerStressMessage_Opcode_UNSET; + p.num_seconds = 0; + } else { + sleep_msec = (int32_t)(p.num_seconds * 1000); + isRunningCommand = !!sleep_msec; // if the command wants us to sleep, make sure to mark that we have something running + + switch (p.cmd) { + case meshtastic_PowerStressMessage_Opcode_UNSET: // No need to start a new command + break; + case meshtastic_PowerStressMessage_Opcode_LED_ON: + break; + default: + LOG_ERROR("PowerStress operation %d not yet implemented!\n", p.cmd); + sleep_msec = 0; // Don't do whatever sleep was requested... + break; + } + } + return sleep_msec; +} \ No newline at end of file diff --git a/src/modules/PowerStressModule.h b/src/modules/PowerStressModule.h new file mode 100644 index 000000000..2d449f690 --- /dev/null +++ b/src/modules/PowerStressModule.h @@ -0,0 +1,38 @@ +#pragma once +#include "ProtobufModule.h" +#include "concurrency/OSThread.h" +#include "mesh/generated/meshtastic/powermon.pb.h" + +/** + * A module that provides easy low-level remote access to device hardware. + */ +class PowerStressModule : public ProtobufModule, private concurrency::OSThread +{ + meshtastic_PowerStressMessage currentMessage = meshtastic_PowerStressMessage_init_default; + bool isRunningCommand = false; + + public: + /** Constructor + * name is for debugging output + */ + PowerStressModule(); + + protected: + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_PowerStressMessage *p) override; + + /** + * Periodically read the gpios we have been asked to WATCH, if they have changed, + * broadcast a message with the change information. + * + * The method that will be called each time our thread gets a chance to run + * + * Returns desired period for next invocation (or RUN_SAME for no change) + */ + virtual int32_t runOnce() override; +}; + +extern PowerStressModule powerStressModule; \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 4f5fbcd13..ba043feab 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -85,53 +85,90 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack return false; // Let others look at this message also if they want } -bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) { if (!aqi.read(&data)) { LOG_WARN("Skipping send measurements. Could not read AQIn\n"); return false; } - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - m.time = getTime(); - m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; - m.variant.air_quality_metrics.pm10_standard = data.pm10_standard; - m.variant.air_quality_metrics.pm25_standard = data.pm25_standard; - m.variant.air_quality_metrics.pm100_standard = data.pm100_standard; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; + m->variant.air_quality_metrics.pm10_standard = data.pm10_standard; + m->variant.air_quality_metrics.pm25_standard = data.pm25_standard; + m->variant.air_quality_metrics.pm100_standard = data.pm100_standard; - m.variant.air_quality_metrics.pm10_environmental = data.pm10_env; - m.variant.air_quality_metrics.pm25_environmental = data.pm25_env; - m.variant.air_quality_metrics.pm100_environmental = data.pm100_env; + m->variant.air_quality_metrics.pm10_environmental = data.pm10_env; + m->variant.air_quality_metrics.pm25_environmental = data.pm25_env; + m->variant.air_quality_metrics.pm100_environmental = data.pm100_env; LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i\n", - m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, - m.variant.air_quality_metrics.pm100_standard); + m->variant.air_quality_metrics.pm10_standard, m->variant.air_quality_metrics.pm25_standard, + m->variant.air_quality_metrics.pm100_standard); LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i\n", - m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental, - m.variant.air_quality_metrics.pm100_environmental); + m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental, + m->variant.air_quality_metrics.pm100_environmental); - meshtastic_MeshPacket *p = allocDataProtobuf(m); - p->to = dest; - p->decoded.want_response = false; - if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) - p->priority = meshtastic_MeshPacket_Priority_RELIABLE; - else - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - - // release previous packet before occupying a new spot - if (lastMeasurementPacket != nullptr) - packetPool.release(lastMeasurementPacket); - - lastMeasurementPacket = packetPool.allocCopy(*p); - if (phoneOnly) { - LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); - } else { - LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); - } return true; } +meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding AirQualityTelemetry module!\n"); + return NULL; + } + // Check for a request for air quality metrics + if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getAirQualityTelemetry(&m)) { + LOG_INFO("Air quality telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + return NULL; +} + +bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +{ + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getAirQualityTelemetry(&m)) { + meshtastic_MeshPacket *p = allocDataProtobuf(m); + p->to = dest; + p->decoded.want_response = false; + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + else + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + + // release previous packet before occupying a new spot + if (lastMeasurementPacket != nullptr) + packetPool.release(lastMeasurementPacket); + + lastMeasurementPacket = packetPool.allocCopy(*p); + if (phoneOnly) { + LOG_INFO("Sending packet to phone\n"); + service.sendToPhone(p); + } else { + LOG_INFO("Sending packet to mesh\n"); + service.sendToMesh(p, RX_SRC_LOCAL, true); + } + return true; + } + + return false; +} + #endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index eb0355001..9d09078b1 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -26,6 +26,11 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; + /** Called to get current Air Quality data + @return true if it contains valid data + */ + bool getAirQualityTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index b64e8d113..9cc4bf6ea 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -52,14 +52,27 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() { - if (ignoreRequest) { - return NULL; + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding DeviceTelemetry module!\n"); + return NULL; + } + // Check for a request for device metrics + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + LOG_INFO("Device telemetry replying to request\n"); + + meshtastic_Telemetry telemetry = getDeviceTelemetry(); + return allocDataProtobuf(telemetry); + } } - - LOG_INFO("Device telemetry replying to request\n"); - - meshtastic_Telemetry telemetry = getDeviceTelemetry(); - return allocDataProtobuf(telemetry); + return NULL; } meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() @@ -104,4 +117,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) service.sendToMesh(p, RX_SRC_LOCAL, true); } return true; -} +} \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index b1149799b..dcaf00077 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -198,7 +198,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (lastMeasurementPacket == nullptr) { // If there's no valid packet, display "Environment" display->drawString(x, y, "Environment"); - display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement"); + display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement"); return; } @@ -223,31 +223,31 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt } // Continue with the remaining details - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Temp/Hum: " + last_temp + " / " + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%"); if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); } if (lastMeasurement.variant.environment_metrics.voltage != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); } if (lastMeasurement.variant.environment_metrics.iaq != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + display->drawString(x, y += _fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); } if (lastMeasurement.variant.environment_metrics.distance != 0) - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); if (lastMeasurement.variant.environment_metrics.weight != 0) - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); } @@ -280,102 +280,138 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac return false; // Let others look at this message also if they want } -bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m) { - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; bool valid = true; bool hasSensor = false; - m.time = getTime(); - m.which_variant = meshtastic_Telemetry_environment_metrics_tag; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_environment_metrics_tag; #ifdef T1000X_SENSOR_EN // add by WayenWeng valid = valid && t1000xSensor.getMetrics(&m); hasSensor = true; #else if (dfRobotLarkSensor.hasSensor()) { - valid = valid && dfRobotLarkSensor.getMetrics(&m); + valid = valid && dfRobotLarkSensor.getMetrics(m); hasSensor = true; } if (sht31Sensor.hasSensor()) { - valid = valid && sht31Sensor.getMetrics(&m); + valid = valid && sht31Sensor.getMetrics(m); + hasSensor = true; + } + if (sht4xSensor.hasSensor()) { + valid = valid && sht4xSensor.getMetrics(m); hasSensor = true; } if (lps22hbSensor.hasSensor()) { - valid = valid && lps22hbSensor.getMetrics(&m); + valid = valid && lps22hbSensor.getMetrics(m); hasSensor = true; } if (shtc3Sensor.hasSensor()) { - valid = valid && shtc3Sensor.getMetrics(&m); + valid = valid && shtc3Sensor.getMetrics(m); hasSensor = true; } if (bmp085Sensor.hasSensor()) { - valid = valid && bmp085Sensor.getMetrics(&m); + valid = valid && bmp085Sensor.getMetrics(m); hasSensor = true; } if (bmp280Sensor.hasSensor()) { - valid = valid && bmp280Sensor.getMetrics(&m); + valid = valid && bmp280Sensor.getMetrics(m); hasSensor = true; } if (bme280Sensor.hasSensor()) { - valid = valid && bme280Sensor.getMetrics(&m); + valid = valid && bme280Sensor.getMetrics(m); hasSensor = true; } if (bme680Sensor.hasSensor()) { - valid = valid && bme680Sensor.getMetrics(&m); + valid = valid && bme680Sensor.getMetrics(m); hasSensor = true; } if (mcp9808Sensor.hasSensor()) { - valid = valid && mcp9808Sensor.getMetrics(&m); + valid = valid && mcp9808Sensor.getMetrics(m); hasSensor = true; } if (ina219Sensor.hasSensor()) { - valid = valid && ina219Sensor.getMetrics(&m); + valid = valid && ina219Sensor.getMetrics(m); hasSensor = true; } if (ina260Sensor.hasSensor()) { - valid = valid && ina260Sensor.getMetrics(&m); + valid = valid && ina260Sensor.getMetrics(m); hasSensor = true; } if (veml7700Sensor.hasSensor()) { - valid = valid && veml7700Sensor.getMetrics(&m); + valid = valid && veml7700Sensor.getMetrics(m); hasSensor = true; } if (tsl2591Sensor.hasSensor()) { - valid = valid && tsl2591Sensor.getMetrics(&m); + valid = valid && tsl2591Sensor.getMetrics(m); hasSensor = true; } if (opt3001Sensor.hasSensor()) { - valid = valid && opt3001Sensor.getMetrics(&m); + valid = valid && opt3001Sensor.getMetrics(m); hasSensor = true; } if (mlx90632Sensor.hasSensor()) { - valid = valid && mlx90632Sensor.getMetrics(&m); + valid = valid && mlx90632Sensor.getMetrics(m); hasSensor = true; } if (rcwl9620Sensor.hasSensor()) { - valid = valid && rcwl9620Sensor.getMetrics(&m); + valid = valid && rcwl9620Sensor.getMetrics(m); hasSensor = true; } if (nau7802Sensor.hasSensor()) { - valid = valid && nau7802Sensor.getMetrics(&m); + valid = valid && nau7802Sensor.getMetrics(m); hasSensor = true; } if (aht10Sensor.hasSensor()) { if (!bmp280Sensor.hasSensor()) { - valid = valid && aht10Sensor.getMetrics(&m); + valid = valid && aht10Sensor.getMetrics(m); hasSensor = true; } else { // prefer bmp280 temp if both sensors are present, fetch only humidity meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n"); aht10Sensor.getMetrics(&m_ahtx); - m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; + m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } #endif - valid = valid && hasSensor; - if (valid) { + return valid && hasSensor; +} + +meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding EnvironmentTelemetry module!\n"); + return NULL; + } + // Check for a request for environment metrics + if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getEnvironmentTelemetry(&m)) { + LOG_INFO("Environment telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + return NULL; +} + +bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +{ + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getEnvironmentTelemetry(&m)) { LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, @@ -413,8 +449,9 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) setIntervalFromNow(5000); } } + return true; } - return valid; + return false; } AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp, @@ -515,4 +552,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index ca150347e..ced617c2f 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -32,6 +32,11 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; + /** Called to get current Environment telemetry data + @return true if it contains valid data + */ + bool getEnvironmentTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 826de8a4a..fb5aee375 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -108,7 +108,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s display->drawString(x, y, "Power Telemetry"); if (lastMeasurementPacket == nullptr) { display->setFont(FONT_SMALL); - display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement"); + display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement"); return; } @@ -120,22 +120,22 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s auto &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { display->setFont(FONT_SMALL); - display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error"); + display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error"); LOG_ERROR("Unable to decode last packet"); return; } display->setFont(FONT_SMALL); String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; - display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) { - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); - display->drawString(x, y += fontHeight(FONT_SMALL), + display->drawString(x, y += _fontHeight(FONT_SMALL), "Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); } @@ -163,29 +163,63 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m return false; // Let others look at this message also if they want } +bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) +{ + bool valid = false; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_power_metrics_tag; + + m->variant.power_metrics.ch1_voltage = 0; + m->variant.power_metrics.ch1_current = 0; + m->variant.power_metrics.ch2_voltage = 0; + m->variant.power_metrics.ch2_current = 0; + m->variant.power_metrics.ch3_voltage = 0; + m->variant.power_metrics.ch3_current = 0; +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) + if (ina219Sensor.hasSensor()) + valid = ina219Sensor.getMetrics(m); + if (ina260Sensor.hasSensor()) + valid = ina260Sensor.getMetrics(m); + if (ina3221Sensor.hasSensor()) + valid = ina3221Sensor.getMetrics(m); +#endif + + return valid; +} + +meshtastic_MeshPacket *PowerTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding PowerTelemetry module!\n"); + return NULL; + } + // Check for a request for power metrics + if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getPowerTelemetry(&m)) { + LOG_INFO("Power telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + + return NULL; +} + bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - bool valid = false; - m.time = getTime(); - m.which_variant = meshtastic_Telemetry_power_metrics_tag; - - m.variant.power_metrics.ch1_voltage = 0; - m.variant.power_metrics.ch1_current = 0; - m.variant.power_metrics.ch2_voltage = 0; - m.variant.power_metrics.ch2_current = 0; - m.variant.power_metrics.ch3_voltage = 0; - m.variant.power_metrics.ch3_current = 0; -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) - if (ina219Sensor.hasSensor()) - valid = ina219Sensor.getMetrics(&m); - if (ina260Sensor.hasSensor()) - valid = ina260Sensor.getMetrics(&m); - if (ina3221Sensor.hasSensor()) - valid = ina3221Sensor.getMetrics(&m); -#endif - - if (valid) { + if (getPowerTelemetry(&m)) { LOG_INFO("(Sending): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " "ch3_voltage=%f, ch3_current=%f\n", m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage, @@ -218,8 +252,9 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) setIntervalFromNow(5000); } } + return true; } - return valid; + return false; } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index 3d6b686f2..1b68847db 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -33,6 +33,11 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; + /** Called to get current Power telemetry data + @return true if it contains valid data + */ + bool getPowerTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index ea2cb4ea8..edd29682e 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -16,8 +16,7 @@ int32_t INA3221Sensor::runOnce() return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } if (!status) { - ina3221.setAddr(INA3221_ADDR42_SDA); // i2c address 0x42 - ina3221.begin(); + ina3221.begin(nodeTelemetrySensorsMap[sensorType].second); ina3221.setShuntRes(100, 100, 100); // 0.1 Ohm shunt resistors status = true; } else { diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index 83485c8ee..d5b7d29ee 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -2,6 +2,11 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "configuration.h" +#if HAS_SCREEN +#include "gps/RTC.h" +#include "graphics/Screen.h" +#include "main.h" +#endif WaypointModule *waypointModule; @@ -11,14 +16,155 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp) auto &p = mp.decoded; LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif - + UIFrameEvent e = {true, true}; // We only store/display messages destined for us. // Keep a copy of the most recent text message. devicestate.rx_waypoint = mp; devicestate.has_rx_waypoint = true; powerFSM.trigger(EVENT_RECEIVED_MSG); - notifyObservers(&mp); + notifyObservers(&e); return ProcessMessage::CONTINUE; // Let others look at this message also if they want } + +#if HAS_SCREEN +bool WaypointModule::shouldDraw() +{ +#if !MESHTASTIC_EXCLUDE_WAYPOINT + // If no waypoint to show + if (!devicestate.has_rx_waypoint) + return false; + + // Decode the message, to find the expiration time (is waypoint still valid) + // This handles "deletion" as well as expiration + meshtastic_Waypoint wp; + memset(&wp, 0, sizeof(wp)); + if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size, + &meshtastic_Waypoint_msg, &wp)) { + // Valid waypoint + if (wp.expire > getTime()) + return devicestate.has_rx_waypoint = true; + + // Expired, or deleted + else + return devicestate.has_rx_waypoint = false; + } + + // If decoding failed + LOG_ERROR("Failed to decode waypoint\n"); + devicestate.has_rx_waypoint = false; + return false; +#else + return false; +#endif +} + +/// Draw the last waypoint we received +void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // Prepare to draw + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + // Handle inverted display + // Unsure of expected behavior: for now, copy drawNodeInfo + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + + // Decode the waypoint + meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + meshtastic_Waypoint wp; + memset(&wp, 0, sizeof(wp)); + if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { + // This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case + display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint"); + devicestate.has_rx_waypoint = false; + return; + } + + // Get timestamp info. Will pass as a field to drawColumns + static char lastStr[20]; + screen->getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr)); + + // Will contain distance information, passed as a field to drawColumns + static char distStr[20]; + + // Get our node, to use our own position + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + + // Text fields to draw (left of compass) + // Last element must be NULL. This signals the end of the char*[] to drawColumns + const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL}; + + // Dimensions / co-ordinates for the compass/circle + int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); + + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + display->getHeight() / 2; + } else { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; + } + + // If our node has a position: + if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) { + const meshtastic_PositionLite &op = ourNode->position; + float myHeading; + if (screen->hasHeading()) + myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians + else + myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + screen->drawCompassNorth(display, compassX, compassY, myHeading); + + // Distance to Waypoint + float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + if (d < (2 * MILES_TO_FEET)) + snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); + else + snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); + } else { + if (d < 2000) + snprintf(distStr, sizeof(distStr), "%.0f m", d); + else + snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); + } + + // Compass bearing to waypoint + float bearingToOther = + GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i)); + // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly + // If the top of the compass is not a static north we need adjust bearingToOther based on heading + if (!config.display.compass_north_top) + bearingToOther -= myHeading; + screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); + } + + // If our node doesn't have position + else { + // ? in the compass + display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); + + // ? in the distance field + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + strncpy(distStr, "? mi", sizeof(distStr)); + else + strncpy(distStr, "? km", sizeof(distStr)); + } + + // Undo color-inversion, if set prior to drawing header + // Unsure of expected behavior? For now: copy drawNodeInfo + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->setColor(BLACK); + } + + // Draw compass circle + display->drawCircle(compassX, compassY, compassDiam / 2); + + // Must be after distStr is populated + screen->drawColumns(display, x, y, fields); +} +#endif \ No newline at end of file diff --git a/src/modules/WaypointModule.h b/src/modules/WaypointModule.h index ddbabf4de..4c9c7b86b 100644 --- a/src/modules/WaypointModule.h +++ b/src/modules/WaypointModule.h @@ -5,21 +5,29 @@ /** * Waypoint message handling for meshtastic */ -class WaypointModule : public SinglePortModule, public Observable +class WaypointModule : public SinglePortModule, public Observable { public: /** Constructor * name is for debugging output */ WaypointModule() : SinglePortModule("waypoint", meshtastic_PortNum_WAYPOINT_APP) {} - +#if HAS_SCREEN + bool shouldDraw(); +#endif protected: /** Called to handle a particular incoming message @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ + + virtual Observable *getUIFrameObservable() override { return this; } +#if HAS_SCREEN + virtual bool wantUIFrame() override { return this->shouldDraw(); } + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; }; -extern WaypointModule *waypointModule; +extern WaypointModule *waypointModule; \ No newline at end of file diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index e6712871d..0bae515df 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -66,10 +66,6 @@ bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m meshtastic_MeshPacket *PaxcounterModule::allocReply() { - if (ignoreRequest) { - return NULL; - } - meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; pl.ble = count_from_libpax.ble_count; @@ -131,4 +127,4 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state } #endif // HAS_SCREEN -#endif +#endif \ No newline at end of file diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index 12cddc520..dc8650ad0 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -319,8 +319,8 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m #ifdef ARCH_ESP32 if (moduleConfig.store_forward.enabled) { - // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT - if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { + // The router node should not be sending messages as a client + if ((getFrom(&mp) != nodeDB->getNodeNum())) { if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 9f9ac5c24..a64720c78 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -14,7 +14,7 @@ #endif #include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "sleep.h" -#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #include #endif @@ -175,7 +175,7 @@ void mqttInit() new MQTT(); } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_MQTT_QUEUE) #else MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) @@ -206,7 +206,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING if (!moduleConfig.mqtt.proxy_to_client_enabled) pubSub.setCallback(mqttCallback); #endif @@ -226,7 +226,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) bool MQTT::isConnectedDirectly() { -#ifdef HAS_NETWORKING +#if HAS_NETWORKING return pubSub.connected(); #else return false; @@ -244,7 +244,7 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) service.sendMqttMessageToClientProxy(msg); return true; } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING else if (isConnectedDirectly()) { return pubSub.publish(topic, payload, retained); } @@ -264,7 +264,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo service.sendMqttMessageToClientProxy(msg); return true; } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING else if (isConnectedDirectly()) { return pubSub.publish(topic, payload, length, retained); } @@ -284,7 +284,7 @@ void MQTT::reconnect() publishStatus(); return; // Don't try to connect directly to the server } -#ifdef HAS_NETWORKING +#if HAS_NETWORKING // Defaults int serverPort = 1883; const char *serverAddr = default_mqtt_address; @@ -357,7 +357,7 @@ void MQTT::reconnect() void MQTT::sendSubscriptions() { -#ifdef HAS_NETWORKING +#if HAS_NETWORKING size_t numChan = channels.getNumChannels(); for (size_t i = 0; i < numChan; i++) { const auto &ch = channels.getByIndex(i); @@ -396,7 +396,7 @@ bool MQTT::wantsLink() const int32_t MQTT::runOnce() { -#ifdef HAS_NETWORKING +#if HAS_NETWORKING if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); @@ -482,7 +482,12 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & auto &ch = channels.getByIndex(chIndex); - if (&mp_decoded.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } + + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index f2eb6b120..1ebba4afe 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -8,17 +8,15 @@ #include "mqtt/JSON.h" #if HAS_WIFI #include -#define HAS_NETWORKING 1 #if !defined(ARCH_PORTDUINO) #include #endif #endif #if HAS_ETHERNET #include -#define HAS_NETWORKING 1 #endif -#ifdef HAS_NETWORKING +#if HAS_NETWORKING #include #endif @@ -43,7 +41,7 @@ class MQTT : private concurrency::OSThread #endif public: -#ifdef HAS_NETWORKING +#if HAS_NETWORKING PubSubClient pubSub; #endif MQTT(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 68aa9b465..d959553a4 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -12,6 +12,7 @@ NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; +NimBLECharacteristic *logRadioCharacteristic; NimBLEServer *bleServer; static bool passkeyShowing; @@ -58,7 +59,6 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks { virtual void onRead(NimBLECharacteristic *pCharacteristic) { - LOG_INFO("From Radio onread\n"); uint8_t fromRadioBytes[meshtastic_FromRadio_size]; size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); @@ -82,7 +82,33 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks LOG_INFO("*** Enter passkey %d on the peer side ***\n", passkey); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen->startBluetoothPinScreen(passkey); +#if HAS_SCREEN + screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + char btPIN[16] = "888888"; + snprintf(btPIN, sizeof(btPIN), "%06u", passkey); + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + 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_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); + }); +#endif passkeyShowing = true; return passkey; @@ -94,7 +120,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks if (passkeyShowing) { passkeyShowing = false; - screen->stopBluetoothPinScreen(); + screen->endAlert(); } } @@ -180,6 +206,8 @@ void NimbleBluetooth::setupService() ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); + logRadioCharacteristic = + bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U); } else { ToRadioCharacteristic = bleService->createCharacteristic( TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); @@ -188,6 +216,9 @@ void NimbleBluetooth::setupService() fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + logRadioCharacteristic = bleService->createCharacteristic( + LOGRADIO_UUID, + NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -236,6 +267,14 @@ void NimbleBluetooth::clearBonds() NimBLEDevice::deleteAllBonds(); } +void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) +{ + if (!bleServer || !isConnected() || length > 512) { + return; + } + logRadioCharacteristic->notify(logMessage, length, true); +} + void clearNVS() { NimBLEDevice::deleteAllBonds(); diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index d1e347830..45602e088 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -11,6 +11,7 @@ class NimbleBluetooth : BluetoothApi bool isActive(); bool isConnected(); int getRssi(); + void sendLog(const uint8_t *logMessage, size_t length); private: void setupService(); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 1dd7a389a..aa51e810a 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -8,7 +8,7 @@ #include "nimble/NimbleBluetooth.h" #endif -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -24,23 +24,22 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { -#ifndef MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI if (!isWifiAvailable() && config.bluetooth.enabled == true) +#else + if (config.bluetooth.enabled == true) #endif -#ifdef MESHTASTIC_EXCLUDE_WIFI - if (config.bluetooth.enabled == true) -#endif - { - if (!nimbleBluetooth) { - nimbleBluetooth = new NimbleBluetooth(); - } - if (enable && !nimbleBluetooth->isActive()) { - nimbleBluetooth->setup(); - } - // For ESP32, no way to recover from bluetooth shutdown without reboot - // BLE advertising automatically stops when MCU enters light-sleep(?) - // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse + { + if (!nimbleBluetooth) { + nimbleBluetooth = new NimbleBluetooth(); } + if (enable && !nimbleBluetooth->isActive()) { + nimbleBluetooth->setup(); + } + // For ESP32, no way to recover from bluetooth shutdown without reboot + // BLE advertising automatically stops when MCU enters light-sleep(?) + // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse + } } #else void setBluetoothEnable(bool enable) {} diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 8b817f51b..455219d2b 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -8,11 +8,11 @@ #include "mesh/mesh-pb-constants.h" #include #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 toRadio = BLECharacteristic(BLEUuid(TORADIO_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 @@ -52,16 +52,14 @@ 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\n", central_name); } - /** * Callback invoked when a connection is dropped * @param conn_handle connection where this event happens @@ -72,37 +70,36 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason) // FIXME - we currently assume only one active connection LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } - void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { // Display the raw request packet LOG_INFO("CCCD Updated: %u\n", cccd_value); - // Check the characteristic this CCCD update is associated with in case // this handler is used for multiple CCCD records. - if (chr->uuid == fromNum.uuid) { - if (chr->notifyEnabled(conn_hdl)) { - LOG_INFO("fromNum 'Notify' enabled\n"); + + // 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\n"); } else { - LOG_INFO("fromNum 'Notify' disabled\n"); + LOG_INFO("Notify/Indicate disabled\n"); } } } - 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 = 152.5 ms @@ -117,7 +114,6 @@ void startAdv(void) 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) { @@ -125,7 +121,6 @@ static void authorizeRead(uint16_t conn_hdl) 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 */ @@ -134,7 +129,6 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e 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); @@ -143,37 +137,22 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e } authorizeRead(conn_hdl); } - void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); } -/** - * client is starting read, pull the bytes from our API class - */ -void onFromNumAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_read_t *request) -{ - LOG_INFO("fromNumAuthorizeCb\n"); - - authorizeRead(conn_hdl); -} - 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( @@ -203,10 +182,15 @@ void setupMeshService(void) // 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(); } - static uint32_t configuredPasskey; - void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw @@ -216,29 +200,23 @@ void NRF52Bluetooth::shutdown() } 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("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } - bool NRF52Bluetooth::isConnected() { return Bluefruit.connected(connectionHandle); } - int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } - void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -246,12 +224,10 @@ void NRF52Bluetooth::setup() 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 @@ -270,7 +246,6 @@ void NRF52Bluetooth::setup() } // 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); @@ -287,24 +262,19 @@ void NRF52Bluetooth::setup() bledis.setModel(optstr(HW_VERSION)); bledis.setFirmwareRev(optstr(APP_VERSION)); bledis.begin(); - // Start the BLE Battery Service and set it to 100% LOG_INFO("Configuring the Battery Service\n"); blebas.begin(); blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using // BLEService and BLECharacteristic classes LOG_INFO("Configuring the Mesh bluetooth service\n"); setupMeshService(); - // Setup the advertising packet(s) LOG_INFO("Setting up the advertising payload(s)\n"); startAdv(); - LOG_INFO("Advertising\n"); } - void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); @@ -312,34 +282,52 @@ void NRF52Bluetooth::resumeAdvertising() 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 NRF52Bluetooth::clearBonds() { LOG_INFO("Clearing bluetooth bonds!\n"); 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\n"); } - bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen->startBluetoothPinScreen(configuredPasskey); + 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 : 32; + 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_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); + }); if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { @@ -350,13 +338,21 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); return true; } - void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) LOG_INFO("BLE pairing success\n"); else LOG_INFO("BLE pairing failed\n"); + screen->endAlert(); +} - screen->stopBluetoothPinScreen(); -} \ No newline at end of file +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); +} diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 450af47f9..2229163f8 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -13,10 +13,10 @@ class NRF52Bluetooth : BluetoothApi void clearBonds(); bool isConnected(); int getRssi(); + void sendLog(const uint8_t *logMessage, size_t length); private: static void onConnectionSecured(uint16_t conn_handle); - void convertToUint8(uint8_t target[4], uint32_t source); static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request); static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status); }; \ No newline at end of file diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 1f2c6867d..b79f28f13 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -63,7 +63,8 @@ static void initBrownout() // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice } -static const bool useSoftDevice = true; // Set to false for easier debugging +// 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) @@ -149,13 +150,43 @@ void nrf52Loop() checkSDEvents(); } +#ifdef USE_SEMIHOSTING +#include + +/** + * 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. + */ +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); + } +} +#endif + void nrf52Setup() { - auto why = NRF_POWER->RESETREAS; + 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\n", why); +#ifdef USE_SEMIHOSTING + 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 diff --git a/src/shutdown.h b/src/shutdown.h index 54fb3071b..3f191eea8 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -38,7 +38,7 @@ void powerCommandsCheck() #if defined(ARCH_ESP32) || defined(ARCH_NRF52) if (shutdownAtMsec) { - screen->startShutdownScreen(); + screen->startAlert("Shutting down..."); } #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 55e70e7b8..04963b8a2 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -8,6 +8,7 @@ #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "PowerMon.h" #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" @@ -17,7 +18,7 @@ #ifdef ARCH_ESP32 #include "esp32/pm.h" #include "esp_pm.h" -#if !MESHTASTIC_EXCLUDE_WIFI +#if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "rom/rtc.h" @@ -56,20 +57,20 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI +#if defined(ARCH_ESP32) && HAS_WIFI if (isWifiAvailable()) { /* * * There's a newly introduced bug in the espressif framework where WiFi is - * unstable when the frequency is less than 240mhz. + * unstable when the frequency is less than 240MHz. * * This mostly impacts WiFi AP mode but we'll bump the frequency for * all WiFi use cases. * (Added: Dec 23, 2021 by Jm Casler) */ #ifndef CONFIG_IDF_TARGET_ESP32C3 - LOG_DEBUG("Setting CPU to 240mhz because WiFi is in use.\n"); + LOG_DEBUG("Setting CPU to 240MHz because WiFi is in use.\n"); setCpuFrequencyMhz(240); #endif return; @@ -85,6 +86,11 @@ void setCPUFast(bool on) void setLed(bool ledOn) { + if (ledOn) + powerMon->setState(meshtastic_PowerMon_State_LED_On); + else + powerMon->clearState(meshtastic_PowerMon_State_LED_On); + #ifdef LED_PIN // toggle the led so we can get some rough sense of how often loop is pausing digitalWrite(LED_PIN, ledOn ^ LED_INVERTED); diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h index 9e1d8a9a0..2bb44161a 100644 --- a/variants/heltec_wireless_paper/pins_arduino.h +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -3,11 +3,7 @@ #include -#define WIFI_Kit_32 true -#define DISPLAY_HEIGHT 64 -#define DISPLAY_WIDTH 128 - -static const uint8_t LED_BUILTIN = 35; +static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN @@ -65,6 +61,6 @@ static const uint8_t LED = 18; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; +static const uint8_t DIO1 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 29b8bbbd1..c41d6d9df 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -1,14 +1,12 @@ #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals +// I2C #define I2C_SDA SDA #define I2C_SCL SCL +// Display (E-Ink) #define USE_EINK - -/* - * eink display pins - */ #define PIN_EINK_CS 4 #define PIN_EINK_BUSY 7 #define PIN_EINK_DC 5 @@ -16,32 +14,28 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 45 // Active low, powers the E-Ink display #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 -#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 diff --git a/variants/heltec_wireless_paper_v1/pins_arduino.h b/variants/heltec_wireless_paper_v1/pins_arduino.h index 9e1d8a9a0..2bb44161a 100644 --- a/variants/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/heltec_wireless_paper_v1/pins_arduino.h @@ -3,11 +3,7 @@ #include -#define WIFI_Kit_32 true -#define DISPLAY_HEIGHT 64 -#define DISPLAY_WIDTH 128 - -static const uint8_t LED_BUILTIN = 35; +static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN @@ -65,6 +61,6 @@ static const uint8_t LED = 18; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; +static const uint8_t DIO1 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 29b8bbbd1..c41d6d9df 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -1,14 +1,12 @@ #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals +// I2C #define I2C_SDA SDA #define I2C_SCL SCL +// Display (E-Ink) #define USE_EINK - -/* - * eink display pins - */ #define PIN_EINK_CS 4 #define PIN_EINK_BUSY 7 #define PIN_EINK_DC 5 @@ -16,32 +14,28 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 45 // Active low, powers the E-Ink display #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 -#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 diff --git a/variants/heltec_wireless_tracker/platformio.ini b/variants/heltec_wireless_tracker/platformio.ini index 3259d563c..c7ecce8ea 100644 --- a/variants/heltec_wireless_tracker/platformio.ini +++ b/variants/heltec_wireless_tracker/platformio.ini @@ -1,7 +1,7 @@ [env:heltec-wireless-tracker] extends = esp32s3_base board = heltec_wireless_tracker -upload_protocol = esp-builtin +upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker @@ -11,4 +11,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX@^1.1.8 diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index ef3e5a645..6a67b0083 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -20,6 +20,7 @@ lib_deps = debug_tool = jlink + ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds ;upload_protocol = jlink @@ -27,26 +28,93 @@ debug_tool = jlink ; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) ; programming time is about the same as the bootloader version. ; For information on this see the meshtastic developers documentation for "Development on the NRF52" -[env:rak4631_dap] +[env:rak4631_dbg] extends = env:rak4631 board_level = extra -; pyocd pack --i nrf52840 + +; if the builtin version of openocd has a buggy version of semihosting, so use the external version +; platform_packages = platformio/tool-openocd@^3.1200.0 + +build_flags = + ${env:rak4631.build_flags} + -D USE_SEMIHOSTING + +lib_deps = + ${env:rak4631.lib_deps} + https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 + +; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. +; However the built in openocd version in platformio has buggy support for TCP to semihosting. +; +; So I'm now trying the external openocd - but the openocd scripts for nrf52.cfg assume you are using a DAP adapter not an STLINK adapter. +; In theory I could change those scripts. But for now I'm trying going back to a DAP adapter but with the external openocd. + +upload_protocol = stlink ; eventually use platformio/tool-pyocd@^2.3600.0 instad -upload_protocol = custom -upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE +;upload_protocol = custom +;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE + +; We want the initial breakpoint at setup() instead of main(). Also we want to enable semihosting at that point so instead of +; debug_init_break = tbreak setup +; we just turn off the platformio tbreak and do it in .gdbinit (where we have more flexibility for scripting) +; also we use a permanent breakpoint so it gets reused each time we restart the debugging session? +debug_init_break = tbreak setup + +; Note: add "monitor arm semihosting_redirect tcp 4444 all" if you want the stdout from the device to go to that port number instead +; (for use by meshtastic command line) +; monitor arm semihosting disable +; monitor debug_level 3 +; +; IMPORTANT: fileio must be disabled before using port 5555 - openocd ver 0.12 has a bug where if enabled it never properly parses the special :tt name +; for stdio access. +; monitor arm semihosting_redirect tcp 5555 stdio + +; Also note: it is _impossible_ to do non blocking reads on the semihost console port (an oversight when ARM specified the semihost API). +; So we'll neve be able to general purpose bi-directional communication with the device over semihosting. +debug_extra_cmds = + echo Running .gdbinit script + monitor arm semihosting enable + monitor arm semihosting_fileio enable + monitor arm semihosting_redirect disable + commands 1 + echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555" + set wantSemihost = true + set useSoftDevice = false + end + ; Only reprogram the board if the code has changed debug_load_mode = modified ;debug_load_mode = manual -debug_tool = custom +debug_tool = stlink +;debug_tool = custom +; debug_server = +; openocd +; -f +; /usr/local/share/openocd/scripts/interface/stlink.cfg +; -f +; /usr/local/share/openocd/scripts/target/nrf52.cfg +; $PLATFORMIO_CORE_DIR/packages/tool-openocd/openocd/scripts/interface/cmsis-dap.cfg + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" ; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) -debug_server = - pyocd - gdbserver - -t - nrf52840 - --elf - ${platformio.build_dir}/${this.__env__}/firmware.elf +;debug_server = +; pyocd +; gdbserver +; -j +; ${platformio.workspace_dir}/.. +; -t +; nrf52840 +; --semihosting +; --elf +; ${platformio.build_dir}/${this.__env__}/firmware.elf + +; If you want to debug the semihosting support you can turn on extra logging in pyocd with +; -L +; pyocd.debug.semihost.trace=debug + ; The following is not needed because it automatically tries do this ;debug_server_ready_pattern = -.*GDB server started on port \d+.* ;debug_port = localhost:3333 \ No newline at end of file diff --git a/variants/tlora_t3s3_v1/platformio.ini b/variants/tlora_t3s3_v1/platformio.ini index 002b2f224..0a5797280 100644 --- a/variants/tlora_t3s3_v1/platformio.ini +++ b/variants/tlora_t3s3_v1/platformio.ini @@ -2,7 +2,7 @@ extends = esp32s3_base board = tlora-t3s3-v1 board_check = true -upload_protocol = esp-builtin +upload_protocol = esptool build_flags = ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/tlora_t3s3_v1 diff --git a/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld b/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld new file mode 100644 index 000000000..6aaeb4034 --- /dev/null +++ b/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index cd3a76d02..4410eff11 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -10,10 +10,24 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +board_build.ldscript = variants/wio-sdk-wm1110/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink +;debug_tool = stlink +;debug_speed = 4000 +; No need to reflash if the binary hasn't changed +debug_load_mode = modified ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = jlink +;upload_protocol = stlink +; we prefer to stop in setup() because we are an 'ardiuno' app +debug_init_break = tbreak setup + +; we need to turn off BLE/soft device if we are debugging otherwise it will watchdog reset us. +debug_extra_cmds = + echo Running .gdbinit script + commands 1 + set useSoftDevice = false + end diff --git a/variants/wio-sdk-wm1110/softdevice/ble.h b/variants/wio-sdk-wm1110/softdevice/ble.h new file mode 100644 index 000000000..177b436ad --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON BLE SoftDevice Common + @{ + @defgroup ble_api Events, type definitions and API calls + @{ + + @brief Module independent events, type definitions and API calls for the BLE SoftDevice. + + */ + +#ifndef BLE_H__ +#define BLE_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_l2cap.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief Common API SVC numbers. + */ +enum BLE_COMMON_SVCS { + SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ + SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ + SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ + SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ + SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ + SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ + SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ + SD_BLE_OPT_SET, /**< Set a BLE option. */ + SD_BLE_OPT_GET, /**< Get a BLE option. */ + SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ + SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ +}; + +/** + * @brief BLE Module Independent Event IDs. + */ +enum BLE_COMMON_EVTS { + BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t + \n Reply with @ref sd_ble_user_mem_reply. */ + BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ +}; + +/**@brief BLE Connection Configuration IDs. + * + * IDs that uniquely identify a connection configuration. + */ +enum BLE_CONN_CFGS { + BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ + BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ + BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ + BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ + BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ +}; + +/**@brief BLE Common Configuration IDs. + * + * IDs that uniquely identify a common configuration. + */ +enum BLE_COMMON_CFGS { + BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ +}; + +/**@brief Common Option IDs. + * IDs that uniquely identify a common option. + */ +enum BLE_COMMON_OPTS { + BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ + BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ + BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ +}; + +/** @} */ + +/** @addtogroup BLE_COMMON_DEFINES Defines + * @{ */ + +/** @brief Required pointer alignment for BLE Events. + */ +#define BLE_EVT_PTR_ALIGNMENT 4 + +/** @brief Leaves the maximum of the two arguments. + */ +#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +/** @brief Maximum possible length for BLE Events. + * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a + * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. + */ +#define BLE_EVT_LEN_MAX(ATT_MTU) \ + (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) + +/** @defgroup BLE_USER_MEM_TYPES User Memory Types + * @{ */ +#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ +#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ +/** @} */ + +/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts + * @{ + */ +#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ +#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ +/** @} */ + +/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. + * @{ + */ +#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ + +/** @} */ + +/** @} */ + +/** @addtogroup BLE_COMMON_STRUCTURES Structures + * @{ */ + +/**@brief User Memory Block. */ +typedef struct { + uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ + uint16_t len; /**< Length in bytes of the user memory block. */ +} ble_user_mem_block_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ +} ble_evt_user_mem_request_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ + ble_user_mem_block_t mem_block; /**< User memory block */ +} ble_evt_user_mem_release_t; + +/**@brief Event structure for events not associated with a specific function module. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ + union { + ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ + ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ + } params; /**< Event parameter union. */ +} ble_common_evt_t; + +/**@brief BLE Event header. */ +typedef struct { + uint16_t evt_id; /**< Value from a BLE__EVT series. */ + uint16_t evt_len; /**< Length in octets including this header. */ +} ble_evt_hdr_t; + +/**@brief Common BLE Event type, wrapping the module specific event reports. */ +typedef struct { + ble_evt_hdr_t header; /**< Event header. */ + union { + ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ + ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ + ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ + ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ + ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ + } evt; /**< Event union. */ +} ble_evt_t; + +/** + * @brief Version Information. + */ +typedef struct { + uint8_t version_number; /**< Link Layer Version number. See + https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ + uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) + (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ + uint16_t + subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ +} ble_version_t; + +/** + * @brief Configuration parameters for the PA and LNA. + */ +typedef struct { + uint8_t enable : 1; /**< Enable toggling for this amplifier */ + uint8_t active_high : 1; /**< Set the pin to be active high */ + uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ +} ble_pa_lna_cfg_t; + +/** + * @brief PA & LNA GPIO toggle configuration + * + * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or + * a low noise amplifier. + * + * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided + * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences + * and must be avoided by the application. + */ +typedef struct { + ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ + ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ + + uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ + uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ + uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ +} ble_common_opt_pa_lna_t; + +/** + * @brief Configuration of extended BLE connection events. + * + * When enabled the SoftDevice will dynamically extend the connection event when possible. + * + * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. + * The connection event can be extended if there is time to send another packet pair before the start of the next connection + * interval, and if there are no conflicts with other BLE roles requesting radio time. + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ +} ble_common_opt_conn_evt_ext_t; + +/** + * @brief Enable/disable extended RC calibration. + * + * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice + * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets + * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. + * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When + * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the + * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. + * + * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the + * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ +} ble_common_opt_extended_rc_cal_t; + +/**@brief Option structure for common options. */ +typedef union { + ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ + ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ + ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ +} ble_common_opt_t; + +/**@brief Common BLE Option type, wrapping the module specific options. */ +typedef union { + ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ + ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ + ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ +} ble_opt_t; + +/**@brief BLE connection configuration type, wrapping the module specific configurations, set with + * @ref sd_ble_cfg_set. + * + * @note Connection configurations don't have to be set. + * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, + * the default connection configuration will be automatically added for the remaining connections. + * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in + * place of @ref ble_conn_cfg_t::conn_cfg_tag. + * + * @sa sd_ble_gap_adv_start() + * @sa sd_ble_gap_connect() + * + * @mscs + * @mmsc{@ref BLE_CONN_CFG} + * @endmscs + + */ +typedef struct { + uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the + @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls + to select this configuration when creating a connection. + Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ + union { + ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ + ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ + ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ + ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ + ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ + } params; /**< Connection configuration union. */ +} ble_conn_cfg_t; + +/** + * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. + */ +typedef struct { + uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. + Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is + @ref BLE_UUID_VS_COUNT_MAX. */ +} ble_common_cfg_vs_uuid_t; + +/**@brief Common BLE Configuration type, wrapping the common configurations. */ +typedef union { + ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ +} ble_common_cfg_t; + +/**@brief BLE Configuration type, wrapping the module specific configurations. */ +typedef union { + ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ + ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ + ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ + ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ +} ble_cfg_t; + +/** @} */ + +/** @addtogroup BLE_COMMON_FUNCTIONS Functions + * @{ */ + +/**@brief Enable the BLE stack + * + * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the + * application RAM region (APP_RAM_BASE). On return, this will + * contain the minimum start address of the application RAM region + * required by the SoftDevice for this configuration. + * @warning After this call, the SoftDevice may generate several events. The list of events provided + * below require the application to initiate a SoftDevice API call. The corresponding API call + * is referenced in the event documentation. + * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop + * communicating with the peer device. + * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST + * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST + * - @ref BLE_GAP_EVT_SEC_REQUEST + * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST + * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST + * - @ref BLE_EVT_USER_MEM_REQUEST + * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located + * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between + * APP_RAM_BASE and the start of the call stack. + * + * @details This call initializes the BLE stack, no BLE related function other than @ref + * sd_ble_cfg_set can be called before this one. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: + * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not + * large enough to fit this configuration's memory requirement. Check *p_app_ram_base + * and set the start address of the application RAM region accordingly. + * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which + * is currently not supported. + * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. + */ +SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); + +/**@brief Add configurations for the BLE stack + * + * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref + * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. + * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. + * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). + * See @ref sd_ble_enable for details about APP_RAM_BASE. + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note If a configuration is set more than once, the last one set is the one that takes effect on + * @ref sd_ble_enable. + * + * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default + * configuration. + * + * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref + * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref + * sd_ble_enable). + * + * @note Error codes for the configurations are described in the configuration structs. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The configuration has been added successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. + * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not + * large enough to fit this configuration's memory requirement. + */ +SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); + +/**@brief Get an event from the pending events queue. + * + * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. + * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. + * The buffer should be interpreted as a @ref ble_evt_t struct. + * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. + * + * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that + * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. + * The application is free to choose whether to call this function from thread mode (main context) or directly from the + * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher + * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) + * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so + * could potentially leave events in the internal queue without the application being aware of this fact. + * + * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to + * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, + * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. + * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length + * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: + * + * \code + * uint16_t len; + * errcode = sd_ble_evt_get(NULL, &len); + * \endcode + * + * @mscs + * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} + * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. + * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. + */ +SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); + +/**@brief Add a Vendor Specific base UUID. + * + * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later + * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t + * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code + * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses + * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to + * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field + * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to + * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, + * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. + * + * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by + * the 16-bit uuid field in @ref ble_uuid_t. + * + * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in + * p_uuid_type along with an @ref NRF_SUCCESS error code. + * + * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding + * bytes 12 and 13. + * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be + * stored. + * + * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. + * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. + */ +SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); + +/**@brief Remove a Vendor Specific base UUID. + * + * @details This call removes a Vendor Specific base UUID. This function allows + * the application to reuse memory allocated for Vendor Specific base UUIDs. + * + * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID + * type. + * + * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. + * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific + * base UUID will be removed. If the function returns successfully, the UUID type that was removed will + * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that + * is in use by the ATT Server. + * + * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. + * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. + */ +SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); + +/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. + * + * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared + * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs + * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index + * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. + * + * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. + * + * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). + * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. + * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. + * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. + */ +SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); + +/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). + * + * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is + * computed. + * + * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. + * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). + * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. + * + * @retval ::NRF_SUCCESS Successfully encoded into the buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. + */ +SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); + +/**@brief Get Version Information. + * + * @details This call allows the application to get the BLE stack version information. + * + * @param[out] p_version Pointer to a ble_version_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Version information stored successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). + */ +SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); + +/**@brief Provide a user memory block. + * + * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. + */ +SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); + +/**@brief Set a BLE option. + * + * @details This call allows the application to set the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. + * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. + * + * @retval ::NRF_SUCCESS Option set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + */ +SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); + +/**@brief Get a BLE option. + * + * @details This call allows the application to retrieve the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. + * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Option retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. + * + */ +SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); + +/** @} */ +#ifdef __cplusplus +} +#endif +#endif /* BLE_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_err.h b/variants/wio-sdk-wm1110/softdevice/ble_err.h new file mode 100644 index 000000000..d20f6d141 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_err.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @addtogroup nrf_error + @{ + @ingroup BLE_COMMON + @} + + @defgroup ble_err General error codes + @{ + + @brief General error code definitions for the BLE API. + + @ingroup BLE_COMMON +*/ +#ifndef NRF_BLE_ERR_H__ +#define NRF_BLE_ERR_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* @defgroup BLE_ERRORS Error Codes + * @{ */ +#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ +#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ +#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ +#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ +#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ +#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ + (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ +/** @} */ + +/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges + * @brief Assignment of subranges for module specific error codes. + * @note For specific error codes, see ble_.h or ble_error_.h. + * @{ */ +#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ +#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ +#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ +#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gap.h b/variants/wio-sdk-wm1110/softdevice/ble_gap.h new file mode 100644 index 000000000..8ebdfa82b --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gap.h @@ -0,0 +1,2895 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GAP Generic Access Profile (GAP) + @{ + @brief Definitions and prototypes for the GAP interface. + */ + +#ifndef BLE_GAP_H__ +#define BLE_GAP_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GAP API SVC numbers. + */ +enum BLE_GAP_SVCS { + SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ + SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ + SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ + SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ + SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ + SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ + SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ + SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ + SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ + SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ + SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ + SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ + SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ + SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ + SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ + SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ + SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ + SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ + SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ + SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ + SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ + SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ + SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ + SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ + SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ + SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ + SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ + SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ + SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ + SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ + SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ + SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ + SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ + SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ + SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ + SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ + SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ + SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = + BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ +}; + +/**@brief GAP Event IDs. + * IDs that uniquely identify an event coming from the stack to the application. + */ +enum BLE_GAP_EVTS { + BLE_GAP_EVT_CONNECTED = + BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ + BLE_GAP_EVT_DISCONNECTED = + BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE = + BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ + BLE_GAP_EVT_SEC_PARAMS_REQUEST = + BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. + \n See @ref ble_gap_evt_sec_params_request_t. */ + BLE_GAP_EVT_SEC_INFO_REQUEST = + BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. + \n See @ref ble_gap_evt_sec_info_request_t. */ + BLE_GAP_EVT_PASSKEY_DISPLAY = + BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref + sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ + BLE_GAP_EVT_KEY_PRESSED = + BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ + BLE_GAP_EVT_AUTH_KEY_REQUEST = + BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. + \n See @ref ble_gap_evt_auth_key_request_t. */ + BLE_GAP_EVT_LESC_DHKEY_REQUEST = + BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref + sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ + BLE_GAP_EVT_AUTH_STATUS = + BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ + BLE_GAP_EVT_CONN_SEC_UPDATE = + BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ + BLE_GAP_EVT_TIMEOUT = + BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ + BLE_GAP_EVT_RSSI_CHANGED = + BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ + BLE_GAP_EVT_ADV_REPORT = + BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ + BLE_GAP_EVT_SEC_REQUEST = + BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate +\n or with @ref sd_ble_gap_encrypt if required security information is available +. \n See @ref ble_gap_evt_sec_request_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref + sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ + BLE_GAP_EVT_SCAN_REQ_REPORT = + BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ + BLE_GAP_EVT_PHY_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n + See @ref ble_gap_evt_phy_update_request_t. */ + BLE_GAP_EVT_PHY_UPDATE = + BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref + sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE = + BLE_GAP_EVT_BASE + + 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ + BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = + BLE_GAP_EVT_BASE + + 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ + BLE_GAP_EVT_ADV_SET_TERMINATED = + BLE_GAP_EVT_BASE + + 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ +}; + +/**@brief GAP Option IDs. + * IDs that uniquely identify a GAP option. + */ +enum BLE_GAP_OPTS { + BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ + BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ + BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ + BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ + BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = + BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ + BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = + BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ +}; + +/**@brief GAP Configuration IDs. + * + * IDs that uniquely identify a GAP configuration. + */ +enum BLE_GAP_CFGS { + BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ + BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ + BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic + inclusion configuration. */ + BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic + inclusion configuration. */ +}; + +/**@brief GAP TX Power roles. + */ +enum BLE_GAP_TX_POWER_ROLES { + BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ + BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ + BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ +}; + +/** @} */ + +/**@addtogroup BLE_GAP_DEFINES Defines + * @{ */ + +/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP + * @{ */ +#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ + (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ +#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ + (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ +#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ + (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ +#define BLE_ERROR_GAP_WHITELIST_IN_USE \ + (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ + (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ + (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLES GAP Roles + * @{ */ +#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ +#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ +#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ +/**@} */ + +/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources + * @{ */ +#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ +#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ +#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ +/**@} */ + +/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types + * @{ */ +#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ +#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ +#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ + 0x7F /**< An advertiser may advertise without its address. \ + This type of advertising is called anonymous. */ +/**@} */ + +/**@brief The default interval in seconds at which a private address is refreshed. */ +#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ +/**@brief The maximum interval in seconds at which a private address can be refreshed. */ +#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ + +/** @brief BLE address length. */ +#define BLE_GAP_ADDR_LEN (6) + +/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes + * @{ */ +#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ +#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ +#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ + 0x02 /**< Device will send and accept only private addresses for its own address, \ + and will not accept a peer using identity address as sender address when \ + the peer IRK is exchanged, non-zero and added to the identity list. */ +/**@} */ + +/** @brief Invalid power level. */ +#define BLE_GAP_POWER_LEVEL_INVALID 127 + +/** @brief Advertising set handle not set. */ +#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) + +/** @brief The default number of advertising sets. */ +#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) + +/** @brief The maximum number of advertising sets supported by this SoftDevice. */ +#define BLE_GAP_ADV_SET_COUNT_MAX (1) + +/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. + * @{ */ +#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ + (31) /**< Maximum data length for an advertising set. \ + If more advertising data is required, use extended advertising instead. */ +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for an extended advertising set. */ + +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ + (238) /**< Maximum supported data length for an extended connectable advertising set. */ +/**@}. */ + +/** @brief Set ID not available in advertising report. */ +#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF + +/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons + * @{ */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ +/**@} */ + +/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format + * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm + * @{ */ +#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ +#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ +#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ +#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ +#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ +#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ +#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ +#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ +#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ +#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ +#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ +#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ +#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ +#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ +#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ +#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags + * @{ */ +#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min + * @{ */ +#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ +#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ + /**@} */ + +/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min + * @{ */ +#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min + * @{ */ +#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min + * @{ */ +#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ +#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size + * + * Scan buffers are used for storing advertising data received from an advertiser. + * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. + * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. + * @{ */ +#define BLE_GAP_SCAN_BUFFER_MIN \ + (31) /**< Minimum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_MAX \ + (31) /**< Maximum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ + (255) /**< Minimum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ + (1650) /**< Maximum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for \ + an extended advertising set. */ +/** @} */ + +/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types + * + * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. + * + * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. + * The maximum supported data length for an extended advertiser is defined by + * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED + * Note that some of the advertising types do not support advertising data. Non-scannable types do not support + * scan response data. + * + * @{ */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x01 /**< Connectable and scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ + 0x02 /**< Connectable non-scannable directed advertising \ + events. Advertising interval is less that 3.75 ms. \ + Use this type for fast reconnections. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x03 /**< Connectable non-scannable directed advertising \ + events. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x04 /**< Non-connectable scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x05 /**< Non-connectable non-scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x06 /**< Connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x07 /**< Connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x08 /**< Non-connectable scannable undirected advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ + 0x09 /**< Non-connectable scannable directed advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x0A /**< Non-connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x0B /**< Non-connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies + * @{ */ +#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ +#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status + * @{ */ +#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ + 0x01 /**< More data to be received. \ + @note This value will only be used if \ + @ref ble_gap_scan_params_t::report_incomplete_evts and \ + @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ + 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ + 0x03 /**< Failed to receive the remaining data. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +/**@} */ + +/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies + * @{ */ +#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ + 0x00 /**< Accept all advertising packets except directed advertising packets \ + not addressed to this device. */ +#define BLE_GAP_SCAN_FP_WHITELIST \ + 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ + packets not addressed to this device. */ +#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ + 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ + 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units + * @{ */ +#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ + (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ + */ +#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ + (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ + mode. */ +#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ + (0) /**< Unlimited advertising in general discoverable mode. \ + For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ +/**@} */ + +/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes + * @{ */ +#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ +#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ +#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ +/**@} */ + +/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities + * @{ */ +#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ +#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ +#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ +/**@} */ + +/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types + * @{ */ +#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ +#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ +#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ +/**@} */ + +/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types + * @{ */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS GAP Security status + * @{ */ +#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ +#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ +#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ +#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ +#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ +#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ +#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ +#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ +#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ +#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ +#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ +#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ +#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ +#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ +#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ +#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ +#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources + * @{ */ +#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ +#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ +/**@} */ + +/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits + * @{ */ +#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ + 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ + 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ + 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ + 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ +/**@} */ + +/**@defgroup BLE_GAP_DEVNAME GAP device name defines. + * @{ */ +#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ +#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ +#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ +/**@} */ + +/**@brief Disable RSSI events for connections */ +#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF + +/**@defgroup BLE_GAP_PHYS GAP PHYs + * @{ */ +#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ +#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ +#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ +#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ +#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ + +/**@brief Supported PHYs in connections, for scanning, and for advertising. */ +#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ + +/**@} */ + +/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters + * + * See @ref ble_gap_conn_sec_mode_t. + * @{ */ +/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ + do { \ + (ptr)->sm = 0; \ + (ptr)->lv = 0; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 2; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 3; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 4; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 2; \ + } while (0) +/**@} */ + +/**@brief GAP Security Random Number Length. */ +#define BLE_GAP_SEC_RAND_LEN 8 + +/**@brief GAP Security Key Length. */ +#define BLE_GAP_SEC_KEY_LEN 16 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ +#define BLE_GAP_LESC_P256_PK_LEN 64 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ +#define BLE_GAP_LESC_DHKEY_LEN 32 + +/**@brief GAP Passkey Length. */ +#define BLE_GAP_PASSKEY_LEN 6 + +/**@brief Maximum amount of addresses in the whitelist. */ +#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) + +/**@brief Maximum amount of identities in the device identities list. */ +#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) + +/**@brief Default connection count for a configuration. */ +#define BLE_GAP_CONN_COUNT_DEFAULT (1) + +/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. + * @{ */ +#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ +#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ +#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. + * @{ */ +#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ + (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ + (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ + +/**@} */ + +/**@brief Automatic data length parameter. */ +#define BLE_GAP_DATA_LENGTH_AUTO 0 + +/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. + * @{ */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ +/**@} */ + +/**@defgroup GAP_SEC_MODES GAP Security Modes + * @{ */ +#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ +/**@} */ + +/**@brief The total number of channels in Bluetooth Low Energy. */ +#define BLE_GAP_CHANNEL_COUNT (40) + +/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines + * @{ */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ + /**@} */ + +/** @} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations + * @{ + */ +#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ + (1) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will reserve the attribute handles \ + which are otherwise used for this characteristic. \ + By reserving the attribute handles it will be possible \ + to upgrade the SoftDevice without changing handle of the \ + Service Changed characteristic. */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ + (2) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will not reserve the attribute handles \ + which are otherwise used for this characteristic. */ +/**@} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values + * @{ */ +#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +/**@} */ + +/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options + * @{ */ +#define BLE_GAP_SLAVE_LATENCY_ENABLE \ + (0) /**< Slave latency is enabled. When slave latency is enabled, \ + the slave will wake up every time it has data to send, \ + and/or every slave latency number of connection events. */ +#define BLE_GAP_SLAVE_LATENCY_DISABLE \ + (1) /**< Disable slave latency. The slave will wake up every connection event \ + regardless of the requested slave latency. \ + This option consumes the most power. */ +#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ + (2) /**< The slave will wake up every connection event if it has not received \ + an ACK from the master for at least slave latency events. This \ + configuration may increase the power consumption in environments \ + with a lot of radio activity. */ +/**@} */ + +/**@addtogroup BLE_GAP_STRUCTURES Structures + * @{ */ + +/**@brief Advertising event properties. */ +typedef struct { + uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ + uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. + @note Anonymous advertising is only available for + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ + uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ +} ble_gap_adv_properties_t; + +/**@brief Advertising report type. */ +typedef struct { + uint16_t connectable : 1; /**< Connectable advertising event type. */ + uint16_t scannable : 1; /**< Scannable advertising event type. */ + uint16_t directed : 1; /**< Directed advertising event type. */ + uint16_t scan_response : 1; /**< Received a scan response. */ + uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ + uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ + uint16_t reserved : 9; /**< Reserved for future use. */ +} ble_gap_adv_report_type_t; + +/**@brief Advertising Auxiliary Pointer. */ +typedef struct { + uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ + uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ +} ble_gap_aux_pointer_t; + +/**@brief Bluetooth Low Energy address. */ +typedef struct { + uint8_t + addr_id_peer : 1; /**< Only valid for peer addresses. + This bit is set by the SoftDevice to indicate whether the address has been resolved from + a Resolvable Private Address (when the peer is using privacy). + If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. + + This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. + */ + uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ + uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. + @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ +} ble_gap_addr_t; + +/**@brief GAP connection parameters. + * + * @note When ble_conn_params_t is received in an event, both min_conn_interval and + * max_conn_interval will be equal to the connection interval set by the central. + * + * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: + * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval + * that corresponds to the following Bluetooth Spec requirement: + * The Supervision_Timeout in milliseconds shall be larger than + * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. + */ +typedef struct { + uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ +} ble_gap_conn_params_t; + +/**@brief GAP connection security modes. + * + * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n + * Security Mode 1 Level 1: No security is needed (aka open link).\n + * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n + * Security Mode 1 Level 3: MITM protected encrypted link required.\n + * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n + * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n + * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n + */ +typedef struct { + uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ + uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ + +} ble_gap_conn_sec_mode_t; + +/**@brief GAP connection security status.*/ +typedef struct { + ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ + uint8_t + encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ +} ble_gap_conn_sec_t; + +/**@brief Identity Resolving Key. */ +typedef struct { + uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ +} ble_gap_irk_t; + +/**@brief Channel mask (40 bits). + * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, + * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents + * channel index 39. If a bit is set to 1, the channel is not used. + */ +typedef uint8_t ble_gap_ch_mask_t[5]; + +/**@brief GAP advertising parameters. */ +typedef struct { + ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ + ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. + @note ble_gap_addr_t::addr_type cannot be + @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. + - When privacy is enabled and the local device uses + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, + the device identity list is searched for a matching entry. If + the local IRK for that device identity is set, the local IRK + for that device will be used to generate the advertiser address + field in the advertising packet. + - If @ref ble_gap_adv_properties_t::type is directed, this must be + set to the targeted scanner or initiator. If the peer address is + in the device identity list, the peer IRK for that device will be + used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE + target addresses used in the advertising event PDUs. */ + uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE + advertising, this parameter is ignored. */ + uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, + an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + @sa BLE_GAP_ADV_TIMEOUT_VALUES. + @note The SoftDevice will always complete at least one advertising + event even if the duration is set too low. */ + uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling + advertising. Setting the value to 0 disables the limitation. When + the count of advertising events specified by this parameter + (if not 0) is reached, advertising will be automatically stopped + and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, + this parameter is ignored. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be used. + Masking away secondary advertising channels is not supported. */ + uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets + are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS + will be used. + Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. + @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets + are transmitted. + If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. + Valid values are + @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. + If @ref ble_gap_adv_properties_t::type is an extended advertising type + and connectable, this is the PHY that will be used to establish a + connection and send AUX_ADV_IND packets on. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other + advertising sets transmitted by this and other devices. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a + scan request is received and the scanner address is allowed + by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is a non-scannable + advertising type. */ +} ble_gap_adv_params_t; + +/**@brief GAP advertising data buffers. + * + * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and + * shall never be modified while advertising. The data shall be kept alive until either: + * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding + * advertising handle. + * - Advertising is stopped. + * - Advertising data is changed. + * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ +typedef struct { + ble_data_t adv_data; /**< Advertising data. + @note + Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type + that is allowed to contain advertising data. */ + ble_data_t scan_rsp_data; /**< Scan response data. + @note + Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type + that is scannable. */ +} ble_gap_adv_data_t; + +/**@brief GAP scanning parameters. */ +typedef struct { + uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. + If set to 0, the scanner will not receive advertising packets + on secondary advertising channels, and will not be able + to receive long advertising PDUs. */ + uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have + @ref ble_gap_adv_report_type_t::status set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. + This parameter is ignored when used with @ref sd_ble_gap_connect + @note This may be used to abort receiving more packets from an extended + advertising event, and is only available for extended + scanning, see @ref sd_ble_gap_scan_start. + @note This feature is not supported by this SoftDevice. */ + uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. + This parameter is ignored when used with @ref sd_ble_gap_connect. */ + uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. + @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and + @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with + @ref sd_ble_gap_connect */ + uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, + scan_phys will default to @ref BLE_GAP_PHY_1MBPS. + - If @ref ble_gap_scan_params_t::extended is set to 0, the only + supported PHY is @ref BLE_GAP_PHY_1MBPS. + - When used with @ref sd_ble_gap_scan_start, + the bitfield indicates the PHYs the scanner will use for scanning + on primary advertising channels. The scanner will accept + @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. + - When used with @ref sd_ble_gap_connect, the bitfield indicates + the PHYs the initiator will use for scanning on primary advertising + channels. The initiator will accept connections initiated on either + of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. + If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, + the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. + If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan + PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is + @ref BLE_GAP_PHY_CODED, the primary scan PHY is + @ref BLE_GAP_PHY_CODED only. */ + uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ + uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. + If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and + @ref BLE_GAP_PHY_CODED interval shall be larger than or + equal to twice the scan window. */ + uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be + set to 0. + Masking away secondary channels is not supported. */ +} ble_gap_scan_params_t; + +/**@brief Privacy. + * + * The privacy feature provides a way for the device to avoid being tracked over a period of time. + * The privacy feature, when enabled, hides the local device identity and replaces it with a private address + * that is automatically refreshed at a specified interval. + * + * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). + * With this key, a device can generate a random private address that can only be recognized by peers in possession of that + * key, and devices can establish connections without revealing their real identities. + * + * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref + * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. + * + * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all + * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. + * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is + * called. + */ +typedef struct { + uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ + uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ + uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use + the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ + ble_gap_irk_t + *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device + default IRK will be used. When used as output, pointer to IRK structure where the current default IRK + will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate + random private resolvable addresses for the local device unless instructed otherwise. */ +} ble_gap_privacy_params_t; + +/**@brief PHY preferences for TX and RX + * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each + * direction. + * @code + * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * @endcode + * + */ +typedef struct { + uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ +} ble_gap_phys_t; + +/** @brief Keys that can be exchanged during a bonding procedure. */ +typedef struct { + uint8_t enc : 1; /**< Long Term Key and Master Identification. */ + uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ + uint8_t sign : 1; /**< Connection Signature Resolving Key. */ + uint8_t link : 1; /**< Derive the Link Key from the LTK. */ +} ble_gap_sec_kdist_t; + +/**@brief GAP security parameters. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ + uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ + uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ + uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ + uint8_t oob : 1; /**< The OOB data flag. + - In LE legacy pairing, this flag is set if a device has out of band authentication data. + The OOB method is used if both of the devices have out of band authentication data. + - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band + authentication data. The OOB method is used if at least one device has the peer device's OOB data + available. */ + uint8_t + min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ + uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ + ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ + ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ +} ble_gap_sec_params_t; + +/**@brief GAP Encryption Information. */ +typedef struct { + uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ + uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ + uint8_t auth : 1; /**< Authenticated Key. */ + uint8_t ltk_len : 6; /**< LTK length in octets. */ +} ble_gap_enc_info_t; + +/**@brief GAP Master Identification. */ +typedef struct { + uint16_t ediv; /**< Encrypted Diversifier. */ + uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ +} ble_gap_master_id_t; + +/**@brief GAP Signing Information. */ +typedef struct { + uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ +} ble_gap_sign_info_t; + +/**@brief GAP LE Secure Connections P-256 Public Key. */ +typedef struct { + uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the + standard SMP protocol format: {X,Y} both in little-endian. */ +} ble_gap_lesc_p256_pk_t; + +/**@brief GAP LE Secure Connections DHKey. */ +typedef struct { + uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ +} ble_gap_lesc_dhkey_t; + +/**@brief GAP LE Secure Connections OOB data. */ +typedef struct { + ble_gap_addr_t addr; /**< Bluetooth address of the device. */ + uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ + uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ +} ble_gap_lesc_oob_data_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ +typedef struct { + ble_gap_addr_t + peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ + uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ +} ble_gap_evt_connected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ +typedef struct { + uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ +} ble_gap_evt_disconnected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ +typedef struct { + ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ +} ble_gap_evt_phy_update_request_t; + +/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ +typedef struct { + uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ + uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ +} ble_gap_evt_phy_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ +typedef struct { + ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ +} ble_gap_evt_sec_params_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ +typedef struct { + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ + ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ + uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ + uint8_t id_info : 1; /**< If 1, Identity Information required. */ + uint8_t sign_info : 1; /**< If 1, Signing Information required. */ +} ble_gap_evt_sec_info_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ +typedef struct { + uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ + uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply + with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or + @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ +} ble_gap_evt_passkey_display_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ +typedef struct { + uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ +} ble_gap_evt_key_pressed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ +typedef struct { + uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ +} ble_gap_evt_auth_key_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ +typedef struct { + ble_gap_lesc_p256_pk_t + *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory + inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ + uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the + procedure. */ +} ble_gap_evt_lesc_dhkey_request_t; + +/**@brief Security levels supported. + * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. + */ +typedef struct { + uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ + uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ + uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ + uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ +} ble_gap_sec_levels_t; + +/**@brief Encryption Key. */ +typedef struct { + ble_gap_enc_info_t enc_info; /**< Encryption Information. */ + ble_gap_master_id_t master_id; /**< Master Identification. */ +} ble_gap_enc_key_t; + +/**@brief Identity Key. */ +typedef struct { + ble_gap_irk_t id_info; /**< Identity Resolving Key. */ + ble_gap_addr_t id_addr_info; /**< Identity Address. */ +} ble_gap_id_key_t; + +/**@brief Security Keys. */ +typedef struct { + ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ + ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ + ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ + ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the + value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ +} ble_gap_sec_keys_t; + +/**@brief Security key set for both local and peer keys. */ +typedef struct { + ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be + generated locally and will always be stored if bonding. */ + ble_gap_sec_keys_t + keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ +} ble_gap_sec_keyset_t; + +/**@brief Data Length Update Procedure parameters. */ +typedef struct { + uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ + uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ +} ble_gap_data_length_params_t; + +/**@brief Data Length Update Procedure local limitation. */ +typedef struct { + uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ + uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ + uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many + microseconds. */ +} ble_gap_data_length_limitation_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ +typedef struct { + uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ + uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ + uint8_t bonded : 1; /**< Procedure resulted in a bond. */ + uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ + ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ + ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ + ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding + with LE Secure Connections, the enc bit will be always set. */ + ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding + with LE Secure Connections, the enc bit will never be set. */ +} ble_gap_evt_auth_status_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ +typedef struct { + ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ +} ble_gap_evt_conn_sec_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ + union { + ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released + scan buffer is contained in this field. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ +typedef struct { + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ +} ble_gap_evt_rssi_changed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ +typedef struct { + uint8_t reason; /**< Reason for why the advertising set terminated. See + @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ + uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, + this field indicates the number of completed advertising events. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. */ +} ble_gap_evt_adv_set_terminated_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. + * + * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * not all fields in the advertising report may be available. + * + * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. + */ +typedef struct { + ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the + peer's identity address. */ + ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if + @ref ble_gap_adv_report_type_t::directed is set to 1. If the + SoftDevice was able to resolve the address, + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr + contains the local identity address. If the target address of the + advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + and the SoftDevice was unable to resolve it, the application may try + to resolve this address to find out if the advertising event was + directed to us. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. + See @ref BLE_GAP_PHYS. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. + See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets + were received on a secondary advertising channel. */ + int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. + This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the + last received packet did not contain the Tx Power field. + @note TX Power is only included in extended advertising packets. */ + int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ + uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present + if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID + is not present if @ref ble_gap_evt_adv_report_t::set_id is set to + @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + ble_data_t data; /**< Received advertising or scan response data. If + @ref ble_gap_adv_report_type_t::status is not set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided + in @ref sd_ble_gap_scan_start is now released. */ + ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising + event. @note This field is only set if @ref ble_gap_adv_report_type_t::status + is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ +} ble_gap_evt_adv_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Man In The Middle protection requested. */ + uint8_t lesc : 1; /**< LE Secure Connections requested. */ + uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ +} ble_gap_evt_sec_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ +typedef struct { + uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ +} ble_gap_evt_scan_req_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ +typedef struct { + ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ +} ble_gap_evt_data_length_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. + * + * @note This event may also be raised after a PHY Update procedure. + */ +typedef struct { + ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ +} ble_gap_evt_data_length_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ +typedef struct { + int8_t + channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy + channels, in dBm, indexed by Channel Index. + If no measurement is available for the given channel, channel_energy is set to + @ref BLE_GAP_POWER_LEVEL_INVALID. */ +} ble_gap_evt_qos_channel_survey_report_t; + +/**@brief GAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + union /**< union alternative identified by evt_id in enclosing struct. */ + { + ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ + ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ + ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ + ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ + ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ + ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ + ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ + ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ + ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ + ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ + ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ + ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ + ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ + ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ + ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ + ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ + ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ + ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ + ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ + ble_gap_evt_qos_channel_survey_report_t + qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_t; + +/** + * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. + * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. + */ +typedef struct { + uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. + The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ + uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. + The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref + BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters + for setting the throughput of a connection. + See the SoftDevice Specification for details on throughput. */ +} ble_gap_conn_cfg_t; + +/** + * @brief Configuration of maximum concurrent connections in the different connected roles, set with + * @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too + * large. The maximum supported sum of concurrent connections is + * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. + * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. + * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum + * supported advertising handles is + * @ref BLE_GAP_ADV_SET_COUNT_MAX. + */ +typedef struct { + uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ + uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref + BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ + uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref + BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ + uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is + @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ + uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to + the application using @ref sd_ble_gap_qos_channel_survey_start. */ +} ble_gap_cfg_role_count_t; + +/** + * @brief Device name and its properties, set with @ref sd_ble_cfg_set. + * + * @note If the device name is not configured, the default device name will be + * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be + * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name + * will have no write access. + * + * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, + * the attribute table size must be increased to have room for the longer device name (see + * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). + * + * @note If vloc is @ref BLE_GATTS_VLOC_STACK : + * - p_value must point to non-volatile memory (flash) or be NULL. + * - If p_value is NULL, the device name will initially be empty. + * + * @note If vloc is @ref BLE_GATTS_VLOC_USER : + * - p_value cannot be NULL. + * - If the device name is writable, p_value must point to volatile memory (RAM). + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Invalid device name location (vloc). + * - Invalid device name security mode. + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). + * - The device name length is too long for the given Attribute Table. + * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. + */ +typedef struct { + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ + uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ + uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ +} ble_gap_cfg_device_name_t; + +/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_ppcp_incl_cfg_t; + +/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_car_incl_cfg_t; + +/**@brief Configuration structure for GAP configurations. */ +typedef union { + ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ + ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ + ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include + configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ + ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, + cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ +} ble_gap_cfg_t; + +/**@brief Channel Map option. + * + * @details Used with @ref sd_ble_opt_get to get the current channel map + * or @ref sd_ble_opt_set to set a new channel map. When setting the + * channel map, it applies to all current and future connections. When getting the + * current channel map, it applies to a single connection and the connection handle + * must be supplied. + * + * @note Setting the channel map may take some time, depending on connection parameters. + * The time taken may be different for each connection and the get operation will + * return the previous channel map until the new one has taken effect. + * + * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. + * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. + * + * @retval ::NRF_SUCCESS Get or set successful. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Less then two bits in @ref ch_map are set. + * - Bits for primary advertising channels (37-39) are set. + * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. + * + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ + uint8_t ch_map[5]; /**< Channel Map (37-bit). */ +} ble_gap_opt_ch_map_t; + +/**@brief Local connection latency option. + * + * @details Local connection latency is a feature which enables the slave to improve + * current consumption by ignoring the slave latency set by the peer. The + * local connection latency can only be set to a multiple of the slave latency, + * and cannot be longer than half of the supervision timeout. + * + * @details Used with @ref sd_ble_opt_set to set the local connection latency. The + * @ref sd_ble_opt_get is not supported for this option, but the actual + * local connection latency (unless set to NULL) is set as a return parameter + * when setting the option. + * + * @note The latency set will be truncated down to the closest slave latency event + * multiple, or the nearest multiple before half of the supervision timeout. + * + * @note The local connection latency is disabled by default, and needs to be enabled for new + * connections and whenever the connection is updated. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t requested_latency; /**< Requested local connection latency. */ + uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return + value). */ +} ble_gap_opt_local_conn_latency_t; + +/**@brief Disable slave latency + * + * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection + * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the + * peripheral will ignore the slave_latency set by the central. + * + * @note Shall only be called on peripheral links. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ +} ble_gap_opt_slave_latency_disable_t; + +/**@brief Passkey Option. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @endmscs + * + * @details Structure containing the passkey to be used during pairing. This can be used with @ref + * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication + * instead of generating a random one. + * + * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + */ +typedef struct { + uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used + during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ +} ble_gap_opt_passkey_t; + +/**@brief Compatibility mode 1 option. + * + * @details This can be used with @ref sd_ble_opt_set to enable and disable + * compatibility mode 1. Compatibility mode 1 is disabled by default. + * + * @note Compatibility mode 1 enables interoperability with devices that do not support a value of + * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a + * limited set of legacy peripheral devices from another vendor. Enabling this compatibility + * mode will only have an effect if the local device will act as a central device and + * initiate a connection to a peripheral device. In that case it may lead to the connection + * creation taking up to one connection interval longer to complete for all connections. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. + */ +typedef struct { + uint8_t enable : 1; /**< Enable compatibility mode 1.*/ +} ble_gap_opt_compat_mode_1_t; + +/**@brief Authenticated payload timeout option. + * + * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other + * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. + * + * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated + * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted + * link. + * + * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance + * to reset the timer. In addition the stack will try to prioritize running of LE ping over other + * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects + * on other activities, it is recommended to use high timeout values. + * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ +} ble_gap_opt_auth_payload_timeout_t; + +/**@brief Option structure for GAP options. */ +typedef union { + ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ + ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ + ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ + ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ + ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ + ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ +} ble_gap_opt_t; + +/**@brief Connection event triggering parameters. */ +typedef struct { + uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until + connection event PPI task triggering is stopped. + The PPI channel ID can not be one of the PPI channels reserved by + the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ + uint32_t task_endpoint; /**< Task Endpoint to trigger. */ + uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ + uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. + If the device is in slave role and slave latency is enabled, + this parameter should be set to a multiple of (slave latency + 1) + to ensure low power operation. */ +} ble_gap_conn_event_trigger_t; +/**@} */ + +/**@addtogroup BLE_GAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set the local Bluetooth identity address. + * + * The local Bluetooth identity address is the address that identifies this device to other peers. + * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @note The identity address cannot be changed while advertising, scanning or creating a connection. + * + * @note This address will be distributed to the peer during bonding. + * If the address changes, the address stored in the peer device will not be valid and the ability to + * reconnect using the old address will be lost. + * + * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being + * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged + * for the lifetime of each IC. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @endmscs + * + * @param[in] p_addr Pointer to address structure. + * + * @retval ::NRF_SUCCESS Address successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, + * scanning or creating a connection. + */ +SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); + +/**@brief Get local Bluetooth identity address. + * + * @note This will always return the identity address irrespective of the privacy settings, + * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + */ +SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); + +/**@brief Get the Bluetooth device address used by the advertiser. + * + * @note This function will return the local Bluetooth address used in advertising PDUs. When + * using privacy, the SoftDevice will generate a new private address every + * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using + * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the + * address returned may not be the latest address that is used in the advertising PDUs. + * + * @param[in] adv_handle The advertising handle to get the address from. + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. + * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); + +/**@brief Set the active whitelist in the SoftDevice. + * + * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. + * The whitelist cannot be set if a BLE role is using the whitelist. + * + * @note If an address is resolved using the information in the device identity list, then the whitelist + * filter policy applies to the peer identity address and not the resolvable address sent on air. + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @endmscs + * + * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. + * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * + * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. + * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when + * pp_wl_addrs is not NULL. + */ +SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); + +/**@brief Set device identity list. + * + * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. + * The device identity list cannot be set if a BLE role is using the list. + * + * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will + * be cleared. + * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the + * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. + * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. + * This code may be returned if the local IRK list also has an invalid entry. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity + * address. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can + * only return when pp_id_keys is not NULL. + */ +SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, + sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, + uint8_t len)); + +/**@brief Set privacy settings. + * + * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. + * + * @param[in] p_privacy_params Privacy settings. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. + * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution + characteristic is not configured to be included and the SoftDevice is configured + to support central roles. + See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning + * or creating a connection. + */ +SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); + +/**@brief Get privacy settings. + * + * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. + * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. + * + * @param[in,out] p_privacy_params Privacy settings. + * + * @retval ::NRF_SUCCESS Privacy settings read. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + */ +SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); + +/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. + * + * @note The format of the advertising data will be checked by this call to ensure interoperability. + * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and + * duplicating the local name in the advertising data and scan response data. + * + * @note In order to update advertising data while advertising, new advertising buffers must be provided. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the + * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. + * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See + * @ref ble_gap_adv_data_t. + * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising + * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. + * + * @retval ::NRF_SUCCESS Advertising set successfully configured. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid advertising data configuration specified. See @ref + * ble_gap_adv_data_t. + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, + * see @ref sd_ble_gap_whitelist_set. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - It is invalid to provide non-NULL advertising set parameters while + * advertising. + * - It is invalid to provide the same data buffers while advertising. To + * update advertising data, provide new advertising buffers. + * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format + * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an + * existing advertising handle instead. + * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. + */ +SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, + sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, + ble_gap_adv_params_t const *p_adv_params)); + +/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @note Only one advertiser may be active at any time. + * + * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. + * See @ref sd_ble_gap_privacy_set(). + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} + * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} + * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable + * advertising, this is ignored. + * + * @retval ::NRF_SUCCESS The BLE stack has started advertising. + * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration + * tag has been reached; connectable advertiser cannot be started. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref + sd_ble_gap_adv_set_configure. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, see @ref + sd_ble_gap_whitelist_set. + * @retval ::NRF_ERROR_RESOURCES Either: + * - adv_handle is configured with connectable advertising, but the event_length parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + * - Not enough BLE role slots available. + Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) + and try again. + * - p_adv_params is configured with connectable advertising, but the event_length + parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); + +/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle The advertising handle that should stop advertising. + * + * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. + * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); + +/**@brief Update connection parameters. + * + * @details In the central role this will initiate a Link Layer connection parameter update procedure, + * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for + * the central to perform the procedure. In both cases, and regardless of success or failure, the application + * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. + * + * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the + * procedure unrequested. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CPU_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, + * the parameters in the PPCP characteristic of the GAP service will be used instead. + * If NULL is provided on a central role and in response to a @ref + * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected + * + * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, + sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Disconnect (GAP Link Termination). + * + * @details This call initiates the disconnection procedure, and its completion will be communicated to the application + * with a @ref BLE_GAP_EVT_DISCONNECTED event. + * + * @events + * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CONN_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref + * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). + * + * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + */ +SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); + +/**@brief Set the radio's transmit power. + * + * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for + * possible roles. + * @param[in] handle The handle parameter is interpreted depending on role: + * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. + * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, + * will use the specified transmit power, and include it in the advertising packet headers if + * @ref ble_gap_adv_properties_t::include_tx_power set. + * - For all other roles handle is ignored. + * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). + * + * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. + * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. + * Setting these values on a chip that does not support them will result in undefined behaviour. + * @note The initiator will have the same transmit power as the scanner. + * @note When a connection is created it will inherit the transmit power from the initiator or + * advertiser leading to the connection. + * + * @retval ::NRF_SUCCESS Successfully changed the transmit power. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); + +/**@brief Set GAP Appearance value. + * + * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); + +/**@brief Get GAP Appearance value. + * + * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); + +/**@brief Set GAP Peripheral Preferred Connection Parameters. + * + * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Get GAP Peripheral Preferred Connection Parameters. + * + * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); + +/**@brief Set GAP device name. + * + * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), + * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. + * + * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. + * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. + * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or + * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). + * + * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, + sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); + +/**@brief Get GAP device name. + * + * @note If the device name is longer than the size of the supplied buffer, + * p_len will return the complete device name length, + * and not the number of bytes actually returned in p_dev_name. + * The application may use this information to allocate a suitable buffer size. + * + * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to + * NULL to obtain the complete device name length. + * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. + * + * @retval ::NRF_SUCCESS GAP device name retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); + +/**@brief Initiate the GAP Authentication procedure. + * + * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), + * otherwise in the peripheral role, an SMP Security Request will be sent. + * + * @events + * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be + * generated:} + * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} + * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} + * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} + * @event{@ref BLE_GAP_EVT_KEY_PRESSED} + * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} + * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} + * @event{@ref BLE_GAP_EVT_AUTH_STATUS} + * @event{@ref BLE_GAP_EVT_TIMEOUT} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the + * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. + * In the central role, this pointer may be NULL to reject a Security Request. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - An encryption is already executing or queued. + * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is + * reached. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. + */ +SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, + sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); + +/**@brief Reply with GAP security parameters. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. + * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be + * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. + * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or + * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this + * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. + * Note that the SoftDevice expects the application to provide memory for storing the + * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The + * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application + * upon reception of the + * @ref BLE_GAP_EVT_AUTH_STATUS event. + * + * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + */ +SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, + sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, + ble_gap_sec_keyset_t const *p_sec_keyset)); + +/**@brief Reply with an authentication key. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, + * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. + * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. + * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL + * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, + * then a 16-byte OOB key value in little-endian format. + * + * @retval ::NRF_SUCCESS Authentication key successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, + sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); + +/**@brief Reply with an LE Secure connections DHKey. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dhkey LE Secure Connections DHKey. + * + * @retval ::NRF_SUCCESS DHKey successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - The peer is not authenticated. + * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, + sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); + +/**@brief Notify the peer of a local keypress. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. + * + * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested. + * - Passkey has not been entered. + * - Keypresses have not been enabled by both peers. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. + */ +SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); + +/**@brief Generate a set of OOB data to send to a peer out of band. + * + * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has + * already been established, the one used during connection setup). The application may manually overwrite it with an updated + * value. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. + * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. + * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. + * + * @retval ::NRF_SUCCESS OOB data successfully generated. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, + sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, + ble_gap_lesc_oob_data_t *p_oobd_own)); + +/**@brief Provide the OOB data sent/received out of band. + * + * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. + * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this + * function. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. + * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. + * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. + * Must correspond to @ref ble_gap_sec_params_t::oob flag + * in @ref sd_ble_gap_authenticate in the central role or + * in @ref sd_ble_gap_sec_params_reply in the peripheral role. + * + * @retval ::NRF_SUCCESS OOB data accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested + * - Not expecting LESC OOB data + * - Have not actually exchanged passkeys. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, + sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, + ble_gap_lesc_oob_data_t const *p_oobd_peer)); + +/**@brief Initiate GAP Encryption procedure. + * + * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. + * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and + * retry. + */ +SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, + sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); + +/**@brief Reply with GAP security information. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in + * @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is + * available. + * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. + * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is + * available. + * + * @retval ::NRF_SUCCESS Successfully accepted security information. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. + * - Encryption information provided by the app without being requested. See @ref + * ble_gap_evt_sec_info_request_t::enc_info. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, + sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, + ble_gap_sign_info_t const *p_sign_info)); + +/**@brief Get the current connection security. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Current connection security successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); + +/**@brief Start reporting the received signal strength to the application. + * + * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. + * + * @events + * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is + * dependent on the settings of the threshold_dbm + * and skip_count input parameters.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are + * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. + * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref + * BLE_GAP_EVT_RSSI_CHANGED event. + * + * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); + +/**@brief Stop reporting the received signal strength. + * + * @note An RSSI change detected before the call but not yet received by the application + * may be reported after @ref sd_ble_gap_rssi_stop has been called. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); + +/**@brief Get the received signal strength for the last connection event. + * + * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND + * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. + * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. + * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. + * + * @retval ::NRF_SUCCESS Successfully read the RSSI. + * @retval ::NRF_ERROR_NOT_FOUND No sample is available. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + */ +SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); + +/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). + * + * @note A call to this function will require the application to keep the memory pointed by + * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped + * or when this function is called with another buffer. + * + * @note The scanner will automatically stop in the following cases: + * - @ref sd_ble_gap_scan_stop is called. + * - @ref sd_ble_gap_connect is called. + * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. + * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application + * access received data. The application must call this function to continue scanning, or call @ref + * sd_ble_gap_scan_stop to stop scanning. + * + * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will + * receive more reports from this advertising event. The following reports will include the old and new received data. + * + * @events + * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue + * scanning, this parameter must be NULL. + * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. + * The memory pointed to should be kept alive until the scanning is stopped. + * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. + * If the scanner receives advertising data larger than can be stored in the buffer, + * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status + * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. + * + * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Scanning is already ongoing and p_scan_params was not NULL + * - Scanning is not running and p_scan_params was NULL. + * - The scanner has timed out when this function is called to continue scanning. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. + * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again + */ +SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, + sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); + +/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). + * + * @note The buffer provided in @ref sd_ble_gap_scan_start is released. + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. + * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. + */ +SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); + +/**@brief Create a connection (GAP Link Establishment). + * + * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. + * The scanning procedure will be stopped even if the function returns an error. + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use + * whitelist, then p_peer_addr is ignored. + * @param[in] p_scan_params Pointer to scan parameters structure. + * @param[in] p_conn_params Pointer to desired connection parameters. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. + * + * @retval ::NRF_SUCCESS Successfully initiated connection procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * - Invalid parameter(s) in p_scan_params or p_conn_params. + * - Use of whitelist requested but whitelist has not been set, see @ref + * sd_ble_gap_whitelist_set. + * - Peer address was not present in the device identity list, see @ref + * sd_ble_gap_device_identities_set. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an + * existing locally initiated connect procedure, which must complete before initiating again. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::NRF_ERROR_RESOURCES Either: + * - Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. + * - The event_length parameter associated with conn_cfg_tag is too small to be able to + * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. + * Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_CONNECT, uint32_t, + sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, + ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); + +/**@brief Cancel a connection establishment. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. + * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection + * completed occurred. + */ +SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); + +/**@brief Initiate or respond to a PHY Update Procedure + * + * @details This function is used to initiate or respond to a PHY Update Procedure. It will always + * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. + * If this function is used to initiate a PHY Update procedure and the only option + * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the + * currently active PHYs in the respective directions, the SoftDevice will generate a + * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the + * procedure in the Link Layer. + * + * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, + * then the stack will select PHYs based on the peer's PHY preferences and the local link + * configuration. The PHY Update procedure will for this case result in a PHY combination + * that respects the time constraints configured with @ref sd_ble_cfg_set and the current + * link layer data length. + * + * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. + * + * If the peer does not support the PHY Update Procedure, then the resulting + * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to + * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. + * + * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status + * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or + * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. + * If the peer responds to the PHY Update procedure with invalid parameters, the status + * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. + * If the PHY Update procedure was rejected by the peer for a different reason, the status will + * contain the reason as specified by the peer. + * + * @events + * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} + * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} + * @endmscs + * + * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. + * @param[in] p_gap_phys Pointer to PHY structure. + * + * @retval ::NRF_SUCCESS Successfully requested a PHY Update. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of + * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref + * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. + * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the + * pending procedure to complete and retry. + * + */ +SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); + +/**@brief Initiate or respond to a Data Length Update Procedure. + * + * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of + * p_dl_params, the SoftDevice will choose the highest value supported in current + * configuration and connection parameters. + * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime + * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and + * MaxRxTime will be limited to maximum 2120 us. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update + * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let + * the SoftDevice automatically decide the value for that member. + * Set to NULL to use automatic values for all members. + * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not + * have enough resources or does not support the requested Data Length + * Update parameters. Ignored if NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect + * p_dl_limitation to see which parameter is not supported. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested + * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation + * to see where the limitation is. + * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the + * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. + */ +SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, + sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, + ble_gap_data_length_limitation_t *p_dl_limitation)); + +/**@brief Start the Quality of Service (QoS) channel survey module. + * + * @details The channel survey module provides measurements of the energy levels on + * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT + * events will periodically report the measured energy levels for each channel. + * + * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, + * Radio Timeslot API events and Flash API events. + * + * @note The channel survey module will attempt to do measurements so that the average interval + * between measurements will be interval_us. However due to the channel survey module + * having the lowest priority of all roles and modules, this may not be possible. In that + * case fewer than expected channel survey reports may be given. + * + * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available + * must be set. This is done using @ref sd_ble_cfg_set. + * + * @param[in] interval_us Requested average interval for the measurements and reports. See + * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set + * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel + * survey role will be scheduled at every available opportunity. + * + * @retval ::NRF_SUCCESS The module is successfully started. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the + * allowed range. + * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. + * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. + * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using + * @ref sd_ble_cfg_set. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); + +/**@brief Stop the Quality of Service (QoS) channel survey module. + * + * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this + * function is called. + * + * @retval ::NRF_SUCCESS The module is successfully stopped. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); + +/**@brief Obtain the next connection event counter value. + * + * @details The connection event counter is initialized to zero on the first connection event. The value is incremented + * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, + * Section 4.5.1. + * + * @note The connection event counter obtained through this API will be outdated if this API is called + * at the same time as the connection event counter is incremented. + * + * @note This API will always return the last connection event counter + 1. + * The actual connection event may be multiple connection events later if: + * - Slave latency is enabled and there is no data to transmit or receive. + * - Another role is scheduled with a higher priority at the same time as the next connection event. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. + * + * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, + sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); + +/**@brief Start triggering a given task on connection event start. + * + * @details When enabled, this feature will trigger a PPI task at the start of connection events. + * The application can configure the SoftDevice to trigger every N connection events starting from + * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_params Connection event trigger parameters. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. + * @retval ::NRF_ERROR_INVALID_STATE Either: + * - Trying to start connection event triggering when it is already ongoing. + * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. + * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value + to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, + sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); + +/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GAP_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatt.h b/variants/wio-sdk-wm1110/softdevice/ble_gatt.h new file mode 100644 index 000000000..df0d728fc --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gatt.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common + @{ + @brief Common definitions and prototypes for the GATT interfaces. + */ + +#ifndef BLE_GATT_H__ +#define BLE_GATT_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATT_DEFINES Defines + * @{ */ + +/** @brief Default ATT MTU, in bytes. */ +#define BLE_GATT_ATT_MTU_DEFAULT 23 + +/**@brief Invalid Attribute Handle. */ +#define BLE_GATT_HANDLE_INVALID 0x0000 + +/**@brief First Attribute Handle. */ +#define BLE_GATT_HANDLE_START 0x0001 + +/**@brief Last Attribute Handle. */ +#define BLE_GATT_HANDLE_END 0xFFFF + +/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources + * @{ */ +#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ +/** @} */ + +/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations + * @{ */ +#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ +/** @} */ + +/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags + * @{ */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ +/** @} */ + +/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations + * @{ */ +#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ +#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ +/** @} */ + +/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes + * @{ */ +#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ +#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ +#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ +#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ +#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ +#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ +#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ + 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ +#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ +#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ + 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ +#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ +#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ +#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ + 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ + */ +#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ + 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ +#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ + 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ +#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats + * @note Found at + * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + * @{ */ +#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ +#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ +#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ +#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ +#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ +#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ +#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ +#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces + * @{ + */ +#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ +#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATT_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. + */ +typedef struct { + uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. + The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + @mscs + @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + @endmscs + */ +} ble_gatt_conn_cfg_t; + +/**@brief GATT Characteristic Properties. */ +typedef struct { + /* Standard properties */ + uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ + uint8_t read : 1; /**< Reading the value permitted. */ + uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ + uint8_t write : 1; /**< Writing the value with Write Request permitted. */ + uint8_t notify : 1; /**< Notification of the value permitted. */ + uint8_t indicate : 1; /**< Indications of the value permitted. */ + uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ +} ble_gatt_char_props_t; + +/**@brief GATT Characteristic Extended Properties. */ +typedef struct { + /* Extended properties */ + uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ + uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ +} ble_gatt_char_ext_props_t; + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATT_H__ + +/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gattc.h b/variants/wio-sdk-wm1110/softdevice/ble_gattc.h new file mode 100644 index 000000000..f1df1782c --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gattc.h @@ -0,0 +1,764 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client + @{ + @brief Definitions and prototypes for the GATT Client interface. + */ + +#ifndef BLE_GATTC_H__ +#define BLE_GATTC_H__ + +#include "ble_err.h" +#include "ble_gatt.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GATTC API SVC numbers. */ +enum BLE_GATTC_SVCS { + SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ + SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ + SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ + SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ + SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ + SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ + SD_BLE_GATTC_READ, /**< Generic read. */ + SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ + SD_BLE_GATTC_WRITE, /**< Generic write. */ + SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ + SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ +}; + +/** + * @brief GATT Client Event IDs. + */ +enum BLE_GATTC_EVTS { + BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref + ble_gattc_evt_prim_srvc_disc_rsp_t. */ + BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. + */ + BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref + ble_gattc_evt_char_disc_rsp_t. */ + BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref + ble_gattc_evt_desc_disc_rsp_t. */ + BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref + ble_gattc_evt_attr_info_disc_rsp_t. */ + BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ + BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ + BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref + ble_gattc_evt_char_vals_read_rsp_t. */ + BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ + BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref + sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ + BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref + ble_gattc_evt_exchange_mtu_rsp_t. */ + BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ + BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref + ble_gattc_evt_write_cmd_tx_complete_t. */ +}; + +/**@brief GATTC Option IDs. + * IDs that uniquely identify a GATTC option. + */ +enum BLE_GATTC_OPTS { + BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTC_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC + * @{ */ +#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ +/** @} */ + +/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats + * @{ */ +#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ +#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ +/** @} */ + +/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults + * @{ */ +#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Write without Response that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTC_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for + transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ +} ble_gattc_conn_cfg_t; + +/**@brief Operation Handle Range. */ +typedef struct { + uint16_t start_handle; /**< Start Handle. */ + uint16_t end_handle; /**< End Handle. */ +} ble_gattc_handle_range_t; + +/**@brief GATT service. */ +typedef struct { + ble_uuid_t uuid; /**< Service UUID. */ + ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ +} ble_gattc_service_t; + +/**@brief GATT include. */ +typedef struct { + uint16_t handle; /**< Include Handle. */ + ble_gattc_service_t included_srvc; /**< Handle of the included service. */ +} ble_gattc_include_t; + +/**@brief GATT characteristic. */ +typedef struct { + ble_uuid_t uuid; /**< Characteristic UUID. */ + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + uint8_t char_ext_props : 1; /**< Extended properties present. */ + uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ + uint16_t handle_value; /**< Handle of the Characteristic Value. */ +} ble_gattc_char_t; + +/**@brief GATT descriptor. */ +typedef struct { + uint16_t handle; /**< Descriptor Handle. */ + ble_uuid_t uuid; /**< Descriptor UUID. */ +} ble_gattc_desc_t; + +/**@brief Write Parameters. */ +typedef struct { + uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ + uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ + uint16_t handle; /**< Handle to the attribute to be written. */ + uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ + uint16_t len; /**< Length of data in bytes. */ + uint8_t const *p_value; /**< Pointer to the value data. */ +} ble_gattc_write_params_t; + +/**@brief Attribute Information for 16-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ +} ble_gattc_attr_info16_t; + +/**@brief Attribute Information for 128-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ +} ble_gattc_attr_info128_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Service count. */ + ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_prim_srvc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Include count. */ + ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_rel_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Characteristic count. */ + ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_char_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Descriptor count. */ + ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_desc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Attribute count. */ + uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ + union { + ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + } info; /**< Attribute information union. */ +} ble_gattc_evt_attr_info_disc_rsp_t; + +/**@brief GATT read by UUID handle value pair. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ +} ble_gattc_handle_value_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ +typedef struct { + uint16_t count; /**< Handle-Value Pair Count. */ + uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ + uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref + sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. + @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_val_by_uuid_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint16_t offset; /**< Offset of the attribute data. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ +typedef struct { + uint16_t len; /**< Concatenated Attribute values length. */ + uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder + for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_vals_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ + uint16_t offset; /**< Data offset. */ + uint16_t len; /**< Data length. */ + uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_write_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ +typedef struct { + uint16_t handle; /**< Handle to which the HVx operation applies. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_hvx_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ +typedef struct { + uint16_t server_rx_mtu; /**< Server RX MTU size. */ +} ble_gattc_evt_exchange_mtu_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gattc_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of write without response transmissions completed. */ +} ble_gattc_evt_write_cmd_tx_complete_t; + +/**@brief GATTC event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint16_t + error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ + union { + ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ + ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ + ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ + ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ + ble_gattc_evt_char_val_by_uuid_read_rsp_t + char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ + ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ + ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ + ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ + ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ + ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ + ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ + ble_gattc_evt_write_cmd_tx_complete_t + write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ + } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ +} ble_gattc_evt_t; + +/**@brief UUID discovery option. + * + * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the + * Vendor Specific UUID table. Disabled by default. + * - When disabled, if a procedure initiated by + * @ref sd_ble_gattc_primary_services_discover, + * @ref sd_ble_gattc_relationships_discover, + * @ref sd_ble_gattc_characteristics_discover, + * @ref sd_ble_gattc_descriptors_discover + * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set + * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use + * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding + * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will + * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + * @retval ::NRF_SUCCESS Set successfully. + * + */ +typedef struct { + uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ +} ble_gattc_opt_uuid_disc_t; + +/**@brief Option structure for GATTC options. */ +typedef union { + ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ +} ble_gattc_opt_t; + +/** @} */ + +/** @addtogroup BLE_GATTC_FUNCTIONS Functions + * @{ */ + +/**@brief Initiate or continue a GATT Primary Service Discovery procedure. + * + * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. + * If the last service has not been reached, this function must be called again with an updated start handle value to + * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] start_handle Handle to start searching from. + * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, + sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); + +/**@brief Initiate or continue a GATT Relationship Discovery procedure. + * + * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been + * reached, this must be called again with an updated handle range to continue the search. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, + sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Discovery procedure. + * + * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, + sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. + * + * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not + * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, + sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. + * + * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_uuid Pointer to a Characteristic value UUID to read. + * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, + sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, + ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. + * + * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or + * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read + * the complete value. + * + * @events + * @event{@ref BLE_GATTC_EVT_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute to be read. + * @param[in] offset Offset into the attribute value to be read. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); + +/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. + * + * @details This function initiates a GATT Read Multiple Characteristic Values procedure. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. + * @param[in] handle_count The number of handles in p_handles. + * + * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, + sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); + +/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) + * procedure. + * + * @details This function can perform all write procedures described in GATT. + * + * @note Only one write with response procedure can be ongoing per connection at a time. + * If the application tries to write with response while another write with response procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. + * + * @note The number of Write without Response that can be queued is configured by @ref + * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. + * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without + * response is complete. + * + * @note The application can keep track of the available queue element count for writes without responses by following the + * procedure below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} + * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_write_params A pointer to a write parameters structure. + * + * @retval ::NRF_SUCCESS Successfully started the Write procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event + * and retry. + * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. + * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); + +/**@brief Send a Handle Value Confirmation to the GATT Server. + * + * @mscs + * @mmsc{@ref BLE_GATTC_HVI_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute in the indication. + * + * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); + +/**@brief Discovers information about a range of attributes on a GATT server. + * + * @events + * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} + * @endevents + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range The range of handles to request information about. + * + * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, + sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value, and + * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @events + * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] client_rx_mtu Client RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + used for this connection. + * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent request to the server. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, + sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); + +/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * + * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * @note If the buffer contains different event, behavior is undefined. + * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with + * the next Handle-Value pair in each iteration. If the function returns other than + * @ref NRF_SUCCESS, it will not be changed. + * - To start iteration, initialize the structure to zero. + * - To continue, pass the value from previous iteration. + * + * \code + * ble_gattc_handle_value_t iter; + * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); + * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) + * { + * app_handle = iter.handle; + * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); + * } + * \endcode + * + * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. + * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. + */ +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter); + +/** @} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter) +{ + uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; + uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; + uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; + + if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { + p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; + p_iter->p_value = p_next + sizeof(uint16_t); + return NRF_SUCCESS; + } else { + return NRF_ERROR_NOT_FOUND; + } +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_GATTC_H__ */ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatts.h b/variants/wio-sdk-wm1110/softdevice/ble_gatts.h new file mode 100644 index 000000000..dc94957cd --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_gatts.h @@ -0,0 +1,904 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server + @{ + @brief Definitions and prototypes for the GATTS interface. + */ + +#ifndef BLE_GATTS_H__ +#define BLE_GATTS_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief GATTS API SVC numbers. + */ +enum BLE_GATTS_SVCS { + SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ + SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ + SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ + SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ + SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ + SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ + SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ + SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ + SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more + attributes. */ + SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ + SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ + SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ + SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ + SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ +}; + +/** + * @brief GATT Server Event IDs. + */ +enum BLE_GATTS_EVTS { + BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See + @ref ble_gatts_evt_write_t. */ + BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with + @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. + */ + BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref + sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ + BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. + */ + BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event + structure applies. */ + BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with + @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. + */ + BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref + ble_gatts_evt_timeout_t. */ + BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref + ble_gatts_evt_hvn_tx_complete_t. */ +}; + +/**@brief GATTS Configuration IDs. + * + * IDs that uniquely identify a GATTS configuration. + */ +enum BLE_GATTS_CFGS { + BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ + BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTS_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS + * @{ */ +#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ +#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths + * @{ */ +#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ +#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ +/** @} */ + +/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types + * @{ */ +#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ +#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ +#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types + * @{ */ +#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ +#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ +#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ +#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ +/** @} */ + +/** @defgroup BLE_GATTS_OPS GATT Server Operations + * @{ */ +#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ +/** @} */ + +/** @defgroup BLE_GATTS_VLOCS GATT Value Locations + * @{ */ +#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ +#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ +#define BLE_GATTS_VLOC_USER \ + 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ + of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ + There are no alignment requirements for the buffer. */ +/** @} */ + +/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types + * @{ */ +#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ +#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ +#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ +/** @} */ + +/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags + * @{ */ +#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ +#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ +/** @} */ + +/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values + * @{ + */ +#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ + (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size + * @{ + */ +#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ +#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ +/** @} */ + +/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults + * @{ + */ +#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTS_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. + The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ +} ble_gatts_conn_cfg_t; + +/**@brief Attribute metadata. */ +typedef struct { + ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vlen : 1; /**< Variable length attribute. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ + uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not + Write Command). */ +} ble_gatts_attr_md_t; + +/**@brief GATT Attribute. */ +typedef struct { + ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ + ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ + uint16_t init_len; /**< Initial attribute value length in bytes. */ + uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the + attribute value will be left uninitialized. */ + uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ + uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is + selected in the attribute metadata, this will have to point to a buffer that remains valid through the + lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any + other temporary location. The stack may access that memory directly without the application's + knowledge. For writable characteristics, this value must not be a location in flash memory.*/ +} ble_gatts_attr_t; + +/**@brief GATT Attribute Value. */ +typedef struct { + uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ + uint16_t offset; /**< Attribute value offset. */ + uint8_t *p_value; /**< Pointer to where value is stored or will be stored. + If value is stored in user memory, only the attribute length is updated when p_value == NULL. + Set to NULL when reading to obtain the complete length of the attribute value */ +} ble_gatts_value_t; + +/**@brief GATT Characteristic Presentation Format. */ +typedef struct { + uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ + int8_t exponent; /**< Exponent for integer data types. */ + uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ + uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ + uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ +} ble_gatts_char_pf_t; + +/**@brief GATT Characteristic metadata. */ +typedef struct { + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ + uint8_t const * + p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ + uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ + uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ + ble_gatts_char_pf_t const + *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ + ble_gatts_attr_md_t const + *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ +} ble_gatts_char_md_t; + +/**@brief GATT Characteristic Definition Handles. */ +typedef struct { + uint16_t value_handle; /**< Handle to the characteristic value. */ + uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ + uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ + uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ +} ble_gatts_char_handles_t; + +/**@brief GATT HVx parameters. */ +typedef struct { + uint16_t handle; /**< Characteristic Value Handle. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t offset; /**< Offset within the attribute value. */ + uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ + uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ +} ble_gatts_hvx_params_t; + +/**@brief GATT Authorization parameters. */ +typedef struct { + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. + Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, + as the data to be written needs to be stored and later provided by the application. */ + uint16_t offset; /**< Offset of the attribute value being updated. */ + uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ + uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ +} ble_gatts_authorize_params_t; + +/**@brief GATT Read or Write Authorize Reply parameters. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ + ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ + } params; /**< Reply Parameters. */ +} ble_gatts_rw_authorize_reply_params_t; + +/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref + BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ +} ble_gatts_cfg_service_changed_t; + +/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - @ref ble_gatts_attr_md_t::write_perm is out of range. + * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is + * not allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is + * allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. + * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported + */ +typedef struct { + ble_gatts_attr_md_t + perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ +} ble_gatts_cfg_service_changed_cccd_perm_t; + +/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The specified Attribute Table size is too small. + * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. + * - The specified Attribute Table size is not a multiple of 4. + */ +typedef struct { + uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref + BLE_GATTS_ATTR_TAB_SIZE_MIN. */ +} ble_gatts_cfg_attr_tab_size_t; + +/**@brief Config structure for GATTS configurations. */ +typedef union { + ble_gatts_cfg_service_changed_t + service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ + ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ + ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ +} ble_gatts_cfg_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ + uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref + sd_ble_gatts_value_set to finalize the writing operation. */ + uint16_t offset; /**< Offset for the write operation. */ + uint16_t len; /**< Length of the received data. */ + uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gatts_evt_write_t; + +/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint16_t offset; /**< Offset for the read operation. */ +} ble_gatts_evt_read_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ + ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ + } request; /**< Request Parameters. */ +} ble_gatts_evt_rw_authorize_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ +typedef struct { + uint8_t hint; /**< Hint (currently unused). */ +} ble_gatts_evt_sys_attr_missing_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ +} ble_gatts_evt_hvc_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ +typedef struct { + uint16_t client_rx_mtu; /**< Client RX MTU size. */ +} ble_gatts_evt_exchange_mtu_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gatts_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of notification transmissions completed. */ +} ble_gatts_evt_hvn_tx_complete_t; + +/**@brief GATTS event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ + union { + ble_gatts_evt_write_t write; /**< Write Event Parameters. */ + ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ + ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ + ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ + ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ + ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ + ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_gatts_evt_t; + +/** @} */ + +/** @addtogroup BLE_GATTS_FUNCTIONS Functions + * @{ */ + +/**@brief Add a service declaration to the Attribute Table. + * + * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to + * add a secondary service declaration that is not referenced by another service later in the Attribute Table. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. + * @param[in] p_uuid Pointer to service UUID. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a service declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); + +/**@brief Add an include declaration to the Attribute Table. + * + * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note The included service must already be present in the Attribute Table prior to this call. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID + * is used, it will be placed sequentially. + * @param[in] inc_srvc_handle Handle of the included service. + * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added an include declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + */ +SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, + sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); + +/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations + * to the Attribute Table. + * + * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and + * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format + * values. + * + * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic + * permissions. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_char_md Characteristic metadata. + * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. + * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a characteristic. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, + sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); + +/**@brief Add a descriptor to the Attribute Table. + * + * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is + * supported at this time). + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_attr Pointer to the attribute structure. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a descriptor. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, + sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); + +/**@brief Set the value of a given attribute. + * + * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully set the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + */ +SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, + sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Get the value of a given attribute. + * + * @note If the attribute value is longer than the size of the supplied buffer, + * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), + * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. + * The application may use this information to allocate a suitable buffer size. + * + * @note When retrieving system attribute values with this function, the connection handle + * may refer to an already disconnected connection. Refer to the documentation of + * @ref sd_ble_gatts_sys_attr_get for further information. + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + */ +SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, + sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Notify or Indicate an attribute value. + * + * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant + * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before + * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single + * API call. + * + * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during + * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, + * @ref NRF_ERROR_BUSY, + * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. + * The caller can check whether the value has been updated by looking at the contents of *(@ref + * ble_gatts_hvx_params_t::p_len). + * + * @note Only one indication procedure can be ongoing per connection at a time. + * If the application tries to indicate an attribute value while another indication procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. + * + * @note The number of Handle Value Notifications that can be queued is configured by @ref + * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. + * + * @note The application can keep track of the available queue element count for notifications by following the procedure + * below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} + * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_HVN_MSC} + * @mmsc{@ref BLE_GATTS_HVI_MSC} + * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data + * contains a non-NULL pointer the attribute value will be updated with the contents + * pointed by it before sending the notification or indication. If the attribute value + * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to + * contain the number of actual bytes written, else it will be set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute + * value. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application + * are available to notify and indicate. + * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and + * indicated. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions + * of the CCCD associated with this characteristic. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC + * event and retry. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. + * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); + +/**@brief Indicate the Service Changed attribute value. + * + * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute + * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will + * be issued. + * + * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. + * + * @events + * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_SC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] start_handle Start of affected attribute handle range. + * @param[in] end_handle End of affected attribute handle range. + * + * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref + * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the + * application. + * @retval ::NRF_ERROR_BUSY Procedure already in progress. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, + sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); + +/**@brief Respond to a Read/Write authorization request. + * + * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. + * + * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond + * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update + * is set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute + * Table updated. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, + * handle supplied does not match requested handle, + * or invalid data to be written provided by the application. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, + sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); + +/**@brief Update persistent system attribute information. + * + * @details Supply information about persistent system attributes to the stack, + * previously obtained using @ref sd_ble_gatts_sys_attr_get. + * This call is only allowed for active connections, and is usually + * made immediately after a connection is established with an known bonded device, + * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. + * + * p_sysattrs may point directly to the application's stored copy of the system attributes + * obtained using @ref sd_ble_gatts_sys_attr_get. + * If the pointer is NULL, the system attribute info is initialized, assuming that + * the application does not have any previously saved system attribute data for this device. + * + * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. + * + * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may + * have been completed only partially. This means that the state of the attribute table is undefined, and the application should + * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be modified. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be modified. + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. + * @param[in] len Size of data pointed by p_sys_attr_data, in octets. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully set the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref + * sd_ble_gatts_sys_attr_get. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, + sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); + +/**@brief Retrieve persistent system attribute information from the stack. + * + * @details This call is used to retrieve information about values to be stored persistently by the application + * during the lifetime of a connection or after it has been terminated. When a new connection is established with the + * same bonded device, the system attribute information retrieved with this function should be restored using using @ref + * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The + * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API + * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the + * system attributes may be written to at any time by the peer during a connection's lifetime. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be returned. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be returned. + * + * @mscs + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle of the recently terminated connection. + * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The + * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. + * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual + * length of system attribute data. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. + * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, + sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); + +/**@brief Retrieve the first valid user attribute handle. + * + * @param[out] p_handle Pointer to an integer where the handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully retrieved the handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); + +/**@brief Retrieve the attribute UUID and/or metadata. + * + * @param[in] handle Attribute handle + * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. + * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. + * + * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. + * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. + */ +SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); + +/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. + * + * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and + * - The Server RX MTU value. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @mscs + * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] server_rx_mtu Server RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + * used for this connection. + * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent response to the client. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATTS_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_hci.h b/variants/wio-sdk-wm1110/softdevice/ble_hci.h new file mode 100644 index 000000000..27f85d52e --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_hci.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ +*/ + +#ifndef BLE_HCI_H__ +#define BLE_HCI_H__ +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes + * @{ */ + +#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ +/*0x03 Hardware Failure +0x04 Page Timeout +*/ +#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ +#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ +#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ +#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ +/*0x09 Connection Limit Exceeded +0x0A Synchronous Connection Limit To A Device Exceeded +0x0B ACL Connection Already Exists*/ +#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ +/*0x0D Connection Rejected due to Limited Resources +0x0E Connection Rejected Due To Security Reasons +0x0F Connection Rejected due to Unacceptable BD_ADDR +0x10 Connection Accept Timeout Exceeded +0x11 Unsupported Feature or Parameter Value*/ +#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ +#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ + 0x14 /**< Remote Device Terminated Connection due to low \ + resources.*/ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ +#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ +/* +0x17 Repeated Attempts +0x18 Pairing Not Allowed +0x19 Unknown LMP PDU +*/ +#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ +/* +0x1B SCO Offset Rejected +0x1C SCO Interval Rejected +0x1D SCO Air Mode Rejected*/ +#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ +#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ +/*0x20 Unsupported LMP Parameter Value +0x21 Role Change Not Allowed +*/ +#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ +#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ +#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ +/*0x25 Encryption Mode Not Acceptable +0x26 Link Key Can Not be Changed +0x27 Requested QoS Not Supported +*/ +#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ +#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ +#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ +/* +0x2B Reserved +0x2C QoS Unacceptable Parameter +0x2D QoS Rejected +0x2E Channel Classification Not Supported +0x2F Insufficient Security +*/ +#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ +/* +0x31 Reserved +0x32 Role Switch Pending +0x33 Reserved +0x34 Reserved Slot Violation +0x35 Role Switch Failed +0x36 Extended Inquiry Response Too Large +0x37 Secure Simple Pairing Not Supported By Host. +0x38 Host Busy - Pairing +0x39 Connection Rejected due to No Suitable Channel Found*/ +#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ +#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ +#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ +#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ +#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_HCI_H__ + +/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h b/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h new file mode 100644 index 000000000..5f4bd277d --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) + @{ + @brief Definitions and prototypes for the L2CAP interface. + */ + +#ifndef BLE_L2CAP_H__ +#define BLE_L2CAP_H__ + +#include "ble_err.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology + * @{ + * @details + * + * L2CAP SDU + * - A data unit that the application can send/receive to/from a peer. + * + * L2CAP PDU + * - A data unit that is exchanged between local and remote L2CAP entities. + * It consists of L2CAP protocol control information and payload fields. + * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. + * + * L2CAP MTU + * - The maximum length of an L2CAP SDU. + * + * L2CAP MPS + * - The maximum length of an L2CAP PDU payload field. + * + * Credits + * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. + * @} */ + +/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief L2CAP API SVC numbers. */ +enum BLE_L2CAP_SVCS { + SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ + SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ + SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ +}; + +/**@brief L2CAP Event IDs. */ +enum BLE_L2CAP_EVTS { + BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. + \n Reply with @ref sd_ble_l2cap_ch_setup. + \n See @ref ble_l2cap_evt_ch_setup_request_t. */ + BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. + \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ + BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. + \n See @ref ble_l2cap_evt_ch_setup_t. */ + BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. + \n No additional event structure applies. */ + BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. + \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ + BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. + \n See @ref ble_l2cap_evt_ch_credit_t. */ + BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. + \n See @ref ble_l2cap_evt_ch_rx_t. */ + BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. + \n See @ref ble_l2cap_evt_ch_tx_t. */ +}; + +/** @} */ + +/**@addtogroup BLE_L2CAP_DEFINES Defines + * @{ */ + +/**@brief Maximum number of L2CAP channels per connection. */ +#define BLE_L2CAP_CH_COUNT_MAX (64) + +/**@brief Minimum L2CAP MTU, in bytes. */ +#define BLE_L2CAP_MTU_MIN (23) + +/**@brief Minimum L2CAP MPS, in bytes. */ +#define BLE_L2CAP_MPS_MIN (23) + +/**@brief Invalid CID. */ +#define BLE_L2CAP_CID_INVALID (0x0000) + +/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ +#define BLE_L2CAP_CREDITS_DEFAULT (1) + +/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources + * @{ */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ + /** @} */ + +/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes + * @{ */ +#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ +#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ +#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ +#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ +#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ +#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ +#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ + (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ +#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ +/** @} */ + +/** @} */ + +/**@addtogroup BLE_L2CAP_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note These parameters are set per connection, so all L2CAP channels created on this connection + * will have the same parameters. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. + * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. + */ +typedef struct { + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to receive on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to transmit on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per + L2CAP channel. The minimum value is one. */ + uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission + per L2CAP channel. The minimum value is one. */ + uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection + with this configuration. The default value is zero, the maximum + value is @ref BLE_L2CAP_CH_COUNT_MAX. + @note if this parameter is set to zero, all other parameters in + @ref ble_l2cap_conn_cfg_t are ignored. */ +} ble_l2cap_conn_cfg_t; + +/**@brief L2CAP channel RX parameters. */ +typedef struct { + uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to + receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be + able to receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. + - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ + ble_data_t sdu_buf; /**< SDU data buffer for reception. + - If @ref ble_data_t::p_data is non-NULL, initial credits are + issued to the peer. + - If @ref ble_data_t::p_data is NULL, no initial credits are + issued to the peer. */ +} ble_l2cap_ch_rx_params_t; + +/**@brief L2CAP channel setup parameters. */ +typedef struct { + ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting + setup of an L2CAP channel, ignored otherwise. */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. + Used when replying to a setup request of an L2CAP + channel, ignored otherwise. */ +} ble_l2cap_ch_setup_params_t; + +/**@brief L2CAP channel TX parameters. */ +typedef struct { + uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to + transmit on this L2CAP channel. */ + uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is + able to receive on this L2CAP channel. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able + to transmit on this L2CAP channel. This is effective tx_mps, + selected by the SoftDevice as + MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ + uint16_t credits; /**< Initial credits given by the peer. */ +} ble_l2cap_ch_tx_params_t; + +/**@brief L2CAP Channel Setup Request event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ +} ble_l2cap_evt_ch_setup_request_t; + +/**@brief L2CAP Channel Setup Refused event. */ +typedef struct { + uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ +} ble_l2cap_evt_ch_setup_refused_t; + +/**@brief L2CAP Channel Setup Completed event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ +} ble_l2cap_evt_ch_setup_t; + +/**@brief L2CAP Channel SDU Data Buffer Released event. */ +typedef struct { + ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice + returns SDU data buffers supplied by the application, which have + not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or + @ref BLE_L2CAP_EVT_CH_TX event. */ +} ble_l2cap_evt_ch_sdu_buf_released_t; + +/**@brief L2CAP Channel Credit received event. */ +typedef struct { + uint16_t credits; /**< Additional credits given by the peer. */ +} ble_l2cap_evt_ch_credit_t; + +/**@brief L2CAP Channel received SDU event. */ +typedef struct { + uint16_t sdu_len; /**< Total SDU length, in bytes. */ + ble_data_t sdu_buf; /**< SDU data buffer. + @note If there is not enough space in the buffer + (sdu_buf.len < sdu_len) then the rest of the SDU will be + silently discarded by the SoftDevice. */ +} ble_l2cap_evt_ch_rx_t; + +/**@brief L2CAP Channel transmitted SDU event. */ +typedef struct { + ble_data_t sdu_buf; /**< SDU data buffer. */ +} ble_l2cap_evt_ch_tx_t; + +/**@brief L2CAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occured. */ + uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or + @ref BLE_L2CAP_CID_INVALID if not present. */ + union { + ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ + ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ + ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ + ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ + ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ + ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ + ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_l2cap_evt_t; + +/** @} */ + +/**@addtogroup BLE_L2CAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set up an L2CAP channel. + * + * @details This function is used to: + * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. + * - Reply to a setup request of an L2CAP channel (if called in response to a + * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection + * Response packet to a peer. + * + * @note A call to this function will require the application to keep the SDU data buffer alive + * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or + * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} + * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: + * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP + * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * event when replying to a setup request of an L2CAP channel. + * - As output: local_cid for this channel. + * @param[in] p_params L2CAP channel parameters. + * + * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, + * see @ref ble_l2cap_conn_cfg_t::ch_count. + */ +SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, + sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); + +/**@brief Release an L2CAP channel. + * + * @details This sends a Disconnection Request packet to a peer. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * + * @retval ::NRF_SUCCESS Successfully queued request for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); + +/**@brief Receive an SDU on an L2CAP channel. + * + * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers + * for reception per L2CAP channel. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Buffer accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a + * @ref BLE_L2CAP_EVT_CH_RX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Transmit an SDU on an L2CAP channel. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for + * transmission per L2CAP channel. + * + * @note The application can keep track of the available credits for transmission by following + * the procedure below: + * - Store initial credits given by the peer in a variable. + * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Decrement the variable, which stores the currently available credits, by + * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns + * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Increment the variable, which stores the currently available credits, by additional + * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than + * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in + * @ref BLE_L2CAP_EVT_CH_SETUP event. + * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a + * @ref BLE_L2CAP_EVT_CH_TX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Advanced SDU reception flow control. + * + * @details Adjust the way the SoftDevice issues credits to the peer. + * This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set + * the value that will be used for newly created channels. + * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every + * time it starts using a new reception buffer. + * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will + * use if this function is not called. + * - If set to zero, the SoftDevice will stop issuing credits for new reception + * buffers the application provides or has provided. SDU reception that is + * currently ongoing will be allowed to complete. + * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be + * written by the SoftDevice with the number of credits that is or will be + * available to the peer. If the value written by the SoftDevice is 0 when + * credits parameter was set to 0, the peer will not be able to send more + * data until more credits are provided by calling this function again with + * credits > 0. This parameter is ignored when local_cid is set to + * @ref BLE_L2CAP_CID_INVALID. + * + * @note Application should take care when setting number of credits higher than default value. In + * this case the application must make sure that the SoftDevice always has reception buffers + * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have + * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic + * on the connection handle may be stalled until the SoftDevice again has an available + * reception buffer. This applies even if the application has used this call to set the + * credits back to default, or zero. + * + * @retval ::NRF_SUCCESS Flow control parameters accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, + sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_L2CAP_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_ranges.h b/variants/wio-sdk-wm1110/softdevice/ble_ranges.h new file mode 100644 index 000000000..2768e4996 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_ranges.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_ranges Module specific SVC, event and option number subranges + @{ + + @brief Definition of SVC, event and option number subranges for each API module. + + @note + SVCs, event and option numbers are split into subranges for each API module. + Each module receives its entire allocated range of SVC calls, whether implemented or not, + but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. + + Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, + rather than the last SVC function call actually defined and implemented. + + Specific SVC, event and option values are defined in each module's ble_.h file, + which defines names of each individual SVC code based on the range start value. +*/ + +#ifndef BLE_RANGES_H__ +#define BLE_RANGES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ +#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ + +#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ +#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ + +#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ +#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ + +#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ +#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ + +#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ +#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ + +#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ + +#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ +#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ + +#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ +#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ + +#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ +#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ + +#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ +#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ + +#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ +#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ + +#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ + +#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ +#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ + +#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ +#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ + +#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ +#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ + +#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ +#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ + +#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ +#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ + +#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ +#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ + +#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ + +#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ +#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ + +#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ +#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ + +#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ +#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ + +#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ +#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ + +#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ +#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ + +#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ +#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ + +#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ +#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_RANGES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_types.h b/variants/wio-sdk-wm1110/softdevice/ble_types.h new file mode 100644 index 000000000..db3656cfd --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/ble_types.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_types Common types and macro definitions + @{ + + @brief Common types and macro definitions for the BLE SoftDevice. + */ + +#ifndef BLE_TYPES_H__ +#define BLE_TYPES_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_TYPES_DEFINES Defines + * @{ */ + +/** @defgroup BLE_CONN_HANDLES BLE Connection Handles + * @{ */ +#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ +#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ +/** @} */ + +/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs + * @{ */ +/* Generic UUIDs, applicable to all services */ +#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ +#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ +#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ +#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ +#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ +#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ +/* GATT specific UUIDs */ +#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ +#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ +/* GAP specific UUIDs */ +#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ +#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ +/** @} */ + +/** @defgroup BLE_UUID_TYPES Types of UUID + * @{ */ +#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ +#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ +#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ +/** @} */ + +/** @defgroup BLE_APPEARANCES Bluetooth Appearance values + * @note Retrieved from + * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * @{ */ +#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ +#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ +#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ +#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ +#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ +#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ +#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ +#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ +#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ +#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ +#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ +#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ +#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ +#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ +#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ +#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ +#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ +#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ +#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ +#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ +#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ +#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ +#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ +#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ +#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ +#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ +#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ +#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ +#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ +#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ +#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ +#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ +#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ +#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ +#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ +#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ + 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ + 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ +/** @} */ + +/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ +#define BLE_UUID_BLE_ASSIGN(instance, value) \ + do { \ + instance.type = BLE_UUID_TYPE_BLE; \ + instance.uuid = value; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ +#define BLE_UUID_COPY_PTR(dst, src) \ + do { \ + (dst)->type = (src)->type; \ + (dst)->uuid = (src)->uuid; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ +#define BLE_UUID_COPY_INST(dst, src) \ + do { \ + (dst).type = (src).type; \ + (dst).uuid = (src).uuid; \ + } while (0) + +/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) + +/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) + +/** @} */ + +/** @addtogroup BLE_TYPES_STRUCTURES Structures + * @{ */ + +/** @brief 128 bit UUID values. */ +typedef struct { + uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ +} ble_uuid128_t; + +/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ +typedef struct { + uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ + uint8_t + type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ +} ble_uuid_t; + +/**@brief Data structure. */ +typedef struct { + uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ + uint16_t len; /**< Length of the data buffer, in bytes. */ +} ble_data_t; + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* BLE_TYPES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h new file mode 100644 index 000000000..4e0bd752a --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_mbr_api Master Boot Record API + @{ + + @brief APIs for updating SoftDevice and BootLoader + +*/ + +#ifndef NRF_MBR_H__ +#define NRF_MBR_H__ + +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_MBR_DEFINES Defines + * @{ */ + +/**@brief MBR SVC Base number. */ +#define MBR_SVC_BASE (0x18) + +/**@brief Page size in words. */ +#define MBR_PAGE_SIZE_IN_WORDS (1024) + +/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. +This is the offset where the first byte of the SoftDevice hex file is written. */ +#define MBR_SIZE (0x1000) + +/** @brief Location (in the flash memory) of the bootloader address. */ +#define MBR_BOOTLOADER_ADDR (0xFF8) + +/** @brief Location (in UICR) of the bootloader address. */ +#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) + +/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ +#define MBR_PARAM_PAGE_ADDR (0xFFC) + +/** @brief Location (in UICR) of the address of the MBR parameter page. */ +#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) + +/** @} */ + +/** @addtogroup NRF_MBR_ENUMS Enumerations + * @{ */ + +/**@brief nRF Master Boot Record API SVC numbers. */ +enum NRF_MBR_SVCS { + SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ +}; + +/**@brief Possible values for ::sd_mbr_command_t.command */ +enum NRF_MBR_COMMANDS { + SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ + SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ + SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any + parameters in ::sd_mbr_command_t params.*/ + SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ + SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see + ::sd_mbr_command_vector_table_base_set_t*/ + SD_MBR_COMMAND_RESERVED, + SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see + ::sd_mbr_command_irq_forward_address_set_t*/ +}; + +/** @} */ + +/** @addtogroup NRF_MBR_TYPES Types + * @{ */ + +/**@brief This command copies part of a new SoftDevice + * + * The destination area is erased before copying. + * If dst is in the middle of a flash page, that whole flash page will be erased. + * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. + * + * The user of this function is responsible for setting the BPROT registers. + * + * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. + * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. + */ +typedef struct { + uint32_t *src; /**< Pointer to the source of data to be copied.*/ + uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ + uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ +} sd_mbr_command_copy_sd_t; + +/**@brief This command works like memcmp, but takes the length in words. + * + * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. + * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. + */ +typedef struct { + uint32_t *ptr1; /**< Pointer to block of memory. */ + uint32_t *ptr2; /**< Pointer to block of memory. */ + uint32_t len; /**< Number of 32 bit words to compare.*/ +} sd_mbr_command_compare_t; + +/**@brief This command copies a new BootLoader. + * + * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to + * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * The bootloader destination is erased by this function. + * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is + * not intended to be written. + * + * On success, this function will not return. It will start the new bootloader from reset-vector as normal. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. + * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ + uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ +} sd_mbr_command_copy_bl_t; + +/**@brief Change the address the MBR starts after a reset + * + * Once this function has been called, this address is where the MBR will start to forward + * interrupts to after a reset. + * + * To restore default forwarding, this function should be called with @ref address set to 0. If a + * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will + * be forwarded to the SoftDevice. + * + * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or + * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * On success, this function will not return. It will reset the device. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_vector_table_base_set_t; + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR + * + * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not + * change where the MBR starts after reset. + * + * @retval ::NRF_SUCCESS + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_irq_forward_address_set_t; + +/**@brief Input structure containing data used when calling ::sd_mbr_command + * + * Depending on what command value that is set, the corresponding params value type must also be + * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command + * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. + */ +typedef struct { + uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ + union { + sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ + sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ + sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ + sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ + sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ + } params; /**< Command parameters. */ +} sd_mbr_command_t; + +/** @} */ + +/** @addtogroup NRF_MBR_FUNCTIONS Functions + * @{ */ + +/**@brief Issue Master Boot Record commands + * + * Commands used when updating a SoftDevice and bootloader. + * + * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires + * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash + * page. The location of the flash page should be provided by the application in either + * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR + * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to + * store the command before reset. When an address is specified, the page it refers to must not be + * used by the application. If no address is provided by the application, i.e. both + * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use + * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. + * + * @param[in] param Pointer to a struct describing the command. + * + * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, + * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, + * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t + * + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided + * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. + */ +SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_MBR_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error.h b/variants/wio-sdk-wm1110/softdevice/nrf_error.h new file mode 100644 index 000000000..fb2831e19 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_error.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_error SoftDevice Global Error Codes + @{ + + @brief Global Error definitions +*/ + +/* Header guard */ +#ifndef NRF_ERROR_H__ +#define NRF_ERROR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions + * @{ */ +#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base +#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base +#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base +#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base +/** @} */ + +#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command +#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing +#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled +#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error +#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation +#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found +#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported +#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter +#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state +#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length +#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags +#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data +#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size +#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out +#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer +#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation +#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address +#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy +#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. +#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h new file mode 100644 index 000000000..2fd621057 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_sdm_api + @{ + @defgroup nrf_sdm_error SoftDevice Manager Error Codes + @{ + + @brief Error definitions for the SDM API +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SDM_H__ +#define NRF_ERROR_SDM_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. +#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ + (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having + ///< enabled SoftDevice interrupts). +#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ + (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SDM_H__ + +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h new file mode 100644 index 000000000..cbd0ba8ac --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_soc_api + @{ + @defgroup nrf_soc_error SoC Library Error Codes + @{ + + @brief Error definitions for the SoC library + +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SOC_H__ +#define NRF_ERROR_SOC_H__ + +#include "nrf_error.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* Mutex Errors */ +#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken + +/* NVIC errors */ +#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available +#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed +#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return + +/* Power errors */ +#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown +#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown +#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return + +/* Rand errors */ +#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values + +/* PPI errors */ +#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel +#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SOC_H__ +/** + @} + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h b/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h new file mode 100644 index 000000000..d4ab204d9 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_nvic_api SoftDevice NVIC API + * @{ + * + * @note In order to use this module, the following code has to be added to a .c file: + * \code + * nrf_nvic_state_t nrf_nvic_state = {0}; + * \endcode + * + * @note Definitions and declarations starting with __ (double underscore) in this header file are + * not intended for direct use by the application. + * + * @brief APIs for the accessing NVIC when using a SoftDevice. + * + */ + +#ifndef NRF_NVIC_H__ +#define NRF_NVIC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_NVIC_DEFINES Defines + * @{ */ + +/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions + * @{ */ + +#define __NRF_NVIC_NVMC_IRQn \ + (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ + number in the MDK. */ + +#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ + +/**@brief Interrupt priority levels used by the SoftDevice. */ +#define __NRF_NVIC_SD_IRQ_PRIOS \ + ((uint8_t)((1U << 0) /**< Priority level high .*/ \ + | (1U << 1) /**< Priority level medium. */ \ + | (1U << 4) /**< Priority level low. */ \ + )) + +/**@brief Interrupt priority levels available to the application. */ +#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ +#define __NRF_NVIC_SD_IRQS_0 \ + ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ + (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ + (1U << (uint32_t)SWI5_IRQn))) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ +#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) + +/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ +#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) + +/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ +#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) + +/**@} */ + +/**@} */ + +/**@addtogroup NRF_NVIC_VARIABLES Variables + * @{ */ + +/**@brief Type representing the state struct for the SoftDevice NVIC module. */ +typedef struct { + uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ + uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ +} nrf_nvic_state_t; + +/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an + * application source file. */ +extern nrf_nvic_state_t nrf_nvic_state; + +/**@} */ + +/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions + * @{ */ + +/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. + * + * @retval The value of PRIMASK prior to disabling the interrupts. + */ +__STATIC_INLINE int __sd_nvic_irq_disable(void); + +/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. + */ +__STATIC_INLINE void __sd_nvic_irq_enable(void); + +/**@brief Checks if IRQn is available to application + * @param[in] IRQn IRQ to check + * + * @retval 1 (true) if the IRQ to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); + +/**@brief Checks if priority is available to application + * @param[in] priority priority to check + * + * @retval 1 (true) if the priority to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); + +/**@} */ + +/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions + * @{ */ + +/**@brief Enable External Interrupt. + * @note Corresponds to NVIC_EnableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was enabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); + +/**@brief Disable External Interrupt. + * @note Corresponds to NVIC_DisableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was disabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); + +/**@brief Get Pending Interrupt. + * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. + * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. + * + * @retval ::NRF_SUCCESS The interrupt is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); + +/**@brief Set Pending Interrupt. + * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt is set pending. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); + +/**@brief Clear Pending Interrupt. + * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); + +/**@brief Set Interrupt Priority. + * @note Corresponds to NVIC_SetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * @pre Priority is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. + * @param[in] priority A valid IRQ priority for use by the application. + * + * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); + +/**@brief Get Interrupt Priority. + * @note Corresponds to NVIC_GetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. + * @param[out] p_priority Return value from NVIC_GetPriority. + * + * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); + +/**@brief System Reset. + * @note Corresponds to NVIC_SystemReset in CMSIS. + * + * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN + */ +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); + +/**@brief Enter critical region. + * + * @post Application interrupts will be disabled. + * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each + * execution context + * @sa sd_nvic_critical_region_exit + * + * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); + +/**@brief Exit critical region. + * + * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. + * @post If not in a nested critical region, the application interrupts will restored to the state before + * ::sd_nvic_critical_region_enter was called. + * + * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa + * sd_nvic_critical_region_enter. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); + +/**@} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE int __sd_nvic_irq_disable(void) +{ + int pm = __get_PRIMASK(); + __disable_irq(); + return pm; +} + +__STATIC_INLINE void __sd_nvic_irq_enable(void) +{ + __enable_irq(); +} + +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) +{ + if (IRQn < 32) { + return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; + } else if (IRQn < 64) { + return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; + } else { + return 1; + } +} + +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) +{ + if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { + return 0; + } + return 1; +} + +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= + (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); + } else { + NVIC_EnableIRQ(IRQn); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); + } else { + NVIC_DisableIRQ(IRQn); + } + + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_pending_irq = NVIC_GetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_SetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_ClearPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (!__sd_nvic_is_app_accessible_priority(priority)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + NVIC_SetPriority(IRQn, (uint32_t)priority); + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) +{ + NVIC_SystemReset(); + return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) +{ + int was_masked = __sd_nvic_irq_disable(); + if (!nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__cr_flag = 1; + nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); + NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; + nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); + NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; + *p_is_nested_critical_region = 0; + } else { + *p_is_nested_critical_region = 1; + } + if (!was_masked) { + __sd_nvic_irq_enable(); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) +{ + if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { + int was_masked = __sd_nvic_irq_disable(); + NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; + NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; + nrf_nvic_state.__cr_flag = 0; + if (!was_masked) { + __sd_nvic_irq_enable(); + } + } + + return NRF_SUCCESS; +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif + +#endif // NRF_NVIC_H__ + +/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h new file mode 100644 index 000000000..2786a86a4 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_sdm_api SoftDevice Manager API + @{ + + @brief APIs for SoftDevice management. + +*/ + +#ifndef NRF_SDM_H__ +#define NRF_SDM_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_sdm.h" +#include "nrf_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ +#ifdef NRFSOC_DOXYGEN +/// Declared in nrf_mbr.h +#define MBR_SIZE 0 +#warning test +#endif + +/** @brief The major version for the SoftDevice binary distributed with this header file. */ +#define SD_MAJOR_VERSION (7) + +/** @brief The minor version for the SoftDevice binary distributed with this header file. */ +#define SD_MINOR_VERSION (3) + +/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ +#define SD_BUGFIX_VERSION (0) + +/** @brief The SoftDevice variant of this firmware. */ +#define SD_VARIANT_ID 140 + +/** @brief The full version number for the SoftDevice binary this header file was distributed + * with, as a decimal number in the form Mmmmbbb, where: + * - M is major version (one or more digits) + * - mmm is minor version (three digits) + * - bbb is bugfix version (three digits). */ +#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) + +/** @brief SoftDevice Manager SVC Base number. */ +#define SDM_SVC_BASE 0x10 + +/** @brief SoftDevice unique string size in bytes. */ +#define SD_UNIQUE_STR_SIZE 20 + +/** @brief Invalid info field. Returned when an info field does not exist. */ +#define SDM_INFO_FIELD_INVALID (0) + +/** @brief Defines the SoftDevice Information Structure location (address) as an offset from +the start of the SoftDevice (without MBR)*/ +#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) + +/** @brief Defines the absolute SoftDevice Information Structure location (address) when the + * SoftDevice is installed just above the MBR (the usual case). */ +#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) + +/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the + * SoftDevice base address. The size value is of type uint8_t. */ +#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) + +/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. + * The size value is of type uint32_t. */ +#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) + +/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value + * is of type uint16_t. */ +#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) + +/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID + * is of type uint32_t. */ +#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) + +/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in + * the same format as @ref SD_VERSION, stored as an uint32_t. */ +#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) + +/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. + * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. + */ +#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) + +/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value + * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is + * installed just above the MBR (the usual case). */ +#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base + * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above + * the MBR (the usual case). */ +#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) + +/** @brief Defines the amount of flash that is used by the SoftDevice. + * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed + * just above the MBR (the usual case). + */ +#define SD_FLASH_SIZE 0x26000 + +/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual + * case). */ +#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the + * usual case). */ +#define SD_ID_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_VERSION_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges + * @{ */ +#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ +#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ +/**@} */ + +/**@defgroup NRF_FAULT_IDS Fault ID types + * @{ */ +#define NRF_FAULT_ID_SD_ASSERT \ + (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ +#define NRF_FAULT_ID_APP_MEMACC \ + (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ + in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ + register violation the info parameter will contain the sub-region number of \ + PREGION[0], on whose address range the disallowed write access caused the \ + memory access fault. */ +/**@} */ + +/** @} */ + +/** @addtogroup NRF_SDM_ENUMS Enumerations + * @{ */ + +/**@brief nRF SoftDevice Manager API SVC numbers. */ +enum NRF_SD_SVCS { + SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ + SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ + SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ + SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ + SVC_SDM_LAST /**< Placeholder for last SDM SVC */ +}; + +/** @} */ + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ + +/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy + * @{ */ + +#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ +#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ +#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ +#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ +#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ +#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ +#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ +#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ +#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ +#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ +#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ +#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ + +/** @} */ + +/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources + * @{ */ + +#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ +#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ +#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ + +/** @} */ + +/** @} */ + +/** @addtogroup NRF_SDM_TYPES Types + * @{ */ + +/**@brief Type representing LFCLK oscillator source. */ +typedef struct { + uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ + uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second + units (nRF52: 1-32). + @note To avoid excessive clock drift, 0.5 degrees Celsius is the + maximum temperature change allowed in one calibration timer + interval. The interval should be selected to ensure this. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ + uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration + intervals) the RC oscillator shall be calibrated if the temperature + hasn't changed. + 0: Always calibrate even if the temperature hasn't changed. + 1: Only calibrate if the temperature has changed (legacy - nRF51 only). + 2-33: Check the temperature and only calibrate if it has changed, + however calibration will take place every rc_temp_ctiv + intervals in any case. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. + + @note For nRF52, the application must ensure calibration at least once + every 8 seconds to ensure +/-500 ppm clock stability. The + recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is + rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at + least once every 8 seconds and for temperature changes of 0.5 + degrees Celsius every 4 seconds. See the Product Specification + for the nRF52 device being used for more information.*/ + uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing + windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ +} nrf_clock_lf_cfg_t; + +/**@brief Fault Handler type. + * + * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. + * The protocol stack will be in an undefined state when this happens and the only way to recover will be to + * perform a reset, using e.g. CMSIS NVIC_SystemReset(). + * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). + * + * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. + * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may + * continously transmit packets. + * + * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. + * + * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. + * @param[in] pc The program counter of the instruction that triggered the fault. + * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. + * + * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time + * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the + * one that triggered the fault. + */ +typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); + +/** @} */ + +/** @addtogroup NRF_SDM_FUNCTIONS Functions + * @{ */ + +/**@brief Enables the SoftDevice and by extension the protocol stack. + * + * @note Some care must be taken if a low frequency clock source is already running when calling this function: + * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new + * clock source will be started. + * + * @note This function has no effect when returning with an error. + * + * @post If return code is ::NRF_SUCCESS + * - SoC library and protocol stack APIs are made available. + * - A portion of RAM will be unavailable (see relevant SDS documentation). + * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). + * - Interrupts will not arrive from protected peripherals or interrupts. + * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. + * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). + * - Chosen low frequency clock source will be running. + * + * @param p_clock_lf_cfg Low frequency clock source and accuracy. + If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 + In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to + the actual characteristics of your XTAL clock. + * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. + * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has + an illegal priority level. + * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. + */ +SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, + sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); + +/**@brief Disables the SoftDevice and by extension the protocol stack. + * + * Idempotent function to disable the SoftDevice. + * + * @post SoC library and protocol stack APIs are made unavailable. + * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). + * @post All peripherals used by the SoftDevice will be reset to default values. + * @post All of RAM become available. + * @post All interrupts are forwarded to the application. + * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); + +/**@brief Check if the SoftDevice is enabled. + * + * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice + * + * This function is only intended to be called when a bootloader is enabled. + * + * @param[in] address The base address of the interrupt vector table for forwarded interrupts. + + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SDM_H__ + +/** + @} +*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_soc.h new file mode 100644 index 000000000..c649ca836 --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_soc.h @@ -0,0 +1,1046 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_soc_api SoC Library API + * @{ + * + * @brief APIs for the SoC library. + * + */ + +#ifndef NRF_SOC_H__ +#define NRF_SOC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_SOC_DEFINES Defines + * @{ */ + +/**@brief The number of the lowest SVC number reserved for the SoC library. */ +#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ +#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ + +/**@brief Guaranteed time for application to process radio inactive notification. */ +#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) + +/**@brief The minimum allowed timeslot extension time. */ +#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) + +/**@brief The maximum processing time to handle a timeslot extension. */ +#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) + +/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ +#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) + +#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ +#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ +#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ + +#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ +#define SD_EVT_IRQHandler \ + (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ + The default interrupt priority for this handler is set to 6 */ +#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ +#define RADIO_NOTIFICATION_IRQHandler \ + (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ + The default interrupt priority for this handler is set to 6 */ +#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ +#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ + +#define NRF_RADIO_DISTANCE_MAX_US \ + (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ + nrf_radio_request_normal_t) in the request. */ + +#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ + (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ + +#define NRF_RADIO_START_JITTER_US \ + (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ + ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ + (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) + +/**@} */ + +/**@addtogroup NRF_SOC_ENUMS Enumerations + * @{ */ + +/**@brief The SVC numbers used by the SVC functions in the SoC library. */ +enum NRF_SOC_SVCS { + SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, + SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, + SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, + SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, + SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, + SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, + SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, + SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, + SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, + SD_FLASH_WRITE = SOC_SVC_BASE + 9, + SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, + SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, + SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, + SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, + SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, + SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, + SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, + SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, + SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, + SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, + SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, + SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, + SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, + SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, + SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, + SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, + SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, + SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, + SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, + SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, + SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, + SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, + SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, + SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, + SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, + SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, + SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, + SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, + SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, + SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, + SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, + SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, + SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, + SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, + SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, + SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, + SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, + SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, + SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 +}; + +/**@brief Possible values of a ::nrf_mutex_t. */ +enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; + +/**@brief Power modes. */ +enum NRF_POWER_MODES { + NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ + NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ +}; + +/**@brief Power failure thresholds */ +enum NRF_POWER_THRESHOLDS { + NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ +}; + +/**@brief Power failure thresholds for high voltage */ +enum NRF_POWER_THRESHOLDVDDHS { + NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ +}; + +/**@brief DC/DC converter modes. */ +enum NRF_POWER_DCDC_MODES { + NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ + NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ +}; + +/**@brief Radio notification distances. */ +enum NRF_RADIO_NOTIFICATION_DISTANCES { + NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ + NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ +}; + +/**@brief Radio notification types. */ +enum NRF_RADIO_NOTIFICATION_TYPES { + NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and + disabled. */ +}; + +/**@brief The Radio signal callback types. */ +enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { + NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ +}; + +/**@brief The actions requested by the signal callback. + * + * This code gives the SOC instructions about what action to take when the signal callback has + * returned. + */ +enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { + NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current + timeslot. Maximum execution time for this action: + @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. + This action must be started at least + @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before + the end of the timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ +}; + +/**@brief Radio timeslot high frequency clock source configuration. */ +enum NRF_RADIO_HFCLK_CFG { + NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the + external crystal for the whole duration of the timeslot. This should be the + preferred option for events that use the radio or require high timing accuracy. + @note The SoftDevice will automatically turn on and off the external crystal, + at the beginning and end of the timeslot, respectively. The crystal may also + intentionally be left running after the timeslot, in cases where it is needed + by the SoftDevice shortly after the end of the timeslot. */ + NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. + The RC oscillator may be the clock source in part or for the whole duration of the + timeslot. The RC oscillator's accuracy must therefore be taken into consideration. + @note If the application will use the radio peripheral in timeslots with this + configuration, it must make sure that the crystal is running and stable before + starting the radio. */ +}; + +/**@brief Radio timeslot priorities. */ +enum NRF_RADIO_PRIORITY { + NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ + NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ +}; + +/**@brief Radio timeslot request type. */ +enum NRF_RADIO_REQUEST_TYPE { + NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first + request in a session. */ + NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ +}; + +/**@brief SoC Events. */ +enum NRF_SOC_EVTS { + NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ + NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ + NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ + NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ + NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ + NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ + NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was + invalid. */ + NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ + NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ + NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ + NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ + NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ + NRF_EVT_NUMBER_OF_EVTS +}; + +/**@} */ + +/**@addtogroup NRF_SOC_STRUCTURES Structures + * @{ */ + +/**@brief Represents a mutex for use with the nrf_mutex functions. + * @note Accessing the value directly is not safe, use the mutex functions! + */ +typedef volatile uint8_t nrf_mutex_t; + +/**@brief Parameters for a request for a timeslot as early as possible. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ + uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref + NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ +} nrf_radio_request_earliest_t; + +/**@brief Parameters for a normal radio timeslot request. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US + microseconds). */ + uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ +} nrf_radio_request_normal_t; + +/**@brief Radio timeslot request parameters. */ +typedef struct { + uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ + union { + nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ + nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ + } params; /**< Parameter union. */ +} nrf_radio_request_t; + +/**@brief Return parameters of the radio timeslot signal callback. */ +typedef struct { + uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref + NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ + union { + struct { + nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ + } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ + struct { + uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref + NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ + } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ + } params; /**< Parameter union. */ +} nrf_radio_signal_callback_return_param_t; + +/**@brief The radio timeslot signal callback type. + * + * @note In case of invalid return parameters, the radio timeslot will automatically end + * immediately after returning from the signal callback and the + * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. + * @note The returned struct pointer must remain valid after the signal callback + * function returns. For instance, this means that it must not point to a stack variable. + * + * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. + * + * @return Pointer to structure containing action requested by the application. + */ +typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); + +/**@brief AES ECB parameter typedefs */ +typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ +typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ +typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ + +/**@brief AES ECB data structure */ +typedef struct { + soc_ecb_key_t key; /**< Encryption key. */ + soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ + soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ +} nrf_ecb_hal_data_t; + +/**@brief AES ECB block. Used to provide multiple blocks in a single call + to @ref sd_ecb_blocks_encrypt.*/ +typedef struct { + soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ + soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ + soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ +} nrf_ecb_hal_data_block_t; + +/**@} */ + +/**@addtogroup NRF_SOC_FUNCTIONS Functions + * @{ */ + +/**@brief Initialize a mutex. + * + * @param[in] p_mutex Pointer to the mutex to initialize. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); + +/**@brief Attempt to acquire a mutex. + * + * @param[in] p_mutex Pointer to the mutex to acquire. + * + * @retval ::NRF_SUCCESS The mutex was successfully acquired. + * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. + */ +SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); + +/**@brief Release a mutex. + * + * @param[in] p_mutex Pointer to the mutex to release. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); + +/**@brief Query the capacity of the application random pool. + * + * @param[out] p_pool_capacity The capacity of the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); + +/**@brief Get number of random bytes available to the application. + * + * @param[out] p_bytes_available The number of bytes currently available in the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); + +/**@brief Get random bytes from the application pool. + * + * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. + * @param[in] length Number of bytes to take from pool and place in p_buff. + * + * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. + * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes + * available. + */ +SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); + +/**@brief Gets the reset reason register. + * + * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); + +/**@brief Clears the bits of the reset reason register. + * + * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); + +/**@brief Sets the power mode when in CPU sleep. + * + * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait + * + * @retval ::NRF_SUCCESS The power mode was set. + * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. + */ +SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); + +/**@brief Puts the chip in System OFF mode. + * + * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN + */ +SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); + +/**@brief Enables or disables the power-fail comparator. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); + +/**@brief Enables or disables the USB power ready event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); + +/**@brief Enables or disables the power USB-detected event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); + +/**@brief Enables or disables the power USB-removed event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); + +/**@brief Get USB supply status register content. + * + * @param[out] usbregstatus The content of USBREGSTATUS register. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); + +/**@brief Sets the power failure comparator threshold value. + * + * @note: Power failure comparator threshold setting. This setting applies both for normal voltage + * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to + * VDDH only). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); + +/**@brief Sets the power failure comparator threshold value for high voltage. + * + * @note: Power failure comparator threshold setting for high voltage mode (supply connected to + * VDDH only). This setting does not apply for normal voltage mode (supply connected to both + * VDD and VDDH). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. + * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. + * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); + +/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. + * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); + +/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be set in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[out] p_gpregret Contents of the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). + * + * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. + */ +SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). + * + * For more details on the REG0 stage, please see product specification. + * + * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. + */ +SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); + +/**@brief Request the high frequency crystal oscillator. + * + * Will start the high frequency crystal oscillator, the startup time of the crystal varies + * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_release + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); + +/**@brief Releases the high frequency crystal oscillator. + * + * Will stop the high frequency crystal oscillator, this happens immediately. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_request + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); + +/**@brief Checks if the high frequency crystal oscillator is running. + * + * @see sd_clock_hfclk_request + * @see sd_clock_hfclk_release + * + * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); + +/**@brief Waits for an application event. + * + * An application event is either an application interrupt or a pended interrupt when the interrupt + * is disabled. + * + * When the application waits for an application event by calling this function, an interrupt that + * is enabled will be taken immediately on pending since this function will wait in thread mode, + * then the execution will return in the application's main thread. + * + * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M + * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets + * pended, this function will return to the application's main thread. + * + * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ + * in order to sleep using this function. This is only necessary for disabled interrupts, as + * the interrupt handler will clear the pending flag automatically for enabled interrupts. + * + * @note If an application interrupt has happened since the last time sd_app_evt_wait was + * called this function will return immediately and not go to sleep. This is to avoid race + * conditions that can occur when a flag is updated in the interrupt handler and processed + * in the main loop. + * + * @post An application interrupt has happened or a interrupt pending flag is set. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); + +/**@brief Get PPI channel enable register contents. + * + * @param[out] p_channel_enable The contents of the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); + +/**@brief Set PPI channel enable register. + * + * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); + +/**@brief Clear PPI channel enable register. + * + * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); + +/**@brief Assign endpoints to a PPI channel. + * + * @param[in] channel_num Number of the PPI channel to assign. + * @param[in] evt_endpoint Event endpoint of the PPI channel. + * @param[in] task_endpoint Task endpoint of the PPI channel. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, + sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); + +/**@brief Task to enable a channel group. + * + * @param[in] group_num Number of the channel group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); + +/**@brief Task to disable a channel group. + * + * @param[in] group_num Number of the PPI group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); + +/**@brief Assign PPI channels to a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[in] channel_msk Mask of the channels to assign to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); + +/**@brief Gets the PPI channels of a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[out] p_channel_msk Mask of the channels assigned to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); + +/**@brief Configures the Radio Notification signal. + * + * @note + * - The notification signal latency depends on the interrupt priority settings of SWI used + * for notification signal. + * - To ensure that the radio notification signal behaves in a consistent way, the radio + * notifications must be configured when there is no protocol stack or other SoftDevice + * activity in progress. It is recommended that the radio notification signal is + * configured directly after the SoftDevice has been enabled. + * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice + * will interrupt the application to do Radio Event preparation. + * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have + * to shorten the connection events to have time for the Radio Notification signals. + * + * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio + * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is + * recommended (but not required) to be used with + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. + * + * @param[in] distance Distance between the notification signal and start of radio activity, see @ref + * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or + * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. + * + * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. + * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all + * running activities and retry. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); + +/**@brief Encrypts a block according to the specified parameters. + * + * 128-bit AES encryption. + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input + * parameters and one output parameter). + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); + +/**@brief Encrypts multiple data blocks provided as an array of data block structures. + * + * @details: Performs 128-bit AES encryption on multiple data blocks + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in] block_count Count of blocks in the p_data_blocks array. + * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of + * @ref nrf_ecb_hal_data_block_t structures. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); + +/**@brief Gets any pending events generated by the SoC API. + * + * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. + * + * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. + * + * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. + * @retval ::NRF_ERROR_NOT_FOUND No pending events. + */ +SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); + +/**@brief Get the temperature measured on the chip + * + * This function will block until the temperature measurement is done. + * It takes around 50 us from call to return. + * + * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. + * + * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp + */ +SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); + +/**@brief Flash Write + * + * Commands to write a buffer to flash + * + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * write has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS + * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. + * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is + * protected. + * + * + * @param[in] p_dst Pointer to start of flash location to be written. + * @param[in] p_src Pointer to buffer with data to be written. + * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one + * flash page. See the device's Product Specification for details. + * + * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. + * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); + +/**@brief Flash Erase page + * + * Commands to erase a flash page + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * erase has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is + * protected. + * + * + * @param[in] page_number Page number of the page to erase + * + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); + +/**@brief Opens a session for radio timeslot requests. + * + * @note Only one session can be open at a time. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot + * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed + * by the application. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 + * interrupt occurs. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO + * interrupt occurs. + * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This + * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). + * + * @param[in] p_radio_signal_callback The signal callback. + * + * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. + * @retval ::NRF_ERROR_BUSY If session cannot be opened. + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); + +/**@brief Closes a session for radio timeslot requests. + * + * @note Any current radio timeslot will be finished before the session is closed. + * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. + * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED + * event is received. + * + * @retval ::NRF_ERROR_FORBIDDEN If session not opened. + * @retval ::NRF_ERROR_BUSY If session is currently being closed. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); + +/**@brief Requests a radio timeslot. + * + * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST + * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref + * NRF_RADIO_REQ_TYPE_EARLIEST. + * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by + * p_request->distance_us and is given relative to the start of the previous timeslot. + * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. + * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this + * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. + * The application may then try to schedule the first radio timeslot again. + * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). + * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. + * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. + * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the + * specified radio timeslot start, but this does not affect the actual start time of the timeslot. + * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency + * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is + * guaranteed to be clocked from the external crystal. + * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral + * during the radio timeslot. + * + * @param[in] p_request Pointer to the request parameters. + * + * @retval ::NRF_ERROR_FORBIDDEN Either: + * - The session is not open. + * - The session is not IDLE. + * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. + * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a + * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. + * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); + +/**@brief Write register protected by the SoftDevice + * + * This function writes to a register that is write-protected by the SoftDevice. Please refer to your + * SoftDevice Specification for more details about which registers that are protected by SoftDevice. + * This function can write to the following protected peripheral: + * - ACL + * + * @note Protected registers may be read directly. + * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in + * the register has not changed. See the Product Specification for more details about register + * properties. + * + * @param[in] p_register Pointer to register to be written. + * @param[in] value Value to be written to the register. + * + * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. + * @retval ::NRF_SUCCESS Value successfully written to register. + * + */ +SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); + +/**@} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SOC_H__ + +/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_svc.h b/variants/wio-sdk-wm1110/softdevice/nrf_svc.h new file mode 100644 index 000000000..1de44656f --- /dev/null +++ b/variants/wio-sdk-wm1110/softdevice/nrf_svc.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NRF_SVC__ +#define NRF_SVC__ + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Supervisor call declaration. + * + * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. + * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. + * + * @param[in] number The SVC number to be used. + * @param[in] return_type The return type of the SVC function. + * @param[in] signature Function signature. The function can have at most four arguments. + */ + +#ifdef SVCALL_AS_NORMAL_FUNCTION +#define SVCALL(number, return_type, signature) return_type signature +#else + +#ifndef SVCALL +#if defined(__CC_ARM) +#define SVCALL(number, return_type, signature) return_type __svc(number) signature +#elif defined(__GNUC__) +#ifdef __cplusplus +#define GCC_CAST_CPP (uint16_t) +#else +#define GCC_CAST_CPP +#endif +#define SVCALL(number, return_type, signature) \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ + __attribute__((unused)) static return_type signature \ + { \ + __asm("svc %0\n" \ + "bx r14" \ + : \ + : "I"(GCC_CAST_CPP number) \ + : "r0"); \ + } \ + _Pragma("GCC diagnostic pop") + +#elif defined(__ICCARM__) +#define PRAGMA(x) _Pragma(#x) +#define SVCALL(number, return_type, signature) \ + PRAGMA(swi_number = (number)) \ + __swi return_type signature; +#else +#define SVCALL(number, return_type, signature) return_type signature +#endif +#endif // SVCALL + +#endif // SVCALL_AS_NORMAL_FUNCTION + +#ifdef __cplusplus +} +#endif +#endif // NRF_SVC__ diff --git a/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld b/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld new file mode 100644 index 000000000..6aaeb4034 --- /dev/null +++ b/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index 03d7d047a..9c04e36f1 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -5,10 +5,10 @@ board = wio-tracker-wm1110 build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +board_build.ldscript = variants/wio-tracker-wm1110/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink \ No newline at end of file +;upload_protocol = jlink diff --git a/variants/wio-tracker-wm1110/softdevice/ble.h b/variants/wio-tracker-wm1110/softdevice/ble.h new file mode 100644 index 000000000..177b436ad --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON BLE SoftDevice Common + @{ + @defgroup ble_api Events, type definitions and API calls + @{ + + @brief Module independent events, type definitions and API calls for the BLE SoftDevice. + + */ + +#ifndef BLE_H__ +#define BLE_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_l2cap.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief Common API SVC numbers. + */ +enum BLE_COMMON_SVCS { + SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ + SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ + SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ + SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ + SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ + SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ + SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ + SD_BLE_OPT_SET, /**< Set a BLE option. */ + SD_BLE_OPT_GET, /**< Get a BLE option. */ + SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ + SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ +}; + +/** + * @brief BLE Module Independent Event IDs. + */ +enum BLE_COMMON_EVTS { + BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t + \n Reply with @ref sd_ble_user_mem_reply. */ + BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ +}; + +/**@brief BLE Connection Configuration IDs. + * + * IDs that uniquely identify a connection configuration. + */ +enum BLE_CONN_CFGS { + BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ + BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ + BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ + BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ + BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ +}; + +/**@brief BLE Common Configuration IDs. + * + * IDs that uniquely identify a common configuration. + */ +enum BLE_COMMON_CFGS { + BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ +}; + +/**@brief Common Option IDs. + * IDs that uniquely identify a common option. + */ +enum BLE_COMMON_OPTS { + BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ + BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ + BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ +}; + +/** @} */ + +/** @addtogroup BLE_COMMON_DEFINES Defines + * @{ */ + +/** @brief Required pointer alignment for BLE Events. + */ +#define BLE_EVT_PTR_ALIGNMENT 4 + +/** @brief Leaves the maximum of the two arguments. + */ +#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +/** @brief Maximum possible length for BLE Events. + * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a + * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. + */ +#define BLE_EVT_LEN_MAX(ATT_MTU) \ + (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) + +/** @defgroup BLE_USER_MEM_TYPES User Memory Types + * @{ */ +#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ +#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ +/** @} */ + +/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts + * @{ + */ +#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ +#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ +/** @} */ + +/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. + * @{ + */ +#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ + +/** @} */ + +/** @} */ + +/** @addtogroup BLE_COMMON_STRUCTURES Structures + * @{ */ + +/**@brief User Memory Block. */ +typedef struct { + uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ + uint16_t len; /**< Length in bytes of the user memory block. */ +} ble_user_mem_block_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ +} ble_evt_user_mem_request_t; + +/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ +typedef struct { + uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ + ble_user_mem_block_t mem_block; /**< User memory block */ +} ble_evt_user_mem_release_t; + +/**@brief Event structure for events not associated with a specific function module. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ + union { + ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ + ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ + } params; /**< Event parameter union. */ +} ble_common_evt_t; + +/**@brief BLE Event header. */ +typedef struct { + uint16_t evt_id; /**< Value from a BLE__EVT series. */ + uint16_t evt_len; /**< Length in octets including this header. */ +} ble_evt_hdr_t; + +/**@brief Common BLE Event type, wrapping the module specific event reports. */ +typedef struct { + ble_evt_hdr_t header; /**< Event header. */ + union { + ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ + ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ + ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ + ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ + ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ + } evt; /**< Event union. */ +} ble_evt_t; + +/** + * @brief Version Information. + */ +typedef struct { + uint8_t version_number; /**< Link Layer Version number. See + https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ + uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) + (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ + uint16_t + subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ +} ble_version_t; + +/** + * @brief Configuration parameters for the PA and LNA. + */ +typedef struct { + uint8_t enable : 1; /**< Enable toggling for this amplifier */ + uint8_t active_high : 1; /**< Set the pin to be active high */ + uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ +} ble_pa_lna_cfg_t; + +/** + * @brief PA & LNA GPIO toggle configuration + * + * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or + * a low noise amplifier. + * + * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided + * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences + * and must be avoided by the application. + */ +typedef struct { + ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ + ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ + + uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ + uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ + uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ +} ble_common_opt_pa_lna_t; + +/** + * @brief Configuration of extended BLE connection events. + * + * When enabled the SoftDevice will dynamically extend the connection event when possible. + * + * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. + * The connection event can be extended if there is time to send another packet pair before the start of the next connection + * interval, and if there are no conflicts with other BLE roles requesting radio time. + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ +} ble_common_opt_conn_evt_ext_t; + +/** + * @brief Enable/disable extended RC calibration. + * + * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice + * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets + * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. + * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When + * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the + * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. + * + * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the + * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). + * + * @note @ref sd_ble_opt_get is not supported for this option. + */ +typedef struct { + uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ +} ble_common_opt_extended_rc_cal_t; + +/**@brief Option structure for common options. */ +typedef union { + ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ + ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ + ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ +} ble_common_opt_t; + +/**@brief Common BLE Option type, wrapping the module specific options. */ +typedef union { + ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ + ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ + ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ +} ble_opt_t; + +/**@brief BLE connection configuration type, wrapping the module specific configurations, set with + * @ref sd_ble_cfg_set. + * + * @note Connection configurations don't have to be set. + * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, + * the default connection configuration will be automatically added for the remaining connections. + * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in + * place of @ref ble_conn_cfg_t::conn_cfg_tag. + * + * @sa sd_ble_gap_adv_start() + * @sa sd_ble_gap_connect() + * + * @mscs + * @mmsc{@ref BLE_CONN_CFG} + * @endmscs + + */ +typedef struct { + uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the + @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls + to select this configuration when creating a connection. + Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ + union { + ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ + ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ + ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ + ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ + ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ + } params; /**< Connection configuration union. */ +} ble_conn_cfg_t; + +/** + * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. + */ +typedef struct { + uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. + Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is + @ref BLE_UUID_VS_COUNT_MAX. */ +} ble_common_cfg_vs_uuid_t; + +/**@brief Common BLE Configuration type, wrapping the common configurations. */ +typedef union { + ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ +} ble_common_cfg_t; + +/**@brief BLE Configuration type, wrapping the module specific configurations. */ +typedef union { + ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ + ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ + ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ + ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ +} ble_cfg_t; + +/** @} */ + +/** @addtogroup BLE_COMMON_FUNCTIONS Functions + * @{ */ + +/**@brief Enable the BLE stack + * + * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the + * application RAM region (APP_RAM_BASE). On return, this will + * contain the minimum start address of the application RAM region + * required by the SoftDevice for this configuration. + * @warning After this call, the SoftDevice may generate several events. The list of events provided + * below require the application to initiate a SoftDevice API call. The corresponding API call + * is referenced in the event documentation. + * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop + * communicating with the peer device. + * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST + * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST + * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST + * - @ref BLE_GAP_EVT_SEC_REQUEST + * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST + * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST + * - @ref BLE_EVT_USER_MEM_REQUEST + * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located + * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between + * APP_RAM_BASE and the start of the call stack. + * + * @details This call initializes the BLE stack, no BLE related function other than @ref + * sd_ble_cfg_set can be called before this one. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: + * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not + * large enough to fit this configuration's memory requirement. Check *p_app_ram_base + * and set the start address of the application RAM region accordingly. + * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which + * is currently not supported. + * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. + */ +SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); + +/**@brief Add configurations for the BLE stack + * + * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref + * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. + * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. + * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). + * See @ref sd_ble_enable for details about APP_RAM_BASE. + * + * @note The memory requirement for a specific configuration will not increase between SoftDevices + * with the same major version number. + * + * @note If a configuration is set more than once, the last one set is the one that takes effect on + * @ref sd_ble_enable. + * + * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default + * configuration. + * + * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref + * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref + * sd_ble_enable). + * + * @note Error codes for the configurations are described in the configuration structs. + * + * @mscs + * @mmsc{@ref BLE_COMMON_ENABLE} + * @endmscs + * + * @retval ::NRF_SUCCESS The configuration has been added successfully. + * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. + * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not + * large enough to fit this configuration's memory requirement. + */ +SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); + +/**@brief Get an event from the pending events queue. + * + * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. + * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. + * The buffer should be interpreted as a @ref ble_evt_t struct. + * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. + * + * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that + * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. + * The application is free to choose whether to call this function from thread mode (main context) or directly from the + * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher + * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) + * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so + * could potentially leave events in the internal queue without the application being aware of this fact. + * + * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to + * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, + * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. + * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length + * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: + * + * \code + * uint16_t len; + * errcode = sd_ble_evt_get(NULL, &len); + * \endcode + * + * @mscs + * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} + * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. + * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. + */ +SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); + +/**@brief Add a Vendor Specific base UUID. + * + * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later + * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t + * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code + * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses + * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to + * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field + * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to + * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, + * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. + * + * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by + * the 16-bit uuid field in @ref ble_uuid_t. + * + * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in + * p_uuid_type along with an @ref NRF_SUCCESS error code. + * + * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding + * bytes 12 and 13. + * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be + * stored. + * + * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. + * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. + */ +SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); + +/**@brief Remove a Vendor Specific base UUID. + * + * @details This call removes a Vendor Specific base UUID. This function allows + * the application to reuse memory allocated for Vendor Specific base UUIDs. + * + * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID + * type. + * + * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. + * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific + * base UUID will be removed. If the function returns successfully, the UUID type that was removed will + * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that + * is in use by the ATT Server. + * + * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. + * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. + * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. + */ +SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); + +/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. + * + * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared + * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs + * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index + * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. + * + * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. + * + * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). + * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. + * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. + * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. + */ +SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); + +/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). + * + * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is + * computed. + * + * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. + * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). + * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. + * + * @retval ::NRF_SUCCESS Successfully encoded into the buffer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. + */ +SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); + +/**@brief Get Version Information. + * + * @details This call allows the application to get the BLE stack version information. + * + * @param[out] p_version Pointer to a ble_version_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Version information stored successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). + */ +SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); + +/**@brief Provide a user memory block. + * + * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. + */ +SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); + +/**@brief Set a BLE option. + * + * @details This call allows the application to set the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. + * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. + * + * @retval ::NRF_SUCCESS Option set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + */ +SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); + +/**@brief Get a BLE option. + * + * @details This call allows the application to retrieve the value of an option. + * + * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. + * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Option retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. + * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. + * + */ +SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); + +/** @} */ +#ifdef __cplusplus +} +#endif +#endif /* BLE_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_err.h b/variants/wio-tracker-wm1110/softdevice/ble_err.h new file mode 100644 index 000000000..d20f6d141 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_err.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @addtogroup nrf_error + @{ + @ingroup BLE_COMMON + @} + + @defgroup ble_err General error codes + @{ + + @brief General error code definitions for the BLE API. + + @ingroup BLE_COMMON +*/ +#ifndef NRF_BLE_ERR_H__ +#define NRF_BLE_ERR_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* @defgroup BLE_ERRORS Error Codes + * @{ */ +#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ +#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ +#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ +#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ +#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ +#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ + (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ +/** @} */ + +/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges + * @brief Assignment of subranges for module specific error codes. + * @note For specific error codes, see ble_.h or ble_error_.h. + * @{ */ +#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ +#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ +#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ +#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gap.h b/variants/wio-tracker-wm1110/softdevice/ble_gap.h new file mode 100644 index 000000000..8ebdfa82b --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gap.h @@ -0,0 +1,2895 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GAP Generic Access Profile (GAP) + @{ + @brief Definitions and prototypes for the GAP interface. + */ + +#ifndef BLE_GAP_H__ +#define BLE_GAP_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GAP API SVC numbers. + */ +enum BLE_GAP_SVCS { + SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ + SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ + SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ + SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ + SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ + SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ + SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ + SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ + SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ + SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ + SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ + SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ + SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ + SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ + SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ + SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ + SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ + SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ + SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ + SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ + SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ + SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ + SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ + SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ + SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ + SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ + SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ + SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ + SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ + SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ + SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ + SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ + SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ + SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ + SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ + SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ + SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ + SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ + SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ + SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = + BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ +}; + +/**@brief GAP Event IDs. + * IDs that uniquely identify an event coming from the stack to the application. + */ +enum BLE_GAP_EVTS { + BLE_GAP_EVT_CONNECTED = + BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ + BLE_GAP_EVT_DISCONNECTED = + BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE = + BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ + BLE_GAP_EVT_SEC_PARAMS_REQUEST = + BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. + \n See @ref ble_gap_evt_sec_params_request_t. */ + BLE_GAP_EVT_SEC_INFO_REQUEST = + BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. + \n See @ref ble_gap_evt_sec_info_request_t. */ + BLE_GAP_EVT_PASSKEY_DISPLAY = + BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref + sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ + BLE_GAP_EVT_KEY_PRESSED = + BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ + BLE_GAP_EVT_AUTH_KEY_REQUEST = + BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. + \n See @ref ble_gap_evt_auth_key_request_t. */ + BLE_GAP_EVT_LESC_DHKEY_REQUEST = + BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref + sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ + BLE_GAP_EVT_AUTH_STATUS = + BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ + BLE_GAP_EVT_CONN_SEC_UPDATE = + BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ + BLE_GAP_EVT_TIMEOUT = + BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ + BLE_GAP_EVT_RSSI_CHANGED = + BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ + BLE_GAP_EVT_ADV_REPORT = + BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ + BLE_GAP_EVT_SEC_REQUEST = + BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate +\n or with @ref sd_ble_gap_encrypt if required security information is available +. \n See @ref ble_gap_evt_sec_request_t. */ + BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref + sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ + BLE_GAP_EVT_SCAN_REQ_REPORT = + BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ + BLE_GAP_EVT_PHY_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n + See @ref ble_gap_evt_phy_update_request_t. */ + BLE_GAP_EVT_PHY_UPDATE = + BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = + BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref + sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ + BLE_GAP_EVT_DATA_LENGTH_UPDATE = + BLE_GAP_EVT_BASE + + 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ + BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = + BLE_GAP_EVT_BASE + + 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ + BLE_GAP_EVT_ADV_SET_TERMINATED = + BLE_GAP_EVT_BASE + + 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ +}; + +/**@brief GAP Option IDs. + * IDs that uniquely identify a GAP option. + */ +enum BLE_GAP_OPTS { + BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ + BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ + BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ + BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ + BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = + BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ + BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = + BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ +}; + +/**@brief GAP Configuration IDs. + * + * IDs that uniquely identify a GAP configuration. + */ +enum BLE_GAP_CFGS { + BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ + BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ + BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic + inclusion configuration. */ + BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic + inclusion configuration. */ +}; + +/**@brief GAP TX Power roles. + */ +enum BLE_GAP_TX_POWER_ROLES { + BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ + BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ + BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ +}; + +/** @} */ + +/**@addtogroup BLE_GAP_DEFINES Defines + * @{ */ + +/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP + * @{ */ +#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ + (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ +#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ + (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ +#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ + (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ +#define BLE_ERROR_GAP_WHITELIST_IN_USE \ + (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ + (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ +#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ + (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLES GAP Roles + * @{ */ +#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ +#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ +#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ +/**@} */ + +/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources + * @{ */ +#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ +#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ +#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ +/**@} */ + +/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types + * @{ */ +#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ +#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ +#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ +#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ + 0x7F /**< An advertiser may advertise without its address. \ + This type of advertising is called anonymous. */ +/**@} */ + +/**@brief The default interval in seconds at which a private address is refreshed. */ +#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ +/**@brief The maximum interval in seconds at which a private address can be refreshed. */ +#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ + +/** @brief BLE address length. */ +#define BLE_GAP_ADDR_LEN (6) + +/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes + * @{ */ +#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ +#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ +#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ + 0x02 /**< Device will send and accept only private addresses for its own address, \ + and will not accept a peer using identity address as sender address when \ + the peer IRK is exchanged, non-zero and added to the identity list. */ +/**@} */ + +/** @brief Invalid power level. */ +#define BLE_GAP_POWER_LEVEL_INVALID 127 + +/** @brief Advertising set handle not set. */ +#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) + +/** @brief The default number of advertising sets. */ +#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) + +/** @brief The maximum number of advertising sets supported by this SoftDevice. */ +#define BLE_GAP_ADV_SET_COUNT_MAX (1) + +/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. + * @{ */ +#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ + (31) /**< Maximum data length for an advertising set. \ + If more advertising data is required, use extended advertising instead. */ +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for an extended advertising set. */ + +#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ + (238) /**< Maximum supported data length for an extended connectable advertising set. */ +/**@}. */ + +/** @brief Set ID not available in advertising report. */ +#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF + +/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons + * @{ */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ +#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ +/**@} */ + +/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format + * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm + * @{ */ +#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ +#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ +#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ +#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ +#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ +#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ +#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ +#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ +#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ +#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ +#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ +#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ +#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ +#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ +#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ +#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ +#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ +#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ +#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ +#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags + * @{ */ +#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ +#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ +#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ +#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ + (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ + BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min + * @{ */ +#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ +#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ + /**@} */ + +/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min + * @{ */ +#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min + * @{ */ +#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ +#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min + * @{ */ +#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ +#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ + /** @} */ + +/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size + * + * Scan buffers are used for storing advertising data received from an advertiser. + * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. + * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. + * @{ */ +#define BLE_GAP_SCAN_BUFFER_MIN \ + (31) /**< Minimum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_MAX \ + (31) /**< Maximum data length for an \ + advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ + (255) /**< Minimum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ + (1650) /**< Maximum data length for an \ + extended advertising set. */ +#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ + (255) /**< Maximum supported data length for \ + an extended advertising set. */ +/** @} */ + +/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types + * + * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. + * + * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. + * The maximum supported data length for an extended advertiser is defined by + * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED + * Note that some of the advertising types do not support advertising data. Non-scannable types do not support + * scan response data. + * + * @{ */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x01 /**< Connectable and scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ + 0x02 /**< Connectable non-scannable directed advertising \ + events. Advertising interval is less that 3.75 ms. \ + Use this type for fast reconnections. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x03 /**< Connectable non-scannable directed advertising \ + events. \ + @note Advertising data is not supported. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x04 /**< Non-connectable scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x05 /**< Non-connectable non-scannable undirected \ + advertising events. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x06 /**< Connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x07 /**< Connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ + 0x08 /**< Non-connectable scannable undirected advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ + 0x09 /**< Non-connectable scannable directed advertising \ + events using extended advertising PDUs. \ + @note Only scan response data is supported. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ + 0x0A /**< Non-connectable non-scannable undirected advertising \ + events using extended advertising PDUs. */ +#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ + 0x0B /**< Non-connectable non-scannable directed advertising \ + events using extended advertising PDUs. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies + * @{ */ +#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ +#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ +#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status + * @{ */ +#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ + 0x01 /**< More data to be received. \ + @note This value will only be used if \ + @ref ble_gap_scan_params_t::report_incomplete_evts and \ + @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ + 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ + 0x03 /**< Failed to receive the remaining data. \ + @note This value will only be used if \ + @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ +/**@} */ + +/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies + * @{ */ +#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ + 0x00 /**< Accept all advertising packets except directed advertising packets \ + not addressed to this device. */ +#define BLE_GAP_SCAN_FP_WHITELIST \ + 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ + packets not addressed to this device. */ +#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ + 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ + 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ + In addition, accept directed advertising packets, where the advertiser's \ + address is a resolvable private address that cannot be resolved. */ +/**@} */ + +/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units + * @{ */ +#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ + (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ + */ +#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ + (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ + mode. */ +#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ + (0) /**< Unlimited advertising in general discoverable mode. \ + For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ +/**@} */ + +/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes + * @{ */ +#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ +#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ +#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ +/**@} */ + +/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities + * @{ */ +#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ +#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ +#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ +#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ +/**@} */ + +/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types + * @{ */ +#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ +#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ +#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ +/**@} */ + +/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types + * @{ */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ +#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS GAP Security status + * @{ */ +#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ +#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ +#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ +#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ +#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ +#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ +#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ +#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ +#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ +#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ +#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ +#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ +#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ +#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ +#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ +#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ +#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ +#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ +/**@} */ + +/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources + * @{ */ +#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ +#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ +/**@} */ + +/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits + * @{ */ +#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ + 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ + 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ + 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ +#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ + 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ + */ +#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ +#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ +/**@} */ + +/**@defgroup BLE_GAP_DEVNAME GAP device name defines. + * @{ */ +#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ +#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ +#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ +/**@} */ + +/**@brief Disable RSSI events for connections */ +#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF + +/**@defgroup BLE_GAP_PHYS GAP PHYs + * @{ */ +#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ +#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ +#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ +#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ +#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ + +/**@brief Supported PHYs in connections, for scanning, and for advertising. */ +#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ + +/**@} */ + +/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters + * + * See @ref ble_gap_conn_sec_mode_t. + * @{ */ +/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ + do { \ + (ptr)->sm = 0; \ + (ptr)->lv = 0; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 2; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 3; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 1; \ + (ptr)->lv = 4; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 1; \ + } while (0) +/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ +#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ + do { \ + (ptr)->sm = 2; \ + (ptr)->lv = 2; \ + } while (0) +/**@} */ + +/**@brief GAP Security Random Number Length. */ +#define BLE_GAP_SEC_RAND_LEN 8 + +/**@brief GAP Security Key Length. */ +#define BLE_GAP_SEC_KEY_LEN 16 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ +#define BLE_GAP_LESC_P256_PK_LEN 64 + +/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ +#define BLE_GAP_LESC_DHKEY_LEN 32 + +/**@brief GAP Passkey Length. */ +#define BLE_GAP_PASSKEY_LEN 6 + +/**@brief Maximum amount of addresses in the whitelist. */ +#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) + +/**@brief Maximum amount of identities in the device identities list. */ +#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) + +/**@brief Default connection count for a configuration. */ +#define BLE_GAP_CONN_COUNT_DEFAULT (1) + +/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. + * @{ */ +#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ +#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ +#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ +/**@} */ + +/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. + * @{ */ +#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ + (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ +#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ + (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ + +/**@} */ + +/**@brief Automatic data length parameter. */ +#define BLE_GAP_DATA_LENGTH_AUTO 0 + +/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. + * @{ */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ +#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ +/**@} */ + +/**@defgroup GAP_SEC_MODES GAP Security Modes + * @{ */ +#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ +/**@} */ + +/**@brief The total number of channels in Bluetooth Low Energy. */ +#define BLE_GAP_CHANNEL_COUNT (40) + +/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines + * @{ */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ +#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ + /**@} */ + +/** @} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations + * @{ + */ +#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ + (1) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will reserve the attribute handles \ + which are otherwise used for this characteristic. \ + By reserving the attribute handles it will be possible \ + to upgrade the SoftDevice without changing handle of the \ + Service Changed characteristic. */ +#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ + (2) /**< Do not include the characteristic in the Attribute table. \ + The SoftDevice will not reserve the attribute handles \ + which are otherwise used for this characteristic. */ +/**@} */ + +/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values + * @{ */ +#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ +/**@} */ + +/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options + * @{ */ +#define BLE_GAP_SLAVE_LATENCY_ENABLE \ + (0) /**< Slave latency is enabled. When slave latency is enabled, \ + the slave will wake up every time it has data to send, \ + and/or every slave latency number of connection events. */ +#define BLE_GAP_SLAVE_LATENCY_DISABLE \ + (1) /**< Disable slave latency. The slave will wake up every connection event \ + regardless of the requested slave latency. \ + This option consumes the most power. */ +#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ + (2) /**< The slave will wake up every connection event if it has not received \ + an ACK from the master for at least slave latency events. This \ + configuration may increase the power consumption in environments \ + with a lot of radio activity. */ +/**@} */ + +/**@addtogroup BLE_GAP_STRUCTURES Structures + * @{ */ + +/**@brief Advertising event properties. */ +typedef struct { + uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ + uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. + @note Anonymous advertising is only available for + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and + @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ + uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ +} ble_gap_adv_properties_t; + +/**@brief Advertising report type. */ +typedef struct { + uint16_t connectable : 1; /**< Connectable advertising event type. */ + uint16_t scannable : 1; /**< Scannable advertising event type. */ + uint16_t directed : 1; /**< Directed advertising event type. */ + uint16_t scan_response : 1; /**< Received a scan response. */ + uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ + uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ + uint16_t reserved : 9; /**< Reserved for future use. */ +} ble_gap_adv_report_type_t; + +/**@brief Advertising Auxiliary Pointer. */ +typedef struct { + uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ + uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ +} ble_gap_aux_pointer_t; + +/**@brief Bluetooth Low Energy address. */ +typedef struct { + uint8_t + addr_id_peer : 1; /**< Only valid for peer addresses. + This bit is set by the SoftDevice to indicate whether the address has been resolved from + a Resolvable Private Address (when the peer is using privacy). + If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. + + This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. + */ + uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ + uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. + @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ +} ble_gap_addr_t; + +/**@brief GAP connection parameters. + * + * @note When ble_conn_params_t is received in an event, both min_conn_interval and + * max_conn_interval will be equal to the connection interval set by the central. + * + * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: + * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval + * that corresponds to the following Bluetooth Spec requirement: + * The Supervision_Timeout in milliseconds shall be larger than + * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. + */ +typedef struct { + uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ + uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ +} ble_gap_conn_params_t; + +/**@brief GAP connection security modes. + * + * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n + * Security Mode 1 Level 1: No security is needed (aka open link).\n + * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n + * Security Mode 1 Level 3: MITM protected encrypted link required.\n + * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n + * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n + * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n + */ +typedef struct { + uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ + uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ + +} ble_gap_conn_sec_mode_t; + +/**@brief GAP connection security status.*/ +typedef struct { + ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ + uint8_t + encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ +} ble_gap_conn_sec_t; + +/**@brief Identity Resolving Key. */ +typedef struct { + uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ +} ble_gap_irk_t; + +/**@brief Channel mask (40 bits). + * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, + * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents + * channel index 39. If a bit is set to 1, the channel is not used. + */ +typedef uint8_t ble_gap_ch_mask_t[5]; + +/**@brief GAP advertising parameters. */ +typedef struct { + ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ + ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. + @note ble_gap_addr_t::addr_type cannot be + @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. + - When privacy is enabled and the local device uses + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, + the device identity list is searched for a matching entry. If + the local IRK for that device identity is set, the local IRK + for that device will be used to generate the advertiser address + field in the advertising packet. + - If @ref ble_gap_adv_properties_t::type is directed, this must be + set to the targeted scanner or initiator. If the peer address is + in the device identity list, the peer IRK for that device will be + used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE + target addresses used in the advertising event PDUs. */ + uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE + advertising, this parameter is ignored. */ + uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, + an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + @sa BLE_GAP_ADV_TIMEOUT_VALUES. + @note The SoftDevice will always complete at least one advertising + event even if the duration is set too low. */ + uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling + advertising. Setting the value to 0 disables the limitation. When + the count of advertising events specified by this parameter + (if not 0) is reached, advertising will be automatically stopped + and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised + @note If @ref ble_gap_adv_properties_t::type is set to + @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, + this parameter is ignored. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be used. + Masking away secondary advertising channels is not supported. */ + uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets + are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS + will be used. + Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. + @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets + are transmitted. + If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. + Valid values are + @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. + If @ref ble_gap_adv_properties_t::type is an extended advertising type + and connectable, this is the PHY that will be used to establish a + connection and send AUX_ADV_IND packets on. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other + advertising sets transmitted by this and other devices. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ + uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a + scan request is received and the scanner address is allowed + by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. + @note This parameter will be ignored when + @ref ble_gap_adv_properties_t::type is a non-scannable + advertising type. */ +} ble_gap_adv_params_t; + +/**@brief GAP advertising data buffers. + * + * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and + * shall never be modified while advertising. The data shall be kept alive until either: + * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. + * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding + * advertising handle. + * - Advertising is stopped. + * - Advertising data is changed. + * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ +typedef struct { + ble_data_t adv_data; /**< Advertising data. + @note + Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type + that is allowed to contain advertising data. */ + ble_data_t scan_rsp_data; /**< Scan response data. + @note + Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type + that is scannable. */ +} ble_gap_adv_data_t; + +/**@brief GAP scanning parameters. */ +typedef struct { + uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. + If set to 0, the scanner will not receive advertising packets + on secondary advertising channels, and will not be able + to receive long advertising PDUs. */ + uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have + @ref ble_gap_adv_report_type_t::status set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. + This parameter is ignored when used with @ref sd_ble_gap_connect + @note This may be used to abort receiving more packets from an extended + advertising event, and is only available for extended + scanning, see @ref sd_ble_gap_scan_start. + @note This feature is not supported by this SoftDevice. */ + uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. + This parameter is ignored when used with @ref sd_ble_gap_connect. */ + uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. + @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and + @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with + @ref sd_ble_gap_connect */ + uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, + scan_phys will default to @ref BLE_GAP_PHY_1MBPS. + - If @ref ble_gap_scan_params_t::extended is set to 0, the only + supported PHY is @ref BLE_GAP_PHY_1MBPS. + - When used with @ref sd_ble_gap_scan_start, + the bitfield indicates the PHYs the scanner will use for scanning + on primary advertising channels. The scanner will accept + @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. + - When used with @ref sd_ble_gap_connect, the bitfield indicates + the PHYs the initiator will use for scanning on primary advertising + channels. The initiator will accept connections initiated on either + of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. + If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, + the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. + If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan + PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is + @ref BLE_GAP_PHY_CODED, the primary scan PHY is + @ref BLE_GAP_PHY_CODED only. */ + uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ + uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. + If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and + @ref BLE_GAP_PHY_CODED interval shall be larger than or + equal to twice the scan window. */ + uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ + ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. + At least one of the primary channels, that is channel index 37-39, must be + set to 0. + Masking away secondary channels is not supported. */ +} ble_gap_scan_params_t; + +/**@brief Privacy. + * + * The privacy feature provides a way for the device to avoid being tracked over a period of time. + * The privacy feature, when enabled, hides the local device identity and replaces it with a private address + * that is automatically refreshed at a specified interval. + * + * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). + * With this key, a device can generate a random private address that can only be recognized by peers in possession of that + * key, and devices can establish connections without revealing their real identities. + * + * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref + * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. + * + * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all + * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. + * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is + * called. + */ +typedef struct { + uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ + uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or + @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ + uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use + the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ + ble_gap_irk_t + *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device + default IRK will be used. When used as output, pointer to IRK structure where the current default IRK + will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate + random private resolvable addresses for the local device unless instructed otherwise. */ +} ble_gap_privacy_params_t; + +/**@brief PHY preferences for TX and RX + * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each + * direction. + * @code + * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; + * @endcode + * + */ +typedef struct { + uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ +} ble_gap_phys_t; + +/** @brief Keys that can be exchanged during a bonding procedure. */ +typedef struct { + uint8_t enc : 1; /**< Long Term Key and Master Identification. */ + uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ + uint8_t sign : 1; /**< Connection Signature Resolving Key. */ + uint8_t link : 1; /**< Derive the Link Key from the LTK. */ +} ble_gap_sec_kdist_t; + +/**@brief GAP security parameters. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ + uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ + uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ + uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ + uint8_t oob : 1; /**< The OOB data flag. + - In LE legacy pairing, this flag is set if a device has out of band authentication data. + The OOB method is used if both of the devices have out of band authentication data. + - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band + authentication data. The OOB method is used if at least one device has the peer device's OOB data + available. */ + uint8_t + min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ + uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ + ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ + ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ +} ble_gap_sec_params_t; + +/**@brief GAP Encryption Information. */ +typedef struct { + uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ + uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ + uint8_t auth : 1; /**< Authenticated Key. */ + uint8_t ltk_len : 6; /**< LTK length in octets. */ +} ble_gap_enc_info_t; + +/**@brief GAP Master Identification. */ +typedef struct { + uint16_t ediv; /**< Encrypted Diversifier. */ + uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ +} ble_gap_master_id_t; + +/**@brief GAP Signing Information. */ +typedef struct { + uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ +} ble_gap_sign_info_t; + +/**@brief GAP LE Secure Connections P-256 Public Key. */ +typedef struct { + uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the + standard SMP protocol format: {X,Y} both in little-endian. */ +} ble_gap_lesc_p256_pk_t; + +/**@brief GAP LE Secure Connections DHKey. */ +typedef struct { + uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ +} ble_gap_lesc_dhkey_t; + +/**@brief GAP LE Secure Connections OOB data. */ +typedef struct { + ble_gap_addr_t addr; /**< Bluetooth address of the device. */ + uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ + uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ +} ble_gap_lesc_oob_data_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ +typedef struct { + ble_gap_addr_t + peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ + uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. + This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ +} ble_gap_evt_connected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ +typedef struct { + uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ +} ble_gap_evt_disconnected_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ +typedef struct { + ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ +} ble_gap_evt_phy_update_request_t; + +/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ +typedef struct { + uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ + uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ + uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ +} ble_gap_evt_phy_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ +typedef struct { + ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ +} ble_gap_evt_sec_params_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ +typedef struct { + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ + ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ + uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ + uint8_t id_info : 1; /**< If 1, Identity Information required. */ + uint8_t sign_info : 1; /**< If 1, Signing Information required. */ +} ble_gap_evt_sec_info_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ +typedef struct { + uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ + uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply + with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or + @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ +} ble_gap_evt_passkey_display_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ +typedef struct { + uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ +} ble_gap_evt_key_pressed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ +typedef struct { + uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ +} ble_gap_evt_auth_key_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ +typedef struct { + ble_gap_lesc_p256_pk_t + *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory + inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ + uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the + procedure. */ +} ble_gap_evt_lesc_dhkey_request_t; + +/**@brief Security levels supported. + * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. + */ +typedef struct { + uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ + uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ + uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ + uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ +} ble_gap_sec_levels_t; + +/**@brief Encryption Key. */ +typedef struct { + ble_gap_enc_info_t enc_info; /**< Encryption Information. */ + ble_gap_master_id_t master_id; /**< Master Identification. */ +} ble_gap_enc_key_t; + +/**@brief Identity Key. */ +typedef struct { + ble_gap_irk_t id_info; /**< Identity Resolving Key. */ + ble_gap_addr_t id_addr_info; /**< Identity Address. */ +} ble_gap_id_key_t; + +/**@brief Security Keys. */ +typedef struct { + ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ + ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ + ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ + ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the + value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ +} ble_gap_sec_keys_t; + +/**@brief Security key set for both local and peer keys. */ +typedef struct { + ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be + generated locally and will always be stored if bonding. */ + ble_gap_sec_keys_t + keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ +} ble_gap_sec_keyset_t; + +/**@brief Data Length Update Procedure parameters. */ +typedef struct { + uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ + uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link + Layer Data Channel PDU. */ + uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer + Data Channel PDU. */ +} ble_gap_data_length_params_t; + +/**@brief Data Length Update Procedure local limitation. */ +typedef struct { + uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ + uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ + uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many + microseconds. */ +} ble_gap_data_length_limitation_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ +typedef struct { + uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ + uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ + uint8_t bonded : 1; /**< Procedure resulted in a bond. */ + uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ + ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ + ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ + ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding + with LE Secure Connections, the enc bit will be always set. */ + ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding + with LE Secure Connections, the enc bit will never be set. */ +} ble_gap_evt_auth_status_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ +typedef struct { + ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ +} ble_gap_evt_conn_sec_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ + union { + ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released + scan buffer is contained in this field. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ +typedef struct { + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ +} ble_gap_evt_rssi_changed_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ +typedef struct { + uint8_t reason; /**< Reason for why the advertising set terminated. See + @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ + uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ + uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, + this field indicates the number of completed advertising events. */ + ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated + advertising set. The advertising buffers provided in + @ref sd_ble_gap_adv_set_configure are now released. */ +} ble_gap_evt_adv_set_terminated_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. + * + * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * not all fields in the advertising report may be available. + * + * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, + * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. + */ +typedef struct { + ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the + peer's identity address. */ + ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if + @ref ble_gap_adv_report_type_t::directed is set to 1. If the + SoftDevice was able to resolve the address, + @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr + contains the local identity address. If the target address of the + advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + and the SoftDevice was unable to resolve it, the application may try + to resolve this address to find out if the advertising event was + directed to us. */ + uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. + See @ref BLE_GAP_PHYS. */ + uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. + See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets + were received on a secondary advertising channel. */ + int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. + This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the + last received packet did not contain the Tx Power field. + @note TX Power is only included in extended advertising packets. */ + int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ + uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present + if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID + is not present if @ref ble_gap_evt_adv_report_t::set_id is set to + @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ + ble_data_t data; /**< Received advertising or scan response data. If + @ref ble_gap_adv_report_type_t::status is not set to + @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided + in @ref sd_ble_gap_scan_start is now released. */ + ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising + event. @note This field is only set if @ref ble_gap_adv_report_type_t::status + is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ +} ble_gap_evt_adv_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ +typedef struct { + uint8_t bond : 1; /**< Perform bonding. */ + uint8_t mitm : 1; /**< Man In The Middle protection requested. */ + uint8_t lesc : 1; /**< LE Secure Connections requested. */ + uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ +} ble_gap_evt_sec_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ +typedef struct { + ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ +} ble_gap_evt_conn_param_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ +typedef struct { + uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ + int8_t rssi; /**< Received Signal Strength Indication in dBm. + @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature + measurement. */ + ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref + ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ +} ble_gap_evt_scan_req_report_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ +typedef struct { + ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ +} ble_gap_evt_data_length_update_request_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. + * + * @note This event may also be raised after a PHY Update procedure. + */ +typedef struct { + ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ +} ble_gap_evt_data_length_update_t; + +/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ +typedef struct { + int8_t + channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy + channels, in dBm, indexed by Channel Index. + If no measurement is available for the given channel, channel_energy is set to + @ref BLE_GAP_POWER_LEVEL_INVALID. */ +} ble_gap_evt_qos_channel_survey_report_t; + +/**@brief GAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + union /**< union alternative identified by evt_id in enclosing struct. */ + { + ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ + ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ + ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ + ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ + ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ + ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ + ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ + ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ + ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ + ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ + ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ + ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ + ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ + ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ + ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ + ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ + ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ + ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ + ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ + ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ + ble_gap_evt_qos_channel_survey_report_t + qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ + } params; /**< Event Parameters. */ +} ble_gap_evt_t; + +/** + * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. + * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. + */ +typedef struct { + uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. + The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ + uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. + The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref + BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters + for setting the throughput of a connection. + See the SoftDevice Specification for details on throughput. */ +} ble_gap_conn_cfg_t; + +/** + * @brief Configuration of maximum concurrent connections in the different connected roles, set with + * @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too + * large. The maximum supported sum of concurrent connections is + * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. + * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. + * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum + * supported advertising handles is + * @ref BLE_GAP_ADV_SET_COUNT_MAX. + */ +typedef struct { + uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ + uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref + BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ + uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref + BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ + uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is + @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ + uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to + the application using @ref sd_ble_gap_qos_channel_survey_start. */ +} ble_gap_cfg_role_count_t; + +/** + * @brief Device name and its properties, set with @ref sd_ble_cfg_set. + * + * @note If the device name is not configured, the default device name will be + * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be + * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name + * will have no write access. + * + * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, + * the attribute table size must be increased to have room for the longer device name (see + * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). + * + * @note If vloc is @ref BLE_GATTS_VLOC_STACK : + * - p_value must point to non-volatile memory (flash) or be NULL. + * - If p_value is NULL, the device name will initially be empty. + * + * @note If vloc is @ref BLE_GATTS_VLOC_USER : + * - p_value cannot be NULL. + * - If the device name is writable, p_value must point to volatile memory (RAM). + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Invalid device name location (vloc). + * - Invalid device name security mode. + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). + * - The device name length is too long for the given Attribute Table. + * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. + */ +typedef struct { + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ + uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ + uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ +} ble_gap_cfg_device_name_t; + +/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_ppcp_incl_cfg_t; + +/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. + See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ +} ble_gap_cfg_car_incl_cfg_t; + +/**@brief Configuration structure for GAP configurations. */ +typedef union { + ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ + ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ + ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include + configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ + ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, + cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ +} ble_gap_cfg_t; + +/**@brief Channel Map option. + * + * @details Used with @ref sd_ble_opt_get to get the current channel map + * or @ref sd_ble_opt_set to set a new channel map. When setting the + * channel map, it applies to all current and future connections. When getting the + * current channel map, it applies to a single connection and the connection handle + * must be supplied. + * + * @note Setting the channel map may take some time, depending on connection parameters. + * The time taken may be different for each connection and the get operation will + * return the previous channel map until the new one has taken effect. + * + * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. + * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. + * + * @retval ::NRF_SUCCESS Get or set successful. + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - Less then two bits in @ref ch_map are set. + * - Bits for primary advertising channels (37-39) are set. + * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. + * + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ + uint8_t ch_map[5]; /**< Channel Map (37-bit). */ +} ble_gap_opt_ch_map_t; + +/**@brief Local connection latency option. + * + * @details Local connection latency is a feature which enables the slave to improve + * current consumption by ignoring the slave latency set by the peer. The + * local connection latency can only be set to a multiple of the slave latency, + * and cannot be longer than half of the supervision timeout. + * + * @details Used with @ref sd_ble_opt_set to set the local connection latency. The + * @ref sd_ble_opt_get is not supported for this option, but the actual + * local connection latency (unless set to NULL) is set as a return parameter + * when setting the option. + * + * @note The latency set will be truncated down to the closest slave latency event + * multiple, or the nearest multiple before half of the supervision timeout. + * + * @note The local connection latency is disabled by default, and needs to be enabled for new + * connections and whenever the connection is updated. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t requested_latency; /**< Requested local connection latency. */ + uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return + value). */ +} ble_gap_opt_local_conn_latency_t; + +/**@brief Disable slave latency + * + * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection + * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the + * peripheral will ignore the slave_latency set by the central. + * + * @note Shall only be called on peripheral links. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ +} ble_gap_opt_slave_latency_disable_t; + +/**@brief Passkey Option. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @endmscs + * + * @details Structure containing the passkey to be used during pairing. This can be used with @ref + * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication + * instead of generating a random one. + * + * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + */ +typedef struct { + uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used + during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ +} ble_gap_opt_passkey_t; + +/**@brief Compatibility mode 1 option. + * + * @details This can be used with @ref sd_ble_opt_set to enable and disable + * compatibility mode 1. Compatibility mode 1 is disabled by default. + * + * @note Compatibility mode 1 enables interoperability with devices that do not support a value of + * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a + * limited set of legacy peripheral devices from another vendor. Enabling this compatibility + * mode will only have an effect if the local device will act as a central device and + * initiate a connection to a peripheral device. In that case it may lead to the connection + * creation taking up to one connection interval longer to complete for all connections. + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. + */ +typedef struct { + uint8_t enable : 1; /**< Enable compatibility mode 1.*/ +} ble_gap_opt_compat_mode_1_t; + +/**@brief Authenticated payload timeout option. + * + * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other + * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. + * + * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated + * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted + * link. + * + * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance + * to reset the timer. In addition the stack will try to prioritize running of LE ping over other + * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects + * on other activities, it is recommended to use high timeout values. + * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. + */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle */ + uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ +} ble_gap_opt_auth_payload_timeout_t; + +/**@brief Option structure for GAP options. */ +typedef union { + ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ + ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ + ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ + ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ + ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ + ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ +} ble_gap_opt_t; + +/**@brief Connection event triggering parameters. */ +typedef struct { + uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until + connection event PPI task triggering is stopped. + The PPI channel ID can not be one of the PPI channels reserved by + the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ + uint32_t task_endpoint; /**< Task Endpoint to trigger. */ + uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ + uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. + If the device is in slave role and slave latency is enabled, + this parameter should be set to a multiple of (slave latency + 1) + to ensure low power operation. */ +} ble_gap_conn_event_trigger_t; +/**@} */ + +/**@addtogroup BLE_GAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set the local Bluetooth identity address. + * + * The local Bluetooth identity address is the address that identifies this device to other peers. + * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @note The identity address cannot be changed while advertising, scanning or creating a connection. + * + * @note This address will be distributed to the peer during bonding. + * If the address changes, the address stored in the peer device will not be valid and the ability to + * reconnect using the old address will be lost. + * + * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being + * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged + * for the lifetime of each IC. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @endmscs + * + * @param[in] p_addr Pointer to address structure. + * + * @retval ::NRF_SUCCESS Address successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, + * scanning or creating a connection. + */ +SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); + +/**@brief Get local Bluetooth identity address. + * + * @note This will always return the identity address irrespective of the privacy settings, + * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + */ +SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); + +/**@brief Get the Bluetooth device address used by the advertiser. + * + * @note This function will return the local Bluetooth address used in advertising PDUs. When + * using privacy, the SoftDevice will generate a new private address every + * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using + * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the + * address returned may not be the latest address that is used in the advertising PDUs. + * + * @param[in] adv_handle The advertising handle to get the address from. + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval ::NRF_SUCCESS Address successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. + * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); + +/**@brief Set the active whitelist in the SoftDevice. + * + * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. + * The whitelist cannot be set if a BLE role is using the whitelist. + * + * @note If an address is resolved using the information in the device identity list, then the whitelist + * filter policy applies to the peer identity address and not the resolvable address sent on air. + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @endmscs + * + * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. + * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * + * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. + * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when + * pp_wl_addrs is not NULL. + */ +SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); + +/**@brief Set device identity list. + * + * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. + * The device identity list cannot be set if a BLE role is using the list. + * + * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will + * be cleared. + * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the + * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. + * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. + * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. + * This code may be returned if the local IRK list also has an invalid entry. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. + * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity + * address. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can + * only return when pp_id_keys is not NULL. + */ +SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, + sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, + uint8_t len)); + +/**@brief Set privacy settings. + * + * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. + * + * @param[in] p_privacy_params Privacy settings. + * + * @mscs + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Set successfully. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. + * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution + characteristic is not configured to be included and the SoftDevice is configured + to support central roles. + See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning + * or creating a connection. + */ +SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); + +/**@brief Get privacy settings. + * + * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. + * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. + * + * @param[in,out] p_privacy_params Privacy settings. + * + * @retval ::NRF_SUCCESS Privacy settings read. + * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. + * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. + */ +SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); + +/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. + * + * @note The format of the advertising data will be checked by this call to ensure interoperability. + * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and + * duplicating the local name in the advertising data and scan response data. + * + * @note In order to update advertising data while advertising, new advertising buffers must be provided. + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the + * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. + * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See + * @ref ble_gap_adv_data_t. + * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising + * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. + * + * @retval ::NRF_SUCCESS Advertising set successfully configured. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid advertising data configuration specified. See @ref + * ble_gap_adv_data_t. + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, + * see @ref sd_ble_gap_whitelist_set. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - It is invalid to provide non-NULL advertising set parameters while + * advertising. + * - It is invalid to provide the same data buffers while advertising. To + * update advertising data, provide new advertising buffers. + * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref + * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format + * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. + * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an + * existing advertising handle instead. + * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. + */ +SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, + sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, + ble_gap_adv_params_t const *p_adv_params)); + +/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @note Only one advertiser may be active at any time. + * + * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. + * See @ref sd_ble_gap_privacy_set(). + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} + * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} + * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable + * advertising, this is ignored. + * + * @retval ::NRF_SUCCESS The BLE stack has started advertising. + * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration + * tag has been reached; connectable advertiser cannot be started. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref + sd_ble_gap_adv_set_configure. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: + * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. + * - Use of whitelist requested but whitelist has not been set, see @ref + sd_ble_gap_whitelist_set. + * @retval ::NRF_ERROR_RESOURCES Either: + * - adv_handle is configured with connectable advertising, but the event_length parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + * - Not enough BLE role slots available. + Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) + and try again. + * - p_adv_params is configured with connectable advertising, but the event_length + parameter + * associated with conn_cfg_tag is too small to be able to establish a connection on + * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); + +/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). + * + * @mscs + * @mmsc{@ref BLE_GAP_ADV_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] adv_handle The advertising handle that should stop advertising. + * + * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. + * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. + */ +SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); + +/**@brief Update connection parameters. + * + * @details In the central role this will initiate a Link Layer connection parameter update procedure, + * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for + * the central to perform the procedure. In both cases, and regardless of success or failure, the application + * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. + * + * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the + * procedure unrequested. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CPU_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, + * the parameters in the PPCP characteristic of the GAP service will be used instead. + * If NULL is provided on a central role and in response to a @ref + * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected + * + * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, + sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Disconnect (GAP Link Termination). + * + * @details This call initiates the disconnection procedure, and its completion will be communicated to the application + * with a @ref BLE_GAP_EVT_DISCONNECTED event. + * + * @events + * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CONN_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref + * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). + * + * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. + */ +SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); + +/**@brief Set the radio's transmit power. + * + * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for + * possible roles. + * @param[in] handle The handle parameter is interpreted depending on role: + * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. + * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, + * will use the specified transmit power, and include it in the advertising packet headers if + * @ref ble_gap_adv_properties_t::include_tx_power set. + * - For all other roles handle is ignored. + * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). + * + * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. + * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. + * Setting these values on a chip that does not support them will result in undefined behaviour. + * @note The initiator will have the same transmit power as the scanner. + * @note When a connection is created it will inherit the transmit power from the initiator or + * advertiser leading to the connection. + * + * @retval ::NRF_SUCCESS Successfully changed the transmit power. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); + +/**@brief Set GAP Appearance value. + * + * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value set successfully. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); + +/**@brief Get GAP Appearance value. + * + * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. + * + * @retval ::NRF_SUCCESS Appearance value retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); + +/**@brief Set GAP Peripheral Preferred Connection Parameters. + * + * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); + +/**@brief Get GAP Peripheral Preferred Connection Parameters. + * + * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. + * + * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, + see @ref ble_gap_cfg_ppcp_incl_cfg_t. + */ +SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); + +/**@brief Set GAP device name. + * + * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), + * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. + * + * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. + * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. + * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or + * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). + * + * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, + sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); + +/**@brief Get GAP device name. + * + * @note If the device name is longer than the size of the supplied buffer, + * p_len will return the complete device name length, + * and not the number of bytes actually returned in p_dev_name. + * The application may use this information to allocate a suitable buffer size. + * + * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to + * NULL to obtain the complete device name length. + * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. + * + * @retval ::NRF_SUCCESS GAP device name retrieved successfully. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + */ +SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); + +/**@brief Initiate the GAP Authentication procedure. + * + * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), + * otherwise in the peripheral role, an SMP Security Request will be sent. + * + * @events + * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be + * generated:} + * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} + * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} + * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} + * @event{@ref BLE_GAP_EVT_KEY_PRESSED} + * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} + * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} + * @event{@ref BLE_GAP_EVT_AUTH_STATUS} + * @event{@ref BLE_GAP_EVT_TIMEOUT} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the + * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. + * In the central role, this pointer may be NULL to reject a Security Request. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - An encryption is already executing or queued. + * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is + * reached. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. + */ +SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, + sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); + +/**@brief Reply with GAP security parameters. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. + * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be + * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. + * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or + * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this + * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. + * Note that the SoftDevice expects the application to provide memory for storing the + * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The + * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application + * upon reception of the + * @ref BLE_GAP_EVT_AUTH_STATUS event. + * + * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. + * Distribution of own Identity Information is only supported if the Central + * Address Resolution characteristic is configured to be included or + * the Softdevice is configured to support peripheral roles only. + * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. + */ +SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, + sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, + ble_gap_sec_keyset_t const *p_sec_keyset)); + +/**@brief Reply with an authentication key. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, + * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. + * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. + * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL + * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, + * then a 16-byte OOB key value in little-endian format. + * + * @retval ::NRF_SUCCESS Authentication key successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, + sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); + +/**@brief Reply with an LE Secure connections DHKey. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in + * an @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dhkey LE Secure Connections DHKey. + * + * @retval ::NRF_SUCCESS DHKey successfully set. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - The peer is not authenticated. + * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, + sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); + +/**@brief Notify the peer of a local keypress. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. + * + * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested. + * - Passkey has not been entered. + * - Keypresses have not been enabled by both peers. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. + */ +SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); + +/**@brief Generate a set of OOB data to send to a peer out of band. + * + * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has + * already been established, the one used during connection setup). The application may manually overwrite it with an updated + * value. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. + * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. + * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. + * + * @retval ::NRF_SUCCESS OOB data successfully generated. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, + sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, + ble_gap_lesc_oob_data_t *p_oobd_own)); + +/**@brief Provide the OOB data sent/received out of band. + * + * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. + * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this + * function. + * + * @events + * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref + * sd_ble_gap_authenticate.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. + * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. + * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. + * Must correspond to @ref ble_gap_sec_params_t::oob flag + * in @ref sd_ble_gap_authenticate in the central role or + * in @ref sd_ble_gap_sec_params_reply in the peripheral role. + * + * @retval ::NRF_SUCCESS OOB data accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Authentication key not requested + * - Not expecting LESC OOB data + * - Have not actually exchanged passkeys. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, + sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, + ble_gap_lesc_oob_data_t const *p_oobd_peer)); + +/**@brief Initiate GAP Encryption procedure. + * + * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. + * + * @events + * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} + * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. + * + * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. + * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and + * retry. + */ +SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, + sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); + +/**@brief Reply with GAP security information. + * + * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in + * @ref NRF_ERROR_INVALID_STATE. + * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected + * parameters. + * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is + * available. + * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. + * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is + * available. + * + * @retval ::NRF_SUCCESS Successfully accepted security information. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - No link has been established. + * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. + * - Encryption information provided by the app without being requested. See @ref + * ble_gap_evt_sec_info_request_t::enc_info. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, + sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, + ble_gap_sign_info_t const *p_sign_info)); + +/**@brief Get the current connection security. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. + * + * @retval ::NRF_SUCCESS Current connection security successfully retrieved. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); + +/**@brief Start reporting the received signal strength to the application. + * + * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. + * + * @events + * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is + * dependent on the settings of the threshold_dbm + * and skip_count input parameters.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are + * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. + * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref + * BLE_GAP_EVT_RSSI_CHANGED event. + * + * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); + +/**@brief Stop reporting the received signal strength. + * + * @note An RSSI change detected before the call but not yet received by the application + * may be reported after @ref sd_ble_gap_rssi_stop has been called. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + */ +SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); + +/**@brief Get the received signal strength for the last connection event. + * + * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND + * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. + * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. + * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. + * + * @retval ::NRF_SUCCESS Successfully read the RSSI. + * @retval ::NRF_ERROR_NOT_FOUND No sample is available. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. + */ +SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); + +/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). + * + * @note A call to this function will require the application to keep the memory pointed by + * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped + * or when this function is called with another buffer. + * + * @note The scanner will automatically stop in the following cases: + * - @ref sd_ble_gap_scan_stop is called. + * - @ref sd_ble_gap_connect is called. + * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. + * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application + * access received data. The application must call this function to continue scanning, or call @ref + * sd_ble_gap_scan_stop to stop scanning. + * + * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to + * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will + * receive more reports from this advertising event. The following reports will include the old and new received data. + * + * @events + * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue + * scanning, this parameter must be NULL. + * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. + * The memory pointed to should be kept alive until the scanning is stopped. + * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. + * If the scanner receives advertising data larger than can be stored in the buffer, + * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status + * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. + * + * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: + * - Scanning is already ongoing and p_scan_params was not NULL + * - Scanning is not running and p_scan_params was NULL. + * - The scanner has timed out when this function is called to continue scanning. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. + * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. + * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again + */ +SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, + sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); + +/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). + * + * @note The buffer provided in @ref sd_ble_gap_scan_start is released. + * + * @mscs + * @mmsc{@ref BLE_GAP_SCAN_MSC} + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. + * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. + */ +SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); + +/**@brief Create a connection (GAP Link Establishment). + * + * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. + * The scanning procedure will be stopped even if the function returns an error. + * + * @events + * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} + * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use + * whitelist, then p_peer_addr is ignored. + * @param[in] p_scan_params Pointer to scan parameters structure. + * @param[in] p_conn_params Pointer to desired connection parameters. + * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or + * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. + * + * @retval ::NRF_SUCCESS Successfully initiated connection procedure. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * - Invalid parameter(s) in p_scan_params or p_conn_params. + * - Use of whitelist requested but whitelist has not been set, see @ref + * sd_ble_gap_whitelist_set. + * - Peer address was not present in the device identity list, see @ref + * sd_ble_gap_device_identities_set. + * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. + * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an + * existing locally initiated connect procedure, which must complete before initiating again. + * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. + * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. + * To increase the number of available connections, + * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. + * @retval ::NRF_ERROR_RESOURCES Either: + * - Not enough BLE role slots available. + * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. + * - The event_length parameter associated with conn_cfg_tag is too small to be able to + * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. + * Use @ref sd_ble_cfg_set to increase the event length. + */ +SVCALL(SD_BLE_GAP_CONNECT, uint32_t, + sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, + ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); + +/**@brief Cancel a connection establishment. + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. + * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection + * completed occurred. + */ +SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); + +/**@brief Initiate or respond to a PHY Update Procedure + * + * @details This function is used to initiate or respond to a PHY Update Procedure. It will always + * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. + * If this function is used to initiate a PHY Update procedure and the only option + * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the + * currently active PHYs in the respective directions, the SoftDevice will generate a + * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the + * procedure in the Link Layer. + * + * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, + * then the stack will select PHYs based on the peer's PHY preferences and the local link + * configuration. The PHY Update procedure will for this case result in a PHY combination + * that respects the time constraints configured with @ref sd_ble_cfg_set and the current + * link layer data length. + * + * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. + * + * If the peer does not support the PHY Update Procedure, then the resulting + * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to + * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. + * + * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status + * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or + * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. + * If the peer responds to the PHY Update procedure with invalid parameters, the status + * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. + * If the PHY Update procedure was rejected by the peer for a different reason, the status will + * contain the reason as specified by the peer. + * + * @events + * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} + * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} + * @endmscs + * + * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. + * @param[in] p_gap_phys Pointer to PHY structure. + * + * @retval ::NRF_SUCCESS Successfully requested a PHY Update. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of + * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref + * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. + * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the + * pending procedure to complete and retry. + * + */ +SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); + +/**@brief Initiate or respond to a Data Length Update Procedure. + * + * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of + * p_dl_params, the SoftDevice will choose the highest value supported in current + * configuration and connection parameters. + * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime + * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and + * MaxRxTime will be limited to maximum 2120 us. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update + * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let + * the SoftDevice automatically decide the value for that member. + * Set to NULL to use automatic values for all members. + * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not + * have enough resources or does not support the requested Data Length + * Update parameters. Ignored if NULL. + * + * @mscs + * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} + * @endmscs + * + * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_STATE No link has been established. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. + * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect + * p_dl_limitation to see which parameter is not supported. + * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested + * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation + * to see where the limitation is. + * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the + * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. + */ +SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, + sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, + ble_gap_data_length_limitation_t *p_dl_limitation)); + +/**@brief Start the Quality of Service (QoS) channel survey module. + * + * @details The channel survey module provides measurements of the energy levels on + * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT + * events will periodically report the measured energy levels for each channel. + * + * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, + * Radio Timeslot API events and Flash API events. + * + * @note The channel survey module will attempt to do measurements so that the average interval + * between measurements will be interval_us. However due to the channel survey module + * having the lowest priority of all roles and modules, this may not be possible. In that + * case fewer than expected channel survey reports may be given. + * + * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available + * must be set. This is done using @ref sd_ble_cfg_set. + * + * @param[in] interval_us Requested average interval for the measurements and reports. See + * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set + * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel + * survey role will be scheduled at every available opportunity. + * + * @retval ::NRF_SUCCESS The module is successfully started. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the + * allowed range. + * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. + * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. + * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using + * @ref sd_ble_cfg_set. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); + +/**@brief Stop the Quality of Service (QoS) channel survey module. + * + * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this + * function is called. + * + * @retval ::NRF_SUCCESS The module is successfully stopped. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. + */ +SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); + +/**@brief Obtain the next connection event counter value. + * + * @details The connection event counter is initialized to zero on the first connection event. The value is incremented + * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, + * Section 4.5.1. + * + * @note The connection event counter obtained through this API will be outdated if this API is called + * at the same time as the connection event counter is incremented. + * + * @note This API will always return the last connection event counter + 1. + * The actual connection event may be multiple connection events later if: + * - Slave latency is enabled and there is no data to transmit or receive. + * - Another role is scheduled with a higher priority at the same time as the next connection event. + * + * @param[in] conn_handle Connection handle. + * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. + * + * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, + sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); + +/**@brief Start triggering a given task on connection event start. + * + * @details When enabled, this feature will trigger a PPI task at the start of connection events. + * The application can configure the SoftDevice to trigger every N connection events starting from + * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. + * + * @param[in] conn_handle Connection handle. + * @param[in] p_params Connection event trigger parameters. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. + * @retval ::NRF_ERROR_INVALID_STATE Either: + * - Trying to start connection event triggering when it is already ongoing. + * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. + * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value + to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, + sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); + +/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. + * + * @param[in] conn_handle Connection handle. + * + * @retval ::NRF_SUCCESS Success. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. + * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. + */ +SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GAP_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatt.h b/variants/wio-tracker-wm1110/softdevice/ble_gatt.h new file mode 100644 index 000000000..df0d728fc --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gatt.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common + @{ + @brief Common definitions and prototypes for the GATT interfaces. + */ + +#ifndef BLE_GATT_H__ +#define BLE_GATT_H__ + +#include "ble_err.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATT_DEFINES Defines + * @{ */ + +/** @brief Default ATT MTU, in bytes. */ +#define BLE_GATT_ATT_MTU_DEFAULT 23 + +/**@brief Invalid Attribute Handle. */ +#define BLE_GATT_HANDLE_INVALID 0x0000 + +/**@brief First Attribute Handle. */ +#define BLE_GATT_HANDLE_START 0x0001 + +/**@brief Last Attribute Handle. */ +#define BLE_GATT_HANDLE_END 0xFFFF + +/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources + * @{ */ +#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ +/** @} */ + +/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations + * @{ */ +#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ +/** @} */ + +/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags + * @{ */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ +#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ +/** @} */ + +/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations + * @{ */ +#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ +#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ +/** @} */ + +/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes + * @{ */ +#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ +#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ +#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ +#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ +#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ +#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ +#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ +#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ + 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ +#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ +#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ +#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ + 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ +#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ +#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ +#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ +#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ +#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ + 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ + */ +#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ + 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ +#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ + 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ +#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats + * @note Found at + * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + * @{ */ +#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ +#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ +#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ +#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ +#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ +#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ +#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ +#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ +#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ +#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ +#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ +/** @} */ + +/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces + * @{ + */ +#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ +#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATT_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. + */ +typedef struct { + uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. + The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + @mscs + @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + @endmscs + */ +} ble_gatt_conn_cfg_t; + +/**@brief GATT Characteristic Properties. */ +typedef struct { + /* Standard properties */ + uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ + uint8_t read : 1; /**< Reading the value permitted. */ + uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ + uint8_t write : 1; /**< Writing the value with Write Request permitted. */ + uint8_t notify : 1; /**< Notification of the value permitted. */ + uint8_t indicate : 1; /**< Indications of the value permitted. */ + uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ +} ble_gatt_char_props_t; + +/**@brief GATT Characteristic Extended Properties. */ +typedef struct { + /* Extended properties */ + uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ + uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ +} ble_gatt_char_ext_props_t; + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATT_H__ + +/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gattc.h b/variants/wio-tracker-wm1110/softdevice/ble_gattc.h new file mode 100644 index 000000000..f1df1782c --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gattc.h @@ -0,0 +1,764 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client + @{ + @brief Definitions and prototypes for the GATT Client interface. + */ + +#ifndef BLE_GATTC_H__ +#define BLE_GATTC_H__ + +#include "ble_err.h" +#include "ble_gatt.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations + * @{ */ + +/**@brief GATTC API SVC numbers. */ +enum BLE_GATTC_SVCS { + SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ + SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ + SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ + SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ + SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ + SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ + SD_BLE_GATTC_READ, /**< Generic read. */ + SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ + SD_BLE_GATTC_WRITE, /**< Generic write. */ + SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ + SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ +}; + +/** + * @brief GATT Client Event IDs. + */ +enum BLE_GATTC_EVTS { + BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref + ble_gattc_evt_prim_srvc_disc_rsp_t. */ + BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. + */ + BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref + ble_gattc_evt_char_disc_rsp_t. */ + BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref + ble_gattc_evt_desc_disc_rsp_t. */ + BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref + ble_gattc_evt_attr_info_disc_rsp_t. */ + BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ + BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ + BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref + ble_gattc_evt_char_vals_read_rsp_t. */ + BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ + BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref + sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ + BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref + ble_gattc_evt_exchange_mtu_rsp_t. */ + BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ + BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref + ble_gattc_evt_write_cmd_tx_complete_t. */ +}; + +/**@brief GATTC Option IDs. + * IDs that uniquely identify a GATTC option. + */ +enum BLE_GATTC_OPTS { + BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTC_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC + * @{ */ +#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ +/** @} */ + +/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats + * @{ */ +#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ +#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ +/** @} */ + +/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults + * @{ */ +#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Write without Response that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTC_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for + transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ +} ble_gattc_conn_cfg_t; + +/**@brief Operation Handle Range. */ +typedef struct { + uint16_t start_handle; /**< Start Handle. */ + uint16_t end_handle; /**< End Handle. */ +} ble_gattc_handle_range_t; + +/**@brief GATT service. */ +typedef struct { + ble_uuid_t uuid; /**< Service UUID. */ + ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ +} ble_gattc_service_t; + +/**@brief GATT include. */ +typedef struct { + uint16_t handle; /**< Include Handle. */ + ble_gattc_service_t included_srvc; /**< Handle of the included service. */ +} ble_gattc_include_t; + +/**@brief GATT characteristic. */ +typedef struct { + ble_uuid_t uuid; /**< Characteristic UUID. */ + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + uint8_t char_ext_props : 1; /**< Extended properties present. */ + uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ + uint16_t handle_value; /**< Handle of the Characteristic Value. */ +} ble_gattc_char_t; + +/**@brief GATT descriptor. */ +typedef struct { + uint16_t handle; /**< Descriptor Handle. */ + ble_uuid_t uuid; /**< Descriptor UUID. */ +} ble_gattc_desc_t; + +/**@brief Write Parameters. */ +typedef struct { + uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ + uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ + uint16_t handle; /**< Handle to the attribute to be written. */ + uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ + uint16_t len; /**< Length of data in bytes. */ + uint8_t const *p_value; /**< Pointer to the value data. */ +} ble_gattc_write_params_t; + +/**@brief Attribute Information for 16-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ +} ble_gattc_attr_info16_t; + +/**@brief Attribute Information for 128-bit Attribute UUID. */ +typedef struct { + uint16_t handle; /**< Attribute handle. */ + ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ +} ble_gattc_attr_info128_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Service count. */ + ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_prim_srvc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Include count. */ + ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use + event structures with variable length array members. */ +} ble_gattc_evt_rel_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Characteristic count. */ + ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_char_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Descriptor count. */ + ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event + structures with variable length array members. */ +} ble_gattc_evt_desc_disc_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ +typedef struct { + uint16_t count; /**< Attribute count. */ + uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ + union { + ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. + @note This is a variable length array. The size of 1 indicated is only a + placeholder for compilation. See @ref sd_ble_evt_get for more information on + how to use event structures with variable length array members. */ + } info; /**< Attribute information union. */ +} ble_gattc_evt_attr_info_disc_rsp_t; + +/**@brief GATT read by UUID handle value pair. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref + ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ +} ble_gattc_handle_value_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ +typedef struct { + uint16_t count; /**< Handle-Value Pair Count. */ + uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ + uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref + sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. + @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_val_by_uuid_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint16_t offset; /**< Offset of the attribute data. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ +typedef struct { + uint16_t len; /**< Concatenated Attribute values length. */ + uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder + for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with + variable length array members. */ +} ble_gattc_evt_char_vals_read_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ + uint16_t offset; /**< Data offset. */ + uint16_t len; /**< Data length. */ + uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_write_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ +typedef struct { + uint16_t handle; /**< Handle to which the HVx operation applies. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t len; /**< Attribute data length. */ + uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gattc_evt_hvx_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ +typedef struct { + uint16_t server_rx_mtu; /**< Server RX MTU size. */ +} ble_gattc_evt_exchange_mtu_rsp_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gattc_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of write without response transmissions completed. */ +} ble_gattc_evt_write_cmd_tx_complete_t; + +/**@brief GATTC event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which event occurred. */ + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint16_t + error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ + union { + ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ + ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ + ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ + ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ + ble_gattc_evt_char_val_by_uuid_read_rsp_t + char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ + ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ + ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ + ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ + ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ + ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ + ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ + ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ + ble_gattc_evt_write_cmd_tx_complete_t + write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ + } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ +} ble_gattc_evt_t; + +/**@brief UUID discovery option. + * + * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the + * Vendor Specific UUID table. Disabled by default. + * - When disabled, if a procedure initiated by + * @ref sd_ble_gattc_primary_services_discover, + * @ref sd_ble_gattc_relationships_discover, + * @ref sd_ble_gattc_characteristics_discover, + * @ref sd_ble_gattc_descriptors_discover + * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set + * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use + * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding + * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will + * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. + * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. + * + * @note @ref sd_ble_opt_get is not supported for this option. + * + * @retval ::NRF_SUCCESS Set successfully. + * + */ +typedef struct { + uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ +} ble_gattc_opt_uuid_disc_t; + +/**@brief Option structure for GATTC options. */ +typedef union { + ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ +} ble_gattc_opt_t; + +/** @} */ + +/** @addtogroup BLE_GATTC_FUNCTIONS Functions + * @{ */ + +/**@brief Initiate or continue a GATT Primary Service Discovery procedure. + * + * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. + * If the last service has not been reached, this function must be called again with an updated start handle value to + * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] start_handle Handle to start searching from. + * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, + sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); + +/**@brief Initiate or continue a GATT Relationship Discovery procedure. + * + * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been + * reached, this must be called again with an updated handle range to continue the search. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, + sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Discovery procedure. + * + * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, + sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. + * + * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not + * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref + * ble_gattc_opt_uuid_disc_t. + * + * @events + * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, + sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. + * + * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been + * reached, this must be called again with an updated handle range to continue the discovery. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_uuid Pointer to a Characteristic value UUID to read. + * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, + sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, + ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. + * + * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or + * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read + * the complete value. + * + * @events + * @event{@ref BLE_GATTC_EVT_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute to be read. + * @param[in] offset Offset into the attribute value to be read. + * + * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); + +/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. + * + * @details This function initiates a GATT Read Multiple Characteristic Values procedure. + * + * @events + * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. + * @param[in] handle_count The number of handles in p_handles. + * + * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, + sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); + +/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) + * procedure. + * + * @details This function can perform all write procedures described in GATT. + * + * @note Only one write with response procedure can be ongoing per connection at a time. + * If the application tries to write with response while another write with response procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. + * + * @note The number of Write without Response that can be queued is configured by @ref + * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. + * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without + * response is complete. + * + * @note The application can keep track of the available queue element count for writes without responses by following the + * procedure below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} + * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} + * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_write_params A pointer to a write parameters structure. + * + * @retval ::NRF_SUCCESS Successfully started the Write procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event + * and retry. + * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. + * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); + +/**@brief Send a Handle Value Confirmation to the GATT Server. + * + * @mscs + * @mmsc{@ref BLE_GATTC_HVI_MSC} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] handle The handle of the attribute in the indication. + * + * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); + +/**@brief Discovers information about a range of attributes on a GATT server. + * + * @events + * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} + * @endevents + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] p_handle_range The range of handles to request information about. + * + * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, + sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); + +/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value, and + * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @events + * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] client_rx_mtu Client RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + used for this connection. + * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent request to the server. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. + * @retval ::NRF_ERROR_BUSY Client procedure already in progress. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + reestablishing the connection. + */ +SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, + sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); + +/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * + * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. + * @note If the buffer contains different event, behavior is undefined. + * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with + * the next Handle-Value pair in each iteration. If the function returns other than + * @ref NRF_SUCCESS, it will not be changed. + * - To start iteration, initialize the structure to zero. + * - To continue, pass the value from previous iteration. + * + * \code + * ble_gattc_handle_value_t iter; + * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); + * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) + * { + * app_handle = iter.handle; + * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); + * } + * \endcode + * + * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. + * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. + */ +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter); + +/** @} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, + ble_gattc_handle_value_t *p_iter) +{ + uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; + uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; + uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; + + if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { + p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; + p_iter->p_value = p_next + sizeof(uint16_t); + return NRF_SUCCESS; + } else { + return NRF_ERROR_NOT_FOUND; + } +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_GATTC_H__ */ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatts.h b/variants/wio-tracker-wm1110/softdevice/ble_gatts.h new file mode 100644 index 000000000..dc94957cd --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_gatts.h @@ -0,0 +1,904 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server + @{ + @brief Definitions and prototypes for the GATTS interface. + */ + +#ifndef BLE_GATTS_H__ +#define BLE_GATTS_H__ + +#include "ble_err.h" +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_hci.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations + * @{ */ + +/** + * @brief GATTS API SVC numbers. + */ +enum BLE_GATTS_SVCS { + SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ + SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ + SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ + SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ + SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ + SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ + SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ + SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ + SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more + attributes. */ + SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ + SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ + SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ + SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ + SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ +}; + +/** + * @brief GATT Server Event IDs. + */ +enum BLE_GATTS_EVTS { + BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See + @ref ble_gatts_evt_write_t. */ + BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with + @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. + */ + BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref + sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ + BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. + */ + BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event + structure applies. */ + BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with + @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. + */ + BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref + ble_gatts_evt_timeout_t. */ + BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref + ble_gatts_evt_hvn_tx_complete_t. */ +}; + +/**@brief GATTS Configuration IDs. + * + * IDs that uniquely identify a GATTS configuration. + */ +enum BLE_GATTS_CFGS { + BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ + BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ +}; + +/** @} */ + +/** @addtogroup BLE_GATTS_DEFINES Defines + * @{ */ + +/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS + * @{ */ +#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ +#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths + * @{ */ +#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ +#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ +/** @} */ + +/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types + * @{ */ +#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ +#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ +#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types + * @{ */ +#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ +#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ +#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ +#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ +#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ +#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ +/** @} */ + +/** @defgroup BLE_GATTS_OPS GATT Server Operations + * @{ */ +#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ +#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ +#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ +#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ +#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ +#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ +/** @} */ + +/** @defgroup BLE_GATTS_VLOCS GATT Value Locations + * @{ */ +#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ +#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ +#define BLE_GATTS_VLOC_USER \ + 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ + of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ + There are no alignment requirements for the buffer. */ +/** @} */ + +/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types + * @{ */ +#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ +#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ +#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ +/** @} */ + +/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags + * @{ */ +#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ +#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ +/** @} */ + +/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values + * @{ + */ +#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ + (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ +/** @} */ + +/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size + * @{ + */ +#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ +#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ +/** @} */ + +/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults + * @{ + */ +#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ + 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ +/** @} */ + +/** @} */ + +/** @addtogroup BLE_GATTS_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. + */ +typedef struct { + uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. + The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ +} ble_gatts_conn_cfg_t; + +/**@brief Attribute metadata. */ +typedef struct { + ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ + ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ + uint8_t vlen : 1; /**< Variable length attribute. */ + uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ + uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ + uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not + Write Command). */ +} ble_gatts_attr_md_t; + +/**@brief GATT Attribute. */ +typedef struct { + ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ + ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ + uint16_t init_len; /**< Initial attribute value length in bytes. */ + uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the + attribute value will be left uninitialized. */ + uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ + uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is + selected in the attribute metadata, this will have to point to a buffer that remains valid through the + lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any + other temporary location. The stack may access that memory directly without the application's + knowledge. For writable characteristics, this value must not be a location in flash memory.*/ +} ble_gatts_attr_t; + +/**@brief GATT Attribute Value. */ +typedef struct { + uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ + uint16_t offset; /**< Attribute value offset. */ + uint8_t *p_value; /**< Pointer to where value is stored or will be stored. + If value is stored in user memory, only the attribute length is updated when p_value == NULL. + Set to NULL when reading to obtain the complete length of the attribute value */ +} ble_gatts_value_t; + +/**@brief GATT Characteristic Presentation Format. */ +typedef struct { + uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ + int8_t exponent; /**< Exponent for integer data types. */ + uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ + uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ + uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ +} ble_gatts_char_pf_t; + +/**@brief GATT Characteristic metadata. */ +typedef struct { + ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ + ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ + uint8_t const * + p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ + uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ + uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ + ble_gatts_char_pf_t const + *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ + ble_gatts_attr_md_t const + *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ + ble_gatts_attr_md_t const + *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ +} ble_gatts_char_md_t; + +/**@brief GATT Characteristic Definition Handles. */ +typedef struct { + uint16_t value_handle; /**< Handle to the characteristic value. */ + uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ + uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ + uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if + not present. */ +} ble_gatts_char_handles_t; + +/**@brief GATT HVx parameters. */ +typedef struct { + uint16_t handle; /**< Characteristic Value Handle. */ + uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ + uint16_t offset; /**< Offset within the attribute value. */ + uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ + uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ +} ble_gatts_hvx_params_t; + +/**@brief GATT Authorization parameters. */ +typedef struct { + uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ + uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. + Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, + as the data to be written needs to be stored and later provided by the application. */ + uint16_t offset; /**< Offset of the attribute value being updated. */ + uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ + uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ +} ble_gatts_authorize_params_t; + +/**@brief GATT Read or Write Authorize Reply parameters. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ + ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ + } params; /**< Reply Parameters. */ +} ble_gatts_rw_authorize_reply_params_t; + +/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ +typedef struct { + uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref + BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ +} ble_gatts_cfg_service_changed_t; + +/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - @ref ble_gatts_attr_md_t::write_perm is out of range. + * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is + * not allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is + * allowed by the Bluetooth Specification. + * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. + * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported + */ +typedef struct { + ble_gatts_attr_md_t + perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ +} ble_gatts_cfg_service_changed_cccd_perm_t; + +/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. + * + * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: + * - The specified Attribute Table size is too small. + * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. + * - The specified Attribute Table size is not a multiple of 4. + */ +typedef struct { + uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref + BLE_GATTS_ATTR_TAB_SIZE_MIN. */ +} ble_gatts_cfg_attr_tab_size_t; + +/**@brief Config structure for GATTS configurations. */ +typedef union { + ble_gatts_cfg_service_changed_t + service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ + ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref + BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ + ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ +} ble_gatts_cfg_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ + uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref + sd_ble_gatts_value_set to finalize the writing operation. */ + uint16_t offset; /**< Offset for the write operation. */ + uint16_t len; /**< Length of the received data. */ + uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for + compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable + length array members. */ +} ble_gatts_evt_write_t; + +/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ + ble_uuid_t uuid; /**< Attribute UUID. */ + uint16_t offset; /**< Offset for the read operation. */ +} ble_gatts_evt_read_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ +typedef struct { + uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ + union { + ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ + ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ + } request; /**< Request Parameters. */ +} ble_gatts_evt_rw_authorize_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ +typedef struct { + uint8_t hint; /**< Hint (currently unused). */ +} ble_gatts_evt_sys_attr_missing_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ +typedef struct { + uint16_t handle; /**< Attribute Handle. */ +} ble_gatts_evt_hvc_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ +typedef struct { + uint16_t client_rx_mtu; /**< Client RX MTU size. */ +} ble_gatts_evt_exchange_mtu_request_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ +typedef struct { + uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ +} ble_gatts_evt_timeout_t; + +/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ +typedef struct { + uint8_t count; /**< Number of notification transmissions completed. */ +} ble_gatts_evt_hvn_tx_complete_t; + +/**@brief GATTS event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ + union { + ble_gatts_evt_write_t write; /**< Write Event Parameters. */ + ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ + ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ + ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ + ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ + ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ + ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_gatts_evt_t; + +/** @} */ + +/** @addtogroup BLE_GATTS_FUNCTIONS Functions + * @{ */ + +/**@brief Add a service declaration to the Attribute Table. + * + * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to + * add a secondary service declaration that is not referenced by another service later in the Attribute Table. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. + * @param[in] p_uuid Pointer to service UUID. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a service declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); + +/**@brief Add an include declaration to the Attribute Table. + * + * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note The included service must already be present in the Attribute Table prior to this call. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID + * is used, it will be placed sequentially. + * @param[in] inc_srvc_handle Handle of the included service. + * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added an include declaration. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + */ +SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, + sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); + +/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations + * to the Attribute Table. + * + * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is + * supported at this time). + * + * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and + * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format + * values. + * + * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic + * permissions. + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_char_md Characteristic metadata. + * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. + * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a characteristic. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, + sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); + +/**@brief Add a descriptor to the Attribute Table. + * + * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is + * supported at this time). + * + * @mscs + * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} + * @endmscs + * + * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is + * used, it will be placed sequentially. + * @param[in] p_attr Pointer to the attribute structure. + * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully added a descriptor. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and + * permissions need to adhere to the constraints. + * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + */ +SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, + sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); + +/**@brief Set the value of a given attribute. + * + * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully set the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + */ +SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, + sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Get the value of a given attribute. + * + * @note If the attribute value is longer than the size of the supplied buffer, + * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), + * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. + * The application may use this information to allocate a suitable buffer size. + * + * @note When retrieving system attribute values with this function, the connection handle + * may refer to an already disconnected connection. Refer to the documentation of + * @ref sd_ble_gatts_sys_attr_get for further information. + * + * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. + * @param[in] handle Attribute handle. + * @param[in,out] p_value Attribute value information. + * + * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + */ +SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, + sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); + +/**@brief Notify or Indicate an attribute value. + * + * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant + * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before + * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single + * API call. + * + * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during + * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, + * @ref NRF_ERROR_BUSY, + * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. + * The caller can check whether the value has been updated by looking at the contents of *(@ref + * ble_gatts_hvx_params_t::p_len). + * + * @note Only one indication procedure can be ongoing per connection at a time. + * If the application tries to indicate an attribute value while another indication procedure is ongoing, + * the function call will return @ref NRF_ERROR_BUSY. + * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. + * + * @note The number of Handle Value Notifications that can be queued is configured by @ref + * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. + * + * @note The application can keep track of the available queue element count for notifications by following the procedure + * below: + * - Store initial queue element count in a variable. + * - Decrement the variable, which stores the currently available queue element count, by one when a call to this + * function returns @ref NRF_SUCCESS. + * - Increment the variable, which stores the current available queue element count, by the count variable in @ref + * BLE_GATTS_EVT_HVN_TX_COMPLETE event. + * + * @events + * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} + * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_HVN_MSC} + * @mmsc{@ref BLE_GATTS_HVI_MSC} + * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data + * contains a non-NULL pointer the attribute value will be updated with the contents + * pointed by it before sending the notification or indication. If the attribute value + * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to + * contain the number of actual bytes written, else it will be set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute + * value. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application + * are available to notify and indicate. + * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and + * indicated. + * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. + * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions + * of the CCCD associated with this characteristic. + * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. + * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC + * event and retry. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. + * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); + +/**@brief Indicate the Service Changed attribute value. + * + * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute + * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will + * be issued. + * + * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. + * + * @events + * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_GATTS_SC_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] start_handle Start of affected attribute handle range. + * @param[in] end_handle End of affected attribute handle range. + * + * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref + * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. + * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: + * - Invalid Connection State + * - Notifications and/or indications not enabled in the CCCD + * - An ATT_MTU exchange is ongoing + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the + * application. + * @retval ::NRF_ERROR_BUSY Procedure already in progress. + * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known + * value. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, + sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); + +/**@brief Respond to a Read/Write authorization request. + * + * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. + * + * @mscs + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} + * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} + * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. + * + * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond + * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update + * is set to 0. + * + * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute + * Table updated. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, + * handle supplied does not match requested handle, + * or invalid data to be written provided by the application. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, + sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); + +/**@brief Update persistent system attribute information. + * + * @details Supply information about persistent system attributes to the stack, + * previously obtained using @ref sd_ble_gatts_sys_attr_get. + * This call is only allowed for active connections, and is usually + * made immediately after a connection is established with an known bonded device, + * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. + * + * p_sysattrs may point directly to the application's stored copy of the system attributes + * obtained using @ref sd_ble_gatts_sys_attr_get. + * If the pointer is NULL, the system attribute info is initialized, assuming that + * the application does not have any previously saved system attribute data for this device. + * + * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. + * + * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may + * have been completed only partially. This means that the state of the attribute table is undefined, and the application should + * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be modified. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be modified. + * + * @mscs + * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle. + * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. + * @param[in] len Size of data pointed by p_sys_attr_data, in octets. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully set the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref + * sd_ble_gatts_sys_attr_get. + * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, + sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); + +/**@brief Retrieve persistent system attribute information from the stack. + * + * @details This call is used to retrieve information about values to be stored persistently by the application + * during the lifetime of a connection or after it has been terminated. When a new connection is established with the + * same bonded device, the system attribute information retrieved with this function should be restored using using @ref + * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The + * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API + * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the + * system attributes may be written to at any time by the peer during a connection's lifetime. + * + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system + * services will be returned. + * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user + * services will be returned. + * + * @mscs + * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} + * @endmscs + * + * @param[in] conn_handle Connection handle of the recently terminated connection. + * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The + * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. + * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual + * length of system attribute data. + * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS + * + * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. + * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. + * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. + */ +SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, + sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); + +/**@brief Retrieve the first valid user attribute handle. + * + * @param[out] p_handle Pointer to an integer where the handle will be stored. + * + * @retval ::NRF_SUCCESS Successfully retrieved the handle. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + */ +SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); + +/**@brief Retrieve the attribute UUID and/or metadata. + * + * @param[in] handle Attribute handle + * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. + * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. + * + * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. + * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. + */ +SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); + +/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. + * + * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. + * + * @details The SoftDevice sets ATT_MTU to the minimum of: + * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and + * - The Server RX MTU value. + * + * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. + * + * @mscs + * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} + * @endmscs + * + * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. + * @param[in] server_rx_mtu Server RX MTU size. + * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. + * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration + * used for this connection. + * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request + * if an ATT_MTU exchange has already been performed in the other direction. + * + * @retval ::NRF_SUCCESS Successfully sent response to the client. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. + * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without + * reestablishing the connection. + */ +SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_GATTS_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_hci.h b/variants/wio-tracker-wm1110/softdevice/ble_hci.h new file mode 100644 index 000000000..27f85d52e --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_hci.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ +*/ + +#ifndef BLE_HCI_H__ +#define BLE_HCI_H__ +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes + * @{ */ + +#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ +#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ +/*0x03 Hardware Failure +0x04 Page Timeout +*/ +#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ +#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ +#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ +#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ +/*0x09 Connection Limit Exceeded +0x0A Synchronous Connection Limit To A Device Exceeded +0x0B ACL Connection Already Exists*/ +#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ +/*0x0D Connection Rejected due to Limited Resources +0x0E Connection Rejected Due To Security Reasons +0x0F Connection Rejected due to Unacceptable BD_ADDR +0x10 Connection Accept Timeout Exceeded +0x11 Unsupported Feature or Parameter Value*/ +#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ +#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ + 0x14 /**< Remote Device Terminated Connection due to low \ + resources.*/ +#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ +#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ +/* +0x17 Repeated Attempts +0x18 Pairing Not Allowed +0x19 Unknown LMP PDU +*/ +#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ +/* +0x1B SCO Offset Rejected +0x1C SCO Interval Rejected +0x1D SCO Air Mode Rejected*/ +#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ +#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ +/*0x20 Unsupported LMP Parameter Value +0x21 Role Change Not Allowed +*/ +#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ +#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ +#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ +/*0x25 Encryption Mode Not Acceptable +0x26 Link Key Can Not be Changed +0x27 Requested QoS Not Supported +*/ +#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ +#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ +#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ +/* +0x2B Reserved +0x2C QoS Unacceptable Parameter +0x2D QoS Rejected +0x2E Channel Classification Not Supported +0x2F Insufficient Security +*/ +#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ +/* +0x31 Reserved +0x32 Role Switch Pending +0x33 Reserved +0x34 Reserved Slot Violation +0x35 Role Switch Failed +0x36 Extended Inquiry Response Too Large +0x37 Secure Simple Pairing Not Supported By Host. +0x38 Host Busy - Pairing +0x39 Connection Rejected due to No Suitable Channel Found*/ +#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ +#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ +#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ +#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ +#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_HCI_H__ + +/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h b/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h new file mode 100644 index 000000000..5f4bd277d --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) + @{ + @brief Definitions and prototypes for the L2CAP interface. + */ + +#ifndef BLE_L2CAP_H__ +#define BLE_L2CAP_H__ + +#include "ble_err.h" +#include "ble_ranges.h" +#include "ble_types.h" +#include "nrf_error.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology + * @{ + * @details + * + * L2CAP SDU + * - A data unit that the application can send/receive to/from a peer. + * + * L2CAP PDU + * - A data unit that is exchanged between local and remote L2CAP entities. + * It consists of L2CAP protocol control information and payload fields. + * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. + * + * L2CAP MTU + * - The maximum length of an L2CAP SDU. + * + * L2CAP MPS + * - The maximum length of an L2CAP PDU payload field. + * + * Credits + * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. + * @} */ + +/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations + * @{ */ + +/**@brief L2CAP API SVC numbers. */ +enum BLE_L2CAP_SVCS { + SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ + SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ + SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ + SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ +}; + +/**@brief L2CAP Event IDs. */ +enum BLE_L2CAP_EVTS { + BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. + \n Reply with @ref sd_ble_l2cap_ch_setup. + \n See @ref ble_l2cap_evt_ch_setup_request_t. */ + BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. + \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ + BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. + \n See @ref ble_l2cap_evt_ch_setup_t. */ + BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. + \n No additional event structure applies. */ + BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. + \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ + BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. + \n See @ref ble_l2cap_evt_ch_credit_t. */ + BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. + \n See @ref ble_l2cap_evt_ch_rx_t. */ + BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. + \n See @ref ble_l2cap_evt_ch_tx_t. */ +}; + +/** @} */ + +/**@addtogroup BLE_L2CAP_DEFINES Defines + * @{ */ + +/**@brief Maximum number of L2CAP channels per connection. */ +#define BLE_L2CAP_CH_COUNT_MAX (64) + +/**@brief Minimum L2CAP MTU, in bytes. */ +#define BLE_L2CAP_MTU_MIN (23) + +/**@brief Minimum L2CAP MPS, in bytes. */ +#define BLE_L2CAP_MPS_MIN (23) + +/**@brief Invalid CID. */ +#define BLE_L2CAP_CID_INVALID (0x0000) + +/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ +#define BLE_L2CAP_CREDITS_DEFAULT (1) + +/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources + * @{ */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ +#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ + /** @} */ + +/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes + * @{ */ +#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ +#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ +#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ +#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ +#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ +#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ +#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ +#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ + (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ +#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ +/** @} */ + +/** @} */ + +/**@addtogroup BLE_L2CAP_STRUCTURES Structures + * @{ */ + +/** + * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. + * + * @note These parameters are set per connection, so all L2CAP channels created on this connection + * will have the same parameters. + * + * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: + * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. + * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. + * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. + */ +typedef struct { + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to receive on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall + be able to transmit on L2CAP channels on connections with this + configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ + uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per + L2CAP channel. The minimum value is one. */ + uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission + per L2CAP channel. The minimum value is one. */ + uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection + with this configuration. The default value is zero, the maximum + value is @ref BLE_L2CAP_CH_COUNT_MAX. + @note if this parameter is set to zero, all other parameters in + @ref ble_l2cap_conn_cfg_t are ignored. */ +} ble_l2cap_conn_cfg_t; + +/**@brief L2CAP channel RX parameters. */ +typedef struct { + uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to + receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ + uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be + able to receive on this L2CAP channel. + - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. + - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ + ble_data_t sdu_buf; /**< SDU data buffer for reception. + - If @ref ble_data_t::p_data is non-NULL, initial credits are + issued to the peer. + - If @ref ble_data_t::p_data is NULL, no initial credits are + issued to the peer. */ +} ble_l2cap_ch_rx_params_t; + +/**@brief L2CAP channel setup parameters. */ +typedef struct { + ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting + setup of an L2CAP channel, ignored otherwise. */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. + Used when replying to a setup request of an L2CAP + channel, ignored otherwise. */ +} ble_l2cap_ch_setup_params_t; + +/**@brief L2CAP channel TX parameters. */ +typedef struct { + uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to + transmit on this L2CAP channel. */ + uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is + able to receive on this L2CAP channel. */ + uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able + to transmit on this L2CAP channel. This is effective tx_mps, + selected by the SoftDevice as + MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ + uint16_t credits; /**< Initial credits given by the peer. */ +} ble_l2cap_ch_tx_params_t; + +/**@brief L2CAP Channel Setup Request event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ + uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ +} ble_l2cap_evt_ch_setup_request_t; + +/**@brief L2CAP Channel Setup Refused event. */ +typedef struct { + uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ + uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ +} ble_l2cap_evt_ch_setup_refused_t; + +/**@brief L2CAP Channel Setup Completed event. */ +typedef struct { + ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ +} ble_l2cap_evt_ch_setup_t; + +/**@brief L2CAP Channel SDU Data Buffer Released event. */ +typedef struct { + ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice + returns SDU data buffers supplied by the application, which have + not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or + @ref BLE_L2CAP_EVT_CH_TX event. */ +} ble_l2cap_evt_ch_sdu_buf_released_t; + +/**@brief L2CAP Channel Credit received event. */ +typedef struct { + uint16_t credits; /**< Additional credits given by the peer. */ +} ble_l2cap_evt_ch_credit_t; + +/**@brief L2CAP Channel received SDU event. */ +typedef struct { + uint16_t sdu_len; /**< Total SDU length, in bytes. */ + ble_data_t sdu_buf; /**< SDU data buffer. + @note If there is not enough space in the buffer + (sdu_buf.len < sdu_len) then the rest of the SDU will be + silently discarded by the SoftDevice. */ +} ble_l2cap_evt_ch_rx_t; + +/**@brief L2CAP Channel transmitted SDU event. */ +typedef struct { + ble_data_t sdu_buf; /**< SDU data buffer. */ +} ble_l2cap_evt_ch_tx_t; + +/**@brief L2CAP event structure. */ +typedef struct { + uint16_t conn_handle; /**< Connection Handle on which the event occured. */ + uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or + @ref BLE_L2CAP_CID_INVALID if not present. */ + union { + ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ + ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ + ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ + ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ + ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ + ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ + ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ + } params; /**< Event Parameters. */ +} ble_l2cap_evt_t; + +/** @} */ + +/**@addtogroup BLE_L2CAP_FUNCTIONS Functions + * @{ */ + +/**@brief Set up an L2CAP channel. + * + * @details This function is used to: + * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. + * - Reply to a setup request of an L2CAP channel (if called in response to a + * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection + * Response packet to a peer. + * + * @note A call to this function will require the application to keep the SDU data buffer alive + * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or + * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} + * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: + * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP + * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST + * event when replying to a setup request of an L2CAP channel. + * - As output: local_cid for this channel. + * @param[in] p_params L2CAP channel parameters. + * + * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. + * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. + * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, + * see @ref ble_l2cap_conn_cfg_t::ch_count. + */ +SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, + sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); + +/**@brief Release an L2CAP channel. + * + * @details This sends a Disconnection Request packet to a peer. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * + * @retval ::NRF_SUCCESS Successfully queued request for transmission. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); + +/**@brief Receive an SDU on an L2CAP channel. + * + * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers + * for reception per L2CAP channel. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Buffer accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a + * @ref BLE_L2CAP_EVT_CH_RX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Transmit an SDU on an L2CAP channel. + * + * @note A call to this function will require the application to keep the memory pointed by + * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX + * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. + * + * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for + * transmission per L2CAP channel. + * + * @note The application can keep track of the available credits for transmission by following + * the procedure below: + * - Store initial credits given by the peer in a variable. + * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Decrement the variable, which stores the currently available credits, by + * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns + * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) + * - Increment the variable, which stores the currently available credits, by additional + * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. + * + * @events + * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} + * @endevents + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel. + * @param[in] p_sdu_buf Pointer to the SDU data buffer. + * + * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for the L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than + * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in + * @ref BLE_L2CAP_EVT_CH_SETUP event. + * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a + * @ref BLE_L2CAP_EVT_CH_TX event and retry. + */ +SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); + +/**@brief Advanced SDU reception flow control. + * + * @details Adjust the way the SoftDevice issues credits to the peer. + * This may issue additional credits to the peer using an LE Flow Control Credit packet. + * + * @mscs + * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} + * @endmscs + * + * @param[in] conn_handle Connection Handle. + * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set + * the value that will be used for newly created channels. + * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every + * time it starts using a new reception buffer. + * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will + * use if this function is not called. + * - If set to zero, the SoftDevice will stop issuing credits for new reception + * buffers the application provides or has provided. SDU reception that is + * currently ongoing will be allowed to complete. + * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be + * written by the SoftDevice with the number of credits that is or will be + * available to the peer. If the value written by the SoftDevice is 0 when + * credits parameter was set to 0, the peer will not be able to send more + * data until more credits are provided by calling this function again with + * credits > 0. This parameter is ignored when local_cid is set to + * @ref BLE_L2CAP_CID_INVALID. + * + * @note Application should take care when setting number of credits higher than default value. In + * this case the application must make sure that the SoftDevice always has reception buffers + * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have + * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic + * on the connection handle may be stalled until the SoftDevice again has an available + * reception buffer. This applies even if the application has used this call to set the + * credits back to default, or zero. + * + * @retval ::NRF_SUCCESS Flow control parameters accepted. + * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. + * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. + * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is + * in progress for an L2CAP channel). + * @retval ::NRF_ERROR_NOT_FOUND CID not found. + */ +SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, + sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // BLE_L2CAP_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_ranges.h b/variants/wio-tracker-wm1110/softdevice/ble_ranges.h new file mode 100644 index 000000000..2768e4996 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_ranges.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_ranges Module specific SVC, event and option number subranges + @{ + + @brief Definition of SVC, event and option number subranges for each API module. + + @note + SVCs, event and option numbers are split into subranges for each API module. + Each module receives its entire allocated range of SVC calls, whether implemented or not, + but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. + + Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, + rather than the last SVC function call actually defined and implemented. + + Specific SVC, event and option values are defined in each module's ble_.h file, + which defines names of each individual SVC code based on the range start value. +*/ + +#ifndef BLE_RANGES_H__ +#define BLE_RANGES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ +#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ + +#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ +#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ + +#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ +#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ + +#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ +#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ + +#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ +#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ + +#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ + +#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ +#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ + +#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ +#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ + +#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ +#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ + +#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ +#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ + +#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ +#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ + +#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ + +#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ +#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ + +#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ +#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ + +#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ +#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ + +#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ +#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ + +#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ +#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ + +#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ +#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ + +#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ + +#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ +#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ + +#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ +#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ + +#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ +#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ + +#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ +#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ + +#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ +#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ + +#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ +#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ + +#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ +#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ + +#ifdef __cplusplus +} +#endif +#endif /* BLE_RANGES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_types.h b/variants/wio-tracker-wm1110/softdevice/ble_types.h new file mode 100644 index 000000000..db3656cfd --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/ble_types.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup BLE_COMMON + @{ + @defgroup ble_types Common types and macro definitions + @{ + + @brief Common types and macro definitions for the BLE SoftDevice. + */ + +#ifndef BLE_TYPES_H__ +#define BLE_TYPES_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup BLE_TYPES_DEFINES Defines + * @{ */ + +/** @defgroup BLE_CONN_HANDLES BLE Connection Handles + * @{ */ +#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ +#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ +/** @} */ + +/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs + * @{ */ +/* Generic UUIDs, applicable to all services */ +#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ +#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ +#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ +#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ +#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ +#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ +#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ +/* GATT specific UUIDs */ +#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ +#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ +/* GAP specific UUIDs */ +#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ +#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ +#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ +/** @} */ + +/** @defgroup BLE_UUID_TYPES Types of UUID + * @{ */ +#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ +#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ +#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ +/** @} */ + +/** @defgroup BLE_APPEARANCES Bluetooth Appearance values + * @note Retrieved from + * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * @{ */ +#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ +#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ +#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ +#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ +#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ +#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ +#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ +#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ +#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ +#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ +#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ +#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ +#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ +#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ +#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ +#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ +#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ +#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ +#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ +#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ +#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ +#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ +#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ +#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ +#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ +#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ +#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ +#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ +#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ +#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ +#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ +#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ +#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ +#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ +#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ +#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ +#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ +#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ +#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ +#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ + 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ +#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ + 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ +/** @} */ + +/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ +#define BLE_UUID_BLE_ASSIGN(instance, value) \ + do { \ + instance.type = BLE_UUID_TYPE_BLE; \ + instance.uuid = value; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ +#define BLE_UUID_COPY_PTR(dst, src) \ + do { \ + (dst)->type = (src)->type; \ + (dst)->uuid = (src)->uuid; \ + } while (0) + +/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ +#define BLE_UUID_COPY_INST(dst, src) \ + do { \ + (dst).type = (src).type; \ + (dst).uuid = (src).uuid; \ + } while (0) + +/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) + +/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ +#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) + +/** @} */ + +/** @addtogroup BLE_TYPES_STRUCTURES Structures + * @{ */ + +/** @brief 128 bit UUID values. */ +typedef struct { + uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ +} ble_uuid128_t; + +/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ +typedef struct { + uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ + uint8_t + type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ +} ble_uuid_t; + +/**@brief Data structure. */ +typedef struct { + uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ + uint16_t len; /**< Length of the data buffer, in bytes. */ +} ble_data_t; + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* BLE_TYPES_H__ */ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h new file mode 100644 index 000000000..4e0bd752a --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_mbr_api Master Boot Record API + @{ + + @brief APIs for updating SoftDevice and BootLoader + +*/ + +#ifndef NRF_MBR_H__ +#define NRF_MBR_H__ + +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_MBR_DEFINES Defines + * @{ */ + +/**@brief MBR SVC Base number. */ +#define MBR_SVC_BASE (0x18) + +/**@brief Page size in words. */ +#define MBR_PAGE_SIZE_IN_WORDS (1024) + +/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. +This is the offset where the first byte of the SoftDevice hex file is written. */ +#define MBR_SIZE (0x1000) + +/** @brief Location (in the flash memory) of the bootloader address. */ +#define MBR_BOOTLOADER_ADDR (0xFF8) + +/** @brief Location (in UICR) of the bootloader address. */ +#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) + +/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ +#define MBR_PARAM_PAGE_ADDR (0xFFC) + +/** @brief Location (in UICR) of the address of the MBR parameter page. */ +#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) + +/** @} */ + +/** @addtogroup NRF_MBR_ENUMS Enumerations + * @{ */ + +/**@brief nRF Master Boot Record API SVC numbers. */ +enum NRF_MBR_SVCS { + SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ +}; + +/**@brief Possible values for ::sd_mbr_command_t.command */ +enum NRF_MBR_COMMANDS { + SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ + SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ + SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any + parameters in ::sd_mbr_command_t params.*/ + SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ + SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see + ::sd_mbr_command_vector_table_base_set_t*/ + SD_MBR_COMMAND_RESERVED, + SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see + ::sd_mbr_command_irq_forward_address_set_t*/ +}; + +/** @} */ + +/** @addtogroup NRF_MBR_TYPES Types + * @{ */ + +/**@brief This command copies part of a new SoftDevice + * + * The destination area is erased before copying. + * If dst is in the middle of a flash page, that whole flash page will be erased. + * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. + * + * The user of this function is responsible for setting the BPROT registers. + * + * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. + * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. + */ +typedef struct { + uint32_t *src; /**< Pointer to the source of data to be copied.*/ + uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ + uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ +} sd_mbr_command_copy_sd_t; + +/**@brief This command works like memcmp, but takes the length in words. + * + * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. + * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. + */ +typedef struct { + uint32_t *ptr1; /**< Pointer to block of memory. */ + uint32_t *ptr2; /**< Pointer to block of memory. */ + uint32_t len; /**< Number of 32 bit words to compare.*/ +} sd_mbr_command_compare_t; + +/**@brief This command copies a new BootLoader. + * + * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to + * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * The bootloader destination is erased by this function. + * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is + * not intended to be written. + * + * On success, this function will not return. It will start the new bootloader from reset-vector as normal. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. + * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ + uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ +} sd_mbr_command_copy_bl_t; + +/**@brief Change the address the MBR starts after a reset + * + * Once this function has been called, this address is where the MBR will start to forward + * interrupts to after a reset. + * + * To restore default forwarding, this function should be called with @ref address set to 0. If a + * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will + * be forwarded to the SoftDevice. + * + * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or + * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize + * @ref MBR_BOOTLOADER_ADDR. + * + * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, + * see @ref sd_mbr_command. + * + * On success, this function will not return. It will reset the device. + * + * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. + * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_vector_table_base_set_t; + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR + * + * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not + * change where the MBR starts after reset. + * + * @retval ::NRF_SUCCESS + */ +typedef struct { + uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ +} sd_mbr_command_irq_forward_address_set_t; + +/**@brief Input structure containing data used when calling ::sd_mbr_command + * + * Depending on what command value that is set, the corresponding params value type must also be + * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command + * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. + */ +typedef struct { + uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ + union { + sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ + sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ + sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ + sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ + sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ + } params; /**< Command parameters. */ +} sd_mbr_command_t; + +/** @} */ + +/** @addtogroup NRF_MBR_FUNCTIONS Functions + * @{ */ + +/**@brief Issue Master Boot Record commands + * + * Commands used when updating a SoftDevice and bootloader. + * + * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires + * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash + * page. The location of the flash page should be provided by the application in either + * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR + * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to + * store the command before reset. When an address is specified, the page it refers to must not be + * used by the application. If no address is provided by the application, i.e. both + * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use + * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. + * + * @param[in] param Pointer to a struct describing the command. + * + * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, + * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, + * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t + * + * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided + * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. + */ +SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_MBR_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error.h b/variants/wio-tracker-wm1110/softdevice/nrf_error.h new file mode 100644 index 000000000..fb2831e19 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_error.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_error SoftDevice Global Error Codes + @{ + + @brief Global Error definitions +*/ + +/* Header guard */ +#ifndef NRF_ERROR_H__ +#define NRF_ERROR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions + * @{ */ +#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base +#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base +#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base +#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base +/** @} */ + +#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command +#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing +#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled +#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error +#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation +#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found +#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported +#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter +#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state +#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length +#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags +#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data +#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size +#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out +#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer +#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation +#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address +#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy +#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. +#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h new file mode 100644 index 000000000..2fd621057 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_sdm_api + @{ + @defgroup nrf_sdm_error SoftDevice Manager Error Codes + @{ + + @brief Error definitions for the SDM API +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SDM_H__ +#define NRF_ERROR_SDM_H__ + +#include "nrf_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. +#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ + (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having + ///< enabled SoftDevice interrupts). +#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ + (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SDM_H__ + +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h new file mode 100644 index 000000000..cbd0ba8ac --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @addtogroup nrf_soc_api + @{ + @defgroup nrf_soc_error SoC Library Error Codes + @{ + + @brief Error definitions for the SoC library + +*/ + +/* Header guard */ +#ifndef NRF_ERROR_SOC_H__ +#define NRF_ERROR_SOC_H__ + +#include "nrf_error.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* Mutex Errors */ +#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken + +/* NVIC errors */ +#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available +#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed +#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return + +/* Power errors */ +#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown +#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown +#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return + +/* Rand errors */ +#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values + +/* PPI errors */ +#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel +#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group + +#ifdef __cplusplus +} +#endif +#endif // NRF_ERROR_SOC_H__ +/** + @} + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h b/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h new file mode 100644 index 000000000..d4ab204d9 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_nvic_api SoftDevice NVIC API + * @{ + * + * @note In order to use this module, the following code has to be added to a .c file: + * \code + * nrf_nvic_state_t nrf_nvic_state = {0}; + * \endcode + * + * @note Definitions and declarations starting with __ (double underscore) in this header file are + * not intended for direct use by the application. + * + * @brief APIs for the accessing NVIC when using a SoftDevice. + * + */ + +#ifndef NRF_NVIC_H__ +#define NRF_NVIC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_NVIC_DEFINES Defines + * @{ */ + +/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions + * @{ */ + +#define __NRF_NVIC_NVMC_IRQn \ + (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ + number in the MDK. */ + +#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ + +/**@brief Interrupt priority levels used by the SoftDevice. */ +#define __NRF_NVIC_SD_IRQ_PRIOS \ + ((uint8_t)((1U << 0) /**< Priority level high .*/ \ + | (1U << 1) /**< Priority level medium. */ \ + | (1U << 4) /**< Priority level low. */ \ + )) + +/**@brief Interrupt priority levels available to the application. */ +#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ +#define __NRF_NVIC_SD_IRQS_0 \ + ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ + (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ + (1U << (uint32_t)SWI5_IRQn))) + +/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ +#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) + +/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ +#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) + +/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ +#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) + +/**@} */ + +/**@} */ + +/**@addtogroup NRF_NVIC_VARIABLES Variables + * @{ */ + +/**@brief Type representing the state struct for the SoftDevice NVIC module. */ +typedef struct { + uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ + uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ +} nrf_nvic_state_t; + +/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an + * application source file. */ +extern nrf_nvic_state_t nrf_nvic_state; + +/**@} */ + +/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions + * @{ */ + +/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. + * + * @retval The value of PRIMASK prior to disabling the interrupts. + */ +__STATIC_INLINE int __sd_nvic_irq_disable(void); + +/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. + */ +__STATIC_INLINE void __sd_nvic_irq_enable(void); + +/**@brief Checks if IRQn is available to application + * @param[in] IRQn IRQ to check + * + * @retval 1 (true) if the IRQ to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); + +/**@brief Checks if priority is available to application + * @param[in] priority priority to check + * + * @retval 1 (true) if the priority to check is available to the application + */ +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); + +/**@} */ + +/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions + * @{ */ + +/**@brief Enable External Interrupt. + * @note Corresponds to NVIC_EnableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was enabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); + +/**@brief Disable External Interrupt. + * @note Corresponds to NVIC_DisableIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt was disabled. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); + +/**@brief Get Pending Interrupt. + * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. + * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. + * + * @retval ::NRF_SUCCESS The interrupt is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); + +/**@brief Set Pending Interrupt. + * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt is set pending. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); + +/**@brief Clear Pending Interrupt. + * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. + * + * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); + +/**@brief Set Interrupt Priority. + * @note Corresponds to NVIC_SetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * @pre Priority is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. + * @param[in] priority A valid IRQ priority for use by the application. + * + * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); + +/**@brief Get Interrupt Priority. + * @note Corresponds to NVIC_GetPriority in CMSIS. + * + * @pre IRQn is valid and not reserved by the stack. + * + * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. + * @param[out] p_priority Return value from NVIC_GetPriority. + * + * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. + * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. + */ +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); + +/**@brief System Reset. + * @note Corresponds to NVIC_SystemReset in CMSIS. + * + * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN + */ +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); + +/**@brief Enter critical region. + * + * @post Application interrupts will be disabled. + * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each + * execution context + * @sa sd_nvic_critical_region_exit + * + * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); + +/**@brief Exit critical region. + * + * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. + * @post If not in a nested critical region, the application interrupts will restored to the state before + * ::sd_nvic_critical_region_enter was called. + * + * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa + * sd_nvic_critical_region_enter. + * + * @retval ::NRF_SUCCESS + */ +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); + +/**@} */ + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE int __sd_nvic_irq_disable(void) +{ + int pm = __get_PRIMASK(); + __disable_irq(); + return pm; +} + +__STATIC_INLINE void __sd_nvic_irq_enable(void) +{ + __enable_irq(); +} + +__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) +{ + if (IRQn < 32) { + return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; + } else if (IRQn < 64) { + return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; + } else { + return 1; + } +} + +__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) +{ + if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { + return 0; + } + return 1; +} + +__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= + (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); + } else { + NVIC_EnableIRQ(IRQn); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); + } else { + NVIC_DisableIRQ(IRQn); + } + + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_pending_irq = NVIC_GetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_SetPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + NVIC_ClearPendingIRQ(IRQn); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) +{ + if (!__sd_nvic_app_accessible_irq(IRQn)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } + + if (!__sd_nvic_is_app_accessible_priority(priority)) { + return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; + } + + NVIC_SetPriority(IRQn, (uint32_t)priority); + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) +{ + if (__sd_nvic_app_accessible_irq(IRQn)) { + *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); + return NRF_SUCCESS; + } else { + return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; + } +} + +__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) +{ + NVIC_SystemReset(); + return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) +{ + int was_masked = __sd_nvic_irq_disable(); + if (!nrf_nvic_state.__cr_flag) { + nrf_nvic_state.__cr_flag = 1; + nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); + NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; + nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); + NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; + *p_is_nested_critical_region = 0; + } else { + *p_is_nested_critical_region = 1; + } + if (!was_masked) { + __sd_nvic_irq_enable(); + } + return NRF_SUCCESS; +} + +__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) +{ + if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { + int was_masked = __sd_nvic_irq_disable(); + NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; + NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; + nrf_nvic_state.__cr_flag = 0; + if (!was_masked) { + __sd_nvic_irq_enable(); + } + } + + return NRF_SUCCESS; +} + +#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif + +#endif // NRF_NVIC_H__ + +/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h new file mode 100644 index 000000000..2786a86a4 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @defgroup nrf_sdm_api SoftDevice Manager API + @{ + + @brief APIs for SoftDevice management. + +*/ + +#ifndef NRF_SDM_H__ +#define NRF_SDM_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_sdm.h" +#include "nrf_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ +#ifdef NRFSOC_DOXYGEN +/// Declared in nrf_mbr.h +#define MBR_SIZE 0 +#warning test +#endif + +/** @brief The major version for the SoftDevice binary distributed with this header file. */ +#define SD_MAJOR_VERSION (7) + +/** @brief The minor version for the SoftDevice binary distributed with this header file. */ +#define SD_MINOR_VERSION (3) + +/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ +#define SD_BUGFIX_VERSION (0) + +/** @brief The SoftDevice variant of this firmware. */ +#define SD_VARIANT_ID 140 + +/** @brief The full version number for the SoftDevice binary this header file was distributed + * with, as a decimal number in the form Mmmmbbb, where: + * - M is major version (one or more digits) + * - mmm is minor version (three digits) + * - bbb is bugfix version (three digits). */ +#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) + +/** @brief SoftDevice Manager SVC Base number. */ +#define SDM_SVC_BASE 0x10 + +/** @brief SoftDevice unique string size in bytes. */ +#define SD_UNIQUE_STR_SIZE 20 + +/** @brief Invalid info field. Returned when an info field does not exist. */ +#define SDM_INFO_FIELD_INVALID (0) + +/** @brief Defines the SoftDevice Information Structure location (address) as an offset from +the start of the SoftDevice (without MBR)*/ +#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) + +/** @brief Defines the absolute SoftDevice Information Structure location (address) when the + * SoftDevice is installed just above the MBR (the usual case). */ +#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) + +/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the + * SoftDevice base address. The size value is of type uint8_t. */ +#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) + +/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. + * The size value is of type uint32_t. */ +#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) + +/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value + * is of type uint16_t. */ +#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) + +/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID + * is of type uint32_t. */ +#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) + +/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in + * the same format as @ref SD_VERSION, stored as an uint32_t. */ +#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) + +/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. + * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. + */ +#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) + +/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value + * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is + * installed just above the MBR (the usual case). */ +#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base + * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above + * the MBR (the usual case). */ +#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) + +/** @brief Defines the amount of flash that is used by the SoftDevice. + * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed + * just above the MBR (the usual case). + */ +#define SD_FLASH_SIZE 0x26000 + +/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual + * case). */ +#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) + +/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use + * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the + * usual case). */ +#define SD_ID_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_VERSION_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. + * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR + * (the usual case). */ +#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ + ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ + ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ + : SDM_INFO_FIELD_INVALID) + +/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges + * @{ */ +#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ +#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ +/**@} */ + +/**@defgroup NRF_FAULT_IDS Fault ID types + * @{ */ +#define NRF_FAULT_ID_SD_ASSERT \ + (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ +#define NRF_FAULT_ID_APP_MEMACC \ + (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ + in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ + register violation the info parameter will contain the sub-region number of \ + PREGION[0], on whose address range the disallowed write access caused the \ + memory access fault. */ +/**@} */ + +/** @} */ + +/** @addtogroup NRF_SDM_ENUMS Enumerations + * @{ */ + +/**@brief nRF SoftDevice Manager API SVC numbers. */ +enum NRF_SD_SVCS { + SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ + SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ + SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ + SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ + SVC_SDM_LAST /**< Placeholder for last SDM SVC */ +}; + +/** @} */ + +/** @addtogroup NRF_SDM_DEFINES Defines + * @{ */ + +/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy + * @{ */ + +#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ +#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ +#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ +#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ +#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ +#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ +#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ +#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ +#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ +#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ +#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ +#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ + +/** @} */ + +/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources + * @{ */ + +#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ +#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ +#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ + +/** @} */ + +/** @} */ + +/** @addtogroup NRF_SDM_TYPES Types + * @{ */ + +/**@brief Type representing LFCLK oscillator source. */ +typedef struct { + uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ + uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second + units (nRF52: 1-32). + @note To avoid excessive clock drift, 0.5 degrees Celsius is the + maximum temperature change allowed in one calibration timer + interval. The interval should be selected to ensure this. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ + uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration + intervals) the RC oscillator shall be calibrated if the temperature + hasn't changed. + 0: Always calibrate even if the temperature hasn't changed. + 1: Only calibrate if the temperature has changed (legacy - nRF51 only). + 2-33: Check the temperature and only calibrate if it has changed, + however calibration will take place every rc_temp_ctiv + intervals in any case. + + @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. + + @note For nRF52, the application must ensure calibration at least once + every 8 seconds to ensure +/-500 ppm clock stability. The + recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is + rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at + least once every 8 seconds and for temperature changes of 0.5 + degrees Celsius every 4 seconds. See the Product Specification + for the nRF52 device being used for more information.*/ + uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing + windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ +} nrf_clock_lf_cfg_t; + +/**@brief Fault Handler type. + * + * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. + * The protocol stack will be in an undefined state when this happens and the only way to recover will be to + * perform a reset, using e.g. CMSIS NVIC_SystemReset(). + * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). + * + * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. + * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may + * continously transmit packets. + * + * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. + * + * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. + * @param[in] pc The program counter of the instruction that triggered the fault. + * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. + * + * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time + * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the + * one that triggered the fault. + */ +typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); + +/** @} */ + +/** @addtogroup NRF_SDM_FUNCTIONS Functions + * @{ */ + +/**@brief Enables the SoftDevice and by extension the protocol stack. + * + * @note Some care must be taken if a low frequency clock source is already running when calling this function: + * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new + * clock source will be started. + * + * @note This function has no effect when returning with an error. + * + * @post If return code is ::NRF_SUCCESS + * - SoC library and protocol stack APIs are made available. + * - A portion of RAM will be unavailable (see relevant SDS documentation). + * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). + * - Interrupts will not arrive from protected peripherals or interrupts. + * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. + * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). + * - Chosen low frequency clock source will be running. + * + * @param p_clock_lf_cfg Low frequency clock source and accuracy. + If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 + In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to + the actual characteristics of your XTAL clock. + * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. + * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. + * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has + an illegal priority level. + * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. + * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. + */ +SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, + sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); + +/**@brief Disables the SoftDevice and by extension the protocol stack. + * + * Idempotent function to disable the SoftDevice. + * + * @post SoC library and protocol stack APIs are made unavailable. + * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). + * @post All peripherals used by the SoftDevice will be reset to default values. + * @post All of RAM become available. + * @post All interrupts are forwarded to the application. + * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); + +/**@brief Check if the SoftDevice is enabled. + * + * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); + +/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice + * + * This function is only intended to be called when a bootloader is enabled. + * + * @param[in] address The base address of the interrupt vector table for forwarded interrupts. + + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SDM_H__ + +/** + @} +*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_soc.h new file mode 100644 index 000000000..c649ca836 --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_soc.h @@ -0,0 +1,1046 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup nrf_soc_api SoC Library API + * @{ + * + * @brief APIs for the SoC library. + * + */ + +#ifndef NRF_SOC_H__ +#define NRF_SOC_H__ + +#include "nrf.h" +#include "nrf_error.h" +#include "nrf_error_soc.h" +#include "nrf_svc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@addtogroup NRF_SOC_DEFINES Defines + * @{ */ + +/**@brief The number of the lowest SVC number reserved for the SoC library. */ +#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ +#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ + +/**@brief Guaranteed time for application to process radio inactive notification. */ +#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) + +/**@brief The minimum allowed timeslot extension time. */ +#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) + +/**@brief The maximum processing time to handle a timeslot extension. */ +#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) + +/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ +#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) + +#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ +#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ +#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ + +#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ +#define SD_EVT_IRQHandler \ + (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ + The default interrupt priority for this handler is set to 6 */ +#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ +#define RADIO_NOTIFICATION_IRQHandler \ + (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ + The default interrupt priority for this handler is set to 6 */ +#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ +#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ + +#define NRF_RADIO_DISTANCE_MAX_US \ + (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ + nrf_radio_request_normal_t) in the request. */ + +#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ + (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ + +#define NRF_RADIO_START_JITTER_US \ + (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ + ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ + (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) + +/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ +#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) + +/**@} */ + +/**@addtogroup NRF_SOC_ENUMS Enumerations + * @{ */ + +/**@brief The SVC numbers used by the SVC functions in the SoC library. */ +enum NRF_SOC_SVCS { + SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, + SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, + SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, + SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, + SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, + SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, + SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, + SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, + SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, + SD_FLASH_WRITE = SOC_SVC_BASE + 9, + SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, + SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, + SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, + SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, + SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, + SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, + SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, + SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, + SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, + SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, + SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, + SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, + SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, + SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, + SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, + SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, + SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, + SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, + SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, + SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, + SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, + SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, + SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, + SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, + SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, + SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, + SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, + SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, + SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, + SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, + SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, + SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, + SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, + SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, + SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, + SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, + SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, + SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, + SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 +}; + +/**@brief Possible values of a ::nrf_mutex_t. */ +enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; + +/**@brief Power modes. */ +enum NRF_POWER_MODES { + NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ + NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ +}; + +/**@brief Power failure thresholds */ +enum NRF_POWER_THRESHOLDS { + NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ +}; + +/**@brief Power failure thresholds for high voltage */ +enum NRF_POWER_THRESHOLDVDDHS { + NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ + NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ +}; + +/**@brief DC/DC converter modes. */ +enum NRF_POWER_DCDC_MODES { + NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ + NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ +}; + +/**@brief Radio notification distances. */ +enum NRF_RADIO_NOTIFICATION_DISTANCES { + NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ + NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ + NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ +}; + +/**@brief Radio notification types. */ +enum NRF_RADIO_NOTIFICATION_TYPES { + NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ + NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and + disabled. */ +}; + +/**@brief The Radio signal callback types. */ +enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { + NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ + NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ +}; + +/**@brief The actions requested by the signal callback. + * + * This code gives the SOC instructions about what action to take when the signal callback has + * returned. + */ +enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { + NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current + timeslot. Maximum execution time for this action: + @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. + This action must be started at least + @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before + the end of the timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ + NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ +}; + +/**@brief Radio timeslot high frequency clock source configuration. */ +enum NRF_RADIO_HFCLK_CFG { + NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the + external crystal for the whole duration of the timeslot. This should be the + preferred option for events that use the radio or require high timing accuracy. + @note The SoftDevice will automatically turn on and off the external crystal, + at the beginning and end of the timeslot, respectively. The crystal may also + intentionally be left running after the timeslot, in cases where it is needed + by the SoftDevice shortly after the end of the timeslot. */ + NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. + The RC oscillator may be the clock source in part or for the whole duration of the + timeslot. The RC oscillator's accuracy must therefore be taken into consideration. + @note If the application will use the radio peripheral in timeslots with this + configuration, it must make sure that the crystal is running and stable before + starting the radio. */ +}; + +/**@brief Radio timeslot priorities. */ +enum NRF_RADIO_PRIORITY { + NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ + NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ +}; + +/**@brief Radio timeslot request type. */ +enum NRF_RADIO_REQUEST_TYPE { + NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first + request in a session. */ + NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ +}; + +/**@brief SoC Events. */ +enum NRF_SOC_EVTS { + NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ + NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ + NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ + NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ + NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ + NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ + NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was + invalid. */ + NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ + NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ + NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ + NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ + NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ + NRF_EVT_NUMBER_OF_EVTS +}; + +/**@} */ + +/**@addtogroup NRF_SOC_STRUCTURES Structures + * @{ */ + +/**@brief Represents a mutex for use with the nrf_mutex functions. + * @note Accessing the value directly is not safe, use the mutex functions! + */ +typedef volatile uint8_t nrf_mutex_t; + +/**@brief Parameters for a request for a timeslot as early as possible. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ + uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref + NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ +} nrf_radio_request_earliest_t; + +/**@brief Parameters for a normal radio timeslot request. */ +typedef struct { + uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ + uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ + uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US + microseconds). */ + uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ +} nrf_radio_request_normal_t; + +/**@brief Radio timeslot request parameters. */ +typedef struct { + uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ + union { + nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ + nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ + } params; /**< Parameter union. */ +} nrf_radio_request_t; + +/**@brief Return parameters of the radio timeslot signal callback. */ +typedef struct { + uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref + NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ + union { + struct { + nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ + } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ + struct { + uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref + NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ + } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ + } params; /**< Parameter union. */ +} nrf_radio_signal_callback_return_param_t; + +/**@brief The radio timeslot signal callback type. + * + * @note In case of invalid return parameters, the radio timeslot will automatically end + * immediately after returning from the signal callback and the + * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. + * @note The returned struct pointer must remain valid after the signal callback + * function returns. For instance, this means that it must not point to a stack variable. + * + * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. + * + * @return Pointer to structure containing action requested by the application. + */ +typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); + +/**@brief AES ECB parameter typedefs */ +typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ +typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ +typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ + +/**@brief AES ECB data structure */ +typedef struct { + soc_ecb_key_t key; /**< Encryption key. */ + soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ + soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ +} nrf_ecb_hal_data_t; + +/**@brief AES ECB block. Used to provide multiple blocks in a single call + to @ref sd_ecb_blocks_encrypt.*/ +typedef struct { + soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ + soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ + soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ +} nrf_ecb_hal_data_block_t; + +/**@} */ + +/**@addtogroup NRF_SOC_FUNCTIONS Functions + * @{ */ + +/**@brief Initialize a mutex. + * + * @param[in] p_mutex Pointer to the mutex to initialize. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); + +/**@brief Attempt to acquire a mutex. + * + * @param[in] p_mutex Pointer to the mutex to acquire. + * + * @retval ::NRF_SUCCESS The mutex was successfully acquired. + * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. + */ +SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); + +/**@brief Release a mutex. + * + * @param[in] p_mutex Pointer to the mutex to release. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); + +/**@brief Query the capacity of the application random pool. + * + * @param[out] p_pool_capacity The capacity of the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); + +/**@brief Get number of random bytes available to the application. + * + * @param[out] p_bytes_available The number of bytes currently available in the pool. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); + +/**@brief Get random bytes from the application pool. + * + * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. + * @param[in] length Number of bytes to take from pool and place in p_buff. + * + * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. + * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes + * available. + */ +SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); + +/**@brief Gets the reset reason register. + * + * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); + +/**@brief Clears the bits of the reset reason register. + * + * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); + +/**@brief Sets the power mode when in CPU sleep. + * + * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait + * + * @retval ::NRF_SUCCESS The power mode was set. + * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. + */ +SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); + +/**@brief Puts the chip in System OFF mode. + * + * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN + */ +SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); + +/**@brief Enables or disables the power-fail comparator. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); + +/**@brief Enables or disables the USB power ready event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); + +/**@brief Enables or disables the power USB-detected event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); + +/**@brief Enables or disables the power USB-removed event. + * + * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. + * The event can be retrieved with sd_evt_get(); + * + * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); + +/**@brief Get USB supply status register content. + * + * @param[out] usbregstatus The content of USBREGSTATUS register. + * + * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); + +/**@brief Sets the power failure comparator threshold value. + * + * @note: Power failure comparator threshold setting. This setting applies both for normal voltage + * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to + * VDDH only). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); + +/**@brief Sets the power failure comparator threshold value for high voltage. + * + * @note: Power failure comparator threshold setting for high voltage mode (supply connected to + * VDDH only). This setting does not apply for normal voltage mode (supply connected to both + * VDD and VDDH). + * + * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. + * + * @retval ::NRF_SUCCESS The power failure threshold was set. + * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. + */ +SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. + * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); + +/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. + * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); + +/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. + * + * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. + * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); + +/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be set in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); + +/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). + * + * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. + * @param[out] p_gpregret Contents of the GPREGRET register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). + * + * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. + */ +SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); + +/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). + * + * For more details on the REG0 stage, please see product specification. + * + * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. + * + * @retval ::NRF_SUCCESS + * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. + */ +SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); + +/**@brief Request the high frequency crystal oscillator. + * + * Will start the high frequency crystal oscillator, the startup time of the crystal varies + * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_release + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); + +/**@brief Releases the high frequency crystal oscillator. + * + * Will stop the high frequency crystal oscillator, this happens immediately. + * + * @see sd_clock_hfclk_is_running + * @see sd_clock_hfclk_request + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); + +/**@brief Checks if the high frequency crystal oscillator is running. + * + * @see sd_clock_hfclk_request + * @see sd_clock_hfclk_release + * + * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); + +/**@brief Waits for an application event. + * + * An application event is either an application interrupt or a pended interrupt when the interrupt + * is disabled. + * + * When the application waits for an application event by calling this function, an interrupt that + * is enabled will be taken immediately on pending since this function will wait in thread mode, + * then the execution will return in the application's main thread. + * + * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M + * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets + * pended, this function will return to the application's main thread. + * + * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ + * in order to sleep using this function. This is only necessary for disabled interrupts, as + * the interrupt handler will clear the pending flag automatically for enabled interrupts. + * + * @note If an application interrupt has happened since the last time sd_app_evt_wait was + * called this function will return immediately and not go to sleep. This is to avoid race + * conditions that can occur when a flag is updated in the interrupt handler and processed + * in the main loop. + * + * @post An application interrupt has happened or a interrupt pending flag is set. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); + +/**@brief Get PPI channel enable register contents. + * + * @param[out] p_channel_enable The contents of the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); + +/**@brief Set PPI channel enable register. + * + * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); + +/**@brief Clear PPI channel enable register. + * + * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); + +/**@brief Assign endpoints to a PPI channel. + * + * @param[in] channel_num Number of the PPI channel to assign. + * @param[in] evt_endpoint Event endpoint of the PPI channel. + * @param[in] task_endpoint Task endpoint of the PPI channel. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, + sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); + +/**@brief Task to enable a channel group. + * + * @param[in] group_num Number of the channel group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); + +/**@brief Task to disable a channel group. + * + * @param[in] group_num Number of the PPI group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); + +/**@brief Assign PPI channels to a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[in] channel_msk Mask of the channels to assign to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); + +/**@brief Gets the PPI channels of a channel group. + * + * @param[in] group_num Number of the channel group. + * @param[out] p_channel_msk Mask of the channels assigned to the group. + * + * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); + +/**@brief Configures the Radio Notification signal. + * + * @note + * - The notification signal latency depends on the interrupt priority settings of SWI used + * for notification signal. + * - To ensure that the radio notification signal behaves in a consistent way, the radio + * notifications must be configured when there is no protocol stack or other SoftDevice + * activity in progress. It is recommended that the radio notification signal is + * configured directly after the SoftDevice has been enabled. + * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice + * will interrupt the application to do Radio Event preparation. + * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have + * to shorten the connection events to have time for the Radio Notification signals. + * + * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio + * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is + * recommended (but not required) to be used with + * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. + * + * @param[in] distance Distance between the notification signal and start of radio activity, see @ref + * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or + * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. + * + * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. + * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all + * running activities and retry. + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); + +/**@brief Encrypts a block according to the specified parameters. + * + * 128-bit AES encryption. + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input + * parameters and one output parameter). + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); + +/**@brief Encrypts multiple data blocks provided as an array of data block structures. + * + * @details: Performs 128-bit AES encryption on multiple data blocks + * + * @note: + * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while + * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application + * main or low interrupt level. + * + * @param[in] block_count Count of blocks in the p_data_blocks array. + * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of + * @ref nrf_ecb_hal_data_block_t structures. + * + * @retval ::NRF_SUCCESS + */ +SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); + +/**@brief Gets any pending events generated by the SoC API. + * + * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. + * + * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. + * + * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. + * @retval ::NRF_ERROR_NOT_FOUND No pending events. + */ +SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); + +/**@brief Get the temperature measured on the chip + * + * This function will block until the temperature measurement is done. + * It takes around 50 us from call to return. + * + * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. + * + * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp + */ +SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); + +/**@brief Flash Write + * + * Commands to write a buffer to flash + * + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * write has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS + * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. + * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is + * protected. + * + * + * @param[in] p_dst Pointer to start of flash location to be written. + * @param[in] p_src Pointer to buffer with data to be written. + * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one + * flash page. See the device's Product Specification for details. + * + * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. + * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); + +/**@brief Flash Erase page + * + * Commands to erase a flash page + * If the SoftDevice is enabled: + * This call initiates the flash access command, and its completion will be communicated to the + * application with exactly one of the following events: + * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. + * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. + * + * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the + * erase has been completed + * + * @note + * - This call takes control over the radio and the CPU during flash erase and write to make sure that + * they will not interfere with the flash access. This means that all interrupts will be blocked + * for a predictable time (depending on the NVMC specification in the device's Product Specification + * and the command parameters). + * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is + * protected. + * + * + * @param[in] page_number Page number of the page to erase + * + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. + * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. + * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. + * @retval ::NRF_SUCCESS The command was accepted. + */ +SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); + +/**@brief Opens a session for radio timeslot requests. + * + * @note Only one session can be open at a time. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot + * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed + * by the application. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 + * interrupt occurs. + * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO + * interrupt occurs. + * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This + * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). + * + * @param[in] p_radio_signal_callback The signal callback. + * + * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. + * @retval ::NRF_ERROR_BUSY If session cannot be opened. + * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); + +/**@brief Closes a session for radio timeslot requests. + * + * @note Any current radio timeslot will be finished before the session is closed. + * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. + * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED + * event is received. + * + * @retval ::NRF_ERROR_FORBIDDEN If session not opened. + * @retval ::NRF_ERROR_BUSY If session is currently being closed. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); + +/**@brief Requests a radio timeslot. + * + * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST + * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref + * NRF_RADIO_REQ_TYPE_EARLIEST. + * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by + * p_request->distance_us and is given relative to the start of the previous timeslot. + * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. + * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. + * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this + * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. + * The application may then try to schedule the first radio timeslot again. + * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). + * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. + * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. + * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the + * specified radio timeslot start, but this does not affect the actual start time of the timeslot. + * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency + * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is + * guaranteed to be clocked from the external crystal. + * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral + * during the radio timeslot. + * + * @param[in] p_request Pointer to the request parameters. + * + * @retval ::NRF_ERROR_FORBIDDEN Either: + * - The session is not open. + * - The session is not IDLE. + * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. + * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a + * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. + * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. + * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. + * @retval ::NRF_SUCCESS Otherwise. + */ +SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); + +/**@brief Write register protected by the SoftDevice + * + * This function writes to a register that is write-protected by the SoftDevice. Please refer to your + * SoftDevice Specification for more details about which registers that are protected by SoftDevice. + * This function can write to the following protected peripheral: + * - ACL + * + * @note Protected registers may be read directly. + * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in + * the register has not changed. See the Product Specification for more details about register + * properties. + * + * @param[in] p_register Pointer to register to be written. + * @param[in] value Value to be written to the register. + * + * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. + * @retval ::NRF_SUCCESS Value successfully written to register. + * + */ +SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); + +/**@} */ + +#ifdef __cplusplus +} +#endif +#endif // NRF_SOC_H__ + +/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_svc.h b/variants/wio-tracker-wm1110/softdevice/nrf_svc.h new file mode 100644 index 000000000..1de44656f --- /dev/null +++ b/variants/wio-tracker-wm1110/softdevice/nrf_svc.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NRF_SVC__ +#define NRF_SVC__ + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Supervisor call declaration. + * + * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. + * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. + * + * @param[in] number The SVC number to be used. + * @param[in] return_type The return type of the SVC function. + * @param[in] signature Function signature. The function can have at most four arguments. + */ + +#ifdef SVCALL_AS_NORMAL_FUNCTION +#define SVCALL(number, return_type, signature) return_type signature +#else + +#ifndef SVCALL +#if defined(__CC_ARM) +#define SVCALL(number, return_type, signature) return_type __svc(number) signature +#elif defined(__GNUC__) +#ifdef __cplusplus +#define GCC_CAST_CPP (uint16_t) +#else +#define GCC_CAST_CPP +#endif +#define SVCALL(number, return_type, signature) \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ + __attribute__((unused)) static return_type signature \ + { \ + __asm("svc %0\n" \ + "bx r14" \ + : \ + : "I"(GCC_CAST_CPP number) \ + : "r0"); \ + } \ + _Pragma("GCC diagnostic pop") + +#elif defined(__ICCARM__) +#define PRAGMA(x) _Pragma(#x) +#define SVCALL(number, return_type, signature) \ + PRAGMA(swi_number = (number)) \ + __swi return_type signature; +#else +#define SVCALL(number, return_type, signature) return_type signature +#endif +#endif // SVCALL + +#endif // SVCALL_AS_NORMAL_FUNCTION + +#ifdef __cplusplus +} +#endif +#endif // NRF_SVC__ diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 613fd3599..6c47780d5 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -11,4 +11,4 @@ lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink \ No newline at end of file +;upload_protocol = jlink diff --git a/version.properties b/version.properties index 268987418..1cb93ac2b 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 14 +build = 15 From 2b9848bf1b51ec081d3ed22448aa655a8fd565bc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 8 Jul 2024 08:16:38 -0500 Subject: [PATCH 0680/1377] Remove tracker variant specific soft device headers (#4255) --- variants/wio-tracker-wm1110/softdevice/ble.h | 652 ---- .../wio-tracker-wm1110/softdevice/ble_err.h | 92 - .../wio-tracker-wm1110/softdevice/ble_gap.h | 2895 ----------------- .../wio-tracker-wm1110/softdevice/ble_gatt.h | 232 -- .../wio-tracker-wm1110/softdevice/ble_gattc.h | 764 ----- .../wio-tracker-wm1110/softdevice/ble_gatts.h | 904 ----- .../wio-tracker-wm1110/softdevice/ble_hci.h | 135 - .../wio-tracker-wm1110/softdevice/ble_l2cap.h | 495 --- .../softdevice/ble_ranges.h | 149 - .../wio-tracker-wm1110/softdevice/ble_types.h | 217 -- .../softdevice/nrf52/nrf_mbr.h | 259 -- .../wio-tracker-wm1110/softdevice/nrf_error.h | 90 - .../softdevice/nrf_error_sdm.h | 73 - .../softdevice/nrf_error_soc.h | 85 - .../wio-tracker-wm1110/softdevice/nrf_nvic.h | 449 --- .../wio-tracker-wm1110/softdevice/nrf_sdm.h | 380 --- .../wio-tracker-wm1110/softdevice/nrf_soc.h | 1046 ------ .../wio-tracker-wm1110/softdevice/nrf_svc.h | 98 - 18 files changed, 9015 deletions(-) delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_err.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gap.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatt.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gattc.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatts.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_hci.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_l2cap.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_ranges.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_types.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_nvic.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_sdm.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_soc.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_svc.h diff --git a/variants/wio-tracker-wm1110/softdevice/ble.h b/variants/wio-tracker-wm1110/softdevice/ble.h deleted file mode 100644 index 177b436ad..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble.h +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON BLE SoftDevice Common - @{ - @defgroup ble_api Events, type definitions and API calls - @{ - - @brief Module independent events, type definitions and API calls for the BLE SoftDevice. - - */ - -#ifndef BLE_H__ -#define BLE_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_gattc.h" -#include "ble_gatts.h" -#include "ble_l2cap.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief Common API SVC numbers. - */ -enum BLE_COMMON_SVCS { - SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ - SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ - SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ - SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ - SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ - SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ - SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ - SD_BLE_OPT_SET, /**< Set a BLE option. */ - SD_BLE_OPT_GET, /**< Get a BLE option. */ - SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ - SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ -}; - -/** - * @brief BLE Module Independent Event IDs. - */ -enum BLE_COMMON_EVTS { - BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t - \n Reply with @ref sd_ble_user_mem_reply. */ - BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ -}; - -/**@brief BLE Connection Configuration IDs. - * - * IDs that uniquely identify a connection configuration. - */ -enum BLE_CONN_CFGS { - BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ - BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ - BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ - BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ - BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ -}; - -/**@brief BLE Common Configuration IDs. - * - * IDs that uniquely identify a common configuration. - */ -enum BLE_COMMON_CFGS { - BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ -}; - -/**@brief Common Option IDs. - * IDs that uniquely identify a common option. - */ -enum BLE_COMMON_OPTS { - BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ - BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ - BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ -}; - -/** @} */ - -/** @addtogroup BLE_COMMON_DEFINES Defines - * @{ */ - -/** @brief Required pointer alignment for BLE Events. - */ -#define BLE_EVT_PTR_ALIGNMENT 4 - -/** @brief Leaves the maximum of the two arguments. - */ -#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) - -/** @brief Maximum possible length for BLE Events. - * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a - * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. - */ -#define BLE_EVT_LEN_MAX(ATT_MTU) \ - (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) - -/** @defgroup BLE_USER_MEM_TYPES User Memory Types - * @{ */ -#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ -#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ -/** @} */ - -/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts - * @{ - */ -#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ -#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ -/** @} */ - -/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. - * @{ - */ -#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ - -/** @} */ - -/** @} */ - -/** @addtogroup BLE_COMMON_STRUCTURES Structures - * @{ */ - -/**@brief User Memory Block. */ -typedef struct { - uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ - uint16_t len; /**< Length in bytes of the user memory block. */ -} ble_user_mem_block_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ -} ble_evt_user_mem_request_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ - ble_user_mem_block_t mem_block; /**< User memory block */ -} ble_evt_user_mem_release_t; - -/**@brief Event structure for events not associated with a specific function module. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ - union { - ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ - ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ - } params; /**< Event parameter union. */ -} ble_common_evt_t; - -/**@brief BLE Event header. */ -typedef struct { - uint16_t evt_id; /**< Value from a BLE__EVT series. */ - uint16_t evt_len; /**< Length in octets including this header. */ -} ble_evt_hdr_t; - -/**@brief Common BLE Event type, wrapping the module specific event reports. */ -typedef struct { - ble_evt_hdr_t header; /**< Event header. */ - union { - ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ - ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ - ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ - ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ - ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ - } evt; /**< Event union. */ -} ble_evt_t; - -/** - * @brief Version Information. - */ -typedef struct { - uint8_t version_number; /**< Link Layer Version number. See - https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ - uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) - (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ - uint16_t - subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ -} ble_version_t; - -/** - * @brief Configuration parameters for the PA and LNA. - */ -typedef struct { - uint8_t enable : 1; /**< Enable toggling for this amplifier */ - uint8_t active_high : 1; /**< Set the pin to be active high */ - uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ -} ble_pa_lna_cfg_t; - -/** - * @brief PA & LNA GPIO toggle configuration - * - * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or - * a low noise amplifier. - * - * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided - * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences - * and must be avoided by the application. - */ -typedef struct { - ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ - ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ - - uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ - uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ - uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ -} ble_common_opt_pa_lna_t; - -/** - * @brief Configuration of extended BLE connection events. - * - * When enabled the SoftDevice will dynamically extend the connection event when possible. - * - * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. - * The connection event can be extended if there is time to send another packet pair before the start of the next connection - * interval, and if there are no conflicts with other BLE roles requesting radio time. - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ -} ble_common_opt_conn_evt_ext_t; - -/** - * @brief Enable/disable extended RC calibration. - * - * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice - * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets - * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. - * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When - * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the - * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. - * - * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the - * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ -} ble_common_opt_extended_rc_cal_t; - -/**@brief Option structure for common options. */ -typedef union { - ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ - ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ - ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ -} ble_common_opt_t; - -/**@brief Common BLE Option type, wrapping the module specific options. */ -typedef union { - ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ - ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ - ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ -} ble_opt_t; - -/**@brief BLE connection configuration type, wrapping the module specific configurations, set with - * @ref sd_ble_cfg_set. - * - * @note Connection configurations don't have to be set. - * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, - * the default connection configuration will be automatically added for the remaining connections. - * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in - * place of @ref ble_conn_cfg_t::conn_cfg_tag. - * - * @sa sd_ble_gap_adv_start() - * @sa sd_ble_gap_connect() - * - * @mscs - * @mmsc{@ref BLE_CONN_CFG} - * @endmscs - - */ -typedef struct { - uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the - @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls - to select this configuration when creating a connection. - Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ - union { - ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ - ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ - ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ - ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ - ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ - } params; /**< Connection configuration union. */ -} ble_conn_cfg_t; - -/** - * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. - */ -typedef struct { - uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. - Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is - @ref BLE_UUID_VS_COUNT_MAX. */ -} ble_common_cfg_vs_uuid_t; - -/**@brief Common BLE Configuration type, wrapping the common configurations. */ -typedef union { - ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ -} ble_common_cfg_t; - -/**@brief BLE Configuration type, wrapping the module specific configurations. */ -typedef union { - ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ - ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ - ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ - ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ -} ble_cfg_t; - -/** @} */ - -/** @addtogroup BLE_COMMON_FUNCTIONS Functions - * @{ */ - -/**@brief Enable the BLE stack - * - * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the - * application RAM region (APP_RAM_BASE). On return, this will - * contain the minimum start address of the application RAM region - * required by the SoftDevice for this configuration. - * @warning After this call, the SoftDevice may generate several events. The list of events provided - * below require the application to initiate a SoftDevice API call. The corresponding API call - * is referenced in the event documentation. - * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop - * communicating with the peer device. - * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST - * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST - * - @ref BLE_GAP_EVT_SEC_REQUEST - * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST - * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST - * - @ref BLE_EVT_USER_MEM_REQUEST - * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located - * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between - * APP_RAM_BASE and the start of the call stack. - * - * @details This call initializes the BLE stack, no BLE related function other than @ref - * sd_ble_cfg_set can be called before this one. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: - * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not - * large enough to fit this configuration's memory requirement. Check *p_app_ram_base - * and set the start address of the application RAM region accordingly. - * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which - * is currently not supported. - * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. - */ -SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); - -/**@brief Add configurations for the BLE stack - * - * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref - * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. - * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. - * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). - * See @ref sd_ble_enable for details about APP_RAM_BASE. - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note If a configuration is set more than once, the last one set is the one that takes effect on - * @ref sd_ble_enable. - * - * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default - * configuration. - * - * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref - * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref - * sd_ble_enable). - * - * @note Error codes for the configurations are described in the configuration structs. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The configuration has been added successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. - * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not - * large enough to fit this configuration's memory requirement. - */ -SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); - -/**@brief Get an event from the pending events queue. - * - * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. - * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. - * The buffer should be interpreted as a @ref ble_evt_t struct. - * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. - * - * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that - * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. - * The application is free to choose whether to call this function from thread mode (main context) or directly from the - * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher - * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) - * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so - * could potentially leave events in the internal queue without the application being aware of this fact. - * - * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to - * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, - * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. - * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length - * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: - * - * \code - * uint16_t len; - * errcode = sd_ble_evt_get(NULL, &len); - * \endcode - * - * @mscs - * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} - * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. - * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. - */ -SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); - -/**@brief Add a Vendor Specific base UUID. - * - * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later - * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t - * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code - * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses - * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to - * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field - * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to - * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, - * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. - * - * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by - * the 16-bit uuid field in @ref ble_uuid_t. - * - * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in - * p_uuid_type along with an @ref NRF_SUCCESS error code. - * - * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding - * bytes 12 and 13. - * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be - * stored. - * - * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. - * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. - */ -SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); - -/**@brief Remove a Vendor Specific base UUID. - * - * @details This call removes a Vendor Specific base UUID. This function allows - * the application to reuse memory allocated for Vendor Specific base UUIDs. - * - * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID - * type. - * - * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. - * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific - * base UUID will be removed. If the function returns successfully, the UUID type that was removed will - * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that - * is in use by the ATT Server. - * - * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. - * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. - */ -SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); - -/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. - * - * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared - * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs - * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index - * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. - * - * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. - * - * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). - * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. - * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. - * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. - */ -SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); - -/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). - * - * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is - * computed. - * - * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. - * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). - * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. - * - * @retval ::NRF_SUCCESS Successfully encoded into the buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. - */ -SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); - -/**@brief Get Version Information. - * - * @details This call allows the application to get the BLE stack version information. - * - * @param[out] p_version Pointer to a ble_version_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Version information stored successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). - */ -SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); - -/**@brief Provide a user memory block. - * - * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. - */ -SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); - -/**@brief Set a BLE option. - * - * @details This call allows the application to set the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. - * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. - * - * @retval ::NRF_SUCCESS Option set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - */ -SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); - -/**@brief Get a BLE option. - * - * @details This call allows the application to retrieve the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. - * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Option retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. - * - */ -SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); - -/** @} */ -#ifdef __cplusplus -} -#endif -#endif /* BLE_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_err.h b/variants/wio-tracker-wm1110/softdevice/ble_err.h deleted file mode 100644 index d20f6d141..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_err.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @addtogroup nrf_error - @{ - @ingroup BLE_COMMON - @} - - @defgroup ble_err General error codes - @{ - - @brief General error code definitions for the BLE API. - - @ingroup BLE_COMMON -*/ -#ifndef NRF_BLE_ERR_H__ -#define NRF_BLE_ERR_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* @defgroup BLE_ERRORS Error Codes - * @{ */ -#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ -#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ -#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ -#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ -#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ -#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ - (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ -/** @} */ - -/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges - * @brief Assignment of subranges for module specific error codes. - * @note For specific error codes, see ble_.h or ble_error_.h. - * @{ */ -#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ -#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ -#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ -#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gap.h b/variants/wio-tracker-wm1110/softdevice/ble_gap.h deleted file mode 100644 index 8ebdfa82b..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gap.h +++ /dev/null @@ -1,2895 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GAP Generic Access Profile (GAP) - @{ - @brief Definitions and prototypes for the GAP interface. - */ - -#ifndef BLE_GAP_H__ -#define BLE_GAP_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GAP API SVC numbers. - */ -enum BLE_GAP_SVCS { - SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ - SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ - SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ - SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ - SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ - SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ - SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ - SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ - SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ - SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ - SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ - SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ - SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ - SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ - SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ - SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ - SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ - SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ - SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ - SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ - SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ - SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ - SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ - SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ - SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ - SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ - SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ - SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ - SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ - SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ - SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ - SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ - SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ - SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ - SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ - SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ - SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ - SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = - BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ -}; - -/**@brief GAP Event IDs. - * IDs that uniquely identify an event coming from the stack to the application. - */ -enum BLE_GAP_EVTS { - BLE_GAP_EVT_CONNECTED = - BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ - BLE_GAP_EVT_DISCONNECTED = - BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE = - BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ - BLE_GAP_EVT_SEC_PARAMS_REQUEST = - BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. - \n See @ref ble_gap_evt_sec_params_request_t. */ - BLE_GAP_EVT_SEC_INFO_REQUEST = - BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. - \n See @ref ble_gap_evt_sec_info_request_t. */ - BLE_GAP_EVT_PASSKEY_DISPLAY = - BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref - sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ - BLE_GAP_EVT_KEY_PRESSED = - BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ - BLE_GAP_EVT_AUTH_KEY_REQUEST = - BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. - \n See @ref ble_gap_evt_auth_key_request_t. */ - BLE_GAP_EVT_LESC_DHKEY_REQUEST = - BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref - sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ - BLE_GAP_EVT_AUTH_STATUS = - BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ - BLE_GAP_EVT_CONN_SEC_UPDATE = - BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ - BLE_GAP_EVT_TIMEOUT = - BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ - BLE_GAP_EVT_RSSI_CHANGED = - BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ - BLE_GAP_EVT_ADV_REPORT = - BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ - BLE_GAP_EVT_SEC_REQUEST = - BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate -\n or with @ref sd_ble_gap_encrypt if required security information is available -. \n See @ref ble_gap_evt_sec_request_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref - sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ - BLE_GAP_EVT_SCAN_REQ_REPORT = - BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ - BLE_GAP_EVT_PHY_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n - See @ref ble_gap_evt_phy_update_request_t. */ - BLE_GAP_EVT_PHY_UPDATE = - BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref - sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE = - BLE_GAP_EVT_BASE + - 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ - BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = - BLE_GAP_EVT_BASE + - 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ - BLE_GAP_EVT_ADV_SET_TERMINATED = - BLE_GAP_EVT_BASE + - 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ -}; - -/**@brief GAP Option IDs. - * IDs that uniquely identify a GAP option. - */ -enum BLE_GAP_OPTS { - BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ - BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ - BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ - BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ - BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = - BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ - BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = - BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ -}; - -/**@brief GAP Configuration IDs. - * - * IDs that uniquely identify a GAP configuration. - */ -enum BLE_GAP_CFGS { - BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ - BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ - BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic - inclusion configuration. */ - BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic - inclusion configuration. */ -}; - -/**@brief GAP TX Power roles. - */ -enum BLE_GAP_TX_POWER_ROLES { - BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ - BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ - BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ -}; - -/** @} */ - -/**@addtogroup BLE_GAP_DEFINES Defines - * @{ */ - -/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP - * @{ */ -#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ - (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ -#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ - (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ -#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ - (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ -#define BLE_ERROR_GAP_WHITELIST_IN_USE \ - (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ - (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ - (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLES GAP Roles - * @{ */ -#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ -#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ -#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ -/**@} */ - -/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources - * @{ */ -#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ -#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ -#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ -/**@} */ - -/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types - * @{ */ -#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ -#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ -#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ - 0x7F /**< An advertiser may advertise without its address. \ - This type of advertising is called anonymous. */ -/**@} */ - -/**@brief The default interval in seconds at which a private address is refreshed. */ -#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ -/**@brief The maximum interval in seconds at which a private address can be refreshed. */ -#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ - -/** @brief BLE address length. */ -#define BLE_GAP_ADDR_LEN (6) - -/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes - * @{ */ -#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ -#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ -#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ - 0x02 /**< Device will send and accept only private addresses for its own address, \ - and will not accept a peer using identity address as sender address when \ - the peer IRK is exchanged, non-zero and added to the identity list. */ -/**@} */ - -/** @brief Invalid power level. */ -#define BLE_GAP_POWER_LEVEL_INVALID 127 - -/** @brief Advertising set handle not set. */ -#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) - -/** @brief The default number of advertising sets. */ -#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) - -/** @brief The maximum number of advertising sets supported by this SoftDevice. */ -#define BLE_GAP_ADV_SET_COUNT_MAX (1) - -/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. - * @{ */ -#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ - (31) /**< Maximum data length for an advertising set. \ - If more advertising data is required, use extended advertising instead. */ -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for an extended advertising set. */ - -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ - (238) /**< Maximum supported data length for an extended connectable advertising set. */ -/**@}. */ - -/** @brief Set ID not available in advertising report. */ -#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF - -/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons - * @{ */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ -/**@} */ - -/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format - * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm - * @{ */ -#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ -#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ -#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ -#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ -#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ -#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ -#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ -#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ -#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ -#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ -#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ -#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ -#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ -#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ -#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ -#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags - * @{ */ -#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min - * @{ */ -#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ -#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ - /**@} */ - -/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min - * @{ */ -#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min - * @{ */ -#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min - * @{ */ -#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ -#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size - * - * Scan buffers are used for storing advertising data received from an advertiser. - * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. - * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. - * @{ */ -#define BLE_GAP_SCAN_BUFFER_MIN \ - (31) /**< Minimum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_MAX \ - (31) /**< Maximum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ - (255) /**< Minimum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ - (1650) /**< Maximum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for \ - an extended advertising set. */ -/** @} */ - -/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types - * - * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. - * - * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. - * The maximum supported data length for an extended advertiser is defined by - * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED - * Note that some of the advertising types do not support advertising data. Non-scannable types do not support - * scan response data. - * - * @{ */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x01 /**< Connectable and scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ - 0x02 /**< Connectable non-scannable directed advertising \ - events. Advertising interval is less that 3.75 ms. \ - Use this type for fast reconnections. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x03 /**< Connectable non-scannable directed advertising \ - events. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x04 /**< Non-connectable scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x05 /**< Non-connectable non-scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x06 /**< Connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x07 /**< Connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x08 /**< Non-connectable scannable undirected advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ - 0x09 /**< Non-connectable scannable directed advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x0A /**< Non-connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x0B /**< Non-connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies - * @{ */ -#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ -#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status - * @{ */ -#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ - 0x01 /**< More data to be received. \ - @note This value will only be used if \ - @ref ble_gap_scan_params_t::report_incomplete_evts and \ - @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ - 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ - 0x03 /**< Failed to receive the remaining data. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -/**@} */ - -/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies - * @{ */ -#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ - 0x00 /**< Accept all advertising packets except directed advertising packets \ - not addressed to this device. */ -#define BLE_GAP_SCAN_FP_WHITELIST \ - 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ - packets not addressed to this device. */ -#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ - 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ - 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units - * @{ */ -#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ - (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ - */ -#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ - (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ - mode. */ -#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ - (0) /**< Unlimited advertising in general discoverable mode. \ - For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ -/**@} */ - -/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes - * @{ */ -#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ -#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ -#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ -/**@} */ - -/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities - * @{ */ -#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ -#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ -#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ -/**@} */ - -/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types - * @{ */ -#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ -#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ -#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ -/**@} */ - -/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types - * @{ */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS GAP Security status - * @{ */ -#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ -#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ -#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ -#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ -#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ -#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ -#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ -#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ -#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ -#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ -#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ -#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ -#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ -#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ -#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ -#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ -#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources - * @{ */ -#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ -#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ -/**@} */ - -/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits - * @{ */ -#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ - 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ - 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ - 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ - 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ -/**@} */ - -/**@defgroup BLE_GAP_DEVNAME GAP device name defines. - * @{ */ -#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ -#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ -#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ -/**@} */ - -/**@brief Disable RSSI events for connections */ -#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF - -/**@defgroup BLE_GAP_PHYS GAP PHYs - * @{ */ -#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ -#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ -#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ -#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ -#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ - -/**@brief Supported PHYs in connections, for scanning, and for advertising. */ -#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ - -/**@} */ - -/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters - * - * See @ref ble_gap_conn_sec_mode_t. - * @{ */ -/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ - do { \ - (ptr)->sm = 0; \ - (ptr)->lv = 0; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 2; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 3; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 4; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 2; \ - } while (0) -/**@} */ - -/**@brief GAP Security Random Number Length. */ -#define BLE_GAP_SEC_RAND_LEN 8 - -/**@brief GAP Security Key Length. */ -#define BLE_GAP_SEC_KEY_LEN 16 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ -#define BLE_GAP_LESC_P256_PK_LEN 64 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ -#define BLE_GAP_LESC_DHKEY_LEN 32 - -/**@brief GAP Passkey Length. */ -#define BLE_GAP_PASSKEY_LEN 6 - -/**@brief Maximum amount of addresses in the whitelist. */ -#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) - -/**@brief Maximum amount of identities in the device identities list. */ -#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) - -/**@brief Default connection count for a configuration. */ -#define BLE_GAP_CONN_COUNT_DEFAULT (1) - -/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. - * @{ */ -#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ -#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ -#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. - * @{ */ -#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ - (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ - (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ - -/**@} */ - -/**@brief Automatic data length parameter. */ -#define BLE_GAP_DATA_LENGTH_AUTO 0 - -/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. - * @{ */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ -/**@} */ - -/**@defgroup GAP_SEC_MODES GAP Security Modes - * @{ */ -#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ -/**@} */ - -/**@brief The total number of channels in Bluetooth Low Energy. */ -#define BLE_GAP_CHANNEL_COUNT (40) - -/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines - * @{ */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ - /**@} */ - -/** @} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations - * @{ - */ -#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ - (1) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will reserve the attribute handles \ - which are otherwise used for this characteristic. \ - By reserving the attribute handles it will be possible \ - to upgrade the SoftDevice without changing handle of the \ - Service Changed characteristic. */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ - (2) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will not reserve the attribute handles \ - which are otherwise used for this characteristic. */ -/**@} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values - * @{ */ -#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -/**@} */ - -/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options - * @{ */ -#define BLE_GAP_SLAVE_LATENCY_ENABLE \ - (0) /**< Slave latency is enabled. When slave latency is enabled, \ - the slave will wake up every time it has data to send, \ - and/or every slave latency number of connection events. */ -#define BLE_GAP_SLAVE_LATENCY_DISABLE \ - (1) /**< Disable slave latency. The slave will wake up every connection event \ - regardless of the requested slave latency. \ - This option consumes the most power. */ -#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ - (2) /**< The slave will wake up every connection event if it has not received \ - an ACK from the master for at least slave latency events. This \ - configuration may increase the power consumption in environments \ - with a lot of radio activity. */ -/**@} */ - -/**@addtogroup BLE_GAP_STRUCTURES Structures - * @{ */ - -/**@brief Advertising event properties. */ -typedef struct { - uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ - uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. - @note Anonymous advertising is only available for - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ - uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ -} ble_gap_adv_properties_t; - -/**@brief Advertising report type. */ -typedef struct { - uint16_t connectable : 1; /**< Connectable advertising event type. */ - uint16_t scannable : 1; /**< Scannable advertising event type. */ - uint16_t directed : 1; /**< Directed advertising event type. */ - uint16_t scan_response : 1; /**< Received a scan response. */ - uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ - uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ - uint16_t reserved : 9; /**< Reserved for future use. */ -} ble_gap_adv_report_type_t; - -/**@brief Advertising Auxiliary Pointer. */ -typedef struct { - uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ - uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ -} ble_gap_aux_pointer_t; - -/**@brief Bluetooth Low Energy address. */ -typedef struct { - uint8_t - addr_id_peer : 1; /**< Only valid for peer addresses. - This bit is set by the SoftDevice to indicate whether the address has been resolved from - a Resolvable Private Address (when the peer is using privacy). - If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. - - This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. - */ - uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ - uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. - @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ -} ble_gap_addr_t; - -/**@brief GAP connection parameters. - * - * @note When ble_conn_params_t is received in an event, both min_conn_interval and - * max_conn_interval will be equal to the connection interval set by the central. - * - * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: - * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval - * that corresponds to the following Bluetooth Spec requirement: - * The Supervision_Timeout in milliseconds shall be larger than - * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. - */ -typedef struct { - uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ -} ble_gap_conn_params_t; - -/**@brief GAP connection security modes. - * - * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n - * Security Mode 1 Level 1: No security is needed (aka open link).\n - * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n - * Security Mode 1 Level 3: MITM protected encrypted link required.\n - * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n - * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n - * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n - */ -typedef struct { - uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ - uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ - -} ble_gap_conn_sec_mode_t; - -/**@brief GAP connection security status.*/ -typedef struct { - ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ - uint8_t - encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ -} ble_gap_conn_sec_t; - -/**@brief Identity Resolving Key. */ -typedef struct { - uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ -} ble_gap_irk_t; - -/**@brief Channel mask (40 bits). - * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, - * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents - * channel index 39. If a bit is set to 1, the channel is not used. - */ -typedef uint8_t ble_gap_ch_mask_t[5]; - -/**@brief GAP advertising parameters. */ -typedef struct { - ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ - ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. - @note ble_gap_addr_t::addr_type cannot be - @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. - - When privacy is enabled and the local device uses - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, - the device identity list is searched for a matching entry. If - the local IRK for that device identity is set, the local IRK - for that device will be used to generate the advertiser address - field in the advertising packet. - - If @ref ble_gap_adv_properties_t::type is directed, this must be - set to the targeted scanner or initiator. If the peer address is - in the device identity list, the peer IRK for that device will be - used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE - target addresses used in the advertising event PDUs. */ - uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE - advertising, this parameter is ignored. */ - uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, - an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - @sa BLE_GAP_ADV_TIMEOUT_VALUES. - @note The SoftDevice will always complete at least one advertising - event even if the duration is set too low. */ - uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling - advertising. Setting the value to 0 disables the limitation. When - the count of advertising events specified by this parameter - (if not 0) is reached, advertising will be automatically stopped - and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, - this parameter is ignored. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be used. - Masking away secondary advertising channels is not supported. */ - uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets - are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS - will be used. - Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. - @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets - are transmitted. - If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. - Valid values are - @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. - If @ref ble_gap_adv_properties_t::type is an extended advertising type - and connectable, this is the PHY that will be used to establish a - connection and send AUX_ADV_IND packets on. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other - advertising sets transmitted by this and other devices. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a - scan request is received and the scanner address is allowed - by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is a non-scannable - advertising type. */ -} ble_gap_adv_params_t; - -/**@brief GAP advertising data buffers. - * - * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and - * shall never be modified while advertising. The data shall be kept alive until either: - * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding - * advertising handle. - * - Advertising is stopped. - * - Advertising data is changed. - * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ -typedef struct { - ble_data_t adv_data; /**< Advertising data. - @note - Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type - that is allowed to contain advertising data. */ - ble_data_t scan_rsp_data; /**< Scan response data. - @note - Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type - that is scannable. */ -} ble_gap_adv_data_t; - -/**@brief GAP scanning parameters. */ -typedef struct { - uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. - If set to 0, the scanner will not receive advertising packets - on secondary advertising channels, and will not be able - to receive long advertising PDUs. */ - uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have - @ref ble_gap_adv_report_type_t::status set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. - This parameter is ignored when used with @ref sd_ble_gap_connect - @note This may be used to abort receiving more packets from an extended - advertising event, and is only available for extended - scanning, see @ref sd_ble_gap_scan_start. - @note This feature is not supported by this SoftDevice. */ - uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. - This parameter is ignored when used with @ref sd_ble_gap_connect. */ - uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. - @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and - @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with - @ref sd_ble_gap_connect */ - uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, - scan_phys will default to @ref BLE_GAP_PHY_1MBPS. - - If @ref ble_gap_scan_params_t::extended is set to 0, the only - supported PHY is @ref BLE_GAP_PHY_1MBPS. - - When used with @ref sd_ble_gap_scan_start, - the bitfield indicates the PHYs the scanner will use for scanning - on primary advertising channels. The scanner will accept - @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. - - When used with @ref sd_ble_gap_connect, the bitfield indicates - the PHYs the initiator will use for scanning on primary advertising - channels. The initiator will accept connections initiated on either - of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. - If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, - the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. - If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan - PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is - @ref BLE_GAP_PHY_CODED, the primary scan PHY is - @ref BLE_GAP_PHY_CODED only. */ - uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ - uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. - If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and - @ref BLE_GAP_PHY_CODED interval shall be larger than or - equal to twice the scan window. */ - uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be - set to 0. - Masking away secondary channels is not supported. */ -} ble_gap_scan_params_t; - -/**@brief Privacy. - * - * The privacy feature provides a way for the device to avoid being tracked over a period of time. - * The privacy feature, when enabled, hides the local device identity and replaces it with a private address - * that is automatically refreshed at a specified interval. - * - * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). - * With this key, a device can generate a random private address that can only be recognized by peers in possession of that - * key, and devices can establish connections without revealing their real identities. - * - * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref - * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. - * - * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all - * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. - * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is - * called. - */ -typedef struct { - uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ - uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ - uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use - the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ - ble_gap_irk_t - *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device - default IRK will be used. When used as output, pointer to IRK structure where the current default IRK - will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate - random private resolvable addresses for the local device unless instructed otherwise. */ -} ble_gap_privacy_params_t; - -/**@brief PHY preferences for TX and RX - * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each - * direction. - * @code - * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * @endcode - * - */ -typedef struct { - uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ -} ble_gap_phys_t; - -/** @brief Keys that can be exchanged during a bonding procedure. */ -typedef struct { - uint8_t enc : 1; /**< Long Term Key and Master Identification. */ - uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ - uint8_t sign : 1; /**< Connection Signature Resolving Key. */ - uint8_t link : 1; /**< Derive the Link Key from the LTK. */ -} ble_gap_sec_kdist_t; - -/**@brief GAP security parameters. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ - uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ - uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ - uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ - uint8_t oob : 1; /**< The OOB data flag. - - In LE legacy pairing, this flag is set if a device has out of band authentication data. - The OOB method is used if both of the devices have out of band authentication data. - - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band - authentication data. The OOB method is used if at least one device has the peer device's OOB data - available. */ - uint8_t - min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ - uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ - ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ - ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ -} ble_gap_sec_params_t; - -/**@brief GAP Encryption Information. */ -typedef struct { - uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ - uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ - uint8_t auth : 1; /**< Authenticated Key. */ - uint8_t ltk_len : 6; /**< LTK length in octets. */ -} ble_gap_enc_info_t; - -/**@brief GAP Master Identification. */ -typedef struct { - uint16_t ediv; /**< Encrypted Diversifier. */ - uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ -} ble_gap_master_id_t; - -/**@brief GAP Signing Information. */ -typedef struct { - uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ -} ble_gap_sign_info_t; - -/**@brief GAP LE Secure Connections P-256 Public Key. */ -typedef struct { - uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the - standard SMP protocol format: {X,Y} both in little-endian. */ -} ble_gap_lesc_p256_pk_t; - -/**@brief GAP LE Secure Connections DHKey. */ -typedef struct { - uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ -} ble_gap_lesc_dhkey_t; - -/**@brief GAP LE Secure Connections OOB data. */ -typedef struct { - ble_gap_addr_t addr; /**< Bluetooth address of the device. */ - uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ - uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ -} ble_gap_lesc_oob_data_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ -typedef struct { - ble_gap_addr_t - peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ - uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ -} ble_gap_evt_connected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ -typedef struct { - uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ -} ble_gap_evt_disconnected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ -typedef struct { - ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ -} ble_gap_evt_phy_update_request_t; - -/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ -typedef struct { - uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ - uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ -} ble_gap_evt_phy_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ -typedef struct { - ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ -} ble_gap_evt_sec_params_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ -typedef struct { - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ - ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ - uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ - uint8_t id_info : 1; /**< If 1, Identity Information required. */ - uint8_t sign_info : 1; /**< If 1, Signing Information required. */ -} ble_gap_evt_sec_info_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ -typedef struct { - uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ - uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply - with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or - @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ -} ble_gap_evt_passkey_display_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ -typedef struct { - uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ -} ble_gap_evt_key_pressed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ -typedef struct { - uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ -} ble_gap_evt_auth_key_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ -typedef struct { - ble_gap_lesc_p256_pk_t - *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory - inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ - uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the - procedure. */ -} ble_gap_evt_lesc_dhkey_request_t; - -/**@brief Security levels supported. - * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. - */ -typedef struct { - uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ - uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ - uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ - uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ -} ble_gap_sec_levels_t; - -/**@brief Encryption Key. */ -typedef struct { - ble_gap_enc_info_t enc_info; /**< Encryption Information. */ - ble_gap_master_id_t master_id; /**< Master Identification. */ -} ble_gap_enc_key_t; - -/**@brief Identity Key. */ -typedef struct { - ble_gap_irk_t id_info; /**< Identity Resolving Key. */ - ble_gap_addr_t id_addr_info; /**< Identity Address. */ -} ble_gap_id_key_t; - -/**@brief Security Keys. */ -typedef struct { - ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ - ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ - ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ - ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the - value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ -} ble_gap_sec_keys_t; - -/**@brief Security key set for both local and peer keys. */ -typedef struct { - ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be - generated locally and will always be stored if bonding. */ - ble_gap_sec_keys_t - keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ -} ble_gap_sec_keyset_t; - -/**@brief Data Length Update Procedure parameters. */ -typedef struct { - uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ - uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ -} ble_gap_data_length_params_t; - -/**@brief Data Length Update Procedure local limitation. */ -typedef struct { - uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ - uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ - uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many - microseconds. */ -} ble_gap_data_length_limitation_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ -typedef struct { - uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ - uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ - uint8_t bonded : 1; /**< Procedure resulted in a bond. */ - uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ - ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ - ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ - ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding - with LE Secure Connections, the enc bit will be always set. */ - ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding - with LE Secure Connections, the enc bit will never be set. */ -} ble_gap_evt_auth_status_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ -typedef struct { - ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ -} ble_gap_evt_conn_sec_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ - union { - ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released - scan buffer is contained in this field. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ -typedef struct { - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ -} ble_gap_evt_rssi_changed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ -typedef struct { - uint8_t reason; /**< Reason for why the advertising set terminated. See - @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ - uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, - this field indicates the number of completed advertising events. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. */ -} ble_gap_evt_adv_set_terminated_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. - * - * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * not all fields in the advertising report may be available. - * - * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. - */ -typedef struct { - ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the - peer's identity address. */ - ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if - @ref ble_gap_adv_report_type_t::directed is set to 1. If the - SoftDevice was able to resolve the address, - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr - contains the local identity address. If the target address of the - advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, - and the SoftDevice was unable to resolve it, the application may try - to resolve this address to find out if the advertising event was - directed to us. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. - See @ref BLE_GAP_PHYS. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. - See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets - were received on a secondary advertising channel. */ - int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. - This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the - last received packet did not contain the Tx Power field. - @note TX Power is only included in extended advertising packets. */ - int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ - uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present - if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID - is not present if @ref ble_gap_evt_adv_report_t::set_id is set to - @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - ble_data_t data; /**< Received advertising or scan response data. If - @ref ble_gap_adv_report_type_t::status is not set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided - in @ref sd_ble_gap_scan_start is now released. */ - ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising - event. @note This field is only set if @ref ble_gap_adv_report_type_t::status - is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ -} ble_gap_evt_adv_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Man In The Middle protection requested. */ - uint8_t lesc : 1; /**< LE Secure Connections requested. */ - uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ -} ble_gap_evt_sec_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ -typedef struct { - uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ -} ble_gap_evt_scan_req_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ -typedef struct { - ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ -} ble_gap_evt_data_length_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. - * - * @note This event may also be raised after a PHY Update procedure. - */ -typedef struct { - ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ -} ble_gap_evt_data_length_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ -typedef struct { - int8_t - channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy - channels, in dBm, indexed by Channel Index. - If no measurement is available for the given channel, channel_energy is set to - @ref BLE_GAP_POWER_LEVEL_INVALID. */ -} ble_gap_evt_qos_channel_survey_report_t; - -/**@brief GAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - union /**< union alternative identified by evt_id in enclosing struct. */ - { - ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ - ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ - ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ - ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ - ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ - ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ - ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ - ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ - ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ - ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ - ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ - ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ - ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ - ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ - ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ - ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ - ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ - ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ - ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ - ble_gap_evt_qos_channel_survey_report_t - qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_t; - -/** - * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. - * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. - */ -typedef struct { - uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. - The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ - uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. - The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref - BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters - for setting the throughput of a connection. - See the SoftDevice Specification for details on throughput. */ -} ble_gap_conn_cfg_t; - -/** - * @brief Configuration of maximum concurrent connections in the different connected roles, set with - * @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too - * large. The maximum supported sum of concurrent connections is - * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. - * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. - * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum - * supported advertising handles is - * @ref BLE_GAP_ADV_SET_COUNT_MAX. - */ -typedef struct { - uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ - uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref - BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ - uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref - BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ - uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is - @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ - uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to - the application using @ref sd_ble_gap_qos_channel_survey_start. */ -} ble_gap_cfg_role_count_t; - -/** - * @brief Device name and its properties, set with @ref sd_ble_cfg_set. - * - * @note If the device name is not configured, the default device name will be - * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be - * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name - * will have no write access. - * - * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, - * the attribute table size must be increased to have room for the longer device name (see - * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). - * - * @note If vloc is @ref BLE_GATTS_VLOC_STACK : - * - p_value must point to non-volatile memory (flash) or be NULL. - * - If p_value is NULL, the device name will initially be empty. - * - * @note If vloc is @ref BLE_GATTS_VLOC_USER : - * - p_value cannot be NULL. - * - If the device name is writable, p_value must point to volatile memory (RAM). - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Invalid device name location (vloc). - * - Invalid device name security mode. - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). - * - The device name length is too long for the given Attribute Table. - * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. - */ -typedef struct { - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ - uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ - uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ -} ble_gap_cfg_device_name_t; - -/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_ppcp_incl_cfg_t; - -/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_car_incl_cfg_t; - -/**@brief Configuration structure for GAP configurations. */ -typedef union { - ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ - ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ - ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include - configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ - ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, - cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ -} ble_gap_cfg_t; - -/**@brief Channel Map option. - * - * @details Used with @ref sd_ble_opt_get to get the current channel map - * or @ref sd_ble_opt_set to set a new channel map. When setting the - * channel map, it applies to all current and future connections. When getting the - * current channel map, it applies to a single connection and the connection handle - * must be supplied. - * - * @note Setting the channel map may take some time, depending on connection parameters. - * The time taken may be different for each connection and the get operation will - * return the previous channel map until the new one has taken effect. - * - * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. - * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. - * - * @retval ::NRF_SUCCESS Get or set successful. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Less then two bits in @ref ch_map are set. - * - Bits for primary advertising channels (37-39) are set. - * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. - * - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ - uint8_t ch_map[5]; /**< Channel Map (37-bit). */ -} ble_gap_opt_ch_map_t; - -/**@brief Local connection latency option. - * - * @details Local connection latency is a feature which enables the slave to improve - * current consumption by ignoring the slave latency set by the peer. The - * local connection latency can only be set to a multiple of the slave latency, - * and cannot be longer than half of the supervision timeout. - * - * @details Used with @ref sd_ble_opt_set to set the local connection latency. The - * @ref sd_ble_opt_get is not supported for this option, but the actual - * local connection latency (unless set to NULL) is set as a return parameter - * when setting the option. - * - * @note The latency set will be truncated down to the closest slave latency event - * multiple, or the nearest multiple before half of the supervision timeout. - * - * @note The local connection latency is disabled by default, and needs to be enabled for new - * connections and whenever the connection is updated. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t requested_latency; /**< Requested local connection latency. */ - uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return - value). */ -} ble_gap_opt_local_conn_latency_t; - -/**@brief Disable slave latency - * - * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection - * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the - * peripheral will ignore the slave_latency set by the central. - * - * @note Shall only be called on peripheral links. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ -} ble_gap_opt_slave_latency_disable_t; - -/**@brief Passkey Option. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @endmscs - * - * @details Structure containing the passkey to be used during pairing. This can be used with @ref - * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication - * instead of generating a random one. - * - * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - */ -typedef struct { - uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used - during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ -} ble_gap_opt_passkey_t; - -/**@brief Compatibility mode 1 option. - * - * @details This can be used with @ref sd_ble_opt_set to enable and disable - * compatibility mode 1. Compatibility mode 1 is disabled by default. - * - * @note Compatibility mode 1 enables interoperability with devices that do not support a value of - * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a - * limited set of legacy peripheral devices from another vendor. Enabling this compatibility - * mode will only have an effect if the local device will act as a central device and - * initiate a connection to a peripheral device. In that case it may lead to the connection - * creation taking up to one connection interval longer to complete for all connections. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. - */ -typedef struct { - uint8_t enable : 1; /**< Enable compatibility mode 1.*/ -} ble_gap_opt_compat_mode_1_t; - -/**@brief Authenticated payload timeout option. - * - * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other - * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. - * - * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated - * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted - * link. - * - * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance - * to reset the timer. In addition the stack will try to prioritize running of LE ping over other - * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects - * on other activities, it is recommended to use high timeout values. - * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ -} ble_gap_opt_auth_payload_timeout_t; - -/**@brief Option structure for GAP options. */ -typedef union { - ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ - ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ - ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ - ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ - ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ - ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ -} ble_gap_opt_t; - -/**@brief Connection event triggering parameters. */ -typedef struct { - uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until - connection event PPI task triggering is stopped. - The PPI channel ID can not be one of the PPI channels reserved by - the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ - uint32_t task_endpoint; /**< Task Endpoint to trigger. */ - uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ - uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. - If the device is in slave role and slave latency is enabled, - this parameter should be set to a multiple of (slave latency + 1) - to ensure low power operation. */ -} ble_gap_conn_event_trigger_t; -/**@} */ - -/**@addtogroup BLE_GAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set the local Bluetooth identity address. - * - * The local Bluetooth identity address is the address that identifies this device to other peers. - * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @note The identity address cannot be changed while advertising, scanning or creating a connection. - * - * @note This address will be distributed to the peer during bonding. - * If the address changes, the address stored in the peer device will not be valid and the ability to - * reconnect using the old address will be lost. - * - * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being - * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged - * for the lifetime of each IC. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @endmscs - * - * @param[in] p_addr Pointer to address structure. - * - * @retval ::NRF_SUCCESS Address successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, - * scanning or creating a connection. - */ -SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); - -/**@brief Get local Bluetooth identity address. - * - * @note This will always return the identity address irrespective of the privacy settings, - * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - */ -SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); - -/**@brief Get the Bluetooth device address used by the advertiser. - * - * @note This function will return the local Bluetooth address used in advertising PDUs. When - * using privacy, the SoftDevice will generate a new private address every - * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using - * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the - * address returned may not be the latest address that is used in the advertising PDUs. - * - * @param[in] adv_handle The advertising handle to get the address from. - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. - * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); - -/**@brief Set the active whitelist in the SoftDevice. - * - * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. - * The whitelist cannot be set if a BLE role is using the whitelist. - * - * @note If an address is resolved using the information in the device identity list, then the whitelist - * filter policy applies to the peer identity address and not the resolvable address sent on air. - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @endmscs - * - * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. - * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. - * - * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. - * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when - * pp_wl_addrs is not NULL. - */ -SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); - -/**@brief Set device identity list. - * - * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. - * The device identity list cannot be set if a BLE role is using the list. - * - * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will - * be cleared. - * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the - * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. - * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. - * This code may be returned if the local IRK list also has an invalid entry. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity - * address. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can - * only return when pp_id_keys is not NULL. - */ -SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, - sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, - uint8_t len)); - -/**@brief Set privacy settings. - * - * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. - * - * @param[in] p_privacy_params Privacy settings. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. - * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution - characteristic is not configured to be included and the SoftDevice is configured - to support central roles. - See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning - * or creating a connection. - */ -SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); - -/**@brief Get privacy settings. - * - * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. - * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. - * - * @param[in,out] p_privacy_params Privacy settings. - * - * @retval ::NRF_SUCCESS Privacy settings read. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - */ -SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); - -/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. - * - * @note The format of the advertising data will be checked by this call to ensure interoperability. - * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and - * duplicating the local name in the advertising data and scan response data. - * - * @note In order to update advertising data while advertising, new advertising buffers must be provided. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the - * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. - * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See - * @ref ble_gap_adv_data_t. - * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising - * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. - * - * @retval ::NRF_SUCCESS Advertising set successfully configured. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid advertising data configuration specified. See @ref - * ble_gap_adv_data_t. - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, - * see @ref sd_ble_gap_whitelist_set. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - It is invalid to provide non-NULL advertising set parameters while - * advertising. - * - It is invalid to provide the same data buffers while advertising. To - * update advertising data, provide new advertising buffers. - * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format - * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an - * existing advertising handle instead. - * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. - */ -SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, - sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, - ble_gap_adv_params_t const *p_adv_params)); - -/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @note Only one advertiser may be active at any time. - * - * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. - * See @ref sd_ble_gap_privacy_set(). - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} - * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} - * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable - * advertising, this is ignored. - * - * @retval ::NRF_SUCCESS The BLE stack has started advertising. - * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration - * tag has been reached; connectable advertiser cannot be started. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref - sd_ble_gap_adv_set_configure. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, see @ref - sd_ble_gap_whitelist_set. - * @retval ::NRF_ERROR_RESOURCES Either: - * - adv_handle is configured with connectable advertising, but the event_length parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - * - Not enough BLE role slots available. - Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) - and try again. - * - p_adv_params is configured with connectable advertising, but the event_length - parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); - -/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle The advertising handle that should stop advertising. - * - * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. - * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); - -/**@brief Update connection parameters. - * - * @details In the central role this will initiate a Link Layer connection parameter update procedure, - * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for - * the central to perform the procedure. In both cases, and regardless of success or failure, the application - * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. - * - * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the - * procedure unrequested. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CPU_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, - * the parameters in the PPCP characteristic of the GAP service will be used instead. - * If NULL is provided on a central role and in response to a @ref - * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected - * - * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, - sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Disconnect (GAP Link Termination). - * - * @details This call initiates the disconnection procedure, and its completion will be communicated to the application - * with a @ref BLE_GAP_EVT_DISCONNECTED event. - * - * @events - * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CONN_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref - * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). - * - * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - */ -SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); - -/**@brief Set the radio's transmit power. - * - * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for - * possible roles. - * @param[in] handle The handle parameter is interpreted depending on role: - * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. - * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, - * will use the specified transmit power, and include it in the advertising packet headers if - * @ref ble_gap_adv_properties_t::include_tx_power set. - * - For all other roles handle is ignored. - * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). - * - * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. - * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. - * Setting these values on a chip that does not support them will result in undefined behaviour. - * @note The initiator will have the same transmit power as the scanner. - * @note When a connection is created it will inherit the transmit power from the initiator or - * advertiser leading to the connection. - * - * @retval ::NRF_SUCCESS Successfully changed the transmit power. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); - -/**@brief Set GAP Appearance value. - * - * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); - -/**@brief Get GAP Appearance value. - * - * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); - -/**@brief Set GAP Peripheral Preferred Connection Parameters. - * - * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Get GAP Peripheral Preferred Connection Parameters. - * - * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); - -/**@brief Set GAP device name. - * - * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), - * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. - * - * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. - * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. - * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or - * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). - * - * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, - sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); - -/**@brief Get GAP device name. - * - * @note If the device name is longer than the size of the supplied buffer, - * p_len will return the complete device name length, - * and not the number of bytes actually returned in p_dev_name. - * The application may use this information to allocate a suitable buffer size. - * - * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to - * NULL to obtain the complete device name length. - * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. - * - * @retval ::NRF_SUCCESS GAP device name retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); - -/**@brief Initiate the GAP Authentication procedure. - * - * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), - * otherwise in the peripheral role, an SMP Security Request will be sent. - * - * @events - * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be - * generated:} - * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} - * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} - * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} - * @event{@ref BLE_GAP_EVT_KEY_PRESSED} - * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} - * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} - * @event{@ref BLE_GAP_EVT_AUTH_STATUS} - * @event{@ref BLE_GAP_EVT_TIMEOUT} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the - * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. - * In the central role, this pointer may be NULL to reject a Security Request. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - An encryption is already executing or queued. - * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is - * reached. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. - */ -SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, - sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); - -/**@brief Reply with GAP security parameters. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. - * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be - * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. - * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or - * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this - * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. - * Note that the SoftDevice expects the application to provide memory for storing the - * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The - * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application - * upon reception of the - * @ref BLE_GAP_EVT_AUTH_STATUS event. - * - * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - */ -SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, - sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, - ble_gap_sec_keyset_t const *p_sec_keyset)); - -/**@brief Reply with an authentication key. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, - * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. - * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. - * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL - * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, - * then a 16-byte OOB key value in little-endian format. - * - * @retval ::NRF_SUCCESS Authentication key successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, - sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); - -/**@brief Reply with an LE Secure connections DHKey. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dhkey LE Secure Connections DHKey. - * - * @retval ::NRF_SUCCESS DHKey successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - The peer is not authenticated. - * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, - sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); - -/**@brief Notify the peer of a local keypress. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. - * - * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested. - * - Passkey has not been entered. - * - Keypresses have not been enabled by both peers. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. - */ -SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); - -/**@brief Generate a set of OOB data to send to a peer out of band. - * - * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has - * already been established, the one used during connection setup). The application may manually overwrite it with an updated - * value. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. - * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. - * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. - * - * @retval ::NRF_SUCCESS OOB data successfully generated. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, - sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, - ble_gap_lesc_oob_data_t *p_oobd_own)); - -/**@brief Provide the OOB data sent/received out of band. - * - * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. - * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this - * function. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. - * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. - * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. - * Must correspond to @ref ble_gap_sec_params_t::oob flag - * in @ref sd_ble_gap_authenticate in the central role or - * in @ref sd_ble_gap_sec_params_reply in the peripheral role. - * - * @retval ::NRF_SUCCESS OOB data accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested - * - Not expecting LESC OOB data - * - Have not actually exchanged passkeys. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, - sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, - ble_gap_lesc_oob_data_t const *p_oobd_peer)); - -/**@brief Initiate GAP Encryption procedure. - * - * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. - * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and - * retry. - */ -SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, - sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); - -/**@brief Reply with GAP security information. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in - * @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is - * available. - * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. - * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is - * available. - * - * @retval ::NRF_SUCCESS Successfully accepted security information. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. - * - Encryption information provided by the app without being requested. See @ref - * ble_gap_evt_sec_info_request_t::enc_info. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, - sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, - ble_gap_sign_info_t const *p_sign_info)); - -/**@brief Get the current connection security. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Current connection security successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); - -/**@brief Start reporting the received signal strength to the application. - * - * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. - * - * @events - * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is - * dependent on the settings of the threshold_dbm - * and skip_count input parameters.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are - * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. - * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref - * BLE_GAP_EVT_RSSI_CHANGED event. - * - * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); - -/**@brief Stop reporting the received signal strength. - * - * @note An RSSI change detected before the call but not yet received by the application - * may be reported after @ref sd_ble_gap_rssi_stop has been called. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); - -/**@brief Get the received signal strength for the last connection event. - * - * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND - * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. - * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. - * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. - * - * @retval ::NRF_SUCCESS Successfully read the RSSI. - * @retval ::NRF_ERROR_NOT_FOUND No sample is available. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - */ -SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); - -/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). - * - * @note A call to this function will require the application to keep the memory pointed by - * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped - * or when this function is called with another buffer. - * - * @note The scanner will automatically stop in the following cases: - * - @ref sd_ble_gap_scan_stop is called. - * - @ref sd_ble_gap_connect is called. - * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. - * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application - * access received data. The application must call this function to continue scanning, or call @ref - * sd_ble_gap_scan_stop to stop scanning. - * - * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will - * receive more reports from this advertising event. The following reports will include the old and new received data. - * - * @events - * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue - * scanning, this parameter must be NULL. - * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. - * The memory pointed to should be kept alive until the scanning is stopped. - * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. - * If the scanner receives advertising data larger than can be stored in the buffer, - * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status - * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. - * - * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Scanning is already ongoing and p_scan_params was not NULL - * - Scanning is not running and p_scan_params was NULL. - * - The scanner has timed out when this function is called to continue scanning. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. - * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again - */ -SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, - sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); - -/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). - * - * @note The buffer provided in @ref sd_ble_gap_scan_start is released. - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. - * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. - */ -SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); - -/**@brief Create a connection (GAP Link Establishment). - * - * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. - * The scanning procedure will be stopped even if the function returns an error. - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use - * whitelist, then p_peer_addr is ignored. - * @param[in] p_scan_params Pointer to scan parameters structure. - * @param[in] p_conn_params Pointer to desired connection parameters. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. - * - * @retval ::NRF_SUCCESS Successfully initiated connection procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * - Invalid parameter(s) in p_scan_params or p_conn_params. - * - Use of whitelist requested but whitelist has not been set, see @ref - * sd_ble_gap_whitelist_set. - * - Peer address was not present in the device identity list, see @ref - * sd_ble_gap_device_identities_set. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an - * existing locally initiated connect procedure, which must complete before initiating again. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::NRF_ERROR_RESOURCES Either: - * - Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. - * - The event_length parameter associated with conn_cfg_tag is too small to be able to - * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. - * Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_CONNECT, uint32_t, - sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, - ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); - -/**@brief Cancel a connection establishment. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. - * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection - * completed occurred. - */ -SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); - -/**@brief Initiate or respond to a PHY Update Procedure - * - * @details This function is used to initiate or respond to a PHY Update Procedure. It will always - * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. - * If this function is used to initiate a PHY Update procedure and the only option - * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the - * currently active PHYs in the respective directions, the SoftDevice will generate a - * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the - * procedure in the Link Layer. - * - * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, - * then the stack will select PHYs based on the peer's PHY preferences and the local link - * configuration. The PHY Update procedure will for this case result in a PHY combination - * that respects the time constraints configured with @ref sd_ble_cfg_set and the current - * link layer data length. - * - * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. - * - * If the peer does not support the PHY Update Procedure, then the resulting - * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to - * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. - * - * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status - * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or - * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. - * If the peer responds to the PHY Update procedure with invalid parameters, the status - * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. - * If the PHY Update procedure was rejected by the peer for a different reason, the status will - * contain the reason as specified by the peer. - * - * @events - * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} - * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} - * @endmscs - * - * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. - * @param[in] p_gap_phys Pointer to PHY structure. - * - * @retval ::NRF_SUCCESS Successfully requested a PHY Update. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of - * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref - * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. - * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the - * pending procedure to complete and retry. - * - */ -SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); - -/**@brief Initiate or respond to a Data Length Update Procedure. - * - * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of - * p_dl_params, the SoftDevice will choose the highest value supported in current - * configuration and connection parameters. - * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime - * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and - * MaxRxTime will be limited to maximum 2120 us. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update - * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let - * the SoftDevice automatically decide the value for that member. - * Set to NULL to use automatic values for all members. - * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not - * have enough resources or does not support the requested Data Length - * Update parameters. Ignored if NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect - * p_dl_limitation to see which parameter is not supported. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested - * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation - * to see where the limitation is. - * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the - * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. - */ -SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, - sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, - ble_gap_data_length_limitation_t *p_dl_limitation)); - -/**@brief Start the Quality of Service (QoS) channel survey module. - * - * @details The channel survey module provides measurements of the energy levels on - * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT - * events will periodically report the measured energy levels for each channel. - * - * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, - * Radio Timeslot API events and Flash API events. - * - * @note The channel survey module will attempt to do measurements so that the average interval - * between measurements will be interval_us. However due to the channel survey module - * having the lowest priority of all roles and modules, this may not be possible. In that - * case fewer than expected channel survey reports may be given. - * - * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available - * must be set. This is done using @ref sd_ble_cfg_set. - * - * @param[in] interval_us Requested average interval for the measurements and reports. See - * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set - * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel - * survey role will be scheduled at every available opportunity. - * - * @retval ::NRF_SUCCESS The module is successfully started. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the - * allowed range. - * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. - * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. - * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using - * @ref sd_ble_cfg_set. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); - -/**@brief Stop the Quality of Service (QoS) channel survey module. - * - * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this - * function is called. - * - * @retval ::NRF_SUCCESS The module is successfully stopped. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); - -/**@brief Obtain the next connection event counter value. - * - * @details The connection event counter is initialized to zero on the first connection event. The value is incremented - * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, - * Section 4.5.1. - * - * @note The connection event counter obtained through this API will be outdated if this API is called - * at the same time as the connection event counter is incremented. - * - * @note This API will always return the last connection event counter + 1. - * The actual connection event may be multiple connection events later if: - * - Slave latency is enabled and there is no data to transmit or receive. - * - Another role is scheduled with a higher priority at the same time as the next connection event. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. - * - * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, - sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); - -/**@brief Start triggering a given task on connection event start. - * - * @details When enabled, this feature will trigger a PPI task at the start of connection events. - * The application can configure the SoftDevice to trigger every N connection events starting from - * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_params Connection event trigger parameters. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. - * @retval ::NRF_ERROR_INVALID_STATE Either: - * - Trying to start connection event triggering when it is already ongoing. - * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. - * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value - to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, - sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); - -/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GAP_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatt.h b/variants/wio-tracker-wm1110/softdevice/ble_gatt.h deleted file mode 100644 index df0d728fc..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gatt.h +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common - @{ - @brief Common definitions and prototypes for the GATT interfaces. - */ - -#ifndef BLE_GATT_H__ -#define BLE_GATT_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATT_DEFINES Defines - * @{ */ - -/** @brief Default ATT MTU, in bytes. */ -#define BLE_GATT_ATT_MTU_DEFAULT 23 - -/**@brief Invalid Attribute Handle. */ -#define BLE_GATT_HANDLE_INVALID 0x0000 - -/**@brief First Attribute Handle. */ -#define BLE_GATT_HANDLE_START 0x0001 - -/**@brief Last Attribute Handle. */ -#define BLE_GATT_HANDLE_END 0xFFFF - -/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources - * @{ */ -#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ -/** @} */ - -/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations - * @{ */ -#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ -/** @} */ - -/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags - * @{ */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ -/** @} */ - -/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations - * @{ */ -#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ -#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ -/** @} */ - -/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes - * @{ */ -#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ -#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ -#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ -#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ -#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ -#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ -#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ - 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ -#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ -#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ - 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ -#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ -#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ -#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ - 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ - */ -#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ - 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ -#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ - 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ -#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats - * @note Found at - * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml - * @{ */ -#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ -#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ -#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ -#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ -#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ -#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ -#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ -#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces - * @{ - */ -#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ -#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATT_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. - */ -typedef struct { - uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. - The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - @mscs - @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - @endmscs - */ -} ble_gatt_conn_cfg_t; - -/**@brief GATT Characteristic Properties. */ -typedef struct { - /* Standard properties */ - uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ - uint8_t read : 1; /**< Reading the value permitted. */ - uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ - uint8_t write : 1; /**< Writing the value with Write Request permitted. */ - uint8_t notify : 1; /**< Notification of the value permitted. */ - uint8_t indicate : 1; /**< Indications of the value permitted. */ - uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ -} ble_gatt_char_props_t; - -/**@brief GATT Characteristic Extended Properties. */ -typedef struct { - /* Extended properties */ - uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ - uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ -} ble_gatt_char_ext_props_t; - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATT_H__ - -/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gattc.h b/variants/wio-tracker-wm1110/softdevice/ble_gattc.h deleted file mode 100644 index f1df1782c..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gattc.h +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client - @{ - @brief Definitions and prototypes for the GATT Client interface. - */ - -#ifndef BLE_GATTC_H__ -#define BLE_GATTC_H__ - -#include "ble_err.h" -#include "ble_gatt.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GATTC API SVC numbers. */ -enum BLE_GATTC_SVCS { - SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ - SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ - SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ - SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ - SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ - SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ - SD_BLE_GATTC_READ, /**< Generic read. */ - SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ - SD_BLE_GATTC_WRITE, /**< Generic write. */ - SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ - SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ -}; - -/** - * @brief GATT Client Event IDs. - */ -enum BLE_GATTC_EVTS { - BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref - ble_gattc_evt_prim_srvc_disc_rsp_t. */ - BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. - */ - BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref - ble_gattc_evt_char_disc_rsp_t. */ - BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref - ble_gattc_evt_desc_disc_rsp_t. */ - BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref - ble_gattc_evt_attr_info_disc_rsp_t. */ - BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ - BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ - BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref - ble_gattc_evt_char_vals_read_rsp_t. */ - BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ - BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref - sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ - BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref - ble_gattc_evt_exchange_mtu_rsp_t. */ - BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ - BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref - ble_gattc_evt_write_cmd_tx_complete_t. */ -}; - -/**@brief GATTC Option IDs. - * IDs that uniquely identify a GATTC option. - */ -enum BLE_GATTC_OPTS { - BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTC_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC - * @{ */ -#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ -/** @} */ - -/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats - * @{ */ -#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ -#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ -/** @} */ - -/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults - * @{ */ -#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Write without Response that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTC_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for - transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ -} ble_gattc_conn_cfg_t; - -/**@brief Operation Handle Range. */ -typedef struct { - uint16_t start_handle; /**< Start Handle. */ - uint16_t end_handle; /**< End Handle. */ -} ble_gattc_handle_range_t; - -/**@brief GATT service. */ -typedef struct { - ble_uuid_t uuid; /**< Service UUID. */ - ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ -} ble_gattc_service_t; - -/**@brief GATT include. */ -typedef struct { - uint16_t handle; /**< Include Handle. */ - ble_gattc_service_t included_srvc; /**< Handle of the included service. */ -} ble_gattc_include_t; - -/**@brief GATT characteristic. */ -typedef struct { - ble_uuid_t uuid; /**< Characteristic UUID. */ - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - uint8_t char_ext_props : 1; /**< Extended properties present. */ - uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ - uint16_t handle_value; /**< Handle of the Characteristic Value. */ -} ble_gattc_char_t; - -/**@brief GATT descriptor. */ -typedef struct { - uint16_t handle; /**< Descriptor Handle. */ - ble_uuid_t uuid; /**< Descriptor UUID. */ -} ble_gattc_desc_t; - -/**@brief Write Parameters. */ -typedef struct { - uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ - uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ - uint16_t handle; /**< Handle to the attribute to be written. */ - uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ - uint16_t len; /**< Length of data in bytes. */ - uint8_t const *p_value; /**< Pointer to the value data. */ -} ble_gattc_write_params_t; - -/**@brief Attribute Information for 16-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ -} ble_gattc_attr_info16_t; - -/**@brief Attribute Information for 128-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ -} ble_gattc_attr_info128_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Service count. */ - ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_prim_srvc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Include count. */ - ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_rel_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Characteristic count. */ - ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_char_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Descriptor count. */ - ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_desc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Attribute count. */ - uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ - union { - ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - } info; /**< Attribute information union. */ -} ble_gattc_evt_attr_info_disc_rsp_t; - -/**@brief GATT read by UUID handle value pair. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ -} ble_gattc_handle_value_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ -typedef struct { - uint16_t count; /**< Handle-Value Pair Count. */ - uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ - uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref - sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. - @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_val_by_uuid_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint16_t offset; /**< Offset of the attribute data. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ -typedef struct { - uint16_t len; /**< Concatenated Attribute values length. */ - uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder - for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_vals_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ - uint16_t offset; /**< Data offset. */ - uint16_t len; /**< Data length. */ - uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_write_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ -typedef struct { - uint16_t handle; /**< Handle to which the HVx operation applies. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_hvx_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ -typedef struct { - uint16_t server_rx_mtu; /**< Server RX MTU size. */ -} ble_gattc_evt_exchange_mtu_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gattc_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of write without response transmissions completed. */ -} ble_gattc_evt_write_cmd_tx_complete_t; - -/**@brief GATTC event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint16_t - error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ - union { - ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ - ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ - ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ - ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ - ble_gattc_evt_char_val_by_uuid_read_rsp_t - char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ - ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ - ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ - ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ - ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ - ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ - ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ - ble_gattc_evt_write_cmd_tx_complete_t - write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ - } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ -} ble_gattc_evt_t; - -/**@brief UUID discovery option. - * - * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the - * Vendor Specific UUID table. Disabled by default. - * - When disabled, if a procedure initiated by - * @ref sd_ble_gattc_primary_services_discover, - * @ref sd_ble_gattc_relationships_discover, - * @ref sd_ble_gattc_characteristics_discover, - * @ref sd_ble_gattc_descriptors_discover - * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set - * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use - * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding - * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will - * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - * @retval ::NRF_SUCCESS Set successfully. - * - */ -typedef struct { - uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ -} ble_gattc_opt_uuid_disc_t; - -/**@brief Option structure for GATTC options. */ -typedef union { - ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ -} ble_gattc_opt_t; - -/** @} */ - -/** @addtogroup BLE_GATTC_FUNCTIONS Functions - * @{ */ - -/**@brief Initiate or continue a GATT Primary Service Discovery procedure. - * - * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. - * If the last service has not been reached, this function must be called again with an updated start handle value to - * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] start_handle Handle to start searching from. - * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, - sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); - -/**@brief Initiate or continue a GATT Relationship Discovery procedure. - * - * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been - * reached, this must be called again with an updated handle range to continue the search. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, - sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Discovery procedure. - * - * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, - sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. - * - * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not - * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, - sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. - * - * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_uuid Pointer to a Characteristic value UUID to read. - * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, - sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, - ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. - * - * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or - * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read - * the complete value. - * - * @events - * @event{@ref BLE_GATTC_EVT_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute to be read. - * @param[in] offset Offset into the attribute value to be read. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); - -/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. - * - * @details This function initiates a GATT Read Multiple Characteristic Values procedure. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. - * @param[in] handle_count The number of handles in p_handles. - * - * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, - sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); - -/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) - * procedure. - * - * @details This function can perform all write procedures described in GATT. - * - * @note Only one write with response procedure can be ongoing per connection at a time. - * If the application tries to write with response while another write with response procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. - * - * @note The number of Write without Response that can be queued is configured by @ref - * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. - * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without - * response is complete. - * - * @note The application can keep track of the available queue element count for writes without responses by following the - * procedure below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} - * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_write_params A pointer to a write parameters structure. - * - * @retval ::NRF_SUCCESS Successfully started the Write procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event - * and retry. - * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. - * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); - -/**@brief Send a Handle Value Confirmation to the GATT Server. - * - * @mscs - * @mmsc{@ref BLE_GATTC_HVI_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute in the indication. - * - * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); - -/**@brief Discovers information about a range of attributes on a GATT server. - * - * @events - * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} - * @endevents - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range The range of handles to request information about. - * - * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, - sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value, and - * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @events - * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] client_rx_mtu Client RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - used for this connection. - * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent request to the server. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, - sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); - -/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * - * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * @note If the buffer contains different event, behavior is undefined. - * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with - * the next Handle-Value pair in each iteration. If the function returns other than - * @ref NRF_SUCCESS, it will not be changed. - * - To start iteration, initialize the structure to zero. - * - To continue, pass the value from previous iteration. - * - * \code - * ble_gattc_handle_value_t iter; - * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); - * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) - * { - * app_handle = iter.handle; - * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); - * } - * \endcode - * - * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. - * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. - */ -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter); - -/** @} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter) -{ - uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; - uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; - uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; - - if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { - p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; - p_iter->p_value = p_next + sizeof(uint16_t); - return NRF_SUCCESS; - } else { - return NRF_ERROR_NOT_FOUND; - } -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_GATTC_H__ */ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatts.h b/variants/wio-tracker-wm1110/softdevice/ble_gatts.h deleted file mode 100644 index dc94957cd..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gatts.h +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server - @{ - @brief Definitions and prototypes for the GATTS interface. - */ - -#ifndef BLE_GATTS_H__ -#define BLE_GATTS_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief GATTS API SVC numbers. - */ -enum BLE_GATTS_SVCS { - SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ - SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ - SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ - SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ - SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ - SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ - SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ - SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ - SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more - attributes. */ - SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ - SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ - SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ - SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ - SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ -}; - -/** - * @brief GATT Server Event IDs. - */ -enum BLE_GATTS_EVTS { - BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See - @ref ble_gatts_evt_write_t. */ - BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with - @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. - */ - BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref - sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ - BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. - */ - BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event - structure applies. */ - BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with - @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. - */ - BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref - ble_gatts_evt_timeout_t. */ - BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref - ble_gatts_evt_hvn_tx_complete_t. */ -}; - -/**@brief GATTS Configuration IDs. - * - * IDs that uniquely identify a GATTS configuration. - */ -enum BLE_GATTS_CFGS { - BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ - BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTS_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS - * @{ */ -#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ -#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths - * @{ */ -#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ -#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ -/** @} */ - -/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types - * @{ */ -#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ -#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ -#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types - * @{ */ -#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ -#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ -#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ -#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ -/** @} */ - -/** @defgroup BLE_GATTS_OPS GATT Server Operations - * @{ */ -#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ -/** @} */ - -/** @defgroup BLE_GATTS_VLOCS GATT Value Locations - * @{ */ -#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ -#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ -#define BLE_GATTS_VLOC_USER \ - 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ - of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ - There are no alignment requirements for the buffer. */ -/** @} */ - -/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types - * @{ */ -#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ -#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ -#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ -/** @} */ - -/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags - * @{ */ -#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ -#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ -/** @} */ - -/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values - * @{ - */ -#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ - (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size - * @{ - */ -#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ -#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ -/** @} */ - -/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults - * @{ - */ -#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTS_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. - The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ -} ble_gatts_conn_cfg_t; - -/**@brief Attribute metadata. */ -typedef struct { - ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vlen : 1; /**< Variable length attribute. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ - uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not - Write Command). */ -} ble_gatts_attr_md_t; - -/**@brief GATT Attribute. */ -typedef struct { - ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ - ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ - uint16_t init_len; /**< Initial attribute value length in bytes. */ - uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the - attribute value will be left uninitialized. */ - uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ - uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is - selected in the attribute metadata, this will have to point to a buffer that remains valid through the - lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any - other temporary location. The stack may access that memory directly without the application's - knowledge. For writable characteristics, this value must not be a location in flash memory.*/ -} ble_gatts_attr_t; - -/**@brief GATT Attribute Value. */ -typedef struct { - uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ - uint16_t offset; /**< Attribute value offset. */ - uint8_t *p_value; /**< Pointer to where value is stored or will be stored. - If value is stored in user memory, only the attribute length is updated when p_value == NULL. - Set to NULL when reading to obtain the complete length of the attribute value */ -} ble_gatts_value_t; - -/**@brief GATT Characteristic Presentation Format. */ -typedef struct { - uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ - int8_t exponent; /**< Exponent for integer data types. */ - uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ - uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ - uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ -} ble_gatts_char_pf_t; - -/**@brief GATT Characteristic metadata. */ -typedef struct { - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ - uint8_t const * - p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ - uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ - uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ - ble_gatts_char_pf_t const - *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ - ble_gatts_attr_md_t const - *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ -} ble_gatts_char_md_t; - -/**@brief GATT Characteristic Definition Handles. */ -typedef struct { - uint16_t value_handle; /**< Handle to the characteristic value. */ - uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ - uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ - uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ -} ble_gatts_char_handles_t; - -/**@brief GATT HVx parameters. */ -typedef struct { - uint16_t handle; /**< Characteristic Value Handle. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t offset; /**< Offset within the attribute value. */ - uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ - uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ -} ble_gatts_hvx_params_t; - -/**@brief GATT Authorization parameters. */ -typedef struct { - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. - Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, - as the data to be written needs to be stored and later provided by the application. */ - uint16_t offset; /**< Offset of the attribute value being updated. */ - uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ - uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ -} ble_gatts_authorize_params_t; - -/**@brief GATT Read or Write Authorize Reply parameters. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ - ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ - } params; /**< Reply Parameters. */ -} ble_gatts_rw_authorize_reply_params_t; - -/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref - BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ -} ble_gatts_cfg_service_changed_t; - -/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - @ref ble_gatts_attr_md_t::write_perm is out of range. - * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is - * not allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is - * allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. - * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported - */ -typedef struct { - ble_gatts_attr_md_t - perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ -} ble_gatts_cfg_service_changed_cccd_perm_t; - -/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The specified Attribute Table size is too small. - * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. - * - The specified Attribute Table size is not a multiple of 4. - */ -typedef struct { - uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref - BLE_GATTS_ATTR_TAB_SIZE_MIN. */ -} ble_gatts_cfg_attr_tab_size_t; - -/**@brief Config structure for GATTS configurations. */ -typedef union { - ble_gatts_cfg_service_changed_t - service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ - ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ - ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ -} ble_gatts_cfg_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ - uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref - sd_ble_gatts_value_set to finalize the writing operation. */ - uint16_t offset; /**< Offset for the write operation. */ - uint16_t len; /**< Length of the received data. */ - uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gatts_evt_write_t; - -/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint16_t offset; /**< Offset for the read operation. */ -} ble_gatts_evt_read_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ - ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ - } request; /**< Request Parameters. */ -} ble_gatts_evt_rw_authorize_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ -typedef struct { - uint8_t hint; /**< Hint (currently unused). */ -} ble_gatts_evt_sys_attr_missing_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ -} ble_gatts_evt_hvc_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ -typedef struct { - uint16_t client_rx_mtu; /**< Client RX MTU size. */ -} ble_gatts_evt_exchange_mtu_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gatts_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of notification transmissions completed. */ -} ble_gatts_evt_hvn_tx_complete_t; - -/**@brief GATTS event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ - union { - ble_gatts_evt_write_t write; /**< Write Event Parameters. */ - ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ - ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ - ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ - ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ - ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ - ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_gatts_evt_t; - -/** @} */ - -/** @addtogroup BLE_GATTS_FUNCTIONS Functions - * @{ */ - -/**@brief Add a service declaration to the Attribute Table. - * - * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to - * add a secondary service declaration that is not referenced by another service later in the Attribute Table. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. - * @param[in] p_uuid Pointer to service UUID. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a service declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); - -/**@brief Add an include declaration to the Attribute Table. - * - * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note The included service must already be present in the Attribute Table prior to this call. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID - * is used, it will be placed sequentially. - * @param[in] inc_srvc_handle Handle of the included service. - * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added an include declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - */ -SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, - sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); - -/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations - * to the Attribute Table. - * - * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and - * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format - * values. - * - * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic - * permissions. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_char_md Characteristic metadata. - * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. - * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a characteristic. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, - sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, - ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); - -/**@brief Add a descriptor to the Attribute Table. - * - * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is - * supported at this time). - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_attr Pointer to the attribute structure. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a descriptor. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, - sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); - -/**@brief Set the value of a given attribute. - * - * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully set the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - */ -SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, - sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Get the value of a given attribute. - * - * @note If the attribute value is longer than the size of the supplied buffer, - * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), - * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. - * The application may use this information to allocate a suitable buffer size. - * - * @note When retrieving system attribute values with this function, the connection handle - * may refer to an already disconnected connection. Refer to the documentation of - * @ref sd_ble_gatts_sys_attr_get for further information. - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - */ -SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, - sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Notify or Indicate an attribute value. - * - * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant - * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before - * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single - * API call. - * - * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during - * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, - * @ref NRF_ERROR_BUSY, - * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. - * The caller can check whether the value has been updated by looking at the contents of *(@ref - * ble_gatts_hvx_params_t::p_len). - * - * @note Only one indication procedure can be ongoing per connection at a time. - * If the application tries to indicate an attribute value while another indication procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. - * - * @note The number of Handle Value Notifications that can be queued is configured by @ref - * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. - * - * @note The application can keep track of the available queue element count for notifications by following the procedure - * below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} - * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_HVN_MSC} - * @mmsc{@ref BLE_GATTS_HVI_MSC} - * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data - * contains a non-NULL pointer the attribute value will be updated with the contents - * pointed by it before sending the notification or indication. If the attribute value - * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to - * contain the number of actual bytes written, else it will be set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute - * value. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application - * are available to notify and indicate. - * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and - * indicated. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions - * of the CCCD associated with this characteristic. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC - * event and retry. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. - * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); - -/**@brief Indicate the Service Changed attribute value. - * - * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute - * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will - * be issued. - * - * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. - * - * @events - * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_SC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] start_handle Start of affected attribute handle range. - * @param[in] end_handle End of affected attribute handle range. - * - * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref - * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the - * application. - * @retval ::NRF_ERROR_BUSY Procedure already in progress. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, - sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); - -/**@brief Respond to a Read/Write authorization request. - * - * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. - * - * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond - * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update - * is set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute - * Table updated. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, - * handle supplied does not match requested handle, - * or invalid data to be written provided by the application. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, - sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, - ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); - -/**@brief Update persistent system attribute information. - * - * @details Supply information about persistent system attributes to the stack, - * previously obtained using @ref sd_ble_gatts_sys_attr_get. - * This call is only allowed for active connections, and is usually - * made immediately after a connection is established with an known bonded device, - * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. - * - * p_sysattrs may point directly to the application's stored copy of the system attributes - * obtained using @ref sd_ble_gatts_sys_attr_get. - * If the pointer is NULL, the system attribute info is initialized, assuming that - * the application does not have any previously saved system attribute data for this device. - * - * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. - * - * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may - * have been completed only partially. This means that the state of the attribute table is undefined, and the application should - * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be modified. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be modified. - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. - * @param[in] len Size of data pointed by p_sys_attr_data, in octets. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully set the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref - * sd_ble_gatts_sys_attr_get. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, - sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); - -/**@brief Retrieve persistent system attribute information from the stack. - * - * @details This call is used to retrieve information about values to be stored persistently by the application - * during the lifetime of a connection or after it has been terminated. When a new connection is established with the - * same bonded device, the system attribute information retrieved with this function should be restored using using @ref - * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The - * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API - * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the - * system attributes may be written to at any time by the peer during a connection's lifetime. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be returned. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be returned. - * - * @mscs - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle of the recently terminated connection. - * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The - * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. - * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual - * length of system attribute data. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. - * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, - sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); - -/**@brief Retrieve the first valid user attribute handle. - * - * @param[out] p_handle Pointer to an integer where the handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully retrieved the handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); - -/**@brief Retrieve the attribute UUID and/or metadata. - * - * @param[in] handle Attribute handle - * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. - * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. - * - * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. - * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. - */ -SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); - -/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. - * - * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and - * - The Server RX MTU value. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @mscs - * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] server_rx_mtu Server RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - * used for this connection. - * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent response to the client. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATTS_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_hci.h b/variants/wio-tracker-wm1110/softdevice/ble_hci.h deleted file mode 100644 index 27f85d52e..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_hci.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ -*/ - -#ifndef BLE_HCI_H__ -#define BLE_HCI_H__ -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes - * @{ */ - -#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ -/*0x03 Hardware Failure -0x04 Page Timeout -*/ -#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ -#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ -#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ -#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ -/*0x09 Connection Limit Exceeded -0x0A Synchronous Connection Limit To A Device Exceeded -0x0B ACL Connection Already Exists*/ -#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ -/*0x0D Connection Rejected due to Limited Resources -0x0E Connection Rejected Due To Security Reasons -0x0F Connection Rejected due to Unacceptable BD_ADDR -0x10 Connection Accept Timeout Exceeded -0x11 Unsupported Feature or Parameter Value*/ -#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ -#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ - 0x14 /**< Remote Device Terminated Connection due to low \ - resources.*/ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ -#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ -/* -0x17 Repeated Attempts -0x18 Pairing Not Allowed -0x19 Unknown LMP PDU -*/ -#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ -/* -0x1B SCO Offset Rejected -0x1C SCO Interval Rejected -0x1D SCO Air Mode Rejected*/ -#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ -#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ -/*0x20 Unsupported LMP Parameter Value -0x21 Role Change Not Allowed -*/ -#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ -#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ -#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ -/*0x25 Encryption Mode Not Acceptable -0x26 Link Key Can Not be Changed -0x27 Requested QoS Not Supported -*/ -#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ -#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ -#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ -/* -0x2B Reserved -0x2C QoS Unacceptable Parameter -0x2D QoS Rejected -0x2E Channel Classification Not Supported -0x2F Insufficient Security -*/ -#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ -/* -0x31 Reserved -0x32 Role Switch Pending -0x33 Reserved -0x34 Reserved Slot Violation -0x35 Role Switch Failed -0x36 Extended Inquiry Response Too Large -0x37 Secure Simple Pairing Not Supported By Host. -0x38 Host Busy - Pairing -0x39 Connection Rejected due to No Suitable Channel Found*/ -#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ -#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ -#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ -#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ -#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_HCI_H__ - -/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h b/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h deleted file mode 100644 index 5f4bd277d..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) - @{ - @brief Definitions and prototypes for the L2CAP interface. - */ - -#ifndef BLE_L2CAP_H__ -#define BLE_L2CAP_H__ - -#include "ble_err.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology - * @{ - * @details - * - * L2CAP SDU - * - A data unit that the application can send/receive to/from a peer. - * - * L2CAP PDU - * - A data unit that is exchanged between local and remote L2CAP entities. - * It consists of L2CAP protocol control information and payload fields. - * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. - * - * L2CAP MTU - * - The maximum length of an L2CAP SDU. - * - * L2CAP MPS - * - The maximum length of an L2CAP PDU payload field. - * - * Credits - * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. - * @} */ - -/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief L2CAP API SVC numbers. */ -enum BLE_L2CAP_SVCS { - SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ - SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ - SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ -}; - -/**@brief L2CAP Event IDs. */ -enum BLE_L2CAP_EVTS { - BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. - \n Reply with @ref sd_ble_l2cap_ch_setup. - \n See @ref ble_l2cap_evt_ch_setup_request_t. */ - BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. - \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ - BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. - \n See @ref ble_l2cap_evt_ch_setup_t. */ - BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. - \n No additional event structure applies. */ - BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. - \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ - BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. - \n See @ref ble_l2cap_evt_ch_credit_t. */ - BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. - \n See @ref ble_l2cap_evt_ch_rx_t. */ - BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. - \n See @ref ble_l2cap_evt_ch_tx_t. */ -}; - -/** @} */ - -/**@addtogroup BLE_L2CAP_DEFINES Defines - * @{ */ - -/**@brief Maximum number of L2CAP channels per connection. */ -#define BLE_L2CAP_CH_COUNT_MAX (64) - -/**@brief Minimum L2CAP MTU, in bytes. */ -#define BLE_L2CAP_MTU_MIN (23) - -/**@brief Minimum L2CAP MPS, in bytes. */ -#define BLE_L2CAP_MPS_MIN (23) - -/**@brief Invalid CID. */ -#define BLE_L2CAP_CID_INVALID (0x0000) - -/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ -#define BLE_L2CAP_CREDITS_DEFAULT (1) - -/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources - * @{ */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ - /** @} */ - -/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes - * @{ */ -#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ -#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ -#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ -#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ -#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ -#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ -#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ - (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ -#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ -/** @} */ - -/** @} */ - -/**@addtogroup BLE_L2CAP_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note These parameters are set per connection, so all L2CAP channels created on this connection - * will have the same parameters. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. - * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. - */ -typedef struct { - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to receive on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to transmit on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per - L2CAP channel. The minimum value is one. */ - uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission - per L2CAP channel. The minimum value is one. */ - uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection - with this configuration. The default value is zero, the maximum - value is @ref BLE_L2CAP_CH_COUNT_MAX. - @note if this parameter is set to zero, all other parameters in - @ref ble_l2cap_conn_cfg_t are ignored. */ -} ble_l2cap_conn_cfg_t; - -/**@brief L2CAP channel RX parameters. */ -typedef struct { - uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to - receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be - able to receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. - - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ - ble_data_t sdu_buf; /**< SDU data buffer for reception. - - If @ref ble_data_t::p_data is non-NULL, initial credits are - issued to the peer. - - If @ref ble_data_t::p_data is NULL, no initial credits are - issued to the peer. */ -} ble_l2cap_ch_rx_params_t; - -/**@brief L2CAP channel setup parameters. */ -typedef struct { - ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting - setup of an L2CAP channel, ignored otherwise. */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. - Used when replying to a setup request of an L2CAP - channel, ignored otherwise. */ -} ble_l2cap_ch_setup_params_t; - -/**@brief L2CAP channel TX parameters. */ -typedef struct { - uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to - transmit on this L2CAP channel. */ - uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is - able to receive on this L2CAP channel. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able - to transmit on this L2CAP channel. This is effective tx_mps, - selected by the SoftDevice as - MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ - uint16_t credits; /**< Initial credits given by the peer. */ -} ble_l2cap_ch_tx_params_t; - -/**@brief L2CAP Channel Setup Request event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ -} ble_l2cap_evt_ch_setup_request_t; - -/**@brief L2CAP Channel Setup Refused event. */ -typedef struct { - uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ -} ble_l2cap_evt_ch_setup_refused_t; - -/**@brief L2CAP Channel Setup Completed event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ -} ble_l2cap_evt_ch_setup_t; - -/**@brief L2CAP Channel SDU Data Buffer Released event. */ -typedef struct { - ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice - returns SDU data buffers supplied by the application, which have - not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or - @ref BLE_L2CAP_EVT_CH_TX event. */ -} ble_l2cap_evt_ch_sdu_buf_released_t; - -/**@brief L2CAP Channel Credit received event. */ -typedef struct { - uint16_t credits; /**< Additional credits given by the peer. */ -} ble_l2cap_evt_ch_credit_t; - -/**@brief L2CAP Channel received SDU event. */ -typedef struct { - uint16_t sdu_len; /**< Total SDU length, in bytes. */ - ble_data_t sdu_buf; /**< SDU data buffer. - @note If there is not enough space in the buffer - (sdu_buf.len < sdu_len) then the rest of the SDU will be - silently discarded by the SoftDevice. */ -} ble_l2cap_evt_ch_rx_t; - -/**@brief L2CAP Channel transmitted SDU event. */ -typedef struct { - ble_data_t sdu_buf; /**< SDU data buffer. */ -} ble_l2cap_evt_ch_tx_t; - -/**@brief L2CAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occured. */ - uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or - @ref BLE_L2CAP_CID_INVALID if not present. */ - union { - ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ - ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ - ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ - ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ - ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ - ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ - ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_l2cap_evt_t; - -/** @} */ - -/**@addtogroup BLE_L2CAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set up an L2CAP channel. - * - * @details This function is used to: - * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. - * - Reply to a setup request of an L2CAP channel (if called in response to a - * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection - * Response packet to a peer. - * - * @note A call to this function will require the application to keep the SDU data buffer alive - * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or - * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} - * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: - * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP - * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * event when replying to a setup request of an L2CAP channel. - * - As output: local_cid for this channel. - * @param[in] p_params L2CAP channel parameters. - * - * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, - * see @ref ble_l2cap_conn_cfg_t::ch_count. - */ -SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, - sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); - -/**@brief Release an L2CAP channel. - * - * @details This sends a Disconnection Request packet to a peer. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * - * @retval ::NRF_SUCCESS Successfully queued request for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); - -/**@brief Receive an SDU on an L2CAP channel. - * - * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers - * for reception per L2CAP channel. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Buffer accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a - * @ref BLE_L2CAP_EVT_CH_RX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Transmit an SDU on an L2CAP channel. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for - * transmission per L2CAP channel. - * - * @note The application can keep track of the available credits for transmission by following - * the procedure below: - * - Store initial credits given by the peer in a variable. - * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Decrement the variable, which stores the currently available credits, by - * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns - * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Increment the variable, which stores the currently available credits, by additional - * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than - * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in - * @ref BLE_L2CAP_EVT_CH_SETUP event. - * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a - * @ref BLE_L2CAP_EVT_CH_TX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Advanced SDU reception flow control. - * - * @details Adjust the way the SoftDevice issues credits to the peer. - * This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set - * the value that will be used for newly created channels. - * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every - * time it starts using a new reception buffer. - * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will - * use if this function is not called. - * - If set to zero, the SoftDevice will stop issuing credits for new reception - * buffers the application provides or has provided. SDU reception that is - * currently ongoing will be allowed to complete. - * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be - * written by the SoftDevice with the number of credits that is or will be - * available to the peer. If the value written by the SoftDevice is 0 when - * credits parameter was set to 0, the peer will not be able to send more - * data until more credits are provided by calling this function again with - * credits > 0. This parameter is ignored when local_cid is set to - * @ref BLE_L2CAP_CID_INVALID. - * - * @note Application should take care when setting number of credits higher than default value. In - * this case the application must make sure that the SoftDevice always has reception buffers - * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have - * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic - * on the connection handle may be stalled until the SoftDevice again has an available - * reception buffer. This applies even if the application has used this call to set the - * credits back to default, or zero. - * - * @retval ::NRF_SUCCESS Flow control parameters accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, - sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_L2CAP_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_ranges.h b/variants/wio-tracker-wm1110/softdevice/ble_ranges.h deleted file mode 100644 index 2768e4996..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_ranges.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_ranges Module specific SVC, event and option number subranges - @{ - - @brief Definition of SVC, event and option number subranges for each API module. - - @note - SVCs, event and option numbers are split into subranges for each API module. - Each module receives its entire allocated range of SVC calls, whether implemented or not, - but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. - - Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, - rather than the last SVC function call actually defined and implemented. - - Specific SVC, event and option values are defined in each module's ble_.h file, - which defines names of each individual SVC code based on the range start value. -*/ - -#ifndef BLE_RANGES_H__ -#define BLE_RANGES_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ -#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ - -#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ -#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ - -#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ -#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ - -#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ -#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ - -#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ -#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ - -#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ - -#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ -#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ - -#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ -#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ - -#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ -#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ - -#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ -#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ - -#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ -#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ - -#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ - -#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ -#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ - -#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ -#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ - -#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ -#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ - -#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ -#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ - -#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ -#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ - -#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ -#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ - -#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ - -#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ -#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ - -#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ -#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ - -#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ -#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ - -#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ -#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ - -#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ -#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ - -#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ -#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ - -#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ -#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_RANGES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_types.h b/variants/wio-tracker-wm1110/softdevice/ble_types.h deleted file mode 100644 index db3656cfd..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_types.h +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_types Common types and macro definitions - @{ - - @brief Common types and macro definitions for the BLE SoftDevice. - */ - -#ifndef BLE_TYPES_H__ -#define BLE_TYPES_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_TYPES_DEFINES Defines - * @{ */ - -/** @defgroup BLE_CONN_HANDLES BLE Connection Handles - * @{ */ -#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ -#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ -/** @} */ - -/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs - * @{ */ -/* Generic UUIDs, applicable to all services */ -#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ -#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ -#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ -#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ -#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ -#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ -/* GATT specific UUIDs */ -#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ -#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ -/* GAP specific UUIDs */ -#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ -#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ -/** @} */ - -/** @defgroup BLE_UUID_TYPES Types of UUID - * @{ */ -#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ -#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ -#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ -/** @} */ - -/** @defgroup BLE_APPEARANCES Bluetooth Appearance values - * @note Retrieved from - * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml - * @{ */ -#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ -#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ -#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ -#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ -#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ -#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ -#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ -#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ -#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ -#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ -#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ -#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ -#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ -#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ -#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ -#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ -#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ -#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ -#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ -#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ -#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ -#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ -#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ -#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ -#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ -#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ -#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ -#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ -#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ -#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ -#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ -#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ -#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ -#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ -#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ -#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ - 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ - 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ -/** @} */ - -/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ -#define BLE_UUID_BLE_ASSIGN(instance, value) \ - do { \ - instance.type = BLE_UUID_TYPE_BLE; \ - instance.uuid = value; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ -#define BLE_UUID_COPY_PTR(dst, src) \ - do { \ - (dst)->type = (src)->type; \ - (dst)->uuid = (src)->uuid; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ -#define BLE_UUID_COPY_INST(dst, src) \ - do { \ - (dst).type = (src).type; \ - (dst).uuid = (src).uuid; \ - } while (0) - -/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) - -/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) - -/** @} */ - -/** @addtogroup BLE_TYPES_STRUCTURES Structures - * @{ */ - -/** @brief 128 bit UUID values. */ -typedef struct { - uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ -} ble_uuid128_t; - -/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ -typedef struct { - uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ - uint8_t - type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ -} ble_uuid_t; - -/**@brief Data structure. */ -typedef struct { - uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ - uint16_t len; /**< Length of the data buffer, in bytes. */ -} ble_data_t; - -/** @} */ -#ifdef __cplusplus -} -#endif - -#endif /* BLE_TYPES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h deleted file mode 100644 index 4e0bd752a..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_mbr_api Master Boot Record API - @{ - - @brief APIs for updating SoftDevice and BootLoader - -*/ - -#ifndef NRF_MBR_H__ -#define NRF_MBR_H__ - -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_MBR_DEFINES Defines - * @{ */ - -/**@brief MBR SVC Base number. */ -#define MBR_SVC_BASE (0x18) - -/**@brief Page size in words. */ -#define MBR_PAGE_SIZE_IN_WORDS (1024) - -/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. -This is the offset where the first byte of the SoftDevice hex file is written. */ -#define MBR_SIZE (0x1000) - -/** @brief Location (in the flash memory) of the bootloader address. */ -#define MBR_BOOTLOADER_ADDR (0xFF8) - -/** @brief Location (in UICR) of the bootloader address. */ -#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) - -/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ -#define MBR_PARAM_PAGE_ADDR (0xFFC) - -/** @brief Location (in UICR) of the address of the MBR parameter page. */ -#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) - -/** @} */ - -/** @addtogroup NRF_MBR_ENUMS Enumerations - * @{ */ - -/**@brief nRF Master Boot Record API SVC numbers. */ -enum NRF_MBR_SVCS { - SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ -}; - -/**@brief Possible values for ::sd_mbr_command_t.command */ -enum NRF_MBR_COMMANDS { - SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ - SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ - SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any - parameters in ::sd_mbr_command_t params.*/ - SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ - SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see - ::sd_mbr_command_vector_table_base_set_t*/ - SD_MBR_COMMAND_RESERVED, - SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see - ::sd_mbr_command_irq_forward_address_set_t*/ -}; - -/** @} */ - -/** @addtogroup NRF_MBR_TYPES Types - * @{ */ - -/**@brief This command copies part of a new SoftDevice - * - * The destination area is erased before copying. - * If dst is in the middle of a flash page, that whole flash page will be erased. - * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. - * - * The user of this function is responsible for setting the BPROT registers. - * - * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. - * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. - */ -typedef struct { - uint32_t *src; /**< Pointer to the source of data to be copied.*/ - uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ - uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ -} sd_mbr_command_copy_sd_t; - -/**@brief This command works like memcmp, but takes the length in words. - * - * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. - * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. - */ -typedef struct { - uint32_t *ptr1; /**< Pointer to block of memory. */ - uint32_t *ptr2; /**< Pointer to block of memory. */ - uint32_t len; /**< Number of 32 bit words to compare.*/ -} sd_mbr_command_compare_t; - -/**@brief This command copies a new BootLoader. - * - * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to - * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * The bootloader destination is erased by this function. - * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is - * not intended to be written. - * - * On success, this function will not return. It will start the new bootloader from reset-vector as normal. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. - * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ - uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ -} sd_mbr_command_copy_bl_t; - -/**@brief Change the address the MBR starts after a reset - * - * Once this function has been called, this address is where the MBR will start to forward - * interrupts to after a reset. - * - * To restore default forwarding, this function should be called with @ref address set to 0. If a - * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will - * be forwarded to the SoftDevice. - * - * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or - * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * On success, this function will not return. It will reset the device. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_vector_table_base_set_t; - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR - * - * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not - * change where the MBR starts after reset. - * - * @retval ::NRF_SUCCESS - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_irq_forward_address_set_t; - -/**@brief Input structure containing data used when calling ::sd_mbr_command - * - * Depending on what command value that is set, the corresponding params value type must also be - * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command - * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. - */ -typedef struct { - uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ - union { - sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ - sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ - sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ - sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ - sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ - } params; /**< Command parameters. */ -} sd_mbr_command_t; - -/** @} */ - -/** @addtogroup NRF_MBR_FUNCTIONS Functions - * @{ */ - -/**@brief Issue Master Boot Record commands - * - * Commands used when updating a SoftDevice and bootloader. - * - * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires - * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash - * page. The location of the flash page should be provided by the application in either - * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR - * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to - * store the command before reset. When an address is specified, the page it refers to must not be - * used by the application. If no address is provided by the application, i.e. both - * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use - * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. - * - * @param[in] param Pointer to a struct describing the command. - * - * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, - * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, - * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t - * - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided - * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. - */ -SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_MBR_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error.h b/variants/wio-tracker-wm1110/softdevice/nrf_error.h deleted file mode 100644 index fb2831e19..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_error.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_error SoftDevice Global Error Codes - @{ - - @brief Global Error definitions -*/ - -/* Header guard */ -#ifndef NRF_ERROR_H__ -#define NRF_ERROR_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions - * @{ */ -#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base -#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base -#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base -#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base -/** @} */ - -#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command -#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing -#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled -#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error -#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation -#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found -#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported -#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter -#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state -#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length -#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags -#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data -#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size -#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out -#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer -#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation -#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address -#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy -#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. -#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h deleted file mode 100644 index 2fd621057..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_sdm_api - @{ - @defgroup nrf_sdm_error SoftDevice Manager Error Codes - @{ - - @brief Error definitions for the SDM API -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SDM_H__ -#define NRF_ERROR_SDM_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. -#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ - (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having - ///< enabled SoftDevice interrupts). -#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ - (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SDM_H__ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h deleted file mode 100644 index cbd0ba8ac..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_soc_api - @{ - @defgroup nrf_soc_error SoC Library Error Codes - @{ - - @brief Error definitions for the SoC library - -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SOC_H__ -#define NRF_ERROR_SOC_H__ - -#include "nrf_error.h" -#ifdef __cplusplus -extern "C" { -#endif - -/* Mutex Errors */ -#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken - -/* NVIC errors */ -#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available -#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed -#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return - -/* Power errors */ -#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown -#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown -#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return - -/* Rand errors */ -#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values - -/* PPI errors */ -#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel -#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SOC_H__ -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h b/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h deleted file mode 100644 index d4ab204d9..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_nvic_api SoftDevice NVIC API - * @{ - * - * @note In order to use this module, the following code has to be added to a .c file: - * \code - * nrf_nvic_state_t nrf_nvic_state = {0}; - * \endcode - * - * @note Definitions and declarations starting with __ (double underscore) in this header file are - * not intended for direct use by the application. - * - * @brief APIs for the accessing NVIC when using a SoftDevice. - * - */ - -#ifndef NRF_NVIC_H__ -#define NRF_NVIC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_NVIC_DEFINES Defines - * @{ */ - -/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions - * @{ */ - -#define __NRF_NVIC_NVMC_IRQn \ - (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ - number in the MDK. */ - -#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ - -/**@brief Interrupt priority levels used by the SoftDevice. */ -#define __NRF_NVIC_SD_IRQ_PRIOS \ - ((uint8_t)((1U << 0) /**< Priority level high .*/ \ - | (1U << 1) /**< Priority level medium. */ \ - | (1U << 4) /**< Priority level low. */ \ - )) - -/**@brief Interrupt priority levels available to the application. */ -#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ -#define __NRF_NVIC_SD_IRQS_0 \ - ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ - (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ - (1U << (uint32_t)SWI5_IRQn))) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ -#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) - -/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ -#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) - -/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ -#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) - -/**@} */ - -/**@} */ - -/**@addtogroup NRF_NVIC_VARIABLES Variables - * @{ */ - -/**@brief Type representing the state struct for the SoftDevice NVIC module. */ -typedef struct { - uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ - uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ -} nrf_nvic_state_t; - -/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an - * application source file. */ -extern nrf_nvic_state_t nrf_nvic_state; - -/**@} */ - -/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions - * @{ */ - -/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. - * - * @retval The value of PRIMASK prior to disabling the interrupts. - */ -__STATIC_INLINE int __sd_nvic_irq_disable(void); - -/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. - */ -__STATIC_INLINE void __sd_nvic_irq_enable(void); - -/**@brief Checks if IRQn is available to application - * @param[in] IRQn IRQ to check - * - * @retval 1 (true) if the IRQ to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); - -/**@brief Checks if priority is available to application - * @param[in] priority priority to check - * - * @retval 1 (true) if the priority to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); - -/**@} */ - -/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions - * @{ */ - -/**@brief Enable External Interrupt. - * @note Corresponds to NVIC_EnableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was enabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); - -/**@brief Disable External Interrupt. - * @note Corresponds to NVIC_DisableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was disabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); - -/**@brief Get Pending Interrupt. - * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. - * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. - * - * @retval ::NRF_SUCCESS The interrupt is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); - -/**@brief Set Pending Interrupt. - * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt is set pending. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); - -/**@brief Clear Pending Interrupt. - * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); - -/**@brief Set Interrupt Priority. - * @note Corresponds to NVIC_SetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * @pre Priority is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. - * @param[in] priority A valid IRQ priority for use by the application. - * - * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); - -/**@brief Get Interrupt Priority. - * @note Corresponds to NVIC_GetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. - * @param[out] p_priority Return value from NVIC_GetPriority. - * - * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); - -/**@brief System Reset. - * @note Corresponds to NVIC_SystemReset in CMSIS. - * - * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN - */ -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); - -/**@brief Enter critical region. - * - * @post Application interrupts will be disabled. - * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each - * execution context - * @sa sd_nvic_critical_region_exit - * - * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); - -/**@brief Exit critical region. - * - * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. - * @post If not in a nested critical region, the application interrupts will restored to the state before - * ::sd_nvic_critical_region_enter was called. - * - * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa - * sd_nvic_critical_region_enter. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); - -/**@} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE int __sd_nvic_irq_disable(void) -{ - int pm = __get_PRIMASK(); - __disable_irq(); - return pm; -} - -__STATIC_INLINE void __sd_nvic_irq_enable(void) -{ - __enable_irq(); -} - -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) -{ - if (IRQn < 32) { - return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; - } else if (IRQn < 64) { - return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; - } else { - return 1; - } -} - -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) -{ - if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { - return 0; - } - return 1; -} - -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= - (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); - } else { - NVIC_EnableIRQ(IRQn); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); - } else { - NVIC_DisableIRQ(IRQn); - } - - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_pending_irq = NVIC_GetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_SetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_ClearPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (!__sd_nvic_is_app_accessible_priority(priority)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - NVIC_SetPriority(IRQn, (uint32_t)priority); - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) -{ - NVIC_SystemReset(); - return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) -{ - int was_masked = __sd_nvic_irq_disable(); - if (!nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__cr_flag = 1; - nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); - NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; - nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); - NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; - *p_is_nested_critical_region = 0; - } else { - *p_is_nested_critical_region = 1; - } - if (!was_masked) { - __sd_nvic_irq_enable(); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) -{ - if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { - int was_masked = __sd_nvic_irq_disable(); - NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; - NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; - nrf_nvic_state.__cr_flag = 0; - if (!was_masked) { - __sd_nvic_irq_enable(); - } - } - - return NRF_SUCCESS; -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif - -#endif // NRF_NVIC_H__ - -/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h deleted file mode 100644 index 2786a86a4..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_sdm_api SoftDevice Manager API - @{ - - @brief APIs for SoftDevice management. - -*/ - -#ifndef NRF_SDM_H__ -#define NRF_SDM_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_sdm.h" -#include "nrf_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ -#ifdef NRFSOC_DOXYGEN -/// Declared in nrf_mbr.h -#define MBR_SIZE 0 -#warning test -#endif - -/** @brief The major version for the SoftDevice binary distributed with this header file. */ -#define SD_MAJOR_VERSION (7) - -/** @brief The minor version for the SoftDevice binary distributed with this header file. */ -#define SD_MINOR_VERSION (3) - -/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ -#define SD_BUGFIX_VERSION (0) - -/** @brief The SoftDevice variant of this firmware. */ -#define SD_VARIANT_ID 140 - -/** @brief The full version number for the SoftDevice binary this header file was distributed - * with, as a decimal number in the form Mmmmbbb, where: - * - M is major version (one or more digits) - * - mmm is minor version (three digits) - * - bbb is bugfix version (three digits). */ -#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) - -/** @brief SoftDevice Manager SVC Base number. */ -#define SDM_SVC_BASE 0x10 - -/** @brief SoftDevice unique string size in bytes. */ -#define SD_UNIQUE_STR_SIZE 20 - -/** @brief Invalid info field. Returned when an info field does not exist. */ -#define SDM_INFO_FIELD_INVALID (0) - -/** @brief Defines the SoftDevice Information Structure location (address) as an offset from -the start of the SoftDevice (without MBR)*/ -#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) - -/** @brief Defines the absolute SoftDevice Information Structure location (address) when the - * SoftDevice is installed just above the MBR (the usual case). */ -#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) - -/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the - * SoftDevice base address. The size value is of type uint8_t. */ -#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) - -/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. - * The size value is of type uint32_t. */ -#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) - -/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value - * is of type uint16_t. */ -#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) - -/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID - * is of type uint32_t. */ -#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) - -/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in - * the same format as @ref SD_VERSION, stored as an uint32_t. */ -#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) - -/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. - * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. - */ -#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) - -/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value - * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is - * installed just above the MBR (the usual case). */ -#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base - * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above - * the MBR (the usual case). */ -#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) - -/** @brief Defines the amount of flash that is used by the SoftDevice. - * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed - * just above the MBR (the usual case). - */ -#define SD_FLASH_SIZE 0x26000 - -/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual - * case). */ -#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the - * usual case). */ -#define SD_ID_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_VERSION_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges - * @{ */ -#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ -#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ -/**@} */ - -/**@defgroup NRF_FAULT_IDS Fault ID types - * @{ */ -#define NRF_FAULT_ID_SD_ASSERT \ - (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ -#define NRF_FAULT_ID_APP_MEMACC \ - (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ - in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ - register violation the info parameter will contain the sub-region number of \ - PREGION[0], on whose address range the disallowed write access caused the \ - memory access fault. */ -/**@} */ - -/** @} */ - -/** @addtogroup NRF_SDM_ENUMS Enumerations - * @{ */ - -/**@brief nRF SoftDevice Manager API SVC numbers. */ -enum NRF_SD_SVCS { - SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ - SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ - SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ - SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ - SVC_SDM_LAST /**< Placeholder for last SDM SVC */ -}; - -/** @} */ - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ - -/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy - * @{ */ - -#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ -#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ -#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ -#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ -#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ -#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ -#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ -#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ -#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ -#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ -#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ -#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ - -/** @} */ - -/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources - * @{ */ - -#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ -#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ -#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ - -/** @} */ - -/** @} */ - -/** @addtogroup NRF_SDM_TYPES Types - * @{ */ - -/**@brief Type representing LFCLK oscillator source. */ -typedef struct { - uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ - uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second - units (nRF52: 1-32). - @note To avoid excessive clock drift, 0.5 degrees Celsius is the - maximum temperature change allowed in one calibration timer - interval. The interval should be selected to ensure this. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ - uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration - intervals) the RC oscillator shall be calibrated if the temperature - hasn't changed. - 0: Always calibrate even if the temperature hasn't changed. - 1: Only calibrate if the temperature has changed (legacy - nRF51 only). - 2-33: Check the temperature and only calibrate if it has changed, - however calibration will take place every rc_temp_ctiv - intervals in any case. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. - - @note For nRF52, the application must ensure calibration at least once - every 8 seconds to ensure +/-500 ppm clock stability. The - recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is - rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at - least once every 8 seconds and for temperature changes of 0.5 - degrees Celsius every 4 seconds. See the Product Specification - for the nRF52 device being used for more information.*/ - uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing - windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ -} nrf_clock_lf_cfg_t; - -/**@brief Fault Handler type. - * - * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. - * The protocol stack will be in an undefined state when this happens and the only way to recover will be to - * perform a reset, using e.g. CMSIS NVIC_SystemReset(). - * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). - * - * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. - * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may - * continously transmit packets. - * - * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. - * - * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. - * @param[in] pc The program counter of the instruction that triggered the fault. - * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. - * - * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time - * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the - * one that triggered the fault. - */ -typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); - -/** @} */ - -/** @addtogroup NRF_SDM_FUNCTIONS Functions - * @{ */ - -/**@brief Enables the SoftDevice and by extension the protocol stack. - * - * @note Some care must be taken if a low frequency clock source is already running when calling this function: - * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new - * clock source will be started. - * - * @note This function has no effect when returning with an error. - * - * @post If return code is ::NRF_SUCCESS - * - SoC library and protocol stack APIs are made available. - * - A portion of RAM will be unavailable (see relevant SDS documentation). - * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). - * - Interrupts will not arrive from protected peripherals or interrupts. - * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. - * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). - * - Chosen low frequency clock source will be running. - * - * @param p_clock_lf_cfg Low frequency clock source and accuracy. - If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 - In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to - the actual characteristics of your XTAL clock. - * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. - * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has - an illegal priority level. - * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. - */ -SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, - sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); - -/**@brief Disables the SoftDevice and by extension the protocol stack. - * - * Idempotent function to disable the SoftDevice. - * - * @post SoC library and protocol stack APIs are made unavailable. - * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). - * @post All peripherals used by the SoftDevice will be reset to default values. - * @post All of RAM become available. - * @post All interrupts are forwarded to the application. - * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); - -/**@brief Check if the SoftDevice is enabled. - * - * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice - * - * This function is only intended to be called when a bootloader is enabled. - * - * @param[in] address The base address of the interrupt vector table for forwarded interrupts. - - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SDM_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_soc.h deleted file mode 100644 index c649ca836..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_soc.h +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_soc_api SoC Library API - * @{ - * - * @brief APIs for the SoC library. - * - */ - -#ifndef NRF_SOC_H__ -#define NRF_SOC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_SOC_DEFINES Defines - * @{ */ - -/**@brief The number of the lowest SVC number reserved for the SoC library. */ -#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ -#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ - -/**@brief Guaranteed time for application to process radio inactive notification. */ -#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) - -/**@brief The minimum allowed timeslot extension time. */ -#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) - -/**@brief The maximum processing time to handle a timeslot extension. */ -#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) - -/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ -#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) - -#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ -#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ -#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ - -#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ -#define SD_EVT_IRQHandler \ - (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ - The default interrupt priority for this handler is set to 6 */ -#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ -#define RADIO_NOTIFICATION_IRQHandler \ - (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ - The default interrupt priority for this handler is set to 6 */ -#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ -#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ - -#define NRF_RADIO_DISTANCE_MAX_US \ - (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ - nrf_radio_request_normal_t) in the request. */ - -#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ - (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ - -#define NRF_RADIO_START_JITTER_US \ - (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ - ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ - (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) - -/**@} */ - -/**@addtogroup NRF_SOC_ENUMS Enumerations - * @{ */ - -/**@brief The SVC numbers used by the SVC functions in the SoC library. */ -enum NRF_SOC_SVCS { - SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, - SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, - SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, - SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, - SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, - SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, - SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, - SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, - SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, - SD_FLASH_WRITE = SOC_SVC_BASE + 9, - SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, - SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, - SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, - SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, - SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, - SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, - SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, - SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, - SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, - SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, - SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, - SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, - SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, - SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, - SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, - SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, - SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, - SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, - SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, - SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, - SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, - SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, - SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, - SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, - SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, - SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, - SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, - SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, - SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, - SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, - SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, - SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, - SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, - SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, - SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, - SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, - SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, - SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, - SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 -}; - -/**@brief Possible values of a ::nrf_mutex_t. */ -enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; - -/**@brief Power modes. */ -enum NRF_POWER_MODES { - NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ - NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ -}; - -/**@brief Power failure thresholds */ -enum NRF_POWER_THRESHOLDS { - NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ -}; - -/**@brief Power failure thresholds for high voltage */ -enum NRF_POWER_THRESHOLDVDDHS { - NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ -}; - -/**@brief DC/DC converter modes. */ -enum NRF_POWER_DCDC_MODES { - NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ - NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ -}; - -/**@brief Radio notification distances. */ -enum NRF_RADIO_NOTIFICATION_DISTANCES { - NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ - NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ -}; - -/**@brief Radio notification types. */ -enum NRF_RADIO_NOTIFICATION_TYPES { - NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and - disabled. */ -}; - -/**@brief The Radio signal callback types. */ -enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { - NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ -}; - -/**@brief The actions requested by the signal callback. - * - * This code gives the SOC instructions about what action to take when the signal callback has - * returned. - */ -enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { - NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current - timeslot. Maximum execution time for this action: - @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. - This action must be started at least - @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before - the end of the timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ -}; - -/**@brief Radio timeslot high frequency clock source configuration. */ -enum NRF_RADIO_HFCLK_CFG { - NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the - external crystal for the whole duration of the timeslot. This should be the - preferred option for events that use the radio or require high timing accuracy. - @note The SoftDevice will automatically turn on and off the external crystal, - at the beginning and end of the timeslot, respectively. The crystal may also - intentionally be left running after the timeslot, in cases where it is needed - by the SoftDevice shortly after the end of the timeslot. */ - NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. - The RC oscillator may be the clock source in part or for the whole duration of the - timeslot. The RC oscillator's accuracy must therefore be taken into consideration. - @note If the application will use the radio peripheral in timeslots with this - configuration, it must make sure that the crystal is running and stable before - starting the radio. */ -}; - -/**@brief Radio timeslot priorities. */ -enum NRF_RADIO_PRIORITY { - NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ - NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ -}; - -/**@brief Radio timeslot request type. */ -enum NRF_RADIO_REQUEST_TYPE { - NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first - request in a session. */ - NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ -}; - -/**@brief SoC Events. */ -enum NRF_SOC_EVTS { - NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ - NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ - NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ - NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ - NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ - NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ - NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was - invalid. */ - NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ - NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ - NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ - NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ - NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ - NRF_EVT_NUMBER_OF_EVTS -}; - -/**@} */ - -/**@addtogroup NRF_SOC_STRUCTURES Structures - * @{ */ - -/**@brief Represents a mutex for use with the nrf_mutex functions. - * @note Accessing the value directly is not safe, use the mutex functions! - */ -typedef volatile uint8_t nrf_mutex_t; - -/**@brief Parameters for a request for a timeslot as early as possible. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ - uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref - NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ -} nrf_radio_request_earliest_t; - -/**@brief Parameters for a normal radio timeslot request. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US - microseconds). */ - uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ -} nrf_radio_request_normal_t; - -/**@brief Radio timeslot request parameters. */ -typedef struct { - uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ - union { - nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ - nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ - } params; /**< Parameter union. */ -} nrf_radio_request_t; - -/**@brief Return parameters of the radio timeslot signal callback. */ -typedef struct { - uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref - NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ - union { - struct { - nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ - } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ - struct { - uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref - NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ - } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ - } params; /**< Parameter union. */ -} nrf_radio_signal_callback_return_param_t; - -/**@brief The radio timeslot signal callback type. - * - * @note In case of invalid return parameters, the radio timeslot will automatically end - * immediately after returning from the signal callback and the - * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. - * @note The returned struct pointer must remain valid after the signal callback - * function returns. For instance, this means that it must not point to a stack variable. - * - * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. - * - * @return Pointer to structure containing action requested by the application. - */ -typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); - -/**@brief AES ECB parameter typedefs */ -typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ -typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ -typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ - -/**@brief AES ECB data structure */ -typedef struct { - soc_ecb_key_t key; /**< Encryption key. */ - soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ - soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ -} nrf_ecb_hal_data_t; - -/**@brief AES ECB block. Used to provide multiple blocks in a single call - to @ref sd_ecb_blocks_encrypt.*/ -typedef struct { - soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ - soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ - soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ -} nrf_ecb_hal_data_block_t; - -/**@} */ - -/**@addtogroup NRF_SOC_FUNCTIONS Functions - * @{ */ - -/**@brief Initialize a mutex. - * - * @param[in] p_mutex Pointer to the mutex to initialize. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); - -/**@brief Attempt to acquire a mutex. - * - * @param[in] p_mutex Pointer to the mutex to acquire. - * - * @retval ::NRF_SUCCESS The mutex was successfully acquired. - * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. - */ -SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); - -/**@brief Release a mutex. - * - * @param[in] p_mutex Pointer to the mutex to release. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); - -/**@brief Query the capacity of the application random pool. - * - * @param[out] p_pool_capacity The capacity of the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); - -/**@brief Get number of random bytes available to the application. - * - * @param[out] p_bytes_available The number of bytes currently available in the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); - -/**@brief Get random bytes from the application pool. - * - * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. - * @param[in] length Number of bytes to take from pool and place in p_buff. - * - * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. - * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes - * available. - */ -SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); - -/**@brief Gets the reset reason register. - * - * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); - -/**@brief Clears the bits of the reset reason register. - * - * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); - -/**@brief Sets the power mode when in CPU sleep. - * - * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait - * - * @retval ::NRF_SUCCESS The power mode was set. - * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. - */ -SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); - -/**@brief Puts the chip in System OFF mode. - * - * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN - */ -SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); - -/**@brief Enables or disables the power-fail comparator. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); - -/**@brief Enables or disables the USB power ready event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); - -/**@brief Enables or disables the power USB-detected event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); - -/**@brief Enables or disables the power USB-removed event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); - -/**@brief Get USB supply status register content. - * - * @param[out] usbregstatus The content of USBREGSTATUS register. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); - -/**@brief Sets the power failure comparator threshold value. - * - * @note: Power failure comparator threshold setting. This setting applies both for normal voltage - * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to - * VDDH only). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); - -/**@brief Sets the power failure comparator threshold value for high voltage. - * - * @note: Power failure comparator threshold setting for high voltage mode (supply connected to - * VDDH only). This setting does not apply for normal voltage mode (supply connected to both - * VDD and VDDH). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. - * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. - * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); - -/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. - * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); - -/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be set in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[out] p_gpregret Contents of the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). - * - * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. - */ -SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). - * - * For more details on the REG0 stage, please see product specification. - * - * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. - */ -SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); - -/**@brief Request the high frequency crystal oscillator. - * - * Will start the high frequency crystal oscillator, the startup time of the crystal varies - * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_release - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); - -/**@brief Releases the high frequency crystal oscillator. - * - * Will stop the high frequency crystal oscillator, this happens immediately. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_request - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); - -/**@brief Checks if the high frequency crystal oscillator is running. - * - * @see sd_clock_hfclk_request - * @see sd_clock_hfclk_release - * - * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); - -/**@brief Waits for an application event. - * - * An application event is either an application interrupt or a pended interrupt when the interrupt - * is disabled. - * - * When the application waits for an application event by calling this function, an interrupt that - * is enabled will be taken immediately on pending since this function will wait in thread mode, - * then the execution will return in the application's main thread. - * - * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M - * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets - * pended, this function will return to the application's main thread. - * - * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ - * in order to sleep using this function. This is only necessary for disabled interrupts, as - * the interrupt handler will clear the pending flag automatically for enabled interrupts. - * - * @note If an application interrupt has happened since the last time sd_app_evt_wait was - * called this function will return immediately and not go to sleep. This is to avoid race - * conditions that can occur when a flag is updated in the interrupt handler and processed - * in the main loop. - * - * @post An application interrupt has happened or a interrupt pending flag is set. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); - -/**@brief Get PPI channel enable register contents. - * - * @param[out] p_channel_enable The contents of the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); - -/**@brief Set PPI channel enable register. - * - * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); - -/**@brief Clear PPI channel enable register. - * - * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); - -/**@brief Assign endpoints to a PPI channel. - * - * @param[in] channel_num Number of the PPI channel to assign. - * @param[in] evt_endpoint Event endpoint of the PPI channel. - * @param[in] task_endpoint Task endpoint of the PPI channel. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, - sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); - -/**@brief Task to enable a channel group. - * - * @param[in] group_num Number of the channel group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); - -/**@brief Task to disable a channel group. - * - * @param[in] group_num Number of the PPI group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); - -/**@brief Assign PPI channels to a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[in] channel_msk Mask of the channels to assign to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); - -/**@brief Gets the PPI channels of a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[out] p_channel_msk Mask of the channels assigned to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); - -/**@brief Configures the Radio Notification signal. - * - * @note - * - The notification signal latency depends on the interrupt priority settings of SWI used - * for notification signal. - * - To ensure that the radio notification signal behaves in a consistent way, the radio - * notifications must be configured when there is no protocol stack or other SoftDevice - * activity in progress. It is recommended that the radio notification signal is - * configured directly after the SoftDevice has been enabled. - * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice - * will interrupt the application to do Radio Event preparation. - * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have - * to shorten the connection events to have time for the Radio Notification signals. - * - * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio - * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is - * recommended (but not required) to be used with - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. - * - * @param[in] distance Distance between the notification signal and start of radio activity, see @ref - * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or - * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. - * - * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. - * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all - * running activities and retry. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); - -/**@brief Encrypts a block according to the specified parameters. - * - * 128-bit AES encryption. - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input - * parameters and one output parameter). - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); - -/**@brief Encrypts multiple data blocks provided as an array of data block structures. - * - * @details: Performs 128-bit AES encryption on multiple data blocks - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in] block_count Count of blocks in the p_data_blocks array. - * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of - * @ref nrf_ecb_hal_data_block_t structures. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); - -/**@brief Gets any pending events generated by the SoC API. - * - * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. - * - * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. - * - * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. - * @retval ::NRF_ERROR_NOT_FOUND No pending events. - */ -SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); - -/**@brief Get the temperature measured on the chip - * - * This function will block until the temperature measurement is done. - * It takes around 50 us from call to return. - * - * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. - * - * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp - */ -SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); - -/**@brief Flash Write - * - * Commands to write a buffer to flash - * - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * write has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS - * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. - * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is - * protected. - * - * - * @param[in] p_dst Pointer to start of flash location to be written. - * @param[in] p_src Pointer to buffer with data to be written. - * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one - * flash page. See the device's Product Specification for details. - * - * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. - * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); - -/**@brief Flash Erase page - * - * Commands to erase a flash page - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * erase has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is - * protected. - * - * - * @param[in] page_number Page number of the page to erase - * - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); - -/**@brief Opens a session for radio timeslot requests. - * - * @note Only one session can be open at a time. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot - * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed - * by the application. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 - * interrupt occurs. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO - * interrupt occurs. - * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This - * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). - * - * @param[in] p_radio_signal_callback The signal callback. - * - * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. - * @retval ::NRF_ERROR_BUSY If session cannot be opened. - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); - -/**@brief Closes a session for radio timeslot requests. - * - * @note Any current radio timeslot will be finished before the session is closed. - * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. - * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED - * event is received. - * - * @retval ::NRF_ERROR_FORBIDDEN If session not opened. - * @retval ::NRF_ERROR_BUSY If session is currently being closed. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); - -/**@brief Requests a radio timeslot. - * - * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST - * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref - * NRF_RADIO_REQ_TYPE_EARLIEST. - * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by - * p_request->distance_us and is given relative to the start of the previous timeslot. - * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. - * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this - * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. - * The application may then try to schedule the first radio timeslot again. - * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). - * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. - * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. - * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the - * specified radio timeslot start, but this does not affect the actual start time of the timeslot. - * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency - * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is - * guaranteed to be clocked from the external crystal. - * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral - * during the radio timeslot. - * - * @param[in] p_request Pointer to the request parameters. - * - * @retval ::NRF_ERROR_FORBIDDEN Either: - * - The session is not open. - * - The session is not IDLE. - * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. - * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a - * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. - * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); - -/**@brief Write register protected by the SoftDevice - * - * This function writes to a register that is write-protected by the SoftDevice. Please refer to your - * SoftDevice Specification for more details about which registers that are protected by SoftDevice. - * This function can write to the following protected peripheral: - * - ACL - * - * @note Protected registers may be read directly. - * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in - * the register has not changed. See the Product Specification for more details about register - * properties. - * - * @param[in] p_register Pointer to register to be written. - * @param[in] value Value to be written to the register. - * - * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. - * @retval ::NRF_SUCCESS Value successfully written to register. - * - */ -SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); - -/**@} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SOC_H__ - -/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_svc.h b/variants/wio-tracker-wm1110/softdevice/nrf_svc.h deleted file mode 100644 index 1de44656f..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_svc.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef NRF_SVC__ -#define NRF_SVC__ - -#include "stdint.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @brief Supervisor call declaration. - * - * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. - * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. - * - * @param[in] number The SVC number to be used. - * @param[in] return_type The return type of the SVC function. - * @param[in] signature Function signature. The function can have at most four arguments. - */ - -#ifdef SVCALL_AS_NORMAL_FUNCTION -#define SVCALL(number, return_type, signature) return_type signature -#else - -#ifndef SVCALL -#if defined(__CC_ARM) -#define SVCALL(number, return_type, signature) return_type __svc(number) signature -#elif defined(__GNUC__) -#ifdef __cplusplus -#define GCC_CAST_CPP (uint16_t) -#else -#define GCC_CAST_CPP -#endif -#define SVCALL(number, return_type, signature) \ - _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ - __attribute__((unused)) static return_type signature \ - { \ - __asm("svc %0\n" \ - "bx r14" \ - : \ - : "I"(GCC_CAST_CPP number) \ - : "r0"); \ - } \ - _Pragma("GCC diagnostic pop") - -#elif defined(__ICCARM__) -#define PRAGMA(x) _Pragma(#x) -#define SVCALL(number, return_type, signature) \ - PRAGMA(swi_number = (number)) \ - __swi return_type signature; -#else -#define SVCALL(number, return_type, signature) return_type signature -#endif -#endif // SVCALL - -#endif // SVCALL_AS_NORMAL_FUNCTION - -#ifdef __cplusplus -} -#endif -#endif // NRF_SVC__ From a664d4597f8b9750785f637aae7ef445a69380fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:26:19 -0500 Subject: [PATCH 0681/1377] [create-pull-request] automated change (#4247) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 1cb93ac2b..c9336d539 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 15 +build = 16 From 308060b1fe37dd0a76821d2b4ad34da821a45f77 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 9 Jul 2024 09:59:27 +0800 Subject: [PATCH 0682/1377] Add wio-sdk-wm1110 to build. (#4258) The wio-sdk-wm1110 is distinct from the wio-tracker-wm1110, with different platformio build options and pin config. This change adds the wio-sdk-wm1110 to the CI matrix so firmware is built as part of release. --- variants/wio-sdk-wm1110/platformio.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index aa10f525d..dc7d47310 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -6,7 +6,6 @@ board = wio-sdk-wm1110 # Remove adafruit USB serial from the build (it is incompatible with using the ch340 serial chip on this board) build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB -board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" From 33c46d6eb1981ee11412530bc82f321af391df8c Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 9 Jul 2024 05:19:03 -0700 Subject: [PATCH 0683/1377] fix python warning in uf2conf (#4235) the old regex worked but was technically incorrect. fixes: Generating NRF52 uf2 file /home/kevinh/development/meshtastic/firmware/bin/uf2conv.py:195: SyntaxWarning: invalid escape sequence '\s' words = re.split('\s+', line) Converting to uf2, output size: 1458688, start address: 0x26000 --- bin/uf2conv.py | 223 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 145 insertions(+), 78 deletions(-) diff --git a/bin/uf2conv.py b/bin/uf2conv.py index b619d14db..a1e241b7a 100755 --- a/bin/uf2conv.py +++ b/bin/uf2conv.py @@ -1,39 +1,38 @@ #!/usr/bin/env python3 -import sys -import struct -import subprocess -import re +import argparse import os import os.path -import argparse +import re +import struct +import subprocess +import sys - -UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" -UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected -UF2_MAGIC_END = 0x0AB16F30 # Ditto +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto families = { - 'SAMD21': 0x68ed2b88, - 'SAML21': 0x1851780a, - 'SAMD51': 0x55114460, - 'NRF52': 0x1b57745f, - 'STM32F0': 0x647824b6, - 'STM32F1': 0x5ee21072, - 'STM32F2': 0x5d1a0a2e, - 'STM32F3': 0x6b846188, - 'STM32F4': 0x57755a57, - 'STM32F7': 0x53b80f00, - 'STM32G0': 0x300f5633, - 'STM32G4': 0x4c71240a, - 'STM32H7': 0x6db66082, - 'STM32L0': 0x202e3a91, - 'STM32L1': 0x1e1f432d, - 'STM32L4': 0x00ff6919, - 'STM32L5': 0x04240bdf, - 'STM32WB': 0x70d16653, - 'STM32WL': 0x21460ff0, - 'ATMEGA32': 0x16573617, - 'MIMXRT10XX': 0x4FB2D5BD + "SAMD21": 0x68ED2B88, + "SAML21": 0x1851780A, + "SAMD51": 0x55114460, + "NRF52": 0x1B57745F, + "STM32F0": 0x647824B6, + "STM32F1": 0x5EE21072, + "STM32F2": 0x5D1A0A2E, + "STM32F3": 0x6B846188, + "STM32F4": 0x57755A57, + "STM32F7": 0x53B80F00, + "STM32G0": 0x300F5633, + "STM32G4": 0x4C71240A, + "STM32H7": 0x6DB66082, + "STM32L0": 0x202E3A91, + "STM32L1": 0x1E1F432D, + "STM32L4": 0x00FF6919, + "STM32L5": 0x04240BDF, + "STM32WB": 0x70D16653, + "STM32WL": 0x21460FF0, + "ATMEGA32": 0x16573617, + "MIMXRT10XX": 0x4FB2D5BD, } INFO_FILE = "/INFO_UF2.TXT" @@ -46,15 +45,17 @@ def is_uf2(buf): w = struct.unpack(" 10*1024*1024: + if padding > 10 * 1024 * 1024: assert False, "More than 10M of padding needed at " + ptr if padding % 4 != 0: assert False, "Non-word padding size at " + ptr @@ -91,6 +92,7 @@ def convert_from_uf2(buf): curraddr = newaddr + datalen return outp + def convert_to_carray(file_content): outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {" for i in range(len(file_content)): @@ -100,6 +102,7 @@ def convert_to_carray(file_content): outp += "\n};\n" return outp + def convert_to_uf2(file_content): global familyid datapadding = b"" @@ -109,13 +112,21 @@ def convert_to_uf2(file_content): outp = b"" for blockno in range(numblocks): ptr = 256 * blockno - chunk = file_content[ptr:ptr + 256] + chunk = file_content[ptr : ptr + 256] flags = 0x0 if familyid: flags |= 0x2000 - hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": drives.append(words[0]) else: @@ -206,7 +238,6 @@ def get_drives(): for d in os.listdir(rootpath): drives.append(os.path.join(rootpath, d)) - def has_info(d): try: return os.path.isfile(d + INFO_FILE) @@ -217,7 +248,7 @@ def get_drives(): def board_id(path): - with open(path + INFO_FILE, mode='r') as file: + with open(path + INFO_FILE, mode="r") as file: file_content = file.read() return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) @@ -235,30 +266,61 @@ def write_file(name, buf): def main(): global appstartaddr, familyid + def error(msg): print(msg) sys.exit(1) - parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') - parser.add_argument('input', metavar='INPUT', type=str, nargs='?', - help='input file (HEX, BIN or UF2)') - parser.add_argument('-b' , '--base', dest='base', type=str, - default="0x2000", - help='set base address of application for BIN format (default: 0x2000)') - parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, - help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') - parser.add_argument('-d' , '--device', dest="device_path", - help='select a device path to flash') - parser.add_argument('-l' , '--list', action='store_true', - help='list connected devices') - parser.add_argument('-c' , '--convert', action='store_true', - help='do not flash, just convert') - parser.add_argument('-D' , '--deploy', action='store_true', - help='just flash, do not convert') - parser.add_argument('-f' , '--family', dest='family', type=str, - default="0x0", - help='specify familyID - number or name (default: 0x0)') - parser.add_argument('-C' , '--carray', action='store_true', - help='convert binary file to a C array, not UF2') + + parser = argparse.ArgumentParser(description="Convert to UF2 or flash directly.") + parser.add_argument( + "input", + metavar="INPUT", + type=str, + nargs="?", + help="input file (HEX, BIN or UF2)", + ) + parser.add_argument( + "-b", + "--base", + dest="base", + type=str, + default="0x2000", + help="set base address of application for BIN format (default: 0x2000)", + ) + parser.add_argument( + "-o", + "--output", + metavar="FILE", + dest="output", + type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible', + ) + parser.add_argument( + "-d", "--device", dest="device_path", help="select a device path to flash" + ) + parser.add_argument( + "-l", "--list", action="store_true", help="list connected devices" + ) + parser.add_argument( + "-c", "--convert", action="store_true", help="do not flash, just convert" + ) + parser.add_argument( + "-D", "--deploy", action="store_true", help="just flash, do not convert" + ) + parser.add_argument( + "-f", + "--family", + dest="family", + type=str, + default="0x0", + help="specify familyID - number or name (default: 0x0)", + ) + parser.add_argument( + "-C", + "--carray", + action="store_true", + help="convert binary file to a C array, not UF2", + ) args = parser.parse_args() appstartaddr = int(args.base, 0) @@ -268,14 +330,17 @@ def main(): try: familyid = int(args.family, 0) except ValueError: - error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) + error( + "Family ID needs to be a number or one of: " + + ", ".join(families.keys()) + ) if args.list: list_drives() else: if not args.input: error("Need input file") - with open(args.input, mode='rb') as f: + with open(args.input, mode="rb") as f: inpbuf = f.read() from_uf2 = is_uf2(inpbuf) ext = "uf2" @@ -291,8 +356,10 @@ def main(): ext = "h" else: outbuf = convert_to_uf2(inpbuf) - print("Converting to %s, output size: %d, start address: 0x%x" % - (ext, len(outbuf), appstartaddr)) + print( + "Converting to %s, output size: %d, start address: 0x%x" + % (ext, len(outbuf), appstartaddr) + ) if args.convert or ext != "uf2": drives = [] if args.output == None: From 9f089746da9c7a5e06698b60174fb5cb4d19232f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 9 Jul 2024 08:31:16 -0500 Subject: [PATCH 0684/1377] Collect hex files and specifically wm1110 sdk --- .github/workflows/main_matrix.yml | 3 ++- bin/build-nrf52.sh | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 25a0fbad2..14c8a9d10 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -136,7 +136,7 @@ jobs: build-rpi2040, package-raspbian, package-raspbian-armv7l, - package-native + package-native, ] steps: - name: Checkout code @@ -168,6 +168,7 @@ jobs: path: | ./firmware-*.bin ./firmware-*.uf2 + ./firmware-*.hex ./firmware-*-ota.zip ./device-*.sh ./device-*.bat diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index fa6eacd23..97b7cd456 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -33,13 +33,15 @@ SRCHEX=.pio/build/$1/firmware.hex # if WM1110 target, merge hex with softdevice 7.3.0 if (echo $1 | grep -q "wio-sdk-wm1110"); then echo "Merging with softdevice" - sudo chmod +x ./bin/mergehex - bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex - SRCHEX=.pio/build/$1/merged_fimware.hex + sudo chmod +x ./bin/mergehex + bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex + SRCHEX=.pio/build/$1/$basename.hex + bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 + cp $SRCHEX $OUTDIR + cp bin/*.uf2 $OUTDIR +else + bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 + cp bin/device-install.* $OUTDIR + cp bin/device-update.* $OUTDIR + cp bin/*.uf2 $OUTDIR fi - -bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 - -cp bin/device-install.* $OUTDIR -cp bin/device-update.* $OUTDIR -cp bin/*.uf2 $OUTDIR From 8b388d1e27f3a5fe4b73d8f73e957b71ccad38d2 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 9 Jul 2024 09:12:23 -0500 Subject: [PATCH 0685/1377] Skip dfu file for sdk (for now) --- bin/build-nrf52.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 97b7cd456..e4fadbb30 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -23,8 +23,17 @@ basename=firmware-$1-$VERSION pio run --environment $1 # -v SRCELF=.pio/build/$1/firmware.elf -DFUPKG=.pio/build/$1/firmware.zip cp $SRCELF $OUTDIR/$basename.elf + +if (echo $1 | grep -q "wio-sdk-wm1110"); then + echo "Skipping dfu file" +else + echo "Generating NRF52 dfu file" + DFUPKG=.pio/build/$1/firmware.zip + cp $DFUPKG $OUTDIR/$basename-ota.zip +fi + +DFUPKG=.pio/build/$1/firmware.zip cp $DFUPKG $OUTDIR/$basename-ota.zip echo "Generating NRF52 uf2 file" From a3777e8f29754a62c80ad1bb902075d517f76a28 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 9 Jul 2024 09:23:59 -0500 Subject: [PATCH 0686/1377] Helps if you remove the original clause --- .github/workflows/build_nrf52.yml | 1 + bin/build-nrf52.sh | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index eb1779963..ac509a096 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -29,6 +29,7 @@ jobs: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip overwrite: true path: | + release/*.hex release/*.uf2 release/*.elf release/*.zip diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index e4fadbb30..077e2af35 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -33,9 +33,6 @@ else cp $DFUPKG $OUTDIR/$basename-ota.zip fi -DFUPKG=.pio/build/$1/firmware.zip -cp $DFUPKG $OUTDIR/$basename-ota.zip - echo "Generating NRF52 uf2 file" SRCHEX=.pio/build/$1/firmware.hex From 1626667400356911f95343ee715ac14078ab43f3 Mon Sep 17 00:00:00 2001 From: "Aaron.Lee" <32860565+Heltec-Aaron-Lee@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:56:57 +0800 Subject: [PATCH 0687/1377] Add Heltec new boards. (#4226) * Add Heltec new boards * Update variant.h disable RTC by default * Add Heltec New boards * Add Heltec new boards * Update Heltec Mesh Node definition. * Update Heltec Vision Mater E290 --- boards/heltec_mesh_node_t114.json | 53 +++++ platformio.ini | 4 + src/Power.cpp | 8 + src/graphics/EInkDisplay2.cpp | 2 +- src/graphics/EInkDisplay2.h | 7 +- src/graphics/Screen.cpp | 27 ++- src/graphics/Screen.h | 2 + src/main.cpp | 5 +- src/mesh/NodeDB.cpp | 2 +- src/platform/esp32/architecture.h | 8 + src/sleep.cpp | 2 + variants/heltec_mesh_node_t114/platformio.ini | 15 ++ variants/heltec_mesh_node_t114/variant.cpp | 44 ++++ variants/heltec_mesh_node_t114/variant.h | 209 ++++++++++++++++++ .../heltec_vision_master_e213/pins_arduino.h | 63 ++++++ .../heltec_vision_master_e213/platformio.ini | 23 ++ variants/heltec_vision_master_e213/variant.h | 58 +++++ .../heltec_vision_master_e290/pins_arduino.h | 63 ++++++ .../heltec_vision_master_e290/platformio.ini | 24 ++ variants/heltec_vision_master_e290/variant.h | 58 +++++ .../heltec_vision_master_t190/pins_arduino.h | 63 ++++++ .../heltec_vision_master_t190/platformio.ini | 13 ++ variants/heltec_vision_master_t190/variant.h | 78 +++++++ variants/heltec_wireless_paper/pins_arduino.h | 5 - 24 files changed, 820 insertions(+), 16 deletions(-) create mode 100644 boards/heltec_mesh_node_t114.json create mode 100644 variants/heltec_mesh_node_t114/platformio.ini create mode 100644 variants/heltec_mesh_node_t114/variant.cpp create mode 100644 variants/heltec_mesh_node_t114/variant.h create mode 100644 variants/heltec_vision_master_e213/pins_arduino.h create mode 100644 variants/heltec_vision_master_e213/platformio.ini create mode 100644 variants/heltec_vision_master_e213/variant.h create mode 100644 variants/heltec_vision_master_e290/pins_arduino.h create mode 100644 variants/heltec_vision_master_e290/platformio.ini create mode 100644 variants/heltec_vision_master_e290/variant.h create mode 100644 variants/heltec_vision_master_t190/pins_arduino.h create mode 100644 variants/heltec_vision_master_t190/platformio.ini create mode 100644 variants/heltec_vision_master_t190/variant.h diff --git a/boards/heltec_mesh_node_t114.json b/boards/heltec_mesh_node_t114.json new file mode 100644 index 000000000..5c97d8c75 --- /dev/null +++ b/boards/heltec_mesh_node_t114.json @@ -0,0 +1,53 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_node_t114", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Heltec nrf (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "FIXME", + "vendor": "Heltec" +} diff --git a/platformio.ini b/platformio.ini index bcdcc0034..b3f677247 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,6 +35,10 @@ default_envs = tbeam ;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_micro ;default_envs = heltec_capsule_sensor_v3 +;default_envs = heltec_vision_master_t190 +;default_envs = heltec_vision_master_e213 +;default_envs = heltec_vision_master_e290 +;default_envs = heltec_mesh_node_t114 extra_configs = arch/*/*.ini diff --git a/src/Power.cpp b/src/Power.cpp index cea373806..78024ee0c 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -233,11 +233,19 @@ class AnalogBatteryLevel : public HasBatteryLevel scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled *= operativeAdcMultiplier; #else // block for all other platforms +#ifdef ADC_CTRL // enable adc voltage divider when we need to read + pinMode(ADC_CTRL, OUTPUT); + digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); + delay(10); +#endif for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) { raw += analogRead(BATTERY_PIN); } raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; +#ifdef ADC_CTRL // disable adc voltage divider when we need to read + digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); +#endif #endif if (!initial_read_done) { diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index bbc12521a..191c46e67 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -156,7 +156,7 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || defined(HELTEC_VISION_MASTER_E290) { // Start HSPI hspi = new SPIClass(HSPI); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index f74416494..426bb5f19 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -5,11 +5,6 @@ #include "GxEPD2_BW.h" #include -#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) -// Re-enable SPI after deep sleep: rtc_gpio_hold_dis() -#include "driver/rtc_io.h" -#endif - /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * @@ -72,7 +67,7 @@ class EInkDisplay : public OLEDDisplay GxEPD2_BW *adafruitDisplay = NULL; // If display uses HSPI -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || defined(HELTEC_VISION_MASTER_E290) SPIClass *hspi = NULL; #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f724ddd3d..86d71dfde 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1485,6 +1485,10 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ screen->drawColumns(display, x, y, fields); } +#if defined(ESP_PLATFORM) && defined(USE_ST7789) +SPIClass SPI1(HSPI); +#endif + Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { @@ -1492,6 +1496,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) dispdev = new SH1106Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_ST7789) +#ifdef ESP_PLATFORM + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS,GEOMETRY_RAWMODE,TFT_WIDTH,TFT_HEIGHT,ST7789_SDA,ST7789_MISO,ST7789_SCK); +#else + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS,GEOMETRY_RAWMODE,TFT_WIDTH,TFT_HEIGHT); +#endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -1570,7 +1580,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif dispdev->displayOn(); - +#ifdef USE_ST7789 +#ifdef ESP_PLATFORM + analogWrite(VTFT_LEDA,BRIGHTNESS_DEFAULT); +#else + pinMode(VTFT_LEDA,OUTPUT); + digitalWrite(VTFT_LEDA,TFT_BACKLIGHT_ON); +#endif +#endif enabled = true; setInterval(0); // Draw ASAP runASAP = true; @@ -1581,6 +1598,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif LOG_INFO("Turning off screen\n"); dispdev->displayOff(); + +#ifdef USE_ST7789 + pinMode(VTFT_LEDA,OUTPUT); + digitalWrite(VTFT_LEDA,!TFT_BACKLIGHT_ON); +#endif + #ifdef T_WATCH_S3 PMU->disablePowerOutput(XPOWERS_ALDO2); #endif @@ -1595,7 +1618,7 @@ void Screen::setup() // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. useDisplay = true; - + #ifdef AutoOLEDWire_h if (isAUTOOled) static_cast(dispdev)->setDetected(model); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 83c9a7a94..b89a2917e 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -45,6 +45,8 @@ class Screen #include #elif defined(USE_SSD1306) #include +#elif defined(USE_ST7789) +#include #else // the SH1106/SSD1306 variant is auto-detected #include diff --git a/src/main.cpp b/src/main.cpp index 1e0d998e1..95af5f6de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -275,6 +275,9 @@ void setup() digitalWrite(VEXT_ENABLE_V05, 1); // turn on the lora antenna boost digitalWrite(ST7735_BL_V05, 1); // turn on display backligth LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); +#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) + pinMode(VEXT_ENABLE, OUTPUT); + digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) pinMode(VEXT_ENABLE, OUTPUT); digitalWrite(VEXT_ENABLE, 0); // turn on the display power @@ -713,7 +716,7 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 84872e471..07184a6bc 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -268,7 +268,7 @@ void NodeDB::installDefaultConfig() // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) bool hasScreen = true; #elif ARCH_PORTDUINO bool hasScreen = false; diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 5565b6468..fd3f92a9c 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -151,6 +151,14 @@ #define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO #elif defined(HELTEC_CAPSULE_SENSOR_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 +#elif defined(HELTEC_VISION_MASTER_T190) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 +#elif defined(HELTEC_VISION_MASTER_E213) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 +#elif defined(HELTEC_VISION_MASTER_E290) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 +#elif defined(HELTEC_MESH_NODE_T114) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #endif // ----------------------------------------------------------------------------- diff --git a/src/sleep.cpp b/src/sleep.cpp index e2c9549f3..3918568a0 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -257,6 +257,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #elif defined(VEXT_ENABLE_V05) digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power digitalWrite(ST7735_BL_V05, 0); // turn off the display power +#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) + digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini new file mode 100644 index 000000000..99bdf77a7 --- /dev/null +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -0,0 +1,15 @@ +; First prototype nrf52840/sx1262 device +[env:heltec-mesh-node-t114] +extends = nrf52840_base +board = heltec_mesh_node_t114 +debug_tool = jlink + +# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. +build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_node_t114 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node_t114> +lib_deps = + ${nrf52840_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 + https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb \ No newline at end of file diff --git a/variants/heltec_mesh_node_t114/variant.cpp b/variants/heltec_mesh_node_t114/variant.cpp new file mode 100644 index 000000000..cae079b74 --- /dev/null +++ b/variants/heltec_mesh_node_t114/variant.cpp @@ -0,0 +1,44 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + pinMode(PIN_LED3, OUTPUT); + ledOff(PIN_LED3); +} diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h new file mode 100644 index 000000000..d6c11a01d --- /dev/null +++ b/variants/heltec_mesh_node_t114/variant.h @@ -0,0 +1,209 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_HELTEC_NRF_ +#define _VARIANT_HELTEC_NRF_ +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define HELTEC_MESH_NODE_T114 + +#define USE_ST7789 + +#define ST7789_NSS 11 +#define ST7789_RS 12 // DC +#define ST7789_SDA 41 // MOSI +#define ST7789_SCK 40 +#define ST7789_RESET 2 +#define ST7789_MISO -1 +#define ST7789_BUSY -1 +#define VTFT_CTRL 3 +#define VTFT_LEDA 15 +// #define ST7789_BL (32+6) +#define TFT_BACKLIGHT_ON LOW +#define ST7789_SPI_HOST SPI1_HOST +// #define ST7789_BACKLIGHT_EN (32+6) +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 135 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +// #define TFT_OFFSET_ROTATION 0 +// #define SCREEN_ROTATE +// #define SCREEN_TRANSITION_FRAMERATE 5 + + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (32 + 3) // 13 red (confirmed on 1.0 board) +// Unused(by firmware) LEDs: +#define PIN_LED2 (1 + 1) // 14 blue +#define PIN_LED3 (1 + 11) // 15 green + +#define LED_RED PIN_LED3 +#define LED_BLUE PIN_LED1 +#define LED_GREEN PIN_LED2 + +#define LED_BUILTIN LED_BLUE +#define LED_CONN PIN_GREEN + +#define LED_STATE_ON 0 // State when LED is lit +#define LED_INVERTED 1 + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 10) +//#define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular GPIO + +/* +No longer populated on PCB +*/ +#define PIN_SERIAL2_RX (0 + 9) +#define PIN_SERIAL2_TX (0 + 10) +// #define PIN_SERIAL2_EN (0 + 17) + +/** + Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (26) +#define PIN_WIRE_SCL (27) + + +// QSPI Pins +#define PIN_QSPI_SCK (32 + 14) +#define PIN_QSPI_CS (32 + 15) +#define PIN_QSPI_IO0 (32 + 12) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (32 + 13) // MISO if using two bit interface +#define PIN_QSPI_IO2 (0 + 7) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (0 + 5) // HOLD if using two bit interface (i.e. not used) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +/* + * Lora radio + */ + +#define USE_SX1262 +// #define USE_SX1268 +#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead +#define LORA_CS (0 + 24) +#define SX126X_DIO1 (0 + 20) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +//#define SX1262_DIO3 \ +// (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main + // CPU? +#define SX126X_BUSY (0 + 17) +#define SX126X_RESET (0 + 25) +// Not really an E22 but TTGO seems to be trying to clone that +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define PIN_SPI1_MISO ST7789_MISO // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO +#define PIN_SPI1_MOSI ST7789_SDA +#define PIN_SPI1_SCK ST7789_SCK + +/* + * GPS pins + */ + +#define GPS_L76K + +#define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +#define GPS_RESET_MODE LOW +#define PIN_GPS_EN (21) +#define GPS_EN_ACTIVE HIGH +#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (32+4) +// Seems to be missing on this new board +// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +// For LORA, spi 0 +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) + +//#define PIN_PWR_EN (0 + 6) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +// #define USE_SEGGER + +// Battery +// The battery sense is hooked to pin A0 (4) +// it is defined in the anlaolgue pin section of this file +// and has 12 bit resolution + +#define ADC_CTRL 6 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 4 +#define ADC_RESOLUTION 14 + +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (4.90F) + +#define HAS_RTC 0 +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h new file mode 100644 index 000000000..01c16c496 --- /dev/null +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -0,0 +1,63 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define HELTEC_VISION_MASTER_E213 true + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini new file mode 100644 index 000000000..1044974c0 --- /dev/null +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -0,0 +1,23 @@ +[env:heltec-vision-master-e213] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -I variants/heltec_vision_master_e213 + -D HELTEC_VISION_MASTER_E213 + -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d + lewisxhe/PCF8563_Library@^1.0.1 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h new file mode 100644 index 000000000..6a57a0dd6 --- /dev/null +++ b/variants/heltec_vision_master_e213/variant.h @@ -0,0 +1,58 @@ +// #define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_EINK + +/* + * eink display pins + */ +#define PIN_EINK_CS 5 +#define PIN_EINK_BUSY 1 +#define PIN_EINK_DC 2 +#define PIN_EINK_RES 3 +#define PIN_EINK_SCLK 4 +#define PIN_EINK_MOSI 6 + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +#define VEXT_ENABLE 18 // powers the oled display and the lora antenna boost +#define VEXT_ON_VALUE 1 +#define BUTTON_PIN 21 + +#define ADC_CTRL 46 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 7 +#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_MULTIPLIER 4.9*1.03 // Voltage divider is roughly 1:1 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h new file mode 100644 index 000000000..a9b474c31 --- /dev/null +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -0,0 +1,63 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define HELTEC_VISION_MASTER_E290 true + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini new file mode 100644 index 000000000..fa89af32b --- /dev/null +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -0,0 +1,24 @@ +[env:heltec-vision-master-e290] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -I variants/heltec_vision_master_e290 + -D HELTEC_VISION_MASTER_E290 + -D EINK_DISPLAY_MODEL=GxEPD2_290_BS + -D EINK_WIDTH=296 + -D EINK_HEIGHT=128 +; -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk +; -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted +; -D EINK_LIMIT_RATE_BACKGROUND_SEC=1 ; Minimum interval between BACKGROUND updates +; -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated +; -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. +; -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" +; -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight + +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d + lewisxhe/PCF8563_Library@^1.0.1 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h new file mode 100644 index 000000000..5b16d7b8f --- /dev/null +++ b/variants/heltec_vision_master_e290/variant.h @@ -0,0 +1,58 @@ +// #define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_EINK + +/* + * eink display pins + */ +#define PIN_EINK_CS 3 +#define PIN_EINK_BUSY 5 +#define PIN_EINK_DC 4 +#define PIN_EINK_RES 5 +#define PIN_EINK_SCLK 2 +#define PIN_EINK_MOSI 1 + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK + +#define VEXT_ENABLE 18 // powers the e-ink display +#define VEXT_ON_VALUE 1 +#define BUTTON_PIN 21 + +#define ADC_CTRL 46 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 7 +#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_MULTIPLIER 4.9*1.03 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/heltec_vision_master_t190/pins_arduino.h new file mode 100644 index 000000000..473187a02 --- /dev/null +++ b/variants/heltec_vision_master_t190/pins_arduino.h @@ -0,0 +1,63 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define HELTEC_VISION_MASTER_T190 true + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini new file mode 100644 index 000000000..7ed64f5e0 --- /dev/null +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -0,0 +1,13 @@ +[env:heltec-vision-master-t190] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -I variants/heltec_vision_master_t190 + -D PRIVATE_HW + ; -D PRIVATE_HW +lib_deps = + ${esp32s3_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 + https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb +upload_speed = 921600 \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h new file mode 100644 index 000000000..01830d719 --- /dev/null +++ b/variants/heltec_vision_master_t190/variant.h @@ -0,0 +1,78 @@ +// #define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA 1 +#define I2C_SCL 2 +#define USE_ST7789 + +#define ST7789_NSS 39 +// #define ST7789_CS 39 +#define ST7789_RS 47 // DC +#define ST7789_SDA 48 // MOSI +#define ST7789_SCK 38 +#define ST7789_RESET 40 +#define ST7789_MISO 4 +#define ST7789_BUSY -1 +#define VTFT_CTRL 7 +// #define TFT_BL 3 +#define VTFT_LEDA 17 +#define TFT_BACKLIGHT_ON HIGH +// #define TFT_BL 17 +// #define TFT_BACKLIGHT_ON HIGH +// #define ST7789_BL 3 +#define ST7789_SPI_HOST SPI2_HOST +// #define ST7789_BACKLIGHT_EN 17 +#define SPI_FREQUENCY 10000000 +#define SPI_READ_FREQUENCY 10000000 +#define TFT_HEIGHT 170 +#define TFT_WIDTH 320 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +// #define TFT_OFFSET_ROTATION 0 +// #define SCREEN_ROTATE +// #define SCREEN_TRANSITION_FRAMERATE 5 +#define BRIGHTNESS_DEFAULT 100 // Medium Low Brightnes + + +// #define SLEEP_TIME 120 + + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +// #define VEXT_ENABLE 7 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + +#define ADC_CTRL 46 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 6 +#define ADC_CHANNEL ADC1_GPIO6_CHANNEL +#define ADC_MULTIPLIER 4.9*1.03 // Voltage divider is roughly 1:1 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h index 2bb44161a..3e36d98f5 100644 --- a/variants/heltec_wireless_paper/pins_arduino.h +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -7,8 +7,6 @@ static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -static const uint8_t KEY_BUILTIN = 0; - static const uint8_t TX = 43; static const uint8_t RX = 44; @@ -56,9 +54,6 @@ static const uint8_t T12 = 12; static const uint8_t T13 = 13; static const uint8_t T14 = 14; -static const uint8_t Vext = 45; -static const uint8_t LED = 18; - static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; static const uint8_t DIO1 = 14; From 9ad0addbbf8288bc15c8aa91c14db1cb5bc27c13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:07:23 -0500 Subject: [PATCH 0688/1377] [create-pull-request] automated change (#4259) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/config.pb.h | 10 +++++++--- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 4 ++-- src/mesh/generated/meshtastic/module_config.pb.h | 12 ++++++++---- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/protobufs b/protobufs index 1198b7dba..d191975eb 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1198b7dbabf9768cb0143d2897707b4c7a51a5da +Subproject commit d191975ebc572527c6d9eec48d5b0a1e3331999f diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index ba9f90873..f5bacea52 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 674 +#define meshtastic_ChannelSet_size 676 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index e3037c910..44a86f4d6 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -497,6 +497,8 @@ typedef struct _meshtastic_Config_LoRaConfig { Please respect your local laws and regulations. If you are a HAM, make sure you enable HAM mode and turn off encryption. */ float override_frequency; + /* If true, disable the build-in PA FAN using pin define in RF95_FAN_EN. */ + bool pa_fan_disabled; /* For testing it is useful sometimes to force a node to never listen to particular other nodes (simulating radio out of range). All nodenums listed in ignore_incoming will have packets they send dropped on receive (by router.cpp) */ @@ -618,7 +620,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -627,7 +629,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} /* Field tags (for use in manual encoding/decoding) */ @@ -702,6 +704,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_override_duty_cycle_tag 12 #define meshtastic_Config_LoRaConfig_sx126x_rx_boosted_gain_tag 13 #define meshtastic_Config_LoRaConfig_override_frequency_tag 14 +#define meshtastic_Config_LoRaConfig_pa_fan_disabled_tag 15 #define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103 #define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104 #define meshtastic_Config_BluetoothConfig_enabled_tag 1 @@ -832,6 +835,7 @@ X(a, STATIC, SINGULAR, UINT32, channel_num, 11) \ X(a, STATIC, SINGULAR, BOOL, override_duty_cycle, 12) \ X(a, STATIC, SINGULAR, BOOL, sx126x_rx_boosted_gain, 13) \ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \ +X(a, STATIC, SINGULAR, BOOL, pa_fan_disabled, 15) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_LoRaConfig_CALLBACK NULL @@ -871,7 +875,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_BluetoothConfig_size 12 #define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 30 -#define meshtastic_Config_LoRaConfig_size 80 +#define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index fc7bea53a..eb37f4f95 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -307,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3384 +#define meshtastic_OEMStore_size 3388 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index c1d2a4ae3..983f48ad3 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,8 +181,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 553 -#define meshtastic_LocalModuleConfig_size 685 +#define meshtastic_LocalConfig_size 555 +#define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index f3c48ee6d..12087655a 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -273,6 +273,8 @@ typedef struct _meshtastic_ModuleConfig_StoreForwardConfig { uint32_t history_return_max; /* TODO: REPLACE */ uint32_t history_return_window; + /* Set to true to let this node act as a server that stores received messages and resends them upon request. */ + bool is_server; } meshtastic_ModuleConfig_StoreForwardConfig; /* Preferences for the RangeTestModule */ @@ -474,7 +476,7 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} @@ -490,7 +492,7 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} @@ -560,6 +562,7 @@ extern "C" { #define meshtastic_ModuleConfig_StoreForwardConfig_records_tag 3 #define meshtastic_ModuleConfig_StoreForwardConfig_history_return_max_tag 4 #define meshtastic_ModuleConfig_StoreForwardConfig_history_return_window_tag 5 +#define meshtastic_ModuleConfig_StoreForwardConfig_is_server_tag 6 #define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1 #define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2 #define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3 @@ -743,7 +746,8 @@ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, BOOL, heartbeat, 2) \ X(a, STATIC, SINGULAR, UINT32, records, 3) \ X(a, STATIC, SINGULAR, UINT32, history_return_max, 4) \ -X(a, STATIC, SINGULAR, UINT32, history_return_window, 5) +X(a, STATIC, SINGULAR, UINT32, history_return_window, 5) \ +X(a, STATIC, SINGULAR, BOOL, is_server, 6) #define meshtastic_ModuleConfig_StoreForwardConfig_CALLBACK NULL #define meshtastic_ModuleConfig_StoreForwardConfig_DEFAULT NULL @@ -848,7 +852,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RangeTestConfig_size 10 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 -#define meshtastic_ModuleConfig_StoreForwardConfig_size 22 +#define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 #define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 From ba8d17b9c12f39f4bce1d15bc868a6be042fc82f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 9 Jul 2024 12:16:56 -0500 Subject: [PATCH 0689/1377] Trunk fmt --- src/Power.cpp | 12 ++++---- src/graphics/EInkDisplay2.cpp | 3 +- src/graphics/EInkDisplay2.h | 3 +- src/graphics/Screen.cpp | 17 ++++++----- src/main.cpp | 3 +- src/mesh/NodeDB.cpp | 3 +- src/sleep.cpp | 2 +- variants/heltec_capsule_sensor_v3/variant.h | 2 +- variants/heltec_mesh_node_t114/variant.h | 31 ++++++++++---------- variants/heltec_vision_master_e213/variant.h | 2 +- variants/heltec_vision_master_e290/variant.h | 8 ++--- variants/heltec_vision_master_t190/variant.h | 4 +-- 12 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 78024ee0c..950ea741d 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -232,11 +232,11 @@ class AnalogBatteryLevel : public HasBatteryLevel raw = espAdcRead(); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled *= operativeAdcMultiplier; -#else // block for all other platforms -#ifdef ADC_CTRL // enable adc voltage divider when we need to read - pinMode(ADC_CTRL, OUTPUT); - digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); - delay(10); +#else // block for all other platforms +#ifdef ADC_CTRL // enable adc voltage divider when we need to read + pinMode(ADC_CTRL, OUTPUT); + digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); + delay(10); #endif for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) { raw += analogRead(BATTERY_PIN); @@ -244,7 +244,7 @@ class AnalogBatteryLevel : public HasBatteryLevel raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; #ifdef ADC_CTRL // disable adc voltage divider when we need to read - digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); + digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); #endif #endif diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 191c46e67..d81ab6ff4 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -156,7 +156,8 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || defined(HELTEC_VISION_MASTER_E290) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ + defined(HELTEC_VISION_MASTER_E290) { // Start HSPI hspi = new SPIClass(HSPI); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 426bb5f19..26091b2cd 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -67,7 +67,8 @@ class EInkDisplay : public OLEDDisplay GxEPD2_BW *adafruitDisplay = NULL; // If display uses HSPI -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || defined(HELTEC_VISION_MASTER_E290) +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ + defined(HELTEC_VISION_MASTER_E290) SPIClass *hspi = NULL; #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 86d71dfde..7f9c905a8 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1498,9 +1498,10 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_ST7789) #ifdef ESP_PLATFORM - dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS,GEOMETRY_RAWMODE,TFT_WIDTH,TFT_HEIGHT,ST7789_SDA,ST7789_MISO,ST7789_SCK); + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7789_SDA, + ST7789_MISO, ST7789_SCK); #else - dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS,GEOMETRY_RAWMODE,TFT_WIDTH,TFT_HEIGHT); + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, @@ -1582,10 +1583,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) dispdev->displayOn(); #ifdef USE_ST7789 #ifdef ESP_PLATFORM - analogWrite(VTFT_LEDA,BRIGHTNESS_DEFAULT); + analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); #else - pinMode(VTFT_LEDA,OUTPUT); - digitalWrite(VTFT_LEDA,TFT_BACKLIGHT_ON); + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); #endif #endif enabled = true; @@ -1600,8 +1601,8 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) dispdev->displayOff(); #ifdef USE_ST7789 - pinMode(VTFT_LEDA,OUTPUT); - digitalWrite(VTFT_LEDA,!TFT_BACKLIGHT_ON); + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, !TFT_BACKLIGHT_ON); #endif #ifdef T_WATCH_S3 @@ -1618,7 +1619,7 @@ void Screen::setup() // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. useDisplay = true; - + #ifdef AutoOLEDWire_h if (isAUTOOled) static_cast(dispdev)->setDetected(model); diff --git a/src/main.cpp b/src/main.cpp index 95af5f6de..95eeb998d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -716,7 +716,8 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ + defined(USE_ST7789) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 07184a6bc..fa5c437c4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -268,7 +268,8 @@ void NodeDB::installDefaultConfig() // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ + defined(USE_ST7789) bool hasScreen = true; #elif ARCH_PORTDUINO bool hasScreen = false; diff --git a/src/sleep.cpp b/src/sleep.cpp index 3918568a0..721c7d188 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -258,7 +258,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power digitalWrite(ST7735_BL_V05, 0); // turn off the display power #elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) - digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power + digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif diff --git a/variants/heltec_capsule_sensor_v3/variant.h b/variants/heltec_capsule_sensor_v3/variant.h index 0d5ab73cf..51c3cb6ad 100644 --- a/variants/heltec_capsule_sensor_v3/variant.h +++ b/variants/heltec_capsule_sensor_v3/variant.h @@ -1,5 +1,5 @@ #define LED_PIN 33 -#define LED_PIN2 34 +#define LED_PIN2 34 #define EXT_PWR_DETECT 35 #define BUTTON_PIN 18 diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index d6c11a01d..b233069c6 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -53,14 +53,13 @@ extern "C" { #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 135 -#define TFT_WIDTH 240 +#define TFT_WIDTH 240 #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 // #define TFT_OFFSET_ROTATION 0 // #define SCREEN_ROTATE // #define SCREEN_TRANSITION_FRAMERATE 5 - // Number of pins defined in PinDescription array #define PINS_COUNT (48) #define NUM_DIGITAL_PINS (48) @@ -70,7 +69,7 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 3) // 13 red (confirmed on 1.0 board) // Unused(by firmware) LEDs: -#define PIN_LED2 (1 + 1) // 14 blue +#define PIN_LED2 (1 + 1) // 14 blue #define PIN_LED3 (1 + 11) // 15 green #define LED_RED PIN_LED3 @@ -87,7 +86,8 @@ extern "C" { * Buttons */ #define PIN_BUTTON1 (32 + 10) -//#define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular GPIO +// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular +// GPIO /* No longer populated on PCB @@ -104,7 +104,6 @@ No longer populated on PCB #define PIN_WIRE_SDA (26) #define PIN_WIRE_SCL (27) - // QSPI Pins #define PIN_QSPI_SCK (32 + 14) #define PIN_QSPI_CS (32 + 15) @@ -124,21 +123,23 @@ No longer populated on PCB #define USE_SX1262 // #define USE_SX1268 #define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead -#define LORA_CS (0 + 24) +#define LORA_CS (0 + 24) #define SX126X_DIO1 (0 + 20) // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching -//#define SX1262_DIO3 \ -// (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main - // CPU? +// #define SX1262_DIO3 \ +// (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the +// main +// CPU? #define SX126X_BUSY (0 + 17) #define SX126X_RESET (0 + 25) // Not really an E22 but TTGO seems to be trying to clone that #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#define PIN_SPI1_MISO ST7789_MISO // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO -#define PIN_SPI1_MOSI ST7789_SDA -#define PIN_SPI1_SCK ST7789_SCK +#define PIN_SPI1_MISO \ + ST7789_MISO // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO +#define PIN_SPI1_MOSI ST7789_SDA +#define PIN_SPI1_SCK ST7789_SCK /* * GPS pins @@ -146,12 +147,12 @@ No longer populated on PCB #define GPS_L76K -#define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +#define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K #define GPS_RESET_MODE LOW #define PIN_GPS_EN (21) #define GPS_EN_ACTIVE HIGH #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake -#define PIN_GPS_PPS (32+4) +#define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS #define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU @@ -175,7 +176,7 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -//#define PIN_PWR_EN (0 + 6) +// #define PIN_PWR_EN (0 + 6) // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index 6a57a0dd6..169602a0d 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -33,7 +33,7 @@ #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 #define ADC_CHANNEL ADC1_GPIO7_CHANNEL -#define ADC_MULTIPLIER 4.9*1.03 // Voltage divider is roughly 1:1 +#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high #define USE_SX1262 diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h index 5b16d7b8f..a122a7e0f 100644 --- a/variants/heltec_vision_master_e290/variant.h +++ b/variants/heltec_vision_master_e290/variant.h @@ -21,9 +21,9 @@ */ #define SPI_INTERFACES_COUNT 2 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK #define VEXT_ENABLE 18 // powers the e-ink display #define VEXT_ON_VALUE 1 @@ -33,7 +33,7 @@ #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 #define ADC_CHANNEL ADC1_GPIO7_CHANNEL -#define ADC_MULTIPLIER 4.9*1.03 +#define ADC_MULTIPLIER 4.9 * 1.03 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high #define USE_SX1262 diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h index 01830d719..97500d357 100644 --- a/variants/heltec_vision_master_t190/variant.h +++ b/variants/heltec_vision_master_t190/variant.h @@ -33,10 +33,8 @@ // #define SCREEN_TRANSITION_FRAMERATE 5 #define BRIGHTNESS_DEFAULT 100 // Medium Low Brightnes - // #define SLEEP_TIME 120 - /* * SPI interfaces */ @@ -53,7 +51,7 @@ #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 6 #define ADC_CHANNEL ADC1_GPIO6_CHANNEL -#define ADC_MULTIPLIER 4.9*1.03 // Voltage divider is roughly 1:1 +#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high #define USE_SX1262 From e74d77dc44baac4aaa820d9cb08f319d8e309f8f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 9 Jul 2024 15:11:11 -0500 Subject: [PATCH 0690/1377] Fix macros --- .../heltec_vision_master_e213/platformio.ini | 24 +++++++++---------- .../heltec_vision_master_e290/pins_arduino.h | 2 -- .../heltec_vision_master_e290/platformio.ini | 10 ++++---- .../heltec_vision_master_t190/pins_arduino.h | 2 -- .../heltec_vision_master_t190/platformio.ini | 4 ++-- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini index 1044974c0..77cc65983 100644 --- a/variants/heltec_vision_master_e213/platformio.ini +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -3,19 +3,19 @@ extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = ${esp32s3_base.build_flags} - -I variants/heltec_vision_master_e213 - -D HELTEC_VISION_MASTER_E213 - -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 - -D EINK_WIDTH=250 - -D EINK_HEIGHT=122 - -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk - -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted - -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates - -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -Ivariants/heltec_vision_master_e213 + -DHELTEC_VISION_MASTER_E213 + -DEINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates ; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated - -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. - -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" - -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h index a9b474c31..e5d507846 100644 --- a/variants/heltec_vision_master_e290/pins_arduino.h +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -3,8 +3,6 @@ #include -#define HELTEC_VISION_MASTER_E290 true - static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini index fa89af32b..8deecf628 100644 --- a/variants/heltec_vision_master_e290/platformio.ini +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -3,11 +3,11 @@ extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = ${esp32s3_base.build_flags} - -I variants/heltec_vision_master_e290 - -D HELTEC_VISION_MASTER_E290 - -D EINK_DISPLAY_MODEL=GxEPD2_290_BS - -D EINK_WIDTH=296 - -D EINK_HEIGHT=128 + -Ivariants/heltec_vision_master_e290 + -DHELTEC_VISION_MASTER_E290 + -DEINK_DISPLAY_MODEL=GxEPD2_290_BS + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 ; -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk ; -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted ; -D EINK_LIMIT_RATE_BACKGROUND_SEC=1 ; Minimum interval between BACKGROUND updates diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/heltec_vision_master_t190/pins_arduino.h index 473187a02..e5d507846 100644 --- a/variants/heltec_vision_master_t190/pins_arduino.h +++ b/variants/heltec_vision_master_t190/pins_arduino.h @@ -3,8 +3,6 @@ #include -#define HELTEC_VISION_MASTER_T190 true - static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini index 7ed64f5e0..bbaa0075c 100644 --- a/variants/heltec_vision_master_t190/platformio.ini +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -3,8 +3,8 @@ extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = ${esp32s3_base.build_flags} - -I variants/heltec_vision_master_t190 - -D PRIVATE_HW + -Ivariants/heltec_vision_master_t190 + -DHELTEC_VISION_MASTER_T190 ; -D PRIVATE_HW lib_deps = ${esp32s3_base.lib_deps} From 8048fab08410464e09f8d30dc597fadb2775de37 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 10 Jul 2024 07:28:29 -0500 Subject: [PATCH 0691/1377] Move e290 to board level extra while CI is broken --- variants/heltec_vision_master_e290/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini index 8deecf628..60ff60036 100644 --- a/variants/heltec_vision_master_e290/platformio.ini +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -1,4 +1,5 @@ [env:heltec-vision-master-e290] +board_level = extra extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = From 5c71187db1e50ceddc976aada5ab67d90e55c4ad Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 10 Jul 2024 07:34:41 -0500 Subject: [PATCH 0692/1377] Tell trunk to ignore bin folder --- .trunk/trunk.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 8a2f18ad5..cbb4d83c9 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.22.1 + version: 1.22.2 plugins: sources: - id: trunk @@ -31,6 +31,9 @@ lint: - gitleaks@8.18.2 - clang-format@16.0.3 - prettier@3.2.5 +ignore: + - linters: [ALL] + paths: bin/** runtimes: enabled: - python@3.10.8 From c887675bb48a254608e9c9adb3f01f4cb39cc57a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 10 Jul 2024 09:35:18 -0500 Subject: [PATCH 0693/1377] Fix missing --- .github/actions/setup-base/action.yml | 2 +- .github/workflows/build_native.yml | 2 +- .github/workflows/build_raspbian.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index b5b4cb6f3..7f8659523 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -14,7 +14,7 @@ runs: - name: Install dependencies shell: bash run: | - sudo apt-get -y update + sudo apt-get -y update --fix-missing sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev - name: Setup Python diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 8fe8e6c31..3e8b4c001 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - sudo apt-get update + sudo apt-get update --fix-missing sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index d262d8739..1fd8fad30 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - apt-get update -y + apt-get update -y --fix-missing apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code From 17c2d60b789c5709d793afa406308f9864e3618c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 10 Jul 2024 12:47:34 -0500 Subject: [PATCH 0694/1377] Update trunk.yaml, fix whitespace --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index cbb4d83c9..9bfeb2c2d 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -31,7 +31,7 @@ lint: - gitleaks@8.18.2 - clang-format@16.0.3 - prettier@3.2.5 -ignore: + ignore: - linters: [ALL] paths: bin/** runtimes: From 51d54a7d99046051fbcd1f8d32d7f0547738dba7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 10 Jul 2024 14:39:41 -0500 Subject: [PATCH 0695/1377] Update trunk.yaml --- .trunk/trunk.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 9bfeb2c2d..2d9f60899 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -33,7 +33,8 @@ lint: - prettier@3.2.5 ignore: - linters: [ALL] - paths: bin/** + paths: + - bin/** runtimes: enabled: - python@3.10.8 From e59e50af0e4f216ab666df0b8e1a21093dfa0ec7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 10 Jul 2024 14:42:29 -0500 Subject: [PATCH 0696/1377] Update build_raspbian_armv7l.yml --fix-missing --- .github/workflows/build_raspbian_armv7l.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index ee5eb66eb..39b297d1b 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -13,6 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | + apt-get update -y --fix-missing apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code From 11bca437fd38534c194cf4902964c5c5bba1a09f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:15:57 -0500 Subject: [PATCH 0697/1377] [create-pull-request] automated change (#4263) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- .../generated/meshtastic/module_config.pb.h | 8 +++++--- src/mesh/generated/meshtastic/telemetry.pb.h | 18 +++++++++++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/protobufs b/protobufs index d191975eb..10494bf32 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d191975ebc572527c6d9eec48d5b0a1e3331999f +Subproject commit 10494bf328ac051fc4add9ddeb677eebf337b531 diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 12087655a..7fd57fe00 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -60,7 +60,9 @@ typedef enum _meshtastic_ModuleConfig_SerialConfig_Serial_Mode { meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG = 3, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA = 4, /* NMEA messages specifically tailored for CalTopo */ - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO = 5 + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO = 5, + /* Ecowitt WS85 weather station */ + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 = 6 } meshtastic_ModuleConfig_SerialConfig_Serial_Mode; /* TODO: REPLACE */ @@ -434,8 +436,8 @@ extern "C" { #define _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Baud)(meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_921600+1)) #define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT -#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO -#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO+1)) +#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 +#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85+1)) #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MAX meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 28d368754..82cd0a55d 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -115,6 +115,10 @@ typedef struct _meshtastic_EnvironmentMetrics { float wind_speed; /* Weight in KG */ float weight; + /* Wind gust in m/s */ + float wind_gust; + /* Wind lull in m/s */ + float wind_lull; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -205,13 +209,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -238,6 +242,8 @@ extern "C" { #define meshtastic_EnvironmentMetrics_wind_direction_tag 13 #define meshtastic_EnvironmentMetrics_wind_speed_tag 14 #define meshtastic_EnvironmentMetrics_weight_tag 15 +#define meshtastic_EnvironmentMetrics_wind_gust_tag 16 +#define meshtastic_EnvironmentMetrics_wind_lull_tag 17 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -289,7 +295,9 @@ X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \ -X(a, STATIC, SINGULAR, FLOAT, weight, 15) +X(a, STATIC, SINGULAR, FLOAT, weight, 15) \ +X(a, STATIC, SINGULAR, FLOAT, wind_gust, 16) \ +X(a, STATIC, SINGULAR, FLOAT, wind_lull, 17) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -357,10 +365,10 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 73 +#define meshtastic_EnvironmentMetrics_size 85 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 -#define meshtastic_Telemetry_size 80 +#define meshtastic_Telemetry_size 92 #ifdef __cplusplus } /* extern "C" */ From 33831cd41c92ed9684c1df731a01836d20e9b4ee Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Thu, 11 Jul 2024 15:26:43 +1200 Subject: [PATCH 0698/1377] GPS Power State tidy-up (#4161) * Refactor GPSPowerState enum Identifies a case where the GPS hardware is awake, but an update is not yet desired * Change terminology * Clear old lock-time prediction on triple press * Use exponential smoothing to predict lock time * Rename averageLockTime to predictedLockTime * Attempt: Send PMREQ with duration 0 on MCU deep-sleep * Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep * Revert "Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit 8b697cd2a445355dcfab5b33e0ce7a3128cab151. * Revert "Attempt: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit 9d29ec7603a88056b9115796b29b5023165a93bb. * Remove unused notifyGPSSleep Observable Handled with notifyDeepSleep, and enable() / disable() * WIP: simplify GPS power management An initial attempt only. * Honor #3e9e0fd * No-op when moving between GPS_IDLE and GPS_ACTIVE * Ensure U-blox GPS is awake to receive indefinite sleep command * Longer pause when waking U-blox to send sleep command * Actually implement soft and hard sleep.. * Dynamically estimate the threshold for GPS_HARDSLEEP * Fallback to GPS_HARDSLEEP, if GPS_SOFTSLEEP unsupported * Move "excessive search time" behavior to scheduler class * Minor logging adjustments * Promote log to warning * Gratuitous buffer clearing on boot * Fix inverted standby pin logic Specifically the standby pin for L76B, L76K and clones Discovered during T-Echo testing: totally broken function, probe method failing. * Remove redundant pin init Now handled by setPowerState * Replace max() with if statements Avoid those platform specific implementations.. * Trunk formatting New round of settings.json changes keep catching me out, have to remember to re-enable my "clang-format" for windows workaround. * Remove some asserts from setPowerState Original aim was to prevent sending a 0 second PMREQ to U-blox hardware as part of a timed sleep (GPS_HARDSLEEP, GPS_SOFTSLEEP). I'm not sure this is super important, and it feels tidier to just allow the 0 second sleeptime here, rather than fudge the sleeptime further up. * Fix an error determining whether GPS_SOFTSLEEP is supported * Clarify a log entry * Set PIN_STANDBY for MCU deep-sleep Required to reach TTGO's advertised 0.25mA sleep current for T-Echo. Without this change: ~6mA. --- src/gps/GPS.cpp | 529 ++++++++++++++++---------------- src/gps/GPS.h | 46 +-- src/gps/GPSUpdateScheduling.cpp | 118 +++++++ src/gps/GPSUpdateScheduling.h | 29 ++ src/sleep.cpp | 8 - src/sleep.h | 2 - 6 files changed, 436 insertions(+), 296 deletions(-) create mode 100644 src/gps/GPSUpdateScheduling.cpp create mode 100644 src/gps/GPSUpdateScheduling.h diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index ec7d725b8..8eb7fef27 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -9,6 +9,7 @@ #include "main.h" // pmu_found #include "sleep.h" +#include "GPSUpdateScheduling.h" #include "cas.h" #include "ubx.h" @@ -22,19 +23,6 @@ #define GPS_RESET_MODE HIGH #endif -// How many minutes of sleep make it worthwhile to power-off the GPS -// Shorter than this, and GPS will only enter standby -// Affected by lock-time, and config.position.gps_update_interval -#ifndef GPS_STANDBY_THRESHOLD_MINUTES -#define GPS_STANDBY_THRESHOLD_MINUTES 15 -#endif - -// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby -// Shorter than this, and we'll just wait instead -#ifndef GPS_IDLE_THRESHOLD_SECONDS -#define GPS_IDLE_THRESHOLD_SECONDS 10 -#endif - #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) HardwareSerial *GPS::_serial_gps = &Serial1; #else @@ -43,6 +31,8 @@ HardwareSerial *GPS::_serial_gps = NULL; GPS *gps = nullptr; +GPSUpdateScheduling scheduling; + /// Multiple GPS instances might use the same serial port (in sequence), but we can /// only init that port once. static bool didSerialInit; @@ -52,6 +42,25 @@ uint8_t uBloxProtocolVersion; #define GPS_SOL_EXPIRY_MS 5000 // in millis. give 1 second time to combine different sentences. NMEA Frequency isn't higher anyway #define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc) +// For logging +const char *getGPSPowerStateString(GPSPowerState state) +{ + switch (state) { + case GPS_ACTIVE: + return "ACTIVE"; + case GPS_IDLE: + return "IDLE"; + case GPS_SOFTSLEEP: + return "SOFTSLEEP"; + case GPS_HARDSLEEP: + return "HARDSLEEP"; + case GPS_OFF: + return "OFF"; + default: + assert(false); // Unhandled enum value.. + } +} + void GPS::UBXChecksum(uint8_t *message, size_t length) { uint8_t CK_A = 0, CK_B = 0; @@ -767,7 +776,6 @@ bool GPS::setup() } notifyDeepSleepObserver.observe(¬ifyDeepSleep); - notifyGPSSleepObserver.observe(¬ifyGPSSleep); return true; } @@ -776,124 +784,185 @@ GPS::~GPS() { // we really should unregister our sleep observer notifyDeepSleepObserver.unobserve(¬ifyDeepSleep); - notifyGPSSleepObserver.observe(¬ifyGPSSleep); } -const char *GPS::powerStateToString() +// Put the GPS hardware into a specified state +void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) { - switch (powerState) { - case GPS_OFF: - return "OFF"; - case GPS_IDLE: - return "IDLE"; - case GPS_STANDBY: - return "STANDBY"; + // Update the stored GPSPowerstate, and create local copies + GPSPowerState oldState = powerState; + powerState = newState; + LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState)); + + switch (newState) { case GPS_ACTIVE: - return "ACTIVE"; - default: - return "UNKNOWN"; + case GPS_IDLE: + if (oldState == GPS_ACTIVE || oldState == GPS_IDLE) // If hardware already awake, no changes needed + break; + if (oldState != GPS_ACTIVE && oldState != GPS_IDLE) // If hardware just waking now, clear buffer + clearBuffer(); + powerMon->setState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(true); // Power (EN pin): on + setPowerPMU(true); // Power (PMU): on + writePinStandby(false); // Standby (pin): awake (not standby) + setPowerUBLOX(true); // Standby (UBLOX): awake + break; + + case GPS_SOFTSLEEP: + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(true); // Power (EN pin): on + setPowerPMU(true); // Power (PMU): on + writePinStandby(true); // Standby (pin): asleep (not awake) + setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed + break; + + case GPS_HARDSLEEP: + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(false); // Power (EN pin): off + setPowerPMU(false); // Power (PMU): off + writePinStandby(true); // Standby (pin): asleep (not awake) + setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed + break; + + case GPS_OFF: + assert(sleepTime == 0); // This is an indefinite sleep + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(false); // Power (EN pin): off + setPowerPMU(false); // Power (PMU): off + writePinStandby(true); // Standby (pin): asleep + setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely + break; } } -void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) +// Set power with EN pin, if relevant +void GPS::writePinEN(bool on) { - // Record the current powerState - if (on) - powerState = GPS_ACTIVE; - else if (!enabled) // User has disabled with triple press - powerState = GPS_OFF; - else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL) - powerState = GPS_IDLE; - else if (standbyOnly) - powerState = GPS_STANDBY; - else - powerState = GPS_OFF; - - LOG_DEBUG("GPS::powerState=%s\n", powerStateToString()); - - // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out. - if (!on && powerState == GPS_IDLE) + // Abort: if conflict with Canned Messages when using Wisblock(?) + if (HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1)) return; - if (on) { - powerMon->setState(meshtastic_PowerMon_State_GPS_Active); - clearBuffer(); // drop any old data waiting in the buffer before re-enabling - if (en_gpio) - digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time - } else { - powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); - } - isInPowersave = !on; - if (!standbyOnly && en_gpio != 0 && - !(HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1))) { - LOG_DEBUG("GPS powerdown using GPS_EN_ACTIVE\n"); - digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); + // Abort: if pin unset + if (!en_gpio) return; - } -#ifdef HAS_PMU // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, so treat as a standby. - if (pmu_found && PMU) { - uint8_t model = PMU->getChipModel(); - if (model == XPOWERS_AXP2101) { - if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { - // t-beam v1.2 GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3); - } else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) { - // t-beam-s3-core GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4); - } - } else if (model == XPOWERS_AXP192) { - // t-beam v1.1 GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3); - } - return; - } + + // Determine new value for the pin + bool val = GPS_EN_ACTIVE ? on : !on; + + // Write and log + pinMode(en_gpio, OUTPUT); + digitalWrite(en_gpio, val); +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("Pin EN %s\n", val == HIGH ? "HIGH" : "LOW"); #endif +} + +// Set the value of the STANDBY pin, if relevant +// true for standby state, false for awake +void GPS::writePinStandby(bool standby) +{ #ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones - if (on) { - LOG_INFO("Waking GPS\n"); - pinMode(PIN_GPS_STANDBY, OUTPUT); - // Some PCB's use an inverse logic due to a transistor driver - // Example for this is the Pico-Waveshare Lora+GPS HAT -#ifdef PIN_GPS_STANDBY_INVERTED - digitalWrite(PIN_GPS_STANDBY, 0); + +// Determine the new value for the pin +// Normally: active HIGH for awake +#if PIN_GPS_STANDBY_INVERTED + bool val = standby; #else - digitalWrite(PIN_GPS_STANDBY, 1); + bool val = !standby; #endif - return; - } else { - LOG_INFO("GPS entering sleep\n"); - // notifyGPSSleep.notifyObservers(NULL); - pinMode(PIN_GPS_STANDBY, OUTPUT); -#ifdef PIN_GPS_STANDBY_INVERTED - digitalWrite(PIN_GPS_STANDBY, 1); -#else - digitalWrite(PIN_GPS_STANDBY, 0); + + // Write and log + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, val); +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("Pin STANDBY %s\n", val == HIGH ? "HIGH" : "LOW"); #endif +#endif +} + +// Enable / Disable GPS with PMU, if present +void GPS::setPowerPMU(bool on) +{ + // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, + // so treat as a standby. +#ifdef HAS_PMU + // Abort: if no PMU + if (!pmu_found) return; + + // Abort: if PMU not initialized + if (!PMU) + return; + + uint8_t model = PMU->getChipModel(); + if (model == XPOWERS_AXP2101) { + if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { + // t-beam v1.2 GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3); + } else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) { + // t-beam-s3-core GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4); + } + } else if (model == XPOWERS_AXP192) { + // t-beam v1.1 GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3); } + +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("PMU %s\n", on ? "on" : "off"); #endif - if (!on) { - if (gnssModel == GNSS_MODEL_UBLOX) { - uint8_t msglen; - LOG_DEBUG("Sleep Time: %i\n", sleepTime); - if (strncmp(info.hwVersion, "000A0000", 8) != 0) { - for (int i = 0; i < 4; i++) { - gps->_message_PMREQ[0 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet - } - msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ); - } else { - for (int i = 0; i < 4; i++) { - gps->_message_PMREQ_10[4 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet - } - msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10); - } - gps->_serial_gps->write(gps->UBXscratch, msglen); +#endif +} + +// Set UBLOX power, if relevant +void GPS::setPowerUBLOX(bool on, uint32_t sleepMs) +{ + // Abort: if not UBLOX hardware + if (gnssModel != GNSS_MODEL_UBLOX) + return; + + // If waking + if (on) { + gps->_serial_gps->write(0xFF); + clearBuffer(); // This often returns old data, so drop it +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("UBLOX: wake\n"); +#endif + } + + // If putting to sleep + else { + uint8_t msglen; + + // If we're being asked to sleep indefinitely, make *sure* we're awake first, to process the new sleep command + if (sleepMs == 0) { + setPowerUBLOX(true); + delay(500); } - } else { - if (gnssModel == GNSS_MODEL_UBLOX) { - gps->_serial_gps->write(0xFF); - clearBuffer(); // This often returns old data, so drop it + + // Determine hardware version + if (strncmp(info.hwVersion, "000A0000", 8) != 0) { + // Encode the sleep time in millis into the packet + for (int i = 0; i < 4; i++) + gps->_message_PMREQ[0 + i] = sleepMs >> (i * 8); + + // Record the message length + msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ); + } else { + // Encode the sleep time in millis into the packet + for (int i = 0; i < 4; i++) + gps->_message_PMREQ_10[4 + i] = sleepMs >> (i * 8); + + // Record the message length + msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10); } + + // Send the UBX packet + gps->_serial_gps->write(gps->UBXscratch, msglen); + +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("UBLOX: sleep for %dmS\n", sleepMs); +#endif } } @@ -906,108 +975,54 @@ void GPS::setConnected() } } -/** - * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode - * - * calls sleep/wake - */ -void GPS::setAwake(bool wantAwake) +// We want a GPS lock. Wake the hardware +void GPS::up() { + scheduling.informSearching(); + setPowerState(GPS_ACTIVE); +} - // If user has disabled GPS, make sure it is off, not just in standby or idle - if (!wantAwake && !enabled && powerState != GPS_OFF) { - setGPSPower(false, false, 0); - return; - } +// We've got a GPS lock. Enter a low power state, potentially. +void GPS::down() +{ + scheduling.informGotLock(); + uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs(); + uint32_t sleepTime = scheduling.msUntilNextSearch(); + uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); - // If GPS power state needs to change - if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) { - LOG_DEBUG("WANT GPS=%d\n", wantAwake); + LOG_DEBUG("%us until next search\n", sleepTime / 1000); - // Calculate how long it takes to get a GPS lock - if (wantAwake) { - // Record the time we start looking for a lock - lastWakeStartMsec = millis(); - } else { - // Record by how much we missed our ideal target postion.gps_update_interval (for logging only) - // Need to calculate this before we update lastSleepStartMsec, to make the new prediction - int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime(); + // If update interval less than 10 seconds, no attempt to sleep + if (updateInterval <= 10 * 1000UL) + setPowerState(GPS_IDLE); - // Record the time we finish looking for a lock - lastSleepStartMsec = millis(); - - // How long did it take to get GPS lock this time? - uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec; - - // Update the lock-time prediction - // Used pre-emptively, attempting to hit target of gps.position_update_interval - switch (GPSCycles) { - case 0: - LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000); - break; - case 1: - predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value - LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000); - break; - default: - // Predict lock-time using exponential smoothing: respond slowly to changes - predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction - LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000, - (lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000); - } - GPSCycles++; - } - - // How long to wait before attempting next GPS update - // Aims to hit position.gps_update_interval by using the lock-time prediction - uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0; - - // If long interval between updates: power off between updates - if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) { - setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime); - } - - // If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time - // We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due - // Will decide which inside setGPSPower method - else { -#ifdef GPS_UC6580 - setGPSPower(wantAwake, false, compensatedSleepTime); -#else - setGPSPower(wantAwake, true, compensatedSleepTime); + else { + // Check whether the GPS hardware is capable of GPS_SOFTSLEEP + // If not, fallback to GPS_HARDSLEEP instead + bool softsleepSupported = false; + if (gnssModel == GNSS_MODEL_UBLOX) // U-blox is supported via PMREQ + softsleepSupported = true; +#ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin + softsleepSupported = true; #endif - } + + // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP? + // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050 + // https://www.desmos.com/calculator/6gvjghoumr + // This is not particularly accurate, but probably an impromevement over a single, fixed threshold + uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22)); + LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000); + + // If update interval too short: softsleep (if supported by hardware) + if (softsleepSupported && updateInterval < hardsleepThreshold) + setPowerState(GPS_SOFTSLEEP, sleepTime); + + // If update interval long enough (or softsleep unsupported): hardsleep instead + else + setPowerState(GPS_HARDSLEEP, sleepTime); } } -/** Get how long we should stay looking for each acquisition in msecs - */ -uint32_t GPS::getWakeTime() const -{ - uint32_t t = config.position.position_broadcast_secs; - - if (t == UINT32_MAX) - return t; // already maxint - - return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); -} - -/** Get how long we should sleep between aqusition attempts in msecs - */ -uint32_t GPS::getSleepTime() const -{ - uint32_t t = config.position.gps_update_interval; - - // We'll not need the GPS thread to wake up again after first acq. with fixed position. - if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED || config.position.fixed_position) - t = UINT32_MAX; // Sleep forever now - - if (t == UINT32_MAX) - return t; // already maxint - - return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval); -} - void GPS::publishUpdate() { if (shouldPublish) { @@ -1056,13 +1071,13 @@ int32_t GPS::runOnce() return disable(); } - if (whileIdle()) { + if (whileActive()) { // if we have received valid NMEA claim we are connected setConnected(); } else { if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) { // reset the GPS on next bootup - if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) { + if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); devicestate.did_gps_reset = false; nodeDB->saveDeviceStateToDisk(); @@ -1077,54 +1092,43 @@ int32_t GPS::runOnce() // gps->factoryReset(); } - // If we are overdue for an update, turn on the GPS and at least publish the current status - uint32_t now = millis(); - uint32_t timeAsleep = now - lastSleepStartMsec; + // If we're due for an update, wake the GPS + if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue()) + up(); - auto sleepTime = getSleepTime(); - if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) && - ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) { - // We now want to be awake - so wake up the GPS - setAwake(true); + // If we've already set time from the GPS, no need to ask the GPS + bool gotTime = (getRTCQuality() >= RTCQualityGPS); + if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time + gotTime = true; + shouldPublish = true; } - // While we are awake - if (powerState == GPS_ACTIVE) { - // LOG_DEBUG("looking for location\n"); - // If we've already set time from the GPS, no need to ask the GPS - bool gotTime = (getRTCQuality() >= RTCQualityGPS); - if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time - gotTime = true; - shouldPublish = true; - } + bool gotLoc = lookForLocation(); + if (gotLoc && !hasValidLocation) { // declare that we have location ASAP + LOG_DEBUG("hasValidLocation RISING EDGE\n"); + hasValidLocation = true; + shouldPublish = true; + } - bool gotLoc = lookForLocation(); - if (gotLoc && !hasValidLocation) { // declare that we have location ASAP - LOG_DEBUG("hasValidLocation RISING EDGE\n"); - hasValidLocation = true; - shouldPublish = true; - } + bool tooLong = scheduling.searchedTooLong(); + if (tooLong) + LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time.\n"); - now = millis(); - auto wakeTime = getWakeTime(); - bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime; + // Once we get a location we no longer desperately want an update + // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); + if ((gotLoc && gotTime) || tooLong) { - // Once we get a location we no longer desperately want an update - // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); - if ((gotLoc && gotTime) || tooLong) { - - if (tooLong) { - // we didn't get a location during this ack window, therefore declare loss of lock - if (hasValidLocation) { - LOG_DEBUG("hasValidLocation FALLING EDGE (last read: %d)\n", gotLoc); - } - p = meshtastic_Position_init_default; - hasValidLocation = false; + if (tooLong) { + // we didn't get a location during this ack window, therefore declare loss of lock + if (hasValidLocation) { + LOG_DEBUG("hasValidLocation FALLING EDGE\n"); } - - setAwake(false); - shouldPublish = true; // publish our update for this just finished acquisition window + p = meshtastic_Position_init_default; + hasValidLocation = false; } + + down(); + shouldPublish = true; // publish our update for this just finished acquisition window } // If state has changed do a publish @@ -1150,9 +1154,7 @@ void GPS::clearBuffer() int GPS::prepareDeepSleep(void *unused) { LOG_INFO("GPS deep sleep!\n"); - - setAwake(false); - + disable(); return 0; } @@ -1348,12 +1350,6 @@ GPS *GPS::createGps() new_gps->tx_gpio = _tx_gpio; new_gps->en_gpio = _en_gpio; - if (_en_gpio != 0) { - LOG_DEBUG("Setting %d to output.\n", _en_gpio); - pinMode(_en_gpio, OUTPUT); - digitalWrite(_en_gpio, !GPS_EN_ACTIVE); - } - #ifdef PIN_GPS_PPS // pulse per second pinMode(PIN_GPS_PPS, INPUT); @@ -1368,7 +1364,8 @@ GPS *GPS::createGps() LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n"); #endif - new_gps->setGPSPower(true, false, 0); + // Make sure the GPS is awake before performing any init. + new_gps->up(); #ifdef PIN_GPS_RESET pinMode(PIN_GPS_RESET, OUTPUT); @@ -1376,7 +1373,6 @@ GPS *GPS::createGps() delay(10); digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); #endif - new_gps->setAwake(true); // Wake GPS power before doing any init if (_serial_gps) { #ifdef ARCH_ESP32 @@ -1662,13 +1658,13 @@ bool GPS::hasFlow() return reader.passedChecksum() > 0; } -bool GPS::whileIdle() +bool GPS::whileActive() { unsigned int charsInBuf = 0; bool isValid = false; if (powerState != GPS_ACTIVE) { clearBuffer(); - return (powerState == GPS_ACTIVE); + return false; } #ifdef SERIAL_BUFFER_SIZE if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) { @@ -1699,20 +1695,21 @@ bool GPS::whileIdle() } void GPS::enable() { - // Clear the old lock-time prediction - GPSCycles = 0; - predictedLockTime = 0; + // Clear the old scheduling info (reset the lock-time prediction) + scheduling.reset(); enabled = true; setInterval(GPS_THREAD_INTERVAL); - setAwake(true); + + scheduling.informSearching(); + setPowerState(GPS_ACTIVE); } int32_t GPS::disable() { enabled = false; setInterval(INT32_MAX); - setAwake(false); + setPowerState(GPS_OFF); return INT32_MAX; } @@ -1721,11 +1718,11 @@ void GPS::toggleGpsMode() { if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; - LOG_DEBUG("Flag set to false for gps power. GpsMode: DISABLED\n"); + LOG_INFO("User toggled GpsMode. Now DISABLED.\n"); disable(); } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; - LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n"); + LOG_INFO("User toggled GpsMode. Now ENABLED\n"); enable(); } } diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 6afbd4fab..7cbf771bc 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -39,10 +39,11 @@ typedef enum { } GPS_RESPONSE; enum GPSPowerState : uint8_t { - GPS_OFF = 0, // Physically powered off - GPS_ACTIVE = 1, // Awake and want a position - GPS_STANDBY = 2, // Physically powered on, but soft-sleeping - GPS_IDLE = 3, // Awake, but not wanting another position yet + GPS_ACTIVE, // Awake and want a position + GPS_IDLE, // Awake, but not wanting another position yet + GPS_SOFTSLEEP, // Physically powered on, but soft-sleeping + GPS_HARDSLEEP, // Physically powered off, but scheduled to wake + GPS_OFF // Powered off indefinitely }; // Generate a string representation of DOP @@ -67,14 +68,11 @@ class GPS : private concurrency::OSThread uint8_t fixType = 0; // fix type from GPGSA #endif private: - uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0; const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600}; uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; uint32_t en_gpio = 0; - uint32_t predictedLockTime = 0; - uint32_t GPSCycles = 0; int speedSelect = 0; int probeTries = 2; @@ -99,7 +97,6 @@ class GPS : private concurrency::OSThread uint8_t numSatellites = 0; CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); - CallbackObserver notifyGPSSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); public: /** If !NULL we will use this serial port to construct our GPS */ @@ -175,7 +172,8 @@ class GPS : private concurrency::OSThread // toggle between enabled/disabled void toggleGpsMode(); - void setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime); + // Change the power state of the GPS - for power saving / shutdown + void setPowerState(GPSPowerState newState, uint32_t sleepMs = 0); /// Returns true if we have acquired GPS lock. virtual bool hasLock(); @@ -206,18 +204,18 @@ class GPS : private concurrency::OSThread GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis); - /** - * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode - * - * calls sleep/wake - */ - void setAwake(bool on); virtual bool factoryReset(); // Creates an instance of the GPS class. // Returns the new instance or null if the GPS is not present. static GPS *createGps(); + // Wake the GPS hardware - ready for an update + void up(); + + // Let the GPS hardware save power between updates + void down(); + protected: /** * Perform any processing that should be done only while the GPS is awake and looking for a fix. @@ -240,7 +238,7 @@ class GPS : private concurrency::OSThread * * Return true if we received a valid message from the GPS */ - virtual bool whileIdle(); + virtual bool whileActive(); /** * Perform any processing that should be done only while the GPS is awake and looking for a fix. @@ -267,13 +265,21 @@ class GPS : private concurrency::OSThread void UBXChecksum(uint8_t *message, size_t length); void CASChecksum(uint8_t *message, size_t length); - /** Get how long we should stay looking for each aquisition + /** Set power with EN pin, if relevant */ - uint32_t getWakeTime() const; + void writePinEN(bool on); - /** Get how long we should sleep between aqusition attempts + /** Set the value of the STANDBY pin, if relevant */ - uint32_t getSleepTime() const; + void writePinStandby(bool standby); + + /** Set GPS power with PMU, if relevant + */ + void setPowerPMU(bool on); + + /** Set UBLOX power, if relevant + */ + void setPowerUBLOX(bool on, uint32_t sleepMs = 0); /** * Tell users we have new GPS readings diff --git a/src/gps/GPSUpdateScheduling.cpp b/src/gps/GPSUpdateScheduling.cpp new file mode 100644 index 000000000..949ef6039 --- /dev/null +++ b/src/gps/GPSUpdateScheduling.cpp @@ -0,0 +1,118 @@ +#include "GPSUpdateScheduling.h" + +#include "Default.h" + +// Mark the time when searching for GPS position begins +void GPSUpdateScheduling::informSearching() +{ + searchStartedMs = millis(); +} + +// Mark the time when searching for GPS is complete, +// then update the predicted lock-time +void GPSUpdateScheduling::informGotLock() +{ + searchEndedMs = millis(); + LOG_DEBUG("Took %us to get lock\n", (searchEndedMs - searchStartedMs) / 1000); + updateLockTimePrediction(); +} + +// Clear old lock-time prediction data. +// When re-enabling GPS with user button. +void GPSUpdateScheduling::reset() +{ + searchStartedMs = 0; + searchEndedMs = 0; + searchCount = 0; + predictedMsToGetLock = 0; +} + +// How many milliseconds before we should next search for GPS position +// Used by GPS hardware directly, to enter timed hardware sleep +uint32_t GPSUpdateScheduling::msUntilNextSearch() +{ + uint32_t now = millis(); + + // Target interval (seconds), between GPS updates + uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval); + + // Check how long until we should start searching, to hopefully hit our target interval + uint32_t dueAtMs = searchEndedMs + updateInterval; + uint32_t compensatedStart = dueAtMs - predictedMsToGetLock; + int32_t remainingMs = compensatedStart - now; + + // If we should have already started (negative value), start ASAP + if (remainingMs < 0) + remainingMs = 0; + + return (uint32_t)remainingMs; +} + +// How long have we already been searching? +// Used to abort a search in progress, if it runs unnaceptably long +uint32_t GPSUpdateScheduling::elapsedSearchMs() +{ + // If searching + if (searchStartedMs > searchEndedMs) + return millis() - searchStartedMs; + + // If not searching - 0ms. We shouldn't really consume this value + else + return 0; +} + +// Is it now time to begin searching for a GPS position? +bool GPSUpdateScheduling::isUpdateDue() +{ + return (msUntilNextSearch() == 0); +} + +// Have we been searching for a GPS position for too long? +bool GPSUpdateScheduling::searchedTooLong() +{ + uint32_t maxSearchMs = + Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); + + // If broadcast interval set to max, no such thing as "too long" + if (maxSearchMs == UINT32_MAX) + return false; + + // If we've been searching longer than our position broadcast interval: that's too long + else if (elapsedSearchMs() > maxSearchMs) + return true; + + // Otherwise, not too long yet! + else + return false; +} + +// Updates the predicted time-to-get-lock, by exponentially smoothing the latest observation +void GPSUpdateScheduling::updateLockTimePrediction() +{ + + // How long did it take to get GPS lock this time? + // Duration between down() calls + int32_t lockTime = searchEndedMs - searchStartedMs; + if (lockTime < 0) + lockTime = 0; + + // Ignore the first lock-time: likely to be long, will skew data + + // Second locktime: likely stable. Use to intialize the smoothing filter + if (searchCount == 1) + predictedMsToGetLock = lockTime; + + // Third locktime and after: predict using exponential smoothing. Respond slowly to changes + else if (searchCount > 1) + predictedMsToGetLock = (lockTime * weighting) + (predictedMsToGetLock * (1 - weighting)); + + searchCount++; // Only tracked so we can diregard initial lock-times + + LOG_DEBUG("Predicting %us to get next lock\n", predictedMsToGetLock / 1000); +} + +// How long do we expect to spend searching for a lock? +uint32_t GPSUpdateScheduling::predictedSearchDurationMs() +{ + return GPSUpdateScheduling::predictedMsToGetLock; +} \ No newline at end of file diff --git a/src/gps/GPSUpdateScheduling.h b/src/gps/GPSUpdateScheduling.h new file mode 100644 index 000000000..7e121c9b6 --- /dev/null +++ b/src/gps/GPSUpdateScheduling.h @@ -0,0 +1,29 @@ +#pragma once + +#include "configuration.h" + +// Encapsulates code responsible for the timing of GPS updates +class GPSUpdateScheduling +{ + public: + // Marks the time of these events, for calculation use + void informSearching(); + void informGotLock(); // Predicted lock-time is recalculated here + + void reset(); // Reset the prediction - after GPS::disable() / GPS::enable() + bool isUpdateDue(); // Is it time to begin searching for a GPS position? + bool searchedTooLong(); // Have we been searching for too long? + + uint32_t msUntilNextSearch(); // How long until we need to begin searching for a GPS? Info provided to GPS hardware for sleep + uint32_t elapsedSearchMs(); // How long have we been searching so far? + uint32_t predictedSearchDurationMs(); // How long do we expect to spend searching for a lock? + + private: + void updateLockTimePrediction(); // Called from informGotLock + uint32_t searchStartedMs = 0; + uint32_t searchEndedMs = 0; + uint32_t searchCount = 0; + uint32_t predictedMsToGetLock = 0; + + const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time". +}; \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index 721c7d188..3793ee0cf 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -37,10 +37,7 @@ Observable preflightSleep; /// Called to tell observers we are now entering sleep and you should prepare. Must return 0 /// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep -/// notifyGPSSleep will be called when config.position.gps_enabled is set to 0 or from buttonthread when GPS_POWER_TOGGLE is -/// enabled. Observable notifySleep, notifyDeepSleep; -Observable notifyGPSSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; @@ -240,11 +237,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #ifdef PIN_POWER_EN pinMode(PIN_POWER_EN, INPUT); // power off peripherals // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); -#endif -#if HAS_GPS - // Kill GPS power completely (even if previously we just had it in sleep mode) - if (gps) - gps->setGPSPower(false, false, 0); #endif setLed(false); diff --git a/src/sleep.h b/src/sleep.h index 8d5b9a94f..f154b8d44 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -41,8 +41,6 @@ extern Observable notifySleep; /// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 extern Observable notifyDeepSleep; -/// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 -extern Observable notifyGPSSleep; void enableModemSleep(); #ifdef ARCH_ESP32 void enableLoraInterrupt(); From e79a7dce070472317a57623d82700b26b7e1342b Mon Sep 17 00:00:00 2001 From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:34:33 +0800 Subject: [PATCH 0699/1377] Optimize the shutdown current of RAK10701 to around 25uA (#4260) Co-authored-by: Jonathan Bennett --- src/platform/nrf52/main-nrf52.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index b79f28f13..c5b217f0e 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -234,6 +234,18 @@ void cpuDeepSleep(uint32_t msecToWake) // 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); +#endif #endif // Sleepy trackers or sensors can low power "sleep" // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event From 974fc318560b1cee4da69820c7d10819b9af593c Mon Sep 17 00:00:00 2001 From: Warren Guy <5602790+warrenguy@users.noreply.github.com> Date: Thu, 11 Jul 2024 14:34:55 +0100 Subject: [PATCH 0700/1377] INA3221 sensor: use for bus voltage & environment metrics (#4215) * use INA3221 for bus voltage; fixes for telemetry variants - add to sensors available for environment telemetry (to report voltage/current) - add vars to define channels to use for battery voltage (for getBusVoltage) and environment metrics (default to CH1 for both) - write to the correct fields on the measurement struct depending on the measurement variant, and DRY up the sensor measurement collection code a bit - this might be suitable for a common implementation for the INA* sensors in a future PR... * formatting * derp --------- Co-authored-by: Ben Meadors --- src/Power.cpp | 5 ++ .../Telemetry/EnvironmentTelemetry.cpp | 11 ++++ .../Telemetry/Sensor/INA3221Sensor.cpp | 65 ++++++++++++++++--- src/modules/Telemetry/Sensor/INA3221Sensor.h | 25 +++++++ 4 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 950ea741d..19c5c9937 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -449,6 +449,11 @@ class AnalogBatteryLevel : public HasBatteryLevel if (!ina260Sensor.isInitialized()) return ina260Sensor.runOnce() > 0; return ina260Sensor.isRunning(); + } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first == + config.power.device_battery_ina_address) { + if (!ina3221Sensor.isInitialized()) + return ina3221Sensor.runOnce() > 0; + return ina3221Sensor.isRunning(); } return false; } diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index d37bb754d..23200fd00 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -115,6 +115,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); + if (ina3221Sensor.hasSensor()) + result = ina3221Sensor.runOnce(); if (veml7700Sensor.hasSensor()) result = veml7700Sensor.runOnce(); if (tsl2591Sensor.hasSensor()) @@ -325,6 +327,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && ina260Sensor.getMetrics(m); hasSensor = true; } + if (ina3221Sensor.hasSensor()) { + valid = valid && ina3221Sensor.getMetrics(m); + hasSensor = true; + } if (veml7700Sensor.hasSensor()) { valid = valid && veml7700Sensor.getMetrics(m); hasSensor = true; @@ -499,6 +505,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (ina3221Sensor.hasSensor()) { + result = ina3221Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } if (veml7700Sensor.hasSensor()) { result = veml7700Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index edd29682e..dec99c551 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -27,22 +27,69 @@ int32_t INA3221Sensor::runOnce() void INA3221Sensor::setup() {} +struct _INA3221Measurement INA3221Sensor::getMeasurement(ina3221_ch_t ch) +{ + struct _INA3221Measurement measurement; + + measurement.voltage = ina3221.getVoltage(ch); + measurement.current = ina3221.getCurrent(ch); + + return measurement; +} + +struct _INA3221Measurements INA3221Sensor::getMeasurements() +{ + struct _INA3221Measurements measurements; + + // INA3221 has 3 channels starting from 0 + for (int i = 0; i < 3; i++) { + measurements.measurements[i] = getMeasurement((ina3221_ch_t)i); + } + + return measurements; +} + bool INA3221Sensor::getMetrics(meshtastic_Telemetry *measurement) { - measurement->variant.environment_metrics.voltage = ina3221.getVoltage(INA3221_CH1); - measurement->variant.environment_metrics.current = ina3221.getCurrent(INA3221_CH1); - measurement->variant.power_metrics.ch1_voltage = ina3221.getVoltage(INA3221_CH1); - measurement->variant.power_metrics.ch1_current = ina3221.getCurrent(INA3221_CH1); - measurement->variant.power_metrics.ch2_voltage = ina3221.getVoltage(INA3221_CH2); - measurement->variant.power_metrics.ch2_current = ina3221.getCurrent(INA3221_CH2); - measurement->variant.power_metrics.ch3_voltage = ina3221.getVoltage(INA3221_CH3); - measurement->variant.power_metrics.ch3_current = ina3221.getCurrent(INA3221_CH3); + switch (measurement->which_variant) { + case meshtastic_Telemetry_environment_metrics_tag: + return getEnvironmentMetrics(measurement); + + case meshtastic_Telemetry_power_metrics_tag: + return getPowerMetrics(measurement); + } + + // unsupported metric + return false; +} + +bool INA3221Sensor::getEnvironmentMetrics(meshtastic_Telemetry *measurement) +{ + struct _INA3221Measurement m = getMeasurement(ENV_CH); + + measurement->variant.environment_metrics.voltage = m.voltage; + measurement->variant.environment_metrics.current = m.current; + + return true; +} + +bool INA3221Sensor::getPowerMetrics(meshtastic_Telemetry *measurement) +{ + struct _INA3221Measurements m = getMeasurements(); + + measurement->variant.power_metrics.ch1_voltage = m.measurements[INA3221_CH1].voltage; + measurement->variant.power_metrics.ch1_current = m.measurements[INA3221_CH1].current; + measurement->variant.power_metrics.ch2_voltage = m.measurements[INA3221_CH2].voltage; + measurement->variant.power_metrics.ch2_current = m.measurements[INA3221_CH2].current; + measurement->variant.power_metrics.ch3_voltage = m.measurements[INA3221_CH3].voltage; + measurement->variant.power_metrics.ch3_current = m.measurements[INA3221_CH3].current; + return true; } uint16_t INA3221Sensor::getBusVoltageMv() { - return lround(ina3221.getVoltage(INA3221_CH1) * 1000); + return lround(ina3221.getVoltage(BAT_CH) * 1000); } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h index 3b8e382ee..d5121aab6 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.h +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h @@ -12,6 +12,21 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor private: INA3221 ina3221 = INA3221(INA3221_ADDR42_SDA); + // channel to report voltage/current for environment metrics + ina3221_ch_t ENV_CH = INA3221_CH1; + + // channel to report battery voltage for device_battery_ina_address + ina3221_ch_t BAT_CH = INA3221_CH1; + + // get a single measurement for a channel + struct _INA3221Measurement getMeasurement(ina3221_ch_t ch); + + // get all measurements for all channels + struct _INA3221Measurements getMeasurements(); + + bool getEnvironmentMetrics(meshtastic_Telemetry *measurement); + bool getPowerMetrics(meshtastic_Telemetry *measurement); + protected: void setup() override; @@ -22,4 +37,14 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor virtual uint16_t getBusVoltageMv() override; }; +struct _INA3221Measurement { + float voltage; + float current; +}; + +struct _INA3221Measurements { + // INA3221 has 3 channels + struct _INA3221Measurement measurements[3]; +}; + #endif \ No newline at end of file From df194ca0f06bb3546c6fb25c4ab1a40ec2702b74 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 11 Jul 2024 14:08:31 -0500 Subject: [PATCH 0701/1377] WM1110 SDK kit enter serial DFU and add deployment packages (#4266) * Switch default upload protocol to nrfutil so that pio generates zip deploy packages * Enter serial DFU on SDK board * Remove guard for DFU zip from SDK build * NRF_USE_SERIAL_DFU macro instead --- bin/build-nrf52.sh | 10 +++------- src/platform/nrf52/main-nrf52.cpp | 5 +++++ variants/wio-sdk-wm1110/platformio.ini | 2 +- variants/wio-sdk-wm1110/variant.h | 2 ++ 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 077e2af35..cf4ca60cb 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -25,13 +25,9 @@ pio run --environment $1 # -v SRCELF=.pio/build/$1/firmware.elf cp $SRCELF $OUTDIR/$basename.elf -if (echo $1 | grep -q "wio-sdk-wm1110"); then - echo "Skipping dfu file" -else - echo "Generating NRF52 dfu file" - DFUPKG=.pio/build/$1/firmware.zip - cp $DFUPKG $OUTDIR/$basename-ota.zip -fi +echo "Generating NRF52 dfu file" +DFUPKG=.pio/build/$1/firmware.zip +cp $DFUPKG $OUTDIR/$basename-ota.zip echo "Generating NRF52 uf2 file" SRCHEX=.pio/build/$1/firmware.hex diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index c5b217f0e..7334f3a04 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -286,5 +286,10 @@ void clearBonds() void enterDfuMode() { +// SDK kit does not have native USB like almost all other NRF52 boards +#ifdef NRF_USE_SERIAL_DFU + enterSerialDfu(); +#else enterUf2Dfu(); +#endif } \ No newline at end of file diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index dc7d47310..766717428 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -20,7 +20,7 @@ debug_tool = jlink ; No need to reflash if the binary hasn't changed debug_load_mode = modified ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -upload_protocol = jlink +upload_protocol = nrfutil ;upload_protocol = stlink ; we prefer to stop in setup() because we are an 'ardiuno' app debug_init_break = tbreak setup diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h index 8ad8c769a..8f66b1f8c 100644 --- a/variants/wio-sdk-wm1110/variant.h +++ b/variants/wio-sdk-wm1110/variant.h @@ -107,6 +107,8 @@ extern "C" { #define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37 +#define NRF_USE_SERIAL_DFU + #ifdef __cplusplus } #endif From eabec5ae3451c0edc33979453c8af635d538c538 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 12 Jul 2024 11:51:26 +1200 Subject: [PATCH 0702/1377] Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors Co-authored-by: slash-bit --- src/graphics/Screen.cpp | 134 ++++++++++++++++++++++++---- src/graphics/Screen.h | 35 +++++++- src/mesh/MeshModule.cpp | 15 +++- src/mesh/MeshModule.h | 28 +++++- src/modules/AdminModule.cpp | 1 + src/modules/AdminModule.h | 2 +- src/modules/CannedMessageModule.cpp | 45 ++++++---- src/modules/CannedMessageModule.h | 6 +- src/modules/WaypointModule.cpp | 18 +++- src/modules/esp32/AudioModule.cpp | 6 +- 10 files changed, 239 insertions(+), 51 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 7f9c905a8..b2059b71c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -41,6 +41,7 @@ along with this program. If not, see . #include "mesh/Channels.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" #include "meshUtils.h" +#include "modules/AdminModule.h" #include "modules/ExternalNotificationModule.h" #include "modules/TextMessageModule.h" #include "sleep.h" @@ -1725,6 +1726,7 @@ void Screen::setup() powerStatusObserver.observe(&powerStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus); + adminMessageObserver.observe(adminModule); if (textMessageModule) textMessageObserver.observe(textMessageModule); if (inputBroker) @@ -1953,9 +1955,6 @@ void Screen::setWelcomeFrames() /// Determine which screensaver frame to use, then set the FrameCallback void Screen::setScreensaverFrames(FrameCallback einkScreensaver) { - // Remember current frame, restore position at power-on - uint8_t frameNumber = ui->getUiState()->currentFrame; - // Retain specified frame / overlay callback beyond scope of this method static FrameCallback screensaverFrame; static OverlayCallback screensaverOverlay; @@ -1993,9 +1992,8 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) #endif // Prepare now for next frame, shown when display wakes - ui->setOverlays(NULL, 0); // Clear overlay - setFrames(); // Return to normal display updates - ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on + ui->setOverlays(NULL, 0); // Clear overlay + setFrames(FOCUS_PRESERVE); // Return to normal display updates, showing same frame as before screensaver, ideally // Pick a refresh method, for when display wakes #ifdef EINK_HASQUIRK_GHOSTING @@ -2006,9 +2004,13 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) } #endif -// restore our regular frame list -void Screen::setFrames() +// Regenerate the normal set of frames, focusing a specific frame if requested +// Called when a frame should be added / removed, or custom frames should be cleared +void Screen::setFrames(FrameFocus focus) { + uint8_t originalPosition = ui->getUiState()->currentFrame; + FramesetInfo fsi; // Location of specific frames, for applying focus parameter + LOG_DEBUG("showing standard frames\n"); showingNormalScreen = true; @@ -2042,20 +2044,33 @@ void Screen::setFrames() // is the same offset into the moduleFrames vector // so that we can invoke the module's callback for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { - normalFrames[numframes++] = drawModuleFrame; + // Draw the module frame, using the hack described above + normalFrames[numframes] = drawModuleFrame; + + // Check if the module being drawn has requested focus + // We will honor this request later, if setFrames was triggered by a UIFrameEvent + MeshModule *m = *i; + if (m->isRequestingFocus()) + fsi.positions.focusedModule = numframes; + + numframes++; } LOG_DEBUG("Added modules. numframes: %d\n", numframes); // If we have a critical fault, show it first - if (error_code) + fsi.positions.fault = numframes; + if (error_code) { normalFrames[numframes++] = drawCriticalFaultFrame; + focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame + } #ifdef T_WATCH_S3 normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame; #endif // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules + fsi.positions.textMessage = numframes; if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { normalFrames[numframes++] = drawTextMessageFrame; } @@ -2070,11 +2085,14 @@ void Screen::setFrames() // // Since frames are basic function pointers, we have to use a helper to // call a method on debugInfo object. + fsi.positions.log = numframes; normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; // call a method on debugInfoScreen object (for more details) + fsi.positions.settings = numframes; normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; + fsi.positions.wifi = numframes; #if HAS_WIFI && !defined(ARCH_PORTDUINO) if (isWifiAvailable()) { // call a method on debugInfoScreen object (for more details) @@ -2082,6 +2100,7 @@ void Screen::setFrames() } #endif + fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); ui->setFrames(normalFrames, numframes); @@ -2095,6 +2114,55 @@ void Screen::setFrames() prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list // just changed) + // Focus on a specific frame, in the frame set we just created + switch (focus) { + case FOCUS_DEFAULT: + ui->switchToFrame(0); // First frame + break; + case FOCUS_FAULT: + ui->switchToFrame(fsi.positions.fault); + break; + case FOCUS_TEXTMESSAGE: + ui->switchToFrame(fsi.positions.textMessage); + break; + case FOCUS_MODULE: + // Whichever frame was marked by MeshModule::requestFocus(), if any + // If no module requested focus, will show the first frame instead + ui->switchToFrame(fsi.positions.focusedModule); + break; + + case FOCUS_PRESERVE: + // If we can identify which type of frame "originalPosition" was, can move directly to it in the new frameset + FramesetInfo &oldFsi = this->framesetInfo; + if (originalPosition == oldFsi.positions.log) + ui->switchToFrame(fsi.positions.log); + else if (originalPosition == oldFsi.positions.settings) + ui->switchToFrame(fsi.positions.settings); + else if (originalPosition == oldFsi.positions.wifi) + ui->switchToFrame(fsi.positions.wifi); + + // If frame count has decreased + else if (fsi.frameCount < oldFsi.frameCount) { + uint8_t numDropped = oldFsi.frameCount - fsi.frameCount; + // Move n frames backwards + if (numDropped <= originalPosition) + ui->switchToFrame(originalPosition - numDropped); + // Unless that would put us "out of bounds" (< 0) + else + ui->switchToFrame(0); + } + + // If we're not sure exactly which frame we were on, at least return to the same frame number + // (node frames; module frames) + else + ui->switchToFrame(originalPosition); + + break; + } + + // Store the info about this frameset, for future setFrames calls + this->framesetInfo = fsi; + setFastFramerate(); // Draw ASAP } @@ -2549,7 +2617,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) switch (arg->getStatusType()) { case STATUS_TYPE_NODE: if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { - setFrames(); // Regen the list of screens + setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible) } nodeDB->updateGUI = false; break; @@ -2561,23 +2629,33 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) { if (showingNormalScreen) { - setFrames(); // Regen the list of screens (will show new text message) + // Outgoing message + if (packet->from == 0) + setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame) + + // Incoming message + else + setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message } return 0; } +// Triggered by MeshModules int Screen::handleUIFrameEvent(const UIFrameEvent *event) { if (showingNormalScreen) { - if (event->frameChanged) { - setFrames(); // Regen the list of screens (will show new text message) - } else if (event->needRedraw) { + // Regenerate the frameset, potentially honoring a module's internal requestFocus() call + if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) + setFrames(FOCUS_MODULE); + + // Regenerate the frameset, while attempting to maintain focus on the current frame + else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) + setFrames(FOCUS_PRESERVE); + + // Don't regenerate the frameset, just re-draw whatever is on screen ASAP + else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) setFastFramerate(); - // TODO: We might also want switch to corresponding frame, - // but we don't know the exact frame number. - // ui->switchToFrame(0); - } } return 0; @@ -2612,6 +2690,24 @@ int Screen::handleInputEvent(const InputEvent *event) return 0; } +int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) +{ + // Note: only selected admin messages notify this observer + // If you wish to handle a new type of message, you should modify AdminModule.cpp first + + switch (arg->which_payload_variant) { + // Node removed manually (i.e. via app) + case meshtastic_AdminMessage_remove_by_nodenum_tag: + setFrames(FOCUS_PRESERVE); + break; + + // Default no-op, in case the admin message observable gets used by other classes in future + default: + break; + } + return 0; +} + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index b89a2917e..93e5f2ef7 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -173,9 +173,11 @@ class Screen : public concurrency::OSThread CallbackObserver textMessageObserver = CallbackObserver(this, &Screen::handleTextMessage); CallbackObserver uiFrameEventObserver = - CallbackObserver(this, &Screen::handleUIFrameEvent); + CallbackObserver(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules CallbackObserver inputObserver = CallbackObserver(this, &Screen::handleInputEvent); + CallbackObserver adminMessageObserver = + CallbackObserver(this, &Screen::handleAdminMessage); public: explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); @@ -394,6 +396,7 @@ class Screen : public concurrency::OSThread int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); + int handleAdminMessage(const meshtastic_AdminMessage *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(bool forceUiUpdate = false); @@ -450,8 +453,34 @@ class Screen : public concurrency::OSThread void handleShowPrevFrame(); void handlePrint(const char *text); void handleStartFirmwareUpdateScreen(); - /// Rebuilds our list of frames (screens) to default ones. - void setFrames(); + + // Info collected by setFrames method. + // Index location of specific frames. Used to apply the FrameFocus parameter of setFrames + struct FramesetInfo { + struct FramePositions { + uint8_t fault = 0; + uint8_t textMessage = 0; + uint8_t focusedModule = 0; + uint8_t log = 0; + uint8_t settings = 0; + uint8_t wifi = 0; + } positions; + + uint8_t frameCount = 0; + } framesetInfo; + + // Which frame we want to be displayed, after we regen the frameset by calling setFrames + enum FrameFocus : uint8_t { + FOCUS_DEFAULT, // No specific frame + FOCUS_PRESERVE, // Return to the previous frame + FOCUS_FAULT, + FOCUS_TEXTMESSAGE, + FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus + }; + + // Regenerate the normal set of frames, focusing a specific frame if requested + // Call when a frame should be added / removed, or custom frames should be cleared + void setFrames(FrameFocus focus = FOCUS_DEFAULT); /// Try to start drawing ASAP void setFastFramerate(); diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 04fa250bf..1ef4f60d8 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -284,4 +284,17 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const mesht } } return handled; -} \ No newline at end of file +} + +#if HAS_SCREEN +// Would our module like its frame to be focused after Screen::setFrames has regenerated the list of frames? +// Only considered if setFrames is triggered by a UIFrameEvent +bool MeshModule::isRequestingFocus() +{ + if (_requestingFocus) { + _requestingFocus = false; // Consume the request + return true; + } else + return false; +} +#endif \ No newline at end of file diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 2e2af33e0..c341b301a 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -35,10 +35,16 @@ enum class AdminMessageHandleResult { /* * This struct is used by Screen to figure out whether screen frame should be updated. */ -typedef struct _UIFrameEvent { - bool frameChanged; - bool needRedraw; -} UIFrameEvent; +struct UIFrameEvent { + // What do we actually want to happen? + enum Action { + REDRAW_ONLY, // Don't change which frames are show, just redraw, asap + REGENERATE_FRAMESET, // Regenerate (change? add? remove?) screen frames, honoring requestFocus() + REGENERATE_FRAMESET_BACKGROUND, // Regenerate screen frames, attempting to remain on the same frame throughout + } action = REDRAW_ONLY; + + // We might want to pass additional data inside this struct at some point +}; /** A baseclass for any mesh "module". * @@ -73,6 +79,7 @@ class MeshModule meshtastic_AdminMessage *response); #if HAS_SCREEN virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } + virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset #endif protected: const char *name; @@ -176,6 +183,19 @@ class MeshModule return AdminMessageHandleResult::NOT_HANDLED; }; +#if HAS_SCREEN + /** Request that our module's screen frame be focused when Screen::setFrames runs + * Only considered if Screen::setFrames is triggered via a UIFrameEvent + * + * Having this as a separate call, instead of part of the UIFrameEvent, allows the module to delay decision + * until drawFrame() is called. This required less restructuring. + */ + bool _requestingFocus = false; + void requestFocus() { _requestingFocus = true; } +#else + void requestFocus(){}; // No-op +#endif + private: /** * If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index e24c62712..cab63e559 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -200,6 +200,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta case meshtastic_AdminMessage_remove_by_nodenum_tag: { LOG_INFO("Client is receiving a remove_nodenum command.\n"); nodeDB->removeNodeByNum(r->remove_by_nodenum); + this->notifyObservers(r); // Observed by screen break; } case meshtastic_AdminMessage_set_favorite_node_tag: { diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 6ecc88829..a5ffeb7d6 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -7,7 +7,7 @@ /** * Admin module for admin messages */ -class AdminModule : public ProtobufModule +class AdminModule : public ProtobufModule, public Observable { public: /** Constructor diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index be414dce1..84b5a3260 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -148,8 +148,9 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (this->currentMessageIndex == 0) { this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - UIFrameEvent e = {false, true}; - e.frameChanged = true; + requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->notifyObservers(&e); return 0; @@ -166,8 +167,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { - UIFrameEvent e = {false, true}; - e.frameChanged = true; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; #if !defined(T_WATCH_S3) && !defined(RAK14014) @@ -353,6 +354,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } if (validEvent) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs + // Let runOnce to be called immediately. if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { setIntervalFromNow(0); // on fast keypresses, this isn't fast enough. @@ -378,6 +381,11 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha p->decoded.payload.size++; } + // Only receive routing messages when expecting ACK for a canned message + // Prevents the canned message module from regenerating the screen's frameset at unexpected times, + // or raising a UIFrameEvent before another module has the chance + this->waitingForAck = true; + LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); service.sendToMesh( @@ -393,13 +401,13 @@ int32_t CannedMessageModule::runOnce() return INT32_MAX; } // LOG_DEBUG("Check status\n"); - UIFrameEvent e = {false, true}; + UIFrameEvent e; if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE)) { // TODO: might have some feedback of sending state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; temporaryMessage = ""; - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -412,7 +420,7 @@ int32_t CannedMessageModule::runOnce() } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { // Reset module - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -449,7 +457,7 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -463,7 +471,7 @@ int32_t CannedMessageModule::runOnce() } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { this->currentMessageIndex = 0; LOG_DEBUG("First touch (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) { if (this->messagesCount > 0) { @@ -567,7 +575,7 @@ int32_t CannedMessageModule::runOnce() break; } if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the // display back to the default window case 0x08: // backspace @@ -706,8 +714,8 @@ int CannedMessageModule::getPrevIndex() void CannedMessageModule::showTemporaryMessage(const String &message) { temporaryMessage = message; - UIFrameEvent e = {false, true}; - e.frameChanged = true; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen notifyObservers(&e); runState = CANNED_MESSAGE_RUN_STATE_MESSAGE; // run this loop again in 2 seconds, next iteration will clear the display @@ -914,11 +922,13 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st char buffer[50]; if (temporaryMessage.length() != 0) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame LOG_DEBUG("Drawing temporary message: %s", temporaryMessage.c_str()); display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); String displayString; @@ -940,6 +950,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi); } } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending..."); @@ -948,7 +959,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setFont(FONT_SMALL); display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled."); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - + requestFocus(); // Tell Screen::setFrames to move to our module's frame #if defined(T_WATCH_S3) || defined(RAK14014) drawKeyboard(display, state, 0, 0); #else @@ -1030,16 +1041,18 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &mp) { - if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP) { + if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP && waitingForAck) { // look for a request_id if (mp.decoded.request_id != 0) { - UIFrameEvent e = {false, true}; - e.frameChanged = true; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + requestFocus(); // Tell Screen::setFrames that our module's frame should be shown, even if not "first" in the frameset this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED; this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id); meshtastic_Routing decoded = meshtastic_Routing_init_default; pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; + waitingForAck = false; // No longer want routing packets this->notifyObservers(&e); // run the next time 2 seconds later setIntervalFromNow(2000); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 00e8c2bf9..797b9f7cf 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -81,9 +81,8 @@ class CannedMessageModule : public SinglePortModule, public Observabledecoded.portnum) { - case meshtastic_PortNum_TEXT_MESSAGE_APP: case meshtastic_PortNum_ROUTING_APP: - return true; + return waitingForAck; default: return false; } @@ -140,7 +139,8 @@ class CannedMessageModule : public SinglePortModule, public ObservablenotifyObservers(&e); } } else { @@ -209,7 +209,7 @@ int32_t AudioModule::runOnce() } tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->notifyObservers(&e); } } From 699d37b04c5a87a07e0444dbd724ad0b3ef7a7b2 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Jul 2024 09:24:42 -0500 Subject: [PATCH 0703/1377] Move up telemetry defaults to every 30 minutes (#4274) --- src/mesh/Default.h | 1 + src/modules/Telemetry/AirQualityTelemetry.cpp | 3 ++- src/modules/Telemetry/DeviceTelemetry.cpp | 3 ++- src/modules/Telemetry/EnvironmentTelemetry.cpp | 6 ++++-- src/modules/Telemetry/PowerTelemetry.cpp | 6 ++++-- src/modules/esp32/PaxcounterModule.cpp | 2 +- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 95723744b..cc3927914 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -5,6 +5,7 @@ #define ONE_MINUTE_MS 60 * 1000 #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) +#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60) #define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) #define default_wait_bluetooth_secs IF_ROUTER(1, 60) #define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index ba043feab..43b0ac46c 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -47,7 +47,8 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 9cc4bf6ea..9fe679b41 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -17,7 +17,8 @@ int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); if (((lastSentToMesh == 0) || - ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && + ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 23200fd00..a9740879d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -63,7 +63,8 @@ int32_t EnvironmentTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -144,7 +145,8 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index fb5aee375..6915d67e3 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -24,7 +24,8 @@ int32_t PowerTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, + default_telemetry_broadcast_interval_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -70,7 +71,8 @@ int32_t PowerTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 0bae515df..34d6fb1d0 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -101,7 +101,7 @@ int32_t PaxcounterModule::runOnce() sendInfo(NODENUM_BROADCAST); } return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, - default_broadcast_interval_secs); + default_telemetry_broadcast_interval_secs); } else { return disable(); } From 0fa99745181cf3c0b193603d89e89b1411e5c40c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Jul 2024 11:48:35 -0500 Subject: [PATCH 0704/1377] Don't send node info interrogation when ch. util is >25% (#4273) --- src/mesh/MeshService.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 9e2a5b110..a652d0a50 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -94,7 +94,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && nodeInfoModule) { LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); - nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); + if (airTime->isTxAllowedChannelUtil(true)) { + nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); + } else { + LOG_DEBUG("Skip sending NodeInfo due to > 25 percent channel util.\n"); + } } printPacket("Forwarding to phone", mp); From c5d747cd3e059448d0057fdcefb590eafb4c45aa Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Jul 2024 05:59:19 -0500 Subject: [PATCH 0705/1377] Scale default intervals based for *online* mesh size past 40 nodes (#4277) * Add congestion scaling coefficient * Added active mesh sized based interval scaling * Moved back to bottom * Format * Add observers and use correct number of online nodes --- src/mesh/Default.cpp | 36 ++++++++++++++----- src/mesh/Default.h | 13 +++++++ src/mesh/ProtobufModule.h | 9 +++++ src/modules/DetectionSensorModule.cpp | 4 +-- src/modules/NeighborInfoModule.cpp | 8 +++-- src/modules/NeighborInfoModule.h | 3 ++ src/modules/PositionModule.cpp | 6 ++-- src/modules/PositionModule.h | 5 ++- src/modules/Telemetry/AirQualityTelemetry.cpp | 5 +-- src/modules/Telemetry/AirQualityTelemetry.h | 5 +++ src/modules/Telemetry/DeviceTelemetry.cpp | 5 +-- src/modules/Telemetry/DeviceTelemetry.h | 4 +++ .../Telemetry/EnvironmentTelemetry.cpp | 5 +-- src/modules/Telemetry/EnvironmentTelemetry.h | 5 +++ src/modules/Telemetry/PowerTelemetry.cpp | 5 +-- src/modules/Telemetry/PowerTelemetry.h | 4 +++ src/modules/esp32/PaxcounterModule.cpp | 4 +-- 17 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp index db058c5b0..d4e9b3d79 100644 --- a/src/mesh/Default.cpp +++ b/src/mesh/Default.cpp @@ -1,12 +1,5 @@ #include "Default.h" -uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval) -{ - if (configuredInterval > 0) - return configuredInterval * 1000; - return default_broadcast_interval_secs * 1000; -} - uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) { if (configuredInterval > 0) @@ -14,10 +7,37 @@ uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t return defaultInterval * 1000; } +uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval) +{ + if (configuredInterval > 0) + return configuredInterval * 1000; + return default_broadcast_interval_secs * 1000; +} + uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue) { if (configured > 0) return configured; - return defaultValue; +} +/** + * Calculates the scaled value of the configured or default value in ms based on the number of online nodes. + * + * For example a default of 30 minutes (1800 seconds * 1000) would yield: + * 45 nodes = 2475 * 1000 + * 60 nodes = 4500 * 1000 + * 75 nodes = 6525 * 1000 + * 90 nodes = 8550 * 1000 + * @param configured The configured value. + * @param defaultValue The default value. + * @param numOnlineNodes The number of online nodes. + * @return The scaled value of the configured or default value. + */ +uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes) +{ + // If we are a router, we don't scale the value. It's already significantly higher. + if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) + return getConfiguredOrDefaultMs(configured, defaultValue); + + return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes); } \ No newline at end of file diff --git a/src/mesh/Default.h b/src/mesh/Default.h index cc3927914..7d79d696e 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -29,4 +29,17 @@ class Default static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval); static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval); static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); + static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes); + + private: + static float congestionScalingCoefficient(int numOnlineNodes) + { + if (numOnlineNodes <= 40) { + return 1.0; // No scaling for 40 or fewer nodes + } else { + // Sscaling based on number of nodes over 40 + int nodesOverForty = (numOnlineNodes - 40); + return 1.0 + (nodesOverForty * 0.075); // Each number of online node scales by 0.075 + } + } }; \ No newline at end of file diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 0d3da9568..ed76b877f 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -13,6 +13,7 @@ template class ProtobufModule : protected SinglePortModule const pb_msgdesc_t *fields; public: + uint8_t numOnlineNodes = 0; /** Constructor * name is for debugging output */ @@ -61,6 +62,14 @@ template class ProtobufModule : protected SinglePortModule return sender; } + int handleStatusUpdate(const meshtastic::Status *arg) + { + if (arg->getStatusType() == STATUS_TYPE_NODE) { + numOnlineNodes = nodeStatus->getNumOnline(); + } + return 0; + } + private: /** Called to handle a particular incoming message diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index fd26749c1..b6e5f1e29 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -58,8 +58,8 @@ int32_t DetectionSensorModule::runOnce() // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && - (millis() - lastSentToMesh) >= - Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) { + (millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, + default_telemetry_broadcast_interval_secs)) { sendCurrentStateMessage(); return DELAYED_INTERVAL; } diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 3925bea9a..774b42d7b 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -39,11 +39,12 @@ NeighborInfoModule::NeighborInfoModule() concurrency::OSThread("NeighborInfoModule") { ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP; + nodeStatusObserver.observe(&nodeStatus->onNewStatus); if (moduleConfig.neighbor_info.enabled) { isPromiscuous = true; // Update neighbors from all packets - setIntervalFromNow( - Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs)); + setIntervalFromNow(Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, + default_telemetry_broadcast_interval_secs)); } else { LOG_DEBUG("NeighborInfoModule is disabled\n"); disable(); @@ -119,7 +120,8 @@ int32_t NeighborInfoModule::runOnce() if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { sendNeighborInfo(NODENUM_BROADCAST, false); } - return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); + return Default::getConfiguredOrDefaultMsScaled(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs, + numOnlineNodes); } /* diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index 496fdece5..aa76a2187 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -7,6 +7,9 @@ */ class NeighborInfoModule : public ProtobufModule, private concurrency::OSThread { + CallbackObserver nodeStatusObserver = + CallbackObserver(this, &NeighborInfoModule::handleStatusUpdate); + std::vector neighbors; public: diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 61616841b..b3294a866 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -28,6 +28,8 @@ PositionModule::PositionModule() { precision = 0; // safe starting value isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others + nodeStatusObserver.observe(&nodeStatus->onNewStatus); + if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) setIntervalFromNow(60 * 1000); @@ -333,8 +335,8 @@ int32_t PositionModule::runOnce() // We limit our GPS broadcasts to a max rate uint32_t now = millis(); - uint32_t intervalMs = - Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); + uint32_t intervalMs = Default::getConfiguredOrDefaultMsScaled(config.position.position_broadcast_secs, + default_broadcast_interval_secs, numOnlineNodes); uint32_t msSinceLastSend = now - lastGpsSend; // Only send packets if the channel util. is less than 25% utilized or we're a tracker with less than 40% utilized. if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 763b51e5c..a5277aff6 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -8,6 +8,9 @@ */ class PositionModule : public ProtobufModule, private concurrency::OSThread { + CallbackObserver nodeStatusObserver = + CallbackObserver(this, &PositionModule::handleStatusUpdate); + /// The id of the last packet we sent, to allow us to cancel it if we make something fresher PacketId prevPacketId = 0; @@ -59,7 +62,7 @@ class PositionModule : public ProtobufModule, private concu void sendLostAndFoundText(); const uint32_t minimumTimeThreshold = - Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); + Default::getConfiguredOrDefaultMsScaled(config.position.broadcast_smart_minimum_interval_secs, 30, numOnlineNodes); }; struct SmartPosition { diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 43b0ac46c..6d2bf5e01 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -47,8 +47,9 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, + numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 9d09078b1..23df6ac58 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -10,6 +10,10 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule { + CallbackObserver nodeStatusObserver = + CallbackObserver(this, + &AirQualityTelemetryModule::handleStatusUpdate); + public: AirQualityTelemetryModule() : concurrency::OSThread("AirQualityTelemetryModule"), @@ -18,6 +22,7 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf lastMeasurementPacket = nullptr; setIntervalFromNow(10 * 1000); aqi = Adafruit_PM25AQI(); + nodeStatusObserver.observe(&nodeStatus->onNewStatus); } protected: diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 9fe679b41..9c1ac289c 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -17,8 +17,9 @@ int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); if (((lastSentToMesh == 0) || - ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval, - default_telemetry_broadcast_interval_secs))) && + ((uptimeLastMs - lastSentToMesh) >= + Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index 5f4e761f9..baaf59f28 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -7,6 +7,9 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModule { + CallbackObserver nodeStatusObserver = + CallbackObserver(this, &DeviceTelemetryModule::handleStatusUpdate); + public: DeviceTelemetryModule() : concurrency::OSThread("DeviceTelemetryModule"), @@ -14,6 +17,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu { uptimeWrapCount = 0; uptimeLastMs = millis(); + nodeStatusObserver.observe(&nodeStatus->onNewStatus); setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent } virtual bool wantUIFrame() { return false; } diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index a9740879d..0784f680d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -145,8 +145,9 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs))) && + ((now - lastSentToMesh) >= + Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index ced617c2f..59d272e78 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -11,12 +11,17 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public ProtobufModule { + CallbackObserver nodeStatusObserver = + CallbackObserver(this, + &EnvironmentTelemetryModule::handleStatusUpdate); + public: EnvironmentTelemetryModule() : concurrency::OSThread("EnvironmentTelemetryModule"), ProtobufModule("EnvironmentTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { lastMeasurementPacket = nullptr; + nodeStatusObserver.observe(&nodeStatus->onNewStatus); setIntervalFromNow(10 * 1000); } virtual bool wantUIFrame() override; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 6915d67e3..a6f922e56 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -71,8 +71,9 @@ int32_t PowerTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, - default_telemetry_broadcast_interval_secs))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.power_update_interval, + default_telemetry_broadcast_interval_secs, + numOnlineNodes))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index 1b68847db..f8248304e 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -12,12 +12,16 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModule { + CallbackObserver nodeStatusObserver = + CallbackObserver(this, &PowerTelemetryModule::handleStatusUpdate); + public: PowerTelemetryModule() : concurrency::OSThread("PowerTelemetryModule"), ProtobufModule("PowerTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { lastMeasurementPacket = nullptr; + nodeStatusObserver.observe(&nodeStatus->onNewStatus); setIntervalFromNow(10 * 1000); } virtual bool wantUIFrame() override; diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 34d6fb1d0..a8fe5c4c5 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -100,8 +100,8 @@ int32_t PaxcounterModule::runOnce() } else { sendInfo(NODENUM_BROADCAST); } - return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, - default_telemetry_broadcast_interval_secs); + return Default::getConfiguredOrDefaultMsScaled(moduleConfig.paxcounter.paxcounter_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes); } else { return disable(); } From 4286f2c2ddb60728f91e3b89cf19a1bf20503813 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Jul 2024 06:07:20 -0500 Subject: [PATCH 0706/1377] Minor version bump --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index c9336d539..7d2c26248 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 -minor = 3 -build = 16 +minor = 4 +build = 0 From ca2b45a6e2843b6f17df740da29ad5b9cc68c5a3 Mon Sep 17 00:00:00 2001 From: Lennart Buhl Date: Sat, 13 Jul 2024 13:09:51 +0200 Subject: [PATCH 0707/1377] Fix that Dockerfile would not run with podman (#4262) * Fix that Dockerfile would not run with podman * Migrate away from non-OCI-compliant SHELL command in Dockerfile --- Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 08cb3925d..fc34fbd4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,6 @@ ENV TZ=Etc/UTC # > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. ENV LANG C.UTF-8 -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - # Install build deps USER root @@ -24,10 +22,10 @@ USER mesh WORKDIR /tmp/firmware RUN python3 -m venv /tmp/firmware -RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14 +RUN bash -o pipefail -c "source bin/activate; pip3 install --no-cache-dir -U platformio==6.1.15" # trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm COPY --chown=mesh:mesh . /tmp/firmware -RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh +RUN bash -o pipefail -c "source ./bin/activate && bash ./bin/build-native.sh" RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd" From 141ae296b73819be64d963076d309973d966e863 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 13 Jul 2024 19:47:38 +0800 Subject: [PATCH 0708/1377] Add Seeed Wio WM1110 to Github issue template (#4283) --- .github/ISSUE_TEMPLATE/Bug Report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml index 7fe42051c..b5ca0db40 100644 --- a/.github/ISSUE_TEMPLATE/Bug Report.yml +++ b/.github/ISSUE_TEMPLATE/Bug Report.yml @@ -52,6 +52,7 @@ body: - Raspberry Pi Pico (W) - Relay v1 - Relay v2 + - Seeed Wio Tracker 1110 - DIY - Other validations: From 9e4ce86c2af78129b0f6c4b0b8bcf8915cae0132 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sat, 13 Jul 2024 19:36:44 +0200 Subject: [PATCH 0709/1377] Let StoreForward server send history to phoneAPI (#4282) * Send StoreForward history of the server to a connected client To extend the ToPhoneQueue * Add delay after sending history info * Don't allow history request over LoRa on default channel --------- Co-authored-by: Ben Meadors --- src/mesh/Channels.cpp | 22 +- src/mesh/Channels.h | 5 +- src/mesh/MeshService.cpp | 11 + src/mesh/MeshService.h | 5 + src/mesh/PhoneAPI.cpp | 8 + src/modules/AdminModule.cpp | 5 + src/modules/esp32/StoreForwardModule.cpp | 364 +++++++++++++---------- src/modules/esp32/StoreForwardModule.h | 22 +- 8 files changed, 259 insertions(+), 183 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 079af4eca..bb4d629e7 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -277,6 +277,17 @@ const char *Channels::getName(size_t chIndex) return channelName; } +bool Channels::isDefaultChannel(const meshtastic_Channel &ch) +{ + if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { + const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + // Check if the name is the default derived from the modem preset + if (strcmp(ch.settings.name, presetName) == 0) + return true; + } + return false; +} + bool Channels::hasDefaultChannel() { // If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel @@ -285,13 +296,8 @@ bool Channels::hasDefaultChannel() // Check if any of the channels are using the default name and PSK for (size_t i = 0; i < getNumChannels(); i++) { const auto &ch = getByIndex(i); - if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { - const char *name = getName(i); - const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); - // Check if the name is the default derived from the modem preset - if (strcmp(name, presetName) == 0) - return true; - } + if (isDefaultChannel(ch)) + return true; } return false; } @@ -324,4 +330,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) int16_t Channels::setActiveByIndex(ChannelIndex channelIndex) { return setCrypto(channelIndex); -} \ No newline at end of file +} diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 952445a1d..eaccea8e1 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -83,6 +83,9 @@ class Channels */ int16_t setActiveByIndex(ChannelIndex channelIndex); + // Returns true if the channel has the default name and PSK + bool isDefaultChannel(const meshtastic_Channel &ch); + // Returns true if we can be reached via a channel with the default settings given a region and modem preset bool hasDefaultChannel(); @@ -126,4 +129,4 @@ class Channels }; /// Singleton channel table -extern Channels channels; \ No newline at end of file +extern Channels channels; diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index a652d0a50..1181ffb9a 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -293,6 +293,17 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) { perhapsDecode(p); +#ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_STOREFORWARD + if (moduleConfig.store_forward.enabled && storeForwardModule->isServer() && + p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) { + releaseToPool(p); // Copy is already stored in StoreForward history + fromNum++; // Notify observers for packet from radio + return; + } +#endif +#endif + if (toPhoneQueue.numFree() == 0) { if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP || p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) { diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index d777b7a01..3ac35bb62 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -13,6 +13,11 @@ #if defined(ARCH_PORTDUINO) && !HAS_RADIO #include "../platform/portduino/SimRadio.h" #endif +#ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_STOREFORWARD +#include "modules/esp32/StoreForwardModule.h" +#endif +#endif extern Allocator &queueStatusPool; extern Allocator &mqttClientProxyMessagePool; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 0f69b21f9..0b63b4a58 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -503,6 +503,14 @@ bool PhoneAPI::available() return true; } +#ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_STOREFORWARD + // Check if StoreForward has packets stored for us. + if (!packetForPhone && storeForwardModule) + packetForPhone = storeForwardModule->getForPhone(); +#endif +#endif + if (!packetForPhone) packetForPhone = service.getForPhone(); hasPacket = !!packetForPhone; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index cab63e559..98b789f41 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -392,6 +392,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) // Router Client is deprecated; Set it to client if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) { config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) { + moduleConfig.store_forward.is_server = true; + changes |= SEGMENT_MODULECONFIG; + requiresReboot = true; + } } break; case meshtastic_Config_position_tag: diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index dc8650ad0..ff0f796a1 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -35,13 +35,10 @@ int32_t StoreForwardModule::runOnce() if (moduleConfig.store_forward.enabled && is_server) { // Send out the message queue. if (this->busy) { - // Only send packets if the channel is less than 25% utilized. - if (airTime->isTxAllowedChannelUtil(true)) { - storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index); - if (this->packetHistoryTXQueue_index < packetHistoryTXQueue_size - 1) { - this->packetHistoryTXQueue_index++; - } else { - this->packetHistoryTXQueue_index = 0; + // Only send packets if the channel is less than 25% utilized and until historyReturnMax + if (airTime->isTxAllowedChannelUtil(true) && this->requestCount < this->historyReturnMax) { + if (!storeForwardModule->sendPayload(this->busyTo, this->last_time)) { + this->requestCount = 0; this->busy = false; } } @@ -75,9 +72,6 @@ void StoreForwardModule::populatePSRAM() LOG_DEBUG("*** Before PSRAM initialization: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreePsram(), memGet.getPsramSize()); - this->packetHistoryTXQueue = - static_cast(ps_calloc(this->historyReturnMax, sizeof(PacketHistoryStruct))); - /* Use a maximum of 2/3 the available PSRAM unless otherwise specified. Note: This needs to be done after every thing that would use PSRAM */ @@ -95,13 +89,15 @@ void StoreForwardModule::populatePSRAM() /** * Sends messages from the message history to the specified recipient. * - * @param msAgo The number of milliseconds ago from which to start sending messages. + * @param sAgo The number of seconds ago from which to start sending messages. * @param to The recipient ID to send the messages to. */ -void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to) +void StoreForwardModule::historySend(uint32_t secAgo, uint32_t to) { - uint32_t lastIndex = lastRequest.find(to) != lastRequest.end() ? lastRequest[to] : 0; - uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to, &lastIndex); + this->last_time = getTime() < secAgo ? 0 : getTime() - secAgo; + uint32_t queueSize = getNumAvailablePackets(to, last_time); + if (queueSize > this->historyReturnMax) + queueSize = this->historyReturnMax; if (queueSize) { LOG_INFO("*** S&F - Sending %u message(s)\n", queueSize); @@ -114,62 +110,66 @@ void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to) sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_HISTORY; sf.which_variant = meshtastic_StoreAndForward_history_tag; sf.variant.history.history_messages = queueSize; - sf.variant.history.window = msAgo; - sf.variant.history.last_request = lastIndex; - lastRequest[to] = lastIndex; + sf.variant.history.window = secAgo * 1000; + sf.variant.history.last_request = lastRequest[to]; storeForwardModule->sendMessage(to, sf); + setIntervalFromNow(this->packetTimeMax); // Delay start of sending payloads } /** - * Creates a new history queue with messages that were received within the specified time frame. + * Returns the number of available packets in the message history for a specified destination node. * - * @param msAgo The number of milliseconds ago to start the history queue. - * @param to The NodeNum of the recipient. - * @param last_request_index The index in the packet history of the last request from this node. - * @return The ID of the newly created history queue. + * @param dest The destination node number. + * @param last_time The relative time to start counting messages from. + * @return The number of available packets in the message history. */ -uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to, uint32_t *last_request_index) +uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_time) { - - this->packetHistoryTXQueue_size = 0; - // If our history was cleared, ignore the last request index - uint32_t last_index = *last_request_index > this->packetHistoryCurrent ? 0 : *last_request_index; - - for (uint32_t i = last_index; i < this->packetHistoryCurrent; i++) { - /* - LOG_DEBUG("SF historyQueueCreate\n"); - LOG_DEBUG("SF historyQueueCreate - time %d\n", this->packetHistory[i].time); - LOG_DEBUG("SF historyQueueCreate - millis %d\n", millis()); - LOG_DEBUG("SF historyQueueCreate - math %d\n", (millis() - msAgo)); - */ - if (this->packetHistoryTXQueue_size < this->historyReturnMax) { - if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { - /* Copy the messages that were received by the router in the last msAgo - to the packetHistoryTXQueue structure. - Client not interested in packets from itself and only in broadcast packets or packets towards it. */ - if (this->packetHistory[i].from != to && - (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) { - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = - this->packetHistory[i].payload_size; - memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, - meshtastic_Constants_DATA_PAYLOAD_LEN); - this->packetHistoryTXQueue_size++; - *last_request_index = i + 1; // Set to one higher such that we don't send the same message again - - LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time, - this->packetHistory[i].payload); - } + uint32_t count = 0; + if (lastRequest.find(dest) == lastRequest.end()) { + lastRequest[dest] = 0; + } + for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) { + if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) { + // Client is only interested in packets not from itself and only in broadcast packets or packets towards it. + if (this->packetHistory[i].from != dest && + (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) { + count++; } - } else { - LOG_WARN("*** S&F - Maximum history return reached.\n"); - return this->packetHistoryTXQueue_size; } } - return this->packetHistoryTXQueue_size; + return count; +} + +/** + * Allocates a mesh packet for sending to the phone. + * + * @return A pointer to the allocated mesh packet or nullptr if none is available. + */ +meshtastic_MeshPacket *StoreForwardModule::getForPhone() +{ + if (moduleConfig.store_forward.enabled && is_server) { + NodeNum to = nodeDB->getNodeNum(); + if (!this->busy) { + // Get number of packets we're going to send in this loop + uint32_t histSize = getNumAvailablePackets(to, 0); // No time limit + if (histSize) { + this->busy = true; + this->busyTo = to; + } else { + return nullptr; + } + } + + // We're busy with sending to us until no payload is available anymore + if (this->busy && this->busyTo == to) { + meshtastic_MeshPacket *p = preparePayload(to, 0, true); // No time limit + if (!p) // No more messages to send + this->busy = false; + return p; + } + } + return nullptr; } /** @@ -181,66 +181,97 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) { const auto &p = mp.decoded; - if (this->packetHistoryCurrent == this->records) { + if (this->packetHistoryTotalCount == this->records) { LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n"); - this->packetHistoryCurrent = 0; - this->packetHistoryMax = 0; + this->packetHistoryTotalCount = 0; for (auto &i : lastRequest) { i.second = 0; // Clear the last request index for each client device } } - this->packetHistory[this->packetHistoryCurrent].time = millis(); - this->packetHistory[this->packetHistoryCurrent].to = mp.to; - this->packetHistory[this->packetHistoryCurrent].channel = mp.channel; - this->packetHistory[this->packetHistoryCurrent].from = mp.from; - this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size; - memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); + this->packetHistory[this->packetHistoryTotalCount].time = getTime(); + this->packetHistory[this->packetHistoryTotalCount].to = mp.to; + this->packetHistory[this->packetHistoryTotalCount].channel = mp.channel; + this->packetHistory[this->packetHistoryTotalCount].from = getFrom(&mp); + this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size; + memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); - this->packetHistoryCurrent++; - this->packetHistoryMax++; -} - -meshtastic_MeshPacket *StoreForwardModule::allocReply() -{ - auto reply = allocDataPacket(); // Allocate a packet for sending - return reply; + this->packetHistoryTotalCount++; } /** * Sends a payload to a specified destination node using the store and forward mechanism. * * @param dest The destination node number. - * @param packetHistory_index The index of the packet in the packet history buffer. + * @param last_time The relative time to start sending messages from. + * @return True if a packet was successfully sent, false otherwise. */ -void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index) +bool StoreForwardModule::sendPayload(NodeNum dest, uint32_t last_time) { - LOG_INFO("*** Sending S&F Payload\n"); - meshtastic_MeshPacket *p = allocReply(); - - p->to = dest; - p->from = this->packetHistoryTXQueue[packetHistory_index].from; - p->channel = this->packetHistoryTXQueue[packetHistory_index].channel; - - // Let's assume that if the router received the S&F request that the client is in range. - // TODO: Make this configurable. - p->want_ack = false; - - meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; - sf.which_variant = meshtastic_StoreAndForward_text_tag; - sf.variant.text.size = this->packetHistoryTXQueue[packetHistory_index].payload_size; - memcpy(sf.variant.text.bytes, this->packetHistoryTXQueue[packetHistory_index].payload, - this->packetHistoryTXQueue[packetHistory_index].payload_size); - if (this->packetHistoryTXQueue[packetHistory_index].to == NODENUM_BROADCAST) { - sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST; - } else { - sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT; + meshtastic_MeshPacket *p = preparePayload(dest, last_time); + if (p) { + LOG_INFO("*** Sending S&F Payload\n"); + service.sendToMesh(p); + this->requestCount++; + return true; } + return false; +} - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_StoreAndForward_msg, &sf); +/** + * Prepares a payload to be sent to a specified destination node from the S&F packet history. + * + * @param dest The destination node number. + * @param last_time The relative time to start sending messages from. + * @return A pointer to the prepared mesh packet or nullptr if none is available. + */ +meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t last_time, bool local) +{ + for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) { + if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) { + /* Copy the messages that were received by the server in the last msAgo + to the packetHistoryTXQueue structure. + Client not interested in packets from itself and only in broadcast packets or packets towards it. */ + if (this->packetHistory[i].from != dest && + (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) { - service.sendToMesh(p); + meshtastic_MeshPacket *p = allocDataPacket(); + + p->to = local ? this->packetHistory[i].to : dest; // PhoneAPI can handle original `to` + p->from = this->packetHistory[i].from; + p->channel = this->packetHistory[i].channel; + p->rx_time = this->packetHistory[i].time; + + // Let's assume that if the server received the S&F request that the client is in range. + // TODO: Make this configurable. + p->want_ack = false; + + if (local) { // PhoneAPI gets normal TEXT_MESSAGE_APP + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + memcpy(p->decoded.payload.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size); + p->decoded.payload.size = this->packetHistory[i].payload_size; + } else { + meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; + sf.which_variant = meshtastic_StoreAndForward_text_tag; + sf.variant.text.size = this->packetHistory[i].payload_size; + memcpy(sf.variant.text.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size); + if (this->packetHistory[i].to == NODENUM_BROADCAST) { + sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST; + } else { + sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT; + } + + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + &meshtastic_StoreAndForward_msg, &sf); + } + + lastRequest[dest] = i + 1; // Update the last request index for the client device + + return p; + } + } + } + return nullptr; } /** @@ -257,11 +288,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - // FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume - // everything is broadcast. - p->delayed = meshtastic_MeshPacket_Delayed_DELAYED_BROADCAST; - - // Let's assume that if the router received the S&F request that the client is in range. + // Let's assume that if the server received the S&F request that the client is in range. // TODO: Make this configurable. p->want_ack = false; p->decoded.want_response = false; @@ -283,6 +310,35 @@ void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward_Re storeForwardModule->sendMessage(dest, sf); } +/** + * Sends a text message with an error (busy or channel not available) to the specified destination node. + * + * @param dest The destination node number. + * @param want_response True if the original message requested a response, false otherwise. + */ +void StoreForwardModule::sendErrorTextMessage(NodeNum dest, bool want_response) +{ + meshtastic_MeshPacket *pr = allocDataPacket(); + pr->to = dest; + pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + pr->want_ack = false; + pr->decoded.want_response = false; + pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + const char *str; + if (this->busy) { + str = "** S&F - Busy. Try again shortly."; + } else { + str = "** S&F - Not available on this channel."; + } + LOG_WARN("%s\n", str); + memcpy(pr->decoded.payload.bytes, str, strlen(str)); + pr->decoded.payload.size = strlen(str); + if (want_response) { + ignoreRequest = true; // This text message counts as response. + } + service.sendToMesh(pr); +} + /** * Sends statistics about the store and forward module to the specified node. * @@ -294,8 +350,8 @@ void StoreForwardModule::statsSend(uint32_t to) sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_STATS; sf.which_variant = meshtastic_StoreAndForward_stats_tag; - sf.variant.stats.messages_total = this->packetHistoryMax; - sf.variant.stats.messages_saved = this->packetHistoryCurrent; + sf.variant.stats.messages_total = this->records; + sf.variant.stats.messages_saved = this->packetHistoryTotalCount; sf.variant.stats.messages_max = this->records; sf.variant.stats.up_time = millis() / 1000; sf.variant.stats.requests = this->requests; @@ -319,51 +375,37 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m #ifdef ARCH_ESP32 if (moduleConfig.store_forward.enabled) { - // The router node should not be sending messages as a client - if ((getFrom(&mp) != nodeDB->getNodeNum())) { + if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { + auto &p = mp.decoded; + if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && + (p.payload.bytes[2] == 0x00)) { + LOG_DEBUG("*** Legacy Request to send\n"); - if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { - auto &p = mp.decoded; - if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && - (p.payload.bytes[2] == 0x00)) { - LOG_DEBUG("*** Legacy Request to send\n"); - - // Send the last 60 minutes of messages. - if (this->busy) { - storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_ROUTER_BUSY); - LOG_INFO("*** S&F - Busy. Try again shortly.\n"); - meshtastic_MeshPacket *pr = allocReply(); - pr->to = getFrom(&mp); - pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - pr->want_ack = false; - pr->decoded.want_response = false; - pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - memcpy(pr->decoded.payload.bytes, "** S&F - Busy. Try again shortly.", 34); - pr->decoded.payload.size = 34; - service.sendToMesh(pr); - } else { - storeForwardModule->historySend(historyReturnWindow * 60000, getFrom(&mp)); - } + // Send the last 60 minutes of messages. + if (this->busy || channels.isDefaultChannel(channels.getByIndex(mp.channel))) { + sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response); } else { - storeForwardModule->historyAdd(mp); - LOG_INFO("*** S&F stored. Message history contains %u records now.\n", this->packetHistoryCurrent); + storeForwardModule->historySend(historyReturnWindow * 60, getFrom(&mp)); } - } else if (mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) { - auto &p = mp.decoded; - meshtastic_StoreAndForward scratch; - meshtastic_StoreAndForward *decoded = NULL; - if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_StoreAndForward_msg, &scratch)) { - decoded = &scratch; - } else { - LOG_ERROR("Error decoding protobuf module!\n"); - // if we can't decode it, nobody can process it! - return ProcessMessage::STOP; - } - return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE; + } else { + storeForwardModule->historyAdd(mp); + LOG_INFO("*** S&F stored. Message history contains %u records now.\n", this->packetHistoryTotalCount); + } + } else if (getFrom(&mp) != nodeDB->getNodeNum() && mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) { + auto &p = mp.decoded; + meshtastic_StoreAndForward scratch; + meshtastic_StoreAndForward *decoded = NULL; + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_StoreAndForward_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding protobuf module!\n"); + // if we can't decode it, nobody can process it! + return ProcessMessage::STOP; } - } // all others are irrelevant - } + return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE; + } + } // all others are irrelevant } #endif @@ -394,7 +436,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, // stop sending stuff, the client wants to abort or has another error if ((this->busy) && (this->busyTo == getFrom(&mp))) { LOG_ERROR("*** Client in ERROR or ABORT requested\n"); - this->packetHistoryTXQueue_index = 0; + this->requestCount = 0; this->busy = false; } } @@ -405,15 +447,14 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, requests_history++; LOG_INFO("*** Client Request to send HISTORY\n"); // Send the last 60 minutes of messages. - if (this->busy) { - storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_ROUTER_BUSY); - LOG_INFO("*** S&F - Busy. Try again shortly.\n"); + if (this->busy || channels.isDefaultChannel(channels.getByIndex(mp.channel))) { + sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response); } else { if ((p->which_variant == meshtastic_StoreAndForward_history_tag) && (p->variant.history.window > 0)) { // window is in minutes - storeForwardModule->historySend(p->variant.history.window * 60000, getFrom(&mp)); + storeForwardModule->historySend(p->variant.history.window * 60, getFrom(&mp)); } else { - storeForwardModule->historySend(historyReturnWindow * 60000, getFrom(&mp)); // defaults to 4 hours + storeForwardModule->historySend(historyReturnWindow * 60, getFrom(&mp)); // defaults to 4 hours } } } @@ -451,7 +492,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, if (is_client) { LOG_DEBUG("*** StoreAndForward_RequestResponse_ROUTER_BUSY\n"); // retry in messages_saved * packetTimeMax ms - retry_delay = millis() + packetHistoryCurrent * packetTimeMax * + retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax * (meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); } break; @@ -482,8 +523,6 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, LOG_DEBUG("*** Router Response STATS\n"); // These fields only have informational purpose on a client. Fill them to consume later. if (p->which_variant == meshtastic_StoreAndForward_stats_tag) { - this->packetHistoryMax = p->variant.stats.messages_total; - this->packetHistoryCurrent = p->variant.stats.messages_saved; this->records = p->variant.stats.messages_max; this->requests = p->variant.stats.requests; this->requests_history = p->variant.stats.requests_history; @@ -508,7 +547,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, default: break; // no need to do anything } - return true; // There's no need for others to look at this message. + return false; // RoutingModule sends it to the phone } StoreForwardModule::StoreForwardModule() @@ -532,9 +571,8 @@ StoreForwardModule::StoreForwardModule() if (moduleConfig.store_forward.enabled) { // Router - if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) || - (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { - LOG_INFO("*** Initializing Store & Forward Module in Router mode\n"); + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || moduleConfig.store_forward.is_server)) { + LOG_INFO("*** Initializing Store & Forward Module in Server mode\n"); if (memGet.getPsramSize() > 0) { if (memGet.getFreePsram() >= 1024 * 1024) { diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h index 0d2fb9fce..e3273470b 100644 --- a/src/modules/esp32/StoreForwardModule.h +++ b/src/modules/esp32/StoreForwardModule.h @@ -25,12 +25,9 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< char routerMessage[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; PacketHistoryStruct *packetHistory = 0; - uint32_t packetHistoryCurrent = 0; - uint32_t packetHistoryMax = 0; - - PacketHistoryStruct *packetHistoryTXQueue = 0; - uint32_t packetHistoryTXQueue_size = 0; - uint32_t packetHistoryTXQueue_index = 0; + uint32_t packetHistoryTotalCount = 0; + uint32_t last_time = 0; + uint32_t requestCount = 0; uint32_t packetTimeMax = 5000; // Interval between sending history packets as a server. @@ -52,18 +49,21 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< */ void historyAdd(const meshtastic_MeshPacket &mp); void statsSend(uint32_t to); - void historySend(uint32_t msAgo, uint32_t to); - - uint32_t historyQueueCreate(uint32_t msAgo, uint32_t to, uint32_t *last_request_index); + void historySend(uint32_t secAgo, uint32_t to); + uint32_t getNumAvailablePackets(NodeNum dest, uint32_t last_time); /** * Send our payload into the mesh */ - void sendPayload(NodeNum dest = NODENUM_BROADCAST, uint32_t packetHistory_index = 0); + bool sendPayload(NodeNum dest = NODENUM_BROADCAST, uint32_t packetHistory_index = 0); + meshtastic_MeshPacket *preparePayload(NodeNum dest, uint32_t packetHistory_index, bool local = false); void sendMessage(NodeNum dest, const meshtastic_StoreAndForward &payload); void sendMessage(NodeNum dest, meshtastic_StoreAndForward_RequestResponse rr); + void sendErrorTextMessage(NodeNum dest, bool want_response); + meshtastic_MeshPacket *getForPhone(); + // Returns true if we are configured as server AND we could allocate PSRAM. + bool isServer() { return is_server; } - virtual meshtastic_MeshPacket *allocReply() override; /* -Override the wantPacket method. */ From 3fa8b357e580bd4d2047f1dff0aba3949c5467ff Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sun, 14 Jul 2024 10:36:07 +1200 Subject: [PATCH 0710/1377] Initial work for Heltec Vision Master 213 (#4286) * Fix for serial monitoring and I2C for Vision Master e213 (#4280) * Fix for serial monitoring and I2C The board did not allow serial monitoring while on boot mode, i was able to fix this by adding a board variant. I also corrected the i2c pins. I was able to test it with a cardkb * oops I delete some code by mistake, all back now * Made some adjustments * Minimize the diff --------- Co-authored-by: Todd Herbert * Don't redefine board identifier Suppresses compiler warnings * Detect Vision Master 213 with PIO serial monitor * Use outermost button as user-button Less chance of accidentally hitting reset * Use 1200bps touch (213) Allows upload without manually entering bootloader --------- Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- boards/heltec_vision_master_e213.json | 42 +++++++++++++++++++ .../heltec_vision_master_e213/pins_arduino.h | 6 +-- .../heltec_vision_master_e213/platformio.ini | 2 +- variants/heltec_vision_master_e213/variant.h | 2 +- variants/heltec_vision_master_e290/variant.h | 2 +- 5 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 boards/heltec_vision_master_e213.json diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json new file mode 100644 index 000000000..bf5fe15ad --- /dev/null +++ b/boards/heltec_vision_master_e213.json @@ -0,0 +1,42 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e213" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E213", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e213/", + "vendor": "Heltec" +} diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h index 01c16c496..359922499 100644 --- a/variants/heltec_vision_master_e213/pins_arduino.h +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -3,8 +3,6 @@ #include -#define HELTEC_VISION_MASTER_E213 true - static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN @@ -12,8 +10,8 @@ static const uint8_t LED_BUILTIN = 35; static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini index 77cc65983..709ae321f 100644 --- a/variants/heltec_vision_master_e213/platformio.ini +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -1,6 +1,6 @@ [env:heltec-vision-master-e213] extends = esp32s3_base -board = heltec_wifi_lora_32_V3 +board = heltec_vision_master_e213 build_flags = ${esp32s3_base.build_flags} -Ivariants/heltec_vision_master_e213 diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index 169602a0d..d4e42ad1c 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -27,7 +27,7 @@ #define VEXT_ENABLE 18 // powers the oled display and the lora antenna boost #define VEXT_ON_VALUE 1 -#define BUTTON_PIN 21 +#define BUTTON_PIN 0 #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h index a122a7e0f..a8ec5485b 100644 --- a/variants/heltec_vision_master_e290/variant.h +++ b/variants/heltec_vision_master_e290/variant.h @@ -27,7 +27,7 @@ #define VEXT_ENABLE 18 // powers the e-ink display #define VEXT_ON_VALUE 1 -#define BUTTON_PIN 21 +#define BUTTON_PIN 0 #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH From 5cc8ca59a37d87943781caf655bb9df2d95a045b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 14 Jul 2024 09:38:19 +0800 Subject: [PATCH 0711/1377] Sync Wio lr1110 refresh with master (#4288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix protobuf structs handling (#4140) * Fix protobuf structs handling * Log instead of assert --------- Co-authored-by: Ben Meadors * BLE based logging (#4146) * WIP log characteristic * Bluetooth logging plumbing * Characteristic * Callback * Check for nullptr * Esp32 bluetooth impl * Formatting * Add thread name and log level * Add settings guard * Remove comments * Field name * Fixes esp32 * Open it up * Whoops * Move va_end past our logic * Use `upload_protocol = esptool` as with the other heltec devices instead of `esp-builtin` (#4151) * Standardize lat/lon position logs (#4156) * Standardize lat/lon position logs * Missed sone and condensed logs * [create-pull-request] automated change (#4157) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Pause BLE logging during want_config flow (#4162) * Update NimBLE to 1.4.2 (#4163) * Implement replies for all telemetry types based on variant tag (#4164) * Implement replies for all telemetry types based on variant tag * Remove check for `ignoreRequest`: modules can set this, don't need to check --------- Co-authored-by: Ben Meadors * Esptool is better * Explicitly set characteristic * fix INA3221 sensor (#4168) - pass wire to begin() - remove redundant setAddr() (already set in header) * Show compass on waypoint frame; clear when waypoint deleted (#4116) * Clear expired or deleted waypoint frame * Return 0 to CallbackObserver * Add a missing comment * Draw compass for waypoint frame * Display our own waypoints * [create-pull-request] automated change (#4171) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Add semihosting support for nrf52 devices (#4137) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * fix my botched merge - keep board_level = extra flag for rak3631_dbg --------- Co-authored-by: Ben Meadors * [create-pull-request] automated change (#4174) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Display alerts (#4170) * Move static functions into Screen.h, show compass during calibration * Move to _fontHeight macro to avoid collision * Move some alert functions to new alert handler * Catch missed reboot code * ESP32 fixes * Bump esp8266-oled-ssd1306 * Fixes for when a device has no screen * Use new startAlert(char*) helper class * Add EINK bits back to alert handling * Add noop class for no-display devices --------- Co-authored-by: Ben Meadors * Send file system manifest up on want_config (#4176) * Send file system manifest up on want_config * Platform specific methods * Helps to actually make the change * Clear * tell vscode, if formatting, use whatever our trunk formatter wants (#4186) without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * fix the build - would loop forever if there were no files to send (#4188) * Show owner.short_name on boot (and E-Ink sleep screen) (#4134) * Show owner.short_name on boot and sleep screen (on e-ink) * Update Screen.cpp - new line for short_name Boot screen short_name now below the region setting. Looks better on small screens. * Draw short_name on right --------- Co-authored-by: Thomas Göttgens Co-authored-by: todd-herbert Co-authored-by: Ben Meadors * nrf52 soft device will watchdog if you use ICE while BT on... (#4189) so have debugger disable bluetooth. * correct xiao_ble build preventing sx1262 init (#4191) * Force a compile time failur if FromRadio or ToRadio get larger than (#4190) a BLE packet size. We are actually very close to this threshold so important to make sure we don't accidentally pass it. * Clear vector after complete config state (#4194) * Clear after complete config * Don't collect . entries * Log file name and size * [create-pull-request] automated change (#4200) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Make the logs Colorful! (#4199) * Squash needlessly static functions (#4183) * Trim extra vprintf and filter for unprintable characters * Deprecate Router Client role (and make it Client) (#4201) * [create-pull-request] automated change (#4205) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Move waypoint (#4202) * Move waypoint screen draw into the waypoint module * Get the observer set up for the waypoint screen draw * Static squashing: screen dimensions Macros moved back to Screen.cpp, as a band-aid until we eventually move all those static functions into the Screen class. * Move getCompassDiam into Screen class (supress compiler warnings) At this stage, the method is still static, because it's used by drawNodeInfo, which has no tidy reference to our screen instance. This is probably just another band-aid until these static functions all move. * Use new getCompassDiam function in AccelerometerThread * Properly gate display code in WaypointModule --------- Co-authored-by: Todd Herbert * Fix flakey phone api transition from file manifest to complete (#4209) * Try fix flakey phone api transition from file manifest to complete * Skip * enable colors in platformio serial monitor (#4217) * When talking via serial, encapsulate log messages in protobufs if necessary (#4187) * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs --------- Co-authored-by: Ben Meadors * [create-pull-request] automated change (#4218) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fix SHT41 support (#4222) * Add SHT41 Serial to I2c Detection Code On the Seeed Wio-WM1110 Dev Kit board, the SHT41 chip was being incorrectly detected as SHT31. This patch adds the necessary serial number for the SHT41 chip to be correctly detected. fixes meshtastic/firmware#4221 * Add missing sensor read for SHT41 * Typo fix in logs - mhz - MHz (#4225) As reported by karamo, a few different places in our logs had incorrect capitalization of MHz. fixes meshtastic/firmware#4126 * New new BLE logging characteristic with LogRecord protos (#4220) * New UUID * New log radio characteristic with LogRecord protobuf * LogRecord * Merge derp * How did you get there * Trunk * Fix length * Remove assert * minor cleanup proposal (#4169) * MESHTASTIC_EXCLUDE_WIFI and HAS_WIFI cleanup... Our code was checking HAS_WIFI and the new MESHTASTIC_EXCLUDE_WIFI flags in various places (even though EXCLUDE_WIFI forces HAS_WIFI to 0). Instead just check HAS_WIFI, only use EXCLUDE_WIFI inside configuration.h * cleanup: use HAS_NETWORKING instead of HAS_WIFI || HAS_ETHERNET We already had HAS_NETWORKING as flag in MQTT to mean 'we have tcpip'. Generallize that and move it into configuration.h so that we can use it elsewhere. * Use #pragma once, because supported by gcc and all modern compilers instead of #ifdef DOTHFILE_H etc... --------- Co-authored-by: Jonathan Bennett * Add PowerMon support (#4155) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * fix linter warning * Cleanup buffer * Merge hex for wm1110 target(s) * Only sdk * Sudo * Fix exclude macros (#4233) * fix MESHTASTIC_EXCLUDE_BLUETOOTH * fix HAS_SCREEN=0 * fix MESHTASTIC_EXCLUDE_GPS * fix typo in build-nrf52.sh (#4231) chmod is the command, '+x' is the argument. * Tidy Wireless Paper variant files (#4238) * Quick tidy of pins_arduino.h Matches requests made at https://github.com/meshtastic/firmware/pull/4226#discussion_r1664183480) * Tidy variant.h * Change deprecated ADC attenuation parameter From 11dB to 12dB. Resolves compiler warning. Allegly, no impact on function: `This is deprecated, it behaves the same as `ADC_ATTEN_DB_12` * Updated raspbian CI to update apt repository ahead of libbluetooth. (#4243) * Fix BLE logging on nrf52 (#4244) * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. --------- Co-authored-by: Ben Meadors * Fix build when HAS_NETWORKING is false on nrf52 (#4237) (tested on a rak4631 by setting HAS_ETHERNET false when shrinking image) * If `toPhoneQueue` is full, still increment `fromNum` to avoid client never getting packets (#4246) * Update to SoftDevice 7.3.0 for wio-sdk-wm1110 and wio-tracker-wm1110 (#4248) * Update variant.h * Update wio-tracker-wm1110.json * Update wio-sdk-wm1110.json * Update platformio.ini * Update platformio.ini * Add files via upload * Add files via upload * Update variant.h * Cleanup NRF s140 Softdevice variants (#4252) Note: This idea is originally from @caveman99 and should be credited as such. Submitting as a separate PR so the work in meshtastic/firmware#4148 can be a bit cleaner and Seeed boards can build while that work is ongoing. The nrf52 boards that depend on the v7 softdevice all use the same code and linker files. Rather than duplicate the code, keep it all together with the platform. * Remove tracker variant specific soft device headers (#4255) * [create-pull-request] automated change (#4247) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Add wio-sdk-wm1110 to build. (#4258) The wio-sdk-wm1110 is distinct from the wio-tracker-wm1110, with different platformio build options and pin config. This change adds the wio-sdk-wm1110 to the CI matrix so firmware is built as part of release. * fix python warning in uf2conf (#4235) the old regex worked but was technically incorrect. fixes: Generating NRF52 uf2 file /home/kevinh/development/meshtastic/firmware/bin/uf2conv.py:195: SyntaxWarning: invalid escape sequence '\s' words = re.split('\s+', line) Converting to uf2, output size: 1458688, start address: 0x26000 * Collect hex files and specifically wm1110 sdk * Skip dfu file for sdk (for now) * Helps if you remove the original clause * Add Heltec new boards. (#4226) * Add Heltec new boards * Update variant.h disable RTC by default * Add Heltec New boards * Add Heltec new boards * Update Heltec Mesh Node definition. * Update Heltec Vision Mater E290 * [create-pull-request] automated change (#4259) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Trunk fmt * Fix macros * Move e290 to board level extra while CI is broken * Tell trunk to ignore bin folder * Fix missing * Update trunk.yaml, fix whitespace * Update trunk.yaml * Update build_raspbian_armv7l.yml --fix-missing * [create-pull-request] automated change (#4263) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * GPS Power State tidy-up (#4161) * Refactor GPSPowerState enum Identifies a case where the GPS hardware is awake, but an update is not yet desired * Change terminology * Clear old lock-time prediction on triple press * Use exponential smoothing to predict lock time * Rename averageLockTime to predictedLockTime * Attempt: Send PMREQ with duration 0 on MCU deep-sleep * Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep * Revert "Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit 8b697cd2a445355dcfab5b33e0ce7a3128cab151. * Revert "Attempt: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit 9d29ec7603a88056b9115796b29b5023165a93bb. * Remove unused notifyGPSSleep Observable Handled with notifyDeepSleep, and enable() / disable() * WIP: simplify GPS power management An initial attempt only. * Honor #3e9e0fd * No-op when moving between GPS_IDLE and GPS_ACTIVE * Ensure U-blox GPS is awake to receive indefinite sleep command * Longer pause when waking U-blox to send sleep command * Actually implement soft and hard sleep.. * Dynamically estimate the threshold for GPS_HARDSLEEP * Fallback to GPS_HARDSLEEP, if GPS_SOFTSLEEP unsupported * Move "excessive search time" behavior to scheduler class * Minor logging adjustments * Promote log to warning * Gratuitous buffer clearing on boot * Fix inverted standby pin logic Specifically the standby pin for L76B, L76K and clones Discovered during T-Echo testing: totally broken function, probe method failing. * Remove redundant pin init Now handled by setPowerState * Replace max() with if statements Avoid those platform specific implementations.. * Trunk formatting New round of settings.json changes keep catching me out, have to remember to re-enable my "clang-format" for windows workaround. * Remove some asserts from setPowerState Original aim was to prevent sending a 0 second PMREQ to U-blox hardware as part of a timed sleep (GPS_HARDSLEEP, GPS_SOFTSLEEP). I'm not sure this is super important, and it feels tidier to just allow the 0 second sleeptime here, rather than fudge the sleeptime further up. * Fix an error determining whether GPS_SOFTSLEEP is supported * Clarify a log entry * Set PIN_STANDBY for MCU deep-sleep Required to reach TTGO's advertised 0.25mA sleep current for T-Echo. Without this change: ~6mA. * Optimize the shutdown current of RAK10701 to around 25uA (#4260) Co-authored-by: Jonathan Bennett * INA3221 sensor: use for bus voltage & environment metrics (#4215) * use INA3221 for bus voltage; fixes for telemetry variants - add to sensors available for environment telemetry (to report voltage/current) - add vars to define channels to use for battery voltage (for getBusVoltage) and environment metrics (default to CH1 for both) - write to the correct fields on the measurement struct depending on the measurement variant, and DRY up the sensor measurement collection code a bit - this might be suitable for a common implementation for the INA* sensors in a future PR... * formatting * derp --------- Co-authored-by: Ben Meadors * WM1110 SDK kit enter serial DFU and add deployment packages (#4266) * Switch default upload protocol to nrfutil so that pio generates zip deploy packages * Enter serial DFU on SDK board * Remove guard for DFU zip from SDK build * NRF_USE_SERIAL_DFU macro instead * Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors Co-authored-by: slash-bit * Move up telemetry defaults to every 30 minutes (#4274) * Don't send node info interrogation when ch. util is >25% (#4273) * Moar LR1110 Targets * update SD_FLASH_SIZE to 0x27000 (#4232) The 7.3.0 softdevice needs the extra 1000 :) * Fix spacing. --------- Co-authored-by: Mike Co-authored-by: Ben Meadors Co-authored-by: Mike G Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Co-authored-by: Warren Guy <5602790+warrenguy@users.noreply.github.com> Co-authored-by: todd-herbert Co-authored-by: geeksville Co-authored-by: Jonathan Bennett Co-authored-by: Alexander <156134901+Dorn8010@users.noreply.github.com> Co-authored-by: Thomas Göttgens Co-authored-by: quimnut Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com> Co-authored-by: Agent Blu, 006 Co-authored-by: Mark Trevor Birss Co-authored-by: Aaron.Lee <32860565+Heltec-Aaron-Lee@users.noreply.github.com> Co-authored-by: Daniel.Cao <144674500+DanielCao0@users.noreply.github.com> Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: slash-bit --- .github/actions/setup-base/action.yml | 2 +- .github/workflows/build_native.yml | 2 +- .github/workflows/build_nrf52.yml | 1 + .github/workflows/build_raspbian.yml | 2 +- .github/workflows/build_raspbian_armv7l.yml | 1 + .github/workflows/main_matrix.yml | 3 +- .trunk/trunk.yaml | 6 +- bin/build-nrf52.sh | 24 +- bin/uf2conv.py | 223 +- boards/heltec_mesh_node_t114.json | 53 + platformio.ini | 4 + protobufs | 2 +- src/Power.cpp | 15 +- src/gps/GPS.cpp | 540 ++- src/gps/GPS.h | 47 +- src/gps/GPSUpdateScheduling.cpp | 118 + src/gps/GPSUpdateScheduling.h | 29 + src/graphics/EInkDisplay2.cpp | 3 +- src/graphics/EInkDisplay2.h | 8 +- src/graphics/Screen.cpp | 160 +- src/graphics/Screen.h | 37 +- src/main.cpp | 6 +- src/mesh/Default.h | 1 + src/mesh/MeshModule.cpp | 15 +- src/mesh/MeshModule.h | 28 +- src/mesh/MeshService.cpp | 6 +- src/mesh/NodeDB.cpp | 3 +- src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/config.pb.h | 10 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 4 +- .../generated/meshtastic/module_config.pb.h | 20 +- src/mesh/generated/meshtastic/telemetry.pb.h | 18 +- src/modules/AdminModule.cpp | 1 + src/modules/AdminModule.h | 2 +- src/modules/CannedMessageModule.cpp | 45 +- src/modules/CannedMessageModule.h | 6 +- src/modules/Telemetry/AirQualityTelemetry.cpp | 3 +- src/modules/Telemetry/DeviceTelemetry.cpp | 3 +- .../Telemetry/EnvironmentTelemetry.cpp | 17 +- src/modules/Telemetry/PowerTelemetry.cpp | 6 +- .../Telemetry/Sensor/INA3221Sensor.cpp | 65 +- src/modules/Telemetry/Sensor/INA3221Sensor.h | 25 + src/modules/WaypointModule.cpp | 18 +- src/modules/esp32/AudioModule.cpp | 6 +- src/modules/esp32/PaxcounterModule.cpp | 2 +- src/platform/esp32/architecture.h | 8 + src/platform/nrf52/NRF52Bluetooth.cpp | 1 + src/platform/nrf52/main-nrf52.cpp | 17 + src/sleep.cpp | 6 +- src/sleep.h | 2 - variants/heltec_capsule_sensor_v3/variant.h | 2 +- variants/heltec_mesh_node_t114/platformio.ini | 15 + variants/heltec_mesh_node_t114/variant.cpp | 44 + variants/heltec_mesh_node_t114/variant.h | 210 ++ .../heltec_vision_master_e213/pins_arduino.h | 63 + .../heltec_vision_master_e213/platformio.ini | 23 + variants/heltec_vision_master_e213/variant.h | 58 + .../heltec_vision_master_e290/pins_arduino.h | 61 + .../heltec_vision_master_e290/platformio.ini | 25 + variants/heltec_vision_master_e290/variant.h | 58 + .../heltec_vision_master_t190/pins_arduino.h | 61 + .../heltec_vision_master_t190/platformio.ini | 13 + variants/heltec_vision_master_t190/variant.h | 76 + variants/heltec_wireless_paper/pins_arduino.h | 5 - variants/wio-sdk-wm1110/nrf52840_s140_v7.ld | 38 - variants/wio-sdk-wm1110/platformio.ini | 6 +- variants/wio-sdk-wm1110/softdevice/nrf_sdm.h | 380 --- variants/wio-sdk-wm1110/variant.h | 2 + .../wio-tracker-wm1110/nrf52840_s140_v7.ld | 38 - variants/wio-tracker-wm1110/platformio.ini | 3 +- variants/wio-tracker-wm1110/softdevice/ble.h | 652 ---- .../wio-tracker-wm1110/softdevice/ble_err.h | 92 - .../wio-tracker-wm1110/softdevice/ble_gap.h | 2895 ----------------- .../wio-tracker-wm1110/softdevice/ble_gatt.h | 232 -- .../wio-tracker-wm1110/softdevice/ble_gattc.h | 764 ----- .../wio-tracker-wm1110/softdevice/ble_gatts.h | 904 ----- .../wio-tracker-wm1110/softdevice/ble_hci.h | 135 - .../wio-tracker-wm1110/softdevice/ble_l2cap.h | 495 --- .../softdevice/ble_ranges.h | 149 - .../wio-tracker-wm1110/softdevice/ble_types.h | 217 -- .../softdevice/nrf52/nrf_mbr.h | 259 -- .../wio-tracker-wm1110/softdevice/nrf_error.h | 90 - .../softdevice/nrf_error_sdm.h | 73 - .../softdevice/nrf_error_soc.h | 85 - .../wio-tracker-wm1110/softdevice/nrf_nvic.h | 449 --- .../wio-tracker-wm1110/softdevice/nrf_sdm.h | 380 --- .../wio-tracker-wm1110/softdevice/nrf_soc.h | 1046 ------ .../wio-tracker-wm1110/softdevice/nrf_svc.h | 98 - version.properties | 2 +- 90 files changed, 1855 insertions(+), 9971 deletions(-) create mode 100644 boards/heltec_mesh_node_t114.json create mode 100644 src/gps/GPSUpdateScheduling.cpp create mode 100644 src/gps/GPSUpdateScheduling.h create mode 100644 variants/heltec_mesh_node_t114/platformio.ini create mode 100644 variants/heltec_mesh_node_t114/variant.cpp create mode 100644 variants/heltec_mesh_node_t114/variant.h create mode 100644 variants/heltec_vision_master_e213/pins_arduino.h create mode 100644 variants/heltec_vision_master_e213/platformio.ini create mode 100644 variants/heltec_vision_master_e213/variant.h create mode 100644 variants/heltec_vision_master_e290/pins_arduino.h create mode 100644 variants/heltec_vision_master_e290/platformio.ini create mode 100644 variants/heltec_vision_master_e290/variant.h create mode 100644 variants/heltec_vision_master_t190/pins_arduino.h create mode 100644 variants/heltec_vision_master_t190/platformio.ini create mode 100644 variants/heltec_vision_master_t190/variant.h delete mode 100644 variants/wio-sdk-wm1110/nrf52840_s140_v7.ld delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_sdm.h delete mode 100644 variants/wio-tracker-wm1110/nrf52840_s140_v7.ld delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_err.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gap.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatt.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gattc.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_gatts.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_hci.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_l2cap.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_ranges.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/ble_types.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_nvic.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_sdm.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_soc.h delete mode 100644 variants/wio-tracker-wm1110/softdevice/nrf_svc.h diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index b5b4cb6f3..7f8659523 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -14,7 +14,7 @@ runs: - name: Install dependencies shell: bash run: | - sudo apt-get -y update + sudo apt-get -y update --fix-missing sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev - name: Setup Python diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 8fe8e6c31..3e8b4c001 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - sudo apt-get update + sudo apt-get update --fix-missing sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index eb1779963..ac509a096 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -29,6 +29,7 @@ jobs: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip overwrite: true path: | + release/*.hex release/*.uf2 release/*.elf release/*.zip diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index d262d8739..1fd8fad30 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - apt-get update -y + apt-get update -y --fix-missing apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index ee5eb66eb..39b297d1b 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -13,6 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | + apt-get update -y --fix-missing apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 25a0fbad2..14c8a9d10 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -136,7 +136,7 @@ jobs: build-rpi2040, package-raspbian, package-raspbian-armv7l, - package-native + package-native, ] steps: - name: Checkout code @@ -168,6 +168,7 @@ jobs: path: | ./firmware-*.bin ./firmware-*.uf2 + ./firmware-*.hex ./firmware-*-ota.zip ./device-*.sh ./device-*.bat diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 8a2f18ad5..2d9f60899 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.22.1 + version: 1.22.2 plugins: sources: - id: trunk @@ -31,6 +31,10 @@ lint: - gitleaks@8.18.2 - clang-format@16.0.3 - prettier@3.2.5 + ignore: + - linters: [ALL] + paths: + - bin/** runtimes: enabled: - python@3.10.8 diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index fa6eacd23..cf4ca60cb 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -23,8 +23,10 @@ basename=firmware-$1-$VERSION pio run --environment $1 # -v SRCELF=.pio/build/$1/firmware.elf -DFUPKG=.pio/build/$1/firmware.zip cp $SRCELF $OUTDIR/$basename.elf + +echo "Generating NRF52 dfu file" +DFUPKG=.pio/build/$1/firmware.zip cp $DFUPKG $OUTDIR/$basename-ota.zip echo "Generating NRF52 uf2 file" @@ -33,13 +35,15 @@ SRCHEX=.pio/build/$1/firmware.hex # if WM1110 target, merge hex with softdevice 7.3.0 if (echo $1 | grep -q "wio-sdk-wm1110"); then echo "Merging with softdevice" - sudo chmod +x ./bin/mergehex - bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/merged_fimware.hex - SRCHEX=.pio/build/$1/merged_fimware.hex + sudo chmod +x ./bin/mergehex + bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex + SRCHEX=.pio/build/$1/$basename.hex + bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 + cp $SRCHEX $OUTDIR + cp bin/*.uf2 $OUTDIR +else + bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 + cp bin/device-install.* $OUTDIR + cp bin/device-update.* $OUTDIR + cp bin/*.uf2 $OUTDIR fi - -bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 - -cp bin/device-install.* $OUTDIR -cp bin/device-update.* $OUTDIR -cp bin/*.uf2 $OUTDIR diff --git a/bin/uf2conv.py b/bin/uf2conv.py index b619d14db..a1e241b7a 100755 --- a/bin/uf2conv.py +++ b/bin/uf2conv.py @@ -1,39 +1,38 @@ #!/usr/bin/env python3 -import sys -import struct -import subprocess -import re +import argparse import os import os.path -import argparse +import re +import struct +import subprocess +import sys - -UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" -UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected -UF2_MAGIC_END = 0x0AB16F30 # Ditto +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto families = { - 'SAMD21': 0x68ed2b88, - 'SAML21': 0x1851780a, - 'SAMD51': 0x55114460, - 'NRF52': 0x1b57745f, - 'STM32F0': 0x647824b6, - 'STM32F1': 0x5ee21072, - 'STM32F2': 0x5d1a0a2e, - 'STM32F3': 0x6b846188, - 'STM32F4': 0x57755a57, - 'STM32F7': 0x53b80f00, - 'STM32G0': 0x300f5633, - 'STM32G4': 0x4c71240a, - 'STM32H7': 0x6db66082, - 'STM32L0': 0x202e3a91, - 'STM32L1': 0x1e1f432d, - 'STM32L4': 0x00ff6919, - 'STM32L5': 0x04240bdf, - 'STM32WB': 0x70d16653, - 'STM32WL': 0x21460ff0, - 'ATMEGA32': 0x16573617, - 'MIMXRT10XX': 0x4FB2D5BD + "SAMD21": 0x68ED2B88, + "SAML21": 0x1851780A, + "SAMD51": 0x55114460, + "NRF52": 0x1B57745F, + "STM32F0": 0x647824B6, + "STM32F1": 0x5EE21072, + "STM32F2": 0x5D1A0A2E, + "STM32F3": 0x6B846188, + "STM32F4": 0x57755A57, + "STM32F7": 0x53B80F00, + "STM32G0": 0x300F5633, + "STM32G4": 0x4C71240A, + "STM32H7": 0x6DB66082, + "STM32L0": 0x202E3A91, + "STM32L1": 0x1E1F432D, + "STM32L4": 0x00FF6919, + "STM32L5": 0x04240BDF, + "STM32WB": 0x70D16653, + "STM32WL": 0x21460FF0, + "ATMEGA32": 0x16573617, + "MIMXRT10XX": 0x4FB2D5BD, } INFO_FILE = "/INFO_UF2.TXT" @@ -46,15 +45,17 @@ def is_uf2(buf): w = struct.unpack(" 10*1024*1024: + if padding > 10 * 1024 * 1024: assert False, "More than 10M of padding needed at " + ptr if padding % 4 != 0: assert False, "Non-word padding size at " + ptr @@ -91,6 +92,7 @@ def convert_from_uf2(buf): curraddr = newaddr + datalen return outp + def convert_to_carray(file_content): outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {" for i in range(len(file_content)): @@ -100,6 +102,7 @@ def convert_to_carray(file_content): outp += "\n};\n" return outp + def convert_to_uf2(file_content): global familyid datapadding = b"" @@ -109,13 +112,21 @@ def convert_to_uf2(file_content): outp = b"" for blockno in range(numblocks): ptr = 256 * blockno - chunk = file_content[ptr:ptr + 256] + chunk = file_content[ptr : ptr + 256] flags = 0x0 if familyid: flags |= 0x2000 - hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": drives.append(words[0]) else: @@ -206,7 +238,6 @@ def get_drives(): for d in os.listdir(rootpath): drives.append(os.path.join(rootpath, d)) - def has_info(d): try: return os.path.isfile(d + INFO_FILE) @@ -217,7 +248,7 @@ def get_drives(): def board_id(path): - with open(path + INFO_FILE, mode='r') as file: + with open(path + INFO_FILE, mode="r") as file: file_content = file.read() return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) @@ -235,30 +266,61 @@ def write_file(name, buf): def main(): global appstartaddr, familyid + def error(msg): print(msg) sys.exit(1) - parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') - parser.add_argument('input', metavar='INPUT', type=str, nargs='?', - help='input file (HEX, BIN or UF2)') - parser.add_argument('-b' , '--base', dest='base', type=str, - default="0x2000", - help='set base address of application for BIN format (default: 0x2000)') - parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, - help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') - parser.add_argument('-d' , '--device', dest="device_path", - help='select a device path to flash') - parser.add_argument('-l' , '--list', action='store_true', - help='list connected devices') - parser.add_argument('-c' , '--convert', action='store_true', - help='do not flash, just convert') - parser.add_argument('-D' , '--deploy', action='store_true', - help='just flash, do not convert') - parser.add_argument('-f' , '--family', dest='family', type=str, - default="0x0", - help='specify familyID - number or name (default: 0x0)') - parser.add_argument('-C' , '--carray', action='store_true', - help='convert binary file to a C array, not UF2') + + parser = argparse.ArgumentParser(description="Convert to UF2 or flash directly.") + parser.add_argument( + "input", + metavar="INPUT", + type=str, + nargs="?", + help="input file (HEX, BIN or UF2)", + ) + parser.add_argument( + "-b", + "--base", + dest="base", + type=str, + default="0x2000", + help="set base address of application for BIN format (default: 0x2000)", + ) + parser.add_argument( + "-o", + "--output", + metavar="FILE", + dest="output", + type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible', + ) + parser.add_argument( + "-d", "--device", dest="device_path", help="select a device path to flash" + ) + parser.add_argument( + "-l", "--list", action="store_true", help="list connected devices" + ) + parser.add_argument( + "-c", "--convert", action="store_true", help="do not flash, just convert" + ) + parser.add_argument( + "-D", "--deploy", action="store_true", help="just flash, do not convert" + ) + parser.add_argument( + "-f", + "--family", + dest="family", + type=str, + default="0x0", + help="specify familyID - number or name (default: 0x0)", + ) + parser.add_argument( + "-C", + "--carray", + action="store_true", + help="convert binary file to a C array, not UF2", + ) args = parser.parse_args() appstartaddr = int(args.base, 0) @@ -268,14 +330,17 @@ def main(): try: familyid = int(args.family, 0) except ValueError: - error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) + error( + "Family ID needs to be a number or one of: " + + ", ".join(families.keys()) + ) if args.list: list_drives() else: if not args.input: error("Need input file") - with open(args.input, mode='rb') as f: + with open(args.input, mode="rb") as f: inpbuf = f.read() from_uf2 = is_uf2(inpbuf) ext = "uf2" @@ -291,8 +356,10 @@ def main(): ext = "h" else: outbuf = convert_to_uf2(inpbuf) - print("Converting to %s, output size: %d, start address: 0x%x" % - (ext, len(outbuf), appstartaddr)) + print( + "Converting to %s, output size: %d, start address: 0x%x" + % (ext, len(outbuf), appstartaddr) + ) if args.convert or ext != "uf2": drives = [] if args.output == None: diff --git a/boards/heltec_mesh_node_t114.json b/boards/heltec_mesh_node_t114.json new file mode 100644 index 000000000..5c97d8c75 --- /dev/null +++ b/boards/heltec_mesh_node_t114.json @@ -0,0 +1,53 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_node_t114", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Heltec nrf (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "FIXME", + "vendor": "Heltec" +} diff --git a/platformio.ini b/platformio.ini index bcdcc0034..b3f677247 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,6 +35,10 @@ default_envs = tbeam ;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_micro ;default_envs = heltec_capsule_sensor_v3 +;default_envs = heltec_vision_master_t190 +;default_envs = heltec_vision_master_e213 +;default_envs = heltec_vision_master_e290 +;default_envs = heltec_mesh_node_t114 extra_configs = arch/*/*.ini diff --git a/protobufs b/protobufs index 1198b7dba..10494bf32 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1198b7dbabf9768cb0143d2897707b4c7a51a5da +Subproject commit 10494bf328ac051fc4add9ddeb677eebf337b531 diff --git a/src/Power.cpp b/src/Power.cpp index cea373806..19c5c9937 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -232,12 +232,20 @@ class AnalogBatteryLevel : public HasBatteryLevel raw = espAdcRead(); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled *= operativeAdcMultiplier; -#else // block for all other platforms +#else // block for all other platforms +#ifdef ADC_CTRL // enable adc voltage divider when we need to read + pinMode(ADC_CTRL, OUTPUT); + digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); + delay(10); +#endif for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) { raw += analogRead(BATTERY_PIN); } raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; +#ifdef ADC_CTRL // disable adc voltage divider when we need to read + digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); +#endif #endif if (!initial_read_done) { @@ -441,6 +449,11 @@ class AnalogBatteryLevel : public HasBatteryLevel if (!ina260Sensor.isInitialized()) return ina260Sensor.runOnce() > 0; return ina260Sensor.isRunning(); + } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first == + config.power.device_battery_ina_address) { + if (!ina3221Sensor.isInitialized()) + return ina3221Sensor.runOnce() > 0; + return ina3221Sensor.isRunning(); } return false; } diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index fe70bcdcd..017a2d025 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -9,6 +9,7 @@ #include "main.h" // pmu_found #include "sleep.h" +#include "GPSUpdateScheduling.h" #include "cas.h" #include "ubx.h" @@ -22,19 +23,6 @@ #define GPS_RESET_MODE HIGH #endif -// How many minutes of sleep make it worthwhile to power-off the GPS -// Shorter than this, and GPS will only enter standby -// Affected by lock-time, and config.position.gps_update_interval -#ifndef GPS_STANDBY_THRESHOLD_MINUTES -#define GPS_STANDBY_THRESHOLD_MINUTES 15 -#endif - -// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby -// Shorter than this, and we'll just wait instead -#ifndef GPS_IDLE_THRESHOLD_SECONDS -#define GPS_IDLE_THRESHOLD_SECONDS 10 -#endif - #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) HardwareSerial *GPS::_serial_gps = &Serial1; #else @@ -43,6 +31,8 @@ HardwareSerial *GPS::_serial_gps = NULL; GPS *gps = nullptr; +GPSUpdateScheduling scheduling; + /// Multiple GPS instances might use the same serial port (in sequence), but we can /// only init that port once. static bool didSerialInit; @@ -52,6 +42,25 @@ uint8_t uBloxProtocolVersion; #define GPS_SOL_EXPIRY_MS 5000 // in millis. give 1 second time to combine different sentences. NMEA Frequency isn't higher anyway #define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc) +// For logging +const char *getGPSPowerStateString(GPSPowerState state) +{ + switch (state) { + case GPS_ACTIVE: + return "ACTIVE"; + case GPS_IDLE: + return "IDLE"; + case GPS_SOFTSLEEP: + return "SOFTSLEEP"; + case GPS_HARDSLEEP: + return "HARDSLEEP"; + case GPS_OFF: + return "OFF"; + default: + assert(false); // Unhandled enum value.. + } +} + void GPS::UBXChecksum(uint8_t *message, size_t length) { uint8_t CK_A = 0, CK_B = 0; @@ -773,7 +782,6 @@ bool GPS::setup() } notifyDeepSleepObserver.observe(¬ifyDeepSleep); - notifyGPSSleepObserver.observe(¬ifyGPSSleep); return true; } @@ -782,132 +790,192 @@ GPS::~GPS() { // we really should unregister our sleep observer notifyDeepSleepObserver.unobserve(¬ifyDeepSleep); - notifyGPSSleepObserver.observe(¬ifyGPSSleep); } -const char *GPS::powerStateToString() +// Put the GPS hardware into a specified state +void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) { - switch (powerState) { - case GPS_OFF: - return "OFF"; - case GPS_IDLE: - return "IDLE"; - case GPS_STANDBY: - return "STANDBY"; + // Update the stored GPSPowerstate, and create local copies + GPSPowerState oldState = powerState; + powerState = newState; + LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState)); + + switch (newState) { case GPS_ACTIVE: - return "ACTIVE"; - default: - return "UNKNOWN"; + case GPS_IDLE: + if (oldState == GPS_ACTIVE || oldState == GPS_IDLE) // If hardware already awake, no changes needed + break; + if (oldState != GPS_ACTIVE && oldState != GPS_IDLE) // If hardware just waking now, clear buffer + clearBuffer(); + powerMon->setState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(true); // Power (EN pin): on + setPowerPMU(true); // Power (PMU): on + writePinStandby(false); // Standby (pin): awake (not standby) + setPowerUBLOX(true); // Standby (UBLOX): awake + break; + + case GPS_SOFTSLEEP: + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(true); // Power (EN pin): on + setPowerPMU(true); // Power (PMU): on + writePinStandby(true); // Standby (pin): asleep (not awake) + setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed + break; + + case GPS_HARDSLEEP: + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(false); // Power (EN pin): off + setPowerPMU(false); // Power (PMU): off + writePinStandby(true); // Standby (pin): asleep (not awake) + setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed + break; + + case GPS_OFF: + assert(sleepTime == 0); // This is an indefinite sleep + powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) + writePinEN(false); // Power (EN pin): off + setPowerPMU(false); // Power (PMU): off + writePinStandby(true); // Standby (pin): asleep + setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely + break; } } -void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) +// Set power with EN pin, if relevant +void GPS::writePinEN(bool on) { - // Record the current powerState - if (on) - powerState = GPS_ACTIVE; - else if (!enabled) // User has disabled with triple press - powerState = GPS_OFF; - else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL) - powerState = GPS_IDLE; - else if (standbyOnly) - powerState = GPS_STANDBY; - else - powerState = GPS_OFF; - - LOG_DEBUG("GPS::powerState=%s\n", powerStateToString()); - - // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out. - if (!on && powerState == GPS_IDLE) + // Abort: if conflict with Canned Messages when using Wisblock(?) + if (HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1)) return; - if (on) { - powerMon->setState(meshtastic_PowerMon_State_GPS_Active); - clearBuffer(); // drop any old data waiting in the buffer before re-enabling - if (en_gpio) - digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time - } else { - powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); - } - isInPowersave = !on; - if (!standbyOnly && en_gpio != 0 && - !(HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1))) { - LOG_DEBUG("GPS powerdown using GPS_EN_ACTIVE\n"); - digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); + // Abort: if pin unset + if (!en_gpio) return; - } -#ifdef HAS_PMU // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, so treat as a standby. - if (pmu_found && PMU) { - uint8_t model = PMU->getChipModel(); - if (model == XPOWERS_AXP2101) { - if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { - // t-beam v1.2 GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3); - } else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) { - // t-beam-s3-core GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4); - } - } else if (model == XPOWERS_AXP192) { - // t-beam v1.1 GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3); - } - return; - } + + // Determine new value for the pin + bool val = GPS_EN_ACTIVE ? on : !on; + + // Write and log + pinMode(en_gpio, OUTPUT); + digitalWrite(en_gpio, val); +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("Pin EN %s\n", val == HIGH ? "HIGH" : "LOW"); #endif +} + +// Set the value of the STANDBY pin, if relevant +// true for standby state, false for awake +void GPS::writePinStandby(bool standby) +{ #ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones - if (on) { - LOG_INFO("Waking GPS\n"); - pinMode(PIN_GPS_STANDBY, OUTPUT); - // Some PCB's use an inverse logic due to a transistor driver - // Example for this is the Pico-Waveshare Lora+GPS HAT -#ifdef PIN_GPS_STANDBY_INVERTED - digitalWrite(PIN_GPS_STANDBY, 0); + +// Determine the new value for the pin +// Normally: active HIGH for awake +#if PIN_GPS_STANDBY_INVERTED + bool val = standby; #else - digitalWrite(PIN_GPS_STANDBY, 1); + bool val = !standby; #endif + + // Write and log + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, val); +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("Pin STANDBY %s\n", val == HIGH ? "HIGH" : "LOW"); +#endif +#endif +} + +// Enable / Disable GPS with PMU, if present +void GPS::setPowerPMU(bool on) +{ + // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, + // so treat as a standby. +#ifdef HAS_PMU + // Abort: if no PMU + if (!pmu_found) return; - } else { - LOG_INFO("GPS entering sleep\n"); - // notifyGPSSleep.notifyObservers(NULL); - pinMode(PIN_GPS_STANDBY, OUTPUT); -#ifdef PIN_GPS_STANDBY_INVERTED - digitalWrite(PIN_GPS_STANDBY, 1); -#else - digitalWrite(PIN_GPS_STANDBY, 0); -#endif + + // Abort: if PMU not initialized + if (!PMU) return; - } -#endif - if (!on) { - if (gnssModel == GNSS_MODEL_UBLOX) { - uint8_t msglen; - LOG_DEBUG("Sleep Time: %i\n", sleepTime); - if (strncmp(info.hwVersion, "000A0000", 8) != 0) { - for (int i = 0; i < 4; i++) { - gps->_message_PMREQ[0 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet - } - msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ); - } else { - for (int i = 0; i < 4; i++) { - gps->_message_PMREQ_10[4 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet - } - msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10); - } - gps->_serial_gps->write(gps->UBXscratch, msglen); + + uint8_t model = PMU->getChipModel(); + if (model == XPOWERS_AXP2101) { + if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { + // t-beam v1.2 GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3); + } else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) { + // t-beam-s3-core GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4); } + } else if (model == XPOWERS_AXP192) { + // t-beam v1.1 GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3); + } + +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("PMU %s\n", on ? "on" : "off"); +#endif +#endif +} + +// Set UBLOX power, if relevant +void GPS::setPowerUBLOX(bool on, uint32_t sleepMs) +{ + // Abort: if not UBLOX hardware + if (gnssModel != GNSS_MODEL_UBLOX) + return; + + // If waking + if (on) { + gps->_serial_gps->write(0xFF); + clearBuffer(); // This often returns old data, so drop it +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("UBLOX: wake\n"); +#endif + } + + // If putting to sleep + else { + uint8_t msglen; + + // If we're being asked to sleep indefinitely, make *sure* we're awake first, to process the new sleep command + if (sleepMs == 0) { + setPowerUBLOX(true); + delay(500); + } + + // Determine hardware version + if (strncmp(info.hwVersion, "000A0000", 8) != 0) { + // Encode the sleep time in millis into the packet + for (int i = 0; i < 4; i++) + gps->_message_PMREQ[0 + i] = sleepMs >> (i * 8); + + // Record the message length + msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ); + } else { + // Encode the sleep time in millis into the packet + for (int i = 0; i < 4; i++) + gps->_message_PMREQ_10[4 + i] = sleepMs >> (i * 8); + + // Record the message length + msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10); + #ifdef GNSS_Airoha // add by WayenWeng - else { if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { // TODO, send rtc mode command digitalWrite(PIN_GPS_EN, LOW); } - } #endif - } else { - if (gnssModel == GNSS_MODEL_UBLOX) { - gps->_serial_gps->write(0xFF); - clearBuffer(); // This often returns old data, so drop it } + + // Send the UBX packet + gps->_serial_gps->write(gps->UBXscratch, msglen); + +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("UBLOX: sleep for %dmS\n", sleepMs); +#endif } } @@ -920,111 +988,57 @@ void GPS::setConnected() } } -/** - * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode - * - * calls sleep/wake - */ -void GPS::setAwake(bool wantAwake) +// We want a GPS lock. Wake the hardware +void GPS::up() { + scheduling.informSearching(); + setPowerState(GPS_ACTIVE); +} - // If user has disabled GPS, make sure it is off, not just in standby or idle - if (!wantAwake && !enabled && powerState != GPS_OFF) { - setGPSPower(false, false, 0); - return; - } +// We've got a GPS lock. Enter a low power state, potentially. +void GPS::down() +{ + scheduling.informGotLock(); + uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs(); + uint32_t sleepTime = scheduling.msUntilNextSearch(); + uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); - // If GPS power state needs to change - if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) { - LOG_DEBUG("WANT GPS=%d\n", wantAwake); + LOG_DEBUG("%us until next search\n", sleepTime / 1000); - // Calculate how long it takes to get a GPS lock - if (wantAwake) { - // Record the time we start looking for a lock - lastWakeStartMsec = millis(); #ifdef GNSS_Airoha - lastFixStartMsec = 0; + lastFixStartMsec = 0; #endif - } else { - // Record by how much we missed our ideal target postion.gps_update_interval (for logging only) - // Need to calculate this before we update lastSleepStartMsec, to make the new prediction - int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime(); - // Record the time we finish looking for a lock - lastSleepStartMsec = millis(); - - // How long did it take to get GPS lock this time? - uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec; - - // Update the lock-time prediction - // Used pre-emptively, attempting to hit target of gps.position_update_interval - switch (GPSCycles) { - case 0: - LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000); - break; - case 1: - predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value - LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000); - break; - default: - // Predict lock-time using exponential smoothing: respond slowly to changes - predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction - LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000, - (lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000); - } - GPSCycles++; - } - - // How long to wait before attempting next GPS update - // Aims to hit position.gps_update_interval by using the lock-time prediction - uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0; - - // If long interval between updates: power off between updates - if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) { - setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime); - } - - // If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time - // We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due - // Will decide which inside setGPSPower method - else { -#ifdef GPS_UC6580 - setGPSPower(wantAwake, false, compensatedSleepTime); -#else - setGPSPower(wantAwake, true, compensatedSleepTime); + // If update interval less than 10 seconds, no attempt to sleep + if (updateInterval <= 10 * 1000UL) + setPowerState(GPS_IDLE); + else { + // Check whether the GPS hardware is capable of GPS_SOFTSLEEP + // If not, fallback to GPS_HARDSLEEP instead + bool softsleepSupported = false; + if (gnssModel == GNSS_MODEL_UBLOX) // U-blox is supported via PMREQ + softsleepSupported = true; +#ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin + softsleepSupported = true; #endif - } + + // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP? + // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050 + // https://www.desmos.com/calculator/6gvjghoumr + // This is not particularly accurate, but probably an impromevement over a single, fixed threshold + uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22)); + LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000); + + // If update interval too short: softsleep (if supported by hardware) + if (softsleepSupported && updateInterval < hardsleepThreshold) + setPowerState(GPS_SOFTSLEEP, sleepTime); + + // If update interval long enough (or softsleep unsupported): hardsleep instead + else + setPowerState(GPS_HARDSLEEP, sleepTime); } } -/** Get how long we should stay looking for each acquisition in msecs - */ -uint32_t GPS::getWakeTime() const -{ - uint32_t t = config.position.position_broadcast_secs; - - if (t == UINT32_MAX) - return t; // already maxint - - return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); -} - -/** Get how long we should sleep between aqusition attempts in msecs - */ -uint32_t GPS::getSleepTime() const -{ - uint32_t t = config.position.gps_update_interval; - - // We'll not need the GPS thread to wake up again after first acq. with fixed position. - if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED || config.position.fixed_position) - t = UINT32_MAX; // Sleep forever now - - if (t == UINT32_MAX) - return t; // already maxint - - return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval); -} - void GPS::publishUpdate() { if (shouldPublish) { @@ -1073,13 +1087,13 @@ int32_t GPS::runOnce() return disable(); } - if (whileIdle()) { + if (whileActive()) { // if we have received valid NMEA claim we are connected setConnected(); } else { if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) { // reset the GPS on next bootup - if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) { + if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); devicestate.did_gps_reset = false; nodeDB->saveDeviceStateToDisk(); @@ -1094,54 +1108,43 @@ int32_t GPS::runOnce() // gps->factoryReset(); } - // If we are overdue for an update, turn on the GPS and at least publish the current status - uint32_t now = millis(); - uint32_t timeAsleep = now - lastSleepStartMsec; + // If we're due for an update, wake the GPS + if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue()) + up(); - auto sleepTime = getSleepTime(); - if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) && - ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) { - // We now want to be awake - so wake up the GPS - setAwake(true); + // If we've already set time from the GPS, no need to ask the GPS + bool gotTime = (getRTCQuality() >= RTCQualityGPS); + if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time + gotTime = true; + shouldPublish = true; } - // While we are awake - if (powerState == GPS_ACTIVE) { - // LOG_DEBUG("looking for location\n"); - // If we've already set time from the GPS, no need to ask the GPS - bool gotTime = (getRTCQuality() >= RTCQualityGPS); - if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time - gotTime = true; - shouldPublish = true; - } + bool gotLoc = lookForLocation(); + if (gotLoc && !hasValidLocation) { // declare that we have location ASAP + LOG_DEBUG("hasValidLocation RISING EDGE\n"); + hasValidLocation = true; + shouldPublish = true; + } - bool gotLoc = lookForLocation(); - if (gotLoc && !hasValidLocation) { // declare that we have location ASAP - LOG_DEBUG("hasValidLocation RISING EDGE\n"); - hasValidLocation = true; - shouldPublish = true; - } + bool tooLong = scheduling.searchedTooLong(); + if (tooLong) + LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time.\n"); - now = millis(); - auto wakeTime = getWakeTime(); - bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime; + // Once we get a location we no longer desperately want an update + // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); + if ((gotLoc && gotTime) || tooLong) { - // Once we get a location we no longer desperately want an update - // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); - if ((gotLoc && gotTime) || tooLong) { - - if (tooLong) { - // we didn't get a location during this ack window, therefore declare loss of lock - if (hasValidLocation) { - LOG_DEBUG("hasValidLocation FALLING EDGE (last read: %d)\n", gotLoc); - } - p = meshtastic_Position_init_default; - hasValidLocation = false; + if (tooLong) { + // we didn't get a location during this ack window, therefore declare loss of lock + if (hasValidLocation) { + LOG_DEBUG("hasValidLocation FALLING EDGE\n"); } - - setAwake(false); - shouldPublish = true; // publish our update for this just finished acquisition window + p = meshtastic_Position_init_default; + hasValidLocation = false; } + + down(); + shouldPublish = true; // publish our update for this just finished acquisition window } // If state has changed do a publish @@ -1167,9 +1170,7 @@ void GPS::clearBuffer() int GPS::prepareDeepSleep(void *unused) { LOG_INFO("GPS deep sleep!\n"); - - setAwake(false); - + disable(); return 0; } @@ -1369,12 +1370,6 @@ GPS *GPS::createGps() new_gps->tx_gpio = _tx_gpio; new_gps->en_gpio = _en_gpio; - if (_en_gpio != 0) { - LOG_DEBUG("Setting %d to output.\n", _en_gpio); - pinMode(_en_gpio, OUTPUT); - digitalWrite(_en_gpio, !GPS_EN_ACTIVE); - } - #ifdef PIN_GPS_PPS // pulse per second pinMode(PIN_GPS_PPS, INPUT); @@ -1389,7 +1384,8 @@ GPS *GPS::createGps() LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n"); #endif - new_gps->setGPSPower(true, false, 0); + // Make sure the GPS is awake before performing any init. + new_gps->up(); #ifdef PIN_GPS_RESET pinMode(PIN_GPS_RESET, OUTPUT); @@ -1397,7 +1393,6 @@ GPS *GPS::createGps() delay(10); digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); #endif - new_gps->setAwake(true); // Wake GPS power before doing any init if (_serial_gps) { #ifdef ARCH_ESP32 @@ -1723,13 +1718,13 @@ bool GPS::hasFlow() return reader.passedChecksum() > 0; } -bool GPS::whileIdle() +bool GPS::whileActive() { unsigned int charsInBuf = 0; bool isValid = false; if (powerState != GPS_ACTIVE) { clearBuffer(); - return (powerState == GPS_ACTIVE); + return false; } #ifdef SERIAL_BUFFER_SIZE if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) { @@ -1760,20 +1755,21 @@ bool GPS::whileIdle() } void GPS::enable() { - // Clear the old lock-time prediction - GPSCycles = 0; - predictedLockTime = 0; + // Clear the old scheduling info (reset the lock-time prediction) + scheduling.reset(); enabled = true; setInterval(GPS_THREAD_INTERVAL); - setAwake(true); + + scheduling.informSearching(); + setPowerState(GPS_ACTIVE); } int32_t GPS::disable() { enabled = false; setInterval(INT32_MAX); - setAwake(false); + setPowerState(GPS_OFF); return INT32_MAX; } @@ -1782,7 +1778,7 @@ void GPS::toggleGpsMode() { if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; - LOG_DEBUG("Flag set to false for gps power. GpsMode: DISABLED\n"); + LOG_INFO("User toggled GpsMode. Now DISABLED.\n"); #ifdef GNSS_Airoha if (powerState != GPS_ACTIVE) { LOG_DEBUG("User power Off GPS\n"); @@ -1792,8 +1788,8 @@ void GPS::toggleGpsMode() disable(); } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; - LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n"); + LOG_INFO("User toggled GpsMode. Now ENABLED\n"); enable(); } } -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 7fa37cb7a..1505b9843 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -39,10 +39,11 @@ typedef enum { } GPS_RESPONSE; enum GPSPowerState : uint8_t { - GPS_OFF = 0, // Physically powered off - GPS_ACTIVE = 1, // Awake and want a position - GPS_STANDBY = 2, // Physically powered on, but soft-sleeping - GPS_IDLE = 3, // Awake, but not wanting another position yet + GPS_ACTIVE, // Awake and want a position + GPS_IDLE, // Awake, but not wanting another position yet + GPS_SOFTSLEEP, // Physically powered on, but soft-sleeping + GPS_HARDSLEEP, // Physically powered off, but scheduled to wake + GPS_OFF // Powered off indefinitely }; // Generate a string representation of DOP @@ -73,8 +74,6 @@ class GPS : private concurrency::OSThread uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; uint32_t en_gpio = 0; - uint32_t predictedLockTime = 0; - uint32_t GPSCycles = 0; int speedSelect = 0; int probeTries = 2; @@ -99,7 +98,6 @@ class GPS : private concurrency::OSThread uint8_t numSatellites = 0; CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); - CallbackObserver notifyGPSSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); public: /** If !NULL we will use this serial port to construct our GPS */ @@ -175,7 +173,8 @@ class GPS : private concurrency::OSThread // toggle between enabled/disabled void toggleGpsMode(); - void setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime); + // Change the power state of the GPS - for power saving / shutdown + void setPowerState(GPSPowerState newState, uint32_t sleepMs = 0); /// Returns true if we have acquired GPS lock. virtual bool hasLock(); @@ -206,18 +205,18 @@ class GPS : private concurrency::OSThread GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis); - /** - * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode - * - * calls sleep/wake - */ - void setAwake(bool on); virtual bool factoryReset(); // Creates an instance of the GPS class. // Returns the new instance or null if the GPS is not present. static GPS *createGps(); + // Wake the GPS hardware - ready for an update + void up(); + + // Let the GPS hardware save power between updates + void down(); + protected: /** * Perform any processing that should be done only while the GPS is awake and looking for a fix. @@ -240,7 +239,7 @@ class GPS : private concurrency::OSThread * * Return true if we received a valid message from the GPS */ - virtual bool whileIdle(); + virtual bool whileActive(); /** * Perform any processing that should be done only while the GPS is awake and looking for a fix. @@ -267,13 +266,21 @@ class GPS : private concurrency::OSThread void UBXChecksum(uint8_t *message, size_t length); void CASChecksum(uint8_t *message, size_t length); - /** Get how long we should stay looking for each aquisition + /** Set power with EN pin, if relevant */ - uint32_t getWakeTime() const; + void writePinEN(bool on); - /** Get how long we should sleep between aqusition attempts + /** Set the value of the STANDBY pin, if relevant */ - uint32_t getSleepTime() const; + void writePinStandby(bool standby); + + /** Set GPS power with PMU, if relevant + */ + void setPowerPMU(bool on); + + /** Set UBLOX power, if relevant + */ + void setPowerUBLOX(bool on, uint32_t sleepMs = 0); /** * Tell users we have new GPS readings @@ -296,4 +303,4 @@ class GPS : private concurrency::OSThread }; extern GPS *gps; -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS diff --git a/src/gps/GPSUpdateScheduling.cpp b/src/gps/GPSUpdateScheduling.cpp new file mode 100644 index 000000000..949ef6039 --- /dev/null +++ b/src/gps/GPSUpdateScheduling.cpp @@ -0,0 +1,118 @@ +#include "GPSUpdateScheduling.h" + +#include "Default.h" + +// Mark the time when searching for GPS position begins +void GPSUpdateScheduling::informSearching() +{ + searchStartedMs = millis(); +} + +// Mark the time when searching for GPS is complete, +// then update the predicted lock-time +void GPSUpdateScheduling::informGotLock() +{ + searchEndedMs = millis(); + LOG_DEBUG("Took %us to get lock\n", (searchEndedMs - searchStartedMs) / 1000); + updateLockTimePrediction(); +} + +// Clear old lock-time prediction data. +// When re-enabling GPS with user button. +void GPSUpdateScheduling::reset() +{ + searchStartedMs = 0; + searchEndedMs = 0; + searchCount = 0; + predictedMsToGetLock = 0; +} + +// How many milliseconds before we should next search for GPS position +// Used by GPS hardware directly, to enter timed hardware sleep +uint32_t GPSUpdateScheduling::msUntilNextSearch() +{ + uint32_t now = millis(); + + // Target interval (seconds), between GPS updates + uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval); + + // Check how long until we should start searching, to hopefully hit our target interval + uint32_t dueAtMs = searchEndedMs + updateInterval; + uint32_t compensatedStart = dueAtMs - predictedMsToGetLock; + int32_t remainingMs = compensatedStart - now; + + // If we should have already started (negative value), start ASAP + if (remainingMs < 0) + remainingMs = 0; + + return (uint32_t)remainingMs; +} + +// How long have we already been searching? +// Used to abort a search in progress, if it runs unnaceptably long +uint32_t GPSUpdateScheduling::elapsedSearchMs() +{ + // If searching + if (searchStartedMs > searchEndedMs) + return millis() - searchStartedMs; + + // If not searching - 0ms. We shouldn't really consume this value + else + return 0; +} + +// Is it now time to begin searching for a GPS position? +bool GPSUpdateScheduling::isUpdateDue() +{ + return (msUntilNextSearch() == 0); +} + +// Have we been searching for a GPS position for too long? +bool GPSUpdateScheduling::searchedTooLong() +{ + uint32_t maxSearchMs = + Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); + + // If broadcast interval set to max, no such thing as "too long" + if (maxSearchMs == UINT32_MAX) + return false; + + // If we've been searching longer than our position broadcast interval: that's too long + else if (elapsedSearchMs() > maxSearchMs) + return true; + + // Otherwise, not too long yet! + else + return false; +} + +// Updates the predicted time-to-get-lock, by exponentially smoothing the latest observation +void GPSUpdateScheduling::updateLockTimePrediction() +{ + + // How long did it take to get GPS lock this time? + // Duration between down() calls + int32_t lockTime = searchEndedMs - searchStartedMs; + if (lockTime < 0) + lockTime = 0; + + // Ignore the first lock-time: likely to be long, will skew data + + // Second locktime: likely stable. Use to intialize the smoothing filter + if (searchCount == 1) + predictedMsToGetLock = lockTime; + + // Third locktime and after: predict using exponential smoothing. Respond slowly to changes + else if (searchCount > 1) + predictedMsToGetLock = (lockTime * weighting) + (predictedMsToGetLock * (1 - weighting)); + + searchCount++; // Only tracked so we can diregard initial lock-times + + LOG_DEBUG("Predicting %us to get next lock\n", predictedMsToGetLock / 1000); +} + +// How long do we expect to spend searching for a lock? +uint32_t GPSUpdateScheduling::predictedSearchDurationMs() +{ + return GPSUpdateScheduling::predictedMsToGetLock; +} \ No newline at end of file diff --git a/src/gps/GPSUpdateScheduling.h b/src/gps/GPSUpdateScheduling.h new file mode 100644 index 000000000..7e121c9b6 --- /dev/null +++ b/src/gps/GPSUpdateScheduling.h @@ -0,0 +1,29 @@ +#pragma once + +#include "configuration.h" + +// Encapsulates code responsible for the timing of GPS updates +class GPSUpdateScheduling +{ + public: + // Marks the time of these events, for calculation use + void informSearching(); + void informGotLock(); // Predicted lock-time is recalculated here + + void reset(); // Reset the prediction - after GPS::disable() / GPS::enable() + bool isUpdateDue(); // Is it time to begin searching for a GPS position? + bool searchedTooLong(); // Have we been searching for too long? + + uint32_t msUntilNextSearch(); // How long until we need to begin searching for a GPS? Info provided to GPS hardware for sleep + uint32_t elapsedSearchMs(); // How long have we been searching so far? + uint32_t predictedSearchDurationMs(); // How long do we expect to spend searching for a lock? + + private: + void updateLockTimePrediction(); // Called from informGotLock + uint32_t searchStartedMs = 0; + uint32_t searchEndedMs = 0; + uint32_t searchCount = 0; + uint32_t predictedMsToGetLock = 0; + + const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time". +}; \ No newline at end of file diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index bbc12521a..d81ab6ff4 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -156,7 +156,8 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ + defined(HELTEC_VISION_MASTER_E290) { // Start HSPI hspi = new SPIClass(HSPI); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index f74416494..26091b2cd 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -5,11 +5,6 @@ #include "GxEPD2_BW.h" #include -#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) -// Re-enable SPI after deep sleep: rtc_gpio_hold_dis() -#include "driver/rtc_io.h" -#endif - /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * @@ -72,7 +67,8 @@ class EInkDisplay : public OLEDDisplay GxEPD2_BW *adafruitDisplay = NULL; // If display uses HSPI -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ + defined(HELTEC_VISION_MASTER_E290) SPIClass *hspi = NULL; #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f724ddd3d..b2059b71c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -41,6 +41,7 @@ along with this program. If not, see . #include "mesh/Channels.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" #include "meshUtils.h" +#include "modules/AdminModule.h" #include "modules/ExternalNotificationModule.h" #include "modules/TextMessageModule.h" #include "sleep.h" @@ -1485,6 +1486,10 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ screen->drawColumns(display, x, y, fields); } +#if defined(ESP_PLATFORM) && defined(USE_ST7789) +SPIClass SPI1(HSPI); +#endif + Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { @@ -1492,6 +1497,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) dispdev = new SH1106Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_ST7789) +#ifdef ESP_PLATFORM + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7789_SDA, + ST7789_MISO, ST7789_SCK); +#else + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); +#endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -1570,7 +1582,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif dispdev->displayOn(); - +#ifdef USE_ST7789 +#ifdef ESP_PLATFORM + analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); +#else + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); +#endif +#endif enabled = true; setInterval(0); // Draw ASAP runASAP = true; @@ -1581,6 +1600,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif LOG_INFO("Turning off screen\n"); dispdev->displayOff(); + +#ifdef USE_ST7789 + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, !TFT_BACKLIGHT_ON); +#endif + #ifdef T_WATCH_S3 PMU->disablePowerOutput(XPOWERS_ALDO2); #endif @@ -1701,6 +1726,7 @@ void Screen::setup() powerStatusObserver.observe(&powerStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus); + adminMessageObserver.observe(adminModule); if (textMessageModule) textMessageObserver.observe(textMessageModule); if (inputBroker) @@ -1929,9 +1955,6 @@ void Screen::setWelcomeFrames() /// Determine which screensaver frame to use, then set the FrameCallback void Screen::setScreensaverFrames(FrameCallback einkScreensaver) { - // Remember current frame, restore position at power-on - uint8_t frameNumber = ui->getUiState()->currentFrame; - // Retain specified frame / overlay callback beyond scope of this method static FrameCallback screensaverFrame; static OverlayCallback screensaverOverlay; @@ -1969,9 +1992,8 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) #endif // Prepare now for next frame, shown when display wakes - ui->setOverlays(NULL, 0); // Clear overlay - setFrames(); // Return to normal display updates - ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on + ui->setOverlays(NULL, 0); // Clear overlay + setFrames(FOCUS_PRESERVE); // Return to normal display updates, showing same frame as before screensaver, ideally // Pick a refresh method, for when display wakes #ifdef EINK_HASQUIRK_GHOSTING @@ -1982,9 +2004,13 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) } #endif -// restore our regular frame list -void Screen::setFrames() +// Regenerate the normal set of frames, focusing a specific frame if requested +// Called when a frame should be added / removed, or custom frames should be cleared +void Screen::setFrames(FrameFocus focus) { + uint8_t originalPosition = ui->getUiState()->currentFrame; + FramesetInfo fsi; // Location of specific frames, for applying focus parameter + LOG_DEBUG("showing standard frames\n"); showingNormalScreen = true; @@ -2018,20 +2044,33 @@ void Screen::setFrames() // is the same offset into the moduleFrames vector // so that we can invoke the module's callback for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { - normalFrames[numframes++] = drawModuleFrame; + // Draw the module frame, using the hack described above + normalFrames[numframes] = drawModuleFrame; + + // Check if the module being drawn has requested focus + // We will honor this request later, if setFrames was triggered by a UIFrameEvent + MeshModule *m = *i; + if (m->isRequestingFocus()) + fsi.positions.focusedModule = numframes; + + numframes++; } LOG_DEBUG("Added modules. numframes: %d\n", numframes); // If we have a critical fault, show it first - if (error_code) + fsi.positions.fault = numframes; + if (error_code) { normalFrames[numframes++] = drawCriticalFaultFrame; + focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame + } #ifdef T_WATCH_S3 normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame; #endif // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules + fsi.positions.textMessage = numframes; if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { normalFrames[numframes++] = drawTextMessageFrame; } @@ -2046,11 +2085,14 @@ void Screen::setFrames() // // Since frames are basic function pointers, we have to use a helper to // call a method on debugInfo object. + fsi.positions.log = numframes; normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; // call a method on debugInfoScreen object (for more details) + fsi.positions.settings = numframes; normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; + fsi.positions.wifi = numframes; #if HAS_WIFI && !defined(ARCH_PORTDUINO) if (isWifiAvailable()) { // call a method on debugInfoScreen object (for more details) @@ -2058,6 +2100,7 @@ void Screen::setFrames() } #endif + fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); ui->setFrames(normalFrames, numframes); @@ -2071,6 +2114,55 @@ void Screen::setFrames() prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list // just changed) + // Focus on a specific frame, in the frame set we just created + switch (focus) { + case FOCUS_DEFAULT: + ui->switchToFrame(0); // First frame + break; + case FOCUS_FAULT: + ui->switchToFrame(fsi.positions.fault); + break; + case FOCUS_TEXTMESSAGE: + ui->switchToFrame(fsi.positions.textMessage); + break; + case FOCUS_MODULE: + // Whichever frame was marked by MeshModule::requestFocus(), if any + // If no module requested focus, will show the first frame instead + ui->switchToFrame(fsi.positions.focusedModule); + break; + + case FOCUS_PRESERVE: + // If we can identify which type of frame "originalPosition" was, can move directly to it in the new frameset + FramesetInfo &oldFsi = this->framesetInfo; + if (originalPosition == oldFsi.positions.log) + ui->switchToFrame(fsi.positions.log); + else if (originalPosition == oldFsi.positions.settings) + ui->switchToFrame(fsi.positions.settings); + else if (originalPosition == oldFsi.positions.wifi) + ui->switchToFrame(fsi.positions.wifi); + + // If frame count has decreased + else if (fsi.frameCount < oldFsi.frameCount) { + uint8_t numDropped = oldFsi.frameCount - fsi.frameCount; + // Move n frames backwards + if (numDropped <= originalPosition) + ui->switchToFrame(originalPosition - numDropped); + // Unless that would put us "out of bounds" (< 0) + else + ui->switchToFrame(0); + } + + // If we're not sure exactly which frame we were on, at least return to the same frame number + // (node frames; module frames) + else + ui->switchToFrame(originalPosition); + + break; + } + + // Store the info about this frameset, for future setFrames calls + this->framesetInfo = fsi; + setFastFramerate(); // Draw ASAP } @@ -2525,7 +2617,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) switch (arg->getStatusType()) { case STATUS_TYPE_NODE: if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { - setFrames(); // Regen the list of screens + setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible) } nodeDB->updateGUI = false; break; @@ -2537,23 +2629,33 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) { if (showingNormalScreen) { - setFrames(); // Regen the list of screens (will show new text message) + // Outgoing message + if (packet->from == 0) + setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame) + + // Incoming message + else + setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message } return 0; } +// Triggered by MeshModules int Screen::handleUIFrameEvent(const UIFrameEvent *event) { if (showingNormalScreen) { - if (event->frameChanged) { - setFrames(); // Regen the list of screens (will show new text message) - } else if (event->needRedraw) { + // Regenerate the frameset, potentially honoring a module's internal requestFocus() call + if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) + setFrames(FOCUS_MODULE); + + // Regenerate the frameset, while attempting to maintain focus on the current frame + else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) + setFrames(FOCUS_PRESERVE); + + // Don't regenerate the frameset, just re-draw whatever is on screen ASAP + else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) setFastFramerate(); - // TODO: We might also want switch to corresponding frame, - // but we don't know the exact frame number. - // ui->switchToFrame(0); - } } return 0; @@ -2588,6 +2690,24 @@ int Screen::handleInputEvent(const InputEvent *event) return 0; } +int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) +{ + // Note: only selected admin messages notify this observer + // If you wish to handle a new type of message, you should modify AdminModule.cpp first + + switch (arg->which_payload_variant) { + // Node removed manually (i.e. via app) + case meshtastic_AdminMessage_remove_by_nodenum_tag: + setFrames(FOCUS_PRESERVE); + break; + + // Default no-op, in case the admin message observable gets used by other classes in future + default: + break; + } + return 0; +} + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 83c9a7a94..93e5f2ef7 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -45,6 +45,8 @@ class Screen #include #elif defined(USE_SSD1306) #include +#elif defined(USE_ST7789) +#include #else // the SH1106/SSD1306 variant is auto-detected #include @@ -171,9 +173,11 @@ class Screen : public concurrency::OSThread CallbackObserver textMessageObserver = CallbackObserver(this, &Screen::handleTextMessage); CallbackObserver uiFrameEventObserver = - CallbackObserver(this, &Screen::handleUIFrameEvent); + CallbackObserver(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules CallbackObserver inputObserver = CallbackObserver(this, &Screen::handleInputEvent); + CallbackObserver adminMessageObserver = + CallbackObserver(this, &Screen::handleAdminMessage); public: explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); @@ -392,6 +396,7 @@ class Screen : public concurrency::OSThread int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); + int handleAdminMessage(const meshtastic_AdminMessage *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(bool forceUiUpdate = false); @@ -448,8 +453,34 @@ class Screen : public concurrency::OSThread void handleShowPrevFrame(); void handlePrint(const char *text); void handleStartFirmwareUpdateScreen(); - /// Rebuilds our list of frames (screens) to default ones. - void setFrames(); + + // Info collected by setFrames method. + // Index location of specific frames. Used to apply the FrameFocus parameter of setFrames + struct FramesetInfo { + struct FramePositions { + uint8_t fault = 0; + uint8_t textMessage = 0; + uint8_t focusedModule = 0; + uint8_t log = 0; + uint8_t settings = 0; + uint8_t wifi = 0; + } positions; + + uint8_t frameCount = 0; + } framesetInfo; + + // Which frame we want to be displayed, after we regen the frameset by calling setFrames + enum FrameFocus : uint8_t { + FOCUS_DEFAULT, // No specific frame + FOCUS_PRESERVE, // Return to the previous frame + FOCUS_FAULT, + FOCUS_TEXTMESSAGE, + FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus + }; + + // Regenerate the normal set of frames, focusing a specific frame if requested + // Call when a frame should be added / removed, or custom frames should be cleared + void setFrames(FrameFocus focus = FOCUS_DEFAULT); /// Try to start drawing ASAP void setFastFramerate(); diff --git a/src/main.cpp b/src/main.cpp index 1e0d998e1..95eeb998d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -275,6 +275,9 @@ void setup() digitalWrite(VEXT_ENABLE_V05, 1); // turn on the lora antenna boost digitalWrite(ST7735_BL_V05, 1); // turn on display backligth LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); +#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) + pinMode(VEXT_ENABLE, OUTPUT); + digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) pinMode(VEXT_ENABLE, OUTPUT); digitalWrite(VEXT_ENABLE, 0); // turn on the display power @@ -713,7 +716,8 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ + defined(USE_ST7789) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 95723744b..cc3927914 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -5,6 +5,7 @@ #define ONE_MINUTE_MS 60 * 1000 #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) +#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60) #define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) #define default_wait_bluetooth_secs IF_ROUTER(1, 60) #define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 04fa250bf..1ef4f60d8 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -284,4 +284,17 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const mesht } } return handled; -} \ No newline at end of file +} + +#if HAS_SCREEN +// Would our module like its frame to be focused after Screen::setFrames has regenerated the list of frames? +// Only considered if setFrames is triggered by a UIFrameEvent +bool MeshModule::isRequestingFocus() +{ + if (_requestingFocus) { + _requestingFocus = false; // Consume the request + return true; + } else + return false; +} +#endif \ No newline at end of file diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 2e2af33e0..c341b301a 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -35,10 +35,16 @@ enum class AdminMessageHandleResult { /* * This struct is used by Screen to figure out whether screen frame should be updated. */ -typedef struct _UIFrameEvent { - bool frameChanged; - bool needRedraw; -} UIFrameEvent; +struct UIFrameEvent { + // What do we actually want to happen? + enum Action { + REDRAW_ONLY, // Don't change which frames are show, just redraw, asap + REGENERATE_FRAMESET, // Regenerate (change? add? remove?) screen frames, honoring requestFocus() + REGENERATE_FRAMESET_BACKGROUND, // Regenerate screen frames, attempting to remain on the same frame throughout + } action = REDRAW_ONLY; + + // We might want to pass additional data inside this struct at some point +}; /** A baseclass for any mesh "module". * @@ -73,6 +79,7 @@ class MeshModule meshtastic_AdminMessage *response); #if HAS_SCREEN virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } + virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset #endif protected: const char *name; @@ -176,6 +183,19 @@ class MeshModule return AdminMessageHandleResult::NOT_HANDLED; }; +#if HAS_SCREEN + /** Request that our module's screen frame be focused when Screen::setFrames runs + * Only considered if Screen::setFrames is triggered via a UIFrameEvent + * + * Having this as a separate call, instead of part of the UIFrameEvent, allows the module to delay decision + * until drawFrame() is called. This required less restructuring. + */ + bool _requestingFocus = false; + void requestFocus() { _requestingFocus = true; } +#else + void requestFocus(){}; // No-op +#endif + private: /** * If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 9e2a5b110..a652d0a50 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -94,7 +94,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && nodeInfoModule) { LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); - nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); + if (airTime->isTxAllowedChannelUtil(true)) { + nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); + } else { + LOG_DEBUG("Skip sending NodeInfo due to > 25 percent channel util.\n"); + } } printPacket("Forwarding to phone", mp); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 84872e471..fa5c437c4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -268,7 +268,8 @@ void NodeDB::installDefaultConfig() // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ + defined(USE_ST7789) bool hasScreen = true; #elif ARCH_PORTDUINO bool hasScreen = false; diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index ba9f90873..f5bacea52 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 674 +#define meshtastic_ChannelSet_size 676 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index e3037c910..44a86f4d6 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -497,6 +497,8 @@ typedef struct _meshtastic_Config_LoRaConfig { Please respect your local laws and regulations. If you are a HAM, make sure you enable HAM mode and turn off encryption. */ float override_frequency; + /* If true, disable the build-in PA FAN using pin define in RF95_FAN_EN. */ + bool pa_fan_disabled; /* For testing it is useful sometimes to force a node to never listen to particular other nodes (simulating radio out of range). All nodenums listed in ignore_incoming will have packets they send dropped on receive (by router.cpp) */ @@ -618,7 +620,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -627,7 +629,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} /* Field tags (for use in manual encoding/decoding) */ @@ -702,6 +704,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_override_duty_cycle_tag 12 #define meshtastic_Config_LoRaConfig_sx126x_rx_boosted_gain_tag 13 #define meshtastic_Config_LoRaConfig_override_frequency_tag 14 +#define meshtastic_Config_LoRaConfig_pa_fan_disabled_tag 15 #define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103 #define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104 #define meshtastic_Config_BluetoothConfig_enabled_tag 1 @@ -832,6 +835,7 @@ X(a, STATIC, SINGULAR, UINT32, channel_num, 11) \ X(a, STATIC, SINGULAR, BOOL, override_duty_cycle, 12) \ X(a, STATIC, SINGULAR, BOOL, sx126x_rx_boosted_gain, 13) \ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \ +X(a, STATIC, SINGULAR, BOOL, pa_fan_disabled, 15) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_LoRaConfig_CALLBACK NULL @@ -871,7 +875,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_BluetoothConfig_size 12 #define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 30 -#define meshtastic_Config_LoRaConfig_size 80 +#define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index fc7bea53a..eb37f4f95 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -307,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3384 +#define meshtastic_OEMStore_size 3388 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index c1d2a4ae3..983f48ad3 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,8 +181,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 553 -#define meshtastic_LocalModuleConfig_size 685 +#define meshtastic_LocalConfig_size 555 +#define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index f3c48ee6d..7fd57fe00 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -60,7 +60,9 @@ typedef enum _meshtastic_ModuleConfig_SerialConfig_Serial_Mode { meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG = 3, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA = 4, /* NMEA messages specifically tailored for CalTopo */ - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO = 5 + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO = 5, + /* Ecowitt WS85 weather station */ + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 = 6 } meshtastic_ModuleConfig_SerialConfig_Serial_Mode; /* TODO: REPLACE */ @@ -273,6 +275,8 @@ typedef struct _meshtastic_ModuleConfig_StoreForwardConfig { uint32_t history_return_max; /* TODO: REPLACE */ uint32_t history_return_window; + /* Set to true to let this node act as a server that stores received messages and resends them upon request. */ + bool is_server; } meshtastic_ModuleConfig_StoreForwardConfig; /* Preferences for the RangeTestModule */ @@ -432,8 +436,8 @@ extern "C" { #define _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Baud)(meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_921600+1)) #define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT -#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO -#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO+1)) +#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 +#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85+1)) #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MAX meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK @@ -474,7 +478,7 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} @@ -490,7 +494,7 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} @@ -560,6 +564,7 @@ extern "C" { #define meshtastic_ModuleConfig_StoreForwardConfig_records_tag 3 #define meshtastic_ModuleConfig_StoreForwardConfig_history_return_max_tag 4 #define meshtastic_ModuleConfig_StoreForwardConfig_history_return_window_tag 5 +#define meshtastic_ModuleConfig_StoreForwardConfig_is_server_tag 6 #define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1 #define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2 #define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3 @@ -743,7 +748,8 @@ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, BOOL, heartbeat, 2) \ X(a, STATIC, SINGULAR, UINT32, records, 3) \ X(a, STATIC, SINGULAR, UINT32, history_return_max, 4) \ -X(a, STATIC, SINGULAR, UINT32, history_return_window, 5) +X(a, STATIC, SINGULAR, UINT32, history_return_window, 5) \ +X(a, STATIC, SINGULAR, BOOL, is_server, 6) #define meshtastic_ModuleConfig_StoreForwardConfig_CALLBACK NULL #define meshtastic_ModuleConfig_StoreForwardConfig_DEFAULT NULL @@ -848,7 +854,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RangeTestConfig_size 10 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 -#define meshtastic_ModuleConfig_StoreForwardConfig_size 22 +#define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 #define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 28d368754..82cd0a55d 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -115,6 +115,10 @@ typedef struct _meshtastic_EnvironmentMetrics { float wind_speed; /* Weight in KG */ float weight; + /* Wind gust in m/s */ + float wind_gust; + /* Wind lull in m/s */ + float wind_lull; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -205,13 +209,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -238,6 +242,8 @@ extern "C" { #define meshtastic_EnvironmentMetrics_wind_direction_tag 13 #define meshtastic_EnvironmentMetrics_wind_speed_tag 14 #define meshtastic_EnvironmentMetrics_weight_tag 15 +#define meshtastic_EnvironmentMetrics_wind_gust_tag 16 +#define meshtastic_EnvironmentMetrics_wind_lull_tag 17 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -289,7 +295,9 @@ X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \ -X(a, STATIC, SINGULAR, FLOAT, weight, 15) +X(a, STATIC, SINGULAR, FLOAT, weight, 15) \ +X(a, STATIC, SINGULAR, FLOAT, wind_gust, 16) \ +X(a, STATIC, SINGULAR, FLOAT, wind_lull, 17) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -357,10 +365,10 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 73 +#define meshtastic_EnvironmentMetrics_size 85 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 -#define meshtastic_Telemetry_size 80 +#define meshtastic_Telemetry_size 92 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index e24c62712..cab63e559 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -200,6 +200,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta case meshtastic_AdminMessage_remove_by_nodenum_tag: { LOG_INFO("Client is receiving a remove_nodenum command.\n"); nodeDB->removeNodeByNum(r->remove_by_nodenum); + this->notifyObservers(r); // Observed by screen break; } case meshtastic_AdminMessage_set_favorite_node_tag: { diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 6ecc88829..a5ffeb7d6 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -7,7 +7,7 @@ /** * Admin module for admin messages */ -class AdminModule : public ProtobufModule +class AdminModule : public ProtobufModule, public Observable { public: /** Constructor diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index be414dce1..84b5a3260 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -148,8 +148,9 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (this->currentMessageIndex == 0) { this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - UIFrameEvent e = {false, true}; - e.frameChanged = true; + requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->notifyObservers(&e); return 0; @@ -166,8 +167,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { - UIFrameEvent e = {false, true}; - e.frameChanged = true; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; #if !defined(T_WATCH_S3) && !defined(RAK14014) @@ -353,6 +354,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } if (validEvent) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs + // Let runOnce to be called immediately. if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { setIntervalFromNow(0); // on fast keypresses, this isn't fast enough. @@ -378,6 +381,11 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha p->decoded.payload.size++; } + // Only receive routing messages when expecting ACK for a canned message + // Prevents the canned message module from regenerating the screen's frameset at unexpected times, + // or raising a UIFrameEvent before another module has the chance + this->waitingForAck = true; + LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); service.sendToMesh( @@ -393,13 +401,13 @@ int32_t CannedMessageModule::runOnce() return INT32_MAX; } // LOG_DEBUG("Check status\n"); - UIFrameEvent e = {false, true}; + UIFrameEvent e; if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE)) { // TODO: might have some feedback of sending state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; temporaryMessage = ""; - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -412,7 +420,7 @@ int32_t CannedMessageModule::runOnce() } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { // Reset module - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -449,7 +457,7 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -463,7 +471,7 @@ int32_t CannedMessageModule::runOnce() } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { this->currentMessageIndex = 0; LOG_DEBUG("First touch (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) { if (this->messagesCount > 0) { @@ -567,7 +575,7 @@ int32_t CannedMessageModule::runOnce() break; } if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the // display back to the default window case 0x08: // backspace @@ -706,8 +714,8 @@ int CannedMessageModule::getPrevIndex() void CannedMessageModule::showTemporaryMessage(const String &message) { temporaryMessage = message; - UIFrameEvent e = {false, true}; - e.frameChanged = true; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen notifyObservers(&e); runState = CANNED_MESSAGE_RUN_STATE_MESSAGE; // run this loop again in 2 seconds, next iteration will clear the display @@ -914,11 +922,13 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st char buffer[50]; if (temporaryMessage.length() != 0) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame LOG_DEBUG("Drawing temporary message: %s", temporaryMessage.c_str()); display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); String displayString; @@ -940,6 +950,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi); } } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending..."); @@ -948,7 +959,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setFont(FONT_SMALL); display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled."); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - + requestFocus(); // Tell Screen::setFrames to move to our module's frame #if defined(T_WATCH_S3) || defined(RAK14014) drawKeyboard(display, state, 0, 0); #else @@ -1030,16 +1041,18 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &mp) { - if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP) { + if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP && waitingForAck) { // look for a request_id if (mp.decoded.request_id != 0) { - UIFrameEvent e = {false, true}; - e.frameChanged = true; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + requestFocus(); // Tell Screen::setFrames that our module's frame should be shown, even if not "first" in the frameset this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED; this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id); meshtastic_Routing decoded = meshtastic_Routing_init_default; pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; + waitingForAck = false; // No longer want routing packets this->notifyObservers(&e); // run the next time 2 seconds later setIntervalFromNow(2000); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 00e8c2bf9..797b9f7cf 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -81,9 +81,8 @@ class CannedMessageModule : public SinglePortModule, public Observabledecoded.portnum) { - case meshtastic_PortNum_TEXT_MESSAGE_APP: case meshtastic_PortNum_ROUTING_APP: - return true; + return waitingForAck; default: return false; } @@ -140,7 +139,8 @@ class CannedMessageModule : public SinglePortModule, public Observable= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 9cc4bf6ea..9fe679b41 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -17,7 +17,8 @@ int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); if (((lastSentToMesh == 0) || - ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && + ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index dcaf00077..9f0dc7b79 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -69,7 +69,8 @@ int32_t EnvironmentTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -124,6 +125,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); + if (ina3221Sensor.hasSensor()) + result = ina3221Sensor.runOnce(); if (veml7700Sensor.hasSensor()) result = veml7700Sensor.runOnce(); if (tsl2591Sensor.hasSensor()) @@ -152,7 +155,8 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); @@ -339,6 +343,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && ina260Sensor.getMetrics(m); hasSensor = true; } + if (ina3221Sensor.hasSensor()) { + valid = valid && ina3221Sensor.getMetrics(m); + hasSensor = true; + } if (veml7700Sensor.hasSensor()) { valid = valid && veml7700Sensor.getMetrics(m); hasSensor = true; @@ -514,6 +522,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (ina3221Sensor.hasSensor()) { + result = ina3221Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } if (veml7700Sensor.hasSensor()) { result = veml7700Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index fb5aee375..6915d67e3 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -24,7 +24,8 @@ int32_t PowerTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, + default_telemetry_broadcast_interval_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -70,7 +71,8 @@ int32_t PowerTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, + default_telemetry_broadcast_interval_secs))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index edd29682e..dec99c551 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -27,22 +27,69 @@ int32_t INA3221Sensor::runOnce() void INA3221Sensor::setup() {} +struct _INA3221Measurement INA3221Sensor::getMeasurement(ina3221_ch_t ch) +{ + struct _INA3221Measurement measurement; + + measurement.voltage = ina3221.getVoltage(ch); + measurement.current = ina3221.getCurrent(ch); + + return measurement; +} + +struct _INA3221Measurements INA3221Sensor::getMeasurements() +{ + struct _INA3221Measurements measurements; + + // INA3221 has 3 channels starting from 0 + for (int i = 0; i < 3; i++) { + measurements.measurements[i] = getMeasurement((ina3221_ch_t)i); + } + + return measurements; +} + bool INA3221Sensor::getMetrics(meshtastic_Telemetry *measurement) { - measurement->variant.environment_metrics.voltage = ina3221.getVoltage(INA3221_CH1); - measurement->variant.environment_metrics.current = ina3221.getCurrent(INA3221_CH1); - measurement->variant.power_metrics.ch1_voltage = ina3221.getVoltage(INA3221_CH1); - measurement->variant.power_metrics.ch1_current = ina3221.getCurrent(INA3221_CH1); - measurement->variant.power_metrics.ch2_voltage = ina3221.getVoltage(INA3221_CH2); - measurement->variant.power_metrics.ch2_current = ina3221.getCurrent(INA3221_CH2); - measurement->variant.power_metrics.ch3_voltage = ina3221.getVoltage(INA3221_CH3); - measurement->variant.power_metrics.ch3_current = ina3221.getCurrent(INA3221_CH3); + switch (measurement->which_variant) { + case meshtastic_Telemetry_environment_metrics_tag: + return getEnvironmentMetrics(measurement); + + case meshtastic_Telemetry_power_metrics_tag: + return getPowerMetrics(measurement); + } + + // unsupported metric + return false; +} + +bool INA3221Sensor::getEnvironmentMetrics(meshtastic_Telemetry *measurement) +{ + struct _INA3221Measurement m = getMeasurement(ENV_CH); + + measurement->variant.environment_metrics.voltage = m.voltage; + measurement->variant.environment_metrics.current = m.current; + + return true; +} + +bool INA3221Sensor::getPowerMetrics(meshtastic_Telemetry *measurement) +{ + struct _INA3221Measurements m = getMeasurements(); + + measurement->variant.power_metrics.ch1_voltage = m.measurements[INA3221_CH1].voltage; + measurement->variant.power_metrics.ch1_current = m.measurements[INA3221_CH1].current; + measurement->variant.power_metrics.ch2_voltage = m.measurements[INA3221_CH2].voltage; + measurement->variant.power_metrics.ch2_current = m.measurements[INA3221_CH2].current; + measurement->variant.power_metrics.ch3_voltage = m.measurements[INA3221_CH3].voltage; + measurement->variant.power_metrics.ch3_current = m.measurements[INA3221_CH3].current; + return true; } uint16_t INA3221Sensor::getBusVoltageMv() { - return lround(ina3221.getVoltage(INA3221_CH1) * 1000); + return lround(ina3221.getVoltage(BAT_CH) * 1000); } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h index 3b8e382ee..d5121aab6 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.h +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h @@ -12,6 +12,21 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor private: INA3221 ina3221 = INA3221(INA3221_ADDR42_SDA); + // channel to report voltage/current for environment metrics + ina3221_ch_t ENV_CH = INA3221_CH1; + + // channel to report battery voltage for device_battery_ina_address + ina3221_ch_t BAT_CH = INA3221_CH1; + + // get a single measurement for a channel + struct _INA3221Measurement getMeasurement(ina3221_ch_t ch); + + // get all measurements for all channels + struct _INA3221Measurements getMeasurements(); + + bool getEnvironmentMetrics(meshtastic_Telemetry *measurement); + bool getPowerMetrics(meshtastic_Telemetry *measurement); + protected: void setup() override; @@ -22,4 +37,14 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor virtual uint16_t getBusVoltageMv() override; }; +struct _INA3221Measurement { + float voltage; + float current; +}; + +struct _INA3221Measurements { + // INA3221 has 3 channels + struct _INA3221Measurement measurements[3]; +}; + #endif \ No newline at end of file diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index d5b7d29ee..e1974db73 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -16,15 +16,31 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp) auto &p = mp.decoded; LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif - UIFrameEvent e = {true, true}; // We only store/display messages destined for us. // Keep a copy of the most recent text message. devicestate.rx_waypoint = mp; devicestate.has_rx_waypoint = true; powerFSM.trigger(EVENT_RECEIVED_MSG); + +#if HAS_SCREEN + + UIFrameEvent e; + + // New or updated waypoint: focus on this frame next time Screen::setFrames runs + if (shouldDraw()) { + requestFocus(); + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + } + + // Deleting an old waypoint: remove the frame quietly, don't change frame position if possible + else + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND; + notifyObservers(&e); +#endif + return ProcessMessage::CONTINUE; // Let others look at this message also if they want } diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 4a7b1c2c6..2e2e4f528 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -190,13 +190,13 @@ int32_t AudioModule::runOnce() firstTime = false; } else { - UIFrameEvent e = {false, true}; + UIFrameEvent e; // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { LOG_INFO("PTT pressed, switching to TX\n"); radio_state = RadioState::tx; - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->notifyObservers(&e); } } else { @@ -209,7 +209,7 @@ int32_t AudioModule::runOnce() } tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; - e.frameChanged = true; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->notifyObservers(&e); } } diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 0bae515df..34d6fb1d0 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -101,7 +101,7 @@ int32_t PaxcounterModule::runOnce() sendInfo(NODENUM_BROADCAST); } return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, - default_broadcast_interval_secs); + default_telemetry_broadcast_interval_secs); } else { return disable(); } diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 5565b6468..fd3f92a9c 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -151,6 +151,14 @@ #define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO #elif defined(HELTEC_CAPSULE_SENSOR_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 +#elif defined(HELTEC_VISION_MASTER_T190) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 +#elif defined(HELTEC_VISION_MASTER_E213) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 +#elif defined(HELTEC_VISION_MASTER_E290) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 +#elif defined(HELTEC_MESH_NODE_T114) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 455219d2b..6138e2aef 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -19,6 +19,7 @@ static BLEBas blebas; // BAS (Battery Service) helper class instance static BLEDfu bledfu; // DFU software update helper service static BLEDfuSecure bledfusecure; // DFU software update helper service +static BLEDfu bledfu; // DFU software update helper service // 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)]; diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index b79f28f13..7334f3a04 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -234,6 +234,18 @@ void cpuDeepSleep(uint32_t msecToWake) // 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); +#endif #endif // Sleepy trackers or sensors can low power "sleep" // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event @@ -274,5 +286,10 @@ void clearBonds() void enterDfuMode() { +// SDK kit does not have native USB like almost all other NRF52 boards +#ifdef NRF_USE_SERIAL_DFU + enterSerialDfu(); +#else enterUf2Dfu(); +#endif } \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index 04963b8a2..ed02ba44a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -37,10 +37,7 @@ Observable preflightSleep; /// Called to tell observers we are now entering sleep and you should prepare. Must return 0 /// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep -/// notifyGPSSleep will be called when config.position.gps_enabled is set to 0 or from buttonthread when GPS_POWER_TOGGLE is -/// enabled. Observable notifySleep, notifyDeepSleep; -Observable notifyGPSSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; @@ -241,6 +238,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) pinMode(PIN_POWER_EN, INPUT); // power off peripherals // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif + #if HAS_GPS // Kill GPS power completely (even if previously we just had it in sleep mode) if (gps) @@ -275,6 +273,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #elif defined(VEXT_ENABLE_V05) digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power digitalWrite(ST7735_BL_V05, 0); // turn off the display power +#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) + digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif diff --git a/src/sleep.h b/src/sleep.h index 8d5b9a94f..f154b8d44 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -41,8 +41,6 @@ extern Observable notifySleep; /// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 extern Observable notifyDeepSleep; -/// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 -extern Observable notifyGPSSleep; void enableModemSleep(); #ifdef ARCH_ESP32 void enableLoraInterrupt(); diff --git a/variants/heltec_capsule_sensor_v3/variant.h b/variants/heltec_capsule_sensor_v3/variant.h index 0d5ab73cf..51c3cb6ad 100644 --- a/variants/heltec_capsule_sensor_v3/variant.h +++ b/variants/heltec_capsule_sensor_v3/variant.h @@ -1,5 +1,5 @@ #define LED_PIN 33 -#define LED_PIN2 34 +#define LED_PIN2 34 #define EXT_PWR_DETECT 35 #define BUTTON_PIN 18 diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini new file mode 100644 index 000000000..99bdf77a7 --- /dev/null +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -0,0 +1,15 @@ +; First prototype nrf52840/sx1262 device +[env:heltec-mesh-node-t114] +extends = nrf52840_base +board = heltec_mesh_node_t114 +debug_tool = jlink + +# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. +build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_node_t114 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node_t114> +lib_deps = + ${nrf52840_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 + https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb \ No newline at end of file diff --git a/variants/heltec_mesh_node_t114/variant.cpp b/variants/heltec_mesh_node_t114/variant.cpp new file mode 100644 index 000000000..cae079b74 --- /dev/null +++ b/variants/heltec_mesh_node_t114/variant.cpp @@ -0,0 +1,44 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + pinMode(PIN_LED3, OUTPUT); + ledOff(PIN_LED3); +} diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h new file mode 100644 index 000000000..b233069c6 --- /dev/null +++ b/variants/heltec_mesh_node_t114/variant.h @@ -0,0 +1,210 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_HELTEC_NRF_ +#define _VARIANT_HELTEC_NRF_ +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define HELTEC_MESH_NODE_T114 + +#define USE_ST7789 + +#define ST7789_NSS 11 +#define ST7789_RS 12 // DC +#define ST7789_SDA 41 // MOSI +#define ST7789_SCK 40 +#define ST7789_RESET 2 +#define ST7789_MISO -1 +#define ST7789_BUSY -1 +#define VTFT_CTRL 3 +#define VTFT_LEDA 15 +// #define ST7789_BL (32+6) +#define TFT_BACKLIGHT_ON LOW +#define ST7789_SPI_HOST SPI1_HOST +// #define ST7789_BACKLIGHT_EN (32+6) +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 135 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +// #define TFT_OFFSET_ROTATION 0 +// #define SCREEN_ROTATE +// #define SCREEN_TRANSITION_FRAMERATE 5 + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (32 + 3) // 13 red (confirmed on 1.0 board) +// Unused(by firmware) LEDs: +#define PIN_LED2 (1 + 1) // 14 blue +#define PIN_LED3 (1 + 11) // 15 green + +#define LED_RED PIN_LED3 +#define LED_BLUE PIN_LED1 +#define LED_GREEN PIN_LED2 + +#define LED_BUILTIN LED_BLUE +#define LED_CONN PIN_GREEN + +#define LED_STATE_ON 0 // State when LED is lit +#define LED_INVERTED 1 + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 10) +// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular +// GPIO + +/* +No longer populated on PCB +*/ +#define PIN_SERIAL2_RX (0 + 9) +#define PIN_SERIAL2_TX (0 + 10) +// #define PIN_SERIAL2_EN (0 + 17) + +/** + Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (26) +#define PIN_WIRE_SCL (27) + +// QSPI Pins +#define PIN_QSPI_SCK (32 + 14) +#define PIN_QSPI_CS (32 + 15) +#define PIN_QSPI_IO0 (32 + 12) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (32 + 13) // MISO if using two bit interface +#define PIN_QSPI_IO2 (0 + 7) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (0 + 5) // HOLD if using two bit interface (i.e. not used) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +/* + * Lora radio + */ + +#define USE_SX1262 +// #define USE_SX1268 +#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead +#define LORA_CS (0 + 24) +#define SX126X_DIO1 (0 + 20) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +// #define SX1262_DIO3 \ +// (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the +// main +// CPU? +#define SX126X_BUSY (0 + 17) +#define SX126X_RESET (0 + 25) +// Not really an E22 but TTGO seems to be trying to clone that +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define PIN_SPI1_MISO \ + ST7789_MISO // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO +#define PIN_SPI1_MOSI ST7789_SDA +#define PIN_SPI1_SCK ST7789_SCK + +/* + * GPS pins + */ + +#define GPS_L76K + +#define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +#define GPS_RESET_MODE LOW +#define PIN_GPS_EN (21) +#define GPS_EN_ACTIVE HIGH +#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (32 + 4) +// Seems to be missing on this new board +// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +// For LORA, spi 0 +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) + +// #define PIN_PWR_EN (0 + 6) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +// #define USE_SEGGER + +// Battery +// The battery sense is hooked to pin A0 (4) +// it is defined in the anlaolgue pin section of this file +// and has 12 bit resolution + +#define ADC_CTRL 6 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 4 +#define ADC_RESOLUTION 14 + +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (4.90F) + +#define HAS_RTC 0 +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h new file mode 100644 index 000000000..01c16c496 --- /dev/null +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -0,0 +1,63 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define HELTEC_VISION_MASTER_E213 true + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini new file mode 100644 index 000000000..77cc65983 --- /dev/null +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -0,0 +1,23 @@ +[env:heltec-vision-master-e213] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -Ivariants/heltec_vision_master_e213 + -DHELTEC_VISION_MASTER_E213 + -DEINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d + lewisxhe/PCF8563_Library@^1.0.1 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h new file mode 100644 index 000000000..169602a0d --- /dev/null +++ b/variants/heltec_vision_master_e213/variant.h @@ -0,0 +1,58 @@ +// #define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_EINK + +/* + * eink display pins + */ +#define PIN_EINK_CS 5 +#define PIN_EINK_BUSY 1 +#define PIN_EINK_DC 2 +#define PIN_EINK_RES 3 +#define PIN_EINK_SCLK 4 +#define PIN_EINK_MOSI 6 + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +#define VEXT_ENABLE 18 // powers the oled display and the lora antenna boost +#define VEXT_ON_VALUE 1 +#define BUTTON_PIN 21 + +#define ADC_CTRL 46 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 7 +#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h new file mode 100644 index 000000000..e5d507846 --- /dev/null +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini new file mode 100644 index 000000000..60ff60036 --- /dev/null +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -0,0 +1,25 @@ +[env:heltec-vision-master-e290] +board_level = extra +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -Ivariants/heltec_vision_master_e290 + -DHELTEC_VISION_MASTER_E290 + -DEINK_DISPLAY_MODEL=GxEPD2_290_BS + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 +; -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk +; -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted +; -D EINK_LIMIT_RATE_BACKGROUND_SEC=1 ; Minimum interval between BACKGROUND updates +; -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated +; -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. +; -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" +; -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight + +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d + lewisxhe/PCF8563_Library@^1.0.1 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h new file mode 100644 index 000000000..a122a7e0f --- /dev/null +++ b/variants/heltec_vision_master_e290/variant.h @@ -0,0 +1,58 @@ +// #define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_EINK + +/* + * eink display pins + */ +#define PIN_EINK_CS 3 +#define PIN_EINK_BUSY 5 +#define PIN_EINK_DC 4 +#define PIN_EINK_RES 5 +#define PIN_EINK_SCLK 2 +#define PIN_EINK_MOSI 1 + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK + +#define VEXT_ENABLE 18 // powers the e-ink display +#define VEXT_ON_VALUE 1 +#define BUTTON_PIN 21 + +#define ADC_CTRL 46 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 7 +#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_MULTIPLIER 4.9 * 1.03 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/heltec_vision_master_t190/pins_arduino.h new file mode 100644 index 000000000..e5d507846 --- /dev/null +++ b/variants/heltec_vision_master_t190/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini new file mode 100644 index 000000000..bbaa0075c --- /dev/null +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -0,0 +1,13 @@ +[env:heltec-vision-master-t190] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -Ivariants/heltec_vision_master_t190 + -DHELTEC_VISION_MASTER_T190 + ; -D PRIVATE_HW +lib_deps = + ${esp32s3_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 + https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb +upload_speed = 921600 \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h new file mode 100644 index 000000000..97500d357 --- /dev/null +++ b/variants/heltec_vision_master_t190/variant.h @@ -0,0 +1,76 @@ +// #define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA 1 +#define I2C_SCL 2 +#define USE_ST7789 + +#define ST7789_NSS 39 +// #define ST7789_CS 39 +#define ST7789_RS 47 // DC +#define ST7789_SDA 48 // MOSI +#define ST7789_SCK 38 +#define ST7789_RESET 40 +#define ST7789_MISO 4 +#define ST7789_BUSY -1 +#define VTFT_CTRL 7 +// #define TFT_BL 3 +#define VTFT_LEDA 17 +#define TFT_BACKLIGHT_ON HIGH +// #define TFT_BL 17 +// #define TFT_BACKLIGHT_ON HIGH +// #define ST7789_BL 3 +#define ST7789_SPI_HOST SPI2_HOST +// #define ST7789_BACKLIGHT_EN 17 +#define SPI_FREQUENCY 10000000 +#define SPI_READ_FREQUENCY 10000000 +#define TFT_HEIGHT 170 +#define TFT_WIDTH 320 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +// #define TFT_OFFSET_ROTATION 0 +// #define SCREEN_ROTATE +// #define SCREEN_TRANSITION_FRAMERATE 5 +#define BRIGHTNESS_DEFAULT 100 // Medium Low Brightnes + +// #define SLEEP_TIME 120 + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +// #define VEXT_ENABLE 7 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + +#define ADC_CTRL 46 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 6 +#define ADC_CHANNEL ADC1_GPIO6_CHANNEL +#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h index 2bb44161a..3e36d98f5 100644 --- a/variants/heltec_wireless_paper/pins_arduino.h +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -7,8 +7,6 @@ static const uint8_t LED_BUILTIN = 18; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN -static const uint8_t KEY_BUILTIN = 0; - static const uint8_t TX = 43; static const uint8_t RX = 44; @@ -56,9 +54,6 @@ static const uint8_t T12 = 12; static const uint8_t T13 = 13; static const uint8_t T14 = 14; -static const uint8_t Vext = 45; -static const uint8_t LED = 18; - static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; static const uint8_t DIO1 = 14; diff --git a/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld b/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld deleted file mode 100644 index 6aaeb4034..000000000 --- a/variants/wio-sdk-wm1110/nrf52840_s140_v7.ld +++ /dev/null @@ -1,38 +0,0 @@ -/* Linker script to configure memory regions. */ - -SEARCH_DIR(.) -GROUP(-lgcc -lc -lnosys) - -MEMORY -{ - FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 - - /* SRAM required by Softdevice depend on - * - Attribute Table Size (Number of Services and Characteristics) - * - Vendor UUID count - * - Max ATT MTU - * - Concurrent connection peripheral + central + secure links - * - Event Len, HVN queue, Write CMD queue - */ - RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 -} - -SECTIONS -{ - . = ALIGN(4); - .svc_data : - { - PROVIDE(__start_svc_data = .); - KEEP(*(.svc_data)) - PROVIDE(__stop_svc_data = .); - } > RAM - - .fs_data : - { - PROVIDE(__start_fs_data = .); - KEEP(*(.fs_data)) - PROVIDE(__stop_fs_data = .); - } > RAM -} INSERT AFTER .data; - -INCLUDE "nrf52_common.ld" diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 4410eff11..766717428 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -6,11 +6,11 @@ board = wio-sdk-wm1110 # Remove adafruit USB serial from the build (it is incompatible with using the ch340 serial chip on this board) build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB -board_level = extra +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -board_build.ldscript = variants/wio-sdk-wm1110/nrf52840_s140_v7.ld +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} @@ -20,7 +20,7 @@ debug_tool = jlink ; No need to reflash if the binary hasn't changed debug_load_mode = modified ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -upload_protocol = jlink +upload_protocol = nrfutil ;upload_protocol = stlink ; we prefer to stop in setup() because we are an 'ardiuno' app debug_init_break = tbreak setup diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h deleted file mode 100644 index 2786a86a4..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_sdm_api SoftDevice Manager API - @{ - - @brief APIs for SoftDevice management. - -*/ - -#ifndef NRF_SDM_H__ -#define NRF_SDM_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_sdm.h" -#include "nrf_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ -#ifdef NRFSOC_DOXYGEN -/// Declared in nrf_mbr.h -#define MBR_SIZE 0 -#warning test -#endif - -/** @brief The major version for the SoftDevice binary distributed with this header file. */ -#define SD_MAJOR_VERSION (7) - -/** @brief The minor version for the SoftDevice binary distributed with this header file. */ -#define SD_MINOR_VERSION (3) - -/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ -#define SD_BUGFIX_VERSION (0) - -/** @brief The SoftDevice variant of this firmware. */ -#define SD_VARIANT_ID 140 - -/** @brief The full version number for the SoftDevice binary this header file was distributed - * with, as a decimal number in the form Mmmmbbb, where: - * - M is major version (one or more digits) - * - mmm is minor version (three digits) - * - bbb is bugfix version (three digits). */ -#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) - -/** @brief SoftDevice Manager SVC Base number. */ -#define SDM_SVC_BASE 0x10 - -/** @brief SoftDevice unique string size in bytes. */ -#define SD_UNIQUE_STR_SIZE 20 - -/** @brief Invalid info field. Returned when an info field does not exist. */ -#define SDM_INFO_FIELD_INVALID (0) - -/** @brief Defines the SoftDevice Information Structure location (address) as an offset from -the start of the SoftDevice (without MBR)*/ -#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) - -/** @brief Defines the absolute SoftDevice Information Structure location (address) when the - * SoftDevice is installed just above the MBR (the usual case). */ -#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) - -/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the - * SoftDevice base address. The size value is of type uint8_t. */ -#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) - -/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. - * The size value is of type uint32_t. */ -#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) - -/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value - * is of type uint16_t. */ -#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) - -/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID - * is of type uint32_t. */ -#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) - -/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in - * the same format as @ref SD_VERSION, stored as an uint32_t. */ -#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) - -/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. - * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. - */ -#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) - -/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value - * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is - * installed just above the MBR (the usual case). */ -#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base - * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above - * the MBR (the usual case). */ -#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) - -/** @brief Defines the amount of flash that is used by the SoftDevice. - * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed - * just above the MBR (the usual case). - */ -#define SD_FLASH_SIZE 0x26000 - -/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual - * case). */ -#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the - * usual case). */ -#define SD_ID_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_VERSION_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges - * @{ */ -#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ -#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ -/**@} */ - -/**@defgroup NRF_FAULT_IDS Fault ID types - * @{ */ -#define NRF_FAULT_ID_SD_ASSERT \ - (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ -#define NRF_FAULT_ID_APP_MEMACC \ - (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ - in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ - register violation the info parameter will contain the sub-region number of \ - PREGION[0], on whose address range the disallowed write access caused the \ - memory access fault. */ -/**@} */ - -/** @} */ - -/** @addtogroup NRF_SDM_ENUMS Enumerations - * @{ */ - -/**@brief nRF SoftDevice Manager API SVC numbers. */ -enum NRF_SD_SVCS { - SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ - SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ - SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ - SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ - SVC_SDM_LAST /**< Placeholder for last SDM SVC */ -}; - -/** @} */ - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ - -/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy - * @{ */ - -#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ -#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ -#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ -#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ -#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ -#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ -#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ -#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ -#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ -#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ -#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ -#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ - -/** @} */ - -/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources - * @{ */ - -#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ -#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ -#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ - -/** @} */ - -/** @} */ - -/** @addtogroup NRF_SDM_TYPES Types - * @{ */ - -/**@brief Type representing LFCLK oscillator source. */ -typedef struct { - uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ - uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second - units (nRF52: 1-32). - @note To avoid excessive clock drift, 0.5 degrees Celsius is the - maximum temperature change allowed in one calibration timer - interval. The interval should be selected to ensure this. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ - uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration - intervals) the RC oscillator shall be calibrated if the temperature - hasn't changed. - 0: Always calibrate even if the temperature hasn't changed. - 1: Only calibrate if the temperature has changed (legacy - nRF51 only). - 2-33: Check the temperature and only calibrate if it has changed, - however calibration will take place every rc_temp_ctiv - intervals in any case. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. - - @note For nRF52, the application must ensure calibration at least once - every 8 seconds to ensure +/-500 ppm clock stability. The - recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is - rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at - least once every 8 seconds and for temperature changes of 0.5 - degrees Celsius every 4 seconds. See the Product Specification - for the nRF52 device being used for more information.*/ - uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing - windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ -} nrf_clock_lf_cfg_t; - -/**@brief Fault Handler type. - * - * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. - * The protocol stack will be in an undefined state when this happens and the only way to recover will be to - * perform a reset, using e.g. CMSIS NVIC_SystemReset(). - * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). - * - * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. - * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may - * continously transmit packets. - * - * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. - * - * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. - * @param[in] pc The program counter of the instruction that triggered the fault. - * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. - * - * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time - * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the - * one that triggered the fault. - */ -typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); - -/** @} */ - -/** @addtogroup NRF_SDM_FUNCTIONS Functions - * @{ */ - -/**@brief Enables the SoftDevice and by extension the protocol stack. - * - * @note Some care must be taken if a low frequency clock source is already running when calling this function: - * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new - * clock source will be started. - * - * @note This function has no effect when returning with an error. - * - * @post If return code is ::NRF_SUCCESS - * - SoC library and protocol stack APIs are made available. - * - A portion of RAM will be unavailable (see relevant SDS documentation). - * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). - * - Interrupts will not arrive from protected peripherals or interrupts. - * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. - * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). - * - Chosen low frequency clock source will be running. - * - * @param p_clock_lf_cfg Low frequency clock source and accuracy. - If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 - In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to - the actual characteristics of your XTAL clock. - * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. - * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has - an illegal priority level. - * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. - */ -SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, - sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); - -/**@brief Disables the SoftDevice and by extension the protocol stack. - * - * Idempotent function to disable the SoftDevice. - * - * @post SoC library and protocol stack APIs are made unavailable. - * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). - * @post All peripherals used by the SoftDevice will be reset to default values. - * @post All of RAM become available. - * @post All interrupts are forwarded to the application. - * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); - -/**@brief Check if the SoftDevice is enabled. - * - * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice - * - * This function is only intended to be called when a bootloader is enabled. - * - * @param[in] address The base address of the interrupt vector table for forwarded interrupts. - - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SDM_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h index 8ad8c769a..8f66b1f8c 100644 --- a/variants/wio-sdk-wm1110/variant.h +++ b/variants/wio-sdk-wm1110/variant.h @@ -107,6 +107,8 @@ extern "C" { #define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37 +#define NRF_USE_SERIAL_DFU + #ifdef __cplusplus } #endif diff --git a/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld b/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld deleted file mode 100644 index 6aaeb4034..000000000 --- a/variants/wio-tracker-wm1110/nrf52840_s140_v7.ld +++ /dev/null @@ -1,38 +0,0 @@ -/* Linker script to configure memory regions. */ - -SEARCH_DIR(.) -GROUP(-lgcc -lc -lnosys) - -MEMORY -{ - FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 - - /* SRAM required by Softdevice depend on - * - Attribute Table Size (Number of Services and Characteristics) - * - Vendor UUID count - * - Max ATT MTU - * - Concurrent connection peripheral + central + secure links - * - Event Len, HVN queue, Write CMD queue - */ - RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 -} - -SECTIONS -{ - . = ALIGN(4); - .svc_data : - { - PROVIDE(__start_svc_data = .); - KEEP(*(.svc_data)) - PROVIDE(__stop_svc_data = .); - } > RAM - - .fs_data : - { - PROVIDE(__start_fs_data = .); - KEEP(*(.fs_data)) - PROVIDE(__stop_fs_data = .); - } > RAM -} INSERT AFTER .data; - -INCLUDE "nrf52_common.ld" diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index 9c04e36f1..5ecc414ad 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -2,10 +2,11 @@ [env:wio-tracker-wm1110] extends = nrf52840_base board = wio-tracker-wm1110 +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -board_build.ldscript = variants/wio-tracker-wm1110/nrf52840_s140_v7.ld +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/wio-tracker-wm1110/softdevice/ble.h b/variants/wio-tracker-wm1110/softdevice/ble.h deleted file mode 100644 index 177b436ad..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble.h +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON BLE SoftDevice Common - @{ - @defgroup ble_api Events, type definitions and API calls - @{ - - @brief Module independent events, type definitions and API calls for the BLE SoftDevice. - - */ - -#ifndef BLE_H__ -#define BLE_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_gattc.h" -#include "ble_gatts.h" -#include "ble_l2cap.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief Common API SVC numbers. - */ -enum BLE_COMMON_SVCS { - SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ - SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ - SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ - SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ - SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ - SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ - SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ - SD_BLE_OPT_SET, /**< Set a BLE option. */ - SD_BLE_OPT_GET, /**< Get a BLE option. */ - SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ - SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ -}; - -/** - * @brief BLE Module Independent Event IDs. - */ -enum BLE_COMMON_EVTS { - BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t - \n Reply with @ref sd_ble_user_mem_reply. */ - BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ -}; - -/**@brief BLE Connection Configuration IDs. - * - * IDs that uniquely identify a connection configuration. - */ -enum BLE_CONN_CFGS { - BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ - BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ - BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ - BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ - BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ -}; - -/**@brief BLE Common Configuration IDs. - * - * IDs that uniquely identify a common configuration. - */ -enum BLE_COMMON_CFGS { - BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ -}; - -/**@brief Common Option IDs. - * IDs that uniquely identify a common option. - */ -enum BLE_COMMON_OPTS { - BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ - BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ - BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ -}; - -/** @} */ - -/** @addtogroup BLE_COMMON_DEFINES Defines - * @{ */ - -/** @brief Required pointer alignment for BLE Events. - */ -#define BLE_EVT_PTR_ALIGNMENT 4 - -/** @brief Leaves the maximum of the two arguments. - */ -#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) - -/** @brief Maximum possible length for BLE Events. - * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a - * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. - */ -#define BLE_EVT_LEN_MAX(ATT_MTU) \ - (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) - -/** @defgroup BLE_USER_MEM_TYPES User Memory Types - * @{ */ -#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ -#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ -/** @} */ - -/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts - * @{ - */ -#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ -#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ -/** @} */ - -/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. - * @{ - */ -#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ - -/** @} */ - -/** @} */ - -/** @addtogroup BLE_COMMON_STRUCTURES Structures - * @{ */ - -/**@brief User Memory Block. */ -typedef struct { - uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ - uint16_t len; /**< Length in bytes of the user memory block. */ -} ble_user_mem_block_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ -} ble_evt_user_mem_request_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ - ble_user_mem_block_t mem_block; /**< User memory block */ -} ble_evt_user_mem_release_t; - -/**@brief Event structure for events not associated with a specific function module. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ - union { - ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ - ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ - } params; /**< Event parameter union. */ -} ble_common_evt_t; - -/**@brief BLE Event header. */ -typedef struct { - uint16_t evt_id; /**< Value from a BLE__EVT series. */ - uint16_t evt_len; /**< Length in octets including this header. */ -} ble_evt_hdr_t; - -/**@brief Common BLE Event type, wrapping the module specific event reports. */ -typedef struct { - ble_evt_hdr_t header; /**< Event header. */ - union { - ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ - ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ - ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ - ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ - ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ - } evt; /**< Event union. */ -} ble_evt_t; - -/** - * @brief Version Information. - */ -typedef struct { - uint8_t version_number; /**< Link Layer Version number. See - https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ - uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) - (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ - uint16_t - subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ -} ble_version_t; - -/** - * @brief Configuration parameters for the PA and LNA. - */ -typedef struct { - uint8_t enable : 1; /**< Enable toggling for this amplifier */ - uint8_t active_high : 1; /**< Set the pin to be active high */ - uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ -} ble_pa_lna_cfg_t; - -/** - * @brief PA & LNA GPIO toggle configuration - * - * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or - * a low noise amplifier. - * - * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided - * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences - * and must be avoided by the application. - */ -typedef struct { - ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ - ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ - - uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ - uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ - uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ -} ble_common_opt_pa_lna_t; - -/** - * @brief Configuration of extended BLE connection events. - * - * When enabled the SoftDevice will dynamically extend the connection event when possible. - * - * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. - * The connection event can be extended if there is time to send another packet pair before the start of the next connection - * interval, and if there are no conflicts with other BLE roles requesting radio time. - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ -} ble_common_opt_conn_evt_ext_t; - -/** - * @brief Enable/disable extended RC calibration. - * - * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice - * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets - * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. - * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When - * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the - * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. - * - * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the - * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ -} ble_common_opt_extended_rc_cal_t; - -/**@brief Option structure for common options. */ -typedef union { - ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ - ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ - ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ -} ble_common_opt_t; - -/**@brief Common BLE Option type, wrapping the module specific options. */ -typedef union { - ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ - ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ - ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ -} ble_opt_t; - -/**@brief BLE connection configuration type, wrapping the module specific configurations, set with - * @ref sd_ble_cfg_set. - * - * @note Connection configurations don't have to be set. - * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, - * the default connection configuration will be automatically added for the remaining connections. - * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in - * place of @ref ble_conn_cfg_t::conn_cfg_tag. - * - * @sa sd_ble_gap_adv_start() - * @sa sd_ble_gap_connect() - * - * @mscs - * @mmsc{@ref BLE_CONN_CFG} - * @endmscs - - */ -typedef struct { - uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the - @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls - to select this configuration when creating a connection. - Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ - union { - ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ - ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ - ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ - ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ - ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ - } params; /**< Connection configuration union. */ -} ble_conn_cfg_t; - -/** - * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. - */ -typedef struct { - uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. - Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is - @ref BLE_UUID_VS_COUNT_MAX. */ -} ble_common_cfg_vs_uuid_t; - -/**@brief Common BLE Configuration type, wrapping the common configurations. */ -typedef union { - ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ -} ble_common_cfg_t; - -/**@brief BLE Configuration type, wrapping the module specific configurations. */ -typedef union { - ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ - ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ - ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ - ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ -} ble_cfg_t; - -/** @} */ - -/** @addtogroup BLE_COMMON_FUNCTIONS Functions - * @{ */ - -/**@brief Enable the BLE stack - * - * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the - * application RAM region (APP_RAM_BASE). On return, this will - * contain the minimum start address of the application RAM region - * required by the SoftDevice for this configuration. - * @warning After this call, the SoftDevice may generate several events. The list of events provided - * below require the application to initiate a SoftDevice API call. The corresponding API call - * is referenced in the event documentation. - * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop - * communicating with the peer device. - * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST - * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST - * - @ref BLE_GAP_EVT_SEC_REQUEST - * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST - * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST - * - @ref BLE_EVT_USER_MEM_REQUEST - * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located - * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between - * APP_RAM_BASE and the start of the call stack. - * - * @details This call initializes the BLE stack, no BLE related function other than @ref - * sd_ble_cfg_set can be called before this one. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: - * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not - * large enough to fit this configuration's memory requirement. Check *p_app_ram_base - * and set the start address of the application RAM region accordingly. - * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which - * is currently not supported. - * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. - */ -SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); - -/**@brief Add configurations for the BLE stack - * - * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref - * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. - * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. - * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). - * See @ref sd_ble_enable for details about APP_RAM_BASE. - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note If a configuration is set more than once, the last one set is the one that takes effect on - * @ref sd_ble_enable. - * - * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default - * configuration. - * - * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref - * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref - * sd_ble_enable). - * - * @note Error codes for the configurations are described in the configuration structs. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The configuration has been added successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. - * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not - * large enough to fit this configuration's memory requirement. - */ -SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); - -/**@brief Get an event from the pending events queue. - * - * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. - * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. - * The buffer should be interpreted as a @ref ble_evt_t struct. - * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. - * - * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that - * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. - * The application is free to choose whether to call this function from thread mode (main context) or directly from the - * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher - * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) - * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so - * could potentially leave events in the internal queue without the application being aware of this fact. - * - * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to - * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, - * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. - * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length - * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: - * - * \code - * uint16_t len; - * errcode = sd_ble_evt_get(NULL, &len); - * \endcode - * - * @mscs - * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} - * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. - * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. - */ -SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); - -/**@brief Add a Vendor Specific base UUID. - * - * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later - * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t - * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code - * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses - * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to - * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field - * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to - * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, - * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. - * - * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by - * the 16-bit uuid field in @ref ble_uuid_t. - * - * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in - * p_uuid_type along with an @ref NRF_SUCCESS error code. - * - * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding - * bytes 12 and 13. - * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be - * stored. - * - * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. - * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. - */ -SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); - -/**@brief Remove a Vendor Specific base UUID. - * - * @details This call removes a Vendor Specific base UUID. This function allows - * the application to reuse memory allocated for Vendor Specific base UUIDs. - * - * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID - * type. - * - * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. - * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific - * base UUID will be removed. If the function returns successfully, the UUID type that was removed will - * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that - * is in use by the ATT Server. - * - * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. - * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. - */ -SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); - -/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. - * - * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared - * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs - * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index - * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. - * - * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. - * - * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). - * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. - * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. - * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. - */ -SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); - -/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). - * - * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is - * computed. - * - * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. - * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). - * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. - * - * @retval ::NRF_SUCCESS Successfully encoded into the buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. - */ -SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); - -/**@brief Get Version Information. - * - * @details This call allows the application to get the BLE stack version information. - * - * @param[out] p_version Pointer to a ble_version_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Version information stored successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). - */ -SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); - -/**@brief Provide a user memory block. - * - * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. - */ -SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); - -/**@brief Set a BLE option. - * - * @details This call allows the application to set the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. - * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. - * - * @retval ::NRF_SUCCESS Option set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - */ -SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); - -/**@brief Get a BLE option. - * - * @details This call allows the application to retrieve the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. - * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Option retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. - * - */ -SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); - -/** @} */ -#ifdef __cplusplus -} -#endif -#endif /* BLE_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_err.h b/variants/wio-tracker-wm1110/softdevice/ble_err.h deleted file mode 100644 index d20f6d141..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_err.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @addtogroup nrf_error - @{ - @ingroup BLE_COMMON - @} - - @defgroup ble_err General error codes - @{ - - @brief General error code definitions for the BLE API. - - @ingroup BLE_COMMON -*/ -#ifndef NRF_BLE_ERR_H__ -#define NRF_BLE_ERR_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* @defgroup BLE_ERRORS Error Codes - * @{ */ -#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ -#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ -#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ -#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ -#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ -#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ - (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ -/** @} */ - -/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges - * @brief Assignment of subranges for module specific error codes. - * @note For specific error codes, see ble_.h or ble_error_.h. - * @{ */ -#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ -#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ -#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ -#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gap.h b/variants/wio-tracker-wm1110/softdevice/ble_gap.h deleted file mode 100644 index 8ebdfa82b..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gap.h +++ /dev/null @@ -1,2895 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GAP Generic Access Profile (GAP) - @{ - @brief Definitions and prototypes for the GAP interface. - */ - -#ifndef BLE_GAP_H__ -#define BLE_GAP_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GAP API SVC numbers. - */ -enum BLE_GAP_SVCS { - SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ - SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ - SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ - SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ - SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ - SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ - SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ - SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ - SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ - SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ - SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ - SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ - SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ - SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ - SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ - SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ - SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ - SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ - SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ - SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ - SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ - SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ - SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ - SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ - SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ - SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ - SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ - SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ - SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ - SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ - SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ - SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ - SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ - SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ - SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ - SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ - SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ - SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = - BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ -}; - -/**@brief GAP Event IDs. - * IDs that uniquely identify an event coming from the stack to the application. - */ -enum BLE_GAP_EVTS { - BLE_GAP_EVT_CONNECTED = - BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ - BLE_GAP_EVT_DISCONNECTED = - BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE = - BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ - BLE_GAP_EVT_SEC_PARAMS_REQUEST = - BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. - \n See @ref ble_gap_evt_sec_params_request_t. */ - BLE_GAP_EVT_SEC_INFO_REQUEST = - BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. - \n See @ref ble_gap_evt_sec_info_request_t. */ - BLE_GAP_EVT_PASSKEY_DISPLAY = - BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref - sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ - BLE_GAP_EVT_KEY_PRESSED = - BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ - BLE_GAP_EVT_AUTH_KEY_REQUEST = - BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. - \n See @ref ble_gap_evt_auth_key_request_t. */ - BLE_GAP_EVT_LESC_DHKEY_REQUEST = - BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref - sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ - BLE_GAP_EVT_AUTH_STATUS = - BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ - BLE_GAP_EVT_CONN_SEC_UPDATE = - BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ - BLE_GAP_EVT_TIMEOUT = - BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ - BLE_GAP_EVT_RSSI_CHANGED = - BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ - BLE_GAP_EVT_ADV_REPORT = - BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ - BLE_GAP_EVT_SEC_REQUEST = - BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate -\n or with @ref sd_ble_gap_encrypt if required security information is available -. \n See @ref ble_gap_evt_sec_request_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref - sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ - BLE_GAP_EVT_SCAN_REQ_REPORT = - BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ - BLE_GAP_EVT_PHY_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n - See @ref ble_gap_evt_phy_update_request_t. */ - BLE_GAP_EVT_PHY_UPDATE = - BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref - sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE = - BLE_GAP_EVT_BASE + - 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ - BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = - BLE_GAP_EVT_BASE + - 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ - BLE_GAP_EVT_ADV_SET_TERMINATED = - BLE_GAP_EVT_BASE + - 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ -}; - -/**@brief GAP Option IDs. - * IDs that uniquely identify a GAP option. - */ -enum BLE_GAP_OPTS { - BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ - BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ - BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ - BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ - BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = - BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ - BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = - BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ -}; - -/**@brief GAP Configuration IDs. - * - * IDs that uniquely identify a GAP configuration. - */ -enum BLE_GAP_CFGS { - BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ - BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ - BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic - inclusion configuration. */ - BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic - inclusion configuration. */ -}; - -/**@brief GAP TX Power roles. - */ -enum BLE_GAP_TX_POWER_ROLES { - BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ - BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ - BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ -}; - -/** @} */ - -/**@addtogroup BLE_GAP_DEFINES Defines - * @{ */ - -/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP - * @{ */ -#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ - (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ -#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ - (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ -#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ - (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ -#define BLE_ERROR_GAP_WHITELIST_IN_USE \ - (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ - (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ - (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLES GAP Roles - * @{ */ -#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ -#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ -#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ -/**@} */ - -/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources - * @{ */ -#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ -#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ -#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ -/**@} */ - -/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types - * @{ */ -#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ -#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ -#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ - 0x7F /**< An advertiser may advertise without its address. \ - This type of advertising is called anonymous. */ -/**@} */ - -/**@brief The default interval in seconds at which a private address is refreshed. */ -#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ -/**@brief The maximum interval in seconds at which a private address can be refreshed. */ -#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ - -/** @brief BLE address length. */ -#define BLE_GAP_ADDR_LEN (6) - -/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes - * @{ */ -#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ -#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ -#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ - 0x02 /**< Device will send and accept only private addresses for its own address, \ - and will not accept a peer using identity address as sender address when \ - the peer IRK is exchanged, non-zero and added to the identity list. */ -/**@} */ - -/** @brief Invalid power level. */ -#define BLE_GAP_POWER_LEVEL_INVALID 127 - -/** @brief Advertising set handle not set. */ -#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) - -/** @brief The default number of advertising sets. */ -#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) - -/** @brief The maximum number of advertising sets supported by this SoftDevice. */ -#define BLE_GAP_ADV_SET_COUNT_MAX (1) - -/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. - * @{ */ -#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ - (31) /**< Maximum data length for an advertising set. \ - If more advertising data is required, use extended advertising instead. */ -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for an extended advertising set. */ - -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ - (238) /**< Maximum supported data length for an extended connectable advertising set. */ -/**@}. */ - -/** @brief Set ID not available in advertising report. */ -#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF - -/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons - * @{ */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ -/**@} */ - -/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format - * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm - * @{ */ -#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ -#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ -#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ -#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ -#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ -#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ -#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ -#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ -#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ -#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ -#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ -#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ -#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ -#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ -#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ -#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags - * @{ */ -#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min - * @{ */ -#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ -#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ - /**@} */ - -/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min - * @{ */ -#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min - * @{ */ -#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min - * @{ */ -#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ -#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size - * - * Scan buffers are used for storing advertising data received from an advertiser. - * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. - * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. - * @{ */ -#define BLE_GAP_SCAN_BUFFER_MIN \ - (31) /**< Minimum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_MAX \ - (31) /**< Maximum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ - (255) /**< Minimum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ - (1650) /**< Maximum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for \ - an extended advertising set. */ -/** @} */ - -/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types - * - * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. - * - * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. - * The maximum supported data length for an extended advertiser is defined by - * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED - * Note that some of the advertising types do not support advertising data. Non-scannable types do not support - * scan response data. - * - * @{ */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x01 /**< Connectable and scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ - 0x02 /**< Connectable non-scannable directed advertising \ - events. Advertising interval is less that 3.75 ms. \ - Use this type for fast reconnections. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x03 /**< Connectable non-scannable directed advertising \ - events. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x04 /**< Non-connectable scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x05 /**< Non-connectable non-scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x06 /**< Connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x07 /**< Connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x08 /**< Non-connectable scannable undirected advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ - 0x09 /**< Non-connectable scannable directed advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x0A /**< Non-connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x0B /**< Non-connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies - * @{ */ -#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ -#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status - * @{ */ -#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ - 0x01 /**< More data to be received. \ - @note This value will only be used if \ - @ref ble_gap_scan_params_t::report_incomplete_evts and \ - @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ - 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ - 0x03 /**< Failed to receive the remaining data. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -/**@} */ - -/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies - * @{ */ -#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ - 0x00 /**< Accept all advertising packets except directed advertising packets \ - not addressed to this device. */ -#define BLE_GAP_SCAN_FP_WHITELIST \ - 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ - packets not addressed to this device. */ -#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ - 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ - 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units - * @{ */ -#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ - (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ - */ -#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ - (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ - mode. */ -#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ - (0) /**< Unlimited advertising in general discoverable mode. \ - For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ -/**@} */ - -/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes - * @{ */ -#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ -#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ -#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ -/**@} */ - -/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities - * @{ */ -#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ -#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ -#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ -/**@} */ - -/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types - * @{ */ -#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ -#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ -#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ -/**@} */ - -/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types - * @{ */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS GAP Security status - * @{ */ -#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ -#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ -#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ -#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ -#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ -#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ -#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ -#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ -#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ -#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ -#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ -#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ -#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ -#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ -#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ -#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ -#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources - * @{ */ -#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ -#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ -/**@} */ - -/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits - * @{ */ -#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ - 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ - 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ - 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ - 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ -/**@} */ - -/**@defgroup BLE_GAP_DEVNAME GAP device name defines. - * @{ */ -#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ -#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ -#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ -/**@} */ - -/**@brief Disable RSSI events for connections */ -#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF - -/**@defgroup BLE_GAP_PHYS GAP PHYs - * @{ */ -#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ -#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ -#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ -#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ -#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ - -/**@brief Supported PHYs in connections, for scanning, and for advertising. */ -#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ - -/**@} */ - -/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters - * - * See @ref ble_gap_conn_sec_mode_t. - * @{ */ -/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ - do { \ - (ptr)->sm = 0; \ - (ptr)->lv = 0; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 2; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 3; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 4; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 2; \ - } while (0) -/**@} */ - -/**@brief GAP Security Random Number Length. */ -#define BLE_GAP_SEC_RAND_LEN 8 - -/**@brief GAP Security Key Length. */ -#define BLE_GAP_SEC_KEY_LEN 16 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ -#define BLE_GAP_LESC_P256_PK_LEN 64 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ -#define BLE_GAP_LESC_DHKEY_LEN 32 - -/**@brief GAP Passkey Length. */ -#define BLE_GAP_PASSKEY_LEN 6 - -/**@brief Maximum amount of addresses in the whitelist. */ -#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) - -/**@brief Maximum amount of identities in the device identities list. */ -#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) - -/**@brief Default connection count for a configuration. */ -#define BLE_GAP_CONN_COUNT_DEFAULT (1) - -/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. - * @{ */ -#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ -#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ -#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. - * @{ */ -#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ - (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ - (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ - -/**@} */ - -/**@brief Automatic data length parameter. */ -#define BLE_GAP_DATA_LENGTH_AUTO 0 - -/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. - * @{ */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ -/**@} */ - -/**@defgroup GAP_SEC_MODES GAP Security Modes - * @{ */ -#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ -/**@} */ - -/**@brief The total number of channels in Bluetooth Low Energy. */ -#define BLE_GAP_CHANNEL_COUNT (40) - -/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines - * @{ */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ - /**@} */ - -/** @} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations - * @{ - */ -#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ - (1) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will reserve the attribute handles \ - which are otherwise used for this characteristic. \ - By reserving the attribute handles it will be possible \ - to upgrade the SoftDevice without changing handle of the \ - Service Changed characteristic. */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ - (2) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will not reserve the attribute handles \ - which are otherwise used for this characteristic. */ -/**@} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values - * @{ */ -#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -/**@} */ - -/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options - * @{ */ -#define BLE_GAP_SLAVE_LATENCY_ENABLE \ - (0) /**< Slave latency is enabled. When slave latency is enabled, \ - the slave will wake up every time it has data to send, \ - and/or every slave latency number of connection events. */ -#define BLE_GAP_SLAVE_LATENCY_DISABLE \ - (1) /**< Disable slave latency. The slave will wake up every connection event \ - regardless of the requested slave latency. \ - This option consumes the most power. */ -#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ - (2) /**< The slave will wake up every connection event if it has not received \ - an ACK from the master for at least slave latency events. This \ - configuration may increase the power consumption in environments \ - with a lot of radio activity. */ -/**@} */ - -/**@addtogroup BLE_GAP_STRUCTURES Structures - * @{ */ - -/**@brief Advertising event properties. */ -typedef struct { - uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ - uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. - @note Anonymous advertising is only available for - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ - uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ -} ble_gap_adv_properties_t; - -/**@brief Advertising report type. */ -typedef struct { - uint16_t connectable : 1; /**< Connectable advertising event type. */ - uint16_t scannable : 1; /**< Scannable advertising event type. */ - uint16_t directed : 1; /**< Directed advertising event type. */ - uint16_t scan_response : 1; /**< Received a scan response. */ - uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ - uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ - uint16_t reserved : 9; /**< Reserved for future use. */ -} ble_gap_adv_report_type_t; - -/**@brief Advertising Auxiliary Pointer. */ -typedef struct { - uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ - uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ -} ble_gap_aux_pointer_t; - -/**@brief Bluetooth Low Energy address. */ -typedef struct { - uint8_t - addr_id_peer : 1; /**< Only valid for peer addresses. - This bit is set by the SoftDevice to indicate whether the address has been resolved from - a Resolvable Private Address (when the peer is using privacy). - If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. - - This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. - */ - uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ - uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. - @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ -} ble_gap_addr_t; - -/**@brief GAP connection parameters. - * - * @note When ble_conn_params_t is received in an event, both min_conn_interval and - * max_conn_interval will be equal to the connection interval set by the central. - * - * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: - * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval - * that corresponds to the following Bluetooth Spec requirement: - * The Supervision_Timeout in milliseconds shall be larger than - * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. - */ -typedef struct { - uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ -} ble_gap_conn_params_t; - -/**@brief GAP connection security modes. - * - * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n - * Security Mode 1 Level 1: No security is needed (aka open link).\n - * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n - * Security Mode 1 Level 3: MITM protected encrypted link required.\n - * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n - * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n - * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n - */ -typedef struct { - uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ - uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ - -} ble_gap_conn_sec_mode_t; - -/**@brief GAP connection security status.*/ -typedef struct { - ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ - uint8_t - encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ -} ble_gap_conn_sec_t; - -/**@brief Identity Resolving Key. */ -typedef struct { - uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ -} ble_gap_irk_t; - -/**@brief Channel mask (40 bits). - * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, - * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents - * channel index 39. If a bit is set to 1, the channel is not used. - */ -typedef uint8_t ble_gap_ch_mask_t[5]; - -/**@brief GAP advertising parameters. */ -typedef struct { - ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ - ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. - @note ble_gap_addr_t::addr_type cannot be - @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. - - When privacy is enabled and the local device uses - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, - the device identity list is searched for a matching entry. If - the local IRK for that device identity is set, the local IRK - for that device will be used to generate the advertiser address - field in the advertising packet. - - If @ref ble_gap_adv_properties_t::type is directed, this must be - set to the targeted scanner or initiator. If the peer address is - in the device identity list, the peer IRK for that device will be - used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE - target addresses used in the advertising event PDUs. */ - uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE - advertising, this parameter is ignored. */ - uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, - an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - @sa BLE_GAP_ADV_TIMEOUT_VALUES. - @note The SoftDevice will always complete at least one advertising - event even if the duration is set too low. */ - uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling - advertising. Setting the value to 0 disables the limitation. When - the count of advertising events specified by this parameter - (if not 0) is reached, advertising will be automatically stopped - and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, - this parameter is ignored. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be used. - Masking away secondary advertising channels is not supported. */ - uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets - are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS - will be used. - Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. - @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets - are transmitted. - If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. - Valid values are - @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. - If @ref ble_gap_adv_properties_t::type is an extended advertising type - and connectable, this is the PHY that will be used to establish a - connection and send AUX_ADV_IND packets on. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other - advertising sets transmitted by this and other devices. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a - scan request is received and the scanner address is allowed - by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is a non-scannable - advertising type. */ -} ble_gap_adv_params_t; - -/**@brief GAP advertising data buffers. - * - * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and - * shall never be modified while advertising. The data shall be kept alive until either: - * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding - * advertising handle. - * - Advertising is stopped. - * - Advertising data is changed. - * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ -typedef struct { - ble_data_t adv_data; /**< Advertising data. - @note - Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type - that is allowed to contain advertising data. */ - ble_data_t scan_rsp_data; /**< Scan response data. - @note - Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type - that is scannable. */ -} ble_gap_adv_data_t; - -/**@brief GAP scanning parameters. */ -typedef struct { - uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. - If set to 0, the scanner will not receive advertising packets - on secondary advertising channels, and will not be able - to receive long advertising PDUs. */ - uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have - @ref ble_gap_adv_report_type_t::status set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. - This parameter is ignored when used with @ref sd_ble_gap_connect - @note This may be used to abort receiving more packets from an extended - advertising event, and is only available for extended - scanning, see @ref sd_ble_gap_scan_start. - @note This feature is not supported by this SoftDevice. */ - uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. - This parameter is ignored when used with @ref sd_ble_gap_connect. */ - uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. - @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and - @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with - @ref sd_ble_gap_connect */ - uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, - scan_phys will default to @ref BLE_GAP_PHY_1MBPS. - - If @ref ble_gap_scan_params_t::extended is set to 0, the only - supported PHY is @ref BLE_GAP_PHY_1MBPS. - - When used with @ref sd_ble_gap_scan_start, - the bitfield indicates the PHYs the scanner will use for scanning - on primary advertising channels. The scanner will accept - @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. - - When used with @ref sd_ble_gap_connect, the bitfield indicates - the PHYs the initiator will use for scanning on primary advertising - channels. The initiator will accept connections initiated on either - of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. - If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, - the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. - If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan - PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is - @ref BLE_GAP_PHY_CODED, the primary scan PHY is - @ref BLE_GAP_PHY_CODED only. */ - uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ - uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. - If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and - @ref BLE_GAP_PHY_CODED interval shall be larger than or - equal to twice the scan window. */ - uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be - set to 0. - Masking away secondary channels is not supported. */ -} ble_gap_scan_params_t; - -/**@brief Privacy. - * - * The privacy feature provides a way for the device to avoid being tracked over a period of time. - * The privacy feature, when enabled, hides the local device identity and replaces it with a private address - * that is automatically refreshed at a specified interval. - * - * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). - * With this key, a device can generate a random private address that can only be recognized by peers in possession of that - * key, and devices can establish connections without revealing their real identities. - * - * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref - * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. - * - * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all - * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. - * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is - * called. - */ -typedef struct { - uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ - uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ - uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use - the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ - ble_gap_irk_t - *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device - default IRK will be used. When used as output, pointer to IRK structure where the current default IRK - will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate - random private resolvable addresses for the local device unless instructed otherwise. */ -} ble_gap_privacy_params_t; - -/**@brief PHY preferences for TX and RX - * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each - * direction. - * @code - * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * @endcode - * - */ -typedef struct { - uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ -} ble_gap_phys_t; - -/** @brief Keys that can be exchanged during a bonding procedure. */ -typedef struct { - uint8_t enc : 1; /**< Long Term Key and Master Identification. */ - uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ - uint8_t sign : 1; /**< Connection Signature Resolving Key. */ - uint8_t link : 1; /**< Derive the Link Key from the LTK. */ -} ble_gap_sec_kdist_t; - -/**@brief GAP security parameters. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ - uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ - uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ - uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ - uint8_t oob : 1; /**< The OOB data flag. - - In LE legacy pairing, this flag is set if a device has out of band authentication data. - The OOB method is used if both of the devices have out of band authentication data. - - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band - authentication data. The OOB method is used if at least one device has the peer device's OOB data - available. */ - uint8_t - min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ - uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ - ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ - ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ -} ble_gap_sec_params_t; - -/**@brief GAP Encryption Information. */ -typedef struct { - uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ - uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ - uint8_t auth : 1; /**< Authenticated Key. */ - uint8_t ltk_len : 6; /**< LTK length in octets. */ -} ble_gap_enc_info_t; - -/**@brief GAP Master Identification. */ -typedef struct { - uint16_t ediv; /**< Encrypted Diversifier. */ - uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ -} ble_gap_master_id_t; - -/**@brief GAP Signing Information. */ -typedef struct { - uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ -} ble_gap_sign_info_t; - -/**@brief GAP LE Secure Connections P-256 Public Key. */ -typedef struct { - uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the - standard SMP protocol format: {X,Y} both in little-endian. */ -} ble_gap_lesc_p256_pk_t; - -/**@brief GAP LE Secure Connections DHKey. */ -typedef struct { - uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ -} ble_gap_lesc_dhkey_t; - -/**@brief GAP LE Secure Connections OOB data. */ -typedef struct { - ble_gap_addr_t addr; /**< Bluetooth address of the device. */ - uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ - uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ -} ble_gap_lesc_oob_data_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ -typedef struct { - ble_gap_addr_t - peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ - uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ -} ble_gap_evt_connected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ -typedef struct { - uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ -} ble_gap_evt_disconnected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ -typedef struct { - ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ -} ble_gap_evt_phy_update_request_t; - -/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ -typedef struct { - uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ - uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ -} ble_gap_evt_phy_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ -typedef struct { - ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ -} ble_gap_evt_sec_params_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ -typedef struct { - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ - ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ - uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ - uint8_t id_info : 1; /**< If 1, Identity Information required. */ - uint8_t sign_info : 1; /**< If 1, Signing Information required. */ -} ble_gap_evt_sec_info_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ -typedef struct { - uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ - uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply - with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or - @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ -} ble_gap_evt_passkey_display_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ -typedef struct { - uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ -} ble_gap_evt_key_pressed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ -typedef struct { - uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ -} ble_gap_evt_auth_key_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ -typedef struct { - ble_gap_lesc_p256_pk_t - *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory - inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ - uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the - procedure. */ -} ble_gap_evt_lesc_dhkey_request_t; - -/**@brief Security levels supported. - * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. - */ -typedef struct { - uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ - uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ - uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ - uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ -} ble_gap_sec_levels_t; - -/**@brief Encryption Key. */ -typedef struct { - ble_gap_enc_info_t enc_info; /**< Encryption Information. */ - ble_gap_master_id_t master_id; /**< Master Identification. */ -} ble_gap_enc_key_t; - -/**@brief Identity Key. */ -typedef struct { - ble_gap_irk_t id_info; /**< Identity Resolving Key. */ - ble_gap_addr_t id_addr_info; /**< Identity Address. */ -} ble_gap_id_key_t; - -/**@brief Security Keys. */ -typedef struct { - ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ - ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ - ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ - ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the - value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ -} ble_gap_sec_keys_t; - -/**@brief Security key set for both local and peer keys. */ -typedef struct { - ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be - generated locally and will always be stored if bonding. */ - ble_gap_sec_keys_t - keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ -} ble_gap_sec_keyset_t; - -/**@brief Data Length Update Procedure parameters. */ -typedef struct { - uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ - uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ -} ble_gap_data_length_params_t; - -/**@brief Data Length Update Procedure local limitation. */ -typedef struct { - uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ - uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ - uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many - microseconds. */ -} ble_gap_data_length_limitation_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ -typedef struct { - uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ - uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ - uint8_t bonded : 1; /**< Procedure resulted in a bond. */ - uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ - ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ - ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ - ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding - with LE Secure Connections, the enc bit will be always set. */ - ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding - with LE Secure Connections, the enc bit will never be set. */ -} ble_gap_evt_auth_status_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ -typedef struct { - ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ -} ble_gap_evt_conn_sec_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ - union { - ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released - scan buffer is contained in this field. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ -typedef struct { - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ -} ble_gap_evt_rssi_changed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ -typedef struct { - uint8_t reason; /**< Reason for why the advertising set terminated. See - @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ - uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, - this field indicates the number of completed advertising events. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. */ -} ble_gap_evt_adv_set_terminated_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. - * - * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * not all fields in the advertising report may be available. - * - * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. - */ -typedef struct { - ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the - peer's identity address. */ - ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if - @ref ble_gap_adv_report_type_t::directed is set to 1. If the - SoftDevice was able to resolve the address, - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr - contains the local identity address. If the target address of the - advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, - and the SoftDevice was unable to resolve it, the application may try - to resolve this address to find out if the advertising event was - directed to us. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. - See @ref BLE_GAP_PHYS. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. - See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets - were received on a secondary advertising channel. */ - int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. - This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the - last received packet did not contain the Tx Power field. - @note TX Power is only included in extended advertising packets. */ - int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ - uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present - if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID - is not present if @ref ble_gap_evt_adv_report_t::set_id is set to - @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - ble_data_t data; /**< Received advertising or scan response data. If - @ref ble_gap_adv_report_type_t::status is not set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided - in @ref sd_ble_gap_scan_start is now released. */ - ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising - event. @note This field is only set if @ref ble_gap_adv_report_type_t::status - is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ -} ble_gap_evt_adv_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Man In The Middle protection requested. */ - uint8_t lesc : 1; /**< LE Secure Connections requested. */ - uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ -} ble_gap_evt_sec_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ -typedef struct { - uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ -} ble_gap_evt_scan_req_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ -typedef struct { - ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ -} ble_gap_evt_data_length_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. - * - * @note This event may also be raised after a PHY Update procedure. - */ -typedef struct { - ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ -} ble_gap_evt_data_length_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ -typedef struct { - int8_t - channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy - channels, in dBm, indexed by Channel Index. - If no measurement is available for the given channel, channel_energy is set to - @ref BLE_GAP_POWER_LEVEL_INVALID. */ -} ble_gap_evt_qos_channel_survey_report_t; - -/**@brief GAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - union /**< union alternative identified by evt_id in enclosing struct. */ - { - ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ - ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ - ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ - ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ - ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ - ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ - ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ - ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ - ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ - ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ - ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ - ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ - ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ - ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ - ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ - ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ - ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ - ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ - ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ - ble_gap_evt_qos_channel_survey_report_t - qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_t; - -/** - * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. - * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. - */ -typedef struct { - uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. - The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ - uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. - The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref - BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters - for setting the throughput of a connection. - See the SoftDevice Specification for details on throughput. */ -} ble_gap_conn_cfg_t; - -/** - * @brief Configuration of maximum concurrent connections in the different connected roles, set with - * @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too - * large. The maximum supported sum of concurrent connections is - * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. - * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. - * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum - * supported advertising handles is - * @ref BLE_GAP_ADV_SET_COUNT_MAX. - */ -typedef struct { - uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ - uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref - BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ - uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref - BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ - uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is - @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ - uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to - the application using @ref sd_ble_gap_qos_channel_survey_start. */ -} ble_gap_cfg_role_count_t; - -/** - * @brief Device name and its properties, set with @ref sd_ble_cfg_set. - * - * @note If the device name is not configured, the default device name will be - * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be - * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name - * will have no write access. - * - * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, - * the attribute table size must be increased to have room for the longer device name (see - * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). - * - * @note If vloc is @ref BLE_GATTS_VLOC_STACK : - * - p_value must point to non-volatile memory (flash) or be NULL. - * - If p_value is NULL, the device name will initially be empty. - * - * @note If vloc is @ref BLE_GATTS_VLOC_USER : - * - p_value cannot be NULL. - * - If the device name is writable, p_value must point to volatile memory (RAM). - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Invalid device name location (vloc). - * - Invalid device name security mode. - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). - * - The device name length is too long for the given Attribute Table. - * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. - */ -typedef struct { - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ - uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ - uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ -} ble_gap_cfg_device_name_t; - -/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_ppcp_incl_cfg_t; - -/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_car_incl_cfg_t; - -/**@brief Configuration structure for GAP configurations. */ -typedef union { - ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ - ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ - ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include - configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ - ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, - cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ -} ble_gap_cfg_t; - -/**@brief Channel Map option. - * - * @details Used with @ref sd_ble_opt_get to get the current channel map - * or @ref sd_ble_opt_set to set a new channel map. When setting the - * channel map, it applies to all current and future connections. When getting the - * current channel map, it applies to a single connection and the connection handle - * must be supplied. - * - * @note Setting the channel map may take some time, depending on connection parameters. - * The time taken may be different for each connection and the get operation will - * return the previous channel map until the new one has taken effect. - * - * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. - * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. - * - * @retval ::NRF_SUCCESS Get or set successful. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Less then two bits in @ref ch_map are set. - * - Bits for primary advertising channels (37-39) are set. - * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. - * - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ - uint8_t ch_map[5]; /**< Channel Map (37-bit). */ -} ble_gap_opt_ch_map_t; - -/**@brief Local connection latency option. - * - * @details Local connection latency is a feature which enables the slave to improve - * current consumption by ignoring the slave latency set by the peer. The - * local connection latency can only be set to a multiple of the slave latency, - * and cannot be longer than half of the supervision timeout. - * - * @details Used with @ref sd_ble_opt_set to set the local connection latency. The - * @ref sd_ble_opt_get is not supported for this option, but the actual - * local connection latency (unless set to NULL) is set as a return parameter - * when setting the option. - * - * @note The latency set will be truncated down to the closest slave latency event - * multiple, or the nearest multiple before half of the supervision timeout. - * - * @note The local connection latency is disabled by default, and needs to be enabled for new - * connections and whenever the connection is updated. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t requested_latency; /**< Requested local connection latency. */ - uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return - value). */ -} ble_gap_opt_local_conn_latency_t; - -/**@brief Disable slave latency - * - * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection - * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the - * peripheral will ignore the slave_latency set by the central. - * - * @note Shall only be called on peripheral links. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ -} ble_gap_opt_slave_latency_disable_t; - -/**@brief Passkey Option. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @endmscs - * - * @details Structure containing the passkey to be used during pairing. This can be used with @ref - * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication - * instead of generating a random one. - * - * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - */ -typedef struct { - uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used - during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ -} ble_gap_opt_passkey_t; - -/**@brief Compatibility mode 1 option. - * - * @details This can be used with @ref sd_ble_opt_set to enable and disable - * compatibility mode 1. Compatibility mode 1 is disabled by default. - * - * @note Compatibility mode 1 enables interoperability with devices that do not support a value of - * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a - * limited set of legacy peripheral devices from another vendor. Enabling this compatibility - * mode will only have an effect if the local device will act as a central device and - * initiate a connection to a peripheral device. In that case it may lead to the connection - * creation taking up to one connection interval longer to complete for all connections. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. - */ -typedef struct { - uint8_t enable : 1; /**< Enable compatibility mode 1.*/ -} ble_gap_opt_compat_mode_1_t; - -/**@brief Authenticated payload timeout option. - * - * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other - * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. - * - * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated - * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted - * link. - * - * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance - * to reset the timer. In addition the stack will try to prioritize running of LE ping over other - * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects - * on other activities, it is recommended to use high timeout values. - * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ -} ble_gap_opt_auth_payload_timeout_t; - -/**@brief Option structure for GAP options. */ -typedef union { - ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ - ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ - ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ - ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ - ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ - ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ -} ble_gap_opt_t; - -/**@brief Connection event triggering parameters. */ -typedef struct { - uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until - connection event PPI task triggering is stopped. - The PPI channel ID can not be one of the PPI channels reserved by - the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ - uint32_t task_endpoint; /**< Task Endpoint to trigger. */ - uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ - uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. - If the device is in slave role and slave latency is enabled, - this parameter should be set to a multiple of (slave latency + 1) - to ensure low power operation. */ -} ble_gap_conn_event_trigger_t; -/**@} */ - -/**@addtogroup BLE_GAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set the local Bluetooth identity address. - * - * The local Bluetooth identity address is the address that identifies this device to other peers. - * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @note The identity address cannot be changed while advertising, scanning or creating a connection. - * - * @note This address will be distributed to the peer during bonding. - * If the address changes, the address stored in the peer device will not be valid and the ability to - * reconnect using the old address will be lost. - * - * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being - * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged - * for the lifetime of each IC. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @endmscs - * - * @param[in] p_addr Pointer to address structure. - * - * @retval ::NRF_SUCCESS Address successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, - * scanning or creating a connection. - */ -SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); - -/**@brief Get local Bluetooth identity address. - * - * @note This will always return the identity address irrespective of the privacy settings, - * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - */ -SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); - -/**@brief Get the Bluetooth device address used by the advertiser. - * - * @note This function will return the local Bluetooth address used in advertising PDUs. When - * using privacy, the SoftDevice will generate a new private address every - * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using - * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the - * address returned may not be the latest address that is used in the advertising PDUs. - * - * @param[in] adv_handle The advertising handle to get the address from. - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. - * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); - -/**@brief Set the active whitelist in the SoftDevice. - * - * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. - * The whitelist cannot be set if a BLE role is using the whitelist. - * - * @note If an address is resolved using the information in the device identity list, then the whitelist - * filter policy applies to the peer identity address and not the resolvable address sent on air. - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @endmscs - * - * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. - * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. - * - * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. - * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when - * pp_wl_addrs is not NULL. - */ -SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); - -/**@brief Set device identity list. - * - * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. - * The device identity list cannot be set if a BLE role is using the list. - * - * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will - * be cleared. - * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the - * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. - * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. - * This code may be returned if the local IRK list also has an invalid entry. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity - * address. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can - * only return when pp_id_keys is not NULL. - */ -SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, - sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, - uint8_t len)); - -/**@brief Set privacy settings. - * - * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. - * - * @param[in] p_privacy_params Privacy settings. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. - * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution - characteristic is not configured to be included and the SoftDevice is configured - to support central roles. - See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning - * or creating a connection. - */ -SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); - -/**@brief Get privacy settings. - * - * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. - * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. - * - * @param[in,out] p_privacy_params Privacy settings. - * - * @retval ::NRF_SUCCESS Privacy settings read. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - */ -SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); - -/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. - * - * @note The format of the advertising data will be checked by this call to ensure interoperability. - * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and - * duplicating the local name in the advertising data and scan response data. - * - * @note In order to update advertising data while advertising, new advertising buffers must be provided. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the - * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. - * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See - * @ref ble_gap_adv_data_t. - * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising - * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. - * - * @retval ::NRF_SUCCESS Advertising set successfully configured. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid advertising data configuration specified. See @ref - * ble_gap_adv_data_t. - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, - * see @ref sd_ble_gap_whitelist_set. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - It is invalid to provide non-NULL advertising set parameters while - * advertising. - * - It is invalid to provide the same data buffers while advertising. To - * update advertising data, provide new advertising buffers. - * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format - * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an - * existing advertising handle instead. - * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. - */ -SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, - sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, - ble_gap_adv_params_t const *p_adv_params)); - -/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @note Only one advertiser may be active at any time. - * - * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. - * See @ref sd_ble_gap_privacy_set(). - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} - * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} - * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable - * advertising, this is ignored. - * - * @retval ::NRF_SUCCESS The BLE stack has started advertising. - * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration - * tag has been reached; connectable advertiser cannot be started. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref - sd_ble_gap_adv_set_configure. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, see @ref - sd_ble_gap_whitelist_set. - * @retval ::NRF_ERROR_RESOURCES Either: - * - adv_handle is configured with connectable advertising, but the event_length parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - * - Not enough BLE role slots available. - Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) - and try again. - * - p_adv_params is configured with connectable advertising, but the event_length - parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); - -/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle The advertising handle that should stop advertising. - * - * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. - * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); - -/**@brief Update connection parameters. - * - * @details In the central role this will initiate a Link Layer connection parameter update procedure, - * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for - * the central to perform the procedure. In both cases, and regardless of success or failure, the application - * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. - * - * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the - * procedure unrequested. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CPU_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, - * the parameters in the PPCP characteristic of the GAP service will be used instead. - * If NULL is provided on a central role and in response to a @ref - * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected - * - * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, - sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Disconnect (GAP Link Termination). - * - * @details This call initiates the disconnection procedure, and its completion will be communicated to the application - * with a @ref BLE_GAP_EVT_DISCONNECTED event. - * - * @events - * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CONN_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref - * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). - * - * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - */ -SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); - -/**@brief Set the radio's transmit power. - * - * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for - * possible roles. - * @param[in] handle The handle parameter is interpreted depending on role: - * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. - * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, - * will use the specified transmit power, and include it in the advertising packet headers if - * @ref ble_gap_adv_properties_t::include_tx_power set. - * - For all other roles handle is ignored. - * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). - * - * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. - * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. - * Setting these values on a chip that does not support them will result in undefined behaviour. - * @note The initiator will have the same transmit power as the scanner. - * @note When a connection is created it will inherit the transmit power from the initiator or - * advertiser leading to the connection. - * - * @retval ::NRF_SUCCESS Successfully changed the transmit power. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); - -/**@brief Set GAP Appearance value. - * - * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); - -/**@brief Get GAP Appearance value. - * - * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); - -/**@brief Set GAP Peripheral Preferred Connection Parameters. - * - * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Get GAP Peripheral Preferred Connection Parameters. - * - * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); - -/**@brief Set GAP device name. - * - * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), - * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. - * - * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. - * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. - * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or - * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). - * - * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, - sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); - -/**@brief Get GAP device name. - * - * @note If the device name is longer than the size of the supplied buffer, - * p_len will return the complete device name length, - * and not the number of bytes actually returned in p_dev_name. - * The application may use this information to allocate a suitable buffer size. - * - * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to - * NULL to obtain the complete device name length. - * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. - * - * @retval ::NRF_SUCCESS GAP device name retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); - -/**@brief Initiate the GAP Authentication procedure. - * - * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), - * otherwise in the peripheral role, an SMP Security Request will be sent. - * - * @events - * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be - * generated:} - * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} - * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} - * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} - * @event{@ref BLE_GAP_EVT_KEY_PRESSED} - * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} - * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} - * @event{@ref BLE_GAP_EVT_AUTH_STATUS} - * @event{@ref BLE_GAP_EVT_TIMEOUT} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the - * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. - * In the central role, this pointer may be NULL to reject a Security Request. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - An encryption is already executing or queued. - * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is - * reached. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. - */ -SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, - sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); - -/**@brief Reply with GAP security parameters. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. - * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be - * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. - * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or - * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this - * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. - * Note that the SoftDevice expects the application to provide memory for storing the - * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The - * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application - * upon reception of the - * @ref BLE_GAP_EVT_AUTH_STATUS event. - * - * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - */ -SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, - sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, - ble_gap_sec_keyset_t const *p_sec_keyset)); - -/**@brief Reply with an authentication key. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, - * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. - * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. - * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL - * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, - * then a 16-byte OOB key value in little-endian format. - * - * @retval ::NRF_SUCCESS Authentication key successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, - sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); - -/**@brief Reply with an LE Secure connections DHKey. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dhkey LE Secure Connections DHKey. - * - * @retval ::NRF_SUCCESS DHKey successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - The peer is not authenticated. - * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, - sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); - -/**@brief Notify the peer of a local keypress. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. - * - * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested. - * - Passkey has not been entered. - * - Keypresses have not been enabled by both peers. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. - */ -SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); - -/**@brief Generate a set of OOB data to send to a peer out of band. - * - * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has - * already been established, the one used during connection setup). The application may manually overwrite it with an updated - * value. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. - * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. - * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. - * - * @retval ::NRF_SUCCESS OOB data successfully generated. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, - sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, - ble_gap_lesc_oob_data_t *p_oobd_own)); - -/**@brief Provide the OOB data sent/received out of band. - * - * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. - * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this - * function. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. - * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. - * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. - * Must correspond to @ref ble_gap_sec_params_t::oob flag - * in @ref sd_ble_gap_authenticate in the central role or - * in @ref sd_ble_gap_sec_params_reply in the peripheral role. - * - * @retval ::NRF_SUCCESS OOB data accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested - * - Not expecting LESC OOB data - * - Have not actually exchanged passkeys. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, - sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, - ble_gap_lesc_oob_data_t const *p_oobd_peer)); - -/**@brief Initiate GAP Encryption procedure. - * - * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. - * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and - * retry. - */ -SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, - sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); - -/**@brief Reply with GAP security information. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in - * @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is - * available. - * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. - * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is - * available. - * - * @retval ::NRF_SUCCESS Successfully accepted security information. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. - * - Encryption information provided by the app without being requested. See @ref - * ble_gap_evt_sec_info_request_t::enc_info. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, - sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, - ble_gap_sign_info_t const *p_sign_info)); - -/**@brief Get the current connection security. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Current connection security successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); - -/**@brief Start reporting the received signal strength to the application. - * - * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. - * - * @events - * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is - * dependent on the settings of the threshold_dbm - * and skip_count input parameters.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are - * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. - * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref - * BLE_GAP_EVT_RSSI_CHANGED event. - * - * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); - -/**@brief Stop reporting the received signal strength. - * - * @note An RSSI change detected before the call but not yet received by the application - * may be reported after @ref sd_ble_gap_rssi_stop has been called. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); - -/**@brief Get the received signal strength for the last connection event. - * - * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND - * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. - * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. - * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. - * - * @retval ::NRF_SUCCESS Successfully read the RSSI. - * @retval ::NRF_ERROR_NOT_FOUND No sample is available. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - */ -SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); - -/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). - * - * @note A call to this function will require the application to keep the memory pointed by - * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped - * or when this function is called with another buffer. - * - * @note The scanner will automatically stop in the following cases: - * - @ref sd_ble_gap_scan_stop is called. - * - @ref sd_ble_gap_connect is called. - * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. - * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application - * access received data. The application must call this function to continue scanning, or call @ref - * sd_ble_gap_scan_stop to stop scanning. - * - * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will - * receive more reports from this advertising event. The following reports will include the old and new received data. - * - * @events - * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue - * scanning, this parameter must be NULL. - * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. - * The memory pointed to should be kept alive until the scanning is stopped. - * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. - * If the scanner receives advertising data larger than can be stored in the buffer, - * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status - * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. - * - * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Scanning is already ongoing and p_scan_params was not NULL - * - Scanning is not running and p_scan_params was NULL. - * - The scanner has timed out when this function is called to continue scanning. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. - * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again - */ -SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, - sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); - -/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). - * - * @note The buffer provided in @ref sd_ble_gap_scan_start is released. - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. - * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. - */ -SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); - -/**@brief Create a connection (GAP Link Establishment). - * - * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. - * The scanning procedure will be stopped even if the function returns an error. - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use - * whitelist, then p_peer_addr is ignored. - * @param[in] p_scan_params Pointer to scan parameters structure. - * @param[in] p_conn_params Pointer to desired connection parameters. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. - * - * @retval ::NRF_SUCCESS Successfully initiated connection procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * - Invalid parameter(s) in p_scan_params or p_conn_params. - * - Use of whitelist requested but whitelist has not been set, see @ref - * sd_ble_gap_whitelist_set. - * - Peer address was not present in the device identity list, see @ref - * sd_ble_gap_device_identities_set. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an - * existing locally initiated connect procedure, which must complete before initiating again. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::NRF_ERROR_RESOURCES Either: - * - Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. - * - The event_length parameter associated with conn_cfg_tag is too small to be able to - * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. - * Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_CONNECT, uint32_t, - sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, - ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); - -/**@brief Cancel a connection establishment. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. - * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection - * completed occurred. - */ -SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); - -/**@brief Initiate or respond to a PHY Update Procedure - * - * @details This function is used to initiate or respond to a PHY Update Procedure. It will always - * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. - * If this function is used to initiate a PHY Update procedure and the only option - * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the - * currently active PHYs in the respective directions, the SoftDevice will generate a - * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the - * procedure in the Link Layer. - * - * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, - * then the stack will select PHYs based on the peer's PHY preferences and the local link - * configuration. The PHY Update procedure will for this case result in a PHY combination - * that respects the time constraints configured with @ref sd_ble_cfg_set and the current - * link layer data length. - * - * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. - * - * If the peer does not support the PHY Update Procedure, then the resulting - * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to - * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. - * - * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status - * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or - * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. - * If the peer responds to the PHY Update procedure with invalid parameters, the status - * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. - * If the PHY Update procedure was rejected by the peer for a different reason, the status will - * contain the reason as specified by the peer. - * - * @events - * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} - * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} - * @endmscs - * - * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. - * @param[in] p_gap_phys Pointer to PHY structure. - * - * @retval ::NRF_SUCCESS Successfully requested a PHY Update. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of - * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref - * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. - * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the - * pending procedure to complete and retry. - * - */ -SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); - -/**@brief Initiate or respond to a Data Length Update Procedure. - * - * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of - * p_dl_params, the SoftDevice will choose the highest value supported in current - * configuration and connection parameters. - * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime - * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and - * MaxRxTime will be limited to maximum 2120 us. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update - * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let - * the SoftDevice automatically decide the value for that member. - * Set to NULL to use automatic values for all members. - * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not - * have enough resources or does not support the requested Data Length - * Update parameters. Ignored if NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect - * p_dl_limitation to see which parameter is not supported. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested - * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation - * to see where the limitation is. - * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the - * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. - */ -SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, - sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, - ble_gap_data_length_limitation_t *p_dl_limitation)); - -/**@brief Start the Quality of Service (QoS) channel survey module. - * - * @details The channel survey module provides measurements of the energy levels on - * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT - * events will periodically report the measured energy levels for each channel. - * - * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, - * Radio Timeslot API events and Flash API events. - * - * @note The channel survey module will attempt to do measurements so that the average interval - * between measurements will be interval_us. However due to the channel survey module - * having the lowest priority of all roles and modules, this may not be possible. In that - * case fewer than expected channel survey reports may be given. - * - * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available - * must be set. This is done using @ref sd_ble_cfg_set. - * - * @param[in] interval_us Requested average interval for the measurements and reports. See - * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set - * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel - * survey role will be scheduled at every available opportunity. - * - * @retval ::NRF_SUCCESS The module is successfully started. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the - * allowed range. - * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. - * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. - * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using - * @ref sd_ble_cfg_set. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); - -/**@brief Stop the Quality of Service (QoS) channel survey module. - * - * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this - * function is called. - * - * @retval ::NRF_SUCCESS The module is successfully stopped. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); - -/**@brief Obtain the next connection event counter value. - * - * @details The connection event counter is initialized to zero on the first connection event. The value is incremented - * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, - * Section 4.5.1. - * - * @note The connection event counter obtained through this API will be outdated if this API is called - * at the same time as the connection event counter is incremented. - * - * @note This API will always return the last connection event counter + 1. - * The actual connection event may be multiple connection events later if: - * - Slave latency is enabled and there is no data to transmit or receive. - * - Another role is scheduled with a higher priority at the same time as the next connection event. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. - * - * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, - sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); - -/**@brief Start triggering a given task on connection event start. - * - * @details When enabled, this feature will trigger a PPI task at the start of connection events. - * The application can configure the SoftDevice to trigger every N connection events starting from - * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_params Connection event trigger parameters. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. - * @retval ::NRF_ERROR_INVALID_STATE Either: - * - Trying to start connection event triggering when it is already ongoing. - * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. - * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value - to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, - sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); - -/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GAP_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatt.h b/variants/wio-tracker-wm1110/softdevice/ble_gatt.h deleted file mode 100644 index df0d728fc..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gatt.h +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common - @{ - @brief Common definitions and prototypes for the GATT interfaces. - */ - -#ifndef BLE_GATT_H__ -#define BLE_GATT_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATT_DEFINES Defines - * @{ */ - -/** @brief Default ATT MTU, in bytes. */ -#define BLE_GATT_ATT_MTU_DEFAULT 23 - -/**@brief Invalid Attribute Handle. */ -#define BLE_GATT_HANDLE_INVALID 0x0000 - -/**@brief First Attribute Handle. */ -#define BLE_GATT_HANDLE_START 0x0001 - -/**@brief Last Attribute Handle. */ -#define BLE_GATT_HANDLE_END 0xFFFF - -/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources - * @{ */ -#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ -/** @} */ - -/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations - * @{ */ -#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ -/** @} */ - -/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags - * @{ */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ -/** @} */ - -/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations - * @{ */ -#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ -#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ -/** @} */ - -/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes - * @{ */ -#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ -#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ -#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ -#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ -#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ -#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ -#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ - 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ -#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ -#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ - 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ -#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ -#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ -#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ - 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ - */ -#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ - 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ -#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ - 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ -#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats - * @note Found at - * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml - * @{ */ -#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ -#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ -#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ -#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ -#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ -#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ -#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ -#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces - * @{ - */ -#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ -#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATT_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. - */ -typedef struct { - uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. - The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - @mscs - @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - @endmscs - */ -} ble_gatt_conn_cfg_t; - -/**@brief GATT Characteristic Properties. */ -typedef struct { - /* Standard properties */ - uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ - uint8_t read : 1; /**< Reading the value permitted. */ - uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ - uint8_t write : 1; /**< Writing the value with Write Request permitted. */ - uint8_t notify : 1; /**< Notification of the value permitted. */ - uint8_t indicate : 1; /**< Indications of the value permitted. */ - uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ -} ble_gatt_char_props_t; - -/**@brief GATT Characteristic Extended Properties. */ -typedef struct { - /* Extended properties */ - uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ - uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ -} ble_gatt_char_ext_props_t; - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATT_H__ - -/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gattc.h b/variants/wio-tracker-wm1110/softdevice/ble_gattc.h deleted file mode 100644 index f1df1782c..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gattc.h +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client - @{ - @brief Definitions and prototypes for the GATT Client interface. - */ - -#ifndef BLE_GATTC_H__ -#define BLE_GATTC_H__ - -#include "ble_err.h" -#include "ble_gatt.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GATTC API SVC numbers. */ -enum BLE_GATTC_SVCS { - SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ - SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ - SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ - SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ - SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ - SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ - SD_BLE_GATTC_READ, /**< Generic read. */ - SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ - SD_BLE_GATTC_WRITE, /**< Generic write. */ - SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ - SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ -}; - -/** - * @brief GATT Client Event IDs. - */ -enum BLE_GATTC_EVTS { - BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref - ble_gattc_evt_prim_srvc_disc_rsp_t. */ - BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. - */ - BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref - ble_gattc_evt_char_disc_rsp_t. */ - BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref - ble_gattc_evt_desc_disc_rsp_t. */ - BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref - ble_gattc_evt_attr_info_disc_rsp_t. */ - BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ - BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ - BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref - ble_gattc_evt_char_vals_read_rsp_t. */ - BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ - BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref - sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ - BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref - ble_gattc_evt_exchange_mtu_rsp_t. */ - BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ - BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref - ble_gattc_evt_write_cmd_tx_complete_t. */ -}; - -/**@brief GATTC Option IDs. - * IDs that uniquely identify a GATTC option. - */ -enum BLE_GATTC_OPTS { - BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTC_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC - * @{ */ -#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ -/** @} */ - -/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats - * @{ */ -#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ -#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ -/** @} */ - -/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults - * @{ */ -#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Write without Response that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTC_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for - transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ -} ble_gattc_conn_cfg_t; - -/**@brief Operation Handle Range. */ -typedef struct { - uint16_t start_handle; /**< Start Handle. */ - uint16_t end_handle; /**< End Handle. */ -} ble_gattc_handle_range_t; - -/**@brief GATT service. */ -typedef struct { - ble_uuid_t uuid; /**< Service UUID. */ - ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ -} ble_gattc_service_t; - -/**@brief GATT include. */ -typedef struct { - uint16_t handle; /**< Include Handle. */ - ble_gattc_service_t included_srvc; /**< Handle of the included service. */ -} ble_gattc_include_t; - -/**@brief GATT characteristic. */ -typedef struct { - ble_uuid_t uuid; /**< Characteristic UUID. */ - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - uint8_t char_ext_props : 1; /**< Extended properties present. */ - uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ - uint16_t handle_value; /**< Handle of the Characteristic Value. */ -} ble_gattc_char_t; - -/**@brief GATT descriptor. */ -typedef struct { - uint16_t handle; /**< Descriptor Handle. */ - ble_uuid_t uuid; /**< Descriptor UUID. */ -} ble_gattc_desc_t; - -/**@brief Write Parameters. */ -typedef struct { - uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ - uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ - uint16_t handle; /**< Handle to the attribute to be written. */ - uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ - uint16_t len; /**< Length of data in bytes. */ - uint8_t const *p_value; /**< Pointer to the value data. */ -} ble_gattc_write_params_t; - -/**@brief Attribute Information for 16-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ -} ble_gattc_attr_info16_t; - -/**@brief Attribute Information for 128-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ -} ble_gattc_attr_info128_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Service count. */ - ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_prim_srvc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Include count. */ - ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_rel_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Characteristic count. */ - ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_char_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Descriptor count. */ - ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_desc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Attribute count. */ - uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ - union { - ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - } info; /**< Attribute information union. */ -} ble_gattc_evt_attr_info_disc_rsp_t; - -/**@brief GATT read by UUID handle value pair. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ -} ble_gattc_handle_value_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ -typedef struct { - uint16_t count; /**< Handle-Value Pair Count. */ - uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ - uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref - sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. - @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_val_by_uuid_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint16_t offset; /**< Offset of the attribute data. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ -typedef struct { - uint16_t len; /**< Concatenated Attribute values length. */ - uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder - for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_vals_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ - uint16_t offset; /**< Data offset. */ - uint16_t len; /**< Data length. */ - uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_write_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ -typedef struct { - uint16_t handle; /**< Handle to which the HVx operation applies. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_hvx_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ -typedef struct { - uint16_t server_rx_mtu; /**< Server RX MTU size. */ -} ble_gattc_evt_exchange_mtu_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gattc_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of write without response transmissions completed. */ -} ble_gattc_evt_write_cmd_tx_complete_t; - -/**@brief GATTC event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint16_t - error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ - union { - ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ - ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ - ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ - ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ - ble_gattc_evt_char_val_by_uuid_read_rsp_t - char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ - ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ - ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ - ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ - ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ - ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ - ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ - ble_gattc_evt_write_cmd_tx_complete_t - write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ - } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ -} ble_gattc_evt_t; - -/**@brief UUID discovery option. - * - * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the - * Vendor Specific UUID table. Disabled by default. - * - When disabled, if a procedure initiated by - * @ref sd_ble_gattc_primary_services_discover, - * @ref sd_ble_gattc_relationships_discover, - * @ref sd_ble_gattc_characteristics_discover, - * @ref sd_ble_gattc_descriptors_discover - * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set - * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use - * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding - * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will - * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - * @retval ::NRF_SUCCESS Set successfully. - * - */ -typedef struct { - uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ -} ble_gattc_opt_uuid_disc_t; - -/**@brief Option structure for GATTC options. */ -typedef union { - ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ -} ble_gattc_opt_t; - -/** @} */ - -/** @addtogroup BLE_GATTC_FUNCTIONS Functions - * @{ */ - -/**@brief Initiate or continue a GATT Primary Service Discovery procedure. - * - * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. - * If the last service has not been reached, this function must be called again with an updated start handle value to - * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] start_handle Handle to start searching from. - * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, - sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); - -/**@brief Initiate or continue a GATT Relationship Discovery procedure. - * - * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been - * reached, this must be called again with an updated handle range to continue the search. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, - sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Discovery procedure. - * - * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, - sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. - * - * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not - * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, - sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. - * - * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_uuid Pointer to a Characteristic value UUID to read. - * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, - sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, - ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. - * - * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or - * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read - * the complete value. - * - * @events - * @event{@ref BLE_GATTC_EVT_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute to be read. - * @param[in] offset Offset into the attribute value to be read. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); - -/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. - * - * @details This function initiates a GATT Read Multiple Characteristic Values procedure. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. - * @param[in] handle_count The number of handles in p_handles. - * - * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, - sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); - -/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) - * procedure. - * - * @details This function can perform all write procedures described in GATT. - * - * @note Only one write with response procedure can be ongoing per connection at a time. - * If the application tries to write with response while another write with response procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. - * - * @note The number of Write without Response that can be queued is configured by @ref - * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. - * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without - * response is complete. - * - * @note The application can keep track of the available queue element count for writes without responses by following the - * procedure below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} - * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_write_params A pointer to a write parameters structure. - * - * @retval ::NRF_SUCCESS Successfully started the Write procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event - * and retry. - * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. - * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); - -/**@brief Send a Handle Value Confirmation to the GATT Server. - * - * @mscs - * @mmsc{@ref BLE_GATTC_HVI_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute in the indication. - * - * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); - -/**@brief Discovers information about a range of attributes on a GATT server. - * - * @events - * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} - * @endevents - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range The range of handles to request information about. - * - * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, - sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value, and - * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @events - * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] client_rx_mtu Client RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - used for this connection. - * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent request to the server. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, - sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); - -/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * - * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * @note If the buffer contains different event, behavior is undefined. - * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with - * the next Handle-Value pair in each iteration. If the function returns other than - * @ref NRF_SUCCESS, it will not be changed. - * - To start iteration, initialize the structure to zero. - * - To continue, pass the value from previous iteration. - * - * \code - * ble_gattc_handle_value_t iter; - * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); - * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) - * { - * app_handle = iter.handle; - * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); - * } - * \endcode - * - * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. - * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. - */ -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter); - -/** @} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter) -{ - uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; - uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; - uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; - - if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { - p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; - p_iter->p_value = p_next + sizeof(uint16_t); - return NRF_SUCCESS; - } else { - return NRF_ERROR_NOT_FOUND; - } -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_GATTC_H__ */ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_gatts.h b/variants/wio-tracker-wm1110/softdevice/ble_gatts.h deleted file mode 100644 index dc94957cd..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_gatts.h +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server - @{ - @brief Definitions and prototypes for the GATTS interface. - */ - -#ifndef BLE_GATTS_H__ -#define BLE_GATTS_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief GATTS API SVC numbers. - */ -enum BLE_GATTS_SVCS { - SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ - SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ - SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ - SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ - SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ - SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ - SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ - SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ - SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more - attributes. */ - SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ - SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ - SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ - SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ - SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ -}; - -/** - * @brief GATT Server Event IDs. - */ -enum BLE_GATTS_EVTS { - BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See - @ref ble_gatts_evt_write_t. */ - BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with - @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. - */ - BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref - sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ - BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. - */ - BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event - structure applies. */ - BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with - @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. - */ - BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref - ble_gatts_evt_timeout_t. */ - BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref - ble_gatts_evt_hvn_tx_complete_t. */ -}; - -/**@brief GATTS Configuration IDs. - * - * IDs that uniquely identify a GATTS configuration. - */ -enum BLE_GATTS_CFGS { - BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ - BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTS_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS - * @{ */ -#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ -#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths - * @{ */ -#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ -#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ -/** @} */ - -/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types - * @{ */ -#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ -#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ -#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types - * @{ */ -#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ -#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ -#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ -#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ -/** @} */ - -/** @defgroup BLE_GATTS_OPS GATT Server Operations - * @{ */ -#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ -/** @} */ - -/** @defgroup BLE_GATTS_VLOCS GATT Value Locations - * @{ */ -#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ -#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ -#define BLE_GATTS_VLOC_USER \ - 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ - of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ - There are no alignment requirements for the buffer. */ -/** @} */ - -/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types - * @{ */ -#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ -#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ -#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ -/** @} */ - -/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags - * @{ */ -#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ -#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ -/** @} */ - -/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values - * @{ - */ -#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ - (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size - * @{ - */ -#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ -#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ -/** @} */ - -/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults - * @{ - */ -#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTS_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. - The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ -} ble_gatts_conn_cfg_t; - -/**@brief Attribute metadata. */ -typedef struct { - ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vlen : 1; /**< Variable length attribute. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ - uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not - Write Command). */ -} ble_gatts_attr_md_t; - -/**@brief GATT Attribute. */ -typedef struct { - ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ - ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ - uint16_t init_len; /**< Initial attribute value length in bytes. */ - uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the - attribute value will be left uninitialized. */ - uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ - uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is - selected in the attribute metadata, this will have to point to a buffer that remains valid through the - lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any - other temporary location. The stack may access that memory directly without the application's - knowledge. For writable characteristics, this value must not be a location in flash memory.*/ -} ble_gatts_attr_t; - -/**@brief GATT Attribute Value. */ -typedef struct { - uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ - uint16_t offset; /**< Attribute value offset. */ - uint8_t *p_value; /**< Pointer to where value is stored or will be stored. - If value is stored in user memory, only the attribute length is updated when p_value == NULL. - Set to NULL when reading to obtain the complete length of the attribute value */ -} ble_gatts_value_t; - -/**@brief GATT Characteristic Presentation Format. */ -typedef struct { - uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ - int8_t exponent; /**< Exponent for integer data types. */ - uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ - uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ - uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ -} ble_gatts_char_pf_t; - -/**@brief GATT Characteristic metadata. */ -typedef struct { - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ - uint8_t const * - p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ - uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ - uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ - ble_gatts_char_pf_t const - *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ - ble_gatts_attr_md_t const - *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ -} ble_gatts_char_md_t; - -/**@brief GATT Characteristic Definition Handles. */ -typedef struct { - uint16_t value_handle; /**< Handle to the characteristic value. */ - uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ - uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ - uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ -} ble_gatts_char_handles_t; - -/**@brief GATT HVx parameters. */ -typedef struct { - uint16_t handle; /**< Characteristic Value Handle. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t offset; /**< Offset within the attribute value. */ - uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ - uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ -} ble_gatts_hvx_params_t; - -/**@brief GATT Authorization parameters. */ -typedef struct { - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. - Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, - as the data to be written needs to be stored and later provided by the application. */ - uint16_t offset; /**< Offset of the attribute value being updated. */ - uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ - uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ -} ble_gatts_authorize_params_t; - -/**@brief GATT Read or Write Authorize Reply parameters. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ - ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ - } params; /**< Reply Parameters. */ -} ble_gatts_rw_authorize_reply_params_t; - -/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref - BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ -} ble_gatts_cfg_service_changed_t; - -/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - @ref ble_gatts_attr_md_t::write_perm is out of range. - * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is - * not allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is - * allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. - * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported - */ -typedef struct { - ble_gatts_attr_md_t - perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ -} ble_gatts_cfg_service_changed_cccd_perm_t; - -/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The specified Attribute Table size is too small. - * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. - * - The specified Attribute Table size is not a multiple of 4. - */ -typedef struct { - uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref - BLE_GATTS_ATTR_TAB_SIZE_MIN. */ -} ble_gatts_cfg_attr_tab_size_t; - -/**@brief Config structure for GATTS configurations. */ -typedef union { - ble_gatts_cfg_service_changed_t - service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ - ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ - ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ -} ble_gatts_cfg_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ - uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref - sd_ble_gatts_value_set to finalize the writing operation. */ - uint16_t offset; /**< Offset for the write operation. */ - uint16_t len; /**< Length of the received data. */ - uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gatts_evt_write_t; - -/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint16_t offset; /**< Offset for the read operation. */ -} ble_gatts_evt_read_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ - ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ - } request; /**< Request Parameters. */ -} ble_gatts_evt_rw_authorize_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ -typedef struct { - uint8_t hint; /**< Hint (currently unused). */ -} ble_gatts_evt_sys_attr_missing_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ -} ble_gatts_evt_hvc_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ -typedef struct { - uint16_t client_rx_mtu; /**< Client RX MTU size. */ -} ble_gatts_evt_exchange_mtu_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gatts_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of notification transmissions completed. */ -} ble_gatts_evt_hvn_tx_complete_t; - -/**@brief GATTS event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ - union { - ble_gatts_evt_write_t write; /**< Write Event Parameters. */ - ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ - ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ - ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ - ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ - ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ - ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_gatts_evt_t; - -/** @} */ - -/** @addtogroup BLE_GATTS_FUNCTIONS Functions - * @{ */ - -/**@brief Add a service declaration to the Attribute Table. - * - * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to - * add a secondary service declaration that is not referenced by another service later in the Attribute Table. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. - * @param[in] p_uuid Pointer to service UUID. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a service declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); - -/**@brief Add an include declaration to the Attribute Table. - * - * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note The included service must already be present in the Attribute Table prior to this call. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID - * is used, it will be placed sequentially. - * @param[in] inc_srvc_handle Handle of the included service. - * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added an include declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - */ -SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, - sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); - -/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations - * to the Attribute Table. - * - * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and - * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format - * values. - * - * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic - * permissions. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_char_md Characteristic metadata. - * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. - * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a characteristic. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, - sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, - ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); - -/**@brief Add a descriptor to the Attribute Table. - * - * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is - * supported at this time). - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_attr Pointer to the attribute structure. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a descriptor. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, - sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); - -/**@brief Set the value of a given attribute. - * - * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully set the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - */ -SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, - sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Get the value of a given attribute. - * - * @note If the attribute value is longer than the size of the supplied buffer, - * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), - * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. - * The application may use this information to allocate a suitable buffer size. - * - * @note When retrieving system attribute values with this function, the connection handle - * may refer to an already disconnected connection. Refer to the documentation of - * @ref sd_ble_gatts_sys_attr_get for further information. - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - */ -SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, - sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Notify or Indicate an attribute value. - * - * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant - * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before - * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single - * API call. - * - * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during - * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, - * @ref NRF_ERROR_BUSY, - * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. - * The caller can check whether the value has been updated by looking at the contents of *(@ref - * ble_gatts_hvx_params_t::p_len). - * - * @note Only one indication procedure can be ongoing per connection at a time. - * If the application tries to indicate an attribute value while another indication procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. - * - * @note The number of Handle Value Notifications that can be queued is configured by @ref - * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. - * - * @note The application can keep track of the available queue element count for notifications by following the procedure - * below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} - * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_HVN_MSC} - * @mmsc{@ref BLE_GATTS_HVI_MSC} - * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data - * contains a non-NULL pointer the attribute value will be updated with the contents - * pointed by it before sending the notification or indication. If the attribute value - * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to - * contain the number of actual bytes written, else it will be set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute - * value. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application - * are available to notify and indicate. - * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and - * indicated. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions - * of the CCCD associated with this characteristic. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC - * event and retry. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. - * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); - -/**@brief Indicate the Service Changed attribute value. - * - * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute - * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will - * be issued. - * - * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. - * - * @events - * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_SC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] start_handle Start of affected attribute handle range. - * @param[in] end_handle End of affected attribute handle range. - * - * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref - * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the - * application. - * @retval ::NRF_ERROR_BUSY Procedure already in progress. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, - sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); - -/**@brief Respond to a Read/Write authorization request. - * - * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. - * - * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond - * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update - * is set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute - * Table updated. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, - * handle supplied does not match requested handle, - * or invalid data to be written provided by the application. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, - sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, - ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); - -/**@brief Update persistent system attribute information. - * - * @details Supply information about persistent system attributes to the stack, - * previously obtained using @ref sd_ble_gatts_sys_attr_get. - * This call is only allowed for active connections, and is usually - * made immediately after a connection is established with an known bonded device, - * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. - * - * p_sysattrs may point directly to the application's stored copy of the system attributes - * obtained using @ref sd_ble_gatts_sys_attr_get. - * If the pointer is NULL, the system attribute info is initialized, assuming that - * the application does not have any previously saved system attribute data for this device. - * - * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. - * - * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may - * have been completed only partially. This means that the state of the attribute table is undefined, and the application should - * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be modified. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be modified. - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. - * @param[in] len Size of data pointed by p_sys_attr_data, in octets. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully set the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref - * sd_ble_gatts_sys_attr_get. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, - sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); - -/**@brief Retrieve persistent system attribute information from the stack. - * - * @details This call is used to retrieve information about values to be stored persistently by the application - * during the lifetime of a connection or after it has been terminated. When a new connection is established with the - * same bonded device, the system attribute information retrieved with this function should be restored using using @ref - * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The - * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API - * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the - * system attributes may be written to at any time by the peer during a connection's lifetime. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be returned. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be returned. - * - * @mscs - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle of the recently terminated connection. - * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The - * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. - * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual - * length of system attribute data. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. - * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, - sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); - -/**@brief Retrieve the first valid user attribute handle. - * - * @param[out] p_handle Pointer to an integer where the handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully retrieved the handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); - -/**@brief Retrieve the attribute UUID and/or metadata. - * - * @param[in] handle Attribute handle - * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. - * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. - * - * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. - * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. - */ -SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); - -/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. - * - * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and - * - The Server RX MTU value. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @mscs - * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] server_rx_mtu Server RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - * used for this connection. - * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent response to the client. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATTS_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_hci.h b/variants/wio-tracker-wm1110/softdevice/ble_hci.h deleted file mode 100644 index 27f85d52e..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_hci.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ -*/ - -#ifndef BLE_HCI_H__ -#define BLE_HCI_H__ -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes - * @{ */ - -#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ -/*0x03 Hardware Failure -0x04 Page Timeout -*/ -#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ -#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ -#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ -#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ -/*0x09 Connection Limit Exceeded -0x0A Synchronous Connection Limit To A Device Exceeded -0x0B ACL Connection Already Exists*/ -#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ -/*0x0D Connection Rejected due to Limited Resources -0x0E Connection Rejected Due To Security Reasons -0x0F Connection Rejected due to Unacceptable BD_ADDR -0x10 Connection Accept Timeout Exceeded -0x11 Unsupported Feature or Parameter Value*/ -#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ -#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ - 0x14 /**< Remote Device Terminated Connection due to low \ - resources.*/ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ -#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ -/* -0x17 Repeated Attempts -0x18 Pairing Not Allowed -0x19 Unknown LMP PDU -*/ -#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ -/* -0x1B SCO Offset Rejected -0x1C SCO Interval Rejected -0x1D SCO Air Mode Rejected*/ -#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ -#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ -/*0x20 Unsupported LMP Parameter Value -0x21 Role Change Not Allowed -*/ -#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ -#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ -#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ -/*0x25 Encryption Mode Not Acceptable -0x26 Link Key Can Not be Changed -0x27 Requested QoS Not Supported -*/ -#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ -#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ -#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ -/* -0x2B Reserved -0x2C QoS Unacceptable Parameter -0x2D QoS Rejected -0x2E Channel Classification Not Supported -0x2F Insufficient Security -*/ -#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ -/* -0x31 Reserved -0x32 Role Switch Pending -0x33 Reserved -0x34 Reserved Slot Violation -0x35 Role Switch Failed -0x36 Extended Inquiry Response Too Large -0x37 Secure Simple Pairing Not Supported By Host. -0x38 Host Busy - Pairing -0x39 Connection Rejected due to No Suitable Channel Found*/ -#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ -#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ -#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ -#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ -#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_HCI_H__ - -/** @} */ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h b/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h deleted file mode 100644 index 5f4bd277d..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_l2cap.h +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) - @{ - @brief Definitions and prototypes for the L2CAP interface. - */ - -#ifndef BLE_L2CAP_H__ -#define BLE_L2CAP_H__ - -#include "ble_err.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology - * @{ - * @details - * - * L2CAP SDU - * - A data unit that the application can send/receive to/from a peer. - * - * L2CAP PDU - * - A data unit that is exchanged between local and remote L2CAP entities. - * It consists of L2CAP protocol control information and payload fields. - * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. - * - * L2CAP MTU - * - The maximum length of an L2CAP SDU. - * - * L2CAP MPS - * - The maximum length of an L2CAP PDU payload field. - * - * Credits - * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. - * @} */ - -/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief L2CAP API SVC numbers. */ -enum BLE_L2CAP_SVCS { - SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ - SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ - SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ -}; - -/**@brief L2CAP Event IDs. */ -enum BLE_L2CAP_EVTS { - BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. - \n Reply with @ref sd_ble_l2cap_ch_setup. - \n See @ref ble_l2cap_evt_ch_setup_request_t. */ - BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. - \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ - BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. - \n See @ref ble_l2cap_evt_ch_setup_t. */ - BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. - \n No additional event structure applies. */ - BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. - \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ - BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. - \n See @ref ble_l2cap_evt_ch_credit_t. */ - BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. - \n See @ref ble_l2cap_evt_ch_rx_t. */ - BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. - \n See @ref ble_l2cap_evt_ch_tx_t. */ -}; - -/** @} */ - -/**@addtogroup BLE_L2CAP_DEFINES Defines - * @{ */ - -/**@brief Maximum number of L2CAP channels per connection. */ -#define BLE_L2CAP_CH_COUNT_MAX (64) - -/**@brief Minimum L2CAP MTU, in bytes. */ -#define BLE_L2CAP_MTU_MIN (23) - -/**@brief Minimum L2CAP MPS, in bytes. */ -#define BLE_L2CAP_MPS_MIN (23) - -/**@brief Invalid CID. */ -#define BLE_L2CAP_CID_INVALID (0x0000) - -/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ -#define BLE_L2CAP_CREDITS_DEFAULT (1) - -/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources - * @{ */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ - /** @} */ - -/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes - * @{ */ -#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ -#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ -#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ -#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ -#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ -#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ -#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ - (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ -#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ -/** @} */ - -/** @} */ - -/**@addtogroup BLE_L2CAP_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note These parameters are set per connection, so all L2CAP channels created on this connection - * will have the same parameters. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. - * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. - */ -typedef struct { - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to receive on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to transmit on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per - L2CAP channel. The minimum value is one. */ - uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission - per L2CAP channel. The minimum value is one. */ - uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection - with this configuration. The default value is zero, the maximum - value is @ref BLE_L2CAP_CH_COUNT_MAX. - @note if this parameter is set to zero, all other parameters in - @ref ble_l2cap_conn_cfg_t are ignored. */ -} ble_l2cap_conn_cfg_t; - -/**@brief L2CAP channel RX parameters. */ -typedef struct { - uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to - receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be - able to receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. - - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ - ble_data_t sdu_buf; /**< SDU data buffer for reception. - - If @ref ble_data_t::p_data is non-NULL, initial credits are - issued to the peer. - - If @ref ble_data_t::p_data is NULL, no initial credits are - issued to the peer. */ -} ble_l2cap_ch_rx_params_t; - -/**@brief L2CAP channel setup parameters. */ -typedef struct { - ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting - setup of an L2CAP channel, ignored otherwise. */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. - Used when replying to a setup request of an L2CAP - channel, ignored otherwise. */ -} ble_l2cap_ch_setup_params_t; - -/**@brief L2CAP channel TX parameters. */ -typedef struct { - uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to - transmit on this L2CAP channel. */ - uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is - able to receive on this L2CAP channel. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able - to transmit on this L2CAP channel. This is effective tx_mps, - selected by the SoftDevice as - MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ - uint16_t credits; /**< Initial credits given by the peer. */ -} ble_l2cap_ch_tx_params_t; - -/**@brief L2CAP Channel Setup Request event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ -} ble_l2cap_evt_ch_setup_request_t; - -/**@brief L2CAP Channel Setup Refused event. */ -typedef struct { - uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ -} ble_l2cap_evt_ch_setup_refused_t; - -/**@brief L2CAP Channel Setup Completed event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ -} ble_l2cap_evt_ch_setup_t; - -/**@brief L2CAP Channel SDU Data Buffer Released event. */ -typedef struct { - ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice - returns SDU data buffers supplied by the application, which have - not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or - @ref BLE_L2CAP_EVT_CH_TX event. */ -} ble_l2cap_evt_ch_sdu_buf_released_t; - -/**@brief L2CAP Channel Credit received event. */ -typedef struct { - uint16_t credits; /**< Additional credits given by the peer. */ -} ble_l2cap_evt_ch_credit_t; - -/**@brief L2CAP Channel received SDU event. */ -typedef struct { - uint16_t sdu_len; /**< Total SDU length, in bytes. */ - ble_data_t sdu_buf; /**< SDU data buffer. - @note If there is not enough space in the buffer - (sdu_buf.len < sdu_len) then the rest of the SDU will be - silently discarded by the SoftDevice. */ -} ble_l2cap_evt_ch_rx_t; - -/**@brief L2CAP Channel transmitted SDU event. */ -typedef struct { - ble_data_t sdu_buf; /**< SDU data buffer. */ -} ble_l2cap_evt_ch_tx_t; - -/**@brief L2CAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occured. */ - uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or - @ref BLE_L2CAP_CID_INVALID if not present. */ - union { - ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ - ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ - ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ - ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ - ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ - ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ - ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_l2cap_evt_t; - -/** @} */ - -/**@addtogroup BLE_L2CAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set up an L2CAP channel. - * - * @details This function is used to: - * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. - * - Reply to a setup request of an L2CAP channel (if called in response to a - * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection - * Response packet to a peer. - * - * @note A call to this function will require the application to keep the SDU data buffer alive - * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or - * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} - * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: - * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP - * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * event when replying to a setup request of an L2CAP channel. - * - As output: local_cid for this channel. - * @param[in] p_params L2CAP channel parameters. - * - * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, - * see @ref ble_l2cap_conn_cfg_t::ch_count. - */ -SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, - sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); - -/**@brief Release an L2CAP channel. - * - * @details This sends a Disconnection Request packet to a peer. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * - * @retval ::NRF_SUCCESS Successfully queued request for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); - -/**@brief Receive an SDU on an L2CAP channel. - * - * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers - * for reception per L2CAP channel. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Buffer accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a - * @ref BLE_L2CAP_EVT_CH_RX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Transmit an SDU on an L2CAP channel. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for - * transmission per L2CAP channel. - * - * @note The application can keep track of the available credits for transmission by following - * the procedure below: - * - Store initial credits given by the peer in a variable. - * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Decrement the variable, which stores the currently available credits, by - * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns - * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Increment the variable, which stores the currently available credits, by additional - * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than - * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in - * @ref BLE_L2CAP_EVT_CH_SETUP event. - * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a - * @ref BLE_L2CAP_EVT_CH_TX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Advanced SDU reception flow control. - * - * @details Adjust the way the SoftDevice issues credits to the peer. - * This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set - * the value that will be used for newly created channels. - * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every - * time it starts using a new reception buffer. - * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will - * use if this function is not called. - * - If set to zero, the SoftDevice will stop issuing credits for new reception - * buffers the application provides or has provided. SDU reception that is - * currently ongoing will be allowed to complete. - * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be - * written by the SoftDevice with the number of credits that is or will be - * available to the peer. If the value written by the SoftDevice is 0 when - * credits parameter was set to 0, the peer will not be able to send more - * data until more credits are provided by calling this function again with - * credits > 0. This parameter is ignored when local_cid is set to - * @ref BLE_L2CAP_CID_INVALID. - * - * @note Application should take care when setting number of credits higher than default value. In - * this case the application must make sure that the SoftDevice always has reception buffers - * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have - * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic - * on the connection handle may be stalled until the SoftDevice again has an available - * reception buffer. This applies even if the application has used this call to set the - * credits back to default, or zero. - * - * @retval ::NRF_SUCCESS Flow control parameters accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, - sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_L2CAP_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_ranges.h b/variants/wio-tracker-wm1110/softdevice/ble_ranges.h deleted file mode 100644 index 2768e4996..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_ranges.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_ranges Module specific SVC, event and option number subranges - @{ - - @brief Definition of SVC, event and option number subranges for each API module. - - @note - SVCs, event and option numbers are split into subranges for each API module. - Each module receives its entire allocated range of SVC calls, whether implemented or not, - but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. - - Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, - rather than the last SVC function call actually defined and implemented. - - Specific SVC, event and option values are defined in each module's ble_.h file, - which defines names of each individual SVC code based on the range start value. -*/ - -#ifndef BLE_RANGES_H__ -#define BLE_RANGES_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ -#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ - -#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ -#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ - -#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ -#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ - -#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ -#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ - -#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ -#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ - -#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ - -#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ -#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ - -#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ -#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ - -#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ -#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ - -#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ -#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ - -#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ -#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ - -#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ - -#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ -#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ - -#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ -#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ - -#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ -#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ - -#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ -#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ - -#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ -#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ - -#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ -#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ - -#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ - -#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ -#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ - -#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ -#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ - -#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ -#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ - -#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ -#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ - -#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ -#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ - -#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ -#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ - -#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ -#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_RANGES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/ble_types.h b/variants/wio-tracker-wm1110/softdevice/ble_types.h deleted file mode 100644 index db3656cfd..000000000 --- a/variants/wio-tracker-wm1110/softdevice/ble_types.h +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_types Common types and macro definitions - @{ - - @brief Common types and macro definitions for the BLE SoftDevice. - */ - -#ifndef BLE_TYPES_H__ -#define BLE_TYPES_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_TYPES_DEFINES Defines - * @{ */ - -/** @defgroup BLE_CONN_HANDLES BLE Connection Handles - * @{ */ -#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ -#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ -/** @} */ - -/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs - * @{ */ -/* Generic UUIDs, applicable to all services */ -#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ -#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ -#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ -#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ -#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ -#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ -/* GATT specific UUIDs */ -#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ -#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ -/* GAP specific UUIDs */ -#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ -#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ -/** @} */ - -/** @defgroup BLE_UUID_TYPES Types of UUID - * @{ */ -#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ -#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ -#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ -/** @} */ - -/** @defgroup BLE_APPEARANCES Bluetooth Appearance values - * @note Retrieved from - * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml - * @{ */ -#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ -#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ -#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ -#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ -#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ -#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ -#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ -#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ -#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ -#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ -#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ -#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ -#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ -#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ -#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ -#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ -#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ -#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ -#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ -#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ -#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ -#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ -#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ -#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ -#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ -#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ -#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ -#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ -#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ -#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ -#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ -#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ -#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ -#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ -#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ -#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ - 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ - 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ -/** @} */ - -/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ -#define BLE_UUID_BLE_ASSIGN(instance, value) \ - do { \ - instance.type = BLE_UUID_TYPE_BLE; \ - instance.uuid = value; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ -#define BLE_UUID_COPY_PTR(dst, src) \ - do { \ - (dst)->type = (src)->type; \ - (dst)->uuid = (src)->uuid; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ -#define BLE_UUID_COPY_INST(dst, src) \ - do { \ - (dst).type = (src).type; \ - (dst).uuid = (src).uuid; \ - } while (0) - -/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) - -/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) - -/** @} */ - -/** @addtogroup BLE_TYPES_STRUCTURES Structures - * @{ */ - -/** @brief 128 bit UUID values. */ -typedef struct { - uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ -} ble_uuid128_t; - -/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ -typedef struct { - uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ - uint8_t - type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ -} ble_uuid_t; - -/**@brief Data structure. */ -typedef struct { - uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ - uint16_t len; /**< Length of the data buffer, in bytes. */ -} ble_data_t; - -/** @} */ -#ifdef __cplusplus -} -#endif - -#endif /* BLE_TYPES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h deleted file mode 100644 index 4e0bd752a..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf52/nrf_mbr.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_mbr_api Master Boot Record API - @{ - - @brief APIs for updating SoftDevice and BootLoader - -*/ - -#ifndef NRF_MBR_H__ -#define NRF_MBR_H__ - -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_MBR_DEFINES Defines - * @{ */ - -/**@brief MBR SVC Base number. */ -#define MBR_SVC_BASE (0x18) - -/**@brief Page size in words. */ -#define MBR_PAGE_SIZE_IN_WORDS (1024) - -/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. -This is the offset where the first byte of the SoftDevice hex file is written. */ -#define MBR_SIZE (0x1000) - -/** @brief Location (in the flash memory) of the bootloader address. */ -#define MBR_BOOTLOADER_ADDR (0xFF8) - -/** @brief Location (in UICR) of the bootloader address. */ -#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) - -/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ -#define MBR_PARAM_PAGE_ADDR (0xFFC) - -/** @brief Location (in UICR) of the address of the MBR parameter page. */ -#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) - -/** @} */ - -/** @addtogroup NRF_MBR_ENUMS Enumerations - * @{ */ - -/**@brief nRF Master Boot Record API SVC numbers. */ -enum NRF_MBR_SVCS { - SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ -}; - -/**@brief Possible values for ::sd_mbr_command_t.command */ -enum NRF_MBR_COMMANDS { - SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ - SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ - SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any - parameters in ::sd_mbr_command_t params.*/ - SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ - SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see - ::sd_mbr_command_vector_table_base_set_t*/ - SD_MBR_COMMAND_RESERVED, - SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see - ::sd_mbr_command_irq_forward_address_set_t*/ -}; - -/** @} */ - -/** @addtogroup NRF_MBR_TYPES Types - * @{ */ - -/**@brief This command copies part of a new SoftDevice - * - * The destination area is erased before copying. - * If dst is in the middle of a flash page, that whole flash page will be erased. - * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. - * - * The user of this function is responsible for setting the BPROT registers. - * - * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. - * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. - */ -typedef struct { - uint32_t *src; /**< Pointer to the source of data to be copied.*/ - uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ - uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ -} sd_mbr_command_copy_sd_t; - -/**@brief This command works like memcmp, but takes the length in words. - * - * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. - * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. - */ -typedef struct { - uint32_t *ptr1; /**< Pointer to block of memory. */ - uint32_t *ptr2; /**< Pointer to block of memory. */ - uint32_t len; /**< Number of 32 bit words to compare.*/ -} sd_mbr_command_compare_t; - -/**@brief This command copies a new BootLoader. - * - * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to - * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * The bootloader destination is erased by this function. - * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is - * not intended to be written. - * - * On success, this function will not return. It will start the new bootloader from reset-vector as normal. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. - * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ - uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ -} sd_mbr_command_copy_bl_t; - -/**@brief Change the address the MBR starts after a reset - * - * Once this function has been called, this address is where the MBR will start to forward - * interrupts to after a reset. - * - * To restore default forwarding, this function should be called with @ref address set to 0. If a - * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will - * be forwarded to the SoftDevice. - * - * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or - * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * On success, this function will not return. It will reset the device. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_vector_table_base_set_t; - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR - * - * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not - * change where the MBR starts after reset. - * - * @retval ::NRF_SUCCESS - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_irq_forward_address_set_t; - -/**@brief Input structure containing data used when calling ::sd_mbr_command - * - * Depending on what command value that is set, the corresponding params value type must also be - * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command - * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. - */ -typedef struct { - uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ - union { - sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ - sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ - sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ - sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ - sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ - } params; /**< Command parameters. */ -} sd_mbr_command_t; - -/** @} */ - -/** @addtogroup NRF_MBR_FUNCTIONS Functions - * @{ */ - -/**@brief Issue Master Boot Record commands - * - * Commands used when updating a SoftDevice and bootloader. - * - * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires - * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash - * page. The location of the flash page should be provided by the application in either - * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR - * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to - * store the command before reset. When an address is specified, the page it refers to must not be - * used by the application. If no address is provided by the application, i.e. both - * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use - * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. - * - * @param[in] param Pointer to a struct describing the command. - * - * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, - * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, - * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t - * - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided - * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. - */ -SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_MBR_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error.h b/variants/wio-tracker-wm1110/softdevice/nrf_error.h deleted file mode 100644 index fb2831e19..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_error.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_error SoftDevice Global Error Codes - @{ - - @brief Global Error definitions -*/ - -/* Header guard */ -#ifndef NRF_ERROR_H__ -#define NRF_ERROR_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions - * @{ */ -#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base -#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base -#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base -#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base -/** @} */ - -#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command -#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing -#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled -#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error -#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation -#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found -#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported -#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter -#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state -#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length -#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags -#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data -#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size -#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out -#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer -#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation -#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address -#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy -#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. -#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h deleted file mode 100644 index 2fd621057..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_error_sdm.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_sdm_api - @{ - @defgroup nrf_sdm_error SoftDevice Manager Error Codes - @{ - - @brief Error definitions for the SDM API -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SDM_H__ -#define NRF_ERROR_SDM_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. -#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ - (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having - ///< enabled SoftDevice interrupts). -#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ - (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SDM_H__ - -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h deleted file mode 100644 index cbd0ba8ac..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_error_soc.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_soc_api - @{ - @defgroup nrf_soc_error SoC Library Error Codes - @{ - - @brief Error definitions for the SoC library - -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SOC_H__ -#define NRF_ERROR_SOC_H__ - -#include "nrf_error.h" -#ifdef __cplusplus -extern "C" { -#endif - -/* Mutex Errors */ -#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken - -/* NVIC errors */ -#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available -#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed -#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return - -/* Power errors */ -#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown -#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown -#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return - -/* Rand errors */ -#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values - -/* PPI errors */ -#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel -#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SOC_H__ -/** - @} - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h b/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h deleted file mode 100644 index d4ab204d9..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_nvic.h +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_nvic_api SoftDevice NVIC API - * @{ - * - * @note In order to use this module, the following code has to be added to a .c file: - * \code - * nrf_nvic_state_t nrf_nvic_state = {0}; - * \endcode - * - * @note Definitions and declarations starting with __ (double underscore) in this header file are - * not intended for direct use by the application. - * - * @brief APIs for the accessing NVIC when using a SoftDevice. - * - */ - -#ifndef NRF_NVIC_H__ -#define NRF_NVIC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_NVIC_DEFINES Defines - * @{ */ - -/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions - * @{ */ - -#define __NRF_NVIC_NVMC_IRQn \ - (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ - number in the MDK. */ - -#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ - -/**@brief Interrupt priority levels used by the SoftDevice. */ -#define __NRF_NVIC_SD_IRQ_PRIOS \ - ((uint8_t)((1U << 0) /**< Priority level high .*/ \ - | (1U << 1) /**< Priority level medium. */ \ - | (1U << 4) /**< Priority level low. */ \ - )) - -/**@brief Interrupt priority levels available to the application. */ -#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ -#define __NRF_NVIC_SD_IRQS_0 \ - ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ - (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ - (1U << (uint32_t)SWI5_IRQn))) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ -#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) - -/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ -#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) - -/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ -#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) - -/**@} */ - -/**@} */ - -/**@addtogroup NRF_NVIC_VARIABLES Variables - * @{ */ - -/**@brief Type representing the state struct for the SoftDevice NVIC module. */ -typedef struct { - uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ - uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ -} nrf_nvic_state_t; - -/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an - * application source file. */ -extern nrf_nvic_state_t nrf_nvic_state; - -/**@} */ - -/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions - * @{ */ - -/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. - * - * @retval The value of PRIMASK prior to disabling the interrupts. - */ -__STATIC_INLINE int __sd_nvic_irq_disable(void); - -/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. - */ -__STATIC_INLINE void __sd_nvic_irq_enable(void); - -/**@brief Checks if IRQn is available to application - * @param[in] IRQn IRQ to check - * - * @retval 1 (true) if the IRQ to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); - -/**@brief Checks if priority is available to application - * @param[in] priority priority to check - * - * @retval 1 (true) if the priority to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); - -/**@} */ - -/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions - * @{ */ - -/**@brief Enable External Interrupt. - * @note Corresponds to NVIC_EnableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was enabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); - -/**@brief Disable External Interrupt. - * @note Corresponds to NVIC_DisableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was disabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); - -/**@brief Get Pending Interrupt. - * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. - * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. - * - * @retval ::NRF_SUCCESS The interrupt is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); - -/**@brief Set Pending Interrupt. - * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt is set pending. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); - -/**@brief Clear Pending Interrupt. - * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); - -/**@brief Set Interrupt Priority. - * @note Corresponds to NVIC_SetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * @pre Priority is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. - * @param[in] priority A valid IRQ priority for use by the application. - * - * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); - -/**@brief Get Interrupt Priority. - * @note Corresponds to NVIC_GetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. - * @param[out] p_priority Return value from NVIC_GetPriority. - * - * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); - -/**@brief System Reset. - * @note Corresponds to NVIC_SystemReset in CMSIS. - * - * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN - */ -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); - -/**@brief Enter critical region. - * - * @post Application interrupts will be disabled. - * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each - * execution context - * @sa sd_nvic_critical_region_exit - * - * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); - -/**@brief Exit critical region. - * - * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. - * @post If not in a nested critical region, the application interrupts will restored to the state before - * ::sd_nvic_critical_region_enter was called. - * - * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa - * sd_nvic_critical_region_enter. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); - -/**@} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE int __sd_nvic_irq_disable(void) -{ - int pm = __get_PRIMASK(); - __disable_irq(); - return pm; -} - -__STATIC_INLINE void __sd_nvic_irq_enable(void) -{ - __enable_irq(); -} - -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) -{ - if (IRQn < 32) { - return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; - } else if (IRQn < 64) { - return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; - } else { - return 1; - } -} - -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) -{ - if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { - return 0; - } - return 1; -} - -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= - (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); - } else { - NVIC_EnableIRQ(IRQn); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); - } else { - NVIC_DisableIRQ(IRQn); - } - - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_pending_irq = NVIC_GetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_SetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_ClearPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (!__sd_nvic_is_app_accessible_priority(priority)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - NVIC_SetPriority(IRQn, (uint32_t)priority); - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) -{ - NVIC_SystemReset(); - return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) -{ - int was_masked = __sd_nvic_irq_disable(); - if (!nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__cr_flag = 1; - nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); - NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; - nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); - NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; - *p_is_nested_critical_region = 0; - } else { - *p_is_nested_critical_region = 1; - } - if (!was_masked) { - __sd_nvic_irq_enable(); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) -{ - if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { - int was_masked = __sd_nvic_irq_disable(); - NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; - NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; - nrf_nvic_state.__cr_flag = 0; - if (!was_masked) { - __sd_nvic_irq_enable(); - } - } - - return NRF_SUCCESS; -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif - -#endif // NRF_NVIC_H__ - -/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h b/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h deleted file mode 100644 index 2786a86a4..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_sdm.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_sdm_api SoftDevice Manager API - @{ - - @brief APIs for SoftDevice management. - -*/ - -#ifndef NRF_SDM_H__ -#define NRF_SDM_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_sdm.h" -#include "nrf_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ -#ifdef NRFSOC_DOXYGEN -/// Declared in nrf_mbr.h -#define MBR_SIZE 0 -#warning test -#endif - -/** @brief The major version for the SoftDevice binary distributed with this header file. */ -#define SD_MAJOR_VERSION (7) - -/** @brief The minor version for the SoftDevice binary distributed with this header file. */ -#define SD_MINOR_VERSION (3) - -/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ -#define SD_BUGFIX_VERSION (0) - -/** @brief The SoftDevice variant of this firmware. */ -#define SD_VARIANT_ID 140 - -/** @brief The full version number for the SoftDevice binary this header file was distributed - * with, as a decimal number in the form Mmmmbbb, where: - * - M is major version (one or more digits) - * - mmm is minor version (three digits) - * - bbb is bugfix version (three digits). */ -#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) - -/** @brief SoftDevice Manager SVC Base number. */ -#define SDM_SVC_BASE 0x10 - -/** @brief SoftDevice unique string size in bytes. */ -#define SD_UNIQUE_STR_SIZE 20 - -/** @brief Invalid info field. Returned when an info field does not exist. */ -#define SDM_INFO_FIELD_INVALID (0) - -/** @brief Defines the SoftDevice Information Structure location (address) as an offset from -the start of the SoftDevice (without MBR)*/ -#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) - -/** @brief Defines the absolute SoftDevice Information Structure location (address) when the - * SoftDevice is installed just above the MBR (the usual case). */ -#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) - -/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the - * SoftDevice base address. The size value is of type uint8_t. */ -#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) - -/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. - * The size value is of type uint32_t. */ -#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) - -/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value - * is of type uint16_t. */ -#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) - -/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID - * is of type uint32_t. */ -#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) - -/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in - * the same format as @ref SD_VERSION, stored as an uint32_t. */ -#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) - -/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. - * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. - */ -#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) - -/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value - * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is - * installed just above the MBR (the usual case). */ -#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base - * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above - * the MBR (the usual case). */ -#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) - -/** @brief Defines the amount of flash that is used by the SoftDevice. - * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed - * just above the MBR (the usual case). - */ -#define SD_FLASH_SIZE 0x26000 - -/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual - * case). */ -#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the - * usual case). */ -#define SD_ID_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_VERSION_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges - * @{ */ -#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ -#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ -/**@} */ - -/**@defgroup NRF_FAULT_IDS Fault ID types - * @{ */ -#define NRF_FAULT_ID_SD_ASSERT \ - (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ -#define NRF_FAULT_ID_APP_MEMACC \ - (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ - in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ - register violation the info parameter will contain the sub-region number of \ - PREGION[0], on whose address range the disallowed write access caused the \ - memory access fault. */ -/**@} */ - -/** @} */ - -/** @addtogroup NRF_SDM_ENUMS Enumerations - * @{ */ - -/**@brief nRF SoftDevice Manager API SVC numbers. */ -enum NRF_SD_SVCS { - SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ - SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ - SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ - SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ - SVC_SDM_LAST /**< Placeholder for last SDM SVC */ -}; - -/** @} */ - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ - -/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy - * @{ */ - -#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ -#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ -#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ -#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ -#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ -#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ -#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ -#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ -#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ -#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ -#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ -#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ - -/** @} */ - -/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources - * @{ */ - -#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ -#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ -#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ - -/** @} */ - -/** @} */ - -/** @addtogroup NRF_SDM_TYPES Types - * @{ */ - -/**@brief Type representing LFCLK oscillator source. */ -typedef struct { - uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ - uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second - units (nRF52: 1-32). - @note To avoid excessive clock drift, 0.5 degrees Celsius is the - maximum temperature change allowed in one calibration timer - interval. The interval should be selected to ensure this. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ - uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration - intervals) the RC oscillator shall be calibrated if the temperature - hasn't changed. - 0: Always calibrate even if the temperature hasn't changed. - 1: Only calibrate if the temperature has changed (legacy - nRF51 only). - 2-33: Check the temperature and only calibrate if it has changed, - however calibration will take place every rc_temp_ctiv - intervals in any case. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. - - @note For nRF52, the application must ensure calibration at least once - every 8 seconds to ensure +/-500 ppm clock stability. The - recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is - rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at - least once every 8 seconds and for temperature changes of 0.5 - degrees Celsius every 4 seconds. See the Product Specification - for the nRF52 device being used for more information.*/ - uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing - windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ -} nrf_clock_lf_cfg_t; - -/**@brief Fault Handler type. - * - * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. - * The protocol stack will be in an undefined state when this happens and the only way to recover will be to - * perform a reset, using e.g. CMSIS NVIC_SystemReset(). - * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). - * - * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. - * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may - * continously transmit packets. - * - * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. - * - * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. - * @param[in] pc The program counter of the instruction that triggered the fault. - * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. - * - * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time - * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the - * one that triggered the fault. - */ -typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); - -/** @} */ - -/** @addtogroup NRF_SDM_FUNCTIONS Functions - * @{ */ - -/**@brief Enables the SoftDevice and by extension the protocol stack. - * - * @note Some care must be taken if a low frequency clock source is already running when calling this function: - * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new - * clock source will be started. - * - * @note This function has no effect when returning with an error. - * - * @post If return code is ::NRF_SUCCESS - * - SoC library and protocol stack APIs are made available. - * - A portion of RAM will be unavailable (see relevant SDS documentation). - * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). - * - Interrupts will not arrive from protected peripherals or interrupts. - * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. - * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). - * - Chosen low frequency clock source will be running. - * - * @param p_clock_lf_cfg Low frequency clock source and accuracy. - If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 - In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to - the actual characteristics of your XTAL clock. - * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. - * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has - an illegal priority level. - * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. - */ -SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, - sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); - -/**@brief Disables the SoftDevice and by extension the protocol stack. - * - * Idempotent function to disable the SoftDevice. - * - * @post SoC library and protocol stack APIs are made unavailable. - * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). - * @post All peripherals used by the SoftDevice will be reset to default values. - * @post All of RAM become available. - * @post All interrupts are forwarded to the application. - * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); - -/**@brief Check if the SoftDevice is enabled. - * - * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice - * - * This function is only intended to be called when a bootloader is enabled. - * - * @param[in] address The base address of the interrupt vector table for forwarded interrupts. - - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SDM_H__ - -/** - @} -*/ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_soc.h b/variants/wio-tracker-wm1110/softdevice/nrf_soc.h deleted file mode 100644 index c649ca836..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_soc.h +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_soc_api SoC Library API - * @{ - * - * @brief APIs for the SoC library. - * - */ - -#ifndef NRF_SOC_H__ -#define NRF_SOC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_SOC_DEFINES Defines - * @{ */ - -/**@brief The number of the lowest SVC number reserved for the SoC library. */ -#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ -#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ - -/**@brief Guaranteed time for application to process radio inactive notification. */ -#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) - -/**@brief The minimum allowed timeslot extension time. */ -#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) - -/**@brief The maximum processing time to handle a timeslot extension. */ -#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) - -/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ -#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) - -#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ -#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ -#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ - -#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ -#define SD_EVT_IRQHandler \ - (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ - The default interrupt priority for this handler is set to 6 */ -#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ -#define RADIO_NOTIFICATION_IRQHandler \ - (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ - The default interrupt priority for this handler is set to 6 */ -#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ -#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ - -#define NRF_RADIO_DISTANCE_MAX_US \ - (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ - nrf_radio_request_normal_t) in the request. */ - -#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ - (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ - -#define NRF_RADIO_START_JITTER_US \ - (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ - ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ - (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) - -/**@} */ - -/**@addtogroup NRF_SOC_ENUMS Enumerations - * @{ */ - -/**@brief The SVC numbers used by the SVC functions in the SoC library. */ -enum NRF_SOC_SVCS { - SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, - SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, - SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, - SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, - SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, - SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, - SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, - SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, - SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, - SD_FLASH_WRITE = SOC_SVC_BASE + 9, - SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, - SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, - SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, - SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, - SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, - SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, - SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, - SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, - SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, - SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, - SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, - SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, - SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, - SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, - SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, - SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, - SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, - SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, - SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, - SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, - SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, - SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, - SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, - SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, - SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, - SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, - SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, - SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, - SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, - SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, - SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, - SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, - SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, - SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, - SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, - SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, - SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, - SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, - SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 -}; - -/**@brief Possible values of a ::nrf_mutex_t. */ -enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; - -/**@brief Power modes. */ -enum NRF_POWER_MODES { - NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ - NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ -}; - -/**@brief Power failure thresholds */ -enum NRF_POWER_THRESHOLDS { - NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ -}; - -/**@brief Power failure thresholds for high voltage */ -enum NRF_POWER_THRESHOLDVDDHS { - NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ -}; - -/**@brief DC/DC converter modes. */ -enum NRF_POWER_DCDC_MODES { - NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ - NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ -}; - -/**@brief Radio notification distances. */ -enum NRF_RADIO_NOTIFICATION_DISTANCES { - NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ - NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ -}; - -/**@brief Radio notification types. */ -enum NRF_RADIO_NOTIFICATION_TYPES { - NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and - disabled. */ -}; - -/**@brief The Radio signal callback types. */ -enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { - NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ -}; - -/**@brief The actions requested by the signal callback. - * - * This code gives the SOC instructions about what action to take when the signal callback has - * returned. - */ -enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { - NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current - timeslot. Maximum execution time for this action: - @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. - This action must be started at least - @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before - the end of the timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ -}; - -/**@brief Radio timeslot high frequency clock source configuration. */ -enum NRF_RADIO_HFCLK_CFG { - NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the - external crystal for the whole duration of the timeslot. This should be the - preferred option for events that use the radio or require high timing accuracy. - @note The SoftDevice will automatically turn on and off the external crystal, - at the beginning and end of the timeslot, respectively. The crystal may also - intentionally be left running after the timeslot, in cases where it is needed - by the SoftDevice shortly after the end of the timeslot. */ - NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. - The RC oscillator may be the clock source in part or for the whole duration of the - timeslot. The RC oscillator's accuracy must therefore be taken into consideration. - @note If the application will use the radio peripheral in timeslots with this - configuration, it must make sure that the crystal is running and stable before - starting the radio. */ -}; - -/**@brief Radio timeslot priorities. */ -enum NRF_RADIO_PRIORITY { - NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ - NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ -}; - -/**@brief Radio timeslot request type. */ -enum NRF_RADIO_REQUEST_TYPE { - NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first - request in a session. */ - NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ -}; - -/**@brief SoC Events. */ -enum NRF_SOC_EVTS { - NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ - NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ - NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ - NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ - NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ - NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ - NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was - invalid. */ - NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ - NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ - NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ - NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ - NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ - NRF_EVT_NUMBER_OF_EVTS -}; - -/**@} */ - -/**@addtogroup NRF_SOC_STRUCTURES Structures - * @{ */ - -/**@brief Represents a mutex for use with the nrf_mutex functions. - * @note Accessing the value directly is not safe, use the mutex functions! - */ -typedef volatile uint8_t nrf_mutex_t; - -/**@brief Parameters for a request for a timeslot as early as possible. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ - uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref - NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ -} nrf_radio_request_earliest_t; - -/**@brief Parameters for a normal radio timeslot request. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US - microseconds). */ - uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ -} nrf_radio_request_normal_t; - -/**@brief Radio timeslot request parameters. */ -typedef struct { - uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ - union { - nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ - nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ - } params; /**< Parameter union. */ -} nrf_radio_request_t; - -/**@brief Return parameters of the radio timeslot signal callback. */ -typedef struct { - uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref - NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ - union { - struct { - nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ - } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ - struct { - uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref - NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ - } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ - } params; /**< Parameter union. */ -} nrf_radio_signal_callback_return_param_t; - -/**@brief The radio timeslot signal callback type. - * - * @note In case of invalid return parameters, the radio timeslot will automatically end - * immediately after returning from the signal callback and the - * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. - * @note The returned struct pointer must remain valid after the signal callback - * function returns. For instance, this means that it must not point to a stack variable. - * - * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. - * - * @return Pointer to structure containing action requested by the application. - */ -typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); - -/**@brief AES ECB parameter typedefs */ -typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ -typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ -typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ - -/**@brief AES ECB data structure */ -typedef struct { - soc_ecb_key_t key; /**< Encryption key. */ - soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ - soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ -} nrf_ecb_hal_data_t; - -/**@brief AES ECB block. Used to provide multiple blocks in a single call - to @ref sd_ecb_blocks_encrypt.*/ -typedef struct { - soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ - soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ - soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ -} nrf_ecb_hal_data_block_t; - -/**@} */ - -/**@addtogroup NRF_SOC_FUNCTIONS Functions - * @{ */ - -/**@brief Initialize a mutex. - * - * @param[in] p_mutex Pointer to the mutex to initialize. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); - -/**@brief Attempt to acquire a mutex. - * - * @param[in] p_mutex Pointer to the mutex to acquire. - * - * @retval ::NRF_SUCCESS The mutex was successfully acquired. - * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. - */ -SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); - -/**@brief Release a mutex. - * - * @param[in] p_mutex Pointer to the mutex to release. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); - -/**@brief Query the capacity of the application random pool. - * - * @param[out] p_pool_capacity The capacity of the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); - -/**@brief Get number of random bytes available to the application. - * - * @param[out] p_bytes_available The number of bytes currently available in the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); - -/**@brief Get random bytes from the application pool. - * - * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. - * @param[in] length Number of bytes to take from pool and place in p_buff. - * - * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. - * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes - * available. - */ -SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); - -/**@brief Gets the reset reason register. - * - * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); - -/**@brief Clears the bits of the reset reason register. - * - * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); - -/**@brief Sets the power mode when in CPU sleep. - * - * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait - * - * @retval ::NRF_SUCCESS The power mode was set. - * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. - */ -SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); - -/**@brief Puts the chip in System OFF mode. - * - * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN - */ -SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); - -/**@brief Enables or disables the power-fail comparator. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); - -/**@brief Enables or disables the USB power ready event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); - -/**@brief Enables or disables the power USB-detected event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); - -/**@brief Enables or disables the power USB-removed event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); - -/**@brief Get USB supply status register content. - * - * @param[out] usbregstatus The content of USBREGSTATUS register. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); - -/**@brief Sets the power failure comparator threshold value. - * - * @note: Power failure comparator threshold setting. This setting applies both for normal voltage - * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to - * VDDH only). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); - -/**@brief Sets the power failure comparator threshold value for high voltage. - * - * @note: Power failure comparator threshold setting for high voltage mode (supply connected to - * VDDH only). This setting does not apply for normal voltage mode (supply connected to both - * VDD and VDDH). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. - * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. - * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); - -/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. - * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); - -/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be set in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[out] p_gpregret Contents of the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). - * - * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. - */ -SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). - * - * For more details on the REG0 stage, please see product specification. - * - * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. - */ -SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); - -/**@brief Request the high frequency crystal oscillator. - * - * Will start the high frequency crystal oscillator, the startup time of the crystal varies - * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_release - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); - -/**@brief Releases the high frequency crystal oscillator. - * - * Will stop the high frequency crystal oscillator, this happens immediately. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_request - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); - -/**@brief Checks if the high frequency crystal oscillator is running. - * - * @see sd_clock_hfclk_request - * @see sd_clock_hfclk_release - * - * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); - -/**@brief Waits for an application event. - * - * An application event is either an application interrupt or a pended interrupt when the interrupt - * is disabled. - * - * When the application waits for an application event by calling this function, an interrupt that - * is enabled will be taken immediately on pending since this function will wait in thread mode, - * then the execution will return in the application's main thread. - * - * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M - * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets - * pended, this function will return to the application's main thread. - * - * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ - * in order to sleep using this function. This is only necessary for disabled interrupts, as - * the interrupt handler will clear the pending flag automatically for enabled interrupts. - * - * @note If an application interrupt has happened since the last time sd_app_evt_wait was - * called this function will return immediately and not go to sleep. This is to avoid race - * conditions that can occur when a flag is updated in the interrupt handler and processed - * in the main loop. - * - * @post An application interrupt has happened or a interrupt pending flag is set. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); - -/**@brief Get PPI channel enable register contents. - * - * @param[out] p_channel_enable The contents of the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); - -/**@brief Set PPI channel enable register. - * - * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); - -/**@brief Clear PPI channel enable register. - * - * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); - -/**@brief Assign endpoints to a PPI channel. - * - * @param[in] channel_num Number of the PPI channel to assign. - * @param[in] evt_endpoint Event endpoint of the PPI channel. - * @param[in] task_endpoint Task endpoint of the PPI channel. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, - sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); - -/**@brief Task to enable a channel group. - * - * @param[in] group_num Number of the channel group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); - -/**@brief Task to disable a channel group. - * - * @param[in] group_num Number of the PPI group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); - -/**@brief Assign PPI channels to a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[in] channel_msk Mask of the channels to assign to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); - -/**@brief Gets the PPI channels of a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[out] p_channel_msk Mask of the channels assigned to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); - -/**@brief Configures the Radio Notification signal. - * - * @note - * - The notification signal latency depends on the interrupt priority settings of SWI used - * for notification signal. - * - To ensure that the radio notification signal behaves in a consistent way, the radio - * notifications must be configured when there is no protocol stack or other SoftDevice - * activity in progress. It is recommended that the radio notification signal is - * configured directly after the SoftDevice has been enabled. - * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice - * will interrupt the application to do Radio Event preparation. - * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have - * to shorten the connection events to have time for the Radio Notification signals. - * - * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio - * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is - * recommended (but not required) to be used with - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. - * - * @param[in] distance Distance between the notification signal and start of radio activity, see @ref - * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or - * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. - * - * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. - * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all - * running activities and retry. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); - -/**@brief Encrypts a block according to the specified parameters. - * - * 128-bit AES encryption. - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input - * parameters and one output parameter). - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); - -/**@brief Encrypts multiple data blocks provided as an array of data block structures. - * - * @details: Performs 128-bit AES encryption on multiple data blocks - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in] block_count Count of blocks in the p_data_blocks array. - * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of - * @ref nrf_ecb_hal_data_block_t structures. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); - -/**@brief Gets any pending events generated by the SoC API. - * - * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. - * - * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. - * - * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. - * @retval ::NRF_ERROR_NOT_FOUND No pending events. - */ -SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); - -/**@brief Get the temperature measured on the chip - * - * This function will block until the temperature measurement is done. - * It takes around 50 us from call to return. - * - * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. - * - * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp - */ -SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); - -/**@brief Flash Write - * - * Commands to write a buffer to flash - * - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * write has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS - * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. - * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is - * protected. - * - * - * @param[in] p_dst Pointer to start of flash location to be written. - * @param[in] p_src Pointer to buffer with data to be written. - * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one - * flash page. See the device's Product Specification for details. - * - * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. - * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); - -/**@brief Flash Erase page - * - * Commands to erase a flash page - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * erase has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is - * protected. - * - * - * @param[in] page_number Page number of the page to erase - * - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); - -/**@brief Opens a session for radio timeslot requests. - * - * @note Only one session can be open at a time. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot - * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed - * by the application. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 - * interrupt occurs. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO - * interrupt occurs. - * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This - * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). - * - * @param[in] p_radio_signal_callback The signal callback. - * - * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. - * @retval ::NRF_ERROR_BUSY If session cannot be opened. - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); - -/**@brief Closes a session for radio timeslot requests. - * - * @note Any current radio timeslot will be finished before the session is closed. - * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. - * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED - * event is received. - * - * @retval ::NRF_ERROR_FORBIDDEN If session not opened. - * @retval ::NRF_ERROR_BUSY If session is currently being closed. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); - -/**@brief Requests a radio timeslot. - * - * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST - * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref - * NRF_RADIO_REQ_TYPE_EARLIEST. - * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by - * p_request->distance_us and is given relative to the start of the previous timeslot. - * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. - * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this - * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. - * The application may then try to schedule the first radio timeslot again. - * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). - * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. - * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. - * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the - * specified radio timeslot start, but this does not affect the actual start time of the timeslot. - * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency - * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is - * guaranteed to be clocked from the external crystal. - * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral - * during the radio timeslot. - * - * @param[in] p_request Pointer to the request parameters. - * - * @retval ::NRF_ERROR_FORBIDDEN Either: - * - The session is not open. - * - The session is not IDLE. - * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. - * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a - * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. - * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); - -/**@brief Write register protected by the SoftDevice - * - * This function writes to a register that is write-protected by the SoftDevice. Please refer to your - * SoftDevice Specification for more details about which registers that are protected by SoftDevice. - * This function can write to the following protected peripheral: - * - ACL - * - * @note Protected registers may be read directly. - * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in - * the register has not changed. See the Product Specification for more details about register - * properties. - * - * @param[in] p_register Pointer to register to be written. - * @param[in] value Value to be written to the register. - * - * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. - * @retval ::NRF_SUCCESS Value successfully written to register. - * - */ -SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); - -/**@} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SOC_H__ - -/**@} */ diff --git a/variants/wio-tracker-wm1110/softdevice/nrf_svc.h b/variants/wio-tracker-wm1110/softdevice/nrf_svc.h deleted file mode 100644 index 1de44656f..000000000 --- a/variants/wio-tracker-wm1110/softdevice/nrf_svc.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef NRF_SVC__ -#define NRF_SVC__ - -#include "stdint.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @brief Supervisor call declaration. - * - * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. - * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. - * - * @param[in] number The SVC number to be used. - * @param[in] return_type The return type of the SVC function. - * @param[in] signature Function signature. The function can have at most four arguments. - */ - -#ifdef SVCALL_AS_NORMAL_FUNCTION -#define SVCALL(number, return_type, signature) return_type signature -#else - -#ifndef SVCALL -#if defined(__CC_ARM) -#define SVCALL(number, return_type, signature) return_type __svc(number) signature -#elif defined(__GNUC__) -#ifdef __cplusplus -#define GCC_CAST_CPP (uint16_t) -#else -#define GCC_CAST_CPP -#endif -#define SVCALL(number, return_type, signature) \ - _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ - __attribute__((unused)) static return_type signature \ - { \ - __asm("svc %0\n" \ - "bx r14" \ - : \ - : "I"(GCC_CAST_CPP number) \ - : "r0"); \ - } \ - _Pragma("GCC diagnostic pop") - -#elif defined(__ICCARM__) -#define PRAGMA(x) _Pragma(#x) -#define SVCALL(number, return_type, signature) \ - PRAGMA(swi_number = (number)) \ - __swi return_type signature; -#else -#define SVCALL(number, return_type, signature) return_type signature -#endif -#endif // SVCALL - -#endif // SVCALL_AS_NORMAL_FUNCTION - -#ifdef __cplusplus -} -#endif -#endif // NRF_SVC__ diff --git a/version.properties b/version.properties index 1cb93ac2b..c9336d539 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 15 +build = 16 From a04de8c6b3c23d20536f635586c11361d198dcce Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 14 Jul 2024 06:27:16 -0500 Subject: [PATCH 0712/1377] Add PaxCounter to the mix --- src/modules/esp32/PaxcounterModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index a8fe5c4c5..895328234 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -80,7 +80,7 @@ int32_t PaxcounterModule::runOnce() firstTime = false; LOG_DEBUG("Paxcounter starting up with interval of %d seconds\n", Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, - default_broadcast_interval_secs)); + default_telemetry_broadcast_interval_secs)); struct libpax_config_t configuration; libpax_default_config(&configuration); From 0f5fdfbab780e3dcdb3a957efd142d9440f52cf3 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 15 Jul 2024 20:11:37 +0800 Subject: [PATCH 0713/1377] Make mergehex executable. (#4290) Previously, we used sudo and chmod to make mergehex executable in our build script. This change attempts to set the executable bit using git properties and remove the dependence on elevated permissions. --- bin/build-nrf52.sh | 1 - bin/mergehex | Bin 2 files changed, 1 deletion(-) mode change 100644 => 100755 bin/mergehex diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index cf4ca60cb..c0658dad9 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -35,7 +35,6 @@ SRCHEX=.pio/build/$1/firmware.hex # if WM1110 target, merge hex with softdevice 7.3.0 if (echo $1 | grep -q "wio-sdk-wm1110"); then echo "Merging with softdevice" - sudo chmod +x ./bin/mergehex bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex SRCHEX=.pio/build/$1/$basename.hex bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 diff --git a/bin/mergehex b/bin/mergehex old mode 100644 new mode 100755 From 9db3552e5a069c3edb8b4ca9538437ab1160ae2c Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 16 Jul 2024 19:31:25 +0800 Subject: [PATCH 0714/1377] Remove softdevice folder from wio-sdk-wm1110 (#4295) Recently, softdevice was moved to platform/nrf52. We missed deleting this one. --- variants/wio-sdk-wm1110/softdevice/ble.h | 652 ---- variants/wio-sdk-wm1110/softdevice/ble_err.h | 92 - variants/wio-sdk-wm1110/softdevice/ble_gap.h | 2895 ----------------- variants/wio-sdk-wm1110/softdevice/ble_gatt.h | 232 -- .../wio-sdk-wm1110/softdevice/ble_gattc.h | 764 ----- .../wio-sdk-wm1110/softdevice/ble_gatts.h | 904 ----- variants/wio-sdk-wm1110/softdevice/ble_hci.h | 135 - .../wio-sdk-wm1110/softdevice/ble_l2cap.h | 495 --- .../wio-sdk-wm1110/softdevice/ble_ranges.h | 149 - .../wio-sdk-wm1110/softdevice/ble_types.h | 217 -- .../wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h | 259 -- .../wio-sdk-wm1110/softdevice/nrf_error.h | 90 - .../wio-sdk-wm1110/softdevice/nrf_error_sdm.h | 73 - .../wio-sdk-wm1110/softdevice/nrf_error_soc.h | 85 - variants/wio-sdk-wm1110/softdevice/nrf_nvic.h | 449 --- variants/wio-sdk-wm1110/softdevice/nrf_sdm.h | 380 --- variants/wio-sdk-wm1110/softdevice/nrf_soc.h | 1046 ------ variants/wio-sdk-wm1110/softdevice/nrf_svc.h | 98 - 18 files changed, 9015 deletions(-) delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_err.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gap.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatt.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gattc.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatts.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_hci.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_l2cap.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_ranges.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_types.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_nvic.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_sdm.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_soc.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_svc.h diff --git a/variants/wio-sdk-wm1110/softdevice/ble.h b/variants/wio-sdk-wm1110/softdevice/ble.h deleted file mode 100644 index 177b436ad..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble.h +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON BLE SoftDevice Common - @{ - @defgroup ble_api Events, type definitions and API calls - @{ - - @brief Module independent events, type definitions and API calls for the BLE SoftDevice. - - */ - -#ifndef BLE_H__ -#define BLE_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_gattc.h" -#include "ble_gatts.h" -#include "ble_l2cap.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief Common API SVC numbers. - */ -enum BLE_COMMON_SVCS { - SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ - SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ - SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ - SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ - SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ - SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ - SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ - SD_BLE_OPT_SET, /**< Set a BLE option. */ - SD_BLE_OPT_GET, /**< Get a BLE option. */ - SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ - SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ -}; - -/** - * @brief BLE Module Independent Event IDs. - */ -enum BLE_COMMON_EVTS { - BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t - \n Reply with @ref sd_ble_user_mem_reply. */ - BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ -}; - -/**@brief BLE Connection Configuration IDs. - * - * IDs that uniquely identify a connection configuration. - */ -enum BLE_CONN_CFGS { - BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ - BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ - BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ - BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ - BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ -}; - -/**@brief BLE Common Configuration IDs. - * - * IDs that uniquely identify a common configuration. - */ -enum BLE_COMMON_CFGS { - BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ -}; - -/**@brief Common Option IDs. - * IDs that uniquely identify a common option. - */ -enum BLE_COMMON_OPTS { - BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ - BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ - BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ -}; - -/** @} */ - -/** @addtogroup BLE_COMMON_DEFINES Defines - * @{ */ - -/** @brief Required pointer alignment for BLE Events. - */ -#define BLE_EVT_PTR_ALIGNMENT 4 - -/** @brief Leaves the maximum of the two arguments. - */ -#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) - -/** @brief Maximum possible length for BLE Events. - * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a - * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. - */ -#define BLE_EVT_LEN_MAX(ATT_MTU) \ - (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) - -/** @defgroup BLE_USER_MEM_TYPES User Memory Types - * @{ */ -#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ -#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ -/** @} */ - -/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts - * @{ - */ -#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ -#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ -/** @} */ - -/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. - * @{ - */ -#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ - -/** @} */ - -/** @} */ - -/** @addtogroup BLE_COMMON_STRUCTURES Structures - * @{ */ - -/**@brief User Memory Block. */ -typedef struct { - uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ - uint16_t len; /**< Length in bytes of the user memory block. */ -} ble_user_mem_block_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ -} ble_evt_user_mem_request_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ - ble_user_mem_block_t mem_block; /**< User memory block */ -} ble_evt_user_mem_release_t; - -/**@brief Event structure for events not associated with a specific function module. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ - union { - ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ - ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ - } params; /**< Event parameter union. */ -} ble_common_evt_t; - -/**@brief BLE Event header. */ -typedef struct { - uint16_t evt_id; /**< Value from a BLE__EVT series. */ - uint16_t evt_len; /**< Length in octets including this header. */ -} ble_evt_hdr_t; - -/**@brief Common BLE Event type, wrapping the module specific event reports. */ -typedef struct { - ble_evt_hdr_t header; /**< Event header. */ - union { - ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ - ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ - ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ - ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ - ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ - } evt; /**< Event union. */ -} ble_evt_t; - -/** - * @brief Version Information. - */ -typedef struct { - uint8_t version_number; /**< Link Layer Version number. See - https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ - uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) - (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ - uint16_t - subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ -} ble_version_t; - -/** - * @brief Configuration parameters for the PA and LNA. - */ -typedef struct { - uint8_t enable : 1; /**< Enable toggling for this amplifier */ - uint8_t active_high : 1; /**< Set the pin to be active high */ - uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ -} ble_pa_lna_cfg_t; - -/** - * @brief PA & LNA GPIO toggle configuration - * - * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or - * a low noise amplifier. - * - * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided - * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences - * and must be avoided by the application. - */ -typedef struct { - ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ - ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ - - uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ - uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ - uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ -} ble_common_opt_pa_lna_t; - -/** - * @brief Configuration of extended BLE connection events. - * - * When enabled the SoftDevice will dynamically extend the connection event when possible. - * - * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. - * The connection event can be extended if there is time to send another packet pair before the start of the next connection - * interval, and if there are no conflicts with other BLE roles requesting radio time. - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ -} ble_common_opt_conn_evt_ext_t; - -/** - * @brief Enable/disable extended RC calibration. - * - * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice - * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets - * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. - * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When - * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the - * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. - * - * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the - * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ -} ble_common_opt_extended_rc_cal_t; - -/**@brief Option structure for common options. */ -typedef union { - ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ - ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ - ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ -} ble_common_opt_t; - -/**@brief Common BLE Option type, wrapping the module specific options. */ -typedef union { - ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ - ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ - ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ -} ble_opt_t; - -/**@brief BLE connection configuration type, wrapping the module specific configurations, set with - * @ref sd_ble_cfg_set. - * - * @note Connection configurations don't have to be set. - * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, - * the default connection configuration will be automatically added for the remaining connections. - * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in - * place of @ref ble_conn_cfg_t::conn_cfg_tag. - * - * @sa sd_ble_gap_adv_start() - * @sa sd_ble_gap_connect() - * - * @mscs - * @mmsc{@ref BLE_CONN_CFG} - * @endmscs - - */ -typedef struct { - uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the - @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls - to select this configuration when creating a connection. - Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ - union { - ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ - ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ - ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ - ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ - ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ - } params; /**< Connection configuration union. */ -} ble_conn_cfg_t; - -/** - * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. - */ -typedef struct { - uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. - Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is - @ref BLE_UUID_VS_COUNT_MAX. */ -} ble_common_cfg_vs_uuid_t; - -/**@brief Common BLE Configuration type, wrapping the common configurations. */ -typedef union { - ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ -} ble_common_cfg_t; - -/**@brief BLE Configuration type, wrapping the module specific configurations. */ -typedef union { - ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ - ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ - ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ - ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ -} ble_cfg_t; - -/** @} */ - -/** @addtogroup BLE_COMMON_FUNCTIONS Functions - * @{ */ - -/**@brief Enable the BLE stack - * - * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the - * application RAM region (APP_RAM_BASE). On return, this will - * contain the minimum start address of the application RAM region - * required by the SoftDevice for this configuration. - * @warning After this call, the SoftDevice may generate several events. The list of events provided - * below require the application to initiate a SoftDevice API call. The corresponding API call - * is referenced in the event documentation. - * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop - * communicating with the peer device. - * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST - * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST - * - @ref BLE_GAP_EVT_SEC_REQUEST - * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST - * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST - * - @ref BLE_EVT_USER_MEM_REQUEST - * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located - * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between - * APP_RAM_BASE and the start of the call stack. - * - * @details This call initializes the BLE stack, no BLE related function other than @ref - * sd_ble_cfg_set can be called before this one. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: - * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not - * large enough to fit this configuration's memory requirement. Check *p_app_ram_base - * and set the start address of the application RAM region accordingly. - * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which - * is currently not supported. - * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. - */ -SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); - -/**@brief Add configurations for the BLE stack - * - * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref - * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. - * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. - * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). - * See @ref sd_ble_enable for details about APP_RAM_BASE. - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note If a configuration is set more than once, the last one set is the one that takes effect on - * @ref sd_ble_enable. - * - * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default - * configuration. - * - * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref - * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref - * sd_ble_enable). - * - * @note Error codes for the configurations are described in the configuration structs. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The configuration has been added successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. - * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not - * large enough to fit this configuration's memory requirement. - */ -SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); - -/**@brief Get an event from the pending events queue. - * - * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. - * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. - * The buffer should be interpreted as a @ref ble_evt_t struct. - * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. - * - * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that - * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. - * The application is free to choose whether to call this function from thread mode (main context) or directly from the - * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher - * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) - * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so - * could potentially leave events in the internal queue without the application being aware of this fact. - * - * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to - * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, - * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. - * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length - * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: - * - * \code - * uint16_t len; - * errcode = sd_ble_evt_get(NULL, &len); - * \endcode - * - * @mscs - * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} - * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. - * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. - */ -SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); - -/**@brief Add a Vendor Specific base UUID. - * - * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later - * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t - * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code - * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses - * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to - * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field - * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to - * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, - * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. - * - * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by - * the 16-bit uuid field in @ref ble_uuid_t. - * - * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in - * p_uuid_type along with an @ref NRF_SUCCESS error code. - * - * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding - * bytes 12 and 13. - * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be - * stored. - * - * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. - * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. - */ -SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); - -/**@brief Remove a Vendor Specific base UUID. - * - * @details This call removes a Vendor Specific base UUID. This function allows - * the application to reuse memory allocated for Vendor Specific base UUIDs. - * - * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID - * type. - * - * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. - * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific - * base UUID will be removed. If the function returns successfully, the UUID type that was removed will - * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that - * is in use by the ATT Server. - * - * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. - * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. - */ -SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); - -/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. - * - * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared - * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs - * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index - * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. - * - * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. - * - * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). - * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. - * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. - * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. - */ -SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); - -/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). - * - * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is - * computed. - * - * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. - * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). - * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. - * - * @retval ::NRF_SUCCESS Successfully encoded into the buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. - */ -SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); - -/**@brief Get Version Information. - * - * @details This call allows the application to get the BLE stack version information. - * - * @param[out] p_version Pointer to a ble_version_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Version information stored successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). - */ -SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); - -/**@brief Provide a user memory block. - * - * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. - */ -SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); - -/**@brief Set a BLE option. - * - * @details This call allows the application to set the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. - * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. - * - * @retval ::NRF_SUCCESS Option set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - */ -SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); - -/**@brief Get a BLE option. - * - * @details This call allows the application to retrieve the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. - * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Option retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. - * - */ -SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); - -/** @} */ -#ifdef __cplusplus -} -#endif -#endif /* BLE_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_err.h b/variants/wio-sdk-wm1110/softdevice/ble_err.h deleted file mode 100644 index d20f6d141..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_err.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @addtogroup nrf_error - @{ - @ingroup BLE_COMMON - @} - - @defgroup ble_err General error codes - @{ - - @brief General error code definitions for the BLE API. - - @ingroup BLE_COMMON -*/ -#ifndef NRF_BLE_ERR_H__ -#define NRF_BLE_ERR_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* @defgroup BLE_ERRORS Error Codes - * @{ */ -#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ -#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ -#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ -#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ -#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ -#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ - (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ -/** @} */ - -/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges - * @brief Assignment of subranges for module specific error codes. - * @note For specific error codes, see ble_.h or ble_error_.h. - * @{ */ -#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ -#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ -#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ -#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gap.h b/variants/wio-sdk-wm1110/softdevice/ble_gap.h deleted file mode 100644 index 8ebdfa82b..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gap.h +++ /dev/null @@ -1,2895 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GAP Generic Access Profile (GAP) - @{ - @brief Definitions and prototypes for the GAP interface. - */ - -#ifndef BLE_GAP_H__ -#define BLE_GAP_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GAP API SVC numbers. - */ -enum BLE_GAP_SVCS { - SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ - SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ - SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ - SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ - SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ - SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ - SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ - SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ - SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ - SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ - SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ - SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ - SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ - SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ - SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ - SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ - SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ - SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ - SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ - SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ - SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ - SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ - SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ - SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ - SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ - SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ - SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ - SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ - SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ - SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ - SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ - SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ - SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ - SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ - SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ - SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ - SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ - SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = - BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ -}; - -/**@brief GAP Event IDs. - * IDs that uniquely identify an event coming from the stack to the application. - */ -enum BLE_GAP_EVTS { - BLE_GAP_EVT_CONNECTED = - BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ - BLE_GAP_EVT_DISCONNECTED = - BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE = - BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ - BLE_GAP_EVT_SEC_PARAMS_REQUEST = - BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. - \n See @ref ble_gap_evt_sec_params_request_t. */ - BLE_GAP_EVT_SEC_INFO_REQUEST = - BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. - \n See @ref ble_gap_evt_sec_info_request_t. */ - BLE_GAP_EVT_PASSKEY_DISPLAY = - BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref - sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ - BLE_GAP_EVT_KEY_PRESSED = - BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ - BLE_GAP_EVT_AUTH_KEY_REQUEST = - BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. - \n See @ref ble_gap_evt_auth_key_request_t. */ - BLE_GAP_EVT_LESC_DHKEY_REQUEST = - BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref - sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ - BLE_GAP_EVT_AUTH_STATUS = - BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ - BLE_GAP_EVT_CONN_SEC_UPDATE = - BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ - BLE_GAP_EVT_TIMEOUT = - BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ - BLE_GAP_EVT_RSSI_CHANGED = - BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ - BLE_GAP_EVT_ADV_REPORT = - BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ - BLE_GAP_EVT_SEC_REQUEST = - BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate -\n or with @ref sd_ble_gap_encrypt if required security information is available -. \n See @ref ble_gap_evt_sec_request_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref - sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ - BLE_GAP_EVT_SCAN_REQ_REPORT = - BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ - BLE_GAP_EVT_PHY_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n - See @ref ble_gap_evt_phy_update_request_t. */ - BLE_GAP_EVT_PHY_UPDATE = - BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref - sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE = - BLE_GAP_EVT_BASE + - 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ - BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = - BLE_GAP_EVT_BASE + - 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ - BLE_GAP_EVT_ADV_SET_TERMINATED = - BLE_GAP_EVT_BASE + - 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ -}; - -/**@brief GAP Option IDs. - * IDs that uniquely identify a GAP option. - */ -enum BLE_GAP_OPTS { - BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ - BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ - BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ - BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ - BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = - BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ - BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = - BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ -}; - -/**@brief GAP Configuration IDs. - * - * IDs that uniquely identify a GAP configuration. - */ -enum BLE_GAP_CFGS { - BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ - BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ - BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic - inclusion configuration. */ - BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic - inclusion configuration. */ -}; - -/**@brief GAP TX Power roles. - */ -enum BLE_GAP_TX_POWER_ROLES { - BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ - BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ - BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ -}; - -/** @} */ - -/**@addtogroup BLE_GAP_DEFINES Defines - * @{ */ - -/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP - * @{ */ -#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ - (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ -#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ - (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ -#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ - (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ -#define BLE_ERROR_GAP_WHITELIST_IN_USE \ - (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ - (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ - (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLES GAP Roles - * @{ */ -#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ -#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ -#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ -/**@} */ - -/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources - * @{ */ -#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ -#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ -#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ -/**@} */ - -/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types - * @{ */ -#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ -#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ -#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ - 0x7F /**< An advertiser may advertise without its address. \ - This type of advertising is called anonymous. */ -/**@} */ - -/**@brief The default interval in seconds at which a private address is refreshed. */ -#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ -/**@brief The maximum interval in seconds at which a private address can be refreshed. */ -#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ - -/** @brief BLE address length. */ -#define BLE_GAP_ADDR_LEN (6) - -/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes - * @{ */ -#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ -#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ -#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ - 0x02 /**< Device will send and accept only private addresses for its own address, \ - and will not accept a peer using identity address as sender address when \ - the peer IRK is exchanged, non-zero and added to the identity list. */ -/**@} */ - -/** @brief Invalid power level. */ -#define BLE_GAP_POWER_LEVEL_INVALID 127 - -/** @brief Advertising set handle not set. */ -#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) - -/** @brief The default number of advertising sets. */ -#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) - -/** @brief The maximum number of advertising sets supported by this SoftDevice. */ -#define BLE_GAP_ADV_SET_COUNT_MAX (1) - -/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. - * @{ */ -#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ - (31) /**< Maximum data length for an advertising set. \ - If more advertising data is required, use extended advertising instead. */ -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for an extended advertising set. */ - -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ - (238) /**< Maximum supported data length for an extended connectable advertising set. */ -/**@}. */ - -/** @brief Set ID not available in advertising report. */ -#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF - -/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons - * @{ */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ -/**@} */ - -/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format - * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm - * @{ */ -#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ -#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ -#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ -#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ -#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ -#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ -#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ -#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ -#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ -#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ -#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ -#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ -#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ -#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ -#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ -#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags - * @{ */ -#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min - * @{ */ -#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ -#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ - /**@} */ - -/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min - * @{ */ -#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min - * @{ */ -#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min - * @{ */ -#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ -#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size - * - * Scan buffers are used for storing advertising data received from an advertiser. - * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. - * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. - * @{ */ -#define BLE_GAP_SCAN_BUFFER_MIN \ - (31) /**< Minimum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_MAX \ - (31) /**< Maximum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ - (255) /**< Minimum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ - (1650) /**< Maximum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for \ - an extended advertising set. */ -/** @} */ - -/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types - * - * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. - * - * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. - * The maximum supported data length for an extended advertiser is defined by - * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED - * Note that some of the advertising types do not support advertising data. Non-scannable types do not support - * scan response data. - * - * @{ */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x01 /**< Connectable and scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ - 0x02 /**< Connectable non-scannable directed advertising \ - events. Advertising interval is less that 3.75 ms. \ - Use this type for fast reconnections. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x03 /**< Connectable non-scannable directed advertising \ - events. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x04 /**< Non-connectable scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x05 /**< Non-connectable non-scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x06 /**< Connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x07 /**< Connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x08 /**< Non-connectable scannable undirected advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ - 0x09 /**< Non-connectable scannable directed advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x0A /**< Non-connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x0B /**< Non-connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies - * @{ */ -#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ -#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status - * @{ */ -#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ - 0x01 /**< More data to be received. \ - @note This value will only be used if \ - @ref ble_gap_scan_params_t::report_incomplete_evts and \ - @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ - 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ - 0x03 /**< Failed to receive the remaining data. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -/**@} */ - -/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies - * @{ */ -#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ - 0x00 /**< Accept all advertising packets except directed advertising packets \ - not addressed to this device. */ -#define BLE_GAP_SCAN_FP_WHITELIST \ - 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ - packets not addressed to this device. */ -#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ - 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ - 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units - * @{ */ -#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ - (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ - */ -#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ - (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ - mode. */ -#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ - (0) /**< Unlimited advertising in general discoverable mode. \ - For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ -/**@} */ - -/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes - * @{ */ -#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ -#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ -#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ -/**@} */ - -/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities - * @{ */ -#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ -#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ -#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ -/**@} */ - -/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types - * @{ */ -#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ -#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ -#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ -/**@} */ - -/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types - * @{ */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS GAP Security status - * @{ */ -#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ -#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ -#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ -#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ -#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ -#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ -#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ -#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ -#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ -#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ -#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ -#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ -#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ -#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ -#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ -#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ -#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources - * @{ */ -#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ -#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ -/**@} */ - -/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits - * @{ */ -#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ - 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ - 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ - 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ - 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ -/**@} */ - -/**@defgroup BLE_GAP_DEVNAME GAP device name defines. - * @{ */ -#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ -#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ -#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ -/**@} */ - -/**@brief Disable RSSI events for connections */ -#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF - -/**@defgroup BLE_GAP_PHYS GAP PHYs - * @{ */ -#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ -#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ -#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ -#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ -#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ - -/**@brief Supported PHYs in connections, for scanning, and for advertising. */ -#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ - -/**@} */ - -/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters - * - * See @ref ble_gap_conn_sec_mode_t. - * @{ */ -/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ - do { \ - (ptr)->sm = 0; \ - (ptr)->lv = 0; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 2; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 3; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 4; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 2; \ - } while (0) -/**@} */ - -/**@brief GAP Security Random Number Length. */ -#define BLE_GAP_SEC_RAND_LEN 8 - -/**@brief GAP Security Key Length. */ -#define BLE_GAP_SEC_KEY_LEN 16 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ -#define BLE_GAP_LESC_P256_PK_LEN 64 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ -#define BLE_GAP_LESC_DHKEY_LEN 32 - -/**@brief GAP Passkey Length. */ -#define BLE_GAP_PASSKEY_LEN 6 - -/**@brief Maximum amount of addresses in the whitelist. */ -#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) - -/**@brief Maximum amount of identities in the device identities list. */ -#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) - -/**@brief Default connection count for a configuration. */ -#define BLE_GAP_CONN_COUNT_DEFAULT (1) - -/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. - * @{ */ -#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ -#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ -#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. - * @{ */ -#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ - (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ - (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ - -/**@} */ - -/**@brief Automatic data length parameter. */ -#define BLE_GAP_DATA_LENGTH_AUTO 0 - -/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. - * @{ */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ -/**@} */ - -/**@defgroup GAP_SEC_MODES GAP Security Modes - * @{ */ -#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ -/**@} */ - -/**@brief The total number of channels in Bluetooth Low Energy. */ -#define BLE_GAP_CHANNEL_COUNT (40) - -/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines - * @{ */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ - /**@} */ - -/** @} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations - * @{ - */ -#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ - (1) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will reserve the attribute handles \ - which are otherwise used for this characteristic. \ - By reserving the attribute handles it will be possible \ - to upgrade the SoftDevice without changing handle of the \ - Service Changed characteristic. */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ - (2) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will not reserve the attribute handles \ - which are otherwise used for this characteristic. */ -/**@} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values - * @{ */ -#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -/**@} */ - -/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options - * @{ */ -#define BLE_GAP_SLAVE_LATENCY_ENABLE \ - (0) /**< Slave latency is enabled. When slave latency is enabled, \ - the slave will wake up every time it has data to send, \ - and/or every slave latency number of connection events. */ -#define BLE_GAP_SLAVE_LATENCY_DISABLE \ - (1) /**< Disable slave latency. The slave will wake up every connection event \ - regardless of the requested slave latency. \ - This option consumes the most power. */ -#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ - (2) /**< The slave will wake up every connection event if it has not received \ - an ACK from the master for at least slave latency events. This \ - configuration may increase the power consumption in environments \ - with a lot of radio activity. */ -/**@} */ - -/**@addtogroup BLE_GAP_STRUCTURES Structures - * @{ */ - -/**@brief Advertising event properties. */ -typedef struct { - uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ - uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. - @note Anonymous advertising is only available for - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ - uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ -} ble_gap_adv_properties_t; - -/**@brief Advertising report type. */ -typedef struct { - uint16_t connectable : 1; /**< Connectable advertising event type. */ - uint16_t scannable : 1; /**< Scannable advertising event type. */ - uint16_t directed : 1; /**< Directed advertising event type. */ - uint16_t scan_response : 1; /**< Received a scan response. */ - uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ - uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ - uint16_t reserved : 9; /**< Reserved for future use. */ -} ble_gap_adv_report_type_t; - -/**@brief Advertising Auxiliary Pointer. */ -typedef struct { - uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ - uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ -} ble_gap_aux_pointer_t; - -/**@brief Bluetooth Low Energy address. */ -typedef struct { - uint8_t - addr_id_peer : 1; /**< Only valid for peer addresses. - This bit is set by the SoftDevice to indicate whether the address has been resolved from - a Resolvable Private Address (when the peer is using privacy). - If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. - - This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. - */ - uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ - uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. - @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ -} ble_gap_addr_t; - -/**@brief GAP connection parameters. - * - * @note When ble_conn_params_t is received in an event, both min_conn_interval and - * max_conn_interval will be equal to the connection interval set by the central. - * - * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: - * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval - * that corresponds to the following Bluetooth Spec requirement: - * The Supervision_Timeout in milliseconds shall be larger than - * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. - */ -typedef struct { - uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ -} ble_gap_conn_params_t; - -/**@brief GAP connection security modes. - * - * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n - * Security Mode 1 Level 1: No security is needed (aka open link).\n - * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n - * Security Mode 1 Level 3: MITM protected encrypted link required.\n - * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n - * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n - * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n - */ -typedef struct { - uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ - uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ - -} ble_gap_conn_sec_mode_t; - -/**@brief GAP connection security status.*/ -typedef struct { - ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ - uint8_t - encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ -} ble_gap_conn_sec_t; - -/**@brief Identity Resolving Key. */ -typedef struct { - uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ -} ble_gap_irk_t; - -/**@brief Channel mask (40 bits). - * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, - * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents - * channel index 39. If a bit is set to 1, the channel is not used. - */ -typedef uint8_t ble_gap_ch_mask_t[5]; - -/**@brief GAP advertising parameters. */ -typedef struct { - ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ - ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. - @note ble_gap_addr_t::addr_type cannot be - @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. - - When privacy is enabled and the local device uses - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, - the device identity list is searched for a matching entry. If - the local IRK for that device identity is set, the local IRK - for that device will be used to generate the advertiser address - field in the advertising packet. - - If @ref ble_gap_adv_properties_t::type is directed, this must be - set to the targeted scanner or initiator. If the peer address is - in the device identity list, the peer IRK for that device will be - used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE - target addresses used in the advertising event PDUs. */ - uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE - advertising, this parameter is ignored. */ - uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, - an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - @sa BLE_GAP_ADV_TIMEOUT_VALUES. - @note The SoftDevice will always complete at least one advertising - event even if the duration is set too low. */ - uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling - advertising. Setting the value to 0 disables the limitation. When - the count of advertising events specified by this parameter - (if not 0) is reached, advertising will be automatically stopped - and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, - this parameter is ignored. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be used. - Masking away secondary advertising channels is not supported. */ - uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets - are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS - will be used. - Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. - @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets - are transmitted. - If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. - Valid values are - @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. - If @ref ble_gap_adv_properties_t::type is an extended advertising type - and connectable, this is the PHY that will be used to establish a - connection and send AUX_ADV_IND packets on. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other - advertising sets transmitted by this and other devices. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a - scan request is received and the scanner address is allowed - by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is a non-scannable - advertising type. */ -} ble_gap_adv_params_t; - -/**@brief GAP advertising data buffers. - * - * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and - * shall never be modified while advertising. The data shall be kept alive until either: - * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding - * advertising handle. - * - Advertising is stopped. - * - Advertising data is changed. - * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ -typedef struct { - ble_data_t adv_data; /**< Advertising data. - @note - Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type - that is allowed to contain advertising data. */ - ble_data_t scan_rsp_data; /**< Scan response data. - @note - Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type - that is scannable. */ -} ble_gap_adv_data_t; - -/**@brief GAP scanning parameters. */ -typedef struct { - uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. - If set to 0, the scanner will not receive advertising packets - on secondary advertising channels, and will not be able - to receive long advertising PDUs. */ - uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have - @ref ble_gap_adv_report_type_t::status set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. - This parameter is ignored when used with @ref sd_ble_gap_connect - @note This may be used to abort receiving more packets from an extended - advertising event, and is only available for extended - scanning, see @ref sd_ble_gap_scan_start. - @note This feature is not supported by this SoftDevice. */ - uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. - This parameter is ignored when used with @ref sd_ble_gap_connect. */ - uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. - @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and - @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with - @ref sd_ble_gap_connect */ - uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, - scan_phys will default to @ref BLE_GAP_PHY_1MBPS. - - If @ref ble_gap_scan_params_t::extended is set to 0, the only - supported PHY is @ref BLE_GAP_PHY_1MBPS. - - When used with @ref sd_ble_gap_scan_start, - the bitfield indicates the PHYs the scanner will use for scanning - on primary advertising channels. The scanner will accept - @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. - - When used with @ref sd_ble_gap_connect, the bitfield indicates - the PHYs the initiator will use for scanning on primary advertising - channels. The initiator will accept connections initiated on either - of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. - If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, - the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. - If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan - PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is - @ref BLE_GAP_PHY_CODED, the primary scan PHY is - @ref BLE_GAP_PHY_CODED only. */ - uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ - uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. - If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and - @ref BLE_GAP_PHY_CODED interval shall be larger than or - equal to twice the scan window. */ - uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be - set to 0. - Masking away secondary channels is not supported. */ -} ble_gap_scan_params_t; - -/**@brief Privacy. - * - * The privacy feature provides a way for the device to avoid being tracked over a period of time. - * The privacy feature, when enabled, hides the local device identity and replaces it with a private address - * that is automatically refreshed at a specified interval. - * - * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). - * With this key, a device can generate a random private address that can only be recognized by peers in possession of that - * key, and devices can establish connections without revealing their real identities. - * - * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref - * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. - * - * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all - * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. - * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is - * called. - */ -typedef struct { - uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ - uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ - uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use - the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ - ble_gap_irk_t - *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device - default IRK will be used. When used as output, pointer to IRK structure where the current default IRK - will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate - random private resolvable addresses for the local device unless instructed otherwise. */ -} ble_gap_privacy_params_t; - -/**@brief PHY preferences for TX and RX - * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each - * direction. - * @code - * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * @endcode - * - */ -typedef struct { - uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ -} ble_gap_phys_t; - -/** @brief Keys that can be exchanged during a bonding procedure. */ -typedef struct { - uint8_t enc : 1; /**< Long Term Key and Master Identification. */ - uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ - uint8_t sign : 1; /**< Connection Signature Resolving Key. */ - uint8_t link : 1; /**< Derive the Link Key from the LTK. */ -} ble_gap_sec_kdist_t; - -/**@brief GAP security parameters. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ - uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ - uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ - uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ - uint8_t oob : 1; /**< The OOB data flag. - - In LE legacy pairing, this flag is set if a device has out of band authentication data. - The OOB method is used if both of the devices have out of band authentication data. - - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band - authentication data. The OOB method is used if at least one device has the peer device's OOB data - available. */ - uint8_t - min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ - uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ - ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ - ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ -} ble_gap_sec_params_t; - -/**@brief GAP Encryption Information. */ -typedef struct { - uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ - uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ - uint8_t auth : 1; /**< Authenticated Key. */ - uint8_t ltk_len : 6; /**< LTK length in octets. */ -} ble_gap_enc_info_t; - -/**@brief GAP Master Identification. */ -typedef struct { - uint16_t ediv; /**< Encrypted Diversifier. */ - uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ -} ble_gap_master_id_t; - -/**@brief GAP Signing Information. */ -typedef struct { - uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ -} ble_gap_sign_info_t; - -/**@brief GAP LE Secure Connections P-256 Public Key. */ -typedef struct { - uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the - standard SMP protocol format: {X,Y} both in little-endian. */ -} ble_gap_lesc_p256_pk_t; - -/**@brief GAP LE Secure Connections DHKey. */ -typedef struct { - uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ -} ble_gap_lesc_dhkey_t; - -/**@brief GAP LE Secure Connections OOB data. */ -typedef struct { - ble_gap_addr_t addr; /**< Bluetooth address of the device. */ - uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ - uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ -} ble_gap_lesc_oob_data_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ -typedef struct { - ble_gap_addr_t - peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ - uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ -} ble_gap_evt_connected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ -typedef struct { - uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ -} ble_gap_evt_disconnected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ -typedef struct { - ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ -} ble_gap_evt_phy_update_request_t; - -/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ -typedef struct { - uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ - uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ -} ble_gap_evt_phy_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ -typedef struct { - ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ -} ble_gap_evt_sec_params_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ -typedef struct { - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ - ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ - uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ - uint8_t id_info : 1; /**< If 1, Identity Information required. */ - uint8_t sign_info : 1; /**< If 1, Signing Information required. */ -} ble_gap_evt_sec_info_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ -typedef struct { - uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ - uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply - with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or - @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ -} ble_gap_evt_passkey_display_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ -typedef struct { - uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ -} ble_gap_evt_key_pressed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ -typedef struct { - uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ -} ble_gap_evt_auth_key_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ -typedef struct { - ble_gap_lesc_p256_pk_t - *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory - inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ - uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the - procedure. */ -} ble_gap_evt_lesc_dhkey_request_t; - -/**@brief Security levels supported. - * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. - */ -typedef struct { - uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ - uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ - uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ - uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ -} ble_gap_sec_levels_t; - -/**@brief Encryption Key. */ -typedef struct { - ble_gap_enc_info_t enc_info; /**< Encryption Information. */ - ble_gap_master_id_t master_id; /**< Master Identification. */ -} ble_gap_enc_key_t; - -/**@brief Identity Key. */ -typedef struct { - ble_gap_irk_t id_info; /**< Identity Resolving Key. */ - ble_gap_addr_t id_addr_info; /**< Identity Address. */ -} ble_gap_id_key_t; - -/**@brief Security Keys. */ -typedef struct { - ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ - ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ - ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ - ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the - value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ -} ble_gap_sec_keys_t; - -/**@brief Security key set for both local and peer keys. */ -typedef struct { - ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be - generated locally and will always be stored if bonding. */ - ble_gap_sec_keys_t - keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ -} ble_gap_sec_keyset_t; - -/**@brief Data Length Update Procedure parameters. */ -typedef struct { - uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ - uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ -} ble_gap_data_length_params_t; - -/**@brief Data Length Update Procedure local limitation. */ -typedef struct { - uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ - uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ - uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many - microseconds. */ -} ble_gap_data_length_limitation_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ -typedef struct { - uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ - uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ - uint8_t bonded : 1; /**< Procedure resulted in a bond. */ - uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ - ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ - ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ - ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding - with LE Secure Connections, the enc bit will be always set. */ - ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding - with LE Secure Connections, the enc bit will never be set. */ -} ble_gap_evt_auth_status_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ -typedef struct { - ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ -} ble_gap_evt_conn_sec_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ - union { - ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released - scan buffer is contained in this field. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ -typedef struct { - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ -} ble_gap_evt_rssi_changed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ -typedef struct { - uint8_t reason; /**< Reason for why the advertising set terminated. See - @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ - uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, - this field indicates the number of completed advertising events. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. */ -} ble_gap_evt_adv_set_terminated_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. - * - * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * not all fields in the advertising report may be available. - * - * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. - */ -typedef struct { - ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the - peer's identity address. */ - ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if - @ref ble_gap_adv_report_type_t::directed is set to 1. If the - SoftDevice was able to resolve the address, - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr - contains the local identity address. If the target address of the - advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, - and the SoftDevice was unable to resolve it, the application may try - to resolve this address to find out if the advertising event was - directed to us. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. - See @ref BLE_GAP_PHYS. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. - See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets - were received on a secondary advertising channel. */ - int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. - This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the - last received packet did not contain the Tx Power field. - @note TX Power is only included in extended advertising packets. */ - int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ - uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present - if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID - is not present if @ref ble_gap_evt_adv_report_t::set_id is set to - @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - ble_data_t data; /**< Received advertising or scan response data. If - @ref ble_gap_adv_report_type_t::status is not set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided - in @ref sd_ble_gap_scan_start is now released. */ - ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising - event. @note This field is only set if @ref ble_gap_adv_report_type_t::status - is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ -} ble_gap_evt_adv_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Man In The Middle protection requested. */ - uint8_t lesc : 1; /**< LE Secure Connections requested. */ - uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ -} ble_gap_evt_sec_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ -typedef struct { - uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ -} ble_gap_evt_scan_req_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ -typedef struct { - ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ -} ble_gap_evt_data_length_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. - * - * @note This event may also be raised after a PHY Update procedure. - */ -typedef struct { - ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ -} ble_gap_evt_data_length_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ -typedef struct { - int8_t - channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy - channels, in dBm, indexed by Channel Index. - If no measurement is available for the given channel, channel_energy is set to - @ref BLE_GAP_POWER_LEVEL_INVALID. */ -} ble_gap_evt_qos_channel_survey_report_t; - -/**@brief GAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - union /**< union alternative identified by evt_id in enclosing struct. */ - { - ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ - ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ - ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ - ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ - ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ - ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ - ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ - ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ - ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ - ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ - ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ - ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ - ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ - ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ - ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ - ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ - ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ - ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ - ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ - ble_gap_evt_qos_channel_survey_report_t - qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_t; - -/** - * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. - * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. - */ -typedef struct { - uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. - The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ - uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. - The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref - BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters - for setting the throughput of a connection. - See the SoftDevice Specification for details on throughput. */ -} ble_gap_conn_cfg_t; - -/** - * @brief Configuration of maximum concurrent connections in the different connected roles, set with - * @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too - * large. The maximum supported sum of concurrent connections is - * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. - * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. - * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum - * supported advertising handles is - * @ref BLE_GAP_ADV_SET_COUNT_MAX. - */ -typedef struct { - uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ - uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref - BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ - uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref - BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ - uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is - @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ - uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to - the application using @ref sd_ble_gap_qos_channel_survey_start. */ -} ble_gap_cfg_role_count_t; - -/** - * @brief Device name and its properties, set with @ref sd_ble_cfg_set. - * - * @note If the device name is not configured, the default device name will be - * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be - * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name - * will have no write access. - * - * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, - * the attribute table size must be increased to have room for the longer device name (see - * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). - * - * @note If vloc is @ref BLE_GATTS_VLOC_STACK : - * - p_value must point to non-volatile memory (flash) or be NULL. - * - If p_value is NULL, the device name will initially be empty. - * - * @note If vloc is @ref BLE_GATTS_VLOC_USER : - * - p_value cannot be NULL. - * - If the device name is writable, p_value must point to volatile memory (RAM). - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Invalid device name location (vloc). - * - Invalid device name security mode. - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). - * - The device name length is too long for the given Attribute Table. - * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. - */ -typedef struct { - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ - uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ - uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ -} ble_gap_cfg_device_name_t; - -/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_ppcp_incl_cfg_t; - -/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_car_incl_cfg_t; - -/**@brief Configuration structure for GAP configurations. */ -typedef union { - ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ - ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ - ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include - configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ - ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, - cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ -} ble_gap_cfg_t; - -/**@brief Channel Map option. - * - * @details Used with @ref sd_ble_opt_get to get the current channel map - * or @ref sd_ble_opt_set to set a new channel map. When setting the - * channel map, it applies to all current and future connections. When getting the - * current channel map, it applies to a single connection and the connection handle - * must be supplied. - * - * @note Setting the channel map may take some time, depending on connection parameters. - * The time taken may be different for each connection and the get operation will - * return the previous channel map until the new one has taken effect. - * - * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. - * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. - * - * @retval ::NRF_SUCCESS Get or set successful. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Less then two bits in @ref ch_map are set. - * - Bits for primary advertising channels (37-39) are set. - * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. - * - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ - uint8_t ch_map[5]; /**< Channel Map (37-bit). */ -} ble_gap_opt_ch_map_t; - -/**@brief Local connection latency option. - * - * @details Local connection latency is a feature which enables the slave to improve - * current consumption by ignoring the slave latency set by the peer. The - * local connection latency can only be set to a multiple of the slave latency, - * and cannot be longer than half of the supervision timeout. - * - * @details Used with @ref sd_ble_opt_set to set the local connection latency. The - * @ref sd_ble_opt_get is not supported for this option, but the actual - * local connection latency (unless set to NULL) is set as a return parameter - * when setting the option. - * - * @note The latency set will be truncated down to the closest slave latency event - * multiple, or the nearest multiple before half of the supervision timeout. - * - * @note The local connection latency is disabled by default, and needs to be enabled for new - * connections and whenever the connection is updated. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t requested_latency; /**< Requested local connection latency. */ - uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return - value). */ -} ble_gap_opt_local_conn_latency_t; - -/**@brief Disable slave latency - * - * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection - * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the - * peripheral will ignore the slave_latency set by the central. - * - * @note Shall only be called on peripheral links. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ -} ble_gap_opt_slave_latency_disable_t; - -/**@brief Passkey Option. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @endmscs - * - * @details Structure containing the passkey to be used during pairing. This can be used with @ref - * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication - * instead of generating a random one. - * - * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - */ -typedef struct { - uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used - during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ -} ble_gap_opt_passkey_t; - -/**@brief Compatibility mode 1 option. - * - * @details This can be used with @ref sd_ble_opt_set to enable and disable - * compatibility mode 1. Compatibility mode 1 is disabled by default. - * - * @note Compatibility mode 1 enables interoperability with devices that do not support a value of - * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a - * limited set of legacy peripheral devices from another vendor. Enabling this compatibility - * mode will only have an effect if the local device will act as a central device and - * initiate a connection to a peripheral device. In that case it may lead to the connection - * creation taking up to one connection interval longer to complete for all connections. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. - */ -typedef struct { - uint8_t enable : 1; /**< Enable compatibility mode 1.*/ -} ble_gap_opt_compat_mode_1_t; - -/**@brief Authenticated payload timeout option. - * - * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other - * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. - * - * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated - * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted - * link. - * - * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance - * to reset the timer. In addition the stack will try to prioritize running of LE ping over other - * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects - * on other activities, it is recommended to use high timeout values. - * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ -} ble_gap_opt_auth_payload_timeout_t; - -/**@brief Option structure for GAP options. */ -typedef union { - ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ - ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ - ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ - ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ - ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ - ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ -} ble_gap_opt_t; - -/**@brief Connection event triggering parameters. */ -typedef struct { - uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until - connection event PPI task triggering is stopped. - The PPI channel ID can not be one of the PPI channels reserved by - the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ - uint32_t task_endpoint; /**< Task Endpoint to trigger. */ - uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ - uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. - If the device is in slave role and slave latency is enabled, - this parameter should be set to a multiple of (slave latency + 1) - to ensure low power operation. */ -} ble_gap_conn_event_trigger_t; -/**@} */ - -/**@addtogroup BLE_GAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set the local Bluetooth identity address. - * - * The local Bluetooth identity address is the address that identifies this device to other peers. - * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @note The identity address cannot be changed while advertising, scanning or creating a connection. - * - * @note This address will be distributed to the peer during bonding. - * If the address changes, the address stored in the peer device will not be valid and the ability to - * reconnect using the old address will be lost. - * - * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being - * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged - * for the lifetime of each IC. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @endmscs - * - * @param[in] p_addr Pointer to address structure. - * - * @retval ::NRF_SUCCESS Address successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, - * scanning or creating a connection. - */ -SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); - -/**@brief Get local Bluetooth identity address. - * - * @note This will always return the identity address irrespective of the privacy settings, - * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - */ -SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); - -/**@brief Get the Bluetooth device address used by the advertiser. - * - * @note This function will return the local Bluetooth address used in advertising PDUs. When - * using privacy, the SoftDevice will generate a new private address every - * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using - * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the - * address returned may not be the latest address that is used in the advertising PDUs. - * - * @param[in] adv_handle The advertising handle to get the address from. - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. - * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); - -/**@brief Set the active whitelist in the SoftDevice. - * - * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. - * The whitelist cannot be set if a BLE role is using the whitelist. - * - * @note If an address is resolved using the information in the device identity list, then the whitelist - * filter policy applies to the peer identity address and not the resolvable address sent on air. - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @endmscs - * - * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. - * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. - * - * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. - * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when - * pp_wl_addrs is not NULL. - */ -SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); - -/**@brief Set device identity list. - * - * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. - * The device identity list cannot be set if a BLE role is using the list. - * - * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will - * be cleared. - * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the - * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. - * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. - * This code may be returned if the local IRK list also has an invalid entry. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity - * address. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can - * only return when pp_id_keys is not NULL. - */ -SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, - sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, - uint8_t len)); - -/**@brief Set privacy settings. - * - * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. - * - * @param[in] p_privacy_params Privacy settings. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. - * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution - characteristic is not configured to be included and the SoftDevice is configured - to support central roles. - See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning - * or creating a connection. - */ -SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); - -/**@brief Get privacy settings. - * - * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. - * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. - * - * @param[in,out] p_privacy_params Privacy settings. - * - * @retval ::NRF_SUCCESS Privacy settings read. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - */ -SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); - -/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. - * - * @note The format of the advertising data will be checked by this call to ensure interoperability. - * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and - * duplicating the local name in the advertising data and scan response data. - * - * @note In order to update advertising data while advertising, new advertising buffers must be provided. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the - * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. - * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See - * @ref ble_gap_adv_data_t. - * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising - * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. - * - * @retval ::NRF_SUCCESS Advertising set successfully configured. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid advertising data configuration specified. See @ref - * ble_gap_adv_data_t. - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, - * see @ref sd_ble_gap_whitelist_set. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - It is invalid to provide non-NULL advertising set parameters while - * advertising. - * - It is invalid to provide the same data buffers while advertising. To - * update advertising data, provide new advertising buffers. - * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format - * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an - * existing advertising handle instead. - * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. - */ -SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, - sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, - ble_gap_adv_params_t const *p_adv_params)); - -/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @note Only one advertiser may be active at any time. - * - * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. - * See @ref sd_ble_gap_privacy_set(). - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} - * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} - * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable - * advertising, this is ignored. - * - * @retval ::NRF_SUCCESS The BLE stack has started advertising. - * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration - * tag has been reached; connectable advertiser cannot be started. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref - sd_ble_gap_adv_set_configure. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, see @ref - sd_ble_gap_whitelist_set. - * @retval ::NRF_ERROR_RESOURCES Either: - * - adv_handle is configured with connectable advertising, but the event_length parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - * - Not enough BLE role slots available. - Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) - and try again. - * - p_adv_params is configured with connectable advertising, but the event_length - parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); - -/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle The advertising handle that should stop advertising. - * - * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. - * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); - -/**@brief Update connection parameters. - * - * @details In the central role this will initiate a Link Layer connection parameter update procedure, - * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for - * the central to perform the procedure. In both cases, and regardless of success or failure, the application - * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. - * - * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the - * procedure unrequested. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CPU_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, - * the parameters in the PPCP characteristic of the GAP service will be used instead. - * If NULL is provided on a central role and in response to a @ref - * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected - * - * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, - sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Disconnect (GAP Link Termination). - * - * @details This call initiates the disconnection procedure, and its completion will be communicated to the application - * with a @ref BLE_GAP_EVT_DISCONNECTED event. - * - * @events - * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CONN_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref - * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). - * - * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - */ -SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); - -/**@brief Set the radio's transmit power. - * - * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for - * possible roles. - * @param[in] handle The handle parameter is interpreted depending on role: - * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. - * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, - * will use the specified transmit power, and include it in the advertising packet headers if - * @ref ble_gap_adv_properties_t::include_tx_power set. - * - For all other roles handle is ignored. - * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). - * - * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. - * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. - * Setting these values on a chip that does not support them will result in undefined behaviour. - * @note The initiator will have the same transmit power as the scanner. - * @note When a connection is created it will inherit the transmit power from the initiator or - * advertiser leading to the connection. - * - * @retval ::NRF_SUCCESS Successfully changed the transmit power. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); - -/**@brief Set GAP Appearance value. - * - * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); - -/**@brief Get GAP Appearance value. - * - * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); - -/**@brief Set GAP Peripheral Preferred Connection Parameters. - * - * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Get GAP Peripheral Preferred Connection Parameters. - * - * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); - -/**@brief Set GAP device name. - * - * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), - * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. - * - * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. - * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. - * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or - * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). - * - * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, - sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); - -/**@brief Get GAP device name. - * - * @note If the device name is longer than the size of the supplied buffer, - * p_len will return the complete device name length, - * and not the number of bytes actually returned in p_dev_name. - * The application may use this information to allocate a suitable buffer size. - * - * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to - * NULL to obtain the complete device name length. - * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. - * - * @retval ::NRF_SUCCESS GAP device name retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); - -/**@brief Initiate the GAP Authentication procedure. - * - * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), - * otherwise in the peripheral role, an SMP Security Request will be sent. - * - * @events - * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be - * generated:} - * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} - * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} - * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} - * @event{@ref BLE_GAP_EVT_KEY_PRESSED} - * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} - * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} - * @event{@ref BLE_GAP_EVT_AUTH_STATUS} - * @event{@ref BLE_GAP_EVT_TIMEOUT} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the - * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. - * In the central role, this pointer may be NULL to reject a Security Request. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - An encryption is already executing or queued. - * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is - * reached. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. - */ -SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, - sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); - -/**@brief Reply with GAP security parameters. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. - * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be - * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. - * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or - * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this - * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. - * Note that the SoftDevice expects the application to provide memory for storing the - * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The - * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application - * upon reception of the - * @ref BLE_GAP_EVT_AUTH_STATUS event. - * - * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - */ -SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, - sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, - ble_gap_sec_keyset_t const *p_sec_keyset)); - -/**@brief Reply with an authentication key. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, - * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. - * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. - * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL - * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, - * then a 16-byte OOB key value in little-endian format. - * - * @retval ::NRF_SUCCESS Authentication key successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, - sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); - -/**@brief Reply with an LE Secure connections DHKey. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dhkey LE Secure Connections DHKey. - * - * @retval ::NRF_SUCCESS DHKey successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - The peer is not authenticated. - * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, - sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); - -/**@brief Notify the peer of a local keypress. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. - * - * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested. - * - Passkey has not been entered. - * - Keypresses have not been enabled by both peers. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. - */ -SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); - -/**@brief Generate a set of OOB data to send to a peer out of band. - * - * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has - * already been established, the one used during connection setup). The application may manually overwrite it with an updated - * value. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. - * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. - * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. - * - * @retval ::NRF_SUCCESS OOB data successfully generated. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, - sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, - ble_gap_lesc_oob_data_t *p_oobd_own)); - -/**@brief Provide the OOB data sent/received out of band. - * - * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. - * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this - * function. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. - * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. - * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. - * Must correspond to @ref ble_gap_sec_params_t::oob flag - * in @ref sd_ble_gap_authenticate in the central role or - * in @ref sd_ble_gap_sec_params_reply in the peripheral role. - * - * @retval ::NRF_SUCCESS OOB data accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested - * - Not expecting LESC OOB data - * - Have not actually exchanged passkeys. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, - sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, - ble_gap_lesc_oob_data_t const *p_oobd_peer)); - -/**@brief Initiate GAP Encryption procedure. - * - * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. - * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and - * retry. - */ -SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, - sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); - -/**@brief Reply with GAP security information. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in - * @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is - * available. - * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. - * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is - * available. - * - * @retval ::NRF_SUCCESS Successfully accepted security information. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. - * - Encryption information provided by the app without being requested. See @ref - * ble_gap_evt_sec_info_request_t::enc_info. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, - sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, - ble_gap_sign_info_t const *p_sign_info)); - -/**@brief Get the current connection security. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Current connection security successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); - -/**@brief Start reporting the received signal strength to the application. - * - * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. - * - * @events - * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is - * dependent on the settings of the threshold_dbm - * and skip_count input parameters.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are - * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. - * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref - * BLE_GAP_EVT_RSSI_CHANGED event. - * - * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); - -/**@brief Stop reporting the received signal strength. - * - * @note An RSSI change detected before the call but not yet received by the application - * may be reported after @ref sd_ble_gap_rssi_stop has been called. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); - -/**@brief Get the received signal strength for the last connection event. - * - * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND - * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. - * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. - * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. - * - * @retval ::NRF_SUCCESS Successfully read the RSSI. - * @retval ::NRF_ERROR_NOT_FOUND No sample is available. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - */ -SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); - -/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). - * - * @note A call to this function will require the application to keep the memory pointed by - * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped - * or when this function is called with another buffer. - * - * @note The scanner will automatically stop in the following cases: - * - @ref sd_ble_gap_scan_stop is called. - * - @ref sd_ble_gap_connect is called. - * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. - * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application - * access received data. The application must call this function to continue scanning, or call @ref - * sd_ble_gap_scan_stop to stop scanning. - * - * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will - * receive more reports from this advertising event. The following reports will include the old and new received data. - * - * @events - * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue - * scanning, this parameter must be NULL. - * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. - * The memory pointed to should be kept alive until the scanning is stopped. - * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. - * If the scanner receives advertising data larger than can be stored in the buffer, - * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status - * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. - * - * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Scanning is already ongoing and p_scan_params was not NULL - * - Scanning is not running and p_scan_params was NULL. - * - The scanner has timed out when this function is called to continue scanning. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. - * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again - */ -SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, - sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); - -/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). - * - * @note The buffer provided in @ref sd_ble_gap_scan_start is released. - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. - * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. - */ -SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); - -/**@brief Create a connection (GAP Link Establishment). - * - * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. - * The scanning procedure will be stopped even if the function returns an error. - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use - * whitelist, then p_peer_addr is ignored. - * @param[in] p_scan_params Pointer to scan parameters structure. - * @param[in] p_conn_params Pointer to desired connection parameters. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. - * - * @retval ::NRF_SUCCESS Successfully initiated connection procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * - Invalid parameter(s) in p_scan_params or p_conn_params. - * - Use of whitelist requested but whitelist has not been set, see @ref - * sd_ble_gap_whitelist_set. - * - Peer address was not present in the device identity list, see @ref - * sd_ble_gap_device_identities_set. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an - * existing locally initiated connect procedure, which must complete before initiating again. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::NRF_ERROR_RESOURCES Either: - * - Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. - * - The event_length parameter associated with conn_cfg_tag is too small to be able to - * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. - * Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_CONNECT, uint32_t, - sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, - ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); - -/**@brief Cancel a connection establishment. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. - * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection - * completed occurred. - */ -SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); - -/**@brief Initiate or respond to a PHY Update Procedure - * - * @details This function is used to initiate or respond to a PHY Update Procedure. It will always - * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. - * If this function is used to initiate a PHY Update procedure and the only option - * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the - * currently active PHYs in the respective directions, the SoftDevice will generate a - * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the - * procedure in the Link Layer. - * - * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, - * then the stack will select PHYs based on the peer's PHY preferences and the local link - * configuration. The PHY Update procedure will for this case result in a PHY combination - * that respects the time constraints configured with @ref sd_ble_cfg_set and the current - * link layer data length. - * - * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. - * - * If the peer does not support the PHY Update Procedure, then the resulting - * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to - * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. - * - * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status - * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or - * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. - * If the peer responds to the PHY Update procedure with invalid parameters, the status - * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. - * If the PHY Update procedure was rejected by the peer for a different reason, the status will - * contain the reason as specified by the peer. - * - * @events - * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} - * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} - * @endmscs - * - * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. - * @param[in] p_gap_phys Pointer to PHY structure. - * - * @retval ::NRF_SUCCESS Successfully requested a PHY Update. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of - * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref - * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. - * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the - * pending procedure to complete and retry. - * - */ -SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); - -/**@brief Initiate or respond to a Data Length Update Procedure. - * - * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of - * p_dl_params, the SoftDevice will choose the highest value supported in current - * configuration and connection parameters. - * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime - * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and - * MaxRxTime will be limited to maximum 2120 us. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update - * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let - * the SoftDevice automatically decide the value for that member. - * Set to NULL to use automatic values for all members. - * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not - * have enough resources or does not support the requested Data Length - * Update parameters. Ignored if NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect - * p_dl_limitation to see which parameter is not supported. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested - * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation - * to see where the limitation is. - * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the - * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. - */ -SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, - sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, - ble_gap_data_length_limitation_t *p_dl_limitation)); - -/**@brief Start the Quality of Service (QoS) channel survey module. - * - * @details The channel survey module provides measurements of the energy levels on - * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT - * events will periodically report the measured energy levels for each channel. - * - * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, - * Radio Timeslot API events and Flash API events. - * - * @note The channel survey module will attempt to do measurements so that the average interval - * between measurements will be interval_us. However due to the channel survey module - * having the lowest priority of all roles and modules, this may not be possible. In that - * case fewer than expected channel survey reports may be given. - * - * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available - * must be set. This is done using @ref sd_ble_cfg_set. - * - * @param[in] interval_us Requested average interval for the measurements and reports. See - * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set - * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel - * survey role will be scheduled at every available opportunity. - * - * @retval ::NRF_SUCCESS The module is successfully started. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the - * allowed range. - * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. - * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. - * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using - * @ref sd_ble_cfg_set. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); - -/**@brief Stop the Quality of Service (QoS) channel survey module. - * - * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this - * function is called. - * - * @retval ::NRF_SUCCESS The module is successfully stopped. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); - -/**@brief Obtain the next connection event counter value. - * - * @details The connection event counter is initialized to zero on the first connection event. The value is incremented - * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, - * Section 4.5.1. - * - * @note The connection event counter obtained through this API will be outdated if this API is called - * at the same time as the connection event counter is incremented. - * - * @note This API will always return the last connection event counter + 1. - * The actual connection event may be multiple connection events later if: - * - Slave latency is enabled and there is no data to transmit or receive. - * - Another role is scheduled with a higher priority at the same time as the next connection event. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. - * - * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, - sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); - -/**@brief Start triggering a given task on connection event start. - * - * @details When enabled, this feature will trigger a PPI task at the start of connection events. - * The application can configure the SoftDevice to trigger every N connection events starting from - * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_params Connection event trigger parameters. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. - * @retval ::NRF_ERROR_INVALID_STATE Either: - * - Trying to start connection event triggering when it is already ongoing. - * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. - * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value - to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, - sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); - -/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GAP_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatt.h b/variants/wio-sdk-wm1110/softdevice/ble_gatt.h deleted file mode 100644 index df0d728fc..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gatt.h +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common - @{ - @brief Common definitions and prototypes for the GATT interfaces. - */ - -#ifndef BLE_GATT_H__ -#define BLE_GATT_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATT_DEFINES Defines - * @{ */ - -/** @brief Default ATT MTU, in bytes. */ -#define BLE_GATT_ATT_MTU_DEFAULT 23 - -/**@brief Invalid Attribute Handle. */ -#define BLE_GATT_HANDLE_INVALID 0x0000 - -/**@brief First Attribute Handle. */ -#define BLE_GATT_HANDLE_START 0x0001 - -/**@brief Last Attribute Handle. */ -#define BLE_GATT_HANDLE_END 0xFFFF - -/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources - * @{ */ -#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ -/** @} */ - -/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations - * @{ */ -#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ -/** @} */ - -/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags - * @{ */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ -/** @} */ - -/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations - * @{ */ -#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ -#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ -/** @} */ - -/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes - * @{ */ -#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ -#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ -#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ -#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ -#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ -#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ -#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ - 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ -#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ -#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ - 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ -#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ -#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ -#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ - 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ - */ -#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ - 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ -#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ - 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ -#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats - * @note Found at - * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml - * @{ */ -#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ -#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ -#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ -#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ -#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ -#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ -#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ -#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces - * @{ - */ -#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ -#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATT_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. - */ -typedef struct { - uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. - The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - @mscs - @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - @endmscs - */ -} ble_gatt_conn_cfg_t; - -/**@brief GATT Characteristic Properties. */ -typedef struct { - /* Standard properties */ - uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ - uint8_t read : 1; /**< Reading the value permitted. */ - uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ - uint8_t write : 1; /**< Writing the value with Write Request permitted. */ - uint8_t notify : 1; /**< Notification of the value permitted. */ - uint8_t indicate : 1; /**< Indications of the value permitted. */ - uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ -} ble_gatt_char_props_t; - -/**@brief GATT Characteristic Extended Properties. */ -typedef struct { - /* Extended properties */ - uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ - uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ -} ble_gatt_char_ext_props_t; - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATT_H__ - -/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gattc.h b/variants/wio-sdk-wm1110/softdevice/ble_gattc.h deleted file mode 100644 index f1df1782c..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gattc.h +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client - @{ - @brief Definitions and prototypes for the GATT Client interface. - */ - -#ifndef BLE_GATTC_H__ -#define BLE_GATTC_H__ - -#include "ble_err.h" -#include "ble_gatt.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GATTC API SVC numbers. */ -enum BLE_GATTC_SVCS { - SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ - SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ - SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ - SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ - SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ - SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ - SD_BLE_GATTC_READ, /**< Generic read. */ - SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ - SD_BLE_GATTC_WRITE, /**< Generic write. */ - SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ - SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ -}; - -/** - * @brief GATT Client Event IDs. - */ -enum BLE_GATTC_EVTS { - BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref - ble_gattc_evt_prim_srvc_disc_rsp_t. */ - BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. - */ - BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref - ble_gattc_evt_char_disc_rsp_t. */ - BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref - ble_gattc_evt_desc_disc_rsp_t. */ - BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref - ble_gattc_evt_attr_info_disc_rsp_t. */ - BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ - BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ - BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref - ble_gattc_evt_char_vals_read_rsp_t. */ - BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ - BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref - sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ - BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref - ble_gattc_evt_exchange_mtu_rsp_t. */ - BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ - BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref - ble_gattc_evt_write_cmd_tx_complete_t. */ -}; - -/**@brief GATTC Option IDs. - * IDs that uniquely identify a GATTC option. - */ -enum BLE_GATTC_OPTS { - BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTC_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC - * @{ */ -#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ -/** @} */ - -/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats - * @{ */ -#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ -#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ -/** @} */ - -/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults - * @{ */ -#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Write without Response that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTC_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for - transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ -} ble_gattc_conn_cfg_t; - -/**@brief Operation Handle Range. */ -typedef struct { - uint16_t start_handle; /**< Start Handle. */ - uint16_t end_handle; /**< End Handle. */ -} ble_gattc_handle_range_t; - -/**@brief GATT service. */ -typedef struct { - ble_uuid_t uuid; /**< Service UUID. */ - ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ -} ble_gattc_service_t; - -/**@brief GATT include. */ -typedef struct { - uint16_t handle; /**< Include Handle. */ - ble_gattc_service_t included_srvc; /**< Handle of the included service. */ -} ble_gattc_include_t; - -/**@brief GATT characteristic. */ -typedef struct { - ble_uuid_t uuid; /**< Characteristic UUID. */ - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - uint8_t char_ext_props : 1; /**< Extended properties present. */ - uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ - uint16_t handle_value; /**< Handle of the Characteristic Value. */ -} ble_gattc_char_t; - -/**@brief GATT descriptor. */ -typedef struct { - uint16_t handle; /**< Descriptor Handle. */ - ble_uuid_t uuid; /**< Descriptor UUID. */ -} ble_gattc_desc_t; - -/**@brief Write Parameters. */ -typedef struct { - uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ - uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ - uint16_t handle; /**< Handle to the attribute to be written. */ - uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ - uint16_t len; /**< Length of data in bytes. */ - uint8_t const *p_value; /**< Pointer to the value data. */ -} ble_gattc_write_params_t; - -/**@brief Attribute Information for 16-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ -} ble_gattc_attr_info16_t; - -/**@brief Attribute Information for 128-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ -} ble_gattc_attr_info128_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Service count. */ - ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_prim_srvc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Include count. */ - ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_rel_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Characteristic count. */ - ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_char_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Descriptor count. */ - ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_desc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Attribute count. */ - uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ - union { - ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - } info; /**< Attribute information union. */ -} ble_gattc_evt_attr_info_disc_rsp_t; - -/**@brief GATT read by UUID handle value pair. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ -} ble_gattc_handle_value_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ -typedef struct { - uint16_t count; /**< Handle-Value Pair Count. */ - uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ - uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref - sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. - @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_val_by_uuid_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint16_t offset; /**< Offset of the attribute data. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ -typedef struct { - uint16_t len; /**< Concatenated Attribute values length. */ - uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder - for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_vals_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ - uint16_t offset; /**< Data offset. */ - uint16_t len; /**< Data length. */ - uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_write_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ -typedef struct { - uint16_t handle; /**< Handle to which the HVx operation applies. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_hvx_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ -typedef struct { - uint16_t server_rx_mtu; /**< Server RX MTU size. */ -} ble_gattc_evt_exchange_mtu_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gattc_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of write without response transmissions completed. */ -} ble_gattc_evt_write_cmd_tx_complete_t; - -/**@brief GATTC event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint16_t - error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ - union { - ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ - ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ - ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ - ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ - ble_gattc_evt_char_val_by_uuid_read_rsp_t - char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ - ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ - ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ - ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ - ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ - ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ - ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ - ble_gattc_evt_write_cmd_tx_complete_t - write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ - } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ -} ble_gattc_evt_t; - -/**@brief UUID discovery option. - * - * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the - * Vendor Specific UUID table. Disabled by default. - * - When disabled, if a procedure initiated by - * @ref sd_ble_gattc_primary_services_discover, - * @ref sd_ble_gattc_relationships_discover, - * @ref sd_ble_gattc_characteristics_discover, - * @ref sd_ble_gattc_descriptors_discover - * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set - * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use - * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding - * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will - * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - * @retval ::NRF_SUCCESS Set successfully. - * - */ -typedef struct { - uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ -} ble_gattc_opt_uuid_disc_t; - -/**@brief Option structure for GATTC options. */ -typedef union { - ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ -} ble_gattc_opt_t; - -/** @} */ - -/** @addtogroup BLE_GATTC_FUNCTIONS Functions - * @{ */ - -/**@brief Initiate or continue a GATT Primary Service Discovery procedure. - * - * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. - * If the last service has not been reached, this function must be called again with an updated start handle value to - * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] start_handle Handle to start searching from. - * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, - sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); - -/**@brief Initiate or continue a GATT Relationship Discovery procedure. - * - * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been - * reached, this must be called again with an updated handle range to continue the search. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, - sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Discovery procedure. - * - * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, - sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. - * - * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not - * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, - sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. - * - * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_uuid Pointer to a Characteristic value UUID to read. - * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, - sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, - ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. - * - * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or - * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read - * the complete value. - * - * @events - * @event{@ref BLE_GATTC_EVT_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute to be read. - * @param[in] offset Offset into the attribute value to be read. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); - -/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. - * - * @details This function initiates a GATT Read Multiple Characteristic Values procedure. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. - * @param[in] handle_count The number of handles in p_handles. - * - * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, - sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); - -/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) - * procedure. - * - * @details This function can perform all write procedures described in GATT. - * - * @note Only one write with response procedure can be ongoing per connection at a time. - * If the application tries to write with response while another write with response procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. - * - * @note The number of Write without Response that can be queued is configured by @ref - * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. - * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without - * response is complete. - * - * @note The application can keep track of the available queue element count for writes without responses by following the - * procedure below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} - * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_write_params A pointer to a write parameters structure. - * - * @retval ::NRF_SUCCESS Successfully started the Write procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event - * and retry. - * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. - * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); - -/**@brief Send a Handle Value Confirmation to the GATT Server. - * - * @mscs - * @mmsc{@ref BLE_GATTC_HVI_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute in the indication. - * - * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); - -/**@brief Discovers information about a range of attributes on a GATT server. - * - * @events - * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} - * @endevents - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range The range of handles to request information about. - * - * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, - sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value, and - * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @events - * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] client_rx_mtu Client RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - used for this connection. - * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent request to the server. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, - sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); - -/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * - * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * @note If the buffer contains different event, behavior is undefined. - * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with - * the next Handle-Value pair in each iteration. If the function returns other than - * @ref NRF_SUCCESS, it will not be changed. - * - To start iteration, initialize the structure to zero. - * - To continue, pass the value from previous iteration. - * - * \code - * ble_gattc_handle_value_t iter; - * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); - * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) - * { - * app_handle = iter.handle; - * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); - * } - * \endcode - * - * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. - * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. - */ -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter); - -/** @} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter) -{ - uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; - uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; - uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; - - if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { - p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; - p_iter->p_value = p_next + sizeof(uint16_t); - return NRF_SUCCESS; - } else { - return NRF_ERROR_NOT_FOUND; - } -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_GATTC_H__ */ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatts.h b/variants/wio-sdk-wm1110/softdevice/ble_gatts.h deleted file mode 100644 index dc94957cd..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gatts.h +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server - @{ - @brief Definitions and prototypes for the GATTS interface. - */ - -#ifndef BLE_GATTS_H__ -#define BLE_GATTS_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief GATTS API SVC numbers. - */ -enum BLE_GATTS_SVCS { - SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ - SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ - SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ - SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ - SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ - SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ - SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ - SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ - SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more - attributes. */ - SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ - SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ - SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ - SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ - SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ -}; - -/** - * @brief GATT Server Event IDs. - */ -enum BLE_GATTS_EVTS { - BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See - @ref ble_gatts_evt_write_t. */ - BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with - @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. - */ - BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref - sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ - BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. - */ - BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event - structure applies. */ - BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with - @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. - */ - BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref - ble_gatts_evt_timeout_t. */ - BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref - ble_gatts_evt_hvn_tx_complete_t. */ -}; - -/**@brief GATTS Configuration IDs. - * - * IDs that uniquely identify a GATTS configuration. - */ -enum BLE_GATTS_CFGS { - BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ - BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTS_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS - * @{ */ -#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ -#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths - * @{ */ -#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ -#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ -/** @} */ - -/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types - * @{ */ -#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ -#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ -#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types - * @{ */ -#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ -#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ -#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ -#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ -/** @} */ - -/** @defgroup BLE_GATTS_OPS GATT Server Operations - * @{ */ -#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ -/** @} */ - -/** @defgroup BLE_GATTS_VLOCS GATT Value Locations - * @{ */ -#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ -#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ -#define BLE_GATTS_VLOC_USER \ - 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ - of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ - There are no alignment requirements for the buffer. */ -/** @} */ - -/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types - * @{ */ -#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ -#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ -#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ -/** @} */ - -/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags - * @{ */ -#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ -#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ -/** @} */ - -/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values - * @{ - */ -#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ - (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size - * @{ - */ -#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ -#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ -/** @} */ - -/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults - * @{ - */ -#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTS_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. - The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ -} ble_gatts_conn_cfg_t; - -/**@brief Attribute metadata. */ -typedef struct { - ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vlen : 1; /**< Variable length attribute. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ - uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not - Write Command). */ -} ble_gatts_attr_md_t; - -/**@brief GATT Attribute. */ -typedef struct { - ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ - ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ - uint16_t init_len; /**< Initial attribute value length in bytes. */ - uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the - attribute value will be left uninitialized. */ - uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ - uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is - selected in the attribute metadata, this will have to point to a buffer that remains valid through the - lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any - other temporary location. The stack may access that memory directly without the application's - knowledge. For writable characteristics, this value must not be a location in flash memory.*/ -} ble_gatts_attr_t; - -/**@brief GATT Attribute Value. */ -typedef struct { - uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ - uint16_t offset; /**< Attribute value offset. */ - uint8_t *p_value; /**< Pointer to where value is stored or will be stored. - If value is stored in user memory, only the attribute length is updated when p_value == NULL. - Set to NULL when reading to obtain the complete length of the attribute value */ -} ble_gatts_value_t; - -/**@brief GATT Characteristic Presentation Format. */ -typedef struct { - uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ - int8_t exponent; /**< Exponent for integer data types. */ - uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ - uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ - uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ -} ble_gatts_char_pf_t; - -/**@brief GATT Characteristic metadata. */ -typedef struct { - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ - uint8_t const * - p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ - uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ - uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ - ble_gatts_char_pf_t const - *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ - ble_gatts_attr_md_t const - *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ -} ble_gatts_char_md_t; - -/**@brief GATT Characteristic Definition Handles. */ -typedef struct { - uint16_t value_handle; /**< Handle to the characteristic value. */ - uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ - uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ - uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ -} ble_gatts_char_handles_t; - -/**@brief GATT HVx parameters. */ -typedef struct { - uint16_t handle; /**< Characteristic Value Handle. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t offset; /**< Offset within the attribute value. */ - uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ - uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ -} ble_gatts_hvx_params_t; - -/**@brief GATT Authorization parameters. */ -typedef struct { - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. - Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, - as the data to be written needs to be stored and later provided by the application. */ - uint16_t offset; /**< Offset of the attribute value being updated. */ - uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ - uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ -} ble_gatts_authorize_params_t; - -/**@brief GATT Read or Write Authorize Reply parameters. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ - ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ - } params; /**< Reply Parameters. */ -} ble_gatts_rw_authorize_reply_params_t; - -/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref - BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ -} ble_gatts_cfg_service_changed_t; - -/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - @ref ble_gatts_attr_md_t::write_perm is out of range. - * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is - * not allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is - * allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. - * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported - */ -typedef struct { - ble_gatts_attr_md_t - perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ -} ble_gatts_cfg_service_changed_cccd_perm_t; - -/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The specified Attribute Table size is too small. - * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. - * - The specified Attribute Table size is not a multiple of 4. - */ -typedef struct { - uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref - BLE_GATTS_ATTR_TAB_SIZE_MIN. */ -} ble_gatts_cfg_attr_tab_size_t; - -/**@brief Config structure for GATTS configurations. */ -typedef union { - ble_gatts_cfg_service_changed_t - service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ - ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ - ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ -} ble_gatts_cfg_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ - uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref - sd_ble_gatts_value_set to finalize the writing operation. */ - uint16_t offset; /**< Offset for the write operation. */ - uint16_t len; /**< Length of the received data. */ - uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gatts_evt_write_t; - -/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint16_t offset; /**< Offset for the read operation. */ -} ble_gatts_evt_read_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ - ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ - } request; /**< Request Parameters. */ -} ble_gatts_evt_rw_authorize_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ -typedef struct { - uint8_t hint; /**< Hint (currently unused). */ -} ble_gatts_evt_sys_attr_missing_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ -} ble_gatts_evt_hvc_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ -typedef struct { - uint16_t client_rx_mtu; /**< Client RX MTU size. */ -} ble_gatts_evt_exchange_mtu_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gatts_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of notification transmissions completed. */ -} ble_gatts_evt_hvn_tx_complete_t; - -/**@brief GATTS event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ - union { - ble_gatts_evt_write_t write; /**< Write Event Parameters. */ - ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ - ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ - ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ - ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ - ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ - ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_gatts_evt_t; - -/** @} */ - -/** @addtogroup BLE_GATTS_FUNCTIONS Functions - * @{ */ - -/**@brief Add a service declaration to the Attribute Table. - * - * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to - * add a secondary service declaration that is not referenced by another service later in the Attribute Table. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. - * @param[in] p_uuid Pointer to service UUID. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a service declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); - -/**@brief Add an include declaration to the Attribute Table. - * - * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note The included service must already be present in the Attribute Table prior to this call. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID - * is used, it will be placed sequentially. - * @param[in] inc_srvc_handle Handle of the included service. - * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added an include declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - */ -SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, - sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); - -/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations - * to the Attribute Table. - * - * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and - * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format - * values. - * - * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic - * permissions. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_char_md Characteristic metadata. - * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. - * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a characteristic. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, - sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, - ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); - -/**@brief Add a descriptor to the Attribute Table. - * - * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is - * supported at this time). - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_attr Pointer to the attribute structure. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a descriptor. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, - sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); - -/**@brief Set the value of a given attribute. - * - * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully set the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - */ -SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, - sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Get the value of a given attribute. - * - * @note If the attribute value is longer than the size of the supplied buffer, - * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), - * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. - * The application may use this information to allocate a suitable buffer size. - * - * @note When retrieving system attribute values with this function, the connection handle - * may refer to an already disconnected connection. Refer to the documentation of - * @ref sd_ble_gatts_sys_attr_get for further information. - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - */ -SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, - sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Notify or Indicate an attribute value. - * - * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant - * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before - * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single - * API call. - * - * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during - * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, - * @ref NRF_ERROR_BUSY, - * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. - * The caller can check whether the value has been updated by looking at the contents of *(@ref - * ble_gatts_hvx_params_t::p_len). - * - * @note Only one indication procedure can be ongoing per connection at a time. - * If the application tries to indicate an attribute value while another indication procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. - * - * @note The number of Handle Value Notifications that can be queued is configured by @ref - * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. - * - * @note The application can keep track of the available queue element count for notifications by following the procedure - * below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} - * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_HVN_MSC} - * @mmsc{@ref BLE_GATTS_HVI_MSC} - * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data - * contains a non-NULL pointer the attribute value will be updated with the contents - * pointed by it before sending the notification or indication. If the attribute value - * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to - * contain the number of actual bytes written, else it will be set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute - * value. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application - * are available to notify and indicate. - * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and - * indicated. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions - * of the CCCD associated with this characteristic. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC - * event and retry. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. - * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); - -/**@brief Indicate the Service Changed attribute value. - * - * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute - * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will - * be issued. - * - * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. - * - * @events - * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_SC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] start_handle Start of affected attribute handle range. - * @param[in] end_handle End of affected attribute handle range. - * - * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref - * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the - * application. - * @retval ::NRF_ERROR_BUSY Procedure already in progress. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, - sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); - -/**@brief Respond to a Read/Write authorization request. - * - * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. - * - * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond - * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update - * is set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute - * Table updated. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, - * handle supplied does not match requested handle, - * or invalid data to be written provided by the application. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, - sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, - ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); - -/**@brief Update persistent system attribute information. - * - * @details Supply information about persistent system attributes to the stack, - * previously obtained using @ref sd_ble_gatts_sys_attr_get. - * This call is only allowed for active connections, and is usually - * made immediately after a connection is established with an known bonded device, - * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. - * - * p_sysattrs may point directly to the application's stored copy of the system attributes - * obtained using @ref sd_ble_gatts_sys_attr_get. - * If the pointer is NULL, the system attribute info is initialized, assuming that - * the application does not have any previously saved system attribute data for this device. - * - * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. - * - * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may - * have been completed only partially. This means that the state of the attribute table is undefined, and the application should - * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be modified. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be modified. - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. - * @param[in] len Size of data pointed by p_sys_attr_data, in octets. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully set the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref - * sd_ble_gatts_sys_attr_get. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, - sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); - -/**@brief Retrieve persistent system attribute information from the stack. - * - * @details This call is used to retrieve information about values to be stored persistently by the application - * during the lifetime of a connection or after it has been terminated. When a new connection is established with the - * same bonded device, the system attribute information retrieved with this function should be restored using using @ref - * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The - * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API - * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the - * system attributes may be written to at any time by the peer during a connection's lifetime. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be returned. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be returned. - * - * @mscs - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle of the recently terminated connection. - * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The - * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. - * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual - * length of system attribute data. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. - * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, - sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); - -/**@brief Retrieve the first valid user attribute handle. - * - * @param[out] p_handle Pointer to an integer where the handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully retrieved the handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); - -/**@brief Retrieve the attribute UUID and/or metadata. - * - * @param[in] handle Attribute handle - * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. - * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. - * - * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. - * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. - */ -SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); - -/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. - * - * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and - * - The Server RX MTU value. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @mscs - * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] server_rx_mtu Server RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - * used for this connection. - * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent response to the client. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATTS_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_hci.h b/variants/wio-sdk-wm1110/softdevice/ble_hci.h deleted file mode 100644 index 27f85d52e..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_hci.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ -*/ - -#ifndef BLE_HCI_H__ -#define BLE_HCI_H__ -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes - * @{ */ - -#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ -/*0x03 Hardware Failure -0x04 Page Timeout -*/ -#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ -#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ -#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ -#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ -/*0x09 Connection Limit Exceeded -0x0A Synchronous Connection Limit To A Device Exceeded -0x0B ACL Connection Already Exists*/ -#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ -/*0x0D Connection Rejected due to Limited Resources -0x0E Connection Rejected Due To Security Reasons -0x0F Connection Rejected due to Unacceptable BD_ADDR -0x10 Connection Accept Timeout Exceeded -0x11 Unsupported Feature or Parameter Value*/ -#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ -#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ - 0x14 /**< Remote Device Terminated Connection due to low \ - resources.*/ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ -#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ -/* -0x17 Repeated Attempts -0x18 Pairing Not Allowed -0x19 Unknown LMP PDU -*/ -#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ -/* -0x1B SCO Offset Rejected -0x1C SCO Interval Rejected -0x1D SCO Air Mode Rejected*/ -#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ -#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ -/*0x20 Unsupported LMP Parameter Value -0x21 Role Change Not Allowed -*/ -#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ -#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ -#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ -/*0x25 Encryption Mode Not Acceptable -0x26 Link Key Can Not be Changed -0x27 Requested QoS Not Supported -*/ -#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ -#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ -#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ -/* -0x2B Reserved -0x2C QoS Unacceptable Parameter -0x2D QoS Rejected -0x2E Channel Classification Not Supported -0x2F Insufficient Security -*/ -#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ -/* -0x31 Reserved -0x32 Role Switch Pending -0x33 Reserved -0x34 Reserved Slot Violation -0x35 Role Switch Failed -0x36 Extended Inquiry Response Too Large -0x37 Secure Simple Pairing Not Supported By Host. -0x38 Host Busy - Pairing -0x39 Connection Rejected due to No Suitable Channel Found*/ -#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ -#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ -#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ -#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ -#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_HCI_H__ - -/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h b/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h deleted file mode 100644 index 5f4bd277d..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) - @{ - @brief Definitions and prototypes for the L2CAP interface. - */ - -#ifndef BLE_L2CAP_H__ -#define BLE_L2CAP_H__ - -#include "ble_err.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology - * @{ - * @details - * - * L2CAP SDU - * - A data unit that the application can send/receive to/from a peer. - * - * L2CAP PDU - * - A data unit that is exchanged between local and remote L2CAP entities. - * It consists of L2CAP protocol control information and payload fields. - * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. - * - * L2CAP MTU - * - The maximum length of an L2CAP SDU. - * - * L2CAP MPS - * - The maximum length of an L2CAP PDU payload field. - * - * Credits - * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. - * @} */ - -/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief L2CAP API SVC numbers. */ -enum BLE_L2CAP_SVCS { - SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ - SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ - SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ -}; - -/**@brief L2CAP Event IDs. */ -enum BLE_L2CAP_EVTS { - BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. - \n Reply with @ref sd_ble_l2cap_ch_setup. - \n See @ref ble_l2cap_evt_ch_setup_request_t. */ - BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. - \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ - BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. - \n See @ref ble_l2cap_evt_ch_setup_t. */ - BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. - \n No additional event structure applies. */ - BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. - \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ - BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. - \n See @ref ble_l2cap_evt_ch_credit_t. */ - BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. - \n See @ref ble_l2cap_evt_ch_rx_t. */ - BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. - \n See @ref ble_l2cap_evt_ch_tx_t. */ -}; - -/** @} */ - -/**@addtogroup BLE_L2CAP_DEFINES Defines - * @{ */ - -/**@brief Maximum number of L2CAP channels per connection. */ -#define BLE_L2CAP_CH_COUNT_MAX (64) - -/**@brief Minimum L2CAP MTU, in bytes. */ -#define BLE_L2CAP_MTU_MIN (23) - -/**@brief Minimum L2CAP MPS, in bytes. */ -#define BLE_L2CAP_MPS_MIN (23) - -/**@brief Invalid CID. */ -#define BLE_L2CAP_CID_INVALID (0x0000) - -/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ -#define BLE_L2CAP_CREDITS_DEFAULT (1) - -/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources - * @{ */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ - /** @} */ - -/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes - * @{ */ -#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ -#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ -#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ -#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ -#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ -#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ -#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ - (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ -#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ -/** @} */ - -/** @} */ - -/**@addtogroup BLE_L2CAP_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note These parameters are set per connection, so all L2CAP channels created on this connection - * will have the same parameters. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. - * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. - */ -typedef struct { - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to receive on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to transmit on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per - L2CAP channel. The minimum value is one. */ - uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission - per L2CAP channel. The minimum value is one. */ - uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection - with this configuration. The default value is zero, the maximum - value is @ref BLE_L2CAP_CH_COUNT_MAX. - @note if this parameter is set to zero, all other parameters in - @ref ble_l2cap_conn_cfg_t are ignored. */ -} ble_l2cap_conn_cfg_t; - -/**@brief L2CAP channel RX parameters. */ -typedef struct { - uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to - receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be - able to receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. - - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ - ble_data_t sdu_buf; /**< SDU data buffer for reception. - - If @ref ble_data_t::p_data is non-NULL, initial credits are - issued to the peer. - - If @ref ble_data_t::p_data is NULL, no initial credits are - issued to the peer. */ -} ble_l2cap_ch_rx_params_t; - -/**@brief L2CAP channel setup parameters. */ -typedef struct { - ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting - setup of an L2CAP channel, ignored otherwise. */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. - Used when replying to a setup request of an L2CAP - channel, ignored otherwise. */ -} ble_l2cap_ch_setup_params_t; - -/**@brief L2CAP channel TX parameters. */ -typedef struct { - uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to - transmit on this L2CAP channel. */ - uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is - able to receive on this L2CAP channel. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able - to transmit on this L2CAP channel. This is effective tx_mps, - selected by the SoftDevice as - MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ - uint16_t credits; /**< Initial credits given by the peer. */ -} ble_l2cap_ch_tx_params_t; - -/**@brief L2CAP Channel Setup Request event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ -} ble_l2cap_evt_ch_setup_request_t; - -/**@brief L2CAP Channel Setup Refused event. */ -typedef struct { - uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ -} ble_l2cap_evt_ch_setup_refused_t; - -/**@brief L2CAP Channel Setup Completed event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ -} ble_l2cap_evt_ch_setup_t; - -/**@brief L2CAP Channel SDU Data Buffer Released event. */ -typedef struct { - ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice - returns SDU data buffers supplied by the application, which have - not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or - @ref BLE_L2CAP_EVT_CH_TX event. */ -} ble_l2cap_evt_ch_sdu_buf_released_t; - -/**@brief L2CAP Channel Credit received event. */ -typedef struct { - uint16_t credits; /**< Additional credits given by the peer. */ -} ble_l2cap_evt_ch_credit_t; - -/**@brief L2CAP Channel received SDU event. */ -typedef struct { - uint16_t sdu_len; /**< Total SDU length, in bytes. */ - ble_data_t sdu_buf; /**< SDU data buffer. - @note If there is not enough space in the buffer - (sdu_buf.len < sdu_len) then the rest of the SDU will be - silently discarded by the SoftDevice. */ -} ble_l2cap_evt_ch_rx_t; - -/**@brief L2CAP Channel transmitted SDU event. */ -typedef struct { - ble_data_t sdu_buf; /**< SDU data buffer. */ -} ble_l2cap_evt_ch_tx_t; - -/**@brief L2CAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occured. */ - uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or - @ref BLE_L2CAP_CID_INVALID if not present. */ - union { - ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ - ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ - ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ - ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ - ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ - ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ - ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_l2cap_evt_t; - -/** @} */ - -/**@addtogroup BLE_L2CAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set up an L2CAP channel. - * - * @details This function is used to: - * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. - * - Reply to a setup request of an L2CAP channel (if called in response to a - * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection - * Response packet to a peer. - * - * @note A call to this function will require the application to keep the SDU data buffer alive - * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or - * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} - * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: - * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP - * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * event when replying to a setup request of an L2CAP channel. - * - As output: local_cid for this channel. - * @param[in] p_params L2CAP channel parameters. - * - * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, - * see @ref ble_l2cap_conn_cfg_t::ch_count. - */ -SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, - sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); - -/**@brief Release an L2CAP channel. - * - * @details This sends a Disconnection Request packet to a peer. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * - * @retval ::NRF_SUCCESS Successfully queued request for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); - -/**@brief Receive an SDU on an L2CAP channel. - * - * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers - * for reception per L2CAP channel. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Buffer accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a - * @ref BLE_L2CAP_EVT_CH_RX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Transmit an SDU on an L2CAP channel. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for - * transmission per L2CAP channel. - * - * @note The application can keep track of the available credits for transmission by following - * the procedure below: - * - Store initial credits given by the peer in a variable. - * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Decrement the variable, which stores the currently available credits, by - * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns - * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Increment the variable, which stores the currently available credits, by additional - * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than - * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in - * @ref BLE_L2CAP_EVT_CH_SETUP event. - * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a - * @ref BLE_L2CAP_EVT_CH_TX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Advanced SDU reception flow control. - * - * @details Adjust the way the SoftDevice issues credits to the peer. - * This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set - * the value that will be used for newly created channels. - * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every - * time it starts using a new reception buffer. - * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will - * use if this function is not called. - * - If set to zero, the SoftDevice will stop issuing credits for new reception - * buffers the application provides or has provided. SDU reception that is - * currently ongoing will be allowed to complete. - * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be - * written by the SoftDevice with the number of credits that is or will be - * available to the peer. If the value written by the SoftDevice is 0 when - * credits parameter was set to 0, the peer will not be able to send more - * data until more credits are provided by calling this function again with - * credits > 0. This parameter is ignored when local_cid is set to - * @ref BLE_L2CAP_CID_INVALID. - * - * @note Application should take care when setting number of credits higher than default value. In - * this case the application must make sure that the SoftDevice always has reception buffers - * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have - * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic - * on the connection handle may be stalled until the SoftDevice again has an available - * reception buffer. This applies even if the application has used this call to set the - * credits back to default, or zero. - * - * @retval ::NRF_SUCCESS Flow control parameters accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, - sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_L2CAP_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_ranges.h b/variants/wio-sdk-wm1110/softdevice/ble_ranges.h deleted file mode 100644 index 2768e4996..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_ranges.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_ranges Module specific SVC, event and option number subranges - @{ - - @brief Definition of SVC, event and option number subranges for each API module. - - @note - SVCs, event and option numbers are split into subranges for each API module. - Each module receives its entire allocated range of SVC calls, whether implemented or not, - but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. - - Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, - rather than the last SVC function call actually defined and implemented. - - Specific SVC, event and option values are defined in each module's ble_.h file, - which defines names of each individual SVC code based on the range start value. -*/ - -#ifndef BLE_RANGES_H__ -#define BLE_RANGES_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ -#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ - -#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ -#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ - -#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ -#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ - -#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ -#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ - -#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ -#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ - -#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ - -#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ -#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ - -#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ -#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ - -#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ -#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ - -#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ -#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ - -#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ -#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ - -#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ - -#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ -#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ - -#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ -#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ - -#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ -#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ - -#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ -#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ - -#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ -#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ - -#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ -#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ - -#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ - -#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ -#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ - -#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ -#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ - -#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ -#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ - -#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ -#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ - -#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ -#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ - -#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ -#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ - -#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ -#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_RANGES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_types.h b/variants/wio-sdk-wm1110/softdevice/ble_types.h deleted file mode 100644 index db3656cfd..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_types.h +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_types Common types and macro definitions - @{ - - @brief Common types and macro definitions for the BLE SoftDevice. - */ - -#ifndef BLE_TYPES_H__ -#define BLE_TYPES_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_TYPES_DEFINES Defines - * @{ */ - -/** @defgroup BLE_CONN_HANDLES BLE Connection Handles - * @{ */ -#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ -#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ -/** @} */ - -/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs - * @{ */ -/* Generic UUIDs, applicable to all services */ -#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ -#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ -#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ -#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ -#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ -#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ -/* GATT specific UUIDs */ -#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ -#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ -/* GAP specific UUIDs */ -#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ -#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ -/** @} */ - -/** @defgroup BLE_UUID_TYPES Types of UUID - * @{ */ -#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ -#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ -#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ -/** @} */ - -/** @defgroup BLE_APPEARANCES Bluetooth Appearance values - * @note Retrieved from - * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml - * @{ */ -#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ -#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ -#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ -#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ -#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ -#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ -#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ -#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ -#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ -#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ -#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ -#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ -#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ -#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ -#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ -#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ -#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ -#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ -#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ -#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ -#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ -#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ -#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ -#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ -#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ -#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ -#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ -#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ -#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ -#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ -#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ -#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ -#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ -#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ -#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ -#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ - 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ - 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ -/** @} */ - -/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ -#define BLE_UUID_BLE_ASSIGN(instance, value) \ - do { \ - instance.type = BLE_UUID_TYPE_BLE; \ - instance.uuid = value; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ -#define BLE_UUID_COPY_PTR(dst, src) \ - do { \ - (dst)->type = (src)->type; \ - (dst)->uuid = (src)->uuid; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ -#define BLE_UUID_COPY_INST(dst, src) \ - do { \ - (dst).type = (src).type; \ - (dst).uuid = (src).uuid; \ - } while (0) - -/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) - -/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) - -/** @} */ - -/** @addtogroup BLE_TYPES_STRUCTURES Structures - * @{ */ - -/** @brief 128 bit UUID values. */ -typedef struct { - uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ -} ble_uuid128_t; - -/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ -typedef struct { - uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ - uint8_t - type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ -} ble_uuid_t; - -/**@brief Data structure. */ -typedef struct { - uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ - uint16_t len; /**< Length of the data buffer, in bytes. */ -} ble_data_t; - -/** @} */ -#ifdef __cplusplus -} -#endif - -#endif /* BLE_TYPES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h deleted file mode 100644 index 4e0bd752a..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_mbr_api Master Boot Record API - @{ - - @brief APIs for updating SoftDevice and BootLoader - -*/ - -#ifndef NRF_MBR_H__ -#define NRF_MBR_H__ - -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_MBR_DEFINES Defines - * @{ */ - -/**@brief MBR SVC Base number. */ -#define MBR_SVC_BASE (0x18) - -/**@brief Page size in words. */ -#define MBR_PAGE_SIZE_IN_WORDS (1024) - -/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. -This is the offset where the first byte of the SoftDevice hex file is written. */ -#define MBR_SIZE (0x1000) - -/** @brief Location (in the flash memory) of the bootloader address. */ -#define MBR_BOOTLOADER_ADDR (0xFF8) - -/** @brief Location (in UICR) of the bootloader address. */ -#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) - -/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ -#define MBR_PARAM_PAGE_ADDR (0xFFC) - -/** @brief Location (in UICR) of the address of the MBR parameter page. */ -#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) - -/** @} */ - -/** @addtogroup NRF_MBR_ENUMS Enumerations - * @{ */ - -/**@brief nRF Master Boot Record API SVC numbers. */ -enum NRF_MBR_SVCS { - SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ -}; - -/**@brief Possible values for ::sd_mbr_command_t.command */ -enum NRF_MBR_COMMANDS { - SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ - SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ - SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any - parameters in ::sd_mbr_command_t params.*/ - SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ - SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see - ::sd_mbr_command_vector_table_base_set_t*/ - SD_MBR_COMMAND_RESERVED, - SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see - ::sd_mbr_command_irq_forward_address_set_t*/ -}; - -/** @} */ - -/** @addtogroup NRF_MBR_TYPES Types - * @{ */ - -/**@brief This command copies part of a new SoftDevice - * - * The destination area is erased before copying. - * If dst is in the middle of a flash page, that whole flash page will be erased. - * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. - * - * The user of this function is responsible for setting the BPROT registers. - * - * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. - * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. - */ -typedef struct { - uint32_t *src; /**< Pointer to the source of data to be copied.*/ - uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ - uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ -} sd_mbr_command_copy_sd_t; - -/**@brief This command works like memcmp, but takes the length in words. - * - * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. - * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. - */ -typedef struct { - uint32_t *ptr1; /**< Pointer to block of memory. */ - uint32_t *ptr2; /**< Pointer to block of memory. */ - uint32_t len; /**< Number of 32 bit words to compare.*/ -} sd_mbr_command_compare_t; - -/**@brief This command copies a new BootLoader. - * - * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to - * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * The bootloader destination is erased by this function. - * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is - * not intended to be written. - * - * On success, this function will not return. It will start the new bootloader from reset-vector as normal. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. - * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ - uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ -} sd_mbr_command_copy_bl_t; - -/**@brief Change the address the MBR starts after a reset - * - * Once this function has been called, this address is where the MBR will start to forward - * interrupts to after a reset. - * - * To restore default forwarding, this function should be called with @ref address set to 0. If a - * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will - * be forwarded to the SoftDevice. - * - * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or - * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * On success, this function will not return. It will reset the device. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_vector_table_base_set_t; - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR - * - * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not - * change where the MBR starts after reset. - * - * @retval ::NRF_SUCCESS - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_irq_forward_address_set_t; - -/**@brief Input structure containing data used when calling ::sd_mbr_command - * - * Depending on what command value that is set, the corresponding params value type must also be - * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command - * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. - */ -typedef struct { - uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ - union { - sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ - sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ - sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ - sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ - sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ - } params; /**< Command parameters. */ -} sd_mbr_command_t; - -/** @} */ - -/** @addtogroup NRF_MBR_FUNCTIONS Functions - * @{ */ - -/**@brief Issue Master Boot Record commands - * - * Commands used when updating a SoftDevice and bootloader. - * - * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires - * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash - * page. The location of the flash page should be provided by the application in either - * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR - * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to - * store the command before reset. When an address is specified, the page it refers to must not be - * used by the application. If no address is provided by the application, i.e. both - * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use - * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. - * - * @param[in] param Pointer to a struct describing the command. - * - * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, - * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, - * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t - * - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided - * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. - */ -SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_MBR_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error.h b/variants/wio-sdk-wm1110/softdevice/nrf_error.h deleted file mode 100644 index fb2831e19..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_error.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_error SoftDevice Global Error Codes - @{ - - @brief Global Error definitions -*/ - -/* Header guard */ -#ifndef NRF_ERROR_H__ -#define NRF_ERROR_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions - * @{ */ -#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base -#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base -#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base -#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base -/** @} */ - -#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command -#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing -#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled -#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error -#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation -#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found -#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported -#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter -#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state -#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length -#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags -#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data -#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size -#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out -#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer -#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation -#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address -#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy -#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. -#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h deleted file mode 100644 index 2fd621057..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_sdm_api - @{ - @defgroup nrf_sdm_error SoftDevice Manager Error Codes - @{ - - @brief Error definitions for the SDM API -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SDM_H__ -#define NRF_ERROR_SDM_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. -#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ - (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having - ///< enabled SoftDevice interrupts). -#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ - (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SDM_H__ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h deleted file mode 100644 index cbd0ba8ac..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_soc_api - @{ - @defgroup nrf_soc_error SoC Library Error Codes - @{ - - @brief Error definitions for the SoC library - -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SOC_H__ -#define NRF_ERROR_SOC_H__ - -#include "nrf_error.h" -#ifdef __cplusplus -extern "C" { -#endif - -/* Mutex Errors */ -#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken - -/* NVIC errors */ -#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available -#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed -#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return - -/* Power errors */ -#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown -#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown -#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return - -/* Rand errors */ -#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values - -/* PPI errors */ -#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel -#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SOC_H__ -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h b/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h deleted file mode 100644 index d4ab204d9..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_nvic_api SoftDevice NVIC API - * @{ - * - * @note In order to use this module, the following code has to be added to a .c file: - * \code - * nrf_nvic_state_t nrf_nvic_state = {0}; - * \endcode - * - * @note Definitions and declarations starting with __ (double underscore) in this header file are - * not intended for direct use by the application. - * - * @brief APIs for the accessing NVIC when using a SoftDevice. - * - */ - -#ifndef NRF_NVIC_H__ -#define NRF_NVIC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_NVIC_DEFINES Defines - * @{ */ - -/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions - * @{ */ - -#define __NRF_NVIC_NVMC_IRQn \ - (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ - number in the MDK. */ - -#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ - -/**@brief Interrupt priority levels used by the SoftDevice. */ -#define __NRF_NVIC_SD_IRQ_PRIOS \ - ((uint8_t)((1U << 0) /**< Priority level high .*/ \ - | (1U << 1) /**< Priority level medium. */ \ - | (1U << 4) /**< Priority level low. */ \ - )) - -/**@brief Interrupt priority levels available to the application. */ -#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ -#define __NRF_NVIC_SD_IRQS_0 \ - ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ - (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ - (1U << (uint32_t)SWI5_IRQn))) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ -#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) - -/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ -#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) - -/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ -#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) - -/**@} */ - -/**@} */ - -/**@addtogroup NRF_NVIC_VARIABLES Variables - * @{ */ - -/**@brief Type representing the state struct for the SoftDevice NVIC module. */ -typedef struct { - uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ - uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ -} nrf_nvic_state_t; - -/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an - * application source file. */ -extern nrf_nvic_state_t nrf_nvic_state; - -/**@} */ - -/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions - * @{ */ - -/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. - * - * @retval The value of PRIMASK prior to disabling the interrupts. - */ -__STATIC_INLINE int __sd_nvic_irq_disable(void); - -/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. - */ -__STATIC_INLINE void __sd_nvic_irq_enable(void); - -/**@brief Checks if IRQn is available to application - * @param[in] IRQn IRQ to check - * - * @retval 1 (true) if the IRQ to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); - -/**@brief Checks if priority is available to application - * @param[in] priority priority to check - * - * @retval 1 (true) if the priority to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); - -/**@} */ - -/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions - * @{ */ - -/**@brief Enable External Interrupt. - * @note Corresponds to NVIC_EnableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was enabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); - -/**@brief Disable External Interrupt. - * @note Corresponds to NVIC_DisableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was disabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); - -/**@brief Get Pending Interrupt. - * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. - * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. - * - * @retval ::NRF_SUCCESS The interrupt is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); - -/**@brief Set Pending Interrupt. - * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt is set pending. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); - -/**@brief Clear Pending Interrupt. - * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); - -/**@brief Set Interrupt Priority. - * @note Corresponds to NVIC_SetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * @pre Priority is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. - * @param[in] priority A valid IRQ priority for use by the application. - * - * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); - -/**@brief Get Interrupt Priority. - * @note Corresponds to NVIC_GetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. - * @param[out] p_priority Return value from NVIC_GetPriority. - * - * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); - -/**@brief System Reset. - * @note Corresponds to NVIC_SystemReset in CMSIS. - * - * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN - */ -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); - -/**@brief Enter critical region. - * - * @post Application interrupts will be disabled. - * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each - * execution context - * @sa sd_nvic_critical_region_exit - * - * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); - -/**@brief Exit critical region. - * - * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. - * @post If not in a nested critical region, the application interrupts will restored to the state before - * ::sd_nvic_critical_region_enter was called. - * - * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa - * sd_nvic_critical_region_enter. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); - -/**@} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE int __sd_nvic_irq_disable(void) -{ - int pm = __get_PRIMASK(); - __disable_irq(); - return pm; -} - -__STATIC_INLINE void __sd_nvic_irq_enable(void) -{ - __enable_irq(); -} - -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) -{ - if (IRQn < 32) { - return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; - } else if (IRQn < 64) { - return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; - } else { - return 1; - } -} - -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) -{ - if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { - return 0; - } - return 1; -} - -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= - (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); - } else { - NVIC_EnableIRQ(IRQn); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); - } else { - NVIC_DisableIRQ(IRQn); - } - - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_pending_irq = NVIC_GetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_SetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_ClearPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (!__sd_nvic_is_app_accessible_priority(priority)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - NVIC_SetPriority(IRQn, (uint32_t)priority); - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) -{ - NVIC_SystemReset(); - return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) -{ - int was_masked = __sd_nvic_irq_disable(); - if (!nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__cr_flag = 1; - nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); - NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; - nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); - NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; - *p_is_nested_critical_region = 0; - } else { - *p_is_nested_critical_region = 1; - } - if (!was_masked) { - __sd_nvic_irq_enable(); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) -{ - if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { - int was_masked = __sd_nvic_irq_disable(); - NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; - NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; - nrf_nvic_state.__cr_flag = 0; - if (!was_masked) { - __sd_nvic_irq_enable(); - } - } - - return NRF_SUCCESS; -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif - -#endif // NRF_NVIC_H__ - -/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h deleted file mode 100644 index 2786a86a4..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_sdm.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_sdm_api SoftDevice Manager API - @{ - - @brief APIs for SoftDevice management. - -*/ - -#ifndef NRF_SDM_H__ -#define NRF_SDM_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_sdm.h" -#include "nrf_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ -#ifdef NRFSOC_DOXYGEN -/// Declared in nrf_mbr.h -#define MBR_SIZE 0 -#warning test -#endif - -/** @brief The major version for the SoftDevice binary distributed with this header file. */ -#define SD_MAJOR_VERSION (7) - -/** @brief The minor version for the SoftDevice binary distributed with this header file. */ -#define SD_MINOR_VERSION (3) - -/** @brief The bugfix version for the SoftDevice binary distributed with this header file. */ -#define SD_BUGFIX_VERSION (0) - -/** @brief The SoftDevice variant of this firmware. */ -#define SD_VARIANT_ID 140 - -/** @brief The full version number for the SoftDevice binary this header file was distributed - * with, as a decimal number in the form Mmmmbbb, where: - * - M is major version (one or more digits) - * - mmm is minor version (three digits) - * - bbb is bugfix version (three digits). */ -#define SD_VERSION (SD_MAJOR_VERSION * 1000000 + SD_MINOR_VERSION * 1000 + SD_BUGFIX_VERSION) - -/** @brief SoftDevice Manager SVC Base number. */ -#define SDM_SVC_BASE 0x10 - -/** @brief SoftDevice unique string size in bytes. */ -#define SD_UNIQUE_STR_SIZE 20 - -/** @brief Invalid info field. Returned when an info field does not exist. */ -#define SDM_INFO_FIELD_INVALID (0) - -/** @brief Defines the SoftDevice Information Structure location (address) as an offset from -the start of the SoftDevice (without MBR)*/ -#define SOFTDEVICE_INFO_STRUCT_OFFSET (0x2000) - -/** @brief Defines the absolute SoftDevice Information Structure location (address) when the - * SoftDevice is installed just above the MBR (the usual case). */ -#define SOFTDEVICE_INFO_STRUCT_ADDRESS (SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE) - -/** @brief Defines the offset for the SoftDevice Information Structure size value relative to the - * SoftDevice base address. The size value is of type uint8_t. */ -#define SD_INFO_STRUCT_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET) - -/** @brief Defines the offset for the SoftDevice size value relative to the SoftDevice base address. - * The size value is of type uint32_t. */ -#define SD_SIZE_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x08) - -/** @brief Defines the offset for FWID value relative to the SoftDevice base address. The FWID value - * is of type uint16_t. */ -#define SD_FWID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x0C) - -/** @brief Defines the offset for the SoftDevice ID relative to the SoftDevice base address. The ID - * is of type uint32_t. */ -#define SD_ID_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10) - -/** @brief Defines the offset for the SoftDevice version relative to the SoftDevice base address in - * the same format as @ref SD_VERSION, stored as an uint32_t. */ -#define SD_VERSION_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14) - -/** @brief Defines the offset for the SoftDevice unique string relative to the SoftDevice base address. - * The SD_UNIQUE_STR is stored as an array of uint8_t. The size of array is @ref SD_UNIQUE_STR_SIZE. - */ -#define SD_UNIQUE_STR_OFFSET (SOFTDEVICE_INFO_STRUCT_OFFSET + 0x18) - -/** @brief Defines a macro for retrieving the actual SoftDevice Information Structure size value - * from a given base address. Use @ref MBR_SIZE as the argument when the SoftDevice is - * installed just above the MBR (the usual case). */ -#define SD_INFO_STRUCT_SIZE_GET(baseaddr) (*((uint8_t *)((baseaddr) + SD_INFO_STRUCT_SIZE_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice size value from a given base - * address. Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above - * the MBR (the usual case). */ -#define SD_SIZE_GET(baseaddr) (*((uint32_t *)((baseaddr) + SD_SIZE_OFFSET))) - -/** @brief Defines the amount of flash that is used by the SoftDevice. - * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed - * just above the MBR (the usual case). - */ -#define SD_FLASH_SIZE 0x26000 - -/** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual - * case). */ -#define SD_FWID_GET(baseaddr) (*((uint16_t *)((baseaddr) + SD_FWID_OFFSET))) - -/** @brief Defines a macro for retrieving the actual SoftDevice ID from a given base address. Use - * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the - * usual case). */ -#define SD_ID_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_ID_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the actual SoftDevice version from a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_VERSION_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (*((uint32_t *)((baseaddr) + SD_VERSION_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/** @brief Defines a macro for retrieving the address of SoftDevice unique str based on a given base address. - * Use @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR - * (the usual case). */ -#define SD_UNIQUE_STR_ADDR_GET(baseaddr) \ - ((SD_INFO_STRUCT_SIZE_GET(baseaddr) > (SD_UNIQUE_STR_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) \ - ? (((uint8_t *)((baseaddr) + SD_UNIQUE_STR_OFFSET))) \ - : SDM_INFO_FIELD_INVALID) - -/**@defgroup NRF_FAULT_ID_RANGES Fault ID ranges - * @{ */ -#define NRF_FAULT_ID_SD_RANGE_START 0x00000000 /**< SoftDevice ID range start. */ -#define NRF_FAULT_ID_APP_RANGE_START 0x00001000 /**< Application ID range start. */ -/**@} */ - -/**@defgroup NRF_FAULT_IDS Fault ID types - * @{ */ -#define NRF_FAULT_ID_SD_ASSERT \ - (NRF_FAULT_ID_SD_RANGE_START + 1) /**< SoftDevice assertion. The info parameter is reserved for future used. */ -#define NRF_FAULT_ID_APP_MEMACC \ - (NRF_FAULT_ID_APP_RANGE_START + 1) /**< Application invalid memory access. The info parameter will contain 0x00000000, \ - in case of SoftDevice RAM access violation. In case of SoftDevice peripheral \ - register violation the info parameter will contain the sub-region number of \ - PREGION[0], on whose address range the disallowed write access caused the \ - memory access fault. */ -/**@} */ - -/** @} */ - -/** @addtogroup NRF_SDM_ENUMS Enumerations - * @{ */ - -/**@brief nRF SoftDevice Manager API SVC numbers. */ -enum NRF_SD_SVCS { - SD_SOFTDEVICE_ENABLE = SDM_SVC_BASE, /**< ::sd_softdevice_enable */ - SD_SOFTDEVICE_DISABLE, /**< ::sd_softdevice_disable */ - SD_SOFTDEVICE_IS_ENABLED, /**< ::sd_softdevice_is_enabled */ - SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, /**< ::sd_softdevice_vector_table_base_set */ - SVC_SDM_LAST /**< Placeholder for last SDM SVC */ -}; - -/** @} */ - -/** @addtogroup NRF_SDM_DEFINES Defines - * @{ */ - -/**@defgroup NRF_CLOCK_LF_ACCURACY Clock accuracy - * @{ */ - -#define NRF_CLOCK_LF_ACCURACY_250_PPM (0) /**< Default: 250 ppm */ -#define NRF_CLOCK_LF_ACCURACY_500_PPM (1) /**< 500 ppm */ -#define NRF_CLOCK_LF_ACCURACY_150_PPM (2) /**< 150 ppm */ -#define NRF_CLOCK_LF_ACCURACY_100_PPM (3) /**< 100 ppm */ -#define NRF_CLOCK_LF_ACCURACY_75_PPM (4) /**< 75 ppm */ -#define NRF_CLOCK_LF_ACCURACY_50_PPM (5) /**< 50 ppm */ -#define NRF_CLOCK_LF_ACCURACY_30_PPM (6) /**< 30 ppm */ -#define NRF_CLOCK_LF_ACCURACY_20_PPM (7) /**< 20 ppm */ -#define NRF_CLOCK_LF_ACCURACY_10_PPM (8) /**< 10 ppm */ -#define NRF_CLOCK_LF_ACCURACY_5_PPM (9) /**< 5 ppm */ -#define NRF_CLOCK_LF_ACCURACY_2_PPM (10) /**< 2 ppm */ -#define NRF_CLOCK_LF_ACCURACY_1_PPM (11) /**< 1 ppm */ - -/** @} */ - -/**@defgroup NRF_CLOCK_LF_SRC Possible LFCLK oscillator sources - * @{ */ - -#define NRF_CLOCK_LF_SRC_RC (0) /**< LFCLK RC oscillator. */ -#define NRF_CLOCK_LF_SRC_XTAL (1) /**< LFCLK crystal oscillator. */ -#define NRF_CLOCK_LF_SRC_SYNTH (2) /**< LFCLK Synthesized from HFCLK. */ - -/** @} */ - -/** @} */ - -/** @addtogroup NRF_SDM_TYPES Types - * @{ */ - -/**@brief Type representing LFCLK oscillator source. */ -typedef struct { - uint8_t source; /**< LF oscillator clock source, see @ref NRF_CLOCK_LF_SRC. */ - uint8_t rc_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: Calibration timer interval in 1/4 second - units (nRF52: 1-32). - @note To avoid excessive clock drift, 0.5 degrees Celsius is the - maximum temperature change allowed in one calibration timer - interval. The interval should be selected to ensure this. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. */ - uint8_t rc_temp_ctiv; /**< Only for ::NRF_CLOCK_LF_SRC_RC: How often (in number of calibration - intervals) the RC oscillator shall be calibrated if the temperature - hasn't changed. - 0: Always calibrate even if the temperature hasn't changed. - 1: Only calibrate if the temperature has changed (legacy - nRF51 only). - 2-33: Check the temperature and only calibrate if it has changed, - however calibration will take place every rc_temp_ctiv - intervals in any case. - - @note Must be 0 if source is not ::NRF_CLOCK_LF_SRC_RC. - - @note For nRF52, the application must ensure calibration at least once - every 8 seconds to ensure +/-500 ppm clock stability. The - recommended configuration for ::NRF_CLOCK_LF_SRC_RC on nRF52 is - rc_ctiv=16 and rc_temp_ctiv=2. This will ensure calibration at - least once every 8 seconds and for temperature changes of 0.5 - degrees Celsius every 4 seconds. See the Product Specification - for the nRF52 device being used for more information.*/ - uint8_t accuracy; /**< External clock accuracy used in the LL to compute timing - windows, see @ref NRF_CLOCK_LF_ACCURACY.*/ -} nrf_clock_lf_cfg_t; - -/**@brief Fault Handler type. - * - * When certain unrecoverable errors occur within the application or SoftDevice the fault handler will be called back. - * The protocol stack will be in an undefined state when this happens and the only way to recover will be to - * perform a reset, using e.g. CMSIS NVIC_SystemReset(). - * If the application returns from the fault handler the SoftDevice will call NVIC_SystemReset(). - * - * @note It is recommended to either perform a reset in the fault handler or to let the SoftDevice reset the device. - * Otherwise SoC peripherals may behave in an undefined way. For example, the RADIO peripherial may - * continously transmit packets. - * - * @note This callback is executed in HardFault context, thus SVC functions cannot be called from the fault callback. - * - * @param[in] id Fault identifier. See @ref NRF_FAULT_IDS. - * @param[in] pc The program counter of the instruction that triggered the fault. - * @param[in] info Optional additional information regarding the fault. Refer to each Fault identifier for details. - * - * @note When id is set to @ref NRF_FAULT_ID_APP_MEMACC, pc will contain the address of the instruction being executed at the time - * when the fault is detected by the CPU. The CPU program counter may have advanced up to 2 instructions (no branching) after the - * one that triggered the fault. - */ -typedef void (*nrf_fault_handler_t)(uint32_t id, uint32_t pc, uint32_t info); - -/** @} */ - -/** @addtogroup NRF_SDM_FUNCTIONS Functions - * @{ */ - -/**@brief Enables the SoftDevice and by extension the protocol stack. - * - * @note Some care must be taken if a low frequency clock source is already running when calling this function: - * If the LF clock has a different source then the one currently running, it will be stopped. Then, the new - * clock source will be started. - * - * @note This function has no effect when returning with an error. - * - * @post If return code is ::NRF_SUCCESS - * - SoC library and protocol stack APIs are made available. - * - A portion of RAM will be unavailable (see relevant SDS documentation). - * - Some peripherals will be unavailable or available only through the SoC API (see relevant SDS documentation). - * - Interrupts will not arrive from protected peripherals or interrupts. - * - nrf_nvic_ functions must be used instead of CMSIS NVIC_ functions for reliable usage of the SoftDevice. - * - Interrupt latency may be affected by the SoftDevice (see relevant SDS documentation). - * - Chosen low frequency clock source will be running. - * - * @param p_clock_lf_cfg Low frequency clock source and accuracy. - If NULL the clock will be configured as an RC source with rc_ctiv = 16 and .rc_temp_ctiv = 2 - In the case of XTAL source, the PPM accuracy of the chosen clock source must be greater than or equal to - the actual characteristics of your XTAL clock. - * @param fault_handler Callback to be invoked in case of fault, cannot be NULL. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE SoftDevice is already enabled, and the clock source and fault handler cannot be updated. - * @retval ::NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION SoftDevice interrupt is already enabled, or an enabled interrupt has - an illegal priority level. - * @retval ::NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN Unknown low frequency clock source selected. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid clock source configuration supplied in p_clock_lf_cfg. - */ -SVCALL(SD_SOFTDEVICE_ENABLE, uint32_t, - sd_softdevice_enable(nrf_clock_lf_cfg_t const *p_clock_lf_cfg, nrf_fault_handler_t fault_handler)); - -/**@brief Disables the SoftDevice and by extension the protocol stack. - * - * Idempotent function to disable the SoftDevice. - * - * @post SoC library and protocol stack APIs are made unavailable. - * @post All interrupts that was protected by the SoftDevice will be disabled and initialized to priority 0 (highest). - * @post All peripherals used by the SoftDevice will be reset to default values. - * @post All of RAM become available. - * @post All interrupts are forwarded to the application. - * @post LFCLK source chosen in ::sd_softdevice_enable will be left running. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_DISABLE, uint32_t, sd_softdevice_disable(void)); - -/**@brief Check if the SoftDevice is enabled. - * - * @param[out] p_softdevice_enabled If the SoftDevice is enabled: 1 else 0. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_IS_ENABLED, uint32_t, sd_softdevice_is_enabled(uint8_t *p_softdevice_enabled)); - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the SoftDevice - * - * This function is only intended to be called when a bootloader is enabled. - * - * @param[in] address The base address of the interrupt vector table for forwarded interrupts. - - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table_base_set(uint32_t address)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SDM_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_soc.h deleted file mode 100644 index c649ca836..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_soc.h +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_soc_api SoC Library API - * @{ - * - * @brief APIs for the SoC library. - * - */ - -#ifndef NRF_SOC_H__ -#define NRF_SOC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_SOC_DEFINES Defines - * @{ */ - -/**@brief The number of the lowest SVC number reserved for the SoC library. */ -#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ -#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ - -/**@brief Guaranteed time for application to process radio inactive notification. */ -#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) - -/**@brief The minimum allowed timeslot extension time. */ -#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) - -/**@brief The maximum processing time to handle a timeslot extension. */ -#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) - -/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ -#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) - -#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ -#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ -#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ - -#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ -#define SD_EVT_IRQHandler \ - (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ - The default interrupt priority for this handler is set to 6 */ -#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ -#define RADIO_NOTIFICATION_IRQHandler \ - (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ - The default interrupt priority for this handler is set to 6 */ -#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ -#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ - -#define NRF_RADIO_DISTANCE_MAX_US \ - (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ - nrf_radio_request_normal_t) in the request. */ - -#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ - (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ - -#define NRF_RADIO_START_JITTER_US \ - (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ - ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ - (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) - -/**@} */ - -/**@addtogroup NRF_SOC_ENUMS Enumerations - * @{ */ - -/**@brief The SVC numbers used by the SVC functions in the SoC library. */ -enum NRF_SOC_SVCS { - SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, - SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, - SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, - SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, - SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, - SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, - SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, - SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, - SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, - SD_FLASH_WRITE = SOC_SVC_BASE + 9, - SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, - SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, - SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, - SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, - SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, - SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, - SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, - SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, - SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, - SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, - SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, - SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, - SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, - SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, - SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, - SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, - SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, - SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, - SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, - SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, - SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, - SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, - SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, - SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, - SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, - SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, - SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, - SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, - SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, - SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, - SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, - SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, - SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, - SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, - SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, - SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, - SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, - SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, - SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 -}; - -/**@brief Possible values of a ::nrf_mutex_t. */ -enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; - -/**@brief Power modes. */ -enum NRF_POWER_MODES { - NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ - NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ -}; - -/**@brief Power failure thresholds */ -enum NRF_POWER_THRESHOLDS { - NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ -}; - -/**@brief Power failure thresholds for high voltage */ -enum NRF_POWER_THRESHOLDVDDHS { - NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ -}; - -/**@brief DC/DC converter modes. */ -enum NRF_POWER_DCDC_MODES { - NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ - NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ -}; - -/**@brief Radio notification distances. */ -enum NRF_RADIO_NOTIFICATION_DISTANCES { - NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ - NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ -}; - -/**@brief Radio notification types. */ -enum NRF_RADIO_NOTIFICATION_TYPES { - NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and - disabled. */ -}; - -/**@brief The Radio signal callback types. */ -enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { - NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ -}; - -/**@brief The actions requested by the signal callback. - * - * This code gives the SOC instructions about what action to take when the signal callback has - * returned. - */ -enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { - NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current - timeslot. Maximum execution time for this action: - @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. - This action must be started at least - @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before - the end of the timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ -}; - -/**@brief Radio timeslot high frequency clock source configuration. */ -enum NRF_RADIO_HFCLK_CFG { - NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the - external crystal for the whole duration of the timeslot. This should be the - preferred option for events that use the radio or require high timing accuracy. - @note The SoftDevice will automatically turn on and off the external crystal, - at the beginning and end of the timeslot, respectively. The crystal may also - intentionally be left running after the timeslot, in cases where it is needed - by the SoftDevice shortly after the end of the timeslot. */ - NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. - The RC oscillator may be the clock source in part or for the whole duration of the - timeslot. The RC oscillator's accuracy must therefore be taken into consideration. - @note If the application will use the radio peripheral in timeslots with this - configuration, it must make sure that the crystal is running and stable before - starting the radio. */ -}; - -/**@brief Radio timeslot priorities. */ -enum NRF_RADIO_PRIORITY { - NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ - NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ -}; - -/**@brief Radio timeslot request type. */ -enum NRF_RADIO_REQUEST_TYPE { - NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first - request in a session. */ - NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ -}; - -/**@brief SoC Events. */ -enum NRF_SOC_EVTS { - NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ - NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ - NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ - NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ - NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ - NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ - NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was - invalid. */ - NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ - NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ - NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ - NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ - NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ - NRF_EVT_NUMBER_OF_EVTS -}; - -/**@} */ - -/**@addtogroup NRF_SOC_STRUCTURES Structures - * @{ */ - -/**@brief Represents a mutex for use with the nrf_mutex functions. - * @note Accessing the value directly is not safe, use the mutex functions! - */ -typedef volatile uint8_t nrf_mutex_t; - -/**@brief Parameters for a request for a timeslot as early as possible. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ - uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref - NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ -} nrf_radio_request_earliest_t; - -/**@brief Parameters for a normal radio timeslot request. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US - microseconds). */ - uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ -} nrf_radio_request_normal_t; - -/**@brief Radio timeslot request parameters. */ -typedef struct { - uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ - union { - nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ - nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ - } params; /**< Parameter union. */ -} nrf_radio_request_t; - -/**@brief Return parameters of the radio timeslot signal callback. */ -typedef struct { - uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref - NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ - union { - struct { - nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ - } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ - struct { - uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref - NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ - } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ - } params; /**< Parameter union. */ -} nrf_radio_signal_callback_return_param_t; - -/**@brief The radio timeslot signal callback type. - * - * @note In case of invalid return parameters, the radio timeslot will automatically end - * immediately after returning from the signal callback and the - * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. - * @note The returned struct pointer must remain valid after the signal callback - * function returns. For instance, this means that it must not point to a stack variable. - * - * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. - * - * @return Pointer to structure containing action requested by the application. - */ -typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); - -/**@brief AES ECB parameter typedefs */ -typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ -typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ -typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ - -/**@brief AES ECB data structure */ -typedef struct { - soc_ecb_key_t key; /**< Encryption key. */ - soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ - soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ -} nrf_ecb_hal_data_t; - -/**@brief AES ECB block. Used to provide multiple blocks in a single call - to @ref sd_ecb_blocks_encrypt.*/ -typedef struct { - soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ - soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ - soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ -} nrf_ecb_hal_data_block_t; - -/**@} */ - -/**@addtogroup NRF_SOC_FUNCTIONS Functions - * @{ */ - -/**@brief Initialize a mutex. - * - * @param[in] p_mutex Pointer to the mutex to initialize. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); - -/**@brief Attempt to acquire a mutex. - * - * @param[in] p_mutex Pointer to the mutex to acquire. - * - * @retval ::NRF_SUCCESS The mutex was successfully acquired. - * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. - */ -SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); - -/**@brief Release a mutex. - * - * @param[in] p_mutex Pointer to the mutex to release. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); - -/**@brief Query the capacity of the application random pool. - * - * @param[out] p_pool_capacity The capacity of the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); - -/**@brief Get number of random bytes available to the application. - * - * @param[out] p_bytes_available The number of bytes currently available in the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); - -/**@brief Get random bytes from the application pool. - * - * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. - * @param[in] length Number of bytes to take from pool and place in p_buff. - * - * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. - * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes - * available. - */ -SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); - -/**@brief Gets the reset reason register. - * - * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); - -/**@brief Clears the bits of the reset reason register. - * - * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); - -/**@brief Sets the power mode when in CPU sleep. - * - * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait - * - * @retval ::NRF_SUCCESS The power mode was set. - * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. - */ -SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); - -/**@brief Puts the chip in System OFF mode. - * - * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN - */ -SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); - -/**@brief Enables or disables the power-fail comparator. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); - -/**@brief Enables or disables the USB power ready event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); - -/**@brief Enables or disables the power USB-detected event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); - -/**@brief Enables or disables the power USB-removed event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); - -/**@brief Get USB supply status register content. - * - * @param[out] usbregstatus The content of USBREGSTATUS register. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); - -/**@brief Sets the power failure comparator threshold value. - * - * @note: Power failure comparator threshold setting. This setting applies both for normal voltage - * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to - * VDDH only). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); - -/**@brief Sets the power failure comparator threshold value for high voltage. - * - * @note: Power failure comparator threshold setting for high voltage mode (supply connected to - * VDDH only). This setting does not apply for normal voltage mode (supply connected to both - * VDD and VDDH). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. - * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. - * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); - -/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. - * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); - -/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be set in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[out] p_gpregret Contents of the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). - * - * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. - */ -SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). - * - * For more details on the REG0 stage, please see product specification. - * - * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. - */ -SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); - -/**@brief Request the high frequency crystal oscillator. - * - * Will start the high frequency crystal oscillator, the startup time of the crystal varies - * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_release - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); - -/**@brief Releases the high frequency crystal oscillator. - * - * Will stop the high frequency crystal oscillator, this happens immediately. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_request - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); - -/**@brief Checks if the high frequency crystal oscillator is running. - * - * @see sd_clock_hfclk_request - * @see sd_clock_hfclk_release - * - * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); - -/**@brief Waits for an application event. - * - * An application event is either an application interrupt or a pended interrupt when the interrupt - * is disabled. - * - * When the application waits for an application event by calling this function, an interrupt that - * is enabled will be taken immediately on pending since this function will wait in thread mode, - * then the execution will return in the application's main thread. - * - * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M - * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets - * pended, this function will return to the application's main thread. - * - * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ - * in order to sleep using this function. This is only necessary for disabled interrupts, as - * the interrupt handler will clear the pending flag automatically for enabled interrupts. - * - * @note If an application interrupt has happened since the last time sd_app_evt_wait was - * called this function will return immediately and not go to sleep. This is to avoid race - * conditions that can occur when a flag is updated in the interrupt handler and processed - * in the main loop. - * - * @post An application interrupt has happened or a interrupt pending flag is set. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); - -/**@brief Get PPI channel enable register contents. - * - * @param[out] p_channel_enable The contents of the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); - -/**@brief Set PPI channel enable register. - * - * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); - -/**@brief Clear PPI channel enable register. - * - * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); - -/**@brief Assign endpoints to a PPI channel. - * - * @param[in] channel_num Number of the PPI channel to assign. - * @param[in] evt_endpoint Event endpoint of the PPI channel. - * @param[in] task_endpoint Task endpoint of the PPI channel. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, - sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); - -/**@brief Task to enable a channel group. - * - * @param[in] group_num Number of the channel group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); - -/**@brief Task to disable a channel group. - * - * @param[in] group_num Number of the PPI group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); - -/**@brief Assign PPI channels to a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[in] channel_msk Mask of the channels to assign to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); - -/**@brief Gets the PPI channels of a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[out] p_channel_msk Mask of the channels assigned to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); - -/**@brief Configures the Radio Notification signal. - * - * @note - * - The notification signal latency depends on the interrupt priority settings of SWI used - * for notification signal. - * - To ensure that the radio notification signal behaves in a consistent way, the radio - * notifications must be configured when there is no protocol stack or other SoftDevice - * activity in progress. It is recommended that the radio notification signal is - * configured directly after the SoftDevice has been enabled. - * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice - * will interrupt the application to do Radio Event preparation. - * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have - * to shorten the connection events to have time for the Radio Notification signals. - * - * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio - * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is - * recommended (but not required) to be used with - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. - * - * @param[in] distance Distance between the notification signal and start of radio activity, see @ref - * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or - * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. - * - * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. - * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all - * running activities and retry. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); - -/**@brief Encrypts a block according to the specified parameters. - * - * 128-bit AES encryption. - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input - * parameters and one output parameter). - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); - -/**@brief Encrypts multiple data blocks provided as an array of data block structures. - * - * @details: Performs 128-bit AES encryption on multiple data blocks - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in] block_count Count of blocks in the p_data_blocks array. - * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of - * @ref nrf_ecb_hal_data_block_t structures. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); - -/**@brief Gets any pending events generated by the SoC API. - * - * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. - * - * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. - * - * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. - * @retval ::NRF_ERROR_NOT_FOUND No pending events. - */ -SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); - -/**@brief Get the temperature measured on the chip - * - * This function will block until the temperature measurement is done. - * It takes around 50 us from call to return. - * - * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. - * - * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp - */ -SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); - -/**@brief Flash Write - * - * Commands to write a buffer to flash - * - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * write has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS - * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. - * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is - * protected. - * - * - * @param[in] p_dst Pointer to start of flash location to be written. - * @param[in] p_src Pointer to buffer with data to be written. - * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one - * flash page. See the device's Product Specification for details. - * - * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. - * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); - -/**@brief Flash Erase page - * - * Commands to erase a flash page - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * erase has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is - * protected. - * - * - * @param[in] page_number Page number of the page to erase - * - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); - -/**@brief Opens a session for radio timeslot requests. - * - * @note Only one session can be open at a time. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot - * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed - * by the application. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 - * interrupt occurs. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO - * interrupt occurs. - * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This - * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). - * - * @param[in] p_radio_signal_callback The signal callback. - * - * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. - * @retval ::NRF_ERROR_BUSY If session cannot be opened. - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); - -/**@brief Closes a session for radio timeslot requests. - * - * @note Any current radio timeslot will be finished before the session is closed. - * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. - * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED - * event is received. - * - * @retval ::NRF_ERROR_FORBIDDEN If session not opened. - * @retval ::NRF_ERROR_BUSY If session is currently being closed. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); - -/**@brief Requests a radio timeslot. - * - * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST - * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref - * NRF_RADIO_REQ_TYPE_EARLIEST. - * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by - * p_request->distance_us and is given relative to the start of the previous timeslot. - * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. - * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this - * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. - * The application may then try to schedule the first radio timeslot again. - * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). - * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. - * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. - * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the - * specified radio timeslot start, but this does not affect the actual start time of the timeslot. - * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency - * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is - * guaranteed to be clocked from the external crystal. - * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral - * during the radio timeslot. - * - * @param[in] p_request Pointer to the request parameters. - * - * @retval ::NRF_ERROR_FORBIDDEN Either: - * - The session is not open. - * - The session is not IDLE. - * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. - * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a - * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. - * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); - -/**@brief Write register protected by the SoftDevice - * - * This function writes to a register that is write-protected by the SoftDevice. Please refer to your - * SoftDevice Specification for more details about which registers that are protected by SoftDevice. - * This function can write to the following protected peripheral: - * - ACL - * - * @note Protected registers may be read directly. - * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in - * the register has not changed. See the Product Specification for more details about register - * properties. - * - * @param[in] p_register Pointer to register to be written. - * @param[in] value Value to be written to the register. - * - * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. - * @retval ::NRF_SUCCESS Value successfully written to register. - * - */ -SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); - -/**@} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SOC_H__ - -/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_svc.h b/variants/wio-sdk-wm1110/softdevice/nrf_svc.h deleted file mode 100644 index 1de44656f..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_svc.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef NRF_SVC__ -#define NRF_SVC__ - -#include "stdint.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @brief Supervisor call declaration. - * - * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. - * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. - * - * @param[in] number The SVC number to be used. - * @param[in] return_type The return type of the SVC function. - * @param[in] signature Function signature. The function can have at most four arguments. - */ - -#ifdef SVCALL_AS_NORMAL_FUNCTION -#define SVCALL(number, return_type, signature) return_type signature -#else - -#ifndef SVCALL -#if defined(__CC_ARM) -#define SVCALL(number, return_type, signature) return_type __svc(number) signature -#elif defined(__GNUC__) -#ifdef __cplusplus -#define GCC_CAST_CPP (uint16_t) -#else -#define GCC_CAST_CPP -#endif -#define SVCALL(number, return_type, signature) \ - _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ - __attribute__((unused)) static return_type signature \ - { \ - __asm("svc %0\n" \ - "bx r14" \ - : \ - : "I"(GCC_CAST_CPP number) \ - : "r0"); \ - } \ - _Pragma("GCC diagnostic pop") - -#elif defined(__ICCARM__) -#define PRAGMA(x) _Pragma(#x) -#define SVCALL(number, return_type, signature) \ - PRAGMA(swi_number = (number)) \ - __swi return_type signature; -#else -#define SVCALL(number, return_type, signature) return_type signature -#endif -#endif // SVCALL - -#endif // SVCALL_AS_NORMAL_FUNCTION - -#ifdef __cplusplus -} -#endif -#endif // NRF_SVC__ From 46d7b82ac1a4292ba52ca690e1a433d3a501a9e5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 16 Jul 2024 09:37:50 -0500 Subject: [PATCH 0715/1377] Migrate to new defaults (#4294) * Upgrade module config state version but don't blow everything away * ModuleConfig version intervals roll forward * Be specific about version migration criteria * initModuleConfigIntervals fix * Don't forget power! --- src/mesh/NodeDB.cpp | 32 ++++++++++++++++++++++++++++---- src/mesh/NodeDB.h | 4 ++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fa5c437c4..5678009c0 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -425,10 +425,14 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) void NodeDB::initModuleConfigIntervals() { - moduleConfig.telemetry.device_update_interval = default_broadcast_interval_secs; - moduleConfig.telemetry.environment_update_interval = default_broadcast_interval_secs; - moduleConfig.telemetry.air_quality_interval = default_broadcast_interval_secs; - moduleConfig.neighbor_info.update_interval = default_broadcast_interval_secs; + // Zero out telemetry intervals so that they coalesce to defaults in Default.h + moduleConfig.telemetry.device_update_interval = 0; + moduleConfig.telemetry.environment_update_interval = 0; + moduleConfig.telemetry.air_quality_interval = 0; + moduleConfig.telemetry.power_update_interval = 0; + moduleConfig.neighbor_info.update_interval = 0; + moduleConfig.paxcounter.paxcounter_update_interval = 0; + moduleConfig.neighbor_info.update_interval = 0; } void NodeDB::installDefaultChannels() @@ -648,6 +652,26 @@ void NodeDB::loadFromDisk() if (state == LoadFileResult::SUCCESS) { LOG_INFO("Loaded OEMStore\n"); } + + // 2.4.X - configuration migration to update new default intervals + if (moduleConfig.version < 23) { + LOG_DEBUG("ModuleConfig version %d is stale, upgrading to new default intervals\n", moduleConfig.version); + moduleConfig.version = DEVICESTATE_CUR_VER; + if (moduleConfig.telemetry.device_update_interval == 900) + moduleConfig.telemetry.device_update_interval = 0; + if (moduleConfig.telemetry.environment_update_interval == 900) + moduleConfig.telemetry.environment_update_interval = 0; + if (moduleConfig.telemetry.air_quality_interval == 900) + moduleConfig.telemetry.air_quality_interval = 0; + if (moduleConfig.telemetry.power_update_interval == 900) + moduleConfig.telemetry.power_update_interval = 0; + if (moduleConfig.neighbor_info.update_interval == 900) + moduleConfig.neighbor_info.update_interval = 0; + if (moduleConfig.paxcounter.paxcounter_update_interval == 900) + moduleConfig.paxcounter.paxcounter_update_interval = 0; + + saveToDisk(SEGMENT_MODULECONFIG); + } } /** Save a protobuf from a file, return true for success */ diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 61bf90d4d..5207d8629 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -20,8 +20,8 @@ DeviceState versions used to be defined in the .proto file but really only this #define SEGMENT_DEVICESTATE 4 #define SEGMENT_CHANNELS 8 -#define DEVICESTATE_CUR_VER 22 -#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER +#define DEVICESTATE_CUR_VER 23 +#define DEVICESTATE_MIN_VER 22 extern meshtastic_DeviceState devicestate; extern meshtastic_ChannelFile channelFile; From f25644e8cfb30f64a4aa9cb2eeff67eeca94c6eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 06:13:03 -0500 Subject: [PATCH 0716/1377] [create-pull-request] automated change (#4287) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 7d2c26248..4c87608d0 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 4 -build = 0 +build = 1 From 54df153e9e23190e0b1201ab3c3689560a3b3acb Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 20 Jul 2024 23:46:26 +1200 Subject: [PATCH 0717/1377] Wait for I2C power to stabilize on Heltec VME213; tidy variant folder (#4308) * Tidy variant.h and pins_arduino.h (VME213) * Wait for peripherals to stabilize after enabling I2C power The 3.3V power for the I2C "quick link" connector is from Ve_3V3 --- src/main.cpp | 7 ++++ .../heltec_vision_master_e213/pins_arduino.h | 6 ++-- variants/heltec_vision_master_e213/variant.h | 34 ++++++++----------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 95eeb998d..187942344 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -308,6 +308,13 @@ void setup() digitalWrite(RESET_OLED, 1); #endif +#ifdef PERIPHERAL_WARMUP_MS + // Some peripherals may require additional time to stabilize after power is connected + // e.g. I2C on Heltec Vision Master + LOG_INFO("Waiting for peripherals to stabilize\n"); + delay(PERIPHERAL_WARMUP_MS); +#endif + #ifdef BUTTON_PIN #ifdef ARCH_ESP32 diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h index 359922499..ce35348fd 100644 --- a/variants/heltec_vision_master_e213/pins_arduino.h +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -3,8 +3,8 @@ #include -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility +static const uint8_t LED_BUILTIN = -1; // Board has no built-in LED, despite what schematic shows +#define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN static const uint8_t TX = 43; @@ -56,6 +56,6 @@ static const uint8_t T14 = 14; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; +static const uint8_t DIO1 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index d4e42ad1c..99bc1d138 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -1,14 +1,11 @@ -// #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals +// I2C #define I2C_SDA SDA #define I2C_SCL SCL +// Display (E-Ink) #define USE_EINK - -/* - * eink display pins - */ #define PIN_EINK_CS 5 #define PIN_EINK_BUSY 1 #define PIN_EINK_DC 2 @@ -16,33 +13,30 @@ #define PIN_EINK_SCLK 4 #define PIN_EINK_MOSI 6 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 10 // MISO +#define PIN_SPI_MOSI 11 // MOSI +#define PIN_SPI_SCK 9 // SCK -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -#define VEXT_ENABLE 18 // powers the oled display and the lora antenna boost -#define VEXT_ON_VALUE 1 -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 18 // Powers the E-Ink display, and the 3.3V supply to the I2C QuickLink connector +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define VEXT_ON_VALUE HIGH #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 #define ADC_CHANNEL ADC1_GPIO7_CHANNEL -#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 -#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high +#define ADC_MULTIPLIER 4.9 * 1.03 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 +// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 From f9d79964ef5c1019b70c40914e01838bb35a3429 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Sat, 20 Jul 2024 13:47:04 +0200 Subject: [PATCH 0718/1377] Remove duplicate code and fix error: #if with no expression (#4307) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. * Removed duplicate code. * Fix error: #if with no expression --------- Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 2 +- src/mesh/NodeDB.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 8eb7fef27..feeac8494 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -865,7 +865,7 @@ void GPS::writePinStandby(bool standby) // Determine the new value for the pin // Normally: active HIGH for awake -#if PIN_GPS_STANDBY_INVERTED +#ifdef PIN_GPS_STANDBY_INVERTED bool val = standby; #else bool val = !standby; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5678009c0..c0bed3437 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -432,7 +432,6 @@ void NodeDB::initModuleConfigIntervals() moduleConfig.telemetry.power_update_interval = 0; moduleConfig.neighbor_info.update_interval = 0; moduleConfig.paxcounter.paxcounter_update_interval = 0; - moduleConfig.neighbor_info.update_interval = 0; } void NodeDB::installDefaultChannels() From dadf9234e5ed7d9e21333608de1f5be9b2fbfeac Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 21 Jul 2024 07:09:10 -0500 Subject: [PATCH 0719/1377] Remove status topic (#4305) --- src/mesh/MeshTypes.h | 2 ++ src/mqtt/MQTT.cpp | 19 ++++++------------- src/mqtt/MQTT.h | 10 +++++----- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 5b2cbd1b1..c0919bf5d 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -10,6 +10,8 @@ typedef uint32_t NodeNum; typedef uint32_t PacketId; // A packet sequence number #define NODENUM_BROADCAST UINT32_MAX +#define NODENUM_BROADCAST_NO_LORA \ + 1 // Reserved to only deliver packets over high speed (non-lora) transports, such as MQTT or BLE mesh (not yet implemented) #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a64720c78..50086e7a2 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -188,12 +188,10 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) mqtt = this; if (*moduleConfig.mqtt.root) { - statusTopic = moduleConfig.mqtt.root + statusTopic; cryptTopic = moduleConfig.mqtt.root + cryptTopic; jsonTopic = moduleConfig.mqtt.root + jsonTopic; mapTopic = moduleConfig.mqtt.root + mapTopic; } else { - statusTopic = "msh" + statusTopic; cryptTopic = "msh" + cryptTopic; jsonTopic = "msh" + jsonTopic; mapTopic = "msh" + mapTopic; @@ -216,7 +214,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) enabled = true; runASAP = true; reconnectCount = 0; - publishStatus(); + publishNodeInfo(); } // preflightSleepObserver.observe(&preflightSleep); } else { @@ -281,7 +279,7 @@ void MQTT::reconnect() runASAP = true; reconnectCount = 0; - publishStatus(); + publishNodeInfo(); return; // Don't try to connect directly to the server } #if HAS_NETWORKING @@ -330,15 +328,14 @@ void MQTT::reconnect() LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, serverPort, mqttUsername, mqttPassword); - auto myStatus = (statusTopic + owner.id); - bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword, myStatus.c_str(), 1, true, "offline"); + bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); if (connected) { LOG_INFO("MQTT connected\n"); enabled = true; // Start running background process again runASAP = true; reconnectCount = 0; - publishStatus(); + publishNodeInfo(); sendSubscriptions(); } else { #if HAS_WIFI && !defined(ARCH_PORTDUINO) @@ -437,14 +434,10 @@ int32_t MQTT::runOnce() return 30000; } -/// FIXME, include more information in the status text -void MQTT::publishStatus() +void MQTT::publishNodeInfo() { - auto myStatus = (statusTopic + owner.id); - bool ok = publish(myStatus.c_str(), "online", true); - LOG_INFO("published online=%d\n", ok); + // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) } - void MQTT::publishQueuedMessages() { if (!mqttQueue.isEmpty()) { diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 1ebba4afe..d68f1b88d 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -81,10 +81,9 @@ class MQTT : private concurrency::OSThread virtual int32_t runOnce() override; private: - std::string statusTopic = "/2/stat/"; // For "online"/"offline" message - std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID - std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID - std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages + std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID + std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID + std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages // For map reporting (only applies when enabled) const uint32_t default_map_position_precision = 14; // defaults to max. offset of ~1459m @@ -110,9 +109,10 @@ class MQTT : private concurrency::OSThread /// Called when a new publish arrives from the MQTT server std::string meshPacketToJson(meshtastic_MeshPacket *mp); - void publishStatus(); void publishQueuedMessages(); + void publishNodeInfo(); + // Check if we should report unencrypted information about our node for consumption by a map void perhapsReportToMap(); From fa6624548b5232efd7a2310ea26bd10635f309a1 Mon Sep 17 00:00:00 2001 From: Tavis Date: Sun, 21 Jul 2024 05:09:37 -0700 Subject: [PATCH 0720/1377] Serial Mode for Ecowitt WS85 weather station. (#4296) * protobufs * initial mods, not tested * manual telem packet creation, compiles. * add gust and lull computation * telem packet is getting fired off * new pb ? * pb and gust lull * need to set the variant type for it to work. * add gust and lull to mqtt json output. * parse bat voltage and cap voltage and send the larger of the two in telem packet also use the new ws85 serial mode (6). must set it with cli. : meshtastic --set serial.mode 6 * set hard coded average/transmit interval to 5 minutes. * proper direction averging with trig. * Update protobufs * sweep some crud * read in 512 bytes at a time and break and clear serial input if we got wind data * factor out sendTelemetry function * Revert "factor out sendTelemetry function" This reverts commit b61ba1a3c5d68ff881a5479f3f85452d7e9b7c8f. * Reapply "factor out sendTelemetry function" This reverts commit d0af9cfd7d141b28ce24a2c3853b2776f9fc456e. * update protobufs * put WS85 Serial2 is tcho and canaryone exclusion #ifdef * include GeoCoord.h so dr-dev will compile. * remove old TODO comment. * breakout WS85 serial operation to it's own function called processWXSerial() * canaryone and t-echo exclusion for Serial2 --------- Co-authored-by: Ben Meadors --- src/modules/SerialModule.cpp | 188 ++++++++++++++++++++++++++++++++++- src/modules/SerialModule.h | 2 + src/mqtt/MQTT.cpp | 4 +- 3 files changed, 191 insertions(+), 3 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 96a99b13e..4b8a4d228 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -1,4 +1,5 @@ #include "SerialModule.h" +#include "GeoCoord.h" #include "MeshService.h" #include "NMEAWPL.h" #include "NodeDB.h" @@ -66,7 +67,7 @@ SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Seria static Print *serialPrint = &Serial2; #endif -char serialBytes[meshtastic_Constants_DATA_PAYLOAD_LEN]; +char serialBytes[512]; size_t serialPayloadSize; SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio") @@ -198,8 +199,12 @@ int32_t SerialModule::runOnce() } } } + #if !defined(TTGO_T_ECHO) && !defined(CANARYONE) - else { + else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { + processWXSerial(); + + } else { while (Serial2.available()) { serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); serialModuleRadio->sendPayload(); @@ -213,6 +218,27 @@ int32_t SerialModule::runOnce() } } +/** + * Sends telemetry packet over the mesh network. + * + * @param m The telemetry data to be sent + * + * @return void + * + * @throws None + */ +void SerialModule::sendTelemetry(meshtastic_Telemetry m) +{ + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_TELEMETRY_APP; + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Telemetry_msg, &m); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + service.sendToMesh(p, RX_SRC_LOCAL, true); +} + /** * Allocates a new mesh packet for use as a reply to a received packet. * @@ -357,4 +383,162 @@ uint32_t SerialModule::getBaudRate() } return BAUD; } + +/** + * Process the received weather station serial data, extract wind, voltage, and temperature information, + * calculate averages and send telemetry data over the mesh network. + * + * @return void + */ +void SerialModule::processWXSerial() +{ +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) + static unsigned int lastAveraged = 0; + static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. + static double dir_sum_sin = 0; + static double dir_sum_cos = 0; + static float velSum = 0; + static float gust = 0; + static float lull = -1; + static int velCount = 0; + static int dirCount = 0; + static char windDir[4] = "xxx"; // Assuming windDir is 3 characters long + null terminator + static char windVel[5] = "xx.x"; // Assuming windVel is 4 characters long + null terminator + static char windGust[5] = "xx.x"; // Assuming windGust is 4 characters long + null terminator + static char batVoltage[5] = "0.0V"; + static char capVoltage[5] = "0.0V"; + static float batVoltageF = 0; + static float capVoltageF = 0; + bool gotwind = false; + + while (Serial2.available()) { + // clear serialBytes buffer + memset(serialBytes, '\0', sizeof(serialBytes)); + // memset(formattedString, '\0', sizeof(formattedString)); + serialPayloadSize = Serial2.readBytes(serialBytes, 512); + // check for a strings we care about + // example output of serial data fields from the WS85 + // WindDir = 79 + // WindSpeed = 0.5 + // WindGust = 0.6 + // GXTS04Temp = 24.4 + if (serialPayloadSize > 0) { + // Define variables for line processing + int lineStart = 0; + int lineEnd = -1; + + // Process each byte in the received data + for (size_t i = 0; i < serialPayloadSize; i++) { + // go until we hit the end of line and then process the line + if (serialBytes[i] == '\n') { + lineEnd = i; + // Extract the current line + char line[meshtastic_Constants_DATA_PAYLOAD_LEN]; + memset(line, '\0', sizeof(line)); + memcpy(line, &serialBytes[lineStart], lineEnd - lineStart); + + if (strstr(line, "Wind") != NULL) // we have a wind line + { + gotwind = true; + // Find the positions of "=" signs in the line + char *windDirPos = strstr(line, "WindDir = "); + char *windSpeedPos = strstr(line, "WindSpeed = "); + char *windGustPos = strstr(line, "WindGust = "); + + if (windDirPos != NULL) { + // Extract data after "=" for WindDir + strcpy(windDir, windDirPos + 15); // Add 15 to skip "WindDir = " + double radians = toRadians(strtof(windDir, nullptr)); + dir_sum_sin += sin(radians); + dir_sum_cos += cos(radians); + dirCount++; + } else if (windSpeedPos != NULL) { + // Extract data after "=" for WindSpeed + strcpy(windVel, windSpeedPos + 15); // Add 15 to skip "WindSpeed = " + float newv = strtof(windVel, nullptr); + velSum += newv; + velCount++; + if (newv < lull || lull == -1) + lull = newv; + + } else if (windGustPos != NULL) { + strcpy(windGust, windGustPos + 15); // Add 15 to skip "WindSpeed = " + float newg = strtof(windGust, nullptr); + if (newg > gust) + gust = newg; + } + + // these are also voltage data we care about possibly + } else if (strstr(line, "BatVoltage") != NULL) { // we have a battVoltage line + char *batVoltagePos = strstr(line, "BatVoltage = "); + if (batVoltagePos != NULL) { + strcpy(batVoltage, batVoltagePos + 17); // 18 for ws 80, 17 for ws85 + batVoltageF = strtof(batVoltage, nullptr); + break; // last possible data we want so break + } + } else if (strstr(line, "CapVoltage") != NULL) { // we have a cappVoltage line + char *capVoltagePos = strstr(line, "CapVoltage = "); + if (capVoltagePos != NULL) { + strcpy(capVoltage, capVoltagePos + 17); // 18 for ws 80, 17 for ws85 + capVoltageF = strtof(capVoltage, nullptr); + } + } + + // Update lineStart for the next line + lineStart = lineEnd + 1; + } + } + break; + // clear the input buffer + while (Serial2.available() > 0) { + Serial2.read(); // Read and discard the bytes in the input buffer + } + } + } + if (gotwind) { + + LOG_INFO("WS85 : %i %.1fg%.1f %.1fv %.1fv\n", atoi(windDir), strtof(windVel, nullptr), strtof(windGust, nullptr), + batVoltageF, capVoltageF); + } + if (gotwind && millis() - lastAveraged > averageIntervalMillis) { + // calulate averages and send to the mesh + float velAvg = 1.0 * velSum / velCount; + + double avgSin = dir_sum_sin / dirCount; + double avgCos = dir_sum_cos / dirCount; + + double avgRadians = atan2(avgSin, avgCos); + float dirAvg = toDegrees(avgRadians); + + if (dirAvg < 0) { + dirAvg += 360.0; + } + lastAveraged = millis(); + + // make a telemetry packet with the data + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + m.which_variant = meshtastic_Telemetry_environment_metrics_tag; + m.variant.environment_metrics.wind_speed = velAvg; + m.variant.environment_metrics.wind_direction = dirAvg; + m.variant.environment_metrics.wind_gust = gust; + m.variant.environment_metrics.wind_lull = lull; + m.variant.environment_metrics.voltage = + capVoltageF > batVoltageF ? capVoltageF : batVoltageF; // send the larger of the two voltage values. + + LOG_INFO("WS85 Transmit speed=%fm/s, direction=%d , lull=%f, gust=%f, voltage=%f\n", + m.variant.environment_metrics.wind_speed, m.variant.environment_metrics.wind_direction, + m.variant.environment_metrics.wind_lull, m.variant.environment_metrics.wind_gust, + m.variant.environment_metrics.voltage); + + sendTelemetry(m); + + // reset counters and gust/lull + velSum = velCount = dirCount = 0; + dir_sum_sin = dir_sum_cos = 0; + gust = 0; + lull = -1; + } +#endif + return; +} #endif \ No newline at end of file diff --git a/src/modules/SerialModule.h b/src/modules/SerialModule.h index 18ad8a1ba..fa86db28f 100644 --- a/src/modules/SerialModule.h +++ b/src/modules/SerialModule.h @@ -28,6 +28,8 @@ class SerialModule : public StreamAPI, private concurrency::OSThread private: uint32_t getBaudRate(); + void sendTelemetry(meshtastic_Telemetry m); + void processWXSerial(); }; extern SerialModule *serialModule; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 50086e7a2..5f7d6d902 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -673,8 +673,10 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux); msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux); msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); - msgPayload["wind_speed"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_speed); + msgPayload["wind_speed"] = new JSONValue(decoded->variant.environment_metrics.wind_speed); msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction); + msgPayload["wind_gust"] = new JSONValue(decoded->variant.environment_metrics.wind_gust); + msgPayload["wind_lull"] = new JSONValue(decoded->variant.environment_metrics.wind_lull); } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); From 1123223058e1797860a0810afb52b824d8fda58b Mon Sep 17 00:00:00 2001 From: Lennart Buhl Date: Mon, 22 Jul 2024 13:58:24 +0200 Subject: [PATCH 0721/1377] Fix a typo in src/mesh/MeshService.h (#4314) --- src/mesh/MeshService.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 3ac35bb62..ef92ba7d4 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -144,7 +144,7 @@ class MeshService /// returns 0 to allow further processing int onGPSChanged(const meshtastic::GPSStatus *arg); #endif - /// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it + /// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it /// needs to keep the packet around it makes a copy int handleFromRadio(const meshtastic_MeshPacket *p); friend class RoutingModule; From bdd1c53072f8d4c425a04d832622c5bd21980690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 22 Jul 2024 15:30:36 +0200 Subject: [PATCH 0722/1377] Revert "Sync Wio lr1110 refresh with master (#4288)" This reverts commit 5cc8ca59a37d87943781caf655bb9df2d95a045b. Revert "Sync Wio lr1110 refresh with master (#4251)" This reverts commit d97e6b86b8bc9be3b24589795c7abac07140fb48. Revert "update SD_FLASH_SIZE to 0x27000 (#4232)" This reverts commit 2df8093fef22034d99df783e5ede1a1ca660a52d. --- .github/actions/setup-base/action.yml | 2 +- .github/workflows/build_native.yml | 2 +- .github/workflows/build_nrf52.yml | 1 - .github/workflows/build_raspbian.yml | 1 - .github/workflows/build_raspbian_armv7l.yml | 1 - .github/workflows/main_matrix.yml | 3 +- .trunk/trunk.yaml | 6 +- .vscode/settings.json | 5 +- arch/esp32/esp32.ini | 2 +- bin/build-nrf52.sh | 29 +- bin/mergehex | Bin 2102544 -> 0 bytes bin/s140_nrf52_7.3.0_softdevice.hex | 9726 ----------------- bin/setup-python-for-esp-debug.sh | 12 - bin/uf2conv.py | 223 +- boards/heltec_mesh_node_t114.json | 53 - boards/wio-sdk-wm1110.json | 2 +- boards/wio-tracker-wm1110.json | 2 +- boards/wiscore_rak4631.json | 2 +- platformio.ini | 10 +- pyocd.yaml | 7 - src/AccelerometerThread.h | 36 +- src/BluetoothCommon.cpp | 6 +- src/BluetoothCommon.h | 4 +- src/ButtonThread.cpp | 5 +- src/DebugConfiguration.cpp | 2 +- src/DebugConfiguration.h | 19 +- src/FSCommon.cpp | 52 - src/FSCommon.h | 2 - src/GPSStatus.h | 2 +- src/Power.cpp | 17 +- src/PowerFSM.cpp | 17 - src/PowerMon.cpp | 45 - src/PowerMon.h | 34 - src/RedirectablePrint.cpp | 280 +- src/RedirectablePrint.h | 23 +- src/SerialConsole.cpp | 36 +- src/SerialConsole.h | 10 +- src/commands.h | 6 +- src/configuration.h | 13 +- src/detect/ScanI2CTwoWire.cpp | 4 +- src/gps/GPS.cpp | 542 +- src/gps/GPS.h | 49 +- src/gps/GPSUpdateScheduling.cpp | 118 - src/gps/GPSUpdateScheduling.h | 29 - src/graphics/EInkDisplay2.cpp | 3 +- src/graphics/EInkDisplay2.h | 8 +- src/graphics/Screen.cpp | 612 +- src/graphics/Screen.h | 162 +- src/graphics/ScreenFonts.h | 8 +- src/main.cpp | 26 +- src/main.h | 2 - src/mesh/Default.h | 1 - src/mesh/FloodingRouter.cpp | 3 +- src/mesh/LR11x0Interface.cpp | 3 +- src/mesh/MeshModule.cpp | 15 +- src/mesh/MeshModule.h | 28 +- src/mesh/MeshService.cpp | 13 +- src/mesh/NodeDB.cpp | 18 +- src/mesh/NodeDB.h | 4 +- src/mesh/PhoneAPI.cpp | 51 +- src/mesh/PhoneAPI.h | 17 +- src/mesh/RF95Interface.cpp | 31 +- src/mesh/RadioInterface.cpp | 3 +- src/mesh/RadioLibInterface.cpp | 21 - src/mesh/RadioLibInterface.h | 13 +- src/mesh/Router.cpp | 6 +- src/mesh/SX126xInterface.cpp | 3 +- src/mesh/SX128xInterface.cpp | 3 +- src/mesh/StreamAPI.cpp | 23 +- src/mesh/StreamAPI.h | 5 +- src/mesh/eth/ethClient.cpp | 4 - src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/config.pb.h | 24 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 3 +- src/mesh/generated/meshtastic/localonly.pb.h | 4 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 5 +- src/mesh/generated/meshtastic/mesh.pb.h | 44 +- .../generated/meshtastic/module_config.pb.h | 20 +- src/mesh/generated/meshtastic/portnums.pb.h | 2 - src/mesh/generated/meshtastic/powermon.pb.cpp | 17 - src/mesh/generated/meshtastic/powermon.pb.h | 138 - src/mesh/generated/meshtastic/telemetry.pb.h | 18 +- src/mesh/http/ContentHandler.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 2 +- src/modules/AdminModule.cpp | 17 +- src/modules/AdminModule.h | 4 +- src/modules/CannedMessageModule.cpp | 49 +- src/modules/CannedMessageModule.h | 6 +- src/modules/Modules.cpp | 6 - src/modules/PositionModule.cpp | 4 +- src/modules/PowerStressModule.cpp | 77 - src/modules/PowerStressModule.h | 38 - src/modules/Telemetry/AirQualityTelemetry.cpp | 108 +- src/modules/Telemetry/AirQualityTelemetry.h | 5 - src/modules/Telemetry/DeviceTelemetry.cpp | 32 +- .../Telemetry/EnvironmentTelemetry.cpp | 122 +- src/modules/Telemetry/EnvironmentTelemetry.h | 5 - src/modules/Telemetry/PowerTelemetry.cpp | 95 +- src/modules/Telemetry/PowerTelemetry.h | 5 - .../Telemetry/Sensor/INA3221Sensor.cpp | 68 +- src/modules/Telemetry/Sensor/INA3221Sensor.h | 25 - src/modules/WaypointModule.cpp | 166 +- src/modules/WaypointModule.h | 14 +- src/modules/esp32/AudioModule.cpp | 6 +- src/modules/esp32/PaxcounterModule.cpp | 8 +- src/modules/esp32/StoreForwardModule.cpp | 4 +- src/mqtt/MQTT.cpp | 25 +- src/mqtt/MQTT.h | 6 +- src/nimble/NimbleBluetooth.cpp | 45 +- src/nimble/NimbleBluetooth.h | 1 - src/platform/esp32/architecture.h | 8 - src/platform/esp32/main-esp32.cpp | 29 +- src/platform/nrf52/NRF52Bluetooth.cpp | 111 +- src/platform/nrf52/NRF52Bluetooth.h | 2 +- src/platform/nrf52/main-nrf52.cpp | 52 +- src/platform/nrf52/softdevice/nrf_sdm.h | 2 +- src/shutdown.h | 2 +- src/sleep.cpp | 20 +- src/sleep.h | 2 + variants/heltec_capsule_sensor_v3/variant.h | 2 +- variants/heltec_mesh_node_t114/platformio.ini | 15 - variants/heltec_mesh_node_t114/variant.cpp | 44 - variants/heltec_mesh_node_t114/variant.h | 210 - .../heltec_vision_master_e213/pins_arduino.h | 63 - .../heltec_vision_master_e213/platformio.ini | 23 - variants/heltec_vision_master_e213/variant.h | 58 - .../heltec_vision_master_e290/pins_arduino.h | 61 - .../heltec_vision_master_e290/platformio.ini | 25 - variants/heltec_vision_master_e290/variant.h | 58 - .../heltec_vision_master_t190/pins_arduino.h | 61 - .../heltec_vision_master_t190/platformio.ini | 13 - variants/heltec_vision_master_t190/variant.h | 76 - variants/heltec_wireless_paper/pins_arduino.h | 13 +- variants/heltec_wireless_paper/variant.h | 28 +- .../heltec_wireless_paper_v1/pins_arduino.h | 8 +- variants/heltec_wireless_paper_v1/variant.h | 28 +- .../heltec_wireless_tracker/platformio.ini | 4 +- variants/rak4631/platformio.ini | 92 +- variants/tlora_t3s3_v1/platformio.ini | 2 +- variants/wio-sdk-wm1110/platformio.ini | 18 +- variants/wio-sdk-wm1110/softdevice/ble.h | 652 -- variants/wio-sdk-wm1110/softdevice/ble_err.h | 92 - variants/wio-sdk-wm1110/softdevice/ble_gap.h | 2895 ----- variants/wio-sdk-wm1110/softdevice/ble_gatt.h | 232 - .../wio-sdk-wm1110/softdevice/ble_gattc.h | 764 -- .../wio-sdk-wm1110/softdevice/ble_gatts.h | 904 -- variants/wio-sdk-wm1110/softdevice/ble_hci.h | 135 - .../wio-sdk-wm1110/softdevice/ble_l2cap.h | 495 - .../wio-sdk-wm1110/softdevice/ble_ranges.h | 149 - .../wio-sdk-wm1110/softdevice/ble_types.h | 217 - .../wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h | 259 - .../wio-sdk-wm1110/softdevice/nrf_error.h | 90 - .../wio-sdk-wm1110/softdevice/nrf_error_sdm.h | 73 - .../wio-sdk-wm1110/softdevice/nrf_error_soc.h | 85 - variants/wio-sdk-wm1110/softdevice/nrf_nvic.h | 449 - variants/wio-sdk-wm1110/softdevice/nrf_soc.h | 1046 -- variants/wio-sdk-wm1110/softdevice/nrf_svc.h | 98 - variants/wio-sdk-wm1110/variant.h | 2 - variants/wio-tracker-wm1110/platformio.ini | 3 +- variants/xiao_ble/platformio.ini | 2 +- version.properties | 2 +- 161 files changed, 1322 insertions(+), 22200 deletions(-) delete mode 100644 bin/mergehex delete mode 100644 bin/s140_nrf52_7.3.0_softdevice.hex delete mode 100644 bin/setup-python-for-esp-debug.sh delete mode 100644 boards/heltec_mesh_node_t114.json delete mode 100644 pyocd.yaml delete mode 100644 src/PowerMon.cpp delete mode 100644 src/PowerMon.h delete mode 100644 src/gps/GPSUpdateScheduling.cpp delete mode 100644 src/gps/GPSUpdateScheduling.h delete mode 100644 src/mesh/generated/meshtastic/powermon.pb.cpp delete mode 100644 src/mesh/generated/meshtastic/powermon.pb.h delete mode 100644 src/modules/PowerStressModule.cpp delete mode 100644 src/modules/PowerStressModule.h delete mode 100644 variants/heltec_mesh_node_t114/platformio.ini delete mode 100644 variants/heltec_mesh_node_t114/variant.cpp delete mode 100644 variants/heltec_mesh_node_t114/variant.h delete mode 100644 variants/heltec_vision_master_e213/pins_arduino.h delete mode 100644 variants/heltec_vision_master_e213/platformio.ini delete mode 100644 variants/heltec_vision_master_e213/variant.h delete mode 100644 variants/heltec_vision_master_e290/pins_arduino.h delete mode 100644 variants/heltec_vision_master_e290/platformio.ini delete mode 100644 variants/heltec_vision_master_e290/variant.h delete mode 100644 variants/heltec_vision_master_t190/pins_arduino.h delete mode 100644 variants/heltec_vision_master_t190/platformio.ini delete mode 100644 variants/heltec_vision_master_t190/variant.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_err.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gap.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatt.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gattc.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_gatts.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_hci.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_l2cap.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_ranges.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/ble_types.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_nvic.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_soc.h delete mode 100644 variants/wio-sdk-wm1110/softdevice/nrf_svc.h diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 7f8659523..b5b4cb6f3 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -14,7 +14,7 @@ runs: - name: Install dependencies shell: bash run: | - sudo apt-get -y update --fix-missing + sudo apt-get -y update sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev - name: Setup Python diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 3e8b4c001..8fe8e6c31 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -13,7 +13,7 @@ jobs: - name: Install libbluetooth shell: bash run: | - sudo apt-get update --fix-missing + sudo apt-get update sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index ac509a096..eb1779963 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -29,7 +29,6 @@ jobs: name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip overwrite: true path: | - release/*.hex release/*.uf2 release/*.elf release/*.zip diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index 1fd8fad30..697d08727 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -13,7 +13,6 @@ jobs: - name: Install libbluetooth shell: bash run: | - apt-get update -y --fix-missing apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index 39b297d1b..ee5eb66eb 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -13,7 +13,6 @@ jobs: - name: Install libbluetooth shell: bash run: | - apt-get update -y --fix-missing apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev - name: Checkout code diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 14c8a9d10..25a0fbad2 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -136,7 +136,7 @@ jobs: build-rpi2040, package-raspbian, package-raspbian-armv7l, - package-native, + package-native ] steps: - name: Checkout code @@ -168,7 +168,6 @@ jobs: path: | ./firmware-*.bin ./firmware-*.uf2 - ./firmware-*.hex ./firmware-*-ota.zip ./device-*.sh ./device-*.bat diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 2d9f60899..8a2f18ad5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.22.2 + version: 1.22.1 plugins: sources: - id: trunk @@ -31,10 +31,6 @@ lint: - gitleaks@8.18.2 - clang-format@16.0.3 - prettier@3.2.5 - ignore: - - linters: [ALL] - paths: - - bin/** runtimes: enabled: - python@3.10.8 diff --git a/.vscode/settings.json b/.vscode/settings.json index bf9b82111..07e198f0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,8 +4,5 @@ "trunk.enableWindows": true, "files.insertFinalNewline": false, "files.trimFinalNewlines": false, - "cmake.configureOnOpen": false, - "[cpp]": { - "editor.defaultFormatter": "trunk.io" - } + "cmake.configureOnOpen": false } diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 58c1302da..f3eb0cbc0 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -44,7 +44,7 @@ lib_deps = ${networking_base.lib_deps} ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 - h2zero/NimBLE-Arduino@^1.4.2 + h2zero/NimBLE-Arduino@^1.4.1 https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index cf4ca60cb..a9980f486 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -2,8 +2,8 @@ set -e -VERSION=$(bin/buildinfo.py long) -SHORT_VERSION=$(bin/buildinfo.py short) +VERSION=`bin/buildinfo.py long` +SHORT_VERSION=`bin/buildinfo.py short` OUTDIR=release/ @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* @@ -23,27 +23,14 @@ basename=firmware-$1-$VERSION pio run --environment $1 # -v SRCELF=.pio/build/$1/firmware.elf -cp $SRCELF $OUTDIR/$basename.elf - -echo "Generating NRF52 dfu file" DFUPKG=.pio/build/$1/firmware.zip +cp $SRCELF $OUTDIR/$basename.elf cp $DFUPKG $OUTDIR/$basename-ota.zip echo "Generating NRF52 uf2 file" SRCHEX=.pio/build/$1/firmware.hex +bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 -# if WM1110 target, merge hex with softdevice 7.3.0 -if (echo $1 | grep -q "wio-sdk-wm1110"); then - echo "Merging with softdevice" - sudo chmod +x ./bin/mergehex - bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex - SRCHEX=.pio/build/$1/$basename.hex - bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 - cp $SRCHEX $OUTDIR - cp bin/*.uf2 $OUTDIR -else - bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 - cp bin/device-install.* $OUTDIR - cp bin/device-update.* $OUTDIR - cp bin/*.uf2 $OUTDIR -fi +cp bin/device-install.* $OUTDIR +cp bin/device-update.* $OUTDIR +cp bin/*.uf2 $OUTDIR diff --git a/bin/mergehex b/bin/mergehex deleted file mode 100644 index 2a93c571003f60f7d94e7a588acbc00285af9354..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2102544 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

$&*Qh7qS<%!@nUL*MI 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

jqWDRdU%3zsH=u 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�hF6bG}d+&NiOQLS&25sIl1%U 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

d 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

guyN)-&D4*RZyzGw7}F7F7xLMB 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

;hP?-a_*c@MfW;siN=Mr7&&B&U!y||w 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 diff --git a/bin/s140_nrf52_7.3.0_softdevice.hex b/bin/s140_nrf52_7.3.0_softdevice.hex deleted file mode 100644 index 639927f50..000000000 --- a/bin/s140_nrf52_7.3.0_softdevice.hex +++ /dev/null @@ -1,9726 +0,0 @@ -:020000040000FA -:1000000000040020810A000015070000610A0000BA -:100010001F07000029070000330700000000000050 -:10002000000000000000000000000000A50A000021 -:100030003D070000000000004707000051070000D6 -:100040005B070000650700006F07000079070000EC -:10005000830700008D07000097070000A10700003C -:10006000AB070000B5070000BF070000C90700008C -:10007000D3070000DD070000E7070000F1070000DC -:10008000FB070000050800000F0800001908000029 -:10009000230800002D080000370800004108000078 -:1000A0004B080000550800005F08000069080000C8 -:1000B000730800007D080000870800009108000018 -:1000C0009B080000A5080000AF080000B908000068 -:1000D000C3080000CD080000D7080000E1080000B8 -:1000E000EB080000F5080000FF0800000909000007 -:1000F000130900001D090000270900003109000054 -:100100003B0900001FB500F003F88DE80F001FBD8C -:1001100000F0ACBC40F6FC7108684FF01022401CA7 -:1001200008D00868401C09D00868401C04D0086842 -:1001300000F037BA9069F5E79069F9E7704770B554 -:100140000B46010B184400F6FF70040B4FF0805073 -:100150000022090303692403406943431D1B104621 -:1001600000F048FA29462046BDE8704000F042BA47 -:10017000F0B54FF6FF734FF4B4751A466E1E11E0DA -:10018000A94201D3344600E00C46091B30F8027B3B -:10019000641E3B441A44F9D19CB204EB134394B25D -:1001A00004EB12420029EBD198B200EB134002EBB2 -:1001B000124140EA0140F0BDF34992B00446D1E952 -:1001C0000001CDE91001FF224021684600F0F4FB58 -:1001D00094E80F008DE80F00684610A902E004C8FB -:1001E00041F8042D8842FAD110216846FFF7C0FF7C -:1001F0001090AA208DF8440000F099F9FFF78AFFCB -:1002000040F6FC7420684FF01025401C0FD0206889 -:1002100010226946803000F078F92068401C08D030 -:100220002068082210A900F070F900F061F9A869AF -:10023000EEE7A869F5E74FF080500369406940F6A2 -:10024000FC71434308684FF01022401C06D0086838 -:1002500000F58050834203D2092070479069F7E788 -:100260000868401C04D00868401C03D00020704778 -:100270009069F9E70420704770B504460068C34DE3 -:10028000072876D2DFE800F033041929631E250021 -:10029000D4E9026564682946304600F062F92A46CE -:1002A0002146304600F031F9AA002146304600F0E0 -:1002B00057FB002800D0032070BD00F009FC4FF46C -:1002C000805007E0201D00F040F90028F4D100F034 -:1002D000FFFB60682860002070BD241D94E80700C3 -:1002E000920000F03DFB0028F6D00E2070BDFFF715 -:1002F000A2FF0028FAD1D4E901034FF0805100EBAE -:10030000830208694D69684382420ED840F6F8704E -:1003100005684FF010226D1C09D0056805EB8305B8 -:100320000B6949694B439D4203D9092070BD55694A -:10033000F4E70168491C03D00068401C02D003E0C8 -:100340005069FAE70F2070BD2046FFF735FFFFF731 -:1003500072FF0028F7D1201D00F0F7F80028F2D135 -:1003600060680028F0D100F0E2F8FFF7D3FE00F05B -:10037000BFF8072070BD10B50C46182802D0012028 -:10038000086010BD2068FFF777FF206010BD41684E -:10039000054609B1012700E0002740F6F8742068FF -:1003A0004FF01026401C2BD02068AA68920000F065 -:1003B000D7FA38B3A86881002068401C27D020688D -:1003C000FFF7BDFED7B12068401C22D026684FF051 -:1003D0008050AC686D68016942695143A9420DD9EA -:1003E000016940694143A14208D92146304600F0E5 -:1003F000B8F822462946304600F087F800F078F831 -:100400007069D2E700F093F8FFF784FEF6E77069B1 -:10041000D6E77669DBE740F6FC7420684FF01026DB -:10042000401C23D02068401C0CD02068401C1FD0EA -:100430002568206805F18005401C1BD027683879A5 -:10044000AA2819D040F6F8700168491C42D001680A -:10045000491C45D00168491C3ED001680968491C07 -:100460003ED00168491C39D000683EE0B069DAE747 -:10047000B569DEE7B769E2E710212846FFF778FEA5 -:100480003968814222D12068401C05D0D4F8001080 -:1004900001F18002C03107E0B169F9E730B108CA63 -:1004A00051F8040D984201D1012000E000208A4259 -:1004B000F4D158B1286810B1042803D0FEE72846CB -:1004C000FFF765FF3149686808600EE0FFF722FE1C -:1004D00000F00EF87169BBE77169BFE7706904E06D -:1004E0004FF480500168491C01D000F0CBFAFEE7C0 -:1004F000BFF34F8F26480168264A01F4E06111439B -:100500000160BFF34F8F00BFFDE72DE9F0411746B3 -:100510000D460646002406E03046296800F054F8EF -:10052000641C2D1D361DBC42F6D3BDE8F08140F69B -:10053000FC700168491C04D0D0F800004FF48051D1 -:10054000FDE54FF010208069F8E74FF080510A690F -:10055000496900684A43824201D810207047002050 -:10056000704770B50C4605464FF4806608E0284693 -:1005700000F017F8B44205D3A4F5806405F5805562 -:10058000002CF4D170BD0000F40A0000000000202F -:100590000CED00E00400FA05144801680029FCD0C5 -:1005A0007047134A0221116010490B68002BFCD0E0 -:1005B0000F4B1B1D186008680028FCD0002010603D -:1005C00008680028FCD07047094B10B501221A605A -:1005D000064A1468002CFCD0016010680028FCD08A -:1005E0000020186010680028FCD010BD00E4014015 -:1005F00004E5014070B50C46054600F073F810B9EB -:1006000000F07EF828B121462846BDE8704000F091 -:1006100007B821462846BDE8704000F037B8000012 -:100620007FB5002200920192029203920A0B000B06 -:100630006946012302440AE0440900F01F0651F80C -:10064000245003FA06F6354341F82450401C8242F8 -:10065000F2D80D490868009A10430860081D016827 -:10066000019A1143016000F03DF800280AD00649C4 -:1006700010310868029A10430860091D0868039A3F -:10068000104308607FBD00000006004030B50F4CED -:10069000002200BF04EB0213D3F800582DB9D3F8A1 -:1006A000045815B9D3F808581DB1521C082AF1D3C3 -:1006B00030BD082AFCD204EB0212C2F80008C3F8CD -:1006C00004180220C3F8080830BD000000E0014013 -:1006D0004FF08050D0F83001082801D0002070473A -:1006E000012070474FF08050D0F83011062905D016 -:1006F000D0F83001401C01D0002070470120704725 -:100700004FF08050D0F830010A2801D00020704707 -:100710000120704708208F490968095808471020B0 -:100720008C4909680958084714208A4909680958FA -:100730000847182087490968095808473020854923 -:100740000968095808473820824909680958084744 -:100750003C20804909680958084740207D490968BC -:100760000958084744207B49096809580847482028 -:1007700078490968095808474C207649096809589A -:10078000084750207349096809580847542071499F -:1007900009680958084758206E49096809580847E8 -:1007A0005C206C4909680958084760206949096854 -:1007B00009580847642067490968095808476820AC -:1007C00064490968095808476C2062490968095852 -:1007D000084770205F4909680958084774205D4937 -:1007E00009680958084778205A490968095808478C -:1007F0007C205849096809580847802055490968EC -:10080000095808478420534909680958084788202F -:1008100050490968095808478C204E490968095809 -:10082000084790204B4909680958084794204949CE -:10083000096809580847982046490968095808472F -:100840009C204449096809580847A0204149096883 -:1008500009580847A4203F49096809580847A820B3 -:100860003C49096809580847AC203A4909680958C1 -:100870000847B0203749096809580847B420354966 -:10088000096809580847B8203249096809580847D3 -:10089000BC203049096809580847C0202D4909681B -:1008A00009580847C4202B49096809580847C82037 -:1008B0002849096809580847CC2026490968095879 -:1008C0000847D0202349096809580847D4202149FE -:1008D000096809580847D8201E4909680958084777 -:1008E000DC201C49096809580847E02019490968B3 -:1008F00009580847E4201749096809580847E820BB -:100900001449096809580847EC2012490968095830 -:100910000847F0200F49096809580847F4200D4995 -:10092000096809580847F8200A490968095808471A -:10093000FC2008490968095808475FF48070054998 -:10094000096809580847000003480449024A034B54 -:100950007047000000000020000B0000000B0000AA -:1009600040EA010310B59B070FD1042A0DD310C82C -:1009700008C9121F9C42F8D020BA19BA884201D97E -:10098000012010BD4FF0FF3010BD1AB1D30703D0C6 -:10099000521C07E0002010BD10F8013B11F8014B7C -:1009A0001B1B07D110F8013B11F8014B1B1B01D198 -:1009B000921EF1D1184610BD02F0FF0343EA032254 -:1009C00042EA024200F005B87047704770474FF0A6 -:1009D00000020429C0F0128010F0030C00F01B800C -:1009E000CCF1040CBCF1020F18BF00F8012BA8BF1A -:1009F00020F8022BA1EB0C0100F00DB85FEAC17CDE -:100A000024BF00F8012B00F8012B48BF00F8012B90 -:100A100070474FF0000200B51346944696462039C1 -:100A200022BFA0E80C50A0E80C50B1F12001BFF4A7 -:100A3000F7AF090728BFA0E80C5048BF0CC05DF80D -:100A400004EB890028BF40F8042B08BF704748BF5B -:100A500020F8022B11F0804F18BF00F8012B7047CF -:100A6000014B1B68DB6818470000002009480A4951 -:100A70007047FFF7FBFFFFF745FB00BD20BFFDE719 -:100A8000064B1847064A1060016881F308884068E1 -:100A900000470000000B0000000B000017040000DE -:100AA000000000201EF0040F0CBFEFF30881EFF3ED -:100AB0000981886902380078182803D100E0000015 -:100AC000074A1047074A12682C3212681047000084 -:100AD00000B5054B1B68054A9B58984700BD0000B0 -:100AE0007703000000000020F00A0000040000006E -:100AF000001000000000000000FFFFFF0090D00386 -:10100000C8130020395E020085C100009F5D020008 -:1010100085C1000085C1000085C1000000000000FE -:10102000000000000000000000000000C55E02009B -:1010300085C100000000000085C1000085C10000DE -:101040002D5F0200335F020085C1000085C10000F2 -:1010500085C1000085C1000085C1000085C1000078 -:10106000395F020085C1000085C100003F5F0200BA -:1010700085C10000455F02004B5F0200515F020026 -:1010800085C1000085C1000085C1000085C1000048 -:1010900085C1000085C1000085C1000085C1000038 -:1010A00085C10000575F020085C1000085C10000B6 -:1010B00085C1000085C1000085C1000085C1000018 -:1010C0005D5F020085C1000085C1000085C1000090 -:1010D00085C1000085C1000085C1000085C10000F8 -:1010E00085C1000085C1000085C1000085C10000E8 -:1010F00085C1000085C1000085C1000085C10000D8 -:1011000085C1000085C1000000F002F824F083FED4 -:101110000AA090E8000C82448344AAF10107DA4552 -:1011200001D124F078FEAFF2090EBAE80F0013F0F7 -:10113000010F18BFFB1A43F00103184718530200B0 -:10114000385302000A444FF0000C10F8013B13F032 -:10115000070408BF10F8014B1D1108BF10F8015B10 -:10116000641E05D010F8016B641E01F8016BF9D103 -:1011700013F0080F1EBF10F8014BAD1C0C1B09D15A -:101180006D1E58BF01F801CBFAD505E014F8016BCC -:1011900001F8016B6D1EF9D59142D6D3704700005E -:1011A0000023002400250026103A28BF78C1FBD870 -:1011B000520728BF30C148BF0B6070471FB500F011 -:1011C00003F88DE80F001FBD24F022BE70B51A4C45 -:1011D00005460A202070A01C00F0D5F85920A080F8 -:1011E00029462046BDE8704008F082B908F08BB966 -:1011F00070B50C461149097829B1A0F160015E294A -:1012000008D3012013E0602804D0692802D043F2FB -:1012100001000CE020CC0A4E94E80E0006EB8000A2 -:10122000A0F58050241FD0F8806E2846B04720607B -:1012300070BD012070470000080000201C00002045 -:10124000A05F02003249884201D20120704700208D -:10125000704770B50446A0F500002E4EB0F1786FCF -:1012600002D23444A4F500042948844201D2012565 -:1012700000E0002500F043F848B125B9B44204D39A -:101280002548006808E0012070BD002070BD002DD9 -:10129000F9D1B442F9D321488442F6D2F3E710B52C -:1012A0000446A0F50000B0F1786F03D21948044459 -:1012B000A4F5000400F023F84FF0804130B1164847 -:1012C000006804E08C4204D2012003E01348844209 -:1012D000F8D2002080F0010010BD10B520B1FFF75A -:1012E000DEFF08B1012010BD002010BD10B520B1F7 -:1012F000FFF7AFFF08B1012010BD002010BD084866 -:1013000008490068884201D10120704700207047D9 -:1013100000700200000000202000002008000020D3 -:101320005C000020BEBAFECA10B5044600210120B0 -:1013300000F042F800210B2000F03EF800210820C8 -:1013400000F03AF80421192000F036F804210D20AD -:1013500000F032F804210E2000F02EF804210F20B6 -:1013600000F02AF80421C84300F026F806211620D0 -:1013700000F022F80621152000F01EF82046FFF7A5 -:1013800025FF002010BD40F2231101807047FFF7B8 -:101390002DBF1148704710487047104A10B51468A7 -:1013A0000E4B0F4A08331A60FFF722FF0B48001D4F -:1013B000046010BD704770474907090E002804DB20 -:1013C00000F1E02080F80014704700F00F0000F1F9 -:1013D000E02080F8141D704703F900421005024018 -:1013E00001000001FD48002101604160018170475A -:1013F0002DE9FF4F93B09B46209F160004460DD069 -:101400001046FFF726FF18B1102017B0BDE8F08F87 -:101410003146012001F0D3FE0028F6D101258DF8D8 -:1014200042504FF4C050ADF84000002210A92846A9 -:1014300006F0C5FC0028E8D18DF84250A8464FF4CC -:1014400028500025ADF840001C2229466846079523 -:101450000DF01DF89DF81C000DF11C0A20F00F0086 -:10146000401C20F0F00010308DF81C0020788DF822 -:101470001D0061789DF81E000DF1400961F34200E6 -:1014800040F001008DF81E009DF8000008AA40F011 -:1014900002008DF800002089ADF83000ADF8325020 -:1014A0006089ADF83400CDF82CA060680E900AA9D0 -:1014B000CDF82890684606F090FA0028A5D160681B -:1014C000FFF70BFF40B16068FFF710FF20B96078AD -:1014D00000F00300022801D0012000E00020BF4CF2 -:1014E00008AA0AA92072BDF8200020808DF8428049 -:1014F00042F60120ADF840009DF81E0020F00600E5 -:10150000801C20F001008DF81E000220ADF8300094 -:10151000ADF8340014A80E90684606F05EFA002874 -:1015200089D1BDF82000608036B1211D304600F021 -:101530005FF90028C2D109E0BBF1000F05D00CF023 -:1015400021FDE8BB0CF01EFDD0BBA58017B1012F1B -:1015500043D04AE08DF8428042F6A620ADF8400024 -:1015600046461C220021684607950CF090FF9DF826 -:101570001C00ADF8346020F00F00401C20F0F0009B -:1015800010308DF81C009DF81D0020F0FF008DF834 -:101590001D009DF81E0020F0060040F00100801C98 -:1015A0008DF81E009DF800008DF8446040F00200A8 -:1015B0008DF80000CDE90A9AADF8306011A800E07E -:1015C00011E00E9008AA0AA9684606F006FA00285B -:1015D000A6D1BDF82000E08008E00CF0D3FC10B9E3 -:1015E0000CF0D0FC08B103200FE7E58000200CE7E9 -:1015F0003EB50446794D0820ADF80000A88828B112 -:101600002046FFF726FE18B110203EBD06203EBD45 -:101610002146012001F0D3FD0028F8D12088ADF843 -:1016200004006088ADF80600A088ADF80800E088E6 -:10163000ADF80A00A88801AB6A46002106F0AAFDB1 -:10164000BDF800100829E2D003203EBD7FB5634DF0 -:101650000446A88868B1002002900820ADF8080070 -:10166000CDF80CD02046FFF7F4FD20B1102004B0D7 -:1016700070BD0620FBE7A98802AA4FF6FF7006F0AE -:10168000CCFF0028F3D1BDF80810082901D00320B1 -:10169000EDE7BDF800102180BDF802106180BDF8B3 -:1016A0000410A180BDF80610E180E0E701B582B02A -:1016B0000220ADF80000494802AB6A46408800218C -:1016C00006F068FDBDF80010022900D003200EBD11 -:1016D0001CB5002100910221ADF800100190FFF728 -:1016E000DEFD08B110201CBD3C486A4641884FF61B -:1016F000FF7006F092FFBDF800100229F3D003201E -:101700001CBDFEB5354C06461546207A0F46C0076F -:1017100005D00846FFF79DFD18B11020FEBD0F2033 -:10172000FEBDF82D01D90C20FEBD3046FFF791FD1E -:1017300018BB208801A905F03AFE0028F4D13078C2 -:101740008DF80500208801A906F003FD0028EBD1E3 -:1017500000909DF800009DF8051040F002008DF803 -:101760000000090703D040F008008DF80000208831 -:10177000694606F08BFC0028D6D1ADF808502088C9 -:101780003B4602AA002106F005FDBDF80810A9425B -:10179000CAD00320FEBD7CB5054600200090019014 -:1017A0000888ADF800000C4628460195FFF795FD26 -:1017B00018B92046FFF773FD08B110207CBD15B1A4 -:1017C000BDF8000060B105486A4601884FF6FF7019 -:1017D00006F023FFBDF8001021807CBD240200200C -:1017E0000C20FAE72F48C088002800D0012070475D -:1017F00030B5044693B000200D46014600901422F7 -:1018000001A80CF044FE1C22002108A80CF03FFEA9 -:101810009DF80000CDF808D020F00F00401C20F00B -:10182000F00010308DF800009DF8010006AA20F0AD -:10183000FF008DF801009DF8200001A940F0020092 -:101840008DF8200001208DF8460042F60420ADF806 -:10185000440011A801902088ADF83C006088ADF8E4 -:101860003E00A088ADF84000E088ADF842009DF849 -:10187000020020F00600801C20F001008DF802001C -:101880000820ADF80C00ADF810000FA8059008A8CE -:1018900006F0A3F8002803D1BDF818002880002026 -:1018A00013B030BD24020020F0B5007B059F1E461A -:1018B00014460D46012800D0FFDF0C2030803A206E -:1018C0003880002C08D0287A032806D0287B0128ED -:1018D00000D0FFDF17206081F0BDA889FBE72DE96C -:1018E000F0470D4686B095F80C900E991446B9F164 -:1018F000010F0BD01022007B2E8A9046052807D0BE -:10190000062839D0FFDF06B0BDE8F0870222F2E7F3 -:10191000E8890C2200EB400002EB400018803320E5 -:101920000880002CEFD0E8896081002720E0009635 -:10193000688808F1020301AA696900F097FF06EBC5 -:101940000800801C07EB470186B204EB4102BDF89A -:1019500004009081F848007808B1012300E00023DA -:101960000DF1060140460E3214F029F87F1CBFB27B -:101970006089B842DBD8C6E734200880E889B9F12D -:10198000010F11D0122148430E301880002CBAD01C -:10199000E88960814846B9F1010F00D00220207328 -:1019A00000270DF1040A1FE00621ECE70096688885 -:1019B00008F1020301AA696900F058FF06EB08006C -:1019C000801C86B2B9F1010F12D007EBC70004EBFF -:1019D0004000BDF80410C18110220AF1020110304C -:1019E0000CF02BFD7F1CBFB26089B842DED88AE7BD -:1019F00007EB470104EB4102BDF80400D0810AF176 -:101A000002014046103213F0FCFFEBE72DE9F047EE -:101A10000E4688B090F80CC096F80C80378AF5898D -:101A20000C20DFF81493109902F10C04BCF1030FA1 -:101A300008D0BCF1040F3DD0BCF1070F75D0FFDF1B -:101A400008B061E705EB850C00EB4C0018803120F5 -:101A50000880002AF4D0A8F1060000F0FF0A5581A2 -:101A600024E01622002101A80CF011FD00977088D7 -:101A7000434601AA716900F0F9FEBDF80400208018 -:101A8000BDF80600E080BDF80800208199F800004C -:101A900008B1012300E00023A21C0DF10A01504609 -:101AA00013F08DFF07EB080087B20A346D1EADB24C -:101AB000D7D2C5E705EB850C00EB4C00188032202F -:101AC0000880002ABCD0A8F1050000F0FF0A55816B -:101AD00037E000977088434601AA716900F0C6FE9E -:101AE0009DF80600BDF80410E1802179420860F3FA -:101AF000000162F34101820862F38201C20862F3CD -:101B0000C301020962F30411420962F3451182091B -:101B100062F386112171C0096071BDF80700208150 -:101B200099F8000010B1012301E00EE000232246E5 -:101B30000DF10901504613F042FF07EB080087B290 -:101B40000A346D1EADB2C4D27AE7A8F1020084B2A5 -:101B500005FB08FC0CF10E00188035200880002AD7 -:101B6000A7D05581948100971FFA8CF370880E32AC -:101B7000716900F07BFE63E72DE9F84F1E460A9D70 -:101B80000C4681462AB1607A00F58070D080E089E9 -:101B9000108199F80C000C274FF000084FF00E0A46 -:101BA0000D2872D2DFE800F09D070E1B272F374566 -:101BB000546972727200214648460095FFF774FE20 -:101BC000BDE8F88F207B9146082802D0032800D07A -:101BD000FFDF3780302009E0A9F80A80F0E7207B9A -:101BE0009146042800D0FFDF378031202880B9F1EA -:101BF000000FF1D1E4E7207B9146042800D0FFDFFD -:101C000037803220F2E7207B9146022800D0FFDFA8 -:101C100037803320EAE7207B1746022800D0FFDF19 -:101C20003420A6F800A02880002FC9D0A7F80A8089 -:101C3000C6E7207B1746042800D0FFDF3520A6F832 -:101C400000A02880002FBBD04046A7F80A8012E0F1 -:101C5000207B1746052802D0062800D0FFDF102081 -:101C6000308036202880002FAAD0E0897881A7F81C -:101C70000E80B9F80E00B881A2E7207B91460728B4 -:101C800000D0FFDF37803720B0E72AE04FF01200A6 -:101C900018804FF038001700288091D0E0897881B3 -:101CA000A7F80E80A7F8108099F80C000A2805D034 -:101CB0000B2809D00C280DD0FFDF81E7207B0A28F4 -:101CC00000D0FFDF01200AE0207B0B2800D0FFDFDF -:101CD000042004E0207B0C2800D0FFDF05203873AF -:101CE0006EE7FFDF6CE770B50C46054601F0AAFB16 -:101CF00020B10078222804D2082070BD43F20200EF -:101D000070BD0521284612F0D1F8206008B10020EE -:101D100070BD032070BD30B44880087820F00F00FB -:101D2000C01C20F0F000903001F8080B1DCA81E8BB -:101D30001D0030BC07F05DBC100000202DE9FF47FE -:101D400084B0002782460297079890468946123051 -:101D50000AF069FA401D20F00306079828B907A980 -:101D60005046FFF7C0FF002854D1B9F1000F05D04D -:101D70000798017B19BB052504681BE098F8000053 -:101D8000092803D00D2812D0FFDF46E0079903256C -:101D90004868B0B3497B42887143914239D98AB2CD -:101DA000B3B2011D11F0F5FE0446078002E0079C66 -:101DB000042508340CB1208810B1032D29D02CE063 -:101DC0000798012112300AF060FAADF80C000246C3 -:101DD00002AB2946504608F0B8FA070001D1A01C12 -:101DE000029007983A461230C8F80400A8F802A0FA -:101DF00003A94046029B0AF055FAD8B10A2817D227 -:101E000000E006E0DFE800F007091414100B0D14E1 -:101E10001412132014E6002012E6112010E6082008 -:101E20000EE643F203000BE6072009E60D2007E665 -:101E3000032005E6BDF80C002346CDE900702A46D4 -:101E40005046079900F022FD57B9032D08D1079895 -:101E5000B3B2417B406871438AB2011D11F0ADFEFF -:101E6000B9F1000FD7D0079981F80C90D3E72DE98D -:101E7000FE4F91461A881C468A468046FAB102AB4C -:101E8000494608F062FA050019D04046A61C27888A -:101E900012F04FF93246072629463B46009611F0CC -:101EA0005EFD20882346CDE900504A465146404613 -:101EB00000F0ECFC002020800120BDE8FE8F002017 -:101EC000FBE710B586B01C46AAB104238DF800309C -:101ED0001388ADF808305288ADF80A208A788DF85A -:101EE0000E200988ADF80C1000236A462146FFF742 -:101EF00025FF06B010BD1020FBE770B50D4605218B -:101F000011F0D4FF040000D1FFDF294604F11200D4 -:101F1000BDE870400AF0A2B92DE9F8430D468046AD -:101F2000002607F063FB04462878102878D2DFE803 -:101F300000F0773B345331311231313108313131D6 -:101F400031312879001FC0B2022801D0102810D1E9 -:101F500014BBFFDF35E004B9FFDF0521404611F077 -:101F6000A5FF007B032806D004280BD0072828D023 -:101F7000FFDF072655E02879801FC0B2022820D055 -:101F800050B1F6E72879401FC0B2022819D01028B6 -:101F900017D0EEE704B9FFDF13E004B9FFDF2879BB -:101FA00001280ED1172137E00521404611F07EFFB0 -:101FB000070000D1FFDF07F1120140460AF02BF9BC -:101FC0002CB12A4621464046FFF7A5FE29E0132101 -:101FD000404602F01FFD24E004B9FFDF0521404622 -:101FE00011F064FF060000D1FFDF694606F1120020 -:101FF0000AF01BF9060000D0FFDFA988172901D2DB -:10200000172200E00A46BDF80000824202D90146CC -:1020100002E005E01729C5D3404600F047FCD0E7B1 -:10202000FFDF3046BDE8F883401D20F0030219B100 -:1020300002FB01F0001D00E000201044704713B5C2 -:10204000009858B10024684611F04DFD002C04D1D1 -:10205000F749009A4A6000220A701CBD0124002042 -:10206000F2E72DE9F0470C461546242200212046D0 -:102070000CF00DFA05B9FFDFA87860732888DFF847 -:10208000B0A3401D20F00301AF788946DAF80400C0 -:1020900011F047FD060000D1FFDF4FF00008266079 -:1020A000A6F8008077B109FB07F1091D0AD0DAF81C -:1020B000040011F036FD060000D1FFDF6660C6F8AF -:1020C000008001E0C4F80480298804F11200BDE812 -:1020D000F0470AF091B82DE9F047804601F112006F -:1020E0000D4681460AF09FF8401DD14F20F00302B3 -:1020F0006E7B14462968786811F03EFD3EB104FB02 -:1021000006F2121D03D06968786811F035FD0520CC -:1021100011F074FE0446052011F078FE201A012803 -:1021200002D1786811F0F2FC49464046BDE8F0471C -:102130000AF078B870B50546052111F0B7FE040025 -:1021400000D1FFDF04F112012846BDE870400AF01B -:1021500062B82DE9F04F91B04FF0000BADF828B008 -:10216000ADF804B047880C4605469246052138462E -:1021700011F09CFE060000D1FFDF24B1A780A4F877 -:1021800006B0A4F808B0297809220B20B2EB111F81 -:1021900073D12A7A04F1100138274FF00C084FF060 -:1021A00012090291102A69D2DFE802F068F2F1F018 -:1021B0008008D3898EA03DDCF3EEB7B7307B0228D0 -:1021C00000D0FFDFA88908EBC001ADF80410302172 -:1021D000ADF82810002C25D06081B5F80E800027BE -:1021E0001DE004EBC709317C89F80E10F189A9F8CC -:1021F0000C10CDF800806888042305AA296900F036 -:1022000035FBBDF81410A9F8101008F10400BDF852 -:1022100016107F1C1FFA80F8A9F81210BFB260894F -:10222000B842DED80CE1307B022800D0FFDFE9891C -:1022300008EBC100ADF804003020ADF8280095F897 -:102240000C90002CA9F10400C0B20F90EAD061817B -:10225000B5F81080002725E0CDF8008068884B464F -:1022600003AA696900F002FB08EB09001FFA80F875 -:102270006F48007818B1012302E0DDE0DAE00023C6 -:1022800004EBC702009204A90C320F9813F097FBDD -:10229000009ABDF80C007F1C1082009ABDF80E0059 -:1022A000BFB250826089B842D6D8C9E00AA800906F -:1022B00001AB224629463046FFF711FBC0E0307BD8 -:1022C000082805D0FFDF03E0307B082800D0FFDFBF -:1022D000E8891030ADF804003620ADF82800002C55 -:1022E0003FD0A9896181F189A18127E0307B09284C -:1022F00000D0FFDFA88901460C30ADF8040037207C -:10230000ADF82800002C2CD06181E8890090AB89C1 -:10231000688804F10C02296955E0E88939211030F8 -:1023200080B2ADF80400ADF82810002C72D0A98955 -:102330006181287A0E280AD002212173E989E1817E -:10234000288A0090EB8968886969029A3BE001213C -:10235000F3E70AA8009001AB224629463046FFF772 -:1023600055FB6DE0307B0A2800D0FFDFADF804900C -:10237000ADF828704CB3A9896181A4F810B0A4F815 -:102380000EB0012020735BE020E002E030E038E096 -:1023900041E0307B0B2800D0FFDF288AADF82870A1 -:1023A0001230ADF8040084B104212173A989618140 -:1023B000E989E181298A2182688A00902B8A6888CC -:1023C00004F11202696900F051FA39E0307B0C28FF -:1023D00000D0FFDFADF80490ADF828703CB30521C4 -:1023E0002173A4F80AB0A4F80EB0A4F810B027E046 -:1023F0000AA8009001AB224629463046FFF754FA5E -:102400001EE00AA8009001AB224629463046FFF79D -:10241000B3FB15E034E03B21ADF80400ADF8281023 -:1024200074B30120E080A4F808B084F80AB007E093 -:1024300010000020FFDF03E0297A012917D0FFDF19 -:10244000BDF80400AAF800006CB1BDF82800208097 -:10245000BDF804006080BDF82800392803D03C286E -:1024600001D086F80CB011B00020BDE8F08F3C21FF -:10247000ADF80400ADF8281014B1697AA172DFE755 -:10248000AAF80000EFE72DE9F84356880F4680468A -:1024900015460521304611F009FD040000D1FFDF8B -:1024A000123400943B46414630466A680AF02EF8E2 -:1024B000B8E570B50D46052111F0F8FC040000D117 -:1024C000FFDF294604F11200BDE8704009F0B8BEF4 -:1024D00070B50D46052111F0E9FC040000D1FFDFC5 -:1024E000294604F11200BDE8704009F0D6BE70B56F -:1024F0000546052111F0DAFC040000D1FFDF04F1EC -:10250000080321462846BDE870400422AFE470B5B8 -:102510000546052111F0CAFC040000D1FFDF214669 -:1025200028462368BDE870400522A0E470B5064641 -:10253000052111F0BBFC040000D1FFDF04F1120003 -:1025400009F071FE401D20F0030511E0011D008817 -:102550000322431821463046FFF789FC00280BD0A0 -:10256000607BABB2684382B26068011D11F05BFB17 -:10257000606841880029E9D170BD70B50E460546F6 -:1025800007F034F8040000D1FFDF012020726672EA -:102590006580207820F00F00C01C20F0F000303063 -:1025A0002070BDE8704007F024B8602801D00720F3 -:1025B00070470878C54900F0010008700020704796 -:1025C0002DE9F0438BB00D461446814606A9FFF76E -:1025D0008AFB002814D14FF6FF7601274FF42058CC -:1025E0008CB103208DF800001020ADF8100007A872 -:1025F000059007AA204604A913F005FA78B1072030 -:102600000BB0BDE8F0830820ADF808508DF80E70CF -:102610008DF80000ADF80A60ADF80C800CE006986B -:10262000A17801742188C1818DF80E70ADF8085031 -:10263000ADF80C80ADF80A606A4602214846069B58 -:10264000FFF77CFBDCE708B501228DF8022042F69B -:102650000202ADF800200A4603236946FFF731FC69 -:1026600008BD08B501228DF8022042F60302ADF83C -:1026700000200A4604236946FFF723FC08BD00B585 -:1026800087B079B102228DF800200A88ADF80820C1 -:102690004988ADF80A1000236A460521FFF74EFB72 -:1026A00007B000BD1020FBE709B1072309E40720AC -:1026B000704770B588B00D461446064606A9FFF768 -:1026C00012FB00280ED17CB10620ADF808508DF821 -:1026D0000000ADF80A40069B6A460821DC813046BE -:1026E000FFF72CFB08B070BD05208DF80000ADF899 -:1026F0000850F0E700B587B059B107238DF80030D6 -:10270000ADF80820039100236A460921FFF716FB64 -:10271000C6E71020C4E770B588B00C460646002511 -:1027200006A9FFF7E0FA0028DCD106980121123053 -:1027300009F0ABFD9CB12178062921D2DFE801F038 -:10274000200505160318801E80B2C01EE28880B2E4 -:102750000AB1A3681BB1824203D90C20C2E7102042 -:10276000C0E7042904D0A08850B901E00620B9E7E9 -:10277000012913D0022905D004291CD005292AD00B -:102780000720AFE709208DF800006088ADF8080049 -:10279000E088ADF80A00A068039023E00A208DF8D5 -:1027A00000006088ADF80800E088ADF80A00A06875 -:1027B0000A25039016E00B208DF800006088ADF824 -:1027C0000800A088ADF80A00E088ADF80C00A06809 -:1027D0000B25049006E00C208DF8000060788DF841 -:1027E00008000C256A4629463046069BFFF7A6FAE4 -:1027F00078E700B587B00D228DF80020ADF80810FD -:1028000000236A461946FFF799FA49E700B587B0F1 -:1028100071B102228DF800200A88ADF8082049889D -:10282000ADF80A1000236A460621FFF787FA37E75A -:10283000102035E770B586B0064601200D46ADF88C -:1028400008108DF80000014600236A463046FFF765 -:1028500075FA040008D12946304605F0B5FC002180 -:10286000304605F0CFFC204606B070BDF8B51C46DA -:1028700015460E46069F11F04AFC2346FF1DBCB2CA -:1028800031462A46009411F036F8F8BD30B41146AE -:10289000DDE902423CB1032903D0002330BC08F03B -:1028A00032BE0123FAE71A8030BC704770B50C467F -:1028B0000546FFF722FB2146284605F094FC2846F2 -:1028C000BDE87040012105F09DBC00001000002013 -:1028D0004FF0E0224FF400400021C2F88001BFF326 -:1028E0004F8FBFF36F8F1748016001601649900248 -:1028F00008607047134900B500220A600A60124B55 -:102900004FF060721A60002808BF00BD0F4A104BDC -:10291000DFF840C001280CD002281CBFFFDF00BD3B -:10292000032008601A604FF4000000BFCCF80000DC -:1029300000BD022008601A604FF04070F6E700B555 -:10294000FFDF00BD00F5004008F50140A4020020B3 -:1029500014F5004004F5014070B50B2000F0BDF9FE -:10296000082000F0BAF900210B2000F0D4F9002172 -:10297000082000F0D0F9F44C01256560A560002026 -:10298000C4F84001C4F84401C4F848010B2000F029 -:10299000B5F9082000F0B2F90B2000F091F925609C -:1029A00070BD10B50B2000F098F9082000F095F9E3 -:1029B000E548012141608160E4490A68002AFCD1B0 -:1029C0000021C0F84011C0F84411C0F848110B2094 -:1029D00000F094F9BDE81040082000F08FB910B560 -:1029E0000B2000F08BF9BDE81040082000F086B9FC -:1029F00000B530B1012806D0022806D0FFDF002044 -:102A000000BDD34800BDD34800BDD248001D00BD65 -:102A100070B5D1494FF000400860D04DC00BC5F8EB -:102A20000803CF4800240460C5F840410820C4359D -:102A300000F053F9C5F83C41CA48047070BD08B5B0 -:102A4000C14A002128B1012811D002281CD0FFDF83 -:102A500008BD4FF48030C2F80803C2F84803BB48F1 -:102A60003C300160C2F84011BDE80840D0E74FF4A7 -:102A70000030C2F80803C2F84803B448403001608F -:102A8000C2F84411B3480CE04FF48020C2F80803A8 -:102A9000C2F84803AD4844300160C2F84811AD485F -:102AA000001D0068009008BD70B516460D4604462E -:102AB000022800D9FFDF0022A348012304F11001FE -:102AC0008B4000EB8401C1F8405526B1C1F840218C -:102AD000C0F8043303E0C0F80833C1F84021C0F85F -:102AE000443370BD2DE9F0411D46144630B1012834 -:102AF00033D0022838D0FFDFBDE8F081891E0022E4 -:102B000021F07F411046FFF7CFFF012D23D0002099 -:102B1000944D924F012668703E61914900203C39E6 -:102B200008600220091D08608D49042030390860C2 -:102B30008B483D34046008206C6000F0DFF83004FE -:102B4000C7F80403082000F0BBF88349F007091F09 -:102B500008602E70D0E70120DAE7012B02D00022B6 -:102B6000012005E00122FBE7012B04D00022022016 -:102B7000BDE8F04198E70122F9E774480068704722 -:102B800070B500F0D8F8704C0546D4F84001002626 -:102B9000012809D1D4F80803C00305D54FF48030CB -:102BA000C4F80803C4F84061D4F8440101280CD1EA -:102BB000D4F80803800308D54FF40030C4F80803A4 -:102BC000C4F84461012013F0EEFED4F84801012856 -:102BD0000CD1D4F80803400308D54FF48020C4F882 -:102BE0000803C4F84861022013F0DDFE5E4805606A -:102BF00070BD70B500F09FF85A4D0446287850B16A -:102C0000FFF706FF687818B10020687013F0CBFE5C -:102C10005548046070BD0320F8E74FF0E0214FF401 -:102C20000010C1F800027047152000F067B84B494A -:102C300001200861082000F061B848494FF47C1079 -:102C4000C1F808030020024601EB8003C3F84025C9 -:102C5000C3F84021401CC0B20628F5D37047410A92 -:102C600043F609525143C0F3080010FB02F000F58F -:102C7000807001EB5020704710B5430B48F2376469 -:102C800063431B0C5C020C60384C03FB0400384BA4 -:102C90004CF2F72443435B0D13FB04F404EB402098 -:102CA00000F580704012107008681844086010BD6C -:102CB0002C484068704729490120C1F8000270473C -:102CC000002809DB00F01F0201219140400980002B -:102CD00000F1E020C0F80011704700280DDB00F083 -:102CE0001F02012191404009800000F1E020C0F85E -:102CF0008011BFF34F8FBFF36F8F7047002809DB40 -:102D000000F01F02012191404009800000F1E02005 -:102D1000C0F8801270474907090E002804DB00F153 -:102D2000E02080F80014704700F00F0000F1E02070 -:102D300080F8141D70470C48001F00680A4A0D49AE -:102D4000121D11607047000000B0004004B5004043 -:102D50004081004044B1004008F50140008000403F -:102D6000408500403C00002014050240F7C2FFFFF0 -:102D70006F0C0100010000010A4810B50468094900 -:102D800009480831086013F0A2FE0648001D0460DF -:102D900010BD0649002008604FF0E0210220C1F874 -:102DA000800270471005024001000001FC1F004036 -:102DB000374901200860704770B50D2000F049F8D0 -:102DC000344C0020C4F800010125C4F804530D2040 -:102DD00000F050F825604FF0E0216014C1F80001C8 -:102DE00070BD10B50D2000F034F82A480121416073 -:102DF0000021C0F80011BDE810400D2000F03AB8E5 -:102E0000254810B504682449244808310860214940 -:102E1000D1F80001012804D0FFDF1F48001D046025 -:102E200010BD1B48001D00680022C0B2C1F800217F -:102E300014F07FFBF1E710B5164800BFD0F8001181 -:102E40000029FBD0FFF7DCFFBDE810400D2000F0AB -:102E500011B800280DDB00F01F020121914040094C -:102E6000800000F1E020C0F88011BFF34F8FBFF366 -:102E70006F8F7047002809DB00F01F02012191408D -:102E80004009800000F1E020C0F880127047000087 -:102E900004D5004000D000401005024001000001B0 -:102EA0004FF0E0214FF00070C1F8800101F5C071D2 -:102EB000BFF34F8FBFF36F8FC1F80001394B8022F2 -:102EC00083F8002441F8800C704700B502460420C6 -:102ED000354903E001EBC0031B792BB1401EC0B2A2 -:102EE000F8D2FFDFFF2000BD41F8302001EBC00128 -:102EF00000224A718A7101220A7100BD2A4A00210A -:102F000002EBC0000171704710B50446042800D3DD -:102F1000FFDF254800EBC4042079012800D0FFDF43 -:102F20006079A179401CC0B2814200D060714FF03D -:102F3000E0214FF00070C1F8000210BD70B504250B -:102F4000194E1A4C16E0217806EBC1000279012ACD -:102F500008D1427983799A4204D04279827156F835 -:102F6000310080472078401CC0B22070042801D373 -:102F7000002020706D1EEDB2E5D270BD0C4810B57A -:102F800004680B490B4808310860064890F80004B3 -:102F90004009042800D0FFDFFFF7D0FF0448001DE0 -:102FA000046010BD19E000E0E0050020580000209A -:102FB00010050240010000010548064A01689142DF -:102FC00001D1002101600449012008607047000020 -:102FD0005C000020BEBAFECA40E5014070B50C4658 -:102FE000054609F02FFC21462846BDE870400AF04E -:102FF00010BD7047704770470021016081807047A5 -:103000002CFFFFFFDBE5B151007002002301FFFF41 -:103010008C00000078DB6A007A2E9AC67DB66CFAC6 -:10302000F35721CCC310D5E51471FB3C30B5FC4DF2 -:103030000446062CA9780ED2DFE804F0030E0E0E2B -:103040000509FFDF08E0022906D0FFDF04E00329BD -:1030500002D0FFDF00E0FFDFAC7030BD30B50446CA -:103060001038EF4D07280CD2DFE800F0040C060CF6 -:103070000C0C0C00FFDF05E0287E112802D0FFDFDA -:1030800000E0FFDF2C7630BD2DE9F04112F026FE86 -:10309000044614F063F8201AC5B2062010F0AEFE04 -:1030A0000446062010F0B2FE211ADD4C207E1228C4 -:1030B00018D000200F18072010F0A0FE06460720A9 -:1030C00010F0A4FE301A3918207E13280CD00020EE -:1030D0000144A078042809D000200844281AC0B26E -:1030E000BDE8F0810120E5E70120F1E70120F4E7E8 -:1030F000CB4810B590F825004108C94800F12600DA -:1031000005D00EF0F5FEBDE8104006F08CB80EF0CC -:10311000D0FEF8E730B50446A1F120000D460A289C -:103120004AD2DFE800F005070C1C2328353A3F445B -:10313000FFDF42E0207820283FD1FFDF3DE0B848A4 -:103140008178052939D0007E122836D020782428AD -:1031500033D0252831D023282FD0FFDF2DE0207851 -:1031600022282AD0232828D8FFDF26E0207822280A -:1031700023D0FFDF21E0207822281ED024281CD075 -:1031800026281AD0272818D0292816D0FFDF14E0C7 -:103190002078252811D0FFDF0FE0207825280CD0DB -:1031A000FFDF0AE02078252807D0FFDF05E0207840 -:1031B000282802D0FFDF00E0FFDF257030BD1FB5FB -:1031C00004466A46002001F0A5FEB4B1BDF8022015 -:1031D0004FF6FF700621824201D1ADF80210BDF812 -:1031E0000420824201D1ADF80410BDF808108142DC -:1031F00003D14FF44860ADF8080068460FF0E2FADA -:1032000006F011F804B010BD70B516460C46054620 -:10321000FEF71FF848B90CB1B44208D90C2070BDB4 -:1032200055F82400FEF715F808B1102070BD2046AF -:10323000641EE4B2F4D270BD2DE9F04105461F468C -:1032400090460E4600240068FEF750F830B9A98871 -:1032500028680844401EFEF749F808B110203FE7EF -:1032600028680028A88802D0B84202D850E0002878 -:10327000F5D0092034E72968085DB8B1671CCA5D3C -:10328000152A2ED03CDC152A3AD2DFE802F039129A -:10329000222228282A2A313139393939393939391C -:1032A00039392200085D30BB641CA4B2A242F9D8AF -:1032B00033E00228DDD1A01C085C88F80000072854 -:1032C00001D2400701D40A200AE7307840F001001B -:1032D00015E0C143C90707E0012807D010E0062028 -:1032E000FEE60107A1F180510029F5D01846F7E666 -:1032F0003078810701D50B20F2E640F002003070F3 -:103300002868005D384484B2A888A04202D2B0E7A1 -:103310004FF4485382B2A242ADD80020E0E610B587 -:10332000027843F2022354080122022C12D003DC5B -:103330003CB1012C16D106E0032C10D07F2C11D10A -:1033400012E0002011E080790324B4EB901F09D132 -:103350000A700BE08079B2EB901F03D1F8E7807917 -:103360008009F5D0184610BDFF200870002010BD60 -:1033700008B500208DF80000294890F82E1051B1B2 -:1033800090F82F0002280FD003280FD0FFDF00BFD6 -:103390009DF8000008BD22486946253001F009FE6D -:1033A0000028F5D0FFDFF3E7032000E001208DF8CF -:1033B0000000EDE738B50C460546694601F0F9FD19 -:1033C00000280DD19DF80010207861F3470020708F -:1033D00055F8010FC4F80100A888A4F805000020E2 -:1033E00038BD38B5137888B102280FD0FF281BD01C -:1033F0000CA46D46246800944C7905EB9414247851 -:1034000064F347031370032805D010E023F0FE0394 -:1034100013700228F7D1D8B240F001000AE0000092 -:10342000F00100200302FF0143F0FE00107010784D -:1034300020F0010010700868C2F801008888A2F826 -:10344000050038BD022110F031BD38B50C460978B1 -:10345000222901D2082038BDADF800008DF80220E5 -:1034600068460EF087FD05F0DEFE050003D1212140 -:103470002046FFF74FFE284638BD1CB500208DF8CA -:103480000000CDF80100ADF80500FB4890F82E00D3 -:10349000022801D0012000E000208DF807006846D6 -:1034A0000EF0F0FD002800D0FFDF1CBD00220A80D6 -:1034B000437892B263F3451222F040020A8000780A -:1034C0000C282BD2DFE800F02A06090E1116191C71 -:1034D0001F220C2742F0110009E042F01D00088075 -:1034E0000020704742F0110012E042F0100040F05E -:1034F0000200F4E742F01000F1E742F00100EEE7CD -:1035000042F0010004E042F00200E8E742F002006D -:1035100040F00400E3E742F00400E0E707207047D2 -:103520002DE9FF478AB00025BDF82C6082461C4675 -:1035300091468DF81C50700703D56068FDF789FE31 -:1035400068B9CD4F4FF0010897F82E0058B197F8A1 -:103550002F00022807D16068FDF7C8FE18B11020BF -:103560000EB0BDE8F087300702D5A08980283DD88D -:10357000700705D4B9F1000F02D097F8240098B372 -:10358000E07DC0F300108DF81B00627D0720032151 -:103590005AB3012A2CD0022AE2D0042AE0D18DF8B5 -:1035A0001710F00627D4A27D072022B3012A22D0CB -:1035B000022A23D0042AD3D18DF819108DF8159042 -:1035C000606810B307A9FFF7AAFE0028C8D19DF8CC -:1035D0001C00FF2816D0606850F8011FCDF80F10AE -:1035E0008088ADF8130014E000E001E00720B7E7A1 -:1035F0008DF81780D5E78DF81980DFE702208DF868 -:103600001900DBE743F20220AAE7CDF80F50ADF82E -:103610001350E07B40B9207C30B9607C20B9A07C9D -:1036200010B9E07CC00601D0062099E78DF800A013 -:10363000BDF82C00ADF80200A0680190A0680290CF -:1036400004F10F0001F0A9FC8DF80C00FFF790FECB -:103650008DF80D009DF81C008DF80E008DF81650A9 -:103660008DF81850E07D08A900F00F008DF81A00C1 -:1036700068460FF0E3F905F0D6FD71E7F0B58FB0BD -:1036800000258DF830508DF814508DF834500646D2 -:103690008DF82850019502950395049519B10FC92D -:1036A00001AC84E80F00744CA078052801D00428F0 -:1036B0000CD101986168884200D120B90398E16873 -:1036C000884203D110B108200FB0F0BD207DC006A4 -:1036D00001D51F2700E0FF273B460DAA05A903A837 -:1036E000FFF7AAFD0028EFD1A08AC10702D0C006CB -:1036F00000D4EE273B460AAA0CA901A8FFF79CFDBF -:103700000028E1D19DF81400C00701D00A20DBE7B2 -:10371000A08A410708D4A17D31B19DF828108907FE -:1037200002D043F20120CFE79DF82810C90709D045 -:10373000400707D4208818B144F25061884201D96B -:103740000720C1E78DF818508DF81960BDF8080002 -:10375000ADF81A000198079006A80FF07BF905F064 -:1037600062FD0028B0D18DF820508DF82160BDF8A1 -:103770001000ADF822000398099008A80FF08CF90A -:1037800005F051FD00289FD101AD241D95E80F00E3 -:1037900084E80F00002097E770B586B00D4604005E -:1037A00005D0FDF7A3FD20B1102006B070BD0820A4 -:1037B000FBE72078C107A98802D0FF2902D303E0E4 -:1037C0001F2901D20920F0E7800763D4FFF75CFCD2 -:1037D00038B12178C1F3C100012804D0032802D0F8 -:1037E00005E01320E1E7244890F82400C8B1C80799 -:1037F0004FF001064FF0000502D08DF80F6001E098 -:103800008DF80F50FFF7B4FD8DF800002078694661 -:10381000C0F3C1008DF8010060788DF80250C20835 -:1038200001D00720C1E730B3C20701D08DF8026094 -:10383000820705D59DF8022042F002028DF8022091 -:10384000400705D59DF8020040F004008DF8020005 -:10385000002022780B18C2F38002DA7001EB4002DC -:103860006388D380401CA388C0B253810228F0D360 -:10387000207A78B905E001E0F00100208DF80260BF -:10388000E6E7607A30B9A07A20B9E07A10B9207BF7 -:10389000C00601D0062088E704F1080001F07DFB96 -:1038A0008DF80E0068460EF0F6FC05F0BCFC002812 -:1038B00089D18DF810608DF81150E088ADF81200B4 -:1038C000ADF8145004A80EF039FD05F0ACFC00284A -:1038D00088D12078C00701D0152000E01320FFF721 -:1038E000BDFB002061E72DE9FF470220FD4E8DF86A -:1038F00004000027708EADF80600B84643F20209B6 -:103900004CE001A810F039FA050006D0708EA8B37B -:10391000A6F83280ADF806803EE0039CA07F010748 -:103920002DD504F124000090A28EBDF80800214698 -:1039300004F1360301F0BCFC050005D04D452AD04A -:10394000112D3CD0FFDF3AE0A07F20F00801E07F9E -:10395000420862F3C711A177810861F30000E077A4 -:1039600094F8210000F01F0084F820002078282817 -:1039700026D129212046FFF7CDFB21E014E04007A6 -:103980000AD5BDF8080004F10E0101F01CFB05008A -:103990000DD04D4510D100257F1CFFB2022010F044 -:1039A0002DFA401CB842ACD8052D11D008E0A07FFC -:1039B00020F00400A07703E0112D00D0FFDF0025E8 -:1039C000BDF806007086052D04D0284604B0C8E571 -:1039D000A6F832800020F9E770B50646FFF732FD01 -:1039E000054605F003FE040000D1FFDF6680207865 -:1039F00020F00F00801C20F0F00020302070032009 -:103A0000207295F83E006072BDE8704005F0F1BD8F -:103A10002DE9F04786B0040000D1FFDF2078B14DDA -:103A200020F00F00801C20F0F000703020706068E3 -:103A30000178491F1B2933D2DFE801F0FE32323210 -:103A400055FD320EFDFD42FC32323278FCFCFBFAB1 -:103A500032FCFCF9F8FCFC00C6883046FFF7F2FCAB -:103A60000546304607F045FCE0B16068007A85F80D -:103A70003E0021212846FFF74DFB3046FEF75AFB5A -:103A8000304603F0D7FE3146012014F017F8A87F26 -:103A900020F01000A877FFF726FF002800D0FFDFF6 -:103AA00006B05EE5207820F0F00020302070032082 -:103AB000207266806068007A607205F09AFDD8E72F -:103AC000C5882846FFF7BEFC00B9FFDF60680079B3 -:103AD000012800D0FFDF6068017A06B02846BDE803 -:103AE000F04707F0EBBDC6883046FFF7ABFC05009A -:103AF00000D1FFDF05F07DFD606831460089288137 -:103B000060684089688160688089A881012013F01D -:103B1000D5FF0020A875A87F00F003000228BFD1C0 -:103B2000FFF7E1FE0028BBD0FFDFB9E7007928B13D -:103B30000228B5D03C28B3D0FFDFB1E705F059FD2E -:103B40006668B6F806A0307A361D012806D0687E71 -:103B5000814605F0D4FA070003D101E0E878F7E7E1 -:103B6000FFDF00220221504610F097F9040000D137 -:103B7000FFDF22212046FFF7CDFA3079012800D05F -:103B80000220A17F804668F30101A177308B20815C -:103B9000708B6081B08BA08184F822908DF80880B2 -:103BA000B8680090F86801906A460321504610F00A -:103BB00074F900B9FFDFB888ADF81000B8788DF857 -:103BC000120004AA0521504610F067F900B9FFDF82 -:103BD000B888ADF80C00F8788DF80E0003AA04211F -:103BE000504610F05AF900B9FFDF062106F1120025 -:103BF0000DF00EF940B37079800700D5FFDF7179C1 -:103C0000E07D61F34700E075D6F80600A061708999 -:103C1000A083062106F10C000DF0FAF8F0B195F83A -:103C200025004108607861F3470006E041E039E093 -:103C300071E059E04EE02FE043E06070D5F82600D7 -:103C4000C4F80200688D12E0E07D20F0FE00801CC8 -:103C5000E075D6F81200A061F08AD9E7607820F00C -:103C6000FE00801C6070F068C4F80200308AE080BA -:103C7000B8F1010F04D0B8F1020F05D0FFDF0FE754 -:103C80000320FFF7D3F90BE7287E122800D0FFDFCF -:103C90001120FFF7E3F903E706B02046BDE8F0473F -:103CA00001F092BD05F0A5FC15F8300F40F00200C0 -:103CB00005E005F09EFC15F8300F40F00400287078 -:103CC000EEE6287E13280AD01528D8D15FF016001A -:103CD000FFF7C4F906B0BDE8F04705F08ABC142030 -:103CE000F6E70000F0010020A978052909D0042991 -:103CF000C5D105F07EFC022006B0BDE8F047FFF715 -:103D000095B900790028BAD0E87801F02DF905F0CE -:103D100070FC0320F0E7287E122802D1687E01F0B3 -:103D200023F91120D4E72DE9F05F054600784FF024 -:103D300000080009DFF8B8A891460C46464601285D -:103D40006ED002286DD007280BD00A286AD0FFDF7A -:103D5000A9F8006014B1A4F8008066800020BDE8D6 -:103D6000F09F6968012704F108000B784FF0020BFF -:103D70005B1F4FF6FF721B2B7ED2DFE803F0647DE2 -:103D80007D7D0E7D7D7D7D7D7D217D7D7D2BFDFC81 -:103D9000FBFA7D14D2F9E7F8F700C8884FF0120853 -:103DA000102621469AE14FF01C080A26BCB38888E9 -:103DB000A0806868807920726868C0796072C7E7FF -:103DC0004FF01B08142654B30320207268688088C3 -:103DD000A080BDE70A793C2ABAD00D1D4FF010082B -:103DE0002C26E4B16988A180298B6182298B2182EC -:103DF000698BA182A98BE1826B790246A91D1846C5 -:103E0000FFF7EFFA2979002001290CD084F80FB0D0 -:103E1000FF212176E06120626062A06298E70FE0F6 -:103E20003BE15EE199E1E77320760AF1040090E856 -:103E30000E00DAF81000C4E90930C4E9071287E778 -:103E4000A9F800608AE72C264FF01D08002CF7D057 -:103E5000A28005460F1D897B008861F30000288041 -:103E6000B97A490861F341002880B97A890861F379 -:103E700082002880B97A00E00CE1C90861F3C30030 -:103E80002880B97AAA1C0911491C61F3041000F0BA -:103E90007F0028807878B91CFFF7A3FA387D05F1F8 -:103EA000090207F11501FFF79CFA387B01F0A9F828 -:103EB0002874787B01F0A5F86874F87EA874787A85 -:103EC000E874387F2875B87B6875388AE882DAF834 -:103ED0001C10A961B97A504697F808A0C1F34111A6 -:103EE000012904D0008C504503D2824609E0FFDF4F -:103EF00010E0022903D0288820F0600009E0504536 -:103F000004D1288820F06000403002E0288840F08A -:103F100060002880A4F824A0524607F11D01A8697A -:103F20009BE011264FF02008002C89D0A280686801 -:103F300004F10A02007920726868007B6072696887 -:103F40008B1D48791946FFF74CFA01E70A264FF016 -:103F50002108002CE9D08888A080686880792072C8 -:103F60006868C07960729AF8301006E078E06BE01B -:103F700052E07FE019E003E03AE021F00401A6E01E -:103F80000B264FF02208002CCFD0C888A08068688C -:103F9000007920726868007A01F033F8607268680E -:103FA000407A01F02EF8A072D2E61C264FF02608C7 -:103FB000002CBAD0A2806868407960726868007A84 -:103FC000A0720AF1040090E80E00DAF81000C4E9CB -:103FD0000530C4E90312686800793C2803D04328FF -:103FE00003D0FFDFB4E62772B2E684F808B0AFE68C -:103FF00010264FF02408002C97D08888A08068688D -:10400000807920816868807A608168680089A081F1 -:1040100068688089E0819BE610264FF02308002C19 -:1040200098D08888A0806868C088208168680089E6 -:10403000608168684089A08168688089E0819AF819 -:10404000301021F0020142E030264FF02508002C0C -:104050009AD0A2806968282249680AF0EEF977E6CA -:104060002A264FF02F08002C8ED0A28069682222C9 -:10407000091DF2E714264FF01B08002C84D0A28003 -:10408000686800790128B0D02772DAE90710C4E91E -:1040900003105DE64A46214660E0287A012803D0F5 -:1040A000022817D0FFDF53E610264FF01F08002C20 -:1040B000A2D06888A080A8892081E8896081288AA8 -:1040C000A081688AE0819AF8301021F001018AF815 -:1040D00030103DE64FF012081026688800F07EFF91 -:1040E00036E6287AC8B3012838D0022836D003280B -:1040F00001D0FFDF2CE609264FF01108002C8FD0ED -:104100006F883846FFF79EF990F822A0A780687A5A -:104110002072042138460FF0DBFE052138460FF0EF -:10412000D7FE002138460FF0D3FE012138460FF0AC -:10413000CFFE032138460FF0CBFE022138460FF0A8 -:10414000C7FE062138460FF0C3FE072138460FF0A0 -:10415000BFFE504600F008FFFAE5FFE72846BDE83D -:10416000F05F01F0BBBC70B5012803D0052800D07A -:10417000FFDF70BD8DB22846FFF764F9040000D15F -:10418000FFDF20782128F4D005F030FA80B10178E3 -:1041900021F00F01891C21F0F00110310170022182 -:1041A000017245800020A075BDE8704005F021BA7D -:1041B00021462846BDE870401322FFF746B92DE995 -:1041C000F04116460C00804600D1FFDF307820F029 -:1041D0000F00801C20F0F000103030702078012893 -:1041E00004D0022818D0FFDFBDE8F0814046FFF779 -:1041F00029F9050000D1FFDF0320A87505F0F9F9C2 -:1042000094E80F00083686E80F00F94810F8301FD0 -:1042100041F001010170E7E74046FFF713F905009F -:1042200000D1FFDFA1884FF6FF700027814202D145 -:10423000E288824203D0814201D1E08840B105F09A -:10424000D8F994E80F00083686E80F00AF75CBE781 -:10425000A87D0128C8D178230022414613F084FBB1 -:104260000220A875C0E738B50C4624285CD008DCCD -:1042700020280FD0212825D022284BD0232806D152 -:104280004CE0252841D0262832D03F2851D00725A0 -:10429000284638BD0021052013F0E6FB08B11120A7 -:1042A00038BDA01C0EF0E1FA04F0BDFF0500EFD10F -:1042B000002208231146052013F056FB0528E7D0FD -:1042C000FFDFE5E76068FDF708F808B1102038BDAA -:1042D000618820886A460EF071FD04F0A4FF050095 -:1042E000D6D160680028D3D0BDF800100180CFE798 -:1042F000206820B1FCF7FAFF08B11025C8E7204676 -:104300000EF03BFE1DE00546C2E7A17820880EF0C6 -:1043100086FD16E0086801F08DFEF4E7087800F0ED -:1043200001000DF0B9FD0CE0618820880EF0C1FCA1 -:1043300007E0087800F001008DF8000068460EF0F4 -:10434000DFF804F070FFDEE770B505460C4608465E -:10435000FCF7A5FF08B1102070BD202D07D0212D3E -:104360000DD0222D0BD0252D09D0072070BD20881F -:10437000A11C0DF065FEBDE8704004F054BF06209E -:1043800070BD9B482530704708B5342200219848FD -:104390000AF07DF80120FEF749FE1120FEF75EFECF -:1043A00093496846263105F0B7F891489DF80020FA -:1043B00010F8251F62F3470121F00101017000216F -:1043C00041724FF46171A0F8071002218172FEF76B -:1043D0008FFE00B1FFDFFDF705F801F0BEF908BD63 -:1043E00010B50C464022002120460AF050F8A07F6C -:1043F00020F00300A077202020700020A07584F812 -:10440000230010BD70472DE9FC410746FCF721FF52 -:1044100010B11020BDE8FC81754E06F12501D6F8DB -:1044200025000090B6F82950ADF8045096F82B40BE -:104430008DF806403846FEF7BDFF0028EAD1FEF7AA -:1044400057FE0028E6D0009946F8251FB580B471C4 -:10445000E0E710B50446FCF722FF08B1102010BDBC -:1044600063486349224690F8250026314008FEF74C -:10447000B8FF002010BD3EB504460D460846FCF7C7 -:104480000EFF08B110203EBD14B143F204003EBD42 -:1044900057488078052803D0042801D008203EBD65 -:1044A000694602A80AF012FC2A4669469DF80800EF -:1044B000FEF797FF00203EBDFEB50D4604004FF00D -:1044C000000712D00822FEF79FFE002812D1002616 -:1044D00009E000BF54F826006946FEF720FF0028D7 -:1044E00008D1761CF6B2AE42F4D30DF01CFC10B12C -:1044F00043F20320FEBD3E4E86F824700CB3002725 -:104500001BE000BF54F8270002A9FEF708FF00B126 -:10451000FFDF9DF808008DF8000054F8270050F8E0 -:10452000011FCDF801108088ADF8050068460DF038 -:104530001FFC00B1FFDF7F1CFFB2AF42E2D386F861 -:1045400024500020FEBD2DE9F0418AB01546884672 -:1045500004001ED00F4608222946FEF755FE00280B -:1045600011D1002613E000BF54F826006946103030 -:1045700000F01FFD002806D13FB157F82600FCF7D8 -:1045800068FE10B110200AB02EE6761CF6B2AE42DC -:10459000EAD3681EC6B217E0701CC7B212E000BFB3 -:1045A00054F82600017C4A0854F827100B7CB2EB23 -:1045B000530F05D106221130113109F011FF50B10E -:1045C0007F1CFFB2AF42EBD3761EF6B2E4D2464672 -:1045D00024B1012003E043F20520D4E700200DF0D0 -:1045E000ECFB10B90DF0F5FB20B143F20420CAE753 -:1045F000F001002064B300270DF1170826E000BF8A -:1046000054F827006946103000F0D3FC00B1FFDFFA -:1046100054F82700102250F8111FCDF8011080889F -:10462000ADF8050054F827100DF1070009F005FF5B -:1046300096B156F827101022404609F0FEFE684653 -:104640000DF07BFB00B1FFDF7F1CFFB2AF42D7D381 -:10465000FEF713FF002096E7404601F0DFFCEEE78F -:1046600030B585B00446FDF7BDF830B906200FF02F -:10467000C5FB10B1062005B030BD2046FCF7E9FDB2 -:1046800018B96068FCF732FE08B11020F3E76088C3 -:104690004AF2B811884206D82078F94D28B101288D -:1046A00006D0022804D00720E5E7FEF721FD18E038 -:1046B0006078022804D0032802D043F20220DAE70F -:1046C00085F82F00C1B200200090ADF80400022947 -:1046D0002CD0032927D0FFDF68460DF009FC04F039 -:1046E000A2FD0028C7D1606801F08BFC207858B18A -:1046F00001208DF800000DF1010001F08FFC6846EB -:104700000EF0FDFB00B1FFDF207885F82E00FEF7EC -:10471000B4FE608860B1A88580B20DF046FB00B1A0 -:10472000FFDF0020A7E78DF80500D5E74020FAE776 -:104730004FF46170EFE710B50446FCF7B0FD20B907 -:10474000606838B1FCF7C9FD08B1102010BD606881 -:1047500001F064FCCA4830F82C1F6180C178617098 -:1047600080782070002010BD2DE9F843144689465A -:104770000646FCF794FDA0B94846FCF7B7FD80B9A2 -:104780002046FCF7B3FD60B9BD4DA878012800D1E3 -:104790003CB13178FF2906D049B143F20400BDE8AD -:1047A000F8831020FBE7012801D00420F7E7CCB301 -:1047B000052811D004280FD069462046FEF776FE62 -:1047C0000028ECD1217D49B1012909D0022909D065 -:1047D000032909D00720E2E70820E0E7024604E0C9 -:1047E000012202E0022200E003228046234617460F -:1047F00000200099FEF794FE0028D0D1A0892880DF -:10480000A07BE875BDF80000A882AF75BDF8001068 -:10481000090701D5A18931B1A1892980C00704D038 -:10482000032003E006E08021F7E70220FEF7FEFB0D -:1048300086F800804946BDE8F8430020FEF71EBF19 -:104840007CB58F4C05460E46A078022803D003287D -:1048500001D008207CBD15B143F204007CBD0720C7 -:104860000FF0D4FA10B9A078032806D0FEF70CFC9C -:1048700028B1A078032804D009E012207CBD1320C1 -:104880007CBD304600F053FB0028F9D1E670FEF7FE -:104890006FFD0AF058F901208DF800008DF8010035 -:1048A0008DF802502088ADF80400E07D8DF80600F8 -:1048B00068460EF0C1F904F0B6FC0028E0D1A078FB -:1048C000032805D05FF00400FEF7B0FB00207CBD9C -:1048D000E07800F03CFB0520F6E71CB510B143F290 -:1048E00004001CBD664CA078042803D0052801D024 -:1048F00008201CBD00208DF8000001218DF801105A -:104900008DF8020068460EF097F904F08CFC002840 -:10491000EFD1A078052805D05FF00200FEF786FBF6 -:1049200000201CBDE07800F01FFB0320F6E72DE916 -:10493000FC4180460E4603250846FCF7D7FC0028BC -:1049400066D14046FEF77EFD040004D02078222880 -:1049500004D208205EE543F202005BE5A07F00F090 -:1049600003073EB1012F0CD000203146FEF727FC93 -:104970000500EFD1012F06D0022F1AD0FFDF284605 -:1049800048E50120F1E7A07D3146022801D011B1B0 -:1049900007E011203EE56846FCF758FE0028D9D113 -:1049A0006946404606F04DFE0500E8D10120A0759D -:1049B000E5E7A07D032804D1314890F83000C00716 -:1049C00001D02EB30EE026B1A07F40071ED40021F7 -:1049D00000E00121404606F054FE0500CFD1A0754D -:1049E000002ECCD03146404600F0EDFA05461128A5 -:1049F000C5D1A07F4107C2D4316844F80E1F716849 -:104A0000616040F0040020740025B8E71125B6E786 -:104A10001020FFE470B50C460546FEF713FD0100BB -:104A200005D022462846BDE87040FEF70EBD43F291 -:104A3000020070BD10B5012807D1114B9B78012BE6 -:104A400000D011B143F2040010BD0DF0E0F9BDE853 -:104A5000104004F0E8BB012300F090BA00231A468E -:104A6000194600F08BBA70B506460C460846FCF7AE -:104A7000F0FB18B92068FCF712FC18B1102070BDCB -:104A8000F0010020F84D2A7E112A04D0132A00D309 -:104A90003EB10820F3E721463046FEF77DFE60B1C7 -:104AA000EDE70920132A0DD0142A0BD0A188FF2985 -:104AB000E5D31520FEF7D2FA0020D4E90012C5E9AB -:104AC0000712DCE7A1881F29D9D31320F2E71CB510 -:104AD000E548007E132801D208201CBD00208DF877 -:104AE000000068460DF02AFC04F09DFB0028F4D17C -:104AF0001120FEF7B3FA00201CBD2DE9F04FDFF8BE -:104B000068A3814691B09AF818009B4615460C465A -:104B1000132803D3FFF7DBFF00281FD12046FCF743 -:104B200098FBE8BB2846FCF794FBC8BB20784FF005 -:104B30000107C0074FF0000102D08DF83A7001E084 -:104B40008DF83A1020788846C0F3C1008DF8000037 -:104B500060788DF80910C10803D0072011B0BDE8B6 -:104B6000F08FB0B3C10701D08DF80970810705D56A -:104B70009DF8091041F002018DF80910400705D594 -:104B80009DF8090040F004008DF809009DF8090027 -:104B9000810703D540F001008DF80900002000E0F6 -:104BA00015E06E4606EB400162884A81401CA288EF -:104BB000C0B20A820328F5D32078C0F3C1000128CF -:104BC00025D0032823D04846FCF743FB28B110200A -:104BD000C4E7FFE78DF80970D8E799F800004008AE -:104BE00008D0012809D0022807D0032805D043F2B5 -:104BF0000220B3E78DF8028001E08DF8027048468C -:104C000050F8011FCDF803108088ADF80700FEF7BB -:104C1000AFFB8DF801000021424606EB41002B88D6 -:104C2000C3826B888383AB884384EB880385491CEC -:104C3000C285C9B282860329EFD3E088ADF83C0073 -:104C400068460DF053FC002887D19AF818005546A5 -:104C5000112801D0082081E706200FF0D7F838B1DD -:104C60002078C0F3C100012804D0032802D006E058 -:104C7000122073E795F8240000283FF46EAFFEF78A -:104C800003FA022801D2132068E7584600F04FF9D2 -:104C900000289DD185F819B068460DF06DFD04F02F -:104CA000C2FA040094D1687E00F051F91220FEF798 -:104CB000D5F9204652E770B56B4D287E122801D0F9 -:104CC0000820DCE60DF05BFD04F0ADFA040005D130 -:104CD000687E00F049F91120FEF7C0F92046CEE6C3 -:104CE00070B5064615460C460846FCF7D8FA18B9C2 -:104CF0002846FCF7D4FA08B11020C0E62A4621461F -:104D000030460EF03BF804F08EFA0028F5D12178F9 -:104D10007F29F2D10520B2E67CB505460C4608464F -:104D2000FCF797FA08B110207CBD2846FEF78AFBF5 -:104D300020B10078222804D208207CBD43F2020072 -:104D40007CBD494890F83000400701D511207CBD5A -:104D50002078C00802D16078C00801D007207CBD4F -:104D6000ADF8005020788DF8020060788DF80300CF -:104D70000220ADF8040068460CF03BFE04F053FA44 -:104D80007CBD70B586B014460D460646FEF75AFB4C -:104D900028B10078222805D2082006B06FE643F239 -:104DA0000200FAE72846FCF7A1FA20B944B12046F0 -:104DB000FCF793FA08B11020EFE700202060A080F4 -:104DC000294890F83000800701D51120E5E703A9B4 -:104DD00030460CF05EFE10B104F025FADDE7ADF8C8 -:104DE0000060BDF81400ADF80200BDF81600ADF883 -:104DF0000400BDF81000BDF81210ADF80600ADF8C3 -:104E000008107DB1298809B1ADF80610698809B18B -:104E1000ADF80210A98809B1ADF80810E98809B108 -:104E2000ADF80410DCB1BDF80610814201D9081AB2 -:104E30002080BDF80210BDF81400814201D9081A83 -:104E40006080BDF80800BDF80410BDF816200144CC -:104E5000BDF812001044814201D9081AA0806846AA -:104E60000CF0D5FEB8E70000F00100201CB56C493D -:104E70000968CDE9001068460DF03AFB04F0D3F95B -:104E80001CBD1CB500200090019068460DF030FB61 -:104E900004F0C9F91CBD70B505460C460846FCF780 -:104EA000FEF908B11020EAE5214628460DF012F976 -:104EB000BDE8704004F0B7B93EB505460C4608465B -:104EC000FCF7EDF908B110203EBD002000900190E4 -:104ED0000290ADF800502089ADF8080020788DF8D8 -:104EE0000200606801902089ADF808006089ADF883 -:104EF0000A0068460DF000F904F095F93EBD0EB5C4 -:104F0000ADF800000020019068460DF0F5F804F0BF -:104F10008AF90EBD10800888508048889080C88823 -:104F200010818888D080002050819081704710B512 -:104F3000044604F0E4F830B1407830B1204604F083 -:104F4000FCFB002010BD052010BD122010BD10B5C7 -:104F500004F0D5F8040000D1FFDF607800B9FFDF6E -:104F60006078401E607010BD10B504F0C8F80400F1 -:104F700000D1FFDF6078401C607010BD1CB5ADF83B -:104F800000008DF802308DF803108DF8042068467B -:104F90000DF0B7FE04F047F91CBD0CB521A2D2E913 -:104FA0000012CDE900120079694601EB501000783B -:104FB0000CBD0278520804D0012A02D043F202202C -:104FC0007047FEF7ACB91FB56A46FFF7A3FF684606 -:104FD0000DF008FC04F027F904B010BD70B50C000A -:104FE00006460DD0FEF72EFA050000D1FFDFA680A1 -:104FF00028892081288960816889A081A889E08129 -:105000003DE500B540B1012805D0022803D00328B2 -:1050100004D0FFDF002000BDFF2000BD042000BD44 -:1050200014610200070605040302010010B50446DE -:10503000FCF70FF908B1102010BD2078C0F3021062 -:10504000042807D86078072804D3A178102901D84C -:10505000814201D2072010BDE078410706D42179B2 -:105060004A0703D4000701D4080701D5062010BD64 -:10507000002010BD10B513785C08837F64F3C7135C -:10508000837713789C08C37F64F30003C377107899 -:10509000C309487863F34100487013781C090B7802 -:1050A00064F347130B701378DB0863F30000487058 -:1050B0005078487110BD10B5C4780B7864F30003C4 -:1050C0000B70C478640864F341030B70C478A408BF -:1050D00064F382030B70C478E40864F3C3030B70B9 -:1050E0000379117863F30001117003795B0863F3AE -:1050F0004101117003799B0863F3820111700079FB -:10510000C00860F3C301117010BD70B514460D46A0 -:10511000064604F06BFA80B10178182221F00F01E5 -:10512000891C21F0F001A03100F8081B214609F08C -:1051300084F9BDE8704004F05CBA29463046BDE809 -:1051400070401322FEF781B92DE9F047064608A802 -:10515000904690E8300489461F46142200212846D4 -:1051600009F095F90021CAF80010B8F1000F03D03A -:10517000B9F1000F03D114E03878C00711D02068CE -:10518000FCF78DF8C0BBB8F1000F07D120681230D2 -:1051900028602068143068602068A8602168CAF818 -:1051A00000103878800724D56068FCF796F818BBA3 -:1051B000B9F1000F21D0FFF7E4F80168C6F86811D3 -:1051C0008188A6F86C11807986F86E0101F013FDD4 -:1051D000F94FEF60626862B196F8680106F26911F2 -:1051E00040081032FEF7FDF810223946606809F0D9 -:1051F00024F90020BDE8F08706E0606820B1E8608F -:105200006068C6F86401F4E71020F3E730B505469E -:1052100008780C4620F00F00401C20F0F0011031FF -:1052200021700020607095F8230030B104280FD061 -:10523000052811D0062814D0FFDF20780121B1EB1A -:10524000101F04D295F8200000F01F00607030BDE0 -:1052500021F0F000203002E021F0F000303020702A -:10526000EBE721F0F0004030F9E7F0B591B002270C -:1052700015460C4606463A46ADF80870092103ABC0 -:1052800005F063F80490002810D004208DF8040085 -:105290008DF80170E034099605948DF818500AA92C -:1052A000684610F0FCFA00B1FFDF012011B0F0BD3C -:1052B00010B588B00C460A99ADF80000CBB118685B -:1052C000CDF80200D3F80400CDF80600ADF80A20AE -:1052D000102203A809F0B1F868460DF0F3FA03F0C4 -:1052E000A2FF002803D1A17F41F01001A17708B0EF -:1052F00010BD0020CDF80200E6E72DE9F84F064684 -:10530000808A0D4680B28246FEF79CF804463078CB -:10531000DFF8A48200274FF00209A8F120080F2827 -:1053200070D2DFE800F06FF23708387D8CC8F1F0FA -:10533000EFF35FF3F300A07F00F00300022809D031 -:105340005FF0000080F0010150460EF0AFFD050057 -:1053500003D101E00120F5E7FFDF98F85C10C907F1 -:1053600002D0D8F860000BE0032105F11D0012F017 -:10537000BEF8D5F81D009149B0FBF1F201FB120017 -:10538000C5F81D0070686867B068A8672078252890 -:1053900000D0FFDFCAE0A07F00F00300022809D0A0 -:1053A0005FF0000080F0010150460EF07FFD060026 -:1053B00003D101E00120F5E7FFDF3078810702D556 -:1053C0002178252904D040F001003070BDE8F88F25 -:1053D00085F80090307F287106F11D002D36C5E953 -:1053E0000206F3E7A07F00F00300022808D00020A7 -:1053F00080F0010150460EF059FD040004D102E096 -:105400000120F5E7A7E1FFDF2078C10604D50720DA -:1054100028703D346C60D9E740F008002070D5E773 -:10542000E07F000700D5FFDF307CB28800F0010389 -:1054300001B05046BDE8F04F092106F064B804B948 -:10544000FFDF716821B1102204F1240008F0F5FF9C -:1054500028212046FDF75EFEA07F00F00300022811 -:105460000ED104F12400002300901A462146504634 -:10547000FFF71EFF112807D029212046FDF74AFE1D -:10548000307A84F82000A1E7A07F000700D5FFDF75 -:1054900014F81E0F40F008002070E782A761E76152 -:1054A000C109607861F34100014660F382016170D7 -:1054B000307AE0708AE7A07F00F00300022809D06C -:1054C0005FF0000080F0010150460EF0EFFC040098 -:1054D00003D101E00120F5E7FFDF022104F185009F -:1054E00012F005F80420287004F5B4706860B4F870 -:1054F00085002882304810387C346C61C5E9028010 -:1055000064E703E024E15BE02DE015E0A07F00F01C -:105510000300022807D0002080F0010150460EF061 -:10552000C5FC18B901E00120F6E7FFDF324621464D -:105530005046BDE8F84FE8E504B9FFDF20782128A0 -:10554000A1D93079012803D1E07F40F00800E0774D -:10555000324621465046FFF7D8FD2046BDE8F84FB9 -:105560002321FDF7D7BD3279AA8005F1080309216F -:10557000504604F0EAFEE86010B10520287025E7E7 -:10558000A07F00F00300022808D0002080F0010175 -:1055900050460EF08BFC040003D101E00120F5E73A -:1055A000FFDF04F1620102231022081F0EF005FB49 -:1055B00007703179417009E75002002040420F0026 -:1055C000A07F00F00300022808D0002080F0010135 -:1055D00050460EF06BFC050003D101E00120F5E719 -:1055E000FFDF95F8840000F0030001287AD1A07F46 -:1055F00000F00307E07F10F0010602D0022F04D173 -:1056000033E095F8A000C0072BD0D5F8601121B386 -:1056100095F88320087C62F387000874A17FCA098B -:10562000D5F8601162F341000874D5F8601166F393 -:1056300000000874AEB1D5F86001102204F1240115 -:10564000883508F0FAFE287E40F001002876287898 -:1056500020F0010005F8880900E016B1022F04D0FF -:105660002DE095F88800C00727D0D5F85C1121B34C -:1056700095F88320087C62F387000874A17FCA092B -:10568000D5F85C1162F341000874D5F85C1166F33B -:10569000000008748EB1D5F85C01102204F12401D9 -:1056A000883508F0CAFE287840F0010005F8180B8C -:1056B000287820F0010005F8A009022F44D000202E -:1056C00000EB400005EBC00090F88800800709D58A -:1056D00095F87C00D5F86421400805F17D01103271 -:1056E000FDF77FFE8DF8009095F884006A4600F083 -:1056F00003008DF8010095F888108DF8021095F8D8 -:10570000A0008DF803002146504601F05DFA207894 -:10571000252805D0212807D0FFDF2078222803D9AB -:1057200022212046FDF7F6FCA07F00F003000228AE -:105730000CD0002080F0010150460EF0C9FB00287B -:105740003FF44FAEFFDF41E60120B9E70120F1E76A -:10575000706847703AE6FFDF38E670B5FE4C00250A -:1057600084F85C50256610F066F804F110012046BC -:1057700003F0F8FE84F8305070BD70B50D46FDF7AB -:1057800061FE040000D1FFDF4FF4B872002128460B -:1057900008F07DFE04F124002861A07F00F00300E2 -:1057A000022809D05FF0010105F1E00010F044F893 -:1057B000002800D0FFDF70BD0221F5E70A46014650 -:1057C00002F1E00010F059B870B50546406886B0A7 -:1057D00001780A2906D00D2933D00E292FD0FFDFFA -:1057E00006B070BD86883046FDF72CFE040000D15F -:1057F000FFDF20782128F3D028281BD168680221F8 -:105800000E3001F0D6F9A8B168680821801D01F0BA -:10581000D0F978B104F1240130460DF00FFA03F00D -:1058200002FD00B1FFDF06B02046BDE8704029212F -:10583000FDF770BC06B0BDE8704003F0DABE012190 -:1058400001726868C6883046FDF7FCFD040000D18F -:10585000FFDFA07F00F00301022902D120F0100039 -:10586000A077207821280AD06868017A09B10079E8 -:1058700080B1A07F00F00300022862D0FFDFA07F8C -:1058800000F003000228ABD1FEF72DF80028A7D0C6 -:10589000FFDFA5E703F0ADFEA17F08062BD5E07F73 -:1058A000C00705D094F8200000F01F00102820D079 -:1058B0005FF0050084F82300207829281DD02428D3 -:1058C000DDD13146042012F0F9F822212046FDF7FF -:1058D00021FCA07F00F00300022830D05FF0000020 -:1058E00080F0010130460EF0F3FA0028C7D0FFDF48 -:1058F000C5E70620DEE70420DCE701F0030002280C -:1059000008D0002080F0010130460EF0CFFA0500EB -:1059100003D101E00120F5E7FFDF25212046FDF757 -:10592000F9FB03208DF80000694605F1E0000FF057 -:105930009BFF0228A3D00028A1D0FFDF9FE7012012 -:10594000CEE703F056FE9AE72DE9F04387B099467B -:10595000164688460746FDF775FD04004BD02078B3 -:10596000222848D3232846D0E07F000743D4A07FD5 -:1059700000F00300022809D05FF0000080F0010170 -:1059800038460EF093FA050002D00CE00120F5E74E -:10599000A07F00F00300022805D001210022384634 -:1059A0000EF07BFA05466946284601F034F9009866 -:1059B00000B9FFDF45B10098E03505612078222865 -:1059C00006D0242804D007E000990020086103E0F5 -:1059D00025212046FDF79EFB00980121417047627A -:1059E000868001A9C0E902890FF059FF022802D080 -:1059F000002800D0FFDF07B0BDE8F08370B586B0A7 -:105A00000546FDF71FFD017822291ED9807F00F091 -:105A10000300022808D0002080F0010128460EF083 -:105A200045FA04002FD101E00120F5E7FFDF2AE06D -:105A3000B4F85E0004F1620630440178427829B17E -:105A400021462846FFF711FCB0B9C9E6ADF804209D -:105A50000921284602AB04F078FC03900028F4D01A -:105A600005208DF80000694604F1E0000FF0FCFE0F -:105A7000022801D000B1FFDF02231022314604F1D9 -:105A80005E000EF0D0F8B4F860000028D0D1A7E690 -:105A900010B586B00446FDF7D5FC017822291BD944 -:105AA000807F00F00300022808D0002080F0010170 -:105AB00020460EF0FBF9040003D101E00120F5E7D8 -:105AC000FFDF06208DF80000694604F1E0000FF0CA -:105AD000CBFE002800D0FFDF06B010BD2DE9F05F3F -:105AE00005460C4600270078904601093E4604F121 -:105AF000080BBA4602297DD0072902D00A2909D10C -:105B000046E0686801780A2905D00D2930D00E29B1 -:105B10002ED0FFDFBBE114271C26002C6BD0808821 -:105B2000A080FDF78FFC5FEA000900D1FFDF99F844 -:105B300017005A46400809F11801FDF752FC686841 -:105B4000C0892082696851F8060FC4F812004868BD -:105B5000C4F81600A07E01E03002002020F006000C -:105B600040F00100A07699F81E0040F020014DE0C1 -:105B70001A270A26002CD1D0C088A080FDF762FC2D -:105B8000050000D1FFDF59462846FFF73FFB7EE1C5 -:105B90000CB1A88BA080287A0B287DD006DC0128C8 -:105BA0007BD0022808D0032804D135E00D2875D019 -:105BB0000E2874D0FFDF6AE11E270926002CADD025 -:105BC000A088FDF73FFC5FEA000900D1FFDF287BDA -:105BD00000F003000128207A1BD020F00100207281 -:105BE000297B890861F341002072297BC90861F390 -:105BF000820001E041E1F2E02072297B090961F3B2 -:105C0000C300207299F81E0040F0400189F81E1070 -:105C10003DE140F00100E2E713270D26002CAAD059 -:105C2000A088FDF70FFC8146807F00F0030002286A -:105C300008D0002080F00101A0880EF037F905009F -:105C400003D101E00120F5E7FFDF99F81E0000F025 -:105C50000302022A50D0686F817801F00301012904 -:105C6000217A4BD021F00101217283789B0863F3E4 -:105C7000410121728378DB0863F38201217283780A -:105C80001B0963F3C3012172037863F306112172C8 -:105C9000437863F3C71103E061E0A9E090E0A1E07D -:105CA000217284F809A0C178A172022A29D0027950 -:105CB000E17A62F30001E1720279520862F3410174 -:105CC000E1720279920862F38201E1720279D208EC -:105CD00062F3C301E1724279217B62F30001217317 -:105CE0004279520862F3410121734279920862F3CA -:105CF00082012173407928E0A86FADE741F00101EE -:105D0000B2E74279E17A62F30001E1724279520826 -:105D100062F34101E1724279920862F38201E17219 -:105D20004279D20862F3C301E1720279217B62F306 -:105D3000000121730279520862F341012173027953 -:105D4000920862F3820121730079C00860F3C301F5 -:105D5000217399F80000232831D9262140E0182723 -:105D60001026E4B3A088FDF76DFB8346807F00F02A -:105D70000300022809D0002080F00101A0880EF065 -:105D800095F85FEA000903D101E00120F4E7FFDFA5 -:105D9000E868A06099F8000040F0040189F800105C -:105DA00099F80100800708D5012020739BF80000B6 -:105DB00023286CD92721584651E084F80CA066E0CE -:105DC00015270F265CB1A088FDF73CFB8146062213 -:105DD0005946E86808F0C7FB0120A073A0E041E045 -:105DE00048463CE016270926E4B3287B20724EE0A3 -:105DF000287B19270E26ACB3C4F808A0A4F80CA081 -:105E0000012807D0022805D0032805D0042803D094 -:105E1000FFDF0DE0207207E0697B042801F00F012D -:105E200041F0800121721ED0607A20F00300607280 -:105E3000A088FDF707FB05460078212827D02328F6 -:105E400000D0FFDFA87F00F00300022813D000205D -:105E500080F00101A0880EF03BF822212846FDF7D2 -:105E600059F914E004E0607A20F00300401CDEE7FA -:105E7000A8F8006010E00120EAE70CB16888A08073 -:105E8000287A68B301280AD002284FD0FFDFA8F88B -:105E900000600CB1278066800020BDE8F09F1527C8 -:105EA0000F26002CE4D0A088FDF7CCFA807F00F00C -:105EB0000300022808D0002080F00101A0880DF026 -:105EC000F5FF050003D101E00120F5E7FFDFD5F87C -:105ED0001D000622594608F046FB84F80EA0D6E7BE -:105EE00017270926002CC3D0A088FDF7ABFA8146FE -:105EF000807F00F00300022808D0002080F001011C -:105F0000A0880DF0D3FF050003D101E00120F5E7E3 -:105F1000FFDF6878800701D5022000E001202072B1 -:105F200099F800002328B2D9272159E719270E260E -:105F3000002C9DD0A088FDF785FA5FEA000900D10A -:105F4000FFDFC4F808A0A4F80CA084F808A0A07A89 -:105F500040F00300A07299F81E10C90961F3820095 -:105F6000A07299F81F2099F81E1012EAD11F05D0CF -:105F700099F8201001F01F0110292BD020F0080003 -:105F8000A07299F81F10607A61F3C3006072697A99 -:105F900001F003010129A2D140F00400607299F8D8 -:105FA0001E0000F003000228E87A16D0217B60F37F -:105FB00000012173AA7A607B62F300006073EA7AC1 -:105FC000520862F341012173A97A490861F3410043 -:105FD00060735CE740F00800D2E7617B60F300018A -:105FE0006173AA7A207B62F300002073EA7A520878 -:105FF00062F341016173A97A490861F3410020739A -:1060000045E710B5FE4C30B10146102204F12000E6 -:1060100008F013FA012084F8300010BD10B50446D2 -:1060200000F0E9FDF64920461022BDE8104020317D -:1060300008F003BA70B5F24D06004FF0000413D01B -:10604000FBF707F908B110240CE00621304608F0F0 -:1060500071FA411C05D028665FF0010085F85C00EC -:1060600000E00724204670BD0020F7E7007810F01C -:106070000F0204D0012A05D0022A0CD110E0000939 -:1060800009D10AE00009012807D0022805D0032819 -:1060900003D0042801D00720704708700020704703 -:1060A0000620704705282AD2DFE800F003070F1703 -:1060B0001F00087820F0FF001EE0087820F00F0095 -:1060C000401C20F0F000103016E0087820F00F009F -:1060D000401C20F0F00020300EE0087820F00F0087 -:1060E000401C20F0F000303006E0087820F00F006F -:1060F000401C20F0F000403008700020704707205E -:1061000070472DE9F041804688B00D4600270846CB -:10611000FBF7ECF8A8B94046FDF794F9040003D06A -:106120002078222815D104E043F2020008B0BDE82F -:10613000F08145B9A07F410603D500F00300022895 -:1061400001D01020F2E7A07FC10601D4010702D5DB -:106150000DB10820EAE7E17F090701D50D20E5E749 -:1061600000F0030002280DD165B12846FEF75EFF5E -:106170000700DBD1FBF736FB20B9E878800701D5B3 -:106180000620D3E7A07F00F00300022808D00020FB -:1061900080F0010140460DF089FE060002D00FE0BC -:1061A0000120F5E7A07F00F0030002280ED00020B8 -:1061B00080F00101002240460DF06FFE060007D07E -:1061C000A07F00F00300022804D009E00120EFE7DF -:1061D0000420ABE725B12A4631462046FEF74AFFA8 -:1061E0006946304600F017FD009800B9FFDF0099BE -:1061F000022006F1E0024870C1F824804A610022C2 -:106200000A81A27F02F00302022A1CD00120087139 -:10621000287800F00102087E62F3010008762A78EF -:10622000520862F3820008762A78920862F3C3006B -:1062300008762A78D20862F30410087624212046D2 -:10624000FCF768FF33E035B30871301D88613078A2 -:10625000400908777078C0F340004877287800F04C -:106260000102887F62F301008877A27FD20962F37E -:1062700082008877E27F62F3C3008877727862F3E6 -:1062800004108877A878C87701F1210228462031C8 -:10629000FEF711FF03E00320087105200876252191 -:1062A0002046FCF737FFA07F20F04000A07701A92F -:1062B00000980FF0F4FA022801D000B1FFDF384651 -:1062C00034E72DE9FF4F8DB09A4693460D460027DF -:1062D0000D98FDF7B7F8060006D03078262806D0CE -:1062E000082011B0BDE8F08F43F20200F9E7B07F5B -:1062F00000F00309B9F1020F11D04DB95846FEF76D -:1063000095FE0028EDD1B07F00F00300022806D0F2 -:10631000BBF1000F11D0FBF765FA20B10DE0BBF126 -:10632000000F50D109E006200DF068FD28B19BF860 -:106330000300800701D50620D3E7B07F00F00300FB -:10634000022809D05FF0000080F001010D980DF0E7 -:10635000ADFD040003D101E00120F5E7FFDF852D4D -:1063600027D007DCEDB1812D1DD0822D1DD0832DCE -:1063700008D11CE0862D1ED0882D1ED0892D1ED060 -:106380008A2D1ED00F2020710F281CD003F02EF96B -:10639000D8B101208DF81400201D06902079B0B1ED -:1063A00056E10020EFE70120EDE70220EBE70320B4 -:1063B000E9E70520E7E70620E5E70820E3E709200D -:1063C000E1E70A20DFE707208BE7112089E7B9F131 -:1063D000020F03D0A56F03D1A06F02E0656FFAE74B -:1063E000606F804631D04FF0010001904FF0020005 -:1063F00000905A4621463046FEF73CFE02E000007F -:10640000300200209BF8000000F00101A87861F341 -:106410000100A870B17FC90961F38200A870F17F03 -:1064200061F3C300A870617861F30410A87020784C -:10643000400928706078C0F3400068709BF8020043 -:10644000E87000206871287103E0022001900120AB -:106450000090A87898F80210C0F3C000C1F3C00102 -:10646000084003902CD05046FAF7F3FEC0BBDAF890 -:106470000C00FAF7EEFE98BBDAF81C00FAF7E9FE1A -:1064800070BBDAF80C00A060DAF81C00E0606078FD -:1064900098F8012042EA500161F34100607098F8D9 -:1064A0000210C0B200EA111161F300006070002018 -:1064B0002077009906F11700022907D0012106E094 -:1064C000607898F8012002EA5001E5E7002104EB2A -:1064D000810148610199701C022902D0012101E06B -:1064E00028E0002104EB81014861A87800F0030056 -:1064F000012857D198F8020000F00300012851D17B -:10650000B9F1020F04D02A1D691D5846FEF7D3FDCC -:10651000287998F8041008408DF82C00697998F8CB -:10652000052011408DF8301008433BD05046FAF753 -:1065300090FE08B11020D4E60AF110018B46B9F1A3 -:10654000020F17D00846002104F18C03CDE90003A7 -:1065500004F5AE7202920BAB2046039AFEF7F4FDEF -:106560000028E8D1B9F1020F08D0504608D14FF009 -:10657000010107E050464FF00101E5E75846F5E715 -:106580004FF0000104F1A403CDE9000304F5B0725B -:10659000029281F001010CAB2046039AFEF7D4FD74 -:1065A0000028C8D16078800733D4A87898F8021002 -:1065B000C0F38000C1F3800108432AD0297898F8FD -:1065C0000000F94AB9F1020F06D032F81120430059 -:1065D000DA4002F003070AE032F810204B00DA40FC -:1065E00012F0030705D0012F0AD0022F0AD0032F83 -:1065F00006D0039A6AB1012906D0042904D008E024 -:106600000227F6E70127F4E7012801D0042800D18A -:106610000427B07F40F08000B077F17F039860F3EB -:106620000001F1776078800705D50320A0710398F9 -:1066300070B9002029E00220022F18D0012F18D0B5 -:10664000042F2AD00020A071B07F20F08000B07706 -:1066500025213046FCF75EFD05A904F1E0000FF0AE -:1066600003F910B1022800D0FFDF002039E6A07145 -:10667000DFE7A0710D22002104F1200007F007FFE1 -:10668000207840F00200207001208DF8100004AA4C -:1066900031460D9800F098FADAE70120A071D7E7AB -:1066A0002DE9F04387B09046894604460025FCF763 -:1066B000C9FE060006D03078272806D0082007B08B -:1066C000BDE8F08343F20200F9E7B07F00F0030079 -:1066D000022809D05FF0000080F0010120460DF093 -:1066E000E5FB040003D101E00120F5E7FFDFA77916 -:1066F0005FEA090005D0012821D0B9F1020F26D1A7 -:1067000010E0B8F1000F22D1012F05D0022F05D0E3 -:10671000032F05D0FFDF2EE00C252CE001252AE019 -:10672000022528E04046FAF794FDB0B9032F0ED1B8 -:106730001022414604F11D0007F07FFE1BE0012FEF -:1067400002D0022F03D104E0B8F1000F13D00720CC -:10675000B5E74046FAF77DFD08B11020AFE71022FB -:10676000002104F11D0007F092FE0621404607F0CB -:10677000E1FEC4F81D002078252140F002002070C1 -:106780003046FCF7C7FC2078C10713D020F0010089 -:10679000207002208DF8000004F11D0002908DF899 -:1067A00004506946C3300FF05FF8022803D010B1DF -:1067B000FFDF00E02577002081E730B587B00D4688 -:1067C0000446FCF73FFE98B1807F00F003000228EA -:1067D00011D0002080F0010120460DF067FB04007D -:1067E0000ED02846FAF735FD38B1102007B030BD7D -:1067F00043F20200FAE70120ECE72078400701D4D9 -:106800000820F3E7294604F13D002022054607F061 -:1068100014FE207840F01000207001070FD520F002 -:106820000800207007208DF80000694604F1E000A0 -:1068300001950FF019F8022801D000B1FFDF002008 -:10684000D4E770B50D460646FCF7FCFD18B101789B -:10685000272921D102E043F2020070BD807F00F0C1 -:106860000300022808D0002080F0010130460DF01E -:106870001DFB040003D101E00120F5E7FFDFA07953 -:10688000022809D16078C00706D02A462146304642 -:10689000FEF7EBFC10B10FE0082070BDB4F860000B -:1068A0000E280BD204F1620102231022081F0DF002 -:1068B00084F9012101704570002070BD112070BD68 -:1068C00070B5064614460D460846FAF7C2FC18B9DC -:1068D0002046FAF7E4FC08B1102070BDA6F57F4011 -:1068E000FF380ED03046FCF7ADFD38B14178224676 -:1068F0004B08811C1846FCF774FD07E043F20200C8 -:1069000070BD2046FDF7A5FD0028F9D11021E01D3E -:1069100010F0EDFDE21D294604F1170000F08BF99F -:10692000002070BD2DE9F04104468AB01546884626 -:1069300000270846FAF7DAFC18B92846FAF7D6FC19 -:1069400018B110200AB0BDE8F0812046FCF77AFDAE -:10695000060003D0307827281BD102E043F2020062 -:10696000F0E7B07F00F00300022809D05FF00000DC -:1069700080F0010120460DF099FA040003D101E0F6 -:106980000120F5E7FFDF2078400702D56078800717 -:1069900001D40820D6E7B07F00F00300022805D01C -:1069A000A06F05D1A16F04E01C610200606FF8E7E1 -:1069B000616F407800B19DB1487810B1B8F1000F17 -:1069C0000ED0ADB1EA1D06A8E16800F034F910223E -:1069D00006A905F1170007F003FD18B1042707E029 -:1069E0000720AFE71022E91D04F12D0007F025FD77 -:1069F000B8F1000F06D0102208F1070104F11D00C4 -:106A000007F01BFD2078252140F002002070304661 -:106A1000FCF780FB2078C10715D020F00100207022 -:106A200002208DF8000004F11D0002901030039048 -:106A30008DF804706946B3300EF016FF022803D0BB -:106A400010B1FFDF00E0277700207BE7F8B515469F -:106A50000E460746FCF7F6FC040004D020782228F6 -:106A600004D00820F8BD43F20200F8BDA07F00F07A -:106A70000300022802D043F20500F8BD3046FAF7C1 -:106A8000E8FB18B92846FAF7E4FB08B11020F8BD76 -:106A900000953288B31C21463846FEF709FC1128C0 -:106AA00015D00028F3D1297C4A08A17F62F3C711D1 -:106AB000A177297CE27F61F30002E277297C8908D3 -:106AC00084F82010A17F21F04001A177F8BDA17FBB -:106AD0000907FBD4D6F80200C4F83600D6F8060041 -:106AE000C4F83A003088A0861022294604F1240018 -:106AF00007F0A3FC287C4108E07F61F34100E077C8 -:106B0000297C61F38200E077287C800884F82100EA -:106B1000A07F40F00800A0770020D3E770B50D46B5 -:106B200006460BB1072070BDFCF78CFC040007D0B3 -:106B30002078222802D3A07F800604D4082070BDCC -:106B400043F2020070BDADB1294630460CF076F834 -:106B500002F069FB297C4A08A17F62F3C711A17783 -:106B6000297CE27F61F30002E277297C890884F8BE -:106B7000201004E030460CF084F802F054FBA17FB2 -:106B800021F02001A17770BD70B50D46FCF75AFCCD -:106B9000040005D02846FAF782FB20B1102070BD12 -:106BA00043F2020070BD29462046FEF72FFB00206D -:106BB00070BD04E010F8012B0AB100207047491E97 -:106BC00089B2F7D20120704770B51546064602F02B -:106BD0000DFD040000D1FFDF207820F00F00801CA5 -:106BE00020F0F0002030207066802868A060BDE8AA -:106BF000704002F0FEBC10B5134C94F83000002831 -:106C000008D104F12001A1F110000EF06FFE012067 -:106C100084F8300010BD10B190F8B9202AB10A48AC -:106C200090F8350018B1002003E0B83001E00648C4 -:106C300034300860704708B50023009313460A46B5 -:106C40000DF031FB08BD00003002002018B1817842 -:106C5000012938D101E010207047018842F6011265 -:106C6000881A914231D018DC42F60102A1EB0200F1 -:106C700091422AD00CDC41B3B1F5C05F25D06FF44E -:106C8000C050081821D0A0F57060FF381BD11CE05F -:106C900001281AD002280AD117E0B0F5807F14D05D -:106CA00008DC012811D002280FD003280DD0FF28BE -:106CB00009D10AE0B0F5817F07D0A0F580700338D4 -:106CC00003D0012801D0002070470F2070470A2808 -:106CD0001FD008DC0A2818D2DFE800F0191B1F1F9C -:106CE000171F231D1F21102815D008DC0B2812D0D8 -:106CF0000C2810D00D2816D00F2806D10DE0112831 -:106D00000BD084280BD087280FD003207047002099 -:106D1000704705207047072070470F2070470420F8 -:106D20007047062070470C20704743F202007047FE -:106D300038B50C46050041D06946FFF797F90028A1 -:106D400019D19DF80010607861F302006070694607 -:106D5000681CFFF78BF900280DD19DF800106078B2 -:106D600061F3C5006070A978C1F34101012903D026 -:106D7000022905D0072038BD217821F0200102E04A -:106D8000217841F020012170410704D0A978C90879 -:106D900061F386106070607810F0380F07D0A97822 -:106DA000090961F3C710607010F0380F02D16078E4 -:106DB000400603D5207840F040002070002038BD08 -:106DC00070B504460020088015466068FFF7B0FFE4 -:106DD000002816D12089A189884211D8606880785E -:106DE000C0070AD0B1F5007F0AD840F20120B1FBFC -:106DF000F0F200FB1210288007E0B1F5FF7F01D907 -:106E00000C2070BD01F201212980002070BD10B559 -:106E10000478137864F3000313700478640864F34F -:106E2000410313700478A40864F382031370047898 -:106E3000E40864F3C30313700478240964F30413AF -:106E400013700478640964F34513137000788009A3 -:106E500060F38613137031B10878C10701D1800740 -:106E600001D5012000E0002060F3C713137010BDAE -:106E70004278530702D002F0070306E012F0380F01 -:106E800002D0C2F3C20300E001234A7863F3020296 -:106E90004A70407810F0380F02D0C0F3C20005E00D -:106EA000430702D000F0070000E0012060F3C502B4 -:106EB0004A7070472DE9F04F95B00D00824613D00F -:106EC00012220021284607F0E2FA4FF6FF7B05AABE -:106ED0000121584607F0A3F80024264637464FF410 -:106EE00020586FF4205972E0102015B0BDE8F08FE3 -:106EF0009DF81E0001280AD1BDF81C1041450BD099 -:106F000011EB09000AD001280CD002280CD0042C67 -:106F10000ED0052C0FD10DE0012400E00224BDF8B5 -:106F20001A6008E0032406E00424BDF81A7002E0A9 -:106F3000052400E00624BDF81A10514547D12C74F1 -:106F4000BEB34FF0000810AA4FF0070ACDE9028245 -:106F5000CDE900A80DF13C091023CDF81090424670 -:106F60003146584607F02BF908BBBDF83C002A46CD -:106F7000C0B210A90EF045FDC8B9AE81CFB1CDE9C0 -:106F800000A80DF1080C0AAE40468CE8410213231C -:106F900000223946584607F012F940B9BDF83C00C6 -:106FA000F11CC01EC0B22A1D0EF02BFD10B1032033 -:106FB0009BE70AE0BDF82900E881062C05D19DF881 -:106FC0001E00A872BDF81C00288100208DE705A8CE -:106FD00007F031F800288BD0FFF779FE85E72DE91F -:106FE000F0471C46DDE90978DDF8209015460E00D3 -:106FF000824600D1FFDF0CB1208818B1D5B1112035 -:10700000BDE8F087022D01D0012100E0002106F14A -:10701000140005F0CDFEA8F8000002463B462946C4 -:10702000504603F092F9C9F8000008B9A41C3C606E -:107030000020E5E71320E3E7F0B41446DDE904524D -:107040008DB1002314B1022C09D101E0012306E027 -:107050000D7CEE0703D025F0010501230D742146B8 -:10706000F0BC04F050BA1A80F0BC70472DE9FE4F16 -:1070700091461A881C468A468046FAB102AB4946B8 -:1070800003F063F9050019D04046A61C27880DF0CF -:1070900050F83246072629463B4600960CF05FFC26 -:1070A00020882346CDE900504A4651464046FFF726 -:1070B000C3FF002020800120BDE8FE8F0020FBE7F9 -:1070C0002DE9F04786B082460EA8904690E8B000C1 -:1070D000894604AA05A903A88DE807001E462A468A -:1070E00021465046FFF77BFF039901B1012139701A -:1070F000002818D1FA4904F1140204AB086003987F -:1071000005998DE8070042464946504606F003FAC5 -:10711000A8B1092811D2DFE800F005080510100A0F -:107120000C0C0E00002006B06AE71120FBE70720D8 -:10713000F9E70820F7E70D20F5E70320F3E7BDF8AE -:1071400010100398CDE9000133462A4621465046E7 -:10715000FFF772FFE6E72DE9F04389B01646DDE957 -:1071600010870D4681461C461422002103A807F013 -:107170008EF9012002218DF810108DF80C008DF889 -:107180001170ADF8146064B1A278D20709D08DF8FF -:107190001600E088ADF81A00A088ADF81800A068C5 -:1071A000079008A80095CDE90110424603A948467A -:1071B0006B68FFF785FF09B0BDE8F083F0B58BB0D1 -:1071C00000240646069407940727089405A8099406 -:1071D000019400970294CDE903400D461023224606 -:1071E000304606F0ECFF78B90AA806A9019400978A -:1071F0000294CDE90310BDF8143000222946304630 -:1072000006F07BFD002801D0FFF761FD0BB0F0BD5B -:1072100006F00CBC2DE9FC410C468046002602F02D -:10722000E5F9054620780D287ED2DFE800F0BC079E -:1072300013B325BD49496383AF959B00A8480068F7 -:1072400020B1417841F010014170ADE0404602F0BC -:10725000FDF9A9E0042140460CF028FE070000D10A -:10726000FFDF07F11401404605F037FDA5BB1321F0 -:107270004046FDF7CFFB97E0042140460CF016FE98 -:10728000070000D1FFDFE088ADF800000020B881E2 -:107290009DF80000010704D5C00602D5A088B8817A -:1072A00005E09DF8010040067ED5A088F88105B96B -:1072B000FFDF22462946404601F0ACFC022673E07F -:1072C000E188ADF800109DF8011009060FD50728D8 -:1072D00003D006280AD00AE024E0042140460CF03E -:1072E000E5FD060000D1FFDFA088F0810226CDB9C0 -:1072F000FFDF17E0042140460CF0D8FD070000D165 -:10730000FFDF07F1140006F0C8FB90F0010F02D177 -:10731000E079000648D5387C022640F00200387437 -:1073200005B9FFDF224600E03DE02946404601F076 -:1073300071FC39E0042140460CF0B8FD017C002DC1 -:1073400001F00206C1F340016171017C21F00201EC -:107350000174E7D1FFDFE5E702260121404602F094 -:10736000A7F921E0042140460CF0A0FD0546606825 -:1073700000902089ADF8040001226946404602F0E1 -:10738000B8F9287C20F0020028740DE0002DC9D146 -:10739000FFDFC7E7022600214046FBF799F8002DE2 -:1073A000C0D1FFDFBEE7FFDF3046BDE8FC813EB560 -:1073B0000C0009D001466B4601AA002006F084FFAC -:1073C00020B1FFF784FC3EBD10203EBD0020208090 -:1073D000A0709DF8050002A900F00700FEF762FE0C -:1073E00050B99DF8080020709DF8050002A9C0F36F -:1073F000C200FEF757FE08B103203EBD9DF808000D -:1074000060709DF80500C109A07861F30410A070B8 -:107410009DF80510890961F3C300A0709DF8041060 -:10742000890601D5022100E0012161F342009DF8A7 -:10743000001061F30000A07000203EBD70B514463E -:1074400006460D4651EA040005D075B10846F9F725 -:1074500044FF78B901E0072070BD2946304606F0A8 -:107460009AFF10B1BDE8704031E454B12046F9F7FD -:1074700034FF08B1102070BD21463046BDE8704091 -:1074800095E7002070BD2DE9FC5F0C46904605464F -:10749000002701780822007A3E46B2EB111F7DD109 -:1074A00004F10A0100910A31821E4FF0020A04F130 -:1074B000080B0191092A72D2DFE802F0EDE005F530 -:1074C00028287BAACE00688804210CF0EFFC060077 -:1074D00000D1FFDFB08928B152270726C3E00000A2 -:1074E0009402002051271026002C7DD06888A080AF -:1074F0000120A071A88900220099FFF79FFF0028B2 -:1075000073D1A8892081288AE081D1E0B5F8129052 -:10751000072824D1E87B000621D5512709F1140062 -:1075200086B2002CE1D0A88900220099FFF786FFDF -:1075300000285AD16888A08084F806A0A8892081F4 -:107540000120A073288A2082A4F81290A88A0090B3 -:1075500068884B46A969019A01F038FBA8E05027DA -:1075600009F1120086B2002C3ED0A88900225946AB -:10757000FFF764FF002838D16888A080A889E080E0 -:10758000287A072813D002202073288AE081E87B1C -:10759000C0096073A4F81090A88A01E085E082E039 -:1075A000009068884B4604F11202A969D4E70120D3 -:1075B000EAE7B5F81290512709F1140086B2002CC1 -:1075C00066D0688804210CF071FC83466888A0802E -:1075D000A88900220099FFF731FF00286ED184F8B6 -:1075E00006A0A889208101E052E067E00420A07392 -:1075F000288A2082A4F81290A88A009068884B46B6 -:10760000A969019A01F0E2FAA989ABF80E104FE0DE -:107610006888FBF717FF0746688804210CF046FCD2 -:10762000064607B9FFDF06B9FFDF687BC00702D057 -:107630005127142601E0502712264CB36888A080F9 -:10764000502F06D084F806A0287B594601F0CEFAC8 -:107650002EE0287BA11DF9E7FE49A88949898142CE -:1076600005D1542706269CB16888A08020E05327C6 -:107670000BE06888A080A889E08019E06888042170 -:107680000CF014FC00B9FFDF55270826002CF0D1C0 -:10769000A8F8006011E056270726002CF8D068886B -:1076A000A080002013E0FFDF02E0012808D0FFDF08 -:1076B000A8F800600CB1278066800020BDE8FC9F20 -:1076C00057270726002CE3D06888A080687AA0712D -:1076D000EEE7401D20F0030009B14143091D01EB15 -:1076E0004000704713B5DB4A00201071009848B184 -:1076F000002468460CF0F7F9002C02D1D64A009914 -:1077000011601CBD01240020F4E770B50D4614463D -:10771000064686B05C220021284606F0B8FE04B971 -:10772000FFDFA0786874A2782188284601F089FAE2 -:107730000020A881E881228805F11401304605F077 -:10774000B0FA6A460121304606F069FC1AE000BF33 -:107750009DF80300000715D5BDF806103046FFF769 -:107760002DFD9DF80300BDF8061040F010008DF8C7 -:107770000300BDF80300ADF81400FF233046059A5E -:1077800006F0D1FD684606F056FC0028E0D006B0B1 -:1077900070BD10B50C4601F1140005F0BAFA0146AF -:1077A000627C2046BDE8104001F080BA30B5044646 -:1077B000A84891B04FF6FF75C18905AA284606F082 -:1077C0002EFC30E09DF81E00A0422AD001282AD1CC -:1077D000BDF81C00B0F5205F03D042F601018842DD -:1077E00021D1002002AB0AAA0CA9019083E807006E -:1077F00007200090BDF81A1010230022284606F03A -:10780000DEFC38B9BDF828000BAAC0B20CA90EF0F6 -:10781000F8F810B1032011B030BD9DF82E00A04241 -:1078200001D10020F7E705A806F005FC0028C9D023 -:107830000520F0E770B5054604210CF037FB040085 -:1078400000D1FFDF04F114010C46284605F045FA8B -:1078500021462846BDE8704005F046BA70B58AB0AA -:107860000C460646FBF7EEFD050014D028782228CA -:1078700027D30CB1A08890B101208DF80C00032013 -:107880008DF8100000208DF8110054B1A088ADF8DB -:107890001800206807E043F202000AB070BD09201A -:1078A000FBE7ADF818000590042130460CF0FEFA15 -:1078B000040000D1FFDF04F1140005F040FA0007D6 -:1078C00001D40820E9E701F091FE60B108A8022187 -:1078D0000094CDE9011095F8232003A93046636890 -:1078E000FFF7EEFBD9E71120D7E72DE9F04FB2F80B -:1078F00002A0834689B0154689465046FBF7A2FD93 -:107900000746042150460CF0D1FA0026044605969D -:107910004FF002080696ADF81C6007B9FFDF04B906 -:10792000FFDF4146504603F055FF50B907AA06A9AC -:1079300005A88DE807004246214650466368FFF7D8 -:107940004EFB444807AB0660DDE9051204F1140064 -:10795000CDF80090CDE90320CDE9013197F823203F -:10796000594650466B6805F033FA06000AD0022EDD -:1079700004D0032E14D0042E00D0FFDF09B030460F -:10798000BDE8F08FBDF81C000028F7D00599CDE9BF -:1079900000104246214650466368FFF74DFBEDE775 -:1079A000687840F008006870E8E710B50C46FFF70B -:1079B000BFF900280BD1607800F00701012905D13B -:1079C00010F0380F02D02078810601D5072010BDB5 -:1079D00040F0C8002070002010BD2DE9F04F99B094 -:1079E00004464FF000081B48ADF81C80ADF820801D -:1079F000ADF82480A0F80880ADF81480ADF81880A8 -:107A0000ADF82880ADF82C80007916460D46474623 -:107A1000012808D0022806D0032804D0042802D068 -:107A2000082019B0ACE72046F9F713FCF0BB284654 -:107A3000F9F70FFCD0BB6068F9F758FCB0BB606881 -:107A400068B160892189884202D8B1F5007F05D9E3 -:107A50000C20E6E7940200201800002080460EAAC1 -:107A600006A92846FFF7ACF90028DAD168688078C3 -:107A7000C0F34100022808D19DF8190010F0380F1A -:107A800003D02869F9F729FC80B905A92069FFF717 -:107A90004FF90028C5D1206950B1607880079DF862 -:107AA000150000F0380002D5F0B301E011E0D8BBBA -:107AB0009DF8140080060ED59DF8150010F0380FC3 -:107AC00003D06068F9F709FC18B96068F9F70EFC93 -:107AD00008B11020A5E70BA906A8FFF7C9F99DF882 -:107AE0002D000BA920F00700401C8DF82D006069C7 -:107AF000FFF75BFF002894D10AA9A069FFF718F9E6 -:107B000000288ED19DF8280080062BD4A06940B1B2 -:107B10009DF8290000F00701012923D110F0380F4A -:107B200020D0E06828B100E01CE00078D0B11C282B -:107B300018D20FAA611C2046FFF769F901213846C7 -:107B400061F30F2082468DF85210B94642F60300C9 -:107B50000F46ADF850000DF13F0218A928680DF04E -:107B600052FF08B107205CE79DF8600015A9CDF829 -:107B70000090C01CCDE9019100F0FF0B00230BF237 -:107B80000122514614A806F075F9E8BBBDF854006F -:107B90000C90FB482A8929690092CDE901106B8974 -:107BA000BDF838202868069906F064F9010077D1FD -:107BB00020784FF0020AC10601D4800616D58DF850 -:107BC000527042F60210ADF85000CDF80C9008A9A2 -:107BD00003AACDF800A0CDE90121002340F2032241 -:107BE00014A80B9906F046F9010059D1E4484D4616 -:107BF00008380089ADF83D000FA8CDE90290CDF816 -:107C00000490CDF8109000E00CE04FF007095B46BF -:107C10000022CDF80090BDF854104FF6FF7006F02A -:107C20006CF810B1FFF753F8FBE69DF83C00000636 -:107C300024D52946012060F30F218DF852704FF4AE -:107C400024500395ADF8500062789DF80C00002395 -:107C500062F300008DF80C006278CDF800A05208A5 -:107C600062F341008DF80C0003AACDE9012540F232 -:107C7000032214A806F0FEF8010011D1606880B359 -:107C80002069A0B905A906A8FFF7F2F86078800777 -:107C900007D49DF8150020F038008DF8150006E097 -:107CA00077E09DF8140040F040008DF814008DF846 -:107CB000527042F60110ADF85000208940F20121C7 -:107CC000B0FBF1F201FB1202606809ABCDF8008055 -:107CD000CDE90103002314A8059906F0CBF80100B3 -:107CE00057D12078C00728D00395A06950B90AA9B8 -:107CF00006A8FFF7BDF89DF8290020F00700401CFA -:107D00008DF829009DF8280007A940F040008DF863 -:107D100028008DF8527042F60310ADF8500003AA07 -:107D2000CDF800A0CDE90121002340F2032214A8E0 -:107D30000A9906F09FF801002BD1E06868B3294644 -:107D4000012060F30F218DF8527042F60410ADF857 -:107D50005000E068002302788DF8582040788DF8B4 -:107D60005900E06816AA4088ADF85A00E06800792A -:107D70008DF85C00E068C088ADF85D00CDF800903B -:107D8000CDE901254FF4027214A806F073F8010042 -:107D900003D00C9800F0B6FF43E679480321083879 -:107DA000017156B100893080BDF824007080BDF8A3 -:107DB0002000B080BDF81C00F080002031E670B5D6 -:107DC00001258AB016460B46012802D0022816D19A -:107DD00004E08DF80E504FF4205003E08DF80E5063 -:107DE00042F60100ADF80C005BB10024601C60F3AA -:107DF0000F2404AA08A918460DF005FE18B10720A3 -:107E00004BE5102049E504A99DF820205C48CDE908 -:107E10000021801E02900023214603A802F20122C5 -:107E200006F028F810B1FEF752FF36E5544808383E -:107E30000EB1C1883180057100202EE5F0B593B0F8 -:107E4000044601268DF83E6041F601000F46ADF86C -:107E50003C0011AA0FA93046FFF7B1FF002837D127 -:107E60002000474C4FF00005A4F1080432D01C223A -:107E7000002102A806F00BFB9DF808008DF83E607B -:107E800040F020008DF8080042F60520ADF83C00D7 -:107E900004200797ADF82C00ADF8300039480A905F -:107EA0000EA80D900E950FA80990ADF82E506A46B9 -:107EB00009A902A8FFF791FD002809D1BDF800002B -:107EC0006081BDF80400A081401CE0812571002084 -:107ED00013B0F0BD6581A581BDF84400F4E72DE93C -:107EE000F74F2749A0B00024083917940A79A14612 -:107EF000012A04D0022A02D0082023B040E5CA8813 -:107F0000824201D00620F8E721988A46824201D1B8 -:107F10000720F2E70120214660F30F21ADF8480069 -:107F20004FF6FF788DF86E000691ADF84A8042F664 -:107F3000020B8DF872401CA9ADF86CB0ADF8704022 -:107F40001391ADF8508012A806F0A4F800252E4633 -:107F50002F460DAB072212A9404606F09EF898B1B5 -:107F60000A2861D1B5B3AEB3ADF86450ADF8666020 -:107F70009DF85E008DF8144019AC012868D06FE0C0 -:107F80009C020020266102009DF83A001FB30128E0 -:107F900059D1BDF8381059451FD118A809A9019425 -:107FA0000294CDE9031007200090BDF8361010238D -:107FB0000022404606F003F9B0BBBDF8600004287B -:107FC00001D006284AD1BDF82410219881423AD127 -:107FD0000F2092E73AE0012835D1BDF83800B0F51E -:107FE000205F03D042F6010188422CD1BAF8060086 -:107FF000BDF83610884201D1012700E0002705B105 -:108000009EB1219881421ED118A809AA0194029418 -:10801000CDE90320072000900D46102300224046A2 -:1080200006F0CDF800B902E02DE04E460BE0BDF8B9 -:108030006000022801D0102810D1C0B217AA09A9E7 -:108040000DF0DFFC50B9BDF8369082E7052054E70B -:1080500005A917A8221D0DF0D6FC08B103204CE796 -:108060009DF814000023001DC2B28DF81420229840 -:108070000092CDE901401BA8069905F0FBFE10B95E -:1080800002228AF80420FEF722FE36E710B50B46DE -:10809000401E88B084B205AA00211846FEF7B7FE3C -:1080A00000200DF1080C06AA05A901908CE8070034 -:1080B000072000900123002221464FF6FF7005F0B3 -:1080C0001CFE0446BDF81800012800D0FFDF204642 -:1080D000FEF7FDFD08B010BDF0B5FF4F044687B0B8 -:1080E00038790E46032804D0042802D0082007B0AF -:1080F000F0BD04AA03A92046FEF762FE0500F6D1F2 -:1081000060688078C0F3410002280AD19DF80D0014 -:1081100010F0380F05D02069F9F7DFF808B110200A -:10812000E5E7208905AA21698DE807006389BDF884 -:1081300010202068039905F09DFE10B1FEF7C7FDE1 -:10814000D5E716B1BDF814003080042038712846F8 -:10815000CDE7F8B50C0006460BD001464FF6FF758B -:1081600000236A46284606F0AFF820B1FEF7AFFDBF -:10817000F8BD1020F8BD69462046FEF7D9FD00285D -:10818000F8D1A078314600F001032846009A06F0A5 -:10819000CAF8EBE730B587B0144600220DF1080CA1 -:1081A00005AD01928CE82C00072200920A46014698 -:1081B00023884FF6FF7005F0A0FDBDF81410218054 -:1081C000FEF785FD07B030BD70B50D4604210BF0FC -:1081D0006DFE040000D1FFDF294604F11400BDE864 -:1081E000704004F0A5BD70B50D4604210BF05EFE95 -:1081F000040000D1FFDF294604F11400BDE87040FF -:1082000004F0B9BD70B50D4604210BF04FFE04001B -:1082100000D1FFDF294604F11400BDE8704004F0EE -:10822000D1BD70B5054604210BF040FE040000D11D -:10823000FFDF214628462368BDE870400122FEF793 -:1082400015BF70B5064604210BF030FE040000D1C6 -:10825000FFDF04F1140004F05CFD401D20F0030575 -:1082600011E0011D00880022431821463046FEF728 -:10827000FDFE00280BD0607CABB2684382B2A068E0 -:10828000011D0BF0D0FCA06841880029E9D170BD28 -:1082900070B5054604210BF009FE040000D1FFDF94 -:1082A000214628466368BDE870400222FEF7DEBE24 -:1082B00070B50E46054601F099F9040000D1FFDFC4 -:1082C0000120207266726580207820F00F00001D6A -:1082D00020F0F00040302070BDE8704001F089B916 -:1082E00010B50446012900D0FFDF2046BDE810404C -:1082F0000121FAF7EDB82DE9F04F97B04FF0000AE1 -:108300000C008346ADF814A0D04619D0E06830B117 -:10831000A068A8B10188ADF81410A0F800A05846D4 -:10832000FBF790F8070043F2020961D03878222861 -:108330005CD3042158460BF0B9FD050005D103E0DC -:10834000102017B0BDE8F08FFFDF05F1140004F036 -:10835000E0FC401D20F00306A078012803D002288D -:1083600001D00720EDE7218807AA584605F057FEFF -:1083700030BB07A805F05FFE10BB07A805F05BFE49 -:1083800048B99DF82600012805D1BDF82400A0F5C4 -:108390002451023902D04FF45050D2E7E068B0B116 -:1083A000CDE902A00720009005AACDF804A0049210 -:1083B000A2882188BDF81430584605F09EFC10B103 -:1083C000FEF785FCBDE7A168BDF8140008809DF8A4 -:1083D0001F00C00602D543F20140B2E70B9838B146 -:1083E000A1780078012905D080071AD40820A8E7D1 -:1083F0004846A6E7C007F9D002208DF83C00A868DF -:108400004FF00009A0B1697C4288714391420FD9B5 -:108410008AB2B3B2011D0BF0BCFB8046A0F800A0ED -:1084200006E003208DF83C00D5F800804FF00109EC -:108430009DF8200010F0380F00D1FFDF9DF82000DC -:108440002649C0F3C200084497F8231010F8010C25 -:10845000884201D90F2074E72088ADF8400014A9A4 -:108460000095CDE90191434607220FA95846FEF732 -:1084700027FE002891D19DF8500050B9A07801281E -:1084800007D1687CB3B2704382B2A868011D0BF0BB -:1084900094FB002055E770B5064615460C46084685 -:1084A000FEF7D4FB002805D12A4621463046BDE818 -:1084B000704084E470BD12E570B51E4614460D0090 -:1084C0000ED06CB1616859B160B10349C98881426D -:1084D00008D0072070BD000094020020296102002E -:1084E0001020F7E72068FEF7B1FB0028F2D13246F2 -:1084F00021462846BDE87040FFF76FBA70B51546B3 -:108500000C0006D038B1FE490989814203D007200A -:10851000E0E71020DEE72068FEF798FB0028D9D1BD -:1085200029462046BDE87040D6E570B5064686B0BF -:108530000D4614461046F8F7B2FED0BB6068F8F757 -:10854000D5FEB0BBA6F57F40FF3803D03046FAF722 -:1085500079FF80B128466946FEF7ACFC00280CD1B3 -:108560009DF810100F2008293DD2DFE801F0080621 -:108570000606060A0A0843F2020006B0AAE703202C -:10858000FBE79DF80210012908D1BDF80010B1F5F4 -:10859000C05FF2D06FF4C052D142EED09DF8061009 -:1085A00001290DD1BDF80410A1F52851062907D2E3 -:1085B00000E029E0DFE801F0030304030303DCE744 -:1085C0009DF80A1001290FD1BDF80810B1F5245FFC -:1085D000D3D0A1F60211B1F50051CED00129CCD0F3 -:1085E000022901D1C9E7FFDF606878B9002305AA35 -:1085F0002946304605F068FE10B1FEF768FBBCE77F -:108600009DF81400800601D41020B6E76188224648 -:1086100028466368FFF7BEFDAFE72DE9F0438146CA -:1086200087B0884614461046F8F739FE18B1102076 -:1086300007B0BDE8F083002306AA4146484605F08E -:1086400043FE10B1FEF743FBF2E79DF81800C006A9 -:1086500002D543F20140EBE70025072705A8019565 -:1086600000970295CDE9035062884FF6FF734146AB -:10867000484605F0A4FD060013D16068F8F70FFE28 -:1086800060B960680195CDE9025000970495238890 -:1086900062884146484605F092FD0646BDF8140042 -:1086A00020803046CEE739B1954B0A889B899A42A3 -:1086B00002D843F2030070471DE610B586B0904C17 -:1086C0000423ADF81430638943B1A4898C4201D2EC -:1086D000914205D943F2030006B010BD0620FBE726 -:1086E000ADF81010002100910191ADF80030022189 -:1086F0008DF8021005A9029104A90391ADF812208A -:108700006946FFF7F8FDE7E72DE9FC4781460D468E -:108710000846F8F79EFD88BB4846FAF793FE5FEAE5 -:1087200000080AD098F80000222829D304214846DE -:108730000BF0BCFB070005D103E043F20200BDE8EB -:10874000FC87FFDF07F1140004F0F9FA06462878E9 -:10875000012803D0022804D00720F0E7B0070FD586 -:1087600002E016F01C0F0BD0A8792C1DC00709D011 -:10877000E08838B1A068F8F76CFD18B11020DEE78A -:108780000820DCE721882A780720B1F5847F35D0DE -:108790001EDC40F20315A1F20313A94226D00EDC21 -:1087A000B1F5807FCBD003DCF9B1012926D1C6E732 -:1087B000A1F58073013BC2D0012B1FD113E0012B27 -:1087C000BDD0022B1AD0032BB9D0042B16D112E046 -:1087D000A1F20912082A11D2DFE802F00B040410FA -:1087E00010101004ABE7022AA9D007E0012AA6D096 -:1087F00004E0320700E0F206002AA0DACDB200F071 -:10880000F5FE50B198F82300CDE90005FA8923461A -:1088100039464846FEF79FFC91E711208FE72DE986 -:10882000F04F8BB01F4615460C4683460026FAF7DC -:1088300009FE28B10078222805D208200BB081E576 -:1088400043F20200FAE7B80801D00720F6E7032F49 -:1088500000D100274FF6FF79CCB1022D71D320460D -:10886000F8F744FD30B904EB0508A8F10100F8F76A -:108870003DFD08B11020E1E7AD1E38F8028CAAB228 -:108880002146484605F081FE40455AD1ADB21C490B -:10889000B80702D58889401C00E001201FFA80F843 -:1088A000F80701D08F8900E04F4605AA4146584697 -:1088B00005F0B5FB4FF0070A4FF00009FCB1204668 -:1088C00008E0408810283CD8361D304486B2AE42BD -:1088D00037D2A01902884245F3D352E09DF8170021 -:1088E00002074ED57CB304EB0608361DB8F80230FB -:1088F000B6B2102B25D89A19AA4222D802E040E03D -:1089000094020020B8F8002091421AD1C0061BD56D -:10891000CDE900A90DF1080C0AAAA11948468CE876 -:108920000700B8F800100022584605F0E6F910B12B -:10893000FEF7CDF982E7B8F80200BDF828108842AA -:1089400002D00B207AE704E0B8F80200304486B287 -:1089500006E0C00604D55846FEF730FC00288AD150 -:108960009DF81700BDF81A1020F010008DF81700C0 -:10897000BDF81700ADF80000FF235846009A05F037 -:10898000D2FC05A805F057FB18B9BDF81A10B9427A -:10899000A4D9042158460BF089FA040000D1FFDF66 -:1089A000A2895AB1CDE900A94D4600232146584677 -:1089B000FEF7D1FB0028BDD1A5813FE700203DE7B0 -:1089C0002DE9FF4F8BB01E4617000D464FF00004F7 -:1089D00012D0B00802D007200FB0B3E4032E00D1AC -:1089E00000265DB10846F8F778FC28B93888691E7A -:1089F0000844F8F772FC08B11020EDE7C74AB00749 -:108A000001D5D18900E00121F0074FF6FF7802D0AF -:108A1000D089401E00E0404686B206AA0B9805F0B9 -:108A2000FEFA4FF000094FF0070B0DF1140A38E081 -:108A30009DF81B00000734D5CDF80490CDF800B0A8 -:108A4000CDF80890CDE9039A434600220B9805F033 -:108A5000B6FB60BB05B3BDF814103A882144281951 -:108A6000091D8A4230D3BDF81E2020F8022BBDF824 -:108A7000142020F8022BCDE900B9CDE90290CDF801 -:108A800010A0BDF81E10BDF8143000220B9805F0A0 -:108A900096FB08B103209FE7BDF814002044001D99 -:108AA00084B206A805F0C7FA20B10A2806D0FEF75E -:108AB0000EF991E7BDF81E10B142B9D934B17DB1BC -:108AC0003888A11C884203D20C2085E7052083E763 -:108AD00022462946404605F058FD014628190180E6 -:108AE000A41C3C80002077E710B50446F8F7D7FBBC -:108AF00008B1102010BD8948C0892080002010BD19 -:108B0000F0B58BB00D4606461422002103A805F0EF -:108B1000BEFC01208DF80C008DF8100000208DF8AF -:108B20001100ADF814503046FAF78CFC48B10078CB -:108B3000222812D3042130460BF0B8F9040005D1E5 -:108B400003E043F202000BB0F0BDFFDF04F11400BC -:108B5000074604F0F4F8800601D40820F3E7207CEF -:108B6000022140F00100207409A80094CDE9011011 -:108B7000072203A930466368FEF7A2FA20B1217CE0 -:108B800021F001012174DEE729463046F9F791FC16 -:108B900008A9384604F0C2F800B1FFDFBDF8204054 -:108BA000172C01D2172000E02046A84201D92C46FC -:108BB00002E0172C00D2172421463046FFF713FBA2 -:108BC00021463046F9F799F90020BCE7F8B51C4674 -:108BD00015460E46069F0BF09AFA2346FF1DBCB2BF -:108BE00031462A4600940AF086FEF8BD70B50C4660 -:108BF00005460E220021204605F049FC0020208079 -:108C00002DB1012D01D0FFDF64E4062000E0052036 -:108C1000A0715FE410B548800878134620F00F007B -:108C2000001D20F0F00080300C4608701422194618 -:108C300004F1080005F001FC00F0DBFC374804609B -:108C400010BD2DE9F047DFF8D890491D064621F008 -:108C5000030117460C46D9F800000AF062FF050030 -:108C600000D1FFDF4FF000083560A5F800802146F5 -:108C7000D9F800000AF055FF050000D1FFDF75604C -:108C8000A5F800807FB104FB07F1091D0BD0D9F8CE -:108C900000000AF046FF040000D1FFDFB460C4F812 -:108CA0000080BDE8F087C6F80880FAE72DE9F041BA -:108CB0001746491D21F00302194D06460168144666 -:108CC00028680AF059FF2246716828680AF054FFA4 -:108CD0003FB104FB07F2121D03D0B16828680AF007 -:108CE0004BFF04200BF08AF8044604200BF08EF8AA -:108CF000201A012804D12868BDE8F0410AF006BF17 -:108D0000BDE8F08110B50C4605F058F900B1FFDF61 -:108D10002046BDE81040FDF7DABF000094020020B5 -:108D20001800002038B50C468288817B19B1418932 -:108D3000914200D90A462280C188121D90B26A462B -:108D40000AF0B2F8BDF80000032800D30320C1B236 -:108D5000208801F020F838BD38B50C468288817B28 -:108D600019B10189914200D90A462280C188121D99 -:108D700090B26A460AF098F8BDF80000022800D3C5 -:108D80000220C1B2208801F006F8401CC0B238BDF4 -:108D90002DE9FF5F82468B46F74814460BF103022C -:108DA000D0E90110CDE9021022F0030201A84FF42E -:108DB000907101920AF097FEF04E002C02D1F0491A -:108DC000019A8A60019901440191B57F05F101057D -:108DD00004D1E8B20CF098FD00B1FFDF019800EB80 -:108DE0000510C01C20F0030101915CB9707AB27AC1 -:108DF0001044C2B200200870B08C80B204F03DFF75 -:108E000000B1FFDF0198716A08440190214601A872 -:108E100000F084FF80460198C01C20F00300019000 -:108E2000B37AF27A717A04B100200AF052FF019904 -:108E300008440190214601A800F0B8FFCF48002760 -:108E40003D4690F801900CE0284600F04AFF0646A7 -:108E500081788088F9F7E8F871786D1C00FB01775C -:108E6000EDB24D45F0D10198C01C20F003000190F7 -:108E700004B100203946F9F7E2F8019900270844C7 -:108E80000190BE483D4690F801900CE0284600F065 -:108E900028FF0646C1788088FEF71BFC71786D1CA0 -:108EA00000FB0177EDB24D45F0D10198C01C20F0D8 -:108EB0000300019004B100203946FEF713FC01992C -:108EC0004FF0000908440190AC484D4647780EE049 -:108ED000284600F006FF0646807B30B106F1080008 -:108EE00002F09CF9727800FB02996D1CEDB2BD4254 -:108EF000EED10198C01C20F00300019004B10020C5 -:108F00009F494A78494602F08DF901990844019039 -:108F1000214601A800F0B8FE0198C01D20F007000E -:108F20000190DAF80010814204D3A0EB0B01B1F5F7 -:108F3000803F04DB4FF00408CAF8000004E0CAF8E0 -:108F40000000B8F1000F03D0404604B0BDE8F09F28 -:108F500084BB8C490020019A0EF044FEFBF714FA02 -:108F6000864C207F0090607F012825D0002328B305 -:108F70000022824800211030F8F73AFA00B1FFDFF2 -:108F80007E49E07F2031FEF759FF00B1FFDF7B48CB -:108F90004FF4F6720021443005F079FA7748042145 -:108FA000443080F8E91180F8EA11062180F8EB11CD -:108FB000032101710020C8E70123D8E702AAD8E7FE -:108FC00070B56E4C06464434207804EB4015E078CA -:108FD000083598B9A01990F8E80100280FD0A078BA -:108FE0000F2800D3FFDF20220021284605F04FFA8A -:108FF000687866F3020068700120E070284670BD52 -:109000002DE9F04105460C460027007805219046E1 -:109010003E46B1EB101F00D0FFDF287A50B1012887 -:109020000ED0FFDFA8F800600CB12780668000201A -:10903000BDE8F0810127092674B16888A08008E0A6 -:109040000227142644B16888A0802869E060A88AB5 -:109050002082287B2072E5E7A8F80060E7E730B5BA -:10906000464C012000212070617020726072032242 -:10907000A272E07261772177217321740521218327 -:109080001F216183607440A161610A21A177E077AB -:1090900039483B4DB0F801102184C07884F8220093 -:1090A0004FF4B06060626868C11C21F00301814226 -:1090B00000D0FFDF6868606030BD30B5304C1568A7 -:1090C000636810339D4202D20420136030BD2B4BE5 -:1090D0005D785A6802EB0512107051700320D08041 -:1090E000172090800120D0709070002090735878E5 -:1090F000401C5870606810306060002030BD70B552 -:1091000006461E480024457807E0204600F0E9FDA9 -:109110000178B14204D0641CE4B2AC42F5D1002025 -:1091200070BDF7B5074608780C4610B3FFF7E7FFA8 -:109130000546A7F12006202F06D0052E19D2DFE81C -:1091400006F00F383815270000F0D6FD0DB169780C -:1091500000E00021401AA17880B20844FF2808D816 -:10916000A07830B1A088022831D202E060881728A8 -:109170002DD20720FEBD000030610200B0030020A8 -:109180001C000020000000206E52463578000000D0 -:10919000207AE0B161881729EBD3A1881729E8D399 -:1091A000A1790029E5D0E1790029E2D0402804D94D -:1091B000DFE7242F0BD1207A48B161884FF6FB708E -:1091C000814202D8A188814201D90420D2E765B941 -:1091D000207802AA0121FFF770FF0028CAD1207869 -:1091E000FFF78DFF050000D1FFDF052E18D2DFE865 -:1091F00006F0030B0E081100A0786870A088E880C4 -:109200000FE06088A8800CE0A078A87009E0A07842 -:10921000E87006E054F8020FA8606068E86000E0BB -:10922000FFDF0020A6E71A2835D00DDC132832D244 -:10923000DFE800F01B31203131272723252D313184 -:1092400029313131312F0F00302802D003DC1E28A4 -:1092500021D1072070473A3809281CD2DFE800F0F6 -:10926000151B0F1B1B1B1B1B07000020704743F225 -:109270000400704743F202007047042070470D203D -:1092800070470F2070470820704711207047132047 -:109290007047062070470320704710B5007800F033 -:1092A000010009F0F3FDBDE81040BCE710B50078FF -:1092B00000F0010009F0F3FDBDE81040B3E70EB582 -:1092C000017801F001018DF80010417801F00101F1 -:1092D0008DF801100178C1F340018DF8021041783A -:1092E000C1F340018DF80310017889088DF804104E -:1092F000417889088DF8051081788DF80610C178BD -:109300008DF8071000798DF80800684608F0FDFD1B -:10931000FFF789FF0EBD2DE9FC5FDFF8F883FE4CF7 -:1093200000264FF490771FE0012000F082FD01201D -:10933000FFF746FE05463946D8F808000AF0F1FB6B -:10934000686000B9FFDF686808F0AAFCB0B1284681 -:10935000FAF75EFB284600F072FD28B93A466968C4 -:10936000D8F808000AF008FC94F9E9010428DBDACF -:1093700002200AF043FD07460025AAE03A46696844 -:10938000D8F808000AF0F8FBF2E7B8F802104046F7 -:10939000491C89B2A8F80210B94201D300214180CA -:1093A0000221B8F802000AF081FD002866D0B8F862 -:1093B0000200694609F0CFFCFFF735FF00B1FFDF7F -:1093C0009DF80000019078B1B8F802000AF0B1FEF3 -:1093D0005FEA000900D1FFDF48460AF020F918B122 -:1093E000B8F8020002F0E4F9B8F802000AF08FFEC3 -:1093F0005FEA000900D1FFDF48460AF008F9E8BB40 -:109400000321B8F802000AF051FD5FEA000B4BD1CE -:10941000FFDF49E0DBF8100010B10078FF284DD0E5 -:10942000022000F006FD0220FFF7CAFD82464846F2 -:109430000AF0F9F9CAF8040000B9FFDFDAF804000D -:109440000AF0C1FA002100900170B8F802105046ED -:10945000AAF8021002F0B2F848460AF0B6FA00B9CB -:10946000FFDF019800B10126504600F0E8FC18B972 -:109470009AF80100000705D5009800E027E0CBF836 -:10948000100011E0DBF8101039B10878401C10F022 -:10949000FF00087008D1FFDF06E0002211464846B1 -:1094A00000F0F0FB00B9FFDF94F9EA01022805DBC8 -:1094B000B8F8020002F049F80028ABD194F9E901AC -:1094C000042804DB48460AF0E4FA00B101266D1CCA -:1094D000EDB2BD4204D294F9EA010228BFF655AFBD -:1094E000002E7FF41DAFBDE8FC5F032000F0A1BC9F -:1094F00010B5884CE06008682061AFF2E510F9F71C -:10950000E4FC607010BD844800214438017081483B -:10951000017082494160704770B505464FF0805038 -:109520000C46D0F8A410491C05D1D0F8A810C943A6 -:109530000904090C0BD050F8A01F01F0010129709B -:10954000416821608068A080287830B970BD06210C -:1095500020460DF0CCFF01202870607940F0C0005B -:10956000607170BD70B54FF080540D46D4F8801016 -:10957000491C0BD1D4F88410491C07D1D4F88810A9 -:10958000491C03D1D4F88C10491C0CD0D4F880109D -:109590000160D4F884104160D4F888108160D4F858 -:1095A0008C10C16002E010210DF0A1FFD4F89000F2 -:1095B000401C0BD1D4F89400401C07D1D4F898007B -:1095C000401C03D1D4F89C00401C09D054F8900FE3 -:1095D000286060686860A068A860E068E86070BDA6 -:1095E0002846BDE8704010210DF081BF4A4800793F -:1095F000E6E470B5484CE07830B3207804EB4010D6 -:10960000407A00F00700204490F9E801002800DCCF -:10961000FFDF2078002504EB4010407A00F00700BF -:10962000011991F8E801401E81F8E8012078401CFA -:10963000C0B220700F2800D12570A078401CA07007 -:109640000DF0D4FDE57070BDFFDF70BD3EB5054681 -:1096500003210AF02BFC044628460AF058FD054673 -:1096600004B9FFDF206918B10078FF2800D1FFDFBF -:1096700001AA6946284600F005FB60B9FFDF0AE051 -:10968000002202A9284600F0FDFA00B9FFDF9DF88C -:10969000080000B1FFDF9DF80000411E8DF80010AA -:1096A000EED220690199884201D1002020613EBD9F -:1096B00070B50546A0F57F400C46FF3800D1FFDFAE -:1096C000012C01D0FFDF70BDFFF790FF040000D137 -:1096D000FFDF207820F00F00401D20F0F000503018 -:1096E000207065800020207201202073BDE870404A -:1096F0007FE72DE9F04116460D460746FFF776FF56 -:10970000040000D1FFDF207820F00F00401D20F082 -:10971000F00005E01C000020F403002048140020A5 -:109720005030207067800120207228682061A8884E -:10973000A0822673BDE8F0415BE77FB5FFF7DFFC51 -:10974000040000D1FFDF02A92046FFF7EBFA05462F -:1097500003A92046FFF700FB8DF800508DF80100AB -:10976000BDF80800001DADF80200BDF80C00001D9A -:10977000ADF80400E088ADF80600684609F070FB1B -:10978000002800D0FFDF7FBD2DE9F05FFC4E814651 -:10979000307810B10820BDE8F09F4846F7F77FFD0C -:1097A00008B11020F7E7F74C207808B9FFF757FC0D -:1097B000A17A607A4D460844C4B200F09DFAA042F6 -:1097C00007D2201AC1B22A460020FFF776FC0028F3 -:1097D000E1D17168EB48C91C002721F003017160D9 -:1097E000B3463E463D46BA463C4690F801800AE004 -:1097F000204600F076FA4178807B0E4410FB01553C -:10980000641CE4B27F1C4445F2D10AEB870000EBF4 -:10981000C600DC4E00EB85005C46F17A012200EBCD -:109820008100DBF80410451829464846FFF7B0FAD6 -:10983000070012D00020FFF762FC05000BD005F1F5 -:109840001300616820F00300884200D0FFDF7078C9 -:10985000401E7070656038469DE7002229464846E4 -:10986000FFF796FA00B1FFDFD9F8000060604FF60D -:10987000FF7060800120207000208CE72DE9F0410E -:109880000446BF4817460D46007810B10820BDE8D1 -:10989000F0810846F7F7DDFC08B11020F7E7B94E74 -:1098A000307808B9FFF7DBFB601E1E2807D8012CB3 -:1098B00023D12878FE2820D8B0770020E7E7A4F14C -:1098C00020001F2805D8E0B23A462946BDE8F041FD -:1098D00027E4A4F140001F2805D829462046BDE80A -:1098E000F04100F0D4BAA4F1A0001F2805D8294601 -:1098F0002046BDE8F04100F006BB0720C7E72DE990 -:10990000F05F81460F460846F7F7C9FC48B948465C -:10991000F7F7E3FC28B909F1030020F003014945FA -:1099200001D0102037E797484FF0000B4430817882 -:1099300069B14178804600EB411408343E883A46CC -:109940000021204600F089FA050004D027E0A7F89E -:1099500000B005201FE7B9F1000F24D03888B042CD -:1099600001D90C251FE0607800F00700824600F066 -:1099700060FA08EB0A063A4696F8E8014946401CA8 -:1099800086F8E801204600F068FA054696F8E801F6 -:10999000401E86F8E801032000F04BFA2DB10C2D93 -:1099A00001D0A7F800B02846F5E6754F5046BAF149 -:1099B000010F25D002280DD0BAF1030F35D0FFDFFB -:1099C00098F801104046491CC9B288F801100F29C7 -:1099D00037D038E0606828B16078000702D460882A -:1099E000FFF734FE98F8EA014446012802D178785E -:1099F000F9F78AFA94F9EA010428E1DBFFDFDFE7EF -:109A0000616821B14FF49072B8680AF0B5F898F81F -:109A1000E9014446032802D17878F9F775FA94F9F8 -:109A2000E9010428CCDBFFDFCAE76078C00602D575 -:109A30006088FFF70BFE98F9EB010628C0DBFFDF1B -:109A4000BEE780F801B08178491E88F8021096F8C8 -:109A5000E801401C86F8E801A5E770B50C4605460C -:109A6000F7F7F7FB18B92046F7F719FC08B11020F3 -:109A700070BD28460BF07FFF207008B1002070BD3C -:109A8000042070BD70B505460BF08EFFC4B22846A9 -:109A9000F7F723FC08B1102070BD35B128782C7081 -:109AA00018B1A04201D0072070BD2046FDF77EFE10 -:109AB000052805D10BF07BFF012801D0002070BDE7 -:109AC0000F2070BD70B5044615460E460846F7F7E0 -:109AD000C0FB18B92846F7F7E2FB08B1102070BDAB -:109AE000022C03D0102C01D0092070BD2A4631462B -:109AF00020460BF086FF0028F7D0052070BD70B51A -:109B000014460D460646F7F7A4FB38B92846F7F782 -:109B1000C6FB18B92046F7F7E0FB08B1102070BD6E -:109B20002246294630460BF06EFF0028F7D007206A -:109B300070BD3EB50446F7F7B2FB08B110203EBD3C -:109B4000684608F053F9FFF76EFB0028F7D19DF83F -:109B500006002070BDF808006080BDF80A00A080F3 -:109B600000203EBD70B505460C460846F7F7B5FB2C -:109B700020B95CB12068F7F792FB28B1102070BDC6 -:109B80001C000020B0030020A08828B121462846F0 -:109B9000BDE87040FDF762BE0920F0E770B50546EC -:109BA0000C460846F7F755FBA0BB681E1E280ED8CA -:109BB000032D01D90720E2E705B9FFDFFE4800EBDE -:109BC000850050F8041C2046BDE870400847A5F108 -:109BD00020001F2805D821462846BDE87040FAF726 -:109BE00042BBA5F160001F2805D821462846BDE8E4 -:109BF0007040F8F7DABCF02D0DD0F12D15D0BF2D47 -:109C0000D8D1A078218800F0010001F08DFB98B137 -:109C10000020B4E703E0A068F7F71BFB08B11020B1 -:109C2000ADE7204609F081F902E0207809F0A0F9BB -:109C3000BDE87040FFF7F7BA0820A0E770B504460A -:109C40000D460846F7F72BFB30B9601E1E280FD8CB -:109C50002846F7F7FEFA08B1102090E7012C03D050 -:109C6000022C01D0032C01D1062088E7072086E7CB -:109C7000A4F120001F28F9D829462046BDE87040ED -:109C8000FAF762BB09F092BC38B50446CB48007BBA -:109C900000F00105F9B904F01DFC0DB1226800E0E7 -:109CA0000022C7484178C06807F06DFDC4481030F5 -:109CB000C0788DF8000010B1012802D004E0012026 -:109CC00000E000208DF80000684608F0FFF8BA4870 -:109CD000243808F0B5FE002D02D02068283020601E -:109CE00038BD30B5B54D04466878A04200D8FFDFD6 -:109CF000686800EB041030BD70B5B04800252C46F4 -:109D0000467807E02046FFF7ECFF4078641C2844C3 -:109D1000C5B2E4B2B442F5D1284630E72DE9F041AE -:109D20000C4607464FF0000800F01FF90646FF28D2 -:109D300001D94FF013083868C01C20F003023A60C4 -:109D400054EA080421D19D48F3B2072128300DF0D0 -:109D5000DBFD09E0072C10D2DFE804F00604080858 -:109D60000A040600974804E0974802E0974800E09C -:109D700097480DF0E9FD054600E0FFDFA54200D061 -:109D8000FFDF641CE4B2072CE4D3386800EB061054 -:109D9000386040467BE5021D5143452900D24521EC -:109DA0000844C01CB0FBF2F0C0B270472DE9FC5F64 -:109DB000064682484FF000088B464746444690F8D6 -:109DC000019022E02046FFF78CFF050000D1FFDF65 -:109DD000687869463844C7B22846FEF7A3FF824632 -:109DE00001A92846FEF7B8FF0346BDF80400524615 -:109DF000001D81B2BDF80000001D80B20AF0D4F849 -:109E00006A78641C00FB0288E4B24C45DAD1306801 -:109E1000C01C20F003003060BBF1000F00D0002018 -:109E2000424639460AF0CEF8316808443060BDE851 -:109E3000FC9F6249443108710020C87070475F4937 -:109E40004431CA782AB10A7801EB421108318142C3 -:109E500001D001207047002070472DE9F0410646EF -:109E60000078154600F00F0400201080601E0F4699 -:109E7000052800D3FFDF50482A46183800EB84003D -:109E8000394650F8043C3046BDE8F04118472DE90A -:109E9000F0414A4E0C46402806D0412823D04228A3 -:109EA0002BD0432806D123E0A07861780D18E17803 -:109EB000814201D90720EAE42078012801D9132042 -:109EC000E5E4FF2D08D80BF009FF07460DF046F931 -:109ED000381A801EA84201DA1220D8E42068B06047 -:109EE000207930730DE0BDE8F041084600F078B805 -:109EF00008780228DED8307703E008780228D9D81D -:109F000070770020C3E4F8B500242C4DA02805D0BC -:109F1000A12815D0A22806D00720F8BD087800F0A7 -:109F20000100E8771FE00E4669463046FDF73DFD2B -:109F30000028F2D130882884B07885F8220012E019 -:109F400008680921F82801D3820701D00846F8BD26 -:109F50006A7C02F00302012A04D16A8BD73293B2E1 -:109F60008342F3D868622046F8BD2DE9F047DFF858 -:109F70004C900026344699F8090099F80A2099F87F -:109F800001700244D5B299F80B20104400F0FF088C -:109F900008E02046FFF7A5FE817B407811FB0066B4 -:109FA000641CE4B2BC42F4D199F8091099F80A0093 -:109FB0002944294441440DE054610200B0030020CB -:109FC0001C0000206741000045B30000DD2F0000A9 -:109FD000FB56010000B1012008443044BDE8F08781 -:109FE00038B50446407800F00300012803D0022869 -:109FF0000BD0072038BD606858B1F7F777F9D0B9B2 -:10A000006068F7F76AF920B915E06068F7F721F999 -:10A0100088B969462046FCF729F80028EAD160781B -:10A0200000F00300022808D19DF8000028B1606804 -:10A03000F7F753F908B1102038BD6189F8290DD818 -:10A04000208988420AD8607800F003020A48012A71 -:10A0500006D1D731426A89B28A4201D2092038BD7D -:10A0600094E80E0000F1100585E80E000AB9002101 -:10A070000183002038BD0000B00300202DE9F0412D -:10A08000074614468846084601F08AFD064608EB56 -:10A0900088001C22796802EBC0000D18688C58B14A -:10A0A0004146384601F08BFD014678680078C200D1 -:10A0B000082305F120000CE0E88CA8B141463846A1 -:10A0C00001F084FD0146786808234078C20005F15C -:10A0D000240009F0A8FD38B1062121726681D0E97B -:10A0E0000010C4E9031009E0287809280BD00520E6 -:10A0F000207266816868E060002028702046BDE814 -:10A10000F04101F02EBD072020726681F4E72DE9B1 -:10A11000F04116460D460746406801EB85011C22BA -:10A1200002EBC1014418204601F072FD40B100214C -:10A13000708865F30F2160F31F4106200DF0BEFC0F -:10A1400009202070324629463846BDE8F04195E79F -:10A150002DE9F0410E46074600241C21F07816E058 -:10A1600004EB8403726801EBC303D25C6AB1FFF7AE -:10A170003DFA050000D1FFDF6F802A4621463046B8 -:10A18000FFF7C5FF0120BDE8F081641CE4B2A042E6 -:10A19000E6D80020F7E770B5064600241C21C078F9 -:10A1A0000AE000BF04EB8403726801EBC303D51817 -:10A1B0002A782AB1641CE4B2A042F3D8402070BDD2 -:10A1C00028220021284604F062F9706880892881DD -:10A1D000204670BD70B5034600201C25DC780CE0DD -:10A1E00000EB80065A6805EBC6063244167816B1B5 -:10A1F000128A8A4204D0401CC0B28442F0D8402067 -:10A2000070BDF0B5044600201C26E5780EE000BFC6 -:10A2100000EB8007636806EBC7073B441F788F425B -:10A2200002D15B78934204D0401CC0B28542EFD883 -:10A230004020F0BD0078032801D0002070470120A5 -:10A2400070470078022801D0002070470120704735 -:10A250000078072801D000207047012070472DE9C1 -:10A26000F041064688461078F1781546884200D3BA -:10A27000FFDF2C781C27641CF078E4B2A04201D8E0 -:10A28000201AC4B204EB8401706807EBC1010844D2 -:10A29000017821B14146884708B12C7073E72878CE -:10A2A000A042E8D1402028706DE770B514460B88B5 -:10A2B0000122A240134207D113430B8001230A223B -:10A2C000011D09F07AFC047070BD2DE9FF4F81B0CB -:10A2D0000878DDE90E7B9A4691460E4640072CD45D -:10A2E000019809F026FF040000D1FFDF07F1040800 -:10A2F00020461FFA88F109F065F8050000D1FFDF5C -:10A30000204629466A4609F0B0FA0098A0F8037082 -:10A31000A0F805A0284609F056FB017869F306016C -:10A320006BF3C711017020461FFA88F109F08DF810 -:10A3300000B9FFDF019807F094F906EB0900017FEF -:10A34000491C017705B0BDE8F08F2DE9F84F0E46A6 -:10A350009A4691460746032109F0A8FD0446008D60 -:10A36000DFF8B885002518B198F80000B0421ED17A -:10A37000384609F0DEFE070000D1FFDF09F10401D5 -:10A38000384689B209F01EF8050010D03846294633 -:10A390006A4609F06AFA009800210A460180817035 -:10A3A00007F01CFA0098C01DCAF8000021E098F8D8 -:10A3B0000000B04216D104F1260734F8341F012002 -:10A3C00000FA06F911EA090F00D0FFDF2088012307 -:10A3D00040EA090020800A22391D384609F008FCAD -:10A3E000067006E0324604F1340104F12600FFF75E -:10A3F0005CFF0A2188F800102846BDE8F88FFEB5FA -:10A4000015460C46064602AB0C220621FFF79DFFBF -:10A41000002827D00299607812220A70801C4870A8 -:10A4200008224A80A07002982988052381806988C3 -:10A43000C180A9880181E988418100250C20CDE9EE -:10A440000005062221463046FFF73FFF294600223D -:10A4500066F31F41F02310460DF086FA6078801CE9 -:10A4600060700120FEBDFEB514460D46062206466C -:10A4700002AB1146FFF769FF002812D0029B1320A0 -:10A4800000211870A8785870022058809C800620FF -:10A49000CDE900010246052329463046FFF715FFA6 -:10A4A0000120FEBD2DE9FE430C46804644E002AB90 -:10A4B0000E2207214046FFF748FF002841D0606880 -:10A4C0001C2267788678BF1C06EB860102EBC1016F -:10A4D000451802981421017047700A214180698A49 -:10A4E0000181E98A4181A9888180A98981813046D9 -:10A4F00001F056FB029905230722C8806F700420E3 -:10A50000287000250E20CDE9000521464046FFF7C2 -:10A51000DCFE294666F30F2168F31F41F023002279 -:10A5200006200DF021FA6078FD49801C6070626899 -:10A530002046921CFFF793FE606880784028B6D1D1 -:10A540000120BDE8FE83FEB50D46064638E002ABAD -:10A550000E2207213046FFF7F8FE002835D0686844 -:10A560001C23C17801EB810203EBC202841802981C -:10A5700015220270627842700A224280A2894281CA -:10A58000A2888281084601F00BFB01460298818077 -:10A59000618AC180E18A0181A088B8B10020207061 -:10A5A00000210E20CDE9000105230722294630466F -:10A5B000FFF78BFE6A68DB492846D21CFFF74FFE87 -:10A5C0006868C0784028C2D10120FEBD0620E6E7B9 -:10A5D0002DE9FE430C46814644E0204601F002FB93 -:10A5E000D0B302AB082207214846FFF7AEFE002891 -:10A5F000A7D060681C2265780679AD1C06EB860141 -:10A6000002EBC10147180298B7F8108006210170CB -:10A61000457004214180304601F0C2FA014602989B -:10A6200005230722C180A0F804807D7008203870BF -:10A630000025CDE9000521464846FFF746FE29469C -:10A6400066F30F2169F31F41F023002206200DF06D -:10A650008BF96078801C60706268B3492046121DD7 -:10A66000FFF7FDFD606801794029B6D1012068E758 -:10A670002DE9F34F83B00D4691E0284601F0B2FA80 -:10A6800000287DD068681C2290F806A00AEB8A0199 -:10A6900002EBC10144185146284601F097FAA1780F -:10A6A000CB0069684978CA00014604F1240009F02A -:10A6B000D6FA07468188E08B4FF00009091A8EB25E -:10A6C00008B1C84607E04FF00108504601F053FAC0 -:10A6D00008B9B61CB6B2208BB04200D80646B346C5 -:10A6E00002AB324607210398FFF72FFE060007D082 -:10A6F000B8F1000F0BD0504601F03DFA10B106E062 -:10A7000000201FE60299B8884FF0020908800196E0 -:10A71000E28B3968ABEB09001FFA80F80A44039812 -:10A720004E46009209F005FDDDE90021F61D434685 -:10A73000009609F014F9E08B404480B2E083B988B8 -:10A74000884201D1012600E00026CDE900B6238A27 -:10A75000072229460398FFF7B8FD504601F00BFA8F -:10A7600010B9E089401EE08156B1A078401CA0706D -:10A770006868E978427811FB02F1CAB2012300E06F -:10A7800007E081690E3009F018FA80F800A0002077 -:10A79000E0836A6865492846921DFFF760FD686896 -:10A7A000817940297FF469AF0120CBE570B5064679 -:10A7B00048680D4614468179402910D104EB840184 -:10A7C0001C2202EBC101084401F043FA002806D024 -:10A7D0006868294684713046BDE8704048E770BD1E -:10A7E000FEB50C460746002645E0204601F0FAF982 -:10A7F000D8B360681C22417901EB810102EBC101F1 -:10A800004518688900B9FFDF02AB082207213846E6 -:10A81000FFF79BFD002833D00299607816220A705A -:10A82000801C4870042048806068407901F0B8F9C5 -:10A83000014602980523072281806989C18008208A -:10A84000CDE9000621463846FFF73FFD6078801CC1 -:10A850006070A88969890844B0F5803F00D3FFDFA4 -:10A86000A88969890844A8816E81626830492046B8 -:10A87000521DFFF7F4FC606841794029B5D10120F1 -:10A88000FEBD30B5438C458BC3F3C704002345B1EF -:10A89000838B641EED1AC38A6D1E1D4495FBF3F372 -:10A8A000E4B22CB1008918B1A04200D8204603447C -:10A8B0004FF6FF70834200D3034613800C7030BD07 -:10A8C0002DE9FC41074616460D46486802EB860115 -:10A8D0001C2202EBC10144186A4601A92046FFF779 -:10A8E000D0FFA089618901448AB2BDF8001091426D -:10A8F00012D0081A00D5002060816868407940288D -:10A900000AD1204601F09BF9002805D06868294645 -:10A9100046713846FFF764FFBDE8FC813000002037 -:10A9200035A2000043A2000051A2000053BC000069 -:10A930003FBC00002DE9FE4F0F468146154650886A -:10A94000032109F0B3FA0190B9F8020001F01BF9F4 -:10A9500082460146019801F045F9002824D001986B -:10A960001C2241680AEB8A0002EBC0000C1820464A -:10A9700001F04EF9002817D1B9F80000E18A8842A9 -:10A980000ED8A18961B1B8420ED100265146019876 -:10A9900001F015F9218C01EB0008608B30B114E057 -:10A9A000504601F0E8F8A0B3BDE8FE8F504601F034 -:10A9B000E2F808B1678308E0022FF5D3B9F8040084 -:10A9C0006083618A884224D80226B81B87B2B8F80F -:10A9D0000400A28B801A002814DD874200DA384672 -:10A9E0001FFA80FB688869680291D8F800100A4451 -:10A9F000009209F08CFBF61D009A5B4602990096C6 -:10AA000008F079FFA08B384480B2A083618B884224 -:10AA100007D96888019903B05246BDE8F04F01F0AC -:10AA200035B91FD14FF009002872B9F802006881CA -:10AA3000D8E90010C5E90410608BA881284601F010 -:10AA400090F85146019801F0BAF8014601980823A0 -:10AA500040680078C20004F1200009F0E4F800200A -:10AA6000A0836083504601F086F810B9A089401E8B -:10AA7000A0816888019903B00AF0FF02BDE8F04F99 -:10AA80001EE72DE9F041064615460F461C461846BE -:10AA9000F6F7DFFB18B92068F6F701FC10B11020BB -:10AAA000BDE8F0817168688C0978B0EBC10F01D303 -:10AAB0001320F5E73946304601F081F80146706809 -:10AAC00008230078C20005F1200009F076F8D4E9E7 -:10AAD0000012C0E900120020E2E710B5044603218D -:10AAE00009F0E4F90146007800F00300022805D0DF -:10AAF0002046BDE8104001F1140280E48A8A204615 -:10AB0000BDE81040AFE470B50446032109F0CEF96A -:10AB1000054601462046FFF75BFD002816D0294672 -:10AB20002046FFF75DFE002810D029462046FFF79B -:10AB30000AFD00280AD029462046FFF7B3FC00286A -:10AB400004D029462046BDE8704091E570BD2DE94E -:10AB5000F0410C4680461EE0E178427811FB02F19C -:10AB6000CAB2816901230E3009F05DF80778606888 -:10AB70001C22C179491EC17107EB8701606802EB95 -:10AB8000C10146183946204601F02CF818B130466C -:10AB900001F037F820B16068C1790029DCD17FE786 -:10ABA000FEF724FD050000D1FFDF0A202872384699 -:10ABB00000F0F6FF68813946204601F007F80146AB -:10ABC000606808234078C20006F1240009F02BF8E1 -:10ABD000D0E90010C5E90310A5F80280284600F06E -:10ABE000C0FFB07800B9FFDFB078401EB07057E703 -:10ABF00070B50C460546032109F058F90146406836 -:10AC0000C2792244C2712846BDE870409FE72DE911 -:10AC1000FE4F8246507814460F464FF00008002839 -:10AC20004FD0012807D0022822D0FFDF2068B8606B -:10AC30006068F860B8E602AB0E2208215046FFF7C4 -:10AC400084FB0028F2D00298152105230170217899 -:10AC500041700A214180C0F80480C0F80880A0F843 -:10AC60000C80628882810E20CDE90008082221E054 -:10AC7000A678304600F094FF054606EB86012C22AC -:10AC8000786802EBC1010822465A02AB11465046D1 -:10AC9000FFF75BFB0028C9D00298072101702178DB -:10ACA00041700421418008218580C680CDE90018CB -:10ACB00005230A4639465046FFF707FB87F8088008 -:10ACC00072E6A678022516B1022E13D0FFDF2A1DE8 -:10ACD000914602AB08215046FFF737FB0028A5D06C -:10ACE00002980121022E01702178417045808680F2 -:10ACF00002D005E00625EAE7A188C180E18801814C -:10AD0000CDE900980523082239465046D4E710B50E -:10AD10000446032109F0CAF8014600F10802204662 -:10AD2000BDE8104073E72DE9F04F0F4605468DB0A2 -:10AD300014465088032109F0B9F84FF000088DF847 -:10AD400014800646ADF81680042F7BD36A78002A5B -:10AD500078D028784FF6FF794FF01C0A132834D0AA -:10AD600008DC012871D006284AD007286ED01228A6 -:10AD70000ED106E014286AD0152869D0162807D10C -:10AD8000AAE10C2F04D1307800F00301022907D08A -:10AD9000CDF80880CDF80C8068788DF808004CE07C -:10ADA00040F0080030706878B07001208DF8140011 -:10ADB000A888ADF81800E888ADF81A002889ADF821 -:10ADC0001C006889ADF81E0011E1B078904239D1BD -:10ADD0003078010736D5062F34D120F008003070C6 -:10ADE0006088414660F31F4100200CF067FE02209E -:10ADF0008DF81400ADF81890A888ADF81A00F6E0A8 -:10AE0000082F1FD1A888EF88814600F0BCFE80463D -:10AE10000146304600F0E6FE18B1404600F0ABFEB9 -:10AE2000B8B1FC48D0E90010CDE902106878ADF85F -:10AE30000C908DF80800ADF80E70608802AA3146BB -:10AE4000FFF7E5FE0DB0BDE8F08FB6E01EE041E093 -:10AE5000ECE0716808EB88002C2202EBC000085A75 -:10AE6000B842EFD1EB4802AAD0E90210CDE90210B6 -:10AE700068788DF8080008F0FF058DF80A506088A2 -:10AE80003146FFF7C4FE224629461FE0082FD9D1DC -:10AE9000B5F80480E88800F076FE074601463046A3 -:10AEA00000F0A0FE0028CDD007EB870271680AEB06 -:10AEB000C2000844028A4245C4D101780829C1D1A0 -:10AEC000407869788842BDD1F9B222463046FFF712 -:10AED0001EF9B7E70E2F7FF45BAFE9886F898B46C9 -:10AEE000B5F808903046FFF775F9ABF140014029FD -:10AEF00001D309204AE0B9F1170F01D3172F01D26E -:10AF00000B2043E040280ED000EB800271680AEB72 -:10AF1000C20008440178012903D140786978884249 -:10AF200090D00A2032E03046FFF735F9014640283C -:10AF30002BD001EB810372680AEBC30002EB00081F -:10AF4000012288F800206A7888F801207068AA88B1 -:10AF50004089B84200D93846AD8903232372A282C2 -:10AF6000E7812082A4F80C906582084600F018FE64 -:10AF70006081A8F81490A8F81870A8F80E50A8F8E6 -:10AF800010B0204600F0EDFD5CE7042005212172A1 -:10AF9000A4F80A80E081012121739E49D1E90421AE -:10AFA000CDE9022169788DF80810ADF80A006088B3 -:10AFB00002AA3146FFF72BFEE3E7062F89D3B078CC -:10AFC00090421AD13078010717D520F00800307070 -:10AFD0006088414660F31F4100200CF06FFD0220A5 -:10AFE0008DF81400A888ADF81800ADF81A906088A4 -:10AFF000224605A9F9F7E3F824E704213046FFF7D4 -:10B0000000F905464028BFD0022083030090224665 -:10B010002946304600F003FE4146608865F30F2163 -:10B0200060F31F4106200CF049FD0BE70E2FABD15A -:10B0300004213046FFF7E5F881464028A4D0414678 -:10B04000608869F30F2160F31F4106200CF036FD84 -:10B05000A8890B906889099070682F894089B84247 -:10B0600000D938468346B5F80680A8880A90484635 -:10B0700000F096FD60810B9818B1022000900B9BA8 -:10B0800024E0B8F1170F1ED3172F1CD30420207211 -:10B0900009986082E781A4F810B0A4F80C8009EB4D -:10B0A000890271680AEBC2000D18DDE90913A5F8E1 -:10B0B0001480A5F818B0E9812B82204600F051FDDC -:10B0C00006202870BEE601200B2300902246494648 -:10B0D000304600F0A4FDB5E6082F8DD1A988304692 -:10B0E000FFF778F80746402886D000F044FD002896 -:10B0F0009BD107EB870271680AEBC20008448046C7 -:10B1000000F086FD002890D1ED88B8F80E002844A4 -:10B11000B0F5803F05D360883A46314600F0B6FD71 -:10B1200090E6002DCED0A8F80E0060883A46314651 -:10B13000FFF73CFB08202072384600F031FD6081AB -:10B14000A5811EE72DE9F05F0C4601281FD09579F7 -:10B1500092F8048092F8056005EB85011F2202EB4E -:10B16000C10121F0030B08EB060111FB05F14FF6BD -:10B17000FF7202EAC10909F1030115FB0611264F0E -:10B1800021F0031ABB6840B101283ED125E0616877 -:10B19000E57891F800804E78DEE75946184608F0C9 -:10B1A000C0FC606000B9FFDF5A460021606803F010 -:10B1B0006EF9E5705146B86808F0B3FC6168486103 -:10B1C00000B9FFDF6068426902EB090181616068D4 -:10B1D00080F800806068467017E0606852464169F8 -:10B1E000184608F0C9FC5A466168B86808F0C4FC03 -:10B1F000032008F003FE0446032008F007FE201A8F -:10B20000012802D1B86808F081FC0BEB0A00BDE808 -:10B21000F09F000060610200300000200246002123 -:10B2200002208FE7F7B5FF4C0A20164620700098E1 -:10B2300060B100254FEA0D0008F055FC0021A17017 -:10B240006670002D01D10099A160FEBD012500208E -:10B25000F2E770B50C46154638220021204603F06F -:10B2600016F9012666700A22002104F11C0003F081 -:10B270000EF905B9FFDF297A207861F3010020700B -:10B28000A87900282DD02A4621460020FFF75AFF32 -:10B2900061684020E34A88706168C870616808711D -:10B2A000616848716168887161682888088161688F -:10B2B00068884881606886819078002811D061682C -:10B2C0000620087761682888C885616828884886CC -:10B2D00060680685606869889288018681864685EF -:10B2E000828570BDC878002802D00022012029E79D -:10B2F000704770B50546002165F31F4100200CF032 -:10B30000DDFB0321284608F0D1FD040000D1FFDF5A -:10B3100021462846FEF71CFF002804D0207840F084 -:10B3200010002070012070BD70B505460C4603204A -:10B3300008F056FD08B1002070BDBA4885708480C1 -:10B34000012070BD2DE9FF4180460E460F0CFEF72F -:10B350004DF9050007D06F800321384608F0A6FD9F -:10B36000040008D106E004B03846BDE8F0411321DE -:10B37000F9F750BBFFDF5FEA080005D0B8F1060F10 -:10B3800018D0FFDFBDE8FF8120782A4620F00800B2 -:10B3900020700020ADF8020002208DF800004FF66A -:10B3A000FF70ADF80400ADF8060069463846F8F7BE -:10B3B00006FFE7E7C6F3072101EB81021C23606863 -:10B3C00003EBC202805C042803D008280AD0FFDF08 -:10B3D000D8E7012000904FF440432A46204600F071 -:10B3E0001EFCCFE704B02A462046BDE8F041FEF738 -:10B3F0008EBE2DE9F05F05464089002790460C4639 -:10B400003E46824600F0BFFB8146287AC01E0828CF -:10B410006BD2DFE800F00D04192058363C47722744 -:10B420001026002C6CD0D5E90301C4E902015CE0D0 -:10B4300070271226002C63D00A2205F10C0104F1BA -:10B44000080002F0FAFF50E071270C26002C57D0BC -:10B45000E868A06049E0742710269CB3D5E9030191 -:10B46000C4E902016888032108F020FD8346FEF745 -:10B47000BDF802466888508049465846FEF7FEFDF2 -:10B4800033E075270A26ECB1A88920812DE07627C4 -:10B490001426BCB105F10C0004F1080307C883E8C9 -:10B4A000070022E07727102664B1D5E90301C4E93B -:10B4B00002016888032108F0F9FC01466888FFF75B -:10B4C00046FB12E01CE073270826CCB168880321F4 -:10B4D00008F0ECFC01460078C00606D56888FEF747 -:10B4E00037FE10B96888F8F777FAA8F800602CB131 -:10B4F0002780A4F806A066806888A080002086E6E1 -:10B50000A8F80060FAE72DE9FC410C461E461746F4 -:10B510008046032108F0CAFC05460A2C0AD2DFE85F -:10B5200004F005050505050509090907042303E0DD -:10B53000062301E0FFDF0023CDE9007622462946FD -:10B540004046FEF7C2FEBDE8FC81F8B50546A0F511 -:10B550007F40FF382BD0284608F0D9FD040000D1E9 -:10B56000FFDF204608F05FF9002821D001466A4637 -:10B57000204608F07AF900980321B0F805602846C3 -:10B5800008F094FC0446052E13D0304600F0FBFA78 -:10B5900005460146204600F025FB40B1606805EBFA -:10B5A00085013E2202EBC101405A002800D0012053 -:10B5B000F8BD007A0028FAD00020F8BDF8B504469E -:10B5C000408808F0A4FD050000D1FFDF6A46284648 -:10B5D000616800F0C4FA01460098091F8BB230F888 -:10B5E000032F0280428842800188994205D1042AB3 -:10B5F00008D0052A20D0062A16D022461946FFF781 -:10B6000099F9F8BD001D0E46054601462246304612 -:10B61000F6F739FF0828F4D1224629463046FCF7D0 -:10B6200064F9F8BD30000020636864880A46011D93 -:10B630002046FAF789F9F4E72246001DFFF773FB6D -:10B64000EFE770B50D460646032108F02FFC040015 -:10B6500004D02078000704D5112070BD43F2020009 -:10B6600070BD2A4621463046FEF7C9FE18B9286843 -:10B6700060616868A061207840F0080020700020B8 -:10B6800070BD70B50D460646032108F00FFC04009E -:10B6900004D02078000704D4082070BD43F20200D3 -:10B6A00070BD2A4621463046FEF7DDFE00B9A58270 -:10B6B000207820F008002070002070BD2DE9F04FA8 -:10B6C0000E4691B08046032108F0F0FB0446404648 -:10B6D00008F02FFD07460020079008900990ADF86C -:10B6E00030000A9002900390049004B9FFDF0DF13E -:10B6F0000809FFB9FFDF1DE038460BA9002207F05B -:10B7000055FF9DF82C0000F07F050A2D00D3FFDFC8 -:10B710006019017F491E01779DF82C00000609D5AC -:10B720002A460CA907A8FEF7C0FD19F80510491C08 -:10B7300009F80510761EF6B2DED204F13400F84D99 -:10B7400004F1260BDFF8DCA304F12A07069010E0D1 -:10B750005846069900F08CFA064628700A2800D34D -:10B76000FFDF5AF8261040468847E08CC05DB042A3 -:10B7700002D0208D0028EBD10A202870E94D4E46DA -:10B7800028350EE00CA907A800F072FA0446375DD0 -:10B7900055F8240000B9FFDF55F82420394640460B -:10B7A0009047BDF81E000028ECD111B0BDE8F08F25 -:10B7B00010B5032108F07AFB040000D1FFDF0A2254 -:10B7C000002104F11C0002F062FE207840F0040029 -:10B7D000207010BD10B50C46032108F067FB204413 -:10B7E000007F002800D0012010BD2DE9F84F8946C8 -:10B7F00015468246032108F059FB070004D028466D -:10B80000F5F727FD40B903E043F20200BDE8F88FE9 -:10B810004846F5F744FD08B11020F7E7786828B1ED -:10B8200069880089814201D90920EFE7B9F8000051 -:10B830001C2488B100F0A7F980460146384600F084 -:10B84000D1F988B108EB8800796804EBC000085C86 -:10B8500001280BD00820D9E73846FEF79CFC80462B -:10B86000402807D11320D1E70520CFE7FDF7BEFE22 -:10B8700006000BD008EB8800796804EBC0000C18B8 -:10B88000B9F8000020B1E88910B113E01120BDE73C -:10B890002888172802D36888172801D20720B5E71F -:10B8A000686838B12B1D224641463846FFF7E9F853 -:10B8B0000028ABD104F10C0269462046FEF7E1FFF7 -:10B8C000288860826888E082B9F8000030B10220E0 -:10B8D0002070E889A080E889A0B12BE003202070C7 -:10B8E000A889A08078688178402905D180F80280F5 -:10B8F00039465046FEF7D6FD404600F051F9A9F80A -:10B90000000021E07868218B4089884200D90846F0 -:10B910002083A6F802A004203072B9F800007081DC -:10B92000E0897082F181208B3082A08AB08130461C -:10B9300000F017F97868C178402905D180F80380B4 -:10B9400039465046FEF7FFFD00205FE770B50D4613 -:10B950000646032108F0AAFA04000ED0284600F09B -:10B9600012F905460146204600F03CF918B1284678 -:10B9700000F001F920B1052070BD43F2020070BD56 -:10B9800005EB85011C22606802EBC101084400F050 -:10B990003FF908B1082070BD2A462146304600F024 -:10B9A00075F9002070BD2DE9F0410C461746804620 -:10B9B000032108F07BFA0546204600F0E4F804462F -:10B9C00095B10146284600F00DF980B104EB8401E1 -:10B9D0001C22686802EBC1014618304600F018F9D5 -:10B9E00038B10820BDE8F08143F20200FAE70520F3 -:10B9F000F8E73B46324621462846FFF742F8002842 -:10BA0000F0D1E2B229464046FEF75AFF708C083862 -:10BA1000082803D242484078F7F776FA0020E1E799 -:10BA20002DE9F0410D4617468046032108F03EFA05 -:10BA30000446284600F0A7F8064624B13846F5F734 -:10BA400008FC38B902E043F20200CBE73868F5F7AA -:10BA500000FC08B11020C5E73146204600F0C2F8CE -:10BA600060B106EB86011C22606802EBC10145183B -:10BA7000284600F0CDF818B10820B3E70520B1E75B -:10BA8000B888A98A884201D90C20ABE76168E88CA4 -:10BA90004978B0EBC10F01D31320A3E7314620460C -:10BAA00000F094F80146606808234078C20005F170 -:10BAB000240008F082F8D7E90012C0E90012F2B2BF -:10BAC00021464046FEF772FE00208BE72DE9F04745 -:10BAD0000D461F4690468146032108F0E7F90446CB -:10BAE000284600F050F806463CB14DB13846F5F70F -:10BAF000F4FB50B11020BDE8F08743F20200FAE7F2 -:10BB0000606858B1A0F80C8027E03146204600F06C -:10BB100069F818B1304600F02EF828B10520EAE7A0 -:10BB2000300000207861020006EB86011C2260686C -:10BB300002EBC1014518284600F06AF808B1082058 -:10BB4000D9E7A5F80880F2B221464846FEF7B8FECC -:10BB50001FB1A8896989084438800020CBE707F025 -:10BB600084BE017821F00F01491C21F0F001103151 -:10BB70000170FDF73EBD20B94E48807808B1012024 -:10BB80007047002070474B498988884201D10020C6 -:10BB90007047402801D2402000E0403880B2704712 -:10BBA00010B50446402800D9FFDF2046FFF7E3FF29 -:10BBB00010B14048808810BD4034A0B210BD40682C -:10BBC00042690078484302EBC0007047C278406881 -:10BBD000037812FB03F24378406901FB032100EB79 -:10BBE000C1007047C2788A4209D9406801EB8101DF -:10BBF0001C2202EBC101405C08B10120704700200B -:10BC000070470078062801D901207047002070474E -:10BC10000078062801D00120704700207047F0B45A -:10BC200001EB81061C27446807EBC6063444049DDB -:10BC300005262670E3802571F0BCFEF71FBA10B50B -:10BC4000418911B1FFF7DDFF08B1002010BD0120CF -:10BC500010BD10B5C18C8278B1EBC20F04D9C18977 -:10BC600011B1FFF7CEFF08B1002010BD012010BDBB -:10BC700010B50C4601230A22011D07F0D4FF0078FD -:10BC80002188012282409143218010BDF0B402EB53 -:10BC900082051C264C6806EBC505072363554B68D7 -:10BCA0001C79402C03D11A71F0BCFEF791BCF0BC9A -:10BCB000704700003000002010B5EFF3108000F056 -:10BCC000010472B6FC484178491C41704078012853 -:10BCD00001D10BF0B3FA002C00D162B610BD70B5E3 -:10BCE000F54CA07848B90125A570FFF7E5FF0BF0EA -:10BCF000B6FA20B100200BF080FA002070BD4FF0A2 -:10BD00008040E570C0F80453F7E770B5EFF310809A -:10BD100000F0010572B6E84C607800B9FFDF60788A -:10BD2000401E6070607808B90BF08CFA002D00D1CD -:10BD300062B670BDE04810B5817821B10021C170B4 -:10BD40008170FFF7E2FF002010BD10B504460BF034 -:10BD500086FAD9498978084000D001202060002067 -:10BD600010BD10B5FFF7A8FF0BF079FA02220123EE -:10BD7000D149540728B1D1480260236103200872D9 -:10BD800002E00A72C4F804330020887110BD2DE966 -:10BD9000F84FDFF824934278817889F80420002650 -:10BDA00089F80510074689F806600078DFF810B3B7 -:10BDB000354620B1012811D0022811D0FFDF0BF049 -:10BDC00060FA4FF0804498B10BF062FAB0420FD1A4 -:10BDD00030460BF061FA0028FAD042E00126EEE787 -:10BDE000FFF76AFF58460168C907FCD00226E6E75C -:10BDF0000120E060C4F80451B2490E600107D1F897 -:10BE00004412B04AC1F3423124321160AD49343199 -:10BE100008604FF0020AC4F804A3A060AA480168B1 -:10BE2000C94341F3001101F10108016841F010011B -:10BE3000016001E019F0A8FFD4F804010028F9D04E -:10BE400030460BF029FA0028FAD0B8F1000F04D1DF -:10BE50009D48016821F010010160C4F808A3C4F8EE -:10BE6000045199F805004E4680B1387870B90BF04E -:10BE7000F6F980460BF006FC6FF00042B8F1000FB7 -:10BE800002D0C6E9032001E0C6E90302DBF80000A6 -:10BE9000C00701D00BF0DFF9387810B13572BDE87A -:10BEA000F88F4FF01808C4F808830127A7614FF4F2 -:10BEB0002070ADF8000000BFBDF80000411EADF8D5 -:10BEC0000010F9D2C4F80C51C4F810517A48C01DC2 -:10BED0000BF06CFA3570FFF744FF676179493079F0 -:10BEE00020310860C4F80483D9E770B5050000D19B -:10BEF000FFDF4FF080424FF0FF30C2F8080300210F -:10BF0000C2F80011C2F80411C2F80C11C2F81011E5 -:10BF1000694C61700BF0AFF910B10120A070607036 -:10BF200067480068C00701D00BF095F92846BDE8C6 -:10BF300070402CE76048007A002800D0012070474C -:10BF40002DE9F04F61484FF0000A85B0D0F800B0FD -:10BF5000D14657465D4A5E49083211608406D4F8DE -:10BF6000080110B14FF0010801E04FF000080BF09C -:10BF7000F0F978B1D4F8240100B101208246D4F858 -:10BF80001C0100B101208146D4F8200108B101272D -:10BF900000E00027D4F8000100B101200490D4F89B -:10BFA000040100B101200390D4F80C0100B101207C -:10BFB0000290D4F8100100B101203F4D0190287883 -:10BFC00000260090B8F1000F04D0C4F808610120E9 -:10BFD0000BF013F9BAF1000F04D0C4F82461092062 -:10BFE0000BF00BF9B9F1000F04D0C4F81C610A2062 -:10BFF0000BF003F927B1C4F820610B200BF0FDF81A -:10C000002D48C01D0BF0DAF900B1FFDFDFF8AC807E -:10C010000498012780B1C4F80873E87818B1EE706D -:10C0200000200BF0EAF8287A022805D103202872B4 -:10C030000221C8F800102761039808B1C4F8046110 -:10C04000029850B1C4F80C61287A032800D0FFDFB1 -:10C05000C8F800602F72FFF758FE019838B1C4F895 -:10C060001061287A012801D100F05CF8676100981E -:10C0700038B12E70287A012801D1FFF772FEFFF740 -:10C0800044FE0D48C01D0BF0AFF91049091DC1F861 -:10C0900000B005B0BDE8F08F074810B5C01D0BF02B -:10C0A0008DF90549B0B1012008704FF0E021C1F8C9 -:10C0B0000002BDE81040FFE544000020340C0040C1 -:10C0C0000C0400401805004010ED00E0100502408F -:10C0D00001000001087A012801D1FFF742FEBDE806 -:10C0E000104024480BF080B970B5224CE41FA078B2 -:10C0F00008B90BF0A7F801208507A861207A00266F -:10C10000032809D1D5F80C0120B900200BF0C4F8A0 -:10C110000028F7D1C5F80C6126724FF0FF30C5F842 -:10C12000080370BD70B5134CE41F6079F0B10128AD -:10C1300003D0A179401E814218DA0BF090F8054631 -:10C140000BF0A0FA6179012902D9A179491CA171EA -:10C150000DB1216900E0E168411A022902DA11F10A -:10C16000020F06DC0DB1206100E0E060BDE8704028 -:10C17000F7E570BD4B0000200F4A12680D498A4256 -:10C180000CD118470C4A12680A4B9A4206D101B5E5 -:10C190000BF04AFA0BF01DFDBDE8014007490968A4 -:10C1A0000958084706480749054A064B70470000EA -:10C1B00000000000BEBAFECA5C000020040000209F -:10C1C000C8130020C8130020F8B51D46DDE9064756 -:10C1D0000E000AD007F0ADFF2346FF1DBCB231466A -:10C1E0002A46009407F0BBFBF8BDD0192246194639 -:10C1F00002F023F92046F8BD70B50D460446102222 -:10C20000002102F044F9258117206081A07B40F0D5 -:10C210000A00A07370BD4FF6FF720A80014602202B -:10C220000BF04CBC704700897047827BD30701D16B -:10C23000920703D48089088000207047052070474A -:10C24000827B920700D581817047014600200988D2 -:10C2500041F6FE52114200D00120704700B503465E -:10C26000807BC00701D0052000BD59811846FFF72B -:10C27000ECFFC00703D0987B40F004009873987BD4 -:10C2800040F001009873002000BD827B520700D56A -:10C2900009B14089704717207047827B61F3C30260 -:10C2A000827370472DE9FC5F0E460446017896467E -:10C2B000012000FA01F14DF6FF5201EA020962681D -:10C2C0004FF6FF7B1188594502D10920BDE8FC9F3C -:10C2D000B9F1000F05D041F6FE55294201D00120E9 -:10C2E000F4E741EA090111801D0014D000232B70EE -:10C2F00094F800C0052103221F464FF0020ABCF14A -:10C300000E0F76D2DFE80CF0F909252F47646B7722 -:10C31000479193B4D1D80420D8E7616820898B7BFA -:10C320009B0767D517284AD30B89834247D389894E -:10C33000172901D3814242D185F800A0A5F8010058 -:10C340003280616888816068817B21F0020181739D -:10C35000C6E0042028702089A5F801006089A5F8AE -:10C3600003003180BCE0208A3188C01D1FFA80F8AC -:10C37000414524D3062028702089A5F80100608952 -:10C38000A5F80300A089A5F805000721208ACDE9BA -:10C390000001636941E00CF0FF00082810D008207C -:10C3A00028702089A5F801006089A5F80300318074 -:10C3B0006A1D694604F10C0009F025FB10B15EE02E -:10C3C0001020EDE730889DF800100844308087E0A9 -:10C3D0000A2028702089A5F80100328044E00C2052 -:10C3E00028702089A5F801006089A5F80300318034 -:10C3F0003AE082E064E02189338800EB41021FFAD1 -:10C4000082F843453BD3B8F1050F38D30E222A708A -:10C410000BEA4101CDE90010E36860882A467146C5 -:10C42000FFF7D2FEA6F800805AE04020287060890D -:10C430003188C01C1FFA80F8414520D32878714606 -:10C4400020F03F00123028702089A5F80100608993 -:10C45000CDE9000260882A46E368FFF7B5FEA6F83A -:10C460000080287840063BD461682089888037E0C6 -:10C47000A0893288401D1FFA80F8424501D2042766 -:10C480003DE0162028702089A5F801006089A5F8F4 -:10C490000300A089CDE9000160882A46714623691E -:10C4A000FFF792FEA6F80080DEE718202870207AB9 -:10C4B0006870A6F800A013E061680A88920401D4AD -:10C4C00005271CE0C9882289914201D0062716E081 -:10C4D0001E21297030806068018821F4005101809C -:10C4E000B9F1000F0BD061887823002202200BF0F5 -:10C4F0003BFA61682078887006E033800327606823 -:10C50000018821EA090101803846DFE62DE9FF4F65 -:10C5100085B01746129C0D001E461CD03078C1070E -:10C5200003D000F03F00192801D9012100E00021CB -:10C530002046FFF7AAFEA8420DD32088A0F57F4130 -:10C54000FF3908D03078410601D4000605D508200F -:10C5500009B0BDE8F08F0720FAE700208DF8000051 -:10C560008DF8010030786B1E00F03F0C0121A81EF1 -:10C570004FF0050A4FF002094FF0030B9AB2BCF1DD -:10C58000200F75D2DFE80CF08B10745E7468748C29 -:10C59000749C74B574BA74C874D474E1747474F10E -:10C5A00074EF74EE74ED748B052D78D18DF80090D6 -:10C5B000A0788DF804007088ADF8060030798DF809 -:10C5C0000100707800F03F000C2829D00ADCA0F1AF -:10C5D0000200092863D2DFE800F0126215621A62D5 -:10C5E0001D622000122824D004DC0E281BD0102845 -:10C5F000DBD11BE016281FD01828D6D11FE02078E9 -:10C60000800701E020784007002848DAEEE0207833 -:10C610000007F9E72078C006F6E720788006F3E700 -:10C6200020784006F0E720780006EDE72088C00576 -:10C63000EAE720884005E7E720880005E4E720884E -:10C64000C004E1E72078800729D5032D27D18DF894 -:10C6500000B0B6F8010081E0217849071FD5062D0A -:10C660001DD381B27078012803D0022817D102E0CF -:10C67000C9E0022000E0102004228DF8002072782A -:10C680008DF80420801CB1FBF0F2ADF8062092B2C8 -:10C6900042438A4203D10397ADF80890A6E079E0BF -:10C6A0002078000776D598B282088DF800A0ADF802 -:10C6B0000420B0EB820F6DD10297ADF8061095E023 -:10C6C0002178C90666D5022D64D381B206208DF883 -:10C6D0000000707802285DD3B1FBF0F28DF8040001 -:10C6E000ADF8062092B242438A4253D1ADF8089089 -:10C6F0007BE0207880064DD5072003E020784006B7 -:10C700007FD508208DF80000A088ADF80400ADF8B2 -:10C710000620ADF8081068E02078000671D50920E1 -:10C72000ADF804208DF80000ADF8061002975DE02A -:10C730002188C90565D5022D63D381B20A208DF801 -:10C740000000707804285CD3C6E72088400558D5DF -:10C75000012D56D10B208DF80000A088ADF8040003 -:10C7600044E021E026E016E0FFE72088000548D5F8 -:10C77000052D46D30C208DF80000A088ADF80400EC -:10C78000B6F803006D1FADF80850ADF80600ADF81F -:10C790000AA02AE035E02088C00432D5012D30D12E -:10C7A0000D208DF8000021E02088800429D4B6F8FF -:10C7B0000100E080A07B000723D5032D21D3307832 -:10C7C00000F03F001B2818D00F208DF800002088B3 -:10C7D00040F40050A4F80000B6F80100ADF80400E1 -:10C7E000ED1EADF80650ADF808B003976946059800 -:10C7F000F5F792FB050008D016E00E208DF800003A -:10C80000EAE7072510E008250EE0307800F03F0049 -:10C810001B2809D01D2807D0022005990BF04EF9DE -:10C82000208800F400502080A07B400708D52046D7 -:10C83000FFF70BFDC00703D1A07B20F00400A0731D -:10C84000284685E61FB5022806D101208DF8000094 -:10C8500088B26946F5F760FB1FBD0000F8B51D46BC -:10C86000DDE906470E000AD007F063FC2346FF1DF2 -:10C87000BCB231462A46009407F071F8F8BDD019D1 -:10C880002246194601F0D9FD2046F8BD2DE9FF4F9B -:10C890008DB09B46DDE91B57DDF87CA00C46082BCC -:10C8A00005D0E06901F0FEF850B11020D2E02888F0 -:10C8B000092140F0100028808AF80010022617E0B5 -:10C8C000E16901208871E2694FF420519180E169AA -:10C8D0008872E06942F601010181E06900218173FB -:10C8E0002888112140F0200028808AF800100426B2 -:10C8F00038780A900A2038704FF0020904F11800C5 -:10C900004D460C9001F0C6FBB04681E0BBF1100F24 -:10C910000ED1022D0CD0A9EB0800801C80B20221A0 -:10C92000CDE9001005AB52461E990D98FFF796FF12 -:10C93000BDF816101A98814203D9F74800790F9074 -:10C9400004E003D10A9808B138702FE04FF00201DB -:10C95000CDE900190DF1160352461E990D98FFF707 -:10C960007DFF1D980088401B801B83B2C6F1FF002D -:10C97000984200D203461E990BA8D9B15FF000027D -:10C98000DDF878C0CDE9032009EB060189B2CDE9D5 -:10C9900001C10F980090BDF8161000220D9801F00B -:10C9A0000EFC387070B1C0B2832807D0BDF81600F5 -:10C9B00020833AE00AEB09018A19E1E7022011B06D -:10C9C000BDE8F08FBDF82C00811901F0FF08022DA1 -:10C9D0000DD09AF80120424506D1BDF820108142C1 -:10C9E00007D0B8F1FF0F04D09AF801801FE08AF851 -:10C9F0000180C94800680178052902D1BDF81610E8 -:10CA0000818009EB08001FFA80F905EB080085B268 -:10CA1000DDE90C1005AB0F9A01F03FFB28B91D981A -:10CA20000088411B4145BFF671AF022D13D0BBF109 -:10CA3000100F0CD1A9EB0800801C81B20220CDE9B7 -:10CA4000000105AB52461E990D98FFF707FF1D9890 -:10CA50000580002038700020B1E72DE9F8439C469E -:10CA6000089E13460027B26B9AB3491F8CB2F18F10 -:10CA7000A1F57F45FF3D05D05518AD882944891D96 -:10CA80008DB200E000252919B6F83C8008314145F7 -:10CA900020D82A44BCF8011022F8021BBCF803106D -:10CAA00022F8021B984622F8024B914607F02FFB12 -:10CAB0004FF00C0C41464A462346CDF800C006F024 -:10CAC0001AFFF587B16B00202944A41D214408807A -:10CAD00003E001E0092700E083273846BDE8F8833A -:10CAE00010B50B88848F9C420CD9846BE0180488A5 -:10CAF00044B1848824F40044A41D23440B801060B6 -:10CB0000002010BD0A2010BD2DE9F0478AB0002595 -:10CB1000904689468246ADF8185007274BE00598A5 -:10CB200006888088000446D4A8F8006007A801950C -:10CB300000970295CDE903504FF40073002231466F -:10CB4000504601F03CFB04003CD1BDF81800ADF8A4 -:10CB50002000059804888188B44216D10A0414D4B0 -:10CB600001950295039521F400410097049541F445 -:10CB7000804342882146504601F0BFF804000BD1A3 -:10CB80000598818841F40041818005AA08A948469A -:10CB9000FFF7A6FF0400DCD00097059802950195E9 -:10CBA000039504950188BDF81C300022504601F021 -:10CBB000A4F80A2C06D105AA06A94846FFF790FF5B -:10CBC0000400ACD0ADF8185004E00598818821F439 -:10CBD0000041818005AA06A94846FFF781FF002889 -:10CBE000F3D00A2C03D020460AB0BDE8F08700201D -:10CBF000FAE710B50C46896B86B051B10C218DF85F -:10CC00000010A18FADF80810A16B01916946FAF7E9 -:10CC100001FB00204FF6FF71A063E187A08706B0FB -:10CC200010BD2DE9F0410D460746896B0020069E98 -:10CC30001446002911D0012B0FD13246294638461F -:10CC4000FFF762FF002808D1002C06D032462946A3 -:10CC50003846BDE8F04100F034BFBDE8F0812DE971 -:10CC6000FC411446DDE9087C0E46DDE90A15521D3B -:10CC7000BCF800E092B2964502D20720BDE8FC81E4 -:10CC8000ACF8002017222A70A5F80160A5F803303F -:10CC90000522CDE900423B462A46FFF7DFFD002092 -:10CCA000ECE770B50C46154648220021204601F0FD -:10CCB000EEFB04F1080044F81C0F00204FF6FF7152 -:10CCC000E06161842084A5841720E08494F82A0020 -:10CCD00040F00A0084F82A0070BD4FF6FF720A8007 -:10CCE000014603200AF0EABE30B585B00C46054681 -:10CCF000FFF77FFFA18E284629B101218DF8001092 -:10CD00006946FAF787FA0020E0622063606305B0A5 -:10CD100030BDB0F8400070476000002090F8462019 -:10CD2000920703D4408808800020F4E70620F2E749 -:10CD300090F846209207EED5A0F84410EBE70146A4 -:10CD4000002009880A0700D5012011F0F00F01D05A -:10CD500040F00200CA0501D540F004008A0501D563 -:10CD600040F008004A0501D540F010000905D2D571 -:10CD700040F02000CFE700B5034690F84600C0071A -:10CD800001D0062000BDA3F842101846FFF7D7FFD8 -:10CD900010F03E0F05D093F8460040F0040083F8F1 -:10CDA000460013F8460F40F001001870002000BD47 -:10CDB00090F84620520700D511B1B0F84200AAE71A -:10CDC0001720A8E710F8462F61F3C3020270A2E70C -:10CDD0002DE9FF4F9BB00E00DDE92B34DDE929780A -:10CDE000289D24D02878C10703D000F03F001928DF -:10CDF00001D9012100E000212046FFF7D9FFB04210 -:10CE000015D32878410600F03F010CD41E290CD020 -:10CE1000218811F47F6F0AD13A8842B1A1F57F428F -:10CE2000FF3A04D001E0122901D1000602D5042006 -:10CE30001FB0C5E5FA491D984FF0000A08718DF83A -:10CE400018A08DF83CA00FAA0A60ADF81CA0ADF8A0 -:10CE500050A02978994601F03F02701F5B1C04F135 -:10CE6000180C4FF0060E4FF0040BCDF858C01F2AD7 -:10CE70007ED2DFE802F07D7D107D267DAC7DF47DE5 -:10CE8000F37DF27DF17DF47DF07D7D7DEF7DEE7DA6 -:10CE90007D7D7D7DED0094F84610B5F80100890791 -:10CEA00001D5032E02D08DF818B01EE34FF40061B7 -:10CEB000ADF85010608003218DF83C10ADF84000B3 -:10CEC000D4E2052EEFD1B5F801002083ADF81C00A7 -:10CED000B5F80310618308B1884201D9012079E1D6 -:10CEE0000020A07220814FF6FF702084169801F078 -:10CEF000D1F8052089F800000220029083460AAB91 -:10CF00001D9A16991B9801F0C8F890BB9DF82E0049 -:10CF1000012804D0022089F80100102003E001203C -:10CF200089F8010002200590002203A90BA808F04F -:10CF30006AFDE8BB9DF80C00059981423DD1398816 -:10CF4000801CA1EB0B01814237DB02990220CDE965 -:10CF500000010DF12A034A4641461B98FFF77EFC6B -:10CF600002980BF1020B801C81B217AA029101E01A -:10CF70009CE228E003A90BA808F045FD02999DF862 -:10CF80000C00CDE9000117AB4A4641461B98FFF75C -:10CF900065FC9DF80C000AAB0BEB00011FFA81FB4E -:10CFA00002991D9A084480B2029016991B9800E0DD -:10CFB00003E001F072F80028B6D0BBF1020F02D0F6 -:10CFC000A7F800B04FE20A208DF818004BE20021CC -:10CFD0000391072EFFF467AFB5F801002083ADF889 -:10CFE0001C00B5F80320628300283FF477AF90421D -:10CFF0003FF674AF0120A072B5F805002081002033 -:10D00000A073E06900F04EFD78B9E16901208871F4 -:10D01000E2694FF420519180E1698872E16942F63A -:10D0200001000881E06900218173F01F20841E98AF -:10D03000606207206084169801F02CF8072089F8B8 -:10D0400000000120049002900020ADF82A0028E0A2 -:10D0500019E29FE135E1E5E012E2A8E080E043E07B -:10D060000298012814D0E0698079012803D1BDF825 -:10D070002800ADF80E00049803ABCDE900B04A4695 -:10D0800041461B98FFF7EAFB0498001D80B204900C -:10D09000BDF82A00ADF80C00ADF80E00059880B27E -:10D0A00002900AAB1D9A16991B9800F0F6FF28B95A -:10D0B00002983988001D05908142D1D2029801283A -:10D0C00081D0E0698079012803D1BDF82800ADF84E -:10D0D0000E00049803ABCDE900B04A4641461B98C8 -:10D0E000FFF7BCFB0298BDE1072E02D0152E7FF49E -:10D0F000DAAEB5F801102183ADF81C10B5F80320A5 -:10D10000628300293FF4EAAE91423FF6E7AE012187 -:10D11000A1724FF0000BA4F808B084F80EB0052EF1 -:10D1200007D0C0B2691DE26908F06BFC00287FF4EB -:10D130004AAF4FF6FF70208401A906AA14A8CDF8C3 -:10D1400000B081E885032878214600F03F031D9A4E -:10D150001B98FFF79BFB8246208BADF81C0082E1F9 -:10D160000120032EC3D14021ADF85010B5F80110B5 -:10D170002183ADF81C100AAAB8F1000F00D00023DB -:10D18000CDE9020304921D98CDF804800090388800 -:10D190000022401E83B21B9801F011F88DF8180090 -:10D1A00090BB0B2089F80000BDF8280035E04FF057 -:10D1B000010C052E9BD18020ADF85000B5F8011070 -:10D1C0002183B5F803002084ADF81C10B0F5007F72 -:10D1D00003D907208DF8180087E140F47C422284AF -:10D1E0000CA8B8F1000F00D00023CDE90330CDE941 -:10D1F000018C1D9800903888401E83B21B9800F067 -:10D20000DEFF8DF8180018B18328A8D10220BFE0F6 -:10D210000D2189F80010BDF83000401C22E100000B -:10D2200060000020032E04D248067FF53CAE0020AB -:10D2300018E1B5F80110ADF81C102878400602D5A9 -:10D240008DF83CE002E007208DF83C004FF000082C -:10D250000320CDE902081E9BCDF810801D98019394 -:10D26000A6F1030B00901FFA8BF342461B9800F0C7 -:10D2700044FD8DF818008DF83C80297849060DD5BD -:10D280002088C00506D5208BBDF81C10884201D12E -:10D29000C4F8248040468DF81880E3E0832801D14B -:10D2A0004FF0020A4FF48070ADF85000BDF81C003A -:10D2B0002083A4F820B01E986062032060841321AC -:10D2C000CDE0052EFFF4EFADB5F80110ADF81C1060 -:10D2D000A28F6AB3A2F57F43FE3B29D008228DF8C6 -:10D2E0003C2000BF4FF0000B0523CDE9023BDDF8E9 -:10D2F00078C0CDF810B01D9A80B2CDF804C040F4CB -:10D3000000430092B5F803201B9800F0F6FC8DF85E -:10D310003CB04FF400718DF81800ADF85010832820 -:10D3200010D0F8B1A18FA1F57F40FE3807D0DCE026 -:10D330000B228DF83C204FF6FE72A287D2E7A4F8AC -:10D340003CB0D2E000942B4631461E9A1B98FFF762 -:10D3500084FB8DF8180008B183284BD1BDF81C0060 -:10D36000208353E700942B4631461E9A1B98FFF703 -:10D3700074FB8DF81800E8BBE18FA06B0844831D97 -:10D380008DE888034388828801881B98FFF767FC33 -:10D39000824668E095F80180022E70D15FEA0800AD -:10D3A00002D0B8F1010F6AD109208DF83C0007A81E -:10D3B00000908DF840804346002221461B98FFF7DD -:10D3C00030FC8DF842004FF0000B8DF843B050B99F -:10D3D000B8F1010F12D0B8F1000F04D1A18FA1F55F -:10D3E0007F40FF380AD0A08F40B18DF83CB04FF499 -:10D3F000806000E037E0ADF850000DE00FA91B9809 -:10D40000F9F708FF82468DF83CB04FF48060ADF824 -:10D410005000BAF1020F06D0FC480068C07928B16C -:10D420008DF8180027E0A4F8188044E0BAF1000F46 -:10D4300003D081208DF818003DE007A800904346F6 -:10D44000012221461B98FFF7ECFB8DF818002146BE -:10D450001B98FFF7CEFB9DF8180020B9192189F819 -:10D460000010012038809DF83C0020B10FA91B98C6 -:10D47000F9F7D0FE8246BAF1000F33D01BE018E076 -:10D480008DF818E031E02078000712D5012E10D178 -:10D490000A208DF83C00E088ADF8400003201B997D -:10D4A0000AF00CFB0820ADF85000C0E648067FF5F6 -:10D4B000FAAC4FF0040A2088BDF8501008432080D1 -:10D4C000BDF8500080050BD5A18FA1F57F40FE3837 -:10D4D00006D11E98E06228982063A6864FF0030AC2 -:10D4E0005046A5E49DF8180078B1012089F80000A5 -:10D4F000297889F80110BDF81C10A9F802109DF8D0 -:10D50000181089F80410052038802088BDF85010C4 -:10D5100088432080E4E72DE9FF4F8846087895B0DE -:10D52000012181404FF20900249C0140ADF82010F8 -:10D530002088DDF88890A0F57F424FF0000AFF3A7E -:10D5400006D039B1000705D5012019B0BDE8F08F2C -:10D550000820FAE7239E4FF0000B0EA886F800B0D3 -:10D5600018995D460988ADF83410A8498DF81CB0AB -:10D57000179A0A718DF838B0086098F800000128F1 -:10D580003BD0022809D003286FD1307820F03F002B -:10D590001D303070B8F80400E08098F800100320C7 -:10D5A000022904D1317821F03F011B31317094F808 -:10D5B0004610090759D505ABB9F1000F13D000216A -:10D5C00002AA82E80B000720CDE90009BDF834006B -:10D5D000B8F80410C01E83B20022159800F0EFFDC9 -:10D5E0000028D1D101E0F11CEAE7B8F80400A6F860 -:10D5F0000100BDF81400C01C04E198F805108DF876 -:10D600001C1098F80400012806D04FF4007A022874 -:10D610002CD00328B8D16CE12188B8F8080011F4A7 -:10D620000061ADF8201020D017281CD3B4F84010AA -:10D63000814218D3B4F84410172901D3814212D182 -:10D64000317821F03F01C91C3170A6F80100032197 -:10D65000ADF83410A4F8440094F8460020F002001D -:10D6600084F8460065E105257EE177E1208808F130 -:10D67000080700F4FE60ADF8200010F0F00F1BD09A -:10D6800010F0C00F03D03888228B9042EBD199B9AB -:10D69000B878C00710D0B9680720CDE902B1CDF83D -:10D6A00004B00090CDF810B0FB88BA88398815987E -:10D6B00000F023FB0028D6D12398BDF82010401C91 -:10D6C00080294ED006DC10290DD020290BD040290E -:10D6D00087D124E0B1F5807F6ED051457ED0B1F581 -:10D6E000806F97D1DEE0C80601D5082000E0102049 -:10D6F00082460DA907AA0520CDE902218DF8380040 -:10D70000ADF83CB0CDE9049608A93888CDE9000110 -:10D710005346072221461598FFF7B8F8A8E09DF870 -:10D720001C2001214FF00A0A002A9BD105ABB9F158 -:10D73000000F00D00020CDE902100720CDE900093C -:10D74000BDF834000493401E83B2218B002215984B -:10D7500000F035FD8DF81C000B203070BDF8140072 -:10D7600020E09DF81C2001214FF00C0A002A22D154 -:10D7700013ABB9F1000F00D00020CDE90210072053 -:10D78000CDE900090493BDF83400228C401E83B219 -:10D79000218B159800F013FD8DF81C000D203070C2 -:10D7A000BDF84C00401CADF8340005208DF8380061 -:10D7B000208BADF83C00BCE03888218B88427FF498 -:10D7C00052AF9DF81C004FF0120A00281CD1606A6D -:10D7D000A8B1B878C0073FF446AF00E018E0BA68D7 -:10D7E0000720CDE902B2CDF804B00090CDF810B01A -:10D7F000FB88BA88159800F080FA8DF81C00132079 -:10D8000030700120ADF8340093E00000600000208B -:10D810003988208B8142D2D19DF81C004FF0160A26 -:10D820000028A06B08D0E0B34FF6FF7000215F46E0 -:10D83000ADF808B0019027E068B1B978C907BED14A -:10D84000E18F0DAB0844821D03968DE80C024388DE -:10D850008288018809E0B878C007BCD0BA680DABEF -:10D8600003968DE80C02BB88FA881598FFF7F7F944 -:10D8700005005ED0072D72D076E0019005AA02A9BE -:10D880002046FFF72DF90146E28FBDF808008242DD -:10D8900001D00029F1D0E08FA16B084407800198E6 -:10D8A000E08746E09DF81C004FF0180A40B1208B3D -:10D8B000C8B13888208321461598FFF79AF938E0D7 -:10D8C00004F118000090237E012221461598FFF7ED -:10D8D000A8F98DF81C000028EDD119203070012026 -:10D8E000ADF83400E7E7052521461598FFF781F9E3 -:10D8F0003AE0208800F40070ADF8200050452DD1AA -:10D90000A08FA0F57F41FE3901D006252CE0D8F884 -:10D9100008004FF0160A48B1A063B8F80C10A187B0 -:10D920004FF6FF71E187A0F800B002E04FF6FF70FC -:10D93000A087BDF8200030F47F611AD07823002240 -:10D94000032015990AF010F898F80000207120883B -:10D95000BDF82010084320800EE000E00725208855 -:10D96000BDF8201088432080208810F47F6F1CD0E1 -:10D970003AE02188814321809DF8380020B10EA92A -:10D980001598F9F747FC05469DF81C000028EBD0D8 -:10D9900086F801A001203070208B70809DF81C005B -:10D9A00030710520ADF83400DEE7A18EE1B11898A2 -:10D9B0000DAB0088ADF834002398CDE90304CDE920 -:10D9C0000139206B0090E36A179A1598FFF700FA67 -:10D9D000054601208DF838000EA91598F9F71AFCB4 -:10D9E00000B10546A4F834B094F8460040070AD5C3 -:10D9F0002046FFF7A4F910F03E0F04D114F8460FAB -:10DA000020F0040020701898BDF8341001802846DA -:10DA10009BE500B585B0032806D102208DF80000F3 -:10DA200088B26946F9F7F6FB05B000BD10B5384C71 -:10DA30000B782268012B02D0022B2AD111E0137837 -:10DA40000BB1052B01D10423137023688A889A80B7 -:10DA50002268CB88D38022680B8913814989518140 -:10DA60000DE08B8893802268CB88D38022680B8955 -:10DA700013814B8953818B899381096911612168D5 -:10DA8000F9F7C8FB226800210228117003D0002892 -:10DA900000D0812010BD832010BD806B002800D0F5 -:10DAA000012070478178012909D10088B0F5205FF5 -:10DAB00003D042F60101884201D1002070470720BF -:10DAC0007047F0B587B0002415460E460746ADF8FE -:10DAD000184011E005980088288005980194811D60 -:10DAE000CDE902410721049400918388428801888E -:10DAF000384600F002F930B905AA06A93046FEF70B -:10DB0000EFFF0028E6D00A2800D1002007B0F0BDC2 -:10DB10006000002010B58B7883B102789A4205D15D -:10DB20000B885BB102E08B79091D4BB18B789A426F -:10DB3000F9D1B0F801300C88A342F4D1002010BD17 -:10DB4000812010BD072826D012B1012A27D103E079 -:10DB5000497801F0070102E04978C1F3C2010529C3 -:10DB60001DD2DFE801F00318080C12000AB10320EF -:10DB700070470220704704280DD250B10DE00528EF -:10DB800009D2801E022808D303E0062803D0032808 -:10DB900003D005207047002070470F207047812078 -:10DBA0007047C0B282060BD4000607D5FA48807AC7 -:10DBB0004143C01D01EBD00080B27047084670475A -:10DBC0000020704770B513880B800B781C0625D594 -:10DBD000F14CA47A844204D843F01000087000206D -:10DBE00070BD956800F0070605EBD0052D78F5406F -:10DBF00065F304130B701378D17803F0030341EA43 -:10DC0000032140F20123B1FBF3F503FB15119268E8 -:10DC1000E41D00FB012000EBD40070BD906870BDD6 -:10DC200037B51446BDF804101180117841F0040195 -:10DC300011709DF804100A061ED5D74AA368C1F3D7 -:10DC40000011927A824208D8FE2811D1D21DD20842 -:10DC50004942184600F01BFC0AE003EBD00200F03A -:10DC60000703012510789D40A84399400843107090 -:10DC7000207820F0100020703EBD2DE9F0410746CD -:10DC8000C81C0E4620F00300B04202D08620BDE83A -:10DC9000F081C14D002034462E60AF802881AA72E9 -:10DCA000E8801AE0E988491CE980810614D4E1780B -:10DCB00000F0030041EA002040F20121B0FBF1F244 -:10DCC00001FB12012068FFF76CFF2989084480B22C -:10DCD0002881381A3044A0600C3420784107E1D400 -:10DCE0000020D4E7AC4801220189C08800EB400045 -:10DCF00002EB8000084480B270472DE9FF4F89B0E5 -:10DD00001646DDE9168A0F46994623F44045084633 -:10DD100000F054FB040002D02078400703D4012017 -:10DD20000DB0BDE8F08F099806F086F802902078D3 -:10DD3000000606D59848817A0298814201D887204A -:10DD4000EEE7224601A90298FFF73CFF8346002038 -:10DD50008DF80C004046B8F1070F1AD00122214679 -:10DD6000FFF7F0FE0028DBD12078400611D5022015 -:10DD70008DF80C00ADF81070BDF80400ADF812007D -:10DD8000ADF814601898ADF81650CDF81CA0ADF899 -:10DD900018005FEA094004D500252E46A846012751 -:10DDA0000CE02178E07801F0030140EA012040F224 -:10DDB0000121B0FBF1F2804601FB12875FEA494086 -:10DDC00009D5B84507D1A178207901F0030140EACF -:10DDD0000120B04201D3BE4201D90720A0E7A81913 -:10DDE0001FFA80F9B94501D90D2099E79DF80C007B -:10DDF00028B103A90998F9F70BFA002890D1B84582 -:10DE000007D1A0784FEA192161F30100A07084F8CE -:10DE100004901A9800B10580199850EA0A0027D09A -:10DE2000199830B10BEB06002A46199900F005FB52 -:10DE30000EE00BEB06085746189E099806F067F9A6 -:10DE40002B46F61DB5B239464246009505F053FD06 -:10DE5000224601A90298FFF7B5FE9DF8040022466C -:10DE600020F010008DF80400DDE90110FFF7D8FE66 -:10DE7000002055E72DE9FF4FDFF81C91824685B061 -:10DE8000B9F80610D9F8000001EB410100EB81045C -:10DE900040F20120B2FBF0F1174600FB1175DDE9FD -:10DEA000138B4E4629460698FFF77BFE0346FFF785 -:10DEB00019FF1844B1880C30884202D9842009B077 -:10DEC0002FE70698C6B2300603D5B00601D5062066 -:10DED000F5E7B9F80620521C92B2A9F80620BBF16A -:10DEE000000F01D0ABF80020B00602D5C4F80880BE -:10DEF0000AE0B9F808201A4492B2A9F80820D9F823 -:10DF00000000891A0844A0602246FE200699FFF707 -:10DF100087FEE77025712078390A61F301002A0A2B -:10DF2000A17840F0040062F30101A17020709AF81A -:10DF300002006071BAF80000E08000252573300609 -:10DF400002D599F80A7000E00127B00601D54FF01C -:10DF500000084E4600244FF007090FE0CDE90258B3 -:10DF60000195CDF800900495F1882046129B089AFF -:10DF7000FFF7C3FE0028A2D1641CE4B2BC42EDD37B -:10DF800000209CE700B5FFF7ADFE03490C308A88FE -:10DF9000904203D9842000BD00060020CA8808688A -:10DFA00002EB420300EB8300521C037823F00403CE -:10DFB0000370CA80002101730846ECE72DE9F047A1 -:10DFC000804600F0FBF9070005D000264446F74DD7 -:10DFD00040F2012916E00120BDE8F087204600F05C -:10DFE000EDF90278C17802F0030241EA0222B2FBA5 -:10DFF000F9F309FB13210068FFF7D3FD3044641CDB -:10E0000086B2A4B2E988601E8142E7DCA8F1010073 -:10E01000E8802889801B288100203870DCE710B553 -:10E02000144631B1491E218005F006FFA070002082 -:10E0300010BD012010BD70B50446DC48C1880368DE -:10E0400001E0401C20802088884207D200EB40027B -:10E0500013EB820202D015786D07F2D580B28842A8 -:10E0600016D2AAB15079A072D08820819178107907 -:10E0700001F0030140EA0120A081A078E11CFFF734 -:10E08000A1FD20612088401C2080E080002070BD20 -:10E090000A2070BD0121018270472DE9FF4F85B034 -:10E0A0004FF6FF798246A3F8009048681E460D4659 -:10E0B00080788DF8060048680088ADF804000020DC -:10E0C0008DF80A00088A0C88A04200D304462C82EE -:10E0D00051E03878400708D4641C288AA4B2401C58 -:10E0E000288208F10100C0B246E0288A401C28823C -:10E0F000781D6968FFF70EFDD8BB3188494501D10D -:10E10000601E30803188A1EB080030806888A04212 -:10E1100038D3B878397900F0030041EA002801A922 -:10E12000781DFFF7F7FC20BB298949452ED0002236 -:10E1300039460798FFF706FDD8B92989414518D116 -:10E14000E9680391B5F80AC0D7F808B05046CDF891 -:10E1500000C005F0DCFFDDF800C05A460CF1070CEA -:10E160001FFA8CFC43460399CDF800C005F08DFBE7 -:10E1700060B1641CA4B200208046204600F01EF965 -:10E180000700A6D1641E2C820A2098E67480787954 -:10E19000B071F888B0803978F87801F0030140EA6E -:10E1A00001207081A6F80C80504605F045FE3A46E5 -:10E1B00006F10801FFF706FD306100207FE62DE93A -:10E1C000FF4F87B081461C469246DDF860B0DDF80F -:10E1D0005480089800F0F2F8050002D02878400733 -:10E1E00002D401200BB09CE5484605F025FE2978B5 -:10E1F000090605D56D49897A814201D88720F1E762 -:10E20000CAF309062A4601A9FFF7DCFC0746149861 -:10E2100007281CD000222946FFF794FC0028E1D1F2 -:10E220002878400613D501208DF808000898ADF82D -:10E230000C00BDF80400ADF80E00ADF81060ADF8AC -:10E24000124002A94846F8F7E3FF0028CAD129780E -:10E25000E87801F0030140EA0121AA78287902F068 -:10E26000030240EA0220564507D0B1F5007F04D9E9 -:10E27000611E814201DD0B20B4E7864201D90720EF -:10E28000B0E7801B85B2A54200D92546BBF1000F3F -:10E2900001D0ABF80050179818B1B9192A4600F010 -:10E2A000CCF8B8F1000F0DD03E4448464446169FC6 -:10E2B00005F03FFF2146FF1DBCB232462B460094BD -:10E2C00005F04DFB00208DE72DE9F04107461D4686 -:10E2D0001646084600F072F8040002D02078400785 -:10E2E00001D40120D3E4384605F0A6FD21780906C3 -:10E2F00005D52E49897A814201D88720C7E4224674 -:10E300003146FFF75FFC65B12178E07801F0030149 -:10E3100040EA0120B0F5007F01D8012000E0002094 -:10E3200028700020B3E42DE9F04107461D4616464B -:10E33000084600F043F8040002D02078400701D4DA -:10E340000120A4E4384605F077FD2178090605D5BB -:10E350001649897A814201D8872098E422463146BD -:10E36000FFF75EFCFF2D14D02178E07801F0030266 -:10E3700040EA022040F20122B0FBF2F302FB13005C -:10E3800015B900F2012080B2E070000A60F30101CB -:10E39000217000207BE410B50C4600F00FF810B19E -:10E3A0000178490704D4012010BD000000060020B8 -:10E3B000C18821804079A0700020F5E70749CA880C -:10E3C000824209D340B1096800EB40006FF00B02B4 -:10E3D00002EB8000084470470020704700060020D0 -:10E3E00070B504460D4621462B460AB9002070BD83 -:10E3F00001E0491C5B1C501E021E03D008781E78E9 -:10E40000B042F6D008781E78801BF0E730B50C4695 -:10E4100001462346051B954206D202E0521E9D5C32 -:10E420008D54002AFAD107E004E01D780D70491CD4 -:10E430005B1C521E002AF8D130BDF0B50E460146D5 -:10E44000334680EA030404F00304B4B906E002B9D9 -:10E45000F0BD13F8017B01F8017B521E01F00307A8 -:10E46000002FF4D10C461D4602E080CD80C4121F5F -:10E47000042AFAD221462B4600BF04E013F8014BD0 -:10E4800001F8014B521E002AF8D100BFE0E7F0B5B9 -:10E490000C460146E6B204E002B9F0BD01F8016B9A -:10E4A000521E01F00307002FF6D10B46E5B245EAF4 -:10E4B000052545EA054501E020C3121F042AFBD2C9 -:10E4C000194602E001F8016B521E002AFAD100BF82 -:10E4D000E3E7000010B509F0A0FDF4F7F9F909F041 -:10E4E000E7FBBDE8104009F0AFBC302834BF012085 -:10E4F00000207047202834BF4FF0A0420C4A01236F -:10E5000000F01F0003FA00F0002914BFC2F80C0548 -:10E51000C2F808057047202834BF4FF0A0410449D5 -:10E5200000F01F00012202FA00F0C1F81805704740 -:10E530000003005070B50346002002466FF02F051F -:10E540000EE09C5CA4F130060A2E02D34FF0FF309F -:10E5500070BD00EB800005EB4000521C2044D2B29D -:10E560008A42EED370BD30B50A230BE0B0FBF3F462 -:10E5700003FB1404B0FBF3F08D183034521E05F881 -:10E58000014CD2B2002AF1D130BD30B500234FF694 -:10E59000FF7510E0040A44EA002084B2C85C6040C1 -:10E5A000C0F30314604005EA00344440E0B25B1C51 -:10E5B00084EA40109BB29342ECD330BD2DE9F04188 -:10E5C000FE4B0026012793F864501C7893F868C02E -:10E5D000B8B183F89140A3F8921083F8902083F8A3 -:10E5E0008E70BCF1000F0CBF83F8946083F89450D8 -:10E5F000F3488068008805F08AFDBDE8F04105F029 -:10E6000021BA4FF6FF7083F89140A3F8920083F887 -:10E61000902083F88E70BCF1000F14BF83F89450E3 -:10E6200083F89460BDE8F0812DE9F041E44D29685C -:10E6300091F89C200024012A23D091F89620012AE9 -:10E6400030D091F86C301422DC4E0127012B32D0EF -:10E6500091F88E30012B4FD091F8A620012A1CBFD3 -:10E660000020BDE8F08144701F2200F8042B222214 -:10E67000A731FFF7E2FE286880F8A6400120BDE838 -:10E68000F08144701B220270D1F89D204260D1F8C5 -:10E69000A120826091F8A520027381F89C4001209E -:10E6A000BDE8F081447007220270D1F898204260E2 -:10E6B00081F89640E2E78046447000F8042B20225F -:10E6C0006E31FFF7BAFE88F80870286880F86C4051 -:10E6D00090F86E000028D1D1B6F87000A6F8980026 -:10E6E000A868417B86F89A1086F89670008805F035 -:10E6F0000EFD05F0B6F9C1E791F86C30012B0BD097 -:10E70000447017220270D1F890204260B1F8942032 -:10E71000028181F88E40B1E78046447000F8042BF6 -:10E7200020226E31FFF789FE88F80870286880F88B -:10E730006C4090F86E000028A0D1CDE7A04800689A -:10E7400090F86C10002914BFB0F870004FF6FF70FD -:10E75000704770B59A4C06462068002808BFFFDF56 -:10E760000025206845706660002808BFFFDF20682C -:10E77000417800291CBFFFDF70BDCC220021FFF7CC -:10E7800086FE2068FF2101707F2180F83810132158 -:10E790004184282180F86910012180F85C1080F8FC -:10E7A00061500AF0C1F9BDE8704009F0AEBA844981 -:10E7B0000968097881420CBF012000207047804819 -:10E7C000006890F82200C0F3400070477C48006861 -:10E7D00090F8220000F0010070477948006890F836 -:10E7E0002200C0F3001070472DE9F0437448002464 -:10E7F000036893F82400B3F822C0C0F38001C0F38B -:10E800004002114400F001000844CCF3001121B390 -:10E81000BCF1100F02BF6B4931F81000BDE8F08366 -:10E82000BCF1120F18BFBCF1130F0ED0BCF1150FC5 -:10E830001EBFFFDF2046BDE8F0830021624A32F8A8 -:10E84000102010FB0120BDE8F083604A002132F85F -:10E85000102010FB0120BDE8F08393F85E2093F8B0 -:10E860005F102E264FF47A774FF014084FF04009CE -:10E87000022A04BF4AF2D745B5FBF7F510D0012AAA -:10E8800004BF4AF22F75B5FBF7F510D04AF62315F1 -:10E89000B5FBF7F5082A08BF4E4613D0042A18D056 -:10E8A0002646082A0ED0042A13D0022A49D004F1A1 -:10E8B0002806042A0FD0082A1CBF4FF01908082286 -:10E8C00004D00AE04FF0140806F5A8764FF0400295 -:10E8D00003E006F5A8764FF0100218FB026212FB67 -:10E8E0000052C0EB00103A4D00EB800005EB8000B9 -:10E8F00010441CF0010F4FF4C8724FF4BF7504BFF1 -:10E90000CCF34006002E65D0CCF3400600F5A57090 -:10E91000EEB1082904BF174640260CD0042904BFD5 -:10E920002F46102607D0022907BF04F11807042636 -:10E9300004F12807082606EB860808EB86163E44F5 -:10E940001BE004F118064FF019080422C5E7082956 -:10E9500004BF164640270CD0042904BF2E461027BA -:10E9600007D0022907BF04F11806042704F128067E -:10E97000082707EB871706EB8706304400F19C0653 -:10E9800093F8690001F00C07002F08BF0020304405 -:10E9900018BF00F5416027D1082904BF164640275B -:10E9A0001BD0042904BF2E46102716D0022906BF0B -:10E9B00004F11806042704F128060CE00C060020D8 -:10E9C00068000020DC610200E4610200D461020002 -:10E9D000D4FEFFFF64E018BF0827C7EBC70707EBAB -:10E9E000470706EB4706304498301CF0010F17D05C -:10E9F000082908BF40210CD0042904BF2A46102151 -:10EA000007D0022907BF04F11802042104F12802EB -:10EA1000082101EB410303EB0111114408443BE0E1 -:10EA2000082904BF944640260CD0042904BFAC46F4 -:10EA3000102607D0022907BF04F1180C042604F1A0 -:10EA4000280C082606EB8616B3F840300CEB860C33 -:10EA50006044EB2B20D944F2552C0B3303FB0CF311 -:10EA60009B0D082907D0042902D0022905D008E00F -:10EA70002A46102108E0402106E004F11802042192 -:10EA800002E004F12802082101EB811102EB81016F -:10EA900001F5A57103FB010000F5B470BDE8F0833A -:10EAA00000F5A570082904BF944640260CD004291F -:10EAB00004BFAC46102607D0022907BF04F1180C8A -:10EAC000042604F1280C082606EB8616B3F8483015 -:10EAD0000CEB860C6044EB2BDED944F2552C0B3347 -:10EAE00003FB0CF39B0D0829C5D00429C0D00229D3 -:10EAF000C7D1C2E7FE4840F271210068806A4843EE -:10EB00007047FB48006890F83700002818BF0120C4 -:10EB1000704710B5F74C207B022818BF032808D196 -:10EB2000207D04F115010EF0E6FE08281CBF01202F -:10EB300010BD207B002816BF022800200120BDE860 -:10EB400010400AF021BDEB4908737047E849096895 -:10EB500081F8300070472DE9F047E54C2168087BCB -:10EB6000002816BF022800200120487301F10E0181 -:10EB70000AF0F4FC2168087B022816BF0328012252 -:10EB8000002281F82F204FF0080081F82D00487BEB -:10EB900001F10E034FF001064FF00007012804BFFA -:10EBA0005B7913F0C00F0AD001F10E03012804D1E4 -:10EBB000587900F0C000402801D0002000E001207A -:10EBC00081F82E00002A04BF91F8220010F0040FF3 -:10EBD00007D0087D01F115010EF08DFE216881F846 -:10EBE0002D002068476007F0BFFA2168C14D4FF043 -:10EBF0000009886095F82D000EF089FE804695F892 -:10EC00002F00002818BFB8F1000F04D095F82D0090 -:10EC10000EF0B1FC68B195F8300000281CBF95F8E3 -:10EC20002E0000281DD0697B05F10E0001290ED0B1 -:10EC300012E06E734A4605F10E0140460AF0E4FC0C -:10EC400095F82D1005F10E000EF063FF09E04079F4 -:10EC500000F0C000402831D0394605F10E000AF01E -:10EC60000BFD2068C77690F8220010F0040F08BF53 -:10EC7000BDE8F087002795F82D000EF017FD050080 -:10EC800008BFBDE8F087102102F0C2F8002818BFC5 -:10EC9000BDE8F08720683A4600F11C01C676284698 -:10ECA0000AF0B2FC206800F11C0160680FF08EF8D9 -:10ECB0006068BDE8F04701210FF0A3B80EF066FFD1 -:10ECC0004A4605F10E010AF09FFCCAE7884A12681D -:10ECD000137B0370D2F80E000860508A888070475A -:10ECE00078B584490446824E407B087332682078A8 -:10ECF00010706088ADF8000080B200F00101C0F330 -:10ED0000400341EA4301C0F3800341EA8301C0F3B9 -:10ED1000C00341EAC301C0F3001341EA0311C0F389 -:10ED2000401341EA4311C0F3801041EA801050843F -:10ED3000E07D012808BF012507D0022808BF022571 -:10ED400003D0032814BFFFDF0825306880F85E5029 -:10ED5000607E012808BF012507D0022808BF0225D0 -:10ED600003D0032814BFFFDF0825316881F85F5006 -:10ED700091F83500012829D0207B81F82400488CA7 -:10ED80001D280CBF002060688862607D81F8370014 -:10ED9000A07B002816BF0228002001200875D4F8A7 -:10EDA0000F00C1F81500B4F81300A1F81900A07EF7 -:10EDB00091F86B2060F3071281F86B20E07E012848 -:10EDC00018BF002081F83400002078BD91F85E2043 -:10EDD0000420082A08BF81F85E00082D08BF81F8CA -:10EDE0005F00C9E742480068408CC0F3001131B1B0 -:10EDF000C0F38000002804BF1F20704702E0C0F36A -:10EE0000400109B10020704710F0010F14BFEE203F -:10EE1000FF20704736480068408CC0F3001119B1DC -:10EE2000C0F3800028B102E0C0F3400008B1002028 -:10EE30007047012070472E49002209684A664B8CB2 -:10EE40001D2B0CBF81F8682081F8680070470023F3 -:10EE5000274A126882F85D30D164A2F85000012080 -:10EE600082F85D007047224A0023126882F85C3005 -:10EE7000A2F858000120516582F85C0070471C49D7 -:10EE8000096881F8360070471949096881F86100FE -:10EE900070471748006890F961007047144800688F -:10EEA00090F82200C0F3401070471148006890F8B5 -:10EEB0002200C0F3C0007047012070470C48006872 -:10EEC00090F85F00704770B509F018FE09F0F7FD83 -:10EED00009F0C0FC09F06CFD054C2068416E491C2E -:10EEE000416690F83300002558B109F01DFE03E09B -:10EEF000680000200C06002008F007FF206880F85A -:10EF000033502068457090F8391021B1BDE8704049 -:10EF100004200AF0AEBF90F86810D9B1406E81426B -:10EF200018D804200AF0A5FF206890F8220010F0FD -:10EF3000010F07D0A06843220188BDE8704001207E -:10EF4000FFF73CBBBDE8704043224FF6FF71002045 -:10EF5000FFF734BBBDE8704000200AF08ABF2DE9FE -:10EF6000F04782B00F468146FE4E4FF000083068F1 -:10EF7000458C15F0030F10D015F0010F05F00200BD -:10EF800005D0002808BF4FF0010806D004E0002893 -:10EF900018BF4FF0020800D1FFDF4FF0000A5446BF -:10EFA00015F0010F05F002000DD080B915F0040F27 -:10EFB0000DD04AF00800002F1CBF40F0010040F0C7 -:10EFC00002044DD09EE010B115F0040F0DD015F0E5 -:10EFD000070F10D015F0010F05F0020043D00028F4 -:10EFE00008BF15F0040F34D04AE0002F18BF4AF0D4 -:10EFF000090444D141E037B14AF00800044615F055 -:10F00000200F1BD07EE0316805F02002B1F84800E7 -:10F01000104308BF4AF0010474D04AF018000446B7 -:10F0200015F0200F6ED191F85E1011F00C0118BF91 -:10F030000121C94361F30000044663E0316891F89F -:10F040005E1011F00C0118BF012161F300000446AD -:10F0500058E04AF00800002F18BF40F0010451D1D9 -:10F0600040F010044EE0002818BF15F0040F07D040 -:10F07000002F18BF4AF00B0444D14AF0180441E0B5 -:10F0800015F0030F3DD115F0040F3AD077B1306879 -:10F090004AF0080490F85E0010F00C0118BF01213E -:10F0A00061F3410415F0200F24D02BE0306805F007 -:10F0B0002002B0F84810114308BF4AF0030421D0E1 -:10F0C0004AF0180415F0200F0AD000BF90F85E0037 -:10F0D00010F00C0018BF0120C04360F3410411E0A0 -:10F0E00090F85E1011F00C0118BF0121C94361F3C3 -:10F0F0000004EBE710F00C0018BF012060F30004DF -:10F1000000E0FFDF15F0400F1CD0CFB93168B1F837 -:10F110004800002804BF488C10F0010F0BD110F0FC -:10F12000020F08BF10F0200F05D115F0010F08BF26 -:10F1300015F0020F04D091F85E0010F00C0F01D111 -:10F1400044F040047068A0F800A0017821F020018C -:10F1500001704FF007010EF005FE414670680EF099 -:10F16000F8FF214670680FF000F814F0010F0CD082 -:10F170004FF006034FF000027B4970680EF0CFFF9E -:10F180003068417B70680EF02FFE14F0020F18D02B -:10F19000D6E90010B9F1000F4FF006034FF001025D -:10F1A00007D01C310EF0BBFF012170680EF029FE64 -:10F1B00007E015310EF0B3FF3068017D70680EF086 -:10F1C00020FE14F0040F18BFFFDF14F0080F19D051 -:10F1D000CDF800A03068BDF800200223B0F86A1016 -:10F1E00061F30B02ADF8002090F86B0003220109D7 -:10F1F0009DF8010061F307108DF801006946706801 -:10F200000EF08DFF012F62D13068B0F84810E1B3E5 -:10F2100090F82200C0F34000B8BB70680EF095FF74 -:10F22000401CC7B23068C7F1FF05B0F84820B0F8FD -:10F230005A10511AA942B8BF0D46AA423BD990F8BC -:10F24000220010F0010F36D144F0100421467068FE -:10F250000EF08BFFF81CC0B2ED1E284482B230685D -:10F26000B0F86A10436EC1F30B0151FA83F190F8C4 -:10F2700060303E4F1944BC460023E1FB07C31B0925 -:10F280006FF0240C03FB0C1100E020E080F860100C -:10F2900090F85F00012101F01FF90090BDF8000017 -:10F2A0009DF80210032340EA01400190042201A9C5 -:10F2B00070680EF034FF3068AAB2416C70680EF0CE -:10F2C00082FF3068B0F85A102944A0F85A1014F0A0 -:10F2D000400F06D0D6E900100123062261310EF05E -:10F2E0001EFF14F0200F18BFFFDF0020002818BFFA -:10F2F000FFDF02B0BDE8F0872DE9F043194C89B07B -:10F300002068002808BFFFDF20684178002944D129 -:10F310000178FF2941D0002680F83160A0F85A60BA -:10F32000867080F83960304609F062FB104802AD03 -:10F3300000F1240191E80E1085E80E10D0E90D10BF -:10F34000CDE9061002A809F041FB08F0BCFF2068D7 -:10F3500090F9610009F090F8064809F093F8064822 -:10F360000CE00000680000201A06002053E4B36E91 -:10F37000C8610200D0610200CD61020009F012FBF9 -:10F38000606809F038FB206890F8240010F0010F45 -:10F3900007D0252009F07EF80AE009B00C20BDE86E -:10F3A000F08310F0020F18BF262069D009F072F820 -:10F3B000206890F85E10252008F043FF206880F850 -:10F3C0002C6009F00FFB206890F85E10002009F017 -:10F3D00028F90F21052008F0F8FF206890F82E107A -:10F3E000002901BF90F82F10002990F8220010F09A -:10F3F000040F74D006F0B8FE0546206829468068E0 -:10F4000007F0AAFBDFF82884074690FBF8F008FB1A -:10F4100010704142284606F08EFB2168886097FBF9 -:10F42000F8F04A68104448600EF062FA014620681D -:10F43000426891426ED8C0E90165FE4D4FF0010867 -:10F4400095F82D000EF063FA814695F82F000127FC -:10F45000002818BFB9F1000F04D095F82D000EF068 -:10F460008AF8A0B195F8300000281CBF95F82E004E -:10F47000002824D0687B05F10E01012815D019E081 -:10F4800010F0040F14BF2720FFDF8FD190E73A461A -:10F490006F7305F10E0148460AF0B6F895F82D1085 -:10F4A00005F10E000EF035FB09E0487900F0C000D0 -:10F4B000402815D0414605F10E000AF0DDF820681D -:10F4C00090F8220010F0040F24D095F82D000EF0D3 -:10F4D000EDF805001ED0102101F09AFC40B119E0B2 -:10F4E0000EF054FB3A4605F10E010AF08DF8E6E7FE -:10F4F00020683A4600F11C01C77628460AF084F8D5 -:10F50000206800F11C0160680EF060FC0121606859 -:10F510000EF077FC2068417B0E3008F038FF206841 -:10F5200090F85C1061B3B0F85810A0F84810416D25 -:10F53000416490F82210C1F30011F1B9B0F86A00EB -:10F540000221C0F30B05ADF80050684607F0B0FF8C -:10F5500028B1BDF80000C0F30B00A84204D1BDF8EB -:10F560000000401CADF800002168BDF80000B1F8B3 -:10F570006A2060F30B02A1F86A20206880F85C60C2 -:10F58000206890F85D1039B1B0F85010A0F8401024 -:10F59000C16CC16380F85D60B0F86A10426EC1F35F -:10F5A0000B0151FA82F190F86020DFF88CC211440F -:10F5B00063460022E1FB0C3212096FF0240302FBC8 -:10F5C000031180F860100EF00CFA032160680EF051 -:10F5D00090FA216881F8330009B00020BDE8F0837B -:10F5E0009649886070472DE9F043944C83B02268B7 -:10F5F00092F831303BB1508C1D2808BFFFDF03B0BB -:10F60000BDE8F0435FE401260027F1B1054692F81A -:10F61000600008F03FFF206890F85F10FF2008F0BE -:10F6200010FE20684FF4A57190F85F20002009F0CB -:10F63000D4F8206890F8221011F0030F00F02C810C -:10F64000002D00F0238100F027B992F822108046A7 -:10F65000D07EC1F30011002956D0054660680780AE -:10F66000017821F020010170518C132937D01FDC63 -:10F67000102908BF022144D0122908BF062140D01A -:10F68000FFDF6C4D606805F10E010EF091FB697BA8 -:10F6900060680EF0A9FB2068418C1D2918BF152950 -:10F6A00063D0B0F84820416C60680EF0B6FB5CE0B7 -:10F6B000152918BF1D29E3D14FF001010EF052FBAF -:10F6C0006068017841F020010170216885B11C312A -:10F6D0000EF07CFB012160680EF093FBD1E7002166 -:10F6E0000EF040FB6068017841F020010170C8E72E -:10F6F00015310EF06BFB2068017D60680EF081FB18 -:10F70000BFE70EF02FFBBCE70021FFF728FC606885 -:10F71000C17811F03F0F28D0017911F0100F24D0DB -:10F720000EF01EFB2368024693F82410C1F38000FC -:10F73000C1F3400C604401F00101084493F82C101F -:10F74000C1F3800CC1F34005AC4401F001016144F8 -:10F75000401AC1B293F85E0000F0BEFE0090032391 -:10F760000422694660680EF0DAFC2068002590F8F3 -:10F77000241090F82C0021EA000212F0010F18BFAB -:10F7800001250ED111F0020F04D010F0020F08BFB6 -:10F79000022506D011F0040F03D010F0040F08BFAB -:10F7A0000425B8F1000F2BD0012D1BD0022D08BF6E -:10F7B00026201BD0042D14BFFFDF272016D0206881 -:10F7C00090F85E10252008F03CFD206890F822108B -:10F7D000C1F3001169B101224FF49671002008F0C5 -:10F7E000FCFF0DE0252008F055FEE8E708F052FE8A -:10F7F000E5E790F85E204FF49671002008F0EDFFE9 -:10F80000206890F82C10294380F82C1090F82420C0 -:10F8100032EA01011CD04670418C13292BD026DC22 -:10F82000102904BF03B0BDE8F083122923D007E0FC -:10F8300040420F000C06002053E4B36E6800002025 -:10F84000C1F30010002818BFFFDF03B0BDE8F0834C -:10F85000418C1D2908BF80F82C70DCD0C1F3001149 -:10F86000002914BF80F8316080F83170D3E7152982 -:10F8700018BF1D29DBD190F85E2003B04FF00101C5 -:10F88000BDE8F043084609F094B900BF90F85F2046 -:10F890000121084609F08DF92168002DC87E7CD031 -:10F8A0004A8C3D46C2F34000002808BF47F00805D7 -:10F8B00012F0400F18BF45F04005002819BFD1F8DD -:10F8C0003C90B1F84080D1F84490B1F8488060682D -:10F8D000072107800EF046FA002160680EF039FC1F -:10F8E000294660680EF041FC15F0080F17D020681B -:10F8F000BDF800100223B0F86A2062F30B01ADF8E6 -:10F90000001090F86B00032201099DF8010061F3DB -:10F9100007108DF80100694660680EF000FC606811 -:10F920000EF0DCFA2168C0F1FE00B1F85A20A8EB15 -:10F9300002018142A8BF0146CFB2D019404544D24E -:10F9400045F0100160680EF010FC60680EF0C6FA19 -:10F950002168C0F1FE00B1F85A10A8EB0101814204 -:10F96000A8BF0146CFB260680EF0EFFB3844421CDE -:10F970002068B0F86A10436EC1F30B0151FA83F1AD -:10F9800090F86030FE4D1944AC460023E1FB05C3FE -:10F990004FEA131C6FF0240300E03CE00CFB031162 -:10F9A00080F8601090F85F00012100F095FD009054 -:10F9B000BDF800009DF80210032340EA01400190C9 -:10F9C000042201A960680EF0AAFB216891F82200C8 -:10F9D00010F0400F05D001230622613160680EF05F -:10F9E0009EFB20683A46B0F85A0000EB09016068B7 -:10F9F0000EF0E9FB2068B0F85A103944A0F85A100C -:10FA000009F0BFFC002818BFFFDF20684670867031 -:10FA100003B0BDE8F0830121FFF7A1FAF0E7D94870 -:10FA200010B50068417841B90078FF2805D0002161 -:10FA30000846FFF7D8FD002010BD09F05FF809F077 -:10FA40003EF808F007FF08F0B3FF0C2010BD2DE9C9 -:10FA5000F041CC4D0446174628680E4690F86C00DD -:10FA6000002818BFFFDF2868002F80F86E7018BFCD -:10FA7000BDE8F0812188A0F870106188A0F8861098 -:10FA8000A188A0F88810E188A0F88A1094F888115D -:10FA900080F88C1090F82F10002749B1427B00F1BC -:10FAA0000E01012A04D1497901F0C001402935D065 -:10FAB00090F8301041B1427B00F10E01012A04BFE1 -:10FAC000497911F0C00F29D000F17A00F3F794FAC8 -:10FAD0006868FF2E0178C1F380116176D0F80310B9 -:10FAE000C4F81A10B0F80700E08328681ED0C0F8E8 -:10FAF0008010E18BA0F8841000F17402511E304692 -:10FB00000DF014FF002808BFFFDF286890F873107D -:10FB100041F0020180F87310BDE8F081D0F80E10BA -:10FB2000C0F87A10418AA0F87E10D1E7C0F8807042 -:10FB3000A0F88470617E80F87310D4F81A104167C1 -:10FB4000E18BA0F87810BDE8F08170B58D4C0125EF -:10FB5000206890F82200C0F3C00038B13C22FF2199 -:10FB6000A068FFF774FF206880F86C50206890F858 -:10FB7000220010F0010F1CBFA06801884FF03C026A -:10FB800012BF01204FF6FF710020FEF717FD20681D -:10FB900080F8395070BD7B49096881F832007047A0 -:10FBA0002DE9F041774C0026206841780127354641 -:10FBB000012906D0022901D003297DD0FFDFBDE84D -:10FBC000F081817802250029418C46D0C1F34002A2 -:10FBD000002A08BF11F0010F6FD090F85F204FF09E -:10FBE00001014FF0000008F0E4FF216891F82200C5 -:10FBF000C0F34000002814BF0C20222091F85F10B1 -:10FC000008F01FFB2068457090F8330058B108F0E9 -:10FC100068F8206890F85F0010F00C0F0CBF4020CF -:10FC2000452008F077FF206890F83400002818BFBE -:10FC300008F08FFF216891F85F0091F8691010F0CB -:10FC40000C0F08BF0021962008F0F6FE09F090FB8B -:10FC5000002818BFFFDFBDE8F081C1F3001282B1B8 -:10FC600010293FD090F8330020B108F03AF8402036 -:10FC700008F050FF206890F8221011F0040F36D0E1 -:10FC800043E090F8242090F82C309A422AD1B0F822 -:10FC90004800002808BF11F0010F05D111F0020F34 -:10FCA00008BF11F0200F6FD04FF001014FF000009E -:10FCB000FFF799FC206801E041E035E0418C11F04C -:10FCC000010F04BFC1F34001002907D1B0F85A1059 -:10FCD000B0F84820914218BFBDE8F08180F831703B -:10FCE000BDE8F081BDE8F041002101207BE490F8FF -:10FCF0003710012914BF0329102646F00E0190F891 -:10FD00005E204FF0000008F054FF206890F83400A7 -:10FD1000002818BF08F01DFF0021962008F08CFE77 -:10FD200020684570BDE8F081B0F85A10B0F848007E -:10FD3000814242D0BDE8F0410121084653E4817878 -:10FD4000D9B1418C11F0010F22D080F86C7090F87D -:10FD50006E20B0F870100120FEF730FC206845706E -:10FD600008F0CCFE08F0ABFE08F074FD08F020FEB1 -:10FD7000BDE8F04103200AF07CB88178012004E05E -:10FD800053E4B36E6800002017E0BDE8F0412AE4B8 -:10FD900011F0020F04BFFFDFBDE8F081B0F85A1088 -:10FDA000B0F84000814208D001210846FFF71BFC53 -:10FDB000216803204870BDE8F081BDE8F041FFF7FD -:10FDC00082B8FFF780B810B5FE4C206890F8341068 -:10FDD00049B1383008F0CCFE18B921687F2081F88D -:10FDE000380008F0ACFE206890F8330018B108F035 -:10FDF0009BFE07F08AFF0AF02EFCA8B1206890F85D -:10FE00002210C1F3001179B14078022818BFFFDF3A -:10FE100000210120FFF7E7FB2068417800291EBF81 -:10FE200040780128FFDF10BDBDE81040FFF74BB858 -:10FE30002DE9F047E34C0F4680462168B8F1030FE7 -:10FE4000488C08BFC0F3400508D000F0010591F8C8 -:10FE50003200002818BF4FF0010901D14FF000090E -:10FE600008F00CFB0646B8F1030F0CBF4FF0020878 -:10FE70004FF0010835EA090008BFBDE8F0872068A7 -:10FE800090F8330068B10DF08FFD38700146FF28FF -:10FE900007D06068C01C0DF060FD38780DF091FD52 -:10FEA000064360680178C1F3801221680B7D9A4295 -:10FEB00008D10622C01C1531FEF792FA002808BFAF -:10FEC000012000D000203978FF2906D0C8B9206869 -:10FED00090F82D00884216D113E0A0B1616811F8A6 -:10FEE000030BC0F380100DF006FD05460DF061FE1A -:10FEF00038B128460DF0DAFB18B1102100F088FF68 -:10FF000008B1012000E00020216891F8221011F0D2 -:10FF1000040F01D0F0B11AE0CEB9AB4890F8370029 -:10FF2000002818BF404515D1616811F8030BC0F3D4 -:10FF300080100DF0E0FC04460DF03BFE38B1204689 -:10FF40000DF0B4FB18B1102100F062FF10B10120D8 -:10FF5000BDE8F0870020BDE8F0872DE9F04F994D0E -:10FF6000044683B0286800264078022818BFFFDFC7 -:10FF700028684FF07F0B90F8341049B1383008F002 -:10FF8000F7FD002804BF286880F838B008F0D7FDD6 -:10FF900068680DF009FF8046002C00F0458208F0EB -:10FFA00010FA002800F04082012400274FF0FF09DA -:10FFB000B8F1050F1ED1686890F8240000F01F000A -:10FFC000102817D9286890F8360098B18DF800905D -:10FFD00069460520FFF72CFF002800F025822868DD -:10FFE00080F8A64069682222A730C91CFEF725FACE -:10FFF00000F01ABA68680EF062F8002800F0148267 -:020000040001F9 -:100000004046DFF8C4814FF0030A062880F02182C1 -:10001000DFE800F0FCFCFC03FCFB8DF80090694677 -:100020000320FFF705FF002800F0F180296891F810 -:10003000340010B191F89C00D8B12868817801296A -:100040004DD06868042107800DF08CFE08F10E0188 -:1000500068680DF0ADFE98F80D1068680DF0C4FEEC -:100060002868B0F84020C16B68680DF0FAFE00F017 -:1000700063B99DF8000081F89C400A7881F89D20C2 -:10008000FF280FD001F19F029E310DF04FFC002898 -:1000900008BFFFDF286890F89E1041F0020180F849 -:1000A0009E100DE068680278C2F3801281F89E20ED -:1000B000D0F80320C1F89F20B0F80700A1F8A300F2 -:1000C000286800F1A50490F838007F2808BFFFDFFA -:1000D000286890F83810217080F838B0ADE790F8B3 -:1000E00022000721C0F3801938480479686869F351 -:1000F000861407800DF036FE002168680EF029F89E -:10010000214668680EF031F80623002208F10E013E -:1001100068680EF004F82868417B68680DF064FE9A -:1001200068680DF0DBFE2968B1F84020C0F1FE01DF -:100130008A42B8BF1146CFB2BA423CD9F81EC7B204 -:1001400044F0100B594668680EF00FF868680DF01F -:10015000FCFF384400F101082868B0F86A10426ECC -:10016000C1F30B0151FA82F190F86020184C0A4457 -:10017000A4460023E2FB04C319096FF0240301FB2A -:10018000032180F8601090F85F004246012100F0E2 -:10019000A3F90190BDF804009DF80610032340EA7E -:1001A00001400290042202A968680DF0B8FF594688 -:1001B00068680DF0DAFFB9F1000F0FD0D5E9001033 -:1001C000012307E0680000200C060020C86102003F -:1001D00053E4B36E062261310DF0A1FF28683A4660 -:1001E000C16B68680DF0EFFF2868A0F85A70B0F88E -:1001F00040108F420CBF0121002180F8311009F01E -:10020000C0F8002818BFFFDF96E007E021E128686A -:100210008078002840F00A8100F006B98DF800903F -:1002200068680178C1F38019D0F803100191B0F823 -:100230000700ADF8080069460520FFF7F9FD002822 -:1002400028687DD0817800297CD090F85FB0D5E90E -:100250000104D0F80F10C4F80E10B0F8131061822A -:10026000417D2175817D6175B0F81710E182B0F88C -:1002700019106180B0F81B10A180B0F81D10E1804A -:1002800000F11F0104F1080015F085FE686890F880 -:10029000241001F01F01217690F82400400984F811 -:1002A000880184F864B084F865B01BF00C0F0CBFB3 -:1002B0000021012104F130000EF0ABF92868002282 -:1002C00090F8691084F8661090F8610084F867006F -:1002D0009DF80010A868FFF7BAFB022009F0C9FDDD -:1002E000B2480DF1040B08210468686807800DF01E -:1002F00039FD002168680DF02CFF214668680DF07B -:1003000034FF0623002208F10E0168680DF007FF94 -:100310002868417B68680DF067FD494668680DF004 -:1003200070FD06230122594668680DF0F8FE09F0B9 -:1003300028F8002818BFFFDF286880F801A077E0C0 -:100340006DE0FFE76868D5F808804FF00109027892 -:1003500098F80D10C2F34012114088F80D10D0F833 -:100360000F10C8F80E10B0F81310A8F81210417D45 -:1003700088F81410817D88F81510B0F81710A8F8C7 -:100380001610B0F81910A8F80210B0F81B10A8F851 -:100390000410B0F81D10A8F8061000F11F0108F1B4 -:1003A000080015F0F8FD686890F8241001F01F01AE -:1003B00088F8181090F824000021400988F8880176 -:1003C00088F8649088F8659008F130000EF021F903 -:1003D0002868002290F8691088F8661090F861008B -:1003E00088F867009DF80010A868FFF730FB2868C0 -:1003F00080F86C4090F86E20B0F870100120FEF785 -:10040000DDF82868477008F079FB08F058FB08F021 -:1004100021FA08F0CDFA012009F02BFD08E090F850 -:100420002200C0F3001008B1012601E0FEF74BFDE9 -:10043000286890F8330018B108F076FB07F065FCE7 -:1004400096B10AF008F960B100210120FFF7CBF85E -:1004500013E0286890F82200C0F300100028E5D0CF -:10046000E2E7FEF730FD08E028688178012904D131 -:1004700090F85F10FF2007F0E4FE2868417800291B -:1004800019BF4178012903B0BDE8F08F40780328F7 -:1004900018BFFFDF03B0BDE8F08F70B5444C0646CF -:1004A0000D462068807858B107F0F2FD21680346B8 -:1004B000304691F85F202946BDE870400AF085BAC1 -:1004C00007F0E6FD21680346304691F85E20294694 -:1004D000BDE870400AF079BA78B50C460021009169 -:1004E000082804BF4FF4C87040210DD0042804BF71 -:1004F0004FF4BF70102107D0022807BF01F1180088 -:10050000042101F128000821521D02FB01062848A0 -:100510009DF80010006890F8602062F3050141F03A -:1005200040058DF8005090F85F00012829D002287E -:100530002ED004281CBF0828FFDF2FD025F0800014 -:100540008DF80000C4EB041000EB80004FF01E019A -:1005500001EB800006FB04041648844228BFFFDF3D -:100560001548A0FB0410BDF80110000960F30C0150 -:10057000ADF80110BDF800009DF8021040EA0140FE -:1005800078BD9DF8020020F0E0008DF80200D5E76C -:100590009DF8020020F0E000203004E09DF8020009 -:1005A00020F0E00040308DF80200C7E7C86102008B -:1005B00068000020C4BF0300898888880023C383A3 -:1005C000428401EBC202521EB2FBF1F1018470477A -:1005D0002DE9F04104460026D9B3552333224FF4C8 -:1005E000FA4501297DD0022900F01481032918BFA2 -:1005F000BDE8F08104F17001207B00F01F00207342 -:1006000084F889605FF0000004EB000C9CF808C0DF -:1006100003EA5C05ACEB050C0CF0FF0C0CF03305A9 -:1006200002EA9C0CAC440D180CEB1C1C0CF00F0CDB -:1006300085F814C04D7E401CAC44C0B281F819C08E -:100640000528E1D30CF0FF00252898BFBDE8F08114 -:10065000DCE0FFE704F17005802200212846FDF769 -:1006600016FFAE71EE712E736E73EE732E746E7193 -:10067000AE76EE76212085F84000492085F84100CD -:10068000FE2085F874002588702200212046FDF7A1 -:10069000FEFE2580012584F8645084F865502820EA -:1006A00084F86600002104F130000DF0B2FF1B2237 -:1006B000A4F84E20A4F85020A4F85220A4F8542006 -:1006C0004FF4A470A4F85600A4F8580065734FF4D2 -:1006D00048606080A4F8F060A4F8F260A4F8F460C8 -:1006E00000E023E0A4F8F660A4F8F86084F8FA606B -:1006F00084F8FD60A4F8066184F80461A4F8186128 -:10070000A4F81A6184F8B66184F8B76184F8C0610E -:1007100084F8C16184F88C6184F88F6184F8A861E1 -:10072000C4F8A061C4F8A461BDE8F081A4F8066132 -:1007300084F8FB606088FE490144B1FBF0F1A4F845 -:1007400090104BF68031A4F89210B4F806C0A4F8CB -:100750009860B4F89C704FEACC0C4743BCFBF0FCAB -:1007600097FBF0F70CF1010CA4F89C701FFA8CFCBD -:100770000CFB00F704F17001A4F89AC0B7F5C84F5C -:10078000C4BFACF1010CA1F82AC0B5FBF0FC0CF120 -:10079000010CA1F830C000F5802C0CF5EE3CACF15A -:1007A0000105B5FBF0FCA1F820C0CD8B05FB00FCDA -:1007B000BCFBF0F0C8830846217B01F01F012173C8 -:1007C0004676002104EB010C9CF808C003EA5C05A6 -:1007D000ACEB050C0CF0FF0C0CF0330502EA9C0CA2 -:1007E000AC4445180CEB1C1C0CF00F0C85F814C025 -:1007F000457E491CAC44C9B280F819C00529E1D333 -:100800000CF0FF00252898BFBDE8F081FFDFBDE8B0 -:10081000F08100BFB4F8B011B4F8B4316288A4F824 -:100820009860B4F89CC0DB000CFB02FCB3FBF1F356 -:100830009CFBF1FC5B1CA4F89CC09BB203FB01FC7D -:1008400004F17000A4F89A30BCF5C84FC4BF5B1E19 -:100850004385B5FBF1F35B1C0386438C01EBC303BB -:100860005B1EB3FBF1F30384C38B5A43B2FBF1F17C -:10087000C183BDE8F0812DE9F04104460025A1B314 -:1008800055234FF4FA464FF0330C01297DD002294D -:1008900000F0E080032918BFBDE8F08104F170008A -:1008A000217B01F01F01217384F889500021621817 -:1008B000127A03EA5205521BD2B202F033050CEA57 -:1008C00092022A44451802EB121202F00F022A7516 -:1008D000457E491C2A44C9B242760529E7D3D0B2E5 -:1008E000252898BFBDE8F081B1E0FFE704F170066C -:1008F000802200213046FDF7CAFDB571F5713573D0 -:100900007573F57335747571B576F576212086F8B3 -:100910004000492086F84100FE2086F874002688B1 -:10092000702200212046FDF7B2FD2680012684F8C2 -:10093000646084F86560282084F86600002104F172 -:1009400030000DF066FE1B22A4F84E20A4F85020C3 -:10095000A4F85220A4F854204FF4A470A4F8560030 -:10096000A4F858006673A4F8F850202084F8FA0020 -:1009700084F8F050C4F8F45084F8245184F82551D8 -:1009800084F82E5184F82F5100E005E084F81451CA -:1009900084F82051BDE8F081618865480844B0FBC7 -:1009A000F1F0A4F890004BF68030A4F89200E288B1 -:1009B000A4F89850B4F89C70D2004F43B2FBF1F207 -:1009C00097FBF1F7521CA4F89C7092B202FB01F75E -:1009D00004F17000A4F89A20B7F5C84FC4BF521EA6 -:1009E0004285B6FBF1F2521C028601F5802202F527 -:1009F000EE32561EB6FBF1F20284C68B06FB01F204 -:100A0000B2FBF1F1C1830146207B00F01F0020738F -:100A10004D7600202218127A03EA5205521BD2B2F8 -:100A200002F033050CEA92022A440D1802EB12126E -:100A300002F00F022A754D7E401C2A44C0B24A764D -:100A40000528E7D3D0B2252898BFBDE8F081FFDFA5 -:100A5000BDE8F081D0F81811628804F1700348896C -:100A6000C989A4F89850B4F89CC0C9000CFB02FCDA -:100A7000B1FBF0F19CFBF0FC491CA4F89CC089B2CE -:100A800001FB00FCA4F89A10BCF5C84FC4BF491E76 -:100A90005985B6FBF0F1491C1986598C00EBC10150 -:100AA000491EB1FBF0F11984D98B5143B1FBF0F031 -:100AB000D883BDE8F0812DE9F003447E0CB1252CEC -:100AC00003D9BDE8F00312207047002A02BF0020BE -:100AD000BDE8F003704791F80DC01F260123154DA6 -:100AE0004FF00008BCF1000F7AD0BCF1010F1EBF1F -:100AF0001F20BDE8F0037047B0F800C00A7C8F7B70 -:100B000091F80F907A404F7C87EA090742EA072262 -:100B100082EA0C0C5FF000070CF0FF0999FAA9F9C2 -:100B20004FEA1C2C4FEA19699CFAACFC04E0000067 -:100B3000FFDB050053E4B36E4FEA1C6C49EA0C2C52 -:100B40000CEB0C1C7F1C9444FFB21FFA8CFC032F8F -:100B5000E2D38CEA020CFB4F0022ECFB0572120977 -:100B60006FF0240502FB05C2D2B201EBD2078276F8 -:100B700002F007053F7A03FA05F52F4218BFC27647 -:100B80007ED104FB0CF2120C521CD2B25FF00004B6 -:100B900000EB040C9CF814C094453CBFA2EB0C0283 -:100BA000D2B212D30D194FF0000C2D7A03FA0CF7C4 -:100BB0003D421CBF521ED2B2002A69D00CF1010C7A -:100BC0000CF0FF0CBCF1080FF0D304F1010C0CF099 -:100BD000FF04052CDCD33046BDE8F0037047FFE787 -:100BE00090F81AC00C7E474604FB02C2D54C4FF069 -:100BF000000CE2FB054C4FEA1C1C6FF024040CFBBC -:100C00000422D2B201EBD204827602F0070C247ADD -:100C100003FA0CFC14EA0C0F1FBFC2764046BDE875 -:100C2000F003704790F819C0B2FBFCF40CFB1422DF -:100C3000521CD2B25FF0000400EB040C9CF814C00C -:100C400094453CBFA2EB0C02D2B212D30D194FF067 -:100C5000000C2D7A03FA0CF815EA080F1CBF521E7F -:100C6000D2B272B10CF1010C0CF0FF0CBCF1080F08 -:100C7000F0D304F1010C0CF0FF04052CDCD3AAE73F -:100C800009E00CEBC401C1763846BDE8F0037047BB -:100C90000CEBC401C1764046BDE8F0037047AA4A98 -:100CA000016812681140A94A126811430160704737 -:100CB00030B4A749A44B00244FF0010C0A78521C11 -:100CC000D2B20A70202A08BF0C700D781A680CFA8C -:100CD00005F52A42F2D0097802680CFA01F1514078 -:100CE000016030BC704770B46FF01F02010C02EA63 -:100CF00090251F23A1F5AA4054381CBFA1F5AA4096 -:100D0000B0F1550009D0A1F52850AA381EBFA1F5B1 -:100D10002A40B0F1AA00012000D100204FF0000CC1 -:100D2000624601248CEA0106F6431643B6F1FF3F02 -:100D300011D005F001064FEA5C0C4CEAC63C03F00A -:100D4000010652086D085B08641C42EAC632162C84 -:100D5000E8DD70BC704770BC0020704790F804C09C -:100D60003CF01F011CBF0020704730B401785522B1 -:100D700002EA5103C91AC9B201F03304332303EA6A -:100D800091012144447801EB111102EA5405641BDE -:100D9000E4B204F0330503EA94042C4404EB141485 -:100DA00001F00F0104F00F040C448178C07802EACE -:100DB0005105491BC9B201F0330503EA91012944E9 -:100DC00001EB111101F00F01214402EA5004001B54 -:100DD000C0B200F0330403EA9000204400EB10108E -:100DE00000F00F00014402EA5C00ACEB0000C0B26E -:100DF00000F0330203EA9000104400EB101000F002 -:100E00000F00084401288CBF0120002030BC70472F -:100E10000A000ED00123012A0BDB491EC9B210F8CB -:100E200001C0BCF1000F01D0002070475B1C934251 -:100E3000F3DD01207047002A08BF70471144401EAF -:100E400012F0010F03D011F8013D00F8013F5208E4 -:100E500008BF704711F8013C437011F8023D00F8DB -:100E6000023F521EF6D1704770B58CB000F11004ED -:100E70001D4616460DF1FF3C5FF0080014F8012CEA -:100E80008CF8012014F8022D0CF8022F401EF5D129 -:100E900001F1100C6C460DF10F0108201CF8012C1B -:100EA0004A701CF8022D01F8022F401EF6D1204690 -:100EB00013F01CFB7EB16A1E04F130005FF00801E4 -:100EC00010F8013C537010F8023D02F8023F491E31 -:100ED000F6D10CB070BD08982860099868600A982F -:100EE000A8600B98E8600CB070BD38B505460C469C -:100EF000684607F03DFE002808BF38BD9DF9002078 -:100F00002272E07E607294F90A100020511A48BFE4 -:100F1000494295F82D308B42C8BF38BDFF2B08BF22 -:100F200038BDE17A491CC9B2E17295F82E30994278 -:100F300003D8A17A7F2918BF38BDA2720020E072C1 -:100F4000012038BD53E4B36E04620200086202005F -:100F5000740000200C2818BF0B2810D00D2818BFD3 -:100F60001F280CD0202818BF212808D0222818BFFD -:100F7000232804D024281EBF2628002070474FF0C5 -:100F8000010070470C2963D2DFE801F006090E1357 -:100F9000161B323C415C484E002A5BD058E0072AC1 -:100FA00018BF082A56D053E00C2A18BF0B2A51D07C -:100FB0004EE00D2A4ED04BE0A2F10F000C2849D98B -:100FC00046E023B1A2F110000B2843D940E0122AD9 -:100FD00018BF112A3ED090F8380020B1122A37D31A -:100FE0001A2A37D934E0162A32D31A2A32D92FE0F6 -:100FF000A2F10F0103292DD990F8380008B31B2A5C -:1010000028D925E0002B08BF042A21D122E013B102 -:10101000062A1FD01CE0012A1AD11BE01C2A1CBF83 -:101020001D2A1E2A16D013E01F2A18BF202A11D00D -:10103000212A18BF222A0DD0232A1CBF242A262A9F -:1010400008D005E013B10E2A04D001E0052A01D032 -:1010500000207047012070472DE9F0410D460446FD -:10106000866805F02FFA58B905F07EF840F236711F -:1010700004F061FDA060204605F024FA0028F3D0BA -:1010800095B13046A16805F067FD00280CDD2844C5 -:10109000401EB0FBF5F707FB05F1304604F04BFDB1 -:1010A000A0603846BDE8F0810020BDE8F08170B551 -:1010B0000446904228BF70BD101B64280BD325182E -:1010C0008D4206D8042105F07AFD00281CBF284671 -:1010D00070BD204670BD6420F1E711F00C0F13D0F5 -:1010E00001F0040100290DBF4022102296214FF487 -:1010F000167101F5BC71A0EB010388428CBF93FB14 -:10110000F2F0002080B27047022919BF6FF00D0184 -:1011100001EBD0006FF00E0101EB9000F2E7084404 -:1011200018449830002A14BF042100210844704755 -:1011300010B4002A14BF4FF429624FF4A472002B9C -:1011400019BF4FF429634FF0080C4FF4A4734FF00C -:10115000010C00280CBF0124002491F866001CF04B -:101160000C0F08BF0020D11808449830002C14BF81 -:1011700004210021084410BC704700280CBF012343 -:10118000002391F86600002BA0F6482000F50050DF -:1011900018BF04231844496A81422CBF0120002053 -:1011A00012F00C0118BF012131EA000014BF002029 -:1011B0000120704710B413680B66137813F00C030A -:1011C00018BF0123527812F00C0218BF012253EA13 -:1011D000020C04BF10BC7047002B0CBF4FF4A4736B -:1011E0004FF42963002A19BF4FF429624FF0080C0D -:1011F0004FF4A4724FF0010C00280CBF012400240E -:1012000091F866001CF00C0F08BF00201A4410442F -:101210009830002C14BF0422002210444A6A8242F3 -:1012200024BF10BC704791F860004FF0030230F00B -:101230000C0381F8603091F8610020F00C0081F817 -:10124000610008BF81F86020002808BF81F8612094 -:1012500010BC704710F0010F1CBF0120704710F048 -:10126000020F1CBF0220704710F0040018BF0820B6 -:1012700070472DE9F0470446174689464FF00108AC -:1012800008460DF0FAF8054648460DF0FAF810F059 -:10129000010F18BF012624D015F0010F18BF01233C -:1012A0002AD000BF56EA030108BF4FF0000810F033 -:1012B000070F08BF002615F0070F08BF002394F89A -:1012C0006400B0420CBF00203046387094F86510BE -:1012D000994208BF00237B70002808BF002B25D14E -:1012E00015E010F0020F18BF0226D5D110F0040F40 -:1012F00014BF08260026CFE715F0020F18BF0223FF -:10130000D0D115F0040F14BF08230023CAE74846C4 -:101310000DF0BDF8B4F87010401A00B247F6FE7137 -:10132000884201DC002801DC4FF0000816B1082ECD -:101330000CD018E094F86400012818BF022812D0DD -:1013400004281EBF0828FFDF032D0CD194F8C0012C -:1013500048B1B4F8C401012894F8640006D0082804 -:1013600001D0082038704046BDE8F087042818BF37 -:101370000420F7D1F5E7012814BF0228704710F0C8 -:101380000C0018BF0420704738B4CBB2C1F3072C4F -:10139000C1B2C0F30724012B07D0022B09D0042BC4 -:1013A00008BFBCF1040F2DD006E0BCF1010F03D142 -:1013B00028E0BCF1020F25D0012906D0022907D070 -:1013C000042908BF042C1DD004E0012C02D119E02F -:1013D000022C17D001EA0C0161F3070204EA0301B1 -:1013E00061F30F22D1B211F0020F18BF022310D007 -:1013F000C2F307218DF8003011F0020F18BF02214F -:101400001BD111E0214003EA0C03194061F30702EC -:10141000E6E711F0010F18BF0123E9D111F0040F25 -:1014200014BF08230023E3E711F0010F18BF0121C7 -:1014300003D111F0040118BF08218DF80110082B09 -:1014400001BF000C012804208DF80000BDF8000049 -:1014500038BC70474FF0000C082902D0042909D08D -:1014600011E001280FD10420907082F803C013808E -:1014700001207047012806D00820907082F803C030 -:1014800013800120704700207047162A10D12A22AD -:101490000C2818BF0D280FD04FF0230C1F280DD09B -:1014A00031B10878012818BF002805D0162805D0CA -:1014B00000207047012070471A70FBE783F800C0D6 -:1014C000F8E7012908D002290BD0042912BF082906 -:1014D00040F6A660704707E0002804BF40F2E240F3 -:1014E000704740F6C410704700B5FFDF40F2E2409D -:1014F00000BD00000178406829B190F82C1190F8E7 -:101500008C0038B901E001F0BDBD19B1042901D04A -:10151000012070470020704770B50C460546062133 -:1015200002F0C4FC606008B1002006E007212846F4 -:1015300002F0BCFC606018B101202070002070BD7A -:10154000022070BD2DE9FC470C4606466946FFF7B0 -:10155000E3FF00287DD19DF8000050B1FDF7EEF8C3 -:10156000B0427CD0214630460AF008FC002873D1F6 -:101570002DE00DF097F9B04271D02146304612F0BF -:10158000B6FA002868D1019D95F8F00022E001200C -:1015900000E00020804695F839004FF0010A4FF036 -:1015A0000009F0B195F83A0080071AD584F8019047 -:1015B00084F800A084F80490E68095F83B1021722E -:1015C000A98F6181E98FA18185F8399044E0019D5F -:1015D00095F82C0170350028DBD1287F0028D8D061 -:1015E000D5E7304602F0A5FD070000D1FFDF384601 -:1015F00001F0B5FF40B184F801900F212170E68021 -:10160000208184F804A027E0304602F080FD070026 -:1016100000D1FFDFB8F1000F21D0384601F0F7FF0D -:10162000B8B19DF8000038B90198D0F81801418888 -:10163000B14201D180F80090304607F00DFF84F8E8 -:1016400001900C21217084F80490E680697F21725A -:1016500000E004E085F81C900120BDE8FC87002034 -:10166000FBE71CB56946FFF757FF00B1FFDF68468F -:1016700001F014FDFE4900208968A1F8F2001CBDAC -:101680002DE9FC4104460E46062002F0B7FB054654 -:10169000072002F0B3FB2844C7B20025A8463E4409 -:1016A00017E02088401C80B22080B04202D3404620 -:1016B000A4F8008080B2B84204D3B04202D2002025 -:1016C000BDE8FC816946FFF727FF0028F8D06D1CB4 -:1016D000EDB2AE42E5D84FF6FF7020801220EFE762 -:1016E00038B54FF6FF70ADF800000DE00621BDF8EB -:1016F000000002F0EDFB04460721BDF8000002F0F7 -:10170000E7FB0CB100B1FFDF00216846FFF7B8FF2F -:101710000028EBD038BD70B507F00CFF0BF034FF9C -:10172000D44C4FF6FF76002526836683D2A0257021 -:1017300001680079A4F14002657042F8421FA11CC3 -:101740001071601C12F0EFFA1B2020814FF4A4717D -:101750006181A081E18107212177617703212174D3 -:10176000042262746082A082A4F13E00E1820570CE -:101770004680BF480C300570A4F11000057046800B -:1017800084F8205070BD70B5B94C16460D466060A7 -:10179000217007F047FEFFF7A3FFFFF7BCFF20789B -:1017A0000FF0BDFFB6480DF0D0F92178606812F057 -:1017B0005FFA20780BF0DCF8284608F0AFFEB0485E -:1017C000FCF7C7FF217860680AF0B2FB3146207849 -:1017D00012F024FDBDE870400BF0D6BE10B5012418 -:1017E0000AB1002010BD21B1012903D000242046F8 -:1017F00010BD02210CF024FDF9E710B50378044672 -:10180000002B406813460A46014609D05FF00100EC -:10181000FFF78EFC6168496A884203D9012010BD38 -:101820000020F5E7002010BD2DE9F04117468A7829 -:101830001E46804642B11546C87838B1044669074D -:1018400006D52AB1012104E00725F5E70724F6E7CC -:101850000021620702D508B1012000E0002001420A -:1018600006D0012211464046FFF7C7FF98B93DE078 -:1018700051B1002201214046FFF7BFFF58B9600770 -:1018800034D50122114620E060B1012200214046FA -:10189000FFF7B3FF10B10920BDE8F081680725D537 -:1018A000012206E068074FEA44700AD5002814DBDD -:1018B000002201214046FFF7A0FFB8B125F0040542 -:1018C00014E0002812DA012200214046FFF795FFBC -:1018D00060B100BF24F0040408E001221146404634 -:1018E000FFF78BFF10B125F00405F3E73D7034706E -:1018F0000020D1E770B58AB0044600886946FFF73A -:101900000BFE002806D1A08830B1012804D002289F -:1019100002D012200AB070BD04AB03AA214668466B -:10192000FFF782FF0500F5D19DF800100120002689 -:101930000029019906D081F8C101019991F80C1292 -:10194000B1BB2DE081F82F01019991F8561139B9F9 -:10195000019991F82E1119B9019991F8971009B1CF -:101960003A2519E00199059681F82E01019A9DF812 -:101970000C0082F83001019B9DF8102083F8312182 -:10198000A388019CA4F832318DF814008DF815203D -:1019900005AA0020FFF70EFC019880F82F6126E0D1 -:1019A000019991F8C01119B9019991F8971009B1ED -:1019B0003A2519E00199059681F8C00101989DF832 -:1019C0000C2080F8C221019B9DF8100083F8C30110 -:1019D000A388019CA4F8C4318DF814208DF815005B -:1019E00005AA0120FFF7E6FB019880F8C1612846AF -:1019F00090E710B504460020A17801B90120E278F3 -:101A00000AB940F0020001F058FB002803D120463B -:101A1000BDE810406EE710BD70B5044691F8650052 -:101A200091F866300D4610F00C0F00D1002321898B -:101A3000A088FFF774FB696A814229D2401A401CD2 -:101A4000A1884008091A8AB2A2802189081A208137 -:101A5000668895F864101046FFF73FFB864200D277 -:101A600030466080E68895F8651020890AE000001D -:101A70007800002018080020FFFFFFFF1F00000073 -:101A8000D8060020FFF729FB864200D23046E080CE -:101A900070BDF0B585B00D46064603A9FFF73CFDC5 -:101AA00000282DD19DF80C0060B300220499FB2082 -:101AB000B1F84E30FB2B00D30346B1F85040FB2069 -:101AC000FB2C00D30446DFF85CC59CE88110009035 -:101AD0000197CDF808C0ADF80230ADF80640684671 -:101AE000FFF79AFF6E80BDF80400E880BDF808009B -:101AF0006881BDF80200A880BDF80600288100209A -:101B000005B0F0BD0122D1E72DE9F04186B00446D1 -:101B100000886946FFF700FD002876D12189E0881A -:101B200001F0E4FA002870D1A188608801F0DEFAA3 -:101B300000286AD12189E08801F0CFFA002864D119 -:101B4000A188608801F0C9FA07005ED1208802A947 -:101B5000FFF79FFF00B1FFDFBDF81010628809207A -:101B6000914252D3BDF80C10E28891424DD3BDF89A -:101B70001210BDF80E2023891144A2881A44914204 -:101B800043D39DF80010019D4FF00008012640F658 -:101B9000480041B185F8B761019991F8F81105F550 -:101BA000DB7541B91AE085F82561019991F84A1170 -:101BB00005F5927509B13A2724E0E18869806188CA -:101BC000E9802189814200D30146A980A188814210 -:101BD00000D208462881012201990FE0E18869803E -:101BE0006188E9802189814200D30146A980A188CA -:101BF000814200D208462881019900222846FFF739 -:101C00000BFF2E7085F80180384606B044E67BE76E -:101C100070B504460CF0FCFDB0B12078182811D145 -:101C2000207901280ED1E088062102F03FF9040056 -:101C300008D0208807F010FC2088062102F048F91F -:101C400000B1FFDF012070BDF74D28780028FAD0E1 -:101C5000002666701420207020223146201DFCF7DB -:101C600016FC022020712E70ECE710B50446FCF73C -:101C7000DBFC002813D0207817280FD1207968B119 -:101C8000E088072102F012F940B1008807F0E4FB78 -:101C9000E088072102F01CF900B1FFDF012010BD30 -:101CA0002DE9F0475FEA000800D1FFDFDE4802219E -:101CB0001A308146FFF7E4FC00B1FFDFDA4C062062 -:101CC000678B02F09BF80546072002F097F828443E -:101CD000C5B2681CC6B2608BB04203D14046FFF764 -:101CE000C4FF58B9608BA84203D14046FFF790FF6C -:101CF00020B9608B4146FFF725FC38B1404601F022 -:101D000003FA0028E7D10120BDE8F0870221484608 -:101D1000FFF7B6FC10B9608BB842DCD14046BDE895 -:101D2000F04712F0C1BA10B501F053F908B10C2018 -:101D300010BD0BF07DFC002010BD10B504460078EE -:101D400018B1012801D0122010BD01F053F920B1C3 -:101D50000BF0C0FD08B10C2010BD207801F013F984 -:101D6000E21D04F11703611CBDE810400BF0DABC62 -:101D700010B5044601F02DF908B10C2010BD2078F3 -:101D800028B1012803D0FF280BD0122010BD01F08C -:101D9000FAF8611C0BF00CFC08B1002010BD072004 -:101DA00010BD01200BF03EFCF7E710B50BF095FDE0 -:101DB00008B1002010BD302010BD10B5044601F060 -:101DC00019F908B10C2010BD20460BF080FD002051 -:101DD00010BD10B501F00EF920B10BF07BFD08B17C -:101DE0000C2010BD0BF0F6FC002010BDFF2181700F -:101DF0004FF6FF7181808D4949680A7882718A881F -:101E000002814988418101214170002070477CB5E1 -:101E10000025022A19D015DC12F10C0F15D009DCAF -:101E200012F1280F11D012F1140F0ED012F1100F71 -:101E300011D10AE012F1080F07D012F1040F04D0FB -:101E40004AB902E0D31E052B05D8012806D0022886 -:101E500008D003280AD0122528467CBD1046FDF77D -:101E600013F8F9E710460CF06BFEF5E70846144648 -:101E70006946FFF751FB08B10225EDE79DF8000028 -:101E80000198002580F86740E6E710B51346012267 -:101E9000FEF7EAFF002010BD10B5044610F02FFA3F -:101EA000052804D020460FF029FC002010BD0C208E -:101EB00010BD10B5044601F09DF808B10C2010BD0E -:101EC0002146002007F037FB002010BD10B5044666 -:101ED0000FF0A3FC50B108F0A6FD38B1207808F04F -:101EE00029FB20780DF04DF9002010BD0C2010BD0D -:101EF00010B5044601F07EF808B10C2010BD214653 -:101F0000012007F018FB002010BD38B504464FF63D -:101F1000FF70ADF80000A079E179884216D02079F1 -:101F2000FCF7E3FA90B16079FCF7DFFA70B10022B8 -:101F3000A079114612F0A0FD40B90022E0791146C7 -:101F400012F09AFD10B9207A072801D9122038BD65 -:101F500008F076FD60B910F0D2F948B90021684662 -:101F6000FFF78EFB20B1204606F044F9002038BD73 -:101F70000C2038BD2DE9FC41817805461A2925D071 -:101F80000EDC16292ED2DFE801F02D2D2D2D2D216E -:101F90002D2D2D2D2D2D2D2D2D2D2D2D2D21212195 -:101FA0002A291FD00BDCA1F11E010C291AD2DFE86F -:101FB00001F019191919191919191919190D3A399D -:101FC00004290FD2DFE801F00E020E022888B0F5D6 -:101FD000706F07D201276946FFF79EFA20B10220F1 -:101FE000BDE8FC811220FBE79DF8000000F0D2FF65 -:101FF000019C10B104F58A7401E004F5C6749DF8E3 -:10200000000000F0C7FF019E10B106F2151601E0B6 -:1020100006F28D166846FFF76DFA08B1207838B1E0 -:102020000C20DDE70C620200180800207800002078 -:102030002770A8783070684601F030F80020CFE7AC -:102040007CB50D466946FFF767FA002618B12E6089 -:102050002E7102207CBD9DF8000000F09BFF019CCA -:102060009DF80000703400F095FF019884F84260FC -:1020700081682960017B297194F842100029F5D10B -:1020800000207CBD10B5044600F0B4FF20B10BF079 -:1020900021FC08B10C2010BD207800F074FFE2791B -:1020A000611C0BF093FD08B1002010BD022010BD93 -:1020B00010B5886E60B1002241F8682F0120CA7106 -:1020C0008979884012F0CCFC002800D01F2010BD78 -:1020D0000C2010BD1CB50C466946FFF71DFA002800 -:1020E00009D19DF8000000280198B0F8700000D0D8 -:1020F000401C208000201CBD1CB504460088694699 -:10210000FFF70AFA08B102201CBD606828B1DDE9BA -:102110000001224601F04CF81CBDDDE90001FFF78B -:10212000C7FF1CBD70B51C460D4618B1012801D073 -:10213000122070BD1946104601F078F830B12146E2 -:10214000284601F07DF808B1002070BD302070BD38 -:1021500070B5044600780E46012804D018B1022854 -:1021600001D0032840D1607828B1012803D002288B -:1021700001D0032838D1E07B10B9A078012833D1F1 -:10218000A07830F005012FD110F0050F2CD0628916 -:10219000E188E0783346FFF7C5FF002825D1A07815 -:1021A00005281DD16589A289218920793346FFF749 -:1021B000B9FF002819D1012004EB40014A891544D8 -:1021C0002218D378927893420ED1CA8889888A429D -:1021D0000AD1401CC0B20228EED3E088A84203D343 -:1021E000A07B08B1072801D9122070BD002070BD66 -:1021F00010B586B0044600F0E1FE10B10C2006B028 -:1022000010BD022104F10A0001F02FF8A0788DF82A -:102210000800A0788DF8000060788DF80400207820 -:102220008DF80300A07B8DF80500E07B00B1012054 -:102230008DF80600A078C10717D0E07801F00CF8FF -:102240008DF80100E088ADF80A006089ADF80C0057 -:10225000A078400716D5207900F0FEFF8DF8020027 -:102260002089ADF80E00A0890AE040070AD5E07881 -:1022700000F0F2FF8DF80200E088ADF80E006089F2 -:10228000ADF8100002A80FF0D4FA0028B7D16846C4 -:102290000CF07CFFB3E710B504460121FFF758FFAF -:1022A000002803D12046BDE81040A1E710BD027808 -:1022B000012A01D0BAB118E042783AB1012A05D01A -:1022C000022A12D189B1818879B100E059B14188DF -:1022D00049B1808838B101EB8101490000EB8000F1 -:1022E000B1EB002F01D2002070471220704770B56B -:1022F000044600780D46012809D010F000F80528A2 -:1023000003D00FF0A6F9002800D00C2070BD0CF00F -:102310000AFE88B10CF01CFE0CF018FF0028F5D165 -:1023200025B160780CF0ACFE0028EFD1A188608860 -:10233000BDE870400FF0A3BA122070BD10B504467E -:102340000121FFF7B4FF002804D12046BDE810406A -:102350000121CCE710BDF0B5871FDDE9056540F62A -:102360007B44A74213D28F1FA74210D288420ED8B7 -:10237000B2F5FA7F0BD2A3F10A00241FA04206D2C5 -:10238000521C4A43B2EB830F01DAAE4201D900205E -:10239000F0BD0120F0BD2DE9FC47477A894604468F -:1023A00017F0050F7ED0F8087CD194F83A0008B9F0 -:1023B000012F77D10025A8462E46F90789F0010A9A -:1023C00019D0208A514600F031FFE8B360895146A8 -:1023D00000F036FFC0B3208A6189884262D8A18E9E -:1023E000E08DCDE90001238D628CA18BE08AFFF79F -:1023F000B2FF48B30125B8070ED504EB4500828E25 -:10240000C18DCDE90012038D428C818BC08AFFF70C -:10241000A2FFC8B1A8466D1C78071ED504EB45067F -:102420005146308A00F002FF70B17089514600F0C9 -:1024300007FF48B1308A7189884253D8B18EF08D38 -:10244000CDE90001338D00E00BE0728CB18BF08A96 -:10245000FFF781FF28B12E466D1CB9F1000F03D0A4 -:1024600030E03020BDE8FC87F80707D0780705D5B5 -:1024700004EB460160894989884233D1228A0121CF -:102480001BE0414503D004EB4100008A024404EB09 -:102490004100C38A868AB34224D1838B468BB342E0 -:1024A00020D100E01EE0438C068CB3421AD1038D8C -:1024B000C08C834216D1491CC9B2A942E1D36089BC -:1024C00090420FD3207810B101280BD102E0A07800 -:1024D0000028F9D1607838B1012805D0022803D04E -:1024E000032801D01220BDE70020BBE7002152E7FE -:1024F0000178C90702D0406811F0A9BE11F076BE7C -:1025000010B50078012800D00020FCF7B8FC0020AE -:1025100010BD2DE9F0478EB00D46AFF6A422D2E9EA -:102520000092014690462846FFF735FF06000CD181 -:1025300000F044FD40B9FE4F387828B90CF0B2F9EC -:10254000A0F57F41FF3903D00C200EB0BDE8F08725 -:10255000032105F1100000F088FEF54809AA3E3875 -:102560000990F4480A90F248062110380B900CA804 -:1025700001F06AFC040037D00021FEF77CF904F179 -:1025800030017B8ABA8ACB830A84797C0091BA466F -:102590003B7CBA8A798A208801F044FD00B1FFDFD4 -:1025A000208806F058FF218804F10E0000F02CFD71 -:1025B000E1A004F1120700680590032105A804F0CA -:1025C0006DFF002005A90A5C3A54401CC0B20328E4 -:1025D000F9D3A88B6080688CA080288DE080687A11 -:1025E000410703D508270AE00920AEE7C10701D05B -:1025F000012704E0800701D5022700E000273A46C2 -:10260000BAF8160011460FF0CFF90146A062204635 -:102610000FF0D8F93A4621460020FEF7AEFD00B98A -:102620000926C34A21461C320020FEF7C3FD0027BD -:1026300084F8767084F87770A87800F0A4FC60764F -:10264000D5F80300C4F81A00B5F80700E083C4F811 -:10265000089084F80C80012084F8200101468DF850 -:102660000070684604F01AFF9DF8000000F00701B2 -:10267000C0F3C1021144C0F3401008448DF80000BB -:10268000401D2076092801D20830207601212046FD -:10269000FEF7F1F868780CF051FCEEBBA9782878C9 -:1026A000EA1C0CF01EFC48B10CF052FCA97828780A -:1026B000EA1C0CF0BFFC060002D052E0122650E0EB -:1026C000687A00F005010020CA0700D001208A07BF -:1026D00001D540F00200490701D540F008000CF098 -:1026E000E9FB06003DD1214603200CF0CDFC06009D -:1026F00037D10CF0D2FC060033D1697A01F0050124 -:102700008DF80810697AC90708D06889ADF80A0001 -:10271000288AADF80C0000E023E00120697A8A07DE -:1027200000D5401C490707D505EB40004189ADF8AD -:102730000E10008AADF8100002A80FF07AF80646D5 -:1027400095F83A0000B101200CF0C6FB4EB90CF030 -:10275000FDFC060005D1A98F20460FF00BF80600FE -:1027600008D0208806F078FE2088062101F0B0FB12 -:1027700000B1FFDF3046E8E601460020C9E638B583 -:102780006B48007878B90FF0BAFD052805D00CF039 -:1027900089F8A0F57F41FF3905D068460FF0B3F8FE -:1027A000040002D00CE00C2038BD0098008806F030 -:1027B00053FE00980621008801F08AFB00B1FFDF7C -:1027C000204638BD1CB582894189CDE900120389B4 -:1027D000C28881884088FFF7BEFD08B100201CBD7B -:1027E00030201CBD70B50546FFF7ECFF00280ED168 -:1027F0002888062101F05AFB040007D000F042FCB3 -:1028000020B1D4F81801017831B901E0022070BD7F -:10281000D4F86411097809B13A2070BD052181719D -:10282000D4F8181100200881D4F81811A88848811C -:10283000D4F81811E8888881D4F818112889C8813B -:10284000D4F81801028941898A4204D88279082A79 -:1028500001D88A4201D3122070BD29884180D4F862 -:10286000181102200870002070BD3EB50446FEF726 -:1028700075FAB0B12E480125A0F1400245702368D9 -:1028800042F8423F237900211371417069460620C6 -:1028900001F095FA00B1FFDF684601F06EFA10B161 -:1028A0000EE012203EBDBDF80440029880F8205191 -:1028B000684601F062FA18B9BDF80400A042F4D1EC -:1028C00000203EBD70B505460088062101F0EEFAF5 -:1028D000040007D000F0D6FB20B1D4F81811087816 -:1028E00030B901E0022070BDD4F86401007808B16D -:1028F0003A2070BDB020005D10F0010F22D0D5F855 -:1029000002004860D5F806008860D4F8180169898B -:1029100010228181D4F8180105F10C010E3004F564 -:102920008C74FBF78AFD216803200870288805E075 -:1029300018080020840000201122330021684880FC -:10294000002070BD0C2070BD38B504460078EF281B -:102950004DD86088ADF80000009800F097FC88B36F -:102960006188080708D4D4E9012082423FD8202A90 -:102970003DD3B0F5804F3AD8207B18B3072836D81E -:10298000607B28B1012803D0022801D003282ED172 -:102990004A0703D4022801D0032805D1A07B08B13F -:1029A000012824D1480707D4607D28B1012803D02D -:1029B000022801D003281AD1C806E07D03D50128DA -:1029C00015D110E013E0012801D003280FD1C8066B -:1029D00009D4607E012803D0022801D0032806D143 -:1029E000A07E0F2803D8E07E18B1012801D0122064 -:1029F00038BD002038BDF8B514460D46064608F02F -:102A00001FF808B10C20F8BD3046FFF79DFF0028E5 -:102A1000F9D1FCF73EFA2870B07554B9FF208DF853 -:102A2000000069460020FCF71EFA69460020FCF70A -:102A30000EFA3046BDE8F840FCF752B90022DAE75A -:102A40000078C10801D012207047FA4981F82000AF -:102A50000020704710B504460078C00704D1608894 -:102A600010B1FCF7D7F980B12078618800F001023D -:102A7000607800F02FFC002806D1FCF7B3F901467E -:102A80006088884203D9072010BD122010BD6168FC -:102A9000FCF7E9F9002010BD10B504460078C00726 -:102AA00004D1608810B1FBF78AFE70B1207861888C -:102AB00000F00102607800F00DFC002804D160886D -:102AC0006168FCF7C4F9002010BD122010BD7CB570 -:102AD000044640784225012808D8A078FBF767FE15 -:102AE00020B120781225012802D090B128467CBD63 -:102AF000FCF7DBF920B1A0880028F7D08028F5D8B2 -:102B0000FCF7DAF960B160780028EFD0207801286E -:102B100008D006F0C3FD044607F05DFC00287FD016 -:102B20000C207CBDFBF7F5FF10B9FCF7B7F990B3AB -:102B300007F086FF0028F3D1FBF700FEA0F57F41E8 -:102B4000FF39EDD1FCF707F8A68842F21070464332 -:102B5000A079FCF770F9FBF739FEF8B100220721E4 -:102B600001A801F071F9040058D0B3480021846035 -:102B70002046FDF72DFD2046FCF732FDAD4D04F15A -:102B800030006A8AA98AC2830184FBF726FE60B1FD -:102B9000E88A01210DE0FFE712207CBD31460020CC -:102BA00007F0CBFC88B3FFDF44E0FCF787F9014670 -:102BB000E88A07F091FD0146A0620022204606F057 -:102BC00070FDFBF70AFE38B9FCF778F9024621469A -:102BD0000120FEF7D2FAD0B1964A21461C320120DC -:102BE000FEF7E8FA687C00902B7CAA8A698A208824 -:102BF00001F018FA00B1FFDF208806F02CFC314606 -:102C0000204607F09AFC00B1FFDF13E008E007213F -:102C1000BDF8040001F05CF900B1FFDF09207CBDC4 -:102C200044B1208806F018FC2088072101F050F9F3 -:102C300000B1FFDF00207CBD002148E770B50D46E4 -:102C4000072101F033F9040003D094F88F0110B18B -:102C50000AE0022070BD94F87D00142801D01528E8 -:102C600002D194F8DC0108B10C2070BD1022294675 -:102C700004F5C870FBF7E1FB012084F88F01002008 -:102C800070BD10B5072101F011F918B190F88F113E -:102C900011B107E0022010BD90F87D10142903D077 -:102CA000152901D00C2010BD022180F88F110020C1 -:102CB00010BD2DE9FC410C464BF6803212219442A6 -:102CC0001DD8E4B16946FEF727FC002815D19DF810 -:102CD000000000F05FF9019E9DF80000703600F0E2 -:102CE00059F9019DAD1C2F88224639463046FDF723 -:102CF00065FC2888B842F6D10020BDE8FC81084672 -:102D0000FBE77CB5044600886946FEF705FC002811 -:102D100010D19DF8000000F03DF9019D9DF80000E4 -:102D2000703500F037F90198A27890F82C10914294 -:102D300001D10C207CBD7F212972A9720021E9728A -:102D4000E17880F82D10217980F82E10A17880F894 -:102D50002C1000207CBD1CB50C466946FEF7DCFB40 -:102D600000280AD19DF8000000F014F9019890F8AD -:102D70008C0000B10120207000201CBD7CB50D46E8 -:102D800014466946FEF7C8FB002809D19DF80000EB -:102D900000F000F9019890F82C00012801D00C20D7 -:102DA0007CBD9DF8000000F0F5F8019890F87810CF -:102DB000297090F87900207000207CBD70B50D4618 -:102DC0001646072101F072F818B381880124C388E0 -:102DD000428804EB4104AC4217D842F210746343BA -:102DE000A4106243B3FBF2F2521E94B24FF4FA7293 -:102DF000944200D91446A54200D22C46491C641CBA -:102E0000B4FBF1F24A43521E91B290F8C8211AB9AC -:102E100001E0022070BD01843180002070BD10B53A -:102E20000C46072101F042F840B1022C08D91220CB -:102E300010BD000018080020780000200220F7E7ED -:102E400014F0010180F8FD10C4F3400280F8FC206A -:102E500004D090F8FA1009B107F054FC0020E7E71D -:102E6000017889B1417879B141881B290CD38188D7 -:102E70001B2909D3C188022906D3F64902680A65CD -:102E800040684865002070471220704710B504461E -:102E90000EF086FD204607F0D8FB0020C8E710B5ED -:102EA00007F0D6FB0020C3E72DE9F04115460F4699 -:102EB00006460122114638460EF076FD04460121F1 -:102EC000384607F009FC844200D20446012130460E -:102ED00000F065F806460121002000F060F8311886 -:102EE000012096318C4206D901F19600611AB1FB9E -:102EF000F0F0401C80B228800020BDE8F08110B5C1 -:102F0000044600F077F808B10C2091E7601C0AF045 -:102F100038FE207800F00100FBF718FE207800F062 -:102F200001000CF010F8002082E710B504460720DD -:102F300000F056FF08B10C207AE72078C00711D0C6 -:102F400000226078114611F097FD08B112206FE75A -:102F5000A06809F01DFB6078D4F8041009F021FB8B -:102F6000002065E7002009F013FB00210846F5E783 -:102F700010B505F036FE00205AE710B5006805F0E0 -:102F800084F8002054E718B1022801D001207047CE -:102F90000020704708B1002070470120704710B52D -:102FA000012904D0022905D0FFDF204640E7C000F8 -:102FB000503001E080002C3084B2F6E710B50FF0FD -:102FC0009EF9042803D0052801D0002030E7012015 -:102FD0002EE710B5FFF7F2FF10B10CF07BF828B91F -:102FE00007F02EFD20B1FBF78CFD08B101201FE793 -:102FF00000201DE710B5FFF7E1FF18B907F020FD2D -:10300000002800D0012013E72DE9FE4300250F46DC -:1030100080460A260421404604F069FA4046FDF73E -:103020003EFE062000F0EAFE044616E06946062051 -:1030300000F0C5FE0BE000BFBDF80400B84206D0AA -:103040000298042241460E30FBF7CAF950B1684697 -:1030500000F093FE0500EFD0641E002C06DD002D6D -:10306000E4D005E04046FDF723FEF5E705B9FFDFB4 -:10307000D8F80000FDF737FE761E01D00028C9D031 -:10308000BDE8FE8390F8F01090F88C0020B919B1DB -:10309000042901D0012070470020704701780029E1 -:1030A0000AD0416891F8FA20002A05D0002281F860 -:1030B000FA20406807F026BB704770B514460546F5 -:1030C000012200F01BF9002806D121462846BDE860 -:1030D0007040002200F012B970BDFB2802D8B1F593 -:1030E000296F01D911207047002070471B38E12853 -:1030F00006D2B1F5A47F03D344F29020814201D9D6 -:1031000012207047002070471FB55249403191F896 -:103110002010CA0702D102781D2A0AD08A0702D4D9 -:1031200002781C2A28D049073DD40178152937D0C8 -:1031300039E08088ADF8000002A9FEF7EDF900B192 -:10314000FFDF9DF80800FFF725FF039810F8601FC8 -:103150008DF8021040788DF803000020ADF80400CF -:1031600001B9FFDF9DF8030000B9FFDF6846FEF7F5 -:1031700040FCD8B1FFDF19E08088ADF800004FF4C3 -:103180002961FB20ADF80410ADF80200ADF806008F -:10319000ADF808106846FEF73AFD38B1FFDF05E0EC -:1031A000807BC00702D0002004B041E60120FBE78D -:1031B000F8B50746508915460C4640B1B0F5004FAA -:1031C00005D20022A878114611F056FC08B1122051 -:1031D000F8BDA06E04F1700630B1A97894F86E00C5 -:1031E000814201D00C20F8BD012184F86F10A9782C -:1031F00084F86E106968A1666989A4F86C10288942 -:10320000B084002184F86F1028886946FEF762FFB9 -:10321000B08CBDF80010081A00B2002804DD214669 -:103220003846FEF745FFDDE70020F8BD042803D34C -:1032300021B9B0F5804F01D90020704701207047B7 -:10324000042803D321B9B0F5804F01D9002070477D -:1032500001207047D8070020012802D018B10020B3 -:103260007047022070470120704710B500224FF4CC -:10327000C84408E030F81230A34200D9234620F8B1 -:103280001230521CD2B28A42F4D3D1E580B2C106C8 -:103290000BD401071CD481064FEAC07101D5B9B91E -:1032A00000E099B1800713D410E0410610D48106E4 -:1032B0000ED4C1074FEA807104D0002902DB400719 -:1032C00004D405E0010703D4400701D4012070476E -:1032D0000020704770B50C460546FF2904D8FBF75F -:1032E0007CFA18B11F2C01D9122070BD2846FBF7BB -:1032F0005EFA08B1002070BD422070BD0AB1012203 -:1033000000E00222024202D1C80802D109B1002025 -:10331000704711207047000030B5058825F400443F -:1033200021448CB24FF4004194420AD2121B92B253 -:103330001B339A4201D2A94307E005F4004121431F -:1033400003E0A21A92B2A9431143018030BD0844A0 -:10335000083050434A31084480B2704770B51D466A -:1033600016460B46044629463046049AFFF7EFFFFF -:103370000646B34200D2FFDF282200212046FBF799 -:1033800086F84FF6FF70A082283EB0B26577608065 -:10339000B0F5004F00D9FFDF618805F13C008142A4 -:1033A00000D2FFDF60880835401B343880B22080AF -:1033B0001B2800D21B2020800020A07770BD8161D7 -:1033C000886170472DE9F05F0D46C188044600F121 -:1033D0002809008921F4004620F4004800F063FB2E -:1033E00010B10020BDE8F09F4FF0000A4FF0010B34 -:1033F000B0450CD9617FA8EB0600401A0838854219 -:1034000019DC09EB06000021058041801AE0608884 -:10341000617F801B471A083F0DD41B2F00DAFFDFA6 -:10342000BD4201DC294600E0B9B2681A0204120C60 -:1034300004D0424502DD84F817A0D2E709EB06006C -:103440000180428084F817B0CCE770B5044600F1E3 -:103450002802C088E37D20F400402BB1104402888C -:10346000438813448B4201D2002070BD00258A425C -:1034700002D30180458008E0891A0904090C4180C3 -:1034800003D0A01D00F01FFB08E0637F0088083315 -:10349000184481B26288A01DFFF73EFFE575012048 -:1034A00070BD70B5034600F12804C588808820F4FB -:1034B00000462644A84202D10020188270BD988997 -:1034C0003588A84206D3401B75882D1A2044ADB21A -:1034D000C01E05E02C1AA5B25C7F20443044401D7C -:1034E0000C88AC4200D90D809C8924B10024147052 -:1034F0000988198270BD0124F9E770B5044600F10E -:103500002801808820F400404518208A002825D012 -:10351000A189084480B2A08129886A881144814227 -:1035200000D2FFDF2888698800260844A1898842E4 -:1035300012D1A069807F2871698819B1201D00F01F -:10354000C2FA08E0637F28880833184481B2628891 -:10355000201DFFF7E1FEA6812682012070BD2DE926 -:10356000F041418987880026044600F12805B942C8 -:1035700019D004F10A0800BF21F400402844418812 -:1035800019B1404600F09FFA08E0637F00880833D5 -:10359000184481B262884046FFF7BEFE761C6189FE -:1035A000B6B2B942E8D13046BDE8F0812DE9F0412C -:1035B00004460B4627892830A68827F40041B4F832 -:1035C0000A8001440D46B74201D10020ECE70AB160 -:1035D000481D106023B1627F691D1846FAF72DFF60 -:1035E0002E88698804F1080021B18A1996B200F08A -:1035F0006AFA06E0637F62880833991989B2FFF797 -:103600008BFE474501D1208960813046CCE7818817 -:10361000C088814201D10120704700207047018994 -:103620008088814201D1012070470020704770B529 -:103630008588C38800F1280425F4004223F4004162 -:1036400014449D421AD08389058A5E1925886388AF -:10365000EC18A64214D313B18B4211D30EE0437F72 -:1036600008325C192244408892B2801A80B2233317 -:10367000984201D211B103E08A4201D1002070BD0D -:10368000012070BD2DE9F0478846C18804460089B5 -:1036900021F4004604F1280720F4004507EB060951 -:1036A00000F001FA002178BBB54204D9627FA81B63 -:1036B000801A002503E06088627F801B801A08382A -:1036C00023D4E28962B1B9F80020B9F802303BB1E5 -:1036D000E81A2177404518DBE0893844801A09E070 -:1036E000801A217740450ADB607FE1890830304449 -:1036F00039440844C01EA4F81280BDE8F08745454F -:1037000003DB01202077E7E7FFE761820020F4E791 -:103710002DE9F74F044600F12805C088884620F4BB -:10372000004A608A05EB0A0608B1404502D2002033 -:10373000BDE8FE8FE08978B13788B6F8029007EBD4 -:103740000901884200D0FFDF207F4FF0000B50EAD4 -:10375000090106D088B33BE00027A07FB94630714D -:10376000F2E7E18959B1607F2944083050440844A8 -:10377000B4F81F1020F8031D94F821108170E2891D -:1037800007EB080002EB0801E1813080A6F802B0E7 -:1037900002985F4650B1637F30880833184481B285 -:1037A0006288A01DFFF7B8FDE78121E0607FE18915 -:1037B00008305044294408442DE0FFE7E089B4F87C -:1037C0001F102844C01B20F8031D94F8211081709D -:1037D00009EB0800E28981B202EB0800E081378042 -:1037E00071800298A0B1A01D00F06DF9A4F80EB090 -:1037F000A07F401CA077A07D08B1E088A08284F85B -:1038000016B000BFA4F812B084F817B001208FE7FB -:10381000E0892844C01B30F8031DA4F81F108078ED -:1038200084F82100EEE710B5818800F1280321F427 -:1038300000442344848AC288A14212D0914210D00D -:10384000818971B9826972B11046FFF7E8FE50B9FB -:103850001089283220F400401044197900798842F8 -:1038600001D1002010BD184610BD00F12803407F93 -:1038700008300844C01E1060088808B9DB1E1360B9 -:1038800008884988084480B270472DE9F04100F16A -:103890002806407F1C4608309046431808884D880B -:1038A000069ADB1EA0B1C01C80B2904214D9801AC7 -:1038B000A04200DB204687B298183A464146FAF704 -:1038C0008FFD002816D1E01B84B2B844002005E02B -:1038D000ED1CADB2F61EE8E7101A80B20119A9423C -:1038E00006D8304422464146BDE8F041FAF778BD9B -:1038F0004FF0FF3058E62DE9F04100F12804407FF9 -:103900001E46083090464318002508884F88069ABE -:10391000DB1E90B1C01C80B2904212D9801AB04216 -:1039200000DB304685B299182A464046FAF785FDF5 -:10393000701B86B2A844002005E0FF1CBFB2E41E45 -:10394000EAE7101A80B28119B94206D82118324626 -:103950004046FAF772FDA81985B2284624E62DE9FB -:10396000F04100F12804407F1E460830904643187D -:10397000002508884F88069ADB1E90B1C01C80B2D3 -:10398000904212D9801AB04200DB304685B29818B6 -:103990002A464146FAF751FD701B86B2A844002022 -:1039A00005E0FF1CBFB2E41EEAE7101A80B28119DD -:1039B000B94206D8204432464146FAF73EFDA819DE -:1039C00085B22846F0E5401D704710B5044600F169 -:1039D0002801C288808820F400431944904206D010 -:1039E000A28922B9228A12B9A28A904201D100206A -:1039F00010BD0888498831B1201D00F064F800200E -:103A00002082012010BD637F62880833184481B290 -:103A1000201DFFF781FCF2E70021C181017741827F -:103A2000C1758175704703881380C28942B1C2880D -:103A300022F4004300F128021A440A60C08970474A -:103A40000020704710B50446808AA0F57F41FF39F9 -:103A500000D0FFDFE088A082E08900B10120A075DE -:103A600010BD4FF6FF71818200218175704710B53E -:103A70000446808AA0F57F41FF3900D1FFDFA07D99 -:103A800028B9A088A18A884201D1002010BD012058 -:103A900010BD8188828A914201D1807D08B10020C9 -:103AA00070470120704720F4004221F400439A42FD -:103AB00007D100F4004001F40041884201D0012008 -:103AC00070470020704730B5044600880D4620F44A -:103AD0000040A84200D2FFDF21884FF40040884315 -:103AE0002843208030BD70B50C00054609D0082C55 -:103AF00000D2FFDF1DB1A1B2286800F044F8201DFC -:103B000070BD0DB100202860002070BD002102684A -:103B100003E093881268194489B2002AF9D100F0B1 -:103B200032B870B500260D460446082900D2FFDFE2 -:103B3000206808B91EE0044620688188A94202D0A6 -:103B400001680029F7D181880646A94201D10068A1 -:103B50000DE005F1080293B20022994209D32844EE -:103B6000491B026081802168096821600160206032 -:103B700000E00026304670BD00230B608A8002689A -:103B80000A600160704700234360021D01810260EA -:103B90007047F0B50F460188408815460C181E4640 -:103BA000AC4200D3641B3044A84200D9FFDFA01907 -:103BB000A84200D9FFDF3819F0BD2DE9F041884651 -:103BC00006460188408815460C181F46AC4200D3B3 -:103BD000641B3844A84200D9FFDFE019A84200D98D -:103BE000FFDF70883844708008EB0400BDE8F08186 -:103BF0002DE9F041054600881E461746841B88467D -:103C0000BC4200D33C442C8068883044B84200D980 -:103C1000FFDFA019B84200D9FFDF68883044688010 -:103C200008EB0400E2E72DE9F04106881D46044652 -:103C3000701980B2174688462080B84201D3C01B55 -:103C400020806088A84200D2FFDF7019B84200D9F6 -:103C5000FFDF6088401B608008EB0600C6E730B5D8 -:103C60000D460188CC18944200D3A41A408898428B -:103C700000D8FFDF281930BD2DE9F041C84D0446BA -:103C80009046A8780E46A04200D8FFDF05EB8607D5 -:103C9000B86A50F8240000B1FFDFB868002816D0D9 -:103CA000304600F044F90146B868FFF73AFF0500D6 -:103CB0000CD0B86A082E40F8245000D3FFDFB94872 -:103CC0004246294650F82630204698472846BDE807 -:103CD000F0812DE9F8431E468C1991460F460546A2 -:103CE000FF2C00D9FFDFB14500D9FFDFE4B200951A -:103CF0004DB300208046E81C20F00300A84200D00D -:103D0000FFDF4946DFF89892684689F8001089F885 -:103D1000017089F8024089F8034089F8044089F865 -:103D2000054089F8066089F80770414600F008F9F7 -:103D3000002142460F464B460098C01C20F003006D -:103D4000009012B10EE00120D4E703EB8106B062CF -:103D5000002005E0D6F828C04CF82070401CC0B206 -:103D6000A042F7D30098491C00EB8400C9B2009030 -:103D70000829E1D3401BBDE8F88310B50446EDF7F0 -:103D80008EFA08B1102010BD2078854A618802EBB8 -:103D9000800092780EE0836A53F8213043B14A1CC8 -:103DA0006280A180806A50F82100A060002010BDD0 -:103DB000491C89B28A42EED86180052010BD70B5D9 -:103DC00005460C460846EDF76AFA08B1102070BDAA -:103DD000082D01D3072070BD25700020608070BDC4 -:103DE0000EB56946FFF7EBFF00B1FFDF6846FFF74E -:103DF000C4FF08B100200EBD01200EBD10B5044661 -:103E0000082800D3FFDF6648005D10BD3EB50546BB -:103E100000246946FFF7D3FF18B1FFDF01E0641CFF -:103E2000E4B26846FFF7A9FF0028F8D02846FFF75C -:103E3000E5FF001BC0B23EBD59498978814201D9D6 -:103E4000C0B27047FF2070472DE9F041544B06295E -:103E500003D007291CD19D7900E0002500244FF6EE -:103E6000FF7603EB810713F801C00AE06319D7F866 -:103E700028E09BB25EF823E0BEF1000F04D0641C82 -:103E8000A4B2A445F2D8334603801846B34201D108 -:103E900000201CE7BDE8F041EEE6A0F57F43FF3BC4 -:103EA00001D0082901D300207047E5E6A0F57F4244 -:103EB000FF3A0BD0082909D2394A9378834205D9B1 -:103EC00002EB8101896A51F8200070470020704799 -:103ED0002DE9F04104460D46A4F57F4143F202006E -:103EE000FF3902D0082D01D30720F0E62C494FF00E -:103EF00000088A78A242F8D901EB8506B26A52F826 -:103F00002470002FF1D027483946203050F8252062 -:103F100020469047B16A284641F8248000F007F80F -:103F200002463946B068FFF727FE0020CFE61D495C -:103F3000403131F810004FF6FC71C01C084070474A -:103F40002DE9F843164E8846054600242868C01C13 -:103F500020F0030028602046FFF7E9FF315D484369 -:103F6000B8F1000F01D0002200E02A68014600925B -:103F700032B100274FEA0D00FFF7B5FD1FB106E093 -:103F800001270020F8E706EB8401009A8A6029687F -:103F9000641C0844E4B22860082CD7D3EBE6000088 -:103FA0003C0800201862020070B50E461D461146FE -:103FB00000F0D3F804462946304600F0D7F82044F4 -:103FC000001D70BD2DE9F04190460D4604004FF0F4 -:103FD000000610D00027E01C20F00300A04200D013 -:103FE000FFDFE5B141460020FFF77DFD0C3000EB1F -:103FF000850617B113E00127EDE7614F04F10C00CE -:10400000AA003C602572606000EB85002060002102 -:104010006068FAF73CFA41463868FFF764FD3046BD -:10402000BDE8F0812DE9FF4F554C804681B02068F6 -:104030009A46934600B9FFDF2068027A424503D9C9 -:10404000416851F8280020B143F2020005B0BDE8F4 -:10405000F08F5146029800F080F886B258460E99CB -:1040600000F084F885B27019001D87B22068A1465F -:1040700039460068FFF755FD04001FD06780258092 -:104080002946201D0E9D07465A4601230095FFF73D -:1040900065F92088314638440123029ACDF800A002 -:1040A000FFF75CF92088C1193846FFF788F9D9F87D -:1040B00000004168002041F82840C7E70420C5E718 -:1040C00070B52F4C0546206800B9FFDF2068017AE3 -:1040D000A9420DD9426852F8251049B1002342F88F -:1040E00025304A880068FFF747FD2168087A06E016 -:1040F00043F2020070BD4A6852F820202AB9401EDF -:10410000C0B2F8D20868FFF701FD002070BD70B59D -:104110001B4E05460024306800B9FFDF3068017A85 -:10412000A94204D9406850F8250000B1041D20467A -:1041300070BD70B5124E05460024306800B9FFDF2F -:104140003068017AA94206D9406850F8251011B1AB -:1041500031F8040B4418204670BD10B50A46012101 -:10416000FFF7F5F8C01C20F0030010BD10B50A469B -:104170000121FFF7ECF8C01C20F0030010BD000087 -:104180008C00002070B50446C2F110052819FAF71A -:1041900054F915F0FF0109D0491ECAB28020A0547D -:1041A0002046BDE870400021FAF771B970BD30B506 -:1041B00005E05B1EDBB2CC5CD55C6C40C454002BCC -:1041C000F7D130BD10B5002409E00B78521E44EA47 -:1041D000430300F8013B11F8013BD2B2DC09002A8D -:1041E000F3D110BD2DE9F04389B01E46DDE9107909 -:1041F00090460D00044622D002460846F949FDF7D4 -:1042000044FE102221463846FFF7DCFFE07B000623 -:1042100006D5F44A3946102310320846FFF7C7FF87 -:10422000102239464846FFF7CDFFF87B000606D539 -:10423000EC4A4946102310320846FFF7B8FF102217 -:1042400000212046FAF723F90DE0103EB6B208EB44 -:104250000601102322466846FFF7A9FF224628469A -:104260006946FDF712FE102EEFD818D0F2B2414683 -:104270006846FFF787FF10234A46694604A8FFF700 -:1042800096FF1023224604A96846FFF790FF2246B6 -:1042900028466946FDF7F9FD09B0BDE8F083102313 -:1042A0003A464146EAE770B59CB01E4605461346BD -:1042B00020980C468DF80800202219460DF10900BF -:1042C000FAF7BBF8202221460DF12900FAF7B5F8DC -:1042D00017A913A8CDE90001412302AA31462846B7 -:1042E000FFF780FF1CB070BD2DE9FF4F9FB014AEEB -:1042F000DDE92D5410AFBB49CDE9007620232031F4 -:104300001AA8FFF76FFF4FF000088DF808804FF0F4 -:1043100001098DF8099054F8010FCDF80A00A08822 -:10432000ADF80E0014F8010C1022C0F340008DF817 -:10433000100055F8010FCDF81100A888ADF8150050 -:1043400015F8010C2C99C0F340008DF8170006A851 -:104350008246FAF772F80AA8834610222299FAF7E1 -:104360006CF8A0483523083802AA40688DF83C80D4 -:10437000CDE900760E901AA91F98FFF733FF8DF84C -:1043800008808DF809902068CDF80A00A088ADF863 -:104390000E0014F8010C1022C0F340008DF810003C -:1043A0002868CDF81100A888ADF8150015F8010CA3 -:1043B0002C99C0F340008DF817005046FAF73DF8ED -:1043C000584610222299FAF738F8864835230838DB -:1043D00002AA40688DF83C90CDE900760E901AA9AB -:1043E0002098FFF7FFFE23B0BDE8F08FF0B59BB03B -:1043F0000C460546DDE922101E461746DDE920324F -:10440000D0F801C0CDF808C0B0F805C0ADF80CC0B8 -:104410000078C0F340008DF80E00D1F80100CDF80F -:104420000F00B1F80500ADF8130008781946C0F385 -:1044300040008DF815001088ADF8160090788DF8C2 -:1044400018000DF119001022F9F7F7FF0DF12900FE -:1044500010223146F9F7F1FF0DF1390010223946EB -:10446000F9F7EBFF17A913A8CDE90001412302AA30 -:1044700021462846FFF7B6FE1BB0F0BDF0B5A3B04D -:1044800017460D4604461E46102202A82899F9F741 -:10449000D4FF06A820223946F9F7CFFF0EA8202224 -:1044A0002946F9F7CAFF1EA91AA8CDE90001502331 -:1044B00002AA314616A8FFF795FE1698206023B091 -:1044C000F0BDF0B589B00446DDE90E070D46397838 -:1044D000109EC1F340018DF8001031789446C1F36D -:1044E00040018DF801101968CDF802109988ADF8D7 -:1044F000061099798DF808100168CDF809108188A7 -:10450000ADF80D1080798DF80F0010236A466146D2 -:1045100004A8FFF74CFE2246284604A9FDF7B5FC87 -:10452000D6F801000090B6F80500ADF80400D7F801 -:104530000100CDF80600B7F80500ADF80A0000202C -:10454000039010236A46214604A8FFF730FE224656 -:10455000284604A9FDF799FC09B0F0BD1FB51C68F9 -:1045600000945B68019313680293526803920246B9 -:1045700008466946FDF789FC1FBD10B588B00446A2 -:104580001068049050680590002006900790084637 -:104590006A4604A9FDF779FCBDF80000208008B048 -:1045A00010BD1FB51288ADF800201A88ADF80220A2 -:1045B0000022019202920392024608466946FDF7E4 -:1045C00064FC1FBD7FB5074B14460546083B9A1C8B -:1045D0006846FFF7E6FF224669462846FFF7CDFF0B -:1045E0007FBD00007062020070B5044600780E4680 -:1045F000012813D0052802D0092813D10EE0A068A5 -:1046000061690578042003F059FA052D0AD0782352 -:1046100000220420616903F0A7F903E00420616926 -:1046200003F04CFA31462046BDE8704001F08AB8EC -:1046300010B500F12D03C2799C78411D144064F33C -:104640000102C271D2070DD04A795C7922404A71C9 -:104650000A791B791A400A718278C9788A4200D98E -:10466000817010BD00224A71F5E74178012900D020 -:104670000C21017070472DE9F04F93B04FF0000B03 -:104680000C690D468DF820B0097801260C201746DC -:104690004FF00D084FF0110A4FF008091B2975D291 -:1046A000DFE811F01B00C40207031F035E03710360 -:1046B000A303B803F9031A0462049504A204EF04E7 -:1046C0002D05370555056005F305360639066806DC -:1046D0008406FE062207EB06F00614B120781D289A -:1046E0002AD0D5F808805FEA08004FD001208DF865 -:1046F0002000686A02220D908DF824200A208DF88F -:104700002500A8690A90A8880028EED098F8001023 -:1047100091B10F2910D27DD2DFE801F07C1349DE80 -:10472000FCFBFAF9F8F738089CF6F50002282DD1C1 -:1047300024B120780C2801D00026F0E38DF8202049 -:10474000CBE10420696A03F0B9F9A8880728EED103 -:10475000204600F0F2FF022809D0204600F0EDFFCD -:10476000032807D9204600F0E8FF072802D20120DD -:10477000207004E0002CB8D020780128D7D198F818 -:104780000400C11F0A2902D30A2061E0C4E1A0701D -:10479000D8F80010E162B8F80410218698F80600F5 -:1047A00084F83200012028700320207044E007289C -:1047B000BDD1002C99D020780D28B8D198F80310DD -:1047C00094F82F20C1F3C000C2F3C002104201D000 -:1047D000062000E00720890707D198F8051001425C -:1047E000D2D198F806100142CED194F8312098F831 -:1047F000051020EA02021142C6D194F8322098F83E -:10480000061090430142BFD198F80400C11F0A2945 -:10481000BAD200E008E2617D81427CD8D8F800106D -:104820006160B8F80410218198F80600A072012098 -:1048300028700E20207003208DF82000686A0D90EB -:1048400004F12D000990601D0A900F300B9022E1B9 -:104850002875FCE3412891D1204600F06EFF042822 -:1048600002D1E078C00704D1204600F066FF0F288F -:1048700084D1A88CD5F80C8080B24FF0400BE6694B -:10488000FFF745FC324641465B464E46CDF8009068 -:10489000FFF731F80B208DF82000686A0D90E06971 -:1048A0000990002108A8FFF79FFE2078042806D071 -:1048B000A07D58B1012809D003280AD04AE3052079 -:1048C0002070032028708DF82060CEE184F800A0CD -:1048D00032E712202070EAE11128BCD1204600F016 -:1048E0002CFF042802D1E078C00719D0204600F040 -:1048F00024FF062805D1E078C00711D1A07D022849 -:104900000ED0204608E0CCE084E072E151E124E1E1 -:1049100003E1E9E019E0B0E100F00FFF11289AD1BE -:10492000102208F1010104F13C00F9F786FD6078DE -:1049300001286ED012202070E078C00760D0A07DE2 -:104940000028C8D00128C6D05AE0112890D12046AE -:1049500000F0F3FE082804D0204600F0EEFE1328F5 -:1049600086D104F16C00102208F101010646F9F726 -:1049700064FD207808280DD014202070E178C80745 -:104980000DD0A07D02280AD06278022A04D0032824 -:10499000A1D035E00920F0E708B1012837D1C807D8 -:1049A00013D0A07D02281DD000200090D4E906215C -:1049B00033460EA8FFF777FC10220EA904F13C0045 -:1049C000F9F70EFDC8B1042042E7D4E90912201D11 -:1049D0008DE8070004F12C0332460EA8616BFFF747 -:1049E00070FDE9E7606BC1F34401491E0068C840EF -:1049F00000F0010040F08000D7E72078092806D1B8 -:104A000085F800908DF8209036E32870EFE30920B8 -:104A1000FBE79EE1112899D1204600F08EFE0A287E -:104A200002D1E078C00704D1204600F086FE1528A8 -:104A30008CD104F13C00102208F101010646F9F77F -:104A4000FCFC20780A2816D016202070D4E9093200 -:104A5000606B611D8DE80F0004F15C0304F16C02D2 -:104A600047310EA8FFF7C2FC10220EA93046F9F715 -:104A7000B7FC18B1F9E20B20207073E22046FFF773 -:104A8000D7FDA078216AC0F110020B18002118464A -:104A9000F9F7FDFC26E3394608A8FFF7A5FD064611 -:104AA0003CE20228B7D1204600F047FE042804D398 -:104AB000204600F042FE082809D3204600F03DFEC3 -:104AC0000E2829D3204600F038FE122824D2A07DDB -:104AD0000228A0D10E208DF82000686A0D9098F869 -:104AE00001008DF82400F5E3022894D1204600F05F -:104AF00024FE002810D0204600F01FFE0128F9D027 -:104B0000204600F01AFE0C28F4D004208DF8240072 -:104B100098F801008DF8250060E21128FCD1002CE6 -:104B2000FAD020781728F7D16178606A022912D06C -:104B30005FF0000101EB4101182606EBC1011022D4 -:104B4000405808F10101F9F778FC0420696A00F087 -:104B5000E7FD2670F0E50121ECE70B28DCD1002C05 -:104B6000DAD020781828D7D16078616A02281CD062 -:104B70005FF0000000EB4002102000EBC20009587B -:104B8000B8F8010008806078616A02280FD0002020 -:104B900000EB4002142000EBC2000958404650F8D8 -:104BA000032F0A604068486039E00120E2E70120F5 -:104BB000EEE71128B0D1002CAED020781928ABD167 -:104BC0006178606A022912D05FF0000101EB4101B7 -:104BD0001C2202EBC1011022405808F10101F9F733 -:104BE0002CFC0420696A00F09BFD1A20B6E001212C -:104BF000ECE7082890D1002C8ED020781A288BD191 -:104C0000606A98F80120017862F347010170616AD7 -:104C1000D8F8022041F8012FB8F806008880042057 -:104C2000696A00F07DFD90E2072011E638780128DE -:104C300094D1182204F114007968F9F7FEFBE079A9 -:104C4000C10894F82F0001EAD001E07861F3000078 -:104C5000E070217D002974D12178032909D0C00793 -:104C600025D0032028708DF82090686A0D9041208F -:104C700008E3607DA178884201D90620E8E5022694 -:104C80002671E179204621F0E001E171617A21F09D -:104C9000F0016172A17A21F0F001A172FFF7C8FC66 -:104CA0002E708DF82090686A0D900720EAE20420AB -:104CB000ABE6387805289DD18DF82000686A0D9004 -:104CC000B8680A900720ADF824000A988DF830B033 -:104CD0006168016021898180A17A8171042020703E -:104CE000F8E23978052985D18DF82010696A0D918F -:104CF000391D09AE0EC986E80E004121ADF8241019 -:104D00008DF830B01070A88CD7F80C8080B2402697 -:104D1000A769FFF70EFA41463A463346C846CDF832 -:104D20000090FEF71CFE002108A8FFF75DFCE0786C -:104D300020F03E00801CE0702078052802D00F2073 -:104D40000CE04AE1A07D20B1012802D0032802D066 -:104D500002E10720BEE584F80080EDE42070EBE47A -:104D6000102104F15C0002F0C2FB606BB0BBA07DBF -:104D700018B1012801D00520FDE006202870F84870 -:104D80006063A063C2E23878022894D1387908B110 -:104D90002875B7E3A07D022802D0032805D022E0C1 -:104DA000B8680028F5D060631CE06078012806D060 -:104DB000A07994F82E10012805D0E94806E0A179E1 -:104DC00094F82E00F7E7B8680028E2D06063E07836 -:104DD000C00701D0012902D0E14803E003E0F868F0 -:104DE0000028D6D0A06306200FE68DF82090696ACF -:104DF0000D91E1784846C90709D06178022903D1AD -:104E0000A17D29B1012903D0A17D032900D007206C -:104E1000287033E138780528BBD1207807281ED0C8 -:104E200084F800A005208DF82000686A0D90B8680D -:104E30000A90ADF824A08DF830B003210170E1781C -:104E4000CA070FD0A27D022A1AD000210091D4E90E -:104E5000061204F15C03401CFFF725FA6BE384F8AB -:104E60000090DFE7D4E90923211D8DE80E0004F14D -:104E70002C0304F15C02401C616BFFF722FB5AE338 -:104E8000626BC1F34401491E1268CA4002F001017D -:104E900041F08001DAE738780528BDD18DF820008F -:104EA000686A0D90B8680A90ADF824A08DF830B00B -:104EB000042100F8011B102204F15C01F9F7BDFA8E -:104EC000002108A8FFF790FB2078092801D01320C3 -:104ED00044E70A2020709AE5E078C10742D0A17D1E -:104EE000012902D0022927D038E0617808A80129D9 -:104EF00016D004F16C010091D4E9061204F15C03B0 -:104F0000001DFFF7BBFA0A20287003268DF82080C9 -:104F1000686A0D90002108A8FFF766FBE1E2C7E28E -:104F200004F15C010091D4E9062104F16C03001D39 -:104F3000FFF7A4FA0026E9E7C0F3440114290DD2D3 -:104F40004FF0006101EBB0104FEAB060E0706078A4 -:104F5000012801D01020BDE40620FFE6607801287A -:104F60003FF4B6AC0A2050E5E178C90708D0A17D2E -:104F7000012903D10B202870042030E028702EE096 -:104F80000E2028706078616B012818D004F15C0352 -:104F900004F16C020EA8FFF7E1FA2046FFF748FB88 -:104FA000A0780EAEC0F1100230440021F9F76FFA7C -:104FB00006208DF82000686A09960D909BE004F1A8 -:104FC0006C0304F15C020EA8FFF7C8FAE8E7397831 -:104FD000022903D139790029D0D0297592E28DF8C0 -:104FE0002000686A0D9056E538780728F6D1D4E994 -:104FF00009216078012808D004F16C00CDE9000295 -:10500000029105D104F16C0304E004F15C00F5E7C2 -:1050100004F15C0304F14C007A680646216AFFF74C -:1050200063F96078012822D1A078216AC0F11002CA -:105030000B1800211846F9F72AFAD4E90923606B06 -:1050400004F12D018DE80F0004F15C0300E05BE248 -:1050500004F16C0231460EA8FFF7C8F910220EA920 -:1050600004F13C00F9F7BCF908B10B20ACE485F879 -:10507000008000BF8DF82090686A0D908DF824A004 -:1050800009E538780528A9D18DF82000686A0D90C7 -:10509000B8680A90ADF824A08DF830B080F8008090 -:1050A000617801291AD0D4E9092104F12D03A66BF6 -:1050B00003910096CDE9013204F16C0304F15C0226 -:1050C00004F14C01401CFFF791F9002108A8FFF7FB -:1050D0008BFA6078012805D015203FE6D4E9091243 -:1050E000631DE4E70E20287006208DF82000686A12 -:1050F000CDF824B00D90A0788DF82800CBE4387856 -:105100000328C0D1E079C00770D00F202870072095 -:1051100065E7387804286BD11422391D04F1140096 -:10512000F9F78BF9616A208CA1F80900616AA0780F -:10513000C871E179626A01F003011172616A627AF1 -:105140000A73616AA07A81F8240016205DE485F86C -:1051500000A08DF82090696A50460D9192E0000001 -:10516000706202003878052842D1B868A861617879 -:10517000606A022901D0012100E0002101EB410118 -:10518000142606EBC1014058082102F0B0F96178FD -:10519000606A022901D0012100E0002101EB4101F8 -:1051A00006EBC101425802A8E169FFF70BFA6078EB -:1051B000626A022801D0012000E0002000EB4001DB -:1051C000102000EBC1000223105802A90932FEF79B -:1051D000EEFF626AFD4B0EA80932A169FFF7E1F903 -:1051E0006178606A022904D0012103E044E18DE086 -:1051F000BFE0002101EB4101182606EBC101A278B6 -:1052000040580EA9F9F719F96178606A022901D0AE -:10521000012100E0002101EB410106EBC1014158F1 -:10522000A0780B18C0F1100200211846F9F72FF9E9 -:1052300005208DF82000686A0D90A8690A90ADF8E5 -:1052400024A08DF830B0062101706278616A022ACC -:1052500001D0012200E0002202EB420206EBC20272 -:10526000401C89581022F9F7E8F8002108A8FFF738 -:10527000BBF91220C5F818B028708DF82090686A24 -:105280000D900B208DF8240005E43878052870D1A6 -:105290008DF82000686A0D90B8680A900B20ADF870 -:1052A00024000A98072101706178626A022901D0FE -:1052B000012100E0002101EB4103102101EBC301BA -:1052C00051580988A0F801106178626A022902D059 -:1052D000012101E02FE1002101EB4103142101EB49 -:1052E000C30151580A6840F8032F4968416059E0EA -:1052F0001920287001208DF8300074E616202870DF -:105300008DF830B0002108A8FFF76EF9032617E1E9 -:1053100014202870AEE6387805282AD18DF82000B0 -:10532000686A0D90B8680A90ADF824A08DF830B086 -:1053300080F800906278616A4E46022A01D001220C -:1053400000E0002202EB42021C2303EBC202401CDD -:1053500089581022F9F771F8002108A8FFF744F9DD -:10536000152028708DF82060686A0D908DF82460F3 -:1053700039E680E0387805287DD18DF82000686A0C -:105380000D90B8680A90ADF8249009210170616908 -:10539000097849084170616951F8012FC0F802206D -:1053A0008988C18020781C28A8D1A1E7E078C007AF -:1053B00002D04FF0060C01E04FF0070C6078022895 -:1053C0000AD000BF4FF0000000EB040101F1090119 -:1053D00005D04FF0010004E04FF00100F4E74FF07A -:1053E00000000B78204413EA0C030B7010F8092F0F -:1053F00002EA0C02027004D14FF01B0C84F800C0CA -:10540000D2B394F801C0BCF1010F00D09BB990F861 -:1054100000C0E0465FEACC7C04D028F001060670AC -:10542000102606E05FEA887C05D528F002060670A3 -:1054300013262E70032694F801C0BCF1020F00D091 -:1054400092B991F800C05FEACC7804D02CF0010644 -:105450000E70172106E05FEA8C7805D52CF0020665 -:105460000E701921217000260078D0BBCAB3C3BBCF -:105470001C20207035E012E002E03878062841D187 -:105480001A2015E4207801283CD00C283AD0204678 -:10549000FFF7EBF809208DF82000686A0D9031E0E5 -:1054A0003878052805D00620387003261820287083 -:1054B00046E005208DF82000696A0D91B9680A91CF -:1054C0000221ADF8241001218DF830100A990870DE -:1054D000287D4870394608A8FFF786F80646182048 -:1054E0002870012E0ED02BE001208DF82000686A74 -:1054F0000D9003208DF82400287D8DF8250085F877 -:1055000014B012E0287D80B11D2020701720287073 -:105510008DF82090686A0D9002208DF8240039469D -:1055200008A8FFF761F806460AE00CB1FE202070DB -:105530009DF8200020B1002108A8FFF755F80CE4E1 -:1055400013B03046BDE8F08F2DE9F04387B00C462C -:105550004E6900218DF804100120257803460227AA -:105560004FF007094FF0050C85B1012D53D0022DE6 -:1055700039D1FE2030708DF80030606A059003202C -:105580008DF80400207E8DF8050063E02179012963 -:1055900025D002292DD0032928D0042923D1B17D7B -:1055A000022920D131780D1F042D04D30A3D032D8B -:1055B00001D31D2917D12189022914D38DF8047034 -:1055C000237020899DF80410884201E0686202007F -:1055D00018D208208DF80000606A059057E07078B6 -:1055E0000128EBD0052007B0BDE8F0831D20307006 -:1055F000E4E771780229F5D131780C29F3D18DF8DF -:105600000490DDE7083402F804CB94E80B0082E84C -:105610000B000320E7E71578052DE4D18DF800C0D5 -:10562000656A0595956802958DF8101094F80480C8 -:10563000B8F1010F13D0B8F1020F2DD0B8F1030F5C -:105640001CD0B8F1040FCED1ADF804700E20287034 -:10565000207E687000216846FEF7C6FF0CE0ADF8BA -:1056600004700B202870207E002100F01F0068705D -:105670006846FEF7B9FF37700020B4E7ADF8047054 -:105680008DF8103085F800C0207E687027701146B4 -:105690006846FEF7A9FFA6E7ADF804902B70207FBF -:1056A0006870607F00F00100A870A07F00F01F000C -:1056B000E870E27F2A71C0071CD094F8200000F047 -:1056C0000700687194F8210000F00700A87100211C -:1056D0006846FEF789FF2868F062A8883086A879B6 -:1056E00086F83200A069407870752879B0700D2076 -:1056F0003070C1E7A9716971E9E700B587B0042886 -:105700000CD101208DF800008DF8040000200591D7 -:105710008DF8050001466846FEF766FF07B000BD3C -:1057200070B50C46054602F0C9F921462846BDE889 -:1057300070407823002202F017B908B10078704752 -:105740000C20704770B50C0005784FF000010CD0AC -:1057500021702146EFF7D1FD69482178405D8842EC -:1057600001D1032070BD022070BDEFF7C6FD0020FF -:1057700070BD0279012A05D000220A704B78012BF6 -:1057800002D003E0042070470A758A610279930011 -:10579000521C0271C15003207047F0B587B00F460C -:1057A00005460124287905EB800050F8046C7078D8 -:1057B000411E02290AD252493A46083901EB8000BB -:1057C000314650F8043C2846984704460CB1012C59 -:1057D00011D12879401E10F0FF00287101D0032458 -:1057E000E0E70A208DF80000706A0590002101961C -:1057F0006846FFF7A7FF032CD4D007B02046F0BDC2 -:1058000070B515460A46044629461046FFF7C5FFFF -:10581000064674B12078FE280BD1207C30B10020E0 -:105820002870294604F10C00FFF7B7FF2046FEF769 -:105830001CFF304670BD704770B50E4604467C2292 -:105840000021F8F724FE0225012E03D0022E04D0F9 -:10585000052070BD0120607000E065702046FEF7F5 -:1058600004FFA575002070BD28B1027C1AB10A465C -:1058700000F10C01C4E70120704710B5044686B062 -:10588000042002F01BF92078FE2806D000208DF8B5 -:10589000000069462046FFF7E7FF06B010BD7CB563 -:1058A0000E4600218DF804104178012903D0022909 -:1058B00003D0002405E0046900E044690CB1217CB8 -:1058C00089B16D4601462846FFF753FF032809D1E9 -:1058D000324629462046FFF793FF9DF80410002921 -:1058E00000D004207CBD04F10C05EBE730B40C467D -:1058F0000146034A204630BC024B0C3AFEF751BE2B -:10590000AC6202006862020070B50D46040011D05E -:1059100085B1220100212846F8F7B9FD102250492F -:105920002846F8F78AFD4F48012101704470456010 -:10593000002070BD012070BD70B505460024494EA1 -:1059400011E07068AA7B00EB0410817B914208D1C2 -:10595000C17BEA7B914204D10C222946F8F740FD35 -:1059600030B1641CE4B230788442EAD3002070BDC8 -:10597000641CE0B270BD70B50546FFF7DDFF00287E -:1059800005D1384C20786178884201D3002070BD61 -:105990006168102201EB00102946F8F74EFD2078CF -:1059A000401CC0B2207070BD2E48007870472D4951 -:1059B0000878012802D0401E08700020704770B59A -:1059C0000D460021917014461180022802D0102843 -:1059D00015D105E0288890B10121A17010800CE05C -:1059E000284613B1FFF7C7FF01E0FFF7A5FFA0703E -:1059F00010F0FF0F03D0A8892080002070BD012087 -:105A000070BD0023DBE770B5054614460E0009D0D3 -:105A100000203070A878012806D003D911490A78EF -:105A200090420AD9012070BD24B1287820702888BE -:105A3000000A5070022008700FE064B1496810221B -:105A400001EB001120461039F8F7F7FC2878207395 -:105A50002888000A607310203070002070BD00009C -:105A6000BB620200900000202DE9F04190460C46F8 -:105A700007460025FE48072F00EB881607D2DFE80F -:105A800007F00707070704040400012500E0FFDF13 -:105A900006F81470002D13D0F548803000EB880113 -:105AA00091F82700202803D006EB4000447001E065 -:105AB00081F8264006EB44022020507081F82740F0 -:105AC000BDE8F081F0B51F4614460E460546202A73 -:105AD00000D1FFDFE649E648803100EB871C0CEB84 -:105AE000440001EB8702202E07D00CEB46014078E2 -:105AF0004B784870184620210AE092F8253040780B -:105B000082F82500F6E701460CEB4100057040786D -:105B1000A142F8D192F82740202C03D00CEB44048A -:105B2000637001E082F826300CEB4104202363709F -:105B300082F82710F0BD30B50D46CE4B4419002237 -:105B4000181A72EB020100D2FFDFCB48854200DD5C -:105B5000FFDFC9484042854200DAFFDFC548401CEC -:105B6000844207DA002C01DB204630BDC148401CCE -:105B7000201830BDBF48C043FAE710B5044601689D -:105B8000407ABE4A52F82020114450B10220084405 -:105B900020F07F40EDF763F894F90810BDE810405D -:105BA000C9E70420F3E72DE9F047B14E803696F8B7 -:105BB0002D50DFF8BC9206EB850090F8264034E0CB -:105BC00009EB85174FF0070817F81400012806D0D5 -:105BD00004282ED005282ED0062800D0FFDF01F0A3 -:105BE00025F9014607EB4400427806EB850080F872 -:105BF000262090F82720A24202D1202280F82720D8 -:105C0000084601F01EF92A4621460120FFF72CFF25 -:105C10009B48414600EB041002682046904796F8E6 -:105C20002D5006EB850090F82640202CC8D1BDE809 -:105C3000F087022000E003208046D0E710B58C4CAE -:105C40002021803484F8251084F8261084F8271049 -:105C5000002084F8280084F82D0084F82E10411EBE -:105C6000A16044F8100B2074607420736073A073FB -:105C70008449E07720750870487000217C4A103C08 -:105C800002F81100491CC9B22029F9D30120ECF710 -:105C9000D6FE0020ECF7D3FE012084F82200EDF7B9 -:105CA000FFF87948EDF711F9764CA41E207077487B -:105CB000EDF70BF96070BDE81040ECF74DBE10B584 -:105CC000ECF76FFE6F4CA41E2078EDF717F96078A3 -:105CD000EDF714F9BDE8104001F0E0B8202070475E -:105CE0000020ECF785BE70B5054601240E46AC4099 -:105CF0005AB1FFF7F5FF0146654800EBC500C0F853 -:105D00001015C0F81465634801E06248001D046086 -:105D100070BD2DE9F34F564C0025803404EB810A09 -:105D200089B09AF82500202821D0691E0291544993 -:105D3000009501EB0017391D03AB07C983E8070085 -:105D4000A18BADF81C10A07F8DF81E009DF81500EA -:105D5000A046C8B10226494951F820400399A2192A -:105D6000114421F07F41019184B102210FE0012013 -:105D7000ECF765FE0020ECF762FEECF730FE01F078 -:105D80008DF884F82F50A9E00426E4E700218DF86F -:105D90001810022801D0012820D103980119099870 -:105DA000081A801C9DF81C1020F07F4001B10221D0 -:105DB000353181420BD203208DF815000398C4F1D0 -:105DC0003201401A20F07F40322403900CE098F812 -:105DD000240018B901F043FA002863D0322C03D212 -:105DE00014B101F04FF801E001F058F8254A10789D -:105DF00018B393465278039B121B00219DF818405C -:105E0000994601281AD0032818D000208DF81E00CA -:105E1000002A04DD981A039001208DF818009DF8DF -:105E20001C0000B1022103981B4A20F07F40039020 -:105E300003AB099801F03EF810B110E00120E5E74E -:105E40009DF81D0018B99BF80000032829D08DF893 -:105E50001C50CDF80C908DF818408DF81E509DF810 -:105E6000180010B30398012381190022184615E089 -:105E7000840A0020FF7F841E0020A107CC6202005C -:105E8000840800209A00002017780100A75B010019 -:105E900000F0014004F50140FFFF3F00ECF722FE57 -:105EA00006E000200BB0BDE8F08F0120ECF7C7FD45 -:105EB00097F90C20012300200199ECF713FEF87BE1 -:105EC000C00701D0ECF7F7FE012188F82F108AF8FF -:105ED000285020226946FE48F8F7AFFA0120E1E792 -:105EE0002DE9F05FDFF8E883064608EB860090F8BE -:105EF0002550202D1FD0A8F180002C4600EB8617DE -:105F0000A0F50079DFF8CCB305E0A24607EB4A0024 -:105F10004478202C0AD0ECF730FE09EB04135A46E3 -:105F200001211B1D00F0C6FF0028EED0AC4202D0BC -:105F3000334652461EE0E84808B1AFF30080ECF764 -:105F40001CFE98F82F206AB1D8F80C20411C891A41 -:105F50000902CA1701EB12610912002902DD0020B3 -:105F6000BDE8F09F3146FFF7D4FE08B10120F7E706 -:105F700033462A4620210420FFF7A4FDEFE72DE950 -:105F8000F041D34C2569ECF7F8FD401B0002C11726 -:105F900000EB1160001200D4FFDF94F8220000B182 -:105FA000FFDF012784F8227094F82E00202800D10A -:105FB000FFDF94F82E60202084F82E00002584F85E -:105FC0002F5084F8205084F82150C4482560007870 -:105FD000022833D0032831D000202077A068401C4D -:105FE00005D04FF0FF30A0600120ECF728FD002025 -:105FF000ECF725FDECF721FEECF719FEECF7EFFCD2 -:106000000EF0D6FDB648056005604FF0E0214FF474 -:106010000040B846C1F88002ECF7BBFE94F82D7042 -:106020003846FFF75DFF0028FAD0A948803800EB1A -:10603000871010F81600022802D006E00120CCE7F5 -:106040003A4631460620FFF70FFD84F8238004EB23 -:10605000870090F82600202804D0A048801E4078B1 -:10606000ECF752FF207F002803D0ECF7D6FD257710 -:10607000657725E5964910B591F82D2000248039E3 -:1060800001EB821111F814302BB1641CE4B2202C06 -:10609000F8D3202010BD934901EB041108600020C3 -:1060A000C87321460120FFF7DFFC204610BD10B564 -:1060B000012801D0032800D171B3854A92F82D3010 -:1060C000834C0022803C04EB831300BF13F8124082 -:1060D0000CB1082010BD521CD2B2202AF6D37F4A40 -:1060E00048B1022807D0072916D2DFE801F01506CB -:1060F000080A0C0E100000210AE01B2108E03A21DA -:1061000006E0582104E0772102E0962100E0B52165 -:1061100051701070002010BD072010BD6F4810B5E1 -:106120004078ECF79CFD80B210BD10B5202811D24C -:10613000674991F82D30A1F1800202EB831414F825 -:1061400010303BB191F82D3002EB831212F8102081 -:10615000012A01D0002010BD91F82D200146002019 -:10616000FFF782FC012010BD10B5ECF706FDBDE87D -:106170001040ECF774BD2DE9F0410E46544F017804 -:106180002025803F0C4607EB831303E0254603EBF5 -:1061900045046478944202D0202CF7D108E0202CEA -:1061A00006D0A14206D103EB41014978017007E016 -:1061B000002085E403EB440003EB45014078487080 -:1061C000494F7EB127B1002140F22D40AFF300804E -:1061D0003078A04206D127B100214FF48660AFF39A -:1061E0000080357027B1002140F23540AFF30080C8 -:1061F000012065E410B542680B689A1A1202D417A0 -:1062000002EB1462121216D4497A91B1427A82B921 -:10621000364A006852F82110126819441044001DD3 -:10622000891C081A0002C11700EB11600012322805 -:1062300001DB012010BD002010BD2DE9F047294EE3 -:10624000814606F500709846144600EB811712E06F -:1062500006EB0415291D4846FFF7CCFF68B988F8FE -:106260000040A97B99F80A00814201D80020DEE4B1 -:1062700007EB44004478202CEAD10120D7E42DE933 -:10628000F047824612480E4600EB8600DFF8548045 -:1062900090F825402020107008F5007099461546AA -:1062A00000EB86170BE000BF08EB04105146001D01 -:1062B000FFF7A0FF28B107EB44002C704478202C96 -:1062C000F2D1297889F800104B46224631460FE07A -:1062D000040B0020FFFF3F00000000009A00002098 -:1062E00000F500408408002000000000CC6202009D -:1062F0005046BDE8F047A0E72DE9FC410F460446B3 -:106300000025FE4E10E000BF9DF80000256806EB5A -:1063100000108168204600F0E1FD2068A84202D10B -:106320000020BDE8FC8101256B4601AA39462046C4 -:10633000FFF7A5FF0028E7D02846F2E770B504462E -:10634000EF480125A54300EB841100EB85104022A6 -:10635000F8F773F8EB4E26B1002140F29D40AFF301 -:106360000080E748803000EB850100EB8400D0F826 -:106370002500C1F8250026B1002140F2A140AFF36D -:106380000080284670BD8A4203D003460520FFF7EF -:1063900099BB202906D0DA4A02EB801000EB4100BD -:1063A00040787047D649803101EB800090F8250095 -:1063B0007047D24901EB0010001DFFF7DEBB7CB532 -:1063C0001D46134604460E4600F1080221461846B3 -:1063D000ECF752FC94F908000F2804DD1F382072F6 -:1063E0002068401C206096B10220C74951F8261051 -:1063F000461820686946801B20F07F40206094F991 -:1064000008002844C01C1F2803DA012009E00420EA -:10641000EBE701AAECF730FC9DF8040010B10098FE -:10642000401C00900099206831440844C01C20F0B2 -:106430007F4060607CBDFEB50C46064609786079F9 -:10644000907220791F461546507279B12179002249 -:106450002846A368FFF7B3FFA9492846803191F881 -:106460002E20202A0AD00969491D0DE0D4E9022313 -:10647000217903B02846BDE8F040A0E7A349497858 -:10648000052900D20521314421F07F4100F026FD8D -:1064900039462846FFF730FFD4E9023221796846B1 -:1064A000FFF78DFF2B4600213046019A00F002FDD8 -:1064B000002806D103B031462846BDE8F04000F080 -:1064C0000DBDFEBD2DE9F14F84B000F0C3FCF0B16D -:1064D00000270498007800284FF000006DD1884D07 -:1064E000884C82468346803524B1002140F2045016 -:1064F000AFF3008095F82D8085F823B0002624B1F5 -:10650000002140F20950AFF3008017B105E00127E8 -:10651000DFE74046FFF712FF804624B1002140F23A -:106520001150AFF30080ECF728FB814643466A46E2 -:106530000499FFF780FF24B1002140F21750AFF318 -:10654000008095F82E0020280CD029690098401A68 -:106550000002C21700EB1260001203D5684600F07B -:10656000BDFC01264CB1002140F22150AFF3008068 -:10657000002140F22650AFF300806B46644A0021B0 -:10658000484600F097FC98B127B941466846FFF7A6 -:10659000B3FE064326B16846FFF7EFFA0499886018 -:1065A0004FF0010A24B1002140F23A50AFF30080CD -:1065B00095F82300002897D1504605B073E42DE9E3 -:1065C000F04F89B08B46824600F044FC4C4C80343E -:1065D00030B39BF80000002710B1012800D0FFDF86 -:1065E000484D25B1002140F2F950AFF300804349F6 -:1065F000012001EB0A18A946CDF81C005FEA090644 -:1066000004D0002140F20160AFF30080079800F051 -:1066100018FC94F82D50002084F8230067B119E08D -:1066200094F82E000127202800D1FFDF9BF80000FE -:106630000028D5D0FFDFD3E72846FFF77FFE0546C9 -:1066400026B1002140F20B60AFF3008094F82300E4 -:106650000028D3D126B1002140F21560AFF30080AD -:10666000ECF78BFA2B4602AA59460790FFF7E3FE98 -:1066700098F80F005FEA060900F001008DF813009A -:1066800004D0002140F21F60AFF300803B462A4651 -:1066900002A9CDF800A0079800F02BFC064604EBF9 -:1066A000850090F828000090B9F1000F04D0002177 -:1066B00040F22660AFF3008000F0B8FB0790B9F11C -:1066C000000F04D0002140F22C60AFF3008094F85A -:1066D0002300002892D1B9F1000F04D0002140F22C -:1066E0003460AFF300800DF1080C9CE80E00C8E99F -:1066F0000112C8F80C30BEB30CE000008408002082 -:10670000840A002000000000CC6202009A000020F1 -:10671000FFFF3F005FEA090604D0002140F241601C -:10672000AFF300800098B84312D094F82E002028D0 -:106730000ED126B1002140F24660AFF3008028461A -:10674000FFF7CEFB20B99BF80000D8B3012849D051 -:10675000B9F1000F04D0002140F26360AFF3008074 -:10676000284600F05CFB01265FEA090504D0002101 -:1067700040F26C60AFF30080079800F062FB25B137 -:1067800000214FF4CE60AFF300808EB194F82D005D -:1067900004EB800090F82600202809D025B10021C4 -:1067A00040F27760AFF30080F7484078ECF7ACFB3D -:1067B00025B1002140F27C60AFF3008009B0304683 -:1067C000BDE8F08FFFE7B9F1000F04D0002140F2DF -:1067D0004E60AFF3008094F82D2051460420FFF75F -:1067E00043F9C0E7002E3FF409AF002140F25960A1 -:1067F000AFF3008002E72DE9F84FE44D814695F8AC -:106800002D004FF00008E24C4FF0010B474624B139 -:10681000002140F28A60AFF30080584600F011FB7F -:1068200085F8237024B1002140F28F60AFF300801F -:1068300095F82D00FFF782FD064695F8230028B154 -:10684000002CE4D0002140F295604BE024B10021FF -:1068500040F29960AFF30080CC48803800EB86119D -:1068600011F81900032856D1334605EB830A4A462E -:106870009AF82500904201D1012000E0002000900C -:106880000AF125000021FFF776FC0146009801423D -:1068900003D001228AF82820AF77E1B324B1002188 -:1068A00040F29E60AFF30080324649460120FFF778 -:1068B000DBF89AF828A024B1002140F2A960AFF3D8 -:1068C000008000F0B3FA834624B1002140F2AE60AC -:1068D000AFF3008095F8230038B1002C97D0002149 -:1068E00040F2B260AFF3008091E7BAF1000F07D039 -:1068F00095F82E00202803D13046FFF7F1FAE0B1D9 -:1069000024B1002140F2C660AFF30080304600F0B1 -:1069100086FA4FF0010824B1002140F2CF60AFF3B6 -:106920000080584600F08DFA24B1002140F2D36077 -:10693000AFF300804046BDE8F88F002CF1D0002175 -:1069400040F2C160AFF30080E6E70120ECF750B8F9 -:106950008D48007870472DE9F0418C4C94F82E005A -:1069600020281FD194F82D6004EB860797F8255056 -:10697000202D00D1FFDF8549803901EB861000EB27 -:106980004500407807F8250F0120F87084F82300AF -:10699000294684F82E50324602202234FFF764F84C -:1069A0000020207005E42DE9F0417A4E774C012556 -:1069B00038B1012821D0022879D003287DD0FFDF0B -:1069C00017E400F05FFAFFF7C6FF207E00B1FFDF9B -:1069D00084F821500020ECF732F8A168481C04D05C -:1069E000012300221846ECF77DF814F82E0F2178C9 -:1069F00006EB01110A68012154E0FFF7ACFF01200A -:106A0000ECF71DF894F8210050B1A068401C07D0A5 -:106A100014F82E0F217806EB01110A68062141E0D7 -:106A2000207EDFF86481002708F10208012803D0E6 -:106A300002281ED0FFDFB5E7A777ECF7EEF898F84D -:106A40000000032801D165772577607D524951F810 -:106A5000200094F8201051B948B161680123091A47 -:106A600000221846ECF73EF8022020769AE72776B7 -:106A700098E784F8205000F005FAA07F50B198F80C -:106A8000010061680123091A00221846ECF72AF870 -:106A9000257600E0277614F82E0F217806EB0111F9 -:106AA0000A680021BDE8F041104700E005E03648E3 -:106AB0000078BDE8F041ECF727BAFFF74CFF14F877 -:106AC0002E0F217806EB01110A680521EAE710B5BF -:106AD0002E4C94F82E00202800D1FFDF14F82E0F42 -:106AE00021782C4A02EB01110A68BDE8104004210C -:106AF00010477CB5254C054694F82E00202800D17F -:106B0000FFDFA068401C00D0FFDF94F82E00214971 -:106B100001AA01EB0010694690F90C002844ECF73B -:106B2000ABF89DF904000F2801DD012000E00020F2 -:106B3000009908446168084420F07F41A16094F8FE -:106B40002100002807D002B00123BDE870400022D8 -:106B50001846EBF7C7BF7CBD30B5104A0B1A541C62 -:106B6000B3EB940F1ED3451AB5EB940F1AD393428F -:106B700003D9101A43185B1C14E0954210D9511A1E -:106B80000844401C43420DE098000020040B002004 -:106B90000000000084080020CC620200FF7F841EF9 -:106BA000FFDF0023184630BD0123002201460220EA -:106BB000EBF798BF0220EBF742BFEBF7DEBF2DE902 -:106BC000FE4FEE4C05468A4694F82E00202800D150 -:106BD000FFDFEA4E94F82E10A0462046A6F520725C -:106BE00002EB011420218DF8001090F82D10376968 -:106BF00000EB8101D8F8000091F82590284402AA02 -:106C000001A90C36ECF738F89DF90800002802DDE0 -:106C10000198401C0190A0680199642D084452D34A -:106C2000D74B00225B1B72EB02014CD36168411A07 -:106C300021F07F41B1F5800F45D220F07F40706098 -:106C400086F80AA098F82D1044466B464A4630460E -:106C5000FFF7F3FAB0B3A068401C10D0EBF78DFF3C -:106C6000A168081A0002C11700EB11600012022887 -:106C70002BDD0120EBF7E3FE4FF0FF30A06094F82E -:106C80002D009DF8002020210F34FFF77CFBA17F11 -:106C9000BA4A803A02EB8111E27F01EB420148706F -:106CA00054F80F0C284444F80F0C012020759DF86F -:106CB0000000202803D0B3484078ECF725F90120E4 -:106CC000BDE8FE8F01E00020FAE77760FBE72DE9E1 -:106CD000F047AA4C074694F82D00A4F1800606EB75 -:106CE000801010F8170000B9FFDF94F82D50A0466F -:106CF000A54C24B1002140F6EA00AFF3008040F635 -:106D0000F60940F6FF0A06EB851600BF16F81700D5 -:106D1000012819D0042811D005280FD006280DD03D -:106D20001CB100214846AFF300800FF02DF8002C75 -:106D3000ECD000215046AFF30080E7E72A46394601 -:106D40000120FEF791FEF2E74FF0010A4FF0000933 -:106D5000454624B1002140F60610AFF300805046AE -:106D600000F06FF885F8239024B1002140F60B1055 -:106D7000AFF3008095F82D00FFF7E0FA064695F88E -:106D8000230028B1002CE4D0002140F611101FE0B0 -:106D900024B1002140F61510AFF3008005EB86000A -:106DA00000F1270133463A462630FFF7E4F924B1D3 -:106DB000002140F61910AFF3008000F037F882464A -:106DC00095F8230038B1002CC3D0002140F61F10E5 -:106DD000AFF30080BDE785F82D60012085F8230022 -:106DE000504600F02EF8002C04D0002140F62C1064 -:106DF000AFF30080BDE8F08730B504465F480D462C -:106E000090F82D005D49803901EB801010F81400D6 -:106E100000B9FFDF5D4800EB0410C57330BD574972 -:106E200081F82D00012081F82300704710B55848E3 -:106E300008B1AFF30080EFF3108000F0010072B6EC -:106E400010BD10B5002804D1524808B1AFF300803E -:106E500062B610BD50480068C005C00D10D0103893 -:106E600040B2002804DB00F1E02090F8000405E0C7 -:106E700000F00F0000F1E02090F8140D4009704779 -:106E80000820704710B53D4C94F82400002804D128 -:106E9000F4F712FF012084F8240010BD10B5374C20 -:106EA00094F82400002804D0F4F72FFF002084F881 -:106EB000240010BD10B51C685B68241A181A24F051 -:106EC0007F4420F07F40A14206D8B4F5800F03D262 -:106ED000904201D8012010BD002010BDD0E9003241 -:106EE000D21A21F07F43114421F07F41C0E90031E3 -:106EF00070472DE9FC418446204815468038089C9F -:106F000000EB85160F4616F81400012804D002285D -:106F100002D00020BDE8FC810B46204A01216046DA -:106F2000FFF7C8FFF0B101AB6A4629463846FFF7C4 -:106F3000A6F9B8B19DF804209DF800102846FFF787 -:106F400022FA06EB440148709DF8000020280DD07D -:106F500006EB400044702A4621460320FEF784FDDC -:106F60000120D7E72A4621460420F7E703480121FC -:106F700000EB850000F8254FC170ECE7040B002002 -:106F8000FF1FA107980000200000000084080020D7 -:106F9000000000000000000004ED00E0FFFF3F00E3 -:106FA0002DE9F041044680074FF000054FF001063F -:106FB0000CD56B480560066000F0E8F920B169481F -:106FC000016841F48061016024F00204E0044FF0A4 -:106FD000FF3705D564484660C0F8087324F4805430 -:106FE000600003D56148056024F08044E0050FD5BA -:106FF0005F48C0F80052C0F808735E490D60091D73 -:107000000D605C4A04210C321160066124F4807426 -:10701000A00409D558484660C0F80052C0F808736B -:107020005648056024F40054C4F38030C4F3C031E2 -:10703000884200D0FFDF14F4404F14D0504846601F -:10704000C0F808734F488660C0F80052C0F8087353 -:107050004D490D600A1D16608660C0F808730D600A -:10706000166024F4404420050AD5484846608660EE -:10707000C0F80873C0F848734548056024F40064FC -:107080000DF070FD4348044200D0FFDFBDE8F08101 -:10709000F0B50022202501234FEA020420FA02F174 -:1070A000C9072DD051B2002910DB00BF4FEA51179C -:1070B0004FEA870701F01F0607F1E02703FA06F6FB -:1070C000C7F88061BFF34F8FBFF36F8F0CDB00BF3A -:1070D0004FEA51174FEA870701F01F0607F1E02733 -:1070E00003FA06F6C7F8806204DB01F1E02181F8BB -:1070F000004405E001F00F0101F1E02181F8144D99 -:1071000002F10102AA42C9D3F0BD10B5224C2060A1 -:107110000846F4F7EAFE2068FFF742FF2068FFF711 -:10712000B7FF0DF045F900F092F90DF01BFD0DF0E1 -:1071300058FCEBF7B5FEBDE810400DF0EDB910B509 -:10714000154C2068FFF72CFF2068FFF7A1FF0DF01A -:1071500009FDF4F7C9FF0020206010BD0A20704728 -:10716000FC1F00403C17004000C0004004E5014007 -:10717000008000400485004000D0004004D500405D -:1071800000E0004000F0004000F5004000B000408A -:1071900008B50040FEFF0FFD9C00002070B5264999 -:1071A0000A680AB30022154601244B685B1C4B6039 -:1071B0000C2B00D34D600E7904FA06F30E681E42C4 -:1071C0000FD0EFF3108212F0010272B600D001224C -:1071D0000C689C430C6002B962B6496801600020EB -:1071E00070BD521C0C2AE0D3052070BD4FF0E02189 -:1071F0004FF48000C1F800027047EFF3108111F0E6 -:10720000010F72B64FF0010202FA00F20A48036859 -:1072100042EA0302026000D162B6E7E706480021B5 -:1072200001604160704701218140034800680840C7 -:1072300000D0012070470000A0000020012081073D -:10724000086070470121880741600021C0F80011E3 -:1072500018480170704717490120087070474FF0B7 -:107260008040D0F80001012803D01248007800289F -:1072700000D00120704710480068C00700D00120EE -:1072800070470D480C300068C00700D001207047DF -:107290000948143000687047074910310A68D20362 -:1072A00006D5096801F00301814201D10120704730 -:1072B00000207047A8000020080400404FF08050D4 -:1072C000D0F830010A2801D0002070470120704713 -:1072D00000B5FFF7F3FF20B14FF08050D0F8340134 -:1072E00008B1002000BD012000BD4FF08050D0F853 -:1072F00030010E2801D000207047012070474FF068 -:107300008050D0F83001062803D0401C01D0002066 -:107310007047012070474FF08050D0F830010D28A1 -:1073200001D000207047012070474FF08050D0F806 -:107330003001082801D000207047012070474FF02D -:107340008050D0F83001102801D000207047012073 -:10735000704700B5FFF7F3FF30B9FFF7DCFF18B94E -:10736000FFF7E3FF002800D0012000BD00B5FFF7C4 -:10737000C6FF38B14FF08050D0F83401062803D34F -:10738000401C01D0002000BD012000BD00B5FFF76A -:10739000B6FF48B14FF08050D0F83401062803D32F -:1073A000401C01D0012000BD002000BD0021017063 -:1073B000084670470146002008707047EFF31081BF -:1073C00001F0010172B60278012A01D0012200E029 -:1073D00000220123037001B962B60AB10020704790 -:1073E0004FF400507047E9E7EFF3108111F0010FFF -:1073F00072B64FF00002027000D162B600207047F2 -:10740000F2E700002DE9F04115460E46044600273C -:1074100000F0EBF8A84215D3002341200FE000BF95 -:1074200094F84220A25CF25494F84210491CB1FB3B -:10743000F0F200FB12115B1C84F84210DBB2AB428D -:10744000EED3012700F0DDF83846BDE8F08172493F -:1074500010B5802081F800047049002081F84200B6 -:1074600081F84100433181F8420081F84100433105 -:1074700081F8420081F841006948FFF797FF6848AA -:10748000401CFFF793FFEBF793FCBDE8104000F0C2 -:10749000B8B840207047614800F0A7B80A460146D6 -:1074A0005E48AFE7402070475C48433000F09DB82D -:1074B0000A46014659484330A4E7402101700020A4 -:1074C000704710B504465548863000F08EF820709D -:1074D000002010BD0A460146504810B58630FFF71F -:1074E00091FF08B1002010BD42F2070010BD70B539 -:1074F0000C460646412900D9FFDF4A48006810388B -:1075000040B200F054F8C5B20D2000F050F8C0B2FF -:10751000854201D3012504E0002502E00DB1EBF71F -:107520008AFC224631463D48FFF76CFF0028F5D023 -:1075300070BD2DE9F0413A4F0025064617F10407CA -:1075400057F82540204600F041F810B36D1CEDB20D -:10755000032DF5D33148433000F038F8002825D00A -:107560002E4800F033F8002820D02C48863000F058 -:107570002DF800281AD0EBF734FC2948FFF71EFF3E -:10758000B0F5005F00D0FFDFBDE8F0412448FFF711 -:107590002BBF94F841004121265414F8410F401CA0 -:1075A000B0FBF1F201FB12002070D3E74DE7002899 -:1075B00004DB00F1E02090F8000405E000F00F008B -:1075C00000F1E02090F8140D4009704710F8411FB9 -:1075D0004122491CB1FBF2F302FB131140788142B6 -:1075E00001D1012070470020704710F8411F4078FA -:1075F000814201D3081A02E0C0F141000844C0B240 -:10760000704710B50648FFF7D9FE002803D1BDE842 -:107610001040EBF7D1BB10BD0DE000E0340B0020B3 -:10762000AC00002004ED00E070B5154D2878401C3A -:10763000C4B26878844202D000F0DBFA2C7070BDCE -:107640002DE9F0410E4C4FF0E02600BF00F0C6FAE5 -:107650000EF09AFB40BF20BF677820786070D6F8A4 -:107660000052E9F798FE854305D1D6F8040210B917 -:107670002078B842EAD000F0ACFA0020BDE8F081F2 -:10768000BC0000202DE9F04101264FF0E02231033B -:107690004FF000084046C2F88011BFF34F8FBFF390 -:1076A0006F8F204CC4F800010C2000F02EF81E4D06 -:1076B0002868C04340F30017286840F01000286095 -:1076C000C4F8046326607F1C02E000BF0EF05CFB80 -:1076D000D4F800010028F9D01FB9286820F0100064 -:1076E0002860124805686660C4F80863C4F8008121 -:1076F0000C2000F00AF82846BDE8F08110B50446D9 -:10770000FFF7C0FF2060002010BD002809DB00F05B -:107710001F02012191404009800000F1E020C0F8E3 -:107720008012704700C0004010ED00E008C5004026 -:107730002DE9F047FF4C0646FF21A06800EB06123A -:1077400011702178FF2910D04FF0080909EB0111C1 -:1077500009EB06174158C05900F0F4F9002807DD7D -:10776000A168207801EB061108702670BDE8F0874B -:1077700094F8008045460DE0A06809EB05114158DA -:10778000C05900F0DFF9002806DCA068A84600EB2D -:1077900008100578FF2DEFD1A06800EB061100EB73 -:1077A00008100D700670E1E7F0B5E24B04460020CA -:1077B00001259A680C269B780CE000BF05EB0017AA -:1077C000D75DA74204D106EB0017D7598F4204D0EA -:1077D000401CC0B28342F1D8FF20F0BD70B5FFF766 -:1077E000ECF9D44C08252278A16805EB02128958DF -:1077F00000F0A8F9012808DD2178A06805EB011147 -:107800004058BDE87040FFF7CFB9FFF7A1F8BDE8D9 -:107810007040EBF779BB2DE9F041C64C2578FFF7B6 -:10782000CCF9FF2D6ED04FF00808A26808EB0516C2 -:10783000915900F087F90228A06801DD80595DE0C8 -:1078400000EB051109782170022101EB0511425C62 -:107850005AB1521E4254815901F5800121F07F41F5 -:1078600081512846FFF764FF34E00423012203EB33 -:10787000051302EB051250F803C0875CBCF1000F42 -:1078800010D0BCF5007F10D9CCF3080250F806C028 -:107890000CEB423C2CF07F4C40F806C0C3589A1ABF -:1078A000520A09E0FF2181540AE0825902EB4C326E -:1078B00022F07F428251002242542846FFF738FFCF -:1078C0000C21A06801EB05114158E06850F8272011 -:1078D000384690472078FF2814D0FFF76EF92278B9 -:1078E000A16808EB02124546895800F02BF90128DF -:1078F00093DD2178A06805EB01114058BDE8F04107 -:10790000FFF752B9BDE8F081F0B51D4614460E46AA -:107910000746FF2B00D3FFDFA00700D0FFDF85481D -:10792000FF210022C0E90247C57006710170427054 -:1079300082701046012204E002EB0013401CE15467 -:10794000C0B2A842F8D3F0BD70B57A4C064665784F -:107950002079854200D3FFDFE06840F82560607839 -:10796000401C6070284670BD2DE9FF5F1D468B46A8 -:107970000746FF24FFF721F9DFF8B891064699F88A -:107980000100B84200D8FFDF00214FF001084FF09E -:107990000C0A99F80220D9F808000EE008EB011350 -:1079A000C35CFF2B0ED0BB4205D10AEB011350F88C -:1079B00003C0DC450CD0491CC9B28A42EED8FF2C6A -:1079C00002D00DE00C46F6E799F803108A4203D185 -:1079D000FF2004B0BDE8F09F1446521C89F8022035 -:1079E00008EB04110AEB0412475440F802B00421DA -:1079F000029B0022012B01EB04110CD040F8012066 -:107A00004FF4007808234FF0020C454513D9E905DF -:107A1000C90D02D002E04550F2E7414606EB413283 -:107A200003EB041322F07F42C250691A0CEB0412DC -:107A3000490A81540BE005B9012506EB453103EBFA -:107A4000041321F07F41C1500CEB0411425499F80A -:107A500000502046FFF76CFE99F80000A84201D0C4 -:107A6000FFF7BCFE3846B4E770B50C460546FFF795 -:107A7000A4F8064621462846FFF796FE0446FF284E -:107A80001AD02C4D082101EB0411A868415830464A -:107A900000F058F800F58050C11700EBD1404013BA -:107AA0000221AA6801EB0411515C09B100EB4120ED -:107AB000002800DC012070BD002070BD2DE9F047DA -:107AC00088468146FFF770FE0746FF281BD0194DF8 -:107AD0002E78A8683146344605E0BC4206D02646DA -:107AE00000EB06121478FF2CF7D10CE0FF2C0AD023 -:107AF000A6420CD100EB011000782870FF2804D0BA -:107B0000FFF76CFE03E0002030E6FFF753F8414634 -:107B10004846FFF7A9FF0123A968024603EB0413B7 -:107B2000FF20C854A878401EB84200D1A87001EBCD -:107B3000041001E0000C002001EB06110078087031 -:107B4000104613E6081A0002C11700EB116000127C -:107B50007047000010B5202000F07FF8202000F0D2 -:107B60008DF84D49202081F80004E9F712FC4B49BB -:107B700008604B48D0F8041341F00101C0F8041329 -:107B8000D0F8041341F08071C0F804134249012079 -:107B90001C39C1F8000110BD10B5202000F05DF8BF -:107BA0003E480021C8380160001D01603D4A481E62 -:107BB00010603B4AC2F80803384B1960C2F8000154 -:107BC000C2F8600138490860BDE81040202000F08C -:107BD00055B834493548091F086070473149334862 -:107BE000086070472D48C8380160001D521E0260B1 -:107BF00070472C4901200860BFF34F8F70472DE973 -:107C0000F0412849D0F8188028480860244CD4F85E -:107C100000010025244E6F1E28B14046E9F712FBF3 -:107C200040B9002111E0D4F8600198B14046E9F76D -:107C300009FB48B1C4F80051C4F860513760BDE891 -:107C4000F041202000F01AB831684046BDE8F0410C -:107C50000EF0A4B8FFDFBDE8F08100280DDB00F0D6 -:107C60001F02012191404009800000F1E020C0F88E -:107C70008011BFF34F8FBFF36F8F7047002809DB70 -:107C800000F01F02012191404009800000F1E02036 -:107C9000C0F880127047000020E000E0C8060240F3 -:107CA00000000240180502400004024001000001EB -:107CB0005E4800210170417010218170704770B5DD -:107CC000054616460C460220EAF714FE57490120E5 -:107CD00008705749F01E086056480560001F046090 -:107CE00070BD10B50220EAF705FE5049012008706A -:107CF00051480021C0F80011C0F80411C0F8081163 -:107D00004E494FF40000086010BD48480178D9B1D1 -:107D10004B4A4FF4000111604749D1F8003100226D -:107D2000002B1CBFD1F80431002B02D0D1F8081170 -:107D300019B142704FF0100104E04FF001014170A1 -:107D400040490968817002704FF00000EAF7D2BD27 -:107D500010B50220EAF7CEFD34480122002102705E -:107D60003548C0F80011C0F80411C0F808110260CD -:107D700010BD2E480178002904BF407870472E4876 -:107D8000D0F80011002904BF02207047D0F800117C -:107D900000291CBFD0F80411002905D0D0F8080133 -:107DA000002804BF01207047002070471F4800B51D -:107DB0000278214B4078C821491EC9B282B1D3F85C -:107DC00000C1BCF1000F10D0D3F8000100281CBF87 -:107DD000D3F8040100280BD0D3F8080150B107E014 -:107DE000022802D0012805D002E00029E4D1FFDFFB -:107DF000002000BD012000BD0C480178002904BF0F -:107E0000807870470C48D0F8001100291CBFD0F8CA -:107E10000411002902D0D0F8080110B14FF0100071 -:107E2000704708480068C0B270470000BE000020DC -:107E300010F5004008F5004000F0004004F5014056 -:107E400008F5014000F400405748002101704170DE -:107E5000704770B5064614460D460120EAF74AFD04 -:107E600052480660001D0460001D05605049002056 -:107E7000C1F850014F490320086050494E4808603E -:107E8000091D4F48086070BD2DE9F0410546464880 -:107E90000C46012606704B4945EA024040F08070CE -:107EA0000860FFF72CFA002804BF47480460002749 -:107EB000464CC4F80471474945480860002D02BF8C -:107EC000C4F800622660BDE8F081012D18BFFFDF15 -:107ED000C4F80072266041493F480860BDE8F0815F -:107EE0003148017871B13B4A394911603749D1F8BD -:107EF00004210021002A08BF417002D0384A1268CC -:107F0000427001700020EAF7F5BC2748017800298B -:107F100004BF407870472D48D0F80401002808BFFE -:107F200070472F480068C0B27047002808BF7047EC -:107F30002DE9F0471C480078002808BFFFDF234CDC -:107F4000D4F80401002818BFBDE8F0874FF00209FB -:107F5000C4F80493234F3868C0F30018386840F021 -:107F600010003860D4F80401002804BF4FF4004525 -:107F70004FF0E02608D100BFC6F880520DF004FF94 -:107F8000D4F804010028F7D0B8F1000F03D1386805 -:107F900020F010003860C4F80893BDE8F0870B4962 -:107FA0000120886070470000C100002008F50040F3 -:107FB000001000401CF500405011004098F50140B1 -:107FC0000CF0004004F5004018F5004000F00040BF -:107FD0000000020308F501400000020204F5014020 -:107FE00000F4004010ED00E0012804BF41F6A47049 -:107FF0007047022804BF41F288307047042804BF4C -:1080000046F218007047082804BF47F2A0307047B6 -:1080100000B5FFDF41F6A47000BD10B5FE48002496 -:1080200001214470047044728472C17280F825404A -:10803000C462846380F83C4080F83D40FF2180F8B2 -:108040003E105F2180F83F1018300DF09FFFF3497C -:10805000601E0860091D0860091D0C60091D08608C -:10806000091D0C60091D0860091D0860091D0860D4 -:10807000091D0860091D0860091D0860091D0860C8 -:10808000091D0860091D086010BDE549486070477A -:10809000E448016801F00F01032904BF0120704783 -:1080A000016801F00F01042904BF02207047016834 -:1080B00001F00F01052904D0006800F00F00062828 -:1080C00007D1D948006810F0060F0CBF0820042023 -:1080D000704700B5FFDF012000BD012812BF022854 -:1080E00000207047042812BF08284FF4C87070475A -:1080F00000B5FFDF002000BD012804BF2820704725 -:10810000022804BF18207047042812BF08284FF423 -:10811000A870704700B5FFDF282000BD70B5C148CA -:10812000016801F00F01032908BF012414D0016880 -:1081300001F00F01042904BF022418210DD00168A9 -:1081400001F00F0105294BD0006800F00F00062850 -:108150001CBFFFDF012443D02821AF48C26A806AD8 -:10816000101A0E18082C04BF4EF6981547F2A030CE -:108170002DD02046042C08BF4EF628350BD0012800 -:1081800008BF41F6A47506D0022C1ABFFFDF41F6E6 -:10819000A47541F28835012C08BF41F6A47016D0B1 -:1081A000022C08BF002005D0042C1ABFFFDF0020DE -:1081B0004FF4C8702D1A022C08BF41F2883006D047 -:1081C000042C1ABFFFDF41F6A47046F21800281AEB -:1081D0004FF47A7100F2E730B0FBF1F0304470BD3B -:1081E0009148006810F0060F0CBF082404244FF4D7 -:1081F000A871B2E710B58D49026801F118040A634D -:1082000042684A63007A81F83800207E48B1207FB6 -:10821000F6F781F9A07E011C18BF0121207FF6F737 -:1082200069F9607E002808BF10BD607FF6F773F91A -:10823000E07E011C18BF0121607FBDE81040F6F709 -:1082400059B930B50024054601290AD0022908BFD2 -:108250004FF0807405D0042916BF08294FF0C74499 -:10826000FFDF44F4847040F480107149086045F4E5 -:10827000403001F1040140F00070086030BD30B5BD -:108280000024054601290AD0022908BF4FF0807456 -:1082900005D0042916BF08294FF0C744FFDF44F476 -:1082A000847040F480106249086045F4403001F168 -:1082B000040140F0007008605E48D0F8000100281A -:1082C00018BFFFDF30BD2DE9F04102274FF0E02855 -:1082D00001250024C8F88071BFF34F8FBFF36F8F63 -:1082E000554804600560FFF751F8544E18B13068E6 -:1082F00040F480603060FFF702F838B1306820F059 -:10830000690040F0960040F0004030604D494C4814 -:1083100008604FF01020806CB0F1FF3F04D04A4954 -:108320000A6860F317420A60484940F25B600860DF -:10833000091F40F203100860081F05603949032037 -:10834000086043480560444A42491160444A434931 -:108350001160121F43491160016821F440710160EE -:10836000016841F480710160C8F8807231491020C1 -:10837000C1F80403284880F83140C46228484068A6 -:10838000002808BFBDE8F081BDE8F0410047274A5A -:108390000368C2F81A308088D08302F11800017295 -:1083A00070471D4B10B51A7A8A4208D10146062241 -:1083B000981CF6F715F8002804BF012010BD002016 -:1083C00010BD154890F825007047134A5170107081 -:1083D0007047F0B50546800000F1804000F5805000 -:1083E0008B88C0F820360B78D1F8011043EA0121C0 -:1083F000C0F8001605F10800012707FA00F61A4C2C -:10840000002A04BF2068B04304D0012A18BFFFDF50 -:1084100020683043206029E0280C0020000E004036 -:10842000C40000201015004014140040100C00205F -:108430001415004000100040FC1F00403C17004095 -:108440002C000089781700408C150040381500403A -:108450005016004000000E0408F501404080004026 -:10846000A4F501401011004040160040206807FAB2 -:1084700005F108432060F0BD0CF0C4BCFE4890F844 -:1084800032007047FD4AC17811600068FC49000263 -:1084900008607047252808BF02210ED0262808BF93 -:1084A0001A210AD0272808BF502106D00A2894BFD5 -:1084B0000422062202EB4001C9B2F24A1160F249DD -:1084C000086070472DE9F047EB4CA17A012956D09E -:1084D000022918BFBDE8F087627E002A08BFBDE808 -:1084E000F087012950D0E17E667F0D1C18BF012561 -:1084F0005FF02401DFF894934FF00108C9F84C8035 -:10850000DFF88CA34718DAF80000B84228BFFFDF75 -:108510000020C9F84C01CAF80070300285F0010152 -:1085200040EA015040F0031194F82000820002F16B -:10853000804202F5C042C2F81015D64901EB800115 -:10854000A07FC20002F1804202F5F832C2F8141591 -:10855000D14BC2F81035E27FD30003F1804303F51D -:10856000F833C3F81415CD49C3F8101508FA00F014 -:1085700008FA02F10843CA490860BDE8F087227E84 -:10858000002AAED1BDE8F087A17E267F002914BF66 -:10859000012500251121ADE72DE9F041C14E8046AE -:1085A00003200D46C6F80002BD49BF4808602846B2 -:1085B0000CF02CFCB04F0124B8F1000F04BFBC72CA -:1085C000346026D0B8F1010F23D1B848006860B9F3 -:1085D00015F00C0F09D0C6F80443012000F0DAFEB4 -:1085E000F463346487F83C4002E0002000F0D2FEDF -:1085F00028460CF0F3FC0220B872FEF7B7FE38B93B -:10860000FEF7C4FE20B9AA48016841F4C021016008 -:1086100074609E48C4649E4800682946BDE8F041E5 -:1086200050E72DE9F0479F4E814603200D46C6F8DE -:108630000002DFF86C829C48C8F8000008460CF085 -:10864000E5FB28460CF0CAFC01248B4FB9F1000F62 -:1086500003D0B9F1010F0AD031E0BC72B86B40F41D -:108660008010B8634FF48010C8F8000027E00220A3 -:10867000B872B86B40F40010B8634FF40010C8F83B -:1086800000008A48006860B915F00C0F09D0C6F8E0 -:108690000443012000F07EFEF463346487F83C401C -:1086A00002E0002000F076FEFEF760FE38B9FEF72B -:1086B0006DFE20B97E48016841F4C0210160EAF7EF -:1086C000F7FA2946BDE8F047FCE62DE9F84F754C6E -:1086D0008246032088461746C4F80002DFF8C0919E -:1086E0007148C9F8000010460CF090FBDFF8C4B1E7 -:1086F000614E0125BAF1000F04BFCBF80040B572FE -:1087000004D0BAF1010F18BFFFDF2FD06A48C0F8BC -:1087100000806B4969480860B06B40F40020B0638A -:10872000D4F800321021C4F808130020C4F8000265 -:10873000DFF890C18A03CCF80020C4F80001C4F827 -:108740000C01C4F81001C4F80401C4F81401C4F801 -:1087500018015D4800680090C4F80032C9F8002094 -:10876000C4F80413BAF1010F14D026E038460CF017 -:1087700035FCFEF7FBFD38B9FEF708FE20B94C4882 -:10878000016841F4C02101605048CBF8000002208C -:10879000B072BBE74548006860B917F00C0F09D00C -:1087A000C4F80453012000F0F5FDE563256486F864 -:1087B0003C5002E0002000F0EDFD4FF40020C9F82D -:1087C00000003248C56432480068404528BFFFDFDA -:1087D00039464046BDE8F84F74E62DE9F041264C95 -:1087E0000646002594F8310017468846002808BF41 -:1087F000FFDF16B1012E16D021E094F831000128D8 -:1088000008D094F83020394640460CF014FBE16A59 -:10881000451814E094F830103A4640460CF049FBF5 -:10882000E16A45180BE094F8310094F83010012803 -:108830003A46404609D00CF064FBE16A45183A46D6 -:1088400029463046BDE8F0413FE70CF014FBE16AF1 -:108850004518F4E72DE9F84F124CD4F8000220F047 -:108860000309D4F804034FF0100AC0F30018C4F849 -:1088700008A300262CE00000280C0020241500404E -:108880001C150040081500405415004000800040B1 -:108890004C850040006000404C81004010110040B9 -:1088A00004F5014000100040000004048817004057 -:1088B00068150040ACF50140488500404881004003 -:1088C000A8F5014008F501401811004004100040CF -:1088D000C4F80062FC48FB490160FC4D0127A97AFD -:1088E000012902D0022903D015E0297E11B912E036 -:1088F000697E81B1A97FEA7F07FA01F107FA02F2E6 -:108900001143016095F82000800000F1804000F5DF -:10891000C040C0F81065FF208DF80000C4F8106159 -:10892000276104E09DF80000401E8DF800009DF8CE -:10893000000018B1D4F810010028F3D09DF8000011 -:10894000002808BFFFDFC4F81061002000F022FDFE -:108950006E72AE72EF72C4F80092B8F1000F18BFD9 -:10896000C4F804A3BDE8F88FFF2008B58DF8000017 -:10897000D7480021C0F810110121016105E000BFB6 -:108980009DF80010491E8DF800109DF8001019B1D7 -:10899000D0F810110029F3D09DF80000002808BF7E -:1089A000FFDF08BD0068CB4920F07F4008607047BA -:1089B0004FF0E0200221C0F8801100F5C070BFF335 -:1089C0004F8FBFF36F8FC0F80011704710B490E85D -:1089D0001C10C14981E81C10D0E90420C1E9042021 -:1089E00010BC70474FF0E0210220C1F80001704731 -:1089F000BA4908707047BA490860704770B50546B3 -:108A0000EAF756F9B14C2844E16A884298BFFFDF83 -:108A100001202074EAF74CF9B24A28440021606131 -:108A2000C2F84411B0490860A06BB04940F480001E -:108A3000A063D001086070BD70B5A44C0546AC4A77 -:108A40000220207410680E4600F00F00032808BFB3 -:108A5000012213D0106800F00F00042808BF022282 -:108A60000CD0106800F00F0005281BD0106800F033 -:108A70000F0006281CBFFFDF012213D094F831003D -:108A800094F83010012815D028460CF081FA954949 -:108A900060610020C1F844016169E06A08449249BC -:108AA000086070BD9348006810F0060F0CBF0822E4 -:108AB0000422E3E7334628460CF038FAE7E7824918 -:108AC0004FF4800008608148816B21F4800181634C -:108AD000002101747047C20002F1804202F5F832B1 -:108AE000854BC2F81035C2F81415012181407F482A -:108AF00001607648826B1143816370477948012198 -:108B00004160C1600021C0F84411774801606F489E -:108B1000C1627047794908606D48D0F8001241F091 -:108B20004001C0F8001270476948D0F8001221F0E7 -:108B30004001C0F800127149002008607047644885 -:108B4000D0F8001221F01001C0F80012012181615B -:108B500070475E49FF2081F83E005D480021C0F863 -:108B60001C11D0F8001241F01001C0F8001270473B -:108B7000574981B0D1F81C21012A0DD0534991F8F1 -:108B80003E10FF290DBF00204942017001B008BF0F -:108B90007047012001B07047594A126802F07F0205 -:108BA000524202700020C1F81C0156480068009033 -:108BB000EFE7F0B517460C00064608BFFFDF434D50 -:108BC00014F0010F2F731CBF012CFFDF002E0CBF10 -:108BD000012002206872EC7201281CBF0228FFDF0E -:108BE000F0BD3A4981F83F007047384A136C036082 -:108BF000506C086070472DE9F84F38480078042819 -:108C000028BFFFDF314CDFF8C080314D94F83C00C5 -:108C100000260127E0B1D5F8040110F1000918BFC2 -:108C20004FF00109D5F81001002818BF012050EAC3 -:108C300009014FF4002B17D08021C5F80813C8F89C -:108C400000B084F83C6090F0010F18BFBDE8F88FC9 -:108C5000DFF89090D9F84C01002871D0A07A012853 -:108C60006FD002286ED0D1E0D5F80001DFF890A0D7 -:108C700030B3C5F800616F61FF20009002E0401E34 -:108C8000009005D0D5F81C0100280098F7D000B955 -:108C9000FFDFDAF8000000F07F0A94F83F0050454B -:108CA0003CBF002000F076FB84F83EA0C5F81C61B4 -:108CB000C5F808731348006800902F64AF6326E07E -:108CC00022E0000000000E0408F50140280C0020FE -:108CD000001000403C150040100C0020C400002093 -:108CE00004150040008000404485004004F5014028 -:108CF000101500401414004004110040601500409D -:108D0000481500401C110040B9F1000F03D0B9F123 -:108D1000000F2ED05CE0DAF8000000F07F0084F84D -:108D20003E00C5F81C6194F83D1061B194F83F1005 -:108D300081421BD2002000F02DFB2F64AF6315E0B1 -:108D400064E04CE04EE0FE49096894F83F308AB296 -:108D5000090C984203D30F2A06D9022904D2012014 -:108D600000F018FB2F6401E02F64AF63F548006842 -:108D700000908022C5F80423F3498F64F348036808 -:108D8000A0F1040CDCF800C043F698273B4463458F -:108D900015D2026842F210731A440260C1F84861A9 -:108DA000EC49EB480860091FEB480860EB48C0F845 -:108DB00000B0A06B40F40020A063BDE8F88F06600F -:108DC000C1F84861C5F80823C8F800B0C1F8486187 -:108DD0008020C5F80803C8F800B0BDE8F88F207EF1 -:108DE00010B913E0607E88B1A07FE17F07FA00F040 -:108DF00007FA01F10843C8F8000094F82000800049 -:108E000000F1804000F5C040C0F81065C9F84C7012 -:108E1000D34800682064D34800686064D248A16BDE -:108E20000160A663217C002019B1D9F84411012901 -:108E300000D00021A27A012A6ED0022A74D000BF8D -:108E4000D5F8101101290CBF1021002141EA0008BA -:108E5000C648016811F0FF0F03D0D5F8141101299D -:108E600000D0002184F83210006810F0FF0F03D00A -:108E7000D5F81801012800D0002084F83300BC4840 -:108E8000006884F83400FEF774FF012818BF002042 -:108E900084F83500C5F80061C5F80C61C5F81061AB -:108EA000C5F80461C5F81461C5F81861B1480068D7 -:108EB0000090A548C0F84461AF480068DFF8BC9254 -:108EC0000090D9F80000A062A9F104000068E062F7 -:108ED000AB48016801F00F01032908BF012013D03E -:108EE000016801F00F01042908BF02200CD00168BD -:108EF00001F00F01052926D0006800F00F000628B8 -:108F00001CBFFFDF01201ED084F83000A07A84F857 -:108F1000310002282CD11EE0D5F80C01012814BF25 -:108F2000002008208CE7FFE7D5F80C01012814BFCA -:108F300000200220934A1268012A14BF0422002252 -:108F4000104308437CE79048006810F0060F0CBF00 -:108F500008200420D8E7607850B18C490968097866 -:108F60000840217831EA000008BF84F8247001D05D -:108F700084F82460DFF818A218F0020F06D0E9F791 -:108F800097FEA16A081ADAF81010884718F0010F46 -:108F900018BF4FF0000B0DD0E9F78AFEE16ADAF84E -:108FA0001420081A594690477A48007810F0010FAB -:108FB0002FD10CE018F0020F18BF4FF0010BEBD1CE -:108FC00018F0080F18BF4FF0020BE5D1ECE7DFF8FF -:108FD000BCB1DBF80000007800F00F00072828BFC4 -:108FE00084F8256015D2DBF80000062200F10901A3 -:108FF000A01CF5F7F5F940B9207ADBF800100978E4 -:10900000B0EBD11F08BF012001D04FF0000084F861 -:109010002500E17A4FF0000011F0020F1CBF18F09C -:10902000020F18F0040F19D111F0100F1CBF94F8A3 -:109030003320002A02D094F835207AB111F0080FBD -:109040001CBF94F82420002A08D111F0040F02D08C -:1090500094F8251011B118F0010F01D04FF0010064 -:10906000617A19B168B1FFF7F5FB10E03E484A4953 -:109070000160D5F8000220F00300C5F80002E77295 -:1090800005E001290AD0022918BFFFDF0DD018F032 -:10909000010F14D0DAF80000804745E06672E772ED -:1090A000A7729621227B002006E06672E7720220FA -:1090B000A072227B96210120FFF78FFBE7E718F0D3 -:1090C000020F2AD018F0040F21D1FEF74FF9F0B9A2 -:1090D000FEF75CF9D8B931480168001F0068C0F399 -:1090E000006CC0F3425500F00F03C0F30312C0F34D -:1090F0000320BCF1000F0AD0002B1CBF002A00285F -:1091000005D1002918BF032D38BF48F0040827EA0D -:109110009800DAF80410884706E018F0080F18BF26 -:10912000DAF8080056D08047A07A022818BFBDE8B8 -:10913000F88F207C002808BFBDE8F88F02492FE097 -:10914000741500401C11004000800040488500401C -:1091500014100040ACF501404881004004F5014086 -:1091600004B500404C85004008F501404016004021 -:109170001014004018110040448100404485004014 -:109180001015004000140040141400400415004065 -:10919000100C0020C40000200000040454140040FF -:1091A000C1F8446102281DD0012818BFFFDFE16A21 -:1091B0006069884298BFFFDFD4F81400C9F8000046 -:1091C000A06B4FF4800140F48000A06382480160EE -:1091D000BDE8F88F18F0100F14BFDAF80C00FFDFAD -:1091E000A1D1A1E76169E06A0844E7E738B57B49A6 -:1091F00004460220887201212046FFF763F9784A6D -:1092000004F13D001060774B0020C3F8440176491B -:10921000C1F80001C1F80C01C1F81001C1F8040146 -:10922000C1F81401C1F818017048006800900120CD -:109230009864101D00681168884228BFFFDF38BDA0 -:109240002DE9F843654A88460024917A0125684F44 -:10925000012902D0022903D015E0117E11B912E0D4 -:10926000517E81B1917FD37F05FA01F105FA03F3B5 -:109270001943396092F82010890001F1804101F50D -:10928000C041C1F8104506460220907201213046C7 -:10929000FFF718F9524906F13D000860514AC2F83B -:1092A00044415148C0F80041C0F80C41C0F8104199 -:1092B000C0F80441C0F81441C0F818414B48006898 -:1092C00000909564081D00680968884228BFFFDF88 -:1092D000B8F1000F1CBF4FF400303860BDE8F883D0 -:1092E000022810B50DD0012804BF42F6CE3010BDC3 -:1092F000042817BF082843F6A440FFDF41F66A00A0 -:1093000010BDFDF7E5FF30B9FDF7F9FF002808BFF4 -:1093100041F6583001D041F2643041F29A010844DC -:1093200010BD314910B50020C1F800023049314864 -:109330000860324930480860091D31480860091D3D -:1093400030480860091D30480860091D2F48086032 -:10935000091D2F48086001200BF058FD1E494FF4ED -:109360003810086010BD22494FF43810086070476B -:109370002848016803291BBF00680228012000203B -:109380007047244801680B291BBF00680A28012088 -:109390000020704720490968C9B9204A204913684C -:1093A00070B123F0820343F07D0343F00043136068 -:1093B0000A6822F0100242F0600242F0004205E02A -:1093C00023F0004313600A6822F000420A60034958 -:1093D00081F83D007047000004F50140280C002092 -:1093E00044850040008000400010004018110040FB -:1093F00008F50140000004041011004098F50140F8 -:109400000410004044810040141000401C11004032 -:109410001010004050150040881700403C170040D5 -:109420007C17004010B5404822220021F5F72FF8A4 -:109430003D480024017821F010010170012104F061 -:10944000FFFE3A494FF6FF7081F822408884384980 -:109450000880488010BD704734498A8C824218BF0A -:109460007047002081F822004FF6FF708884704713 -:109470002D49016070472E49088070472B498A8C1E -:10948000A2F57F43FF3B03D00021016008467047EF -:1094900091F822202549012A1ABF016001200020ED -:1094A0007047224901F1220091F82220012A04BFCD -:1094B00000207047012202701D48008888841046F1 -:1094C00070471B49488070471849194B8A8C5B8844 -:1094D0009A4206D191F82220002A1EBF0160012085 -:1094E0007047002070471148114A818C5288914280 -:1094F00009D14FF6FF71818410F8221F19B10021A4 -:10950000017001207047002070470848084A818C8C -:109510005288914205D190F8220000281CBF0020FB -:109520007047012070470000960C0020700C00204E -:10953000CC0000207047584A012340B1012818BFD1 -:1095400070471370086890608888908170475370E6 -:109550000868C2F802008888D08070474E4A10B16F -:10956000012807D00EE0507860B1D2F80200086000 -:10957000D08804E0107828B19068086090898880CD -:109580000120704700207047434910B1012803D0E3 -:1095900006E0487810B903E0087808B10120704768 -:1095A0000020704730B58DB00C4605460D220021D5 -:1095B00004A8F4F76CFFE0788DF81F0020798DF88F -:1095C0001E0060798DF81D00286800906868019081 -:1095D000A8680290E868039068460AF087FF207840 -:1095E0009DF82F1088420CD160789DF82E1088428B -:1095F00007D1A0789DF82D10884202BF01200DB040 -:1096000030BD00200DB030BD30B50C4605468DB0E4 -:109610004FF0030104F1030012B1FDF749FF01E02F -:10962000FDF765FF60790D2220F0C00040F040009A -:109630006071002104A8F4F72AFFE0788DF81F007C -:1096400020798DF81E0060798DF81D002868009043 -:1096500068680190A8680290E868039068460AF07C -:1096600045FF9DF82F0020709DF82E0060709DF83A -:109670002D00A0700DB030BD10B5002904464FF08C -:10968000060102D0FDF714FF01E0FDF730FF60791D -:1096900020F0C000607110BDD0000020FE4840687E -:1096A00070472DE9F0410F46064601461446012059 -:1096B00005F06FF8054696F86500FEF795FC4AF24E -:1096C000B12108444FF47A71B0FBF1F0718840F297 -:1096D00071225143C0EB4100001BA0F55A7402F007 -:1096E0005AFF002818BF1E3CAF4234BF28463846F8 -:1096F000A04203D2AF422CBF3C462C467462BDE868 -:10970000F0812DE9FF4F8BB0044690F86500884644 -:109710000390DDE90D1008430A90E0480027057822 -:109720000C2D28BFFFDFDE4E36F8159094F88851D7 -:109730000C2D28BFFFDFDA4830F81500484480B20E -:10974000009094F87D000D280CBF012000200790A8 -:109750000D98002804BF94F82C0103282BD10798FA -:1097600048B3B4F8AA01404525D1D4F83401C4F86F -:109770002001608840F2E2414843C4F82401B4F873 -:109780007A01B4F806110844C4F82801204602F012 -:109790000CFFB4F8AE01E08294F8AC016075B4F847 -:1097A000B0016080B4F8B201A080B4F8B401E080E8 -:1097B000022084F82C01D4F884010990D4F88001A7 -:1097C0000690B4F80661B4F87801D4F874110191E8 -:1097D0000D9921B194F8401151B100F0D6B804F5BB -:1097E000807104917431059104F5B075091D07E08D -:1097F00004F5AA710491091D059104F5A275091DCE -:109800000891B4F87010A8EB0000A8EB01010FFA62 -:1098100080F90FFA81FBB9F1000F05DAD4F8700175 -:1098200001900120D9460A909C484FF0000A007927 -:10983000A8B3F2F77FFB90B3B4F8180102282ED337 -:1098400094F82C0102282AD094F8430138BB94F8EC -:10985000880100900C2828BFFFDF9148009930F85C -:10986000110000F5C86080B2009094F82C01012826 -:109870007ED0608840F2E2414843009901F0E6F86A -:10988000D4F8342180B206EB0B01A1EB0901821A56 -:1098900001FB02AAC4F83401012084F8430194F8C2 -:1098A0002C01002865D0012800F01482022800F065 -:1098B0007181032818BFFFDF00F04782A7EB0A0180 -:1098C0000198FCF738F90599012640F271220860E9 -:1098D0000898A0F80080002028702E710598006874 -:1098E000A8606188D4F834015143C0EB41006B4952 -:1098F000A0F54E70C8618969814287BF04990860EC -:10990000049801600498616A0068084400F5D47006 -:10991000E86002F040FE10B1E8681E30E8606E7149 -:10992000B4F8F000A0EB080000B20028C4BF032088 -:109930006871079800280E9800F06982E0B100BFB6 -:10994000B4F8181100290CBF0020B4F81A01A4F8CB -:109950001A0194F81C21401C504388420CD26879AB -:10996000401E002808DD6E71B4F81A01401C01E0A9 -:109970000FE05AE0A4F81A010D98002800F06A825E -:1099800094F84001002800F061820FB00220BDE889 -:10999000F08F94F8800003283DD03F4894F865107C -:1099A00090F82C00F7F78DFDE18A40F271225143C7 -:1099B00000EB4100CDF80800D4F82401009901F033 -:1099C00045F8D4F82021D4F82811821A01FB02AA04 -:1099D000C4F820010099029801F038F8D4F8301149 -:1099E000C4F83001411A8A44608840F2E241484399 -:1099F000009901F02BF806EB0B01D4F82821A1EB1C -:109A00000901891AD4F83421C4F83401821A491E94 -:109A100001FB02AA40E7E18A40F27122D4F8240156 -:109A2000514300EB41000290C6E70698002808BFAA -:109A3000FFDF94F86510184890F82C00F7F741FD07 -:109A40000990E08A40F271214143099800EB4100FE -:109A5000009900F0FBFFC4F83001608840F2E24159 -:109A60004843009900F0F2FFC4F8340103A902A8AA -:109A7000FFF7BBF8DDE90160039FE9F7F0F8014665 -:109A80003046FDF769F800F10F06E9F711F9381AC9 -:109A9000801B009006E00000B80C0020E0000020D1 -:109AA000E4620200B4F83401214686B20120D4F801 -:109AB000289004F06EFE074694F86500FEF794FACD -:109AC0004AF2B12108444FF47A7BB0FBFBF0618885 -:109AD00040F271225143C0EB4100801BA0F55A7641 -:109AE00002F059FD002818BF1E3EB94534BF384664 -:109AF0004846B04203D2B9452CBF4E463E46666248 -:109B000094F86500FEF7E9FA00F2E140B0FBFBF1E2 -:109B100006980F1894F86500FEF7DFFA064694F8E9 -:109B20006500FEF761FA30444AF2AB310844B0FBFD -:109B3000FBF1E08A40F2712242430998D4F8306187 -:109B400000EB4200401A801B384400993138471A14 -:109B5000607D40F2E24110FB01F994F8650000904D -:109B600010F00C0F0ABF00984EF62830FEF73CFAB2 -:109B70004AF2B1210844B0FBFBF000EB460000EBD9 -:109B800009060098FEF7B8FA304400F18401FE4857 -:109B9000816193E6E18A40F27122D4F824015143B5 -:109BA00000EB4100009900F051FFC4F830016188DA -:109BB00040F2E2404843009900F048FFC4F8340105 -:109BC00087B221460120D4F828B004F0E2FD814696 -:109BD00094F86500FEF708FA4AF2B12101444FF407 -:109BE0007A70B1FBF0F0618840F271225143C0EB12 -:109BF0004100C01BA0F55A7702F0CDFC002818BF29 -:109C00001E3FCB4534BF48465846B84203D2CB45E9 -:109C10002CBF5F464F4667621EBB0E9808B394F890 -:109C200065603046FEF7E0F94AF2B12101444FF495 -:109C30007A70B1FBF0F0D4F83011E28A084440F2B7 -:109C40007123D4F824115A4301EB42010F1A304614 -:109C5000FEF752FA01460998401A3844A0F120074D -:109C60000AE0E18A40F27122D4F82401514300EB6A -:109C70004100D4F83011471AD4F82821D4F8201123 -:109C8000D4F8300101FB0209607D40F2E24110FB93 -:109C900001FB94F8656016F00C0F0ABF30464EF6D3 -:109CA0002830FEF7A1F94AF2B12101444FF47A704D -:109CB000B1FBF0F000EB490000EB0B093046FEF77A -:109CC0001BFA484400F16001AF488161012084F82B -:109CD0002C01F3E5618840F271225143D4F834013C -:109CE000D4F82821C0EB410101FB09F706EB0B0179 -:109CF000891AD4F820C1D4F83031491E0CFB023245 -:109D000001FB0029607D40F2E24110FB01FB94F869 -:109D1000656016F00C0F0ABF30464EF62830FEF78D -:109D200063F94AF2B12101444FF47A70B1FBF0F0CB -:109D300000EB490000EB0B093046FEF7DDF9484423 -:109D400000F1600190488161B8E5618840F27122BC -:109D5000D4F834015143C0EB410000FB09F794F8FB -:109D60007C0024281CBF94F87D0024280BD1B4F873 -:109D7000AA01A8EB000000B2002804DB94F8AD01B2 -:109D8000002818BF03900A9800B3FEB9099800286C -:109D90001ABF06980028FFDF94F8650010F00C0F3A -:109DA00014BF4EF62830FEF71FF94AF2B1210144E4 -:109DB0004FF47A70B1FBF0F03F1A94F86500FEF7AB -:109DC0009BF90999081A3844A0F12007D4F83411F6 -:109DD00006EB0B0000FB01F6039810F00C0F0ABF16 -:109DE00003984EF62830FEF7FFF84AF2B1210144FD -:109DF0004FF47A70B1FBF0F000EB46060398FEF7E3 -:109E00007BF9304400F160015F48816156E500282C -:109E10007FF496AD94F82C0100283FF4ADAD618835 -:109E200040F27122D4F834015143C0EB410128467D -:109E3000F7F712F90004000C3FF49EAD18990029C1 -:109E400018BF088001200FB0BDE8F08F94F87C01A6 -:109E5000FCF7D1FC94F87C012946FCF7B0FB20B15B -:109E60000D9880F0010084F841010FB00020BDE89A -:109E7000F08F2DE9F843454C0246434F00266168B8 -:109E8000606A052A60D2DFE802F003464B4F5600B5 -:109E9000A07A002560B101216846FDF709FB9DF815 -:109EA000000042F210710002B0FBF1F201FB12055A -:109EB000F4F720FE4119A069FBF73DFEA06126746E -:109EC000032060754FF0010884F81480607AD0B9DF -:109ED000A06A80B1F4F70EFE0544F4F785FC411941 -:109EE000A06A884224BF401BA06204D2C4F8288024 -:109EF000F5F72BFE07E0207B04F11001FCF75FFB78 -:109F0000002808BFFFDF2684FCF739F87879BDE820 -:109F1000F843E8F7F9BFBDE8F843002100F0B3BD0E -:109F2000C1F88001BDE8F883D1F88001BDE8F843AD -:109F3000012100F0A8BD84F83060FCF720F87879A2 -:109F4000BDE8F843E8F7E0BFFFDFBDE8F8832DE99F -:109F5000F04F0E4C824683B020788B4601270025B7 -:109F6000094E4FF00209032804BF207B50457DD1E4 -:109F7000606870612078032818BFFFDF4FF0030886 -:109F8000BBF1080F73D203E0E0000020B80C002002 -:109F9000DFE80BF0040F32322D9999926562F5F7E4 -:109FA000ABF9002818BFFFDF86F8028003B0BDE8D8 -:109FB000F08FF4F77AFF68B9F4F716FC0546E0690C -:109FC000A84228BFE56105D2281A0421FCF7F7FD55 -:109FD000E56138B1F5F723FD002818BFFFDF03B0B6 -:109FE000BDE8F08F03B00020BDE8F04F41E703B0BB -:109FF000BDE8F04FFEF7FFBD2775257494F83000DB -:10A000004FF0010A58B14FF47A71A069FBF793FD44 -:10A01000A061002104F11000F7F71EF80EE0F4F73C -:10A0200069FD82465146A069FBF785FDA061514656 -:10A0300004F11000F7F710F800F1010A208C411C20 -:10A040000A293CBF50442084606830B1208C401CF9 -:10A050000A2828BF84F8159001D284F81580607A08 -:10A06000A8B9A06AE8B1F4F745FD01E02FE02AE0C5 -:10A070008046F4F7B9FB00EB0801A06A884224BFD0 -:10A08000A0EB0800A0620CD2A762F5F75EFD207B72 -:10A09000FCF74BF82570707903B0BDE8F04FE8F796 -:10A0A00033BF207B04F11001FCF789FA002808BFB8 -:10A0B000FFDF03B0BDE8F08F207BFCF736F825709A -:10A0C00003B0BDE8F08FFFDF03B0BDE8F08FBAF159 -:10A0D000200F28BFFFDFDFF8E886072138F81A00D5 -:10A0E000F9F7E4FE040008BFFFDFBAF1200F28BF34 -:10A0F000FFDF38F81A002188884218BFFFDF4FF0D1 -:10A10000200A7461BBF1080F80F06181DFE80BF079 -:10A110000496A0A099FEFDFCC4F88051F580C4F817 -:10A12000845194F8410138B9FCF71EF8D4F84C1169 -:10A13000FCF712FD00281BDCB4F83E11B4F87000E7 -:10A14000814206D1B4F8F410081AA4F8F6002046AB -:10A1500005E0081AA4F8F600B4F83E112046A4F869 -:10A160007010D4F86811C4F84C11C0F870111DE0DB -:10A17000B4F83C11B4F87000081AA4F8F600B4F86A -:10A180003C112046A4F87010D4F84C11C4F86811A2 -:10A19000C4F87011D4F85411C4F80011D4F858114F -:10A1A000C4F87411B4F85C11A4F8781102F008F93D -:10A1B000FBF7B4FF804694F86500FDF715FF4AF2FF -:10A1C000B12108444FF47A71B0FBF1F0D4F83411A6 -:10A1D00040F27122084461885143C0EB4100A0F174 -:10A1E000300AB8F1B70F98BF4FF0B70821460120E9 -:10A1F00004F0CFFA4044AAEB0000A0F21D38A246BA -:10A200002146012004F0C5FADAF824109C3081427E -:10A2100088BF0D1AC6F80C80454528BF4546B56075 -:10A22000D4F86C01A0F5D4703061FCF762FC84F8BE -:10A23000407186F8029003B0BDE8F08F02F0A6F9F5 -:10A2400001E0FEF7D8FC84F8407103B0BDE8F08F60 -:10A25000FBF78AFFD4F8702101461046FCF77CFC1E -:10A2600048B1628840F27123D4F834115A43C1EBEB -:10A270004201B0FBF1F094F87D100D290FD0B4F835 -:10A280007010B4F83E210B189A42AEBF501C401C0F -:10A290000844A4F83E0194F8420178B905E0B4F806 -:10A2A0003E01401CA4F83E0108E0B4F83E01B4F8B9 -:10A2B000F410884204BF401CA4F83E01B4F87A01AF -:10A2C0000DF1040B401CA4F87A01B4F89A00B4F81C -:10A2D0009810401AB4F87010401E08441FFA80F914 -:10A2E0000BE000231A462046CDF800B0FFF709FA2C -:10A2F00068B3012818BFFFDF48D0B4F83E11A9EBBE -:10A30000010000B2002802E053E047E05FE0E8DA35 -:10A31000082084F88D0084F88C70204601F012FE2D -:10A3200084F82C5194F87C514FF6FF77202D00D300 -:10A33000FFDF28F8157094F87C01FBF7F6FE84F82F -:10A340007CA1707903B0BDE8F04FE8F7DDBDA06EE9 -:10A35000002804BF03B0BDE8F08FB4F83E01B4F8A4 -:10A360009420801A01B20029DCBF03B0BDE8F08F51 -:10A37000B4F86C000144491E91FBF0F189B201FB75 -:10A380000020A4F8940003B0BDE8F08FB4F83E01BB -:10A39000BDF804100844A4F83E01AEE7FEF7E4FA65 -:10A3A000FEF729FC4FF0E020C0F8809203B0BDE832 -:10A3B000F08F94F82C01042818BFFFDF84F82C518B -:10A3C00094F87C514FF6FF77202DB2D3B0E7FFDF32 -:10A3D00003B0BDE8F08F10B5FA4C207850B10120E1 -:10A3E0006072F5F7D8FB2078032805D0207A002882 -:10A3F00008BF10BD0C2010BD207BFCF7FCF9207BB2 -:10A40000FCF765FC207BFBF790FE002808BFFFDF10 -:10A410000020207010BD2DE9F04FEA4F83B038784E -:10A4200001244FF0000840B17C720120F5F7B3FB26 -:10A430003878032818BF387A0DD0DFF88C9389F864 -:10A44000034069460720F9F7BAFC002818BFFFDF70 -:10A450004FF6FF7440E0387BFCF7CDF9387BFCF712 -:10A4600036FC387BFBF761FE002808BFFFDF87F86A -:10A470000080E2E7029800281CBF90F82C11002908 -:10A480002AD00088A0421CBFDFF834A34FF0200B75 -:10A490003AD00721F9F70AFD040008BFFFDF94F85E -:10A4A0007C01FCF714FC84F82C8194F87C514FF665 -:10A4B000FF76202D28BFFFDF2AF8156094F87C0175 -:10A4C000FBF733FE84F87CB169460720F9F777FC87 -:10A4D000002818BFFFDF12E06846F9F74EFC00289D -:10A4E000C8D011E0029800281CBF90F82C11002958 -:10A4F00005D00088A0F57F41FF39CAD104E0684645 -:10A50000F9F73BFC0028EDD089F8038087F830800C -:10A5100087F80B8003B00020BDE8F08FAA4948718E -:10A520000020887001220A7048700A71C870A5491D -:10A53000087070E7A449087070472DE9F84FA14CE6 -:10A5400006460F462078002862D1A048FBF792FD0E -:10A55000207320285CD04FF00308666084F80080E8 -:10A56000002565722572AEB1012106F58E70FCF7EB -:10A57000BEFF0620F9F742FC81460720F9F73EFCB2 -:10A5800096F81C114844B1FBF0F200FB1210401C7D -:10A5900086F81C01FBF7C2FD40F2F651884238BF35 -:10A5A00040F2F65000F5A0701FFA80F9F4F7A2FA15 -:10A5B000012680B3A672F4F717F9E061FBF7D4FD2A -:10A5C000824601216846FCF769FF9DF8000042F2CF -:10A5D00010710002B0FBF1F201FB120000EB090167 -:10A5E0005046FBF7A8FAA762A061267584F815808B -:10A5F0002574207B04F11001FBF7E1FF002808BF60 -:10A60000FFDF25840020F5F7C6FA0020BDE8F88FAB -:10A610000C20BDE8F88FFFE7E761FBF7A5FD494691 -:10A62000FBF789FAA061A57284F830600120FDF77C -:10A6300054FD4FF47A7100F2E140B0FBF1F0381AAA -:10A64000A0F5AB60A5626063CFE75F4948707047D3 -:10A650005D49087170475B4810B5417A00291CBFFD -:10A66000002010BD816A51B990F8301039B1416AAB -:10A67000406B814203D9F5F768FA002010BD012034 -:10A6800010BD2DE9F041504C0646E088401CE080AA -:10A69000D4E902516078D6F8807120B13A46284654 -:10A6A000F6F705FD0546A068854205D02169281A00 -:10A6B00008442061FCF71DFAA560AF4209D896F85E -:10A6C0002C01012805D0E078002804BF0120BDE856 -:10A6D000F0810020BDE8F08110B504460846FDF782 -:10A6E00083FC4AF2B12108444FF47A71B0FBF1F0D7 -:10A6F00040F2E241614300F54E7081428CBF081A7E -:10A70000002010BD70B5044682B0002084F84001DE -:10A7100094F8FB00002807BF94F82C01032802B02E -:10A7200070BDFBF721FDD4F8702101461046FCF7FF -:10A7300013FA0028DCBF02B070BD628840F27123BA -:10A74000D4F834115A43C1EB4201B0FBF1F0B4F834 -:10A750007010401C0844A4F83C01B4F8F400B4F8AC -:10A760003C21801A00B20028DCBF02B070BD01207D -:10A7700084F84201B4F89A00B4F8982001AE801A27 -:10A78000401E084485B212E00096B4F83C11002344 -:10A7900001222046FEF7B5FF002804BF02B070BDBD -:10A7A000012815D0022812BFFFDF02B070BDB4F837 -:10A7B0003C01281A00B20028BCBF02B070BDE3E71C -:10A7C000F00C0020B80C0020E00000204F9F01009A -:10A7D000B4F83C01BDF804100844A4F83C01E6E7D5 -:10A7E000F8B50422002506295BD2DFE801F0072630 -:10A7F0000319192A044680F82C2107E00446C948A9 -:10A80000C078002818BF84F82C210AD0FBF7B7FBCA -:10A81000A4F87A51B4F87000A4F83E0184F84251CB -:10A82000F8BD0095B4F8F410012300222046FEF78D -:10A8300068FF002818BFFFDFE8E7032180F82C112C -:10A84000F8BD0646876AB0F83401314685B201206A -:10A8500003F09FFF044696F86500FDF7C5FB4AF23A -:10A86000B12108444FF47A71B0FBF1F0718840F2E5 -:10A8700071225143C0EB4100401BA0F55A7501F015 -:10A880008AFE002818BF1E3DA74234BF2046384626 -:10A89000A84228BF2C4602D2A74228BF3C46746279 -:10A8A000F8BDFFDFF8BD2DE9F05F9E4EB1780229BB -:10A8B00006BFF1880029BDE8F09F7469C4F88401DF -:10A8C00094F86500FDF718FCD4F88411081AB168F3 -:10A8D0000144B160F1680844F060746994F8430180 -:10A8E000002808BFBDE8F09F94F82C01032818BF8A -:10A8F000BDE8F09F94F8655037780C2F28BFFFDF34 -:10A90000894E36F8178094F888710C2F28BFFFDF26 -:10A9100036F81700404494F8888187B2B8F10C0FDC -:10A9200028BFFFDF36F8180000F5C8601FFA80F86E -:10A930002846FDF7E1FBD4F884114FF0000A0E1A07 -:10A9400015F00C0F0ABF28464EF62830FDF74CFBD9 -:10A950004FF47A7900F2E730B0FBF9F0361A284666 -:10A96000FDF7CAFBD4F8001115F00C0FA1EB000B9A -:10A970000ABF28464EF62830FDF736FB4AF2B121D1 -:10A980000844B0FBF9F0ABEB0000A0F160017943A3 -:10A99000B1FBF8F1292202EB50006031A0EB51022B -:10A9A00000EB5100B24201D8B04201D8F1F774FB7C -:10A9B000608840F2E2414843394600F047F8C4F865 -:10A9C000340184F843A1BDE8F09F70B505465548B1 -:10A9D00090F802C0BCF1020F07BF406900F5C074D7 -:10A9E000524800F12404002904BF256070BD4FF4D3 -:10A9F0007A7601290DD002291CBFFFDF70BD1046F9 -:10AA0000FEF76EFC00F2E140B0FBF6F0281A206081 -:10AA100070BD1846FDF761FB00F2E140B0FBF6F0B7 -:10AA2000281A206070BD4148007800281CBF002013 -:10AA3000704710B50720F9F7D3F980F0010010BD79 -:10AA40003A480078002818BF0120704730B5024608 -:10AA50000020002908BF30BDA2FB0110490A41EACD -:10AA6000C051400A4C1C40F100000022D4F1FF31DB -:10AA700040F2A17572EB000038BFFFDF04F5F4600F -:10AA8000B0FBF5F030BD2DE9F843284C0025814698 -:10AA900084F83050D4F8188084F82C10E5722570B2 -:10AAA0000127277239466068F5F792FD6168C1F8A1 -:10AAB0007081267B81F87C61C1F88091C1F8748136 -:10AAC000B1F80080202E28BFFFDF194820F816803B -:10AAD000646884F82C510023A4F878511A4619466A -:10AAE00020460095FEF70DFE002818BFFFDFC4F8D2 -:10AAF0002851C4F8205184F82C71A4F83E51A4F8D0 -:10AB00003C5184F84251B4F87000401EA4F8700023 -:10AB1000A4F87A51FBF733FA02484079BDE8F843CC -:10AB2000E8F7F2B9E0000020E4620200B80C00206F -:10AB3000F00C0020012804D0022805D0032808D1F9 -:10AB400005E0012907D004E0022904D001E004292E -:10AB500001D000207047012070472DE9F0410E46DA -:10AB6000044603F08AFC0546204603F08AFC0446AE -:10AB7000F6F770FBFB4F010015D0386990F86420A0 -:10AB80008A4210D090F8C0311BB190F8C2312342F4 -:10AB90001FD02EB990F85D30234201D18A4218D8D7 -:10ABA00090F8C001A8B12846F6F754FB70B1396996 -:10ABB00091F86520824209D091F8C00118B191F84E -:10ABC000C301284205D091F8C00110B10120BDE8B1 -:10ABD000F0810020FBE730B5E24C85B0E069002849 -:10ABE0005FD0142200216846F3F751FC206990F8E9 -:10ABF0006500FDF7F9F94FF47A7100F5FA70B0FBD2 -:10AC0000F1F5206990F86500FDF776FA2844ADF873 -:10AC1000060020690188ADF80010B0F87010ADF89A -:10AC200004104188ADF8021090F8A20130B1A0697B -:10AC3000C11C039103F002FB8DF81000206990F80D -:10AC4000A1018DF80800E169684688472069002164 -:10AC500080F8A21180F8A1110399002921D090F861 -:10AC6000A01100291DD190F87C10272919D09DF83A -:10AC70001010039A002914D013780124FF2B12D04E -:10AC8000072B0ED102290CD15178FF2909D100BF21 -:10AC900080F8A0410399C0F8A4119DF8101080F825 -:10ACA000A31105B030BD1B29F2D9FAE770B5AD4C40 -:10ACB000206990F87D001B2800D0FFDF2069002567 -:10ACC00080F8A75090F8D40100B1FFDF206990F818 -:10ACD000A81041B180F8A8500188A0F8D81180F8D8 -:10ACE000D6510E2108E00188A0F8D81180F8D6517D -:10ACF000012180F8DA110D2180F8D4110088F9F7CC -:10AD000006FAF8F79FFE2079E8F7FEF8206980F848 -:10AD10007D5070BD70B5934CA07980072CD5A0787C -:10AD2000002829D162692046D37801690D2B01F1F1 -:10AD300070005FD00DDCA3F102034FF001050B2B77 -:10AD400019D2DFE803F01A1844506127182C183A7A -:10AD50006400152B6FD008DC112B4BD0122B5AD06E -:10AD6000132B62D0142B06D166E0162B71D0172B53 -:10AD700070D0FF2B6FD0FFDF70BD91F87F200123D3 -:10AD80001946F6F7FFF80028F6D12169082081F866 -:10AD90007F0070BD1079BDE8704001F090BC91F863 -:10ADA0007E00C00700D1FFDF01F048FC206910F8E9 -:10ADB0007E1F21F00101017070BD91F87D00102807 -:10ADC00000D0FFDF2069112180F8A75008E091F83A -:10ADD0007D00142800D0FFDF2069152180F8A750DE -:10ADE00080F87D1070BD91F87D00152800D0FFDF40 -:10ADF000172005E091F87D00152800D0FFDF19200D -:10AE0000216981F87D0070BDBDE870404EE7BDE866 -:10AE1000704001F028BC91F87C2001230021F6F756 -:10AE2000B1F800B9FFDF0E200FE011F87E0F20F01F -:10AE3000040008701DE00FE091F87C200123002140 -:10AE4000F6F7A0F800B9FFDF1C20216981F87C002B -:10AE500070BD12E01BE022E091F87E00C0F301100B -:10AE6000012800D0FFDF206910F87E1F21F01001BB -:10AE70000170BDE8704001F0E1BB91F87C20012336 -:10AE80000021F6F77FF800B9FFDF1F20DDE791F81A -:10AE90007D00212801D000B1FFDF2220B0E7BDE80E -:10AEA000704001F0D7BB2F48016991F87E2013074D -:10AEB00002D501218170704742F0080281F87E209E -:10AEC0008069C07881F8E10001F0AFBB10B5254C76 -:10AED00021690A88A1F8162281F8140291F8640009 -:10AEE00001F091FB216981F8180291F8650001F0E9 -:10AEF0008AFB216981F81902012081F812020020E1 -:10AF000081F8C0012079BDE81040E7F7FDBF10B51A -:10AF1000144C05212069FFF763FC206990F85A1052 -:10AF2000012908D000F5F57103F001FC2079BDE896 -:10AF30001040E7F7E9BF022180F85A1010BD10B5A4 -:10AF4000084C01230921206990F87C207030F6F725 -:10AF500019F848B12169002001F8960F087301F82B -:10AF60001A0C10BD000100200120A070F9E770B597 -:10AF7000F74D012329462869896990F87C200979D1 -:10AF80000E2A01D1122903D000241C2A03D004E088 -:10AF9000BDE87040D3E7142902D0202A07D008E08A -:10AFA00080F87C4080F8A240BDE87040AFE71629E9 -:10AFB00006D0262A01D1162902D0172909D00CE083 -:10AFC00000F87C4F80F82640407821280CD01A20C9 -:10AFD00017E090F87D20222A07D0EA69002A03D0E2 -:10AFE000FF2901D180F8A23132E780F87D4001F0DD -:10AFF00025FB286980F8974090F8C0010028F3D01D -:10B000000020BDE8704061E710B5D14C216991F88E -:10B010007C10202902D0262902D0A2E7FFF756FF94 -:10B020002169002081F87C0081F8A20099E72DE9D0 -:10B03000F843C74C206990F87C10202908D00027DD -:10B0400090F87D10222905D07FB300F17C0503E044 -:10B050000127F5E700F17D0510F8B01F41F004016C -:10B060000170A06903F015FA4FF00108002608B33B -:10B070003946A069FFF771FDE0B16A46A169206910 -:10B08000F6F7F7F890B3A06903F001FA2169A1F887 -:10B09000AA01B1F8701001F0AAFA40B32069282182 -:10B0A00080F88D1080F88C8058E0FFE70220A070B7 -:10B0B000BDE8F883206990F8C00110B11E20FFF7A9 -:10B0C00005FFAFB1A0692169C07881F8E20008FAF4 -:10B0D00000F1C1F3006000B9FFDF20690A2180F8A8 -:10B0E0007C1090F8A20040B9FFDF06E009E02AE0FA -:10B0F0002E7001F0A3FAFFF7D6FE206980F8976062 -:10B10000D6E7226992F8C00170B1B2F8703092F8B7 -:10B110006410B2F8C40102F5D572F6F79BF968B174 -:10B120002169252081F87C00206900F17D0180F8EB -:10B1300097608D4212D180F87D600FE00020FFF70C -:10B14000C5FE2E70F0E720699DF8001080F8AC1164 -:10B150009DF8011080F8AD1124202870206900F1BD -:10B160007D018D4203D1BDE8F84301F067BA80F854 -:10B17000A2609DE770B5764C01230B21206990F801 -:10B180007D207030F5F7FEFE202650BB206901239C -:10B19000002190F87D207030F5F7F4FE0125F0B124 -:10B1A000206990F87C0024281BD0A06903F04FF997 -:10B1B000C8B1206990F8B01041F0040180F8B010D7 -:10B1C000A1694A7902F0070280F85D20097901F04F -:10B1D000070180F85C1090F8C1311BBB06E0A57038 -:10B1E00036E6A67034E6BDE870405CE690F8C03103 -:10B1F000C3B900F164035E788E4205D1197891429B -:10B2000002D180F897500DE000F503710D700288AF -:10B210004A8090F85C200A7190F85D0048712079AE -:10B22000E7F772FE2169212081F87D00BDE87040BA -:10B2300001F0FBB9F8B5464C206990F87E0010F09B -:10B24000300F04D0A07840F00100A070F8BDA069D4 -:10B2500003F0E2F850B3A06903F0D8F80746A069FC -:10B2600003F0D8F80646A06903F0CEF80546A069B9 -:10B2700003F0CEF801460097206933462A46303065 -:10B2800003F0BFF9A079800703D56069C07814285E -:10B290000FD0216991F87C001C280AD091F85A003F -:10B2A00001280ED091F8B70158B907E0BDE8F84081 -:10B2B000F9E52169012081F85A0002E091F8B60110 -:10B2C00030B1206910F87E1F41F0100101700EE0CE -:10B2D00091F87E0001F5FC7240F0200081F87E00BC -:10B2E00031F8300B03F017FA2079E7F70DFEBDE8CF -:10B2F000F84001F09AB970B5154C206990F87E10AD -:10B30000890707D590F87C20012308217030F5F7D4 -:10B3100039FEF8B1206990F8AA00800712D4A0691C -:10B3200003F056F8216981F8AB00A06930F8052FC9 -:10B33000A1F8AC204088A1F8AE0011F8AA0F40F0A7 -:10B3400002000870206990F8AA10C90705D011E022 -:10B35000000100200120A0707AE590F87E008007AF -:10B3600000D5FFDF206910F87E1F41F00201017057 -:10B3700001F05BF92069002590F87C10062906D1C0 -:10B3800080F87C5080F8A2502079E7F7BDFD206955 -:10B3900090F8A8110429DFD180F8A8512079E7F7A7 -:10B3A000B3FD206990F87C100029D5D180F8A25017 -:10B3B0004EE570B5FB4C01230021206990F87D20FB -:10B3C0007030F5F7DFFD012578B9206990F87D2010 -:10B3D000122A0AD0012305217030F5F7D3FD10B1F0 -:10B3E0000820A07034E5A57032E5206990F8A80027 -:10B3F00008B901F01AF92169A06901F5847102F018 -:10B40000C8FF2169A069D83102F0CEFF206990F809 -:10B41000DC0100B1FFDF21690888A1F8DE0101F538 -:10B42000F071A06902F0A3FF2169A06901F5F47130 -:10B4300002F0A5FF206980F8DC51142180F87D100E -:10B440002079BDE87040E7F75FBD70B5D54C0123AA -:10B450000021206990F87D207030F5F793FD0125DB -:10B46000A8B1A06902F04FFF98B1A0692169B0F8B6 -:10B470000D00A1F8AA01B1F8701001F0B8F858B1A8 -:10B480002069282180F88D1080F88C50E0E4A570A8 -:10B49000DEE4BDE8704006E5A0692169027981F823 -:10B4A000AC21B0F80520A1F8AE2102F01FFF216900 -:10B4B000A1F8B001A06902F01CFF2169A1F8B20156 -:10B4C000A06902F01DFF2169A1F8B4010D2081F8E7 -:10B4D0007D00BDE47CB5B34CA079C00738D0A0692D -:10B4E00001230521C578206990F87D207030F5F79B -:10B4F00049FD68B1AD1E0A2D06D2DFE805F0090945 -:10B500000505090905050909A07840F00800A070A3 -:10B51000A07800281CD1A06902F0BEFE00286ED0E1 -:10B52000A0690226C5781DB1012D01D0162D18D1B4 -:10B53000206990F87C00F5F70DFD90B1216991F834 -:10B540007C001F280DD0202803D0162D16D0A67001 -:10B550007CBD262081F87C00162D02D02A20FFF722 -:10B56000B5FC0C2D5BD00CDC0C2D48D2DFE805F0CF -:10B5700036331F48BEBE4BB55ABE393C2020A070A2 -:10B580007CBD0120142D6ED008DC0D2D6CD0112D4A -:10B590006BD0122D6ED0132D31D168E0152D7FD0D8 -:10B5A000162D6FD0182D6ED0FF2D28D198E0206970 -:10B5B0000123194690F87F207030F5F7E3FC00284E -:10B5C00008D1A06902F0CCFE216981F88E01072024 -:10B5D00081F87F008CE001F0EDF889E0FFF735FF9E -:10B5E00086E001F0C7F883E0206990F87D1011290A -:10B5F00001D0A6707CE0122180F87D1078E075E023 -:10B60000FFF7D7FE74E0206990F87D001728F0D18D -:10B6100001F014F821691B2081F87D0068E0FFF734 -:10B620006AFE65E0206990F87E00C00703D0A0782C -:10B6300040F0010023E06946A06902F0D0FE9DF8C9 -:10B64000000000F02501206900F8B01F9DF80110EE -:10B6500001F04901417000F0E8FF206910F87E1FF9 -:10B6600041F0010117E018E023E025E002E0FFF7D8 -:10B6700066FC3DE0216991F87E10490704D5A07071 -:10B6800036E00DE00FE011E000F0CFFF206910F888 -:10B690007E1F41F0040101702AE0FFF7CBFD27E097 -:10B6A00001F030F824E0FFF765FD21E0FFF7BFFC73 -:10B6B0001EE0A06900790DE0206910F8B01F41F08C -:10B6C00004010170A06902F0F7FE162810D1A069EC -:10B6D00002F0F6FEFFF798FC0AE0FFF748FC07E0EF -:10B6E000E16919B1216981F8A20101E0FFF7DBFBF3 -:10B6F0002169F1E93002401C42F10002C1E9000277 -:10B700007CBD70B5274CA07900074AD5A0780028E9 -:10B7100047D1206990F8E400FE2800D1FFDF2069BE -:10B72000FE21002580F8E41090F87D10192906D13B -:10B7300080F8A75000F082FF206980F87D502069D2 -:10B7400090F87C101F2902D0272921D119E090F808 -:10B750007D00F5F7FFFB78B120692621012380F8F1 -:10B760007C1090F87D200B217030F5F70BFC78B938 -:10B770002A20FFF7ABFB0BE02169202081F87C0039 -:10B7800006E0012180F8A11180F87C5080F8A250D9 -:10B79000206990F87F10082903D10221217080F8D8 -:10B7A000E41021E40001002010B5FD4C216991F85E -:10B7B000AC210AB991F8642081F8642091F8AD2198 -:10B7C0000AB991F8652081F8652010B10020FFF7D3 -:10B7D0007DFB206902F041FF002806D02069BDE80A -:10B7E000104000F5F57102F0A2BF16E470B5EC4C04 -:10B7F00006460D46206990F8E400FE2800D0FFDFE1 -:10B800002269002082F8E46015B1A2F8A400E7E400 -:10B8100022F89E0F01201071E2E470B5E04C012384 -:10B820000021206990F87C207030F5F7ABFB0028F0 -:10B830007BD0206990F8B61111B190F8B71139B1E9 -:10B8400090F8C01100296FD090F8C11119B36BE0C6 -:10B8500090F87D1024291CD090F87C10242918D051 -:10B860005FF0000300F5D67200F5DB7102F096FE82 -:10B870002169002081F8B60101461420FFF7B6FFC8 -:10B88000216901F13000C28A21F8E62F408B4880FF -:10B8900050E00123E6E790F87D2001230B21703072 -:10B8A000F5F770FB68BB206990F8640000F0ABFE10 -:10B8B0000646206990F8650000F0A5FE054620695F -:10B8C00090F8C2113046FFF735F9D8B1206990F8E9 -:10B8D000C3112846FFF72EF9A0B12269B2F87030E3 -:10B8E00092F86410B2F8C40102F5D572F5F7B2FD12 -:10B8F00020B12169252081F87C001BE00020FFF7A2 -:10B90000E5FA11E020690123032190F87D207030D1 -:10B91000F5F738FB40B920690123022190F87D201A -:10B920007030F5F72FFB08B1002059E400211620F4 -:10B93000FFF75CFF012053E410B5E8BB984C206989 -:10B9400090F87E10CA0702D00121092052E08A0730 -:10B950000AD501210C20FFF749FF206910F8AA1F22 -:10B9600041F00101017047E04A0702D5012113208F -:10B9700040E00A0705D510F8E11F417101210720B9 -:10B9800038E011F0300F3BD090F8B711A1B990F822 -:10B99000B611E1B190F87D1024292FD090F87C10D9 -:10B9A00024292BD05FF0000300F5D67200F5DB717F -:10B9B00002F0F4FD216900E022E011F87E0F20F092 -:10B9C000200040F010000870002081F83801206944 -:10B9D00090F87E10C90613D502F03FFEFFF797FAE4 -:10B9E000216901F13000C28A21F8E62F408B48809E -:10B9F00001211520FFF7FAFE0120F6E60123D3E727 -:10BA00000020F2E670B5664C206990F8E410FE293B -:10BA100078D1A178002975D190F87F2001231946AB -:10BA20007030F5F7AFFA00286CD1206990F88C11CE -:10BA300049B10021A0F89C1090F88D1180F8E61013 -:10BA4000002102205BE090F87D200123042170306A -:10BA5000F5F798FA0546FFF76FFF002852D1284600 -:10BA600000F00CFF00284DD120690123002190F83F -:10BA70007C207030F5F786FA78B120690123042123 -:10BA800090F87D207030F5F77DFA30B9206990F894 -:10BA9000960010B10021122031E0206990F87C203E -:10BAA0000A2A0DD0002D2DD1012300217030F5F789 -:10BAB00069FA78B1206990F8A81104290AD105E043 -:10BAC00010F8E21F01710021072018E090F8AA0089 -:10BAD000800718D0FFF7A1FE002813D120690123A9 -:10BAE000002190F87C207030F5F74CFA002809D03E -:10BAF000206990F8A001002804D00021FF20BDE8B3 -:10BB0000704073E609E000210C20FFF76FFE20690A -:10BB100010F8AA1F41F0010101701DE43EB5054671 -:10BB20006846FDF7ABFC00B9FFDF22220021009838 -:10BB3000F2F7ADFC0321009802F096FB0098017823 -:10BB400021F010010170294602F0B3FB144C0D2DB9 -:10BB500043D00BDCA5F102050B2D19D2DFE805F06F -:10BB600022184B191922185718192700152D5FD0C4 -:10BB700008DC112D28D0122D0BD0132D09D0142D37 -:10BB800006D155E0162D2CD0172D6AD0FF2D74D07C -:10BB9000FFDFFDF786FC002800D1FFDF3EBD00007F -:10BBA000000100202169009891F8E61017E0E26892 -:10BBB00000981178017191884171090A8171518849 -:10BBC000C171090A0172E4E70321009802F072FCD6 -:10BBD0000621009802F072FCDBE700980621017153 -:10BBE000D7E70098D4F8101091F8C221027191F8AB -:10BBF000C3114171CDE72169009801F5887102F008 -:10BC0000D7FB21690098DC3102F0DCFBC1E7FA497F -:10BC1000D1E90001CDE90101206901A990F8B00046 -:10BC200000F025008DF80400009802F006FCB0E753 -:10BC30002069B0F84810009802F0D6FB2069B0F8EF -:10BC4000E810009802F0D4FB2069B0F84410009886 -:10BC500002F0D2FB2069B0F8E610009802F0D0FBA9 -:10BC600097E7216991F8C00100280098BCD111F82C -:10BC7000642F02714978BCE7FFE7206990F8A3219F -:10BC8000D0F8A411009802F022FB82E7DB4810B53F -:10BC9000006990F8821041B990F87D2001230621B7 -:10BCA0007030F5F76FF9002800D001209DE570B5E0 -:10BCB000D24D286990F8801039B1012905D00229A8 -:10BCC00006D0032904D0FFDF03E4B0F8F41037E016 -:10BCD00090F87F10082936D0B0F89810B0F89A2064 -:10BCE00000248B1C9A4206D3511A891E0C04240C82 -:10BCF00001D0641EA4B290F8961039B190F87C205F -:10BD0000012309217030F5F73DF940B3FFF7BEFF7D -:10BD100078B129690020B1F89020B1F88E108B1C01 -:10BD20009A4203D3501A801E00D0401EA04200D277 -:10BD300084B20CB1641EA4B22869B0F8F410214496 -:10BD4000A0F8F0102DE5B0F898100329BDD330F815 -:10BD5000701F428D1144491CA0F8801021E5002479 -:10BD6000EAE770B50C4605464FF4087200212046FC -:10BD7000F2F78DFB258014E5F8F7A2B92DE9F04123 -:10BD80000D4607460721F8F791F8041E3CD094F8B9 -:10BD9000C8010026A8B16E70092028700BE0268427 -:10BDA00084F8C861D4F8CA016860D4F8CE01A860EC -:10BDB000B4F8D201A88194F8C8010028EFD12E71FF -:10BDC000AEE094F8D40190B394F8D4010D2813D0C8 -:10BDD0000E2801D0FFDFA3E02088F8F798F9074686 -:10BDE000F7F745FE78B96E700E20287094F8D601EA -:10BDF00028712088E88014E02088F8F788F9074641 -:10BE0000F7F735FE10B10020BDE8F0816E700D200F -:10BE1000287094F8D60128712088E88094F8DA0117 -:10BE2000287284F8D4613846F7F71BFE78E0FFE704 -:10BE300094F80A0230B16E701020287084F80A62FB -:10BE4000AF806DE094F8DC0190B16E700A2028702C -:10BE50002088A880D4F8E011C5F80610D4F8E411C1 -:10BE6000C5F80A10B4F8E801E88184F8DC6157E00D -:10BE700094F8040270B16E701A20287005E000BFBB -:10BE800084F80462D4F80602686094F8040200287A -:10BE9000F6D145E094F8EA0188B16E70152028705B -:10BEA00008E000BF84F8EA6104F5F6702B1D07C8AE -:10BEB00083E8070094F8EA010028F3D130E094F811 -:10BEC000F80170B16E701C20287084F8F861D4F805 -:10BED000FA016860D4F8FE01A860B4F80202A881F3 -:10BEE0001EE094F80C0238B11D20287084F80C6212 -:10BEF000D4F80E02686013E094F81202002883D090 -:10BF00006E701620287007E084F81262D4F81402CC -:10BF10006860B4F81802288194F812020028F3D15E -:10BF2000012071E735480021C16101620846704770 -:10BF300030B5324D0C46E860FFF7F4FF00B1FFDF8B -:10BF40002C7130BD002180F87C1080F87D1080F8C5 -:10BF5000801090F8FB1009B1022100E00321FEF7E8 -:10BF60003FBC2DE9F041254C0546206909B100216F -:10BF700004E0B0F80611B0F8F6201144A0F806115C -:10BF800090F88C1139B990F87F2001231946703050 -:10BF9000F4F7F8FF30B1206930F89C1FB0F85A2050 -:10BFA00011440180206990F8A23033B1B0F89E109E -:10BFB000B0F8F6201144A0F89E1090F9A670002F5A -:10BFC00006DDB0F8A410B0F8F6201144A0F8A410D3 -:10BFD00001213D2615B180F88D6017E02278022AF4 -:10BFE0000ED0012A15D0A2784AB380F88C1012F036 -:10BFF000140F11D01E2117E0FC6202000001002086 -:10C0000090F8E620062A3CD016223AE080F88C1000 -:10C0100044E090F88E2134E0110702D580F88D605D -:10C020003CE0910603D5232180F88D1036E090077F -:10C0300000D1FFDF21692A2081F88D002AE02BB191 -:10C04000B0F89E20B0F8A0309A4210D2002F05DD43 -:10C05000B0F8A420B0F8A0309A4208D2B0F89C30D2 -:10C06000B0F89A20934204D390F88C310BB122227D -:10C0700007E090F880303BB1B0F89830934209D394 -:10C08000082280F88D20C1E7B0F89820062A01D355 -:10C090003E22F6E7206990F88C1019B12069BDE8BE -:10C0A000F0414FE7BDE8F0410021FEF799BB2DE9D3 -:10C0B000F047FF4C81460D4620690088F8F739F8B3 -:10C0C000060000D1FFDFA0782843A070A0794FF0D0 -:10C0D00000058006206904D5A0F8985080F8045126 -:10C0E00003E030F8981F491C0180FFF7CFFD4FF0A7 -:10C0F000010830B3E088000506D5206990F8821069 -:10C1000011B1A0F88E501CE02069B0F88E10491CC7 -:10C1100089B2A0F88E10B0F890208A4201D3531A49 -:10C1200000E0002327897F1DBB4201D880F896805C -:10C13000914206D3A0F88E5080F80A822079E6F763 -:10C14000E3FEA0794FF0020710F0600F0ED02069D7 -:10C1500090F8801011B1032908D102E080F88080A6 -:10C1600001E080F880700121FEF73AFB206990F829 -:10C170008010012904D1E188C90501D580F88070BB -:10C18000B9F1000F72D1E188890502D5A0F81851E4 -:10C1900004E0B0F81811491CA0F8181100F035FBA4 -:10C1A000FEF719FDFFF72EFC2769B7F8F800401CD1 -:10C1B000A7F8F80097F8FC0028B100F01BFFA8B121 -:10C1C000A7F8F85012E000F012FF08B1A7F8F850F5 -:10C1D00000F015FF50B197F80401401CC0B287F879 -:10C1E0000401022802D927F8F85F3D732069012372 -:10C1F000002190F87D207030F4F7C4FE20B920694A -:10C2000090F87D000C2859D120690123002190F875 -:10C210007C207030F4F7B6FE48B32069012300217A -:10C2200090F87F207030F4F7ADFE00B3206990F8ED -:10C230008010022942D190F80401C0B93046F7F7C6 -:10C24000E6F9A0B1216991F8E400FE2836D1B1F8F1 -:10C25000F200012832D981F8FA80B1F89A00B1F8D9 -:10C260009820831E9A4203DB012004E032E025E09F -:10C27000801A401E80B2B1F8F82023899A4201D377 -:10C28000012202E09A1A521C92B2904200D9104642 -:10C29000012801D181F8FA5091F86F2092B98A6E85 -:10C2A00082B1B1F89420B1F87010511A09B2002986 -:10C2B00008DD884200DB084680B203E021690120E6 -:10C2C00081F8FA502169B1F870201044A1F8F40007 -:10C2D000FFF7EDFCE088C0F340214846FFF741FE40 -:10C2E000206980F8FB50BDE8F047FDF7FCB87049C5 -:10C2F00002468878CB78184312D10846006942B1CB -:10C300008979090703D590F87F00082808D0012013 -:10C310007047B0F84C10028E914201D8FEF7B1B9C7 -:10C320000020704770B5624C05460E46E0882843F1 -:10C33000E080A80703D5E80700D0FFDF6661EA07C1 -:10C340004FF000014FF001001AD0A661F278062AE2 -:10C3500002D00B2A14D10AE0226992F87D30172B03 -:10C360000ED10023E2E92E3302F8370C08E02269EF -:10C3700092F87D30112B03D182F8811082F8A80049 -:10C38000AA0718D56269D278052A02D00B2A12D1E1 -:10C390000AE0216991F87D20152A0CD10022E1E9FB -:10C3A000302201F83E0C06E0206990F87D20102A2A -:10C3B00001D180F88210280601D50820E07083E4BE -:10C3C0002DE9F84301273A4C002567F30701E58082 -:10C3D000A570E570257020618946804680F8FB7065 -:10C3E0000088F7F7A6FE00B9FFDF20690088FDF797 -:10C3F00042F820690088FDF764F82069B0F8F2106F -:10C4000071B190F8E410FE290FD190F88C1189B128 -:10C4100090F87F20012319467030F4F7B3FD78B10E -:10C42000206990F8E400FE2804D0206990F8E40028 -:10C43000FFF774FB206990F8FD1089B1258118E0A1 -:10C440002069A0F89C5090F88D1180F8E61000212A -:10C450000220FFF7CBF9206980F8FA500220E7E7C5 -:10C4600090F8C81119B9018C8288914200D881884E -:10C47000218130F8F61F491E8EB230F8021F314478 -:10C4800020F86019018831440180FFF7FFFB20B1DB -:10C49000206930F88E1F314401802069B0F8F21015 -:10C4A000012902D8491CA0F8F2102EB102E00000C8 -:10C4B0000001002080F8045180F8FA5090F87D10B7 -:10C4C0000B2901D00C2916D1B0F87020B0F8AA3190 -:10C4D000D21A12B2002A0EDBD0F8AC11816090F8AB -:10C4E000B01101730321F4F773F8206980F87D50CF -:10C4F00080F8B27026E0242910D1B0F87010B0F89E -:10C50000AA21891A09B2002908DB90F8C001FFF7B7 -:10C510004BF9206900F87D5F857613E090F87C1078 -:10C52000242901D025290DD1B0F87010B0F8AA0146 -:10C53000081A00B2002805DB0120FFF735F9206951 -:10C5400080F87C5020690146B0F8F6207030F4F78E -:10C55000B2FAFC480090FC4BFC4A4146484600F0C9 -:10C560007DFC216A11B16078FCF7B5FA20690123DE -:10C57000052190F87D207030F4F704FD002803D0E9 -:10C58000BDE8F84300F0FDB9BDE8F88300F015BD43 -:10C59000EF49C8617047EE48C069002800D001200B -:10C5A0007047EB4A50701162704710B5044600881E -:10C5B000A4F8CC01B4F8B001A4F8CE01B4F8B201EB -:10C5C000A4F8D001B4F8B401A4F8D201012084F891 -:10C5D000C801DF480079E6F797FC02212046F3F70F -:10C5E000F7FF002004F87D0F0320E07010BD401A13 -:10C5F00000B247F6FE71884201DC002801DC012010 -:10C6000070470020704710B5012808D0022808D0D4 -:10C61000042808D0082806D0FFDF204610BD0124DA -:10C62000FBE70224F9E70324F7E7C9480021006982 -:10C6300020F8A41F8178491C81707047C44800B558 -:10C64000016911F8A60F401E40B20870002800DAF8 -:10C65000FFDF00BDBE482721006980F87C10002163 -:10C6600080F8A011704710B5B94C206990F8A81156 -:10C67000042916D190F87C20012300217030F4F7B2 -:10C6800081FC00B9FFDF206990F8AA10890703D464 -:10C69000062180F87C1004E0002180F8A21080F8C8 -:10C6A000A811206990F87E00800707D5FFF7C6FF24 -:10C6B000206910F87E1F21F00201017010BDA4490D -:10C6C00010B5096991F87C200A2A09D191F8E22075 -:10C6D000824205D1002081F87C0081F8A20010BDC3 -:10C6E00091F87E20130706D522F0080081F87E001D -:10C6F000BDE81040A2E7FF2801D0FFDF10BDBDE874 -:10C700001040A7E7F8B5924C01230A21206990F860 -:10C710007C207030F4F736FC38B3A06901F07CFE61 -:10C72000C8B1A06901F072FE0746A06901F072FE6F -:10C730000646A06901F068FE0546A06901F068FEA2 -:10C7400001460097206933462A46303001F059FFF0 -:10C75000206901F082FF2169002081F8A20081F8A0 -:10C760007C00BDE8F840FEF7D2BBA07840F00100A5 -:10C77000A070F8BD10B5764C01230021206990F817 -:10C780007D207030F4F7FEFB30B1FFF74EFF2169DA -:10C79000102081F87D0010BD20690123052190F84B -:10C7A0007D207030F4F7EEFB08B1082000E0012096 -:10C7B000A07010BD70B5664C01230021206990F86F -:10C7C0007D207030F4F7DEFB012588B1A06901F00F -:10C7D000C4FD2169A1F8AA01B1F87010FFF707FFA5 -:10C7E00040B12069282180F88D1080F88C50E6E552 -:10C7F000A570E4E52169A06901F5D67101F0A8FDF5 -:10C8000021690B2081F87D00D9E510B5FEF779FF8D -:10C81000FEF760FE4E4CA079400708D5A07830B9ED -:10C82000206990F87F00072801D101202070FEF7D1 -:10C8300071FAA079C00609D5A07838B9206990F8B6 -:10C840007D100B2902D10C2180F87D10E0780007C3 -:10C850000ED520690123052190F87D207030F4F772 -:10C8600091FB30B10820A0702169002081F8D4012B -:10C8700010BDBDE81040002000F0C4BB10B5344C22 -:10C88000216991F87D2048B3102A06D0142A07D0D8 -:10C89000152A1AD01B2A2CD11AE001210B2019E0ED -:10C8A000FAF702FE0C2817D32069082100F58870DA -:10C8B000FAF7FEFD28B120690421DC30FAF7F8FD13 -:10C8C00000B9FFDF0121042004E000F017F803E0C5 -:10C8D00001210620FEF78AFF012010BD212A08D180 -:10C8E00091F8970038B991F8C00110B191F8C101E1 -:10C8F00008B1002010BD01211720EBE770B5144CE2 -:10C900000025206990F88F1101290AD002292ED123 -:10C9100090F8A810F1B1062180F8E610012102205C -:10C9200020E090F8D411002921D100F1C80300F5CE -:10C930008471002200F5C870F4F796FA01210520F1 -:10C9400010E00000AFC00100EFC2010025C30100EC -:10C950000001002090F8B000400701D5112000E050 -:10C960000D200121FEF742FF206980F88F5126E556 -:10C9700030B5FB4C05462078002818BFFFDFE57175 -:10C9800030BDF7490120887170472DE9F14FF54D11 -:10C990002846446804F1700794F86510608F94F895 -:10C9A0008280268F082978D0F4F797FBB8F1000F22 -:10C9B00004BF001D80B2864238BF304600F0FF0839 -:10C9C000DFF89C93E848C9F8240009F134006E6848 -:10C9D000406800F1700A90F882B096F86510358FC3 -:10C9E000708F08295DD0F4F778FB00BFBBF1000F12 -:10C9F00004BF001D80B2854238BF2846C0B29AF8F5 -:10CA00001210002918BF04210844C0B296F865101E -:10CA1000FBF735FCB87C002847D007F15801D24815 -:10CA200091E80E1000F5027585E80E10B96EC0F899 -:10CA30002112F96EC0F8251200F58170FBF7DBFFBB -:10CA4000C848007800280CBF0120002080F00101B8 -:10CA5000C6480176D7E91412C0E90412A0F5837222 -:10CA6000D9F82410FBF7F5F994F86500012808BF00 -:10CA700000220CD0022808BF012208D0042808BFD9 -:10CA8000032204D008281ABFFFDF002202224146F9 -:10CA90000120FBF7F9F90EE0FFE70421F4F71DFB95 -:10CAA00084E70421F4F719FBA0E7D9F82400FBF789 -:10CAB000A2FFFBF715FA009850B994F8650094F8B6 -:10CAC000661010F00C0F08BF00219620FBF7B4FF92 -:10CAD00094F8642001210020FCF76BF894F82C00F6 -:10CAE000012808BFFCF735F8022089F80000FCF7A0 -:10CAF0003FFC002818BFFFDFBDE8F88F2DE9F04F9D -:10CB0000DFF860A28BB050469AF800204068AAF186 -:10CB10001401059190F8751000F1700504464FF06E -:10CB200008080127AAF13406A1B3012900F0068103 -:10CB3000022900F00781032918BFFFDF00F01881E8 -:10CB4000306A0423017821F008010170AA7908EA0B -:10CB5000C202114321F004010170EA7903EA820262 -:10CB6000114321F01001017095F80590F06AF6F775 -:10CB70005EFD8046FCF7C9FCB9F1020F00F00081B0 -:10CB8000B9F1010F00F00081B9F1030F00F000814D -:10CB900000F003B9FFE795F80CC04FF002094FF021 -:10CBA000000BBCF1240F1CBF6B7B242B08D0BCF105 -:10CBB0001F0F18BFBCF1200F2AD0222B4DD077E0D9 -:10CBC00094F864109AB190F8AC01002874D0082948 -:10CBD00018BF042969D0082818BF042865D0012986 -:10CBE00018BF012853D000BF4FF0020164E090F855 -:10CBF0001201002860D0082918BF042955D0082840 -:10CC000018BF042851D0012918BF01283FD0EBE7F5 -:10CC1000222B22D0002A4BD090F8C20194F8641045 -:10CC200010F0040F18BF40460CD0082918BF042983 -:10CC30003BD0082818BF042837D0012918BF012885 -:10CC400025D0D1E710F0010F18BF3846EDD110F014 -:10CC5000020F18BF4846E8D12EE04AB390F8C2212F -:10CC600090F85D0094F8641002EA000010F0040FE0 -:10CC700018BF40460ED0082918BF042915D008282F -:10CC800018BF042811D0012918BF0128ACD14FF0DA -:10CC9000010111E010F0010F18BF3846EBD110F080 -:10CCA000020F18BF4846E6D106E04FF0080103E046 -:10CCB00094F864100429F8D0A08E11F00C0F18BF5E -:10CCC0004FF42960F4F709FA218E814238BF0846F3 -:10CCD000ADF80400A4F84C000598FCF7F5FB60B132 -:10CCE0007289316A42F48062728172694FF48060A5 -:10CCF000904703206871EF7022E709AA01A9F06A42 -:10CD0000F6F7CFFB306210B195F8371021B10598D6 -:10CD1000FCF7AEFB6F7113E79DF8241031B9A0F852 -:10CD200000B080F802B0012101F09EFABDF80410B5 -:10CD3000306A01F0C7FB85F8059001E70598FCF71C -:10CD400097FBFDE6B4F84C00ADF8040009AA01A970 -:10CD5000F06AF6F7A6FB3062002808BFFFDFEFE6B7 -:10CD60002401002058010020300D0020380F002041 -:10CD70000598FCF7A9FB002808BFFFDFE0E600BF2D -:10CD800030EA080009D106E030EA080005D102E0E7 -:10CD9000B8F1000F01D0012100E00021306A0278D3 -:10CDA00042EA01110170697C00291CBF69790129DF -:10CDB0003BD005F15801FD4891E80E1000F50278CE -:10CDC00088E80E10A96EC0F82112E96EC0F825128D -:10CDD00000F58170FBF70FFE9AF8000000280CBFE9 -:10CDE00001210021F2480176D5E91212C0E90412AE -:10CDF000A0F58371326AFBF72CF894F864000128DF -:10CE000008BF00220CD0022808BF012208D0042845 -:10CE100008BF032204D008281ABFFFDF0022022225 -:10CE2000FB210020FBF730F803E0FBF7E4FDFBF704 -:10CE300057F8012194F865200846FBF7BAFE3771D0 -:10CE4000306A0188F181807830743770FCF799FA84 -:10CE5000002818BFFFDF0BB0BDE8F08F2DE9F043CD -:10CE6000D44D87B081462878DDF838801E461746B5 -:10CE70000C4628B9002F1CBF002EB8F1000F00D1BE -:10CE8000FFDFC5F81C80C5E90D94C5E905764FF0B4 -:10CE90000000A8716871E870A8702871C64E68819A -:10CEA000A881307804F170072088F7F742F9E8622A -:10CEB0002088F7F72CF92863FBF705FA94F9670047 -:10CEC000FBF7DAFA04F11200FBF76CFD04F10E0037 -:10CED000FBF7D8FA307800280CBF03200120FBF7BD -:10CEE00087FDB64890E80E108DE80E10D0E90410CA -:10CEF000CDE90410307800280CBFB148B148049047 -:10CF00006846FBF763FDF87EFBF7C4FAFBF76AFDA2 -:10CF100094F86F0078B9A06E68B1B88C39888842EF -:10CF200009D1B4F86C1001220844B88494F86E005A -:10CF3000A16EF8F7D8FE3078002804BFFF2094F8DF -:10CF400064401AD094F8651097F81280258F608F8E -:10CF5000082926D0F4F7C1F8B8F1000F04BF001D6E -:10CF600080B2854238BF2846C0B2B97C002918BFBC -:10CF70000421084494F86540C0B22146FBF77FF9CC -:10CF80003078214688B10120FBF74BFB7068D0F860 -:10CF90000001FBF733FD0120FFF7F7FC07B0BDE808 -:10CFA000F0830421F4F799F8D6E70020FBF739FB6A -:10CFB000FFF7A4FD07B0BDE8F0837F4800B5017816 -:10CFC0003438007819B1022818BFFFDF00BD0128EE -:10CFD00018BFFFDF00BD774810B50078022818BFE2 -:10CFE000FFDFBDE8104000F070BA00F06EBA714883 -:10CFF000007970476F488089C0F3002070476D4802 -:10D00000C07870472DE9F04706006B48694D4068CD -:10D0100000F17004686A90F8019018BF012E03D1E6 -:10D02000296B07F0F1FF6870687800274FF001085E -:10D03000A0B101283CD0022860D003281CBFFFDF2C -:10D04000BDE8F087012E08BFBDE8F087286BF6F732 -:10D05000E3FCE879BDE8F047E5F756BF012E14D0B0 -:10D06000A86A002808BFFFDF2889C21CD5E909107B -:10D07000F1F7E3F9A86A686201224946286BF6F7DE -:10D0800047FB022E08BFBDE8F087D4E91401401C1D -:10D0900041F10001C4E91401E079012801D1E771EF -:10D0A00001E084F80780E879BDE8F047E5F72CBF98 -:10D0B000012E14D0A86A002808BFFFDF2889C21CEF -:10D0C000D5E90910F1F7B9F9A86A68620022494662 -:10D0D000286BF6F71DFB022E08BFBDE8F087D4E9E8 -:10D0E0001410491C40F10000C4E91410E079012833 -:10D0F0000CBFE77184F80780BDE8F087012E06D0E9 -:10D10000286BF6F789FC022E08BFBDE8F087D4E94A -:10D110001410491C40F10000C4E91410E079012802 -:10D12000BFD1BCE72DE9F041234D2846A5F13404D9 -:10D13000406800F170062078012818BFFFDFB07842 -:10D140000127002158B1B1706289042042F0040225 -:10D150006281626990472878002818BF3771216A78 -:10D160000322087832EA000009D1628912F4806F44 -:10D1700005D042F002026281626902209047A169F3 -:10D180000020884760B3607950BB287818B30E48F8 -:10D19000007810F0100F04D10449097811F0100F35 -:10D1A0001ED06189E1B9A16AA9B90FE0300D002054 -:10D1B000380F0020240100205801002004630200E1 -:10D1C000BB220200A7A8010032010020218911B171 -:10D1D00010F0100F04D0BDE8F0410020FFF7D5BBE0 -:10D1E000BDE8F04100F071B92DE9F05FCC4E044686 -:10D1F0003046A6F134054068002700F1700A28780F -:10D20000B846022818BFFFDFA889FF2240F400704B -:10D21000A881706890F864101046FBF730F89AF80F -:10D2200012004FF00109002C00F0F080FAF77DFEAB -:10D23000FAF76BFE90B99AF8120078B1686A4178F3 -:10D2400061B100789AF80710C0F3C000884205D198 -:10D2500085F80290BDE8F05F00F037B9686A417860 -:10D260002981002908BFAF6203D0286BF6F70AFABC -:10D27000A862A88940F02000A881EF70706800F1D2 -:10D28000700B044690F82C0001281BD1FBF757FCCB -:10D2900059462046F3F729FEA0B13078002870687F -:10D2A0000CBF00F59A7000F50170218841809BF851 -:10D2B000081001719BF80910417180F80090E8791D -:10D2C000E5F722FE686A9AF806100078C0F380003D -:10D2D00088423AD0706800F1700490F87500002818 -:10D2E0002FD002284AD06771307800281CBF2079DF -:10D2F000002809D027716A89394642F010026A81F4 -:10D300006A694FF010009047E078A0B1E770FCF731 -:10D31000EAF8002808BFFFDF08206A89002142F0F0 -:10D3200008026A816A699047D4E91210491C40F1E9 -:10D330000000C4E91210A07901280CBFA77184F87D -:10D340000690A88940F48070A881696A9AF807302D -:10D350000878C0F3C0029A424DD1726800F0030011 -:10D3600002F17004012818BF02282DD003281CBF29 -:10D37000687940F0040012D068713CE0E86AF6F782 -:10D38000BCF8002808BFFFDFD4E91210491C40F1A7 -:10D390000000C4E91210E879E5F7B6FDA3E784F8C8 -:10D3A0000290AA89484642F40062AA816A8942F042 -:10D3B00001026A816A699047E079012801D1E77129 -:10D3C00019E084F8079016E04878D8B1A98941F4AB -:10D3D0000061A981A96A71B1FB2884BF687940F016 -:10D3E0001000C9D8A879002808BFC84603D08020FB -:10D3F0006A69002190470120A9698847E0B36879EC -:10D40000A0B13AE0E0790128DBD1D8E7002818BFC5 -:10D41000FAF7C5FDA88940F04000A881E97801200D -:10D42000491CC9B2E97001292DD8E5E7307890B9D7 -:10D430003C48007810F0100F04D13B49097811F0F6 -:10D44000100F1AD06989B9B9A96A21B9298911B10E -:10D4500010F0100F11D0B8F1000F1CBF0120FFF722 -:10D46000D1FDFFF74BFBB8F1000F08BFBDE8F09FFF -:10D470000220BDE8F05FC5E5FFE7B8F1000F1CBF73 -:10D480000020FFF7BFFDBDE8F05F00F01EB870B5EB -:10D490000D4606462248224900784C6850B1FAF7FA -:10D4A000F7FD034694F8642029463046BDE87040F5 -:10D4B000FDF78BBAFAF7ECFD034694F86420294691 -:10D4C0003046BDE8704004F0FCBE154910B54C680C -:10D4D000FBF714FBFBF7F3FAFBF7BCF9FBF768FA71 -:10D4E000FAF7FEFC94F82C00012808BFFBF727FB95 -:10D4F00094F86F0038B9A06E28B1002294F86E003D -:10D500001146F8F7F0FB094C00216269A0899047A9 -:10D51000E2696179A07890470020207010BD00007A -:10D520005801002032010020300D0020240100208D -:10D530002DE9F047FA4F894680463D782C0014D0FB -:10D540000126012D11DB601EC4B207EBC40090F868 -:10D550005311414506D10622494600F5AA70F0F75D -:10D560003FFF28B1761CAE42EDDD1020BDE8F0870C -:10D570002046BDE8F087EA498A78824286BF08449F -:10D5800090F843010020704710B540F2D3120021FB -:10D59000E348F0F77CFF0822FF21E248F0F777FF2D -:10D5A000E1480021417081704FF46171818010BDAC -:10D5B0002DE9F0410E460546FFF7BAFFD84C10287A -:10D5C00016D004EBC00191F85A0110F0010F1CBFF6 -:10D5D0000120BDE8F081607808283CBF012081F877 -:10D5E0005A011CD26078401C60700120BDE8F081B7 -:10D5F0006078082813D222780127501C207004EB91 -:10D60000C2083068C8F85401B088A8F85801102A38 -:10D6100028BFFFDF88F8535188F85A71E2E70020ED -:10D62000BDE8F081C04988707047BF488078704776 -:10D630002DE9F041BA4D00272878401E44B2002C55 -:10D6400030DB00BF05EBC40090F85A0110F0010F69 -:10D6500024D06878E6B2401E687005EBC6083046F4 -:10D6600088F85A7100F0E8FA102817D12878401E7F -:10D67000C0B22870B04211D005EBC001D1F85301FF -:10D68000C8F85301D1F85701C8F85701287800F0BD -:10D69000D3FA10281CBF284480F80361601E44B2EE -:10D6A000002CCFDAA0488770BDE8F0819C498A78C9 -:10D6B000824286BF01EB0010C01C002070472DE99C -:10D6C000F0470127994690463D460026FFF730FF78 -:10D6D000102820D0924C04EBC00191F85A1101F0AF -:10D6E000010600F0A9FA102815D0B9F1000F18BFF3 -:10D6F00089F80000A17881420DD904EB001111F1E5 -:10D70000030F08D0204490F84B5190F83B010128BA -:10D710000CBF0127002748EA060047EA0501084038 -:10D72000BDE8F0872DE9F05F1F4690468946064622 -:10D73000FFF7FEFE7A4C054610282ED000F07CFA4A -:10D7400010281CBF1220BDE8F09FA07808283ED208 -:10D75000A6781022701CA07004EB061909F10300D2 -:10D760004146F3F768FB09F1830010223946F3F7CD -:10D7700062FB10213846F3F74BFB3444102184F848 -:10D7800043014046F3F744FB84F84B0184F803510E -:10D79000002084F83B01BDE8F09FA078082816D24D -:10D7A00025784FF0000A681C207004EBC50BD9F8EF -:10D7B0000000CBF85401B9F80400ABF85801102D63 -:10D7C00028BFFFDF8BF853618BF85AA1C0E7072011 -:10D7D000BDE8F09F2DE9F041514CA078401E45B2C4 -:10D7E000002DB8BFBDE8F081EAB2A078401EC1B2FA -:10D7F000A17054FA85F090F803618A423DD004EBA1 -:10D80000011004EB0213D0F803C0C3F803C0D0F832 -:10D8100007C0C3F807C0D0F80BC0C3F80BC0D0F8DE -:10D820000FC0C3F80FC0D0F883C0C3F883C0D0F8CE -:10D8300087C0C3F887C0D0F88BC0C3F88BC0D0F8BE -:10D840008F00C3F88F006318A01801EB410193F813 -:10D8500003C102EB420204EB410180F803C104EB77 -:10D860004202D1F80BC1C2F80BC1B1F80F11A2F8F6 -:10D870000F1193F83B1180F83B1104EBC60797F8A2 -:10D880005A0110F0010F1CD1304600F0D5F91028D4 -:10D8900017D12078401EC0B22070B04211D004EBE6 -:10D8A000C000D0F85311C7F85311D0F85701C7F88A -:10D8B0005701207800F0C0F910281CBF204480F8E0 -:10D8C0000361681E45B2002D8EDABDE8F08116496D -:10D8D0004870704714484078704738B14AF2B81120 -:10D8E000884203D810498880012070470020704783 -:10D8F0000D488088704710B5FFF71AFE102804D035 -:10D9000000F09AF9102818BF10BD082010BD044976 -:10D910008A78824286BF01EB001083300020704776 -:10D92000600F00206C01002060010020FE4B93F886 -:10D9300002C084459CBF00207047184490F8030142 -:10D9400003EBC00090F853310B70D0F85411116004 -:10D95000B0F85801908001207047F34A114491F8C3 -:10D960000321F2490A700268C1F8062080884881C4 -:10D97000704770B516460C460546FBF7D5F8FAF722 -:10D98000C4F9EA48407868B1E748817851B12A196A -:10D99000002E0CBF8330C01CFAF791F9FAF7D8F9C2 -:10D9A000012070BD002070BD10B5FAF7FFF9002806 -:10D9B00004BFFF2010BDBDE81040FAF71DBAFAF70A -:10D9C000F5B9D9498A7882429CBF00207047084443 -:10D9D00090F8030101EBC00090F85A0100F001003B -:10D9E00070472DE9F047D04D00273E4628780028A3 -:10D9F00086BF4FF01009DFF83883BDE8F087AC78B8 -:10DA000021000CD00122012909DB601EC4B22819B3 -:10DA100090F80331B34203D0521C8A42F5DD4C46E4 -:10DA2000A14286BF05EB0410C01C002005EBC60A0E -:10DA30009AF85A1111F0010F16D050B1102C04D0E1 -:10DA4000291991F83B11012903D01021F3F7E0F9CE -:10DA500050B108F8074038467B1C9AF853210AF564 -:10DA6000AA71DFB2FAF7B5FC701CC6B22878B042D2 -:10DA7000C5D8BDE8F0872DE9F041AB4C002635460E -:10DA8000A07800288CBFAA4FBDE8F0816119C0B210 -:10DA900091F80381A84286BF04EB0510C01C00204A -:10DAA00091F83B11012903D01021F3F7B1F958B1D6 -:10DAB00004EBC800BD5590F8532100F5AA7130461B -:10DAC000731CDEB2FAF785FC681CC5B2A078A842C8 -:10DAD000DCD8BDE8F0810144934810B500EB02109A -:10DAE0000A4601218330FAF7EAF8BDE81040FAF758 -:10DAF0002FB90A468D4910B5497841B18A4B9978BA -:10DB000029B10244D81CFAF7DAF8012010BD002030 -:10DB100010BD854A01EB410102EB41010268C1F8E9 -:10DB20000B218088A1F80F0170472DE9F0417E4D4F -:10DB300007460024A878002898BFBDE8F081C0B24D -:10DB4000A04217D905EB041010F1830612D0102162 -:10DB50003046F3F75DF968B904EB440005EB400883 -:10DB600008F20B113A463046FBF74EFDB8F80F01AC -:10DB7000A8F80F01601CC4B2A878A042DFD8BDE8A5 -:10DB8000F081014610226B48F3F755B96948704798 -:10DB900065498A78824203D90A1892F843210AB16A -:10DBA0000020704700EB400001EB400000F20B103A -:10DBB00070475D498A78824206D9084490F83B0153 -:10DBC000002804BF01207047002070472DE9F04174 -:10DBD0000E460746144606213046F3F719F9524D12 -:10DBE00098B1A97871B105F59D7011F0010F18BFBA -:10DBF00000F8014FA978490804D0447000F8024F9A -:10DC0000491EFAD10120BDE8F08138463146FFF7C0 -:10DC10008FFC10280CD000F00FF8102818BF08282F -:10DC200006D0284480F83B414FF00100BDE8F08168 -:10DC30004FF00000BDE8F0813B4B10B4844698786B -:10DC400001000ED0012201290BDB401EC0B21C18BE -:10DC500094F80341644504BF10BC7047521C8A42CB -:10DC6000F3DD10BC1020704770B52F4C01466218D0 -:10DC7000A078401EC0B2A07092F8035181423CD0FF -:10DC800004EB011304EB001C01EB4101DCF8036021 -:10DC9000C3F80360DCF80760C3F80760DCF80B60CA -:10DCA000C3F80B60DCF80F60C3F80F60DCF883602A -:10DCB000C3F88360DCF88760C3F88760DCF88B60AA -:10DCC000C3F88B60DCF88FC0C3F88FC0231800EB5B -:10DCD000400093F803C104EB400082F803C104EB59 -:10DCE0004101D0F80BC1C1F80BC1B0F80F01A1F888 -:10DCF0000F0193F83B0182F83B0104EBC50696F84F -:10DD00005A0110F0010F18BF70BD2846FFF794FFAD -:10DD1000102818BF70BD2078401EC0B22070A842E5 -:10DD200008BF70BD08E00000600F00206001002007 -:10DD30006C0100203311002004EBC000D0F8531117 -:10DD4000C6F85311D0F85701C6F857012078FFF7ED -:10DD500073FF10281CBF204480F8035170BD0000E1 -:10DD60004078704730B50546007801F00F0220F08A -:10DD70000F0010432870092912D2DFE801F00507CF -:10DD800005070509050B0F0006240BE00C2409E02C -:10DD9000222407E001240020E87003E00E2401E0C3 -:10DDA0000024FFDF6C7030BD007800F00F0070477A -:10DDB0000A68C0F803208988A0F807107047D0F8D7 -:10DDC00003200A60B0F80700888070470A68C0F82E -:10DDD00009208988A0F80D107047D0F809200A6042 -:10DDE000B0F80D00888070470278402322F040028E -:10DDF00003EA81111143017070470078C0F380106D -:10DE000070470278802322F0800203EAC111114397 -:10DE1000017070470078C009704770B514460E460F -:10DE200005461F2A88BFFFDF2246314605F109005B -:10DE3000F0F703FBA01D687070BD70B544780E4606 -:10DE40000546062C38BFFFDFA01F84B21F2C88BFF9 -:10DE50001F24224605F109013046F0F7EEFA20466C -:10DE600070BD70B514460E4605461F2A88BFFFDFF9 -:10DE70002246314605F10900F0F7DFFAA01D68706F -:10DE800070BD0968C0F80F1070470A88A0F8132009 -:10DE900089784175704790F8242001F01F0122F025 -:10DEA0001F02114380F824107047072988BF0721FB -:10DEB00090F82420E02322F0E00203EA411111430C -:10DEC00080F8241070471F3008F065B810B504467C -:10DED00000F000FB002818BF204410BDC17811F0ED -:10DEE0003F0F1BBF027912F0010F0022012211F037 -:10DEF0003F0F1BBF037913F0020F002301231A44C5 -:10DF000002EB4202530011F03F0F1BBF027912F0E7 -:10DF1000080F0022012203EB420311F03F0F1BBF49 -:10DF2000027912F0040F00220122134411F03F0F76 -:10DF30001BBF027912F0200F0022012202EBC20265 -:10DF400003EB420311F03F0F1BBF027912F0100FD9 -:10DF50000022012202EB42021A4411F03F0F1BBFC4 -:10DF6000007910F0400F00200120104410F0FF0055 -:10DF700014BF012100210844C0B2704770B5027877 -:10DF8000417802F00F02082A4DD2DFE802F00408BF -:10DF90000B4C4C4C0F14881F1F280AD943E00C2946 -:10DFA00007D040E0881F1F2803D93CE0881F1F28A6 -:10DFB00039D8012070BD4A1EFE2A34D88446C07864 -:10DFC00000258209032A09D000F03F04601C884222 -:10DFD00004D86046FFF782FFA04201D9284670BDF1 -:10DFE0009CF803004FF0010610F03F0F1EBF1CF11C -:10DFF0000400007810F0100F13D06446042160462E -:10E0000000F068FA002818BF14EB0000E6D0017891 -:10E0100001F03F012529E1D280780221B1EB501FA8 -:10E02000DCD3304670BD002070BD70B5017801258D -:10E0300001F00F01002404290AD007290DD0082976 -:10E040001CBF002070BD40780E2836D0204670BD21 -:10E050004078801F1F2830D9F8E7844640789CF824 -:10E0600003108A09032AF1D001F03F06711C814296 -:10E07000ECD86046FFF732FFB042E7D89CF80300C7 -:10E0800010F03F0F1EBF1CF10400007810F0100FBD -:10E0900013D066460421604600F01CFA002818BF21 -:10E0A00016EB0000D2D0017801F03F012529CDD236 -:10E0B00080780221B1EB501FC8D3284670BD10B440 -:10E0C000017801F00F01032920D0052921D14478DE -:10E0D000B0F81910B0F81BC0B0F81730827D222CB0 -:10E0E00017D1062915D3B1F5486F98BFBCF5FA7F53 -:10E0F0000FD272B1082A98BF8A420AD28B429CBFC3 -:10E10000B0F81D00B0F5486F03D805E040780C2842 -:10E1100002D010BC0020704710BC012070472DE9D0 -:10E12000F0411F4614460D00064608BFFFDF21469A -:10E13000304600F0CFF9040008BFFFDF30193A463F -:10E140002946BDE8F041F0F778B9C07800F03F000B -:10E150007047C02202EA8111C27802F03F021143E7 -:10E16000C1707047C07880097047C9B201F00102E0 -:10E17000C1F340031A4402EB4202C1F3800303EBF4 -:10E180004202C1F3C00302EB4302C1F3001303EBED -:10E1900043031A44C1F3401303EBC30302EB4302EE -:10E1A000C1F380131A4412F0FF0202D0521CD2B203 -:10E1B0000171C37802F03F0103F0C0031943C1703D -:10E1C000511C417070472DE9F0410546C078164654 -:10E1D00000F03F041019401C0F46FF2888BFFFDFE6 -:10E1E000281932463946001DF0F727F9A019401CBE -:10E1F0006870BDE8F081C178407801F03F01401AB5 -:10E20000401E80B2704710B590F803C00B460CF06A -:10E210003F0144780CF03F0CA4EB0C0CACF1010C6A -:10E220001FFA8CF4944288BF14462BB10844011D98 -:10E2300022461846F0F701F9204610BD4078704795 -:10E2400000B5027801F0030322F003021A430270C2 -:10E25000012914BF0229002104D0032916BFFFDFC2 -:10E26000012100BD417000BD00B5027801F003033B -:10E2700022F003021A430270012914BF022900216F -:10E2800004D0032916BFFFDF012100BD417000BD8E -:10E29000007800F003007047417841B1C078192838 -:10E2A00003D2BC4A105C884201D101207047002093 -:10E2B000704730B501240546C17019293CBFB548E7 -:10E2C000445C02D3FF2918BFFFDF6C7030BD70B50E -:10E2D00015460E4604461B2A88BFFFDF65702A4696 -:10E2E0003146E01CBDE87040F0F7A7B8B0F8070071 -:10E2F0007047B0F809007047C172090A017370478E -:10E30000B0F80B00704730B4B0F80720B0F809C07F -:10E31000B0F805300179941F40F67A45AC4298BFB9 -:10E32000BCF5FA7F0ED269B1082998BF914209D293 -:10E3300093429FBFB0F80B00B0F5486F012030BC8E -:10E3400098BF7047002030BC7047001D07F023BE07 -:10E35000021D0846114607F01EBEB0F809007047BE -:10E36000007970470A684260496881607047426876 -:10E370000A60806848607047098881817047808999 -:10E38000088070470A68C0F80E204968C0F812106B -:10E390007047D0F80E200A60D0F81200486070472D -:10E3A0000968C0F816107047D0F81600086070476A -:10E3B0000A68426049688160704742680A60806804 -:10E3C000486070470968C1607047C068086070475E -:10E3D000007970470A684260496881607047426806 -:10E3E0000A608068486070470171090A417170478E -:10E3F0008171090AC17170470172090A417270473F -:10E400008172090AC172704780887047C08870475E -:10E41000008970474089704701891B2924BF4189C1 -:10E42000B1F5A47F07D381881B2921BFC088B0F52F -:10E43000A47F01207047002070470A684260496845 -:10E440008160704742680A6080684860704701795F -:10E4500011F0070F1BBF407910F0070F00200120BB -:10E460007047017911F0070F1BBF407910F0070FBB -:10E470000020012070470171704700797047417199 -:10E480007047407970478171090AC1717047C0882F -:10E4900070470179407901F007023F498A5C012AFF -:10E4A00006D800F00700085C01289CBF01207047D7 -:10E4B00000207047017170470079704741717047C3 -:10E4C0004079704730B50C460546FB2988BFFFDF11 -:10E4D0006C7030BDC378024613F03F0008BF704730 -:10E4E0000520127903F03F0312F0010F37D0002905 -:10E4F00014BF0B20704700BF12F0020F32D0012969 -:10E5000014BF801D704700BF12F0040F2DD00229E8 -:10E5100014BF401C704700BF12F0080F28D0032919 -:10E5200014BF801C704700BF12F0100F23D00429C5 -:10E5300014BFC01C704700BF12F0200F1ED0052969 -:10E540001ABF1230C0B2704712F0400F19D006291E -:10E550001ABF401CC0B27047072918D114E0002927 -:10E56000CAD114E00129CFD111E00229D4D10EE0A3 -:10E570000329D9D10BE00429DED108E00529E3D134 -:10E5800005E00629E8D102E0834288BF70470020F9 -:10E5900070470000246302001C63020030B490F84E -:10E5A00064508C88B1F808C015F00C0F1BD000BF68 -:10E5B000B4F5296F98BF4FF4296490F8655015F0B1 -:10E5C0000C0F17D0BCF5296F98BF4FF4296C4A88FF -:10E5D000C988A0F84420A0F84810A0F84640A0F848 -:10E5E0004AC030BC7047002B1CBF157815F00C0FCB -:10E5F000DED1E2E7002B1CBF527812F00C0FE1D104 -:10E60000E5E7DDF800C08181C2810382A0F812C075 -:10E6100070471B2202838282C281828142800281F2 -:10E62000028042848284828359B14FF429614183FC -:10E63000C18241820182C18041818180C184018582 -:10E6400070474FF4A4714183C18241820182C1802D -:10E6500041818180C18401857047F0B4B0F84820C1 -:10E66000818F468EC58E8A4228BF0A4690F8651073 -:10E670004FF0000311F00C0F18BF4FF4296106D1C1 -:10E68000B0F84AC0B0F840108C4538BF61464286A9 -:10E69000C186048FB0F83AC0944238BF14468C4506 -:10E6A00038BF8C460487A0F83AC0B2420ABFA942DC -:10E6B0004FF0010C4FF0000C058EB0F84410C28FE3 -:10E6C000848E914228BF114690F8642012F00C0FFE -:10E6D00018BF4FF4296206D1B0F84660B0F8422066 -:10E6E000964238BF324690F85A60022E0AD0018610 -:10E6F0008286A9420ABFA2420120002040EA0C0003 -:10E70000F0BC70478D4238BF2946944238BF22463C -:10E7100080F85A30EBE7508088899080C889D08093 -:10E72000088A1081488A508101201070704730B4E7 -:10E7300002884A80B0F830C0A1F804C0838ECB8034 -:10E74000428E0A81C48E4C81B0F85650A54204BF57 -:10E75000B0F85240944208D1B0F858409C4202BFF1 -:10E76000B0F854306345002301D04FF001030B7320 -:10E7700000F13003A0F852201A464B89D3848B88CD -:10E780009384CA88A0F858204FF00100087030BC6C -:10E79000704730B404460A46088E91F864104FF46E -:10E7A000747311F00C0F1CBF03EB801080B21ED0ED -:10E7B000918E814238BF0846118F92F865C01CF0D7 -:10E7C0000C0F1CBF03EB811189B218D0538F8B4201 -:10E7D00038BF194692F866301CF00C0F08BF0023B2 -:10E7E000002C0CBF0122002230BCF2F798BC022999 -:10E7F00007BF80003C30C000703080B2D8E7BCF169 -:10E80000020F07BF89003C31C900703189B2DDE7D2 -:10E810002DE9F041044606F099FCC8B9FE4F78682E -:10E8200090F8221001260025012914D00178012931 -:10E830001BD090F8281001291CBF0020BDE8F081F2 -:10E84000657018212170D0F82A10616080F8285076 -:10E850000120BDE8F081657007212170416A616087 -:10E8600080F822500120BDE8F081657014212170EC -:10E87000811C2022201DEFF7E0FD257279680D70C4 -:10E8800081F82850E54882888284C26B527B80F8E8 -:10E89000262080F82260C86B0088F5F738FCF5F771 -:10E8A000E0F8D5E7DC4840680178002914BF80888B -:10E8B0004FF6FF70704730B5D74C83B00D462078C7 -:10E8C0007F2808BFFFDF94F900307F202070D4F844 -:10E8D00004C09CF85000062808BF002205D09CF810 -:10E8E000500008280CBF022201229CF85400CDE9F8 -:10E8F000000302929CF873309CF880200CF13201E6 -:10E90000284606F08FFC03B0BDE8304006F01FBE7D -:10E910002DE9F04106F05FFC002818BF06F0E4FB8B -:10E92000BD4C606800F1840290F87610895C80F834 -:10E930008010002003F07EF828B3FAF753F86068DF -:10E94000B74990F855000D5C2846F9F7A3FD6068BB -:10E950004FF0000680F8735090F8801011F00C0F03 -:10E960000CBF25200F20F9F76CFC606890F8801030 -:10E970000120F9F711FE606890F84010032918BFD4 -:10E9800002290FD103E0BDE8F04101F02FB990F862 -:10E9900076108430085C012804D101221146002041 -:10E9A000FAF707F9FAF7D5F8606890F88050012D6A -:10E9B00007BF0127032100270521A068FFF799F869 -:10E9C000616881F8520040B1002F18BF402521D066 -:10E9D000F9F787F92846FAF79DF86068806DFAF72D -:10E9E0000DF8606890F85410FF291CBF6D30FEF7D9 -:10E9F000B4FFFF21606880F8531080F8541080F84D -:10EA0000626080F8616080F87D60062180F85010B7 -:10EA1000BDE8F08115F00C0F14BF55255025D7E740 -:10EA200070B57D4C0646606800F150052046806850 -:10EA300041B1D0F80510C5F81D10B0F80900A5F8CF -:10EA4000210003E005F11D01FFF7B9F9A068FFF708 -:10EA5000D4F985F82400A0680021032E018002D09B -:10EA6000052E04D03DE00321FFF77CF939E00521B4 -:10EA7000FFF778F96068C06B00F10E01A068FFF73E -:10EA800000FA6068C06B00F11201A068FFF7FDF9A1 -:10EA9000D4E90110CA6B527D8275CA6BD28AC275E5 -:10EAA000120A0276CA6B52884276120A8276CA6BC2 -:10EAB0009288C276120A0277CA6BD2884277120A0B -:10EAC0008277C96B0831FFF7FEF96068C06B017E81 -:10EAD000A068FFF7E0F9606890F88610A068FFF77B -:10EAE000E4F905F11D01A068FFF770F995F824100D -:10EAF000A068FFF786F9606800F1320590F8316090 -:10EB000090F8511091B190F84010032906D190F877 -:10EB10003910002918BF90F8560001D190F8530021 -:10EB2000FFF736F800281CBF012605462946A068D5 -:10EB3000FFF73EF93146A068BDE87040FFF754B9D1 -:10EB40003549496881F84B00704770B5324D002453 -:10EB50000126A8606968A1F8814081F8834081F8A6 -:10EB6000506091F85020022A1FBF91F850100129DF -:10EB7000FFDF70BD06F0CDFA6868047080F82240AF -:10EB800080F8284090F8520030B1F9F7CDFFF9F73E -:10EB9000BCF8686880F852406868072180F84A40ED -:10EBA00080F8396080F8404080F8554080F84B404C -:10EBB00080F87D4080F8381070BD2DE9F041164C8A -:10EBC000054686B0606890F85000012818BF0228FA -:10EBD00005D003281EBF0C2006B0BDE8F081687A7E -:10EBE000022839D0F9F76FFB0220F9F701FF0D4930 -:10EBF00001F10C0090E80D108DE80D10D1E907012E -:10EC0000CDE904016846F9F7E1FE606890F94B0030 -:10EC1000F9F732FCA06807E07401002044110020DD -:10EC20004363020040630200F9F7E5FEFC48F9F790 -:10EC3000B9FEFC48F9F726FC606890F831103230D4 -:10EC4000F9F7A5FB0F210720F9F7BFFB606890F8E3 -:10EC50003900E0B1FEF70FFF6168287A01F1840204 -:10EC600081F87600287A805C81F880006868886581 -:10EC70002A68CA65687A68B1012824D00525022867 -:10EC800008BF81F850506FD0032878D080E0FEF79D -:10EC9000A8FEE1E7E44B91F83850002291F85500C6 -:10ECA000401CA3FB006C4FEA5C0CACEB8C0C60448A -:10ECB00081F8550025FA00F010F0010F03D1501C27 -:10ECC000C2B2032AEAD3002681F87D6091F8490098 -:10ECD000002804BF91F85100002841D0F7F744FA0A -:10ECE000074660683946406CF7F736FFDFF83C832B -:10ECF000054690FBF8F008FB105041423846F6F705 -:10ED00001AFF6168486495FBF8F08A6F10448867C1 -:10ED1000FEF7EEFD01466068826F914220D847649D -:10ED2000866790F8510000281CBF0120FEF7FDFE09 -:10ED30000121606890F84A20002A1CBF90F8492001 -:10ED4000002A0DD090F8313000F13202012B04D1AD -:10ED5000527902F0C002402A08D03230FAF78CFC17 -:10ED60006168042081F8500012E008E00125FEF7F8 -:10ED70000DFF61682A463231FAF746FCF0E7002AB7 -:10ED800018BFFFDF012000F089FF606880F8505055 -:10ED900006B00020BDE8F08170B5A54D686890F818 -:10EDA000501004292ED005291CBF0C2070BD90F8EE -:10EDB0007D100026002990F883104FEA511124D0CD -:10EDC000002908BF012407D0012908BF022403D06D -:10EDD000022914BF00240824C06D00281CBF002095 -:10EDE00000F05CFF6868806DF9F708FE686890F8CD -:10EDF0004010022943D0032904BF90F86C10012968 -:10EE000041D04DE0FFF784FD52E0002908BF012406 -:10EE100007D0012908BF022403D0022914BF00240F -:10EE20000824C06D00281CBF002000F037FF686870 -:10EE3000806DF9F7E3FD686890F84010022906D06C -:10EE4000032904BF90F86C10012904D010E090F859 -:10EE50006C1002290CD1224614F00C0F04D090F84B -:10EE60004C00012808BF042201210020F9F7A1FE6F -:10EE70006868072180F8804080F8616016E090F8AB -:10EE80006C1002290CD1224614F00C0F04D090F81B -:10EE90004C00012808BF042201210020F9F789FE57 -:10EEA0006868082180F8804080F8616080F8501020 -:10EEB000002070BD5E49002210F0010F496802D0A9 -:10EEC000012281F8842010F0080F03D0114408209B -:10EED00081F88400002070475549496881F848004E -:10EEE000704710B5524C636893F83030022B14BF52 -:10EEF000032B00280BD100291ABF02290120002072 -:10EF00001146FEF7F8FC08281CBF012010BD606800 -:10EF100090F83000002816BF022800200120BDE82C -:10EF20001040FAF731BB4248406890F830000028A2 -:10EF300016BF022800200120FAF726BB3C49496889 -:10EF400081F8300070473A49496881F84A007047B3 -:10EF500070B5374C616891F83000002816BF022860 -:10EF60000020012081F8310001F13201FAF7F6FAB0 -:10EF7000606890F83010022916BF03290121002192 -:10EF800080F8511090F8312000F132034FF0000565 -:10EF9000012A04BF5B7913F0C00F0AD000F13203DD -:10EFA000012A04D15A7902F0C002402A01D000227D -:10EFB00000E0012280F84920002A04BF002970BD2A -:10EFC0008567F7F7D1F86168486491F85100002827 -:10EFD0001CBF0020FEF7A9FD0026606890F84A10CB -:10EFE00000291ABF90F84910002970BD90F831200F -:10EFF00000F13201012A04D1497901F0C001402910 -:10F0000005D02946BDE870403230FAF735BBFEF72F -:10F01000BDFD61683246BDE870403231FAF7F4BA9E -:10F020004063020046630200ABAAAAAA40420F0056 -:10F030007401002070B5FF4D0C4600280CBF012361 -:10F040000023696881F8393081F842004FF00800E8 -:10F0500081F856000CD1002C1ABF022C0120002090 -:10F060001146FEF748FC6968082881F8560001D06F -:10F07000002070BD022C14BF032C1220F8D170BDEB -:10F08000002818BF112070470328EA4A526808BFB9 -:10F09000D16382F840000020704710B5E54C6068ED -:10F0A00090F8401003291CBF002180F8601001D0A7 -:10F0B000002010BD0123C16B1A460020F2F738F87A -:10F0C0006168CA6B526A904294BF0120002081F8A7 -:10F0D0006000EDE7D748416891F84000032804D06C -:10F0E000012818BF022807D004E091F84200012847 -:10F0F00008BF70470020704791F84100012814BFF5 -:10F1000003280120F6D1704770B5F9F7F7FCF9F73D -:10F11000D6FCF9F79FFBF9F74BFCC64C002560685D -:10F1200090F8520030B1F9F7FFFCF8F7EEFD606897 -:10F1300080F8525060680121A0F8815080F8835017 -:10F1400080F8501080F82850002070BDB94810B5E4 -:10F150004068643006F0B1FB002010BDB5480121C5 -:10F16000406890F84020032A03BF80F82A10C26B41 -:10F170001288002218BF80F82A20828580F8281083 -:10F180007047AC49496881F88600704701780023D0 -:10F1900011F0010FA749496809D04278032A08BF36 -:10F1A000CB6381F84020012281F884201346027845 -:10F1B00012F0040F0CD082784FF0000C032A08BF25 -:10F1C000C1F83CC081F840200B44082283F8842019 -:10F1D000C27881F830200279002A16BF022A012362 -:10F1E000002381F8393081F84120427981F83820B4 -:10F1F000807981F848004FF0000070478D484068E2 -:10F200008030704770B58B4C06460D46606890F8AC -:10F210005000032818BFFFDF022E1EBF032EFFDFA2 -:10F2200070BD002D18BF06F0A1F900216068A0F89C -:10F23000811080F88310012180F8501070BD00F01B -:10F24000D5BC2DE9F0477B4C0646894660684FF0F7 -:10F250000108072E90F8397038BF032540D3082ED7 -:10F2600084BF0020BDE8F08790F85010062908BF41 -:10F27000002105D090F8501008290CBF022101216F -:10F2800090F8800005F0AEFF002873D1A068C17827 -:10F2900011F03F0F12D0027912F0010F0ED0616809 -:10F2A0004FF0050591F85220002A18BFB9F1000F60 -:10F2B00016D091F88010012909D011E011F03F0F0C -:10F2C0001ABF007910F0100F002F53D14CE04FF00F -:10F2D00001024FF00501FEF74CFB616881F8520016 -:10F2E000A16808782944C0F3801030B1487900F053 -:10F2F000C000402808BF012000D00020616891F8BC -:10F300005210002918BF002807D0FEF74DFB014618 -:10F31000606880F8531080F86180606890F853103E -:10F32000FF292AD080F854100846FEF74AFB40EA2D -:10F330000705606890F85320FF2A18BF002D10D0F1 -:10F34000072E0ED3A068C17811F03F0F09D00179C4 -:10F3500011F0020F05D00B21FEF7BDFB606880F8AD -:10F3600062802846BDE8F087FEF75FF9002808BFF5 -:10F37000BDE8F0870120BDE8F087A36890F8392048 -:10F3800059191B78C3F3801C00F153036046FEF744 -:10F3900096F90546CDE72DE9F043264C87B0A068E5 -:10F3A000FEF7E0FE7F264FF00108002558B1022746 -:10F3B00001287DD0022800F0EF80F9F74BFA07B062 -:10F3C0000620BDE8F083F9F745FA616891F840003E -:10F3D000032800F01581A068C27812F03F0F05D015 -:10F3E000037913F0100F18BF012700D10027002F59 -:10F3F00014BF0823012312F03F0F00F001810079B0 -:10F4000033EA000240F0FC8010F0020F08D091F8BF -:10F410008000002105F064FE002808BF012000D014 -:10F4200000208DF80C508DF810508DF814504FF0CE -:10F43000FF0801E074010020D0B105AA03A904A8C7 -:10F4400000F07AFC606890F831809DF80C0000288C -:10F4500018BF48F002080BD1A068FEF7DBFC81461C -:10F460000121A068FEF732FD4946F8F79AFF28B35C -:10F47000FFB1012000F0DDFB002852D020787F286A -:10F4800008BFFFDF94F900102670606890F85420E0 -:10F49000CDE90021029590F8733090F8802000F1BA -:10F4A0003201404605F0BEFE606880F86C50A3E073 -:10F4B00038E041460020FFF7FEF9A1E0606890F8CF -:10F4C0004100032818BF02282BD19DF81000002806 -:10F4D00027D09DF80C00002823D1F7B1012000F0BF -:10F4E000A8FB00281DD020787F2808BFFFDF94F9F3 -:10F4F00000102670606890F85420CDE90021029534 -:10F5000090F8733090F8802000F13201FE2005F071 -:10F5100089FE606880F86C506EE0FE210020FFF7E5 -:10F52000CAF96DE0F9F796F9A0681821C27812F0CF -:10F530003F0F65D00279914362D10421FEF7C6FCEA -:10F54000616891F84020032A01BF8078B7EB501F13 -:10F5500091F86000002853D04FF0010000F069FBE3 -:10F56000E8B320787F2808BFFFDF94F900102670E9 -:10F57000606890F85420CDE90021029590F873302E -:10F5800090F8802000F13201FF2005F04BFE60680A -:10F5900080F86C8030E000BFF9F75CF9606890F8A3 -:10F5A000400003282CD0A0681821C27812F03F0F29 -:10F5B00026D0007931EA000022D1012000F039FB89 -:10F5C00068B120787F2808BFFFDF94F9001026700B -:10F5D000606890F85420CDE90021029500E00FE02A -:10F5E00090F8733090F8802000F13201FF2005F090 -:10F5F00019FE606880F86C7007B00320BDE8F083E6 -:10F6000007B00620BDE8F083F0B5FE4C074683B096 -:10F6100060686D460078002818BFFFDF002661682B -:10F620008E70C86B02888A8042884A8382888A8367 -:10F63000C088C88381F8206047B10121A068FEF727 -:10F6400045FC0546A0680078C10907E06946A06846 -:10F65000FEF7B5FBA0680078C0F380116068012751 -:10F6600090F85120002A18BF002904D06A7902F0CE -:10F67000C002402A26D090F84A20002A18BF00294C -:10F6800003D0697911F0C00F1CD000F10E00E3F730 -:10F69000B3FC616891F85400FF2819D001F1080209 -:10F6A000C91DFEF743F9002808BFFFDF6068C17974 -:10F6B00041F00201C171D0F86D104161B0F87110D4 -:10F6C00001830FE02968C0F80E10A9884182E0E7A5 -:10F6D000C86B427ECA71D0F81A208A60C08B8881BC -:10F6E0004E610E8360680770C26B90F84B1082F811 -:10F6F0006710C06B0088F4F70AFDF4F7A3F903B0B4 -:10F70000F0BD2DE9F041BF4C0546002760684FF081 -:10F7100001083E4690F84000012818BF022802D098 -:10F72000032818BFFFDF5DB1A068FEF727FC18B9FA -:10F73000A068FEF77AFC18B100F08FFB074645E0A1 -:10F74000606890F850007F25801F06283ED2DFE8D1 -:10F7500000F003191924352FAA48F9F709FA0028EF -:10F7600008BF2570F9F7EBF9606890F8520030B1E6 -:10F77000F9F7DAF9F8F7C9FA606880F85260F9F732 -:10F7800069F830E09F48F9F7F3F9002808BF2570C1 -:10F79000F9F7D5F905F0EAFEC3E09A48F9F7E8F978 -:10F7A000002808BF2570F9F7CAF9F9F753F81AE0ED -:10F7B0009448F9F7DDF930B9257004E09148F9F77C -:10F7C000D7F90028F8D0F9F7BAF9AAE0102F80F09D -:10F7D0003881DFE807F01E9DA6AAF1F108B3F2F127 -:10F7E000F1F10C832051BDE8F041FFF791B80320FF -:10F7F00002F020F9002870D000210320FFF710F953 -:10F80000012211461046F9F7D4F961680C2081F8FD -:10F810005000BDE8F081606800F15005042002F05E -:10F8200009F900287DD00E202870012002F0FDFC8F -:10F83000A06861680078C0F3401081F8750000216D -:10F840000520FFF7EDF87048A1684FF0200CC26B5F -:10F850000B78527B23F020030CEA42121A430A7001 -:10F86000C16B95F825304A7B1A404A73C06B28213A -:10F8700080F86610BDE8F081062002F0DBF8002871 -:10F880004FD0614D0F2085F85000022002F0CDFCD2 -:10F890006068012190F880200846F9F78AF9A0688D -:10F8A00061680078C0F3401081F8750001210520DF -:10F8B000FFF7B6F8E86B80F80D80A068017821F0BA -:10F8C00020010170F9F75DFD002818BFFFDF282037 -:10F8D000E96B81F86600BDE8F08122E0052002F0C6 -:10F8E000A9F8F0B101210320FFF79AF8F9F749FDD3 -:10F8F000002818BFFFDF6068012190F880200846CB -:10F90000F9F757F961680D2081F85000BDE8F081E2 -:10F910006068A0F8816080F8836080F85080BDE85E -:10F92000F081BDE8F04100F061B96168032081F821 -:10F930005000BDE8F041082002F077BC606890F804 -:10F940008310490908BF012507D0012908BF0225F6 -:10F9500003D0022914BF00250825C06D00281CBF54 -:10F96000002000F09BF96068806DF9F747F8606847 -:10F9700090F84010022906D0032904BF90F86C10BB -:10F98000012904D010E090F86C1002290CD12A460D -:10F9900015F00C0F04D090F84C00012808BF042289 -:10F9A00001210020F9F705F96068072180F88050EF -:10F9B00080F8616041E000E043E0606890F8831007 -:10F9C000490908BF012507D0012908BF022503D036 -:10F9D000022914BF00250825C06D00281CBF002087 -:10F9E00000F05CF96068806DF9F708F8606890F8DD -:10F9F000401002290AD0032904BF90F86C10012995 -:10FA000008D014E0740100204411002090F86C101C -:10FA100002290CD12A4615F00C0F04D090F84C00A6 -:10FA2000012808BF042201210020F9F7C2F860680C -:10FA3000082180F8805080F8616080F85010BDE89F -:10FA4000F081FFDFBDE8F08170B5FE4C606890F892 -:10FA5000503000210C2B38D001220D2B40D00E2B22 -:10FA600055D00F2B1CBFFFDF70BD042002F0DDFB63 -:10FA7000606890F880100E20F8F7E3FB606890F85B -:10FA8000800010F00C0F14BF282100219620F8F7F9 -:10FA9000D3FFF9F75EF86068052190F88050A06800 -:10FAA000FEF727F8616881F8520048B115F00C0F95 -:10FAB0000CBF50255525F8F714F92846F9F72AF810 -:10FAC00061680B2081F8500070BDF9F742F8002101 -:10FAD0009620F8F7B1FF6168092081F8500070BDE9 -:10FAE00090F88010FF20F8F7ACFB606890F8800079 -:10FAF00010F00C0F14BF282100219620F8F79CFF6E -:10FB0000F9F727F861680A2081F8500070BDA0F865 -:10FB1000811080F8831080F850200020FFF774FDDA -:10FB2000BDE87040032002F080BB70B5C54C606832 -:10FB300090F850007F25801F062828BF70BDDFE8A1 -:10FB400000F0171F1D032A11BE48F9F711F800280D -:10FB500008BF2570F8F7F3FFF8F77CFEBDE87040AA -:10FB6000FEF7D6BEB748F9F703F8C8B9257017E015 -:10FB7000B448F8F7FDFF40B9257006E005F0F6FC43 -:10FB8000B048F8F7F5FF0028F6D0F8F7D8FFBDE841 -:10FB9000704000F02BB8AB48F8F7EAFF0028E5D03A -:10FBA000F8F7CDFF60680021643005F037FEBDE84E -:10FBB000704000F01BB870B5A24C06460D460129F6 -:10FBC00008D0606890F880203046BDE87040134649 -:10FBD00002F077BBF8F75CFA61680346304691F8AB -:10FBE00080202946BDE8704002F06BBB70B5F8F785 -:10FBF00085FFF8F764FFF8F72DFEF8F7D9FE914C72 -:10FC00000025606890F8520030B1F8F78DFFF8F7E2 -:10FC10007CF8606880F852506068022180F85010CB -:10FC2000A0F8815080F88350BDE87040002002F0B9 -:10FC3000FCBA70B5834D06460421A868FEF746F964 -:10FC4000044605F0C8FA002808BF70BD207800F00F -:10FC50003F00252814D2F8F761FA217811F0800FBF -:10FC60000CBF1E214FF49671B4F80120C2F30C02B0 -:10FC700012FB01F10A1AB2F5877F28BF814201D237 -:10FC8000002070BD68682188A0F88110A17880F8F4 -:10FC900083103046BDE8704001F0CCBE2DE9F04144 -:10FCA000684C0746606800F1810690F883004009BF -:10FCB00008BF012507D0012808BF022503D002286C -:10FCC00014BF00250825F8F78DFE307800F03F06B8 -:10FCD0003046F8F7DFFB606880F8736090F86C00DE -:10FCE00002280CBF4020FF202946F8F7AAFA27B1C6 -:10FCF00029460120F8F795FC05E060682A46C16DA9 -:10FD00000120F8F7E2FCF8F724FF0521A068FDF7D1 -:10FD1000F0FE6168002881F8520008BFBDE8F0815C -:10FD200015F00C0F0CBF50245524F7F7DAFF2046CE -:10FD3000BDE8F041F8F7EEBE2DE9F74F414C002544 -:10FD4000914660688A4690F8510000280CBF4FF039 -:10FD500001084FF00008A0680178CE090121FEF7E4 -:10FD6000B5F836B1407900F0C000402808BF012640 -:10FD700000D00026606890F85210002961D090F8F9 -:10FD800040104FF0000B032906D190F839100029DC -:10FD900018BF90F856700ED1A068C17811F03F0FCF -:10FDA0001CBF007910F0010F02D105F061F940B3DA -:10FDB000606890F85370FF2F18BF082F21D0384685 -:10FDC000FDF7D9FB002818BF4FF00108002E38D0EE -:10FDD000606890F8620030B1FDF7F1FD054660689B -:10FDE00080F862B02DE03846FDF791FD054601210F -:10FDF000A068FEF76BF801462846F9F7D3FB0546E5 -:10FE00001FE0F6B1606890F86100D0B9A068C178D1 -:10FE100011F03F0F05D0017911F0010F18BF0B2130 -:10FE200000D105210022FDF7A4FD616881F8520090 -:10FE300038B1FDF7B9FDFF2803D06168012581F8CD -:10FE4000530001E0740100208AF800500098067009 -:10FE500089F8008003B0BDE8F08F2DE9F04FFF4C2A -:10FE600087B00025606890F850002E46801F4FF044 -:10FE70007F08062880F0D581DFE800F00308088BB2 -:10FE8000FDDB00F0F8FB054600F0CCB9F348F8F7CD -:10FE90006FFE002808BF84F80080F8F750FEA068C5 -:10FEA000FDF782FF0546072861D1A068FEF75AF9E1 -:10FEB0000146606890F86C208A4258D190F8501042 -:10FEC000062908BF002005D090F8500008280CBF74 -:10FED0000220012005F08AF970B90321A068FDF71E -:10FEE000F5FF002843D001884078C1F30B010009D9 -:10FEF00005F07BFC00283AD000212846FFF7A1F945 -:10FF0000A0B38DF80C608DF808608DF8046062680D -:10FF1000FF2592F8500008280CBF02210121A0689B -:10FF2000C37813F03F0F1CBF007910F0020F12D0FE -:10FF300092F8800005F0D4F868B901AA03A902A8D4 -:10FF4000FFF7FAFE606890F831509DF80C00002829 -:10FF500018BF45F002052B469DF804209DF80810B7 -:10FF60009DF80C0000F0D5F9054603E0FFE705F029 -:10FF7000FDFA0225606890F85200002800F05281D6 -:10FF8000F8F7D2FDF7F7C1FE606880F8526000F024 -:10FF900049B9A068FDF708FF0646A1686068CA78FD -:10FFA00090F86D309A4221D10A7990F86E309A42D9 -:10FFB0001CD14A7990F86F309A4217D18A7990F81B -:10FFC00070309A4212D1CA7990F871309A420DD1AC -:10FFD0000A7A90F872309A4208D1097890F8740041 -:10FFE000C1F38011814208BF012500D00025F8F738 -:10FFF00031FC9A48F8F7BCFD002808BF84F800805F -:020000040002F8 -:10000000F8F79DFD042E11D185B120787F2808BF17 -:10001000FFDF94F9003084F80080606890F8732066 -:1000200090F87D1090F8540005F06EFB062500F066 -:10003000F9B802278948F8F79BFD002808BF84F823 -:100040000080F8F77CFDA068FDF7AEFE0546A068CD -:10005000FEF788F8082D08BF00287CD1A0684FF073 -:100060000301C27812F03F0F75D0007931EA000029 -:1000700071D1606800E095E000F1500890F8390017 -:10008000002814BF98F8066098F803604FF0000944 -:1000900098F8020078B1FDF787FC0546FF280AD0E2 -:1000A0000146A068401DFDF758FCB5420CBF4FF05B -:1000B00001094FF000090021A068FDF707FF0622A3 -:1000C00008F11D01EEF78CF940B9A068FDF795FE27 -:1000D00098F82410884208BF012000D0002059EA77 -:1000E00000095DD0606800F1320590F831A098F801 -:1000F000010038B13046FDF74BFD00281CBF054616 -:100100004FF0010A4FF00008A06801784FEAD11BB8 -:100110000121FDF7DBFEBBF1000F07D0407900F0B5 -:10012000C000402808BF4FF0010B01D04FF0000B7A -:100130000121A068FDF7CAFE06222946EEF750F914 -:1001400030B9A068FDF766FE504508BF012501D013 -:100150004FF0000500E023E03BEA050018BFFF2E4A -:100160000DD03046FDF7D3FB060008D00121A06872 -:10017000FDF7ACFE01463046F9F714FA804645EA31 -:10018000080019EA000F0BD060680121643005F007 -:1001900045FB01273846FFF737FA052002F045F8FE -:1001A0003D463FE002252D48F8F7E2FC002808BF55 -:1001B00084F80080F8F7C3FCA068FDF7F5FD06465B -:1001C000A068FDF7CFFF072E08BF00282AD1A0683E -:1001D0004FF00101C27812F03F0F23D00279914312 -:1001E00020D1616801F150060021FDF76FFE062263 -:1001F00006F11D01EEF7F4F8A0B9A068FDF7FDFDCA -:1002000096F8241088420DD160680121643005F011 -:1002100005FBFF21022000F009F8002818BF032584 -:1002200000E0FFDF07B02846BDE8F08F2DE9F0437E -:100230000A4C0F4601466068002683B090F87D2086 -:10024000002A35D090F8500008280CBF022501255F -:10025000A168C87810F03F0F02E000007401002090 -:10026000FD484FF000084FF07F0990F900001CBFD7 -:10027000097911F0100F22D07F2808BFFFDF94F911 -:10028000001084F80090606890F85420CDE90021B7 -:10029000029590F8733090F8802000F132013846D2 -:1002A00004F0C0FF05F0AAFA10B305F050F92CE0F5 -:1002B000002914BF0221012180F87D10C2E77F28A8 -:1002C00008BFFFDF94F9001084F80090606890F890 -:1002D0005420CDE90021029590F8733090F88020E9 -:1002E00000F13201384604F09DFF05F030F90CE0D2 -:1002F0000220FFF79EFC30B16068012680F86C8018 -:10030000F8F7A8FA01E005F031F903B03046BDE88E -:10031000F0832DE9F047D04C054684B09A46174645 -:100320000E46A068FDF71EFF4FF00109002800F0FF -:10033000CF804FF00208012808D0022800F00E817B -:1003400005F014F904B04046BDE8F087A068092123 -:10035000C27812F03F0F00F059810279914340F0CA -:100360005581616891F84010032906D012F0020F00 -:1003700008BFFF2118D05DB115E00021FDF7A6FDF3 -:1003800061680622C96B1A31EEF72AF848BB1EE0F5 -:10039000FDF740FD05460121A068FDF797FD2946C0 -:1003A000F7F7FFFF18B15146012000F051B960681E -:1003B00090F84100032818BF022840F02781002E42 -:1003C0001CBFFE21012040F0438100F01FB9A0684E -:1003D000FDF713FD6168C96B497E884208BF01269D -:1003E00000D00026A068C17811F03F0F05D0017938 -:1003F00011F0020F01D06DB338E0616891F842202E -:10040000012A01D096B11BE0D6B90021FDF75EFDAF -:1004100061680268C96BC1F81A208088C883A06827 -:10042000FDF7EBFC6168C96B487609E091F8530071 -:1004300091F85610884203D004B04046BDE8F087DA -:100440006068643005F02EFA002840D004B00F2018 -:10045000BDE8F08767B1FDF7DDFC05460121A06826 -:10046000FDF734FD2946F7F79CFF08B1012200E0B3 -:100470000022616891F84200012807D040B92EB9E6 -:1004800091F8533091F856108B4201D1012100E0D0 -:1004900000210A421BD0012808BF002E11D14FF0C5 -:1004A0000001A068FDF712FD61680268C96BC1F820 -:1004B0001A208088C883A068FDF79FFC6168C96B1B -:1004C00048766068643005F0EDF90028BED19DE003 -:1004D00060682F46554690F840104FF002080329F7 -:1004E000AAD0A168CA7812F03F0F1BBF097911F09A -:1004F000020F002201224FF0FF0A90F85010082945 -:100500000CBF0221012192B190F8800004F0E8FDB7 -:1005100068B95FB9A068FDF77DFC07460121A068B6 -:10052000FDF7D4FC3946F7F73CFF48B1AA465146DF -:100530000020FFF77BFE002818BF4FF003087BE781 -:10054000606890F84100032818BF02287FF474AF58 -:10055000002E18BF4FF0FE0AE9D16DE7616891F8EF -:100560004030032B52D0A0684FF0090CC27812F033 -:100570003F0F4BD002793CEA020C47D1022B06D048 -:1005800012F0020F08BFFF2161D0E5B35EE012F068 -:10059000020F4FF07F0801D04DB114E001F164006B -:1005A00005F080F980B320787F2842D013E067B34C -:1005B000FDF730FC05460121A068FDF787FC2946C0 -:1005C000F7F7EFFE08B36068643005F06BF9D8B157 -:1005D00020787F282DD094F9001084F8008060687E -:1005E00090F85420CDE90021CDF8089090F87330B0 -:1005F00090F8802000F13201504604F013FE0D20E7 -:1006000004B0BDE8F08716E000E001E00220F7E763 -:10061000606890F84100032818BF0228F6D1002E28 -:10062000F4D04FF0FE014FF00200FEF744F9022033 -:10063000E6E7FFDFCFE7FDF7EDFB05460121A06808 -:10064000FDF744FC2946F7F7ACFE38B151460220CD -:10065000FEF731F9DAE7000074010020606890F8D5 -:100660004100032818BF0228D0D1002E1CBFFE2154 -:100670000220EDD1CAE72DE9F84F4FF00008F74806 -:10068000F8F776FA7F27F54C002808BF2770F8F7AF -:1006900056FAA068FDF788FB81460121FEF7D1FDDF -:1006A000616891F88020012A14D0042A1CBF082A0E -:1006B000FFDF00F0D781606890F8520038B1F8F79A -:1006C00033FAF7F722FB6168002081F852004046B8 -:1006D000BDE8F88F0125E24EB9F1080F3AD2DFE804 -:1006E00009F03EC00439393914FC0546F8F7B2F870 -:1006F000002D72D0606890F84000012818BF0228D1 -:100700006BD120787F2869D122E018B391F840009E -:10071000022802D0012818D01CE020787F2808BFCA -:10072000FFDF94F90000277000906068FF2190F8C7 -:10073000733090F85420323004F02FFF61680020AD -:100740004FF00C0881F87D00B5E720787F2860D154 -:10075000FFDF5EE0F8F77EF84FF00608ABE74FF0FA -:100760000008002800F0508191F84000022836D09F -:1007700001284BD003289ED1A068CA6BC37892F899 -:100780001AC0634521D1037992F81BC063451CD17F -:10079000437992F81CC0634517D1837992F81DC044 -:1007A000634512D1C37992F81EC063450DD1037A17 -:1007B00092F81FC0634508D1037892F819C0C3F3BB -:1007C0008013634508BF012300D0002391F8421035 -:1007D00001292CD0C3B300F013B93FE019E0207811 -:1007E0007F2808BFFFDF94F9000027700090606841 -:1007F000FF2190F8733090F85420323004F0CDFE91 -:1008000060684FF00C0880F87D5054E720787F280E -:100810009ED094F90000277000906068FF2190F846 -:10082000733090F85420323004F0B7FE16E0002BFD -:100830007ED102F11A01FDF7C2FAA068FDF7DDFAD8 -:100840006168C96B4876DBE0FFE796F85600082838 -:1008500070D096F8531081426AD0D5E04FF0060868 -:1008600029E7054691F8510000280CBF4FF0010B15 -:100870004FF0000B4FF00008A06810F8092BD209C8 -:1008800007D0407900F0C000402808BF4FF0010AAF -:1008900001D04FF0000A91F84000032806D191F8EA -:1008A0003900002818BF91F8569001D191F8539063 -:1008B0004846FDF72CF80090D8B34846FCF75BFE9D -:1008C000002818BF4FF0010BBAF1000F37D0A06815 -:1008D000A14600F10901009800E0B6E0F8F762FED9 -:1008E0005FEA0008D9F8040090F8319018BF49F089 -:1008F0000209606890F84010032924D0F7F7AAFF96 -:10090000002DABD0F7F75DFD002808BFB8F1000F50 -:100910007DD020787F2808BFFFDF94F90000277082 -:1009200000906068494690F8733090F8542002E0D7 -:1009300066E004E068E0323004F02FFE8EE7606885 -:1009400090F83190D5E7A168C06BCA78837E9A424F -:100950001BD10A79C37E9A4217D14A79037F9A4202 -:1009600013D18A79437F9A420FD1CA79837F9A4201 -:100970000BD10A7AC37F9A4207D10978407EC1F32E -:100980008011814208BF012700D0002796F853004C -:10099000082806D096F85610884208BF4FF0010983 -:1009A00001D04FF00009B8F1000F05D1BBF1000FE5 -:1009B00004D0F7F706FD08B1012000E000204DB19A -:1009C00096F84210012903D021B957EA090101D054 -:1009D000012100E00021084216D0606890F8421022 -:1009E000012908BF002F0BD1C06B00F11A01A068CC -:1009F000FDF7E5F9A068FDF700FA6168C96B487674 -:100A00004FF00E0857E602E0F7F724FF26E760688C -:100A100090F84100032818BF02287FF41FAFBAF1F5 -:100A2000000F3FF41BAF20787F2808BFFFDF94F949 -:100A30000000277000906068FE2190F8733090F8F5 -:100A40005420323004F0A9FD08E791F8481000293D -:100A500018BF00283FF47EAE0BE0000074010020B8 -:100A600044110020B9F1070F7FF474AE00283FF461 -:100A700071AEFEF790FC80461DE60000D0F8001134 -:100A800049B1D0E941231A448B691A448A61D0E9FB -:100A90003F12D16003E0FE4AD0F8FC101162D0E9A9 -:100AA0003F1009B1086170470028FCD00021816126 -:100AB00070472DE9FF4F06460C46488883B040F248 -:100AC000E24148430190E08A002500FB01FA94F8D6 -:100AD0007C0090460D2822D00C2820D024281ED03F -:100AE00094F87D0024281AD000208346069818B177 -:100AF0000121204603F0C0F894F8641094F86500D2 -:100B0000009094F8F0200F464FF47A794AB1012A08 -:100B100061D0022A44D0032A5DD0FFDFB5E0012076 -:100B2000E3E7B8F1000F00D1FFDFD94814F8641FE4 -:100B3000243090F83400F0F7C4FC01902078F8F7E6 -:100B4000CFFB4D4600F2E730B0FBF5F1DFF8409304 -:100B5000D9F80C0001EB00082078F8F7C1FB01463A -:100B600014F86409022816D0012816D040F6340083 -:100B700008444AF2EF010844B0FBF5F10198D9F8B6 -:100B80001C20411A514402EB08000D18012084F882 -:100B9000F0002D1D78E02846EAE74FF4C860E7E74B -:100BA000DFF8EC92A8F10100D9F80810014300D158 -:100BB000FFDFB848B8F1000F016801EB0A0506D065 -:100BC000D9F8080000F22630A84200D9FFDF032040 -:100BD00084F8F00058E094F87C20019D242A05D088 -:100BE00094F87D30242B01D0252A3AD1B4F8702016 -:100BF000B4F81031D21A521C12B2002A31DB94F828 -:100C0000122172B3174694F8132102B110460090D6 -:100C1000022916D0012916D040F6340049F60852B0 -:100C20008118022F12D0012F12D040F63400104448 -:100C3000814210D9081A00F5FA70B0FBF9F00544AA -:100C40000FE04846EAE74FF4C860E7E74846EEE7BA -:100C50004FF4C860EBE7401A00F5FA70B0FBF9F00A -:100C60002D1AB8F1000F0FD0DFF82482D8F8080051 -:100C700018B9B8F8020000B1FFDFD8F8080000F298 -:100C80002630A84200D9FFDF05B9FFDF2946D4F896 -:100C9000F400F4F750FFC4F8F400B06000203070A6 -:100CA0004FF0010886F80480204603F040F8ABF1CD -:100CB0000101084202D186F8058005E094F8F000B1 -:100CC000012844D003207071606A3946009A01F00F -:100CD00042FBF060069830EA0B0035D029463046DA -:100CE000F0F7BAF987B2204603F021F8B8420FD8DE -:100CF000074686F8058005FB07F1D4F8F400F4F701 -:100D00001AFFB06029463046F0F7A6F9384487B29A -:100D10003946204602F0B0FFB068C4F8F400A06E77 -:100D2000002811D0B4F87000B4F89420801A01B2F1 -:100D3000002909DD34F86C0F0144491E91FBF0F1E4 -:100D400089B201FB0020208507B0BDE8F08F0220AA -:100D5000B9E72DE9F04106460C46012001F0DBFA27 -:100D6000C5B20B2001F0D7FAC0B2854200D0FFDF38 -:100D70000025082C7ED2DFE804F00461696965C6AD -:100D80008293304601F0DDFA0621F3F78FF8040074 -:100D900000D1FFDF304601F0D4FA2188884200D02C -:100DA000FFDF94F8F00000B9FFDF204602F00FFEED -:100DB000374E21460020B5607580F561FDF7E9FCEE -:100DC00000F19807606AB84217D994F86500F7F700 -:100DD0000BF9014694F864004FF47A72022828D087 -:100DE000012828D040F6340008444AF2473108442C -:100DF000B0FBF2F1606A0844C51B21460020356152 -:100E0000FDF7C7FC618840F2E24251439830081A6E -:100E1000A0F22630706194F8652094F86410606A3E -:100E200001F099FAA0F5CB70B061BDE8F041F5F79B -:100E300060BE1046D8E74FF4C860D5E7BDE8F04182 -:100E400002F02FBEBDE8F041F7F7D5BE304601F005 -:100E500078FA0621F3F72AF8040000D1FFDF3046C4 -:100E600001F06FFA2188884200D0FFDF01220021C3 -:100E7000204600E047E0BDE8F04101F089BAF7F70D -:100E800073FDF7F7B8FE02204FF0E02104E0000008 -:100E9000CC11002084010020C1F88002BDE8F0815F -:100EA000304601F04EFA0621F3F700F8040000D1B5 -:100EB000FFDF304601F045FA2188884200D0FFDF8D -:100EC00094F8F000042800D0FFDF84F8F05094F884 -:100ED000FA504FF6FF76202D00D3FFDFFA4820F8B6 -:100EE000156094F8FA00F5F720F900B9FFDF20202B -:100EF00084F8FA002046FFF7C1FDF4480078BDE809 -:100F0000F041E2F701B8FFDFC8E770B5EE4C00250D -:100F1000443C84F82850E07868B1E570FEF71EF98B -:100F20002078042803D0606AFFF7A8FD6562E748CF -:100F30000078E1F7E9FFBDE8704001F03ABA70B51A -:100F4000E14C0146443CE069F5F706FE6568A2788D -:100F500090FBF5F172B140F27122B5FBF2F292B260 -:100F6000A36B01FB02F6B34202D901FB123200E08F -:100F70000022A2634D43002800DAFFDF2946E06922 -:100F8000F4F7D9FDE06170BD2DE9F05FFEF736F9A9 -:100F90008246CD48683800F1240881684646D8F872 -:100FA0001800F4F7C8FD0146F069F5F7D5FD4FF0DC -:100FB0000009074686F835903C4640F28F254E469C -:100FC0001EE000BF0AEB06000079F7F70DF80146B6 -:100FD0004AF2B12001444FF47A70B1FBF0F008EB13 -:100FE0008602414692681044844207D3241A91F83D -:100FF0003500A4F28F24401C88F83500761CF6B228 -:1010000098F83600B042DDD8002C10DD98F8351085 -:10101000404608EB81018968A14208D24168C91B9A -:10102000B1F5247F00D30D466C4288F8359098F8CE -:101030003560C3460AEB060898F80400F6F7D4FFBB -:101040004AF2B12101444FF47A7AB1FBFAF298F8EE -:101050000410082909D0042909D0002013180429F4 -:101060000AD0082908D0252207E0082000E0022045 -:1010700000EB40002830F1E70F22521D4FF4A8701A -:10108000082914D0042915D0022916D04FF0080CD5 -:101090005FF0280012FB0C00184462190BEB86036A -:1010A00010449A68D84690420BD8791925E04FF041 -:1010B000400CEFE74FF0100CECE74FF0040C182059 -:1010C000E8E798F8352098F836604046B24210D2EA -:1010D000521C88F835203C1B986862198418084611 -:1010E000F6F782FF4AF2B1210144B1FBFAF001198F -:1010F00003E080F83590D8F80410D8F81C00BDE85B -:10110000F05FF4F718BD2DE9FE4F14460546FEF7D3 -:1011100075F8DFF8B4A10290AAF1440A50469AF893 -:1011200035604FF0000B0AEB86018968CAF83C1065 -:10113000F4B3044600780027042825D005283ED0C3 -:10114000FFDFA04639466069F4F7F5FC0746F5F77E -:101150000BF881463946D8F80440F5F7FDFC401EEF -:1011600090FBF4F0C14361433846F4F7E4FC0146D8 -:10117000C8F81C004846F5F7EFFC002800DDFFDF4B -:10118000012188F813108DE0D4F81490D4F804806D -:1011900001F07AF9070010D0387800B9FFDF7969DB -:1011A00078684A460844414601F05AF9074600E08B -:1011B0000BE04045C5D9FFDFC3E75F46C1E7606A82 -:1011C00001F004F940F6B837BBE7C1690AEB460005 -:1011D0000191408D10B35446DAF81400FFF7AFFECA -:1011E0006168E069F4F7A7FC074684F835B0019C14 -:1011F000D0462046DAF81410F5F7AEFC81463946A1 -:101200002046F5F7A9FCD8F804200146B9FBF2F016 -:10121000B1FBF2F1884242D0012041E0F4F7A4FF93 -:10122000FFF78DFEFFF7B0FE9AF83510DAF804905C -:101230000AEB81010746896800913946DAF81C00FB -:10124000F5F78AFC00248046484504DB98FBF9F456 -:1012500004FB09F41AE0002052469AF8351007E022 -:1012600002EB800304F28F249B68401C1C44C0B234 -:101270008142F5D851B10120F6F7B6FE4AF2B1210C -:1012800001444FF47A70B1FBF0F004440099A8EBEC -:1012900004000C1A00D5FFDFCAF83C40A7E7002085 -:1012A00088F813009AF802005446B8B13946E0694C -:1012B000F5F752FC0146A26B40F2712042438A428C -:1012C00006D2C4F83CB009E03412002080010020AE -:1012D000E06B511A884200D30846E063AF6085F89E -:1012E00000B001202871029F94F835003F1DC05DB9 -:1012F000F6F77AFE4AF23B5101444FF47A70B1FBA3 -:10130000F0F0E16BFE300844E8602078042808D152 -:1013100094F8350004EB4000408D0A2801D20320E8 -:1013200000E00220687104EB4600408DC0B1284601 -:101330006168EFF791FE82B20020761C0CE000BFDE -:1013400004EB4001B0424B8D13449BB24B8501D35B -:101350005B1C4B85401CC0B294F836108142EFD222 -:10136000A8686061A06194F8350004EB4001488DE5 -:10137000401C488594F83500C05D082803D0042837 -:1013800003D000210BE0082100E0022101EB410124 -:1013900028314FF4A872082804D0042802D002286B -:1013A00007D028220A44042805D0082803D0252184 -:1013B00002E01822F6E70F21491D08280CD0042866 -:1013C0000CD002280CD0082011FB0020E16B8842D1 -:1013D00008D20120BDE8FE8F4020F5E71020F3E79A -:1013E0000420F1E70020F5E770B5FE4C061D14F867 -:1013F000352F905DF6F7F8FD4FF47A7100F2E73083 -:10140000B0FBF1F0D4F8071045182078805DF6F7AE -:1014100073FE2178895D082903D0042903D00022B6 -:101420000BE0082200E0022202EB420228324FF4D5 -:10143000A873082904D0042902D0022907D0282340 -:101440001344042905D0082903D0252202E01823DB -:10145000F6E70F22521D08290AD004290AD00229D2 -:101460000AD0082112FB0131081A281A293070BD50 -:101470004021F7E71021F5E70421F3E72DE9FF41CB -:1014800007460C46012000F046FFC5B20B2000F0D5 -:1014900042FFC0B2854200D0FFDF20460126002572 -:1014A000D04C082869D2DFE800F004304646426894 -:1014B0006865667426746078002819D1FDF79EFE71 -:1014C000009594F835108DF808104188C90411D0A2 -:1014D000206C019003208DF80900C24824388560F3 -:1014E000C56125746846FDF768FB002800D0FFDF62 -:1014F000BDE8FF81FFF778FF0190E07C10B18DF827 -:101500000950EAE78DF80960E7E7607840B1207C90 -:1015100008B9FDF7F9FD6574BDE8FF41F4F72FBD8B -:10152000A674FDF739FC0028E2D0FFDFE0E7BDE854 -:10153000FF41F7F760BBFDF761FE4088C00407D0AC -:1015400001210320FDF75EFEA7480078E1F7DCFCEF -:10155000002239466846FFF7D6FD38B1694638465D -:1015600000F0EDFE0028C3D1FFDFC1E7E670FFF712 -:10157000CCFCBDE7BDE8FF41C7E4FFDFB8E7994910 -:1015800050B101228A704A6840F27123B2FBF3F233 -:1015900002EB0010886370470020887070472DE9C7 -:1015A000F05F894640F271218E4E48430025044683 -:1015B000706090462F46D0074AF2B12A4FF47A7BEA -:1015C0000FD0B9F800004843B0600120F6F70CFDD9 -:1015D00000EB0A01B1FBFBF0241AB7680125A4F265 -:1015E0008F245FEA087016D539F8151040F2712083 -:1015F000414306EB85080820C8F80810F6F7F4FC0C -:1016000000EB0A01B1FBFBF0241AD8F80800A4F2A1 -:101610008F2407446D1CA74219D9002D17D0391B00 -:10162000B1FBF5F0B268101AB1FBF5F205FB12122E -:10163000801AB060012008E0B1FBF5F306EB8002F0 -:101640009468E31A401CC0B29360A842F4D3BDE88A -:10165000F09F2DE9F041634C00262078042804D047 -:101660002078052801D00C2018E401206070607CEF -:10167000002538B1EFF3108010F0010F72B610D0D2 -:1016800001270FE0FDF7BAFD074694F82000F5F7B3 -:10169000B2F87888C00411D000210320FDF7B2FD14 -:1016A0000CE00027607C38B1A07C28B1FDF72CFD50 -:1016B0006574A574F4F763FC07B962B694F820006A -:1016C000F5F705FB94F8280030B184F8285020780D -:1016D000052800D0FFDF0C26657000F06AFE30465A -:1016E00012E4404810B5007808B1FFF7B2FF00F0EF -:1016F000D4FE3C4900202439086210BD10B53A4C94 -:1017000058B1012807D0FFDFA06841F66A0188427E -:1017100000D3FFDF10BD40F6C410A060F4E73249EB -:1017200008B508702F4900200870487081F828001B -:10173000C8700874487488742022486281F8202098 -:10174000243948704FF6FF7211F1680121F810201A -:10175000401CC0B22028F9D30020FFF7CFFFFFF7CD -:10176000C0FF1020ADF80000012269460420FFF7F9 -:1017700016FF08BD7FB51B4C05460E46207810B1FC -:101780000C2004B070BD95F8652095F86410686A67 -:1017900000F0C5FEC5F80401656295F8F00000B1DF -:1017A000FFDF104900202439C861052121706070D5 -:1017B00084F82800014604E004EB4102491C5085EE -:1017C000C9B294F836208A42F6D284F83500304601 -:1017D000FFF7D5FE0548F4F74DFC84F820002028DB -:1017E00007D105E0F0110020800100207D140200E7 -:1017F000FFDFF4F7B9FC606194F82010012268461D -:10180000FFF781FC00B9FFDF94F82000694600F083 -:1018100096FD00B9FFDF0020B3E7F94810B5007866 -:1018200008B1002010BD0620F2F7DAFA80F00100BE -:1018300010BDF8B5F24D0446287800B1FFDF002056 -:10184000009023780246DE0701466B4605D060888B -:10185000A188ADF80010012211462678760706D53A -:10186000E088248923F8114042F00802491C491EEF -:1018700085F836101946FFF792FE0020F8BD1FB517 -:1018800011B1112004B010BDDD4C217809B10C203C -:10189000F8E70022627004212170114605E000BFC4 -:1018A00004EB4103491C5A85C9B294F836308B4287 -:1018B000F6D284F83520FFF762FED248F4F7DAFB5F -:1018C00084F82000202800D1FFDF00F0DDFD10B1FA -:1018D000F4F74AFC05E0F4F747FC40F6B831F4F7BA -:1018E0002AF9606194F8201001226846FFF70BFC8A -:1018F00000B9FFDF94F82000694600F020FD00B930 -:10190000FFDF0020BEE770B5BD4C616A0160FFF7E4 -:10191000A0FE050002D1606AFFF7B0F80020606207 -:10192000284670BD7FB5B64C2178052901D00C2022 -:1019300027E7B3492439C860606A00B9FFDF606AED -:1019400090F8F00000B1FFDF606A90F8FA002028FC -:1019500000D0FFDFAC48F4F78DFB616A0546202814 -:1019600081F8FA000E8800D3FFDFA548443020F844 -:101970001560606A90F8FA00202800D1FFDF00238C -:1019800001226846616AFFF794F8606A694690F838 -:10199000FA0000F0D4FC00B9FFDF00206062F0E63E -:1019A000974924394870704710B540F2E24300FB74 -:1019B00003F4002000F0B3FD844201D9201A10BDC9 -:1019C000002010BD70B50D46064601460020FCF70C -:1019D000E0FE044696F86500F6F706FB014696F829 -:1019E00064004FF47A72022815D0012815D040F611 -:1019F000340008444AF247310844B0FBF2F17088E1 -:101A000040F271225043C1EB4000A0F22630A542C3 -:101A100006D2214605E01046EBE74FF4C860E8E740 -:101A20002946814204D2A54201D2204600E0284640 -:101A3000706270BD70B50546FDF7E0FB7049007837 -:101A400024398C689834072D30D2DFE805F004344F -:101A500034252C34340014214FF4A873042810D0FA -:101A60000822082809D02A2102280FD011FB0240A1 -:101A700000222823D118441819E0402211FB02400B -:101A8000F8E7102211FB02402E22F3E7042211FB9B -:101A9000024000221823EDE7282100F04BFC04440B -:101AA00004F5317403E004F5B07400E0FFDF54483E -:101AB000C06BA04201D9012070BD002070BD70B57F -:101AC0004F4C243C607870B1D4E904512846A26898 -:101AD000EFF7EDFA2061A84205D0A169401B084448 -:101AE000A061F5F706F82169A068884201D820783E -:101AF00008B1002070BD012070BD2DE9F04F0546F2 -:101B000085B016460F461C461846F6F7F5FA05EB63 -:101B100047014718204600F0F5FB4AF2C5714FF423 -:101B20007A7908444D46B0FBF5F0384400F160087E -:101B30003348761C24388068304404902046F6F7F9 -:101B4000DBFAA8EB0007204600F0DCFB0646204647 -:101B5000F6F74AFA301AB0FBF5F03A1A18252820A1 -:101B60004FF4C8764FF4BF774FF0020B082C30D0FB -:101B7000042C2BD00021022C2ED0082311F1280197 -:101B800003EB830C0CEB831319440A444FF0000A57 -:101B9000082C29D0042C22D00021022C29D0054663 -:101BA000082001F5B07100BF00EB0010284481420D -:101BB00032D2082C2AD0042C1ED00020022C28D08F -:101BC0000821283001EB0111084434E03946102384 -:101BD000D6E731464023D3E704231831D0E73D460A -:101BE00040F2EE311020DFE735464FF435614020FA -:101BF000DAE70420B431D7E738461021E2E70000E5 -:101C0000F01100207D140200530D020030464021E7 -:101C1000D8E704211830D5E7082C4FD0042C4AD03F -:101C20000021022C4DD0082311F12801C3EBC30081 -:101C300000EB4310084415182821204600F07AFBD9 -:101C400005EB4001082C42D0042C3DD00026022C8C -:101C50003FD0082016F1280600EB801006EB80002C -:101C60000E180120FA4D8DF804008DF800A08DF8B3 -:101C700005B0A86906F22A260499F3F75CFFCDE9BE -:101C800002062046F6F7B0F94AF23B510144B1FB97 -:101C9000F9F0301AFE38E8630298C5F84080A86170 -:101CA00095F82000694600F04AFB002800D1FFDFCC -:101CB00005B0BDE8F08F39461023B7E73146402321 -:101CC000B4E704231831B1E73E461020C4E74020B2 -:101CD000C2E704201836BFE72DE9FE4F06461C4632 -:101CE000174688464FF0010A1846F6F705FAD84D10 -:101CF000243DA9688A1907EB48011144471820467A -:101D000000F000FB4FF47A7BD84600F6FB00B0FBF6 -:101D1000F8F0384400F120092046F6F7EDF9A968FB -:101D20000246A9EB0100801B871A204600F0EAFA60 -:101D300005462046F6F758F9281AB0FBF8F03A1A8B -:101D4000182528204FF4C8774FF4BF78082C2DD0E1 -:101D5000042C28D00021022C2BD0082311F12801BB -:101D600003EB830C0CEB831319440A44082C28D092 -:101D7000042C21D00021022C28D00546082001F592 -:101D8000B07100BF00EB0010284481422AD2082C19 -:101D900022D0042C1DD00020022C20D00821283075 -:101DA00001EB01112CE041461023D9E739464023CD -:101DB000D6E704231831D3E7454640F2EE31102030 -:101DC000E0E73D464FF435614020DBE70420B431C5 -:101DD000D8E740461021E3E738464021E0E70421F8 -:101DE0001830DDE7082C48D0042C43D00020022C0A -:101DF00046D0082110F12800C1EBC10303EB4111CB -:101E0000084415182821204600F094FA05EB4001FB -:101E1000082C3BD0042C36D00027022C38D00820C8 -:101E200017F1280700EB801007EB80000C1804F571 -:101E300096740C98F6F7D8F84AF23B510144B1FB7E -:101E4000FBF0834DFE30A5F12407E96B06F1FE029D -:101E50000844B9680B191A44824224D93219114432 -:101E60000C1AFE342044B0F1807F37D2642C12D299 -:101E7000642011E040461021BEE738464021BBE710 -:101E800004211830B8E747461020CBE74020C9E7C7 -:101E900004201837C6E720460421F4F790FEE8B185 -:101EA000E86B2044E863E0F703FFB9682938314460 -:101EB0000844CDE9000995F835008DF808000220A6 -:101EC0008DF809006846FCF778FE00B1FFDFFCF7EB -:101ED00063FF00B1FFDF5046BDE8FE8F4FF0000A00 -:101EE000F9E71FB500F021FB594C607880B994F8F0 -:101EF000201000226846FFF706F938B194F8200058 -:101F0000694600F01CFA18B9FFDF01E00120E0701B -:101F1000F4F735F800206074A0741FBD2DE9F84F68 -:101F2000FDF76CF90646451CC07840090CD0012825 -:101F30000CD002280CD000202978824608064FF4E5 -:101F4000967407D41E2006E00120F5E70220F3E78F -:101F50000820F1E72046B5F80120C2F30C0212FB7D -:101F600000F7C80901D010B103E01E2401E0FFDF33 -:101F70000024F6F7D3F8A7EB00092878B77909EB26 -:101F80000408C0F3801010B120B1322504E04FF4F2 -:101F9000FA7501E0FFDF00250C2F00D3FFDF2D488D -:101FA0002D4A30F81700291801FB0821501CB1FBFD -:101FB000F0F5F6F76DF8F6F717F84FF47A7100F2CE -:101FC0007160B0FBF1F1A9EB0100471BA7F15900CB -:101FD000103FB0F5247F11D31D4E717829B9024608 -:101FE000534629462046FFF788FD00F09EFAF3F796 -:101FF000C6FF00207074B074BDE8F88F3078009090 -:102000005346224629463846FFF766FE0028F3D19C -:1020100001210220FDF7F6F8BDE8F84F61E710B5A1 -:102020000446012903D10A482438007830B104203D -:1020300084F8F000BDE81040F3F7A1BF00220121B1 -:10204000204600F0A5F934F8700F401C2080F1E71D -:10205000F0110020646302003F420F002DE9F041BF -:102060000746FDF7CBF8050000D1FFDF287810F018 -:102070000C0F01D0012100E00021F74C606A3030E4 -:10208000FCF7C7FA29783846EFF71BFAA4F12406C3 -:102090000146A069B26802446FB32878082803D0CB -:1020A000042803D000230BE0082300E0022303EB05 -:1020B000430328334FF4A877082804D0042802D01B -:1020C000022810D028273B4408280ED004280ED020 -:1020D00002280ED05FF00800C0EBC00707EB4010ED -:1020E0001844983009E01827EDE74020F4E7102065 -:1020F000F2E70420F0E74FF4FC701044471828780A -:102100003F1DF5F771FF014628784FF47A720228D7 -:102110001DD001281DD040F6340008444AF2EF01DA -:102120000844B0FBF2F03A1A606A40F2E241B0466D -:102130004788F0304F43316A81420DD03946206BD9 -:1021400000F08EF90646B84207D9FFDF05E01046D9 -:10215000E3E74FF4C860E0E70026C04880688642A5 -:1021600007D2616A40F271224888424306EB420678 -:1021700004E040F2E240B6FBF0F0616AC882606AB7 -:10218000297880F86410297880F865100521417558 -:10219000C08A6FF41C71484306EB400040F635419D -:1021A000C8F81C00B0EB410F00D3FFDFBDE8F081A1 -:1021B00010B5052937D2DFE801F00509030D31001C -:1021C000002100E00121BDE8104028E7032180F84C -:1021D000F01010BD0446408840F2E24148439F4958 -:1021E000091D0860D4F818010089E082D4F81801AC -:1021F00080796075D4F8180140896080D4F818019E -:102200008089A080D4F81801C089E0802046A16AA6 -:10221000FFF7D8FB022084F8F00010BD816ABDE80A -:102220001040FFF7CFBBFFDF10BD70B58A4C243CD8 -:102230000928A1683FD2DFE800F0050B0B15131544 -:1022400038380800BDE870404BE6BDE8704065E6F0 -:10225000022803D00020BDE87040FFE60120FAE725 -:10226000E16070BD032802D005281CD000E0E160C9 -:102270005FF0000600F059F9774D012085F828003D -:1022800085F83460686AA9690026C0F8F41080F8FF -:10229000F060E068FFF746FB00B1FFDFF3F76FFE89 -:1022A0006E74AE7470BD0126E4E76C480078BDE83A -:1022B0007040E0F729BEFFDF70BD674924394860F0 -:1022C000704770B5644D0446243DB1B14FF47A7641 -:1022D000012903D0022905D0FFDF70BD1846F5F7AC -:1022E000FCFE05E06888401C68801046F6F7F8FFA1 -:1022F00000F2E730B0FBF6F0201AA86070BD564837 -:1023000000787047082803D0042801D0F5F76CBE88 -:102310004EF628307047002804DB00F1E02090F8EA -:10232000000405E000F00F0000F1E02090F8140D2B -:102330004009704710F00C0000D008467047F4F7D1 -:102340003EB910B50446202800D3FFDF4248443090 -:1023500030F8140010BD70B505460C461046F5F770 -:1023600043FE4FF47A71022C0DD0012C0DD040F6B3 -:10237000340210444AF247321044B0FBF1F02844D2 -:1023800000F5CB7070BD0A46F3E74FF4C862F0E782 -:102390001FB513460A46044601466846FEF789FB08 -:1023A00094F8FA006946FFF7CAFF002800D1FFDF62 -:1023B0001FBD70B5284C0025257094F82000F3F758 -:1023C000B4FE00B9FFDF84F8205070BD2DE9F04164 -:1023D000050000D1FFDF204A0024243AD5F804612B -:1023E0002046631E116A08E08869B04203D3984210 -:1023F00001D203460C460846C9680029F4D104B945 -:1024000004460021C5F80041F035C4B1E068E5603C -:10241000E86000B105612E698846A96156B1B069CE -:1024200030B16F69B84200D2FFDFB069C01BA8614C -:10243000C6F81880084D5CB1207820B902E0E96048 -:102440001562E8E7FFDF6169606808442863ADE66C -:10245000C5F83080AAE60000F011002080010020BD -:1024600010B50C4601461046F4F776FB002806DA54 -:10247000211A491EB1FBF4F101FB040010BD90FBD1 -:10248000F4F101FB140010BD2E48016A002001E0A8 -:102490000846C9680029FBD170472DE9FE43294D44 -:1024A0000120287000264FF6FF7420E00621F1F786 -:1024B000FDFC070000D1FFDF97F8FA00F037F4F7D2 -:1024C00006FC07F80A6BA14617F8FA89B8F1200F45 -:1024D00000D3FFDF1B4A683222F8189097F8FA0001 -:1024E000F3F723FE00B9FFDF202087F8FA006946E2 -:1024F0000620F1F764FC50B1FFDF08E0029830B12C -:1025000090F8F01019B10088A042CFD104E06846DD -:10251000F1F733FC0028F1D02E70BDE8FE8310B532 -:10252000FFF719FF00F5C87010BD064800212430E0 -:1025300090F8352000EB4200418503480078E0F731 -:10254000E3BC0000CC11002080010020012804D051 -:10255000022805D0032808D105E0012907D004E0AE -:10256000022904D001E0042901D000207047012095 -:102570007047F748806890F8A21029B1B0F89E1013 -:10258000B0F8A020914215D290F8A61029B1B0F869 -:10259000A410B0F8A02091420CD2B0F89C20B0F862 -:1025A0009A108A4206D290F88020B0F898001AB1AA -:1025B000884203D3012070470628FBD200207047D1 -:1025C0002DE9F041E24D0746A86800F1700490F84B -:1025D000140130B9E27B002301212046EEF7D2FC42 -:1025E00010B1A08D401CA08501263D21AFB92878EF -:1025F000022808D001280AD06878C8B110F0140F5A -:1026000009D01E2039E0162037E026773EE0A86882 -:1026100090F8160131E0020701D56177F5E78107EF -:1026200001D02A2029E0800600D4FFDF232024E007 -:1026300094F8320028B1E08D411CE185218E88425A -:1026400013D294F8360028B1A08E411CA186218EA9 -:1026500088420AD2A18D608D814203D3AA6892F884 -:10266000142112B9228E914201D3222005E0217C4F -:1026700029B1218D814207D308206077C5E7208DDD -:10268000062801D33E20F8E7207FB0B10020207358 -:10269000607320740221A868FFF78AFDA86890F88B -:1026A000E410012904D1D0F81C110878401E0870EC -:1026B000E878BDE8F041E0F727BCA868BDE8F04144 -:1026C0000021FFF775BDA2490C28896881F8E40054 -:1026D00014D0132812D0182810D0002211280ED0A0 -:1026E00007280BD015280AD0012807D0002805D0CC -:1026F000022803D021F89E2F012008717047A1F80D -:10270000A420704710B5924CA1680A88A1F86021F6 -:1027100081F85E0191F8640001F046FBA16881F840 -:10272000620191F8650001F03FFBA16881F8630147 -:10273000012081F85C01002081F82E01E078BDE8DD -:102740001040E0F7E1BB70B5814C00231946A0684A -:1027500090F87C207030EEF715FC00283DD0A06882 -:1027600090F820110025C9B3A1690978B1BB90F890 -:102770007D00EEF7EFFB88BBA168B1F870000A2876 -:102780002DD905220831E069EBF72AFE10B3A068C5 -:10279000D0F81C11087858B10522491CE069EBF704 -:1027A0001FFE002819D1A068D0F81C01007840B99C -:1027B000A068E169D0F81C010A68C0F80120097915 -:1027C0004171A068D0F81C110878401C08700120E5 -:1027D000FFF779FFA06880F8205170BDFFE7A0687F -:1027E00090F8241111B190F82511C1B390F82E1171 -:1027F0000029F2D090F82F110029EED190F87D0039 -:10280000EEF7A8FB0028E8D1A06890F8640001F07A -:10281000CBFA0646A06890F8650001F0C5FA0546B7 -:10282000A06890F830113046FFF790FEA0B3A06882 -:1028300090F831112846FFF789FE68B3A268B2F814 -:10284000703092F86410B2F8320102F58872EEF737 -:1028500001FE20B3A168252081F87C00BDE7FFE7D9 -:1028600090F87D10242918D090F87C10242914D0D9 -:102870005FF0000300F5897200F59271FBF78EFEA0 -:10288000A16881F8245101F13000C28A21F8E62FB5 -:10289000408B4880142007E005E00123EAE7BDE80B -:1028A000704000202EE71620BDE870400BE710B501 -:1028B000F4F7FAFD0C2813D3254C0821A068D0F8B2 -:1028C00018011E30F4F7F4FD28B1A0680421D830B7 -:1028D000F4F7EEFD00B9FFDFBDE810400320F2E69B -:1028E00010BD10B51A4CA068D0F818110A78002A4B -:1028F0001FD04988028891421BD190F87C20002388 -:1029000019467030EEF73EFB002812D0A068D0F8D0 -:1029100018110978022907D003290BD0042919D0EE -:10292000052906D108200DE090F87D00EEF712FB96 -:1029300040B110BD90F8811039B190F8820000B913 -:10294000FFDF0A20BDE81040BDE6BDE81040AEE75D -:102950008C01002090F8AA008007EAD10C20FFF734 -:10296000B2FEA068002120F89E1F01210171017BA9 -:1029700041F00101017310BD70B5F74CA268556EAE -:10298000EEF702FDEBB2C1B200228B4203D0A36886 -:1029900083F8121102E0A16881F81221C5F3072122 -:1029A000C0F30720814203D0A16881F8130114E726 -:1029B000A06880F8132110E710B5E74C0421A06847 -:1029C000FFF7F6FBA06890F85A10012908D000F52F -:1029D0009E71FBF7ACFEE078BDE81040E0F794BADA -:1029E000022180F85A1010BD70B5DB4CA06890F839 -:1029F000E410FE2955D16178002952D190F87F204A -:102A0000002301217030EEF7BDFA002849D1A068FB -:102A100090F8141109B1022037E090F87C200023CF -:102A200019467030EEF7AEFA28B1A06890F896001B -:102A300008B1122029E0A068002590F87C20122A15 -:102A40001DD004DC032A23D0112A04D119E0182A4E -:102A50001AD0232A26D0002304217030EEF792FAF0 -:102A600000281ED1A06890F87D10192971D020DCB3 -:102A700001292AD0022935D0032932D120E00B20A8 -:102A800003E0BDE8704012E70620BDE870401AE69A -:102A900010F8E21F01710720FFF715FEA06880F80B -:102AA0007C509AE61820FFF70EFEA068A0F89E5012 -:102AB00093E61D2918D01E2916D0212966D149E098 -:102AC00010F8E11F4171072070E00C20FFF7FBFDBB -:102AD000A06820F8A45F817941F00101817100F8BC -:102AE000275C53E013202CE090F8252182BB90F85E -:102AF0002421B2B1242912D090F87C1024290ED0C0 -:102B00005FF0000300F5897200F59271FBF746FD56 -:102B1000A0681E2180F87D1080F8245103E0012375 -:102B2000F0E71E2932D1A068FBF797FDFFF744FFBD -:102B3000A16801F13000C28A21F8E62F408B48805D -:102B40001520FFF7C0FDA068A0F8A45080F87D50C4 -:102B50001CE02AE090F8971051B180F8125180F8EB -:102B600013511820FFF7AFFDA068A0F8A4500DE0A6 -:102B700090F82F1151B990F82E1139B1C16DD0F8DC -:102B80003001FFF7F9FE1820FFF79DFDA06890F8CF -:102B9000E400FE2885D1FFF7A4FEA06890F8E400C9 -:102BA000FE2885D1BDE87040CDE51120FFF78BFDF3 -:102BB000A068CBE7684A0129926819D0002302294E -:102BC0000FD003291ED010B301282BD0032807D122 -:102BD00092F87C00132803D0162801D0182804D1BD -:102BE000704792F8E4000028FAD0D2F8180117E0F4 -:102BF00092F8E4000128F3D0D2F81C110878401EA6 -:102C00000870704792F8E4000328EED17047D2F8BC -:102C10001801B2F870108288891A09B20029F5DB10 -:102C200003707047B2F87000B2F82211401A00B277 -:102C30000028F6DBD2F81C010178491E01707047AC -:102C400070B5044690F87C0000250C2810D00D28A3 -:102C50002ED1D4F81811B4F870008988401C88422D -:102C600026D1D4F864013C4E017811B3FFDF42E075 -:102C7000B4F87000B4F82211401C884218D1D4F87E -:102C80001C01D0F80110A160407920730321204677 -:102C9000EDF7F1FDD4F81C01007800B9FFDF012148 -:102CA000FE20FFF787FF84F87C50012084F8B200F3 -:102CB00093E52188C180D4F81801D4F864114089C3 -:102CC0000881D4F81801D4F8641180894881D4F8B7 -:102CD0001801D4F86411C0898881D4F864010571A1 -:102CE000D4F8641109200870D4F864112088488051 -:102CF000F078E0F709F902212046EDF7BCFD032149 -:102D00002046FFF755FAB068D0F81801007802287D -:102D100000D0FFDF0221FE20FFF74CFF84F87C503B -:102D20005BE52DE9F0410C4C00260327D4F808C0E0 -:102D3000012598B12069C0788CF8E20005FA00F00E -:102D4000C0F3C05000B9FFDFA06800F87C7F468464 -:102D500080F82650BDE8F0818C01002000239CF80B -:102D60007D2019460CF17000EEF70CF970B1607817 -:102D70000028EFD12069C178A06880F8E11080F8C0 -:102D80007D70A0F8A46080F8A650E3E76570E1E7E5 -:102D9000F0B5F74C002385B0A068194690F87D2067 -:102DA0007030EEF7EFF8012580B1A06890F87C0054 -:102DB00023280ED024280CD06846F6F785FB68B18E -:102DC000009801A9C0788DF8040008E0657005B08E -:102DD000F0BD607840F020006070F8E70021A06846 -:102DE00003AB162290F87C00EEF74FFB002648B1AB -:102DF000A0689DF80C20162180F80C2180F80D1198 -:102E0000192136E02069FBF722FB78B121690879A6 -:102E100000F00702A06880F85C20497901F0070102 -:102E200080F85D1090F82F310BBB03E00020FFF716 -:102E300078FFCCE790F82E31CBB900F164035F78CE -:102E4000974205D11A788A4202D180F897500EE055 -:102E500000F5AC71028821F8022990F85C200A7113 -:102E600090F85D0048710D70E078E0F74DF8A068CB -:102E7000212180F87D1080F8A650A0F8A460A6E774 -:102E8000F8B5BB4C00231946A06890F87D2070303F -:102E9000EEF778F840B32069FBF7BEFA48B3206933 -:102EA000FBF7B4FA07462069FBF7B4FA0646206937 -:102EB000FBF7AAFA05462069FBF7AAFA0146009734 -:102EC000A06833462A463030FBF79BFBA1680125FA -:102ED00091F87C001C2810D091F85A00012812D0DB -:102EE00091F8250178B90BE0607840F0010060703E -:102EF000F8BDBDE8F840002013E781F85A5002E021 -:102F000091F8240118B11E2081F87D000BE01D20EE -:102F100081F87D0001F5A57231F8300BFBF7FBFB62 -:102F2000E078DFF7F1FFA068002120F8A41F85708A -:102F3000F8BD10B58E4C00230921A06890F87C20C4 -:102F40007030EEF71FF848B16078002805D1A1680D -:102F500001F8960F087301F81A0C10BD012060707B -:102F600010BD7CB5824C00230721A06890F87C201E -:102F70007030EEF707F838B36078002826D169463C -:102F80002069FBF75FFA9DF80000002500F025019D -:102F9000A06880F8B0109DF8011001F0490180F898 -:102FA000B11080F8A250D0F81811008849888142E9 -:102FB00000D0FFDFA068D0F818110D70D0F86411B0 -:102FC0000A7822B1FFDF16E0012060707CBD30F886 -:102FD000E82BCA80C16F0D71C16F009A8A60019A97 -:102FE000CA60C26F0821117030F8E81CC06F4180C0 -:102FF000E078DFF789FFA06880F87C507CBD70B571 -:103000005B4C00231946A06890F87D207030EDF7E6 -:10301000B9FF012540B9A0680023082190F87C2061 -:103020007030EDF7AFFF10B36078002820D1A068B2 -:1030300090F8AA00800712D42069FBF7C9F9A168AB -:1030400081F8AB00206930F8052FA1F8AC2040884A -:10305000A1F8AE0011F8AA0F40F002000870A068B5 -:103060004FF0000690F8AA10C90702D011E0657071 -:103070009DE490F87D20002319467030EDF782FF23 -:1030800000B9FFDFA06880F87D5080F8A650A0F856 -:10309000A460A06890F87C10012906D180F87C60BB -:1030A00080F8A260E078DFF72FFFA168D1F818015F -:1030B000098842888A42DBD101780429D8D1067078 -:1030C000E078DFF721FFA06890F87C100029CFD1CD -:1030D00080F8A2606BE470B5254DA86890F87C106C -:1030E0001A2902D00220687061E469780029FBD1B6 -:1030F000002480F8A74080F8A240D0F8181100887A -:103100004988814200D0FFDFA868D0F818110C7000 -:10311000D0F864110A780AB1FFDF25E090F8A82002 -:1031200072B180F8A8400288CA80D0F864110C718E -:10313000D0F864210E2111700188D0F864010DE0EF -:1031400030F8E82BCA80C16F0C71C26F0121117277 -:10315000C26F0D21117030F8E81CC06F418000F083 -:10316000A1FEE878DFF7D0FEA86880F87C401EE476 -:103170008C01002070B5FA4CA16891F87C20162AC9 -:1031800001D0132A02D191F8A82012B10220607058 -:103190000DE46278002AFBD181F8E000002581F877 -:1031A000A75081F8A250D1F81801098840888842B8 -:1031B00000D0FFDFA068D0F818010078032800D005 -:1031C000FFDF0321FE20FFF7F5FCA068D0F86411B3 -:1031D0000A780AB1FFDF14E030F8E02BCA8010F85B -:1031E000081BC26F1171C16F0D72C26F0D2111707A -:1031F00030F8E81CC06F418000F054FEE078DFF743 -:1032000083FEA06880F87C504BE470B5D44C092153 -:103210000023A06890F87C207030EDF7B3FE002505 -:1032200018B12069007912281ED0A0680A21002355 -:1032300090F87C207030EDF7A5FE18B12069007978 -:10324000142814D02069007916281AD1A06890F8A3 -:103250007C101F2915D180F87C5080F8A250BDE861 -:1032600070401A20FFF74EBABDE8704061E6A068D2 -:1032700000F87C5F458480F82650BDE87040FFF779 -:103280009BBB0EE470B5B64C2079C00773D02069A3 -:1032900000230521C578A06890F87C207030EDF7F8 -:1032A00071FE98B1062D11D006DC022D0ED0042D32 -:1032B0000CD0052D06D109E00B2D07D00D2D05D022 -:1032C000112D03D0607840F008006070607800280D -:1032D00051D12069FAF7E0FF00287ED0206900254F -:1032E0000226C178891E162977D2DFE801F00B7615 -:1032F00034374722764D76254A457676763A5350CE -:103300006A6D7073A0680023012190F87F207030EF -:10331000EDF738FE08BB2069FBF722F8A16881F8B9 -:103320001601072081F87F0081F8A65081F8A2508D -:1033300056E0FFF76AFF53E0A06890F87C100F2971 -:1033400001D066704CE0617839B980F88150122163 -:1033500080F87C1044E000F0D0FD41E000F0ACFDCE -:103360003EE0FBF7A9F803283AD12069FBF7A8F85B -:10337000FFF700FF34E03BE00079F9E7FFF7ABFE31 -:103380002EE0FFF73CFE2BE0FFF7EBFD28E0FFF718 -:10339000D0FD25E0A0680023194690F87D2070300C -:1033A000EDF7F0FD012110B16078C8B901E061705E -:1033B00016E0A06820F8A45F817000F8276C0FE089 -:1033C0000BE0FFF75DFD0BE000F034FD08E0FFF7D8 -:1033D000DFFC05E000F0FAFC02E00020FFF7A1FCB2 -:1033E000A268F2E93001401C41F10001C2E900018C -:1033F0005EE42DE9F0415A4C2079800741D5607890 -:1034000000283ED1E06801270026C178204619290E -:10341000856805F170006FD2DFE801F04B3E0D6F5B -:10342000C1C1801C34C1556287C1C1C1C1BE8B9569 -:1034300098A4B0C1BA0095F87F2000230121EDF7D0 -:10344000A1FD00281DD1A068082180F87F1080F818 -:10345000A26090E0002395F87D201946EDF792FDDB -:1034600010B1A06880F8A660A0680023194690F803 -:103470007C207030EDF786FD002802D0A06880F82F -:10348000A26067E4002395F87C201946EDF77AFDE9 -:1034900000B9FFDF042008E0002395F87C201946DE -:1034A000EDF770FD00B9FFDF0C20A16881F87C000A -:1034B00050E4002395F87C201946EDF763FD00B930 -:1034C000FFDF0D20F1E7002395F87C201946EDF78A -:1034D00059FD00B9FFDFA0680F2180F8A77008E050 -:1034E00095F87C00122800D0FFDFA068112180F839 -:1034F000A87080F87C102DE451E0002395F87C2022 -:103500001946EDF73FFD20B9A06890F8A80000B972 -:10351000FFDFA068132180F8A770EAE795F87C0028 -:10352000182800D0FFDF1A20BFE7BDE8F04100F007 -:1035300063BD002395F87C201946EDF723FD00B903 -:10354000FFDF0520B1E785F8A66003E4002395F8C6 -:103550007C201946EDF716FD00B9FFDF1C20A4E71B -:103560008C010020002395F87D201946EDF70AFD17 -:1035700000B9FFDFA06880F8A66006E4002395F894 -:103580007C201946EDF7FEFC00B9FFDF1F208CE719 -:10359000BDE8F04100F0F8BC85F87D60D3E7FFDFBF -:1035A0006FE710B5F74C6078002837D120794007D5 -:1035B0000FD5A06890F87C00032800D1FFDFA06839 -:1035C00090F87F10072904D101212170002180F893 -:1035D0007F10FFF70EFF00F0B5FCFFF753FEA07859 -:1035E000000716D5A0680023052190F87C207030D4 -:1035F000EDF7C8FC50B108206070A068D0F86411E5 -:1036000008780D2800D10020087002E00020F9F7AA -:10361000F9FCA068BDE81040FFF712BB10BD2DE912 -:10362000F041D84C07464FF00005607808436070C1 -:10363000207981062046806802D5A0F8985004E0E1 -:10364000B0F89810491CA0F8981000F018FD012659 -:10365000F8B1A088000506D5A06890F8821011B1D5 -:10366000A0F88E5015E0A068B0F88E10491CA0F8A4 -:103670008E1000F0F3FCA068B0F88E10B0F8902027 -:10368000914206D3A0F88E5080F83A61E078DFF7D7 -:103690003BFC207910F0600F08D0A06890F88010F3 -:1036A00021B980F880600121FEF782FD1FB9FFF784 -:1036B00078FFFFF799F93846FEF782FFBDE8F04141 -:1036C000F5F711BFAF4A51789378194313D11146DA -:1036D0000128896808D01079400703D591F87F0048 -:1036E000072808D001207047B1F84C00098E8842A5 -:1036F00001D8FEF7E4B900207047A249C278896872 -:10370000012A06D05AB1182A08D1B1F81011FAF7D7 -:10371000BABEB1F822114172090A81727047D1F81C -:10372000181189884173090A8173704770B5954CE7 -:1037300005460E46A0882843A080A80703D5E807C1 -:1037400000D0FFDFE660E80700D02661A80719D5A2 -:10375000F078062802D00B2814D10BE0A06890F86E -:103760007C1018290ED10021E0E93011012100F868 -:103770003E1C07E0A06890F87C10122902D10021BD -:1037800080F88210280601D50820A07068050AD5A7 -:10379000A0688288B0F87010304600F07FFC304698 -:1037A000BDE87040A9E763E43EB505466846F5F715 -:1037B00065FE00B9FFDF222200210098EAF767FECC -:1037C00003210098FAF750FD0098017821F01001CC -:1037D00001702946FAF76DFD6A4C192D71D2DFE8A8 -:1037E00005F020180D3EC8C8C91266C8C9C959C815 -:1037F000C8C8C8BBC9C971718AC89300A1680098BC -:1038000091F8151103E0A168009891F8E610017194 -:10381000B0E0A068D0F81C110098491CFAF795FD9B -:10382000A8E0A1680098D1F8182192790271D1F826 -:10383000182112894271120A8271D1F81821528915 -:10384000C271120A0272D1F8182192894272120AC8 -:103850008272D1F81811C989FAF74EFD8AE0A06882 -:10386000D0F818110098091DFAF77CFDA068D0F86F -:10387000181100980C31FAF77FFDA068D0F81811E4 -:1038800000981E31FAF77EFDA1680098D831FAF74A -:1038900087FD6FE06269009811780171918841712C -:1038A000090A81715188C171090A017262E03649C1 -:1038B000D1E90001CDE9010101A90098FAF78AFDDB -:1038C00058E056E0A068B0F844100098FAF794FD6C -:1038D000A068B0F8E6100098FAF792FDA068B0F87A -:1038E00048100098FAF780FDA068B0F8E81000983A -:1038F000FAF77EFD3EE0A168009891F83021027150 -:1039000091F83111417135E0A06890F81301EDF79D -:1039100032FD01460098FAF7B2FDA06890F8120156 -:1039200000F03DFA70B1A06890F8640000F037FA3A -:1039300040B1A06890F8121190F86400814201D063 -:10394000002002E0A06890F81201EDF714FD014696 -:103950000098FAF790FD0DE0A06890F80D1100981E -:10396000FAF7A8FDA06890F80C110098FAF7A6FDE8 -:1039700000E0FFDFF5F795FD00B9FFDF0098FFF7E6 -:10398000BCFE3EBD8C0100207C63020010B5F94CEA -:10399000A06890F8121109B990F8641080F86410CA -:1039A00090F8131109B990F8651080F8651000209F -:1039B000FEF7A8FEA068FAF750FE002806D0A0681F -:1039C000BDE8104000F59E71FAF7B1BE10BDF8B524 -:1039D000E84E00250446B060B5807570B57035704E -:1039E0000088F5F748FDB0680088F5F76AFDB4F87F -:1039F000F800B168401C82B201F17000EDF75BF88D -:103A000000B1FFDF94F87D00242809D1B4F87010CC -:103A1000B4F81001081A00B2002801DB707830B148 -:103A200094F87C0024280AD0252808D015E0FFF758 -:103A3000ADFF84F87D50B16881F897500DE0B4F87F -:103A40007010B4F81001081A00B2002805DB707875 -:103A500018B9FFF79BFF84F87C50A4F8F850FEF7E4 -:103A600088FD00281CD1B06890F8E400FE2801D041 -:103A7000FFF79AFEC0480090C04BC14A2146284635 -:103A8000F9F7ECF9B0680023052190F87C2070303C -:103A9000EDF778FA002803D0BDE8F840F8F771BFD9 -:103AA000F8BD10B5FEF765FD20B10020BDE810405F -:103AB0000146B4E5BDE81040F9F77FBA70B50C4691 -:103AC000154606464FF4B47200212046EAF7DFFCA3 -:103AD000268005B9FFDF2868C4F818016868C4F8B3 -:103AE0001C01A868C4F8640182E4F0F7E9BA2DE982 -:103AF000F0410D4607460621F0F7D8F9041E3DD0E7 -:103B0000D4F864110026087858B14A8821888A427E -:103B100007D109280FD00E2819D00D2826D0082843 -:103B20003ED094F83A01D0B36E701020287084F81B -:103B30003A61AF809AE06E7009202870D4F8640171 -:103B4000416869608168A9608089A88133E008467E -:103B5000F0F7DDFA0746EFF78AFF70B96E700E20B6 -:103B60002870D4F864014068686011E00846F0F7F6 -:103B7000CEFA0746EFF77BFF08B1002081E46E70B4 -:103B80000D202870D4F8640141686960008928819B -:103B9000D4F8640106703846EFF763FF66E00EE084 -:103BA0006E7008202870D4F86401416869608168EB -:103BB000A960C068E860D4F86401067056E094F823 -:103BC0003C0198B16E70152028700AE084F83C61C1 -:103BD000D4F83E016860D4F84201A860D4F84601E8 -:103BE000E86094F83C010028F0D13FE094F84A01E5 -:103BF00058B16E701C20287084F84A610A2204F5BE -:103C0000A671281DEAF719FC30E094F8560140B17E -:103C10006E701D20287084F85661D4F858016860D1 -:103C200024E094F8340168B16E701A20287004E022 -:103C300084F83461D4F83601686094F834010028BF -:103C4000F6D113E094F85C01002897D06E7016202E -:103C5000287007E084F85C61D4F85E016860B4F80D -:103C60006201288194F85C010028F3D1012008E466 -:103C7000404A5061D170704770B50D4604464EE021 -:103C8000B4F8F800401CA4F8F800B4F89800401C00 -:103C9000A4F89800204600F0F2F9B8B1B4F88E000C -:103CA000401CA4F88E00204600F0D8F9B4F88E002D -:103CB000B4F89010884209D30020A4F88E000120A7 -:103CC00084F83A012B48C078DFF71EF994F8A20077 -:103CD00020B1B4F89E00401CA4F89E0094F8A60001 -:103CE00020B1B4F8A400401CA4F8A40094F8140176 -:103CF00040B994F87F200023012104F17000EDF712 -:103D000041F920B1B4F89C00401CA4F89C00204666 -:103D1000FEF796FFB4F87000401CA4F870006D1E0A -:103D2000ADB2ADD23FE5134AC2E90601704770B5A6 -:103D30000446B0F8980094F88010D1B1B4F89A1005 -:103D40000D1A2D1F94F8960040B194F87C200023A2 -:103D5000092104F17000EDF715F9B8B1B4F88E60DF -:103D6000204600F08CF980B1B4F89000801B001F51 -:103D70000CE007E08C0100201F360200C53602006F -:103D80002D370200C0F10205DCE72846A84200DA20 -:103D90000546002D01DC002005E5A8B203E510F082 -:103DA0000C0000D00120704710B5012808D002286F -:103DB00008D0042808D0082806D0FFDF204610BD10 -:103DC0000124FBE70224F9E70324F7E770B5CC4CA4 -:103DD000A06890F87C001F2804D0607840F00100B3 -:103DE0006070E0E42069FAF73CFBD8B12069012259 -:103DF0000179407901F0070161F30705294600F0D8 -:103E0000070060F30F21A06880F8A2200022A0F82C -:103E10009E20232200F87C2FD0F8B400BDE870402B -:103E2000FEF7AABD0120FEF77CFFBDE870401E2012 -:103E3000FEF768BCF8B5B24C00230A21A06890F8E0 -:103E40007C207030EDF79EF838B32069FAF7E4FA79 -:103E5000C8B12069FAF7DAFA07462069FAF7DAFA00 -:103E600006462069FAF7D0FA05462069FAF7D0FA33 -:103E700001460097A06833462A463030FAF7C1FB66 -:103E8000A068FAF7EAFBA168002081F8A20081F897 -:103E90007C00BDE8F840FEF78FBD607840F001007F -:103EA0006070F8BD964810B580680088F0F72FF96B -:103EB000BDE81040EFF7C6BD10B5914CA36893F86C -:103EC0007C00162802D00220607010BD60780028A7 -:103ED000FBD1D3F81801002200F11E010E30C833C7 -:103EE000ECF7C2FFA0680021C0E92E11012180F883 -:103EF0008110182180F87C1010BD10B5804CA0688E -:103F000090F87C10132902D00220607010BD6178F7 -:103F10000029FBD1D0F8181100884988814200D0CF -:103F2000FFDFA068D0F8181120692631FAF745FAAA -:103F3000A1682069DC31FAF748FAA168162081F8F7 -:103F40007C0010BD10B56E4C207900071BD5607841 -:103F5000002818D1A068002190F8E400FEF72AFE9E -:103F6000A06890F8E400FE2800D1FFDFA068FE21E1 -:103F700080F8E41090F87F10082904D10221217004 -:103F8000002180F87F1010BD70B55D4D2421002404 -:103F9000A86890F87D20212A05D090F87C20232A5B -:103FA00018D0FFDFA0E590F8122112B990F8132184 -:103FB0002AB180F87D10A86880F8A64094E500F842 -:103FC0007D4F847690F8B1000028F4D00020FEF7F1 -:103FD00099FBF0E790F8122112B990F813212AB159 -:103FE00080F87C10A86880F8A2407DE580F87C40CD -:103FF0000020FEF787FBF5E770B5414C0025A0686F -:10400000D0F8181103884A889A4219D109780429EE -:1040100016D190F87C20002319467030ECF7B2FFDF -:1040200000B9FFDFA06890F8AA10890703D4012126 -:1040300080F87C1004E080F8A250D0F818010570D8 -:10404000A0680023194690F87D207030ECF79AFFA5 -:10405000002802D0A06880F8A65045E5B0F890206E -:10406000B0F88E108A4201D3511A00E000218288F4 -:10407000521D8A4202D3012180F89610704710B574 -:1040800090F8821041B990F87C200023062170300E -:10409000ECF778FF002800D0012010BD70B5114466 -:1040A000174D891D8CB2C078A968012806D040B18F -:1040B000182805D191F8120138B109E0A1F8224180 -:1040C00012E5D1F8180184800EE591F8131191B131 -:1040D000FFF765FE80B1A86890F86400FFF75FFE07 -:1040E00050B1A86890F8121190F86420914203D062 -:1040F00090F8130100B90024A868A0F81041F3E477 -:104100008C01002070B58F4C0829207A6CD2DFE832 -:1041100001F004176464276B6B6458B1F4F7D3F8AB -:10412000F5F7FFF80020A072F4F7B4F9BDE870408D -:10413000F4F758BCF5F717F9BDE87040F1F71FBF69 -:10414000DEF7B6FDF5F752F8D4E90001F1F7F3FC1C -:104150002060A07A401CC0B2A072282824D370BD71 -:10416000A07A0025401EC6B2E0683044F4F700FD96 -:1041700010B9E1687F208855A07A272828BF01253B -:10418000DEF796FDA17A01EB4102C2EB81110844F2 -:104190002946F5F755F8A07A282809D2401CC0B264 -:1041A000A072282828BF70BDBDE87040F4F772B92E -:1041B000207A002818BF00F086F8F4F74BFBF4F7DC -:1041C000F7FBF5F7D0F80120E0725F480078DEF7E2 -:1041D0009BFEBDE87040F1F7D2BE002808BF70BD5D -:1041E000BDE8704000F06FB8FFDF70BD10B5554CF2 -:1041F000207A002804BF0C2010BD00202072E0723D -:10420000607AF2F7F8FA607AF2F761FD607AF1F716 -:104210008CFF00280CBF1F20002010BD002270B5AD -:10422000484C06460D46207A68B12272E272607AE6 -:10423000F2F7E1FA607AF2F74AFD607AF1F775FF7A -:10424000002808BFFFDF4048E560067070BD70B50C -:10425000050007D0A5F5E8503C494C3881429CBF89 -:10426000122070BD374CE068002804BF092070BDE3 -:10427000207A00281CBF0C2070BD3548F1F7FAFEEB -:104280006072202804BF1F2070BDF1F76DFF206011 -:104290001DB12946F1F74FFC2060012065602072B6 -:1042A00000F011F8002070BD2649CA7A002A04BF28 -:1042B000002070471E22027000224270CB684360CB -:1042C000CA7201207047F0B585B0F1F74DFF1D4D62 -:1042D0000746394668682C6800EB80004600204697 -:1042E000F2F73AFCB04206DB6868811B3846F1F70A -:1042F00022FC0446286040F2367621463846F2F722 -:104300002BFCB04204DA31463846F1F714FC04467F -:1043100000208DF8000040F6E210039004208DF894 -:10432000050001208DF8040068460294F2F7CAF8EF -:10433000687A6946F2F743F9002808BFFFDF05B045 -:10434000F0BD000074120020AC010020B5EB3C0071 -:10435000054102002DE9F0410C4612490D68114A51 -:10436000114908321160A0F12001312901D3012047 -:104370000CE0412810D040CC0C4F94E80E0007EB25 -:104380008000241F50F8807C3046B84720600548E4 -:10439000001D0560BDE8F081204601F0EBFCF5E76B -:1043A00006207047100502400100000184630200EE -:1043B00010B55548F2F7FAFF00B1FFDF5248401C34 -:1043C000F2F7F4FF002800D0FFDF10BD2DE9F14F18 -:1043D0004E4E82B0D6F800B001274B48F2F7EEFF00 -:1043E000DFF8248120B9002708F10100F2F7FCFF73 -:1043F000474C00254FF0030901206060C4F80051CC -:10440000C4F80451029931602060DFF808A11BE074 -:10441000DAF80000C00617D50E2000F068F8EFF3B8 -:10442000108010F0010072B600D001200090C4F896 -:104430000493D4F8000120B9D4F8040108B901F0BC -:10444000A3FC009800B962B6D4F8000118B9D4F8FA -:1044500004010028DCD0D4F804010028CCD137B105 -:10446000C6F800B008F10100F2F7A8FF11E008F16A -:104470000100F2F7A3FF0028B6D1C4F80893C4F8EE -:104480000451C4F800510E2000F031F81E48F2F734 -:10449000ABFF0020BDE8FE8F2DE9F0438DB00D4647 -:1044A000064600240DF110090DF1200818E000BFA8 -:1044B00004EB4407102255F827106846E9F7BDFFC2 -:1044C00005EB8707102248467968E9F7B6FF68468A -:1044D000FFF77CFF10224146B868E9F7AEFF641C85 -:1044E000B442E5DB0DB00020BDE8F0836EE70028A4 -:1044F00009DB00F01F02012191404009800000F11A -:10450000E020C0F880127047AD01002004E50040B3 -:1045100000E0004010ED00E0B54900200870704751 -:1045200070B5B44D01232B60B34B1C68002CFCD03C -:10453000002407E00E6806601E68002EFCD0001DF7 -:10454000091D641C9442F5D30020286018680028D7 -:10455000FCD070BD70B5A64E0446A84D3078022838 -:1045600000D0FFDFAC4200D3FFDF7169A44801290E -:1045700003D847F23052944201DD03224271491CB4 -:104580007161291BC1609E49707800F02EF90028E6 -:1045900000D1FFDF70BD70B5954C0D466178884243 -:1045A00000D0FFDF954E082D4BD2DFE805F04A041E -:1045B0001E2D4A4A4A382078022800D0FFDF032007 -:1045C0002070A078012801D020B108E0A06801F097 -:1045D00085F904E004F1080007C8FFF7A1FF0520F2 -:1045E0002070BDE87040F1F7CABCF1F7BDFD01468F -:1045F0006068F2F7B1FAB04202D2616902290BD3C6 -:104600000320F2F7FAFD12E0F1F7AEFD0146606813 -:10461000F2F7A2FAB042F3D2BDE870409AE72078F0 -:1046200002280AD0052806D0FFDF04202070BDE84C -:10463000704000F0D0B8022000E00320F2F7DDFD6A -:10464000F3E7FFDF70BD70B50546F1F78DFD684CEF -:1046500060602078012800D0FFDF694901200870E0 -:104660000020087104208D6048716448C8600220F1 -:104670002070607800F0B9F8002800D1FFDF70BD2D -:1046800010B55B4C207838B90220F2F7CCFD18B990 -:104690000320F2F7C8FD08B1112010BD5948F1F709 -:1046A000E9FC6070202804D00120207000206061A7 -:1046B00010BD032010BD2DE9F0471446054600EB60 -:1046C00084000E46A0F1040801F01BF907464FF0E4 -:1046D000805001694F4306EB8401091FB14201D2AA -:1046E000012100E0002189461CB10069B4EB900F64 -:1046F00002D90920BDE8F0872846DCF7A3FD90B970 -:10470000A84510D3BD4205D2B84503D245EA0600FC -:10471000800701D01020EDE73046DCF793FD10B99B -:10472000B9F1000F01D00F20E4E73748374900689E -:10473000884205D0224631462846FFF7F1FE1AE0AE -:10474000FFF79EFF0028D5D1294800218560C0E9E8 -:1047500003648170F2F7D3FD08B12D4801E04AF2FD -:10476000F87060434FF47A7100F2E730B0FBF1F07B -:104770001830FFF768FF0020BCE770B505464FF022 -:10478000805004696C432046DCF75CFD08B10F20C3 -:1047900070BD01F0B6F8A84201D8102070BD1A48CB -:1047A0001A490068884203D0204601F097F810E0CB -:1047B000FFF766FF0028F1D10D4801218460817068 -:1047C000F2F79DFD08B1134800E013481830FFF7D9 -:1047D0003AFF002070BD10B5054C6078F1F7A5FCDC -:1047E00000B9FFDF0020207010BDF1F7E8BE000027 -:1047F000B001002004E5014000E40140105C0C0021 -:1048000084120020974502005C000020BEBAFECA58 -:1048100050280500645E0100A85B01007E4909681C -:104820000160002070477C4908600020704701212A -:104830008A0720B1012804D042F204007047916732 -:1048400000E0D1670020704774490120086042F2FF -:104850000600704708B50423704A1907103230B1BA -:10486000C1F80433106840F0010010600BE01068DC -:1048700020F001001060C1F808330020C1F80801E1 -:10488000674800680090002008BD011F0B2909D867 -:10489000624910310A6822F01E0242EA40000860B4 -:1048A0000020704742F2050070470F2809D85B4985 -:1048B00010310A6822F4706242EA00200860002089 -:1048C000704742F205007047000100F18040C0F8D7 -:1048D000041900207047000100F18040C0F8081959 -:1048E00000207047000100F18040D0F80009086006 -:1048F00000207047012801D907207047494A52F823 -:10490000200002680A43026000207047012801D994 -:1049100007207047434A52F8200002688A43026029 -:1049200000207047012801D9072070473D4A52F8FE -:104930002000006808600020704702003A494FF0EC -:10494000000003D0012A01D0072070470A60704799 -:10495000020036494FF0000003D0012A01D00720A1 -:1049600070470A60704708B54FF40072510510B1E6 -:10497000C1F8042308E0C1F808230020C1F824018D -:1049800027481C3000680090002008BD08B5802230 -:10499000D10510B1C1F8042308E0C1F808230020B4 -:1049A000C1F81C011E48143000680090002008BDAA -:1049B00008B54FF48072910510B1C1F8042308E0E6 -:1049C000C1F808230020C1F82001154818300068FC -:1049D0000090002008BD10493831096801600020AE -:1049E000704770B54FF080450024C5F80841F2F7D4 -:1049F00092FC10B9F2F799FC28B1C5F82441C5F82A -:104A00001C41C5F820414FF0E020802180F80014BF -:104A10000121C0F8001170BD0004004000050040F5 -:104A2000080100404864020078050040800500400D -:104A30006249634B0A6863499A42096801D1C1F32C -:104A400010010160002070475C495D4B0A685D49B8 -:104A5000091D9A4201D1C0F3100008600020704780 -:104A60005649574B0A68574908319A4201D1C0F359 -:104A7000100008600020704730B5504B504D1C6846 -:104A800042F20803AC4202D0142802D203E01128FB -:104A900001D3184630BDC3004B481844C0F8101568 -:104AA000C0F81425002030BD4449454B0A6842F245 -:104AB00009019A4202D0062802D203E0042801D359 -:104AC00008467047404A012142F8301000207047E4 -:104AD0003A493B4B0A6842F209019A4202D0062841 -:104AE00002D203E0042801D308467047364A012168 -:104AF00002EBC00041600020704770B52F4A304E75 -:104B0000314C156842F2090304EB8002B54204D02F -:104B1000062804D2C2F8001807E0042801D318467A -:104B200070BDC1F31000C2F80008002070BD70B560 -:104B3000224A234E244C156842F2090304EB8002FA -:104B4000B54204D0062804D2D2F8000807E00428B1 -:104B500001D3184670BDD2F80008C0F310000860F9 -:104B6000002070BD174910B50831184808601120A1 -:104B7000154A002102EBC003C3F81015C3F8141541 -:104B8000401C1428F6D3002006E0042804D302EBCE -:104B90008003C3F8001807E002EB8003D3F8004855 -:104BA000C4F31004C3F80048401C0628EDD310BD20 -:104BB0000449064808310860704700005C00002086 -:104BC000BEBAFECA00F5014000F001400000FEFF41 -:104BD000814B1B6803B19847BFF34F8F7F48016833 -:104BE0007F4A01F4E06111430160BFF34F8F00BFC2 -:104BF000FDE710B5EFF3108010F0010F72B601D091 -:104C0000012400E0002400F0DDF850B1DCF7BFFB28 -:104C1000F1F755F8F2F793FAF2F7BEFF7149002069 -:104C2000086004B962B6002010BD2DE9F0410C46C1 -:104C30000546EFF3108010F0010F72B601D0012687 -:104C400000E0002600F0BEF820B106B962B60820E8 -:104C5000BDE8F08101F01EF9DCF79DFB0246002063 -:104C600001234709BF0007F1E02700F01F01D7F833 -:104C70000071CF40F9071BD0202803D222FA00F19F -:104C8000C90727D141B2002904DB01F1E02191F8E5 -:104C9000001405E001F00F0101F1E02191F8141D6D -:104CA0004909082916D203FA01F717F0EC0F11D0C1 -:104CB000401C6428D5D3F2F74DFF4B4A4B490020E6 -:104CC000F2F790FF47494A4808602046DCF7C1FAEE -:104CD00060B904E006B962B641F20100B8E73E48A7 -:104CE00004602DB12846DCF701FB18B1102428E040 -:104CF000404D19E02878022802D94FF4805420E072 -:104D000007240028687801D0D8B908E0C8B1202865 -:104D100017D8A878212814D8012812D001E0A87843 -:104D200078B9E8780B280CD8DCF735FB2946F2F780 -:104D3000ECF9F0F783FF00F017FE2846DCF7F4FAF1 -:104D4000044606B962B61CB1FFF753FF20467FE761 -:104D500000207DE710B5044600F034F800B10120D2 -:104D60002070002010BD244908600020704770B5F5 -:104D70000C4622490D682149214E08310E60102849 -:104D800007D011280CD012280FD0132811D00120E1 -:104D900013E0D4E90001FFF748FF354620600DE03D -:104DA000FFF727FF0025206008E02068FFF7D2FF0B -:104DB00003E0114920680860002020600F48001DB2 -:104DC000056070BD07480A490068884201D101208A -:104DD0007047002070470000C80100200CED00E083 -:104DE0000400FA055C0000204814002000000020A8 -:104DF000BEBAFECA50640200040000201005024042 -:104E0000010000017D49C0B20860704700B57C49CF -:104E1000012808BF03200CD0022808BF042008D0B6 -:104E2000042808BF062004D0082816BFFFDF05208D -:104E300000BD086000BD70B505460C46164610461C -:104E4000F3F7D2F8022C08BF4FF47A7105D0012C89 -:104E50000CBF4FF4C86140F6340144183046F3F7F4 -:104E60003CF9204449F6797108444FF47A71B0FB5B -:104E7000F1F0281A70BD70B505460C460846F4F7E7 -:104E80002FFA022C08BF40F24C4105D0012C0CBF78 -:104E900040F634014FF4AF5149F6CA62511A084442 -:104EA0004FF47A7100F2E140B0FBF1F0281A801E55 -:104EB00070BD70B5064615460C460846F4F710FA64 -:104EC000022D08BF4FF47A7105D0012D0CBF4FF4AD -:104ED000C86140F63401022C08BF40F24C4205D0B4 -:104EE000012C0CBF40F634024FF4AF52891A08442B -:104EF00049F6FC6108444FF47A71B0FBF1F0301AC6 -:104F000070BD70B504460E460846F3F76DF80546C9 -:104F10003046F3F7E2F828444AF2AB3108444FF444 -:104F20007A71B0FBF1F0201A801E70BD2DE9F041BE -:104F300007461E460D4614461046082A16BF04288A -:104F40004EF62830F3F750F807EB4701C1EBC711D5 -:104F500000EBC100022D08BF40F24C4105D0012DED -:104F60000CBF40F634014FF4AF5147182846F4F710 -:104F7000B7F9381A4FF47A7100F6B730B0FBF1F593 -:104F80002046F3F7B9F828443044401DBDE8F081CD -:104F900070B5054614460E460846F3F725F805EBAE -:104FA0004502C2EBC512C0EBC2053046F3F795F8D7 -:104FB0002D1A2046082C16BF04284EF62830F3F789 -:104FC00013F828444FF47A7100F6B730B0FBF1F5CE -:104FD0002046F3F791F82844401D70BD0949082880 -:104FE00018BF0428086803BF20F46C5040F4444004 -:104FF00040F0004020F00040086070470C15004071 -:105000001015004040170040F0B585B00C4605462D -:10501000F9F73EF907466E78204603A96A46EEF78F -:1050200002FD81198EB258B1012F02D0032005B0C4 -:10503000F0BD204604AA0399EEF717FC049D01E099 -:10504000022F0FD1ED1C042E0FD32888BDF80010BD -:10505000001D80B2884201D8864202D14FF0000084 -:10506000E5E702D34FF00200E1E74FF00100DEE791 -:10507000FA48C078FF2814BF0120002070472DE9AE -:10508000F041F74C0746160060680D4603D0F9F76B -:1050900069F8A0B121E0F9F765F8D8B96068F9F7C7 -:1050A00061F8D0B915F00C0F17D06068C17811F015 -:1050B0003F0F1CBF007910F0100F0ED00AE0022E37 -:1050C00008D0E6481FB1807DFF2806D002E0C078F6 -:1050D000FF2802D00120BDE8F0810020BDE8F0816A -:1050E0000A4601460120CAE710B5DC4C1D2200210A -:1050F000A01CE9F7CCF97F206077FF202074E070D6 -:10510000A075A08920F060002030A08100202070D0 -:1051100010BD70B5D249486001200870D248D1490D -:10512000002541600570CD4C1D222946A01CE9F7E1 -:10513000AEF97F206077FF202074E070A075A08911 -:1051400020F060002030A081257070BD2DE9F0476F -:10515000C24C06462078C24F4FF0010907F10808FB -:10516000002520B13878D0B998F80000B8B198F887 -:10517000000068B387F80090D8F804103C2239B3D7 -:105180007570301DE9F759F90520307086F80490E4 -:105190003878002818BF88F8005005D015E03D7019 -:1051A000A11C4FF48E72EAE71D220021A01CE9F732 -:1051B0006EF97F206077FF202074E070A075A089D1 -:1051C00020F060002030A08125700120BDE8F0872C -:1051D0000020BDE8F087A148007800280CBF01201E -:1051E000002070470A460146002048E710B510B17C -:1051F000022810D014E09A4C6068F8F7B3FF78B931 -:105200006068C17811F03F0F1CBF007910F0100FDB -:1052100006D1012010BD9148007B10F0080FF8D195 -:10522000002010BD2DE9FF4F81B08C4D8346DDE994 -:105230000F042978DDF838A09846164600291CBFCF -:1052400005B0BDE8F08F8849097800291CBF05B07A -:10525000BDE8F08FE872B4B1012E08BF012708D075 -:10526000022E08BF022704D0042E16BF082E0327E3 -:10527000FFDFEF7385F81E804FF00008784F8CB188 -:10528000022C1DD020E0012E08BF012708D0022EDD -:1052900008BF022704D0042E16BF082E0327FFDF05 -:1052A000AF73E7E77868F8F75DFF68B97868C178A9 -:1052B00011F03F0F1CBF007910F0100F04D110E067 -:1052C000287B10F0080F0CD14FF003017868F8F735 -:1052D000FDFD30B14178090929740088C0F30B0045 -:1052E0006882CDF800807868F8F73CFF0146012815 -:1052F000BDF8000005F102090CBF40F0010020F0EC -:105300000100ADF8000099F80A2012F0020F4ED10A -:10531000022918BF20F0020049D000BFADF80000FC -:1053200010F0020F04D0002908BF40F0080801D097 -:1053300020F00808ADF800807868C17811F03F0FC0 -:105340001CBF007910F0020F0CD0314622464FF0FE -:105350000100FFF794FE002804BF48F00400ADF8F8 -:10536000000006D099F80A00800860F38208ADF8C2 -:10537000008099F80A004109BDF8000061F3461069 -:10538000ADF8000080B20090BDF80000A8810421B3 -:105390007868F8F79BFD002804BFA88920F060001A -:1053A0000CD0B0F80100C004C00C03D007E040F0FE -:1053B0000200B3E7A88920F060004030A8815CB902 -:1053C00016F00C0F08D07868C17811F03F0F1CBFA1 -:1053D000007910F0100F0DD17868C17811F03F0FEF -:1053E00008D0017911F0400F04D00621F8F76EFDC6 -:1053F00000786877314622460020FFF740FE60BB08 -:105400007968C87810F03F0F3FD0087910F0010F8D -:105410003BD0504605F1040905F10308BAF1FF0F2E -:105420000DD04A464146F8F781FA002808BFFFDF51 -:1054300098F8000040F0020088F8000025E00846D7 -:10544000F8F7DBFC88F800007868F8F7ADFC07286F -:105450000CD249467868F8F7B2FC16E094120020A6 -:10546000CC010020D2120020D40100207868F8F787 -:105470009BFC072809D100217868F8F727FD01680F -:10548000C9F800108088A9F804003146224601209E -:10549000FFF7F5FD80BB7868C17811F03F0F2BD086 -:1054A000017911F0020F27D005F1170605F1160852 -:1054B000BBF1020F18BFBBF1030F08D0F8F774FC63 -:1054C00007280AD231467868F8F787FC12E002987C -:1054D000016831608088B0800CE07868F8F764FC7F -:1054E000072807D101217868F8F7F0FC01683160DE -:1054F0008088B08088F800B0002C04BF05B0BDE8FB -:10550000F08F7868F8F72EFE022804BF05B0BDE8DA -:10551000F08F05F11F047868F8F76DFEAB7AC3F1E0 -:10552000FF01884228BF084605D9A98921F06001FA -:1055300001F14001A981C2B203EB04017868F8F7D8 -:1055400062FEA97A0844A87205B0BDE8F08FB048A1 -:105550000178002918BF704701220270007B10F00B -:10556000080F14BF07200620FCF75FBEA848C17BC8 -:10557000002908BF70470122818921F06001403174 -:1055800081810378002B18BF7047027011F0080F5B -:1055900014BF07200620FCF748BE2DE9FF5F9C4F93 -:1055A000DDF838B0914638780E4600281CBF04B0AC -:1055B000BDE8F09FBC1C1D2200212046E8F767FFD4 -:1055C000944D4FF0010A84F800A06868F8F7ECFBEE -:1055D00018B3012826D0022829D0062818BFFFDFDB -:1055E0002AD000BF04F11D016868F8F726FC20727C -:1055F000484604F1020904F10108FF2821D04A4677 -:105600004146F8F793F9002808BFFFDF98F800003B -:1056100040F0020088F8000031E0608940F013009B -:105620006081DFE7608940F015006081E0E7608914 -:1056300040F010006081D5E7608940F01200608181 -:10564000D0E76868F8F7D9FB88F800006868F8F7D1 -:10565000ABFB072804D249466868F8F7B0FB0EE0B8 -:105660006868F8F7A1FB072809D100216868F8F7F6 -:105670002DFC0168C9F800108088A9F8040084F89E -:1056800009B084F80CA000206073FF20A073A17AF9 -:1056900011F0040F08BF20752AD004F1150804F199 -:1056A0001409022E18BF032E09D06868F8F77CFB96 -:1056B00007280CD241466868F8F78FFB16E000987F -:1056C0000168C8F800108088A8F804000EE0686837 -:1056D000F8F76AFB072809D101216868F8F7F6FB9B -:1056E0000168C8F800108088A8F8040089F80060F4 -:1056F0007F20E0760398207787F800A004B006208A -:10570000BDE8F05FFCF791BD2DE9FF5F424F814698 -:105710009A4638788B4600281CBF04B0BDE8F09F3D -:105720003B48017831B1007B10F0100F04BF04B08A -:10573000BDE8F09F1D227C6800212046E8F7A7FE07 -:1057400048464FF00108661C324D84F8008004F191 -:105750000209FF280BD04A463146F8F7E7F800283F -:1057600008BFFFDF307840F0020030701CE068684E -:10577000F8F743FB30706868F8F716FB072804D287 -:1057800049466868F8F71BFB0EE06868F8F70CFB01 -:10579000072809D100216868F8F798FB0168C9F863 -:1057A00000108088A9F8040004F11D016868F8F76A -:1057B00044FB207284F809A060896BF3000040F07C -:1057C0001A00608184F80C8000206073FF20A073B1 -:1057D00020757F20E0760298207787F8008004B05B -:1057E0000720BDE8F05FFCF720BD094A137C834227 -:1057F00005BF508A88420020012070470448007B82 -:10580000C0F3411002280CBF0120002070470000A7 -:1058100094120020CC010020D4010020C2790D2375 -:1058200041B342BB8188012904D94908818004BF62 -:10583000012282800168012918BF002930D0016847 -:105840006FEA0101C1EBC10202EB011281796FEA3B -:10585000010101EB8103C3EB811111444FEA914235 -:1058600001608188B2FBF1F301FB132181714FF0DC -:10587000010102E01AB14FF00001C1717047818847 -:10588000FF2908D24FF6FF7202EA41018180FF2909 -:1058900084BFFF2282800168012918BF0029CED170 -:1058A0000360CCE7817931B1491E11F0FF018171AC -:1058B0001CBF002070470120704710B50121C17145 -:1058C0008171818004460421F1F7E8FD002818BFAA -:1058D00010BD2068401C206010BD00000B4A022152 -:1058E00011600B490B68002BFCD0084B1B1D186086 -:1058F00008680028FCD00020106008680028FCD050 -:1059000070474FF0805040697047000004E5014047 -:1059100000E4014002000B464FF00000014620D099 -:10592000012A04D0022A04D0032A0DD103E0012069 -:1059300002E0022015E00320072B05D2DFE803F088 -:105940000406080A0C0E100007207047012108E029 -:10595000022106E0032104E0042102E0052100E029 -:105960000621F0F7A4BB0000E24805218170002168 -:10597000017041707047E0490A78012A05D0CA6871 -:105980001044C8604038F1F7B4B88A6810448860A1 -:10599000F8E7002819D00378D849D94A13B1012B68 -:1059A0000ED011E00379012B00D06BB943790BB114 -:1059B000012B09D18368643B8B4205D2C0680EE09D -:1059C0000379012B02D00BB10020704743790BB152 -:1059D000012BF9D1C368643B8B42F5D280689042B9 -:1059E000F2D801207047C44901220A70027972B1CD -:1059F00000220A71427962B104224A7182685232ED -:105A00008A60C068C860BB49022088707047032262 -:105A1000EFE70322F1E770B5B74D04460020287088 -:105A2000207988B100202871607978B10420B14EC6 -:105A30006871A168F068F0F77EF8A860E0685230FD -:105A4000E8600320B07070BD0120ECE70320EEE7B2 -:105A50002DE9F04105460226F0F777FF006800B116 -:105A6000FFDFA44C01273DB12878B8B1012805D04B -:105A7000022811D0032814D027710DE06868C828C7 -:105A800008D30421F1F79BF820B16868FFF773FF92 -:105A9000012603E0002601E000F014F93046BDE8DD -:105AA000F08120780028F7D16868FFF772FF00289E -:105AB000E2D06868017879B1A078042800D0FFDFCF -:105AC00001216868FFF7A7FF8B49E07800F003F930 -:105AD0000028E1D1FFDFDFE7FFF785FF6770DBE735 -:105AE0002DE9F041834C0F46E178884200D0FFDF7A -:105AF00000250126082F7DD2DFE807F0040B2828B7 -:105B00003D434F57A0780328C9D00228C7D0FFDFF4 -:105B1000C5E7A078032802D0022800D0FFDF0420C8 -:105B2000A07025712078B8BB0020FFF724FF7248D1 -:105B30000178012906D08068E06000F0EDF820616E -:105B4000002023E0E078F0F734FCF5E7A0780328A4 -:105B500002D0022800D0FFDF207880BB022F08D0BF -:105B60005FF00500F1F749FBA078032840D0A5704D -:105B700095E70420F6E7A078042800D0FFDF022094 -:105B800004E0A078042800D0FFDF0120A168884746 -:105B9000FFF75EFF054633E003E0A078042800D05D -:105BA000FFDFBDE8F04100F08DB8A078042804D0F4 -:105BB000617809B1022800D0FFDF207818B1BDE874 -:105BC000F04100F08AB8207920B10620F1F715FBEA -:105BD00025710DE0607840B14749E07800F07BF82E -:105BE00000B9FFDF65705AE704E00720F1F705FB15 -:105BF000A67054E7FFDF52E73DB1012D03D0FFDF70 -:105C0000022DF9D14BE70420C0E70320BEE770B5B1 -:105C1000050004D0374CA078052806D101E01020FB -:105C200070BD0820F1F7FFFA08B1112070BD3548AA -:105C3000F0F720FAE070202806D00121F1F7DCF817 -:105C40000020A560A07070BD032070BD294810B56C -:105C5000017809B1112010BD8178052906D00129EC -:105C600006D029B101210170002010BD0F2010BD08 -:105C700000F033F8F8E770B51E4C0546A07808B17F -:105C8000012809D155B12846FFF783FE40B1287895 -:105C900040B1A078012809D00F2070BD102070BD40 -:105CA000072070BD2846FFF79EFE03E0002128462E -:105CB000FFF7B1FE1049E07800F00DF800B9FFDF02 -:105CC000002070BD0B4810B5006900F01DF8BDE85C -:105CD0001040F0F754B9F0F772BC064810B5C07820 -:105CE000F0F723FA00B9FFDF0820F1F786FABDE8E4 -:105CF000104039E6DC010020B41300203D8601008D -:105D0000FF1FA107E15A02000C490A6848F202137A -:105D10009A4302430A607047084A116848F2021326 -:105D200001EA03009943116070470246044B1020BA -:105D30001344FC2B01D8116000207047C8060240B4 -:105D40000018FEBF1EF0040F0CBFEFF30880EFF346 -:105D50000980014A10470000FF7B010001B41EB416 -:105D600000B5F1F76DFC01B40198864601BC01B0A5 -:105D70001EBD00008269034981614FF0010010449B -:105D8000704700005D5D02000FF20C0000F10000A2 -:105D9000694641F8080C20BF70470000FEDF184933 -:105DA0000978F9B90420714608421BD10699154AB1 -:105DB000914217DC0699022914DB02394878DF2862 -:105DC00010D10878FE2807D0FF280BD14FF0010032 -:105DD0004FF000020C4B184741F201000099019A64 -:105DE000094B1847094B002B02D01B68DB6818478A -:105DF0004FF0FF3071464FF00002034B1847000090 -:105E000028ED00E000700200D14B020004000020E9 -:105E1000174818497047FFF7FBFFDBF7CFF900BDC4 -:105E2000154816490968884203D1154A13605B6812 -:105E3000184700BD20BFFDE70F4810490968884298 -:105E400010D1104B18684FF0FF318842F2D080F328 -:105E500008884FF02021884204DD0B4802680321A6 -:105E60000A4302600948804709488047FFDF000075 -:105E7000C8130020C81300200010000000000020FC -:105E8000040000200070020014090040B92F000037 -:105E9000215E0200F0B44046494652465B460FB4CC -:105EA00002A0013001B50648004700BF01BC86468C -:105EB0000FBC8046894692469B46F0BC7047000066 -:105EC0000911000004207146084202D0EFF3098155 -:105ED00001E0EFF30881886902380078102813DBAD -:105EE00020280FDB2C280BDB0A4A12680A4B9A4247 -:105EF00003D1602804DB094A10470220086070477C -:105F0000074A1047074A1047074A12682C3212689E -:105F1000104700005C000020BEBAFECA9B130000C0 -:105F2000554302006F4D0200040000200D4B0E4946 -:105F300008470E4B0C4908470D4B0B4908470D4BC2 -:105F4000094908470C4B084908470C4B06490847C4 -:105F50000B4B054908470B4B034908470A4B0249BD -:105F60000847000041BF000079C10000792D000002 -:105F7000F32B0000812B0000012E0000B71300005E -:105F80003F2900007D2F0000455D020000210160D7 -:105F90004160017270470A6802600B7903717047B3 -:105FA00089970000FF9800005B9A0000C59A0000E6 -:105FB000FF9A0000339B0000659B00009D9B000042 -:105FC0003D9C00007D980000859A0000331200007F -:105FD0000744000053440000B94400004745000056 -:105FE0006146000037470000694700004148000053 -:105FF000DB4800002F490000154A0000354A000028 -:10600000AD160000D1160000F11500004D1600007D -:10601000031700009717000003610000C36200002F -:10602000A1660000BB67000043680000C168000073 -:10603000256900004D6A00001D6B0000896B00009F -:10604000574A00005D4A0000674A0000CF4A00003E -:10605000FB4A0000B74C0000E14C0000194D000065 -:10606000834D00006D4E0000834E00007744000019 -:10607000974E0000B94E0000FF4E000033120000A2 -:10608000331200003312000033120000C12500005B -:1060900047260000632600007F2600000D28000030 -:1060A000A9260000B3260000F526000017270000EF -:1060B000F3270000352800003312000033120000DF -:1060C00097840000B7840000B9840000FD840000BC -:1060D0002B8500001B860000A7860000BB86000001 -:1060E000098700001F880000C1890000E98A0000BC -:1060F0003D740000018B00003312000033120000D9 -:10610000EBB700004DB90000A7B9000021BA0000AC -:10611000CDBA0000010000000000000010011001D5 -:106120003A0200001A020000020004050600000006 -:1061300007111102FFFFFFFF0000FFFFF3B3000094 -:10614000273D0000532100008774000001900000EB -:1061500000000000BF9200009B920000AD92000082 -:10616000000002000000000000020000000000002B -:1061700000010000000000004382000023820000B4 -:10618000918200002D250000EF2400000F25000063 -:10619000DBAA000007AB00000FAD0000FD590000B6 -:1061A000B182000000000000E18200007B250000B9 -:1061B000000000000000000000000000F1AB000043 -:1061C00000000000915A00000300000001555555E1 -:1061D000D6BE898E00006606660C661200000A03B1 -:1061E000AE055208000056044608360CC7FD0000F4 -:1061F0005BFF0000A1FB0000C3FD0000A7A8010099 -:106200009B040100AAAED7AB15412010000000008E -:10621000900A0000900A00007B5700007B570000A6 -:10622000E143000053B200000B7700006320000040 -:10623000BD3A020063BD0100BD570000BD5700001C -:1062400005440000E5B2000093770000D72000006D -:10625000EB3A020079BD0100700170014000380086 -:106260005C0024006801200200000300656C746279 -:10627000000000000000000000000000000000001E -:106280008700000000000000000000000000000087 -:10629000BE83605ADB0B376038A5F5AA9183886C02 -:1062A000010000007746010049550100000000018F -:1062B0000206030405000000070000FB349B5F801A -:1062C000000080001000000000000000000000003E -:1062D000060000000A000000320000007300000009 -:1062E000B4000000F401FA00960064004B00320094 -:1062F0001E0014000A000500020001000049000011 -:1063000000000000D7CF0100E9D1010025D1010034 -:10631000EBCF0100000000008FD40100000101025A -:10632000010202030C0802170D0101020909010113 -:1063300006020918180301010909030305000000FA -:10634000555555252627D6BE898E00002BFB01000A -:1063500003F7010049FA01003FF20100BB220200ED -:10636000B7FB0100F401FA00960064004B00320014 -:106370001E0014000A00050002000100254900006B -:1063800000000000314A0200494A0200614A02004E -:10639000794A0200A94A0200D14A0200FB4A0200DF -:1063A0002F4B02007B470200B7460200A1430200C8 -:1063B0002B5D0200AD730100BD730100E9730100A4 -:1063C000BB740100C3740100D57401002F480200A2 -:1063D000494802001D4802002748020055480200B3 -:1063E0008B480200AB480200C9480200D7480200AF -:1063F000E5480200F54802000D4902002549020067 -:106400003B4902005149020000000000DFBC0000CF -:1064100035BD00004BBD000015590200CD43020000 -:10642000994402000F5C02004D5C0200775C0200A0 -:106430009D710100FD760100674902008D4902004F -:10644000B1490200D74902001C0500402005004068 -:10645000001002007464020008000020E80100003F -:106460004411000098640200F0010020D8110000DF -:10647000A011000001181348140244200B440C061C -:106480004813770B1B2034041ABA0401A40213101A -:08649000327F0B744411C000BF -:00000001FF diff --git a/bin/setup-python-for-esp-debug.sh b/bin/setup-python-for-esp-debug.sh deleted file mode 100644 index edba43e72..000000000 --- a/bin/setup-python-for-esp-debug.sh +++ /dev/null @@ -1,12 +0,0 @@ -# shellcheck shell=bash -# (this minor script is actually shell agnostic, and is intended to be sourced rather than run in a subshell) - -# This is a little script you can source if you want to make ESP debugging work on a modern (24.04) ubuntu machine -# It assumes you have built and installed python 2.7 from source with: -# ./configure --enable-optimizations --enable-shared --enable-unicode=ucs4 -# sudo make clean -# make -# sudo make altinstall - -export LD_LIBRARY_PATH=$HOME/packages/python-2.7.18/ -export PYTHON_HOME=/usr/local/lib/python2.7/ diff --git a/bin/uf2conv.py b/bin/uf2conv.py index a1e241b7a..b619d14db 100755 --- a/bin/uf2conv.py +++ b/bin/uf2conv.py @@ -1,38 +1,39 @@ #!/usr/bin/env python3 -import argparse -import os -import os.path -import re +import sys import struct import subprocess -import sys +import re +import os +import os.path +import argparse -UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" -UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected -UF2_MAGIC_END = 0x0AB16F30 # Ditto + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto families = { - "SAMD21": 0x68ED2B88, - "SAML21": 0x1851780A, - "SAMD51": 0x55114460, - "NRF52": 0x1B57745F, - "STM32F0": 0x647824B6, - "STM32F1": 0x5EE21072, - "STM32F2": 0x5D1A0A2E, - "STM32F3": 0x6B846188, - "STM32F4": 0x57755A57, - "STM32F7": 0x53B80F00, - "STM32G0": 0x300F5633, - "STM32G4": 0x4C71240A, - "STM32H7": 0x6DB66082, - "STM32L0": 0x202E3A91, - "STM32L1": 0x1E1F432D, - "STM32L4": 0x00FF6919, - "STM32L5": 0x04240BDF, - "STM32WB": 0x70D16653, - "STM32WL": 0x21460FF0, - "ATMEGA32": 0x16573617, - "MIMXRT10XX": 0x4FB2D5BD, + 'SAMD21': 0x68ed2b88, + 'SAML21': 0x1851780a, + 'SAMD51': 0x55114460, + 'NRF52': 0x1b57745f, + 'STM32F0': 0x647824b6, + 'STM32F1': 0x5ee21072, + 'STM32F2': 0x5d1a0a2e, + 'STM32F3': 0x6b846188, + 'STM32F4': 0x57755a57, + 'STM32F7': 0x53b80f00, + 'STM32G0': 0x300f5633, + 'STM32G4': 0x4c71240a, + 'STM32H7': 0x6db66082, + 'STM32L0': 0x202e3a91, + 'STM32L1': 0x1e1f432d, + 'STM32L4': 0x00ff6919, + 'STM32L5': 0x04240bdf, + 'STM32WB': 0x70d16653, + 'STM32WL': 0x21460ff0, + 'ATMEGA32': 0x16573617, + 'MIMXRT10XX': 0x4FB2D5BD } INFO_FILE = "/INFO_UF2.TXT" @@ -45,17 +46,15 @@ def is_uf2(buf): w = struct.unpack(" 10 * 1024 * 1024: + if padding > 10*1024*1024: assert False, "More than 10M of padding needed at " + ptr if padding % 4 != 0: assert False, "Non-word padding size at " + ptr @@ -92,7 +91,6 @@ def convert_from_uf2(buf): curraddr = newaddr + datalen return outp - def convert_to_carray(file_content): outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {" for i in range(len(file_content)): @@ -102,7 +100,6 @@ def convert_to_carray(file_content): outp += "\n};\n" return outp - def convert_to_uf2(file_content): global familyid datapadding = b"" @@ -112,21 +109,13 @@ def convert_to_uf2(file_content): outp = b"" for blockno in range(numblocks): ptr = 256 * blockno - chunk = file_content[ptr : ptr + 256] + chunk = file_content[ptr:ptr + 256] flags = 0x0 if familyid: flags |= 0x2000 - hd = struct.pack( - b"= 3 and words[1] == "2" and words[2] == "FAT": drives.append(words[0]) else: @@ -238,6 +206,7 @@ def get_drives(): for d in os.listdir(rootpath): drives.append(os.path.join(rootpath, d)) + def has_info(d): try: return os.path.isfile(d + INFO_FILE) @@ -248,7 +217,7 @@ def get_drives(): def board_id(path): - with open(path + INFO_FILE, mode="r") as file: + with open(path + INFO_FILE, mode='r') as file: file_content = file.read() return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) @@ -266,61 +235,30 @@ def write_file(name, buf): def main(): global appstartaddr, familyid - def error(msg): print(msg) sys.exit(1) - - parser = argparse.ArgumentParser(description="Convert to UF2 or flash directly.") - parser.add_argument( - "input", - metavar="INPUT", - type=str, - nargs="?", - help="input file (HEX, BIN or UF2)", - ) - parser.add_argument( - "-b", - "--base", - dest="base", - type=str, - default="0x2000", - help="set base address of application for BIN format (default: 0x2000)", - ) - parser.add_argument( - "-o", - "--output", - metavar="FILE", - dest="output", - type=str, - help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible', - ) - parser.add_argument( - "-d", "--device", dest="device_path", help="select a device path to flash" - ) - parser.add_argument( - "-l", "--list", action="store_true", help="list connected devices" - ) - parser.add_argument( - "-c", "--convert", action="store_true", help="do not flash, just convert" - ) - parser.add_argument( - "-D", "--deploy", action="store_true", help="just flash, do not convert" - ) - parser.add_argument( - "-f", - "--family", - dest="family", - type=str, - default="0x0", - help="specify familyID - number or name (default: 0x0)", - ) - parser.add_argument( - "-C", - "--carray", - action="store_true", - help="convert binary file to a C array, not UF2", - ) + parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') + parser.add_argument('input', metavar='INPUT', type=str, nargs='?', + help='input file (HEX, BIN or UF2)') + parser.add_argument('-b' , '--base', dest='base', type=str, + default="0x2000", + help='set base address of application for BIN format (default: 0x2000)') + parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') + parser.add_argument('-d' , '--device', dest="device_path", + help='select a device path to flash') + parser.add_argument('-l' , '--list', action='store_true', + help='list connected devices') + parser.add_argument('-c' , '--convert', action='store_true', + help='do not flash, just convert') + parser.add_argument('-D' , '--deploy', action='store_true', + help='just flash, do not convert') + parser.add_argument('-f' , '--family', dest='family', type=str, + default="0x0", + help='specify familyID - number or name (default: 0x0)') + parser.add_argument('-C' , '--carray', action='store_true', + help='convert binary file to a C array, not UF2') args = parser.parse_args() appstartaddr = int(args.base, 0) @@ -330,17 +268,14 @@ def main(): try: familyid = int(args.family, 0) except ValueError: - error( - "Family ID needs to be a number or one of: " - + ", ".join(families.keys()) - ) + error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) if args.list: list_drives() else: if not args.input: error("Need input file") - with open(args.input, mode="rb") as f: + with open(args.input, mode='rb') as f: inpbuf = f.read() from_uf2 = is_uf2(inpbuf) ext = "uf2" @@ -356,10 +291,8 @@ def main(): ext = "h" else: outbuf = convert_to_uf2(inpbuf) - print( - "Converting to %s, output size: %d, start address: 0x%x" - % (ext, len(outbuf), appstartaddr) - ) + print("Converting to %s, output size: %d, start address: 0x%x" % + (ext, len(outbuf), appstartaddr)) if args.convert or ext != "uf2": drives = [] if args.output == None: diff --git a/boards/heltec_mesh_node_t114.json b/boards/heltec_mesh_node_t114.json deleted file mode 100644 index 5c97d8c75..000000000 --- a/boards/heltec_mesh_node_t114.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52840_s140_v6.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", - "f_cpu": "64000000L", - "hwids": [ - ["0x239A", "0x4405"], - ["0x239A", "0x0029"], - ["0x239A", "0x002A"] - ], - "usb_product": "HT-n5262", - "mcu": "nrf52840", - "variant": "heltec_mesh_node_t114", - "variants_dir": "variants", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" - }, - "bootloader": { - "settings_addr": "0xFF000" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52840_xxAA", - "onboard_tools": ["jlink"], - "svd_path": "nrf52840.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "Heltec nrf (Adafruit BSP)", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "speed": 115200, - "protocol": "nrfutil", - "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], - "use_1200bps_touch": true, - "require_upload_port": true, - "wait_for_upload_port": true - }, - "url": "FIXME", - "vendor": "Heltec" -} diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index f45b030d1..9db60e203 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -27,7 +27,7 @@ "jlink_device": "nRF52840_xxAA", "svd_path": "nrf52840.svd" }, - "frameworks": ["arduino", "freertos"], + "frameworks": ["arduino"], "name": "Seeed WIO WM1110", "upload": { "maximum_ram_size": 248832, diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index 37a9186ab..b4ab8db11 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -23,7 +23,7 @@ "sd_flags": "-DS140", "sd_name": "s140", "sd_version": "7.3.0", - "sd_fwid": "0x0123" + "sd_fwid": "0x00B6" }, "bootloader": { "settings_addr": "0xFF000" diff --git a/boards/wiscore_rak4631.json b/boards/wiscore_rak4631.json index c783f33a6..6dec3f7cb 100644 --- a/boards/wiscore_rak4631.json +++ b/boards/wiscore_rak4631.json @@ -35,7 +35,7 @@ "svd_path": "nrf52840.svd", "openocd_target": "nrf52840-mdk-rs" }, - "frameworks": ["arduino", "freertos"], + "frameworks": ["arduino"], "name": "WisCore RAK4631 Board", "upload": { "maximum_ram_size": 248832, diff --git a/platformio.ini b/platformio.ini index b3f677247..23ff53a10 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,10 +35,6 @@ default_envs = tbeam ;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_micro ;default_envs = heltec_capsule_sensor_v3 -;default_envs = heltec_vision_master_t190 -;default_envs = heltec_vision_master_e213 -;default_envs = heltec_vision_master_e290 -;default_envs = heltec_mesh_node_t114 extra_configs = arch/*/*.ini @@ -81,11 +77,10 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_DROPZONE=1 monitor_speed = 115200 -monitor_filters = direct lib_deps = jgromes/RadioLib@~6.6.0 - https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 + https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306 mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 @@ -155,4 +150,5 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + diff --git a/pyocd.yaml b/pyocd.yaml deleted file mode 100644 index 84bd9336b..000000000 --- a/pyocd.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# This is a config file to control pyocd ICE debugger probe options (only used for NRF52 targets with hardware debugging connections) -# for more info see FIXMEURL - -# console or telnet -semihost_console_type: telnet -enable_semihosting: True -telnet_port: 4444 diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index c2910007e..f45511cca 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -16,8 +16,6 @@ #include #ifdef RAK_4631 #include "Fusion/Fusion.h" -#include "graphics/Screen.h" -#include "graphics/ScreenFonts.h" #include #endif @@ -103,11 +101,7 @@ class AccelerometerThread : public concurrency::OSThread bmx160.getAllData(&magAccel, NULL, &gAccel); // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 12 * 1000 && millis() < 30 * 1000) { - if (!showingScreen) { - showingScreen = true; - screen->startAlert((FrameCallback)drawFrameCalibration); - } + if (millis() > 10 * 1000 && millis() < 30 * 1000) { if (magAccel.x > highestX) highestX = magAccel.x; if (magAccel.x < lowestX) @@ -120,9 +114,6 @@ class AccelerometerThread : public concurrency::OSThread highestZ = magAccel.z; if (magAccel.z < lowestZ) lowestZ = magAccel.z; - } else if (showingScreen && millis() >= 30 * 1000) { - showingScreen = false; - screen->endAlert(); } int highestRealX = highestX - (highestX + lowestX) / 2; @@ -264,34 +255,11 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LIS3DH lis; Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; - bool BMA_IRQ = false; #ifdef RAK_4631 - bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; - - static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - display->drawString(x, y, "Calibrating\nCompass"); - int16_t compassX = 0, compassY = 0; - uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); - - // coordinates for the center of the compass/circle - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + display->getHeight() / 2; - } else { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; - } - display->drawCircle(compassX, compassY, compassDiam / 2); - screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); - } #endif + bool BMA_IRQ = false; }; #endif \ No newline at end of file diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp index d9502e4f5..53faae997 100644 --- a/src/BluetoothCommon.cpp +++ b/src/BluetoothCommon.cpp @@ -10,8 +10,4 @@ const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, const uint8_t FROMRADIO_UUID_16[16u] = {0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0x93, 0x49, 0x9e, 0xe6, 0x55, 0x2c}; const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, - 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; -const uint8_t LEGACY_LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa, - 0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c}; -const uint8_t LOGRADIO_UUID_16[16u] = {0x47, 0x95, 0xDF, 0x8C, 0xDE, 0xE9, 0x44, 0x99, - 0x23, 0x44, 0xE6, 0x06, 0x49, 0x6E, 0x3D, 0x5A}; \ No newline at end of file + 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; \ No newline at end of file diff --git a/src/BluetoothCommon.h b/src/BluetoothCommon.h index 440d13844..586ffaa3c 100644 --- a/src/BluetoothCommon.h +++ b/src/BluetoothCommon.h @@ -11,12 +11,10 @@ #define TORADIO_UUID "f75c76d2-129e-4dad-a1dd-7866124401e7" #define FROMRADIO_UUID "2c55e69e-4993-11ed-b878-0242ac120002" #define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453" -#define LEGACY_LOGRADIO_UUID "6c6fd238-78fa-436b-aacf-15c5be1ef2e2" -#define LOGRADIO_UUID "5a3d6e49-06e6-4423-9944-e9de8cdf9547" // NRF52 wants these constants as byte arrays // Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER -extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[], LOGRADIO_UUID_16[]; +extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[]; /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level); diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index a81518f31..dc062fce4 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -187,9 +187,8 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_LONG_PRESSED: { LOG_BUTTON("Long press!\n"); powerFSM.trigger(EVENT_PRESS); - if (screen) { - screen->startAlert("Shutting down..."); - } + if (screen) + screen->startShutdownScreen(); playBeep(); break; } diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index d9ecd9fe3..9df402e77 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,7 +26,7 @@ SOFTWARE.*/ #include "DebugConfiguration.h" -#if HAS_NETWORKING +#if HAS_WIFI || HAS_ETHERNET Syslog::Syslog(UDP &client) { diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index ebe9da8d4..ca908197e 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -1,6 +1,5 @@ -#pragma once - -#include "configuration.h" +#ifndef SYSLOG_H +#define SYSLOG_H // DEBUG LED #ifndef LED_INVERTED @@ -26,14 +25,6 @@ #include "SerialConsole.h" -// If defined we will include support for ARM ICE "semihosting" for a virtual -// console over the JTAG port (to replace the normal serial port) -// Note: Normally this flag is passed into the gcc commandline by platformio.ini. -// for an example see env:rak4631_dap. -// #ifndef USE_SEMIHOSTING -// #define USE_SEMIHOSTING -// #endif - #define DEBUG_PORT (*console) // Serial debug port #ifdef USE_SEGGER @@ -126,7 +117,7 @@ #include #endif // HAS_WIFI -#if HAS_NETWORKING +#if HAS_WIFI || HAS_ETHERNET class Syslog { @@ -161,4 +152,6 @@ class Syslog bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0))); }; -#endif // HAS_ETHERNET || HAS_WIFI \ No newline at end of file +#endif // HAS_ETHERNET || HAS_WIFI + +#endif // SYSLOG_H \ No newline at end of file diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 7d3788c4d..96aad1a9a 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -84,58 +84,6 @@ bool renameFile(const char *pathFrom, const char *pathTo) #endif } -#include - -/** - * @brief Get the list of files in a directory. - * - * This function returns a list of files in a directory. The list includes the full path of each file. - * - * @param dirname The name of the directory. - * @param levels The number of levels of subdirectories to list. - * @return A vector of strings containing the full path of each file in the directory. - */ -std::vector getFiles(const char *dirname, uint8_t levels) -{ - std::vector filenames = {}; -#ifdef FSCom - File root = FSCom.open(dirname, FILE_O_READ); - if (!root) - return filenames; - if (!root.isDirectory()) - return filenames; - - File file = root.openNextFile(); - while (file) { - if (file.isDirectory() && !String(file.name()).endsWith(".")) { - if (levels) { -#ifdef ARCH_ESP32 - std::vector subDirFilenames = getFiles(file.path(), levels - 1); -#else - std::vector subDirFilenames = getFiles(file.name(), levels - 1); -#endif - filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end()); - file.close(); - } - } else { - meshtastic_FileInfo fileInfo = {"", file.size()}; -#ifdef ARCH_ESP32 - strcpy(fileInfo.file_name, file.path()); -#else - strcpy(fileInfo.file_name, file.name()); -#endif - if (!String(fileInfo.file_name).endsWith(".")) { - filenames.push_back(fileInfo); - } - file.close(); - } - file = root.openNextFile(); - } - root.close(); -#endif - return filenames; -} - /** * Lists the contents of a directory. * diff --git a/src/FSCommon.h b/src/FSCommon.h index 8fbabd952..ef1d3e4c1 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -1,7 +1,6 @@ #pragma once #include "configuration.h" -#include // Cross platform filesystem API @@ -50,7 +49,6 @@ using namespace Adafruit_LittleFS_Namespace; void fsInit(); bool copyFile(const char *from, const char *to); bool renameFile(const char *pathFrom, const char *pathTo); -std::vector getFiles(const char *dirname, uint8_t levels); void listDir(const char *dirname, uint8_t levels, bool del); void rmDir(const char *dirname); void setupSDCard(); \ No newline at end of file diff --git a/src/GPSStatus.h b/src/GPSStatus.h index c2ab16c86..1245d5e5d 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -124,7 +124,7 @@ class GPSStatus : public Status if (isDirty) { if (hasLock) { // In debug logs, identify position by @timestamp:stage (stage 3 = notify) - LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d\n", p.timestamp, + LOG_DEBUG("New GPS pos@%x:3 lat=%f, lon=%f, alt=%d, pdop=%.2f, track=%.2f, speed=%.2f, sats=%d\n", p.timestamp, p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5, p.ground_speed * 1e-2, p.sats_in_view); } else { diff --git a/src/Power.cpp b/src/Power.cpp index 19c5c9937..18a527cee 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -27,7 +27,7 @@ #if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #include "target_specific.h" -#if HAS_WIFI +#if !MESTASTIC_EXCLUDE_WIFI #include #endif #endif @@ -232,20 +232,12 @@ class AnalogBatteryLevel : public HasBatteryLevel raw = espAdcRead(); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled *= operativeAdcMultiplier; -#else // block for all other platforms -#ifdef ADC_CTRL // enable adc voltage divider when we need to read - pinMode(ADC_CTRL, OUTPUT); - digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); - delay(10); -#endif +#else // block for all other platforms for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) { raw += analogRead(BATTERY_PIN); } raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; -#ifdef ADC_CTRL // disable adc voltage divider when we need to read - digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); -#endif #endif if (!initial_read_done) { @@ -449,11 +441,6 @@ class AnalogBatteryLevel : public HasBatteryLevel if (!ina260Sensor.isInitialized()) return ina260Sensor.runOnce() > 0; return ina260Sensor.isRunning(); - } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first == - config.power.device_battery_ina_address) { - if (!ina3221Sensor.isInitialized()) - return ina3221Sensor.runOnce() > 0; - return ina3221Sensor.isRunning(); } return false; } diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 72e00810b..a7bc18f1a 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -11,7 +11,6 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" -#include "PowerMon.h" #include "configuration.h" #include "graphics/Screen.h" #include "main.h" @@ -50,7 +49,6 @@ static bool isPowered() static void sdsEnter() { LOG_DEBUG("Enter state: SDS\n"); - powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false); } @@ -70,7 +68,6 @@ static uint32_t secsSlept; static void lsEnter() { LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs); - powerMon->clearState(meshtastic_PowerMon_State_Screen_On); screen->setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -90,10 +87,8 @@ static void lsIdle() // Briefly come out of sleep long enough to blink the led once every few seconds uint32_t sleepTime = SLEEP_TIME; - powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); setLed(false); // Never leave led on while in light sleep esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); - powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); switch (wakeCause2) { case ESP_SLEEP_WAKEUP_TIMER: @@ -149,7 +144,6 @@ static void lsExit() static void nbEnter() { LOG_DEBUG("Enter state: NB\n"); - powerMon->clearState(meshtastic_PowerMon_State_BT_On); screen->setOn(false); #ifdef ARCH_ESP32 // Only ESP32 should turn off bluetooth @@ -161,8 +155,6 @@ static void nbEnter() static void darkEnter() { - powerMon->clearState(meshtastic_PowerMon_State_BT_On); - powerMon->clearState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(true); screen->setOn(false); } @@ -170,8 +162,6 @@ static void darkEnter() static void serialEnter() { LOG_DEBUG("Enter state: SERIAL\n"); - powerMon->clearState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(false); screen->setOn(true); screen->print("Serial connected\n"); @@ -180,7 +170,6 @@ static void serialEnter() static void serialExit() { // Turn bluetooth back on when we leave serial stream API - powerMon->setState(meshtastic_PowerMon_State_BT_On); setBluetoothEnable(true); screen->print("Serial disconnected\n"); } @@ -193,8 +182,6 @@ static void powerEnter() LOG_INFO("Loss of power in Powered\n"); powerFSM.trigger(EVENT_POWER_DISCONNECTED); } else { - powerMon->setState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); // within enter() the function getState() returns the state we came from @@ -218,8 +205,6 @@ static void powerIdle() static void powerExit() { - powerMon->setState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); @@ -231,8 +216,6 @@ static void powerExit() static void onEnter() { LOG_DEBUG("Enter state: ON\n"); - powerMon->setState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); } diff --git a/src/PowerMon.cpp b/src/PowerMon.cpp deleted file mode 100644 index 3d28715e0..000000000 --- a/src/PowerMon.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "PowerMon.h" -#include "NodeDB.h" - -// Use the 'live' config flag to figure out if we should be showing this message -static bool is_power_enabled(uint64_t m) -{ - return (m & config.power.powermon_enables) ? true : false; -} - -void PowerMon::setState(_meshtastic_PowerMon_State state, const char *reason) -{ -#ifdef USE_POWERMON - auto oldstates = states; - states |= state; - if (oldstates != states && is_power_enabled(state)) { - emitLog(reason); - } -#endif -} - -void PowerMon::clearState(_meshtastic_PowerMon_State state, const char *reason) -{ -#ifdef USE_POWERMON - auto oldstates = states; - states &= ~state; - if (oldstates != states && is_power_enabled(state)) { - emitLog(reason); - } -#endif -} - -void PowerMon::emitLog(const char *reason) -{ -#ifdef USE_POWERMON - // The nrf52 printf doesn't understand 64 bit ints, so if we ever reach that point this function will need to change. - LOG_INFO("S:PM:0x%08lx,%s\n", (uint32_t)states, reason); -#endif -} - -PowerMon *powerMon; - -void powerMonInit() -{ - powerMon = new PowerMon(); -} \ No newline at end of file diff --git a/src/PowerMon.h b/src/PowerMon.h deleted file mode 100644 index e9f5dbd59..000000000 --- a/src/PowerMon.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "configuration.h" - -#include "meshtastic/powermon.pb.h" - -#ifndef MESHTASTIC_EXCLUDE_POWERMON -#define USE_POWERMON // FIXME turn this only for certain builds -#endif - -/** - * The singleton class for monitoring power consumption of device - * subsystems/modes. - * - * For more information see the PowerMon docs. - */ -class PowerMon -{ - uint64_t states = 0UL; - - public: - PowerMon() {} - - // Mark entry/exit of a power consuming state - void setState(_meshtastic_PowerMon_State state, const char *reason = ""); - void clearState(_meshtastic_PowerMon_State state, const char *reason = ""); - - private: - // Emit the coded log message - void emitLog(const char *reason); -}; - -extern PowerMon *powerMon; - -void powerMonInit(); \ No newline at end of file diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 9c3dcdc98..e09e5fe30 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -3,8 +3,6 @@ #include "RTC.h" #include "concurrency/OSThread.h" #include "configuration.h" -#include "main.h" -#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -16,7 +14,12 @@ #include "platform/portduino/PortduinoGlue.h" #endif -#if HAS_NETWORKING +/** + * A printer that doesn't go anywhere + */ +NoopPrint noopPrint; + +#if HAS_WIFI || HAS_ETHERNET extern Syslog syslog; #endif void RedirectablePrint::rpInit() @@ -35,7 +38,7 @@ void RedirectablePrint::setDestination(Print *_dest) size_t RedirectablePrint::write(uint8_t c) { // Always send the characters to our segger JTAG debugger -#ifdef USE_SEGGER +#ifdef SEGGER_STDOUT_CH SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif @@ -46,7 +49,7 @@ size_t RedirectablePrint::write(uint8_t c) // serial port said (which could be zero) } -size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) +size_t RedirectablePrint::vprintf(const char *format, va_list arg) { va_list copy; static char printBuf[160]; @@ -62,200 +65,25 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l len = sizeof(printBuf) - 1; printBuf[sizeof(printBuf) - 2] = '\n'; } - for (size_t f = 0; f < len; f++) { - if (!std::isprint(static_cast(printBuf[f])) && printBuf[f] != '\n') - printBuf[f] = '#'; - } - if (logLevel != nullptr) { - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - Print::write("\u001b[34m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - Print::write("\u001b[32m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - Print::write("\u001b[33m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) - Print::write("\u001b[31m", 6); - } + len = Print::write(printBuf, len); - Print::write("\u001b[0m", 5); return len; } -void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, va_list arg) -{ - size_t r = 0; - - // Cope with 0 len format strings, but look for new line terminator - bool hasNewline = *format && format[strlen(format) - 1] == '\n'; - - // If we are the first message on a report, include the header - if (!isContinuationMessage) { - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - Print::write("\u001b[34m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - Print::write("\u001b[32m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - Print::write("\u001b[33m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) - Print::write("\u001b[31m", 6); - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile - if (rtc_sec > 0) { - long hms = rtc_sec % SEC_PER_DAY; - // hms += tz.tz_dsttime * SEC_PER_HOUR; - // hms -= tz.tz_minuteswest * SEC_PER_MIN; - // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) - hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; - - // Tear apart hms into h:m:s - int hour = hms / SEC_PER_HOUR; - int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; - int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN -#ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); -#else - printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); -#endif - } else -#ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); -#else - printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); -#endif - - auto thread = concurrency::OSThread::currentThread; - if (thread) { - print("["); - // printf("%p ", thread); - // assert(thread->ThreadName.length()); - print(thread->ThreadName); - print("] "); - } - } - r += vprintf(logLevel, format, arg); - - isContinuationMessage = !hasNewline; -} - -void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg) -{ -#if HAS_NETWORKING && !defined(ARCH_PORTDUINO) - // if syslog is in use, collect the log messages and send them to syslog - if (syslog.isEnabled()) { - int ll = 0; - switch (logLevel[0]) { - case 'D': - ll = SYSLOG_DEBUG; - break; - case 'I': - ll = SYSLOG_INFO; - break; - case 'W': - ll = SYSLOG_WARN; - break; - case 'E': - ll = SYSLOG_ERR; - break; - case 'C': - ll = SYSLOG_CRIT; - break; - default: - ll = 0; - } - auto thread = concurrency::OSThread::currentThread; - if (thread) { - syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg); - } else { - syslog.vlogf(ll, format, arg); - } - } -#endif -} - -void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) -{ -#if !MESHTASTIC_EXCLUDE_BLUETOOTH - if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { - bool isBleConnected = false; -#ifdef ARCH_ESP32 - isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); -#elif defined(ARCH_NRF52) - isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); -#endif - if (isBleConnected) { - char *message; - size_t initialLen; - size_t len; - initialLen = strlen(format); - message = new char[initialLen + 1]; - len = vsnprintf(message, initialLen + 1, format, arg); - if (len > initialLen) { - delete[] message; - message = new char[len + 1]; - vsnprintf(message, len + 1, format, arg); - } - auto thread = concurrency::OSThread::currentThread; - meshtastic_LogRecord logRecord = meshtastic_LogRecord_init_zero; - logRecord.level = getLogLevel(logLevel); - strcpy(logRecord.message, message); - if (thread) - strcpy(logRecord.source, thread->ThreadName.c_str()); - logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true); - - uint8_t *buffer = new uint8_t[meshtastic_LogRecord_size]; - size_t size = pb_encode_to_bytes(buffer, meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); -#ifdef ARCH_ESP32 - nimbleBluetooth->sendLog(buffer, size); -#elif defined(ARCH_NRF52) - nrf52Bluetooth->sendLog(buffer, size); -#endif - delete[] message; - delete[] buffer; - } - } -#else - (void)logLevel; - (void)format; - (void)arg; -#endif -} - -meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel) -{ - meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset - switch (logLevel[0]) { - case 'D': - ll = meshtastic_LogRecord_Level_DEBUG; - break; - case 'I': - ll = meshtastic_LogRecord_Level_INFO; - break; - case 'W': - ll = meshtastic_LogRecord_Level_WARNING; - break; - case 'E': - ll = meshtastic_LogRecord_Level_ERROR; - break; - case 'C': - ll = meshtastic_LogRecord_Level_CRITICAL; - break; - } - return ll; -} - -void RedirectablePrint::log(const char *logLevel, const char *format, ...) +size_t RedirectablePrint::log(const char *logLevel, const char *format, ...) { #ifdef ARCH_PORTDUINO if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - return; + return 0; else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - return; + return 0; else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - return; + return 0; #endif if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - return; + return 0; } - + size_t r = 0; #ifdef HAS_FREE_RTOS if (inDebugPrint != nullptr && xSemaphoreTake(inDebugPrint, portMAX_DELAY) == pdTRUE) { #else @@ -266,11 +94,81 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) va_list arg; va_start(arg, format); - log_to_serial(logLevel, format, arg); - log_to_syslog(logLevel, format, arg); - log_to_ble(logLevel, format, arg); + // Cope with 0 len format strings, but look for new line terminator + bool hasNewline = *format && format[strlen(format) - 1] == '\n'; + + // If we are the first message on a report, include the header + if (!isContinuationMessage) { + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile + if (rtc_sec > 0) { + long hms = rtc_sec % SEC_PER_DAY; + // hms += tz.tz_dsttime * SEC_PER_HOUR; + // hms -= tz.tz_minuteswest * SEC_PER_MIN; + // mod `hms` to ensure in positive range of [0...SEC_PER_DAY) + hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; + + // Tear apart hms into h:m:s + int hour = hms / SEC_PER_HOUR; + int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; + int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN +#ifdef ARCH_PORTDUINO + r += ::printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); +#else + r += printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); +#endif + } else +#ifdef ARCH_PORTDUINO + r += ::printf("%s | ??:??:?? %u ", logLevel, millis() / 1000); +#else + r += printf("%s | ??:??:?? %u ", logLevel, millis() / 1000); +#endif + + auto thread = concurrency::OSThread::currentThread; + if (thread) { + print("["); + // printf("%p ", thread); + // assert(thread->ThreadName.length()); + print(thread->ThreadName); + print("] "); + } + } + r += vprintf(format, arg); + +#if (HAS_WIFI || HAS_ETHERNET) && !defined(ARCH_PORTDUINO) + // if syslog is in use, collect the log messages and send them to syslog + if (syslog.isEnabled()) { + int ll = 0; + switch (logLevel[0]) { + case 'D': + ll = SYSLOG_DEBUG; + break; + case 'I': + ll = SYSLOG_INFO; + break; + case 'W': + ll = SYSLOG_WARN; + break; + case 'E': + ll = SYSLOG_ERR; + break; + case 'C': + ll = SYSLOG_CRIT; + break; + default: + ll = 0; + } + auto thread = concurrency::OSThread::currentThread; + if (thread) { + syslog.vlogf(ll, thread->ThreadName.c_str(), format, arg); + } else { + syslog.vlogf(ll, format, arg); + } + } +#endif va_end(arg); + + isContinuationMessage = !hasNewline; #ifdef HAS_FREE_RTOS xSemaphoreGive(inDebugPrint); #else @@ -278,7 +176,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif } - return; + return r; } void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len) diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h index 23ae3c44d..31cc1b6ef 100644 --- a/src/RedirectablePrint.h +++ b/src/RedirectablePrint.h @@ -1,7 +1,6 @@ #pragma once #include "../freertosinc.h" -#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -42,21 +41,23 @@ class RedirectablePrint : public Print * log message. Otherwise we assume more prints will come before the log message ends. This * allows you to call logDebug a few times to build up a single log message line if you wish. */ - void log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); + size_t log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4))); /** like printf but va_list based */ - size_t vprintf(const char *logLevel, const char *format, va_list arg); + size_t vprintf(const char *format, va_list arg); void hexDump(const char *logLevel, unsigned char *buf, uint16_t len); std::string mt_sprintf(const std::string fmt_str, ...); +}; - protected: - /// Subclasses can override if they need to change how we format over the serial port - virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); +class NoopPrint : public Print +{ + public: + virtual size_t write(uint8_t c) { return 1; } +}; - private: - void log_to_syslog(const char *logLevel, const char *format, va_list arg); - void log_to_ble(const char *logLevel, const char *format, va_list arg); - meshtastic_LogRecord_Level getLogLevel(const char *logLevel); -}; \ No newline at end of file +/** + * A printer that doesn't go anywhere + */ +extern NoopPrint noopPrint; \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 9a9331e47..cf6375585 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -28,7 +28,7 @@ void consolePrintf(const char *format, ...) { va_list arg; va_start(arg, format); - console->vprintf(nullptr, format, arg); + console->vprintf(format, arg); va_end(arg); console->flush(); } @@ -38,6 +38,7 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con assert(!console); console = this; canWrite = false; // We don't send packets to our port until it has talked to us first + // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks #ifdef RP2040_SLOW_CLOCK Port.setTX(SERIAL2_TX); @@ -84,40 +85,13 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled. if (config.has_lora && config.device.serial_enabled) { - // Switch to protobufs for log messages - usingProtobufs = true; + // Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets + if (!config.device.debug_log_enabled) + setDestination(&noopPrint); canWrite = true; return StreamAPI::handleToRadio(buf, len); } else { return false; } -} - -void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) -{ - if (usingProtobufs) { - meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset - switch (logLevel[0]) { - case 'D': - ll = meshtastic_LogRecord_Level_DEBUG; - break; - case 'I': - ll = meshtastic_LogRecord_Level_INFO; - break; - case 'W': - ll = meshtastic_LogRecord_Level_WARNING; - break; - case 'E': - ll = meshtastic_LogRecord_Level_ERROR; - break; - case 'C': - ll = meshtastic_LogRecord_Level_CRITICAL; - break; - } - - auto thread = concurrency::OSThread::currentThread; - emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg); - } else - RedirectablePrint::log_to_serial(logLevel, format, arg); } \ No newline at end of file diff --git a/src/SerialConsole.h b/src/SerialConsole.h index f1e636c9d..f8891ba14 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -8,11 +8,6 @@ */ class SerialConsole : public StreamAPI, public RedirectablePrint, private concurrency::OSThread { - /** - * If true we are talking to a smart host and all messages (including log messages) must be framed as protobufs. - */ - bool usingProtobufs = false; - public: SerialConsole(); @@ -36,13 +31,10 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur protected: /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() override; - - /// Possibly switch to protobufs if we see a valid protobuf message - virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); }; // A simple wrapper to allow non class aware code write to the console void consolePrintf(const char *format, ...); void consoleInit(); -extern SerialConsole *console; \ No newline at end of file +extern SerialConsole *console; diff --git a/src/commands.h b/src/commands.h index f2b783010..03ede5982 100644 --- a/src/commands.h +++ b/src/commands.h @@ -8,11 +8,13 @@ enum class Cmd { SET_ON, SET_OFF, ON_PRESS, - START_ALERT_FRAME, - STOP_ALERT_FRAME, + START_BLUETOOTH_PIN_SCREEN, START_FIRMWARE_UPDATE_SCREEN, + STOP_BLUETOOTH_PIN_SCREEN, STOP_BOOT_SCREEN, PRINT, + START_SHUTDOWN_SCREEN, + START_REBOOT_SCREEN, SHOW_PREV_FRAME, SHOW_NEXT_FRAME }; \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index aad4ac457..3d10feeaa 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -242,6 +242,9 @@ along with this program. If not, see . #define HAS_BLUETOOTH 0 #endif +#include "DebugConfiguration.h" +#include "RF95Configuration.h" + #ifndef HW_VENDOR #error HW_VENDOR must be defined #endif @@ -258,7 +261,6 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_GPS 1 #define MESHTASTIC_EXCLUDE_SCREEN 1 #define MESHTASTIC_EXCLUDE_MQTT 1 -#define MESHTASTIC_EXCLUDE_POWERMON 1 #endif // Turn off all optional modules @@ -279,7 +281,6 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_WAYPOINT 1 #define MESHTASTIC_EXCLUDE_INPUTBROKER 1 #define MESHTASTIC_EXCLUDE_SERIAL 1 -#define MESHTASTIC_EXCLUDE_POWERSTRESS 1 #endif // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) @@ -289,9 +290,6 @@ along with this program. If not, see . #define HAS_WIFI 0 #endif -// Allow code that needs internet to just check HAS_NETWORKING rather than HAS_WIFI || HAS_ETHERNET -#define HAS_NETWORKING (HAS_WIFI || HAS_ETHERNET) - // // Turn off Bluetooth #ifdef MESHTASTIC_EXCLUDE_BLUETOOTH #undef HAS_BLUETOOTH @@ -310,7 +308,4 @@ along with this program. If not, see . #ifdef MESHTASTIC_EXCLUDE_SCREEN #undef HAS_SCREEN #define HAS_SCREEN 0 -#endif - -#include "DebugConfiguration.h" -#include "RF95Configuration.h" \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 8738e2722..86408b8d2 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -314,7 +314,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case SHT31_4x_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2 || registerValue == 0x11da) { + if (registerValue == 0x11a2) { type = SHT4X; LOG_INFO("SHT4X sensor found\n"); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { @@ -402,4 +402,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); -} +} \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 017a2d025..40ee4ea03 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -3,13 +3,11 @@ #include "Default.h" #include "GPS.h" #include "NodeDB.h" -#include "PowerMon.h" #include "RTC.h" #include "main.h" // pmu_found #include "sleep.h" -#include "GPSUpdateScheduling.h" #include "cas.h" #include "ubx.h" @@ -23,6 +21,19 @@ #define GPS_RESET_MODE HIGH #endif +// How many minutes of sleep make it worthwhile to power-off the GPS +// Shorter than this, and GPS will only enter standby +// Affected by lock-time, and config.position.gps_update_interval +#ifndef GPS_STANDBY_THRESHOLD_MINUTES +#define GPS_STANDBY_THRESHOLD_MINUTES 15 +#endif + +// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby +// Shorter than this, and we'll just wait instead +#ifndef GPS_IDLE_THRESHOLD_SECONDS +#define GPS_IDLE_THRESHOLD_SECONDS 10 +#endif + #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) HardwareSerial *GPS::_serial_gps = &Serial1; #else @@ -31,8 +42,6 @@ HardwareSerial *GPS::_serial_gps = NULL; GPS *gps = nullptr; -GPSUpdateScheduling scheduling; - /// Multiple GPS instances might use the same serial port (in sequence), but we can /// only init that port once. static bool didSerialInit; @@ -42,25 +51,6 @@ uint8_t uBloxProtocolVersion; #define GPS_SOL_EXPIRY_MS 5000 // in millis. give 1 second time to combine different sentences. NMEA Frequency isn't higher anyway #define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc) -// For logging -const char *getGPSPowerStateString(GPSPowerState state) -{ - switch (state) { - case GPS_ACTIVE: - return "ACTIVE"; - case GPS_IDLE: - return "IDLE"; - case GPS_SOFTSLEEP: - return "SOFTSLEEP"; - case GPS_HARDSLEEP: - return "HARDSLEEP"; - case GPS_OFF: - return "OFF"; - default: - assert(false); // Unhandled enum value.. - } -} - void GPS::UBXChecksum(uint8_t *message, size_t length) { uint8_t CK_A = 0, CK_B = 0; @@ -782,6 +772,7 @@ bool GPS::setup() } notifyDeepSleepObserver.observe(¬ifyDeepSleep); + notifyGPSSleepObserver.observe(¬ifyGPSSleep); return true; } @@ -790,192 +781,113 @@ GPS::~GPS() { // we really should unregister our sleep observer notifyDeepSleepObserver.unobserve(¬ifyDeepSleep); + notifyGPSSleepObserver.observe(¬ifyGPSSleep); } -// Put the GPS hardware into a specified state -void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) +void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) { - // Update the stored GPSPowerstate, and create local copies - GPSPowerState oldState = powerState; - powerState = newState; - LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState)); + // Record the current powerState + if (on) + powerState = GPS_ACTIVE; + else if (!enabled) // User has disabled with triple press + powerState = GPS_OFF; + else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL) + powerState = GPS_IDLE; + else if (standbyOnly) + powerState = GPS_STANDBY; + else + powerState = GPS_OFF; - switch (newState) { - case GPS_ACTIVE: - case GPS_IDLE: - if (oldState == GPS_ACTIVE || oldState == GPS_IDLE) // If hardware already awake, no changes needed - break; - if (oldState != GPS_ACTIVE && oldState != GPS_IDLE) // If hardware just waking now, clear buffer - clearBuffer(); - powerMon->setState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) - writePinEN(true); // Power (EN pin): on - setPowerPMU(true); // Power (PMU): on - writePinStandby(false); // Standby (pin): awake (not standby) - setPowerUBLOX(true); // Standby (UBLOX): awake - break; + LOG_DEBUG("GPS::powerState=%d\n", powerState); - case GPS_SOFTSLEEP: - powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) - writePinEN(true); // Power (EN pin): on - setPowerPMU(true); // Power (PMU): on - writePinStandby(true); // Standby (pin): asleep (not awake) - setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed - break; - - case GPS_HARDSLEEP: - powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) - writePinEN(false); // Power (EN pin): off - setPowerPMU(false); // Power (PMU): off - writePinStandby(true); // Standby (pin): asleep (not awake) - setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed - break; - - case GPS_OFF: - assert(sleepTime == 0); // This is an indefinite sleep - powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing) - writePinEN(false); // Power (EN pin): off - setPowerPMU(false); // Power (PMU): off - writePinStandby(true); // Standby (pin): asleep - setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely - break; - } -} - -// Set power with EN pin, if relevant -void GPS::writePinEN(bool on) -{ - // Abort: if conflict with Canned Messages when using Wisblock(?) - if (HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1)) + // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out. + if (!on && powerState == GPS_IDLE) return; - // Abort: if pin unset - if (!en_gpio) - return; - - // Determine new value for the pin - bool val = GPS_EN_ACTIVE ? on : !on; - - // Write and log - pinMode(en_gpio, OUTPUT); - digitalWrite(en_gpio, val); -#ifdef GPS_EXTRAVERBOSE - LOG_DEBUG("Pin EN %s\n", val == HIGH ? "HIGH" : "LOW"); -#endif -} - -// Set the value of the STANDBY pin, if relevant -// true for standby state, false for awake -void GPS::writePinStandby(bool standby) -{ -#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones - -// Determine the new value for the pin -// Normally: active HIGH for awake -#if PIN_GPS_STANDBY_INVERTED - bool val = standby; -#else - bool val = !standby; -#endif - - // Write and log - pinMode(PIN_GPS_STANDBY, OUTPUT); - digitalWrite(PIN_GPS_STANDBY, val); -#ifdef GPS_EXTRAVERBOSE - LOG_DEBUG("Pin STANDBY %s\n", val == HIGH ? "HIGH" : "LOW"); -#endif -#endif -} - -// Enable / Disable GPS with PMU, if present -void GPS::setPowerPMU(bool on) -{ - // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, - // so treat as a standby. -#ifdef HAS_PMU - // Abort: if no PMU - if (!pmu_found) - return; - - // Abort: if PMU not initialized - if (!PMU) - return; - - uint8_t model = PMU->getChipModel(); - if (model == XPOWERS_AXP2101) { - if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { - // t-beam v1.2 GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3); - } else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) { - // t-beam-s3-core GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4); - } - } else if (model == XPOWERS_AXP192) { - // t-beam v1.1 GNSS power channel - on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3); - } - -#ifdef GPS_EXTRAVERBOSE - LOG_DEBUG("PMU %s\n", on ? "on" : "off"); -#endif -#endif -} - -// Set UBLOX power, if relevant -void GPS::setPowerUBLOX(bool on, uint32_t sleepMs) -{ - // Abort: if not UBLOX hardware - if (gnssModel != GNSS_MODEL_UBLOX) - return; - - // If waking if (on) { - gps->_serial_gps->write(0xFF); - clearBuffer(); // This often returns old data, so drop it -#ifdef GPS_EXTRAVERBOSE - LOG_DEBUG("UBLOX: wake\n"); -#endif + clearBuffer(); // drop any old data waiting in the buffer before re-enabling + if (en_gpio) + digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time } - - // If putting to sleep - else { - uint8_t msglen; - - // If we're being asked to sleep indefinitely, make *sure* we're awake first, to process the new sleep command - if (sleepMs == 0) { - setPowerUBLOX(true); - delay(500); + isInPowersave = !on; + if (!standbyOnly && en_gpio != 0 && + !(HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1))) { + LOG_DEBUG("GPS powerdown using GPS_EN_ACTIVE\n"); + digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); + return; + } +#ifdef HAS_PMU // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, so treat as a standby. + if (pmu_found && PMU) { + uint8_t model = PMU->getChipModel(); + if (model == XPOWERS_AXP2101) { + if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { + // t-beam v1.2 GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3); + } else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) { + // t-beam-s3-core GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4); + } + } else if (model == XPOWERS_AXP192) { + // t-beam v1.1 GNSS power channel + on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3); + } + return; + } +#endif +#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones + if (on) { + LOG_INFO("Waking GPS\n"); + pinMode(PIN_GPS_STANDBY, OUTPUT); + // Some PCB's use an inverse logic due to a transistor driver + // Example for this is the Pico-Waveshare Lora+GPS HAT +#ifdef PIN_GPS_STANDBY_INVERTED + digitalWrite(PIN_GPS_STANDBY, 0); +#else + digitalWrite(PIN_GPS_STANDBY, 1); +#endif + return; + } else { + LOG_INFO("GPS entering sleep\n"); + // notifyGPSSleep.notifyObservers(NULL); + pinMode(PIN_GPS_STANDBY, OUTPUT); +#ifdef PIN_GPS_STANDBY_INVERTED + digitalWrite(PIN_GPS_STANDBY, 1); +#else + digitalWrite(PIN_GPS_STANDBY, 0); +#endif + return; + } +#endif + if (!on) { + if (gnssModel == GNSS_MODEL_UBLOX) { + uint8_t msglen; + LOG_DEBUG("Sleep Time: %i\n", sleepTime); + if (strncmp(info.hwVersion, "000A0000", 8) != 0) { + for (int i = 0; i < 4; i++) { + gps->_message_PMREQ[0 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet + } + msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ); + } else { + for (int i = 0; i < 4; i++) { + gps->_message_PMREQ_10[4 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet + } + msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10); + } + gps->_serial_gps->write(gps->UBXscratch, msglen); } - - // Determine hardware version - if (strncmp(info.hwVersion, "000A0000", 8) != 0) { - // Encode the sleep time in millis into the packet - for (int i = 0; i < 4; i++) - gps->_message_PMREQ[0 + i] = sleepMs >> (i * 8); - - // Record the message length - msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ); - } else { - // Encode the sleep time in millis into the packet - for (int i = 0; i < 4; i++) - gps->_message_PMREQ_10[4 + i] = sleepMs >> (i * 8); - - // Record the message length - msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10); - #ifdef GNSS_Airoha // add by WayenWeng + else { if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { // TODO, send rtc mode command digitalWrite(PIN_GPS_EN, LOW); } -#endif } - - // Send the UBX packet - gps->_serial_gps->write(gps->UBXscratch, msglen); - -#ifdef GPS_EXTRAVERBOSE - LOG_DEBUG("UBLOX: sleep for %dmS\n", sleepMs); #endif + } else { + if (gnssModel == GNSS_MODEL_UBLOX) { + gps->_serial_gps->write(0xFF); + clearBuffer(); // This often returns old data, so drop it + } } } @@ -988,55 +900,109 @@ void GPS::setConnected() } } -// We want a GPS lock. Wake the hardware -void GPS::up() +/** + * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode + * + * calls sleep/wake + */ +void GPS::setAwake(bool wantAwake) { - scheduling.informSearching(); - setPowerState(GPS_ACTIVE); + + // If user has disabled GPS, make sure it is off, not just in standby or idle + if (!wantAwake && !enabled && powerState != GPS_OFF) { + setGPSPower(false, false, 0); + return; + } + + // If GPS power state needs to change + if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) { + LOG_DEBUG("WANT GPS=%d\n", wantAwake); + + // Calculate how long it takes to get a GPS lock + if (wantAwake) { + // Record the time we start looking for a lock + lastWakeStartMsec = millis(); +#ifdef GNSS_Airoha + lastFixStartMsec = 0; +#endif + } else { + // Record by how much we missed our ideal target postion.gps_update_interval (for logging only) + // Need to calculate this before we update lastSleepStartMsec, to make the new prediction + int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime(); + + // Record the time we finish looking for a lock + lastSleepStartMsec = millis(); + + // How long did it take to get GPS lock this time? + uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec; + + // Update the lock-time prediction + // Used pre-emptively, attempting to hit target of gps.position_update_interval + switch (GPSCycles) { + case 0: + LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000); + break; + case 1: + predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value + LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000); + break; + default: + // Predict lock-time using exponential smoothing: respond slowly to changes + predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction + LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000, + (lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000); + } + GPSCycles++; + } + + // How long to wait before attempting next GPS update + // Aims to hit position.gps_update_interval by using the lock-time prediction + uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0; + + // If long interval between updates: power off between updates + if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) { + setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime); + } + + // If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time + // We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due + // Will decide which inside setGPSPower method + else { +#ifdef GPS_UC6580 + setGPSPower(wantAwake, false, compensatedSleepTime); +#else + setGPSPower(wantAwake, true, compensatedSleepTime); +#endif + } + } } -// We've got a GPS lock. Enter a low power state, potentially. -void GPS::down() +/** Get how long we should stay looking for each acquisition in msecs + */ +uint32_t GPS::getWakeTime() const { - scheduling.informGotLock(); - uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs(); - uint32_t sleepTime = scheduling.msUntilNextSearch(); - uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); + uint32_t t = config.position.position_broadcast_secs; - LOG_DEBUG("%us until next search\n", sleepTime / 1000); + if (t == UINT32_MAX) + return t; // already maxint -#ifdef GNSS_Airoha - lastFixStartMsec = 0; -#endif + return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs); +} - // If update interval less than 10 seconds, no attempt to sleep - if (updateInterval <= 10 * 1000UL) - setPowerState(GPS_IDLE); - else { - // Check whether the GPS hardware is capable of GPS_SOFTSLEEP - // If not, fallback to GPS_HARDSLEEP instead - bool softsleepSupported = false; - if (gnssModel == GNSS_MODEL_UBLOX) // U-blox is supported via PMREQ - softsleepSupported = true; -#ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin - softsleepSupported = true; -#endif +/** Get how long we should sleep between aqusition attempts in msecs + */ +uint32_t GPS::getSleepTime() const +{ + uint32_t t = config.position.gps_update_interval; - // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP? - // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050 - // https://www.desmos.com/calculator/6gvjghoumr - // This is not particularly accurate, but probably an impromevement over a single, fixed threshold - uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22)); - LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000); + // We'll not need the GPS thread to wake up again after first acq. with fixed position. + if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED || config.position.fixed_position) + t = UINT32_MAX; // Sleep forever now - // If update interval too short: softsleep (if supported by hardware) - if (softsleepSupported && updateInterval < hardsleepThreshold) - setPowerState(GPS_SOFTSLEEP, sleepTime); + if (t == UINT32_MAX) + return t; // already maxint - // If update interval long enough (or softsleep unsupported): hardsleep instead - else - setPowerState(GPS_HARDSLEEP, sleepTime); - } + return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval); } void GPS::publishUpdate() @@ -1087,13 +1053,13 @@ int32_t GPS::runOnce() return disable(); } - if (whileActive()) { + if (whileIdle()) { // if we have received valid NMEA claim we are connected setConnected(); } else { if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) { // reset the GPS on next bootup - if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { + if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); devicestate.did_gps_reset = false; nodeDB->saveDeviceStateToDisk(); @@ -1108,43 +1074,54 @@ int32_t GPS::runOnce() // gps->factoryReset(); } - // If we're due for an update, wake the GPS - if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue()) - up(); + // If we are overdue for an update, turn on the GPS and at least publish the current status + uint32_t now = millis(); + uint32_t timeAsleep = now - lastSleepStartMsec; - // If we've already set time from the GPS, no need to ask the GPS - bool gotTime = (getRTCQuality() >= RTCQualityGPS); - if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time - gotTime = true; - shouldPublish = true; + auto sleepTime = getSleepTime(); + if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) && + ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) { + // We now want to be awake - so wake up the GPS + setAwake(true); } - bool gotLoc = lookForLocation(); - if (gotLoc && !hasValidLocation) { // declare that we have location ASAP - LOG_DEBUG("hasValidLocation RISING EDGE\n"); - hasValidLocation = true; - shouldPublish = true; - } - - bool tooLong = scheduling.searchedTooLong(); - if (tooLong) - LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time.\n"); - - // Once we get a location we no longer desperately want an update - // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); - if ((gotLoc && gotTime) || tooLong) { - - if (tooLong) { - // we didn't get a location during this ack window, therefore declare loss of lock - if (hasValidLocation) { - LOG_DEBUG("hasValidLocation FALLING EDGE\n"); - } - p = meshtastic_Position_init_default; - hasValidLocation = false; + // While we are awake + if (powerState == GPS_ACTIVE) { + // LOG_DEBUG("looking for location\n"); + // If we've already set time from the GPS, no need to ask the GPS + bool gotTime = (getRTCQuality() >= RTCQualityGPS); + if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time + gotTime = true; + shouldPublish = true; } - down(); - shouldPublish = true; // publish our update for this just finished acquisition window + bool gotLoc = lookForLocation(); + if (gotLoc && !hasValidLocation) { // declare that we have location ASAP + LOG_DEBUG("hasValidLocation RISING EDGE\n"); + hasValidLocation = true; + shouldPublish = true; + } + + now = millis(); + auto wakeTime = getWakeTime(); + bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime; + + // Once we get a location we no longer desperately want an update + // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); + if ((gotLoc && gotTime) || tooLong) { + + if (tooLong) { + // we didn't get a location during this ack window, therefore declare loss of lock + if (hasValidLocation) { + LOG_DEBUG("hasValidLocation FALLING EDGE (last read: %d)\n", gotLoc); + } + p = meshtastic_Position_init_default; + hasValidLocation = false; + } + + setAwake(false); + shouldPublish = true; // publish our update for this just finished acquisition window + } } // If state has changed do a publish @@ -1170,7 +1147,9 @@ void GPS::clearBuffer() int GPS::prepareDeepSleep(void *unused) { LOG_INFO("GPS deep sleep!\n"); - disable(); + + setAwake(false); + return 0; } @@ -1370,6 +1349,12 @@ GPS *GPS::createGps() new_gps->tx_gpio = _tx_gpio; new_gps->en_gpio = _en_gpio; + if (_en_gpio != 0) { + LOG_DEBUG("Setting %d to output.\n", _en_gpio); + pinMode(_en_gpio, OUTPUT); + digitalWrite(_en_gpio, !GPS_EN_ACTIVE); + } + #ifdef PIN_GPS_PPS // pulse per second pinMode(PIN_GPS_PPS, INPUT); @@ -1384,8 +1369,7 @@ GPS *GPS::createGps() LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n"); #endif - // Make sure the GPS is awake before performing any init. - new_gps->up(); + new_gps->setGPSPower(true, false, 0); #ifdef PIN_GPS_RESET pinMode(PIN_GPS_RESET, OUTPUT); @@ -1393,6 +1377,7 @@ GPS *GPS::createGps() delay(10); digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); #endif + new_gps->setAwake(true); // Wake GPS power before doing any init if (_serial_gps) { #ifdef ARCH_ESP32 @@ -1718,13 +1703,13 @@ bool GPS::hasFlow() return reader.passedChecksum() > 0; } -bool GPS::whileActive() +bool GPS::whileIdle() { unsigned int charsInBuf = 0; bool isValid = false; if (powerState != GPS_ACTIVE) { clearBuffer(); - return false; + return (powerState == GPS_ACTIVE); } #ifdef SERIAL_BUFFER_SIZE if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) { @@ -1755,21 +1740,20 @@ bool GPS::whileActive() } void GPS::enable() { - // Clear the old scheduling info (reset the lock-time prediction) - scheduling.reset(); + // Clear the old lock-time prediction + GPSCycles = 0; + predictedLockTime = 0; enabled = true; setInterval(GPS_THREAD_INTERVAL); - - scheduling.informSearching(); - setPowerState(GPS_ACTIVE); + setAwake(true); } int32_t GPS::disable() { enabled = false; setInterval(INT32_MAX); - setPowerState(GPS_OFF); + setAwake(false); return INT32_MAX; } @@ -1778,7 +1762,7 @@ void GPS::toggleGpsMode() { if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; - LOG_INFO("User toggled GpsMode. Now DISABLED.\n"); + LOG_DEBUG("Flag set to false for gps power. GpsMode: DISABLED\n"); #ifdef GNSS_Airoha if (powerState != GPS_ACTIVE) { LOG_DEBUG("User power Off GPS\n"); @@ -1788,8 +1772,8 @@ void GPS::toggleGpsMode() disable(); } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; - LOG_INFO("User toggled GpsMode. Now ENABLED\n"); + LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n"); enable(); } } -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 1505b9843..91548ce2c 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -39,11 +39,10 @@ typedef enum { } GPS_RESPONSE; enum GPSPowerState : uint8_t { - GPS_ACTIVE, // Awake and want a position - GPS_IDLE, // Awake, but not wanting another position yet - GPS_SOFTSLEEP, // Physically powered on, but soft-sleeping - GPS_HARDSLEEP, // Physically powered off, but scheduled to wake - GPS_OFF // Powered off indefinitely + GPS_OFF = 0, // Physically powered off + GPS_ACTIVE = 1, // Awake and want a position + GPS_STANDBY = 2, // Physically powered on, but soft-sleeping + GPS_IDLE = 3, // Awake, but not wanting another position yet }; // Generate a string representation of DOP @@ -74,6 +73,8 @@ class GPS : private concurrency::OSThread uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; uint32_t en_gpio = 0; + uint32_t predictedLockTime = 0; + uint32_t GPSCycles = 0; int speedSelect = 0; int probeTries = 2; @@ -98,6 +99,7 @@ class GPS : private concurrency::OSThread uint8_t numSatellites = 0; CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); + CallbackObserver notifyGPSSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); public: /** If !NULL we will use this serial port to construct our GPS */ @@ -173,8 +175,7 @@ class GPS : private concurrency::OSThread // toggle between enabled/disabled void toggleGpsMode(); - // Change the power state of the GPS - for power saving / shutdown - void setPowerState(GPSPowerState newState, uint32_t sleepMs = 0); + void setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime); /// Returns true if we have acquired GPS lock. virtual bool hasLock(); @@ -205,18 +206,18 @@ class GPS : private concurrency::OSThread GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis); + /** + * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode + * + * calls sleep/wake + */ + void setAwake(bool on); virtual bool factoryReset(); // Creates an instance of the GPS class. // Returns the new instance or null if the GPS is not present. static GPS *createGps(); - // Wake the GPS hardware - ready for an update - void up(); - - // Let the GPS hardware save power between updates - void down(); - protected: /** * Perform any processing that should be done only while the GPS is awake and looking for a fix. @@ -239,7 +240,7 @@ class GPS : private concurrency::OSThread * * Return true if we received a valid message from the GPS */ - virtual bool whileActive(); + virtual bool whileIdle(); /** * Perform any processing that should be done only while the GPS is awake and looking for a fix. @@ -266,21 +267,13 @@ class GPS : private concurrency::OSThread void UBXChecksum(uint8_t *message, size_t length); void CASChecksum(uint8_t *message, size_t length); - /** Set power with EN pin, if relevant + /** Get how long we should stay looking for each aquisition */ - void writePinEN(bool on); + uint32_t getWakeTime() const; - /** Set the value of the STANDBY pin, if relevant + /** Get how long we should sleep between aqusition attempts */ - void writePinStandby(bool standby); - - /** Set GPS power with PMU, if relevant - */ - void setPowerPMU(bool on); - - /** Set UBLOX power, if relevant - */ - void setPowerUBLOX(bool on, uint32_t sleepMs = 0); + uint32_t getSleepTime() const; /** * Tell users we have new GPS readings @@ -296,11 +289,9 @@ class GPS : private concurrency::OSThread // delay counter to allow more sats before fixed position stops GPS thread uint8_t fixeddelayCtr = 0; - const char *powerStateToString(); - protected: GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN; }; extern GPS *gps; -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file diff --git a/src/gps/GPSUpdateScheduling.cpp b/src/gps/GPSUpdateScheduling.cpp deleted file mode 100644 index 949ef6039..000000000 --- a/src/gps/GPSUpdateScheduling.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "GPSUpdateScheduling.h" - -#include "Default.h" - -// Mark the time when searching for GPS position begins -void GPSUpdateScheduling::informSearching() -{ - searchStartedMs = millis(); -} - -// Mark the time when searching for GPS is complete, -// then update the predicted lock-time -void GPSUpdateScheduling::informGotLock() -{ - searchEndedMs = millis(); - LOG_DEBUG("Took %us to get lock\n", (searchEndedMs - searchStartedMs) / 1000); - updateLockTimePrediction(); -} - -// Clear old lock-time prediction data. -// When re-enabling GPS with user button. -void GPSUpdateScheduling::reset() -{ - searchStartedMs = 0; - searchEndedMs = 0; - searchCount = 0; - predictedMsToGetLock = 0; -} - -// How many milliseconds before we should next search for GPS position -// Used by GPS hardware directly, to enter timed hardware sleep -uint32_t GPSUpdateScheduling::msUntilNextSearch() -{ - uint32_t now = millis(); - - // Target interval (seconds), between GPS updates - uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval); - - // Check how long until we should start searching, to hopefully hit our target interval - uint32_t dueAtMs = searchEndedMs + updateInterval; - uint32_t compensatedStart = dueAtMs - predictedMsToGetLock; - int32_t remainingMs = compensatedStart - now; - - // If we should have already started (negative value), start ASAP - if (remainingMs < 0) - remainingMs = 0; - - return (uint32_t)remainingMs; -} - -// How long have we already been searching? -// Used to abort a search in progress, if it runs unnaceptably long -uint32_t GPSUpdateScheduling::elapsedSearchMs() -{ - // If searching - if (searchStartedMs > searchEndedMs) - return millis() - searchStartedMs; - - // If not searching - 0ms. We shouldn't really consume this value - else - return 0; -} - -// Is it now time to begin searching for a GPS position? -bool GPSUpdateScheduling::isUpdateDue() -{ - return (msUntilNextSearch() == 0); -} - -// Have we been searching for a GPS position for too long? -bool GPSUpdateScheduling::searchedTooLong() -{ - uint32_t maxSearchMs = - Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); - - // If broadcast interval set to max, no such thing as "too long" - if (maxSearchMs == UINT32_MAX) - return false; - - // If we've been searching longer than our position broadcast interval: that's too long - else if (elapsedSearchMs() > maxSearchMs) - return true; - - // Otherwise, not too long yet! - else - return false; -} - -// Updates the predicted time-to-get-lock, by exponentially smoothing the latest observation -void GPSUpdateScheduling::updateLockTimePrediction() -{ - - // How long did it take to get GPS lock this time? - // Duration between down() calls - int32_t lockTime = searchEndedMs - searchStartedMs; - if (lockTime < 0) - lockTime = 0; - - // Ignore the first lock-time: likely to be long, will skew data - - // Second locktime: likely stable. Use to intialize the smoothing filter - if (searchCount == 1) - predictedMsToGetLock = lockTime; - - // Third locktime and after: predict using exponential smoothing. Respond slowly to changes - else if (searchCount > 1) - predictedMsToGetLock = (lockTime * weighting) + (predictedMsToGetLock * (1 - weighting)); - - searchCount++; // Only tracked so we can diregard initial lock-times - - LOG_DEBUG("Predicting %us to get next lock\n", predictedMsToGetLock / 1000); -} - -// How long do we expect to spend searching for a lock? -uint32_t GPSUpdateScheduling::predictedSearchDurationMs() -{ - return GPSUpdateScheduling::predictedMsToGetLock; -} \ No newline at end of file diff --git a/src/gps/GPSUpdateScheduling.h b/src/gps/GPSUpdateScheduling.h deleted file mode 100644 index 7e121c9b6..000000000 --- a/src/gps/GPSUpdateScheduling.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "configuration.h" - -// Encapsulates code responsible for the timing of GPS updates -class GPSUpdateScheduling -{ - public: - // Marks the time of these events, for calculation use - void informSearching(); - void informGotLock(); // Predicted lock-time is recalculated here - - void reset(); // Reset the prediction - after GPS::disable() / GPS::enable() - bool isUpdateDue(); // Is it time to begin searching for a GPS position? - bool searchedTooLong(); // Have we been searching for too long? - - uint32_t msUntilNextSearch(); // How long until we need to begin searching for a GPS? Info provided to GPS hardware for sleep - uint32_t elapsedSearchMs(); // How long have we been searching so far? - uint32_t predictedSearchDurationMs(); // How long do we expect to spend searching for a lock? - - private: - void updateLockTimePrediction(); // Called from informGotLock - uint32_t searchStartedMs = 0; - uint32_t searchEndedMs = 0; - uint32_t searchCount = 0; - uint32_t predictedMsToGetLock = 0; - - const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time". -}; \ No newline at end of file diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index d81ab6ff4..bbc12521a 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -156,8 +156,7 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ - defined(HELTEC_VISION_MASTER_E290) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) { // Start HSPI hspi = new SPIClass(HSPI); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 26091b2cd..f74416494 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -5,6 +5,11 @@ #include "GxEPD2_BW.h" #include +#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) +// Re-enable SPI after deep sleep: rtc_gpio_hold_dis() +#include "driver/rtc_io.h" +#endif + /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * @@ -67,8 +72,7 @@ class EInkDisplay : public OLEDDisplay GxEPD2_BW *adafruitDisplay = NULL; // If display uses HSPI -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ - defined(HELTEC_VISION_MASTER_E290) +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) SPIClass *hspi = NULL; #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b2059b71c..60168cffc 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -41,7 +41,6 @@ along with this program. If not, see . #include "mesh/Channels.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" #include "meshUtils.h" -#include "modules/AdminModule.h" #include "modules/ExternalNotificationModule.h" #include "modules/TextMessageModule.h" #include "sleep.h" @@ -76,6 +75,7 @@ namespace graphics // A text message frame + debug frame + all the node infos FrameCallback *normalFrames; static uint32_t targetFramerate = IDLE_FRAMERATE; +static char btPIN[16] = "888888"; uint32_t logo_timeout = 5000; // 4 seconds for EACH logo @@ -108,39 +108,15 @@ GeoCoord geoCoord; static bool heartbeat = false; #endif -// Quick access to screen dimensions from static drawing functions -// DEPRECATED. To-do: move static functions inside Screen class -#define SCREEN_WIDTH display->getWidth() -#define SCREEN_HEIGHT display->getHeight() +static uint16_t displayWidth, displayHeight; + +#define SCREEN_WIDTH displayWidth +#define SCREEN_HEIGHT displayHeight #include "graphics/ScreenFonts.h" #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) -/// Check if the display can render a string (detect special chars; emoji) -static bool haveGlyphs(const char *str) -{ -#if defined(OLED_UA) || defined(OLED_RU) - // Don't want to make any assumptions about custom language support - return true; -#endif - - // Check each character with the lookup function for the OLED library - // We're not really meant to use this directly.. - bool have = true; - for (uint16_t i = 0; i < strlen(str); i++) { - uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); - // If font doesn't support a character, it is substituted for ¿ - if (result == 191 && (uint8_t)str[i] != 191) { - have = false; - break; - } - } - - LOG_DEBUG("haveGlyphs=%d\n", have); - return have; -} - /** * Draw the icon with extra info printed around the corners */ @@ -164,15 +140,13 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); - // Draw version and short name in upper right - char buf[25]; - snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : ""); - - display->setTextAlignment(TEXT_ALIGN_RIGHT); - display->drawString(x + SCREEN_WIDTH, y + 0, buf); + // Draw version in upper right + char buf[16]; + snprintf(buf, sizeof(buf), "%s", + xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); screen->forceDisplay(); - - display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code + // FIXME - draw serial # somewhere? } static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -207,15 +181,14 @@ static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDi if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); - // Draw version and shortname in upper right - char buf[25]; - snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : ""); - - display->setTextAlignment(TEXT_ALIGN_RIGHT); - display->drawString(x + SCREEN_WIDTH, y + 0, buf); + // Draw version in upper right + char buf[16]; + snprintf(buf, sizeof(buf), "%s", + xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf); screen->forceDisplay(); - display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code + // FIXME - draw serial # somewhere? } static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -225,7 +198,7 @@ static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i drawOEMIconScreen(region, display, state, x, y); } -void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) +static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message) { uint16_t x_offset = display->width() / 2; display->setTextAlignment(TEXT_ALIGN_CENTER); @@ -233,6 +206,20 @@ void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int1 display->drawString(x_offset + x, 26 + y, message); } +static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ +#ifdef ARCH_ESP32 + if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { + drawFrameText(display, state, x, y, "Resuming..."); + } else +#endif + { + // Draw region in upper left + const char *region = myRegion ? myRegion->name : NULL; + drawIconScreen(region, display, state, x, y); + } +} + // Used on boot when a certificate is being created static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -290,19 +277,40 @@ static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) } } +/// Check if the display can render a string (detect special chars; emoji) +static bool haveGlyphs(const char *str) +{ +#if defined(OLED_UA) || defined(OLED_RU) + // Don't want to make any assumptions about custom language support + return true; +#endif + + // Check each character with the lookup function for the OLED library + // We're not really meant to use this directly.. + bool have = true; + for (uint16_t i = 0; i < strlen(str); i++) { + uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]); + // If font doesn't support a character, it is substituted for ¿ + if (result == 191 && (uint8_t)str[i] != 191) { + have = false; + break; + } + } + + LOG_DEBUG("haveGlyphs=%d\n", have); + return have; +} + #ifdef USE_EINK /// Used on eink displays while in deep sleep static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { - // Next frame should use full-refresh, and block while running, else device will sleep before async callback EINK_ADD_FRAMEFLAG(display, COSMETIC); EINK_ADD_FRAMEFLAG(display, BLOCKING); LOG_DEBUG("Drawing deep sleep screen\n"); - - // Display displayStr on the screen - drawIconScreen("Sleeping", display, state, x, y); + drawIconScreen("Sleeping...", display, state, x, y); } /// Used on eink displays when screen updates are paused @@ -367,7 +375,7 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int // in the array of "drawScreen" functions; however, // the passed-state doesn't quite reflect the "current" // screen, so we have to detect it. - if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == TransitionRelationship_INCOMING) { + if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) { // if we're transitioning from the end of the frame list back around to the first // frame, then we want this to be `0` module_frame = state->transitionFrameTarget; @@ -381,6 +389,31 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int pi.drawFrame(display, state, x, y); } +static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 32; + 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_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); +} + static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_CENTER); @@ -1058,8 +1091,45 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state #endif } +/// Draw the last waypoint we received +static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + static char tempBuf[237]; + + meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); + + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + } + + uint32_t seconds = sinceReceived(&mp); + uint32_t minutes = seconds / 60; + uint32_t hours = minutes / 60; + uint32_t days = hours / 24; + + if (config.display.heading_bold) { + display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s", + screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); + } + display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(), + (node && node->has_user) ? node->user.short_name : "???"); + + display->setColor(WHITE); + meshtastic_Waypoint scratch; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + snprintf(tempBuf, sizeof(tempBuf), "Received waypoint: %s", scratch.name); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); + } +} + /// Draw a series of fields in a column, wrapping to multiple columns if needed -void Screen::drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) +static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) { // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1239,13 +1309,56 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } } #endif +namespace +{ + +/// A basic 2D point class for drawing +class Point +{ + public: + float x, y; + + Point(float _x, float _y) : x(_x), y(_y) {} + + /// Apply a rotation around zero (standard rotation matrix math) + void rotate(float radian) + { + float cos = cosf(radian), sin = sinf(radian); + float rx = x * cos + y * sin, ry = -x * sin + y * cos; + + x = rx; + y = ry; + } + + void translate(int16_t dx, int dy) + { + x += dx; + y += dy; + } + + void scale(float f) + { + // We use -f here to counter the flip that happens + // on the y axis when drawing and rotating on screen + x *= f; + y *= -f; + } +}; + +} // namespace + +static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2) +{ + d->drawLine(p1.x, p1.y, p2.x, p2.y); +} + /** * Given a recent lat/lon return a guess of the heading the user is walking on. * * We keep a series of "after you've gone 10 meters, what is your heading since * the last reference point?" */ -float Screen::estimatedHeading(double lat, double lon) +static float estimatedHeading(double lat, double lon) { static double oldLat, oldLon; static float b; @@ -1269,13 +1382,38 @@ float Screen::estimatedHeading(double lat, double lon) return b; } +static uint16_t getCompassDiam(OLEDDisplay *display) +{ + uint16_t diam = 0; + uint16_t offset = 0; + + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) + offset = FONT_HEIGHT_SMALL; + + // get the smaller of the 2 dimensions and subtract 20 + if (display->getWidth() > (display->getHeight() - offset)) { + diam = display->getHeight() - offset; + // if 2/3 of the other size would be smaller, use that + if (diam > (display->getWidth() * 2 / 3)) { + diam = display->getWidth() * 2 / 3; + } + } else { + diam = display->getWidth(); + if (diam > ((display->getHeight() - offset) * 2 / 3)) { + diam = (display->getHeight() - offset) * 2 / 3; + } + } + + return diam - 20; +}; + /// We will skip one node - the one for us, so we just blindly loop over all /// nodes static size_t nodeIndex; static int8_t prevFrame = -1; // Draw the arrow pointing to a node's location -void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian) +static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian) { Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; @@ -1285,45 +1423,16 @@ void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t com for (int i = 0; i < 4; i++) { arrowPoints[i]->rotate(headingRadian); - arrowPoints[i]->scale(compassDiam * 0.6); + arrowPoints[i]->scale(getCompassDiam(display) * 0.6); arrowPoints[i]->translate(compassX, compassY); } - display->drawLine(tip.x, tip.y, tail.x, tail.y); - display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y); - display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y); + drawLine(display, tip, tail); + drawLine(display, leftArrow, tip); + drawLine(display, rightArrow, tip); } -// Get a string representation of the time passed since something happened -void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) -{ - // Use an absolute timestamp in some cases. - // Particularly useful with E-Ink displays. Static UI, fewer refreshes. - uint8_t timestampHours, timestampMinutes; - int32_t daysAgo; - bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); - - if (agoSecs < 120) // last 2 mins? - snprintf(timeStr, maxLength, "%u seconds ago", agoSecs); - // -- if suitable for timestamp -- - else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes - snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); - else if (useTimestamp && daysAgo == 0) // Today - snprintf(timeStr, maxLength, "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); - else if (useTimestamp && daysAgo == 1) // Yesterday - snprintf(timeStr, maxLength, "Seen yesterday"); - else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) - snprintf(timeStr, maxLength, "%li days ago", (long)daysAgo); - // -- if using time delta instead -- - else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60); - // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. - else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) - snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60); - else - snprintf(timeStr, maxLength, "unknown age"); -} - -void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) +// Draw north +static void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading) { // If north is supposed to be at the top of the compass we want rotation to be +0 if (config.display.compass_north_top) @@ -1333,43 +1442,19 @@ void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t co Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); Point *rosePoints[] = {&N1, &N2, &N3, &N4}; - uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); - for (int i = 0; i < 4; i++) { // North on compass will be negative of heading rosePoints[i]->rotate(-myHeading); - rosePoints[i]->scale(compassDiam); + rosePoints[i]->scale(getCompassDiam(display)); rosePoints[i]->translate(compassX, compassY); } - display->drawLine(N1.x, N1.y, N3.x, N3.y); - display->drawLine(N2.x, N2.y, N4.x, N4.y); - display->drawLine(N1.x, N1.y, N4.x, N4.y); + drawLine(display, N1, N3); + drawLine(display, N2, N4); + drawLine(display, N1, N4); } -uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight) -{ - uint16_t diam = 0; - uint16_t offset = 0; - - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) - offset = FONT_HEIGHT_SMALL; - - // get the smaller of the 2 dimensions and subtract 20 - if (displayWidth > (displayHeight - offset)) { - diam = displayHeight - offset; - // if 2/3 of the other size would be smaller, use that - if (diam > (displayWidth * 2 / 3)) { - diam = displayWidth * 2 / 3; - } - } else { - diam = displayWidth; - if (diam > ((displayHeight - offset) * 2 / 3)) { - diam = (displayHeight - offset) * 2 / 3; - } - } - - return diam - 20; -}; +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -1409,8 +1494,34 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100)); } + uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; - screen->getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr)); + + // Use an absolute timestamp in some cases. + // Particularly useful with E-Ink displays. Static UI, fewer refreshes. + uint8_t timestampHours, timestampMinutes; + int32_t daysAgo; + bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo); + + if (agoSecs < 120) // last 2 mins? + snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); + // -- if suitable for timestamp -- + else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / SECONDS_IN_MINUTE); + else if (useTimestamp && daysAgo == 0) // Today + snprintf(lastStr, sizeof(lastStr), "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes); + else if (useTimestamp && daysAgo == 1) // Yesterday + snprintf(lastStr, sizeof(lastStr), "Seen yesterday"); + else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method) + snprintf(lastStr, sizeof(lastStr), "%li days ago", (long)daysAgo); + // -- if using time delta instead -- + else if (agoSecs < 120 * 60) // last 2 hrs + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); + // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. + else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) + snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); + else + snprintf(lastStr, sizeof(lastStr), "unknown age"); static char distStr[20]; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { @@ -1421,14 +1532,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); const char *fields[] = {username, lastStr, signalStr, distStr, NULL}; int16_t compassX = 0, compassY = 0; - uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); // coordinates for the center of the compass/circle if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5; + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; compassY = y + SCREEN_HEIGHT / 2; } else { - compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5; + compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5; compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; } bool hasNodeHeading = false; @@ -1439,8 +1549,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (screen->hasHeading()) myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians else - myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - screen->drawCompassNorth(display, compassX, compassY, myHeading); + myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); + drawCompassNorth(display, compassX, compassY, myHeading); if (hasValidPosition(node)) { // display direction toward node @@ -1467,7 +1577,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // If the top of the compass is not a static north we need adjust bearingToOther based on heading if (!config.display.compass_north_top) bearingToOther -= myHeading; - screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); + drawNodeHeading(display, compassX, compassY, bearingToOther); } } if (!hasNodeHeading) { @@ -1477,19 +1587,15 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // hasValidPosition(node)); display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); } - display->drawCircle(compassX, compassY, compassDiam / 2); + display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { display->setColor(BLACK); } // Must be after distStr is populated - screen->drawColumns(display, x, y, fields); + drawColumns(display, x, y, fields); } -#if defined(ESP_PLATFORM) && defined(USE_ST7789) -SPIClass SPI1(HSPI); -#endif - Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { @@ -1497,13 +1603,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) dispdev = new SH1106Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif defined(USE_ST7789) -#ifdef ESP_PLATFORM - dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7789_SDA, - ST7789_MISO, ST7789_SCK); -#else - dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); -#endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -1582,14 +1681,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif dispdev->displayOn(); -#ifdef USE_ST7789 -#ifdef ESP_PLATFORM - analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); -#else - pinMode(VTFT_LEDA, OUTPUT); - digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); -#endif -#endif + enabled = true; setInterval(0); // Draw ASAP runASAP = true; @@ -1600,12 +1692,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif LOG_INFO("Turning off screen\n"); dispdev->displayOff(); - -#ifdef USE_ST7789 - pinMode(VTFT_LEDA, OUTPUT); - digitalWrite(VTFT_LEDA, !TFT_BACKLIGHT_ON); -#endif - #ifdef T_WATCH_S3 PMU->disablePowerOutput(XPOWERS_ALDO2); #endif @@ -1655,19 +1741,9 @@ void Screen::setup() // Add frames. EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); - alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { -#ifdef ARCH_ESP32 - if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) { - drawFrameText(display, state, x, y, "Resuming..."); - } else -#endif - { - // Draw region in upper left - const char *region = myRegion ? myRegion->name : NULL; - drawIconScreen(region, display, state, x, y); - } - }; - ui->setFrames(alertFrames, 1); + static FrameCallback bootFrames[] = {drawBootScreen}; + static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); + ui->setFrames(bootFrames, bootFrameCount); // No overlays. ui->setOverlays(nullptr, 0); @@ -1726,7 +1802,6 @@ void Screen::setup() powerStatusObserver.observe(&powerStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus); - adminMessageObserver.observe(adminModule); if (textMessageModule) textMessageObserver.observe(textMessageModule); if (inputBroker) @@ -1841,22 +1916,13 @@ int32_t Screen::runOnce() case Cmd::SHOW_NEXT_FRAME: handleShowNextFrame(); break; - case Cmd::START_ALERT_FRAME: { - showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away - showingNormalScreen = false; - alertFrames[0] = alertFrame; -#ifdef USE_EINK - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please - EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update - handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) -#endif - setFrameImmediateDraw(alertFrames); + case Cmd::START_BLUETOOTH_PIN_SCREEN: + handleStartBluetoothPinScreen(cmd.bluetooth_pin); break; - } case Cmd::START_FIRMWARE_UPDATE_SCREEN: handleStartFirmwareUpdateScreen(); break; - case Cmd::STOP_ALERT_FRAME: + case Cmd::STOP_BLUETOOTH_PIN_SCREEN: case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame setFrames(); @@ -1865,6 +1931,12 @@ int32_t Screen::runOnce() handlePrint(cmd.print_text); free(cmd.print_text); break; + case Cmd::START_SHUTDOWN_SCREEN: + handleShutdownScreen(); + break; + case Cmd::START_REBOOT_SCREEN: + handleRebootScreen(); + break; default: LOG_ERROR("Invalid screen cmd\n"); } @@ -1955,6 +2027,9 @@ void Screen::setWelcomeFrames() /// Determine which screensaver frame to use, then set the FrameCallback void Screen::setScreensaverFrames(FrameCallback einkScreensaver) { + // Remember current frame, restore position at power-on + uint8_t frameNumber = ui->getUiState()->currentFrame; + // Retain specified frame / overlay callback beyond scope of this method static FrameCallback screensaverFrame; static OverlayCallback screensaverOverlay; @@ -1992,8 +2067,9 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) #endif // Prepare now for next frame, shown when display wakes - ui->setOverlays(NULL, 0); // Clear overlay - setFrames(FOCUS_PRESERVE); // Return to normal display updates, showing same frame as before screensaver, ideally + ui->setOverlays(NULL, 0); // Clear overlay + setFrames(); // Return to normal display updates + ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on // Pick a refresh method, for when display wakes #ifdef EINK_HASQUIRK_GHOSTING @@ -2004,13 +2080,9 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) } #endif -// Regenerate the normal set of frames, focusing a specific frame if requested -// Called when a frame should be added / removed, or custom frames should be cleared -void Screen::setFrames(FrameFocus focus) +// restore our regular frame list +void Screen::setFrames() { - uint8_t originalPosition = ui->getUiState()->currentFrame; - FramesetInfo fsi; // Location of specific frames, for applying focus parameter - LOG_DEBUG("showing standard frames\n"); showingNormalScreen = true; @@ -2044,36 +2116,27 @@ void Screen::setFrames(FrameFocus focus) // is the same offset into the moduleFrames vector // so that we can invoke the module's callback for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { - // Draw the module frame, using the hack described above - normalFrames[numframes] = drawModuleFrame; - - // Check if the module being drawn has requested focus - // We will honor this request later, if setFrames was triggered by a UIFrameEvent - MeshModule *m = *i; - if (m->isRequestingFocus()) - fsi.positions.focusedModule = numframes; - - numframes++; + normalFrames[numframes++] = drawModuleFrame; } LOG_DEBUG("Added modules. numframes: %d\n", numframes); // If we have a critical fault, show it first - fsi.positions.fault = numframes; - if (error_code) { + if (error_code) normalFrames[numframes++] = drawCriticalFaultFrame; - focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame - } #ifdef T_WATCH_S3 normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame; #endif // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules - fsi.positions.textMessage = numframes; if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { normalFrames[numframes++] = drawTextMessageFrame; } + // If we have a waypoint - show it next, unless it's a phone message and we aren't using any special modules + if (devicestate.has_rx_waypoint && shouldDrawMessage(&devicestate.rx_waypoint)) { + normalFrames[numframes++] = drawWaypointFrame; + } // then all the nodes // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens @@ -2085,14 +2148,11 @@ void Screen::setFrames(FrameFocus focus) // // Since frames are basic function pointers, we have to use a helper to // call a method on debugInfo object. - fsi.positions.log = numframes; normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; // call a method on debugInfoScreen object (for more details) - fsi.positions.settings = numframes; normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; - fsi.positions.wifi = numframes; #if HAS_WIFI && !defined(ARCH_PORTDUINO) if (isWifiAvailable()) { // call a method on debugInfoScreen object (for more details) @@ -2100,7 +2160,6 @@ void Screen::setFrames(FrameFocus focus) } #endif - fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); ui->setFrames(normalFrames, numframes); @@ -2114,58 +2173,20 @@ void Screen::setFrames(FrameFocus focus) prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list // just changed) - // Focus on a specific frame, in the frame set we just created - switch (focus) { - case FOCUS_DEFAULT: - ui->switchToFrame(0); // First frame - break; - case FOCUS_FAULT: - ui->switchToFrame(fsi.positions.fault); - break; - case FOCUS_TEXTMESSAGE: - ui->switchToFrame(fsi.positions.textMessage); - break; - case FOCUS_MODULE: - // Whichever frame was marked by MeshModule::requestFocus(), if any - // If no module requested focus, will show the first frame instead - ui->switchToFrame(fsi.positions.focusedModule); - break; - - case FOCUS_PRESERVE: - // If we can identify which type of frame "originalPosition" was, can move directly to it in the new frameset - FramesetInfo &oldFsi = this->framesetInfo; - if (originalPosition == oldFsi.positions.log) - ui->switchToFrame(fsi.positions.log); - else if (originalPosition == oldFsi.positions.settings) - ui->switchToFrame(fsi.positions.settings); - else if (originalPosition == oldFsi.positions.wifi) - ui->switchToFrame(fsi.positions.wifi); - - // If frame count has decreased - else if (fsi.frameCount < oldFsi.frameCount) { - uint8_t numDropped = oldFsi.frameCount - fsi.frameCount; - // Move n frames backwards - if (numDropped <= originalPosition) - ui->switchToFrame(originalPosition - numDropped); - // Unless that would put us "out of bounds" (< 0) - else - ui->switchToFrame(0); - } - - // If we're not sure exactly which frame we were on, at least return to the same frame number - // (node frames; module frames) - else - ui->switchToFrame(originalPosition); - - break; - } - - // Store the info about this frameset, for future setFrames calls - this->framesetInfo = fsi; - setFastFramerate(); // Draw ASAP } +void Screen::handleStartBluetoothPinScreen(uint32_t pin) +{ + LOG_DEBUG("showing bluetooth screen\n"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame + + static FrameCallback frames[] = {drawFrameBluetooth}; + snprintf(btPIN, sizeof(btPIN), "%06u", pin); + setFrameImmediateDraw(frames); +} + void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) { ui->disableAllIndicators(); @@ -2173,6 +2194,41 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) setFastFramerate(); } +void Screen::handleShutdownScreen() +{ + LOG_DEBUG("showing shutdown screen\n"); + showingNormalScreen = false; +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) +#endif + + auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + drawFrameText(display, state, x, y, "Shutting down..."); + }; + static FrameCallback frames[] = {frame}; + + setFrameImmediateDraw(frames); +} + +void Screen::handleRebootScreen() +{ + LOG_DEBUG("showing reboot screen\n"); + showingNormalScreen = false; +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?) +#endif + + auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + drawFrameText(display, state, x, y, "Rebooting..."); + }; + static FrameCallback frames[] = {frame}; + setFrameImmediateDraw(frames); +} + void Screen::handleStartFirmwareUpdateScreen() { LOG_DEBUG("showing firmware screen\n"); @@ -2189,7 +2245,7 @@ void Screen::blink() uint8_t count = 10; dispdev->setBrightness(254); while (count > 0) { - dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight()); + dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); dispdev->display(); delay(50); dispdev->clear(); @@ -2617,7 +2673,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) switch (arg->getStatusType()) { case STATUS_TYPE_NODE: if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { - setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible) + setFrames(); // Regen the list of screens } nodeDB->updateGUI = false; break; @@ -2629,33 +2685,23 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) { if (showingNormalScreen) { - // Outgoing message - if (packet->from == 0) - setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame) - - // Incoming message - else - setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message + setFrames(); // Regen the list of screens (will show new text message) } return 0; } -// Triggered by MeshModules int Screen::handleUIFrameEvent(const UIFrameEvent *event) { if (showingNormalScreen) { - // Regenerate the frameset, potentially honoring a module's internal requestFocus() call - if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) - setFrames(FOCUS_MODULE); - - // Regenerate the frameset, while attempting to maintain focus on the current frame - else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) - setFrames(FOCUS_PRESERVE); - - // Don't regenerate the frameset, just re-draw whatever is on screen ASAP - else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) + if (event->frameChanged) { + setFrames(); // Regen the list of screens (will show new text message) + } else if (event->needRedraw) { setFastFramerate(); + // TODO: We might also want switch to corresponding frame, + // but we don't know the exact frame number. + // ui->switchToFrame(0); + } } return 0; @@ -2690,24 +2736,6 @@ int Screen::handleInputEvent(const InputEvent *event) return 0; } -int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) -{ - // Note: only selected admin messages notify this observer - // If you wish to handle a new type of message, you should modify AdminModule.cpp first - - switch (arg->which_payload_variant) { - // Node removed manually (i.e. via app) - case meshtastic_AdminMessage_remove_by_nodenum_tag: - setFrames(FOCUS_PRESERVE); - break; - - // Default no-op, in case the admin message observable gets used by other classes in future - default: - break; - } - return 0; -} - } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 93e5f2ef7..f4d719715 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -21,13 +21,11 @@ class Screen void print(const char *) {} void doDeepSleep() {} void forceDisplay(bool forceUiUpdate = false) {} + void startBluetoothPinScreen(uint32_t pin) {} + void stopBluetoothPinScreen() {} + void startRebootScreen() {} + void startShutdownScreen() {} void startFirmwareUpdateScreen() {} - void increaseBrightness() {} - void decreaseBrightness() {} - void setFunctionSymbal(std::string) {} - void removeFunctionSymbal(std::string) {} - void startAlert(const char *) {} - void endAlert() {} }; } // namespace graphics #else @@ -36,8 +34,6 @@ class Screen #include #include "../configuration.h" -#include "gps/GeoCoord.h" -#include "graphics/ScreenFonts.h" #ifdef USE_ST7567 #include @@ -45,8 +41,6 @@ class Screen #include #elif defined(USE_SSD1306) #include -#elif defined(USE_ST7789) -#include #else // the SH1106/SSD1306 variant is auto-detected #include @@ -88,46 +82,6 @@ class Screen #define SEGMENT_WIDTH 16 #define SEGMENT_HEIGHT 4 -/// Convert an integer GPS coords to a floating point -#define DegD(i) (i * 1e-7) - -namespace -{ -/// A basic 2D point class for drawing -class Point -{ - public: - float x, y; - - Point(float _x, float _y) : x(_x), y(_y) {} - - /// Apply a rotation around zero (standard rotation matrix math) - void rotate(float radian) - { - float cos = cosf(radian), sin = sinf(radian); - float rx = x * cos + y * sin, ry = -x * sin + y * cos; - - x = rx; - y = ry; - } - - void translate(int16_t dx, int dy) - { - x += dx; - y += dy; - } - - void scale(float f) - { - // We use -f here to counter the flip that happens - // on the y axis when drawing and rotating on screen - x *= f; - y *= -f; - } -}; - -} // namespace - namespace graphics { @@ -173,11 +127,9 @@ class Screen : public concurrency::OSThread CallbackObserver textMessageObserver = CallbackObserver(this, &Screen::handleTextMessage); CallbackObserver uiFrameEventObserver = - CallbackObserver(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules + CallbackObserver(this, &Screen::handleUIFrameEvent); CallbackObserver inputObserver = CallbackObserver(this, &Screen::handleInputEvent); - CallbackObserver adminMessageObserver = - CallbackObserver(this, &Screen::handleAdminMessage); public: explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); @@ -214,49 +166,20 @@ class Screen : public concurrency::OSThread void blink(); - void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *); - - void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength); - - // Draw north - void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading); - - static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight); - - float estimatedHeading(double lat, double lon); - - void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian); - - void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); - /// Handle button press, trackball or swipe action) void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); } void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); } void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); } - // generic alert start - void startAlert(FrameCallback _alertFrame) - { - alertFrame = _alertFrame; - ScreenCmd cmd; - cmd.cmd = Cmd::START_ALERT_FRAME; - enqueueCmd(cmd); - } - - void startAlert(const char *_alertMessage) - { - startAlert([_alertMessage](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - uint16_t x_offset = display->width() / 2; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, 26 + y, _alertMessage); - }); - } - - void endAlert() + /// Starts showing the Bluetooth PIN screen. + // + // Switches over to a static frame showing the Bluetooth pairing screen + // with the PIN. + void startBluetoothPinScreen(uint32_t pin) { ScreenCmd cmd; - cmd.cmd = Cmd::STOP_ALERT_FRAME; + cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN; + cmd.bluetooth_pin = pin; enqueueCmd(cmd); } @@ -267,6 +190,20 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } + void startShutdownScreen() + { + ScreenCmd cmd; + cmd.cmd = Cmd::START_SHUTDOWN_SCREEN; + enqueueCmd(cmd); + } + + void startRebootScreen() + { + ScreenCmd cmd; + cmd.cmd = Cmd::START_REBOOT_SCREEN; + enqueueCmd(cmd); + } + // Function to allow the AccelerometerThread to set the heading if a sensor provides it // Mutex needed? void setHeading(long _heading) @@ -285,6 +222,9 @@ class Screen : public concurrency::OSThread void setFunctionSymbal(std::string sym); void removeFunctionSymbal(std::string sym); + /// Stops showing the bluetooth PIN screen. + void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); } + /// Stops showing the boot screen. void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); } @@ -396,7 +336,6 @@ class Screen : public concurrency::OSThread int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); - int handleAdminMessage(const meshtastic_AdminMessage *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(bool forceUiUpdate = false); @@ -419,13 +358,7 @@ class Screen : public concurrency::OSThread bool isAUTOOled = false; - // Screen dimensions (for convenience) - // Defined during Screen::setup - uint16_t displayWidth = 0; - uint16_t displayHeight = 0; - private: - FrameCallback alertFrames[1]; struct ScreenCmd { Cmd cmd; union { @@ -451,36 +384,13 @@ class Screen : public concurrency::OSThread void handleOnPress(); void handleShowNextFrame(); void handleShowPrevFrame(); + void handleStartBluetoothPinScreen(uint32_t pin); void handlePrint(const char *text); void handleStartFirmwareUpdateScreen(); - - // Info collected by setFrames method. - // Index location of specific frames. Used to apply the FrameFocus parameter of setFrames - struct FramesetInfo { - struct FramePositions { - uint8_t fault = 0; - uint8_t textMessage = 0; - uint8_t focusedModule = 0; - uint8_t log = 0; - uint8_t settings = 0; - uint8_t wifi = 0; - } positions; - - uint8_t frameCount = 0; - } framesetInfo; - - // Which frame we want to be displayed, after we regen the frameset by calling setFrames - enum FrameFocus : uint8_t { - FOCUS_DEFAULT, // No specific frame - FOCUS_PRESERVE, // Return to the previous frame - FOCUS_FAULT, - FOCUS_TEXTMESSAGE, - FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus - }; - - // Regenerate the normal set of frames, focusing a specific frame if requested - // Call when a frame should be added / removed, or custom frames should be cleared - void setFrames(FrameFocus focus = FOCUS_DEFAULT); + void handleShutdownScreen(); + void handleRebootScreen(); + /// Rebuilds our list of frames (screens) to default ones. + void setFrames(); /// Try to start drawing ASAP void setFastFramerate(); @@ -516,9 +426,6 @@ class Screen : public concurrency::OSThread bool digitalWatchFace = true; #endif - /// callback for current alert frame - FrameCallback alertFrame; - /// Queue of commands to execute in doTask. TypedQueue cmdQueue; /// Whether we are using a display @@ -545,5 +452,4 @@ class Screen : public concurrency::OSThread }; } // namespace graphics - #endif \ No newline at end of file diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 8a48d053e..4b34563f7 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -28,8 +28,8 @@ #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #endif -#define _fontHeight(font) ((font)[1] + 1) // height is position 1 +#define fontHeight(font) ((font)[1] + 1) // height is position 1 -#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE) \ No newline at end of file +#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) diff --git a/src/main.cpp b/src/main.cpp index 95eeb998d..ddb99568d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,7 +6,6 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" -#include "PowerMon.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" @@ -49,6 +48,7 @@ NimbleBluetooth *nimbleBluetooth = nullptr; #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" NRF52Bluetooth *nrf52Bluetooth = nullptr; +; #endif #if HAS_WIFI @@ -155,7 +155,6 @@ bool isVibrating = false; bool eink_found = true; uint32_t serialSinceMsec; -bool pauseBluetoothLogging = false; bool pmu_found; @@ -174,7 +173,7 @@ const char *getDeviceName() static char name[20]; snprintf(name, sizeof(name), "%02x%02x", dmac[4], dmac[5]); // if the shortname exists and is NOT the new default of ab3c, use it for BLE name. - if (strcmp(owner.short_name, name) != 0) { + if ((owner.short_name != NULL) && (strcmp(owner.short_name, name) != 0)) { snprintf(name, sizeof(name), "%s_%02x%02x", owner.short_name, dmac[4], dmac[5]); } else { snprintf(name, sizeof(name), "Meshtastic_%02x%02x", dmac[4], dmac[5]); @@ -215,14 +214,6 @@ __attribute__((weak, noinline)) bool loopCanSleep() return true; } -/** - * Print info as a structured log message (for automated log processing) - */ -void printInfo() -{ - LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION)); -} - void setup() { concurrency::hasBeenSetup = true; @@ -230,7 +221,7 @@ void setup() meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; -#ifdef USE_SEGGER +#ifdef SEGGER_STDOUT_CH auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM; #ifdef NRF52840_XXAA auto buflen = 4096; // this board has a fair amount of ram @@ -243,7 +234,6 @@ void setup() #ifdef DEBUG_PORT consoleInit(); // Set serial baud rate and init our mesh console #endif - powerMonInit(); serialSinceMsec = millis(); @@ -275,9 +265,6 @@ void setup() digitalWrite(VEXT_ENABLE_V05, 1); // turn on the lora antenna boost digitalWrite(ST7735_BL_V05, 1); // turn on display backligth LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); -#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) - pinMode(VEXT_ENABLE, OUTPUT); - digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) pinMode(VEXT_ENABLE, OUTPUT); digitalWrite(VEXT_ENABLE, 0); // turn on the display power @@ -566,7 +553,7 @@ void setup() #endif // Hello - printInfo(); + LOG_INFO("Meshtastic hwvendor=%d, swver=%s\n", HW_VENDOR, optstr(APP_VERSION)); #ifdef ARCH_ESP32 esp32Setup(); @@ -716,8 +703,7 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ - defined(USE_ST7789) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { @@ -944,7 +930,7 @@ void setup() nodeDB->saveToDisk(SEGMENT_CONFIG); if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting\n"); - screen->startAlert("Rebooting..."); + screen->startRebootScreen(); rebootAtMsec = millis() + 5000; } } diff --git a/src/main.h b/src/main.h index ea2d80f94..2ef7edb3a 100644 --- a/src/main.h +++ b/src/main.h @@ -85,8 +85,6 @@ extern uint32_t serialSinceMsec; // This will suppress the current delay and instead try to run ASAP. extern bool runASAP; -extern bool pauseBluetoothLogging; - void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearBonds(), enterDfuMode(); meshtastic_DeviceMetadata getDeviceMetadata(); diff --git a/src/mesh/Default.h b/src/mesh/Default.h index cc3927914..95723744b 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -5,7 +5,6 @@ #define ONE_MINUTE_MS 60 * 1000 #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) -#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60) #define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) #define default_wait_bluetooth_secs IF_ROUTER(1, 60) #define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 0fdde5277..dd547a6f1 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -20,8 +20,9 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p) bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record - printPacket("Ignoring incoming msg we've already seen", p); + printPacket("Ignoring incoming msg, because we've already seen it", p); if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && + config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! Router::cancelSending(p->from, p->id); diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index fc059ec16..bffca0c44 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -184,7 +184,6 @@ template void LR11x0Interface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore - RadioLibInterface::setStandby(); } /** @@ -224,7 +223,7 @@ template void LR11x0Interface::startReceive() 0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving assert(err == RADIOLIB_ERR_NONE); - RadioLibInterface::startReceive(); + isReceiving = true; // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 1ef4f60d8..04fa250bf 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -284,17 +284,4 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const mesht } } return handled; -} - -#if HAS_SCREEN -// Would our module like its frame to be focused after Screen::setFrames has regenerated the list of frames? -// Only considered if setFrames is triggered by a UIFrameEvent -bool MeshModule::isRequestingFocus() -{ - if (_requestingFocus) { - _requestingFocus = false; // Consume the request - return true; - } else - return false; -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index c341b301a..2e2af33e0 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -35,16 +35,10 @@ enum class AdminMessageHandleResult { /* * This struct is used by Screen to figure out whether screen frame should be updated. */ -struct UIFrameEvent { - // What do we actually want to happen? - enum Action { - REDRAW_ONLY, // Don't change which frames are show, just redraw, asap - REGENERATE_FRAMESET, // Regenerate (change? add? remove?) screen frames, honoring requestFocus() - REGENERATE_FRAMESET_BACKGROUND, // Regenerate screen frames, attempting to remain on the same frame throughout - } action = REDRAW_ONLY; - - // We might want to pass additional data inside this struct at some point -}; +typedef struct _UIFrameEvent { + bool frameChanged; + bool needRedraw; +} UIFrameEvent; /** A baseclass for any mesh "module". * @@ -79,7 +73,6 @@ class MeshModule meshtastic_AdminMessage *response); #if HAS_SCREEN virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } - virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset #endif protected: const char *name; @@ -183,19 +176,6 @@ class MeshModule return AdminMessageHandleResult::NOT_HANDLED; }; -#if HAS_SCREEN - /** Request that our module's screen frame be focused when Screen::setFrames runs - * Only considered if Screen::setFrames is triggered via a UIFrameEvent - * - * Having this as a separate call, instead of part of the UIFrameEvent, allows the module to delay decision - * until drawFrame() is called. This required less restructuring. - */ - bool _requestingFocus = false; - void requestFocus() { _requestingFocus = true; } -#else - void requestFocus(){}; // No-op -#endif - private: /** * If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index a652d0a50..2cfb4843c 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -94,11 +94,7 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && nodeInfoModule) { LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); - if (airTime->isTxAllowedChannelUtil(true)) { - nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); - } else { - LOG_DEBUG("Skip sending NodeInfo due to > 25 percent channel util.\n"); - } + nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); } printPacket("Forwarding to phone", mp); @@ -273,7 +269,7 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies) assert(node); if (hasValidPosition(node)) { -#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS +#if HAS_GPS if (positionModule) { LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); positionModule->sendOurPosition(dest, wantReplies, node->channel); @@ -303,7 +299,6 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) } else { LOG_WARN("ToPhone queue is full, dropping packet.\n"); releaseToPool(p); - fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets return; } } @@ -378,8 +373,8 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) pos.time = getValidTime(RTCQualityFromNet); // In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB) - LOG_DEBUG("onGPSChanged() pos@%x time=%u lat=%d lon=%d alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, pos.longitude_i, - pos.altitude); + LOG_DEBUG("onGPSChanged() pos@%x, time=%u, lat=%d, lon=%d, alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, + pos.longitude_i, pos.altitude); // Update our current position in the local DB nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fa5c437c4..cf576e94f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -26,7 +26,7 @@ #include #ifdef ARCH_ESP32 -#if HAS_WIFI +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "modules/esp32/StoreForwardModule.h" @@ -141,6 +141,11 @@ NodeDB::NodeDB() if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) saveWhat |= SEGMENT_CHANNELS; + if (!devicestate.node_remote_hardware_pins) { + meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; + memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); + } + if (config.position.gps_enabled) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_enabled = 0; @@ -180,7 +185,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset) if (didFactoryReset) { LOG_INFO("Rebooting due to factory reset"); - screen->startAlert("Rebooting..."); + screen->startRebootScreen(); rebootAtMsec = millis() + (5 * 1000); } @@ -268,8 +273,7 @@ void NodeDB::installDefaultConfig() // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ - defined(USE_ST7789) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) bool hasScreen = true; #elif ARCH_PORTDUINO bool hasScreen = false; @@ -822,8 +826,8 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou if (src == RX_SRC_LOCAL) { // Local packet, fully authoritative - LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d\n", p.timestamp, p.time, p.latitude_i, p.longitude_i, - p.altitude); + LOG_INFO("updatePosition LOCAL pos@%x, time=%u, latI=%d, lonI=%d, alt=%d\n", p.timestamp, p.time, p.latitude_i, + p.longitude_i, p.altitude); setLocalPosition(p); info->position = TypeConversions::ConvertToPositionLite(p); @@ -838,7 +842,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou // recorded based on the packet rxTime // // FIXME perhaps handle RX_SRC_USER separately? - LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); + LOG_INFO("updatePosition REMOTE node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); // First, back up fields that we want to protect from overwrite uint32_t tmp_time = info->position.time; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 61bf90d4d..e9e36cc61 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -155,8 +155,8 @@ class NodeDB localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; return; } - LOG_DEBUG("Setting local position: lat=%i lon=%i time=%u timestamp=%u\n", position.latitude_i, position.longitude_i, - position.time, position.timestamp); + LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%u, timestamp=%u\n", position.latitude_i, + position.longitude_i, position.time, position.timestamp); localPosition = position; } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 0f69b21f9..26d0d9525 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -5,7 +5,6 @@ #include "Channels.h" #include "Default.h" -#include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" #include "PhoneAPI.h" @@ -47,9 +46,6 @@ void PhoneAPI::handleStartConfig() // even if we were already connected - restart our state machine state = STATE_SEND_MY_INFO; - pauseBluetoothLogging = true; - filesManifest = getFiles("/", 10); - LOG_DEBUG("Got %d files in manifest\n", filesManifest.size()); LOG_INFO("Starting API client config\n"); nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos @@ -152,7 +148,6 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) STATE_SEND_CONFIG, STATE_SEND_MODULE_CONFIG, STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to the client - STATE_SEND_FILEMANIFEST, STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings */ @@ -328,7 +323,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Advance when we have sent all of our ModuleConfig objects if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) { // Clients sending special nonce don't want to see other nodeinfos - state = config_nonce == SPECIAL_NONCE ? STATE_SEND_FILEMANIFEST : STATE_SEND_OTHER_NODEINFOS; + state = config_nonce == SPECIAL_NONCE ? STATE_SEND_COMPLETE_ID : STATE_SEND_OTHER_NODEINFOS; config_state = 0; } break; @@ -344,36 +339,22 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time } else { LOG_INFO("Done sending nodeinfos\n"); - state = STATE_SEND_FILEMANIFEST; + state = STATE_SEND_COMPLETE_ID; // Go ahead and send that ID right now return getFromRadio(buf); } break; } - case STATE_SEND_FILEMANIFEST: { - LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n"); - // last element - if (config_state == filesManifest.size()) { // also handles an empty filesManifest - config_state = 0; - filesManifest.clear(); - // Skip to complete packet - sendConfigComplete(); - } else { - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag; - fromRadioScratch.fileInfo = filesManifest.at(config_state); - LOG_DEBUG("File: %s (%d) bytes\n", fromRadioScratch.fileInfo.file_name, fromRadioScratch.fileInfo.size_bytes); - config_state++; - } - break; - } - case STATE_SEND_COMPLETE_ID: - sendConfigComplete(); + LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; + fromRadioScratch.config_complete_id = config_nonce; + config_nonce = 0; + state = STATE_SEND_PACKETS; break; case STATE_SEND_PACKETS: - pauseBluetoothLogging = false; // Do we have a message from the mesh or packet from the local device? LOG_INFO("getFromRadio=STATE_SEND_PACKETS\n"); if (queueStatusPacketForPhone) { @@ -407,9 +388,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Encapsulate as a FromRadio packet size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch); - // VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer - // for logging (when we are encapsulating with protobufs) - // LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes); + LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes); return numbytes; } @@ -417,20 +396,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) return 0; } -void PhoneAPI::sendConfigComplete() -{ - LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n"); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; - fromRadioScratch.config_complete_id = config_nonce; - config_nonce = 0; - state = STATE_SEND_PACKETS; - pauseBluetoothLogging = false; -} - void PhoneAPI::handleDisconnect() { - filesManifest.clear(); - pauseBluetoothLogging = false; LOG_INFO("PhoneAPI disconnect\n"); } @@ -472,7 +439,6 @@ bool PhoneAPI::available() case STATE_SEND_MODULECONFIG: case STATE_SEND_METADATA: case STATE_SEND_OWN_NODEINFO: - case STATE_SEND_FILEMANIFEST: case STATE_SEND_COMPLETE_ID: return true; @@ -487,6 +453,7 @@ bool PhoneAPI::available() } } return true; // Always say we have something, because we might need to advance our state machine + case STATE_SEND_PACKETS: { if (!queueStatusPacketForPhone) queueStatusPacketForPhone = service.getQueueStatusForPhone(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 3c3668300..49bf0e292 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -2,20 +2,10 @@ #include "Observer.h" #include "mesh-pb-constants.h" -#include #include -#include // Make sure that we never let our packets grow too large for one BLE packet #define MAX_TO_FROM_RADIO_SIZE 512 - -#if meshtastic_FromRadio_size > MAX_TO_FROM_RADIO_SIZE -#error "meshtastic_FromRadio_size is too large for our BLE packets" -#endif -#if meshtastic_ToRadio_size > MAX_TO_FROM_RADIO_SIZE -#error "meshtastic_ToRadio_size is too large for our BLE packets" -#endif - #define SPECIAL_NONCE 69420 /** @@ -39,7 +29,6 @@ class PhoneAPI STATE_SEND_CONFIG, // Replacement for the old Radioconfig STATE_SEND_MODULECONFIG, // Send Module specific config STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client - STATE_SEND_FILEMANIFEST, // Send file manifest STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings }; @@ -76,8 +65,6 @@ class PhoneAPI uint32_t config_nonce = 0; uint32_t readIndex = 0; - std::vector filesManifest = {}; - void resetReadIndex() { readIndex = 0; } public: @@ -104,8 +91,6 @@ class PhoneAPI */ size_t getFromRadio(uint8_t *buf); - void sendConfigComplete(); - /** * Return true if we have data available to send to the phone */ @@ -113,6 +98,8 @@ class PhoneAPI bool isConnected() { return state != STATE_SEND_NOTHING; } + void setInitialState() { state = STATE_SEND_MY_INFO; } + protected: /// Our fromradio packet while it is being assembled meshtastic_FromRadio fromRadioScratch = {}; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index bd1ebdb0e..c5356ad3b 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -25,8 +25,7 @@ typedef struct { } DACDB; // Interpolation function -DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) -{ +DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) { DACDB result; double fraction = (double)(dbm - dbm1) / (dbm2 - dbm1); result.dac = (uint8_t)(val1.dac + fraction * (val2.dac - val1.dac)); @@ -35,17 +34,16 @@ DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val } // Function to find the correct DAC and DB values based on dBm using interpolation -DACDB getDACandDB(uint8_t dbm) -{ +DACDB getDACandDB(uint8_t dbm) { // Predefined values static const struct { uint8_t dbm; DACDB values; } dbmToDACDB[] = { - {20, {168, 2}}, // 100mW - {24, {148, 6}}, // 250mW - {27, {128, 9}}, // 500mW - {30, {90, 12}} // 1000mW + {20, {168, 2}}, // 100mW + {24, {148, 6}}, // 250mW + {27, {128, 9}}, // 500mW + {30, {90, 12}} // 1000mW }; const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); @@ -105,7 +103,7 @@ bool RF95Interface::init() if (power > RF95_MAX_POWER) // This chip has lower power limits than some power = RF95_MAX_POWER; - + limitPower(); iface = lora = new RadioLibRF95(&module); @@ -118,13 +116,13 @@ bool RF95Interface::init() // enable PA #ifdef RF95_PA_EN #if defined(RF95_PA_DAC_EN) -#ifdef RADIOMASTER_900_BANDIT_NANO - // Use calculated DAC value - dacWrite(RF95_PA_EN, powerDAC); -#else - // Use Value set in /*/variant.h - dacWrite(RF95_PA_EN, RF95_PA_LEVEL); -#endif + #ifdef RADIOMASTER_900_BANDIT_NANO + // Use calculated DAC value + dacWrite(RF95_PA_EN, powerDAC); + #else + // Use Value set in /*/variant.h + dacWrite(RF95_PA_EN, RF95_PA_LEVEL); + #endif #endif #endif @@ -256,7 +254,6 @@ void RF95Interface::setStandby() isReceiving = false; // If we were receiving, not any more disableInterrupt(); completeSending(); // If we were sending, not anymore - RadioLibInterface::setStandby(); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 343b7f200..78228c077 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -261,6 +261,7 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d\n", snr, CWsize); if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { delay = random(0, 2 * CWsize) * slotTimeMsec; LOG_DEBUG("rx_snr found in packet. As a router, setting tx delay:%d\n", delay); @@ -521,7 +522,7 @@ void RadioInterface::applyModemConfig() LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, loraConfig.frequency_offset); LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset, channel_num, power); - LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd, + LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f mhz)\n", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart); LOG_INFO("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw); LOG_INFO("Radio channel_num: %d\n", channel_num + 1); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index f299ebff2..a4ceac9f1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -1,7 +1,6 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" #include "NodeDB.h" -#include "PowerMon.h" #include "SPILock.h" #include "configuration.h" #include "error.h" @@ -318,7 +317,6 @@ void RadioLibInterface::handleTransmitInterrupt() // ignore the transmit interrupt if (sendingPacket) completeSending(); - powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // But our transmitter is deffinitely off now } void RadioLibInterface::completeSending() @@ -414,24 +412,6 @@ void RadioLibInterface::handleReceiveInterrupt() } } -void RadioLibInterface::startReceive() -{ - isReceiving = true; - powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); -} - -void RadioLibInterface::configHardwareForSend() -{ - powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); -} - -void RadioLibInterface::setStandby() -{ - // neither sending nor receiving - powerMon->clearState(meshtastic_PowerMon_State_Lora_RXOn); - powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); -} - /** start an immediate transmit */ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) { @@ -451,7 +431,6 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) // This send failed, but make sure to 'complete' it properly completeSending(); - powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // Transmitter off now startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode) } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index dd01d2037..2c841a19e 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -126,9 +126,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified * Start waiting to receive a message * * External functions can call this method to wake the device from sleep. - * Subclasses must override and call this base method */ - virtual void startReceive(); + virtual void startReceive() = 0; /** can we detect a LoRa preamble on the current channel? */ virtual bool isChannelActive() = 0; @@ -167,9 +166,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified meshtastic_QueueStatus getQueueStatus(); protected: - /** Do any hardware setup needed on entry into send configuration for the radio. - * Subclasses can customize, but must also call this base method */ - virtual void configHardwareForSend(); + /** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */ + virtual void configHardwareForSend() {} /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately(); @@ -188,8 +186,5 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0; - /** - * Subclasses must override, implement and then call into this base class implementation - */ - virtual void setStandby(); + virtual void setStandby() = 0; }; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c8c18ae6d..3141d986b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -244,10 +244,8 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - if (!(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || - p->which_payload_variant == meshtastic_MeshPacket_decoded_tag)) { - return meshtastic_Routing_Error_BAD_REQUEST; - } + assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag || + p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index b564ba287..afaa13b7f 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -231,7 +231,6 @@ template void SX126xInterface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore - RadioLibInterface::setStandby(); } /** @@ -271,7 +270,7 @@ template void SX126xInterface::startReceive() LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); - RadioLibInterface::startReceive(); + isReceiving = true; // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index fdb2b9a39..9e4fbfa77 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -190,7 +190,6 @@ template void SX128xInterface::setStandby() activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore - RadioLibInterface::setStandby(); } /** @@ -264,7 +263,7 @@ template void SX128xInterface::startReceive() LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); - RadioLibInterface::startReceive(); + isReceiving = true; // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 9f59aa971..4d04dffe4 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -1,6 +1,5 @@ #include "StreamAPI.h" #include "PowerFSM.h" -#include "RTC.h" #include "configuration.h" #define START1 0x94 @@ -97,6 +96,7 @@ void StreamAPI::writeStream() void StreamAPI::emitTxBuffer(size_t len) { if (len != 0) { + // LOG_DEBUG("emit tx %d\n", len); txBuf[0] = START1; txBuf[1] = START2; txBuf[2] = (len >> 8) & 0xff; @@ -119,25 +119,6 @@ void StreamAPI::emitRebooted() emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch)); } -void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg) -{ - // In case we send a FromRadio packet - memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_log_record_tag; - fromRadioScratch.log_record.level = level; - - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); - fromRadioScratch.log_record.time = rtc_sec; - strncpy(fromRadioScratch.log_record.source, src, sizeof(fromRadioScratch.log_record.source) - 1); - - auto num_printed = - vsnprintf(fromRadioScratch.log_record.message, sizeof(fromRadioScratch.log_record.message) - 1, format, arg); - if (num_printed > 0 && fromRadioScratch.log_record.message[num_printed - 1] == - '\n') // Strip any ending newline, because we have records for framing instead. - fromRadioScratch.log_record.message[num_printed - 1] = '\0'; - emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch)); -} - /// Hookable to find out when connection changes void StreamAPI::onConnectionChanged(bool connected) { @@ -150,4 +131,4 @@ void StreamAPI::onConnectionChanged(bool connected) // received a packet in a while powerFSM.trigger(EVENT_SERIAL_DISCONNECTED); } -} \ No newline at end of file +} diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 45cbb231c..3196e96f8 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -82,7 +82,4 @@ class StreamAPI : public PhoneAPI /// Subclasses can use this scratch buffer if they wish uint8_t txBuf[MAX_STREAM_BUF_SIZE] = {0}; - - /// Low level function to emit a protobuf encapsulated log record - void emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg); -}; \ No newline at end of file +}; diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 9f3bb8ab7..5373f243e 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -12,8 +12,6 @@ #include #include -#if HAS_NETWORKING - #ifndef DISABLE_NTP #include @@ -185,5 +183,3 @@ bool isEthernetAvailable() return true; } } - -#endif \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index f5bacea52..ba9f90873 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 676 +#define meshtastic_ChannelSet_size 674 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 44a86f4d6..5a78f1366 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -22,6 +22,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role { The wifi radio and the oled screen will be put to sleep. This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. */ meshtastic_Config_DeviceConfig_Role_ROUTER = 2, + /* Description: Combination of both ROUTER and CLIENT. Not for mobile devices. */ meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3, /* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list. Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry @@ -371,9 +372,6 @@ typedef struct _meshtastic_Config_PowerConfig { uint32_t min_wake_secs; /* I2C address of INA_2XX to use for reading device battery voltage */ uint8_t device_battery_ina_address; - /* If non-zero, we want powermon log outputs. With the particular (bitfield) sources enabled. - Note: we picked an ID of 32 so that lower more efficient IDs can be used for more frequently used options. */ - uint64_t powermon_enables; } meshtastic_Config_PowerConfig; typedef struct _meshtastic_Config_NetworkConfig_IpV4Config { @@ -497,8 +495,6 @@ typedef struct _meshtastic_Config_LoRaConfig { Please respect your local laws and regulations. If you are a HAM, make sure you enable HAM mode and turn off encryption. */ float override_frequency; - /* If true, disable the build-in PA FAN using pin define in RF95_FAN_EN. */ - bool pa_fan_disabled; /* For testing it is useful sometimes to force a node to never listen to particular other nodes (simulating radio out of range). All nodenums listed in ignore_incoming will have packets they send dropped on receive (by router.cpp) */ @@ -616,20 +612,20 @@ extern "C" { #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} -#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} -#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} /* Field tags (for use in manual encoding/decoding) */ @@ -666,7 +662,6 @@ extern "C" { #define meshtastic_Config_PowerConfig_ls_secs_tag 7 #define meshtastic_Config_PowerConfig_min_wake_secs_tag 8 #define meshtastic_Config_PowerConfig_device_battery_ina_address_tag 9 -#define meshtastic_Config_PowerConfig_powermon_enables_tag 32 #define meshtastic_Config_NetworkConfig_IpV4Config_ip_tag 1 #define meshtastic_Config_NetworkConfig_IpV4Config_gateway_tag 2 #define meshtastic_Config_NetworkConfig_IpV4Config_subnet_tag 3 @@ -704,7 +699,6 @@ extern "C" { #define meshtastic_Config_LoRaConfig_override_duty_cycle_tag 12 #define meshtastic_Config_LoRaConfig_sx126x_rx_boosted_gain_tag 13 #define meshtastic_Config_LoRaConfig_override_frequency_tag 14 -#define meshtastic_Config_LoRaConfig_pa_fan_disabled_tag 15 #define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103 #define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104 #define meshtastic_Config_BluetoothConfig_enabled_tag 1 @@ -779,8 +773,7 @@ X(a, STATIC, SINGULAR, UINT32, wait_bluetooth_secs, 4) \ X(a, STATIC, SINGULAR, UINT32, sds_secs, 6) \ X(a, STATIC, SINGULAR, UINT32, ls_secs, 7) \ X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 8) \ -X(a, STATIC, SINGULAR, UINT32, device_battery_ina_address, 9) \ -X(a, STATIC, SINGULAR, UINT64, powermon_enables, 32) +X(a, STATIC, SINGULAR, UINT32, device_battery_ina_address, 9) #define meshtastic_Config_PowerConfig_CALLBACK NULL #define meshtastic_Config_PowerConfig_DEFAULT NULL @@ -835,7 +828,6 @@ X(a, STATIC, SINGULAR, UINT32, channel_num, 11) \ X(a, STATIC, SINGULAR, BOOL, override_duty_cycle, 12) \ X(a, STATIC, SINGULAR, BOOL, sx126x_rx_boosted_gain, 13) \ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \ -X(a, STATIC, SINGULAR, BOOL, pa_fan_disabled, 15) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_LoRaConfig_CALLBACK NULL @@ -875,11 +867,11 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_BluetoothConfig_size 12 #define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 30 -#define meshtastic_Config_LoRaConfig_size 82 +#define meshtastic_Config_LoRaConfig_size 80 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 -#define meshtastic_Config_PowerConfig_size 52 +#define meshtastic_Config_PowerConfig_size 40 #define meshtastic_Config_size 199 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index eb37f4f95..5e291ee94 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -8,6 +8,7 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" +#include "meshtastic/module_config.pb.h" #include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -307,7 +308,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3388 +#define meshtastic_OEMStore_size 3372 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 983f48ad3..96a9976f0 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,8 +181,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 555 -#define meshtastic_LocalModuleConfig_size 687 +#define meshtastic_LocalConfig_size 541 +#define meshtastic_LocalModuleConfig_size 685 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 3fa81e131..46d59d609 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -36,7 +36,7 @@ PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, AUTO) PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO) -PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, 2) +PB_BIND(meshtastic_LogRecord, meshtastic_LogRecord, AUTO) PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) @@ -45,9 +45,6 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2) -PB_BIND(meshtastic_FileInfo, meshtastic_FileInfo, AUTO) - - PB_BIND(meshtastic_ToRadio, meshtastic_ToRadio, 2) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index dbe9281ec..064115815 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -167,15 +167,6 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64, /* Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors */ meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65, - /* Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display */ - meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 = 66, - /* Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display */ - meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 = 67, - /* Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display */ - meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 = 68, - /* Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design, - specifically adapted for the Meshtatic project */ - meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -700,11 +691,11 @@ typedef struct _meshtastic_MyNodeInfo { and then extend as needed by emitting multiple records. */ typedef struct _meshtastic_LogRecord { /* Log levels, chosen to match python logging conventions. */ - char message[384]; + char message[64]; /* Seconds since 1970 - or 0 for unknown/unset */ uint32_t time; /* Usually based on thread name - if known */ - char source[32]; + char source[8]; /* Not yet set */ meshtastic_LogRecord_Level level; } meshtastic_LogRecord; @@ -720,14 +711,6 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; -/* Individual File info for the device */ -typedef struct _meshtastic_FileInfo { - /* The fully qualified path of the file */ - char file_name[228]; - /* The size of the file in bytes */ - uint32_t size_bytes; -} meshtastic_FileInfo; - typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t; /* Compressed message payload */ typedef struct _meshtastic_Compressed { @@ -832,8 +815,6 @@ typedef struct _meshtastic_FromRadio { meshtastic_DeviceMetadata metadata; /* MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT) */ meshtastic_MqttClientProxyMessage mqttClientProxyMessage; - /* File system manifest messages */ - meshtastic_FileInfo fileInfo; }; } meshtastic_FromRadio; @@ -977,7 +958,6 @@ extern "C" { - #define meshtastic_Compressed_portnum_ENUMTYPE meshtastic_PortNum @@ -1005,7 +985,6 @@ extern "C" { #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} -#define meshtastic_FileInfo_init_default {"", 0} #define meshtastic_ToRadio_init_default {0, {meshtastic_MeshPacket_init_default}} #define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} @@ -1029,7 +1008,6 @@ extern "C" { #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} -#define meshtastic_FileInfo_init_zero {"", 0} #define meshtastic_ToRadio_init_zero {0, {meshtastic_MeshPacket_init_zero}} #define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} @@ -1132,8 +1110,6 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 -#define meshtastic_FileInfo_file_name_tag 1 -#define meshtastic_FileInfo_size_bytes_tag 2 #define meshtastic_Compressed_portnum_tag 1 #define meshtastic_Compressed_data_tag 2 #define meshtastic_Neighbor_node_id_tag 1 @@ -1168,7 +1144,6 @@ extern "C" { #define meshtastic_FromRadio_xmodemPacket_tag 12 #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 -#define meshtastic_FromRadio_fileInfo_tag 15 #define meshtastic_ToRadio_packet_tag 1 #define meshtastic_ToRadio_want_config_id_tag 3 #define meshtastic_ToRadio_disconnect_tag 4 @@ -1346,8 +1321,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,channel,channel), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,queueStatus,queueStatus), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) #define meshtastic_FromRadio_CALLBACK NULL #define meshtastic_FromRadio_DEFAULT NULL #define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket @@ -1361,13 +1335,6 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) #define meshtastic_FromRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem #define meshtastic_FromRadio_payload_variant_metadata_MSGTYPE meshtastic_DeviceMetadata #define meshtastic_FromRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage -#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo - -#define meshtastic_FileInfo_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, STRING, file_name, 1) \ -X(a, STATIC, SINGULAR, UINT32, size_bytes, 2) -#define meshtastic_FileInfo_CALLBACK NULL -#define meshtastic_FileInfo_DEFAULT NULL #define meshtastic_ToRadio_FIELDLIST(X, a) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \ @@ -1467,7 +1434,6 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg; extern const pb_msgdesc_t meshtastic_LogRecord_msg; extern const pb_msgdesc_t meshtastic_QueueStatus_msg; extern const pb_msgdesc_t meshtastic_FromRadio_msg; -extern const pb_msgdesc_t meshtastic_FileInfo_msg; extern const pb_msgdesc_t meshtastic_ToRadio_msg; extern const pb_msgdesc_t meshtastic_Compressed_msg; extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; @@ -1493,7 +1459,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg #define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg #define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg -#define meshtastic_FileInfo_fields &meshtastic_FileInfo_msg #define meshtastic_ToRadio_fields &meshtastic_ToRadio_msg #define meshtastic_Compressed_fields &meshtastic_Compressed_msg #define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg @@ -1513,10 +1478,9 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 -#define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 -#define meshtastic_LogRecord_size 426 +#define meshtastic_LogRecord_size 81 #define meshtastic_MeshPacket_size 326 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 7fd57fe00..f3c48ee6d 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -60,9 +60,7 @@ typedef enum _meshtastic_ModuleConfig_SerialConfig_Serial_Mode { meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG = 3, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA = 4, /* NMEA messages specifically tailored for CalTopo */ - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO = 5, - /* Ecowitt WS85 weather station */ - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 = 6 + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO = 5 } meshtastic_ModuleConfig_SerialConfig_Serial_Mode; /* TODO: REPLACE */ @@ -275,8 +273,6 @@ typedef struct _meshtastic_ModuleConfig_StoreForwardConfig { uint32_t history_return_max; /* TODO: REPLACE */ uint32_t history_return_window; - /* Set to true to let this node act as a server that stores received messages and resends them upon request. */ - bool is_server; } meshtastic_ModuleConfig_StoreForwardConfig; /* Preferences for the RangeTestModule */ @@ -436,8 +432,8 @@ extern "C" { #define _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Baud)(meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_921600+1)) #define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT -#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 -#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85+1)) +#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO +#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO+1)) #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MAX meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK @@ -478,7 +474,7 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} @@ -494,7 +490,7 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} @@ -564,7 +560,6 @@ extern "C" { #define meshtastic_ModuleConfig_StoreForwardConfig_records_tag 3 #define meshtastic_ModuleConfig_StoreForwardConfig_history_return_max_tag 4 #define meshtastic_ModuleConfig_StoreForwardConfig_history_return_window_tag 5 -#define meshtastic_ModuleConfig_StoreForwardConfig_is_server_tag 6 #define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1 #define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2 #define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3 @@ -748,8 +743,7 @@ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, BOOL, heartbeat, 2) \ X(a, STATIC, SINGULAR, UINT32, records, 3) \ X(a, STATIC, SINGULAR, UINT32, history_return_max, 4) \ -X(a, STATIC, SINGULAR, UINT32, history_return_window, 5) \ -X(a, STATIC, SINGULAR, BOOL, is_server, 6) +X(a, STATIC, SINGULAR, UINT32, history_return_window, 5) #define meshtastic_ModuleConfig_StoreForwardConfig_CALLBACK NULL #define meshtastic_ModuleConfig_StoreForwardConfig_DEFAULT NULL @@ -854,7 +848,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RangeTestConfig_size 10 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 -#define meshtastic_ModuleConfig_StoreForwardConfig_size 24 +#define meshtastic_ModuleConfig_StoreForwardConfig_size 22 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 #define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 6cc82352a..233e8d653 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -124,8 +124,6 @@ typedef enum _meshtastic_PortNum { meshtastic_PortNum_ATAK_PLUGIN = 72, /* Provides unencrypted information about a node for consumption by a map via MQTT */ meshtastic_PortNum_MAP_REPORT_APP = 73, - /* PowerStress based monitoring support (for automated power consumption testing) */ - meshtastic_PortNum_POWERSTRESS_APP = 74, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ diff --git a/src/mesh/generated/meshtastic/powermon.pb.cpp b/src/mesh/generated/meshtastic/powermon.pb.cpp deleted file mode 100644 index ce41ea021..000000000 --- a/src/mesh/generated/meshtastic/powermon.pb.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ - -#include "meshtastic/powermon.pb.h" -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -PB_BIND(meshtastic_PowerMon, meshtastic_PowerMon, AUTO) - - -PB_BIND(meshtastic_PowerStressMessage, meshtastic_PowerStressMessage, AUTO) - - - - - diff --git a/src/mesh/generated/meshtastic/powermon.pb.h b/src/mesh/generated/meshtastic/powermon.pb.h deleted file mode 100644 index 7de0618e9..000000000 --- a/src/mesh/generated/meshtastic/powermon.pb.h +++ /dev/null @@ -1,138 +0,0 @@ -/* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ - -#ifndef PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED -#define PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED -#include - -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -/* Enum definitions */ -/* Any significant power changing event in meshtastic should be tagged with a powermon state transition. -If you are making new meshtastic features feel free to add new entries at the end of this definition. */ -typedef enum _meshtastic_PowerMon_State { - meshtastic_PowerMon_State_None = 0, - meshtastic_PowerMon_State_CPU_DeepSleep = 1, - meshtastic_PowerMon_State_CPU_LightSleep = 2, - /* The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only -occasionally. In cases where that rail has multiple devices on it we usually want to have logging on -the state of that rail as an independent record. -For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen. - -The log messages will be short and complete (see PowerMon.Event in the protobufs for details). -something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states. -(We use a bitmask for states so that if a log message gets lost it won't be fatal) */ - meshtastic_PowerMon_State_Vext1_On = 4, - meshtastic_PowerMon_State_Lora_RXOn = 8, - meshtastic_PowerMon_State_Lora_TXOn = 16, - meshtastic_PowerMon_State_Lora_RXActive = 32, - meshtastic_PowerMon_State_BT_On = 64, - meshtastic_PowerMon_State_LED_On = 128, - meshtastic_PowerMon_State_Screen_On = 256, - meshtastic_PowerMon_State_Screen_Drawing = 512, - meshtastic_PowerMon_State_Wifi_On = 1024, - /* GPS is actively trying to find our location -See GPSPowerState for more details */ - meshtastic_PowerMon_State_GPS_Active = 2048 -} meshtastic_PowerMon_State; - -/* What operation would we like the UUT to perform. -note: senders should probably set want_response in their request packets, so that they can know when the state -machine has started processing their request */ -typedef enum _meshtastic_PowerStressMessage_Opcode { - /* Unset/unused */ - meshtastic_PowerStressMessage_Opcode_UNSET = 0, - meshtastic_PowerStressMessage_Opcode_PRINT_INFO = 1, /* Print board version slog and send an ack that we are alive and ready to process commands */ - meshtastic_PowerStressMessage_Opcode_FORCE_QUIET = 2, /* Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation) */ - meshtastic_PowerStressMessage_Opcode_END_QUIET = 3, /* Stop powerstress processing - probably by just rebooting the board */ - meshtastic_PowerStressMessage_Opcode_SCREEN_ON = 16, /* Turn the screen on */ - meshtastic_PowerStressMessage_Opcode_SCREEN_OFF = 17, /* Turn the screen off */ - meshtastic_PowerStressMessage_Opcode_CPU_IDLE = 32, /* Let the CPU run but we assume mostly idling for num_seconds */ - meshtastic_PowerStressMessage_Opcode_CPU_DEEPSLEEP = 33, /* Force deep sleep for FIXME seconds */ - meshtastic_PowerStressMessage_Opcode_CPU_FULLON = 34, /* Spin the CPU as fast as possible for num_seconds */ - meshtastic_PowerStressMessage_Opcode_LED_ON = 48, /* Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes) */ - meshtastic_PowerStressMessage_Opcode_LED_OFF = 49, /* Force the LED off for num_seconds */ - meshtastic_PowerStressMessage_Opcode_LORA_OFF = 64, /* Completely turn off the LORA radio for num_seconds */ - meshtastic_PowerStressMessage_Opcode_LORA_TX = 65, /* Send Lora packets for num_seconds */ - meshtastic_PowerStressMessage_Opcode_LORA_RX = 66, /* Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel) */ - meshtastic_PowerStressMessage_Opcode_BT_OFF = 80, /* Turn off the BT radio for num_seconds */ - meshtastic_PowerStressMessage_Opcode_BT_ON = 81, /* Turn on the BT radio for num_seconds */ - meshtastic_PowerStressMessage_Opcode_WIFI_OFF = 96, /* Turn off the WIFI radio for num_seconds */ - meshtastic_PowerStressMessage_Opcode_WIFI_ON = 97, /* Turn on the WIFI radio for num_seconds */ - meshtastic_PowerStressMessage_Opcode_GPS_OFF = 112, /* Turn off the GPS radio for num_seconds */ - meshtastic_PowerStressMessage_Opcode_GPS_ON = 113 /* Turn on the GPS radio for num_seconds */ -} meshtastic_PowerStressMessage_Opcode; - -/* Struct definitions */ -/* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). -But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */ -typedef struct _meshtastic_PowerMon { - char dummy_field; -} meshtastic_PowerMon; - -/* PowerStress testing support via the C++ PowerStress module */ -typedef struct _meshtastic_PowerStressMessage { - /* What type of HardwareMessage is this? */ - meshtastic_PowerStressMessage_Opcode cmd; - float num_seconds; -} meshtastic_PowerStressMessage; - - -#ifdef __cplusplus -extern "C" { -#endif - -/* Helper constants for enums */ -#define _meshtastic_PowerMon_State_MIN meshtastic_PowerMon_State_None -#define _meshtastic_PowerMon_State_MAX meshtastic_PowerMon_State_GPS_Active -#define _meshtastic_PowerMon_State_ARRAYSIZE ((meshtastic_PowerMon_State)(meshtastic_PowerMon_State_GPS_Active+1)) - -#define _meshtastic_PowerStressMessage_Opcode_MIN meshtastic_PowerStressMessage_Opcode_UNSET -#define _meshtastic_PowerStressMessage_Opcode_MAX meshtastic_PowerStressMessage_Opcode_GPS_ON -#define _meshtastic_PowerStressMessage_Opcode_ARRAYSIZE ((meshtastic_PowerStressMessage_Opcode)(meshtastic_PowerStressMessage_Opcode_GPS_ON+1)) - - -#define meshtastic_PowerStressMessage_cmd_ENUMTYPE meshtastic_PowerStressMessage_Opcode - - -/* Initializer values for message structs */ -#define meshtastic_PowerMon_init_default {0} -#define meshtastic_PowerStressMessage_init_default {_meshtastic_PowerStressMessage_Opcode_MIN, 0} -#define meshtastic_PowerMon_init_zero {0} -#define meshtastic_PowerStressMessage_init_zero {_meshtastic_PowerStressMessage_Opcode_MIN, 0} - -/* Field tags (for use in manual encoding/decoding) */ -#define meshtastic_PowerStressMessage_cmd_tag 1 -#define meshtastic_PowerStressMessage_num_seconds_tag 2 - -/* Struct field encoding specification for nanopb */ -#define meshtastic_PowerMon_FIELDLIST(X, a) \ - -#define meshtastic_PowerMon_CALLBACK NULL -#define meshtastic_PowerMon_DEFAULT NULL - -#define meshtastic_PowerStressMessage_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, cmd, 1) \ -X(a, STATIC, SINGULAR, FLOAT, num_seconds, 2) -#define meshtastic_PowerStressMessage_CALLBACK NULL -#define meshtastic_PowerStressMessage_DEFAULT NULL - -extern const pb_msgdesc_t meshtastic_PowerMon_msg; -extern const pb_msgdesc_t meshtastic_PowerStressMessage_msg; - -/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define meshtastic_PowerMon_fields &meshtastic_PowerMon_msg -#define meshtastic_PowerStressMessage_fields &meshtastic_PowerStressMessage_msg - -/* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_POWERMON_PB_H_MAX_SIZE meshtastic_PowerStressMessage_size -#define meshtastic_PowerMon_size 0 -#define meshtastic_PowerStressMessage_size 7 - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 82cd0a55d..28d368754 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -115,10 +115,6 @@ typedef struct _meshtastic_EnvironmentMetrics { float wind_speed; /* Weight in KG */ float weight; - /* Wind gust in m/s */ - float wind_gust; - /* Wind lull in m/s */ - float wind_lull; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -209,13 +205,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -242,8 +238,6 @@ extern "C" { #define meshtastic_EnvironmentMetrics_wind_direction_tag 13 #define meshtastic_EnvironmentMetrics_wind_speed_tag 14 #define meshtastic_EnvironmentMetrics_weight_tag 15 -#define meshtastic_EnvironmentMetrics_wind_gust_tag 16 -#define meshtastic_EnvironmentMetrics_wind_lull_tag 17 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -295,9 +289,7 @@ X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \ -X(a, STATIC, SINGULAR, FLOAT, weight, 15) \ -X(a, STATIC, SINGULAR, FLOAT, wind_gust, 16) \ -X(a, STATIC, SINGULAR, FLOAT, wind_lull, 17) +X(a, STATIC, SINGULAR, FLOAT, weight, 15) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -365,10 +357,10 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 85 +#define meshtastic_EnvironmentMetrics_size 73 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 -#define meshtastic_Telemetry_size 92 +#define meshtastic_Telemetry_size 80 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index b309484e2..7f9df058d 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -6,7 +6,7 @@ #include "main.h" #include "mesh/http/ContentHelper.h" #include "mesh/http/WebServer.h" -#if HAS_WIFI +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "mqtt/JSON.h" diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index e733d1801..ffb16bd3e 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -1,5 +1,5 @@ #include "configuration.h" -#if HAS_WIFI +#if !MESHTASTIC_EXCLUDE_WIFI #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index cab63e559..091586462 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -137,7 +137,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH if (BleOta::getOtaAppVersion().isEmpty()) { LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); - screen->startAlert("Rebooting..."); + screen->startRebootScreen(); } else { screen->startFirmwareUpdateScreen(); BleOta::switchToOtaApp(); @@ -145,7 +145,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } #else LOG_INFO("Not on ESP32, scheduling regular reboot in %d seconds\n", s); - screen->startAlert("Rebooting..."); + screen->startRebootScreen(); #endif rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); break; @@ -200,7 +200,6 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta case meshtastic_AdminMessage_remove_by_nodenum_tag: { LOG_INFO("Client is receiving a remove_nodenum command.\n"); nodeDB->removeNodeByNum(r->remove_by_nodenum); - this->notifyObservers(r); // Observed by screen break; } case meshtastic_AdminMessage_set_favorite_node_tag: { @@ -233,9 +232,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) gps->enable(); +#endif // Send our new fixed position to the mesh for good measure positionModule->sendOurPosition(); -#endif } break; } @@ -300,8 +299,8 @@ void AdminModule::handleGetModuleConfigResponse(const meshtastic_MeshPacket &mp, { // Skip if it's disabled or no pins are exposed if (!r->get_module_config_response.payload_variant.remote_hardware.enabled || - r->get_module_config_response.payload_variant.remote_hardware.available_pins_count == 0) { - LOG_DEBUG("Remote hardware module disabled or no available_pins. Skipping...\n"); + !r->get_module_config_response.payload_variant.remote_hardware.available_pins) { + LOG_DEBUG("Remote hardware module disabled or no vailable_pins. Skipping...\n"); return; } for (uint8_t i = 0; i < devicestate.node_remote_hardware_pins_count; i++) { @@ -389,10 +388,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs); config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; } - // Router Client is deprecated; Set it to client - if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) { - config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; - } break; case meshtastic_Config_position_tag: LOG_INFO("Setting config: Position\n"); @@ -816,7 +811,7 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch void AdminModule::reboot(int32_t seconds) { LOG_INFO("Rebooting in %d seconds\n", seconds); - screen->startAlert("Rebooting..."); + screen->startRebootScreen(); rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000); } diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index a5ffeb7d6..32b32c253 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,13 +1,13 @@ #pragma once #include "ProtobufModule.h" -#if HAS_WIFI +#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif /** * Admin module for admin messages */ -class AdminModule : public ProtobufModule, public Observable +class AdminModule : public ProtobufModule { public: /** Constructor diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 84b5a3260..f513e045f 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -148,9 +148,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (this->currentMessageIndex == 0) { this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + UIFrameEvent e = {false, true}; + e.frameChanged = true; this->notifyObservers(&e); return 0; @@ -167,8 +166,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + UIFrameEvent e = {false, true}; + e.frameChanged = true; this->currentMessageIndex = -1; #if !defined(T_WATCH_S3) && !defined(RAK14014) @@ -354,8 +353,6 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } if (validEvent) { - requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs - // Let runOnce to be called immediately. if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { setIntervalFromNow(0); // on fast keypresses, this isn't fast enough. @@ -381,11 +378,6 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha p->decoded.payload.size++; } - // Only receive routing messages when expecting ACK for a canned message - // Prevents the canned message module from regenerating the screen's frameset at unexpected times, - // or raising a UIFrameEvent before another module has the chance - this->waitingForAck = true; - LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); service.sendToMesh( @@ -401,13 +393,13 @@ int32_t CannedMessageModule::runOnce() return INT32_MAX; } // LOG_DEBUG("Check status\n"); - UIFrameEvent e; + UIFrameEvent e = {false, true}; if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE)) { // TODO: might have some feedback of sending state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; temporaryMessage = ""; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -420,7 +412,7 @@ int32_t CannedMessageModule::runOnce() } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { // Reset module - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -457,7 +449,7 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; @@ -471,7 +463,7 @@ int32_t CannedMessageModule::runOnce() } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { this->currentMessageIndex = 0; LOG_DEBUG("First touch (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) { if (this->messagesCount > 0) { @@ -575,7 +567,7 @@ int32_t CannedMessageModule::runOnce() break; } if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the // display back to the default window case 0x08: // backspace @@ -605,14 +597,14 @@ int32_t CannedMessageModule::runOnce() // handle fn+s for shutdown case 0x9b: if (screen) - screen->startAlert("Shutting down..."); + screen->startShutdownScreen(); shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; // and fn+r for reboot case 0x90: if (screen) - screen->startAlert("Rebooting..."); + screen->startRebootScreen(); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; @@ -714,8 +706,8 @@ int CannedMessageModule::getPrevIndex() void CannedMessageModule::showTemporaryMessage(const String &message) { temporaryMessage = message; - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + UIFrameEvent e = {false, true}; + e.frameChanged = true; notifyObservers(&e); runState = CANNED_MESSAGE_RUN_STATE_MESSAGE; // run this loop again in 2 seconds, next iteration will clear the display @@ -922,13 +914,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st char buffer[50]; if (temporaryMessage.length() != 0) { - requestFocus(); // Tell Screen::setFrames to move to our module's frame LOG_DEBUG("Drawing temporary message: %s", temporaryMessage.c_str()); display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { - requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); String displayString; @@ -950,7 +940,6 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi); } } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { - requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending..."); @@ -959,7 +948,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setFont(FONT_SMALL); display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled."); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - requestFocus(); // Tell Screen::setFrames to move to our module's frame + #if defined(T_WATCH_S3) || defined(RAK14014) drawKeyboard(display, state, 0, 0); #else @@ -1041,18 +1030,16 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &mp) { - if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP && waitingForAck) { + if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP) { // look for a request_id if (mp.decoded.request_id != 0) { - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen - requestFocus(); // Tell Screen::setFrames that our module's frame should be shown, even if not "first" in the frameset + UIFrameEvent e = {false, true}; + e.frameChanged = true; this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED; this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id); meshtastic_Routing decoded = meshtastic_Routing_init_default; pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; - waitingForAck = false; // No longer want routing packets this->notifyObservers(&e); // run the next time 2 seconds later setIntervalFromNow(2000); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 797b9f7cf..00e8c2bf9 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -81,8 +81,9 @@ class CannedMessageModule : public SinglePortModule, public Observabledecoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: case meshtastic_PortNum_ROUTING_APP: - return waitingForAck; + return true; default: return false; } @@ -139,8 +140,7 @@ class CannedMessageModule : public SinglePortModule, public Observable, private concurrency::OSThread -{ - meshtastic_PowerStressMessage currentMessage = meshtastic_PowerStressMessage_init_default; - bool isRunningCommand = false; - - public: - /** Constructor - * name is for debugging output - */ - PowerStressModule(); - - protected: - /** Called to handle a particular incoming message - - @return true if you've guaranteed you've handled this message and no other handlers should be considered for it - */ - virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_PowerStressMessage *p) override; - - /** - * Periodically read the gpios we have been asked to WATCH, if they have changed, - * broadcast a message with the change information. - * - * The method that will be called each time our thread gets a chance to run - * - * Returns desired period for next invocation (or RUN_SAME for no change) - */ - virtual int32_t runOnce() override; -}; - -extern PowerStressModule powerStressModule; \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 43b0ac46c..4f5fbcd13 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -47,8 +47,7 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); @@ -86,90 +85,53 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack return false; // Let others look at this message also if they want } -bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) +bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { if (!aqi.read(&data)) { LOG_WARN("Skipping send measurements. Could not read AQIn\n"); return false; } - m->time = getTime(); - m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; - m->variant.air_quality_metrics.pm10_standard = data.pm10_standard; - m->variant.air_quality_metrics.pm25_standard = data.pm25_standard; - m->variant.air_quality_metrics.pm100_standard = data.pm100_standard; + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + m.time = getTime(); + m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; + m.variant.air_quality_metrics.pm10_standard = data.pm10_standard; + m.variant.air_quality_metrics.pm25_standard = data.pm25_standard; + m.variant.air_quality_metrics.pm100_standard = data.pm100_standard; - m->variant.air_quality_metrics.pm10_environmental = data.pm10_env; - m->variant.air_quality_metrics.pm25_environmental = data.pm25_env; - m->variant.air_quality_metrics.pm100_environmental = data.pm100_env; + m.variant.air_quality_metrics.pm10_environmental = data.pm10_env; + m.variant.air_quality_metrics.pm25_environmental = data.pm25_env; + m.variant.air_quality_metrics.pm100_environmental = data.pm100_env; LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i\n", - m->variant.air_quality_metrics.pm10_standard, m->variant.air_quality_metrics.pm25_standard, - m->variant.air_quality_metrics.pm100_standard); + m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, + m.variant.air_quality_metrics.pm100_standard); LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i\n", - m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental, - m->variant.air_quality_metrics.pm100_environmental); + m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental, + m.variant.air_quality_metrics.pm100_environmental); + meshtastic_MeshPacket *p = allocDataProtobuf(m); + p->to = dest; + p->decoded.want_response = false; + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + else + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + + // release previous packet before occupying a new spot + if (lastMeasurementPacket != nullptr) + packetPool.release(lastMeasurementPacket); + + lastMeasurementPacket = packetPool.allocCopy(*p); + if (phoneOnly) { + LOG_INFO("Sending packet to phone\n"); + service.sendToPhone(p); + } else { + LOG_INFO("Sending packet to mesh\n"); + service.sendToMesh(p, RX_SRC_LOCAL, true); + } return true; } -meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() -{ - if (currentRequest) { - auto req = *currentRequest; - const auto &p = req.decoded; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - } else { - LOG_ERROR("Error decoding AirQualityTelemetry module!\n"); - return NULL; - } - // Check for a request for air quality metrics - if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - if (getAirQualityTelemetry(&m)) { - LOG_INFO("Air quality telemetry replying to request\n"); - return allocDataProtobuf(m); - } else { - return NULL; - } - } - } - return NULL; -} - -bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) -{ - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - if (getAirQualityTelemetry(&m)) { - meshtastic_MeshPacket *p = allocDataProtobuf(m); - p->to = dest; - p->decoded.want_response = false; - if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) - p->priority = meshtastic_MeshPacket_Priority_RELIABLE; - else - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - - // release previous packet before occupying a new spot - if (lastMeasurementPacket != nullptr) - packetPool.release(lastMeasurementPacket); - - lastMeasurementPacket = packetPool.allocCopy(*p); - if (phoneOnly) { - LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); - } else { - LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); - } - return true; - } - - return false; -} - #endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 9d09078b1..eb0355001 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -26,11 +26,6 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; - /** Called to get current Air Quality data - @return true if it contains valid data - */ - bool getAirQualityTelemetry(meshtastic_Telemetry *m); - virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 9fe679b41..b64e8d113 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -17,8 +17,7 @@ int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); if (((lastSentToMesh == 0) || - ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval, - default_telemetry_broadcast_interval_secs))) && + ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { @@ -53,27 +52,14 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() { - if (currentRequest) { - auto req = *currentRequest; - const auto &p = req.decoded; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - } else { - LOG_ERROR("Error decoding DeviceTelemetry module!\n"); - return NULL; - } - // Check for a request for device metrics - if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { - LOG_INFO("Device telemetry replying to request\n"); - - meshtastic_Telemetry telemetry = getDeviceTelemetry(); - return allocDataProtobuf(telemetry); - } + if (ignoreRequest) { + return NULL; } - return NULL; + + LOG_INFO("Device telemetry replying to request\n"); + + meshtastic_Telemetry telemetry = getDeviceTelemetry(); + return allocDataProtobuf(telemetry); } meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() @@ -118,4 +104,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) service.sendToMesh(p, RX_SRC_LOCAL, true); } return true; -} \ No newline at end of file +} diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 9f0dc7b79..b1149799b 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -69,8 +69,7 @@ int32_t EnvironmentTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -125,8 +124,6 @@ int32_t EnvironmentTelemetryModule::runOnce() result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); - if (ina3221Sensor.hasSensor()) - result = ina3221Sensor.runOnce(); if (veml7700Sensor.hasSensor()) result = veml7700Sensor.runOnce(); if (tsl2591Sensor.hasSensor()) @@ -155,8 +152,7 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); @@ -202,7 +198,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (lastMeasurementPacket == nullptr) { // If there's no valid packet, display "Environment" display->drawString(x, y, "Environment"); - display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement"); + display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement"); return; } @@ -227,31 +223,31 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt } // Continue with the remaining details - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Temp/Hum: " + last_temp + " / " + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%"); if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); } if (lastMeasurement.variant.environment_metrics.voltage != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); } if (lastMeasurement.variant.environment_metrics.iaq != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); } if (lastMeasurement.variant.environment_metrics.distance != 0) - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); if (lastMeasurement.variant.environment_metrics.weight != 0) - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); } @@ -284,142 +280,102 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac return false; // Let others look at this message also if they want } -bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m) +bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; bool valid = true; bool hasSensor = false; - m->time = getTime(); - m->which_variant = meshtastic_Telemetry_environment_metrics_tag; + m.time = getTime(); + m.which_variant = meshtastic_Telemetry_environment_metrics_tag; #ifdef T1000X_SENSOR_EN // add by WayenWeng valid = valid && t1000xSensor.getMetrics(&m); hasSensor = true; #else if (dfRobotLarkSensor.hasSensor()) { - valid = valid && dfRobotLarkSensor.getMetrics(m); + valid = valid && dfRobotLarkSensor.getMetrics(&m); hasSensor = true; } if (sht31Sensor.hasSensor()) { - valid = valid && sht31Sensor.getMetrics(m); - hasSensor = true; - } - if (sht4xSensor.hasSensor()) { - valid = valid && sht4xSensor.getMetrics(m); + valid = valid && sht31Sensor.getMetrics(&m); hasSensor = true; } if (lps22hbSensor.hasSensor()) { - valid = valid && lps22hbSensor.getMetrics(m); + valid = valid && lps22hbSensor.getMetrics(&m); hasSensor = true; } if (shtc3Sensor.hasSensor()) { - valid = valid && shtc3Sensor.getMetrics(m); + valid = valid && shtc3Sensor.getMetrics(&m); hasSensor = true; } if (bmp085Sensor.hasSensor()) { - valid = valid && bmp085Sensor.getMetrics(m); + valid = valid && bmp085Sensor.getMetrics(&m); hasSensor = true; } if (bmp280Sensor.hasSensor()) { - valid = valid && bmp280Sensor.getMetrics(m); + valid = valid && bmp280Sensor.getMetrics(&m); hasSensor = true; } if (bme280Sensor.hasSensor()) { - valid = valid && bme280Sensor.getMetrics(m); + valid = valid && bme280Sensor.getMetrics(&m); hasSensor = true; } if (bme680Sensor.hasSensor()) { - valid = valid && bme680Sensor.getMetrics(m); + valid = valid && bme680Sensor.getMetrics(&m); hasSensor = true; } if (mcp9808Sensor.hasSensor()) { - valid = valid && mcp9808Sensor.getMetrics(m); + valid = valid && mcp9808Sensor.getMetrics(&m); hasSensor = true; } if (ina219Sensor.hasSensor()) { - valid = valid && ina219Sensor.getMetrics(m); + valid = valid && ina219Sensor.getMetrics(&m); hasSensor = true; } if (ina260Sensor.hasSensor()) { - valid = valid && ina260Sensor.getMetrics(m); - hasSensor = true; - } - if (ina3221Sensor.hasSensor()) { - valid = valid && ina3221Sensor.getMetrics(m); + valid = valid && ina260Sensor.getMetrics(&m); hasSensor = true; } if (veml7700Sensor.hasSensor()) { - valid = valid && veml7700Sensor.getMetrics(m); + valid = valid && veml7700Sensor.getMetrics(&m); hasSensor = true; } if (tsl2591Sensor.hasSensor()) { - valid = valid && tsl2591Sensor.getMetrics(m); + valid = valid && tsl2591Sensor.getMetrics(&m); hasSensor = true; } if (opt3001Sensor.hasSensor()) { - valid = valid && opt3001Sensor.getMetrics(m); + valid = valid && opt3001Sensor.getMetrics(&m); hasSensor = true; } if (mlx90632Sensor.hasSensor()) { - valid = valid && mlx90632Sensor.getMetrics(m); + valid = valid && mlx90632Sensor.getMetrics(&m); hasSensor = true; } if (rcwl9620Sensor.hasSensor()) { - valid = valid && rcwl9620Sensor.getMetrics(m); + valid = valid && rcwl9620Sensor.getMetrics(&m); hasSensor = true; } if (nau7802Sensor.hasSensor()) { - valid = valid && nau7802Sensor.getMetrics(m); + valid = valid && nau7802Sensor.getMetrics(&m); hasSensor = true; } if (aht10Sensor.hasSensor()) { if (!bmp280Sensor.hasSensor()) { - valid = valid && aht10Sensor.getMetrics(m); + valid = valid && aht10Sensor.getMetrics(&m); hasSensor = true; } else { // prefer bmp280 temp if both sensors are present, fetch only humidity meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n"); aht10Sensor.getMetrics(&m_ahtx); - m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; + m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } #endif + valid = valid && hasSensor; - return valid && hasSensor; -} - -meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() -{ - if (currentRequest) { - auto req = *currentRequest; - const auto &p = req.decoded; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - } else { - LOG_ERROR("Error decoding EnvironmentTelemetry module!\n"); - return NULL; - } - // Check for a request for environment metrics - if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - if (getEnvironmentTelemetry(&m)) { - LOG_INFO("Environment telemetry replying to request\n"); - return allocDataProtobuf(m); - } else { - return NULL; - } - } - } - return NULL; -} - -bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) -{ - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - if (getEnvironmentTelemetry(&m)) { + if (valid) { LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, @@ -457,9 +413,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) setIntervalFromNow(5000); } } - return true; } - return false; + return valid; } AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp, @@ -522,11 +477,6 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } - if (ina3221Sensor.hasSensor()) { - result = ina3221Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } if (veml7700Sensor.hasSensor()) { result = veml7700Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) @@ -565,4 +515,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule return result; } -#endif +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index ced617c2f..ca150347e 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -32,11 +32,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; - /** Called to get current Environment telemetry data - @return true if it contains valid data - */ - bool getEnvironmentTelemetry(meshtastic_Telemetry *m); - virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 6915d67e3..826de8a4a 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -24,8 +24,7 @@ int32_t PowerTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, - default_telemetry_broadcast_interval_secs); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -71,8 +70,7 @@ int32_t PowerTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval, - default_telemetry_broadcast_interval_secs))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; @@ -110,7 +108,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s display->drawString(x, y, "Power Telemetry"); if (lastMeasurementPacket == nullptr) { display->setFont(FONT_SMALL); - display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement"); + display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement"); return; } @@ -122,22 +120,22 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s auto &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { display->setFont(FONT_SMALL); - display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error"); + display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error"); LOG_ERROR("Unable to decode last packet"); return; } display->setFont(FONT_SMALL); String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; - display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); - display->drawString(x, y += _fontHeight(FONT_SMALL), + display->drawString(x, y += fontHeight(FONT_SMALL), "Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); } @@ -165,63 +163,29 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m return false; // Let others look at this message also if they want } -bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) -{ - bool valid = false; - m->time = getTime(); - m->which_variant = meshtastic_Telemetry_power_metrics_tag; - - m->variant.power_metrics.ch1_voltage = 0; - m->variant.power_metrics.ch1_current = 0; - m->variant.power_metrics.ch2_voltage = 0; - m->variant.power_metrics.ch2_current = 0; - m->variant.power_metrics.ch3_voltage = 0; - m->variant.power_metrics.ch3_current = 0; -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) - if (ina219Sensor.hasSensor()) - valid = ina219Sensor.getMetrics(m); - if (ina260Sensor.hasSensor()) - valid = ina260Sensor.getMetrics(m); - if (ina3221Sensor.hasSensor()) - valid = ina3221Sensor.getMetrics(m); -#endif - - return valid; -} - -meshtastic_MeshPacket *PowerTelemetryModule::allocReply() -{ - if (currentRequest) { - auto req = *currentRequest; - const auto &p = req.decoded; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - } else { - LOG_ERROR("Error decoding PowerTelemetry module!\n"); - return NULL; - } - // Check for a request for power metrics - if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { - meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - if (getPowerTelemetry(&m)) { - LOG_INFO("Power telemetry replying to request\n"); - return allocDataProtobuf(m); - } else { - return NULL; - } - } - } - - return NULL; -} - bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; - if (getPowerTelemetry(&m)) { + bool valid = false; + m.time = getTime(); + m.which_variant = meshtastic_Telemetry_power_metrics_tag; + + m.variant.power_metrics.ch1_voltage = 0; + m.variant.power_metrics.ch1_current = 0; + m.variant.power_metrics.ch2_voltage = 0; + m.variant.power_metrics.ch2_current = 0; + m.variant.power_metrics.ch3_voltage = 0; + m.variant.power_metrics.ch3_current = 0; +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) + if (ina219Sensor.hasSensor()) + valid = ina219Sensor.getMetrics(&m); + if (ina260Sensor.hasSensor()) + valid = ina260Sensor.getMetrics(&m); + if (ina3221Sensor.hasSensor()) + valid = ina3221Sensor.getMetrics(&m); +#endif + + if (valid) { LOG_INFO("(Sending): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " "ch3_voltage=%f, ch3_current=%f\n", m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage, @@ -254,9 +218,8 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) setIntervalFromNow(5000); } } - return true; } - return false; + return valid; } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index 1b68847db..3d6b686f2 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -33,11 +33,6 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual int32_t runOnce() override; - /** Called to get current Power telemetry data - @return true if it contains valid data - */ - bool getPowerTelemetry(meshtastic_Telemetry *m); - virtual meshtastic_MeshPacket *allocReply() override; /** * Send our Telemetry into the mesh */ diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index dec99c551..ea2cb4ea8 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -16,7 +16,8 @@ int32_t INA3221Sensor::runOnce() return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } if (!status) { - ina3221.begin(nodeTelemetrySensorsMap[sensorType].second); + ina3221.setAddr(INA3221_ADDR42_SDA); // i2c address 0x42 + ina3221.begin(); ina3221.setShuntRes(100, 100, 100); // 0.1 Ohm shunt resistors status = true; } else { @@ -27,69 +28,22 @@ int32_t INA3221Sensor::runOnce() void INA3221Sensor::setup() {} -struct _INA3221Measurement INA3221Sensor::getMeasurement(ina3221_ch_t ch) -{ - struct _INA3221Measurement measurement; - - measurement.voltage = ina3221.getVoltage(ch); - measurement.current = ina3221.getCurrent(ch); - - return measurement; -} - -struct _INA3221Measurements INA3221Sensor::getMeasurements() -{ - struct _INA3221Measurements measurements; - - // INA3221 has 3 channels starting from 0 - for (int i = 0; i < 3; i++) { - measurements.measurements[i] = getMeasurement((ina3221_ch_t)i); - } - - return measurements; -} - bool INA3221Sensor::getMetrics(meshtastic_Telemetry *measurement) { - switch (measurement->which_variant) { - case meshtastic_Telemetry_environment_metrics_tag: - return getEnvironmentMetrics(measurement); - - case meshtastic_Telemetry_power_metrics_tag: - return getPowerMetrics(measurement); - } - - // unsupported metric - return false; -} - -bool INA3221Sensor::getEnvironmentMetrics(meshtastic_Telemetry *measurement) -{ - struct _INA3221Measurement m = getMeasurement(ENV_CH); - - measurement->variant.environment_metrics.voltage = m.voltage; - measurement->variant.environment_metrics.current = m.current; - - return true; -} - -bool INA3221Sensor::getPowerMetrics(meshtastic_Telemetry *measurement) -{ - struct _INA3221Measurements m = getMeasurements(); - - measurement->variant.power_metrics.ch1_voltage = m.measurements[INA3221_CH1].voltage; - measurement->variant.power_metrics.ch1_current = m.measurements[INA3221_CH1].current; - measurement->variant.power_metrics.ch2_voltage = m.measurements[INA3221_CH2].voltage; - measurement->variant.power_metrics.ch2_current = m.measurements[INA3221_CH2].current; - measurement->variant.power_metrics.ch3_voltage = m.measurements[INA3221_CH3].voltage; - measurement->variant.power_metrics.ch3_current = m.measurements[INA3221_CH3].current; - + measurement->variant.environment_metrics.voltage = ina3221.getVoltage(INA3221_CH1); + measurement->variant.environment_metrics.current = ina3221.getCurrent(INA3221_CH1); + measurement->variant.power_metrics.ch1_voltage = ina3221.getVoltage(INA3221_CH1); + measurement->variant.power_metrics.ch1_current = ina3221.getCurrent(INA3221_CH1); + measurement->variant.power_metrics.ch2_voltage = ina3221.getVoltage(INA3221_CH2); + measurement->variant.power_metrics.ch2_current = ina3221.getCurrent(INA3221_CH2); + measurement->variant.power_metrics.ch3_voltage = ina3221.getVoltage(INA3221_CH3); + measurement->variant.power_metrics.ch3_current = ina3221.getCurrent(INA3221_CH3); return true; } uint16_t INA3221Sensor::getBusVoltageMv() { - return lround(ina3221.getVoltage(BAT_CH) * 1000); + return lround(ina3221.getVoltage(INA3221_CH1) * 1000); } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h index d5121aab6..3b8e382ee 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.h +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h @@ -12,21 +12,6 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor private: INA3221 ina3221 = INA3221(INA3221_ADDR42_SDA); - // channel to report voltage/current for environment metrics - ina3221_ch_t ENV_CH = INA3221_CH1; - - // channel to report battery voltage for device_battery_ina_address - ina3221_ch_t BAT_CH = INA3221_CH1; - - // get a single measurement for a channel - struct _INA3221Measurement getMeasurement(ina3221_ch_t ch); - - // get all measurements for all channels - struct _INA3221Measurements getMeasurements(); - - bool getEnvironmentMetrics(meshtastic_Telemetry *measurement); - bool getPowerMetrics(meshtastic_Telemetry *measurement); - protected: void setup() override; @@ -37,14 +22,4 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor virtual uint16_t getBusVoltageMv() override; }; -struct _INA3221Measurement { - float voltage; - float current; -}; - -struct _INA3221Measurements { - // INA3221 has 3 channels - struct _INA3221Measurement measurements[3]; -}; - #endif \ No newline at end of file diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index e1974db73..83485c8ee 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -2,11 +2,6 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "configuration.h" -#if HAS_SCREEN -#include "gps/RTC.h" -#include "graphics/Screen.h" -#include "main.h" -#endif WaypointModule *waypointModule; @@ -16,171 +11,14 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp) auto &p = mp.decoded; LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif + // We only store/display messages destined for us. // Keep a copy of the most recent text message. devicestate.rx_waypoint = mp; devicestate.has_rx_waypoint = true; powerFSM.trigger(EVENT_RECEIVED_MSG); - -#if HAS_SCREEN - - UIFrameEvent e; - - // New or updated waypoint: focus on this frame next time Screen::setFrames runs - if (shouldDraw()) { - requestFocus(); - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - } - - // Deleting an old waypoint: remove the frame quietly, don't change frame position if possible - else - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND; - - notifyObservers(&e); - -#endif + notifyObservers(&mp); return ProcessMessage::CONTINUE; // Let others look at this message also if they want } - -#if HAS_SCREEN -bool WaypointModule::shouldDraw() -{ -#if !MESHTASTIC_EXCLUDE_WAYPOINT - // If no waypoint to show - if (!devicestate.has_rx_waypoint) - return false; - - // Decode the message, to find the expiration time (is waypoint still valid) - // This handles "deletion" as well as expiration - meshtastic_Waypoint wp; - memset(&wp, 0, sizeof(wp)); - if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size, - &meshtastic_Waypoint_msg, &wp)) { - // Valid waypoint - if (wp.expire > getTime()) - return devicestate.has_rx_waypoint = true; - - // Expired, or deleted - else - return devicestate.has_rx_waypoint = false; - } - - // If decoding failed - LOG_ERROR("Failed to decode waypoint\n"); - devicestate.has_rx_waypoint = false; - return false; -#else - return false; -#endif -} - -/// Draw the last waypoint we received -void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - // Prepare to draw - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); - - // Handle inverted display - // Unsure of expected behavior: for now, copy drawNodeInfo - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) - display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - - // Decode the waypoint - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; - meshtastic_Waypoint wp; - memset(&wp, 0, sizeof(wp)); - if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { - // This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case - display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint"); - devicestate.has_rx_waypoint = false; - return; - } - - // Get timestamp info. Will pass as a field to drawColumns - static char lastStr[20]; - screen->getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr)); - - // Will contain distance information, passed as a field to drawColumns - static char distStr[20]; - - // Get our node, to use our own position - meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - - // Text fields to draw (left of compass) - // Last element must be NULL. This signals the end of the char*[] to drawColumns - const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL}; - - // Dimensions / co-ordinates for the compass/circle - int16_t compassX = 0, compassY = 0; - uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); - - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + display->getHeight() / 2; - } else { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; - } - - // If our node has a position: - if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) { - const meshtastic_PositionLite &op = ourNode->position; - float myHeading; - if (screen->hasHeading()) - myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians - else - myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); - screen->drawCompassNorth(display, compassX, compassY, myHeading); - - // Distance to Waypoint - float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - if (d < (2 * MILES_TO_FEET)) - snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET); - else - snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET); - } else { - if (d < 2000) - snprintf(distStr, sizeof(distStr), "%.0f m", d); - else - snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000); - } - - // Compass bearing to waypoint - float bearingToOther = - GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i)); - // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly - // If the top of the compass is not a static north we need adjust bearingToOther based on heading - if (!config.display.compass_north_top) - bearingToOther -= myHeading; - screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); - } - - // If our node doesn't have position - else { - // ? in the compass - display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); - - // ? in the distance field - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - strncpy(distStr, "? mi", sizeof(distStr)); - else - strncpy(distStr, "? km", sizeof(distStr)); - } - - // Undo color-inversion, if set prior to drawing header - // Unsure of expected behavior? For now: copy drawNodeInfo - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { - display->setColor(BLACK); - } - - // Draw compass circle - display->drawCircle(compassX, compassY, compassDiam / 2); - - // Must be after distStr is populated - screen->drawColumns(display, x, y, fields); -} -#endif \ No newline at end of file diff --git a/src/modules/WaypointModule.h b/src/modules/WaypointModule.h index 4c9c7b86b..ddbabf4de 100644 --- a/src/modules/WaypointModule.h +++ b/src/modules/WaypointModule.h @@ -5,29 +5,21 @@ /** * Waypoint message handling for meshtastic */ -class WaypointModule : public SinglePortModule, public Observable +class WaypointModule : public SinglePortModule, public Observable { public: /** Constructor * name is for debugging output */ WaypointModule() : SinglePortModule("waypoint", meshtastic_PortNum_WAYPOINT_APP) {} -#if HAS_SCREEN - bool shouldDraw(); -#endif + protected: /** Called to handle a particular incoming message @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ - - virtual Observable *getUIFrameObservable() override { return this; } -#if HAS_SCREEN - virtual bool wantUIFrame() override { return this->shouldDraw(); } - virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; -#endif virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; }; -extern WaypointModule *waypointModule; \ No newline at end of file +extern WaypointModule *waypointModule; diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 2e2e4f528..4a7b1c2c6 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -190,13 +190,13 @@ int32_t AudioModule::runOnce() firstTime = false; } else { - UIFrameEvent e; + UIFrameEvent e = {false, true}; // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { LOG_INFO("PTT pressed, switching to TX\n"); radio_state = RadioState::tx; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; this->notifyObservers(&e); } } else { @@ -209,7 +209,7 @@ int32_t AudioModule::runOnce() } tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen + e.frameChanged = true; this->notifyObservers(&e); } } diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 34d6fb1d0..e6712871d 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -66,6 +66,10 @@ bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m meshtastic_MeshPacket *PaxcounterModule::allocReply() { + if (ignoreRequest) { + return NULL; + } + meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; pl.ble = count_from_libpax.ble_count; @@ -101,7 +105,7 @@ int32_t PaxcounterModule::runOnce() sendInfo(NODENUM_BROADCAST); } return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, - default_telemetry_broadcast_interval_secs); + default_broadcast_interval_secs); } else { return disable(); } @@ -127,4 +131,4 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state } #endif // HAS_SCREEN -#endif \ No newline at end of file +#endif diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index dc8650ad0..12cddc520 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -319,8 +319,8 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m #ifdef ARCH_ESP32 if (moduleConfig.store_forward.enabled) { - // The router node should not be sending messages as a client - if ((getFrom(&mp) != nodeDB->getNodeNum())) { + // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT + if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a64720c78..9f9ac5c24 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -14,7 +14,7 @@ #endif #include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "sleep.h" -#if HAS_WIFI +#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #include #endif @@ -175,7 +175,7 @@ void mqttInit() new MQTT(); } -#if HAS_NETWORKING +#ifdef HAS_NETWORKING MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_MQTT_QUEUE) #else MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) @@ -206,7 +206,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); } -#if HAS_NETWORKING +#ifdef HAS_NETWORKING if (!moduleConfig.mqtt.proxy_to_client_enabled) pubSub.setCallback(mqttCallback); #endif @@ -226,7 +226,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) bool MQTT::isConnectedDirectly() { -#if HAS_NETWORKING +#ifdef HAS_NETWORKING return pubSub.connected(); #else return false; @@ -244,7 +244,7 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) service.sendMqttMessageToClientProxy(msg); return true; } -#if HAS_NETWORKING +#ifdef HAS_NETWORKING else if (isConnectedDirectly()) { return pubSub.publish(topic, payload, retained); } @@ -264,7 +264,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo service.sendMqttMessageToClientProxy(msg); return true; } -#if HAS_NETWORKING +#ifdef HAS_NETWORKING else if (isConnectedDirectly()) { return pubSub.publish(topic, payload, length, retained); } @@ -284,7 +284,7 @@ void MQTT::reconnect() publishStatus(); return; // Don't try to connect directly to the server } -#if HAS_NETWORKING +#ifdef HAS_NETWORKING // Defaults int serverPort = 1883; const char *serverAddr = default_mqtt_address; @@ -357,7 +357,7 @@ void MQTT::reconnect() void MQTT::sendSubscriptions() { -#if HAS_NETWORKING +#ifdef HAS_NETWORKING size_t numChan = channels.getNumChannels(); for (size_t i = 0; i < numChan; i++) { const auto &ch = channels.getByIndex(i); @@ -396,7 +396,7 @@ bool MQTT::wantsLink() const int32_t MQTT::runOnce() { -#if HAS_NETWORKING +#ifdef HAS_NETWORKING if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); @@ -482,12 +482,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & auto &ch = channels.getByIndex(chIndex); - if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { - LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); - return; - } - - if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + if (&mp_decoded.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 1ebba4afe..f2eb6b120 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -8,15 +8,17 @@ #include "mqtt/JSON.h" #if HAS_WIFI #include +#define HAS_NETWORKING 1 #if !defined(ARCH_PORTDUINO) #include #endif #endif #if HAS_ETHERNET #include +#define HAS_NETWORKING 1 #endif -#if HAS_NETWORKING +#ifdef HAS_NETWORKING #include #endif @@ -41,7 +43,7 @@ class MQTT : private concurrency::OSThread #endif public: -#if HAS_NETWORKING +#ifdef HAS_NETWORKING PubSubClient pubSub; #endif MQTT(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index d959553a4..68aa9b465 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -12,7 +12,6 @@ NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; -NimBLECharacteristic *logRadioCharacteristic; NimBLEServer *bleServer; static bool passkeyShowing; @@ -59,6 +58,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks { virtual void onRead(NimBLECharacteristic *pCharacteristic) { + LOG_INFO("From Radio onread\n"); uint8_t fromRadioBytes[meshtastic_FromRadio_size]; size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); @@ -82,33 +82,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks LOG_INFO("*** Enter passkey %d on the peer side ***\n", passkey); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); -#if HAS_SCREEN - screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - char btPIN[16] = "888888"; - snprintf(btPIN, sizeof(btPIN), "%06u", passkey); - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - 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_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); - }); -#endif + screen->startBluetoothPinScreen(passkey); passkeyShowing = true; return passkey; @@ -120,7 +94,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks if (passkeyShowing) { passkeyShowing = false; - screen->endAlert(); + screen->stopBluetoothPinScreen(); } } @@ -206,8 +180,6 @@ void NimbleBluetooth::setupService() ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); - logRadioCharacteristic = - bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U); } else { ToRadioCharacteristic = bleService->createCharacteristic( TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); @@ -216,9 +188,6 @@ void NimbleBluetooth::setupService() fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); - logRadioCharacteristic = bleService->createCharacteristic( - LOGRADIO_UUID, - NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -267,14 +236,6 @@ void NimbleBluetooth::clearBonds() NimBLEDevice::deleteAllBonds(); } -void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) -{ - if (!bleServer || !isConnected() || length > 512) { - return; - } - logRadioCharacteristic->notify(logMessage, length, true); -} - void clearNVS() { NimBLEDevice::deleteAllBonds(); diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 45602e088..d1e347830 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -11,7 +11,6 @@ class NimbleBluetooth : BluetoothApi bool isActive(); bool isConnected(); int getRssi(); - void sendLog(const uint8_t *logMessage, size_t length); private: void setupService(); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index fd3f92a9c..5565b6468 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -151,14 +151,6 @@ #define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO #elif defined(HELTEC_CAPSULE_SENSOR_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 -#elif defined(HELTEC_VISION_MASTER_T190) -#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_T190 -#elif defined(HELTEC_VISION_MASTER_E213) -#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E213 -#elif defined(HELTEC_VISION_MASTER_E290) -#define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 -#elif defined(HELTEC_MESH_NODE_T114) -#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index aa51e810a..1dd7a389a 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -8,7 +8,7 @@ #include "nimble/NimbleBluetooth.h" #endif -#if HAS_WIFI +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -24,22 +24,23 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { -#if HAS_WIFI +#ifndef MESHTASTIC_EXCLUDE_WIFI if (!isWifiAvailable() && config.bluetooth.enabled == true) -#else - if (config.bluetooth.enabled == true) #endif - { - if (!nimbleBluetooth) { - nimbleBluetooth = new NimbleBluetooth(); +#ifdef MESHTASTIC_EXCLUDE_WIFI + if (config.bluetooth.enabled == true) +#endif + { + if (!nimbleBluetooth) { + nimbleBluetooth = new NimbleBluetooth(); + } + if (enable && !nimbleBluetooth->isActive()) { + nimbleBluetooth->setup(); + } + // For ESP32, no way to recover from bluetooth shutdown without reboot + // BLE advertising automatically stops when MCU enters light-sleep(?) + // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse } - if (enable && !nimbleBluetooth->isActive()) { - nimbleBluetooth->setup(); - } - // For ESP32, no way to recover from bluetooth shutdown without reboot - // BLE advertising automatically stops when MCU enters light-sleep(?) - // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse - } } #else void setBluetoothEnable(bool enable) {} diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 6138e2aef..8b817f51b 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -8,18 +8,17 @@ #include "mesh/mesh-pb-constants.h" #include #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 toRadio = BLECharacteristic(BLEUuid(TORADIO_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 static BLEDfu bledfu; // DFU software update helper service static BLEDfuSecure bledfusecure; // DFU software update helper service -static BLEDfu bledfu; // DFU software update helper service // 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)]; @@ -53,14 +52,16 @@ 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\n", central_name); } + /** * Callback invoked when a connection is dropped * @param conn_handle connection where this event happens @@ -71,36 +72,37 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason) // FIXME - we currently assume only one active connection LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } + void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { // Display the raw request packet LOG_INFO("CCCD Updated: %u\n", 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 - - 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\n"); + if (chr->uuid == fromNum.uuid) { + if (chr->notifyEnabled(conn_hdl)) { + LOG_INFO("fromNum 'Notify' enabled\n"); } else { - LOG_INFO("Notify/Indicate disabled\n"); + LOG_INFO("fromNum 'Notify' disabled\n"); } } } + 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 = 152.5 ms @@ -115,6 +117,7 @@ void startAdv(void) 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) { @@ -122,6 +125,7 @@ static void authorizeRead(uint16_t conn_hdl) 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 */ @@ -130,6 +134,7 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e 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); @@ -138,22 +143,37 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e } authorizeRead(conn_hdl); } + void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); + bluetoothPhoneAPI->handleToRadio(data, len); } +/** + * client is starting read, pull the bytes from our API class + */ +void onFromNumAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_read_t *request) +{ + LOG_INFO("fromNumAuthorizeCb\n"); + + authorizeRead(conn_hdl); +} + 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( @@ -183,15 +203,10 @@ void setupMeshService(void) // 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(); } + static uint32_t configuredPasskey; + void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw @@ -201,23 +216,29 @@ void NRF52Bluetooth::shutdown() } 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("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } + bool NRF52Bluetooth::isConnected() { return Bluefruit.connected(connectionHandle); } + int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -225,10 +246,12 @@ void NRF52Bluetooth::setup() 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 @@ -247,6 +270,7 @@ void NRF52Bluetooth::setup() } // 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); @@ -263,19 +287,24 @@ void NRF52Bluetooth::setup() bledis.setModel(optstr(HW_VERSION)); bledis.setFirmwareRev(optstr(APP_VERSION)); bledis.begin(); + // Start the BLE Battery Service and set it to 100% LOG_INFO("Configuring the Battery Service\n"); blebas.begin(); blebas.write(0); // Unknown battery level for now + // Setup the Heart Rate Monitor service using // BLEService and BLECharacteristic classes LOG_INFO("Configuring the Mesh bluetooth service\n"); setupMeshService(); + // Setup the advertising packet(s) LOG_INFO("Setting up the advertising payload(s)\n"); startAdv(); + LOG_INFO("Advertising\n"); } + void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); @@ -283,52 +312,34 @@ void NRF52Bluetooth::resumeAdvertising() 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 NRF52Bluetooth::clearBonds() { LOG_INFO("Clearing bluetooth bonds!\n"); 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\n"); } + bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - 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 : 32; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, y_offset + y, "Bluetooth"); + screen->startBluetoothPinScreen(configuredPasskey); - 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_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); - }); if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { @@ -339,21 +350,13 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); return true; } + void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) LOG_INFO("BLE pairing success\n"); else LOG_INFO("BLE pairing failed\n"); - 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); -} + screen->stopBluetoothPinScreen(); +} \ No newline at end of file diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 2229163f8..450af47f9 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -13,10 +13,10 @@ class NRF52Bluetooth : BluetoothApi void clearBonds(); bool isConnected(); int getRssi(); - void sendLog(const uint8_t *logMessage, size_t length); private: static void onConnectionSecured(uint16_t conn_handle); + void convertToUint8(uint8_t target[4], uint32_t source); static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request); static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status); }; \ No newline at end of file diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 7334f3a04..1f2c6867d 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -63,8 +63,7 @@ static void initBrownout() // 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 -bool useSoftDevice = true; // Set to false for easier debugging +static const bool useSoftDevice = true; // Set to false for easier debugging #if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) @@ -150,43 +149,13 @@ void nrf52Loop() checkSDEvents(); } -#ifdef USE_SEMIHOSTING -#include - -/** - * 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. - */ -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); - } -} -#endif - void nrf52Setup() { - uint32_t why = NRF_POWER->RESETREAS; + auto 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\n", why); -#ifdef USE_SEMIHOSTING - 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 @@ -234,18 +203,6 @@ void cpuDeepSleep(uint32_t msecToWake) // 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); -#endif #endif // Sleepy trackers or sensors can low power "sleep" // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event @@ -286,10 +243,5 @@ void clearBonds() void enterDfuMode() { -// SDK kit does not have native USB like almost all other NRF52 boards -#ifdef NRF_USE_SERIAL_DFU - enterSerialDfu(); -#else enterUf2Dfu(); -#endif } \ No newline at end of file diff --git a/src/platform/nrf52/softdevice/nrf_sdm.h b/src/platform/nrf52/softdevice/nrf_sdm.h index 33b6cc342..2786a86a4 100644 --- a/src/platform/nrf52/softdevice/nrf_sdm.h +++ b/src/platform/nrf52/softdevice/nrf_sdm.h @@ -141,7 +141,7 @@ the start of the SoftDevice (without MBR)*/ * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed * just above the MBR (the usual case). */ -#define SD_FLASH_SIZE 0x27000 +#define SD_FLASH_SIZE 0x26000 /** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual diff --git a/src/shutdown.h b/src/shutdown.h index 3f191eea8..54fb3071b 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -38,7 +38,7 @@ void powerCommandsCheck() #if defined(ARCH_ESP32) || defined(ARCH_NRF52) if (shutdownAtMsec) { - screen->startAlert("Shutting down..."); + screen->startShutdownScreen(); } #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index ed02ba44a..55e70e7b8 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -8,7 +8,6 @@ #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" -#include "PowerMon.h" #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" @@ -18,7 +17,7 @@ #ifdef ARCH_ESP32 #include "esp32/pm.h" #include "esp_pm.h" -#if HAS_WIFI +#if !MESHTASTIC_EXCLUDE_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "rom/rtc.h" @@ -37,7 +36,10 @@ Observable preflightSleep; /// Called to tell observers we are now entering sleep and you should prepare. Must return 0 /// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep +/// notifyGPSSleep will be called when config.position.gps_enabled is set to 0 or from buttonthread when GPS_POWER_TOGGLE is +/// enabled. Observable notifySleep, notifyDeepSleep; +Observable notifyGPSSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; @@ -54,20 +56,20 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#if defined(ARCH_ESP32) && HAS_WIFI +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI if (isWifiAvailable()) { /* * * There's a newly introduced bug in the espressif framework where WiFi is - * unstable when the frequency is less than 240MHz. + * unstable when the frequency is less than 240mhz. * * This mostly impacts WiFi AP mode but we'll bump the frequency for * all WiFi use cases. * (Added: Dec 23, 2021 by Jm Casler) */ #ifndef CONFIG_IDF_TARGET_ESP32C3 - LOG_DEBUG("Setting CPU to 240MHz because WiFi is in use.\n"); + LOG_DEBUG("Setting CPU to 240mhz because WiFi is in use.\n"); setCpuFrequencyMhz(240); #endif return; @@ -83,11 +85,6 @@ void setCPUFast(bool on) void setLed(bool ledOn) { - if (ledOn) - powerMon->setState(meshtastic_PowerMon_State_LED_On); - else - powerMon->clearState(meshtastic_PowerMon_State_LED_On); - #ifdef LED_PIN // toggle the led so we can get some rough sense of how often loop is pausing digitalWrite(LED_PIN, ledOn ^ LED_INVERTED); @@ -238,7 +235,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) pinMode(PIN_POWER_EN, INPUT); // power off peripherals // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif - #if HAS_GPS // Kill GPS power completely (even if previously we just had it in sleep mode) if (gps) @@ -273,8 +269,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #elif defined(VEXT_ENABLE_V05) digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power digitalWrite(ST7735_BL_V05, 0); // turn off the display power -#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) - digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif diff --git a/src/sleep.h b/src/sleep.h index f154b8d44..8d5b9a94f 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -41,6 +41,8 @@ extern Observable notifySleep; /// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 extern Observable notifyDeepSleep; +/// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 +extern Observable notifyGPSSleep; void enableModemSleep(); #ifdef ARCH_ESP32 void enableLoraInterrupt(); diff --git a/variants/heltec_capsule_sensor_v3/variant.h b/variants/heltec_capsule_sensor_v3/variant.h index 51c3cb6ad..0d5ab73cf 100644 --- a/variants/heltec_capsule_sensor_v3/variant.h +++ b/variants/heltec_capsule_sensor_v3/variant.h @@ -1,5 +1,5 @@ #define LED_PIN 33 -#define LED_PIN2 34 +#define LED_PIN2 34 #define EXT_PWR_DETECT 35 #define BUTTON_PIN 18 diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini deleted file mode 100644 index 99bdf77a7..000000000 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ /dev/null @@ -1,15 +0,0 @@ -; First prototype nrf52840/sx1262 device -[env:heltec-mesh-node-t114] -extends = nrf52840_base -board = heltec_mesh_node_t114 -debug_tool = jlink - -# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_node_t114 - -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" - -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node_t114> -lib_deps = - ${nrf52840_base.lib_deps} - lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb \ No newline at end of file diff --git a/variants/heltec_mesh_node_t114/variant.cpp b/variants/heltec_mesh_node_t114/variant.cpp deleted file mode 100644 index cae079b74..000000000 --- a/variants/heltec_mesh_node_t114/variant.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled - 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - - pinMode(PIN_LED3, OUTPUT); - ledOff(PIN_LED3); -} diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h deleted file mode 100644 index b233069c6..000000000 --- a/variants/heltec_mesh_node_t114/variant.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _VARIANT_HELTEC_NRF_ -#define _VARIANT_HELTEC_NRF_ -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#define HELTEC_MESH_NODE_T114 - -#define USE_ST7789 - -#define ST7789_NSS 11 -#define ST7789_RS 12 // DC -#define ST7789_SDA 41 // MOSI -#define ST7789_SCK 40 -#define ST7789_RESET 2 -#define ST7789_MISO -1 -#define ST7789_BUSY -1 -#define VTFT_CTRL 3 -#define VTFT_LEDA 15 -// #define ST7789_BL (32+6) -#define TFT_BACKLIGHT_ON LOW -#define ST7789_SPI_HOST SPI1_HOST -// #define ST7789_BACKLIGHT_EN (32+6) -#define SPI_FREQUENCY 40000000 -#define SPI_READ_FREQUENCY 16000000 -#define TFT_HEIGHT 135 -#define TFT_WIDTH 240 -#define TFT_OFFSET_X 0 -#define TFT_OFFSET_Y 0 -// #define TFT_OFFSET_ROTATION 0 -// #define SCREEN_ROTATE -// #define SCREEN_TRANSITION_FRAMERATE 5 - -// Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (1) -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (32 + 3) // 13 red (confirmed on 1.0 board) -// Unused(by firmware) LEDs: -#define PIN_LED2 (1 + 1) // 14 blue -#define PIN_LED3 (1 + 11) // 15 green - -#define LED_RED PIN_LED3 -#define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 - -#define LED_BUILTIN LED_BLUE -#define LED_CONN PIN_GREEN - -#define LED_STATE_ON 0 // State when LED is lit -#define LED_INVERTED 1 - -/* - * Buttons - */ -#define PIN_BUTTON1 (32 + 10) -// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular -// GPIO - -/* -No longer populated on PCB -*/ -#define PIN_SERIAL2_RX (0 + 9) -#define PIN_SERIAL2_TX (0 + 10) -// #define PIN_SERIAL2_EN (0 + 17) - -/** - Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) - -// QSPI Pins -#define PIN_QSPI_SCK (32 + 14) -#define PIN_QSPI_CS (32 + 15) -#define PIN_QSPI_IO0 (32 + 12) // MOSI if using two bit interface -#define PIN_QSPI_IO1 (32 + 13) // MISO if using two bit interface -#define PIN_QSPI_IO2 (0 + 7) // WP if using two bit interface (i.e. not used) -#define PIN_QSPI_IO3 (0 + 5) // HOLD if using two bit interface (i.e. not used) - -// On-board QSPI Flash -#define EXTERNAL_FLASH_DEVICES MX25R1635F -#define EXTERNAL_FLASH_USE_QSPI - -/* - * Lora radio - */ - -#define USE_SX1262 -// #define USE_SX1268 -#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead -#define LORA_CS (0 + 24) -#define SX126X_DIO1 (0 + 20) -// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching -// #define SX1262_DIO3 \ -// (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the -// main -// CPU? -#define SX126X_BUSY (0 + 17) -#define SX126X_RESET (0 + 25) -// Not really an E22 but TTGO seems to be trying to clone that -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -#define PIN_SPI1_MISO \ - ST7789_MISO // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO -#define PIN_SPI1_MOSI ST7789_SDA -#define PIN_SPI1_SCK ST7789_SCK - -/* - * GPS pins - */ - -#define GPS_L76K - -#define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K -#define GPS_RESET_MODE LOW -#define PIN_GPS_EN (21) -#define GPS_EN_ACTIVE HIGH -#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake -#define PIN_GPS_PPS (32 + 4) -// Seems to be missing on this new board -// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS - -#define GPS_THREAD_INTERVAL 50 - -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN - -// PCF8563 RTC Module -#define PCF8563_RTC 0x51 - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -// For LORA, spi 0 -#define PIN_SPI_MISO (0 + 23) -#define PIN_SPI_MOSI (0 + 22) -#define PIN_SPI_SCK (0 + 19) - -// #define PIN_PWR_EN (0 + 6) - -// To debug via the segger JLINK console rather than the CDC-ACM serial device -// #define USE_SEGGER - -// Battery -// The battery sense is hooked to pin A0 (4) -// it is defined in the anlaolgue pin section of this file -// and has 12 bit resolution - -#define ADC_CTRL 6 -#define ADC_CTRL_ENABLED HIGH -#define BATTERY_PIN 4 -#define ADC_RESOLUTION 14 - -#define BATTERY_SENSE_RESOLUTION_BITS 12 -#define BATTERY_SENSE_RESOLUTION 4096.0 -#undef AREF_VOLTAGE -#define AREF_VOLTAGE 3.0 -#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER (4.90F) - -#define HAS_RTC 0 -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h deleted file mode 100644 index 01c16c496..000000000 --- a/variants/heltec_vision_master_e213/pins_arduino.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef Pins_Arduino_h -#define Pins_Arduino_h - -#include - -#define HELTEC_VISION_MASTER_E213 true - -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - -static const uint8_t TX = 43; -static const uint8_t RX = 44; - -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; - -static const uint8_t SS = 8; -static const uint8_t MOSI = 10; -static const uint8_t MISO = 11; -static const uint8_t SCK = 9; - -static const uint8_t A0 = 1; -static const uint8_t A1 = 2; -static const uint8_t A2 = 3; -static const uint8_t A3 = 4; -static const uint8_t A4 = 5; -static const uint8_t A5 = 6; -static const uint8_t A6 = 7; -static const uint8_t A7 = 8; -static const uint8_t A8 = 9; -static const uint8_t A9 = 10; -static const uint8_t A10 = 11; -static const uint8_t A11 = 12; -static const uint8_t A12 = 13; -static const uint8_t A13 = 14; -static const uint8_t A14 = 15; -static const uint8_t A15 = 16; -static const uint8_t A16 = 17; -static const uint8_t A17 = 18; -static const uint8_t A18 = 19; -static const uint8_t A19 = 20; - -static const uint8_t T1 = 1; -static const uint8_t T2 = 2; -static const uint8_t T3 = 3; -static const uint8_t T4 = 4; -static const uint8_t T5 = 5; -static const uint8_t T6 = 6; -static const uint8_t T7 = 7; -static const uint8_t T8 = 8; -static const uint8_t T9 = 9; -static const uint8_t T10 = 10; -static const uint8_t T11 = 11; -static const uint8_t T12 = 12; -static const uint8_t T13 = 13; -static const uint8_t T14 = 14; - -static const uint8_t RST_LoRa = 12; -static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; - -#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini deleted file mode 100644 index 77cc65983..000000000 --- a/variants/heltec_vision_master_e213/platformio.ini +++ /dev/null @@ -1,23 +0,0 @@ -[env:heltec-vision-master-e213] -extends = esp32s3_base -board = heltec_wifi_lora_32_V3 -build_flags = - ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_e213 - -DHELTEC_VISION_MASTER_E213 - -DEINK_DISPLAY_MODEL=GxEPD2_213_FC1 - -DEINK_WIDTH=250 - -DEINK_HEIGHT=122 - -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk - -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted - -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates - -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated - -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. - -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" - -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight -lib_deps = - ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d - lewisxhe/PCF8563_Library@^1.0.1 -upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h deleted file mode 100644 index 169602a0d..000000000 --- a/variants/heltec_vision_master_e213/variant.h +++ /dev/null @@ -1,58 +0,0 @@ -// #define LED_PIN 18 - -// Enable bus for external periherals -#define I2C_SDA SDA -#define I2C_SCL SCL - -#define USE_EINK - -/* - * eink display pins - */ -#define PIN_EINK_CS 5 -#define PIN_EINK_BUSY 1 -#define PIN_EINK_DC 2 -#define PIN_EINK_RES 3 -#define PIN_EINK_SCLK 4 -#define PIN_EINK_MOSI 6 - -/* - * SPI interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -#define VEXT_ENABLE 18 // powers the oled display and the lora antenna boost -#define VEXT_ON_VALUE 1 -#define BUTTON_PIN 21 - -#define ADC_CTRL 46 -#define ADC_CTRL_ENABLED HIGH -#define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL -#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 -#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high - -#define USE_SX1262 - -#define LORA_DIO0 -1 // a No connect on the SX1262 module -#define LORA_RESET 12 -#define LORA_DIO1 14 // SX1262 IRQ -#define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled - -#define LORA_SCK 9 -#define LORA_MISO 11 -#define LORA_MOSI 10 -#define LORA_CS 8 - -#define SX126X_CS LORA_CS -#define SX126X_DIO1 LORA_DIO1 -#define SX126X_BUSY LORA_DIO2 -#define SX126X_RESET LORA_RESET - -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h deleted file mode 100644 index e5d507846..000000000 --- a/variants/heltec_vision_master_e290/pins_arduino.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef Pins_Arduino_h -#define Pins_Arduino_h - -#include - -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - -static const uint8_t TX = 43; -static const uint8_t RX = 44; - -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; - -static const uint8_t SS = 8; -static const uint8_t MOSI = 10; -static const uint8_t MISO = 11; -static const uint8_t SCK = 9; - -static const uint8_t A0 = 1; -static const uint8_t A1 = 2; -static const uint8_t A2 = 3; -static const uint8_t A3 = 4; -static const uint8_t A4 = 5; -static const uint8_t A5 = 6; -static const uint8_t A6 = 7; -static const uint8_t A7 = 8; -static const uint8_t A8 = 9; -static const uint8_t A9 = 10; -static const uint8_t A10 = 11; -static const uint8_t A11 = 12; -static const uint8_t A12 = 13; -static const uint8_t A13 = 14; -static const uint8_t A14 = 15; -static const uint8_t A15 = 16; -static const uint8_t A16 = 17; -static const uint8_t A17 = 18; -static const uint8_t A18 = 19; -static const uint8_t A19 = 20; - -static const uint8_t T1 = 1; -static const uint8_t T2 = 2; -static const uint8_t T3 = 3; -static const uint8_t T4 = 4; -static const uint8_t T5 = 5; -static const uint8_t T6 = 6; -static const uint8_t T7 = 7; -static const uint8_t T8 = 8; -static const uint8_t T9 = 9; -static const uint8_t T10 = 10; -static const uint8_t T11 = 11; -static const uint8_t T12 = 12; -static const uint8_t T13 = 13; -static const uint8_t T14 = 14; - -static const uint8_t RST_LoRa = 12; -static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; - -#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini deleted file mode 100644 index 60ff60036..000000000 --- a/variants/heltec_vision_master_e290/platformio.ini +++ /dev/null @@ -1,25 +0,0 @@ -[env:heltec-vision-master-e290] -board_level = extra -extends = esp32s3_base -board = heltec_wifi_lora_32_V3 -build_flags = - ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_e290 - -DHELTEC_VISION_MASTER_E290 - -DEINK_DISPLAY_MODEL=GxEPD2_290_BS - -DEINK_WIDTH=296 - -DEINK_HEIGHT=128 -; -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk -; -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted -; -D EINK_LIMIT_RATE_BACKGROUND_SEC=1 ; Minimum interval between BACKGROUND updates -; -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -; -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -; -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -; -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight - -lib_deps = - ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d - lewisxhe/PCF8563_Library@^1.0.1 -upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h deleted file mode 100644 index a122a7e0f..000000000 --- a/variants/heltec_vision_master_e290/variant.h +++ /dev/null @@ -1,58 +0,0 @@ -// #define LED_PIN 18 - -// Enable bus for external periherals -#define I2C_SDA SDA -#define I2C_SCL SCL - -#define USE_EINK - -/* - * eink display pins - */ -#define PIN_EINK_CS 3 -#define PIN_EINK_BUSY 5 -#define PIN_EINK_DC 4 -#define PIN_EINK_RES 5 -#define PIN_EINK_SCLK 2 -#define PIN_EINK_MOSI 1 - -/* - * SPI interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK - -#define VEXT_ENABLE 18 // powers the e-ink display -#define VEXT_ON_VALUE 1 -#define BUTTON_PIN 21 - -#define ADC_CTRL 46 -#define ADC_CTRL_ENABLED HIGH -#define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL -#define ADC_MULTIPLIER 4.9 * 1.03 -#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high - -#define USE_SX1262 - -#define LORA_DIO0 -1 // a No connect on the SX1262 module -#define LORA_RESET 12 -#define LORA_DIO1 14 // SX1262 IRQ -#define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled - -#define LORA_SCK 9 -#define LORA_MISO 11 -#define LORA_MOSI 10 -#define LORA_CS 8 - -#define SX126X_CS LORA_CS -#define SX126X_DIO1 LORA_DIO1 -#define SX126X_BUSY LORA_DIO2 -#define SX126X_RESET LORA_RESET - -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/heltec_vision_master_t190/pins_arduino.h deleted file mode 100644 index e5d507846..000000000 --- a/variants/heltec_vision_master_t190/pins_arduino.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef Pins_Arduino_h -#define Pins_Arduino_h - -#include - -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - -static const uint8_t TX = 43; -static const uint8_t RX = 44; - -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; - -static const uint8_t SS = 8; -static const uint8_t MOSI = 10; -static const uint8_t MISO = 11; -static const uint8_t SCK = 9; - -static const uint8_t A0 = 1; -static const uint8_t A1 = 2; -static const uint8_t A2 = 3; -static const uint8_t A3 = 4; -static const uint8_t A4 = 5; -static const uint8_t A5 = 6; -static const uint8_t A6 = 7; -static const uint8_t A7 = 8; -static const uint8_t A8 = 9; -static const uint8_t A9 = 10; -static const uint8_t A10 = 11; -static const uint8_t A11 = 12; -static const uint8_t A12 = 13; -static const uint8_t A13 = 14; -static const uint8_t A14 = 15; -static const uint8_t A15 = 16; -static const uint8_t A16 = 17; -static const uint8_t A17 = 18; -static const uint8_t A18 = 19; -static const uint8_t A19 = 20; - -static const uint8_t T1 = 1; -static const uint8_t T2 = 2; -static const uint8_t T3 = 3; -static const uint8_t T4 = 4; -static const uint8_t T5 = 5; -static const uint8_t T6 = 6; -static const uint8_t T7 = 7; -static const uint8_t T8 = 8; -static const uint8_t T9 = 9; -static const uint8_t T10 = 10; -static const uint8_t T11 = 11; -static const uint8_t T12 = 12; -static const uint8_t T13 = 13; -static const uint8_t T14 = 14; - -static const uint8_t RST_LoRa = 12; -static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; - -#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini deleted file mode 100644 index bbaa0075c..000000000 --- a/variants/heltec_vision_master_t190/platformio.ini +++ /dev/null @@ -1,13 +0,0 @@ -[env:heltec-vision-master-t190] -extends = esp32s3_base -board = heltec_wifi_lora_32_V3 -build_flags = - ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_t190 - -DHELTEC_VISION_MASTER_T190 - ; -D PRIVATE_HW -lib_deps = - ${esp32s3_base.lib_deps} - lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb -upload_speed = 921600 \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h deleted file mode 100644 index 97500d357..000000000 --- a/variants/heltec_vision_master_t190/variant.h +++ /dev/null @@ -1,76 +0,0 @@ -// #define LED_PIN 18 - -// Enable bus for external periherals -#define I2C_SDA 1 -#define I2C_SCL 2 -#define USE_ST7789 - -#define ST7789_NSS 39 -// #define ST7789_CS 39 -#define ST7789_RS 47 // DC -#define ST7789_SDA 48 // MOSI -#define ST7789_SCK 38 -#define ST7789_RESET 40 -#define ST7789_MISO 4 -#define ST7789_BUSY -1 -#define VTFT_CTRL 7 -// #define TFT_BL 3 -#define VTFT_LEDA 17 -#define TFT_BACKLIGHT_ON HIGH -// #define TFT_BL 17 -// #define TFT_BACKLIGHT_ON HIGH -// #define ST7789_BL 3 -#define ST7789_SPI_HOST SPI2_HOST -// #define ST7789_BACKLIGHT_EN 17 -#define SPI_FREQUENCY 10000000 -#define SPI_READ_FREQUENCY 10000000 -#define TFT_HEIGHT 170 -#define TFT_WIDTH 320 -#define TFT_OFFSET_X 0 -#define TFT_OFFSET_Y 0 -// #define TFT_OFFSET_ROTATION 0 -// #define SCREEN_ROTATE -// #define SCREEN_TRANSITION_FRAMERATE 5 -#define BRIGHTNESS_DEFAULT 100 // Medium Low Brightnes - -// #define SLEEP_TIME 120 - -/* - * SPI interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -// #define VEXT_ENABLE 7 // active low, powers the oled display and the lora antenna boost -#define BUTTON_PIN 0 - -#define ADC_CTRL 46 -#define ADC_CTRL_ENABLED HIGH -#define BATTERY_PIN 6 -#define ADC_CHANNEL ADC1_GPIO6_CHANNEL -#define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 -#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high - -#define USE_SX1262 - -#define LORA_DIO0 -1 // a No connect on the SX1262 module -#define LORA_RESET 12 -#define LORA_DIO1 14 // SX1262 IRQ -#define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled - -#define LORA_SCK 9 -#define LORA_MISO 11 -#define LORA_MOSI 10 -#define LORA_CS 8 - -#define SX126X_CS LORA_CS -#define SX126X_DIO1 LORA_DIO1 -#define SX126X_BUSY LORA_DIO2 -#define SX126X_RESET LORA_RESET - -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h index 3e36d98f5..9e1d8a9a0 100644 --- a/variants/heltec_wireless_paper/pins_arduino.h +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -3,10 +3,16 @@ #include -static const uint8_t LED_BUILTIN = 18; +#define WIFI_Kit_32 true +#define DISPLAY_HEIGHT 64 +#define DISPLAY_WIDTH 128 + +static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN +static const uint8_t KEY_BUILTIN = 0; + static const uint8_t TX = 43; static const uint8_t RX = 44; @@ -54,8 +60,11 @@ static const uint8_t T12 = 12; static const uint8_t T13 = 13; static const uint8_t T14 = 14; +static const uint8_t Vext = 45; +static const uint8_t LED = 18; + static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO1 = 14; +static const uint8_t DIO0 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index c41d6d9df..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -1,12 +1,14 @@ #define LED_PIN 18 -#define BUTTON_PIN 0 -// I2C +// Enable bus for external periherals #define I2C_SDA SDA #define I2C_SCL SCL -// Display (E-Ink) #define USE_EINK + +/* + * eink display pins + */ #define PIN_EINK_CS 4 #define PIN_EINK_BUSY 7 #define PIN_EINK_DC 5 @@ -14,28 +16,32 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -// SPI +/* + * SPI interfaces + */ #define SPI_INTERFACES_COUNT 2 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK -// Power -#define VEXT_ENABLE 45 // Active low, powers the E-Ink display +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 -#define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high -// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 diff --git a/variants/heltec_wireless_paper_v1/pins_arduino.h b/variants/heltec_wireless_paper_v1/pins_arduino.h index 2bb44161a..9e1d8a9a0 100644 --- a/variants/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/heltec_wireless_paper_v1/pins_arduino.h @@ -3,7 +3,11 @@ #include -static const uint8_t LED_BUILTIN = 18; +#define WIFI_Kit_32 true +#define DISPLAY_HEIGHT 64 +#define DISPLAY_WIDTH 128 + +static const uint8_t LED_BUILTIN = 35; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN @@ -61,6 +65,6 @@ static const uint8_t LED = 18; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO1 = 14; +static const uint8_t DIO0 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index c41d6d9df..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -1,12 +1,14 @@ #define LED_PIN 18 -#define BUTTON_PIN 0 -// I2C +// Enable bus for external periherals #define I2C_SDA SDA #define I2C_SCL SCL -// Display (E-Ink) #define USE_EINK + +/* + * eink display pins + */ #define PIN_EINK_CS 4 #define PIN_EINK_BUSY 7 #define PIN_EINK_DC 5 @@ -14,28 +16,32 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -// SPI +/* + * SPI interfaces + */ #define SPI_INTERFACES_COUNT 2 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK -// Power -#define VEXT_ENABLE 45 // Active low, powers the E-Ink display +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 -#define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high -// LoRa #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 diff --git a/variants/heltec_wireless_tracker/platformio.ini b/variants/heltec_wireless_tracker/platformio.ini index c7ecce8ea..3259d563c 100644 --- a/variants/heltec_wireless_tracker/platformio.ini +++ b/variants/heltec_wireless_tracker/platformio.ini @@ -1,7 +1,7 @@ [env:heltec-wireless-tracker] extends = esp32s3_base board = heltec_wireless_tracker -upload_protocol = esptool +upload_protocol = esp-builtin build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker @@ -11,4 +11,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 6a67b0083..ef3e5a645 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -20,7 +20,6 @@ lib_deps = debug_tool = jlink - ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds ;upload_protocol = jlink @@ -28,93 +27,26 @@ debug_tool = jlink ; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) ; programming time is about the same as the bootloader version. ; For information on this see the meshtastic developers documentation for "Development on the NRF52" -[env:rak4631_dbg] +[env:rak4631_dap] extends = env:rak4631 board_level = extra - -; if the builtin version of openocd has a buggy version of semihosting, so use the external version -; platform_packages = platformio/tool-openocd@^3.1200.0 - -build_flags = - ${env:rak4631.build_flags} - -D USE_SEMIHOSTING - -lib_deps = - ${env:rak4631.lib_deps} - https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 - -; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. -; However the built in openocd version in platformio has buggy support for TCP to semihosting. -; -; So I'm now trying the external openocd - but the openocd scripts for nrf52.cfg assume you are using a DAP adapter not an STLINK adapter. -; In theory I could change those scripts. But for now I'm trying going back to a DAP adapter but with the external openocd. - -upload_protocol = stlink +; pyocd pack --i nrf52840 ; eventually use platformio/tool-pyocd@^2.3600.0 instad -;upload_protocol = custom -;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE - -; We want the initial breakpoint at setup() instead of main(). Also we want to enable semihosting at that point so instead of -; debug_init_break = tbreak setup -; we just turn off the platformio tbreak and do it in .gdbinit (where we have more flexibility for scripting) -; also we use a permanent breakpoint so it gets reused each time we restart the debugging session? -debug_init_break = tbreak setup - -; Note: add "monitor arm semihosting_redirect tcp 4444 all" if you want the stdout from the device to go to that port number instead -; (for use by meshtastic command line) -; monitor arm semihosting disable -; monitor debug_level 3 -; -; IMPORTANT: fileio must be disabled before using port 5555 - openocd ver 0.12 has a bug where if enabled it never properly parses the special :tt name -; for stdio access. -; monitor arm semihosting_redirect tcp 5555 stdio - -; Also note: it is _impossible_ to do non blocking reads on the semihost console port (an oversight when ARM specified the semihost API). -; So we'll neve be able to general purpose bi-directional communication with the device over semihosting. -debug_extra_cmds = - echo Running .gdbinit script - monitor arm semihosting enable - monitor arm semihosting_fileio enable - monitor arm semihosting_redirect disable - commands 1 - echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555" - set wantSemihost = true - set useSoftDevice = false - end - +upload_protocol = custom +upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE ; Only reprogram the board if the code has changed debug_load_mode = modified ;debug_load_mode = manual -debug_tool = stlink -;debug_tool = custom -; debug_server = -; openocd -; -f -; /usr/local/share/openocd/scripts/interface/stlink.cfg -; -f -; /usr/local/share/openocd/scripts/target/nrf52.cfg -; $PLATFORMIO_CORE_DIR/packages/tool-openocd/openocd/scripts/interface/cmsis-dap.cfg - -; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) -; programming time is about the same as the bootloader version. -; For information on this see the meshtastic developers documentation for "Development on the NRF52" +debug_tool = custom ; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) -;debug_server = -; pyocd -; gdbserver -; -j -; ${platformio.workspace_dir}/.. -; -t -; nrf52840 -; --semihosting -; --elf -; ${platformio.build_dir}/${this.__env__}/firmware.elf - -; If you want to debug the semihosting support you can turn on extra logging in pyocd with -; -L -; pyocd.debug.semihost.trace=debug - +debug_server = + pyocd + gdbserver + -t + nrf52840 + --elf + ${platformio.build_dir}/${this.__env__}/firmware.elf ; The following is not needed because it automatically tries do this ;debug_server_ready_pattern = -.*GDB server started on port \d+.* ;debug_port = localhost:3333 \ No newline at end of file diff --git a/variants/tlora_t3s3_v1/platformio.ini b/variants/tlora_t3s3_v1/platformio.ini index 0a5797280..002b2f224 100644 --- a/variants/tlora_t3s3_v1/platformio.ini +++ b/variants/tlora_t3s3_v1/platformio.ini @@ -2,7 +2,7 @@ extends = esp32s3_base board = tlora-t3s3-v1 board_check = true -upload_protocol = esptool +upload_protocol = esp-builtin build_flags = ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/tlora_t3s3_v1 diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 766717428..cd3a76d02 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -6,7 +6,7 @@ board = wio-sdk-wm1110 # Remove adafruit USB serial from the build (it is incompatible with using the ch340 serial chip on this board) build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB -; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. @@ -15,19 +15,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink -;debug_tool = stlink -;debug_speed = 4000 -; No need to reflash if the binary hasn't changed -debug_load_mode = modified ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -upload_protocol = nrfutil -;upload_protocol = stlink -; we prefer to stop in setup() because we are an 'ardiuno' app -debug_init_break = tbreak setup - -; we need to turn off BLE/soft device if we are debugging otherwise it will watchdog reset us. -debug_extra_cmds = - echo Running .gdbinit script - commands 1 - set useSoftDevice = false - end +upload_protocol = jlink diff --git a/variants/wio-sdk-wm1110/softdevice/ble.h b/variants/wio-sdk-wm1110/softdevice/ble.h deleted file mode 100644 index 177b436ad..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble.h +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON BLE SoftDevice Common - @{ - @defgroup ble_api Events, type definitions and API calls - @{ - - @brief Module independent events, type definitions and API calls for the BLE SoftDevice. - - */ - -#ifndef BLE_H__ -#define BLE_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_gattc.h" -#include "ble_gatts.h" -#include "ble_l2cap.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_COMMON_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief Common API SVC numbers. - */ -enum BLE_COMMON_SVCS { - SD_BLE_ENABLE = BLE_SVC_BASE, /**< Enable and initialize the BLE stack */ - SD_BLE_EVT_GET, /**< Get an event from the pending events queue. */ - SD_BLE_UUID_VS_ADD, /**< Add a Vendor Specific base UUID. */ - SD_BLE_UUID_DECODE, /**< Decode UUID bytes. */ - SD_BLE_UUID_ENCODE, /**< Encode UUID bytes. */ - SD_BLE_VERSION_GET, /**< Get the local version information (company ID, Link Layer Version, Link Layer Subversion). */ - SD_BLE_USER_MEM_REPLY, /**< User Memory Reply. */ - SD_BLE_OPT_SET, /**< Set a BLE option. */ - SD_BLE_OPT_GET, /**< Get a BLE option. */ - SD_BLE_CFG_SET, /**< Add a configuration to the BLE stack. */ - SD_BLE_UUID_VS_REMOVE, /**< Remove a Vendor Specific base UUID. */ -}; - -/** - * @brief BLE Module Independent Event IDs. - */ -enum BLE_COMMON_EVTS { - BLE_EVT_USER_MEM_REQUEST = BLE_EVT_BASE + 0, /**< User Memory request. See @ref ble_evt_user_mem_request_t - \n Reply with @ref sd_ble_user_mem_reply. */ - BLE_EVT_USER_MEM_RELEASE = BLE_EVT_BASE + 1, /**< User Memory release. See @ref ble_evt_user_mem_release_t */ -}; - -/**@brief BLE Connection Configuration IDs. - * - * IDs that uniquely identify a connection configuration. - */ -enum BLE_CONN_CFGS { - BLE_CONN_CFG_GAP = BLE_CONN_CFG_BASE + 0, /**< BLE GAP specific connection configuration. */ - BLE_CONN_CFG_GATTC = BLE_CONN_CFG_BASE + 1, /**< BLE GATTC specific connection configuration. */ - BLE_CONN_CFG_GATTS = BLE_CONN_CFG_BASE + 2, /**< BLE GATTS specific connection configuration. */ - BLE_CONN_CFG_GATT = BLE_CONN_CFG_BASE + 3, /**< BLE GATT specific connection configuration. */ - BLE_CONN_CFG_L2CAP = BLE_CONN_CFG_BASE + 4, /**< BLE L2CAP specific connection configuration. */ -}; - -/**@brief BLE Common Configuration IDs. - * - * IDs that uniquely identify a common configuration. - */ -enum BLE_COMMON_CFGS { - BLE_COMMON_CFG_VS_UUID = BLE_CFG_BASE, /**< Vendor specific base UUID configuration */ -}; - -/**@brief Common Option IDs. - * IDs that uniquely identify a common option. - */ -enum BLE_COMMON_OPTS { - BLE_COMMON_OPT_PA_LNA = BLE_OPT_BASE + 0, /**< PA and LNA options */ - BLE_COMMON_OPT_CONN_EVT_EXT = BLE_OPT_BASE + 1, /**< Extended connection events option */ - BLE_COMMON_OPT_EXTENDED_RC_CAL = BLE_OPT_BASE + 2, /**< Extended RC calibration option */ -}; - -/** @} */ - -/** @addtogroup BLE_COMMON_DEFINES Defines - * @{ */ - -/** @brief Required pointer alignment for BLE Events. - */ -#define BLE_EVT_PTR_ALIGNMENT 4 - -/** @brief Leaves the maximum of the two arguments. - */ -#define BLE_MAX(a, b) ((a) < (b) ? (b) : (a)) - -/** @brief Maximum possible length for BLE Events. - * @note The highest value used for @ref ble_gatt_conn_cfg_t::att_mtu in any connection configuration shall be used as a - * parameter. If that value has not been configured for any connections then @ref BLE_GATT_ATT_MTU_DEFAULT must be used instead. - */ -#define BLE_EVT_LEN_MAX(ATT_MTU) \ - (offsetof(ble_evt_t, evt.gattc_evt.params.prim_srvc_disc_rsp.services) + ((ATT_MTU)-1) / 4 * sizeof(ble_gattc_service_t)) - -/** @defgroup BLE_USER_MEM_TYPES User Memory Types - * @{ */ -#define BLE_USER_MEM_TYPE_INVALID 0x00 /**< Invalid User Memory Types. */ -#define BLE_USER_MEM_TYPE_GATTS_QUEUED_WRITES 0x01 /**< User Memory for GATTS queued writes. */ -/** @} */ - -/** @defgroup BLE_UUID_VS_COUNTS Vendor Specific base UUID counts - * @{ - */ -#define BLE_UUID_VS_COUNT_DEFAULT 10 /**< Default VS UUID count. */ -#define BLE_UUID_VS_COUNT_MAX 254 /**< Maximum VS UUID count. */ -/** @} */ - -/** @defgroup BLE_COMMON_CFG_DEFAULTS Configuration defaults. - * @{ - */ -#define BLE_CONN_CFG_TAG_DEFAULT 0 /**< Default configuration tag, SoftDevice default connection configuration. */ - -/** @} */ - -/** @} */ - -/** @addtogroup BLE_COMMON_STRUCTURES Structures - * @{ */ - -/**@brief User Memory Block. */ -typedef struct { - uint8_t *p_mem; /**< Pointer to the start of the user memory block. */ - uint16_t len; /**< Length in bytes of the user memory block. */ -} ble_user_mem_block_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_REQUEST. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ -} ble_evt_user_mem_request_t; - -/**@brief Event structure for @ref BLE_EVT_USER_MEM_RELEASE. */ -typedef struct { - uint8_t type; /**< User memory type, see @ref BLE_USER_MEM_TYPES. */ - ble_user_mem_block_t mem_block; /**< User memory block */ -} ble_evt_user_mem_release_t; - -/**@brief Event structure for events not associated with a specific function module. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which this event occurred. */ - union { - ble_evt_user_mem_request_t user_mem_request; /**< User Memory Request Event Parameters. */ - ble_evt_user_mem_release_t user_mem_release; /**< User Memory Release Event Parameters. */ - } params; /**< Event parameter union. */ -} ble_common_evt_t; - -/**@brief BLE Event header. */ -typedef struct { - uint16_t evt_id; /**< Value from a BLE__EVT series. */ - uint16_t evt_len; /**< Length in octets including this header. */ -} ble_evt_hdr_t; - -/**@brief Common BLE Event type, wrapping the module specific event reports. */ -typedef struct { - ble_evt_hdr_t header; /**< Event header. */ - union { - ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ - ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ - ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ - ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */ - ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ - } evt; /**< Event union. */ -} ble_evt_t; - -/** - * @brief Version Information. - */ -typedef struct { - uint8_t version_number; /**< Link Layer Version number. See - https://www.bluetooth.org/en-us/specification/assigned-numbers/link-layer for assigned values. */ - uint16_t company_id; /**< Company ID, Nordic Semiconductor's company ID is 89 (0x0059) - (https://www.bluetooth.org/apps/content/Default.aspx?doc_id=49708). */ - uint16_t - subversion_number; /**< Link Layer Sub Version number, corresponds to the SoftDevice Config ID or Firmware ID (FWID). */ -} ble_version_t; - -/** - * @brief Configuration parameters for the PA and LNA. - */ -typedef struct { - uint8_t enable : 1; /**< Enable toggling for this amplifier */ - uint8_t active_high : 1; /**< Set the pin to be active high */ - uint8_t gpio_pin : 6; /**< The GPIO pin to toggle for this amplifier */ -} ble_pa_lna_cfg_t; - -/** - * @brief PA & LNA GPIO toggle configuration - * - * This option configures the SoftDevice to toggle pins when the radio is active for use with a power amplifier and/or - * a low noise amplifier. - * - * Toggling the pins is achieved by using two PPI channels and a GPIOTE channel. The hardware channel IDs are provided - * by the application and should be regarded as reserved as long as any PA/LNA toggling is enabled. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * @note Setting this option while the radio is in use (i.e. any of the roles are active) may have undefined consequences - * and must be avoided by the application. - */ -typedef struct { - ble_pa_lna_cfg_t pa_cfg; /**< Power Amplifier configuration */ - ble_pa_lna_cfg_t lna_cfg; /**< Low Noise Amplifier configuration */ - - uint8_t ppi_ch_id_set; /**< PPI channel used for radio pin setting */ - uint8_t ppi_ch_id_clr; /**< PPI channel used for radio pin clearing */ - uint8_t gpiote_ch_id; /**< GPIOTE channel used for radio pin toggling */ -} ble_common_opt_pa_lna_t; - -/** - * @brief Configuration of extended BLE connection events. - * - * When enabled the SoftDevice will dynamically extend the connection event when possible. - * - * The connection event length is controlled by the connection configuration as set by @ref ble_gap_conn_cfg_t::event_length. - * The connection event can be extended if there is time to send another packet pair before the start of the next connection - * interval, and if there are no conflicts with other BLE roles requesting radio time. - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended BLE connection events, disabled by default. */ -} ble_common_opt_conn_evt_ext_t; - -/** - * @brief Enable/disable extended RC calibration. - * - * If extended RC calibration is enabled and the internal RC oscillator (@ref NRF_CLOCK_LF_SRC_RC) is used as the SoftDevice - * LFCLK source, the SoftDevice as a peripheral will by default try to increase the receive window if two consecutive packets - * are not received. If it turns out that the packets were not received due to clock drift, the RC calibration is started. - * This calibration comes in addition to the periodic calibration that is configured by @ref sd_softdevice_enable(). When - * using only peripheral connections, the periodic calibration can therefore be configured with a much longer interval as the - * peripheral will be able to detect and adjust automatically to clock drift, and calibrate on demand. - * - * If extended RC calibration is disabled and the internal RC oscillator is used as the SoftDevice LFCLK source, the - * RC oscillator is calibrated periodically as configured by @ref sd_softdevice_enable(). - * - * @note @ref sd_ble_opt_get is not supported for this option. - */ -typedef struct { - uint8_t enable : 1; /**< Enable extended RC calibration, enabled by default. */ -} ble_common_opt_extended_rc_cal_t; - -/**@brief Option structure for common options. */ -typedef union { - ble_common_opt_pa_lna_t pa_lna; /**< Parameters for controlling PA and LNA pin toggling. */ - ble_common_opt_conn_evt_ext_t conn_evt_ext; /**< Parameters for enabling extended connection events. */ - ble_common_opt_extended_rc_cal_t extended_rc_cal; /**< Parameters for enabling extended RC calibration. */ -} ble_common_opt_t; - -/**@brief Common BLE Option type, wrapping the module specific options. */ -typedef union { - ble_common_opt_t common_opt; /**< COMMON options, opt_id in @ref BLE_COMMON_OPTS series. */ - ble_gap_opt_t gap_opt; /**< GAP option, opt_id in @ref BLE_GAP_OPTS series. */ - ble_gattc_opt_t gattc_opt; /**< GATTC option, opt_id in @ref BLE_GATTC_OPTS series. */ -} ble_opt_t; - -/**@brief BLE connection configuration type, wrapping the module specific configurations, set with - * @ref sd_ble_cfg_set. - * - * @note Connection configurations don't have to be set. - * In the case that no configurations has been set, or fewer connection configurations has been set than enabled connections, - * the default connection configuration will be automatically added for the remaining connections. - * When creating connections with the default configuration, @ref BLE_CONN_CFG_TAG_DEFAULT should be used in - * place of @ref ble_conn_cfg_t::conn_cfg_tag. - * - * @sa sd_ble_gap_adv_start() - * @sa sd_ble_gap_connect() - * - * @mscs - * @mmsc{@ref BLE_CONN_CFG} - * @endmscs - - */ -typedef struct { - uint8_t conn_cfg_tag; /**< The application chosen tag it can use with the - @ref sd_ble_gap_adv_start() and @ref sd_ble_gap_connect() calls - to select this configuration when creating a connection. - Must be different for all connection configurations added and not @ref BLE_CONN_CFG_TAG_DEFAULT. */ - union { - ble_gap_conn_cfg_t gap_conn_cfg; /**< GAP connection configuration, cfg_id is @ref BLE_CONN_CFG_GAP. */ - ble_gattc_conn_cfg_t gattc_conn_cfg; /**< GATTC connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTC. */ - ble_gatts_conn_cfg_t gatts_conn_cfg; /**< GATTS connection configuration, cfg_id is @ref BLE_CONN_CFG_GATTS. */ - ble_gatt_conn_cfg_t gatt_conn_cfg; /**< GATT connection configuration, cfg_id is @ref BLE_CONN_CFG_GATT. */ - ble_l2cap_conn_cfg_t l2cap_conn_cfg; /**< L2CAP connection configuration, cfg_id is @ref BLE_CONN_CFG_L2CAP. */ - } params; /**< Connection configuration union. */ -} ble_conn_cfg_t; - -/** - * @brief Configuration of Vendor Specific base UUIDs, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM Too many UUIDs configured. - */ -typedef struct { - uint8_t vs_uuid_count; /**< Number of 128-bit Vendor Specific base UUID bases to allocate memory for. - Default value is @ref BLE_UUID_VS_COUNT_DEFAULT. Maximum value is - @ref BLE_UUID_VS_COUNT_MAX. */ -} ble_common_cfg_vs_uuid_t; - -/**@brief Common BLE Configuration type, wrapping the common configurations. */ -typedef union { - ble_common_cfg_vs_uuid_t vs_uuid_cfg; /**< Vendor Specific base UUID configuration, cfg_id is @ref BLE_COMMON_CFG_VS_UUID. */ -} ble_common_cfg_t; - -/**@brief BLE Configuration type, wrapping the module specific configurations. */ -typedef union { - ble_conn_cfg_t conn_cfg; /**< Connection specific configurations, cfg_id in @ref BLE_CONN_CFGS series. */ - ble_common_cfg_t common_cfg; /**< Global common configurations, cfg_id in @ref BLE_COMMON_CFGS series. */ - ble_gap_cfg_t gap_cfg; /**< Global GAP configurations, cfg_id in @ref BLE_GAP_CFGS series. */ - ble_gatts_cfg_t gatts_cfg; /**< Global GATTS configuration, cfg_id in @ref BLE_GATTS_CFGS series. */ -} ble_cfg_t; - -/** @} */ - -/** @addtogroup BLE_COMMON_FUNCTIONS Functions - * @{ */ - -/**@brief Enable the BLE stack - * - * @param[in, out] p_app_ram_base Pointer to a variable containing the start address of the - * application RAM region (APP_RAM_BASE). On return, this will - * contain the minimum start address of the application RAM region - * required by the SoftDevice for this configuration. - * @warning After this call, the SoftDevice may generate several events. The list of events provided - * below require the application to initiate a SoftDevice API call. The corresponding API call - * is referenced in the event documentation. - * If the application fails to do so, the BLE connection may timeout, or the SoftDevice may stop - * communicating with the peer device. - * - @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST - * - @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST - * - @ref BLE_GAP_EVT_SEC_INFO_REQUEST - * - @ref BLE_GAP_EVT_SEC_REQUEST - * - @ref BLE_GAP_EVT_AUTH_KEY_REQUEST - * - @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST - * - @ref BLE_EVT_USER_MEM_REQUEST - * - @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note At runtime the IC's RAM is split into 2 regions: The SoftDevice RAM region is located - * between 0x20000000 and APP_RAM_BASE-1 and the application's RAM region is located between - * APP_RAM_BASE and the start of the call stack. - * - * @details This call initializes the BLE stack, no BLE related function other than @ref - * sd_ble_cfg_set can be called before this one. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The BLE stack has been initialized successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized and cannot be reinitialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NO_MEM One or more of the following is true: - * - The amount of memory assigned to the SoftDevice by *p_app_ram_base is not - * large enough to fit this configuration's memory requirement. Check *p_app_ram_base - * and set the start address of the application RAM region accordingly. - * - Dynamic part of the SoftDevice RAM region is larger then 64 kB which - * is currently not supported. - * @retval ::NRF_ERROR_RESOURCES The total number of L2CAP Channels configured using @ref sd_ble_cfg_set is too large. - */ -SVCALL(SD_BLE_ENABLE, uint32_t, sd_ble_enable(uint32_t *p_app_ram_base)); - -/**@brief Add configurations for the BLE stack - * - * @param[in] cfg_id Config ID, see @ref BLE_CONN_CFGS, @ref BLE_COMMON_CFGS, @ref - * BLE_GAP_CFGS or @ref BLE_GATTS_CFGS. - * @param[in] p_cfg Pointer to a ble_cfg_t structure containing the configuration value. - * @param[in] app_ram_base The start address of the application RAM region (APP_RAM_BASE). - * See @ref sd_ble_enable for details about APP_RAM_BASE. - * - * @note The memory requirement for a specific configuration will not increase between SoftDevices - * with the same major version number. - * - * @note If a configuration is set more than once, the last one set is the one that takes effect on - * @ref sd_ble_enable. - * - * @note Any part of the BLE stack that is NOT configured with @ref sd_ble_cfg_set will have default - * configuration. - * - * @note @ref sd_ble_cfg_set may be called at any time when the SoftDevice is enabled (see @ref - * sd_softdevice_enable) while the BLE part of the SoftDevice is not enabled (see @ref - * sd_ble_enable). - * - * @note Error codes for the configurations are described in the configuration structs. - * - * @mscs - * @mmsc{@ref BLE_COMMON_ENABLE} - * @endmscs - * - * @retval ::NRF_SUCCESS The configuration has been added successfully. - * @retval ::NRF_ERROR_INVALID_STATE The BLE stack had already been initialized. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid cfg_id supplied. - * @retval ::NRF_ERROR_NO_MEM The amount of memory assigned to the SoftDevice by app_ram_base is not - * large enough to fit this configuration's memory requirement. - */ -SVCALL(SD_BLE_CFG_SET, uint32_t, sd_ble_cfg_set(uint32_t cfg_id, ble_cfg_t const *p_cfg, uint32_t app_ram_base)); - -/**@brief Get an event from the pending events queue. - * - * @param[out] p_dest Pointer to buffer to be filled in with an event, or NULL to retrieve the event length. - * This buffer must be aligned to the extend defined by @ref BLE_EVT_PTR_ALIGNMENT. - * The buffer should be interpreted as a @ref ble_evt_t struct. - * @param[in, out] p_len Pointer the length of the buffer, on return it is filled with the event length. - * - * @details This call allows the application to pull a BLE event from the BLE stack. The application is signaled that - * an event is available from the BLE stack by the triggering of the SD_EVT_IRQn interrupt. - * The application is free to choose whether to call this function from thread mode (main context) or directly from the - * Interrupt Service Routine that maps to SD_EVT_IRQn. In any case however, and because the BLE stack runs at a higher - * priority than the application, this function should be called in a loop (until @ref NRF_ERROR_NOT_FOUND is returned) - * every time SD_EVT_IRQn is raised to ensure that all available events are pulled from the BLE stack. Failure to do so - * could potentially leave events in the internal queue without the application being aware of this fact. - * - * Sizing the p_dest buffer is equally important, since the application needs to provide all the memory necessary for the event to - * be copied into application memory. If the buffer provided is not large enough to fit the entire contents of the event, - * @ref NRF_ERROR_DATA_SIZE will be returned and the application can then call again with a larger buffer size. - * The maximum possible event length is defined by @ref BLE_EVT_LEN_MAX. The application may also "peek" the event length - * by providing p_dest as a NULL pointer and inspecting the value of *p_len upon return: - * - * \code - * uint16_t len; - * errcode = sd_ble_evt_get(NULL, &len); - * \endcode - * - * @mscs - * @mmsc{@ref BLE_COMMON_IRQ_EVT_MSC} - * @mmsc{@ref BLE_COMMON_THREAD_EVT_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Event pulled and stored into the supplied buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or not sufficiently aligned pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND No events ready to be pulled. - * @retval ::NRF_ERROR_DATA_SIZE Event ready but could not fit into the supplied buffer. - */ -SVCALL(SD_BLE_EVT_GET, uint32_t, sd_ble_evt_get(uint8_t *p_dest, uint16_t *p_len)); - -/**@brief Add a Vendor Specific base UUID. - * - * @details This call enables the application to add a Vendor Specific base UUID to the BLE stack's table, for later - * use with all other modules and APIs. This then allows the application to use the shorter, 24-bit @ref ble_uuid_t - * format when dealing with both 16-bit and 128-bit UUIDs without having to check for lengths and having split code - * paths. This is accomplished by extending the grouping mechanism that the Bluetooth SIG standard base UUID uses - * for all other 128-bit UUIDs. The type field in the @ref ble_uuid_t structure is an index (relative to - * @ref BLE_UUID_TYPE_VENDOR_BEGIN) to the table populated by multiple calls to this function, and the UUID field - * in the same structure contains the 2 bytes at indexes 12 and 13. The number of possible 128-bit UUIDs available to - * the application is therefore the number of Vendor Specific UUIDs added with the help of this function times 65536, - * although restricted to modifying bytes 12 and 13 for each of the entries in the supplied array. - * - * @note Bytes 12 and 13 of the provided UUID will not be used internally, since those are always replaced by - * the 16-bit uuid field in @ref ble_uuid_t. - * - * @note If a UUID is already present in the BLE stack's internal table, the corresponding index will be returned in - * p_uuid_type along with an @ref NRF_SUCCESS error code. - * - * @param[in] p_vs_uuid Pointer to a 16-octet (128-bit) little endian Vendor Specific base UUID disregarding - * bytes 12 and 13. - * @param[out] p_uuid_type Pointer to a uint8_t where the type field in @ref ble_uuid_t corresponding to this UUID will be - * stored. - * - * @retval ::NRF_SUCCESS Successfully added the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_vs_uuid or p_uuid_type is NULL or invalid. - * @retval ::NRF_ERROR_NO_MEM If there are no more free slots for VS UUIDs. - */ -SVCALL(SD_BLE_UUID_VS_ADD, uint32_t, sd_ble_uuid_vs_add(ble_uuid128_t const *p_vs_uuid, uint8_t *p_uuid_type)); - -/**@brief Remove a Vendor Specific base UUID. - * - * @details This call removes a Vendor Specific base UUID. This function allows - * the application to reuse memory allocated for Vendor Specific base UUIDs. - * - * @note Currently this function can only be called with a p_uuid_type set to @ref BLE_UUID_TYPE_UNKNOWN or the last added UUID - * type. - * - * @param[inout] p_uuid_type Pointer to a uint8_t where its value matches the UUID type in @ref ble_uuid_t::type to be removed. - * If the type is set to @ref BLE_UUID_TYPE_UNKNOWN, or the pointer is NULL, the last Vendor Specific - * base UUID will be removed. If the function returns successfully, the UUID type that was removed will - * be written back to @p p_uuid_type. If function returns with a failure, it contains the last type that - * is in use by the ATT Server. - * - * @retval ::NRF_SUCCESS Successfully removed the Vendor Specific base UUID. - * @retval ::NRF_ERROR_INVALID_ADDR If p_uuid_type is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If p_uuid_type points to a non-valid UUID type. - * @retval ::NRF_ERROR_FORBIDDEN If the Vendor Specific base UUID is in use by the ATT Server. - */ -SVCALL(SD_BLE_UUID_VS_REMOVE, uint32_t, sd_ble_uuid_vs_remove(uint8_t *p_uuid_type)); - -/** @brief Decode little endian raw UUID bytes (16-bit or 128-bit) into a 24 bit @ref ble_uuid_t structure. - * - * @details The raw UUID bytes excluding bytes 12 and 13 (i.e. bytes 0-11 and 14-15) of p_uuid_le are compared - * to the corresponding ones in each entry of the table of Vendor Specific base UUIDs - * to look for a match. If there is such a match, bytes 12 and 13 are returned as p_uuid->uuid and the index - * relative to @ref BLE_UUID_TYPE_VENDOR_BEGIN as p_uuid->type. - * - * @note If the UUID length supplied is 2, then the type set by this call will always be @ref BLE_UUID_TYPE_BLE. - * - * @param[in] uuid_le_len Length in bytes of the buffer pointed to by p_uuid_le (must be 2 or 16 bytes). - * @param[in] p_uuid_le Pointer pointing to little endian raw UUID bytes. - * @param[out] p_uuid Pointer to a @ref ble_uuid_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Successfully decoded into the @ref ble_uuid_t structure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid UUID length. - * @retval ::NRF_ERROR_NOT_FOUND For a 128-bit UUID, no match in the populated table of UUIDs. - */ -SVCALL(SD_BLE_UUID_DECODE, uint32_t, sd_ble_uuid_decode(uint8_t uuid_le_len, uint8_t const *p_uuid_le, ble_uuid_t *p_uuid)); - -/** @brief Encode a @ref ble_uuid_t structure into little endian raw UUID bytes (16-bit or 128-bit). - * - * @note The pointer to the destination buffer p_uuid_le may be NULL, in which case only the validity and size of p_uuid is - * computed. - * - * @param[in] p_uuid Pointer to a @ref ble_uuid_t structure that will be encoded into bytes. - * @param[out] p_uuid_le_len Pointer to a uint8_t that will be filled with the encoded length (2 or 16 bytes). - * @param[out] p_uuid_le Pointer to a buffer where the little endian raw UUID bytes (2 or 16) will be stored. - * - * @retval ::NRF_SUCCESS Successfully encoded into the buffer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid UUID type. - */ -SVCALL(SD_BLE_UUID_ENCODE, uint32_t, sd_ble_uuid_encode(ble_uuid_t const *p_uuid, uint8_t *p_uuid_le_len, uint8_t *p_uuid_le)); - -/**@brief Get Version Information. - * - * @details This call allows the application to get the BLE stack version information. - * - * @param[out] p_version Pointer to a ble_version_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Version information stored successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy (typically doing a locally-initiated disconnection procedure). - */ -SVCALL(SD_BLE_VERSION_GET, uint32_t, sd_ble_version_get(ble_version_t *p_version)); - -/**@brief Provide a user memory block. - * - * @note This call can only be used as a response to a @ref BLE_EVT_USER_MEM_REQUEST event issued to the application. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_block Pointer to a user memory block structure or NULL if memory is managed by the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid user memory block length supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection state or no user memory request pending. - */ -SVCALL(SD_BLE_USER_MEM_REPLY, uint32_t, sd_ble_user_mem_reply(uint16_t conn_handle, ble_user_mem_block_t const *p_block)); - -/**@brief Set a BLE option. - * - * @details This call allows the application to set the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS, @ref BLE_GAP_OPTS, and @ref BLE_GATTC_OPTS. - * @param[in] p_opt Pointer to a @ref ble_opt_t structure containing the option value. - * - * @retval ::NRF_SUCCESS Option set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to set the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - */ -SVCALL(SD_BLE_OPT_SET, uint32_t, sd_ble_opt_set(uint32_t opt_id, ble_opt_t const *p_opt)); - -/**@brief Get a BLE option. - * - * @details This call allows the application to retrieve the value of an option. - * - * @param[in] opt_id Option ID, see @ref BLE_COMMON_OPTS and @ref BLE_GAP_OPTS. - * @param[out] p_opt Pointer to a ble_opt_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Option retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Unable to retrieve the parameter at this time. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy or the previous procedure has not completed. - * @retval ::NRF_ERROR_NOT_SUPPORTED This option is not supported. - * - */ -SVCALL(SD_BLE_OPT_GET, uint32_t, sd_ble_opt_get(uint32_t opt_id, ble_opt_t *p_opt)); - -/** @} */ -#ifdef __cplusplus -} -#endif -#endif /* BLE_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_err.h b/variants/wio-sdk-wm1110/softdevice/ble_err.h deleted file mode 100644 index d20f6d141..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_err.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @addtogroup nrf_error - @{ - @ingroup BLE_COMMON - @} - - @defgroup ble_err General error codes - @{ - - @brief General error code definitions for the BLE API. - - @ingroup BLE_COMMON -*/ -#ifndef NRF_BLE_ERR_H__ -#define NRF_BLE_ERR_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* @defgroup BLE_ERRORS Error Codes - * @{ */ -#define BLE_ERROR_NOT_ENABLED (NRF_ERROR_STK_BASE_NUM + 0x001) /**< @ref sd_ble_enable has not been called. */ -#define BLE_ERROR_INVALID_CONN_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x002) /**< Invalid connection handle. */ -#define BLE_ERROR_INVALID_ATTR_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x003) /**< Invalid attribute handle. */ -#define BLE_ERROR_INVALID_ADV_HANDLE (NRF_ERROR_STK_BASE_NUM + 0x004) /**< Invalid advertising handle. */ -#define BLE_ERROR_INVALID_ROLE (NRF_ERROR_STK_BASE_NUM + 0x005) /**< Invalid role. */ -#define BLE_ERROR_BLOCKED_BY_OTHER_LINKS \ - (NRF_ERROR_STK_BASE_NUM + 0x006) /**< The attempt to change link settings failed due to the scheduling of other links. */ -/** @} */ - -/** @defgroup BLE_ERROR_SUBRANGES Module specific error code subranges - * @brief Assignment of subranges for module specific error codes. - * @note For specific error codes, see ble_.h or ble_error_.h. - * @{ */ -#define NRF_L2CAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x100) /**< L2CAP specific errors. */ -#define NRF_GAP_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x200) /**< GAP specific errors. */ -#define NRF_GATTC_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x300) /**< GATT client specific errors. */ -#define NRF_GATTS_ERR_BASE (NRF_ERROR_STK_BASE_NUM + 0x400) /**< GATT server specific errors. */ -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gap.h b/variants/wio-sdk-wm1110/softdevice/ble_gap.h deleted file mode 100644 index 8ebdfa82b..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gap.h +++ /dev/null @@ -1,2895 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GAP Generic Access Profile (GAP) - @{ - @brief Definitions and prototypes for the GAP interface. - */ - -#ifndef BLE_GAP_H__ -#define BLE_GAP_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_GAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GAP API SVC numbers. - */ -enum BLE_GAP_SVCS { - SD_BLE_GAP_ADDR_SET = BLE_GAP_SVC_BASE, /**< Set own Bluetooth Address. */ - SD_BLE_GAP_ADDR_GET = BLE_GAP_SVC_BASE + 1, /**< Get own Bluetooth Address. */ - SD_BLE_GAP_WHITELIST_SET = BLE_GAP_SVC_BASE + 2, /**< Set active whitelist. */ - SD_BLE_GAP_DEVICE_IDENTITIES_SET = BLE_GAP_SVC_BASE + 3, /**< Set device identity list. */ - SD_BLE_GAP_PRIVACY_SET = BLE_GAP_SVC_BASE + 4, /**< Set Privacy settings*/ - SD_BLE_GAP_PRIVACY_GET = BLE_GAP_SVC_BASE + 5, /**< Get Privacy settings*/ - SD_BLE_GAP_ADV_SET_CONFIGURE = BLE_GAP_SVC_BASE + 6, /**< Configure an advertising set. */ - SD_BLE_GAP_ADV_START = BLE_GAP_SVC_BASE + 7, /**< Start Advertising. */ - SD_BLE_GAP_ADV_STOP = BLE_GAP_SVC_BASE + 8, /**< Stop Advertising. */ - SD_BLE_GAP_CONN_PARAM_UPDATE = BLE_GAP_SVC_BASE + 9, /**< Connection Parameter Update. */ - SD_BLE_GAP_DISCONNECT = BLE_GAP_SVC_BASE + 10, /**< Disconnect. */ - SD_BLE_GAP_TX_POWER_SET = BLE_GAP_SVC_BASE + 11, /**< Set TX Power. */ - SD_BLE_GAP_APPEARANCE_SET = BLE_GAP_SVC_BASE + 12, /**< Set Appearance. */ - SD_BLE_GAP_APPEARANCE_GET = BLE_GAP_SVC_BASE + 13, /**< Get Appearance. */ - SD_BLE_GAP_PPCP_SET = BLE_GAP_SVC_BASE + 14, /**< Set PPCP. */ - SD_BLE_GAP_PPCP_GET = BLE_GAP_SVC_BASE + 15, /**< Get PPCP. */ - SD_BLE_GAP_DEVICE_NAME_SET = BLE_GAP_SVC_BASE + 16, /**< Set Device Name. */ - SD_BLE_GAP_DEVICE_NAME_GET = BLE_GAP_SVC_BASE + 17, /**< Get Device Name. */ - SD_BLE_GAP_AUTHENTICATE = BLE_GAP_SVC_BASE + 18, /**< Initiate Pairing/Bonding. */ - SD_BLE_GAP_SEC_PARAMS_REPLY = BLE_GAP_SVC_BASE + 19, /**< Reply with Security Parameters. */ - SD_BLE_GAP_AUTH_KEY_REPLY = BLE_GAP_SVC_BASE + 20, /**< Reply with an authentication key. */ - SD_BLE_GAP_LESC_DHKEY_REPLY = BLE_GAP_SVC_BASE + 21, /**< Reply with an LE Secure Connections DHKey. */ - SD_BLE_GAP_KEYPRESS_NOTIFY = BLE_GAP_SVC_BASE + 22, /**< Notify of a keypress during an authentication procedure. */ - SD_BLE_GAP_LESC_OOB_DATA_GET = BLE_GAP_SVC_BASE + 23, /**< Get the local LE Secure Connections OOB data. */ - SD_BLE_GAP_LESC_OOB_DATA_SET = BLE_GAP_SVC_BASE + 24, /**< Set the remote LE Secure Connections OOB data. */ - SD_BLE_GAP_ENCRYPT = BLE_GAP_SVC_BASE + 25, /**< Initiate encryption procedure. */ - SD_BLE_GAP_SEC_INFO_REPLY = BLE_GAP_SVC_BASE + 26, /**< Reply with Security Information. */ - SD_BLE_GAP_CONN_SEC_GET = BLE_GAP_SVC_BASE + 27, /**< Obtain connection security level. */ - SD_BLE_GAP_RSSI_START = BLE_GAP_SVC_BASE + 28, /**< Start reporting of changes in RSSI. */ - SD_BLE_GAP_RSSI_STOP = BLE_GAP_SVC_BASE + 29, /**< Stop reporting of changes in RSSI. */ - SD_BLE_GAP_SCAN_START = BLE_GAP_SVC_BASE + 30, /**< Start Scanning. */ - SD_BLE_GAP_SCAN_STOP = BLE_GAP_SVC_BASE + 31, /**< Stop Scanning. */ - SD_BLE_GAP_CONNECT = BLE_GAP_SVC_BASE + 32, /**< Connect. */ - SD_BLE_GAP_CONNECT_CANCEL = BLE_GAP_SVC_BASE + 33, /**< Cancel ongoing connection procedure. */ - SD_BLE_GAP_RSSI_GET = BLE_GAP_SVC_BASE + 34, /**< Get the last RSSI sample. */ - SD_BLE_GAP_PHY_UPDATE = BLE_GAP_SVC_BASE + 35, /**< Initiate or respond to a PHY Update Procedure. */ - SD_BLE_GAP_DATA_LENGTH_UPDATE = BLE_GAP_SVC_BASE + 36, /**< Initiate or respond to a Data Length Update Procedure. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_START = BLE_GAP_SVC_BASE + 37, /**< Start Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP = BLE_GAP_SVC_BASE + 38, /**< Stop Quality of Service (QoS) channel survey module. */ - SD_BLE_GAP_ADV_ADDR_GET = BLE_GAP_SVC_BASE + 39, /**< Get the Address used on air while Advertising. */ - SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET = BLE_GAP_SVC_BASE + 40, /**< Get the next connection event counter. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_START = BLE_GAP_SVC_BASE + 41, /** Start triggering a given task on connection event start. */ - SD_BLE_GAP_CONN_EVT_TRIGGER_STOP = - BLE_GAP_SVC_BASE + 42, /** Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. */ -}; - -/**@brief GAP Event IDs. - * IDs that uniquely identify an event coming from the stack to the application. - */ -enum BLE_GAP_EVTS { - BLE_GAP_EVT_CONNECTED = - BLE_GAP_EVT_BASE, /**< Connected to peer. \n See @ref ble_gap_evt_connected_t */ - BLE_GAP_EVT_DISCONNECTED = - BLE_GAP_EVT_BASE + 1, /**< Disconnected from peer. \n See @ref ble_gap_evt_disconnected_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE = - BLE_GAP_EVT_BASE + 2, /**< Connection Parameters updated. \n See @ref ble_gap_evt_conn_param_update_t. */ - BLE_GAP_EVT_SEC_PARAMS_REQUEST = - BLE_GAP_EVT_BASE + 3, /**< Request to provide security parameters. \n Reply with @ref sd_ble_gap_sec_params_reply. - \n See @ref ble_gap_evt_sec_params_request_t. */ - BLE_GAP_EVT_SEC_INFO_REQUEST = - BLE_GAP_EVT_BASE + 4, /**< Request to provide security information. \n Reply with @ref sd_ble_gap_sec_info_reply. - \n See @ref ble_gap_evt_sec_info_request_t. */ - BLE_GAP_EVT_PASSKEY_DISPLAY = - BLE_GAP_EVT_BASE + 5, /**< Request to display a passkey to the user. \n In LESC Numeric Comparison, reply with @ref - sd_ble_gap_auth_key_reply. \n See @ref ble_gap_evt_passkey_display_t. */ - BLE_GAP_EVT_KEY_PRESSED = - BLE_GAP_EVT_BASE + 6, /**< Notification of a keypress on the remote device.\n See @ref ble_gap_evt_key_pressed_t */ - BLE_GAP_EVT_AUTH_KEY_REQUEST = - BLE_GAP_EVT_BASE + 7, /**< Request to provide an authentication key. \n Reply with @ref sd_ble_gap_auth_key_reply. - \n See @ref ble_gap_evt_auth_key_request_t. */ - BLE_GAP_EVT_LESC_DHKEY_REQUEST = - BLE_GAP_EVT_BASE + 8, /**< Request to calculate an LE Secure Connections DHKey. \n Reply with @ref - sd_ble_gap_lesc_dhkey_reply. \n See @ref ble_gap_evt_lesc_dhkey_request_t */ - BLE_GAP_EVT_AUTH_STATUS = - BLE_GAP_EVT_BASE + 9, /**< Authentication procedure completed with status. \n See @ref ble_gap_evt_auth_status_t. */ - BLE_GAP_EVT_CONN_SEC_UPDATE = - BLE_GAP_EVT_BASE + 10, /**< Connection security updated. \n See @ref ble_gap_evt_conn_sec_update_t. */ - BLE_GAP_EVT_TIMEOUT = - BLE_GAP_EVT_BASE + 11, /**< Timeout expired. \n See @ref ble_gap_evt_timeout_t. */ - BLE_GAP_EVT_RSSI_CHANGED = - BLE_GAP_EVT_BASE + 12, /**< RSSI report. \n See @ref ble_gap_evt_rssi_changed_t. */ - BLE_GAP_EVT_ADV_REPORT = - BLE_GAP_EVT_BASE + 13, /**< Advertising report. \n See @ref ble_gap_evt_adv_report_t. */ - BLE_GAP_EVT_SEC_REQUEST = - BLE_GAP_EVT_BASE + 14, /**< Security Request. \n Reply with @ref sd_ble_gap_authenticate -\n or with @ref sd_ble_gap_encrypt if required security information is available -. \n See @ref ble_gap_evt_sec_request_t. */ - BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 15, /**< Connection Parameter Update Request. \n Reply with @ref - sd_ble_gap_conn_param_update. \n See @ref ble_gap_evt_conn_param_update_request_t. */ - BLE_GAP_EVT_SCAN_REQ_REPORT = - BLE_GAP_EVT_BASE + 16, /**< Scan request report. \n See @ref ble_gap_evt_scan_req_report_t. */ - BLE_GAP_EVT_PHY_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 17, /**< PHY Update Request. \n Reply with @ref sd_ble_gap_phy_update. \n - See @ref ble_gap_evt_phy_update_request_t. */ - BLE_GAP_EVT_PHY_UPDATE = - BLE_GAP_EVT_BASE + 18, /**< PHY Update Procedure is complete. \n See @ref ble_gap_evt_phy_update_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST = - BLE_GAP_EVT_BASE + 19, /**< Data Length Update Request. \n Reply with @ref - sd_ble_gap_data_length_update. \n See @ref ble_gap_evt_data_length_update_request_t. */ - BLE_GAP_EVT_DATA_LENGTH_UPDATE = - BLE_GAP_EVT_BASE + - 20, /**< LL Data Channel PDU payload length updated. \n See @ref ble_gap_evt_data_length_update_t. */ - BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT = - BLE_GAP_EVT_BASE + - 21, /**< Channel survey report. \n See @ref ble_gap_evt_qos_channel_survey_report_t. */ - BLE_GAP_EVT_ADV_SET_TERMINATED = - BLE_GAP_EVT_BASE + - 22, /**< Advertising set terminated. \n See @ref ble_gap_evt_adv_set_terminated_t. */ -}; - -/**@brief GAP Option IDs. - * IDs that uniquely identify a GAP option. - */ -enum BLE_GAP_OPTS { - BLE_GAP_OPT_CH_MAP = BLE_GAP_OPT_BASE, /**< Channel Map. @ref ble_gap_opt_ch_map_t */ - BLE_GAP_OPT_LOCAL_CONN_LATENCY = BLE_GAP_OPT_BASE + 1, /**< Local connection latency. @ref ble_gap_opt_local_conn_latency_t */ - BLE_GAP_OPT_PASSKEY = BLE_GAP_OPT_BASE + 2, /**< Set passkey. @ref ble_gap_opt_passkey_t */ - BLE_GAP_OPT_COMPAT_MODE_1 = BLE_GAP_OPT_BASE + 3, /**< Compatibility mode. @ref ble_gap_opt_compat_mode_1_t */ - BLE_GAP_OPT_AUTH_PAYLOAD_TIMEOUT = - BLE_GAP_OPT_BASE + 4, /**< Set Authenticated payload timeout. @ref ble_gap_opt_auth_payload_timeout_t */ - BLE_GAP_OPT_SLAVE_LATENCY_DISABLE = - BLE_GAP_OPT_BASE + 5, /**< Disable slave latency. @ref ble_gap_opt_slave_latency_disable_t */ -}; - -/**@brief GAP Configuration IDs. - * - * IDs that uniquely identify a GAP configuration. - */ -enum BLE_GAP_CFGS { - BLE_GAP_CFG_ROLE_COUNT = BLE_GAP_CFG_BASE, /**< Role count configuration. */ - BLE_GAP_CFG_DEVICE_NAME = BLE_GAP_CFG_BASE + 1, /**< Device name configuration. */ - BLE_GAP_CFG_PPCP_INCL_CONFIG = BLE_GAP_CFG_BASE + 2, /**< Peripheral Preferred Connection Parameters characteristic - inclusion configuration. */ - BLE_GAP_CFG_CAR_INCL_CONFIG = BLE_GAP_CFG_BASE + 3, /**< Central Address Resolution characteristic - inclusion configuration. */ -}; - -/**@brief GAP TX Power roles. - */ -enum BLE_GAP_TX_POWER_ROLES { - BLE_GAP_TX_POWER_ROLE_ADV = 1, /**< Advertiser role. */ - BLE_GAP_TX_POWER_ROLE_SCAN_INIT = 2, /**< Scanner and initiator role. */ - BLE_GAP_TX_POWER_ROLE_CONN = 3, /**< Connection role. */ -}; - -/** @} */ - -/**@addtogroup BLE_GAP_DEFINES Defines - * @{ */ - -/**@defgroup BLE_ERRORS_GAP SVC return values specific to GAP - * @{ */ -#define BLE_ERROR_GAP_UUID_LIST_MISMATCH \ - (NRF_GAP_ERR_BASE + 0x000) /**< UUID list does not contain an integral number of UUIDs. */ -#define BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST \ - (NRF_GAP_ERR_BASE + 0x001) /**< Use of Whitelist not permitted with discoverable advertising. */ -#define BLE_ERROR_GAP_INVALID_BLE_ADDR \ - (NRF_GAP_ERR_BASE + 0x002) /**< The upper two bits of the address do not correspond to the specified address type. */ -#define BLE_ERROR_GAP_WHITELIST_IN_USE \ - (NRF_GAP_ERR_BASE + 0x003) /**< Attempt to modify the whitelist while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE \ - (NRF_GAP_ERR_BASE + 0x004) /**< Attempt to modify the device identity list while already in use by another operation. */ -#define BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE \ - (NRF_GAP_ERR_BASE + 0x005) /**< The device identity list contains entries with duplicate identity addresses. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLES GAP Roles - * @{ */ -#define BLE_GAP_ROLE_INVALID 0x0 /**< Invalid Role. */ -#define BLE_GAP_ROLE_PERIPH 0x1 /**< Peripheral Role. */ -#define BLE_GAP_ROLE_CENTRAL 0x2 /**< Central Role. */ -/**@} */ - -/**@defgroup BLE_GAP_TIMEOUT_SOURCES GAP Timeout sources - * @{ */ -#define BLE_GAP_TIMEOUT_SRC_SCAN 0x01 /**< Scanning timeout. */ -#define BLE_GAP_TIMEOUT_SRC_CONN 0x02 /**< Connection timeout. */ -#define BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD 0x03 /**< Authenticated payload timeout. */ -/**@} */ - -/**@defgroup BLE_GAP_ADDR_TYPES GAP Address types - * @{ */ -#define BLE_GAP_ADDR_TYPE_PUBLIC 0x00 /**< Public (identity) address.*/ -#define BLE_GAP_ADDR_TYPE_RANDOM_STATIC 0x01 /**< Random static (identity) address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE 0x02 /**< Random private resolvable address. */ -#define BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE 0x03 /**< Random private non-resolvable address. */ -#define BLE_GAP_ADDR_TYPE_ANONYMOUS \ - 0x7F /**< An advertiser may advertise without its address. \ - This type of advertising is called anonymous. */ -/**@} */ - -/**@brief The default interval in seconds at which a private address is refreshed. */ -#define BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S (900) /* 15 minutes. */ -/**@brief The maximum interval in seconds at which a private address can be refreshed. */ -#define BLE_GAP_MAX_PRIVATE_ADDR_CYCLE_INTERVAL_S (41400) /* 11 hours 30 minutes. */ - -/** @brief BLE address length. */ -#define BLE_GAP_ADDR_LEN (6) - -/**@defgroup BLE_GAP_PRIVACY_MODES Privacy modes - * @{ */ -#define BLE_GAP_PRIVACY_MODE_OFF 0x00 /**< Device will send and accept its identity address for its own address. */ -#define BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY 0x01 /**< Device will send and accept only private addresses for its own address. */ -#define BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY \ - 0x02 /**< Device will send and accept only private addresses for its own address, \ - and will not accept a peer using identity address as sender address when \ - the peer IRK is exchanged, non-zero and added to the identity list. */ -/**@} */ - -/** @brief Invalid power level. */ -#define BLE_GAP_POWER_LEVEL_INVALID 127 - -/** @brief Advertising set handle not set. */ -#define BLE_GAP_ADV_SET_HANDLE_NOT_SET (0xFF) - -/** @brief The default number of advertising sets. */ -#define BLE_GAP_ADV_SET_COUNT_DEFAULT (1) - -/** @brief The maximum number of advertising sets supported by this SoftDevice. */ -#define BLE_GAP_ADV_SET_COUNT_MAX (1) - -/**@defgroup BLE_GAP_ADV_SET_DATA_SIZES Advertising data sizes. - * @{ */ -#define BLE_GAP_ADV_SET_DATA_SIZE_MAX \ - (31) /**< Maximum data length for an advertising set. \ - If more advertising data is required, use extended advertising instead. */ -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for an extended advertising set. */ - -#define BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED \ - (238) /**< Maximum supported data length for an extended connectable advertising set. */ -/**@}. */ - -/** @brief Set ID not available in advertising report. */ -#define BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE 0xFF - -/**@defgroup BLE_GAP_EVT_ADV_SET_TERMINATED_REASON GAP Advertising Set Terminated reasons - * @{ */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_TIMEOUT 0x01 /**< Timeout value reached. */ -#define BLE_GAP_EVT_ADV_SET_TERMINATED_REASON_LIMIT_REACHED 0x02 /**< @ref ble_gap_adv_params_t::max_adv_evts was reached. */ -/**@} */ - -/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format - * @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm - * @{ */ -#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */ -#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */ -#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */ -#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */ -#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */ -#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */ -#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */ -#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */ -#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */ -#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */ -#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */ -#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */ -#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */ -#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */ -#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */ -#define BLE_GAP_AD_TYPE_LESC_CONFIRMATION_VALUE 0x22 /**< LE Secure Connections Confirmation Value */ -#define BLE_GAP_AD_TYPE_LESC_RANDOM_VALUE 0x23 /**< LE Secure Connections Random Value */ -#define BLE_GAP_AD_TYPE_URI 0x24 /**< URI */ -#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */ -#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags - * @{ */ -#define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ -#define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ -#define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ -#define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE \ - (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | \ - BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_INTERVALS GAP Advertising interval max and min - * @{ */ -#define BLE_GAP_ADV_INTERVAL_MIN 0x000020 /**< Minimum Advertising interval in 625 us units, i.e. 20 ms. */ -#define BLE_GAP_ADV_INTERVAL_MAX 0x004000 /**< Maximum Advertising interval in 625 us units, i.e. 10.24 s. */ - /**@} */ - -/**@defgroup BLE_GAP_SCAN_INTERVALS GAP Scan interval max and min - * @{ */ -#define BLE_GAP_SCAN_INTERVAL_MIN 0x0004 /**< Minimum Scan interval in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_INTERVAL_MAX 0xFFFF /**< Maximum Scan interval in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_WINDOW GAP Scan window max and min - * @{ */ -#define BLE_GAP_SCAN_WINDOW_MIN 0x0004 /**< Minimum Scan window in 625 us units, i.e. 2.5 ms. */ -#define BLE_GAP_SCAN_WINDOW_MAX 0xFFFF /**< Maximum Scan window in 625 us units, i.e. 40,959.375 s. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_TIMEOUT GAP Scan timeout max and min - * @{ */ -#define BLE_GAP_SCAN_TIMEOUT_MIN 0x0001 /**< Minimum Scan timeout in 10 ms units, i.e 10 ms. */ -#define BLE_GAP_SCAN_TIMEOUT_UNLIMITED 0x0000 /**< Continue to scan forever. */ - /** @} */ - -/**@defgroup BLE_GAP_SCAN_BUFFER_SIZE GAP Minimum scanner buffer size - * - * Scan buffers are used for storing advertising data received from an advertiser. - * If ble_gap_scan_params_t::extended is set to 0, @ref BLE_GAP_SCAN_BUFFER_MIN is the minimum scan buffer length. - * else the minimum scan buffer size is @ref BLE_GAP_SCAN_BUFFER_EXTENDED_MIN. - * @{ */ -#define BLE_GAP_SCAN_BUFFER_MIN \ - (31) /**< Minimum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_MAX \ - (31) /**< Maximum data length for an \ - advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MIN \ - (255) /**< Minimum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX \ - (1650) /**< Maximum data length for an \ - extended advertising set. */ -#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED \ - (255) /**< Maximum supported data length for \ - an extended advertising set. */ -/** @} */ - -/**@defgroup BLE_GAP_ADV_TYPES GAP Advertising types - * - * Advertising types defined in Bluetooth Core Specification v5.0, Vol 6, Part B, Section 4.4.2. - * - * The maximum advertising data length is defined by @ref BLE_GAP_ADV_SET_DATA_SIZE_MAX. - * The maximum supported data length for an extended advertiser is defined by - * @ref BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED - * Note that some of the advertising types do not support advertising data. Non-scannable types do not support - * scan response data. - * - * @{ */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x01 /**< Connectable and scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE \ - 0x02 /**< Connectable non-scannable directed advertising \ - events. Advertising interval is less that 3.75 ms. \ - Use this type for fast reconnections. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x03 /**< Connectable non-scannable directed advertising \ - events. \ - @note Advertising data is not supported. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x04 /**< Non-connectable scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x05 /**< Non-connectable non-scannable undirected \ - advertising events. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x06 /**< Connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x07 /**< Connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED \ - 0x08 /**< Non-connectable scannable undirected advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED \ - 0x09 /**< Non-connectable scannable directed advertising \ - events using extended advertising PDUs. \ - @note Only scan response data is supported. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED \ - 0x0A /**< Non-connectable non-scannable undirected advertising \ - events using extended advertising PDUs. */ -#define BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED \ - 0x0B /**< Non-connectable non-scannable directed advertising \ - events using extended advertising PDUs. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_FILTER_POLICIES GAP Advertising filter policies - * @{ */ -#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ -#define BLE_GAP_ADV_FP_FILTER_SCANREQ 0x01 /**< Filter scan requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_CONNREQ 0x02 /**< Filter connect requests with whitelist. */ -#define BLE_GAP_ADV_FP_FILTER_BOTH 0x03 /**< Filter both scan and connect requests with whitelist. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status - * @{ */ -#define BLE_GAP_ADV_DATA_STATUS_COMPLETE 0x00 /**< All data in the advertising event have been received. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA \ - 0x01 /**< More data to be received. \ - @note This value will only be used if \ - @ref ble_gap_scan_params_t::report_incomplete_evts and \ - @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED \ - 0x02 /**< Incomplete data. Buffer size insufficient to receive more. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED \ - 0x03 /**< Failed to receive the remaining data. \ - @note This value will only be used if \ - @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */ -/**@} */ - -/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies - * @{ */ -#define BLE_GAP_SCAN_FP_ACCEPT_ALL \ - 0x00 /**< Accept all advertising packets except directed advertising packets \ - not addressed to this device. */ -#define BLE_GAP_SCAN_FP_WHITELIST \ - 0x01 /**< Accept advertising packets from devices in the whitelist except directed \ - packets not addressed to this device. */ -#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED \ - 0x02 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED \ - 0x03 /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST. \ - In addition, accept directed advertising packets, where the advertiser's \ - address is a resolvable private address that cannot be resolved. */ -/**@} */ - -/**@defgroup BLE_GAP_ADV_TIMEOUT_VALUES GAP Advertising timeout values in 10 ms units - * @{ */ -#define BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX \ - (128) /**< Maximum high duty advertising time in 10 ms units. Corresponds to 1.28 s. \ - */ -#define BLE_GAP_ADV_TIMEOUT_LIMITED_MAX \ - (18000) /**< Maximum advertising time in 10 ms units corresponding to TGAP(lim_adv_timeout) = 180 s in limited discoverable \ - mode. */ -#define BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED \ - (0) /**< Unlimited advertising in general discoverable mode. \ - For high duty cycle advertising, this corresponds to @ref BLE_GAP_ADV_TIMEOUT_HIGH_DUTY_MAX. */ -/**@} */ - -/**@defgroup BLE_GAP_DISC_MODES GAP Discovery modes - * @{ */ -#define BLE_GAP_DISC_MODE_NOT_DISCOVERABLE 0x00 /**< Not discoverable discovery Mode. */ -#define BLE_GAP_DISC_MODE_LIMITED 0x01 /**< Limited Discovery Mode. */ -#define BLE_GAP_DISC_MODE_GENERAL 0x02 /**< General Discovery Mode. */ -/**@} */ - -/**@defgroup BLE_GAP_IO_CAPS GAP IO Capabilities - * @{ */ -#define BLE_GAP_IO_CAPS_DISPLAY_ONLY 0x00 /**< Display Only. */ -#define BLE_GAP_IO_CAPS_DISPLAY_YESNO 0x01 /**< Display and Yes/No entry. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_ONLY 0x02 /**< Keyboard Only. */ -#define BLE_GAP_IO_CAPS_NONE 0x03 /**< No I/O capabilities. */ -#define BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY 0x04 /**< Keyboard and Display. */ -/**@} */ - -/**@defgroup BLE_GAP_AUTH_KEY_TYPES GAP Authentication Key Types - * @{ */ -#define BLE_GAP_AUTH_KEY_TYPE_NONE 0x00 /**< No key (may be used to reject). */ -#define BLE_GAP_AUTH_KEY_TYPE_PASSKEY 0x01 /**< 6-digit Passkey. */ -#define BLE_GAP_AUTH_KEY_TYPE_OOB 0x02 /**< Out Of Band data. */ -/**@} */ - -/**@defgroup BLE_GAP_KP_NOT_TYPES GAP Keypress Notification Types - * @{ */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_START 0x00 /**< Passkey entry started. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_IN 0x01 /**< Passkey digit entered. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_DIGIT_OUT 0x02 /**< Passkey digit erased. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_CLEAR 0x03 /**< Passkey cleared. */ -#define BLE_GAP_KP_NOT_TYPE_PASSKEY_END 0x04 /**< Passkey entry completed. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS GAP Security status - * @{ */ -#define BLE_GAP_SEC_STATUS_SUCCESS 0x00 /**< Procedure completed with success. */ -#define BLE_GAP_SEC_STATUS_TIMEOUT 0x01 /**< Procedure timed out. */ -#define BLE_GAP_SEC_STATUS_PDU_INVALID 0x02 /**< Invalid PDU received. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_BEGIN 0x03 /**< Reserved for Future Use range #1 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE1_END 0x80 /**< Reserved for Future Use range #1 end. */ -#define BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED 0x81 /**< Passkey entry failed (user canceled or other). */ -#define BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE 0x82 /**< Out of Band Key not available. */ -#define BLE_GAP_SEC_STATUS_AUTH_REQ 0x83 /**< Authentication requirements not met. */ -#define BLE_GAP_SEC_STATUS_CONFIRM_VALUE 0x84 /**< Confirm value failed. */ -#define BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP 0x85 /**< Pairing not supported. */ -#define BLE_GAP_SEC_STATUS_ENC_KEY_SIZE 0x86 /**< Encryption key size. */ -#define BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED 0x87 /**< Unsupported SMP command. */ -#define BLE_GAP_SEC_STATUS_UNSPECIFIED 0x88 /**< Unspecified reason. */ -#define BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS 0x89 /**< Too little time elapsed since last attempt. */ -#define BLE_GAP_SEC_STATUS_INVALID_PARAMS 0x8A /**< Invalid parameters. */ -#define BLE_GAP_SEC_STATUS_DHKEY_FAILURE 0x8B /**< DHKey check failure. */ -#define BLE_GAP_SEC_STATUS_NUM_COMP_FAILURE 0x8C /**< Numeric Comparison failure. */ -#define BLE_GAP_SEC_STATUS_BR_EDR_IN_PROG 0x8D /**< BR/EDR pairing in progress. */ -#define BLE_GAP_SEC_STATUS_X_TRANS_KEY_DISALLOWED 0x8E /**< BR/EDR Link Key cannot be used for LE keys. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_BEGIN 0x8F /**< Reserved for Future Use range #2 begin. */ -#define BLE_GAP_SEC_STATUS_RFU_RANGE2_END 0xFF /**< Reserved for Future Use range #2 end. */ -/**@} */ - -/**@defgroup BLE_GAP_SEC_STATUS_SOURCES GAP Security status sources - * @{ */ -#define BLE_GAP_SEC_STATUS_SOURCE_LOCAL 0x00 /**< Local failure. */ -#define BLE_GAP_SEC_STATUS_SOURCE_REMOTE 0x01 /**< Remote failure. */ -/**@} */ - -/**@defgroup BLE_GAP_CP_LIMITS GAP Connection Parameters Limits - * @{ */ -#define BLE_GAP_CP_MIN_CONN_INTVL_NONE 0xFFFF /**< No new minimum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MIN \ - 0x0006 /**< Lowest minimum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MIN_CONN_INTVL_MAX \ - 0x0C80 /**< Highest minimum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_MAX_CONN_INTVL_NONE 0xFFFF /**< No new maximum connection interval specified in connect parameters. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MIN \ - 0x0006 /**< Lowest maximum connection interval permitted, in units of 1.25 ms, i.e. 7.5 ms. */ -#define BLE_GAP_CP_MAX_CONN_INTVL_MAX \ - 0x0C80 /**< Highest maximum connection interval permitted, in units of 1.25 ms, i.e. 4 s. \ - */ -#define BLE_GAP_CP_SLAVE_LATENCY_MAX 0x01F3 /**< Highest slave latency permitted, in connection events. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE 0xFFFF /**< No new supervision timeout specified in connect parameters. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN 0x000A /**< Lowest supervision timeout permitted, in units of 10 ms, i.e. 100 ms. */ -#define BLE_GAP_CP_CONN_SUP_TIMEOUT_MAX 0x0C80 /**< Highest supervision timeout permitted, in units of 10 ms, i.e. 32 s. */ -/**@} */ - -/**@defgroup BLE_GAP_DEVNAME GAP device name defines. - * @{ */ -#define BLE_GAP_DEVNAME_DEFAULT "nRF5x" /**< Default device name value. */ -#define BLE_GAP_DEVNAME_DEFAULT_LEN 31 /**< Default number of octets in device name. */ -#define BLE_GAP_DEVNAME_MAX_LEN 248 /**< Maximum number of octets in device name. */ -/**@} */ - -/**@brief Disable RSSI events for connections */ -#define BLE_GAP_RSSI_THRESHOLD_INVALID 0xFF - -/**@defgroup BLE_GAP_PHYS GAP PHYs - * @{ */ -#define BLE_GAP_PHY_AUTO 0x00 /**< Automatic PHY selection. Refer @ref sd_ble_gap_phy_update for more information.*/ -#define BLE_GAP_PHY_1MBPS 0x01 /**< 1 Mbps PHY. */ -#define BLE_GAP_PHY_2MBPS 0x02 /**< 2 Mbps PHY. */ -#define BLE_GAP_PHY_CODED 0x04 /**< Coded PHY. */ -#define BLE_GAP_PHY_NOT_SET 0xFF /**< PHY is not configured. */ - -/**@brief Supported PHYs in connections, for scanning, and for advertising. */ -#define BLE_GAP_PHYS_SUPPORTED (BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS | BLE_GAP_PHY_CODED) /**< All PHYs are supported. */ - -/**@} */ - -/**@defgroup BLE_GAP_CONN_SEC_MODE_SET_MACROS GAP attribute security requirement setters - * - * See @ref ble_gap_conn_sec_mode_t. - * @{ */ -/**@brief Set sec_mode pointed to by ptr to have no access rights.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(ptr) \ - do { \ - (ptr)->sm = 0; \ - (ptr)->lv = 0; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require no protection, open link.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_OPEN(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption, but no MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 2; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 3; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require LESC encryption and MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 1; \ - (ptr)->lv = 4; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption, no MITM protection needed.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 1; \ - } while (0) -/**@brief Set sec_mode pointed to by ptr to require signing or encryption with MITM protection.*/ -#define BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(ptr) \ - do { \ - (ptr)->sm = 2; \ - (ptr)->lv = 2; \ - } while (0) -/**@} */ - -/**@brief GAP Security Random Number Length. */ -#define BLE_GAP_SEC_RAND_LEN 8 - -/**@brief GAP Security Key Length. */ -#define BLE_GAP_SEC_KEY_LEN 16 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key Length. */ -#define BLE_GAP_LESC_P256_PK_LEN 64 - -/**@brief GAP LE Secure Connections Elliptic Curve Diffie-Hellman DHKey Length. */ -#define BLE_GAP_LESC_DHKEY_LEN 32 - -/**@brief GAP Passkey Length. */ -#define BLE_GAP_PASSKEY_LEN 6 - -/**@brief Maximum amount of addresses in the whitelist. */ -#define BLE_GAP_WHITELIST_ADDR_MAX_COUNT (8) - -/**@brief Maximum amount of identities in the device identities list. */ -#define BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT (8) - -/**@brief Default connection count for a configuration. */ -#define BLE_GAP_CONN_COUNT_DEFAULT (1) - -/**@defgroup BLE_GAP_EVENT_LENGTH GAP event length defines. - * @{ */ -#define BLE_GAP_EVENT_LENGTH_MIN (2) /**< Minimum event length, in 1.25 ms units. */ -#define BLE_GAP_EVENT_LENGTH_CODED_PHY_MIN (6) /**< The shortest event length in 1.25 ms units supporting LE Coded PHY. */ -#define BLE_GAP_EVENT_LENGTH_DEFAULT (3) /**< Default event length, in 1.25 ms units. */ -/**@} */ - -/**@defgroup BLE_GAP_ROLE_COUNT GAP concurrent connection count defines. - * @{ */ -#define BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT (1) /**< Default maximum number of connections concurrently acting as peripherals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT (3) /**< Default maximum number of connections concurrently acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT \ - (1) /**< Default number of SMP instances shared between all connections acting as centrals. */ -#define BLE_GAP_ROLE_COUNT_COMBINED_MAX \ - (20) /**< Maximum supported number of concurrent connections in the peripheral and central roles combined. */ - -/**@} */ - -/**@brief Automatic data length parameter. */ -#define BLE_GAP_DATA_LENGTH_AUTO 0 - -/**@defgroup BLE_GAP_AUTH_PAYLOAD_TIMEOUT Authenticated payload timeout defines. - * @{ */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX (48000) /**< Maximum authenticated payload timeout in 10 ms units, i.e. 8 minutes. */ -#define BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MIN (1) /**< Minimum authenticated payload timeout in 10 ms units, i.e. 10 ms. */ -/**@} */ - -/**@defgroup GAP_SEC_MODES GAP Security Modes - * @{ */ -#define BLE_GAP_SEC_MODE 0x00 /**< No key (may be used to reject). */ -/**@} */ - -/**@brief The total number of channels in Bluetooth Low Energy. */ -#define BLE_GAP_CHANNEL_COUNT (40) - -/**@defgroup BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS Quality of Service (QoS) Channel survey interval defines - * @{ */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS (0) /**< Continuous channel survey. */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MIN_US (7500) /**< Minimum channel survey interval in microseconds (7.5 ms). */ -#define BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_MAX_US (4000000) /**< Maximum channel survey interval in microseconds (4 s). */ - /**@} */ - -/** @} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG GAP Characteristic inclusion configurations - * @{ - */ -#define BLE_GAP_CHAR_INCL_CONFIG_INCLUDE (0) /**< Include the characteristic in the Attribute Table */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITH_SPACE \ - (1) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will reserve the attribute handles \ - which are otherwise used for this characteristic. \ - By reserving the attribute handles it will be possible \ - to upgrade the SoftDevice without changing handle of the \ - Service Changed characteristic. */ -#define BLE_GAP_CHAR_INCL_CONFIG_EXCLUDE_WITHOUT_SPACE \ - (2) /**< Do not include the characteristic in the Attribute table. \ - The SoftDevice will not reserve the attribute handles \ - which are otherwise used for this characteristic. */ -/**@} */ - -/** @defgroup BLE_GAP_CHAR_INCL_CONFIG_DEFAULTS Characteristic inclusion default values - * @{ */ -#define BLE_GAP_PPCP_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -#define BLE_GAP_CAR_INCL_CONFIG_DEFAULT (BLE_GAP_CHAR_INCL_CONFIG_INCLUDE) /**< Included by default. */ -/**@} */ - -/** @defgroup BLE_GAP_SLAVE_LATENCY Slave latency configuration options - * @{ */ -#define BLE_GAP_SLAVE_LATENCY_ENABLE \ - (0) /**< Slave latency is enabled. When slave latency is enabled, \ - the slave will wake up every time it has data to send, \ - and/or every slave latency number of connection events. */ -#define BLE_GAP_SLAVE_LATENCY_DISABLE \ - (1) /**< Disable slave latency. The slave will wake up every connection event \ - regardless of the requested slave latency. \ - This option consumes the most power. */ -#define BLE_GAP_SLAVE_LATENCY_WAIT_FOR_ACK \ - (2) /**< The slave will wake up every connection event if it has not received \ - an ACK from the master for at least slave latency events. This \ - configuration may increase the power consumption in environments \ - with a lot of radio activity. */ -/**@} */ - -/**@addtogroup BLE_GAP_STRUCTURES Structures - * @{ */ - -/**@brief Advertising event properties. */ -typedef struct { - uint8_t type; /**< Advertising type. See @ref BLE_GAP_ADV_TYPES. */ - uint8_t anonymous : 1; /**< Omit advertiser's address from all PDUs. - @note Anonymous advertising is only available for - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED and - @ref BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED. */ - uint8_t include_tx_power : 1; /**< This feature is not supported on this SoftDevice. */ -} ble_gap_adv_properties_t; - -/**@brief Advertising report type. */ -typedef struct { - uint16_t connectable : 1; /**< Connectable advertising event type. */ - uint16_t scannable : 1; /**< Scannable advertising event type. */ - uint16_t directed : 1; /**< Directed advertising event type. */ - uint16_t scan_response : 1; /**< Received a scan response. */ - uint16_t extended_pdu : 1; /**< Received an extended advertising set. */ - uint16_t status : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */ - uint16_t reserved : 9; /**< Reserved for future use. */ -} ble_gap_adv_report_type_t; - -/**@brief Advertising Auxiliary Pointer. */ -typedef struct { - uint16_t aux_offset; /**< Time offset from the beginning of advertising packet to the auxiliary packet in 100 us units. */ - uint8_t aux_phy; /**< Indicates the PHY on which the auxiliary advertising packet is sent. See @ref BLE_GAP_PHYS. */ -} ble_gap_aux_pointer_t; - -/**@brief Bluetooth Low Energy address. */ -typedef struct { - uint8_t - addr_id_peer : 1; /**< Only valid for peer addresses. - This bit is set by the SoftDevice to indicate whether the address has been resolved from - a Resolvable Private Address (when the peer is using privacy). - If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address. - - This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. - */ - uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */ - uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format. - @ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */ -} ble_gap_addr_t; - -/**@brief GAP connection parameters. - * - * @note When ble_conn_params_t is received in an event, both min_conn_interval and - * max_conn_interval will be equal to the connection interval set by the central. - * - * @note If both conn_sup_timeout and max_conn_interval are specified, then the following constraint applies: - * conn_sup_timeout * 4 > (1 + slave_latency) * max_conn_interval - * that corresponds to the following Bluetooth Spec requirement: - * The Supervision_Timeout in milliseconds shall be larger than - * (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is given in milliseconds. - */ -typedef struct { - uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/ - uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/ -} ble_gap_conn_params_t; - -/**@brief GAP connection security modes. - * - * Security Mode 0 Level 0: No access permissions at all (this level is not defined by the Bluetooth Core specification).\n - * Security Mode 1 Level 1: No security is needed (aka open link).\n - * Security Mode 1 Level 2: Encrypted link required, MITM protection not necessary.\n - * Security Mode 1 Level 3: MITM protected encrypted link required.\n - * Security Mode 1 Level 4: LESC MITM protected encrypted link using a 128-bit strength encryption key required.\n - * Security Mode 2 Level 1: Signing or encryption required, MITM protection not necessary.\n - * Security Mode 2 Level 2: MITM protected signing required, unless link is MITM protected encrypted.\n - */ -typedef struct { - uint8_t sm : 4; /**< Security Mode (1 or 2), 0 for no permissions at all. */ - uint8_t lv : 4; /**< Level (1, 2, 3 or 4), 0 for no permissions at all. */ - -} ble_gap_conn_sec_mode_t; - -/**@brief GAP connection security status.*/ -typedef struct { - ble_gap_conn_sec_mode_t sec_mode; /**< Currently active security mode for this connection.*/ - uint8_t - encr_key_size; /**< Length of currently active encryption key, 7 to 16 octets (only applicable for bonding procedures). */ -} ble_gap_conn_sec_t; - -/**@brief Identity Resolving Key. */ -typedef struct { - uint8_t irk[BLE_GAP_SEC_KEY_LEN]; /**< Array containing IRK. */ -} ble_gap_irk_t; - -/**@brief Channel mask (40 bits). - * Every channel is represented with a bit positioned as per channel index defined in Bluetooth Core Specification v5.0, - * Vol 6, Part B, Section 1.4.1. The LSB contained in array element 0 represents channel index 0, and bit 39 represents - * channel index 39. If a bit is set to 1, the channel is not used. - */ -typedef uint8_t ble_gap_ch_mask_t[5]; - -/**@brief GAP advertising parameters. */ -typedef struct { - ble_gap_adv_properties_t properties; /**< The properties of the advertising events. */ - ble_gap_addr_t const *p_peer_addr; /**< Address of a known peer. - @note ble_gap_addr_t::addr_type cannot be - @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. - - When privacy is enabled and the local device uses - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE addresses, - the device identity list is searched for a matching entry. If - the local IRK for that device identity is set, the local IRK - for that device will be used to generate the advertiser address - field in the advertising packet. - - If @ref ble_gap_adv_properties_t::type is directed, this must be - set to the targeted scanner or initiator. If the peer address is - in the device identity list, the peer IRK for that device will be - used to generate @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE - target addresses used in the advertising event PDUs. */ - uint32_t interval; /**< Advertising interval in 625 us units. @sa BLE_GAP_ADV_INTERVALS. - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE - advertising, this parameter is ignored. */ - uint16_t duration; /**< Advertising duration in 10 ms units. When timeout is reached, - an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - @sa BLE_GAP_ADV_TIMEOUT_VALUES. - @note The SoftDevice will always complete at least one advertising - event even if the duration is set too low. */ - uint8_t max_adv_evts; /**< Maximum advertising events that shall be sent prior to disabling - advertising. Setting the value to 0 disables the limitation. When - the count of advertising events specified by this parameter - (if not 0) is reached, advertising will be automatically stopped - and an event of type @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised - @note If @ref ble_gap_adv_properties_t::type is set to - @ref BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE, - this parameter is ignored. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be used. - Masking away secondary advertising channels is not supported. */ - uint8_t filter_policy; /**< Filter Policy. @sa BLE_GAP_ADV_FILTER_POLICIES. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising channel packets - are transmitted. If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS - will be used. - Valid values are @ref BLE_GAP_PHY_1MBPS and @ref BLE_GAP_PHY_CODED. - @note The primary_phy shall indicate @ref BLE_GAP_PHY_1MBPS if - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising channel packets - are transmitted. - If set to @ref BLE_GAP_PHY_AUTO, @ref BLE_GAP_PHY_1MBPS will be used. - Valid values are - @ref BLE_GAP_PHY_1MBPS, @ref BLE_GAP_PHY_2MBPS, and @ref BLE_GAP_PHY_CODED. - If @ref ble_gap_adv_properties_t::type is an extended advertising type - and connectable, this is the PHY that will be used to establish a - connection and send AUX_ADV_IND packets on. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t set_id : 4; /**< The advertising set identifier distinguishes this advertising set from other - advertising sets transmitted by this and other devices. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is not an extended advertising type. */ - uint8_t scan_req_notification : 1; /**< Enable scan request notifications for this advertising set. When a - scan request is received and the scanner address is allowed - by the filter policy, @ref BLE_GAP_EVT_SCAN_REQ_REPORT is raised. - @note This parameter will be ignored when - @ref ble_gap_adv_properties_t::type is a non-scannable - advertising type. */ -} ble_gap_adv_params_t; - -/**@brief GAP advertising data buffers. - * - * The application must provide the buffers for advertisement. The memory shall reside in application RAM, and - * shall never be modified while advertising. The data shall be kept alive until either: - * - @ref BLE_GAP_EVT_ADV_SET_TERMINATED is raised. - * - @ref BLE_GAP_EVT_CONNECTED is raised with @ref ble_gap_evt_connected_t::adv_handle set to the corresponding - * advertising handle. - * - Advertising is stopped. - * - Advertising data is changed. - * To update advertising data while advertising, provide new buffers to @ref sd_ble_gap_adv_set_configure. */ -typedef struct { - ble_data_t adv_data; /**< Advertising data. - @note - Advertising data can only be specified for a @ref ble_gap_adv_properties_t::type - that is allowed to contain advertising data. */ - ble_data_t scan_rsp_data; /**< Scan response data. - @note - Scan response data can only be specified for a @ref ble_gap_adv_properties_t::type - that is scannable. */ -} ble_gap_adv_data_t; - -/**@brief GAP scanning parameters. */ -typedef struct { - uint8_t extended : 1; /**< If 1, the scanner will accept extended advertising packets. - If set to 0, the scanner will not receive advertising packets - on secondary advertising channels, and will not be able - to receive long advertising PDUs. */ - uint8_t report_incomplete_evts : 1; /**< If 1, events of type @ref ble_gap_evt_adv_report_t may have - @ref ble_gap_adv_report_type_t::status set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. - This parameter is ignored when used with @ref sd_ble_gap_connect - @note This may be used to abort receiving more packets from an extended - advertising event, and is only available for extended - scanning, see @ref sd_ble_gap_scan_start. - @note This feature is not supported by this SoftDevice. */ - uint8_t active : 1; /**< If 1, perform active scanning by sending scan requests. - This parameter is ignored when used with @ref sd_ble_gap_connect. */ - uint8_t filter_policy : 2; /**< Scanning filter policy. @sa BLE_GAP_SCAN_FILTER_POLICIES. - @note Only @ref BLE_GAP_SCAN_FP_ACCEPT_ALL and - @ref BLE_GAP_SCAN_FP_WHITELIST are valid when used with - @ref sd_ble_gap_connect */ - uint8_t scan_phys; /**< Bitfield of PHYs to scan on. If set to @ref BLE_GAP_PHY_AUTO, - scan_phys will default to @ref BLE_GAP_PHY_1MBPS. - - If @ref ble_gap_scan_params_t::extended is set to 0, the only - supported PHY is @ref BLE_GAP_PHY_1MBPS. - - When used with @ref sd_ble_gap_scan_start, - the bitfield indicates the PHYs the scanner will use for scanning - on primary advertising channels. The scanner will accept - @ref BLE_GAP_PHYS_SUPPORTED as secondary advertising channel PHYs. - - When used with @ref sd_ble_gap_connect, the bitfield indicates - the PHYs the initiator will use for scanning on primary advertising - channels. The initiator will accept connections initiated on either - of the @ref BLE_GAP_PHYS_SUPPORTED PHYs. - If scan_phys contains @ref BLE_GAP_PHY_1MBPS and/or @ref BLE_GAP_PHY_2MBPS, - the primary scan PHY is @ref BLE_GAP_PHY_1MBPS. - If scan_phys also contains @ref BLE_GAP_PHY_CODED, the primary scan - PHY will also contain @ref BLE_GAP_PHY_CODED. If the only scan PHY is - @ref BLE_GAP_PHY_CODED, the primary scan PHY is - @ref BLE_GAP_PHY_CODED only. */ - uint16_t interval; /**< Scan interval in 625 us units. @sa BLE_GAP_SCAN_INTERVALS. */ - uint16_t window; /**< Scan window in 625 us units. @sa BLE_GAP_SCAN_WINDOW. - If scan_phys contains both @ref BLE_GAP_PHY_1MBPS and - @ref BLE_GAP_PHY_CODED interval shall be larger than or - equal to twice the scan window. */ - uint16_t timeout; /**< Scan timeout in 10 ms units. @sa BLE_GAP_SCAN_TIMEOUT. */ - ble_gap_ch_mask_t channel_mask; /**< Channel mask for primary and secondary advertising channels. - At least one of the primary channels, that is channel index 37-39, must be - set to 0. - Masking away secondary channels is not supported. */ -} ble_gap_scan_params_t; - -/**@brief Privacy. - * - * The privacy feature provides a way for the device to avoid being tracked over a period of time. - * The privacy feature, when enabled, hides the local device identity and replaces it with a private address - * that is automatically refreshed at a specified interval. - * - * If a device still wants to be recognized by other peers, it needs to share it's Identity Resolving Key (IRK). - * With this key, a device can generate a random private address that can only be recognized by peers in possession of that - * key, and devices can establish connections without revealing their real identities. - * - * Both network privacy (@ref BLE_GAP_PRIVACY_MODE_NETWORK_PRIVACY) and device privacy (@ref - * BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY) are supported. - * - * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all - * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. - * The IRK distributed during bonding procedure is the device IRK that is active when @ref sd_ble_gap_sec_params_reply is - * called. - */ -typedef struct { - uint8_t privacy_mode; /**< Privacy mode, see @ref BLE_GAP_PRIVACY_MODES. Default is @ref BLE_GAP_PRIVACY_MODE_OFF. */ - uint8_t private_addr_type; /**< The private address type must be either @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE or - @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE. */ - uint16_t private_addr_cycle_s; /**< Private address cycle interval in seconds. Providing an address cycle value of 0 will use - the default value defined by @ref BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S. */ - ble_gap_irk_t - *p_device_irk; /**< When used as input, pointer to IRK structure that will be used as the default IRK. If NULL, the device - default IRK will be used. When used as output, pointer to IRK structure where the current default IRK - will be written to. If NULL, this argument is ignored. By default, the default IRK is used to generate - random private resolvable addresses for the local device unless instructed otherwise. */ -} ble_gap_privacy_params_t; - -/**@brief PHY preferences for TX and RX - * @note tx_phys and rx_phys are bit fields. Multiple bits can be set in them to indicate multiple preferred PHYs for each - * direction. - * @code - * p_gap_phys->tx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * p_gap_phys->rx_phys = BLE_GAP_PHY_1MBPS | BLE_GAP_PHY_2MBPS; - * @endcode - * - */ -typedef struct { - uint8_t tx_phys; /**< Preferred transmit PHYs, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phys; /**< Preferred receive PHYs, see @ref BLE_GAP_PHYS. */ -} ble_gap_phys_t; - -/** @brief Keys that can be exchanged during a bonding procedure. */ -typedef struct { - uint8_t enc : 1; /**< Long Term Key and Master Identification. */ - uint8_t id : 1; /**< Identity Resolving Key and Identity Address Information. */ - uint8_t sign : 1; /**< Connection Signature Resolving Key. */ - uint8_t link : 1; /**< Derive the Link Key from the LTK. */ -} ble_gap_sec_kdist_t; - -/**@brief GAP security parameters. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Enable Man In The Middle protection. */ - uint8_t lesc : 1; /**< Enable LE Secure Connection pairing. */ - uint8_t keypress : 1; /**< Enable generation of keypress notifications. */ - uint8_t io_caps : 3; /**< IO capabilities, see @ref BLE_GAP_IO_CAPS. */ - uint8_t oob : 1; /**< The OOB data flag. - - In LE legacy pairing, this flag is set if a device has out of band authentication data. - The OOB method is used if both of the devices have out of band authentication data. - - In LE Secure Connections pairing, this flag is set if a device has the peer device's out of band - authentication data. The OOB method is used if at least one device has the peer device's OOB data - available. */ - uint8_t - min_key_size; /**< Minimum encryption key size in octets between 7 and 16. If 0 then not applicable in this instance. */ - uint8_t max_key_size; /**< Maximum encryption key size in octets between min_key_size and 16. */ - ble_gap_sec_kdist_t kdist_own; /**< Key distribution bitmap: keys that the local device will distribute. */ - ble_gap_sec_kdist_t kdist_peer; /**< Key distribution bitmap: keys that the remote device will distribute. */ -} ble_gap_sec_params_t; - -/**@brief GAP Encryption Information. */ -typedef struct { - uint8_t ltk[BLE_GAP_SEC_KEY_LEN]; /**< Long Term Key. */ - uint8_t lesc : 1; /**< Key generated using LE Secure Connections. */ - uint8_t auth : 1; /**< Authenticated Key. */ - uint8_t ltk_len : 6; /**< LTK length in octets. */ -} ble_gap_enc_info_t; - -/**@brief GAP Master Identification. */ -typedef struct { - uint16_t ediv; /**< Encrypted Diversifier. */ - uint8_t rand[BLE_GAP_SEC_RAND_LEN]; /**< Random Number. */ -} ble_gap_master_id_t; - -/**@brief GAP Signing Information. */ -typedef struct { - uint8_t csrk[BLE_GAP_SEC_KEY_LEN]; /**< Connection Signature Resolving Key. */ -} ble_gap_sign_info_t; - -/**@brief GAP LE Secure Connections P-256 Public Key. */ -typedef struct { - uint8_t pk[BLE_GAP_LESC_P256_PK_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman P-256 Public Key. Stored in the - standard SMP protocol format: {X,Y} both in little-endian. */ -} ble_gap_lesc_p256_pk_t; - -/**@brief GAP LE Secure Connections DHKey. */ -typedef struct { - uint8_t key[BLE_GAP_LESC_DHKEY_LEN]; /**< LE Secure Connections Elliptic Curve Diffie-Hellman Key. Stored in little-endian. */ -} ble_gap_lesc_dhkey_t; - -/**@brief GAP LE Secure Connections OOB data. */ -typedef struct { - ble_gap_addr_t addr; /**< Bluetooth address of the device. */ - uint8_t r[BLE_GAP_SEC_KEY_LEN]; /**< Random Number. */ - uint8_t c[BLE_GAP_SEC_KEY_LEN]; /**< Confirm Value. */ -} ble_gap_lesc_oob_data_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONNECTED. */ -typedef struct { - ble_gap_addr_t - peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ - uint8_t role; /**< BLE role for this connection, see @ref BLE_GAP_ROLES */ - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. - This variable is only set if role is set to @ref BLE_GAP_ROLE_PERIPH. */ -} ble_gap_evt_connected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DISCONNECTED. */ -typedef struct { - uint8_t reason; /**< HCI error code, see @ref BLE_HCI_STATUS_CODES. */ -} ble_gap_evt_disconnected_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PHY_UPDATE_REQUEST. */ -typedef struct { - ble_gap_phys_t peer_preferred_phys; /**< The PHYs the peer prefers to use. */ -} ble_gap_evt_phy_update_request_t; - -/**@brief Event Structure for @ref BLE_GAP_EVT_PHY_UPDATE. */ -typedef struct { - uint8_t status; /**< Status of the procedure, see @ref BLE_HCI_STATUS_CODES.*/ - uint8_t tx_phy; /**< TX PHY for this connection, see @ref BLE_GAP_PHYS. */ - uint8_t rx_phy; /**< RX PHY for this connection, see @ref BLE_GAP_PHYS. */ -} ble_gap_evt_phy_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. */ -typedef struct { - ble_gap_sec_params_t peer_params; /**< Initiator Security Parameters. */ -} ble_gap_evt_sec_params_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_INFO_REQUEST. */ -typedef struct { - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ - ble_gap_master_id_t master_id; /**< Master Identification for LTK lookup. */ - uint8_t enc_info : 1; /**< If 1, Encryption Information required. */ - uint8_t id_info : 1; /**< If 1, Identity Information required. */ - uint8_t sign_info : 1; /**< If 1, Signing Information required. */ -} ble_gap_evt_sec_info_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_PASSKEY_DISPLAY. */ -typedef struct { - uint8_t passkey[BLE_GAP_PASSKEY_LEN]; /**< 6-digit passkey in ASCII ('0'-'9' digits only). */ - uint8_t match_request : 1; /**< If 1 requires the application to report the match using @ref sd_ble_gap_auth_key_reply - with either @ref BLE_GAP_AUTH_KEY_TYPE_NONE if there is no match or - @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY if there is a match. */ -} ble_gap_evt_passkey_display_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_KEY_PRESSED. */ -typedef struct { - uint8_t kp_not; /**< Keypress notification type, see @ref BLE_GAP_KP_NOT_TYPES. */ -} ble_gap_evt_key_pressed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_KEY_REQUEST. */ -typedef struct { - uint8_t key_type; /**< See @ref BLE_GAP_AUTH_KEY_TYPES. */ -} ble_gap_evt_auth_key_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST. */ -typedef struct { - ble_gap_lesc_p256_pk_t - *p_pk_peer; /**< LE Secure Connections remote P-256 Public Key. This will point to the application-supplied memory - inside the keyset during the call to @ref sd_ble_gap_sec_params_reply. */ - uint8_t oobd_req : 1; /**< LESC OOB data required. A call to @ref sd_ble_gap_lesc_oob_data_set is required to complete the - procedure. */ -} ble_gap_evt_lesc_dhkey_request_t; - -/**@brief Security levels supported. - * @note See Bluetooth Specification Version 4.2 Volume 3, Part C, Chapter 10, Section 10.2.1. - */ -typedef struct { - uint8_t lv1 : 1; /**< If 1: Level 1 is supported. */ - uint8_t lv2 : 1; /**< If 1: Level 2 is supported. */ - uint8_t lv3 : 1; /**< If 1: Level 3 is supported. */ - uint8_t lv4 : 1; /**< If 1: Level 4 is supported. */ -} ble_gap_sec_levels_t; - -/**@brief Encryption Key. */ -typedef struct { - ble_gap_enc_info_t enc_info; /**< Encryption Information. */ - ble_gap_master_id_t master_id; /**< Master Identification. */ -} ble_gap_enc_key_t; - -/**@brief Identity Key. */ -typedef struct { - ble_gap_irk_t id_info; /**< Identity Resolving Key. */ - ble_gap_addr_t id_addr_info; /**< Identity Address. */ -} ble_gap_id_key_t; - -/**@brief Security Keys. */ -typedef struct { - ble_gap_enc_key_t *p_enc_key; /**< Encryption Key, or NULL. */ - ble_gap_id_key_t *p_id_key; /**< Identity Key, or NULL. */ - ble_gap_sign_info_t *p_sign_key; /**< Signing Key, or NULL. */ - ble_gap_lesc_p256_pk_t *p_pk; /**< LE Secure Connections P-256 Public Key. When in debug mode the application must use the - value defined in the Core Bluetooth Specification v4.2 Vol.3, Part H, Section 2.3.5.6.1 */ -} ble_gap_sec_keys_t; - -/**@brief Security key set for both local and peer keys. */ -typedef struct { - ble_gap_sec_keys_t keys_own; /**< Keys distributed by the local device. For LE Secure Connections the encryption key will be - generated locally and will always be stored if bonding. */ - ble_gap_sec_keys_t - keys_peer; /**< Keys distributed by the remote device. For LE Secure Connections, p_enc_key must always be NULL. */ -} ble_gap_sec_keyset_t; - -/**@brief Data Length Update Procedure parameters. */ -typedef struct { - uint16_t max_tx_octets; /**< Maximum number of payload octets that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_octets; /**< Maximum number of payload octets that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ - uint16_t max_tx_time_us; /**< Maximum time, in microseconds, that a Controller supports for transmission of a single Link - Layer Data Channel PDU. */ - uint16_t max_rx_time_us; /**< Maximum time, in microseconds, that a Controller supports for reception of a single Link Layer - Data Channel PDU. */ -} ble_gap_data_length_params_t; - -/**@brief Data Length Update Procedure local limitation. */ -typedef struct { - uint16_t tx_payload_limited_octets; /**< If > 0, the requested TX packet length is too long by this many octets. */ - uint16_t rx_payload_limited_octets; /**< If > 0, the requested RX packet length is too long by this many octets. */ - uint16_t tx_rx_time_limited_us; /**< If > 0, the requested combination of TX and RX packet lengths is too long by this many - microseconds. */ -} ble_gap_data_length_limitation_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_AUTH_STATUS. */ -typedef struct { - uint8_t auth_status; /**< Authentication status, see @ref BLE_GAP_SEC_STATUS. */ - uint8_t error_src : 2; /**< On error, source that caused the failure, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ - uint8_t bonded : 1; /**< Procedure resulted in a bond. */ - uint8_t lesc : 1; /**< Procedure resulted in a LE Secure Connection. */ - ble_gap_sec_levels_t sm1_levels; /**< Levels supported in Security Mode 1. */ - ble_gap_sec_levels_t sm2_levels; /**< Levels supported in Security Mode 2. */ - ble_gap_sec_kdist_t kdist_own; /**< Bitmap stating which keys were exchanged (distributed) by the local device. If bonding - with LE Secure Connections, the enc bit will be always set. */ - ble_gap_sec_kdist_t kdist_peer; /**< Bitmap stating which keys were exchanged (distributed) by the remote device. If bonding - with LE Secure Connections, the enc bit will never be set. */ -} ble_gap_evt_auth_status_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_SEC_UPDATE. */ -typedef struct { - ble_gap_conn_sec_t conn_sec; /**< Connection security level. */ -} ble_gap_evt_conn_sec_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Source of timeout event, see @ref BLE_GAP_TIMEOUT_SOURCES. */ - union { - ble_data_t adv_report_buffer; /**< If source is set to @ref BLE_GAP_TIMEOUT_SRC_SCAN, the released - scan buffer is contained in this field. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_RSSI_CHANGED. */ -typedef struct { - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Data Channel Index on which the Signal Strength is measured (0-36). */ -} ble_gap_evt_rssi_changed_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_SET_TERMINATED */ -typedef struct { - uint8_t reason; /**< Reason for why the advertising set terminated. See - @ref BLE_GAP_EVT_ADV_SET_TERMINATED_REASON. */ - uint8_t adv_handle; /**< Advertising handle in which advertising has ended. */ - uint8_t num_completed_adv_events; /**< If @ref ble_gap_adv_params_t::max_adv_evts was not set to 0, - this field indicates the number of completed advertising events. */ - ble_gap_adv_data_t adv_data; /**< Advertising buffers corresponding to the terminated - advertising set. The advertising buffers provided in - @ref sd_ble_gap_adv_set_configure are now released. */ -} ble_gap_evt_adv_set_terminated_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT. - * - * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * not all fields in the advertising report may be available. - * - * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, - * scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start. - */ -typedef struct { - ble_gap_adv_report_type_t type; /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr is resolved: - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the - peer's identity address. */ - ble_gap_addr_t direct_addr; /**< Contains the target address of the advertising event if - @ref ble_gap_adv_report_type_t::directed is set to 1. If the - SoftDevice was able to resolve the address, - @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr - contains the local identity address. If the target address of the - advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, - and the SoftDevice was unable to resolve it, the application may try - to resolve this address to find out if the advertising event was - directed to us. */ - uint8_t primary_phy; /**< Indicates the PHY on which the primary advertising packet was received. - See @ref BLE_GAP_PHYS. */ - uint8_t secondary_phy; /**< Indicates the PHY on which the secondary advertising packet was received. - See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets - were received on a secondary advertising channel. */ - int8_t tx_power; /**< TX Power reported by the advertiser in the last packet header received. - This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the - last received packet did not contain the Tx Power field. - @note TX Power is only included in extended advertising packets. */ - int8_t rssi; /**< Received Signal Strength Indication in dBm of the last packet received. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - uint8_t ch_index; /**< Channel Index on which the last advertising packet is received (0-39). */ - uint8_t set_id; /**< Set ID of the received advertising data. Set ID is not present - if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - uint16_t data_id : 12; /**< The advertising data ID of the received advertising data. Data ID - is not present if @ref ble_gap_evt_adv_report_t::set_id is set to - @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */ - ble_data_t data; /**< Received advertising or scan response data. If - @ref ble_gap_adv_report_type_t::status is not set to - @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided - in @ref sd_ble_gap_scan_start is now released. */ - ble_gap_aux_pointer_t aux_pointer; /**< The offset and PHY of the next advertising packet in this extended advertising - event. @note This field is only set if @ref ble_gap_adv_report_type_t::status - is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */ -} ble_gap_evt_adv_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SEC_REQUEST. */ -typedef struct { - uint8_t bond : 1; /**< Perform bonding. */ - uint8_t mitm : 1; /**< Man In The Middle protection requested. */ - uint8_t lesc : 1; /**< LE Secure Connections requested. */ - uint8_t keypress : 1; /**< Generation of keypress notifications requested. */ -} ble_gap_evt_sec_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST. */ -typedef struct { - ble_gap_conn_params_t conn_params; /**< GAP Connection Parameters. */ -} ble_gap_evt_conn_param_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_SCAN_REQ_REPORT. */ -typedef struct { - uint8_t adv_handle; /**< Advertising handle for the advertising set which received the Scan Request */ - int8_t rssi; /**< Received Signal Strength Indication in dBm. - @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature - measurement. */ - ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. If the peer_addr resolved: @ref - ble_gap_addr_t::addr_id_peer is set to 1 and the address is the device's identity address. */ -} ble_gap_evt_scan_req_report_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST. */ -typedef struct { - ble_gap_data_length_params_t peer_params; /**< Peer data length parameters. */ -} ble_gap_evt_data_length_update_request_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE. - * - * @note This event may also be raised after a PHY Update procedure. - */ -typedef struct { - ble_gap_data_length_params_t effective_params; /**< The effective data length parameters. */ -} ble_gap_evt_data_length_update_t; - -/**@brief Event structure for @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT. */ -typedef struct { - int8_t - channel_energy[BLE_GAP_CHANNEL_COUNT]; /**< The measured energy on the Bluetooth Low Energy - channels, in dBm, indexed by Channel Index. - If no measurement is available for the given channel, channel_energy is set to - @ref BLE_GAP_POWER_LEVEL_INVALID. */ -} ble_gap_evt_qos_channel_survey_report_t; - -/**@brief GAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - union /**< union alternative identified by evt_id in enclosing struct. */ - { - ble_gap_evt_connected_t connected; /**< Connected Event Parameters. */ - ble_gap_evt_disconnected_t disconnected; /**< Disconnected Event Parameters. */ - ble_gap_evt_conn_param_update_t conn_param_update; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_sec_params_request_t sec_params_request; /**< Security Parameters Request Event Parameters. */ - ble_gap_evt_sec_info_request_t sec_info_request; /**< Security Information Request Event Parameters. */ - ble_gap_evt_passkey_display_t passkey_display; /**< Passkey Display Event Parameters. */ - ble_gap_evt_key_pressed_t key_pressed; /**< Key Pressed Event Parameters. */ - ble_gap_evt_auth_key_request_t auth_key_request; /**< Authentication Key Request Event Parameters. */ - ble_gap_evt_lesc_dhkey_request_t lesc_dhkey_request; /**< LE Secure Connections DHKey calculation request. */ - ble_gap_evt_auth_status_t auth_status; /**< Authentication Status Event Parameters. */ - ble_gap_evt_conn_sec_update_t conn_sec_update; /**< Connection Security Update Event Parameters. */ - ble_gap_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gap_evt_rssi_changed_t rssi_changed; /**< RSSI Event Parameters. */ - ble_gap_evt_adv_report_t adv_report; /**< Advertising Report Event Parameters. */ - ble_gap_evt_adv_set_terminated_t adv_set_terminated; /**< Advertising Set Terminated Event Parameters. */ - ble_gap_evt_sec_request_t sec_request; /**< Security Request Event Parameters. */ - ble_gap_evt_conn_param_update_request_t conn_param_update_request; /**< Connection Parameter Update Parameters. */ - ble_gap_evt_scan_req_report_t scan_req_report; /**< Scan Request Report Parameters. */ - ble_gap_evt_phy_update_request_t phy_update_request; /**< PHY Update Request Event Parameters. */ - ble_gap_evt_phy_update_t phy_update; /**< PHY Update Parameters. */ - ble_gap_evt_data_length_update_request_t data_length_update_request; /**< Data Length Update Request Event Parameters. */ - ble_gap_evt_data_length_update_t data_length_update; /**< Data Length Update Event Parameters. */ - ble_gap_evt_qos_channel_survey_report_t - qos_channel_survey_report; /**< Quality of Service (QoS) Channel Survey Report Parameters. */ - } params; /**< Event Parameters. */ -} ble_gap_evt_t; - -/** - * @brief BLE GAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The connection count for the connection configurations is zero. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - The sum of conn_count for all connection configurations combined exceeds UINT8_MAX. - * - The event length is smaller than @ref BLE_GAP_EVENT_LENGTH_MIN. - */ -typedef struct { - uint8_t conn_count; /**< The number of concurrent connections the application can create with this configuration. - The default and minimum value is @ref BLE_GAP_CONN_COUNT_DEFAULT. */ - uint16_t event_length; /**< The time set aside for this connection on every connection interval in 1.25 ms units. - The default value is @ref BLE_GAP_EVENT_LENGTH_DEFAULT, the minimum value is @ref - BLE_GAP_EVENT_LENGTH_MIN. The event length and the connection interval are the primary parameters - for setting the throughput of a connection. - See the SoftDevice Specification for details on throughput. */ -} ble_gap_conn_cfg_t; - -/** - * @brief Configuration of maximum concurrent connections in the different connected roles, set with - * @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_CONN_COUNT The sum of periph_role_count and central_role_count is too - * large. The maximum supported sum of concurrent connections is - * @ref BLE_GAP_ROLE_COUNT_COMBINED_MAX. - * @retval ::NRF_ERROR_INVALID_PARAM central_sec_count is larger than central_role_count. - * @retval ::NRF_ERROR_RESOURCES The adv_set_count is too large. The maximum - * supported advertising handles is - * @ref BLE_GAP_ADV_SET_COUNT_MAX. - */ -typedef struct { - uint8_t adv_set_count; /**< Maximum number of advertising sets. Default value is @ref BLE_GAP_ADV_SET_COUNT_DEFAULT. */ - uint8_t periph_role_count; /**< Maximum number of connections concurrently acting as a peripheral. Default value is @ref - BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT. */ - uint8_t central_role_count; /**< Maximum number of connections concurrently acting as a central. Default value is @ref - BLE_GAP_ROLE_COUNT_CENTRAL_DEFAULT. */ - uint8_t central_sec_count; /**< Number of SMP instances shared between all connections acting as a central. Default value is - @ref BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT. */ - uint8_t qos_channel_survey_role_available : 1; /**< If set, the Quality of Service (QoS) channel survey module is available to - the application using @ref sd_ble_gap_qos_channel_survey_start. */ -} ble_gap_cfg_role_count_t; - -/** - * @brief Device name and its properties, set with @ref sd_ble_cfg_set. - * - * @note If the device name is not configured, the default device name will be - * @ref BLE_GAP_DEVNAME_DEFAULT, the maximum device name length will be - * @ref BLE_GAP_DEVNAME_DEFAULT_LEN, vloc will be set to @ref BLE_GATTS_VLOC_STACK and the device name - * will have no write access. - * - * @note If @ref max_len is more than @ref BLE_GAP_DEVNAME_DEFAULT_LEN and vloc is set to @ref BLE_GATTS_VLOC_STACK, - * the attribute table size must be increased to have room for the longer device name (see - * @ref sd_ble_cfg_set and @ref ble_gatts_cfg_attr_tab_size_t). - * - * @note If vloc is @ref BLE_GATTS_VLOC_STACK : - * - p_value must point to non-volatile memory (flash) or be NULL. - * - If p_value is NULL, the device name will initially be empty. - * - * @note If vloc is @ref BLE_GATTS_VLOC_USER : - * - p_value cannot be NULL. - * - If the device name is writable, p_value must point to volatile memory (RAM). - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Invalid device name location (vloc). - * - Invalid device name security mode. - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The device name length is invalid (must be between 0 and @ref BLE_GAP_DEVNAME_MAX_LEN). - * - The device name length is too long for the given Attribute Table. - * @retval ::NRF_ERROR_NOT_SUPPORTED Device name security mode is not supported. - */ -typedef struct { - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t *p_value; /**< Pointer to where the value (device name) is stored or will be stored. */ - uint16_t current_len; /**< Current length in bytes of the memory pointed to by p_value.*/ - uint16_t max_len; /**< Maximum length in bytes of the memory pointed to by p_value.*/ -} ble_gap_cfg_device_name_t; - -/**@brief Peripheral Preferred Connection Parameters include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Peripheral Preferred Connection Parameters characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_PPCP_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_ppcp_incl_cfg_t; - -/**@brief Central Address Resolution include configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t include_cfg; /**< Inclusion configuration of the Central Address Resolution characteristic. - See @ref BLE_GAP_CHAR_INCL_CONFIG. Default is @ref BLE_GAP_CAR_INCL_CONFIG_DEFAULT. */ -} ble_gap_cfg_car_incl_cfg_t; - -/**@brief Configuration structure for GAP configurations. */ -typedef union { - ble_gap_cfg_role_count_t role_count_cfg; /**< Role count configuration, cfg_id is @ref BLE_GAP_CFG_ROLE_COUNT. */ - ble_gap_cfg_device_name_t device_name_cfg; /**< Device name configuration, cfg_id is @ref BLE_GAP_CFG_DEVICE_NAME. */ - ble_gap_cfg_ppcp_incl_cfg_t ppcp_include_cfg; /**< Peripheral Preferred Connection Parameters characteristic include - configuration, cfg_id is @ref BLE_GAP_CFG_PPCP_INCL_CONFIG. */ - ble_gap_cfg_car_incl_cfg_t car_include_cfg; /**< Central Address Resolution characteristic include configuration, - cfg_id is @ref BLE_GAP_CFG_CAR_INCL_CONFIG. */ -} ble_gap_cfg_t; - -/**@brief Channel Map option. - * - * @details Used with @ref sd_ble_opt_get to get the current channel map - * or @ref sd_ble_opt_set to set a new channel map. When setting the - * channel map, it applies to all current and future connections. When getting the - * current channel map, it applies to a single connection and the connection handle - * must be supplied. - * - * @note Setting the channel map may take some time, depending on connection parameters. - * The time taken may be different for each connection and the get operation will - * return the previous channel map until the new one has taken effect. - * - * @note After setting the channel map, by spec it can not be set again until at least 1 s has passed. - * See Bluetooth Specification Version 4.1 Volume 2, Part E, Section 7.3.46. - * - * @retval ::NRF_SUCCESS Get or set successful. - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - Less then two bits in @ref ch_map are set. - * - Bits for primary advertising channels (37-39) are set. - * @retval ::NRF_ERROR_BUSY Channel map was set again before enough time had passed. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied for get. - * - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle (only applicable for get) */ - uint8_t ch_map[5]; /**< Channel Map (37-bit). */ -} ble_gap_opt_ch_map_t; - -/**@brief Local connection latency option. - * - * @details Local connection latency is a feature which enables the slave to improve - * current consumption by ignoring the slave latency set by the peer. The - * local connection latency can only be set to a multiple of the slave latency, - * and cannot be longer than half of the supervision timeout. - * - * @details Used with @ref sd_ble_opt_set to set the local connection latency. The - * @ref sd_ble_opt_get is not supported for this option, but the actual - * local connection latency (unless set to NULL) is set as a return parameter - * when setting the option. - * - * @note The latency set will be truncated down to the closest slave latency event - * multiple, or the nearest multiple before half of the supervision timeout. - * - * @note The local connection latency is disabled by default, and needs to be enabled for new - * connections and whenever the connection is updated. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t requested_latency; /**< Requested local connection latency. */ - uint16_t *p_actual_latency; /**< Pointer to storage for the actual local connection latency (can be set to NULL to skip return - value). */ -} ble_gap_opt_local_conn_latency_t; - -/**@brief Disable slave latency - * - * @details Used with @ref sd_ble_opt_set to temporarily disable slave latency of a peripheral connection - * (see @ref ble_gap_conn_params_t::slave_latency). And to re-enable it again. When disabled, the - * peripheral will ignore the slave_latency set by the central. - * - * @note Shall only be called on peripheral links. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_NOT_SUPPORTED Get is not supported. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint8_t disable; /**< For allowed values see @ref BLE_GAP_SLAVE_LATENCY */ -} ble_gap_opt_slave_latency_disable_t; - -/**@brief Passkey Option. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @endmscs - * - * @details Structure containing the passkey to be used during pairing. This can be used with @ref - * sd_ble_opt_set to make the SoftDevice use a preprogrammed passkey for authentication - * instead of generating a random one. - * - * @note Repeated pairing attempts using the same preprogrammed passkey makes pairing vulnerable to MITM attacks. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - */ -typedef struct { - uint8_t const *p_passkey; /**< Pointer to 6-digit ASCII string (digit 0..9 only, no NULL termination) passkey to be used - during pairing. If this is NULL, the SoftDevice will generate a random passkey if required.*/ -} ble_gap_opt_passkey_t; - -/**@brief Compatibility mode 1 option. - * - * @details This can be used with @ref sd_ble_opt_set to enable and disable - * compatibility mode 1. Compatibility mode 1 is disabled by default. - * - * @note Compatibility mode 1 enables interoperability with devices that do not support a value of - * 0 for the WinOffset parameter in the Link Layer CONNECT_IND packet. This applies to a - * limited set of legacy peripheral devices from another vendor. Enabling this compatibility - * mode will only have an effect if the local device will act as a central device and - * initiate a connection to a peripheral device. In that case it may lead to the connection - * creation taking up to one connection interval longer to complete for all connections. - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_STATE When connection creation is ongoing while mode 1 is set. - */ -typedef struct { - uint8_t enable : 1; /**< Enable compatibility mode 1.*/ -} ble_gap_opt_compat_mode_1_t; - -/**@brief Authenticated payload timeout option. - * - * @details This can be used with @ref sd_ble_opt_set to change the Authenticated payload timeout to a value other - * than the default of @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT_MAX. - * - * @note The authenticated payload timeout event ::BLE_GAP_TIMEOUT_SRC_AUTH_PAYLOAD will be generated - * if auth_payload_timeout time has elapsed without receiving a packet with a valid MIC on an encrypted - * link. - * - * @note The LE ping procedure will be initiated before the timer expires to give the peer a chance - * to reset the timer. In addition the stack will try to prioritize running of LE ping over other - * activities to increase chances of finishing LE ping before timer expires. To avoid side-effects - * on other activities, it is recommended to use high timeout values. - * Recommended timeout > 2*(connInterval * (6 + connSlaveLatency)). - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. auth_payload_timeout was outside of allowed range. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter. - */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle */ - uint16_t auth_payload_timeout; /**< Requested timeout in 10 ms unit, see @ref BLE_GAP_AUTH_PAYLOAD_TIMEOUT. */ -} ble_gap_opt_auth_payload_timeout_t; - -/**@brief Option structure for GAP options. */ -typedef union { - ble_gap_opt_ch_map_t ch_map; /**< Parameters for the Channel Map option. */ - ble_gap_opt_local_conn_latency_t local_conn_latency; /**< Parameters for the Local connection latency option */ - ble_gap_opt_passkey_t passkey; /**< Parameters for the Passkey option.*/ - ble_gap_opt_compat_mode_1_t compat_mode_1; /**< Parameters for the compatibility mode 1 option.*/ - ble_gap_opt_auth_payload_timeout_t auth_payload_timeout; /**< Parameters for the authenticated payload timeout option.*/ - ble_gap_opt_slave_latency_disable_t slave_latency_disable; /**< Parameters for the Disable slave latency option */ -} ble_gap_opt_t; - -/**@brief Connection event triggering parameters. */ -typedef struct { - uint8_t ppi_ch_id; /**< PPI channel to use. This channel should be regarded as reserved until - connection event PPI task triggering is stopped. - The PPI channel ID can not be one of the PPI channels reserved by - the SoftDevice. See @ref NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK. */ - uint32_t task_endpoint; /**< Task Endpoint to trigger. */ - uint16_t conn_evt_counter_start; /**< The connection event on which the task triggering should start. */ - uint16_t period_in_events; /**< Trigger period. Valid range is [1, 32767]. - If the device is in slave role and slave latency is enabled, - this parameter should be set to a multiple of (slave latency + 1) - to ensure low power operation. */ -} ble_gap_conn_event_trigger_t; -/**@} */ - -/**@addtogroup BLE_GAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set the local Bluetooth identity address. - * - * The local Bluetooth identity address is the address that identifies this device to other peers. - * The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @note The identity address cannot be changed while advertising, scanning or creating a connection. - * - * @note This address will be distributed to the peer during bonding. - * If the address changes, the address stored in the peer device will not be valid and the ability to - * reconnect using the old address will be lost. - * - * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC upon being - * enabled. The address is a random number populated during the IC manufacturing process and remains unchanged - * for the lifetime of each IC. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @endmscs - * - * @param[in] p_addr Pointer to address structure. - * - * @retval ::NRF_SUCCESS Address successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, - * scanning or creating a connection. - */ -SVCALL(SD_BLE_GAP_ADDR_SET, uint32_t, sd_ble_gap_addr_set(ble_gap_addr_t const *p_addr)); - -/**@brief Get local Bluetooth identity address. - * - * @note This will always return the identity address irrespective of the privacy settings, - * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC. - * - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - */ -SVCALL(SD_BLE_GAP_ADDR_GET, uint32_t, sd_ble_gap_addr_get(ble_gap_addr_t *p_addr)); - -/**@brief Get the Bluetooth device address used by the advertiser. - * - * @note This function will return the local Bluetooth address used in advertising PDUs. When - * using privacy, the SoftDevice will generate a new private address every - * @ref ble_gap_privacy_params_t::private_addr_cycle_s configured using - * @ref sd_ble_gap_privacy_set. Hence depending on when the application calls this API, the - * address returned may not be the latest address that is used in the advertising PDUs. - * - * @param[in] adv_handle The advertising handle to get the address from. - * @param[out] p_addr Pointer to address structure to be filled in. - * - * @retval ::NRF_SUCCESS Address successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid or NULL pointer supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. - * @retval ::NRF_ERROR_INVALID_STATE The advertising set is currently not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_ADDR_GET, uint32_t, sd_ble_gap_adv_addr_get(uint8_t adv_handle, ble_gap_addr_t *p_addr)); - -/**@brief Set the active whitelist in the SoftDevice. - * - * @note Only one whitelist can be used at a time and the whitelist is shared between the BLE roles. - * The whitelist cannot be set if a BLE role is using the whitelist. - * - * @note If an address is resolved using the information in the device identity list, then the whitelist - * filter policy applies to the peer identity address and not the resolvable address sent on air. - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @endmscs - * - * @param[in] pp_wl_addrs Pointer to a whitelist of peer addresses, if NULL the whitelist will be cleared. - * @param[in] len Length of the whitelist, maximum @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. - * - * @retval ::NRF_SUCCESS The whitelist is successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The whitelist (or one of its entries) provided is invalid. - * @retval ::BLE_ERROR_GAP_WHITELIST_IN_USE The whitelist is in use by a BLE role and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given whitelist size is invalid (zero or too large); this can only return when - * pp_wl_addrs is not NULL. - */ -SVCALL(SD_BLE_GAP_WHITELIST_SET, uint32_t, sd_ble_gap_whitelist_set(ble_gap_addr_t const *const *pp_wl_addrs, uint8_t len)); - -/**@brief Set device identity list. - * - * @note Only one device identity list can be used at a time and the list is shared between the BLE roles. - * The device identity list cannot be set if a BLE role is using the list. - * - * @param[in] pp_id_keys Pointer to an array of peer identity addresses and peer IRKs, if NULL the device identity list will - * be cleared. - * @param[in] pp_local_irks Pointer to an array of local IRKs. Each entry in the array maps to the entry in pp_id_keys at the - * same index. To fill in the list with the currently set device IRK for all peers, set to NULL. - * @param[in] len Length of the device identity list, maximum @ref BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_PRIVATE_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS The device identity list successfully set/cleared. - * @retval ::NRF_ERROR_INVALID_ADDR The device identity list (or one of its entries) provided is invalid. - * This code may be returned if the local IRK list also has an invalid entry. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE The device identity list is in use and cannot be set or cleared. - * @retval ::BLE_ERROR_GAP_DEVICE_IDENTITIES_DUPLICATE The device identity list contains multiple entries with the same identity - * address. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_DATA_SIZE The given device identity list size invalid (zero or too large); this can - * only return when pp_id_keys is not NULL. - */ -SVCALL(SD_BLE_GAP_DEVICE_IDENTITIES_SET, uint32_t, - sd_ble_gap_device_identities_set(ble_gap_id_key_t const *const *pp_id_keys, ble_gap_irk_t const *const *pp_local_irks, - uint8_t len)); - -/**@brief Set privacy settings. - * - * @note Privacy settings cannot be changed while advertising, scanning or creating a connection. - * - * @param[in] p_privacy_params Privacy settings. - * - * @mscs - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_SCAN_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Set successfully. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid address type is supplied. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer to privacy settings is NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - * @retval ::NRF_ERROR_INVALID_PARAM Out of range parameters are provided. - * @retval ::NRF_ERROR_NOT_SUPPORTED The SoftDevice does not support privacy if the Central Address Resolution - characteristic is not configured to be included and the SoftDevice is configured - to support central roles. - See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while advertising, scanning - * or creating a connection. - */ -SVCALL(SD_BLE_GAP_PRIVACY_SET, uint32_t, sd_ble_gap_privacy_set(ble_gap_privacy_params_t const *p_privacy_params)); - -/**@brief Get privacy settings. - * - * @note ::ble_gap_privacy_params_t::p_device_irk must be initialized to NULL or a valid address before this function is called. - * If it is initialized to a valid address, the address pointed to will contain the current device IRK on return. - * - * @param[in,out] p_privacy_params Privacy settings. - * - * @retval ::NRF_SUCCESS Privacy settings read. - * @retval ::NRF_ERROR_INVALID_ADDR The pointer given for returning the privacy settings may be NULL or invalid. - * Otherwise, the p_device_irk pointer in privacy parameter is an invalid pointer. - */ -SVCALL(SD_BLE_GAP_PRIVACY_GET, uint32_t, sd_ble_gap_privacy_get(ble_gap_privacy_params_t *p_privacy_params)); - -/**@brief Configure an advertising set. Set, clear or update advertising and scan response data. - * - * @note The format of the advertising data will be checked by this call to ensure interoperability. - * Limitations imposed by this API call to the data provided include having a flags data type in the scan response data and - * duplicating the local name in the advertising data and scan response data. - * - * @note In order to update advertising data while advertising, new advertising buffers must be provided. - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in,out] p_adv_handle Provide a pointer to a handle containing @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising set. On success, a new handle is then returned through the - * pointer. Provide a pointer to an existing advertising handle to configure an existing advertising set. - * @param[in] p_adv_data Advertising data. If set to NULL, no advertising data will be used. See - * @ref ble_gap_adv_data_t. - * @param[in] p_adv_params Advertising parameters. When this function is used to update advertising - * data while advertising, this parameter must be NULL. See @ref ble_gap_adv_params_t. - * - * @retval ::NRF_SUCCESS Advertising set successfully configured. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid advertising data configuration specified. See @ref - * ble_gap_adv_data_t. - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, - * see @ref sd_ble_gap_whitelist_set. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR ble_gap_adv_params_t::p_peer_addr is invalid. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - It is invalid to provide non-NULL advertising set parameters while - * advertising. - * - It is invalid to provide the same data buffers while advertising. To - * update advertising data, provide new advertising buffers. - * @retval ::BLE_ERROR_GAP_DISCOVERABLE_WITH_WHITELIST Discoverable mode and whitelist incompatible. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE The provided advertising handle was not found. Use @ref - * BLE_GAP_ADV_SET_HANDLE_NOT_SET to configure a new advertising handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_FLAGS Invalid combination of advertising flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data type(s) supplied. Check the advertising data format - * specification given in Bluetooth Specification Version 5.0, Volume 3, Part C, Chapter 11. - * @retval ::NRF_ERROR_INVALID_LENGTH Invalid data length(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported data length or advertising parameter configuration. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to configure a new advertising handle. Update an - * existing advertising handle instead. - * @retval ::BLE_ERROR_GAP_UUID_LIST_MISMATCH Invalid UUID list supplied. - */ -SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, - sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, - ble_gap_adv_params_t const *p_adv_params)); - -/**@brief Start advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @note Only one advertiser may be active at any time. - * - * @note If privacy is enabled, the advertiser's private address will be refreshed when this function is called. - * See @ref sd_ble_gap_privacy_set(). - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, Generated after connection has been established through connectable advertising.} - * @event{@ref BLE_GAP_EVT_ADV_SET_TERMINATED, Advertising set has terminated.} - * @event{@ref BLE_GAP_EVT_SCAN_REQ_REPORT, A scan request was received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_PRIVACY_ADV_DIR_PRIV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle Advertising handle to advertise on, received from @ref sd_ble_gap_adv_set_configure. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. For non-connectable - * advertising, this is ignored. - * - * @retval ::NRF_SUCCESS The BLE stack has started advertising. - * @retval ::NRF_ERROR_INVALID_STATE adv_handle is not configured or already advertising. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration - * tag has been reached; connectable advertiser cannot be started. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. Configure a new adveriting handle with @ref - sd_ble_gap_adv_set_configure. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied: - * - Invalid configuration of p_adv_params. See @ref ble_gap_adv_params_t. - * - Use of whitelist requested but whitelist has not been set, see @ref - sd_ble_gap_whitelist_set. - * @retval ::NRF_ERROR_RESOURCES Either: - * - adv_handle is configured with connectable advertising, but the event_length parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - * - Not enough BLE role slots available. - Stop one or more currently active roles (Central, Peripheral, Broadcaster or Observer) - and try again. - * - p_adv_params is configured with connectable advertising, but the event_length - parameter - * associated with conn_cfg_tag is too small to be able to establish a connection on - * the selected advertising phys. Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_ADV_START, uint32_t, sd_ble_gap_adv_start(uint8_t adv_handle, uint8_t conn_cfg_tag)); - -/**@brief Stop advertising (GAP Discoverable, Connectable modes, Broadcast Procedure). - * - * @mscs - * @mmsc{@ref BLE_GAP_ADV_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] adv_handle The advertising handle that should stop advertising. - * - * @retval ::NRF_SUCCESS The BLE stack has stopped advertising. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Invalid advertising handle. - * @retval ::NRF_ERROR_INVALID_STATE The advertising handle is not advertising. - */ -SVCALL(SD_BLE_GAP_ADV_STOP, uint32_t, sd_ble_gap_adv_stop(uint8_t adv_handle)); - -/**@brief Update connection parameters. - * - * @details In the central role this will initiate a Link Layer connection parameter update procedure, - * otherwise in the peripheral role, this will send the corresponding L2CAP request and wait for - * the central to perform the procedure. In both cases, and regardless of success or failure, the application - * will be informed of the result with a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE event. - * - * @details This function can be used as a central both to reply to a @ref BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST or to start the - * procedure unrequested. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_PARAM_UPDATE, Result of the connection parameter update procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CPU_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CPU_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CPU_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_conn_params Pointer to desired connection parameters. If NULL is provided on a peripheral role, - * the parameters in the PPCP characteristic of the GAP service will be used instead. - * If NULL is provided on a central role and in response to a @ref - * BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST, the peripheral request will be rejected - * - * @retval ::NRF_SUCCESS The Connection Update procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, check parameter limits and constraints. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - * @retval ::NRF_ERROR_BUSY Procedure already in progress, wait for pending procedures to complete and retry. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GAP_CONN_PARAM_UPDATE, uint32_t, - sd_ble_gap_conn_param_update(uint16_t conn_handle, ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Disconnect (GAP Link Termination). - * - * @details This call initiates the disconnection procedure, and its completion will be communicated to the application - * with a @ref BLE_GAP_EVT_DISCONNECTED event. - * - * @events - * @event{@ref BLE_GAP_EVT_DISCONNECTED, Generated when disconnection procedure is complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CONN_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] hci_status_code HCI status code, see @ref BLE_HCI_STATUS_CODES (accepted values are @ref - * BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION and @ref BLE_HCI_CONN_INTERVAL_UNACCEPTABLE). - * - * @retval ::NRF_SUCCESS The disconnection procedure has been started successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress or link has not been established. - */ -SVCALL(SD_BLE_GAP_DISCONNECT, uint32_t, sd_ble_gap_disconnect(uint16_t conn_handle, uint8_t hci_status_code)); - -/**@brief Set the radio's transmit power. - * - * @param[in] role The role to set the transmit power for, see @ref BLE_GAP_TX_POWER_ROLES for - * possible roles. - * @param[in] handle The handle parameter is interpreted depending on role: - * - If role is @ref BLE_GAP_TX_POWER_ROLE_CONN, this value is the specific connection handle. - * - If role is @ref BLE_GAP_TX_POWER_ROLE_ADV, the advertising set identified with the advertising handle, - * will use the specified transmit power, and include it in the advertising packet headers if - * @ref ble_gap_adv_properties_t::include_tx_power set. - * - For all other roles handle is ignored. - * @param[in] tx_power Radio transmit power in dBm (see note for accepted values). - * - * @note Supported tx_power values: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, +3dBm and +4dBm. - * In addition, on some chips following values are supported: +2dBm, +5dBm, +6dBm, +7dBm and +8dBm. - * Setting these values on a chip that does not support them will result in undefined behaviour. - * @note The initiator will have the same transmit power as the scanner. - * @note When a connection is created it will inherit the transmit power from the initiator or - * advertiser leading to the connection. - * - * @retval ::NRF_SUCCESS Successfully changed the transmit power. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ADV_HANDLE Advertising handle not found. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_TX_POWER_SET, uint32_t, sd_ble_gap_tx_power_set(uint8_t role, uint16_t handle, int8_t tx_power)); - -/**@brief Set GAP Appearance value. - * - * @param[in] appearance Appearance (16-bit), see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value set successfully. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_SET, uint32_t, sd_ble_gap_appearance_set(uint16_t appearance)); - -/**@brief Get GAP Appearance value. - * - * @param[out] p_appearance Pointer to appearance (16-bit) to be filled in, see @ref BLE_APPEARANCES. - * - * @retval ::NRF_SUCCESS Appearance value retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_APPEARANCE_GET, uint32_t, sd_ble_gap_appearance_get(uint16_t *p_appearance)); - -/**@brief Set GAP Peripheral Preferred Connection Parameters. - * - * @param[in] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure with the desired parameters. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_SET, uint32_t, sd_ble_gap_ppcp_set(ble_gap_conn_params_t const *p_conn_params)); - -/**@brief Get GAP Peripheral Preferred Connection Parameters. - * - * @param[out] p_conn_params Pointer to a @ref ble_gap_conn_params_t structure where the parameters will be stored. - * - * @retval ::NRF_SUCCESS Peripheral Preferred Connection Parameters retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The characteristic is not included in the Attribute Table, - see @ref ble_gap_cfg_ppcp_incl_cfg_t. - */ -SVCALL(SD_BLE_GAP_PPCP_GET, uint32_t, sd_ble_gap_ppcp_get(ble_gap_conn_params_t *p_conn_params)); - -/**@brief Set GAP device name. - * - * @note If the device name is located in application flash memory (see @ref ble_gap_cfg_device_name_t), - * it cannot be changed. Then @ref NRF_ERROR_FORBIDDEN will be returned. - * - * @param[in] p_write_perm Write permissions for the Device Name characteristic, see @ref ble_gap_conn_sec_mode_t. - * @param[in] p_dev_name Pointer to a UTF-8 encoded, non NULL-terminated string. - * @param[in] len Length of the UTF-8, non NULL-terminated string pointed to by p_dev_name in octets (must be smaller or - * equal than @ref BLE_GAP_DEVNAME_MAX_LEN). - * - * @retval ::NRF_SUCCESS GAP device name and permissions set successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_FORBIDDEN Device name is not writable. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_SET, uint32_t, - sd_ble_gap_device_name_set(ble_gap_conn_sec_mode_t const *p_write_perm, uint8_t const *p_dev_name, uint16_t len)); - -/**@brief Get GAP device name. - * - * @note If the device name is longer than the size of the supplied buffer, - * p_len will return the complete device name length, - * and not the number of bytes actually returned in p_dev_name. - * The application may use this information to allocate a suitable buffer size. - * - * @param[out] p_dev_name Pointer to an empty buffer where the UTF-8 non NULL-terminated string will be placed. Set to - * NULL to obtain the complete device name length. - * @param[in,out] p_len Length of the buffer pointed by p_dev_name, complete device name length on output. - * - * @retval ::NRF_SUCCESS GAP device name retrieved successfully. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - */ -SVCALL(SD_BLE_GAP_DEVICE_NAME_GET, uint32_t, sd_ble_gap_device_name_get(uint8_t *p_dev_name, uint16_t *p_len)); - -/**@brief Initiate the GAP Authentication procedure. - * - * @details In the central role, this function will send an SMP Pairing Request (or an SMP Pairing Failed if rejected), - * otherwise in the peripheral role, an SMP Security Request will be sent. - * - * @events - * @event{Depending on the security parameters set and the packet exchanges with the peer\, the following events may be - * generated:} - * @event{@ref BLE_GAP_EVT_SEC_PARAMS_REQUEST} - * @event{@ref BLE_GAP_EVT_SEC_INFO_REQUEST} - * @event{@ref BLE_GAP_EVT_PASSKEY_DISPLAY} - * @event{@ref BLE_GAP_EVT_KEY_PRESSED} - * @event{@ref BLE_GAP_EVT_AUTH_KEY_REQUEST} - * @event{@ref BLE_GAP_EVT_LESC_DHKEY_REQUEST} - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE} - * @event{@ref BLE_GAP_EVT_AUTH_STATUS} - * @event{@ref BLE_GAP_EVT_TIMEOUT} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sec_params Pointer to the @ref ble_gap_sec_params_t structure with the security parameters to be used during the - * pairing or bonding procedure. In the peripheral role, only the bond, mitm, lesc and keypress fields of this structure are used. - * In the central role, this pointer may be NULL to reject a Security Request. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - An encryption is already executing or queued. - * @retval ::NRF_ERROR_NO_MEM The maximum number of authentication procedures that can run in parallel for the given role is - * reached. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - * @retval ::NRF_ERROR_TIMEOUT A SMP timeout has occurred, and further SMP operations on this link is prohibited. - */ -SVCALL(SD_BLE_GAP_AUTHENTICATE, uint32_t, - sd_ble_gap_authenticate(uint16_t conn_handle, ble_gap_sec_params_t const *p_sec_params)); - -/**@brief Reply with GAP security parameters. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_STATIC_PK_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_CONFIRM_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_KS_TOO_SMALL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_APP_ERROR_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_REMOTE_PAIRING_FAIL_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_PAIRING_TIMEOUT_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] sec_status Security status, see @ref BLE_GAP_SEC_STATUS. - * @param[in] p_sec_params Pointer to a @ref ble_gap_sec_params_t security parameters structure. In the central role this must be - * set to NULL, as the parameters have already been provided during a previous call to @ref sd_ble_gap_authenticate. - * @param[in,out] p_sec_keyset Pointer to a @ref ble_gap_sec_keyset_t security keyset structure. Any keys generated and/or - * distributed as a result of the ongoing security procedure will be stored into the memory referenced by the pointers inside this - * structure. The keys will be stored and available to the application upon reception of a @ref BLE_GAP_EVT_AUTH_STATUS event. - * Note that the SoftDevice expects the application to provide memory for storing the - * peer's keys. So it must be ensured that the relevant pointers inside this structure are not NULL. The - * pointers to the local key can, however, be NULL, in which case, the local key data will not be available to the application - * upon reception of the - * @ref BLE_GAP_EVT_AUTH_STATUS event. - * - * @retval ::NRF_SUCCESS Successfully accepted security parameter from the application. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Security parameters has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED Setting of sign or link fields in @ref ble_gap_sec_kdist_t not supported. - * Distribution of own Identity Information is only supported if the Central - * Address Resolution characteristic is configured to be included or - * the Softdevice is configured to support peripheral roles only. - * See @ref ble_gap_cfg_car_incl_cfg_t and @ref ble_gap_cfg_role_count_t. - */ -SVCALL(SD_BLE_GAP_SEC_PARAMS_REPLY, uint32_t, - sd_ble_gap_sec_params_reply(uint16_t conn_handle, uint8_t sec_status, ble_gap_sec_params_t const *p_sec_params, - ble_gap_sec_keyset_t const *p_sec_keyset)); - -/**@brief Reply with an authentication key. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_AUTH_KEY_REQUEST or a @ref BLE_GAP_EVT_PASSKEY_DISPLAY, - * calling it at other times will result in an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_BONDING_PK_CENTRAL_OOB_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_BONDING_PK_PERIPH_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] key_type See @ref BLE_GAP_AUTH_KEY_TYPES. - * @param[in] p_key If key type is @ref BLE_GAP_AUTH_KEY_TYPE_NONE, then NULL. - * If key type is @ref BLE_GAP_AUTH_KEY_TYPE_PASSKEY, then a 6-byte ASCII string (digit 0..9 only, no NULL - * termination) or NULL when confirming LE Secure Connections Numeric Comparison. If key type is @ref BLE_GAP_AUTH_KEY_TYPE_OOB, - * then a 16-byte OOB key value in little-endian format. - * - * @retval ::NRF_SUCCESS Authentication key successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Authentication key has not been requested. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_AUTH_KEY_REPLY, uint32_t, - sd_ble_gap_auth_key_reply(uint16_t conn_handle, uint8_t key_type, uint8_t const *p_key)); - -/**@brief Reply with an LE Secure connections DHKey. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST, calling it at other times will result in - * an @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_PAIRING_JW_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_NC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_PD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dhkey LE Secure Connections DHKey. - * - * @retval ::NRF_SUCCESS DHKey successfully set. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - The peer is not authenticated. - * - The application has not pulled a @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_DHKEY_REPLY, uint32_t, - sd_ble_gap_lesc_dhkey_reply(uint16_t conn_handle, ble_gap_lesc_dhkey_t const *p_dhkey)); - -/**@brief Notify the peer of a local keypress. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_PKE_CD_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_PKE_CD_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] kp_not See @ref BLE_GAP_KP_NOT_TYPES. - * - * @retval ::NRF_SUCCESS Keypress notification successfully queued for transmission. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested. - * - Passkey has not been entered. - * - Keypresses have not been enabled by both peers. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_BUSY The BLE stack is busy. Retry at later time. - */ -SVCALL(SD_BLE_GAP_KEYPRESS_NOTIFY, uint32_t, sd_ble_gap_keypress_notify(uint16_t conn_handle, uint8_t kp_not)); - -/**@brief Generate a set of OOB data to send to a peer out of band. - * - * @note The @ref ble_gap_addr_t included in the OOB data returned will be the currently active one (or, if a connection has - * already been established, the one used during connection setup). The application may manually overwrite it with an updated - * value. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Can be @ref BLE_CONN_HANDLE_INVALID if a BLE connection has not been established yet. - * @param[in] p_pk_own LE Secure Connections local P-256 Public Key. - * @param[out] p_oobd_own The OOB data to be sent out of band to a peer. - * - * @retval ::NRF_SUCCESS OOB data successfully generated. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_GET, uint32_t, - sd_ble_gap_lesc_oob_data_get(uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, - ble_gap_lesc_oob_data_t *p_oobd_own)); - -/**@brief Provide the OOB data sent/received out of band. - * - * @note An authentication procedure with OOB selected as an algorithm must be in progress when calling this function. - * @note A @ref BLE_GAP_EVT_LESC_DHKEY_REQUEST event with the oobd_req set to 1 must have been received prior to calling this - * function. - * - * @events - * @event{This function is used during authentication procedures\, see the list of events in the documentation of @ref - * sd_ble_gap_authenticate.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_LESC_BONDING_OOB_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_LESC_BONDING_OOB_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_oobd_own The OOB data sent out of band to a peer or NULL if the peer has not received OOB data. - * Must correspond to @ref ble_gap_sec_params_t::oob flag in @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST. - * @param[in] p_oobd_peer The OOB data received out of band from a peer or NULL if none received. - * Must correspond to @ref ble_gap_sec_params_t::oob flag - * in @ref sd_ble_gap_authenticate in the central role or - * in @ref sd_ble_gap_sec_params_reply in the peripheral role. - * - * @retval ::NRF_SUCCESS OOB data accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Authentication key not requested - * - Not expecting LESC OOB data - * - Have not actually exchanged passkeys. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_LESC_OOB_DATA_SET, uint32_t, - sd_ble_gap_lesc_oob_data_set(uint16_t conn_handle, ble_gap_lesc_oob_data_t const *p_oobd_own, - ble_gap_lesc_oob_data_t const *p_oobd_peer)); - -/**@brief Initiate GAP Encryption procedure. - * - * @details In the central role, this function will initiate the encryption procedure using the encryption information provided. - * - * @events - * @event{@ref BLE_GAP_EVT_CONN_SEC_UPDATE, The connection security has been updated.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_AUTH_MUTEX_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_ENC_MSC} - * @mmsc{@ref BLE_GAP_MULTILINK_CTRL_PROC_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_SEC_REQ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_master_id Pointer to a @ref ble_gap_master_id_t master identification structure. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. - * - * @retval ::NRF_SUCCESS Successfully initiated authentication procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::BLE_ERROR_INVALID_ROLE Operation is not supported in the Peripheral role. - * @retval ::NRF_ERROR_BUSY Procedure already in progress or not allowed at this time, wait for pending procedures to complete and - * retry. - */ -SVCALL(SD_BLE_GAP_ENCRYPT, uint32_t, - sd_ble_gap_encrypt(uint16_t conn_handle, ble_gap_master_id_t const *p_master_id, ble_gap_enc_info_t const *p_enc_info)); - -/**@brief Reply with GAP security information. - * - * @details This function is only used to reply to a @ref BLE_GAP_EVT_SEC_INFO_REQUEST, calling it at other times will result in - * @ref NRF_ERROR_INVALID_STATE. - * @note If the call returns an error code, the request is still pending, and the reply call may be repeated with corrected - * parameters. - * @note Data signing is not yet supported, and p_sign_info must therefore be NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_PERIPH_ENC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_enc_info Pointer to a @ref ble_gap_enc_info_t encryption information structure. May be NULL to signal none is - * available. - * @param[in] p_id_info Pointer to a @ref ble_gap_irk_t identity information structure. May be NULL to signal none is available. - * @param[in] p_sign_info Pointer to a @ref ble_gap_sign_info_t signing information structure. May be NULL to signal none is - * available. - * - * @retval ::NRF_SUCCESS Successfully accepted security information. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - No link has been established. - * - No @ref BLE_GAP_EVT_SEC_INFO_REQUEST pending. - * - Encryption information provided by the app without being requested. See @ref - * ble_gap_evt_sec_info_request_t::enc_info. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_SEC_INFO_REPLY, uint32_t, - sd_ble_gap_sec_info_reply(uint16_t conn_handle, ble_gap_enc_info_t const *p_enc_info, ble_gap_irk_t const *p_id_info, - ble_gap_sign_info_t const *p_sign_info)); - -/**@brief Get the current connection security. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_conn_sec Pointer to a @ref ble_gap_conn_sec_t structure to be filled in. - * - * @retval ::NRF_SUCCESS Current connection security successfully retrieved. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_CONN_SEC_GET, uint32_t, sd_ble_gap_conn_sec_get(uint16_t conn_handle, ble_gap_conn_sec_t *p_conn_sec)); - -/**@brief Start reporting the received signal strength to the application. - * - * A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called. - * - * @events - * @event{@ref BLE_GAP_EVT_RSSI_CHANGED, New RSSI data available. How often the event is generated is - * dependent on the settings of the threshold_dbm - * and skip_count input parameters.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are - * disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID. - * @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref - * BLE_GAP_EVT_RSSI_CHANGED event. - * - * @retval ::NRF_SUCCESS Successfully activated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is already ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count)); - -/**@brief Stop reporting the received signal strength. - * - * @note An RSSI change detected before the call but not yet received by the application - * may be reported after @ref sd_ble_gap_rssi_stop has been called. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @mmsc{@ref BLE_GAP_RSSI_FILT_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Successfully deactivated RSSI reporting. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - */ -SVCALL(SD_BLE_GAP_RSSI_STOP, uint32_t, sd_ble_gap_rssi_stop(uint16_t conn_handle)); - -/**@brief Get the received signal strength for the last connection event. - * - * @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND - * will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start. - * @note ERRATA-153 and ERRATA-225 require the rssi sample to be compensated based on a temperature measurement. - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_RSSI_READ_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored. - * @param[out] p_ch_index Pointer to the location where Channel Index for the RSSI measurement shall be stored. - * - * @retval ::NRF_SUCCESS Successfully read the RSSI. - * @retval ::NRF_ERROR_NOT_FOUND No sample is available. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing. - */ -SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi, uint8_t *p_ch_index)); - -/**@brief Start or continue scanning (GAP Discovery procedure, Observer Procedure). - * - * @note A call to this function will require the application to keep the memory pointed by - * p_adv_report_buffer alive until the buffer is released. The buffer is released when the scanner is stopped - * or when this function is called with another buffer. - * - * @note The scanner will automatically stop in the following cases: - * - @ref sd_ble_gap_scan_stop is called. - * - @ref sd_ble_gap_connect is called. - * - A @ref BLE_GAP_EVT_TIMEOUT with source set to @ref BLE_GAP_TIMEOUT_SRC_SCAN is received. - * - When a @ref BLE_GAP_EVT_ADV_REPORT event is received and @ref ble_gap_adv_report_type_t::status is not set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. In this case scanning is only paused to let the application - * access received data. The application must call this function to continue scanning, or call @ref - * sd_ble_gap_scan_stop to stop scanning. - * - * @note If a @ref BLE_GAP_EVT_ADV_REPORT event is received with @ref ble_gap_adv_report_type_t::status set to - * @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the scanner will continue scanning, and the application will - * receive more reports from this advertising event. The following reports will include the old and new received data. - * - * @events - * @event{@ref BLE_GAP_EVT_ADV_REPORT, An advertising or scan response packet has been received.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Scanner has timed out.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @param[in] p_scan_params Pointer to scan parameters structure. When this function is used to continue - * scanning, this parameter must be NULL. - * @param[in] p_adv_report_buffer Pointer to buffer used to store incoming advertising data. - * The memory pointed to should be kept alive until the scanning is stopped. - * See @ref BLE_GAP_SCAN_BUFFER_SIZE for minimum and maximum buffer size. - * If the scanner receives advertising data larger than can be stored in the buffer, - * a @ref BLE_GAP_EVT_ADV_REPORT will be raised with @ref ble_gap_adv_report_type_t::status - * set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED. - * - * @retval ::NRF_SUCCESS Successfully initiated scanning procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation. Either: - * - Scanning is already ongoing and p_scan_params was not NULL - * - Scanning is not running and p_scan_params was NULL. - * - The scanner has timed out when this function is called to continue scanning. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_NOT_SUPPORTED Unsupported parameters supplied. See @ref ble_gap_scan_params_t. - * @retval ::NRF_ERROR_INVALID_LENGTH The provided buffer length is invalid. See @ref BLE_GAP_SCAN_BUFFER_MIN. - * @retval ::NRF_ERROR_RESOURCES Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Broadcaster) and try again - */ -SVCALL(SD_BLE_GAP_SCAN_START, uint32_t, - sd_ble_gap_scan_start(ble_gap_scan_params_t const *p_scan_params, ble_data_t const *p_adv_report_buffer)); - -/**@brief Stop scanning (GAP Discovery procedure, Observer Procedure). - * - * @note The buffer provided in @ref sd_ble_gap_scan_start is released. - * - * @mscs - * @mmsc{@ref BLE_GAP_SCAN_MSC} - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully stopped scanning procedure. - * @retval ::NRF_ERROR_INVALID_STATE Not in the scanning state. - */ -SVCALL(SD_BLE_GAP_SCAN_STOP, uint32_t, sd_ble_gap_scan_stop(void)); - -/**@brief Create a connection (GAP Link Establishment). - * - * @note If a scanning procedure is currently in progress it will be automatically stopped when calling this function. - * The scanning procedure will be stopped even if the function returns an error. - * - * @events - * @event{@ref BLE_GAP_EVT_CONNECTED, A connection was established.} - * @event{@ref BLE_GAP_EVT_TIMEOUT, Failed to establish a connection.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_WL_SHARE_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_PRIV_MSC} - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @param[in] p_peer_addr Pointer to peer identity address. If @ref ble_gap_scan_params_t::filter_policy is set to use - * whitelist, then p_peer_addr is ignored. - * @param[in] p_scan_params Pointer to scan parameters structure. - * @param[in] p_conn_params Pointer to desired connection parameters. - * @param[in] conn_cfg_tag Tag identifying a configuration set by @ref sd_ble_cfg_set or - * @ref BLE_CONN_CFG_TAG_DEFAULT to use the default connection configuration. - * - * @retval ::NRF_SUCCESS Successfully initiated connection procedure. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid parameter(s) pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * - Invalid parameter(s) in p_scan_params or p_conn_params. - * - Use of whitelist requested but whitelist has not been set, see @ref - * sd_ble_gap_whitelist_set. - * - Peer address was not present in the device identity list, see @ref - * sd_ble_gap_device_identities_set. - * @retval ::NRF_ERROR_NOT_FOUND conn_cfg_tag not found. - * @retval ::NRF_ERROR_INVALID_STATE The SoftDevice is in an invalid state to perform this operation. This may be due to an - * existing locally initiated connect procedure, which must complete before initiating again. - * @retval ::BLE_ERROR_GAP_INVALID_BLE_ADDR Invalid Peer address. - * @retval ::NRF_ERROR_CONN_COUNT The limit of available connections for this connection configuration tag has been reached. - * To increase the number of available connections, - * use @ref sd_ble_cfg_set with @ref BLE_GAP_CFG_ROLE_COUNT or @ref BLE_CONN_CFG_GAP. - * @retval ::NRF_ERROR_RESOURCES Either: - * - Not enough BLE role slots available. - * Stop one or more currently active roles (Central, Peripheral or Observer) and try again. - * - The event_length parameter associated with conn_cfg_tag is too small to be able to - * establish a connection on the selected @ref ble_gap_scan_params_t::scan_phys. - * Use @ref sd_ble_cfg_set to increase the event length. - */ -SVCALL(SD_BLE_GAP_CONNECT, uint32_t, - sd_ble_gap_connect(ble_gap_addr_t const *p_peer_addr, ble_gap_scan_params_t const *p_scan_params, - ble_gap_conn_params_t const *p_conn_params, uint8_t conn_cfg_tag)); - -/**@brief Cancel a connection establishment. - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_CONN_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully canceled an ongoing connection procedure. - * @retval ::NRF_ERROR_INVALID_STATE No locally initiated connect procedure started or connection - * completed occurred. - */ -SVCALL(SD_BLE_GAP_CONNECT_CANCEL, uint32_t, sd_ble_gap_connect_cancel(void)); - -/**@brief Initiate or respond to a PHY Update Procedure - * - * @details This function is used to initiate or respond to a PHY Update Procedure. It will always - * generate a @ref BLE_GAP_EVT_PHY_UPDATE event if successfully executed. - * If this function is used to initiate a PHY Update procedure and the only option - * provided in @ref ble_gap_phys_t::tx_phys and @ref ble_gap_phys_t::rx_phys is the - * currently active PHYs in the respective directions, the SoftDevice will generate a - * @ref BLE_GAP_EVT_PHY_UPDATE with the current PHYs set and will not initiate the - * procedure in the Link Layer. - * - * If @ref ble_gap_phys_t::tx_phys or @ref ble_gap_phys_t::rx_phys is @ref BLE_GAP_PHY_AUTO, - * then the stack will select PHYs based on the peer's PHY preferences and the local link - * configuration. The PHY Update procedure will for this case result in a PHY combination - * that respects the time constraints configured with @ref sd_ble_cfg_set and the current - * link layer data length. - * - * When acting as a central, the SoftDevice will select the fastest common PHY in each direction. - * - * If the peer does not support the PHY Update Procedure, then the resulting - * @ref BLE_GAP_EVT_PHY_UPDATE event will have a status set to - * @ref BLE_HCI_UNSUPPORTED_REMOTE_FEATURE. - * - * If the PHY Update procedure was rejected by the peer due to a procedure collision, the status - * will be @ref BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION or - * @ref BLE_HCI_DIFFERENT_TRANSACTION_COLLISION. - * If the peer responds to the PHY Update procedure with invalid parameters, the status - * will be @ref BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS. - * If the PHY Update procedure was rejected by the peer for a different reason, the status will - * contain the reason as specified by the peer. - * - * @events - * @event{@ref BLE_GAP_EVT_PHY_UPDATE, Result of the PHY Update Procedure.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GAP_CENTRAL_PHY_UPDATE} - * @mmsc{@ref BLE_GAP_PERIPHERAL_PHY_UPDATE} - * @endmscs - * - * @param[in] conn_handle Connection handle to indicate the connection for which the PHY Update is requested. - * @param[in] p_gap_phys Pointer to PHY structure. - * - * @retval ::NRF_SUCCESS Successfully requested a PHY Update. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the combination of - * @ref ble_gap_phys_t::tx_phys, @ref ble_gap_phys_t::rx_phys, and @ref - * ble_gap_data_length_params_t. The connection event length is configured with @ref BLE_CONN_CFG_GAP using @ref sd_ble_cfg_set. - * @retval ::NRF_ERROR_BUSY Procedure is already in progress or not allowed at this time. Process pending events and wait for the - * pending procedure to complete and retry. - * - */ -SVCALL(SD_BLE_GAP_PHY_UPDATE, uint32_t, sd_ble_gap_phy_update(uint16_t conn_handle, ble_gap_phys_t const *p_gap_phys)); - -/**@brief Initiate or respond to a Data Length Update Procedure. - * - * @note If the application uses @ref BLE_GAP_DATA_LENGTH_AUTO for one or more members of - * p_dl_params, the SoftDevice will choose the highest value supported in current - * configuration and connection parameters. - * @note If the link PHY is Coded, the SoftDevice will ensure that the MaxTxTime and/or MaxRxTime - * used in the Data Length Update procedure is at least 2704 us. Otherwise, MaxTxTime and - * MaxRxTime will be limited to maximum 2120 us. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_dl_params Pointer to local parameters to be used in Data Length Update - * Procedure. Set any member to @ref BLE_GAP_DATA_LENGTH_AUTO to let - * the SoftDevice automatically decide the value for that member. - * Set to NULL to use automatic values for all members. - * @param[out] p_dl_limitation Pointer to limitation to be written when local device does not - * have enough resources or does not support the requested Data Length - * Update parameters. Ignored if NULL. - * - * @mscs - * @mmsc{@ref BLE_GAP_DATA_LENGTH_UPDATE_PROCEDURE_MSC} - * @endmscs - * - * @retval ::NRF_SUCCESS Successfully set Data Length Extension initiation/response parameters. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_STATE No link has been established. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. - * @retval ::NRF_ERROR_NOT_SUPPORTED The requested parameters are not supported by the SoftDevice. Inspect - * p_dl_limitation to see which parameter is not supported. - * @retval ::NRF_ERROR_RESOURCES The connection event length configured for this link is not sufficient for the requested - * parameters. Use @ref sd_ble_cfg_set with @ref BLE_CONN_CFG_GAP to increase the connection event length. Inspect p_dl_limitation - * to see where the limitation is. - * @retval ::NRF_ERROR_BUSY Peer has already initiated a Data Length Update Procedure. Process the - * pending @ref BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST event to respond. - */ -SVCALL(SD_BLE_GAP_DATA_LENGTH_UPDATE, uint32_t, - sd_ble_gap_data_length_update(uint16_t conn_handle, ble_gap_data_length_params_t const *p_dl_params, - ble_gap_data_length_limitation_t *p_dl_limitation)); - -/**@brief Start the Quality of Service (QoS) channel survey module. - * - * @details The channel survey module provides measurements of the energy levels on - * the Bluetooth Low Energy channels. When the module is enabled, @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT - * events will periodically report the measured energy levels for each channel. - * - * @note The measurements are scheduled with lower priority than other Bluetooth Low Energy roles, - * Radio Timeslot API events and Flash API events. - * - * @note The channel survey module will attempt to do measurements so that the average interval - * between measurements will be interval_us. However due to the channel survey module - * having the lowest priority of all roles and modules, this may not be possible. In that - * case fewer than expected channel survey reports may be given. - * - * @note In order to use the channel survey module, @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available - * must be set. This is done using @ref sd_ble_cfg_set. - * - * @param[in] interval_us Requested average interval for the measurements and reports. See - * @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVALS for valid ranges. If set - * to @ref BLE_GAP_QOS_CHANNEL_SURVEY_INTERVAL_CONTINUOUS, the channel - * survey role will be scheduled at every available opportunity. - * - * @retval ::NRF_SUCCESS The module is successfully started. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. interval_us is out of the - * allowed range. - * @retval ::NRF_ERROR_INVALID_STATE Trying to start the module when already running. - * @retval ::NRF_ERROR_RESOURCES The channel survey module is not available to the application. - * Set @ref ble_gap_cfg_role_count_t::qos_channel_survey_role_available using - * @ref sd_ble_cfg_set. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_START, uint32_t, sd_ble_gap_qos_channel_survey_start(uint32_t interval_us)); - -/**@brief Stop the Quality of Service (QoS) channel survey module. - * - * @note The SoftDevice may generate one @ref BLE_GAP_EVT_QOS_CHANNEL_SURVEY_REPORT event after this - * function is called. - * - * @retval ::NRF_SUCCESS The module is successfully stopped. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop the module when it is not running. - */ -SVCALL(SD_BLE_GAP_QOS_CHANNEL_SURVEY_STOP, uint32_t, sd_ble_gap_qos_channel_survey_stop(void)); - -/**@brief Obtain the next connection event counter value. - * - * @details The connection event counter is initialized to zero on the first connection event. The value is incremented - * by one for each connection event. For more information see Bluetooth Core Specification v5.0, Vol 6, Part B, - * Section 4.5.1. - * - * @note The connection event counter obtained through this API will be outdated if this API is called - * at the same time as the connection event counter is incremented. - * - * @note This API will always return the last connection event counter + 1. - * The actual connection event may be multiple connection events later if: - * - Slave latency is enabled and there is no data to transmit or receive. - * - Another role is scheduled with a higher priority at the same time as the next connection event. - * - * @param[in] conn_handle Connection handle. - * @param[out] p_counter Pointer to the variable where the next connection event counter will be written. - * - * @retval ::NRF_SUCCESS The connection event counter was successfully retrieved. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle parameter supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GAP_NEXT_CONN_EVT_COUNTER_GET, uint32_t, - sd_ble_gap_next_conn_evt_counter_get(uint16_t conn_handle, uint16_t *p_counter)); - -/**@brief Start triggering a given task on connection event start. - * - * @details When enabled, this feature will trigger a PPI task at the start of connection events. - * The application can configure the SoftDevice to trigger every N connection events starting from - * a given connection event counter. See also @ref ble_gap_conn_event_trigger_t. - * - * @param[in] conn_handle Connection handle. - * @param[in] p_params Connection event trigger parameters. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter supplied. See @ref ble_gap_conn_event_trigger_t. - * @retval ::NRF_ERROR_INVALID_STATE Either: - * - Trying to start connection event triggering when it is already ongoing. - * - @ref ble_gap_conn_event_trigger_t::conn_evt_counter_start is in the past. - * Use @ref sd_ble_gap_next_conn_evt_counter_get to find a new value - to be used as ble_gap_conn_event_trigger_t::conn_evt_counter_start. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_START, uint32_t, - sd_ble_gap_conn_evt_trigger_start(uint16_t conn_handle, ble_gap_conn_event_trigger_t const *p_params)); - -/**@brief Stop triggering the task configured using @ref sd_ble_gap_conn_evt_trigger_start. - * - * @param[in] conn_handle Connection handle. - * - * @retval ::NRF_SUCCESS Success. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied. - * @retval ::NRF_ERROR_INVALID_STATE Trying to stop connection event triggering when it is not enabled. - */ -SVCALL(SD_BLE_GAP_CONN_EVT_TRIGGER_STOP, uint32_t, sd_ble_gap_conn_evt_trigger_stop(uint16_t conn_handle)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GAP_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatt.h b/variants/wio-sdk-wm1110/softdevice/ble_gatt.h deleted file mode 100644 index df0d728fc..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gatt.h +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATT Generic Attribute Profile (GATT) Common - @{ - @brief Common definitions and prototypes for the GATT interfaces. - */ - -#ifndef BLE_GATT_H__ -#define BLE_GATT_H__ - -#include "ble_err.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATT_DEFINES Defines - * @{ */ - -/** @brief Default ATT MTU, in bytes. */ -#define BLE_GATT_ATT_MTU_DEFAULT 23 - -/**@brief Invalid Attribute Handle. */ -#define BLE_GATT_HANDLE_INVALID 0x0000 - -/**@brief First Attribute Handle. */ -#define BLE_GATT_HANDLE_START 0x0001 - -/**@brief Last Attribute Handle. */ -#define BLE_GATT_HANDLE_END 0xFFFF - -/** @defgroup BLE_GATT_TIMEOUT_SOURCES GATT Timeout sources - * @{ */ -#define BLE_GATT_TIMEOUT_SRC_PROTOCOL 0x00 /**< ATT Protocol timeout. */ -/** @} */ - -/** @defgroup BLE_GATT_WRITE_OPS GATT Write operations - * @{ */ -#define BLE_GATT_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATT_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATT_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATT_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATT_OP_EXEC_WRITE_REQ 0x05 /**< Execute Write Request. */ -/** @} */ - -/** @defgroup BLE_GATT_EXEC_WRITE_FLAGS GATT Execute Write flags - * @{ */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_CANCEL 0x00 /**< Cancel prepared write. */ -#define BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE 0x01 /**< Execute prepared write. */ -/** @} */ - -/** @defgroup BLE_GATT_HVX_TYPES GATT Handle Value operations - * @{ */ -#define BLE_GATT_HVX_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATT_HVX_NOTIFICATION 0x01 /**< Handle Value Notification. */ -#define BLE_GATT_HVX_INDICATION 0x02 /**< Handle Value Indication. */ -/** @} */ - -/** @defgroup BLE_GATT_STATUS_CODES GATT Status Codes - * @{ */ -#define BLE_GATT_STATUS_SUCCESS 0x0000 /**< Success. */ -#define BLE_GATT_STATUS_UNKNOWN 0x0001 /**< Unknown or not applicable status. */ -#define BLE_GATT_STATUS_ATTERR_INVALID 0x0100 /**< ATT Error: Invalid Error Code. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_HANDLE 0x0101 /**< ATT Error: Invalid Attribute Handle. */ -#define BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED 0x0102 /**< ATT Error: Read not permitted. */ -#define BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED 0x0103 /**< ATT Error: Write not permitted. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_PDU 0x0104 /**< ATT Error: Used in ATT as Invalid PDU. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION 0x0105 /**< ATT Error: Authenticated link required. */ -#define BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED 0x0106 /**< ATT Error: Used in ATT as Request Not Supported. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_OFFSET 0x0107 /**< ATT Error: Offset specified was past the end of the attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION 0x0108 /**< ATT Error: Used in ATT as Insufficient Authorization. */ -#define BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL 0x0109 /**< ATT Error: Used in ATT as Prepare Queue Full. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND 0x010A /**< ATT Error: Used in ATT as Attribute not found. */ -#define BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG \ - 0x010B /**< ATT Error: Attribute cannot be read or written using read/write blob requests. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE 0x010C /**< ATT Error: Encryption key size used is insufficient. */ -#define BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH 0x010D /**< ATT Error: Invalid value size. */ -#define BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR 0x010E /**< ATT Error: Very unlikely error. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION 0x010F /**< ATT Error: Encrypted link required. */ -#define BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE \ - 0x0110 /**< ATT Error: Attribute type is not a supported grouping attribute. */ -#define BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES 0x0111 /**< ATT Error: Insufficient resources. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_BEGIN 0x0112 /**< ATT Error: Reserved for Future Use range #1 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE1_END 0x017F /**< ATT Error: Reserved for Future Use range #1 end. */ -#define BLE_GATT_STATUS_ATTERR_APP_BEGIN 0x0180 /**< ATT Error: Application range begin. */ -#define BLE_GATT_STATUS_ATTERR_APP_END 0x019F /**< ATT Error: Application range end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_BEGIN 0x01A0 /**< ATT Error: Reserved for Future Use range #2 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE2_END 0x01DF /**< ATT Error: Reserved for Future Use range #2 end. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_BEGIN 0x01E0 /**< ATT Error: Reserved for Future Use range #3 begin. */ -#define BLE_GATT_STATUS_ATTERR_RFU_RANGE3_END 0x01FC /**< ATT Error: Reserved for Future Use range #3 end. */ -#define BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED \ - 0x01FC /**< ATT Common Profile and Service Error: Write request rejected. \ - */ -#define BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR \ - 0x01FD /**< ATT Common Profile and Service Error: Client Characteristic Configuration Descriptor improperly configured. */ -#define BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG \ - 0x01FE /**< ATT Common Profile and Service Error: Procedure Already in Progress. */ -#define BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE 0x01FF /**< ATT Common Profile and Service Error: Out Of Range. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_FORMATS Characteristic Presentation Formats - * @note Found at - * http://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml - * @{ */ -#define BLE_GATT_CPF_FORMAT_RFU 0x00 /**< Reserved For Future Use. */ -#define BLE_GATT_CPF_FORMAT_BOOLEAN 0x01 /**< Boolean. */ -#define BLE_GATT_CPF_FORMAT_2BIT 0x02 /**< Unsigned 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_NIBBLE 0x03 /**< Unsigned 4-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /**< Unsigned 8-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT12 0x05 /**< Unsigned 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT16 0x06 /**< Unsigned 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT24 0x07 /**< Unsigned 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT32 0x08 /**< Unsigned 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT48 0x09 /**< Unsigned 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT64 0x0A /**< Unsigned 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_UINT128 0x0B /**< Unsigned 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT8 0x0C /**< Signed 2-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT12 0x0D /**< Signed 12-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT16 0x0E /**< Signed 16-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT24 0x0F /**< Signed 24-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT32 0x10 /**< Signed 32-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT48 0x11 /**< Signed 48-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT64 0x12 /**< Signed 64-bit integer. */ -#define BLE_GATT_CPF_FORMAT_SINT128 0x13 /**< Signed 128-bit integer. */ -#define BLE_GATT_CPF_FORMAT_FLOAT32 0x14 /**< IEEE-754 32-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_FLOAT64 0x15 /**< IEEE-754 64-bit floating point. */ -#define BLE_GATT_CPF_FORMAT_SFLOAT 0x16 /**< IEEE-11073 16-bit SFLOAT. */ -#define BLE_GATT_CPF_FORMAT_FLOAT 0x17 /**< IEEE-11073 32-bit FLOAT. */ -#define BLE_GATT_CPF_FORMAT_DUINT16 0x18 /**< IEEE-20601 format. */ -#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /**< UTF-8 string. */ -#define BLE_GATT_CPF_FORMAT_UTF16S 0x1A /**< UTF-16 string. */ -#define BLE_GATT_CPF_FORMAT_STRUCT 0x1B /**< Opaque Structure. */ -/** @} */ - -/** @defgroup BLE_GATT_CPF_NAMESPACES GATT Bluetooth Namespaces - * @{ - */ -#define BLE_GATT_CPF_NAMESPACE_BTSIG 0x01 /**< Bluetooth SIG defined Namespace. */ -#define BLE_GATT_CPF_NAMESPACE_DESCRIPTION_UNKNOWN 0x0000 /**< Namespace Description Unknown. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATT_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATT connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_PARAM att_mtu is smaller than @ref BLE_GATT_ATT_MTU_DEFAULT. - */ -typedef struct { - uint16_t att_mtu; /**< Maximum size of ATT packet the SoftDevice can send or receive. - The default and minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - @mscs - @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - @endmscs - */ -} ble_gatt_conn_cfg_t; - -/**@brief GATT Characteristic Properties. */ -typedef struct { - /* Standard properties */ - uint8_t broadcast : 1; /**< Broadcasting of the value permitted. */ - uint8_t read : 1; /**< Reading the value permitted. */ - uint8_t write_wo_resp : 1; /**< Writing the value with Write Command permitted. */ - uint8_t write : 1; /**< Writing the value with Write Request permitted. */ - uint8_t notify : 1; /**< Notification of the value permitted. */ - uint8_t indicate : 1; /**< Indications of the value permitted. */ - uint8_t auth_signed_wr : 1; /**< Writing the value with Signed Write Command permitted. */ -} ble_gatt_char_props_t; - -/**@brief GATT Characteristic Extended Properties. */ -typedef struct { - /* Extended properties */ - uint8_t reliable_wr : 1; /**< Writing the value with Queued Write operations permitted. */ - uint8_t wr_aux : 1; /**< Writing the Characteristic User Description descriptor permitted. */ -} ble_gatt_char_ext_props_t; - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATT_H__ - -/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gattc.h b/variants/wio-sdk-wm1110/softdevice/ble_gattc.h deleted file mode 100644 index f1df1782c..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gattc.h +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTC Generic Attribute Profile (GATT) Client - @{ - @brief Definitions and prototypes for the GATT Client interface. - */ - -#ifndef BLE_GATTC_H__ -#define BLE_GATTC_H__ - -#include "ble_err.h" -#include "ble_gatt.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTC_ENUMERATIONS Enumerations - * @{ */ - -/**@brief GATTC API SVC numbers. */ -enum BLE_GATTC_SVCS { - SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER = BLE_GATTC_SVC_BASE, /**< Primary Service Discovery. */ - SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, /**< Relationship Discovery. */ - SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, /**< Characteristic Discovery. */ - SD_BLE_GATTC_DESCRIPTORS_DISCOVER, /**< Characteristic Descriptor Discovery. */ - SD_BLE_GATTC_ATTR_INFO_DISCOVER, /**< Attribute Information Discovery. */ - SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, /**< Read Characteristic Value by UUID. */ - SD_BLE_GATTC_READ, /**< Generic read. */ - SD_BLE_GATTC_CHAR_VALUES_READ, /**< Read multiple Characteristic Values. */ - SD_BLE_GATTC_WRITE, /**< Generic write. */ - SD_BLE_GATTC_HV_CONFIRM, /**< Handle Value Confirmation. */ - SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. */ -}; - -/** - * @brief GATT Client Event IDs. - */ -enum BLE_GATTC_EVTS { - BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP = BLE_GATTC_EVT_BASE, /**< Primary Service Discovery Response event. \n See @ref - ble_gattc_evt_prim_srvc_disc_rsp_t. */ - BLE_GATTC_EVT_REL_DISC_RSP, /**< Relationship Discovery Response event. \n See @ref ble_gattc_evt_rel_disc_rsp_t. - */ - BLE_GATTC_EVT_CHAR_DISC_RSP, /**< Characteristic Discovery Response event. \n See @ref - ble_gattc_evt_char_disc_rsp_t. */ - BLE_GATTC_EVT_DESC_DISC_RSP, /**< Descriptor Discovery Response event. \n See @ref - ble_gattc_evt_desc_disc_rsp_t. */ - BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, /**< Attribute Information Response event. \n See @ref - ble_gattc_evt_attr_info_disc_rsp_t. */ - BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP, /**< Read By UUID Response event. \n See @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t. */ - BLE_GATTC_EVT_READ_RSP, /**< Read Response event. \n See @ref ble_gattc_evt_read_rsp_t. */ - BLE_GATTC_EVT_CHAR_VALS_READ_RSP, /**< Read multiple Response event. \n See @ref - ble_gattc_evt_char_vals_read_rsp_t. */ - BLE_GATTC_EVT_WRITE_RSP, /**< Write Response event. \n See @ref ble_gattc_evt_write_rsp_t. */ - BLE_GATTC_EVT_HVX, /**< Handle Value Notification or Indication event. \n Confirm indication with @ref - sd_ble_gattc_hv_confirm. \n See @ref ble_gattc_evt_hvx_t. */ - BLE_GATTC_EVT_EXCHANGE_MTU_RSP, /**< Exchange MTU Response event. \n See @ref - ble_gattc_evt_exchange_mtu_rsp_t. */ - BLE_GATTC_EVT_TIMEOUT, /**< Timeout event. \n See @ref ble_gattc_evt_timeout_t. */ - BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE /**< Write without Response transmission complete. \n See @ref - ble_gattc_evt_write_cmd_tx_complete_t. */ -}; - -/**@brief GATTC Option IDs. - * IDs that uniquely identify a GATTC option. - */ -enum BLE_GATTC_OPTS { - BLE_GATTC_OPT_UUID_DISC = BLE_GATTC_OPT_BASE, /**< UUID discovery. @ref ble_gattc_opt_uuid_disc_t */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTC_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTC SVC return values specific to GATTC - * @{ */ -#define BLE_ERROR_GATTC_PROC_NOT_PERMITTED (NRF_GATTC_ERR_BASE + 0x000) /**< Procedure not Permitted. */ -/** @} */ - -/** @defgroup BLE_GATTC_ATTR_INFO_FORMAT Attribute Information Formats - * @{ */ -#define BLE_GATTC_ATTR_INFO_FORMAT_16BIT 1 /**< 16-bit Attribute Information Format. */ -#define BLE_GATTC_ATTR_INFO_FORMAT_128BIT 2 /**< 128-bit Attribute Information Format. */ -/** @} */ - -/** @defgroup BLE_GATTC_DEFAULTS GATT Client defaults - * @{ */ -#define BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Write without Response that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTC_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTC connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t write_cmd_tx_queue_size; /**< The guaranteed minimum number of Write without Response that can be queued for - transmission. The default value is @ref BLE_GATTC_WRITE_CMD_TX_QUEUE_SIZE_DEFAULT */ -} ble_gattc_conn_cfg_t; - -/**@brief Operation Handle Range. */ -typedef struct { - uint16_t start_handle; /**< Start Handle. */ - uint16_t end_handle; /**< End Handle. */ -} ble_gattc_handle_range_t; - -/**@brief GATT service. */ -typedef struct { - ble_uuid_t uuid; /**< Service UUID. */ - ble_gattc_handle_range_t handle_range; /**< Service Handle Range. */ -} ble_gattc_service_t; - -/**@brief GATT include. */ -typedef struct { - uint16_t handle; /**< Include Handle. */ - ble_gattc_service_t included_srvc; /**< Handle of the included service. */ -} ble_gattc_include_t; - -/**@brief GATT characteristic. */ -typedef struct { - ble_uuid_t uuid; /**< Characteristic UUID. */ - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - uint8_t char_ext_props : 1; /**< Extended properties present. */ - uint16_t handle_decl; /**< Handle of the Characteristic Declaration. */ - uint16_t handle_value; /**< Handle of the Characteristic Value. */ -} ble_gattc_char_t; - -/**@brief GATT descriptor. */ -typedef struct { - uint16_t handle; /**< Descriptor Handle. */ - ble_uuid_t uuid; /**< Descriptor UUID. */ -} ble_gattc_desc_t; - -/**@brief Write Parameters. */ -typedef struct { - uint8_t write_op; /**< Write Operation to be performed, see @ref BLE_GATT_WRITE_OPS. */ - uint8_t flags; /**< Flags, see @ref BLE_GATT_EXEC_WRITE_FLAGS. */ - uint16_t handle; /**< Handle to the attribute to be written. */ - uint16_t offset; /**< Offset in bytes. @note For WRITE_CMD and WRITE_REQ, offset must be 0. */ - uint16_t len; /**< Length of data in bytes. */ - uint8_t const *p_value; /**< Pointer to the value data. */ -} ble_gattc_write_params_t; - -/**@brief Attribute Information for 16-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid_t uuid; /**< 16-bit Attribute UUID. */ -} ble_gattc_attr_info16_t; - -/**@brief Attribute Information for 128-bit Attribute UUID. */ -typedef struct { - uint16_t handle; /**< Attribute handle. */ - ble_uuid128_t uuid; /**< 128-bit Attribute UUID. */ -} ble_gattc_attr_info128_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Service count. */ - ble_gattc_service_t services[1]; /**< Service data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_prim_srvc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_REL_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Include count. */ - ble_gattc_include_t includes[1]; /**< Include data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use - event structures with variable length array members. */ -} ble_gattc_evt_rel_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Characteristic count. */ - ble_gattc_char_t chars[1]; /**< Characteristic data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_char_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_DESC_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Descriptor count. */ - ble_gattc_desc_t descs[1]; /**< Descriptor data. @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on how to use event - structures with variable length array members. */ -} ble_gattc_evt_desc_disc_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP. */ -typedef struct { - uint16_t count; /**< Attribute count. */ - uint8_t format; /**< Attribute information format, see @ref BLE_GATTC_ATTR_INFO_FORMAT. */ - union { - ble_gattc_attr_info16_t attr_info16[1]; /**< Attribute information for 16-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - ble_gattc_attr_info128_t attr_info128[1]; /**< Attribute information for 128-bit Attribute UUID. - @note This is a variable length array. The size of 1 indicated is only a - placeholder for compilation. See @ref sd_ble_evt_get for more information on - how to use event structures with variable length array members. */ - } info; /**< Attribute information union. */ -} ble_gattc_evt_attr_info_disc_rsp_t; - -/**@brief GATT read by UUID handle value pair. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t *p_value; /**< Pointer to the Attribute Value, length is available in @ref - ble_gattc_evt_char_val_by_uuid_read_rsp_t::value_len. */ -} ble_gattc_handle_value_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP. */ -typedef struct { - uint16_t count; /**< Handle-Value Pair Count. */ - uint16_t value_len; /**< Length of the value in Handle-Value(s) list. */ - uint8_t handle_value[1]; /**< Handle-Value(s) list. To iterate through the list use @ref - sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter. - @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_val_by_uuid_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_READ_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint16_t offset; /**< Offset of the attribute data. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP. */ -typedef struct { - uint16_t len; /**< Concatenated Attribute values length. */ - uint8_t values[1]; /**< Attribute values. @note This is a variable length array. The size of 1 indicated is only a placeholder - for compilation. See @ref sd_ble_evt_get for more information on how to use event structures with - variable length array members. */ -} ble_gattc_evt_char_vals_read_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_RSP. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - uint8_t write_op; /**< Type of write operation, see @ref BLE_GATT_WRITE_OPS. */ - uint16_t offset; /**< Data offset. */ - uint16_t len; /**< Data length. */ - uint8_t data[1]; /**< Data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_write_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_HVX. */ -typedef struct { - uint16_t handle; /**< Handle to which the HVx operation applies. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t len; /**< Attribute data length. */ - uint8_t data[1]; /**< Attribute data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gattc_evt_hvx_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. */ -typedef struct { - uint16_t server_rx_mtu; /**< Server RX MTU size. */ -} ble_gattc_evt_exchange_mtu_rsp_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gattc_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of write without response transmissions completed. */ -} ble_gattc_evt_write_cmd_tx_complete_t; - -/**@brief GATTC event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which event occurred. */ - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint16_t - error_handle; /**< In case of error: The handle causing the error. In all other cases @ref BLE_GATT_HANDLE_INVALID. */ - union { - ble_gattc_evt_prim_srvc_disc_rsp_t prim_srvc_disc_rsp; /**< Primary Service Discovery Response Event Parameters. */ - ble_gattc_evt_rel_disc_rsp_t rel_disc_rsp; /**< Relationship Discovery Response Event Parameters. */ - ble_gattc_evt_char_disc_rsp_t char_disc_rsp; /**< Characteristic Discovery Response Event Parameters. */ - ble_gattc_evt_desc_disc_rsp_t desc_disc_rsp; /**< Descriptor Discovery Response Event Parameters. */ - ble_gattc_evt_char_val_by_uuid_read_rsp_t - char_val_by_uuid_read_rsp; /**< Characteristic Value Read by UUID Response Event Parameters. */ - ble_gattc_evt_read_rsp_t read_rsp; /**< Read Response Event Parameters. */ - ble_gattc_evt_char_vals_read_rsp_t char_vals_read_rsp; /**< Characteristic Values Read Response Event Parameters. */ - ble_gattc_evt_write_rsp_t write_rsp; /**< Write Response Event Parameters. */ - ble_gattc_evt_hvx_t hvx; /**< Handle Value Notification/Indication Event Parameters. */ - ble_gattc_evt_exchange_mtu_rsp_t exchange_mtu_rsp; /**< Exchange MTU Response Event Parameters. */ - ble_gattc_evt_timeout_t timeout; /**< Timeout Event Parameters. */ - ble_gattc_evt_attr_info_disc_rsp_t attr_info_disc_rsp; /**< Attribute Information Discovery Event Parameters. */ - ble_gattc_evt_write_cmd_tx_complete_t - write_cmd_tx_complete; /**< Write without Response transmission complete Event Parameters. */ - } params; /**< Event Parameters. @note Only valid if @ref gatt_status == @ref BLE_GATT_STATUS_SUCCESS. */ -} ble_gattc_evt_t; - -/**@brief UUID discovery option. - * - * @details Used with @ref sd_ble_opt_set to enable and disable automatic insertion of discovered 128-bit UUIDs to the - * Vendor Specific UUID table. Disabled by default. - * - When disabled, if a procedure initiated by - * @ref sd_ble_gattc_primary_services_discover, - * @ref sd_ble_gattc_relationships_discover, - * @ref sd_ble_gattc_characteristics_discover, - * @ref sd_ble_gattc_descriptors_discover - * finds a 128-bit UUID which was not added by @ref sd_ble_uuid_vs_add, @ref ble_uuid_t::type will be set - * to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * - When enabled, all found 128-bit UUIDs will be automatically added. The application can use - * @ref sd_ble_uuid_encode to retrieve the 128-bit UUID from @ref ble_uuid_t received in the corresponding - * event. If the total number of Vendor Specific UUIDs exceeds the table capacity, @ref ble_uuid_t::type will - * be set to @ref BLE_UUID_TYPE_UNKNOWN in the corresponding event. - * See also @ref ble_common_cfg_vs_uuid_t, @ref sd_ble_uuid_vs_remove. - * - * @note @ref sd_ble_opt_get is not supported for this option. - * - * @retval ::NRF_SUCCESS Set successfully. - * - */ -typedef struct { - uint8_t auto_add_vs_enable : 1; /**< Set to 1 to enable (or 0 to disable) automatic insertion of discovered 128-bit UUIDs. */ -} ble_gattc_opt_uuid_disc_t; - -/**@brief Option structure for GATTC options. */ -typedef union { - ble_gattc_opt_uuid_disc_t uuid_disc; /**< Parameters for the UUID discovery option. */ -} ble_gattc_opt_t; - -/** @} */ - -/** @addtogroup BLE_GATTC_FUNCTIONS Functions - * @{ */ - -/**@brief Initiate or continue a GATT Primary Service Discovery procedure. - * - * @details This function initiates or resumes a Primary Service discovery procedure, starting from the supplied handle. - * If the last service has not been reached, this function must be called again with an updated start handle value to - * continue the search. See also @ref ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_PRIM_SRVC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] start_handle Handle to start searching from. - * @param[in] p_srvc_uuid Pointer to the service UUID to be found. If it is NULL, all primary services will be returned. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Primary Service Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_PRIMARY_SERVICES_DISCOVER, uint32_t, - sd_ble_gattc_primary_services_discover(uint16_t conn_handle, uint16_t start_handle, ble_uuid_t const *p_srvc_uuid)); - -/**@brief Initiate or continue a GATT Relationship Discovery procedure. - * - * @details This function initiates or resumes the Find Included Services sub-procedure. If the last included service has not been - * reached, this must be called again with an updated handle range to continue the search. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_REL_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_REL_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Relationship Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_RELATIONSHIPS_DISCOVER, uint32_t, - sd_ble_gattc_relationships_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Discovery procedure. - * - * @details This function initiates or resumes a Characteristic discovery procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_CHAR_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Service to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Characteristic Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHARACTERISTICS_DISCOVER, uint32_t, - sd_ble_gattc_characteristics_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Characteristic Descriptor Discovery procedure. - * - * @details This function initiates or resumes a Characteristic Descriptor discovery procedure. If the last Descriptor has not - * been reached, this must be called again with an updated handle range to continue the discovery. See also @ref - * ble_gattc_opt_uuid_disc_t. - * - * @events - * @event{@ref BLE_GATTC_EVT_DESC_DISC_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_DESC_DISC_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range A pointer to the range of handles of the Characteristic to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Descriptor Discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_DESCRIPTORS_DISCOVER, uint32_t, - sd_ble_gattc_descriptors_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read using Characteristic UUID procedure. - * - * @details This function initiates or resumes a Read using Characteristic UUID procedure. If the last Characteristic has not been - * reached, this must be called again with an updated handle range to continue the discovery. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_UUID_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_uuid Pointer to a Characteristic value UUID to read. - * @param[in] p_handle_range A pointer to the range of handles to perform this procedure on. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read using Characteristic UUID procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUE_BY_UUID_READ, uint32_t, - sd_ble_gattc_char_value_by_uuid_read(uint16_t conn_handle, ble_uuid_t const *p_uuid, - ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Initiate or continue a GATT Read (Long) Characteristic or Descriptor procedure. - * - * @details This function initiates or resumes a GATT Read (Long) Characteristic or Descriptor procedure. If the Characteristic or - * Descriptor to be read is longer than ATT_MTU - 1, this function must be called multiple times with appropriate offset to read - * the complete value. - * - * @events - * @event{@ref BLE_GATTC_EVT_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_READ_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute to be read. - * @param[in] offset Offset into the attribute value to be read. - * - * @retval ::NRF_SUCCESS Successfully started or resumed the Read (Long) procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_READ, uint32_t, sd_ble_gattc_read(uint16_t conn_handle, uint16_t handle, uint16_t offset)); - -/**@brief Initiate a GATT Read Multiple Characteristic Values procedure. - * - * @details This function initiates a GATT Read Multiple Characteristic Values procedure. - * - * @events - * @event{@ref BLE_GATTC_EVT_CHAR_VALS_READ_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_READ_MULT_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handles A pointer to the handle(s) of the attribute(s) to be read. - * @param[in] handle_count The number of handles in p_handles. - * - * @retval ::NRF_SUCCESS Successfully started the Read Multiple Characteristic Values procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_CHAR_VALUES_READ, uint32_t, - sd_ble_gattc_char_values_read(uint16_t conn_handle, uint16_t const *p_handles, uint16_t handle_count)); - -/**@brief Perform a Write (Characteristic Value or Descriptor, with or without response, signed or not, long or reliable) - * procedure. - * - * @details This function can perform all write procedures described in GATT. - * - * @note Only one write with response procedure can be ongoing per connection at a time. - * If the application tries to write with response while another write with response procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTC_EVT_WRITE_RSP event will be issued as soon as the write response arrives from the peer. - * - * @note The number of Write without Response that can be queued is configured by @ref - * ble_gattc_conn_cfg_t::write_cmd_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. - * A @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event will be issued as soon as the transmission of the write without - * response is complete. - * - * @note The application can keep track of the available queue element count for writes without responses by following the - * procedure below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE, Write without response transmission complete.} - * @event{@ref BLE_GATTC_EVT_WRITE_RSP, Write response received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_WITHOUT_RESP_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_LONG_WRITE_MSC} - * @mmsc{@ref BLE_GATTC_VALUE_RELIABLE_WRITE_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_write_params A pointer to a write parameters structure. - * - * @retval ::NRF_SUCCESS Successfully started the Write procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For write with response, procedure already in progress. Wait for a @ref BLE_GATTC_EVT_WRITE_RSP event - * and retry. - * @retval ::NRF_ERROR_RESOURCES Too many writes without responses queued. - * Wait for a @ref BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_WRITE, uint32_t, sd_ble_gattc_write(uint16_t conn_handle, ble_gattc_write_params_t const *p_write_params)); - -/**@brief Send a Handle Value Confirmation to the GATT Server. - * - * @mscs - * @mmsc{@ref BLE_GATTC_HVI_MSC} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] handle The handle of the attribute in the indication. - * - * @retval ::NRF_SUCCESS Successfully queued the Handle Value Confirmation for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no Indication pending to be confirmed. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_HV_CONFIRM, uint32_t, sd_ble_gattc_hv_confirm(uint16_t conn_handle, uint16_t handle)); - -/**@brief Discovers information about a range of attributes on a GATT server. - * - * @events - * @event{@ref BLE_GATTC_EVT_ATTR_INFO_DISC_RSP, Generated when information about a range of attributes has been received.} - * @endevents - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] p_handle_range The range of handles to request information about. - * - * @retval ::NRF_SUCCESS Successfully started an attribute information discovery procedure. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_ATTR_INFO_DISCOVER, uint32_t, - sd_ble_gattc_attr_info_discover(uint16_t conn_handle, ble_gattc_handle_range_t const *p_handle_range)); - -/**@brief Start an ATT_MTU exchange by sending an Exchange MTU Request to the server. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value, and - * - The Server RX MTU value from @ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @events - * @event{@ref BLE_GATTC_EVT_EXCHANGE_MTU_RSP} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTC_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] client_rx_mtu Client RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - used for this connection. - * - The value must be equal to Server RX MTU size given in @ref sd_ble_gatts_exchange_mtu_reply - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent request to the server. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid connection state or an ATT_MTU exchange was already requested once. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Client RX MTU size supplied. - * @retval ::NRF_ERROR_BUSY Client procedure already in progress. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - reestablishing the connection. - */ -SVCALL(SD_BLE_GATTC_EXCHANGE_MTU_REQUEST, uint32_t, - sd_ble_gattc_exchange_mtu_request(uint16_t conn_handle, uint16_t client_rx_mtu)); - -/**@brief Iterate through Handle-Value(s) list in @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * - * @param[in] p_gattc_evt Pointer to event buffer containing @ref BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP event. - * @note If the buffer contains different event, behavior is undefined. - * @param[in,out] p_iter Iterator, points to @ref ble_gattc_handle_value_t structure that will be filled in with - * the next Handle-Value pair in each iteration. If the function returns other than - * @ref NRF_SUCCESS, it will not be changed. - * - To start iteration, initialize the structure to zero. - * - To continue, pass the value from previous iteration. - * - * \code - * ble_gattc_handle_value_t iter; - * memset(&iter, 0, sizeof(ble_gattc_handle_value_t)); - * while (sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(&ble_evt.evt.gattc_evt, &iter) == NRF_SUCCESS) - * { - * app_handle = iter.handle; - * memcpy(app_value, iter.p_value, ble_evt.evt.gattc_evt.params.char_val_by_uuid_read_rsp.value_len); - * } - * \endcode - * - * @retval ::NRF_SUCCESS Successfully retrieved the next Handle-Value pair. - * @retval ::NRF_ERROR_NOT_FOUND No more Handle-Value pairs available in the list. - */ -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter); - -/** @} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE uint32_t sd_ble_gattc_evt_char_val_by_uuid_read_rsp_iter(ble_gattc_evt_t *p_gattc_evt, - ble_gattc_handle_value_t *p_iter) -{ - uint32_t value_len = p_gattc_evt->params.char_val_by_uuid_read_rsp.value_len; - uint8_t *p_first = p_gattc_evt->params.char_val_by_uuid_read_rsp.handle_value; - uint8_t *p_next = p_iter->p_value ? p_iter->p_value + value_len : p_first; - - if ((p_next - p_first) / (sizeof(uint16_t) + value_len) < p_gattc_evt->params.char_val_by_uuid_read_rsp.count) { - p_iter->handle = (uint16_t)p_next[1] << 8 | p_next[0]; - p_iter->p_value = p_next + sizeof(uint16_t); - return NRF_SUCCESS; - } else { - return NRF_ERROR_NOT_FOUND; - } -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_GATTC_H__ */ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_gatts.h b/variants/wio-sdk-wm1110/softdevice/ble_gatts.h deleted file mode 100644 index dc94957cd..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_gatts.h +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_GATTS Generic Attribute Profile (GATT) Server - @{ - @brief Definitions and prototypes for the GATTS interface. - */ - -#ifndef BLE_GATTS_H__ -#define BLE_GATTS_H__ - -#include "ble_err.h" -#include "ble_gap.h" -#include "ble_gatt.h" -#include "ble_hci.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_GATTS_ENUMERATIONS Enumerations - * @{ */ - -/** - * @brief GATTS API SVC numbers. - */ -enum BLE_GATTS_SVCS { - SD_BLE_GATTS_SERVICE_ADD = BLE_GATTS_SVC_BASE, /**< Add a service. */ - SD_BLE_GATTS_INCLUDE_ADD, /**< Add an included service. */ - SD_BLE_GATTS_CHARACTERISTIC_ADD, /**< Add a characteristic. */ - SD_BLE_GATTS_DESCRIPTOR_ADD, /**< Add a generic attribute. */ - SD_BLE_GATTS_VALUE_SET, /**< Set an attribute value. */ - SD_BLE_GATTS_VALUE_GET, /**< Get an attribute value. */ - SD_BLE_GATTS_HVX, /**< Handle Value Notification or Indication. */ - SD_BLE_GATTS_SERVICE_CHANGED, /**< Perform a Service Changed Indication to one or more peers. */ - SD_BLE_GATTS_RW_AUTHORIZE_REPLY, /**< Reply to an authorization request for a read or write operation on one or more - attributes. */ - SD_BLE_GATTS_SYS_ATTR_SET, /**< Set the persistent system attributes for a connection. */ - SD_BLE_GATTS_SYS_ATTR_GET, /**< Retrieve the persistent system attributes. */ - SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, /**< Retrieve the first valid user handle. */ - SD_BLE_GATTS_ATTR_GET, /**< Retrieve the UUID and/or metadata of an attribute. */ - SD_BLE_GATTS_EXCHANGE_MTU_REPLY /**< Reply to Exchange MTU Request. */ -}; - -/** - * @brief GATT Server Event IDs. - */ -enum BLE_GATTS_EVTS { - BLE_GATTS_EVT_WRITE = BLE_GATTS_EVT_BASE, /**< Write operation performed. \n See - @ref ble_gatts_evt_write_t. */ - BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, /**< Read/Write Authorization request. \n Reply with - @ref sd_ble_gatts_rw_authorize_reply. \n See @ref ble_gatts_evt_rw_authorize_request_t. - */ - BLE_GATTS_EVT_SYS_ATTR_MISSING, /**< A persistent system attribute access is pending. \n Respond with @ref - sd_ble_gatts_sys_attr_set. \n See @ref ble_gatts_evt_sys_attr_missing_t. */ - BLE_GATTS_EVT_HVC, /**< Handle Value Confirmation. \n See @ref ble_gatts_evt_hvc_t. - */ - BLE_GATTS_EVT_SC_CONFIRM, /**< Service Changed Confirmation. \n No additional event - structure applies. */ - BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, /**< Exchange MTU Request. \n Reply with - @ref sd_ble_gatts_exchange_mtu_reply. \n See @ref ble_gatts_evt_exchange_mtu_request_t. - */ - BLE_GATTS_EVT_TIMEOUT, /**< Peer failed to respond to an ATT request in time. \n See @ref - ble_gatts_evt_timeout_t. */ - BLE_GATTS_EVT_HVN_TX_COMPLETE /**< Handle Value Notification transmission complete. \n See @ref - ble_gatts_evt_hvn_tx_complete_t. */ -}; - -/**@brief GATTS Configuration IDs. - * - * IDs that uniquely identify a GATTS configuration. - */ -enum BLE_GATTS_CFGS { - BLE_GATTS_CFG_SERVICE_CHANGED = BLE_GATTS_CFG_BASE, /**< Service changed configuration. */ - BLE_GATTS_CFG_ATTR_TAB_SIZE, /**< Attribute table size configuration. */ - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM, /**< Service changed CCCD permission configuration. */ -}; - -/** @} */ - -/** @addtogroup BLE_GATTS_DEFINES Defines - * @{ */ - -/** @defgroup BLE_ERRORS_GATTS SVC return values specific to GATTS - * @{ */ -#define BLE_ERROR_GATTS_INVALID_ATTR_TYPE (NRF_GATTS_ERR_BASE + 0x000) /**< Invalid attribute type. */ -#define BLE_ERROR_GATTS_SYS_ATTR_MISSING (NRF_GATTS_ERR_BASE + 0x001) /**< System Attributes missing. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_LENS_MAX Maximum attribute lengths - * @{ */ -#define BLE_GATTS_FIX_ATTR_LEN_MAX (510) /**< Maximum length for fixed length Attribute Values. */ -#define BLE_GATTS_VAR_ATTR_LEN_MAX (512) /**< Maximum length for variable length Attribute Values. */ -/** @} */ - -/** @defgroup BLE_GATTS_SRVC_TYPES GATT Server Service Types - * @{ */ -#define BLE_GATTS_SRVC_TYPE_INVALID 0x00 /**< Invalid Service Type. */ -#define BLE_GATTS_SRVC_TYPE_PRIMARY 0x01 /**< Primary Service. */ -#define BLE_GATTS_SRVC_TYPE_SECONDARY 0x02 /**< Secondary Type. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TYPES GATT Server Attribute Types - * @{ */ -#define BLE_GATTS_ATTR_TYPE_INVALID 0x00 /**< Invalid Attribute Type. */ -#define BLE_GATTS_ATTR_TYPE_PRIM_SRVC_DECL 0x01 /**< Primary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_SEC_SRVC_DECL 0x02 /**< Secondary Service Declaration. */ -#define BLE_GATTS_ATTR_TYPE_INC_DECL 0x03 /**< Include Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_DECL 0x04 /**< Characteristic Declaration. */ -#define BLE_GATTS_ATTR_TYPE_CHAR_VAL 0x05 /**< Characteristic Value. */ -#define BLE_GATTS_ATTR_TYPE_DESC 0x06 /**< Descriptor. */ -#define BLE_GATTS_ATTR_TYPE_OTHER 0x07 /**< Other, non-GATT specific type. */ -/** @} */ - -/** @defgroup BLE_GATTS_OPS GATT Server Operations - * @{ */ -#define BLE_GATTS_OP_INVALID 0x00 /**< Invalid Operation. */ -#define BLE_GATTS_OP_WRITE_REQ 0x01 /**< Write Request. */ -#define BLE_GATTS_OP_WRITE_CMD 0x02 /**< Write Command. */ -#define BLE_GATTS_OP_SIGN_WRITE_CMD 0x03 /**< Signed Write Command. */ -#define BLE_GATTS_OP_PREP_WRITE_REQ 0x04 /**< Prepare Write Request. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL 0x05 /**< Execute Write Request: Cancel all prepared writes. */ -#define BLE_GATTS_OP_EXEC_WRITE_REQ_NOW 0x06 /**< Execute Write Request: Immediately execute all prepared writes. */ -/** @} */ - -/** @defgroup BLE_GATTS_VLOCS GATT Value Locations - * @{ */ -#define BLE_GATTS_VLOC_INVALID 0x00 /**< Invalid Location. */ -#define BLE_GATTS_VLOC_STACK 0x01 /**< Attribute Value is located in stack memory, no user memory is required. */ -#define BLE_GATTS_VLOC_USER \ - 0x02 /**< Attribute Value is located in user memory. This requires the user to maintain a valid buffer through the lifetime \ - of the attribute, since the stack will read and write directly to the memory using the pointer provided in the APIs. \ - There are no alignment requirements for the buffer. */ -/** @} */ - -/** @defgroup BLE_GATTS_AUTHORIZE_TYPES GATT Server Authorization Types - * @{ */ -#define BLE_GATTS_AUTHORIZE_TYPE_INVALID 0x00 /**< Invalid Type. */ -#define BLE_GATTS_AUTHORIZE_TYPE_READ 0x01 /**< Authorize a Read Operation. */ -#define BLE_GATTS_AUTHORIZE_TYPE_WRITE 0x02 /**< Authorize a Write Request Operation. */ -/** @} */ - -/** @defgroup BLE_GATTS_SYS_ATTR_FLAGS System Attribute Flags - * @{ */ -#define BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS (1 << 0) /**< Restrict system attributes to system services only. */ -#define BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS (1 << 1) /**< Restrict system attributes to user services only. */ -/** @} */ - -/** @defgroup BLE_GATTS_SERVICE_CHANGED Service Changed Inclusion Values - * @{ - */ -#define BLE_GATTS_SERVICE_CHANGED_DEFAULT \ - (1) /**< Default is to include the Service Changed characteristic in the Attribute Table. */ -/** @} */ - -/** @defgroup BLE_GATTS_ATTR_TAB_SIZE Attribute Table size - * @{ - */ -#define BLE_GATTS_ATTR_TAB_SIZE_MIN (248) /**< Minimum Attribute Table size */ -#define BLE_GATTS_ATTR_TAB_SIZE_DEFAULT (1408) /**< Default Attribute Table size. */ -/** @} */ - -/** @defgroup BLE_GATTS_DEFAULTS GATT Server defaults - * @{ - */ -#define BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT \ - 1 /**< Default number of Handle Value Notifications that can be queued for transmission. */ -/** @} */ - -/** @} */ - -/** @addtogroup BLE_GATTS_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE GATTS connection configuration parameters, set with @ref sd_ble_cfg_set. - */ -typedef struct { - uint8_t hvn_tx_queue_size; /**< Minimum guaranteed number of Handle Value Notifications that can be queued for transmission. - The default value is @ref BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT */ -} ble_gatts_conn_cfg_t; - -/**@brief Attribute metadata. */ -typedef struct { - ble_gap_conn_sec_mode_t read_perm; /**< Read permissions. */ - ble_gap_conn_sec_mode_t write_perm; /**< Write permissions. */ - uint8_t vlen : 1; /**< Variable length attribute. */ - uint8_t vloc : 2; /**< Value location, see @ref BLE_GATTS_VLOCS.*/ - uint8_t rd_auth : 1; /**< Read authorization and value will be requested from the application on every read operation. */ - uint8_t wr_auth : 1; /**< Write authorization will be requested from the application on every Write Request operation (but not - Write Command). */ -} ble_gatts_attr_md_t; - -/**@brief GATT Attribute. */ -typedef struct { - ble_uuid_t const *p_uuid; /**< Pointer to the attribute UUID. */ - ble_gatts_attr_md_t const *p_attr_md; /**< Pointer to the attribute metadata structure. */ - uint16_t init_len; /**< Initial attribute value length in bytes. */ - uint16_t init_offs; /**< Initial attribute value offset in bytes. If different from zero, the first init_offs bytes of the - attribute value will be left uninitialized. */ - uint16_t max_len; /**< Maximum attribute value length in bytes, see @ref BLE_GATTS_ATTR_LENS_MAX for maximum values. */ - uint8_t *p_value; /**< Pointer to the attribute data. Please note that if the @ref BLE_GATTS_VLOC_USER value location is - selected in the attribute metadata, this will have to point to a buffer that remains valid through the - lifetime of the attribute. This excludes usage of automatic variables that may go out of scope or any - other temporary location. The stack may access that memory directly without the application's - knowledge. For writable characteristics, this value must not be a location in flash memory.*/ -} ble_gatts_attr_t; - -/**@brief GATT Attribute Value. */ -typedef struct { - uint16_t len; /**< Length in bytes to be written or read. Length in bytes written or read after successful return.*/ - uint16_t offset; /**< Attribute value offset. */ - uint8_t *p_value; /**< Pointer to where value is stored or will be stored. - If value is stored in user memory, only the attribute length is updated when p_value == NULL. - Set to NULL when reading to obtain the complete length of the attribute value */ -} ble_gatts_value_t; - -/**@brief GATT Characteristic Presentation Format. */ -typedef struct { - uint8_t format; /**< Format of the value, see @ref BLE_GATT_CPF_FORMATS. */ - int8_t exponent; /**< Exponent for integer data types. */ - uint16_t unit; /**< Unit from Bluetooth Assigned Numbers. */ - uint8_t name_space; /**< Namespace from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ - uint16_t desc; /**< Namespace description from Bluetooth Assigned Numbers, see @ref BLE_GATT_CPF_NAMESPACES. */ -} ble_gatts_char_pf_t; - -/**@brief GATT Characteristic metadata. */ -typedef struct { - ble_gatt_char_props_t char_props; /**< Characteristic Properties. */ - ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic Extended Properties. */ - uint8_t const * - p_char_user_desc; /**< Pointer to a UTF-8 encoded string (non-NULL terminated), NULL if the descriptor is not required. */ - uint16_t char_user_desc_max_size; /**< The maximum size in bytes of the user description descriptor. */ - uint16_t char_user_desc_size; /**< The size of the user description, must be smaller or equal to char_user_desc_max_size. */ - ble_gatts_char_pf_t const - *p_char_pf; /**< Pointer to a presentation format structure or NULL if the CPF descriptor is not required. */ - ble_gatts_attr_md_t const - *p_user_desc_md; /**< Attribute metadata for the User Description descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_cccd_md; /**< Attribute metadata for the Client Characteristic Configuration Descriptor, or NULL for default values. */ - ble_gatts_attr_md_t const - *p_sccd_md; /**< Attribute metadata for the Server Characteristic Configuration Descriptor, or NULL for default values. */ -} ble_gatts_char_md_t; - -/**@brief GATT Characteristic Definition Handles. */ -typedef struct { - uint16_t value_handle; /**< Handle to the characteristic value. */ - uint16_t user_desc_handle; /**< Handle to the User Description descriptor, or @ref BLE_GATT_HANDLE_INVALID if not present. */ - uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ - uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or @ref BLE_GATT_HANDLE_INVALID if - not present. */ -} ble_gatts_char_handles_t; - -/**@brief GATT HVx parameters. */ -typedef struct { - uint16_t handle; /**< Characteristic Value Handle. */ - uint8_t type; /**< Indication or Notification, see @ref BLE_GATT_HVX_TYPES. */ - uint16_t offset; /**< Offset within the attribute value. */ - uint16_t *p_len; /**< Length in bytes to be written, length in bytes written after return. */ - uint8_t const *p_data; /**< Actual data content, use NULL to use the current attribute value. */ -} ble_gatts_hvx_params_t; - -/**@brief GATT Authorization parameters. */ -typedef struct { - uint16_t gatt_status; /**< GATT status code for the operation, see @ref BLE_GATT_STATUS_CODES. */ - uint8_t update : 1; /**< If set, data supplied in p_data will be used to update the attribute value. - Please note that for @ref BLE_GATTS_AUTHORIZE_TYPE_WRITE operations this bit must always be set, - as the data to be written needs to be stored and later provided by the application. */ - uint16_t offset; /**< Offset of the attribute value being updated. */ - uint16_t len; /**< Length in bytes of the value in p_data pointer, see @ref BLE_GATTS_ATTR_LENS_MAX. */ - uint8_t const *p_data; /**< Pointer to new value used to update the attribute value. */ -} ble_gatts_authorize_params_t; - -/**@brief GATT Read or Write Authorize Reply parameters. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_authorize_params_t read; /**< Read authorization parameters. */ - ble_gatts_authorize_params_t write; /**< Write authorization parameters. */ - } params; /**< Reply Parameters. */ -} ble_gatts_rw_authorize_reply_params_t; - -/**@brief Service Changed Inclusion configuration parameters, set with @ref sd_ble_cfg_set. */ -typedef struct { - uint8_t service_changed : 1; /**< If 1, include the Service Changed characteristic in the Attribute Table. Default is @ref - BLE_GATTS_SERVICE_CHANGED_DEFAULT. */ -} ble_gatts_cfg_service_changed_t; - -/**@brief Service Changed CCCD permission configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note @ref ble_gatts_attr_md_t::vlen is ignored and should be set to 0. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - @ref ble_gatts_attr_md_t::write_perm is out of range. - * - @ref ble_gatts_attr_md_t::write_perm is @ref BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS, that is - * not allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::read_perm, only @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN is - * allowed by the Bluetooth Specification. - * - wrong @ref ble_gatts_attr_md_t::vloc, only @ref BLE_GATTS_VLOC_STACK is allowed. - * @retval ::NRF_ERROR_NOT_SUPPORTED Security Mode 2 not supported - */ -typedef struct { - ble_gatts_attr_md_t - perm; /**< Permission for Service Changed CCCD. Default is @ref BLE_GAP_CONN_SEC_MODE_SET_OPEN, no authorization. */ -} ble_gatts_cfg_service_changed_cccd_perm_t; - -/**@brief Attribute table size configuration parameters, set with @ref sd_ble_cfg_set. - * - * @retval ::NRF_ERROR_INVALID_LENGTH One or more of the following is true: - * - The specified Attribute Table size is too small. - * The minimum acceptable size is defined by @ref BLE_GATTS_ATTR_TAB_SIZE_MIN. - * - The specified Attribute Table size is not a multiple of 4. - */ -typedef struct { - uint32_t attr_tab_size; /**< Attribute table size. Default is @ref BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, minimum is @ref - BLE_GATTS_ATTR_TAB_SIZE_MIN. */ -} ble_gatts_cfg_attr_tab_size_t; - -/**@brief Config structure for GATTS configurations. */ -typedef union { - ble_gatts_cfg_service_changed_t - service_changed; /**< Include service changed characteristic, cfg_id is @ref BLE_GATTS_CFG_SERVICE_CHANGED. */ - ble_gatts_cfg_service_changed_cccd_perm_t service_changed_cccd_perm; /**< Service changed CCCD permission, cfg_id is @ref - BLE_GATTS_CFG_SERVICE_CHANGED_CCCD_PERM. */ - ble_gatts_cfg_attr_tab_size_t attr_tab_size; /**< Attribute table size, cfg_id is @ref BLE_GATTS_CFG_ATTR_TAB_SIZE. */ -} ble_gatts_cfg_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_WRITE. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */ - uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref - sd_ble_gatts_value_set to finalize the writing operation. */ - uint16_t offset; /**< Offset for the write operation. */ - uint16_t len; /**< Length of the received data. */ - uint8_t data[1]; /**< Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for - compilation. See @ref sd_ble_evt_get for more information on how to use event structures with variable - length array members. */ -} ble_gatts_evt_write_t; - -/**@brief Event substructure for authorized read requests, see @ref ble_gatts_evt_rw_authorize_request_t. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ - ble_uuid_t uuid; /**< Attribute UUID. */ - uint16_t offset; /**< Offset for the read operation. */ -} ble_gatts_evt_read_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST. */ -typedef struct { - uint8_t type; /**< Type of authorize operation, see @ref BLE_GATTS_AUTHORIZE_TYPES. */ - union { - ble_gatts_evt_read_t read; /**< Attribute Read Parameters. */ - ble_gatts_evt_write_t write; /**< Attribute Write Parameters. */ - } request; /**< Request Parameters. */ -} ble_gatts_evt_rw_authorize_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. */ -typedef struct { - uint8_t hint; /**< Hint (currently unused). */ -} ble_gatts_evt_sys_attr_missing_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVC. */ -typedef struct { - uint16_t handle; /**< Attribute Handle. */ -} ble_gatts_evt_hvc_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST. */ -typedef struct { - uint16_t client_rx_mtu; /**< Client RX MTU size. */ -} ble_gatts_evt_exchange_mtu_request_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_TIMEOUT. */ -typedef struct { - uint8_t src; /**< Timeout source, see @ref BLE_GATT_TIMEOUT_SOURCES. */ -} ble_gatts_evt_timeout_t; - -/**@brief Event structure for @ref BLE_GATTS_EVT_HVN_TX_COMPLETE. */ -typedef struct { - uint8_t count; /**< Number of notification transmissions completed. */ -} ble_gatts_evt_hvn_tx_complete_t; - -/**@brief GATTS event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occurred. */ - union { - ble_gatts_evt_write_t write; /**< Write Event Parameters. */ - ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ - ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ - ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ - ble_gatts_evt_exchange_mtu_request_t exchange_mtu_request; /**< Exchange MTU Request Event Parameters. */ - ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */ - ble_gatts_evt_hvn_tx_complete_t hvn_tx_complete; /**< Handle Value Notification transmission complete Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_gatts_evt_t; - -/** @} */ - -/** @addtogroup BLE_GATTS_FUNCTIONS Functions - * @{ */ - -/**@brief Add a service declaration to the Attribute Table. - * - * @note Secondary Services are only relevant in the context of the entity that references them, it is therefore forbidden to - * add a secondary service declaration that is not referenced by another service later in the Attribute Table. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] type Toggles between primary and secondary services, see @ref BLE_GATTS_SRVC_TYPES. - * @param[in] p_uuid Pointer to service UUID. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a service declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, Vendor Specific UUIDs need to be present in the table. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SERVICE_ADD, uint32_t, sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle)); - -/**@brief Add an include declaration to the Attribute Table. - * - * @note It is currently only possible to add an include declaration to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note The included service must already be present in the Attribute Table prior to this call. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the included service is to be placed, if @ref BLE_GATT_HANDLE_INVALID - * is used, it will be placed sequentially. - * @param[in] inc_srvc_handle Handle of the included service. - * @param[out] p_include_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added an include declaration. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, handle values need to match previously added services. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_NOT_SUPPORTED Feature is not supported, service_handle must be that of the last added service. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, self inclusions are not allowed. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - */ -SVCALL(SD_BLE_GATTS_INCLUDE_ADD, uint32_t, - sd_ble_gatts_include_add(uint16_t service_handle, uint16_t inc_srvc_handle, uint16_t *p_include_handle)); - -/**@brief Add a characteristic declaration, a characteristic value declaration and optional characteristic descriptor declarations - * to the Attribute Table. - * - * @note It is currently only possible to add a characteristic to the last added service (i.e. only sequential population is - * supported at this time). - * - * @note Several restrictions apply to the parameters, such as matching permissions between the user description descriptor and - * the writable auxiliaries bits, readable (no security) and writable (selectable) CCCDs and SCCDs and valid presentation format - * values. - * - * @note If no metadata is provided for the optional descriptors, their permissions will be derived from the characteristic - * permissions. - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] service_handle Handle of the service where the characteristic is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_char_md Characteristic metadata. - * @param[in] p_attr_char_value Pointer to the attribute structure corresponding to the characteristic value. - * @param[out] p_handles Pointer to the structure where the assigned handles will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a characteristic. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, service handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a service context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_CHARACTERISTIC_ADD, uint32_t, - sd_ble_gatts_characteristic_add(uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, - ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles)); - -/**@brief Add a descriptor to the Attribute Table. - * - * @note It is currently only possible to add a descriptor to the last added characteristic (i.e. only sequential population is - * supported at this time). - * - * @mscs - * @mmsc{@ref BLE_GATTS_ATT_TABLE_POP_MSC} - * @endmscs - * - * @param[in] char_handle Handle of the characteristic where the descriptor is to be placed, if @ref BLE_GATT_HANDLE_INVALID is - * used, it will be placed sequentially. - * @param[in] p_attr Pointer to the attribute structure. - * @param[out] p_handle Pointer to a 16-bit word where the assigned handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully added a descriptor. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied, characteristic handle, Vendor Specific UUIDs, lengths, and - * permissions need to adhere to the constraints. - * @retval ::NRF_ERROR_INVALID_STATE Invalid state to perform operation, a characteristic context is required. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden value supplied, certain UUIDs are reserved for the stack. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - */ -SVCALL(SD_BLE_GATTS_DESCRIPTOR_ADD, uint32_t, - sd_ble_gatts_descriptor_add(uint16_t char_handle, ble_gatts_attr_t const *p_attr, uint16_t *p_handle)); - -/**@brief Set the value of a given attribute. - * - * @note Values other than system attributes can be set at any time, regardless of whether any active connections exist. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully set the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN Forbidden handle supplied, certain attributes are not modifiable by the application. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied, attribute lengths are restricted by @ref BLE_GATTS_ATTR_LENS_MAX. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - */ -SVCALL(SD_BLE_GATTS_VALUE_SET, uint32_t, - sd_ble_gatts_value_set(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Get the value of a given attribute. - * - * @note If the attribute value is longer than the size of the supplied buffer, - * @ref ble_gatts_value_t::len will return the total attribute value length (excluding offset), - * and not the number of bytes actually returned in @ref ble_gatts_value_t::p_value. - * The application may use this information to allocate a suitable buffer size. - * - * @note When retrieving system attribute values with this function, the connection handle - * may refer to an already disconnected connection. Refer to the documentation of - * @ref sd_ble_gatts_sys_attr_get for further information. - * - * @param[in] conn_handle Connection handle. Ignored if the value does not belong to a system attribute. - * @param[in] handle Attribute handle. - * @param[in,out] p_value Attribute value information. - * - * @retval ::NRF_SUCCESS Successfully retrieved the value of the attribute. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid attribute offset supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied on a system attribute. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - */ -SVCALL(SD_BLE_GATTS_VALUE_GET, uint32_t, - sd_ble_gatts_value_get(uint16_t conn_handle, uint16_t handle, ble_gatts_value_t *p_value)); - -/**@brief Notify or Indicate an attribute value. - * - * @details This function checks for the relevant Client Characteristic Configuration descriptor value to verify that the relevant - * operation (notification or indication) has been enabled by the client. It is also able to update the attribute value before - * issuing the PDU, so that the application can atomically perform a value update and a server initiated transaction with a single - * API call. - * - * @note The local attribute value may be updated even if an outgoing packet is not sent to the peer due to an error during - * execution. The Attribute Table has been updated if one of the following error codes is returned: @ref NRF_ERROR_INVALID_STATE, - * @ref NRF_ERROR_BUSY, - * @ref NRF_ERROR_FORBIDDEN, @ref BLE_ERROR_GATTS_SYS_ATTR_MISSING and @ref NRF_ERROR_RESOURCES. - * The caller can check whether the value has been updated by looking at the contents of *(@ref - * ble_gatts_hvx_params_t::p_len). - * - * @note Only one indication procedure can be ongoing per connection at a time. - * If the application tries to indicate an attribute value while another indication procedure is ongoing, - * the function call will return @ref NRF_ERROR_BUSY. - * A @ref BLE_GATTS_EVT_HVC event will be issued as soon as the confirmation arrives from the peer. - * - * @note The number of Handle Value Notifications that can be queued is configured by @ref - * ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return @ref NRF_ERROR_RESOURCES. A @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete. - * - * @note The application can keep track of the available queue element count for notifications by following the procedure - * below: - * - Store initial queue element count in a variable. - * - Decrement the variable, which stores the currently available queue element count, by one when a call to this - * function returns @ref NRF_SUCCESS. - * - Increment the variable, which stores the current available queue element count, by the count variable in @ref - * BLE_GATTS_EVT_HVN_TX_COMPLETE event. - * - * @events - * @event{@ref BLE_GATTS_EVT_HVN_TX_COMPLETE, Notification transmission complete.} - * @event{@ref BLE_GATTS_EVT_HVC, Confirmation received from the peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_HVN_MSC} - * @mmsc{@ref BLE_GATTS_HVI_MSC} - * @mmsc{@ref BLE_GATTS_HVX_DISABLED_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in,out] p_hvx_params Pointer to an HVx parameters structure. If @ref ble_gatts_hvx_params_t::p_data - * contains a non-NULL pointer the attribute value will be updated with the contents - * pointed by it before sending the notification or indication. If the attribute value - * is updated, @ref ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to - * contain the number of actual bytes written, else it will be set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a notification or indication for transmission, and optionally updated the attribute - * value. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied. Only attributes added directly by the application - * are available to notify and indicate. - * @retval ::BLE_ERROR_GATTS_INVALID_ATTR_TYPE Invalid attribute type(s) supplied, only characteristic values may be notified and - * indicated. - * @retval ::NRF_ERROR_NOT_FOUND Attribute not found. - * @retval ::NRF_ERROR_FORBIDDEN The connection's current security level is lower than the one required by the write permissions - * of the CCCD associated with this characteristic. - * @retval ::NRF_ERROR_DATA_SIZE Invalid data size(s) supplied. - * @retval ::NRF_ERROR_BUSY For @ref BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a @ref BLE_GATTS_EVT_HVC - * event and retry. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_RESOURCES Too many notifications queued. - * Wait for a @ref BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_HVX, uint32_t, sd_ble_gatts_hvx(uint16_t conn_handle, ble_gatts_hvx_params_t const *p_hvx_params)); - -/**@brief Indicate the Service Changed attribute value. - * - * @details This call will send a Handle Value Indication to one or more peers connected to inform them that the Attribute - * Table layout has changed. As soon as the peer has confirmed the indication, a @ref BLE_GATTS_EVT_SC_CONFIRM event will - * be issued. - * - * @note Some of the restrictions and limitations that apply to @ref sd_ble_gatts_hvx also apply here. - * - * @events - * @event{@ref BLE_GATTS_EVT_SC_CONFIRM, Confirmation of attribute table change received from peer.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_GATTS_SC_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] start_handle Start of affected attribute handle range. - * @param[in] end_handle End of affected attribute handle range. - * - * @retval ::NRF_SUCCESS Successfully queued the Service Changed indication for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_NOT_SUPPORTED Service Changed not enabled at initialization. See @ref - * sd_ble_cfg_set and @ref ble_gatts_cfg_service_changed_t. - * @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true: - * - Invalid Connection State - * - Notifications and/or indications not enabled in the CCCD - * - An ATT_MTU exchange is ongoing - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::BLE_ERROR_INVALID_ATTR_HANDLE Invalid attribute handle(s) supplied, handles must be in the range populated by the - * application. - * @retval ::NRF_ERROR_BUSY Procedure already in progress. - * @retval ::BLE_ERROR_GATTS_SYS_ATTR_MISSING System attributes missing, use @ref sd_ble_gatts_sys_attr_set to set them to a known - * value. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_SERVICE_CHANGED, uint32_t, - sd_ble_gatts_service_changed(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle)); - -/**@brief Respond to a Read/Write authorization request. - * - * @note This call should only be used as a response to a @ref BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event issued to the application. - * - * @mscs - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_BUF_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_NOBUF_NOAUTH_MSC} - * @mmsc{@ref BLE_GATTS_READ_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_WRITE_REQ_AUTH_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_QUEUE_FULL_MSC} - * @mmsc{@ref BLE_GATTS_QUEUED_WRITE_PEER_CANCEL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_rw_authorize_reply_params Pointer to a structure with the attribute provided by the application. - * - * @note @ref ble_gatts_authorize_params_t::p_data is ignored when this function is used to respond - * to a @ref BLE_GATTS_AUTHORIZE_TYPE_READ event if @ref ble_gatts_authorize_params_t::update - * is set to 0. - * - * @retval ::NRF_SUCCESS Successfully queued a response to the peer, and in the case of a write operation, Attribute - * Table updated. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no authorization request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Authorization op invalid, - * handle supplied does not match requested handle, - * or invalid data to be written provided by the application. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_RW_AUTHORIZE_REPLY, uint32_t, - sd_ble_gatts_rw_authorize_reply(uint16_t conn_handle, - ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params)); - -/**@brief Update persistent system attribute information. - * - * @details Supply information about persistent system attributes to the stack, - * previously obtained using @ref sd_ble_gatts_sys_attr_get. - * This call is only allowed for active connections, and is usually - * made immediately after a connection is established with an known bonded device, - * often as a response to a @ref BLE_GATTS_EVT_SYS_ATTR_MISSING. - * - * p_sysattrs may point directly to the application's stored copy of the system attributes - * obtained using @ref sd_ble_gatts_sys_attr_get. - * If the pointer is NULL, the system attribute info is initialized, assuming that - * the application does not have any previously saved system attribute data for this device. - * - * @note The state of persistent system attributes is reset upon connection establishment and then remembered for its duration. - * - * @note If this call returns with an error code different from @ref NRF_SUCCESS, the storage of persistent system attributes may - * have been completed only partially. This means that the state of the attribute table is undefined, and the application should - * either provide a new set of attributes using this same call or reset the SoftDevice to return to a known state. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be modified. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be modified. - * - * @mscs - * @mmsc{@ref BLE_GATTS_HVX_SYS_ATTRS_MISSING_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_UNK_PEER_MSC} - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle. - * @param[in] p_sys_attr_data Pointer to a saved copy of system attributes supplied to the stack, or NULL. - * @param[in] len Size of data pointed by p_sys_attr_data, in octets. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully set the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_INVALID_DATA Invalid data supplied, the data should be exactly the same as retrieved with @ref - * sd_ble_gatts_sys_attr_get. - * @retval ::NRF_ERROR_NO_MEM Not enough memory to complete operation. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_SET, uint32_t, - sd_ble_gatts_sys_attr_set(uint16_t conn_handle, uint8_t const *p_sys_attr_data, uint16_t len, uint32_t flags)); - -/**@brief Retrieve persistent system attribute information from the stack. - * - * @details This call is used to retrieve information about values to be stored persistently by the application - * during the lifetime of a connection or after it has been terminated. When a new connection is established with the - * same bonded device, the system attribute information retrieved with this function should be restored using using @ref - * sd_ble_gatts_sys_attr_set. If retrieved after disconnection, the data should be read before a new connection established. The - * connection handle for the previous, now disconnected, connection will remain valid until a new one is created to allow this API - * call to refer to it. Connection handles belonging to active connections can be used as well, but care should be taken since the - * system attributes may be written to at any time by the peer during a connection's lifetime. - * - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS is used with this function, only the system attributes included in system - * services will be returned. - * @note When the @ref BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS is used with this function, only the system attributes included in user - * services will be returned. - * - * @mscs - * @mmsc{@ref BLE_GATTS_SYS_ATTRS_BONDED_PEER_MSC} - * @endmscs - * - * @param[in] conn_handle Connection handle of the recently terminated connection. - * @param[out] p_sys_attr_data Pointer to a buffer where updated information about system attributes will be filled in. The - * format of the data is described in @ref BLE_GATTS_SYS_ATTRS_FORMAT. NULL can be provided to obtain the length of the data. - * @param[in,out] p_len Size of application buffer if p_sys_attr_data is not NULL. Unconditionally updated to actual - * length of system attribute data. - * @param[in] flags Optional additional flags, see @ref BLE_GATTS_SYS_ATTR_FLAGS - * - * @retval ::NRF_SUCCESS Successfully retrieved the system attribute information. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid flags supplied. - * @retval ::NRF_ERROR_DATA_SIZE The system attribute information did not fit into the provided buffer. - * @retval ::NRF_ERROR_NOT_FOUND No system attributes found. - */ -SVCALL(SD_BLE_GATTS_SYS_ATTR_GET, uint32_t, - sd_ble_gatts_sys_attr_get(uint16_t conn_handle, uint8_t *p_sys_attr_data, uint16_t *p_len, uint32_t flags)); - -/**@brief Retrieve the first valid user attribute handle. - * - * @param[out] p_handle Pointer to an integer where the handle will be stored. - * - * @retval ::NRF_SUCCESS Successfully retrieved the handle. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - */ -SVCALL(SD_BLE_GATTS_INITIAL_USER_HANDLE_GET, uint32_t, sd_ble_gatts_initial_user_handle_get(uint16_t *p_handle)); - -/**@brief Retrieve the attribute UUID and/or metadata. - * - * @param[in] handle Attribute handle - * @param[out] p_uuid UUID of the attribute. Use NULL to omit this field. - * @param[out] p_md Metadata of the attribute. Use NULL to omit this field. - * - * @retval ::NRF_SUCCESS Successfully retrieved the attribute metadata, - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameters supplied. Returned when both @c p_uuid and @c p_md are NULL. - * @retval ::NRF_ERROR_NOT_FOUND Attribute was not found. - */ -SVCALL(SD_BLE_GATTS_ATTR_GET, uint32_t, sd_ble_gatts_attr_get(uint16_t handle, ble_uuid_t *p_uuid, ble_gatts_attr_md_t *p_md)); - -/**@brief Reply to an ATT_MTU exchange request by sending an Exchange MTU Response to the client. - * - * @details This function is only used to reply to a @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST event. - * - * @details The SoftDevice sets ATT_MTU to the minimum of: - * - The Client RX MTU value from @ref BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST, and - * - The Server RX MTU value. - * - * However, the SoftDevice never sets ATT_MTU lower than @ref BLE_GATT_ATT_MTU_DEFAULT. - * - * @mscs - * @mmsc{@ref BLE_GATTS_MTU_EXCHANGE} - * @endmscs - * - * @param[in] conn_handle The connection handle identifying the connection to perform this procedure on. - * @param[in] server_rx_mtu Server RX MTU size. - * - The minimum value is @ref BLE_GATT_ATT_MTU_DEFAULT. - * - The maximum value is @ref ble_gatt_conn_cfg_t::att_mtu in the connection configuration - * used for this connection. - * - The value must be equal to Client RX MTU size given in @ref sd_ble_gattc_exchange_mtu_request - * if an ATT_MTU exchange has already been performed in the other direction. - * - * @retval ::NRF_SUCCESS Successfully sent response to the client. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid Connection State or no ATT_MTU exchange request pending. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid Server RX MTU size supplied. - * @retval ::NRF_ERROR_TIMEOUT There has been a GATT procedure timeout. No new GATT procedure can be performed without - * reestablishing the connection. - */ -SVCALL(SD_BLE_GATTS_EXCHANGE_MTU_REPLY, uint32_t, sd_ble_gatts_exchange_mtu_reply(uint16_t conn_handle, uint16_t server_rx_mtu)); -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_GATTS_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_hci.h b/variants/wio-sdk-wm1110/softdevice/ble_hci.h deleted file mode 100644 index 27f85d52e..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_hci.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ -*/ - -#ifndef BLE_HCI_H__ -#define BLE_HCI_H__ -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup BLE_HCI_STATUS_CODES Bluetooth status codes - * @{ */ - -#define BLE_HCI_STATUS_CODE_SUCCESS 0x00 /**< Success. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND 0x01 /**< Unknown BLE Command. */ -#define BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 /**< Unknown Connection Identifier. */ -/*0x03 Hardware Failure -0x04 Page Timeout -*/ -#define BLE_HCI_AUTHENTICATION_FAILURE 0x05 /**< Authentication Failure. */ -#define BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING 0x06 /**< Pin or Key missing. */ -#define BLE_HCI_MEMORY_CAPACITY_EXCEEDED 0x07 /**< Memory Capacity Exceeded. */ -#define BLE_HCI_CONNECTION_TIMEOUT 0x08 /**< Connection Timeout. */ -/*0x09 Connection Limit Exceeded -0x0A Synchronous Connection Limit To A Device Exceeded -0x0B ACL Connection Already Exists*/ -#define BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED 0x0C /**< Command Disallowed. */ -/*0x0D Connection Rejected due to Limited Resources -0x0E Connection Rejected Due To Security Reasons -0x0F Connection Rejected due to Unacceptable BD_ADDR -0x10 Connection Accept Timeout Exceeded -0x11 Unsupported Feature or Parameter Value*/ -#define BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS 0x12 /**< Invalid BLE Command Parameters. */ -#define BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 /**< Remote User Terminated Connection. */ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES \ - 0x14 /**< Remote Device Terminated Connection due to low \ - resources.*/ -#define BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 /**< Remote Device Terminated Connection due to power off. */ -#define BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 /**< Local Host Terminated Connection. */ -/* -0x17 Repeated Attempts -0x18 Pairing Not Allowed -0x19 Unknown LMP PDU -*/ -#define BLE_HCI_UNSUPPORTED_REMOTE_FEATURE 0x1A /**< Unsupported Remote Feature. */ -/* -0x1B SCO Offset Rejected -0x1C SCO Interval Rejected -0x1D SCO Air Mode Rejected*/ -#define BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS 0x1E /**< Invalid LMP Parameters. */ -#define BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR 0x1F /**< Unspecified Error. */ -/*0x20 Unsupported LMP Parameter Value -0x21 Role Change Not Allowed -*/ -#define BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT 0x22 /**< LMP Response Timeout. */ -#define BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 /**< LMP Error Transaction Collision/LL Procedure Collision. */ -#define BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED 0x24 /**< LMP PDU Not Allowed. */ -/*0x25 Encryption Mode Not Acceptable -0x26 Link Key Can Not be Changed -0x27 Requested QoS Not Supported -*/ -#define BLE_HCI_INSTANT_PASSED 0x28 /**< Instant Passed. */ -#define BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED 0x29 /**< Pairing with Unit Key Unsupported. */ -#define BLE_HCI_DIFFERENT_TRANSACTION_COLLISION 0x2A /**< Different Transaction Collision. */ -/* -0x2B Reserved -0x2C QoS Unacceptable Parameter -0x2D QoS Rejected -0x2E Channel Classification Not Supported -0x2F Insufficient Security -*/ -#define BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 /**< Parameter Out Of Mandatory Range. */ -/* -0x31 Reserved -0x32 Role Switch Pending -0x33 Reserved -0x34 Reserved Slot Violation -0x35 Role Switch Failed -0x36 Extended Inquiry Response Too Large -0x37 Secure Simple Pairing Not Supported By Host. -0x38 Host Busy - Pairing -0x39 Connection Rejected due to No Suitable Channel Found*/ -#define BLE_HCI_CONTROLLER_BUSY 0x3A /**< Controller Busy. */ -#define BLE_HCI_CONN_INTERVAL_UNACCEPTABLE 0x3B /**< Connection Interval Unacceptable. */ -#define BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT 0x3C /**< Directed Advertisement Timeout. */ -#define BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE 0x3D /**< Connection Terminated due to MIC Failure. */ -#define BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED 0x3E /**< Connection Failed to be Established. */ - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_HCI_H__ - -/** @} */ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h b/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h deleted file mode 100644 index 5f4bd277d..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_l2cap.h +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_L2CAP Logical Link Control and Adaptation Protocol (L2CAP) - @{ - @brief Definitions and prototypes for the L2CAP interface. - */ - -#ifndef BLE_L2CAP_H__ -#define BLE_L2CAP_H__ - -#include "ble_err.h" -#include "ble_ranges.h" -#include "ble_types.h" -#include "nrf_error.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup BLE_L2CAP_TERMINOLOGY Terminology - * @{ - * @details - * - * L2CAP SDU - * - A data unit that the application can send/receive to/from a peer. - * - * L2CAP PDU - * - A data unit that is exchanged between local and remote L2CAP entities. - * It consists of L2CAP protocol control information and payload fields. - * The payload field can contain an L2CAP SDU or a part of an L2CAP SDU. - * - * L2CAP MTU - * - The maximum length of an L2CAP SDU. - * - * L2CAP MPS - * - The maximum length of an L2CAP PDU payload field. - * - * Credits - * - A value indicating the number of L2CAP PDUs that the receiver of the credit can send to the peer. - * @} */ - -/**@addtogroup BLE_L2CAP_ENUMERATIONS Enumerations - * @{ */ - -/**@brief L2CAP API SVC numbers. */ -enum BLE_L2CAP_SVCS { - SD_BLE_L2CAP_CH_SETUP = BLE_L2CAP_SVC_BASE + 0, /**< Set up an L2CAP channel. */ - SD_BLE_L2CAP_CH_RELEASE = BLE_L2CAP_SVC_BASE + 1, /**< Release an L2CAP channel. */ - SD_BLE_L2CAP_CH_RX = BLE_L2CAP_SVC_BASE + 2, /**< Receive an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_TX = BLE_L2CAP_SVC_BASE + 3, /**< Transmit an SDU on an L2CAP channel. */ - SD_BLE_L2CAP_CH_FLOW_CONTROL = BLE_L2CAP_SVC_BASE + 4, /**< Advanced SDU reception flow control. */ -}; - -/**@brief L2CAP Event IDs. */ -enum BLE_L2CAP_EVTS { - BLE_L2CAP_EVT_CH_SETUP_REQUEST = BLE_L2CAP_EVT_BASE + 0, /**< L2CAP Channel Setup Request event. - \n Reply with @ref sd_ble_l2cap_ch_setup. - \n See @ref ble_l2cap_evt_ch_setup_request_t. */ - BLE_L2CAP_EVT_CH_SETUP_REFUSED = BLE_L2CAP_EVT_BASE + 1, /**< L2CAP Channel Setup Refused event. - \n See @ref ble_l2cap_evt_ch_setup_refused_t. */ - BLE_L2CAP_EVT_CH_SETUP = BLE_L2CAP_EVT_BASE + 2, /**< L2CAP Channel Setup Completed event. - \n See @ref ble_l2cap_evt_ch_setup_t. */ - BLE_L2CAP_EVT_CH_RELEASED = BLE_L2CAP_EVT_BASE + 3, /**< L2CAP Channel Released event. - \n No additional event structure applies. */ - BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED = BLE_L2CAP_EVT_BASE + 4, /**< L2CAP Channel SDU data buffer released event. - \n See @ref ble_l2cap_evt_ch_sdu_buf_released_t. */ - BLE_L2CAP_EVT_CH_CREDIT = BLE_L2CAP_EVT_BASE + 5, /**< L2CAP Channel Credit received. - \n See @ref ble_l2cap_evt_ch_credit_t. */ - BLE_L2CAP_EVT_CH_RX = BLE_L2CAP_EVT_BASE + 6, /**< L2CAP Channel SDU received. - \n See @ref ble_l2cap_evt_ch_rx_t. */ - BLE_L2CAP_EVT_CH_TX = BLE_L2CAP_EVT_BASE + 7, /**< L2CAP Channel SDU transmitted. - \n See @ref ble_l2cap_evt_ch_tx_t. */ -}; - -/** @} */ - -/**@addtogroup BLE_L2CAP_DEFINES Defines - * @{ */ - -/**@brief Maximum number of L2CAP channels per connection. */ -#define BLE_L2CAP_CH_COUNT_MAX (64) - -/**@brief Minimum L2CAP MTU, in bytes. */ -#define BLE_L2CAP_MTU_MIN (23) - -/**@brief Minimum L2CAP MPS, in bytes. */ -#define BLE_L2CAP_MPS_MIN (23) - -/**@brief Invalid CID. */ -#define BLE_L2CAP_CID_INVALID (0x0000) - -/**@brief Default number of credits for @ref sd_ble_l2cap_ch_flow_control. */ -#define BLE_L2CAP_CREDITS_DEFAULT (1) - -/**@defgroup BLE_L2CAP_CH_SETUP_REFUSED_SRCS L2CAP channel setup refused sources - * @{ */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_LOCAL (0x01) /**< Local. */ -#define BLE_L2CAP_CH_SETUP_REFUSED_SRC_REMOTE (0x02) /**< Remote. */ - /** @} */ - -/** @defgroup BLE_L2CAP_CH_STATUS_CODES L2CAP channel status codes - * @{ */ -#define BLE_L2CAP_CH_STATUS_CODE_SUCCESS (0x0000) /**< Success. */ -#define BLE_L2CAP_CH_STATUS_CODE_LE_PSM_NOT_SUPPORTED (0x0002) /**< LE_PSM not supported. */ -#define BLE_L2CAP_CH_STATUS_CODE_NO_RESOURCES (0x0004) /**< No resources available. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHENTICATION (0x0005) /**< Insufficient authentication. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_AUTHORIZATION (0x0006) /**< Insufficient authorization. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC_KEY_SIZE (0x0007) /**< Insufficient encryption key size. */ -#define BLE_L2CAP_CH_STATUS_CODE_INSUFF_ENC (0x0008) /**< Insufficient encryption. */ -#define BLE_L2CAP_CH_STATUS_CODE_INVALID_SCID (0x0009) /**< Invalid Source CID. */ -#define BLE_L2CAP_CH_STATUS_CODE_SCID_ALLOCATED (0x000A) /**< Source CID already allocated. */ -#define BLE_L2CAP_CH_STATUS_CODE_UNACCEPTABLE_PARAMS (0x000B) /**< Unacceptable parameters. */ -#define BLE_L2CAP_CH_STATUS_CODE_NOT_UNDERSTOOD \ - (0x8000) /**< Command Reject received instead of LE Credit Based Connection Response. */ -#define BLE_L2CAP_CH_STATUS_CODE_TIMEOUT (0xC000) /**< Operation timed out. */ -/** @} */ - -/** @} */ - -/**@addtogroup BLE_L2CAP_STRUCTURES Structures - * @{ */ - -/** - * @brief BLE L2CAP connection configuration parameters, set with @ref sd_ble_cfg_set. - * - * @note These parameters are set per connection, so all L2CAP channels created on this connection - * will have the same parameters. - * - * @retval ::NRF_ERROR_INVALID_PARAM One or more of the following is true: - * - rx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - tx_mps is smaller than @ref BLE_L2CAP_MPS_MIN. - * - ch_count is greater than @ref BLE_L2CAP_CH_COUNT_MAX. - * @retval ::NRF_ERROR_NO_MEM rx_mps or tx_mps is set too high. - */ -typedef struct { - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to receive on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall - be able to transmit on L2CAP channels on connections with this - configuration. The minimum value is @ref BLE_L2CAP_MPS_MIN. */ - uint8_t rx_queue_size; /**< Number of SDU data buffers that can be queued for reception per - L2CAP channel. The minimum value is one. */ - uint8_t tx_queue_size; /**< Number of SDU data buffers that can be queued for transmission - per L2CAP channel. The minimum value is one. */ - uint8_t ch_count; /**< Number of L2CAP channels the application can create per connection - with this configuration. The default value is zero, the maximum - value is @ref BLE_L2CAP_CH_COUNT_MAX. - @note if this parameter is set to zero, all other parameters in - @ref ble_l2cap_conn_cfg_t are ignored. */ -} ble_l2cap_conn_cfg_t; - -/**@brief L2CAP channel RX parameters. */ -typedef struct { - uint16_t rx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP shall be able to - receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MTU_MIN. */ - uint16_t rx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP shall be - able to receive on this L2CAP channel. - - Must be equal to or greater than @ref BLE_L2CAP_MPS_MIN. - - Must be equal to or less than @ref ble_l2cap_conn_cfg_t::rx_mps. */ - ble_data_t sdu_buf; /**< SDU data buffer for reception. - - If @ref ble_data_t::p_data is non-NULL, initial credits are - issued to the peer. - - If @ref ble_data_t::p_data is NULL, no initial credits are - issued to the peer. */ -} ble_l2cap_ch_rx_params_t; - -/**@brief L2CAP channel setup parameters. */ -typedef struct { - ble_l2cap_ch_rx_params_t rx_params; /**< L2CAP channel RX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. Used when requesting - setup of an L2CAP channel, ignored otherwise. */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES. - Used when replying to a setup request of an L2CAP - channel, ignored otherwise. */ -} ble_l2cap_ch_setup_params_t; - -/**@brief L2CAP channel TX parameters. */ -typedef struct { - uint16_t tx_mtu; /**< The maximum L2CAP SDU size, in bytes, that L2CAP is able to - transmit on this L2CAP channel. */ - uint16_t peer_mps; /**< The maximum L2CAP PDU payload size, in bytes, that the peer is - able to receive on this L2CAP channel. */ - uint16_t tx_mps; /**< The maximum L2CAP PDU payload size, in bytes, that L2CAP is able - to transmit on this L2CAP channel. This is effective tx_mps, - selected by the SoftDevice as - MIN( @ref ble_l2cap_ch_tx_params_t::peer_mps, @ref ble_l2cap_conn_cfg_t::tx_mps ) */ - uint16_t credits; /**< Initial credits given by the peer. */ -} ble_l2cap_ch_tx_params_t; - -/**@brief L2CAP Channel Setup Request event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ - uint16_t le_psm; /**< LE Protocol/Service Multiplexer. */ -} ble_l2cap_evt_ch_setup_request_t; - -/**@brief L2CAP Channel Setup Refused event. */ -typedef struct { - uint8_t source; /**< Source, see @ref BLE_L2CAP_CH_SETUP_REFUSED_SRCS */ - uint16_t status; /**< Status code, see @ref BLE_L2CAP_CH_STATUS_CODES */ -} ble_l2cap_evt_ch_setup_refused_t; - -/**@brief L2CAP Channel Setup Completed event. */ -typedef struct { - ble_l2cap_ch_tx_params_t tx_params; /**< L2CAP channel TX parameters. */ -} ble_l2cap_evt_ch_setup_t; - -/**@brief L2CAP Channel SDU Data Buffer Released event. */ -typedef struct { - ble_data_t sdu_buf; /**< Returned reception or transmission SDU data buffer. The SoftDevice - returns SDU data buffers supplied by the application, which have - not yet been returned previously via a @ref BLE_L2CAP_EVT_CH_RX or - @ref BLE_L2CAP_EVT_CH_TX event. */ -} ble_l2cap_evt_ch_sdu_buf_released_t; - -/**@brief L2CAP Channel Credit received event. */ -typedef struct { - uint16_t credits; /**< Additional credits given by the peer. */ -} ble_l2cap_evt_ch_credit_t; - -/**@brief L2CAP Channel received SDU event. */ -typedef struct { - uint16_t sdu_len; /**< Total SDU length, in bytes. */ - ble_data_t sdu_buf; /**< SDU data buffer. - @note If there is not enough space in the buffer - (sdu_buf.len < sdu_len) then the rest of the SDU will be - silently discarded by the SoftDevice. */ -} ble_l2cap_evt_ch_rx_t; - -/**@brief L2CAP Channel transmitted SDU event. */ -typedef struct { - ble_data_t sdu_buf; /**< SDU data buffer. */ -} ble_l2cap_evt_ch_tx_t; - -/**@brief L2CAP event structure. */ -typedef struct { - uint16_t conn_handle; /**< Connection Handle on which the event occured. */ - uint16_t local_cid; /**< Local Channel ID of the L2CAP channel, or - @ref BLE_L2CAP_CID_INVALID if not present. */ - union { - ble_l2cap_evt_ch_setup_request_t ch_setup_request; /**< L2CAP Channel Setup Request Event Parameters. */ - ble_l2cap_evt_ch_setup_refused_t ch_setup_refused; /**< L2CAP Channel Setup Refused Event Parameters. */ - ble_l2cap_evt_ch_setup_t ch_setup; /**< L2CAP Channel Setup Completed Event Parameters. */ - ble_l2cap_evt_ch_sdu_buf_released_t ch_sdu_buf_released; /**< L2CAP Channel SDU Data Buffer Released Event Parameters. */ - ble_l2cap_evt_ch_credit_t credit; /**< L2CAP Channel Credit Received Event Parameters. */ - ble_l2cap_evt_ch_rx_t rx; /**< L2CAP Channel SDU Received Event Parameters. */ - ble_l2cap_evt_ch_tx_t tx; /**< L2CAP Channel SDU Transmitted Event Parameters. */ - } params; /**< Event Parameters. */ -} ble_l2cap_evt_t; - -/** @} */ - -/**@addtogroup BLE_L2CAP_FUNCTIONS Functions - * @{ */ - -/**@brief Set up an L2CAP channel. - * - * @details This function is used to: - * - Request setup of an L2CAP channel: sends an LE Credit Based Connection Request packet to a peer. - * - Reply to a setup request of an L2CAP channel (if called in response to a - * @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST event): sends an LE Credit Based Connection - * Response packet to a peer. - * - * @note A call to this function will require the application to keep the SDU data buffer alive - * until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX or - * @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_SETUP, Setup successful.} - * @event{@ref BLE_L2CAP_EVT_CH_SETUP_REFUSED, Setup failed.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_SETUP_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in,out] p_local_cid Pointer to a uint16_t containing Local Channel ID of the L2CAP channel: - * - As input: @ref BLE_L2CAP_CID_INVALID when requesting setup of an L2CAP - * channel or local_cid provided in the @ref BLE_L2CAP_EVT_CH_SETUP_REQUEST - * event when replying to a setup request of an L2CAP channel. - * - As output: local_cid for this channel. - * @param[in] p_params L2CAP channel parameters. - * - * @retval ::NRF_SUCCESS Successfully queued request or response for transmission. - * @retval ::NRF_ERROR_BUSY The stack is busy, process pending events and retry. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_PARAM Invalid parameter(s) supplied. - * @retval ::NRF_ERROR_INVALID_LENGTH Supplied higher rx_mps than has been configured on this link. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (L2CAP channel already set up). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES The limit has been reached for available L2CAP channels, - * see @ref ble_l2cap_conn_cfg_t::ch_count. - */ -SVCALL(SD_BLE_L2CAP_CH_SETUP, uint32_t, - sd_ble_l2cap_ch_setup(uint16_t conn_handle, uint16_t *p_local_cid, ble_l2cap_ch_setup_params_t const *p_params)); - -/**@brief Release an L2CAP channel. - * - * @details This sends a Disconnection Request packet to a peer. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RELEASED, Release complete.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RELEASE_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * - * @retval ::NRF_SUCCESS Successfully queued request for transmission. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_RELEASE, uint32_t, sd_ble_l2cap_ch_release(uint16_t conn_handle, uint16_t local_cid)); - -/**@brief Receive an SDU on an L2CAP channel. - * - * @details This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_RX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::rx_queue_size SDU data buffers - * for reception per L2CAP channel. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_RX, The SDU is received.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_RX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Buffer accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_RESOURCES Too many SDU data buffers supplied. Wait for a - * @ref BLE_L2CAP_EVT_CH_RX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_RX, uint32_t, sd_ble_l2cap_ch_rx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Transmit an SDU on an L2CAP channel. - * - * @note A call to this function will require the application to keep the memory pointed by - * @ref ble_data_t::p_data alive until the SDU data buffer is returned in @ref BLE_L2CAP_EVT_CH_TX - * or @ref BLE_L2CAP_EVT_CH_SDU_BUF_RELEASED event. - * - * @note The SoftDevice can queue up to @ref ble_l2cap_conn_cfg_t::tx_queue_size SDUs for - * transmission per L2CAP channel. - * - * @note The application can keep track of the available credits for transmission by following - * the procedure below: - * - Store initial credits given by the peer in a variable. - * (Initial credits are provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Decrement the variable, which stores the currently available credits, by - * ceiling((@ref ble_data_t::len + 2) / tx_mps) when a call to this function returns - * @ref NRF_SUCCESS. (tx_mps is provided in a @ref BLE_L2CAP_EVT_CH_SETUP event.) - * - Increment the variable, which stores the currently available credits, by additional - * credits given by the peer in a @ref BLE_L2CAP_EVT_CH_CREDIT event. - * - * @events - * @event{@ref BLE_L2CAP_EVT_CH_TX, The SDU is transmitted.} - * @endevents - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_TX_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel. - * @param[in] p_sdu_buf Pointer to the SDU data buffer. - * - * @retval ::NRF_SUCCESS Successfully queued L2CAP SDU for transmission. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for the L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - * @retval ::NRF_ERROR_DATA_SIZE Invalid SDU length supplied, must not be more than - * @ref ble_l2cap_ch_tx_params_t::tx_mtu provided in - * @ref BLE_L2CAP_EVT_CH_SETUP event. - * @retval ::NRF_ERROR_RESOURCES Too many SDUs queued for transmission. Wait for a - * @ref BLE_L2CAP_EVT_CH_TX event and retry. - */ -SVCALL(SD_BLE_L2CAP_CH_TX, uint32_t, sd_ble_l2cap_ch_tx(uint16_t conn_handle, uint16_t local_cid, ble_data_t const *p_sdu_buf)); - -/**@brief Advanced SDU reception flow control. - * - * @details Adjust the way the SoftDevice issues credits to the peer. - * This may issue additional credits to the peer using an LE Flow Control Credit packet. - * - * @mscs - * @mmsc{@ref BLE_L2CAP_CH_FLOW_CONTROL_MSC} - * @endmscs - * - * @param[in] conn_handle Connection Handle. - * @param[in] local_cid Local Channel ID of the L2CAP channel or @ref BLE_L2CAP_CID_INVALID to set - * the value that will be used for newly created channels. - * @param[in] credits Number of credits that the SoftDevice will make sure the peer has every - * time it starts using a new reception buffer. - * - @ref BLE_L2CAP_CREDITS_DEFAULT is the default value the SoftDevice will - * use if this function is not called. - * - If set to zero, the SoftDevice will stop issuing credits for new reception - * buffers the application provides or has provided. SDU reception that is - * currently ongoing will be allowed to complete. - * @param[out] p_credits NULL or pointer to a uint16_t. If a valid pointer is provided, it will be - * written by the SoftDevice with the number of credits that is or will be - * available to the peer. If the value written by the SoftDevice is 0 when - * credits parameter was set to 0, the peer will not be able to send more - * data until more credits are provided by calling this function again with - * credits > 0. This parameter is ignored when local_cid is set to - * @ref BLE_L2CAP_CID_INVALID. - * - * @note Application should take care when setting number of credits higher than default value. In - * this case the application must make sure that the SoftDevice always has reception buffers - * available (see @ref sd_ble_l2cap_ch_rx) for that channel. If the SoftDevice does not have - * such buffers available, packets may be NACKed on the Link Layer and all Bluetooth traffic - * on the connection handle may be stalled until the SoftDevice again has an available - * reception buffer. This applies even if the application has used this call to set the - * credits back to default, or zero. - * - * @retval ::NRF_SUCCESS Flow control parameters accepted. - * @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied. - * @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid Connection Handle. - * @retval ::NRF_ERROR_INVALID_STATE Invalid State to perform operation (Setup or release is - * in progress for an L2CAP channel). - * @retval ::NRF_ERROR_NOT_FOUND CID not found. - */ -SVCALL(SD_BLE_L2CAP_CH_FLOW_CONTROL, uint32_t, - sd_ble_l2cap_ch_flow_control(uint16_t conn_handle, uint16_t local_cid, uint16_t credits, uint16_t *p_credits)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // BLE_L2CAP_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_ranges.h b/variants/wio-sdk-wm1110/softdevice/ble_ranges.h deleted file mode 100644 index 2768e4996..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_ranges.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_ranges Module specific SVC, event and option number subranges - @{ - - @brief Definition of SVC, event and option number subranges for each API module. - - @note - SVCs, event and option numbers are split into subranges for each API module. - Each module receives its entire allocated range of SVC calls, whether implemented or not, - but return BLE_ERROR_NOT_SUPPORTED for unimplemented or undefined calls in its range. - - Note that the symbols BLE__SVC_LAST is the end of the allocated SVC range, - rather than the last SVC function call actually defined and implemented. - - Specific SVC, event and option values are defined in each module's ble_.h file, - which defines names of each individual SVC code based on the range start value. -*/ - -#ifndef BLE_RANGES_H__ -#define BLE_RANGES_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLE_SVC_BASE 0x60 /**< Common BLE SVC base. */ -#define BLE_SVC_LAST 0x6B /**< Common BLE SVC last. */ - -#define BLE_GAP_SVC_BASE 0x6C /**< GAP BLE SVC base. */ -#define BLE_GAP_SVC_LAST 0x9A /**< GAP BLE SVC last. */ - -#define BLE_GATTC_SVC_BASE 0x9B /**< GATTC BLE SVC base. */ -#define BLE_GATTC_SVC_LAST 0xA7 /**< GATTC BLE SVC last. */ - -#define BLE_GATTS_SVC_BASE 0xA8 /**< GATTS BLE SVC base. */ -#define BLE_GATTS_SVC_LAST 0xB7 /**< GATTS BLE SVC last. */ - -#define BLE_L2CAP_SVC_BASE 0xB8 /**< L2CAP BLE SVC base. */ -#define BLE_L2CAP_SVC_LAST 0xBF /**< L2CAP BLE SVC last. */ - -#define BLE_EVT_INVALID 0x00 /**< Invalid BLE Event. */ - -#define BLE_EVT_BASE 0x01 /**< Common BLE Event base. */ -#define BLE_EVT_LAST 0x0F /**< Common BLE Event last. */ - -#define BLE_GAP_EVT_BASE 0x10 /**< GAP BLE Event base. */ -#define BLE_GAP_EVT_LAST 0x2F /**< GAP BLE Event last. */ - -#define BLE_GATTC_EVT_BASE 0x30 /**< GATTC BLE Event base. */ -#define BLE_GATTC_EVT_LAST 0x4F /**< GATTC BLE Event last. */ - -#define BLE_GATTS_EVT_BASE 0x50 /**< GATTS BLE Event base. */ -#define BLE_GATTS_EVT_LAST 0x6F /**< GATTS BLE Event last. */ - -#define BLE_L2CAP_EVT_BASE 0x70 /**< L2CAP BLE Event base. */ -#define BLE_L2CAP_EVT_LAST 0x8F /**< L2CAP BLE Event last. */ - -#define BLE_OPT_INVALID 0x00 /**< Invalid BLE Option. */ - -#define BLE_OPT_BASE 0x01 /**< Common BLE Option base. */ -#define BLE_OPT_LAST 0x1F /**< Common BLE Option last. */ - -#define BLE_GAP_OPT_BASE 0x20 /**< GAP BLE Option base. */ -#define BLE_GAP_OPT_LAST 0x3F /**< GAP BLE Option last. */ - -#define BLE_GATT_OPT_BASE 0x40 /**< GATT BLE Option base. */ -#define BLE_GATT_OPT_LAST 0x5F /**< GATT BLE Option last. */ - -#define BLE_GATTC_OPT_BASE 0x60 /**< GATTC BLE Option base. */ -#define BLE_GATTC_OPT_LAST 0x7F /**< GATTC BLE Option last. */ - -#define BLE_GATTS_OPT_BASE 0x80 /**< GATTS BLE Option base. */ -#define BLE_GATTS_OPT_LAST 0x9F /**< GATTS BLE Option last. */ - -#define BLE_L2CAP_OPT_BASE 0xA0 /**< L2CAP BLE Option base. */ -#define BLE_L2CAP_OPT_LAST 0xBF /**< L2CAP BLE Option last. */ - -#define BLE_CFG_INVALID 0x00 /**< Invalid BLE configuration. */ - -#define BLE_CFG_BASE 0x01 /**< Common BLE configuration base. */ -#define BLE_CFG_LAST 0x1F /**< Common BLE configuration last. */ - -#define BLE_CONN_CFG_BASE 0x20 /**< BLE connection configuration base. */ -#define BLE_CONN_CFG_LAST 0x3F /**< BLE connection configuration last. */ - -#define BLE_GAP_CFG_BASE 0x40 /**< GAP BLE configuration base. */ -#define BLE_GAP_CFG_LAST 0x5F /**< GAP BLE configuration last. */ - -#define BLE_GATT_CFG_BASE 0x60 /**< GATT BLE configuration base. */ -#define BLE_GATT_CFG_LAST 0x7F /**< GATT BLE configuration last. */ - -#define BLE_GATTC_CFG_BASE 0x80 /**< GATTC BLE configuration base. */ -#define BLE_GATTC_CFG_LAST 0x9F /**< GATTC BLE configuration last. */ - -#define BLE_GATTS_CFG_BASE 0xA0 /**< GATTS BLE configuration base. */ -#define BLE_GATTS_CFG_LAST 0xBF /**< GATTS BLE configuration last. */ - -#define BLE_L2CAP_CFG_BASE 0xC0 /**< L2CAP BLE configuration base. */ -#define BLE_L2CAP_CFG_LAST 0xDF /**< L2CAP BLE configuration last. */ - -#ifdef __cplusplus -} -#endif -#endif /* BLE_RANGES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/ble_types.h b/variants/wio-sdk-wm1110/softdevice/ble_types.h deleted file mode 100644 index db3656cfd..000000000 --- a/variants/wio-sdk-wm1110/softdevice/ble_types.h +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup BLE_COMMON - @{ - @defgroup ble_types Common types and macro definitions - @{ - - @brief Common types and macro definitions for the BLE SoftDevice. - */ - -#ifndef BLE_TYPES_H__ -#define BLE_TYPES_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup BLE_TYPES_DEFINES Defines - * @{ */ - -/** @defgroup BLE_CONN_HANDLES BLE Connection Handles - * @{ */ -#define BLE_CONN_HANDLE_INVALID 0xFFFF /**< Invalid Connection Handle. */ -#define BLE_CONN_HANDLE_ALL 0xFFFE /**< Applies to all Connection Handles. */ -/** @} */ - -/** @defgroup BLE_UUID_VALUES Assigned Values for BLE UUIDs - * @{ */ -/* Generic UUIDs, applicable to all services */ -#define BLE_UUID_UNKNOWN 0x0000 /**< Reserved UUID. */ -#define BLE_UUID_SERVICE_PRIMARY 0x2800 /**< Primary Service. */ -#define BLE_UUID_SERVICE_SECONDARY 0x2801 /**< Secondary Service. */ -#define BLE_UUID_SERVICE_INCLUDE 0x2802 /**< Include. */ -#define BLE_UUID_CHARACTERISTIC 0x2803 /**< Characteristic. */ -#define BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP 0x2900 /**< Characteristic Extended Properties Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_USER_DESC 0x2901 /**< Characteristic User Description Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG 0x2902 /**< Client Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG 0x2903 /**< Server Characteristic Configuration Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_PRESENTATION_FORMAT 0x2904 /**< Characteristic Presentation Format Descriptor. */ -#define BLE_UUID_DESCRIPTOR_CHAR_AGGREGATE_FORMAT 0x2905 /**< Characteristic Aggregate Format Descriptor. */ -/* GATT specific UUIDs */ -#define BLE_UUID_GATT 0x1801 /**< Generic Attribute Profile. */ -#define BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED 0x2A05 /**< Service Changed Characteristic. */ -/* GAP specific UUIDs */ -#define BLE_UUID_GAP 0x1800 /**< Generic Access Profile. */ -#define BLE_UUID_GAP_CHARACTERISTIC_DEVICE_NAME 0x2A00 /**< Device Name Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_APPEARANCE 0x2A01 /**< Appearance Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR 0x2A03 /**< Reconnection Address Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_PPCP 0x2A04 /**< Peripheral Preferred Connection Parameters Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_CAR 0x2AA6 /**< Central Address Resolution Characteristic. */ -#define BLE_UUID_GAP_CHARACTERISTIC_RPA_ONLY 0x2AC9 /**< Resolvable Private Address Only Characteristic. */ -/** @} */ - -/** @defgroup BLE_UUID_TYPES Types of UUID - * @{ */ -#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */ -#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */ -#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */ -/** @} */ - -/** @defgroup BLE_APPEARANCES Bluetooth Appearance values - * @note Retrieved from - * http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml - * @{ */ -#define BLE_APPEARANCE_UNKNOWN 0 /**< Unknown. */ -#define BLE_APPEARANCE_GENERIC_PHONE 64 /**< Generic Phone. */ -#define BLE_APPEARANCE_GENERIC_COMPUTER 128 /**< Generic Computer. */ -#define BLE_APPEARANCE_GENERIC_WATCH 192 /**< Generic Watch. */ -#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193 /**< Watch: Sports Watch. */ -#define BLE_APPEARANCE_GENERIC_CLOCK 256 /**< Generic Clock. */ -#define BLE_APPEARANCE_GENERIC_DISPLAY 320 /**< Generic Display. */ -#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384 /**< Generic Remote Control. */ -#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448 /**< Generic Eye-glasses. */ -#define BLE_APPEARANCE_GENERIC_TAG 512 /**< Generic Tag. */ -#define BLE_APPEARANCE_GENERIC_KEYRING 576 /**< Generic Keyring. */ -#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640 /**< Generic Media Player. */ -#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704 /**< Generic Barcode Scanner. */ -#define BLE_APPEARANCE_GENERIC_THERMOMETER 768 /**< Generic Thermometer. */ -#define BLE_APPEARANCE_THERMOMETER_EAR 769 /**< Thermometer: Ear. */ -#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832 /**< Generic Heart rate Sensor. */ -#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 /**< Heart Rate Sensor: Heart Rate Belt. */ -#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896 /**< Generic Blood Pressure. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897 /**< Blood Pressure: Arm. */ -#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898 /**< Blood Pressure: Wrist. */ -#define BLE_APPEARANCE_GENERIC_HID 960 /**< Human Interface Device (HID). */ -#define BLE_APPEARANCE_HID_KEYBOARD 961 /**< Keyboard (HID Subtype). */ -#define BLE_APPEARANCE_HID_MOUSE 962 /**< Mouse (HID Subtype). */ -#define BLE_APPEARANCE_HID_JOYSTICK 963 /**< Joystick (HID Subtype). */ -#define BLE_APPEARANCE_HID_GAMEPAD 964 /**< Gamepad (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965 /**< Digitizer Tablet (HID Subtype). */ -#define BLE_APPEARANCE_HID_CARD_READER 966 /**< Card Reader (HID Subtype). */ -#define BLE_APPEARANCE_HID_DIGITAL_PEN 967 /**< Digital Pen (HID Subtype). */ -#define BLE_APPEARANCE_HID_BARCODE 968 /**< Barcode Scanner (HID Subtype). */ -#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024 /**< Generic Glucose Meter. */ -#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088 /**< Generic Running Walking Sensor. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 /**< Running Walking Sensor: In-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 /**< Running Walking Sensor: On-Shoe. */ -#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091 /**< Running Walking Sensor: On-Hip. */ -#define BLE_APPEARANCE_GENERIC_CYCLING 1152 /**< Generic Cycling. */ -#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153 /**< Cycling: Cycling Computer. */ -#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154 /**< Cycling: Speed Sensor. */ -#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155 /**< Cycling: Cadence Sensor. */ -#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156 /**< Cycling: Power Sensor. */ -#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157 /**< Cycling: Speed and Cadence Sensor. */ -#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 3136 /**< Generic Pulse Oximeter. */ -#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 3137 /**< Fingertip (Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST_WORN 3138 /**< Wrist Worn(Pulse Oximeter subtype). */ -#define BLE_APPEARANCE_GENERIC_WEIGHT_SCALE 3200 /**< Generic Weight Scale. */ -#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS_ACT 5184 /**< Generic Outdoor Sports Activity. */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 /**< Location Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP \ - 5186 /**< Location and Navigation Display Device (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 /**< Location Pod (Outdoor Sports Activity subtype). */ -#define BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD \ - 5188 /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ -/** @} */ - -/** @brief Set .type and .uuid fields of ble_uuid_struct to specified UUID value. */ -#define BLE_UUID_BLE_ASSIGN(instance, value) \ - do { \ - instance.type = BLE_UUID_TYPE_BLE; \ - instance.uuid = value; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t pointer. Both pointers must be valid/non-null. */ -#define BLE_UUID_COPY_PTR(dst, src) \ - do { \ - (dst)->type = (src)->type; \ - (dst)->uuid = (src)->uuid; \ - } while (0) - -/** @brief Copy type and uuid members from src to dst ble_uuid_t struct. */ -#define BLE_UUID_COPY_INST(dst, src) \ - do { \ - (dst).type = (src).type; \ - (dst).uuid = (src).uuid; \ - } while (0) - -/** @brief Compare for equality both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_EQ(p_uuid1, p_uuid2) (((p_uuid1)->type == (p_uuid2)->type) && ((p_uuid1)->uuid == (p_uuid2)->uuid)) - -/** @brief Compare for difference both type and uuid members of two (valid, non-null) ble_uuid_t pointers. */ -#define BLE_UUID_NEQ(p_uuid1, p_uuid2) (((p_uuid1)->type != (p_uuid2)->type) || ((p_uuid1)->uuid != (p_uuid2)->uuid)) - -/** @} */ - -/** @addtogroup BLE_TYPES_STRUCTURES Structures - * @{ */ - -/** @brief 128 bit UUID values. */ -typedef struct { - uint8_t uuid128[16]; /**< Little-Endian UUID bytes. */ -} ble_uuid128_t; - -/** @brief Bluetooth Low Energy UUID type, encapsulates both 16-bit and 128-bit UUIDs. */ -typedef struct { - uint16_t uuid; /**< 16-bit UUID value or octets 12-13 of 128-bit UUID. */ - uint8_t - type; /**< UUID type, see @ref BLE_UUID_TYPES. If type is @ref BLE_UUID_TYPE_UNKNOWN, the value of uuid is undefined. */ -} ble_uuid_t; - -/**@brief Data structure. */ -typedef struct { - uint8_t *p_data; /**< Pointer to the data buffer provided to/from the application. */ - uint16_t len; /**< Length of the data buffer, in bytes. */ -} ble_data_t; - -/** @} */ -#ifdef __cplusplus -} -#endif - -#endif /* BLE_TYPES_H__ */ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h b/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h deleted file mode 100644 index 4e0bd752a..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf52/nrf_mbr.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_mbr_api Master Boot Record API - @{ - - @brief APIs for updating SoftDevice and BootLoader - -*/ - -#ifndef NRF_MBR_H__ -#define NRF_MBR_H__ - -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @addtogroup NRF_MBR_DEFINES Defines - * @{ */ - -/**@brief MBR SVC Base number. */ -#define MBR_SVC_BASE (0x18) - -/**@brief Page size in words. */ -#define MBR_PAGE_SIZE_IN_WORDS (1024) - -/** @brief The size that must be reserved for the MBR when a SoftDevice is written to flash. -This is the offset where the first byte of the SoftDevice hex file is written. */ -#define MBR_SIZE (0x1000) - -/** @brief Location (in the flash memory) of the bootloader address. */ -#define MBR_BOOTLOADER_ADDR (0xFF8) - -/** @brief Location (in UICR) of the bootloader address. */ -#define MBR_UICR_BOOTLOADER_ADDR (&(NRF_UICR->NRFFW[0])) - -/** @brief Location (in the flash memory) of the address of the MBR parameter page. */ -#define MBR_PARAM_PAGE_ADDR (0xFFC) - -/** @brief Location (in UICR) of the address of the MBR parameter page. */ -#define MBR_UICR_PARAM_PAGE_ADDR (&(NRF_UICR->NRFFW[1])) - -/** @} */ - -/** @addtogroup NRF_MBR_ENUMS Enumerations - * @{ */ - -/**@brief nRF Master Boot Record API SVC numbers. */ -enum NRF_MBR_SVCS { - SD_MBR_COMMAND = MBR_SVC_BASE, /**< ::sd_mbr_command */ -}; - -/**@brief Possible values for ::sd_mbr_command_t.command */ -enum NRF_MBR_COMMANDS { - SD_MBR_COMMAND_COPY_BL, /**< Copy a new BootLoader. @see ::sd_mbr_command_copy_bl_t*/ - SD_MBR_COMMAND_COPY_SD, /**< Copy a new SoftDevice. @see ::sd_mbr_command_copy_sd_t*/ - SD_MBR_COMMAND_INIT_SD, /**< Initialize forwarding interrupts to SD, and run reset function in SD. Does not require any - parameters in ::sd_mbr_command_t params.*/ - SD_MBR_COMMAND_COMPARE, /**< This command works like memcmp. @see ::sd_mbr_command_compare_t*/ - SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET, /**< Change the address the MBR starts after a reset. @see - ::sd_mbr_command_vector_table_base_set_t*/ - SD_MBR_COMMAND_RESERVED, - SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, /**< Start forwarding all interrupts to this address. @see - ::sd_mbr_command_irq_forward_address_set_t*/ -}; - -/** @} */ - -/** @addtogroup NRF_MBR_TYPES Types - * @{ */ - -/**@brief This command copies part of a new SoftDevice - * - * The destination area is erased before copying. - * If dst is in the middle of a flash page, that whole flash page will be erased. - * If (dst+len) is in the middle of a flash page, that whole flash page will be erased. - * - * The user of this function is responsible for setting the BPROT registers. - * - * @retval ::NRF_SUCCESS indicates that the contents of the memory blocks where copied correctly. - * @retval ::NRF_ERROR_INTERNAL indicates that the contents of the memory blocks where not verified correctly after copying. - */ -typedef struct { - uint32_t *src; /**< Pointer to the source of data to be copied.*/ - uint32_t *dst; /**< Pointer to the destination where the content is to be copied.*/ - uint32_t len; /**< Number of 32 bit words to copy. Must be a multiple of @ref MBR_PAGE_SIZE_IN_WORDS words.*/ -} sd_mbr_command_copy_sd_t; - -/**@brief This command works like memcmp, but takes the length in words. - * - * @retval ::NRF_SUCCESS indicates that the contents of both memory blocks are equal. - * @retval ::NRF_ERROR_NULL indicates that the contents of the memory blocks are not equal. - */ -typedef struct { - uint32_t *ptr1; /**< Pointer to block of memory. */ - uint32_t *ptr2; /**< Pointer to block of memory. */ - uint32_t len; /**< Number of 32 bit words to compare.*/ -} sd_mbr_command_compare_t; - -/**@brief This command copies a new BootLoader. - * - * The MBR assumes that either @ref MBR_BOOTLOADER_ADDR or @ref MBR_UICR_BOOTLOADER_ADDR is set to - * the address where the bootloader will be copied. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * The bootloader destination is erased by this function. - * If (destination+bl_len) is in the middle of a flash page, that whole flash page will be erased. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * This command will use the flash protect peripheral (BPROT or ACL) to protect the flash that is - * not intended to be written. - * - * On success, this function will not return. It will start the new bootloader from reset-vector as normal. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_FORBIDDEN if the bootloader address is not set. - * @retval ::NRF_ERROR_INVALID_LENGTH if parameters attempts to read or write outside flash area. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t *bl_src; /**< Pointer to the source of the bootloader to be be copied.*/ - uint32_t bl_len; /**< Number of 32 bit words to copy for BootLoader. */ -} sd_mbr_command_copy_bl_t; - -/**@brief Change the address the MBR starts after a reset - * - * Once this function has been called, this address is where the MBR will start to forward - * interrupts to after a reset. - * - * To restore default forwarding, this function should be called with @ref address set to 0. If a - * bootloader is present, interrupts will be forwarded to the bootloader. If not, interrupts will - * be forwarded to the SoftDevice. - * - * The location of a bootloader can be specified in @ref MBR_BOOTLOADER_ADDR or - * @ref MBR_UICR_BOOTLOADER_ADDR. If both addresses are set, the MBR will prioritize - * @ref MBR_BOOTLOADER_ADDR. - * - * This command requires that @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR is set, - * see @ref sd_mbr_command. - * - * On success, this function will not return. It will reset the device. - * - * @retval ::NRF_ERROR_INTERNAL indicates an internal error that should not happen. - * @retval ::NRF_ERROR_INVALID_ADDR if parameter address is outside of the flash size. - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page is provided. See @ref sd_mbr_command. - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_vector_table_base_set_t; - -/**@brief Sets the base address of the interrupt vector table for interrupts forwarded from the MBR - * - * Unlike sd_mbr_command_vector_table_base_set_t, this function does not reset, and it does not - * change where the MBR starts after reset. - * - * @retval ::NRF_SUCCESS - */ -typedef struct { - uint32_t address; /**< The base address of the interrupt vector table for forwarded interrupts.*/ -} sd_mbr_command_irq_forward_address_set_t; - -/**@brief Input structure containing data used when calling ::sd_mbr_command - * - * Depending on what command value that is set, the corresponding params value type must also be - * set. See @ref NRF_MBR_COMMANDS for command types and corresponding params value type. If command - * @ref SD_MBR_COMMAND_INIT_SD is set, it is not necessary to set any values under params. - */ -typedef struct { - uint32_t command; /**< Type of command to be issued. See @ref NRF_MBR_COMMANDS. */ - union { - sd_mbr_command_copy_sd_t copy_sd; /**< Parameters for copy SoftDevice.*/ - sd_mbr_command_compare_t compare; /**< Parameters for verify.*/ - sd_mbr_command_copy_bl_t copy_bl; /**< Parameters for copy BootLoader. Requires parameter page. */ - sd_mbr_command_vector_table_base_set_t base_set; /**< Parameters for vector table base set. Requires parameter page.*/ - sd_mbr_command_irq_forward_address_set_t irq_forward_address_set; /**< Parameters for irq forward address set*/ - } params; /**< Command parameters. */ -} sd_mbr_command_t; - -/** @} */ - -/** @addtogroup NRF_MBR_FUNCTIONS Functions - * @{ */ - -/**@brief Issue Master Boot Record commands - * - * Commands used when updating a SoftDevice and bootloader. - * - * The @ref SD_MBR_COMMAND_COPY_BL and @ref SD_MBR_COMMAND_VECTOR_TABLE_BASE_SET requires - * parameters to be retained by the MBR when resetting the IC. This is done in a separate flash - * page. The location of the flash page should be provided by the application in either - * @ref MBR_PARAM_PAGE_ADDR or @ref MBR_UICR_PARAM_PAGE_ADDR. If both addresses are set, the MBR - * will prioritize @ref MBR_PARAM_PAGE_ADDR. This page will be cleared by the MBR and is used to - * store the command before reset. When an address is specified, the page it refers to must not be - * used by the application. If no address is provided by the application, i.e. both - * @ref MBR_PARAM_PAGE_ADDR and @ref MBR_UICR_PARAM_PAGE_ADDR is 0xFFFFFFFF, MBR commands which use - * flash will be unavailable and return @ref NRF_ERROR_NO_MEM. - * - * @param[in] param Pointer to a struct describing the command. - * - * @note For a complete set of return values, see ::sd_mbr_command_copy_sd_t, - * ::sd_mbr_command_copy_bl_t, ::sd_mbr_command_compare_t, - * ::sd_mbr_command_vector_table_base_set_t, ::sd_mbr_command_irq_forward_address_set_t - * - * @retval ::NRF_ERROR_NO_MEM No MBR parameter page provided - * @retval ::NRF_ERROR_INVALID_PARAM if an invalid command is given. - */ -SVCALL(SD_MBR_COMMAND, uint32_t, sd_mbr_command(sd_mbr_command_t *param)); - -/** @} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_MBR_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error.h b/variants/wio-sdk-wm1110/softdevice/nrf_error.h deleted file mode 100644 index fb2831e19..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_error.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @defgroup nrf_error SoftDevice Global Error Codes - @{ - - @brief Global Error definitions -*/ - -/* Header guard */ -#ifndef NRF_ERROR_H__ -#define NRF_ERROR_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup NRF_ERRORS_BASE Error Codes Base number definitions - * @{ */ -#define NRF_ERROR_BASE_NUM (0x0) ///< Global error base -#define NRF_ERROR_SDM_BASE_NUM (0x1000) ///< SDM error base -#define NRF_ERROR_SOC_BASE_NUM (0x2000) ///< SoC error base -#define NRF_ERROR_STK_BASE_NUM (0x3000) ///< STK error base -/** @} */ - -#define NRF_SUCCESS (NRF_ERROR_BASE_NUM + 0) ///< Successful command -#define NRF_ERROR_SVC_HANDLER_MISSING (NRF_ERROR_BASE_NUM + 1) ///< SVC handler is missing -#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED (NRF_ERROR_BASE_NUM + 2) ///< SoftDevice has not been enabled -#define NRF_ERROR_INTERNAL (NRF_ERROR_BASE_NUM + 3) ///< Internal Error -#define NRF_ERROR_NO_MEM (NRF_ERROR_BASE_NUM + 4) ///< No Memory for operation -#define NRF_ERROR_NOT_FOUND (NRF_ERROR_BASE_NUM + 5) ///< Not found -#define NRF_ERROR_NOT_SUPPORTED (NRF_ERROR_BASE_NUM + 6) ///< Not supported -#define NRF_ERROR_INVALID_PARAM (NRF_ERROR_BASE_NUM + 7) ///< Invalid Parameter -#define NRF_ERROR_INVALID_STATE (NRF_ERROR_BASE_NUM + 8) ///< Invalid state, operation disallowed in this state -#define NRF_ERROR_INVALID_LENGTH (NRF_ERROR_BASE_NUM + 9) ///< Invalid Length -#define NRF_ERROR_INVALID_FLAGS (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags -#define NRF_ERROR_INVALID_DATA (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data -#define NRF_ERROR_DATA_SIZE (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size -#define NRF_ERROR_TIMEOUT (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out -#define NRF_ERROR_NULL (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer -#define NRF_ERROR_FORBIDDEN (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation -#define NRF_ERROR_INVALID_ADDR (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address -#define NRF_ERROR_BUSY (NRF_ERROR_BASE_NUM + 17) ///< Busy -#define NRF_ERROR_CONN_COUNT (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded. -#define NRF_ERROR_RESOURCES (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_H__ - -/** - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h deleted file mode 100644 index 2fd621057..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_error_sdm.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_sdm_api - @{ - @defgroup nrf_sdm_error SoftDevice Manager Error Codes - @{ - - @brief Error definitions for the SDM API -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SDM_H__ -#define NRF_ERROR_SDM_H__ - -#include "nrf_error.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define NRF_ERROR_SDM_LFCLK_SOURCE_UNKNOWN (NRF_ERROR_SDM_BASE_NUM + 0) ///< Unknown LFCLK source. -#define NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION \ - (NRF_ERROR_SDM_BASE_NUM + 1) ///< Incorrect interrupt configuration (can be caused by using illegal priority levels, or having - ///< enabled SoftDevice interrupts). -#define NRF_ERROR_SDM_INCORRECT_CLENR0 \ - (NRF_ERROR_SDM_BASE_NUM + 2) ///< Incorrect CLENR0 (can be caused by erroneous SoftDevice flashing). - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SDM_H__ - -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h deleted file mode 100644 index cbd0ba8ac..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_error_soc.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - @addtogroup nrf_soc_api - @{ - @defgroup nrf_soc_error SoC Library Error Codes - @{ - - @brief Error definitions for the SoC library - -*/ - -/* Header guard */ -#ifndef NRF_ERROR_SOC_H__ -#define NRF_ERROR_SOC_H__ - -#include "nrf_error.h" -#ifdef __cplusplus -extern "C" { -#endif - -/* Mutex Errors */ -#define NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN (NRF_ERROR_SOC_BASE_NUM + 0) ///< Mutex already taken - -/* NVIC errors */ -#define NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE (NRF_ERROR_SOC_BASE_NUM + 1) ///< NVIC interrupt not available -#define NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED (NRF_ERROR_SOC_BASE_NUM + 2) ///< NVIC interrupt priority not allowed -#define NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 3) ///< NVIC should not return - -/* Power errors */ -#define NRF_ERROR_SOC_POWER_MODE_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 4) ///< Power mode unknown -#define NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN (NRF_ERROR_SOC_BASE_NUM + 5) ///< Power POF threshold unknown -#define NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN (NRF_ERROR_SOC_BASE_NUM + 6) ///< Power off should not return - -/* Rand errors */ -#define NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES (NRF_ERROR_SOC_BASE_NUM + 7) ///< RAND not enough values - -/* PPI errors */ -#define NRF_ERROR_SOC_PPI_INVALID_CHANNEL (NRF_ERROR_SOC_BASE_NUM + 8) ///< Invalid PPI Channel -#define NRF_ERROR_SOC_PPI_INVALID_GROUP (NRF_ERROR_SOC_BASE_NUM + 9) ///< Invalid PPI Group - -#ifdef __cplusplus -} -#endif -#endif // NRF_ERROR_SOC_H__ -/** - @} - @} -*/ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h b/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h deleted file mode 100644 index d4ab204d9..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_nvic.h +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_nvic_api SoftDevice NVIC API - * @{ - * - * @note In order to use this module, the following code has to be added to a .c file: - * \code - * nrf_nvic_state_t nrf_nvic_state = {0}; - * \endcode - * - * @note Definitions and declarations starting with __ (double underscore) in this header file are - * not intended for direct use by the application. - * - * @brief APIs for the accessing NVIC when using a SoftDevice. - * - */ - -#ifndef NRF_NVIC_H__ -#define NRF_NVIC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_NVIC_DEFINES Defines - * @{ */ - -/**@defgroup NRF_NVIC_ISER_DEFINES SoftDevice NVIC internal definitions - * @{ */ - -#define __NRF_NVIC_NVMC_IRQn \ - (30) /**< The peripheral ID of the NVMC. IRQ numbers are used to identify peripherals, but the NVMC doesn't have an IRQ \ - number in the MDK. */ - -#define __NRF_NVIC_ISER_COUNT (2) /**< The number of ISER/ICER registers in the NVIC that are used. */ - -/**@brief Interrupt priority levels used by the SoftDevice. */ -#define __NRF_NVIC_SD_IRQ_PRIOS \ - ((uint8_t)((1U << 0) /**< Priority level high .*/ \ - | (1U << 1) /**< Priority level medium. */ \ - | (1U << 4) /**< Priority level low. */ \ - )) - -/**@brief Interrupt priority levels available to the application. */ -#define __NRF_NVIC_APP_IRQ_PRIOS ((uint8_t)~__NRF_NVIC_SD_IRQ_PRIOS) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 0-31. */ -#define __NRF_NVIC_SD_IRQS_0 \ - ((uint32_t)((1U << POWER_CLOCK_IRQn) | (1U << RADIO_IRQn) | (1U << RTC0_IRQn) | (1U << TIMER0_IRQn) | (1U << RNG_IRQn) | \ - (1U << ECB_IRQn) | (1U << CCM_AAR_IRQn) | (1U << TEMP_IRQn) | (1U << __NRF_NVIC_NVMC_IRQn) | \ - (1U << (uint32_t)SWI5_IRQn))) - -/**@brief Interrupts used by the SoftDevice, with IRQn in the range 32-63. */ -#define __NRF_NVIC_SD_IRQS_1 ((uint32_t)0) - -/**@brief Interrupts available for to application, with IRQn in the range 0-31. */ -#define __NRF_NVIC_APP_IRQS_0 (~__NRF_NVIC_SD_IRQS_0) - -/**@brief Interrupts available for to application, with IRQn in the range 32-63. */ -#define __NRF_NVIC_APP_IRQS_1 (~__NRF_NVIC_SD_IRQS_1) - -/**@} */ - -/**@} */ - -/**@addtogroup NRF_NVIC_VARIABLES Variables - * @{ */ - -/**@brief Type representing the state struct for the SoftDevice NVIC module. */ -typedef struct { - uint32_t volatile __irq_masks[__NRF_NVIC_ISER_COUNT]; /**< IRQs enabled by the application in the NVIC. */ - uint32_t volatile __cr_flag; /**< Non-zero if already in a critical region */ -} nrf_nvic_state_t; - -/**@brief Variable keeping the state for the SoftDevice NVIC module. This must be declared in an - * application source file. */ -extern nrf_nvic_state_t nrf_nvic_state; - -/**@} */ - -/**@addtogroup NRF_NVIC_INTERNAL_FUNCTIONS SoftDevice NVIC internal functions - * @{ */ - -/**@brief Disables IRQ interrupts globally, including the SoftDevice's interrupts. - * - * @retval The value of PRIMASK prior to disabling the interrupts. - */ -__STATIC_INLINE int __sd_nvic_irq_disable(void); - -/**@brief Enables IRQ interrupts globally, including the SoftDevice's interrupts. - */ -__STATIC_INLINE void __sd_nvic_irq_enable(void); - -/**@brief Checks if IRQn is available to application - * @param[in] IRQn IRQ to check - * - * @retval 1 (true) if the IRQ to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn); - -/**@brief Checks if priority is available to application - * @param[in] priority priority to check - * - * @retval 1 (true) if the priority to check is available to the application - */ -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority); - -/**@} */ - -/**@addtogroup NRF_NVIC_FUNCTIONS SoftDevice NVIC public functions - * @{ */ - -/**@brief Enable External Interrupt. - * @note Corresponds to NVIC_EnableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_EnableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was enabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt has a priority not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn); - -/**@brief Disable External Interrupt. - * @note Corresponds to NVIC_DisableIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_DisableIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt was disabled. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE The interrupt is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn); - -/**@brief Get Pending Interrupt. - * @note Corresponds to NVIC_GetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPendingIRQ documentation in CMSIS. - * @param[out] p_pending_irq Return value from NVIC_GetPendingIRQ. - * - * @retval ::NRF_SUCCESS The interrupt is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq); - -/**@brief Set Pending Interrupt. - * @note Corresponds to NVIC_SetPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt is set pending. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn); - -/**@brief Clear Pending Interrupt. - * @note Corresponds to NVIC_ClearPendingIRQ in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_ClearPendingIRQ documentation in CMSIS. - * - * @retval ::NRF_SUCCESS The interrupt pending flag is cleared. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn); - -/**@brief Set Interrupt Priority. - * @note Corresponds to NVIC_SetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * @pre Priority is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_SetPriority documentation in CMSIS. - * @param[in] priority A valid IRQ priority for use by the application. - * - * @retval ::NRF_SUCCESS The interrupt and priority level is available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE IRQn is not available for the application. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED The interrupt priority is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority); - -/**@brief Get Interrupt Priority. - * @note Corresponds to NVIC_GetPriority in CMSIS. - * - * @pre IRQn is valid and not reserved by the stack. - * - * @param[in] IRQn See the NVIC_GetPriority documentation in CMSIS. - * @param[out] p_priority Return value from NVIC_GetPriority. - * - * @retval ::NRF_SUCCESS The interrupt priority is returned in p_priority. - * @retval ::NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE - IRQn is not available for the application. - */ -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority); - -/**@brief System Reset. - * @note Corresponds to NVIC_SystemReset in CMSIS. - * - * @retval ::NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN - */ -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void); - -/**@brief Enter critical region. - * - * @post Application interrupts will be disabled. - * @note sd_nvic_critical_region_enter() and ::sd_nvic_critical_region_exit() must be called in matching pairs inside each - * execution context - * @sa sd_nvic_critical_region_exit - * - * @param[out] p_is_nested_critical_region If 1, the application is now in a nested critical region. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region); - -/**@brief Exit critical region. - * - * @pre Application has entered a critical region using ::sd_nvic_critical_region_enter. - * @post If not in a nested critical region, the application interrupts will restored to the state before - * ::sd_nvic_critical_region_enter was called. - * - * @param[in] is_nested_critical_region If this is set to 1, the critical region won't be exited. @sa - * sd_nvic_critical_region_enter. - * - * @retval ::NRF_SUCCESS - */ -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region); - -/**@} */ - -#ifndef SUPPRESS_INLINE_IMPLEMENTATION - -__STATIC_INLINE int __sd_nvic_irq_disable(void) -{ - int pm = __get_PRIMASK(); - __disable_irq(); - return pm; -} - -__STATIC_INLINE void __sd_nvic_irq_enable(void) -{ - __enable_irq(); -} - -__STATIC_INLINE uint32_t __sd_nvic_app_accessible_irq(IRQn_Type IRQn) -{ - if (IRQn < 32) { - return ((1UL << IRQn) & __NRF_NVIC_APP_IRQS_0) != 0; - } else if (IRQn < 64) { - return ((1UL << (IRQn - 32)) & __NRF_NVIC_APP_IRQS_1) != 0; - } else { - return 1; - } -} - -__STATIC_INLINE uint32_t __sd_nvic_is_app_accessible_priority(uint32_t priority) -{ - if ((priority >= (1 << __NVIC_PRIO_BITS)) || (((1 << priority) & __NRF_NVIC_APP_IRQ_PRIOS) == 0)) { - return 0; - } - return 1; -} - -__STATIC_INLINE uint32_t sd_nvic_EnableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - if (!__sd_nvic_is_app_accessible_priority(NVIC_GetPriority(IRQn))) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] |= - (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); - } else { - NVIC_EnableIRQ(IRQn); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_DisableIRQ(IRQn_Type IRQn) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__irq_masks[(uint32_t)((int32_t)IRQn) >> 5] &= ~(1UL << ((uint32_t)(IRQn)&0x1F)); - } else { - NVIC_DisableIRQ(IRQn); - } - - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPendingIRQ(IRQn_Type IRQn, uint32_t *p_pending_irq) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_pending_irq = NVIC_GetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_SetPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_ClearPendingIRQ(IRQn_Type IRQn) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - NVIC_ClearPendingIRQ(IRQn); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SetPriority(IRQn_Type IRQn, uint32_t priority) -{ - if (!__sd_nvic_app_accessible_irq(IRQn)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } - - if (!__sd_nvic_is_app_accessible_priority(priority)) { - return NRF_ERROR_SOC_NVIC_INTERRUPT_PRIORITY_NOT_ALLOWED; - } - - NVIC_SetPriority(IRQn, (uint32_t)priority); - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_GetPriority(IRQn_Type IRQn, uint32_t *p_priority) -{ - if (__sd_nvic_app_accessible_irq(IRQn)) { - *p_priority = (NVIC_GetPriority(IRQn) & 0xFF); - return NRF_SUCCESS; - } else { - return NRF_ERROR_SOC_NVIC_INTERRUPT_NOT_AVAILABLE; - } -} - -__STATIC_INLINE uint32_t sd_nvic_SystemReset(void) -{ - NVIC_SystemReset(); - return NRF_ERROR_SOC_NVIC_SHOULD_NOT_RETURN; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t *p_is_nested_critical_region) -{ - int was_masked = __sd_nvic_irq_disable(); - if (!nrf_nvic_state.__cr_flag) { - nrf_nvic_state.__cr_flag = 1; - nrf_nvic_state.__irq_masks[0] = (NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0); - NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0; - nrf_nvic_state.__irq_masks[1] = (NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1); - NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1; - *p_is_nested_critical_region = 0; - } else { - *p_is_nested_critical_region = 1; - } - if (!was_masked) { - __sd_nvic_irq_enable(); - } - return NRF_SUCCESS; -} - -__STATIC_INLINE uint32_t sd_nvic_critical_region_exit(uint8_t is_nested_critical_region) -{ - if (nrf_nvic_state.__cr_flag && (is_nested_critical_region == 0)) { - int was_masked = __sd_nvic_irq_disable(); - NVIC->ISER[0] = nrf_nvic_state.__irq_masks[0]; - NVIC->ISER[1] = nrf_nvic_state.__irq_masks[1]; - nrf_nvic_state.__cr_flag = 0; - if (!was_masked) { - __sd_nvic_irq_enable(); - } - } - - return NRF_SUCCESS; -} - -#endif /* SUPPRESS_INLINE_IMPLEMENTATION */ - -#ifdef __cplusplus -} -#endif - -#endif // NRF_NVIC_H__ - -/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_soc.h b/variants/wio-sdk-wm1110/softdevice/nrf_soc.h deleted file mode 100644 index c649ca836..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_soc.h +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @defgroup nrf_soc_api SoC Library API - * @{ - * - * @brief APIs for the SoC library. - * - */ - -#ifndef NRF_SOC_H__ -#define NRF_SOC_H__ - -#include "nrf.h" -#include "nrf_error.h" -#include "nrf_error_soc.h" -#include "nrf_svc.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/**@addtogroup NRF_SOC_DEFINES Defines - * @{ */ - -/**@brief The number of the lowest SVC number reserved for the SoC library. */ -#define SOC_SVC_BASE (0x20) /**< Base value for SVCs that are available when the SoftDevice is disabled. */ -#define SOC_SVC_BASE_NOT_AVAILABLE (0x2C) /**< Base value for SVCs that are not available when the SoftDevice is disabled. */ - -/**@brief Guaranteed time for application to process radio inactive notification. */ -#define NRF_RADIO_NOTIFICATION_INACTIVE_GUARANTEED_TIME_US (62) - -/**@brief The minimum allowed timeslot extension time. */ -#define NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US (200) - -/**@brief The maximum processing time to handle a timeslot extension. */ -#define NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US (20) - -/**@brief The latest time before the end of a timeslot the timeslot can be extended. */ -#define NRF_RADIO_MIN_EXTENSION_MARGIN_US (82) - -#define SOC_ECB_KEY_LENGTH (16) /**< ECB key length. */ -#define SOC_ECB_CLEARTEXT_LENGTH (16) /**< ECB cleartext length. */ -#define SOC_ECB_CIPHERTEXT_LENGTH (SOC_ECB_CLEARTEXT_LENGTH) /**< ECB ciphertext length. */ - -#define SD_EVT_IRQn (SWI2_IRQn) /**< SoftDevice Event IRQ number. Used for both protocol events and SoC events. */ -#define SD_EVT_IRQHandler \ - (SWI2_IRQHandler) /**< SoftDevice Event IRQ handler. Used for both protocol events and SoC events. \ - The default interrupt priority for this handler is set to 6 */ -#define RADIO_NOTIFICATION_IRQn (SWI1_IRQn) /**< The radio notification IRQ number. */ -#define RADIO_NOTIFICATION_IRQHandler \ - (SWI1_IRQHandler) /**< The radio notification IRQ handler. \ - The default interrupt priority for this handler is set to 6 */ -#define NRF_RADIO_LENGTH_MIN_US (100) /**< The shortest allowed radio timeslot, in microseconds. */ -#define NRF_RADIO_LENGTH_MAX_US (100000) /**< The longest allowed radio timeslot, in microseconds. */ - -#define NRF_RADIO_DISTANCE_MAX_US \ - (128000000UL - 1UL) /**< The longest timeslot distance, in microseconds, allowed for the distance parameter (see @ref \ - nrf_radio_request_normal_t) in the request. */ - -#define NRF_RADIO_EARLIEST_TIMEOUT_MAX_US \ - (128000000UL - 1UL) /**< The longest timeout, in microseconds, allowed when requesting the earliest possible timeslot. */ - -#define NRF_RADIO_START_JITTER_US \ - (2) /**< The maximum jitter in @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START relative to the requested start time. */ - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI channels reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_CHANNELS_SD_ENABLED_MSK \ - ((uint32_t)((1U << 17) | (1U << 18) | (1U << 19) | (1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | \ - (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | (1U << 30) | (1U << 31))) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is disabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_DISABLED_MSK ((uint32_t)(0)) - -/**@brief Mask of PPI groups reserved by the SoftDevice when the SoftDevice is enabled. */ -#define NRF_SOC_SD_PPI_GROUPS_SD_ENABLED_MSK ((uint32_t)((1U << 4) | (1U << 5))) - -/**@} */ - -/**@addtogroup NRF_SOC_ENUMS Enumerations - * @{ */ - -/**@brief The SVC numbers used by the SVC functions in the SoC library. */ -enum NRF_SOC_SVCS { - SD_PPI_CHANNEL_ENABLE_GET = SOC_SVC_BASE, - SD_PPI_CHANNEL_ENABLE_SET = SOC_SVC_BASE + 1, - SD_PPI_CHANNEL_ENABLE_CLR = SOC_SVC_BASE + 2, - SD_PPI_CHANNEL_ASSIGN = SOC_SVC_BASE + 3, - SD_PPI_GROUP_TASK_ENABLE = SOC_SVC_BASE + 4, - SD_PPI_GROUP_TASK_DISABLE = SOC_SVC_BASE + 5, - SD_PPI_GROUP_ASSIGN = SOC_SVC_BASE + 6, - SD_PPI_GROUP_GET = SOC_SVC_BASE + 7, - SD_FLASH_PAGE_ERASE = SOC_SVC_BASE + 8, - SD_FLASH_WRITE = SOC_SVC_BASE + 9, - SD_PROTECTED_REGISTER_WRITE = SOC_SVC_BASE + 11, - SD_MUTEX_NEW = SOC_SVC_BASE_NOT_AVAILABLE, - SD_MUTEX_ACQUIRE = SOC_SVC_BASE_NOT_AVAILABLE + 1, - SD_MUTEX_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 2, - SD_RAND_APPLICATION_POOL_CAPACITY_GET = SOC_SVC_BASE_NOT_AVAILABLE + 3, - SD_RAND_APPLICATION_BYTES_AVAILABLE_GET = SOC_SVC_BASE_NOT_AVAILABLE + 4, - SD_RAND_APPLICATION_VECTOR_GET = SOC_SVC_BASE_NOT_AVAILABLE + 5, - SD_POWER_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 6, - SD_POWER_SYSTEM_OFF = SOC_SVC_BASE_NOT_AVAILABLE + 7, - SD_POWER_RESET_REASON_GET = SOC_SVC_BASE_NOT_AVAILABLE + 8, - SD_POWER_RESET_REASON_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 9, - SD_POWER_POF_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 10, - SD_POWER_POF_THRESHOLD_SET = SOC_SVC_BASE_NOT_AVAILABLE + 11, - SD_POWER_POF_THRESHOLDVDDH_SET = SOC_SVC_BASE_NOT_AVAILABLE + 12, - SD_POWER_RAM_POWER_SET = SOC_SVC_BASE_NOT_AVAILABLE + 13, - SD_POWER_RAM_POWER_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 14, - SD_POWER_RAM_POWER_GET = SOC_SVC_BASE_NOT_AVAILABLE + 15, - SD_POWER_GPREGRET_SET = SOC_SVC_BASE_NOT_AVAILABLE + 16, - SD_POWER_GPREGRET_CLR = SOC_SVC_BASE_NOT_AVAILABLE + 17, - SD_POWER_GPREGRET_GET = SOC_SVC_BASE_NOT_AVAILABLE + 18, - SD_POWER_DCDC_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 19, - SD_POWER_DCDC0_MODE_SET = SOC_SVC_BASE_NOT_AVAILABLE + 20, - SD_APP_EVT_WAIT = SOC_SVC_BASE_NOT_AVAILABLE + 21, - SD_CLOCK_HFCLK_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 22, - SD_CLOCK_HFCLK_RELEASE = SOC_SVC_BASE_NOT_AVAILABLE + 23, - SD_CLOCK_HFCLK_IS_RUNNING = SOC_SVC_BASE_NOT_AVAILABLE + 24, - SD_RADIO_NOTIFICATION_CFG_SET = SOC_SVC_BASE_NOT_AVAILABLE + 25, - SD_ECB_BLOCK_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 26, - SD_ECB_BLOCKS_ENCRYPT = SOC_SVC_BASE_NOT_AVAILABLE + 27, - SD_RADIO_SESSION_OPEN = SOC_SVC_BASE_NOT_AVAILABLE + 28, - SD_RADIO_SESSION_CLOSE = SOC_SVC_BASE_NOT_AVAILABLE + 29, - SD_RADIO_REQUEST = SOC_SVC_BASE_NOT_AVAILABLE + 30, - SD_EVT_GET = SOC_SVC_BASE_NOT_AVAILABLE + 31, - SD_TEMP_GET = SOC_SVC_BASE_NOT_AVAILABLE + 32, - SD_POWER_USBPWRRDY_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 33, - SD_POWER_USBDETECTED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 34, - SD_POWER_USBREMOVED_ENABLE = SOC_SVC_BASE_NOT_AVAILABLE + 35, - SD_POWER_USBREGSTATUS_GET = SOC_SVC_BASE_NOT_AVAILABLE + 36, - SVC_SOC_LAST = SOC_SVC_BASE_NOT_AVAILABLE + 37 -}; - -/**@brief Possible values of a ::nrf_mutex_t. */ -enum NRF_MUTEX_VALUES { NRF_MUTEX_FREE, NRF_MUTEX_TAKEN }; - -/**@brief Power modes. */ -enum NRF_POWER_MODES { - NRF_POWER_MODE_CONSTLAT, /**< Constant latency mode. See power management in the reference manual. */ - NRF_POWER_MODE_LOWPWR /**< Low power mode. See power management in the reference manual. */ -}; - -/**@brief Power failure thresholds */ -enum NRF_POWER_THRESHOLDS { - NRF_POWER_THRESHOLD_V17 = 4UL, /**< 1.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V18, /**< 1.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V19, /**< 1.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V20, /**< 2.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V21, /**< 2.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V22, /**< 2.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V23, /**< 2.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V24, /**< 2.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V25, /**< 2.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V26, /**< 2.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLD_V28 /**< 2.8 Volts power failure threshold. */ -}; - -/**@brief Power failure thresholds for high voltage */ -enum NRF_POWER_THRESHOLDVDDHS { - NRF_POWER_THRESHOLDVDDH_V27, /**< 2.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V28, /**< 2.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V29, /**< 2.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V30, /**< 3.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V31, /**< 3.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V32, /**< 3.2 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V33, /**< 3.3 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V34, /**< 3.4 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V35, /**< 3.5 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V36, /**< 3.6 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V37, /**< 3.7 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V38, /**< 3.8 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V39, /**< 3.9 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V40, /**< 4.0 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V41, /**< 4.1 Volts power failure threshold. */ - NRF_POWER_THRESHOLDVDDH_V42 /**< 4.2 Volts power failure threshold. */ -}; - -/**@brief DC/DC converter modes. */ -enum NRF_POWER_DCDC_MODES { - NRF_POWER_DCDC_DISABLE, /**< The DCDC is disabled. */ - NRF_POWER_DCDC_ENABLE /**< The DCDC is enabled. */ -}; - -/**@brief Radio notification distances. */ -enum NRF_RADIO_NOTIFICATION_DISTANCES { - NRF_RADIO_NOTIFICATION_DISTANCE_NONE = 0, /**< The event does not have a notification. */ - NRF_RADIO_NOTIFICATION_DISTANCE_800US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_1740US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_2680US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_3620US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_4560US, /**< The distance from the active notification to start of radio activity. */ - NRF_RADIO_NOTIFICATION_DISTANCE_5500US /**< The distance from the active notification to start of radio activity. */ -}; - -/**@brief Radio notification types. */ -enum NRF_RADIO_NOTIFICATION_TYPES { - NRF_RADIO_NOTIFICATION_TYPE_NONE = 0, /**< The event does not have a radio notification signal. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_ACTIVE, /**< Using interrupt for notification when the radio will be enabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE, /**< Using interrupt for notification when the radio has been disabled. */ - NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, /**< Using interrupt for notification both when the radio will be enabled and - disabled. */ -}; - -/**@brief The Radio signal callback types. */ -enum NRF_RADIO_CALLBACK_SIGNAL_TYPE { - NRF_RADIO_CALLBACK_SIGNAL_TYPE_START, /**< This signal indicates the start of the radio timeslot. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0, /**< This signal indicates the NRF_TIMER0 interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO, /**< This signal indicates the NRF_RADIO interrupt. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED, /**< This signal indicates extend action failed. */ - NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED /**< This signal indicates extend action succeeded. */ -}; - -/**@brief The actions requested by the signal callback. - * - * This code gives the SOC instructions about what action to take when the signal callback has - * returned. - */ -enum NRF_RADIO_SIGNAL_CALLBACK_ACTION { - NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE, /**< Return without action. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND, /**< Request an extension of the current - timeslot. Maximum execution time for this action: - @ref NRF_RADIO_MAX_EXTENSION_PROCESSING_TIME_US. - This action must be started at least - @ref NRF_RADIO_MIN_EXTENSION_MARGIN_US before - the end of the timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_END, /**< End the current radio timeslot. */ - NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END /**< Request a new radio timeslot and end the current timeslot. */ -}; - -/**@brief Radio timeslot high frequency clock source configuration. */ -enum NRF_RADIO_HFCLK_CFG { - NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED, /**< The SoftDevice will guarantee that the high frequency clock source is the - external crystal for the whole duration of the timeslot. This should be the - preferred option for events that use the radio or require high timing accuracy. - @note The SoftDevice will automatically turn on and off the external crystal, - at the beginning and end of the timeslot, respectively. The crystal may also - intentionally be left running after the timeslot, in cases where it is needed - by the SoftDevice shortly after the end of the timeslot. */ - NRF_RADIO_HFCLK_CFG_NO_GUARANTEE /**< This configuration allows for earlier and tighter scheduling of timeslots. - The RC oscillator may be the clock source in part or for the whole duration of the - timeslot. The RC oscillator's accuracy must therefore be taken into consideration. - @note If the application will use the radio peripheral in timeslots with this - configuration, it must make sure that the crystal is running and stable before - starting the radio. */ -}; - -/**@brief Radio timeslot priorities. */ -enum NRF_RADIO_PRIORITY { - NRF_RADIO_PRIORITY_HIGH, /**< High (equal priority as the normal connection priority of the SoftDevice stack(s)). */ - NRF_RADIO_PRIORITY_NORMAL, /**< Normal (equal priority as the priority of secondary activities of the SoftDevice stack(s)). */ -}; - -/**@brief Radio timeslot request type. */ -enum NRF_RADIO_REQUEST_TYPE { - NRF_RADIO_REQ_TYPE_EARLIEST, /**< Request radio timeslot as early as possible. This should always be used for the first - request in a session. */ - NRF_RADIO_REQ_TYPE_NORMAL /**< Normal radio timeslot request. */ -}; - -/**@brief SoC Events. */ -enum NRF_SOC_EVTS { - NRF_EVT_HFCLKSTARTED, /**< Event indicating that the HFCLK has started. */ - NRF_EVT_POWER_FAILURE_WARNING, /**< Event indicating that a power failure warning has occurred. */ - NRF_EVT_FLASH_OPERATION_SUCCESS, /**< Event indicating that the ongoing flash operation has completed successfully. */ - NRF_EVT_FLASH_OPERATION_ERROR, /**< Event indicating that the ongoing flash operation has timed out with an error. */ - NRF_EVT_RADIO_BLOCKED, /**< Event indicating that a radio timeslot was blocked. */ - NRF_EVT_RADIO_CANCELED, /**< Event indicating that a radio timeslot was canceled by SoftDevice. */ - NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was - invalid. */ - NRF_EVT_RADIO_SESSION_IDLE, /**< Event indicating that a radio timeslot session is idle. */ - NRF_EVT_RADIO_SESSION_CLOSED, /**< Event indicating that a radio timeslot session is closed. */ - NRF_EVT_POWER_USB_POWER_READY, /**< Event indicating that a USB 3.3 V supply is ready. */ - NRF_EVT_POWER_USB_DETECTED, /**< Event indicating that voltage supply is detected on VBUS. */ - NRF_EVT_POWER_USB_REMOVED, /**< Event indicating that voltage supply is removed from VBUS. */ - NRF_EVT_NUMBER_OF_EVTS -}; - -/**@} */ - -/**@addtogroup NRF_SOC_STRUCTURES Structures - * @{ */ - -/**@brief Represents a mutex for use with the nrf_mutex functions. - * @note Accessing the value directly is not safe, use the mutex functions! - */ -typedef volatile uint8_t nrf_mutex_t; - -/**@brief Parameters for a request for a timeslot as early as possible. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t length_us; /**< The radio timeslot length (in the range 100 to 100,000] microseconds). */ - uint32_t timeout_us; /**< Longest acceptable delay until the start of the requested timeslot (up to @ref - NRF_RADIO_EARLIEST_TIMEOUT_MAX_US microseconds). */ -} nrf_radio_request_earliest_t; - -/**@brief Parameters for a normal radio timeslot request. */ -typedef struct { - uint8_t hfclk; /**< High frequency clock source, see @ref NRF_RADIO_HFCLK_CFG. */ - uint8_t priority; /**< The radio timeslot priority, see @ref NRF_RADIO_PRIORITY. */ - uint32_t distance_us; /**< Distance from the start of the previous radio timeslot (up to @ref NRF_RADIO_DISTANCE_MAX_US - microseconds). */ - uint32_t length_us; /**< The radio timeslot length (in the range [100..100,000] microseconds). */ -} nrf_radio_request_normal_t; - -/**@brief Radio timeslot request parameters. */ -typedef struct { - uint8_t request_type; /**< Type of request, see @ref NRF_RADIO_REQUEST_TYPE. */ - union { - nrf_radio_request_earliest_t earliest; /**< Parameters for requesting a radio timeslot as early as possible. */ - nrf_radio_request_normal_t normal; /**< Parameters for requesting a normal radio timeslot. */ - } params; /**< Parameter union. */ -} nrf_radio_request_t; - -/**@brief Return parameters of the radio timeslot signal callback. */ -typedef struct { - uint8_t callback_action; /**< The action requested by the application when returning from the signal callback, see @ref - NRF_RADIO_SIGNAL_CALLBACK_ACTION. */ - union { - struct { - nrf_radio_request_t *p_next; /**< The request parameters for the next radio timeslot. */ - } request; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END. */ - struct { - uint32_t length_us; /**< Requested extension of the radio timeslot duration (microseconds) (for minimum time see @ref - NRF_RADIO_MINIMUM_TIMESLOT_LENGTH_EXTENSION_TIME_US). */ - } extend; /**< Additional parameters for return_code @ref NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND. */ - } params; /**< Parameter union. */ -} nrf_radio_signal_callback_return_param_t; - -/**@brief The radio timeslot signal callback type. - * - * @note In case of invalid return parameters, the radio timeslot will automatically end - * immediately after returning from the signal callback and the - * @ref NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN event will be sent. - * @note The returned struct pointer must remain valid after the signal callback - * function returns. For instance, this means that it must not point to a stack variable. - * - * @param[in] signal_type Type of signal, see @ref NRF_RADIO_CALLBACK_SIGNAL_TYPE. - * - * @return Pointer to structure containing action requested by the application. - */ -typedef nrf_radio_signal_callback_return_param_t *(*nrf_radio_signal_callback_t)(uint8_t signal_type); - -/**@brief AES ECB parameter typedefs */ -typedef uint8_t soc_ecb_key_t[SOC_ECB_KEY_LENGTH]; /**< Encryption key type. */ -typedef uint8_t soc_ecb_cleartext_t[SOC_ECB_CLEARTEXT_LENGTH]; /**< Cleartext data type. */ -typedef uint8_t soc_ecb_ciphertext_t[SOC_ECB_CIPHERTEXT_LENGTH]; /**< Ciphertext data type. */ - -/**@brief AES ECB data structure */ -typedef struct { - soc_ecb_key_t key; /**< Encryption key. */ - soc_ecb_cleartext_t cleartext; /**< Cleartext data. */ - soc_ecb_ciphertext_t ciphertext; /**< Ciphertext data. */ -} nrf_ecb_hal_data_t; - -/**@brief AES ECB block. Used to provide multiple blocks in a single call - to @ref sd_ecb_blocks_encrypt.*/ -typedef struct { - soc_ecb_key_t const *p_key; /**< Pointer to the Encryption key. */ - soc_ecb_cleartext_t const *p_cleartext; /**< Pointer to the Cleartext data. */ - soc_ecb_ciphertext_t *p_ciphertext; /**< Pointer to the Ciphertext data. */ -} nrf_ecb_hal_data_block_t; - -/**@} */ - -/**@addtogroup NRF_SOC_FUNCTIONS Functions - * @{ */ - -/**@brief Initialize a mutex. - * - * @param[in] p_mutex Pointer to the mutex to initialize. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_NEW, uint32_t, sd_mutex_new(nrf_mutex_t *p_mutex)); - -/**@brief Attempt to acquire a mutex. - * - * @param[in] p_mutex Pointer to the mutex to acquire. - * - * @retval ::NRF_SUCCESS The mutex was successfully acquired. - * @retval ::NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN The mutex could not be acquired. - */ -SVCALL(SD_MUTEX_ACQUIRE, uint32_t, sd_mutex_acquire(nrf_mutex_t *p_mutex)); - -/**@brief Release a mutex. - * - * @param[in] p_mutex Pointer to the mutex to release. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_MUTEX_RELEASE, uint32_t, sd_mutex_release(nrf_mutex_t *p_mutex)); - -/**@brief Query the capacity of the application random pool. - * - * @param[out] p_pool_capacity The capacity of the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_POOL_CAPACITY_GET, uint32_t, sd_rand_application_pool_capacity_get(uint8_t *p_pool_capacity)); - -/**@brief Get number of random bytes available to the application. - * - * @param[out] p_bytes_available The number of bytes currently available in the pool. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RAND_APPLICATION_BYTES_AVAILABLE_GET, uint32_t, sd_rand_application_bytes_available_get(uint8_t *p_bytes_available)); - -/**@brief Get random bytes from the application pool. - * - * @param[out] p_buff Pointer to unit8_t buffer for storing the bytes. - * @param[in] length Number of bytes to take from pool and place in p_buff. - * - * @retval ::NRF_SUCCESS The requested bytes were written to p_buff. - * @retval ::NRF_ERROR_SOC_RAND_NOT_ENOUGH_VALUES No bytes were written to the buffer, because there were not enough bytes - * available. - */ -SVCALL(SD_RAND_APPLICATION_VECTOR_GET, uint32_t, sd_rand_application_vector_get(uint8_t *p_buff, uint8_t length)); - -/**@brief Gets the reset reason register. - * - * @param[out] p_reset_reason Contents of the NRF_POWER->RESETREAS register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_GET, uint32_t, sd_power_reset_reason_get(uint32_t *p_reset_reason)); - -/**@brief Clears the bits of the reset reason register. - * - * @param[in] reset_reason_clr_msk Contains the bits to clear from the reset reason register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RESET_REASON_CLR, uint32_t, sd_power_reset_reason_clr(uint32_t reset_reason_clr_msk)); - -/**@brief Sets the power mode when in CPU sleep. - * - * @param[in] power_mode The power mode to use when in CPU sleep, see @ref NRF_POWER_MODES. @sa sd_app_evt_wait - * - * @retval ::NRF_SUCCESS The power mode was set. - * @retval ::NRF_ERROR_SOC_POWER_MODE_UNKNOWN The power mode was unknown. - */ -SVCALL(SD_POWER_MODE_SET, uint32_t, sd_power_mode_set(uint8_t power_mode)); - -/**@brief Puts the chip in System OFF mode. - * - * @retval ::NRF_ERROR_SOC_POWER_OFF_SHOULD_NOT_RETURN - */ -SVCALL(SD_POWER_SYSTEM_OFF, uint32_t, sd_power_system_off(void)); - -/**@brief Enables or disables the power-fail comparator. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_FAILURE_WARNING) when the power failure warning occurs. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] pof_enable True if the power-fail comparator should be enabled, false if it should be disabled. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_POF_ENABLE, uint32_t, sd_power_pof_enable(uint8_t pof_enable)); - -/**@brief Enables or disables the USB power ready event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_POWER_READY) when a USB 3.3 V supply is ready. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbpwrrdy_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBPWRRDY_ENABLE, uint32_t, sd_power_usbpwrrdy_enable(uint8_t usbpwrrdy_enable)); - -/**@brief Enables or disables the power USB-detected event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_DETECTED) when a voltage supply is detected on VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbdetected_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBDETECTED_ENABLE, uint32_t, sd_power_usbdetected_enable(uint8_t usbdetected_enable)); - -/**@brief Enables or disables the power USB-removed event. - * - * Enabling this will give a SoftDevice event (NRF_EVT_POWER_USB_REMOVED) when a voltage supply is removed from VBUS. - * The event can be retrieved with sd_evt_get(); - * - * @param[in] usbremoved_enable True if the power ready event should be enabled, false if it should be disabled. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREMOVED_ENABLE, uint32_t, sd_power_usbremoved_enable(uint8_t usbremoved_enable)); - -/**@brief Get USB supply status register content. - * - * @param[out] usbregstatus The content of USBREGSTATUS register. - * - * @note Calling this function on a chip without USBD peripheral will result in undefined behaviour. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_USBREGSTATUS_GET, uint32_t, sd_power_usbregstatus_get(uint32_t *usbregstatus)); - -/**@brief Sets the power failure comparator threshold value. - * - * @note: Power failure comparator threshold setting. This setting applies both for normal voltage - * mode (supply connected to both VDD and VDDH) and high voltage mode (supply connected to - * VDDH only). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLD_SET, uint32_t, sd_power_pof_threshold_set(uint8_t threshold)); - -/**@brief Sets the power failure comparator threshold value for high voltage. - * - * @note: Power failure comparator threshold setting for high voltage mode (supply connected to - * VDDH only). This setting does not apply for normal voltage mode (supply connected to both - * VDD and VDDH). - * - * @param[in] threshold The power-fail threshold value to use, see @ref NRF_POWER_THRESHOLDVDDHS. - * - * @retval ::NRF_SUCCESS The power failure threshold was set. - * @retval ::NRF_ERROR_SOC_POWER_POF_THRESHOLD_UNKNOWN The power failure threshold is unknown. - */ -SVCALL(SD_POWER_POF_THRESHOLDVDDH_SET, uint32_t, sd_power_pof_thresholdvddh_set(uint8_t threshold)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERSET register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERSET register to write to. - * @param[in] ram_powerset Contains the word to write to the NRF_POWER->RAM[index].POWERSET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_SET, uint32_t, sd_power_ram_power_set(uint8_t index, uint32_t ram_powerset)); - -/**@brief Writes the NRF_POWER->RAM[index].POWERCLR register. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWERCLR register to write to. - * @param[in] ram_powerclr Contains the word to write to the NRF_POWER->RAM[index].POWERCLR register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_CLR, uint32_t, sd_power_ram_power_clr(uint8_t index, uint32_t ram_powerclr)); - -/**@brief Get contents of NRF_POWER->RAM[index].POWER register, indicates power status of RAM[index] blocks. - * - * @param[in] index Contains the index in the NRF_POWER->RAM[index].POWER register to read from. - * @param[out] p_ram_power Content of NRF_POWER->RAM[index].POWER register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_RAM_POWER_GET, uint32_t, sd_power_ram_power_get(uint8_t index, uint32_t *p_ram_power)); - -/**@brief Set bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be set in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_SET, uint32_t, sd_power_gpregret_set(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Clear bits in the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[in] gpregret_msk Bits to be clear in the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_CLR, uint32_t, sd_power_gpregret_clr(uint32_t gpregret_id, uint32_t gpregret_msk)); - -/**@brief Get contents of the general purpose retention registers (NRF_POWER->GPREGRET*). - * - * @param[in] gpregret_id 0 for GPREGRET, 1 for GPREGRET2. - * @param[out] p_gpregret Contents of the GPREGRET register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_POWER_GPREGRET_GET, uint32_t, sd_power_gpregret_get(uint32_t gpregret_id, uint32_t *p_gpregret)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 1 (REG1). - * - * @param[in] dcdc_mode The mode of the DCDC, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The DCDC mode is invalid. - */ -SVCALL(SD_POWER_DCDC_MODE_SET, uint32_t, sd_power_dcdc_mode_set(uint8_t dcdc_mode)); - -/**@brief Enable or disable the DC/DC regulator for the regulator stage 0 (REG0). - * - * For more details on the REG0 stage, please see product specification. - * - * @param[in] dcdc_mode The mode of the DCDC0, see @ref NRF_POWER_DCDC_MODES. - * - * @retval ::NRF_SUCCESS - * @retval ::NRF_ERROR_INVALID_PARAM The dcdc_mode is invalid. - */ -SVCALL(SD_POWER_DCDC0_MODE_SET, uint32_t, sd_power_dcdc0_mode_set(uint8_t dcdc_mode)); - -/**@brief Request the high frequency crystal oscillator. - * - * Will start the high frequency crystal oscillator, the startup time of the crystal varies - * and the ::sd_clock_hfclk_is_running function can be polled to check if it has started. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_release - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_REQUEST, uint32_t, sd_clock_hfclk_request(void)); - -/**@brief Releases the high frequency crystal oscillator. - * - * Will stop the high frequency crystal oscillator, this happens immediately. - * - * @see sd_clock_hfclk_is_running - * @see sd_clock_hfclk_request - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_RELEASE, uint32_t, sd_clock_hfclk_release(void)); - -/**@brief Checks if the high frequency crystal oscillator is running. - * - * @see sd_clock_hfclk_request - * @see sd_clock_hfclk_release - * - * @param[out] p_is_running 1 if the external crystal oscillator is running, 0 if not. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_CLOCK_HFCLK_IS_RUNNING, uint32_t, sd_clock_hfclk_is_running(uint32_t *p_is_running)); - -/**@brief Waits for an application event. - * - * An application event is either an application interrupt or a pended interrupt when the interrupt - * is disabled. - * - * When the application waits for an application event by calling this function, an interrupt that - * is enabled will be taken immediately on pending since this function will wait in thread mode, - * then the execution will return in the application's main thread. - * - * In order to wake up from disabled interrupts, the SEVONPEND flag has to be set in the Cortex-M - * MCU's System Control Register (SCR), CMSIS_SCB. In that case, when a disabled interrupt gets - * pended, this function will return to the application's main thread. - * - * @note The application must ensure that the pended flag is cleared using ::sd_nvic_ClearPendingIRQ - * in order to sleep using this function. This is only necessary for disabled interrupts, as - * the interrupt handler will clear the pending flag automatically for enabled interrupts. - * - * @note If an application interrupt has happened since the last time sd_app_evt_wait was - * called this function will return immediately and not go to sleep. This is to avoid race - * conditions that can occur when a flag is updated in the interrupt handler and processed - * in the main loop. - * - * @post An application interrupt has happened or a interrupt pending flag is set. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_APP_EVT_WAIT, uint32_t, sd_app_evt_wait(void)); - -/**@brief Get PPI channel enable register contents. - * - * @param[out] p_channel_enable The contents of the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_GET, uint32_t, sd_ppi_channel_enable_get(uint32_t *p_channel_enable)); - -/**@brief Set PPI channel enable register. - * - * @param[in] channel_enable_set_msk Mask containing the bits to set in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_SET, uint32_t, sd_ppi_channel_enable_set(uint32_t channel_enable_set_msk)); - -/**@brief Clear PPI channel enable register. - * - * @param[in] channel_enable_clr_msk Mask containing the bits to clear in the PPI CHEN register. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ENABLE_CLR, uint32_t, sd_ppi_channel_enable_clr(uint32_t channel_enable_clr_msk)); - -/**@brief Assign endpoints to a PPI channel. - * - * @param[in] channel_num Number of the PPI channel to assign. - * @param[in] evt_endpoint Event endpoint of the PPI channel. - * @param[in] task_endpoint Task endpoint of the PPI channel. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_CHANNEL The channel number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_CHANNEL_ASSIGN, uint32_t, - sd_ppi_channel_assign(uint8_t channel_num, const volatile void *evt_endpoint, const volatile void *task_endpoint)); - -/**@brief Task to enable a channel group. - * - * @param[in] group_num Number of the channel group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_ENABLE, uint32_t, sd_ppi_group_task_enable(uint8_t group_num)); - -/**@brief Task to disable a channel group. - * - * @param[in] group_num Number of the PPI group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_TASK_DISABLE, uint32_t, sd_ppi_group_task_disable(uint8_t group_num)); - -/**@brief Assign PPI channels to a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[in] channel_msk Mask of the channels to assign to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_ASSIGN, uint32_t, sd_ppi_group_assign(uint8_t group_num, uint32_t channel_msk)); - -/**@brief Gets the PPI channels of a channel group. - * - * @param[in] group_num Number of the channel group. - * @param[out] p_channel_msk Mask of the channels assigned to the group. - * - * @retval ::NRF_ERROR_SOC_PPI_INVALID_GROUP The group number is invalid. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_PPI_GROUP_GET, uint32_t, sd_ppi_group_get(uint8_t group_num, uint32_t *p_channel_msk)); - -/**@brief Configures the Radio Notification signal. - * - * @note - * - The notification signal latency depends on the interrupt priority settings of SWI used - * for notification signal. - * - To ensure that the radio notification signal behaves in a consistent way, the radio - * notifications must be configured when there is no protocol stack or other SoftDevice - * activity in progress. It is recommended that the radio notification signal is - * configured directly after the SoftDevice has been enabled. - * - In the period between the ACTIVE signal and the start of the Radio Event, the SoftDevice - * will interrupt the application to do Radio Event preparation. - * - Using the Radio Notification feature may limit the bandwidth, as the SoftDevice may have - * to shorten the connection events to have time for the Radio Notification signals. - * - * @param[in] type Type of notification signal, see @ref NRF_RADIO_NOTIFICATION_TYPES. - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE shall be used to turn off radio - * notification. Using @ref NRF_RADIO_NOTIFICATION_DISTANCE_NONE is - * recommended (but not required) to be used with - * @ref NRF_RADIO_NOTIFICATION_TYPE_NONE. - * - * @param[in] distance Distance between the notification signal and start of radio activity, see @ref - * NRF_RADIO_NOTIFICATION_DISTANCES. This parameter is ignored when @ref NRF_RADIO_NOTIFICATION_TYPE_NONE or - * @ref NRF_RADIO_NOTIFICATION_TYPE_INT_ON_INACTIVE is used. - * - * @retval ::NRF_ERROR_INVALID_PARAM The group number is invalid. - * @retval ::NRF_ERROR_INVALID_STATE A protocol stack or other SoftDevice is running. Stop all - * running activities and retry. - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_RADIO_NOTIFICATION_CFG_SET, uint32_t, sd_radio_notification_cfg_set(uint8_t type, uint8_t distance)); - -/**@brief Encrypts a block according to the specified parameters. - * - * 128-bit AES encryption. - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in, out] p_ecb_data Pointer to the ECB parameters' struct (two input - * parameters and one output parameter). - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCK_ENCRYPT, uint32_t, sd_ecb_block_encrypt(nrf_ecb_hal_data_t *p_ecb_data)); - -/**@brief Encrypts multiple data blocks provided as an array of data block structures. - * - * @details: Performs 128-bit AES encryption on multiple data blocks - * - * @note: - * - The application may set the SEVONPEND bit in the SCR to 1 to make the SoftDevice sleep while - * the ECB is running. The SEVONPEND bit should only be cleared (set to 0) from application - * main or low interrupt level. - * - * @param[in] block_count Count of blocks in the p_data_blocks array. - * @param[in,out] p_data_blocks Pointer to the first entry in a contiguous array of - * @ref nrf_ecb_hal_data_block_t structures. - * - * @retval ::NRF_SUCCESS - */ -SVCALL(SD_ECB_BLOCKS_ENCRYPT, uint32_t, sd_ecb_blocks_encrypt(uint8_t block_count, nrf_ecb_hal_data_block_t *p_data_blocks)); - -/**@brief Gets any pending events generated by the SoC API. - * - * The application should keep calling this function to get events, until ::NRF_ERROR_NOT_FOUND is returned. - * - * @param[out] p_evt_id Set to one of the values in @ref NRF_SOC_EVTS, if any events are pending. - * - * @retval ::NRF_SUCCESS An event was pending. The event id is written in the p_evt_id parameter. - * @retval ::NRF_ERROR_NOT_FOUND No pending events. - */ -SVCALL(SD_EVT_GET, uint32_t, sd_evt_get(uint32_t *p_evt_id)); - -/**@brief Get the temperature measured on the chip - * - * This function will block until the temperature measurement is done. - * It takes around 50 us from call to return. - * - * @param[out] p_temp Result of temperature measurement. Die temperature in 0.25 degrees Celsius. - * - * @retval ::NRF_SUCCESS A temperature measurement was done, and the temperature was written to temp - */ -SVCALL(SD_TEMP_GET, uint32_t, sd_temp_get(int32_t *p_temp)); - -/**@brief Flash Write - * - * Commands to write a buffer to flash - * - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * write has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - The data in the p_src buffer should not be modified before the @ref NRF_EVT_FLASH_OPERATION_SUCCESS - * or the @ref NRF_EVT_FLASH_OPERATION_ERROR have been received if the SoftDevice is enabled. - * - This call will make the SoftDevice trigger a hardfault when the page is written, if it is - * protected. - * - * - * @param[in] p_dst Pointer to start of flash location to be written. - * @param[in] p_src Pointer to buffer with data to be written. - * @param[in] size Number of 32-bit words to write. Maximum size is the number of words in one - * flash page. See the device's Product Specification for details. - * - * @retval ::NRF_ERROR_INVALID_ADDR Tried to write to a non existing flash address, or p_dst or p_src was unaligned. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_INVALID_LENGTH Size was 0, or higher than the maximum allowed size. - * @retval ::NRF_ERROR_FORBIDDEN Tried to write to an address outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_WRITE, uint32_t, sd_flash_write(uint32_t *p_dst, uint32_t const *p_src, uint32_t size)); - -/**@brief Flash Erase page - * - * Commands to erase a flash page - * If the SoftDevice is enabled: - * This call initiates the flash access command, and its completion will be communicated to the - * application with exactly one of the following events: - * - @ref NRF_EVT_FLASH_OPERATION_SUCCESS - The command was successfully completed. - * - @ref NRF_EVT_FLASH_OPERATION_ERROR - The command could not be started. - * - * If the SoftDevice is not enabled no event will be generated, and this call will return @ref NRF_SUCCESS when the - * erase has been completed - * - * @note - * - This call takes control over the radio and the CPU during flash erase and write to make sure that - * they will not interfere with the flash access. This means that all interrupts will be blocked - * for a predictable time (depending on the NVMC specification in the device's Product Specification - * and the command parameters). - * - This call will make the SoftDevice trigger a hardfault when the page is erased, if it is - * protected. - * - * - * @param[in] page_number Page number of the page to erase - * - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_ERROR_INVALID_ADDR Tried to erase to a non existing flash page. - * @retval ::NRF_ERROR_BUSY The previous command has not yet completed. - * @retval ::NRF_ERROR_FORBIDDEN Tried to erase a page outside the application flash area. - * @retval ::NRF_SUCCESS The command was accepted. - */ -SVCALL(SD_FLASH_PAGE_ERASE, uint32_t, sd_flash_page_erase(uint32_t page_number)); - -/**@brief Opens a session for radio timeslot requests. - * - * @note Only one session can be open at a time. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) will be called when the radio timeslot - * starts. From this point the NRF_RADIO and NRF_TIMER0 peripherals can be freely accessed - * by the application. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0) is called whenever the NRF_TIMER0 - * interrupt occurs. - * @note p_radio_signal_callback(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO) is called whenever the NRF_RADIO - * interrupt occurs. - * @note p_radio_signal_callback() will be called at ARM interrupt priority level 0. This - * implies that none of the sd_* API calls can be used from p_radio_signal_callback(). - * - * @param[in] p_radio_signal_callback The signal callback. - * - * @retval ::NRF_ERROR_INVALID_ADDR p_radio_signal_callback is an invalid function pointer. - * @retval ::NRF_ERROR_BUSY If session cannot be opened. - * @retval ::NRF_ERROR_INTERNAL If a new session could not be opened due to an internal error. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_OPEN, uint32_t, sd_radio_session_open(nrf_radio_signal_callback_t p_radio_signal_callback)); - -/**@brief Closes a session for radio timeslot requests. - * - * @note Any current radio timeslot will be finished before the session is closed. - * @note If a radio timeslot is scheduled when the session is closed, it will be canceled. - * @note The application cannot consider the session closed until the @ref NRF_EVT_RADIO_SESSION_CLOSED - * event is received. - * - * @retval ::NRF_ERROR_FORBIDDEN If session not opened. - * @retval ::NRF_ERROR_BUSY If session is currently being closed. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_SESSION_CLOSE, uint32_t, sd_radio_session_close(void)); - -/**@brief Requests a radio timeslot. - * - * @note The request type is determined by p_request->request_type, and can be one of @ref NRF_RADIO_REQ_TYPE_EARLIEST - * and @ref NRF_RADIO_REQ_TYPE_NORMAL. The first request in a session must always be of type @ref - * NRF_RADIO_REQ_TYPE_EARLIEST. - * @note For a normal request (@ref NRF_RADIO_REQ_TYPE_NORMAL), the start time of a radio timeslot is specified by - * p_request->distance_us and is given relative to the start of the previous timeslot. - * @note A too small p_request->distance_us will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note Timeslots scheduled too close will lead to a @ref NRF_EVT_RADIO_BLOCKED event. - * @note See the SoftDevice Specification for more on radio timeslot scheduling, distances and lengths. - * @note If an opportunity for the first radio timeslot is not found before 100 ms after the call to this - * function, it is not scheduled, and instead a @ref NRF_EVT_RADIO_BLOCKED event is sent. - * The application may then try to schedule the first radio timeslot again. - * @note Successful requests will result in nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START). - * Unsuccessful requests will result in a @ref NRF_EVT_RADIO_BLOCKED event, see @ref NRF_SOC_EVTS. - * @note The jitter in the start time of the radio timeslots is +/- @ref NRF_RADIO_START_JITTER_US us. - * @note The nrf_radio_signal_callback_t(@ref NRF_RADIO_CALLBACK_SIGNAL_TYPE_START) call has a latency relative to the - * specified radio timeslot start, but this does not affect the actual start time of the timeslot. - * @note NRF_TIMER0 is reset at the start of the radio timeslot, and is clocked at 1MHz from the high frequency - * (16 MHz) clock source. If p_request->hfclk_force_xtal is true, the high frequency clock is - * guaranteed to be clocked from the external crystal. - * @note The SoftDevice will neither access the NRF_RADIO peripheral nor the NRF_TIMER0 peripheral - * during the radio timeslot. - * - * @param[in] p_request Pointer to the request parameters. - * - * @retval ::NRF_ERROR_FORBIDDEN Either: - * - The session is not open. - * - The session is not IDLE. - * - This is the first request and its type is not @ref NRF_RADIO_REQ_TYPE_EARLIEST. - * - The request type was set to @ref NRF_RADIO_REQ_TYPE_NORMAL after a - * @ref NRF_RADIO_REQ_TYPE_EARLIEST request was blocked. - * @retval ::NRF_ERROR_INVALID_ADDR If the p_request pointer is invalid. - * @retval ::NRF_ERROR_INVALID_PARAM If the parameters of p_request are not valid. - * @retval ::NRF_SUCCESS Otherwise. - */ -SVCALL(SD_RADIO_REQUEST, uint32_t, sd_radio_request(nrf_radio_request_t const *p_request)); - -/**@brief Write register protected by the SoftDevice - * - * This function writes to a register that is write-protected by the SoftDevice. Please refer to your - * SoftDevice Specification for more details about which registers that are protected by SoftDevice. - * This function can write to the following protected peripheral: - * - ACL - * - * @note Protected registers may be read directly. - * @note Register that are write-once will return @ref NRF_SUCCESS on second set, even the value in - * the register has not changed. See the Product Specification for more details about register - * properties. - * - * @param[in] p_register Pointer to register to be written. - * @param[in] value Value to be written to the register. - * - * @retval ::NRF_ERROR_INVALID_ADDR This function can not write to the reguested register. - * @retval ::NRF_SUCCESS Value successfully written to register. - * - */ -SVCALL(SD_PROTECTED_REGISTER_WRITE, uint32_t, sd_protected_register_write(volatile uint32_t *p_register, uint32_t value)); - -/**@} */ - -#ifdef __cplusplus -} -#endif -#endif // NRF_SOC_H__ - -/**@} */ diff --git a/variants/wio-sdk-wm1110/softdevice/nrf_svc.h b/variants/wio-sdk-wm1110/softdevice/nrf_svc.h deleted file mode 100644 index 1de44656f..000000000 --- a/variants/wio-sdk-wm1110/softdevice/nrf_svc.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) Nordic Semiconductor ASA - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form, except as embedded into a Nordic - * Semiconductor ASA integrated circuit in a product or a software update for - * such product, must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * 4. This software, with or without modification, must only be used with a - * Nordic Semiconductor ASA integrated circuit. - * - * 5. Any software provided in binary form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * - * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef NRF_SVC__ -#define NRF_SVC__ - -#include "stdint.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @brief Supervisor call declaration. - * - * A call to a function marked with @ref SVCALL, will trigger a Supervisor Call (SVC) Exception. - * The SVCs with SVC numbers 0x00-0x0F are forwared to the application. All other SVCs are handled by the SoftDevice. - * - * @param[in] number The SVC number to be used. - * @param[in] return_type The return type of the SVC function. - * @param[in] signature Function signature. The function can have at most four arguments. - */ - -#ifdef SVCALL_AS_NORMAL_FUNCTION -#define SVCALL(number, return_type, signature) return_type signature -#else - -#ifndef SVCALL -#if defined(__CC_ARM) -#define SVCALL(number, return_type, signature) return_type __svc(number) signature -#elif defined(__GNUC__) -#ifdef __cplusplus -#define GCC_CAST_CPP (uint16_t) -#else -#define GCC_CAST_CPP -#endif -#define SVCALL(number, return_type, signature) \ - _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") __attribute__((naked)) \ - __attribute__((unused)) static return_type signature \ - { \ - __asm("svc %0\n" \ - "bx r14" \ - : \ - : "I"(GCC_CAST_CPP number) \ - : "r0"); \ - } \ - _Pragma("GCC diagnostic pop") - -#elif defined(__ICCARM__) -#define PRAGMA(x) _Pragma(#x) -#define SVCALL(number, return_type, signature) \ - PRAGMA(swi_number = (number)) \ - __swi return_type signature; -#else -#define SVCALL(number, return_type, signature) return_type signature -#endif -#endif // SVCALL - -#endif // SVCALL_AS_NORMAL_FUNCTION - -#ifdef __cplusplus -} -#endif -#endif // NRF_SVC__ diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h index 8f66b1f8c..8ad8c769a 100644 --- a/variants/wio-sdk-wm1110/variant.h +++ b/variants/wio-sdk-wm1110/variant.h @@ -107,8 +107,6 @@ extern "C" { #define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37 -#define NRF_USE_SERIAL_DFU - #ifdef __cplusplus } #endif diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index 5ecc414ad..03d7d047a 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -2,7 +2,6 @@ [env:wio-tracker-wm1110] extends = nrf52840_base board = wio-tracker-wm1110 -; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. @@ -12,4 +11,4 @@ lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/xiao_ble/platformio.ini b/variants/xiao_ble/platformio.ini index 6c47780d5..613fd3599 100644 --- a/variants/xiao_ble/platformio.ini +++ b/variants/xiao_ble/platformio.ini @@ -11,4 +11,4 @@ lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink +;upload_protocol = jlink \ No newline at end of file diff --git a/version.properties b/version.properties index c9336d539..268987418 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 3 -build = 16 +build = 14 From 8bd74588cef19f1641d69fd7960e4be757938a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 22 Jul 2024 15:42:46 +0200 Subject: [PATCH 0723/1377] bring back changes from #4248 --- boards/wio-tracker-wm1110.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index b4ab8db11..37a9186ab 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -23,7 +23,7 @@ "sd_flags": "-DS140", "sd_name": "s140", "sd_version": "7.3.0", - "sd_fwid": "0x00B6" + "sd_fwid": "0x0123" }, "bootloader": { "settings_addr": "0xFF000" From 5781149f8803f5dce2a67d5af9b4178c3ceb1c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 22 Jul 2024 15:46:15 +0200 Subject: [PATCH 0724/1377] *sigh* --- src/platform/nrf52/softdevice/nrf_sdm.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/nrf52/softdevice/nrf_sdm.h b/src/platform/nrf52/softdevice/nrf_sdm.h index 2786a86a4..02bf135b4 100644 --- a/src/platform/nrf52/softdevice/nrf_sdm.h +++ b/src/platform/nrf52/softdevice/nrf_sdm.h @@ -141,7 +141,7 @@ the start of the SoftDevice (without MBR)*/ * Add @ref MBR_SIZE to find the first available flash address when the SoftDevice is installed * just above the MBR (the usual case). */ -#define SD_FLASH_SIZE 0x26000 +#define SD_FLASH_SIZE 0x27000 /** @brief Defines a macro for retrieving the actual FWID value from a given base address. Use * @ref MBR_SIZE as the argument when the SoftDevice is installed just above the MBR (the usual @@ -377,4 +377,4 @@ SVCALL(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, uint32_t, sd_softdevice_vector_table /** @} -*/ +*/ \ No newline at end of file From 646f5ad26230e7913472ac7effa8db7681066367 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 09:34:19 -0500 Subject: [PATCH 0725/1377] [create-pull-request] automated change (#4316) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 10494bf32..7f90178f1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 10494bf328ac051fc4add9ddeb677eebf337b531 +Subproject commit 7f90178f183820e288aec41133144f30723228fe diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index dbe9281ec..841ca7aa4 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -176,6 +176,8 @@ typedef enum _meshtastic_HardwareModel { /* Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design, specifically adapted for the Meshtatic project */ meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69, + /* Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor */ + meshtastic_HardwareModel_SENSECAP_INDICATOR = 70, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 7568a3537243efa8b16f315a003388795a2d313f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 22 Jul 2024 17:09:46 +0200 Subject: [PATCH 0726/1377] fix build and probably break GPS --- src/gps/GPS.cpp | 28 ------------------- .../Telemetry/EnvironmentTelemetry.cpp | 4 +-- src/platform/nrf52/NRF52Bluetooth.cpp | 4 ++- 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 3fac7a56b..9628784d6 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1489,20 +1489,6 @@ bool GPS::lookForTime() #ifdef GNSS_Airoha // add by WayenWeng uint8_t fix = reader.fixQuality(); uint32_t now = millis(); - if (fix > 0) { - if (lastFixStartMsec > 0) { - if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { - return false; - } else { - clearBuffer(); - } - } else { - lastFixStartMsec = now; - return false; - } - } else { - return false; - } #endif auto ti = reader.time; @@ -1543,20 +1529,6 @@ bool GPS::lookForLocation() if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { uint8_t fix = reader.fixQuality(); uint32_t now = millis(); - if (fix > 0) { - if (lastFixStartMsec > 0) { - if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { - return false; - } else { - clearBuffer(); - } - } else { - lastFixStartMsec = now; - return false; - } - } else { - return false; - } } #endif diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ca3bb665e..92f90cfdd 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -293,7 +293,7 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m m->which_variant = meshtastic_Telemetry_environment_metrics_tag; #ifdef T1000X_SENSOR_EN // add by WayenWeng - valid = valid && t1000xSensor.getMetrics(&m); + valid = valid && t1000xSensor.getMetrics(m); hasSensor = true; #else if (dfRobotLarkSensor.hasSensor()) { @@ -566,4 +566,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule return result; } -#endif +#endif \ No newline at end of file diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 890886b4d..665e1402f 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -16,10 +16,12 @@ 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 +#endif -static BLEDfu bledfu; // DFU software update helper service // 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)]; From d8fd3f615de886a127c0b51b4eee9ba642168758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 22 Jul 2024 22:35:39 +0200 Subject: [PATCH 0727/1377] meesa jinxed it --- src/platform/nrf52/NRF52Bluetooth.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 665e1402f..81a165f2d 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -14,12 +14,12 @@ 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 BLEDis bledis; // DIS (Device Information Service) helper class instance -static BLEBas blebas; // BAS (Battery Service) helper class instance +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 +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 From 0d2a9b6282c9f2ddd792696a6575be3fe12935dd Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 23 Jul 2024 06:16:53 -0500 Subject: [PATCH 0728/1377] Fix de/compression buffer overflows in TAK packets (#4317) * Fix de/compression buffer overflows in TAK packets * Log message --- .semgrepignore | 2 +- .../compression/{unishox2.c => unishox2.cpp} | 19 +++- src/mesh/compression/unishox2.h | 76 ++++---------- src/modules/AtakPluginModule.cpp | 99 +++++++++++++------ src/modules/PositionModule.cpp | 8 +- 5 files changed, 108 insertions(+), 96 deletions(-) rename src/mesh/compression/{unishox2.c => unishox2.cpp} (98%) diff --git a/.semgrepignore b/.semgrepignore index 10fcb5f75..b4267ad23 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -1,2 +1,2 @@ .github/workflows/main_matrix.yml -src/mesh/compression/unishox2.c +src/mesh/compression/unishox2.cpp diff --git a/src/mesh/compression/unishox2.c b/src/mesh/compression/unishox2.cpp similarity index 98% rename from src/mesh/compression/unishox2.c rename to src/mesh/compression/unishox2.cpp index 99c62f659..fcb12a222 100644 --- a/src/mesh/compression/unishox2.c +++ b/src/mesh/compression/unishox2.cpp @@ -15,6 +15,7 @@ * * @author Arundale Ramanathan * + * Port for Particle (particle.io) / Aruino - Jonathan Greenblatt */ /** * @file unishox2.c @@ -36,6 +37,14 @@ /// uint8_t is unsigned char typedef unsigned char uint8_t; +const char *USX_FREQ_SEQ_DFLT[] = {"\": \"", "\": ", ""}; +const char *USX_FREQ_SEQ_XML[] = {"", "" \ - } -/// Frequently occurring sequences in XML content -#define USX_FREQ_SEQ_XML \ - (const char *[]) \ - { \ - "", "has_contact) { - auto length = unishox2_compress_simple(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign); + auto length = unishox2_compress_lines(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign, + sizeof(compressed.contact.callsign) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Compression overflowed contact.callsign. Reverting to uncompressed packet\n"); + return; + } LOG_DEBUG("Compressed callsign: %d bytes\n", length); - - length = unishox2_compress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign), - compressed.contact.device_callsign); + length = unishox2_compress_lines(t->contact.device_callsign, strlen(t->contact.device_callsign), + compressed.contact.device_callsign, sizeof(compressed.contact.device_callsign) - 1, + USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Compression overflowed contact.device_callsign. Reverting to uncompressed packet\n"); + return; + } LOG_DEBUG("Compressed device_callsign: %d bytes\n", length); } if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) { - auto length = unishox2_compress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), - compressed.payload_variant.chat.message); + auto length = unishox2_compress_lines(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), + compressed.payload_variant.chat.message, + sizeof(compressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Compression overflowed chat.message. Reverting to uncompressed packet\n"); + return; + } LOG_DEBUG("Compressed chat message: %d bytes\n", length); if (t->payload_variant.chat.has_to) { compressed.payload_variant.chat.has_to = true; - length = unishox2_compress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), - compressed.payload_variant.chat.to); + length = unishox2_compress_lines(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), + compressed.payload_variant.chat.to, + sizeof(compressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Compression overflowed chat.to. Reverting to uncompressed packet\n"); + return; + } LOG_DEBUG("Compressed chat to: %d bytes\n", length); } if (t->payload_variant.chat.has_to_callsign) { compressed.payload_variant.chat.has_to_callsign = true; - length = - unishox2_compress_simple(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), - compressed.payload_variant.chat.to_callsign); + length = unishox2_compress_lines(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), + compressed.payload_variant.chat.to_callsign, + sizeof(compressed.payload_variant.chat.to_callsign) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Compression overflowed chat.to_callsign. Reverting to uncompressed packet\n"); + return; + } LOG_DEBUG("Compressed chat to_callsign: %d bytes\n", length); } } @@ -102,7 +122,7 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast } else { if (!t->is_compressed) { // Not compressed. Something is wrong - LOG_ERROR("Received uncompressed TAKPacket over radio!\n"); + LOG_WARN("Received uncompressed TAKPacket over radio! Skipping\n"); return; } @@ -112,32 +132,55 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast uncompressed.is_compressed = false; if (t->has_contact) { auto length = - unishox2_decompress_simple(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign); - + unishox2_decompress_lines(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign, + sizeof(uncompressed.contact.callsign) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Decompression overflowed contact.callsign. Bailing out\n"); + return; + } LOG_DEBUG("Decompressed callsign: %d bytes\n", length); - length = unishox2_decompress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign), - uncompressed.contact.device_callsign); - + length = unishox2_decompress_lines(t->contact.device_callsign, strlen(t->contact.device_callsign), + uncompressed.contact.device_callsign, + sizeof(uncompressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Decompression overflowed contact.device_callsign. Bailing out\n"); + return; + } LOG_DEBUG("Decompressed device_callsign: %d bytes\n", length); } if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) { - auto length = unishox2_decompress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), - uncompressed.payload_variant.chat.message); + auto length = unishox2_decompress_lines(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), + uncompressed.payload_variant.chat.message, + sizeof(uncompressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Decompression overflowed chat.message. Bailing out\n"); + return; + } LOG_DEBUG("Decompressed chat message: %d bytes\n", length); if (t->payload_variant.chat.has_to) { uncompressed.payload_variant.chat.has_to = true; - length = unishox2_decompress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), - uncompressed.payload_variant.chat.to); + length = unishox2_decompress_lines(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), + uncompressed.payload_variant.chat.to, + sizeof(uncompressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Decompression overflowed chat.to. Bailing out\n"); + return; + } LOG_DEBUG("Decompressed chat to: %d bytes\n", length); } if (t->payload_variant.chat.has_to_callsign) { uncompressed.payload_variant.chat.has_to_callsign = true; length = - unishox2_decompress_simple(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), - uncompressed.payload_variant.chat.to_callsign); + unishox2_decompress_lines(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), + uncompressed.payload_variant.chat.to_callsign, + sizeof(uncompressed.payload_variant.chat.to_callsign) - 1, USX_PSET_DFLT, NULL); + if (length < 0) { + LOG_WARN("Decompression overflowed chat.to_callsign. Bailing out\n"); + return; + } LOG_DEBUG("Decompressed chat to_callsign: %d bytes\n", length); } } @@ -148,4 +191,4 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast service.sendToPhone(decompressedCopy); } return; -} \ No newline at end of file +} diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index b3294a866..228929e96 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -11,12 +11,12 @@ #include "configuration.h" #include "gps/GeoCoord.h" #include "main.h" +#include "mesh/compression/unishox2.h" #include "meshtastic/atak.pb.h" #include "sleep.h" #include "target_specific.h" extern "C" { -#include "mesh/compression/unishox2.h" #include } @@ -255,10 +255,12 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() .course = static_cast(localPosition.ground_track), }}}; - auto length = unishox2_compress_simple(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign); + auto length = unishox2_compress_lines(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign, + sizeof(takPacket.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); LOG_DEBUG("Uncompressed device_callsign '%s' - %d bytes\n", owner.long_name, strlen(owner.long_name)); LOG_DEBUG("Compressed device_callsign '%s' - %d bytes\n", takPacket.contact.device_callsign, length); - length = unishox2_compress_simple(owner.long_name, strlen(owner.long_name), takPacket.contact.callsign); + length = unishox2_compress_lines(owner.long_name, strlen(owner.long_name), takPacket.contact.callsign, + sizeof(takPacket.contact.callsign) - 1, USX_PSET_DFLT, NULL); mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_TAKPacket_msg, &takPacket); return mp; From 316928deb079d41d0d5806a1cf8bbe80ec0f85cb Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 23 Jul 2024 19:18:27 +0800 Subject: [PATCH 0729/1377] Cleanup GPS, add UC6580 autodetect (#4319) * Cleanup GPS, add UC6580 autodetect Our GPS code autodetects devices by default. Previously UC6580 was statically assigned, and had its own baudrate configuration inside the GPS code. This change adds autodetect functionality for the UC6580 and moves any 'special' GPS baud rate requirements for a variant out into the variant configuration. Thereby cleaning up core GPS code a little, saving the whales, and curing global warming. New Functionality: * If GPS_BAUDRATE is defined in variant.h, GPS autodetection will try that baudrate first. * UC6580 GPS chips are now automatically detected * Only run speedSelect skip the first time * Cleanup GPS, add UC6580 autodetect Our GPS code autodetects devices by default. Previously UC6580 was statically assigned, and had its own baudrate configuration inside the GPS code. This change adds autodetect functionality for the UC6580 and moves any 'special' GPS baud rate requirements for a variant out into the variant configuration. Thereby cleaning up core GPS code a little, saving the whales, and curing global warming. New Functionality: * If GPS_BAUDRATE is defined in variant.h, GPS autodetection will try that baudrate first. * UC6580 GPS chips are now automatically detected * Cleanup GPS, add UC6580 autodetect Our GPS code autodetects devices by default. Previously UC6580 was statically assigned, and had its own baudrate configuration inside the GPS code. This change adds autodetect functionality for the UC6580 and moves any 'special' GPS baud rate requirements for a variant out into the variant configuration. Thereby cleaning up core GPS code a little, saving the whales, and curing global warming. New Functionality: * If GPS_BAUDRATE is defined in variant.h, GPS autodetection will try that baudrate first. * UC6580 GPS chips are now automatically detected * Remove Airoha baud rate code It's no longer needed. --- src/configuration.h | 10 +++--- src/gps/GPS.cpp | 34 +++++++++---------- variants/heltec_wireless_tracker/variant.h | 1 + .../heltec_wireless_tracker_V1_0/variant.h | 1 + variants/tracksenger/internal/variant.h | 3 +- variants/tracksenger/lcd/variant.h | 3 +- variants/tracksenger/oled/variant.h | 3 +- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index aad4ac457..6351c35b1 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -171,10 +171,6 @@ along with this program. If not, see . // ----------------------------------------------------------------------------- // GPS // ----------------------------------------------------------------------------- -#ifndef GPS_BAUDRATE -#define GPS_BAUDRATE 9600 -#endif - #ifndef GPS_THREAD_INTERVAL #define GPS_THREAD_INTERVAL 200 #endif @@ -185,6 +181,10 @@ along with this program. If not, see . /* Step #1: offer chance for variant-specific defines */ #include "variant.h" +#ifndef GPS_BAUDRATE +#define GPS_BAUDRATE 9600 +#endif + /* Step #2: follow with defines common to the architecture; also enable HAS_ option not specifically disabled by variant.h */ #include "architecture.h" @@ -313,4 +313,4 @@ along with this program. If not, see . #endif #include "DebugConfiguration.h" -#include "RF95Configuration.h" \ No newline at end of file +#include "RF95Configuration.h" diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 9628784d6..f04b45622 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -400,14 +400,14 @@ bool GPS::setup() int msglen = 0; if (!didSerialInit) { -#ifdef GNSS_Airoha // change by WayenWeng - if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { - probe(GPS_BAUDRATE); - LOG_INFO("GPS setting to %d.\n", GPS_BAUDRATE); - } -#else -#if !defined(GPS_UC6580) + if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { + + // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate. + if (speedSelect == 0 && GPS_BAUDRATE != serialSpeeds[speedSelect]) { + speedSelect = std::find(serialSpeeds, std::end(serialSpeeds), GPS_BAUDRATE) - serialSpeeds; + } + LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]); gnssModel = probe(serialSpeeds[speedSelect]); if (gnssModel == GNSS_MODEL_UNKNOWN) { @@ -423,9 +423,6 @@ bool GPS::setup() } else { gnssModel = GNSS_MODEL_UNKNOWN; } -#else - gnssModel = GNSS_MODEL_UC6580; -#endif if (gnssModel == GNSS_MODEL_MTK) { /* @@ -777,7 +774,6 @@ bool GPS::setup() LOG_INFO("GNSS module configuration saved!\n"); } } -#endif // !GNSS_Airoha didSerialInit = true; } @@ -1191,6 +1187,15 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); delay(20); + // get version information from Unicore UFirebirdII Series + // Works for: UC6580, UM620, UM621, UM670A, UM680A, or UM681A + _serial_gps->write("$PDTINFO\r\n"); + delay(750); + if (getACK("UC6580", 500) == GNSS_RESPONSE_OK) { + LOG_INFO("UC6580 detected, using UC6580 Module\n"); + return GNSS_MODEL_UC6580; + } + // Get version information clearBuffer(); _serial_gps->write("$PCAS06,1*1A\r\n"); @@ -1398,13 +1403,6 @@ GPS *GPS::createGps() #else _serial_gps->begin(GPS_BAUDRATE); #endif - - /* - * T-Beam-S3-Core will be preset to use gps Probe here, and other boards will not be changed first - */ -#if defined(GPS_UC6580) - _serial_gps->updateBaudRate(115200); -#endif } return new_gps; } diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index f0ee0631d..685c9f079 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -52,6 +52,7 @@ #define GPS_RESET_MODE LOW #define GPS_UC6580 +#define GPS_BAUDRATE 115200 #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 1b4751a57..6b038dc28 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -49,6 +49,7 @@ #define GPS_RESET_MODE LOW #define GPS_UC6580 +#define GPS_BAUDRATE 11520 #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module diff --git a/variants/tracksenger/internal/variant.h b/variants/tracksenger/internal/variant.h index e63cecd7b..929c38793 100644 --- a/variants/tracksenger/internal/variant.h +++ b/variants/tracksenger/internal/variant.h @@ -48,6 +48,7 @@ #define GPS_RESET_MODE LOW #define GPS_UC6580 +#define GPS_BAUDRATE 115200 #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module @@ -87,4 +88,4 @@ { \ 26, 37, 17, 16, 15, 7 \ } -// #end keyboard \ No newline at end of file +// #end keyboard diff --git a/variants/tracksenger/lcd/variant.h b/variants/tracksenger/lcd/variant.h index 0f3423d52..3f952361b 100644 --- a/variants/tracksenger/lcd/variant.h +++ b/variants/tracksenger/lcd/variant.h @@ -72,6 +72,7 @@ #define GPS_RESET_MODE LOW #define GPS_UC6580 +#define GPS_BAUDRATE 115200 #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module @@ -111,4 +112,4 @@ { \ 26, 37, 17, 16, 15, 7 \ } -// #end keyboard \ No newline at end of file +// #end keyboard diff --git a/variants/tracksenger/oled/variant.h b/variants/tracksenger/oled/variant.h index d6bacf139..99f12bd23 100644 --- a/variants/tracksenger/oled/variant.h +++ b/variants/tracksenger/oled/variant.h @@ -50,6 +50,7 @@ #define GPS_RESET_MODE LOW #define GPS_UC6580 +#define GPS_BAUDRATE 115200 #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module @@ -89,4 +90,4 @@ { \ 26, 37, 17, 16, 15, 7 \ } -// #end keyboard \ No newline at end of file +// #end keyboard From 1d3ac57943b59d17c07468aed139ca84ac256240 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 23 Jul 2024 08:35:01 -0500 Subject: [PATCH 0730/1377] Fix type (#4323) --- variants/heltec_wireless_tracker_V1_0/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 6b038dc28..23987adf0 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -49,7 +49,7 @@ #define GPS_RESET_MODE LOW #define GPS_UC6580 -#define GPS_BAUDRATE 11520 +#define GPS_BAUDRATE 115200 #define USE_SX1262 #define LORA_DIO0 -1 // a No connect on the SX1262 module From e27375d331346f63da2662f3e0f92472e641dd86 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 23 Jul 2024 17:13:58 +0300 Subject: [PATCH 0731/1377] Set PIN_3V3_EN to HIGH (nrf52_promicro_diy variants) (#4321) * Update variant.cpp PIN 3v3 to HIGH * Update variant.cpp * Trunk fmt --- variants/diy/nrf52_promicro_diy_tcxo/variant.cpp | 9 ++++++++- variants/diy/nrf52_promicro_diy_xtal/variant.cpp | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp b/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp index 4030122e5..5869ed1d4 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp @@ -28,4 +28,11 @@ const uint32_t g_ADigitalPinMap[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; \ No newline at end of file + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.cpp b/variants/diy/nrf52_promicro_diy_xtal/variant.cpp index 4030122e5..5869ed1d4 100644 --- a/variants/diy/nrf52_promicro_diy_xtal/variant.cpp +++ b/variants/diy/nrf52_promicro_diy_xtal/variant.cpp @@ -28,4 +28,11 @@ const uint32_t g_ADigitalPinMap[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; \ No newline at end of file + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} From 300c3d32aa7d8103f473c5b7233fbbf3409879f9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 23 Jul 2024 11:52:14 -0500 Subject: [PATCH 0732/1377] Just a bit of security hygiene. (#4313) * Make sure to call randomSeed() on esp32 * Randomize the top 22 bits of the Message ID * Make it clear that we are not calling randomSeed() on purpose --------- Co-authored-by: Ben Meadors --- src/mesh/Router.cpp | 15 ++++++++------- src/platform/esp32/main-esp32.cpp | 4 ++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c8c18ae6d..35536e714 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -92,22 +92,23 @@ void Router::enqueueReceivedMessage(meshtastic_MeshPacket *p) // FIXME, move this someplace better PacketId generatePacketId() { - static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static uint32_t rollingPacketId; // Note: trying to keep this in noinit didn't help for working across reboots static bool didInit = false; - uint32_t numPacketId = UINT32_MAX; - if (!didInit) { didInit = true; // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) // Note: we mask the high order bit to ensure that we never pass a 'negative' number to random - i = random(numPacketId & 0x7fffffff); - LOG_DEBUG("Initial packet id %u, numPacketId %u\n", i, numPacketId); + rollingPacketId = random(UINT32_MAX & 0x7fffffff); + LOG_DEBUG("Initial packet id %u\n", rollingPacketId); } - i++; - PacketId id = (i % numPacketId) + 1; // return number between 1 and numPacketId (ie - never zero) + rollingPacketId++; + + rollingPacketId &= UINT32_MAX >> 22; // Mask out the top 22 bits + PacketId id = rollingPacketId | random(UINT32_MAX & 0x7fffffff) << 10; // top 22 bits + LOG_DEBUG("Partially randomized packet id %u\n", id); return id; } diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index aa51e810a..3910f718f 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -91,8 +91,12 @@ void enableSlowCLK() void esp32Setup() { + /* We explicitly don't want to do call randomSeed, + // as that triggers the esp32 core to use a less secure pseudorandom function. uint32_t seed = esp_random(); LOG_DEBUG("Setting random seed %u\n", seed); + randomSeed(seed); + */ LOG_DEBUG("Total heap: %d\n", ESP.getHeapSize()); LOG_DEBUG("Free heap: %d\n", ESP.getFreeHeap()); From 01e089fd07f766aec13705b7a1c3d2805f1f9d5e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 24 Jul 2024 08:23:04 -0500 Subject: [PATCH 0733/1377] Ignore invalid service envelopes (#4326) --- src/mqtt/MQTT.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 5f7d6d902..a7085dffe 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -135,6 +135,10 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); return; } else { + if (e.channel_id == NULL || e.gateway_id == NULL) { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } meshtastic_Channel ch = channels.getByName(e.channel_id); if (strcmp(e.gateway_id, owner.id) == 0) { // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. From a000a8d347d1040ecde038a0435e19c23c8c58b7 Mon Sep 17 00:00:00 2001 From: dylanli <167049793+Dylanliacc@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:10:38 +0800 Subject: [PATCH 0734/1377] Support Seeed Tracker-T1000-E (#4303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature-T1000-E: add Added the board definition for T1000-E - integrate a script for rapid dependency download that is compatible with both Linux and Windows platforms. - add the pin definitions for UART, SPI, GPIO, and other peripherals have been ensured to be correct. - add the env configuration for PlatformIO. * refact-T1000-E: redefine T1000-E board * feature-T1000-E: add basic sensors * feature-T1000-E: add button init * feat: add DRADIOLIB_GODMODE defination for use function setDioAsRfSwitch to DIO LORA RF * feat : add gps(GNSS_Airoha) sleep mode * feat: add behavier when rec or send message * chore: hang IIC bus usage to avoid sensor address conflict * feat: add sensor data acquisition * feat : support Airoha GPS - add disable it in FSM - update lookForTime and lookForLocation function * fix: fix a bug * version: change version to 0.9.0 * Update tracker-t1000-e.json Remove a space * Delete variants/tracker-t1000-e/run_once.sh Delete not need as we will change platformio.ini * Update platformio.ini Update SoftDevice 7.3.0 usage in line with other lr1110 targets Do we need to keep GODMODE ? * fix: Button behavier incorrect bug * fix:remove some invaild code of TextMessageModule * fix: remove invaild comment * version: change version to 0.9.1 - update mark's patch - remove some invaild code and comments - fix button behavier * trunk format * fix: HELTEC_CAPSULE_SENSOR_V3 block got accidentally deleted * fix: EnvironmentTelemetry upstream merge went awry. * fix: Added macro definitions to ensure correct operation of LORA section * fix :GNSS_AIROHA macro defination in line with others * fix: upstream backmerge accidentally. * fix: wrap macro PIN_3V3_EN BUZZER_EN_PIN GNSS_AIROHA in the TRACKER_T1000_E macro guard --------- Co-authored-by: Mark Trevor Birss Co-authored-by: Ben Meadors Co-authored-by: Thomas Göttgens --- boards/tracker-t1000-e.json | 58 +++++++ src/ButtonThread.cpp | 5 +- src/gps/GPS.cpp | 55 ++++++- src/gps/GPS.h | 2 +- src/mesh/LR11x0Interface.cpp | 8 +- .../Telemetry/EnvironmentTelemetry.cpp | 8 +- src/modules/Telemetry/Sensor/T1000xSensor.h | 1 - src/modules/TextMessageModule.cpp | 3 +- src/sleep.cpp | 19 +++ variants/tracker-t1000-e/platformio.ini | 16 ++ variants/tracker-t1000-e/variant.cpp | 64 ++++++++ variants/tracker-t1000-e/variant.h | 150 ++++++++++++++++++ variants/wio-t1000-s/variant.h | 2 +- 13 files changed, 373 insertions(+), 18 deletions(-) create mode 100644 boards/tracker-t1000-e.json create mode 100644 variants/tracker-t1000-e/platformio.ini create mode 100644 variants/tracker-t1000-e/variant.cpp create mode 100644 variants/tracker-t1000-e/variant.h diff --git a/boards/tracker-t1000-e.json b/boards/tracker-t1000-e.json new file mode 100644 index 000000000..2be716e22 --- /dev/null +++ b/boards/tracker-t1000-e.json @@ -0,0 +1,58 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v7.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "T1000-E-BOOT", + "mcu": "nrf52840", + "variant": "Seeed_T1000-E", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "7.3.0", + "sd_fwid": "0x0123" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Seeed T1000-E", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-E-for-Meshtastic-p-5913.html", + "vendor": "Seeed Studio" +} diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index a81518f31..d479de6ed 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -29,7 +29,6 @@ volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BU #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) OneButton ButtonThread::userButton; // Get reference to static member #endif - ButtonThread::ButtonThread() : OSThread("Button") { #if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) @@ -43,7 +42,7 @@ ButtonThread::ButtonThread() : OSThread("Button") int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin #if defined(HELTEC_CAPSULE_SENSOR_V3) this->userButton = OneButton(pin, false, false); -#elif defined(BUTTON_ACTIVE_LOW) // change by WayenWeng +#elif defined(BUTTON_ACTIVE_LOW) this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP); #else this->userButton = OneButton(pin, true, true); @@ -53,7 +52,7 @@ ButtonThread::ButtonThread() : OSThread("Button") #ifdef INPUT_PULLUP_SENSE // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did -#ifdef BUTTON_SENSE_TYPE // change by WayenWeng +#ifdef BUTTON_SENSE_TYPE pinMode(pin, BUTTON_SENSE_TYPE); #else pinMode(pin, INPUT_PULLUP_SENSE); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index f04b45622..494622dc6 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -787,7 +787,6 @@ GPS::~GPS() // we really should unregister our sleep observer notifyDeepSleepObserver.unobserve(¬ifyDeepSleep); } - // Put the GPS hardware into a specified state void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) { @@ -824,6 +823,11 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) setPowerPMU(false); // Power (PMU): off writePinStandby(true); // Standby (pin): asleep (not awake) setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed +#ifdef GNSS_AIROHA + if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) { + digitalWrite(PIN_GPS_EN, LOW); + } +#endif break; case GPS_OFF: @@ -833,6 +837,11 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) setPowerPMU(false); // Power (PMU): off writePinStandby(true); // Standby (pin): asleep setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely +#ifdef GNSS_AIROHA + if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) { + digitalWrite(PIN_GPS_EN, LOW); + } +#endif break; } } @@ -1171,7 +1180,8 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->updateBaudRate(serialSpeed); } #endif -#ifdef GNSS_Airoha // add by WayenWeng +#ifdef GNSS_AIROHA + return GNSS_MODEL_UNKNOWN; #else #ifdef GPS_DEBUG @@ -1484,11 +1494,25 @@ bool GPS::factoryReset() */ bool GPS::lookForTime() { -#ifdef GNSS_Airoha // add by WayenWeng + +#ifdef GNSS_AIROHA uint8_t fix = reader.fixQuality(); uint32_t now = millis(); + if (fix > 0) { + if (lastFixStartMsec > 0) { + if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { + return false; + } else { + clearBuffer(); + } + } else { + lastFixStartMsec = now; + return false; + } + } else { + return false; + } #endif - auto ti = reader.time; auto d = reader.date; if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed @@ -1523,13 +1547,26 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s */ bool GPS::lookForLocation() { -#ifdef GNSS_Airoha // add by WayenWeng +#ifdef GNSS_AIROHA if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { uint8_t fix = reader.fixQuality(); uint32_t now = millis(); + if (fix > 0) { + if (lastFixStartMsec > 0) { + if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { + return false; + } else { + clearBuffer(); + } + } else { + lastFixStartMsec = now; + return false; + } + } else { + return false; + } } #endif - // By default, TinyGPS++ does not parse GPGSA lines, which give us // the 2D/3D fixType (see NMEAGPS.h) // At a minimum, use the fixQuality indicator in GPGGA (FIXME?) @@ -1739,6 +1776,12 @@ void GPS::toggleGpsMode() if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; LOG_INFO("User toggled GpsMode. Now DISABLED.\n"); +#ifdef GNSS_AIROHA + if (powerState == GPS_ACTIVE) { + LOG_DEBUG("User power Off GPS\n"); + digitalWrite(PIN_GPS_EN, LOW); + } +#endif disable(); } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 7cbf771bc..87d03c592 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -69,7 +69,7 @@ class GPS : private concurrency::OSThread #endif private: const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600}; - + uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0; uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; uint32_t en_gpio = 0; diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index fc059ec16..1965eef89 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -100,7 +100,13 @@ template bool LR11x0Interface::init() // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option if (res == RADIOLIB_ERR_NONE) res = lora.setRegulatorDCDC(); - +#ifdef TRACKER_T1000_E +#ifdef LR11X0_DIO_RF_SWITCH_CONFIG + res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG); +#else + res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0); +#endif +#endif if (res == RADIOLIB_ERR_NONE) { if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate res = lora.setRxBoostedGainMode(true); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 92f90cfdd..fec1ee461 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -33,9 +33,7 @@ #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" -#ifdef T1000X_SENSOR_EN #include "Sensor/T1000xSensor.h" -#endif #include "Sensor/TSL2591Sensor.h" #include "Sensor/VEML7700Sensor.h" @@ -98,7 +96,7 @@ int32_t EnvironmentTelemetryModule::runOnce() LOG_INFO("Environment Telemetry: Initializing\n"); // it's possible to have this module enabled, only for displaying values on the screen. // therefore, we should only enable the sensor loop if measurement is also enabled -#ifdef T1000X_SENSOR_EN // add by WayenWeng +#ifdef T1000X_SENSOR_EN result = t1000xSensor.runOnce(); #else if (dfRobotLarkSensor.hasSensor()) @@ -420,7 +418,11 @@ meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; +#ifdef T1000X_SENSOR_EN + if (t1000xSensor.getMetrics(&m)) { +#else if (getEnvironmentTelemetry(&m)) { +#endif LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.h b/src/modules/Telemetry/Sensor/T1000xSensor.h index 127d2630c..a1c771cfa 100644 --- a/src/modules/Telemetry/Sensor/T1000xSensor.h +++ b/src/modules/Telemetry/Sensor/T1000xSensor.h @@ -7,7 +7,6 @@ class T1000xSensor : public TelemetrySensor { - private: protected: virtual void setup() override; diff --git a/src/modules/TextMessageModule.cpp b/src/modules/TextMessageModule.cpp index 0f86a6470..2933718af 100644 --- a/src/modules/TextMessageModule.cpp +++ b/src/modules/TextMessageModule.cpp @@ -2,8 +2,8 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "buzz.h" #include "configuration.h" - TextMessageModule *textMessageModule; ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp) @@ -12,7 +12,6 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp auto &p = mp.decoded; LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif - // We only store/display messages destined for us. // Keep a copy of the most recent text message. devicestate.rx_text_message = mp; diff --git a/src/sleep.cpp b/src/sleep.cpp index 3793ee0cf..4e685563a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -237,6 +237,25 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #ifdef PIN_POWER_EN pinMode(PIN_POWER_EN, INPUT); // power off peripherals // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); +#endif + +#ifdef TRACKER_T1000_E +#ifdef GNSS_AIROHA + digitalWrite(GPS_VRTC_EN, LOW); + digitalWrite(PIN_GPS_RESET, LOW); + digitalWrite(GPS_SLEEP_INT, LOW); + digitalWrite(GPS_RTC_INT, LOW); + pinMode(GPS_RESETB_OUT, OUTPUT); + digitalWrite(GPS_RESETB_OUT, LOW); +#endif + +#ifdef BUZZER_EN_PIN + digitalWrite(BUZZER_EN_PIN, LOW); +#endif + +#ifdef PIN_3V3_EN + digitalWrite(PIN_3V3_EN, LOW); +#endif #endif setLed(false); diff --git a/variants/tracker-t1000-e/platformio.ini b/variants/tracker-t1000-e/platformio.ini new file mode 100644 index 000000000..1db57ca29 --- /dev/null +++ b/variants/tracker-t1000-e/platformio.ini @@ -0,0 +1,16 @@ +; tracker-t1000-e v0.9.1 +[env:tracker-t1000-e] +extends = nrf52840_base +board = tracker-t1000-e +; board_level = extra +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E -DRADIOLIB_GODMODE + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/tracker-t1000-e> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = nrfutil diff --git a/variants/tracker-t1000-e/variant.cpp b/variants/tracker-t1000-e/variant.cpp new file mode 100644 index 000000000..85e0c44f3 --- /dev/null +++ b/variants/tracker-t1000-e/variant.cpp @@ -0,0 +1,64 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); + + pinMode(PIN_3V3_ACC_EN, OUTPUT); + digitalWrite(PIN_3V3_ACC_EN, LOW); + + pinMode(BUZZER_EN_PIN, OUTPUT); + digitalWrite(BUZZER_EN_PIN, HIGH); + + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, LOW); + + pinMode(GPS_VRTC_EN, OUTPUT); + digitalWrite(GPS_VRTC_EN, HIGH); + + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, LOW); + + pinMode(GPS_SLEEP_INT, OUTPUT); + digitalWrite(GPS_SLEEP_INT, HIGH); + + pinMode(GPS_RTC_INT, OUTPUT); + digitalWrite(GPS_RTC_INT, LOW); + + pinMode(GPS_RESETB_OUT, INPUT); +} \ No newline at end of file diff --git a/variants/tracker-t1000-e/variant.h b/variants/tracker-t1000-e/variant.h new file mode 100644 index 000000000..75d8ddffc --- /dev/null +++ b/variants/tracker-t1000-e/variant.h @@ -0,0 +1,150 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_TRACKER_T1000_E_ +#define _VARIANT_TRACKER_T1000_E_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +#define PIN_3V3_EN (32 + 6) // P1.6, Power to Sensors +#define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc + +#define PIN_LED1 (0 + 24) // P0.24 +#define LED_PIN PIN_LED1 +#define LED_BUILTIN -1 +#define LED_BLUE -1 // Actually green +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN (0 + 6) // P0.6 +#define BUTTON_ACTIVE_LOW false +#define BUTTON_ACTIVE_PULLUP false +#define BUTTON_SENSE_TYPE 0x6 + +#define HAS_WIRE 1 + +#define WIRE_INTERFACES_COUNT 1 + +// unused pins +#define PIN_WIRE_SDA (0 + 9) // P0.26 +#define PIN_WIRE_SCL (0 + 10) // P0.27 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 14) // P0.14 +#define PIN_SERIAL1_TX (0 + 13) // P0.13 + +#define PIN_SERIAL2_RX (0 + 17) // P0.17 +#define PIN_SERIAL2_TX (0 + 16) // P0.16 + +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (32 + 8) // P1.08 +#define PIN_SPI_MOSI (32 + 9) // P1.09 +#define PIN_SPI_SCK (0 + 11) // P0.11 +#define PIN_SPI_NSS (0 + 12) // P0.12 + +#define LORA_RESET (32 + 10) // P1.10 // RST +#define LORA_DIO1 (32 + 1) // P1.01 // IRQ +#define LORA_DIO2 (0 + 7) // P0.07 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +// supported modules list +#define USE_LR1110 + +#define LR1110_IRQ_PIN LORA_DIO1 +#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO2 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.6 +#define LR11X0_DIO_AS_RF_SWITCH +#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 + +#define HAS_GPS 1 +#define GNSS_AIROHA +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +#define GPS_BAUDRATE 115200 + +#define PIN_GPS_EN (32 + 11) // P1.11 +#define GPS_EN_ACTIVE HIGH + +#define PIN_GPS_RESET (32 + 15) // P1.15 +#define GPS_RESET_MODE HIGH + +#define GPS_VRTC_EN (0 + 8) // P0.8, awlays high +#define GPS_SLEEP_INT (32 + 12) // P1.12, awlays high +#define GPS_RTC_INT (0 + 15) // P0.15, normal is LOW, wake by HIGH +#define GPS_RESETB_OUT (32 + 14) // P1.14, awlays input pull_up + +#define GPS_FIX_HOLD_TIME 15000 // ms +#define BATTERY_PIN 2 +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 + +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 + +// Buzzer +#define BUZZER_EN_PIN (32 + 5) // P1.05, awlays high +#define PIN_BUZZER (0 + 25) // P0.25, pwm output + +#define T1000X_SENSOR_EN +#define T1000X_VCC_PIN (0 + 4) // P0.4 +#define T1000X_NTC_PIN (0 + 31) // P0.31 +#define T1000X_LUX_PIN (0 + 29) // P0.29 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_TRACKER_T1000_E_ diff --git a/variants/wio-t1000-s/variant.h b/variants/wio-t1000-s/variant.h index 86bd34f62..fa6ea4abc 100644 --- a/variants/wio-t1000-s/variant.h +++ b/variants/wio-t1000-s/variant.h @@ -106,7 +106,7 @@ extern "C" { #define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 #define HAS_GPS 1 -#define GNSS_Airoha +#define GNSS_AIROHA #define GPS_RX_PIN PIN_SERIAL1_RX #define GPS_TX_PIN PIN_SERIAL1_TX From c5f2d2736d77f80652963621b621a0c85cdab810 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 24 Jul 2024 21:14:58 -0500 Subject: [PATCH 0735/1377] Whitespace trunk grousing --- src/ButtonThread.cpp | 2 +- src/gps/GPS.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index d479de6ed..914ff8e06 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -42,7 +42,7 @@ ButtonThread::ButtonThread() : OSThread("Button") int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin #if defined(HELTEC_CAPSULE_SENSOR_V3) this->userButton = OneButton(pin, false, false); -#elif defined(BUTTON_ACTIVE_LOW) +#elif defined(BUTTON_ACTIVE_LOW) this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP); #else this->userButton = OneButton(pin, true, true); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 494622dc6..aec3d595d 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1180,7 +1180,7 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->updateBaudRate(serialSpeed); } #endif -#ifdef GNSS_AIROHA +#ifdef GNSS_AIROHA return GNSS_MODEL_UNKNOWN; #else From 1481ce987e405c8856f0b7aa39f98ecd2c48dd1c Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 25 Jul 2024 20:05:03 +0200 Subject: [PATCH 0736/1377] Fix T1000-E GPS - some changes went missing from #4303? (#4328) * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp * Update GPS.cpp --- src/gps/GPS.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index aec3d595d..815eb9f0f 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -400,9 +400,16 @@ bool GPS::setup() int msglen = 0; if (!didSerialInit) { +#ifdef GNSS_AIROHA + if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { + probe(GPS_BAUDRATE); + LOG_INFO("GPS setting to %d.\n", GPS_BAUDRATE); + } +#else +#if !defined(GPS_UC6580) if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { - + // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate. if (speedSelect == 0 && GPS_BAUDRATE != serialSpeeds[speedSelect]) { speedSelect = std::find(serialSpeeds, std::end(serialSpeeds), GPS_BAUDRATE) - serialSpeeds; @@ -423,6 +430,9 @@ bool GPS::setup() } else { gnssModel = GNSS_MODEL_UNKNOWN; } +#else + gnssModel = GNSS_MODEL_UC6580; +#endif if (gnssModel == GNSS_MODEL_MTK) { /* @@ -774,6 +784,7 @@ bool GPS::setup() LOG_INFO("GNSS module configuration saved!\n"); } } +#endif didSerialInit = true; } @@ -1789,4 +1800,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS From 7ac64bd7626c389f405ae1c45599f71a6fdf77df Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 25 Jul 2024 13:09:28 -0500 Subject: [PATCH 0737/1377] Trunk --- src/gps/GPS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 815eb9f0f..c50bc7b41 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -409,7 +409,7 @@ bool GPS::setup() #if !defined(GPS_UC6580) if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { - + // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate. if (speedSelect == 0 && GPS_BAUDRATE != serialSpeeds[speedSelect]) { speedSelect = std::find(serialSpeeds, std::end(serialSpeeds), GPS_BAUDRATE) - serialSpeeds; From 4b0bbb8af13a67f92e6e1a353b0b8d871b279cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 26 Jul 2024 03:16:21 +0200 Subject: [PATCH 0738/1377] Make STM compile again and update toolchain. (#2960) * Make STM compile again and update toolchain. The binary is too big for the flash. WIP * Making progress with OSFS, still WIP * more progress, still too big. Adding RAK3172 to the equasion * Make STM compile again and update toolchain. The binary is too big for the flash. WIP * Making progress with OSFS, still WIP * more progress, still too big. Adding RAK3172 to the equasion * still too big * minimize build * trunk fmt * fix a couple of symbol clashes * trunk fmt * down to 101% with a release vs. debug build and omitting the flash strings * fix compilation * fix compilation once more * update protobufs linkage * - Toolchain updated - Fixed macro error * silence compiler warning note: do something about this assert... * new toolkit and fix Power.cpp * STM32WL make it fit (#4330) * Add option to exclude I2C parts The I2C hals and related code uses a significant amount of flash space and aren't required for a basic node. * Add option to disable Admin and NodeInfo modules Disabled by default in minimal build. This saves a significant amount of flash * Disable unused hals These use up significant flash * Add float support for printf for debugging Makes serial look nice for debugging * This breaks my build for some reason * These build flags can save a bit of flash * Don't disable NodeInfo and Admin modules in minimal build They fit in flash * Don't include printf float support by default Only useful for debugging --------- Co-authored-by: Adam Lawson --------- Co-authored-by: Ben Meadors Co-authored-by: Adam Lawson --- arch/stm32/stm32.ini | 36 ++ arch/stm32/stm32wl5e.ini | 28 -- ...generic_wl5e.json => wiscore_rak3172.json} | 3 + src/FSCommon.cpp | 60 ++- src/FSCommon.h | 11 +- src/Power.cpp | 5 +- src/configuration.h | 1 + src/detect/ScanI2CTwoWire.cpp | 4 +- src/detect/ScanI2CTwoWire.h | 6 +- src/freertosinc.h | 2 +- src/gps/GPS.cpp | 4 +- src/gps/GeoCoord.cpp | 4 +- src/gps/GeoCoord.h | 4 +- src/main.cpp | 15 +- src/mesh/NodeDB.cpp | 16 +- src/mesh/NodeDB.h | 2 +- src/mesh/PhoneAPI.cpp | 8 + src/mesh/STM32WLE5JCInterface.h | 2 +- src/mesh/mesh-pb-constants.cpp | 6 +- src/modules/AdminModule.cpp | 2 + src/modules/CannedMessageModule.cpp | 2 +- src/modules/ExternalNotificationModule.cpp | 2 +- src/modules/Modules.cpp | 10 + src/platform/stm32wl/InternalFileSystem.cpp | 141 ------- src/platform/stm32wl/InternalFileSystem.h | 54 --- src/platform/stm32wl/LittleFS.cpp | 258 ------------- src/platform/stm32wl/LittleFS.h | 85 ---- src/platform/stm32wl/LittleFS_File.cpp | 362 ------------------ src/platform/stm32wl/LittleFS_File.h | 82 ---- src/platform/stm32wl/STM32WLCryptoEngine.cpp | 47 ++- src/platform/stm32wl/main-stm32wl.cpp | 8 - src/xmodem.cpp | 5 +- src/xmodem.h | 3 + variants/rak3172/platformio.ini | 12 + variants/rak3172/variant.h | 12 + variants/wio-e5/platformio.ini | 29 +- variants/wio-e5/variant.h | 1 + 37 files changed, 271 insertions(+), 1061 deletions(-) create mode 100644 arch/stm32/stm32.ini delete mode 100644 arch/stm32/stm32wl5e.ini rename boards/{generic_wl5e.json => wiscore_rak3172.json} (91%) delete mode 100644 src/platform/stm32wl/InternalFileSystem.cpp delete mode 100644 src/platform/stm32wl/InternalFileSystem.h delete mode 100644 src/platform/stm32wl/LittleFS.cpp delete mode 100644 src/platform/stm32wl/LittleFS.h delete mode 100644 src/platform/stm32wl/LittleFS_File.cpp delete mode 100644 src/platform/stm32wl/LittleFS_File.h create mode 100644 variants/rak3172/platformio.ini create mode 100644 variants/rak3172/variant.h diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini new file mode 100644 index 000000000..2cea4bbc5 --- /dev/null +++ b/arch/stm32/stm32.ini @@ -0,0 +1,36 @@ +[stm32_base] +extends = arduino_base +platform = ststm32 +platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#361a7fdb67e2a7104e99b4f42a802469eef8b129 + +build_type = release + +;board_build.flash_offset = 0x08000000 + +build_flags = + ${arduino_base.build_flags} + -flto + -Isrc/platform/stm32wl -g + -DMESHTASTIC_MINIMIZE_BUILD + -DDEBUG_MUTE +; -DVECT_TAB_OFFSET=0x08000000 + -DconfigUSE_CMSIS_RTOS_V2=1 +; -DSPI_MODE_0=SPI_MODE0 + -fmerge-all-constants + -ffunction-sections + -fdata-sections + +build_src_filter = + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - + +board_upload.offset_address = 0x08000000 +upload_protocol = stlink + +lib_deps = + ${env.lib_deps} + charlesbaynham/OSFS@^1.2.3 + https://github.com/caveman99/Crypto.git#f61ae26a53f7a2d0ba5511625b8bf8eff3a35d5e + +lib_ignore = + mathertel/OneButton + Wire \ No newline at end of file diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini deleted file mode 100644 index 4d74ade8f..000000000 --- a/arch/stm32/stm32wl5e.ini +++ /dev/null @@ -1,28 +0,0 @@ -[stm32wl5e_base] -platform_packages = platformio/framework-arduinoststm32 @ https://github.com/stm32duino/Arduino_Core_STM32.git#6e3f9910d0122e82a6c3438507dfac3d2fd80a39 -platform = ststm32 -board = generic_wl5e -framework = arduino - -build_type = debug - -build_flags = - ${arduino_base.build_flags} - -Isrc/platform/stm32wl -g - -DconfigUSE_CMSIS_RTOS_V2=1 - -DVECT_TAB_OFFSET=0x08000000 - -build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - - - -board_upload.offset_address = 0x08000000 -upload_protocol = stlink - -lib_deps = - ${env.lib_deps} - https://github.com/kokke/tiny-AES-c.git#f06ac37fc31dfdaca2e0d9bec83f90d5663c319b - https://github.com/littlefs-project/littlefs.git#v2.5.1 - https://github.com/stm32duino/STM32FreeRTOS.git#10.3.1 - -lib_ignore = - mathertel/OneButton \ No newline at end of file diff --git a/boards/generic_wl5e.json b/boards/wiscore_rak3172.json similarity index 91% rename from boards/generic_wl5e.json rename to boards/wiscore_rak3172.json index 5c4bc24a7..714e09115 100644 --- a/boards/generic_wl5e.json +++ b/boards/wiscore_rak3172.json @@ -1,5 +1,8 @@ { "build": { + "arduino": { + "variant_h": "variant_RAK3172_MODULE.h" + }, "core": "stm32", "cpu": "cortex-m4", "extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX", diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 7d3788c4d..3017ec085 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -24,6 +24,30 @@ SPIClass SPI1(HSPI); #endif // HAS_SDCARD +#if defined(ARCH_STM32WL) + +uint16_t OSFS::startOfEEPROM = 1; +uint16_t OSFS::endOfEEPROM = 2048; + +// 3) How do I read from the medium? +void OSFS::readNBytes(uint16_t address, unsigned int num, byte *output) +{ + for (uint16_t i = address; i < address + num; i++) { + *output = EEPROM.read(i); + output++; + } +} + +// 4) How to I write to the medium? +void OSFS::writeNBytes(uint16_t address, unsigned int num, const byte *input) +{ + for (uint16_t i = address; i < address + num; i++) { + EEPROM.update(i, *input); + input++; + } +} +#endif + /** * @brief Copies a file from one location to another. * @@ -33,7 +57,33 @@ SPIClass SPI1(HSPI); */ bool copyFile(const char *from, const char *to) { -#ifdef FSCom +#ifdef ARCH_STM32WL + unsigned char cbuffer[2048]; + + // Var to hold the result of actions + OSFS::result r; + + r = OSFS::getFile(from, cbuffer); + + if (r == notfound) { + LOG_ERROR("Failed to open source file %s\n", from); + return false; + } else if (r == noerr) { + r = OSFS::newFile(to, cbuffer, true); + if (r == noerr) { + return true; + } else { + LOG_ERROR("OSFS Error %d\n", r); + return false; + } + + } else { + LOG_ERROR("OSFS Error %d\n", r); + return false; + } + return true; + +#elif defined(FSCom) unsigned char cbuffer[16]; File f1 = FSCom.open(from, FILE_O_READ); @@ -70,7 +120,13 @@ bool copyFile(const char *from, const char *to) */ bool renameFile(const char *pathFrom, const char *pathTo) { -#ifdef FSCom +#ifdef ARCH_STM32WL + if (copyFile(pathFrom, pathTo) && (OSFS::deleteFile(pathFrom) == OSFS::result::NO_ERROR)) { + return true; + } else { + return false; + } +#elif defined(FSCom) #ifdef ARCH_ESP32 // rename was fixed for ESP32 IDF LittleFS in April return FSCom.rename(pathFrom, pathTo); diff --git a/src/FSCommon.h b/src/FSCommon.h index 8fbabd952..3d485d1b1 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -15,10 +15,13 @@ #endif #if defined(ARCH_STM32WL) -#include "platform/stm32wl/InternalFileSystem.h" // STM32WL version -#define FSCom InternalFS -#define FSBegin() FSCom.begin() -using namespace LittleFS_Namespace; +// STM32WL series 2 Kbytes (8 rows of 256 bytes) +#include +#include + +// Useful consts +const OSFS::result noerr = OSFS::result::NO_ERROR; +const OSFS::result notfound = OSFS::result::FILE_NOT_FOUND; #endif #if defined(ARCH_RP2040) diff --git a/src/Power.cpp b/src/Power.cpp index 19c5c9937..138b06e71 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -200,7 +200,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(HAS_PMU) && \ + !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (hasINA()) { LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address); return getINAVoltage(); @@ -420,7 +421,7 @@ class AnalogBatteryLevel : public HasBatteryLevel } #endif -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) uint16_t getINAVoltage() { if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) { diff --git a/src/configuration.h b/src/configuration.h index 6351c35b1..b298d5424 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -259,6 +259,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_SCREEN 1 #define MESHTASTIC_EXCLUDE_MQTT 1 #define MESHTASTIC_EXCLUDE_POWERMON 1 +#define MESHTASTIC_EXCLUDE_I2C 1 #endif // Turn off all optional modules diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 8738e2722..b831b0e71 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -1,7 +1,8 @@ #include "ScanI2CTwoWire.h" +#if !MESHTASTIC_EXCLUDE_I2C + #include "concurrency/LockGuard.h" -#include "configuration.h" #if defined(ARCH_PORTDUINO) #include "linux/LinuxHardwareI2C.h" #endif @@ -403,3 +404,4 @@ size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); } +#endif \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 82b48f6b4..c8dd96469 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -1,5 +1,8 @@ #pragma once +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_I2C + #include #include #include @@ -55,4 +58,5 @@ class ScanI2CTwoWire : public ScanI2C uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const; DeviceType probeOLED(ScanI2C::DeviceAddress) const; -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/freertosinc.h b/src/freertosinc.h index 166054241..e9e6cd53a 100644 --- a/src/freertosinc.h +++ b/src/freertosinc.h @@ -12,7 +12,7 @@ #include #endif -#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_RP2040) +#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_RP2040) #define HAS_FREE_RTOS #include diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index c50bc7b41..67f6adb98 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1182,7 +1182,7 @@ int GPS::prepareDeepSleep(void *unused) GnssModel_t GPS::probe(int serialSpeed) { -#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) +#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) _serial_gps->end(); _serial_gps->begin(serialSpeed); #else @@ -1270,7 +1270,7 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->write(_message_prt, sizeof(_message_prt)); delay(500); serialSpeed = 9600; -#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) +#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) _serial_gps->end(); _serial_gps->begin(serialSpeed); #else diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index 2224bd281..5abb25a06 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -493,7 +493,7 @@ std::shared_ptr GeoCoord::pointAtDistance(double bearing, double range * The bearing in string format * @return Bearing in degrees */ -uint GeoCoord::bearingToDegrees(const char *bearing) +unsigned int GeoCoord::bearingToDegrees(const char *bearing) { if (strcmp(bearing, "N") == 0) return 0; @@ -537,7 +537,7 @@ uint GeoCoord::bearingToDegrees(const char *bearing) * The bearing in degrees * @return Bearing in string format */ -const char *GeoCoord::degreesToBearing(uint degrees) +const char *GeoCoord::degreesToBearing(unsigned int degrees) { if (degrees >= 348 || degrees < 11) return "N"; diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index b02d12afb..ecdaf0ec7 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -117,8 +117,8 @@ class GeoCoord static float bearing(double lat1, double lon1, double lat2, double lon2); static float rangeRadiansToMeters(double range_radians); static float rangeMetersToRadians(double range_meters); - static uint bearingToDegrees(const char *bearing); - static const char *degreesToBearing(uint degrees); + static unsigned int bearingToDegrees(const char *bearing); + static const char *degreesToBearing(unsigned int degrees); // Point to point conversions int32_t distanceTo(const GeoCoord &pointB); diff --git a/src/main.cpp b/src/main.cpp index 187942344..dc5863bbc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,11 @@ #include "concurrency/OSThread.h" #include "concurrency/Periodic.h" #include "detect/ScanI2C.h" + +#if !MESHTASTIC_EXCLUDE_I2C #include "detect/ScanI2CTwoWire.h" +#include +#endif #include "detect/axpDebug.h" #include "detect/einkScan.h" #include "graphics/RAKled.h" @@ -31,7 +35,6 @@ #include "shutdown.h" #include "sleep.h" #include "target_specific.h" -#include #include #include // #include @@ -159,8 +162,10 @@ bool pauseBluetoothLogging = false; bool pmu_found; +#if !MESHTASTIC_EXCLUDE_I2C // Array map of sensor types with i2c address and wire as we'll find in the i2c scan std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1] = {}; +#endif Router *router = NULL; // Users of router don't care what sort of subclass implements that API @@ -349,6 +354,7 @@ void setup() #endif +#if !MESHTASTIC_EXCLUDE_I2C #if defined(I2C_SDA1) && defined(ARCH_RP2040) Wire1.setSDA(I2C_SDA1); Wire1.setSCL(I2C_SCL1); @@ -373,6 +379,7 @@ void setup() #elif HAS_WIRE Wire.begin(); #endif +#endif #ifdef PIN_LCD_RESET // FIXME - move this someplace better, LCD is at address 0x3F @@ -405,6 +412,7 @@ void setup() powerStatus->observe(&power->newStatus); power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration +#if !MESHTASTIC_EXCLUDE_I2C // We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to // accessories auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); @@ -560,6 +568,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) i2cScanner.reset(); +#endif #ifdef HAS_SDCARD setupSDCard(); @@ -620,6 +629,7 @@ void setup() screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 #endif +#if !MESHTASTIC_EXCLUDE_I2C #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (acc_info.type != ScanI2C::DeviceType::NONE) { config.display.wake_on_tap_or_motion = true; @@ -635,6 +645,7 @@ void setup() ambientLightingThread = new AmbientLightingThread(rgb_found.type); } #endif +#endif #ifdef T_WATCH_S3 drv.begin(); @@ -721,6 +732,7 @@ void setup() RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_AXP192); // Record a hardware fault for missing hardware #endif +#if !MESHTASTIC_EXCLUDE_I2C // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ @@ -733,6 +745,7 @@ void setup() #else if (screen_found.port != ScanI2C::I2CPort::NO_I2C) screen->setup(); +#endif #endif screen->print("Started...\n"); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index c0bed3437..4257837b3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -192,9 +192,11 @@ bool NodeDB::factoryReset() LOG_INFO("Performing factory reset!\n"); // first, remove the "/prefs" (this removes most prefs) rmDir("/prefs"); +#ifdef FSCom if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) { LOG_ERROR("Could not remove rangetest.csv file\n"); } +#endif // second, install default state (this will deal with the duplicate mac address issue) installDefaultDeviceState(); installDefaultConfig(); @@ -574,7 +576,7 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t state = LoadFileResult::DECODE_FAILED; } else { LOG_INFO("Loaded %s successfully\n", filename); - state = LoadFileResult::SUCCESS; + state = LoadFileResult::LOAD_SUCCESS; } f.close(); } else { @@ -582,7 +584,7 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t } #else LOG_ERROR("ERROR: Filesystem not implemented\n"); - state = LoadFileState::NO_FILESYSTEM; + state = LoadFileResult::NO_FILESYSTEM; #endif return state; } @@ -593,7 +595,7 @@ void NodeDB::loadFromDisk() auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate); - if (state != LoadFileResult::SUCCESS) { + if (state != LoadFileResult::LOAD_SUCCESS) { installDefaultDeviceState(); // Our in RAM copy might now be corrupt } else { if (devicestate.version < DEVICESTATE_MIN_VER) { @@ -610,7 +612,7 @@ void NodeDB::loadFromDisk() state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, &config); - if (state != LoadFileResult::SUCCESS) { + if (state != LoadFileResult::LOAD_SUCCESS) { installDefaultConfig(); // Our in RAM copy might now be corrupt } else { if (config.version < DEVICESTATE_MIN_VER) { @@ -623,7 +625,7 @@ void NodeDB::loadFromDisk() state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), &meshtastic_LocalModuleConfig_msg, &moduleConfig); - if (state != LoadFileResult::SUCCESS) { + if (state != LoadFileResult::LOAD_SUCCESS) { installDefaultModuleConfig(); // Our in RAM copy might now be corrupt } else { if (moduleConfig.version < DEVICESTATE_MIN_VER) { @@ -636,7 +638,7 @@ void NodeDB::loadFromDisk() state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg, &channelFile); - if (state != LoadFileResult::SUCCESS) { + if (state != LoadFileResult::LOAD_SUCCESS) { installDefaultChannels(); // Our in RAM copy might now be corrupt } else { if (channelFile.version < DEVICESTATE_MIN_VER) { @@ -648,7 +650,7 @@ void NodeDB::loadFromDisk() } state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore); - if (state == LoadFileResult::SUCCESS) { + if (state == LoadFileResult::LOAD_SUCCESS) { LOG_INFO("Loaded OEMStore\n"); } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 5207d8629..258b7276d 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -40,7 +40,7 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p); enum LoadFileResult { // Successfully opened the file - SUCCESS = 1, + LOAD_SUCCESS = 1, // File does not exist NOT_FOUND = 2, // Device does not have a filesystem diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 0b63b4a58..f0775741a 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -42,7 +42,9 @@ void PhoneAPI::handleStartConfig() if (!isConnected()) { onConnectionChanged(true); observe(&service.fromNumChanged); +#ifdef FSCom observe(&xModem.packetReady); +#endif } // even if we were already connected - restart our state machine @@ -62,7 +64,9 @@ void PhoneAPI::close() state = STATE_SEND_NOTHING; unobserve(&service.fromNumChanged); +#ifdef FSCom unobserve(&xModem.packetReady); +#endif releasePhonePacket(); // Don't leak phone packets on shutdown releaseQueueStatusPhonePacket(); releaseMqttClientProxyPhonePacket(); @@ -110,7 +114,9 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) break; case meshtastic_ToRadio_xmodemPacket_tag: LOG_INFO("Got xmodem packet\n"); +#ifdef FSCom xModem.handlePacket(toRadioScratch.xmodemPacket); +#endif break; #if !MESHTASTIC_EXCLUDE_MQTT case meshtastic_ToRadio_mqttClientProxyMessage_tag: @@ -496,12 +502,14 @@ bool PhoneAPI::available() if (hasPacket) return true; +#ifdef FSCom if (xmodemPacketForPhone.control == meshtastic_XModem_Control_NUL) xmodemPacketForPhone = xModem.getForPhone(); if (xmodemPacketForPhone.control != meshtastic_XModem_Control_NUL) { xModem.resetForPhone(); return true; } +#endif #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_STOREFORWARD diff --git a/src/mesh/STM32WLE5JCInterface.h b/src/mesh/STM32WLE5JCInterface.h index 73d53d92f..fad793332 100644 --- a/src/mesh/STM32WLE5JCInterface.h +++ b/src/mesh/STM32WLE5JCInterface.h @@ -23,7 +23,7 @@ static const float tcxoVoltage = 1.7; * Wio-E5 module ONLY transmits through RFO_HP * Receive: PA4=1, PA5=0 * Transmit(high output power, SMPS mode): PA4=0, PA5=1 */ -static const RADIOLIB_PIN_TYPE rfswitch_pins[3] = {PA4, PA5, RADIOLIB_NC}; +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[4] = { {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/src/mesh/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp index 93dbf0178..676208e25 100644 --- a/src/mesh/mesh-pb-constants.cpp +++ b/src/mesh/mesh-pb-constants.cpp @@ -1,6 +1,7 @@ -#include "mesh-pb-constants.h" -#include "FSCommon.h" #include "configuration.h" + +#include "FSCommon.h" +#include "mesh-pb-constants.h" #include #include #include @@ -15,6 +16,7 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc LOG_ERROR("Panic: can't encode protobuf reason='%s'\n", PB_GET_ERROR(&stream)); assert( 0); // If this assert fails it probably means you made a field too large for the max limits specified in mesh.options + return 0; } else { return stream.bytes_written; } diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 98b789f41..8939b972b 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -259,11 +259,13 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_delete_file_request_tag: { LOG_DEBUG("Client is requesting to delete file: %s\n", r->delete_file_request); +#ifdef FSCom if (FSCom.remove(r->delete_file_request)) { LOG_DEBUG("Successfully deleted file\n"); } else { LOG_DEBUG("Failed to delete file\n"); } +#endif break; } #ifdef ARCH_PORTDUINO diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 84b5a3260..524d37a3d 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1066,7 +1066,7 @@ void CannedMessageModule::loadProtoForModule() { if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, - &cannedMessageModuleConfig) != LoadFileResult::SUCCESS) { + &cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) { installDefaultCannedMessageModuleConfig(); } } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index c02559240..652db04d3 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -355,7 +355,7 @@ ExternalNotificationModule::ExternalNotificationModule() if (moduleConfig.external_notification.enabled) { if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), - &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::SUCCESS) { + &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); strncpy(rtttlConfig.ringtone, "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p", diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 300afc246..5a0e36fea 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -7,7 +7,9 @@ #include "input/cardKbI2cImpl.h" #include "input/kbMatrixImpl.h" #endif +#if !MESHTASTIC_EXCLUDE_ADMIN #include "modules/AdminModule.h" +#endif #if !MESHTASTIC_EXCLUDE_ATAK #include "modules/AtakPluginModule.h" #endif @@ -20,7 +22,9 @@ #if !MESHTASTIC_EXCLUDE_NEIGHBORINFO #include "modules/NeighborInfoModule.h" #endif +#if !MESHTASTIC_EXCLUDE_NODEINFO #include "modules/NodeInfoModule.h" +#endif #if !MESHTASTIC_EXCLUDE_GPS #include "modules/PositionModule.h" #endif @@ -88,8 +92,12 @@ void setupModules() #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER inputBroker = new InputBroker(); #endif +#if !MESHTASTIC_EXCLUDE_ADMIN adminModule = new AdminModule(); +#endif +#if !MESHTASTIC_EXCLUDE_NODEINFO nodeInfoModule = new NodeInfoModule(); +#endif #if !MESHTASTIC_EXCLUDE_GPS positionModule = new PositionModule(); #endif @@ -192,7 +200,9 @@ void setupModules() #endif #endif } else { +#if !MESHTASTIC_EXCLUDE_ADMIN adminModule = new AdminModule(); +#endif #if HAS_TELEMETRY new DeviceTelemetryModule(); #endif diff --git a/src/platform/stm32wl/InternalFileSystem.cpp b/src/platform/stm32wl/InternalFileSystem.cpp deleted file mode 100644 index d42a646a5..000000000 --- a/src/platform/stm32wl/InternalFileSystem.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 hathach for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "InternalFileSystem.h" -#include - -//--------------------------------------------------------------------+ -// LFS Disk IO -//--------------------------------------------------------------------+ - -static inline uint32_t lba2addr(uint32_t block) -{ - return ((uint32_t)LFS_FLASH_ADDR) + block * LFS_BLOCK_SIZE; -} - -static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) -{ - (void)c; - - uint32_t addr = lba2addr(block) + off; - uint8_t prom; - - for (int i = 0; i < size; i++) { - prom = EEPROM.read(addr + i); - memcpy((char *)buffer + i, &prom, 1); - } - return 0; -} - -// Program a region in a block. The block must have previously -// been erased. Negative error codes are propagated to the user. -// May return LFS_ERR_CORRUPT if the block should be considered bad. -static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) -{ - (void)c; - - uint32_t addr = lba2addr(block) + off; - uint8_t prom; - - for (int i = 0; i < size; i++) { - memcpy(&prom, (char *)buffer + i, 1); - EEPROM.update(addr + i, prom); - } - return 0; -} - -// Erase a block. A block must be erased before being programmed. -// The state of an erased block is undefined. Negative error codes -// are propagated to the user. -// May return LFS_ERR_CORRUPT if the block should be considered bad. -static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block) -{ - (void)c; - - uint32_t addr = lba2addr(block); - - // implement as write 0xff to whole block address - for (int i = 0; i < LFS_BLOCK_SIZE; i++) { - EEPROM.update(addr, 0xff); - } - - return 0; -} - -// Sync the state of the underlying block device. Negative error codes -// are propagated to the user. -static int _internal_flash_sync(const struct lfs_config *c) -{ - // we don't use a ram cache, this is a noop - return 0; -} - -static struct lfs_config _InternalFSConfig = {.context = NULL, - - .read = _internal_flash_read, - .prog = _internal_flash_prog, - .erase = _internal_flash_erase, - .sync = _internal_flash_sync, - - .read_size = LFS_CACHE_SIZE, - .prog_size = LFS_CACHE_SIZE, - .block_size = LFS_BLOCK_SIZE, - .block_count = LFS_FLASH_TOTAL_SIZE / LFS_BLOCK_SIZE, - .block_cycles = - 500, // protection against wear leveling (suggested values between 100-1000) - .cache_size = LFS_CACHE_SIZE, - .lookahead_size = LFS_CACHE_SIZE, - - .read_buffer = lfs_read_buffer, - .prog_buffer = lfs_prog_buffer, - .lookahead_buffer = lfs_lookahead_buffer}; - -InternalFileSystem InternalFS; - -//--------------------------------------------------------------------+ -// -//--------------------------------------------------------------------+ - -InternalFileSystem::InternalFileSystem(void) : LittleFS(&_InternalFSConfig) {} - -bool InternalFileSystem::begin(void) -{ - // failed to mount, erase all sector then format and mount again - if (!LittleFS::begin()) { - // Erase all sectors of internal flash region for Filesystem. - // implement as write 0xff to whole block address - for (uint32_t addr = LFS_FLASH_ADDR; addr < (LFS_FLASH_ADDR + LFS_FLASH_TOTAL_SIZE); addr++) { - EEPROM.update(addr, 0xff); - } - - // lfs format - this->format(); - - // mount again if still failed, give up - if (!LittleFS::begin()) - return false; - } - - return true; -} diff --git a/src/platform/stm32wl/InternalFileSystem.h b/src/platform/stm32wl/InternalFileSystem.h deleted file mode 100644 index 66344194e..000000000 --- a/src/platform/stm32wl/InternalFileSystem.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 hathach for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef INTERNALFILESYSTEM_H_ -#define INTERNALFILESYSTEM_H_ - -#include "LittleFS.h" - -// The EEPROM Library assumes our usable flash area starts at logical 0 -#define LFS_FLASH_ADDR 0 - -// use the built in EEPROM emulation. Total Size is 2Kbyte -#define LFS_BLOCK_SIZE 128 // min. block size is 128 to fit CTZ pointers -#define LFS_CACHE_SIZE 16 - -#define LFS_FLASH_TOTAL_SIZE FLASH_PAGE_SIZE - -static uint8_t lfs_read_buffer[LFS_CACHE_SIZE] = {0}; -static uint8_t lfs_prog_buffer[LFS_CACHE_SIZE] = {0}; -static uint8_t lfs_lookahead_buffer[LFS_CACHE_SIZE] = {0}; - -class InternalFileSystem : public LittleFS -{ - public: - InternalFileSystem(void); - - // overwrite to also perform low level format (sector erase of whole flash region) - bool begin(void); -}; - -extern InternalFileSystem InternalFS; - -#endif /* INTERNALFILESYSTEM_H_ */ diff --git a/src/platform/stm32wl/LittleFS.cpp b/src/platform/stm32wl/LittleFS.cpp deleted file mode 100644 index b1267d88a..000000000 --- a/src/platform/stm32wl/LittleFS.cpp +++ /dev/null @@ -1,258 +0,0 @@ -#include -#include - -#include "LittleFS.h" - -using namespace LittleFS_Namespace; - -//--------------------------------------------------------------------+ -// Implementation -//--------------------------------------------------------------------+ - -LittleFS::LittleFS(void) : LittleFS(NULL) {} - -LittleFS::LittleFS(struct lfs_config *cfg) -{ - memset(&_lfs, 0, sizeof(_lfs)); - _lfs_cfg = cfg; - _mounted = false; - _mutex = xSemaphoreCreateMutexStatic(&this->_MutexStorageSpace); -} - -LittleFS::~LittleFS() {} - -// Initialize and mount the file system -// Return true if mounted successfully else probably corrupted. -// User should format the disk and try again -bool LittleFS::begin(struct lfs_config *cfg) -{ - _lockFS(); - - bool ret; - // not a loop, just an quick way to short-circuit on error - do { - if (_mounted) { - ret = true; - break; - } - if (cfg) { - _lfs_cfg = cfg; - } - if (nullptr == _lfs_cfg) { - ret = false; - break; - } - // actually attempt to mount, and log error if one occurs - int err = lfs_mount(&_lfs, _lfs_cfg); - PRINT_LFS_ERR(err); - _mounted = (err == LFS_ERR_OK); - ret = _mounted; - } while (0); - - _unlockFS(); - return ret; -} - -// Tear down and unmount file system -void LittleFS::end(void) -{ - _lockFS(); - - if (_mounted) { - _mounted = false; - int err = lfs_unmount(&_lfs); - PRINT_LFS_ERR(err); - (void)err; - } - - _unlockFS(); -} - -bool LittleFS::format(void) -{ - _lockFS(); - - int err = LFS_ERR_OK; - bool attemptMount = _mounted; - // not a loop, just an quick way to short-circuit on error - do { - // if already mounted: umount first -> format -> remount - if (_mounted) { - _mounted = false; - err = lfs_unmount(&_lfs); - if (LFS_ERR_OK != err) { - PRINT_LFS_ERR(err); - break; - } - } - err = lfs_format(&_lfs, _lfs_cfg); - if (LFS_ERR_OK != err) { - PRINT_LFS_ERR(err); - break; - } - - if (attemptMount) { - err = lfs_mount(&_lfs, _lfs_cfg); - if (LFS_ERR_OK != err) { - PRINT_LFS_ERR(err); - break; - } - _mounted = true; - } - // success! - } while (0); - - _unlockFS(); - return LFS_ERR_OK == err; -} - -// Open a file or folder -LittleFS_Namespace::File LittleFS::open(char const *filepath, uint8_t mode) -{ - // No lock is required here ... the File() object will synchronize with the mutex provided - return LittleFS_Namespace::File(filepath, mode, *this); -} - -// Check if file or folder exists -bool LittleFS::exists(char const *filepath) -{ - struct lfs_info info; - _lockFS(); - - bool ret = (0 == lfs_stat(&_lfs, filepath, &info)); - - _unlockFS(); - return ret; -} - -// Create a directory, create intermediate parent if needed -bool LittleFS::mkdir(char const *filepath) -{ - bool ret = true; - const char *slash = filepath; - if (slash[0] == '/') - slash++; // skip root '/' - - _lockFS(); - - // make intermediate parent directory(ies) - while (NULL != (slash = strchr(slash, '/'))) { - char parent[slash - filepath + 1] = {0}; - memcpy(parent, filepath, slash - filepath); - - int rc = lfs_mkdir(&_lfs, parent); - if (rc != LFS_ERR_OK && rc != LFS_ERR_EXIST) { - PRINT_LFS_ERR(rc); - ret = false; - break; - } - slash++; - } - // make the final requested directory - if (ret) { - int rc = lfs_mkdir(&_lfs, filepath); - if (rc != LFS_ERR_OK && rc != LFS_ERR_EXIST) { - PRINT_LFS_ERR(rc); - ret = false; - } - } - - _unlockFS(); - return ret; -} - -// Remove a file -bool LittleFS::remove(char const *filepath) -{ - _lockFS(); - - int err = lfs_remove(&_lfs, filepath); - PRINT_LFS_ERR(err); - - _unlockFS(); - return LFS_ERR_OK == err; -} - -// Rename a file -bool LittleFS::rename(char const *oldfilepath, char const *newfilepath) -{ - _lockFS(); - - int err = lfs_rename(&_lfs, oldfilepath, newfilepath); - PRINT_LFS_ERR(err); - - _unlockFS(); - return LFS_ERR_OK == err; -} - -// Remove a folder -bool LittleFS::rmdir(char const *filepath) -{ - _lockFS(); - - int err = lfs_remove(&_lfs, filepath); - PRINT_LFS_ERR(err); - - _unlockFS(); - return LFS_ERR_OK == err; -} - -// Remove a folder recursively -bool LittleFS::rmdir_r(char const *filepath) -{ - /* adafruit: lfs is modified to remove non-empty folder, - According to below issue, comment these 2 line won't corrupt filesystem - at least when using LFS v1. If moving to LFS v2, see tracked issue - to see if issues (such as the orphans in threaded linked list) are resolved. - https://github.com/ARMmbed/littlefs/issues/43 - */ - _lockFS(); - - int err = lfs_remove(&_lfs, filepath); - PRINT_LFS_ERR(err); - - _unlockFS(); - return LFS_ERR_OK == err; -} - -//------------- Debug -------------// -#if CFG_DEBUG - -const char *dbg_strerr_lfs(int32_t err) -{ - switch (err) { - case LFS_ERR_OK: - return "LFS_ERR_OK"; - case LFS_ERR_IO: - return "LFS_ERR_IO"; - case LFS_ERR_CORRUPT: - return "LFS_ERR_CORRUPT"; - case LFS_ERR_NOENT: - return "LFS_ERR_NOENT"; - case LFS_ERR_EXIST: - return "LFS_ERR_EXIST"; - case LFS_ERR_NOTDIR: - return "LFS_ERR_NOTDIR"; - case LFS_ERR_ISDIR: - return "LFS_ERR_ISDIR"; - case LFS_ERR_NOTEMPTY: - return "LFS_ERR_NOTEMPTY"; - case LFS_ERR_BADF: - return "LFS_ERR_BADF"; - case LFS_ERR_INVAL: - return "LFS_ERR_INVAL"; - case LFS_ERR_NOSPC: - return "LFS_ERR_NOSPC"; - case LFS_ERR_NOMEM: - return "LFS_ERR_NOMEM"; - - default: - static char errcode[10]; - sprintf(errcode, "%ld", err); - return errcode; - } - - return NULL; -} - -#endif diff --git a/src/platform/stm32wl/LittleFS.h b/src/platform/stm32wl/LittleFS.h deleted file mode 100644 index 4a0b01af2..000000000 --- a/src/platform/stm32wl/LittleFS.h +++ /dev/null @@ -1,85 +0,0 @@ - -#ifndef LITTLEFS_H_ -#define LITTLEFS_H_ - -#include - -#include "lfs.h" - -#include "LittleFS_File.h" - -#include "FreeRTOS.h" // tied to FreeRTOS for serialization -#include "semphr.h" - -class LittleFS -{ - public: - LittleFS(void); - explicit LittleFS(struct lfs_config *cfg); - virtual ~LittleFS(); - - bool begin(struct lfs_config *cfg = NULL); - void end(void); - - // Open the specified file/directory with the supplied mode (e.g. read or - // write, etc). Returns a File object for interacting with the file. - // Note that currently only one file can be open at a time. - LittleFS_Namespace::File open(char const *filename, uint8_t mode = LittleFS_Namespace::FILE_O_READ); - - // Methods to determine if the requested file path exists. - bool exists(char const *filepath); - - // Create the requested directory hierarchy--if intermediate directories - // do not exist they will be created. - bool mkdir(char const *filepath); - - // Delete the file. - bool remove(char const *filepath); - - // Rename the file. - bool rename(char const *oldfilepath, char const *newfilepath); - - // Delete a folder (must be empty) - bool rmdir(char const *filepath); - - // Delete a folder (recursively) - bool rmdir_r(char const *filepath); - - // format file system - bool format(void); - - /*------------------------------------------------------------------*/ - /* INTERNAL USAGE ONLY - * Although declare as public, it is meant to be invoked by internal - * code. User should not call these directly - *------------------------------------------------------------------*/ - lfs_t *_getFS(void) { return &_lfs; } - void _lockFS(void) { xSemaphoreTake(_mutex, portMAX_DELAY); } - void _unlockFS(void) { xSemaphoreGive(_mutex); } - - protected: - bool _mounted; - struct lfs_config *_lfs_cfg; - lfs_t _lfs; - SemaphoreHandle_t _mutex; - - private: - StaticSemaphore_t _MutexStorageSpace; -}; - -#if !CFG_DEBUG -#define VERIFY_LFS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, NULL) -#define PRINT_LFS_ERR(_err) -#else -#define VERIFY_LFS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, dbg_strerr_lfs) -#define PRINT_LFS_ERR(_err) \ - do { \ - if (_err) { \ - VERIFY_MESS((long int)_err, dbg_strerr_lfs); \ - } \ - } while (0) // LFS_ERR are of type int, VERIFY_MESS expects long_int - -const char *dbg_strerr_lfs(int32_t err); -#endif - -#endif /* LITTLEFS_H_ */ diff --git a/src/platform/stm32wl/LittleFS_File.cpp b/src/platform/stm32wl/LittleFS_File.cpp deleted file mode 100644 index 548a3d300..000000000 --- a/src/platform/stm32wl/LittleFS_File.cpp +++ /dev/null @@ -1,362 +0,0 @@ -#include - -#include "LittleFS.h" - -#include - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -using namespace LittleFS_Namespace; - -File::File(LittleFS &fs) -{ - _fs = &fs; - _is_dir = false; - _name[0] = 0; - _dir_path = NULL; - - _dir = NULL; - _file = NULL; -} - -File::File(char const *filename, uint8_t mode, LittleFS &fs) : File(fs) -{ - // public constructor calls public API open(), which will obtain the mutex - this->open(filename, mode); -} - -bool File::_open_file(char const *filepath, uint8_t mode) -{ - int flags = (mode == FILE_O_READ) ? LFS_O_RDONLY : (mode == FILE_O_WRITE) ? (LFS_O_RDWR | LFS_O_CREAT) : 0; - - if (flags) { - _file = (lfs_file_t *)malloc(sizeof(lfs_file_t)); - if (!_file) - return false; - - int rc = lfs_file_open(_fs->_getFS(), _file, filepath, flags); - - if (rc) { - // failed to open - PRINT_LFS_ERR(rc); - return false; - } - - // move to end of file - if (mode == FILE_O_WRITE) - lfs_file_seek(_fs->_getFS(), _file, 0, LFS_SEEK_END); - - _is_dir = false; - } - - return true; -} - -bool File::_open_dir(char const *filepath) -{ - _dir = (lfs_dir_t *)malloc(sizeof(lfs_dir_t)); - if (!_dir) - return false; - - int rc = lfs_dir_open(_fs->_getFS(), _dir, filepath); - - if (rc) { - // failed to open - PRINT_LFS_ERR(rc); - return false; - } - - _is_dir = true; - - _dir_path = (char *)malloc(strlen(filepath) + 1); - strcpy(_dir_path, filepath); - - return true; -} - -bool File::open(char const *filepath, uint8_t mode) -{ - bool ret = false; - _fs->_lockFS(); - - ret = this->_open(filepath, mode); - - _fs->_unlockFS(); - return ret; -} - -bool File::_open(char const *filepath, uint8_t mode) -{ - bool ret = false; - - // close if currently opened - if (this->isOpen()) - _close(); - - struct lfs_info info; - int rc = lfs_stat(_fs->_getFS(), filepath, &info); - - if (LFS_ERR_OK == rc) { - // file existed, open file or directory accordingly - ret = (info.type == LFS_TYPE_REG) ? _open_file(filepath, mode) : _open_dir(filepath); - } else if (LFS_ERR_NOENT == rc) { - // file not existed, only proceed with FILE_O_WRITE mode - if (mode == FILE_O_WRITE) - ret = _open_file(filepath, mode); - } else { - PRINT_LFS_ERR(rc); - } - - // save bare file name - if (ret) { - char const *splash = strrchr(filepath, '/'); - strncpy(_name, splash ? (splash + 1) : filepath, LFS_NAME_MAX); - } - return ret; -} - -size_t File::write(uint8_t ch) -{ - return write(&ch, 1); -} - -size_t File::write(uint8_t const *buf, size_t size) -{ - lfs_ssize_t wrcount = 0; - _fs->_lockFS(); - - if (!this->_is_dir) { - wrcount = lfs_file_write(_fs->_getFS(), _file, buf, size); - if (wrcount < 0) { - wrcount = 0; - } - } - - _fs->_unlockFS(); - return wrcount; -} - -int File::read(void) -{ - // this thin wrapper relies on called function to synchronize - int ret = -1; - uint8_t ch; - if (read(&ch, 1) > 0) { - ret = static_cast(ch); - } - return ret; -} - -int File::read(void *buf, uint16_t nbyte) -{ - int ret = 0; - _fs->_lockFS(); - - if (!this->_is_dir) { - ret = lfs_file_read(_fs->_getFS(), _file, buf, nbyte); - } - - _fs->_unlockFS(); - return ret; -} - -int File::peek(void) -{ - int ret = -1; - _fs->_lockFS(); - - if (!this->_is_dir) { - uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); - uint8_t ch = 0; - if (lfs_file_read(_fs->_getFS(), _file, &ch, 1) > 0) { - ret = static_cast(ch); - } - (void)lfs_file_seek(_fs->_getFS(), _file, pos, LFS_SEEK_SET); - } - - _fs->_unlockFS(); - return ret; -} - -int File::available(void) -{ - int ret = 0; - _fs->_lockFS(); - - if (!this->_is_dir) { - uint32_t fsize = lfs_file_size(_fs->_getFS(), _file); - uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); - ret = fsize - pos; - } - - _fs->_unlockFS(); - return ret; -} - -bool File::seek(uint32_t pos) -{ - bool ret = false; - _fs->_lockFS(); - - if (!this->_is_dir) { - ret = lfs_file_seek(_fs->_getFS(), _file, pos, LFS_SEEK_SET) >= 0; - } - - _fs->_unlockFS(); - return ret; -} - -uint32_t File::position(void) -{ - uint32_t ret = 0; - _fs->_lockFS(); - - if (!this->_is_dir) { - ret = lfs_file_tell(_fs->_getFS(), _file); - } - - _fs->_unlockFS(); - return ret; -} - -uint32_t File::size(void) -{ - uint32_t ret = 0; - _fs->_lockFS(); - - if (!this->_is_dir) { - ret = lfs_file_size(_fs->_getFS(), _file); - } - - _fs->_unlockFS(); - return ret; -} - -bool File::truncate(uint32_t pos) -{ - int32_t ret = LFS_ERR_ISDIR; - _fs->_lockFS(); - if (!this->_is_dir) { - ret = lfs_file_truncate(_fs->_getFS(), _file, pos); - } - _fs->_unlockFS(); - return (ret == 0); -} - -bool File::truncate(void) -{ - int32_t ret = LFS_ERR_ISDIR; - _fs->_lockFS(); - if (!this->_is_dir) { - uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); - ret = lfs_file_truncate(_fs->_getFS(), _file, pos); - } - _fs->_unlockFS(); - return (ret == 0); -} - -void File::flush(void) -{ - _fs->_lockFS(); - - if (!this->_is_dir) { - lfs_file_sync(_fs->_getFS(), _file); - } - - _fs->_unlockFS(); - return; -} - -void File::close(void) -{ - _fs->_lockFS(); - this->_close(); - _fs->_unlockFS(); -} - -void File::_close(void) -{ - if (this->isOpen()) { - if (this->_is_dir) { - lfs_dir_close(_fs->_getFS(), _dir); - free(_dir); - _dir = NULL; - - if (this->_dir_path) - free(_dir_path); - _dir_path = NULL; - } else { - lfs_file_close(this->_fs->_getFS(), _file); - free(_file); - _file = NULL; - } - } -} - -File::operator bool(void) -{ - return isOpen(); -} - -bool File::isOpen(void) -{ - return (_file != NULL) || (_dir != NULL); -} - -// WARNING -- although marked as `const`, the values pointed -// to may change. For example, if the same File -// object has `open()` called with a different -// file or directory name, this same pointer will -// suddenly (unexpectedly?) have different values. -char const *File::name(void) -{ - return this->_name; -} - -bool File::isDirectory(void) -{ - return this->_is_dir; -} - -File File::openNextFile(uint8_t mode) -{ - _fs->_lockFS(); - - File ret(*_fs); - if (this->_is_dir) { - struct lfs_info info; - int rc; - - // lfs_dir_read returns 0 when reaching end of directory, 1 if found an entry - // Skip the "." and ".." entries ... - do { - rc = lfs_dir_read(_fs->_getFS(), _dir, &info); - } while (rc == 1 && (!strcmp(".", info.name) || !strcmp("..", info.name))); - - if (rc == 1) { - // string cat name with current folder - char filepath[strlen(_dir_path) + 1 + strlen(info.name) + 1]; // potential for significant stack usage - strcpy(filepath, _dir_path); - if (!(_dir_path[0] == '/' && _dir_path[1] == 0)) - strcat(filepath, "/"); // only add '/' if cwd is not root - strcat(filepath, info.name); - - (void)ret._open(filepath, mode); // return value is ignored ... caller is expected to check isOpened() - } else if (rc < 0) { - PRINT_LFS_ERR(rc); - } - } - _fs->_unlockFS(); - return ret; -} - -void File::rewindDirectory(void) -{ - _fs->_lockFS(); - if (this->_is_dir) { - lfs_dir_rewind(_fs->_getFS(), _dir); - } - _fs->_unlockFS(); -} diff --git a/src/platform/stm32wl/LittleFS_File.h b/src/platform/stm32wl/LittleFS_File.h deleted file mode 100644 index e88a2790d..000000000 --- a/src/platform/stm32wl/LittleFS_File.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef LITTLEFS_FILE_H_ -#define LITTLEFS_FILE_H_ - -// Forward declaration -class LittleFS; - -namespace LittleFS_Namespace -{ - -// avoid conflict with other FileSystem FILE_READ/FILE_WRITE -enum { - FILE_O_READ = 0, - FILE_O_WRITE = 1, -}; - -class File : public Stream -{ - public: - explicit File(LittleFS &fs); - File(char const *filename, uint8_t mode, LittleFS &fs); - - public: - bool open(char const *filename, uint8_t mode); - - //------------- Stream API -------------// - virtual size_t write(uint8_t ch); - virtual size_t write(uint8_t const *buf, size_t size); - size_t write(const char *str) - { - if (str == NULL) - return 0; - return write((const uint8_t *)str, strlen(str)); - } - size_t write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); } - - virtual int read(void); - int read(void *buf, uint16_t nbyte); - - virtual int peek(void); - virtual int available(void); - virtual void flush(void); - - bool seek(uint32_t pos); - uint32_t position(void); - uint32_t size(void); - - bool truncate(uint32_t pos); - bool truncate(void); - - void close(void); - - operator bool(void); - - bool isOpen(void); - char const *name(void); - - bool isDirectory(void); - File openNextFile(uint8_t mode = FILE_O_READ); - void rewindDirectory(void); - - private: - LittleFS *_fs; - - bool _is_dir; - - union { - lfs_file_t *_file; - lfs_dir_t *_dir; - }; - - char *_dir_path; - char _name[LFS_NAME_MAX + 1]; - - bool _open(char const *filepath, uint8_t mode); - bool _open_file(char const *filepath, uint8_t mode); - bool _open_dir(char const *filepath); - void _close(void); -}; - -} // namespace LittleFS_Namespace - -#endif /* LITTLEFS_FILE_H_ */ diff --git a/src/platform/stm32wl/STM32WLCryptoEngine.cpp b/src/platform/stm32wl/STM32WLCryptoEngine.cpp index 7367a2bc0..4debdf78e 100644 --- a/src/platform/stm32wl/STM32WLCryptoEngine.cpp +++ b/src/platform/stm32wl/STM32WLCryptoEngine.cpp @@ -1,33 +1,64 @@ +#undef RNG +#include "AES.h" +#include "CTR.h" #include "CryptoEngine.h" -#include "aes.hpp" #include "configuration.h" class STM32WLCryptoEngine : public CryptoEngine { + + CTRCommon *ctr = NULL; + public: STM32WLCryptoEngine() {} ~STM32WLCryptoEngine() {} + virtual void setKey(const CryptoKey &k) override + { + CryptoEngine::setKey(k); + LOG_DEBUG("Installing AES%d key!\n", key.length * 8); + if (ctr) { + delete ctr; + ctr = NULL; + } + if (key.length != 0) { + if (key.length == 16) + ctr = new CTR(); + else + ctr = new CTR(); + + ctr->setKey(key.bytes, key.length); + } + } /** * Encrypt a packet * * @param bytes is updated in place */ - virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) override + virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override { if (key.length > 0) { - AES_ctx ctx; - initNonce(fromNode, packetNum); - AES_init_ctx_iv(&ctx, key.bytes, nonce); - AES_CTR_xcrypt_buffer(&ctx, bytes, numBytes); + initNonce(fromNode, packetId); + if (numBytes <= MAX_BLOCKSIZE) { + static uint8_t scratch[MAX_BLOCKSIZE]; + memcpy(scratch, bytes, numBytes); + memset(scratch + numBytes, 0, + sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) + + ctr->setIV(nonce, sizeof(nonce)); + ctr->setCounterSize(4); + ctr->encrypt(bytes, scratch, numBytes); + } else { + LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); + } } } - virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) override + virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override { // For CTR, the implementation is the same - encrypt(fromNode, packetNum, numBytes, bytes); + encrypt(fromNode, packetId, numBytes, bytes); } private: diff --git a/src/platform/stm32wl/main-stm32wl.cpp b/src/platform/stm32wl/main-stm32wl.cpp index 60c3cce10..3eddbb3cf 100644 --- a/src/platform/stm32wl/main-stm32wl.cpp +++ b/src/platform/stm32wl/main-stm32wl.cpp @@ -26,11 +26,3 @@ void getMacAddr(uint8_t *dmac) } void cpuDeepSleep(uint32_t msecToWake) {} - -/* pacify libc_nano */ -extern "C" { -int _gettimeofday(struct timeval *tv, void *tzvp) -{ - return -1; -} -} \ No newline at end of file diff --git a/src/xmodem.cpp b/src/xmodem.cpp index 852ff3453..de73e8668 100644 --- a/src/xmodem.cpp +++ b/src/xmodem.cpp @@ -50,6 +50,8 @@ #include "xmodem.h" +#ifdef FSCom + XModemAdapter xModem; XModemAdapter::XModemAdapter() {} @@ -248,4 +250,5 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket) // Unknown control character break; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/xmodem.h b/src/xmodem.h index 2ba0bb39f..4cfcb43e1 100644 --- a/src/xmodem.h +++ b/src/xmodem.h @@ -38,6 +38,8 @@ #define MAXRETRANS 25 +#ifdef FSCom + class XModemAdapter { public: @@ -75,3 +77,4 @@ class XModemAdapter }; extern XModemAdapter xModem; +#endif // FSCom \ No newline at end of file diff --git a/variants/rak3172/platformio.ini b/variants/rak3172/platformio.ini new file mode 100644 index 000000000..63766c988 --- /dev/null +++ b/variants/rak3172/platformio.ini @@ -0,0 +1,12 @@ +[env:rak3172] +extends = stm32_base +board_level = extra +board = wiscore_rak3172 +build_flags = + ${stm32_base.build_flags} + -Ivariants/rak3172 + -DHAL_DAC_MODULE_ONLY + -DSERIAL_UART_INSTANCE=1 + -DPIN_SERIAL_RX=PB7 + -DPIN_SERIAL_TX=PB6 +upload_port = stlink \ No newline at end of file diff --git a/variants/rak3172/variant.h b/variants/rak3172/variant.h new file mode 100644 index 000000000..21de65b2c --- /dev/null +++ b/variants/rak3172/variant.h @@ -0,0 +1,12 @@ +/* +This variant is a work in progress. +Do not expect a working Meshtastic device with this target. +*/ + +#ifndef _VARIANT_RAK3172_ +#define _VARIANT_RAK3172_ + +#define USE_STM32WLx +#define MAX_NUM_NODES 10 + +#endif \ No newline at end of file diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini index 07f6efa6d..12cd6190d 100644 --- a/variants/wio-e5/platformio.ini +++ b/variants/wio-e5/platformio.ini @@ -1,11 +1,34 @@ [env:wio-e5] -extends = stm32wl5e_base +extends = stm32_base board_level = extra +board = lora_e5_dev_board build_flags = - ${stm32wl5e_base.build_flags} + ${stm32_base.build_flags} -Ivariants/wio-e5 - -DHAL_DAC_MODULE_ONLY -DSERIAL_UART_INSTANCE=1 -DPIN_SERIAL_RX=PB7 -DPIN_SERIAL_TX=PB6 + -DHAL_DAC_MODULE_ONLY + -DHAL_ADC_MODULE_DISABLED + -DHAL_COMP_MODULE_DISABLED + -DHAL_CRC_MODULE_DISABLED + -DHAL_CRYP_MODULE_DISABLED + -DHAL_GTZC_MODULE_DISABLED + -DHAL_HSEM_MODULE_DISABLED + -DHAL_I2C_MODULE_DISABLED + -DHAL_I2S_MODULE_DISABLED + -DHAL_IPCC_MODULE_DISABLED + -DHAL_IRDA_MODULE_DISABLED + -DHAL_IWDG_MODULE_DISABLED + -DHAL_LPTIM_MODULE_DISABLED + -DHAL_PKA_MODULE_DISABLED + -DHAL_RNG_MODULE_DISABLED + -DHAL_RTC_MODULE_DISABLED + -DHAL_SMARTCARD_MODULE_DISABLED + -DHAL_SMBUS_MODULE_DISABLED + -DHAL_TIM_MODULE_DISABLED + -DHAL_WWDG_MODULE_DISABLED + -DHAL_EXTI_MODULE_DISABLED +; -D PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF + upload_port = stlink \ No newline at end of file diff --git a/variants/wio-e5/variant.h b/variants/wio-e5/variant.h index 86e58bcb2..b4345a530 100644 --- a/variants/wio-e5/variant.h +++ b/variants/wio-e5/variant.h @@ -13,5 +13,6 @@ Do not expect a working Meshtastic device with this target. #define _VARIANT_WIOE5_ #define USE_STM32WLx +#define MAX_NUM_NODES 10 #endif \ No newline at end of file From f645ae943dd1411fefe8c48ae83d01ad6a4072c5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 25 Jul 2024 20:50:42 -0500 Subject: [PATCH 0739/1377] JSON serialization refactor (#4331) --- src/mesh/http/ContentHandler.cpp | 2 +- src/mqtt/MQTT.cpp | 304 +------------------- src/mqtt/MQTT.h | 5 +- src/{mqtt => serialization}/JSON.cpp | 0 src/{mqtt => serialization}/JSON.h | 0 src/{mqtt => serialization}/JSONValue.cpp | 0 src/{mqtt => serialization}/JSONValue.h | 0 src/serialization/MeshPacketSerializer.cpp | 317 +++++++++++++++++++++ src/serialization/MeshPacketSerializer.h | 8 + 9 files changed, 331 insertions(+), 305 deletions(-) rename src/{mqtt => serialization}/JSON.cpp (100%) rename src/{mqtt => serialization}/JSON.h (100%) rename src/{mqtt => serialization}/JSONValue.cpp (100%) rename src/{mqtt => serialization}/JSONValue.h (100%) create mode 100644 src/serialization/MeshPacketSerializer.cpp create mode 100644 src/serialization/MeshPacketSerializer.h diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index b309484e2..ca2c5d4be 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -9,8 +9,8 @@ #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif -#include "mqtt/JSON.h" #include "power.h" +#include "serialization/JSON.h" #include "sleep.h" #include #include diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a7085dffe..2fce526a0 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -19,6 +19,8 @@ #include #endif #include "Default.h" +#include "serialization/JSON.h" +#include "serialization/MeshPacketSerializer.h" #include const int reconnectMax = 5; @@ -459,7 +461,7 @@ void MQTT::publishQueuedMessages() #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic - auto jsonString = this->meshPacketToJson(env->packet); + auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); if (jsonString.length() != 0) { std::string topicJson = jsonTopic + env->channel_id + "/" + owner.id; LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), @@ -520,7 +522,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic - auto jsonString = this->meshPacketToJson((meshtastic_MeshPacket *)&mp_decoded); + auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); if (jsonString.length() != 0) { std::string topicJson = jsonTopic + channelId + "/" + owner.id; LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), @@ -621,304 +623,6 @@ void MQTT::perhapsReportToMap() } } -// converts a downstream packet into a json message -std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) -{ - // the created jsonObj is immutable after creation, so - // we need to do the heavy lifting before assembling it. - std::string msgType; - JSONObject jsonObj; - - if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - JSONObject msgPayload; - switch (mp->decoded.portnum) { - case meshtastic_PortNum_TEXT_MESSAGE_APP: { - msgType = "text"; - // convert bytes to string - LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - // check if this is a JSON payload - JSONValue *json_value = JSON::Parse(payloadStr); - if (json_value != NULL) { - LOG_INFO("text message payload is of type json\n"); - // if it is, then we can just use the json object - jsonObj["payload"] = json_value; - } else { - // if it isn't, then we need to create a json object - // with the string as the value - LOG_INFO("text message payload is of type plaintext\n"); - msgPayload["text"] = new JSONValue(payloadStr); - jsonObj["payload"] = new JSONValue(msgPayload); - } - break; - } - case meshtastic_PortNum_TELEMETRY_APP: { - msgType = "telemetry"; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { - msgPayload["battery_level"] = new JSONValue((unsigned int)decoded->variant.device_metrics.battery_level); - msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage); - msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization); - msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx); - msgPayload["uptime_seconds"] = new JSONValue((unsigned int)decoded->variant.device_metrics.uptime_seconds); - } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { - msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature); - msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity); - msgPayload["barometric_pressure"] = new JSONValue(decoded->variant.environment_metrics.barometric_pressure); - msgPayload["gas_resistance"] = new JSONValue(decoded->variant.environment_metrics.gas_resistance); - msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage); - msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current); - msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux); - msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux); - msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); - msgPayload["wind_speed"] = new JSONValue(decoded->variant.environment_metrics.wind_speed); - msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction); - msgPayload["wind_gust"] = new JSONValue(decoded->variant.environment_metrics.wind_gust); - msgPayload["wind_lull"] = new JSONValue(decoded->variant.environment_metrics.wind_lull); - } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { - msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); - msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); - msgPayload["voltage_ch2"] = new JSONValue(decoded->variant.power_metrics.ch2_voltage); - msgPayload["current_ch2"] = new JSONValue(decoded->variant.power_metrics.ch2_current); - msgPayload["voltage_ch3"] = new JSONValue(decoded->variant.power_metrics.ch3_voltage); - msgPayload["current_ch3"] = new JSONValue(decoded->variant.power_metrics.ch3_current); - } - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for telemetry message!\n"); - } - break; - } - case meshtastic_PortNum_NODEINFO_APP: { - msgType = "nodeinfo"; - meshtastic_User scratch; - meshtastic_User *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { - decoded = &scratch; - msgPayload["id"] = new JSONValue(decoded->id); - msgPayload["longname"] = new JSONValue(decoded->long_name); - msgPayload["shortname"] = new JSONValue(decoded->short_name); - msgPayload["hardware"] = new JSONValue(decoded->hw_model); - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); - } - break; - } - case meshtastic_PortNum_POSITION_APP: { - msgType = "position"; - meshtastic_Position scratch; - meshtastic_Position *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { - decoded = &scratch; - if ((int)decoded->time) { - msgPayload["time"] = new JSONValue((unsigned int)decoded->time); - } - if ((int)decoded->timestamp) { - msgPayload["timestamp"] = new JSONValue((unsigned int)decoded->timestamp); - } - msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); - msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); - if ((int)decoded->altitude) { - msgPayload["altitude"] = new JSONValue((int)decoded->altitude); - } - if ((int)decoded->ground_speed) { - msgPayload["ground_speed"] = new JSONValue((unsigned int)decoded->ground_speed); - } - if (int(decoded->ground_track)) { - msgPayload["ground_track"] = new JSONValue((unsigned int)decoded->ground_track); - } - if (int(decoded->sats_in_view)) { - msgPayload["sats_in_view"] = new JSONValue((unsigned int)decoded->sats_in_view); - } - if ((int)decoded->PDOP) { - msgPayload["PDOP"] = new JSONValue((int)decoded->PDOP); - } - if ((int)decoded->HDOP) { - msgPayload["HDOP"] = new JSONValue((int)decoded->HDOP); - } - if ((int)decoded->VDOP) { - msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP); - } - if ((int)decoded->precision_bits) { - msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits); - } - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for position message!\n"); - } - break; - } - case meshtastic_PortNum_WAYPOINT_APP: { - msgType = "position"; - meshtastic_Waypoint scratch; - meshtastic_Waypoint *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { - decoded = &scratch; - msgPayload["id"] = new JSONValue((unsigned int)decoded->id); - msgPayload["name"] = new JSONValue(decoded->name); - msgPayload["description"] = new JSONValue(decoded->description); - msgPayload["expire"] = new JSONValue((unsigned int)decoded->expire); - msgPayload["locked_to"] = new JSONValue((unsigned int)decoded->locked_to); - msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); - msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for position message!\n"); - } - break; - } - case meshtastic_PortNum_NEIGHBORINFO_APP: { - msgType = "neighborinfo"; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, - &scratch)) { - decoded = &scratch; - msgPayload["node_id"] = new JSONValue((unsigned int)decoded->node_id); - msgPayload["node_broadcast_interval_secs"] = new JSONValue((unsigned int)decoded->node_broadcast_interval_secs); - msgPayload["last_sent_by_id"] = new JSONValue((unsigned int)decoded->last_sent_by_id); - msgPayload["neighbors_count"] = new JSONValue(decoded->neighbors_count); - JSONArray neighbors; - for (uint8_t i = 0; i < decoded->neighbors_count; i++) { - JSONObject neighborObj; - neighborObj["node_id"] = new JSONValue((unsigned int)decoded->neighbors[i].node_id); - neighborObj["snr"] = new JSONValue((int)decoded->neighbors[i].snr); - neighbors.push_back(new JSONValue(neighborObj)); - } - msgPayload["neighbors"] = new JSONValue(neighbors); - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); - } - break; - } - case meshtastic_PortNum_TRACEROUTE_APP: { - if (mp->decoded.request_id) { // Only report the traceroute response - msgType = "traceroute"; - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, - &scratch)) { - decoded = &scratch; - JSONArray route; // Route this message took - // Lambda function for adding a long name to the route - auto addToRoute = [](JSONArray *route, NodeNum num) { - char long_name[40] = "Unknown"; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); - bool name_known = node ? node->has_user : false; - if (name_known) - memcpy(long_name, node->user.long_name, sizeof(long_name)); - route->push_back(new JSONValue(long_name)); - }; - addToRoute(&route, mp->to); // Started at the original transmitter (destination of response) - for (uint8_t i = 0; i < decoded->route_count; i++) { - addToRoute(&route, decoded->route[i]); - } - addToRoute(&route, mp->from); // Ended at the original destination (source of response) - - msgPayload["route"] = new JSONValue(route); - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for traceroute message!\n"); - } - } - break; - } - case meshtastic_PortNum_DETECTION_SENSOR_APP: { - msgType = "detection"; - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - msgPayload["text"] = new JSONValue(payloadStr); - jsonObj["payload"] = new JSONValue(msgPayload); - break; - } -#ifdef ARCH_ESP32 - case meshtastic_PortNum_PAXCOUNTER_APP: { - msgType = "paxcounter"; - meshtastic_Paxcount scratch; - meshtastic_Paxcount *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Paxcount_msg, &scratch)) { - decoded = &scratch; - msgPayload["wifi_count"] = new JSONValue((unsigned int)decoded->wifi); - msgPayload["ble_count"] = new JSONValue((unsigned int)decoded->ble); - msgPayload["uptime"] = new JSONValue((unsigned int)decoded->uptime); - jsonObj["payload"] = new JSONValue(msgPayload); - } else { - LOG_ERROR("Error decoding protobuf for Paxcount message!\n"); - } - break; - } -#endif - case meshtastic_PortNum_REMOTE_HARDWARE_APP: { - meshtastic_HardwareMessage scratch; - meshtastic_HardwareMessage *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, - &scratch)) { - decoded = &scratch; - if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { - msgType = "gpios_changed"; - msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value); - jsonObj["payload"] = new JSONValue(msgPayload); - } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { - msgType = "gpios_read_reply"; - msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value); - msgPayload["gpio_mask"] = new JSONValue((unsigned int)decoded->gpio_mask); - jsonObj["payload"] = new JSONValue(msgPayload); - } - } else { - LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); - } - break; - } - // add more packet types here if needed - default: - break; - } - } else { - LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); - } - - jsonObj["id"] = new JSONValue((unsigned int)mp->id); - jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time); - jsonObj["to"] = new JSONValue((unsigned int)mp->to); - jsonObj["from"] = new JSONValue((unsigned int)mp->from); - jsonObj["channel"] = new JSONValue((unsigned int)mp->channel); - jsonObj["type"] = new JSONValue(msgType.c_str()); - jsonObj["sender"] = new JSONValue(owner.id); - if (mp->rx_rssi != 0) - jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); - if (mp->rx_snr != 0) - jsonObj["snr"] = new JSONValue((float)mp->rx_snr); - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { - jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); - jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); - } - - // serialize and write it to the stream - JSONValue *value = new JSONValue(jsonObj); - std::string jsonStr = value->Stringify(); - - LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); - - delete value; - return jsonStr; -} - bool MQTT::isValidJsonEnvelope(JSONObject &json) { // if "sender" is provided, avoid processing packets we uplinked diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index d68f1b88d..ba0987783 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -5,7 +5,7 @@ #include "concurrency/OSThread.h" #include "mesh/Channels.h" #include "mesh/generated/meshtastic/mqtt.pb.h" -#include "mqtt/JSON.h" +#include "serialization/JSON.h" #if HAS_WIFI #include #if !defined(ARCH_PORTDUINO) @@ -106,9 +106,6 @@ class MQTT : private concurrency::OSThread /// Called when a new publish arrives from the MQTT server void onReceive(char *topic, byte *payload, size_t length); - /// Called when a new publish arrives from the MQTT server - std::string meshPacketToJson(meshtastic_MeshPacket *mp); - void publishQueuedMessages(); void publishNodeInfo(); diff --git a/src/mqtt/JSON.cpp b/src/serialization/JSON.cpp similarity index 100% rename from src/mqtt/JSON.cpp rename to src/serialization/JSON.cpp diff --git a/src/mqtt/JSON.h b/src/serialization/JSON.h similarity index 100% rename from src/mqtt/JSON.h rename to src/serialization/JSON.h diff --git a/src/mqtt/JSONValue.cpp b/src/serialization/JSONValue.cpp similarity index 100% rename from src/mqtt/JSONValue.cpp rename to src/serialization/JSONValue.cpp diff --git a/src/mqtt/JSONValue.h b/src/serialization/JSONValue.h similarity index 100% rename from src/mqtt/JSONValue.h rename to src/serialization/JSONValue.h diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp new file mode 100644 index 000000000..dc2db1e6f --- /dev/null +++ b/src/serialization/MeshPacketSerializer.cpp @@ -0,0 +1,317 @@ +#include "MeshPacketSerializer.h" +#include "JSON.h" +#include "NodeDB.h" +#include "mesh/generated/meshtastic/mqtt.pb.h" +#include "mesh/generated/meshtastic/telemetry.pb.h" +#include "modules/RoutingModule.h" +#include +#include +#if defined(ARCH_ESP32) +#include "../mesh/generated/meshtastic/paxcount.pb.h" +#endif +#include "mesh/generated/meshtastic/remote_hardware.pb.h" + +std::string MeshPacketSerializer::JsonSerialize(meshtastic_MeshPacket *mp, bool shouldLog) +{ + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; + JSONObject jsonObj; + + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + JSONObject msgPayload; + switch (mp->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) { + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); + + // if it is, then we can just use the json object + jsonObj["payload"] = json_value; + } else { + // if it isn't, then we need to create a json object + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); + + msgPayload["text"] = new JSONValue(payloadStr); + jsonObj["payload"] = new JSONValue(msgPayload); + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + msgPayload["battery_level"] = new JSONValue((unsigned int)decoded->variant.device_metrics.battery_level); + msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage); + msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization); + msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx); + msgPayload["uptime_seconds"] = new JSONValue((unsigned int)decoded->variant.device_metrics.uptime_seconds); + } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature); + msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity); + msgPayload["barometric_pressure"] = new JSONValue(decoded->variant.environment_metrics.barometric_pressure); + msgPayload["gas_resistance"] = new JSONValue(decoded->variant.environment_metrics.gas_resistance); + msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage); + msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current); + msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux); + msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux); + msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); + msgPayload["wind_speed"] = new JSONValue(decoded->variant.environment_metrics.wind_speed); + msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction); + msgPayload["wind_gust"] = new JSONValue(decoded->variant.environment_metrics.wind_gust); + msgPayload["wind_lull"] = new JSONValue(decoded->variant.environment_metrics.wind_lull); + } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); + msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); + msgPayload["voltage_ch2"] = new JSONValue(decoded->variant.power_metrics.ch2_voltage); + msgPayload["current_ch2"] = new JSONValue(decoded->variant.power_metrics.ch2_current); + msgPayload["voltage_ch3"] = new JSONValue(decoded->variant.power_metrics.ch3_voltage); + msgPayload["current_ch3"] = new JSONValue(decoded->variant.power_metrics.ch3_current); + } + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { + decoded = &scratch; + msgPayload["id"] = new JSONValue(decoded->id); + msgPayload["longname"] = new JSONValue(decoded->long_name); + msgPayload["shortname"] = new JSONValue(decoded->short_name); + msgPayload["hardware"] = new JSONValue(decoded->hw_model); + msgPayload["role"] = new JSONValue((int)decoded->role); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + } + break; + } + case meshtastic_PortNum_POSITION_APP: { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { + decoded = &scratch; + if ((int)decoded->time) { + msgPayload["time"] = new JSONValue((unsigned int)decoded->time); + } + if ((int)decoded->timestamp) { + msgPayload["timestamp"] = new JSONValue((unsigned int)decoded->timestamp); + } + msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); + msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); + if ((int)decoded->altitude) { + msgPayload["altitude"] = new JSONValue((int)decoded->altitude); + } + if ((int)decoded->ground_speed) { + msgPayload["ground_speed"] = new JSONValue((unsigned int)decoded->ground_speed); + } + if (int(decoded->ground_track)) { + msgPayload["ground_track"] = new JSONValue((unsigned int)decoded->ground_track); + } + if (int(decoded->sats_in_view)) { + msgPayload["sats_in_view"] = new JSONValue((unsigned int)decoded->sats_in_view); + } + if ((int)decoded->PDOP) { + msgPayload["PDOP"] = new JSONValue((int)decoded->PDOP); + } + if ((int)decoded->HDOP) { + msgPayload["HDOP"] = new JSONValue((int)decoded->HDOP); + } + if ((int)decoded->VDOP) { + msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP); + } + if ((int)decoded->precision_bits) { + msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits); + } + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + decoded = &scratch; + msgPayload["id"] = new JSONValue((unsigned int)decoded->id); + msgPayload["name"] = new JSONValue(decoded->name); + msgPayload["description"] = new JSONValue(decoded->description); + msgPayload["expire"] = new JSONValue((unsigned int)decoded->expire); + msgPayload["locked_to"] = new JSONValue((unsigned int)decoded->locked_to); + msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); + msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) { + decoded = &scratch; + msgPayload["node_id"] = new JSONValue((unsigned int)decoded->node_id); + msgPayload["node_broadcast_interval_secs"] = new JSONValue((unsigned int)decoded->node_broadcast_interval_secs); + msgPayload["last_sent_by_id"] = new JSONValue((unsigned int)decoded->last_sent_by_id); + msgPayload["neighbors_count"] = new JSONValue(decoded->neighbors_count); + JSONArray neighbors; + for (uint8_t i = 0; i < decoded->neighbors_count; i++) { + JSONObject neighborObj; + neighborObj["node_id"] = new JSONValue((unsigned int)decoded->neighbors[i].node_id); + neighborObj["snr"] = new JSONValue((int)decoded->neighbors[i].snr); + neighbors.push_back(new JSONValue(neighborObj)); + } + msgPayload["neighbors"] = new JSONValue(neighbors); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + } + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: { + if (mp->decoded.request_id) { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) { + decoded = &scratch; + JSONArray route; // Route this message took + // Lambda function for adding a long name to the route + auto addToRoute = [](JSONArray *route, NodeNum num) { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->push_back(new JSONValue(long_name)); + }; + addToRoute(&route, mp->to); // Started at the original transmitter (destination of response) + for (uint8_t i = 0; i < decoded->route_count; i++) { + addToRoute(&route, decoded->route[i]); + } + addToRoute(&route, mp->from); // Ended at the original destination (source of response) + + msgPayload["route"] = new JSONValue(route); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + } + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + msgPayload["text"] = new JSONValue(payloadStr); + jsonObj["payload"] = new JSONValue(msgPayload); + break; + } +#ifdef ARCH_ESP32 + case meshtastic_PortNum_PAXCOUNTER_APP: { + msgType = "paxcounter"; + meshtastic_Paxcount scratch; + meshtastic_Paxcount *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Paxcount_msg, &scratch)) { + decoded = &scratch; + msgPayload["wifi_count"] = new JSONValue((unsigned int)decoded->wifi); + msgPayload["ble_count"] = new JSONValue((unsigned int)decoded->ble); + msgPayload["uptime"] = new JSONValue((unsigned int)decoded->uptime); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for Paxcount message!\n"); + } + break; + } +#endif + case meshtastic_PortNum_REMOTE_HARDWARE_APP: { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { + msgType = "gpios_changed"; + msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { + msgType = "gpios_read_reply"; + msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value); + msgPayload["gpio_mask"] = new JSONValue((unsigned int)decoded->gpio_mask); + jsonObj["payload"] = new JSONValue(msgPayload); + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + } + break; + } + // add more packet types here if needed + default: + break; + } + } else if (shouldLog) { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + } + + jsonObj["id"] = new JSONValue((unsigned int)mp->id); + jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time); + jsonObj["to"] = new JSONValue((unsigned int)mp->to); + jsonObj["from"] = new JSONValue((unsigned int)mp->from); + jsonObj["channel"] = new JSONValue((unsigned int)mp->channel); + jsonObj["type"] = new JSONValue(msgType.c_str()); + jsonObj["sender"] = new JSONValue(owner.id); + if (mp->rx_rssi != 0) + jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); + if (mp->rx_snr != 0) + jsonObj["snr"] = new JSONValue((float)mp->rx_snr); + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); + jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); + } + + // serialize and write it to the stream + JSONValue *value = new JSONValue(jsonObj); + std::string jsonStr = value->Stringify(); + + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + + delete value; + return jsonStr; +} \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer.h b/src/serialization/MeshPacketSerializer.h new file mode 100644 index 000000000..579ee2fd6 --- /dev/null +++ b/src/serialization/MeshPacketSerializer.h @@ -0,0 +1,8 @@ +#include +#include + +class MeshPacketSerializer +{ + public: + static std::string JsonSerialize(meshtastic_MeshPacket *mp, bool shouldLog = true); +}; \ No newline at end of file From 755952c261322a97c521c81fc2966f5b7fe0acfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Jul 2024 06:29:05 -0500 Subject: [PATCH 0740/1377] [create-pull-request] automated change (#4333) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 7f90178f1..b1a79d5db 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 7f90178f183820e288aec41133144f30723228fe +Subproject commit b1a79d5db00f6aeeb0bbae156e8939e146c95299 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 841ca7aa4..ca860aed5 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -178,6 +178,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 = 69, /* Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor */ meshtastic_HardwareModel_SENSECAP_INDICATOR = 70, + /* Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors. */ + meshtastic_HardwareModel_TRACKER_T1000_E = 71, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 394e0e1b3e33cbf64d6518e4b7911a402eae5284 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 26 Jul 2024 06:30:28 -0500 Subject: [PATCH 0741/1377] T1000_E hw model --- src/platform/nrf52/architecture.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index b66552a28..99879f0f6 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -60,6 +60,8 @@ #define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY #elif defined(WIO_WM1110) #define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110 +#elif defined(TRACKER_T1000_E) +#define HW_VENDOR meshtastic_HardwareModel_TRACKER_T1000_E #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else From 4ee15d81282e7f861d55fc79a141292699eae790 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 26 Jul 2024 06:38:54 -0500 Subject: [PATCH 0742/1377] Trunk --- src/platform/nrf52/architecture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 99879f0f6..a276a6081 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -60,7 +60,7 @@ #define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY #elif defined(WIO_WM1110) #define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110 -#elif defined(TRACKER_T1000_E) +#elif defined(TRACKER_T1000_E) #define HW_VENDOR meshtastic_HardwareModel_TRACKER_T1000_E #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW From 8641777bac4a81a8dbe5c31d1261bba53ca4a4f6 Mon Sep 17 00:00:00 2001 From: Nestpebble <116762865+Nestpebble@users.noreply.github.com> Date: Sat, 27 Jul 2024 02:14:31 +0100 Subject: [PATCH 0743/1377] Add the UF2 conversion script to the p.io task menu (#4337) * Add the UF2 conversion script to the p.io task menu Update platformio-custom.py to include the UF2 conversion script as a project task. Saves you dropping into the command line every time. Tested on Windows only... * Forgot the build target... --- bin/platformio-custom.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 3202a1e7a..065f1267b 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -91,3 +91,12 @@ projenv.Append( "-DAPP_VERSION_SHORT=" + verObj["short"], ] ) + +# Add a custom p.io project task to run the UF2 conversion script. +env.AddCustomTarget( + name="Convert Hex to UF2", + dependencies=None, + actions=["PYTHON .\\bin\\uf2conv.py $BUILD_DIR\$env\\firmware.hex -c -f 0xADA52840 -o $BUILD_DIR\$env\\firmware.uf2"], + title="Convert hex to uf2", + description="Runs the python script to convert an already-built .hex file into .uf2 for copying to a device" +) From 6f235232f04523c17d091e57ccc19014e40944b1 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 27 Jul 2024 13:58:55 +0300 Subject: [PATCH 0744/1377] Added RF95 SX1268 support (#4338) --- variants/diy/nrf52_promicro_diy_tcxo/variant.h | 2 ++ variants/diy/nrf52_promicro_diy_xtal/variant.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/diy/nrf52_promicro_diy_tcxo/variant.h index 8b957fe12..bacc0796d 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.h @@ -115,6 +115,8 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LORA MODULES #define USE_LLCC68 #define USE_SX1262 +#define USE_RF95 +#define USE_SX1268 // LORA CONFIG #define SX126X_CS (32 + 13) // P1.13 FIXME - we really should define LORA_CS instead diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.h b/variants/diy/nrf52_promicro_diy_xtal/variant.h index fd0b21681..c00c424cc 100644 --- a/variants/diy/nrf52_promicro_diy_xtal/variant.h +++ b/variants/diy/nrf52_promicro_diy_xtal/variant.h @@ -114,6 +114,8 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LORA MODULES #define USE_LLCC68 #define USE_SX1262 +#define USE_RF95 +#define USE_SX1268 // LORA CONFIG #define SX126X_CS (32 + 13) // P1.13 FIXME - we really should define LORA_CS instead From f583837b4edfc1c75baa24534e90afb4f45b3a2c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 27 Jul 2024 06:39:16 -0500 Subject: [PATCH 0745/1377] Enable STM32 build (#4339) * Enable stm32 builds and wio-e5 board * Chmod --- .github/workflows/build_stm32.yml | 33 +++++++++++++++++++++++++++++++ .github/workflows/main_matrix.yml | 10 ++++++++++ bin/build-stm32.sh | 29 +++++++++++++++++++++++++++ variants/wio-e5/platformio.ini | 1 - 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_stm32.yml create mode 100755 bin/build-stm32.sh diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml new file mode 100644 index 000000000..d13c52c8a --- /dev/null +++ b/.github/workflows/build_stm32.yml @@ -0,0 +1,33 @@ +name: Build STM32 + +on: + workflow_call: + inputs: + board: + required: true + type: string + +jobs: + build-stm32: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Build STM32 + run: bin/build-stm32.sh ${{ inputs.board }} + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | + release/*.hex + release/*.bin diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 14c8a9d10..b1d1d307a 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -41,6 +41,7 @@ jobs: esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + stm32: ${{ steps.jsonStep.outputs.stm32 }} check: ${{ steps.jsonStep.outputs.check }} check: @@ -103,6 +104,15 @@ jobs: with: board: ${{ matrix.board }} + build-stm32: + needs: setup + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.stm32) }} + uses: ./.github/workflows/build_stm32.yml + with: + board: ${{ matrix.board }} + package-raspbian: uses: ./.github/workflows/package_raspbian.yml diff --git a/bin/build-stm32.sh b/bin/build-stm32.sh new file mode 100755 index 000000000..9e813a8c0 --- /dev/null +++ b/bin/build-stm32.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e + +VERSION=$(bin/buildinfo.py long) +SHORT_VERSION=$(bin/buildinfo.py short) + +OUTDIR=release/ + +rm -f $OUTDIR/firmware* +rm -r $OUTDIR/* || true + +# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale +platformio pkg update -e $1 + +echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" +rm -f .pio/build/$1/firmware.* + +# The shell vars the build tool expects to find +export APP_VERSION=$VERSION + +basename=firmware-$1-$VERSION + +pio run --environment $1 # -v +SRCELF=.pio/build/$1/firmware.elf +cp $SRCELF $OUTDIR/$basename.elf + +SRCELF=.pio/build/$1/firmware.bin +cp $SRCHEX $OUTDIR/$basename.bin diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini index 12cd6190d..51591d569 100644 --- a/variants/wio-e5/platformio.ini +++ b/variants/wio-e5/platformio.ini @@ -1,6 +1,5 @@ [env:wio-e5] extends = stm32_base -board_level = extra board = lora_e5_dev_board build_flags = ${stm32_base.build_flags} From e70435ebd7b2635fbc67aa0371b7c2697d15a0a0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 27 Jul 2024 06:49:11 -0500 Subject: [PATCH 0746/1377] All builds need to only pkg update for their target environment --- .github/workflows/main_matrix.yml | 2 +- bin/build-esp32.sh | 2 +- bin/build-nrf52.sh | 2 +- bin/build-rpi2040.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index b1d1d307a..ae50b94ec 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, check] + arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check] runs-on: ubuntu-latest steps: - id: checkout diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh index f60331ed5..adb6ab12a 100755 --- a/bin/build-esp32.sh +++ b/bin/build-esp32.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index c0658dad9..060d06cfd 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* diff --git a/bin/build-rpi2040.sh b/bin/build-rpi2040.sh index fe0725085..dad6a7e67 100755 --- a/bin/build-rpi2040.sh +++ b/bin/build-rpi2040.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update +platformio pkg update -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* From bca9fbe7e4f91daf809eb8131b8a25385df7d35f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 27 Jul 2024 06:49:50 -0500 Subject: [PATCH 0747/1377] Missed a needs --- .github/workflows/main_matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ae50b94ec..36125f72f 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -144,6 +144,7 @@ jobs: build-esp32-c3, build-nrf52, build-rpi2040, + build-stm32, package-raspbian, package-raspbian-armv7l, package-native, From 1b249c32bfb10d249f069b17cbe74ddb95db5bd1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 27 Jul 2024 07:28:11 -0500 Subject: [PATCH 0748/1377] Copy the actual bin --- bin/build-stm32.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/build-stm32.sh b/bin/build-stm32.sh index 9e813a8c0..76c5a75fb 100755 --- a/bin/build-stm32.sh +++ b/bin/build-stm32.sh @@ -25,5 +25,5 @@ pio run --environment $1 # -v SRCELF=.pio/build/$1/firmware.elf cp $SRCELF $OUTDIR/$basename.elf -SRCELF=.pio/build/$1/firmware.bin -cp $SRCHEX $OUTDIR/$basename.bin +SRCBIN=.pio/build/$1/firmware.bin +cp $SRCBIN $OUTDIR/$basename.bin From 32bc2f1137ab04440569fae9394a6acba1495110 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 27 Jul 2024 09:38:28 -0500 Subject: [PATCH 0749/1377] Add RAK3172 to the STM32WL canon --- variants/rak3172/platformio.ini | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/variants/rak3172/platformio.ini b/variants/rak3172/platformio.ini index 63766c988..d1bd55e83 100644 --- a/variants/rak3172/platformio.ini +++ b/variants/rak3172/platformio.ini @@ -1,12 +1,31 @@ [env:rak3172] extends = stm32_base -board_level = extra board = wiscore_rak3172 build_flags = ${stm32_base.build_flags} -Ivariants/rak3172 - -DHAL_DAC_MODULE_ONLY -DSERIAL_UART_INSTANCE=1 -DPIN_SERIAL_RX=PB7 -DPIN_SERIAL_TX=PB6 + -DHAL_DAC_MODULE_ONLY + -DHAL_ADC_MODULE_DISABLED + -DHAL_COMP_MODULE_DISABLED + -DHAL_CRC_MODULE_DISABLED + -DHAL_CRYP_MODULE_DISABLED + -DHAL_GTZC_MODULE_DISABLED + -DHAL_HSEM_MODULE_DISABLED + -DHAL_I2C_MODULE_DISABLED + -DHAL_I2S_MODULE_DISABLED + -DHAL_IPCC_MODULE_DISABLED + -DHAL_IRDA_MODULE_DISABLED + -DHAL_IWDG_MODULE_DISABLED + -DHAL_LPTIM_MODULE_DISABLED + -DHAL_PKA_MODULE_DISABLED + -DHAL_RNG_MODULE_DISABLED + -DHAL_RTC_MODULE_DISABLED + -DHAL_SMARTCARD_MODULE_DISABLED + -DHAL_SMBUS_MODULE_DISABLED + -DHAL_TIM_MODULE_DISABLED + -DHAL_WWDG_MODULE_DISABLED + -DHAL_EXTI_MODULE_DISABLED upload_port = stlink \ No newline at end of file From 1a1d545c38bfb3daa298d4aee03ec97f51d1b3ee Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 28 Jul 2024 14:12:30 -0500 Subject: [PATCH 0750/1377] Adds a userPrefs.h file, default blank, used for default settings for custom builds (#4325) * add a userPrefs.h file, default blank, which can be used to easily set defaults on custom builds. * Add Splash Screen to userPrefs * Add channel 0 defaults to userPrefs.h * CONFIG_LORA_IGNORE_MQTT_DEFAULT * Unify naming for USERPREFS defines --------- Co-authored-by: Ben Meadors --- src/graphics/Screen.cpp | 5 +++++ src/graphics/img/icon.xbm | 4 +++- src/mesh/Channels.cpp | 27 ++++++++++++++++++++++++++- src/mesh/NodeDB.cpp | 13 +++++++++++++ userPrefs.h | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 userPrefs.h diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b2059b71c..54fd1ea4d 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -20,6 +20,7 @@ along with this program. If not, see . */ #include "Screen.h" +#include "../userPrefs.h" #include "configuration.h" #if HAS_SCREEN #include @@ -156,7 +157,11 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl display->setFont(FONT_MEDIUM); display->setTextAlignment(TEXT_ALIGN_LEFT); +#ifdef SPLASH_TITLE_USERPREFS + const char *title = SPLASH_TITLE_USERPREFS; +#else const char *title = "meshtastic.org"; +#endif display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); display->setFont(FONT_SMALL); diff --git a/src/graphics/img/icon.xbm b/src/graphics/img/icon.xbm index 297f31ed6..f90cf4946 100644 --- a/src/graphics/img/icon.xbm +++ b/src/graphics/img/icon.xbm @@ -1,3 +1,4 @@ +#ifndef HAS_USERPREFS_SPLASH #define icon_width 50 #define icon_height 28 static uint8_t icon_bits[] = { @@ -17,4 +18,5 @@ static uint8_t icon_bits[] = { 0xFE, 0x00, 0x00, 0xFC, 0x01, 0x7E, 0x00, 0x7F, 0x00, 0x00, 0xF8, 0x01, 0x7E, 0x00, 0x3E, 0x00, 0x00, 0xF8, 0x01, 0x38, 0x00, 0x3C, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, }; \ No newline at end of file + 0x00, 0x00, 0x00, 0x00, }; +#endif \ No newline at end of file diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index bb4d629e7..1a23c7861 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -1,4 +1,5 @@ #include "Channels.h" +#include "../userPrefs.h" #include "CryptoEngine.h" #include "DisplayFormatters.h" #include "NodeDB.h" @@ -90,6 +91,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast loraConfig.use_preset = true; loraConfig.tx_power = 0; // default + loraConfig.channel_num = 0; uint8_t defaultpskIndex = 1; channelSettings.psk.bytes[0] = defaultpskIndex; channelSettings.psk.size = 1; @@ -99,6 +101,29 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) ch.has_settings = true; ch.role = meshtastic_Channel_Role_PRIMARY; + +#ifdef LORACONFIG_MODEM_PRESET_USERPREFS + loraConfig.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS; +#endif +#ifdef LORACONFIG_CHANNEL_NUM_USERPREFS + loraConfig.channel_num = LORACONFIG_CHANNEL_NUM_USERPREFS; +#endif + + // Install custom defaults. Will eventually support setting multiple default channels + if (chIndex == 0) { +#ifdef CHANNEL_0_PSK_USERPREFS + static const uint8_t defaultpsk[] = CHANNEL_0_PSK_USERPREFS; + memcpy(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)); + channelSettings.psk.size = sizeof(defaultpsk); + +#endif +#ifdef CHANNEL_0_NAME_USERPREFS + strcpy(channelSettings.name, CHANNEL_0_NAME_USERPREFS); +#endif +#ifdef CHANNEL_0_PRECISION_USERPREFS + channelSettings.module_settings.position_precision = CHANNEL_0_PRECISION_USERPREFS; +#endif + } } CryptoKey Channels::getKey(ChannelIndex chIndex) @@ -330,4 +355,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) int16_t Channels::setActiveByIndex(ChannelIndex channelIndex) { return setCrypto(channelIndex); -} +} \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 4257837b3..b4b5ec286 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1,3 +1,4 @@ +#include "../userPrefs.h" #include "configuration.h" #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" @@ -237,10 +238,22 @@ void NodeDB::installDefaultConfig() config.lora.tx_enabled = true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) config.lora.override_duty_cycle = false; +#ifdef CONFIG_LORA_REGION_USERPREFS + config.lora.region = CONFIG_LORA_REGION_USERPREFS; +#else config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; +#endif +#ifdef LORACONFIG_MODEM_PRESET_USERPREFS + config.lora.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS; +#else config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; +#endif config.lora.hop_limit = HOP_RELIABLE; +#ifdef CONFIG_LORA_IGNORE_MQTT_USERPREFS + config.lora.ignore_mqtt = CONFIG_LORA_IGNORE_MQTT_USERPREFS; +#else config.lora.ignore_mqtt = false; +#endif #ifdef PIN_GPS_EN config.position.gps_en_gpio = PIN_GPS_EN; #endif diff --git a/userPrefs.h b/userPrefs.h new file mode 100644 index 000000000..e03b70023 --- /dev/null +++ b/userPrefs.h @@ -0,0 +1,33 @@ +#ifndef _USERPREFS_ +#define _USERPREFS_ +// Uncomment and modify to set device defaults + +// #define CONFIG_LORA_REGION_USERPREFS meshtastic_Config_LoRaConfig_RegionCode_US +// #define LORACONFIG_MODEM_PRESET_USERPREFS meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST +// #define LORACONFIG_CHANNEL_NUM_USERPREFS 31 +// #define CONFIG_LORA_IGNORE_MQTT_USERPREFS true +/* +#define CHANNEL_0_PSK_USERPREFS \ + { \ + 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, \ + 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 \ + } +*/ +// #define CHANNEL_0_NAME_USERPREFS "DEFCONnect" +// #define CHANNEL_0_PRECISION_USERPREFS 13 + +// #define SPLASH_TITLE_USERPREFS "DEFCONtastic" +// #define icon_width 34 +// #define icon_height 29 +// #define HAS_USERPREFS_SPLASH +/* +static unsigned char icon_bits[] = { + 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, + 0x9E, 0xE7, 0x00, 0x00, 0x00, 0x0E, 0xC7, 0x01, 0x00, 0x1C, 0x0F, 0xC7, 0x01, 0x00, 0x1C, 0xDF, 0xE7, 0x63, 0x00, 0x1C, 0xFF, + 0xBF, 0xE1, 0x00, 0x3C, 0xF3, 0xBF, 0xE3, 0x00, 0x7F, 0xF7, 0xBF, 0xF1, 0x00, 0xFF, 0xF7, 0xBF, 0xF9, 0x03, 0xFF, 0xE7, 0x9F, + 0xFF, 0x03, 0xC0, 0xCF, 0xEF, 0xDF, 0x03, 0x00, 0xDF, 0xE3, 0x8F, 0x00, 0x00, 0x7C, 0xFB, 0x03, 0x00, 0x00, 0xF8, 0xFF, 0x00, + 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x78, 0x3F, 0x00, 0x00, 0x00, 0xFC, 0xFC, 0x00, 0x00, + 0x98, 0x3F, 0xF0, 0x23, 0x00, 0xFC, 0x0F, 0xE0, 0x7F, 0x00, 0xFC, 0x03, 0x80, 0xFF, 0x01, 0xFC, 0x00, 0x00, 0x3E, 0x00, 0x70, + 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00}; +*/ +#endif \ No newline at end of file From 8b0208d1c6ec750ad343ec71fc0ccf742646df77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 28 Jul 2024 14:53:25 -0500 Subject: [PATCH 0751/1377] [create-pull-request] automated change (#4335) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 4c87608d0..b8bb5e67d 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 4 -build = 1 +build = 2 From 811a9ae2615e8586d18bf31725370e103be87d5d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 28 Jul 2024 19:49:10 -0500 Subject: [PATCH 0752/1377] Macro to trace log all MeshPackets as JSON (#4336) * Macro to trace log all MeshPackets as JSON * Comment * Add trace logging to file for native target * bytes to hex * Add time_ms --------- Co-authored-by: Jonathan Bennett --- bin/config-dist.yaml | 3 +- src/RedirectablePrint.cpp | 22 +++++++++++++- src/mesh/Router.cpp | 24 +++++++++++++++ src/platform/portduino/PortduinoGlue.cpp | 14 ++++++++- src/platform/portduino/PortduinoGlue.h | 5 +++- src/serialization/MeshPacketSerializer.cpp | 34 +++++++++++++++++++++- src/serialization/MeshPacketSerializer.h | 17 ++++++++++- 7 files changed, 113 insertions(+), 6 deletions(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 333d6eadc..0ec5a440b 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -135,10 +135,11 @@ Input: Logging: LogLevel: info # debug, info, warn, error +# TraceFile: /var/log/meshtasticd.json Webserver: # Port: 443 # Port for Webserver & Webservices # RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer General: - MaxNodes: 200 + MaxNodes: 200 \ No newline at end of file diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 9c3dcdc98..05d349de9 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -49,7 +49,11 @@ size_t RedirectablePrint::write(uint8_t c) size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) { va_list copy; +#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO + static char printBuf[512]; +#else static char printBuf[160]; +#endif va_copy(copy, arg); size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); @@ -98,6 +102,8 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, Print::write("\u001b[33m", 6); if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) Print::write("\u001b[31m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) + Print::write("\u001b[35m", 6); uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; @@ -244,7 +250,21 @@ meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel) void RedirectablePrint::log(const char *logLevel, const char *format, ...) { -#ifdef ARCH_PORTDUINO +#if ARCH_PORTDUINO + // level trace is special, two possible ways to handle it. + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { + if (settingsStrings[traceFilename] != "") { + va_list arg; + va_start(arg, format); + try { + traceFile << va_arg(arg, char *) << std::endl; + } catch (const std::ios_base::failure &e) { + } + va_end(arg); + } + if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) + return; + } if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) return; else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 35536e714..c1801d419 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -11,6 +11,12 @@ #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #endif +#if ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif +#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO +#include "serialization/MeshPacketSerializer.h" +#endif /** * Router todo * @@ -356,6 +362,13 @@ bool perhapsDecode(meshtastic_MeshPacket *p) } */ printPacket("decoded message", p); +#if ENABLE_JSON_LOGGING + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); +#elif ARCH_PORTDUINO + if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); + } +#endif return true; } } @@ -491,6 +504,17 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) { +#if ENABLE_JSON_LOGGING + // Even ignored packets get logged in the trace + p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); +#elif ARCH_PORTDUINO + // Even ignored packets get logged in the trace + if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { + p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); + } +#endif // assert(radioConfig.has_preferences); bool ignore = is_in_repeated(config.lora.ignore_incoming, p->from) || (config.lora.ignore_mqtt && p->via_mqtt); diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 89ac806dd..b910206ec 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -17,6 +17,7 @@ std::map settingsMap; std::map settingsStrings; +std::ofstream traceFile; char *configPath = nullptr; // FIXME - move setBluetoothEnable into a HALPlatform class @@ -134,7 +135,9 @@ void portduinoSetup() try { if (yamlConfig["Logging"]) { - if (yamlConfig["Logging"]["LogLevel"].as("info") == "debug") { + if (yamlConfig["Logging"]["LogLevel"].as("info") == "trace") { + settingsMap[logoutputlevel] = level_trace; + } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "debug") { settingsMap[logoutputlevel] = level_debug; } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "info") { settingsMap[logoutputlevel] = level_info; @@ -143,6 +146,7 @@ void portduinoSetup() } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "error") { settingsMap[logoutputlevel] = level_error; } + settingsStrings[traceFilename] = yamlConfig["Logging"]["TraceFile"].as(""); } if (yamlConfig["Lora"]) { settingsMap[use_sx1262] = false; @@ -346,6 +350,14 @@ void portduinoSetup() if (settingsStrings[spidev] != "") { SPI.begin(settingsStrings[spidev].c_str()); } + if (settingsStrings[traceFilename] != "") { + try { + traceFile.open(settingsStrings[traceFilename], std::ios::out | std::ios::app); + } catch (std::ofstream::failure &e) { + std::cout << "*** traceFile Exception " << e.what() << std::endl; + exit(EXIT_FAILURE); + } + } return; } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index ca935ea3b..6b9a8eb8e 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -1,4 +1,5 @@ #pragma once +#include #include enum configNames { @@ -46,6 +47,7 @@ enum configNames { displayInvert, keyboardDevice, logoutputlevel, + traceFilename, webserver, webserverport, webserverrootpath, @@ -53,8 +55,9 @@ enum configNames { }; enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; -enum { level_error, level_warn, level_info, level_debug }; +enum { level_error, level_warn, level_info, level_debug, level_trace }; extern std::map settingsMap; extern std::map settingsStrings; +extern std::ofstream traceFile; int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index dc2db1e6f..a42bb867a 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -11,7 +11,7 @@ #endif #include "mesh/generated/meshtastic/remote_hardware.pb.h" -std::string MeshPacketSerializer::JsonSerialize(meshtastic_MeshPacket *mp, bool shouldLog) +std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { // the created jsonObj is immutable after creation, so // we need to do the heavy lifting before assembling it. @@ -312,6 +312,38 @@ std::string MeshPacketSerializer::JsonSerialize(meshtastic_MeshPacket *mp, bool if (shouldLog) LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + delete value; + return jsonStr; +} + +std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) +{ + JSONObject jsonObj; + + jsonObj["id"] = new JSONValue((unsigned int)mp->id); + jsonObj["time_ms"] = new JSONValue((double)millis()); + jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time); + jsonObj["to"] = new JSONValue((unsigned int)mp->to); + jsonObj["from"] = new JSONValue((unsigned int)mp->from); + jsonObj["channel"] = new JSONValue((unsigned int)mp->channel); + jsonObj["want_ack"] = new JSONValue(mp->want_ack); + + if (mp->rx_rssi != 0) + jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); + if (mp->rx_snr != 0) + jsonObj["snr"] = new JSONValue((float)mp->rx_snr); + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit)); + jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start)); + } + jsonObj["size"] = new JSONValue((unsigned int)mp->encrypted.size); + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = new JSONValue(encryptedStr.c_str()); + + // serialize and write it to the stream + JSONValue *value = new JSONValue(jsonObj); + std::string jsonStr = value->Stringify(); + delete value; return jsonStr; } \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer.h b/src/serialization/MeshPacketSerializer.h index 579ee2fd6..03860ab35 100644 --- a/src/serialization/MeshPacketSerializer.h +++ b/src/serialization/MeshPacketSerializer.h @@ -1,8 +1,23 @@ #include #include +static const char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + class MeshPacketSerializer { public: - static std::string JsonSerialize(meshtastic_MeshPacket *mp, bool shouldLog = true); + static std::string JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog = true); + static std::string JsonSerializeEncrypted(const meshtastic_MeshPacket *mp); + + private: + static std::string bytesToHex(const uint8_t *bytes, int len) + { + std::string result = ""; + for (int i = 0; i < len; ++i) { + char const byte = bytes[i]; + result += hexChars[(byte & 0xF0) >> 4]; + result += hexChars[(byte & 0x0F) >> 0]; + } + return result; + } }; \ No newline at end of file From cf22b7ff04e9f51114762a4c7443d544bd7e0e8d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 06:11:08 -0500 Subject: [PATCH 0753/1377] Latest pip version of setuptools is broken. Install specific version --- .github/actions/setup-base/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 7f8659523..cbb1e12f7 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -33,6 +33,7 @@ runs: shell: bash run: | python -m pip install --upgrade pip + pip install -U setuptools<72.0.0 pip install -U platformio adafruit-nrfutil pip install -U meshtastic --pre From 8c0ff89972f017b9f478a53ed8777a6c37d8314c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 06:14:32 -0500 Subject: [PATCH 0754/1377] Trunk --- .github/actions/setup-base/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index cbb1e12f7..f74f702a7 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -11,7 +11,7 @@ runs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Install dependencies + - name: Install dependencies shell: bash run: | sudo apt-get -y update --fix-missing From c501cc501dc646607cbf7e5799daeaeadd599671 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 06:37:19 -0500 Subject: [PATCH 0755/1377] Pip pip cheerios plz --- .github/actions/setup-base/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index f74f702a7..80fa8db9d 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -32,8 +32,8 @@ runs: - name: Upgrade python tools shell: bash run: | + pip install --no-build-isolation --no-cache-dir "setuptools<72" python -m pip install --upgrade pip - pip install -U setuptools<72.0.0 pip install -U platformio adafruit-nrfutil pip install -U meshtastic --pre From 2ffc93324db330d17b68f5ed6d6db5bc83bb22e6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 06:38:34 -0500 Subject: [PATCH 0756/1377] After --- .github/actions/setup-base/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 80fa8db9d..1fb3d6c96 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -32,8 +32,8 @@ runs: - name: Upgrade python tools shell: bash run: | - pip install --no-build-isolation --no-cache-dir "setuptools<72" python -m pip install --upgrade pip + pip install --no-build-isolation --no-cache-dir "setuptools<72" pip install -U platformio adafruit-nrfutil pip install -U meshtastic --pre From 4aa6f60e95a6d3fb88f08110e65ff925f56d32c6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 06:41:26 -0500 Subject: [PATCH 0757/1377] Maybe remove pip cache --- .github/actions/setup-base/action.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 1fb3d6c96..e19eff786 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -22,12 +22,12 @@ runs: with: python-version: 3.x - - name: Cache python libs - uses: actions/cache@v4 - id: cache-pip # needed in if test - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip + # - name: Cache python libs + # uses: actions/cache@v4 + # id: cache-pip # needed in if test + # with: + # path: ~/.cache/pip + # key: ${{ runner.os }}-pip - name: Upgrade python tools shell: bash From 6813b8e4e9bf5e4f424070a9b92392f7daa6c366 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 06:52:14 -0500 Subject: [PATCH 0758/1377] Just literally trying stuff at this point --- .github/actions/setup-base/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index e19eff786..61466655d 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -33,9 +33,9 @@ runs: shell: bash run: | python -m pip install --upgrade pip - pip install --no-build-isolation --no-cache-dir "setuptools<72" - pip install -U platformio adafruit-nrfutil - pip install -U meshtastic --pre + pip install -U --no-build-isolation --no-cache-dir "setuptools<72" + pip install -U platformio adafruit-nrfutil --no-build-isolation + pip install -U meshtastic --pre --no-build-isolation - name: Upgrade platformio shell: bash From 59cc57fc29b7f7f1e0285c5ab10bed549d284d4f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 29 Jul 2024 20:16:47 -0500 Subject: [PATCH 0759/1377] Event mode: Enforce reliable hop limit and disallow default public MQTT (#4343) * Event mode: Enforce reliable hop limit * Event mode: Short circuit wantsLink on MQTT for default broker address * Just enforce at channels level since everything uses this * For events never forward packets with excessive hop_limit * In EVENT_MODE, don't respond with hop_limit set more then the configured max. * Correct hop_start when correcting hop_limit in event mode. * Make EVENT_MODE work from userPrefs.h * Event mode: Disallow Router or Repeater roles --------- Co-authored-by: Jonathan Bennett --- src/mesh/Channels.cpp | 7 +++++++ src/mesh/Default.cpp | 10 ++++++++++ src/mesh/Default.h | 1 + src/mesh/FloodingRouter.cpp | 8 ++++++++ src/mesh/ReliableRouter.cpp | 3 ++- src/mesh/Router.cpp | 3 ++- src/modules/AdminModule.cpp | 7 +++++++ src/modules/RoutingModule.cpp | 6 +++++- userPrefs.h | 3 +++ 9 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 1a23c7861..8d5b4353b 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -1,6 +1,7 @@ #include "Channels.h" #include "../userPrefs.h" #include "CryptoEngine.h" +#include "Default.h" #include "DisplayFormatters.h" #include "NodeDB.h" #include "RadioInterface.h" @@ -276,6 +277,12 @@ void Channels::setChannel(const meshtastic_Channel &c) bool Channels::anyMqttEnabled() { +#if EVENT_MODE + // Don't publish messages on the public MQTT broker if we are in event mode + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0) { + return false; + } +#endif for (int i = 0; i < getNumChannels(); i++) if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings && (channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled)) diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp index d4e9b3d79..ac7441394 100644 --- a/src/mesh/Default.cpp +++ b/src/mesh/Default.cpp @@ -1,4 +1,5 @@ #include "Default.h" +#include "../userPrefs.h" uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) { @@ -40,4 +41,13 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d return getConfiguredOrDefaultMs(configured, defaultValue); return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes); +} + +uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured) +{ +#if EVENT_MODE + return (configured > HOP_RELIABLE) ? HOP_RELIABLE : config.lora.hop_limit; +#else + return (configured >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit; +#endif } \ No newline at end of file diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 7d79d696e..3c95544da 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -30,6 +30,7 @@ class Default static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval); static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes); + static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured); private: static float congestionScalingCoefficient(int numOnlineNodes) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 0fdde5277..fbe56159c 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -1,4 +1,5 @@ #include "FloodingRouter.h" +#include "../userPrefs.h" #include "configuration.h" #include "mesh-pb-constants.h" @@ -46,6 +47,13 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it tosend->hop_limit--; // bump down the hop count +#if EVENT_MODE + if (tosend->hop_limit > 2) { + // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away. + tosend->hop_start -= (tosend->hop_limit - 2); + tosend->hop_limit = 2; + } +#endif LOG_INFO("Rebroadcasting received floodmsg to neighbors\n"); // Note: we are careful to resend using the original senders node id diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index d3246b48d..c91ce50c5 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -1,4 +1,5 @@ #include "ReliableRouter.h" +#include "Default.h" #include "MeshModule.h" #include "MeshTypes.h" #include "configuration.h" @@ -17,7 +18,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p) // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop // counts and we want this message to get through the whole mesh, so use the default. if (p->hop_limit == 0) { - p->hop_limit = (config.lora.hop_limit >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit; + p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); } auto copy = packetPool.allocCopy(*p); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c1801d419..9bffd7a5d 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -11,6 +11,7 @@ #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #endif +#include "Default.h" #if ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif @@ -125,7 +126,7 @@ meshtastic_MeshPacket *Router::allocForSending() p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB->getNodeNum(); p->to = NODENUM_BROADCAST; - p->hop_limit = (config.lora.hop_limit >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit; + p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); p->id = generatePacketId(); p->rx_time = getValidTime(RTCQualityFromNet); // Just in case we process the packet locally - make sure it has a valid timestamp diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 8939b972b..c1e9b388b 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -400,6 +400,13 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) requiresReboot = true; } } +#if EVENT_MODE + // If we're in event mode, nobody is a Router or Repeater + if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { + config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + } +#endif break; case meshtastic_Config_position_tag: LOG_INFO("Setting config: Position\n"); diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index fe1abab05..80ac92fff 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -1,4 +1,5 @@ #include "RoutingModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "Router.h" @@ -50,12 +51,15 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit // Hops used by the request. If somebody in between running modified firmware modified it, ignore it uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit; if (hopsUsed > config.lora.hop_limit) { +// In event mode, we never want to send packets with more than our default 3 hops. +#if !(EVENTMODE) // This falls through to the default. return hopsUsed; // If the request used more hops than the limit, use the same amount of hops +#endif } else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) { return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different } } - return config.lora.hop_limit; // Use the default hop limit + return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit } RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) diff --git a/userPrefs.h b/userPrefs.h index e03b70023..b365e8c6f 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -1,7 +1,10 @@ #ifndef _USERPREFS_ #define _USERPREFS_ + // Uncomment and modify to set device defaults +// #define EVENT_MODE 1 + // #define CONFIG_LORA_REGION_USERPREFS meshtastic_Config_LoRaConfig_RegionCode_US // #define LORACONFIG_MODEM_PRESET_USERPREFS meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST // #define LORACONFIG_CHANNEL_NUM_USERPREFS 31 From 302caa854a9799250b6f3ac05660aae2d86b9891 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:13:25 -0500 Subject: [PATCH 0760/1377] [create-pull-request] automated change (#4354) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index b1a79d5db..abd5eaa87 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit b1a79d5db00f6aeeb0bbae156e8939e146c95299 +Subproject commit abd5eaa875233d9bcee7e4cef3d5edcbc5b66307 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 44a86f4d6..27a7c1383 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -303,6 +303,8 @@ typedef struct _meshtastic_Config_DeviceConfig { char tzdef[65]; /* If true, disable the default blinking LED (LED_PIN) behavior on the device */ bool led_heartbeat_disabled; + /* Enable LogRecord messages over Serial for API connections */ + bool logrecord_serial_enabled; } meshtastic_Config_DeviceConfig; /* Position Config */ @@ -614,7 +616,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} @@ -623,7 +625,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} @@ -645,6 +647,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10 #define meshtastic_Config_DeviceConfig_tzdef_tag 11 #define meshtastic_Config_DeviceConfig_led_heartbeat_disabled_tag 12 +#define meshtastic_Config_DeviceConfig_logrecord_serial_enabled_tag 13 #define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1 #define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2 #define meshtastic_Config_PositionConfig_fixed_position_tag 3 @@ -750,7 +753,8 @@ X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \ X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \ X(a, STATIC, SINGULAR, STRING, tzdef, 11) \ -X(a, STATIC, SINGULAR, BOOL, led_heartbeat_disabled, 12) +X(a, STATIC, SINGULAR, BOOL, led_heartbeat_disabled, 12) \ +X(a, STATIC, SINGULAR, BOOL, logrecord_serial_enabled, 13) #define meshtastic_Config_DeviceConfig_CALLBACK NULL #define meshtastic_Config_DeviceConfig_DEFAULT NULL @@ -873,7 +877,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 12 -#define meshtastic_Config_DeviceConfig_size 100 +#define meshtastic_Config_DeviceConfig_size 102 #define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index eb37f4f95..fb6ab1bf2 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -307,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3388 +#define meshtastic_OEMStore_size 3390 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 983f48ad3..9eff14754 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 555 +#define meshtastic_LocalConfig_size 557 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus From a1c998e7e06b3e4c89f25fb5c5e68b9e7e32bd9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:57:24 -0500 Subject: [PATCH 0761/1377] [create-pull-request] automated change (#4361) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++-------- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/protobufs b/protobufs index abd5eaa87..976748839 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit abd5eaa875233d9bcee7e4cef3d5edcbc5b66307 +Subproject commit 976748839fafcf0049bb364fe2c7226a194d18a9 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 27a7c1383..44a86f4d6 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -303,8 +303,6 @@ typedef struct _meshtastic_Config_DeviceConfig { char tzdef[65]; /* If true, disable the default blinking LED (LED_PIN) behavior on the device */ bool led_heartbeat_disabled; - /* Enable LogRecord messages over Serial for API connections */ - bool logrecord_serial_enabled; } meshtastic_Config_DeviceConfig; /* Position Config */ @@ -616,7 +614,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, 0} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} @@ -625,7 +623,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, 0} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} @@ -647,7 +645,6 @@ extern "C" { #define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10 #define meshtastic_Config_DeviceConfig_tzdef_tag 11 #define meshtastic_Config_DeviceConfig_led_heartbeat_disabled_tag 12 -#define meshtastic_Config_DeviceConfig_logrecord_serial_enabled_tag 13 #define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1 #define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2 #define meshtastic_Config_PositionConfig_fixed_position_tag 3 @@ -753,8 +750,7 @@ X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \ X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \ X(a, STATIC, SINGULAR, STRING, tzdef, 11) \ -X(a, STATIC, SINGULAR, BOOL, led_heartbeat_disabled, 12) \ -X(a, STATIC, SINGULAR, BOOL, logrecord_serial_enabled, 13) +X(a, STATIC, SINGULAR, BOOL, led_heartbeat_disabled, 12) #define meshtastic_Config_DeviceConfig_CALLBACK NULL #define meshtastic_Config_DeviceConfig_DEFAULT NULL @@ -877,7 +873,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 12 -#define meshtastic_Config_DeviceConfig_size 102 +#define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index fb6ab1bf2..eb37f4f95 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -307,7 +307,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3390 +#define meshtastic_OEMStore_size 3388 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 9eff14754..983f48ad3 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 557 +#define meshtastic_LocalConfig_size 555 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus From 93ba19d1e16f72c167c3edaae4aa0eae0576cede Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 30 Jul 2024 15:05:33 -0500 Subject: [PATCH 0762/1377] Make LogRecord protobuf serial logging over Phone API opt-in instead (#4358) * Make LogRecord protobuf serial logging over Phone API opt-in instead of enabled by default * debug_log_enabled --- src/SerialConsole.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 9a9331e47..d25b81da7 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -96,7 +96,7 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) { - if (usingProtobufs) { + if (usingProtobufs && config.device.debug_log_enabled) { meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset switch (logLevel[0]) { case 'D': @@ -120,4 +120,4 @@ void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_l emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg); } else RedirectablePrint::log_to_serial(logLevel, format, arg); -} \ No newline at end of file +} From 1951569b1a5986aea7a48729ba51c7214776ad5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Tue, 30 Jul 2024 22:05:51 +0200 Subject: [PATCH 0763/1377] PA FAN Disable (#4355) * PA FAN Disable * PA FAN Disable * Trunk * Trunk * Trunk * Thunk ..... --- src/main.cpp | 9 ++++++++- src/modules/AdminModule.cpp | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index dc5863bbc..2e0115139 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -973,6 +973,13 @@ void setup() mqttInit(); #endif +#ifdef RF95_FAN_EN + // Ability to disable FAN if PIN has been set with RF95_FAN_EN. + // Make sure LoRa has been started before disabling FAN. + if (config.lora.pa_fan_disabled) + digitalWrite(RF95_FAN_EN, LOW ^ 0); +#endif + #ifndef ARCH_PORTDUINO // Initialize Wifi @@ -1087,4 +1094,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index c1e9b388b..c0e2a1ab2 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -466,6 +466,15 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) { requiresReboot = false; } + +#ifdef RF95_FAN_EN + // Turn PA off if disabled by config + if (c.payload_variant.lora.pa_fan_disabled) { + digitalWrite(RF95_FAN_EN, LOW ^ 0); + } else { + digitalWrite(RF95_FAN_EN, HIGH ^ 0); + } +#endif config.lora = c.payload_variant.lora; // If we're setting region for the first time, init the region if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { @@ -877,4 +886,4 @@ AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_AP { // restrict to the admin channel for rx boundChannel = Channels::adminChannel; -} \ No newline at end of file +} From 5dde738a31df27086778f4af155ec23649a672e1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 30 Jul 2024 15:08:38 -0500 Subject: [PATCH 0764/1377] Trunk --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 2e0115139..39212a0a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -982,7 +982,7 @@ void setup() #ifndef ARCH_PORTDUINO - // Initialize Wifi + // Initialize Wifi #if HAS_WIFI initWifi(); #endif From 1f9dacf486e2d22505ff0a92ad75d89ec6a31ea1 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 31 Jul 2024 04:57:13 +0800 Subject: [PATCH 0765/1377] Add support for ATGM332D series (#4351) Adds detection code for the ATGM332D series of chips (11, 21, 31, 51, 71), and sets meshtastic to use GNSS_MODEL_ATGM336H for them. --- src/gps/GPS.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 67f6adb98..091b4e6bc 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1217,7 +1217,7 @@ GnssModel_t GPS::probe(int serialSpeed) return GNSS_MODEL_UC6580; } - // Get version information + // Get version information for ATGM336H clearBuffer(); _serial_gps->write("$PCAS06,1*1A\r\n"); if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) { @@ -1225,6 +1225,15 @@ GnssModel_t GPS::probe(int serialSpeed) return GNSS_MODEL_ATGM336H; } + /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) + based on AT6558 */ + clearBuffer(); + _serial_gps->write("$PCAS06,1*1A\r\n"); + if (getACK("$GPTXT,01,01,02,HW=ATGM332D", 500) == GNSS_RESPONSE_OK) { + LOG_INFO("ATGM332D detected, using ATGM336H Module\n"); + return GNSS_MODEL_ATGM336H; + } + // Get version information clearBuffer(); _serial_gps->write("$PCAS06,0*1B\r\n"); From 9f5f630dca02f4cecf04a6e2599cc9735f649e4b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 31 Jul 2024 04:57:31 +0800 Subject: [PATCH 0766/1377] Remove unused define in NRF52 architecture (#4350) The NRF52 architecture defaulted to setting GPS_UBLOX. However, GPS_UBLOX is not used anywhere in the code. Remove these lines. --- src/platform/nrf52/architecture.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index a276a6081..2b285ec2e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -88,10 +88,6 @@ #endif -#ifndef TTGO_T_ECHO -#define GPS_UBLOX -#endif - #define LED_PIN PIN_LED1 // LED1 on nrf52840-DK #ifdef PIN_BUTTON1 @@ -120,4 +116,4 @@ #if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA) // No serial ports on this board - ONLY use segger in memory console #define USE_SEGGER -#endif \ No newline at end of file +#endif From a111f54b6189093123b6ee0fd846a708dfd91b35 Mon Sep 17 00:00:00 2001 From: Oliver0804 Date: Wed, 31 Jul 2024 06:15:50 +0800 Subject: [PATCH 0767/1377] Add #define USE_SSD1306 to avoid automatic detection causing pixel shift. (#4356) Co-authored-by: Ben Meadors --- variants/heltec_v3/variant.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/heltec_v3/variant.h b/variants/heltec_v3/variant.h index 2417b873d..4f1d91db8 100644 --- a/variants/heltec_v3/variant.h +++ b/variants/heltec_v3/variant.h @@ -1,5 +1,7 @@ #define LED_PIN LED +#define USE_SSD1306 // Heltec_v3 has a SSD1306 display + #define RESET_OLED RST_OLED #define I2C_SDA SDA_OLED // I2C pins for this board #define I2C_SCL SCL_OLED From 29fe6e7448d1566675b1ec388df0892752ed36ff Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 31 Jul 2024 05:52:17 -0500 Subject: [PATCH 0768/1377] Event mode: Block problematic portnums of traffic (#4362) --- src/mesh/Router.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 9bffd7a5d..bac25171b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -485,6 +485,20 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) cancelSending(p->from, p->id); skipHandle = true; } + +#if EVENT_MODE + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + (p->decoded.portnum == meshtastic_PortNum_ATAK_FORWARDER || p->decoded.portnum == meshtastic_PortNum_ATAK_PLUGIN || + p->decoded.portnum == meshtastic_PortNum_PAXCOUNTER_APP || p->decoded.portnum == meshtastic_PortNum_IP_TUNNEL_APP || + p->decoded.portnum == meshtastic_PortNum_AUDIO_APP || p->decoded.portnum == meshtastic_PortNum_PRIVATE_APP || + p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP || + p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + p->decoded.portnum == meshtastic_PortNum_REMOTE_HARDWARE_APP)) { + LOG_DEBUG("Ignoring packet on blacklisted portnum during event\n"); + cancelSending(p->from, p->id); + skipHandle = true; + } +#endif } else { printPacket("packet decoding failed or skipped (no PSK?)", p); } From ce1eb149ac0b2e326010a850e7a4d3488d7390d9 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 31 Jul 2024 18:58:19 +0800 Subject: [PATCH 0769/1377] Cleanup for Air530z GPS (#4344) It turns out that the Air530z is a GNSS_MODEL_MTK. Our existing code detects and configures it properly. This patch removes a couple of AIROHA ifdefs since they are not required for a working GPS. Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 091b4e6bc..d6ea2cb08 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -400,12 +400,6 @@ bool GPS::setup() int msglen = 0; if (!didSerialInit) { -#ifdef GNSS_AIROHA - if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { - probe(GPS_BAUDRATE); - LOG_INFO("GPS setting to %d.\n", GPS_BAUDRATE); - } -#else #if !defined(GPS_UC6580) if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { @@ -784,7 +778,6 @@ bool GPS::setup() LOG_INFO("GNSS module configuration saved!\n"); } } -#endif didSerialInit = true; } @@ -1191,10 +1184,6 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->updateBaudRate(serialSpeed); } #endif -#ifdef GNSS_AIROHA - - return GNSS_MODEL_UNKNOWN; -#else #ifdef GPS_DEBUG for (int i = 0; i < 20; i++) { getACK("$GP", 200); @@ -1359,7 +1348,6 @@ GnssModel_t GPS::probe(int serialSpeed) } return GNSS_MODEL_UBLOX; -#endif // !GNSS_Airoha } GPS *GPS::createGps() @@ -1809,4 +1797,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file From 848b9773b9b33af1036ebdecb0193c33c27d476e Mon Sep 17 00:00:00 2001 From: Alexander Smyslov <37107500+alexander-smyslov@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:58:41 +0200 Subject: [PATCH 0770/1377] Add trackerd to build. (#4347) Co-authored-by: Ben Meadors --- variants/trackerd/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/trackerd/platformio.ini b/variants/trackerd/platformio.ini index 23868ffb4..6fba190f3 100644 --- a/variants/trackerd/platformio.ini +++ b/variants/trackerd/platformio.ini @@ -4,10 +4,10 @@ extends = esp32_base platform = espressif32 board = pico32 board_build.f_flash = 80000000L -board_level = extra + build_flags = ${esp32_base.build_flags} -D PRIVATE_HW -I variants/trackerd -D BSFILE=\"boards/dragino_lbt2.h\" ;board_build.partitions = no_ota.csv ;platform_packages = ; platformio/framework-arduinoespressif32@3 -;platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.1-RC1 \ No newline at end of file +;platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.1-RC1 From 103ab0c242cdc9b08ce2d1b97c165444db5de51c Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 31 Jul 2024 19:56:06 +0800 Subject: [PATCH 0771/1377] Cleanup - remove unused defines. (#4353) * Cleanup - remove unused defines. There were a number of defined variables that were carried over from old code. - Removed. Also a typo. - Fixed fix. Also duplicate definitions of the number of seconds in a day. -deduplicated. * Cleanup - remove unused defines. There were a number of defined variables that were carried over from old code. - Removed. Also a typo. - Fixed fix. Also duplicate definitions of the number of seconds in a day. -deduplicated. --------- Co-authored-by: Ben Meadors --- src/ButtonThread.h | 2 +- src/configuration.h | 4 ---- src/gps/RTC.h | 2 +- src/mesh/NodeDB.h | 5 +---- src/mesh/RF95Interface.cpp | 1 - src/mesh/RadioInterface.cpp | 3 --- src/modules/RangeTestModule.cpp | 6 +----- 7 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/ButtonThread.h b/src/ButtonThread.h index d7a9201a3..9cd7b3dac 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -13,7 +13,7 @@ #endif #ifndef BUTTON_TOUCH_MS -#define BUTTON_TOCH_MS 400 +#define BUTTON_TOUCH_MS 400 #endif class ButtonThread : public concurrency::OSThread diff --git a/src/configuration.h b/src/configuration.h index b298d5424..4ebc59ffc 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -52,10 +52,6 @@ along with this program. If not, see . // Configuration // ----------------------------------------------------------------------------- -// If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled) -// we don't support jtag on the ttgo - access to gpio 12 is a PITA -#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found - /// Convert a preprocessor name into a quoted string #define xstr(s) ystr(s) #define ystr(s) #s diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 4b065b376..d31e85433 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -43,4 +43,4 @@ time_t gm_mktime(struct tm *tm); #define SEC_PER_DAY 86400 #define SEC_PER_HOUR 3600 -#define SEC_PER_MIN 60 \ No newline at end of file +#define SEC_PER_MIN 60 diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 258b7276d..e2c2471d4 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -204,9 +204,6 @@ extern NodeDB *nodeDB; prefs.is_power_saving = True */ -// Our delay functions check for this for times that should never expire -#define NODE_DELAY_FOREVER 0xffffffff - /// Sometimes we will have Position objects that only have a time, so check for /// valid lat/lon static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n) @@ -231,4 +228,4 @@ extern uint32_t error_address; ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ ModuleConfig_TelemetryConfig_size + ModuleConfig_size) -// Please do not remove this comment, it makes trunk and compiler happy at the same time. \ No newline at end of file +// Please do not remove this comment, it makes trunk and compiler happy at the same time. diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index bd1ebdb0e..b6083e7f9 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -16,7 +16,6 @@ // In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING // if you set power to something higher than 17 or 20 you might fry your board. -#define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level #ifdef RADIOMASTER_900_BANDIT_NANO // Structure to hold DAC and DB values typedef struct { diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 343b7f200..e9445ccac 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -177,9 +177,6 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. */ -// 1kb was too small -#define RADIO_STACK_SIZE 4096 - /** * Calculate airtime per * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index a66a0513e..9bdb36b0e 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -27,10 +27,6 @@ RangeTestModule::RangeTestModule() : concurrency::OSThread("RangeTestModule") {} uint32_t packetSequence = 0; -#define SEC_PER_DAY 86400 -#define SEC_PER_HOUR 3600 -#define SEC_PER_MIN 60 - int32_t RangeTestModule::runOnce() { #if defined(ARCH_ESP32) || defined(ARCH_NRF52) @@ -295,4 +291,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} \ No newline at end of file +} From 106a50bce245ed674b026570621d69859e0907dc Mon Sep 17 00:00:00 2001 From: Sylvain Migaud Date: Wed, 31 Jul 2024 14:38:21 +0200 Subject: [PATCH 0772/1377] Adding support for Chatter keypad (#4022) * Adding support for Chatter keypad * Remove user button mapping since full keypad is now useable * Adding TAB key and RIGHT to allow selecting a destination * Fix shift bug (there's only three levels, not four) * reformat file * Fix bug with fast repeated keypresses --------- Co-authored-by: Ben Meadors --- src/input/SerialKeyboard.cpp | 187 +++++++++++++++++++++++++++++++ src/input/SerialKeyboard.h | 25 +++++ src/input/SerialKeyboardImpl.cpp | 21 ++++ src/input/SerialKeyboardImpl.h | 19 ++++ src/modules/Modules.cpp | 5 + variants/chatter2/variant.h | 9 +- 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 src/input/SerialKeyboard.cpp create mode 100644 src/input/SerialKeyboard.h create mode 100644 src/input/SerialKeyboardImpl.cpp create mode 100644 src/input/SerialKeyboardImpl.h diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp new file mode 100644 index 000000000..b3d11b0fc --- /dev/null +++ b/src/input/SerialKeyboard.cpp @@ -0,0 +1,187 @@ +#include "SerialKeyboard.h" +#include "configuration.h" + +#ifdef INPUTBROKER_SERIAL_TYPE +#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file + +#if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter +// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number +unsigned char KeyMap[3][4][10]= {{{'.','a','d','g','j','m','p','t','w',' '}, + {',','b','e','h','k','n','q','u','x',' '}, + {'?','c','f','i','l','o','r','v','y',' '}, + {'1','2','3','4','5','6','s','8','z',' '}}, // low case + {{'!','A','D','G','J','M','P','T','W',' '}, + {'+','B','E','H','K','N','Q','U','X',' '}, + {'-','C','F','I','L','O','R','V','Y',' '}, + {'1','2','3','4','5','6','S','8','Z',' '}}, // upper case + {{'1','2','3','4','5','6','7','8','9','0'}, + {'1','2','3','4','5','6','7','8','9','0'}, + {'1','2','3','4','5','6','7','8','9','0'}, + {'1','2','3','4','5','6','7','8','9','0'}}}; // numbers + +#endif + + +SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) +{ + this->_originName = name; +} + + +void SerialKeyboard::erase(){ + InputEvent e; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; + e.kbchar = 0x08; + e.source = this->_originName; + this->notifyObservers(&e); +} + + +int32_t SerialKeyboard::runOnce() +{ + if (!INPUTBROKER_SERIAL_TYPE) { + // Input device is not requested. + return disable(); + } + + if (firstTime) { + // This is the first time the OSThread library has called this function, so do port setup + firstTime = 0; + pinMode(KB_LOAD, OUTPUT); + pinMode(KB_CLK, OUTPUT); + pinMode(KB_DATA, INPUT); + digitalWrite(KB_LOAD, HIGH); + digitalWrite(KB_CLK, LOW); + prevKeys = 0b1111111111111111; + LOG_DEBUG("Serial Keyboard setup\n"); + } + + if (INPUTBROKER_SERIAL_TYPE == 1) { //Chatter V1.0 & V2.0 keypads + // scan for keypresses + // Write pulse to load pin + digitalWrite(KB_LOAD, LOW); + delayMicroseconds(5); + digitalWrite(KB_LOAD, HIGH); + delayMicroseconds(5); + + // Get data from 74HC165 + byte shiftRegister1 = shiftIn(KB_DATA, KB_CLK, LSBFIRST); + byte shiftRegister2 = shiftIn(KB_DATA, KB_CLK, LSBFIRST); + + keys = (shiftRegister1 << 8) + shiftRegister2; + + // Print to serial monitor + //Serial.print (shiftRegister1, BIN); + //Serial.print ("X"); + //Serial.println (shiftRegister2, BIN); + + if (millis()-lastPressTime > 500){ + quickPress = 0; + } + + if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but shouldn't be a limitation + InputEvent e; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.source = this->_originName; + // SELECT OR SEND OR CANCEL EVENT + if (!(shiftRegister2 & (1 << 3))) { + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; + } + else if (!(shiftRegister2 & (1 << 2))) { + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; + e.kbchar = 0xb7; + } + else if (!(shiftRegister2 & (1 << 1))) { + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; + } + else if (!(shiftRegister2 & (1 << 0))) { + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; + } + + // TEXT INPUT EVENT + else if (!(shiftRegister1 & (1 << 4))) { + keyPressed = 0; + } + else if (!(shiftRegister1 & (1 << 3))) { + keyPressed = 1; + } + else if (!(shiftRegister2 & (1 << 4))) { + keyPressed = 2; + } + else if (!(shiftRegister1 & (1 << 5))) { + keyPressed = 3; + } + else if (!(shiftRegister1 & (1 << 2))) { + keyPressed = 4; + } + else if (!(shiftRegister2 & (1 << 5))) { + keyPressed = 5; + } + else if (!(shiftRegister1 & (1 << 6))) { + keyPressed = 6; + } + else if (!(shiftRegister1 & (1 << 1))) { + keyPressed = 7; + } + else if (!(shiftRegister2 & (1 << 6))) { + keyPressed = 8; + } + else if (!(shiftRegister1 & (1 << 0))) { + keyPressed = 9; + } + // BACKSPACE or TAB + else if (!(shiftRegister1 & (1 << 7))) { + if (shift == 0 || shift ==2){ // BACKSPACE + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; + e.kbchar = 0x08; + } else { // shift = 1 => TAB + e.inputEvent = ANYKEY; + e.kbchar = 0x09; + } + } + // SHIFT + else if (!(shiftRegister2 & (1 << 7))) { + keyPressed = 10; + } + + + if (keyPressed < 11){ + if (keyPressed == lastKeyPressed && millis()-lastPressTime < 500){ + quickPress += 1; + if (quickPress > 3){ + quickPress = 0; + } + } + if (keyPressed != lastKeyPressed){ + quickPress = 0; + } + if (keyPressed < 10){ // if it's a letter + if (keyPressed == lastKeyPressed && millis()-lastPressTime < 500){ + erase(); + } + e.inputEvent = ANYKEY; + e.kbchar = char(KeyMap[shift][quickPress][keyPressed]); + } + else { //then it's shift + shift += 1; + if (shift > 2){ + shift = 0; + } + } + lastPressTime = millis(); + lastKeyPressed = keyPressed; + keyPressed = 13; + } + + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + this->notifyObservers(&e); + } + } + prevKeys = keys; + + } + return 50; +} + + +#endif // INPUTBROKER_SERIAL_TYPE \ No newline at end of file diff --git a/src/input/SerialKeyboard.h b/src/input/SerialKeyboard.h new file mode 100644 index 000000000..1480c4d58 --- /dev/null +++ b/src/input/SerialKeyboard.h @@ -0,0 +1,25 @@ +#pragma once + +#include "InputBroker.h" +#include "concurrency/OSThread.h" + +class SerialKeyboard : public Observable, public concurrency::OSThread +{ + public: + explicit SerialKeyboard(const char *name); + + protected: + virtual int32_t runOnce() override; + void erase(); + + private: + const char *_originName; + bool firstTime = 1; + int prevKeys = 0; + int keys = 0; + int shift = 0; + int keyPressed = 13; + int lastKeyPressed = 13; + int quickPress = 0; + unsigned long lastPressTime = 0; +}; \ No newline at end of file diff --git a/src/input/SerialKeyboardImpl.cpp b/src/input/SerialKeyboardImpl.cpp new file mode 100644 index 000000000..579356f47 --- /dev/null +++ b/src/input/SerialKeyboardImpl.cpp @@ -0,0 +1,21 @@ +#include "configuration.h" +#include "InputBroker.h" +#include "SerialKeyboardImpl.h" + +#ifdef INPUTBROKER_SERIAL_TYPE + +SerialKeyboardImpl *aSerialKeyboardImpl; + +SerialKeyboardImpl::SerialKeyboardImpl() : SerialKeyboard("serialKB") {} + +void SerialKeyboardImpl::init() +{ + if (!INPUTBROKER_SERIAL_TYPE) { + disable(); + return; + } + + inputBroker->registerSource(this); +} + +#endif // INPUTBROKER_SERIAL_TYPE \ No newline at end of file diff --git a/src/input/SerialKeyboardImpl.h b/src/input/SerialKeyboardImpl.h new file mode 100644 index 000000000..7f62aa420 --- /dev/null +++ b/src/input/SerialKeyboardImpl.h @@ -0,0 +1,19 @@ +#pragma once +#include "SerialKeyboard.h" +#include "main.h" + +/** + * @brief The idea behind this class to have static methods for the event handlers. + * Check attachInterrupt() at RotaryEncoderInteruptBase.cpp + * Technically you can have as many rotary encoders hardver attached + * to your device as you wish, but you always need to have separate event + * handlers, thus you need to have a RotaryEncoderInterrupt implementation. + */ +class SerialKeyboardImpl : public SerialKeyboard +{ + public: + SerialKeyboardImpl(); + void init(); +}; + +extern SerialKeyboardImpl *aSerialKeyboardImpl; \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 5a0e36fea..40352a56e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -6,6 +6,7 @@ #include "input/UpDownInterruptImpl1.h" #include "input/cardKbI2cImpl.h" #include "input/kbMatrixImpl.h" +#include "input/SerialKeyboardImpl.h" #endif #if !MESHTASTIC_EXCLUDE_ADMIN #include "modules/AdminModule.h" @@ -149,6 +150,10 @@ void setupModules() kbMatrixImpl = new KbMatrixImpl(); kbMatrixImpl->init(); #endif // INPUTBROKER_MATRIX_TYPE +#ifdef INPUTBROKER_SERIAL_TYPE + aSerialKeyboardImpl = new SerialKeyboardImpl(); + aSerialKeyboardImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE #endif // HAS_BUTTON #if ARCH_PORTDUINO aLinuxInputImpl = new LinuxInputImpl(); diff --git a/variants/chatter2/variant.h b/variants/chatter2/variant.h index 90faa1d7b..52aceafcd 100644 --- a/variants/chatter2/variant.h +++ b/variants/chatter2/variant.h @@ -34,7 +34,7 @@ // Buzzer #define PIN_BUZZER 19 // Buttons -#define BUTTON_PIN 36 // Use the WAKE button as the user button +//#define BUTTON_PIN 36 // Use the WAKE button as the user button // I2C // #define I2C_SCL 27 // #define I2C_SDA 26 @@ -91,6 +91,13 @@ #define GPS_TX_PIN 13 #define GPS_RX_PIN 2 +// keyboard +#define INPUTBROKER_SERIAL_TYPE 1 +#define KB_LOAD 21 // load values from the switch and store in shift register +#define KB_CLK 22 // clock pin for serial data out +#define KB_DATA 23 // data pin +#define CANNED_MESSAGE_MODULE_ENABLE 1 + ///////////////////////////////////////////////////////////////////////////////// // // // You should have no need to modify the code below, nor in pins_arduino.h // From 24ecfa1a45372e5cb3ac4c9e5d1346d30a6e8c26 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 31 Jul 2024 07:42:23 -0500 Subject: [PATCH 0773/1377] Trunk fmt --- src/input/SerialKeyboard.cpp | 109 +++++++++++++------------------ src/input/SerialKeyboardImpl.cpp | 4 +- src/modules/Modules.cpp | 2 +- variants/chatter2/variant.h | 8 +-- 4 files changed, 53 insertions(+), 70 deletions(-) diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index b3d11b0fc..fa3eb2528 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -6,29 +6,28 @@ #if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter // 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number -unsigned char KeyMap[3][4][10]= {{{'.','a','d','g','j','m','p','t','w',' '}, - {',','b','e','h','k','n','q','u','x',' '}, - {'?','c','f','i','l','o','r','v','y',' '}, - {'1','2','3','4','5','6','s','8','z',' '}}, // low case - {{'!','A','D','G','J','M','P','T','W',' '}, - {'+','B','E','H','K','N','Q','U','X',' '}, - {'-','C','F','I','L','O','R','V','Y',' '}, - {'1','2','3','4','5','6','S','8','Z',' '}}, // upper case - {{'1','2','3','4','5','6','7','8','9','0'}, - {'1','2','3','4','5','6','7','8','9','0'}, - {'1','2','3','4','5','6','7','8','9','0'}, - {'1','2','3','4','5','6','7','8','9','0'}}}; // numbers +unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w', ' '}, + {',', 'b', 'e', 'h', 'k', 'n', 'q', 'u', 'x', ' '}, + {'?', 'c', 'f', 'i', 'l', 'o', 'r', 'v', 'y', ' '}, + {'1', '2', '3', '4', '5', '6', 's', '8', 'z', ' '}}, // low case + {{'!', 'A', 'D', 'G', 'J', 'M', 'P', 'T', 'W', ' '}, + {'+', 'B', 'E', 'H', 'K', 'N', 'Q', 'U', 'X', ' '}, + {'-', 'C', 'F', 'I', 'L', 'O', 'R', 'V', 'Y', ' '}, + {'1', '2', '3', '4', '5', '6', 'S', '8', 'Z', ' '}}, // upper case + {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}, + {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}, + {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}, + {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}}}; // numbers #endif - SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) { this->_originName = name; } - -void SerialKeyboard::erase(){ +void SerialKeyboard::erase() +{ InputEvent e; e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; e.kbchar = 0x08; @@ -36,7 +35,6 @@ void SerialKeyboard::erase(){ this->notifyObservers(&e); } - int32_t SerialKeyboard::runOnce() { if (!INPUTBROKER_SERIAL_TYPE) { @@ -56,82 +54,71 @@ int32_t SerialKeyboard::runOnce() LOG_DEBUG("Serial Keyboard setup\n"); } - if (INPUTBROKER_SERIAL_TYPE == 1) { //Chatter V1.0 & V2.0 keypads + if (INPUTBROKER_SERIAL_TYPE == 1) { // Chatter V1.0 & V2.0 keypads // scan for keypresses // Write pulse to load pin digitalWrite(KB_LOAD, LOW); delayMicroseconds(5); digitalWrite(KB_LOAD, HIGH); delayMicroseconds(5); - + // Get data from 74HC165 byte shiftRegister1 = shiftIn(KB_DATA, KB_CLK, LSBFIRST); byte shiftRegister2 = shiftIn(KB_DATA, KB_CLK, LSBFIRST); - + keys = (shiftRegister1 << 8) + shiftRegister2; // Print to serial monitor - //Serial.print (shiftRegister1, BIN); - //Serial.print ("X"); - //Serial.println (shiftRegister2, BIN); + // Serial.print (shiftRegister1, BIN); + // Serial.print ("X"); + // Serial.println (shiftRegister2, BIN); - if (millis()-lastPressTime > 500){ + if (millis() - lastPressTime > 500) { quickPress = 0; } - if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but shouldn't be a limitation + if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but + // shouldn't be a limitation InputEvent e; e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; e.source = this->_originName; // SELECT OR SEND OR CANCEL EVENT if (!(shiftRegister2 & (1 << 3))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; - } - else if (!(shiftRegister2 & (1 << 2))) { + } else if (!(shiftRegister2 & (1 << 2))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; e.kbchar = 0xb7; - } - else if (!(shiftRegister2 & (1 << 1))) { + } else if (!(shiftRegister2 & (1 << 1))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; - } - else if (!(shiftRegister2 & (1 << 0))) { + } else if (!(shiftRegister2 & (1 << 0))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; } - + // TEXT INPUT EVENT else if (!(shiftRegister1 & (1 << 4))) { keyPressed = 0; - } - else if (!(shiftRegister1 & (1 << 3))) { + } else if (!(shiftRegister1 & (1 << 3))) { keyPressed = 1; - } - else if (!(shiftRegister2 & (1 << 4))) { + } else if (!(shiftRegister2 & (1 << 4))) { keyPressed = 2; - } - else if (!(shiftRegister1 & (1 << 5))) { + } else if (!(shiftRegister1 & (1 << 5))) { keyPressed = 3; - } - else if (!(shiftRegister1 & (1 << 2))) { + } else if (!(shiftRegister1 & (1 << 2))) { keyPressed = 4; - } - else if (!(shiftRegister2 & (1 << 5))) { + } else if (!(shiftRegister2 & (1 << 5))) { keyPressed = 5; - } - else if (!(shiftRegister1 & (1 << 6))) { + } else if (!(shiftRegister1 & (1 << 6))) { keyPressed = 6; - } - else if (!(shiftRegister1 & (1 << 1))) { + } else if (!(shiftRegister1 & (1 << 1))) { keyPressed = 7; - } - else if (!(shiftRegister2 & (1 << 6))) { + } else if (!(shiftRegister2 & (1 << 6))) { keyPressed = 8; - } - else if (!(shiftRegister1 & (1 << 0))) { + } else if (!(shiftRegister1 & (1 << 0))) { keyPressed = 9; } // BACKSPACE or TAB else if (!(shiftRegister1 & (1 << 7))) { - if (shift == 0 || shift ==2){ // BACKSPACE + if (shift == 0 || shift == 2) { // BACKSPACE e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; e.kbchar = 0x08; } else { // shift = 1 => TAB @@ -144,27 +131,25 @@ int32_t SerialKeyboard::runOnce() keyPressed = 10; } - - if (keyPressed < 11){ - if (keyPressed == lastKeyPressed && millis()-lastPressTime < 500){ + if (keyPressed < 11) { + if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) { quickPress += 1; - if (quickPress > 3){ + if (quickPress > 3) { quickPress = 0; } } - if (keyPressed != lastKeyPressed){ + if (keyPressed != lastKeyPressed) { quickPress = 0; } - if (keyPressed < 10){ // if it's a letter - if (keyPressed == lastKeyPressed && millis()-lastPressTime < 500){ + if (keyPressed < 10) { // if it's a letter + if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) { erase(); - } + } e.inputEvent = ANYKEY; e.kbchar = char(KeyMap[shift][quickPress][keyPressed]); - } - else { //then it's shift + } else { // then it's shift shift += 1; - if (shift > 2){ + if (shift > 2) { shift = 0; } } @@ -178,10 +163,8 @@ int32_t SerialKeyboard::runOnce() } } prevKeys = keys; - } return 50; } - #endif // INPUTBROKER_SERIAL_TYPE \ No newline at end of file diff --git a/src/input/SerialKeyboardImpl.cpp b/src/input/SerialKeyboardImpl.cpp index 579356f47..249b76fe3 100644 --- a/src/input/SerialKeyboardImpl.cpp +++ b/src/input/SerialKeyboardImpl.cpp @@ -1,6 +1,6 @@ -#include "configuration.h" -#include "InputBroker.h" #include "SerialKeyboardImpl.h" +#include "InputBroker.h" +#include "configuration.h" #ifdef INPUTBROKER_SERIAL_TYPE diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 40352a56e..08e681ff8 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -2,11 +2,11 @@ #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "input/InputBroker.h" #include "input/RotaryEncoderInterruptImpl1.h" +#include "input/SerialKeyboardImpl.h" #include "input/TrackballInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h" #include "input/cardKbI2cImpl.h" #include "input/kbMatrixImpl.h" -#include "input/SerialKeyboardImpl.h" #endif #if !MESHTASTIC_EXCLUDE_ADMIN #include "modules/AdminModule.h" diff --git a/variants/chatter2/variant.h b/variants/chatter2/variant.h index 52aceafcd..70438e52a 100644 --- a/variants/chatter2/variant.h +++ b/variants/chatter2/variant.h @@ -34,7 +34,7 @@ // Buzzer #define PIN_BUZZER 19 // Buttons -//#define BUTTON_PIN 36 // Use the WAKE button as the user button +// #define BUTTON_PIN 36 // Use the WAKE button as the user button // I2C // #define I2C_SCL 27 // #define I2C_SDA 26 @@ -93,9 +93,9 @@ // keyboard #define INPUTBROKER_SERIAL_TYPE 1 -#define KB_LOAD 21 // load values from the switch and store in shift register -#define KB_CLK 22 // clock pin for serial data out -#define KB_DATA 23 // data pin +#define KB_LOAD 21 // load values from the switch and store in shift register +#define KB_CLK 22 // clock pin for serial data out +#define KB_DATA 23 // data pin #define CANNED_MESSAGE_MODULE_ENABLE 1 ///////////////////////////////////////////////////////////////////////////////// From bcdda4de8ab81fec6a35be52cc2a3a0fa3fa5332 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 31 Jul 2024 08:53:59 -0500 Subject: [PATCH 0774/1377] Missed some includes of userPrefs that would allow behavior we don't want --- src/mesh/Router.cpp | 1 + src/modules/AdminModule.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index bac25171b..1260b7c04 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -18,6 +18,7 @@ #if ENABLE_JSON_LOGGING || ARCH_PORTDUINO #include "serialization/MeshPacketSerializer.h" #endif +#include "../userPrefs.h" /** * Router todo * diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index c0e2a1ab2..747f8a8a5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -16,6 +16,7 @@ #ifdef ARCH_PORTDUINO #include "unistd.h" #endif +#include "../userPrefs.h" #include "Default.h" #include "TypeConversions.h" @@ -886,4 +887,4 @@ AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_AP { // restrict to the admin channel for rx boundChannel = Channels::adminChannel; -} +} \ No newline at end of file From 4c1c5b070eef35ee8320df608781c1866477d865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:53:38 +0200 Subject: [PATCH 0775/1377] Changed a RADIOMASTER_900_BANDIT_NANO to DISPLAY_FLIP_SCREEN (#4366) * More compatible Changed a RADIOMASTER_900_BANDIT_NANO to DISPLAY_FLIP_SCREEN that is responsible for flipping the OLED screen for better compatible with other devices. * Update variant.h Radiomaster Remove a un-used SCREEN_ROTATE and added DISPLAY_FLIP_SCREEN --- src/mesh/NodeDB.cpp | 4 ++-- variants/radiomaster_900_bandit_nano/variant.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b4b5ec286..6fbbbf546 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -303,7 +303,7 @@ void NodeDB::installDefaultConfig() meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_DOP | meshtastic_Config_PositionConfig_PositionFlags_SATINVIEW); -#ifdef RADIOMASTER_900_BANDIT_NANO +#ifdef DISPLAY_FLIP_SCREEN config.display.flip_screen = true; #endif #ifdef T_WATCH_S3 @@ -1054,4 +1054,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} \ No newline at end of file +} diff --git a/variants/radiomaster_900_bandit_nano/variant.h b/variants/radiomaster_900_bandit_nano/variant.h index bd6687733..39c1bc06f 100644 --- a/variants/radiomaster_900_bandit_nano/variant.h +++ b/variants/radiomaster_900_bandit_nano/variant.h @@ -50,7 +50,7 @@ #define BUTTON_PIN 39 #define BUTTON_NEED_PULLUP -#define SCREEN_ROTATE +#define DISPLAY_FLIP_SCREEN /* No External notification. @@ -81,4 +81,4 @@ */ #define RF95_PA_EN 26 #define RF95_PA_DAC_EN -#define RF95_PA_LEVEL 90 \ No newline at end of file +#define RF95_PA_LEVEL 90 From d2ea430a3ee40647e1a414d5bc95c0bfdb4a37b7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 1 Aug 2024 19:29:49 -0500 Subject: [PATCH 0776/1377] Make SPI frequency and TOPHONE queue size configurable on Native (#4369) * Make SPI frequency configurable on Native * Make the tophone queue size configurable for Portduino * The modified SPISettings must be configured in setup(), after config.yaml is processed * make MeshService a pointer, so we can configure MAX_RX_TOPHONE at run time * Got a little over excited with refactoring * Silence a warning --- bin/config-dist.yaml | 14 +++++++++++-- src/ButtonThread.cpp | 4 ++-- src/main.cpp | 15 +++++++++----- src/mesh/MeshModule.cpp | 2 +- src/mesh/MeshService.cpp | 6 +++++- src/mesh/MeshService.h | 2 +- src/mesh/PhoneAPI.cpp | 20 +++++++++---------- src/mesh/ProtobufModule.h | 2 +- src/mesh/RadioInterface.cpp | 4 ++-- src/mesh/SinglePortModule.h | 4 ++-- src/mesh/mesh-pb-constants.h | 2 ++ src/modules/AdminModule.cpp | 6 +++--- src/modules/AtakPluginModule.cpp | 4 ++-- src/modules/CannedMessageModule.cpp | 8 ++++---- src/modules/DetectionSensorModule.cpp | 4 ++-- src/modules/DropzoneModule.cpp | 4 ++-- src/modules/NeighborInfoModule.cpp | 2 +- src/modules/NodeInfoModule.cpp | 8 ++++---- src/modules/PositionModule.cpp | 14 ++++++------- src/modules/RangeTestModule.cpp | 4 ++-- src/modules/RemoteHardwareModule.cpp | 2 +- src/modules/RoutingModule.cpp | 2 +- src/modules/SerialModule.cpp | 4 ++-- src/modules/Telemetry/AirQualityTelemetry.cpp | 6 +++--- src/modules/Telemetry/DeviceTelemetry.cpp | 6 +++--- .../Telemetry/EnvironmentTelemetry.cpp | 6 +++--- src/modules/Telemetry/PowerTelemetry.cpp | 6 +++--- src/modules/esp32/AudioModule.cpp | 2 +- src/modules/esp32/PaxcounterModule.cpp | 2 +- src/modules/esp32/StoreForwardModule.cpp | 6 +++--- src/mqtt/MQTT.cpp | 8 ++++---- src/platform/portduino/PortduinoGlue.cpp | 3 +++ src/platform/portduino/PortduinoGlue.h | 2 ++ src/platform/portduino/SimRadio.cpp | 4 ++-- variants/heltec_mesh_node_t114/variant.h | 4 ++-- variants/portduino/variant.h | 1 + 36 files changed, 110 insertions(+), 83 deletions(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 0ec5a440b..1cd219c4c 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -54,6 +54,8 @@ Lora: # ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341 +# spiSpeed: 2000000 + ### Set gpio chip to use in /dev/. Defaults to 0. ### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4 # gpiochip: 4 @@ -111,6 +113,9 @@ Display: # Height: 320 # Rotate: true +### You can also specify the spi device for the display to use +# spidev: spidev0.0 + Touchscreen: ### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching. @@ -126,9 +131,13 @@ Touchscreen: # CS: 7 # IRQ: 17 -### Configure device for direct keyboard input +### You can also specify the spi device for the touchscreen to use +# spidev: spidev0.0 + Input: +### Configure device for direct keyboard input + # KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd ### @@ -142,4 +151,5 @@ Webserver: # RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer General: - MaxNodes: 200 \ No newline at end of file + MaxNodes: 200 + MaxMessageQueue: 100 \ No newline at end of file diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 914ff8e06..1b101044f 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -144,8 +144,8 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_DOUBLE_PRESSED: { LOG_BUTTON("Double press!\n"); - service.refreshLocalMeshNode(); - auto sentPosition = service.trySendPosition(NODENUM_BROADCAST, true); + service->refreshLocalMeshNode(); + auto sentPosition = service->trySendPosition(NODENUM_BROADCAST, true); if (screen) { if (sentPosition) screen->print("Sent ad-hoc position\n"); diff --git a/src/main.cpp b/src/main.cpp index 39212a0a9..561b24a34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -208,7 +208,6 @@ uint32_t timeLastPowered = 0; static Periodic *ledPeriodic; static OSThread *powerFSMthread; static OSThread *ambientLightingThread; -SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); RadioInterface *rIf = NULL; @@ -231,6 +230,12 @@ void printInfo() void setup() { concurrency::hasBeenSetup = true; +#if ARCH_PORTDUINO + SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0); +#else + SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); +#endif + meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; @@ -714,8 +719,8 @@ void setup() LOG_DEBUG("Starting audio thread\n"); audioThread = new AudioThread(); #endif - - service.init(); + service = new MeshService(); + service->init(); // Now that the mesh service is created, create any modules setupModules(); @@ -1080,7 +1085,7 @@ void loop() // TODO: This should go into a thread handled by FreeRTOS. // handleWebResponse(); - service.loop(); + service->loop(); long delayMsec = mainController.runOrDelay(); @@ -1094,4 +1099,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 1ef4f60d8..604ac9dc4 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -170,7 +170,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) if (isDecoded && mp.decoded.want_response && toUs) { if (currentReply) { printPacket("Sending response", currentReply); - service.sendToMesh(currentReply); + service->sendToMesh(currentReply); currentReply = NULL; } else if (mp.from != ourNodeNum && !ignoreRequest) { // Note: if the message started with the local node or a module asked to ignore the request, we don't want to send a diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 1181ffb9a..fd07c3801 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -23,6 +23,10 @@ #include "nimble/NimbleBluetooth.h" #endif +#if ARCH_PORTDUINO +#include "PortduinoGlue.h" +#endif + /* receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone. It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were @@ -53,7 +57,7 @@ nodenum (filtering against whatever it knows about the nodedb) and rebroadcast t FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first. */ -MeshService service; +MeshService *service; static MemoryDynamic staticMqttClientProxyMessagePool; diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index ef92ba7d4..528adb137 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -150,4 +150,4 @@ class MeshService friend class RoutingModule; }; -extern MeshService service; \ No newline at end of file +extern MeshService *service; \ No newline at end of file diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index f0775741a..fc0099e87 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -41,7 +41,7 @@ void PhoneAPI::handleStartConfig() // Must be before setting state (because state is how we know !connected) if (!isConnected()) { onConnectionChanged(true); - observe(&service.fromNumChanged); + observe(&service->fromNumChanged); #ifdef FSCom observe(&xModem.packetReady); #endif @@ -63,7 +63,7 @@ void PhoneAPI::close() if (state != STATE_SEND_NOTHING) { state = STATE_SEND_NOTHING; - unobserve(&service.fromNumChanged); + unobserve(&service->fromNumChanged); #ifdef FSCom unobserve(&xModem.packetReady); #endif @@ -186,7 +186,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.my_info = myNodeInfo; state = STATE_SEND_OWN_NODEINFO; - service.refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon. + service->refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon. break; case STATE_SEND_OWN_NODEINFO: { @@ -443,7 +443,7 @@ void PhoneAPI::handleDisconnect() void PhoneAPI::releasePhonePacket() { if (packetForPhone) { - service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore + service->releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore packetForPhone = NULL; } } @@ -451,7 +451,7 @@ void PhoneAPI::releasePhonePacket() void PhoneAPI::releaseQueueStatusPhonePacket() { if (queueStatusPacketForPhone) { - service.releaseQueueStatusToPool(queueStatusPacketForPhone); + service->releaseQueueStatusToPool(queueStatusPacketForPhone); queueStatusPacketForPhone = NULL; } } @@ -459,7 +459,7 @@ void PhoneAPI::releaseQueueStatusPhonePacket() void PhoneAPI::releaseMqttClientProxyPhonePacket() { if (mqttClientProxyMessageForPhone) { - service.releaseMqttClientProxyMessageToPool(mqttClientProxyMessageForPhone); + service->releaseMqttClientProxyMessageToPool(mqttClientProxyMessageForPhone); mqttClientProxyMessageForPhone = NULL; } } @@ -495,9 +495,9 @@ bool PhoneAPI::available() return true; // Always say we have something, because we might need to advance our state machine case STATE_SEND_PACKETS: { if (!queueStatusPacketForPhone) - queueStatusPacketForPhone = service.getQueueStatusForPhone(); + queueStatusPacketForPhone = service->getQueueStatusForPhone(); if (!mqttClientProxyMessageForPhone) - mqttClientProxyMessageForPhone = service.getMqttClientProxyMessageForPhone(); + mqttClientProxyMessageForPhone = service->getMqttClientProxyMessageForPhone(); bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone; if (hasPacket) return true; @@ -520,7 +520,7 @@ bool PhoneAPI::available() #endif if (!packetForPhone) - packetForPhone = service.getForPhone(); + packetForPhone = service->getForPhone(); hasPacket = !!packetForPhone; // LOG_DEBUG("available hasPacket=%d\n", hasPacket); return hasPacket; @@ -538,7 +538,7 @@ bool PhoneAPI::available() bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) { printPacket("PACKET FROM PHONE", &p); - service.handleToRadio(p); + service->handleToRadio(p); return true; } diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index ed76b877f..3b438ebeb 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -38,7 +38,7 @@ template class ProtobufModule : protected SinglePortModule /** * Return a mesh packet which has been preinited with a particular protobuf data payload and port number. * You can then send this packet (after customizing any of the payload fields you might need) with - * service.sendToMesh() + * service->sendToMesh() */ meshtastic_MeshPacket *allocDataProtobuf(const T &payload) { diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index e9445ccac..262d2d6a9 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -334,7 +334,7 @@ bool RadioInterface::init() { LOG_INFO("Starting meshradio init...\n"); - configChangedObserver.observe(&service.configChanged); + configChangedObserver.observe(&service->configChanged); preflightSleepObserver.observe(&preflightSleep); notifyDeepSleepObserver.observe(¬ifyDeepSleep); @@ -585,4 +585,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} +} \ No newline at end of file diff --git a/src/mesh/SinglePortModule.h b/src/mesh/SinglePortModule.h index a5aaa2582..e43de09d1 100644 --- a/src/mesh/SinglePortModule.h +++ b/src/mesh/SinglePortModule.h @@ -26,7 +26,7 @@ class SinglePortModule : public MeshModule /** * Return a mesh packet which has been preinited as a data packet with a particular port number. * You can then send this packet (after customizing any of the payload fields you might need) with - * service.sendToMesh() + * service->sendToMesh() */ meshtastic_MeshPacket *allocDataPacket() { @@ -36,4 +36,4 @@ class SinglePortModule : public MeshModule return p; } -}; +}; \ No newline at end of file diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index b8ef236c9..f91c48560 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -14,7 +14,9 @@ /// max number of packets which can be waiting for delivery to android - note, this value comes from mesh.options protobuf // FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in // RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0])) +#ifndef MAX_RX_TOPHONE #define MAX_RX_TOPHONE 32 +#endif /// max number of nodes allowed in the mesh #ifndef MAX_NUM_NODES diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 747f8a8a5..26ddab0db 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -348,7 +348,7 @@ void AdminModule::handleSetOwner(const meshtastic_User &o) } if (changed) { // If nothing really changed, don't broadcast on the network or write to flash - service.reloadOwner(!hasOpenEditTransaction); + service->reloadOwner(!hasOpenEditTransaction); saveChanges(SEGMENT_DEVICESTATE); } } @@ -848,7 +848,7 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot) { if (!hasOpenEditTransaction) { LOG_INFO("Saving changes to disk\n"); - service.reloadConfig(saveWhat); // Calls saveToDisk among other things + service->reloadConfig(saveWhat); // Calls saveToDisk among other things } else { LOG_INFO("Delaying save of changes to disk until the open transaction is committed\n"); } @@ -879,7 +879,7 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p) channels.setChannel(primaryChannel); channels.onConfigChanged(); - service.reloadOwner(false); + service->reloadOwner(false); saveChanges(SEGMENT_CONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); } diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index c05f055d4..437a341db 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -188,7 +188,7 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload), meshtastic_TAKPacket_fields, &uncompressed); - service.sendToPhone(decompressedCopy); + service->sendToPhone(decompressedCopy); } return; -} +} \ No newline at end of file diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 524d37a3d..8df5d2d9e 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -264,8 +264,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) #endif break; case 0xaf: // fn+space send network ping like double press does - service.refreshLocalMeshNode(); - if (service.trySendPosition(NODENUM_BROADCAST, true)) { + service->refreshLocalMeshNode(); + if (service->trySendPosition(NODENUM_BROADCAST, true)) { showTemporaryMessage("Position \nUpdate Sent"); } else { showTemporaryMessage("Node Info \nUpdate Sent"); @@ -388,7 +388,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); - service.sendToMesh( + service->sendToMesh( p, RX_SRC_LOCAL, true); // send to mesh, cc to phone. Even if there's no phone connected, this stores the message to match ACKs } @@ -1048,7 +1048,7 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket & e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen requestFocus(); // Tell Screen::setFrames that our module's frame should be shown, even if not "first" in the frameset this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED; - this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id); + this->incoming = service->getNodenumFromRequestId(mp.decoded.request_id); meshtastic_Routing decoded = meshtastic_Routing_init_default; pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index b6e5f1e29..20d91a381 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -82,7 +82,7 @@ void DetectionSensorModule::sendDetectionMessage() } LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); lastSentToMesh = millis(); - service.sendToMesh(p); + service->sendToMesh(p); delete[] message; } @@ -97,7 +97,7 @@ void DetectionSensorModule::sendCurrentStateMessage() memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); lastSentToMesh = millis(); - service.sendToMesh(p); + service->sendToMesh(p); delete[] message; } diff --git a/src/modules/DropzoneModule.cpp b/src/modules/DropzoneModule.cpp index 8c5b5dcdd..b1ca2af81 100644 --- a/src/modules/DropzoneModule.cpp +++ b/src/modules/DropzoneModule.cpp @@ -1,7 +1,7 @@ #if !MESHTASTIC_EXCLUDE_DROPZONE #include "DropzoneModule.h" -#include "MeshService.h" +#include "Meshservice->h" #include "configuration.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" @@ -20,7 +20,7 @@ int32_t DropzoneModule::runOnce() { // Send on a 5 second delay from receiving the matching request if (startSendConditions != 0 && (startSendConditions + 5000U) < millis()) { - service.sendToMesh(sendConditions(), RX_SRC_LOCAL); + service->sendToMesh(sendConditions(), RX_SRC_LOCAL); startSendConditions = 0; } // Run every second to check if we need to send conditions diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 774b42d7b..fb1249029 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -108,7 +108,7 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies) p->to = dest; p->decoded.want_response = wantReplies; printNeighborInfo("SENDING", &neighborInfo); - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); } /* diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 78af7099a..62cf9d2a1 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -26,7 +26,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // if user has changed while packet was not for us, inform phone if (hasChanged && !wasBroadcast && mp.to != nodeDB->getNodeNum()) - service.sendToPhone(packetPool.allocCopy(mp)); + service->sendToPhone(packetPool.allocCopy(mp)); // LOG_DEBUG("did handleReceived\n"); return false; // Let others look at this message also if they want @@ -36,7 +36,7 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha { // cancel any not yet sent (now stale) position packets if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal) - service.cancelSending(prevPacketId); + service->cancelSending(prevPacketId); meshtastic_MeshPacket *p = allocReply(); if (p) { // Check whether we didn't ignore it @@ -52,7 +52,7 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha prevPacketId = p->id; - service.sendToMesh(p); + service->sendToMesh(p); } } @@ -98,4 +98,4 @@ int32_t NodeInfoModule::runOnce() sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies) } return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs); -} +} \ No newline at end of file diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 228929e96..5da918049 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -141,7 +141,7 @@ meshtastic_MeshPacket *PositionModule::allocReply() return nullptr; } - meshtastic_NodeInfoLite *node = service.refreshLocalMeshNode(); // should guarantee there is now a position + meshtastic_NodeInfoLite *node = service->refreshLocalMeshNode(); // should guarantee there is now a position assert(node->has_position); // configuration of POSITION packet @@ -280,7 +280,7 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha { // cancel any not yet sent (now stale) position packets if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal) - service.cancelSending(prevPacketId); + service->cancelSending(prevPacketId); // Set's the class precision value for this particular packet if (channels.getByIndex(channel).settings.has_module_settings) { @@ -309,7 +309,7 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha if (channel > 0) p->channel = channel; - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && @@ -359,7 +359,7 @@ int32_t PositionModule::runOnce() } } } else if (config.position.position_broadcast_smart_enabled) { - const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position + const meshtastic_NodeInfoLite *node2 = service->refreshLocalMeshNode(); // should guarantee there is now a position if (hasValidPosition(node2)) { // The minimum time (in seconds) that would pass before we are able to send a new position packet. @@ -398,7 +398,7 @@ void PositionModule::sendLostAndFoundText() p->decoded.payload.size = strlen(message); memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); delete[] message; } @@ -437,7 +437,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic void PositionModule::handleNewPosition() { meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); - const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position + const meshtastic_NodeInfoLite *node2 = service->refreshLocalMeshNode(); // should guarantee there is now a position // We limit our GPS broadcasts to a max rate if (hasValidPosition(node2)) { auto smartPosition = getDistanceTraveledSinceLastSend(node->position); @@ -458,4 +458,4 @@ void PositionModule::handleNewPosition() } } -#endif +#endif \ No newline at end of file diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 9bdb36b0e..8154a661e 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -120,7 +120,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply memcpy(p->decoded.payload.bytes, heartbeatString, p->decoded.payload.size); - service.sendToMesh(p); + service->sendToMesh(p); // TODO: Handle this better. We want to keep the phone awake otherwise it stops sending. powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); @@ -291,4 +291,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} +} \ No newline at end of file diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index 8e64b9a9c..d999e75cc 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -131,7 +131,7 @@ int32_t RemoteHardwareModule::runOnce() r.type = meshtastic_HardwareMessage_Type_GPIOS_CHANGED; r.gpio_value = curVal; meshtastic_MeshPacket *p = allocDataProtobuf(r); - service.sendToMesh(p); + service->sendToMesh(p); } } } else { diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index 80ac92fff..87015032d 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -17,7 +17,7 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh // Note: we are careful not to send back packets that started with the phone back to the phone if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB->getNodeNum()) && (mp.from != 0)) { printPacket("Delivering rx packet", &mp); - service.handleFromRadio(&mp); + service->handleFromRadio(&mp); } return false; // Let others look at this message also if they want diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 4b8a4d228..f0ba64f65 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -236,7 +236,7 @@ void SerialModule::sendTelemetry(meshtastic_Telemetry m) p->to = NODENUM_BROADCAST; p->decoded.want_response = false; p->priority = meshtastic_MeshPacket_Priority_RELIABLE; - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); } /** @@ -272,7 +272,7 @@ void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies) p->decoded.payload.size = serialPayloadSize; // You must specify how many bytes are in the reply memcpy(p->decoded.payload.bytes, serialBytes, p->decoded.payload.size); - service.sendToMesh(p); + service->sendToMesh(p); } /** diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 6d2bf5e01..d07296710 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -54,7 +54,7 @@ int32_t AirQualityTelemetryModule::runOnce() airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; - } else if (service.isToPhoneQueueEmpty()) { + } else if (service->isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); @@ -162,10 +162,10 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) lastMeasurementPacket = packetPool.allocCopy(*p); if (phoneOnly) { LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); + service->sendToPhone(p); } else { LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); } return true; } diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 9c1ac289c..4bde73f41 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -25,7 +25,7 @@ int32_t DeviceTelemetryModule::runOnce() config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { sendTelemetry(); lastSentToMesh = uptimeLastMs; - } else if (service.isToPhoneQueueEmpty()) { + } else if (service->isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); @@ -113,10 +113,10 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) nodeDB->updateTelemetry(nodeDB->getNodeNum(), telemetry, RX_SRC_LOCAL); if (phoneOnly) { LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); + service->sendToPhone(p); } else { LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); } return true; } \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index fec1ee461..a100d1ef5 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -161,7 +161,7 @@ int32_t EnvironmentTelemetryModule::runOnce() sendTelemetry(); lastSentToMesh = now; } else if (((lastSentToPhone == 0) || ((now - lastSentToPhone) >= sendToPhoneIntervalMs)) && - (service.isToPhoneQueueEmpty())) { + (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); @@ -449,10 +449,10 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) lastMeasurementPacket = packetPool.allocCopy(*p); if (phoneOnly) { LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); + service->sendToPhone(p); } else { LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index a6f922e56..90371780f 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -78,7 +78,7 @@ int32_t PowerTelemetryModule::runOnce() sendTelemetry(); lastSentToMesh = now; } else if (((lastSentToPhone == 0) || ((now - lastSentToPhone) >= sendToPhoneIntervalMs)) && - (service.isToPhoneQueueEmpty())) { + (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); @@ -244,10 +244,10 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) lastMeasurementPacket = packetPool.allocCopy(*p); if (phoneOnly) { LOG_INFO("Sending packet to phone\n"); - service.sendToPhone(p); + service->sendToPhone(p); } else { LOG_INFO("Sending packet to mesh\n"); - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 2e2e4f528..8a29f9a2e 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -266,7 +266,7 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->decoded.payload.size = tx_encode_frame_index; memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size); - service.sendToMesh(p); + service->sendToMesh(p); } ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 895328234..71810df07 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -52,7 +52,7 @@ bool PaxcounterModule::sendInfo(NodeNum dest) p->decoded.want_response = false; p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - service.sendToMesh(p, RX_SRC_LOCAL, true); + service->sendToMesh(p, RX_SRC_LOCAL, true); paxcounterModule->reportedDataSent = true; diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index ff0f796a1..7581bbc38 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -211,7 +211,7 @@ bool StoreForwardModule::sendPayload(NodeNum dest, uint32_t last_time) meshtastic_MeshPacket *p = preparePayload(dest, last_time); if (p) { LOG_INFO("*** Sending S&F Payload\n"); - service.sendToMesh(p); + service->sendToMesh(p); this->requestCount++; return true; } @@ -293,7 +293,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw p->want_ack = false; p->decoded.want_response = false; - service.sendToMesh(p); + service->sendToMesh(p); } /** @@ -336,7 +336,7 @@ void StoreForwardModule::sendErrorTextMessage(NodeNum dest, bool want_response) if (want_response) { ignoreRequest = true; // This text message counts as response. } - service.sendToMesh(pr); + service->sendToMesh(pr); } /** diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2fce526a0..4bb9cd5eb 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -83,7 +83,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); p->decoded.payload.size = jsonPayloadStr.length(); - service.sendToMesh(p, RX_SRC_LOCAL); + service->sendToMesh(p, RX_SRC_LOCAL); } else { LOG_WARN("Received MQTT json payload too long, dropping\n"); } @@ -114,7 +114,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Position_msg, &pos); // make the Data protobuf from position - service.sendToMesh(p, RX_SRC_LOCAL); + service->sendToMesh(p, RX_SRC_LOCAL); } else { LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); } @@ -245,7 +245,7 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) strcpy(msg->topic, topic); strcpy(msg->payload_variant.text, payload); msg->retained = retained; - service.sendMqttMessageToClientProxy(msg); + service->sendMqttMessageToClientProxy(msg); return true; } #if HAS_NETWORKING @@ -265,7 +265,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo msg->payload_variant.data.size = length; memcpy(msg->payload_variant.data.bytes, payload, length); msg->retained = retained; - service.sendMqttMessageToClientProxy(msg); + service->sendMqttMessageToClientProxy(msg); return true; } #if HAS_NETWORKING diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index b910206ec..dc7a9ed61 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -97,6 +97,7 @@ void portduinoSetup() settingsStrings[webserverrootpath] = ""; settingsStrings[spidev] = ""; settingsStrings[displayspidev] = ""; + settingsMap[spiSpeed] = 2000000; YAML::Node yamlConfig; @@ -173,6 +174,7 @@ void portduinoSetup() settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC); settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); settingsMap[ch341Quirk] = yamlConfig["Lora"]["ch341_quirk"].as(false); + settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as(2000000); gpioChipName += std::to_string(settingsMap[gpiochip]); settingsStrings[spidev] = "/dev/" + yamlConfig["Lora"]["spidev"].as("spidev0.0"); @@ -280,6 +282,7 @@ void portduinoSetup() } settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as(200); + settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as(100); } catch (YAML::Exception &e) { std::cout << "*** Exception " << e.what() << std::endl; diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 6b9a8eb8e..0c81b8686 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -19,6 +19,7 @@ enum configNames { user, gpiochip, spidev, + spiSpeed, i2cdev, has_gps, touchscreenModule, @@ -51,6 +52,7 @@ enum configNames { webserver, webserverport, webserverrootpath, + maxtophone, maxnodes }; enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp index d0072cf35..12757fe6b 100644 --- a/src/platform/portduino/SimRadio.cpp +++ b/src/platform/portduino/SimRadio.cpp @@ -199,8 +199,8 @@ void SimRadio::startSend(meshtastic_MeshPacket *txp) pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Compressed_msg, &c); p->decoded.portnum = meshtastic_PortNum_SIMULATOR_APP; - service.sendQueueStatusToPhone(router->getQueueStatus(), 0, p->id); - service.sendToPhone(p); // Sending back to simulator + service->sendQueueStatusToPhone(router->getQueueStatus(), 0, p->id); + service->sendToPhone(p); // Sending back to simulator } void SimRadio::startReceive(meshtastic_MeshPacket *p) diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index b233069c6..615526b22 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -126,8 +126,8 @@ No longer populated on PCB #define LORA_CS (0 + 24) #define SX126X_DIO1 (0 + 20) // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching -// #define SX1262_DIO3 \ -// (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the +// #define SX1262_DIO3 (0 + 21) +// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the // main // CPU? #define SX126X_BUSY (0 + 17) diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h index 414a3fa56..70d7cbd52 100644 --- a/variants/portduino/variant.h +++ b/variants/portduino/variant.h @@ -1,5 +1,6 @@ #define HAS_SCREEN 1 #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 +#define MAX_RX_TOPHONE settingsMap[maxtophone] #define MAX_NUM_NODES settingsMap[maxnodes] #define RADIOLIB_GODMODE 1 From 4b4c1669a964c7e3f543952a2633db09afbbf2a1 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Fri, 2 Aug 2024 14:03:59 +1200 Subject: [PATCH 0777/1377] Initial support for HT-VME290, sleep fixes for HT-VME213 (#4334) * Fix I2C pindefs * Initial driver testing for HT-VME290 * E-Ink full refresh after canned message pop up * Tidy variant folders * Clean ESP32 cpuDeepSleep method Merge sections, and remove the random assortment of gpio holds left behind. * Enable 32kHz in variant.h * Orient E290 with LoRa antenna facing up * Revert "Clean ESP32 cpuDeepSleep method" This reverts commit cb8ee508ec2d6bc27a8e228021fd1efbd034c4a0. * Reduce deep-sleep current for VME213 (non-intrusive) Originally I made an attempt at tidying up the cpuDeepSleep method, but have reverted that. New commit makes only the changes needed to support VME213. Don't really want the headache of breaking sleep for other variants, especially when this PR is just about implementing new boards. * Update lib_deps; remove board_level extra --- boards/heltec_vision_master_e290.json | 42 +++++++++++++++++++ src/graphics/Screen.cpp | 7 ++++ src/mesh/NodeDB.cpp | 4 ++ src/modules/CannedMessageModule.cpp | 9 ++++ src/platform/esp32/main-esp32.cpp | 18 ++++---- variants/heltec_vision_master_e213/variant.h | 9 ++-- .../heltec_vision_master_e290/pins_arduino.h | 8 ++-- .../heltec_vision_master_e290/platformio.ini | 32 +++++++------- variants/heltec_vision_master_e290/variant.h | 36 +++++++--------- variants/heltec_wireless_paper/variant.h | 9 ++-- variants/heltec_wireless_paper_v1/variant.h | 9 ++-- 11 files changed, 121 insertions(+), 62 deletions(-) create mode 100644 boards/heltec_vision_master_e290.json diff --git a/boards/heltec_vision_master_e290.json b/boards/heltec_vision_master_e290.json new file mode 100644 index 000000000..70f7d5f02 --- /dev/null +++ b/boards/heltec_vision_master_e290.json @@ -0,0 +1,42 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e290" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E290", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e290/", + "vendor": "Heltec" +} diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 54fd1ea4d..633fb4c67 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1902,6 +1902,13 @@ int32_t Screen::runOnce() // standard screen loop handling here if (config.display.auto_screen_carousel_secs > 0 && (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) { + +// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead +// Carousel is potentially a major source of E-Ink display wear +#if !defined(EINK_BACKGROUND_USES_FAST) + EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); +#endif + LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition)); handleOnPress(); } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6fbbbf546..1f66857f8 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -310,6 +310,10 @@ void NodeDB::installDefaultConfig() config.display.screen_on_secs = 30; config.display.wake_on_tap_or_motion = true; #endif +#ifdef HELTEC_VISION_MASTER_E290 + // Orient so that LoRa antenna faces up + config.display.flip_screen = true; +#endif initConfigIntervals(); } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 8df5d2d9e..f4ee3abd2 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -17,6 +17,9 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" #endif +#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) +#include "graphics/EInkDynamicDisplay.h" // To select between full and fast refresh on E-Ink displays +#endif #ifndef INPUTBROKER_MATRIX_TYPE #define INPUTBROKER_MATRIX_TYPE 0 @@ -928,6 +931,9 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { + // E-Ink: clean the screen *after* this pop-up + EINK_ADD_FRAMEFLAG(display, COSMETIC); + requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); @@ -950,6 +956,9 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi); } } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + // E-Ink: clean the screen *after* this pop-up + EINK_ADD_FRAMEFLAG(display, COSMETIC); + requestFocus(); // Tell Screen::setFrames to move to our module's frame display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 3910f718f..6bce498ab 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -195,16 +195,16 @@ void cpuDeepSleep(uint32_t msecToWake) button(s), maybe we should not include any other GPIOs... */ #if SOC_RTCIO_HOLD_SUPPORTED - static const uint8_t rtcGpios[] = {/* 0, */ 2, - /* 4, */ -#ifndef USE_JTAG - 13, - /* 14, */ /* 15, */ + static const uint8_t rtcGpios[] = { +#ifndef HELTEC_VISION_MASTER_E213 + // For this variant, >20mA leaks through the display if pin 2 held + // Todo: check if it's safe to remove this pin for all variants + 2, #endif - /* 25, */ /* 26, */ /* 27, */ - /* 32, */ /* 33, */ 34, 35, - /* 36, */ 37 - /* 38, 39 */}; +#ifndef USE_JTAG + 13, +#endif + 34, 35, 37}; for (int i = 0; i < sizeof(rtcGpios); i++) rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index 99bc1d138..bbc697f09 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -15,9 +15,9 @@ // SPI #define SPI_INTERFACES_COUNT 2 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK +#define PIN_SPI_MISO 11 +#define PIN_SPI_MOSI 10 +#define PIN_SPI_SCK 9 // Power #define VEXT_ENABLE 18 // Powers the E-Ink display, and the 3.3V supply to the I2C QuickLink connector @@ -29,11 +29,12 @@ #define ADC_CHANNEL ADC1_GPIO7_CHANNEL #define ADC_MULTIPLIER 4.9 * 1.03 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 +#define HAS_32768HZ // LoRa #define USE_SX1262 -#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_DIO0 RADIOLIB_NC // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h index e5d507846..77cf3176a 100644 --- a/variants/heltec_vision_master_e290/pins_arduino.h +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -3,15 +3,15 @@ #include -static const uint8_t LED_BUILTIN = 35; +static const uint8_t LED_BUILTIN = -1; #define BUILTIN_LED LED_BUILTIN // backward compatibility #define LED_BUILTIN LED_BUILTIN static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; static const uint8_t SS = 8; static const uint8_t MOSI = 10; @@ -56,6 +56,6 @@ static const uint8_t T14 = 14; static const uint8_t RST_LoRa = 12; static const uint8_t BUSY_LoRa = 13; -static const uint8_t DIO0 = 14; +static const uint8_t DIO1 = 14; #endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini index 60ff60036..e1ba100ae 100644 --- a/variants/heltec_vision_master_e290/platformio.ini +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -1,25 +1,25 @@ [env:heltec-vision-master-e290] -board_level = extra extends = esp32s3_base -board = heltec_wifi_lora_32_V3 +board = heltec_vision_master_e290 build_flags = ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_e290 - -DHELTEC_VISION_MASTER_E290 - -DEINK_DISPLAY_MODEL=GxEPD2_290_BS - -DEINK_WIDTH=296 - -DEINK_HEIGHT=128 -; -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk -; -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted -; -D EINK_LIMIT_RATE_BACKGROUND_SEC=1 ; Minimum interval between BACKGROUND updates -; -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -; -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -; -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -; -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight + -I variants/heltec_vision_master_e290 + -D HELTEC_VISION_MASTER_E290 + -D BUTTON_CLICK_MS=200 + -D EINK_DISPLAY_MODEL=GxEPD2_290_BN8 + -D EINK_WIDTH=296 + -D EINK_HEIGHT=128 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight +; -D EINK_LIMIT_GHOSTING_PX=2000 ; How much image ghosting is tolerated +; -D EINK_BACKGROUND_USES_FAST ; (If enabled) don't redraw RESPONSIVE frames at next BACKGROUND update lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d + https://github.com/meshtastic/GxEPD2#448c8538129fde3d02a7cb5e6fc81971ad92547f lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h index a8ec5485b..6af4b06a5 100644 --- a/variants/heltec_vision_master_e290/variant.h +++ b/variants/heltec_vision_master_e290/variant.h @@ -1,48 +1,42 @@ -// #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals +// I2C #define I2C_SDA SDA #define I2C_SCL SCL +// Display (E-Ink) #define USE_EINK - -/* - * eink display pins - */ #define PIN_EINK_CS 3 -#define PIN_EINK_BUSY 5 +#define PIN_EINK_BUSY 6 #define PIN_EINK_DC 4 #define PIN_EINK_RES 5 #define PIN_EINK_SCLK 2 #define PIN_EINK_MOSI 1 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 11 +#define PIN_SPI_MOSI 10 +#define PIN_SPI_SCK 9 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK - -#define VEXT_ENABLE 18 // powers the e-ink display -#define VEXT_ON_VALUE 1 -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 18 // Powers the E-Ink display only +#define VEXT_ON_VALUE HIGH #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 #define ADC_CHANNEL ADC1_GPIO7_CHANNEL #define ADC_MULTIPLIER 4.9 * 1.03 -#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 +#define HAS_32768HZ +// LoRa #define USE_SX1262 -#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_DIO0 RADIOLIB_NC // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY -#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define LORA_SCK 9 #define LORA_MISO 11 diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index c41d6d9df..a7bd460f7 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -16,9 +16,9 @@ // SPI #define SPI_INTERFACES_COUNT 2 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK +#define PIN_SPI_MISO 11 +#define PIN_SPI_MOSI 10 +#define PIN_SPI_SCK 9 // Power #define VEXT_ENABLE 45 // Active low, powers the E-Ink display @@ -28,11 +28,12 @@ #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +#define HAS_32768HZ // LoRa #define USE_SX1262 -#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_DIO0 RADIOLIB_NC // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index c41d6d9df..a7bd460f7 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -16,9 +16,9 @@ // SPI #define SPI_INTERFACES_COUNT 2 -#define PIN_SPI_MISO 10 // MISO -#define PIN_SPI_MOSI 11 // MOSI -#define PIN_SPI_SCK 9 // SCK +#define PIN_SPI_MISO 11 +#define PIN_SPI_MOSI 10 +#define PIN_SPI_SCK 9 // Power #define VEXT_ENABLE 45 // Active low, powers the E-Ink display @@ -28,11 +28,12 @@ #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high +#define HAS_32768HZ // LoRa #define USE_SX1262 -#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_DIO0 RADIOLIB_NC // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY From 8db6039264003aa2a803a3690c83ef07cd0da3ef Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 2 Aug 2024 19:32:28 +0800 Subject: [PATCH 0778/1377] Remove empty file, StatusHandler.h (#4372) Was added 4 years ago, never used/modified. --- src/StatusHandler.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/StatusHandler.h diff --git a/src/StatusHandler.h b/src/StatusHandler.h deleted file mode 100644 index e69de29bb..000000000 From 48c063518887616f208629f7b4f9573487c7d9b2 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 2 Aug 2024 23:10:55 +0800 Subject: [PATCH 0779/1377] Remove OSTimer (#4373) This code is not included anywhere, and none of the functions are called by code elsewhere in meshtastic. --- src/OSTimer.cpp | 60 ------------------------------------------------- src/OSTimer.h | 8 ------- 2 files changed, 68 deletions(-) delete mode 100644 src/OSTimer.cpp delete mode 100644 src/OSTimer.h diff --git a/src/OSTimer.cpp b/src/OSTimer.cpp deleted file mode 100644 index f6739d75e..000000000 --- a/src/OSTimer.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "OSTimer.h" -#include "configuration.h" - -/** - * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) - * - * NOTE! xTimerPend... seems to ignore the time passed in on ESP32 and on NRF52 - * The reason this didn't work is because xTimerPednFunctCall really isn't a timer function at all - it just means run the -callback - * from the timer thread the next time you have spare cycles. - * - * @return true if successful, false if the timer fifo is too full. - -bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) -{ - return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec)); -} */ - -#ifdef ARCH_ESP32 - -// Super skanky quick hack to use hardware timers of the ESP32 -static hw_timer_t *timer; -static PendableFunction tCallback; -static void *tParam1; -static uint32_t tParam2; - -static void IRAM_ATTR onTimer() -{ - (*tCallback)(tParam1, tParam2); -} - -/** - * Schedules a hardware callback function to be executed after a specified delay. - * - * @param callback The function to be executed. - * @param param1 The first parameter to be passed to the function. - * @param param2 The second parameter to be passed to the function. - * @param delayMsec The delay time in milliseconds before the function is executed. - * - * @return True if the function was successfully scheduled, false otherwise. - */ -bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) -{ - if (!timer) { - timer = timerBegin(0, 80, true); // one usec per tick (main clock is 80MhZ on ESP32) - assert(timer); - timerAttachInterrupt(timer, &onTimer, true); - } - - tCallback = callback; - tParam1 = param1; - tParam2 = param2; - - timerAlarmWrite(timer, delayMsec * 1000UL, false); // Do not reload, we want it to be a single shot timer - timerRestart(timer); - timerAlarmEnable(timer); - return true; -} - -#endif \ No newline at end of file diff --git a/src/OSTimer.h b/src/OSTimer.h deleted file mode 100644 index 37415f3a6..000000000 --- a/src/OSTimer.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2); - -/// Uses a hardware timer, but calls the handler in _interrupt_ context -bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); \ No newline at end of file From 703da1d8c7956490d569812ff6fb80a0e8e7aa2d Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 2 Aug 2024 16:28:04 -0700 Subject: [PATCH 0780/1377] Fix build to not use incorrect OneButton version (#4374) * Fix build to not use incorrect OneButton version OneButton pushed out a new update today that has a different API rather than just use whichever new version they push, stay on 2.5.x until someone sees a need to update. Fixes build for wm1100 tracker. * Update stm32.ini * 2.6.1 * Try github tag instead? * Update stm32.ini --------- Co-authored-by: Ben Meadors --- arch/stm32/stm32.ini | 4 ++-- platformio.ini | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index 2cea4bbc5..d6d14e2c9 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -32,5 +32,5 @@ lib_deps = https://github.com/caveman99/Crypto.git#f61ae26a53f7a2d0ba5511625b8bf8eff3a35d5e lib_ignore = - mathertel/OneButton - Wire \ No newline at end of file + https://github.com/mathertel/OneButton@~2.6.1 + Wire diff --git a/platformio.ini b/platformio.ini index b3f677247..e60f0d7b9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -86,7 +86,7 @@ monitor_filters = direct lib_deps = jgromes/RadioLib@~6.6.0 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 - mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce + https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 @@ -155,4 +155,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee From 09ea198205c1a9a543154fea9b2e41891360bab8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 2 Aug 2024 16:55:04 -0700 Subject: [PATCH 0781/1377] Automatically generate .uf2 files anytime we generate a .hex file for nrf52 (#4370) * Automatically generate .uf2 files (which are often used by nrf52 bootloaders for installing app loads) anytime we generate a new hex file. This tool takes very little time to run and it is handy for development * Remove an old custom target I had tried to add to autogen uf2 files (that never worked) Build output now looks like: $ pio run --environment tracker-t1000-e Processing tracker-t1000-e (board: tracker-t1000-e; platform: platformio/nordicnrf52@^10.5.0; framework: arduino) ... Generating UF2 file Converting to uf2, output size: 1395200, start address: 0x27000 Wrote 1395200 bytes to /home/kevinh/development/meshtastic/firmware/.pio/build/tracker-t1000-e/firmware.uf2 Building .pio/build/tracker-t1000-e/firmware.zip Zip created at .pio/build/tracker-t1000-e/firmware.zip =================================================================================== [SUCCESS] Took 9.33 seconds =================================================================================== Environment Status Duration --------------- -------- ------------ tracker-t1000-e SUCCESS 00:00:09.327 =================================================================================== 1 succeeded in 00:00:09.327 =================================================================================== Co-authored-by: Ben Meadors --- bin/platformio-custom.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 065f1267b..4f7ce55d0 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -78,6 +78,11 @@ if platform.name == "espressif32": # For newer ESP32 targets, using newlib nano works better. env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) +if platform.name == "nordicnrf52": + env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", + env.VerboseAction(f"python ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", + "Generating UF2 file")) + Import("projenv") prefsLoc = projenv["PROJECT_DIR"] + "/version.properties" @@ -90,13 +95,4 @@ projenv.Append( "-DAPP_VERSION=" + verObj["long"], "-DAPP_VERSION_SHORT=" + verObj["short"], ] -) - -# Add a custom p.io project task to run the UF2 conversion script. -env.AddCustomTarget( - name="Convert Hex to UF2", - dependencies=None, - actions=["PYTHON .\\bin\\uf2conv.py $BUILD_DIR\$env\\firmware.hex -c -f 0xADA52840 -o $BUILD_DIR\$env\\firmware.uf2"], - title="Convert hex to uf2", - description="Runs the python script to convert an already-built .hex file into .uf2 for copying to a device" -) +) \ No newline at end of file From dd552a99e18ec0c67710211ae357f88e43e32607 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 2 Aug 2024 18:20:44 -0700 Subject: [PATCH 0782/1377] fix #4367 make USB power detection work correctly on seeed trackers (#4376) for wio tracker 1110 and 1000-E and possibly other nrf52 boards. The problem was that nrf52 power stuff wasn't generating regular powerstatus notifications (because that code was guarded by a batteryLevel check which was null for those boards). So I've cleaned up the battery status stuff a bit and we now have fewer special cases. Tested on a 1000-E, tracker 1110 and a rak4631 board. Co-authored-by: Ben Meadors --- src/Power.cpp | 158 +++++++++++++------------- src/PowerFSM.cpp | 2 +- variants/tracker-t1000-e/variant.h | 5 +- variants/wio-tracker-wm1110/variant.h | 5 +- 4 files changed, 87 insertions(+), 83 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 138b06e71..e8e406a94 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -461,7 +461,7 @@ class AnalogBatteryLevel : public HasBatteryLevel #endif }; -AnalogBatteryLevel analogLevel; +static AnalogBatteryLevel analogLevel; Power::Power() : OSThread("Power") { @@ -560,6 +560,10 @@ bool Power::setup() { bool found = axpChipInit() || analogInit(); +#ifdef NRF_APM + found = true; +#endif + enabled = found; low_voltage_counter = 0; @@ -589,10 +593,16 @@ void Power::shutdown() // TODO(girts): move this and other axp stuff to power.h/power.cpp. void Power::readPowerStatus() { + int32_t batteryVoltageMv = -1; // Assume unknown + int8_t batteryChargePercent = -1; + OptionalBool usbPowered = OptUnknown; + OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time + OptionalBool isCharging = OptUnknown; + if (batteryLevel) { - bool hasBattery = batteryLevel->isBatteryConnect(); - uint32_t batteryVoltageMv = 0; - int8_t batteryChargePercent = 0; + hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse; + usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse; + isCharging = batteryLevel->isCharging() ? OptTrue : OptFalse; if (hasBattery) { batteryVoltageMv = batteryLevel->getBattVoltage(); // If the AXP192 returns a valid battery percentage, use it @@ -607,102 +617,90 @@ void Power::readPowerStatus() 0, 100); } } + } - OptionalBool NRF_USB = OptFalse; - +// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass +// (which shares a superclass with the BatteryLevel stuff) +// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current +// practice. #ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect // changes. - static nrfx_power_usb_state_t prev_nrf_usb_state = (nrfx_power_usb_state_t)-1; // -1 so that state detected at boot - nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); + nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); + // LOG_DEBUG("NRF Power %d\n", nrf_usb_state); - // If state changed - if (nrf_usb_state != prev_nrf_usb_state) { - // If changed to DISCONNECTED - if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) { - powerFSM.trigger(EVENT_POWER_DISCONNECTED); - NRF_USB = OptFalse; - } - // If changed to CONNECTED / READY - else { - powerFSM.trigger(EVENT_POWER_CONNECTED); - NRF_USB = OptTrue; - } + // If changed to DISCONNECTED + if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) + isCharging = usbPowered = OptFalse; + // If changed to CONNECTED / READY + else + isCharging = usbPowered = OptTrue; - // Cache the current state - prev_nrf_usb_state = nrf_usb_state; - } #endif - // Notify any status instances that are observing us - const PowerStatus powerStatus2 = PowerStatus( - hasBattery ? OptTrue : OptFalse, batteryLevel->isVbusIn() || NRF_USB == OptTrue ? OptTrue : OptFalse, - batteryLevel->isCharging() || NRF_USB == OptTrue ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent); - LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(), - powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); - newStatus.notifyObservers(&powerStatus2); + + // Notify any status instances that are observing us + const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isCharging, batteryVoltageMv, batteryChargePercent); + LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(), + powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); + newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP - if (lastheap != memGet.getFreeHeap()) { - LOG_DEBUG("Threads running:"); - int running = 0; - for (int i = 0; i < MAX_THREADS; i++) { - auto thread = concurrency::mainController.get(i); - if ((thread != nullptr) && (thread->enabled)) { - LOG_DEBUG(" %s", thread->ThreadName.c_str()); - running++; - } + if (lastheap != memGet.getFreeHeap()) { + LOG_DEBUG("Threads running:"); + int running = 0; + for (int i = 0; i < MAX_THREADS; i++) { + auto thread = concurrency::mainController.get(i); + if ((thread != nullptr) && (thread->enabled)) { + LOG_DEBUG(" %s", thread->ThreadName.c_str()); + running++; } - LOG_DEBUG("\n"); - LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads\n", memGet.getFreeHeap(), memGet.getHeapSize(), - memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false)); - lastheap = memGet.getFreeHeap(); } + LOG_DEBUG("\n"); + LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads\n", memGet.getFreeHeap(), memGet.getHeapSize(), + memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false)); + lastheap = memGet.getFreeHeap(); + } #ifdef DEBUG_HEAP_MQTT - if (mqtt) { - // send MQTT-Packet with Heap-Size - uint8_t dmac[6]; - getMacAddr(dmac); // Get our hardware ID - char mac[18]; - sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]); + if (mqtt) { + // send MQTT-Packet with Heap-Size + uint8_t dmac[6]; + getMacAddr(dmac); // Get our hardware ID + char mac[18]; + sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]); - auto newHeap = memGet.getFreeHeap(); - std::string heapTopic = - (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac); - std::string heapString = std::to_string(newHeap); - mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false); - auto wifiRSSI = WiFi.RSSI(); - std::string wifiTopic = - (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac); - std::string wifiString = std::to_string(wifiRSSI); - mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false); - } + auto newHeap = memGet.getFreeHeap(); + std::string heapTopic = + (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac); + std::string heapString = std::to_string(newHeap); + mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false); + auto wifiRSSI = WiFi.RSSI(); + std::string wifiTopic = + (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac); + std::string wifiString = std::to_string(wifiRSSI); + mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false); + } #endif #endif - // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in - // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. - // - if (powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { - if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) { - low_voltage_counter++; - LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter); - if (low_voltage_counter > 10) { + // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in + // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. + // + if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { + if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) { + low_voltage_counter++; + LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter); + if (low_voltage_counter > 10) { #ifdef ARCH_NRF52 - // We can't trigger deep sleep on NRF52, it's freezing the board - LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n"); + // We can't trigger deep sleep on NRF52, it's freezing the board + LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n"); #else - LOG_INFO("Low voltage detected, triggering deep sleep\n"); - powerFSM.trigger(EVENT_LOW_BATTERY); + LOG_INFO("Low voltage detected, triggering deep sleep\n"); + powerFSM.trigger(EVENT_LOW_BATTERY); #endif - } - } else { - low_voltage_counter = 0; } + } else { + low_voltage_counter = 0; } - } else { - // No power sensing on this board - tell everyone else we have no idea what is happening - const PowerStatus powerStatus3 = PowerStatus(OptUnknown, OptUnknown, OptUnknown, -1, -1); - newStatus.notifyObservers(&powerStatus3); } } @@ -1051,4 +1049,4 @@ bool Power::axpChipInit() #else return false; #endif -} +} \ No newline at end of file diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 72e00810b..699a6bca6 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -26,7 +26,7 @@ static bool isPowered() { // Circumvent the battery sensing logic and assumes constant power if no battery pin or power mgmt IC -#if !defined(BATTERY_PIN) && !defined(HAS_AXP192) && !defined(HAS_AXP2101) +#if !defined(BATTERY_PIN) && !defined(HAS_AXP192) && !defined(HAS_AXP2101) && !defined(NRF_APM) return true; #endif diff --git a/variants/tracker-t1000-e/variant.h b/variants/tracker-t1000-e/variant.h index 75d8ddffc..63c2a76dc 100644 --- a/variants/tracker-t1000-e/variant.h +++ b/variants/tracker-t1000-e/variant.h @@ -40,6 +40,9 @@ extern "C" { #define NUM_ANALOG_INPUTS (6) #define NUM_ANALOG_OUTPUTS (0) +// Use the native nrf52 usb power detection +#define NRF_APM + #define PIN_3V3_EN (32 + 6) // P1.6, Power to Sensors #define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc @@ -147,4 +150,4 @@ extern "C" { * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_TRACKER_T1000_E_ +#endif // _VARIANT_TRACKER_T1000_E_ \ No newline at end of file diff --git a/variants/wio-tracker-wm1110/variant.h b/variants/wio-tracker-wm1110/variant.h index e929332e6..2bb2f1a59 100644 --- a/variants/wio-tracker-wm1110/variant.h +++ b/variants/wio-tracker-wm1110/variant.h @@ -43,6 +43,9 @@ extern "C" { #define WIRE_INTERFACES_COUNT 1 +// We rely on the nrf52840 USB controller to tell us if we are hooked to a power supply +#define NRF_APM + #define PIN_3V3_EN (32 + 1) // P1.01, Power to Sensors #define PIN_WIRE_SDA (0 + 5) // P0.05 @@ -108,4 +111,4 @@ extern "C" { * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_WIO_TRACKER_WM1110_ +#endif // _VARIANT_WIO_TRACKER_WM1110_ \ No newline at end of file From d1ff160256c802a5903b4dd392dd8149cc5e6158 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 3 Aug 2024 05:41:35 -0700 Subject: [PATCH 0783/1377] Generalize SWD debugging stuff so it works on all nrf52 targets. (#4377) * add bootloader install script for wio tracker 1110 board Mostly for documentation purposes for future devs. * Generalize nrf52 hw debugging support so it works on all nrf52 targets --- arch/nrf52/nrf52840.ini | 69 +++++++++++++++++++++ bin/wio_tracker_bootloader_update.bin | 30 ++++++++++ variants/rak4631/platformio.ini | 70 +--------------------- variants/wio-tracker-wm1110/platformio.ini | 3 +- 4 files changed, 101 insertions(+), 71 deletions(-) create mode 100644 bin/wio_tracker_bootloader_update.bin diff --git a/arch/nrf52/nrf52840.ini b/arch/nrf52/nrf52840.ini index cf08fd02e..a13a600f3 100644 --- a/arch/nrf52/nrf52840.ini +++ b/arch/nrf52/nrf52840.ini @@ -7,3 +7,72 @@ lib_deps = ${nrf52_base.lib_deps} ${environmental_base.lib_deps} https://github.com/Kongduino/Adafruit_nRFCrypto.git#e31a8825ea3300b163a0a3c1ddd5de34e10e1371 + +; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board. + +; We want the initial breakpoint at setup() instead of main(). Also we want to enable semihosting at that point so instead of +debug_init_break = tbreak setup +; we just turn off the platformio tbreak and do it in .gdbinit (where we have more flexibility for scripting) +; also we use a permanent breakpoint so it gets reused each time we restart the debugging session? +; debug_init_break = tbreak main + +; Note: add "monitor arm semihosting_redirect tcp 4444 all" if you want the stdout from the device to go to that port number instead +; (for use by meshtastic command line) +; monitor arm semihosting disable +; monitor debug_level 3 +; +; IMPORTANT: fileio must be disabled before using port 5555 - openocd ver 0.12 has a bug where if enabled it never properly parses the special :tt name +; for stdio access. +; monitor arm semihosting_redirect tcp 5555 stdio + +; Also note: it is _impossible_ to do non blocking reads on the semihost console port (an oversight when ARM specified the semihost API). +; So we'll neve be able to general purpose bi-directional communication with the device over semihosting. +debug_extra_cmds = + echo Running .gdbinit script + ;monitor arm semihosting enable + ;monitor arm semihosting_fileio enable + ;monitor arm semihosting_redirect disable + commands 1 + ; echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555" + ; set wantSemihost = 1 + set useSoftDevice = 0 + end + + ; Only reprogram the board if the code has changed +debug_load_mode = modified +;debug_load_mode = manual +; We default to the stlink adapter because it is very cheap and works well, though others (such as jlink) are also supported. +;debug_tool = jlink +debug_tool = stlink +debug_speed = 4000 +;debug_tool = custom +; debug_server = +; openocd +; -f +; /usr/local/share/openocd/scripts/interface/stlink.cfg +; -f +; /usr/local/share/openocd/scripts/target/nrf52.cfg +; $PLATFORMIO_CORE_DIR/packages/tool-openocd/openocd/scripts/interface/cmsis-dap.cfg + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" +; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) +;debug_server = +; pyocd +; gdbserver +; -j +; ${platformio.workspace_dir}/.. +; -t +; nrf52840 +; --semihosting +; --elf +; ${platformio.build_dir}/${this.__env__}/firmware.elf + +; If you want to debug the semihosting support you can turn on extra logging in pyocd with +; -L +; pyocd.debug.semihost.trace=debug + +; The following is not needed because it automatically tries do this +;debug_server_ready_pattern = -.*GDB server started on port \d+.* +;debug_port = localhost:3333 \ No newline at end of file diff --git a/bin/wio_tracker_bootloader_update.bin b/bin/wio_tracker_bootloader_update.bin new file mode 100644 index 000000000..57cd0ad17 --- /dev/null +++ b/bin/wio_tracker_bootloader_update.bin @@ -0,0 +1,30 @@ +# tips from mark on how to replace the (broken) bootloader on the red wio_tracker_1110 boards. + +~/.platformio/penv/bin/adafruit-nrfutil --verbose dfu serial --package wio_tracker_1110_bootloader-0.9.1_s140_7.3.0.zip -p /dev/ttyACM1 -b 115200 --singlebank --touch 1200 + +exit + +# Output should look like + +Upgrading target on /dev/ttyACM1 with DFU package /home/kevinh/development/meshtastic/WioWM1110/wio_tracker_1110_bootloader-0.9.1_s140_7.3.0.zip. Flow control is disabled, Single bank, Touch 1200 +Touched serial port /dev/ttyACM1 +Opened serial port /dev/ttyACM1 +Starting DFU upgrade of type 3, SoftDevice size: 152728, bootloader size: 39000, application size: 0 +Sending DFU start packet +Sending DFU init packet +Sending firmware file +######################################## +######################################## +######################################## +######################################## +######################################## +######################################## +######################################## +######################################## +######################################## +############### +Activating new firmware + +DFU upgrade took 20.242434978485107s +Device programmed. + diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 6a67b0083..8f1006eca 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -17,9 +17,6 @@ lib_deps = https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 -debug_tool = jlink - - ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds @@ -52,69 +49,4 @@ lib_deps = upload_protocol = stlink ; eventually use platformio/tool-pyocd@^2.3600.0 instad ;upload_protocol = custom -;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE - -; We want the initial breakpoint at setup() instead of main(). Also we want to enable semihosting at that point so instead of -; debug_init_break = tbreak setup -; we just turn off the platformio tbreak and do it in .gdbinit (where we have more flexibility for scripting) -; also we use a permanent breakpoint so it gets reused each time we restart the debugging session? -debug_init_break = tbreak setup - -; Note: add "monitor arm semihosting_redirect tcp 4444 all" if you want the stdout from the device to go to that port number instead -; (for use by meshtastic command line) -; monitor arm semihosting disable -; monitor debug_level 3 -; -; IMPORTANT: fileio must be disabled before using port 5555 - openocd ver 0.12 has a bug where if enabled it never properly parses the special :tt name -; for stdio access. -; monitor arm semihosting_redirect tcp 5555 stdio - -; Also note: it is _impossible_ to do non blocking reads on the semihost console port (an oversight when ARM specified the semihost API). -; So we'll neve be able to general purpose bi-directional communication with the device over semihosting. -debug_extra_cmds = - echo Running .gdbinit script - monitor arm semihosting enable - monitor arm semihosting_fileio enable - monitor arm semihosting_redirect disable - commands 1 - echo Breakpoint at setup() has semihosting console, connect to it with "telnet localhost 5555" - set wantSemihost = true - set useSoftDevice = false - end - - -; Only reprogram the board if the code has changed -debug_load_mode = modified -;debug_load_mode = manual -debug_tool = stlink -;debug_tool = custom -; debug_server = -; openocd -; -f -; /usr/local/share/openocd/scripts/interface/stlink.cfg -; -f -; /usr/local/share/openocd/scripts/target/nrf52.cfg -; $PLATFORMIO_CORE_DIR/packages/tool-openocd/openocd/scripts/interface/cmsis-dap.cfg - -; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) -; programming time is about the same as the bootloader version. -; For information on this see the meshtastic developers documentation for "Development on the NRF52" -; We manually pass in the elf file so that pyocd can reverse engineer FreeRTOS data (running threads, etc...) -;debug_server = -; pyocd -; gdbserver -; -j -; ${platformio.workspace_dir}/.. -; -t -; nrf52840 -; --semihosting -; --elf -; ${platformio.build_dir}/${this.__env__}/firmware.elf - -; If you want to debug the semihosting support you can turn on extra logging in pyocd with -; -L -; pyocd.debug.semihost.trace=debug - -; The following is not needed because it automatically tries do this -;debug_server_ready_pattern = -.*GDB server started on port \d+.* -;debug_port = localhost:3333 \ No newline at end of file +;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE \ No newline at end of file diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini index b58b6291f..614fea588 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/wio-tracker-wm1110/platformio.ini @@ -9,6 +9,5 @@ board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} -debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink +;upload_protocol = jlink \ No newline at end of file From 5453c495e2732442c5e34a9c9a0809971aaa338c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 3 Aug 2024 13:12:22 -0500 Subject: [PATCH 0784/1377] Actually set the rand() seed for Portduino (#4380) --- src/platform/portduino/PortduinoGlue.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index dc7a9ed61..f8dd79b20 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "PortduinoGlue.h" #include "linux/gpio/LinuxGPIOPin.h" @@ -134,6 +135,9 @@ void portduinoSetup() return; } + // Rather important to set this, if not running simulated. + randomSeed(time(NULL)); + try { if (yamlConfig["Logging"]) { if (yamlConfig["Logging"]["LogLevel"].as("info") == "trace") { @@ -382,4 +386,4 @@ int initGPIOPin(int pinNum, const std::string gpioChipName) #else return ERRNO_OK; #endif -} \ No newline at end of file +} From 5bbafdfd311f63d560d82a73bc1ff32b688bf37c Mon Sep 17 00:00:00 2001 From: Ken Piper Date: Sun, 4 Aug 2024 06:06:36 -0500 Subject: [PATCH 0785/1377] Configure pin modes of selected pins before attempting to write to them (#4385) --- src/modules/RemoteHardwareModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index d999e75cc..6910005a8 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -60,13 +60,13 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r // Print notification to LCD screen screen->print("Write GPIOs\n"); + pinModes(p.gpio_mask, OUTPUT); for (uint8_t i = 0; i < NUM_GPIOS; i++) { uint64_t mask = 1ULL << i; if (p.gpio_mask & mask) { digitalWrite(i, (p.gpio_value & mask) ? 1 : 0); } } - pinModes(p.gpio_mask, OUTPUT); break; } From 7d00e1cef9408eb0470b589b84554c611a583f8e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 4 Aug 2024 18:52:10 -0500 Subject: [PATCH 0786/1377] Output more useful log message when the NodeDB is full (#4389) --- src/mesh/NodeDB.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1f66857f8..de63af08e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1010,7 +1010,8 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { if (screen) screen->print("Warn: node database full!\nErasing oldest entry\n"); - LOG_WARN("Node database full! Erasing oldest entry\n"); + LOG_WARN("Node database full with %i nodes and %i bytes free! Erasing oldest entry\n", numMeshNodes, + memGet.getFreeHeap()); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; From 40d6b99911e85eeebe3f64e43b1e81294688b521 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Mon, 5 Aug 2024 12:59:57 +0200 Subject: [PATCH 0787/1377] Add Minewsemi LR1110+nRF52840-ME25LS01 [both 4.2inch e-ink and non e-ink varaint] (#4387) * Add files via upload * Update EInkDisplay2.cpp * Add files via upload * Update platformio.ini * Update platformio.ini * Update platformio.ini * Update platformio.ini --- boards/me25ls01-4y10td.json | 58 +++++++ src/graphics/EInkDisplay2.cpp | 8 +- variants/ME25LS01-4Y10TD/platformio.ini | 15 ++ variants/ME25LS01-4Y10TD/variant.cpp | 40 +++++ variants/ME25LS01-4Y10TD/variant.h | 139 +++++++++++++++ variants/ME25LS01-4Y10TD_e-ink/platformio.ini | 19 ++ variants/ME25LS01-4Y10TD_e-ink/variant.cpp | 40 +++++ variants/ME25LS01-4Y10TD_e-ink/variant.h | 162 ++++++++++++++++++ 8 files changed, 477 insertions(+), 4 deletions(-) create mode 100644 boards/me25ls01-4y10td.json create mode 100644 variants/ME25LS01-4Y10TD/platformio.ini create mode 100644 variants/ME25LS01-4Y10TD/variant.cpp create mode 100644 variants/ME25LS01-4Y10TD/variant.h create mode 100644 variants/ME25LS01-4Y10TD_e-ink/platformio.ini create mode 100644 variants/ME25LS01-4Y10TD_e-ink/variant.cpp create mode 100644 variants/ME25LS01-4Y10TD_e-ink/variant.h diff --git a/boards/me25ls01-4y10td.json b/boards/me25ls01-4y10td.json new file mode 100644 index 000000000..46c526a7c --- /dev/null +++ b/boards/me25ls01-4y10td.json @@ -0,0 +1,58 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v7.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "ME25LS01-BOOT", + "mcu": "nrf52840", + "variant": "MINEWSEMI_ME25LS01", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "7.3.0", + "sd_fwid": "0x0123" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Minesemi ME25LS01", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://en.minewsemi.com/lora-module/lr1110-nrf52840-me25LS01l", + "vendor": "Minesemi" +} diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index d81ab6ff4..4b845bd51 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -174,13 +174,13 @@ bool EInkDisplay::connect() adafruitDisplay->init(); adafruitDisplay->setRotation(3); } -#elif defined(PCA10059) +#elif defined(PCA10059) || defined(ME25LS01) { auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); adafruitDisplay = new GxEPD2_BW(*lowLevel); - adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); - adafruitDisplay->setRotation(3); - adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); + adafruitDisplay->init(115200, true, 40, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); + adafruitDisplay->setRotation(0); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } #elif defined(M5_COREINK) auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); diff --git a/variants/ME25LS01-4Y10TD/platformio.ini b/variants/ME25LS01-4Y10TD/platformio.ini new file mode 100644 index 000000000..985919d2d --- /dev/null +++ b/variants/ME25LS01-4Y10TD/platformio.ini @@ -0,0 +1,15 @@ +[env:ME25LS01-4Y10TD] +extends = nrf52840_base +board = me25ls01-4y10td +board_level = extra +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD ;-DRADIOLIB_GODMODE + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ME25LS01-4Y10TD> +lib_deps = + ${nrf52840_base.lib_deps} +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = nrfutil +upload_port = /dev/ttyACM1 diff --git a/variants/ME25LS01-4Y10TD/variant.cpp b/variants/ME25LS01-4Y10TD/variant.cpp new file mode 100644 index 000000000..35dc1d39b --- /dev/null +++ b/variants/ME25LS01-4Y10TD/variant.cpp @@ -0,0 +1,40 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} \ No newline at end of file diff --git a/variants/ME25LS01-4Y10TD/variant.h b/variants/ME25LS01-4Y10TD/variant.h new file mode 100644 index 000000000..24ed65b33 --- /dev/null +++ b/variants/ME25LS01-4Y10TD/variant.h @@ -0,0 +1,139 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_ME25LS01_4Y10TD_ +#define _VARIANT_ME25LS01_4Y10TD_ + +#define ME25LS01 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// Use the native nrf52 usb power detection +#define NRF_APM + +#define PIN_3V3_EN (32 + 5) //-1 +#define PIN_3V3_ACC_EN -1 + +#define PIN_LED1 (32 + 7) // P1.07 Blue D2 + +#define LED_PIN PIN_LED1 +#define LED_BUILTIN -1 + +#define LED_BLUE -1 +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN (0 + 27) // P0.27 K3 +#define BUTTON_NEED_PULLUP + +#define HAS_WIRE 1 + +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (0 + 15) // P0.15 +#define PIN_WIRE_SCL (0 + 17) // P0.17 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 14) // P0.14 +#define PIN_SERIAL1_TX (0 + 13) // P0.13 + +#define PIN_SERIAL2_RX (0 + 17) // P0.17 +#define PIN_SERIAL2_TX (0 + 16) // P0.16 + +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 29) // P0.20 // MISO +#define PIN_SPI_MOSI (0 + 2) // P0.02 // MOSI +#define PIN_SPI_SCK (32 + 15) // P1.15 // SCK +#define PIN_SPI_NSS (32 + 13) // P1.13 // NSS + +#define LORA_RESET (32 + 11) // P1.11 // RST +#define LORA_DIO1 (32 + 12) // P1.12 // IRQ +#define LORA_DIO2 (32 + 10) // P1.10 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +// supported modules list +#define USE_LR1110 + +#define LR1110_IRQ_PIN LORA_DIO1 +#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO2 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.6 +#define LR11X0_DIO_AS_RF_SWITCH +#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 + +#define HAS_GPS 0 + +#define PIN_GPS_EN -1 +#define GPS_EN_ACTIVE HIGH +#define PIN_GPS_RESET -1 +#define GPS_VRTC_EN -1 +#define GPS_SLEEP_INT -1 +#define GPS_RTC_INT -1 +#define GPS_RESETB_OUT -1 + +#define BATTERY_PIN -1 +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 + +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 + +// Buzzer +#define BUZZER_EN_PIN -1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_ME25LS01_4Y10TD_ \ No newline at end of file diff --git a/variants/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/ME25LS01-4Y10TD_e-ink/platformio.ini new file mode 100644 index 000000000..4e837abd8 --- /dev/null +++ b/variants/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -0,0 +1,19 @@ +[env:ME25LS01-4Y10TD_e-ink] +extends = nrf52840_base +board = me25ls01-4y10td +board_level = extra +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD_e-ink -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD ;-DRADIOLIB_GODMODE + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81 + -DEINK_WIDTH=400 + -DEINK_HEIGHT=300 +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ME25LS01-4Y10TD_e-ink> +lib_deps = + ${nrf52840_base.lib_deps} + zinggjm/GxEPD2@^1.5.8 +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = nrfutil +upload_port = /dev/ttyACM1 diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.cpp b/variants/ME25LS01-4Y10TD_e-ink/variant.cpp new file mode 100644 index 000000000..35dc1d39b --- /dev/null +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.cpp @@ -0,0 +1,40 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} \ No newline at end of file diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/ME25LS01-4Y10TD_e-ink/variant.h new file mode 100644 index 000000000..7c7740505 --- /dev/null +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.h @@ -0,0 +1,162 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_ME25LS01_4Y10TD_ +#define _VARIANT_ME25LS01_4Y10TD_ + +#define ME25LS01 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// Use the native nrf52 usb power detection +#define NRF_APM + +#define PIN_3V3_EN (32 + 5) //-1 +#define PIN_3V3_ACC_EN -1 + +#define PIN_LED1 (32 + 7) // P1.07 Blue D2 + +#define LED_PIN PIN_LED1 +#define LED_BUILTIN -1 + +#define LED_BLUE -1 +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN (0 + 27) // P0.27 K3 +#define BUTTON_NEED_PULLUP + +#define HAS_WIRE 1 + +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (0 + 15) // P0.15 +#define PIN_WIRE_SCL (0 + 17) // P0.17 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 14) // P0.14 +#define PIN_SERIAL1_TX (0 + 13) // P0.13 + +#define PIN_SERIAL2_RX (0 + 17) // P0.17 +#define PIN_SERIAL2_TX (0 + 16) // P0.16 + +#define SPI_INTERFACES_COUNT 2 + +// LoRa SPI +#define PIN_SPI_MISO (0 + 29) // P0.20 // MISO +#define PIN_SPI_MOSI (0 + 2) // P0.02 // MOSI +#define PIN_SPI_SCK (32 + 15) // P1.15 // SCK +#define PIN_SPI_NSS (32 + 13) // P1.13 // NSS + +#define LORA_RESET (32 + 11) // P1.11 // RST +#define LORA_DIO1 (32 + 12) // P1.12 // IRQ +#define LORA_DIO2 (32 + 10) // P1.10 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +static const uint8_t SS = (32 + 13); // P1.13 // NSS +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +// EPD SPI +#define PIN_SPI1_MISO (-1) // Not Used for EPD +#define PIN_SPI1_MOSI (0 + 10) // EPD_MOSI P0.10 +#define PIN_SPI1_SCK (0 + 9) // EPD_SCLK P0.09 + +/* + * eink display pins + */ + +#define USE_EINK +#define PIN_EINK_CS (32 + 0) // EPD_CS +#define PIN_EINK_BUSY (0 + 19) // EPD_BUSY +#define PIN_EINK_DC (0 + 24) // EPD_D/C +#define PIN_EINK_RES (0 + 23) // EPD_RESET +#define PIN_EINK_SCLK (0 + 9) // EPD_SCLK +#define PIN_EINK_MOSI (0 + 10) // EPD_MOSI + +// supported modules list +#define USE_LR1110 + +#define LR1110_IRQ_PIN LORA_DIO1 +#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO2 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.6 +#define LR11X0_DIO_AS_RF_SWITCH +#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 + +#define HAS_GPS 0 + +#define PIN_GPS_EN -1 +#define GPS_EN_ACTIVE HIGH +#define PIN_GPS_RESET -1 +#define GPS_VRTC_EN -1 +#define GPS_SLEEP_INT -1 +#define GPS_RTC_INT -1 +#define GPS_RESETB_OUT -1 + +#define BATTERY_PIN -1 +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 + +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 + +// Buzzer +#define BUZZER_EN_PIN -1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_ME25LS01_4Y10TD__ \ No newline at end of file From 1a38c4e51d2d0d8799130c199f94c667f7132d94 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 5 Aug 2024 04:02:32 -0700 Subject: [PATCH 0788/1377] Remove LED_INVERTED, see below for why ;-) (#4382) While working on #4378 I noticed a funny problem: the blinking system LED was on during deep-sleep. Initially I thought it was some weird sleep hw config thing but it turns out it was easier but more pervasive. We had two different preprocessor symbols which both meant approximately the same thing LED_INVERTED and LED_STATE_ON (though their polarity was opposite). Some variant files were setting one, others were setting the other, and others were setting both. heh. In the case of the board I was testing (seeed tracker wio 1100) it was only setting one and the default behavior for the other (for all boards) was incorrect. So I did a grep and it seems like LED_STATE_ON was used more often, so I kept that one and removed LED_INVERTED everywhere. --- src/DebugConfiguration.h | 4 ++-- src/main.cpp | 4 ++-- src/modules/AdminModule.cpp | 2 +- src/platform/nrf52/architecture.h | 10 +++++----- src/sleep.cpp | 4 ++-- variants/EBYTE_ESP32-S3/variant.h | 2 +- variants/canaryone/variant.h | 1 - variants/heltec_esp32c3/variant.h | 6 +++--- variants/heltec_mesh_node_t114/variant.h | 1 - variants/m5stack_coreink/variant.h | 4 ++-- variants/nano-g2-ultra/variant.h | 1 - variants/t-echo/variant.h | 1 - variants/tbeam-s3-core/variant.h | 4 ++-- variants/tbeam/variant.h | 4 ++-- variants/unphone/variant.h | 4 ++-- 15 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index ebe9da8d4..f5b3fa25a 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -3,8 +3,8 @@ #include "configuration.h" // DEBUG LED -#ifndef LED_INVERTED -#define LED_INVERTED 0 // define as 1 if LED is active low (on) +#ifndef LED_STATE_ON +#define LED_STATE_ON 1 #endif // ----------------------------------------------------------------------------- diff --git a/src/main.cpp b/src/main.cpp index 561b24a34..64fe04f00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -583,7 +583,7 @@ void setup() #ifdef LED_PIN pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now + digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now #endif // Hello @@ -728,7 +728,7 @@ void setup() #ifdef LED_PIN // Turn LED off after boot, if heartbeat by config if (config.device.led_heartbeat_disabled) - digitalWrite(LED_PIN, LOW ^ LED_INVERTED); + digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); #endif // Do this after service.init (because that clears error_code) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 26ddab0db..8287f8a00 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -372,7 +372,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #ifdef LED_PIN // Turn LED off if heartbeat by config if (c.payload_variant.device.led_heartbeat_disabled) { - digitalWrite(LED_PIN, LOW ^ LED_INVERTED); + digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); } #endif if (config.device.button_gpio == c.payload_variant.device.button_gpio && diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 2b285ec2e..d5685d611 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -75,16 +75,16 @@ #ifdef ARDUINO_NRF52840_PCA10056 // This board uses 0 to be mean LED on -#undef LED_INVERTED -#define LED_INVERTED 1 +#undef LED_STATE_ON +#define LED_STATE_ON 0 // State when LED is lit #endif #ifdef _SEEED_XIAO_NRF52840_SENSE_H_ // This board uses 0 to be mean LED on -#undef LED_INVERTED -#define LED_INVERTED 1 +#undef LED_STATE_ON +#define LED_STATE_ON 0 // State when LED is lit #endif @@ -116,4 +116,4 @@ #if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA) // No serial ports on this board - ONLY use segger in memory console #define USE_SEGGER -#endif +#endif \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index 4e685563a..486d22dfe 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -90,7 +90,7 @@ void setLed(bool ledOn) #ifdef LED_PIN // toggle the led so we can get some rough sense of how often loop is pausing - digitalWrite(LED_PIN, ledOn ^ LED_INVERTED); + digitalWrite(LED_PIN, ledOn ^ LED_STATE_ON ^ HIGH); #endif #ifdef HAS_PMU @@ -519,4 +519,4 @@ void enableLoraInterrupt() } #endif } -#endif +#endif \ No newline at end of file diff --git a/variants/EBYTE_ESP32-S3/variant.h b/variants/EBYTE_ESP32-S3/variant.h index 10b39617b..80fb26434 100644 --- a/variants/EBYTE_ESP32-S3/variant.h +++ b/variants/EBYTE_ESP32-S3/variant.h @@ -101,7 +101,7 @@ // Status #define LED_PIN 1 -#define LED_INVERTED 0 +#define LED_STATE_ON 1 // State when LED is lit // External notification // FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the // app/preferences diff --git a/variants/canaryone/variant.h b/variants/canaryone/variant.h index d283f08f6..140b605ad 100644 --- a/variants/canaryone/variant.h +++ b/variants/canaryone/variant.h @@ -56,7 +56,6 @@ extern "C" { #define LED_CONN PIN_LED3 #define LED_STATE_ON 0 // State when LED is lit -#define LED_INVERTED 1 /* * Buttons diff --git a/variants/heltec_esp32c3/variant.h b/variants/heltec_esp32c3/variant.h index 360d9bf1f..ca00c43fa 100644 --- a/variants/heltec_esp32c3/variant.h +++ b/variants/heltec_esp32c3/variant.h @@ -3,8 +3,8 @@ // LED pin on HT-DEV-ESP_V2 and HT-DEV-ESP_V3 // https://resource.heltec.cn/download/HT-CT62/HT-CT62_Reference_Design.pdf // https://resource.heltec.cn/download/HT-DEV-ESP/HT-DEV-ESP_V3_Sch.pdf -#define LED_PIN 2 // LED -#define LED_INVERTED 0 +#define LED_PIN 2 // LED +#define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 #define HAS_GPS 0 @@ -26,4 +26,4 @@ #define SX126X_BUSY LORA_BUSY #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index 615526b22..8df59e394 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -80,7 +80,6 @@ extern "C" { #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit -#define LED_INVERTED 1 /* * Buttons diff --git a/variants/m5stack_coreink/variant.h b/variants/m5stack_coreink/variant.h index f19da2696..ecd93b7be 100644 --- a/variants/m5stack_coreink/variant.h +++ b/variants/m5stack_coreink/variant.h @@ -10,7 +10,7 @@ // #define GPS_TX_PIN 32 (now used by SX1262 BUSY as GPS works with just RX) // Green LED -#define LED_INVERTED 0 +#define LED_STATE_ON 1 // State when LED is lit #define LED_PIN 10 #include "pcf8563.h" @@ -106,4 +106,4 @@ // GND // https://github.com/m5stack/M5Core-Ink/blob/master/examples/Basics/FactoryTest/FactoryTest.ino#L58 #define ADC_MULTIPLIER 5 -// https://embeddedexplorer.com/esp32-adc-esp-idf-tutorial/ +// https://embeddedexplorer.com/esp32-adc-esp-idf-tutorial/ \ No newline at end of file diff --git a/variants/nano-g2-ultra/variant.h b/variants/nano-g2-ultra/variant.h index 4d8aa5784..fd51cf9a1 100644 --- a/variants/nano-g2-ultra/variant.h +++ b/variants/nano-g2-ultra/variant.h @@ -54,7 +54,6 @@ extern "C" { #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit -// #define LED_INVERTED 1 /* * Buttons diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 1c263a61a..9abb4ea69 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -56,7 +56,6 @@ extern "C" { #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit -#define LED_INVERTED 1 /* * Buttons diff --git a/variants/tbeam-s3-core/variant.h b/variants/tbeam-s3-core/variant.h index 0fa9f8fe8..cc706459f 100644 --- a/variants/tbeam-s3-core/variant.h +++ b/variants/tbeam-s3-core/variant.h @@ -11,7 +11,7 @@ // anywhere. // #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. -#define LED_INVERTED 1 +#define LED_STATE_ON 0 // State when LED is lit // TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if // not found then probe for SX1262 @@ -66,4 +66,4 @@ // has 32768 Hz crystal #define HAS_32768HZ -#define USE_SH1106 +#define USE_SH1106 \ No newline at end of file diff --git a/variants/tbeam/variant.h b/variants/tbeam/variant.h index d237f542b..8771c20d2 100644 --- a/variants/tbeam/variant.h +++ b/variants/tbeam/variant.h @@ -8,8 +8,8 @@ // anywhere. #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. -#define LED_INVERTED 1 -#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4 +#define LED_STATE_ON 0 // State when LED is lit +#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4 // TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if // not found then probe for SX1262 diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h index 7d5c30f79..0a94c5987 100644 --- a/variants/unphone/variant.h +++ b/variants/unphone/variant.h @@ -51,8 +51,8 @@ // #define HAS_SDCARD 1 // causes hang if defined #define SDCARD_CS 43 -#define LED_PIN 13 // the red part of the RGB LED -#define LED_INVERTED 1 +#define LED_PIN 13 // the red part of the RGB LED +#define LED_STATE_ON 0 // State when LED is lit #define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode #define BUTTON_NEED_PULLUP // we do need a helping hand up From 9ddfc6de4c45918bd07a52885c631d93276b347d Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 5 Aug 2024 14:02:54 +0300 Subject: [PATCH 0789/1377] Commented RF95(1276) as no needed right now (#4386) --- variants/diy/nrf52_promicro_diy_tcxo/variant.h | 2 +- variants/diy/nrf52_promicro_diy_xtal/variant.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/diy/nrf52_promicro_diy_tcxo/variant.h index bacc0796d..b09d3bdb4 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.h @@ -115,7 +115,7 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LORA MODULES #define USE_LLCC68 #define USE_SX1262 -#define USE_RF95 +// #define USE_RF95 #define USE_SX1268 // LORA CONFIG diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.h b/variants/diy/nrf52_promicro_diy_xtal/variant.h index c00c424cc..7aafab7da 100644 --- a/variants/diy/nrf52_promicro_diy_xtal/variant.h +++ b/variants/diy/nrf52_promicro_diy_xtal/variant.h @@ -114,7 +114,7 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LORA MODULES #define USE_LLCC68 #define USE_SX1262 -#define USE_RF95 +// #define USE_RF95 #define USE_SX1268 // LORA CONFIG From d8f3c3324c95b370db3054b874e6550f7d0431f0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 5 Aug 2024 04:47:04 -0700 Subject: [PATCH 0790/1377] Make lora radio reset reliable on wio-tracker-1100 and lower lr11x0 power consumption in sleep (#4383) * Fix wio-tracker-1110 lora radio reset GPIO assignment This fixes flaky lora radio init on this board. * No need to keep lr11x0 radio config during sleep anymore, also stop TCXO I think the problem (at least on the board I'm using for power testing a wio tracker 1110) was that actually the RESET GPIO was not correct for the radio. This led to the radio not being properly reinited after exiting sleep mode. Now that the GPIO is fixed I can enter deep sleep (fully shutting down radio) and then later when the CPU resets, it can successfully init the radio and send packets. After this seeming success, I also turned off the TCXO during sleep and that worked as well. --- src/mesh/LR11x0Interface.cpp | 8 +++----- variants/wio-tracker-wm1110/variant.h | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 1965eef89..13b32533e 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -285,17 +285,15 @@ template bool LR11x0Interface::isActivelyReceiving() template bool LR11x0Interface::sleep() { - // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet // \todo Display actual typename of the adapter, not just `LR11x0` - LOG_DEBUG("LR11x0 entering sleep mode (FIXME, don't keep config)\n"); + LOG_DEBUG("LR11x0 entering sleep mode\n"); setStandby(); // Stop any pending operations // turn off TCXO if it was powered - // FIXME - this isn't correct - // lora.setTCXO(0); + lora.setTCXO(0); // put chipset into sleep mode (we've already disabled interrupts by now) - bool keepConfig = true; + bool keepConfig = false; lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed #ifdef LR11X0_POWER_EN diff --git a/variants/wio-tracker-wm1110/variant.h b/variants/wio-tracker-wm1110/variant.h index 2bb2f1a59..de7da9911 100644 --- a/variants/wio-tracker-wm1110/variant.h +++ b/variants/wio-tracker-wm1110/variant.h @@ -79,9 +79,9 @@ extern "C" { #define PIN_SPI_SCK (32 + 13) // P1.13 45 #define PIN_SPI_NSS (32 + 12) // P1.12 44 -#define LORA_RESET (0 + 18) // P0.18 18 // RST -#define LORA_DIO1 (0 + 2) // P0.02 2 // IRQ -#define LORA_DIO2 (32 + 11) // P1.11 43 // BUSY +#define LORA_RESET (32 + 10) // P1.10 10 // RST +#define LORA_DIO1 (0 + 2) // P0.02 2 // IRQ +#define LORA_DIO2 (32 + 11) // P1.11 43 // BUSY #define LORA_SCK PIN_SPI_SCK #define LORA_MISO PIN_SPI_MISO #define LORA_MOSI PIN_SPI_MOSI From e509a9101996d737f8db0cccf2f6aaa7d1b168a9 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 5 Aug 2024 23:00:32 +0800 Subject: [PATCH 0791/1377] Fix UC6580 ifdefs (#4393) meshtastic/firmware#4319 added autodetect code for UC6580, and removed these ifdefs. However, they were inadvertantly re-added by #4328 . --- src/gps/GPS.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index d6ea2cb08..0d937ae63 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -400,8 +400,6 @@ bool GPS::setup() int msglen = 0; if (!didSerialInit) { -#if !defined(GPS_UC6580) - if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate. @@ -424,9 +422,6 @@ bool GPS::setup() } else { gnssModel = GNSS_MODEL_UNKNOWN; } -#else - gnssModel = GNSS_MODEL_UC6580; -#endif if (gnssModel == GNSS_MODEL_MTK) { /* @@ -1797,4 +1792,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS From 66a4632f34028208921c48408cc089faebdf7671 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 5 Aug 2024 23:00:52 +0800 Subject: [PATCH 0792/1377] Fix python call in UF2 generation. (#4392) The call to generate UF2 files in the platformio custom script was a bare call to python. In some environments, this command won't exist in this way. Instead, use the standard env approach to find the right python. Additionally, add the shebang line on line 1 so this script can be executed standalone if needed. --- bin/platformio-custom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 4f7ce55d0..b22d76098 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # trunk-ignore-all(ruff/F821) # trunk-ignore-all(flake8/F821): For SConstruct imports import sys @@ -80,7 +81,7 @@ if platform.name == "espressif32": if platform.name == "nordicnrf52": env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", - env.VerboseAction(f"python ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", + env.VerboseAction(f"/usr/bin/env python3 ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", "Generating UF2 file")) Import("projenv") @@ -95,4 +96,4 @@ projenv.Append( "-DAPP_VERSION=" + verObj["long"], "-DAPP_VERSION_SHORT=" + verObj["short"], ] -) \ No newline at end of file +) From 02231fd487c588683629cb0ac158550f8c4149b8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 5 Aug 2024 15:07:43 -0700 Subject: [PATCH 0793/1377] fix minor type comparison warning that I saw in the build (#4398) --- src/mesh/compression/unishox2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/compression/unishox2.cpp b/src/mesh/compression/unishox2.cpp index fcb12a222..4e239d489 100644 --- a/src/mesh/compression/unishox2.cpp +++ b/src/mesh/compression/unishox2.cpp @@ -1096,7 +1096,7 @@ int decodeRepeat(const char *in, int len, char *out, int olen, int ol, int *bit_ return -1; if (left <= 0) return olen + 1; - if (dist >= strlen(cur_line->data)) + if ((size_t)dist >= strlen(cur_line->data)) return -1; memmove(out + ol, cur_line->data + dist, min_of(left, dict_len)); if (left < dict_len) @@ -1289,7 +1289,7 @@ int unishox2_decompress_lines(const char *in, int len, UNISHOX_API_OUT_AND_LEN(c if (usx_templates[idx] == NULL) break; size_t tlen = strlen(usx_templates[idx]); - if (rem > tlen) + if ((size_t)rem > tlen) break; rem = tlen - rem; int eof = 0; From 1f458d6397f259b8c305d6254a31e162da96761b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 6 Aug 2024 08:25:47 +0800 Subject: [PATCH 0794/1377] Make UF2 build command windows-friendly (#4399) As reported by @mrekin, the previous changes to the platformio custom build script may not work on windows. Change to use python3 instead of a call to /usr/bin/env python3. --- bin/platformio-custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index b22d76098..bfcee897a 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -81,7 +81,7 @@ if platform.name == "espressif32": if platform.name == "nordicnrf52": env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", - env.VerboseAction(f"/usr/bin/env python3 ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", + env.VerboseAction(f"python3 ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", "Generating UF2 file")) Import("projenv") From 4a79a690dbb6414333d6947af0b8d82ac5a854d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:14:07 -0500 Subject: [PATCH 0795/1377] [create-pull-request] automated change (#4400) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 976748839..d0fe91ab9 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 976748839fafcf0049bb364fe2c7226a194d18a9 +Subproject commit d0fe91ab99734cacdc188403f73fe30f766917cf diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index ca860aed5..59664b792 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -228,7 +228,14 @@ typedef enum _meshtastic_CriticalErrorCode { meshtastic_CriticalErrorCode_SX1262_FAILURE = 10, /* A (likely software but possibly hardware) failure was detected while trying to send packets. If this occurs on your board, please post in the forum so that we can ask you to collect some information to allow fixing this bug */ - meshtastic_CriticalErrorCode_RADIO_SPI_BUG = 11 + meshtastic_CriticalErrorCode_RADIO_SPI_BUG = 11, + /* Corruption was detected on the flash filesystem but we were able to repair things. + If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field. */ + meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE = 12, + /* Corruption was detected on the flash filesystem but we were unable to repair things. + NOTE: Your node will probably need to be reconfigured the next time it reboots (it will lose the region code etc...) + If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field. */ + meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE = 13 } meshtastic_CriticalErrorCode; /* How the location was acquired: manual, onboard GPS, external (EUD) GPS */ @@ -931,8 +938,8 @@ extern "C" { #define _meshtastic_Constants_ARRAYSIZE ((meshtastic_Constants)(meshtastic_Constants_DATA_PAYLOAD_LEN+1)) #define _meshtastic_CriticalErrorCode_MIN meshtastic_CriticalErrorCode_NONE -#define _meshtastic_CriticalErrorCode_MAX meshtastic_CriticalErrorCode_RADIO_SPI_BUG -#define _meshtastic_CriticalErrorCode_ARRAYSIZE ((meshtastic_CriticalErrorCode)(meshtastic_CriticalErrorCode_RADIO_SPI_BUG+1)) +#define _meshtastic_CriticalErrorCode_MAX meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE +#define _meshtastic_CriticalErrorCode_ARRAYSIZE ((meshtastic_CriticalErrorCode)(meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE+1)) #define _meshtastic_Position_LocSource_MIN meshtastic_Position_LocSource_LOC_UNSET #define _meshtastic_Position_LocSource_MAX meshtastic_Position_LocSource_LOC_EXTERNAL From 06eaf2ba5d78a14e9a101db578999e6f01f76278 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 6 Aug 2024 19:48:05 +0800 Subject: [PATCH 0796/1377] Use sys.executable to refer to python. (#4402) Thanks to @mrekin for testing the build on Windows. The previous fix for this UF2 call did not work. sys.executable should fix it. --- bin/platformio-custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index bfcee897a..0f099c09a 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -81,7 +81,7 @@ if platform.name == "espressif32": if platform.name == "nordicnrf52": env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", - env.VerboseAction(f"python3 ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", + env.VerboseAction(f"{sys.executable} ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", "Generating UF2 file")) Import("projenv") From c1870f91fcdb718a79ea5c3e46fdc76d68503c86 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 6 Aug 2024 10:35:54 -0700 Subject: [PATCH 0797/1377] Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors --- src/GpioLogic.cpp | 84 +++++++++++++++ src/GpioLogic.h | 144 ++++++++++++++++++++++++++ src/Led.cpp | 66 ++++++++++++ src/Led.h | 7 ++ src/Power.cpp | 3 - src/PowerFSM.cpp | 21 +--- src/PowerMon.cpp | 6 +- src/PowerMon.h | 10 ++ src/graphics/Screen.cpp | 3 + src/main.cpp | 3 +- src/mesh/http/ContentHandler.cpp | 6 +- src/modules/PowerStressModule.cpp | 80 +++++++++++--- src/platform/esp32/main-esp32.cpp | 2 + src/platform/nrf52/main-nrf52.cpp | 7 +- src/power.h | 7 ++ src/sleep.cpp | 25 +---- src/sleep.h | 1 - variants/tbeam-s3-core/pins_arduino.h | 16 +-- 18 files changed, 422 insertions(+), 69 deletions(-) create mode 100644 src/GpioLogic.cpp create mode 100644 src/GpioLogic.h create mode 100644 src/Led.cpp create mode 100644 src/Led.h diff --git a/src/GpioLogic.cpp b/src/GpioLogic.cpp new file mode 100644 index 000000000..d164615a7 --- /dev/null +++ b/src/GpioLogic.cpp @@ -0,0 +1,84 @@ +#include "GpioLogic.h" +#include + +void GpioVirtPin::set(bool value) +{ + if (value != this->value) { + this->value = value ? PinState::On : PinState::Off; + if (dependentPin) + dependentPin->update(); + } +} + +GpioTransformer::GpioTransformer(GpioPin *outPin) : outPin(outPin) {} + +void GpioTransformer::set(bool value) +{ + outPin->set(value); +} + +GpioNotTransformer::GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin) : GpioTransformer(outPin), inPin(inPin) +{ + assert(!inPin->dependentPin); // We only allow one dependent pin + inPin->dependentPin = this; + + // Don't update at construction time, because various GpioPins might be global constructor based not yet initied because + // order of operations for global constructors is not defined. + // update(); +} + +/** + * Update the output pin based on the current state of the input pin. + */ +void GpioNotTransformer::update() +{ + auto p = inPin->get(); + if (p == GpioVirtPin::PinState::Unset) + return; // Not yet fully initialized + + set(!p); +} + +GpioBinaryTransformer::GpioBinaryTransformer(GpioVirtPin *inPin1, GpioVirtPin *inPin2, GpioPin *outPin, Operation operation) + : GpioTransformer(outPin), inPin1(inPin1), inPin2(inPin2), operation(operation) +{ + assert(!inPin1->dependentPin); // We only allow one dependent pin + inPin1->dependentPin = this; + assert(!inPin2->dependentPin); // We only allow one dependent pin + inPin2->dependentPin = this; + + // Don't update at construction time, because various GpioPins might be global constructor based not yet initied because + // order of operations for global constructors is not defined. + // update(); +} + +void GpioBinaryTransformer::update() +{ + auto p1 = inPin1->get(), p2 = inPin2->get(); + GpioVirtPin::PinState newValue = GpioVirtPin::PinState::Unset; + + if (p1 == GpioVirtPin::PinState::Unset) + newValue = p2; // Not yet fully initialized + else if (p2 == GpioVirtPin::PinState::Unset) + newValue = p1; // Not yet fully initialized + + // If we've already found our value just use it, otherwise need to do the operation + if (newValue == GpioVirtPin::PinState::Unset) { + switch (operation) { + case And: + newValue = (GpioVirtPin::PinState)(p1 && p2); + break; + case Or: + newValue = (GpioVirtPin::PinState)(p1 || p2); + break; + case Xor: + newValue = (GpioVirtPin::PinState)(p1 != p2); + break; + default: + assert(false); + } + } + set(newValue); +} + +GpioSplitter::GpioSplitter(GpioPin *outPin1, GpioPin *outPin2) : outPin1(outPin1), outPin2(outPin2) {} \ No newline at end of file diff --git a/src/GpioLogic.h b/src/GpioLogic.h new file mode 100644 index 000000000..27e85d55b --- /dev/null +++ b/src/GpioLogic.h @@ -0,0 +1,144 @@ +#pragma once + +#include "configuration.h" + +/**This is a set of classes to mediate access to GPIOs in a structured way. Most usage of GPIOs do not + require these classes! But if your hardware has a GPIO that is 'shared' between multiple devices (i.e. a shared power enable) + then using these classes might be able to let you cleanly turn on that enable when either dependent device is needed. + + Note: these classes are intended to be 99% inline for the common case so should have minimal impact on flash or RAM + requirements. +*/ + +/** + * A logical GPIO pin (not necessary raw hardware). + */ +class GpioPin +{ + public: + virtual void set(bool value) = 0; +}; + +/** + * A physical GPIO hw pin. + */ +class GpioHwPin : public GpioPin +{ + uint32_t num; + + public: + explicit GpioHwPin(uint32_t num) : num(num) {} + + void set(bool value) { digitalWrite(num, value); } +}; + +class GpioTransformer; +class GpioNotTransformer; +class GpioBinaryTransformer; + +/** + * A virtual GPIO pin. + */ +class GpioVirtPin : public GpioPin +{ + friend class GpioBinaryTransformer; + friend class GpioNotTransformer; + + public: + enum PinState { On = true, Off = false, Unset = 2 }; + + void set(bool value); + PinState get() const { return value; } + + private: + PinState value = PinState::Unset; + GpioTransformer *dependentPin = NULL; +}; + +#include + +/** + * A 'smart' trigger that can depend in a fake GPIO and if that GPIO changes, drive some other downstream GPIO to change. + * notably: the set method is not public (because it always is calculated by a subclass) + */ +class GpioTransformer +{ + public: + /** + * Update the output pin based on the current state of the input pin. + */ + virtual void update() = 0; + + protected: + GpioTransformer(GpioPin *outPin); + + void set(bool value); + + private: + GpioPin *outPin; +}; + +/** + * A transformer that performs a unary NOT operation from an input. + */ +class GpioNotTransformer : public GpioTransformer +{ + public: + GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin); + + protected: + friend class GpioVirtPin; + + /** + * Update the output pin based on the current state of the input pin. + */ + void update(); + + private: + GpioVirtPin *inPin; +}; + +/** + * A transformer that combines multiple virtual pins to drive an output pin + */ +class GpioBinaryTransformer : public GpioTransformer +{ + + public: + enum Operation { And, Or, Xor }; + + GpioBinaryTransformer(GpioVirtPin *inPin1, GpioVirtPin *inPin2, GpioPin *outPin, Operation operation); + + protected: + friend class GpioVirtPin; + + /** + * Update the output pin based on the current state of the input pins. + */ + void update(); + + private: + GpioVirtPin *inPin1; + GpioVirtPin *inPin2; + Operation operation; +}; + +/** + * Sometimes a single output GPIO single needs to drive multiple physical GPIOs. This class provides that. + */ +class GpioSplitter : public GpioPin +{ + + public: + GpioSplitter(GpioPin *outPin1, GpioPin *outPin2); + + void set(bool value) + { + outPin1->set(value); + outPin2->set(value); + } + + private: + GpioPin *outPin1; + GpioPin *outPin2; +}; \ No newline at end of file diff --git a/src/Led.cpp b/src/Led.cpp new file mode 100644 index 000000000..6406cd2f7 --- /dev/null +++ b/src/Led.cpp @@ -0,0 +1,66 @@ +#include "Led.h" +#include "PowerMon.h" +#include "main.h" +#include "power.h" + +GpioVirtPin ledForceOn, ledBlink; + +#if defined(LED_PIN) +// Most boards have a GPIO for LED control +static GpioHwPin ledRawHwPin(LED_PIN); +#else +static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware +#endif + +#if LED_STATE_ON == 0 +static GpioVirtPin ledHwPin; +static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin); +#else +static GpioPin &ledHwPin = ledRawHwPin; +#endif + +#if defined(HAS_PMU) +/** + * A GPIO controlled by the PMU + */ +class GpioPmuPin : public GpioPin +{ + public: + void set(bool value) + { + if (pmu_found && PMU) { + // blink the axp led + PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); + } + } +} ledPmuHwPin; + +// In some cases we need to drive a PMU LED and a normal LED +static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin); +#else +static GpioPin &ledFinalPin = ledHwPin; +#endif + +#ifdef USE_POWERMON +/** + * We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff. + */ +class MonitoredLedPin : public GpioPin +{ + public: + void set(bool value) + { + if (powerMon) { + if (value) + powerMon->setState(meshtastic_PowerMon_State_LED_On); + else + powerMon->clearState(meshtastic_PowerMon_State_LED_On); + } + ledFinalPin.set(value); + } +} monitoredLedPin; +#else +static GpioPin &monitoredLedPin = ledFinalPin; +#endif + +static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or); \ No newline at end of file diff --git a/src/Led.h b/src/Led.h new file mode 100644 index 000000000..68833e041 --- /dev/null +++ b/src/Led.h @@ -0,0 +1,7 @@ +#include "GpioLogic.h" +#include "configuration.h" + +/** + * ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main) + */ +extern GpioVirtPin ledForceOn, ledBlink; \ No newline at end of file diff --git a/src/Power.cpp b/src/Power.cpp index e8e406a94..d63c43137 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -80,9 +80,6 @@ RAK9154Sensor rak9154Sensor; #endif #ifdef HAS_PMU -#include "XPowersAXP192.tpp" -#include "XPowersAXP2101.tpp" -#include "XPowersLibInterface.hpp" XPowersLibInterface *PMU = NULL; #else diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 699a6bca6..0a954c1b8 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -9,6 +9,7 @@ */ #include "PowerFSM.h" #include "Default.h" +#include "Led.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" @@ -50,7 +51,6 @@ static bool isPowered() static void sdsEnter() { LOG_DEBUG("Enter state: SDS\n"); - powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false); } @@ -70,7 +70,6 @@ static uint32_t secsSlept; static void lsEnter() { LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs); - powerMon->clearState(meshtastic_PowerMon_State_Screen_On); screen->setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -91,7 +90,7 @@ static void lsIdle() uint32_t sleepTime = SLEEP_TIME; powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); - setLed(false); // Never leave led on while in light sleep + ledBlink.set(false); // Never leave led on while in light sleep esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); @@ -99,7 +98,7 @@ static void lsIdle() case ESP_SLEEP_WAKEUP_TIMER: // Normal case: timer expired, we should just go back to sleep ASAP - setLed(true); // briefly turn on led + ledBlink.set(true); // briefly turn on led wakeCause2 = doLightSleep(100); // leave led on for 1ms secsSlept += sleepTime; @@ -134,7 +133,7 @@ static void lsIdle() } } else { // Time to stop sleeping! - setLed(false); + ledBlink.set(false); LOG_INFO("Reached ls_secs, servicing loop()\n"); powerFSM.trigger(EVENT_WAKE_TIMER); } @@ -149,7 +148,6 @@ static void lsExit() static void nbEnter() { LOG_DEBUG("Enter state: NB\n"); - powerMon->clearState(meshtastic_PowerMon_State_BT_On); screen->setOn(false); #ifdef ARCH_ESP32 // Only ESP32 should turn off bluetooth @@ -161,8 +159,6 @@ static void nbEnter() static void darkEnter() { - powerMon->clearState(meshtastic_PowerMon_State_BT_On); - powerMon->clearState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(true); screen->setOn(false); } @@ -170,8 +166,6 @@ static void darkEnter() static void serialEnter() { LOG_DEBUG("Enter state: SERIAL\n"); - powerMon->clearState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); setBluetoothEnable(false); screen->setOn(true); screen->print("Serial connected\n"); @@ -180,7 +174,6 @@ static void serialEnter() static void serialExit() { // Turn bluetooth back on when we leave serial stream API - powerMon->setState(meshtastic_PowerMon_State_BT_On); setBluetoothEnable(true); screen->print("Serial disconnected\n"); } @@ -193,8 +186,6 @@ static void powerEnter() LOG_INFO("Loss of power in Powered\n"); powerFSM.trigger(EVENT_POWER_DISCONNECTED); } else { - powerMon->setState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); // within enter() the function getState() returns the state we came from @@ -218,8 +209,6 @@ static void powerIdle() static void powerExit() { - powerMon->setState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); @@ -231,8 +220,6 @@ static void powerExit() static void onEnter() { LOG_DEBUG("Enter state: ON\n"); - powerMon->setState(meshtastic_PowerMon_State_BT_On); - powerMon->setState(meshtastic_PowerMon_State_Screen_On); screen->setOn(true); setBluetoothEnable(true); } diff --git a/src/PowerMon.cpp b/src/PowerMon.cpp index 3d28715e0..16909262d 100644 --- a/src/PowerMon.cpp +++ b/src/PowerMon.cpp @@ -2,9 +2,11 @@ #include "NodeDB.h" // Use the 'live' config flag to figure out if we should be showing this message -static bool is_power_enabled(uint64_t m) +bool PowerMon::is_power_enabled(uint64_t m) { - return (m & config.power.powermon_enables) ? true : false; + // FIXME: VERY STRANGE BUG: if I or in "force_enabled || " the flashed image on a rak4631 is not accepted by the bootloader as + // valid!!! Possibly a linker/gcc/bootloader bug somewhere? + return ((m & config.power.powermon_enables) ? true : false); } void PowerMon::setState(_meshtastic_PowerMon_State state, const char *reason) diff --git a/src/PowerMon.h b/src/PowerMon.h index e9f5dbd59..a19a6d010 100644 --- a/src/PowerMon.h +++ b/src/PowerMon.h @@ -17,6 +17,13 @@ class PowerMon { uint64_t states = 0UL; + friend class PowerStressModule; + + /** + * If stress testing we always want all events logged + */ + bool force_enabled = false; + public: PowerMon() {} @@ -27,6 +34,9 @@ class PowerMon private: // Emit the coded log message void emitLog(const char *reason); + + // Use the 'live' config flag to figure out if we should be showing this message + bool is_power_enabled(uint64_t m); }; extern PowerMon *powerMon; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 633fb4c67..fe6fd3f06 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . */ #include "Screen.h" #include "../userPrefs.h" +#include "PowerMon.h" #include "configuration.h" #if HAS_SCREEN #include @@ -1574,6 +1575,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) if (on != screenOn) { if (on) { LOG_INFO("Turning on screen\n"); + powerMon->setState(meshtastic_PowerMon_State_Screen_On); #ifdef T_WATCH_S3 PMU->enablePowerOutput(XPOWERS_ALDO2); #endif @@ -1599,6 +1601,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) setInterval(0); // Draw ASAP runASAP = true; } else { + powerMon->clearState(meshtastic_PowerMon_State_Screen_On); #ifdef USE_EINK // eInkScreensaver parameter is usually NULL (default argument), default frame used instead setScreensaverFrames(einkScreensaver); diff --git a/src/main.cpp b/src/main.cpp index 64fe04f00..f4f7ad537 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,7 @@ #include "power.h" // #include "debug.h" #include "FSCommon.h" +#include "Led.h" #include "RTC.h" #include "SPILock.h" #include "concurrency/OSThread.h" @@ -197,7 +198,7 @@ static int32_t ledBlinker() static bool ledOn; ledOn ^= 1; - setLed(ledOn); + ledBlink.set(ledOn); // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000); diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index ca2c5d4be..9a981ee54 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -9,9 +9,9 @@ #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif +#include "Led.h" #include "power.h" #include "serialization/JSON.h" -#include "sleep.h" #include #include #include @@ -798,9 +798,9 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) if (blink_target == "LED") { uint8_t count = 10; while (count > 0) { - setLed(true); + ledBlink.set(true); delay(50); - setLed(false); + ledBlink.set(false); delay(50); count = count - 1; } diff --git a/src/modules/PowerStressModule.cpp b/src/modules/PowerStressModule.cpp index c86017ae2..4c9f0df88 100644 --- a/src/modules/PowerStressModule.cpp +++ b/src/modules/PowerStressModule.cpp @@ -1,10 +1,14 @@ #include "PowerStressModule.h" +#include "Led.h" #include "MeshService.h" #include "NodeDB.h" +#include "PowerMon.h" #include "RTC.h" #include "Router.h" #include "configuration.h" #include "main.h" +#include "sleep.h" +#include "target_specific.h" extern void printInfo(); @@ -29,6 +33,10 @@ bool PowerStressModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, case meshtastic_PowerStressMessage_Opcode_PRINT_INFO: printInfo(); + + // Now that we know we are actually doing power stress testing, go ahead and turn on all enables (so the log is fully + // detailed) + powerMon->force_enabled = true; break; default: @@ -44,7 +52,6 @@ bool PowerStressModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, int32_t PowerStressModule::runOnce() { - if (!config.power.powermon_enables) { // Powermon not enabled - stop using CPU/stop this thread return disable(); @@ -58,19 +65,68 @@ int32_t PowerStressModule::runOnce() // Done with the previous command - our sleep must have finished p.cmd = meshtastic_PowerStressMessage_Opcode_UNSET; p.num_seconds = 0; + isRunningCommand = false; + LOG_INFO("S:PS:%u\n", p.cmd); } else { - sleep_msec = (int32_t)(p.num_seconds * 1000); - isRunningCommand = !!sleep_msec; // if the command wants us to sleep, make sure to mark that we have something running + if (p.cmd != meshtastic_PowerStressMessage_Opcode_UNSET) { + sleep_msec = (int32_t)(p.num_seconds * 1000); + isRunningCommand = !!sleep_msec; // if the command wants us to sleep, make sure to mark that we have something running + LOG_INFO( + "S:PS:%u\n", + p.cmd); // Emit a structured log saying we are starting a powerstress state (to make it easier to parse later) - switch (p.cmd) { - case meshtastic_PowerStressMessage_Opcode_UNSET: // No need to start a new command - break; - case meshtastic_PowerStressMessage_Opcode_LED_ON: - break; - default: - LOG_ERROR("PowerStress operation %d not yet implemented!\n", p.cmd); - sleep_msec = 0; // Don't do whatever sleep was requested... - break; + switch (p.cmd) { + case meshtastic_PowerStressMessage_Opcode_LED_ON: + ledForceOn.set(true); + break; + case meshtastic_PowerStressMessage_Opcode_LED_OFF: + ledForceOn.set(false); + break; + case meshtastic_PowerStressMessage_Opcode_GPS_ON: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_GPS_OFF: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_LORA_OFF: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_LORA_RX: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_LORA_TX: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_SCREEN_OFF: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_SCREEN_ON: + // FIXME - implement + break; + case meshtastic_PowerStressMessage_Opcode_BT_OFF: + setBluetoothEnable(false); + break; + case meshtastic_PowerStressMessage_Opcode_BT_ON: + setBluetoothEnable(true); + break; + case meshtastic_PowerStressMessage_Opcode_CPU_DEEPSLEEP: + doDeepSleep(sleep_msec, true); + break; + case meshtastic_PowerStressMessage_Opcode_CPU_FULLON: { + uint32_t start_msec = millis(); + while ((millis() - start_msec) < (uint32_t)sleep_msec) + ; // Don't let CPU idle at all + sleep_msec = 0; // we already slept + break; + } + case meshtastic_PowerStressMessage_Opcode_CPU_IDLE: + // FIXME - implement + break; + default: + LOG_ERROR("PowerStress operation %d not yet implemented!\n", p.cmd); + sleep_msec = 0; // Don't do whatever sleep was requested... + break; + } } } return sleep_msec; diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 6bce498ab..19f435908 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -1,4 +1,5 @@ #include "PowerFSM.h" +#include "PowerMon.h" #include "configuration.h" #include "esp_task_wdt.h" #include "main.h" @@ -34,6 +35,7 @@ void setBluetoothEnable(bool enable) nimbleBluetooth = new NimbleBluetooth(); } if (enable && !nimbleBluetooth->isActive()) { + powerMon->setState(meshtastic_PowerMon_State_BT_On); nimbleBluetooth->setup(); } // For ESP32, no way to recover from bluetooth shutdown without reboot diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 7334f3a04..a7d5d8c3d 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -9,6 +9,7 @@ #include // #include #include "NodeDB.h" +#include "PowerMon.h" #include "error.h" #include "main.h" @@ -91,6 +92,8 @@ void setBluetoothEnable(bool enable) } if (enable) { + powerMon->setState(meshtastic_PowerMon_State_BT_On); + // If not yet set-up if (!nrf52Bluetooth) { LOG_DEBUG("Initializing NRF52 Bluetooth\n"); @@ -105,8 +108,10 @@ void setBluetoothEnable(bool enable) nrf52Bluetooth->resumeAdvertising(); } // Disable (if previously set-up) - else if (nrf52Bluetooth) + 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 diff --git a/src/power.h b/src/power.h index b970dfeaf..a4307ee07 100644 --- a/src/power.h +++ b/src/power.h @@ -53,6 +53,13 @@ extern INA3221Sensor ina3221Sensor; extern RAK9154Sensor rak9154Sensor; #endif +#ifdef HAS_PMU +#include "XPowersAXP192.tpp" +#include "XPowersAXP2101.tpp" +#include "XPowersLibInterface.hpp" +extern XPowersLibInterface *PMU; +#endif + class Power : private concurrency::OSThread { diff --git a/src/sleep.cpp b/src/sleep.cpp index 486d22dfe..b4171002a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -5,6 +5,7 @@ #endif #include "ButtonThread.h" +#include "Led.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -81,26 +82,6 @@ void setCPUFast(bool on) #endif } -void setLed(bool ledOn) -{ - if (ledOn) - powerMon->setState(meshtastic_PowerMon_State_LED_On); - else - powerMon->clearState(meshtastic_PowerMon_State_LED_On); - -#ifdef LED_PIN - // toggle the led so we can get some rough sense of how often loop is pausing - digitalWrite(LED_PIN, ledOn ^ LED_STATE_ON ^ HIGH); -#endif - -#ifdef HAS_PMU - if (pmu_found && PMU) { - // blink the axp led - PMU->setChargingLedMode(ledOn ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); - } -#endif -} - // Perform power on init that we do on each wake from deep sleep void initDeepSleep() { @@ -230,6 +211,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) notifyDeepSleep.notifyObservers(NULL); #endif + powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep); + screen->doDeepSleep(); // datasheet says this will draw only 10ua nodeDB->saveToDisk(); @@ -257,7 +240,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) digitalWrite(PIN_3V3_EN, LOW); #endif #endif - setLed(false); + ledBlink.set(false); #ifdef RESET_OLED digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power diff --git a/src/sleep.h b/src/sleep.h index f154b8d44..6ac420769 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -22,7 +22,6 @@ extern XPowersLibInterface *PMU; void initDeepSleep(); void setCPUFast(bool on); -void setLed(bool ledOn); /** return true if sleep is allowed right now */ bool doPreflightSleep(); diff --git a/variants/tbeam-s3-core/pins_arduino.h b/variants/tbeam-s3-core/pins_arduino.h index 24edb7d9f..e66b69e02 100644 --- a/variants/tbeam-s3-core/pins_arduino.h +++ b/variants/tbeam-s3-core/pins_arduino.h @@ -6,13 +6,13 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -#define EXTERNAL_NUM_INTERRUPTS 46 -#define NUM_DIGITAL_PINS 48 -#define NUM_ANALOG_INPUTS 20 - -#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) -#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) -#define digitalPinHasPWM(p) (p < 46) +// Now declared in .platformio/packages/framework-arduinoespressif32/cores/esp32/Arduino.h +// #define NUM_ANALOG_INPUTS 20 +// #define EXTERNAL_NUM_INTERRUPTS 46 +// #define NUM_DIGITAL_PINS 48 +// #define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +// #define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +// #define digitalPinHasPWM(p) (p < 46) static const uint8_t TX = 43; static const uint8_t RX = 44; @@ -39,4 +39,4 @@ static const uint8_t SCK = 12; // #define PMU_IRQ (40) #define RTC_INT (14) -#endif /* Pins_Arduino_h */ +#endif /* Pins_Arduino_h */ \ No newline at end of file From 66c41e683d7401d02c158f6ceaaba4e00e270bc1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 6 Aug 2024 11:59:06 -0700 Subject: [PATCH 0798/1377] bug #4184: fix config file loss due to filesystem write errors (#4397) * Use SafeFile for atomic file writing (with xor checksum readback) * Write db.proto last because it could be the largest file on the FS (and less critical) * Don't keep a tmp file around while writing db.proto (because too big to fit two files in the filesystem) * generate a new critial fault if we encounter errors writing to flash either CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE or CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE (depending on if the second write attempt worked) * reformat the filesystem if we detect it is corrupted (then rewrite our config files) (only on nrf52 - not sure yet if we should bother on ESP32) * If we have to format the FS, make sure to preserve the oem.proto if it exists Co-authored-by: Ben Meadors --- src/SafeFile.cpp | 99 +++++++++++ src/SafeFile.h | 49 ++++++ src/gps/GPS.cpp | 2 +- src/mesh/NodeDB.cpp | 165 +++++++++++------- src/mesh/NodeDB.h | 17 +- src/mesh/mesh-pb-constants.cpp | 4 +- .../Telemetry/Sensor/NAU7802Sensor.cpp | 25 ++- 7 files changed, 278 insertions(+), 83 deletions(-) create mode 100644 src/SafeFile.cpp create mode 100644 src/SafeFile.h diff --git a/src/SafeFile.cpp b/src/SafeFile.cpp new file mode 100644 index 000000000..161a80870 --- /dev/null +++ b/src/SafeFile.cpp @@ -0,0 +1,99 @@ +#include "SafeFile.h" + +#ifdef FSCom + +// Only way to work on both esp32 and nrf52 +static File openFile(const char *filename, bool fullAtomic) +{ + if (!fullAtomic) + FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists) + + String filenameTmp = filename; + filenameTmp += ".tmp"; + + return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE); +} + +SafeFile::SafeFile(const char *_filename, bool fullAtomic) + : filename(_filename), f(openFile(_filename, fullAtomic)), fullAtomic(fullAtomic) +{ +} + +size_t SafeFile::write(uint8_t ch) +{ + if (!f) + return 0; + + hash ^= ch; + return f.write(ch); +} + +size_t SafeFile::write(const uint8_t *buffer, size_t size) +{ + if (!f) + return 0; + + for (size_t i = 0; i < size; i++) { + hash ^= buffer[i]; + } + return f.write((uint8_t const *)buffer, size); // This nasty cast is _IMPORTANT_ otherwise the correct adafruit method does + // not get used (they made a mistake in their typing) +} + +/** + * Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches + * + * @return false for failure + */ +bool SafeFile::close() +{ + if (!f) + return false; + + f.close(); + if (!testReadback()) + return false; + + // brief window of risk here ;-) + if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) { + LOG_ERROR("Can't remove old pref file\n"); + return false; + } + + String filenameTmp = filename; + filenameTmp += ".tmp"; + if (!renameFile(filenameTmp.c_str(), filename.c_str())) { + LOG_ERROR("Error: can't rename new pref file\n"); + return false; + } + + return true; +} + +/// Read our (closed) tempfile back in and compare the hash +bool SafeFile::testReadback() +{ + String filenameTmp = filename; + filenameTmp += ".tmp"; + auto f2 = FSCom.open(filenameTmp.c_str(), FILE_O_READ); + if (!f2) { + LOG_ERROR("Can't open tmp file for readback\n"); + return false; + } + + int c = 0; + uint8_t test_hash = 0; + while ((c = f2.read()) >= 0) { + test_hash ^= (uint8_t)c; + } + f2.close(); + + if (test_hash != hash) { + LOG_ERROR("Readback failed hash mismatch\n"); + return false; + } + + return true; +} + +#endif \ No newline at end of file diff --git a/src/SafeFile.h b/src/SafeFile.h new file mode 100644 index 000000000..7088074cd --- /dev/null +++ b/src/SafeFile.h @@ -0,0 +1,49 @@ +#pragma once + +#include "FSCommon.h" +#include "configuration.h" + +#ifdef FSCom + +/** + * This class provides 'safe'/paranoid file writing. + * + * Some of our filesystems (in particular the nrf52) may have bugs beneath our layer. Therefore we want to + * be very careful about how we write files. This class provides a restricted (Stream only) writing API for writing to files. + * + * Notably: + * - we keep a simple xor hash of all characters that were written. + * - We do not allow seeking (because we want to maintain our hash) + * - we provide an close() method which is similar to close but returns false if we were unable to successfully write the + * file. Also this method + * - atomically replaces any old version of the file on the disk with our new file (after first rereading the file from the disk + * to confirm the hash matches) + * - Some files are super huge so we can't do the full atomic rename/copy (because of filesystem size limits). If !fullAtomic + * then we still do the readback to verify file is valid so higher level code can handle failures. + */ +class SafeFile : public Print +{ + public: + SafeFile(char const *filepath, bool fullAtomic = false); + + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *buffer, size_t size); + + /** + * Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches + * + * @return false for failure + */ + bool close(); + + private: + /// Read our (closed) tempfile back in and compare the hash + bool testReadback(); + + String filename; + File f; + bool fullAtomic; + uint8_t hash = 0; +}; + +#endif \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0d937ae63..93742a99a 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1090,7 +1090,7 @@ int32_t GPS::runOnce() if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); devicestate.did_gps_reset = false; - nodeDB->saveDeviceStateToDisk(); + nodeDB->saveToDisk(SEGMENT_DEVICESTATE); return disable(); // Stop the GPS thread as it can do nothing useful until next reboot. } } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index de63af08e..61f08fe65 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -14,6 +14,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "SafeFile.h" #include "TypeConversions.h" #include "error.h" #include "main.h" @@ -53,6 +54,28 @@ meshtastic_LocalConfig config; meshtastic_LocalModuleConfig moduleConfig; meshtastic_ChannelFile channelFile; meshtastic_OEMStore oemStore; +static bool hasOemStore = false; + +// These are not publically exposed - copied from InternalFileSystem.cpp +// #define FLASH_NRF52_PAGE_SIZE 4096 +// #define LFS_FLASH_TOTAL_SIZE (7*FLASH_NRF52_PAGE_SIZE) +// #define LFS_BLOCK_SIZE 128 + +/// List all files in the FS and test write and readback. +/// Useful for filesystem stress testing - normally stripped from build by the linker. +void flashTest() +{ + auto filesManifest = getFiles("/", 5); + + uint32_t totalSize = 0; + for (size_t i = 0; i < filesManifest.size(); i++) { + LOG_INFO("File %s (size %d)\n", filesManifest[i].file_name, filesManifest[i].size_bytes); + totalSize += filesManifest[i].size_bytes; + } + LOG_INFO("%d files (total size %u)\n", filesManifest.size(), totalSize); + // LOG_INFO("Filesystem block size %u, total bytes %u", LFS_FLASH_TOTAL_SIZE, LFS_BLOCK_SIZE); + nodeDB->saveToDisk(); +} bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field) { @@ -608,22 +631,30 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t void NodeDB::loadFromDisk() { + devicestate.version = + 0; // Mark the current device state as completely unusable, so that if we fail reading the entire file from + // disk we will still factoryReset to restore things. + // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate); - if (state != LoadFileResult::LOAD_SUCCESS) { - installDefaultDeviceState(); // Our in RAM copy might now be corrupt + // See https://github.com/meshtastic/firmware/issues/4184#issuecomment-2269390786 + // It is very important to try and use the saved prefs even if we fail to read meshtastic_DeviceState. Because most of our + // critical config may still be valid (in the other files - loaded next). + // Also, if we did fail on reading we probably failed on the enormous (and non critical) nodeDB. So DO NOT install default + // device state. + // if (state != LoadFileResult::LOAD_SUCCESS) { + // installDefaultDeviceState(); // Our in RAM copy might now be corrupt + //} else { + if (devicestate.version < DEVICESTATE_MIN_VER) { + LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version); + factoryReset(); } else { - if (devicestate.version < DEVICESTATE_MIN_VER) { - LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version); - factoryReset(); - } else { - LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version, - devicestate.node_db_lite.size()); - meshNodes = &devicestate.node_db_lite; - numMeshNodes = devicestate.node_db_lite.size(); - } + LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version, + devicestate.node_db_lite.size()); + meshNodes = &devicestate.node_db_lite; + numMeshNodes = devicestate.node_db_lite.size(); } meshNodes->resize(MAX_NUM_NODES); @@ -669,6 +700,7 @@ void NodeDB::loadFromDisk() state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore); if (state == LoadFileResult::LOAD_SUCCESS) { LOG_INFO("Loaded OEMStore\n"); + hasOemStore = true; } // 2.4.X - configuration migration to update new default intervals @@ -693,48 +725,26 @@ void NodeDB::loadFromDisk() } /** Save a protobuf from a file, return true for success */ -bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct) +bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, + bool fullAtomic) { bool okay = false; #ifdef FSCom - // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM - String filenameTmp = filename; - filenameTmp += ".tmp"; - auto f = FSCom.open(filenameTmp.c_str(), FILE_O_WRITE); - if (f) { - LOG_INFO("Saving %s\n", filename); - pb_ostream_t stream = {&writecb, &f, protoSize}; + auto f = SafeFile(filename, fullAtomic); - if (!pb_encode(&stream, fields, dest_struct)) { - LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); - } else { - okay = true; - } - f.flush(); - f.close(); + LOG_INFO("Saving %s\n", filename); + pb_ostream_t stream = {&writecb, static_cast(&f), protoSize}; - // brief window of risk here ;-) - if (FSCom.exists(filename) && !FSCom.remove(filename)) { - LOG_WARN("Can't remove old pref file\n"); - } - if (!renameFile(filenameTmp.c_str(), filename)) { - LOG_ERROR("Error: can't rename new pref file\n"); - } + if (!pb_encode(&stream, fields, dest_struct)) { + LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); } else { - LOG_ERROR("Can't write prefs\n"); -#ifdef ARCH_NRF52 - static uint8_t failedCounter = 0; - failedCounter++; - if (failedCounter >= 2) { - LOG_ERROR("Failed to save file twice. Rebooting...\n"); - delay(100); - NVIC_SystemReset(); - // We used to blow away the filesystem here, but that's a bit extreme - // FSCom.format(); - // // After formatting, the device needs to be restarted - // nodeDB->resetRadioConfig(true); - } -#endif + okay = true; + } + + bool writeSucceeded = f.close(); + + if (!okay || !writeSucceeded) { + LOG_ERROR("Can't write prefs!\n"); } #else LOG_ERROR("ERROR: Filesystem not implemented\n"); @@ -742,32 +752,32 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ return okay; } -void NodeDB::saveChannelsToDisk() +bool NodeDB::saveChannelsToDisk() { #ifdef FSCom FSCom.mkdir("/prefs"); #endif - saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); + return saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); } -void NodeDB::saveDeviceStateToDisk() +bool NodeDB::saveDeviceStateToDisk() { #ifdef FSCom FSCom.mkdir("/prefs"); #endif - saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg, - &devicestate); + // Note: if MAX_NUM_NODES=100 and meshtastic_NodeInfoLite_size=166, so will be approximately 17KB + // Because so huge we _must_ not use fullAtomic, because the filesystem is probably too small to hold two copies of this + return saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg, + &devicestate, false); } -void NodeDB::saveToDisk(int saveWhat) +bool NodeDB::saveToDiskNoRetry(int saveWhat) { + bool success = true; + #ifdef FSCom FSCom.mkdir("/prefs"); #endif - if (saveWhat & SEGMENT_DEVICESTATE) { - saveDeviceStateToDisk(); - } - if (saveWhat & SEGMENT_CONFIG) { config.has_device = true; config.has_display = true; @@ -777,7 +787,7 @@ void NodeDB::saveToDisk(int saveWhat) config.has_network = true; config.has_bluetooth = true; - saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); + success &= saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); } if (saveWhat & SEGMENT_MODULECONFIG) { @@ -794,12 +804,45 @@ void NodeDB::saveToDisk(int saveWhat) moduleConfig.has_audio = true; moduleConfig.has_paxcounter = true; - saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); + success &= + saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); + } + + // We might need to rewrite the OEM data if we are reformatting the FS + if ((saveWhat & SEGMENT_OEM) && hasOemStore) { + success &= saveProto(oemConfigFile, meshtastic_OEMStore_size, &meshtastic_OEMStore_msg, &oemStore); } if (saveWhat & SEGMENT_CHANNELS) { - saveChannelsToDisk(); + success &= saveChannelsToDisk(); } + + if (saveWhat & SEGMENT_DEVICESTATE) { + success &= saveDeviceStateToDisk(); + } + + return success; +} + +bool NodeDB::saveToDisk(int saveWhat) +{ + bool success = saveToDiskNoRetry(saveWhat); + + if (!success) { + LOG_ERROR("Failed to save to disk, retrying...\n"); +#ifdef ARCH_NRF52 // @geeksville is not ready yet to say we should do this on other platforms. See bug #4184 discussion + FSCom.format(); + + // We need to rewrite the OEM data if we are reformatting the FS + saveWhat |= SEGMENT_OEM; +#endif + success = saveToDiskNoRetry(saveWhat); + + RECORD_CRITICALERROR(success ? meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE + : meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE); + } + + return success; } const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) @@ -1059,4 +1102,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} +} \ No newline at end of file diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e2c2471d4..c00ab8c03 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -19,6 +19,7 @@ DeviceState versions used to be defined in the .proto file but really only this #define SEGMENT_MODULECONFIG 2 #define SEGMENT_DEVICESTATE 4 #define SEGMENT_CHANNELS 8 +#define SEGMENT_OEM 16 #define DEVICESTATE_CUR_VER 23 #define DEVICESTATE_MIN_VER 22 @@ -72,8 +73,8 @@ class NodeDB NodeDB(); /// write to flash - void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS), - saveChannelsToDisk(), saveDeviceStateToDisk(); + /// @return true if the save was successful + bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); /** Reinit radio config if needed, because either: * a) sometimes a buggy android app might send us bogus settings or @@ -130,7 +131,8 @@ class NodeDB LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct); - bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct); + bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, + bool fullAtomic = true); void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); @@ -181,6 +183,13 @@ class NodeDB /// Reinit device state from scratch (not loading from disk) void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig(); + + /// write to flash + /// @return true if the save was successful + bool saveToDiskNoRetry(int saveWhat); + + bool saveChannelsToDisk(); + bool saveDeviceStateToDisk(); }; extern NodeDB *nodeDB; @@ -228,4 +237,4 @@ extern uint32_t error_address; ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ ModuleConfig_TelemetryConfig_size + ModuleConfig_size) -// Please do not remove this comment, it makes trunk and compiler happy at the same time. +// Please do not remove this comment, it makes trunk and compiler happy at the same time. \ No newline at end of file diff --git a/src/mesh/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp index 676208e25..5b5d669cc 100644 --- a/src/mesh/mesh-pb-constants.cpp +++ b/src/mesh/mesh-pb-constants.cpp @@ -58,7 +58,7 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) /// Write to an arduino file bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) { - File *file = (File *)stream->state; + auto file = (Print *)stream->state; // LOG_DEBUG("writing %d bytes to protobuf file\n", count); return file->write(buf, count) == count; } @@ -71,4 +71,4 @@ bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count) return true; return false; -} +} \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp index 39ac4b08b..3560c6580 100644 --- a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp @@ -5,6 +5,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "FSCommon.h" #include "NAU7802Sensor.h" +#include "SafeFile.h" #include "TelemetrySensor.h" #include #include @@ -95,27 +96,21 @@ void NAU7802Sensor::tare() bool NAU7802Sensor::saveCalibrationData() { - if (FSCom.exists(nau7802ConfigFileName) && !FSCom.remove(nau7802ConfigFileName)) { - LOG_WARN("Can't remove old state file\n"); - } - auto file = FSCom.open(nau7802ConfigFileName, FILE_O_WRITE); + auto file = SafeFile(nau7802ConfigFileName); nau7802config.zeroOffset = nau7802.getZeroOffset(); nau7802config.calibrationFactor = nau7802.getCalibrationFactor(); bool okay = false; - if (file) { - LOG_INFO("%s state write to %s.\n", sensorName, nau7802ConfigFileName); - pb_ostream_t stream = {&writecb, &file, meshtastic_Nau7802Config_size}; - if (!pb_encode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) { - LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); - } else { - okay = true; - } - file.flush(); - file.close(); + LOG_INFO("%s state write to %s.\n", sensorName, nau7802ConfigFileName); + pb_ostream_t stream = {&writecb, static_cast(&file), meshtastic_Nau7802Config_size}; + + if (!pb_encode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) { + LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); } else { - LOG_INFO("Can't write %s state (File: %s).\n", sensorName, nau7802ConfigFileName); + okay = true; } + okay &= file.close(); + return okay; } From 9ec7dbd69504581c789d865624761fa841a6e7c4 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:59:33 -0400 Subject: [PATCH 0799/1377] Initial Support for Heltec VM-T190 (#4391) Initial Support for Heltec VM-T190 --- boards/heltec_vision_master_t190.json | 42 +++++++++++++++++++ .../heltec_vision_master_t190/pins_arduino.h | 4 +- .../heltec_vision_master_t190/platformio.ini | 2 +- variants/heltec_vision_master_t190/variant.h | 38 +++++++---------- 4 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 boards/heltec_vision_master_t190.json diff --git a/boards/heltec_vision_master_t190.json b/boards/heltec_vision_master_t190.json new file mode 100644 index 000000000..341e70218 --- /dev/null +++ b/boards/heltec_vision_master_t190.json @@ -0,0 +1,42 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_t190" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master t190", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-t190/", + "vendor": "Heltec" +} diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/heltec_vision_master_t190/pins_arduino.h index e5d507846..eeef95ff1 100644 --- a/variants/heltec_vision_master_t190/pins_arduino.h +++ b/variants/heltec_vision_master_t190/pins_arduino.h @@ -10,8 +10,8 @@ static const uint8_t LED_BUILTIN = 35; static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; +static const uint8_t SDA = 2; +static const uint8_t SCL = 1; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini index bbaa0075c..38a3169e8 100644 --- a/variants/heltec_vision_master_t190/platformio.ini +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -1,6 +1,6 @@ [env:heltec-vision-master-t190] extends = esp32s3_base -board = heltec_wifi_lora_32_V3 +board = heltec_vision_master_t190 build_flags = ${esp32s3_base.build_flags} -Ivariants/heltec_vision_master_t190 diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h index 97500d357..726f6d864 100644 --- a/variants/heltec_vision_master_t190/variant.h +++ b/variants/heltec_vision_master_t190/variant.h @@ -1,12 +1,12 @@ -// #define LED_PIN 18 +#define BUTTON_PIN 0 -// Enable bus for external periherals -#define I2C_SDA 1 -#define I2C_SCL 2 +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + +// Display (TFT) #define USE_ST7789 - #define ST7789_NSS 39 -// #define ST7789_CS 39 #define ST7789_RS 47 // DC #define ST7789_SDA 48 // MOSI #define ST7789_SCK 38 @@ -14,14 +14,9 @@ #define ST7789_MISO 4 #define ST7789_BUSY -1 #define VTFT_CTRL 7 -// #define TFT_BL 3 #define VTFT_LEDA 17 #define TFT_BACKLIGHT_ON HIGH -// #define TFT_BL 17 -// #define TFT_BACKLIGHT_ON HIGH -// #define ST7789_BL 3 #define ST7789_SPI_HOST SPI2_HOST -// #define ST7789_BACKLIGHT_EN 17 #define SPI_FREQUENCY 10000000 #define SPI_READ_FREQUENCY 10000000 #define TFT_HEIGHT 170 @@ -35,28 +30,27 @@ // #define SLEEP_TIME 120 -/* - * SPI interfaces - */ +// SPI #define SPI_INTERFACES_COUNT 2 +#define PIN_SPI_MISO 11 +#define PIN_SPI_MOSI 10 +#define PIN_SPI_SCK 9 -#define PIN_SPI_MISO 10 // MISO P0.17 -#define PIN_SPI_MOSI 11 // MOSI P0.15 -#define PIN_SPI_SCK 9 // SCK P0.13 - -// #define VEXT_ENABLE 7 // active low, powers the oled display and the lora antenna boost -#define BUTTON_PIN 0 - +// Power +#define VEXT_ENABLE 5 +#define VEXT_ON_VALUE HIGH #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 6 #define ADC_CHANNEL ADC1_GPIO6_CHANNEL #define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high +#define HAS_32768HZ +// LoRa #define USE_SX1262 -#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_DIO0 RADIOLIB_NC // a No connect on the SX1262 module #define LORA_RESET 12 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY From 92526fca23bb2d9e585937fd0769debcced96879 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 7 Aug 2024 10:16:56 +1200 Subject: [PATCH 0800/1377] "Scan and Select" input for Canned Messages (#4365) * Add "Scan and Select" input method for canned messages * Adapt canned message drawing if USE_EINK * Indicate current selection with indent rather than inverse text * Avoid large text on "sending" and delivery report pop-ups * Fit SNR and RSSI details on screen * Change hash function which detects changes in E-Ink images The old function struggled to distingush between images on the canned-message frame, failing to update when scrolling between messages. No real justification for the new algorithm, other than "it works" and doesn't seem "too expensive". For context, this function runs once a second. * Use canned messages (scan and select) by default with HT-VME213 and HT-VME290 * Guard for HAS_SCREEN --- src/graphics/EInkDynamicDisplay.cpp | 2 +- src/graphics/Screen.cpp | 6 + src/input/ScanAndSelect.cpp | 204 +++++++++++++++++++ src/input/ScanAndSelect.h | 50 +++++ src/mesh/NodeDB.cpp | 7 + src/modules/CannedMessageModule.cpp | 63 +++++- src/modules/CannedMessageModule.h | 1 + src/modules/Modules.cpp | 11 + variants/heltec_vision_master_e213/variant.h | 2 + variants/heltec_vision_master_e290/variant.h | 2 + 10 files changed, 338 insertions(+), 10 deletions(-) create mode 100644 src/input/ScanAndSelect.cpp create mode 100644 src/input/ScanAndSelect.h diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 5b97b8d48..c31941a60 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -375,7 +375,7 @@ void EInkDynamicDisplay::hashImage() // Sum all bytes of the image buffer together for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { - imageHash += buffer[b]; + imageHash ^= buffer[b] << b; } } diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index fe6fd3f06..ea5ab9788 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -37,6 +37,7 @@ along with this program. If not, see . #include "gps/RTC.h" #include "graphics/ScreenFonts.h" #include "graphics/images.h" +#include "input/ScanAndSelect.h" #include "input/TouchScreenImpl1.h" #include "main.h" #include "mesh-pb-constants.h" @@ -2291,6 +2292,11 @@ void Screen::handlePrint(const char *text) void Screen::handleOnPress() { + // If Canned Messages is using the "Scan and Select" input, dismiss the canned message frame when user button is pressed + // Minimize impact as a courtesy, as "scan and select" may be used as default config for some boards + if (scanAndSelectInput != nullptr && scanAndSelectInput->dismissCannedMessageFrame()) + return; + // If screen was off, just wake it, otherwise advance to next frame // If we are in a transition, the press must have bounced, drop it. if (ui->getUiState()->frameState == FIXED) { diff --git a/src/input/ScanAndSelect.cpp b/src/input/ScanAndSelect.cpp new file mode 100644 index 000000000..d693d768c --- /dev/null +++ b/src/input/ScanAndSelect.cpp @@ -0,0 +1,204 @@ +#include "configuration.h" + +// Normally these input methods are protected by guarding in setupModules +// In order to have the user button dismiss the canned message frame, this class lightly interacts with the Screen class +#if HAS_SCREEN + +#include "ScanAndSelect.h" +#include "modules/CannedMessageModule.h" + +// Config +static const char name[] = "scanAndSelect"; // should match "allow input source" string +static constexpr uint32_t durationShortMs = 50; +static constexpr uint32_t durationLongMs = 1500; +static constexpr uint32_t durationAlertMs = 2000; + +// Constructor: init base class +ScanAndSelectInput::ScanAndSelectInput() : concurrency::OSThread(name) {} + +// Attempt to setup class; true if success. +// Called by setupModules method. Instance deleted if setup fails. +bool ScanAndSelectInput::init() +{ + // Short circuit: Canned messages enabled? + if (!moduleConfig.canned_message.enabled) + return false; + + // Short circuit: Using correct "input source"? + // Todo: protobuf enum instead of string? + if (strcasecmp(moduleConfig.canned_message.allow_input_source, name) != 0) + return false; + + // Use any available inputbroker pin as the button + if (moduleConfig.canned_message.inputbroker_pin_press) + pin = moduleConfig.canned_message.inputbroker_pin_press; + else if (moduleConfig.canned_message.inputbroker_pin_a) + pin = moduleConfig.canned_message.inputbroker_pin_a; + else if (moduleConfig.canned_message.inputbroker_pin_b) + pin = moduleConfig.canned_message.inputbroker_pin_b; + else + return false; // Short circuit: no button found + + // Set-up the button + pinMode(pin, INPUT_PULLUP); + attachInterrupt(pin, handleChangeInterrupt, CHANGE); + + // Connect our class to the canned message module + inputBroker->registerSource(this); + + LOG_INFO("Initialized 'Scan and Select' input for Canned Messages, using pin %d\n", pin); + return true; // Init succeded +} + +// Runs periodically, unless sleeping between presses +int32_t ScanAndSelectInput::runOnce() +{ + uint32_t now = millis(); + + // If: "no messages added" alert screen currently shown + if (alertingNoMessage) { + // Dismiss the alert screen several seconds after it appears + if (now > alertingSinceMs + durationAlertMs) { + alertingNoMessage = false; + screen->endAlert(); + } + } + + // If: Button is pressed + if (digitalRead(pin) == LOW) { + // New press + if (!held) { + downSinceMs = now; + } + + // Existing press + else { + // Duration enough for long press + // Long press not yet fired (prevent repeat firing while held) + if (!longPressFired && now - downSinceMs > durationLongMs) { + longPressFired = true; + longPress(); + } + } + + // Record the change of state: button is down + held = true; + } + + // If: Button is not pressed + else { + // Button newly released + // Long press event didn't already fire + if (held && !longPressFired) { + // Duration enough for short press + if (now - downSinceMs > durationShortMs) { + shortPress(); + } + } + + // Record the change of state: button is up + held = false; + longPressFired = false; // Re-Arm: allow another long press + } + + // If thread's job is done, let it sleep + if (!held && !alertingNoMessage) { + Thread::canSleep = true; + return OSThread::disable(); + } + + // Run this method again is a few ms + return durationShortMs; +} + +void ScanAndSelectInput::longPress() +{ + // (If canned messages set) + if (cannedMessageModule->hasMessages()) { + // If module frame displayed already, send the current message + if (cannedMessageModule->shouldDraw()) + raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT); + + // Otherwise, initial long press opens the module frame + else + raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN); + } + + // (If canned messages not set) tell the user + else + alertNoMessage(); +} + +void ScanAndSelectInput::shortPress() +{ + // (If canned messages set) scroll to next message + if (cannedMessageModule->hasMessages()) + raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN); + + // (If canned messages not yet set) tell the user + else + alertNoMessage(); +} + +// Begin running runOnce at regular intervals +// Called from pin change interrupt +void ScanAndSelectInput::enableThread() +{ + Thread::canSleep = false; + OSThread::enabled = true; + OSThread::setIntervalFromNow(0); +} + +// Inform user (screen) that no canned messages have been added +// Automatically dismissed after several seconds +void ScanAndSelectInput::alertNoMessage() +{ + alertingNoMessage = true; + alertingSinceMs = millis(); + + // Graphics code: the alert frame to show on screen + screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + display->setTextAlignment(TEXT_ALIGN_CENTER_BOTH); + display->setFont(FONT_SMALL); + int16_t textX = display->getWidth() / 2; + int16_t textY = display->getHeight() / 2; + display->drawString(textX + x, textY + y, "No Canned Messages"); + }); +} + +// Remove the canned message frame from screen +// Used to dismiss the module frame when user button pressed +// Returns true if the frame was previously displayed, and has now been closed +// Return value consumed by Screen class when determining how to handle user button +bool ScanAndSelectInput::dismissCannedMessageFrame() +{ + if (cannedMessageModule->shouldDraw()) { + raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL); + return true; + } + + return false; +} + +// Feed input to the canned messages module +void ScanAndSelectInput::raiseEvent(_meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar key) +{ + InputEvent e; + e.source = name; + e.inputEvent = key; + notifyObservers(&e); +} + +// Pin change interrupt +void ScanAndSelectInput::handleChangeInterrupt() +{ + // Because we need to detect both press and release (rising and falling edge), the interrupt itself can't determine the + // action. Instead, we start up the thread and get it to read the button for us + + // The instance we're referring to here is created in setupModules() + scanAndSelectInput->enableThread(); +} + +ScanAndSelectInput *scanAndSelectInput = nullptr; // Instantiated in setupModules method. Deleted if unused, or init() fails + +#endif \ No newline at end of file diff --git a/src/input/ScanAndSelect.h b/src/input/ScanAndSelect.h new file mode 100644 index 000000000..0b3e2716e --- /dev/null +++ b/src/input/ScanAndSelect.h @@ -0,0 +1,50 @@ +/* + A "single button" input method for Canned Messages + + - Short press to cycle through messages + - Long Press to send + + To use: + - set "allow input source" to "scanAndSelect" + - set the single button's GPIO as either pin A, pin B, or pin Press + + Originally designed to make use of "extra" built-in button on some boards. + Non-intrusive; suitable for use as a default module config. +*/ + +#pragma once +#include "concurrency/OSThread.h" +#include "main.h" + +// Normally these input methods are protected by guarding in setupModules +// In order to have the user button dismiss the canned message frame, this class lightly interacts with the Screen class +#if HAS_SCREEN + +class ScanAndSelectInput : public Observable, public concurrency::OSThread +{ + public: + ScanAndSelectInput(); // No-op constructor, only initializes OSThread base class + bool init(); // Attempt to setup class; true if success. Instance deleted if setup fails + bool dismissCannedMessageFrame(); // Remove the canned message frame from screen. True if frame was open, and now closed. + void alertNoMessage(); // Inform user (screen) that no canned messages have been added + + protected: + int32_t runOnce() override; // Runs at regular intervals, when enabled + void enableThread(); // Begin running runOnce at regular intervals + static void handleChangeInterrupt(); // Calls enableThread from pin change interrupt + void shortPress(); // Code to run when short press fires + void longPress(); // Code to run when long press fires + void raiseEvent(_meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar key); // Feed input to canned message module + + bool held = false; // Have we handled a change in button state? + bool longPressFired = false; // Long press fires while button still held. This bool ensures the release is no-op + uint32_t downSinceMs = 0; // Debouncing for short press, timing for long press + uint8_t pin = -1; // Read from cannned message config during init + + bool alertingNoMessage = false; // Is the "no canned messages" alert shown on screen? + uint32_t alertingSinceMs = 0; // Used to dismiss the "no canned message" alert several seconds +}; + +extern ScanAndSelectInput *scanAndSelectInput; // Instantiated in setupModules method. Deleted if unused, or init() fails + +#endif \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 61f08fe65..d74e96bc6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -394,6 +394,13 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.active = true; #endif +#ifdef BUTTON_SECONDARY_CANNEDMESSAGES + // Use a board's second built-in button as input source for canned messages + moduleConfig.canned_message.enabled = true; + moduleConfig.canned_message.inputbroker_pin_press = BUTTON_PIN_SECONDARY; + strcpy(moduleConfig.canned_message.allow_input_source, "scanAndSelect"); +#endif + moduleConfig.has_canned_message = true; strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(moduleConfig.mqtt.address)); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index f4ee3abd2..4df5a03fc 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -10,6 +10,7 @@ #include "NodeDB.h" #include "PowerFSM.h" // needed for button bypass #include "detect/ScanI2C.h" +#include "input/ScanAndSelect.h" #include "mesh/generated/meshtastic/cannedmessages.pb.h" #include "main.h" // for cardkb_found @@ -694,9 +695,22 @@ bool CannedMessageModule::shouldDraw() if (!moduleConfig.canned_message.enabled && !CANNED_MESSAGE_MODULE_ENABLE) { return false; } + + // If using "scan and select" input, don't draw the module frame just to say "disabled" + // The scanAndSelectInput class will draw its own temporary alert for user, when the input button is pressed + else if (scanAndSelectInput != nullptr && !hasMessages()) + return false; + return (currentMessageIndex != -1) || (this->runState != CANNED_MESSAGE_RUN_STATE_INACTIVE); } +// Has the user defined any canned messages? +// Expose publicly whether canned message module is ready for use +bool CannedMessageModule::hasMessages() +{ + return (this->messagesCount > 0); +} + int CannedMessageModule::getNextIndex() { if (this->currentMessageIndex >= (this->messagesCount - 1)) { @@ -931,13 +945,17 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { - // E-Ink: clean the screen *after* this pop-up - EINK_ADD_FRAMEFLAG(display, COSMETIC); + requestFocus(); // Tell Screen::setFrames to move to our module's frame + EINK_ADD_FRAMEFLAG(display, COSMETIC); // Clean after this popup. Layout makes ghosting particularly obvious + +#ifdef USE_EINK + display->setFont(FONT_SMALL); // No chunky text +#else + display->setFont(FONT_MEDIUM); // Chunky text +#endif - requestFocus(); // Tell Screen::setFrames to move to our module's frame - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); String displayString; + display->setTextAlignment(TEXT_ALIGN_CENTER); if (this->ack) { displayString = "Delivered to\n%s"; } else { @@ -951,17 +969,37 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st String snrString = "Last Rx SNR: %f"; String rssiString = "Last Rx RSSI: %d"; - if (this->ack) { - display->drawStringf(display->getWidth() / 2 + x, y + 100, buffer, snrString, this->lastRxSnr); - display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi); + // Don't bother drawing snr and rssi for tiny displays + if (display->getHeight() > 100) { + + // Original implementation used constants of y = 100 and y = 130. Shrink this if screen is *slightly* small + int16_t snrY = 100; + int16_t rssiY = 130; + + // If dislay is *slighly* too small for the original consants, squish up a bit + if (display->getHeight() < rssiY) { + snrY = display->getHeight() - ((1.5) * FONT_HEIGHT_SMALL); + rssiY = display->getHeight() - ((2.5) * FONT_HEIGHT_SMALL); + } + + if (this->ack) { + display->drawStringf(display->getWidth() / 2 + x, snrY + y, buffer, snrString, this->lastRxSnr); + display->drawStringf(display->getWidth() / 2 + x, rssiY + y, buffer, rssiString, this->lastRxRssi); + } } } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { // E-Ink: clean the screen *after* this pop-up EINK_ADD_FRAMEFLAG(display, COSMETIC); requestFocus(); // Tell Screen::setFrames to move to our module's frame + +#ifdef USE_EINK + display->setFont(FONT_SMALL); // No chunky text +#else + display->setFont(FONT_MEDIUM); // Chunky text +#endif + display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending..."); } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1033,11 +1071,18 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int topMsg = (messagesCount > lines && currentMessageIndex >= lines - 1) ? currentMessageIndex - lines + 2 : 0; for (int i = 0; i < std::min(messagesCount, lines); i++) { if (i == currentMessageIndex - topMsg) { +#ifdef USE_EINK + // Avoid drawing solid black with fillRect: harder to clear for E-Ink + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), ">"); + display->drawString(12 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), + cannedMessageModule->getCurrentMessage()); +#else display->fillRect(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), x + display->getWidth(), y + FONT_HEIGHT_SMALL); display->setColor(BLACK); display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), cannedMessageModule->getCurrentMessage()); display->setColor(WHITE); +#endif } else { display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), cannedMessageModule->getMessageByIndex(topMsg + i)); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 797b9f7cf..9e6af8890 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -56,6 +56,7 @@ class CannedMessageModule : public SinglePortModule, public Observableinit()) { + delete scanAndSelectInput; + scanAndSelectInput = nullptr; + } +#endif + cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); #ifdef INPUTBROKER_MATRIX_TYPE diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index bbc697f09..0771b3517 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -1,4 +1,6 @@ #define BUTTON_PIN 0 +#define BUTTON_PIN_SECONDARY 21 // Second built-in button +#define BUTTON_SECONDARY_CANNEDMESSAGES // By default, use the secondary button as canned message input // I2C #define I2C_SDA SDA diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h index 6af4b06a5..72a82cfdb 100644 --- a/variants/heltec_vision_master_e290/variant.h +++ b/variants/heltec_vision_master_e290/variant.h @@ -1,4 +1,6 @@ #define BUTTON_PIN 0 +#define BUTTON_PIN_SECONDARY 21 // Second built-in button +#define BUTTON_SECONDARY_CANNEDMESSAGES // By default, use the secondary button as canned message input // I2C #define I2C_SDA SDA From 789e8f02bf914b3582be69270925cc930651b6ff Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 6 Aug 2024 18:48:55 -0500 Subject: [PATCH 0801/1377] Add more exclude options to save program ram/flash (#4408) * Add PowerFSM Exclude option * Add TEXTMESSAGE module exclude option --------- Co-authored-by: Ben Meadors --- src/PowerFSM.cpp | 8 ++++++-- src/PowerFSM.h | 25 ++++++++++++++++++++++++- src/PowerFSMThread.h | 4 ++++ src/configuration.h | 2 ++ src/modules/AdminModule.cpp | 5 ++++- src/modules/Modules.cpp | 2 ++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 0a954c1b8..8bf7d3218 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -22,7 +22,10 @@ #ifndef SLEEP_TIME #define SLEEP_TIME 30 #endif - +#if EXCLUDE_POWER_FSM +FakeFsm powerFSM; +void PowerFSM_setup(){}; +#else /// Should we behave as if we have AC power now? static bool isPowered() { @@ -395,4 +398,5 @@ void PowerFSM_setup() #endif powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 752a9f7dd..13dfdc4cc 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "configuration.h" // See sw-design.md for documentation @@ -22,7 +22,30 @@ #define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep) #define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen +#if EXCLUDE_POWER_FSM +class FakeFsm +{ + public: + void trigger(int event) + { + if (event == EVENT_SERIAL_CONNECTED) { + serialConnected = true; + } else if (event == EVENT_SERIAL_DISCONNECTED) { + serialConnected = false; + } + }; + bool getState() { return serialConnected; }; + + private: + bool serialConnected = false; +}; +extern FakeFsm powerFSM; +void PowerFSM_setup(); + +#else +#include extern Fsm powerFSM; extern State stateON, statePOWER, stateSERIAL, stateDARK; void PowerFSM_setup(); +#endif \ No newline at end of file diff --git a/src/PowerFSMThread.h b/src/PowerFSMThread.h index fb640dd8b..c842f4515 100644 --- a/src/PowerFSMThread.h +++ b/src/PowerFSMThread.h @@ -18,6 +18,7 @@ class PowerFSMThread : public OSThread protected: int32_t runOnce() override { +#if !EXCLUDE_POWER_FSM powerFSM.run_machine(); /// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake @@ -35,6 +36,9 @@ class PowerFSMThread : public OSThread } return 100; +#else + return INT32_MAX; +#endif } }; diff --git a/src/configuration.h b/src/configuration.h index 4ebc59ffc..5365db239 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -256,6 +256,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_MQTT 1 #define MESHTASTIC_EXCLUDE_POWERMON 1 #define MESHTASTIC_EXCLUDE_I2C 1 +#define MESHTASTIC_EXCLUDE_POWER_FSM 1 #endif // Turn off all optional modules @@ -269,6 +270,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_RANGETEST 1 #define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1 #define MESHTASTIC_EXCLUDE_STOREFORWARD 1 +#define MESHTASTIC_EXCLUDE_TEXTMESSAGE 1 #define MESHTASTIC_EXCLUDE_ATAK 1 #define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1 #define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1 diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 8287f8a00..0f053a049 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -818,8 +818,11 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r #endif #endif conn.has_serial = true; // No serial-less devices +#if !EXCLUDE_POWER_FSM conn.serial.is_connected = powerFSM.getState() == &stateSERIAL; - conn.serial.baud = SERIAL_BAUD; +#else + conn.serial.is_connected = powerFSM.getState(); +#endif conn.serial.baud = SERIAL_BAUD; r.get_device_connection_status_response = conn; r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag; diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 3df8fc170..a1c9f4a03 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -106,7 +106,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_WAYPOINT waypointModule = new WaypointModule(); #endif +#if !MESHTASTIC_EXCLUDE_TEXTMESSAGE textMessageModule = new TextMessageModule(); +#endif #if !MESHTASTIC_EXCLUDE_TRACEROUTE traceRouteModule = new TraceRouteModule(); #endif From 5111bd703a20a762c8399f85e6d40ec730be109e Mon Sep 17 00:00:00 2001 From: Tilen Komel <46865859+KomelT@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:23:31 +0200 Subject: [PATCH 0802/1377] Updted protobuf url (#4411) --- src/mesh/http/ContentHandler.cpp | 4 ++-- src/mesh/raspihttp/PiWebServer.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 9a981ee54..772b3e821 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -161,7 +161,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) res->setHeader("Content-Type", "application/x-protobuf"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); - res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto"); uint8_t txBuf[MAX_STREAM_BUF_SIZE]; uint32_t len = 1; @@ -205,7 +205,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) res->setHeader("Access-Control-Allow-Headers", "Content-Type"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS"); - res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto"); if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp index bffd6c340..d7e596759 100644 --- a/src/mesh/raspihttp/PiWebServer.cpp +++ b/src/mesh/raspihttp/PiWebServer.cpp @@ -230,7 +230,7 @@ int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, vo ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*"); ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS"); ulfius_add_header_to_response(res, "X-Protobuf-Schema", - "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + "https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto"); if (req->http_verb == "OPTIONS") { ulfius_set_response_properties(res, U_OPT_STATUS, 204); @@ -267,7 +267,7 @@ int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*"); ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET"); ulfius_add_header_to_response(res, "X-Protobuf-Schema", - "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + "https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto"); uint8_t txBuf[MAX_STREAM_BUF_SIZE]; uint32_t len = 1; From 02ae24b6fa7fb66b9126fb8a24e7b9f55e88e698 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 9 Aug 2024 09:08:14 +0800 Subject: [PATCH 0803/1377] Remove outdated comments (#4417) These comments from four years ago no longer reflect how things work. --- src/mesh/MeshService.cpp | 15 +-------------- src/mesh/Router.cpp | 10 +--------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index fd07c3801..07dd71ddc 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -44,17 +44,6 @@ arbitrating to select a node number and keeping the current nodedb. The algorithm is as follows: * when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so the new node can build its node db) -* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that -indicates a collision has occurred and the following steps should happen: - -If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to -keep its nodenum. We send a broadcast message of OUR User (we use a broadcast so that the other node can receive our message, -considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay. - -If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random -nodenum (filtering against whatever it knows about the nodedb) and rebroadcast their User. - -FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first. */ MeshService *service; @@ -77,8 +66,6 @@ MeshService::MeshService() void MeshService::init() { - // moved much earlier in boot (called from setup()) - // nodeDB.init(); #if HAS_GPS if (gps) gpsObserver.observe(&gps->newStatus); @@ -405,4 +392,4 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) bool MeshService::isToPhoneQueueEmpty() { return toPhoneQueue.isEmpty(); -} \ No newline at end of file +} diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1260b7c04..87fcfe1b6 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -19,14 +19,6 @@ #include "serialization/MeshPacketSerializer.h" #endif #include "../userPrefs.h" -/** - * Router todo - * - * DONE: Implement basic interface and use it elsewhere in app - * Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s - * Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet) - * - **/ #define MAX_RX_FROMRADIO \ 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big @@ -546,4 +538,4 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) handleReceived(p); packetPool.release(p); -} \ No newline at end of file +} From b498c0bfbfe2630605318482b0ba2b619bee4663 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 9 Aug 2024 09:18:18 +0800 Subject: [PATCH 0804/1377] [WIP] Add support for Airoha AG3335 GPS (#4394) * Add GPS detection code for Airoha AG3335 Airoha AG3335 is used in Seeed T-1000E Tracker * Add support for Airoha AG3335 Airoha AG3335 is used in Seeed T-1000E Tracker. This adds detection code, and code to configure its use. --------- Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 24 ++++++++++++++++++++++++ src/gps/GPS.h | 5 +++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 93742a99a..fcc7c0bd6 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -504,6 +504,22 @@ bool GPS::setup() delay(250); _serial_gps->write("$CFGMSG,6,1,0\r\n"); delay(250); + } else if (gnssModel == GNSS_MODEL_AG3335) { + + _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B"); // Enable GPS+GALILEO+NAVIC + + // Configure NMEA (sentences will output once per fix) + _serial_gps->write("$PAIR062,0,0*3F"); // GGA ON + _serial_gps->write("$PAIR062,1,0*3F"); // GLL OFF + _serial_gps->write("$PAIR062,2,1*3D"); // GSA ON + _serial_gps->write("$PAIR062,3,0*3D"); // GSV OFF + _serial_gps->write("$PAIR062,4,0*3B"); // RMC ON + _serial_gps->write("$PAIR062,5,0*3B"); // VTG OFF + _serial_gps->write("$PAIR062,6,1*39"); // ZDA ON + + delay(250); + _serial_gps->write("$PAIR513*3D"); // save configuration + } else if (gnssModel == GNSS_MODEL_UBLOX) { // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) // We need set it because by default it is GPS only, and we want to use GLONASS too @@ -1218,6 +1234,14 @@ GnssModel_t GPS::probe(int serialSpeed) return GNSS_MODEL_ATGM336H; } + /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ + clearBuffer(); + _serial_gps->write("PAIR020*38\r\n"); + if (getACK("$PAIR020,AG3335", 500) == GNSS_RESPONSE_OK) { + LOG_INFO("Aioha AG3335 detected, using AG3335 Module\n"); + return GNSS_MODEL_AG3335; + } + // Get version information clearBuffer(); _serial_gps->write("$PCAS06,0*1B\r\n"); diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 87d03c592..1c0977bdd 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -28,7 +28,8 @@ typedef enum { GNSS_MODEL_UBLOX, GNSS_MODEL_UC6580, GNSS_MODEL_UNKNOWN, - GNSS_MODEL_MTK_L76B + GNSS_MODEL_MTK_L76B, + GNSS_MODEL_AG3335 } GnssModel_t; typedef enum { @@ -302,4 +303,4 @@ class GPS : private concurrency::OSThread }; extern GPS *gps; -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS From 5b4530325f83d836740128cec1a6242613039f65 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 8 Aug 2024 20:53:26 -0500 Subject: [PATCH 0805/1377] Short circuit while the probe code does not auto-detect --- src/gps/GPS.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index fcc7c0bd6..82370937b 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1195,6 +1195,9 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->updateBaudRate(serialSpeed); } #endif +#ifdef GNSS_AIROHA + return GNSS_MODEL_AG3335; +#endif #ifdef GPS_DEBUG for (int i = 0; i < 20; i++) { getACK("$GP", 200); @@ -1241,6 +1244,9 @@ GnssModel_t GPS::probe(int serialSpeed) LOG_INFO("Aioha AG3335 detected, using AG3335 Module\n"); return GNSS_MODEL_AG3335; } + // Get version information for Airoha AG3335 + clearBuffer(); + _serial_gps->write("$PMTK605*31\r\n"); // Get version information clearBuffer(); From a7da3537e2710cbebc27151c63c0ba612a2d876a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 9 Aug 2024 00:52:31 -0500 Subject: [PATCH 0806/1377] Adds MESHTASTIC_EXCLUDE_TZ option (#4423) --- src/configuration.h | 3 ++- src/gps/RTC.cpp | 4 ++++ src/main.cpp | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/configuration.h b/src/configuration.h index 5365db239..9148f1d37 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -257,6 +257,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_POWERMON 1 #define MESHTASTIC_EXCLUDE_I2C 1 #define MESHTASTIC_EXCLUDE_POWER_FSM 1 +#define MESHTASTIC_EXCLUDE_TZ 1 #endif // Turn off all optional modules @@ -312,4 +313,4 @@ along with this program. If not, see . #endif #include "DebugConfiguration.h" -#include "RF95Configuration.h" +#include "RF95Configuration.h" \ No newline at end of file diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index d60e3825c..710baca98 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -256,6 +256,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local) time_t gm_mktime(struct tm *tm) { +#if !MESHTASTIC_EXCLUDE_TZ setenv("TZ", "GMT0", 1); time_t res = mktime(tm); if (*config.device.tzdef) { @@ -264,4 +265,7 @@ time_t gm_mktime(struct tm *tm) setenv("TZ", "UTC0", 1); } return res; +#else + return mktime(tm); +#endif } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f4f7ad537..0a3c1ae0b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -689,6 +689,7 @@ void setup() screen = new graphics::Screen(screen_found, screen_model, screen_geometry); // setup TZ prior to time actions. +#if !MESHTASTIC_EXCLUDE_TZ if (*config.device.tzdef) { setenv("TZ", config.device.tzdef, 1); } else { @@ -696,6 +697,7 @@ void setup() } tzset(); LOG_DEBUG("Set Timezone to %s\n", getenv("TZ")); +#endif readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) From c6a9edf8c7fdf0df8da46a5c9930748309e7cf89 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 9 Aug 2024 01:43:13 -0500 Subject: [PATCH 0807/1377] Move printBytes to meshUtils (#4424) --- src/mesh/Router.cpp | 9 +-------- src/meshUtils.cpp | 8 ++++++++ src/meshUtils.h | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 87fcfe1b6..79095805d 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -7,6 +7,7 @@ #include "configuration.h" #include "main.h" #include "mesh-pb-constants.h" +#include "meshUtils.h" #include "modules/RoutingModule.h" #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" @@ -188,14 +189,6 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } } -void printBytes(const char *label, const uint8_t *p, size_t numbytes) -{ - LOG_DEBUG("%s: ", label); - for (size_t i = 0; i < numbytes; i++) - LOG_DEBUG("%02x ", p[i]); - LOG_DEBUG("\n"); -} - /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 59d4e6714..86d237129 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -56,4 +56,12 @@ char *strnstr(const char *s, const char *find, size_t slen) s--; } return ((char *)s); +} + +void printBytes(const char *label, const uint8_t *p, size_t numbytes) +{ + LOG_DEBUG("%s: ", label); + for (size_t i = 0; i < numbytes; i++) + LOG_DEBUG("%02x ", p[i]); + LOG_DEBUG("\n"); } \ No newline at end of file diff --git a/src/meshUtils.h b/src/meshUtils.h index e32ef230a..9dfe9b558 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -1,4 +1,6 @@ #pragma once +#include "DebugConfiguration.h" +#include /// C++ v17+ clamp function, limits a given value to a range defined by lo and hi template constexpr const T &clamp(const T &v, const T &lo, const T &hi) From d8bdb92efe47d6dc180026def55b973e20db1048 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 06:35:26 -0500 Subject: [PATCH 0808/1377] [create-pull-request] automated change (#4409) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index b8bb5e67d..0450e7054 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 4 -build = 2 +build = 3 From e38aca3cba890d5aa00bd94c052f1bf2195fb5fb Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 9 Aug 2024 19:35:42 +0800 Subject: [PATCH 0809/1377] NimbleBluetooth.h is not required in MeshService. (#4419) There are no calls to the functions defined in Nimble from this class. See also older comment on line 8 about the dream to seperate mesh and bluetooth :) --- src/mesh/MeshService.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 07dd71ddc..697644a4f 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -19,10 +19,6 @@ #include #include -#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH -#include "nimble/NimbleBluetooth.h" -#endif - #if ARCH_PORTDUINO #include "PortduinoGlue.h" #endif From 3ab4bebdcba570686e97f6c75b0dfed44ea7b972 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 06:37:49 -0500 Subject: [PATCH 0810/1377] [create-pull-request] automated change (#4426) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index d0fe91ab9..2fa7d6a4b 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d0fe91ab99734cacdc188403f73fe30f766917cf +Subproject commit 2fa7d6a4b702fcd58b54b0d1d6e4b3b85164f649 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index d0e643dff..bef2abf9b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -168,6 +168,8 @@ typedef struct _meshtastic_AdminMessage { bool begin_edit_settings; /* Commits an open transaction for any edits made to config, module config, owner, and channel settings */ bool commit_edit_settings; + /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ + int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */ int32_t reboot_ota_seconds; @@ -178,8 +180,8 @@ typedef struct _meshtastic_AdminMessage { int32_t reboot_seconds; /* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown) */ int32_t shutdown_seconds; - /* Tell the node to factory reset, all device settings will be returned to factory defaults. */ - int32_t factory_reset; + /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */ + int32_t factory_reset_config; /* Tell the node to reset the nodedb. */ int32_t nodedb_reset; }; @@ -254,11 +256,12 @@ extern "C" { #define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 +#define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 #define meshtastic_AdminMessage_reboot_seconds_tag 97 #define meshtastic_AdminMessage_shutdown_seconds_tag 98 -#define meshtastic_AdminMessage_factory_reset_tag 99 +#define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 /* Struct field encoding specification for nanopb */ @@ -298,11 +301,12 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset,factory_reset), 99) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL From 3878e025e4c467f151f3489ea30ff4261e3dabd7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 9 Aug 2024 08:38:29 -0500 Subject: [PATCH 0811/1377] Split factory reset into config and device variants (#4427) * Split factory reset into config and device variants * Trunk * Default only in header --- src/mesh/NodeDB.cpp | 21 ++++++++++++--------- src/mesh/NodeDB.h | 2 +- src/modules/AdminModule.cpp | 10 ++++++++-- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d74e96bc6..0f6c444ce 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -211,7 +211,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset) return didFactoryReset; } -bool NodeDB::factoryReset() +bool NodeDB::factoryReset(bool eraseBleBonds) { LOG_INFO("Performing factory reset!\n"); // first, remove the "/prefs" (this removes most prefs) @@ -228,18 +228,21 @@ bool NodeDB::factoryReset() installDefaultChannels(); // third, write everything to disk saveToDisk(); + if (eraseBleBonds) { + LOG_INFO("Erasing BLE bonds\n"); #ifdef ARCH_ESP32 - // This will erase what's in NVS including ssl keys, persistent variables and ble pairing - nvs_flash_erase(); + // This will erase what's in NVS including ssl keys, persistent variables and ble pairing + nvs_flash_erase(); #endif #ifdef ARCH_NRF52 - Bluefruit.begin(); - LOG_INFO("Clearing bluetooth bonds!\n"); - bond_print_list(BLE_GAP_ROLE_PERIPH); - bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.Periph.clearBonds(); - Bluefruit.Central.clearBonds(); + Bluefruit.begin(); + LOG_INFO("Clearing bluetooth bonds!\n"); + bond_print_list(BLE_GAP_ROLE_PERIPH); + bond_print_list(BLE_GAP_ROLE_CENTRAL); + Bluefruit.Periph.clearBonds(); + Bluefruit.Central.clearBonds(); #endif + } return true; } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index c00ab8c03..447ce10d4 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -127,7 +127,7 @@ class NodeDB void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum); - bool factoryReset(); + bool factoryReset(bool eraseBleBonds = false); LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 0f053a049..778b7193d 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -162,12 +162,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta handleGetDeviceMetadata(mp); break; } - case meshtastic_AdminMessage_factory_reset_tag: { - LOG_INFO("Initiating factory reset\n"); + case meshtastic_AdminMessage_factory_reset_config_tag: { + LOG_INFO("Initiating factory config reset\n"); nodeDB->factoryReset(); reboot(DEFAULT_REBOOT_SECONDS); break; } + case meshtastic_AdminMessage_factory_reset_device_tag: { + LOG_INFO("Initiating full factory reset\n"); + nodeDB->factoryReset(true); + reboot(DEFAULT_REBOOT_SECONDS); + break; + } case meshtastic_AdminMessage_nodedb_reset_tag: { LOG_INFO("Initiating node-db reset\n"); nodeDB->resetNodes(); From debf4b934f3cffe6f9410cb320406c395e26030d Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:26:22 +0200 Subject: [PATCH 0812/1377] Fix for "has default channel" with empty channel name (#4430) --- src/mesh/Channels.cpp | 9 +++++---- src/mesh/Channels.h | 4 ++-- src/modules/esp32/StoreForwardModule.cpp | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 8d5b4353b..6e721d9b0 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -309,12 +309,14 @@ const char *Channels::getName(size_t chIndex) return channelName; } -bool Channels::isDefaultChannel(const meshtastic_Channel &ch) +bool Channels::isDefaultChannel(ChannelIndex chIndex) { + const auto &ch = getByIndex(chIndex); if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { + const char *name = getName(chIndex); const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); // Check if the name is the default derived from the modem preset - if (strcmp(ch.settings.name, presetName) == 0) + if (strcmp(name, presetName) == 0) return true; } return false; @@ -327,8 +329,7 @@ bool Channels::hasDefaultChannel() return false; // Check if any of the channels are using the default name and PSK for (size_t i = 0; i < getNumChannels(); i++) { - const auto &ch = getByIndex(i); - if (isDefaultChannel(ch)) + if (isDefaultChannel(i)) return true; } return false; diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index eaccea8e1..4f87cb309 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -84,7 +84,7 @@ class Channels int16_t setActiveByIndex(ChannelIndex channelIndex); // Returns true if the channel has the default name and PSK - bool isDefaultChannel(const meshtastic_Channel &ch); + bool isDefaultChannel(ChannelIndex chIndex); // Returns true if we can be reached via a channel with the default settings given a region and modem preset bool hasDefaultChannel(); @@ -129,4 +129,4 @@ class Channels }; /// Singleton channel table -extern Channels channels; +extern Channels channels; \ No newline at end of file diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index 7581bbc38..c696d342a 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -382,7 +382,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m LOG_DEBUG("*** Legacy Request to send\n"); // Send the last 60 minutes of messages. - if (this->busy || channels.isDefaultChannel(channels.getByIndex(mp.channel))) { + if (this->busy || channels.isDefaultChannel(mp.channel)) { sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response); } else { storeForwardModule->historySend(historyReturnWindow * 60, getFrom(&mp)); @@ -447,7 +447,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, requests_history++; LOG_INFO("*** Client Request to send HISTORY\n"); // Send the last 60 minutes of messages. - if (this->busy || channels.isDefaultChannel(channels.getByIndex(mp.channel))) { + if (this->busy || channels.isDefaultChannel(mp.channel)) { sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response); } else { if ((p->which_variant == meshtastic_StoreAndForward_history_tag) && (p->variant.history.window > 0)) { From 3513d88794e83ddf436df77c5ca1e93709ae8611 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 07:25:05 -0500 Subject: [PATCH 0813/1377] Protobufs --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 12 +- src/mesh/generated/meshtastic/config.pb.cpp | 3 + src/mesh/generated/meshtastic/config.pb.h | 83 +++++++++-- src/mesh/generated/meshtastic/deviceonly.pb.h | 4 +- src/mesh/generated/meshtastic/localonly.pb.h | 14 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 5 +- src/mesh/generated/meshtastic/mesh.pb.h | 110 +++++++++++--- src/mesh/generated/meshtastic/telemetry.pb.h | 136 +++++++++++------- 9 files changed, 273 insertions(+), 96 deletions(-) diff --git a/protobufs b/protobufs index 2fa7d6a4b..c112ce6e1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 2fa7d6a4b702fcd58b54b0d1d6e4b3b85164f649 +Subproject commit c112ce6e1392e4bc812655fae5e8461c932b5267 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index bef2abf9b..d0e643dff 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -168,8 +168,6 @@ typedef struct _meshtastic_AdminMessage { bool begin_edit_settings; /* Commits an open transaction for any edits made to config, module config, owner, and channel settings */ bool commit_edit_settings; - /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ - int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */ int32_t reboot_ota_seconds; @@ -180,8 +178,8 @@ typedef struct _meshtastic_AdminMessage { int32_t reboot_seconds; /* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown) */ int32_t shutdown_seconds; - /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */ - int32_t factory_reset_config; + /* Tell the node to factory reset, all device settings will be returned to factory defaults. */ + int32_t factory_reset; /* Tell the node to reset the nodedb. */ int32_t nodedb_reset; }; @@ -256,12 +254,11 @@ extern "C" { #define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 -#define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 #define meshtastic_AdminMessage_reboot_seconds_tag 97 #define meshtastic_AdminMessage_shutdown_seconds_tag 98 -#define meshtastic_AdminMessage_factory_reset_config_tag 99 +#define meshtastic_AdminMessage_factory_reset_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 /* Struct field encoding specification for nanopb */ @@ -301,12 +298,11 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset,factory_reset), 99) \ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index bb82198c0..c6274aed4 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -33,6 +33,9 @@ PB_BIND(meshtastic_Config_LoRaConfig, meshtastic_Config_LoRaConfig, 2) PB_BIND(meshtastic_Config_BluetoothConfig, meshtastic_Config_BluetoothConfig, AUTO) +PB_BIND(meshtastic_Config_SecurityConfig, meshtastic_Config_SecurityConfig, AUTO) + + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 44a86f4d6..dbb0deb00 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -248,7 +248,8 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset { meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST = 0, /* Long Range - Slow */ meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW = 1, - /* Very Long Range - Slow */ + /* Very Long Range - Slow + Deprecated in 2.5: Works only with txco and is unusably slow */ meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW = 2, /* Medium Range - Slow */ meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW = 3, @@ -259,7 +260,11 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset { /* Short Range - Fast */ meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST = 6, /* Long Range - Moderately Fast */ - meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE = 7 + meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE = 7, + /* Short Range - Turbo + This is the fastest preset and the only one with 500kHz bandwidth. + It is not legal to use in all regions due to this wider bandwidth. */ + meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8 } meshtastic_Config_LoRaConfig_ModemPreset; typedef enum _meshtastic_Config_BluetoothConfig_PairingMode { @@ -276,10 +281,12 @@ typedef enum _meshtastic_Config_BluetoothConfig_PairingMode { typedef struct _meshtastic_Config_DeviceConfig { /* Sets the role of node */ meshtastic_Config_DeviceConfig_Role role; - /* Disabling this will disable the SerialConsole by not initilizing the StreamAPI */ + /* Disabling this will disable the SerialConsole by not initilizing the StreamAPI + Moved to SecurityConfig */ bool serial_enabled; /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). - Set this to true to leave the debug log outputting even when API is active. */ + Set this to true to leave the debug log outputting even when API is active. + Moved to SecurityConfig */ bool debug_log_enabled; /* For boards without a hard wired button, this is the pin number that will be used Boards that have more than one button can swap the function with this one. defaults to BUTTON_PIN if defined. */ @@ -295,7 +302,8 @@ typedef struct _meshtastic_Config_DeviceConfig { /* Treat double tap interrupt on supported accelerometers as a button press if set to true */ bool double_tap_as_button_press; /* If true, device is considered to be "managed" by a mesh administrator - Clients should then limit available configuration and administrative options inside the user interface */ + Clients should then limit available configuration and administrative options inside the user interface + Moved to SecurityConfig */ bool is_managed; /* Disables the triple-press of user button to enable or disable GPS */ bool disable_triple_click; @@ -515,10 +523,37 @@ typedef struct _meshtastic_Config_BluetoothConfig { meshtastic_Config_BluetoothConfig_PairingMode mode; /* Specified PIN for PairingMode.FixedPin */ uint32_t fixed_pin; - /* Enables device (serial style logs) over Bluetooth */ + /* Enables device (serial style logs) over Bluetooth + Moved to SecurityConfig */ bool device_logging_enabled; } meshtastic_Config_BluetoothConfig; +typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_public_key_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_private_key_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_admin_key_t; +typedef struct _meshtastic_Config_SecurityConfig { + /* The public key of the user's device. + Sent out to other nodes on the mesh to allow them to compute a shared secret key. */ + meshtastic_Config_SecurityConfig_public_key_t public_key; + /* The private key of the device. + Used to create a shared key with a remote device. */ + meshtastic_Config_SecurityConfig_private_key_t private_key; + /* The public key authorized to send admin messages to this node. */ + meshtastic_Config_SecurityConfig_admin_key_t admin_key; + /* If true, device is considered to be "managed" by a mesh administrator via admin messages + Device is managed by a mesh administrator. */ + bool is_managed; + /* Serial Console over the Stream API." */ + bool serial_enabled; + /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). + Output live debug logging over serial. */ + bool debug_log_api_enabled; + /* Enables device (serial style logs) over Bluetooth */ + bool bluetooth_logging_enabled; + /* Allow incoming device control over the insecure legacy admin channel. */ + bool admin_channel_enabled; +} meshtastic_Config_SecurityConfig; + typedef struct _meshtastic_Config { pb_size_t which_payload_variant; union { @@ -529,6 +564,7 @@ typedef struct _meshtastic_Config { meshtastic_Config_DisplayConfig display; meshtastic_Config_LoRaConfig lora; meshtastic_Config_BluetoothConfig bluetooth; + meshtastic_Config_SecurityConfig security; } payload_variant; } meshtastic_Config; @@ -583,8 +619,8 @@ extern "C" { #define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_SG_923+1)) #define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST -#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE -#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE+1)) +#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO +#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO+1)) #define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN #define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN @@ -612,6 +648,7 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_mode_ENUMTYPE meshtastic_Config_BluetoothConfig_PairingMode + /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -622,6 +659,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} @@ -631,6 +669,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Config_DeviceConfig_role_tag 1 @@ -711,6 +750,14 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 #define meshtastic_Config_BluetoothConfig_device_logging_enabled_tag 4 +#define meshtastic_Config_SecurityConfig_public_key_tag 1 +#define meshtastic_Config_SecurityConfig_private_key_tag 2 +#define meshtastic_Config_SecurityConfig_admin_key_tag 3 +#define meshtastic_Config_SecurityConfig_is_managed_tag 4 +#define meshtastic_Config_SecurityConfig_serial_enabled_tag 5 +#define meshtastic_Config_SecurityConfig_debug_log_api_enabled_tag 6 +#define meshtastic_Config_SecurityConfig_bluetooth_logging_enabled_tag 7 +#define meshtastic_Config_SecurityConfig_admin_channel_enabled_tag 8 #define meshtastic_Config_device_tag 1 #define meshtastic_Config_position_tag 2 #define meshtastic_Config_power_tag 3 @@ -718,6 +765,7 @@ extern "C" { #define meshtastic_Config_display_tag 5 #define meshtastic_Config_lora_tag 6 #define meshtastic_Config_bluetooth_tag 7 +#define meshtastic_Config_security_tag 8 /* Struct field encoding specification for nanopb */ #define meshtastic_Config_FIELDLIST(X, a) \ @@ -727,7 +775,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,power,payload_variant.power) X(a, STATIC, ONEOF, MESSAGE, (payload_variant,network,payload_variant.network), 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,display,payload_variant.display), 5) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lora,payload_variant.lora), 6) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bluetooth), 7) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bluetooth), 7) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,security,payload_variant.security), 8) #define meshtastic_Config_CALLBACK NULL #define meshtastic_Config_DEFAULT NULL #define meshtastic_Config_payload_variant_device_MSGTYPE meshtastic_Config_DeviceConfig @@ -737,6 +786,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bl #define meshtastic_Config_payload_variant_display_MSGTYPE meshtastic_Config_DisplayConfig #define meshtastic_Config_payload_variant_lora_MSGTYPE meshtastic_Config_LoRaConfig #define meshtastic_Config_payload_variant_bluetooth_MSGTYPE meshtastic_Config_BluetoothConfig +#define meshtastic_Config_payload_variant_security_MSGTYPE meshtastic_Config_SecurityConfig #define meshtastic_Config_DeviceConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, role, 1) \ @@ -849,6 +899,18 @@ X(a, STATIC, SINGULAR, BOOL, device_logging_enabled, 4) #define meshtastic_Config_BluetoothConfig_CALLBACK NULL #define meshtastic_Config_BluetoothConfig_DEFAULT NULL +#define meshtastic_Config_SecurityConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 1) \ +X(a, STATIC, SINGULAR, BYTES, private_key, 2) \ +X(a, STATIC, SINGULAR, BYTES, admin_key, 3) \ +X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \ +X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \ +X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \ +X(a, STATIC, SINGULAR, BOOL, bluetooth_logging_enabled, 7) \ +X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8) +#define meshtastic_Config_SecurityConfig_CALLBACK NULL +#define meshtastic_Config_SecurityConfig_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_Config_msg; extern const pb_msgdesc_t meshtastic_Config_DeviceConfig_msg; extern const pb_msgdesc_t meshtastic_Config_PositionConfig_msg; @@ -858,6 +920,7 @@ extern const pb_msgdesc_t meshtastic_Config_NetworkConfig_IpV4Config_msg; extern const pb_msgdesc_t meshtastic_Config_DisplayConfig_msg; extern const pb_msgdesc_t meshtastic_Config_LoRaConfig_msg; extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; +extern const pb_msgdesc_t meshtastic_Config_SecurityConfig_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Config_fields &meshtastic_Config_msg @@ -869,6 +932,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_DisplayConfig_fields &meshtastic_Config_DisplayConfig_msg #define meshtastic_Config_LoRaConfig_fields &meshtastic_Config_LoRaConfig_msg #define meshtastic_Config_BluetoothConfig_fields &meshtastic_Config_BluetoothConfig_msg +#define meshtastic_Config_SecurityConfig_fields &meshtastic_Config_SecurityConfig_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size @@ -880,6 +944,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 +#define meshtastic_Config_SecurityConfig_size 112 #define meshtastic_Config_size 199 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index eb37f4f95..2c91fe30e 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -306,8 +306,8 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 -#define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3388 +#define meshtastic_NodeInfoLite_size 200 +#define meshtastic_OEMStore_size 3502 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 983f48ad3..c612b24ab 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -38,6 +38,9 @@ typedef struct _meshtastic_LocalConfig { incompatible changes This integer is set at build time and is private to NodeDB.cpp in the device code. */ uint32_t version; + /* The part of the config that is specific to Security settings */ + bool has_security; + meshtastic_Config_SecurityConfig security; } meshtastic_LocalConfig; typedef struct _meshtastic_LocalModuleConfig { @@ -92,9 +95,9 @@ extern "C" { #endif /* Initializer values for message structs */ -#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0} +#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} #define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} -#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0} +#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} #define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ @@ -106,6 +109,7 @@ extern "C" { #define meshtastic_LocalConfig_lora_tag 6 #define meshtastic_LocalConfig_bluetooth_tag 7 #define meshtastic_LocalConfig_version_tag 8 +#define meshtastic_LocalConfig_security_tag 9 #define meshtastic_LocalModuleConfig_mqtt_tag 1 #define meshtastic_LocalModuleConfig_serial_tag 2 #define meshtastic_LocalModuleConfig_external_notification_tag 3 @@ -130,7 +134,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, network, 4) \ X(a, STATIC, OPTIONAL, MESSAGE, display, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, lora, 6) \ X(a, STATIC, OPTIONAL, MESSAGE, bluetooth, 7) \ -X(a, STATIC, SINGULAR, UINT32, version, 8) +X(a, STATIC, SINGULAR, UINT32, version, 8) \ +X(a, STATIC, OPTIONAL, MESSAGE, security, 9) #define meshtastic_LocalConfig_CALLBACK NULL #define meshtastic_LocalConfig_DEFAULT NULL #define meshtastic_LocalConfig_device_MSGTYPE meshtastic_Config_DeviceConfig @@ -140,6 +145,7 @@ X(a, STATIC, SINGULAR, UINT32, version, 8) #define meshtastic_LocalConfig_display_MSGTYPE meshtastic_Config_DisplayConfig #define meshtastic_LocalConfig_lora_MSGTYPE meshtastic_Config_LoRaConfig #define meshtastic_LocalConfig_bluetooth_MSGTYPE meshtastic_Config_BluetoothConfig +#define meshtastic_LocalConfig_security_MSGTYPE meshtastic_Config_SecurityConfig #define meshtastic_LocalModuleConfig_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, mqtt, 1) \ @@ -181,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 555 +#define meshtastic_LocalConfig_size 669 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 3fa81e131..8c8b9ded7 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -30,7 +30,7 @@ PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2) PB_BIND(meshtastic_MeshPacket, meshtastic_MeshPacket, 2) -PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, AUTO) +PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, 2) PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO) @@ -45,6 +45,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2) +PB_BIND(meshtastic_ClientNotification, meshtastic_ClientNotification, 2) + + PB_BIND(meshtastic_FileInfo, meshtastic_FileInfo, AUTO) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 59664b792..1d677e0d5 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -367,10 +367,13 @@ typedef enum _meshtastic_LogRecord_Level { typedef struct _meshtastic_Position { /* The new preferred location encoding, multiply by 1e-7 to get degrees in floating point */ + bool has_latitude_i; int32_t latitude_i; /* TODO: REPLACE */ + bool has_longitude_i; int32_t longitude_i; /* In meters above MSL (but see issue #359) */ + bool has_altitude; int32_t altitude; /* This is usually not sent over the mesh (to save space), but it is sent from the phone so that the local device can set its time if it is sent over @@ -386,8 +389,10 @@ typedef struct _meshtastic_Position { /* Pos. timestamp milliseconds adjustment (rarely available or required) */ int32_t timestamp_millis_adjust; /* HAE altitude in meters - can be used instead of MSL altitude */ + bool has_altitude_hae; int32_t altitude_hae; /* Geoidal separation in meters */ + bool has_altitude_geoidal_separation; int32_t altitude_geoidal_separation; /* Horizontal, Vertical and Position Dilution of Precision, in 1/100 units - PDOP is sufficient for most cases @@ -409,8 +414,10 @@ typedef struct _meshtastic_Position { - "heading" is where the fuselage points (measured in horizontal plane) - "yaw" indicates a relative rotation about the vertical axis TODO: REMOVE/INTEGRATE */ + bool has_ground_speed; uint32_t ground_speed; /* TODO: REPLACE */ + bool has_ground_track; uint32_t ground_track; /* GPS fix quality (from NMEA GxGGA statement or similar) */ uint32_t fix_quality; @@ -432,6 +439,7 @@ typedef struct _meshtastic_Position { uint32_t precision_bits; } meshtastic_Position; +typedef PB_BYTES_ARRAY_T(32) meshtastic_User_public_key_t; /* Broadcast when a newly powered mesh node wants to find a node num it can use Sent from the phone over bluetooth to set the user id for the owner of this node. Also sent from nodes to each other when a new node signs on (so all clients can have this info) @@ -478,6 +486,9 @@ typedef struct _meshtastic_User { bool is_licensed; /* Indicates that the user's role in the mesh */ meshtastic_Config_DeviceConfig_Role role; + /* The public key of the user's device. + This is sent out to other nodes on the mesh to allow them to compute a shared secret key. */ + meshtastic_User_public_key_t public_key; } meshtastic_User; /* A message used in our Dynamic Source Routing protocol (RFC 4728 based) */ @@ -539,8 +550,10 @@ typedef struct _meshtastic_Waypoint { /* Id of the waypoint */ uint32_t id; /* latitude_i */ + bool has_latitude_i; int32_t latitude_i; /* longitude_i */ + bool has_longitude_i; int32_t longitude_i; /* Time the waypoint is to expire (epoch) */ uint32_t expire; @@ -572,6 +585,7 @@ typedef struct _meshtastic_MqttClientProxyMessage { } meshtastic_MqttClientProxyMessage; typedef PB_BYTES_ARRAY_T(256) meshtastic_MeshPacket_encrypted_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_MeshPacket_public_key_t; /* A packet envelope sent/received over the mesh only payload_variant is sent in the payload portion of the LORA packet. The other fields are either not sent at all, or sent in the special 16 byte LORA header. */ @@ -642,6 +656,10 @@ typedef struct _meshtastic_MeshPacket { /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */ uint8_t hop_start; + /* Records the public key the packet was encrypted with, if applicable. */ + meshtastic_MeshPacket_public_key_t public_key; + /* Indicates whether the packet was en/decrypted using PKI */ + bool pki_encrypted; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -731,6 +749,22 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; +/* A notification message from the device to the client + To be used for important messages that should to be displayed to the user + in the form of push notifications or validation messages when saving + invalid configuration. */ +typedef struct _meshtastic_ClientNotification { + /* The id of the packet we're notifying in response to */ + bool has_reply_id; + uint32_t reply_id; + /* Seconds since 1970 - or 0 for unknown/unset */ + uint32_t time; + /* The level type of notification */ + meshtastic_LogRecord_Level level; + /* The message body of the notification */ + char message[400]; +} meshtastic_ClientNotification; + /* Individual File info for the device */ typedef struct _meshtastic_FileInfo { /* The fully qualified path of the file */ @@ -845,6 +879,8 @@ typedef struct _meshtastic_FromRadio { meshtastic_MqttClientProxyMessage mqttClientProxyMessage; /* File system manifest messages */ meshtastic_FileInfo fileInfo; + /* Notification message to the client */ + meshtastic_ClientNotification clientNotification; }; } meshtastic_FromRadio; @@ -987,6 +1023,8 @@ extern "C" { +#define meshtastic_ClientNotification_level_ENUMTYPE meshtastic_LogRecord_Level + #define meshtastic_Compressed_portnum_ENUMTYPE meshtastic_PortNum @@ -1003,19 +1041,20 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} +#define meshtastic_Position_init_default {false, 0, false, 0, false, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, false, 0, false, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} -#define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0} +#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} +#define meshtastic_ClientNotification_init_default {false, 0, 0, _meshtastic_LogRecord_Level_MIN, ""} #define meshtastic_FileInfo_init_default {"", 0} #define meshtastic_ToRadio_init_default {0, {meshtastic_MeshPacket_init_default}} #define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}} @@ -1027,19 +1066,20 @@ extern "C" { #define meshtastic_ChunkedPayload_init_default {0, 0, 0, {0, {0}}} #define meshtastic_resend_chunks_init_default {{{NULL}, NULL}} #define meshtastic_ChunkedPayloadResponse_init_default {0, 0, {0}} -#define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} +#define meshtastic_Position_init_zero {false, 0, false, 0, false, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, false, 0, false, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} -#define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0} +#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} +#define meshtastic_ClientNotification_init_zero {false, 0, 0, _meshtastic_LogRecord_Level_MIN, ""} #define meshtastic_FileInfo_init_zero {"", 0} #define meshtastic_ToRadio_init_zero {0, {meshtastic_MeshPacket_init_zero}} #define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}} @@ -1083,6 +1123,7 @@ extern "C" { #define meshtastic_User_hw_model_tag 5 #define meshtastic_User_is_licensed_tag 6 #define meshtastic_User_role_tag 7 +#define meshtastic_User_public_key_tag 8 #define meshtastic_RouteDiscovery_route_tag 1 #define meshtastic_Routing_route_request_tag 1 #define meshtastic_Routing_route_reply_tag 2 @@ -1122,6 +1163,8 @@ extern "C" { #define meshtastic_MeshPacket_delayed_tag 13 #define meshtastic_MeshPacket_via_mqtt_tag 14 #define meshtastic_MeshPacket_hop_start_tag 15 +#define meshtastic_MeshPacket_public_key_tag 16 +#define meshtastic_MeshPacket_pki_encrypted_tag 17 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -1143,6 +1186,10 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 +#define meshtastic_ClientNotification_reply_id_tag 1 +#define meshtastic_ClientNotification_time_tag 2 +#define meshtastic_ClientNotification_level_tag 3 +#define meshtastic_ClientNotification_message_tag 4 #define meshtastic_FileInfo_file_name_tag 1 #define meshtastic_FileInfo_size_bytes_tag 2 #define meshtastic_Compressed_portnum_tag 1 @@ -1180,6 +1227,7 @@ extern "C" { #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 #define meshtastic_FromRadio_fileInfo_tag 15 +#define meshtastic_FromRadio_clientNotification_tag 16 #define meshtastic_ToRadio_packet_tag 1 #define meshtastic_ToRadio_want_config_id_tag 3 #define meshtastic_ToRadio_disconnect_tag 4 @@ -1200,22 +1248,22 @@ extern "C" { /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ -X(a, STATIC, SINGULAR, INT32, altitude, 3) \ +X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 1) \ +X(a, STATIC, OPTIONAL, SFIXED32, longitude_i, 2) \ +X(a, STATIC, OPTIONAL, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, FIXED32, time, 4) \ X(a, STATIC, SINGULAR, UENUM, location_source, 5) \ X(a, STATIC, SINGULAR, UENUM, altitude_source, 6) \ X(a, STATIC, SINGULAR, FIXED32, timestamp, 7) \ X(a, STATIC, SINGULAR, INT32, timestamp_millis_adjust, 8) \ -X(a, STATIC, SINGULAR, SINT32, altitude_hae, 9) \ -X(a, STATIC, SINGULAR, SINT32, altitude_geoidal_separation, 10) \ +X(a, STATIC, OPTIONAL, SINT32, altitude_hae, 9) \ +X(a, STATIC, OPTIONAL, SINT32, altitude_geoidal_separation, 10) \ X(a, STATIC, SINGULAR, UINT32, PDOP, 11) \ X(a, STATIC, SINGULAR, UINT32, HDOP, 12) \ X(a, STATIC, SINGULAR, UINT32, VDOP, 13) \ X(a, STATIC, SINGULAR, UINT32, gps_accuracy, 14) \ -X(a, STATIC, SINGULAR, UINT32, ground_speed, 15) \ -X(a, STATIC, SINGULAR, UINT32, ground_track, 16) \ +X(a, STATIC, OPTIONAL, UINT32, ground_speed, 15) \ +X(a, STATIC, OPTIONAL, UINT32, ground_track, 16) \ X(a, STATIC, SINGULAR, UINT32, fix_quality, 17) \ X(a, STATIC, SINGULAR, UINT32, fix_type, 18) \ X(a, STATIC, SINGULAR, UINT32, sats_in_view, 19) \ @@ -1233,7 +1281,8 @@ X(a, STATIC, SINGULAR, STRING, short_name, 3) \ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) \ X(a, STATIC, SINGULAR, UENUM, hw_model, 5) \ X(a, STATIC, SINGULAR, BOOL, is_licensed, 6) \ -X(a, STATIC, SINGULAR, UENUM, role, 7) +X(a, STATIC, SINGULAR, UENUM, role, 7) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 8) #define meshtastic_User_CALLBACK NULL #define meshtastic_User_DEFAULT NULL @@ -1265,8 +1314,8 @@ X(a, STATIC, SINGULAR, FIXED32, emoji, 8) #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 2) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 3) \ +X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \ +X(a, STATIC, OPTIONAL, SFIXED32, longitude_i, 3) \ X(a, STATIC, SINGULAR, UINT32, expire, 4) \ X(a, STATIC, SINGULAR, UINT32, locked_to, 5) \ X(a, STATIC, SINGULAR, STRING, name, 6) \ @@ -1298,7 +1347,9 @@ X(a, STATIC, SINGULAR, UENUM, priority, 11) \ X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \ X(a, STATIC, SINGULAR, UENUM, delayed, 13) \ X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \ -X(a, STATIC, SINGULAR, UINT32, hop_start, 15) +X(a, STATIC, SINGULAR, UINT32, hop_start, 15) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 16) \ +X(a, STATIC, SINGULAR, BOOL, pki_encrypted, 17) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -1358,7 +1409,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,queueStatus,queueStatus), 1 X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,clientNotification,clientNotification), 16) #define meshtastic_FromRadio_CALLBACK NULL #define meshtastic_FromRadio_DEFAULT NULL #define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket @@ -1373,6 +1425,15 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) #define meshtastic_FromRadio_payload_variant_metadata_MSGTYPE meshtastic_DeviceMetadata #define meshtastic_FromRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage #define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo +#define meshtastic_FromRadio_payload_variant_clientNotification_MSGTYPE meshtastic_ClientNotification + +#define meshtastic_ClientNotification_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, UINT32, reply_id, 1) \ +X(a, STATIC, SINGULAR, FIXED32, time, 2) \ +X(a, STATIC, SINGULAR, UENUM, level, 3) \ +X(a, STATIC, SINGULAR, STRING, message, 4) +#define meshtastic_ClientNotification_CALLBACK NULL +#define meshtastic_ClientNotification_DEFAULT NULL #define meshtastic_FileInfo_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, file_name, 1) \ @@ -1478,6 +1539,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg; extern const pb_msgdesc_t meshtastic_LogRecord_msg; extern const pb_msgdesc_t meshtastic_QueueStatus_msg; extern const pb_msgdesc_t meshtastic_FromRadio_msg; +extern const pb_msgdesc_t meshtastic_ClientNotification_msg; extern const pb_msgdesc_t meshtastic_FileInfo_msg; extern const pb_msgdesc_t meshtastic_ToRadio_msg; extern const pb_msgdesc_t meshtastic_Compressed_msg; @@ -1504,6 +1566,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg #define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg #define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg +#define meshtastic_ClientNotification_fields &meshtastic_ClientNotification_msg #define meshtastic_FileInfo_fields &meshtastic_FileInfo_msg #define meshtastic_ToRadio_fields &meshtastic_ToRadio_msg #define meshtastic_Compressed_fields &meshtastic_Compressed_msg @@ -1521,6 +1584,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; /* meshtastic_ChunkedPayloadResponse_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size #define meshtastic_ChunkedPayload_size 245 +#define meshtastic_ClientNotification_size 415 #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 @@ -1528,19 +1592,19 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 426 -#define meshtastic_MeshPacket_size 326 +#define meshtastic_MeshPacket_size 364 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 283 +#define meshtastic_NodeInfo_size 317 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 #define meshtastic_Routing_size 42 #define meshtastic_ToRadio_size 504 -#define meshtastic_User_size 79 +#define meshtastic_User_size 113 #define meshtastic_Waypoint_size 165 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 82cd0a55d..43899ac9c 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -70,98 +70,138 @@ typedef enum _meshtastic_TelemetrySensorType { /* Key native device metrics such as battery level */ typedef struct _meshtastic_DeviceMetrics { /* 0-100 (>100 means powered) */ + bool has_battery_level; uint32_t battery_level; /* Voltage measured */ + bool has_voltage; float voltage; /* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). */ + bool has_channel_utilization; float channel_utilization; /* Percent of airtime for transmission used within the last hour. */ + bool has_air_util_tx; float air_util_tx; /* How long the device has been running since the last reboot (in seconds) */ + bool has_uptime_seconds; uint32_t uptime_seconds; } meshtastic_DeviceMetrics; /* Weather station or other environmental metrics */ typedef struct _meshtastic_EnvironmentMetrics { /* Temperature measured */ + bool has_temperature; float temperature; /* Relative humidity percent measured */ + bool has_relative_humidity; float relative_humidity; /* Barometric pressure in hPA measured */ + bool has_barometric_pressure; float barometric_pressure; /* Gas resistance in MOhm measured */ + bool has_gas_resistance; float gas_resistance; /* Voltage measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ + bool has_voltage; float voltage; /* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ + bool has_current; float current; /* relative scale IAQ value as measured by Bosch BME680 . value 0-500. Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ + bool has_iaq; uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ + bool has_distance; float distance; /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + bool has_lux; float lux; /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ + bool has_white_lux; float white_lux; /* Infrared lux */ + bool has_ir_lux; float ir_lux; /* Ultraviolet lux */ + bool has_uv_lux; float uv_lux; /* Wind direction in degrees 0 degrees = North, 90 = East, etc... */ + bool has_wind_direction; uint16_t wind_direction; /* Wind speed in m/s */ + bool has_wind_speed; float wind_speed; /* Weight in KG */ + bool has_weight; float weight; /* Wind gust in m/s */ + bool has_wind_gust; float wind_gust; /* Wind lull in m/s */ + bool has_wind_lull; float wind_lull; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ typedef struct _meshtastic_PowerMetrics { /* Voltage (Ch1) */ + bool has_ch1_voltage; float ch1_voltage; /* Current (Ch1) */ + bool has_ch1_current; float ch1_current; /* Voltage (Ch2) */ + bool has_ch2_voltage; float ch2_voltage; /* Current (Ch2) */ + bool has_ch2_current; float ch2_current; /* Voltage (Ch3) */ + bool has_ch3_voltage; float ch3_voltage; /* Current (Ch3) */ + bool has_ch3_current; float ch3_current; } meshtastic_PowerMetrics; /* Air quality metrics */ typedef struct _meshtastic_AirQualityMetrics { /* Concentration Units Standard PM1.0 */ + bool has_pm10_standard; uint32_t pm10_standard; /* Concentration Units Standard PM2.5 */ + bool has_pm25_standard; uint32_t pm25_standard; /* Concentration Units Standard PM10.0 */ + bool has_pm100_standard; uint32_t pm100_standard; /* Concentration Units Environmental PM1.0 */ + bool has_pm10_environmental; uint32_t pm10_environmental; /* Concentration Units Environmental PM2.5 */ + bool has_pm25_environmental; uint32_t pm25_environmental; /* Concentration Units Environmental PM10.0 */ + bool has_pm100_environmental; uint32_t pm100_environmental; /* 0.3um Particle Count */ + bool has_particles_03um; uint32_t particles_03um; /* 0.5um Particle Count */ + bool has_particles_05um; uint32_t particles_05um; /* 1.0um Particle Count */ + bool has_particles_10um; uint32_t particles_10um; /* 2.5um Particle Count */ + bool has_particles_25um; uint32_t particles_25um; /* 5.0um Particle Count */ + bool has_particles_50um; uint32_t particles_50um; /* 10.0um Particle Count */ + bool has_particles_100um; uint32_t particles_100um; } meshtastic_AirQualityMetrics; @@ -208,16 +248,16 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} -#define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} -#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} -#define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} @@ -272,58 +312,58 @@ extern "C" { /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, battery_level, 1) \ -X(a, STATIC, SINGULAR, FLOAT, voltage, 2) \ -X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 3) \ -X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) \ -X(a, STATIC, SINGULAR, UINT32, uptime_seconds, 5) +X(a, STATIC, OPTIONAL, UINT32, battery_level, 1) \ +X(a, STATIC, OPTIONAL, FLOAT, voltage, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, channel_utilization, 3) \ +X(a, STATIC, OPTIONAL, FLOAT, air_util_tx, 4) \ +X(a, STATIC, OPTIONAL, UINT32, uptime_seconds, 5) #define meshtastic_DeviceMetrics_CALLBACK NULL #define meshtastic_DeviceMetrics_DEFAULT NULL #define meshtastic_EnvironmentMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, FLOAT, temperature, 1) \ -X(a, STATIC, SINGULAR, FLOAT, relative_humidity, 2) \ -X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) \ -X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ -X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ -X(a, STATIC, SINGULAR, FLOAT, current, 6) \ -X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ -X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ -X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) \ -X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ -X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ -X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ -X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \ -X(a, STATIC, SINGULAR, FLOAT, weight, 15) \ -X(a, STATIC, SINGULAR, FLOAT, wind_gust, 16) \ -X(a, STATIC, SINGULAR, FLOAT, wind_lull, 17) +X(a, STATIC, OPTIONAL, FLOAT, temperature, 1) \ +X(a, STATIC, OPTIONAL, FLOAT, relative_humidity, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, barometric_pressure, 3) \ +X(a, STATIC, OPTIONAL, FLOAT, gas_resistance, 4) \ +X(a, STATIC, OPTIONAL, FLOAT, voltage, 5) \ +X(a, STATIC, OPTIONAL, FLOAT, current, 6) \ +X(a, STATIC, OPTIONAL, UINT32, iaq, 7) \ +X(a, STATIC, OPTIONAL, FLOAT, distance, 8) \ +X(a, STATIC, OPTIONAL, FLOAT, lux, 9) \ +X(a, STATIC, OPTIONAL, FLOAT, white_lux, 10) \ +X(a, STATIC, OPTIONAL, FLOAT, ir_lux, 11) \ +X(a, STATIC, OPTIONAL, FLOAT, uv_lux, 12) \ +X(a, STATIC, OPTIONAL, UINT32, wind_direction, 13) \ +X(a, STATIC, OPTIONAL, FLOAT, wind_speed, 14) \ +X(a, STATIC, OPTIONAL, FLOAT, weight, 15) \ +X(a, STATIC, OPTIONAL, FLOAT, wind_gust, 16) \ +X(a, STATIC, OPTIONAL, FLOAT, wind_lull, 17) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL #define meshtastic_PowerMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, FLOAT, ch1_voltage, 1) \ -X(a, STATIC, SINGULAR, FLOAT, ch1_current, 2) \ -X(a, STATIC, SINGULAR, FLOAT, ch2_voltage, 3) \ -X(a, STATIC, SINGULAR, FLOAT, ch2_current, 4) \ -X(a, STATIC, SINGULAR, FLOAT, ch3_voltage, 5) \ -X(a, STATIC, SINGULAR, FLOAT, ch3_current, 6) +X(a, STATIC, OPTIONAL, FLOAT, ch1_voltage, 1) \ +X(a, STATIC, OPTIONAL, FLOAT, ch1_current, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, ch2_voltage, 3) \ +X(a, STATIC, OPTIONAL, FLOAT, ch2_current, 4) \ +X(a, STATIC, OPTIONAL, FLOAT, ch3_voltage, 5) \ +X(a, STATIC, OPTIONAL, FLOAT, ch3_current, 6) #define meshtastic_PowerMetrics_CALLBACK NULL #define meshtastic_PowerMetrics_DEFAULT NULL #define meshtastic_AirQualityMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, pm10_standard, 1) \ -X(a, STATIC, SINGULAR, UINT32, pm25_standard, 2) \ -X(a, STATIC, SINGULAR, UINT32, pm100_standard, 3) \ -X(a, STATIC, SINGULAR, UINT32, pm10_environmental, 4) \ -X(a, STATIC, SINGULAR, UINT32, pm25_environmental, 5) \ -X(a, STATIC, SINGULAR, UINT32, pm100_environmental, 6) \ -X(a, STATIC, SINGULAR, UINT32, particles_03um, 7) \ -X(a, STATIC, SINGULAR, UINT32, particles_05um, 8) \ -X(a, STATIC, SINGULAR, UINT32, particles_10um, 9) \ -X(a, STATIC, SINGULAR, UINT32, particles_25um, 10) \ -X(a, STATIC, SINGULAR, UINT32, particles_50um, 11) \ -X(a, STATIC, SINGULAR, UINT32, particles_100um, 12) +X(a, STATIC, OPTIONAL, UINT32, pm10_standard, 1) \ +X(a, STATIC, OPTIONAL, UINT32, pm25_standard, 2) \ +X(a, STATIC, OPTIONAL, UINT32, pm100_standard, 3) \ +X(a, STATIC, OPTIONAL, UINT32, pm10_environmental, 4) \ +X(a, STATIC, OPTIONAL, UINT32, pm25_environmental, 5) \ +X(a, STATIC, OPTIONAL, UINT32, pm100_environmental, 6) \ +X(a, STATIC, OPTIONAL, UINT32, particles_03um, 7) \ +X(a, STATIC, OPTIONAL, UINT32, particles_05um, 8) \ +X(a, STATIC, OPTIONAL, UINT32, particles_10um, 9) \ +X(a, STATIC, OPTIONAL, UINT32, particles_25um, 10) \ +X(a, STATIC, OPTIONAL, UINT32, particles_50um, 11) \ +X(a, STATIC, OPTIONAL, UINT32, particles_100um, 12) #define meshtastic_AirQualityMetrics_CALLBACK NULL #define meshtastic_AirQualityMetrics_DEFAULT NULL From 2012a0ae1c166dad9c9274252eff22f46895e8e7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 07:51:59 -0500 Subject: [PATCH 0814/1377] Protos --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index c112ce6e1..f5e84249f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c112ce6e1392e4bc812655fae5e8461c932b5267 +Subproject commit f5e84249fe47fbddfb1d007c465f8f9623771290 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index d0e643dff..bef2abf9b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -168,6 +168,8 @@ typedef struct _meshtastic_AdminMessage { bool begin_edit_settings; /* Commits an open transaction for any edits made to config, module config, owner, and channel settings */ bool commit_edit_settings; + /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ + int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */ int32_t reboot_ota_seconds; @@ -178,8 +180,8 @@ typedef struct _meshtastic_AdminMessage { int32_t reboot_seconds; /* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown) */ int32_t shutdown_seconds; - /* Tell the node to factory reset, all device settings will be returned to factory defaults. */ - int32_t factory_reset; + /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */ + int32_t factory_reset_config; /* Tell the node to reset the nodedb. */ int32_t nodedb_reset; }; @@ -254,11 +256,12 @@ extern "C" { #define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 +#define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 #define meshtastic_AdminMessage_reboot_seconds_tag 97 #define meshtastic_AdminMessage_shutdown_seconds_tag 98 -#define meshtastic_AdminMessage_factory_reset_tag 99 +#define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 /* Struct field encoding specification for nanopb */ @@ -298,11 +301,12 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset,factory_reset), 99) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL From 861f0b6769cda66c624daddfe969b9e15700bfc1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 08:33:42 -0500 Subject: [PATCH 0815/1377] Add ClientNotification hello world --- src/mesh/MeshService.cpp | 20 +++++++++++++++++++- src/mesh/MeshService.h | 10 ++++++++++ src/mesh/PhoneAPI.h | 3 +++ src/mesh/Router.cpp | 8 ++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 697644a4f..d05f43b01 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -48,14 +48,18 @@ static MemoryDynamic staticMqttClientProxyMes static MemoryDynamic staticQueueStatusPool; +static MemoryDynamic staticClientNotificationPool; + Allocator &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool; +Allocator &clientNotificationPool = staticClientNotificationPool; + Allocator &queueStatusPool = staticQueueStatusPool; #include "Router.h" MeshService::MeshService() - : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE) + : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) { lastQueueStatus = {0, 0, 16, 0}; } @@ -324,6 +328,20 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage fromNum++; } +void MeshService::sendClientNotification(meshtastic_ClientNotification *n) +{ + LOG_DEBUG("Sending client notification to phone\n"); + if (toPhoneClientNotificationQueue.numFree() == 0) { + LOG_WARN("ClientNotification queue is full, discarding oldest\n"); + meshtastic_ClientNotification *d = toPhoneClientNotificationQueue.dequeuePtr(0); + if (d) + releaseClientNotificationToPool(d); + } + + assert(toPhoneClientNotificationQueue.enqueue(n, 0)); + fromNum++; +} + meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() { meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 528adb137..ea1c4e345 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -21,6 +21,7 @@ extern Allocator &queueStatusPool; extern Allocator &mqttClientProxyMessagePool; +extern Allocator &clientNotificationPool; /** * Top level app for this service. keeps the mesh, the radio config and the queue of received packets. @@ -44,6 +45,9 @@ class MeshService // keep list of MqttClientProxyMessages to be send to the client for delivery PointerQueue toPhoneMqttProxyQueue; + // keep list of ClientNotifications to be send to the client (phone) + PointerQueue toPhoneClientNotificationQueue; + // This holds the last QueueStatus send meshtastic_QueueStatus lastQueueStatus; @@ -97,6 +101,9 @@ class MeshService // Release MqttClientProxyMessage packet to pool void releaseMqttClientProxyMessageToPool(meshtastic_MqttClientProxyMessage *p) { mqttClientProxyMessagePool.release(p); } + /// Release the next ClientNotification packet to pool. + void releaseClientNotificationToPool(meshtastic_ClientNotification *p) { clientNotificationPool.release(p); } + /** * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep @@ -134,6 +141,9 @@ class MeshService /// Send an MQTT message to the phone for client proxying void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); + /// Send a ClientNotification to the phone + void sendClientNotification(meshtastic_ClientNotification *cn); + bool isToPhoneQueueEmpty(); ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 3c3668300..5feb1c4bf 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -66,6 +66,9 @@ class PhoneAPI // Keep MqttClientProxyMessage packet just as packetForPhone meshtastic_MqttClientProxyMessage *mqttClientProxyMessageForPhone = NULL; + // Keep ClientNotification packet just as packetForPhone + meshtastic_ClientNotification *clientNotification = NULL; + /// We temporarily keep the nodeInfo here between the call to available and getFromRadio meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 79095805d..f59d61ea2 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -2,6 +2,7 @@ #include "Channels.h" #include "CryptoEngine.h" #include "MeshRadio.h" +#include "MeshService.h" #include "NodeDB.h" #include "RTC.h" #include "configuration.h" @@ -209,6 +210,13 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) #ifdef DEBUG_PORT uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes); + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->has_reply_id = true; + cn->reply_id = p->id; + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d minutes.", silentMinutes); + service->sendClientNotification(cn); #endif meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh From a767997cea4d940d3f4e6418145bab66b3b551e0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 08:57:37 -0500 Subject: [PATCH 0816/1377] Get in the trunk! --- src/mesh/MeshService.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index d05f43b01..ac97d51a7 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -59,7 +59,8 @@ Allocator &queueStatusPool = staticQueueStatusPool; #include "Router.h" MeshService::MeshService() - : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) + : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), + toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) { lastQueueStatus = {0, 0, 16, 0}; } From 8daebf80dd958e55ea3dae22a32ceb13f8aa1463 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Sat, 10 Aug 2024 19:32:52 +0200 Subject: [PATCH 0817/1377] Fix warning: extra tokens at end of #endif directive. (#4432) --- src/modules/AdminModule.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 778b7193d..25450992b 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -828,7 +828,8 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r conn.serial.is_connected = powerFSM.getState() == &stateSERIAL; #else conn.serial.is_connected = powerFSM.getState(); -#endif conn.serial.baud = SERIAL_BAUD; +#endif + conn.serial.baud = SERIAL_BAUD; r.get_device_connection_status_response = conn; r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag; From 74afd131712ac8ae2988b9a74f5a9e39867b3e86 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 13:45:41 -0500 Subject: [PATCH 0818/1377] Re-implement PKI from #1509 (#4379) * Re-implement PKI from #1509 co-authored-by: edinnen * Set the key lengnth to actually make PKI work. * Remove unused variable and initialize keys to null * move printBytes() to meshUtils * Don't reset PKI key son reboot unless needed. * Remove double encryption for PKI messages * Cleanup encrypt logic * Add the MESHTASTIC_EXCLUDE_PKI option, and set it for minimal builds. Required for STM32 targets for now. * Use SHA-256 for PKI key hashing, and add MESHTASTIC_EXCLUDE_PKI_KEYGEN for STM32 * Fix a crash when node is null * Don't send PKI encrypted packets while licensed * use chIndex 8 for PKI * Don't be so clever, that you corrupt incoming packets * Pass on channel 8 for now * Typo * Lock keys once non-zero * We in fact need 2 scratch buffers, to store the encrypted bytes, unencrypted bytes, and decoded protobuf. * Lighter approach to retaining known key * Attach the public key to PKI decrypted packets in device memory * Turn PKI back off for STM32 :( * Don't just memcp over a protobuf * Don't PKI encrypt nodeinfo packets * Add a bit more memory logging around nodeDB * Use the proper macro to refer to NODENUM_BROADCAST * Typo fix * Don't PKI encrypt ROUTING (naks and acks) * Adds SecurityConfig protobuf * Add admin messages over PKI * Disable PKI for the WIO-e5 * Add MINIMUM_SAFE_FREE_HEAP macro and set to safe 1.5k * Add missed "has_security" * Add the admin_channel_enabled option * STM32 again * add missed configuration.h at the top of files * Add EXCLUDE_TZ and RTC * Enable PKI build on STM32 once again * Attempt 1 at moving PKI to aes-ccm * Fix buffers for encrypt/decrypt * Eliminate unused aes variable * Add debugging lines * Set hash to 0 for PKI * Fix debug lines so they don't print pointers. * logic fix and more debug * Rather important typo * Check for short packets before attempting decrypt * Don't forget to give cryptoEngine the keys! * Use the right scratch buffer * Cleanup * moar cleanups * Minor hardening * Remove some in-progress stuff * Turn PKI back off on STM32 * Return false * 2.5 protos * Sync up protos * Add initial cryptography test vector tests * re-add MINIMUM_SAFE_FREE_HEAP * Housekeeping and comment fixes * Add explanatory comment about weak dh25519 keys --------- Co-authored-by: Ben Meadors --- arch/esp32/esp32.ini | 1 + arch/nrf52/nrf52.ini | 1 + platformio.ini | 1 + src/RedirectablePrint.cpp | 4 +- src/SerialConsole.cpp | 4 +- src/configuration.h | 5 + src/main.cpp | 9 +- src/mesh/CryptoEngine.cpp | 170 ++++++++++++++++++++++++++++++++ src/mesh/CryptoEngine.h | 31 +++++- src/mesh/NodeDB.cpp | 50 +++++++++- src/mesh/NodeDB.h | 2 +- src/mesh/PhoneAPI.cpp | 4 + src/mesh/Router.cpp | 173 +++++++++++++++++++++------------ src/mesh/aes-ccm.cpp | 157 ++++++++++++++++++++++++++++++ src/mesh/aes-ccm.h | 10 ++ src/meshUtils.h | 4 +- src/modules/AdminModule.cpp | 41 +++++++- test/test_crypto/test_main.cpp | 50 ++++++++++ userPrefs.h | 6 ++ 19 files changed, 643 insertions(+), 80 deletions(-) create mode 100644 src/mesh/aes-ccm.cpp create mode 100644 src/mesh/aes-ccm.h create mode 100644 test/test_crypto/test_main.cpp diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 58c1302da..0dd6cbc1d 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -48,6 +48,7 @@ lib_deps = https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f + rweather/Crypto@^0.4.0 lib_ignore = segger_rtt diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 1a371e920..762c81d41 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -16,6 +16,7 @@ build_src_filter = lib_deps= ${arduino_base.lib_deps} + rweather/Crypto@^0.4.0 lib_ignore = BluetoothOTA \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index e60f0d7b9..5ad7d60a2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,6 +45,7 @@ extra_configs = variants/*/platformio.ini [env] +test_build_src = true extra_scripts = bin/platformio-custom.py ; note: we add src to our include search path so that lmic_project_config can override diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 05d349de9..02cd8b309 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -39,7 +39,7 @@ size_t RedirectablePrint::write(uint8_t c) SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif - if (!config.has_lora || config.device.serial_enabled) + if (!config.has_lora || config.security.serial_enabled) dest->write(c); return 1; // We always claim one was written, rather than trusting what the @@ -180,7 +180,7 @@ void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) { #if !MESHTASTIC_EXCLUDE_BLUETOOTH - if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { + if (config.security.bluetooth_logging_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index d25b81da7..b911e15da 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -83,7 +83,7 @@ bool SerialConsole::checkIsConnected() bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled. - if (config.has_lora && config.device.serial_enabled) { + if (config.has_lora && config.security.serial_enabled) { // Switch to protobufs for log messages usingProtobufs = true; canWrite = true; @@ -96,7 +96,7 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) { - if (usingProtobufs && config.device.debug_log_enabled) { + if (usingProtobufs && config.security.debug_log_api_enabled) { meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset switch (logLevel[0]) { case 'D': diff --git a/src/configuration.h b/src/configuration.h index 9148f1d37..c9a5d7fb0 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -193,6 +193,10 @@ along with this program. If not, see . #define DEFAULT_SHUTDOWN_SECONDS 2 #endif +#ifndef MINIMUM_SAFE_FREE_HEAP +#define MINIMUM_SAFE_FREE_HEAP 1500 +#endif + /* Step #3: mop up with disabled values for HAS_ options not handled by the above two */ #ifndef HAS_WIFI @@ -256,6 +260,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_MQTT 1 #define MESHTASTIC_EXCLUDE_POWERMON 1 #define MESHTASTIC_EXCLUDE_I2C 1 +#define MESHTASTIC_EXCLUDE_PKI 1 #define MESHTASTIC_EXCLUDE_POWER_FSM 1 #define MESHTASTIC_EXCLUDE_TZ 1 #endif diff --git a/src/main.cpp b/src/main.cpp index 0a3c1ae0b..1c4a64843 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -227,7 +227,7 @@ void printInfo() { LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION)); } - +#ifndef PIO_UNIT_TESTING void setup() { concurrency::hasBeenSetup = true; @@ -1034,7 +1034,7 @@ void setup() powerFSMthread = new PowerFSMThread(); setCPUFast(false); // 80MHz is fine for our slow peripherals } - +#endif uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes) uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client) @@ -1057,7 +1057,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled; return deviceMetadata; } - +#ifndef PIO_UNIT_TESTING void loop() { runASAP = false; @@ -1102,4 +1102,5 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 1e44cb9b7..677667aef 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,6 +1,176 @@ #include "CryptoEngine.h" +#include "NodeDB.h" +#include "RadioInterface.h" #include "configuration.h" +#if !(MESHTASTIC_EXCLUDE_PKI) +#include "aes-ccm.h" +#include "meshUtils.h" +#include +#include +#include +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) +/** + * Create a public/private key pair with Curve25519. + * + * @param pubKey The destination for the public key. + * @param privKey The destination for the private key. + */ +void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey) +{ + LOG_DEBUG("Generating Curve25519 key pair...\n"); + Curve25519::dh1(public_key, private_key); + memcpy(pubKey, public_key, sizeof(public_key)); + memcpy(privKey, private_key, sizeof(private_key)); +} +#endif +uint8_t shared_key[32]; +void CryptoEngine::clearKeys() +{ + memset(public_key, 0, sizeof(public_key)); + memset(private_key, 0, sizeof(private_key)); +} + +/** + * Encrypt a packet's payload using a key generated with Curve25519 and SHA256 + * for a specific node. + * + * @param bytes is updated in place + */ +bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, + uint8_t *bytesOut) +{ + uint8_t *auth; + auth = bytesOut + numBytes; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); + if (node->num < 1 || node->user.public_key.size == 0) { + LOG_DEBUG("Node %d or their public_key not found\n", toNode); + return false; + } + if (!crypto->setDHKey(toNode)) { + return false; + } + initNonce(fromNode, packetNum); + + // Calculate the shared secret with the destination node and encrypt + printBytes("Attempting encrypt using nonce: ", nonce, 16); + printBytes("Attempting encrypt using shared_key: ", shared_key, 32); + aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); + return true; +} + +/** + * Decrypt a packet's payload using a key generated with Curve25519 and SHA256 + * for a specific node. + * + * @param bytes is updated in place + */ +bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) +{ + uint8_t *auth; // set to last 8 bytes of text? + auth = bytes + numBytes - 8; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); + + if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) { + LOG_DEBUG("Node or its public key not found in database\n"); + return false; + } + + // Calculate the shared secret with the sending node and decrypt + if (!crypto->setDHKey(fromNode)) { + return false; + } + initNonce(fromNode, packetNum); + printBytes("Attempting decrypt using nonce: ", nonce, 16); + printBytes("Attempting decrypt using shared_key: ", shared_key, 32); + return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 8, nullptr, 0, auth, bytesOut); +} + +void CryptoEngine::setPrivateKey(uint8_t *_private_key) +{ + memcpy(private_key, _private_key, 32); +} +/** + * Set the PKI key used for encrypt, decrypt. + * + * @param nodeNum the node number of the node who's public key we want to use + */ +bool CryptoEngine::setDHKey(uint32_t nodeNum) +{ + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum); + if (node->num < 1 || node->user.public_key.size == 0) { + LOG_DEBUG("Node %d or their public_key not found\n", nodeNum); + return false; + } + + uint8_t *pubKey = node->user.public_key.bytes; + uint8_t local_priv[32]; + memcpy(shared_key, pubKey, 32); + memcpy(local_priv, private_key, 32); + // Calculate the shared secret with the specified node's public key and our private key + // This includes an internal weak key check, which among other things looks for an all 0 public key and shared key. + if (!Curve25519::dh2(shared_key, local_priv)) { + LOG_WARN("Curve25519DH step 2 failed!\n"); + return false; + } + + printBytes("DH Output: ", shared_key, 32); + + /** + * D.J. Bernstein reccomends hashing the shared key. We want to do this because there are + * at least 128 bits of entropy in the 256-bit output of the DH key exchange, but we don't + * really know where. If you extract, for instance, the first 128 bits with basic truncation, + * then you don't know if you got all of your 128 entropy bits, or less, possibly much less. + * + * No exploitable bias is really known at that point, but we know enough to be wary. + * Hashing the DH output is a simple and safe way to gather all the entropy and spread + * it around as needed. + */ + crypto->hash(shared_key, 32); + return true; +} + +/** + * Hash arbitrary data using SHA256. + * + * @param bytes + * @param numBytes + */ +void CryptoEngine::hash(uint8_t *bytes, size_t numBytes) +{ + SHA256 hash; + size_t posn, len; + uint8_t size = numBytes; + uint8_t inc = 16; + hash.reset(); + for (posn = 0; posn < size; posn += inc) { + len = size - posn; + if (len > inc) + len = inc; + hash.update(bytes + posn, len); + } + hash.finalize(bytes, 32); +} + +void CryptoEngine::aesSetKey(const uint8_t *key_bytes, size_t key_len) +{ + if (aes) { + delete aes; + aes = nullptr; + } + if (key_len != 0) { + aes = new AESSmall256(); + aes->setKey(key_bytes, key_len); + } +} + +void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out) +{ + aes->encryptBlock(out, in); +} + +#endif + concurrency::Lock *cryptLock; void CryptoEngine::setKey(const CryptoKey &k) diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 2737dab2d..51080fd59 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -1,6 +1,8 @@ #pragma once - +#include "AES.h" #include "concurrency/LockGuard.h" +#include "configuration.h" +#include "mesh-pb-constants.h" #include extern concurrency::Lock *cryptLock; @@ -26,9 +28,34 @@ class CryptoEngine uint8_t nonce[16] = {0}; CryptoKey key = {}; +#if !(MESHTASTIC_EXCLUDE_PKI) + uint8_t private_key[32] = {0}; +#endif public: +#if !(MESHTASTIC_EXCLUDE_PKI) + uint8_t public_key[32] = {0}; +#endif + virtual ~CryptoEngine() {} +#if !(MESHTASTIC_EXCLUDE_PKI) +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey); +#endif + void clearKeys(); + void setPrivateKey(uint8_t *_private_key); + virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, + uint8_t *bytesOut); + virtual bool decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); + virtual bool setDHKey(uint32_t nodeNum); + virtual void hash(uint8_t *bytes, size_t numBytes); + + virtual void aesSetKey(const uint8_t *key, size_t key_len); + + virtual void aesEncrypt(uint8_t *in, uint8_t *out); + AESSmall256 *aes = NULL; + +#endif /** * Set the key used for encrypt, decrypt. @@ -61,4 +88,4 @@ class CryptoEngine void initNonce(uint32_t fromNode, uint64_t packetId); }; -extern CryptoEngine *crypto; +extern CryptoEngine *crypto; \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0f6c444ce..3400529ab 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -19,6 +19,7 @@ #include "error.h" #include "main.h" #include "mesh-pb-constants.h" +#include "meshUtils.h" #include "modules/NeighborInfoModule.h" #include #include @@ -144,6 +145,31 @@ NodeDB::NodeDB() // Include our owner in the node db under our nodenum meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); +#if !(MESHTASTIC_EXCLUDE_PKI) + // Calculate Curve25519 public and private keys + printBytes("Old Pubkey", config.security.public_key.bytes, 32); + if (config.security.private_key.size == 32 && config.security.public_key.size == 32) { + LOG_INFO("Using saved PKI keys\n"); + owner.public_key.size = config.security.public_key.size; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); + crypto->setPrivateKey(config.security.private_key.bytes); + } else { +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + LOG_INFO("Generating new PKI keys\n"); + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + config.security.public_key.size = 32; + config.security.private_key.size = 32; + + printBytes("New Pubkey", config.security.public_key.bytes, 32); + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); +#else + LOG_INFO("No PKI keys set, and generation disabled!\n"); +#endif + } + +#endif + info->user = owner; info->has_user = true; @@ -258,6 +284,7 @@ void NodeDB::installDefaultConfig() config.has_power = true; config.has_network = true; config.has_bluetooth = (HAS_BLUETOOTH ? true : false); + config.has_security = true; config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; config.lora.sx126x_rx_boosted_gain = true; @@ -280,6 +307,14 @@ void NodeDB::installDefaultConfig() #else config.lora.ignore_mqtt = false; #endif +#ifdef ADMIN_KEY_USERPREFS + memcpy(config.security.admin_key.bytes, admin_key_userprefs, 32); + config.security.admin_key.size = 32; +#else + config.security.admin_key.size = 0; +#endif + config.security.public_key.size = 0; + config.security.private_key.size = 0; #ifdef PIN_GPS_EN config.position.gps_en_gpio = PIN_GPS_EN; #endif @@ -303,7 +338,8 @@ void NodeDB::installDefaultConfig() config.position.broadcast_smart_minimum_interval_secs = 30; if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER) config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; - config.device.serial_enabled = true; + config.security.serial_enabled = true; + config.security.admin_channel_enabled = false; resetRadioConfig(); strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); // FIXME: Default to bluetooth capability of platform as default @@ -796,6 +832,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) config.has_power = true; config.has_network = true; config.has_bluetooth = true; + config.has_security = true; success &= saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); } @@ -975,7 +1012,7 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS /** Update user info and channel for this node based on received user data */ -bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex) +bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex) { meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); if (!info) { @@ -983,6 +1020,12 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t chann } LOG_DEBUG("old user %s/%s/%s, channel=%d\n", info->user.id, info->user.long_name, info->user.short_name, info->channel); +#if !(MESHTASTIC_EXCLUDE_PKI) + if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one + printBytes("Retaining Old Pubkey: ", info->user.public_key.bytes, 32); + memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); + } +#endif // Both of info->user and p start as filled with zero so I think this is okay bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); @@ -1060,7 +1103,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) meshtastic_NodeInfoLite *lite = getMeshNode(n); if (!lite) { - if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { + if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP)) { if (screen) screen->print("Warn: node database full!\nErasing oldest entry\n"); LOG_WARN("Node database full with %i nodes and %i bytes free! Erasing oldest entry\n", numMeshNodes, @@ -1086,6 +1129,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) // everything is missing except the nodenum memset(lite, 0, sizeof(*lite)); lite->num = n; + LOG_INFO("Adding node to database with %i nodes and %i bytes free!\n", numMeshNodes, memGet.getFreeHeap()); } return lite; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 447ce10d4..a71f3e134 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -98,7 +98,7 @@ class NodeDB /** Update user info and channel for this node based on received user data */ - bool updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex = 0); + bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0); /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index fc0099e87..0a9bb5b10 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -255,6 +255,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.config.which_payload_variant = meshtastic_Config_bluetooth_tag; fromRadioScratch.config.payload_variant.bluetooth = config.bluetooth; break; + case meshtastic_Config_security_tag: + fromRadioScratch.config.which_payload_variant = meshtastic_Config_security_tag; + fromRadioScratch.config.payload_variant.security = config.security; + break; default: LOG_ERROR("Unknown config type %d\n", config_state); } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f59d61ea2..1fecef6d7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -37,6 +37,7 @@ static MemoryDynamic staticPool; Allocator &packetPool = staticPool; static uint8_t bytes[MAX_RHPACKETLEN]; +static uint8_t ScratchEncrypted[MAX_RHPACKETLEN]; /** * Constructor @@ -307,70 +308,105 @@ bool perhapsDecode(meshtastic_MeshPacket *p) if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) return true; // If packet was already decoded just return - // assert(p->which_payloadVariant == MeshPacket_encrypted_tag); + size_t rawSize = p->encrypted.size; + if (rawSize > sizeof(bytes)) { + LOG_ERROR("Packet too large to attempt decryption! (rawSize=%d > 256)\n", rawSize); + return false; + } + bool decrypted = false; + ChannelIndex chIndex = 0; + memcpy(bytes, p->encrypted.bytes, + rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf + memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize); +#if !(MESHTASTIC_EXCLUDE_PKI) + // Attempt PKI decryption first + if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && nodeDB->getMeshNode(p->from) != nullptr && + nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && + rawSize > 8) { + LOG_DEBUG("Attempting PKI decryption\n"); - // Try to find a channel that works with this hash - for (ChannelIndex chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) { - // Try to use this hash/channel pair - if (channels.decryptForHash(chIndex, p->channel)) { - // Try to decrypt the packet if we can - size_t rawSize = p->encrypted.size; - if (rawSize > sizeof(bytes)) { - LOG_ERROR("Packet too large to attempt decription! (rawSize=%d > 256)\n", rawSize); - return false; - } - memcpy(bytes, p->encrypted.bytes, - rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf - crypto->decrypt(p->from, p->id, rawSize, bytes); - - // printBytes("plaintext", bytes, p->encrypted.size); - - // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) { + LOG_INFO("PKI Decryption worked!\n"); memset(&p->decoded, 0, sizeof(p->decoded)); - if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded)) { - LOG_ERROR("Invalid protobufs in received mesh packet (bad psk?)!\n"); - } else if (p->decoded.portnum == meshtastic_PortNum_UNKNOWN_APP) { - LOG_ERROR("Invalid portnum (bad psk?)!\n"); + rawSize -= 8; + if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded) && + p->decoded.portnum != meshtastic_PortNum_UNKNOWN_APP) { + decrypted = true; + LOG_INFO("Packet decrypted using PKI!\n"); + p->pki_encrypted = true; + memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32); + p->public_key.size = 32; + // memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers + // chIndex = 8; } else { - // parsing was successful - p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded - p->channel = chIndex; // change to store the index instead of the hash - - /* Not actually ever used. - // Decompress if needed. jm - if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) { - // Decompress the payload - char compressed_in[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; - char decompressed_out[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; - int decompressed_len; - - memcpy(compressed_in, p->decoded.payload.bytes, p->decoded.payload.size); - - decompressed_len = unishox2_decompress_simple(compressed_in, p->decoded.payload.size, decompressed_out); - - // LOG_DEBUG("\n\n**\n\nDecompressed length - %d \n", decompressed_len); - - memcpy(p->decoded.payload.bytes, decompressed_out, decompressed_len); - - // Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - } */ - - printPacket("decoded message", p); -#if ENABLE_JSON_LOGGING - LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); -#elif ARCH_PORTDUINO - if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { - LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); - } -#endif - return true; + return false; } } } +#endif - LOG_WARN("No suitable channel found for decoding, hash was 0x%x!\n", p->channel); - return false; + // assert(p->which_payloadVariant == MeshPacket_encrypted_tag); + if (!decrypted) { + // Try to find a channel that works with this hash + for (chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) { + // Try to use this hash/channel pair + if (channels.decryptForHash(chIndex, p->channel)) { + // Try to decrypt the packet if we can + crypto->decrypt(p->from, p->id, rawSize, bytes); + + // printBytes("plaintext", bytes, p->encrypted.size); + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + memset(&p->decoded, 0, sizeof(p->decoded)); + if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded)) { + LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!\n", p->id); + } else if (p->decoded.portnum == meshtastic_PortNum_UNKNOWN_APP) { + LOG_ERROR("Invalid portnum (bad psk?)!\n"); + } else { + decrypted = true; + break; + } + } + } + } + if (decrypted) { + // parsing was successful + p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded + p->channel = chIndex; // change to store the index instead of the hash + + /* Not actually ever used. + // Decompress if needed. jm + if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) { + // Decompress the payload + char compressed_in[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; + char decompressed_out[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; + int decompressed_len; + + memcpy(compressed_in, p->decoded.payload.bytes, p->decoded.payload.size); + + decompressed_len = unishox2_decompress_simple(compressed_in, p->decoded.payload.size, decompressed_out); + + // LOG_DEBUG("\n\n**\n\nDecompressed length - %d \n", decompressed_len); + + memcpy(p->decoded.payload.bytes, decompressed_out, decompressed_len); + + // Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + } */ + + printPacket("decoded message", p); +#if ENABLE_JSON_LOGGING + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); +#elif ARCH_PORTDUINO + if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); + } +#endif + return true; + } else { + LOG_WARN("No suitable channel found for decoding, hash was 0x%x!\n", p->channel); + return false; + } } /** Return 0 for success or a Routing_Errror code for failure @@ -384,7 +420,6 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); /* Not actually used, so save the cycles - // Only allow encryption on the text message app. // TODO: Allow modules to opt into compression. if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) { @@ -432,10 +467,28 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; +#if !(MESHTASTIC_EXCLUDE_PKI) + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); + if (!owner.is_licensed && p->to != NODENUM_BROADCAST && node != nullptr && node->user.public_key.size > 0 && + p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && + p->decoded.portnum != meshtastic_PortNum_ROUTING_APP) { // TODO: check for size due to 8 byte tag + LOG_DEBUG("Using PKI!\n"); + if (numbytes + 8 > MAX_RHPACKETLEN) + return meshtastic_Routing_Error_TOO_LARGE; + crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); + numbytes += 8; + memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); + p->channel = 0; + } else { + crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + memcpy(p->encrypted.bytes, bytes, numbytes); + } +#else crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + memcpy(p->encrypted.bytes, bytes, numbytes); +#endif // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); p->encrypted.size = numbytes; p->which_payload_variant = meshtastic_MeshPacket_encrypted_tag; } @@ -539,4 +592,4 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) handleReceived(p); packetPool.release(p); -} +} \ No newline at end of file diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp new file mode 100644 index 000000000..cd18ae6c5 --- /dev/null +++ b/src/mesh/aes-ccm.cpp @@ -0,0 +1,157 @@ +/* + * Counter with CBC-MAC (CCM) with AES + * + * Copyright (c) 2010-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +#define AES_BLOCK_SIZE 16 +#include "aes-ccm.h" +#if !MESHTASTIC_EXCLUDE_PKI + +static inline void WPA_PUT_BE16(uint8_t *a, uint16_t val) +{ + a[0] = val >> 8; + a[1] = val & 0xff; +} + +static void xor_aes_block(uint8_t *dst, const uint8_t *src) +{ + uint32_t *d = (uint32_t *)dst; + uint32_t *s = (uint32_t *)src; + *d++ ^= *s++; + *d++ ^= *s++; + *d++ ^= *s++; + *d++ ^= *s++; +} +static void aes_ccm_auth_start(size_t M, size_t L, const uint8_t *nonce, const uint8_t *aad, size_t aad_len, size_t plain_len, + uint8_t *x) +{ + uint8_t aad_buf[2 * AES_BLOCK_SIZE]; + uint8_t b[AES_BLOCK_SIZE]; + /* Authentication */ + /* B_0: Flags | Nonce N | l(m) */ + b[0] = aad_len ? 0x40 : 0 /* Adata */; + b[0] |= (((M - 2) / 2) /* M' */ << 3); + b[0] |= (L - 1) /* L' */; + memcpy(&b[1], nonce, 15 - L); + WPA_PUT_BE16(&b[AES_BLOCK_SIZE - L], plain_len); + crypto->aesEncrypt(b, x); /* X_1 = E(K, B_0) */ + if (!aad_len) + return; + WPA_PUT_BE16(aad_buf, aad_len); + memcpy(aad_buf + 2, aad, aad_len); + memset(aad_buf + 2 + aad_len, 0, sizeof(aad_buf) - 2 - aad_len); + xor_aes_block(aad_buf, x); + crypto->aesEncrypt(aad_buf, x); /* X_2 = E(K, X_1 XOR B_1) */ + if (aad_len > AES_BLOCK_SIZE - 2) { + xor_aes_block(&aad_buf[AES_BLOCK_SIZE], x); + /* X_3 = E(K, X_2 XOR B_2) */ + crypto->aesEncrypt(&aad_buf[AES_BLOCK_SIZE], x); + } +} +static void aes_ccm_auth(const uint8_t *data, size_t len, uint8_t *x) +{ + size_t last = len % AES_BLOCK_SIZE; + size_t i; + for (i = 0; i < len / AES_BLOCK_SIZE; i++) { + /* X_i+1 = E(K, X_i XOR B_i) */ + xor_aes_block(x, data); + data += AES_BLOCK_SIZE; + crypto->aesEncrypt(x, x); + } + if (last) { + /* XOR zero-padded last block */ + for (i = 0; i < last; i++) + x[i] ^= *data++; + crypto->aesEncrypt(x, x); + } +} +static void aes_ccm_encr_start(size_t L, const uint8_t *nonce, uint8_t *a) +{ + /* A_i = Flags | Nonce N | Counter i */ + a[0] = L - 1; /* Flags = L' */ + memcpy(&a[1], nonce, 15 - L); +} +static void aes_ccm_encr(size_t L, const uint8_t *in, size_t len, uint8_t *out, uint8_t *a) +{ + size_t last = len % AES_BLOCK_SIZE; + size_t i; + /* crypt = msg XOR (S_1 | S_2 | ... | S_n) */ + for (i = 1; i <= len / AES_BLOCK_SIZE; i++) { + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], i); + /* S_i = E(K, A_i) */ + crypto->aesEncrypt(a, out); + xor_aes_block(out, in); + out += AES_BLOCK_SIZE; + in += AES_BLOCK_SIZE; + } + if (last) { + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], i); + crypto->aesEncrypt(a, out); + /* XOR zero-padded last block */ + for (i = 0; i < last; i++) + *out++ ^= *in++; + } +} +static void aes_ccm_encr_auth(size_t M, uint8_t *x, uint8_t *a, uint8_t *auth) +{ + size_t i; + uint8_t tmp[AES_BLOCK_SIZE]; + /* U = T XOR S_0; S_0 = E(K, A_0) */ + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], 0); + crypto->aesEncrypt(a, tmp); + for (i = 0; i < M; i++) + auth[i] = x[i] ^ tmp[i]; +} +static void aes_ccm_decr_auth(size_t M, uint8_t *a, const uint8_t *auth, uint8_t *t) +{ + size_t i; + uint8_t tmp[AES_BLOCK_SIZE]; + /* U = T XOR S_0; S_0 = E(K, A_0) */ + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], 0); + crypto->aesEncrypt(a, tmp); + for (i = 0; i < M; i++) + t[i] = auth[i] ^ tmp[i]; +} +/* AES-CCM with fixed L=2 and aad_len <= 30 assumption */ +int aes_ccm_ae(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *plain, size_t plain_len, + const uint8_t *aad, size_t aad_len, uint8_t *crypt, uint8_t *auth) +{ + const size_t L = 2; + uint8_t x[AES_BLOCK_SIZE], a[AES_BLOCK_SIZE]; + if (aad_len > 30 || M > AES_BLOCK_SIZE) + return -1; + crypto->aesSetKey(key, key_len); + aes_ccm_auth_start(M, L, nonce, aad, aad_len, plain_len, x); + aes_ccm_auth(plain, plain_len, x); + /* Encryption */ + aes_ccm_encr_start(L, nonce, a); + aes_ccm_encr(L, plain, plain_len, crypt, a); + aes_ccm_encr_auth(M, x, a, auth); + return 0; +} +/* AES-CCM with fixed L=2 and aad_len <= 30 assumption */ +bool aes_ccm_ad(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *crypt, size_t crypt_len, + const uint8_t *aad, size_t aad_len, const uint8_t *auth, uint8_t *plain) +{ + const size_t L = 2; + uint8_t x[AES_BLOCK_SIZE], a[AES_BLOCK_SIZE]; + uint8_t t[AES_BLOCK_SIZE]; + if (aad_len > 30 || M > AES_BLOCK_SIZE) + return false; + crypto->aesSetKey(key, key_len); + /* Decryption */ + aes_ccm_encr_start(L, nonce, a); + aes_ccm_decr_auth(M, a, auth, t); + /* plaintext = msg XOR (S_1 | S_2 | ... | S_n) */ + aes_ccm_encr(L, crypt, crypt_len, plain, a); + aes_ccm_auth_start(M, L, nonce, aad, aad_len, crypt_len, x); + aes_ccm_auth(plain, crypt_len, x); + if (memcmp(x, t, M) != 0) { // FIXME make const comp + return false; + } + return true; +} +#endif \ No newline at end of file diff --git a/src/mesh/aes-ccm.h b/src/mesh/aes-ccm.h new file mode 100644 index 000000000..6b8edcde4 --- /dev/null +++ b/src/mesh/aes-ccm.h @@ -0,0 +1,10 @@ +#pragma once +#include "CryptoEngine.h" +#if !MESHTASTIC_EXCLUDE_PKI + +int aes_ccm_ae(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *plain, size_t plain_len, + const uint8_t *aad, size_t aad_len, uint8_t *crypt, uint8_t *auth); + +bool aes_ccm_ad(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *crypt, size_t crypt_len, + const uint8_t *aad, size_t aad_len, const uint8_t *auth, uint8_t *plain); +#endif \ No newline at end of file diff --git a/src/meshUtils.h b/src/meshUtils.h index 9dfe9b558..e2d4188d7 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -12,4 +12,6 @@ template constexpr const T &clamp(const T &v, const T &lo, const T &hi #define STRNSTR #include char *strnstr(const char *s, const char *find, size_t slen); -#endif \ No newline at end of file +#endif + +void printBytes(const char *label, const uint8_t *p, size_t numbytes); \ No newline at end of file diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 778b7193d..fe426f8f5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -65,7 +65,29 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta bool handled = false; assert(r); bool fromOthers = mp.from != 0 && mp.from != nodeDB->getNodeNum(); - + if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + return handled; + } + meshtastic_Channel *ch = &channels.getByIndex(mp.channel); + // Could tighten this up further by tracking the last poblic_key we went an AdminMessage request to + // and only allowing responses from that remote. + if (!((mp.from == 0 && !config.security.is_managed) || + r->which_payload_variant == meshtastic_AdminMessage_get_channel_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_owner_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_config_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_module_config_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_canned_message_module_messages_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_metadata_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag || + r->which_payload_variant == meshtastic_NodeRemoteHardwarePinsResponse_node_remote_hardware_pins_tag || + (strcasecmp(ch->settings.name, Channels::adminChannel) == 0 && config.security.admin_channel_enabled) || + (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key.bytes, 32) == 0))) { + LOG_INFO("Ignoring admin payload %i\n", r->which_payload_variant); + return handled; + } + LOG_INFO("Handling admin payload %i\n", r->which_payload_variant); switch (r->which_payload_variant) { /** @@ -383,8 +405,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #endif if (config.device.button_gpio == c.payload_variant.device.button_gpio && config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio && - config.device.debug_log_enabled == c.payload_variant.device.debug_log_enabled && - config.device.serial_enabled == c.payload_variant.device.serial_enabled && config.device.role == c.payload_variant.device.role && config.device.disable_triple_click == c.payload_variant.device.disable_triple_click && config.device.rebroadcast_mode == c.payload_variant.device.rebroadcast_mode) { @@ -501,6 +521,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.has_bluetooth = true; config.bluetooth = c.payload_variant.bluetooth; break; + case meshtastic_Config_security_tag: + LOG_INFO("Setting config: Security\n"); + config.security = c.payload_variant.security; + owner.public_key.size = config.security.public_key.size; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); + if (config.security.debug_log_api_enabled == c.payload_variant.security.debug_log_api_enabled && + config.security.serial_enabled == c.payload_variant.security.serial_enabled) + requiresReboot = false; + + break; } saveChanges(changes, requiresReboot); @@ -828,7 +858,8 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r conn.serial.is_connected = powerFSM.getState() == &stateSERIAL; #else conn.serial.is_connected = powerFSM.getState(); -#endif conn.serial.baud = SERIAL_BAUD; +#endif + conn.serial.baud = SERIAL_BAUD; r.get_device_connection_status_response = conn; r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag; @@ -895,5 +926,5 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p) AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_APP, &meshtastic_AdminMessage_msg) { // restrict to the admin channel for rx - boundChannel = Channels::adminChannel; + // boundChannel = Channels::adminChannel; } \ No newline at end of file diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp new file mode 100644 index 000000000..e564d5d0e --- /dev/null +++ b/test/test_crypto/test_main.cpp @@ -0,0 +1,50 @@ +#include "CryptoEngine.h" + +#include + +void setUp(void) +{ + // set stuff up here +} + +void tearDown(void) +{ + // clean stuff up here +} + +void test_SHA256(void) +{ + uint8_t hash2[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + uint8_t hash[32] = {0}; + crypto->hash(hash, 0); + TEST_ASSERT_EQUAL_MEMORY(hash, hash2, 32); +} +void test_ECB_AES256(void) +{ + uint8_t key[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; + uint8_t plain1[] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; + uint8_t scratch[16] = {0}; + + uint8_t cipher1[] = {0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8}; + crypto->aesSetKey(key, 32); + crypto->aesEncrypt(plain1, scratch); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(scratch, cipher1, 16); +} + +void setup() +{ + // NOTE!!! Wait for >2 secs + // if board doesn't support software reset via Serial.DTR/RTS + delay(2000); + + UNITY_BEGIN(); // IMPORTANT LINE! + RUN_TEST(test_SHA256); + RUN_TEST(test_ECB_AES256); +} + +void loop() +{ + UNITY_END(); // stop unit testing +} \ No newline at end of file diff --git a/userPrefs.h b/userPrefs.h index b365e8c6f..5812fa0c8 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -33,4 +33,10 @@ static unsigned char icon_bits[] = { 0x98, 0x3F, 0xF0, 0x23, 0x00, 0xFC, 0x0F, 0xE0, 0x7F, 0x00, 0xFC, 0x03, 0x80, 0xFF, 0x01, 0xFC, 0x00, 0x00, 0x3E, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00}; */ +/* +#define ADMIN_KEY_USERPREFS 1 +static unsigned char admin_key_userprefs[] = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, + 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, + 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c}; +*/ #endif \ No newline at end of file From 8ca884bafd12881e4a16b3b26aab6b671e017a7c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 15:45:29 -0500 Subject: [PATCH 0819/1377] Add DH25519 unit test --- src/DebugConfiguration.h | 2 +- src/mesh/CryptoEngine.cpp | 27 ++++++----- src/mesh/CryptoEngine.h | 25 +++++----- src/mesh/NodeDB.cpp | 2 +- test/test_crypto/test_main.cpp | 89 +++++++++++++++++++++++++++++----- 5 files changed, 109 insertions(+), 36 deletions(-) diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index f5b3fa25a..6f55dfa90 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -45,7 +45,7 @@ #define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else -#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) && !defined(PIO_UNIT_TESTING) #define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__) #define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__) #define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 677667aef..d284f3410 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -24,7 +24,6 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey) memcpy(privKey, private_key, sizeof(private_key)); } #endif -uint8_t shared_key[32]; void CryptoEngine::clearKeys() { memset(public_key, 0, sizeof(public_key)); @@ -86,7 +85,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 8, nullptr, 0, auth, bytesOut); } -void CryptoEngine::setPrivateKey(uint8_t *_private_key) +void CryptoEngine::setDHPrivateKey(uint8_t *_private_key) { memcpy(private_key, _private_key, 32); } @@ -103,16 +102,8 @@ bool CryptoEngine::setDHKey(uint32_t nodeNum) return false; } - uint8_t *pubKey = node->user.public_key.bytes; - uint8_t local_priv[32]; - memcpy(shared_key, pubKey, 32); - memcpy(local_priv, private_key, 32); - // Calculate the shared secret with the specified node's public key and our private key - // This includes an internal weak key check, which among other things looks for an all 0 public key and shared key. - if (!Curve25519::dh2(shared_key, local_priv)) { - LOG_WARN("Curve25519DH step 2 failed!\n"); + if (!setDHPublicKey(node->user.public_key.bytes)) return false; - } printBytes("DH Output: ", shared_key, 32); @@ -171,6 +162,20 @@ void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out) #endif +bool CryptoEngine::setDHPublicKey(uint8_t *pubKey) +{ + uint8_t local_priv[32]; + memcpy(shared_key, pubKey, 32); + memcpy(local_priv, private_key, 32); + // Calculate the shared secret with the specified node's public key and our private key + // This includes an internal weak key check, which among other things looks for an all 0 public key and shared key. + if (!Curve25519::dh2(shared_key, local_priv)) { + LOG_WARN("Curve25519DH step 2 failed!\n"); + return false; + } + return true; +} + concurrency::Lock *cryptLock; void CryptoEngine::setKey(const CryptoKey &k) diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 51080fd59..fd607c29e 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -23,15 +23,6 @@ struct CryptoKey { class CryptoEngine { - protected: - /** Our per packet nonce */ - uint8_t nonce[16] = {0}; - - CryptoKey key = {}; -#if !(MESHTASTIC_EXCLUDE_PKI) - uint8_t private_key[32] = {0}; -#endif - public: #if !(MESHTASTIC_EXCLUDE_PKI) uint8_t public_key[32] = {0}; @@ -43,11 +34,12 @@ class CryptoEngine virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey); #endif void clearKeys(); - void setPrivateKey(uint8_t *_private_key); + void setDHPrivateKey(uint8_t *_private_key); virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); virtual bool decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); - virtual bool setDHKey(uint32_t nodeNum); + bool setDHKey(uint32_t nodeNum); + virtual bool setDHPublicKey(uint8_t *publicKey); virtual void hash(uint8_t *bytes, size_t numBytes); virtual void aesSetKey(const uint8_t *key, size_t key_len); @@ -75,8 +67,17 @@ class CryptoEngine */ virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); - +#ifndef PIO_UNIT_TESTING protected: +#endif + /** Our per packet nonce */ + uint8_t nonce[16] = {0}; + + CryptoKey key = {}; +#if !(MESHTASTIC_EXCLUDE_PKI) + uint8_t shared_key[32] = {0}; + uint8_t private_key[32] = {0}; +#endif /** * Init our 128 bit nonce for a new packet * diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 3400529ab..ac77e7830 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -152,7 +152,7 @@ NodeDB::NodeDB() LOG_INFO("Using saved PKI keys\n"); owner.public_key.size = config.security.public_key.size; memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); - crypto->setPrivateKey(config.security.private_key.bytes); + crypto->setDHPrivateKey(config.security.private_key.bytes); } else { #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) LOG_INFO("Generating new PKI keys\n"); diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index e564d5d0e..e9aee928e 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -2,6 +2,18 @@ #include +void HexToBytes(uint8_t *result, const std::string hex, size_t len = 0) +{ + if (len) { + memset(result, 0, len); + } + for (unsigned int i = 0; i < hex.length(); i += 2) { + std::string byteString = hex.substr(i, 2); + result[i / 2] = (uint8_t)strtol(byteString.c_str(), NULL, 16); + } + return; +} + void setUp(void) { // set stuff up here @@ -14,25 +26,79 @@ void tearDown(void) void test_SHA256(void) { - uint8_t hash2[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, - 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + uint8_t expected[32]; uint8_t hash[32] = {0}; + + HexToBytes(expected, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); crypto->hash(hash, 0); - TEST_ASSERT_EQUAL_MEMORY(hash, hash2, 32); + TEST_ASSERT_EQUAL_MEMORY(hash, expected, 32); + + HexToBytes(hash, "d3", 32); + HexToBytes(expected, "28969cdfa74a12c82f3bad960b0b000aca2ac329deea5c2328ebc6f2ba9802c1"); + crypto->hash(hash, 1); + TEST_ASSERT_EQUAL_MEMORY(hash, expected, 32); + + HexToBytes(hash, "11af", 32); + HexToBytes(expected, "5ca7133fa735326081558ac312c620eeca9970d1e70a4b95533d956f072d1f98"); + crypto->hash(hash, 2); + TEST_ASSERT_EQUAL_MEMORY(hash, expected, 32); } void test_ECB_AES256(void) { - uint8_t key[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, - 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; - uint8_t plain1[] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; - uint8_t scratch[16] = {0}; + // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ECB.pdf - uint8_t cipher1[] = {0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8}; + uint8_t key[32] = {0}; + uint8_t plain[16] = {0}; + uint8_t result[16] = {0}; + uint8_t expected[16] = {0}; + + HexToBytes(key, "603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4"); + + HexToBytes(plain, "6BC1BEE22E409F96E93D7E117393172A"); + HexToBytes(expected, "F3EED1BDB5D2A03C064B5A7E3DB181F8"); crypto->aesSetKey(key, 32); - crypto->aesEncrypt(plain1, scratch); // Does 16 bytes at a time - TEST_ASSERT_EQUAL_MEMORY(scratch, cipher1, 16); -} + crypto->aesEncrypt(plain, result); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(expected, result, 16); + HexToBytes(plain, "AE2D8A571E03AC9C9EB76FAC45AF8E51"); + HexToBytes(expected, "591CCB10D410ED26DC5BA74A31362870"); + crypto->aesSetKey(key, 32); + crypto->aesEncrypt(plain, result); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(expected, result, 16); + + HexToBytes(plain, "30C81C46A35CE411E5FBC1191A0A52EF"); + HexToBytes(expected, "B6ED21B99CA6F4F9F153E7B1BEAFED1D"); + crypto->aesSetKey(key, 32); + crypto->aesEncrypt(plain, result); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(expected, result, 16); +} +void test_DH25519(void) +{ + // test vectors from wycheproof x25519 + // https://github.com/C2SP/wycheproof/blob/master/testvectors/x25519_test.json + uint8_t private_key[32]; + uint8_t public_key[32]; + uint8_t expected_shared[32]; + + HexToBytes(public_key, "504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); + HexToBytes(private_key, "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475"); + HexToBytes(expected_shared, "436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(crypto->setDHPublicKey(public_key)); + TEST_ASSERT_EQUAL_MEMORY(expected_shared, crypto->shared_key, 32); + + HexToBytes(public_key, "63aa40c6e38346c5caf23a6df0a5e6c80889a08647e551b3563449befcfc9733"); + HexToBytes(private_key, "d85d8c061a50804ac488ad774ac716c3f5ba714b2712e048491379a500211958"); + HexToBytes(expected_shared, "279df67a7c4611db4708a0e8282b195e5ac0ed6f4b2f292c6fbd0acac30d1332"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(crypto->setDHPublicKey(public_key)); + TEST_ASSERT_EQUAL_MEMORY(expected_shared, crypto->shared_key, 32); + + HexToBytes(public_key, "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"); + HexToBytes(private_key, "18630f93598637c35da623a74559cf944374a559114c7937811041fc8605564a"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(!crypto->setDHPublicKey(public_key)); // Weak public key results in 0 shared key +} void setup() { // NOTE!!! Wait for >2 secs @@ -42,6 +108,7 @@ void setup() UNITY_BEGIN(); // IMPORTANT LINE! RUN_TEST(test_SHA256); RUN_TEST(test_ECB_AES256); + RUN_TEST(test_DH25519); } void loop() From b573e0eacc1683a5ef810ad4601ec106ce7ddeb1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 20:04:38 -0500 Subject: [PATCH 0820/1377] Fix compile on STM32 --- src/mesh/CryptoEngine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index d284f3410..fd7246fa9 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -160,8 +160,6 @@ void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out) aes->encryptBlock(out, in); } -#endif - bool CryptoEngine::setDHPublicKey(uint8_t *pubKey) { uint8_t local_priv[32]; @@ -176,6 +174,7 @@ bool CryptoEngine::setDHPublicKey(uint8_t *pubKey) return true; } +#endif concurrency::Lock *cryptLock; void CryptoEngine::setKey(const CryptoKey &k) From 1cfd5d12d2daa1237835aa67a0520598c8b7b83e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 22:38:05 -0500 Subject: [PATCH 0821/1377] Refactor platform cryptography, add tests --- src/mesh/CryptoEngine.cpp | 42 +++++++++- src/mesh/CryptoEngine.h | 6 +- src/mesh/Router.cpp | 2 +- src/platform/esp32/ESP32CryptoEngine.cpp | 41 ++-------- src/platform/esp32/architecture.h | 3 + src/platform/nrf52/NRF52CryptoEngine.cpp | 29 ++----- src/platform/nrf52/architecture.h | 3 + .../portduino/CrossPlatformCryptoEngine.cpp | 78 ------------------- src/platform/rp2040/rp2040CryptoEngine.cpp | 66 ---------------- src/platform/stm32wl/STM32WLCryptoEngine.cpp | 67 ---------------- test/test_crypto/test_main.cpp | 27 +++++++ 11 files changed, 88 insertions(+), 276 deletions(-) delete mode 100644 src/platform/portduino/CrossPlatformCryptoEngine.cpp delete mode 100644 src/platform/rp2040/rp2040CryptoEngine.cpp delete mode 100644 src/platform/stm32wl/STM32WLCryptoEngine.cpp diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index fd7246fa9..e83236eab 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,6 +1,7 @@ #include "CryptoEngine.h" #include "NodeDB.h" #include "RadioInterface.h" +#include "architecture.h" #include "configuration.h" #if !(MESHTASTIC_EXCLUDE_PKI) @@ -188,14 +189,44 @@ void CryptoEngine::setKey(const CryptoKey &k) * * @param bytes is updated in place */ -void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) +void CryptoEngine::encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) { - LOG_WARN("noop encryption!\n"); + if (key.length > 0) { + initNonce(fromNode, packetId); + if (numBytes <= MAX_BLOCKSIZE) { + encryptAESCtr(key, nonce, numBytes, bytes); + } else { + LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); + } + } } void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) { - LOG_WARN("noop decryption!\n"); + // For CTR, the implementation is the same + encryptPacket(fromNode, packetId, numBytes, bytes); +} + +// Generic implementation of AES-CTR encryption. +void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) +{ + if (ctr) { + delete ctr; + ctr = nullptr; + } + if (_key.length == 16) + ctr = new CTR(); + else + ctr = new CTR(); + ctr->setKey(_key.bytes, _key.length); + static uint8_t scratch[MAX_BLOCKSIZE]; + memcpy(scratch, bytes, numBytes); + memset(scratch + numBytes, 0, + sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) + + ctr->setIV(_nonce, 16); + ctr->setCounterSize(4); + ctr->encrypt(bytes, scratch, numBytes); } /** @@ -208,4 +239,7 @@ void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId) // use memcpy to avoid breaking strict-aliasing memcpy(nonce, &packetId, sizeof(uint64_t)); memcpy(nonce + sizeof(uint64_t), &fromNode, sizeof(uint32_t)); -} \ No newline at end of file +} +#ifndef HAS_CUSTOM_CRYPTO_ENGINE +CryptoEngine *crypto = new CryptoEngine; +#endif \ No newline at end of file diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index fd607c29e..5ca9db7c1 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -1,5 +1,6 @@ #pragma once #include "AES.h" +#include "CTR.h" #include "concurrency/LockGuard.h" #include "configuration.h" #include "mesh-pb-constants.h" @@ -65,15 +66,16 @@ class CryptoEngine * * @param bytes is updated in place */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); + virtual void encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); + virtual void encryptAESCtr(CryptoKey key, uint8_t *nonce, size_t numBytes, uint8_t *bytes); #ifndef PIO_UNIT_TESTING protected: #endif /** Our per packet nonce */ uint8_t nonce[16] = {0}; - CryptoKey key = {}; + CTRCommon *ctr = NULL; #if !(MESHTASTIC_EXCLUDE_PKI) uint8_t shared_key[32] = {0}; uint8_t private_key[32] = {0}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1fecef6d7..b00b66a47 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -480,7 +480,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); p->channel = 0; } else { - crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); memcpy(p->encrypted.bytes, bytes, numbytes); } #else diff --git a/src/platform/esp32/ESP32CryptoEngine.cpp b/src/platform/esp32/ESP32CryptoEngine.cpp index 998419df8..230139036 100644 --- a/src/platform/esp32/ESP32CryptoEngine.cpp +++ b/src/platform/esp32/ESP32CryptoEngine.cpp @@ -13,58 +13,29 @@ class ESP32CryptoEngine : public CryptoEngine ~ESP32CryptoEngine() { mbedtls_aes_free(&aes); } - /** - * Set the key used for encrypt, decrypt. - * - * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. - * - * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) - * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the - * provided pointer) - */ - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - - if (key.length != 0) { - auto res = mbedtls_aes_setkey_enc(&aes, key.bytes, key.length * 8); - assert(!res); - } - } - /** * Encrypt a packet * * @param bytes is updated in place + * TODO: return bool, and handle graciously when something fails */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override + virtual void encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) override { - if (key.length > 0) { - LOG_DEBUG("ESP32 crypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t)packetId, numBytes); - initNonce(fromNode, packetId); + if (_key.length > 0) { if (numBytes <= MAX_BLOCKSIZE) { + mbedtls_aes_setkey_enc(&aes, _key.bytes, _key.length * 8); static uint8_t scratch[MAX_BLOCKSIZE]; uint8_t stream_block[16]; size_t nc_off = 0; memcpy(scratch, bytes, numBytes); memset(scratch + numBytes, 0, sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes); - assert(!res); + mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, _nonce, stream_block, scratch, bytes); } else { LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); } } } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: }; -CryptoEngine *crypto = new ESP32CryptoEngine(); +CryptoEngine *crypto = new ESP32CryptoEngine(); \ No newline at end of file diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index fd3f92a9c..b6def5b01 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -42,6 +42,9 @@ #ifndef DEFAULT_VREF #define DEFAULT_VREF 1100 #endif +#ifndef HAS_CUSTOM_CRYPTO_ENGINE +#define HAS_CUSTOM_CRYPTO_ENGINE 1 +#endif #if defined(HAS_AXP192) || defined(HAS_AXP2101) #define HAS_PMU diff --git a/src/platform/nrf52/NRF52CryptoEngine.cpp b/src/platform/nrf52/NRF52CryptoEngine.cpp index a7cf3d5bf..5de13c58b 100644 --- a/src/platform/nrf52/NRF52CryptoEngine.cpp +++ b/src/platform/nrf52/NRF52CryptoEngine.cpp @@ -9,41 +9,24 @@ class NRF52CryptoEngine : public CryptoEngine ~NRF52CryptoEngine() {} - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override + virtual void encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) override { - if (key.length > 16) { - LOG_DEBUG("Software encrypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t)packetId, numBytes); + if (_key.length > 16) { AES_ctx ctx; - initNonce(fromNode, packetId); - AES_init_ctx_iv(&ctx, key.bytes, nonce); + AES_init_ctx_iv(&ctx, _key.bytes, _nonce); AES_CTR_xcrypt_buffer(&ctx, bytes, numBytes); - } else if (key.length > 0) { - LOG_DEBUG("nRF52 encrypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t)packetId, numBytes); + } else if (_key.length > 0) { nRFCrypto.begin(); nRFCrypto_AES ctx; uint8_t myLen = ctx.blockLen(numBytes); char encBuf[myLen] = {0}; - initNonce(fromNode, packetId); ctx.begin(); - ctx.Process((char *)bytes, numBytes, nonce, key.bytes, key.length, encBuf, ctx.encryptFlag, ctx.ctrMode); + ctx.Process((char *)bytes, numBytes, _nonce, _key.bytes, _key.length, encBuf, ctx.encryptFlag, ctx.ctrMode); ctx.end(); nRFCrypto.end(); memcpy(bytes, encBuf, numBytes); } } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: }; -CryptoEngine *crypto = new NRF52CryptoEngine(); +CryptoEngine *crypto = new NRF52CryptoEngine(); \ No newline at end of file diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index d5685d611..8781347c3 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -32,6 +32,9 @@ #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 1 #endif +#ifndef HAS_CUSTOM_CRYPTO_ENGINE +#define HAS_CUSTOM_CRYPTO_ENGINE 1 +#endif // // set HW_VENDOR diff --git a/src/platform/portduino/CrossPlatformCryptoEngine.cpp b/src/platform/portduino/CrossPlatformCryptoEngine.cpp deleted file mode 100644 index 46ef942f0..000000000 --- a/src/platform/portduino/CrossPlatformCryptoEngine.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "AES.h" -#include "CTR.h" -#include "CryptoEngine.h" -#include "configuration.h" - -/** A platform independent AES engine implemented using Tiny-AES - */ -class CrossPlatformCryptoEngine : public CryptoEngine -{ - - CTRCommon *ctr = NULL; - - public: - CrossPlatformCryptoEngine() {} - - ~CrossPlatformCryptoEngine() {} - - /** - * Set the key used for encrypt, decrypt. - * - * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. - * - * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) - * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the - * provided pointer) - */ - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - LOG_DEBUG("Installing AES%d key!\n", key.length * 8); - if (ctr) { - delete ctr; - ctr = NULL; - } - if (key.length != 0) { - if (key.length == 16) - ctr = new CTR(); - else - ctr = new CTR(); - - ctr->setKey(key.bytes, key.length); - } - } - - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - if (key.length > 0) { - initNonce(fromNode, packetId); - if (numBytes <= MAX_BLOCKSIZE) { - static uint8_t scratch[MAX_BLOCKSIZE]; - memcpy(scratch, bytes, numBytes); - memset(scratch + numBytes, 0, - sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - ctr->setIV(nonce, sizeof(nonce)); - ctr->setCounterSize(4); - ctr->encrypt(bytes, scratch, numBytes); - } else { - LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); - } - } - } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: -}; - -CryptoEngine *crypto = new CrossPlatformCryptoEngine(); diff --git a/src/platform/rp2040/rp2040CryptoEngine.cpp b/src/platform/rp2040/rp2040CryptoEngine.cpp deleted file mode 100644 index 5486e51e5..000000000 --- a/src/platform/rp2040/rp2040CryptoEngine.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "AES.h" -#include "CTR.h" -#include "CryptoEngine.h" -#include "configuration.h" - -class RP2040CryptoEngine : public CryptoEngine -{ - - CTRCommon *ctr = NULL; - - public: - RP2040CryptoEngine() {} - - ~RP2040CryptoEngine() {} - - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - LOG_DEBUG("Installing AES%d key!\n", key.length * 8); - if (ctr) { - delete ctr; - ctr = NULL; - } - if (key.length != 0) { - if (key.length == 16) - ctr = new CTR(); - else - ctr = new CTR(); - - ctr->setKey(key.bytes, key.length); - } - } - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - if (key.length > 0) { - initNonce(fromNode, packetId); - if (numBytes <= MAX_BLOCKSIZE) { - static uint8_t scratch[MAX_BLOCKSIZE]; - memcpy(scratch, bytes, numBytes); - memset(scratch + numBytes, 0, - sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - ctr->setIV(nonce, sizeof(nonce)); - ctr->setCounterSize(4); - ctr->encrypt(bytes, scratch, numBytes); - } else { - LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); - } - } - } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: -}; - -CryptoEngine *crypto = new RP2040CryptoEngine(); diff --git a/src/platform/stm32wl/STM32WLCryptoEngine.cpp b/src/platform/stm32wl/STM32WLCryptoEngine.cpp deleted file mode 100644 index 4debdf78e..000000000 --- a/src/platform/stm32wl/STM32WLCryptoEngine.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#undef RNG -#include "AES.h" -#include "CTR.h" -#include "CryptoEngine.h" -#include "configuration.h" - -class STM32WLCryptoEngine : public CryptoEngine -{ - - CTRCommon *ctr = NULL; - - public: - STM32WLCryptoEngine() {} - - ~STM32WLCryptoEngine() {} - - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - LOG_DEBUG("Installing AES%d key!\n", key.length * 8); - if (ctr) { - delete ctr; - ctr = NULL; - } - if (key.length != 0) { - if (key.length == 16) - ctr = new CTR(); - else - ctr = new CTR(); - - ctr->setKey(key.bytes, key.length); - } - } - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - if (key.length > 0) { - initNonce(fromNode, packetId); - if (numBytes <= MAX_BLOCKSIZE) { - static uint8_t scratch[MAX_BLOCKSIZE]; - memcpy(scratch, bytes, numBytes); - memset(scratch + numBytes, 0, - sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - ctr->setIV(nonce, sizeof(nonce)); - ctr->setCounterSize(4); - ctr->encrypt(bytes, scratch, numBytes); - } else { - LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); - } - } - } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: -}; - -CryptoEngine *crypto = new STM32WLCryptoEngine(); diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index e9aee928e..129c88283 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -99,6 +99,32 @@ void test_DH25519(void) crypto->setDHPrivateKey(private_key); TEST_ASSERT(!crypto->setDHPublicKey(public_key)); // Weak public key results in 0 shared key } +void test_AES_CTR(void) +{ + uint8_t expected[32]; + uint8_t plain[32]; + uint8_t nonce[32]; + CryptoKey k; + + // vectors from https://www.rfc-editor.org/rfc/rfc3686#section-6 + k.length = 32; + HexToBytes(k.bytes, "776BEFF2851DB06F4C8A0542C8696F6C6A81AF1EEC96B4D37FC1D689E6C1C104"); + HexToBytes(nonce, "00000060DB5672C97AA8F0B200000001"); + HexToBytes(expected, "145AD01DBF824EC7560863DC71E3E0C0"); + memcpy(plain, "Single block msg", 16); + + crypto->encryptAESCtr(k, nonce, 16, plain); + TEST_ASSERT_EQUAL_MEMORY(expected, plain, 16); + + k.length = 16; + memcpy(plain, "Single block msg", 16); + HexToBytes(k.bytes, "AE6852F8121067CC4BF7A5765577F39E"); + HexToBytes(nonce, "00000030000000000000000000000001"); + HexToBytes(expected, "E4095D4FB7A7B3792D6175A3261311B8"); + crypto->encryptAESCtr(k, nonce, 16, plain); + TEST_ASSERT_EQUAL_MEMORY(expected, plain, 16); +} + void setup() { // NOTE!!! Wait for >2 secs @@ -109,6 +135,7 @@ void setup() RUN_TEST(test_SHA256); RUN_TEST(test_ECB_AES256); RUN_TEST(test_DH25519); + RUN_TEST(test_AES_CTR); } void loop() From 54a2e14e356b6c0a08b70ace9caaa8458ea3e606 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 23:11:04 -0500 Subject: [PATCH 0822/1377] Add missed function rename. (Thanks VSCode) --- src/mesh/Router.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b00b66a47..2a6fb31fc 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -484,7 +484,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) memcpy(p->encrypted.bytes, bytes, numbytes); } #else - crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); memcpy(p->encrypted.bytes, bytes, numbytes); #endif From cf392a4c201f03361563fbcf54caf303d0617fd6 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 11 Aug 2024 20:06:38 +0800 Subject: [PATCH 0823/1377] Address some FIXME comments (#4435) * Address some FIXME comments These comments have since been addressed by more modern code. Remove them to reduce the clutter in the codebase. * Remove 'dumb idea' from SimpleAllocator 4 year old code that was set never to run can probably be safely deleted. --- src/gps/GPS.h | 2 +- src/main.h | 10 ++----- src/platform/esp32/SimpleAllocator.cpp | 38 -------------------------- 3 files changed, 3 insertions(+), 47 deletions(-) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 1c0977bdd..96171cba5 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -51,7 +51,7 @@ enum GPSPowerState : uint8_t { const char *getDOPString(uint32_t dop); /** - * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) + * A gps class that only reads from the GPS periodically and keeps the gps powered down except when reading * * When new data is available it will notify observers. */ diff --git a/src/main.h b/src/main.h index ea2d80f94..52a3fff1f 100644 --- a/src/main.h +++ b/src/main.h @@ -65,12 +65,6 @@ extern bool isVibrating; extern int TCPPort; // set by Portduino -// extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class - -// extern meshtastic::PowerStatus *powerStatus; -// extern meshtastic::GPSStatus *gpsStatus; -// extern meshtastic::NodeStatusHandler *nodeStatusHandler; - // Return a human readable string of the form "Meshtastic_ab13" const char *getDeviceName(); @@ -91,5 +85,5 @@ void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearB meshtastic_DeviceMetadata getDeviceMetadata(); -// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that -extern SPISettings spiSettings; \ No newline at end of file +// We default to 4MHz SPI, SPI mode 0 +extern SPISettings spiSettings; diff --git a/src/platform/esp32/SimpleAllocator.cpp b/src/platform/esp32/SimpleAllocator.cpp index 63f3b02de..04ce35eb3 100644 --- a/src/platform/esp32/SimpleAllocator.cpp +++ b/src/platform/esp32/SimpleAllocator.cpp @@ -26,41 +26,3 @@ void *operator new(size_t size, SimpleAllocator &p) { return p.alloc(size); } - -#if 0 -// This was a dumb idea, turn off for now - -SimpleAllocator *activeAllocator; - -AllocatorScope::AllocatorScope(SimpleAllocator &a) -{ - assert(!activeAllocator); - activeAllocator = &a; -} - -AllocatorScope::~AllocatorScope() -{ - assert(activeAllocator); - activeAllocator = NULL; -} - -/// Global new/delete, uses a simple allocator if it is in scope - -void *operator new(size_t sz) throw(std::bad_alloc) -{ - void *mem = activeAllocator ? activeAllocator->alloc(sz) : malloc(sz); - if (mem) - return mem; - else - throw std::bad_alloc(); -} - -void operator delete(void *ptr) throw() -{ - if (activeAllocator) - LOG_WARN("Leaking an active allocator object\n"); // We don't properly handle this yet - else - free(ptr); -} - -#endif \ No newline at end of file From e1b4b226c9d9aae9c163510f3bf1c9f74db0d493 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Aug 2024 14:12:20 -0500 Subject: [PATCH 0824/1377] Manual protobuf update --- src/mesh/generated/meshtastic/admin.pb.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index bef2abf9b..13093839d 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -30,7 +30,9 @@ typedef enum _meshtastic_AdminMessage_ConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ConfigType_LORA_CONFIG = 5, /* TODO: REPLACE */ - meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG = 6 + meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG = 6, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG = 7 } meshtastic_AdminMessage_ConfigType; /* TODO: REPLACE */ @@ -194,8 +196,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG -#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG -#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG+1)) +#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG +#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG #define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG From 9bc222416414a2efec8b36602c2b0e0871f9a31f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Aug 2024 14:18:33 -0500 Subject: [PATCH 0825/1377] Exclude position packets from PKI (at least for now) --- src/mesh/Router.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 2a6fb31fc..945f92bb7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -470,8 +470,9 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) #if !(MESHTASTIC_EXCLUDE_PKI) meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); if (!owner.is_licensed && p->to != NODENUM_BROADCAST && node != nullptr && node->user.public_key.size > 0 && - p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && - p->decoded.portnum != meshtastic_PortNum_ROUTING_APP) { // TODO: check for size due to 8 byte tag + numbytes <= MAX_RHPACKETLEN - 8 && p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && + p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && + p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); if (numbytes + 8 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; From 6cd1882aaa861ef44882d5e55a6c769d42edaccd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Aug 2024 17:22:01 -0500 Subject: [PATCH 0826/1377] [create-pull-request] automated change (#4439) Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 4 ++++ src/mesh/generated/meshtastic/telemetry.pb.h | 12 +++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 2fa7d6a4b..071fd931e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 2fa7d6a4b702fcd58b54b0d1d6e4b3b85164f649 +Subproject commit 071fd931ec6679bb21427c872f9839edea63e351 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 59664b792..1bea952ce 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -180,6 +180,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_SENSECAP_INDICATOR = 70, /* Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors. */ meshtastic_HardwareModel_TRACKER_T1000_E = 71, + /* RAK3172 STM32WLE5 Module (https://store.rakwireless.com/products/wisduo-lpwan-module-rak3172) */ + meshtastic_HardwareModel_RAK3172 = 72, + /* Seeed Studio Wio-E5 (either mini or Dev kit) using STM32WL chip. */ + meshtastic_HardwareModel_WIO_E5 = 73, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 82cd0a55d..a4acd3f4a 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -63,7 +63,13 @@ typedef enum _meshtastic_TelemetrySensorType { /* DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) */ meshtastic_TelemetrySensorType_DFROBOT_LARK = 24, /* NAU7802 Scale Chip or compatible */ - meshtastic_TelemetrySensorType_NAU7802 = 25 + meshtastic_TelemetrySensorType_NAU7802 = 25, + /* BMP3XX High accuracy temperature and pressure */ + meshtastic_TelemetrySensorType_BMP3XX = 26, + /* ICM-20948 9-Axis digital motion processor */ + meshtastic_TelemetrySensorType_ICM20948 = 27, + /* MAX17048 1S lipo battery sensor (voltage, state of charge, time to go) */ + meshtastic_TelemetrySensorType_MAX17048 = 28 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -197,8 +203,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_NAU7802 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_NAU7802+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_MAX17048 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_MAX17048+1)) From a28f10e0c2525b15c6d415c86026d7bba255a7a3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 11 Aug 2024 17:22:11 -0500 Subject: [PATCH 0827/1377] User to UserLite in NodeDB (#4438) * User to UserLite in the nodedb * Tronkdor the burninator --- protobufs | 2 +- src/RedirectablePrint.cpp | 5 +- src/mesh/NodeDB.cpp | 14 +++-- src/mesh/TypeConversions.cpp | 32 +++++++++- src/mesh/TypeConversions.h | 2 + .../generated/meshtastic/deviceonly.pb.cpp | 3 + src/mesh/generated/meshtastic/deviceonly.pb.h | 62 +++++++++++++++++-- 7 files changed, 106 insertions(+), 14 deletions(-) diff --git a/protobufs b/protobufs index f5e84249f..ee41c42e4 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit f5e84249fe47fbddfb1d007c465f8f9623771290 +Subproject commit ee41c42e4f89d4153415b941305d23dec3647210 diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 02cd8b309..0eab0de0a 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -38,8 +38,9 @@ size_t RedirectablePrint::write(uint8_t c) #ifdef USE_SEGGER SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif - - if (!config.has_lora || config.security.serial_enabled) + // Account for legacy config transition + bool serialEnabled = config.has_security ? config.security.serial_enabled : config.device.serial_enabled; + if (!config.has_lora || serialEnabled) dest->write(c); return 1; // We always claim one was written, rather than trusting what the diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ac77e7830..e3030fb02 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -155,6 +155,10 @@ NodeDB::NodeDB() crypto->setDHPrivateKey(config.security.private_key.bytes); } else { #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + config.has_security = true; + config.security.serial_enabled = config.device.serial_enabled; + config.security.bluetooth_logging_enabled = config.bluetooth.device_logging_enabled; + config.security.is_managed = config.device.is_managed; LOG_INFO("Generating new PKI keys\n"); crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); config.security.public_key.size = 32; @@ -170,7 +174,7 @@ NodeDB::NodeDB() #endif - info->user = owner; + info->user = TypeConversions::ConvertToUserLite(owner); info->has_user = true; #ifdef ARCH_ESP32 @@ -1019,7 +1023,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde return false; } - LOG_DEBUG("old user %s/%s/%s, channel=%d\n", info->user.id, info->user.long_name, info->user.short_name, info->channel); + LOG_DEBUG("old user %s/%s, channel=%d\n", info->user.long_name, info->user.short_name, info->channel); #if !(MESHTASTIC_EXCLUDE_PKI) if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one printBytes("Retaining Old Pubkey: ", info->user.public_key.bytes, 32); @@ -1030,11 +1034,11 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde // Both of info->user and p start as filled with zero so I think this is okay bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); - info->user = p; + info->user = TypeConversions::ConvertToUserLite(p); if (nodeId != getNodeNum()) info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel) - LOG_DEBUG("updating changed=%d user %s/%s/%s, channel=%d\n", changed, info->user.id, info->user.long_name, - info->user.short_name, info->channel); + LOG_DEBUG("updating changed=%d user %s/%s, channel=%d\n", changed, info->user.long_name, info->user.short_name, + info->channel); info->has_user = true; if (changed) { diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index bcd600f24..30b06d0ad 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -24,7 +24,7 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo } if (lite->has_user) { info.has_user = true; - info.user = lite->user; + info.user = ConvertToUser(lite->num, lite->user); } if (lite->has_device_metrics) { info.has_device_metrics = true; @@ -55,4 +55,34 @@ meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite l position.time = lite.time; return position; +} + +meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user) +{ + meshtastic_UserLite lite = meshtastic_UserLite_init_default; + + strncpy(lite.long_name, user.long_name, sizeof(lite.long_name)); + strncpy(lite.short_name, user.short_name, sizeof(lite.short_name)); + lite.hw_model = user.hw_model; + lite.role = user.role; + lite.is_licensed = user.is_licensed; + memccpy(lite.macaddr, user.macaddr, sizeof(user.macaddr), sizeof(lite.macaddr)); + memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes)); + return lite; +} + +meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_UserLite lite) +{ + meshtastic_User user = meshtastic_User_init_default; + + snprintf(user.id, sizeof(user.id), "!%08x", nodeNum); + strncpy(user.long_name, lite.long_name, sizeof(user.long_name)); + strncpy(user.short_name, lite.short_name, sizeof(user.short_name)); + user.hw_model = lite.hw_model; + user.role = lite.role; + user.is_licensed = lite.is_licensed; + memccpy(user.macaddr, lite.macaddr, sizeof(lite.macaddr), sizeof(user.macaddr)); + memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes)); + + return user; } \ No newline at end of file diff --git a/src/mesh/TypeConversions.h b/src/mesh/TypeConversions.h index ffc3c12a7..19e471f98 100644 --- a/src/mesh/TypeConversions.h +++ b/src/mesh/TypeConversions.h @@ -10,4 +10,6 @@ class TypeConversions static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite); static meshtastic_PositionLite ConvertToPositionLite(meshtastic_Position position); static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite); + static meshtastic_UserLite ConvertToUserLite(meshtastic_User user); + static meshtastic_User ConvertToUser(uint32_t nodeNum, meshtastic_UserLite lite); }; diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 672192f67..2747ac9d9 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -9,6 +9,9 @@ PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO) +PB_BIND(meshtastic_UserLite, meshtastic_UserLite, AUTO) + + PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 2c91fe30e..343e5f48a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -9,6 +9,7 @@ #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" #include "meshtastic/telemetry.pb.h" +#include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -45,12 +46,37 @@ typedef struct _meshtastic_PositionLite { meshtastic_Position_LocSource location_source; } meshtastic_PositionLite; +typedef PB_BYTES_ARRAY_T(32) meshtastic_UserLite_public_key_t; +typedef struct _meshtastic_UserLite { + /* This is the addr of the radio. */ + pb_byte_t macaddr[6]; + /* A full name for this user, i.e. "Kevin Hester" */ + char long_name[40]; + /* A VERY short name, ideally two characters. + Suitable for a tiny OLED screen */ + char short_name[5]; + /* TBEAM, HELTEC, etc... + Starting in 1.2.11 moved to hw_model enum in the NodeInfo object. + Apps will still need the string here for older builds + (so OTA update can find the right image), but if the enum is available it will be used instead. */ + meshtastic_HardwareModel hw_model; + /* In some regions Ham radio operators have different bandwidth limitations than others. + If this user is a licensed operator, set this flag. + Also, "long_name" should be their licence number. */ + bool is_licensed; + /* Indicates that the user's role in the mesh */ + meshtastic_Config_DeviceConfig_Role role; + /* The public key of the user's device. + This is sent out to other nodes on the mesh to allow them to compute a shared secret key. */ + meshtastic_UserLite_public_key_t public_key; +} meshtastic_UserLite; + typedef struct _meshtastic_NodeInfoLite { /* The node number */ uint32_t num; /* The user info for this node */ bool has_user; - meshtastic_User user; + meshtastic_UserLite user; /* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. Position.time now indicates the last time we received a POSITION from that node. */ bool has_position; @@ -164,6 +190,9 @@ extern "C" { #define meshtastic_PositionLite_location_source_ENUMTYPE meshtastic_Position_LocSource +#define meshtastic_UserLite_hw_model_ENUMTYPE meshtastic_HardwareModel +#define meshtastic_UserLite_role_ENUMTYPE meshtastic_Config_DeviceConfig_Role + @@ -172,12 +201,14 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} +#define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} +#define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -188,6 +219,13 @@ extern "C" { #define meshtastic_PositionLite_altitude_tag 3 #define meshtastic_PositionLite_time_tag 4 #define meshtastic_PositionLite_location_source_tag 5 +#define meshtastic_UserLite_macaddr_tag 1 +#define meshtastic_UserLite_long_name_tag 2 +#define meshtastic_UserLite_short_name_tag 3 +#define meshtastic_UserLite_hw_model_tag 4 +#define meshtastic_UserLite_is_licensed_tag 5 +#define meshtastic_UserLite_role_tag 6 +#define meshtastic_UserLite_public_key_tag 7 #define meshtastic_NodeInfoLite_num_tag 1 #define meshtastic_NodeInfoLite_user_tag 2 #define meshtastic_NodeInfoLite_position_tag 3 @@ -229,6 +267,17 @@ X(a, STATIC, SINGULAR, UENUM, location_source, 5) #define meshtastic_PositionLite_CALLBACK NULL #define meshtastic_PositionLite_DEFAULT NULL +#define meshtastic_UserLite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 1) \ +X(a, STATIC, SINGULAR, STRING, long_name, 2) \ +X(a, STATIC, SINGULAR, STRING, short_name, 3) \ +X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \ +X(a, STATIC, SINGULAR, BOOL, is_licensed, 5) \ +X(a, STATIC, SINGULAR, UENUM, role, 6) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 7) +#define meshtastic_UserLite_CALLBACK NULL +#define meshtastic_UserLite_DEFAULT NULL + #define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ @@ -242,7 +291,7 @@ X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL -#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User +#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite #define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite #define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics @@ -290,6 +339,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, oem_local_module_config, 8) #define meshtastic_OEMStore_oem_local_module_config_MSGTYPE meshtastic_LocalModuleConfig extern const pb_msgdesc_t meshtastic_PositionLite_msg; +extern const pb_msgdesc_t meshtastic_UserLite_msg; extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_DeviceState_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; @@ -297,6 +347,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_PositionLite_fields &meshtastic_PositionLite_msg +#define meshtastic_UserLite_fields &meshtastic_UserLite_msg #define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg #define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg @@ -306,9 +357,10 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 -#define meshtastic_NodeInfoLite_size 200 +#define meshtastic_NodeInfoLite_size 183 #define meshtastic_OEMStore_size 3502 #define meshtastic_PositionLite_size 28 +#define meshtastic_UserLite_size 96 #ifdef __cplusplus } /* extern "C" */ From 48eee747da8a092d8272ee4f16fa0dcd8da8590c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 11 Aug 2024 18:25:32 -0500 Subject: [PATCH 0828/1377] protos --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index ee41c42e4..0c052b5d2 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit ee41c42e4f89d4153415b941305d23dec3647210 +Subproject commit 0c052b5d25fe8ed74c675178702f20a3fbc29afa From c74bce93607cd1749d7e8157089b6e1172f1b521 Mon Sep 17 00:00:00 2001 From: Ben Loomis Date: Mon, 12 Aug 2024 04:40:57 -0700 Subject: [PATCH 0829/1377] Detect UM600 as UC6580 (#4444) --- src/gps/GPS.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 82370937b..0164b554d 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1220,6 +1220,14 @@ GnssModel_t GPS::probe(int serialSpeed) return GNSS_MODEL_UC6580; } + clearBuffer(); + _serial_gps->write("$PDTINFO\r\n"); + delay(750); + if (getACK("UM600", 500) == GNSS_RESPONSE_OK) { + LOG_INFO("UM600 detected, using UC6580 Module\n"); + return GNSS_MODEL_UC6580; + } + // Get version information for ATGM336H clearBuffer(); _serial_gps->write("$PCAS06,1*1A\r\n"); @@ -1822,4 +1830,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file From bee959150b0938f983af370958240dea654e1d00 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 06:43:54 -0500 Subject: [PATCH 0830/1377] Add logic to nodeDB to prefer evicting boring nodes (#4441) --- src/mesh/NodeDB.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index e3030fb02..b68fdaae8 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1115,11 +1115,24 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; + int oldestIndex = -1; + int oldestBoringIndex = -1; for (int i = 1; i < numMeshNodes; i++) { + // Simply the oldest non-favorite node if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) { oldest = meshNodes->at(i).last_heard; oldestIndex = i; } + // The oldest "boring" node + if (!meshNodes->at(i).is_favorite && meshNodes->at(i).user.public_key.size == 0 && + meshNodes->at(i).last_heard < oldestBoring) { + oldestBoring = meshNodes->at(i).last_heard; + oldestBoringIndex = i; + } + } + // if we found a "boring" node, evict it + if (oldestBoringIndex != -1) { + oldestIndex = oldestBoringIndex; } // Shove the remaining nodes down the chain for (int i = oldestIndex; i < numMeshNodes - 1; i++) { @@ -1160,4 +1173,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} \ No newline at end of file +} From 2ee53d1500679a8b386c68eae53facf6b74f5943 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 11:26:43 -0500 Subject: [PATCH 0831/1377] Don't goober public_key in Userlite conversion --- src/mesh/NodeDB.cpp | 15 ++++++++++++--- src/mesh/TypeConversions.cpp | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b68fdaae8..376593fcd 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1025,9 +1025,15 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde LOG_DEBUG("old user %s/%s, channel=%d\n", info->user.long_name, info->user.short_name, info->channel); #if !(MESHTASTIC_EXCLUDE_PKI) - if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one - printBytes("Retaining Old Pubkey: ", info->user.public_key.bytes, 32); - memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); + if (p.public_key.size > 0) { + printBytes("Incoming Pubkey: ", p.public_key.bytes, 32); + if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one + LOG_INFO("Public Key set for node, not updateing!\n"); + // we copy the key into the incoming packet, to prevent overwrite + memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); + } else { + LOG_INFO("Updating Node Pubkey!\n"); + } } #endif @@ -1035,6 +1041,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); info->user = TypeConversions::ConvertToUserLite(p); + if (info->user.public_key.size == 32) { + printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32); + } if (nodeId != getNodeNum()) info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel) LOG_DEBUG("updating changed=%d user %s/%s, channel=%d\n", changed, info->user.long_name, info->user.short_name, diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 30b06d0ad..5303dfb49 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -68,6 +68,7 @@ meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user) lite.is_licensed = user.is_licensed; memccpy(lite.macaddr, user.macaddr, sizeof(user.macaddr), sizeof(lite.macaddr)); memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes)); + lite.public_key.size = user.public_key.size; return lite; } From bc69621c3e8d4945bdca2790d84459bc14711daa Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 11:37:50 -0500 Subject: [PATCH 0832/1377] Ungoober oldestBoring --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 376593fcd..33d8b51ef 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1123,7 +1123,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) memGet.getFreeHeap()); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; - int oldestIndex = -1; + uint32_t oldestBoring = UINT32_MAX; int oldestIndex = -1; int oldestBoringIndex = -1; for (int i = 1; i < numMeshNodes; i++) { From 9bd293a94106f50abcc86ede4c0fef4b92852b7c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 16:20:07 -0500 Subject: [PATCH 0833/1377] Don't forget public_key.size in converting back --- src/mesh/TypeConversions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 5303dfb49..d8ee6afc7 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -84,6 +84,7 @@ meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_User user.is_licensed = lite.is_licensed; memccpy(user.macaddr, lite.macaddr, sizeof(lite.macaddr), sizeof(user.macaddr)); memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes)); + user.public_key.size = lite.public_key.size; return user; } \ No newline at end of file From f97ae522630f9ae73005807bb6e9bc7e8b8e6ddd Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Tue, 13 Aug 2024 03:31:45 +0200 Subject: [PATCH 0834/1377] STM32WL improvements (#4449) * STM32WL: Enable DeviceTelemetry * Add long/short name user preference options * Add new STM32WL-based hardware models --- arch/stm32/stm32.ini | 4 ++-- src/mesh/NodeDB.cpp | 8 ++++++++ src/platform/stm32wl/architecture.h | 12 +++++++++--- userPrefs.h | 3 +++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index d6d14e2c9..ffbc51680 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -21,7 +21,7 @@ build_flags = -fdata-sections build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - board_upload.offset_address = 0x08000000 upload_protocol = stlink @@ -33,4 +33,4 @@ lib_deps = lib_ignore = https://github.com/mathertel/OneButton@~2.6.1 - Wire + Wire \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0f6c444ce..666255c74 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -563,8 +563,16 @@ void NodeDB::installDefaultDeviceState() // Set default owner name pickNewNodeNum(); // based on macaddr now +#ifdef CONFIG_OWNER_LONG_NAME_USERPREFS + snprintf(owner.long_name, sizeof(owner.long_name), CONFIG_OWNER_LONG_NAME_USERPREFS); +#else snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]); +#endif +#ifdef CONFIG_OWNER_SHORT_NAME_USERPREFS + snprintf(owner.short_name, sizeof(owner.short_name), CONFIG_OWNER_SHORT_NAME_USERPREFS); +#else snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]); +#endif snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum()); // Default node ID now based on nodenum memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); } diff --git a/src/platform/stm32wl/architecture.h b/src/platform/stm32wl/architecture.h index 1c021903a..325a192a4 100644 --- a/src/platform/stm32wl/architecture.h +++ b/src/platform/stm32wl/architecture.h @@ -9,12 +9,18 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif +#ifndef HAS_TELEMETRY +#define HAS_TELEMETRY 1 +#endif // // set HW_VENDOR // - -#ifndef HW_VENDOR +#ifdef _VARIANT_WIOE5_ +#define HW_VENDOR meshtastic_HardwareModel_WIO_E5 +#elif defined(_VARIANT_RAK3172_) +#define HW_VENDOR meshtastic_HardwareModel_RAK3172 +#else #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #endif @@ -22,4 +28,4 @@ #define SX126X_CS 1000 #define SX126X_DIO1 1001 #define SX126X_RESET 1003 -#define SX126X_BUSY 1004 +#define SX126X_BUSY 1004 \ No newline at end of file diff --git a/userPrefs.h b/userPrefs.h index b365e8c6f..3ebbefcaf 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -19,6 +19,9 @@ // #define CHANNEL_0_NAME_USERPREFS "DEFCONnect" // #define CHANNEL_0_PRECISION_USERPREFS 13 +// #define CONFIG_OWNER_LONG_NAME_USERPREFS "My Long Name" +// #define CONFIG_OWNER_SHORT_NAME_USERPREFS "MLN" + // #define SPLASH_TITLE_USERPREFS "DEFCONtastic" // #define icon_width 34 // #define icon_height 29 From 6e8300287b8ff056f5987242456334480493d467 Mon Sep 17 00:00:00 2001 From: "Aaron.Lee" <32860565+Heltec-Aaron-Lee@users.noreply.github.com> Date: Tue, 13 Aug 2024 19:30:35 +0800 Subject: [PATCH 0835/1377] Heltec boards sensor and low power features update (#4418) * Update sensor drive and low power features. * Update ST7789 TFT control logic. * Update Heltec nRF board low power features. * Update the GPS UART port pointer --- src/gps/GPS.cpp | 10 ++++++++++ src/graphics/Screen.cpp | 20 +++++++++++++++++--- src/main.cpp | 19 +++++++++++++++++++ src/platform/esp32/main-esp32.cpp | 5 +++++ src/platform/nrf52/NRF52Bluetooth.cpp | 11 +++++++++-- src/platform/nrf52/main-nrf52.cpp | 6 ++++++ src/sleep.cpp | 7 +++++++ variants/heltec_capsule_sensor_v3/variant.h | 10 ++++++++++ variants/heltec_mesh_node_t114/variant.h | 2 +- 9 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0164b554d..89af0430a 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -810,6 +810,16 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) powerState = newState; LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState)); +#ifdef HELTEC_MESH_NODE_T114 + if( (oldState==GPS_OFF || oldState==GPS_HARDSLEEP) && (newState!=GPS_OFF && newState!=GPS_HARDSLEEP) ) + { + _serial_gps->begin(serialSpeeds[speedSelect]); + } + else if( (newState==GPS_OFF || newState==GPS_HARDSLEEP) && (oldState!=GPS_OFF && oldState!=GPS_HARDSLEEP) ) + { + _serial_gps->end(); + } +#endif switch (newState) { case GPS_ACTIVE: case GPS_IDLE: diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ea5ab9788..bc7b3d1e6 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1591,6 +1591,9 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) dispdev->displayOn(); #ifdef USE_ST7789 + pinMode(VTFT_CTRL, OUTPUT); + digitalWrite(VTFT_CTRL,LOW); + ui->init(); #ifdef ESP_PLATFORM analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); #else @@ -1609,10 +1612,21 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif LOG_INFO("Turning off screen\n"); dispdev->displayOff(); - #ifdef USE_ST7789 - pinMode(VTFT_LEDA, OUTPUT); - digitalWrite(VTFT_LEDA, !TFT_BACKLIGHT_ON); + SPI1.end(); +#if defined(ARCH_ESP32) + pinMode(VTFT_LEDA, ANALOG); + pinMode(VTFT_CTRL, ANALOG); + pinMode(ST7789_RESET,ANALOG); + pinMode(ST7789_RS,ANALOG); + pinMode(ST7789_NSS,ANALOG); +#else + nrf_gpio_cfg_default(VTFT_LEDA); + nrf_gpio_cfg_default(VTFT_CTRL); + nrf_gpio_cfg_default(ST7789_RESET); + nrf_gpio_cfg_default(ST7789_RS); + nrf_gpio_cfg_default(ST7789_NSS); +#endif #endif #ifdef T_WATCH_S3 diff --git a/src/main.cpp b/src/main.cpp index 0a3c1ae0b..c3e779554 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -319,6 +319,14 @@ void setup() digitalWrite(RESET_OLED, 1); #endif +#ifdef SENSOR_POWER_CTRL_PIN + pinMode(SENSOR_POWER_CTRL_PIN,OUTPUT); + digitalWrite(SENSOR_POWER_CTRL_PIN,SENSOR_POWER_ON); +#endif + +#ifdef SENSOR_GPS_CONFLICT + bool sensor_detected=false; +#endif #ifdef PERIPHERAL_WARMUP_MS // Some peripherals may require additional time to stabilize after power is connected // e.g. I2C on Heltec Vision Master @@ -458,6 +466,9 @@ void setup() LOG_INFO("No I2C devices found\n"); } else { LOG_INFO("%i I2C devices found\n", i2cCount); +#ifdef SENSOR_GPS_CONFLICT + sensor_detected=true; +#endif } #ifdef ARCH_ESP32 @@ -703,6 +714,10 @@ void setup() #if !MESHTASTIC_EXCLUDE_GPS // If we're taking on the repeater role, ignore GPS +#ifdef SENSOR_GPS_CONFLICT + if(sensor_detected==false) + { +#endif if (HAS_GPS) { if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { @@ -714,6 +729,10 @@ void setup() } } } +#ifdef SENSOR_GPS_CONFLICT + } +#endif + #endif nodeStatus->observe(&nodeDB->newStatus); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 19f435908..36bd3fbb3 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -228,6 +228,9 @@ void cpuDeepSleep(uint32_t msecToWake) // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); +#ifdef ESP32S3_WAKE_TYPE + esp_sleep_enable_ext1_wakeup(gpioMask, ESP32S3_WAKE_TYPE); +#else #if SOC_PM_SUPPORT_EXT_WAKEUP #ifdef CONFIG_IDF_TARGET_ESP32 // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target. @@ -236,6 +239,8 @@ void cpuDeepSleep(uint32_t msecToWake) esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW); #endif #endif + +#endif //#end ESP32S3_WAKE_TYPE #endif // We want RTC peripherals to stay on diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 81a165f2d..b27f9df0c 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -198,8 +198,15 @@ void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth\n"); - if (connectionHandle != 0) { - Bluefruit.disconnect(connectionHandle); + uint8_t connection_num=Bluefruit.connected(); + if(connection_num) + { + for(uint8_t i=0;i 100ms will reset the L76K +//#define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K #define GPS_RESET_MODE LOW #define PIN_GPS_EN (21) #define GPS_EN_ACTIVE HIGH From 62a0321c7d0dc351bb87ea6abda3b1a991b47e20 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 13 Aug 2024 04:45:39 -0700 Subject: [PATCH 0836/1377] Fixes for #4395: nrf52 flash filesystem reliability (#4406) * bug #4184: fix config file loss due to filesystem write errors * Use SafeFile for atomic file writing (with xor checksum readback) * Write db.proto last because it could be the largest file on the FS (and less critical) * Don't keep a tmp file around while writing db.proto (because too big to fit two files in the filesystem) * generate a new critial fault if we encounter errors writing to flash either CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE or CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE (depending on if the second write attempt worked) * reformat the filesystem if we detect it is corrupted (then rewrite our config files) (only on nrf52 - not sure yet if we should bother on ESP32) * If we have to format the FS, make sure to preserve the oem.proto if it exists * add logLegacy() so old C code in libs can log via our logging * move filesList() to a better location (used only in developer builds) * Reformat with "trunk fmt" to match our coding conventions * for #4395: don't use .exists() to before attempting file open If a LFS filesystem is corrupted, .exists() can fail when a mere .open() attempt would have succeeded. Therefore better to do the .open() in hopes that we can read the file (in case we need to reformat to fix the FS). (Seen and confirmed in stress testing) * for #4395 more fixes, see below for details: * check for LFS assertion failures during file operations (needs customized lfs_util.h to provide suitable hooks) * Remove fsCheck() because checking filesystem by writing to it is very high risk, it makes likelyhood that we will be able to read the config protobufs quite low. * Update the LFS inside of adafruitnrf52 to 1.7.2 (from their old 1.6.1) to get the following fix: https://github.com/littlefs-project/littlefs/commit/97d8d5e96a7781596708664f18f2ea6c3a179330 * use disable_adafruit_usb.py now that we are (temporarily?) using a forked adafruit lib We need to reach inside the adafruit project and turn off USE_TINYUSB, just doing that from platformio.ini is no longer sufficient. Tested on a wio-sdk-wm1110 board (which is the only board that had this problem) --------- Co-authored-by: Ben Meadors --- arch/nrf52/cpp_overrides/lfs_util.h | 208 +++++++++++++++++++++++++ arch/nrf52/nrf52.ini | 6 +- extra_scripts/disable_adafruit_usb.py | 18 ++- monitor/filter_c3_exception_decoder.py | 21 +-- src/DebugConfiguration.cpp | 12 +- src/DebugConfiguration.h | 3 + src/FSCommon.cpp | 100 ++---------- src/FSCommon.h | 8 +- src/SafeFile.cpp | 8 +- src/mesh/NodeDB.cpp | 26 ---- variants/wio-sdk-wm1110/platformio.ini | 30 ++-- 11 files changed, 285 insertions(+), 155 deletions(-) create mode 100644 arch/nrf52/cpp_overrides/lfs_util.h diff --git a/arch/nrf52/cpp_overrides/lfs_util.h b/arch/nrf52/cpp_overrides/lfs_util.h new file mode 100644 index 000000000..bf5a347b1 --- /dev/null +++ b/arch/nrf52/cpp_overrides/lfs_util.h @@ -0,0 +1,208 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ + +// MESHTASTIC/@geeksville note: This file is copied from the Adafruit nrf52 arduino lib. And we use a special -include in +// nrf52.ini to load it before EVERY file we do this hack because the default definitions for LFS_ASSERT are quite poor and we +// don't want to fork the adafruit lib (again) and send in a PR that they probably won't merge anyways. This file might break if +// they ever update lfs.util on their side, in which case we'll need to update this file to match their new version. The version +// this is a copy from is almost exactly +// https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/c25d93268a3b9c23e9a1ccfcaf9b208beca624ca/libraries/Adafruit_LittleFS/src/littlefs/lfs_util.h + +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start I would suggest copying lfs_util.h and +// modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif + +#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifndef LFS_NO_DEBUG + +void logLegacy(const char *level, const char *fmt, ...); +#define LFS_DEBUG(fmt, ...) logLegacy("DEBUG", "lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_DEBUG(fmt, ...) +#endif + +#ifndef LFS_NO_WARN +#define LFS_WARN(fmt, ...) logLegacy("WARN", "lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_WARN(fmt, ...) +#endif + +#ifndef LFS_NO_ERROR +#define LFS_ERROR(fmt, ...) logLegacy("ERROR", "lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_ERROR(fmt, ...) +#endif + +// Runtime assertions +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +extern void lfs_assert(const char *reason); +#define LFS_ASSERT(test) \ + if (!(test)) \ + lfs_assert(#test) +#endif + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) +{ + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) +{ + return (a < b) ? a : b; +} + +// Find the next smallest power of 2 less than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a - 1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; + a >>= s; + r |= s; + s = (a > 0xff) << 3; + a >>= s; + r |= s; + s = (a > 0xf) << 2; + a >>= s; + r |= s; + s = (a > 0x3) << 1; + a >>= s; + r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) +{ + return (int)(unsigned)(a - b); +} + +// Convert from 32-bit little-endian to native order +static inline uint32_t lfs_fromle32(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && ((defined(BYTE_ORDER) && BYTE_ORDER == ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && BYTE_ORDER == ORDER_BIG_ENDIAN) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t *)&a)[0] << 0) | (((uint8_t *)&a)[1] << 8) | (((uint8_t *)&a)[2] << 16) | (((uint8_t *)&a)[3] << 24); +#endif +} + +// Convert to 32-bit little-endian from native order +static inline uint32_t lfs_tole32(uint32_t a) +{ + return lfs_fromle32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +void lfs_crc(uint32_t *crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +static inline void *lfs_malloc(size_t size) +{ +#ifndef LFS_NO_MALLOC + extern void *pvPortMalloc(size_t xWantedSize); + return pvPortMalloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) +{ +#ifndef LFS_NO_MALLOC + extern void vPortFree(void *pv); + vPortFree(p); +#else + (void)p; +#endif +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif \ No newline at end of file diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 1a371e920..f3e7c3036 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -2,9 +2,13 @@ ; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files platform = platformio/nordicnrf52@^10.5.0 extends = arduino_base +platform_packages = + ; our custom Git version until they merge our PR + framework-arduinoadafruitnrf52 @ https://github.com/geeksville/Adafruit_nRF52_Arduino.git build_type = debug -build_flags = +build_flags = + -include arch/nrf52/cpp_overrides/lfs_util.h ${arduino_base.build_flags} -DSERIAL_BUFFER_SIZE=1024 -Wno-unused-variable diff --git a/extra_scripts/disable_adafruit_usb.py b/extra_scripts/disable_adafruit_usb.py index fb391715c..596242184 100644 --- a/extra_scripts/disable_adafruit_usb.py +++ b/extra_scripts/disable_adafruit_usb.py @@ -5,16 +5,24 @@ Import("env") # NOTE: This is not currently used, but can serve as an example on how to write extra_scripts -print("Current CLI targets", COMMAND_LINE_TARGETS) -print("Current Build targets", BUILD_TARGETS) -print("CPP defs", env.get("CPPDEFINES")) +# print("Current CLI targets", COMMAND_LINE_TARGETS) +# print("Current Build targets", BUILD_TARGETS) +# print("CPP defs", env.get("CPPDEFINES")) +# print(env.Dump()) # Adafruit.py in the platformio build tree is a bit naive and always enables their USB stack for building. We don't want this. # So come in after that python script has run and disable it. This hack avoids us having to fork that big project and send in a PR # which might not be accepted. -@geeksville -env["CPPDEFINES"].remove("USBCON") -env["CPPDEFINES"].remove("USE_TINYUSB") +lib_builders = env.get("__PIO_LIB_BUILDERS", None) +if lib_builders is not None: + print("Disabling Adafruit USB stack") + for k in lib_builders: + if k.name == "Adafruit TinyUSB Library": + libenv = k.env + # print(f"{k.name }: { libenv.Dump() } ") + # libenv["CPPDEFINES"].remove("USBCON") + libenv["CPPDEFINES"].remove("USE_TINYUSB") # Custom actions when building program/firmware # env.AddPreAction("buildprog", callback...) diff --git a/monitor/filter_c3_exception_decoder.py b/monitor/filter_c3_exception_decoder.py index e59b0be2a..6d7b5370c 100644 --- a/monitor/filter_c3_exception_decoder.py +++ b/monitor/filter_c3_exception_decoder.py @@ -18,10 +18,7 @@ import subprocess import sys from platformio.project.exception import PlatformioException -from platformio.public import ( - DeviceMonitorFilterBase, - load_build_metadata, -) +from platformio.public import DeviceMonitorFilterBase, load_build_metadata # By design, __init__ is called inside miniterm and we can't pass context to it. # pylint: disable=attribute-defined-outside-init @@ -32,7 +29,7 @@ IS_WINDOWS = sys.platform.startswith("win") class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): NAME = "esp32_c3_exception_decoder" - PCADDR_PATTERN = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE) + PCADDR_PATTERN = re.compile(r"0x4[0-9a-f]{7}", re.IGNORECASE) def __call__(self): self.buffer = "" @@ -75,14 +72,14 @@ See https://docs.platformio.org/page/projectconf/build_configurations.html % self.__class__.__name__ ) return False - + if not os.path.isfile(self.addr2line_path): sys.stderr.write( "%s: disabling, addr2line at %s does not exist\n" % (self.__class__.__name__, self.addr2line_path) ) return False - + return True except PlatformioException as e: sys.stderr.write( @@ -117,7 +114,7 @@ See https://docs.platformio.org/page/projectconf/build_configurations.html trace = self.get_backtrace(m) if len(trace) != "": - text = text[: last] + trace + text[last :] + text = text[:last] + trace + text[last:] last += len(trace) return text @@ -125,14 +122,10 @@ See https://docs.platformio.org/page/projectconf/build_configurations.html def get_backtrace(self, match): trace = "\n" enc = "mbcs" if IS_WINDOWS else "utf-8" - args = [self.addr2line_path, u"-fipC", u"-e", self.firmware_path] + args = [self.addr2line_path, "-fipC", "-e", self.firmware_path] try: addr = match.group() - output = ( - subprocess.check_output(args + [addr]) - .decode(enc) - .strip() - ) + output = subprocess.check_output(args + [addr]).decode(enc).strip() output = output.replace( "\n", "\n " ) # newlines happen with inlined methods diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index d9ecd9fe3..d58856c4d 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,6 +26,16 @@ SOFTWARE.*/ #include "DebugConfiguration.h" +/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic +extern "C" void logLegacy(const char *level, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (console) + console->vprintf(level, fmt, args); + va_end(args); +} + #if HAS_NETWORKING Syslog::Syslog(UDP &client) @@ -169,4 +179,4 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess return true; } -#endif +#endif \ No newline at end of file diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index f5b3fa25a..7987e7fa1 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -62,6 +62,9 @@ #endif #endif +/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic +extern "C" void logLegacy(const char *level, const char *fmt, ...); + #define SYSLOG_NILVALUE "-" #define SYSLOG_CRIT 2 /* critical conditions */ diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 3017ec085..f45319c0b 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -48,6 +48,15 @@ void OSFS::writeNBytes(uint16_t address, unsigned int num, const byte *input) } #endif +bool lfs_assert_failed = + false; // Note: we use this global on all platforms, though it can only be set true on nrf52 (in our modified lfs_util.h) + +extern "C" void lfs_assert(const char *reason) +{ + LOG_ERROR("LFS assert: %s\n", reason); + lfs_assert_failed = true; +} + /** * @brief Copies a file from one location to another. * @@ -199,7 +208,7 @@ std::vector getFiles(const char *dirname, uint8_t levels) * @param levels The number of levels of subdirectories to list. * @param del Whether or not to delete the contents of the directory after listing. */ -void listDir(const char *dirname, uint8_t levels, bool del = false) +void listDir(const char *dirname, uint8_t levels, bool del) { #ifdef FSCom #if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)) @@ -214,7 +223,9 @@ void listDir(const char *dirname, uint8_t levels, bool del = false) } File file = root.openNextFile(); - while (file) { + while ( + file && + file.name()[0]) { // This file.name() check is a workaround for a bug in the Adafruit LittleFS nrf52 glue (see issue 4395) if (file.isDirectory() && !String(file.name()).endsWith(".")) { if (levels) { #ifdef ARCH_ESP32 @@ -313,62 +324,6 @@ void rmDir(const char *dirname) #endif } -bool fsCheck() -{ -#if defined(ARCH_NRF52) - size_t write_size = 0; - size_t read_size = 0; - char buf[32] = {0}; - - Adafruit_LittleFS_Namespace::File file(FSCom); - const char *text = "meshtastic fs test"; - size_t text_length = strlen(text); - const char *filename = "/meshtastic.txt"; - - LOG_DEBUG("Try create file .\n"); - if (file.open(filename, FILE_O_WRITE)) { - write_size = file.write(text); - } else { - LOG_DEBUG("Open file failed .\n"); - goto FORMAT_FS; - } - - if (write_size != text_length) { - LOG_DEBUG("Text bytes do not match .\n"); - file.close(); - goto FORMAT_FS; - } - - file.close(); - - if (!file.open(filename, FILE_O_READ)) { - LOG_DEBUG("Open file failed .\n"); - goto FORMAT_FS; - } - - read_size = file.readBytes(buf, text_length); - if (read_size != text_length) { - LOG_DEBUG("Text bytes do not match .\n"); - file.close(); - goto FORMAT_FS; - } - - if (memcmp(buf, text, text_length) != 0) { - LOG_DEBUG("The written bytes do not match the read bytes .\n"); - file.close(); - goto FORMAT_FS; - } - return true; -FORMAT_FS: - LOG_DEBUG("Format FS ....\n"); - FSCom.format(); - FSCom.begin(); - return false; -#else - return true; -#endif -} - void fsInit() { #ifdef FSCom @@ -378,35 +333,6 @@ void fsInit() } #if defined(ARCH_ESP32) LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes()); -#elif defined(ARCH_NRF52) - /* - * nRF52840 has a certain chance of automatic formatting failure. - * Try to create a file after initializing the file system. If the creation fails, - * it means that the file system is not working properly. Please format it manually again. - * To check the normality of the file system, you need to disable the LFS_NO_ASSERT assertion. - * Otherwise, the assertion will be entered at the moment of reading or opening, and the FS will not be formatted. - * */ - bool ret = false; - uint8_t retry = 3; - - while (retry--) { - ret = fsCheck(); - if (ret) { - LOG_DEBUG("File system check is OK.\n"); - break; - } - delay(10); - } - - // It may not be possible to reach this step. - // Add a loop here to prevent unpredictable situations from happening. - // Can add a screen to display error status later. - if (!ret) { - while (1) { - LOG_ERROR("The file system is damaged and cannot proceed to the next step.\n"); - delay(1000); - } - } #else LOG_DEBUG("Filesystem files:\n"); #endif diff --git a/src/FSCommon.h b/src/FSCommon.h index 3d485d1b1..254245b29 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -51,9 +51,13 @@ using namespace Adafruit_LittleFS_Namespace; #endif void fsInit(); +void fsListFiles(); bool copyFile(const char *from, const char *to); bool renameFile(const char *pathFrom, const char *pathTo); std::vector getFiles(const char *dirname, uint8_t levels); -void listDir(const char *dirname, uint8_t levels, bool del); +void listDir(const char *dirname, uint8_t levels, bool del = false); void rmDir(const char *dirname); -void setupSDCard(); \ No newline at end of file +void setupSDCard(); + +extern bool lfs_assert_failed; // Note: we use this global on all platforms, though it can only be set true on nrf52 (in our + // modified lfs_util.h) diff --git a/src/SafeFile.cpp b/src/SafeFile.cpp index 161a80870..c17d422bd 100644 --- a/src/SafeFile.cpp +++ b/src/SafeFile.cpp @@ -11,6 +11,9 @@ static File openFile(const char *filename, bool fullAtomic) String filenameTmp = filename; filenameTmp += ".tmp"; + // clear any previous LFS errors + lfs_assert_failed = false; + return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE); } @@ -73,6 +76,9 @@ bool SafeFile::close() /// Read our (closed) tempfile back in and compare the hash bool SafeFile::testReadback() { + bool lfs_failed = lfs_assert_failed; + lfs_assert_failed = false; + String filenameTmp = filename; filenameTmp += ".tmp"; auto f2 = FSCom.open(filenameTmp.c_str(), FILE_O_READ); @@ -93,7 +99,7 @@ bool SafeFile::testReadback() return false; } - return true; + return !lfs_failed; } #endif \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 666255c74..000da335f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -56,27 +56,6 @@ meshtastic_ChannelFile channelFile; meshtastic_OEMStore oemStore; static bool hasOemStore = false; -// These are not publically exposed - copied from InternalFileSystem.cpp -// #define FLASH_NRF52_PAGE_SIZE 4096 -// #define LFS_FLASH_TOTAL_SIZE (7*FLASH_NRF52_PAGE_SIZE) -// #define LFS_BLOCK_SIZE 128 - -/// List all files in the FS and test write and readback. -/// Useful for filesystem stress testing - normally stripped from build by the linker. -void flashTest() -{ - auto filesManifest = getFiles("/", 5); - - uint32_t totalSize = 0; - for (size_t i = 0; i < filesManifest.size(); i++) { - LOG_INFO("File %s (size %d)\n", filesManifest[i].file_name, filesManifest[i].size_bytes); - totalSize += filesManifest[i].size_bytes; - } - LOG_INFO("%d files (total size %u)\n", filesManifest.size(), totalSize); - // LOG_INFO("Filesystem block size %u, total bytes %u", LFS_FLASH_TOTAL_SIZE, LFS_BLOCK_SIZE); - nodeDB->saveToDisk(); -} - bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field) { if (ostream) { @@ -617,11 +596,6 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t LoadFileResult state = LoadFileResult::OTHER_FAILURE; #ifdef FSCom - if (!FSCom.exists(filename)) { - LOG_INFO("File %s not found\n", filename); - return LoadFileResult::NOT_FOUND; - } - auto f = FSCom.open(filename, FILE_O_READ); if (f) { diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini index 077356553..e77455bcf 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/wio-sdk-wm1110/platformio.ini @@ -3,29 +3,23 @@ extends = nrf52840_base board = wio-sdk-wm1110 +extra_scripts = + bin/platformio-custom.py + extra_scripts/disable_adafruit_usb.py + # Remove adafruit USB serial from the build (it is incompatible with using the ch340 serial chip on this board) build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DCFG_TUD_CDC=0 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> -lib_deps = - ${nrf52840_base.lib_deps} -debug_tool = jlink -;debug_tool = stlink -;debug_speed = 4000 -; No need to reflash if the binary hasn't changed -debug_load_mode = modified -; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -upload_protocol = nrfutil -;upload_protocol = stlink -; we prefer to stop in setup() because we are an 'ardiuno' app -debug_init_break = tbreak setup -; we need to turn off BLE/soft device if we are debugging otherwise it will watchdog reset us. -debug_extra_cmds = - echo Running .gdbinit script - commands 1 - set useSoftDevice = false - end +;debug_tool = jlink +debug_tool = stlink +; No need to reflash if the binary hasn't changed + +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = nrfutil +;upload_protocol = stlink \ No newline at end of file From e85a2e827bfe455648c644ac263adc23b4d6bc8c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 13 Aug 2024 06:49:32 -0500 Subject: [PATCH 0837/1377] Update protos --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 071fd931e..c9ca0dbe9 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 071fd931ec6679bb21427c872f9839edea63e351 +Subproject commit c9ca0dbe9cc7105399e0486c07e0601f849b94af diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 1bea952ce..955484cb0 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -184,6 +184,9 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RAK3172 = 72, /* Seeed Studio Wio-E5 (either mini or Dev kit) using STM32WL chip. */ meshtastic_HardwareModel_WIO_E5 = 73, + /* RadioMaster 900 Bandit, https://www.radiomasterrc.com/products/bandit-expresslrs-rf-module + SSD1306 OLED and No GPS */ + meshtastic_HardwareModel_RADIOMASTER_900_BANDIT = 74, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 7740b4bccd8859fa92a5bb6285836722b8fa5318 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 13 Aug 2024 06:52:03 -0500 Subject: [PATCH 0838/1377] Sweep up some missed trunk formatting --- src/gps/GPS.cpp | 7 ++--- src/graphics/Screen.cpp | 8 +++--- src/main.cpp | 29 ++++++++++----------- src/platform/esp32/main-esp32.cpp | 4 +-- src/platform/nrf52/NRF52Bluetooth.cpp | 12 ++++----- src/sleep.cpp | 6 ++--- variants/heltec_capsule_sensor_v3/variant.h | 2 +- variants/heltec_mesh_node_t114/variant.h | 2 +- 8 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 89af0430a..7d7de8700 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -811,12 +811,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState)); #ifdef HELTEC_MESH_NODE_T114 - if( (oldState==GPS_OFF || oldState==GPS_HARDSLEEP) && (newState!=GPS_OFF && newState!=GPS_HARDSLEEP) ) - { + if ((oldState == GPS_OFF || oldState == GPS_HARDSLEEP) && (newState != GPS_OFF && newState != GPS_HARDSLEEP)) { _serial_gps->begin(serialSpeeds[speedSelect]); - } - else if( (newState==GPS_OFF || newState==GPS_HARDSLEEP) && (oldState!=GPS_OFF && oldState!=GPS_HARDSLEEP) ) - { + } else if ((newState == GPS_OFF || newState == GPS_HARDSLEEP) && (oldState != GPS_OFF && oldState != GPS_HARDSLEEP)) { _serial_gps->end(); } #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index bc7b3d1e6..3a4db6ee0 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1592,7 +1592,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) dispdev->displayOn(); #ifdef USE_ST7789 pinMode(VTFT_CTRL, OUTPUT); - digitalWrite(VTFT_CTRL,LOW); + digitalWrite(VTFT_CTRL, LOW); ui->init(); #ifdef ESP_PLATFORM analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); @@ -1617,9 +1617,9 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #if defined(ARCH_ESP32) pinMode(VTFT_LEDA, ANALOG); pinMode(VTFT_CTRL, ANALOG); - pinMode(ST7789_RESET,ANALOG); - pinMode(ST7789_RS,ANALOG); - pinMode(ST7789_NSS,ANALOG); + pinMode(ST7789_RESET, ANALOG); + pinMode(ST7789_RS, ANALOG); + pinMode(ST7789_NSS, ANALOG); #else nrf_gpio_cfg_default(VTFT_LEDA); nrf_gpio_cfg_default(VTFT_CTRL); diff --git a/src/main.cpp b/src/main.cpp index c3e779554..7f45905d1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -320,12 +320,12 @@ void setup() #endif #ifdef SENSOR_POWER_CTRL_PIN - pinMode(SENSOR_POWER_CTRL_PIN,OUTPUT); - digitalWrite(SENSOR_POWER_CTRL_PIN,SENSOR_POWER_ON); + pinMode(SENSOR_POWER_CTRL_PIN, OUTPUT); + digitalWrite(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON); #endif #ifdef SENSOR_GPS_CONFLICT - bool sensor_detected=false; + bool sensor_detected = false; #endif #ifdef PERIPHERAL_WARMUP_MS // Some peripherals may require additional time to stabilize after power is connected @@ -467,7 +467,7 @@ void setup() } else { LOG_INFO("%i I2C devices found\n", i2cCount); #ifdef SENSOR_GPS_CONFLICT - sensor_detected=true; + sensor_detected = true; #endif } @@ -715,20 +715,19 @@ void setup() #if !MESHTASTIC_EXCLUDE_GPS // If we're taking on the repeater role, ignore GPS #ifdef SENSOR_GPS_CONFLICT - if(sensor_detected==false) - { + if (sensor_detected == false) { #endif - if (HAS_GPS) { - if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && - config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { - gps = GPS::createGps(); - if (gps) { - gpsStatus->observe(&gps->newStatus); - } else { - LOG_DEBUG("Running without GPS.\n"); + if (HAS_GPS) { + if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { + gps = GPS::createGps(); + if (gps) { + gpsStatus->observe(&gps->newStatus); + } else { + LOG_DEBUG("Running without GPS.\n"); + } } } - } #ifdef SENSOR_GPS_CONFLICT } #endif diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 36bd3fbb3..1b997500a 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -229,7 +229,7 @@ void cpuDeepSleep(uint32_t msecToWake) // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); #ifdef ESP32S3_WAKE_TYPE - esp_sleep_enable_ext1_wakeup(gpioMask, ESP32S3_WAKE_TYPE); + esp_sleep_enable_ext1_wakeup(gpioMask, ESP32S3_WAKE_TYPE); #else #if SOC_PM_SUPPORT_EXT_WAKEUP #ifdef CONFIG_IDF_TARGET_ESP32 @@ -240,7 +240,7 @@ void cpuDeepSleep(uint32_t msecToWake) #endif #endif -#endif //#end ESP32S3_WAKE_TYPE +#endif // #end ESP32S3_WAKE_TYPE #endif // We want RTC peripherals to stay on diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index b27f9df0c..1405ea4f3 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -198,15 +198,13 @@ void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth\n"); - uint8_t connection_num=Bluefruit.connected(); - if(connection_num) - { - for(uint8_t i=0;i 100ms will reset the L76K +// #define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K #define GPS_RESET_MODE LOW #define PIN_GPS_EN (21) #define GPS_EN_ACTIVE HIGH From 464f270b12e31d5ec3545ca9033a38ab2c4d3a0e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 13 Aug 2024 06:56:20 -0500 Subject: [PATCH 0839/1377] More explicit guards for attempting to set RTC (#4452) * Guard against timesources from the mesh if we have good time * Trunk * Consider phone time in the past 24 hours authoritative as well * Rename * GPS can be null * Declaration * Remove RemoteHardware * Explicitly remove GPS * Exclude GPS earlier for RAK2560 --- arch/stm32/stm32.ini | 3 ++- src/gps/RTC.cpp | 4 ++++ src/gps/RTC.h | 2 ++ src/modules/PositionModule.cpp | 13 ++++++++++++- src/modules/PositionModule.h | 1 + variants/rak2560/platformio.ini | 1 + variants/rak2560/variant.h | 2 +- 7 files changed, 23 insertions(+), 3 deletions(-) diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index ffbc51680..050dbf7f0 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -12,6 +12,7 @@ build_flags = -flto -Isrc/platform/stm32wl -g -DMESHTASTIC_MINIMIZE_BUILD + -DMESHTASTIC_EXCLUDE_GPS -DDEBUG_MUTE ; -DVECT_TAB_OFFSET=0x08000000 -DconfigUSE_CMSIS_RTOS_V2=1 @@ -21,7 +22,7 @@ build_flags = -fdata-sections build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - board_upload.offset_address = 0x08000000 upload_protocol = stlink diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 710baca98..070038672 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -6,6 +6,7 @@ #include static RTCQuality currentQuality = RTCQualityNone; +uint32_t lastSetFromPhoneNtpOrGps = 0; RTCQuality getRTCQuality() { @@ -121,6 +122,9 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) if (shouldSet) { currentQuality = q; lastSetMsec = now; + if (currentQuality >= RTCQualityNTP) { + lastSetFromPhoneNtpOrGps = now; + } // This delta value works on all platforms timeStartMsec = now; diff --git a/src/gps/RTC.h b/src/gps/RTC.h index d31e85433..caa48dc06 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -24,6 +24,8 @@ enum RTCQuality { RTCQuality getRTCQuality(); +extern uint32_t lastSetFromPhoneNtpOrGps; + /// If we haven't yet set our RTC this boot, set it from a GPS derived time bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); bool perhapsSetRTC(RTCQuality q, struct tm &t); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 5da918049..1618ba3ed 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -90,7 +90,6 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // will always be an equivalent or lesser RTCQuality (RTCQualityNTP or RTCQualityNet). force = true; #endif - // Set from phone RTC Quality to RTCQualityNTP since it should be approximately so trySetRtc(p, isLocal, force); } @@ -126,14 +125,26 @@ void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate) { + if (hasQualityTimesource() && !isLocal) { + LOG_DEBUG("Ignoring time from mesh because we have a GPS, RTC, or Phone/NTP time source in the past day\n"); + return; + } struct timeval tv; uint32_t secs = p.time; tv.tv_sec = secs; tv.tv_usec = 0; + perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv, forceUpdate); } +bool PositionModule::hasQualityTimesource() +{ + bool setFromPhoneOrNtpToday = (millis() - lastSetFromPhoneNtpOrGps) <= (SEC_PER_DAY * 1000UL); + bool hasGpsOrRtc = (gps && gps->isConnected()) || (rtc_found.address != ScanI2C::ADDRESS_NONE.address); + return hasGpsOrRtc || setFromPhoneOrNtpToday; +} + meshtastic_MeshPacket *PositionModule::allocReply() { if (precision == 0) { diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index a5277aff6..c4ef66501 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -60,6 +60,7 @@ class PositionModule : public ProtobufModule, private concu void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false); uint32_t precision; void sendLostAndFoundText(); + bool hasQualityTimesource(); const uint32_t minimumTimeThreshold = Default::getConfiguredOrDefaultMsScaled(config.position.broadcast_smart_minimum_interval_secs, 30, numOnlineNodes); diff --git a/variants/rak2560/platformio.ini b/variants/rak2560/platformio.ini index 96c1bfa92..93c7acf71 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/rak2560/platformio.ini @@ -6,6 +6,7 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -Ivariants/rak2560 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DMESHTASTIC_EXCLUDE_GPS=1 -DHAS_RAKPROT=1 ; Define if RAk OneWireSerial is used (disables GPS) build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak2560> + + + lib_deps = diff --git a/variants/rak2560/variant.h b/variants/rak2560/variant.h index 0c1c9aed9..8e5d90553 100644 --- a/variants/rak2560/variant.h +++ b/variants/rak2560/variant.h @@ -227,7 +227,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // #define GPS_TX_PIN PIN_SERIAL2_TX // #define PIN_GPS_EN PIN_3V3_EN // Disable GPS -#define MESHTASTIC_EXCLUDE_GPS 1 +// #define MESHTASTIC_EXCLUDE_GPS 1 // Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press // RAK12002 RTC Module From 8d1a34a4bf074aa3a5302d2105d128279f9a4fb1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 07:25:05 -0500 Subject: [PATCH 0840/1377] Protobufs --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 12 +- src/mesh/generated/meshtastic/config.pb.cpp | 3 + src/mesh/generated/meshtastic/config.pb.h | 83 +++++++++-- src/mesh/generated/meshtastic/deviceonly.pb.h | 4 +- src/mesh/generated/meshtastic/localonly.pb.h | 14 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 5 +- src/mesh/generated/meshtastic/mesh.pb.h | 110 +++++++++++--- src/mesh/generated/meshtastic/telemetry.pb.h | 136 +++++++++++------- 9 files changed, 273 insertions(+), 96 deletions(-) diff --git a/protobufs b/protobufs index c9ca0dbe9..126293c38 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c9ca0dbe9cc7105399e0486c07e0601f849b94af +Subproject commit 126293c38ff974ca253606c36da48cfab7fe6446 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index bef2abf9b..d0e643dff 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -168,8 +168,6 @@ typedef struct _meshtastic_AdminMessage { bool begin_edit_settings; /* Commits an open transaction for any edits made to config, module config, owner, and channel settings */ bool commit_edit_settings; - /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ - int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */ int32_t reboot_ota_seconds; @@ -180,8 +178,8 @@ typedef struct _meshtastic_AdminMessage { int32_t reboot_seconds; /* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown) */ int32_t shutdown_seconds; - /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */ - int32_t factory_reset_config; + /* Tell the node to factory reset, all device settings will be returned to factory defaults. */ + int32_t factory_reset; /* Tell the node to reset the nodedb. */ int32_t nodedb_reset; }; @@ -256,12 +254,11 @@ extern "C" { #define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 -#define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 #define meshtastic_AdminMessage_reboot_seconds_tag 97 #define meshtastic_AdminMessage_shutdown_seconds_tag 98 -#define meshtastic_AdminMessage_factory_reset_config_tag 99 +#define meshtastic_AdminMessage_factory_reset_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 /* Struct field encoding specification for nanopb */ @@ -301,12 +298,11 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset,factory_reset), 99) \ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index bb82198c0..c6274aed4 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -33,6 +33,9 @@ PB_BIND(meshtastic_Config_LoRaConfig, meshtastic_Config_LoRaConfig, 2) PB_BIND(meshtastic_Config_BluetoothConfig, meshtastic_Config_BluetoothConfig, AUTO) +PB_BIND(meshtastic_Config_SecurityConfig, meshtastic_Config_SecurityConfig, AUTO) + + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 44a86f4d6..dbb0deb00 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -248,7 +248,8 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset { meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST = 0, /* Long Range - Slow */ meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW = 1, - /* Very Long Range - Slow */ + /* Very Long Range - Slow + Deprecated in 2.5: Works only with txco and is unusably slow */ meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW = 2, /* Medium Range - Slow */ meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW = 3, @@ -259,7 +260,11 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset { /* Short Range - Fast */ meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST = 6, /* Long Range - Moderately Fast */ - meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE = 7 + meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE = 7, + /* Short Range - Turbo + This is the fastest preset and the only one with 500kHz bandwidth. + It is not legal to use in all regions due to this wider bandwidth. */ + meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8 } meshtastic_Config_LoRaConfig_ModemPreset; typedef enum _meshtastic_Config_BluetoothConfig_PairingMode { @@ -276,10 +281,12 @@ typedef enum _meshtastic_Config_BluetoothConfig_PairingMode { typedef struct _meshtastic_Config_DeviceConfig { /* Sets the role of node */ meshtastic_Config_DeviceConfig_Role role; - /* Disabling this will disable the SerialConsole by not initilizing the StreamAPI */ + /* Disabling this will disable the SerialConsole by not initilizing the StreamAPI + Moved to SecurityConfig */ bool serial_enabled; /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). - Set this to true to leave the debug log outputting even when API is active. */ + Set this to true to leave the debug log outputting even when API is active. + Moved to SecurityConfig */ bool debug_log_enabled; /* For boards without a hard wired button, this is the pin number that will be used Boards that have more than one button can swap the function with this one. defaults to BUTTON_PIN if defined. */ @@ -295,7 +302,8 @@ typedef struct _meshtastic_Config_DeviceConfig { /* Treat double tap interrupt on supported accelerometers as a button press if set to true */ bool double_tap_as_button_press; /* If true, device is considered to be "managed" by a mesh administrator - Clients should then limit available configuration and administrative options inside the user interface */ + Clients should then limit available configuration and administrative options inside the user interface + Moved to SecurityConfig */ bool is_managed; /* Disables the triple-press of user button to enable or disable GPS */ bool disable_triple_click; @@ -515,10 +523,37 @@ typedef struct _meshtastic_Config_BluetoothConfig { meshtastic_Config_BluetoothConfig_PairingMode mode; /* Specified PIN for PairingMode.FixedPin */ uint32_t fixed_pin; - /* Enables device (serial style logs) over Bluetooth */ + /* Enables device (serial style logs) over Bluetooth + Moved to SecurityConfig */ bool device_logging_enabled; } meshtastic_Config_BluetoothConfig; +typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_public_key_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_private_key_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_admin_key_t; +typedef struct _meshtastic_Config_SecurityConfig { + /* The public key of the user's device. + Sent out to other nodes on the mesh to allow them to compute a shared secret key. */ + meshtastic_Config_SecurityConfig_public_key_t public_key; + /* The private key of the device. + Used to create a shared key with a remote device. */ + meshtastic_Config_SecurityConfig_private_key_t private_key; + /* The public key authorized to send admin messages to this node. */ + meshtastic_Config_SecurityConfig_admin_key_t admin_key; + /* If true, device is considered to be "managed" by a mesh administrator via admin messages + Device is managed by a mesh administrator. */ + bool is_managed; + /* Serial Console over the Stream API." */ + bool serial_enabled; + /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). + Output live debug logging over serial. */ + bool debug_log_api_enabled; + /* Enables device (serial style logs) over Bluetooth */ + bool bluetooth_logging_enabled; + /* Allow incoming device control over the insecure legacy admin channel. */ + bool admin_channel_enabled; +} meshtastic_Config_SecurityConfig; + typedef struct _meshtastic_Config { pb_size_t which_payload_variant; union { @@ -529,6 +564,7 @@ typedef struct _meshtastic_Config { meshtastic_Config_DisplayConfig display; meshtastic_Config_LoRaConfig lora; meshtastic_Config_BluetoothConfig bluetooth; + meshtastic_Config_SecurityConfig security; } payload_variant; } meshtastic_Config; @@ -583,8 +619,8 @@ extern "C" { #define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_SG_923+1)) #define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST -#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE -#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE+1)) +#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO +#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO+1)) #define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN #define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN @@ -612,6 +648,7 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_mode_ENUMTYPE meshtastic_Config_BluetoothConfig_PairingMode + /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -622,6 +659,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} @@ -631,6 +669,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Config_DeviceConfig_role_tag 1 @@ -711,6 +750,14 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 #define meshtastic_Config_BluetoothConfig_device_logging_enabled_tag 4 +#define meshtastic_Config_SecurityConfig_public_key_tag 1 +#define meshtastic_Config_SecurityConfig_private_key_tag 2 +#define meshtastic_Config_SecurityConfig_admin_key_tag 3 +#define meshtastic_Config_SecurityConfig_is_managed_tag 4 +#define meshtastic_Config_SecurityConfig_serial_enabled_tag 5 +#define meshtastic_Config_SecurityConfig_debug_log_api_enabled_tag 6 +#define meshtastic_Config_SecurityConfig_bluetooth_logging_enabled_tag 7 +#define meshtastic_Config_SecurityConfig_admin_channel_enabled_tag 8 #define meshtastic_Config_device_tag 1 #define meshtastic_Config_position_tag 2 #define meshtastic_Config_power_tag 3 @@ -718,6 +765,7 @@ extern "C" { #define meshtastic_Config_display_tag 5 #define meshtastic_Config_lora_tag 6 #define meshtastic_Config_bluetooth_tag 7 +#define meshtastic_Config_security_tag 8 /* Struct field encoding specification for nanopb */ #define meshtastic_Config_FIELDLIST(X, a) \ @@ -727,7 +775,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,power,payload_variant.power) X(a, STATIC, ONEOF, MESSAGE, (payload_variant,network,payload_variant.network), 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,display,payload_variant.display), 5) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lora,payload_variant.lora), 6) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bluetooth), 7) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bluetooth), 7) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,security,payload_variant.security), 8) #define meshtastic_Config_CALLBACK NULL #define meshtastic_Config_DEFAULT NULL #define meshtastic_Config_payload_variant_device_MSGTYPE meshtastic_Config_DeviceConfig @@ -737,6 +786,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bl #define meshtastic_Config_payload_variant_display_MSGTYPE meshtastic_Config_DisplayConfig #define meshtastic_Config_payload_variant_lora_MSGTYPE meshtastic_Config_LoRaConfig #define meshtastic_Config_payload_variant_bluetooth_MSGTYPE meshtastic_Config_BluetoothConfig +#define meshtastic_Config_payload_variant_security_MSGTYPE meshtastic_Config_SecurityConfig #define meshtastic_Config_DeviceConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, role, 1) \ @@ -849,6 +899,18 @@ X(a, STATIC, SINGULAR, BOOL, device_logging_enabled, 4) #define meshtastic_Config_BluetoothConfig_CALLBACK NULL #define meshtastic_Config_BluetoothConfig_DEFAULT NULL +#define meshtastic_Config_SecurityConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 1) \ +X(a, STATIC, SINGULAR, BYTES, private_key, 2) \ +X(a, STATIC, SINGULAR, BYTES, admin_key, 3) \ +X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \ +X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \ +X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \ +X(a, STATIC, SINGULAR, BOOL, bluetooth_logging_enabled, 7) \ +X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8) +#define meshtastic_Config_SecurityConfig_CALLBACK NULL +#define meshtastic_Config_SecurityConfig_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_Config_msg; extern const pb_msgdesc_t meshtastic_Config_DeviceConfig_msg; extern const pb_msgdesc_t meshtastic_Config_PositionConfig_msg; @@ -858,6 +920,7 @@ extern const pb_msgdesc_t meshtastic_Config_NetworkConfig_IpV4Config_msg; extern const pb_msgdesc_t meshtastic_Config_DisplayConfig_msg; extern const pb_msgdesc_t meshtastic_Config_LoRaConfig_msg; extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; +extern const pb_msgdesc_t meshtastic_Config_SecurityConfig_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Config_fields &meshtastic_Config_msg @@ -869,6 +932,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_DisplayConfig_fields &meshtastic_Config_DisplayConfig_msg #define meshtastic_Config_LoRaConfig_fields &meshtastic_Config_LoRaConfig_msg #define meshtastic_Config_BluetoothConfig_fields &meshtastic_Config_BluetoothConfig_msg +#define meshtastic_Config_SecurityConfig_fields &meshtastic_Config_SecurityConfig_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size @@ -880,6 +944,7 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 +#define meshtastic_Config_SecurityConfig_size 112 #define meshtastic_Config_size 199 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index eb37f4f95..2c91fe30e 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -306,8 +306,8 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 -#define meshtastic_NodeInfoLite_size 166 -#define meshtastic_OEMStore_size 3388 +#define meshtastic_NodeInfoLite_size 200 +#define meshtastic_OEMStore_size 3502 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 983f48ad3..c612b24ab 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -38,6 +38,9 @@ typedef struct _meshtastic_LocalConfig { incompatible changes This integer is set at build time and is private to NodeDB.cpp in the device code. */ uint32_t version; + /* The part of the config that is specific to Security settings */ + bool has_security; + meshtastic_Config_SecurityConfig security; } meshtastic_LocalConfig; typedef struct _meshtastic_LocalModuleConfig { @@ -92,9 +95,9 @@ extern "C" { #endif /* Initializer values for message structs */ -#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0} +#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} #define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} -#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0} +#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} #define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ @@ -106,6 +109,7 @@ extern "C" { #define meshtastic_LocalConfig_lora_tag 6 #define meshtastic_LocalConfig_bluetooth_tag 7 #define meshtastic_LocalConfig_version_tag 8 +#define meshtastic_LocalConfig_security_tag 9 #define meshtastic_LocalModuleConfig_mqtt_tag 1 #define meshtastic_LocalModuleConfig_serial_tag 2 #define meshtastic_LocalModuleConfig_external_notification_tag 3 @@ -130,7 +134,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, network, 4) \ X(a, STATIC, OPTIONAL, MESSAGE, display, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, lora, 6) \ X(a, STATIC, OPTIONAL, MESSAGE, bluetooth, 7) \ -X(a, STATIC, SINGULAR, UINT32, version, 8) +X(a, STATIC, SINGULAR, UINT32, version, 8) \ +X(a, STATIC, OPTIONAL, MESSAGE, security, 9) #define meshtastic_LocalConfig_CALLBACK NULL #define meshtastic_LocalConfig_DEFAULT NULL #define meshtastic_LocalConfig_device_MSGTYPE meshtastic_Config_DeviceConfig @@ -140,6 +145,7 @@ X(a, STATIC, SINGULAR, UINT32, version, 8) #define meshtastic_LocalConfig_display_MSGTYPE meshtastic_Config_DisplayConfig #define meshtastic_LocalConfig_lora_MSGTYPE meshtastic_Config_LoRaConfig #define meshtastic_LocalConfig_bluetooth_MSGTYPE meshtastic_Config_BluetoothConfig +#define meshtastic_LocalConfig_security_MSGTYPE meshtastic_Config_SecurityConfig #define meshtastic_LocalModuleConfig_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, mqtt, 1) \ @@ -181,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 555 +#define meshtastic_LocalConfig_size 669 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 3fa81e131..8c8b9ded7 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -30,7 +30,7 @@ PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2) PB_BIND(meshtastic_MeshPacket, meshtastic_MeshPacket, 2) -PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, AUTO) +PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, 2) PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO) @@ -45,6 +45,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2) +PB_BIND(meshtastic_ClientNotification, meshtastic_ClientNotification, 2) + + PB_BIND(meshtastic_FileInfo, meshtastic_FileInfo, AUTO) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 955484cb0..70423f673 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -374,10 +374,13 @@ typedef enum _meshtastic_LogRecord_Level { typedef struct _meshtastic_Position { /* The new preferred location encoding, multiply by 1e-7 to get degrees in floating point */ + bool has_latitude_i; int32_t latitude_i; /* TODO: REPLACE */ + bool has_longitude_i; int32_t longitude_i; /* In meters above MSL (but see issue #359) */ + bool has_altitude; int32_t altitude; /* This is usually not sent over the mesh (to save space), but it is sent from the phone so that the local device can set its time if it is sent over @@ -393,8 +396,10 @@ typedef struct _meshtastic_Position { /* Pos. timestamp milliseconds adjustment (rarely available or required) */ int32_t timestamp_millis_adjust; /* HAE altitude in meters - can be used instead of MSL altitude */ + bool has_altitude_hae; int32_t altitude_hae; /* Geoidal separation in meters */ + bool has_altitude_geoidal_separation; int32_t altitude_geoidal_separation; /* Horizontal, Vertical and Position Dilution of Precision, in 1/100 units - PDOP is sufficient for most cases @@ -416,8 +421,10 @@ typedef struct _meshtastic_Position { - "heading" is where the fuselage points (measured in horizontal plane) - "yaw" indicates a relative rotation about the vertical axis TODO: REMOVE/INTEGRATE */ + bool has_ground_speed; uint32_t ground_speed; /* TODO: REPLACE */ + bool has_ground_track; uint32_t ground_track; /* GPS fix quality (from NMEA GxGGA statement or similar) */ uint32_t fix_quality; @@ -439,6 +446,7 @@ typedef struct _meshtastic_Position { uint32_t precision_bits; } meshtastic_Position; +typedef PB_BYTES_ARRAY_T(32) meshtastic_User_public_key_t; /* Broadcast when a newly powered mesh node wants to find a node num it can use Sent from the phone over bluetooth to set the user id for the owner of this node. Also sent from nodes to each other when a new node signs on (so all clients can have this info) @@ -485,6 +493,9 @@ typedef struct _meshtastic_User { bool is_licensed; /* Indicates that the user's role in the mesh */ meshtastic_Config_DeviceConfig_Role role; + /* The public key of the user's device. + This is sent out to other nodes on the mesh to allow them to compute a shared secret key. */ + meshtastic_User_public_key_t public_key; } meshtastic_User; /* A message used in our Dynamic Source Routing protocol (RFC 4728 based) */ @@ -546,8 +557,10 @@ typedef struct _meshtastic_Waypoint { /* Id of the waypoint */ uint32_t id; /* latitude_i */ + bool has_latitude_i; int32_t latitude_i; /* longitude_i */ + bool has_longitude_i; int32_t longitude_i; /* Time the waypoint is to expire (epoch) */ uint32_t expire; @@ -579,6 +592,7 @@ typedef struct _meshtastic_MqttClientProxyMessage { } meshtastic_MqttClientProxyMessage; typedef PB_BYTES_ARRAY_T(256) meshtastic_MeshPacket_encrypted_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_MeshPacket_public_key_t; /* A packet envelope sent/received over the mesh only payload_variant is sent in the payload portion of the LORA packet. The other fields are either not sent at all, or sent in the special 16 byte LORA header. */ @@ -649,6 +663,10 @@ typedef struct _meshtastic_MeshPacket { /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */ uint8_t hop_start; + /* Records the public key the packet was encrypted with, if applicable. */ + meshtastic_MeshPacket_public_key_t public_key; + /* Indicates whether the packet was en/decrypted using PKI */ + bool pki_encrypted; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -738,6 +756,22 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; +/* A notification message from the device to the client + To be used for important messages that should to be displayed to the user + in the form of push notifications or validation messages when saving + invalid configuration. */ +typedef struct _meshtastic_ClientNotification { + /* The id of the packet we're notifying in response to */ + bool has_reply_id; + uint32_t reply_id; + /* Seconds since 1970 - or 0 for unknown/unset */ + uint32_t time; + /* The level type of notification */ + meshtastic_LogRecord_Level level; + /* The message body of the notification */ + char message[400]; +} meshtastic_ClientNotification; + /* Individual File info for the device */ typedef struct _meshtastic_FileInfo { /* The fully qualified path of the file */ @@ -852,6 +886,8 @@ typedef struct _meshtastic_FromRadio { meshtastic_MqttClientProxyMessage mqttClientProxyMessage; /* File system manifest messages */ meshtastic_FileInfo fileInfo; + /* Notification message to the client */ + meshtastic_ClientNotification clientNotification; }; } meshtastic_FromRadio; @@ -994,6 +1030,8 @@ extern "C" { +#define meshtastic_ClientNotification_level_ENUMTYPE meshtastic_LogRecord_Level + #define meshtastic_Compressed_portnum_ENUMTYPE meshtastic_PortNum @@ -1010,19 +1048,20 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} +#define meshtastic_Position_init_default {false, 0, false, 0, false, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, false, 0, false, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} -#define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0} +#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} +#define meshtastic_ClientNotification_init_default {false, 0, 0, _meshtastic_LogRecord_Level_MIN, ""} #define meshtastic_FileInfo_init_default {"", 0} #define meshtastic_ToRadio_init_default {0, {meshtastic_MeshPacket_init_default}} #define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}} @@ -1034,19 +1073,20 @@ extern "C" { #define meshtastic_ChunkedPayload_init_default {0, 0, 0, {0, {0}}} #define meshtastic_resend_chunks_init_default {{{NULL}, NULL}} #define meshtastic_ChunkedPayloadResponse_init_default {0, 0, {0}} -#define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} +#define meshtastic_Position_init_zero {false, 0, false, 0, false, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, false, 0, false, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} -#define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0} +#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} +#define meshtastic_ClientNotification_init_zero {false, 0, 0, _meshtastic_LogRecord_Level_MIN, ""} #define meshtastic_FileInfo_init_zero {"", 0} #define meshtastic_ToRadio_init_zero {0, {meshtastic_MeshPacket_init_zero}} #define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}} @@ -1090,6 +1130,7 @@ extern "C" { #define meshtastic_User_hw_model_tag 5 #define meshtastic_User_is_licensed_tag 6 #define meshtastic_User_role_tag 7 +#define meshtastic_User_public_key_tag 8 #define meshtastic_RouteDiscovery_route_tag 1 #define meshtastic_Routing_route_request_tag 1 #define meshtastic_Routing_route_reply_tag 2 @@ -1129,6 +1170,8 @@ extern "C" { #define meshtastic_MeshPacket_delayed_tag 13 #define meshtastic_MeshPacket_via_mqtt_tag 14 #define meshtastic_MeshPacket_hop_start_tag 15 +#define meshtastic_MeshPacket_public_key_tag 16 +#define meshtastic_MeshPacket_pki_encrypted_tag 17 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -1150,6 +1193,10 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 +#define meshtastic_ClientNotification_reply_id_tag 1 +#define meshtastic_ClientNotification_time_tag 2 +#define meshtastic_ClientNotification_level_tag 3 +#define meshtastic_ClientNotification_message_tag 4 #define meshtastic_FileInfo_file_name_tag 1 #define meshtastic_FileInfo_size_bytes_tag 2 #define meshtastic_Compressed_portnum_tag 1 @@ -1187,6 +1234,7 @@ extern "C" { #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 #define meshtastic_FromRadio_fileInfo_tag 15 +#define meshtastic_FromRadio_clientNotification_tag 16 #define meshtastic_ToRadio_packet_tag 1 #define meshtastic_ToRadio_want_config_id_tag 3 #define meshtastic_ToRadio_disconnect_tag 4 @@ -1207,22 +1255,22 @@ extern "C" { /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ -X(a, STATIC, SINGULAR, INT32, altitude, 3) \ +X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 1) \ +X(a, STATIC, OPTIONAL, SFIXED32, longitude_i, 2) \ +X(a, STATIC, OPTIONAL, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, FIXED32, time, 4) \ X(a, STATIC, SINGULAR, UENUM, location_source, 5) \ X(a, STATIC, SINGULAR, UENUM, altitude_source, 6) \ X(a, STATIC, SINGULAR, FIXED32, timestamp, 7) \ X(a, STATIC, SINGULAR, INT32, timestamp_millis_adjust, 8) \ -X(a, STATIC, SINGULAR, SINT32, altitude_hae, 9) \ -X(a, STATIC, SINGULAR, SINT32, altitude_geoidal_separation, 10) \ +X(a, STATIC, OPTIONAL, SINT32, altitude_hae, 9) \ +X(a, STATIC, OPTIONAL, SINT32, altitude_geoidal_separation, 10) \ X(a, STATIC, SINGULAR, UINT32, PDOP, 11) \ X(a, STATIC, SINGULAR, UINT32, HDOP, 12) \ X(a, STATIC, SINGULAR, UINT32, VDOP, 13) \ X(a, STATIC, SINGULAR, UINT32, gps_accuracy, 14) \ -X(a, STATIC, SINGULAR, UINT32, ground_speed, 15) \ -X(a, STATIC, SINGULAR, UINT32, ground_track, 16) \ +X(a, STATIC, OPTIONAL, UINT32, ground_speed, 15) \ +X(a, STATIC, OPTIONAL, UINT32, ground_track, 16) \ X(a, STATIC, SINGULAR, UINT32, fix_quality, 17) \ X(a, STATIC, SINGULAR, UINT32, fix_type, 18) \ X(a, STATIC, SINGULAR, UINT32, sats_in_view, 19) \ @@ -1240,7 +1288,8 @@ X(a, STATIC, SINGULAR, STRING, short_name, 3) \ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) \ X(a, STATIC, SINGULAR, UENUM, hw_model, 5) \ X(a, STATIC, SINGULAR, BOOL, is_licensed, 6) \ -X(a, STATIC, SINGULAR, UENUM, role, 7) +X(a, STATIC, SINGULAR, UENUM, role, 7) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 8) #define meshtastic_User_CALLBACK NULL #define meshtastic_User_DEFAULT NULL @@ -1272,8 +1321,8 @@ X(a, STATIC, SINGULAR, FIXED32, emoji, 8) #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 2) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 3) \ +X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \ +X(a, STATIC, OPTIONAL, SFIXED32, longitude_i, 3) \ X(a, STATIC, SINGULAR, UINT32, expire, 4) \ X(a, STATIC, SINGULAR, UINT32, locked_to, 5) \ X(a, STATIC, SINGULAR, STRING, name, 6) \ @@ -1305,7 +1354,9 @@ X(a, STATIC, SINGULAR, UENUM, priority, 11) \ X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \ X(a, STATIC, SINGULAR, UENUM, delayed, 13) \ X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \ -X(a, STATIC, SINGULAR, UINT32, hop_start, 15) +X(a, STATIC, SINGULAR, UINT32, hop_start, 15) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 16) \ +X(a, STATIC, SINGULAR, BOOL, pki_encrypted, 17) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -1365,7 +1416,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,queueStatus,queueStatus), 1 X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,clientNotification,clientNotification), 16) #define meshtastic_FromRadio_CALLBACK NULL #define meshtastic_FromRadio_DEFAULT NULL #define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket @@ -1380,6 +1432,15 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) #define meshtastic_FromRadio_payload_variant_metadata_MSGTYPE meshtastic_DeviceMetadata #define meshtastic_FromRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage #define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo +#define meshtastic_FromRadio_payload_variant_clientNotification_MSGTYPE meshtastic_ClientNotification + +#define meshtastic_ClientNotification_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, UINT32, reply_id, 1) \ +X(a, STATIC, SINGULAR, FIXED32, time, 2) \ +X(a, STATIC, SINGULAR, UENUM, level, 3) \ +X(a, STATIC, SINGULAR, STRING, message, 4) +#define meshtastic_ClientNotification_CALLBACK NULL +#define meshtastic_ClientNotification_DEFAULT NULL #define meshtastic_FileInfo_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, file_name, 1) \ @@ -1485,6 +1546,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg; extern const pb_msgdesc_t meshtastic_LogRecord_msg; extern const pb_msgdesc_t meshtastic_QueueStatus_msg; extern const pb_msgdesc_t meshtastic_FromRadio_msg; +extern const pb_msgdesc_t meshtastic_ClientNotification_msg; extern const pb_msgdesc_t meshtastic_FileInfo_msg; extern const pb_msgdesc_t meshtastic_ToRadio_msg; extern const pb_msgdesc_t meshtastic_Compressed_msg; @@ -1511,6 +1573,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg #define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg #define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg +#define meshtastic_ClientNotification_fields &meshtastic_ClientNotification_msg #define meshtastic_FileInfo_fields &meshtastic_FileInfo_msg #define meshtastic_ToRadio_fields &meshtastic_ToRadio_msg #define meshtastic_Compressed_fields &meshtastic_Compressed_msg @@ -1528,6 +1591,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; /* meshtastic_ChunkedPayloadResponse_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size #define meshtastic_ChunkedPayload_size 245 +#define meshtastic_ClientNotification_size 415 #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 @@ -1535,19 +1599,19 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 426 -#define meshtastic_MeshPacket_size 326 +#define meshtastic_MeshPacket_size 364 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 283 +#define meshtastic_NodeInfo_size 317 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 #define meshtastic_Routing_size 42 #define meshtastic_ToRadio_size 504 -#define meshtastic_User_size 79 +#define meshtastic_User_size 113 #define meshtastic_Waypoint_size 165 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index a4acd3f4a..60681916f 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -76,98 +76,138 @@ typedef enum _meshtastic_TelemetrySensorType { /* Key native device metrics such as battery level */ typedef struct _meshtastic_DeviceMetrics { /* 0-100 (>100 means powered) */ + bool has_battery_level; uint32_t battery_level; /* Voltage measured */ + bool has_voltage; float voltage; /* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). */ + bool has_channel_utilization; float channel_utilization; /* Percent of airtime for transmission used within the last hour. */ + bool has_air_util_tx; float air_util_tx; /* How long the device has been running since the last reboot (in seconds) */ + bool has_uptime_seconds; uint32_t uptime_seconds; } meshtastic_DeviceMetrics; /* Weather station or other environmental metrics */ typedef struct _meshtastic_EnvironmentMetrics { /* Temperature measured */ + bool has_temperature; float temperature; /* Relative humidity percent measured */ + bool has_relative_humidity; float relative_humidity; /* Barometric pressure in hPA measured */ + bool has_barometric_pressure; float barometric_pressure; /* Gas resistance in MOhm measured */ + bool has_gas_resistance; float gas_resistance; /* Voltage measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ + bool has_voltage; float voltage; /* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ + bool has_current; float current; /* relative scale IAQ value as measured by Bosch BME680 . value 0-500. Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ + bool has_iaq; uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ + bool has_distance; float distance; /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */ + bool has_lux; float lux; /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */ + bool has_white_lux; float white_lux; /* Infrared lux */ + bool has_ir_lux; float ir_lux; /* Ultraviolet lux */ + bool has_uv_lux; float uv_lux; /* Wind direction in degrees 0 degrees = North, 90 = East, etc... */ + bool has_wind_direction; uint16_t wind_direction; /* Wind speed in m/s */ + bool has_wind_speed; float wind_speed; /* Weight in KG */ + bool has_weight; float weight; /* Wind gust in m/s */ + bool has_wind_gust; float wind_gust; /* Wind lull in m/s */ + bool has_wind_lull; float wind_lull; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ typedef struct _meshtastic_PowerMetrics { /* Voltage (Ch1) */ + bool has_ch1_voltage; float ch1_voltage; /* Current (Ch1) */ + bool has_ch1_current; float ch1_current; /* Voltage (Ch2) */ + bool has_ch2_voltage; float ch2_voltage; /* Current (Ch2) */ + bool has_ch2_current; float ch2_current; /* Voltage (Ch3) */ + bool has_ch3_voltage; float ch3_voltage; /* Current (Ch3) */ + bool has_ch3_current; float ch3_current; } meshtastic_PowerMetrics; /* Air quality metrics */ typedef struct _meshtastic_AirQualityMetrics { /* Concentration Units Standard PM1.0 */ + bool has_pm10_standard; uint32_t pm10_standard; /* Concentration Units Standard PM2.5 */ + bool has_pm25_standard; uint32_t pm25_standard; /* Concentration Units Standard PM10.0 */ + bool has_pm100_standard; uint32_t pm100_standard; /* Concentration Units Environmental PM1.0 */ + bool has_pm10_environmental; uint32_t pm10_environmental; /* Concentration Units Environmental PM2.5 */ + bool has_pm25_environmental; uint32_t pm25_environmental; /* Concentration Units Environmental PM10.0 */ + bool has_pm100_environmental; uint32_t pm100_environmental; /* 0.3um Particle Count */ + bool has_particles_03um; uint32_t particles_03um; /* 0.5um Particle Count */ + bool has_particles_05um; uint32_t particles_05um; /* 1.0um Particle Count */ + bool has_particles_10um; uint32_t particles_10um; /* 2.5um Particle Count */ + bool has_particles_25um; uint32_t particles_25um; /* 5.0um Particle Count */ + bool has_particles_50um; uint32_t particles_50um; /* 10.0um Particle Count */ + bool has_particles_100um; uint32_t particles_100um; } meshtastic_AirQualityMetrics; @@ -214,16 +254,16 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} -#define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} -#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} -#define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} @@ -278,58 +318,58 @@ extern "C" { /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, battery_level, 1) \ -X(a, STATIC, SINGULAR, FLOAT, voltage, 2) \ -X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 3) \ -X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) \ -X(a, STATIC, SINGULAR, UINT32, uptime_seconds, 5) +X(a, STATIC, OPTIONAL, UINT32, battery_level, 1) \ +X(a, STATIC, OPTIONAL, FLOAT, voltage, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, channel_utilization, 3) \ +X(a, STATIC, OPTIONAL, FLOAT, air_util_tx, 4) \ +X(a, STATIC, OPTIONAL, UINT32, uptime_seconds, 5) #define meshtastic_DeviceMetrics_CALLBACK NULL #define meshtastic_DeviceMetrics_DEFAULT NULL #define meshtastic_EnvironmentMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, FLOAT, temperature, 1) \ -X(a, STATIC, SINGULAR, FLOAT, relative_humidity, 2) \ -X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) \ -X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ -X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ -X(a, STATIC, SINGULAR, FLOAT, current, 6) \ -X(a, STATIC, SINGULAR, UINT32, iaq, 7) \ -X(a, STATIC, SINGULAR, FLOAT, distance, 8) \ -X(a, STATIC, SINGULAR, FLOAT, lux, 9) \ -X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) \ -X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \ -X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \ -X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \ -X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \ -X(a, STATIC, SINGULAR, FLOAT, weight, 15) \ -X(a, STATIC, SINGULAR, FLOAT, wind_gust, 16) \ -X(a, STATIC, SINGULAR, FLOAT, wind_lull, 17) +X(a, STATIC, OPTIONAL, FLOAT, temperature, 1) \ +X(a, STATIC, OPTIONAL, FLOAT, relative_humidity, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, barometric_pressure, 3) \ +X(a, STATIC, OPTIONAL, FLOAT, gas_resistance, 4) \ +X(a, STATIC, OPTIONAL, FLOAT, voltage, 5) \ +X(a, STATIC, OPTIONAL, FLOAT, current, 6) \ +X(a, STATIC, OPTIONAL, UINT32, iaq, 7) \ +X(a, STATIC, OPTIONAL, FLOAT, distance, 8) \ +X(a, STATIC, OPTIONAL, FLOAT, lux, 9) \ +X(a, STATIC, OPTIONAL, FLOAT, white_lux, 10) \ +X(a, STATIC, OPTIONAL, FLOAT, ir_lux, 11) \ +X(a, STATIC, OPTIONAL, FLOAT, uv_lux, 12) \ +X(a, STATIC, OPTIONAL, UINT32, wind_direction, 13) \ +X(a, STATIC, OPTIONAL, FLOAT, wind_speed, 14) \ +X(a, STATIC, OPTIONAL, FLOAT, weight, 15) \ +X(a, STATIC, OPTIONAL, FLOAT, wind_gust, 16) \ +X(a, STATIC, OPTIONAL, FLOAT, wind_lull, 17) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL #define meshtastic_PowerMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, FLOAT, ch1_voltage, 1) \ -X(a, STATIC, SINGULAR, FLOAT, ch1_current, 2) \ -X(a, STATIC, SINGULAR, FLOAT, ch2_voltage, 3) \ -X(a, STATIC, SINGULAR, FLOAT, ch2_current, 4) \ -X(a, STATIC, SINGULAR, FLOAT, ch3_voltage, 5) \ -X(a, STATIC, SINGULAR, FLOAT, ch3_current, 6) +X(a, STATIC, OPTIONAL, FLOAT, ch1_voltage, 1) \ +X(a, STATIC, OPTIONAL, FLOAT, ch1_current, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, ch2_voltage, 3) \ +X(a, STATIC, OPTIONAL, FLOAT, ch2_current, 4) \ +X(a, STATIC, OPTIONAL, FLOAT, ch3_voltage, 5) \ +X(a, STATIC, OPTIONAL, FLOAT, ch3_current, 6) #define meshtastic_PowerMetrics_CALLBACK NULL #define meshtastic_PowerMetrics_DEFAULT NULL #define meshtastic_AirQualityMetrics_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, pm10_standard, 1) \ -X(a, STATIC, SINGULAR, UINT32, pm25_standard, 2) \ -X(a, STATIC, SINGULAR, UINT32, pm100_standard, 3) \ -X(a, STATIC, SINGULAR, UINT32, pm10_environmental, 4) \ -X(a, STATIC, SINGULAR, UINT32, pm25_environmental, 5) \ -X(a, STATIC, SINGULAR, UINT32, pm100_environmental, 6) \ -X(a, STATIC, SINGULAR, UINT32, particles_03um, 7) \ -X(a, STATIC, SINGULAR, UINT32, particles_05um, 8) \ -X(a, STATIC, SINGULAR, UINT32, particles_10um, 9) \ -X(a, STATIC, SINGULAR, UINT32, particles_25um, 10) \ -X(a, STATIC, SINGULAR, UINT32, particles_50um, 11) \ -X(a, STATIC, SINGULAR, UINT32, particles_100um, 12) +X(a, STATIC, OPTIONAL, UINT32, pm10_standard, 1) \ +X(a, STATIC, OPTIONAL, UINT32, pm25_standard, 2) \ +X(a, STATIC, OPTIONAL, UINT32, pm100_standard, 3) \ +X(a, STATIC, OPTIONAL, UINT32, pm10_environmental, 4) \ +X(a, STATIC, OPTIONAL, UINT32, pm25_environmental, 5) \ +X(a, STATIC, OPTIONAL, UINT32, pm100_environmental, 6) \ +X(a, STATIC, OPTIONAL, UINT32, particles_03um, 7) \ +X(a, STATIC, OPTIONAL, UINT32, particles_05um, 8) \ +X(a, STATIC, OPTIONAL, UINT32, particles_10um, 9) \ +X(a, STATIC, OPTIONAL, UINT32, particles_25um, 10) \ +X(a, STATIC, OPTIONAL, UINT32, particles_50um, 11) \ +X(a, STATIC, OPTIONAL, UINT32, particles_100um, 12) #define meshtastic_AirQualityMetrics_CALLBACK NULL #define meshtastic_AirQualityMetrics_DEFAULT NULL From da53b8152d8a3aa813bd0b9e1cb3093c02819a30 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 07:51:59 -0500 Subject: [PATCH 0841/1377] Protos --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index 126293c38..778667d93 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 126293c38ff974ca253606c36da48cfab7fe6446 +Subproject commit 778667d93b9769d51c386c461456bdec4f14f433 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index d0e643dff..bef2abf9b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -168,6 +168,8 @@ typedef struct _meshtastic_AdminMessage { bool begin_edit_settings; /* Commits an open transaction for any edits made to config, module config, owner, and channel settings */ bool commit_edit_settings; + /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ + int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */ int32_t reboot_ota_seconds; @@ -178,8 +180,8 @@ typedef struct _meshtastic_AdminMessage { int32_t reboot_seconds; /* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown) */ int32_t shutdown_seconds; - /* Tell the node to factory reset, all device settings will be returned to factory defaults. */ - int32_t factory_reset; + /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */ + int32_t factory_reset_config; /* Tell the node to reset the nodedb. */ int32_t nodedb_reset; }; @@ -254,11 +256,12 @@ extern "C" { #define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 +#define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 #define meshtastic_AdminMessage_reboot_seconds_tag 97 #define meshtastic_AdminMessage_shutdown_seconds_tag 98 -#define meshtastic_AdminMessage_factory_reset_tag 99 +#define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 /* Struct field encoding specification for nanopb */ @@ -298,11 +301,12 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset,factory_reset), 99) \ +X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL From 95682c9095e1c36e92e846dd8c23639dfb4a6a0b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 08:33:42 -0500 Subject: [PATCH 0842/1377] Add ClientNotification hello world --- src/mesh/MeshService.cpp | 20 +++++++++++++++++++- src/mesh/MeshService.h | 10 ++++++++++ src/mesh/PhoneAPI.h | 3 +++ src/mesh/Router.cpp | 8 ++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 697644a4f..d05f43b01 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -48,14 +48,18 @@ static MemoryDynamic staticMqttClientProxyMes static MemoryDynamic staticQueueStatusPool; +static MemoryDynamic staticClientNotificationPool; + Allocator &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool; +Allocator &clientNotificationPool = staticClientNotificationPool; + Allocator &queueStatusPool = staticQueueStatusPool; #include "Router.h" MeshService::MeshService() - : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE) + : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) { lastQueueStatus = {0, 0, 16, 0}; } @@ -324,6 +328,20 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage fromNum++; } +void MeshService::sendClientNotification(meshtastic_ClientNotification *n) +{ + LOG_DEBUG("Sending client notification to phone\n"); + if (toPhoneClientNotificationQueue.numFree() == 0) { + LOG_WARN("ClientNotification queue is full, discarding oldest\n"); + meshtastic_ClientNotification *d = toPhoneClientNotificationQueue.dequeuePtr(0); + if (d) + releaseClientNotificationToPool(d); + } + + assert(toPhoneClientNotificationQueue.enqueue(n, 0)); + fromNum++; +} + meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() { meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 528adb137..ea1c4e345 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -21,6 +21,7 @@ extern Allocator &queueStatusPool; extern Allocator &mqttClientProxyMessagePool; +extern Allocator &clientNotificationPool; /** * Top level app for this service. keeps the mesh, the radio config and the queue of received packets. @@ -44,6 +45,9 @@ class MeshService // keep list of MqttClientProxyMessages to be send to the client for delivery PointerQueue toPhoneMqttProxyQueue; + // keep list of ClientNotifications to be send to the client (phone) + PointerQueue toPhoneClientNotificationQueue; + // This holds the last QueueStatus send meshtastic_QueueStatus lastQueueStatus; @@ -97,6 +101,9 @@ class MeshService // Release MqttClientProxyMessage packet to pool void releaseMqttClientProxyMessageToPool(meshtastic_MqttClientProxyMessage *p) { mqttClientProxyMessagePool.release(p); } + /// Release the next ClientNotification packet to pool. + void releaseClientNotificationToPool(meshtastic_ClientNotification *p) { clientNotificationPool.release(p); } + /** * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep @@ -134,6 +141,9 @@ class MeshService /// Send an MQTT message to the phone for client proxying void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); + /// Send a ClientNotification to the phone + void sendClientNotification(meshtastic_ClientNotification *cn); + bool isToPhoneQueueEmpty(); ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 3c3668300..5feb1c4bf 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -66,6 +66,9 @@ class PhoneAPI // Keep MqttClientProxyMessage packet just as packetForPhone meshtastic_MqttClientProxyMessage *mqttClientProxyMessageForPhone = NULL; + // Keep ClientNotification packet just as packetForPhone + meshtastic_ClientNotification *clientNotification = NULL; + /// We temporarily keep the nodeInfo here between the call to available and getFromRadio meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 79095805d..f59d61ea2 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -2,6 +2,7 @@ #include "Channels.h" #include "CryptoEngine.h" #include "MeshRadio.h" +#include "MeshService.h" #include "NodeDB.h" #include "RTC.h" #include "configuration.h" @@ -209,6 +210,13 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) #ifdef DEBUG_PORT uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes); + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->has_reply_id = true; + cn->reply_id = p->id; + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d minutes.", silentMinutes); + service->sendClientNotification(cn); #endif meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh From c451db3a3f1ac88668d86834404b89e6225f5abf Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 10 Aug 2024 08:57:37 -0500 Subject: [PATCH 0843/1377] Get in the trunk! --- src/mesh/MeshService.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index d05f43b01..ac97d51a7 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -59,7 +59,8 @@ Allocator &queueStatusPool = staticQueueStatusPool; #include "Router.h" MeshService::MeshService() - : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) + : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), + toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) { lastQueueStatus = {0, 0, 16, 0}; } From b726792efd1ea7ba34eeb23af22a48eda1a00ec9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 13:45:41 -0500 Subject: [PATCH 0844/1377] Re-implement PKI from #1509 (#4379) * Re-implement PKI from #1509 co-authored-by: edinnen * Set the key lengnth to actually make PKI work. * Remove unused variable and initialize keys to null * move printBytes() to meshUtils * Don't reset PKI key son reboot unless needed. * Remove double encryption for PKI messages * Cleanup encrypt logic * Add the MESHTASTIC_EXCLUDE_PKI option, and set it for minimal builds. Required for STM32 targets for now. * Use SHA-256 for PKI key hashing, and add MESHTASTIC_EXCLUDE_PKI_KEYGEN for STM32 * Fix a crash when node is null * Don't send PKI encrypted packets while licensed * use chIndex 8 for PKI * Don't be so clever, that you corrupt incoming packets * Pass on channel 8 for now * Typo * Lock keys once non-zero * We in fact need 2 scratch buffers, to store the encrypted bytes, unencrypted bytes, and decoded protobuf. * Lighter approach to retaining known key * Attach the public key to PKI decrypted packets in device memory * Turn PKI back off for STM32 :( * Don't just memcp over a protobuf * Don't PKI encrypt nodeinfo packets * Add a bit more memory logging around nodeDB * Use the proper macro to refer to NODENUM_BROADCAST * Typo fix * Don't PKI encrypt ROUTING (naks and acks) * Adds SecurityConfig protobuf * Add admin messages over PKI * Disable PKI for the WIO-e5 * Add MINIMUM_SAFE_FREE_HEAP macro and set to safe 1.5k * Add missed "has_security" * Add the admin_channel_enabled option * STM32 again * add missed configuration.h at the top of files * Add EXCLUDE_TZ and RTC * Enable PKI build on STM32 once again * Attempt 1 at moving PKI to aes-ccm * Fix buffers for encrypt/decrypt * Eliminate unused aes variable * Add debugging lines * Set hash to 0 for PKI * Fix debug lines so they don't print pointers. * logic fix and more debug * Rather important typo * Check for short packets before attempting decrypt * Don't forget to give cryptoEngine the keys! * Use the right scratch buffer * Cleanup * moar cleanups * Minor hardening * Remove some in-progress stuff * Turn PKI back off on STM32 * Return false * 2.5 protos * Sync up protos * Add initial cryptography test vector tests * re-add MINIMUM_SAFE_FREE_HEAP * Housekeeping and comment fixes * Add explanatory comment about weak dh25519 keys --------- Co-authored-by: Ben Meadors --- arch/esp32/esp32.ini | 1 + arch/nrf52/nrf52.ini | 1 + platformio.ini | 1 + src/RedirectablePrint.cpp | 4 +- src/SerialConsole.cpp | 4 +- src/configuration.h | 5 + src/main.cpp | 9 +- src/mesh/CryptoEngine.cpp | 170 ++++++++++++++++++++++++++++++++ src/mesh/CryptoEngine.h | 31 +++++- src/mesh/NodeDB.cpp | 50 +++++++++- src/mesh/NodeDB.h | 2 +- src/mesh/PhoneAPI.cpp | 4 + src/mesh/Router.cpp | 173 +++++++++++++++++++++------------ src/mesh/aes-ccm.cpp | 157 ++++++++++++++++++++++++++++++ src/mesh/aes-ccm.h | 10 ++ src/meshUtils.h | 4 +- src/modules/AdminModule.cpp | 38 +++++++- test/test_crypto/test_main.cpp | 50 ++++++++++ userPrefs.h | 6 ++ 19 files changed, 641 insertions(+), 79 deletions(-) create mode 100644 src/mesh/aes-ccm.cpp create mode 100644 src/mesh/aes-ccm.h create mode 100644 test/test_crypto/test_main.cpp diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 58c1302da..0dd6cbc1d 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -48,6 +48,7 @@ lib_deps = https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f + rweather/Crypto@^0.4.0 lib_ignore = segger_rtt diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index f3e7c3036..503da2aab 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -20,6 +20,7 @@ build_src_filter = lib_deps= ${arduino_base.lib_deps} + rweather/Crypto@^0.4.0 lib_ignore = BluetoothOTA \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index e60f0d7b9..5ad7d60a2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,6 +45,7 @@ extra_configs = variants/*/platformio.ini [env] +test_build_src = true extra_scripts = bin/platformio-custom.py ; note: we add src to our include search path so that lmic_project_config can override diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 05d349de9..02cd8b309 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -39,7 +39,7 @@ size_t RedirectablePrint::write(uint8_t c) SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif - if (!config.has_lora || config.device.serial_enabled) + if (!config.has_lora || config.security.serial_enabled) dest->write(c); return 1; // We always claim one was written, rather than trusting what the @@ -180,7 +180,7 @@ void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) { #if !MESHTASTIC_EXCLUDE_BLUETOOTH - if (config.bluetooth.device_logging_enabled && !pauseBluetoothLogging) { + if (config.security.bluetooth_logging_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index d25b81da7..b911e15da 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -83,7 +83,7 @@ bool SerialConsole::checkIsConnected() bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // only talk to the API once the configuration has been loaded and we're sure the serial port is not disabled. - if (config.has_lora && config.device.serial_enabled) { + if (config.has_lora && config.security.serial_enabled) { // Switch to protobufs for log messages usingProtobufs = true; canWrite = true; @@ -96,7 +96,7 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg) { - if (usingProtobufs && config.device.debug_log_enabled) { + if (usingProtobufs && config.security.debug_log_api_enabled) { meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset switch (logLevel[0]) { case 'D': diff --git a/src/configuration.h b/src/configuration.h index 9148f1d37..c9a5d7fb0 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -193,6 +193,10 @@ along with this program. If not, see . #define DEFAULT_SHUTDOWN_SECONDS 2 #endif +#ifndef MINIMUM_SAFE_FREE_HEAP +#define MINIMUM_SAFE_FREE_HEAP 1500 +#endif + /* Step #3: mop up with disabled values for HAS_ options not handled by the above two */ #ifndef HAS_WIFI @@ -256,6 +260,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_MQTT 1 #define MESHTASTIC_EXCLUDE_POWERMON 1 #define MESHTASTIC_EXCLUDE_I2C 1 +#define MESHTASTIC_EXCLUDE_PKI 1 #define MESHTASTIC_EXCLUDE_POWER_FSM 1 #define MESHTASTIC_EXCLUDE_TZ 1 #endif diff --git a/src/main.cpp b/src/main.cpp index 7f45905d1..b6af60d2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -227,7 +227,7 @@ void printInfo() { LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION)); } - +#ifndef PIO_UNIT_TESTING void setup() { concurrency::hasBeenSetup = true; @@ -1052,7 +1052,7 @@ void setup() powerFSMthread = new PowerFSMThread(); setCPUFast(false); // 80MHz is fine for our slow peripherals } - +#endif uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes) uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client) @@ -1075,7 +1075,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled; return deviceMetadata; } - +#ifndef PIO_UNIT_TESTING void loop() { runASAP = false; @@ -1120,4 +1120,5 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 1e44cb9b7..677667aef 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,6 +1,176 @@ #include "CryptoEngine.h" +#include "NodeDB.h" +#include "RadioInterface.h" #include "configuration.h" +#if !(MESHTASTIC_EXCLUDE_PKI) +#include "aes-ccm.h" +#include "meshUtils.h" +#include +#include +#include +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) +/** + * Create a public/private key pair with Curve25519. + * + * @param pubKey The destination for the public key. + * @param privKey The destination for the private key. + */ +void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey) +{ + LOG_DEBUG("Generating Curve25519 key pair...\n"); + Curve25519::dh1(public_key, private_key); + memcpy(pubKey, public_key, sizeof(public_key)); + memcpy(privKey, private_key, sizeof(private_key)); +} +#endif +uint8_t shared_key[32]; +void CryptoEngine::clearKeys() +{ + memset(public_key, 0, sizeof(public_key)); + memset(private_key, 0, sizeof(private_key)); +} + +/** + * Encrypt a packet's payload using a key generated with Curve25519 and SHA256 + * for a specific node. + * + * @param bytes is updated in place + */ +bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, + uint8_t *bytesOut) +{ + uint8_t *auth; + auth = bytesOut + numBytes; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); + if (node->num < 1 || node->user.public_key.size == 0) { + LOG_DEBUG("Node %d or their public_key not found\n", toNode); + return false; + } + if (!crypto->setDHKey(toNode)) { + return false; + } + initNonce(fromNode, packetNum); + + // Calculate the shared secret with the destination node and encrypt + printBytes("Attempting encrypt using nonce: ", nonce, 16); + printBytes("Attempting encrypt using shared_key: ", shared_key, 32); + aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); + return true; +} + +/** + * Decrypt a packet's payload using a key generated with Curve25519 and SHA256 + * for a specific node. + * + * @param bytes is updated in place + */ +bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) +{ + uint8_t *auth; // set to last 8 bytes of text? + auth = bytes + numBytes - 8; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); + + if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) { + LOG_DEBUG("Node or its public key not found in database\n"); + return false; + } + + // Calculate the shared secret with the sending node and decrypt + if (!crypto->setDHKey(fromNode)) { + return false; + } + initNonce(fromNode, packetNum); + printBytes("Attempting decrypt using nonce: ", nonce, 16); + printBytes("Attempting decrypt using shared_key: ", shared_key, 32); + return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 8, nullptr, 0, auth, bytesOut); +} + +void CryptoEngine::setPrivateKey(uint8_t *_private_key) +{ + memcpy(private_key, _private_key, 32); +} +/** + * Set the PKI key used for encrypt, decrypt. + * + * @param nodeNum the node number of the node who's public key we want to use + */ +bool CryptoEngine::setDHKey(uint32_t nodeNum) +{ + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum); + if (node->num < 1 || node->user.public_key.size == 0) { + LOG_DEBUG("Node %d or their public_key not found\n", nodeNum); + return false; + } + + uint8_t *pubKey = node->user.public_key.bytes; + uint8_t local_priv[32]; + memcpy(shared_key, pubKey, 32); + memcpy(local_priv, private_key, 32); + // Calculate the shared secret with the specified node's public key and our private key + // This includes an internal weak key check, which among other things looks for an all 0 public key and shared key. + if (!Curve25519::dh2(shared_key, local_priv)) { + LOG_WARN("Curve25519DH step 2 failed!\n"); + return false; + } + + printBytes("DH Output: ", shared_key, 32); + + /** + * D.J. Bernstein reccomends hashing the shared key. We want to do this because there are + * at least 128 bits of entropy in the 256-bit output of the DH key exchange, but we don't + * really know where. If you extract, for instance, the first 128 bits with basic truncation, + * then you don't know if you got all of your 128 entropy bits, or less, possibly much less. + * + * No exploitable bias is really known at that point, but we know enough to be wary. + * Hashing the DH output is a simple and safe way to gather all the entropy and spread + * it around as needed. + */ + crypto->hash(shared_key, 32); + return true; +} + +/** + * Hash arbitrary data using SHA256. + * + * @param bytes + * @param numBytes + */ +void CryptoEngine::hash(uint8_t *bytes, size_t numBytes) +{ + SHA256 hash; + size_t posn, len; + uint8_t size = numBytes; + uint8_t inc = 16; + hash.reset(); + for (posn = 0; posn < size; posn += inc) { + len = size - posn; + if (len > inc) + len = inc; + hash.update(bytes + posn, len); + } + hash.finalize(bytes, 32); +} + +void CryptoEngine::aesSetKey(const uint8_t *key_bytes, size_t key_len) +{ + if (aes) { + delete aes; + aes = nullptr; + } + if (key_len != 0) { + aes = new AESSmall256(); + aes->setKey(key_bytes, key_len); + } +} + +void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out) +{ + aes->encryptBlock(out, in); +} + +#endif + concurrency::Lock *cryptLock; void CryptoEngine::setKey(const CryptoKey &k) diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 2737dab2d..51080fd59 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -1,6 +1,8 @@ #pragma once - +#include "AES.h" #include "concurrency/LockGuard.h" +#include "configuration.h" +#include "mesh-pb-constants.h" #include extern concurrency::Lock *cryptLock; @@ -26,9 +28,34 @@ class CryptoEngine uint8_t nonce[16] = {0}; CryptoKey key = {}; +#if !(MESHTASTIC_EXCLUDE_PKI) + uint8_t private_key[32] = {0}; +#endif public: +#if !(MESHTASTIC_EXCLUDE_PKI) + uint8_t public_key[32] = {0}; +#endif + virtual ~CryptoEngine() {} +#if !(MESHTASTIC_EXCLUDE_PKI) +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey); +#endif + void clearKeys(); + void setPrivateKey(uint8_t *_private_key); + virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, + uint8_t *bytesOut); + virtual bool decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); + virtual bool setDHKey(uint32_t nodeNum); + virtual void hash(uint8_t *bytes, size_t numBytes); + + virtual void aesSetKey(const uint8_t *key, size_t key_len); + + virtual void aesEncrypt(uint8_t *in, uint8_t *out); + AESSmall256 *aes = NULL; + +#endif /** * Set the key used for encrypt, decrypt. @@ -61,4 +88,4 @@ class CryptoEngine void initNonce(uint32_t fromNode, uint64_t packetId); }; -extern CryptoEngine *crypto; +extern CryptoEngine *crypto; \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 000da335f..fb7926977 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -19,6 +19,7 @@ #include "error.h" #include "main.h" #include "mesh-pb-constants.h" +#include "meshUtils.h" #include "modules/NeighborInfoModule.h" #include #include @@ -123,6 +124,31 @@ NodeDB::NodeDB() // Include our owner in the node db under our nodenum meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); +#if !(MESHTASTIC_EXCLUDE_PKI) + // Calculate Curve25519 public and private keys + printBytes("Old Pubkey", config.security.public_key.bytes, 32); + if (config.security.private_key.size == 32 && config.security.public_key.size == 32) { + LOG_INFO("Using saved PKI keys\n"); + owner.public_key.size = config.security.public_key.size; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); + crypto->setPrivateKey(config.security.private_key.bytes); + } else { +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + LOG_INFO("Generating new PKI keys\n"); + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + config.security.public_key.size = 32; + config.security.private_key.size = 32; + + printBytes("New Pubkey", config.security.public_key.bytes, 32); + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); +#else + LOG_INFO("No PKI keys set, and generation disabled!\n"); +#endif + } + +#endif + info->user = owner; info->has_user = true; @@ -237,6 +263,7 @@ void NodeDB::installDefaultConfig() config.has_power = true; config.has_network = true; config.has_bluetooth = (HAS_BLUETOOTH ? true : false); + config.has_security = true; config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; config.lora.sx126x_rx_boosted_gain = true; @@ -259,6 +286,14 @@ void NodeDB::installDefaultConfig() #else config.lora.ignore_mqtt = false; #endif +#ifdef ADMIN_KEY_USERPREFS + memcpy(config.security.admin_key.bytes, admin_key_userprefs, 32); + config.security.admin_key.size = 32; +#else + config.security.admin_key.size = 0; +#endif + config.security.public_key.size = 0; + config.security.private_key.size = 0; #ifdef PIN_GPS_EN config.position.gps_en_gpio = PIN_GPS_EN; #endif @@ -282,7 +317,8 @@ void NodeDB::installDefaultConfig() config.position.broadcast_smart_minimum_interval_secs = 30; if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER) config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; - config.device.serial_enabled = true; + config.security.serial_enabled = true; + config.security.admin_channel_enabled = false; resetRadioConfig(); strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); // FIXME: Default to bluetooth capability of platform as default @@ -778,6 +814,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) config.has_power = true; config.has_network = true; config.has_bluetooth = true; + config.has_security = true; success &= saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); } @@ -957,7 +994,7 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS /** Update user info and channel for this node based on received user data */ -bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex) +bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex) { meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); if (!info) { @@ -965,6 +1002,12 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t chann } LOG_DEBUG("old user %s/%s/%s, channel=%d\n", info->user.id, info->user.long_name, info->user.short_name, info->channel); +#if !(MESHTASTIC_EXCLUDE_PKI) + if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one + printBytes("Retaining Old Pubkey: ", info->user.public_key.bytes, 32); + memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); + } +#endif // Both of info->user and p start as filled with zero so I think this is okay bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); @@ -1042,7 +1085,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) meshtastic_NodeInfoLite *lite = getMeshNode(n); if (!lite) { - if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { + if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP)) { if (screen) screen->print("Warn: node database full!\nErasing oldest entry\n"); LOG_WARN("Node database full with %i nodes and %i bytes free! Erasing oldest entry\n", numMeshNodes, @@ -1068,6 +1111,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) // everything is missing except the nodenum memset(lite, 0, sizeof(*lite)); lite->num = n; + LOG_INFO("Adding node to database with %i nodes and %i bytes free!\n", numMeshNodes, memGet.getFreeHeap()); } return lite; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 447ce10d4..a71f3e134 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -98,7 +98,7 @@ class NodeDB /** Update user info and channel for this node based on received user data */ - bool updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex = 0); + bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0); /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index fc0099e87..0a9bb5b10 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -255,6 +255,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.config.which_payload_variant = meshtastic_Config_bluetooth_tag; fromRadioScratch.config.payload_variant.bluetooth = config.bluetooth; break; + case meshtastic_Config_security_tag: + fromRadioScratch.config.which_payload_variant = meshtastic_Config_security_tag; + fromRadioScratch.config.payload_variant.security = config.security; + break; default: LOG_ERROR("Unknown config type %d\n", config_state); } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f59d61ea2..1fecef6d7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -37,6 +37,7 @@ static MemoryDynamic staticPool; Allocator &packetPool = staticPool; static uint8_t bytes[MAX_RHPACKETLEN]; +static uint8_t ScratchEncrypted[MAX_RHPACKETLEN]; /** * Constructor @@ -307,70 +308,105 @@ bool perhapsDecode(meshtastic_MeshPacket *p) if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) return true; // If packet was already decoded just return - // assert(p->which_payloadVariant == MeshPacket_encrypted_tag); + size_t rawSize = p->encrypted.size; + if (rawSize > sizeof(bytes)) { + LOG_ERROR("Packet too large to attempt decryption! (rawSize=%d > 256)\n", rawSize); + return false; + } + bool decrypted = false; + ChannelIndex chIndex = 0; + memcpy(bytes, p->encrypted.bytes, + rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf + memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize); +#if !(MESHTASTIC_EXCLUDE_PKI) + // Attempt PKI decryption first + if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && nodeDB->getMeshNode(p->from) != nullptr && + nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && + rawSize > 8) { + LOG_DEBUG("Attempting PKI decryption\n"); - // Try to find a channel that works with this hash - for (ChannelIndex chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) { - // Try to use this hash/channel pair - if (channels.decryptForHash(chIndex, p->channel)) { - // Try to decrypt the packet if we can - size_t rawSize = p->encrypted.size; - if (rawSize > sizeof(bytes)) { - LOG_ERROR("Packet too large to attempt decription! (rawSize=%d > 256)\n", rawSize); - return false; - } - memcpy(bytes, p->encrypted.bytes, - rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf - crypto->decrypt(p->from, p->id, rawSize, bytes); - - // printBytes("plaintext", bytes, p->encrypted.size); - - // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) { + LOG_INFO("PKI Decryption worked!\n"); memset(&p->decoded, 0, sizeof(p->decoded)); - if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded)) { - LOG_ERROR("Invalid protobufs in received mesh packet (bad psk?)!\n"); - } else if (p->decoded.portnum == meshtastic_PortNum_UNKNOWN_APP) { - LOG_ERROR("Invalid portnum (bad psk?)!\n"); + rawSize -= 8; + if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded) && + p->decoded.portnum != meshtastic_PortNum_UNKNOWN_APP) { + decrypted = true; + LOG_INFO("Packet decrypted using PKI!\n"); + p->pki_encrypted = true; + memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32); + p->public_key.size = 32; + // memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers + // chIndex = 8; } else { - // parsing was successful - p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded - p->channel = chIndex; // change to store the index instead of the hash - - /* Not actually ever used. - // Decompress if needed. jm - if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) { - // Decompress the payload - char compressed_in[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; - char decompressed_out[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; - int decompressed_len; - - memcpy(compressed_in, p->decoded.payload.bytes, p->decoded.payload.size); - - decompressed_len = unishox2_decompress_simple(compressed_in, p->decoded.payload.size, decompressed_out); - - // LOG_DEBUG("\n\n**\n\nDecompressed length - %d \n", decompressed_len); - - memcpy(p->decoded.payload.bytes, decompressed_out, decompressed_len); - - // Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - } */ - - printPacket("decoded message", p); -#if ENABLE_JSON_LOGGING - LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); -#elif ARCH_PORTDUINO - if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { - LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); - } -#endif - return true; + return false; } } } +#endif - LOG_WARN("No suitable channel found for decoding, hash was 0x%x!\n", p->channel); - return false; + // assert(p->which_payloadVariant == MeshPacket_encrypted_tag); + if (!decrypted) { + // Try to find a channel that works with this hash + for (chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) { + // Try to use this hash/channel pair + if (channels.decryptForHash(chIndex, p->channel)) { + // Try to decrypt the packet if we can + crypto->decrypt(p->from, p->id, rawSize, bytes); + + // printBytes("plaintext", bytes, p->encrypted.size); + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + memset(&p->decoded, 0, sizeof(p->decoded)); + if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded)) { + LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!\n", p->id); + } else if (p->decoded.portnum == meshtastic_PortNum_UNKNOWN_APP) { + LOG_ERROR("Invalid portnum (bad psk?)!\n"); + } else { + decrypted = true; + break; + } + } + } + } + if (decrypted) { + // parsing was successful + p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded + p->channel = chIndex; // change to store the index instead of the hash + + /* Not actually ever used. + // Decompress if needed. jm + if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) { + // Decompress the payload + char compressed_in[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; + char decompressed_out[meshtastic_Constants_DATA_PAYLOAD_LEN] = {}; + int decompressed_len; + + memcpy(compressed_in, p->decoded.payload.bytes, p->decoded.payload.size); + + decompressed_len = unishox2_decompress_simple(compressed_in, p->decoded.payload.size, decompressed_out); + + // LOG_DEBUG("\n\n**\n\nDecompressed length - %d \n", decompressed_len); + + memcpy(p->decoded.payload.bytes, decompressed_out, decompressed_len); + + // Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + } */ + + printPacket("decoded message", p); +#if ENABLE_JSON_LOGGING + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); +#elif ARCH_PORTDUINO + if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { + LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str()); + } +#endif + return true; + } else { + LOG_WARN("No suitable channel found for decoding, hash was 0x%x!\n", p->channel); + return false; + } } /** Return 0 for success or a Routing_Errror code for failure @@ -384,7 +420,6 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); /* Not actually used, so save the cycles - // Only allow encryption on the text message app. // TODO: Allow modules to opt into compression. if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) { @@ -432,10 +467,28 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; +#if !(MESHTASTIC_EXCLUDE_PKI) + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); + if (!owner.is_licensed && p->to != NODENUM_BROADCAST && node != nullptr && node->user.public_key.size > 0 && + p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && + p->decoded.portnum != meshtastic_PortNum_ROUTING_APP) { // TODO: check for size due to 8 byte tag + LOG_DEBUG("Using PKI!\n"); + if (numbytes + 8 > MAX_RHPACKETLEN) + return meshtastic_Routing_Error_TOO_LARGE; + crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); + numbytes += 8; + memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); + p->channel = 0; + } else { + crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + memcpy(p->encrypted.bytes, bytes, numbytes); + } +#else crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + memcpy(p->encrypted.bytes, bytes, numbytes); +#endif // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); p->encrypted.size = numbytes; p->which_payload_variant = meshtastic_MeshPacket_encrypted_tag; } @@ -539,4 +592,4 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) handleReceived(p); packetPool.release(p); -} +} \ No newline at end of file diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp new file mode 100644 index 000000000..cd18ae6c5 --- /dev/null +++ b/src/mesh/aes-ccm.cpp @@ -0,0 +1,157 @@ +/* + * Counter with CBC-MAC (CCM) with AES + * + * Copyright (c) 2010-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +#define AES_BLOCK_SIZE 16 +#include "aes-ccm.h" +#if !MESHTASTIC_EXCLUDE_PKI + +static inline void WPA_PUT_BE16(uint8_t *a, uint16_t val) +{ + a[0] = val >> 8; + a[1] = val & 0xff; +} + +static void xor_aes_block(uint8_t *dst, const uint8_t *src) +{ + uint32_t *d = (uint32_t *)dst; + uint32_t *s = (uint32_t *)src; + *d++ ^= *s++; + *d++ ^= *s++; + *d++ ^= *s++; + *d++ ^= *s++; +} +static void aes_ccm_auth_start(size_t M, size_t L, const uint8_t *nonce, const uint8_t *aad, size_t aad_len, size_t plain_len, + uint8_t *x) +{ + uint8_t aad_buf[2 * AES_BLOCK_SIZE]; + uint8_t b[AES_BLOCK_SIZE]; + /* Authentication */ + /* B_0: Flags | Nonce N | l(m) */ + b[0] = aad_len ? 0x40 : 0 /* Adata */; + b[0] |= (((M - 2) / 2) /* M' */ << 3); + b[0] |= (L - 1) /* L' */; + memcpy(&b[1], nonce, 15 - L); + WPA_PUT_BE16(&b[AES_BLOCK_SIZE - L], plain_len); + crypto->aesEncrypt(b, x); /* X_1 = E(K, B_0) */ + if (!aad_len) + return; + WPA_PUT_BE16(aad_buf, aad_len); + memcpy(aad_buf + 2, aad, aad_len); + memset(aad_buf + 2 + aad_len, 0, sizeof(aad_buf) - 2 - aad_len); + xor_aes_block(aad_buf, x); + crypto->aesEncrypt(aad_buf, x); /* X_2 = E(K, X_1 XOR B_1) */ + if (aad_len > AES_BLOCK_SIZE - 2) { + xor_aes_block(&aad_buf[AES_BLOCK_SIZE], x); + /* X_3 = E(K, X_2 XOR B_2) */ + crypto->aesEncrypt(&aad_buf[AES_BLOCK_SIZE], x); + } +} +static void aes_ccm_auth(const uint8_t *data, size_t len, uint8_t *x) +{ + size_t last = len % AES_BLOCK_SIZE; + size_t i; + for (i = 0; i < len / AES_BLOCK_SIZE; i++) { + /* X_i+1 = E(K, X_i XOR B_i) */ + xor_aes_block(x, data); + data += AES_BLOCK_SIZE; + crypto->aesEncrypt(x, x); + } + if (last) { + /* XOR zero-padded last block */ + for (i = 0; i < last; i++) + x[i] ^= *data++; + crypto->aesEncrypt(x, x); + } +} +static void aes_ccm_encr_start(size_t L, const uint8_t *nonce, uint8_t *a) +{ + /* A_i = Flags | Nonce N | Counter i */ + a[0] = L - 1; /* Flags = L' */ + memcpy(&a[1], nonce, 15 - L); +} +static void aes_ccm_encr(size_t L, const uint8_t *in, size_t len, uint8_t *out, uint8_t *a) +{ + size_t last = len % AES_BLOCK_SIZE; + size_t i; + /* crypt = msg XOR (S_1 | S_2 | ... | S_n) */ + for (i = 1; i <= len / AES_BLOCK_SIZE; i++) { + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], i); + /* S_i = E(K, A_i) */ + crypto->aesEncrypt(a, out); + xor_aes_block(out, in); + out += AES_BLOCK_SIZE; + in += AES_BLOCK_SIZE; + } + if (last) { + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], i); + crypto->aesEncrypt(a, out); + /* XOR zero-padded last block */ + for (i = 0; i < last; i++) + *out++ ^= *in++; + } +} +static void aes_ccm_encr_auth(size_t M, uint8_t *x, uint8_t *a, uint8_t *auth) +{ + size_t i; + uint8_t tmp[AES_BLOCK_SIZE]; + /* U = T XOR S_0; S_0 = E(K, A_0) */ + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], 0); + crypto->aesEncrypt(a, tmp); + for (i = 0; i < M; i++) + auth[i] = x[i] ^ tmp[i]; +} +static void aes_ccm_decr_auth(size_t M, uint8_t *a, const uint8_t *auth, uint8_t *t) +{ + size_t i; + uint8_t tmp[AES_BLOCK_SIZE]; + /* U = T XOR S_0; S_0 = E(K, A_0) */ + WPA_PUT_BE16(&a[AES_BLOCK_SIZE - 2], 0); + crypto->aesEncrypt(a, tmp); + for (i = 0; i < M; i++) + t[i] = auth[i] ^ tmp[i]; +} +/* AES-CCM with fixed L=2 and aad_len <= 30 assumption */ +int aes_ccm_ae(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *plain, size_t plain_len, + const uint8_t *aad, size_t aad_len, uint8_t *crypt, uint8_t *auth) +{ + const size_t L = 2; + uint8_t x[AES_BLOCK_SIZE], a[AES_BLOCK_SIZE]; + if (aad_len > 30 || M > AES_BLOCK_SIZE) + return -1; + crypto->aesSetKey(key, key_len); + aes_ccm_auth_start(M, L, nonce, aad, aad_len, plain_len, x); + aes_ccm_auth(plain, plain_len, x); + /* Encryption */ + aes_ccm_encr_start(L, nonce, a); + aes_ccm_encr(L, plain, plain_len, crypt, a); + aes_ccm_encr_auth(M, x, a, auth); + return 0; +} +/* AES-CCM with fixed L=2 and aad_len <= 30 assumption */ +bool aes_ccm_ad(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *crypt, size_t crypt_len, + const uint8_t *aad, size_t aad_len, const uint8_t *auth, uint8_t *plain) +{ + const size_t L = 2; + uint8_t x[AES_BLOCK_SIZE], a[AES_BLOCK_SIZE]; + uint8_t t[AES_BLOCK_SIZE]; + if (aad_len > 30 || M > AES_BLOCK_SIZE) + return false; + crypto->aesSetKey(key, key_len); + /* Decryption */ + aes_ccm_encr_start(L, nonce, a); + aes_ccm_decr_auth(M, a, auth, t); + /* plaintext = msg XOR (S_1 | S_2 | ... | S_n) */ + aes_ccm_encr(L, crypt, crypt_len, plain, a); + aes_ccm_auth_start(M, L, nonce, aad, aad_len, crypt_len, x); + aes_ccm_auth(plain, crypt_len, x); + if (memcmp(x, t, M) != 0) { // FIXME make const comp + return false; + } + return true; +} +#endif \ No newline at end of file diff --git a/src/mesh/aes-ccm.h b/src/mesh/aes-ccm.h new file mode 100644 index 000000000..6b8edcde4 --- /dev/null +++ b/src/mesh/aes-ccm.h @@ -0,0 +1,10 @@ +#pragma once +#include "CryptoEngine.h" +#if !MESHTASTIC_EXCLUDE_PKI + +int aes_ccm_ae(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *plain, size_t plain_len, + const uint8_t *aad, size_t aad_len, uint8_t *crypt, uint8_t *auth); + +bool aes_ccm_ad(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t M, const uint8_t *crypt, size_t crypt_len, + const uint8_t *aad, size_t aad_len, const uint8_t *auth, uint8_t *plain); +#endif \ No newline at end of file diff --git a/src/meshUtils.h b/src/meshUtils.h index 9dfe9b558..e2d4188d7 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -12,4 +12,6 @@ template constexpr const T &clamp(const T &v, const T &lo, const T &hi #define STRNSTR #include char *strnstr(const char *s, const char *find, size_t slen); -#endif \ No newline at end of file +#endif + +void printBytes(const char *label, const uint8_t *p, size_t numbytes); \ No newline at end of file diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 25450992b..fe426f8f5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -65,7 +65,29 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta bool handled = false; assert(r); bool fromOthers = mp.from != 0 && mp.from != nodeDB->getNodeNum(); - + if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + return handled; + } + meshtastic_Channel *ch = &channels.getByIndex(mp.channel); + // Could tighten this up further by tracking the last poblic_key we went an AdminMessage request to + // and only allowing responses from that remote. + if (!((mp.from == 0 && !config.security.is_managed) || + r->which_payload_variant == meshtastic_AdminMessage_get_channel_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_owner_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_config_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_module_config_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_canned_message_module_messages_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_metadata_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag || + r->which_payload_variant == meshtastic_NodeRemoteHardwarePinsResponse_node_remote_hardware_pins_tag || + (strcasecmp(ch->settings.name, Channels::adminChannel) == 0 && config.security.admin_channel_enabled) || + (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key.bytes, 32) == 0))) { + LOG_INFO("Ignoring admin payload %i\n", r->which_payload_variant); + return handled; + } + LOG_INFO("Handling admin payload %i\n", r->which_payload_variant); switch (r->which_payload_variant) { /** @@ -383,8 +405,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #endif if (config.device.button_gpio == c.payload_variant.device.button_gpio && config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio && - config.device.debug_log_enabled == c.payload_variant.device.debug_log_enabled && - config.device.serial_enabled == c.payload_variant.device.serial_enabled && config.device.role == c.payload_variant.device.role && config.device.disable_triple_click == c.payload_variant.device.disable_triple_click && config.device.rebroadcast_mode == c.payload_variant.device.rebroadcast_mode) { @@ -501,6 +521,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.has_bluetooth = true; config.bluetooth = c.payload_variant.bluetooth; break; + case meshtastic_Config_security_tag: + LOG_INFO("Setting config: Security\n"); + config.security = c.payload_variant.security; + owner.public_key.size = config.security.public_key.size; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); + if (config.security.debug_log_api_enabled == c.payload_variant.security.debug_log_api_enabled && + config.security.serial_enabled == c.payload_variant.security.serial_enabled) + requiresReboot = false; + + break; } saveChanges(changes, requiresReboot); @@ -896,5 +926,5 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p) AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_APP, &meshtastic_AdminMessage_msg) { // restrict to the admin channel for rx - boundChannel = Channels::adminChannel; + // boundChannel = Channels::adminChannel; } \ No newline at end of file diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp new file mode 100644 index 000000000..e564d5d0e --- /dev/null +++ b/test/test_crypto/test_main.cpp @@ -0,0 +1,50 @@ +#include "CryptoEngine.h" + +#include + +void setUp(void) +{ + // set stuff up here +} + +void tearDown(void) +{ + // clean stuff up here +} + +void test_SHA256(void) +{ + uint8_t hash2[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + uint8_t hash[32] = {0}; + crypto->hash(hash, 0); + TEST_ASSERT_EQUAL_MEMORY(hash, hash2, 32); +} +void test_ECB_AES256(void) +{ + uint8_t key[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; + uint8_t plain1[] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; + uint8_t scratch[16] = {0}; + + uint8_t cipher1[] = {0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8}; + crypto->aesSetKey(key, 32); + crypto->aesEncrypt(plain1, scratch); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(scratch, cipher1, 16); +} + +void setup() +{ + // NOTE!!! Wait for >2 secs + // if board doesn't support software reset via Serial.DTR/RTS + delay(2000); + + UNITY_BEGIN(); // IMPORTANT LINE! + RUN_TEST(test_SHA256); + RUN_TEST(test_ECB_AES256); +} + +void loop() +{ + UNITY_END(); // stop unit testing +} \ No newline at end of file diff --git a/userPrefs.h b/userPrefs.h index 3ebbefcaf..4e80b579f 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -36,4 +36,10 @@ static unsigned char icon_bits[] = { 0x98, 0x3F, 0xF0, 0x23, 0x00, 0xFC, 0x0F, 0xE0, 0x7F, 0x00, 0xFC, 0x03, 0x80, 0xFF, 0x01, 0xFC, 0x00, 0x00, 0x3E, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00}; */ +/* +#define ADMIN_KEY_USERPREFS 1 +static unsigned char admin_key_userprefs[] = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, + 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, + 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c}; +*/ #endif \ No newline at end of file From 26d0b2b4771de39f10f589392fb0351e5d96fdd4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 15:45:29 -0500 Subject: [PATCH 0845/1377] Add DH25519 unit test --- src/DebugConfiguration.h | 2 +- src/mesh/CryptoEngine.cpp | 27 ++++++----- src/mesh/CryptoEngine.h | 25 +++++----- src/mesh/NodeDB.cpp | 2 +- test/test_crypto/test_main.cpp | 89 +++++++++++++++++++++++++++++----- 5 files changed, 109 insertions(+), 36 deletions(-) diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index 7987e7fa1..55453ea1e 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -45,7 +45,7 @@ #define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else -#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) && !defined(PIO_UNIT_TESTING) #define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__) #define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__) #define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 677667aef..d284f3410 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -24,7 +24,6 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey) memcpy(privKey, private_key, sizeof(private_key)); } #endif -uint8_t shared_key[32]; void CryptoEngine::clearKeys() { memset(public_key, 0, sizeof(public_key)); @@ -86,7 +85,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 8, nullptr, 0, auth, bytesOut); } -void CryptoEngine::setPrivateKey(uint8_t *_private_key) +void CryptoEngine::setDHPrivateKey(uint8_t *_private_key) { memcpy(private_key, _private_key, 32); } @@ -103,16 +102,8 @@ bool CryptoEngine::setDHKey(uint32_t nodeNum) return false; } - uint8_t *pubKey = node->user.public_key.bytes; - uint8_t local_priv[32]; - memcpy(shared_key, pubKey, 32); - memcpy(local_priv, private_key, 32); - // Calculate the shared secret with the specified node's public key and our private key - // This includes an internal weak key check, which among other things looks for an all 0 public key and shared key. - if (!Curve25519::dh2(shared_key, local_priv)) { - LOG_WARN("Curve25519DH step 2 failed!\n"); + if (!setDHPublicKey(node->user.public_key.bytes)) return false; - } printBytes("DH Output: ", shared_key, 32); @@ -171,6 +162,20 @@ void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out) #endif +bool CryptoEngine::setDHPublicKey(uint8_t *pubKey) +{ + uint8_t local_priv[32]; + memcpy(shared_key, pubKey, 32); + memcpy(local_priv, private_key, 32); + // Calculate the shared secret with the specified node's public key and our private key + // This includes an internal weak key check, which among other things looks for an all 0 public key and shared key. + if (!Curve25519::dh2(shared_key, local_priv)) { + LOG_WARN("Curve25519DH step 2 failed!\n"); + return false; + } + return true; +} + concurrency::Lock *cryptLock; void CryptoEngine::setKey(const CryptoKey &k) diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 51080fd59..fd607c29e 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -23,15 +23,6 @@ struct CryptoKey { class CryptoEngine { - protected: - /** Our per packet nonce */ - uint8_t nonce[16] = {0}; - - CryptoKey key = {}; -#if !(MESHTASTIC_EXCLUDE_PKI) - uint8_t private_key[32] = {0}; -#endif - public: #if !(MESHTASTIC_EXCLUDE_PKI) uint8_t public_key[32] = {0}; @@ -43,11 +34,12 @@ class CryptoEngine virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey); #endif void clearKeys(); - void setPrivateKey(uint8_t *_private_key); + void setDHPrivateKey(uint8_t *_private_key); virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); virtual bool decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); - virtual bool setDHKey(uint32_t nodeNum); + bool setDHKey(uint32_t nodeNum); + virtual bool setDHPublicKey(uint8_t *publicKey); virtual void hash(uint8_t *bytes, size_t numBytes); virtual void aesSetKey(const uint8_t *key, size_t key_len); @@ -75,8 +67,17 @@ class CryptoEngine */ virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); - +#ifndef PIO_UNIT_TESTING protected: +#endif + /** Our per packet nonce */ + uint8_t nonce[16] = {0}; + + CryptoKey key = {}; +#if !(MESHTASTIC_EXCLUDE_PKI) + uint8_t shared_key[32] = {0}; + uint8_t private_key[32] = {0}; +#endif /** * Init our 128 bit nonce for a new packet * diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fb7926977..468bffab3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -131,7 +131,7 @@ NodeDB::NodeDB() LOG_INFO("Using saved PKI keys\n"); owner.public_key.size = config.security.public_key.size; memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); - crypto->setPrivateKey(config.security.private_key.bytes); + crypto->setDHPrivateKey(config.security.private_key.bytes); } else { #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) LOG_INFO("Generating new PKI keys\n"); diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index e564d5d0e..e9aee928e 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -2,6 +2,18 @@ #include +void HexToBytes(uint8_t *result, const std::string hex, size_t len = 0) +{ + if (len) { + memset(result, 0, len); + } + for (unsigned int i = 0; i < hex.length(); i += 2) { + std::string byteString = hex.substr(i, 2); + result[i / 2] = (uint8_t)strtol(byteString.c_str(), NULL, 16); + } + return; +} + void setUp(void) { // set stuff up here @@ -14,25 +26,79 @@ void tearDown(void) void test_SHA256(void) { - uint8_t hash2[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, - 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + uint8_t expected[32]; uint8_t hash[32] = {0}; + + HexToBytes(expected, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); crypto->hash(hash, 0); - TEST_ASSERT_EQUAL_MEMORY(hash, hash2, 32); + TEST_ASSERT_EQUAL_MEMORY(hash, expected, 32); + + HexToBytes(hash, "d3", 32); + HexToBytes(expected, "28969cdfa74a12c82f3bad960b0b000aca2ac329deea5c2328ebc6f2ba9802c1"); + crypto->hash(hash, 1); + TEST_ASSERT_EQUAL_MEMORY(hash, expected, 32); + + HexToBytes(hash, "11af", 32); + HexToBytes(expected, "5ca7133fa735326081558ac312c620eeca9970d1e70a4b95533d956f072d1f98"); + crypto->hash(hash, 2); + TEST_ASSERT_EQUAL_MEMORY(hash, expected, 32); } void test_ECB_AES256(void) { - uint8_t key[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, - 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; - uint8_t plain1[] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; - uint8_t scratch[16] = {0}; + // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ECB.pdf - uint8_t cipher1[] = {0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8}; + uint8_t key[32] = {0}; + uint8_t plain[16] = {0}; + uint8_t result[16] = {0}; + uint8_t expected[16] = {0}; + + HexToBytes(key, "603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4"); + + HexToBytes(plain, "6BC1BEE22E409F96E93D7E117393172A"); + HexToBytes(expected, "F3EED1BDB5D2A03C064B5A7E3DB181F8"); crypto->aesSetKey(key, 32); - crypto->aesEncrypt(plain1, scratch); // Does 16 bytes at a time - TEST_ASSERT_EQUAL_MEMORY(scratch, cipher1, 16); -} + crypto->aesEncrypt(plain, result); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(expected, result, 16); + HexToBytes(plain, "AE2D8A571E03AC9C9EB76FAC45AF8E51"); + HexToBytes(expected, "591CCB10D410ED26DC5BA74A31362870"); + crypto->aesSetKey(key, 32); + crypto->aesEncrypt(plain, result); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(expected, result, 16); + + HexToBytes(plain, "30C81C46A35CE411E5FBC1191A0A52EF"); + HexToBytes(expected, "B6ED21B99CA6F4F9F153E7B1BEAFED1D"); + crypto->aesSetKey(key, 32); + crypto->aesEncrypt(plain, result); // Does 16 bytes at a time + TEST_ASSERT_EQUAL_MEMORY(expected, result, 16); +} +void test_DH25519(void) +{ + // test vectors from wycheproof x25519 + // https://github.com/C2SP/wycheproof/blob/master/testvectors/x25519_test.json + uint8_t private_key[32]; + uint8_t public_key[32]; + uint8_t expected_shared[32]; + + HexToBytes(public_key, "504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); + HexToBytes(private_key, "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475"); + HexToBytes(expected_shared, "436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(crypto->setDHPublicKey(public_key)); + TEST_ASSERT_EQUAL_MEMORY(expected_shared, crypto->shared_key, 32); + + HexToBytes(public_key, "63aa40c6e38346c5caf23a6df0a5e6c80889a08647e551b3563449befcfc9733"); + HexToBytes(private_key, "d85d8c061a50804ac488ad774ac716c3f5ba714b2712e048491379a500211958"); + HexToBytes(expected_shared, "279df67a7c4611db4708a0e8282b195e5ac0ed6f4b2f292c6fbd0acac30d1332"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(crypto->setDHPublicKey(public_key)); + TEST_ASSERT_EQUAL_MEMORY(expected_shared, crypto->shared_key, 32); + + HexToBytes(public_key, "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"); + HexToBytes(private_key, "18630f93598637c35da623a74559cf944374a559114c7937811041fc8605564a"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(!crypto->setDHPublicKey(public_key)); // Weak public key results in 0 shared key +} void setup() { // NOTE!!! Wait for >2 secs @@ -42,6 +108,7 @@ void setup() UNITY_BEGIN(); // IMPORTANT LINE! RUN_TEST(test_SHA256); RUN_TEST(test_ECB_AES256); + RUN_TEST(test_DH25519); } void loop() From 192af05a25ef219c14eec702129bdc800ce31721 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 20:04:38 -0500 Subject: [PATCH 0846/1377] Fix compile on STM32 --- src/mesh/CryptoEngine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index d284f3410..fd7246fa9 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -160,8 +160,6 @@ void CryptoEngine::aesEncrypt(uint8_t *in, uint8_t *out) aes->encryptBlock(out, in); } -#endif - bool CryptoEngine::setDHPublicKey(uint8_t *pubKey) { uint8_t local_priv[32]; @@ -176,6 +174,7 @@ bool CryptoEngine::setDHPublicKey(uint8_t *pubKey) return true; } +#endif concurrency::Lock *cryptLock; void CryptoEngine::setKey(const CryptoKey &k) From c3aa56ef30ff4069065c1819d8ab0ac8ca8d7aba Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 22:38:05 -0500 Subject: [PATCH 0847/1377] Refactor platform cryptography, add tests --- src/mesh/CryptoEngine.cpp | 42 +++++++++- src/mesh/CryptoEngine.h | 6 +- src/mesh/Router.cpp | 2 +- src/platform/esp32/ESP32CryptoEngine.cpp | 41 ++-------- src/platform/esp32/architecture.h | 3 + src/platform/nrf52/NRF52CryptoEngine.cpp | 29 ++----- src/platform/nrf52/architecture.h | 3 + .../portduino/CrossPlatformCryptoEngine.cpp | 78 ------------------- src/platform/rp2040/rp2040CryptoEngine.cpp | 66 ---------------- src/platform/stm32wl/STM32WLCryptoEngine.cpp | 67 ---------------- test/test_crypto/test_main.cpp | 27 +++++++ 11 files changed, 88 insertions(+), 276 deletions(-) delete mode 100644 src/platform/portduino/CrossPlatformCryptoEngine.cpp delete mode 100644 src/platform/rp2040/rp2040CryptoEngine.cpp delete mode 100644 src/platform/stm32wl/STM32WLCryptoEngine.cpp diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index fd7246fa9..e83236eab 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,6 +1,7 @@ #include "CryptoEngine.h" #include "NodeDB.h" #include "RadioInterface.h" +#include "architecture.h" #include "configuration.h" #if !(MESHTASTIC_EXCLUDE_PKI) @@ -188,14 +189,44 @@ void CryptoEngine::setKey(const CryptoKey &k) * * @param bytes is updated in place */ -void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) +void CryptoEngine::encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) { - LOG_WARN("noop encryption!\n"); + if (key.length > 0) { + initNonce(fromNode, packetId); + if (numBytes <= MAX_BLOCKSIZE) { + encryptAESCtr(key, nonce, numBytes, bytes); + } else { + LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); + } + } } void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) { - LOG_WARN("noop decryption!\n"); + // For CTR, the implementation is the same + encryptPacket(fromNode, packetId, numBytes, bytes); +} + +// Generic implementation of AES-CTR encryption. +void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) +{ + if (ctr) { + delete ctr; + ctr = nullptr; + } + if (_key.length == 16) + ctr = new CTR(); + else + ctr = new CTR(); + ctr->setKey(_key.bytes, _key.length); + static uint8_t scratch[MAX_BLOCKSIZE]; + memcpy(scratch, bytes, numBytes); + memset(scratch + numBytes, 0, + sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) + + ctr->setIV(_nonce, 16); + ctr->setCounterSize(4); + ctr->encrypt(bytes, scratch, numBytes); } /** @@ -208,4 +239,7 @@ void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId) // use memcpy to avoid breaking strict-aliasing memcpy(nonce, &packetId, sizeof(uint64_t)); memcpy(nonce + sizeof(uint64_t), &fromNode, sizeof(uint32_t)); -} \ No newline at end of file +} +#ifndef HAS_CUSTOM_CRYPTO_ENGINE +CryptoEngine *crypto = new CryptoEngine; +#endif \ No newline at end of file diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index fd607c29e..5ca9db7c1 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -1,5 +1,6 @@ #pragma once #include "AES.h" +#include "CTR.h" #include "concurrency/LockGuard.h" #include "configuration.h" #include "mesh-pb-constants.h" @@ -65,15 +66,16 @@ class CryptoEngine * * @param bytes is updated in place */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); + virtual void encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes); + virtual void encryptAESCtr(CryptoKey key, uint8_t *nonce, size_t numBytes, uint8_t *bytes); #ifndef PIO_UNIT_TESTING protected: #endif /** Our per packet nonce */ uint8_t nonce[16] = {0}; - CryptoKey key = {}; + CTRCommon *ctr = NULL; #if !(MESHTASTIC_EXCLUDE_PKI) uint8_t shared_key[32] = {0}; uint8_t private_key[32] = {0}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1fecef6d7..b00b66a47 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -480,7 +480,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); p->channel = 0; } else { - crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); memcpy(p->encrypted.bytes, bytes, numbytes); } #else diff --git a/src/platform/esp32/ESP32CryptoEngine.cpp b/src/platform/esp32/ESP32CryptoEngine.cpp index 998419df8..230139036 100644 --- a/src/platform/esp32/ESP32CryptoEngine.cpp +++ b/src/platform/esp32/ESP32CryptoEngine.cpp @@ -13,58 +13,29 @@ class ESP32CryptoEngine : public CryptoEngine ~ESP32CryptoEngine() { mbedtls_aes_free(&aes); } - /** - * Set the key used for encrypt, decrypt. - * - * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. - * - * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) - * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the - * provided pointer) - */ - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - - if (key.length != 0) { - auto res = mbedtls_aes_setkey_enc(&aes, key.bytes, key.length * 8); - assert(!res); - } - } - /** * Encrypt a packet * * @param bytes is updated in place + * TODO: return bool, and handle graciously when something fails */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override + virtual void encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) override { - if (key.length > 0) { - LOG_DEBUG("ESP32 crypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t)packetId, numBytes); - initNonce(fromNode, packetId); + if (_key.length > 0) { if (numBytes <= MAX_BLOCKSIZE) { + mbedtls_aes_setkey_enc(&aes, _key.bytes, _key.length * 8); static uint8_t scratch[MAX_BLOCKSIZE]; uint8_t stream_block[16]; size_t nc_off = 0; memcpy(scratch, bytes, numBytes); memset(scratch + numBytes, 0, sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes); - assert(!res); + mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, _nonce, stream_block, scratch, bytes); } else { LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); } } } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: }; -CryptoEngine *crypto = new ESP32CryptoEngine(); +CryptoEngine *crypto = new ESP32CryptoEngine(); \ No newline at end of file diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index fd3f92a9c..b6def5b01 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -42,6 +42,9 @@ #ifndef DEFAULT_VREF #define DEFAULT_VREF 1100 #endif +#ifndef HAS_CUSTOM_CRYPTO_ENGINE +#define HAS_CUSTOM_CRYPTO_ENGINE 1 +#endif #if defined(HAS_AXP192) || defined(HAS_AXP2101) #define HAS_PMU diff --git a/src/platform/nrf52/NRF52CryptoEngine.cpp b/src/platform/nrf52/NRF52CryptoEngine.cpp index a7cf3d5bf..5de13c58b 100644 --- a/src/platform/nrf52/NRF52CryptoEngine.cpp +++ b/src/platform/nrf52/NRF52CryptoEngine.cpp @@ -9,41 +9,24 @@ class NRF52CryptoEngine : public CryptoEngine ~NRF52CryptoEngine() {} - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override + virtual void encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) override { - if (key.length > 16) { - LOG_DEBUG("Software encrypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t)packetId, numBytes); + if (_key.length > 16) { AES_ctx ctx; - initNonce(fromNode, packetId); - AES_init_ctx_iv(&ctx, key.bytes, nonce); + AES_init_ctx_iv(&ctx, _key.bytes, _nonce); AES_CTR_xcrypt_buffer(&ctx, bytes, numBytes); - } else if (key.length > 0) { - LOG_DEBUG("nRF52 encrypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t)packetId, numBytes); + } else if (_key.length > 0) { nRFCrypto.begin(); nRFCrypto_AES ctx; uint8_t myLen = ctx.blockLen(numBytes); char encBuf[myLen] = {0}; - initNonce(fromNode, packetId); ctx.begin(); - ctx.Process((char *)bytes, numBytes, nonce, key.bytes, key.length, encBuf, ctx.encryptFlag, ctx.ctrMode); + ctx.Process((char *)bytes, numBytes, _nonce, _key.bytes, _key.length, encBuf, ctx.encryptFlag, ctx.ctrMode); ctx.end(); nRFCrypto.end(); memcpy(bytes, encBuf, numBytes); } } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: }; -CryptoEngine *crypto = new NRF52CryptoEngine(); +CryptoEngine *crypto = new NRF52CryptoEngine(); \ No newline at end of file diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index d5685d611..8781347c3 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -32,6 +32,9 @@ #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 1 #endif +#ifndef HAS_CUSTOM_CRYPTO_ENGINE +#define HAS_CUSTOM_CRYPTO_ENGINE 1 +#endif // // set HW_VENDOR diff --git a/src/platform/portduino/CrossPlatformCryptoEngine.cpp b/src/platform/portduino/CrossPlatformCryptoEngine.cpp deleted file mode 100644 index 46ef942f0..000000000 --- a/src/platform/portduino/CrossPlatformCryptoEngine.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "AES.h" -#include "CTR.h" -#include "CryptoEngine.h" -#include "configuration.h" - -/** A platform independent AES engine implemented using Tiny-AES - */ -class CrossPlatformCryptoEngine : public CryptoEngine -{ - - CTRCommon *ctr = NULL; - - public: - CrossPlatformCryptoEngine() {} - - ~CrossPlatformCryptoEngine() {} - - /** - * Set the key used for encrypt, decrypt. - * - * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. - * - * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) - * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the - * provided pointer) - */ - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - LOG_DEBUG("Installing AES%d key!\n", key.length * 8); - if (ctr) { - delete ctr; - ctr = NULL; - } - if (key.length != 0) { - if (key.length == 16) - ctr = new CTR(); - else - ctr = new CTR(); - - ctr->setKey(key.bytes, key.length); - } - } - - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - if (key.length > 0) { - initNonce(fromNode, packetId); - if (numBytes <= MAX_BLOCKSIZE) { - static uint8_t scratch[MAX_BLOCKSIZE]; - memcpy(scratch, bytes, numBytes); - memset(scratch + numBytes, 0, - sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - ctr->setIV(nonce, sizeof(nonce)); - ctr->setCounterSize(4); - ctr->encrypt(bytes, scratch, numBytes); - } else { - LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); - } - } - } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: -}; - -CryptoEngine *crypto = new CrossPlatformCryptoEngine(); diff --git a/src/platform/rp2040/rp2040CryptoEngine.cpp b/src/platform/rp2040/rp2040CryptoEngine.cpp deleted file mode 100644 index 5486e51e5..000000000 --- a/src/platform/rp2040/rp2040CryptoEngine.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "AES.h" -#include "CTR.h" -#include "CryptoEngine.h" -#include "configuration.h" - -class RP2040CryptoEngine : public CryptoEngine -{ - - CTRCommon *ctr = NULL; - - public: - RP2040CryptoEngine() {} - - ~RP2040CryptoEngine() {} - - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - LOG_DEBUG("Installing AES%d key!\n", key.length * 8); - if (ctr) { - delete ctr; - ctr = NULL; - } - if (key.length != 0) { - if (key.length == 16) - ctr = new CTR(); - else - ctr = new CTR(); - - ctr->setKey(key.bytes, key.length); - } - } - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - if (key.length > 0) { - initNonce(fromNode, packetId); - if (numBytes <= MAX_BLOCKSIZE) { - static uint8_t scratch[MAX_BLOCKSIZE]; - memcpy(scratch, bytes, numBytes); - memset(scratch + numBytes, 0, - sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - ctr->setIV(nonce, sizeof(nonce)); - ctr->setCounterSize(4); - ctr->encrypt(bytes, scratch, numBytes); - } else { - LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); - } - } - } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: -}; - -CryptoEngine *crypto = new RP2040CryptoEngine(); diff --git a/src/platform/stm32wl/STM32WLCryptoEngine.cpp b/src/platform/stm32wl/STM32WLCryptoEngine.cpp deleted file mode 100644 index 4debdf78e..000000000 --- a/src/platform/stm32wl/STM32WLCryptoEngine.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#undef RNG -#include "AES.h" -#include "CTR.h" -#include "CryptoEngine.h" -#include "configuration.h" - -class STM32WLCryptoEngine : public CryptoEngine -{ - - CTRCommon *ctr = NULL; - - public: - STM32WLCryptoEngine() {} - - ~STM32WLCryptoEngine() {} - - virtual void setKey(const CryptoKey &k) override - { - CryptoEngine::setKey(k); - LOG_DEBUG("Installing AES%d key!\n", key.length * 8); - if (ctr) { - delete ctr; - ctr = NULL; - } - if (key.length != 0) { - if (key.length == 16) - ctr = new CTR(); - else - ctr = new CTR(); - - ctr->setKey(key.bytes, key.length); - } - } - /** - * Encrypt a packet - * - * @param bytes is updated in place - */ - virtual void encrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - if (key.length > 0) { - initNonce(fromNode, packetId); - if (numBytes <= MAX_BLOCKSIZE) { - static uint8_t scratch[MAX_BLOCKSIZE]; - memcpy(scratch, bytes, numBytes); - memset(scratch + numBytes, 0, - sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) - - ctr->setIV(nonce, sizeof(nonce)); - ctr->setCounterSize(4); - ctr->encrypt(bytes, scratch, numBytes); - } else { - LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes); - } - } - } - - virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes) override - { - // For CTR, the implementation is the same - encrypt(fromNode, packetId, numBytes, bytes); - } - - private: -}; - -CryptoEngine *crypto = new STM32WLCryptoEngine(); diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index e9aee928e..129c88283 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -99,6 +99,32 @@ void test_DH25519(void) crypto->setDHPrivateKey(private_key); TEST_ASSERT(!crypto->setDHPublicKey(public_key)); // Weak public key results in 0 shared key } +void test_AES_CTR(void) +{ + uint8_t expected[32]; + uint8_t plain[32]; + uint8_t nonce[32]; + CryptoKey k; + + // vectors from https://www.rfc-editor.org/rfc/rfc3686#section-6 + k.length = 32; + HexToBytes(k.bytes, "776BEFF2851DB06F4C8A0542C8696F6C6A81AF1EEC96B4D37FC1D689E6C1C104"); + HexToBytes(nonce, "00000060DB5672C97AA8F0B200000001"); + HexToBytes(expected, "145AD01DBF824EC7560863DC71E3E0C0"); + memcpy(plain, "Single block msg", 16); + + crypto->encryptAESCtr(k, nonce, 16, plain); + TEST_ASSERT_EQUAL_MEMORY(expected, plain, 16); + + k.length = 16; + memcpy(plain, "Single block msg", 16); + HexToBytes(k.bytes, "AE6852F8121067CC4BF7A5765577F39E"); + HexToBytes(nonce, "00000030000000000000000000000001"); + HexToBytes(expected, "E4095D4FB7A7B3792D6175A3261311B8"); + crypto->encryptAESCtr(k, nonce, 16, plain); + TEST_ASSERT_EQUAL_MEMORY(expected, plain, 16); +} + void setup() { // NOTE!!! Wait for >2 secs @@ -109,6 +135,7 @@ void setup() RUN_TEST(test_SHA256); RUN_TEST(test_ECB_AES256); RUN_TEST(test_DH25519); + RUN_TEST(test_AES_CTR); } void loop() From c86a3200f04fc90ea045988cdbbb62078390f115 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 10 Aug 2024 23:11:04 -0500 Subject: [PATCH 0848/1377] Add missed function rename. (Thanks VSCode) --- src/mesh/Router.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b00b66a47..2a6fb31fc 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -484,7 +484,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) memcpy(p->encrypted.bytes, bytes, numbytes); } #else - crypto->encrypt(getFrom(p), p->id, numbytes, bytes); + crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); memcpy(p->encrypted.bytes, bytes, numbytes); #endif From 185eb318ad274fd95315ebd2da3aafe20e5802ca Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Aug 2024 14:12:20 -0500 Subject: [PATCH 0849/1377] Manual protobuf update --- src/mesh/generated/meshtastic/admin.pb.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index bef2abf9b..13093839d 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -30,7 +30,9 @@ typedef enum _meshtastic_AdminMessage_ConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ConfigType_LORA_CONFIG = 5, /* TODO: REPLACE */ - meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG = 6 + meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG = 6, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG = 7 } meshtastic_AdminMessage_ConfigType; /* TODO: REPLACE */ @@ -194,8 +196,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG -#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG -#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG+1)) +#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG +#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG #define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG From e7dfabc20fa5518103821c7ca95bd77cdcea9768 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 11 Aug 2024 14:18:33 -0500 Subject: [PATCH 0850/1377] Exclude position packets from PKI (at least for now) --- src/mesh/Router.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 2a6fb31fc..945f92bb7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -470,8 +470,9 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) #if !(MESHTASTIC_EXCLUDE_PKI) meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); if (!owner.is_licensed && p->to != NODENUM_BROADCAST && node != nullptr && node->user.public_key.size > 0 && - p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && - p->decoded.portnum != meshtastic_PortNum_ROUTING_APP) { // TODO: check for size due to 8 byte tag + numbytes <= MAX_RHPACKETLEN - 8 && p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && + p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && + p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); if (numbytes + 8 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; From 8f3614d66c2671d5160c3bb95374c0ba712dc2f6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 11 Aug 2024 17:22:11 -0500 Subject: [PATCH 0851/1377] User to UserLite in NodeDB (#4438) * User to UserLite in the nodedb * Tronkdor the burninator --- protobufs | 2 +- src/RedirectablePrint.cpp | 5 +- src/mesh/NodeDB.cpp | 14 +++-- src/mesh/TypeConversions.cpp | 32 +++++++++- src/mesh/TypeConversions.h | 2 + .../generated/meshtastic/deviceonly.pb.cpp | 3 + src/mesh/generated/meshtastic/deviceonly.pb.h | 62 +++++++++++++++++-- 7 files changed, 106 insertions(+), 14 deletions(-) diff --git a/protobufs b/protobufs index 778667d93..c7a5a410b 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 778667d93b9769d51c386c461456bdec4f14f433 +Subproject commit c7a5a410b9652a91533f44c5fa4c28f0a6c96429 diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 02cd8b309..0eab0de0a 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -38,8 +38,9 @@ size_t RedirectablePrint::write(uint8_t c) #ifdef USE_SEGGER SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c); #endif - - if (!config.has_lora || config.security.serial_enabled) + // Account for legacy config transition + bool serialEnabled = config.has_security ? config.security.serial_enabled : config.device.serial_enabled; + if (!config.has_lora || serialEnabled) dest->write(c); return 1; // We always claim one was written, rather than trusting what the diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 468bffab3..a9c8bbb44 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -134,6 +134,10 @@ NodeDB::NodeDB() crypto->setDHPrivateKey(config.security.private_key.bytes); } else { #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + config.has_security = true; + config.security.serial_enabled = config.device.serial_enabled; + config.security.bluetooth_logging_enabled = config.bluetooth.device_logging_enabled; + config.security.is_managed = config.device.is_managed; LOG_INFO("Generating new PKI keys\n"); crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); config.security.public_key.size = 32; @@ -149,7 +153,7 @@ NodeDB::NodeDB() #endif - info->user = owner; + info->user = TypeConversions::ConvertToUserLite(owner); info->has_user = true; #ifdef ARCH_ESP32 @@ -1001,7 +1005,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde return false; } - LOG_DEBUG("old user %s/%s/%s, channel=%d\n", info->user.id, info->user.long_name, info->user.short_name, info->channel); + LOG_DEBUG("old user %s/%s, channel=%d\n", info->user.long_name, info->user.short_name, info->channel); #if !(MESHTASTIC_EXCLUDE_PKI) if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one printBytes("Retaining Old Pubkey: ", info->user.public_key.bytes, 32); @@ -1012,11 +1016,11 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde // Both of info->user and p start as filled with zero so I think this is okay bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); - info->user = p; + info->user = TypeConversions::ConvertToUserLite(p); if (nodeId != getNodeNum()) info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel) - LOG_DEBUG("updating changed=%d user %s/%s/%s, channel=%d\n", changed, info->user.id, info->user.long_name, - info->user.short_name, info->channel); + LOG_DEBUG("updating changed=%d user %s/%s, channel=%d\n", changed, info->user.long_name, info->user.short_name, + info->channel); info->has_user = true; if (changed) { diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index bcd600f24..30b06d0ad 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -24,7 +24,7 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo } if (lite->has_user) { info.has_user = true; - info.user = lite->user; + info.user = ConvertToUser(lite->num, lite->user); } if (lite->has_device_metrics) { info.has_device_metrics = true; @@ -55,4 +55,34 @@ meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite l position.time = lite.time; return position; +} + +meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user) +{ + meshtastic_UserLite lite = meshtastic_UserLite_init_default; + + strncpy(lite.long_name, user.long_name, sizeof(lite.long_name)); + strncpy(lite.short_name, user.short_name, sizeof(lite.short_name)); + lite.hw_model = user.hw_model; + lite.role = user.role; + lite.is_licensed = user.is_licensed; + memccpy(lite.macaddr, user.macaddr, sizeof(user.macaddr), sizeof(lite.macaddr)); + memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes)); + return lite; +} + +meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_UserLite lite) +{ + meshtastic_User user = meshtastic_User_init_default; + + snprintf(user.id, sizeof(user.id), "!%08x", nodeNum); + strncpy(user.long_name, lite.long_name, sizeof(user.long_name)); + strncpy(user.short_name, lite.short_name, sizeof(user.short_name)); + user.hw_model = lite.hw_model; + user.role = lite.role; + user.is_licensed = lite.is_licensed; + memccpy(user.macaddr, lite.macaddr, sizeof(lite.macaddr), sizeof(user.macaddr)); + memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes)); + + return user; } \ No newline at end of file diff --git a/src/mesh/TypeConversions.h b/src/mesh/TypeConversions.h index ffc3c12a7..19e471f98 100644 --- a/src/mesh/TypeConversions.h +++ b/src/mesh/TypeConversions.h @@ -10,4 +10,6 @@ class TypeConversions static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite); static meshtastic_PositionLite ConvertToPositionLite(meshtastic_Position position); static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite); + static meshtastic_UserLite ConvertToUserLite(meshtastic_User user); + static meshtastic_User ConvertToUser(uint32_t nodeNum, meshtastic_UserLite lite); }; diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 672192f67..2747ac9d9 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -9,6 +9,9 @@ PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO) +PB_BIND(meshtastic_UserLite, meshtastic_UserLite, AUTO) + + PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 2c91fe30e..343e5f48a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -9,6 +9,7 @@ #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" #include "meshtastic/telemetry.pb.h" +#include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -45,12 +46,37 @@ typedef struct _meshtastic_PositionLite { meshtastic_Position_LocSource location_source; } meshtastic_PositionLite; +typedef PB_BYTES_ARRAY_T(32) meshtastic_UserLite_public_key_t; +typedef struct _meshtastic_UserLite { + /* This is the addr of the radio. */ + pb_byte_t macaddr[6]; + /* A full name for this user, i.e. "Kevin Hester" */ + char long_name[40]; + /* A VERY short name, ideally two characters. + Suitable for a tiny OLED screen */ + char short_name[5]; + /* TBEAM, HELTEC, etc... + Starting in 1.2.11 moved to hw_model enum in the NodeInfo object. + Apps will still need the string here for older builds + (so OTA update can find the right image), but if the enum is available it will be used instead. */ + meshtastic_HardwareModel hw_model; + /* In some regions Ham radio operators have different bandwidth limitations than others. + If this user is a licensed operator, set this flag. + Also, "long_name" should be their licence number. */ + bool is_licensed; + /* Indicates that the user's role in the mesh */ + meshtastic_Config_DeviceConfig_Role role; + /* The public key of the user's device. + This is sent out to other nodes on the mesh to allow them to compute a shared secret key. */ + meshtastic_UserLite_public_key_t public_key; +} meshtastic_UserLite; + typedef struct _meshtastic_NodeInfoLite { /* The node number */ uint32_t num; /* The user info for this node */ bool has_user; - meshtastic_User user; + meshtastic_UserLite user; /* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. Position.time now indicates the last time we received a POSITION from that node. */ bool has_position; @@ -164,6 +190,9 @@ extern "C" { #define meshtastic_PositionLite_location_source_ENUMTYPE meshtastic_Position_LocSource +#define meshtastic_UserLite_hw_model_ENUMTYPE meshtastic_HardwareModel +#define meshtastic_UserLite_role_ENUMTYPE meshtastic_Config_DeviceConfig_Role + @@ -172,12 +201,14 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} +#define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} +#define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -188,6 +219,13 @@ extern "C" { #define meshtastic_PositionLite_altitude_tag 3 #define meshtastic_PositionLite_time_tag 4 #define meshtastic_PositionLite_location_source_tag 5 +#define meshtastic_UserLite_macaddr_tag 1 +#define meshtastic_UserLite_long_name_tag 2 +#define meshtastic_UserLite_short_name_tag 3 +#define meshtastic_UserLite_hw_model_tag 4 +#define meshtastic_UserLite_is_licensed_tag 5 +#define meshtastic_UserLite_role_tag 6 +#define meshtastic_UserLite_public_key_tag 7 #define meshtastic_NodeInfoLite_num_tag 1 #define meshtastic_NodeInfoLite_user_tag 2 #define meshtastic_NodeInfoLite_position_tag 3 @@ -229,6 +267,17 @@ X(a, STATIC, SINGULAR, UENUM, location_source, 5) #define meshtastic_PositionLite_CALLBACK NULL #define meshtastic_PositionLite_DEFAULT NULL +#define meshtastic_UserLite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 1) \ +X(a, STATIC, SINGULAR, STRING, long_name, 2) \ +X(a, STATIC, SINGULAR, STRING, short_name, 3) \ +X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \ +X(a, STATIC, SINGULAR, BOOL, is_licensed, 5) \ +X(a, STATIC, SINGULAR, UENUM, role, 6) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 7) +#define meshtastic_UserLite_CALLBACK NULL +#define meshtastic_UserLite_DEFAULT NULL + #define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ @@ -242,7 +291,7 @@ X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL -#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User +#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite #define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite #define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics @@ -290,6 +339,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, oem_local_module_config, 8) #define meshtastic_OEMStore_oem_local_module_config_MSGTYPE meshtastic_LocalModuleConfig extern const pb_msgdesc_t meshtastic_PositionLite_msg; +extern const pb_msgdesc_t meshtastic_UserLite_msg; extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_DeviceState_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; @@ -297,6 +347,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_PositionLite_fields &meshtastic_PositionLite_msg +#define meshtastic_UserLite_fields &meshtastic_UserLite_msg #define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg #define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg @@ -306,9 +357,10 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; /* meshtastic_DeviceState_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 -#define meshtastic_NodeInfoLite_size 200 +#define meshtastic_NodeInfoLite_size 183 #define meshtastic_OEMStore_size 3502 #define meshtastic_PositionLite_size 28 +#define meshtastic_UserLite_size 96 #ifdef __cplusplus } /* extern "C" */ From 884bc529f0ce5c52ec3402f10446023a75fbd9e5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 11 Aug 2024 18:25:32 -0500 Subject: [PATCH 0852/1377] protos --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index c7a5a410b..8063f8026 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c7a5a410b9652a91533f44c5fa4c28f0a6c96429 +Subproject commit 8063f80260a5af438d5b68c6587b4fed77d6c46d From 67ddae2851a6d51a059baaf70540f4770b37c320 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 06:43:54 -0500 Subject: [PATCH 0853/1377] Add logic to nodeDB to prefer evicting boring nodes (#4441) --- src/mesh/NodeDB.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a9c8bbb44..f2a0520e3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1097,11 +1097,24 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; + int oldestIndex = -1; + int oldestBoringIndex = -1; for (int i = 1; i < numMeshNodes; i++) { + // Simply the oldest non-favorite node if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) { oldest = meshNodes->at(i).last_heard; oldestIndex = i; } + // The oldest "boring" node + if (!meshNodes->at(i).is_favorite && meshNodes->at(i).user.public_key.size == 0 && + meshNodes->at(i).last_heard < oldestBoring) { + oldestBoring = meshNodes->at(i).last_heard; + oldestBoringIndex = i; + } + } + // if we found a "boring" node, evict it + if (oldestBoringIndex != -1) { + oldestIndex = oldestBoringIndex; } // Shove the remaining nodes down the chain for (int i = oldestIndex; i < numMeshNodes - 1; i++) { @@ -1142,4 +1155,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} \ No newline at end of file +} From 2d1813023500dccb2f42763aec8810a3448c849c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 11:26:43 -0500 Subject: [PATCH 0854/1377] Don't goober public_key in Userlite conversion --- src/mesh/NodeDB.cpp | 15 ++++++++++++--- src/mesh/TypeConversions.cpp | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index f2a0520e3..3fdc22685 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1007,9 +1007,15 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde LOG_DEBUG("old user %s/%s, channel=%d\n", info->user.long_name, info->user.short_name, info->channel); #if !(MESHTASTIC_EXCLUDE_PKI) - if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one - printBytes("Retaining Old Pubkey: ", info->user.public_key.bytes, 32); - memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); + if (p.public_key.size > 0) { + printBytes("Incoming Pubkey: ", p.public_key.bytes, 32); + if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one + LOG_INFO("Public Key set for node, not updateing!\n"); + // we copy the key into the incoming packet, to prevent overwrite + memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); + } else { + LOG_INFO("Updating Node Pubkey!\n"); + } } #endif @@ -1017,6 +1023,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); info->user = TypeConversions::ConvertToUserLite(p); + if (info->user.public_key.size == 32) { + printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32); + } if (nodeId != getNodeNum()) info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel) LOG_DEBUG("updating changed=%d user %s/%s, channel=%d\n", changed, info->user.long_name, info->user.short_name, diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 30b06d0ad..5303dfb49 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -68,6 +68,7 @@ meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user) lite.is_licensed = user.is_licensed; memccpy(lite.macaddr, user.macaddr, sizeof(user.macaddr), sizeof(lite.macaddr)); memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes)); + lite.public_key.size = user.public_key.size; return lite; } From 7537b55586aaaa526098a798cc3a9da59aedb589 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 11:37:50 -0500 Subject: [PATCH 0855/1377] Ungoober oldestBoring --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 3fdc22685..ef649288e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1105,7 +1105,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) memGet.getFreeHeap()); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; - int oldestIndex = -1; + uint32_t oldestBoring = UINT32_MAX; int oldestIndex = -1; int oldestBoringIndex = -1; for (int i = 1; i < numMeshNodes; i++) { From b91d66b436d11f98963f5a6d93390d7517cb5c53 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 16:20:07 -0500 Subject: [PATCH 0856/1377] Don't forget public_key.size in converting back --- src/mesh/TypeConversions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 5303dfb49..d8ee6afc7 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -84,6 +84,7 @@ meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_User user.is_licensed = lite.is_licensed; memccpy(user.macaddr, lite.macaddr, sizeof(lite.macaddr), sizeof(user.macaddr)); memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes)); + user.public_key.size = lite.public_key.size; return user; } \ No newline at end of file From 0e7253d309d75a048abb59459d8dd5b037673727 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 12 Aug 2024 19:37:39 -0500 Subject: [PATCH 0857/1377] Protos --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 8063f8026..6307b44cd 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8063f80260a5af438d5b68c6587b4fed77d6c46d +Subproject commit 6307b44cd98ef1d86d5f39edefe9361e79d9ece4 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 70423f673..3800e6c0c 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -300,7 +300,9 @@ typedef enum _meshtastic_Routing_Error { meshtastic_Routing_Error_BAD_REQUEST = 32, /* The application layer service on the remote node received your request, but considered your request not authorized (i.e you did not send the request on the required bound channel) */ - meshtastic_Routing_Error_NOT_AUTHORIZED = 33 + meshtastic_Routing_Error_NOT_AUTHORIZED = 33, + /* This message is not a failure, and indicates that the message was sent via PKI */ + meshtastic_Routing_Error_NONE_PKI = 34 } meshtastic_Routing_Error; /* The priority of this message for sending. @@ -993,8 +995,8 @@ extern "C" { #define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1)) #define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE -#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_NOT_AUTHORIZED -#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_NOT_AUTHORIZED+1)) +#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_NONE_PKI +#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_NONE_PKI+1)) #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX From b4cbea1b3d63c759eb771c1f752b88e4dca66661 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 23:29:54 -0500 Subject: [PATCH 0858/1377] Move security migrate to if has_security --- src/mesh/NodeDB.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ef649288e..521a9d6d7 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -124,6 +124,12 @@ NodeDB::NodeDB() // Include our owner in the node db under our nodenum meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); + if (!config.has_security) { + config.has_security = true; + config.security.serial_enabled = config.device.serial_enabled; + config.security.bluetooth_logging_enabled = config.bluetooth.device_logging_enabled; + config.security.is_managed = config.device.is_managed; + } #if !(MESHTASTIC_EXCLUDE_PKI) // Calculate Curve25519 public and private keys printBytes("Old Pubkey", config.security.public_key.bytes, 32); From c16f20de21cded1d56ea9992b86ebe9b2ce3c816 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 23:30:34 -0500 Subject: [PATCH 0859/1377] Make "Alloc an error" a LOG_WARN --- src/mesh/MeshModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 604ac9dc4..3fe4c85d5 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -54,8 +54,8 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod p->to = to; p->decoded.request_id = idFrom; p->channel = chIndex; - if (err != meshtastic_Routing_Error_NONE) - LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); + if (err != meshtastic_Routing_Error_NONE && err != meshtastic_Routing_Error_NONE_PKI) + LOG_WARN("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); return p; } From 754db3f2bcff99e38aea1443934bc64416549e9a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 23:32:56 -0500 Subject: [PATCH 0860/1377] Finish fixing config migrate --- src/mesh/NodeDB.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 521a9d6d7..7f403c53a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -140,10 +140,6 @@ NodeDB::NodeDB() crypto->setDHPrivateKey(config.security.private_key.bytes); } else { #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) - config.has_security = true; - config.security.serial_enabled = config.device.serial_enabled; - config.security.bluetooth_logging_enabled = config.bluetooth.device_logging_enabled; - config.security.is_managed = config.device.is_managed; LOG_INFO("Generating new PKI keys\n"); crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); config.security.public_key.size = 32; From 308c0a6bb8e5f4366e079b9c767af049b4263ef5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 12 Aug 2024 23:39:45 -0500 Subject: [PATCH 0861/1377] Add Routing_Error_NONE --- src/mesh/MeshService.cpp | 3 ++- src/mesh/MeshTypes.h | 2 +- src/mesh/ReliableRouter.cpp | 23 ++++++++++------------- src/mesh/Router.cpp | 8 +++++--- src/modules/CannedMessageModule.cpp | 3 ++- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index ac97d51a7..f22949576 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -253,7 +253,8 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh LOG_DEBUG("Can't send status to phone"); } - if (res == ERRNO_OK && ccToPhone) { // Check if p is not released in case it couldn't be sent + if ((res == ERRNO_OK || res == meshtastic_Routing_Error_NONE_PKI) && + ccToPhone) { // Check if p is not released in case it couldn't be sent sendToPhone(packetPool.allocCopy(*p)); } } diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index c0919bf5d..ecac626a8 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -15,7 +15,7 @@ typedef uint32_t PacketId; // A packet sequence number #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER -#define ERRNO_DISABLED 34 // the interface is disabled +#define ERRNO_DISABLED 35 // the interface is disabled /* * Source of a received message diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index c91ce50c5..6b1cf92e7 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -117,20 +117,17 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas } // We consider an ack to be either a !routing packet with a request ID or a routing packet with !error - PacketId ackId = ((c && c->error_reason == meshtastic_Routing_Error_NONE) || !c) ? p->decoded.request_id : 0; + if ((c && (c->error_reason == meshtastic_Routing_Error_NONE || c->error_reason == meshtastic_Routing_Error_NONE_PKI)) || + !c) { + LOG_DEBUG("Received an ack for 0x%x, stopping retransmissions\n", p->decoded.request_id); + stopRetransmission(p->to, p->decoded.request_id); + // } else if (c && (c->error_reason == meshtastic_Routing_Error_NO_CHANNEL)) { + // noop? + } else if (c && + (c->error_reason != meshtastic_Routing_Error_NONE && c->error_reason != meshtastic_Routing_Error_NONE_PKI)) { - // A nak is a routing packt that has an error code - PacketId nakId = (c && c->error_reason != meshtastic_Routing_Error_NONE) ? p->decoded.request_id : 0; - - // We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records - if (ackId || nakId) { - if (ackId) { - LOG_DEBUG("Received an ack for 0x%x, stopping retransmissions\n", ackId); - stopRetransmission(p->to, ackId); - } else { - LOG_DEBUG("Received a nak for 0x%x, stopping retransmissions\n", nakId); - stopRetransmission(p->to, nakId); - } + LOG_DEBUG("Received a nak for 0x%x, stopping retransmissions\n", p->decoded.request_id); + stopRetransmission(p->to, p->decoded.request_id); } } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 945f92bb7..64fa4cca2 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -258,7 +258,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p); auto encodeResult = perhapsEncode(p); - if (encodeResult != meshtastic_Routing_Error_NONE) { + if (encodeResult != meshtastic_Routing_Error_NONE && encodeResult != meshtastic_Routing_Error_NONE_PKI) { packetPool.release(p_decoded); abortSendAndNak(encodeResult, p); return encodeResult; // FIXME - this isn't a valid ErrorCode @@ -493,8 +493,10 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->encrypted.size = numbytes; p->which_payload_variant = meshtastic_MeshPacket_encrypted_tag; } - - return meshtastic_Routing_Error_NONE; + if (p->pki_encrypted) + return meshtastic_Routing_Error_NONE_PKI; + else + return meshtastic_Routing_Error_NONE; } NodeNum Router::getNodeNum() diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 4df5a03fc..2ec914668 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1105,7 +1105,8 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket & this->incoming = service->getNodenumFromRequestId(mp.decoded.request_id); meshtastic_Routing decoded = meshtastic_Routing_init_default; pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); - this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; + this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE || + decoded.error_reason == meshtastic_Routing_Error_NONE_PKI; waitingForAck = false; // No longer want routing packets this->notifyObservers(&e); // run the next time 2 seconds later From bcd77c45232c2b05c63f7f646452ef53182e7dad Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 06:31:05 -0500 Subject: [PATCH 0862/1377] Cleanup public_keys (#4450) --- src/mesh/NodeDB.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 7f403c53a..1caaaf39b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -554,10 +554,21 @@ void NodeDB::cleanupMeshDB() { int newPos = 0, removed = 0; for (int i = 0; i < numMeshNodes; i++) { - if (meshNodes->at(i).has_user) + if (meshNodes->at(i).has_user) { + if (meshNodes->at(i).user.public_key.size > 0) { + for (int j = 0; j < numMeshNodes; j++) { + if (meshNodes->at(i).user.public_key.bytes[j] != 0) { + break; + } + if (j == 31) { + meshNodes->at(i).user.public_key.size = 0; + } + } + } meshNodes->at(newPos++) = meshNodes->at(i); - else + } else { removed++; + } } numMeshNodes -= removed; std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed, @@ -1166,4 +1177,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} +} \ No newline at end of file From f3fa8daedf9da40ca9e2f4c92880e349da5fe36a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 09:15:20 -0500 Subject: [PATCH 0863/1377] Revert "Add Routing_Error_NONE" This reverts commit e1985fa0f90db0cbc346db18cecbf3604f6973c1. --- src/mesh/MeshService.cpp | 3 +-- src/mesh/MeshTypes.h | 2 +- src/mesh/ReliableRouter.cpp | 23 +++++++++++++---------- src/mesh/Router.cpp | 8 +++----- src/modules/CannedMessageModule.cpp | 3 +-- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index f22949576..ac97d51a7 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -253,8 +253,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh LOG_DEBUG("Can't send status to phone"); } - if ((res == ERRNO_OK || res == meshtastic_Routing_Error_NONE_PKI) && - ccToPhone) { // Check if p is not released in case it couldn't be sent + if (res == ERRNO_OK && ccToPhone) { // Check if p is not released in case it couldn't be sent sendToPhone(packetPool.allocCopy(*p)); } } diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index ecac626a8..c0919bf5d 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -15,7 +15,7 @@ typedef uint32_t PacketId; // A packet sequence number #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER -#define ERRNO_DISABLED 35 // the interface is disabled +#define ERRNO_DISABLED 34 // the interface is disabled /* * Source of a received message diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 6b1cf92e7..c91ce50c5 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -117,17 +117,20 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas } // We consider an ack to be either a !routing packet with a request ID or a routing packet with !error - if ((c && (c->error_reason == meshtastic_Routing_Error_NONE || c->error_reason == meshtastic_Routing_Error_NONE_PKI)) || - !c) { - LOG_DEBUG("Received an ack for 0x%x, stopping retransmissions\n", p->decoded.request_id); - stopRetransmission(p->to, p->decoded.request_id); - // } else if (c && (c->error_reason == meshtastic_Routing_Error_NO_CHANNEL)) { - // noop? - } else if (c && - (c->error_reason != meshtastic_Routing_Error_NONE && c->error_reason != meshtastic_Routing_Error_NONE_PKI)) { + PacketId ackId = ((c && c->error_reason == meshtastic_Routing_Error_NONE) || !c) ? p->decoded.request_id : 0; - LOG_DEBUG("Received a nak for 0x%x, stopping retransmissions\n", p->decoded.request_id); - stopRetransmission(p->to, p->decoded.request_id); + // A nak is a routing packt that has an error code + PacketId nakId = (c && c->error_reason != meshtastic_Routing_Error_NONE) ? p->decoded.request_id : 0; + + // We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records + if (ackId || nakId) { + if (ackId) { + LOG_DEBUG("Received an ack for 0x%x, stopping retransmissions\n", ackId); + stopRetransmission(p->to, ackId); + } else { + LOG_DEBUG("Received a nak for 0x%x, stopping retransmissions\n", nakId); + stopRetransmission(p->to, nakId); + } } } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 64fa4cca2..945f92bb7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -258,7 +258,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p); auto encodeResult = perhapsEncode(p); - if (encodeResult != meshtastic_Routing_Error_NONE && encodeResult != meshtastic_Routing_Error_NONE_PKI) { + if (encodeResult != meshtastic_Routing_Error_NONE) { packetPool.release(p_decoded); abortSendAndNak(encodeResult, p); return encodeResult; // FIXME - this isn't a valid ErrorCode @@ -493,10 +493,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->encrypted.size = numbytes; p->which_payload_variant = meshtastic_MeshPacket_encrypted_tag; } - if (p->pki_encrypted) - return meshtastic_Routing_Error_NONE_PKI; - else - return meshtastic_Routing_Error_NONE; + + return meshtastic_Routing_Error_NONE; } NodeNum Router::getNodeNum() diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 2ec914668..4df5a03fc 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1105,8 +1105,7 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket & this->incoming = service->getNodenumFromRequestId(mp.decoded.request_id); meshtastic_Routing decoded = meshtastic_Routing_init_default; pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); - this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE || - decoded.error_reason == meshtastic_Routing_Error_NONE_PKI; + this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; waitingForAck = false; // No longer want routing packets this->notifyObservers(&e); // run the next time 2 seconds later From 80fd121d87eac1dbb447828ada7c4a5122795832 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 12:10:46 -0500 Subject: [PATCH 0864/1377] Add meshtastic_Routing_Error_NO_CHANNEL --- src/mesh/MeshModule.cpp | 2 +- src/mesh/Router.cpp | 20 +++++++++++++++++--- src/mesh/generated/meshtastic/mesh.pb.h | 8 ++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 3fe4c85d5..3b137d4bd 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -54,7 +54,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod p->to = to; p->decoded.request_id = idFrom; p->channel = chIndex; - if (err != meshtastic_Routing_Error_NONE && err != meshtastic_Routing_Error_NONE_PKI) + if (err != meshtastic_Routing_Error_NONE) LOG_WARN("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); return p; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 945f92bb7..832d7b91f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -461,9 +461,6 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it auto hash = channels.setActiveByIndex(chIndex); - if (hash < 0) - // No suitable channel could be found for sending - return meshtastic_Routing_Error_NO_CHANNEL; // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; @@ -480,11 +477,28 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) numbytes += 8; memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); p->channel = 0; + p->pki_encrypted = true; } else { + if (p->pki_encrypted == true) { + // Client specifically requested PKI encryption + return meshtastic_Routing_Error_PKI_FAILED; + } + if (hash < 0) { + // No suitable channel could be found for sending + return meshtastic_Routing_Error_NO_CHANNEL; + } crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); memcpy(p->encrypted.bytes, bytes, numbytes); } #else + if (p->pki_encrypted == true) { + // Client specifically requested PKI encryption + return meshtastic_Routing_Error_PKI_FAILED; + } + if (hash < 0) { + // No suitable channel could be found for sending + return meshtastic_Routing_Error_NO_CHANNEL; + } crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); memcpy(p->encrypted.bytes, bytes, numbytes); #endif diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 3800e6c0c..7625fbf92 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -301,8 +301,8 @@ typedef enum _meshtastic_Routing_Error { /* The application layer service on the remote node received your request, but considered your request not authorized (i.e you did not send the request on the required bound channel) */ meshtastic_Routing_Error_NOT_AUTHORIZED = 33, - /* This message is not a failure, and indicates that the message was sent via PKI */ - meshtastic_Routing_Error_NONE_PKI = 34 + /* The client specified a PKI transport, but the node was unable to send the packet using PKI (and did not send the message at all) */ + meshtastic_Routing_Error_PKI_FAILED = 34 } meshtastic_Routing_Error; /* The priority of this message for sending. @@ -995,8 +995,8 @@ extern "C" { #define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1)) #define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE -#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_NONE_PKI -#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_NONE_PKI+1)) +#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_PKI_FAILED +#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_PKI_FAILED+1)) #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX From ff89dca5b39db03defbbfe4fd9e2ca5d6b9fe9b0 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 14:50:35 -0500 Subject: [PATCH 0865/1377] Add PKI indicator to printPacket --- src/mesh/RadioInterface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 262d2d6a9..968b9d251 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -283,6 +283,9 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p) if (s.want_response) out += DEBUG_PORT.mt_sprintf(" WANTRESP"); + if (p->pki_encrypted) + out += DEBUG_PORT.mt_sprintf(" PKI"); + if (s.source != 0) out += DEBUG_PORT.mt_sprintf(" source=%08x", s.source); From b528290fde246205d7888c9d276d66f0d8a4403d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 17:16:40 -0500 Subject: [PATCH 0866/1377] Failure returns PKI_FAILED message if client requested PKI --- src/mesh/Router.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 832d7b91f..015c2c809 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -415,6 +415,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) { concurrency::LockGuard g(cryptLock); + int16_t hash; + // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); @@ -460,19 +462,20 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // printBytes("plaintext", bytes, numbytes); ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it - auto hash = channels.setActiveByIndex(chIndex); - // Now that we are encrypting the packet channel should be the hash (no longer the index) - p->channel = hash; #if !(MESHTASTIC_EXCLUDE_PKI) meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); - if (!owner.is_licensed && p->to != NODENUM_BROADCAST && node != nullptr && node->user.public_key.size > 0 && - numbytes <= MAX_RHPACKETLEN - 8 && p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && + if (!owner.is_licensed && config.security.private_key.size == 32 && p->to != NODENUM_BROADCAST && node != nullptr && + node->user.public_key.size > 0 && p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); if (numbytes + 8 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; + if (memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { + LOG_WARN("Client public key for client differs from requested!\n"); + return meshtastic_Routing_Error_PKI_FAILED; + } crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); numbytes += 8; memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); @@ -483,6 +486,10 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Client specifically requested PKI encryption return meshtastic_Routing_Error_PKI_FAILED; } + hash = channels.setActiveByIndex(chIndex); + + // Now that we are encrypting the packet channel should be the hash (no longer the index) + p->channel = hash; if (hash < 0) { // No suitable channel could be found for sending return meshtastic_Routing_Error_NO_CHANNEL; @@ -495,6 +502,10 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Client specifically requested PKI encryption return meshtastic_Routing_Error_PKI_FAILED; } + hash = channels.setActiveByIndex(chIndex); + + // Now that we are encrypting the packet channel should be the hash (no longer the index) + p->channel = hash; if (hash < 0) { // No suitable channel could be found for sending return meshtastic_Routing_Error_NO_CHANNEL; From 2661fc694f21c0692bc7d18f0b3e3d0ed5f4765d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 20:06:36 -0500 Subject: [PATCH 0867/1377] sync protobufs --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 6307b44cd..97fa34517 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 6307b44cd98ef1d86d5f39edefe9361e79d9ece4 +Subproject commit 97fa34517f80332a11046a73f26d55100fbee9e2 From 8ce1c07c4ea36bc53ede1c77219fe5d5fc4e3e61 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 13 Aug 2024 22:34:21 -0500 Subject: [PATCH 0868/1377] Check for blank key coming from client --- src/mesh/Router.cpp | 6 ++++-- src/meshUtils.cpp | 9 +++++++++ src/meshUtils.h | 5 ++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 015c2c809..a96773042 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -472,8 +472,10 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) LOG_DEBUG("Using PKI!\n"); if (numbytes + 8 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; - if (memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { - LOG_WARN("Client public key for client differs from requested!\n"); + if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && + memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { + LOG_WARN("Client public key for client differs from requested! Requested 0x%02x, but stored key begins 0x%02x\n", + *p->public_key.bytes, *node->user.public_key.bytes); return meshtastic_Routing_Error_PKI_FAILED; } crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 86d237129..99fcd2a57 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -64,4 +64,13 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes) for (size_t i = 0; i < numbytes; i++) LOG_DEBUG("%02x ", p[i]); LOG_DEBUG("\n"); +} + +bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes) +{ + for (int i = 0; i < numbytes; i++) { + if (mem[i] != find) + return false; + } + return true; } \ No newline at end of file diff --git a/src/meshUtils.h b/src/meshUtils.h index e2d4188d7..ce063cb6a 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -14,4 +14,7 @@ template constexpr const T &clamp(const T &v, const T &lo, const T &hi char *strnstr(const char *s, const char *find, size_t slen); #endif -void printBytes(const char *label, const uint8_t *p, size_t numbytes); \ No newline at end of file +void printBytes(const char *label, const uint8_t *p, size_t numbytes); + +// is the memory region filled with a single character? +bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes); \ No newline at end of file From 207b9b49a575b02db6cf32cf7b808f753755b51f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 14 Aug 2024 07:42:30 -0500 Subject: [PATCH 0869/1377] Always attempt to set NTP or GPS time on a fresh position packet (#4460) --- src/modules/PositionModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 1618ba3ed..1756e8508 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -186,7 +186,7 @@ meshtastic_MeshPacket *PositionModule::allocReply() p.longitude_i = localPosition.longitude_i; } p.precision_bits = precision; - p.time = localPosition.time; + p.time = getValidTime(RTCQualityNTP) > 0 ? getValidTime(RTCQualityNTP) : localPosition.time; if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE) { if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE_MSL) From 181325103a614f78cbd0806e136f81df35dd873d Mon Sep 17 00:00:00 2001 From: Christopher Hoover Date: Wed, 14 Aug 2024 05:51:32 -0700 Subject: [PATCH 0870/1377] Improves ignore messages in Router.cpp (#4442) Signed-off-by: Christopher Hoover . --- src/mesh/Router.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 79095805d..db968ce51 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -517,18 +517,26 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) } #endif // assert(radioConfig.has_preferences); - bool ignore = is_in_repeated(config.lora.ignore_incoming, p->from) || (config.lora.ignore_mqtt && p->via_mqtt); + if (is_in_repeated(config.lora.ignore_incoming, p->from)) { + LOG_DEBUG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from); + packetPool.release(p); + return; + } - if (ignore) { - LOG_DEBUG("Ignoring incoming message, 0x%x is in our ignore list or came via MQTT\n", p->from); - } else if (ignore |= shouldFilterReceived(p)) { - LOG_DEBUG("Incoming message was filtered 0x%x\n", p->from); + if (config.lora.ignore_mqtt && p->via_mqtt) { + LOG_DEBUG("Message came in via MQTT from 0x%x\n", p->from); + packetPool.release(p); + return; + } + + if (shouldFilterReceived(p)) { + LOG_DEBUG("Incoming message was filtered from 0x%x\n", p->from); + packetPool.release(p); + return; } // Note: we avoid calling shouldFilterReceived if we are supposed to ignore certain nodes - because some overrides might // cache/learn of the existence of nodes (i.e. FloodRouter) that they should not - if (!ignore) - handleReceived(p); - + handleReceived(p); packetPool.release(p); } From 837c4e9e7ba89efaeefca6bc6fb8cd1446f66f3e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:33:57 -0500 Subject: [PATCH 0871/1377] [create-pull-request] automated change (#4461) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index c9ca0dbe9..666b481ae 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c9ca0dbe9cc7105399e0486c07e0601f849b94af +Subproject commit 666b481ae02f1f88ec30f10a2d80184b31c0fc4e From efc27f205106f7caf7b1541b64770e98e8b40872 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 14 Aug 2024 16:24:28 -0500 Subject: [PATCH 0872/1377] Initial telemetry with time and variant tags (#4463) --- src/modules/Telemetry/DeviceTelemetry.cpp | 3 +-- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 ++ src/modules/Telemetry/PowerTelemetry.cpp | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 4bde73f41..4b4869341 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -80,9 +80,8 @@ meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() { meshtastic_Telemetry t = meshtastic_Telemetry_init_zero; - - t.time = getTime(); t.which_variant = meshtastic_Telemetry_device_metrics_tag; + t.time = getTime(); t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent(); #if ARCH_PORTDUINO t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index a100d1ef5..db56fb1a5 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -418,6 +418,8 @@ meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + m.which_variant = meshtastic_Telemetry_environment_metrics_tag; + m.time = getTime(); #ifdef T1000X_SENSOR_EN if (t1000xSensor.getMetrics(&m)) { #else diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 90371780f..318acf456 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -222,6 +222,8 @@ meshtastic_MeshPacket *PowerTelemetryModule::allocReply() bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + m.which_variant = meshtastic_Telemetry_power_metrics_tag; + m.time = getTime(); if (getPowerTelemetry(&m)) { LOG_INFO("(Sending): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " "ch3_voltage=%f, ch3_current=%f\n", From 8ef72a5c080086d6ced5bd276ea1a098b16c62e3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 14 Aug 2024 17:17:53 -0500 Subject: [PATCH 0873/1377] Shorter nodeinfo timeout redux (#4458) * Add shorterTimeout bool to sendOurNodeInfo * Respond to likely PKI decode errors with a quick nodeinfo * Protbufs * Move to PKI_UNKNOWN_PUBKEY for PKI decode error --- protobufs | 2 +- src/mesh/ReliableRouter.cpp | 14 +++++++++++++- src/mesh/generated/meshtastic/mesh.pb.h | 8 +++++--- src/modules/NodeInfoModule.cpp | 16 ++++++++++++---- src/modules/NodeInfoModule.h | 4 +++- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/protobufs b/protobufs index 97fa34517..8b5b2faf6 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 97fa34517f80332a11046a73f26d55100fbee9e2 +Subproject commit 8b5b2faf662b364754809f923271022f4f1492ed diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index c91ce50c5..0c9180eb5 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -4,6 +4,7 @@ #include "MeshTypes.h" #include "configuration.h" #include "mesh-pb-constants.h" +#include "modules/NodeInfoModule.h" // ReliableRouter::ReliableRouter() {} @@ -109,13 +110,24 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n"); } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); + } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 && + (nodeDB->getMeshNode(p->from) != nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) { + // This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY + sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(), + p->hop_start, p->hop_limit); } else { // Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start, p->hop_limit); } } - + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c && + c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) { + if (owner.public_key.size == 32) { + LOG_INFO("This seems like a remote PKI decrypt failure, so send a NodeInfo"); + nodeInfoModule->sendOurNodeInfo(p->from, false, p->channel, true); + } + } // We consider an ack to be either a !routing packet with a request ID or a routing packet with !error PacketId ackId = ((c && c->error_reason == meshtastic_Routing_Error_NONE) || !c) ? p->decoded.request_id : 0; diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 7625fbf92..1f0621f9a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -302,7 +302,9 @@ typedef enum _meshtastic_Routing_Error { (i.e you did not send the request on the required bound channel) */ meshtastic_Routing_Error_NOT_AUTHORIZED = 33, /* The client specified a PKI transport, but the node was unable to send the packet using PKI (and did not send the message at all) */ - meshtastic_Routing_Error_PKI_FAILED = 34 + meshtastic_Routing_Error_PKI_FAILED = 34, + /* The receiving node does not have a Public Key to decode with */ + meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY = 35 } meshtastic_Routing_Error; /* The priority of this message for sending. @@ -995,8 +997,8 @@ extern "C" { #define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1)) #define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE -#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_PKI_FAILED -#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_PKI_FAILED+1)) +#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY +#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY+1)) #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 62cf9d2a1..cb047a4dc 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -32,19 +32,22 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes return false; // Let others look at this message also if they want } -void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel) +void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout) { // cancel any not yet sent (now stale) position packets if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal) service->cancelSending(prevPacketId); - + shorterTimeout = _shorterTimeout; meshtastic_MeshPacket *p = allocReply(); if (p) { // Check whether we didn't ignore it p->to = dest; p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && wantReplies; - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + if (_shorterTimeout) + p->priority = meshtastic_MeshPacket_Priority_DEFAULT; + else + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; if (channel > 0) { LOG_DEBUG("sending ourNodeInfo to channel %d\n", channel); p->channel = channel; @@ -53,6 +56,7 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha prevPacketId = p->id; service->sendToMesh(p); + shorterTimeout = false; } } @@ -65,10 +69,14 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() } uint32_t now = millis(); // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. - if (lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) { + if (!shorterTimeout && lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) { LOG_DEBUG("Skip sending NodeInfo since we just sent it less than 5 minutes ago.\n"); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; + } else if (shorterTimeout && lastSentToMesh && (now - lastSentToMesh) < (60 * 1000)) { + LOG_DEBUG("Skip sending actively requested NodeInfo since we just sent it less than 60 seconds ago.\n"); + ignoreRequest = true; // Mark it as ignored for MeshModule + return NULL; } else { ignoreRequest = false; // Don't ignore requests anymore meshtastic_User &u = owner; diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h index b10cccdf1..c1fb9ccce 100644 --- a/src/modules/NodeInfoModule.h +++ b/src/modules/NodeInfoModule.h @@ -20,7 +20,8 @@ class NodeInfoModule : public ProtobufModule, private concurren /** * Send our NodeInfo into the mesh */ - void sendOurNodeInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false, uint8_t channel = 0); + void sendOurNodeInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false, uint8_t channel = 0, + bool _shorterTimeout = false); protected: /** Called to handle a particular incoming message @@ -38,6 +39,7 @@ class NodeInfoModule : public ProtobufModule, private concurren private: uint32_t lastSentToMesh = 0; // Last time we sent our NodeInfo to the mesh + bool shorterTimeout = false; }; extern NodeInfoModule *nodeInfoModule; From ced87596cb6f94cb24e42cb73490667daeb266b7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 14 Aug 2024 19:32:45 -0500 Subject: [PATCH 0874/1377] Add PKI channel for MQTT (#4464) * Add PKI channel for MQTT --- src/mqtt/MQTT.cpp | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4bb9cd5eb..1d2d9bca0 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -152,7 +152,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) LOG_INFO("Ignoring downlink message we originally sent.\n"); } else { // Find channel by channel_id and check downlink_enabled - if (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled) { + if ((strcmp(e.channel_id, "PKI") && e.packet) || + (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) { LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); p->via_mqtt = true; // Mark that the packet was received via MQTT @@ -161,8 +162,11 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) p->channel = ch.index; } + // PKI messages get accepted even if we can't decrypt + if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0) + router->enqueueReceivedMessage(p); // ignore messages if we don't have the channel key - if (router && perhapsDecode(p)) + else if (router && perhapsDecode(p)) router->enqueueReceivedMessage(p); else packetPool.release(p); @@ -377,6 +381,11 @@ void MQTT::sendSubscriptions() #endif // ARCH_NRF52 } } +#if !MESHTASTIC_EXCLUDE_PKI + std::string topic = cryptTopic + "PKI/#"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); +#endif #endif } @@ -452,8 +461,12 @@ void MQTT::publishQueuedMessages() meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); static uint8_t bytes[meshtastic_MeshPacket_size + 64]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - - std::string topic = cryptTopic + env->channel_id + "/" + owner.id; + std::string topic; + if (env->packet->pki_encrypted) { + topic = cryptTopic + "PKI/" + owner.id; + } else { + topic = cryptTopic + env->channel_id + "/" + owner.id; + } LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); publish(topic.c_str(), bytes, numBytes, false); @@ -463,7 +476,12 @@ void MQTT::publishQueuedMessages() // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); if (jsonString.length() != 0) { - std::string topicJson = jsonTopic + env->channel_id + "/" + owner.id; + std::string topicJson; + if (env->packet->pki_encrypted) { + topicJson = jsonTopic + "PKI/" + owner.id; + } else { + topicJson = jsonTopic + env->channel_id + "/" + owner.id; + } LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); @@ -513,8 +531,12 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets static uint8_t bytes[meshtastic_MeshPacket_size + 64]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - - std::string topic = cryptTopic + channelId + "/" + owner.id; + std::string topic; + if (mp.pki_encrypted) { + topic = cryptTopic + "PKI/" + owner.id; + } else { + topic = cryptTopic + channelId + "/" + owner.id; + } LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); publish(topic.c_str(), bytes, numBytes, false); @@ -524,7 +546,12 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); if (jsonString.length() != 0) { - std::string topicJson = jsonTopic + channelId + "/" + owner.id; + std::string topicJson; + if (mp.pki_encrypted) { + topicJson = jsonTopic + "PKI/" + owner.id; + } else { + topicJson = jsonTopic + channelId + "/" + owner.id; + } LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); From 96cf78aadd5e84f661a7942158dde0fe482c7729 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 14 Aug 2024 21:16:21 -0500 Subject: [PATCH 0875/1377] Short turbo preset (#4465) --- src/DisplayFormatters.cpp | 3 +++ src/mesh/RadioInterface.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp index f15052de6..0718ffcbd 100644 --- a/src/DisplayFormatters.cpp +++ b/src/DisplayFormatters.cpp @@ -3,6 +3,9 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName) { switch (preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + return useShortName ? "ShortT" : "ShortTurbo"; + break; case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: return useShortName ? "ShortS" : "ShortSlow"; break; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 968b9d251..7fe02f291 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -415,6 +415,11 @@ void RadioInterface::applyModemConfig() if (loraConfig.use_preset) { switch (loraConfig.modem_preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bw = (myRegion->wideLora) ? 812.5 : 500; + cr = 5; + sf = 7; + break; case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: bw = (myRegion->wideLora) ? 812.5 : 250; cr = 5; From d398419aef5303c7121d551589d8dcaa470d2b10 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Aug 2024 08:47:49 -0500 Subject: [PATCH 0876/1377] Router and sensor are impolite (#4468) --- src/modules/Telemetry/DeviceTelemetry.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 4b4869341..1104e6c4a 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -16,12 +16,14 @@ int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); + bool isImpoliteRole = config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER; if (((lastSentToMesh == 0) || ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && - airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && - airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() && + config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { sendTelemetry(); lastSentToMesh = uptimeLastMs; From 6f1dae1b1b0eeda18217c13fd7ea06dd7b24e642 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Aug 2024 15:05:38 -0500 Subject: [PATCH 0877/1377] Re-compute correct timeslot on applyModemConfig (#4469) * Re-compute correct timeslot on applyModemConfig * Cap contention window max at 7 --- src/mesh/RadioInterface.cpp | 1 + src/mesh/RadioInterface.h | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 7fe02f291..104ddbd1d 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -520,6 +520,7 @@ void RadioInterface::applyModemConfig() saveChannelNum(channel_num); saveFreq(freq + loraConfig.frequency_offset); + slotTimeMsec = computeSlotTimeMsec(bw, sf); preambleTimeMsec = getPacketTime((uint32_t)0); maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index b965328e4..1f2ec9bab 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -71,18 +71,20 @@ class RadioInterface - roundtrip air propagation time (assuming max. 30km between nodes); - Tx/Rx turnaround time (maximum of SX126x and SX127x); - MAC processing time (measured on T-beam) */ - uint32_t slotTimeMsec = 8.5 * pow(2, sf) / bw + 0.2 + 0.4 + 7; + uint32_t slotTimeMsec = computeSlotTimeMsec(bw, sf); uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast const uint32_t PROCESSING_TIME_MSEC = 4500; // time to construct, process and construct a packet again (empirically determined) const uint8_t CWmin = 2; // minimum CWsize - const uint8_t CWmax = 8; // maximum CWsize + const uint8_t CWmax = 7; // maximum CWsize meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending uint32_t lastTxStart = 0L; + uint32_t computeSlotTimeMsec(float bw, float sf) { return 8.5 * pow(2, sf) / bw + 0.2 + 0.4 + 7; } + /** * A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need * */ From 85176756ec9c148a28e76d94e58477d4b0bac340 Mon Sep 17 00:00:00 2001 From: Christopher Hoover Date: Thu, 15 Aug 2024 17:09:06 -0700 Subject: [PATCH 0878/1377] Adds ASCII log option needed by portudino (#4443) * Adds ASCII logs useful to portudino Activates ASCII log option when stdout is not a terminal. This is generally the right thing to do; if not, the behavior can be overridden in config.yaml using AsciiLogs under Logging. The result is reasonable system logs for portudino when running under systemd or the like. Signed-off-by: Christopher Hoover --- bin/config-dist.yaml | 3 +- src/DebugConfiguration.cpp | 16 +++++- src/RedirectablePrint.cpp | 70 +++++++++++++++++------- src/platform/portduino/PortduinoGlue.cpp | 5 ++ src/platform/portduino/PortduinoGlue.h | 5 +- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 1cd219c4c..5590ed3b0 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -145,6 +145,7 @@ Input: Logging: LogLevel: info # debug, info, warn, error # TraceFile: /var/log/meshtasticd.json +# AsciiLogs: true # default if not specified is !isatty() on stdout Webserver: # Port: 443 # Port for Webserver & Webservices @@ -152,4 +153,4 @@ Webserver: General: MaxNodes: 200 - MaxMessageQueue: 100 \ No newline at end of file + MaxMessageQueue: 100 diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index d58856c4d..23b140daf 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,6 +26,10 @@ SOFTWARE.*/ #include "DebugConfiguration.h" +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + /// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic extern "C" void logLegacy(const char *level, const char *fmt, ...) { @@ -139,6 +143,11 @@ bool Syslog::vlogf(uint16_t pri, const char *appName, const char *fmt, va_list a inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message) { int result; +#ifdef ARCH_PORTDUINO + bool utf = !settingsMap[ascii_logs]; +#else + bool utf = true; +#endif if (!this->_enabled) return false; @@ -169,7 +178,12 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess this->_client->print(this->_deviceHostname); this->_client->print(' '); this->_client->print(appName); - this->_client->print(F(" - - - \xEF\xBB\xBF")); + this->_client->print(F(" - - - ")); + if (utf) { + this->_client->print(F("\xEF\xBB\xBF")); + } else { + this->_client->print(F(" ")); + } this->_client->print(F("[")); this->_client->print(int(millis() / 1000)); this->_client->print(F("]: ")); diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 05d349de9..c2b0c55db 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -55,6 +55,12 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l static char printBuf[160]; #endif +#ifdef ARCH_PORTDUINO + bool color = !settingsMap[ascii_logs]; +#else + bool color = true; +#endif + va_copy(copy, arg); size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); va_end(copy); @@ -70,7 +76,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l if (!std::isprint(static_cast(printBuf[f])) && printBuf[f] != '\n') printBuf[f] = '#'; } - if (logLevel != nullptr) { + if (color && logLevel != nullptr) { if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) Print::write("\u001b[34m", 6); if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) @@ -81,7 +87,9 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l Print::write("\u001b[31m", 6); } len = Print::write(printBuf, len); - Print::write("\u001b[0m", 5); + if (color && logLevel != nullptr) { + Print::write("\u001b[0m", 5); + } return len; } @@ -91,19 +99,27 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, // Cope with 0 len format strings, but look for new line terminator bool hasNewline = *format && format[strlen(format) - 1] == '\n'; +#ifdef ARCH_PORTDUINO + bool color = !settingsMap[ascii_logs]; +#else + bool color = true; +#endif // If we are the first message on a report, include the header if (!isContinuationMessage) { - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - Print::write("\u001b[34m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - Print::write("\u001b[32m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - Print::write("\u001b[33m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) - Print::write("\u001b[31m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) - Print::write("\u001b[35m", 6); + if (color) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) + Print::write("\u001b[35m", 6); + } + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; @@ -117,17 +133,33 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN #ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); + ::printf("%s ", logLevel); + if (color) { + ::printf("\u001b[0m"); + } + ::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000); #else - printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); + printf("%s ", logLevel); + if (color) { + printf("\u001b[0m"); + } + printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000); #endif - } else + } else { #ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); + ::printf("%s ", logLevel); + if (color) { + ::printf("\u001b[0m"); + } + ::printf("| ??:??:?? %u ", millis() / 1000); #else - printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); + printf("%s ", logLevel); + if (color) { + printf("\u001b[0m"); + } + printf("| ??:??:?? %u ", millis() / 1000); #endif - + } auto thread = concurrency::OSThread::currentThread; if (thread) { print("["); @@ -350,4 +382,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...) break; } return std::string(formatted.get()); -} \ No newline at end of file +} diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index f8dd79b20..3532d2e79 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -99,6 +99,7 @@ void portduinoSetup() settingsStrings[spidev] = ""; settingsStrings[displayspidev] = ""; settingsMap[spiSpeed] = 2000000; + settingsMap[ascii_logs] = !isatty(1); YAML::Node yamlConfig; @@ -152,6 +153,10 @@ void portduinoSetup() settingsMap[logoutputlevel] = level_error; } settingsStrings[traceFilename] = yamlConfig["Logging"]["TraceFile"].as(""); + if (yamlConfig["Logging"]["AsciiLogs"]) { + // Default is !isatty(1) but can be set explicitly in config.yaml + settingsMap[ascii_logs] = yamlConfig["Logging"]["AsciiLogs"].as(); + } } if (yamlConfig["Lora"]) { settingsMap[use_sx1262] = false; diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 0c81b8686..3fee1db40 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -53,7 +53,8 @@ enum configNames { webserverport, webserverrootpath, maxtophone, - maxnodes + maxnodes, + ascii_logs }; enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; @@ -62,4 +63,4 @@ enum { level_error, level_warn, level_info, level_debug, level_trace }; extern std::map settingsMap; extern std::map settingsStrings; extern std::ofstream traceFile; -int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file +int initGPIOPin(int pinNum, std::string gpioChipname); From 390de724ba1834e34a05d5918f2cc13f03f9c42d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 16 Aug 2024 06:09:02 -0500 Subject: [PATCH 0879/1377] Update 2.5 protos --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 15 ++++-- .../generated/meshtastic/telemetry.pb.cpp | 3 ++ src/mesh/generated/meshtastic/telemetry.pb.h | 53 ++++++++++++++++++- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/protobufs b/protobufs index 8b5b2faf6..4eb4f4251 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8b5b2faf662b364754809f923271022f4f1492ed +Subproject commit 4eb4f425170f08839abc6ececd13e8db30094ad5 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 13093839d..b4971991a 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -87,6 +87,7 @@ typedef struct _meshtastic_NodeRemoteHardwarePinsResponse { meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[16]; } meshtastic_NodeRemoteHardwarePinsResponse; +typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; /* This message is handled by the Admin module and is responsible for all settings/channel read/write operations. This message is used to do settings operations to both remote AND local nodes. (Prior to 1.2 these operations were done via special ToRadio operations) */ @@ -187,6 +188,10 @@ typedef struct _meshtastic_AdminMessage { /* Tell the node to reset the nodedb. */ int32_t nodedb_reset; }; + /* The node generates this key and sends it with any get_x_response packets. + The client MUST include the same key with any set_x commands. Key expires after 300 seconds. + Prevents replay attacks for admin messages. */ + meshtastic_AdminMessage_session_passkey_t session_passkey; } meshtastic_AdminMessage; @@ -210,10 +215,10 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_AdminMessage_init_default {0, {0}} +#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_HamParameters_init_default {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} -#define meshtastic_AdminMessage_init_zero {0, {0}} +#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_HamParameters_init_zero {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} @@ -265,6 +270,7 @@ extern "C" { #define meshtastic_AdminMessage_shutdown_seconds_tag 98 #define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 +#define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ #define meshtastic_AdminMessage_FIELDLIST(X, a) \ @@ -309,7 +315,8 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulato X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) +X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) \ +X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel @@ -351,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size -#define meshtastic_AdminMessage_size 500 +#define meshtastic_AdminMessage_size 511 #define meshtastic_HamParameters_size 31 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index c93483a15..90859c98e 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -18,6 +18,9 @@ PB_BIND(meshtastic_PowerMetrics, meshtastic_PowerMetrics, AUTO) PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO) +PB_BIND(meshtastic_LocalStats, meshtastic_LocalStats, AUTO) + + PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, AUTO) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 60681916f..2d3eb407a 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -211,6 +211,26 @@ typedef struct _meshtastic_AirQualityMetrics { uint32_t particles_100um; } meshtastic_AirQualityMetrics; +/* Local device mesh statistics */ +typedef struct _meshtastic_LocalStats { + /* How long the device has been running since the last reboot (in seconds) */ + uint32_t uptime_seconds; + /* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). */ + float channel_utilization; + /* Percent of airtime for transmission used within the last hour. */ + float air_util_tx; + /* Number of packets sent */ + uint32_t num_packets_tx; + /* Number of packets received good */ + uint32_t num_packets_rx; + /* Number of packets received that are malformed or violate the protocol */ + uint32_t num_packets_rx_bad; + /* Number of nodes online (in the past 2 hours) */ + uint16_t num_online_nodes; + /* Number of nodes total */ + uint16_t num_total_nodes; +} meshtastic_LocalStats; + /* Types of Measurements the telemetry module is equipped to handle */ typedef struct _meshtastic_Telemetry { /* Seconds since 1970 - or 0 for unknown/unset */ @@ -225,6 +245,8 @@ typedef struct _meshtastic_Telemetry { meshtastic_AirQualityMetrics air_quality_metrics; /* Power Metrics */ meshtastic_PowerMetrics power_metrics; + /* Local device mesh statistics */ + meshtastic_LocalStats local_stats; } variant; } meshtastic_Telemetry; @@ -253,17 +275,20 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} @@ -308,11 +333,20 @@ extern "C" { #define meshtastic_AirQualityMetrics_particles_25um_tag 10 #define meshtastic_AirQualityMetrics_particles_50um_tag 11 #define meshtastic_AirQualityMetrics_particles_100um_tag 12 +#define meshtastic_LocalStats_uptime_seconds_tag 1 +#define meshtastic_LocalStats_channel_utilization_tag 2 +#define meshtastic_LocalStats_air_util_tx_tag 3 +#define meshtastic_LocalStats_num_packets_tx_tag 4 +#define meshtastic_LocalStats_num_packets_rx_tag 5 +#define meshtastic_LocalStats_num_packets_rx_bad_tag 6 +#define meshtastic_LocalStats_num_online_nodes_tag 7 +#define meshtastic_LocalStats_num_total_nodes_tag 8 #define meshtastic_Telemetry_time_tag 1 #define meshtastic_Telemetry_device_metrics_tag 2 #define meshtastic_Telemetry_environment_metrics_tag 3 #define meshtastic_Telemetry_air_quality_metrics_tag 4 #define meshtastic_Telemetry_power_metrics_tag 5 +#define meshtastic_Telemetry_local_stats_tag 6 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 @@ -373,18 +407,32 @@ X(a, STATIC, OPTIONAL, UINT32, particles_100um, 12) #define meshtastic_AirQualityMetrics_CALLBACK NULL #define meshtastic_AirQualityMetrics_DEFAULT NULL +#define meshtastic_LocalStats_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, uptime_seconds, 1) \ +X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 2) \ +X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 3) \ +X(a, STATIC, SINGULAR, UINT32, num_packets_tx, 4) \ +X(a, STATIC, SINGULAR, UINT32, num_packets_rx, 5) \ +X(a, STATIC, SINGULAR, UINT32, num_packets_rx_bad, 6) \ +X(a, STATIC, SINGULAR, UINT32, num_online_nodes, 7) \ +X(a, STATIC, SINGULAR, UINT32, num_total_nodes, 8) +#define meshtastic_LocalStats_CALLBACK NULL +#define meshtastic_LocalStats_DEFAULT NULL + #define meshtastic_Telemetry_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, FIXED32, time, 1) \ X(a, STATIC, ONEOF, MESSAGE, (variant,device_metrics,variant.device_metrics), 2) \ X(a, STATIC, ONEOF, MESSAGE, (variant,environment_metrics,variant.environment_metrics), 3) \ X(a, STATIC, ONEOF, MESSAGE, (variant,air_quality_metrics,variant.air_quality_metrics), 4) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics), 5) +X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics), 5) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), 6) #define meshtastic_Telemetry_CALLBACK NULL #define meshtastic_Telemetry_DEFAULT NULL #define meshtastic_Telemetry_variant_device_metrics_MSGTYPE meshtastic_DeviceMetrics #define meshtastic_Telemetry_variant_environment_metrics_MSGTYPE meshtastic_EnvironmentMetrics #define meshtastic_Telemetry_variant_air_quality_metrics_MSGTYPE meshtastic_AirQualityMetrics #define meshtastic_Telemetry_variant_power_metrics_MSGTYPE meshtastic_PowerMetrics +#define meshtastic_Telemetry_variant_local_stats_MSGTYPE meshtastic_LocalStats #define meshtastic_Nau7802Config_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \ @@ -396,6 +444,7 @@ extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg; extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg; +extern const pb_msgdesc_t meshtastic_LocalStats_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; @@ -404,6 +453,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_EnvironmentMetrics_fields &meshtastic_EnvironmentMetrics_msg #define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg #define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg +#define meshtastic_LocalStats_fields &meshtastic_LocalStats_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg #define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg @@ -412,6 +462,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 #define meshtastic_EnvironmentMetrics_size 85 +#define meshtastic_LocalStats_size 42 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 92 From eefe9efa9f11675792bd38a55db011b1aa3792d3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 16 Aug 2024 07:42:19 -0500 Subject: [PATCH 0880/1377] Adds ASCII log option needed by portudino (#4443) (#4474) * Adds ASCII logs useful to portudino Activates ASCII log option when stdout is not a terminal. This is generally the right thing to do; if not, the behavior can be overridden in config.yaml using AsciiLogs under Logging. The result is reasonable system logs for portudino when running under systemd or the like. Signed-off-by: Christopher Hoover Co-authored-by: Christopher Hoover --- bin/config-dist.yaml | 3 +- src/DebugConfiguration.cpp | 16 +++++- src/RedirectablePrint.cpp | 70 +++++++++++++++++------- src/platform/portduino/PortduinoGlue.cpp | 5 ++ src/platform/portduino/PortduinoGlue.h | 5 +- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 1cd219c4c..5590ed3b0 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -145,6 +145,7 @@ Input: Logging: LogLevel: info # debug, info, warn, error # TraceFile: /var/log/meshtasticd.json +# AsciiLogs: true # default if not specified is !isatty() on stdout Webserver: # Port: 443 # Port for Webserver & Webservices @@ -152,4 +153,4 @@ Webserver: General: MaxNodes: 200 - MaxMessageQueue: 100 \ No newline at end of file + MaxMessageQueue: 100 diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index d58856c4d..23b140daf 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,6 +26,10 @@ SOFTWARE.*/ #include "DebugConfiguration.h" +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + /// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic extern "C" void logLegacy(const char *level, const char *fmt, ...) { @@ -139,6 +143,11 @@ bool Syslog::vlogf(uint16_t pri, const char *appName, const char *fmt, va_list a inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message) { int result; +#ifdef ARCH_PORTDUINO + bool utf = !settingsMap[ascii_logs]; +#else + bool utf = true; +#endif if (!this->_enabled) return false; @@ -169,7 +178,12 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess this->_client->print(this->_deviceHostname); this->_client->print(' '); this->_client->print(appName); - this->_client->print(F(" - - - \xEF\xBB\xBF")); + this->_client->print(F(" - - - ")); + if (utf) { + this->_client->print(F("\xEF\xBB\xBF")); + } else { + this->_client->print(F(" ")); + } this->_client->print(F("[")); this->_client->print(int(millis() / 1000)); this->_client->print(F("]: ")); diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 0eab0de0a..96cf851e4 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -56,6 +56,12 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l static char printBuf[160]; #endif +#ifdef ARCH_PORTDUINO + bool color = !settingsMap[ascii_logs]; +#else + bool color = true; +#endif + va_copy(copy, arg); size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); va_end(copy); @@ -71,7 +77,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l if (!std::isprint(static_cast(printBuf[f])) && printBuf[f] != '\n') printBuf[f] = '#'; } - if (logLevel != nullptr) { + if (color && logLevel != nullptr) { if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) Print::write("\u001b[34m", 6); if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) @@ -82,7 +88,9 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l Print::write("\u001b[31m", 6); } len = Print::write(printBuf, len); - Print::write("\u001b[0m", 5); + if (color && logLevel != nullptr) { + Print::write("\u001b[0m", 5); + } return len; } @@ -92,19 +100,27 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, // Cope with 0 len format strings, but look for new line terminator bool hasNewline = *format && format[strlen(format) - 1] == '\n'; +#ifdef ARCH_PORTDUINO + bool color = !settingsMap[ascii_logs]; +#else + bool color = true; +#endif // If we are the first message on a report, include the header if (!isContinuationMessage) { - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) - Print::write("\u001b[34m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) - Print::write("\u001b[32m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) - Print::write("\u001b[33m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) - Print::write("\u001b[31m", 6); - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) - Print::write("\u001b[35m", 6); + if (color) { + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) + Print::write("\u001b[34m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) + Print::write("\u001b[32m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) + Print::write("\u001b[33m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0) + Print::write("\u001b[31m", 6); + if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) + Print::write("\u001b[35m", 6); + } + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; @@ -118,17 +134,33 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN; int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN #ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); + ::printf("%s ", logLevel); + if (color) { + ::printf("\u001b[0m"); + } + ::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000); #else - printf("%s \u001b[0m| %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000); + printf("%s ", logLevel); + if (color) { + printf("\u001b[0m"); + } + printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000); #endif - } else + } else { #ifdef ARCH_PORTDUINO - ::printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); + ::printf("%s ", logLevel); + if (color) { + ::printf("\u001b[0m"); + } + ::printf("| ??:??:?? %u ", millis() / 1000); #else - printf("%s \u001b[0m| ??:??:?? %u ", logLevel, millis() / 1000); + printf("%s ", logLevel); + if (color) { + printf("\u001b[0m"); + } + printf("| ??:??:?? %u ", millis() / 1000); #endif - + } auto thread = concurrency::OSThread::currentThread; if (thread) { print("["); @@ -351,4 +383,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...) break; } return std::string(formatted.get()); -} \ No newline at end of file +} diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index f8dd79b20..3532d2e79 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -99,6 +99,7 @@ void portduinoSetup() settingsStrings[spidev] = ""; settingsStrings[displayspidev] = ""; settingsMap[spiSpeed] = 2000000; + settingsMap[ascii_logs] = !isatty(1); YAML::Node yamlConfig; @@ -152,6 +153,10 @@ void portduinoSetup() settingsMap[logoutputlevel] = level_error; } settingsStrings[traceFilename] = yamlConfig["Logging"]["TraceFile"].as(""); + if (yamlConfig["Logging"]["AsciiLogs"]) { + // Default is !isatty(1) but can be set explicitly in config.yaml + settingsMap[ascii_logs] = yamlConfig["Logging"]["AsciiLogs"].as(); + } } if (yamlConfig["Lora"]) { settingsMap[use_sx1262] = false; diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 0c81b8686..3fee1db40 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -53,7 +53,8 @@ enum configNames { webserverport, webserverrootpath, maxtophone, - maxnodes + maxnodes, + ascii_logs }; enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; @@ -62,4 +63,4 @@ enum { level_error, level_warn, level_info, level_debug, level_trace }; extern std::map settingsMap; extern std::map settingsStrings; extern std::ofstream traceFile; -int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file +int initGPIOPin(int pinNum, std::string gpioChipname); From b0c1b7b7b5fd295161164e9696673e482160941f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 16 Aug 2024 10:10:08 -0500 Subject: [PATCH 0881/1377] MQTT PKI fixes --- src/mqtt/MQTT.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 1d2d9bca0..d3a2bb92c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -152,7 +152,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) LOG_INFO("Ignoring downlink message we originally sent.\n"); } else { // Find channel by channel_id and check downlink_enabled - if ((strcmp(e.channel_id, "PKI") && e.packet) || + if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) { LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); @@ -163,7 +163,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) } // PKI messages get accepted even if we can't decrypt - if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0) + if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && + strcmp(e.channel_id, "PKI") == 0) router->enqueueReceivedMessage(p); // ignore messages if we don't have the channel key else if (router && perhapsDecode(p)) @@ -365,10 +366,12 @@ void MQTT::reconnect() void MQTT::sendSubscriptions() { #if HAS_NETWORKING + bool hasDownlink = false; size_t numChan = channels.getNumChannels(); for (size_t i = 0; i < numChan; i++) { const auto &ch = channels.getByIndex(i); if (ch.settings.downlink_enabled) { + hasDownlink = true; std::string topic = cryptTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? @@ -382,9 +385,11 @@ void MQTT::sendSubscriptions() } } #if !MESHTASTIC_EXCLUDE_PKI - std::string topic = cryptTopic + "PKI/#"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); + if (hasDownlink) { + std::string topic = cryptTopic + "PKI/#"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); + } #endif #endif } @@ -496,7 +501,13 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & { if (mp.via_mqtt) return; // Don't send messages that came from MQTT back into MQTT - + bool uplinkEnabled = false; + for (int i = 0; i <= 7; i++) { + if (channels.getByIndex(i).settings.uplink_enabled) + uplinkEnabled = true; + } + if (!uplinkEnabled) + return; // no channels have an uplink enabled auto &ch = channels.getByIndex(chIndex); if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { @@ -511,7 +522,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & return; } - if (ch.settings.uplink_enabled) { + if (ch.settings.uplink_enabled || mp.pki_encrypted) { const char *channelId = channels.getGlobalId(chIndex); // FIXME, for now we just use the human name for the channel meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); From e61bd841169b38ba3049b2642fd080bb5750f553 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 16 Aug 2024 17:15:51 -0500 Subject: [PATCH 0882/1377] Send local stats telemetry to phone every 15 minutes (#4475) * Send local stats telemetry to phone every 10 minutes * Add debug log and bump to 15 minutes * Tronk * Explicit has_ optional --- src/mesh/RadioLibInterface.h | 10 ++-- src/modules/Telemetry/DeviceTelemetry.cpp | 46 +++++++++++++++++++ src/modules/Telemetry/DeviceTelemetry.h | 5 +- .../Telemetry/EnvironmentTelemetry.cpp | 1 + src/modules/Telemetry/Sensor/AHT10.cpp | 3 ++ src/modules/Telemetry/Sensor/BME280Sensor.cpp | 4 ++ src/modules/Telemetry/Sensor/BME680Sensor.cpp | 7 +++ src/modules/Telemetry/Sensor/BMP085Sensor.cpp | 3 ++ src/modules/Telemetry/Sensor/BMP280Sensor.cpp | 3 ++ .../Telemetry/Sensor/DFRobotLarkSensor.cpp | 6 +++ src/modules/Telemetry/Sensor/INA219Sensor.cpp | 3 ++ src/modules/Telemetry/Sensor/INA260Sensor.cpp | 3 ++ .../Telemetry/Sensor/INA3221Sensor.cpp | 10 ++++ .../Telemetry/Sensor/LPS22HBSensor.cpp | 3 ++ .../Telemetry/Sensor/MCP9808Sensor.cpp | 2 + .../Telemetry/Sensor/MLX90632Sensor.cpp | 1 + .../Telemetry/Sensor/NAU7802Sensor.cpp | 1 + .../Telemetry/Sensor/OPT3001Sensor.cpp | 1 + .../Telemetry/Sensor/RCWL9620Sensor.cpp | 1 + src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 2 + src/modules/Telemetry/Sensor/SHT4XSensor.cpp | 3 ++ src/modules/Telemetry/Sensor/SHTC3Sensor.cpp | 3 ++ src/modules/Telemetry/Sensor/T1000xSensor.cpp | 3 ++ .../Telemetry/Sensor/TSL2591Sensor.cpp | 1 + .../Telemetry/Sensor/VEML7700Sensor.cpp | 3 ++ 25 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index dd01d2037..edcbb394f 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -61,11 +61,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ static void isrTxLevel0(), isrLevel0Common(PendingISR code); - /** - * Debugging counts - */ - uint32_t rxBad = 0, rxGood = 0, txGood = 0; - MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); protected: @@ -109,6 +104,11 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void enableInterrupt(void (*)()) = 0; + /** + * Debugging counts + */ + uint32_t rxBad = 0, rxGood = 0, txGood = 0; + public: RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL); diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 1104e6c4a..f22685d43 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -5,6 +5,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "RTC.h" +#include "RadioLibInterface.h" #include "Router.h" #include "configuration.h" #include "main.h" @@ -31,6 +32,10 @@ int32_t DeviceTelemetryModule::runOnce() // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); + if (lastSentStatsToPhone == 0 || (uptimeLastMs - lastSentStatsToPhone) >= sendStatsToPhoneIntervalMs) { + sendLocalStatsToPhone(); + lastSentStatsToPhone = uptimeLastMs; + } } return sendToPhoneIntervalMs; } @@ -84,6 +89,13 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() meshtastic_Telemetry t = meshtastic_Telemetry_init_zero; t.which_variant = meshtastic_Telemetry_device_metrics_tag; t.time = getTime(); + t.variant.device_metrics = meshtastic_DeviceMetrics_init_zero; + t.variant.device_metrics.has_air_util_tx = true; + t.variant.device_metrics.has_battery_level = true; + t.variant.device_metrics.has_channel_utilization = true; + t.variant.device_metrics.has_voltage = true; + t.variant.device_metrics.has_uptime_seconds = true; + t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent(); #if ARCH_PORTDUINO t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL; @@ -98,6 +110,40 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() return t; } +void DeviceTelemetryModule::sendLocalStatsToPhone() +{ + meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero; + telemetry.which_variant = meshtastic_Telemetry_local_stats_tag; + telemetry.variant.local_stats = meshtastic_LocalStats_init_zero; + telemetry.time = getTime(); + telemetry.variant.local_stats.uptime_seconds = getUptimeSeconds(); + telemetry.variant.local_stats.channel_utilization = airTime->channelUtilizationPercent(); + telemetry.variant.local_stats.air_util_tx = airTime->utilizationTXPercent(); + telemetry.variant.local_stats.num_online_nodes = numOnlineNodes; + telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); + if (RadioLibInterface::instance) { + telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; + telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood; + telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; + } + + LOG_INFO( + "(Sending local stats): uptime=%i, channel_utilization=%f, air_util_tx=%f, num_online_nodes=%i, num_total_nodes=%i\n", + telemetry.variant.local_stats.uptime_seconds, telemetry.variant.local_stats.channel_utilization, + telemetry.variant.local_stats.air_util_tx, telemetry.variant.local_stats.num_online_nodes, + telemetry.variant.local_stats.num_total_nodes); + + LOG_INFO("num_packets_tx=%i, num_packets_rx=%i, num_packets_rx_bad=%i\n", telemetry.variant.local_stats.num_packets_tx, + telemetry.variant.local_stats.num_packets_rx, telemetry.variant.local_stats.num_packets_rx_bad); + + meshtastic_MeshPacket *p = allocDataProtobuf(telemetry); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + + service->sendToPhone(p); +} + bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry telemetry = getDeviceTelemetry(); diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index baaf59f28..6d7f69891 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -42,7 +42,10 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu private: meshtastic_Telemetry getDeviceTelemetry(); - uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute + void sendLocalStatsToPhone(); + uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute + uint32_t sendStatsToPhoneIntervalMs = 15 * SECONDS_IN_MINUTE * 1000; // Send stats to phone every 15 minutes + uint32_t lastSentStatsToPhone = 0; uint32_t lastSentToMesh = 0; void refreshUptime() diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index db56fb1a5..4755a5be5 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -289,6 +289,7 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m bool hasSensor = false; m->time = getTime(); m->which_variant = meshtastic_Telemetry_environment_metrics_tag; + m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero; #ifdef T1000X_SENSOR_EN // add by WayenWeng valid = valid && t1000xSensor.getMetrics(m); diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp index a5212b39b..f9e8ba18a 100644 --- a/src/modules/Telemetry/Sensor/AHT10.cpp +++ b/src/modules/Telemetry/Sensor/AHT10.cpp @@ -32,6 +32,9 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) sensors_event_t humidity, temp; aht10.getEvent(&humidity, &temp); + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.temperature = temp.temperature; measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity; diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp index aea6f2c3d..55bc16744 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp @@ -31,6 +31,10 @@ void BME280Sensor::setup() {} bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + LOG_DEBUG("BME280Sensor::getMetrics\n"); bme280.takeForcedMeasurement(); measurement->variant.environment_metrics.temperature = bme280.readTemperature(); diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 411cbbf69..328ec827d 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -54,6 +54,13 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) return false; + + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + measurement->variant.environment_metrics.has_gas_resistance = true; + measurement->variant.environment_metrics.has_iaq = true; + measurement->variant.environment_metrics.temperature = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE).signal; measurement->variant.environment_metrics.relative_humidity = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY).signal; diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp index 0c4d0b5ca..15951126f 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -26,6 +26,9 @@ void BMP085Sensor::setup() {} bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + LOG_DEBUG("BMP085Sensor::getMetrics\n"); measurement->variant.environment_metrics.temperature = bmp085.readTemperature(); measurement->variant.environment_metrics.barometric_pressure = bmp085.readPressure() / 100.0F; diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp index 8d0e4c180..6b0743d75 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp @@ -31,6 +31,9 @@ void BMP280Sensor::setup() {} bool BMP280Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + LOG_DEBUG("BMP280Sensor::getMetrics\n"); bmp280.takeForcedMeasurement(); measurement->variant.environment_metrics.temperature = bmp280.readTemperature(); diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp index 830552023..4b01eb444 100644 --- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp +++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp @@ -35,6 +35,12 @@ void DFRobotLarkSensor::setup() {} bool DFRobotLarkSensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.has_wind_speed = true; + measurement->variant.environment_metrics.has_wind_direction = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + measurement->variant.environment_metrics.temperature = lark.getValue("Temp").toFloat(); measurement->variant.environment_metrics.relative_humidity = lark.getValue("Humi").toFloat(); measurement->variant.environment_metrics.wind_speed = lark.getValue("Speed").toFloat(); diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp index 040e59575..f70d3705e 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp @@ -32,6 +32,9 @@ void INA219Sensor::setup() {} bool INA219Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_voltage = true; + measurement->variant.environment_metrics.has_current = true; + measurement->variant.environment_metrics.voltage = ina219.getBusVoltage_V(); measurement->variant.environment_metrics.current = ina219.getCurrent_mA() * INA219_MULTIPLIER; return true; diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.cpp b/src/modules/Telemetry/Sensor/INA260Sensor.cpp index f156a9aba..751608c82 100644 --- a/src/modules/Telemetry/Sensor/INA260Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA260Sensor.cpp @@ -26,6 +26,9 @@ void INA260Sensor::setup() {} bool INA260Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_voltage = true; + measurement->variant.environment_metrics.has_current = true; + // mV conversion to V measurement->variant.environment_metrics.voltage = ina260.readBusVoltage() / 1000; measurement->variant.environment_metrics.current = ina260.readCurrent(); diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp index dec99c551..549346d72 100644 --- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp @@ -67,6 +67,9 @@ bool INA3221Sensor::getEnvironmentMetrics(meshtastic_Telemetry *measurement) { struct _INA3221Measurement m = getMeasurement(ENV_CH); + measurement->variant.environment_metrics.has_voltage = true; + measurement->variant.environment_metrics.has_current = true; + measurement->variant.environment_metrics.voltage = m.voltage; measurement->variant.environment_metrics.current = m.current; @@ -77,6 +80,13 @@ bool INA3221Sensor::getPowerMetrics(meshtastic_Telemetry *measurement) { struct _INA3221Measurements m = getMeasurements(); + measurement->variant.power_metrics.has_ch1_voltage = true; + measurement->variant.power_metrics.has_ch1_current = true; + measurement->variant.power_metrics.has_ch2_voltage = true; + measurement->variant.power_metrics.has_ch2_current = true; + measurement->variant.power_metrics.has_ch3_voltage = true; + measurement->variant.power_metrics.has_ch3_current = true; + measurement->variant.power_metrics.ch1_voltage = m.measurements[INA3221_CH1].voltage; measurement->variant.power_metrics.ch1_current = m.measurements[INA3221_CH1].current; measurement->variant.power_metrics.ch2_voltage = m.measurements[INA3221_CH2].voltage; diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp index c3c994cfa..111d86d1a 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp @@ -27,6 +27,9 @@ void LPS22HBSensor::setup() bool LPS22HBSensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + sensors_event_t temp; sensors_event_t pressure; lps22hb.getEvent(&pressure, &temp); diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp index b01a19291..c1cda7227 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp @@ -26,6 +26,8 @@ void MCP9808Sensor::setup() bool MCP9808Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + LOG_DEBUG("MCP9808Sensor::getMetrics\n"); measurement->variant.environment_metrics.temperature = mcp9808.readTempC(); return true; diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp index 4c459c365..0568a4652 100644 --- a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp @@ -32,6 +32,7 @@ void MLX90632Sensor::setup() {} bool MLX90632Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; measurement->variant.environment_metrics.temperature = mlx.getObjectTemp(); // Get the object temperature in Fahrenheit return true; diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp index 3560c6580..d7dcbd09f 100644 --- a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp @@ -45,6 +45,7 @@ bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement) return false; } } + measurement->variant.environment_metrics.has_weight = true; // Check if we have correct calibration values after powerup LOG_DEBUG("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor()); measurement->variant.environment_metrics.weight = nau7802.getWeight() / 1000; // sample is in kg diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp index d0e38bf88..d6cbcbb91 100644 --- a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp @@ -38,6 +38,7 @@ void OPT3001Sensor::setup() bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_lux = true; OPT3001 result = opt3001.readResult(); measurement->variant.environment_metrics.lux = result.lux; diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index 49a509d38..b9a29ab7d 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -23,6 +23,7 @@ void RCWL9620Sensor::setup() {} bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_distance = true; LOG_DEBUG("RCWL9620Sensor::getMetrics\n"); measurement->variant.environment_metrics.distance = getDistance(); return true; diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index aa2b5dcfc..c372f7986 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -27,6 +27,8 @@ void SHT31Sensor::setup() bool SHT31Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; measurement->variant.environment_metrics.temperature = sht31.readTemperature(); measurement->variant.environment_metrics.relative_humidity = sht31.readHumidity(); diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp index 7f37327c6..94367cba4 100644 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp @@ -39,6 +39,9 @@ void SHT4XSensor::setup() bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + sensors_event_t humidity, temp; sht4x.getEvent(&humidity, &temp); measurement->variant.environment_metrics.temperature = temp.temperature; diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp index 37685fed7..64ebfb472 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp @@ -26,6 +26,9 @@ void SHTC3Sensor::setup() bool SHTC3Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + sensors_event_t humidity, temp; shtc3.getEvent(&humidity, &temp); diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp index e544d0dc5..4079d8ae3 100644 --- a/src/modules/Telemetry/Sensor/T1000xSensor.cpp +++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp @@ -108,6 +108,9 @@ float T1000xSensor::getTemp() bool T1000xSensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_lux = true; + measurement->variant.environment_metrics.temperature = getTemp(); measurement->variant.environment_metrics.lux = getLux(); return true; diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp index d20e48dce..9002874b3 100644 --- a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp @@ -29,6 +29,7 @@ void TSL2591Sensor::setup() bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_lux = true; uint32_t lum = tsl.getFullLuminosity(); uint16_t ir, full; ir = lum >> 16; diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp index cbeaf4c2e..c176ed21b 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -53,6 +53,9 @@ float VEML7700Sensor::getResolution(void) bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement) { + measurement->variant.environment_metrics.has_lux = true; + measurement->variant.environment_metrics.has_white_lux = true; + int16_t white; measurement->variant.environment_metrics.lux = veml7700.readLux(VEML_LUX_AUTO); white = veml7700.readWhite(true); From 0ebdc7ab0c241adaeef5893c9bb4751b52869685 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Sat, 17 Aug 2024 00:37:22 +0200 Subject: [PATCH 0883/1377] Update architecture.h add Minewsemi ME25LS01 LR1110 breakout ME25LE01_V1.0 and fix buzzer (#4472) * Update architecture.h * Update variant.h * Update variant.h * Update architecture.h * Update architecture.h * Delete src/platform/nrf52/architecture.h * Add files via upload * Update architecture.h * Update architecture.h * Update architecture.h --- src/platform/nrf52/architecture.h | 4 +++- variants/ME25LS01-4Y10TD/variant.h | 4 ++-- variants/ME25LS01-4Y10TD_e-ink/variant.h | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index d5685d611..ebb847dc8 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -62,6 +62,8 @@ #define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110 #elif defined(TRACKER_T1000_E) #define HW_VENDOR meshtastic_HardwareModel_TRACKER_T1000_E +#elif defined(ME25LS01) +#define HW_VENDOR meshtastic_HardwareModel_ME25LS01 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else @@ -116,4 +118,4 @@ #if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA) // No serial ports on this board - ONLY use segger in memory console #define USE_SEGGER -#endif \ No newline at end of file +#endif diff --git a/variants/ME25LS01-4Y10TD/variant.h b/variants/ME25LS01-4Y10TD/variant.h index 24ed65b33..715de8ee6 100644 --- a/variants/ME25LS01-4Y10TD/variant.h +++ b/variants/ME25LS01-4Y10TD/variant.h @@ -126,7 +126,7 @@ extern "C" { #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 // Buzzer -#define BUZZER_EN_PIN -1 +#define PIN_BUZZER (0 + 25) #ifdef __cplusplus } @@ -136,4 +136,4 @@ extern "C" { * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_ME25LS01_4Y10TD_ \ No newline at end of file +#endif // _VARIANT_ME25LS01_4Y10TD_ diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/ME25LS01-4Y10TD_e-ink/variant.h index 7c7740505..9006d5a63 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.h @@ -149,7 +149,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 // Buzzer -#define BUZZER_EN_PIN -1 +#define PIN_BUZZER (0 + 25) #ifdef __cplusplus } @@ -159,4 +159,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_ME25LS01_4Y10TD__ \ No newline at end of file +#endif // _VARIANT_ME25LS01_4Y10TD__ From cec8233cd1f7d8420f5a52596ad9ae904e1b2fdf Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 16 Aug 2024 19:33:06 -0500 Subject: [PATCH 0884/1377] Don't attempt PKI decryption on broadcast packets --- src/mesh/Router.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a797e81b3..f8655b1e3 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -320,9 +320,9 @@ bool perhapsDecode(meshtastic_MeshPacket *p) memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize); #if !(MESHTASTIC_EXCLUDE_PKI) // Attempt PKI decryption first - if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && nodeDB->getMeshNode(p->from) != nullptr && - nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && - rawSize > 8) { + if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && p->to != NODENUM_BROADCAST && + nodeDB->getMeshNode(p->from) != nullptr && nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && + nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > 8) { LOG_DEBUG("Attempting PKI decryption\n"); if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) { From 6eabbaf4321a3694fb48b4a55331c53d2ebb5d63 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 16 Aug 2024 19:37:28 -0500 Subject: [PATCH 0885/1377] Add PKI logiv to KNOWN_ONLY and LOCAL_ONLY routing modes. --- src/modules/RoutingModule.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index 87015032d..b7be4abc9 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -13,6 +13,19 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh printPacket("Routing sniffing", &mp); router->sniffReceived(&mp, r); + bool maybePKI = + mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag && mp.channel == 0 && mp.to != NODENUM_BROADCAST; + // Beginning of logic whether to drop the packet based on Rebroadcast mode + if (mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag && + (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY || + config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY)) { + if (!maybePKI) + return false; + if ((nodeDB->getMeshNode(mp.from) == NULL || !nodeDB->getMeshNode(mp.from)->has_user) && + (nodeDB->getMeshNode(mp.to) == NULL || !nodeDB->getMeshNode(mp.to)->has_user)) + return false; + } + // FIXME - move this to a non promsicious PhoneAPI module? // Note: we are careful not to send back packets that started with the phone back to the phone if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB->getNodeNum()) && (mp.from != 0)) { @@ -65,6 +78,9 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) { isPromiscuous = true; - encryptedOk = config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY && - config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY; + + // moved the ReboradcastMode logic into handleReceivedProtobuf + // LocalOnly requires either the from or to to be a known node + // knownOnly specifically requires the from to be a known node. + encryptedOk = true; } \ No newline at end of file From e0b4a8e31ea22206ad60de20dac24dfc9dcbb8da Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 17 Aug 2024 22:51:53 +1200 Subject: [PATCH 0886/1377] Radio Master Joystick (#4476) * Radio Master Bandit 5-Way Joystick: first draft Untested on genuine hardware * "Okay" moves to next frame, even when canned message disabled * Refactor to allow easier customization * Implement feedback from testing * guard toggleGPS() * show "Shutting down.." screen * split adhoc ping alert onto two lines * Don't block while waiting for shutdown Was preventing the alert from showing --- src/input/ExpressLRSFiveWay.cpp | 260 ++++++++++++++++++ src/input/ExpressLRSFiveWay.h | 85 ++++++ src/modules/Modules.cpp | 4 + .../radiomaster_900_bandit_nano/variant.h | 11 +- 4 files changed, 353 insertions(+), 7 deletions(-) create mode 100644 src/input/ExpressLRSFiveWay.cpp create mode 100644 src/input/ExpressLRSFiveWay.h diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp new file mode 100644 index 000000000..c444800ba --- /dev/null +++ b/src/input/ExpressLRSFiveWay.cpp @@ -0,0 +1,260 @@ + +#include "ExpressLRSFiveWay.h" + +#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE + +static const char inputSourceName[] = "ExpressLRS5Way"; // should match "allow input source" string + +/** + * @brief Calculate fuzz: half the distance to the next nearest neighbor for each joystick position. + * + * The goal is to avoid collisions between joystick positions while still maintaining + * the widest tolerance for the analog value. + * + * Example: {10,50,800,1000,300,1600} + * If we just choose the minimum difference for this array the value would + * be 40/2 = 20. + * + * 20 does not leave enough room for the joystick position using 1600 which + * could have a +-100 offset. + * + * Example Fuzz values: {20, 20, 100, 100, 125, 300} now the fuzz for the 1600 + * position is 300 instead of 20 + */ +void ExpressLRSFiveWay::calcFuzzValues() +{ + for (unsigned int i = 0; i < N_JOY_ADC_VALUES; i++) { + uint16_t closestDist = 0xffff; + uint16_t ival = joyAdcValues[i]; + // Find the closest value to ival + for (unsigned int j = 0; j < N_JOY_ADC_VALUES; j++) { + // Don't compare value with itself + if (j == i) + continue; + uint16_t jval = joyAdcValues[j]; + if (jval < ival && (ival - jval < closestDist)) + closestDist = ival - jval; + if (jval > ival && (jval - ival < closestDist)) + closestDist = jval - ival; + } // for j + + // And the fuzz is half the distance to the closest value + fuzzValues[i] = closestDist / 2; + // DBG("joy%u=%u f=%u, ", i, ival, fuzzValues[i]); + } // for i +} + +int ExpressLRSFiveWay::readKey() +{ + uint16_t value = analogRead(PIN_JOYSTICK); + + constexpr uint8_t IDX_TO_INPUT[N_JOY_ADC_VALUES - 1] = {UP, DOWN, LEFT, RIGHT, OK}; + for (unsigned int i = 0; i < N_JOY_ADC_VALUES - 1; ++i) { + if (value < (joyAdcValues[i] + fuzzValues[i]) && value > (joyAdcValues[i] - fuzzValues[i])) + return IDX_TO_INPUT[i]; + } + return NO_PRESS; +} + +ExpressLRSFiveWay::ExpressLRSFiveWay() : concurrency::OSThread(inputSourceName) +{ + // ExpressLRS: init values + isLongPressed = false; + keyInProcess = NO_PRESS; + keyDownStart = 0; + + // Express LRS: calculate the threshold for interpreting ADC values as various buttons + calcFuzzValues(); + + // Meshtastic: register with canned messages + inputBroker->registerSource(this); +} + +// ExpressLRS: interpret reading as key events +void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed) +{ + *keyValue = NO_PRESS; + + int newKey = readKey(); + uint32_t now = millis(); + if (keyInProcess == NO_PRESS) { + // New key down + if (newKey != NO_PRESS) { + keyDownStart = now; + // DBGLN("down=%u", newKey); + } + } else { + // if key released + if (newKey == NO_PRESS) { + // DBGLN("up=%u", keyInProcess); + if (!isLongPressed) { + if ((now - keyDownStart) > KEY_DEBOUNCE_MS) { + *keyValue = keyInProcess; + *keyLongPressed = false; + } + } + isLongPressed = false; + } + // else if the key has changed while down, reset state for next go-around + else if (newKey != keyInProcess) { + newKey = NO_PRESS; + } + // else still pressing, waiting for long if not already signaled + else if (!isLongPressed) { + if ((now - keyDownStart) > KEY_LONG_PRESS_MS) { + *keyValue = keyInProcess; + *keyLongPressed = true; + isLongPressed = true; + } + } + } // if keyInProcess != NO_PRESS + + keyInProcess = newKey; +} + +// Meshtastic: runs at regular intervals +int32_t ExpressLRSFiveWay::runOnce() +{ + uint32_t now = millis(); + + // Dismiss any alert frames after 2 seconds + // Feedback for GPS toggle / adhoc ping + if (alerting && now > alertingSinceMs + 2000) { + alerting = false; + screen->endAlert(); + } + + // Get key events from ExpressLRS code + int keyValue; + bool longPressed; + update(&keyValue, &longPressed); + + // Do something about this key press + determineAction((KeyType)keyValue, longPressed ? LONG : SHORT); + + // If there has been recent key activity, poll the joystick slightly more frequently + if (now < keyDownStart + (20 * 1000UL)) // Within last 20 seconds + return 100; + + // Otherwise, poll slightly less often + // Too many missed pressed if much slower than 250ms + return 250; +} + +// Determine what action to take when a button press is detected +// Written verbose for easier remapping by user +void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length) +{ + switch (key) { + case LEFT: + if (inCannedMessageMenu()) // If in canned message menu + sendKey(CANCEL); // exit the menu (press imaginary cancel key) + else + sendKey(LEFT); + break; + + case RIGHT: + if (inCannedMessageMenu()) // If in canned message menu: + sendKey(CANCEL); // exit the menu (press imaginary cancel key) + else + sendKey(RIGHT); + break; + + case UP: + if (length == LONG) + toggleGPS(); + else + sendKey(UP); + break; + + case DOWN: + if (length == LONG) + sendAdhocPing(); + else + sendKey(DOWN); + break; + + case OK: + if (length == LONG) + shutdown(); + else + click(); // Use instead of sendKey(OK). Works better when canned message module disabled + break; + + default: + break; + } +} + +// Feed input to the canned messages module +void ExpressLRSFiveWay::sendKey(KeyType key) +{ + InputEvent e; + e.source = inputSourceName; + e.inputEvent = key; + notifyObservers(&e); +} + +// Enable or Disable a connected GPS +// Contained as one method for easier remapping of buttons by user +void ExpressLRSFiveWay::toggleGPS() +{ +#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS + if (!config.device.disable_triple_click && (gps != nullptr)) { + gps->toggleGpsMode(); + screen->startAlert("GPS Toggled"); + alerting = true; + alertingSinceMs = millis(); + } +#endif +} + +// Send either node-info or position, on demand +// Contained as one method for easier remapping of buttons by user +void ExpressLRSFiveWay::sendAdhocPing() +{ + service->refreshLocalMeshNode(); + bool sentPosition = service->trySendPosition(NODENUM_BROADCAST, true); + + // Show custom alert frame, with multi-line centering + screen->startAlert([sentPosition](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + uint16_t x_offset = display->width() / 2; + uint16_t y_offset = 26; // Same constant as the default startAlert frame + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Sent ad-hoc"); + display->drawString(x_offset + x, y_offset + FONT_HEIGHT_MEDIUM + y, sentPosition ? "position" : "nodeinfo"); + }); + + alerting = true; + alertingSinceMs = millis(); +} + +// Shutdown the node (enter deep-sleep) +// Contained as one method for easier remapping of buttons by user +void ExpressLRSFiveWay::shutdown() +{ + LOG_INFO("Shutdown from long press\n"); + powerFSM.trigger(EVENT_PRESS); + screen->startAlert("Shutting down..."); + // Don't set alerting = true. We don't want to auto-dismiss this alert. + + playShutdownMelody(); // In case user adds a buzzer + + shutdownAtMsec = millis() + 3000; +} + +// Emulate user button, or canned message SELECT +// This is necessary as canned message module doesn't translate SELECT to user button presses if the module is disabled +// Contained as one method for easier remapping of buttons by user +void ExpressLRSFiveWay::click() +{ + if (!moduleConfig.canned_message.enabled) + powerFSM.trigger(EVENT_PRESS); + else + sendKey(OK); +} + +ExpressLRSFiveWay *expressLRSFiveWayInput = nullptr; + +#endif \ No newline at end of file diff --git a/src/input/ExpressLRSFiveWay.h b/src/input/ExpressLRSFiveWay.h new file mode 100644 index 000000000..c53aa9c09 --- /dev/null +++ b/src/input/ExpressLRSFiveWay.h @@ -0,0 +1,85 @@ +/* + Input source for Radio Master Bandit Nano, and similar hardware. + Devices have a 5-button "resistor ladder" style joystick, read by ADC. + These devices do not use the ADC to monitor input voltage. + + Much of this code taken directly from ExpressLRS FiveWayButton class: + https://github.com/ExpressLRS/ExpressLRS/tree/d9f56f8bd6f9f7144d5f01caaca766383e1e0950/src/lib/SCREEN/FiveWayButton +*/ + +#pragma once + +#include "configuration.h" + +#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE + +#include +#include + +#include "InputBroker.h" +#include "MeshService.h" // For adhoc ping action +#include "buzz.h" +#include "concurrency/OSThread.h" +#include "graphics/Screen.h" // Feedback for adhoc ping / toggle GPS +#include "main.h" +#include "modules/CannedMessageModule.h" + +#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" // For toggle GPS action +#endif + +class ExpressLRSFiveWay : public Observable, public concurrency::OSThread +{ + private: + // Number of values in JOY_ADC_VALUES, if defined + // These must be ADC readings for {UP, DOWN, LEFT, RIGHT, ENTER, IDLE} + static constexpr size_t N_JOY_ADC_VALUES = 6; + static constexpr uint32_t KEY_DEBOUNCE_MS = 25; + static constexpr uint32_t KEY_LONG_PRESS_MS = 3000; // How many milliseconds to hold key for a long press + + // This merged an enum used by the ExpressLRS code, with meshtastic canned message values + // Key names are kept simple, to allow user customizaton + typedef enum { + UP = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP, + DOWN = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN, + LEFT = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT, + RIGHT = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT, + OK = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT, + CANCEL = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL, + NO_PRESS = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE + } KeyType; + + typedef enum { SHORT, LONG } PressLength; + + // From ExpressLRS + int keyInProcess; + uint32_t keyDownStart; + bool isLongPressed; + const uint16_t joyAdcValues[N_JOY_ADC_VALUES] = {JOYSTICK_ADC_VALS}; + uint16_t fuzzValues[N_JOY_ADC_VALUES]; + void calcFuzzValues(); + int readKey(); + void update(int *keyValue, bool *keyLongPressed); + + // Meshtastic code + void determineAction(KeyType key, PressLength length); + void sendKey(KeyType key); + inline bool inCannedMessageMenu() { return cannedMessageModule->shouldDraw(); } + int32_t runOnce() override; + + // Simplified Meshtastic actions, for easier remapping by user + void toggleGPS(); + void sendAdhocPing(); + void shutdown(); + void click(); + + bool alerting = false; // Is the screen showing an alert frame? Feedback for GPS toggle / adhoc ping actions + uint32_t alertingSinceMs = 0; // When did screen begin showing an alert frame? Used to auto-dismiss + + public: + ExpressLRSFiveWay(); +}; + +extern ExpressLRSFiveWay *expressLRSFiveWayInput; + +#endif \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index a1c9f4a03..eca0e8ac9 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,5 +1,6 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/ExpressLRSFiveWay.h" #include "input/InputBroker.h" #include "input/RotaryEncoderInterruptImpl1.h" #include "input/ScanAndSelect.h" @@ -176,6 +177,9 @@ void setupModules() trackballInterruptImpl1 = new TrackballInterruptImpl1(); trackballInterruptImpl1->init(); #endif +#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE + expressLRSFiveWayInput = new ExpressLRSFiveWay(); +#endif #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES cannedMessageModule = new CannedMessageModule(); #endif diff --git a/variants/radiomaster_900_bandit_nano/variant.h b/variants/radiomaster_900_bandit_nano/variant.h index 39c1bc06f..1b6bba126 100644 --- a/variants/radiomaster_900_bandit_nano/variant.h +++ b/variants/radiomaster_900_bandit_nano/variant.h @@ -41,14 +41,11 @@ /* Five way button when using ADC. - 2.632V, 2.177V, 1.598V, 1.055V, 0V - - Possible ADC Values: - { UP, DOWN, LEFT, RIGHT, ENTER, IDLE } - 3227, 0 ,1961, 2668, 1290, 4095 + https://github.com/ExpressLRS/targets/blob/f3215b5ec891108db1a13523e4163950cfcadaac/TX/Radiomaster%20Bandit.json#L41 */ -#define BUTTON_PIN 39 -#define BUTTON_NEED_PULLUP +#define INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE +#define PIN_JOYSTICK 39 +#define JOYSTICK_ADC_VALS /*UP*/ 3227, /*DOWN*/ 0, /*LEFT*/ 1961, /*RIGHT*/ 2668, /*OK*/ 1290, /*IDLE*/ 4095 #define DISPLAY_FLIP_SCREEN From 9dad62e3c49fbf810cff24b5fb5757847aec0585 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 17 Aug 2024 05:52:36 -0500 Subject: [PATCH 0887/1377] Set time-only admin command (#4479) --- src/mesh/generated/meshtastic/admin.pb.h | 5 +++++ src/modules/AdminModule.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index b4971991a..99b8fd8c3 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -166,6 +166,9 @@ typedef struct _meshtastic_AdminMessage { meshtastic_Position set_fixed_position; /* Clear fixed position coordinates and then set position.fixed_position = false */ bool remove_fixed_position; + /* Set time only on the node + Convenience method to set the time on the node (as Net quality) without any other position data */ + uint32_t set_time_only; /* Begins an edit transaction for config, module config, owner, and channel settings changes This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */ bool begin_edit_settings; @@ -261,6 +264,7 @@ extern "C" { #define meshtastic_AdminMessage_remove_favorite_node_tag 40 #define meshtastic_AdminMessage_set_fixed_position_tag 41 #define meshtastic_AdminMessage_remove_fixed_position_tag 42 +#define meshtastic_AdminMessage_set_time_only_tag 43 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 #define meshtastic_AdminMessage_factory_reset_device_tag 94 @@ -307,6 +311,7 @@ X(a, STATIC, ONEOF, UINT32, (payload_variant,set_favorite_node,set_favori X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_favorite_node,remove_favorite_node), 40) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed_position), 41) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ +X(a, STATIC, ONEOF, FIXED32, (payload_variant,set_time_only,set_time_only), 43) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index fe426f8f5..0c39a4304 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -3,6 +3,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "RTC.h" #include #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "BleOta.h" @@ -279,6 +280,15 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } break; } + case meshtastic_AdminMessage_set_time_only_tag: { + LOG_INFO("Client is receiving a set_time_only command.\n"); + struct timeval tv; + tv.tv_sec = r->set_time_only; + tv.tv_usec = 0; + + perhapsSetRTC(RTCQualityFromNet, &tv, false); + break; + } case meshtastic_AdminMessage_enter_dfu_mode_request_tag: { LOG_INFO("Client is requesting to enter DFU mode.\n"); #if defined(ARCH_NRF52) || defined(ARCH_RP2040) From a577dd4142ab7becd0addd1b87a6a545f8b0af0f Mon Sep 17 00:00:00 2001 From: Matt Ranostaj Date: Sat, 17 Aug 2024 19:37:05 +0800 Subject: [PATCH 0888/1377] define PERIPHERAL_WARMUP_MS for heltec_capsule_sensor_v3 (#4473) * delay is at least needed for the GXHTC3 module to be detected --- variants/heltec_capsule_sensor_v3/variant.h | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_capsule_sensor_v3/variant.h b/variants/heltec_capsule_sensor_v3/variant.h index d77571c27..415de0559 100644 --- a/variants/heltec_capsule_sensor_v3/variant.h +++ b/variants/heltec_capsule_sensor_v3/variant.h @@ -47,6 +47,7 @@ #define SENSOR_POWER_CTRL_PIN 21 #define SENSOR_POWER_ON 1 +#define PERIPHERAL_WARMUP_MS 100 #define SENSOR_GPS_CONFLICT #define ESP32S3_WAKE_TYPE ESP_EXT1_WAKEUP_ANY_HIGH \ No newline at end of file From f86dde3c405641f8a756494be50303ec78492b9c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 17 Aug 2024 08:41:12 -0500 Subject: [PATCH 0889/1377] AdminModule session_passkey (#4478) * Protobuf * Adds session_passkey for remote admin changes --- src/modules/AdminModule.cpp | 84 +++++++++++++++++++++++++++++++------ src/modules/AdminModule.h | 9 ++++ 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 0c39a4304..721220cf1 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -70,25 +70,24 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta return handled; } meshtastic_Channel *ch = &channels.getByIndex(mp.channel); - // Could tighten this up further by tracking the last poblic_key we went an AdminMessage request to + // Could tighten this up further by tracking the last public_key we went an AdminMessage request to // and only allowing responses from that remote. - if (!((mp.from == 0 && !config.security.is_managed) || - r->which_payload_variant == meshtastic_AdminMessage_get_channel_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_owner_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_config_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_module_config_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_canned_message_module_messages_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_device_metadata_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_response_tag || - r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag || - r->which_payload_variant == meshtastic_NodeRemoteHardwarePinsResponse_node_remote_hardware_pins_tag || + if (!((mp.from == 0 && !config.security.is_managed) || messageIsResponse(r) || (strcasecmp(ch->settings.name, Channels::adminChannel) == 0 && config.security.admin_channel_enabled) || (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key.bytes, 32) == 0))) { LOG_INFO("Ignoring admin payload %i\n", r->which_payload_variant); return handled; } LOG_INFO("Handling admin payload %i\n", r->which_payload_variant); + + // all of the get and set messages, including those for other modules, flow through here first. + // any message that changes state, we want to check the passkey for + if (mp.from != 0 && !messageIsRequest(r) && !messageIsResponse(r)) { + if (!checkPassKey(r)) { + LOG_WARN("Admin message without session_key!\n"); + return handled; + } + } switch (r->which_payload_variant) { /** @@ -319,6 +318,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllModules(mp, r, &res); if (handleResult == AdminMessageHandleResult::HANDLED_WITH_RESPONSE) { + setPassKey(&res); myReply = allocDataProtobuf(res); } else if (mp.decoded.want_response) { LOG_DEBUG("We did not responded to a request that wanted a respond. req.variant=%d\n", r->which_payload_variant); @@ -638,6 +638,7 @@ void AdminModule::handleGetOwner(const meshtastic_MeshPacket &req) res.get_owner_response = owner; res.which_payload_variant = meshtastic_AdminMessage_get_owner_response_tag; + setPassKey(&res); myReply = allocDataProtobuf(res); } } @@ -694,6 +695,7 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 // and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password); // r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag; res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag; + setPassKey(&res); myReply = allocDataProtobuf(res); } } @@ -779,6 +781,7 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const // and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password); // r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag; res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag; + setPassKey(&res); myReply = allocDataProtobuf(res); } } @@ -802,6 +805,7 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r nodePin.pin = moduleConfig.remote_hardware.available_pins[i]; r.get_node_remote_hardware_pins_response.node_remote_hardware_pins[i + 12] = nodePin; } + setPassKey(&r); myReply = allocDataProtobuf(r); } @@ -810,6 +814,7 @@ void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req) meshtastic_AdminMessage r = meshtastic_AdminMessage_init_default; r.get_device_metadata_response = getDeviceMetadata(); r.which_payload_variant = meshtastic_AdminMessage_get_device_metadata_response_tag; + setPassKey(&r); myReply = allocDataProtobuf(r); } @@ -873,6 +878,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r r.get_device_connection_status_response = conn; r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag; + setPassKey(&r); myReply = allocDataProtobuf(r); } @@ -883,6 +889,7 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch meshtastic_AdminMessage r = meshtastic_AdminMessage_init_default; r.get_channel_response = channels.getByIndex(channelIndex); r.which_payload_variant = meshtastic_AdminMessage_get_channel_response_tag; + setPassKey(&r); myReply = allocDataProtobuf(r); } } @@ -937,4 +944,57 @@ AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_AP { // restrict to the admin channel for rx // boundChannel = Channels::adminChannel; +} + +void AdminModule::setPassKey(meshtastic_AdminMessage *res) +{ + if (millis() / 1000 > session_time + 150) { + for (int i = 0; i < 8; i++) { + session_passkey[i] = random(); + } + session_time = millis() / 1000; + } + memcpy(res->session_passkey.bytes, session_passkey, 8); + res->session_passkey.size = 8; + // if halfway to session_expire, regenerate session_passkey, reset the timeout + // set the key in the packet +} + +bool AdminModule::checkPassKey(meshtastic_AdminMessage *res) +{ // check that the key in the packet is still valid + return (session_time + 300 < millis() / 1000 && res->session_passkey.size == 8 && + memcmp(res->session_passkey.bytes, session_passkey, 8) == 0); +} + +bool AdminModule::messageIsResponse(meshtastic_AdminMessage *r) +{ + if (r->which_payload_variant == meshtastic_AdminMessage_get_channel_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_owner_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_config_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_module_config_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_canned_message_module_messages_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_metadata_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_response_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag || + r->which_payload_variant == meshtastic_NodeRemoteHardwarePinsResponse_node_remote_hardware_pins_tag) + return true; + else + return false; +} + +bool AdminModule::messageIsRequest(meshtastic_AdminMessage *r) +{ + if (r->which_payload_variant == meshtastic_AdminMessage_get_channel_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_owner_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_config_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_module_config_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_canned_message_module_messages_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_metadata_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_request_tag || + r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_request_tag) + return true; + else + return false; } \ No newline at end of file diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index a5ffeb7d6..61c54d1b1 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -25,6 +25,9 @@ class AdminModule : public ProtobufModule, public Obser private: bool hasOpenEditTransaction = false; + uint8_t session_passkey[8] = {0}; + uint session_time = 0; + void saveChanges(int saveWhat, bool shouldReboot = true); /** @@ -48,6 +51,12 @@ class AdminModule : public ProtobufModule, public Obser void handleSetChannel(); void handleSetHamMode(const meshtastic_HamParameters &req); void reboot(int32_t seconds); + + void setPassKey(meshtastic_AdminMessage *res); + bool checkPassKey(meshtastic_AdminMessage *res); + + bool messageIsResponse(meshtastic_AdminMessage *r); + bool messageIsRequest(meshtastic_AdminMessage *r); }; extern AdminModule *adminModule; \ No newline at end of file From 0b010b4fd813d177ff20e1822b2463bcf956e127 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 17 Aug 2024 11:06:00 -0500 Subject: [PATCH 0890/1377] Get STM32 building again by disabling admin module --- src/configuration.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/configuration.h b/src/configuration.h index c9a5d7fb0..2e0efffd4 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -285,6 +285,7 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_INPUTBROKER 1 #define MESHTASTIC_EXCLUDE_SERIAL 1 #define MESHTASTIC_EXCLUDE_POWERSTRESS 1 +#define MESHTASTIC_EXCLUDE_ADMIN 1 #endif // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) From 5ff1078c8c150eb61f1f611ec1118aa6b833e44c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 17 Aug 2024 11:51:53 -0500 Subject: [PATCH 0891/1377] Move NeighborInfo interval default to 6 hours (double NodeInfo) (#4483) * Move NeighborInfo in line with NodeInfo * Set default to 6 hours and cap minimum at 2 hours --- src/mesh/Default.h | 2 ++ src/modules/AdminModule.cpp | 4 ++++ src/modules/NeighborInfoModule.cpp | 3 +-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 3c95544da..bafa60898 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -13,7 +13,9 @@ #define default_min_wake_secs 10 #define default_screen_on_secs IF_ROUTER(1, 60 * 10) #define default_node_info_broadcast_secs 3 * 60 * 60 +#define default_neighbor_info_broadcast_secs 6 * 60 * 60 #define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour +#define min_neighbor_info_broadcast_secs 2 * 60 * 60 #define default_mqtt_address "mqtt.meshtastic.org" #define default_mqtt_username "meshdev" diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 25450992b..cf2247031 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -557,6 +557,10 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) case meshtastic_ModuleConfig_neighbor_info_tag: LOG_INFO("Setting module config: Neighbor Info\n"); moduleConfig.has_neighbor_info = true; + if (moduleConfig.neighbor_info.update_interval < min_neighbor_info_broadcast_secs) { + LOG_DEBUG("Tried to set update_interval too low, setting to %d\n", default_neighbor_info_broadcast_secs); + moduleConfig.neighbor_info.update_interval = default_neighbor_info_broadcast_secs; + } moduleConfig.neighbor_info = c.payload_variant.neighbor_info; break; case meshtastic_ModuleConfig_detection_sensor_tag: diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index fb1249029..8284c52d9 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -120,8 +120,7 @@ int32_t NeighborInfoModule::runOnce() if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { sendNeighborInfo(NODENUM_BROADCAST, false); } - return Default::getConfiguredOrDefaultMsScaled(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs, - numOnlineNodes); + return Default::getConfiguredOrDefault(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs); } /* From e37acae40583cac5cb55bdd3210db1b51950cd89 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Sat, 17 Aug 2024 22:09:13 +0200 Subject: [PATCH 0892/1377] Fix RF switching logic on rp2040-lora board. (#4486) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. * Removed duplicate code. * Fix error: #if with no expression * Fix warning: extra tokens at end of #endif directive. * Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board. --------- Co-authored-by: Ben Meadors --- variants/rp2040-lora/variant.h | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/variants/rp2040-lora/variant.h b/variants/rp2040-lora/variant.h index 1f42c4db7..d0bbc0ec3 100644 --- a/variants/rp2040-lora/variant.h +++ b/variants/rp2040-lora/variant.h @@ -15,7 +15,7 @@ // rxd = 9 #define EXT_NOTIFY_OUT 22 -#define BUTTON_PIN 17 +#undef BUTTON_PIN // Pin 17 used for antenna switching via DIO4 #define LED_PIN PIN_LED @@ -32,23 +32,28 @@ // https://www.waveshare.com/rp2040-lora.htm // https://www.waveshare.com/img/devkit/RP2040-LoRa-HF/RP2040-LoRa-HF-details-11.jpg -#define LORA_SCK 14 // 10 -#define LORA_MISO 24 // 12 -#define LORA_MOSI 15 // 11 -#define LORA_CS 13 // 3 +#define LORA_SCK 14 // GPIO14 +#define LORA_MISO 24 // GPIO24 +#define LORA_MOSI 15 // GPIO15 +#define LORA_CS 13 // GPIO13 -#define LORA_DIO0 RADIOLIB_NC -#define LORA_RESET 23 // 15 -#define LORA_DIO1 16 // 20 -#define LORA_DIO2 18 // 2 -#define LORA_DIO3 RADIOLIB_NC -#define LORA_DIO4 17 +#define LORA_DIO0 RADIOLIB_NC // No GPIO connection +#define LORA_RESET 23 // GPIO23 +#define LORA_BUSY 18 // GPIO18 +#define LORA_DIO1 16 // GPIO16 +#define LORA_DIO2 RADIOLIB_NC // Antenna switching, no GPIO connection +#define LORA_DIO3 RADIOLIB_NC // No GPIO connection +#define LORA_DIO4 17 // GPIO17 + +// On rp2040-lora board the antenna switch is wired and works with complementary-pin control logic. +// See PE4259 datasheet page 4 #ifdef USE_SX1262 #define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 -#define SX126X_BUSY LORA_DIO2 +#define SX126X_BUSY LORA_BUSY #define SX126X_RESET LORA_RESET -#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO2_AS_RF_SWITCH // Antenna switch CTRL +#define SX126X_RXEN LORA_DIO4 // Antenna switch !CTRL via GPIO17 // #define SX126X_DIO3_TCXO_VOLTAGE 1.8 #endif \ No newline at end of file From 33ced7e87aad4e7a79c3096c5cf2167dc8b7ec1f Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 18 Aug 2024 00:34:32 +0200 Subject: [PATCH 0893/1377] Add two-way traceroute result with SNR per hop (#4485) * Add two-way traceroute result with SNR per hop * Update protos --- src/mesh/generated/meshtastic/mesh.pb.h | 31 +++- .../generated/meshtastic/module_config.pb.h | 2 +- src/mesh/generated/meshtastic/portnums.pb.h | 2 +- src/modules/TraceRouteModule.cpp | 136 +++++++++++++----- src/modules/TraceRouteModule.h | 6 +- 5 files changed, 131 insertions(+), 46 deletions(-) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 1f0621f9a..f32f865db 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -187,6 +187,8 @@ typedef enum _meshtastic_HardwareModel { /* RadioMaster 900 Bandit, https://www.radiomasterrc.com/products/bandit-expresslrs-rf-module SSD1306 OLED and No GPS */ meshtastic_HardwareModel_RADIOMASTER_900_BANDIT = 74, + /* Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins. */ + meshtastic_HardwareModel_ME25LS01_4Y10TD = 75, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -502,11 +504,20 @@ typedef struct _meshtastic_User { meshtastic_User_public_key_t public_key; } meshtastic_User; -/* A message used in our Dynamic Source Routing protocol (RFC 4728 based) */ +/* A message used in a traceroute */ typedef struct _meshtastic_RouteDiscovery { - /* The list of nodenums this packet has visited so far */ + /* The list of nodenums this packet has visited so far to the destination. */ pb_size_t route_count; uint32_t route[8]; + /* The list of SNRs (in dB, scaled by 4) in the route towards the destination. */ + pb_size_t snr_towards_count; + int8_t snr_towards[8]; + /* The list of nodenums the packet has visited on the way back from the destination. */ + pb_size_t route_back_count; + uint32_t route_back[8]; + /* The list of SNRs (in dB, scaled by 4) in the route back from the destination. */ + pb_size_t snr_back_count; + int8_t snr_back[8]; } meshtastic_RouteDiscovery; /* A Routing control Data packet handled by the routing module */ @@ -1054,7 +1065,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Position_init_default {false, 0, false, 0, false, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, false, 0, false, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} -#define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} @@ -1079,7 +1090,7 @@ extern "C" { #define meshtastic_ChunkedPayloadResponse_init_default {0, 0, {0}} #define meshtastic_Position_init_zero {false, 0, false, 0, false, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, false, 0, false, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} -#define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} @@ -1136,6 +1147,9 @@ extern "C" { #define meshtastic_User_role_tag 7 #define meshtastic_User_public_key_tag 8 #define meshtastic_RouteDiscovery_route_tag 1 +#define meshtastic_RouteDiscovery_snr_towards_tag 2 +#define meshtastic_RouteDiscovery_route_back_tag 3 +#define meshtastic_RouteDiscovery_snr_back_tag 4 #define meshtastic_Routing_route_request_tag 1 #define meshtastic_Routing_route_reply_tag 2 #define meshtastic_Routing_error_reason_tag 3 @@ -1298,7 +1312,10 @@ X(a, STATIC, SINGULAR, BYTES, public_key, 8) #define meshtastic_User_DEFAULT NULL #define meshtastic_RouteDiscovery_FIELDLIST(X, a) \ -X(a, STATIC, REPEATED, FIXED32, route, 1) +X(a, STATIC, REPEATED, FIXED32, route, 1) \ +X(a, STATIC, REPEATED, INT32, snr_towards, 2) \ +X(a, STATIC, REPEATED, FIXED32, route_back, 3) \ +X(a, STATIC, REPEATED, INT32, snr_back, 4) #define meshtastic_RouteDiscovery_CALLBACK NULL #define meshtastic_RouteDiscovery_DEFAULT NULL @@ -1612,8 +1629,8 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 -#define meshtastic_RouteDiscovery_size 40 -#define meshtastic_Routing_size 42 +#define meshtastic_RouteDiscovery_size 256 +#define meshtastic_Routing_size 259 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 113 #define meshtastic_Waypoint_size 165 diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 7fd57fe00..2e1985660 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -341,7 +341,7 @@ typedef struct _meshtastic_ModuleConfig_CannedMessageConfig { /* Enable/disable CannedMessageModule. */ bool enabled; /* Input event origin accepted by the canned message module. - Can be e.g. "rotEnc1", "upDownEnc1" or keyword "_any" */ + Can be e.g. "rotEnc1", "upDownEnc1", "scanAndSelect", "cardkb", "serialkb", or keyword "_any" */ char allow_input_source[16]; /* CannedMessageModule also sends a bell character with the messages. ExternalNotificationModule can benefit from this feature. */ diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 6cc82352a..b9e537ddf 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -113,7 +113,7 @@ typedef enum _meshtastic_PortNum { ENCODING: Protobuf (?) */ meshtastic_PortNum_SIMULATOR_APP = 69, /* Provides a traceroute functionality to show the route a packet towards - a certain destination would take on the mesh. + a certain destination would take on the mesh. Contains a RouteDiscovery message as payload. ENCODING: Protobuf */ meshtastic_PortNum_TRACEROUTE_APP = 70, /* Aggregates edge info for the network by sending out a list of each node's neighbors diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index f390aafcd..dd3d0b4f9 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -5,71 +5,141 @@ TraceRouteModule *traceRouteModule; bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) { - // Only handle a response - if (mp.decoded.request_id) { - printRoute(r, mp.to, mp.from); - } - + // We only alter the packet in alterReceivedProtobuf() return false; // let it be handled by RoutingModule } void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { auto &incoming = p.decoded; - // Only append IDs for the request (one way) - if (!incoming.request_id) { - // Insert unknown hops if necessary - insertUnknownHops(p, r); - // Don't add ourselves if we are the destination (the reply will have our NodeNum already) - if (p.to != nodeDB->getNodeNum()) { - appendMyID(r); - printRoute(r, p.from, NODENUM_BROADCAST); - } - // Set updated route to the payload of the to be flooded packet - p.decoded.payload.size = - pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r); - } + // Insert unknown hops if necessary + insertUnknownHops(p, r, !incoming.request_id); + + // Append ID and SNR. For the last hop (p.to == nodeDB->getNodeNum()), we only need to append the SNR + appendMyIDandSNR(r, p.rx_snr, !incoming.request_id, p.to == nodeDB->getNodeNum()); + if (!incoming.request_id) + printRoute(r, p.from, p.to, true); + else + printRoute(r, p.to, p.from, false); + + // Set updated route to the payload of the to be flooded packet + p.decoded.payload.size = + pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r); } -void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) +void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination) { + pb_size_t *route_count; + uint32_t *route; + pb_size_t *snr_count; + int8_t *snr_list; + + // Pick the correct route array and SNR list + if (isTowardsDestination) { + route_count = &r->route_count; + route = r->route; + snr_count = &r->snr_towards_count; + snr_list = r->snr_towards; + } else { + route_count = &r->route_back_count; + route = r->route_back; + snr_count = &r->snr_back_count; + snr_list = r->snr_back; + } + // Only insert unknown hops if hop_start is valid if (p.hop_start != 0 && p.hop_limit <= p.hop_start) { uint8_t hopsTaken = p.hop_start - p.hop_limit; - int8_t diff = hopsTaken - r->route_count; + int8_t diff = hopsTaken - *route_count; for (uint8_t i = 0; i < diff; i++) { - if (r->route_count < sizeof(r->route) / sizeof(r->route[0])) { - r->route[r->route_count] = NODENUM_BROADCAST; // This will represent an unknown hop - r->route_count += 1; + if (*route_count < sizeof(*route) / sizeof(route[0])) { + route[*route_count] = NODENUM_BROADCAST; // This will represent an unknown hop + *route_count += 1; + } + } + // Add unknown SNR values if necessary + diff = *route_count - *snr_count; + for (uint8_t i = 0; i < diff; i++) { + if (*snr_count < sizeof(*snr_list) / sizeof(snr_list[0])) { + snr_list[*snr_count] = INT8_MIN; // This will represent an unknown SNR + *snr_count += 1; } } } } -void TraceRouteModule::appendMyID(meshtastic_RouteDiscovery *updated) +void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, float snr, bool isTowardsDestination, bool SNRonly) { + pb_size_t *route_count; + uint32_t *route; + pb_size_t *snr_count; + int8_t *snr_list; + + // Pick the correct route array and SNR list + if (isTowardsDestination) { + route_count = &updated->route_count; + route = updated->route; + snr_count = &updated->snr_towards_count; + snr_list = updated->snr_towards; + } else { + route_count = &updated->route_back_count; + route = updated->route_back; + snr_count = &updated->snr_back_count; + snr_list = updated->snr_back; + } + + if (*snr_count < sizeof(*snr_list) / sizeof(snr_list[0])) { + snr_list[*snr_count] = (int8_t)(snr * 4); // Convert SNR to 1 byte + *snr_count += 1; + } + if (SNRonly) + return; + // Length of route array can normally not be exceeded due to the max. hop_limit of 7 - if (updated->route_count < sizeof(updated->route) / sizeof(updated->route[0])) { - updated->route[updated->route_count] = myNodeInfo.my_node_num; - updated->route_count += 1; + if (*route_count < sizeof(*route) / sizeof(route[0])) { + route[*route_count] = myNodeInfo.my_node_num; + *route_count += 1; } else { LOG_WARN("Route exceeded maximum hop limit, are you bridging networks?\n"); } } -void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest) +void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination) { #ifdef DEBUG_PORT LOG_INFO("Route traced:\n"); LOG_INFO("0x%x --> ", origin); for (uint8_t i = 0; i < r->route_count; i++) { - LOG_INFO("0x%x --> ", r->route[i]); + if (i < r->snr_towards_count && r->snr_towards[i] != INT8_MIN) + LOG_INFO("0x%x (%.2fdB) --> ", r->route[i], (float)r->snr_towards[i] / 4); + else + LOG_INFO("0x%x (?dB) --> ", r->route[i]); } - if (dest != NODENUM_BROADCAST) - LOG_INFO("0x%x\n", dest); - else + // If we are the destination, or it has already reached the destination, print it + if (dest == nodeDB->getNodeNum() || !isTowardsDestination) { + if (r->snr_towards_count > 0 && r->snr_towards[r->snr_towards_count - 1] != INT8_MIN) + LOG_INFO("0x%x (%.2fdB)\n", dest, (float)r->snr_towards[r->snr_towards_count - 1] / 4); + else + LOG_INFO("0x%x (?dB)\n", dest); + } else LOG_INFO("...\n"); + + // If there's a route back (or we are the destination as then the route is complete), print it + if (r->route_back_count > 0 || origin == nodeDB->getNodeNum()) { + if (r->snr_towards_count > 0 && origin == nodeDB->getNodeNum()) + LOG_INFO("(%.2fdB) 0x%x <-- ", (float)r->snr_back[r->snr_back_count - 1] / 4, origin); + else + LOG_INFO("..."); + + for (int8_t i = r->route_back_count - 1; i >= 0; i--) { + if (i < r->snr_back_count && r->snr_back[i] != INT8_MIN) + LOG_INFO("(%.2fdB) 0x%x <-- ", (float)r->snr_back[i] / 4, r->route_back[i]); + else + LOG_INFO("(?dB) 0x%x <-- ", r->route_back[i]); + } + LOG_INFO("0x%x\n", dest); + } #endif } @@ -86,8 +156,6 @@ meshtastic_MeshPacket *TraceRouteModule::allocReply() pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_RouteDiscovery_msg, &scratch); updated = &scratch; - printRoute(updated, req.from, req.to); - // Create a MeshPacket with this payload and set it as the reply meshtastic_MeshPacket *reply = allocDataProtobuf(*updated); diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index 18a5ac0cb..fe69300de 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -20,15 +20,15 @@ class TraceRouteModule : public ProtobufModule private: // Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit - void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); + void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination); // Call to add your ID to the route array of a RouteDiscovery message - void appendMyID(meshtastic_RouteDiscovery *r); + void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly); /* Call to print the route array of a RouteDiscovery message. Set origin to where the request came from. Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */ - void printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest); + void printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination); }; extern TraceRouteModule *traceRouteModule; \ No newline at end of file From 1bbc273ba6cac076fa2c97a30178ef2bc12e682b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 17 Aug 2024 17:35:05 -0500 Subject: [PATCH 0894/1377] Don't reject network time updates unintentionally (#4489) --- src/modules/PositionModule.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 1756e8508..5acb5e9d1 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -140,7 +140,8 @@ void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUp bool PositionModule::hasQualityTimesource() { - bool setFromPhoneOrNtpToday = (millis() - lastSetFromPhoneNtpOrGps) <= (SEC_PER_DAY * 1000UL); + bool setFromPhoneOrNtpToday = + lastSetFromPhoneNtpOrGps == 0 ? false : (millis() - lastSetFromPhoneNtpOrGps) <= (SEC_PER_DAY * 1000UL); bool hasGpsOrRtc = (gps && gps->isConnected()) || (rtc_found.address != ScanI2C::ADDRESS_NONE.address); return hasGpsOrRtc || setFromPhoneOrNtpToday; } From a8999d77590408d1f5f71941ab17d4ce7331e47a Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Mon, 19 Aug 2024 00:22:16 +1200 Subject: [PATCH 0895/1377] Don't manually clear runAsap flag (#4496) --- src/ButtonThread.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 1b101044f..5351fa049 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -224,7 +224,6 @@ int32_t ButtonThread::runOnce() btnEvent = BUTTON_EVENT_NONE; } - runASAP = false; return 50; } From 23e3e6db929e2c379a902d17a08f3d3cf664cad5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 07:23:56 -0500 Subject: [PATCH 0896/1377] Add 4 bytes of random nonce to PKI (#4493) --- src/mesh/CryptoEngine.cpp | 23 ++++++++++++++++------- src/mesh/CryptoEngine.h | 2 +- src/mesh/Router.cpp | 8 ++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index e83236eab..fbda4a678 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -41,7 +41,11 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ uint8_t *bytesOut) { uint8_t *auth; + uint32_t *extraNonce; auth = bytesOut + numBytes; + extraNonce = (uint32_t *)(auth + 8); + *extraNonce = random(); + LOG_INFO("Random nonce value: %d\n", *extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); if (node->num < 1 || node->user.public_key.size == 0) { LOG_DEBUG("Node %d or their public_key not found\n", toNode); @@ -50,10 +54,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ if (!crypto->setDHKey(toNode)) { return false; } - initNonce(fromNode, packetNum); + initNonce(fromNode, packetNum, *extraNonce); // Calculate the shared secret with the destination node and encrypt - printBytes("Attempting encrypt using nonce: ", nonce, 16); + printBytes("Attempting encrypt using nonce: ", nonce, 13); printBytes("Attempting encrypt using shared_key: ", shared_key, 32); aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); return true; @@ -68,7 +72,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) { uint8_t *auth; // set to last 8 bytes of text? - auth = bytes + numBytes - 8; + uint32_t *extraNonce; + auth = bytes + numBytes - 12; + extraNonce = (uint32_t *)(auth + 8); + LOG_INFO("Random nonce value: %d\n", *extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) { @@ -80,10 +87,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size if (!crypto->setDHKey(fromNode)) { return false; } - initNonce(fromNode, packetNum); - printBytes("Attempting decrypt using nonce: ", nonce, 16); + initNonce(fromNode, packetNum, *extraNonce); + printBytes("Attempting decrypt using nonce: ", nonce, 13); printBytes("Attempting decrypt using shared_key: ", shared_key, 32); - return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 8, nullptr, 0, auth, bytesOut); + return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 12, nullptr, 0, auth, bytesOut); } void CryptoEngine::setDHPrivateKey(uint8_t *_private_key) @@ -232,13 +239,15 @@ void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numByte /** * Init our 128 bit nonce for a new packet */ -void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId) +void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetId, uint32_t extraNonce) { memset(nonce, 0, sizeof(nonce)); // use memcpy to avoid breaking strict-aliasing memcpy(nonce, &packetId, sizeof(uint64_t)); memcpy(nonce + sizeof(uint64_t), &fromNode, sizeof(uint32_t)); + if (extraNonce) + memcpy(nonce + sizeof(uint32_t), &extraNonce, sizeof(uint32_t)); } #ifndef HAS_CUSTOM_CRYPTO_ENGINE CryptoEngine *crypto = new CryptoEngine; diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 5ca9db7c1..64382f6a7 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -88,7 +88,7 @@ class CryptoEngine * a 32 bit sending node number (stored in little endian order) * a 32 bit block counter (starts at zero) */ - void initNonce(uint32_t fromNode, uint64_t packetId); + void initNonce(uint32_t fromNode, uint64_t packetId, uint32_t extraNonce = 0); }; extern CryptoEngine *crypto; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f8655b1e3..bdd8c4e6c 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -322,13 +322,13 @@ bool perhapsDecode(meshtastic_MeshPacket *p) // Attempt PKI decryption first if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && p->to != NODENUM_BROADCAST && nodeDB->getMeshNode(p->from) != nullptr && nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && - nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > 8) { + nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > 12) { LOG_DEBUG("Attempting PKI decryption\n"); if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) { LOG_INFO("PKI Decryption worked!\n"); memset(&p->decoded, 0, sizeof(p->decoded)); - rawSize -= 8; + rawSize -= 12; if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded) && p->decoded.portnum != meshtastic_PortNum_UNKNOWN_APP) { decrypted = true; @@ -470,7 +470,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); - if (numbytes + 8 > MAX_RHPACKETLEN) + if (numbytes + 12 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { @@ -479,7 +479,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) return meshtastic_Routing_Error_PKI_FAILED; } crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); - numbytes += 8; + numbytes += 12; memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); p->channel = 0; p->pki_encrypted = true; From 7129cee944d78387f65a223bb69024253cdc20bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Cort=C3=AAs?= <593860+mrfyda@users.noreply.github.com> Date: Sun, 18 Aug 2024 13:38:54 +0100 Subject: [PATCH 0897/1377] feature: default to fuzzy GPS location on the Default Channel (#4467) * feature: default to fuzzy GPS location on the Default Channel * Default to 13 * 13 default --------- Co-authored-by: Ben Meadors --- src/mesh/Channels.cpp | 4 ++-- src/modules/PositionModule.cpp | 5 +++-- userPrefs.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 6e721d9b0..bba7571d2 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -97,7 +97,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) channelSettings.psk.bytes[0] = defaultpskIndex; channelSettings.psk.size = 1; strncpy(channelSettings.name, "", sizeof(channelSettings.name)); - channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel + channelSettings.module_settings.position_precision = 13; // default to sending location on the primary channel channelSettings.has_module_settings = true; ch.has_settings = true; @@ -363,4 +363,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) int16_t Channels::setActiveByIndex(ChannelIndex channelIndex) { return setCrypto(channelIndex); -} \ No newline at end of file +} diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 5acb5e9d1..f534baf67 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -298,7 +298,8 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha if (channels.getByIndex(channel).settings.has_module_settings) { precision = channels.getByIndex(channel).settings.module_settings.position_precision; } else if (channels.getByIndex(channel).role == meshtastic_Channel_Role_PRIMARY) { - precision = 32; + // backwards compatibility for Primary channels created before position_precision was set by default + precision = 13; } else { precision = 0; } @@ -470,4 +471,4 @@ void PositionModule::handleNewPosition() } } -#endif \ No newline at end of file +#endif diff --git a/userPrefs.h b/userPrefs.h index 3ebbefcaf..52209deaa 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -17,7 +17,7 @@ } */ // #define CHANNEL_0_NAME_USERPREFS "DEFCONnect" -// #define CHANNEL_0_PRECISION_USERPREFS 13 +// #define CHANNEL_0_PRECISION_USERPREFS 14 // #define CONFIG_OWNER_LONG_NAME_USERPREFS "My Long Name" // #define CONFIG_OWNER_SHORT_NAME_USERPREFS "MLN" From e3e36e23f9bec8e737847ed3da1caddbb05b8ee3 Mon Sep 17 00:00:00 2001 From: Andre K Date: Sun, 18 Aug 2024 11:13:53 -0300 Subject: [PATCH 0898/1377] add admin getter for `SECURITY_CONFIG` (#4499) --- src/modules/AdminModule.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 727445090..f1c397927 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -690,6 +690,11 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 res.get_config_response.which_payload_variant = meshtastic_Config_bluetooth_tag; res.get_config_response.payload_variant.bluetooth = config.bluetooth; break; + case meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG: + LOG_INFO("Getting config: Security\n"); + res.get_config_response.which_payload_variant = meshtastic_Config_security_tag; + res.get_config_response.payload_variant.bluetooth = config.security; + break; } // NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior. // So even if we internally use 0 to represent 'use default' we still need to send the value we are From 7a65c8838d1f1c92893bb134d0ae4eea28d678f1 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 18 Aug 2024 16:14:23 +0200 Subject: [PATCH 0899/1377] Fall back to default modem preset if requested bandwidth is too large (#4497) --- src/mesh/RadioInterface.cpp | 147 ++++++++++++++++++++---------------- src/mesh/RadioInterface.h | 1 + 2 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 104ddbd1d..f0048dd3d 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -412,72 +412,93 @@ void RadioInterface::applyModemConfig() // Set up default configuration // No Sync Words in LORA mode meshtastic_Config_LoRaConfig &loraConfig = config.lora; - if (loraConfig.use_preset) { + bool validConfig = false; // We need to check for a valid configuration + while (!validConfig) { + if (loraConfig.use_preset) { - switch (loraConfig.modem_preset) { - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 812.5 : 500; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 8; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 9; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 10; - break; - default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 12; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW: - bw = (myRegion->wideLora) ? 203.125 : 62.5; - cr = 8; - sf = 12; - break; + switch (loraConfig.modem_preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bw = (myRegion->wideLora) ? 812.5 : 500; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + bw = (myRegion->wideLora) ? 812.5 : 250; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + bw = (myRegion->wideLora) ? 812.5 : 250; + cr = 5; + sf = 8; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + bw = (myRegion->wideLora) ? 812.5 : 250; + cr = 5; + sf = 9; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + bw = (myRegion->wideLora) ? 812.5 : 250; + cr = 5; + sf = 10; + break; + default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. + bw = (myRegion->wideLora) ? 812.5 : 250; + cr = 5; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + bw = (myRegion->wideLora) ? 406.25 : 125; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + bw = (myRegion->wideLora) ? 406.25 : 125; + cr = 8; + sf = 12; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW: + bw = (myRegion->wideLora) ? 203.125 : 62.5; + cr = 8; + sf = 12; + break; + } + } else { + sf = loraConfig.spread_factor; + cr = loraConfig.coding_rate; + bw = loraConfig.bandwidth; + + if (bw == 31) // This parameter is not an integer + bw = 31.25; + if (bw == 62) // Fix for 62.5Khz bandwidth + bw = 62.5; + if (bw == 200) + bw = 203.125; + if (bw == 400) + bw = 406.25; + if (bw == 800) + bw = 812.5; + if (bw == 1600) + bw = 1625.0; } - } else { - sf = loraConfig.spread_factor; - cr = loraConfig.coding_rate; - bw = loraConfig.bandwidth; - if (bw == 31) // This parameter is not an integer - bw = 31.25; - if (bw == 62) // Fix for 62.5Khz bandwidth - bw = 62.5; - if (bw == 200) - bw = 203.125; - if (bw == 400) - bw = 406.25; - if (bw == 800) - bw = 812.5; - if (bw == 1600) - bw = 1625.0; + if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) { + static const char *err_string = + "Regional frequency range is smaller than bandwidth. Falling back to default preset.\n"; + LOG_ERROR(err_string); + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + sprintf(cn->message, err_string); + service->sendClientNotification(cn); + + // Set to default modem preset + loraConfig.use_preset = true; + loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + } else { + validConfig = true; + } } power = loraConfig.tx_power; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 1f2ec9bab..f1016e3d8 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -5,6 +5,7 @@ #include "Observer.h" #include "PointerQueue.h" #include "airtime.h" +#include "error.h" #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission From a85df199a5ae273910ffbf0e8f72fe073c4b61b8 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:15:50 +0200 Subject: [PATCH 0900/1377] Only accept PKI messages for MQTT downlink if we know transmitter and receiver (#4498) --- src/mqtt/MQTT.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index d3a2bb92c..22f68bac8 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -164,10 +164,14 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) // PKI messages get accepted even if we can't decrypt if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && - strcmp(e.channel_id, "PKI") == 0) - router->enqueueReceivedMessage(p); - // ignore messages if we don't have the channel key - else if (router && perhapsDecode(p)) + strcmp(e.channel_id, "PKI") == 0) { + meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); + meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); + // Only accept PKI messages if we have both the sender and receiver in our nodeDB, as then it's likely + // they discovered each other via a channel we have downlink enabled for + if (tx && tx->has_user && rx && rx->has_user) + router->enqueueReceivedMessage(p); + } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key router->enqueueReceivedMessage(p); else packetPool.release(p); From 22e129e7160913c1b1dae433f751fd75799f89fb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 13:00:52 -0500 Subject: [PATCH 0901/1377] bluetooth != security; security = security --- src/modules/AdminModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index f1c397927..3a611d205 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -693,7 +693,7 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 case meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG: LOG_INFO("Getting config: Security\n"); res.get_config_response.which_payload_variant = meshtastic_Config_security_tag; - res.get_config_response.payload_variant.bluetooth = config.security; + res.get_config_response.payload_variant.security = config.security; break; } // NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior. From bfbc4bf93a89228f2852f61b2a0139396a86447d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 14:11:17 -0500 Subject: [PATCH 0902/1377] Set the private_key in crypto when changed by admin --- src/modules/AdminModule.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 3a611d205..e7dff60ce 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -536,6 +536,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.security = c.payload_variant.security; owner.public_key.size = config.security.public_key.size; memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); + crypto->setDHPrivateKey(config.security.private_key.bytes); if (config.security.debug_log_api_enabled == c.payload_variant.security.debug_log_api_enabled && config.security.serial_enabled == c.payload_variant.security.serial_enabled) requiresReboot = false; From f439081674e695bba40bab21ca77c7d05ecc3ccb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 14:11:39 -0500 Subject: [PATCH 0903/1377] Don't segfault on PKI from unknown nodes --- src/mesh/ReliableRouter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 0c9180eb5..4c8c1e1e7 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -111,7 +111,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 && - (nodeDB->getMeshNode(p->from) != nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) { + (nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) { // This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start, p->hop_limit); From ecb4fb72dbca2c8dd555c3c9e8b423dfda17fc2e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 15:51:43 -0500 Subject: [PATCH 0904/1377] Don't break EXCLUDE_PKI --- src/modules/AdminModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index e7dff60ce..604541cd5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -536,7 +536,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.security = c.payload_variant.security; owner.public_key.size = config.security.public_key.size; memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); +#if !MESHTASTIC_EXCLUDE_PKI crypto->setDHPrivateKey(config.security.private_key.bytes); +#endif if (config.security.debug_log_api_enabled == c.payload_variant.security.debug_log_api_enabled && config.security.serial_enabled == c.payload_variant.security.serial_enabled) requiresReboot = false; From 94d5ee9fe6f523a67e59d2197a73cdbf5eb48456 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 22:22:21 -0500 Subject: [PATCH 0905/1377] Deal with adminModule session_time of 0 --- src/modules/AdminModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 604541cd5..599e389df 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -960,7 +960,7 @@ AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_AP void AdminModule::setPassKey(meshtastic_AdminMessage *res) { - if (millis() / 1000 > session_time + 150) { + if (session_time == 0 || millis() / 1000 > session_time + 150) { for (int i = 0; i < 8; i++) { session_passkey[i] = random(); } From 273beef148a59f485268e73c6aa4ff3fab5bd5e9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Aug 2024 22:25:08 -0500 Subject: [PATCH 0906/1377] Re-set the extra-nonce value --- src/mesh/CryptoEngine.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index fbda4a678..eef9f74b1 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -42,9 +42,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ { uint8_t *auth; uint32_t *extraNonce; + long extraNonceTmp = random(); auth = bytesOut + numBytes; extraNonce = (uint32_t *)(auth + 8); - *extraNonce = random(); + *extraNonce = extraNonceTmp; LOG_INFO("Random nonce value: %d\n", *extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); if (node->num < 1 || node->user.public_key.size == 0) { @@ -59,7 +60,9 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ // Calculate the shared secret with the destination node and encrypt printBytes("Attempting encrypt using nonce: ", nonce, 13); printBytes("Attempting encrypt using shared_key: ", shared_key, 32); - aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); + aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, + auth); // this can write up to 15 bytes longer than numbytes past bytesOut + *extraNonce = extraNonceTmp; return true; } From 14146d6ff57134a1328c3ea55293b0dcf00263d6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Aug 2024 07:04:48 -0500 Subject: [PATCH 0907/1377] Ms! --- src/modules/NeighborInfoModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 8284c52d9..2de4374e6 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -120,7 +120,7 @@ int32_t NeighborInfoModule::runOnce() if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { sendNeighborInfo(NODENUM_BROADCAST, false); } - return Default::getConfiguredOrDefault(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs); + return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs); } /* From e65e79c6c9f4fcd3e74e21b02b4cfd967c913c93 Mon Sep 17 00:00:00 2001 From: Talie5in Date: Mon, 19 Aug 2024 21:35:28 +0930 Subject: [PATCH 0908/1377] We need Millseconds not... rapid fire NeighbourInfo! (#4504) From 6de3ca4301c32ec1f2c5094357597bf5ae5646e7 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Mon, 19 Aug 2024 14:09:09 +0200 Subject: [PATCH 0909/1377] Fix deprecated macros. (#4505) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. * Removed duplicate code. * Fix error: #if with no expression * Fix warning: extra tokens at end of #endif directive. * Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board. * Fix deprecated macros. --------- Co-authored-by: Ben Meadors --- src/modules/Telemetry/Sensor/OPT3001Sensor.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp index d0e38bf88..338bd9e2c 100644 --- a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp @@ -25,10 +25,10 @@ void OPT3001Sensor::setup() { OPT3001_Config newConfig; - newConfig.RangeNumber = B1100; - newConfig.ConvertionTime = B0; - newConfig.Latch = B1; - newConfig.ModeOfConversionOperation = B11; + newConfig.RangeNumber = 0b1100; + newConfig.ConvertionTime = 0b0; + newConfig.Latch = 0b1; + newConfig.ModeOfConversionOperation = 0b11; OPT3001_ErrorCode errorConfig = opt3001.writeConfig(newConfig); if (errorConfig != NO_ERROR) { From ab9268cba93be45fbff8401aa953ff56459d18fb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 19 Aug 2024 11:12:42 -0500 Subject: [PATCH 0910/1377] Admin session key debugging messages --- src/modules/AdminModule.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 599e389df..81d595d29 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -4,6 +4,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "RTC.h" +#include "meshUtils.h" #include #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "BleOta.h" @@ -968,13 +969,16 @@ void AdminModule::setPassKey(meshtastic_AdminMessage *res) } memcpy(res->session_passkey.bytes, session_passkey, 8); res->session_passkey.size = 8; + printBytes("Setting admin key to ", res->session_passkey.bytes, 8); // if halfway to session_expire, regenerate session_passkey, reset the timeout // set the key in the packet } bool AdminModule::checkPassKey(meshtastic_AdminMessage *res) { // check that the key in the packet is still valid - return (session_time + 300 < millis() / 1000 && res->session_passkey.size == 8 && + printBytes("Incoming session key: ", res->session_passkey.bytes, 8); + printBytes("Expected session key: ", session_passkey, 8); + return (session_time + 300 > millis() / 1000 && res->session_passkey.size == 8 && memcmp(res->session_passkey.bytes, session_passkey, 8) == 0); } From c1569b0f702e3f565f3920c8879914d45004f1e6 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Mon, 19 Aug 2024 10:03:40 -0700 Subject: [PATCH 0911/1377] add draft contributing.md file --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..229166c4a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to Meshtastic Firmware + +We're excited that you're interested in contributing to the Meshtastic firmware! This document provides a high-level overview of how you can get involved. + +## Important First Steps + +Before you begin, please: + +1. **Read our documentation**: Our [official documentation](https://meshtastic.org/docs/) is a crucial resource. It contains essential information about the project, including setup guides, feature explanations, and contribution guidelines. + +2. **Check out the firmware build guide**: For specific instructions on setting up your development environment and building the firmware, refer to our [Firmware Build Guide](https://meshtastic.org/docs/development/firmware/build/). + +3. Read our [Code of Conduct](CODE_OF_CONDUCT.md) + +4. Join our [Discord community](https://discord.com/invite/ktMAKGBnBs) to connect with other contributors and get help + +## Getting Help and Discussing Ideas + +We encourage open communication and discussion before diving into code changes: + +1. **Use GitHub Discussions**: For new ideas, questions, or to discuss potential changes, start a conversation in our [GitHub Discussions](https://github.com/meshtastic/firmware/discussions) first. This helps us collaborate and avoid duplicate work. + +2. **Join our Discord**: For real-time chat and quick questions, join our [Discord server](https://discord.com/invite/ktMAKGBnBs). It's a great place to get help and connect with the community. + +3. **Reporting Issues**: If you've identified a bug, please use our bug report template when creating a new issue in the [issue tracker](https://github.com/meshtastic/firmware/issues). Ensure you've searched existing issues to avoid duplicates. + +## Making Contributions + +1. Fork the repository +2. Create a new branch for your feature or bug fix +3. Make your changes +4. Test your changes thoroughly +5. Create a pull request with a clear description of your changes + +## Coding Standards + +[Placeholder for coding standards] + +Thank you for contributing to Meshtastic! \ No newline at end of file From 2a664e01b0c7a75b49ac1d4ab351ff42ba680707 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Mon, 19 Aug 2024 10:06:33 -0700 Subject: [PATCH 0912/1377] update code of conduct link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 229166c4a..7d642f1f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Before you begin, please: 2. **Check out the firmware build guide**: For specific instructions on setting up your development environment and building the firmware, refer to our [Firmware Build Guide](https://meshtastic.org/docs/development/firmware/build/). -3. Read our [Code of Conduct](CODE_OF_CONDUCT.md) +3. Read our [Code of Conduct](https://meshtastic.org/docs/legal/conduct/) 4. Join our [Discord community](https://discord.com/invite/ktMAKGBnBs) to connect with other contributors and get help From 9d323a3832dae5264f9ef545c59a9e721a8bd108 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Mon, 19 Aug 2024 10:08:43 -0700 Subject: [PATCH 0913/1377] verbiage changes --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d642f1f5..f0976554f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ Before you begin, please: 3. Read our [Code of Conduct](https://meshtastic.org/docs/legal/conduct/) -4. Join our [Discord community](https://discord.com/invite/ktMAKGBnBs) to connect with other contributors and get help +4. Join our [Discord community](https://discord.com/invite/ktMAKGBnBs) to connect with developers and other contributors to get help. ## Getting Help and Discussing Ideas @@ -20,7 +20,7 @@ We encourage open communication and discussion before diving into code changes: 1. **Use GitHub Discussions**: For new ideas, questions, or to discuss potential changes, start a conversation in our [GitHub Discussions](https://github.com/meshtastic/firmware/discussions) first. This helps us collaborate and avoid duplicate work. -2. **Join our Discord**: For real-time chat and quick questions, join our [Discord server](https://discord.com/invite/ktMAKGBnBs). It's a great place to get help and connect with the community. +2. **Join our Discord**: For real-time chat and quick questions, join our [Discord server](https://discord.com/invite/ktMAKGBnBs). It's a great place to get help and connect with other developers and the community. 3. **Reporting Issues**: If you've identified a bug, please use our bug report template when creating a new issue in the [issue tracker](https://github.com/meshtastic/firmware/issues). Ensure you've searched existing issues to avoid duplicates. @@ -30,7 +30,7 @@ We encourage open communication and discussion before diving into code changes: 2. Create a new branch for your feature or bug fix 3. Make your changes 4. Test your changes thoroughly -5. Create a pull request with a clear description of your changes +5. Create a pull request with a clear description, using the provided template, of your changes ## Coding Standards From 33b12126e00b8bab1b66c5f0825d5f0d115ca3ef Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Mon, 19 Aug 2024 10:10:32 -0700 Subject: [PATCH 0914/1377] more verbiage --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f0976554f..c4cdd3bcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ We're excited that you're interested in contributing to the Meshtastic firmware! Before you begin, please: -1. **Read our documentation**: Our [official documentation](https://meshtastic.org/docs/) is a crucial resource. It contains essential information about the project, including setup guides, feature explanations, and contribution guidelines. +1. **Read our documentation**: Our [official documentation](https://meshtastic.org/docs/) is a crucial resource. It contains essential information about the project. 2. **Check out the firmware build guide**: For specific instructions on setting up your development environment and building the firmware, refer to our [Firmware Build Guide](https://meshtastic.org/docs/development/firmware/build/). From 9b4ad68f437565ddfe05286809d7ae9d6f070b95 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Aug 2024 18:39:26 -0500 Subject: [PATCH 0915/1377] Add simulator back as a separate step --- .github/workflows/test_simulator.yml | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/test_simulator.yml diff --git a/.github/workflows/test_simulator.yml b/.github/workflows/test_simulator.yml new file mode 100644 index 000000000..9c33008c1 --- /dev/null +++ b/.github/workflows/test_simulator.yml @@ -0,0 +1,47 @@ +name: Test Simulator + +on: + schedule: + - cron: "0 0 * * *" # Run every day at midnight + workflow_dispatch: {} + +jobs: + test-simulator: + runs-on: ubuntu-latest + steps: + - name: Install libbluetooth + shell: bash + run: | + sudo apt-get update --fix-missing + sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev + + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Upgrade python tools + shell: bash + run: | + python -m pip install --upgrade pip + pip install -U platformio adafruit-nrfutil + pip install -U meshtastic --pre + + - name: Upgrade platformio + shell: bash + run: | + pio upgrade + + - name: Build Native + run: bin/build-native.sh + + # We now run integration test before other build steps (to quickly see runtime failures) + - name: Build for native + run: platformio run -e native + + - name: Integration test + run: | + .pio/build/native/program + & sleep 20 # 5 seconds was not enough + echo "Simulator started, launching python test..." + python3 -c 'from meshtastic.test import testSimulator; testSimulator()' From bd21a0455bab978a4c729311d991452a4a24eee1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Aug 2024 19:19:16 -0500 Subject: [PATCH 0916/1377] & --- .github/workflows/test_simulator.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test_simulator.yml b/.github/workflows/test_simulator.yml index 9c33008c1..9dbcf0554 100644 --- a/.github/workflows/test_simulator.yml +++ b/.github/workflows/test_simulator.yml @@ -41,7 +41,6 @@ jobs: - name: Integration test run: | - .pio/build/native/program - & sleep 20 # 5 seconds was not enough + .pio/build/native/program & sleep 10 # 5 seconds was not enough echo "Simulator started, launching python test..." python3 -c 'from meshtastic.test import testSimulator; testSimulator()' From ef5279e85e7cb572bafb19d01af8dd0783b580a9 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Tue, 20 Aug 2024 13:04:39 +0200 Subject: [PATCH 0917/1377] Set RP2040 in dormant mode when deep sleep is triggered. (#4510) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. * Removed duplicate code. * Fix error: #if with no expression * Fix warning: extra tokens at end of #endif directive. * Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board. * Fix deprecated macros. * Set RP2040 in dormant mode when deep sleep is triggered. --------- Co-authored-by: Ben Meadors --- src/platform/rp2040/main-rp2040.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/platform/rp2040/main-rp2040.cpp b/src/platform/rp2040/main-rp2040.cpp index af3aeadc3..6306f34c1 100644 --- a/src/platform/rp2040/main-rp2040.cpp +++ b/src/platform/rp2040/main-rp2040.cpp @@ -1,4 +1,5 @@ #include "configuration.h" +#include "hardware/xosc.h" #include #include #include @@ -12,7 +13,11 @@ void setBluetoothEnable(bool enable) void cpuDeepSleep(uint32_t msecs) { - // not needed + /* Disable both PLL to avoid power dissipation */ + pll_deinit(pll_sys); + pll_deinit(pll_usb); + /* Set RP2040 in dormant mode. Will not wake up. */ + xosc_dormant(); } void updateBatteryLevel(uint8_t level) From 3b2c37c47fcebb2fe041c25c53e1fe4a0e181a9d Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 20 Aug 2024 19:19:02 +0800 Subject: [PATCH 0918/1377] Remove heltec-specific gps code from main.cpp (#4508) After the recent GPS power work we have an clear set of definitions for turning GPS on and off. Rather than manipulating specific heltec tracker-related pins in main setu, the relevant power management code in the GPS module will turn things on/off later as needed. --- src/main.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b6af60d2c..f4fb24fa9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -294,21 +294,11 @@ void setup() digitalWrite(VEXT_ENABLE, 0); // turn on the display power #endif -#if defined(VGNSS_CTRL_V03) - pinMode(VGNSS_CTRL_V03, OUTPUT); - digitalWrite(VGNSS_CTRL_V03, LOW); -#endif - #if defined(VTFT_CTRL_V03) pinMode(VTFT_CTRL_V03, OUTPUT); digitalWrite(VTFT_CTRL_V03, LOW); #endif -#if defined(VGNSS_CTRL) - pinMode(VGNSS_CTRL, OUTPUT); - digitalWrite(VGNSS_CTRL, LOW); -#endif - #if defined(VTFT_CTRL) pinMode(VTFT_CTRL, OUTPUT); digitalWrite(VTFT_CTRL, LOW); @@ -1121,4 +1111,4 @@ void loop() } // if (didWake) LOG_DEBUG("wake!\n"); } -#endif \ No newline at end of file +#endif From 058e9769d602d8919fcd1a02153e7231e939c066 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Tue, 20 Aug 2024 23:19:29 +1200 Subject: [PATCH 0919/1377] Add heartbeat LED for HT-VME290 and HT-VME213 (#4511) * Add heartbeat LED for HT-VME290 and HT-VME213 Not populated on original board, however revisions are now shipping which do have the LED * Update outdated commenting * Trunk strikes again --- variants/heltec_vision_master_e213/pins_arduino.h | 4 ++-- variants/heltec_vision_master_e213/variant.h | 1 + variants/heltec_vision_master_e290/pins_arduino.h | 4 ++-- variants/heltec_vision_master_e290/variant.h | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h index ce35348fd..56f5ef157 100644 --- a/variants/heltec_vision_master_e213/pins_arduino.h +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -3,8 +3,8 @@ #include -static const uint8_t LED_BUILTIN = -1; // Board has no built-in LED, despite what schematic shows -#define BUILTIN_LED LED_BUILTIN // backward compatibility +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility #define LED_BUILTIN LED_BUILTIN static const uint8_t TX = 43; diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index 0771b3517..386df6fcf 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -1,3 +1,4 @@ +#define LED_PIN 45 // LED is not populated on earliest board variant #define BUTTON_PIN 0 #define BUTTON_PIN_SECONDARY 21 // Second built-in button #define BUTTON_SECONDARY_CANNEDMESSAGES // By default, use the secondary button as canned message input diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h index 77cf3176a..56f5ef157 100644 --- a/variants/heltec_vision_master_e290/pins_arduino.h +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -3,8 +3,8 @@ #include -static const uint8_t LED_BUILTIN = -1; -#define BUILTIN_LED LED_BUILTIN // backward compatibility +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility #define LED_BUILTIN LED_BUILTIN static const uint8_t TX = 43; diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/heltec_vision_master_e290/variant.h index 72a82cfdb..299186549 100644 --- a/variants/heltec_vision_master_e290/variant.h +++ b/variants/heltec_vision_master_e290/variant.h @@ -1,3 +1,4 @@ +#define LED_PIN 45 // LED is not populated on earliest board variant #define BUTTON_PIN 0 #define BUTTON_PIN_SECONDARY 21 // Second built-in button #define BUTTON_SECONDARY_CANNEDMESSAGES // By default, use the secondary button as canned message input From 2472c7cdc73dc2c763d8fed6f970b2ac464d417c Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 20 Aug 2024 19:20:01 +0800 Subject: [PATCH 0920/1377] JP frequency - 20mW limit, change freqs to avoid duty cycle (#4446) Thanks to user Goyath on Discord, we discovered that in Japan the 250mW radio level requires licensing, and 20mW is the practical limit. We also discovered that a duty cycle of 10% is needed on most frequencies. CH 24-38 920.5-923.5 20mW no airtime restrictions CH 39-61 923.5-928.1 20mW 10% airtime --- src/mesh/RadioInterface.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index f0048dd3d..eacd49644 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -53,8 +53,10 @@ const RegionInfo regions[] = { /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf + https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf + https://qiita.com/ammo0613/items/d952154f1195b64dc29f */ - RDEF(JP, 920.8f, 927.8f, 100, 0, 16, true, false, false), + RDEF(JP, 920.5f, 923.5f, 100, 0, 13, true, false, false), /* https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf @@ -615,4 +617,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} \ No newline at end of file +} From 2043ad3bd00dbd57e6ae7dea739a3cad3c892c00 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Tue, 20 Aug 2024 13:38:16 +0200 Subject: [PATCH 0921/1377] bin: remove unused imports from readprops.py (#3907) Co-authored-by: Ben Meadors --- bin/readprops.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/bin/readprops.py b/bin/readprops.py index ffa361541..4b730658a 100644 --- a/bin/readprops.py +++ b/bin/readprops.py @@ -1,9 +1,5 @@ - - -import subprocess import configparser -import traceback -import sys +import subprocess def readProps(prefsLoc): @@ -11,27 +7,36 @@ def readProps(prefsLoc): config = configparser.RawConfigParser() config.read(prefsLoc) - version = dict(config.items('VERSION')) - verObj = dict(short = "{}.{}.{}".format(version["major"], version["minor"], version["build"]), - long = "unset") + version = dict(config.items("VERSION")) + verObj = dict( + short="{}.{}.{}".format(version["major"], version["minor"], version["build"]), + long="unset", + ) # Try to find current build SHA if if the workspace is clean. This could fail if git is not installed try: - sha = subprocess.check_output( - ['git', 'rev-parse', '--short', 'HEAD']).decode("utf-8").strip() - isDirty = subprocess.check_output( - ['git', 'diff', 'HEAD']).decode("utf-8").strip() + sha = ( + subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]) + .decode("utf-8") + .strip() + ) + isDirty = ( + subprocess.check_output(["git", "diff", "HEAD"]).decode("utf-8").strip() + ) suffix = sha # if isDirty: # # short for 'dirty', we want to keep our verstrings source for protobuf reasons # suffix = sha + "-d" - verObj['long'] = "{}.{}.{}.{}".format( - version["major"], version["minor"], version["build"], suffix) + verObj["long"] = "{}.{}.{}.{}".format( + version["major"], version["minor"], version["build"], suffix + ) except: # print("Unexpected error:", sys.exc_info()[0]) # traceback.print_exc() - verObj['long'] = verObj['short'] + verObj["long"] = verObj["short"] # print("firmware version " + verStr) return verObj + + # print("path is" + ','.join(sys.path)) From 929b3e4f8809b5f9707465abd9402125a86ef4ea Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 20 Aug 2024 06:49:54 -0500 Subject: [PATCH 0922/1377] Add platformio tests --- .github/workflows/test_simulator.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/test_simulator.yml b/.github/workflows/test_simulator.yml index 9dbcf0554..ec0171a27 100644 --- a/.github/workflows/test_simulator.yml +++ b/.github/workflows/test_simulator.yml @@ -44,3 +44,14 @@ jobs: .pio/build/native/program & sleep 10 # 5 seconds was not enough echo "Simulator started, launching python test..." python3 -c 'from meshtastic.test import testSimulator; testSimulator()' + + - name: PlatformIO Tests + run: platformio test -e native --junit-output-path reports/testreport.xml + + - name: Test Report + uses: dorny/test-reporter@v1.9.1 + if: success() || failure() # run this step even if previous step failed + with: + name: PlatformIO Tests + path: reports/testreport.xml + reporter: java-junit From ee9e46ec929d8ae3bf368315f2b8aeff94daef3b Mon Sep 17 00:00:00 2001 From: Nestpebble <116762865+Nestpebble@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:54:18 +0100 Subject: [PATCH 0923/1377] Make it possible to define TCXO and XTAL radio modules within one variant (#4492) * Update main.cpp Add in TCXO_OPTIONAL variable for tcxoVoltage and a double-check for working in both modes. * Update SX126xInterface.cpp Make a change to the tcxoVoltage setting so that TCXO_OPTIONAL works if defined. * Update variant.h Added define for TCXO_OPTIONAL and the tcxoVoltage variable. Added detail on the compatible boards. --------- Co-authored-by: Ben Meadors --- src/main.cpp | 40 ++++++++++++++++++- src/mesh/SX126xInterface.cpp | 4 +- .../diy/nrf52_promicro_diy_tcxo/variant.h | 31 ++++++++------ 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f4fb24fa9..48dec89e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -112,6 +112,10 @@ AccelerometerThread *accelerometerThread = nullptr; AudioThread *audioThread = nullptr; #endif +#if defined(TCXO_OPTIONAL) +float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down. +#endif + using namespace concurrency; // We always create a screen object, but we only init it if we find the hardware @@ -890,7 +894,7 @@ void setup() } #endif -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) if (!rIf) { rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { @@ -904,6 +908,40 @@ void setup() } #endif +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) + if (!rIf) { + // Try using the specified TCXO voltage + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("Failed to find SX1262 radio with TCXO using DIO3 reference voltage at %f V\n", tcxoVoltage); + delete rIf; + rIf = NULL; + tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt + } else { + LOG_INFO("SX1262 Radio init succeeded, using "); + LOG_WARN("SX1262 Radio with TCXO"); + LOG_INFO(", reference voltage at %f V\n", tcxoVoltage); + radioType = SX1262_RADIO; + } + } + + if (!rIf) { + // If specified TCXO voltage fails, attempt to use DIO3 as a reference instea + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("Failed to find SX1262 radio with XTAL using DIO3 reference voltage at %f V\n", tcxoVoltage); + delete rIf; + rIf = NULL; + tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search + } else { + LOG_INFO("SX1262 Radio init succeeded, using "); + LOG_WARN("SX1262 Radio with XTAL"); + LOG_INFO(", reference voltage at %f V\n", tcxoVoltage); + radioType = SX1262_RADIO; + } + } +#endif + #if defined(USE_SX1268) if (!rIf) { rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index b564ba287..39ffb0ac9 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -40,7 +40,7 @@ template bool SX126xInterface::init() 0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104 // (DIO3 is free to be used as an IRQ) -#else +#elif !defined(TCXO_OPTIONAL) float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // (DIO3 is not free to be used as an IRQ) #endif @@ -345,4 +345,4 @@ template bool SX126xInterface::sleep() #endif return true; -} \ No newline at end of file +} diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/diy/nrf52_promicro_diy_tcxo/variant.h index b09d3bdb4..2e506d055 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.h @@ -128,22 +128,29 @@ NRF52 PRO MICRO PIN ASSIGNMENT #define SX126X_RXEN (0 + 17) // P0.17 #define SX126X_TXEN RADIOLIB_NC // Assuming that DIO2 is connected to TXEN pin. If not, TXEN must be connected. +// #define SX126X_MAX_POWER 8 set this if using a high-power board! + /* -On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, then this should not be used. +On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, use TCXO_OPTIONAL to try both settings. -Ebyte -e22-900mm22s has no TCXO -e22-900m22s has TCXO -e220-900mm22s has no TCXO, works with/without this definition, looks like DIO3 not connected at all - -AI-thinker -RA-01SH does not have TCXO - -Waveshare -Core1262 has TCXO +| Mfr | Module | TCXO | RF Switch | Notes | +| ---------- | ---------------- | ---- | --------- | -------------------------------------------- | +| Ebyte | E22-900M22S | Yes | Ext | | +| Ebyte | E22-900MM22S | No | Ext | | +| Ebyte | E22-900M30S | Yes | Ext | | +| Ebyte | E22-900M33S | Yes | Ext | MAX_POWER must be set to 8 for this | +| Ebyte | E220-900M22S | No | Ext | LLCC68, looks like DIO3 not connected at all | +| AI-Thinker | RA-01SH | No | Int | | +| Heltec | HT-RA62 | Yes | Int | | +| NiceRF | Lora1262 | yes | Int | | +| Waveshare | Core1262-HF | yes | Ext | | +| Waveshare | LoRa Node Module | yes | Int | | */ + #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL +extern float tcxoVoltage; // make this available everywhere #ifdef __cplusplus } @@ -153,4 +160,4 @@ Core1262 has TCXO * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif From d404a493362df9325d1dc1a1d6a79deadad2867c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 20 Aug 2024 07:08:42 -0500 Subject: [PATCH 0924/1377] Trunk --- src/main.cpp | 48 +++++++++---------- .../diy/nrf52_promicro_diy_tcxo/variant.h | 5 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 48dec89e7..d38b4e669 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -911,34 +911,34 @@ void setup() #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) if (!rIf) { // Try using the specified TCXO voltage - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("Failed to find SX1262 radio with TCXO using DIO3 reference voltage at %f V\n", tcxoVoltage); - delete rIf; - rIf = NULL; - tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt - } else { - LOG_INFO("SX1262 Radio init succeeded, using "); - LOG_WARN("SX1262 Radio with TCXO"); - LOG_INFO(", reference voltage at %f V\n", tcxoVoltage); - radioType = SX1262_RADIO; - } + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("Failed to find SX1262 radio with TCXO using DIO3 reference voltage at %f V\n", tcxoVoltage); + delete rIf; + rIf = NULL; + tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt + } else { + LOG_INFO("SX1262 Radio init succeeded, using "); + LOG_WARN("SX1262 Radio with TCXO"); + LOG_INFO(", reference voltage at %f V\n", tcxoVoltage); + radioType = SX1262_RADIO; + } } if (!rIf) { // If specified TCXO voltage fails, attempt to use DIO3 as a reference instea - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("Failed to find SX1262 radio with XTAL using DIO3 reference voltage at %f V\n", tcxoVoltage); - delete rIf; - rIf = NULL; - tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search - } else { - LOG_INFO("SX1262 Radio init succeeded, using "); - LOG_WARN("SX1262 Radio with XTAL"); - LOG_INFO(", reference voltage at %f V\n", tcxoVoltage); - radioType = SX1262_RADIO; - } + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("Failed to find SX1262 radio with XTAL using DIO3 reference voltage at %f V\n", tcxoVoltage); + delete rIf; + rIf = NULL; + tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search + } else { + LOG_INFO("SX1262 Radio init succeeded, using "); + LOG_WARN("SX1262 Radio with XTAL"); + LOG_INFO(", reference voltage at %f V\n", tcxoVoltage); + radioType = SX1262_RADIO; + } } #endif diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/diy/nrf52_promicro_diy_tcxo/variant.h index 2e506d055..05d4a088c 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/diy/nrf52_promicro_diy_tcxo/variant.h @@ -131,7 +131,8 @@ NRF52 PRO MICRO PIN ASSIGNMENT // #define SX126X_MAX_POWER 8 set this if using a high-power board! /* -On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, use TCXO_OPTIONAL to try both settings. +On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, use TCXO_OPTIONAL to try both +settings. | Mfr | Module | TCXO | RF Switch | Notes | | ---------- | ---------------- | ---- | --------- | -------------------------------------------- | @@ -149,7 +150,7 @@ On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If */ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL extern float tcxoVoltage; // make this available everywhere #ifdef __cplusplus From 2d9126f87378fc08a6c048e63d692a932a370e03 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 20 Aug 2024 07:17:39 -0500 Subject: [PATCH 0925/1377] Try cwd --- .github/workflows/test_simulator.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_simulator.yml b/.github/workflows/test_simulator.yml index ec0171a27..1d20657a0 100644 --- a/.github/workflows/test_simulator.yml +++ b/.github/workflows/test_simulator.yml @@ -46,12 +46,12 @@ jobs: python3 -c 'from meshtastic.test import testSimulator; testSimulator()' - name: PlatformIO Tests - run: platformio test -e native --junit-output-path reports/testreport.xml + run: platformio test -e native --junit-output-path testreport.xml - name: Test Report uses: dorny/test-reporter@v1.9.1 if: success() || failure() # run this step even if previous step failed with: name: PlatformIO Tests - path: reports/testreport.xml + path: testreport.xml reporter: java-junit From 314009a10fa0d0afa4480ff9c0068901a1560c1f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 20 Aug 2024 07:35:47 -0500 Subject: [PATCH 0926/1377] Version 2.5 bump --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 0450e7054..95d3d2538 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 -minor = 4 -build = 3 +minor = 5 +build = 0 From 6ee30043c31fe7353a53c8cb35dd8fbaec8866ed Mon Sep 17 00:00:00 2001 From: Mictronics Date: Tue, 20 Aug 2024 20:36:10 +0200 Subject: [PATCH 0927/1377] Fix array out of bounds read. (#4514) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. * Removed duplicate code. * Fix error: #if with no expression * Fix warning: extra tokens at end of #endif directive. * Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board. * Fix deprecated macros. * Set RP2040 in dormant mode when deep sleep is triggered. * Fix array out of bounds read. --------- Co-authored-by: Ben Meadors --- src/modules/AdminModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 81d595d29..9d3d5f53f 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -350,7 +350,7 @@ void AdminModule::handleGetModuleConfigResponse(const meshtastic_MeshPacket &mp, if (devicestate.node_remote_hardware_pins[i].node_num == 0 || !devicestate.node_remote_hardware_pins[i].has_pin) { continue; } - for (uint8_t j = 0; j < sizeof(r->get_module_config_response.payload_variant.remote_hardware.available_pins); j++) { + for (uint8_t j = 0; j < r->get_module_config_response.payload_variant.remote_hardware.available_pins_count; j++) { auto availablePin = r->get_module_config_response.payload_variant.remote_hardware.available_pins[j]; if (i < devicestate.node_remote_hardware_pins_count) { devicestate.node_remote_hardware_pins[i].node_num = mp.from; From ab7de7f6a0be5e5130801ffadfd2166d734aaa6d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 20 Aug 2024 13:36:24 -0500 Subject: [PATCH 0928/1377] Add handling for sessionkey config (#4513) * Add handling for sessionkey config * Protos --------- Co-authored-by: Ben Meadors --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 8 +++++--- src/mesh/generated/meshtastic/config.pb.cpp | 3 +++ src/mesh/generated/meshtastic/config.pb.h | 22 ++++++++++++++++++++- src/modules/AdminModule.cpp | 4 ++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index 4eb4f4251..56a435507 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4eb4f425170f08839abc6ececd13e8db30094ad5 +Subproject commit 56a4355070f3371213d48f3a8cac1ddaf0d553fe diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 99b8fd8c3..c1ff7ebd4 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -32,7 +32,9 @@ typedef enum _meshtastic_AdminMessage_ConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG = 6, /* TODO: REPLACE */ - meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG = 7 + meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG = 7, + /* */ + meshtastic_AdminMessage_ConfigType_SESSIONKEY_CONFIG = 8 } meshtastic_AdminMessage_ConfigType; /* TODO: REPLACE */ @@ -204,8 +206,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG -#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG -#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_SECURITY_CONFIG+1)) +#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_SESSIONKEY_CONFIG +#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_SESSIONKEY_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG #define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index c6274aed4..92c3313bd 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -36,6 +36,9 @@ PB_BIND(meshtastic_Config_BluetoothConfig, meshtastic_Config_BluetoothConfig, AU PB_BIND(meshtastic_Config_SecurityConfig, meshtastic_Config_SecurityConfig, AUTO) +PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig, AUTO) + + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index dbb0deb00..2f4c00fb0 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -554,6 +554,11 @@ typedef struct _meshtastic_Config_SecurityConfig { bool admin_channel_enabled; } meshtastic_Config_SecurityConfig; +/* Blank config request, strictly for getting the session key */ +typedef struct _meshtastic_Config_SessionkeyConfig { + char dummy_field; +} meshtastic_Config_SessionkeyConfig; + typedef struct _meshtastic_Config { pb_size_t which_payload_variant; union { @@ -565,6 +570,7 @@ typedef struct _meshtastic_Config { meshtastic_Config_LoRaConfig lora; meshtastic_Config_BluetoothConfig bluetooth; meshtastic_Config_SecurityConfig security; + meshtastic_Config_SessionkeyConfig sessionkey; } payload_variant; } meshtastic_Config; @@ -649,6 +655,7 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -660,6 +667,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_Config_SessionkeyConfig_init_default {0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} @@ -670,6 +678,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_Config_SessionkeyConfig_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Config_DeviceConfig_role_tag 1 @@ -766,6 +775,7 @@ extern "C" { #define meshtastic_Config_lora_tag 6 #define meshtastic_Config_bluetooth_tag 7 #define meshtastic_Config_security_tag 8 +#define meshtastic_Config_sessionkey_tag 9 /* Struct field encoding specification for nanopb */ #define meshtastic_Config_FIELDLIST(X, a) \ @@ -776,7 +786,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,network,payload_variant.netw X(a, STATIC, ONEOF, MESSAGE, (payload_variant,display,payload_variant.display), 5) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lora,payload_variant.lora), 6) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bluetooth), 7) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,security,payload_variant.security), 8) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,security,payload_variant.security), 8) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sessionkey,payload_variant.sessionkey), 9) #define meshtastic_Config_CALLBACK NULL #define meshtastic_Config_DEFAULT NULL #define meshtastic_Config_payload_variant_device_MSGTYPE meshtastic_Config_DeviceConfig @@ -787,6 +798,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,security,payload_variant.sec #define meshtastic_Config_payload_variant_lora_MSGTYPE meshtastic_Config_LoRaConfig #define meshtastic_Config_payload_variant_bluetooth_MSGTYPE meshtastic_Config_BluetoothConfig #define meshtastic_Config_payload_variant_security_MSGTYPE meshtastic_Config_SecurityConfig +#define meshtastic_Config_payload_variant_sessionkey_MSGTYPE meshtastic_Config_SessionkeyConfig #define meshtastic_Config_DeviceConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, role, 1) \ @@ -911,6 +923,11 @@ X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8) #define meshtastic_Config_SecurityConfig_CALLBACK NULL #define meshtastic_Config_SecurityConfig_DEFAULT NULL +#define meshtastic_Config_SessionkeyConfig_FIELDLIST(X, a) \ + +#define meshtastic_Config_SessionkeyConfig_CALLBACK NULL +#define meshtastic_Config_SessionkeyConfig_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_Config_msg; extern const pb_msgdesc_t meshtastic_Config_DeviceConfig_msg; extern const pb_msgdesc_t meshtastic_Config_PositionConfig_msg; @@ -921,6 +938,7 @@ extern const pb_msgdesc_t meshtastic_Config_DisplayConfig_msg; extern const pb_msgdesc_t meshtastic_Config_LoRaConfig_msg; extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; extern const pb_msgdesc_t meshtastic_Config_SecurityConfig_msg; +extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Config_fields &meshtastic_Config_msg @@ -933,6 +951,7 @@ extern const pb_msgdesc_t meshtastic_Config_SecurityConfig_msg; #define meshtastic_Config_LoRaConfig_fields &meshtastic_Config_LoRaConfig_msg #define meshtastic_Config_BluetoothConfig_fields &meshtastic_Config_BluetoothConfig_msg #define meshtastic_Config_SecurityConfig_fields &meshtastic_Config_SecurityConfig_msg +#define meshtastic_Config_SessionkeyConfig_fields &meshtastic_Config_SessionkeyConfig_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size @@ -945,6 +964,7 @@ extern const pb_msgdesc_t meshtastic_Config_SecurityConfig_msg; #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 #define meshtastic_Config_SecurityConfig_size 112 +#define meshtastic_Config_SessionkeyConfig_size 0 #define meshtastic_Config_size 199 #ifdef __cplusplus diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 9d3d5f53f..d64aea5d8 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -699,6 +699,10 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 res.get_config_response.which_payload_variant = meshtastic_Config_security_tag; res.get_config_response.payload_variant.security = config.security; break; + case meshtastic_AdminMessage_ConfigType_SESSIONKEY_CONFIG: + LOG_INFO("Getting config: Sessionkey\n"); + res.get_config_response.which_payload_variant = meshtastic_Config_sessionkey_tag; + break; } // NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior. // So even if we internally use 0 to represent 'use default' we still need to send the value we are From 9014058935053416cdcfaf6133a8ad30f9510950 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Tue, 20 Aug 2024 13:09:39 -0700 Subject: [PATCH 0929/1377] add CLA admonition --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4cdd3bcb..ddcec12ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,9 @@ We encourage open communication and discussion before diving into code changes: ## Making Contributions +> [!IMPORTANT] "Sign our CLA agreement" +> Before making any contributions, you must sign our Contributor License Agreement (CLA). You can do this by visiting https://cla-assistant.io/meshtastic/firmware. Be sure to use the GitHub account you will use to submit your contributions when signing. + 1. Fork the repository 2. Create a new branch for your feature or bug fix 3. Make your changes From ba771ae50707f1cc2327f54680504a66c42868a6 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Tue, 20 Aug 2024 13:11:03 -0700 Subject: [PATCH 0930/1377] fix --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddcec12ba..736e2ba5b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ We encourage open communication and discussion before diving into code changes: ## Making Contributions -> [!IMPORTANT] "Sign our CLA agreement" +> [!IMPORTANT] > Before making any contributions, you must sign our Contributor License Agreement (CLA). You can do this by visiting https://cla-assistant.io/meshtastic/firmware. Be sure to use the GitHub account you will use to submit your contributions when signing. 1. Fork the repository From 48e0fd7ed03405de78020b5c773b5306a3918226 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 20 Aug 2024 15:38:39 -0700 Subject: [PATCH 0931/1377] fix #4448 (by seeing there is actually no problem) (#4517) Print directory names when listing directories --- src/FSCommon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index f45319c0b..d6a542808 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -249,6 +249,7 @@ void listDir(const char *dirname, uint8_t levels, bool del) file.close(); } #else + LOG_DEBUG(" %s (directory)\n", file.name()); listDir(file.name(), levels - 1, del); file.close(); #endif @@ -275,7 +276,7 @@ void listDir(const char *dirname, uint8_t levels, bool del) file.close(); } #else - LOG_DEBUG(" %s (%i Bytes)\n", file.name(), file.size()); + LOG_DEBUG(" %s (%i Bytes)\n", file.name(), file.size()); file.close(); #endif } From d556ae762c208cc0e30cc410b6ed761bf439949c Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Wed, 21 Aug 2024 13:04:03 +0200 Subject: [PATCH 0932/1377] Update ScanI2CTwoWire.cpp (#4520) --- src/detect/ScanI2CTwoWire.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index b831b0e71..1183d0ddc 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -315,7 +315,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case SHT31_4x_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2 || registerValue == 0x11da) { + if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) { type = SHT4X; LOG_INFO("SHT4X sensor found\n"); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { @@ -404,4 +404,4 @@ size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); } -#endif \ No newline at end of file +#endif From 6ddee795d6c8742e317759ce330a8a91bd37b8ff Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 21 Aug 2024 17:24:56 -0500 Subject: [PATCH 0933/1377] Poetry --- .github/actions/setup-base/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 61466655d..929c1df38 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -35,6 +35,7 @@ runs: python -m pip install --upgrade pip pip install -U --no-build-isolation --no-cache-dir "setuptools<72" pip install -U platformio adafruit-nrfutil --no-build-isolation + pip install -U poetry --no-build-isolation pip install -U meshtastic --pre --no-build-isolation - name: Upgrade platformio From d017fc7a5d392eb35b197c104a33ab2c533bc6ea Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 21 Aug 2024 16:53:12 -0700 Subject: [PATCH 0934/1377] for #4154 use internal pull-ups to power ADC_Ctrl * Currently only on heltec tracker, but could use ADC_USE_PULLUP on other boards that could benefit * Thanks @todd-herbert and @StevenCellist for the instructions ;-) * Remove nasty Heltec_wireless #ifdefs that got somehow added to Power.cpp, instead use proper variant defs * Cleanup adc enable/disable code a bit for less copy-paste cruft --- src/Power.cpp | 60 ++++++++++----------- variants/heltec_wireless_paper/variant.h | 3 +- variants/heltec_wireless_paper_v1/variant.h | 3 +- variants/heltec_wireless_tracker/variant.h | 6 +-- 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index d63c43137..61a6c987d 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -136,6 +136,30 @@ using namespace meshtastic; */ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor +static void adcEnable() +{ +#ifdef ADC_CTRL // enable adc voltage divider when we need to read +#ifdef ADC_USE_PULLUP + pinMode(ADC_CTRL, INPUT_PULLUP); +#else + pinMode(ADC_CTRL, OUTPUT); + digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); +#endif + delay(10); +#endif +} + +static void adcDisable() +{ +#ifdef ADC_CTRL // disable adc voltage divider when we need to read +#ifdef ADC_USE_PULLUP + pinMode(ADC_CTRL, INPUT_PULLDOWN); +#else + digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); +#endif +#endif +} + /** * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input */ @@ -226,25 +250,19 @@ class AnalogBatteryLevel : public HasBatteryLevel uint32_t raw = 0; float scaled = 0; + adcEnable(); #ifdef ARCH_ESP32 // ADC block for espressif platforms raw = espAdcRead(); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled *= operativeAdcMultiplier; -#else // block for all other platforms -#ifdef ADC_CTRL // enable adc voltage divider when we need to read - pinMode(ADC_CTRL, OUTPUT); - digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); - delay(10); -#endif +#else // block for all other platforms for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) { raw += analogRead(BATTERY_PIN); } raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; -#ifdef ADC_CTRL // disable adc voltage divider when we need to read - digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); -#endif #endif + adcDisable(); if (!initial_read_done) { // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct @@ -275,11 +293,6 @@ class AnalogBatteryLevel : public HasBatteryLevel uint8_t raw_c = 0; // raw reading counter #ifndef BAT_MEASURE_ADC_UNIT // ADC1 -#ifdef ADC_CTRL // enable adc voltage divider when we need to read - pinMode(ADC_CTRL, OUTPUT); - digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED); - delay(10); -#endif for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { int val_ = adc1_get_raw(adc_channel); if (val_ >= 0) { // save only valid readings @@ -288,18 +301,7 @@ class AnalogBatteryLevel : public HasBatteryLevel } // delayMicroseconds(100); } -#ifdef ADC_CTRL // disable adc voltage divider when we need to read - digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED); -#endif -#else // ADC2 -#ifdef ADC_CTRL -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) - pinMode(ADC_CTRL, OUTPUT); - digitalWrite(ADC_CTRL, LOW); // ACTIVE LOW - delay(10); -#endif -#endif // End ADC_CTRL - +#else // ADC2 #ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 // ADC2 wifi bug workaround not required, breaks compile // On ESP32S3, ADC2 can take turns with Wifi (?) @@ -334,12 +336,6 @@ class AnalogBatteryLevel : public HasBatteryLevel } #endif // BAT_MEASURE_ADC_UNIT -#ifdef ADC_CTRL -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) - digitalWrite(ADC_CTRL, HIGH); -#endif -#endif // End ADC_CTRL - #endif // End BAT_MEASURE_ADC_UNIT return (raw / (raw_c < 1 ? 1 : raw_c)); } diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index a7bd460f7..36ab80445 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -29,6 +29,7 @@ #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high #define HAS_32768HZ +#define ADC_CTRL_ENABLED LOW // LoRa #define USE_SX1262 @@ -49,4 +50,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index a7bd460f7..36ab80445 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -29,6 +29,7 @@ #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high #define HAS_32768HZ +#define ADC_CTRL_ENABLED LOW // LoRa #define USE_SX1262 @@ -49,4 +50,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index 685c9f079..a2ca095d8 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -38,8 +38,8 @@ #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 -#define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 -#define ADC_CTRL_ENABLED HIGH +#define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 +#define ADC_USE_PULLUP // Use internal pullup/pulldown instead of actively driving the output #undef GPS_RX_PIN #undef GPS_TX_PIN @@ -72,4 +72,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file From 1e655052fc66be1876d2d4e87b5a99da407f08e6 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 22 Aug 2024 14:00:19 +0200 Subject: [PATCH 0935/1377] Fixes for ME25LS01_4Y10TD and ESP32-PICO (#4522) * Update platformio.ini * Update variant.h * Update architecture.h * Update variant.h --- src/platform/nrf52/architecture.h | 4 ++-- variants/ME25LS01-4Y10TD_e-ink/variant.h | 4 ++-- variants/esp32-s3-pico/platformio.ini | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 895525f5a..834ff6f0c 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -65,8 +65,8 @@ #define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110 #elif defined(TRACKER_T1000_E) #define HW_VENDOR meshtastic_HardwareModel_TRACKER_T1000_E -#elif defined(ME25LS01) -#define HW_VENDOR meshtastic_HardwareModel_ME25LS01 +#elif defined(ME25LS01_4Y10TD) +#define HW_VENDOR meshtastic_HardwareModel_ME25LS01_4Y10TD #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/ME25LS01-4Y10TD_e-ink/variant.h index 9006d5a63..60996d468 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.h @@ -110,8 +110,8 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_BUSY (0 + 19) // EPD_BUSY #define PIN_EINK_DC (0 + 24) // EPD_D/C #define PIN_EINK_RES (0 + 23) // EPD_RESET -#define PIN_EINK_SCLK (0 + 9) // EPD_SCLK -#define PIN_EINK_MOSI (0 + 10) // EPD_MOSI +#define PIN_EINK_SCLK PIN_SPI1_SCK +#define PIN_EINK_MOSI PIN_SPI1_MOSI // supported modules list #define USE_LR1110 diff --git a/variants/esp32-s3-pico/platformio.ini b/variants/esp32-s3-pico/platformio.ini index ff77c30e0..916f623bd 100644 --- a/variants/esp32-s3-pico/platformio.ini +++ b/variants/esp32-s3-pico/platformio.ini @@ -11,7 +11,7 @@ board_upload.require_upload_port = yes ;upload_port = /dev/ttyACM0 -build_flags = ${esp32_base.build_flags} +build_flags = ${esp32s3_base.build_flags} -DESP32_S3_PICO ;-DPRIVATE_HW -Ivariants/esp32-s3-pico @@ -22,4 +22,4 @@ build_flags = ${esp32_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} zinggjm/GxEPD2@^1.5.3 - adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file + adafruit/Adafruit NeoPixel @ ^1.12.0 From 734f36589dd54f60e9a07e3dbbb368716642235d Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 22 Aug 2024 17:44:49 +0200 Subject: [PATCH 0936/1377] Update variant.h (#4534) --- variants/wio-e5/variant.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/variants/wio-e5/variant.h b/variants/wio-e5/variant.h index b4345a530..ac92915bb 100644 --- a/variants/wio-e5/variant.h +++ b/variants/wio-e5/variant.h @@ -15,4 +15,7 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx #define MAX_NUM_NODES 10 -#endif \ No newline at end of file +#define LED_PIN PB5 +#define LED_STATE_ON 1 + +#endif From 7fb9b094d522fefb41c8e13ee8de8cf526b8b227 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 08:59:46 -0700 Subject: [PATCH 0937/1377] Remove redundant ST7735_BL variant defs. No need for _V05 and _V03 definitions - I think there was a slight misunderstanding on how variant files are supposed to _decrease_ #ifdef code in the cpp files. --- src/graphics/TFTDisplay.cpp | 32 ++++++------------- src/main.cpp | 6 +--- src/sleep.cpp | 1 - variants/heltec_wireless_tracker/variant.h | 2 +- .../heltec_wireless_tracker_V1_0/variant.h | 4 +-- variants/tracksenger/internal/variant.h | 2 +- variants/tracksenger/lcd/variant.h | 4 +-- variants/tracksenger/oled/variant.h | 2 +- 8 files changed, 18 insertions(+), 35 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 8ea90c523..73ad4d130 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -95,14 +95,8 @@ class LGFX : public lgfx::LGFX_Device { auto cfg = _light_instance.config(); // Gets a structure for backlight settings. -#ifdef ST7735_BL_V03 - cfg.pin_bl = ST7735_BL_V03; -#elif defined(ST7735_BL_V05) - cfg.pin_bl = ST7735_BL_V05; -#else cfg.pin_bl = ST7735_BL; // Pin number to which the backlight is connected -#endif - cfg.invert = true; // true to invert the brightness of the backlight + cfg.invert = true; // true to invert the brightness of the backlight // cfg.freq = 44100; // PWM frequency of backlight // cfg.pwm_channel = 1; // PWM channel number to use @@ -581,11 +575,9 @@ void TFTDisplay::sendCommand(uint8_t com) display(true); if (settingsMap[displayBacklight] > 0) digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL_V03) - digitalWrite(ST7735_BL_V03, TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL_V05) - pinMode(ST7735_BL_V05, OUTPUT); - digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); +#elif defined(ST7735_BL) + pinMode(ST7735_BL, OUTPUT); + digitalWrite(ST7735_BL, TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->wakeup(); tft->powerSaveOff(); @@ -614,11 +606,9 @@ void TFTDisplay::sendCommand(uint8_t com) tft->clear(); if (settingsMap[displayBacklight] > 0) digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL_V03) - digitalWrite(ST7735_BL_V03, !TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL_V05) - pinMode(ST7735_BL_V05, OUTPUT); - digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON); +#elif defined(ST7735_BL) + pinMode(ST7735_BL, OUTPUT); + digitalWrite(ST7735_BL, !TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->sleep(); tft->powerSaveOn(); @@ -720,11 +710,9 @@ bool TFTDisplay::connect() LOG_INFO("Power to TFT Backlight\n"); #endif -#ifdef ST7735_BL_V03 - digitalWrite(ST7735_BL_V03, TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL_V05) - pinMode(ST7735_BL_V05, OUTPUT); - digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON); +#ifdef ST7735_BL + pinMode(ST7735_BL, OUTPUT); + digitalWrite(ST7735_BL, TFT_BACKLIGHT_ON); #endif #ifdef UNPHONE unphone.backlight(true); // using unPhone library diff --git a/src/main.cpp b/src/main.cpp index d38b4e669..968ee6053 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -280,15 +280,11 @@ void setup() #if defined(VEXT_ENABLE_V03) pinMode(VEXT_ENABLE_V03, OUTPUT); - pinMode(ST7735_BL_V03, OUTPUT); digitalWrite(VEXT_ENABLE_V03, 0); // turn on the display power and antenna boost - digitalWrite(ST7735_BL_V03, 1); // display backligth on LOG_DEBUG("HELTEC Detect Tracker V1.0\n"); #elif defined(VEXT_ENABLE_V05) pinMode(VEXT_ENABLE_V05, OUTPUT); - pinMode(ST7735_BL_V05, OUTPUT); digitalWrite(VEXT_ENABLE_V05, 1); // turn on the lora antenna boost - digitalWrite(ST7735_BL_V05, 1); // turn on display backligth LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); #elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) pinMode(VEXT_ENABLE, OUTPUT); @@ -1149,4 +1145,4 @@ void loop() } // if (didWake) LOG_DEBUG("wake!\n"); } -#endif +#endif \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index bf50d8ffa..693d02a29 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -250,7 +250,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) digitalWrite(VEXT_ENABLE_V03, 1); // turn off the display power #elif defined(VEXT_ENABLE_V05) digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power - digitalWrite(ST7735_BL_V05, 0); // turn off the display power #elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power #elif defined(VEXT_ENABLE) diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index a2ca095d8..f10612601 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -15,7 +15,7 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +#define ST7735_BL 21 /* V1.1 PCB marking */ #define ST7735_SPI_HOST SPI3_HOST #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 23987adf0..e97219163 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -15,7 +15,7 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL_V03 45 +#define ST7735_BL 45 #define ST7735_SPI_HOST SPI3_HOST #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 @@ -69,4 +69,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/tracksenger/internal/variant.h b/variants/tracksenger/internal/variant.h index 929c38793..75d81752b 100644 --- a/variants/tracksenger/internal/variant.h +++ b/variants/tracksenger/internal/variant.h @@ -17,7 +17,7 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +#define ST7735_BL 21 /* V1.1 PCB marking */ #define ST7735_SPI_HOST SPI3_HOST #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 diff --git a/variants/tracksenger/lcd/variant.h b/variants/tracksenger/lcd/variant.h index 3f952361b..7e814de68 100644 --- a/variants/tracksenger/lcd/variant.h +++ b/variants/tracksenger/lcd/variant.h @@ -16,7 +16,7 @@ #define ST7789_CS 38 #define ST7789_RS 40 #define ST7789_BL 21 -// P#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +// P#define ST7735_BL 21 /* V1.1 PCB marking */ #define ST7789_RESET -1 #define ST7789_MISO -1 @@ -41,7 +41,7 @@ // #define ST7735_RESET 39 // #define ST7735_MISO -1 // #define ST7735_BUSY -1 -#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +#define ST7735_BL 21 /* V1.1 PCB marking */ // #define ST7735_SPI_HOST SPI3_HOST // #define SPI_FREQUENCY 40000000 // #define SPI_READ_FREQUENCY 16000000 diff --git a/variants/tracksenger/oled/variant.h b/variants/tracksenger/oled/variant.h index 99f12bd23..10bd7358b 100644 --- a/variants/tracksenger/oled/variant.h +++ b/variants/tracksenger/oled/variant.h @@ -19,7 +19,7 @@ // #define ST7735_RESET 39 // #define ST7735_MISO -1 // #define ST7735_BUSY -1 -#define ST7735_BL_V05 21 /* V1.1 PCB marking */ +#define ST7735_BL 21 /* V1.1 PCB marking */ // #define ST7735_SPI_HOST SPI3_HOST // #define SPI_FREQUENCY 40000000 // #define SPI_READ_FREQUENCY 16000000 From 3ae8aadaf0214d86c20842b3e67eed4bb0056ed1 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 09:15:59 -0700 Subject: [PATCH 0938/1377] Merge the three redundant backlight enables into the single TFT_BL flag --- src/graphics/TFTDisplay.cpp | 30 +++++++------------ variants/chatter2/variant.h | 2 +- variants/heltec_wireless_tracker/variant.h | 2 +- .../heltec_wireless_tracker_V1_0/variant.h | 2 +- variants/lora_relay_v1/variant.h | 4 +-- variants/lora_relay_v2/variant.h | 4 +-- variants/tracksenger/internal/variant.h | 4 +-- variants/tracksenger/lcd/variant.h | 6 ++-- variants/tracksenger/oled/variant.h | 2 +- 9 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 73ad4d130..d20733e90 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -21,10 +21,6 @@ extern SX1509 gpioExtender; #if defined(ST7735S) #include // Graphics and font library for ST7735 driver chip -#if defined(ST7735_BACKLIGHT_EN) && !defined(TFT_BL) -#define TFT_BL ST7735_BACKLIGHT_EN -#endif - #ifndef TFT_INVERT #define TFT_INVERT true #endif @@ -91,18 +87,20 @@ class LGFX : public lgfx::LGFX_Device _panel_instance.config(cfg); } +#ifdef TFT_BL // Set the backlight control { auto cfg = _light_instance.config(); // Gets a structure for backlight settings. - cfg.pin_bl = ST7735_BL; // Pin number to which the backlight is connected - cfg.invert = true; // true to invert the brightness of the backlight + cfg.pin_bl = TFT_BL; // Pin number to which the backlight is connected + cfg.invert = true; // true to invert the brightness of the backlight // cfg.freq = 44100; // PWM frequency of backlight // cfg.pwm_channel = 1; // PWM channel number to use _light_instance.config(cfg); _panel_instance.setLight(&_light_instance); // Set the backlight on the panel. } +#endif setPanel(&_panel_instance); } @@ -575,14 +573,12 @@ void TFTDisplay::sendCommand(uint8_t com) display(true); if (settingsMap[displayBacklight] > 0) digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL) - pinMode(ST7735_BL, OUTPUT); - digitalWrite(ST7735_BL, TFT_BACKLIGHT_ON); +#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) + pinMode(TFT_BL, OUTPUT); + digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->wakeup(); tft->powerSaveOff(); -#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) - digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); #endif #ifdef VTFT_CTRL_V03 @@ -606,14 +602,12 @@ void TFTDisplay::sendCommand(uint8_t com) tft->clear(); if (settingsMap[displayBacklight] > 0) digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON); -#elif defined(ST7735_BL) - pinMode(ST7735_BL, OUTPUT); - digitalWrite(ST7735_BL, !TFT_BACKLIGHT_ON); +#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) + pinMode(TFT_BL, OUTPUT); + digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->sleep(); tft->powerSaveOn(); -#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) - digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); #endif #ifdef VTFT_CTRL_V03 @@ -710,10 +704,6 @@ bool TFTDisplay::connect() LOG_INFO("Power to TFT Backlight\n"); #endif -#ifdef ST7735_BL - pinMode(ST7735_BL, OUTPUT); - digitalWrite(ST7735_BL, TFT_BACKLIGHT_ON); -#endif #ifdef UNPHONE unphone.backlight(true); // using unPhone library LOG_INFO("Power to TFT Backlight\n"); diff --git a/variants/chatter2/variant.h b/variants/chatter2/variant.h index 70438e52a..b7f946970 100644 --- a/variants/chatter2/variant.h +++ b/variants/chatter2/variant.h @@ -54,7 +54,7 @@ #define ST7735_RESET 15 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL 32 +#define TFT_BL 32 #define ST7735_SPI_HOST HSPI_HOST // SPI2_HOST for S3, auto may work too #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index f10612601..519c76d8f 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -15,7 +15,7 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL 21 /* V1.1 PCB marking */ +#define TFT_BL 21 /* V1.1 PCB marking */ #define ST7735_SPI_HOST SPI3_HOST #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index e97219163..b638dec53 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -15,7 +15,7 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL 45 +#define TFT_BL 45 #define ST7735_SPI_HOST SPI3_HOST #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 diff --git a/variants/lora_relay_v1/variant.h b/variants/lora_relay_v1/variant.h index 54bc87b68..6efd711c6 100644 --- a/variants/lora_relay_v1/variant.h +++ b/variants/lora_relay_v1/variant.h @@ -144,7 +144,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define ST7735_RESET (11) // Output #define ST7735_CS (12) -#define ST7735_BACKLIGHT_EN (13) +#define TFT_BL (13) #define ST7735_RS (9) // #define LORA_DISABLE_SENDING // The board can brownout during lora TX if you don't have a battery connected. Disable sending @@ -158,4 +158,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/lora_relay_v2/variant.h b/variants/lora_relay_v2/variant.h index 6ef7ad7d6..f18f81034 100644 --- a/variants/lora_relay_v2/variant.h +++ b/variants/lora_relay_v2/variant.h @@ -166,7 +166,7 @@ static const uint8_t SCK = PIN_SPI_SCK; // ST7565 SPI #define ST7735_RESET (11) // Output #define ST7735_CS (12) -#define ST7735_BACKLIGHT_EN (13) +#define TFT_BL (13) #define ST7735_RS (9) #define ST7735_SDA (39) // actually spi MOSI #define ST7735_SCK (37) // actually spi clk @@ -185,4 +185,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/tracksenger/internal/variant.h b/variants/tracksenger/internal/variant.h index 75d81752b..bd4a51a2d 100644 --- a/variants/tracksenger/internal/variant.h +++ b/variants/tracksenger/internal/variant.h @@ -17,7 +17,7 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL 21 /* V1.1 PCB marking */ +#define TFT_BL 21 /* V1.1 PCB marking */ #define ST7735_SPI_HOST SPI3_HOST #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 @@ -88,4 +88,4 @@ { \ 26, 37, 17, 16, 15, 7 \ } -// #end keyboard +// #end keyboard \ No newline at end of file diff --git a/variants/tracksenger/lcd/variant.h b/variants/tracksenger/lcd/variant.h index 7e814de68..17b012ae7 100644 --- a/variants/tracksenger/lcd/variant.h +++ b/variants/tracksenger/lcd/variant.h @@ -16,7 +16,7 @@ #define ST7789_CS 38 #define ST7789_RS 40 #define ST7789_BL 21 -// P#define ST7735_BL 21 /* V1.1 PCB marking */ +// P#define TFT_BL 21 /* V1.1 PCB marking */ #define ST7789_RESET -1 #define ST7789_MISO -1 @@ -41,7 +41,7 @@ // #define ST7735_RESET 39 // #define ST7735_MISO -1 // #define ST7735_BUSY -1 -#define ST7735_BL 21 /* V1.1 PCB marking */ +#define TFT_BL 21 /* V1.1 PCB marking */ // #define ST7735_SPI_HOST SPI3_HOST // #define SPI_FREQUENCY 40000000 // #define SPI_READ_FREQUENCY 16000000 @@ -112,4 +112,4 @@ { \ 26, 37, 17, 16, 15, 7 \ } -// #end keyboard +// #end keyboard \ No newline at end of file diff --git a/variants/tracksenger/oled/variant.h b/variants/tracksenger/oled/variant.h index 10bd7358b..e6e28e459 100644 --- a/variants/tracksenger/oled/variant.h +++ b/variants/tracksenger/oled/variant.h @@ -19,7 +19,7 @@ // #define ST7735_RESET 39 // #define ST7735_MISO -1 // #define ST7735_BUSY -1 -#define ST7735_BL 21 /* V1.1 PCB marking */ +#define TFT_BL 21 /* V1.1 PCB marking */ // #define ST7735_SPI_HOST SPI3_HOST // #define SPI_FREQUENCY 40000000 // #define SPI_READ_FREQUENCY 16000000 From 5ccb6df142a3136be813f0c0e98f2c0a72b779ca Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 09:28:41 -0700 Subject: [PATCH 0939/1377] Remove all sorts of redundant VEXT_ENABLE ifdefs --- src/graphics/TFTDisplay.cpp | 7 ------- src/main.cpp | 18 +----------------- src/sleep.cpp | 8 +------- variants/heltec_wireless_tracker/variant.h | 3 ++- .../heltec_wireless_tracker_V1_0/variant.h | 5 +++-- variants/tracksenger/internal/variant.h | 3 ++- variants/tracksenger/lcd/variant.h | 3 ++- variants/tracksenger/oled/variant.h | 5 +++-- 8 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index d20733e90..0e203a9d6 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -581,10 +581,6 @@ void TFTDisplay::sendCommand(uint8_t com) tft->powerSaveOff(); #endif -#ifdef VTFT_CTRL_V03 - digitalWrite(VTFT_CTRL_V03, LOW); -#endif - #ifdef VTFT_CTRL digitalWrite(VTFT_CTRL, LOW); #endif @@ -610,9 +606,6 @@ void TFTDisplay::sendCommand(uint8_t com) tft->powerSaveOn(); #endif -#ifdef VTFT_CTRL_V03 - digitalWrite(VTFT_CTRL_V03, HIGH); -#endif #ifdef VTFT_CTRL digitalWrite(VTFT_CTRL, HIGH); #endif diff --git a/src/main.cpp b/src/main.cpp index 968ee6053..7520667dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -278,25 +278,9 @@ void setup() digitalWrite(LORA_TCXO_GPIO, HIGH); #endif -#if defined(VEXT_ENABLE_V03) - pinMode(VEXT_ENABLE_V03, OUTPUT); - digitalWrite(VEXT_ENABLE_V03, 0); // turn on the display power and antenna boost - LOG_DEBUG("HELTEC Detect Tracker V1.0\n"); -#elif defined(VEXT_ENABLE_V05) - pinMode(VEXT_ENABLE_V05, OUTPUT); - digitalWrite(VEXT_ENABLE_V05, 1); // turn on the lora antenna boost - LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); -#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) +#if defined(VEXT_ENABLE) pinMode(VEXT_ENABLE, OUTPUT); digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power -#elif defined(VEXT_ENABLE) - pinMode(VEXT_ENABLE, OUTPUT); - digitalWrite(VEXT_ENABLE, 0); // turn on the display power -#endif - -#if defined(VTFT_CTRL_V03) - pinMode(VTFT_CTRL_V03, OUTPUT); - digitalWrite(VTFT_CTRL_V03, LOW); #endif #if defined(VTFT_CTRL) diff --git a/src/sleep.cpp b/src/sleep.cpp index 693d02a29..27e81ce54 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -246,14 +246,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power #endif -#if defined(VEXT_ENABLE_V03) - digitalWrite(VEXT_ENABLE_V03, 1); // turn off the display power -#elif defined(VEXT_ENABLE_V05) - digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power -#elif defined(VEXT_ENABLE) && defined(VEXT_ON_VALUE) +#if defined(VEXT_ENABLE) digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power -#elif defined(VEXT_ENABLE) - digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif #ifdef ARCH_ESP32 diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index 519c76d8f..46e922eb5 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -31,7 +31,8 @@ // GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR // GPS LNA SW7125DE: VCC(4), pulls up SHDN(5) through 10kR // LED: VDD, LEDA (through diode) -#define VEXT_ENABLE_V05 3 // active HIGH - powers the GPS, GPS LNA and OLED VDD/anode +#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED VDD/anode +#define VEXT_ON_VALUE HIGH #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index b638dec53..36c3dd261 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -24,11 +24,12 @@ #define TFT_WIDTH DISPLAY_HEIGHT #define TFT_OFFSET_X 26 #define TFT_OFFSET_Y -1 -#define VTFT_CTRL_V03 46 // Heltec Tracker needs this pulled low for TFT +#define VTFT_CTRL 46 // Heltec Tracker needs this pulled low for TFT #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS -#define VEXT_ENABLE_V03 Vext // active low, powers the oled display and the lora antenna boost +#define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost +#define VEXT_ON_VALUE LOW #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage diff --git a/variants/tracksenger/internal/variant.h b/variants/tracksenger/internal/variant.h index bd4a51a2d..57ead848d 100644 --- a/variants/tracksenger/internal/variant.h +++ b/variants/tracksenger/internal/variant.h @@ -29,7 +29,8 @@ #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS -#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost +#define VEXT_ENABLE 3 // active HIGH, powers the lora antenna boost +#define VEXT_ON_VALUE HIGH #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage diff --git a/variants/tracksenger/lcd/variant.h b/variants/tracksenger/lcd/variant.h index 17b012ae7..c89bf141c 100644 --- a/variants/tracksenger/lcd/variant.h +++ b/variants/tracksenger/lcd/variant.h @@ -53,7 +53,8 @@ #define SCREEN_TRANSITION_FRAMERATE 3 // fps // #define DISPLAY_FORCE_SMALL_FONTS -#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost +#define VEXT_ENABLE 3 // active HIGH, powers the lora antenna boost +#define VEXT_ON_VALUE HIGH #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage diff --git a/variants/tracksenger/oled/variant.h b/variants/tracksenger/oled/variant.h index e6e28e459..70f0f3209 100644 --- a/variants/tracksenger/oled/variant.h +++ b/variants/tracksenger/oled/variant.h @@ -31,7 +31,8 @@ #define SCREEN_TRANSITION_FRAMERATE 3 // fps // #define DISPLAY_FORCE_SMALL_FONTS -#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost +#define VEXT_ENABLE 3 // active HIGH, powers the lora antenna boost +#define VEXT_ON_VALUE HIGH #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage @@ -90,4 +91,4 @@ { \ 26, 37, 17, 16, 15, 7 \ } -// #end keyboard +// #end keyboard \ No newline at end of file From 2dda640d27c6c1373515f8a6fef895fdee2c669f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 09:33:43 -0700 Subject: [PATCH 0940/1377] Remove unneeded VGNSS_CTRL_V03 --- variants/heltec_wireless_tracker_V1_0/variant.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 36c3dd261..876ff1146 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -44,8 +44,7 @@ #define PIN_GPS_RESET 35 #define PIN_GPS_PPS 36 -#define VGNSS_CTRL_V03 37 // Heltec Tracker needs this pulled low for GPS -#define PIN_GPS_EN VGNSS_CTRL_V03 +#define PIN_GPS_EN 37 // Heltec Tracker needs this pulled low for GPS #define GPS_EN_ACTIVE LOW #define GPS_RESET_MODE LOW From 5570b6bbc6542642735f774b95af56451ca87dda Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 10:15:23 -0700 Subject: [PATCH 0941/1377] change GPS to use virtual GPIOs (for #4154) --- src/GpioLogic.cpp | 6 ++++++ src/GpioLogic.h | 2 +- src/gps/GPS.cpp | 27 +++++++++++++++++---------- src/gps/GPS.h | 11 +++++++++-- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/GpioLogic.cpp b/src/GpioLogic.cpp index d164615a7..c23c40a7f 100644 --- a/src/GpioLogic.cpp +++ b/src/GpioLogic.cpp @@ -10,6 +10,12 @@ void GpioVirtPin::set(bool value) } } +void GpioHwPin::set(bool value) +{ + pinMode(num, OUTPUT); + digitalWrite(num, value); +} + GpioTransformer::GpioTransformer(GpioPin *outPin) : outPin(outPin) {} void GpioTransformer::set(bool value) diff --git a/src/GpioLogic.h b/src/GpioLogic.h index 27e85d55b..c5a3cefa9 100644 --- a/src/GpioLogic.h +++ b/src/GpioLogic.h @@ -29,7 +29,7 @@ class GpioHwPin : public GpioPin public: explicit GpioHwPin(uint32_t num) : num(num) {} - void set(bool value) { digitalWrite(num, value); } + void set(bool value); }; class GpioTransformer; diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 7d7de8700..f7db1367a 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -2,6 +2,7 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "Default.h" #include "GPS.h" +#include "GpioLogic.h" #include "NodeDB.h" #include "PowerMon.h" #include "RTC.h" @@ -875,16 +876,8 @@ void GPS::writePinEN(bool on) if (HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1)) return; - // Abort: if pin unset - if (!en_gpio) - return; - - // Determine new value for the pin - bool val = GPS_EN_ACTIVE ? on : !on; - // Write and log - pinMode(en_gpio, OUTPUT); - digitalWrite(en_gpio, val); + enablePin->set(on); #ifdef GPS_EXTRAVERBOSE LOG_DEBUG("Pin EN %s\n", val == HIGH ? "HIGH" : "LOW"); #endif @@ -1421,7 +1414,21 @@ GPS *GPS::createGps() GPS *new_gps = new GPS; new_gps->rx_gpio = _rx_gpio; new_gps->tx_gpio = _tx_gpio; - new_gps->en_gpio = _en_gpio; + + if (_en_gpio) { + GpioPin *p = new GpioHwPin(_en_gpio); + + if (!GPS_EN_ACTIVE) { // Need to invert the pin before hardware + auto virtPin = new GpioVirtPin(); + new GpioNotTransformer( + virtPin, p); // We just leave this created object on the heap so it can stay watching virtPin and driving en_gpio + p = virtPin; + } + new_gps->enablePin = p; + } else { + // Just use a simulated pin + new_gps->enablePin = new GpioVirtPin(); + } #ifdef PIN_GPS_PPS // pulse per second diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 96171cba5..494bddae8 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -3,6 +3,7 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "GPSStatus.h" +#include "GpioLogic.h" #include "Observer.h" #include "TinyGPS++.h" #include "concurrency/OSThread.h" @@ -73,7 +74,6 @@ class GPS : private concurrency::OSThread uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0; uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; - uint32_t en_gpio = 0; int speedSelect = 0; int probeTries = 2; @@ -152,6 +152,13 @@ class GPS : private concurrency::OSThread meshtastic_Position p = meshtastic_Position_init_default; + /** This is normally bound to config.position.gps_en_gpio but some rare boards (like heltec tracker) need more advanced + * implementations. Those boards will set this public variable to a custom implementation. + * + * Normally set by GPS::createGPS() + */ + GpioPin *enablePin; + GPS() : concurrency::OSThread("GPS") {} virtual ~GPS(); @@ -303,4 +310,4 @@ class GPS : private concurrency::OSThread }; extern GPS *gps; -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file From db6e591c078deb5829922d7f6d2254b00cd6be6d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 10:38:19 -0700 Subject: [PATCH 0942/1377] For #4154 - change TFT driver to use virtual GPIO for backlight enable --- src/graphics/TFTDisplay.cpp | 30 ++++++++++++++++++------------ src/graphics/TFTDisplay.h | 7 +++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 0e203a9d6..21b9c2bd1 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -516,6 +516,21 @@ extern unPhone unphone; TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { LOG_DEBUG("TFTDisplay!\n"); + +#ifdef TFT_BL + GpioPin *p = new GpioHwPin(TFT_BL); + + if (!TFT_BACKLIGHT_ON) { // Need to invert the pin before hardware + auto virtPin = new GpioVirtPin(); + new GpioNotTransformer( + virtPin, p); // We just leave this created object on the heap so it can stay watching virtPin and driving en_gpio + p = virtPin; + } +#else + GpioPin *p = new GpioVirtPin(); // Just simulate a pin +#endif + backlightEnable = p; + #if ARCH_PORTDUINO if (settingsMap[displayRotate]) { setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]); @@ -569,13 +584,11 @@ void TFTDisplay::sendCommand(uint8_t com) // handle display on/off directly switch (com) { case DISPLAYON: { + backlightEnable->set(true); #if ARCH_PORTDUINO display(true); if (settingsMap[displayBacklight] > 0) digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON); -#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) - pinMode(TFT_BL, OUTPUT); - digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->wakeup(); tft->powerSaveOff(); @@ -594,13 +607,11 @@ void TFTDisplay::sendCommand(uint8_t com) break; } case DISPLAYOFF: { + backlightEnable->set(false); #if ARCH_PORTDUINO tft->clear(); if (settingsMap[displayBacklight] > 0) digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON); -#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) - pinMode(TFT_BL, OUTPUT); - digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->sleep(); tft->powerSaveOn(); @@ -689,13 +700,8 @@ bool TFTDisplay::connect() tft = new LGFX; #endif -#ifdef TFT_BL - pinMode(TFT_BL, OUTPUT); - digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); - // pinMode(PIN_3V3_EN, OUTPUT); - // digitalWrite(PIN_3V3_EN, HIGH); + backlightEnable->set(true); LOG_INFO("Power to TFT Backlight\n"); -#endif #ifdef UNPHONE unphone.backlight(true); // using unPhone library diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 42aa3abff..595984fbc 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -1,5 +1,6 @@ #pragma once +#include #include /** @@ -39,6 +40,12 @@ class TFTDisplay : public OLEDDisplay */ void setDetected(uint8_t detected); + /** + * This is normally managed entirely by TFTDisplay, but some rare applications (heltec tracker) might need to replace the + * default GPIO behavior with something a bit more complex. + */ + GpioPin *backlightEnable; + protected: // the header size of the buffer used, e.g. for the SPI command header virtual int getBufferOffset(void) override { return 0; } From 2a7cf9d3873ee4a8d485c8688d96cd084b08b31a Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 10:40:12 -0700 Subject: [PATCH 0943/1377] Remove redundant defintions of ST7789_BACKLIGHT_EN --- src/graphics/TFTDisplay.cpp | 4 ---- variants/heltec_mesh_node_t114/variant.h | 2 +- variants/picomputer-s3/variant.h | 2 +- variants/t-deck/variant.h | 2 +- variants/t-watch-s3/variant.h | 4 +--- variants/tracksenger/lcd/variant.h | 2 +- variants/wiphone/variant.h | 2 +- 7 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 21b9c2bd1..7a0a8c3eb 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -123,10 +123,6 @@ static void rak14014_tpIntHandle(void) #elif defined(ST7789_CS) #include // Graphics and font library for ST7735 driver chip -#if defined(ST7789_BACKLIGHT_EN) && !defined(TFT_BL) -#define TFT_BL ST7789_BACKLIGHT_EN -#endif - class LGFX : public lgfx::LGFX_Device { lgfx::Panel_ST7789 _panel_instance; diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index f7a268148..e8c305990 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -49,7 +49,7 @@ extern "C" { // #define ST7789_BL (32+6) #define TFT_BACKLIGHT_ON LOW #define ST7789_SPI_HOST SPI1_HOST -// #define ST7789_BACKLIGHT_EN (32+6) +// #define TFT_BL (32+6) #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 135 diff --git a/variants/picomputer-s3/variant.h b/variants/picomputer-s3/variant.h index fc746c599..ff8faa6f4 100644 --- a/variants/picomputer-s3/variant.h +++ b/variants/picomputer-s3/variant.h @@ -37,7 +37,7 @@ #define ST7789_MISO -1 #define ST7789_BUSY -1 #define ST7789_SPI_HOST SPI3_HOST -#define ST7789_BACKLIGHT_EN 5 +#define TFT_BL 5 #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 320 diff --git a/variants/t-deck/variant.h b/variants/t-deck/variant.h index 7efa00c82..9860d608f 100644 --- a/variants/t-deck/variant.h +++ b/variants/t-deck/variant.h @@ -8,7 +8,7 @@ #define ST7789_BUSY -1 #define ST7789_BL 42 #define ST7789_SPI_HOST SPI2_HOST -#define ST7789_BACKLIGHT_EN 42 +#define TFT_BL 42 #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 320 diff --git a/variants/t-watch-s3/variant.h b/variants/t-watch-s3/variant.h index ad7e6b56b..9f939d859 100644 --- a/variants/t-watch-s3/variant.h +++ b/variants/t-watch-s3/variant.h @@ -8,7 +8,7 @@ #define ST7789_BUSY -1 #define ST7789_BL 45 #define ST7789_SPI_HOST SPI3_HOST -#define ST7789_BACKLIGHT_EN 45 +#define TFT_BL 45 #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 240 @@ -30,8 +30,6 @@ #define I2C_SDA1 39 // Used for capacitive touch #define I2C_SCL1 40 // Used for capacitive touch -#define TFT_BL ST7789_BACKLIGHT_EN - #define HAS_I2S #define DAC_I2S_BCK 48 #define DAC_I2S_WS 15 diff --git a/variants/tracksenger/lcd/variant.h b/variants/tracksenger/lcd/variant.h index c89bf141c..ecf4e854e 100644 --- a/variants/tracksenger/lcd/variant.h +++ b/variants/tracksenger/lcd/variant.h @@ -22,7 +22,7 @@ #define ST7789_MISO -1 #define ST7789_BUSY -1 #define ST7789_SPI_HOST SPI3_HOST -#define ST7789_BACKLIGHT_EN 21 +#define TFT_BL 21 #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 320 diff --git a/variants/wiphone/variant.h b/variants/wiphone/variant.h index b2b3ade78..052dc5ea8 100644 --- a/variants/wiphone/variant.h +++ b/variants/wiphone/variant.h @@ -40,7 +40,7 @@ #define ST7789_MISO 19 #define ST7789_BUSY -1 #define ST7789_SPI_HOST SPI3_HOST -#define ST7789_BACKLIGHT_EN -1 // EXTENDER_PIN(9) +#define TFT_BL -1 // EXTENDER_PIN(9) #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 240 From f77c5f6a5bb42ea5d28cb2db54417d2694acc7c9 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 10:50:44 -0700 Subject: [PATCH 0944/1377] Don't create backlight instances when the variant hasn't specified a pin --- src/graphics/TFTDisplay.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 7a0a8c3eb..f6bd6513a 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -192,6 +192,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance.config(cfg); } +#ifdef ST7789_BL // Set the backlight control. (delete if not necessary) { auto cfg = _light_instance.config(); // Gets a structure for backlight settings. @@ -203,6 +204,7 @@ class LGFX : public lgfx::LGFX_Device _light_instance.config(cfg); _panel_instance.setLight(&_light_instance); // Set the backlight on the panel. } +#endif #if HAS_TOUCHSCREEN // Configure settings for touch screen control. @@ -312,6 +314,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance.config(cfg); } +#ifdef TFT_BL // Set the backlight control { auto cfg = _light_instance.config(); // Gets a structure for backlight settings. @@ -324,6 +327,7 @@ class LGFX : public lgfx::LGFX_Device _light_instance.config(cfg); _panel_instance.setLight(&_light_instance); // Set the backlight on the panel. } +#endif setPanel(&_panel_instance); } From 5c5cbb23cfaae33693f2f7b505b827a493a60811 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 10:51:15 -0700 Subject: [PATCH 0945/1377] wiphone isn't setting a valid backlight enable pin Therefore don't just randomly be writing to a GPIO numbered -1 Instead just don't try to control the backlight NOTE: I don't have a 'wiphone' to test with, but I saw this via inspection while cleaning up some other stuff. --- variants/wiphone/variant.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/variants/wiphone/variant.h b/variants/wiphone/variant.h index 052dc5ea8..cfa5667bb 100644 --- a/variants/wiphone/variant.h +++ b/variants/wiphone/variant.h @@ -34,13 +34,17 @@ #define ST7789_SCK 18 #define ST7789_CS 5 #define ST7789_RS 26 -#define ST7789_BL -1 // EXTENDER_PIN(9) +// I don't have a 'wiphone' but this I think should not be defined this way (don't set TFT_BL if we don't have a hw way to control +// it) +// #define ST7789_BL -1 // EXTENDER_PIN(9) #define ST7789_RESET -1 #define ST7789_MISO 19 #define ST7789_BUSY -1 #define ST7789_SPI_HOST SPI3_HOST -#define TFT_BL -1 // EXTENDER_PIN(9) +// I don't have a 'wiphone' but this I think should not be defined this way (don't set TFT_BL if we don't have a hw way to control +// it) +// #define TFT_BL -1 // EXTENDER_PIN(9) #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define TFT_HEIGHT 240 From e6163a59cd0521a19bf2d1db63e9f6792964d359 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 22 Aug 2024 11:07:06 -0700 Subject: [PATCH 0946/1377] Make specifying VEXT_ON_VALUE manatory if using VEXT_ENABLE --- variants/heltec_wireless_paper/variant.h | 1 + variants/heltec_wireless_paper_v1/variant.h | 1 + variants/heltec_wsl_v3/variant.h | 3 ++- variants/tlora_v1/variant.h | 5 +++-- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 36ab80445..520dcec9b 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -22,6 +22,7 @@ // Power #define VEXT_ENABLE 45 // Active low, powers the E-Ink display +#define VEXT_ON_VALUE LOW #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 36ab80445..520dcec9b 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -22,6 +22,7 @@ // Power #define VEXT_ENABLE 45 // Active low, powers the E-Ink display +#define VEXT_ON_VALUE LOW #define ADC_CTRL 19 #define BATTERY_PIN 20 #define ADC_CHANNEL ADC2_GPIO20_CHANNEL diff --git a/variants/heltec_wsl_v3/variant.h b/variants/heltec_wsl_v3/variant.h index 75cea538d..c103b9172 100644 --- a/variants/heltec_wsl_v3/variant.h +++ b/variants/heltec_wsl_v3/variant.h @@ -4,6 +4,7 @@ #define LED_PIN LED #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost +#define VEXT_ON_VALUE LOW #define BUTTON_PIN 0 #define ADC_CTRL 37 @@ -32,4 +33,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file diff --git a/variants/tlora_v1/variant.h b/variants/tlora_v1/variant.h index 08fefa809..83e2c193e 100644 --- a/variants/tlora_v1/variant.h +++ b/variants/tlora_v1/variant.h @@ -4,8 +4,9 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 2 // If defined we will blink this LED -#define BUTTON_PIN 0 // If defined, this will be used for user button presses +#define VEXT_ON_VALUE LOW +#define LED_PIN 2 // If defined we will blink this LED +#define BUTTON_PIN 0 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. From ff500bc5a934d2fcc07aa06ef85dcc8468cb4c11 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 22 Aug 2024 20:57:03 -0500 Subject: [PATCH 0947/1377] Save nodedb after favoriting (or removing) (#4537) --- src/modules/AdminModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index d64aea5d8..ef60a4bf2 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -238,6 +238,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node); if (node != NULL) { node->is_favorite = true; + saveChanges(SEGMENT_DEVICESTATE, false); } break; } @@ -246,6 +247,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node); if (node != NULL) { node->is_favorite = false; + saveChanges(SEGMENT_DEVICESTATE, false); } break; } From 601ae29fe917f90da0679ae6455b8e8e51ae4b6e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Aug 2024 06:25:40 -0500 Subject: [PATCH 0948/1377] Adds has_x bools to position packet. (#4540) --- src/modules/PositionModule.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f534baf67..2a0c95a9b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -187,16 +187,23 @@ meshtastic_MeshPacket *PositionModule::allocReply() p.longitude_i = localPosition.longitude_i; } p.precision_bits = precision; + p.has_latitude_i = true; + p.has_longitude_i = true; p.time = getValidTime(RTCQualityNTP) > 0 ? getValidTime(RTCQualityNTP) : localPosition.time; if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE) { - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE_MSL) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE_MSL) { p.altitude = localPosition.altitude; - else + p.has_altitude = true; + } else { p.altitude_hae = localPosition.altitude_hae; + p.has_altitude_hae = true; + } - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_GEOIDAL_SEPARATION) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_GEOIDAL_SEPARATION) { p.altitude_geoidal_separation = localPosition.altitude_geoidal_separation; + p.has_altitude_geoidal_separation = true; + } } if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_DOP) { @@ -216,11 +223,15 @@ meshtastic_MeshPacket *PositionModule::allocReply() if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_SEQ_NO) p.seq_number = localPosition.seq_number; - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_HEADING) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_HEADING) { p.ground_track = localPosition.ground_track; + p.has_ground_track = true; + } - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_SPEED) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_SPEED) { p.ground_speed = localPosition.ground_speed; + p.has_ground_speed = true; + } // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS or NTP to include the time, so that devices @@ -471,4 +482,4 @@ void PositionModule::handleNewPosition() } } -#endif +#endif \ No newline at end of file From 00ea9182a48887741e0e22aa1971100d455f1344 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Aug 2024 06:26:19 -0500 Subject: [PATCH 0949/1377] Fix copyPasta in NodeDB (#4538) --- src/mesh/NodeDB.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1caaaf39b..34d3e4ce9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -556,13 +556,8 @@ void NodeDB::cleanupMeshDB() for (int i = 0; i < numMeshNodes; i++) { if (meshNodes->at(i).has_user) { if (meshNodes->at(i).user.public_key.size > 0) { - for (int j = 0; j < numMeshNodes; j++) { - if (meshNodes->at(i).user.public_key.bytes[j] != 0) { - break; - } - if (j == 31) { - meshNodes->at(i).user.public_key.size = 0; - } + if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) { + meshNodes->at(i).user.public_key.size = 0; } } meshNodes->at(newPos++) = meshNodes->at(i); From 0850ad6c8d87be756008001393cdc426a677dcc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:28:23 +0200 Subject: [PATCH 0950/1377] Initial support for RadioMaster Bandit. (#4523) * Initial support for RadioMaster Bandit. * Different lighting can be made for Button 1 & 2 on the Bandit. Changes to AmbientLighting will turn off af shutdown(). * Trunk * Trunk again. --- platformio.ini | 1 + src/AmbientLightingThread.h | 59 ++++++++- src/mesh/RF95Interface.cpp | 26 +++- src/platform/esp32/architecture.h | 2 + .../radiomaster_900_bandit/platformio.ini | 14 ++ variants/radiomaster_900_bandit/variant.h | 121 ++++++++++++++++++ 6 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 variants/radiomaster_900_bandit/platformio.ini create mode 100644 variants/radiomaster_900_bandit/variant.h diff --git a/platformio.ini b/platformio.ini index 5ad7d60a2..4de1ec39f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,6 +34,7 @@ default_envs = tbeam ;default_envs = wio-e5 ;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_micro +;default_envs = radiomaster_900_bandit ;default_envs = heltec_capsule_sensor_v3 ;default_envs = heltec_vision_master_t190 ;default_envs = heltec_vision_master_e213 diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 6b3360b1f..fdd4b53fa 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -1,3 +1,4 @@ +#include "Observer.h" #include "configuration.h" #ifdef HAS_NCP5623 @@ -22,10 +23,18 @@ class AmbientLightingThread : public concurrency::OSThread public: explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLightingThread") { + notifyDeepSleepObserver.observe(¬ifyDeepSleep); // Let us know when shutdown() is issued. + +// Enables Ambient Lighting by default if conditions are meet. +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) +#ifdef ENABLE_AMBIENTLIGHTING + moduleConfig.ambient_lighting.led_state = true; +#endif +#endif // Uncomment to test module // moduleConfig.ambient_lighting.led_state = true; // moduleConfig.ambient_lighting.current = 10; - // // Default to a color based on our node number + // Default to a color based on our node number // moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; @@ -82,9 +91,46 @@ class AmbientLightingThread : public concurrency::OSThread return disable(); } + // When shutdown() is issued, setLightingOff will be called. + CallbackObserver notifyDeepSleepObserver = + CallbackObserver(this, &AmbientLightingThread::setLightingOff); + private: ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE; + // Turn RGB lighting off, is used in junction to shutdown() + int setLightingOff(void *unused) + { +#ifdef HAS_NCP5623 + rgb.setCurrent(0); + rgb.setRed(0); + rgb.setGreen(0); + rgb.setBlue(0); + LOG_INFO("Turn Off NCP5623 Ambient lighting.\n"); +#endif +#ifdef HAS_NEOPIXEL + pixels.clear(); + pixels.show(); + LOG_INFO("Turn Off NeoPixel Ambient lighting.\n"); +#endif +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255 - 0); + analogWrite(RGBLED_GREEN, 255 - 0); + analogWrite(RGBLED_BLUE, 255 - 0); + LOG_INFO("Turn Off Ambient lighting RGB Common Anode.\n"); +#elif defined(RGBLED_RED) + analogWrite(RGBLED_RED, 0); + analogWrite(RGBLED_GREEN, 0); + analogWrite(RGBLED_BLUE, 0); + LOG_INFO("Turn Off Ambient lighting RGB Common Cathode.\n"); +#endif +#ifdef UNPHONE + unphone.rgb(0, 0, 0); + LOG_INFO("Turn Off unPhone Ambient lighting.\n"); +#endif + return 0; + } + void setLighting() { #ifdef HAS_NCP5623 @@ -100,6 +146,17 @@ class AmbientLightingThread : public concurrency::OSThread pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue), 0, NEOPIXEL_COUNT); + +// RadioMaster Bandit has addressable LED at the two buttons +// this allow us to set different lighting for them in variant.h file. +#ifdef RADIOMASTER_900_BANDIT +#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX) + pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1); +#endif +#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX) + pixels.fill(BUTTON2_COLOR, BUTTON1_COLOR_INDEX, 1); +#endif +#endif pixels.show(); LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index b6083e7f9..25df3258f 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -16,7 +16,7 @@ // In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING // if you set power to something higher than 17 or 20 you might fry your board. -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) // Structure to hold DAC and DB values typedef struct { uint8_t dac; @@ -40,12 +40,23 @@ DACDB getDACandDB(uint8_t dbm) static const struct { uint8_t dbm; DACDB values; - } dbmToDACDB[] = { + } +#ifdef RADIOMASTER_900_BANDIT_NANO + dbmToDACDB[] = { {20, {168, 2}}, // 100mW {24, {148, 6}}, // 250mW {27, {128, 9}}, // 500mW {30, {90, 12}} // 1000mW }; +#endif +#ifdef RADIOMASTER_900_BANDIT + dbmToDACDB[] = { + {20, {165, 2}}, // 100mW + {24, {155, 6}}, // 250mW + {27, {142, 9}}, // 500mW + {30, {110, 10}} // 1000mW + }; +#endif const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); // Find the interval dbm falls within and interpolate @@ -56,7 +67,12 @@ DACDB getDACandDB(uint8_t dbm) } // Return a default value if no match is found and default to 100mW +#ifdef RADIOMASTER_900_BANDIT_NANO DACDB defaultValue = {168, 2}; +#endif +#ifdef RADIOMASTER_900_BANDIT + DACDB defaultValue = {165, 2}; +#endif return defaultValue; } #endif @@ -95,7 +111,7 @@ bool RF95Interface::init() { RadioLibInterface::init(); -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) // DAC and DB values based on dBm using interpolation DACDB dacDbValues = getDACandDB(power); int8_t powerDAC = dacDbValues.dac; @@ -117,7 +133,7 @@ bool RF95Interface::init() // enable PA #ifdef RF95_PA_EN #if defined(RF95_PA_DAC_EN) -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) // Use calculated DAC value dacWrite(RF95_PA_EN, powerDAC); #else @@ -163,7 +179,7 @@ bool RF95Interface::init() LOG_INFO("Frequency set to %f\n", getFreq()); LOG_INFO("Bandwidth set to %f\n", bw); LOG_INFO("Power output set to %d\n", power); -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) LOG_INFO("DAC output set to %d\n", powerDAC); #endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index b6def5b01..3761235a0 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -152,6 +152,8 @@ #define HW_VENDOR meshtastic_HardwareModel_WIPHONE #elif defined(RADIOMASTER_900_BANDIT_NANO) #define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO +#elif defined(RADIOMASTER_900_BANDIT) +#define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT #elif defined(HELTEC_CAPSULE_SENSOR_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 #elif defined(HELTEC_VISION_MASTER_T190) diff --git a/variants/radiomaster_900_bandit/platformio.ini b/variants/radiomaster_900_bandit/platformio.ini new file mode 100644 index 000000000..4ff8a6ea2 --- /dev/null +++ b/variants/radiomaster_900_bandit/platformio.ini @@ -0,0 +1,14 @@ +[env:radiomaster_900_bandit] +extends = esp32_base +board = esp32doit-devkit-v1 +build_flags = + ${esp32_base.build_flags} + -DRADIOMASTER_900_BANDIT + -DVTABLES_IN_FLASH=1 + -DCONFIG_DISABLE_HAL_LOCKS=1 + -O2 + -Ivariants/radiomaster_900_bandit +board_build.f_cpu = 240000000L +upload_protocol = esptool +lib_deps = + ${esp32_base.lib_deps} \ No newline at end of file diff --git a/variants/radiomaster_900_bandit/variant.h b/variants/radiomaster_900_bandit/variant.h new file mode 100644 index 000000000..0499970f5 --- /dev/null +++ b/variants/radiomaster_900_bandit/variant.h @@ -0,0 +1,121 @@ +/* + Initial settings and work by https://github.com/gjelsoe + Unit provided by Radio Master RC + https://radiomasterrc.com/products/bandit-expresslrs-rf-module with 1.29" OLED display CH1115 driver +*/ + +/* + On this model then screen is NOT upside down, don't flip it for the user. +*/ +#undef DISPLAY_FLIP_SCREEN + +/* + I2C SDA and SCL. + 0x18 - STK8XXX Accelerometer, Not supported yet. + 0x3C - SH1115 Display Driver +*/ +#define I2C_SDA 14 +#define I2C_SCL 12 + +/* + No GPS - but free pins are available. +*/ +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +/* + Pin connections from ESP32-D0WDQ6 to SX1276. +*/ +#define LORA_DIO0 22 +#define LORA_DIO1 21 +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 +#define LORA_CS 4 +#define LORA_RESET 5 +#define LORA_TXEN 33 + +/* + This unit has a FAN built-in. + FAN is active at 250mW on it's ExpressLRS Firmware. + This FAN has TACHO signal on Pin 27 for use with PWM. +*/ +#define RF95_FAN_EN 2 + +/* + LED PIN setup and it has a NeoPixel LED. + It's possible to setup colors for Button 1 and 2, + look at BUTTON1_COLOR, BUTTON1_COLOR_INDEX, BUTTON2_COLOR and BUTTON2_COLOR_INDEX + this is done here for now. +*/ +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 6 // How many neopixels are connected +#define NEOPIXEL_DATA 15 // GPIO pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // Type of neopixels in use +#define ENABLE_AMBIENTLIGHTING // Turn on Ambient Lighting +// #define BUTTON1_COLOR 0xFF0000 // Background light for Button 1 in HEX RGB Color (RadioMaster Bandit only). +// #define BUTTON1_COLOR_INDEX 0 // NeoPixel Index ID for Button 1 +// #define BUTTON2_COLOR 0x0000FF // Background light for Button 2 in HEX RGB Color (RadioMaster Bandit only). +// #define BUTTON2_COLOR_INDEX 1 // NeoPixel Index ID for Button 2 + +/* + It has 1 x five-way and 2 x normal buttons. + + Button GPIO RGB Index + --------------------------- + Five-way 39 - + Button 1 34 0 + Button 2 35 1 + + Five way button when using ADC. + 2.632V, 2.177V, 1.598V, 1.055V, 0V + + ADC Values: + { UP, DOWN, LEFT, RIGHT, ENTER, IDLE } + 3227, 0 ,1961, 2668, 1290, 4095 + + Five way button when using ADC. + https://github.com/ExpressLRS/targets/blob/f3215b5ec891108db1a13523e4163950cfcadaac/TX/Radiomaster%20Bandit.json#L41 + +*/ +#define INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE +#define PIN_JOYSTICK 39 +#define JOYSTICK_ADC_VALS /*UP*/ 3227, /*DOWN*/ 0, /*LEFT*/ 1961, /*RIGHT*/ 2668, /*OK*/ 1290, /*IDLE*/ 4095 + +/* + Normal Button Pin setup. +*/ +#define BUTTON_PIN 34 +#define BUTTON_NEED_PULLUP + +/* + No External notification. +*/ +#undef EXT_NOTIFY_OUT + +/* + Remapping PIN Names. + Note, that this unit uses RFO +*/ +#define USE_RF95 +#define USE_RF95_RFO +#define RF95_CS LORA_CS +#define RF95_DIO1 LORA_DIO1 +#define RF95_TXEN LORA_TXEN +#define RF95_RESET LORA_RESET +#define RF95_MAX_POWER 10 + +/* + This module has Skyworks SKY66122 controlled by dacWrite + power ranging from 100mW to 1000mW. + + Mapping of PA_LEVEL to Power output: GPIO26/dacWrite + 168 -> 100mW + 155 -> 250mW + 142 -> 500mW + 110 -> 1000mW +*/ +#define RF95_PA_EN 26 +#define RF95_PA_DAC_EN +#define RF95_PA_LEVEL 110 \ No newline at end of file From f99b81acf771279cfeea09bc350db979d6c89592 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Fri, 23 Aug 2024 05:03:29 -0700 Subject: [PATCH 0951/1377] Use the '+' wildcard for MQTT rather than '#', to subscribe only to topics one nesting level deep (#4528) --- src/mqtt/MQTT.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 22f68bac8..2f7e82e3d 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -376,12 +376,12 @@ void MQTT::sendSubscriptions() const auto &ch = channels.getByIndex(i); if (ch.settings.downlink_enabled) { hasDownlink = true; - std::string topic = cryptTopic + channels.getGlobalId(i) + "/#"; + std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled == true) { - std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/#"; + std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } @@ -390,7 +390,7 @@ void MQTT::sendSubscriptions() } #if !MESHTASTIC_EXCLUDE_PKI if (hasDownlink) { - std::string topic = cryptTopic + "PKI/#"; + std::string topic = cryptTopic + "PKI/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); } @@ -674,4 +674,4 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type (json.find("payload") != json.end()); // should have a payload -} \ No newline at end of file +} From 7abc194ef509f03a3f018b44e51bb0208cac4634 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 23 Aug 2024 07:04:34 -0500 Subject: [PATCH 0952/1377] Found more places to set explicit has_optional on position (#4542) --- src/mesh/TypeConversions.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index d8ee6afc7..bcff0b817 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -16,8 +16,14 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo if (lite->has_position) { info.has_position = true; + if (lite->position.latitude_i > 0) + info.position.has_latitude_i = true; info.position.latitude_i = lite->position.latitude_i; + if (lite->position.longitude_i > 0) + info.position.has_longitude_i = true; info.position.longitude_i = lite->position.longitude_i; + if (lite->position.altitude > 0) + info.position.has_altitude = true; info.position.altitude = lite->position.altitude; info.position.location_source = lite->position.location_source; info.position.time = lite->position.time; @@ -48,8 +54,14 @@ meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Positi meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite lite) { meshtastic_Position position = meshtastic_Position_init_default; + if (lite.latitude_i > 0) + position.has_latitude_i = true; position.latitude_i = lite.latitude_i; + if (lite.longitude_i > 0) + position.has_longitude_i = true; position.longitude_i = lite.longitude_i; + if (lite.altitude > 0) + position.has_altitude = true; position.altitude = lite.altitude; position.location_source = lite.location_source; position.time = lite.time; From 2a279c7f3dcabab5beb13f9a376948760725f8d7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 23 Aug 2024 07:07:28 -0500 Subject: [PATCH 0953/1377] Dum dum zero comparision --- src/mesh/TypeConversions.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index bcff0b817..513728ca5 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -16,13 +16,13 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo if (lite->has_position) { info.has_position = true; - if (lite->position.latitude_i > 0) + if (lite->position.latitude_i != 0) info.position.has_latitude_i = true; info.position.latitude_i = lite->position.latitude_i; - if (lite->position.longitude_i > 0) + if (lite->position.longitude_i != 0) info.position.has_longitude_i = true; info.position.longitude_i = lite->position.longitude_i; - if (lite->position.altitude > 0) + if (lite->position.altitude != 0) info.position.has_altitude = true; info.position.altitude = lite->position.altitude; info.position.location_source = lite->position.location_source; @@ -54,13 +54,13 @@ meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Positi meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite lite) { meshtastic_Position position = meshtastic_Position_init_default; - if (lite.latitude_i > 0) + if (lite.latitude_i != 0) position.has_latitude_i = true; position.latitude_i = lite.latitude_i; - if (lite.longitude_i > 0) + if (lite.longitude_i != 0) position.has_longitude_i = true; position.longitude_i = lite.longitude_i; - if (lite.altitude > 0) + if (lite.altitude != 0) position.has_altitude = true; position.altitude = lite.altitude; position.location_source = lite.location_source; From aa54335e214fbc55de01a694accfeac2a427422a Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 23 Aug 2024 18:18:36 -0700 Subject: [PATCH 0954/1377] remove deprecated serial/bt logging options and unify in the new (#4516) security option. Per discussion in https://github.com/meshtastic/firmware/issues/4375 no need to preserve the old options when changing to this new simpler single boolean because they were newish, rarely used and only for 'advanced' developers. --- protobufs | 2 +- src/RedirectablePrint.cpp | 2 +- src/mesh/NodeDB.cpp | 1 - src/mesh/generated/meshtastic/config.pb.h | 37 ++++++------------- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 6 files changed, 15 insertions(+), 31 deletions(-) diff --git a/protobufs b/protobufs index 56a435507..183ba970a 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 56a4355070f3371213d48f3a8cac1ddaf0d553fe +Subproject commit 183ba970a7a71de7a81541b74c413543523417d2 diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 96cf851e4..6eb6f8319 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -213,7 +213,7 @@ void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) { #if !MESHTASTIC_EXCLUDE_BLUETOOTH - if (config.security.bluetooth_logging_enabled && !pauseBluetoothLogging) { + if (config.security.debug_log_api_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 34d3e4ce9..0504cc273 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -127,7 +127,6 @@ NodeDB::NodeDB() if (!config.has_security) { config.has_security = true; config.security.serial_enabled = config.device.serial_enabled; - config.security.bluetooth_logging_enabled = config.bluetooth.device_logging_enabled; config.security.is_managed = config.device.is_managed; } #if !(MESHTASTIC_EXCLUDE_PKI) diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 2f4c00fb0..d79654856 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -284,10 +284,6 @@ typedef struct _meshtastic_Config_DeviceConfig { /* Disabling this will disable the SerialConsole by not initilizing the StreamAPI Moved to SecurityConfig */ bool serial_enabled; - /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). - Set this to true to leave the debug log outputting even when API is active. - Moved to SecurityConfig */ - bool debug_log_enabled; /* For boards without a hard wired button, this is the pin number that will be used Boards that have more than one button can swap the function with this one. defaults to BUTTON_PIN if defined. */ uint32_t button_gpio; @@ -523,9 +519,6 @@ typedef struct _meshtastic_Config_BluetoothConfig { meshtastic_Config_BluetoothConfig_PairingMode mode; /* Specified PIN for PairingMode.FixedPin */ uint32_t fixed_pin; - /* Enables device (serial style logs) over Bluetooth - Moved to SecurityConfig */ - bool device_logging_enabled; } meshtastic_Config_BluetoothConfig; typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_public_key_t; @@ -546,10 +539,8 @@ typedef struct _meshtastic_Config_SecurityConfig { /* Serial Console over the Stream API." */ bool serial_enabled; /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). - Output live debug logging over serial. */ + Output live debug logging over serial or bluetooth is set to true. */ bool debug_log_api_enabled; - /* Enables device (serial style logs) over Bluetooth */ - bool bluetooth_logging_enabled; /* Allow incoming device control over the insecure legacy admin channel. */ bool admin_channel_enabled; } meshtastic_Config_SecurityConfig; @@ -658,32 +649,31 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} -#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} -#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} -#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} -#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Config_DeviceConfig_role_tag 1 #define meshtastic_Config_DeviceConfig_serial_enabled_tag 2 -#define meshtastic_Config_DeviceConfig_debug_log_enabled_tag 3 #define meshtastic_Config_DeviceConfig_button_gpio_tag 4 #define meshtastic_Config_DeviceConfig_buzzer_gpio_tag 5 #define meshtastic_Config_DeviceConfig_rebroadcast_mode_tag 6 @@ -758,14 +748,12 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_enabled_tag 1 #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 -#define meshtastic_Config_BluetoothConfig_device_logging_enabled_tag 4 #define meshtastic_Config_SecurityConfig_public_key_tag 1 #define meshtastic_Config_SecurityConfig_private_key_tag 2 #define meshtastic_Config_SecurityConfig_admin_key_tag 3 #define meshtastic_Config_SecurityConfig_is_managed_tag 4 #define meshtastic_Config_SecurityConfig_serial_enabled_tag 5 #define meshtastic_Config_SecurityConfig_debug_log_api_enabled_tag 6 -#define meshtastic_Config_SecurityConfig_bluetooth_logging_enabled_tag 7 #define meshtastic_Config_SecurityConfig_admin_channel_enabled_tag 8 #define meshtastic_Config_device_tag 1 #define meshtastic_Config_position_tag 2 @@ -803,7 +791,6 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sessionkey,payload_variant.s #define meshtastic_Config_DeviceConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, role, 1) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 2) \ -X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 3) \ X(a, STATIC, SINGULAR, UINT32, button_gpio, 4) \ X(a, STATIC, SINGULAR, UINT32, buzzer_gpio, 5) \ X(a, STATIC, SINGULAR, UENUM, rebroadcast_mode, 6) \ @@ -906,8 +893,7 @@ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_BluetoothConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, UENUM, mode, 2) \ -X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) \ -X(a, STATIC, SINGULAR, BOOL, device_logging_enabled, 4) +X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) #define meshtastic_Config_BluetoothConfig_CALLBACK NULL #define meshtastic_Config_BluetoothConfig_DEFAULT NULL @@ -918,7 +904,6 @@ X(a, STATIC, SINGULAR, BYTES, admin_key, 3) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \ -X(a, STATIC, SINGULAR, BOOL, bluetooth_logging_enabled, 7) \ X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8) #define meshtastic_Config_SecurityConfig_CALLBACK NULL #define meshtastic_Config_SecurityConfig_DEFAULT NULL @@ -955,15 +940,15 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size -#define meshtastic_Config_BluetoothConfig_size 12 -#define meshtastic_Config_DeviceConfig_size 100 +#define meshtastic_Config_BluetoothConfig_size 10 +#define meshtastic_Config_DeviceConfig_size 98 #define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 -#define meshtastic_Config_SecurityConfig_size 112 +#define meshtastic_Config_SecurityConfig_size 110 #define meshtastic_Config_SessionkeyConfig_size 0 #define meshtastic_Config_size 199 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 343e5f48a..976e0e135 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -358,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3502 +#define meshtastic_OEMStore_size 3496 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index c612b24ab..38529fc14 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 669 +#define meshtastic_LocalConfig_size 663 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus From 5514aab0076352f9dcc610ea1ce65ce3d3823619 Mon Sep 17 00:00:00 2001 From: Nestpebble <116762865+Nestpebble@users.noreply.github.com> Date: Sat, 24 Aug 2024 02:24:23 +0100 Subject: [PATCH 0955/1377] add a .yml to setup a Gitpod instance quickly (#4551) * Create .gitpod.yml * Update .gitpod.yml --- .gitpod.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..0af235a3a --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,2 @@ +tasks: + - init: pip install platformio && pip install --upgrade pip From 8847945734f190e844e866197bf372957b3a85fb Mon Sep 17 00:00:00 2001 From: John Hollowell Date: Fri, 23 Aug 2024 21:25:16 -0400 Subject: [PATCH 0956/1377] Add devcontainer (#4491) devcontainers can be used by IDEs/editors like VS Code to create a standardized development environment in a container --- .devcontainer/Dockerfile | 24 ++++++++++++++++++++++++ .devcontainer/devcontainer.json | 28 ++++++++++++++++++++++++++++ .devcontainer/setup.sh | 3 +++ 3 files changed, 55 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/setup.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..82f4d91bf --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,24 @@ +FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12 + +# [Optional] Uncomment this section to install additional packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + ca-certificates \ + g++ \ + git \ + libbluetooth-dev \ + libgpiod-dev \ + liborcania-dev \ + libssl-dev \ + libulfius-dev \ + libyaml-cpp-dev \ + pkg-config \ + python3 \ + python3-pip \ + python3-venv \ + python3-wheel \ + wget \ + zip \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN pip3 install --no-cache-dir -U platformio==6.1.15 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..e45fd5d93 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "Meshtastic Firmware Dev", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/python:1": { + "installTools": true, + "version": "latest" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "platformio.platformio-ide", + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ 4403 ], + + // Run commands to prepare the container for use + "postCreateCommand": ".devcontainer/setup.sh", +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 000000000..866a4a417 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +git submodule update --init \ No newline at end of file From 23844389ac22a4f80ab96e91783f984835250c4a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:44:14 -0500 Subject: [PATCH 0957/1377] [create-pull-request] automated change (#4544) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 183ba970a..52cfa2c1c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 183ba970a7a71de7a81541b74c413543523417d2 +Subproject commit 52cfa2c1c2cd5a1a714e1338d551d967f674fca8 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index f32f865db..d612d74be 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -189,6 +189,13 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RADIOMASTER_900_BANDIT = 74, /* Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins. */ meshtastic_HardwareModel_ME25LS01_4Y10TD = 75, + /* RP2040_FEATHER_RFM95 + Adafruit Feather RP2040 with RFM95 LoRa Radio RFM95 with SX1272, SSD1306 OLED + https://www.adafruit.com/product/5714 + https://www.adafruit.com/product/326 + https://www.adafruit.com/product/938 + ^^^ short A0 to switch to I2C address 0x3C */ + meshtastic_HardwareModel_RP2040_FEATHER_RFM95 = 76, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From d6dac1737acba65fcd48e9d30d2311aaf210f5f4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 24 Aug 2024 12:19:31 -0500 Subject: [PATCH 0958/1377] Userlite mem comparison (#4552) --- src/mesh/NodeDB.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0504cc273..3af6c6a45 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1027,9 +1027,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde #endif // Both of info->user and p start as filled with zero so I think this is okay - bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); + auto lite = TypeConversions::ConvertToUserLite(p); + bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex); - info->user = TypeConversions::ConvertToUserLite(p); + info->user = lite; if (info->user.public_key.size == 32) { printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32); } From d0fd17134e9fdc51d838694a9a51e9143ae628a3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 26 Aug 2024 07:48:07 -0500 Subject: [PATCH 0959/1377] Protos --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 11 ++++++----- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index 52cfa2c1c..431291e67 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 52cfa2c1c2cd5a1a714e1338d551d967f674fca8 +Subproject commit 431291e673c1c7592ee64cb972d7845589f60138 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d79654856..eb03ddc58 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -532,7 +532,8 @@ typedef struct _meshtastic_Config_SecurityConfig { Used to create a shared key with a remote device. */ meshtastic_Config_SecurityConfig_private_key_t private_key; /* The public key authorized to send admin messages to this node. */ - meshtastic_Config_SecurityConfig_admin_key_t admin_key; + pb_size_t admin_key_count; + meshtastic_Config_SecurityConfig_admin_key_t admin_key[1]; /* If true, device is considered to be "managed" by a mesh administrator via admin messages Device is managed by a mesh administrator. */ bool is_managed; @@ -657,7 +658,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} -#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -668,7 +669,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} -#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ @@ -900,7 +901,7 @@ X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) #define meshtastic_Config_SecurityConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BYTES, public_key, 1) \ X(a, STATIC, SINGULAR, BYTES, private_key, 2) \ -X(a, STATIC, SINGULAR, BYTES, admin_key, 3) \ +X(a, STATIC, REPEATED, BYTES, admin_key, 3) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \ @@ -948,7 +949,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 -#define meshtastic_Config_SecurityConfig_size 110 +#define meshtastic_Config_SecurityConfig_size 111 #define meshtastic_Config_SessionkeyConfig_size 0 #define meshtastic_Config_size 199 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 976e0e135..692402210 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -358,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3496 +#define meshtastic_OEMStore_size 3497 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 38529fc14..91a23dc4f 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 663 +#define meshtastic_LocalConfig_size 664 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus From 777ae2b99c13bbe59f94fd23f9fcdcbab9674419 Mon Sep 17 00:00:00 2001 From: John Milton Date: Mon, 26 Aug 2024 11:28:08 -0400 Subject: [PATCH 0960/1377] Add support for Adafruit Feather RP2040 with RFM95. (#4451) * Add support for Adafruit Feather RP2040 with RFM95. * Update mesh.pb.h dropping this change from the file generated by the protobuf * Update mesh.pb.h remove these reverting changes * Update mesh.pb.h oops, missed a comma --- src/platform/rp2040/architecture.h | 2 + variants/feather_rp2040_rfm95/platformio.ini | 16 +++++ variants/feather_rp2040_rfm95/variant.h | 61 ++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 variants/feather_rp2040_rfm95/platformio.ini create mode 100644 variants/feather_rp2040_rfm95/variant.h diff --git a/src/platform/rp2040/architecture.h b/src/platform/rp2040/architecture.h index d7d7214c0..3f75735d3 100644 --- a/src/platform/rp2040/architecture.h +++ b/src/platform/rp2040/architecture.h @@ -29,4 +29,6 @@ #define HW_VENDOR meshtastic_HardwareModel_SENSELORA_RP2040 #elif defined(RP2040_LORA) #define HW_VENDOR meshtastic_HardwareModel_RP2040_LORA +#elif defined(RP2040_FEATHER_RFM95) +#define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95 #endif \ No newline at end of file diff --git a/variants/feather_rp2040_rfm95/platformio.ini b/variants/feather_rp2040_rfm95/platformio.ini new file mode 100644 index 000000000..a28ad7655 --- /dev/null +++ b/variants/feather_rp2040_rfm95/platformio.ini @@ -0,0 +1,16 @@ +[env:feather_rp2040_rfm95] +extends = rp2040_base +board = adafruit_feather +upload_protocol = picotool + +# add our variants files to the include and src paths +build_flags = ${rp2040_base.build_flags} + -DRP2040_FEATHER_RFM95 + -Ivariants/feather_rp2040_rfm95 + -DDEBUG_RP2040_PORT=Serial + -DHW_SPI1_DEVICE + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/feather_rp2040_rfm95/variant.h b/variants/feather_rp2040_rfm95/variant.h new file mode 100644 index 000000000..e9e178202 --- /dev/null +++ b/variants/feather_rp2040_rfm95/variant.h @@ -0,0 +1,61 @@ +// #define RADIOLIB_CUSTOM_ARDUINO 1 +// #define RADIOLIB_TONE_UNSUPPORTED 1 +// #define RADIOLIB_SOFTWARE_SERIAL_UNSUPPORTED 1 + +#define ARDUINO_ARCH_AVR + +// #define USE_SSD1306 + +// #define USE_SH1106 1 + +// default I2C pins: +// SDA = 4 +// SCL = 5 + +// Recommended pins for SerialModule: +// txd = 8 +// rxd = 9 + +#define EXT_NOTIFY_OUT 22 +#define BUTTON_PIN 7 +// #define BUTTON_NEED_PULLUP + +#define LED_PIN PIN_LED + +// #define BATTERY_PIN 26 +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +// #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic + +#define USE_RF95 // RFM95/SX127x + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// https://www.adafruit.com/product/5714 +// https://learn.adafruit.com/feather-rp2040-rfm95 +// https://learn.adafruit.com/assets/120283 +// https://learn.adafruit.com/assets/120813 +#define LORA_SCK 14 // 10 12P +#define LORA_MISO 8 // 12 10P +#define LORA_MOSI 15 // 11 11P +#define LORA_CS 16 // 3 13P + +#define LORA_RESET 17 // 15 14P + +#define LORA_DIO0 21 // ?? 6P +#define LORA_DIO1 22 // 20 7P +#define LORA_DIO2 23 // 2 8P +#define LORA_DIO3 19 // ?? 3P +#define LORA_DIO4 20 // ?? 4P +#define LORA_DIO5 18 // ?? 15P + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file From 5824a8f4c1eaae2c6b9b62bb227387b32d488d3a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Aug 2024 12:29:44 -0500 Subject: [PATCH 0961/1377] Deal with admin_key being repeated (#4558) --- src/mesh/NodeDB.cpp | 6 +++--- src/modules/AdminModule.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 3af6c6a45..fa736e08a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -292,10 +292,10 @@ void NodeDB::installDefaultConfig() config.lora.ignore_mqtt = false; #endif #ifdef ADMIN_KEY_USERPREFS - memcpy(config.security.admin_key.bytes, admin_key_userprefs, 32); - config.security.admin_key.size = 32; + memcpy(config.security.admin_key[0].bytes, admin_key_userprefs, 32); + config.security.admin_key[0].size = 32; #else - config.security.admin_key.size = 0; + config.security.admin_key[0].size = 0; #endif config.security.public_key.size = 0; config.security.private_key.size = 0; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index ef60a4bf2..b63eca396 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -75,7 +75,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta // and only allowing responses from that remote. if (!((mp.from == 0 && !config.security.is_managed) || messageIsResponse(r) || (strcasecmp(ch->settings.name, Channels::adminChannel) == 0 && config.security.admin_channel_enabled) || - (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key.bytes, 32) == 0))) { + (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key[0].bytes, 32) == 0))) { LOG_INFO("Ignoring admin payload %i\n", r->which_payload_variant); return handled; } From b9a8683a4bfe9c453313554f8de1e85acb789383 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Aug 2024 15:48:47 -0500 Subject: [PATCH 0962/1377] Mask out random bits when doing queue ordering (#4561) * Mask out random bits when doing queue ordering * Parenthesis --- src/mesh/MeshPacketQueue.cpp | 7 ++++--- src/mesh/MeshTypes.h | 5 +++-- src/mesh/Router.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index 24756fd2a..f1c6c4ff3 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -20,8 +20,9 @@ bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_Mes // If priorities differ, use that // for equal priorities, order by id (older packets have higher priority - this will briefly be wrong when IDs roll over but // no big deal) - return (p1p != p2p) ? (p1p < p2p) // prefer bigger priorities - : (p1->id >= p2->id); // prefer smaller packet ids + return (p1p != p2p) + ? (p1p < p2p) // prefer bigger priorities + : ((p1->id & ID_COUNTER_MASK) >= (p2->id & ID_COUNTER_MASK)); // Mask to counter portion, prefer smaller packet ids } MeshPacketQueue::MeshPacketQueue(size_t _maxLen) : maxLen(_maxLen) {} @@ -127,4 +128,4 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); return true; -} +} \ No newline at end of file diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index c0919bf5d..1c9099c39 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -14,8 +14,9 @@ typedef uint32_t PacketId; // A packet sequence number 1 // Reserved to only deliver packets over high speed (non-lora) transports, such as MQTT or BLE mesh (not yet implemented) #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 -#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER -#define ERRNO_DISABLED 34 // the interface is disabled +#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER +#define ERRNO_DISABLED 34 // the interface is disabled +#define ID_COUNTER_MASK (UINT32_MAX >> 22) // mask to select the counter portion of the ID /* * Source of a received message diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index bdd8c4e6c..61b1bbfb6 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -109,7 +109,7 @@ PacketId generatePacketId() rollingPacketId++; - rollingPacketId &= UINT32_MAX >> 22; // Mask out the top 22 bits + rollingPacketId &= ID_COUNTER_MASK; // Mask out the top 22 bits PacketId id = rollingPacketId | random(UINT32_MAX & 0x7fffffff) << 10; // top 22 bits LOG_DEBUG("Partially randomized packet id %u\n", id); return id; From ada61ae17840da40b5f3b257a7683b822d1a1446 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Aug 2024 14:49:58 -0500 Subject: [PATCH 0963/1377] Don't compare nodeDB macaddr to owner.macaddr, because in rare cases that may be unset. (#4562) Co-authored-by: Ben Meadors --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fa736e08a..8409eaff6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -620,7 +620,7 @@ void NodeDB::pickNewNodeNum() meshtastic_NodeInfoLite *found; while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) || - ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr)) != 0)) { + ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0)) { NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate); nodeNum = candidate; From ab62590aa98049e6be46dbac648571863cb2f1fa Mon Sep 17 00:00:00 2001 From: Power Li Date: Wed, 28 Aug 2024 05:26:02 +0800 Subject: [PATCH 0964/1377] set current time to system time in portduino build (#4556) * set current time to system time in portduino build * fix includes order --------- Co-authored-by: Jonathan Bennett --- src/platform/portduino/PortduinoGlue.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 3532d2e79..cce893c0b 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -1,5 +1,6 @@ #include "CryptoEngine.h" #include "PortduinoGPIO.h" +#include "RTC.h" #include "SPIChip.h" #include "mesh/RF95Interface.h" #include "sleep.h" @@ -370,6 +371,12 @@ void portduinoSetup() exit(EXIT_FAILURE); } } + + struct timeval tv; + tv.tv_sec = time(NULL); + tv.tv_usec = 0; + perhapsSetRTC(RTCQualityNTP, &tv); + return; } From 50d778d281d58bb78094e4b0f45a3f7bf139b6f4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 27 Aug 2024 18:24:14 -0500 Subject: [PATCH 0965/1377] Add RAK4631 hex to firmware release --- bin/build-nrf52.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 060d06cfd..9d0b3dfdd 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -46,3 +46,8 @@ else cp bin/device-update.* $OUTDIR cp bin/*.uf2 $OUTDIR fi + +if (echo $1 | grep -q "rak4631"); then + echo "Copying hex file" + cp .pio/build/$1/firmware.hex $OUTDIR/$basename.hex +fi \ No newline at end of file From 0ee9d375b3c204586474dea98d0123986176c615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 28 Aug 2024 12:43:19 +0200 Subject: [PATCH 0966/1377] fix #4390 (#4571) --- src/serialization/MeshPacketSerializer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index a42bb867a..e00dde024 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -76,6 +76,13 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction); msgPayload["wind_gust"] = new JSONValue(decoded->variant.environment_metrics.wind_gust); msgPayload["wind_lull"] = new JSONValue(decoded->variant.environment_metrics.wind_lull); + } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + msgPayload["pm10"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_standard); + msgPayload["pm25"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_standard); + msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard); + msgPayload["pm10_e"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental); + msgPayload["pm25_e"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental); + msgPayload["pm100_e"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental); } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); From ef9ecec341f162e5bd6cecc48cc97eea59055c95 Mon Sep 17 00:00:00 2001 From: "FW\\AM5" Date: Wed, 28 Aug 2024 13:10:19 +0200 Subject: [PATCH 0967/1377] Support for Polish OLED characters Added support for Polish OLED characters. - Custom FONT_SMALL ArialMT_Plain_10_PL - Automatic selection between Polish and Ukrainian/Russian characters mapping depending on the -D OLED_{LANG_NAME} flage --- .vscode/extensions.json | 7 +- platformio.ini | 3 +- src/graphics/Screen.cpp | 2 +- src/graphics/Screen.h | 50 +++ src/graphics/ScreenFonts.h | 8 + src/graphics/fonts/OLEDDisplayFontsPL.cpp | 440 ++++++++++++++++++++++ src/graphics/fonts/OLEDDisplayFontsPL.h | 11 + 7 files changed, 516 insertions(+), 5 deletions(-) create mode 100644 src/graphics/fonts/OLEDDisplayFontsPL.cpp create mode 100644 src/graphics/fonts/OLEDDisplayFontsPL.h diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b50c95349..080e70d08 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,9 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", - "platformio.platformio-ide", - "trunk.io" + "platformio.platformio-ide" ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] } diff --git a/platformio.ini b/platformio.ini index 4de1ec39f..5c3c4e421 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,6 +81,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS -DRADIOLIB_EXCLUDE_LORAWAN -DMESHTASTIC_EXCLUDE_DROPZONE=1 + ;-D OLED_PL monitor_speed = 115200 monitor_filters = direct @@ -157,4 +158,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3a4db6ee0..7fe5964d5 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -123,7 +123,7 @@ static bool heartbeat = false; /// Check if the display can render a string (detect special chars; emoji) static bool haveGlyphs(const char *str) { -#if defined(OLED_UA) || defined(OLED_RU) +#if defined(OLED_PL) ||defined(OLED_UA) || defined(OLED_RU) // Don't want to make any assumptions about custom language support return true; #endif diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 93e5f2ef7..7db580272 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -322,6 +322,53 @@ class Screen : public concurrency::OSThread uint8_t last = LASTCHAR; // get last char LASTCHAR = ch; +#if defined(OLED_PL) + + switch (last) { // conversion depending on first UTF8-character + case 0xC2: { + SKIPREST = false; + return (uint8_t)ch; + } + case 0xC3: { + + if (ch == 147) + return (uint8_t)(ch); // Ó + else + if (ch == 179) + return (uint8_t)(148); // ó + else + return (uint8_t)(ch | 0xC0); + break; + + } + + case 0xC4: { + SKIPREST = false; + return (uint8_t)(ch); + } + + case 0xC5: { + SKIPREST = false; + if (ch == 132) + return (uint8_t)(136); // ń + else + if (ch == 186) + return (uint8_t)(137); // ź + else + return (uint8_t)(ch); + break; + } + } + + // We want to strip out prefix chars for two-byte char formats + if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5) + return (uint8_t)0; + + +#endif + +#if defined(OLED_UA) || defined(OLED_RU) + switch (last) { // conversion depending on first UTF8-character case 0xC2: { SKIPREST = false; @@ -376,6 +423,9 @@ class Screen : public concurrency::OSThread if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1) return (uint8_t)0; +#endif + + // If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the // rest of it if (SKIPREST) diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 8a48d053e..3e4c220a0 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -1,5 +1,9 @@ #pragma once +#ifdef OLED_PL +#include "graphics/fonts/OLEDDisplayFontsPL.h" +#endif + #ifdef OLED_RU #include "graphics/fonts/OLEDDisplayFontsRU.h" #endif @@ -15,6 +19,9 @@ #define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #else +#ifdef OLED_PL +#define FONT_SMALL ArialMT_Plain_10_PL +#else #ifdef OLED_RU #define FONT_SMALL ArialMT_Plain_10_RU #else @@ -24,6 +31,7 @@ #define FONT_SMALL ArialMT_Plain_10 // Height: 13 #endif #endif +#endif #define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #endif diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.cpp b/src/graphics/fonts/OLEDDisplayFontsPL.cpp new file mode 100644 index 000000000..1322b1772 --- /dev/null +++ b/src/graphics/fonts/OLEDDisplayFontsPL.cpp @@ -0,0 +1,440 @@ +#include "OLEDDisplayFontsPL.h" + +// Font generated or edited with the glyphEditor +const uint8_t ArialMT_Plain_10_PL[] PROGMEM = { +0x0A, // Width: 10 +0x0D, // Height: 13 +0x20, // First char: 32 +0xE0, // Number of chars: 224 + +// Jump Table: +0xFF, 0xFF, 0x00, 0x03, // 32:65535 +0x00, 0x00, 0x04, 0x03, // 33 +0x00, 0x04, 0x05, 0x04, // 34 +0x00, 0x09, 0x09, 0x06, // 35 +0x00, 0x12, 0x0A, 0x06, // 36 +0x00, 0x1C, 0x10, 0x09, // 37 +0x00, 0x2C, 0x0E, 0x08, // 38 +0x00, 0x3A, 0x01, 0x02, // 39 +0x00, 0x3B, 0x06, 0x04, // 40 +0x00, 0x41, 0x06, 0x04, // 41 +0x00, 0x47, 0x05, 0x04, // 42 +0x00, 0x4C, 0x09, 0x06, // 43 +0x00, 0x55, 0x04, 0x03, // 44 +0x00, 0x59, 0x03, 0x03, // 45 +0x00, 0x5C, 0x04, 0x03, // 46 +0x00, 0x60, 0x05, 0x04, // 47 +0x00, 0x65, 0x0A, 0x06, // 48 +0x00, 0x6F, 0x08, 0x05, // 49 +0x00, 0x77, 0x0A, 0x06, // 50 +0x00, 0x81, 0x0A, 0x06, // 51 +0x00, 0x8B, 0x0B, 0x07, // 52 +0x00, 0x96, 0x0A, 0x06, // 53 +0x00, 0xA0, 0x0A, 0x06, // 54 +0x00, 0xAA, 0x09, 0x06, // 55 +0x00, 0xB3, 0x0A, 0x06, // 56 +0x00, 0xBD, 0x0A, 0x06, // 57 +0x00, 0xC7, 0x04, 0x03, // 58 +0x00, 0xCB, 0x04, 0x03, // 59 +0x00, 0xCF, 0x0A, 0x06, // 60 +0x00, 0xD9, 0x09, 0x06, // 61 +0x00, 0xE2, 0x09, 0x06, // 62 +0x00, 0xEB, 0x0B, 0x07, // 63 +0x00, 0xF6, 0x14, 0x0B, // 64 +0x01, 0x0A, 0x0E, 0x08, // 65 +0x01, 0x18, 0x0C, 0x07, // 66 +0x01, 0x24, 0x0C, 0x07, // 67 +0x01, 0x30, 0x0B, 0x07, // 68 +0x01, 0x3B, 0x0C, 0x07, // 69 +0x01, 0x47, 0x09, 0x06, // 70 +0x01, 0x50, 0x0D, 0x08, // 71 +0x01, 0x5D, 0x0C, 0x07, // 72 +0x01, 0x69, 0x04, 0x03, // 73 +0x01, 0x6D, 0x08, 0x05, // 74 +0x01, 0x75, 0x0E, 0x08, // 75 +0x01, 0x83, 0x0C, 0x07, // 76 +0x01, 0x8F, 0x10, 0x09, // 77 +0x01, 0x9F, 0x0C, 0x07, // 78 +0x01, 0xAB, 0x0E, 0x08, // 79 +0x01, 0xB9, 0x0B, 0x07, // 80 +0x01, 0xC4, 0x0E, 0x08, // 81 +0x01, 0xD2, 0x0C, 0x07, // 82 +0x01, 0xDE, 0x0C, 0x07, // 83 +0x01, 0xEA, 0x0B, 0x07, // 84 +0x01, 0xF5, 0x0C, 0x07, // 85 +0x02, 0x01, 0x0D, 0x08, // 86 +0x02, 0x0E, 0x11, 0x0A, // 87 +0x02, 0x1F, 0x0E, 0x08, // 88 +0x02, 0x2D, 0x0D, 0x08, // 89 +0x02, 0x3A, 0x0C, 0x07, // 90 +0x02, 0x46, 0x06, 0x04, // 91 +0x02, 0x4C, 0x06, 0x04, // 92 +0x02, 0x52, 0x04, 0x03, // 93 +0x02, 0x56, 0x09, 0x06, // 94 +0x02, 0x5F, 0x0C, 0x07, // 95 +0x02, 0x6B, 0x03, 0x03, // 96 +0x02, 0x6E, 0x0A, 0x06, // 97 +0x02, 0x78, 0x0A, 0x06, // 98 +0x02, 0x82, 0x0A, 0x06, // 99 +0x02, 0x8C, 0x0A, 0x06, // 100 +0x02, 0x96, 0x0A, 0x06, // 101 +0x02, 0xA0, 0x05, 0x04, // 102 +0x02, 0xA5, 0x0A, 0x06, // 103 +0x02, 0xAF, 0x0A, 0x06, // 104 +0x02, 0xB9, 0x04, 0x03, // 105 +0x02, 0xBD, 0x04, 0x03, // 106 +0x02, 0xC1, 0x08, 0x05, // 107 +0x02, 0xC9, 0x04, 0x03, // 108 +0x02, 0xCD, 0x10, 0x09, // 109 +0x02, 0xDD, 0x0A, 0x06, // 110 +0x02, 0xE7, 0x0A, 0x06, // 111 +0x02, 0xF1, 0x0A, 0x06, // 112 +0x02, 0xFB, 0x0A, 0x06, // 113 +0x03, 0x05, 0x05, 0x04, // 114 +0x03, 0x0A, 0x08, 0x05, // 115 +0x03, 0x12, 0x06, 0x04, // 116 +0x03, 0x18, 0x0A, 0x06, // 117 +0x03, 0x22, 0x09, 0x06, // 118 +0x03, 0x2B, 0x0E, 0x08, // 119 +0x03, 0x39, 0x0A, 0x06, // 120 +0x03, 0x43, 0x09, 0x06, // 121 +0x03, 0x4C, 0x0A, 0x06, // 122 +0x03, 0x56, 0x06, 0x04, // 123 +0x03, 0x5C, 0x04, 0x03, // 124 +0x03, 0x60, 0x05, 0x04, // 125 +0x03, 0x65, 0x09, 0x06, // 126 +0xFF, 0xFF, 0x00, 0x0A, // 127 +0xFF, 0xFF, 0x00, 0x0A, // 128 +0x03, 0x6E, 0x0C, 0x07, // 129 +0x03, 0x7A, 0x05, 0x04, // 130 +0x03, 0x7F, 0x0C, 0x07, // 131 +0x03, 0x8B, 0x0E, 0x08, // 132 +0x03, 0x99, 0x0C, 0x07, // 133 +0x03, 0xA5, 0x0C, 0x07, // 134 +0x03, 0xB1, 0x0A, 0x06, // 135 +0x03, 0xBB, 0x0A, 0x06, // 136 +0x03, 0xC5, 0x0A, 0x06, // 137 +0xFF, 0xFF, 0x00, 0x0A, // 138 +0xFF, 0xFF, 0x00, 0x0A, // 139 +0xFF, 0xFF, 0x00, 0x0A, // 140 +0xFF, 0xFF, 0x00, 0x0A, // 141 +0xFF, 0xFF, 0x00, 0x0A, // 142 +0xFF, 0xFF, 0x00, 0x0A, // 143 +0xFF, 0xFF, 0x00, 0x0A, // 144 +0xFF, 0xFF, 0x00, 0x0A, // 145 +0xFF, 0xFF, 0x00, 0x0A, // 146 +0x03, 0xCF, 0x0E, 0x08, // 147 +0x03, 0xDD, 0x0A, 0x06, // 148 +0xFF, 0xFF, 0x00, 0x0A, // 149 +0xFF, 0xFF, 0x00, 0x0A, // 150 +0xFF, 0xFF, 0x00, 0x0A, // 151 +0x03, 0xE7, 0x0C, 0x07, // 152 +0x03, 0xF3, 0x0C, 0x07, // 153 +0x03, 0xFF, 0x0C, 0x07, // 154 +0x04, 0x0B, 0x08, 0x05, // 155 +0xFF, 0xFF, 0x00, 0x0A, // 156 +0xFF, 0xFF, 0x00, 0x0A, // 157 +0xFF, 0xFF, 0x00, 0x0A, // 158 +0xFF, 0xFF, 0x00, 0x0A, // 159 +0xFF, 0xFF, 0x00, 0x0A, // 160 +0x04, 0x13, 0x04, 0x03, // 161 +0x04, 0x17, 0x0A, 0x06, // 162 +0x04, 0x21, 0x0C, 0x07, // 163 +0x04, 0x2D, 0x0A, 0x06, // 164 +0x04, 0x37, 0x0A, 0x06, // 165 +0x04, 0x41, 0x04, 0x03, // 166 +0x04, 0x45, 0x0A, 0x06, // 167 +0x04, 0x4F, 0x05, 0x04, // 168 +0x04, 0x54, 0x0D, 0x08, // 169 +0x04, 0x61, 0x07, 0x05, // 170 +0x04, 0x68, 0x0A, 0x06, // 171 +0x04, 0x72, 0x09, 0x06, // 172 +0x04, 0x7B, 0x03, 0x03, // 173 +0x04, 0x7E, 0x0D, 0x08, // 174 +0x04, 0x8B, 0x0B, 0x07, // 175 +0x04, 0x96, 0x07, 0x05, // 176 +0x04, 0x9D, 0x0A, 0x06, // 177 +0x04, 0xA7, 0x05, 0x04, // 178 +0x04, 0xAC, 0x05, 0x04, // 179 +0x04, 0xB1, 0x05, 0x04, // 180 +0x04, 0xB6, 0x0A, 0x06, // 181 +0x04, 0xC0, 0x09, 0x06, // 182 +0x04, 0xC9, 0x03, 0x03, // 183 +0x04, 0xCC, 0x06, 0x04, // 184 +0x04, 0xD2, 0x0C, 0x07, // 185 +0x04, 0xDE, 0x07, 0x05, // 186 +0x04, 0xE5, 0x0C, 0x07, // 187 +0x04, 0xF1, 0x0A, 0x06, // 188 +0x04, 0xFB, 0x10, 0x09, // 189 +0x05, 0x0B, 0x10, 0x09, // 190 +0x05, 0x1B, 0x0A, 0x06, // 191 +0x05, 0x25, 0x0E, 0x08, // 192 +0x05, 0x33, 0x0E, 0x08, // 193 +0x05, 0x41, 0x0E, 0x08, // 194 +0x05, 0x4F, 0x0E, 0x08, // 195 +0x05, 0x5D, 0x0E, 0x08, // 196 +0x05, 0x6B, 0x0E, 0x08, // 197 +0x05, 0x79, 0x12, 0x0A, // 198 +0x05, 0x8B, 0x0C, 0x07, // 199 +0x05, 0x97, 0x0C, 0x07, // 200 +0x05, 0xA3, 0x0C, 0x07, // 201 +0x05, 0xAF, 0x0C, 0x07, // 202 +0x05, 0xBB, 0x0C, 0x07, // 203 +0x05, 0xC7, 0x05, 0x04, // 204 +0x05, 0xCC, 0x04, 0x03, // 205 +0x05, 0xD0, 0x04, 0x03, // 206 +0x05, 0xD4, 0x05, 0x04, // 207 +0x05, 0xD9, 0x0B, 0x07, // 208 +0x05, 0xE4, 0x0C, 0x07, // 209 +0x05, 0xF0, 0x0E, 0x08, // 210 +0x05, 0xFE, 0x0E, 0x08, // 211 +0x06, 0x0C, 0x0E, 0x08, // 212 +0x06, 0x1A, 0x0E, 0x08, // 213 +0x06, 0x28, 0x0E, 0x08, // 214 +0x06, 0x36, 0x0A, 0x06, // 215 +0x06, 0x40, 0x0D, 0x08, // 216 +0x06, 0x4D, 0x0C, 0x07, // 217 +0x06, 0x59, 0x0C, 0x07, // 218 +0x06, 0x65, 0x0C, 0x07, // 219 +0x06, 0x71, 0x0C, 0x07, // 220 +0x06, 0x7D, 0x0D, 0x08, // 221 +0x06, 0x8A, 0x0B, 0x07, // 222 +0x06, 0x95, 0x0C, 0x07, // 223 +0x06, 0xA1, 0x0A, 0x06, // 224 +0x06, 0xAB, 0x0A, 0x06, // 225 +0x06, 0xB5, 0x0A, 0x06, // 226 +0x06, 0xBF, 0x0A, 0x06, // 227 +0x06, 0xC9, 0x0A, 0x06, // 228 +0x06, 0xD3, 0x0A, 0x06, // 229 +0x06, 0xDD, 0x10, 0x09, // 230 +0x06, 0xED, 0x0A, 0x06, // 231 +0x06, 0xF7, 0x0A, 0x06, // 232 +0x07, 0x01, 0x0A, 0x06, // 233 +0x07, 0x0B, 0x0A, 0x06, // 234 +0x07, 0x15, 0x0A, 0x06, // 235 +0x07, 0x1F, 0x05, 0x04, // 236 +0x07, 0x24, 0x04, 0x03, // 237 +0x07, 0x28, 0x05, 0x04, // 238 +0x07, 0x2D, 0x05, 0x04, // 239 +0x07, 0x32, 0x0A, 0x06, // 240 +0x07, 0x3C, 0x0A, 0x06, // 241 +0x07, 0x46, 0x0A, 0x06, // 242 +0x07, 0x50, 0x0A, 0x06, // 243 +0x07, 0x5A, 0x0A, 0x06, // 244 +0x07, 0x64, 0x0A, 0x06, // 245 +0x07, 0x6E, 0x0A, 0x06, // 246 +0x07, 0x78, 0x09, 0x06, // 247 +0x07, 0x81, 0x0A, 0x06, // 248 +0x07, 0x8B, 0x0A, 0x06, // 249 +0x07, 0x95, 0x0A, 0x06, // 250 +0x07, 0x9F, 0x0A, 0x06, // 251 +0x07, 0xA9, 0x0A, 0x06, // 252 +0x07, 0xB3, 0x09, 0x06, // 253 +0x07, 0xBC, 0x0A, 0x06, // 254 +0x07, 0xC6, 0x09, 0x06, // 255 +// Font Data: +0x00, 0x00, 0xF8, 0x02, // 33 +0x38, 0x00, 0x00, 0x00, 0x38, // 34 +0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 +0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 +0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 +0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 +0x38, // 39 +0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 +0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 +0x28, 0x00, 0x18, 0x00, 0x28, // 42 +0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 +0x00, 0x00, 0x00, 0x06, // 44 +0x80, 0x00, 0x80, // 45 +0x00, 0x00, 0x00, 0x02, // 46 +0x00, 0x03, 0xE0, 0x00, 0x18, // 47 +0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 +0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 +0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 +0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 +0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 +0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 +0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 +0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 +0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 +0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 +0x00, 0x00, 0x20, 0x02, // 58 +0x00, 0x00, 0x20, 0x06, // 59 +0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 +0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 +0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 +0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 +0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 +0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 +0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 +0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 +0x00, 0x00, 0xF8, 0x03, // 73 +0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 +0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 +0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 +0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 +0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 +0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 +0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 +0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 +0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 +0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 +0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 +0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 +0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 +0x08, 0x08, 0xF8, 0x0F, // 93 +0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 +0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 +0x08, 0x00, 0x10, // 96 +0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 +0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 +0x20, 0x00, 0xF0, 0x03, 0x28, // 102 +0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 +0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 +0x00, 0x00, 0xE8, 0x03, // 105 +0x00, 0x08, 0xE8, 0x07, // 106 +0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 +0x00, 0x00, 0xF8, 0x03, // 108 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 +0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 +0x00, 0x00, 0xE0, 0x03, 0x20, // 114 +0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 +0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 +0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 +0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 +0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 +0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 +0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 +0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 +0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 +0x00, 0x00, 0xF8, 0x0F, // 124 +0x08, 0x08, 0x78, 0x0F, 0x80, // 125 +0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 +0x00, 0x00, 0xF8, 0x03, 0x40, 0x02, 0x20, 0x02, 0x00, 0x02, 0x00, 0x02, // 129 +0x40, 0x00, 0xF8, 0x03, 0x20, // 130 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x44, 0x00, 0x82, 0x01, 0xF8, 0x03, // 131 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x05, 0x00, 0x0A, // 132 +0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x07, 0x00, 0x08, // 133 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 134 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0x44, 0x01, // 135 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x28, 0x00, 0xC4, 0x03, // 136 +0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 137 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 147 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0xC4, 0x01, // 148 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x06, 0x48, 0x0A, // 152 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x06, 0x00, 0x08, // 153 +0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 154 +0x40, 0x02, 0xA0, 0x02, 0xA8, 0x02, 0x24, 0x01, // 155 +0x00, 0x00, 0xA0, 0x0F, // 161 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162 +0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163 +0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164 +0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165 +0x00, 0x00, 0x38, 0x0F, // 166 +0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167 +0x08, 0x00, 0x00, 0x00, 0x08, // 168 +0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169 +0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170 +0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171 +0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172 +0x80, 0x00, 0x80, // 173 +0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174 +0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175 +0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176 +0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177 +0x48, 0x00, 0x68, 0x00, 0x58, // 178 +0x48, 0x00, 0x58, 0x00, 0x68, // 179 +0x00, 0x00, 0x10, 0x00, 0x08, // 180 +0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181 +0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182 +0x00, 0x00, 0x40, // 183 +0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184 +0x08, 0x03, 0x88, 0x02, 0xCA, 0x02, 0x69, 0x02, 0x38, 0x02, 0x18, 0x02, // 185 +0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186 +0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x6A, 0x02, 0x38, 0x02, 0x18, 0x02, // 187 +0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x60, 0x02, 0x20, 0x02, // 188 +0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189 +0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190 +0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193 +0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194 +0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195 +0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196 +0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197 +0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199 +0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201 +0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202 +0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203 +0x00, 0x00, 0xF9, 0x03, 0x02, // 204 +0x02, 0x00, 0xF9, 0x03, // 205 +0x01, 0x00, 0xFA, 0x03, // 206 +0x02, 0x00, 0xF8, 0x03, 0x02, // 207 +0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208 +0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212 +0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213 +0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214 +0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215 +0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216 +0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220 +0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221 +0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 +0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223 +0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226 +0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228 +0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229 +0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231 +0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235 +0x00, 0x00, 0xE4, 0x03, 0x08, // 236 +0x08, 0x00, 0xE4, 0x03, // 237 +0x08, 0x00, 0xE4, 0x03, 0x08, // 238 +0x08, 0x00, 0xE0, 0x03, 0x08, // 239 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240 +0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241 +0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244 +0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246 +0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247 +0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248 +0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249 +0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250 +0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251 +0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252 +0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253 +0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254 +0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255 +}; \ No newline at end of file diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.h b/src/graphics/fonts/OLEDDisplayFontsPL.h new file mode 100644 index 000000000..59dd92c41 --- /dev/null +++ b/src/graphics/fonts/OLEDDisplayFontsPL.h @@ -0,0 +1,11 @@ +#ifndef OLEDDISPLAYFONTSPL_h +#define OLEDDISPLAYFONTSPL_h + +#ifdef ARDUINO +#include +#elif __MBED__ +#define PROGMEM +#endif + +extern const uint8_t ArialMT_Plain_10_PL[] PROGMEM; +#endif \ No newline at end of file From b8609ff1308ee07814079511127c5cd23f028c22 Mon Sep 17 00:00:00 2001 From: "FW\\AM5" Date: Wed, 28 Aug 2024 13:10:19 +0200 Subject: [PATCH 0968/1377] Support for Polish OLED characters Added support for Polish OLED characters. - Custom FONT_SMALL ArialMT_Plain_10_PL - Automatic selection between Polish and Ukrainian/Russian characters mapping depending on the -D OLED_{LANG_NAME} flage --- .vscode/extensions.json | 7 +- platformio.ini | 3 +- src/graphics/Screen.cpp | 2 +- src/graphics/Screen.h | 50 +++ src/graphics/ScreenFonts.h | 8 + src/graphics/fonts/OLEDDisplayFontsPL.cpp | 440 ++++++++++++++++++++++ src/graphics/fonts/OLEDDisplayFontsPL.h | 11 + 7 files changed, 516 insertions(+), 5 deletions(-) create mode 100644 src/graphics/fonts/OLEDDisplayFontsPL.cpp create mode 100644 src/graphics/fonts/OLEDDisplayFontsPL.h diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b50c95349..080e70d08 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,9 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", - "platformio.platformio-ide", - "trunk.io" + "platformio.platformio-ide" ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] } diff --git a/platformio.ini b/platformio.ini index 4de1ec39f..5c3c4e421 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,6 +81,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS -DRADIOLIB_EXCLUDE_LORAWAN -DMESHTASTIC_EXCLUDE_DROPZONE=1 + ;-D OLED_PL monitor_speed = 115200 monitor_filters = direct @@ -157,4 +158,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3a4db6ee0..7fe5964d5 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -123,7 +123,7 @@ static bool heartbeat = false; /// Check if the display can render a string (detect special chars; emoji) static bool haveGlyphs(const char *str) { -#if defined(OLED_UA) || defined(OLED_RU) +#if defined(OLED_PL) ||defined(OLED_UA) || defined(OLED_RU) // Don't want to make any assumptions about custom language support return true; #endif diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 93e5f2ef7..7db580272 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -322,6 +322,53 @@ class Screen : public concurrency::OSThread uint8_t last = LASTCHAR; // get last char LASTCHAR = ch; +#if defined(OLED_PL) + + switch (last) { // conversion depending on first UTF8-character + case 0xC2: { + SKIPREST = false; + return (uint8_t)ch; + } + case 0xC3: { + + if (ch == 147) + return (uint8_t)(ch); // Ó + else + if (ch == 179) + return (uint8_t)(148); // ó + else + return (uint8_t)(ch | 0xC0); + break; + + } + + case 0xC4: { + SKIPREST = false; + return (uint8_t)(ch); + } + + case 0xC5: { + SKIPREST = false; + if (ch == 132) + return (uint8_t)(136); // ń + else + if (ch == 186) + return (uint8_t)(137); // ź + else + return (uint8_t)(ch); + break; + } + } + + // We want to strip out prefix chars for two-byte char formats + if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5) + return (uint8_t)0; + + +#endif + +#if defined(OLED_UA) || defined(OLED_RU) + switch (last) { // conversion depending on first UTF8-character case 0xC2: { SKIPREST = false; @@ -376,6 +423,9 @@ class Screen : public concurrency::OSThread if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1) return (uint8_t)0; +#endif + + // If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the // rest of it if (SKIPREST) diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 8a48d053e..3e4c220a0 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -1,5 +1,9 @@ #pragma once +#ifdef OLED_PL +#include "graphics/fonts/OLEDDisplayFontsPL.h" +#endif + #ifdef OLED_RU #include "graphics/fonts/OLEDDisplayFontsRU.h" #endif @@ -15,6 +19,9 @@ #define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #else +#ifdef OLED_PL +#define FONT_SMALL ArialMT_Plain_10_PL +#else #ifdef OLED_RU #define FONT_SMALL ArialMT_Plain_10_RU #else @@ -24,6 +31,7 @@ #define FONT_SMALL ArialMT_Plain_10 // Height: 13 #endif #endif +#endif #define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 #define FONT_LARGE ArialMT_Plain_24 // Height: 28 #endif diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.cpp b/src/graphics/fonts/OLEDDisplayFontsPL.cpp new file mode 100644 index 000000000..1322b1772 --- /dev/null +++ b/src/graphics/fonts/OLEDDisplayFontsPL.cpp @@ -0,0 +1,440 @@ +#include "OLEDDisplayFontsPL.h" + +// Font generated or edited with the glyphEditor +const uint8_t ArialMT_Plain_10_PL[] PROGMEM = { +0x0A, // Width: 10 +0x0D, // Height: 13 +0x20, // First char: 32 +0xE0, // Number of chars: 224 + +// Jump Table: +0xFF, 0xFF, 0x00, 0x03, // 32:65535 +0x00, 0x00, 0x04, 0x03, // 33 +0x00, 0x04, 0x05, 0x04, // 34 +0x00, 0x09, 0x09, 0x06, // 35 +0x00, 0x12, 0x0A, 0x06, // 36 +0x00, 0x1C, 0x10, 0x09, // 37 +0x00, 0x2C, 0x0E, 0x08, // 38 +0x00, 0x3A, 0x01, 0x02, // 39 +0x00, 0x3B, 0x06, 0x04, // 40 +0x00, 0x41, 0x06, 0x04, // 41 +0x00, 0x47, 0x05, 0x04, // 42 +0x00, 0x4C, 0x09, 0x06, // 43 +0x00, 0x55, 0x04, 0x03, // 44 +0x00, 0x59, 0x03, 0x03, // 45 +0x00, 0x5C, 0x04, 0x03, // 46 +0x00, 0x60, 0x05, 0x04, // 47 +0x00, 0x65, 0x0A, 0x06, // 48 +0x00, 0x6F, 0x08, 0x05, // 49 +0x00, 0x77, 0x0A, 0x06, // 50 +0x00, 0x81, 0x0A, 0x06, // 51 +0x00, 0x8B, 0x0B, 0x07, // 52 +0x00, 0x96, 0x0A, 0x06, // 53 +0x00, 0xA0, 0x0A, 0x06, // 54 +0x00, 0xAA, 0x09, 0x06, // 55 +0x00, 0xB3, 0x0A, 0x06, // 56 +0x00, 0xBD, 0x0A, 0x06, // 57 +0x00, 0xC7, 0x04, 0x03, // 58 +0x00, 0xCB, 0x04, 0x03, // 59 +0x00, 0xCF, 0x0A, 0x06, // 60 +0x00, 0xD9, 0x09, 0x06, // 61 +0x00, 0xE2, 0x09, 0x06, // 62 +0x00, 0xEB, 0x0B, 0x07, // 63 +0x00, 0xF6, 0x14, 0x0B, // 64 +0x01, 0x0A, 0x0E, 0x08, // 65 +0x01, 0x18, 0x0C, 0x07, // 66 +0x01, 0x24, 0x0C, 0x07, // 67 +0x01, 0x30, 0x0B, 0x07, // 68 +0x01, 0x3B, 0x0C, 0x07, // 69 +0x01, 0x47, 0x09, 0x06, // 70 +0x01, 0x50, 0x0D, 0x08, // 71 +0x01, 0x5D, 0x0C, 0x07, // 72 +0x01, 0x69, 0x04, 0x03, // 73 +0x01, 0x6D, 0x08, 0x05, // 74 +0x01, 0x75, 0x0E, 0x08, // 75 +0x01, 0x83, 0x0C, 0x07, // 76 +0x01, 0x8F, 0x10, 0x09, // 77 +0x01, 0x9F, 0x0C, 0x07, // 78 +0x01, 0xAB, 0x0E, 0x08, // 79 +0x01, 0xB9, 0x0B, 0x07, // 80 +0x01, 0xC4, 0x0E, 0x08, // 81 +0x01, 0xD2, 0x0C, 0x07, // 82 +0x01, 0xDE, 0x0C, 0x07, // 83 +0x01, 0xEA, 0x0B, 0x07, // 84 +0x01, 0xF5, 0x0C, 0x07, // 85 +0x02, 0x01, 0x0D, 0x08, // 86 +0x02, 0x0E, 0x11, 0x0A, // 87 +0x02, 0x1F, 0x0E, 0x08, // 88 +0x02, 0x2D, 0x0D, 0x08, // 89 +0x02, 0x3A, 0x0C, 0x07, // 90 +0x02, 0x46, 0x06, 0x04, // 91 +0x02, 0x4C, 0x06, 0x04, // 92 +0x02, 0x52, 0x04, 0x03, // 93 +0x02, 0x56, 0x09, 0x06, // 94 +0x02, 0x5F, 0x0C, 0x07, // 95 +0x02, 0x6B, 0x03, 0x03, // 96 +0x02, 0x6E, 0x0A, 0x06, // 97 +0x02, 0x78, 0x0A, 0x06, // 98 +0x02, 0x82, 0x0A, 0x06, // 99 +0x02, 0x8C, 0x0A, 0x06, // 100 +0x02, 0x96, 0x0A, 0x06, // 101 +0x02, 0xA0, 0x05, 0x04, // 102 +0x02, 0xA5, 0x0A, 0x06, // 103 +0x02, 0xAF, 0x0A, 0x06, // 104 +0x02, 0xB9, 0x04, 0x03, // 105 +0x02, 0xBD, 0x04, 0x03, // 106 +0x02, 0xC1, 0x08, 0x05, // 107 +0x02, 0xC9, 0x04, 0x03, // 108 +0x02, 0xCD, 0x10, 0x09, // 109 +0x02, 0xDD, 0x0A, 0x06, // 110 +0x02, 0xE7, 0x0A, 0x06, // 111 +0x02, 0xF1, 0x0A, 0x06, // 112 +0x02, 0xFB, 0x0A, 0x06, // 113 +0x03, 0x05, 0x05, 0x04, // 114 +0x03, 0x0A, 0x08, 0x05, // 115 +0x03, 0x12, 0x06, 0x04, // 116 +0x03, 0x18, 0x0A, 0x06, // 117 +0x03, 0x22, 0x09, 0x06, // 118 +0x03, 0x2B, 0x0E, 0x08, // 119 +0x03, 0x39, 0x0A, 0x06, // 120 +0x03, 0x43, 0x09, 0x06, // 121 +0x03, 0x4C, 0x0A, 0x06, // 122 +0x03, 0x56, 0x06, 0x04, // 123 +0x03, 0x5C, 0x04, 0x03, // 124 +0x03, 0x60, 0x05, 0x04, // 125 +0x03, 0x65, 0x09, 0x06, // 126 +0xFF, 0xFF, 0x00, 0x0A, // 127 +0xFF, 0xFF, 0x00, 0x0A, // 128 +0x03, 0x6E, 0x0C, 0x07, // 129 +0x03, 0x7A, 0x05, 0x04, // 130 +0x03, 0x7F, 0x0C, 0x07, // 131 +0x03, 0x8B, 0x0E, 0x08, // 132 +0x03, 0x99, 0x0C, 0x07, // 133 +0x03, 0xA5, 0x0C, 0x07, // 134 +0x03, 0xB1, 0x0A, 0x06, // 135 +0x03, 0xBB, 0x0A, 0x06, // 136 +0x03, 0xC5, 0x0A, 0x06, // 137 +0xFF, 0xFF, 0x00, 0x0A, // 138 +0xFF, 0xFF, 0x00, 0x0A, // 139 +0xFF, 0xFF, 0x00, 0x0A, // 140 +0xFF, 0xFF, 0x00, 0x0A, // 141 +0xFF, 0xFF, 0x00, 0x0A, // 142 +0xFF, 0xFF, 0x00, 0x0A, // 143 +0xFF, 0xFF, 0x00, 0x0A, // 144 +0xFF, 0xFF, 0x00, 0x0A, // 145 +0xFF, 0xFF, 0x00, 0x0A, // 146 +0x03, 0xCF, 0x0E, 0x08, // 147 +0x03, 0xDD, 0x0A, 0x06, // 148 +0xFF, 0xFF, 0x00, 0x0A, // 149 +0xFF, 0xFF, 0x00, 0x0A, // 150 +0xFF, 0xFF, 0x00, 0x0A, // 151 +0x03, 0xE7, 0x0C, 0x07, // 152 +0x03, 0xF3, 0x0C, 0x07, // 153 +0x03, 0xFF, 0x0C, 0x07, // 154 +0x04, 0x0B, 0x08, 0x05, // 155 +0xFF, 0xFF, 0x00, 0x0A, // 156 +0xFF, 0xFF, 0x00, 0x0A, // 157 +0xFF, 0xFF, 0x00, 0x0A, // 158 +0xFF, 0xFF, 0x00, 0x0A, // 159 +0xFF, 0xFF, 0x00, 0x0A, // 160 +0x04, 0x13, 0x04, 0x03, // 161 +0x04, 0x17, 0x0A, 0x06, // 162 +0x04, 0x21, 0x0C, 0x07, // 163 +0x04, 0x2D, 0x0A, 0x06, // 164 +0x04, 0x37, 0x0A, 0x06, // 165 +0x04, 0x41, 0x04, 0x03, // 166 +0x04, 0x45, 0x0A, 0x06, // 167 +0x04, 0x4F, 0x05, 0x04, // 168 +0x04, 0x54, 0x0D, 0x08, // 169 +0x04, 0x61, 0x07, 0x05, // 170 +0x04, 0x68, 0x0A, 0x06, // 171 +0x04, 0x72, 0x09, 0x06, // 172 +0x04, 0x7B, 0x03, 0x03, // 173 +0x04, 0x7E, 0x0D, 0x08, // 174 +0x04, 0x8B, 0x0B, 0x07, // 175 +0x04, 0x96, 0x07, 0x05, // 176 +0x04, 0x9D, 0x0A, 0x06, // 177 +0x04, 0xA7, 0x05, 0x04, // 178 +0x04, 0xAC, 0x05, 0x04, // 179 +0x04, 0xB1, 0x05, 0x04, // 180 +0x04, 0xB6, 0x0A, 0x06, // 181 +0x04, 0xC0, 0x09, 0x06, // 182 +0x04, 0xC9, 0x03, 0x03, // 183 +0x04, 0xCC, 0x06, 0x04, // 184 +0x04, 0xD2, 0x0C, 0x07, // 185 +0x04, 0xDE, 0x07, 0x05, // 186 +0x04, 0xE5, 0x0C, 0x07, // 187 +0x04, 0xF1, 0x0A, 0x06, // 188 +0x04, 0xFB, 0x10, 0x09, // 189 +0x05, 0x0B, 0x10, 0x09, // 190 +0x05, 0x1B, 0x0A, 0x06, // 191 +0x05, 0x25, 0x0E, 0x08, // 192 +0x05, 0x33, 0x0E, 0x08, // 193 +0x05, 0x41, 0x0E, 0x08, // 194 +0x05, 0x4F, 0x0E, 0x08, // 195 +0x05, 0x5D, 0x0E, 0x08, // 196 +0x05, 0x6B, 0x0E, 0x08, // 197 +0x05, 0x79, 0x12, 0x0A, // 198 +0x05, 0x8B, 0x0C, 0x07, // 199 +0x05, 0x97, 0x0C, 0x07, // 200 +0x05, 0xA3, 0x0C, 0x07, // 201 +0x05, 0xAF, 0x0C, 0x07, // 202 +0x05, 0xBB, 0x0C, 0x07, // 203 +0x05, 0xC7, 0x05, 0x04, // 204 +0x05, 0xCC, 0x04, 0x03, // 205 +0x05, 0xD0, 0x04, 0x03, // 206 +0x05, 0xD4, 0x05, 0x04, // 207 +0x05, 0xD9, 0x0B, 0x07, // 208 +0x05, 0xE4, 0x0C, 0x07, // 209 +0x05, 0xF0, 0x0E, 0x08, // 210 +0x05, 0xFE, 0x0E, 0x08, // 211 +0x06, 0x0C, 0x0E, 0x08, // 212 +0x06, 0x1A, 0x0E, 0x08, // 213 +0x06, 0x28, 0x0E, 0x08, // 214 +0x06, 0x36, 0x0A, 0x06, // 215 +0x06, 0x40, 0x0D, 0x08, // 216 +0x06, 0x4D, 0x0C, 0x07, // 217 +0x06, 0x59, 0x0C, 0x07, // 218 +0x06, 0x65, 0x0C, 0x07, // 219 +0x06, 0x71, 0x0C, 0x07, // 220 +0x06, 0x7D, 0x0D, 0x08, // 221 +0x06, 0x8A, 0x0B, 0x07, // 222 +0x06, 0x95, 0x0C, 0x07, // 223 +0x06, 0xA1, 0x0A, 0x06, // 224 +0x06, 0xAB, 0x0A, 0x06, // 225 +0x06, 0xB5, 0x0A, 0x06, // 226 +0x06, 0xBF, 0x0A, 0x06, // 227 +0x06, 0xC9, 0x0A, 0x06, // 228 +0x06, 0xD3, 0x0A, 0x06, // 229 +0x06, 0xDD, 0x10, 0x09, // 230 +0x06, 0xED, 0x0A, 0x06, // 231 +0x06, 0xF7, 0x0A, 0x06, // 232 +0x07, 0x01, 0x0A, 0x06, // 233 +0x07, 0x0B, 0x0A, 0x06, // 234 +0x07, 0x15, 0x0A, 0x06, // 235 +0x07, 0x1F, 0x05, 0x04, // 236 +0x07, 0x24, 0x04, 0x03, // 237 +0x07, 0x28, 0x05, 0x04, // 238 +0x07, 0x2D, 0x05, 0x04, // 239 +0x07, 0x32, 0x0A, 0x06, // 240 +0x07, 0x3C, 0x0A, 0x06, // 241 +0x07, 0x46, 0x0A, 0x06, // 242 +0x07, 0x50, 0x0A, 0x06, // 243 +0x07, 0x5A, 0x0A, 0x06, // 244 +0x07, 0x64, 0x0A, 0x06, // 245 +0x07, 0x6E, 0x0A, 0x06, // 246 +0x07, 0x78, 0x09, 0x06, // 247 +0x07, 0x81, 0x0A, 0x06, // 248 +0x07, 0x8B, 0x0A, 0x06, // 249 +0x07, 0x95, 0x0A, 0x06, // 250 +0x07, 0x9F, 0x0A, 0x06, // 251 +0x07, 0xA9, 0x0A, 0x06, // 252 +0x07, 0xB3, 0x09, 0x06, // 253 +0x07, 0xBC, 0x0A, 0x06, // 254 +0x07, 0xC6, 0x09, 0x06, // 255 +// Font Data: +0x00, 0x00, 0xF8, 0x02, // 33 +0x38, 0x00, 0x00, 0x00, 0x38, // 34 +0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 +0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 +0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 +0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 +0x38, // 39 +0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 +0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 +0x28, 0x00, 0x18, 0x00, 0x28, // 42 +0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 +0x00, 0x00, 0x00, 0x06, // 44 +0x80, 0x00, 0x80, // 45 +0x00, 0x00, 0x00, 0x02, // 46 +0x00, 0x03, 0xE0, 0x00, 0x18, // 47 +0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 +0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 +0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 +0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 +0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 +0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 +0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 +0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 +0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 +0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 +0x00, 0x00, 0x20, 0x02, // 58 +0x00, 0x00, 0x20, 0x06, // 59 +0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 +0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 +0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 +0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 +0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 +0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 +0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 +0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 +0x00, 0x00, 0xF8, 0x03, // 73 +0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 +0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 +0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 +0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 +0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 +0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 +0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 +0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 +0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 +0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 +0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 +0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 +0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 +0x08, 0x08, 0xF8, 0x0F, // 93 +0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 +0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 +0x08, 0x00, 0x10, // 96 +0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 +0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 +0x20, 0x00, 0xF0, 0x03, 0x28, // 102 +0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 +0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 +0x00, 0x00, 0xE8, 0x03, // 105 +0x00, 0x08, 0xE8, 0x07, // 106 +0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 +0x00, 0x00, 0xF8, 0x03, // 108 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 +0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 +0x00, 0x00, 0xE0, 0x03, 0x20, // 114 +0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 +0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 +0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 +0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 +0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 +0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 +0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 +0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 +0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 +0x00, 0x00, 0xF8, 0x0F, // 124 +0x08, 0x08, 0x78, 0x0F, 0x80, // 125 +0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 +0x00, 0x00, 0xF8, 0x03, 0x40, 0x02, 0x20, 0x02, 0x00, 0x02, 0x00, 0x02, // 129 +0x40, 0x00, 0xF8, 0x03, 0x20, // 130 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x44, 0x00, 0x82, 0x01, 0xF8, 0x03, // 131 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x05, 0x00, 0x0A, // 132 +0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x07, 0x00, 0x08, // 133 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 134 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0x44, 0x01, // 135 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x28, 0x00, 0xC4, 0x03, // 136 +0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 137 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 147 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0xC4, 0x01, // 148 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x06, 0x48, 0x0A, // 152 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x06, 0x00, 0x08, // 153 +0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 154 +0x40, 0x02, 0xA0, 0x02, 0xA8, 0x02, 0x24, 0x01, // 155 +0x00, 0x00, 0xA0, 0x0F, // 161 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162 +0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163 +0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164 +0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165 +0x00, 0x00, 0x38, 0x0F, // 166 +0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167 +0x08, 0x00, 0x00, 0x00, 0x08, // 168 +0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169 +0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170 +0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171 +0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172 +0x80, 0x00, 0x80, // 173 +0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174 +0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175 +0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176 +0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177 +0x48, 0x00, 0x68, 0x00, 0x58, // 178 +0x48, 0x00, 0x58, 0x00, 0x68, // 179 +0x00, 0x00, 0x10, 0x00, 0x08, // 180 +0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181 +0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182 +0x00, 0x00, 0x40, // 183 +0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184 +0x08, 0x03, 0x88, 0x02, 0xCA, 0x02, 0x69, 0x02, 0x38, 0x02, 0x18, 0x02, // 185 +0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186 +0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x6A, 0x02, 0x38, 0x02, 0x18, 0x02, // 187 +0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x60, 0x02, 0x20, 0x02, // 188 +0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189 +0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190 +0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193 +0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194 +0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195 +0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196 +0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197 +0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199 +0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201 +0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202 +0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203 +0x00, 0x00, 0xF9, 0x03, 0x02, // 204 +0x02, 0x00, 0xF9, 0x03, // 205 +0x01, 0x00, 0xFA, 0x03, // 206 +0x02, 0x00, 0xF8, 0x03, 0x02, // 207 +0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208 +0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212 +0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213 +0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214 +0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215 +0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216 +0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220 +0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221 +0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 +0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223 +0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226 +0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228 +0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229 +0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231 +0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235 +0x00, 0x00, 0xE4, 0x03, 0x08, // 236 +0x08, 0x00, 0xE4, 0x03, // 237 +0x08, 0x00, 0xE4, 0x03, 0x08, // 238 +0x08, 0x00, 0xE0, 0x03, 0x08, // 239 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240 +0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241 +0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244 +0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246 +0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247 +0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248 +0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249 +0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250 +0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251 +0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252 +0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253 +0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254 +0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255 +}; \ No newline at end of file diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.h b/src/graphics/fonts/OLEDDisplayFontsPL.h new file mode 100644 index 000000000..59dd92c41 --- /dev/null +++ b/src/graphics/fonts/OLEDDisplayFontsPL.h @@ -0,0 +1,11 @@ +#ifndef OLEDDISPLAYFONTSPL_h +#define OLEDDISPLAYFONTSPL_h + +#ifdef ARDUINO +#include +#elif __MBED__ +#define PROGMEM +#endif + +extern const uint8_t ArialMT_Plain_10_PL[] PROGMEM; +#endif \ No newline at end of file From d1e64c74dee780f2b3f4200a5aa8b92f6e258812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Cort=C3=AAs?= Date: Mon, 26 Aug 2024 17:15:42 +0100 Subject: [PATCH 0969/1377] Fix devcontainer Dockerfile build --- .devcontainer/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 82f4d91bf..c21564491 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -12,6 +12,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ libssl-dev \ libulfius-dev \ libyaml-cpp-dev \ + pipx \ pkg-config \ python3 \ python3-pip \ @@ -21,4 +22,4 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ zip \ && apt-get clean && rm -rf /var/lib/apt/lists/* -RUN pip3 install --no-cache-dir -U platformio==6.1.15 \ No newline at end of file +RUN pipx install platformio==6.1.15 \ No newline at end of file From f27281d3fa2dd4e5d51db22300230e1b04c68a7a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Aug 2024 06:43:30 -0500 Subject: [PATCH 0970/1377] Fix super tiny T1114 tft font size and fork repo to fix compiler warnings (#4573) --- src/graphics/Screen.cpp | 13 ++++++++----- src/graphics/ScreenFonts.h | 3 ++- src/graphics/images.h | 4 ++-- variants/heltec_mesh_node_t114/platformio.ini | 2 +- variants/heltec_vision_master_t190/platformio.ini | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3a4db6ee0..2690d0b70 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1093,7 +1093,8 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat { char usersString[20]; snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x, y + 3, 8, 8, imgUser); #else @@ -2414,7 +2415,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #ifdef ARCH_ESP32 if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -2425,7 +2427,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 imgQuestion); #endif } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -2439,8 +2442,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #endif } else { // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ - ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 8a48d053e..41e739fa1 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -8,7 +8,8 @@ #include "graphics/fonts/OLEDDisplayFontsUA.h" #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL ArialMT_Plain_16 // Height: 19 diff --git a/src/graphics/images.h b/src/graphics/images.h index d4c738610..ab3767a89 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -20,8 +20,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ - ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini index 99bdf77a7..c2a458f78 100644 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -12,4 +12,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb \ No newline at end of file + https://github.com/meshtastic/st7789#7181320e7ed05c7fb5fd2d32f14723bce6088b7b \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini index 38a3169e8..fd0001439 100644 --- a/variants/heltec_vision_master_t190/platformio.ini +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -9,5 +9,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb + https://github.com/meshtastic/st7789#7181320e7ed05c7fb5fd2d32f14723bce6088b7b upload_speed = 921600 \ No newline at end of file From 5b3579af52bec586743f87922c047d739cb7562d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 28 Aug 2024 13:51:44 +0200 Subject: [PATCH 0971/1377] trunk upgrade (#4574) --- .trunk/trunk.yaml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 2d9f60899..b20a1ec95 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,36 +1,36 @@ version: 0.1 cli: - version: 1.22.2 + version: 1.22.3 plugins: sources: - id: trunk - ref: v1.5.0 + ref: v1.6.2 uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.76.3 + - trufflehog@3.81.9 - yamllint@1.35.1 - - bandit@1.7.8 - - checkov@3.2.95 + - bandit@1.7.9 + - checkov@3.2.238 - terrascan@1.19.1 - - trivy@0.51.1 + - trivy@0.54.1 #- trufflehog@3.63.2-rc0 - - taplo@0.8.1 - - ruff@0.4.4 + - taplo@0.9.3 + - ruff@0.6.2 - isort@5.13.2 - - markdownlint@0.40.0 - - oxipng@9.1.1 + - markdownlint@0.41.0 + - oxipng@9.1.2 - svgo@3.3.2 - - actionlint@1.7.0 - - flake8@7.0.0 + - actionlint@1.7.1 + - flake8@7.1.1 - hadolint@2.12.0 - shfmt@3.6.0 - shellcheck@0.10.0 - - black@24.4.2 + - black@24.8.0 - git-diff-check - - gitleaks@8.18.2 + - gitleaks@8.18.4 - clang-format@16.0.3 - - prettier@3.2.5 + - prettier@3.3.3 ignore: - linters: [ALL] paths: From a34170654c083bce032c5ec9b7bd796d709ae085 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Aug 2024 07:24:41 -0500 Subject: [PATCH 0972/1377] Fix T1000-E default to turn on buzzer for Ext. Notification (#4575) --- src/mesh/NodeDB.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8409eaff6..004f27a0d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -395,6 +395,12 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; +#if defined(PIN_BUZZER) + moduleConfig.external_notification.enabled = true; + moduleConfig.external_notification.output_buzzer = PIN_BUZZER; + moduleConfig.external_notification.alert_message_buzzer = true; + moduleConfig.external_notification.nag_timeout = 60; +#endif #if defined(RAK4630) || defined(RAK11310) // Default to RAK led pin 2 (blue) moduleConfig.external_notification.enabled = true; From 50f06840d756d3d34f68237956d47b5d13ed1688 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Aug 2024 07:54:50 -0500 Subject: [PATCH 0973/1377] Add button secondary and enable scan-select on T190 (#4577) --- variants/heltec_vision_master_t190/variant.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h index 726f6d864..1da3f9971 100644 --- a/variants/heltec_vision_master_t190/variant.h +++ b/variants/heltec_vision_master_t190/variant.h @@ -1,4 +1,6 @@ #define BUTTON_PIN 0 +#define BUTTON_PIN_SECONDARY 21 // Second built-in button +#define BUTTON_SECONDARY_CANNEDMESSAGES // By default, use the secondary button as canned message input // I2C #define I2C_SDA SDA From 06175737ccb25ea69688023ffd02c05d4fdd8291 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 22 Aug 2024 20:57:03 -0500 Subject: [PATCH 0974/1377] Save nodedb after favoriting (or removing) (#4537) --- src/modules/AdminModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index d64aea5d8..ef60a4bf2 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -238,6 +238,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node); if (node != NULL) { node->is_favorite = true; + saveChanges(SEGMENT_DEVICESTATE, false); } break; } @@ -246,6 +247,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node); if (node != NULL) { node->is_favorite = false; + saveChanges(SEGMENT_DEVICESTATE, false); } break; } From 710fdbd4e530d244efc86841e2432afa6058da0e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Aug 2024 06:25:40 -0500 Subject: [PATCH 0975/1377] Adds has_x bools to position packet. (#4540) --- src/modules/PositionModule.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f534baf67..2a0c95a9b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -187,16 +187,23 @@ meshtastic_MeshPacket *PositionModule::allocReply() p.longitude_i = localPosition.longitude_i; } p.precision_bits = precision; + p.has_latitude_i = true; + p.has_longitude_i = true; p.time = getValidTime(RTCQualityNTP) > 0 ? getValidTime(RTCQualityNTP) : localPosition.time; if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE) { - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE_MSL) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE_MSL) { p.altitude = localPosition.altitude; - else + p.has_altitude = true; + } else { p.altitude_hae = localPosition.altitude_hae; + p.has_altitude_hae = true; + } - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_GEOIDAL_SEPARATION) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_GEOIDAL_SEPARATION) { p.altitude_geoidal_separation = localPosition.altitude_geoidal_separation; + p.has_altitude_geoidal_separation = true; + } } if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_DOP) { @@ -216,11 +223,15 @@ meshtastic_MeshPacket *PositionModule::allocReply() if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_SEQ_NO) p.seq_number = localPosition.seq_number; - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_HEADING) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_HEADING) { p.ground_track = localPosition.ground_track; + p.has_ground_track = true; + } - if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_SPEED) + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_SPEED) { p.ground_speed = localPosition.ground_speed; + p.has_ground_speed = true; + } // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS or NTP to include the time, so that devices @@ -471,4 +482,4 @@ void PositionModule::handleNewPosition() } } -#endif +#endif \ No newline at end of file From 9b2ef971c219e2c40336584d6592cf6f874a0311 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Aug 2024 06:26:19 -0500 Subject: [PATCH 0976/1377] Fix copyPasta in NodeDB (#4538) --- src/mesh/NodeDB.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1caaaf39b..34d3e4ce9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -556,13 +556,8 @@ void NodeDB::cleanupMeshDB() for (int i = 0; i < numMeshNodes; i++) { if (meshNodes->at(i).has_user) { if (meshNodes->at(i).user.public_key.size > 0) { - for (int j = 0; j < numMeshNodes; j++) { - if (meshNodes->at(i).user.public_key.bytes[j] != 0) { - break; - } - if (j == 31) { - meshNodes->at(i).user.public_key.size = 0; - } + if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) { + meshNodes->at(i).user.public_key.size = 0; } } meshNodes->at(newPos++) = meshNodes->at(i); From de41a054b0a399123c141d08e043fe8567a502c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:28:23 +0200 Subject: [PATCH 0977/1377] Initial support for RadioMaster Bandit. (#4523) * Initial support for RadioMaster Bandit. * Different lighting can be made for Button 1 & 2 on the Bandit. Changes to AmbientLighting will turn off af shutdown(). * Trunk * Trunk again. --- platformio.ini | 1 + src/AmbientLightingThread.h | 59 ++++++++- src/mesh/RF95Interface.cpp | 26 +++- src/platform/esp32/architecture.h | 2 + .../radiomaster_900_bandit/platformio.ini | 14 ++ variants/radiomaster_900_bandit/variant.h | 121 ++++++++++++++++++ 6 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 variants/radiomaster_900_bandit/platformio.ini create mode 100644 variants/radiomaster_900_bandit/variant.h diff --git a/platformio.ini b/platformio.ini index 5ad7d60a2..4de1ec39f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,6 +34,7 @@ default_envs = tbeam ;default_envs = wio-e5 ;default_envs = radiomaster_900_bandit_nano ;default_envs = radiomaster_900_bandit_micro +;default_envs = radiomaster_900_bandit ;default_envs = heltec_capsule_sensor_v3 ;default_envs = heltec_vision_master_t190 ;default_envs = heltec_vision_master_e213 diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 6b3360b1f..fdd4b53fa 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -1,3 +1,4 @@ +#include "Observer.h" #include "configuration.h" #ifdef HAS_NCP5623 @@ -22,10 +23,18 @@ class AmbientLightingThread : public concurrency::OSThread public: explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLightingThread") { + notifyDeepSleepObserver.observe(¬ifyDeepSleep); // Let us know when shutdown() is issued. + +// Enables Ambient Lighting by default if conditions are meet. +#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE) +#ifdef ENABLE_AMBIENTLIGHTING + moduleConfig.ambient_lighting.led_state = true; +#endif +#endif // Uncomment to test module // moduleConfig.ambient_lighting.led_state = true; // moduleConfig.ambient_lighting.current = 10; - // // Default to a color based on our node number + // Default to a color based on our node number // moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; @@ -82,9 +91,46 @@ class AmbientLightingThread : public concurrency::OSThread return disable(); } + // When shutdown() is issued, setLightingOff will be called. + CallbackObserver notifyDeepSleepObserver = + CallbackObserver(this, &AmbientLightingThread::setLightingOff); + private: ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE; + // Turn RGB lighting off, is used in junction to shutdown() + int setLightingOff(void *unused) + { +#ifdef HAS_NCP5623 + rgb.setCurrent(0); + rgb.setRed(0); + rgb.setGreen(0); + rgb.setBlue(0); + LOG_INFO("Turn Off NCP5623 Ambient lighting.\n"); +#endif +#ifdef HAS_NEOPIXEL + pixels.clear(); + pixels.show(); + LOG_INFO("Turn Off NeoPixel Ambient lighting.\n"); +#endif +#ifdef RGBLED_CA + analogWrite(RGBLED_RED, 255 - 0); + analogWrite(RGBLED_GREEN, 255 - 0); + analogWrite(RGBLED_BLUE, 255 - 0); + LOG_INFO("Turn Off Ambient lighting RGB Common Anode.\n"); +#elif defined(RGBLED_RED) + analogWrite(RGBLED_RED, 0); + analogWrite(RGBLED_GREEN, 0); + analogWrite(RGBLED_BLUE, 0); + LOG_INFO("Turn Off Ambient lighting RGB Common Cathode.\n"); +#endif +#ifdef UNPHONE + unphone.rgb(0, 0, 0); + LOG_INFO("Turn Off unPhone Ambient lighting.\n"); +#endif + return 0; + } + void setLighting() { #ifdef HAS_NCP5623 @@ -100,6 +146,17 @@ class AmbientLightingThread : public concurrency::OSThread pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue), 0, NEOPIXEL_COUNT); + +// RadioMaster Bandit has addressable LED at the two buttons +// this allow us to set different lighting for them in variant.h file. +#ifdef RADIOMASTER_900_BANDIT +#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX) + pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1); +#endif +#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX) + pixels.fill(BUTTON2_COLOR, BUTTON1_COLOR_INDEX, 1); +#endif +#endif pixels.show(); LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index b6083e7f9..25df3258f 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -16,7 +16,7 @@ // In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING // if you set power to something higher than 17 or 20 you might fry your board. -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) // Structure to hold DAC and DB values typedef struct { uint8_t dac; @@ -40,12 +40,23 @@ DACDB getDACandDB(uint8_t dbm) static const struct { uint8_t dbm; DACDB values; - } dbmToDACDB[] = { + } +#ifdef RADIOMASTER_900_BANDIT_NANO + dbmToDACDB[] = { {20, {168, 2}}, // 100mW {24, {148, 6}}, // 250mW {27, {128, 9}}, // 500mW {30, {90, 12}} // 1000mW }; +#endif +#ifdef RADIOMASTER_900_BANDIT + dbmToDACDB[] = { + {20, {165, 2}}, // 100mW + {24, {155, 6}}, // 250mW + {27, {142, 9}}, // 500mW + {30, {110, 10}} // 1000mW + }; +#endif const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]); // Find the interval dbm falls within and interpolate @@ -56,7 +67,12 @@ DACDB getDACandDB(uint8_t dbm) } // Return a default value if no match is found and default to 100mW +#ifdef RADIOMASTER_900_BANDIT_NANO DACDB defaultValue = {168, 2}; +#endif +#ifdef RADIOMASTER_900_BANDIT + DACDB defaultValue = {165, 2}; +#endif return defaultValue; } #endif @@ -95,7 +111,7 @@ bool RF95Interface::init() { RadioLibInterface::init(); -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) // DAC and DB values based on dBm using interpolation DACDB dacDbValues = getDACandDB(power); int8_t powerDAC = dacDbValues.dac; @@ -117,7 +133,7 @@ bool RF95Interface::init() // enable PA #ifdef RF95_PA_EN #if defined(RF95_PA_DAC_EN) -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) // Use calculated DAC value dacWrite(RF95_PA_EN, powerDAC); #else @@ -163,7 +179,7 @@ bool RF95Interface::init() LOG_INFO("Frequency set to %f\n", getFreq()); LOG_INFO("Bandwidth set to %f\n", bw); LOG_INFO("Power output set to %d\n", power); -#ifdef RADIOMASTER_900_BANDIT_NANO +#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) LOG_INFO("DAC output set to %d\n", powerDAC); #endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index b6def5b01..3761235a0 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -152,6 +152,8 @@ #define HW_VENDOR meshtastic_HardwareModel_WIPHONE #elif defined(RADIOMASTER_900_BANDIT_NANO) #define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO +#elif defined(RADIOMASTER_900_BANDIT) +#define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT #elif defined(HELTEC_CAPSULE_SENSOR_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 #elif defined(HELTEC_VISION_MASTER_T190) diff --git a/variants/radiomaster_900_bandit/platformio.ini b/variants/radiomaster_900_bandit/platformio.ini new file mode 100644 index 000000000..4ff8a6ea2 --- /dev/null +++ b/variants/radiomaster_900_bandit/platformio.ini @@ -0,0 +1,14 @@ +[env:radiomaster_900_bandit] +extends = esp32_base +board = esp32doit-devkit-v1 +build_flags = + ${esp32_base.build_flags} + -DRADIOMASTER_900_BANDIT + -DVTABLES_IN_FLASH=1 + -DCONFIG_DISABLE_HAL_LOCKS=1 + -O2 + -Ivariants/radiomaster_900_bandit +board_build.f_cpu = 240000000L +upload_protocol = esptool +lib_deps = + ${esp32_base.lib_deps} \ No newline at end of file diff --git a/variants/radiomaster_900_bandit/variant.h b/variants/radiomaster_900_bandit/variant.h new file mode 100644 index 000000000..0499970f5 --- /dev/null +++ b/variants/radiomaster_900_bandit/variant.h @@ -0,0 +1,121 @@ +/* + Initial settings and work by https://github.com/gjelsoe + Unit provided by Radio Master RC + https://radiomasterrc.com/products/bandit-expresslrs-rf-module with 1.29" OLED display CH1115 driver +*/ + +/* + On this model then screen is NOT upside down, don't flip it for the user. +*/ +#undef DISPLAY_FLIP_SCREEN + +/* + I2C SDA and SCL. + 0x18 - STK8XXX Accelerometer, Not supported yet. + 0x3C - SH1115 Display Driver +*/ +#define I2C_SDA 14 +#define I2C_SCL 12 + +/* + No GPS - but free pins are available. +*/ +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +/* + Pin connections from ESP32-D0WDQ6 to SX1276. +*/ +#define LORA_DIO0 22 +#define LORA_DIO1 21 +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 +#define LORA_CS 4 +#define LORA_RESET 5 +#define LORA_TXEN 33 + +/* + This unit has a FAN built-in. + FAN is active at 250mW on it's ExpressLRS Firmware. + This FAN has TACHO signal on Pin 27 for use with PWM. +*/ +#define RF95_FAN_EN 2 + +/* + LED PIN setup and it has a NeoPixel LED. + It's possible to setup colors for Button 1 and 2, + look at BUTTON1_COLOR, BUTTON1_COLOR_INDEX, BUTTON2_COLOR and BUTTON2_COLOR_INDEX + this is done here for now. +*/ +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 6 // How many neopixels are connected +#define NEOPIXEL_DATA 15 // GPIO pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // Type of neopixels in use +#define ENABLE_AMBIENTLIGHTING // Turn on Ambient Lighting +// #define BUTTON1_COLOR 0xFF0000 // Background light for Button 1 in HEX RGB Color (RadioMaster Bandit only). +// #define BUTTON1_COLOR_INDEX 0 // NeoPixel Index ID for Button 1 +// #define BUTTON2_COLOR 0x0000FF // Background light for Button 2 in HEX RGB Color (RadioMaster Bandit only). +// #define BUTTON2_COLOR_INDEX 1 // NeoPixel Index ID for Button 2 + +/* + It has 1 x five-way and 2 x normal buttons. + + Button GPIO RGB Index + --------------------------- + Five-way 39 - + Button 1 34 0 + Button 2 35 1 + + Five way button when using ADC. + 2.632V, 2.177V, 1.598V, 1.055V, 0V + + ADC Values: + { UP, DOWN, LEFT, RIGHT, ENTER, IDLE } + 3227, 0 ,1961, 2668, 1290, 4095 + + Five way button when using ADC. + https://github.com/ExpressLRS/targets/blob/f3215b5ec891108db1a13523e4163950cfcadaac/TX/Radiomaster%20Bandit.json#L41 + +*/ +#define INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE +#define PIN_JOYSTICK 39 +#define JOYSTICK_ADC_VALS /*UP*/ 3227, /*DOWN*/ 0, /*LEFT*/ 1961, /*RIGHT*/ 2668, /*OK*/ 1290, /*IDLE*/ 4095 + +/* + Normal Button Pin setup. +*/ +#define BUTTON_PIN 34 +#define BUTTON_NEED_PULLUP + +/* + No External notification. +*/ +#undef EXT_NOTIFY_OUT + +/* + Remapping PIN Names. + Note, that this unit uses RFO +*/ +#define USE_RF95 +#define USE_RF95_RFO +#define RF95_CS LORA_CS +#define RF95_DIO1 LORA_DIO1 +#define RF95_TXEN LORA_TXEN +#define RF95_RESET LORA_RESET +#define RF95_MAX_POWER 10 + +/* + This module has Skyworks SKY66122 controlled by dacWrite + power ranging from 100mW to 1000mW. + + Mapping of PA_LEVEL to Power output: GPIO26/dacWrite + 168 -> 100mW + 155 -> 250mW + 142 -> 500mW + 110 -> 1000mW +*/ +#define RF95_PA_EN 26 +#define RF95_PA_DAC_EN +#define RF95_PA_LEVEL 110 \ No newline at end of file From fe9a80a4e0a9b88fd04229c2b4e18e1cf8033080 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Fri, 23 Aug 2024 05:03:29 -0700 Subject: [PATCH 0978/1377] Use the '+' wildcard for MQTT rather than '#', to subscribe only to topics one nesting level deep (#4528) --- src/mqtt/MQTT.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 22f68bac8..2f7e82e3d 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -376,12 +376,12 @@ void MQTT::sendSubscriptions() const auto &ch = channels.getByIndex(i); if (ch.settings.downlink_enabled) { hasDownlink = true; - std::string topic = cryptTopic + channels.getGlobalId(i) + "/#"; + std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled == true) { - std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/#"; + std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } @@ -390,7 +390,7 @@ void MQTT::sendSubscriptions() } #if !MESHTASTIC_EXCLUDE_PKI if (hasDownlink) { - std::string topic = cryptTopic + "PKI/#"; + std::string topic = cryptTopic + "PKI/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); } @@ -674,4 +674,4 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type (json.find("payload") != json.end()); // should have a payload -} \ No newline at end of file +} From 9de0b7cfac56a0e8ceea13e48112d8ddd88a8555 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 23 Aug 2024 07:04:34 -0500 Subject: [PATCH 0979/1377] Found more places to set explicit has_optional on position (#4542) --- src/mesh/TypeConversions.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index d8ee6afc7..bcff0b817 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -16,8 +16,14 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo if (lite->has_position) { info.has_position = true; + if (lite->position.latitude_i > 0) + info.position.has_latitude_i = true; info.position.latitude_i = lite->position.latitude_i; + if (lite->position.longitude_i > 0) + info.position.has_longitude_i = true; info.position.longitude_i = lite->position.longitude_i; + if (lite->position.altitude > 0) + info.position.has_altitude = true; info.position.altitude = lite->position.altitude; info.position.location_source = lite->position.location_source; info.position.time = lite->position.time; @@ -48,8 +54,14 @@ meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Positi meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite lite) { meshtastic_Position position = meshtastic_Position_init_default; + if (lite.latitude_i > 0) + position.has_latitude_i = true; position.latitude_i = lite.latitude_i; + if (lite.longitude_i > 0) + position.has_longitude_i = true; position.longitude_i = lite.longitude_i; + if (lite.altitude > 0) + position.has_altitude = true; position.altitude = lite.altitude; position.location_source = lite.location_source; position.time = lite.time; From b285aa5bd608e57e5cbd8398402805eca8c27dd0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 23 Aug 2024 07:07:28 -0500 Subject: [PATCH 0980/1377] Dum dum zero comparision --- src/mesh/TypeConversions.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index bcff0b817..513728ca5 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -16,13 +16,13 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo if (lite->has_position) { info.has_position = true; - if (lite->position.latitude_i > 0) + if (lite->position.latitude_i != 0) info.position.has_latitude_i = true; info.position.latitude_i = lite->position.latitude_i; - if (lite->position.longitude_i > 0) + if (lite->position.longitude_i != 0) info.position.has_longitude_i = true; info.position.longitude_i = lite->position.longitude_i; - if (lite->position.altitude > 0) + if (lite->position.altitude != 0) info.position.has_altitude = true; info.position.altitude = lite->position.altitude; info.position.location_source = lite->position.location_source; @@ -54,13 +54,13 @@ meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Positi meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite lite) { meshtastic_Position position = meshtastic_Position_init_default; - if (lite.latitude_i > 0) + if (lite.latitude_i != 0) position.has_latitude_i = true; position.latitude_i = lite.latitude_i; - if (lite.longitude_i > 0) + if (lite.longitude_i != 0) position.has_longitude_i = true; position.longitude_i = lite.longitude_i; - if (lite.altitude > 0) + if (lite.altitude != 0) position.has_altitude = true; position.altitude = lite.altitude; position.location_source = lite.location_source; From 5ce5b7b08bfadb9312e2e9f95d10b54d6dff7b4c Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 23 Aug 2024 08:03:16 -0700 Subject: [PATCH 0981/1377] Older variant.h files (IMO sloppily) don't define VEXT_ON_VALUE But in an attempt to avoid updating lots of files, make it default to LOW --- src/configuration.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/configuration.h b/src/configuration.h index 2e0efffd4..047dbd727 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -177,6 +177,11 @@ along with this program. If not, see . /* Step #1: offer chance for variant-specific defines */ #include "variant.h" +#if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE) +// Older variant.h files might not be defining this value, so stay with the old default +#define VEXT_ON_VALUE LOW +#endif + #ifndef GPS_BAUDRATE #define GPS_BAUDRATE 9600 #endif From cdafa87cefac890ae2398885cc9052b6aaa0e4d7 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 28 Aug 2024 09:51:00 -0700 Subject: [PATCH 0982/1377] add lateInitVariant() as a concept. see below for docs (from src/extra_variants/README.md) This directory tree is designed to solve two problems. - The ESP32 arduino/platformio project doesn't support the nice "if initVariant() is found, call that after init" behavior of the nrf52 builds (they use initVariant() internally). - Over the years a lot of 'board specific' init code has been added to init() in main.cpp. It would be great to have a general/clean mechanism to allow developers to specify board specific/unique code in a clean fashion without mucking in main. So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define lateInitVariant() if your board needs it. If you'd like a board specific variant to be run, add the variant.cpp file to an appropriately named subdirectory and check for \_VARIANT_boardname in the cpp file (so that your code is only built for your board). You'll need to define \_VARIANT_boardname in your corresponding variant.h file. See existing boards for examples. This approach has no added runtime cost. --- src/main.cpp | 7 ++++ src/platform/extra_variants/README.md | 15 ++++++++ .../heltec_wireless_tracker/variant.cpp | 34 +++++++++++++++++++ variants/heltec_wireless_tracker/variant.h | 4 ++- 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/platform/extra_variants/README.md create mode 100644 src/platform/extra_variants/heltec_wireless_tracker/variant.cpp diff --git a/src/main.cpp b/src/main.cpp index 7520667dd..8f64960bb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -224,6 +224,11 @@ __attribute__((weak, noinline)) bool loopCanSleep() return true; } +// Weak empty variant initialization function. +// May be redefined by variant files. +void lateInitVariant() __attribute__((weak)); +void lateInitVariant() {} + /** * Print info as a structured log message (for automated log processing) */ @@ -1003,6 +1008,8 @@ void setup() } } + lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation) + #if !MESHTASTIC_EXCLUDE_MQTT mqttInit(); #endif diff --git a/src/platform/extra_variants/README.md b/src/platform/extra_variants/README.md new file mode 100644 index 000000000..e558502f0 --- /dev/null +++ b/src/platform/extra_variants/README.md @@ -0,0 +1,15 @@ +# About extra_variants + +This directory tree is designed to solve two problems. + +- The ESP32 arduino/platformio project doesn't support the nice "if initVariant() is found, call that after init" behavior of the nrf52 builds (they use initVariant() internally). +- Over the years a lot of 'board specific' init code has been added to init() in main.cpp. It would be great to have a general/clean mechanism to allow developers to specify board specific/unique code in a clean fashion without mucking in main. + +So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define lateInitVariant() if your board needs it. + +If you'd like a board specific variant to be run, add the variant.cpp file to an appropriately named +subdirectory and check for \_VARIANT_boardname in the cpp file (so that your code is only built for your board). +You'll need to define \_VARIANT_boardname in your corresponding variant.h file. +See existing boards for examples. + +This approach has no added runtime cost. diff --git a/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp b/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp new file mode 100644 index 000000000..84264ef58 --- /dev/null +++ b/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp @@ -0,0 +1,34 @@ +#include "configuration.h" + +#ifdef _VARIANT_HELTEC_WIRELESS_TRACKER + +#include "GPS.h" +#include "GpioLogic.h" +#include "graphics/TFTDisplay.h" + +// Heltec tracker specific init +void lateInitVariant() +{ + // LOG_DEBUG("Heltec tracker initVariant\n"); +#ifdef VEXT_ENABLE + GpioPin *hwEnable = new GpioHwPin(VEXT_ENABLE); + GpioVirtPin *virtGpsEnable = gps ? gps->enablePin : new GpioVirtPin(); + + // On this board we are actually using the backlightEnable signal to already be controlling a physical enable to the + // display controller. But we'd _ALSO_ like to have that signal drive a virtual GPIO. So nest it as needed. + GpioVirtPin *virtScreenEnable = new GpioVirtPin(); + if (TFTDisplay::backlightEnable) { + GpioPin *physScreenEnable = TFTDisplay::backlightEnable; + GpioPin *splitter = new GpioSplitter(virtScreenEnable, physScreenEnable); + TFTDisplay::backlightEnable = splitter; + + // Assume screen is initially powered + splitter->set(true); + } + + // If either the GPS or the screen is on, turn on the external power regulator + new GpioBinaryTransformer(virtGpsEnable, virtScreenEnable, hwEnable, GpioBinaryTransformer::Or); +#endif +} + +#endif \ No newline at end of file diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index 46e922eb5..79fa0e801 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -1,5 +1,6 @@ #define LED_PIN 18 +#define _VARIANT_HELTEC_WIRELESS_TRACKER #define HELTEC_TRACKER_V1_X // I2C @@ -31,7 +32,8 @@ // GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR // GPS LNA SW7125DE: VCC(4), pulls up SHDN(5) through 10kR // LED: VDD, LEDA (through diode) -#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED VDD/anode + +#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED #define VEXT_ON_VALUE HIGH #define BUTTON_PIN 0 From 8a9cc727a8d73b9b9e0b1754d483954151abcb40 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 28 Aug 2024 09:59:42 -0700 Subject: [PATCH 0983/1377] for #4154 use a binary gpio transformer to manage vext on heltec-tracker (saves power) --- src/GpioLogic.cpp | 16 +++++++++++++++- src/GpioLogic.h | 30 +++++++++++++++++++++++------- src/gps/GPS.cpp | 11 +++++------ src/gps/GPS.h | 2 +- src/graphics/TFTDisplay.cpp | 4 ++++ src/graphics/TFTDisplay.h | 4 +++- 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/GpioLogic.cpp b/src/GpioLogic.cpp index c23c40a7f..cbe26fc60 100644 --- a/src/GpioLogic.cpp +++ b/src/GpioLogic.cpp @@ -12,6 +12,7 @@ void GpioVirtPin::set(bool value) void GpioHwPin::set(bool value) { + // if (num == 3) LOG_DEBUG("Setting pin %d to %d\n", num, value); pinMode(num, OUTPUT); digitalWrite(num, value); } @@ -23,7 +24,7 @@ void GpioTransformer::set(bool value) outPin->set(value); } -GpioNotTransformer::GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin) : GpioTransformer(outPin), inPin(inPin) +GpioUnaryTransformer::GpioUnaryTransformer(GpioVirtPin *inPin, GpioPin *outPin) : GpioTransformer(outPin), inPin(inPin) { assert(!inPin->dependentPin); // We only allow one dependent pin inPin->dependentPin = this; @@ -33,6 +34,18 @@ GpioNotTransformer::GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin) : Gp // update(); } +/** + * Update the output pin based on the current state of the input pin. + */ +void GpioUnaryTransformer::update() +{ + auto p = inPin->get(); + if (p == GpioVirtPin::PinState::Unset) + return; // Not yet fully initialized + + set(p); +} + /** * Update the output pin based on the current state of the input pin. */ @@ -75,6 +88,7 @@ void GpioBinaryTransformer::update() newValue = (GpioVirtPin::PinState)(p1 && p2); break; case Or: + // LOG_DEBUG("Doing GPIO OR\n"); newValue = (GpioVirtPin::PinState)(p1 || p2); break; case Xor: diff --git a/src/GpioLogic.h b/src/GpioLogic.h index c5a3cefa9..947d49625 100644 --- a/src/GpioLogic.h +++ b/src/GpioLogic.h @@ -42,7 +42,7 @@ class GpioBinaryTransformer; class GpioVirtPin : public GpioPin { friend class GpioBinaryTransformer; - friend class GpioNotTransformer; + friend class GpioUnaryTransformer; public: enum PinState { On = true, Off = false, Unset = 2 }; @@ -79,12 +79,31 @@ class GpioTransformer }; /** - * A transformer that performs a unary NOT operation from an input. + * A transformer that just drives a hw pin based on a virtual pin. */ -class GpioNotTransformer : public GpioTransformer +class GpioUnaryTransformer : public GpioTransformer { public: - GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin); + GpioUnaryTransformer(GpioVirtPin *inPin, GpioPin *outPin); + + protected: + friend class GpioVirtPin; + + /** + * Update the output pin based on the current state of the input pin. + */ + virtual void update(); + + GpioVirtPin *inPin; +}; + +/** + * A transformer that performs a unary NOT operation from an input. + */ +class GpioNotTransformer : public GpioUnaryTransformer +{ + public: + GpioNotTransformer(GpioVirtPin *inPin, GpioPin *outPin) : GpioUnaryTransformer(inPin, outPin) {} protected: friend class GpioVirtPin; @@ -93,9 +112,6 @@ class GpioNotTransformer : public GpioTransformer * Update the output pin based on the current state of the input pin. */ void update(); - - private: - GpioVirtPin *inPin; }; /** diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index f7db1367a..12ef34c52 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1415,19 +1415,18 @@ GPS *GPS::createGps() new_gps->rx_gpio = _rx_gpio; new_gps->tx_gpio = _tx_gpio; + GpioVirtPin *virtPin = new GpioVirtPin(); + new_gps->enablePin = virtPin; // Always at least populate a virtual pin if (_en_gpio) { GpioPin *p = new GpioHwPin(_en_gpio); if (!GPS_EN_ACTIVE) { // Need to invert the pin before hardware - auto virtPin = new GpioVirtPin(); new GpioNotTransformer( virtPin, p); // We just leave this created object on the heap so it can stay watching virtPin and driving en_gpio - p = virtPin; + } else { + new GpioUnaryTransformer( + virtPin, p); // We just leave this created object on the heap so it can stay watching virtPin and driving en_gpio } - new_gps->enablePin = p; - } else { - // Just use a simulated pin - new_gps->enablePin = new GpioVirtPin(); } #ifdef PIN_GPS_PPS diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 494bddae8..befa4eef0 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -157,7 +157,7 @@ class GPS : private concurrency::OSThread * * Normally set by GPS::createGPS() */ - GpioPin *enablePin; + GpioVirtPin *enablePin; GPS() : concurrency::OSThread("GPS") {} diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index f6bd6513a..1dc4569db 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -25,6 +25,8 @@ extern SX1509 gpioExtender; #define TFT_INVERT true #endif +GpioPin *TFTDisplay::backlightEnable; + class LGFX : public lgfx::LGFX_Device { lgfx::Panel_ST7735S _panel_instance; @@ -584,6 +586,7 @@ void TFTDisplay::sendCommand(uint8_t com) // handle display on/off directly switch (com) { case DISPLAYON: { + // LOG_DEBUG("Display on\n"); backlightEnable->set(true); #if ARCH_PORTDUINO display(true); @@ -607,6 +610,7 @@ void TFTDisplay::sendCommand(uint8_t com) break; } case DISPLAYOFF: { + // LOG_DEBUG("Display off\n"); backlightEnable->set(false); #if ARCH_PORTDUINO tft->clear(); diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 595984fbc..38cd53ebb 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -43,8 +43,10 @@ class TFTDisplay : public OLEDDisplay /** * This is normally managed entirely by TFTDisplay, but some rare applications (heltec tracker) might need to replace the * default GPIO behavior with something a bit more complex. + * + * We (cruftily) make it static so that variant.cpp can access it without needing a ptr to the TFTDisplay instance. */ - GpioPin *backlightEnable; + static GpioPin *backlightEnable; protected: // the header size of the buffer used, e.g. for the SPI command header From 9631a1be3875169a7e9b802e4d06e1c881ef6783 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 23 Aug 2024 18:18:36 -0700 Subject: [PATCH 0984/1377] remove deprecated serial/bt logging options and unify in the new (#4516) security option. Per discussion in https://github.com/meshtastic/firmware/issues/4375 no need to preserve the old options when changing to this new simpler single boolean because they were newish, rarely used and only for 'advanced' developers. --- protobufs | 2 +- src/RedirectablePrint.cpp | 2 +- src/mesh/NodeDB.cpp | 1 - src/mesh/generated/meshtastic/config.pb.h | 37 ++++++------------- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 6 files changed, 15 insertions(+), 31 deletions(-) diff --git a/protobufs b/protobufs index 56a435507..183ba970a 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 56a4355070f3371213d48f3a8cac1ddaf0d553fe +Subproject commit 183ba970a7a71de7a81541b74c413543523417d2 diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 96cf851e4..6eb6f8319 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -213,7 +213,7 @@ void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_list arg) { #if !MESHTASTIC_EXCLUDE_BLUETOOTH - if (config.security.bluetooth_logging_enabled && !pauseBluetoothLogging) { + if (config.security.debug_log_api_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 34d3e4ce9..0504cc273 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -127,7 +127,6 @@ NodeDB::NodeDB() if (!config.has_security) { config.has_security = true; config.security.serial_enabled = config.device.serial_enabled; - config.security.bluetooth_logging_enabled = config.bluetooth.device_logging_enabled; config.security.is_managed = config.device.is_managed; } #if !(MESHTASTIC_EXCLUDE_PKI) diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 2f4c00fb0..d79654856 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -284,10 +284,6 @@ typedef struct _meshtastic_Config_DeviceConfig { /* Disabling this will disable the SerialConsole by not initilizing the StreamAPI Moved to SecurityConfig */ bool serial_enabled; - /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). - Set this to true to leave the debug log outputting even when API is active. - Moved to SecurityConfig */ - bool debug_log_enabled; /* For boards without a hard wired button, this is the pin number that will be used Boards that have more than one button can swap the function with this one. defaults to BUTTON_PIN if defined. */ uint32_t button_gpio; @@ -523,9 +519,6 @@ typedef struct _meshtastic_Config_BluetoothConfig { meshtastic_Config_BluetoothConfig_PairingMode mode; /* Specified PIN for PairingMode.FixedPin */ uint32_t fixed_pin; - /* Enables device (serial style logs) over Bluetooth - Moved to SecurityConfig */ - bool device_logging_enabled; } meshtastic_Config_BluetoothConfig; typedef PB_BYTES_ARRAY_T(32) meshtastic_Config_SecurityConfig_public_key_t; @@ -546,10 +539,8 @@ typedef struct _meshtastic_Config_SecurityConfig { /* Serial Console over the Stream API." */ bool serial_enabled; /* By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). - Output live debug logging over serial. */ + Output live debug logging over serial or bluetooth is set to true. */ bool debug_log_api_enabled; - /* Enables device (serial style logs) over Bluetooth */ - bool bluetooth_logging_enabled; /* Allow incoming device control over the insecure legacy admin channel. */ bool admin_channel_enabled; } meshtastic_Config_SecurityConfig; @@ -658,32 +649,31 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} -#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} -#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} -#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0} -#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Config_DeviceConfig_role_tag 1 #define meshtastic_Config_DeviceConfig_serial_enabled_tag 2 -#define meshtastic_Config_DeviceConfig_debug_log_enabled_tag 3 #define meshtastic_Config_DeviceConfig_button_gpio_tag 4 #define meshtastic_Config_DeviceConfig_buzzer_gpio_tag 5 #define meshtastic_Config_DeviceConfig_rebroadcast_mode_tag 6 @@ -758,14 +748,12 @@ extern "C" { #define meshtastic_Config_BluetoothConfig_enabled_tag 1 #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 -#define meshtastic_Config_BluetoothConfig_device_logging_enabled_tag 4 #define meshtastic_Config_SecurityConfig_public_key_tag 1 #define meshtastic_Config_SecurityConfig_private_key_tag 2 #define meshtastic_Config_SecurityConfig_admin_key_tag 3 #define meshtastic_Config_SecurityConfig_is_managed_tag 4 #define meshtastic_Config_SecurityConfig_serial_enabled_tag 5 #define meshtastic_Config_SecurityConfig_debug_log_api_enabled_tag 6 -#define meshtastic_Config_SecurityConfig_bluetooth_logging_enabled_tag 7 #define meshtastic_Config_SecurityConfig_admin_channel_enabled_tag 8 #define meshtastic_Config_device_tag 1 #define meshtastic_Config_position_tag 2 @@ -803,7 +791,6 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sessionkey,payload_variant.s #define meshtastic_Config_DeviceConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, role, 1) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 2) \ -X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 3) \ X(a, STATIC, SINGULAR, UINT32, button_gpio, 4) \ X(a, STATIC, SINGULAR, UINT32, buzzer_gpio, 5) \ X(a, STATIC, SINGULAR, UENUM, rebroadcast_mode, 6) \ @@ -906,8 +893,7 @@ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_BluetoothConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, UENUM, mode, 2) \ -X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) \ -X(a, STATIC, SINGULAR, BOOL, device_logging_enabled, 4) +X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) #define meshtastic_Config_BluetoothConfig_CALLBACK NULL #define meshtastic_Config_BluetoothConfig_DEFAULT NULL @@ -918,7 +904,6 @@ X(a, STATIC, SINGULAR, BYTES, admin_key, 3) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \ -X(a, STATIC, SINGULAR, BOOL, bluetooth_logging_enabled, 7) \ X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8) #define meshtastic_Config_SecurityConfig_CALLBACK NULL #define meshtastic_Config_SecurityConfig_DEFAULT NULL @@ -955,15 +940,15 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size -#define meshtastic_Config_BluetoothConfig_size 12 -#define meshtastic_Config_DeviceConfig_size 100 +#define meshtastic_Config_BluetoothConfig_size 10 +#define meshtastic_Config_DeviceConfig_size 98 #define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 -#define meshtastic_Config_SecurityConfig_size 112 +#define meshtastic_Config_SecurityConfig_size 110 #define meshtastic_Config_SessionkeyConfig_size 0 #define meshtastic_Config_size 199 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 343e5f48a..976e0e135 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -358,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3502 +#define meshtastic_OEMStore_size 3496 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index c612b24ab..38529fc14 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 669 +#define meshtastic_LocalConfig_size 663 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus From eddb72705f4b540970dbc141539b1e97edf0f816 Mon Sep 17 00:00:00 2001 From: Nestpebble <116762865+Nestpebble@users.noreply.github.com> Date: Sat, 24 Aug 2024 02:24:23 +0100 Subject: [PATCH 0985/1377] add a .yml to setup a Gitpod instance quickly (#4551) * Create .gitpod.yml * Update .gitpod.yml --- .gitpod.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..0af235a3a --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,2 @@ +tasks: + - init: pip install platformio && pip install --upgrade pip From 17b2a83b44fdf5e4788ec16d8de1aee389a3cf08 Mon Sep 17 00:00:00 2001 From: John Hollowell Date: Fri, 23 Aug 2024 21:25:16 -0400 Subject: [PATCH 0986/1377] Add devcontainer (#4491) devcontainers can be used by IDEs/editors like VS Code to create a standardized development environment in a container --- .devcontainer/Dockerfile | 24 ++++++++++++++++++++++++ .devcontainer/devcontainer.json | 28 ++++++++++++++++++++++++++++ .devcontainer/setup.sh | 3 +++ 3 files changed, 55 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/setup.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..82f4d91bf --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,24 @@ +FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12 + +# [Optional] Uncomment this section to install additional packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + ca-certificates \ + g++ \ + git \ + libbluetooth-dev \ + libgpiod-dev \ + liborcania-dev \ + libssl-dev \ + libulfius-dev \ + libyaml-cpp-dev \ + pkg-config \ + python3 \ + python3-pip \ + python3-venv \ + python3-wheel \ + wget \ + zip \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN pip3 install --no-cache-dir -U platformio==6.1.15 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..e45fd5d93 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "Meshtastic Firmware Dev", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/python:1": { + "installTools": true, + "version": "latest" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "platformio.platformio-ide", + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ 4403 ], + + // Run commands to prepare the container for use + "postCreateCommand": ".devcontainer/setup.sh", +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 000000000..866a4a417 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +git submodule update --init \ No newline at end of file From 059d5582d15c5922e437ffd0a6a772fc50fbfdf8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:44:14 -0500 Subject: [PATCH 0987/1377] [create-pull-request] automated change (#4544) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 183ba970a..52cfa2c1c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 183ba970a7a71de7a81541b74c413543523417d2 +Subproject commit 52cfa2c1c2cd5a1a714e1338d551d967f674fca8 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index f32f865db..d612d74be 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -189,6 +189,13 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RADIOMASTER_900_BANDIT = 74, /* Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins. */ meshtastic_HardwareModel_ME25LS01_4Y10TD = 75, + /* RP2040_FEATHER_RFM95 + Adafruit Feather RP2040 with RFM95 LoRa Radio RFM95 with SX1272, SSD1306 OLED + https://www.adafruit.com/product/5714 + https://www.adafruit.com/product/326 + https://www.adafruit.com/product/938 + ^^^ short A0 to switch to I2C address 0x3C */ + meshtastic_HardwareModel_RP2040_FEATHER_RFM95 = 76, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From c11a66030f5c35d32d26c0e750d52b851c071984 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 24 Aug 2024 12:19:31 -0500 Subject: [PATCH 0988/1377] Userlite mem comparison (#4552) --- src/mesh/NodeDB.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0504cc273..3af6c6a45 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1027,9 +1027,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde #endif // Both of info->user and p start as filled with zero so I think this is okay - bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); + auto lite = TypeConversions::ConvertToUserLite(p); + bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex); - info->user = TypeConversions::ConvertToUserLite(p); + info->user = lite; if (info->user.public_key.size == 32) { printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32); } From 927a35ef51c5e644432def8581757231c7a526f1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 26 Aug 2024 07:48:07 -0500 Subject: [PATCH 0989/1377] Protos --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 11 ++++++----- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index 52cfa2c1c..431291e67 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 52cfa2c1c2cd5a1a714e1338d551d967f674fca8 +Subproject commit 431291e673c1c7592ee64cb972d7845589f60138 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d79654856..eb03ddc58 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -532,7 +532,8 @@ typedef struct _meshtastic_Config_SecurityConfig { Used to create a shared key with a remote device. */ meshtastic_Config_SecurityConfig_private_key_t private_key; /* The public key authorized to send admin messages to this node. */ - meshtastic_Config_SecurityConfig_admin_key_t admin_key; + pb_size_t admin_key_count; + meshtastic_Config_SecurityConfig_admin_key_t admin_key[1]; /* If true, device is considered to be "managed" by a mesh administrator via admin messages Device is managed by a mesh administrator. */ bool is_managed; @@ -657,7 +658,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} -#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -668,7 +669,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} -#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ @@ -900,7 +901,7 @@ X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) #define meshtastic_Config_SecurityConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BYTES, public_key, 1) \ X(a, STATIC, SINGULAR, BYTES, private_key, 2) \ -X(a, STATIC, SINGULAR, BYTES, admin_key, 3) \ +X(a, STATIC, REPEATED, BYTES, admin_key, 3) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \ @@ -948,7 +949,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 -#define meshtastic_Config_SecurityConfig_size 110 +#define meshtastic_Config_SecurityConfig_size 111 #define meshtastic_Config_SessionkeyConfig_size 0 #define meshtastic_Config_size 199 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 976e0e135..692402210 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -358,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3496 +#define meshtastic_OEMStore_size 3497 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 38529fc14..91a23dc4f 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 663 +#define meshtastic_LocalConfig_size 664 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus From 1fe80e0f304986d4fe1a9a8e8faf642b6735e0c9 Mon Sep 17 00:00:00 2001 From: John Milton Date: Mon, 26 Aug 2024 11:28:08 -0400 Subject: [PATCH 0990/1377] Add support for Adafruit Feather RP2040 with RFM95. (#4451) * Add support for Adafruit Feather RP2040 with RFM95. * Update mesh.pb.h dropping this change from the file generated by the protobuf * Update mesh.pb.h remove these reverting changes * Update mesh.pb.h oops, missed a comma --- src/platform/rp2040/architecture.h | 2 + variants/feather_rp2040_rfm95/platformio.ini | 16 +++++ variants/feather_rp2040_rfm95/variant.h | 61 ++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 variants/feather_rp2040_rfm95/platformio.ini create mode 100644 variants/feather_rp2040_rfm95/variant.h diff --git a/src/platform/rp2040/architecture.h b/src/platform/rp2040/architecture.h index d7d7214c0..3f75735d3 100644 --- a/src/platform/rp2040/architecture.h +++ b/src/platform/rp2040/architecture.h @@ -29,4 +29,6 @@ #define HW_VENDOR meshtastic_HardwareModel_SENSELORA_RP2040 #elif defined(RP2040_LORA) #define HW_VENDOR meshtastic_HardwareModel_RP2040_LORA +#elif defined(RP2040_FEATHER_RFM95) +#define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95 #endif \ No newline at end of file diff --git a/variants/feather_rp2040_rfm95/platformio.ini b/variants/feather_rp2040_rfm95/platformio.ini new file mode 100644 index 000000000..a28ad7655 --- /dev/null +++ b/variants/feather_rp2040_rfm95/platformio.ini @@ -0,0 +1,16 @@ +[env:feather_rp2040_rfm95] +extends = rp2040_base +board = adafruit_feather +upload_protocol = picotool + +# add our variants files to the include and src paths +build_flags = ${rp2040_base.build_flags} + -DRP2040_FEATHER_RFM95 + -Ivariants/feather_rp2040_rfm95 + -DDEBUG_RP2040_PORT=Serial + -DHW_SPI1_DEVICE + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/feather_rp2040_rfm95/variant.h b/variants/feather_rp2040_rfm95/variant.h new file mode 100644 index 000000000..e9e178202 --- /dev/null +++ b/variants/feather_rp2040_rfm95/variant.h @@ -0,0 +1,61 @@ +// #define RADIOLIB_CUSTOM_ARDUINO 1 +// #define RADIOLIB_TONE_UNSUPPORTED 1 +// #define RADIOLIB_SOFTWARE_SERIAL_UNSUPPORTED 1 + +#define ARDUINO_ARCH_AVR + +// #define USE_SSD1306 + +// #define USE_SH1106 1 + +// default I2C pins: +// SDA = 4 +// SCL = 5 + +// Recommended pins for SerialModule: +// txd = 8 +// rxd = 9 + +#define EXT_NOTIFY_OUT 22 +#define BUTTON_PIN 7 +// #define BUTTON_NEED_PULLUP + +#define LED_PIN PIN_LED + +// #define BATTERY_PIN 26 +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +// #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic + +#define USE_RF95 // RFM95/SX127x + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// https://www.adafruit.com/product/5714 +// https://learn.adafruit.com/feather-rp2040-rfm95 +// https://learn.adafruit.com/assets/120283 +// https://learn.adafruit.com/assets/120813 +#define LORA_SCK 14 // 10 12P +#define LORA_MISO 8 // 12 10P +#define LORA_MOSI 15 // 11 11P +#define LORA_CS 16 // 3 13P + +#define LORA_RESET 17 // 15 14P + +#define LORA_DIO0 21 // ?? 6P +#define LORA_DIO1 22 // 20 7P +#define LORA_DIO2 23 // 2 8P +#define LORA_DIO3 19 // ?? 3P +#define LORA_DIO4 20 // ?? 4P +#define LORA_DIO5 18 // ?? 15P + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file From 574124aee55dfc79d5e40053ec12887d83e48c02 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Aug 2024 12:29:44 -0500 Subject: [PATCH 0991/1377] Deal with admin_key being repeated (#4558) --- src/mesh/NodeDB.cpp | 6 +++--- src/modules/AdminModule.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 3af6c6a45..fa736e08a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -292,10 +292,10 @@ void NodeDB::installDefaultConfig() config.lora.ignore_mqtt = false; #endif #ifdef ADMIN_KEY_USERPREFS - memcpy(config.security.admin_key.bytes, admin_key_userprefs, 32); - config.security.admin_key.size = 32; + memcpy(config.security.admin_key[0].bytes, admin_key_userprefs, 32); + config.security.admin_key[0].size = 32; #else - config.security.admin_key.size = 0; + config.security.admin_key[0].size = 0; #endif config.security.public_key.size = 0; config.security.private_key.size = 0; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index ef60a4bf2..b63eca396 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -75,7 +75,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta // and only allowing responses from that remote. if (!((mp.from == 0 && !config.security.is_managed) || messageIsResponse(r) || (strcasecmp(ch->settings.name, Channels::adminChannel) == 0 && config.security.admin_channel_enabled) || - (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key.bytes, 32) == 0))) { + (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key[0].bytes, 32) == 0))) { LOG_INFO("Ignoring admin payload %i\n", r->which_payload_variant); return handled; } From 3c4d964334788e7c335268c746ed0c9eb6947d32 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Aug 2024 15:48:47 -0500 Subject: [PATCH 0992/1377] Mask out random bits when doing queue ordering (#4561) * Mask out random bits when doing queue ordering * Parenthesis --- src/mesh/MeshPacketQueue.cpp | 7 ++++--- src/mesh/MeshTypes.h | 5 +++-- src/mesh/Router.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index 24756fd2a..f1c6c4ff3 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -20,8 +20,9 @@ bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_Mes // If priorities differ, use that // for equal priorities, order by id (older packets have higher priority - this will briefly be wrong when IDs roll over but // no big deal) - return (p1p != p2p) ? (p1p < p2p) // prefer bigger priorities - : (p1->id >= p2->id); // prefer smaller packet ids + return (p1p != p2p) + ? (p1p < p2p) // prefer bigger priorities + : ((p1->id & ID_COUNTER_MASK) >= (p2->id & ID_COUNTER_MASK)); // Mask to counter portion, prefer smaller packet ids } MeshPacketQueue::MeshPacketQueue(size_t _maxLen) : maxLen(_maxLen) {} @@ -127,4 +128,4 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); return true; -} +} \ No newline at end of file diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index c0919bf5d..1c9099c39 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -14,8 +14,9 @@ typedef uint32_t PacketId; // A packet sequence number 1 // Reserved to only deliver packets over high speed (non-lora) transports, such as MQTT or BLE mesh (not yet implemented) #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 -#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER -#define ERRNO_DISABLED 34 // the interface is disabled +#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER +#define ERRNO_DISABLED 34 // the interface is disabled +#define ID_COUNTER_MASK (UINT32_MAX >> 22) // mask to select the counter portion of the ID /* * Source of a received message diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index bdd8c4e6c..61b1bbfb6 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -109,7 +109,7 @@ PacketId generatePacketId() rollingPacketId++; - rollingPacketId &= UINT32_MAX >> 22; // Mask out the top 22 bits + rollingPacketId &= ID_COUNTER_MASK; // Mask out the top 22 bits PacketId id = rollingPacketId | random(UINT32_MAX & 0x7fffffff) << 10; // top 22 bits LOG_DEBUG("Partially randomized packet id %u\n", id); return id; From e3ce3a3a4f478ebaf266a1335e6e40f4cbb93f42 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Aug 2024 14:49:58 -0500 Subject: [PATCH 0993/1377] Don't compare nodeDB macaddr to owner.macaddr, because in rare cases that may be unset. (#4562) Co-authored-by: Ben Meadors --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fa736e08a..8409eaff6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -620,7 +620,7 @@ void NodeDB::pickNewNodeNum() meshtastic_NodeInfoLite *found; while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) || - ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr)) != 0)) { + ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0)) { NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate); nodeNum = candidate; From cc93df27a5835ebe90649d1351c451d0fdffd789 Mon Sep 17 00:00:00 2001 From: Power Li Date: Wed, 28 Aug 2024 05:26:02 +0800 Subject: [PATCH 0994/1377] set current time to system time in portduino build (#4556) * set current time to system time in portduino build * fix includes order --------- Co-authored-by: Jonathan Bennett --- src/platform/portduino/PortduinoGlue.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 3532d2e79..cce893c0b 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -1,5 +1,6 @@ #include "CryptoEngine.h" #include "PortduinoGPIO.h" +#include "RTC.h" #include "SPIChip.h" #include "mesh/RF95Interface.h" #include "sleep.h" @@ -370,6 +371,12 @@ void portduinoSetup() exit(EXIT_FAILURE); } } + + struct timeval tv; + tv.tv_sec = time(NULL); + tv.tv_usec = 0; + perhapsSetRTC(RTCQualityNTP, &tv); + return; } From 72c82c1c08b83986fe9afdf5b55fa10d9933e308 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 27 Aug 2024 18:24:14 -0500 Subject: [PATCH 0995/1377] Add RAK4631 hex to firmware release --- bin/build-nrf52.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 060d06cfd..9d0b3dfdd 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -46,3 +46,8 @@ else cp bin/device-update.* $OUTDIR cp bin/*.uf2 $OUTDIR fi + +if (echo $1 | grep -q "rak4631"); then + echo "Copying hex file" + cp .pio/build/$1/firmware.hex $OUTDIR/$basename.hex +fi \ No newline at end of file From 94c3bb4a560b0e3e3aa9e4b1c81fa0b6fc0db8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 28 Aug 2024 12:43:19 +0200 Subject: [PATCH 0996/1377] fix #4390 (#4571) --- src/serialization/MeshPacketSerializer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index a42bb867a..e00dde024 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -76,6 +76,13 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction); msgPayload["wind_gust"] = new JSONValue(decoded->variant.environment_metrics.wind_gust); msgPayload["wind_lull"] = new JSONValue(decoded->variant.environment_metrics.wind_lull); + } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + msgPayload["pm10"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_standard); + msgPayload["pm25"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_standard); + msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard); + msgPayload["pm10_e"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental); + msgPayload["pm25_e"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental); + msgPayload["pm100_e"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental); } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current); From 545d32fcec222a86c487950c21ecbcbf37634d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Cort=C3=AAs?= Date: Mon, 26 Aug 2024 17:15:42 +0100 Subject: [PATCH 0997/1377] Fix devcontainer Dockerfile build --- .devcontainer/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 82f4d91bf..c21564491 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -12,6 +12,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ libssl-dev \ libulfius-dev \ libyaml-cpp-dev \ + pipx \ pkg-config \ python3 \ python3-pip \ @@ -21,4 +22,4 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ zip \ && apt-get clean && rm -rf /var/lib/apt/lists/* -RUN pip3 install --no-cache-dir -U platformio==6.1.15 \ No newline at end of file +RUN pipx install platformio==6.1.15 \ No newline at end of file From 3ad0af5ce8afa3009009acaf4961c5677377da46 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Aug 2024 06:43:30 -0500 Subject: [PATCH 0998/1377] Fix super tiny T1114 tft font size and fork repo to fix compiler warnings (#4573) --- src/graphics/Screen.cpp | 13 ++++++++----- src/graphics/ScreenFonts.h | 3 ++- src/graphics/images.h | 4 ++-- variants/heltec_mesh_node_t114/platformio.ini | 2 +- variants/heltec_vision_master_t190/platformio.ini | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3a4db6ee0..2690d0b70 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1093,7 +1093,8 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat { char usersString[20]; snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x, y + 3, 8, 8, imgUser); #else @@ -2414,7 +2415,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #ifdef ARCH_ESP32 if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -2425,7 +2427,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 imgQuestion); #endif } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -2439,8 +2442,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #endif } else { // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ - ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 8a48d053e..41e739fa1 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -8,7 +8,8 @@ #include "graphics/fonts/OLEDDisplayFontsUA.h" #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL ArialMT_Plain_16 // Height: 19 diff --git a/src/graphics/images.h b/src/graphics/images.h index d4c738610..ab3767a89 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -20,8 +20,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \ - ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ + defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini index 99bdf77a7..c2a458f78 100644 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -12,4 +12,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb \ No newline at end of file + https://github.com/meshtastic/st7789#7181320e7ed05c7fb5fd2d32f14723bce6088b7b \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini index 38a3169e8..fd0001439 100644 --- a/variants/heltec_vision_master_t190/platformio.ini +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -9,5 +9,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/Bei-Ji-Quan/st7789#b8e7e076714b670764139289d3829b0beff67edb + https://github.com/meshtastic/st7789#7181320e7ed05c7fb5fd2d32f14723bce6088b7b upload_speed = 921600 \ No newline at end of file From ad931799c9d434d6861869cf4e3fa83740346913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 28 Aug 2024 13:51:44 +0200 Subject: [PATCH 0999/1377] trunk upgrade (#4574) --- .trunk/trunk.yaml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 2d9f60899..b20a1ec95 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,36 +1,36 @@ version: 0.1 cli: - version: 1.22.2 + version: 1.22.3 plugins: sources: - id: trunk - ref: v1.5.0 + ref: v1.6.2 uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.76.3 + - trufflehog@3.81.9 - yamllint@1.35.1 - - bandit@1.7.8 - - checkov@3.2.95 + - bandit@1.7.9 + - checkov@3.2.238 - terrascan@1.19.1 - - trivy@0.51.1 + - trivy@0.54.1 #- trufflehog@3.63.2-rc0 - - taplo@0.8.1 - - ruff@0.4.4 + - taplo@0.9.3 + - ruff@0.6.2 - isort@5.13.2 - - markdownlint@0.40.0 - - oxipng@9.1.1 + - markdownlint@0.41.0 + - oxipng@9.1.2 - svgo@3.3.2 - - actionlint@1.7.0 - - flake8@7.0.0 + - actionlint@1.7.1 + - flake8@7.1.1 - hadolint@2.12.0 - shfmt@3.6.0 - shellcheck@0.10.0 - - black@24.4.2 + - black@24.8.0 - git-diff-check - - gitleaks@8.18.2 + - gitleaks@8.18.4 - clang-format@16.0.3 - - prettier@3.2.5 + - prettier@3.3.3 ignore: - linters: [ALL] paths: From f5633bf0c5cff3bfc2c83d1ae293cf23664d8ff1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Aug 2024 07:24:41 -0500 Subject: [PATCH 1000/1377] Fix T1000-E default to turn on buzzer for Ext. Notification (#4575) --- src/mesh/NodeDB.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8409eaff6..004f27a0d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -395,6 +395,12 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; +#if defined(PIN_BUZZER) + moduleConfig.external_notification.enabled = true; + moduleConfig.external_notification.output_buzzer = PIN_BUZZER; + moduleConfig.external_notification.alert_message_buzzer = true; + moduleConfig.external_notification.nag_timeout = 60; +#endif #if defined(RAK4630) || defined(RAK11310) // Default to RAK led pin 2 (blue) moduleConfig.external_notification.enabled = true; From a1bf0d8519263a2663db0374211d370cacee0127 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Aug 2024 07:54:50 -0500 Subject: [PATCH 1001/1377] Add button secondary and enable scan-select on T190 (#4577) --- variants/heltec_vision_master_t190/variant.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/heltec_vision_master_t190/variant.h index 726f6d864..1da3f9971 100644 --- a/variants/heltec_vision_master_t190/variant.h +++ b/variants/heltec_vision_master_t190/variant.h @@ -1,4 +1,6 @@ #define BUTTON_PIN 0 +#define BUTTON_PIN_SECONDARY 21 // Second built-in button +#define BUTTON_SECONDARY_CANNEDMESSAGES // By default, use the secondary button as canned message input // I2C #define I2C_SDA SDA From dc9f6e1360a8f2e7d8b84886482a9e4322877c75 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 28 Aug 2024 10:44:46 -0700 Subject: [PATCH 1002/1377] fix CI warnings (and change CI comment to be correct) --- .github/workflows/build_native.yml | 2 +- src/gps/GPS.h | 2 +- src/graphics/TFTDisplay.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 3e8b4c001..51bef0c13 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -10,7 +10,7 @@ jobs: build-native: runs-on: ubuntu-latest steps: - - name: Install libbluetooth + - name: Install libs needed for native build shell: bash run: | sudo apt-get update --fix-missing diff --git a/src/gps/GPS.h b/src/gps/GPS.h index befa4eef0..c0e9fb8b6 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -157,7 +157,7 @@ class GPS : private concurrency::OSThread * * Normally set by GPS::createGPS() */ - GpioVirtPin *enablePin; + GpioVirtPin *enablePin = NULL; GPS() : concurrency::OSThread("GPS") {} diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 1dc4569db..2849dd9a9 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -25,8 +25,6 @@ extern SX1509 gpioExtender; #define TFT_INVERT true #endif -GpioPin *TFTDisplay::backlightEnable; - class LGFX : public lgfx::LGFX_Device { lgfx::Panel_ST7735S _panel_instance; @@ -515,6 +513,8 @@ static LGFX *tft = nullptr; extern unPhone unphone; #endif +GpioPin *TFTDisplay::backlightEnable = NULL; + TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { LOG_DEBUG("TFTDisplay!\n"); From 92eae39a1b83d5f667dbcc3c9f9b7798cf41010e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Aug 2024 05:39:30 -0500 Subject: [PATCH 1003/1377] Move Time set from system to main (#4583) --- src/main.cpp | 6 ++++++ src/platform/portduino/PortduinoGlue.cpp | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d38b4e669..b73d9803b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -257,6 +257,12 @@ void setup() #ifdef DEBUG_PORT consoleInit(); // Set serial baud rate and init our mesh console +#endif +#if ARCH_PORTDUINO + struct timeval tv; + tv.tv_sec = time(NULL); + tv.tv_usec = 0; + perhapsSetRTC(RTCQualityNTP, &tv); #endif powerMonInit(); diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index cce893c0b..dc143c661 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -1,6 +1,5 @@ #include "CryptoEngine.h" #include "PortduinoGPIO.h" -#include "RTC.h" #include "SPIChip.h" #include "mesh/RF95Interface.h" #include "sleep.h" @@ -8,7 +7,6 @@ #include #include -#include #include "PortduinoGlue.h" #include "linux/gpio/LinuxGPIOPin.h" @@ -372,11 +370,6 @@ void portduinoSetup() } } - struct timeval tv; - tv.tv_sec = time(NULL); - tv.tv_usec = 0; - perhapsSetRTC(RTCQualityNTP, &tv); - return; } From c02bbad9f386b757aa567a71492cd5153de7d407 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:46:33 +1000 Subject: [PATCH 1004/1377] Update nightly.yml --- .github/workflows/nightly.yml | 37 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e249823a7..67256c21e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,19 +1,20 @@ name: Nightly -on: - schedule: - - cron: 0 8 * * 1-5 - workflow_dispatch: {} - -jobs: - trunk_check: - name: Trunk Check Upload - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Trunk Check - uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b - with: - trunk-token: ${{ secrets.TRUNK_TOKEN }} +on: workflow_dispatch +# on: +# schedule: +# - cron: 0 8 * * 1-5 +# workflow_dispatch: {} +# +# jobs: +# trunk_check: +# name: Trunk Check Upload +# runs-on: ubuntu-latest +# +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# +# - name: Trunk Check +# uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b +# with: +# trunk-token: ${{ secrets.TRUNK_TOKEN }} From fc1e60ac584dcceeb3acf38cd6d1af1977a5a7e7 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 17 Aug 2024 16:19:39 +1000 Subject: [PATCH 1005/1377] Initial upload --- .vscode/extensions.json | 7 ++- platformio.ini | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 15 ++++- src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 23 +++++++- src/modules/Telemetry/Sensor/BMP3XXSensor.cpp | 58 +++++++++++++++++++ src/modules/Telemetry/Sensor/BMP3XXSensor.h | 31 ++++++++++ 8 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/BMP3XXSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/BMP3XXSensor.h diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b50c95349..080e70d08 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,9 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", - "platformio.platformio-ide", - "trunk.io" + "platformio.platformio-ide" ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] } diff --git a/platformio.ini b/platformio.ini index 4de1ec39f..414232f61 100644 --- a/platformio.ini +++ b/platformio.ini @@ -130,6 +130,7 @@ lib_deps = adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP085 Library@^1.2.4 adafruit/Adafruit BME280 Library@^2.2.2 + adafruit/Adafruit BMP3XX Library@^2.1.5 adafruit/Adafruit MCP9808 Library@^2.0.0 adafruit/Adafruit INA260 Library@^1.5.0 adafruit/Adafruit INA219@^1.2.0 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 711e8bee5..0a5b360de 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -24,6 +24,7 @@ class ScanI2C BME_280, BMP_280, BMP_085, + BMP_3XX, INA260, INA219, INA3221, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 1183d0ddc..76ca42b7f 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -267,8 +267,19 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = BMP_085; break; default: - LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address); - type = BMP_280; + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID + switch(registerValue) { + case 0x50: // BMP-388 should be 0x50 + LOG_INFO("BMP-388 sensor found at address 0x%x\n", (uint8_t)addr.address); + type = BMP_3XX; + break; + case 0x58: // BMP-280 should be 0x58 + default: + LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address); + type = BMP_280; + break; + } + break; } break; #ifndef HAS_NCP5623 diff --git a/src/main.cpp b/src/main.cpp index b73d9803b..b7d25e764 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -562,6 +562,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 4755a5be5..51b49a0bc 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -23,6 +23,7 @@ #include "Sensor/BME680Sensor.h" #include "Sensor/BMP085Sensor.h" #include "Sensor/BMP280Sensor.h" +#include "Sensor/BMP3XXSensor.h" #include "Sensor/DFRobotLarkSensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" @@ -40,6 +41,7 @@ BMP085Sensor bmp085Sensor; BMP280Sensor bmp280Sensor; BME280Sensor bme280Sensor; +BMP3XXSensor bmp3xxSensor; BME680Sensor bme680Sensor; MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; @@ -107,6 +109,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = bmp280Sensor.runOnce(); if (bme280Sensor.hasSensor()) result = bme280Sensor.runOnce(); + if (bmp3xxSensor.hasSensor()) + result = bmp3xxSensor.runOnce(); if (bme680Sensor.hasSensor()) result = bme680Sensor.runOnce(); if (mcp9808Sensor.hasSensor()) @@ -327,6 +331,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && bme280Sensor.getMetrics(m); hasSensor = true; } + if (bmp3xxSensor.hasSensor()) { + valid = valid && bmp3xxSensor.getMetrics(m); + hasSensor = true; + } if (bme680Sensor.hasSensor()) { valid = valid && bme680Sensor.getMetrics(m); hasSensor = true; @@ -372,15 +380,21 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m hasSensor = true; } if (aht10Sensor.hasSensor()) { - if (!bmp280Sensor.hasSensor()) { + if (!bmp280Sensor.hasSensor() && !bmp3xxSensor.hasSensor()) { valid = valid && aht10Sensor.getMetrics(m); hasSensor = true; - } else { + } else if (bmp280Sensor.hasSensor()) { // prefer bmp280 temp if both sensors are present, fetch only humidity meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n"); aht10Sensor.getMetrics(&m_ahtx); m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; + } else { + // prefer bmp3xx temp if both sensors are present, fetch only humidity + meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; + LOG_INFO("AHTX0+BMP3XX module detected: using temp from BMP3XX and humy from AHTX0\n"); + aht10Sensor.getMetrics(&m_ahtx); + m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } @@ -508,6 +522,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (bmp3xxSensor.hasSensor()) { + result = bmp3xxSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } if (bme680Sensor.hasSensor()) { result = bme680Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp new file mode 100644 index 000000000..7aec83818 --- /dev/null +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp @@ -0,0 +1,58 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BMP3XXSensor.h" +#include "TelemetrySensor.h" +#include +#include + +BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX"){} + +int32_t BMP3XXSensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) + { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + status = bmp3xx.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + + // set up oversampling and filter initialization + bmp3xx.setTemperatureOversampling(BMP3_OVERSAMPLING_4X); + bmp3xx.setPressureOversampling(BMP3_OVERSAMPLING_8X); + bmp3xx.setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3); + bmp3xx.setOutputDataRate(BMP3_ODR_25_HZ); + + // take a couple of initial readings to settle the sensor filters + for (int i = 0; i < 3; i++) + { + bmp3xx.performReading(); + } + return initI2CSensor(); +} + +bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + if ((int)measurement->which_variant == meshtastic_Telemetry_environment_metrics_tag) + { + bmp3xx.performReading(); + measurement->variant.environment_metrics.temperature = bmp3xx.readTemperature(); + measurement->variant.environment_metrics.barometric_pressure = bmp3xx.readPressure() / 100.0F; + LOG_DEBUG("BMP3XXSensor::getMetrics id: %i temp: %.1f press %.1f\n", measurement->which_variant, measurement->variant.environment_metrics.temperature, measurement->variant.environment_metrics.barometric_pressure); + } + else + { + LOG_DEBUG("BMP3XXSensor::getMetrics id: %i\n", measurement->which_variant); + } + return true; +} + +float BMP3XXSensor::getAltitudeAMSL() +{ + return bmp3xx.readAltitude(SEAL_LEVEL_HPA); +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h new file mode 100644 index 000000000..262d581e9 --- /dev/null +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h @@ -0,0 +1,31 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#ifndef _BMP3XX_SENSOR_H +#define _BMP3XX_SENSOR_H + +#define SEAL_LEVEL_HPA 1013.2f + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class BMP3XXSensor : public TelemetrySensor +{ +protected: + Adafruit_BMP3XX bmp3xx; + float pressureHPa = 0.0f; + float temperatureCelcius = 0.0f; + float altitudeAmslMetres = 0.0f; + +public: + BMP3XXSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual float getAltitudeAMSL(); +}; + +#endif + +#endif \ No newline at end of file From 47e1580a62fc0dbd4429a9c3ca9b4e3fc0fe6e8c Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:01:43 +1000 Subject: [PATCH 1006/1377] Integration test --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 +- src/modules/Telemetry/Sensor/BMP3XXSensor.cpp | 4 ++++ src/modules/Telemetry/Sensor/BMP3XXSensor.h | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 51b49a0bc..89e3b9d5f 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -41,7 +41,6 @@ BMP085Sensor bmp085Sensor; BMP280Sensor bmp280Sensor; BME280Sensor bme280Sensor; -BMP3XXSensor bmp3xxSensor; BME680Sensor bme680Sensor; MCP9808Sensor mcp9808Sensor; SHTC3Sensor shtc3Sensor; @@ -56,6 +55,7 @@ AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; NAU7802Sensor nau7802Sensor; +extern BMP3XXSensor bmp3xxSensor; #ifdef T1000X_SENSOR_EN T1000xSensor t1000xSensor; #endif diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp index 7aec83818..6f0f9fc10 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp @@ -10,6 +10,10 @@ BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX"){} +BMP3XXSensor bmp3xxSensor; + +void BMP3XXSensor::setup(){}; + int32_t BMP3XXSensor::runOnce() { LOG_INFO("Init sensor: %s\n", sensorName); diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h index 262d581e9..514ee1762 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h @@ -21,11 +21,14 @@ protected: public: BMP3XXSensor(); + virtual void setup() override; virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual float getAltitudeAMSL(); }; +extern BMP3XXSensor bmp3xxSensor; + #endif #endif \ No newline at end of file From 6d2011c17241c7213814672ccec6b5c2d2f55ff6 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:25:55 +1000 Subject: [PATCH 1007/1377] Revert "Update nightly.yml" This reverts commit 44b975386d042b1810d5f3e1f2796af3ba7c118a. --- .github/workflows/nightly.yml | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 67256c21e..e249823a7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,20 +1,19 @@ name: Nightly -on: workflow_dispatch -# on: -# schedule: -# - cron: 0 8 * * 1-5 -# workflow_dispatch: {} -# -# jobs: -# trunk_check: -# name: Trunk Check Upload -# runs-on: ubuntu-latest -# -# steps: -# - name: Checkout -# uses: actions/checkout@v4 -# -# - name: Trunk Check -# uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b -# with: -# trunk-token: ${{ secrets.TRUNK_TOKEN }} +on: + schedule: + - cron: 0 8 * * 1-5 + workflow_dispatch: {} + +jobs: + trunk_check: + name: Trunk Check Upload + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Trunk Check + uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b + with: + trunk-token: ${{ secrets.TRUNK_TOKEN }} From 28d0cef42760b81825e905b890a52aa977cf2646 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:31:37 +1000 Subject: [PATCH 1008/1377] Undo inadvertent changes to extensions.json --- .vscode/extensions.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 080e70d08..783791f0b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,9 +2,8 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "platformio.platformio-ide" + "ms-vscode.cpptools", + "platformio.platformio-ide", + "trunk.io" ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} +} \ No newline at end of file From db870dc17db36cee31bfbe60ea5f006e9b4bc42f Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:35:27 +1000 Subject: [PATCH 1009/1377] Update extensions.json --- .vscode/extensions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 783791f0b..b50c95349 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,8 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", + "ms-vscode.cpptools", "platformio.platformio-ide", "trunk.io" ], -} \ No newline at end of file +} From 171512d2f65fe9819a341d68cbd263e10764549b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Aug 2024 11:42:27 -0500 Subject: [PATCH 1010/1377] Fixed buzzer --- src/mesh/NodeDB.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 004f27a0d..bf8596423 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -398,6 +398,7 @@ void NodeDB::installDefaultModuleConfig() #if defined(PIN_BUZZER) moduleConfig.external_notification.enabled = true; moduleConfig.external_notification.output_buzzer = PIN_BUZZER; + moduleConfig.external_notification.use_pwm = true; moduleConfig.external_notification.alert_message_buzzer = true; moduleConfig.external_notification.nag_timeout = 60; #endif @@ -410,6 +411,7 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = 60; #endif + #ifdef HAS_I2S // Don't worry about the other settings for T-Watch, we'll also use the DRV2056 behavior for notifications moduleConfig.external_notification.enabled = true; From 50631f96fc02e1d842de407c62e665a9a747b1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 29 Aug 2024 21:51:06 +0200 Subject: [PATCH 1011/1377] trunk fmt --- src/graphics/Screen.cpp | 2 +- src/graphics/Screen.h | 19 +- src/graphics/fonts/OLEDDisplayFontsPL.cpp | 868 +++++++++++----------- 3 files changed, 442 insertions(+), 447 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f8abe030d..04fe73e44 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -123,7 +123,7 @@ static bool heartbeat = false; /// Check if the display can render a string (detect special chars; emoji) static bool haveGlyphs(const char *str) { -#if defined(OLED_PL) ||defined(OLED_UA) || defined(OLED_RU) +#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) // Don't want to make any assumptions about custom language support return true; #endif diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 7db580272..1b6f541be 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -322,7 +322,7 @@ class Screen : public concurrency::OSThread uint8_t last = LASTCHAR; // get last char LASTCHAR = ch; -#if defined(OLED_PL) +#if defined(OLED_PL) switch (last) { // conversion depending on first UTF8-character case 0xC2: { @@ -333,28 +333,25 @@ class Screen : public concurrency::OSThread if (ch == 147) return (uint8_t)(ch); // Ó - else - if (ch == 179) + else if (ch == 179) return (uint8_t)(148); // ó - else + else return (uint8_t)(ch | 0xC0); break; - } - case 0xC4: { + case 0xC4: { SKIPREST = false; return (uint8_t)(ch); } - + case 0xC5: { SKIPREST = false; if (ch == 132) return (uint8_t)(136); // ń - else - if (ch == 186) + else if (ch == 186) return (uint8_t)(137); // ź - else + else return (uint8_t)(ch); break; } @@ -364,7 +361,6 @@ class Screen : public concurrency::OSThread if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5) return (uint8_t)0; - #endif #if defined(OLED_UA) || defined(OLED_RU) @@ -425,7 +421,6 @@ class Screen : public concurrency::OSThread #endif - // If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the // rest of it if (SKIPREST) diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.cpp b/src/graphics/fonts/OLEDDisplayFontsPL.cpp index 1322b1772..03fdab5fa 100644 --- a/src/graphics/fonts/OLEDDisplayFontsPL.cpp +++ b/src/graphics/fonts/OLEDDisplayFontsPL.cpp @@ -2,439 +2,439 @@ // Font generated or edited with the glyphEditor const uint8_t ArialMT_Plain_10_PL[] PROGMEM = { -0x0A, // Width: 10 -0x0D, // Height: 13 -0x20, // First char: 32 -0xE0, // Number of chars: 224 + 0x0A, // Width: 10 + 0x0D, // Height: 13 + 0x20, // First char: 32 + 0xE0, // Number of chars: 224 -// Jump Table: -0xFF, 0xFF, 0x00, 0x03, // 32:65535 -0x00, 0x00, 0x04, 0x03, // 33 -0x00, 0x04, 0x05, 0x04, // 34 -0x00, 0x09, 0x09, 0x06, // 35 -0x00, 0x12, 0x0A, 0x06, // 36 -0x00, 0x1C, 0x10, 0x09, // 37 -0x00, 0x2C, 0x0E, 0x08, // 38 -0x00, 0x3A, 0x01, 0x02, // 39 -0x00, 0x3B, 0x06, 0x04, // 40 -0x00, 0x41, 0x06, 0x04, // 41 -0x00, 0x47, 0x05, 0x04, // 42 -0x00, 0x4C, 0x09, 0x06, // 43 -0x00, 0x55, 0x04, 0x03, // 44 -0x00, 0x59, 0x03, 0x03, // 45 -0x00, 0x5C, 0x04, 0x03, // 46 -0x00, 0x60, 0x05, 0x04, // 47 -0x00, 0x65, 0x0A, 0x06, // 48 -0x00, 0x6F, 0x08, 0x05, // 49 -0x00, 0x77, 0x0A, 0x06, // 50 -0x00, 0x81, 0x0A, 0x06, // 51 -0x00, 0x8B, 0x0B, 0x07, // 52 -0x00, 0x96, 0x0A, 0x06, // 53 -0x00, 0xA0, 0x0A, 0x06, // 54 -0x00, 0xAA, 0x09, 0x06, // 55 -0x00, 0xB3, 0x0A, 0x06, // 56 -0x00, 0xBD, 0x0A, 0x06, // 57 -0x00, 0xC7, 0x04, 0x03, // 58 -0x00, 0xCB, 0x04, 0x03, // 59 -0x00, 0xCF, 0x0A, 0x06, // 60 -0x00, 0xD9, 0x09, 0x06, // 61 -0x00, 0xE2, 0x09, 0x06, // 62 -0x00, 0xEB, 0x0B, 0x07, // 63 -0x00, 0xF6, 0x14, 0x0B, // 64 -0x01, 0x0A, 0x0E, 0x08, // 65 -0x01, 0x18, 0x0C, 0x07, // 66 -0x01, 0x24, 0x0C, 0x07, // 67 -0x01, 0x30, 0x0B, 0x07, // 68 -0x01, 0x3B, 0x0C, 0x07, // 69 -0x01, 0x47, 0x09, 0x06, // 70 -0x01, 0x50, 0x0D, 0x08, // 71 -0x01, 0x5D, 0x0C, 0x07, // 72 -0x01, 0x69, 0x04, 0x03, // 73 -0x01, 0x6D, 0x08, 0x05, // 74 -0x01, 0x75, 0x0E, 0x08, // 75 -0x01, 0x83, 0x0C, 0x07, // 76 -0x01, 0x8F, 0x10, 0x09, // 77 -0x01, 0x9F, 0x0C, 0x07, // 78 -0x01, 0xAB, 0x0E, 0x08, // 79 -0x01, 0xB9, 0x0B, 0x07, // 80 -0x01, 0xC4, 0x0E, 0x08, // 81 -0x01, 0xD2, 0x0C, 0x07, // 82 -0x01, 0xDE, 0x0C, 0x07, // 83 -0x01, 0xEA, 0x0B, 0x07, // 84 -0x01, 0xF5, 0x0C, 0x07, // 85 -0x02, 0x01, 0x0D, 0x08, // 86 -0x02, 0x0E, 0x11, 0x0A, // 87 -0x02, 0x1F, 0x0E, 0x08, // 88 -0x02, 0x2D, 0x0D, 0x08, // 89 -0x02, 0x3A, 0x0C, 0x07, // 90 -0x02, 0x46, 0x06, 0x04, // 91 -0x02, 0x4C, 0x06, 0x04, // 92 -0x02, 0x52, 0x04, 0x03, // 93 -0x02, 0x56, 0x09, 0x06, // 94 -0x02, 0x5F, 0x0C, 0x07, // 95 -0x02, 0x6B, 0x03, 0x03, // 96 -0x02, 0x6E, 0x0A, 0x06, // 97 -0x02, 0x78, 0x0A, 0x06, // 98 -0x02, 0x82, 0x0A, 0x06, // 99 -0x02, 0x8C, 0x0A, 0x06, // 100 -0x02, 0x96, 0x0A, 0x06, // 101 -0x02, 0xA0, 0x05, 0x04, // 102 -0x02, 0xA5, 0x0A, 0x06, // 103 -0x02, 0xAF, 0x0A, 0x06, // 104 -0x02, 0xB9, 0x04, 0x03, // 105 -0x02, 0xBD, 0x04, 0x03, // 106 -0x02, 0xC1, 0x08, 0x05, // 107 -0x02, 0xC9, 0x04, 0x03, // 108 -0x02, 0xCD, 0x10, 0x09, // 109 -0x02, 0xDD, 0x0A, 0x06, // 110 -0x02, 0xE7, 0x0A, 0x06, // 111 -0x02, 0xF1, 0x0A, 0x06, // 112 -0x02, 0xFB, 0x0A, 0x06, // 113 -0x03, 0x05, 0x05, 0x04, // 114 -0x03, 0x0A, 0x08, 0x05, // 115 -0x03, 0x12, 0x06, 0x04, // 116 -0x03, 0x18, 0x0A, 0x06, // 117 -0x03, 0x22, 0x09, 0x06, // 118 -0x03, 0x2B, 0x0E, 0x08, // 119 -0x03, 0x39, 0x0A, 0x06, // 120 -0x03, 0x43, 0x09, 0x06, // 121 -0x03, 0x4C, 0x0A, 0x06, // 122 -0x03, 0x56, 0x06, 0x04, // 123 -0x03, 0x5C, 0x04, 0x03, // 124 -0x03, 0x60, 0x05, 0x04, // 125 -0x03, 0x65, 0x09, 0x06, // 126 -0xFF, 0xFF, 0x00, 0x0A, // 127 -0xFF, 0xFF, 0x00, 0x0A, // 128 -0x03, 0x6E, 0x0C, 0x07, // 129 -0x03, 0x7A, 0x05, 0x04, // 130 -0x03, 0x7F, 0x0C, 0x07, // 131 -0x03, 0x8B, 0x0E, 0x08, // 132 -0x03, 0x99, 0x0C, 0x07, // 133 -0x03, 0xA5, 0x0C, 0x07, // 134 -0x03, 0xB1, 0x0A, 0x06, // 135 -0x03, 0xBB, 0x0A, 0x06, // 136 -0x03, 0xC5, 0x0A, 0x06, // 137 -0xFF, 0xFF, 0x00, 0x0A, // 138 -0xFF, 0xFF, 0x00, 0x0A, // 139 -0xFF, 0xFF, 0x00, 0x0A, // 140 -0xFF, 0xFF, 0x00, 0x0A, // 141 -0xFF, 0xFF, 0x00, 0x0A, // 142 -0xFF, 0xFF, 0x00, 0x0A, // 143 -0xFF, 0xFF, 0x00, 0x0A, // 144 -0xFF, 0xFF, 0x00, 0x0A, // 145 -0xFF, 0xFF, 0x00, 0x0A, // 146 -0x03, 0xCF, 0x0E, 0x08, // 147 -0x03, 0xDD, 0x0A, 0x06, // 148 -0xFF, 0xFF, 0x00, 0x0A, // 149 -0xFF, 0xFF, 0x00, 0x0A, // 150 -0xFF, 0xFF, 0x00, 0x0A, // 151 -0x03, 0xE7, 0x0C, 0x07, // 152 -0x03, 0xF3, 0x0C, 0x07, // 153 -0x03, 0xFF, 0x0C, 0x07, // 154 -0x04, 0x0B, 0x08, 0x05, // 155 -0xFF, 0xFF, 0x00, 0x0A, // 156 -0xFF, 0xFF, 0x00, 0x0A, // 157 -0xFF, 0xFF, 0x00, 0x0A, // 158 -0xFF, 0xFF, 0x00, 0x0A, // 159 -0xFF, 0xFF, 0x00, 0x0A, // 160 -0x04, 0x13, 0x04, 0x03, // 161 -0x04, 0x17, 0x0A, 0x06, // 162 -0x04, 0x21, 0x0C, 0x07, // 163 -0x04, 0x2D, 0x0A, 0x06, // 164 -0x04, 0x37, 0x0A, 0x06, // 165 -0x04, 0x41, 0x04, 0x03, // 166 -0x04, 0x45, 0x0A, 0x06, // 167 -0x04, 0x4F, 0x05, 0x04, // 168 -0x04, 0x54, 0x0D, 0x08, // 169 -0x04, 0x61, 0x07, 0x05, // 170 -0x04, 0x68, 0x0A, 0x06, // 171 -0x04, 0x72, 0x09, 0x06, // 172 -0x04, 0x7B, 0x03, 0x03, // 173 -0x04, 0x7E, 0x0D, 0x08, // 174 -0x04, 0x8B, 0x0B, 0x07, // 175 -0x04, 0x96, 0x07, 0x05, // 176 -0x04, 0x9D, 0x0A, 0x06, // 177 -0x04, 0xA7, 0x05, 0x04, // 178 -0x04, 0xAC, 0x05, 0x04, // 179 -0x04, 0xB1, 0x05, 0x04, // 180 -0x04, 0xB6, 0x0A, 0x06, // 181 -0x04, 0xC0, 0x09, 0x06, // 182 -0x04, 0xC9, 0x03, 0x03, // 183 -0x04, 0xCC, 0x06, 0x04, // 184 -0x04, 0xD2, 0x0C, 0x07, // 185 -0x04, 0xDE, 0x07, 0x05, // 186 -0x04, 0xE5, 0x0C, 0x07, // 187 -0x04, 0xF1, 0x0A, 0x06, // 188 -0x04, 0xFB, 0x10, 0x09, // 189 -0x05, 0x0B, 0x10, 0x09, // 190 -0x05, 0x1B, 0x0A, 0x06, // 191 -0x05, 0x25, 0x0E, 0x08, // 192 -0x05, 0x33, 0x0E, 0x08, // 193 -0x05, 0x41, 0x0E, 0x08, // 194 -0x05, 0x4F, 0x0E, 0x08, // 195 -0x05, 0x5D, 0x0E, 0x08, // 196 -0x05, 0x6B, 0x0E, 0x08, // 197 -0x05, 0x79, 0x12, 0x0A, // 198 -0x05, 0x8B, 0x0C, 0x07, // 199 -0x05, 0x97, 0x0C, 0x07, // 200 -0x05, 0xA3, 0x0C, 0x07, // 201 -0x05, 0xAF, 0x0C, 0x07, // 202 -0x05, 0xBB, 0x0C, 0x07, // 203 -0x05, 0xC7, 0x05, 0x04, // 204 -0x05, 0xCC, 0x04, 0x03, // 205 -0x05, 0xD0, 0x04, 0x03, // 206 -0x05, 0xD4, 0x05, 0x04, // 207 -0x05, 0xD9, 0x0B, 0x07, // 208 -0x05, 0xE4, 0x0C, 0x07, // 209 -0x05, 0xF0, 0x0E, 0x08, // 210 -0x05, 0xFE, 0x0E, 0x08, // 211 -0x06, 0x0C, 0x0E, 0x08, // 212 -0x06, 0x1A, 0x0E, 0x08, // 213 -0x06, 0x28, 0x0E, 0x08, // 214 -0x06, 0x36, 0x0A, 0x06, // 215 -0x06, 0x40, 0x0D, 0x08, // 216 -0x06, 0x4D, 0x0C, 0x07, // 217 -0x06, 0x59, 0x0C, 0x07, // 218 -0x06, 0x65, 0x0C, 0x07, // 219 -0x06, 0x71, 0x0C, 0x07, // 220 -0x06, 0x7D, 0x0D, 0x08, // 221 -0x06, 0x8A, 0x0B, 0x07, // 222 -0x06, 0x95, 0x0C, 0x07, // 223 -0x06, 0xA1, 0x0A, 0x06, // 224 -0x06, 0xAB, 0x0A, 0x06, // 225 -0x06, 0xB5, 0x0A, 0x06, // 226 -0x06, 0xBF, 0x0A, 0x06, // 227 -0x06, 0xC9, 0x0A, 0x06, // 228 -0x06, 0xD3, 0x0A, 0x06, // 229 -0x06, 0xDD, 0x10, 0x09, // 230 -0x06, 0xED, 0x0A, 0x06, // 231 -0x06, 0xF7, 0x0A, 0x06, // 232 -0x07, 0x01, 0x0A, 0x06, // 233 -0x07, 0x0B, 0x0A, 0x06, // 234 -0x07, 0x15, 0x0A, 0x06, // 235 -0x07, 0x1F, 0x05, 0x04, // 236 -0x07, 0x24, 0x04, 0x03, // 237 -0x07, 0x28, 0x05, 0x04, // 238 -0x07, 0x2D, 0x05, 0x04, // 239 -0x07, 0x32, 0x0A, 0x06, // 240 -0x07, 0x3C, 0x0A, 0x06, // 241 -0x07, 0x46, 0x0A, 0x06, // 242 -0x07, 0x50, 0x0A, 0x06, // 243 -0x07, 0x5A, 0x0A, 0x06, // 244 -0x07, 0x64, 0x0A, 0x06, // 245 -0x07, 0x6E, 0x0A, 0x06, // 246 -0x07, 0x78, 0x09, 0x06, // 247 -0x07, 0x81, 0x0A, 0x06, // 248 -0x07, 0x8B, 0x0A, 0x06, // 249 -0x07, 0x95, 0x0A, 0x06, // 250 -0x07, 0x9F, 0x0A, 0x06, // 251 -0x07, 0xA9, 0x0A, 0x06, // 252 -0x07, 0xB3, 0x09, 0x06, // 253 -0x07, 0xBC, 0x0A, 0x06, // 254 -0x07, 0xC6, 0x09, 0x06, // 255 -// Font Data: -0x00, 0x00, 0xF8, 0x02, // 33 -0x38, 0x00, 0x00, 0x00, 0x38, // 34 -0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 -0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 -0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 -0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 -0x38, // 39 -0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 -0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 -0x28, 0x00, 0x18, 0x00, 0x28, // 42 -0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 -0x00, 0x00, 0x00, 0x06, // 44 -0x80, 0x00, 0x80, // 45 -0x00, 0x00, 0x00, 0x02, // 46 -0x00, 0x03, 0xE0, 0x00, 0x18, // 47 -0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 -0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 -0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 -0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 -0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 -0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 -0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 -0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 -0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 -0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 -0x00, 0x00, 0x20, 0x02, // 58 -0x00, 0x00, 0x20, 0x06, // 59 -0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 -0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 -0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 -0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 -0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64 -0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 -0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 -0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 -0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 -0x00, 0x00, 0xF8, 0x03, // 73 -0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 -0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 -0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 -0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 -0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 -0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 -0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 -0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 -0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 -0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 -0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 -0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 -0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 -0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 -0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 -0x08, 0x08, 0xF8, 0x0F, // 93 -0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 -0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 -0x08, 0x00, 0x10, // 96 -0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 -0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 -0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 -0x20, 0x00, 0xF0, 0x03, 0x28, // 102 -0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 -0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 -0x00, 0x00, 0xE8, 0x03, // 105 -0x00, 0x08, 0xE8, 0x07, // 106 -0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 -0x00, 0x00, 0xF8, 0x03, // 108 -0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 -0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 -0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 -0x00, 0x00, 0xE0, 0x03, 0x20, // 114 -0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 -0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 -0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 -0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 -0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 -0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 -0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 -0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 -0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 -0x00, 0x00, 0xF8, 0x0F, // 124 -0x08, 0x08, 0x78, 0x0F, 0x80, // 125 -0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 -0x00, 0x00, 0xF8, 0x03, 0x40, 0x02, 0x20, 0x02, 0x00, 0x02, 0x00, 0x02, // 129 -0x40, 0x00, 0xF8, 0x03, 0x20, // 130 -0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x44, 0x00, 0x82, 0x01, 0xF8, 0x03, // 131 -0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x05, 0x00, 0x0A, // 132 -0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x07, 0x00, 0x08, // 133 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 134 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0x44, 0x01, // 135 -0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x28, 0x00, 0xC4, 0x03, // 136 -0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 137 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 147 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0xC4, 0x01, // 148 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x06, 0x48, 0x0A, // 152 -0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x06, 0x00, 0x08, // 153 -0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 154 -0x40, 0x02, 0xA0, 0x02, 0xA8, 0x02, 0x24, 0x01, // 155 -0x00, 0x00, 0xA0, 0x0F, // 161 -0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162 -0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163 -0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164 -0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165 -0x00, 0x00, 0x38, 0x0F, // 166 -0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167 -0x08, 0x00, 0x00, 0x00, 0x08, // 168 -0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169 -0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170 -0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171 -0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172 -0x80, 0x00, 0x80, // 173 -0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174 -0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175 -0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176 -0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177 -0x48, 0x00, 0x68, 0x00, 0x58, // 178 -0x48, 0x00, 0x58, 0x00, 0x68, // 179 -0x00, 0x00, 0x10, 0x00, 0x08, // 180 -0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181 -0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182 -0x00, 0x00, 0x40, // 183 -0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184 -0x08, 0x03, 0x88, 0x02, 0xCA, 0x02, 0x69, 0x02, 0x38, 0x02, 0x18, 0x02, // 185 -0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186 -0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x6A, 0x02, 0x38, 0x02, 0x18, 0x02, // 187 -0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x60, 0x02, 0x20, 0x02, // 188 -0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189 -0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190 -0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191 -0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192 -0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193 -0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194 -0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195 -0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196 -0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197 -0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199 -0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200 -0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201 -0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202 -0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203 -0x00, 0x00, 0xF9, 0x03, 0x02, // 204 -0x02, 0x00, 0xF9, 0x03, // 205 -0x01, 0x00, 0xFA, 0x03, // 206 -0x02, 0x00, 0xF8, 0x03, 0x02, // 207 -0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208 -0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211 -0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212 -0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213 -0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214 -0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215 -0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216 -0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217 -0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218 -0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219 -0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220 -0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221 -0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 -0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223 -0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224 -0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225 -0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226 -0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227 -0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228 -0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229 -0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230 -0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231 -0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232 -0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233 -0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234 -0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235 -0x00, 0x00, 0xE4, 0x03, 0x08, // 236 -0x08, 0x00, 0xE4, 0x03, // 237 -0x08, 0x00, 0xE4, 0x03, 0x08, // 238 -0x08, 0x00, 0xE0, 0x03, 0x08, // 239 -0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240 -0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241 -0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242 -0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243 -0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244 -0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245 -0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246 -0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247 -0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248 -0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249 -0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250 -0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251 -0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252 -0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253 -0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254 -0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255 + // Jump Table: + 0xFF, 0xFF, 0x00, 0x03, // 32:65535 + 0x00, 0x00, 0x04, 0x03, // 33 + 0x00, 0x04, 0x05, 0x04, // 34 + 0x00, 0x09, 0x09, 0x06, // 35 + 0x00, 0x12, 0x0A, 0x06, // 36 + 0x00, 0x1C, 0x10, 0x09, // 37 + 0x00, 0x2C, 0x0E, 0x08, // 38 + 0x00, 0x3A, 0x01, 0x02, // 39 + 0x00, 0x3B, 0x06, 0x04, // 40 + 0x00, 0x41, 0x06, 0x04, // 41 + 0x00, 0x47, 0x05, 0x04, // 42 + 0x00, 0x4C, 0x09, 0x06, // 43 + 0x00, 0x55, 0x04, 0x03, // 44 + 0x00, 0x59, 0x03, 0x03, // 45 + 0x00, 0x5C, 0x04, 0x03, // 46 + 0x00, 0x60, 0x05, 0x04, // 47 + 0x00, 0x65, 0x0A, 0x06, // 48 + 0x00, 0x6F, 0x08, 0x05, // 49 + 0x00, 0x77, 0x0A, 0x06, // 50 + 0x00, 0x81, 0x0A, 0x06, // 51 + 0x00, 0x8B, 0x0B, 0x07, // 52 + 0x00, 0x96, 0x0A, 0x06, // 53 + 0x00, 0xA0, 0x0A, 0x06, // 54 + 0x00, 0xAA, 0x09, 0x06, // 55 + 0x00, 0xB3, 0x0A, 0x06, // 56 + 0x00, 0xBD, 0x0A, 0x06, // 57 + 0x00, 0xC7, 0x04, 0x03, // 58 + 0x00, 0xCB, 0x04, 0x03, // 59 + 0x00, 0xCF, 0x0A, 0x06, // 60 + 0x00, 0xD9, 0x09, 0x06, // 61 + 0x00, 0xE2, 0x09, 0x06, // 62 + 0x00, 0xEB, 0x0B, 0x07, // 63 + 0x00, 0xF6, 0x14, 0x0B, // 64 + 0x01, 0x0A, 0x0E, 0x08, // 65 + 0x01, 0x18, 0x0C, 0x07, // 66 + 0x01, 0x24, 0x0C, 0x07, // 67 + 0x01, 0x30, 0x0B, 0x07, // 68 + 0x01, 0x3B, 0x0C, 0x07, // 69 + 0x01, 0x47, 0x09, 0x06, // 70 + 0x01, 0x50, 0x0D, 0x08, // 71 + 0x01, 0x5D, 0x0C, 0x07, // 72 + 0x01, 0x69, 0x04, 0x03, // 73 + 0x01, 0x6D, 0x08, 0x05, // 74 + 0x01, 0x75, 0x0E, 0x08, // 75 + 0x01, 0x83, 0x0C, 0x07, // 76 + 0x01, 0x8F, 0x10, 0x09, // 77 + 0x01, 0x9F, 0x0C, 0x07, // 78 + 0x01, 0xAB, 0x0E, 0x08, // 79 + 0x01, 0xB9, 0x0B, 0x07, // 80 + 0x01, 0xC4, 0x0E, 0x08, // 81 + 0x01, 0xD2, 0x0C, 0x07, // 82 + 0x01, 0xDE, 0x0C, 0x07, // 83 + 0x01, 0xEA, 0x0B, 0x07, // 84 + 0x01, 0xF5, 0x0C, 0x07, // 85 + 0x02, 0x01, 0x0D, 0x08, // 86 + 0x02, 0x0E, 0x11, 0x0A, // 87 + 0x02, 0x1F, 0x0E, 0x08, // 88 + 0x02, 0x2D, 0x0D, 0x08, // 89 + 0x02, 0x3A, 0x0C, 0x07, // 90 + 0x02, 0x46, 0x06, 0x04, // 91 + 0x02, 0x4C, 0x06, 0x04, // 92 + 0x02, 0x52, 0x04, 0x03, // 93 + 0x02, 0x56, 0x09, 0x06, // 94 + 0x02, 0x5F, 0x0C, 0x07, // 95 + 0x02, 0x6B, 0x03, 0x03, // 96 + 0x02, 0x6E, 0x0A, 0x06, // 97 + 0x02, 0x78, 0x0A, 0x06, // 98 + 0x02, 0x82, 0x0A, 0x06, // 99 + 0x02, 0x8C, 0x0A, 0x06, // 100 + 0x02, 0x96, 0x0A, 0x06, // 101 + 0x02, 0xA0, 0x05, 0x04, // 102 + 0x02, 0xA5, 0x0A, 0x06, // 103 + 0x02, 0xAF, 0x0A, 0x06, // 104 + 0x02, 0xB9, 0x04, 0x03, // 105 + 0x02, 0xBD, 0x04, 0x03, // 106 + 0x02, 0xC1, 0x08, 0x05, // 107 + 0x02, 0xC9, 0x04, 0x03, // 108 + 0x02, 0xCD, 0x10, 0x09, // 109 + 0x02, 0xDD, 0x0A, 0x06, // 110 + 0x02, 0xE7, 0x0A, 0x06, // 111 + 0x02, 0xF1, 0x0A, 0x06, // 112 + 0x02, 0xFB, 0x0A, 0x06, // 113 + 0x03, 0x05, 0x05, 0x04, // 114 + 0x03, 0x0A, 0x08, 0x05, // 115 + 0x03, 0x12, 0x06, 0x04, // 116 + 0x03, 0x18, 0x0A, 0x06, // 117 + 0x03, 0x22, 0x09, 0x06, // 118 + 0x03, 0x2B, 0x0E, 0x08, // 119 + 0x03, 0x39, 0x0A, 0x06, // 120 + 0x03, 0x43, 0x09, 0x06, // 121 + 0x03, 0x4C, 0x0A, 0x06, // 122 + 0x03, 0x56, 0x06, 0x04, // 123 + 0x03, 0x5C, 0x04, 0x03, // 124 + 0x03, 0x60, 0x05, 0x04, // 125 + 0x03, 0x65, 0x09, 0x06, // 126 + 0xFF, 0xFF, 0x00, 0x0A, // 127 + 0xFF, 0xFF, 0x00, 0x0A, // 128 + 0x03, 0x6E, 0x0C, 0x07, // 129 + 0x03, 0x7A, 0x05, 0x04, // 130 + 0x03, 0x7F, 0x0C, 0x07, // 131 + 0x03, 0x8B, 0x0E, 0x08, // 132 + 0x03, 0x99, 0x0C, 0x07, // 133 + 0x03, 0xA5, 0x0C, 0x07, // 134 + 0x03, 0xB1, 0x0A, 0x06, // 135 + 0x03, 0xBB, 0x0A, 0x06, // 136 + 0x03, 0xC5, 0x0A, 0x06, // 137 + 0xFF, 0xFF, 0x00, 0x0A, // 138 + 0xFF, 0xFF, 0x00, 0x0A, // 139 + 0xFF, 0xFF, 0x00, 0x0A, // 140 + 0xFF, 0xFF, 0x00, 0x0A, // 141 + 0xFF, 0xFF, 0x00, 0x0A, // 142 + 0xFF, 0xFF, 0x00, 0x0A, // 143 + 0xFF, 0xFF, 0x00, 0x0A, // 144 + 0xFF, 0xFF, 0x00, 0x0A, // 145 + 0xFF, 0xFF, 0x00, 0x0A, // 146 + 0x03, 0xCF, 0x0E, 0x08, // 147 + 0x03, 0xDD, 0x0A, 0x06, // 148 + 0xFF, 0xFF, 0x00, 0x0A, // 149 + 0xFF, 0xFF, 0x00, 0x0A, // 150 + 0xFF, 0xFF, 0x00, 0x0A, // 151 + 0x03, 0xE7, 0x0C, 0x07, // 152 + 0x03, 0xF3, 0x0C, 0x07, // 153 + 0x03, 0xFF, 0x0C, 0x07, // 154 + 0x04, 0x0B, 0x08, 0x05, // 155 + 0xFF, 0xFF, 0x00, 0x0A, // 156 + 0xFF, 0xFF, 0x00, 0x0A, // 157 + 0xFF, 0xFF, 0x00, 0x0A, // 158 + 0xFF, 0xFF, 0x00, 0x0A, // 159 + 0xFF, 0xFF, 0x00, 0x0A, // 160 + 0x04, 0x13, 0x04, 0x03, // 161 + 0x04, 0x17, 0x0A, 0x06, // 162 + 0x04, 0x21, 0x0C, 0x07, // 163 + 0x04, 0x2D, 0x0A, 0x06, // 164 + 0x04, 0x37, 0x0A, 0x06, // 165 + 0x04, 0x41, 0x04, 0x03, // 166 + 0x04, 0x45, 0x0A, 0x06, // 167 + 0x04, 0x4F, 0x05, 0x04, // 168 + 0x04, 0x54, 0x0D, 0x08, // 169 + 0x04, 0x61, 0x07, 0x05, // 170 + 0x04, 0x68, 0x0A, 0x06, // 171 + 0x04, 0x72, 0x09, 0x06, // 172 + 0x04, 0x7B, 0x03, 0x03, // 173 + 0x04, 0x7E, 0x0D, 0x08, // 174 + 0x04, 0x8B, 0x0B, 0x07, // 175 + 0x04, 0x96, 0x07, 0x05, // 176 + 0x04, 0x9D, 0x0A, 0x06, // 177 + 0x04, 0xA7, 0x05, 0x04, // 178 + 0x04, 0xAC, 0x05, 0x04, // 179 + 0x04, 0xB1, 0x05, 0x04, // 180 + 0x04, 0xB6, 0x0A, 0x06, // 181 + 0x04, 0xC0, 0x09, 0x06, // 182 + 0x04, 0xC9, 0x03, 0x03, // 183 + 0x04, 0xCC, 0x06, 0x04, // 184 + 0x04, 0xD2, 0x0C, 0x07, // 185 + 0x04, 0xDE, 0x07, 0x05, // 186 + 0x04, 0xE5, 0x0C, 0x07, // 187 + 0x04, 0xF1, 0x0A, 0x06, // 188 + 0x04, 0xFB, 0x10, 0x09, // 189 + 0x05, 0x0B, 0x10, 0x09, // 190 + 0x05, 0x1B, 0x0A, 0x06, // 191 + 0x05, 0x25, 0x0E, 0x08, // 192 + 0x05, 0x33, 0x0E, 0x08, // 193 + 0x05, 0x41, 0x0E, 0x08, // 194 + 0x05, 0x4F, 0x0E, 0x08, // 195 + 0x05, 0x5D, 0x0E, 0x08, // 196 + 0x05, 0x6B, 0x0E, 0x08, // 197 + 0x05, 0x79, 0x12, 0x0A, // 198 + 0x05, 0x8B, 0x0C, 0x07, // 199 + 0x05, 0x97, 0x0C, 0x07, // 200 + 0x05, 0xA3, 0x0C, 0x07, // 201 + 0x05, 0xAF, 0x0C, 0x07, // 202 + 0x05, 0xBB, 0x0C, 0x07, // 203 + 0x05, 0xC7, 0x05, 0x04, // 204 + 0x05, 0xCC, 0x04, 0x03, // 205 + 0x05, 0xD0, 0x04, 0x03, // 206 + 0x05, 0xD4, 0x05, 0x04, // 207 + 0x05, 0xD9, 0x0B, 0x07, // 208 + 0x05, 0xE4, 0x0C, 0x07, // 209 + 0x05, 0xF0, 0x0E, 0x08, // 210 + 0x05, 0xFE, 0x0E, 0x08, // 211 + 0x06, 0x0C, 0x0E, 0x08, // 212 + 0x06, 0x1A, 0x0E, 0x08, // 213 + 0x06, 0x28, 0x0E, 0x08, // 214 + 0x06, 0x36, 0x0A, 0x06, // 215 + 0x06, 0x40, 0x0D, 0x08, // 216 + 0x06, 0x4D, 0x0C, 0x07, // 217 + 0x06, 0x59, 0x0C, 0x07, // 218 + 0x06, 0x65, 0x0C, 0x07, // 219 + 0x06, 0x71, 0x0C, 0x07, // 220 + 0x06, 0x7D, 0x0D, 0x08, // 221 + 0x06, 0x8A, 0x0B, 0x07, // 222 + 0x06, 0x95, 0x0C, 0x07, // 223 + 0x06, 0xA1, 0x0A, 0x06, // 224 + 0x06, 0xAB, 0x0A, 0x06, // 225 + 0x06, 0xB5, 0x0A, 0x06, // 226 + 0x06, 0xBF, 0x0A, 0x06, // 227 + 0x06, 0xC9, 0x0A, 0x06, // 228 + 0x06, 0xD3, 0x0A, 0x06, // 229 + 0x06, 0xDD, 0x10, 0x09, // 230 + 0x06, 0xED, 0x0A, 0x06, // 231 + 0x06, 0xF7, 0x0A, 0x06, // 232 + 0x07, 0x01, 0x0A, 0x06, // 233 + 0x07, 0x0B, 0x0A, 0x06, // 234 + 0x07, 0x15, 0x0A, 0x06, // 235 + 0x07, 0x1F, 0x05, 0x04, // 236 + 0x07, 0x24, 0x04, 0x03, // 237 + 0x07, 0x28, 0x05, 0x04, // 238 + 0x07, 0x2D, 0x05, 0x04, // 239 + 0x07, 0x32, 0x0A, 0x06, // 240 + 0x07, 0x3C, 0x0A, 0x06, // 241 + 0x07, 0x46, 0x0A, 0x06, // 242 + 0x07, 0x50, 0x0A, 0x06, // 243 + 0x07, 0x5A, 0x0A, 0x06, // 244 + 0x07, 0x64, 0x0A, 0x06, // 245 + 0x07, 0x6E, 0x0A, 0x06, // 246 + 0x07, 0x78, 0x09, 0x06, // 247 + 0x07, 0x81, 0x0A, 0x06, // 248 + 0x07, 0x8B, 0x0A, 0x06, // 249 + 0x07, 0x95, 0x0A, 0x06, // 250 + 0x07, 0x9F, 0x0A, 0x06, // 251 + 0x07, 0xA9, 0x0A, 0x06, // 252 + 0x07, 0xB3, 0x09, 0x06, // 253 + 0x07, 0xBC, 0x0A, 0x06, // 254 + 0x07, 0xC6, 0x09, 0x06, // 255 + // Font Data: + 0x00, 0x00, 0xF8, 0x02, // 33 + 0x38, 0x00, 0x00, 0x00, 0x38, // 34 + 0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 + 0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 + 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 + 0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 + 0x38, // 39 + 0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 + 0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 + 0x28, 0x00, 0x18, 0x00, 0x28, // 42 + 0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 + 0x00, 0x00, 0x00, 0x06, // 44 + 0x80, 0x00, 0x80, // 45 + 0x00, 0x00, 0x00, 0x02, // 46 + 0x00, 0x03, 0xE0, 0x00, 0x18, // 47 + 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 + 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 + 0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 + 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 + 0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 + 0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 + 0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 + 0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 + 0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 + 0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 + 0x00, 0x00, 0x20, 0x02, // 58 + 0x00, 0x00, 0x20, 0x06, // 59 + 0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 + 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 + 0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 + 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 + 0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64 + 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 + 0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 + 0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 + 0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 + 0x00, 0x00, 0xF8, 0x03, // 73 + 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 + 0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 + 0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 + 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 + 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 + 0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 + 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 + 0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 + 0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 + 0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 + 0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 + 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 + 0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 + 0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 + 0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 + 0x08, 0x08, 0xF8, 0x0F, // 93 + 0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 + 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 + 0x08, 0x00, 0x10, // 96 + 0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 + 0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 + 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 + 0x20, 0x00, 0xF0, 0x03, 0x28, // 102 + 0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 + 0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 + 0x00, 0x00, 0xE8, 0x03, // 105 + 0x00, 0x08, 0xE8, 0x07, // 106 + 0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 + 0x00, 0x00, 0xF8, 0x03, // 108 + 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 + 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 + 0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 + 0x00, 0x00, 0xE0, 0x03, 0x20, // 114 + 0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 + 0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 + 0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 + 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 + 0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 + 0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 + 0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 + 0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 + 0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 + 0x00, 0x00, 0xF8, 0x0F, // 124 + 0x08, 0x08, 0x78, 0x0F, 0x80, // 125 + 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 + 0x00, 0x00, 0xF8, 0x03, 0x40, 0x02, 0x20, 0x02, 0x00, 0x02, 0x00, 0x02, // 129 + 0x40, 0x00, 0xF8, 0x03, 0x20, // 130 + 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x44, 0x00, 0x82, 0x01, 0xF8, 0x03, // 131 + 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x05, 0x00, 0x0A, // 132 + 0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x07, 0x00, 0x08, // 133 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 134 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0x44, 0x01, // 135 + 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x28, 0x00, 0xC4, 0x03, // 136 + 0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 137 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 147 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0xC4, 0x01, // 148 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x06, 0x48, 0x0A, // 152 + 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x06, 0x00, 0x08, // 153 + 0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 154 + 0x40, 0x02, 0xA0, 0x02, 0xA8, 0x02, 0x24, 0x01, // 155 + 0x00, 0x00, 0xA0, 0x0F, // 161 + 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162 + 0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163 + 0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164 + 0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165 + 0x00, 0x00, 0x38, 0x0F, // 166 + 0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167 + 0x08, 0x00, 0x00, 0x00, 0x08, // 168 + 0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169 + 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170 + 0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171 + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172 + 0x80, 0x00, 0x80, // 173 + 0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174 + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175 + 0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176 + 0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177 + 0x48, 0x00, 0x68, 0x00, 0x58, // 178 + 0x48, 0x00, 0x58, 0x00, 0x68, // 179 + 0x00, 0x00, 0x10, 0x00, 0x08, // 180 + 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181 + 0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182 + 0x00, 0x00, 0x40, // 183 + 0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184 + 0x08, 0x03, 0x88, 0x02, 0xCA, 0x02, 0x69, 0x02, 0x38, 0x02, 0x18, 0x02, // 185 + 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186 + 0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x6A, 0x02, 0x38, 0x02, 0x18, 0x02, // 187 + 0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x60, 0x02, 0x20, 0x02, // 188 + 0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189 + 0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190 + 0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191 + 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192 + 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193 + 0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194 + 0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195 + 0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196 + 0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197 + 0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199 + 0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200 + 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201 + 0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202 + 0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203 + 0x00, 0x00, 0xF9, 0x03, 0x02, // 204 + 0x02, 0x00, 0xF9, 0x03, // 205 + 0x01, 0x00, 0xFA, 0x03, // 206 + 0x02, 0x00, 0xF8, 0x03, 0x02, // 207 + 0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208 + 0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211 + 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212 + 0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213 + 0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214 + 0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215 + 0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216 + 0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217 + 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218 + 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219 + 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220 + 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221 + 0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 + 0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223 + 0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224 + 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225 + 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226 + 0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227 + 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228 + 0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229 + 0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230 + 0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231 + 0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232 + 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233 + 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234 + 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235 + 0x00, 0x00, 0xE4, 0x03, 0x08, // 236 + 0x08, 0x00, 0xE4, 0x03, // 237 + 0x08, 0x00, 0xE4, 0x03, 0x08, // 238 + 0x08, 0x00, 0xE0, 0x03, 0x08, // 239 + 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240 + 0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241 + 0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242 + 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243 + 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244 + 0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245 + 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246 + 0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247 + 0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248 + 0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249 + 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250 + 0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251 + 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252 + 0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253 + 0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254 + 0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255 }; \ No newline at end of file From 22454c95c7711a11632e9fb9483f6de8a0d36d10 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 29 Aug 2024 23:17:44 +0200 Subject: [PATCH 1012/1377] [BOARD] Add Minewsemi MS24SF1 nRF52840 SX1262 Module (SoftDevice 7.3.0) (#4584) * Update architecture.h * Add files via upload * Add files via upload * Update variant.h * Update variant.h * Update variant.cpp * Update variant.cpp * Update variant.cpp --- boards/ms24sf1.json | 58 +++++++++++++ src/platform/nrf52/architecture.h | 2 + variants/MS24SF1/platformio.ini | 15 ++++ variants/MS24SF1/variant.cpp | 30 +++++++ variants/MS24SF1/variant.h | 139 ++++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 boards/ms24sf1.json create mode 100644 variants/MS24SF1/platformio.ini create mode 100644 variants/MS24SF1/variant.cpp create mode 100644 variants/MS24SF1/variant.h diff --git a/boards/ms24sf1.json b/boards/ms24sf1.json new file mode 100644 index 000000000..4e28507da --- /dev/null +++ b/boards/ms24sf1.json @@ -0,0 +1,58 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v7.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "MS24SF1-BOOT", + "mcu": "nrf52840", + "variant": "MINEWSEMI_MS24SF1_SX1262", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "7.3.0", + "sd_fwid": "0x0123" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "MINEWSEMI_MS24SF1_SX1262", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://en.minewsemi.com/lora-module/nrf52840-sx1262-ms24sf1", + "vendor": "Minesemi" +} diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 834ff6f0c..5a04dd6a7 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -67,6 +67,8 @@ #define HW_VENDOR meshtastic_HardwareModel_TRACKER_T1000_E #elif defined(ME25LS01_4Y10TD) #define HW_VENDOR meshtastic_HardwareModel_ME25LS01_4Y10TD +#elif defined(MS24SF1) +#define HW_VENDOR meshtastic_HardwareModel_MS24SF1 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #else diff --git a/variants/MS24SF1/platformio.ini b/variants/MS24SF1/platformio.ini new file mode 100644 index 000000000..5cbd078d0 --- /dev/null +++ b/variants/MS24SF1/platformio.ini @@ -0,0 +1,15 @@ +[env:ms24sf1] +extends = nrf52840_base +board = ms24sf1 +board_level = extra +; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e +build_flags = ${nrf52840_base.build_flags} -Ivariants/MS24SF1 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/MS24SF1> +lib_deps = + ${nrf52840_base.lib_deps} +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = nrfutil +upload_port = /dev/ttyACM1 diff --git a/variants/MS24SF1/variant.cpp b/variants/MS24SF1/variant.cpp new file mode 100644 index 000000000..d1d79c8ce --- /dev/null +++ b/variants/MS24SF1/variant.cpp @@ -0,0 +1,30 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() {} diff --git a/variants/MS24SF1/variant.h b/variants/MS24SF1/variant.h new file mode 100644 index 000000000..d26dcebc2 --- /dev/null +++ b/variants/MS24SF1/variant.h @@ -0,0 +1,139 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_MINEWSEMI_MS24SF1_ +#define _VARIANT_MINEWSEMI_MS24SF1_ + +#define ME25LS01 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// Use the native nrf52 usb power detection +#define NRF_APM + +#define PIN_3V3_EN (32 + 5) //-1 +#define PIN_3V3_ACC_EN -1 + +#define PIN_LED1 (-1) + +#define LED_PIN PIN_LED1 +#define LED_BUILTIN -1 + +#define LED_BLUE -1 +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN (-1) +#define BUTTON_NEED_PULLUP + +#define HAS_WIRE 1 + +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (0 + 29) // P0.15 +#define PIN_WIRE_SCL (0 + 30) // P0.17 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (-1) // P0.14 +#define PIN_SERIAL1_TX (-1) // P0.13 + +#define PIN_SERIAL2_RX (-1) // P0.17 +#define PIN_SERIAL2_TX (-1) // P0.16 + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 // 2 + +#define PIN_SPI_MISO (0 + 17) // MISO P0.17 +#define PIN_SPI_MOSI (0 + 20) // MOSI P0.20 +#define PIN_SPI_SCK (0 + 21) // SCK P0.21 + +// #define PIN_SPI1_MISO (-1) // +// #define PIN_SPI1_MOSI (10) // EPD_MOSI P0.10 +// #define PIN_SPI1_SCK (9) // EPD_SCLK P0.09 + +static const uint8_t SS = (0 + 22); // LORA_CS P0.22 +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +// MINEWSEMI nRF52840+SX1262 MS24SF1 (NRF82540 with integrated SX1262) +#define USE_SX1262 +#define SX126X_CS (0 + 22) // LORA_CS P0.22 +#define SX126X_DIO1 (0 + 16) // DIO1 P0.16 +#define SX126X_BUSY (0 + 19) // LORA_BUSY P0.19 +#define SX126X_RESET (0 + 12) // LORA_RESET P0.12 +#define SX126X_TXEN (32 + 4) // TXEN P1.04 +#define SX126X_RXEN (32 + 2) // RXEN P1.02 + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH + +#define HAS_GPS 0 + +#define PIN_GPS_EN -1 +#define GPS_EN_ACTIVE HIGH +#define PIN_GPS_RESET -1 +#define GPS_VRTC_EN -1 +#define GPS_SLEEP_INT -1 +#define GPS_RTC_INT -1 +#define GPS_RESETB_OUT -1 + +#define BATTERY_PIN -1 +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 + +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 + +// Buzzer +// #define PIN_BUZZER (0 + 25) + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_MINEWSEMI_MS24SF1_ From 5bc17a99112e6695dce63e95be48fb4f2f886361 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Aug 2024 16:28:03 -0500 Subject: [PATCH 1013/1377] Key regen and MQTT fix (#4585) * Add public key regen * Properly label and handle PKI MQTT packets * Extra debug message to indicate PKI_UNKNOWN_PUBKEY * Ternary! * Don't call non-existant function on stm32 * Actually fix STM32 compilation --- src/mesh/CryptoEngine.cpp | 25 +++++++++++++++++++++++++ src/mesh/CryptoEngine.h | 3 +++ src/mesh/NodeDB.cpp | 26 ++++++++++++++++++-------- src/mesh/ReliableRouter.cpp | 2 +- src/modules/AdminModule.cpp | 9 +++++++++ src/mqtt/MQTT.cpp | 22 ++++++---------------- 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index eef9f74b1..a5322a65a 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -11,6 +11,7 @@ #include #include #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) + /** * Create a public/private key pair with Curve25519. * @@ -24,6 +25,30 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey) memcpy(pubKey, public_key, sizeof(public_key)); memcpy(privKey, private_key, sizeof(private_key)); } + +/** + * regenerate a public key with Curve25519. + * + * @param pubKey The destination for the public key. + * @param privKey The source for the private key. + */ +bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey) +{ + if (!memfll(privKey, 0, sizeof(private_key))) { + Curve25519::eval(pubKey, privKey, 0); + if (Curve25519::isWeakPoint(pubKey)) { + LOG_ERROR("PKI key generation failed. Specified private key results in a weak\n"); + memset(pubKey, 0, 32); + return false; + } + memcpy(private_key, privKey, sizeof(private_key)); + memcpy(public_key, pubKey, sizeof(public_key)); + } else { + LOG_WARN("X25519 key generation failed due to blank private key\n"); + return false; + } + return true; +} #endif void CryptoEngine::clearKeys() { diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 64382f6a7..4c2fc19d9 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -21,6 +21,7 @@ struct CryptoKey { */ #define MAX_BLOCKSIZE 256 +#define TEST_CURVE25519_FIELD_OPS // Exposes Curve25519::isWeakPoint() for testing keys class CryptoEngine { @@ -33,6 +34,8 @@ class CryptoEngine #if !(MESHTASTIC_EXCLUDE_PKI) #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey); + virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey); + #endif void clearKeys(); void setDHPrivateKey(uint8_t *_private_key); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index bf8596423..ba7671dc5 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -139,14 +139,24 @@ NodeDB::NodeDB() crypto->setDHPrivateKey(config.security.private_key.bytes); } else { #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) - LOG_INFO("Generating new PKI keys\n"); - crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); - config.security.public_key.size = 32; - config.security.private_key.size = 32; - - printBytes("New Pubkey", config.security.public_key.bytes, 32); - owner.public_key.size = 32; - memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + bool keygenSuccess = false; + if (config.security.private_key.size == 32) { + LOG_INFO("Calculating PKI Public Key\n"); + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + keygenSuccess = true; + } + } else { + LOG_INFO("Generating new PKI keys\n"); + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; + } + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + printBytes("New Pubkey", config.security.public_key.bytes, 32); + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + } #else LOG_INFO("No PKI keys set, and generation disabled!\n"); #endif diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 4c8c1e1e7..1f2c01473 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -112,7 +112,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 && (nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) { - // This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY + LOG_INFO("This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY\n"); sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start, p->hop_limit); } else { diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index b63eca396..c9b875bd1 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -537,6 +537,15 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_security_tag: LOG_INFO("Setting config: Security\n"); config.security = c.payload_variant.security; +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) && !(MESHTASTIC_EXCLUDE_PKI) + // We check for a potentially valid private key, and a blank public key, and regen the public key if needed. + if (config.security.private_key.size == 32 && !memfll(config.security.private_key.bytes, 0, 32) && + (config.security.public_key.size == 0 || memfll(config.security.public_key.bytes, 0, 32))) { + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + config.security.public_key.size = 32; + } + } +#endif owner.public_key.size = config.security.public_key.size; memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); #if !MESHTASTIC_EXCLUDE_PKI diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2f7e82e3d..797fc7dc3 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -167,9 +167,9 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) strcmp(e.channel_id, "PKI") == 0) { meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); - // Only accept PKI messages if we have both the sender and receiver in our nodeDB, as then it's likely - // they discovered each other via a channel we have downlink enabled for - if (tx && tx->has_user && rx && rx->has_user) + // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's + // likely they discovered each other via a channel we have downlink enabled for + if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) router->enqueueReceivedMessage(p); } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key router->enqueueReceivedMessage(p); @@ -527,7 +527,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & } if (ch.settings.uplink_enabled || mp.pki_encrypted) { - const char *channelId = channels.getGlobalId(chIndex); // FIXME, for now we just use the human name for the channel + const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); env->channel_id = (char *)channelId; @@ -546,12 +546,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets static uint8_t bytes[meshtastic_MeshPacket_size + 64]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic; - if (mp.pki_encrypted) { - topic = cryptTopic + "PKI/" + owner.id; - } else { - topic = cryptTopic + channelId + "/" + owner.id; - } + std::string topic = cryptTopic + channelId + "/" + owner.id; LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); publish(topic.c_str(), bytes, numBytes, false); @@ -561,12 +556,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); if (jsonString.length() != 0) { - std::string topicJson; - if (mp.pki_encrypted) { - topicJson = jsonTopic + "PKI/" + owner.id; - } else { - topicJson = jsonTopic + channelId + "/" + owner.id; - } + std::string topicJson = jsonTopic + channelId + "/" + owner.id; LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); From 79925406d690464b69821e7c582a99b0ce2a6ca0 Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Fri, 30 Aug 2024 03:18:43 +0100 Subject: [PATCH 1014/1377] Update variant.h --- variants/heltec_wireless_tracker_V1_0/variant.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h index 23987adf0..7549d6964 100644 --- a/variants/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -43,8 +43,7 @@ #define PIN_GPS_RESET 35 #define PIN_GPS_PPS 36 -#define VGNSS_CTRL_V03 37 // Heltec Tracker needs this pulled low for GPS -#define PIN_GPS_EN VGNSS_CTRL_V03 +#define PIN_GPS_EN 37 // Heltec Tracker needs this pulled low for GPS #define GPS_EN_ACTIVE LOW #define GPS_RESET_MODE LOW From dd933e6babd1d8b5f4f12c8dd9520493e79eaca1 Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:51:46 +0100 Subject: [PATCH 1015/1377] Add bluetooth capability marker to some ESP32S3 boards (#4587) * Update ESP32-S3-WROOM-1-N4.json * Update CDEBYTE_EoRa-S3.json * Update tlora-t3s3-v1.json --- boards/CDEBYTE_EoRa-S3.json | 2 +- boards/ESP32-S3-WROOM-1-N4.json | 2 +- boards/tlora-t3s3-v1.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boards/CDEBYTE_EoRa-S3.json b/boards/CDEBYTE_EoRa-S3.json index 9ecee3c9f..afaabc5a7 100644 --- a/boards/CDEBYTE_EoRa-S3.json +++ b/boards/CDEBYTE_EoRa-S3.json @@ -19,7 +19,7 @@ "mcu": "esp32s3", "variant": "CDEBYTE_EoRa-S3" }, - "connectivity": ["wifi"], + "connectivity": ["wifi", "bluetooth"], "debug": { "openocd_target": "esp32s3.cfg" }, diff --git a/boards/ESP32-S3-WROOM-1-N4.json b/boards/ESP32-S3-WROOM-1-N4.json index 3620a711d..160926b21 100644 --- a/boards/ESP32-S3-WROOM-1-N4.json +++ b/boards/ESP32-S3-WROOM-1-N4.json @@ -19,7 +19,7 @@ "mcu": "esp32s3", "variant": "ESP32-S3-WROOM-1-N4" }, - "connectivity": ["wifi"], + "connectivity": ["wifi", "bluetooth"], "debug": { "default_tool": "esp-builtin", "onboard_tools": ["esp-builtin"], diff --git a/boards/tlora-t3s3-v1.json b/boards/tlora-t3s3-v1.json index c5a68981b..0bfd17afc 100644 --- a/boards/tlora-t3s3-v1.json +++ b/boards/tlora-t3s3-v1.json @@ -19,7 +19,7 @@ "mcu": "esp32s3", "variant": "tlora-t3s3-v1" }, - "connectivity": ["wifi"], + "connectivity": ["wifi", "bluetooth"], "debug": { "openocd_target": "esp32s3.cfg" }, From 6a24566efb0789789ea46159ed6825957abfc7a7 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:53:06 +0200 Subject: [PATCH 1016/1377] Lilygo T3S3 E-Paper support (#4569) * t3s3 e-paper support * remove GPS autodetect (which leads to crashes during startup when no GPS present) * update EINK defines * keep definitions for external GPS connector but disable GPS auto scan by default --- src/graphics/EInkDisplay2.cpp | 2 +- src/graphics/EInkDisplay2.h | 2 +- src/mesh/NodeDB.cpp | 2 +- src/platform/esp32/architecture.h | 2 + variants/tlora_t3s3_epaper/pins_arduino.h | 26 +++++++++ variants/tlora_t3s3_epaper/platformio.ini | 23 ++++++++ variants/tlora_t3s3_epaper/variant.h | 69 +++++++++++++++++++++++ 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 variants/tlora_t3s3_epaper/pins_arduino.h create mode 100644 variants/tlora_t3s3_epaper/platformio.ini create mode 100644 variants/tlora_t3s3_epaper/variant.h diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 4b845bd51..c4cb10f82 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -157,7 +157,7 @@ bool EInkDisplay::connect() } #elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ - defined(HELTEC_VISION_MASTER_E290) + defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) { // Start HSPI hspi = new SPIClass(HSPI); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 26091b2cd..af631150e 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -68,7 +68,7 @@ class EInkDisplay : public OLEDDisplay // If display uses HSPI #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ - defined(HELTEC_VISION_MASTER_E290) + defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) SPIClass *hspi = NULL; #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ba7671dc5..fa3667f32 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -317,7 +317,7 @@ void NodeDB::installDefaultConfig() #else config.device.disable_triple_click = true; #endif -#if !HAS_GPS || defined(T_DECK) +#if !HAS_GPS || defined(T_DECK) || defined(TLORA_T3S3_EPAPER) config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT; #elif !defined(GPS_RX_PIN) if (config.position.rx_gpio == 0) diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 3761235a0..f86b342ce 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -122,6 +122,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER #elif defined(TLORA_T3S3_V1) #define HW_VENDOR meshtastic_HardwareModel_TLORA_T3_S3 +#elif defined(TLORA_T3S3_EPAPER) +#define HW_VENDOR meshtastic_HardwareModel_TLORA_T3_S3 #elif defined(CDEBYTE_EORA_S3) #define HW_VENDOR meshtastic_HardwareModel_CDEBYTE_EORA_S3 #elif defined(BETAFPV_2400_TX) diff --git a/variants/tlora_t3s3_epaper/pins_arduino.h b/variants/tlora_t3s3_epaper/pins_arduino.h new file mode 100644 index 000000000..ca44959c8 --- /dev/null +++ b/variants/tlora_t3s3_epaper/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 18; +static const uint8_t SCL = 12; // t3s3 e-Paper has no pin 17 as t3s3 v1, so use another free pin next to it + +// Default SPI will be mapped to Radio +static const uint8_t SS = 7; +static const uint8_t MOSI = 6; +static const uint8_t MISO = 3; +static const uint8_t SCK = 5; + +#define SPI_MOSI (11) +#define SPI_SCK (14) +#define SPI_MISO (2) +#define SPI_CS (13) + +#define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ diff --git a/variants/tlora_t3s3_epaper/platformio.ini b/variants/tlora_t3s3_epaper/platformio.ini new file mode 100644 index 000000000..ceb4fbaf5 --- /dev/null +++ b/variants/tlora_t3s3_epaper/platformio.ini @@ -0,0 +1,23 @@ +[env:tlora-t3s3-epaper] +extends = esp32s3_base +board = tlora-t3s3-v1 +board_check = true +upload_protocol = esptool + +build_flags = + ${esp32_base.build_flags} -D TLORA_T3S3_EPAPER -I variants/tlora_t3s3_epaper + -DGPS_POWER_TOGGLE + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -DEINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear + ;-DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + ;-DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d diff --git a/variants/tlora_t3s3_epaper/variant.h b/variants/tlora_t3s3_epaper/variant.h new file mode 100644 index 000000000..461ce0c31 --- /dev/null +++ b/variants/tlora_t3s3_epaper/variant.h @@ -0,0 +1,69 @@ +#define HAS_SDCARD +#define SDCARD_USE_SPI1 + +// Display (E-Ink) +#define USE_EINK +#define PIN_EINK_CS 15 +#define PIN_EINK_BUSY 48 +#define PIN_EINK_DC 16 +#define PIN_EINK_RES 47 +#define PIN_EINK_SCLK 14 +#define PIN_EINK_MOSI 11 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to +// measure battery voltage ratio of voltage divider = 2.0 (assumption) +#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL + +#define I2C_SDA SDA +#define I2C_SCL SCL + +// external qwiic connector +#define GPS_RX_PIN 44 +#define GPS_TX_PIN 43 + +#define LED_PIN 37 +#define BUTTON_PIN 0 +#define BUTTON_NEED_PULLUP + +// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and +// we will probe at runtime for RF95 and if not found then probe for SX1262 +#define USE_RF95 // RFM95/SX127x +#define USE_SX1262 +#define USE_SX1280 + +#define LORA_SCK 5 +#define LORA_MISO 3 +#define LORA_MOSI 6 +#define LORA_CS 7 +#define LORA_RESET 8 + +// per SX1276_Receive_Interrupt/utilities.h +#define LORA_DIO0 9 +#define LORA_DIO1 33 // TCXO_EN ? +#define LORA_DIO2 34 +#define LORA_RXEN 21 +#define LORA_TXEN 10 + +// per SX1262_Receive_Interrupt/utilities.h +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 33 +#define SX126X_BUSY 34 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif + +// per SX128x_Receive_Interrupt/utilities.h +#ifdef USE_SX1280 +#define SX128X_CS LORA_CS +#define SX128X_DIO1 9 +#define SX128X_DIO2 33 +#define SX128X_DIO3 34 +#define SX128X_BUSY 36 +#define SX128X_RESET LORA_RESET +#define SX128X_RXEN 21 +#define SX128X_TXEN 10 +#define SX128X_MAX_POWER 3 +#endif From 2b0113ae82f2dc5cde82e5c00921d41d10ac141d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Aug 2024 06:02:48 -0500 Subject: [PATCH 1017/1377] Consider an admin timestamp to be higher quality than from net (#4589) --- src/modules/AdminModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index c9b875bd1..bfe3a9ba5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -288,7 +288,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta tv.tv_sec = r->set_time_only; tv.tv_usec = 0; - perhapsSetRTC(RTCQualityFromNet, &tv, false); + perhapsSetRTC(RTCQualityNTP, &tv, false); break; } case meshtastic_AdminMessage_enter_dfu_mode_request_tag: { @@ -1028,4 +1028,4 @@ bool AdminModule::messageIsRequest(meshtastic_AdminMessage *r) return true; else return false; -} \ No newline at end of file +} From 8144dcbc2537a0ec1fd1851dedf6166d9815292f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:46:51 -0500 Subject: [PATCH 1018/1377] [create-pull-request] automated change (#4591) Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 5 +++++ src/mesh/generated/meshtastic/telemetry.pb.h | 8 +++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 431291e67..28492e88e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 431291e673c1c7592ee64cb972d7845589f60138 +Subproject commit 28492e88e515aabf5c886dd23631518d6dee82d7 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index d612d74be..a711da806 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -196,6 +196,9 @@ typedef enum _meshtastic_HardwareModel { https://www.adafruit.com/product/938 ^^^ short A0 to switch to I2C address 0x3C */ meshtastic_HardwareModel_RP2040_FEATHER_RFM95 = 76, + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */ + meshtastic_HardwareModel_M5STACK_COREBASIC = 77, + meshtastic_HardwareModel_M5STACK_CORE2 = 78, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -347,6 +350,8 @@ typedef enum _meshtastic_MeshPacket_Priority { /* If priority is unset but the message is marked as want_ack, assume it is important and use a slightly higher priority */ meshtastic_MeshPacket_Priority_RELIABLE = 70, + /* Higher priority for specific message types (portnums) to distinguish between other reliable packets. */ + meshtastic_MeshPacket_Priority_HIGH = 100, /* Ack/naks are sent with very high priority to ensure that retransmission stops as soon as possible */ meshtastic_MeshPacket_Priority_ACK = 120, diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 2d3eb407a..cedc2867e 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -69,7 +69,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* ICM-20948 9-Axis digital motion processor */ meshtastic_TelemetrySensorType_ICM20948 = 27, /* MAX17048 1S lipo battery sensor (voltage, state of charge, time to go) */ - meshtastic_TelemetrySensorType_MAX17048 = 28 + meshtastic_TelemetrySensorType_MAX17048 = 28, + /* Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor */ + meshtastic_TelemetrySensorType_CUSTOM_SENSOR = 29 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -265,8 +267,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_MAX17048 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_MAX17048+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_CUSTOM_SENSOR +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_CUSTOM_SENSOR+1)) From eb071ec80d9cbd8cfc339ddfadd730076781da66 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:54:44 +0200 Subject: [PATCH 1019/1377] Set high priority for text messages (#4592) --- src/mesh/MeshPacketQueue.cpp | 15 +++++++++------ src/mesh/MeshTypes.h | 5 ++++- src/mesh/Router.cpp | 2 ++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index f1c6c4ff3..8e5eedc87 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -40,19 +40,22 @@ void fixPriority(meshtastic_MeshPacket *p) // We might receive acks from other nodes (and since generated remotely, they won't have priority assigned. Check for that // and fix it if (p->priority == meshtastic_MeshPacket_Priority_UNSET) { - // if acks give high priority // if a reliable message give a bit higher default priority - p->priority = (p->decoded.portnum == meshtastic_PortNum_ROUTING_APP) - ? meshtastic_MeshPacket_Priority_ACK - : (p->want_ack ? meshtastic_MeshPacket_Priority_RELIABLE : meshtastic_MeshPacket_Priority_DEFAULT); + p->priority = (p->want_ack ? meshtastic_MeshPacket_Priority_RELIABLE : meshtastic_MeshPacket_Priority_DEFAULT); + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + // if acks/naks give very high priority + if (p->decoded.portnum == meshtastic_PortNum_ROUTING_APP) + p->priority = meshtastic_MeshPacket_Priority_ACK; + // if text give high priority + else if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) + p->priority = meshtastic_MeshPacket_Priority_HIGH; + } } } /** enqueue a packet, return false if full */ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p) { - fixPriority(p); - // no space - try to replace a lower priority packet in the queue if (queue.size() >= maxLen) { return replaceLowerPriorityPacket(p); diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 1c9099c39..90cfd5897 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -48,4 +48,7 @@ extern Allocator &packetPool; * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on * the local node. If from is zero this function returns our node number instead */ -NodeNum getFrom(const meshtastic_MeshPacket *p); \ No newline at end of file +NodeNum getFrom(const meshtastic_MeshPacket *p); + +/* Some clients might not properly set priority, therefore we fix it here. */ +void fixPriority(meshtastic_MeshPacket *p); \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 61b1bbfb6..804761f4e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -252,6 +252,8 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) return meshtastic_Routing_Error_BAD_REQUEST; } + fixPriority(p); // Before encryption, fix the priority if it's unset + // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it From 7475cc301e0f7270002b5ed6baea97f24bfc69ac Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 30 Aug 2024 15:37:39 -0500 Subject: [PATCH 1020/1377] GPS_POWER_TOGGLE on T114 --- variants/heltec_mesh_node_t114/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini index c2a458f78..1009ecce5 100644 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -7,6 +7,7 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_node_t114 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node_t114> lib_deps = From 33eb073535c2bece2353b9fd76d0dda5d120ac05 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 30 Aug 2024 19:02:48 -0500 Subject: [PATCH 1021/1377] Ignore (from)Net time on positions with an unknown or fixed location source (#4593) * Ignore (from)Net time on positions with an unknown or fixed location source * Dunk a trunk --- src/modules/PositionModule.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 2a0c95a9b..7c08835bc 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -129,6 +129,10 @@ void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUp LOG_DEBUG("Ignoring time from mesh because we have a GPS, RTC, or Phone/NTP time source in the past day\n"); return; } + if (!isLocal && p.location_source < meshtastic_Position_LocSource_LOC_INTERNAL) { + LOG_DEBUG("Ignoring time from mesh because it has a unknown or manual source\n"); + return; + } struct timeval tv; uint32_t secs = p.time; @@ -191,6 +195,10 @@ meshtastic_MeshPacket *PositionModule::allocReply() p.has_longitude_i = true; p.time = getValidTime(RTCQualityNTP) > 0 ? getValidTime(RTCQualityNTP) : localPosition.time; + if (config.position.fixed_position) { + p.location_source = meshtastic_Position_LocSource_LOC_MANUAL; + } + if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE) { if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE_MSL) { p.altitude = localPosition.altitude; From 644e213b13d0066b8c8b034188d0cfdc099252eb Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 31 Aug 2024 18:15:33 +1000 Subject: [PATCH 1022/1377] Added a singleton wrapper for bmp3xx --- .../Telemetry/EnvironmentTelemetry.cpp | 2 +- src/modules/Telemetry/Sensor/BMP3XXSensor.cpp | 72 ++++++++++++++----- src/modules/Telemetry/Sensor/BMP3XXSensor.h | 50 +++++++++---- 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 89e3b9d5f..31cb2f838 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -55,7 +55,7 @@ AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; NAU7802Sensor nau7802Sensor; -extern BMP3XXSensor bmp3xxSensor; +BMP3XXSensor bmp3xxSensor; #ifdef T1000X_SENSOR_EN T1000xSensor t1000xSensor; #endif diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp index 6f0f9fc10..feb08d8ff 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp @@ -2,17 +2,11 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "../mesh/generated/meshtastic/telemetry.pb.h" #include "BMP3XXSensor.h" -#include "TelemetrySensor.h" -#include -#include BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX"){} -BMP3XXSensor bmp3xxSensor; - -void BMP3XXSensor::setup(){}; +void BMP3XXSensor::setup() {} int32_t BMP3XXSensor::runOnce() { @@ -22,30 +16,44 @@ int32_t BMP3XXSensor::runOnce() return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = bmp3xx.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + // Get a singleton instance and initialise the bmp3xx + if (bmp3xx == nullptr) { + bmp3xx = BMP3XXSingleton::GetInstance(); + } + status = bmp3xx->begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); // set up oversampling and filter initialization - bmp3xx.setTemperatureOversampling(BMP3_OVERSAMPLING_4X); - bmp3xx.setPressureOversampling(BMP3_OVERSAMPLING_8X); - bmp3xx.setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3); - bmp3xx.setOutputDataRate(BMP3_ODR_25_HZ); + bmp3xx->setTemperatureOversampling(BMP3_OVERSAMPLING_4X); + bmp3xx->setPressureOversampling(BMP3_OVERSAMPLING_8X); + bmp3xx->setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3); + bmp3xx->setOutputDataRate(BMP3_ODR_25_HZ); // take a couple of initial readings to settle the sensor filters for (int i = 0; i < 3; i++) { - bmp3xx.performReading(); + bmp3xx->performReading(); } return initI2CSensor(); } bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) { + if (bmp3xx == nullptr) { + bmp3xx = BMP3XXSingleton::GetInstance(); + } if ((int)measurement->which_variant == meshtastic_Telemetry_environment_metrics_tag) { - bmp3xx.performReading(); - measurement->variant.environment_metrics.temperature = bmp3xx.readTemperature(); - measurement->variant.environment_metrics.barometric_pressure = bmp3xx.readPressure() / 100.0F; - LOG_DEBUG("BMP3XXSensor::getMetrics id: %i temp: %.1f press %.1f\n", measurement->which_variant, measurement->variant.environment_metrics.temperature, measurement->variant.environment_metrics.barometric_pressure); + bmp3xx->performReading(); + + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + measurement->variant.environment_metrics.has_relative_humidity = false; + + measurement->variant.environment_metrics.temperature = static_cast(bmp3xx->temperature); + measurement->variant.environment_metrics.barometric_pressure = static_cast(bmp3xx->pressure) / 100.0F; + measurement->variant.environment_metrics.relative_humidity = 0.0f; + + LOG_DEBUG("BMP3XXSensor::getMetrics id: %i temp: %.1f press %.1f\n", measurement->which_variant, measurement->variant.environment_metrics.temperature, measurement->variant.environment_metrics.barometric_pressure); } else { @@ -54,9 +62,35 @@ bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) return true; } -float BMP3XXSensor::getAltitudeAMSL() +// Get a singleton wrapper for an Adafruit_bmp3xx +BMP3XXSingleton *BMP3XXSingleton::GetInstance() { - return bmp3xx.readAltitude(SEAL_LEVEL_HPA); + std::lock_guard lock(mutex_); + if (pinstance_ == nullptr) + { + pinstance_ = new BMP3XXSingleton(); + } + return pinstance_; +} + +BMP3XXSingleton::BMP3XXSingleton(){} + +BMP3XXSingleton::~BMP3XXSingleton(){} + +BMP3XXSingleton* BMP3XXSingleton::pinstance_{nullptr}; + +std::mutex BMP3XXSingleton::mutex_; + +bool BMP3XXSingleton::performReading() +{ + bool result = Adafruit_BMP3XX::performReading(); + if (result) { + double atmospheric = this->pressure / 100.0; + altitudeAmslMetres = 44330.0 * (1.0 - pow(atmospheric / SEAL_LEVEL_HPA, 0.1903)); + } else { + altitudeAmslMetres = 0.0; + } + return result; } #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h index 514ee1762..04e8f9783 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h @@ -7,28 +7,52 @@ #define SEAL_LEVEL_HPA 1013.2f -#include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" +#include +#include #include +#include "TelemetrySensor.h" + +// Singleton wrapper for the Adafruit_BMP3XX class +class BMP3XXSingleton : public Adafruit_BMP3XX +{ +private: + static BMP3XXSingleton * pinstance_; + static std::mutex mutex_; + +protected: + BMP3XXSingleton(); + ~BMP3XXSingleton(); + +public: + // Create a singleton instance (with lock for thread safety) + static BMP3XXSingleton *GetInstance(); + + // Singletons should not be cloneable. + BMP3XXSingleton(BMP3XXSingleton &other) = delete; + + // Singletons should not be assignable. + void operator=(const BMP3XXSingleton &) = delete; + + // Performs a full reading of all sensors in the BMP3XX. Assigns + // the internal temperature, pressure and altitudeAmsl variables + bool performReading(); + + // Altitude in metres above mean sea level, assigned after calling performReading() + double altitudeAmslMetres = 0.0f; +}; class BMP3XXSensor : public TelemetrySensor { protected: - Adafruit_BMP3XX bmp3xx; - float pressureHPa = 0.0f; - float temperatureCelcius = 0.0f; - float altitudeAmslMetres = 0.0f; + BMP3XXSingleton *bmp3xx = nullptr; + virtual void setup() override; public: - BMP3XXSensor(); - virtual void setup() override; - virtual int32_t runOnce() override; - virtual bool getMetrics(meshtastic_Telemetry *measurement) override; - virtual float getAltitudeAMSL(); + BMP3XXSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; -extern BMP3XXSensor bmp3xxSensor; - #endif #endif \ No newline at end of file From eb1f80f520bcc25ac75dad674cd74a97e176963e Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:40:54 +1000 Subject: [PATCH 1023/1377] Fix build issue with mutex --- src/modules/Telemetry/Sensor/BMP3XXSensor.cpp | 11 ++++------- src/modules/Telemetry/Sensor/BMP3XXSensor.h | 6 ++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp index feb08d8ff..d948cfe38 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp @@ -65,21 +65,18 @@ bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) // Get a singleton wrapper for an Adafruit_bmp3xx BMP3XXSingleton *BMP3XXSingleton::GetInstance() { - std::lock_guard lock(mutex_); - if (pinstance_ == nullptr) + if (pinstance == nullptr) { - pinstance_ = new BMP3XXSingleton(); + pinstance = new BMP3XXSingleton(); } - return pinstance_; + return pinstance; } BMP3XXSingleton::BMP3XXSingleton(){} BMP3XXSingleton::~BMP3XXSingleton(){} -BMP3XXSingleton* BMP3XXSingleton::pinstance_{nullptr}; - -std::mutex BMP3XXSingleton::mutex_; +BMP3XXSingleton* BMP3XXSingleton::pinstance{nullptr}; bool BMP3XXSingleton::performReading() { diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h index 04e8f9783..77b434caa 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h @@ -7,7 +7,6 @@ #define SEAL_LEVEL_HPA 1013.2f -#include #include #include #include "TelemetrySensor.h" @@ -16,15 +15,14 @@ class BMP3XXSingleton : public Adafruit_BMP3XX { private: - static BMP3XXSingleton * pinstance_; - static std::mutex mutex_; + static BMP3XXSingleton * pinstance; protected: BMP3XXSingleton(); ~BMP3XXSingleton(); public: - // Create a singleton instance (with lock for thread safety) + // Create a singleton instance (not thread safe) static BMP3XXSingleton *GetInstance(); // Singletons should not be cloneable. From b71e12c5e51bc6f57ffa23c35f2310605b7c78c1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 31 Aug 2024 15:04:35 -0500 Subject: [PATCH 1024/1377] Phone admin work (#4599) * Don't throw an error on the sessionkey admin tag * Throw an error on packet sent to 0 --- src/mesh/PhoneAPI.cpp | 3 +++ src/mesh/Router.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 0a9bb5b10..0ca89b1ef 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -259,6 +259,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.config.which_payload_variant = meshtastic_Config_security_tag; fromRadioScratch.config.payload_variant.security = config.security; break; + case meshtastic_Config_sessionkey_tag: + fromRadioScratch.config.which_payload_variant = meshtastic_Config_sessionkey_tag; + break; default: LOG_ERROR("Unknown config type %d\n", config_state); } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 804761f4e..d8e578db1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -165,6 +165,9 @@ meshtastic_QueueStatus Router::getQueueStatus() ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) { + if (p->to == 0) { + LOG_ERROR("Packet received with to: of 0!\n"); + } // No need to deliver externally if the destination is the local node if (p->to == nodeDB->getNodeNum()) { printPacket("Enqueued local", p); From e45a358de07ce438556dc23127e315c9a0bcd7bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 09:20:10 -0500 Subject: [PATCH 1025/1377] [create-pull-request] automated change (#4594) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 28492e88e..5f7c91adb 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 28492e88e515aabf5c886dd23631518d6dee82d7 +Subproject commit 5f7c91adb97187e0cb2140de7057344d93444bd1 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index a711da806..9d7ff74a1 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -350,6 +350,9 @@ typedef enum _meshtastic_MeshPacket_Priority { /* If priority is unset but the message is marked as want_ack, assume it is important and use a slightly higher priority */ meshtastic_MeshPacket_Priority_RELIABLE = 70, + /* If priority is unset but the packet is a response to a request, we want it to get there relatively quickly. + Furthermore, responses stop relaying packets directed to a node early. */ + meshtastic_MeshPacket_Priority_RESPONSE = 80, /* Higher priority for specific message types (portnums) to distinguish between other reliable packets. */ meshtastic_MeshPacket_Priority_HIGH = 100, /* Ack/naks are sent with very high priority to ensure that retransmission From 56223710b502727d7a8da18c551dbbc3859c9c12 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:29:53 +0200 Subject: [PATCH 1026/1377] More priorities for different types of MeshPackets (#4606) --- src/mesh/MeshPacketQueue.cpp | 14 +++++++++++--- src/modules/NeighborInfoModule.cpp | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index 8e5eedc87..6581b1ce4 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -44,11 +44,19 @@ void fixPriority(meshtastic_MeshPacket *p) p->priority = (p->want_ack ? meshtastic_MeshPacket_Priority_RELIABLE : meshtastic_MeshPacket_Priority_DEFAULT); if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { // if acks/naks give very high priority - if (p->decoded.portnum == meshtastic_PortNum_ROUTING_APP) + if (p->decoded.portnum == meshtastic_PortNum_ROUTING_APP) { p->priority = meshtastic_MeshPacket_Priority_ACK; - // if text give high priority - else if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) + // if text or admin, give high priority + } else if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP || + p->decoded.portnum == meshtastic_PortNum_ADMIN_APP) { p->priority = meshtastic_MeshPacket_Priority_HIGH; + // if it is a response, give higher priority to let it arrive early and stop the request being relayed + } else if (p->decoded.request_id != 0) { + p->priority = meshtastic_MeshPacket_Priority_RESPONSE; + // Also if we want a response, give a bit higher priority + } else if (p->decoded.want_response) { + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + } } } } diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 2de4374e6..218fb8801 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -107,6 +107,7 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies) // because we want to get neighbors for the next cycle p->to = dest; p->decoded.want_response = wantReplies; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; printNeighborInfo("SENDING", &neighborInfo); service->sendToMesh(p, RX_SRC_LOCAL, true); } From 7d2f3a34253c0b74e0c542d56939b5e3188f234f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 1 Sep 2024 11:29:34 -0500 Subject: [PATCH 1027/1377] Hello world for MeshTestic (#4607) --- .gitmodules | 3 +++ meshtestic | 1 + 2 files changed, 4 insertions(+) create mode 160000 meshtestic diff --git a/.gitmodules b/.gitmodules index e6f376a0b..7c54ad513 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "protobufs"] path = protobufs url = https://github.com/meshtastic/protobufs.git +[submodule "meshtestic"] + path = meshtestic + url = https://github.com/meshtastic/meshTestic diff --git a/meshtestic b/meshtestic new file mode 160000 index 000000000..31ee3d90c --- /dev/null +++ b/meshtestic @@ -0,0 +1 @@ +Subproject commit 31ee3d90c8bef61e835c3271be2c7cda8c4a5cc2 From 24501c30c0b64081fe8e7eb36ed4ef236942125e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 1 Sep 2024 11:33:41 -0500 Subject: [PATCH 1028/1377] Update and rename test_simulator.yml to tests.yml --- .../{test_simulator.yml => tests.yml} | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) rename .github/workflows/{test_simulator.yml => tests.yml} (64%) diff --git a/.github/workflows/test_simulator.yml b/.github/workflows/tests.yml similarity index 64% rename from .github/workflows/test_simulator.yml rename to .github/workflows/tests.yml index 1d20657a0..f58b38ac9 100644 --- a/.github/workflows/test_simulator.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: Test Simulator +name: End to end tests on: schedule: @@ -55,3 +55,37 @@ jobs: name: PlatformIO Tests path: testreport.xml reporter: java-junit + + hardware-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Upgrade python tools + shell: bash + run: | + python -m pip install --upgrade pip + pip install -U --no-build-isolation --no-cache-dir "setuptools<72" + pip install -U platformio adafruit-nrfutil --no-build-isolation + pip install -U poetry --no-build-isolation + pip install -U meshtastic --pre --no-build-isolation + + - name: Upgrade platformio + shell: bash + run: | + pio upgrade + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: latest + + - name: Install Dependencies + run: pnpm install + + - name: Setup devices + run: pnpm run setup + + - name: Execute end to end tests on connected hardware + run: pnpm run test From cd16b7b00a090ab2ef90dcafa408b96547b45ebe Mon Sep 17 00:00:00 2001 From: Riley Nielsen Date: Sun, 1 Sep 2024 18:26:08 -0700 Subject: [PATCH 1029/1377] fix display of degree symbol (+ other symbols) --- src/graphics/Screen.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 1b6f541be..bb5d947e3 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -309,7 +309,7 @@ class Screen : public concurrency::OSThread static char customFontTableLookup(const uint8_t ch) { // UTF-8 to font table index converter - // Code form http://playground.arduino.cc/Main/Utf8ascii + // Code from http://playground.arduino.cc/Main/Utf8ascii static uint8_t LASTCHAR; static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters @@ -322,13 +322,19 @@ class Screen : public concurrency::OSThread uint8_t last = LASTCHAR; // get last char LASTCHAR = ch; -#if defined(OLED_PL) - - switch (last) { // conversion depending on first UTF8-character + switch(last) { case 0xC2: { SKIPREST = false; return (uint8_t)ch; } + } + + // We want to strip out prefix chars for two-byte char formats + if (ch == 0xC2) + return (uint8_t)0; + +#if defined(OLED_PL) + case 0xC3: { if (ch == 147) @@ -365,11 +371,6 @@ class Screen : public concurrency::OSThread #if defined(OLED_UA) || defined(OLED_RU) - switch (last) { // conversion depending on first UTF8-character - case 0xC2: { - SKIPREST = false; - return (uint8_t)ch; - } case 0xC3: { SKIPREST = false; return (uint8_t)(ch | 0xC0); From 234e652a0753e56bdace676ebe28e44f9600cb27 Mon Sep 17 00:00:00 2001 From: Riley Nielsen Date: Sun, 1 Sep 2024 20:40:56 -0700 Subject: [PATCH 1030/1377] Fix switch --- src/graphics/Screen.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index bb5d947e3..d5a2879cd 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -335,6 +335,7 @@ class Screen : public concurrency::OSThread #if defined(OLED_PL) + switch(last) { case 0xC3: { if (ch == 147) @@ -371,6 +372,7 @@ class Screen : public concurrency::OSThread #if defined(OLED_UA) || defined(OLED_RU) + switch(last) { case 0xC3: { SKIPREST = false; return (uint8_t)(ch | 0xC0); From 719faf4f970eaddf9feb97ed36110a507a0ad805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 2 Sep 2024 10:16:32 +0200 Subject: [PATCH 1031/1377] trunk fmt --- src/graphics/Screen.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index d5a2879cd..021b11bda 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -322,7 +322,7 @@ class Screen : public concurrency::OSThread uint8_t last = LASTCHAR; // get last char LASTCHAR = ch; - switch(last) { + switch (last) { case 0xC2: { SKIPREST = false; return (uint8_t)ch; @@ -335,7 +335,7 @@ class Screen : public concurrency::OSThread #if defined(OLED_PL) - switch(last) { + switch (last) { case 0xC3: { if (ch == 147) @@ -372,7 +372,7 @@ class Screen : public concurrency::OSThread #if defined(OLED_UA) || defined(OLED_RU) - switch(last) { + switch (last) { case 0xC3: { SKIPREST = false; return (uint8_t)(ch | 0xC0); From e543b61dd8850e300c193167cb45bcb95da0856b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 2 Sep 2024 10:23:31 +0200 Subject: [PATCH 1032/1377] trunk fmt --- src/detect/ScanI2CTwoWire.cpp | 4 +-- src/modules/Telemetry/Sensor/BMP3XXSensor.cpp | 28 ++++++++----------- src/modules/Telemetry/Sensor/BMP3XXSensor.h | 16 +++++------ 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 76ca42b7f..21e7ca8ac 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -268,7 +268,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; default: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID - switch(registerValue) { + switch (registerValue) { case 0x50: // BMP-388 should be 0x50 LOG_INFO("BMP-388 sensor found at address 0x%x\n", (uint8_t)addr.address); type = BMP_3XX; @@ -279,7 +279,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = BMP_280; break; } - break; + break; } break; #ifndef HAS_NCP5623 diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp index d948cfe38..399610613 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp @@ -4,15 +4,14 @@ #include "BMP3XXSensor.h" -BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX"){} +BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX") {} void BMP3XXSensor::setup() {} int32_t BMP3XXSensor::runOnce() { LOG_INFO("Init sensor: %s\n", sensorName); - if (!hasSensor()) - { + if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } @@ -29,8 +28,7 @@ int32_t BMP3XXSensor::runOnce() bmp3xx->setOutputDataRate(BMP3_ODR_25_HZ); // take a couple of initial readings to settle the sensor filters - for (int i = 0; i < 3; i++) - { + for (int i = 0; i < 3; i++) { bmp3xx->performReading(); } return initI2CSensor(); @@ -41,8 +39,7 @@ bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) if (bmp3xx == nullptr) { bmp3xx = BMP3XXSingleton::GetInstance(); } - if ((int)measurement->which_variant == meshtastic_Telemetry_environment_metrics_tag) - { + if ((int)measurement->which_variant == meshtastic_Telemetry_environment_metrics_tag) { bmp3xx->performReading(); measurement->variant.environment_metrics.has_temperature = true; @@ -53,10 +50,10 @@ bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = static_cast(bmp3xx->pressure) / 100.0F; measurement->variant.environment_metrics.relative_humidity = 0.0f; - LOG_DEBUG("BMP3XXSensor::getMetrics id: %i temp: %.1f press %.1f\n", measurement->which_variant, measurement->variant.environment_metrics.temperature, measurement->variant.environment_metrics.barometric_pressure); - } - else - { + LOG_DEBUG("BMP3XXSensor::getMetrics id: %i temp: %.1f press %.1f\n", measurement->which_variant, + measurement->variant.environment_metrics.temperature, + measurement->variant.environment_metrics.barometric_pressure); + } else { LOG_DEBUG("BMP3XXSensor::getMetrics id: %i\n", measurement->which_variant); } return true; @@ -65,18 +62,17 @@ bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) // Get a singleton wrapper for an Adafruit_bmp3xx BMP3XXSingleton *BMP3XXSingleton::GetInstance() { - if (pinstance == nullptr) - { + if (pinstance == nullptr) { pinstance = new BMP3XXSingleton(); } return pinstance; } -BMP3XXSingleton::BMP3XXSingleton(){} +BMP3XXSingleton::BMP3XXSingleton() {} -BMP3XXSingleton::~BMP3XXSingleton(){} +BMP3XXSingleton::~BMP3XXSingleton() {} -BMP3XXSingleton* BMP3XXSingleton::pinstance{nullptr}; +BMP3XXSingleton *BMP3XXSingleton::pinstance{nullptr}; bool BMP3XXSingleton::performReading() { diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h index 77b434caa..79939c8d8 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h @@ -7,21 +7,21 @@ #define SEAL_LEVEL_HPA 1013.2f -#include -#include #include "TelemetrySensor.h" +#include +#include // Singleton wrapper for the Adafruit_BMP3XX class class BMP3XXSingleton : public Adafruit_BMP3XX { -private: - static BMP3XXSingleton * pinstance; + private: + static BMP3XXSingleton *pinstance; -protected: + protected: BMP3XXSingleton(); ~BMP3XXSingleton(); -public: + public: // Create a singleton instance (not thread safe) static BMP3XXSingleton *GetInstance(); @@ -41,11 +41,11 @@ public: class BMP3XXSensor : public TelemetrySensor { -protected: + protected: BMP3XXSingleton *bmp3xx = nullptr; virtual void setup() override; -public: + public: BMP3XXSensor(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; From 2f0c19ebeac0054cb8b8f417b9f2952d18532fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 2 Sep 2024 15:06:06 +0200 Subject: [PATCH 1033/1377] - use setRfSwitchTable - ditch Godmode - fixes Signedness Error in Loop. - add V3 factory erase for 7.3.0 softdevice --- ...astic_nRF52_factory_erase_v3_S140_7.3.0.uf2 | Bin 0 -> 122368 bytes src/mesh/LR11x0Interface.cpp | 14 +++++++------- src/meshUtils.cpp | 2 +- variants/tracker-t1000-e/platformio.ini | 2 +- variants/tracker-t1000-e/variant.h | 1 - 5 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 bin/Meshtastic_nRF52_factory_erase_v3_S140_7.3.0.uf2 diff --git a/bin/Meshtastic_nRF52_factory_erase_v3_S140_7.3.0.uf2 b/bin/Meshtastic_nRF52_factory_erase_v3_S140_7.3.0.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..73537a7358bf4e217e1218ceeb4ce7d317cdfc13 GIT binary patch literal 122368 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 v_xzx8xj)eEf~HJYRP+zn@z1U!k9wBmfPRg(; bool LR11x0Interface::init() // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option if (res == RADIOLIB_ERR_NONE) res = lora.setRegulatorDCDC(); -#ifdef TRACKER_T1000_E -#ifdef LR11X0_DIO_RF_SWITCH_CONFIG - res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG); -#else - res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0); -#endif -#endif +// #ifdef TRACKER_T1000_E +// #ifdef LR11X0_DIO_RF_SWITCH_CONFIG +// res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG); +// #else +// res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0); +// #endif +// #endif if (res == RADIOLIB_ERR_NONE) { if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate res = lora.setRxBoostedGainMode(true); diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 99fcd2a57..c6f2c69b4 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -68,7 +68,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes) bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes) { - for (int i = 0; i < numbytes; i++) { + for (uint8_t i = 0; i < numbytes; i++) { if (mem[i] != find) return false; } diff --git a/variants/tracker-t1000-e/platformio.ini b/variants/tracker-t1000-e/platformio.ini index 1db57ca29..dfc72f3f0 100644 --- a/variants/tracker-t1000-e/platformio.ini +++ b/variants/tracker-t1000-e/platformio.ini @@ -4,7 +4,7 @@ extends = nrf52840_base board = tracker-t1000-e ; board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E -DRADIOLIB_GODMODE +build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld diff --git a/variants/tracker-t1000-e/variant.h b/variants/tracker-t1000-e/variant.h index 63c2a76dc..470edd08c 100644 --- a/variants/tracker-t1000-e/variant.h +++ b/variants/tracker-t1000-e/variant.h @@ -102,7 +102,6 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 1.6 #define LR11X0_DIO_AS_RF_SWITCH -#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 #define HAS_GPS 1 #define GNSS_AIROHA From b526a3ad53b6584a702e56769be1ecf03210ba5a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 2 Sep 2024 17:51:02 -0500 Subject: [PATCH 1034/1377] Own node should be favorited and have zero hops away (#4618) --- src/mesh/PhoneAPI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 0ca89b1ef..30af9d7b0 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -194,6 +194,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) auto us = nodeDB->readNextMeshNode(readIndex); if (us) { nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us); + nodeInfoForPhone.hops_away = 0; + nodeInfoForPhone.is_favorite = true; fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; fromRadioScratch.node_info = nodeInfoForPhone; // Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS From cdea602181b283c87c55908999f7242054edc723 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 3 Sep 2024 09:08:02 +0800 Subject: [PATCH 1035/1377] Remove unused define (#4620) Neither RF95_DIO2 nor LORA_DIO2 are found anywhere in the code. --- src/RF95Configuration.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/RF95Configuration.h b/src/RF95Configuration.h index 66b2dcf86..5c525814b 100644 --- a/src/RF95Configuration.h +++ b/src/RF95Configuration.h @@ -3,5 +3,4 @@ #define RF95_RESET LORA_RESET #define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0 #define RF95_DIO1 LORA_DIO1 // Note: not really used for RF95, but used for pure SX127x -#define RF95_DIO2 LORA_DIO2 // Note: not really used for RF95 -#endif \ No newline at end of file +#endif From 3bb1cb8f1de3eb56e4dff01c0592651c50155064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 09:25:33 +0200 Subject: [PATCH 1036/1377] Update Erase tool for legacy softdevices to V3 --- bin/Meshtastic_nRF52_factory_erase_v2.uf2 | Bin 127488 -> 0 bytes ...astic_nRF52_factory_erase_v3_S140_6.1.0.uf2 | Bin 0 -> 126976 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bin/Meshtastic_nRF52_factory_erase_v2.uf2 create mode 100644 bin/Meshtastic_nRF52_factory_erase_v3_S140_6.1.0.uf2 diff --git a/bin/Meshtastic_nRF52_factory_erase_v2.uf2 b/bin/Meshtastic_nRF52_factory_erase_v2.uf2 deleted file mode 100644 index 8a83bc8ecff5b9cc00839b16f338deaf071f275b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127488 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

5H9oZFl=>o9U)+aRT>E-+rTFPk&cG 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
Q01$vUkfbqhs9=nV;fNT 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 diff --git a/bin/Meshtastic_nRF52_factory_erase_v3_S140_6.1.0.uf2 b/bin/Meshtastic_nRF52_factory_erase_v3_S140_6.1.0.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..e44d9da2a7934c1230927695ce899dc928faa452 GIT binary patch literal 126976 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 literal 0 HcmV?d00001 From 85986459310bf55a18dbbd999b5c6dfc4805ced8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 2 Sep 2024 15:06:06 +0200 Subject: [PATCH 1037/1377] - use setRfSwitchTable - ditch Godmode - fixes Signedness Error in Loop. - add V3 factory erase for 7.3.0 softdevice --- ...astic_nRF52_factory_erase_v3_S140_7.3.0.uf2 | Bin 0 -> 122368 bytes src/mesh/LR11x0Interface.cpp | 14 +++++++------- src/meshUtils.cpp | 2 +- variants/tracker-t1000-e/platformio.ini | 2 +- variants/tracker-t1000-e/variant.h | 1 - 5 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 bin/Meshtastic_nRF52_factory_erase_v3_S140_7.3.0.uf2 diff --git a/bin/Meshtastic_nRF52_factory_erase_v3_S140_7.3.0.uf2 b/bin/Meshtastic_nRF52_factory_erase_v3_S140_7.3.0.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..73537a7358bf4e217e1218ceeb4ce7d317cdfc13 GIT binary patch literal 122368 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 v_xzx8xj)eEf~HJYRP+zn@z1U!k9wBmfPRg(; bool LR11x0Interface::init() // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option if (res == RADIOLIB_ERR_NONE) res = lora.setRegulatorDCDC(); -#ifdef TRACKER_T1000_E -#ifdef LR11X0_DIO_RF_SWITCH_CONFIG - res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG); -#else - res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0); -#endif -#endif +// #ifdef TRACKER_T1000_E +// #ifdef LR11X0_DIO_RF_SWITCH_CONFIG +// res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG); +// #else +// res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0); +// #endif +// #endif if (res == RADIOLIB_ERR_NONE) { if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate res = lora.setRxBoostedGainMode(true); diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 99fcd2a57..c6f2c69b4 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -68,7 +68,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes) bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes) { - for (int i = 0; i < numbytes; i++) { + for (uint8_t i = 0; i < numbytes; i++) { if (mem[i] != find) return false; } diff --git a/variants/tracker-t1000-e/platformio.ini b/variants/tracker-t1000-e/platformio.ini index 1db57ca29..dfc72f3f0 100644 --- a/variants/tracker-t1000-e/platformio.ini +++ b/variants/tracker-t1000-e/platformio.ini @@ -4,7 +4,7 @@ extends = nrf52840_base board = tracker-t1000-e ; board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E -DRADIOLIB_GODMODE +build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld diff --git a/variants/tracker-t1000-e/variant.h b/variants/tracker-t1000-e/variant.h index 63c2a76dc..470edd08c 100644 --- a/variants/tracker-t1000-e/variant.h +++ b/variants/tracker-t1000-e/variant.h @@ -102,7 +102,6 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 1.6 #define LR11X0_DIO_AS_RF_SWITCH -#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 #define HAS_GPS 1 #define GNSS_AIROHA From 190c7ecdd4c0701233d9a649502542647ad91e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 09:25:33 +0200 Subject: [PATCH 1038/1377] Update Erase tool for legacy softdevices to V3 --- bin/Meshtastic_nRF52_factory_erase_v2.uf2 | Bin 127488 -> 0 bytes ...astic_nRF52_factory_erase_v3_S140_6.1.0.uf2 | Bin 0 -> 126976 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bin/Meshtastic_nRF52_factory_erase_v2.uf2 create mode 100644 bin/Meshtastic_nRF52_factory_erase_v3_S140_6.1.0.uf2 diff --git a/bin/Meshtastic_nRF52_factory_erase_v2.uf2 b/bin/Meshtastic_nRF52_factory_erase_v2.uf2 deleted file mode 100644 index 8a83bc8ecff5b9cc00839b16f338deaf071f275b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127488 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
5H9oZFl=>o9U)+aRT>E-+rTFPk&cG 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
Q01$vUkfbqhs9=nV;fNT zYi0cDq{o2&k5Ys*^tx$Zp6YD{>7o(Wvl0rR-{TVUOuM4tYa@44Z9jPWko@#4s_TzR z<`J~#SJ**JGz(vqZ9n{Q0UIrHI(KDx2CViRcx+6vUoMY>=Z3oT>n|ob!T@5&IT0z(K7yyApCul zniimj?`+(4XZq{TqOYAHLtjuya6-^oZ2q-!(Lu+GpxVNNj=7*?;y)%0G&VKuwdpLy0x~B$aJuCcKYzSD4=?F-e%yhfzS1*5RU&;o|si-}>-yzR95$>^xRq zAtuLjf)*>XRQw>96JrL7W@h3x%-}*>U79Jxp$@aJhIUsPR+x(A5xl%-c|C8YSEj}1 zDtXbZ#Jh1q1m>SUDE!qD=}HnJ^a?rx=`aX7K1m4G)AtCM=PK@T?0gEwt8Z~)RTJKEQ_d(shjk`5F#P@J z|HR7p?+n79qD*JYP>e?jly0t^cvmK@Es#1quVVkG^ee8c#L-+_c@g_R;0o-!z^88M4vgpb^?H(} zNZ1dIAu^xPF^##z5Dx3%uM+fCsn_#aZ=WZ+i?&pnd=2b?!0k8UzMuTZ$@uTWj}5f{ z0XryQ2P{NeECR2`D6jbKfk}1hyl}+#kUX{?Qa@>LI%Jo{4Bts+$+H0Sgp;2nx?aya zyEO$N`I{X&V;Y3d|Td6)9)*h0W|z#YDI z98{OnO=0D1+FkPu-1Kl?#F1F|eM<3;hp;Jq-vQ&rilA>v!`&|t-6m|LO#?f@JC3C6 zyTE0X+gMu`?neCvu88il1a@HIKS;*EE(m|^uGfW^ZLbSf8#BJhw!ueZO%dy(lWA9k zupn9Mq;yQ?=U^t?2bQiT)>n3e?PXh$O;cq0gqBs56bqN#T#0s<^8yp|fUwfG%yOqq z({Mk$@6=m|5xXf&QKC(r!#AtGpq@sXCuzwI6d!DuAy84jwM`TL*agM^BVwX-N>VhR zy1*K+vNJbJ!-@A~>`PpVeMScQ5vKTT(yG-GG0H?*fpaTt$H>xLs{auef`8c;@olMn zrDX`8+eGIDSsVx2bR@M4b4|>znavlO@O9?Gi$n4Z=$72LpMLT`SjPXOApAe+mVO=H z^-`avd+`3Oi%gcPJF;zqC8Vi4Ue)F`*~NtN4Hom|!R74lGPCC-zFWJqTS9a;ZASJ* zE}L{m_WeC4r0KQ<(&o*ui^}r9=a?_EU7U&zG2yJG=XO^x=`+$r;lwa?>>3;q9v|>ZWJn!|@*M;Z?{F zdcnSo|0=Pj*3sQ3Ms{nX@xIxbZX#{WQTyM4J-ji8TyC<*^Y6B56y}TJ<_7mF{2Cf> z-eD&r@fM5;trvZdon1?RMI`bY_|p&nAu|5|9fW^@Yn6pPQUU7jwl7O8YULP42l=^a zlM}R>{+=6h!|A%XUtcpbU93paYBHO$xNu-V`9_ND9!zln-)O)$iY~Up&pWr-#aHZ< zcUIbE-YEy~_;B;#OS(0Fe8D@nEe5`en%GH`v%g6M=kR6j&)an2T$idNr7OHWIZM|S z?qRxA?S&WP?$CFIw++wAq;JqU9JzfXyA$XeObhAObgj%`yHuA6N@|*l;zPhWd52f# z@J*ZB*bsg8+pVEY!&UCdy~yN}fi?dxUdDfS5dIozH>Af+C3xJ;hj-_U0Scoay&>lJ zTQ%y9-Rz3OoX`1uKJmy9=Yx)&t&!}bt&CdT72b2%JpCfedd%RHQDmenQ#Zr94|A*d z3oY?{Z%bCrxx>3#*~pSs%@huVD)NCC0QC4q{q&Rn1R4K&{NRB7M`@<1 zQ(F~$tb^z&jg(j|vvfY^Sf5L?^z7}FZR`o4XXGxfjzOj%W!@}FVI&y;CE|mf><+Z> ztsL5ET32aZ#EgjP^OD}xl97M6gt)?Hgiq(h*E<7Ol&;df_ehG&^|{O~|8fV8F1yXe zL>JiZ#xZLJ4~-HIA>uHTcrdTkLel))iz$BR)70??*JKcn4EO z^j^;;f3_t;ZmlDu^PG1|ircWJ-1v5zLb1EyY&rjHPqB@$U2-SF@(gp-!19b1?umcT66Q)3^x1dE*I1%$ zr(DS>JBB#_$ z48LhW;h!Yqzb6R)Ne$s6$W6$kaALEC1N|~=pwC`L{Kl)#4;AA0*XwnN^-r|yM*EJsd$W=aE?S<>xBl3B#%&PhBn>$lX5^X-w&d&)Hbrx>@Qx+CAKTr79eJD9qM{_P;-$MLuZ>%soS!+uu)$sI6X)et=6~)K#jqxA)G=#dD zdZMP_&Ap!E-mK(y7ji>Ue^KFp!arHY|C1p6BltIV#PRES736qnNmxDBe)X=fzF|pn zJtZvoW0P8Wbjyk=C3|$!V~n`GZ_A1#HPdJ;X|0#J;HTbwjs<@_sg7)isEj1({- z2EK}i<}rpkMx=MTK5kWng*oS7e2+fQ3y%<0SmS*BQwT^kJp3jd)p{$Ty9 z?LVairDM8rkW*cCH&18fg@)^SvJIN_$A|Nb?mci7gW3}?Z!t7n$7{hurYRxmToW;V=2WgZh>{FE z%8WsDb6ZGdIzQN|pkvK9@TXt4>0@0JyFR8z0SY!6u< z?Wb9YOf&J`?z7O%AHpX57Te3%CSkK+dt*39zDGOrI~?D1!JB)}C9Rd*6v-#>Mt)C& zvW;X7#W?>>hOp9z>QjgmxrR50I?X2yDbUut+xRG@b9Y-#6mc=PuzJ$W>A5XTB@4WA zP|qxWJlcj2dUvh8gV zOcRUK6>Z)LkrMO3IKJ(qY23CpR;iGfL@Y&+MvKc7*e%B%6IOKb%)WYc%vYC9-iQc;U-# zVhF7?2+B%2H)Nd@zeCm#T$_f)91-0SL?1YGAqI1VVpfb6NtU|uJ1O3-`Ik2IE$gWZ zDyC2zQ26U){PzXPzjr(xfBS`{15q?hxCBKBm;!1Dj=W&HOC;eQzR9Ft)vdP6c`<<09( z8ISdDRaI{Kr!F9bY<>0aJ#CLf#kn8Wvi;!J<525cEaI)E67A$!<3peH4)aSeAk8H>1yRzfyhFP-1~kHjUq% z%b3Qv=HY1%N0~1-+aa|KO zI>Sa^yzmU_WosT&R@StJaC34pPRiJ40{i3YNk>Dgl+Xf465nZMLN?@VYS_Tj96uB3 z-5UShq4bXGM0$T#&Jxcv+vPXAA%XY*sDAhxWc&{X;V(6sT)02mH5YC|_C#pDwQPZ$Bu|`NK32zv2@!V5n zM5X0WLv`ZM@nmG;ggQ$i{JNTEih1B}4jJj>*zL^3&)q-9-=A#Kl}lM9B^#MUv=vg8 zmd*GAh^Jh=qs7VG(gY1p`yt`{8h+G&g>NVuYlnw{zLome+_9kp%l|t<#{aWm{Kvvt zv(Lu=8oQ#+qFN?dN~bix-&V{GGX8pu**H@&h=0a?Yp_N9X8U*7-WbV|{V2Bt`#|{y z|1rHDxb_mRg$dL<5Q+@rzUP{d7r1!@R)OBEB5evTv{5O9*~m@Y@tX{fS*ZQ_hrCcj zLh~Te97Vs0Z@8)U7k8L2LWm!#v8Ab)#-Re+T%zi39J>7OyvN6eH)sr@27>6+S=vX0 zzaxh6JIFJq7Zr@&7Ew=bp5YwK*A1-kKhT`^|2_}GzrBs?j{f}@52_^Q&tv&LmI%J1 zNn;E{8-H4@X*84+!Jhu1!DPVqw_!%WEo+lfioQed%gS@G_06!rf9hjQ(|xij_Z%nY zA?ItUL5LKFtL~K)b0TeVjZN^iWr;e@U>jO*5YXetk=C#S_Md#U%#cR!GM#b%L6z6A zwYJ4QnBN0zM+&x*p+h5V6IEDk*cfkPn->A!c=-DaMT7Z|Q6h0@sBM@qS0H-ohu+w! zaoCe=wqn6hMDxdInKro%CF7wH%)ApCXbUt@wL11F@>KgRz#CZo_wWCYlJWl{2>*AX zF|X058aaE<2prnSEtbnCTiuw?*_NQX0()%^Y_o;fCe^8C5Q749KaI9N&x?Io$*jvL za1MiQ(%uPJ_sjXbw@oHHO}- zaLFZ?dY9Kv!W`95)Xum*6#n53!I7rnlJK=#E)kB^?B6)ihTMu(p;vuZ0}B5%8UHVX z@Tb1J@m54j5mad9M>w{W#MaXu{4d^{LBA-9(~4c4;kpF%n4_mu(MLlt;$sobTunJ` zjeplX!cXs3cdDP>E$-Rsn8u<7(~vtPo7-Svoz!!GMIwgc;+ho(F%|dX7%^WAz&}gW z;M`DRt*=?Zb?6rFyh4_6PCDAeh)+u!eb3e+E(x+i7;0?AC37q_MTiztuNCd&V~i!2 zt7kV9gOfVkMCSpnbPW1K{HAZQ(ch-2Q|TCSWDnvCir;tMbiq@|m4svbNX_rNTyAG> z8&LR0l!6n7?~%ex$n#pQ$b7?+$lrYAwjvt8>2qYBPd&lhk_JCM z37?H;!PnPZS^J^yDY0FDX+O|9EU3o)Ify_+Yn*-_HKyw5??|ba>kLJmCSHzb9wR6+ zN&2@g=C&`~^0A0xXt*2r)6f1-m+}8P2!EnOZycY1ul4trvaBWM08$!=F-rVab3 zf)806Re5{8R@d$MTkpOL%ssZbO510);`s@}osRp30vla_u?kt17_n4B9(F_w$Q)MR zV1U1st0V@xR__!%?f-541bG;#S2p8f2$$rFs;S{!Tpsy&v=$RgrXFX*d_>$h1) zG;gz#N==5oD%M8U54!3-+=Q!KM!WlspV{X~%AJ>nU@-ZWpF^Kx#VpCr+Z<68^{P|1cz@n*y-xwF@Jt;#i>~;l`2LzFOcPmq z40Es=%*>(&UVQ>%|4hUQKX-7UaKb<0;8Mti>Jl!Q^$j8~7#^|y^c)W@V?+QEhiSAB zcxa0PI@fH}Qum6DBKufN+WlePXa(!}KvDZO`PtoVwhcw%=(`SnQ?SkZ&9ZF|J4ay- zC!9HTS%cE4xCvRssJ+o@#mMB@B(2y5>v3k@S8=H*!`<{;B}Nkt^t@r1M-hQ>ECrK# zN8vwC#{WbJ{tV{qDPQP_Ju{M|C~Gy(L9?4ok|}nvDPhn24(Bwr(61cSI?!yaEPM5- zlevfgQnG#2wm;`~t^aNDK+~yQEbra$B=oJvKo5Zk@#n_PQxGqXTY$e@5`N}p(jO_A zOMd2NNA7cXpocl3 zcA+0{edPam8UMe9;Qw5qES+;Cjxt#%{JMjbf7Gx3hh4IyPQCFG)a^(OhrduZWgv1mc4gtl zqrRwUa`nd>tE3iy*7PuYu#S^d+m+8`w4zXI`SCWbUG#~JxAZ2ndlEbh zXa+$q%5_7Qr=AY7i`LZ~sh2kM zrgjJ8P3!7y|1WfC?Yc~g1HE1@6J3bIY;n=@lr81;afh7Ow@A-}FCf;w%aGgl$bod= z8y7)KTUFQiLi&`TCSsMeisNpIKjSrb!6)!8ZEf#+Xl@R`dw4o~-{UtO1A5+r=1$)O zS_3=Ml^{KX@ow#4?=1Ttos7RT1plDU_Azi+?V^|rlJHp%-UQF>!nxeGlmpsbD~4%n z2V*2qm+E)6pa#uoRTd(au7u1V-9dSewgBzHh&)0PL4!c8xm2c*T{M4L-lX1OU}s>1 zTzIBaE(!iy$Z32!730nBKnYqwb~C@-pYVGFeVa)xlryQ^$YrXs`!7d(bIgOCLL4lD zqOn$a4s?q#@I{WzsAaPQ^Ah+!wZw@Uv{ROT2slre?Qgvt9|EA0MyF&2K8KZHL zx;psjP}h;2OosXyU2=F;{5mEhzUr55+KMvXeVud`Ugmiu%7Wc_!mX(zV~F?9HYMix z$kjJ&G1BjsY>F$HM%C_ofvaPZ;!RjXY~o-izO`G89#=#Q2br&F*=OMDUUQ@huXz-j zuj5a}bG0gaSA#}spMPf~;|H|lz|xEAhp{*3V(o@nI?vFtjwV)kvCNW)xE6Fbb(rBEkR3PdY+`r5yF${`G32iK`jjApx2GQ`Ie12 z=o89kDsND23}xf$uWd>@i)U}Q&1#Lcf8d*_Scq2oC&N_s!v=>#Wmi3aB|g&rj>8_e zJ0TLig(D7f4@qrwpbROY*`?$Q`EJ#2&2RIurbty+tEDsE{TKc3SpShJ<9{jyfAs5WX^q*?R(*isA4UJ@x|_APxgU4N35mLY z0|to4d68l>sRe6trN|DEu5@7`EUPKDQ8yUaj#Jbx(^jJWy|>W++f^wl!e|ej`UxQ3 zFS|%uZrj`YrE=svlCN;;rSx-MInutlYwsm;&m`efw{|yeRVE2_U()tN0}D~^AT2Yr zU#Nss7$aT30MbN-_gdgw58n{PyRqxOayCOLJVVqzEu+=lY`@vbB#)M^&Fv<@8Y`9T z3r*@;)~>)<@)1E*s}>l$R9gFS0q9fzC&>7p4#8jH3rr5Q*K{fbT_(Fj4GPx&S%h zHJ~&xcJ^Yc>%97t=A6{?uACH_4{f)Z$GMOK{nL71O ztXi6~%`gUSLr~64?mCjciE4lF2N2HE(x-M2uMVE9`Z4@{$A7Y9{F_7Yr}co=57d0` zDAY%Aq(?R3@-iWWwKZ;Tke794bn*@)W+KbWghHYChdJRwUF>0;w!0AH7q)=XrSV>aZ ziv*s_yc;hqC*wOXe-ztho12EnVHwVnRrOmRLhrE5dPm_uQO5sF2>zIJT@lfcvhr9w z#!`e!%afLm5)Kv^6YICCT%)Z+M~^)_=KjCtol%c!Cds8G=eXqZHQP&Qi}7Mh((+f98-@EY=6n!-YE&14chVWi zU%Az$*E~dnRYeX4u7&HN zRC81m_S>zG{GTM_{||g|Q2*PBwOxBw!Q)29D0p=Vv_BXqt2nP#3azK>x7w90(z(cv zb8cnNJnui5(0PiCEP&dnudBj?fpC+HCCr`bi{MVm6Y zgxVa_cVS2%fLPE*m9Rx1zUkZ@g@2BW|G5zS$-Sp9GtV7G>#S@ZVI^bRUD@!C;@`Y) zE0d}|*VqxIV=@)a(HQAFR-iUovmj>)ftUZ|rC94Ssu90~U86MJ)0V;VZgik3M0?(d=Y~Nbb zxJym#P}I$wuK+e;DP|_6%uM}$GW(VT*N+2arxNc1-?0AaN2w&QQ+|6o#j8*ly)4?2 z&)t!QhfC|X@+C&E4Zn%K%*@z%^TLL=@rGFK=gqn?s)1Ue&^s75h>Y?{kx@O-S#Yhs^Qmi3b{1UU+xgV>Umg9%<$agt%X-ypMuVK^ zW|2`0Z1BU%7m+z>#Hev9N0y+-R%Z{!7@pe2=mulHk9E-fv0O%n9Hh8X%ydK&bRbr# zB+mowxjn~KjlyzHh!m#5PNB2iK$j`Bm%YhO*@stn>UM$&sTpih%NZ z_0VSs^+hXvbU*#q2mi@3{ul7QLHWNkP|^Ti{V1w6DVf6vT6g;n%*lF7EV)Q5HRW(t)JgQuc_2pQj ziUSGp$pe2-gQtIWk?MU3&U))%L+nMf z6?Q@Cf%abolF`eDCt-&6^r_F{h(Ho7%h8_*j1@S~lL}_y2mV2}d(dK0tzV%{;7%PS zTue%gcS8~9Agm@c##=>?7!F5yII{IqUO^ z8UL0L{2#igGE2VFe{F=V-9`5y*zx@VwQmG^bc3R{N>+G3ffYA=upiuaXgJnXhIc6r zqm~vTrMS$5qnY7d5g#f&1M{hkpVIhf{tU??Z}7~|&N6nrUvW%fKkR3Ba2eF9gLg() zk+b$!H0SVlk;i*}_F!0-vhfWQy+7PI8+R`Py@zuN+ORIgJcTFTxGW#mhWB4O%j_zg z!=>+b!!mFxN!{aOroD)@D}Li(A=ay4=^TaHnKYBRkEG1^C&PR6dpFg>TQOPA)ythTx23GXjtxx@*CgcAJzBnlV&-yiaGmqw7c?2`{0UBum_Iet^ z3DjdJ3G=AKn{(iOgPzj`=;#nVdhBL_uqr0P$y>9JCZpF#(~<&w$uUG-<%BOV(nmE& z;%J5a2kJD|-psJ3APRVCT)kC|bvQ+0X*|Z>QIm5oQ2CvNc(F=oe)9v zoFb&glM<{m=TiP!9NwVuu_|U-wlLmI`?(1>Wk0q37JPhIAN+G={Qnt(Ka;+Y{K0MT zbJ0xZ`))3W@>1{T(q7C*xM#Pnk%hH%Y(kU^=qLuID?E=A#`8D>8{miG-)YufgTkpi z#U#fFx~9#BtEW1#>Nk+lPHSY57V`oFFw%M#xP2j@9Hw#6UPPe%jcx|I1eb*}^6Qr! zlZAVcy59PVBT=9>dV%)3?rG&8mW(V{qdWlrFg!U*jdB4wuMP7&p}ZoHdmn^+K%4=` z@XpQtyE?bGCurfVPV<2$!KmYQMN3XChj#$q`?^1Zg)Jw1gju(&Ka%lG?I@>J zAkPQd&vcWW#OE>cxMvl7~+P}j+JXQ{Io=u89S2FT&ABiguv~TD><}t+l z(>;82HpY4)1@7FC6v+>Hm(g5|th3#7}C}|&K zh@fny*TV)WnRu;!x)LQShF)L4ky;W-5?oKbcOB`?d4ZpfT@QAYSm<>qFSs_&LGzZM zJn#s?8YcBDx`Jr{+L1d7e}jzwXCe3_4lnwAQ5~U(QM^Lec_D_|ZHUU}Z^yjjMraN) z=C|kJzwL@(9zh)Fdl2vSKG$qI$Q)%}H#8JB3|tpc61y&D7*-bZ7!fLnK{SF&FGKw9 zP(0;)&iBb@MnhoC6OL^t(cx8e-rxn~f~YZfmi#x$_< z+29R!qo`195(&ZR4io%6aoiS%u4#am6cvd~Iz|K&XNm*TS!bTX?9nrbz=m{1=1g&5 zChMGQDDxN<^vT5gs_kVT!!<+Si@d;gM%_s6R9>;g>3xJzK-06u89S9%AJj*+?)CA? zC|t#iLHdnRj+tIoOV1sPo8$TX5yngM)$mS7qAvP~_U9f#Dm)C+aLqP6;Z(ylGW?FJ z|0Ws#FGBFwo|>UqEb2&+I7Q2W-$kNnZtOK%w9Q**d)G&bG>*k$v9{Pph~C4^(Thq( z*R)uism;^t!AEAwCXJo+;|n^2$}W12*qI^&uY(WGc+x*``5nj z)3Qvb)|=GH`n0V`Vb40?Us?;#(mr^2zH!l^7q!feyf5Nkaxi$>Xrr#FQslVdQxeZ> zCq!L@Hpm@Ej(jKQ#XVQ^LbI=+++Syy*l}=jU;XhTNdi~R)B6^sE zBoUq>KYx+Zl}BdiM>+J68oH)I*KFJr2XoCfKnJg(BtPGKQ?GBUl00kany9>pm-3;{ zNW_>9D-?_H-t3qwDza$K`ZlcG#ysE-sr-Y>sy8-Kgs|sie=b+D}&+5PE ze@FjMknz71fJrcu^bYP-F!6tmh(2y~mSI#SI?5;}N^}W}X;@8jG55YPtm75zh!{XSTJjOcV#q zAL>XH2QGM4)J8uCBHmB@V@+sJR7v8@IVs}Id3ckoIH}y|QGcP?4a|1JKl$J2dw$x* zeyQ>jLu;4fOZ--S+NJo>izrcXj@e!&>SbSmdk5=^mt4zR&7E8<&W>887d$GJLHu*l z9ff~^j6a>i3y%Nj#RYSU#1HvA@h8Pa;@`%kO=v`W3m&8cdl*Z_ejtgODe&o;<6G5Qk+T{}SZRglnx#uFCRUd4=YQD-B7wI>vLV3X=P}!J}e+A{wL6qKK!eXdaT3$9){`Q&LGE zyidA^>Ib)9&(mjInJuzWc+OSK{ak^inu4|p^sVkF{HM$K|0@K4@Zky%z7y~}ly9`P zjE22E5%SRTGt7WC+i;D8nbPjmiz?t20GJA+au{aVLVHU0ZCZ5lQ{xf9!FNfey?Y^o0jhK%M z@(~r;D8`nd^-Rte2Mo>^V=P4?xhD!(QBTg#8%(}#j)N3sPcGCHi5cuW@fO%$%g04+ zwirYK9QzI;KCJO5qLLl&#IF$$gN5{5`@f|x`kwR`zbE}7Ss<#S;+#dI5^pwSO_8V? zQY3z$Fp6_)ip2NP{v;+IbJ21d;nIsl(m+tcX1?R5Y0yoLgzuGZxz`WuZ}cd%cNG5j z%lK2JHz@x(2k_E{>iAVnz;e3}&8a z$^(v_D91YtV;yJjsuVx3%7X;zzgr*uzfi^>o#0#eGo-2@$V*A;PEorkPbo*wP^_k$ za>A#61jY#*kE1_Ois)K*3uOWU7EEKH{r6a>Kixt3%QVgMYrYP&w*_dFM#4YrM{FjC zoF)b)^hs+CJliMjZ{Lx2{I+{*lQ$q@W}zqm2K;4 z4tq-H#-1w@|CD0HNQY4@o(G+_A8&o|pDE-2RS5p1C%{oXOK5 zwmZoSrV|HS_yBx+(9yH10y=*8ANx(~pFAG;AG^Lc|C{KK_!~j*YTn%qtAx zYIDu%@-n_+u8@_%3+A%bmDTI{xt1F2E&SRHK0TYi-(10G=rSho&snP+nWgSW*ruU0GSSf}g^#Tv{`3TG`TNv(!l%iWk{q=r68q zo3moY1Li}sraYo7<~JYt+Eg3u{a5{&Gru@&{vQY5Ut&Lf^3;>-J`3NIc%_MkBaya_ zT9!mU%aZx!73&wwGw|~))#c`p8fvz)g=@0yLu&I6u3x zdPzA7wImX}*fWKB&eN(Ujz=p4ev#VASk zJDLfvhwUlY-o0n-mcj2>kSUXh^naGLoZXuL(aaZ5T6XPTvhBA7o`Via&nAm$CW{|g z0&9Qr;43US0qOwxUS&z+YxMO9`Q;&C40;QsKg^OfZ_DXZ-(bls(8Hh^`%o_4lG7jC z&yp69@&HS+4zeT%M7MG8vScghb_fJT5ug6;v`3mOF)4H^TYj+@4Ur~`-bpfu3@Rn>E_qkDed+A`@| zjOLXqOU-2~XnG`G9IaOs8OtwSQ&G0Myt3jxn%-ZxKKNt(3uVd`d~mS-^8}UJQcZcm zCy%U2hSFEQ(z4_}ekpcR%2#bPaxW{2vS(!s)pbmNbkjpq95owSiUzNd;h=t8=r$rx+K~t4 zecrF-{gw~p{c^vww>>^yPp~;-%cT9||M-oxx12aZ_x$2LtO5Ift(`uJ?q7N~eiin4 zU%se&3Hu$xj=y^b`w914TcI_Q9-PRYvrqhZ!dfHw%j{*V{u*rs8iuJM%?+q3N0CS*pK#ZKmt@fBF$SuF>@Nu5;^+>qHZkUsUEi1~QW3qIokO zpz?wbZbFCf4%R}|Bm|L|05ay>-f|l{yE?gm5p?ZfkxN%bHYDP`w9Qa zTY%=b!>P!|xFOXa=+E2MkJE(z#Lf5vdHvA&AGrPhiPL@X|FMieUGx~l|NB1Vhxqef zef~0RS${|ES+QM*UegAmG2efPsC??(zVGY%M|vOpACU2ff&4A}8KR$OoH66y`QRTq f50$bMZ3d_rZN}7H*niHF0r>s@xBprWQ2hTFz}`kt diff --git a/bin/Meshtastic_nRF52_factory_erase_v3_S140_6.1.0.uf2 b/bin/Meshtastic_nRF52_factory_erase_v3_S140_6.1.0.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..e44d9da2a7934c1230927695ce899dc928faa452 GIT binary patch literal 126976 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 literal 0 HcmV?d00001 From 7dad2286e25b01ed01325ce53eb8dece5932fa54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 09:58:27 +0200 Subject: [PATCH 1039/1377] trunk fmt --- src/mesh/LR11x0Interface.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index ecd222c48..0201282db 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -100,13 +100,6 @@ template bool LR11x0Interface::init() // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option if (res == RADIOLIB_ERR_NONE) res = lora.setRegulatorDCDC(); -// #ifdef TRACKER_T1000_E -// #ifdef LR11X0_DIO_RF_SWITCH_CONFIG -// res = lora.setDioAsRfSwitch(LR11X0_DIO_RF_SWITCH_CONFIG); -// #else -// res = lora.setDioAsRfSwitch(0x03, 0x0, 0x01, 0x03, 0x02, 0x0, 0x0, 0x0); -// #endif -// #endif if (res == RADIOLIB_ERR_NONE) { if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate res = lora.setRxBoostedGainMode(true); From 543e7f334227397b44e6fc5bb47e5dce1b7b396c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 12:03:06 +0200 Subject: [PATCH 1040/1377] add generic bootloaders by @markbirss --- ...stic_6.1.0_bootloader-0.9.2_s140_6.1.1.hex | 11744 +++++++++++++++ ...stic_6.1.0_bootloader-0.9.2_s140_6.1.1.zip | Bin 0 -> 190874 bytes ...stic_7.3.0_bootloader-0.9.2_s140_7.3.0.hex | 11851 ++++++++++++++++ ...stic_7.3.0_bootloader-0.9.2_s140_7.3.0.zip | Bin 0 -> 192586 bytes ...Meshtastic_6.1.0_bootloader-0.9.2_nosd.uf2 | Bin 0 -> 74752 bytes ...Meshtastic_7.3.0_bootloader-0.9.2_nosd.uf2 | Bin 0 -> 74752 bytes 6 files changed, 23595 insertions(+) create mode 100644 bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.hex create mode 100644 bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.zip create mode 100644 bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.hex create mode 100644 bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.zip create mode 100644 bin/generic/update-Meshtastic_6.1.0_bootloader-0.9.2_nosd.uf2 create mode 100644 bin/generic/update-Meshtastic_7.3.0_bootloader-0.9.2_nosd.uf2 diff --git a/bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.hex b/bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.hex new file mode 100644 index 000000000..eebabf484 --- /dev/null +++ b/bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.hex @@ -0,0 +1,11744 @@ +:04000003F000B2CD8A +:020000040000FA +:1000000000040020810A000015070000610A0000BA +:100010001F07000029070000330700000000000050 +:10002000000000000000000000000000A50A000021 +:100030003D070000000000004707000051070000D6 +:100040005B070000650700006F07000079070000EC +:10005000830700008D07000097070000A10700003C +:10006000AB070000B5070000BF070000C90700008C +:10007000D3070000DD070000E7070000F1070000DC +:10008000FB070000050800000F0800001908000029 +:10009000230800002D080000370800004108000078 +:1000A0004B080000550800005F08000069080000C8 +:1000B000730800007D080000870800009108000018 +:1000C0009B080000A5080000AF080000B908000068 +:1000D000C3080000CD080000D7080000E1080000B8 +:1000E000EB080000F5080000FF0800000909000007 +:1000F000130900001D090000270900003109000054 +:100100003B0900001FB500F003F88DE80F001FBD8C +:1001100000F0ACBC40F6FC7108684FF01022401CA7 +:1001200008D00868401C09D00868401C04D0086842 +:1001300000F037BA9069F5E79069F9E7704770B554 +:100140000B46010B184400F6FF70040B4FF0805073 +:100150000022090303692403406943431D1B104621 +:1001600000F048FA29462046BDE8704000F042BA47 +:10017000F0B54FF6FF734FF4B4751A466E1E11E0DA +:10018000A94201D3344600E00C46091B30F8027B3B +:10019000641E3B441A44F9D19CB204EB134394B25D +:1001A00004EB12420029EBD198B200EB134002EBB2 +:1001B000124140EA0140F0BDF34992B00446D1E952 +:1001C0000001CDE91001FF224021684600F0F4FB58 +:1001D00094E80F008DE80F00684610A902E004C8FB +:1001E00041F8042D8842FAD110216846FFF7C0FF7C +:1001F0001090AA208DF8440000F099F9FFF78AFFCB +:1002000040F6FC7420684FF01025401C0FD0206889 +:1002100010226946803000F078F92068401C08D030 +:100220002068082210A900F070F900F061F9A869AF +:10023000EEE7A869F5E74FF080500369406940F6A2 +:10024000FC71434308684FF01022401C06D0086838 +:1002500000F58050834203D2092070479069F7E788 +:100260000868401C04D00868401C03D00020704778 +:100270009069F9E70420704770B504460068C34DE3 +:10028000072876D2DFE800F033041929631E250021 +:10029000D4E9026564682946304600F062F92A46CE +:1002A0002146304600F031F9AA002146304600F0E0 +:1002B00057FB002800D0032070BD00F009FC4FF46C +:1002C000805007E0201D00F040F90028F4D100F034 +:1002D000FFFB60682860002070BD241D94E80700C3 +:1002E000920000F03DFB0028F6D00E2070BDFFF715 +:1002F000A2FF0028FAD1D4E901034FF0805100EBAE +:10030000830208694D69684382420ED840F6F8704E +:1003100005684FF010226D1C09D0056805EB8305B8 +:100320000B6949694B439D4203D9092070BD55694A +:10033000F4E70168491C03D00068401C02D003E0C8 +:100340005069FAE70F2070BD2046FFF735FFFFF731 +:1003500072FF0028F7D1201D00F0F7F80028F2D135 +:1003600060680028F0D100F0E2F8FFF7D3FE00F05B +:10037000BFF8072070BD10B50C46182802D0012028 +:10038000086010BD2068FFF777FF206010BD41684E +:10039000054609B1012700E0002740F6F8742068FF +:1003A0004FF01026401C2BD02068AA68920000F065 +:1003B000D7FA38B3A86881002068401C27D020688D +:1003C000FFF7BDFED7B12068401C22D026684FF051 +:1003D0008050AC686D68016942695143A9420DD9EA +:1003E000016940694143A14208D92146304600F0E5 +:1003F000B8F822462946304600F087F800F078F831 +:100400007069D2E700F093F8FFF784FEF6E77069B1 +:10041000D6E77669DBE740F6FC7420684FF01026DB +:10042000401C23D02068401C0CD02068401C1FD0EA +:100430002568206805F18005401C1BD027683879A5 +:10044000AA2819D040F6F8700168491C42D001680A +:10045000491C45D00168491C3ED001680968491C07 +:100460003ED00168491C39D000683EE0B069DAE747 +:10047000B569DEE7B769E2E710212846FFF778FEA5 +:100480003968814222D12068401C05D0D4F8001080 +:1004900001F18002C03107E0B169F9E730B108CA63 +:1004A00051F8040D984201D1012000E000208A4259 +:1004B000F4D158B1286810B1042803D0FEE72846CB +:1004C000FFF765FF3149686808600EE0FFF722FE1C +:1004D00000F00EF87169BBE77169BFE7706904E06D +:1004E0004FF480500168491C01D000F0CBFAFEE7C0 +:1004F000BFF34F8F26480168264A01F4E06111439B +:100500000160BFF34F8F00BFFDE72DE9F0411746B3 +:100510000D460646002406E03046296800F054F8EF +:10052000641C2D1D361DBC42F6D3BDE8F08140F69B +:10053000FC700168491C04D0D0F800004FF48051D1 +:10054000FDE54FF010208069F8E74FF080510A690F +:10055000496900684A43824201D810207047002050 +:10056000704770B50C4605464FF4806608E0284693 +:1005700000F017F8B44205D3A4F5806405F5805562 +:10058000002CF4D170BD0000F40A0000000000202F +:100590000CED00E00400FA05144801680029FCD0C5 +:1005A0007047134A0221116010490B68002BFCD0E0 +:1005B0000F4B1B1D186008680028FCD0002010603D +:1005C00008680028FCD07047094B10B501221A605A +:1005D000064A1468002CFCD0016010680028FCD08A +:1005E0000020186010680028FCD010BD00E4014015 +:1005F00004E5014070B50C46054600F073F810B9EB +:1006000000F07EF828B121462846BDE8704000F091 +:1006100007B821462846BDE8704000F037B8000012 +:100620007FB5002200920192029203920A0B000B06 +:100630006946012302440AE0440900F01F0651F80C +:10064000245003FA06F6354341F82450401C8242F8 +:10065000F2D80D490868009A10430860081D016827 +:10066000019A1143016000F03DF800280AD00649C4 +:1006700010310868029A10430860091D0868039A3F +:10068000104308607FBD00000006004030B50F4CED +:10069000002200BF04EB0213D3F800582DB9D3F8A1 +:1006A000045815B9D3F808581DB1521C082AF1D3C3 +:1006B00030BD082AFCD204EB0212C2F80008C3F8CD +:1006C00004180220C3F8080830BD000000E0014013 +:1006D0004FF08050D0F83001082801D0002070473A +:1006E000012070474FF08050D0F83011062905D016 +:1006F000D0F83001401C01D0002070470120704725 +:100700004FF08050D0F830010A2801D00020704707 +:100710000120704708208F490968095808471020B0 +:100720008C4909680958084714208A4909680958FA +:100730000847182087490968095808473020854923 +:100740000968095808473820824909680958084744 +:100750003C20804909680958084740207D490968BC +:100760000958084744207B49096809580847482028 +:1007700078490968095808474C207649096809589A +:10078000084750207349096809580847542071499F +:1007900009680958084758206E49096809580847E8 +:1007A0005C206C4909680958084760206949096854 +:1007B00009580847642067490968095808476820AC +:1007C00064490968095808476C2062490968095852 +:1007D000084770205F4909680958084774205D4937 +:1007E00009680958084778205A490968095808478C +:1007F0007C205849096809580847802055490968EC +:10080000095808478420534909680958084788202F +:1008100050490968095808478C204E490968095809 +:10082000084790204B4909680958084794204949CE +:10083000096809580847982046490968095808472F +:100840009C204449096809580847A0204149096883 +:1008500009580847A4203F49096809580847A820B3 +:100860003C49096809580847AC203A4909680958C1 +:100870000847B0203749096809580847B420354966 +:10088000096809580847B8203249096809580847D3 +:10089000BC203049096809580847C0202D4909681B +:1008A00009580847C4202B49096809580847C82037 +:1008B0002849096809580847CC2026490968095879 +:1008C0000847D0202349096809580847D4202149FE +:1008D000096809580847D8201E4909680958084777 +:1008E000DC201C49096809580847E02019490968B3 +:1008F00009580847E4201749096809580847E820BB +:100900001449096809580847EC2012490968095830 +:100910000847F0200F49096809580847F4200D4995 +:10092000096809580847F8200A490968095808471A +:10093000FC2008490968095808475FF48070054998 +:10094000096809580847000003480449024A034B54 +:100950007047000000000020000B0000000B0000AA +:1009600040EA010310B59B070FD1042A0DD310C82C +:1009700008C9121F9C42F8D020BA19BA884201D97E +:10098000012010BD4FF0FF3010BD1AB1D30703D0C6 +:10099000521C07E0002010BD10F8013B11F8014B7C +:1009A0001B1B07D110F8013B11F8014B1B1B01D198 +:1009B000921EF1D1184610BD02F0FF0343EA032254 +:1009C00042EA024200F005B87047704770474FF0A6 +:1009D00000020429C0F0128010F0030C00F01B800C +:1009E000CCF1040CBCF1020F18BF00F8012BA8BF1A +:1009F00020F8022BA1EB0C0100F00DB85FEAC17CDE +:100A000024BF00F8012B00F8012B48BF00F8012B90 +:100A100070474FF0000200B51346944696462039C1 +:100A200022BFA0E80C50A0E80C50B1F12001BFF4A7 +:100A3000F7AF090728BFA0E80C5048BF0CC05DF80D +:100A400004EB890028BF40F8042B08BF704748BF5B +:100A500020F8022B11F0804F18BF00F8012B7047CF +:100A6000014B1B68DB6818470000002009480A4951 +:100A70007047FFF7FBFFFFF745FB00BD20BFFDE719 +:100A8000064B1847064A1060016881F308884068E1 +:100A900000470000000B0000000B000017040000DE +:100AA000000000201EF0040F0CBFEFF30881EFF3ED +:100AB0000981886902380078182803D100E0000015 +:100AC000074A1047074A12682C3212681047000084 +:100AD00000B5054B1B68054A9B58984700BD0000B0 +:100AE0007703000000000020F00A0000040000006E +:100AF000001000000000000000FFFFFF0090D00386 +:1010000080130020B157020069C00000175702008A +:1010100069C0000069C0000069C000000000000055 +:101020000000000000000000000000000D58020059 +:1010300069C000000000000069C0000069C0000035 +:10104000755802007B58020069C0000069C00000AA +:1010500069C0000069C0000069C0000069C00000EC +:101060008158020069C0000069C000008758020072 +:1010700069C000008D580200935802009958020080 +:1010800069C0000069C0000069C0000069C00000BC +:1010900069C0000069C0000069C0000069C00000AC +:1010A00069C000009F58020069C0000069C00000CC +:1010B00069C0000069C0000069C0000069C000008C +:1010C000A558020069C0000069C0000069C00000A6 +:1010D00069C0000069C0000069C0000069C000006C +:1010E00069C0000069C0000069C0000069C000005C +:1010F00069C0000069C0000069C0000069C000004C +:1011000069C0000069C0000000F002F824F03FFB55 +:101110000AA090E8000C82448344AAF10107DA4552 +:1011200001D124F034FBAFF2090EBAE80F0013F03E +:10113000010F18BFFB1A43F001031847584C020077 +:10114000784C02000A444FF0000C10F8013B13F0F9 +:10115000070408BF10F8014B1D1108BF10F8015B10 +:10116000641E05D010F8016B641E01F8016BF9D103 +:1011700013F0080F1EBF10F8014BAD1C0C1B09D15A +:101180006D1E58BF01F801CBFAD505E014F8016BCC +:1011900001F8016B6D1EF9D59142D6D3704700005E +:1011A0000023002400250026103A28BF78C1FBD870 +:1011B000520728BF30C148BF0B6070471FB500F011 +:1011C0003DF88DE80F001FBD1EF0040F0CBFEFF3BC +:1011D0000880EFF30980014A10470000ABBF000010 +:1011E000F0B44046494652465B460FB402A0013077 +:1011F00001B50648004700BF01BC86460FBC8046CB +:10120000894692469B46F0BC7047000009110000D9 +:101210008269034981614FF001001044704700006A +:101220002512000001B41EB400B514F0CBFE01B4C9 +:101230000198864601BC01B01EBD000024F0A4BA8E +:1012400070B51A4C054609202070A01C00F0D1F89A +:101250005920A08029462046BDE8704008F0CEB84D +:1012600008F0D7B870B50C461149097829B1A0F13A +:1012700060015E2908D3012013E0602804D06928AA +:1012800002D043F201000CE020CC0A4E94E80E009C +:1012900006EB8000A0F58050241FD0F8806E284611 +:1012A000B047206070BD012070470000080000209A +:1012B00018000020F05802003249884201D2012073 +:1012C00070470020704770B50446A0F500002E4E10 +:1012D000B0F1786F02D23444A4F500042948844266 +:1012E00001D2012500E0002500F043F848B125B9FE +:1012F000B44204D32548006808E0012070BD0020F6 +:1013000070BD002DF9D1B442F9D321488442F6D200 +:10131000F3E710B50446A0F50000B0F1786F03D2F2 +:1013200019480444A4F5000400F023F84FF080416C +:1013300030B11648006804E08C4204D2012003E07A +:1013400013488442F8D2002080F0010010BD10B58F +:1013500020B1FFF7DEFF08B1012010BD002010BD55 +:1013600010B520B1FFF7AFFF08B1012010BD00207C +:1013700010BD084808490068884201D10120704723 +:101380000020704700600200000000201C000020C8 +:101390000800002054000020BEBAFECA10B5044662 +:1013A0000021012000F03DF800210B2000F039F869 +:1013B0000421192000F035F804210D2000F031F847 +:1013C00004210E2000F02DF804210F2000F029F850 +:1013D0000421C84300F025F80621162000F021F86A +:1013E0000621152000F01DF82046FFF729FF0020F8 +:1013F00010BDB62101807047FFF732BF114870471A +:1014000010487047104A10B514680F4B0F4A083344 +:101410001A60FFF727FF0C48001D046010BD7047DD +:1014200070474907090E002804DB00F1E02080F82E +:101430000014704700F00F0000F1E02080F8141D48 +:101440007047000003F9004210050240010000014E +:10145000FE48002101604160018170472DE9F7439A +:10146000044692B091464068FFF771FF40B1606852 +:10147000FFF776FF20B9607800F00300022801D062 +:10148000012000E00020F14E30724846FFF71BFFBC +:1014900018B1102015B0BDE8F0834946012001F0D5 +:1014A0008EFE0028F6D101258DF842504FF4C05031 +:1014B000ADF84000002210A9284606F009FC0028DB +:1014C000E8D18DF842504FF428504FF00008ADF8A5 +:1014D000400047461C216846CDF81C8024F0EFF8F8 +:1014E0009DF81C0008AA20F00F00401C20F0F0001E +:1014F00010308DF81C0020788DF81D0061789DF863 +:101500001E0061F3420040F001008DF81E009DF8BE +:1015100000000AA940F002008DF800002089ADF813 +:101520003000ADF83270608907AFADF834000B972A +:10153000606810AC0E900A94684606F0BCF900286A +:10154000A8D1BDF8200030808DF8425042F601202D +:10155000ADF840009DF81E0008AA20F00600801C8F +:1015600020F001008DF81E000220ADF83000ADF82B +:10157000340013A80E900AA9684606F09CF90028CA +:1015800088D1BDF820007080311D484600F033F945 +:10159000002887D18DF8425042F6A620ADF84000D1 +:1015A0001C216846CDF81C8024F089F89DF81C00A9 +:1015B000ADF8345020F00F00401C20F0F000103047 +:1015C0008DF81C009DF81D0008AA20F0FF008DF882 +:1015D0001D009DF81E000AA920F0060040F0010041 +:1015E000801C8DF81E009DF800008DF8445040F0DE +:1015F00002008DF80000CDE90A4711A80E90ADF861 +:101600003050684606F057F9002899D1BDF82000FF +:10161000F08000203EE73EB504460820ADF800000B +:101620002046FFF750FE08B110203EBD21460120A4 +:1016300001F0C5FD0028F8D12088ADF804006088CD +:10164000ADF80600A088ADF80800E088ADF80A0003 +:101650007E4801AB6A468088002106F035FDBDF862 +:1016600000100829E1D003203EBD1FB5044600202C +:1016700002900820ADF80800CDF80CD02046FFF706 +:1016800022FE10B1102004B010BD704802AA81885B +:101690004FF6FF7006F05AFF0028F4D1BDF808108D +:1016A000082901D00320EEE7BDF800102180BDF825 +:1016B00002106180BDF80410A180BDF80610E18021 +:1016C000E1E701B582B00220ADF800005F4802AB4F +:1016D0006A464088002106F0F7FCBDF80010022998 +:1016E00000D003200EBD1CB5002100910221ADF8F1 +:1016F00000100190FFF70DFE08B110201CBD5348EB +:101700006A4641884FF6FF7006F020FFBDF80010D2 +:101710000229F3D003201CBDFEB54C4C06461546ED +:10172000207A0F46C00705D00846FFF7CCFD18B158 +:101730001020FEBD0F20FEBDF82D01D90C20FEBDEE +:101740003046FFF7C0FD18BB208801A905F0B8FDA1 +:101750000028F4D130788DF80500208801A906F022 +:1017600091FC0028EBD100909DF800009DF8051039 +:1017700040F002008DF80000090703D040F0080097 +:101780008DF800002088694606F019FC0028D6D1A3 +:10179000ADF8085020883B4602AA002106F094FCD0 +:1017A000BDF80810A942CAD00320FEBD7CB505468D +:1017B0000020009001900888ADF800000C462846F3 +:1017C0000195FFF7C4FD18B92046FFF7A2FD08B147 +:1017D00010207CBD15B1BDF8000050B11B486A4611 +:1017E00001884FF6FF7006F0B1FEBDF800102180B1 +:1017F0007CBD0C207CBD30B593B0044600200D4666 +:101800000090142101A823F05AFF1C2108A823F0FE +:1018100056FF9DF80000CDF808D020F00F00401CC6 +:1018200020F0F00010308DF800009DF8010020F04D +:10183000FF008DF801009DF8200040F002008DF8B7 +:10184000200001208DF8460002E000002002002068 +:1018500042F60420ADF8440011A801902088ADF8AC +:101860003C006088ADF83E00A088ADF84000E088FC +:10187000ADF842009DF8020006AA20F00600801C88 +:1018800020F001008DF802000820ADF80C00ADF842 +:1018900010000FA8059001A908A806F00CF8002870 +:1018A00003D1BDF818002880002013B030BD00001F +:1018B000F0B5007B059F1E4614460D46012800D05A +:1018C000FFDF0C2030803A203880002C08D0287AA6 +:1018D000032806D0287B012800D0FFDF1720608175 +:1018E000F0BDA889FBE72DE9F04786B0144691F8D2 +:1018F0000C900E9A0D46B9F1010F0BD01021007B10 +:101900002E8A8846052807D0062833D0FFDF06B088 +:10191000BDE8F0870221F2E7E8890C2100EB4000E6 +:1019200001EB4000188033201080002CEFD0E889B4 +:10193000608100271AE00096688808F1020301AA76 +:10194000696900F084FF06EB0800801C07EB470183 +:1019500086B204EB4102BDF8040090810DF106014E +:1019600040460E3212F0D3FD7F1CBFB26089B842F0 +:10197000E1D8CCE734201080E889B9F1010F11D00B +:10198000122148430E301880002CC0D0E8896081B5 +:101990004846B9F1010F00D00220207300270DF155 +:1019A000040A1FE00621ECE70096688808F10203AC +:1019B00001AA696900F04BFF06EB0800801C86B2A3 +:1019C000B9F1010F12D007EBC70004EB4000BDF8DE +:1019D0000410C18110220AF10201103023F0CEFD63 +:1019E0007F1CBFB26089B842DED890E707EB4701A1 +:1019F00004EB4102BDF80400D0810AF10201404627 +:101A0000103212F084FDEBE72DE9F0470E4688B066 +:101A100090F80CC096F80C80378AF5890C20109944 +:101A200002F10C044FF0000ABCF1030F08D0BCF126 +:101A3000040F3ED0BCF1070F7DD0FFDF08B067E791 +:101A400005EB850C00EB4C00188031200880002A43 +:101A5000F4D0A8F1060000F0FF09558125E0182117 +:101A600001A823F02CFE00977088434601AA7169F3 +:101A700000F0EDFEBDF804002080BDF80600E08017 +:101A8000BDF808002081A21C0DF10A01484612F0A1 +:101A90003EFDB9F1000F00D018B184F804A0A4F8FD +:101AA00002A007EB080087B20A346D1EADB2D6D291 +:101AB000C4E705EB850C00EB4C0018803220088051 +:101AC000002ABBD0A8F1050000F0FF09558137E0DE +:101AD00000977088434601AA716900F0B8FE9DF82E +:101AE0000600BDF80410E1802179420860F300018E +:101AF00062F34101820862F38201C20862F3C3010A +:101B0000020962F30411420962F34511820962F38A +:101B100086112171C0096071BDF80700208122463D +:101B20000DF10901484612F0F2FC18B184F802A048 +:101B3000A4F800A000E007E007EB080087B20A3431 +:101B40006D1EADB2C4D279E7A8F1020084B205FBE4 +:101B500008F000F10E0CA3F800C035230B80002A1A +:101B6000A6D055819481009783B270880E32716936 +:101B700000F06DFE62E72DE9F84F1E460A9D0C4607 +:101B800081462AB1607A00F58070D080E0891081AA +:101B900099F80C000C274FF000084FF00E0A0D28A2 +:101BA00073D2DFE800F09E070E1C28303846556AD5 +:101BB00073737300214648460095FFF779FEBDE830 +:101BC000F88F207B9146082802D0032800D0FFDF41 +:101BD000378030200AE000BFA9F80A80EFE7207BB9 +:101BE0009146042800D0FFDF378031202880B9F1EA +:101BF000000FF1D1E3E7207B9146042800D0FFDFFE +:101C000037803220F2E7207B9146022800D0FFDFA8 +:101C100037803320EAE7207B1746022800D0FFDF19 +:101C20003420A6F800A02880002FC8D0A7F80A808A +:101C3000C5E7207B1746042800D0FFDF3520A6F833 +:101C400000A02880002FBAD04046A7F80A8012E0F2 +:101C5000207B1746052802D0062800D0FFDF102081 +:101C6000308036202880002FA9D0E0897881A7F81D +:101C70000E80B9F80E00B881A1E7207B91460728B5 +:101C800000D0FFDF37803720B0E72AE04FF01200A6 +:101C900018804FF038001700288090D0E0897881B4 +:101CA000A7F80E80A7F8108099F80C000A2805D034 +:101CB0000B2809D00C280DD0FFDF80E7207B0A28F5 +:101CC00000D0FFDF01200AE0207B0B2800D0FFDFDF +:101CD000042004E0207B0C2800D0FFDF05203873AF +:101CE0006DE7FFDF6BE770B50C46054601F0ABFB17 +:101CF00020B10078222804D2082070BD43F20200EF +:101D000070BD0521284610F075FE206008B1002046 +:101D100070BD032070BD30B44880087820F00F00FB +:101D2000C01C20F0F000903001F8080B1DCA81E8BB +:101D30001D0030BC07F0E3BB2DE9FF4784B000274E +:101D40008246029707989046894612300AF0DCF9DD +:101D5000401D20F00306079828B907A95046FFF751 +:101D6000C2FF002854D1B9F1000F05D00798017BBC +:101D700019BB052504681BE098F80000092803D06A +:101D80000D2812D0FFDF46E0079903254868B0B35D +:101D9000497B42887143914239D98AB2B3B2011D5D +:101DA00010F09BFC0446078002E0079C04250834E1 +:101DB0000CB1208810B1032D29D02CE00798012107 +:101DC00012300AF0D3F9ADF80C00024602AB2946F6 +:101DD000504608F000FA070001D1A01C02900798B5 +:101DE0003A461230C8F80400A8F802A003A94046F9 +:101DF000029B0AF0C8F9D8B10A2817D200E006E021 +:101E0000DFE800F007091414100B0D14141213204E +:101E100014E6002012E6112010E608200EE643F238 +:101E200003000BE6072009E60D2007E6032005E680 +:101E3000BDF80C002346CDE900702A4650460799AC +:101E400000F015FD57B9032D08D10798B3B2417BB7 +:101E5000406871438AB2011D10F053FCB9F1000FC4 +:101E6000D7D0079981F80C90D3E72DE9FE4F914622 +:101E70001A881C468A468046FAB102AB494608F0E9 +:101E8000AAF9050019D04046A61C278810F0F6FED6 +:101E90003246072629463B46009610F004FB208870 +:101EA0002346CDE900504A465146404600F0DFFC4B +:101EB000002020800120BDE8FE8F0020FBE710B548 +:101EC00086B01C46AAB104238DF800301388ADF803 +:101ED00008305288ADF80A208A788DF80E200988DB +:101EE000ADF80C1000236A462146FFF725FF06B027 +:101EF00010BD1020FBE770B50D46052110F07AFDEE +:101F0000040000D1FFDF294604F11200BDE8704053 +:101F10000AF015B92DE9F8430D468046002607F072 +:101F2000EBFA04462878102878D2DFE800F0773BF7 +:101F30003453313112313131083131313131287975 +:101F4000001FC0B2022801D0102810D114BBFFDF3F +:101F500035E004B9FFDF0521404610F04BFD007B62 +:101F6000032806D004280BD0072828D0FFDF072637 +:101F700055E02879801FC0B2022820D050B1F6E782 +:101F80002879401FC0B2022819D0102817D0EEE7D8 +:101F900004B9FFDF13E004B9FFDF287901280ED16F +:101FA000172137E00521404610F024FD070000D13D +:101FB000FFDF07F1120140460AF09EF82CB12A46D5 +:101FC00021464046FFF7A7FE29E01321404602F0D4 +:101FD000F7FC24E004B9FFDF0521404610F00AFDBC +:101FE000060000D1FFDF694606F112000AF08EF804 +:101FF000060000D0FFDFA988172901D2172200E0D0 +:102000000A46BDF80000824202D9014602E005E01E +:102010001729C5D3404600F03AFCD0E7FFDF304631 +:10202000BDE8F883401D20F0030219B102FB01F066 +:10203000001D00E000201044704713B5009848B11F +:102040000024684610F0F3FA002C02D1F74A0099F8 +:1020500011601CBD01240020F4E72DE9F0470C4677 +:1020600015462421204623F02AFB05B9FFDFA87876 +:1020700060732888DFF8B4A3401D20F00301AF7817 +:102080008946DAF8000010F0F0FA060000D1FFDF10 +:102090004FF000082660A6F8008077B109FB07F131 +:1020A000091D0AD0DAF8000010F0DFFA060000D1AE +:1020B000FFDF6660C6F8008001E0C4F8048029886C +:1020C00004F11200BDE8F0470AF008B82DE9F04726 +:1020D000804601F112000D4681460AF015F8401DB8 +:1020E000D24F20F003026E7B14462968386810F046 +:1020F000E7FA3EB104FB06F2121D03D069683868A6 +:1021000010F0DEFA052010F01DFC0446052010F04A +:1021100021FC201A012802D1386810F09BFA4946A8 +:102120004046BDE8F04709F0EEBF70B50546052111 +:1021300010F060FC040000D1FFDF04F1120128461A +:10214000BDE8704009F0D8BF2DE9F04F91B04FF0D5 +:10215000000BADF834B0ADF804B047880C46054626 +:1021600092460521384610F045FC060000D1FFDFFD +:1021700024B1A780A4F806B0A4F808B029780922F1 +:102180000B20B2EB111F7DD12A7A04F11001382700 +:102190004FF00C084FF001090391102A73D2DFE8C9 +:1021A00002F072F2F1F07F08D2888D9F3DDBF3EEF2 +:1021B000B6B6307B022800D0FFDFA88908EBC0014B +:1021C000ADF804103021ADF83410002C25D060811A +:1021D000B5F80E9000271DE004EBC708317C88F8A5 +:1021E0000E10F189A8F80C10CDF80090688804232F +:1021F00004AA296900F02BFBBDF81010A8F81010F4 +:1022000009F10400BDF812107F1C1FFA80F9A8F82C +:102210001210BFB26089B842DED80DE1307B0228CF +:1022200000D0FFDFE98908EBC100ADF804003020E1 +:10223000ADF83400287B0A90001FC0B20F90002C2C +:10224000EBD06181B5F81090002725E0CDF8009023 +:102250006888696903AA0A9B00F0F9FA0A9804EBF6 +:10226000C70848441FFA80F908F10C0204A90F9826 +:1022700012F04DF918B188F80EB0A8F80CB0BDF8FE +:102280000C1001E0D4E0CFE0A8F81010BDF80E105B +:102290007F1CA8F81210BFB26089B842D6D8CBE034 +:1022A0000DA8009001AB224629463046FFF71BFBE4 +:1022B000C2E0307B082805D0FFDF03E0307B082830 +:1022C00000D0FFDFE8891030ADF804003620ADF80B +:1022D0003400002C3FD0A9896181F189A18127E0D8 +:1022E000307B092800D0FFDFA88900F10C01ADF890 +:1022F00004103721ADF83410002C2CD06081E8890F +:102300000090AB89688804F10C02296956E0E889DD +:102310003921103080B2ADF80400ADF83410002C33 +:1023200074D0A9896181287A0E280AD002212173EC +:10233000E989E181288A0090EB8968886969039AB4 +:102340003CE00121F3E70DA8009001AB22462946AD +:102350003046FFF759FB6FE0307B0A2800D0FFDFE3 +:102360001220ADF80400ADF834704CB3A989618136 +:10237000A4F810B0A4F80EB084F80C905CE020E053 +:1023800002E031E039E042E0307B0B2800D0FFDF93 +:10239000288AADF834701230ADF8040084B10421FD +:1023A0002173A9896181E989E181298A2182688A69 +:1023B00000902B8A688804F11202696900F047FADC +:1023C0003AE0307B0C2800D0FFDF1220ADF804008B +:1023D000ADF834703CB305212173A4F80AB0A4F819 +:1023E0000EB0A4F810B027E00DA8009001AB224673 +:1023F00029463046FFF75CFA1EE00DA8009001ABBD +:10240000224629463046FFF7B6FB15E034E03B2173 +:10241000ADF80400ADF8341074B3A4F80690A4F835 +:1024200008B084F80AB007E0FFDF05E010000020E4 +:10243000297A012917D0FFDFBDF80400AAF80000AF +:102440006CB1BDF834002080BDF804006080BDF898 +:102450003400392803D03C2801D086F80CB011B0E4 +:102460000020BDE8F08F3C21ADF80400ADF8341039 +:1024700014B1697AA172DFE7AAF80000EFE72DE94D +:10248000F84356880F46804615460521304610F021 +:10249000B1FA040000D1FFDF123400943B464146FC +:1024A00030466A6809F0A3FFBAE570B50D4605210C +:1024B00010F0A0FA040000D1FFDF294604F1120059 +:1024C000BDE8704009F02DBE70B50D46052110F035 +:1024D00091FA040000D1FFDF294604F11200BDE8A3 +:1024E000704009F04BBE70B50546052110F082FA28 +:1024F000040000D1FFDF04F1080321462846BDE8AF +:1025000070400422B1E470B50546052110F072FA5E +:10251000040000D1FFDF214628462368BDE8704053 +:102520000522A2E470B50646052110F063FA040006 +:1025300000D1FFDF04F1120009F0E6FD401D20F09C +:10254000030511E0011D008803224318214630468F +:10255000FFF78BFC00280BD0607BABB2684382B2E4 +:102560006068011D10F003F9606841880029E9D115 +:1025700070BD70B50E46054606F0BEFF040000D1E2 +:10258000FFDF0120207266726580207820F00F0046 +:10259000C01C20F0F00030302070BDE8704006F024 +:1025A000AEBF2DE9F0438BB00D461446814606A917 +:1025B000FFF799FB002814D14FF6FF7601274FF45F +:1025C00020588CB103208DF800001020ADF81000C9 +:1025D00007A8059007AA204604A911F0B7FF78B113 +:1025E00007200BB0BDE8F0830820ADF808508DF847 +:1025F0000E708DF80000ADF80A60ADF80C800CE0AC +:102600000698A17801742188C1818DF80E70ADF80B +:102610000850ADF80C80ADF80A606A4602214846C1 +:10262000069BFFF789FBDCE708B501228DF8022045 +:1026300042F60202ADF800200A4603236946FFF77E +:102640003EFC08BD08B501228DF8022042F60302C7 +:10265000ADF800200A4604236946FFF730FC08BDA8 +:1026600000B587B079B102228DF800200A88ADF854 +:1026700008204988ADF80A1000236A460521FFF7B3 +:102680005BFB07B000BD1020FBE709B1072316E490 +:102690000720704770B588B00D461446064606A957 +:1026A000FFF721FB00280ED17CB10620ADF80850C1 +:1026B0008DF80000ADF80A40069B6A460821DC81CF +:1026C0003046FFF739FB08B070BD05208DF80000DB +:1026D000ADF80850F0E700B587B059B107238DF881 +:1026E0000030ADF80820039100236A460921FFF766 +:1026F00023FBC6E71020C4E770B588B00C46064639 +:10270000002506A9FFF7EFFA0028DCD10698012181 +:10271000123009F02BFD9CB12178062921D2DFE887 +:1027200001F0200505160318801E80B2C01EE28845 +:1027300080B20AB1A3681BB1824203D90C20C2E760 +:102740001020C0E7042904D0A08850B901E0062079 +:10275000B9E7012913D0022905D004291CD0052985 +:102760002AD00720AFE709208DF800006088ADF877 +:102770000800E088ADF80A00A068039023E00A2072 +:102780008DF800006088ADF80800E088ADF80A0018 +:10279000A0680A25039016E00B208DF800006088E1 +:1027A000ADF80800A088ADF80A00E088ADF80C008C +:1027B000A0680B25049006E00C208DF800006078DE +:1027C0008DF808000C256A4629463046069BFFF71F +:1027D000B3FA78E700B587B00D228DF80020ADF888 +:1027E000081000236A461946FFF7A6FA49E700B524 +:1027F00087B071B102228DF800200A88ADF8082058 +:102800004988ADF80A1000236A460621FFF794FABA +:1028100037E7102035E770B586B0064601200D4633 +:10282000ADF808108DF80000014600236A463046D6 +:10283000FFF782FA040008D12946304605F05EFC15 +:102840000021304605F078FC204606B070BDF8B592 +:102850001C4615460E46069F10F0FEF92346FF1D46 +:10286000BCB231462A4600940FF0E9FDF8BD30B401 +:102870001146DDE902423CB1032903D0002330BCFC +:1028800008F034BB0123FAE71A8030BC704770B5FA +:102890000C460546FFF72FFB2146284605F03DFC78 +:1028A0002846BDE87040012105F046BC4FF0E0220B +:1028B0004FF400400021C2F88001BFF34F8FBFF3F7 +:1028C0006F8F1748016001601649900208607047D9 +:1028D000134900B500220A600A60124B4FF0607283 +:1028E0001A60002808BF00BD0F4A104BDFF840C037 +:1028F00001280CD002281CBFFFDF00BD03200860A8 +:102900001A604FF4000000BFCCF8000000BD0220A8 +:1029100008601A604FF04070F6E700B5FFDF00BDB9 +:1029200000F5004008F50140A002002014F5004029 +:1029300004F5014070B50B2000F0BDF9082000F04F +:10294000BAF900210B2000F0D4F90021082000F092 +:10295000D0F9F44C01256560A5600020C4F8400161 +:10296000C4F84401C4F848010B2000F0B5F9082070 +:1029700000F0B2F90B2000F091F9256070BD10B5A0 +:102980000B2000F098F9082000F095F9E5480121A6 +:1029900041608160E4490A68002AFCD10021C0F846 +:1029A0004011C0F84411C0F848110B2000F094F910 +:1029B000BDE81040082000F08FB910B50B2000F0E2 +:1029C0008BF9BDE81040082000F086B900B530B1A1 +:1029D000012806D0022806D0FFDF002000BDD34822 +:1029E00000BDD34800BDD248001D00BD70B5D1491F +:1029F0004FF000400860D04DC00BC5F80803CF4829 +:102A000000240460C5F840410820C43500F053F9A3 +:102A1000C5F83C41CA48047070BD08B5C14A0021E0 +:102A200028B1012811D002281CD0FFDF08BD4FF4C7 +:102A30008030C2F80803C2F84803BB483C3001604C +:102A4000C2F84011BDE80840D0E74FF40030C2F8AA +:102A50000803C2F84803B44840300160C2F844118A +:102A6000B3480CE04FF48020C2F80803C2F84803D2 +:102A7000AD4844300160C2F84811AD48001D0068FF +:102A8000009008BD70B516460D460446022800D9D0 +:102A9000FFDF0022A348012304F110018B4000EB6B +:102AA0008401C1F8405526B1C1F84021C0F8043373 +:102AB00003E0C0F80833C1F84021C0F8443370BDCA +:102AC0002DE9F0411D46144630B1012833D00228CB +:102AD00038D0FFDFBDE8F081891E002221F07F4160 +:102AE0001046FFF7CFFF012D23D00020944D924FC9 +:102AF000012668703E61914900203C39086002203F +:102B0000091D08608D490420303908608B483D3428 +:102B1000046008206C6000F0DFF83004C7F804039C +:102B2000082000F0BBF88349F007091F08602E70E9 +:102B3000D0E70120DAE7012B02D00022012005E0D6 +:102B40000122FBE7012B04D000220220BDE8F04166 +:102B500098E70122F9E774480068704770B500F003 +:102B6000D8F8704C0546D4F840010026012809D158 +:102B7000D4F80803C00305D54FF48030C4F8080327 +:102B8000C4F84061D4F8440101280CD1D4F80803FA +:102B9000800308D54FF40030C4F80803C4F844613A +:102BA000012012F0A9FCD4F8480101280CD1D4F876 +:102BB0000803400308D54FF48020C4F80803C4F884 +:102BC0004861022012F098FC5E48056070BD70B547 +:102BD00000F09FF85A4D0446287850B1FFF706FFE1 +:102BE000687818B10020687012F086FC55480460BF +:102BF00070BD0320F8E74FF0E0214FF40010C1F85A +:102C000000027047152000F067B84B4901200861A9 +:102C1000082000F061B848494FF47C10C1F808035F +:102C20000020024601EB8003C3F84025C3F8402191 +:102C3000401CC0B20628F5D37047410A43F609523A +:102C40005143C0F3080010FB02F000F5807001EB67 +:102C50005020704710B5430B48F2376463431B0C98 +:102C60005C020C60384C03FB0400384B4CF2F72438 +:102C700043435B0D13FB04F404EB402000F580702C +:102C80004012107008681844086010BD2C48406855 +:102C9000704729490120C1F800027047002809DB6C +:102CA00000F01F02012191404009800000F1E02066 +:102CB000C0F80011704700280DDB00F01F02012151 +:102CC00091404009800000F1E020C0F88011BFF37E +:102CD0004F8FBFF36F8F7047002809DB00F01F0292 +:102CE000012191404009800000F1E020C0F88012ED +:102CF00070474907090E002804DB00F1E02080F846 +:102D00000014704700F00F0000F1E02080F8141D5F +:102D100070470C48001F00680A4A0D49121D1160D7 +:102D20007047000000B0004004B500404081004002 +:102D300044B1004008F5014000800040408500405B +:102D40003400002014050240F7C2FFFF6F0C0100A1 +:102D5000010000010A4810B5046809490948083112 +:102D6000086012F05DFC0648001D046010BD0649B5 +:102D7000002008604FF0E0210220C1F88002704777 +:102D80001005024001000001FC1F004010B50D209D +:102D900000F077F8C4B26FF0040000F072F8C0B22F +:102DA000844200D0FFDF3E490120086010BD70B5AD +:102DB0000D2000F048F83B4C0020C4F8000101252C +:102DC000C4F804530D2000F04FF825604FF0E021C7 +:102DD0006014C1F8000170BD10B50D2000F033F88B +:102DE0003048012141600021C0F80011BDE81040C9 +:102DF0000D2000F039B82C4810B504682A492B483A +:102E0000083108602749D1F80001012804D0FFDF0C +:102E10002548001D046010BD2148001D00680022E7 +:102E2000C0B2C1F8002113F047F8F1E710B51D4812 +:102E3000D0F800110029FBD0FFF7DDFFBDE81040FE +:102E40000D2000F011B800280DDB00F01F02012159 +:102E500091404009800000F1E020C0F88011BFF3EC +:102E60004F8FBFF36F8F7047002809DB00F01F0200 +:102E7000012191404009800000F1E020C0F880125B +:102E80007047002804DB00F1E02090F8000405E022 +:102E900000F00F0000F1E02090F8140D4009704799 +:102EA00004D5004000D000401005024001000001A0 +:102EB0004FF0E0214FF00070C1F8800101F5C071C2 +:102EC000BFF34F8FBFF36F8FC1F80001384B8022E3 +:102ED00083F8002441F8800C704700B502460420B6 +:102EE000344903E001EBC0031B792BB1401EC0B293 +:102EF000F8D2FFDFFF2000BD41F8302001EBC00118 +:102F000000224A718A7101220A7100BD294A0021FA +:102F100002EBC0000171704710B50446042800D3CD +:102F2000FFDF244800EBC4042079012800D0FFDF34 +:102F30006079A179401CC0B2814200D060714FF02D +:102F4000E0214FF00070C1F8000210BD2DE9F04102 +:102F500019480568184919480831086014480426BA +:102F600090F80004134F4009154C042818D0FFDFD7 +:102F700016E0217807EBC1000279012A08D14279D5 +:102F800083799A4204D04279827157F831008047A0 +:102F90002078401CC0B22070042801D3002020708B +:102FA000761EF6B2E5D20448001D0560BDE8F0814A +:102FB00019E000E0D80500201005024001000001E2 +:102FC000500000200548064A0168914201D10021C5 +:102FD000016004490120086070470000540000208F +:102FE000BEBAFECA40E5014070B50C46054609F080 +:102FF0009BFB21462846BDE870400AF080BC704724 +:103000002CFFFFFFDBE5B15100600200B600FFFFBF +:103010008C00000069915B00935FFEEDA0843C731F +:10302000F87462145E06C0CB72F2136030B5F84DCE +:103030000446062CA9780ED2DFE804F0030E0E0E2B +:103040000509FFDF08E0022906D0FFDF04E00329BD +:1030500002D0FFDF00E0FFDFAC7030BD30B50446CA +:103060001038EB4D07280CD2DFE800F0040C060CFA +:103070000C0C0C00FFDF05E0287E112802D0FFDFDA +:1030800000E0FFDF2C7630BD2DE9F04111F0C8FBE8 +:10309000044612F0A1FD201AC5B206200FF052FC22 +:1030A000044606200FF056FC211AD94C207E122827 +:1030B00018D000200F1807200FF044FC0646072008 +:1030C0000FF048FC301A3918207E13280CD000204D +:1030D0000144A078042809D000200844281AC0B26E +:1030E000BDE8F0810120E5E70120F1E70120F4E7E8 +:1030F000C74810B590F825004108C54800F12600E2 +:1031000005D00DF018FBBDE8104006F00BB80DF02F +:10311000F3FAF8E730B50446A1F120000D460A287D +:103120004AD2DFE800F005070C1C2328353A3F445B +:10313000FFDF42E0207820283FD1FFDF3DE0B448A8 +:103140008178052939D0007E122836D020782428AD +:1031500033D0252831D023282FD0FFDF2DE0207851 +:1031600022282AD0232828D8FFDF26E0207822280A +:1031700023D0FFDF21E0207822281ED024281CD075 +:1031800026281AD0272818D0292816D0FFDF14E0C7 +:103190002078252811D0FFDF0FE0207825280CD0DB +:1031A000FFDF0AE02078252807D0FFDF05E0207840 +:1031B000282802D0FFDF00E0FFDF257030BD1FB5FB +:1031C00004466A46002001F03CFEB4B1BDF802207E +:1031D0004FF6FF700621824201D1ADF80210BDF812 +:1031E0000420824201D1ADF80410BDF808108142DC +:1031F00003D14FF44860ADF8080068460EF014F9AA +:1032000005F090FF04B010BD70B514460D4606469B +:10321000FEF759F858B90DB1A54201D90C2070BD7F +:10322000002408E056F82400FEF74DF808B11020FD +:1032300070BD641CE4B2AC42F4D3002070BD2DE933 +:10324000F04105461F4690460E4600240068FEF7F2 +:1032500087F830B9A98828680844401EFEF780F82E +:1032600008B110203CE728680028A88802D0B8429E +:1032700002D850E00028F5D0092031E72968085D20 +:10328000B8B1671CCA5D152A2ED03CDC152A3AD28B +:10329000DFE802F03912222228282A2A313139396E +:1032A00039393939393939392200085D30BB641C64 +:1032B000A4B2A242F9D833E00228DDD1A01C085CF8 +:1032C00088F80000072801D2400701D40A2007E748 +:1032D000307840F0010015E0C143C90707E001283C +:1032E00007D010E00620FBE60107A1F1805100297C +:1032F000F5D01846F4E63078810701D50B20EFE6CB +:1033000040F0020030702868005D384484B2A8881C +:10331000A04202D2B0E74FF4485382B2A242ADD8E5 +:103320000020DDE610B5027843F2022354080122A2 +:10333000022C12D003DC3CB1012C16D106E0032C88 +:1033400010D07F2C11D112E0002011E080790324ED +:10335000B4EB901F09D10A700BE08079B2EB901F9B +:1033600003D1F8E780798009F5D0184610BDFF2019 +:103370000870002010BD08B500208DF8000024481A +:1033800090F82E1049B190F82F0002280ED0032893 +:103390000ED0FFDF9DF8000008BD1D4869462530AE +:1033A00001F09EFD0028F5D0FFDFF3E7032000E0E9 +:1033B00001208DF80000EDE738B50C46054669465A +:1033C00001F08EFD00280DD19DF80010207861F3EA +:1033D0004700207055F8010FC4F80100A888A4F830 +:1033E0000500002038BD38B51378A8B1022813D0E5 +:1033F000FF281AD007A46D46246800944C7905EB89 +:103400009414247864F347031370032809D00FE061 +:10341000EC0100200302FF0123F0FE0313700228D9 +:10342000F3D1D8B240F0010005E043F0FE00107087 +:10343000107820F0010010700868C2F80100888838 +:10344000A2F8050038BD02210FF0D4BA38B50C46F9 +:103450000978222901D2082038BDADF800008DF886 +:10346000022068460DF0A9F905F05CFE050003D1C5 +:1034700021212046FFF74EFE284638BD1CB500200E +:103480008DF80000CDF80100ADF80500FB4890F87C +:103490002E00022801D0012000E000208DF8070056 +:1034A00068460DF0FAFA002800D0FFDF1CBD0022AC +:1034B0000A80437892B263F3451222F040020A80F8 +:1034C00000780C282BD2DFE800F02A06090E11162E +:1034D000191C1F220C2742F0110009E042F01D00C8 +:1034E00008800020704742F0110012E042F0100006 +:1034F00040F00200F4E742F01000F1E742F0010072 +:10350000EEE742F0010004E042F00200E8E742F09A +:10351000020040F00400E3E742F00400E0E7072087 +:1035200070472DE9FF478AB00025BDF82C60824620 +:103530001C4691468DF81C50700703D56068FDF756 +:10354000C2FE68B9CD4F4FF0010897F82E0058B170 +:1035500097F82F00022807D16068FDF701FF18B126 +:1035600010200EB0BDE8F087300702D5A089802872 +:103570003ED8700705D4B9F1000F02D097F82400A7 +:10358000A0B3E07DC0F300108DF81B00627D072022 +:10359000032162B3012A2DD0022AE2D0042AE0D10D +:1035A0008DF81710F00628D4A27D07202AB3012A2F +:1035B00023D0022A24D0042AD3D18DF8191000BFB9 +:1035C0008DF81590606810B307A9FFF7ABFE0028CF +:1035D000C7D19DF81C00FF2816D0606850F8011F65 +:1035E000CDF80F108088ADF8130014E000E001E082 +:1035F0000720B6E78DF81780D4E78DF81980DFE74C +:1036000002208DF81900DBE743F20220A9E7CDF88C +:103610000F50ADF81350E07B40B9207C30B9607C8E +:1036200020B9A07C10B9E07CC00601D0062098E744 +:103630008DF800A0BDF82C00ADF80200A068019044 +:10364000A068029004F10F0001F03EFC8DF80C0020 +:10365000FFF791FE8DF80D009DF81C008DF80E000F +:103660008DF816508DF81850E07D08A900F00F0075 +:103670008DF81A0068460EF015F805F053FD70E756 +:10368000F0B58FB000258DF830508DF814508DF8BE +:10369000345006468DF828500195029503950495FF +:1036A00019B10FC901AC84E80F00744CA07805284B +:1036B00001D004280CD101986168884200D120B95A +:1036C0000398E168884203D110B108200FB0F0BD23 +:1036D000207DC00601D51F2700E0FF273B460DAA2D +:1036E00005A903A8FFF7ABFD0028EFD1A08AC10709 +:1036F00002D0C00600D4EE273B460AAA0CA901A8B6 +:10370000FFF79DFD0028E1D19DF81400C00701D00E +:103710000A20DBE7A08A410708D4A17D31B19DF8DA +:103720002810890702D043F20120CFE79DF8281026 +:10373000C90709D0400707D4208818B144F2506166 +:10374000884201D90720C1E78DF818508DF819601B +:10375000BDF80800ADF81A000198079006A80DF012 +:10376000BBFF05F0DFFC0028B0D18DF820508DF8AC +:103770002160BDF81000ADF822000398099008A858 +:103780000DF0C9FF05F0CEFC00289FD101AD241D2E +:1037900095E80F0084E80F00002097E770B586B029 +:1037A0000D46040005D0FDF7DBFD20B1102006B06A +:1037B00070BD0820FBE72078C107A98802D0FF2947 +:1037C00002D303E01F2901D20920F0E7800763D468 +:1037D000FFF75AFC38B12178C1F3C100012804D0A9 +:1037E000032802D005E01320E1E7244890F82400E4 +:1037F000C8B1C8074FF001064FF0000502D08DF8A0 +:103800000F6001E08DF80F50FFF7B5FD8DF8000057 +:1038100020786946C0F3C1008DF8010060788DF80A +:103820000250C20801D00720C1E730B3C20701D05F +:103830008DF80260820705D59DF8022042F0020251 +:103840008DF80220400705D59DF8020040F00400E5 +:103850008DF80200002022780B18C2F38002DA7083 +:1038600001EB40026388D380401CA388C0B253811F +:103870000228F0D3207A78B905E001E0EC010020BD +:103880008DF80260E6E7607A30B9A07A20B9E07A74 +:1038900010B9207BC00601D0062088E704F108009B +:1038A00001F012FB8DF80E0068460DF0BFFA05F02E +:1038B00039FC002889D18DF810608DF81150E0880E +:1038C000ADF81200ADF8145004A80DF002FB05F09D +:1038D00029FC002888D12078C00701D0152000E0FD +:1038E0001320FFF7BBFB002061E72DE9FF47022013 +:1038F000FB4E8DF804000027708EADF80600B84628 +:1039000043F202094CE001A80EF0DBFF050006D0EF +:10391000708EA8B3A6F83280ADF806803EE0039C16 +:10392000A07F01072DD504F124000090A28EBDF8E0 +:103930000800214604F1360301F05FFC050005D0C4 +:103940004D452AD0112D3CD0FFDF3AE0A07F20F07A +:103950000801E07F420862F3C711A177810861F393 +:103960000000E07794F8210000F01F0084F82000A8 +:103970002078282826D129212046FFF7CBFB21E0FB +:1039800014E040070AD5BDF8080004F10E0101F06B +:10399000B1FA05000DD04D4510D100257F1CFFB2B6 +:1039A00002200EF0CFFF401CB842ACD8052D11D03C +:1039B00008E0A07F20F00400A07703E0112D00D0E4 +:1039C000FFDF0025BDF806007086052D04D02846CF +:1039D00004B0C7E5A6F832800020F9E770B50646C6 +:1039E000FFF731FD054605F087FD040000D1FFDF3C +:1039F0006680207820F00F00801C20F0F00020303E +:103A000020700320207295F83E006072BDE870407F +:103A100005F075BD2DE9F04786B0040000D1FFDF49 +:103A20002078AF4D20F00F00801C20F0F0007030A7 +:103A3000207060680178491F1B2933D2DFE801F04C +:103A4000FE32323255FD320EFDFD42FC323232780A +:103A5000FCFCFBFA3232FCFCF9F8FC00C68830466C +:103A6000FFF7F1FC0546304607F03EF9E0B160682B +:103A7000007A85F83E0021212846FFF74BFB3046AF +:103A8000FEF753FB304603F05BFE3146012012F097 +:103A9000D3FCA87F20F01000A877FFF726FF0028AE +:103AA00000D0FFDF06B05DE5207820F0F000203088 +:103AB00020700320207266806068007A607205F0D2 +:103AC0001EFDD8E7C5882846FFF7BDFC00B9FFDF1B +:103AD00060680079012800D0FFDF6068017A06B0D5 +:103AE0002846BDE8F04707F0DEBCC6883046FFF741 +:103AF000AAFC050000D1FFDF05F001FD606831463A +:103B00000089288160684089688160688089A8810F +:103B1000012012F091FC0020A875A87F00F003009E +:103B20000228BFD1FFF7E1FE0028BBD0FFDFB9E7D5 +:103B300000790228B6D000B1FFDF05F0E0FC66682E +:103B4000B6F806A0307A361D012806D0687E814678 +:103B500005F054FA070003D101E0E878F7E7FFDF4A +:103B60000022022150460EF03CFF040000D1FFDF8E +:103B700022212046FFF7CEFA3079012800D002201A +:103B8000A17F804668F30101A177308B2081708B83 +:103B90006081B08BA08184F822908DF80880B8688D +:103BA0000090F86801906A46032150460EF019FF14 +:103BB00000B9FFDFB888ADF81000B8788DF81200B2 +:103BC00004AA052150460EF00CFF00B9FFDFB888AB +:103BD000ADF80C00F8788DF80E0003AA04215046C9 +:103BE0000EF0FFFE00B9FFDF062106F1120001F022 +:103BF0009FF940B37079800700D5FFDF7179E07DD0 +:103C000061F34700E075D6F80600A0617089A083D3 +:103C1000062106F10C0001F08BF9F0B195F82500B2 +:103C20004108607861F347006070D5F8260006E02F +:103C30003EE036E06DE055E04AE02CE040E0C4F8BC +:103C40000200688D12E0E07D20F0FE00801CE0752F +:103C5000D6F81200A061F08AD9E7607820F0FE0063 +:103C6000801C6070F068C4F80200308AE080B8F10F +:103C7000010F04D0B8F1020F05D0FFDF12E70320D7 +:103C8000FFF7D4F90EE7287E122800D0FFDF1120BD +:103C9000FFF7E4F906E706B02046BDE8F04701F07B +:103CA00035BD05F02CFC15F8300F40F0020005E0A2 +:103CB00005F025FC15F8300F40F004002870F1E6FF +:103CC000287E132809D01528D8D11620FFF7C6F969 +:103CD00006B0BDE8F04705F012BC1420F6E700007E +:103CE000EC010020A978052909D00429C6D105F0E6 +:103CF00006FC022006B0BDE8F047FFF797B900794F +:103D00000028BBD0E87801F0C6F805F0F8FB0320E6 +:103D1000F0E7287E122802D1687E01F0BCF811205D +:103D2000D4E72DE9F047054600784FF00008000978 +:103D3000DFF8C0A891460C464646012875D00228F7 +:103D400074D007280AD00A2871D0FFDFA9F80060D4 +:103D500014B1A4F800806680002003E4696801279C +:103D600004F108000A784FF0020C4FF6FF73172A8F +:103D70007ED00EDC142A32D006DC052A68D0092A4F +:103D800010D0102A75D120E0152A73D0162AF9D147 +:103D9000F8E0183A082A6CD2DFE802F0F36B6B0AFD +:103DA000CAF2DFF1C8884FF01208102621468DE1D3 +:103DB0004FF01C080A26BCB38888A0806868807908 +:103DC00020726868C0796072C0E74FF01B08142643 +:103DD00054B30320207268688088A080B6E70A790F +:103DE0003C2AB3D00D1D4FF010082C26E4B1698891 +:103DF000A180298B6182298B2182698BA182A98B69 +:103E0000E1826B790246A91D1846FFF7ECFA297981 +:103E1000002001290CD084F80FC0FF212176E06139 +:103E200020626062A06291E70FE02EE151E18CE137 +:103E3000E77320760AF1040090E80E00DAF810002B +:103E4000C4E90930C4E9071280E7A9F8006083E7F4 +:103E50002C264FF01D08002CF7D00546A380887B48 +:103E60002A880F1D60F300022A80887B400802E048 +:103E70009DE007E1BEE060F341022A80887B800874 +:103E800060F382022A80887BB91CC00860F3C302F9 +:103E90002A80B87A0011401C60F3041202F07F00FF +:103EA00028807878AA1CFFF79EFA387D05F1090270 +:103EB00007F11501FFF797FA387B01F048F82874ED +:103EC000787B01F044F86874F87EA874787AE87416 +:103ED000387F2875B87B6875388AE882DAF81C0064 +:103EE000A861B87A524697F808A0C0F34111012999 +:103EF00004D0108C504503D2824609E0FFDF10E069 +:103F0000022903D0288820F0600009E0504504D140 +:103F1000288820F06000403002E0288840F06000EF +:103F20002880A4F824A0524607F11D01A86996E054 +:103F300011264FF02008002C87D0A380686804F178 +:103F40000A02007920726868007B607269688B1DC4 +:103F500048791946FFF747FAF8E60A264FF0210894 +:103F6000002CE9D08888A080686880792072686811 +:103F7000C07960729AF8301021F004019FE065E08A +:103F80004CE06FE00B264FF02208002CD4D0C888FC +:103F9000A0806868007920726868007A00F0D7FF16 +:103FA00060726868407A00F0D2FFA072CEE61C26EC +:103FB0004FF02608002CBFD0A3806868407960725B +:103FC0006868007AA0720AF1040090E80E00DAF83E +:103FD0001000C4E90530C4E90312686800793C2880 +:103FE00003D0432803D0FFDFB0E62772AEE684F8A3 +:103FF00008C0ABE610264FF02408002C9CD088881F +:10400000A0806868807920816868807A60816868AB +:104010000089A08168688089E08197E610264FF0CA +:104020002308002C88D08888A0806868C0882081F8 +:1040300068680089608168684089A08168688089B3 +:10404000E0819AF8301021F0020138E030264FF07C +:104050002508002C85D0A38069682822496821F0B2 +:104060008DFA73E614264FF01B08002C8ED0A38027 +:10407000686800790128BAD02772DAE90710C4E924 +:10408000031063E64A46214660E0287A012803D0FF +:10409000022817D0FFDF59E610264FF01F08002C2A +:1040A00089D06888A080A8892081E8896081288AD1 +:1040B000A081688AE0819AF8301021F001018AF825 +:1040C000301043E64FF012081026688800F01DFFFC +:1040D0003CE6287AC8B3012838D0022836D0032815 +:1040E00001D0FFDF32E609264FF01108002C85D001 +:1040F0006F883846FFF7A7F990F822A0A780687A62 +:104100002072042138460EF087FC052138460EF057 +:1041100083FC002138460EF07FFC012138460EF06A +:104120007BFC032138460EF077FC022138460EF066 +:1041300073FC062138460EF06FFC072138460EF05E +:104140006BFC504600F0A7FE00E6FFE72846BDE8FE +:10415000F04701F065BC70B5012803D0052800D0F8 +:10416000FFDF70BD8DB22846FFF76DF9040000D166 +:10417000FFDF20782128F4D005F0BEF980B1017866 +:1041800021F00F01891C21F0F00110310170022192 +:10419000017245800020A075BDE8704005F0AFB900 +:1041A00021462846BDE870401322FFF74FB92DE99C +:1041B000F04116460C00804600D1FFDF307820F039 +:1041C0000F00801C20F0F0001030307020780128A3 +:1041D00004D0022818D0FFDFBDE8F0814046FFF789 +:1041E00032F9050000D1FFDF0320A87505F087F93B +:1041F00094E80F00083686E80F00FE4810F8301FDC +:1042000041F001010170E7E74046FFF71CF90500A6 +:1042100000D1FFDFA1884FF6FF700027814202D155 +:10422000E288824203D0814201D1E08840B105F0AA +:1042300066F994E80F00083686E80F00AF75CBE703 +:10424000A87D0128C8D178230022414612F04AF8FF +:104250000220A875C0E738B505460C460846FDF7AC +:1042600032F818BB203D062D4AD2DFE805F0031BCB +:10427000373C42300021052012F0B4F808B111207B +:1042800038BDA01C0DF023F904F04CFF050038D117 +:10429000002208231146052012F024F8052830D00A +:1042A000FFDF2EE06068FDF752F808B1102038BD3E +:1042B000618820886A460DF0C5FB04F033FF0500D5 +:1042C0001FD16068E8B1BDF80010018019E0A07846 +:1042D00000F0010120880DF0E6FB0EE0206801F0FF +:1042E0004BFE05460DE0207800F001000CF0EDF9E2 +:1042F00003E0618820880DF020FB04F013FFF0E755 +:104300000725284638BD70B505460C460846FDF71A +:1043100000F808B1102070BD202D07D0212D0DD040 +:10432000222D0BD0252D09D0072070BD2088A11C7F +:104330000CF0A0FABDE8704004F0F4BE062070BD99 +:10434000AC482530704708B53421AA4821F0B7F9A8 +:104350000120FEF76BFE1120FEF780FEA54968469E +:10436000263105F05FF8A3489DF8002010F8251FBE +:1043700062F3470121F001010170002141724FF405 +:104380006171A0F8071002218172FEF7B1FE00B141 +:10439000FFDFFDF75DF801F084F908BD10B50C46AC +:1043A0004021204621F069F9A07F20F00300A0778A +:1043B000202020700020A07584F8230010BD7047D5 +:1043C0002DE9FC410746FCF77EFF10B11020BDE847 +:1043D000FC81884E06F12501D6F825000090B6F83C +:1043E0002950ADF8045096F82B408DF80640384619 +:1043F000FEF7E2FF0028EAD1FEF77AFE0028E6D0B9 +:10440000009946F8251FB580B471E0E710B5044661 +:10441000FCF77FFF08B1102010BD76487549224691 +:1044200090F8250026314008FEF7DDFF002010BD82 +:104430003EB504460D460846FCF76BFF08B1102058 +:104440003EBD14B143F204003EBD6A4880780528A1 +:1044500003D0042801D008203EBD694602A80AF016 +:10446000AEFA2A4669469DF80800FEF7BCFF002018 +:104470003EBDFEB50D4604004FF0000711D00822E6 +:10448000FEF7C2FE002811D1002608E054F82600ED +:104490006946FEF747FF002808D1761CF6B2AE4207 +:1044A000F4D30CF059F810B143F20320FEBD514E85 +:1044B00086F824700CB300271BE000BF54F82700D7 +:1044C00002A9FEF72FFF00B1FFDF9DF808008DF86D +:1044D000000054F8270050F8011FCDF80110808823 +:1044E000ADF8050068460CF05CF800B1FFDF7F1CFA +:1044F000FFB2AF42E2D386F824500020FEBD2DE982 +:10450000F0478AB01546894604001ED00F4608229F +:104510002946FEF779FE002811D1002613E000BFDE +:1045200054F826006946103000F0DAFC002806D165 +:104530003FB157F82600FCF7C6FE10B110200AB0B4 +:104540000BE4761CF6B2AE42EAD30026A5F10108D0 +:104550001CE000BF06F1010A0AF0FF0712E000BFED +:1045600054F82600017C4A0854F827100B7CB2EB63 +:10457000530F05D106221130113120F0D3FF58B16D +:104580007F1CFFB2AF42EBD30AF0FF064645E1DBEA +:104590004E4624B1012003E043F20520CFE700207E +:1045A0000CF024F810B90CF02DF810B143F20420EF +:1045B000C5E774B300270DF1170828E054F8270069 +:1045C0006946103000F08CFC00B1FFDF54F8270082 +:1045D000102250F8111FCDF801108088ADF80500A9 +:1045E00054F827100DF1070020F0C8FFAEB156F8BF +:1045F000271001E0EC0100201022404620F0BEFF11 +:1046000068460BF0B3FF00B1FFDF7F1CFFB2AF4283 +:10461000D4D3FEF733FF002091E7404601F0A0FC21 +:10462000EEE730B585B00446FCF74DFE18B960687A +:10463000FCF796FE10B1102005B030BD60884AF23C +:10464000B811884206D82078F84D28B1012806D044 +:10465000022804D00720EFE7FEF74AFD18E0607853 +:10466000022804D0032802D043F20220E4E785F8B0 +:104670002F00C1B200200090ADF8040002292CD018 +:10468000032927D0FFDF68460CF055F804F04AFDF7 +:104690000028D1D1606801F056FC207858B1012083 +:1046A0008DF800000DF1010001F05AFC68460DF094 +:1046B0005EFA00B1FFDF207885F82E00FEF7DEFEFF +:1046C000608860B1A88580B20BF088FF00B1FFDF81 +:1046D0000020B1E78DF80500D5E74020FAE74FF458 +:1046E0006170EFE710B50446FCF713FE20B960686F +:1046F00038B1FCF72CFE08B1102010BD606801F045 +:104700002FFCCA4830F82C1F6180C1786170807816 +:104710002070002010BD2DE9F84314468946064656 +:10472000FCF7F7FDA0B94846FCF71AFE80B9204611 +:10473000FCF716FE60B9BD4DA878012800D13CB148 +:104740003178FF2906D049B143F20400BDE8F8836F +:104750001020FBE7012801D00420F7E7CCB305289F +:1047600011D004280FD069462046FEF7A0FE00288D +:10477000ECD1217D49B1012909D0022909D00329B1 +:1047800009D00720E2E70820E0E7024604E0012222 +:1047900002E0022200E00322804623461746002062 +:1047A0000099FEF7BEFE0028D0D1A0892880A07B0A +:1047B000E875BDF80000A882AF75BDF800100907C4 +:1047C00001D5A18931B1A1892980C00704D0032076 +:1047D00003E006E08021F7E70220FEF727FC86F8D9 +:1047E00000804946BDE8F8430020FEF749BF7CB58C +:1047F0008E4C05460E46A078022803D0032801D02F +:1048000008207CBD15B143F204007CBD07200EF0EA +:10481000A1F810B9A078032806D0FEF735FC28B11E +:10482000A078032804D009E012207CBD13207CBDB1 +:10483000304600F013FB0028F9D1E670FEF79BFD2F +:1048400009F0FAFF01208DF800008DF801008DF8C5 +:1048500002502088ADF80400E07D8DF8060068461F +:104860000DF02EF804F05EFC0028E0D1A0780328BB +:1048700004D00420FEF7DAFB00207CBDE07800F0D5 +:10488000FDFA0520F6E71CB510B143F204001CBD8B +:10489000664CA078042803D0052801D008201CBD50 +:1048A00000208DF8000001218DF801108DF8020024 +:1048B00068460DF005F804F035FC0028EFD1A0782B +:1048C000052805D05FF00200FEF7B0FB00201CBDFC +:1048D000E07800F0E0FA0320F6E72DE9FC4180469D +:1048E0000E4603250846FCF73BFD002866D14046EE +:1048F000FEF7A9FD040004D02078222804D2082065 +:1049000065E543F2020062E5A07F00F003073EB1D7 +:10491000012F0CD000203146FEF751FC0500EFD1ED +:10492000012F06D0022F1AD0FFDF28464FE50120C5 +:10493000F1E7A07D3146022801D011B107E0112036 +:1049400045E56846FCF791FE0028D9D16946404606 +:1049500006F06CFD0500E8D10120A075E5E7A07D1B +:10496000032804D1314890F83000C00701D02EB39D +:104970000EE026B1A07F40071ED4002100E00121F7 +:10498000404606F073FD0500CFD1A075002ECCD0B7 +:104990003146404600F0AEFA05461128C5D1A07F49 +:1049A0004107C2D4316844F80E1F7168616040F05D +:1049B000040020740025B8E71125B6E7102006E5AD +:1049C00070B50C460546FEF73EFD010005D02246B7 +:1049D0002846BDE87040FEF739BD43F2020070BDC5 +:1049E00010B5012807D1114B9B78012B00D011B1D4 +:1049F00043F2040010BD0BF023FEBDE8104004F0AC +:104A000091BB012300F051BA00231A46194600F069 +:104A10004CBA70B506460C460846FCF754FC18B96B +:104A20002068FCF776FC18B1102070BDEC01002066 +:104A3000F84D2A7E112A04D0132A00D33EB1082053 +:104A4000F3E721463046FEF7A9FE60B1EDE7092005 +:104A5000132A0DD0142A0BD0A188FF29E5D31520E5 +:104A6000FEF7FCFA0020D4E90012C5E90712DCE7E2 +:104A7000A1881F29D9D31320F2E71CB5E548007E91 +:104A8000132801D208201CBD00208DF800006846C4 +:104A90000CF01FFA04F046FB0028F4D11120FEF7B9 +:104AA000DDFA00201CBD2DE9F04FDFF868A3814638 +:104AB00091B09AF818009B4615460C46132803D36C +:104AC000FFF7DBFF00281FD12046FCF7FCFBE8BB0B +:104AD0002846FCF7F8FBC8BB20784FF00107C00759 +:104AE0004FF0000102D08DF83A7001E08DF83A10D5 +:104AF00020788846C0F3C1008DF8000060788DF8FA +:104B00000910C10803D0072011B0BDE8F08FB0B381 +:104B1000C10701D08DF80970810705D59DF80910EE +:104B200041F002018DF80910400705D59DF80900F4 +:104B300040F004008DF809009DF80900810703D5B5 +:104B400040F001008DF80900002000E015E06E46FD +:104B500006EB400162884A81401CA288C0B20A82EA +:104B60000328F5D32078C0F3C100012825D00328FD +:104B700023D04846FCF7A7FB28B11020C4E7FFE785 +:104B80008DF80970D8E799F80000400808D001288E +:104B900009D0022807D0032805D043F20220B3E74A +:104BA0008DF8028001E08DF80270484650F8011F30 +:104BB000CDF803108088ADF80700FEF7DCFB8DF818 +:104BC00001000021424606EB41002B88C3826B881E +:104BD0008383AB884384EB880385491CC285C9B2B3 +:104BE00082860329EFD3E088ADF83C0068460CF0DC +:104BF000B5FA002887D19AF818005546112801D037 +:104C0000082081E706200DF0A5FE38B12078C0F31A +:104C1000C100012804D0032802D006E0122073E767 +:104C200095F8240000283FF46EAFFEF72DFA022815 +:104C300001D2132068E7584600F010F900289DD1F2 +:104C400085F819B068460CF0C9FB04F06BFA040053 +:104C500094D1687E00F012F91220FEF7FFF9204689 +:104C600052E770B56B4D287E122801D00820DCE693 +:104C70000CF0B7FB04F056FA040005D1687E00F092 +:104C80000AF91120FEF7EAF92046CEE670B506468D +:104C900015460C460846FCF73CFB18B92846FCF7BD +:104CA00038FB08B11020C0E62A46214630460CF0F9 +:104CB000A9FE04F037FA0028F5D121787F29F2D136 +:104CC0000520B2E67CB505460C460846FCF7FBFA23 +:104CD00008B110207CBD2846FEF7B5FB20B1007856 +:104CE000222804D208207CBD43F202007CBD494842 +:104CF00090F83000400701D511207CBD2078C00815 +:104D000002D16078C00801D007207CBDADF800500A +:104D100020788DF8020060788DF803000220ADF84D +:104D2000040068460BF0B6FF04F0FCF97CBD70B5DA +:104D300086B014460D460646FEF785FB28B100787E +:104D4000222805D2082006B06FE643F20200FAE7F7 +:104D50002846FCF705FB20B944B12046FCF7F7FADA +:104D600008B11020EFE700202060A080294890F8CB +:104D70003000800701D51120E5E703A930460BF08C +:104D8000CCFD10B104F0CEF9DDE7ADF80060BDF860 +:104D90001400ADF80200BDF81600ADF80400BDF82F +:104DA0001000BDF81210ADF80600ADF808107DB186 +:104DB000298809B1ADF80610698809B1ADF802106B +:104DC000A98809B1ADF80810E98809B1ADF8041057 +:104DD000DCB1BDF80610814201D9081A2080BDF867 +:104DE0000210BDF81400814201D9081A6080BDF894 +:104DF0000800BDF80410BDF816200144BDF81200EB +:104E00001044814201D9081AA08068460BF044FE84 +:104E1000B8E70000EC0100201CB554490968CDE951 +:104E2000001068460CF09CF904F07CF91CBD1CB520 +:104E300000200090019068460CF092F904F072F99D +:104E40001CBD10800888508048889080C8881081D8 +:104E50008888D080002050819081704710B504462A +:104E600004F0CCF830B1407830B1204604F0EBFBD0 +:104E7000002010BD052010BD122010BD10B504F09B +:104E8000BDF8040000D1FFDF607800B9FFDF607873 +:104E9000401E607010BD10B504F0B0F8040000D1E1 +:104EA000FFDF6078401C607010BD1CB5ADF80000DD +:104EB0008DF802308DF803108DF8042068460CF050 +:104EC00064FD04F02FF91CBD0CB529A2D2E9001233 +:104ED000CDE900120079694601EB501000780CBD55 +:104EE0000278520804D0012A02D043F2022070470F +:104EF000FEF718BA1FB56A46FFF7A3FF68460CF025 +:104F0000A3FA04F00FF904B010BD70B50C0006460A +:104F10000DD0FEF798FA050000D1FFDFA6802889A2 +:104F20002081288960816889A081A889E0817CE549 +:104F300010B500231A4603E0845C2343521CD2B20E +:104F40008A42F9D30BB1002010BD012010BD00B57D +:104F500040B1012805D0022803D0032804D0FFDF88 +:104F6000002000BDFF2000BD042000BD645A0200E7 +:104F7000070605040302010010B50446FCF7A3F977 +:104F800008B1102010BD2078C0F30210042807D803 +:104F90006078072804D3A178102901D8814201D272 +:104FA000072010BDE078410706D421794A0703D4D1 +:104FB000000701D4080701D5062010BD002010BD50 +:104FC00010B513785C08837F64F3C7138377137875 +:104FD0009C08C37F64F30003C3771078C309487843 +:104FE00063F34100487013781C090B7864F347138E +:104FF0000B701378DB0863F3000048705078487139 +:1050000010BD10B5C4780B7864F300030B70C4783E +:10501000640864F341030B70C478A40864F382034A +:105020000B70C478E40864F3C3030B700379117840 +:1050300063F30001117003795B0863F341011170A0 +:1050400003799B0863F3820111700079C00860F353 +:10505000C301117010BD70B514460D46064604F02C +:105060004BFA80B10178182221F00F01891C21F040 +:10507000F001A03100F8081B214620F0C4FABDE879 +:10508000704004F03CBA29463046BDE87040132217 +:10509000FEF7DCB92DE9F047064608A8894690E8F6 +:1050A00030041F4690461421284620F008FB0021BA +:1050B000CAF80010B8F1000F03D0B9F1000F03D106 +:1050C00014E03878C00711D02068FCF722F9C0BB83 +:1050D000B8F1000F07D12068123028602068143022 +:1050E00068602068A8602168CAF8001038788007D6 +:1050F00024D56068FCF72BF918BBB9F1000F21D05B +:10510000FFF71EF90168C6F868118188A6F86C11CE +:10511000807986F86E0101F0F8FCF94FEF60626863 +:1051200062B196F8680106F2691140081032FEF784 +:105130005AF910223946606820F020FA0020BDE8B4 +:10514000F08706E0606820B1E8606068C6F8640136 +:10515000F4E71020F3E730B5054608780C4620F058 +:105160000F00401C20F0F001103121700020607011 +:1051700095F8230030B104280FD0052811D0062857 +:1051800014D0FFDF20780121B1EB101F04D295F875 +:10519000200000F01F00607030BD21F0F0002030D2 +:1051A00002E021F0F00030302070EBE721F0F00059 +:1051B0004030F9E7F0B591B0022715460C46064697 +:1051C0003A46ADF80870092103AB05F004F80490E5 +:1051D000002810D004208DF804008DF80170E03410 +:1051E000099605948DF818500AA968460FF0F2F850 +:1051F00000B1FFDF012011B0F0BD10B588B00C4642 +:105200000A99ADF80000C3B11868CDF802005868DB +:10521000CDF80600ADF80A20102203A820F0AEF960 +:1052200068460CF081F903F07DFF002803D1A17FCF +:1052300041F01001A17708B010BD0020CDF80200A8 +:10524000E6E72DE9F84F0646808A0D4680B2824691 +:10525000FEF7F9F804463078DFF8A48200274FF013 +:105260000209A8F120080F2870D2DFE800F06FF2E1 +:105270003708387D8CC8F1F0EFF35FF3F300A07FBF +:1052800000F00300022809D05FF0000080F0010167 +:1052900050460DF0AFFB050003D101E00120F5E71A +:1052A000FFDF98F85C10C90702D0D8F860000BE067 +:1052B000032105F11D0010F0E0FDD5F81D00914916 +:1052C000B0FBF1F201FB1200C5F81D0070686867C1 +:1052D000B068A8672078252800D0FFDFCAE0A07F4B +:1052E00000F00300022809D05FF0000080F0010107 +:1052F00050460DF07FFB060003D101E00120F5E7E9 +:10530000FFDF3078810702D52178252904D040F0CD +:1053100001003070BDE8F88F85F80090307F28716B +:1053200006F11D002D36C5E90206F3E7A07F00F067 +:105330000300022808D0002080F0010150460DF043 +:1053400059FB040004D102E00120F5E7A7E1FFDFEB +:105350002078C10604D5072028703D346C60D9E759 +:1053600040F008002070D5E7E07F000700D5FFDFA0 +:10537000307CB28800F0010301B05046BDE8F04F28 +:10538000092105F0B3BD04B9FFDF716821B1102216 +:1053900004F1240020F0F2F828212046FDF7BAFE9F +:1053A000A07F00F0030002280ED104F124000023A6 +:1053B00000901A4621465046FFF71FFF112807D0DC +:1053C00029212046FDF7A6FE307A84F82000A1E7C7 +:1053D000A07F000700D5FFDF14F81E0F40F0080083 +:1053E0002070E782A761E761C109607861F341003D +:1053F000014660F382016170307AE0708AE7A07F35 +:1054000000F00300022809D05FF0000080F00101E5 +:1054100050460DF0EFFA040003D101E00120F5E75A +:10542000FFDF022104F1850010F027FD0420287021 +:1054300004F5B4706860B4F88500288230481038EC +:105440007C346C61C5E9028064E703E024E15BE041 +:105450002DE015E0A07F00F00300022807D0002017 +:1054600080F0010150460DF0C5FA18B901E00120A5 +:10547000F6E7FFDF324621465046BDE8F84FEAE541 +:1054800004B9FFDF20782128A1D93079012803D180 +:10549000E07F40F00800E077324621465046FFF7B3 +:1054A000DAFD2046BDE8F84F2321FDF733BE3279FF +:1054B000AA8005F108030921504604F08CFEE8603B +:1054C00010B10520287025E7A07F00F00300022816 +:1054D00008D0002080F0010150460DF08BFA040046 +:1054E00003D101E00120F5E7FFDF04F162010223AF +:1054F0001022081F0DF005F907703179417009E796 +:105500004C02002040420F00A07F00F00300022860 +:1055100008D0002080F0010150460DF06BFA050024 +:1055200003D101E00120F5E7FFDF95F8840000F0EA +:10553000030001287AD1A07F00F00307E07F10F07C +:10554000010602D0022F04D133E095F8A000C00775 +:105550002BD0D5F8601121B395F88320087C62F335 +:1055600087000874A17FCA09D5F8601162F3410071 +:105570000874D5F8601166F300000874AEB1D5F870 +:105580006001102204F1240188351FF0F7FF287E06 +:1055900040F001002876287820F0010005F88809FD +:1055A00000E016B1022F04D02DE095F88800C00766 +:1055B00027D0D5F85C1121B395F88320087C62F3DD +:1055C00087000874A17FCA09D5F85C1162F3410015 +:1055D0000874D5F85C1166F3000008748EB1D5F834 +:1055E0005C01102204F1240188351FF0C7FF2878E0 +:1055F00040F0010005F8180B287820F0010005F8AC +:10560000A009022F44D0002000EB400005EBC000B1 +:1056100090F88800800709D595F87C00D5F86421BA +:10562000400805F17D011032FDF7DDFE8DF8009098 +:1056300095F884006A4600F003008DF8010095F8A3 +:1056400088108DF8021095F8A0008DF8030021460F +:10565000504601F043FA2078252805D0212807D0AC +:10566000FFDF2078222803D922212046FDF752FDB2 +:10567000A07F00F0030002280CD0002080F0010180 +:1056800050460DF0C9F900283FF44FAEFFDF41E668 +:105690000120B9E70120F1E7706847703AE6FFDFC3 +:1056A00038E670B5FE4C002584F85C5025660EF097 +:1056B0005EFE04F11001204603F0DAFE84F830505B +:1056C00070BD70B50D46FDF7BEFE040000D1FFDFD2 +:1056D0004FF4B87128461FF0F2FF04F1240028614E +:1056E000A07F00F00300022808D0012105F1E000AE +:1056F0000EF03EFE002800D0FFDF70BD0221F5E76E +:105700000A46014602F1E0000EF052BE70B50546B1 +:10571000406886B001780A2906D00D2933D00E29B9 +:105720002FD0FFDF06B070BD86883046FDF78BFEB8 +:10573000040000D1FFDF20782128F3D028281BD1D6 +:10574000686802210E3001F0BEF9A8B1686808212E +:10575000801D01F0B8F978B104F1240130460CF055 +:10576000B1F803F0DFFC00B1FFDF06B02046BDE872 +:1057700070402921FDF7CEBC06B0BDE8704003F0B3 +:10578000BEBE012101726868C6883046FDF75BFE27 +:10579000040000D1FFDFA07F00F00301022902D145 +:1057A00020F01000A077207821280AD06868017ABC +:1057B00009B1007980B1A07F00F00300022862D017 +:1057C000FFDFA07F00F003000228ABD1FEF78DF8C9 +:1057D0000028A7D0FFDFA5E703F091FEA17F080610 +:1057E0002BD5E07FC00705D094F8200000F01F0003 +:1057F000102820D05FF0050084F8230020782928A5 +:105800001DD02428DDD13146042010F015FE2221C0 +:105810002046FDF77FFCA07F00F00300022830D077 +:105820005FF0000080F0010130460DF0F5F800282F +:10583000C7D0FFDFC5E70620DEE70420DCE701F084 +:105840000300022808D0002080F0010130460DF04E +:10585000D1F8050003D101E00120F5E7FFDF2521A4 +:105860002046FDF757FC03208DF80000694605F13E +:10587000E0000EF094FD0228A3D00028A1D0FFDFA5 +:105880009FE70120CEE703F03AFE9AE72DE9F043C7 +:1058900087B09946164688460746FDF7D4FD0400B2 +:1058A0004BD02078222848D3232846D0E07F000719 +:1058B00043D4A07F00F00300022809D05FF000006D +:1058C00080F0010138460DF095F8050002D00CE09B +:1058D0000120F5E7A07F00F00300022805D0012198 +:1058E000002238460DF07DF805466946284601F04D +:1058F0001CF9009800B9FFDF45B10098E03505615B +:105900002078222806D0242804D007E0009900201F +:10591000086103E025212046FDF7FCFB00980121EA +:1059200041704762868001A9C0E902890EF052FDEC +:10593000022802D0002800D0FFDF07B0BDE8F083C6 +:1059400070B586B00546FDF77EFD017822291ED987 +:10595000807F00F00300022808D0002080F00101C1 +:1059600028460DF047F804002FD101E00120F5E7AB +:10597000FFDF2AE0B4F85E0004F1620630440178EB +:10598000427829B121462846FFF714FCB0B9C9E690 +:10599000ADF804200921284602AB04F01CFC03905A +:1059A0000028F4D005208DF80000694604F1E000DD +:1059B0000EF0F5FC022801D000B1FFDF0223102217 +:1059C000314604F15E000CF0D2FEB4F8600000280D +:1059D000D0D1A7E610B586B00446FDF734FD0178B6 +:1059E00022291BD9807F00F00300022808D0002064 +:1059F00080F0010120460CF0FDFF040003D101E01E +:105A00000120F5E7FFDF06208DF80000694604F16C +:105A1000E0000EF0C4FC002800D0FFDF06B010BD8F +:105A20002DE9F05F05460C460027007890460109F5 +:105A30003E4604F1080BBA4602297DD0072902D060 +:105A40000A2909D146E0686801780A2905D00D299C +:105A500030D00E292ED0FFDFBBE114271C26002CEE +:105A60006BD08088A080FDF7EEFC5FEA000900D1D2 +:105A7000FFDF99F817005A46400809F11801FDF7B1 +:105A8000B2FC6868C0892082696851F8060FC4F8C2 +:105A900012004868C4F81600A07E20F0060001E05D +:105AA0002C02002040F00100A07699F81E0040F082 +:105AB00020014DE01A270A26002CD1D0C088A080F2 +:105AC000FDF7C1FC050000D1FFDF59462846FFF76E +:105AD00042FB7EE10CB1A88BA080287A0B287DD0F8 +:105AE00006DC01287BD0022808D0032804D135E049 +:105AF0000D2875D00E2874D0FFDF6AE11E27092615 +:105B0000002CADD0A088FDF79EFC5FEA000900D113 +:105B1000FFDF287B00F003000128207A1BD020F053 +:105B200001002072297B890861F341002072297BE2 +:105B3000C90861F3820001E041E1F2E02072297BB3 +:105B4000090961F3C300207299F81E0040F040017A +:105B500089F81E103DE140F00100E2E713270D2611 +:105B6000002CAAD0A088FDF76EFC8146807F00F053 +:105B70000300022808D0002080F00101A0880CF06A +:105B800039FF050003D101E00120F5E7FFDF99F8B7 +:105B90001E0000F00302022A50D0686F817801F0E5 +:105BA00003010129217A4BD021F001012172837870 +:105BB0009B0863F3410121728378DB0863F3820160 +:105BC000217283781B0963F3C3012172037863F3A5 +:105BD00006112172437863F3C71103E061E0A9E085 +:105BE00090E0A1E0217284F809A0C178A172022A94 +:105BF00029D00279E17A62F30001E1720279520858 +:105C000062F34101E1720279920862F38201E1726A +:105C10000279D20862F3C301E1724279217B62F317 +:105C2000000121734279520862F3410121734279E4 +:105C3000920862F382012173407928E0A86FADE7F2 +:105C400041F00101B2E74279E17A62F30001E172C9 +:105C50004279520862F34101E1724279920862F39B +:105C60008201E1724279D20862F3C301E1720279E2 +:105C7000217B62F3000121730279520862F3410132 +:105C800021730279920862F3820121730079C008BE +:105C900060F3C301217399F80000232831D926212C +:105CA00040E018271026E4B3A088FDF7CCFB83461C +:105CB000807F00F00300022809D0002080F001015D +:105CC000A0880CF097FE5FEA000903D101E00120F3 +:105CD000F4E7FFDFE868A06099F8000040F00401F5 +:105CE00089F8001099F80100800708D50120207379 +:105CF0009BF8000023286CD92721584651E084F8EE +:105D00000CA066E015270F265CB1A088FDF79BFB71 +:105D1000814606225946E86808F0CBFA0120A073B4 +:105D2000A0E041E048463CE016270926E4B3287B82 +:105D300020724EE0287B19270E26ACB3C4F808A0C9 +:105D4000A4F80CA0012807D0022805D0032805D00C +:105D5000042803D0FFDF0DE0207207E0697B0428F0 +:105D600001F00F0141F0800121721ED0607A20F015 +:105D700003006072A088FDF766FB054600782128C5 +:105D800027D0232800D0FFDFA87F00F003000228DF +:105D900013D0002080F00101A0880CF03DFE2221EC +:105DA0002846FDF7B7F914E004E0607A20F003001C +:105DB000401CDEE7A8F8006010E00120EAE70CB123 +:105DC0006888A080287A68B301280AD002284FD0BA +:105DD000FFDFA8F800600CB1278066800020BDE8D6 +:105DE000F09F15270F26002CE4D0A088FDF72BFB91 +:105DF000807F00F00300022808D0002080F001011D +:105E0000A0880CF0F7FD050003D101E00120F5E7C3 +:105E1000FFDFD5F81D000622594608F04AFA84F83B +:105E20000EA0D6E717270926002CC3D0A088FDF7BF +:105E30000AFB8146807F00F00300022808D0002082 +:105E400080F00101A0880CF0D5FD050003D101E030 +:105E50000120F5E7FFDF6878800701D5022000E028 +:105E60000120207299F800002328B2D9272159E790 +:105E700019270E26002C9DD0A088FDF7E4FA5FEAD2 +:105E8000000900D1FFDFC4F808A0A4F80CA084F832 +:105E900008A0A07A40F00300A07299F81E10C9096A +:105EA00061F38200A07299F81F2099F81E1012EA7F +:105EB000D11F05D099F8201001F01F0110292BD017 +:105EC00020F00800A07299F81F10607A61F3C300F7 +:105ED0006072697A01F003010129A2D140F0040047 +:105EE000607299F81E0000F003000228E87A16D0CC +:105EF000217B60F300012173AA7A607B62F30000CA +:105F00006073EA7A520862F341012173A97A490861 +:105F100061F3410060735CE740F00800D2E7617B09 +:105F200060F300016173AA7A207B62F300002073A2 +:105F3000EA7A520862F341016173A97A490861F370 +:105F40004100207345E710B5FE4C30B101461022E8 +:105F500004F120001FF012FB012084F8300010BD76 +:105F600010B5044600F0D1FDF64920461022BDE8E8 +:105F7000104020311FF002BB70B5F24D06004FF00B +:105F8000000413D0FBF79FF908B110240CE00621A0 +:105F9000304608F075F9411C05D028665FF0010015 +:105FA00085F85C0000E00724204670BD0020F7E77C +:105FB000007810F00F0204D0012A05D0022A0CD17B +:105FC00010E0000909D10AE00009012807D00228E1 +:105FD00005D0032803D0042801D00720704708709B +:105FE000002070470620704705282AD2DFE800F01D +:105FF00003070F171F00087820F0FF001EE0087845 +:1060000020F00F00401C20F0F000103016E008785F +:1060100020F00F00401C20F0F00020300EE0087847 +:1060200020F00F00401C20F0F000303006E008782F +:1060300020F00F00401C20F0F000403008700020DD +:106040007047072070472DE9F041804688B00D4623 +:1060500000270846FBF784F9A8B94046FDF7F3F995 +:10606000040003D02078222815D104E043F2020076 +:1060700008B0BDE8F08145B9A07F410603D500F026 +:106080000300022801D01020F2E7A07FC10601D44E +:10609000010702D50DB10820EAE7E17F090701D524 +:1060A0000D20E5E700F00300022805D125B12846C0 +:1060B000FEF762FF0700DBD1A07F00F0030002289B +:1060C00008D0002080F0010140460CF093FC06004F +:1060D00002D00FE00120F5E7A07F00F003000228C6 +:1060E0000ED0002080F00101002240460CF079FC27 +:1060F000060007D0A07F00F00300022804D009E0CA +:106100000120EFE70420B3E725B12A4631462046B7 +:10611000FEF756FF6946304600F007FD009800B9CB +:10612000FFDF0099022006F1E0024870C1F82480E8 +:106130004A6100220A81A27F02F00302022A1CD0D7 +:1061400001200871287800F00102087E62F3010046 +:1061500008762A78520862F3820008762A78920834 +:1061600062F3C30008762A78D20862F30410087636 +:1061700024212046FCF7CEFF33E035B30871301DF3 +:1061800088613078400908777078C0F3400048771C +:10619000287800F00102887F62F301008877A27FEF +:1061A000D20962F382008877E27F62F3C3008877C6 +:1061B000727862F304108877A878C87701F1210219 +:1061C00028462031FEF71DFF03E00320087105205B +:1061D000087625212046FCF79DFFA07F20F0400097 +:1061E000A07701A900980EF0F5F8022801D000B1BF +:1061F000FFDF38463CE72DE9FF4F534A0D4699B083 +:106200009A4607CA0AAB002783E807001998FDF7EA +:106210001AF9060006D03078262806D008201DB0CE +:10622000BDE8F08F43F20200F9E7B07F00F0030908 +:10623000B9F1020F0AD05DB91B98FEF79DFE002848 +:10624000EDD1B07F00F00300022801D11B9890BB74 +:10625000B07F00F00300022808D0002080F0010188 +:1062600019980CF0C7FB040003D101E00120F5E709 +:10627000FFDF852D28D007DCF5B1812D1ED0822DC2 +:106280001ED0832D08D11DE0862D1FD0882D1FD054 +:10629000892D1FD08A2D1FD00F2020710F281DD0CF +:1062A00003F02AF9E0B101208DF83C00201D109088 +:1062B0002079B8B15BE111E00020EEE70120ECE7C6 +:1062C0000220EAE70320E8E70520E6E70620E4E706 +:1062D0000820E2E70920E0E70A20DEE707209EE742 +:1062E00011209CE7B9F1020F03D0A56F03D1A06F75 +:1062F00002E0656FFAE7606F804632D04FF0010030 +:1063000001904FF002000090214630461B9AFEF7A4 +:1063100057FE1B98007800F00101A87861F3010096 +:10632000A870B17FC90961F38200A870F17F61F3A1 +:10633000C300A870617861F30410A8702078400948 +:10634000287003E02C0200206C5A02006078C0F331 +:10635000400068701B988078E87000206871287190 +:1063600003E00220019001200090A87898F8021024 +:10637000C0F3C000C1F3C00108405FEA000B2DD09C +:106380005046FAF7A0FF78BBDAF80C00FAF79BFF4B +:1063900050BBDAF81C00FAF796FF28BBDAF80C00BD +:1063A000A060DAF81C00E060607898F8012042EA0A +:1063B000500100BF61F34100607098F80210C0B254 +:1063C00000EA111161F3000060700020207700994D +:1063D00006F11700022908D0012107E0607898F83B +:1063E000012002EA5001E5E732E0002104EB8101DF +:1063F00048610199701C022901D0012100E00021AF +:1064000004EB81014861A87800F00300012857D10E +:1064100098F8020000F00300012851D1B9F1020FF1 +:1064200004D02A1D691D1B98FEF7EBFD287998F80A +:10643000041008408DF83400697998F8052011405F +:106440008DF8381008433BD05046FAF73CFF08B1AE +:106450001020E4E60AF110010491B9F1020F17D0FF +:106460000846002104F18C03CDE9000304F5AE7267 +:1064700002920DAB5A462046FEF70CFE0028E8D1EA +:10648000B9F1020F08D0504608D14FF0010107E0E2 +:1064900050464FF00101E5E70498F5E74FF00001A1 +:1064A00004F1A403CDE9000304F5B072029281F077 +:1064B00001010EAB5A462046FEF7ECFD0028C8D17C +:1064C0006078800734D4A87898F80210C0F3800070 +:1064D000C1F3800108432BD0297898F800000AAA5C +:1064E000B9F1020F06D032F811204300DA4002F071 +:1064F00003070AE032F810204B00DA4012F00307DD +:1065000005D0012F0BD0022F0BD0032F07D0BBF1EA +:10651000000F0DD0012906D0042904D008E002277D +:10652000F5E70127F3E7012801D0042800D104276B +:10653000B07F40F08000B077F17F6BF30001F1771E +:106540006078800706D50320A071BBF1000F0ED143 +:10655000002028E00220022F18D0012F18D0042F8D +:1065600029D00020A071B07F20F08000B0772521D5 +:106570003046FCF7CFFD0FA904F1E0000DF00FFF4E +:1065800010B1022800D0FFDF002048E6A071DFE74D +:10659000A0710D2104F120001FF091F8207840F047 +:1065A0000200207001208DF85C0017AA314619986E +:1065B00000F094FADBE70120A071D8E72DE9F04361 +:1065C00087B09046894604460025FCF73CFF06004C +:1065D00006D03078272806D0082007B0BDE8F08321 +:1065E00043F20200F9E7B07F00F00300022809D06F +:1065F0005FF0000080F0010120460CF0FBF9040080 +:1066000003D101E00120F5E7FFDFA7795FEA090088 +:1066100005D0012821D0B9F1020F26D110E0B8F140 +:10662000000F22D1012F05D0022F05D0032F05D056 +:10663000FFDF2DE00C252BE0012529E0022527E0D6 +:106640004046FAF740FEB0B9032F0ED11022414662 +:1066500004F11D001EF092FF1AE0012F02D0022F5C +:1066600003D104E0B8F1000F12D00720B5E740468F +:10667000FAF729FE08B11020AFE7102104F11D0040 +:106680001EF0FBFF0621404607F0FAFDC4F81D008E +:106690002078252140F0020020703046FCF73AFDBA +:1066A0002078C10713D020F00100207002208DF85F +:1066B000000004F11D0002908DF804506946C330BB +:1066C0000DF06DFE022803D010B1FFDF00E025774A +:1066D000002082E730B587B00D460446FCF7B3FED4 +:1066E000A0B1807F00F00300022812D05FF000000C +:1066F00080F0010120460CF07DF904000ED0284600 +:10670000FAF7E1FD38B1102007B030BD43F20200C6 +:10671000FAE70120ECE72078400701D40820F3E7EE +:10672000294604F13D00202205461EF027FF20786F +:1067300040F01000207001070FD520F008002070F5 +:1067400007208DF80000694604F1E00001950DF086 +:1067500026FE022801D000B1FFDF0020D4E770B58B +:106760000D460646FCF76FFE18B10178272921D1A6 +:1067700002E043F2020070BD807F00F003000228B7 +:1067800008D0002080F0010130460CF033F90400FD +:1067900003D101E00120F5E7FFDFA079022809D14C +:1067A0006078C00706D02A4621463046FEF702FD33 +:1067B00010B10FE0082070BDB4F860000E280BD2B5 +:1067C00004F1620102231022081F0BF09AFF01213D +:1067D00001704570002070BD112070BD70B5064677 +:1067E00014460D460846FAF76EFD18B92046FAF72A +:1067F00090FD08B1102070BDA6F57F40FF380ED087 +:106800003046FCF720FE38B1417822464B08811C07 +:106810001846FCF7E8FD07E043F2020070BD204691 +:10682000FDF7F4FD0028F9D11021E01D0FF025FB44 +:10683000E21D294604F1170000F087F9002070BD21 +:106840002DE9F04104468AB01546884600270846DF +:10685000FAF786FD18B92846FAF782FD10B1102024 +:106860000AB006E42046FCF7EEFD060003D03078BF +:1068700027281AD102E043F20200F1E7B07F00F0CE +:106880000300022808D0002080F0010120460CF00F +:10689000B1F8040003D101E00120F5E7FFDF207823 +:1068A000400702D56078800701D40820D8E7B07F80 +:1068B00000F00300022803D0A06F03D1A16F02E013 +:1068C000606FFAE7616F407800B19DB1487810B110 +:1068D000B8F1000F0ED0ADB1EA1D06A8E16800F0D6 +:1068E00034F9102206A905F117001EF01BFE18B19D +:1068F000042707E00720B3E71022E91D04F12D006B +:106900001EF03CFEB8F1000F06D0102208F107017E +:1069100004F11D001EF032FE2078252140F0020017 +:1069200020703046FCF7F6FB2078C10715D020F028 +:106930000100207002208DF8000004F11D0002907B +:10694000103003908DF804706946B3300DF027FDC8 +:10695000022803D010B1FFDF00E0277700207FE797 +:10696000F8B515460E460746FCF76DFD040004D049 +:106970002078222804D00820F8BD43F20200F8BD98 +:10698000A07F00F00300022802D043F20500F8BD0A +:106990003046FAF798FC18B92846FAF794FC08B183 +:1069A0001020F8BD00953288B31C21463846FEF70A +:1069B00024FC112815D00028F3D1297C4A08A17F96 +:1069C00062F3C711A177297CE27F61F30002E277CD +:1069D000297C890884F82010A17F21F04001A1774B +:1069E000F8BDA17F0907FBD4D6F80200C4F8360031 +:1069F000D6F80600C4F83A003088A086102229464E +:106A000004F124001EF0BAFD287C4108E07F61F308 +:106A10004100E077297C61F38200E077287C8008E0 +:106A200084F82100A07F40F00800A0770020D3E781 +:106A300070B50D4606460BB1072070BDFCF703FD8F +:106A4000040007D02078222802D3A07F800604D437 +:106A5000082070BD43F2020070BDADB1294630463A +:106A60000AF030FF02F05EFB297C4A08A17F62F346 +:106A7000C711A177297CE27F61F30002E277297CCC +:106A8000890884F8201004E030460AF03EFF02F046 +:106A900049FBA17F21F02001A17770BD70B50D46A3 +:106AA000FCF7D1FC040005D02846FAF732FC20B1EF +:106AB000102070BD43F2020070BD29462046FEF74B +:106AC0004AFB002070BD04E010F8012B0AB1002041 +:106AD0007047491E89B2F7D20120704770B515463C +:106AE000064602F009FD040000D1FFDF207820F007 +:106AF0000F00801C20F0F000203020706680286895 +:106B0000A060BDE8704002F0FABC10B5134C94F8D8 +:106B10003000002808D104F12001A1F110000DF08F +:106B200080FC012084F8300010BD10B190F8B9202D +:106B30002AB10A4890F8350018B1002003E0B830B7 +:106B400001E0064834300860704708B50023009320 +:106B500013460A460CF049F908BD00002C0200203B +:106B600018B18178012938D101E0102070470188DF +:106B700042F60112881A914231D018DC42F6010225 +:106B8000A1EB020091422AD00CDC41B3B1F5C05F09 +:106B900025D06FF4C050081821D0A0F57060FF38E0 +:106BA0001BD11CE001281AD002280AD117E0B0F549 +:106BB000807F14D008DC012811D002280FD00328D0 +:106BC0000DD0FF2809D10AE0B0F5817F07D0A0F5EC +:106BD0008070033803D0012801D0002070470F20B7 +:106BE00070470A281FD008DC0A2818D2DFE800F016 +:106BF000191B1F1F171F231D1F21102815D008DC6C +:106C00000B2812D00C2810D00D2816D00F2806D132 +:106C10000DE011280BD084280BD087280FD003203B +:106C200070470020704705207047072070470F20ED +:106C3000704704207047062070470C20704743F2CD +:106C40000200704738B50C46050041D06946FFF791 +:106C5000AFF9002819D19DF80010607861F30200A7 +:106C600060706946681CFFF7A3F900280DD19DF8F4 +:106C70000010607861F3C5006070A978C1F341012C +:106C8000012903D0022905D0072038BD217821F041 +:106C9000200102E0217841F020012170410704D059 +:106CA000A978C90861F386106070607810F0380F19 +:106CB00007D0A978090961F3C710607010F0380F88 +:106CC00002D16078400603D5207840F04000207063 +:106CD000002038BD70B50446002008801546606865 +:106CE000FFF7B0FF002816D12089A189884211D86A +:106CF00060688078C0070AD0B1F5007F0AD840F2FA +:106D00000120B1FBF0F200FB1210288007E0B1F582 +:106D1000FF7F01D90C2070BD01F2012129800020E4 +:106D200070BD10B50478137864F300031370047811 +:106D3000640864F3410313700478A40864F38203C5 +:106D400013700478E40864F3C3031370047824090F +:106D500064F3041313700478640964F34513137027 +:106D60000078800960F38613137031B10878C10789 +:106D700001D1800701D5012000E0002060F3C71396 +:106D8000137010BD4278530702D002F0070306E0EB +:106D900012F0380F02D0C2F3C20300E001234A7898 +:106DA00063F302024A70407810F0380F02D0C0F34B +:106DB000C20005E0430702D000F0070000E0012018 +:106DC00060F3C5024A7070472DE9F04F95B00D0091 +:106DD000824612D0122128461EF04FFC4FF6FF7B50 +:106DE00005AA0121584607F066F8002426463746D2 +:106DF0004FF420586FF4205973E0102015B0BDE80F +:106E0000F08F00BF9DF81E0001280AD1BDF81C10AC +:106E100041450BD011EB09000AD001280CD0022803 +:106E20000CD0042C0ED0052C0FD10DE0012400E075 +:106E30000224BDF81A6008E0032406E00424BDF82B +:106E40001A7002E0052400E00624BDF81A1051452E +:106E500047D12C74BEB34FF0000810AA4FF0070AB8 +:106E6000CDE90282CDE900A80DF13C091023CDF84F +:106E7000109042463146584607F0D0F808BBBDF89E +:106E80003C002A46C0B210A90DF041FBC8B9AE8142 +:106E9000CFB1CDE900A80DF1080C0AAE40468CE850 +:106EA0004102132300223946584607F0B7F840B98B +:106EB000BDF83C00F11CC01EC0B22A1D0DF027FB1E +:106EC00010B103209AE70AE0BDF82900E881062CFA +:106ED00005D19DF81E00A872BDF81C002881002075 +:106EE0008CE705A806F0F3FF00288BD0FFF779FEAA +:106EF00084E72DE9F0471C46DDE90978DDF82090AC +:106F000015460E00824600D1FFDF0CB1208818B173 +:106F1000D5B11120BDE8F087022D01D0012100E09C +:106F2000002106F1140005F0B5FEA8F800000246A5 +:106F30003B462946504603F04EF9C9F8000008B90F +:106F4000A41C3C600020E5E71320E3E7F0B41446FE +:106F5000DDE904528DB1002314B1022C09D101E006 +:106F6000012306E00D7CEE0703D025F00105012387 +:106F70000D742146F0BC03F0B9BF1A80F0BC704715 +:106F80002DE9FE4F91461A881C468A468046FAB182 +:106F900002AB494603F01FF9050019D04046A61C74 +:106FA00027880BF06BFE3246072629463B460096A3 +:106FB0000BF079FA20882346CDE900504A46514625 +:106FC0004046FFF7C3FF002020800120BDE8FE8F70 +:106FD0000020FBE72DE9F04786B082460EA89046D8 +:106FE00090E8B000894604AA05A903A88DE8070027 +:106FF0001E462A4621465046FFF77BFF039901B102 +:1070000001213970002818D1F94904F1140204ABA8 +:107010000860039805998DE80700424649465046A6 +:1070200006F0EFF9A8B1092811D2DFE800F0050851 +:107030000510100A0C0C0E00002006B06AE71120A3 +:10704000FBE70720F9E70820F7E70D20F5E7032025 +:10705000F3E7BDF810100398CDE9000133462A4646 +:1070600021465046FFF772FFE6E72DE9F04389B06D +:107070000D46DDE9108781461C461646142103A8FB +:107080001EF01DFB012002218DF810108DF80C0060 +:107090008DF81170ADF8146064B1A278D20709D0F0 +:1070A0008DF81600E088ADF81A00A088ADF8180039 +:1070B000A068079008A80095CDE90110424603A9F1 +:1070C00048466B68FFF786FF09B0BDE8F083F0B56E +:1070D0008BB000240646069407940727089405A859 +:1070E0000994019400970294CDE903400D461023C2 +:1070F0002246304606F092FF78B90AA806A9019404 +:1071000000970294CDE90310BDF8143000222946FF +:10711000304606F059FD002801D0FFF762FD0BB0A4 +:10712000F0BD06F0F9BB2DE9FC410C468046002677 +:1071300002F0E2F9054620780D287DD2DFE800F064 +:10714000BC0713B325BD49496383AF959B00A8488D +:10715000006820B1417841F010014170ADE0404637 +:1071600002F0FAF9A9E0042140460BF043FC0700C5 +:1071700000D1FFDF07F11401404605F01FFDA5BB5C +:1071800013214046FDF71CFC97E0042140460BF01C +:1071900031FC070000D1FFDFE088ADF800000020DF +:1071A000B8819DF80000010704D5C00602D5A0886B +:1071B000B88105E09DF8010040067ED5A088F881E1 +:1071C00005B9FFDF22462946404601F0BDFC0226F4 +:1071D00073E0E188ADF800109DF8011009060FD5A5 +:1071E000072803D006280AD00AE024E004214046FC +:1071F0000BF000FC060000D1FFDFA088F081022622 +:10720000CDB9FFDF17E0042140460BF0F3FB070088 +:1072100000D1FFDF07F1140006F0B5FB90F0010F7D +:1072200002D1E079000648D5387C022640F0020001 +:10723000387405B9FFDF00E03EE0224629464046AB +:1072400001F082FC39E0042140460BF0D3FB017CC5 +:10725000002D01F00206C1F340016171017C21F0B3 +:1072600002010174E7D1FFDFE5E702260121404674 +:1072700002F0A4F921E0042140460BF0BBFB0546D7 +:10728000606800902089ADF80400012269464046FC +:1072900002F0B5F9287C20F0020028740DE0002DE2 +:1072A000C9D1FFDFC7E7022600214046FBF70CF9F2 +:1072B000002DC0D1FFDFBEE7FFDF3046BDE8FC8117 +:1072C0003EB50C0009D001466B4601AA002006F02D +:1072D00027FF20B1FFF785FC3EBD10203EBD0020FA +:1072E0002080A0709DF8050002A900F00700FEF7BD +:1072F0007BFE50B99DF8080020709DF8050002A99A +:10730000C0F3C200FEF770FE08B103203EBD9DF839 +:10731000080060709DF80500C109A07861F30410B1 +:10732000A0709DF80510890961F3C300A0709DF855 +:107330000410890601D5022100E0012161F3420019 +:107340009DF8001061F30000A07000203EBD70B5F4 +:10735000144606460D4651EA040005D075B10846AC +:10736000F9F7F5FF78B901E0072070BD29463046EE +:1073700006F037FF10B1BDE8704032E454B120464A +:10738000F9F7E5FF08B1102070BD21463046BDE891 +:10739000704095E7002070BD2DE9FC5F0C469046DB +:1073A0000546002701780822007A3E46B2EB111FFD +:1073B0007ED104F10A0100910A31821E4FF0020AC7 +:1073C00004F1080B0191092A73D2DFE802F0ECDF27 +:1073D00005F427277AA9CD00688804210BF00AFB61 +:1073E000060000D1FFDFB08920B152270726C2E096 +:1073F0009002002051271026002C7DD06888A080A4 +:107400000120A071A88900220099FFF7A0FF0028A1 +:1074100073D1A8892081288AE081D1E0B5F8129043 +:10742000072824D1E87B000621D5512709F1140053 +:1074300086B2002CE1D0A88900220099FFF787FFCF +:1074400000285AD16888A08084F806A0A8892081E5 +:107450000120A073288A2082A4F81290A88A0090A4 +:1074600068884B46A969019A01F04BFBA8E05027B8 +:1074700009F1120086B2002C3ED0A889002259469C +:10748000FFF765FF002838D16888A080A889E080D0 +:10749000287A072813D002202073288AE081E87B0D +:1074A000C0096073A4F81090A88A0090688801E071 +:1074B00083E080E04B4604F11202A969D4E7012081 +:1074C000EAE7B5F81290512709F1140086B2002CB2 +:1074D00066D0688804210BF08DFA83466888A08006 +:1074E000A88900220099FFF732FF00286ED184F8A6 +:1074F00006A0A889208101E052E067E00420A07383 +:10750000288A2082A4F81290A88A009068884B46A6 +:10751000A969019A01F0F5FAA989ABF80E104FE0BC +:107520006888FBF790FF0746688804210BF062FA31 +:10753000064607B9FFDF06B9FFDF687BC00702D048 +:107540005127142601E0502712264CB36888A080EA +:10755000502F06D084F806A0287B594601F0E1FAA6 +:107560002EE0287BA11DF9E7FE49A88949898142BF +:1075700005D1542706269CB16888A08020E05327B7 +:107580000BE06888A080A889E08019E06888042161 +:107590000BF030FA00B9FFDF55270826002CF0D198 +:1075A000A8F8006011E056270726002CF8D068885C +:1075B000A080002013E0FFDF02E0012808D0FFDFF9 +:1075C000A8F800600CB1278066800020BDE8FC9F11 +:1075D00057270726002CE3D06888A080687AA0711E +:1075E000EEE7401D20F0030009B14143091D01EB06 +:1075F0004000704713B5DB4A00201071009848B175 +:10760000002468460BF013F8002C02D1D64A0099EA +:1076100011601CBD01240020F4E770B50D4606463C +:1076200086B014465C2128461EF049F804B9FFDFF5 +:10763000A0786874A2782188284601F09CFA00207E +:10764000A881E881228805F11401304605F09BFAF3 +:107650006A460121304606F02EFC19E09DF8030031 +:10766000000715D5BDF806103046FFF730FD9DF830 +:107670000300BDF8061040F010008DF80300BDF8BF +:107680000300ADF81400FF233046059A06F074FDA0 +:10769000684606F01CFC0028E0D006B070BD10B5AE +:1076A0000C4601F1140005F0A5FA0146627C204663 +:1076B000BDE8104001F094BA30B50446A94891B035 +:1076C0004FF6FF75C18905AA284606F0F4FB30E0A5 +:1076D0009DF81E00A0422AD001282AD1BDF81C0026 +:1076E000B0F5205F03D042F60101884221D100208D +:1076F00002AB0AAA0CA9019083E8070007200090BA +:10770000BDF81A1010230022284606F087FC38B96D +:10771000BDF828000BAAC0B20CA90CF0F8FE10B1FD +:10772000032011B030BD9DF82E00A04201D10020F1 +:10773000F7E705A806F0CBFB0028C9D00520F0E745 +:1077400070B5054604210BF055F9040000D1FFDFA8 +:1077500004F114010C46284605F030FA214628466B +:10776000BDE8704005F031BA70B58AB00C460646E7 +:10777000FBF769FE050014D02878222827D30CB126 +:10778000A08890B101208DF80C0003208DF8100026 +:1077900000208DF8110054B1A088ADF818002068C1 +:1077A00007E043F202000AB070BD0920FBE7ADF824 +:1077B00018000590042130460BF01CF9040000D19C +:1077C000FFDF04F1140005F02CFA000701D40820B3 +:1077D000E9E701F091FE60B108A802210094CDE92B +:1077E000011095F8232003A930466368FFF7F2FBE8 +:1077F000D9E71120D7E72DE9F04FB2F802A0834670 +:1078000089B0154689465046FBF71DFE0746042100 +:1078100050460BF0EFF80026044605964FF002089C +:107820000696ADF81C6007B9FFDF04B9FFDF4146DB +:10783000504603F0C6FE50B907AA06A905A88DE870 +:1078400007004246214650466368FFF752FB454811 +:1078500007AB0660DDE9051204F11400CDF80090D5 +:10786000CDE90320CDE9013197F823205946504650 +:107870006B6805F01FFA06000AD0022E04D0032E12 +:1078800014D0042E00D0FFDF09B03046BDE8F08FE1 +:10789000BDF81C000028F7D00599CDE9001042463C +:1078A000214650466368FFF751FBEDE7687840F0EA +:1078B00008006870E8E72DE9F04F99B004464FF0F2 +:1078C00000082848ADF81C80ADF82080ADF8248071 +:1078D000A0F80880ADF81480ADF81880ADF82C80C1 +:1078E000ADF82880007916460D464746012808D095 +:1078F000022806D0032804D0042802D0082019B09A +:10790000C4E72046F9F7DFFC80BB2846F9F7DBFC2B +:1079100060BB6068F9F724FD40BB606848B16089CE +:107920002189884202D8B1F5007F01D90C20E6E711 +:1079300080460EAA06A92846FFF7CCF90028DED11A +:1079400068688078C0F34100022808D19DF81900CA +:1079500010F0380F03D02869F9F7F9FC30B905A900 +:10796000206904E0900200201400002020E0FFF7CE +:1079700069F90028C3D1206948B1607880079DF873 +:10798000150000F0380001D5F0B300E0E0BB9DF831 +:10799000140080060ED59DF8150010F0380F03D0A6 +:1079A0006068F9F7D4FC18B96068F9F7D9FC08B138 +:1079B0001020A4E70AA96069FFF744F900289ED1C6 +:1079C000606940B19DF8290000F0070101293CD110 +:1079D00010F0380F39D00BA9A069FFF733F9002850 +:1079E0008DD19DF8280080062FD49DF82C008006AC +:1079F0002BD4A06950B19DF82D0000F0070101299A +:107A000023D110F0380F00E01FE01ED0E06818B15D +:107A10000078D0B11C2818D20FAA611C2046FFF7AD +:107A200080F90121384661F30F2082468DF852100B +:107A3000B94642F603000F46ADF850000DF13F0283 +:107A400018A928680CF082FD08B1072057E79DF8B7 +:107A5000600015A9CDF80090C01CCDE9019100F09F +:107A6000FF0B00230BF20122514614A806F066F921 +:107A7000F0BBBDF854000C90FD492A89286900929A +:107A8000CDE901016B89BDF838202868069906F018 +:107A900055F901007ED120784FF0020AC10601D4C9 +:107AA00080062BD5ADF80C90606950B90AA906A8DC +:107AB000FFF768F99DF8290020F00700401C8DF8B9 +:107AC00029009DF8280008A940F0C8008DF828007A +:107AD0008DF8527042F60210ADF8500003AACDF8AE +:107AE00000A0CDE90121002340F2032214A800E008 +:107AF0001EE00A9906F022F901004BD1DC484D4600 +:107B000008385B460089ADF83D000FA8CDE902902A +:107B1000CDF80490CDF810904FF007090022CDF871 +:107B20000090BDF854104FF6FF7006F04DF810B1FC +:107B3000FFF757F8E3E69DF83C00000625D52946F7 +:107B4000012060F30F218DF852704FF42450ADF8EE +:107B50005000ADF80C5062789DF80C00002362F3E1 +:107B600000008DF80C006278CDF800A0520862F396 +:107B700041008DF80C0003AACDE9012540F2032253 +:107B800014A806F0DBF8010004D1606888B320690E +:107B9000A8B900E086E005A906A8FFF7F3F8607829 +:107BA000800706D49DF8150020F038008DF81500E8 +:107BB00005E09DF8140040F040008DF814008DF8A9 +:107BC000527042F60110ADF85000208940F20121B8 +:107BD000B0FBF1F201FB1202606809ABCDF8008046 +:107BE000CDE90103002314A8059906F0A7F80100C8 +:107BF00058D12078C00729D0ADF80C50A06950B9F1 +:107C00000BA906A8FFF7BEF89DF82D0020F007008D +:107C1000401C8DF82D009DF82C008DF8527040F01E +:107C200040008DF82C0042F60310ADF8500007A973 +:107C300003AACDF800A0CDE90121002340F20322E0 +:107C400014A80B9906F07AF801002BD1E06868B30C +:107C50002946012060F30F218DF8527042F604107E +:107C6000ADF85000E068002302788DF85820407885 +:107C70008DF85900E06816AA4088ADF85A00E0680F +:107C800000798DF85C00E068C088ADF85D00CDF843 +:107C90000090CDE901254FF4027214A806F04EF8C9 +:107CA000010003D00C9800F0C7FF28E670480321BC +:107CB0000838017156B100893080BDF82400708009 +:107CC000BDF82000B080BDF81C00F080002016E652 +:107CD00070B501258AB016460B46012802D002284D +:107CE00016D104E08DF80E504FF4205003E08DF8CB +:107CF0000E5042F60100ADF80C005BB10024601C90 +:107D000060F30F2404AA08A918460CF01FFC18B150 +:107D1000072048E5102046E504A99DF82020544896 +:107D2000CDE90021801E02900023214603A802F223 +:107D3000012206F003F810B1FEF753FF33E54C487B +:107D400008380EB1C1883180057100202BE5F0B5EF +:107D500093B0074601268DF83E6041F60100ADF86C +:107D60003C0012AA0FA93046FFF7B2FF002848D105 +:107D70003F4C0025083CE7B31C2102A81DF09FFCE6 +:107D80009DF808008DF83E6040F020008DF8080056 +:107D900042F60520ADF83C000E959DF83A0011958D +:107DA00020F00600801C8DF83A009DF838006A46E5 +:107DB00020F0FF008DF838009DF8390009A920F067 +:107DC000FF008DF839000420ADF82C00ADF830002C +:107DD0000EA80A9011A80D900FA80990ADF82E508A +:107DE00002A8FFF768FD00280BD1BDF800006081F4 +:107DF00000E008E0BDF80400A081401CE08125718E +:107E0000002013B0F0BD6581A581BDF84800F4E7FE +:107E10002DE9F74F1649A0B00024083917940A79C4 +:107E2000A146012A04D0022A02D0082023B02DE561 +:107E3000CA88824201D00620F8E721988A46824209 +:107E400001D10720F2E701202146ADF848004FF6A6 +:107E5000FF788DF86E0042F6020B60F30F21ADF84B +:107E60004A80ADF86CB006918DF8724002E00000D7 +:107E7000980200201CA9ADF870401391ADF8508015 +:107E800012A806F048F800252E462F460DAB072213 +:107E900012A9404606F042F878B10A285DD195B3A0 +:107EA0008EB3ADF86450ADF866609DF85E008DF855 +:107EB000144019AC012864D06BE09DF83A001FB360 +:107EC000012859D1BDF8381059451FD118A809A962 +:107ED00001940294CDE9031007200090BDF83610FC +:107EE00010230022404606F099F8B0BBBDF86000B0 +:107EF000042801D006284AD1BDF8241021988142D7 +:107F00003AD10F2092E73AE0012835D1BDF8380088 +:107F1000B0F5205F03D042F6010188422CD1BAF8B7 +:107F20000600BDF83610884201D1012700E0002785 +:107F300005B19EB1219881421ED118A809AA0194C9 +:107F40000294CDE90320072000900D461023002263 +:107F5000404606F063F800B902E02DE04E460BE023 +:107F6000BDF86000022801D0102810D1C0B217AAB5 +:107F700009A90CF0CCFA50B9BDF8369086E7052077 +:107F800054E705A917A8221D0CF0E0FA08B1032058 +:107F90004CE79DF814000023001DC2B28DF8142098 +:107FA00022980092CDE901401BA8069905F0C6FE73 +:107FB00010B902228AF80420FEF713FE36E710B546 +:107FC0000B46401E88B084B205AA00211846FEF771 +:107FD000A8FE00200DF1080C06AA05A901908CE866 +:107FE0000700072000900123002221464FF6FF7072 +:107FF00005F0EAFD0446BDF81800012800D0FFDFB7 +:108000002046FEF7EEFD08B010BDF0B5F74F044670 +:1080100087B038790E46032804D0042802D00820FF +:1080200007B0F0BD04AA03A92046FEF753FE0500E1 +:10803000F6D160688078C0F3410002280AD19DF82B +:108040000D0010F0380F05D02069F9F780F908B15C +:108050001020E5E7208905AA21698DE807006389DA +:10806000BDF810202068039905F068FE10B1FEF7F6 +:10807000B8FDD5E716B1BDF8140030800420387182 +:108080002846CDE7F8B50C0006460CD001464FF661 +:10809000FF7500236A46284606F042F828B100BF63 +:1080A000FEF79FFDF8BD1020F8BD69462046FEF79B +:1080B000C9FD0028F8D1A078314600F00103284618 +:1080C000009A06F059F8EBE730B587B01446002265 +:1080D0000DF1080C05AD01928CE82C0007220092EE +:1080E0000A46014623884FF6FF7005F06DFDBDF886 +:1080F00014102180FEF775FD07B030BD70B50D4638 +:1081000004210AF077FC040000D1FFDF294604F1C6 +:108110001400BDE8704004F07DBD70B50D4604212B +:108120000AF068FC040000D1FFDF294604F11400C6 +:10813000BDE8704004F091BD70B50D4604210AF011 +:1081400059FC040000D1FFDF294604F11400BDE80A +:10815000704004F0A9BD70B5054604210AF04AFC40 +:10816000040000D1FFDF214628462368BDE87040A7 +:108170000122FEF705BF70B5064604210AF03AFC5D +:10818000040000D1FFDF04F1140004F033FD401DB2 +:1081900020F0030511E0011D00880022431821464C +:1081A0003046FEF7EDFE00280BD0607CABB2684392 +:1081B00082B2A068011D0AF0DAFAA068418800299D +:1081C000E9D170BD70B5054604210AF013FC040026 +:1081D00000D1FFDF214628466368BDE870400222D7 +:1081E000FEF7CEBE70B50E46054601F085F90400D7 +:1081F00000D1FFDF0120207266726580207820F0B8 +:108200000F00001D20F0F00040302070BDE87040ED +:1082100001F075B910B50446012900D0FFDF2046F2 +:10822000BDE810400121FAF74FB92DE9F04F97B0A2 +:108230004FF0000A0C008346ADF814A0D04619D0C8 +:10824000E06830B1A068A8B10188ADF81410A0F8BA +:1082500000A05846FBF7F7F8070043F2020961D087 +:10826000387822285CD3042158460AF0C3FB050065 +:1082700005D103E0102017B0BDE8F08FFFDF05F156 +:10828000140004F0B7FC401D20F00306A07801287C +:1082900003D0022801D00720EDE7218807AA58461D +:1082A00005F009FE30BB07A805F011FE10BB07A8BA +:1082B00005F00DFE48B99DF82600012805D1BDF84E +:1082C0002400A0F52451023902D04FF45050D2E7D7 +:1082D000E068B0B1CDE902A00720009005AACDF872 +:1082E00004A00492A2882188BDF81430584605F0F5 +:1082F0006BFC10B1FEF775FCBDE7A168BDF814007A +:1083000008809DF81F00C00602D543F20140B2E785 +:108310000B9838B1A1780078012905D080071AD4CC +:108320000820A8E74846A6E7C007F9D002208DF844 +:108330003C00A8684FF00009A0B1697C42887143F5 +:1083400091420FD98AB2B3B2011D0AF0C6F9804634 +:10835000A0F800A006E003208DF83C00D5F80080CE +:108360004FF001099DF8200010F0380F00D1FFDF19 +:108370009DF820001E49C0F3C200084497F823105E +:1083800010F8010C884201D90F2074E72088ADF85D +:10839000400014A90095CDE90191434607220FA999 +:1083A0005846FEF717FE002891D19DF8500050B9AD +:1083B000A078012807D1687CB3B2704382B2A86864 +:1083C000011D0AF09EF9002055E770B506461546D6 +:1083D0000C460846FEF7C4FB002805D12A46214674 +:1083E0003046BDE8704073E470BD11E59002002096 +:1083F000765A020070B51E4614460D0009D044B1ED +:10840000616831B138B1FC49C988814203D0072085 +:1084100070BD102070BD2068FEF7A2FB0028F9D1C6 +:10842000324621462846BDE87040FFF744BA70B591 +:1084300015460C0006D038B1EF490989814203D0B6 +:10844000072070BD102070BD2068FEF789FB002852 +:10845000F9D129462046BDE87040D6E570B50646FC +:1084600086B00D4614461046F8F753FFD0BB60683F +:10847000F8F776FFB0BBA6F57F40FF3803D0304653 +:10848000FAF7E1FF80B128466946FEF79DFC002817 +:108490000CD19DF810100F2008293DD2DFE801F023 +:1084A00008060606060A0A0843F2020006B070BD76 +:1084B0000320FBE79DF80210012908D1BDF8001048 +:1084C000B1F5C05FF2D06FF4C052D142EED09DF84A +:1084D000061001290DD1BDF80410A1F52851062977 +:1084E00007D200E029E0DFE801F0030304030303FF +:1084F000DCE79DF80A1001290FD1BDF80810B1F58D +:10850000245FD3D0A1F60211B1F50051CED00129DC +:10851000CCD0022901D1C9E7FFDF606878B9002318 +:1085200005AA2946304605F0FBFD10B1FEF759FBC0 +:10853000BCE79DF81400800601D41020B6E76188DE +:10854000224628466368FFF7BFFDAFE72DE9F043F9 +:10855000814687B0884614461046F8F7DAFE18B10F +:10856000102007B0BDE8F083002306AA4146484624 +:1085700005F0D6FD18B100BFFEF733FBF1E79DF81B +:108580001800C00602D543F20140EAE7002507279C +:1085900005A8019500970295CDE9035062884FF632 +:1085A000FF734146484605F039FD060013D1606867 +:1085B000F8F7AFFE60B960680195CDE90250009709 +:1085C0000495238862884146484605F027FD064603 +:1085D000BDF8140020803046CEE739B1864B0A88BA +:1085E0009B899A4202D843F2030070471DE610B5FA +:1085F00086B0814C0423ADF81430638943B1A4895B +:108600008C4201D2914205D943F2030006B010BD5D +:108610000620FBE7ADF81010002100910191ADF8A4 +:10862000003002218DF8021005A9029104A90391DE +:10863000ADF812206946FFF7F8FDE7E72DE9FC47A2 +:1086400081460D460846F8F73EFE88BB4846FAF7D5 +:10865000FAFE5FEA00080AD098F80000222829D321 +:10866000042148460AF0C6F9070005D103E043F2A9 +:108670000200BDE8FC87FFDF07F1140004F0D1FA27 +:1086800006462878012803D0022804D00720F0E706 +:10869000B0070FD502E016F01C0F0BD0A8792C1DE7 +:1086A000C00709D0E08838B1A068F8F70CFE18B10F +:1086B0001020DEE70820DCE721882A780720B1F5C2 +:1086C000847F35D01EDC40F20315A1F20313A942CA +:1086D00026D00EDCB1F5807FCBD003DCF9B10129C7 +:1086E00026D1C6E7A1F58073013BC2D0012B1FD173 +:1086F00013E0012BBDD0022B1AD0032BB9D0042BD1 +:1087000016D112E0A1F20912082A11D2DFE802F014 +:108710000B04041010101004ABE7022AA9D007E0E4 +:10872000012AA6D004E0320700E0F206002AA0DA0F +:10873000CDB200F0E1FE50B198F82300CDE900057C +:10874000FA89234639464846FEF78FFC91E7112007 +:108750008FE72DE9F04F8BB01F4615460C46834638 +:108760000026FAF770FE28B10078222805D20820EA +:108770000BB081E543F20200FAE7B80801D0072008 +:10878000F6E7032F00D100274FF6FF79CCB1022D79 +:1087900073D32046F8F7E4FD30B904EB0508A8F1DF +:1087A0000100F8F7DDFD08B11020E1E7AD1EAAB227 +:1087B0002146484605F08FFD38F8021C88425CD1FE +:1087C000ADB20D49B80702D58889401C00E00120F0 +:1087D0001FFA80F8F80701D08F8900E04F4605AAFC +:1087E0004146584605F067FB4FF0070A4FF0000975 +:1087F000DCB320460BE000009002002040881028E7 +:108800003BD8361D304486B2AE4236D2A01902881B +:108810004245F3D351E000BF9DF8170002074CD545 +:1088200094B304EB0608361DB8F80230B6B2102B2C +:1088300023D89A19AA4220D8B8F8002091421CD116 +:10884000C0061CD5CDE900A90DF1080C0AAAA11992 +:1088500048468CE80700B8F800100022584605F09A +:10886000B3F920B1FEF7BDF982E726E005E0B8F8DC +:108870000200BDF82810884201D00B2078E7B8F834 +:108880000200304486B207E0FFE7C00604D5584630 +:10889000FEF71DFC002888D19DF81700BDF81A10BE +:1088A00020F010008DF81700BDF81700ADF800009B +:1088B000FF235846009A05F05FFC05A805F007FB6A +:1088C00018B9BDF81A10B942A6D9042158460AF0C1 +:1088D00091F8040000D1FFDFA2895AB1CDE900A9C7 +:1088E0004D46002321465846FEF7BFFB0028BBD16A +:1088F000A5813DE700203BE72DE9FF4F8BB01E46E9 +:1089000017000D464FF0000412D0B00802D0072027 +:108910000FB0B1E4032E00D100265DB10846F8F790 +:1089200016FD28B93888691E0844F8F710FD08B10B +:108930001020EDE7C64AB00701D5D18900E001213A +:10894000F0074FF6FF7802D0D089401E00E0404685 +:1089500086B206AA0B9805F0AEFA4FF000094FF068 +:10896000070B0DF1140A38E09DF81B00000734D501 +:10897000CDF80490CDF800B0CDF80890CDE9039A79 +:10898000434600220B9805F049FB60BB05B3BDF8D8 +:1089900014103A8821442819091D8A4230D3BDF8A1 +:1089A0001E2020F8022BBDF8142020F8022BCDE960 +:1089B00000B9CDE90290CDF810A0BDF81E10BDF8A9 +:1089C000143000220B9805F029FB08B103209FE723 +:1089D000BDF814002044001D84B206A805F077FA03 +:1089E00020B10A2806D0FEF7FCF891E7BDF81E106A +:1089F000B142B9D934B17DB13888A11C884203D2C3 +:108A00000C2085E7052083E722462946404605F0ED +:108A100062FC014628190180A41C3C80002077E7F5 +:108A200010B50446F8F775FC08B1102010BD884851 +:108A3000C0892080002010BDF0B58BB00D460646E1 +:108A4000142103A81CF03BFE01208DF80C008DF8CA +:108A5000100000208DF81100ADF814503046FAF7E0 +:108A6000F2FC48B10078222812D30421304609F0E4 +:108A7000C1FF040005D103E043F202000BB0F0BDDA +:108A8000FFDF04F11400074604F0CBF8800601D4A0 +:108A90000820F3E7207C022140F00100207409A89F +:108AA0000094CDE90110072203A930466368FEF760 +:108AB00091FA20B1217C21F001012174DEE72946E1 +:108AC0003046F9F7F2FC08A9384604F099F800B1ED +:108AD000FFDFBDF82040172C01D2172000E0204610 +:108AE000A84201D92C4602E0172C00D217242146B7 +:108AF0003046FFF712FB21463046F9F7FCF900201B +:108B0000BCE7F8B51C4615460E46069F0AF0A4F8C9 +:108B10002346FF1DBCB231462A46009409F08FFC63 +:108B2000F8BD70B50C4605460E2120461CF0A5FD8B +:108B3000002020802DB1012D01D0FFDF70BD062067 +:108B400000E00520A07170BD10B54880087813467C +:108B500020F00F00001D20F0F00080300C4608705F +:108B60001422194604F108001CF04DFD00F0C7FC6A +:108B70003748046010BD2DE9F047DFF8D890491D53 +:108B8000064621F0030117460C46D9F8000009F00B +:108B90006CFD050000D1FFDF4FF000083560A5F83F +:108BA00000802146D9F8000009F05FFD050000D1E2 +:108BB000FFDF7560A5F800807FB104FB07F1091D98 +:108BC0000BD0D9F8000009F050FD040000D1FFDF00 +:108BD000B460C4F80080BDE8F087C6F80880FAE702 +:108BE0002DE9F0411746491D21F00302194D0646B3 +:108BF00001681446286809F063FD224671682868F8 +:108C000009F05EFD3FB104FB07F2121D03D0B1680D +:108C1000286809F055FD042009F094FE044604205C +:108C200009F098FE201A012804D12868BDE8F04117 +:108C300009F010BDBDE8F08110B50C4605F007F94C +:108C400000B1FFDF2046BDE81040FDF7CABF0000BD +:108C5000900200201400002038B50C468288817BE9 +:108C600019B14189914200D90A462280C188121D5A +:108C700090B26A4608F04FFFBDF80000032800D309 +:108C80000320C1B2208801F011F838BD38B50C4678 +:108C90008288817B19B10189914200D90A462280DC +:108CA000C188121D90B26A4608F035FFBDF8000079 +:108CB000022800D30220C1B2208800F0F7FF401C38 +:108CC000C0B238BD2DE9FE4F82468B46F9481446A6 +:108CD0000BF10302D0E90010CDE9011022F00302EC +:108CE00068464FF49071009209F0A1FCF24E002CFE +:108CF00002D1F24A00999160009901440091357FB8 +:108D000005F1010504D1E8B20BF09AFB00B1FFDFD9 +:108D1000009800EB0510C01C20F0030100915CB925 +:108D2000707AB27A1044C2B200200870308C80B2DF +:108D300004F015FF00B1FFDF0098316A084400908D +:108D40002146684600F075FF80460098C01C20F060 +:108D500003000090B37AF27A717A04B1002009F02E +:108D60005CFD0099084400902146684600F0A9FF88 +:108D7000D14800273D4690F801900CE0284600F0CD +:108D80003BFF064681788088F9F74CF971786D1CB5 +:108D900000FB0177EDB24D45F0D10098C01C20F0EA +:108DA0000300009004B100203946F9F746F9009914 +:108DB000002708440090C0483D4690F801900CE020 +:108DC000284600F019FF0646C1788088FEF709FCA6 +:108DD00071786D1C00FB0177EDB24D45F0D1009824 +:108DE000C01C20F00300009004B100203946FEF7BB +:108DF00001FC00994FF0000908440090AE484D4630 +:108E000047780EE0284600F0F7FE0646807B30B13A +:108E100006F1080001F019FF727800FB02996D1C41 +:108E2000EDB2BD42EED10098C01C20F003000090CE +:108E300004B10020494601F00CFF0099084400905D +:108E40002146684600F0AFFE0098C01D20F00700E4 +:108E50000090DAF80010814204D3A0EB0B01B1F5C9 +:108E6000803F04DB4FF00408CAF8000004E0CAF8B1 +:108E70000000B8F1000F02D04046BDE8FE8F34BBC1 +:108E80008F490020009A03F083F8FBF75CFA8A48C8 +:108E900001AA00211030F8F7E1FA00B1FFDF86489F +:108EA000407FFEF754FF00B1FFDF83484FF4F671B7 +:108EB00040301CF004FC80480421403080F8E91167 +:108EC00080F8EA11062180F8EB11032101710020DE +:108ED000D3E770B5784C06464034207804EB401553 +:108EE000E078083590B9A01990F8E80100280ED074 +:108EF000A0780F2800D3FFDF202128461CF0DFFBDD +:108F0000687866F3020068700120E070284670BD42 +:108F10002DE9F04105460C460027007805219046D2 +:108F20003E46B1EB101F00D0FFDF287A50B1012878 +:108F30000ED0FFDFA8F800600CB12780668000200B +:108F4000BDE8F0810127092674B16888A08008E097 +:108F50000227142644B16888A0802869E060A88AA6 +:108F60002082287B2072E5E7A8F80060E7E730B5AB +:108F7000514C012000212070617020726072032228 +:108F8000A272E07221732174052121831F21618364 +:108F900060744CA161610A2121776077474D4FF4DD +:108FA000B06020626868C11C21F00301814200D0DA +:108FB000FFDF6868606030BD30B5404C156863689D +:108FC00010339D4202D20420136030BD3A4B5D78CD +:108FD0005A6802EB0512107051700320D0801720E0 +:108FE00090800120D0709070002090735878401CC1 +:108FF0005870606810306060002030BD70B5064663 +:109000002D480024457807E0204600F0F5FD017862 +:10901000B14204D0641CE4B2AC42F5D1002070BD72 +:10902000F7B5074608780C4610B3FFF7E7FF05468B +:10903000A7F12006202F06D0052E19D2DFE806F072 +:109040000F2B2B151A0000F0E2FD0DB1697800E03E +:109050000021401AA17880B20844FF2808D8A078DF +:1090600030B1A088022831D202E0608817282DD2C2 +:109070000720FEBD207AE0B161881729F8D3A188C6 +:109080001729F5D3A1790029F2D0E1790029EFD091 +:10909000402804D9ECE7242F18D1207A48B1618800 +:1090A0004FF6FB70814202D8A18881420ED904207C +:1090B000FEBD0BE07C5A0200AC030020180000202B +:1090C000000000206E5246357800000065B9207817 +:1090D00002AA0121FFF770FF0028E9D12078FFF7ED +:1090E0008DFF050000D1FFDF052E18D2DFE806F066 +:1090F000030B0E081100A0786870A088E8800FE0CC +:109100006088A8800CE0A078A87009E0A078E870DA +:1091100006E054F8020FA8606068E86000E0FFDF36 +:109120000020C5E71A2835D00DDC132832D2DFE83D +:1091300000F01B31203131272723252D31312931F2 +:109140003131312F0F00302802D003DC1E2821D10D +:10915000072070473A3809281CD2DFE800F0151BB9 +:109160000F1B1B1B1B1B07000020704743F2040052 +:10917000704743F202007047042070470D2070478B +:109180000F20704708207047112070471320704748 +:10919000062070470320704710B5007800F00100EA +:1091A00008F0ABFCBDE81040BCE710B5007818B182 +:1091B000012801D0072010BD08F0EFFCBDE81040E9 +:1091C000B0E710B5007800F0010008F09FFCBDE8A2 +:1091D0001040A7E70EB5017801F001018DF80010ED +:1091E000417801F001018DF801100178C1F34001CF +:1091F0008DF802104178C1F340018DF80310017819 +:1092000089088DF80410417889088DF80510817857 +:109210008DF80610C1788DF8071000798DF80800D8 +:10922000684607F095FAFFF77DFF0EBD2DE9F84F70 +:10923000DFF8F883FE4C00264FF490771FE0012002 +:1092400000F082FD0120FFF744FE05463946D8F8BC +:10925000080009F00AFA686000B9FFDF686807F0E3 +:1092600006F9B0B12846FAF7D5FB284600F072FDA2 +:1092700028B93A466968D8F8080009F021FA94F943 +:10928000E9010428DBDA022009F05CFB074600252F +:10929000A5E03A466968D8F8080009F011FAF2E743 +:1092A000B8F802104046491C89B2A8F80210B94229 +:1092B00001D3002141800221B8F8020009F09AFB95 +:1092C000002864D0B8F80200694608F088FBFFF770 +:1092D00029FF00B1FFDF9DF8000078B1B8F8020067 +:1092E00009F0CCFC5FEA000900D1FFDF484608F036 +:1092F0003AFF18B1B8F8020002F052F9B8F80200CB +:1093000009F0AAFC5FEA000900D1FFDF484608F037 +:1093100022FFE0BB0321B8F8020009F06BFB5FEA13 +:10932000000B47D1FFDF45E0DBF8100010B10078FB +:10933000FF2849D0022000F007FD0220FFF7C9FDF9 +:109340008246484609F013F8CAF8040000B9FFDF66 +:10935000DAF8040009F0DBF8002100900170B8F899 +:1093600002105046AAF8021001F01CFE484609F00F +:10937000D0F800B9FFDF504600F0ECFC18B99AF8BD +:109380000100000704D50098CBF8100012E024E09B +:10939000DBF8100038B10178491C11F0FF010170B1 +:1093A00008D1FFDF06E000221146484600F0F9FB35 +:1093B00000B9FFDF94F9EA01022805DBB8F80200E2 +:1093C00001F0B5FD0028AFD194F9E901042804DBD0 +:1093D000484609F002F900B101266D1CEDB2BD420C +:1093E00004D294F9EA010228BFF65AAF002E7FF4A6 +:1093F00022AFBDE8F84F032000F0A6BC10B58B4C9F +:10940000E06008682061AFF2DB10F9F766FD60707C +:1094100010BD87480021403801708448017085499B +:109420004160704770B505464FF080500C46D0F84B +:10943000A410491C05D1D0F8A810C9430904090C8F +:109440000BD050F8A01F01F0010129704168216084 +:109450008068A080287830B970BD062120460CF0C5 +:109460000CFD01202870607940F0C000607170BD73 +:1094700070B54FF080540D46D4F88010491C0BD1C4 +:10948000D4F88410491C07D1D4F88810491C03D1A2 +:10949000D4F88C10491C0CD0D4F880100160D4F89A +:1094A00084104160D4F888108160D4F88C10C160B9 +:1094B00002E010210CF0E1FCD4F89000401C0BD12C +:1094C000D4F89400401C07D1D4F89800401C03D174 +:1094D000D4F89C00401C09D054F8900F28606068B4 +:1094E0006860A068A860E068E86070BD2846BDE8D4 +:1094F000704010210CF0C1BC4D480079E9E470B512 +:109500004B4CE07830B3207804EB4010407A00F008 +:109510000700204490F9E801002800DCFFDF2078F4 +:10952000002504EB4010407A00F00700011991F883 +:10953000E801401E81F8E8012078401CC0B220708C +:109540000F2800D12570A078401CA0700CF08CFB77 +:10955000E57070BDFFDF70BD3EB50546032109F023 +:1095600049FA0446284609F077FB054604B9FFDFAF +:10957000206918B10078FF2800D1FFDF01AA6946F1 +:10958000284600F00EFB60B9FFDF0AE0002202A9C6 +:10959000284600F006FB00B9FFDF9DF8080000B187 +:1095A000FFDF9DF80000411E8DF80010EED220690B +:1095B0000199884201D1002020613EBD70B5054669 +:1095C000A0F57F400C46FF3800D1FFDF012C01D011 +:1095D000FFDF70BDFFF790FF040000D1FFDF2078B0 +:1095E00020F00F00401D20F0F0005030207065800A +:1095F0000020207201202073BDE870407FE72DE934 +:10960000F04116460D460746FFF776FF040000D1ED +:10961000FFDF207820F00F00401D20F0F0005030D8 +:109620002070678001202072286805E01800002063 +:10963000EC030020F81300202061A888A082267384 +:10964000BDE8F0415BE77FB5FFF7D8FC040000D12F +:10965000FFDF02A92046FFF7FFFA054603A92046CF +:10966000FFF714FB8DF800508DF80100BDF80800DD +:10967000001DADF80200BDF80C00001DADF804009F +:10968000E088ADF80600684608F01FFA002800D010 +:10969000FFDF7FBD2DE9F05FF94E8146307810B1D4 +:1096A0000820BDE8F09F4846F7F733FE08B11020C8 +:1096B000F7E7F44C207808B9FFF759FCA17A607AF3 +:1096C0004D460844C4B200F0A2FAA04207D2201AC4 +:1096D000C1B22A460020FFF76FFC0028E1D1716873 +:1096E000E848C91C002721F003017160B3463E46DB +:1096F0003D46BA463C4690F801800AE0204600F01C +:109700007BFA4178807B0E4410FB0155641CE4B267 +:109710007F1C4445F2D1C6EBC601DA4E0AEB870046 +:1097200000EB8100F17A00EB850000EB8100DBF8B3 +:1097300004105C464518012229464846FFF7C2FA44 +:10974000070012D00020FFF759FC05000BD005F1EF +:109750001300616820F00300884200D0FFDF7078BA +:10976000401E7070656038469BE7002229464846D7 +:10977000FFF7A8FA00B1FFDFD9F8000060604FF6EC +:10978000FF7060800120207000208AE72DE9F04101 +:109790000446BB4817460E46007810B10820BDE8C5 +:1097A000F0810846F7F78FFD08B11020F7E7B54DB7 +:1097B000287808B9FFF7DBFB601E1E2807D8012CAC +:1097C00022D13078FE281FD828770020E7E7A4F1BF +:1097D00020001F2805D8E0B23A463146BDE8F041E6 +:1097E0001EE4A4F140001F2805D831462046BDE8FC +:1097F000F04100F0D7BAA4F1A0001F2804D800203F +:10980000A02C03D0A12C06D00720C8E7317801F0A6 +:1098100001016977C3E731680922F82901D38B0771 +:1098200001D01046BBE76B7C03F00303012B04D18E +:109830006B8BD7339CB28C42F3D82962AFE72DE90A +:10984000F04781460E460846F7F763FD48B948469B +:10985000F7F77DFD28B909F1030020F00301494520 +:1098600002D01020BDE8F08786484FF0000A403053 +:10987000817869B14178804600EB4114083437881B +:1098800032460021204600F073FA050004D027E09C +:10989000A6F800A00520E5E7B9F1000F24D0308834 +:1098A000B84201D90C251FE0607800F00705284672 +:1098B00000F04AFA08EB0507324697F8E8014946F6 +:1098C000401C87F8E801204607F5F47700F050FACD +:1098D00005463878401E3870032000F035FA2DB167 +:1098E0000C2D01D0A6F800A02846BBE76078644E96 +:1098F00000F00701012923D002290CD0032934D01C +:10990000FFDF98F801104046491CC9B288F80110E1 +:109910000F2935D036E0616821B1000702D4608894 +:10992000FFF71AFE98F8EA014746012802D170783D +:10993000F9F7F2FA97F9EA010428E2DBFFDFE0E742 +:10994000616821B14FF49072B06808F0B9FE98F8E0 +:10995000E9014746032802D17078F9F7DDFA97F953 +:10996000E9010428CDDBFFDFCBE7C00602D5608824 +:10997000FFF7F2FD98F9EB010628C2DBFFDFC0E735 +:1099800080F801A08178491E8170617801F007019B +:1099900001EB080090F8E811491C80F8E811A3E7F2 +:1099A00070B50D460446F7F78EFC18B92846F7F750 +:1099B000B0FC08B1102070BD29462046BDE87040BB +:1099C0000AF075BD70B505460AF094FDC4B228468C +:1099D000F7F7BDFC08B1102070BD35B128782C70A8 +:1099E00018B1A04201D0072070BD2046FDF764FEEB +:1099F000052805D10AF082FD012801D0002070BDA4 +:109A00000F2070BD70B5044615460E460846F7F7A0 +:109A10005AFC18B92846F7F77CFC08B1102070BD35 +:109A2000022C03D0102C01D0092070BD2A463146EB +:109A300020460AF06CFD0028F7D0052070BD70B5F7 +:109A400014460D460646F7F73EFC38B92846F7F7A8 +:109A500060FC18B92046F7F77AFC08B1102070BDF9 +:109A60002246294630460AF071FD0028F7D007202B +:109A700070BD3EB50446F7F74CFC28B110203EBD42 +:109A800018000020AC030020684606F0C8FDFFF770 +:109A900049FB0028F3D19DF806002070BDF80800AE +:109AA0006080BDF80A00A0800020E8E770B5054698 +:109AB0000C460846F7F74BFC20B93CB12068F7F795 +:109AC00028FC08B1102070BDA08828B12146284686 +:109AD000BDE87040FDF748BE092070BD70B5054671 +:109AE0000C460846F7F7EFFB30B9681E1E2814D85D +:109AF0002046F7F7E8FB08B1102070BD042D01D90E +:109B0000072070BD05B9FFDFF84800EB850050F86D +:109B1000041C2046BDE870400847A5F120001F281E +:109B200005D821462846BDE87040FAF794BBF02DD1 +:109B300008D0F12DE4D1207808F05CF8BDE8704041 +:109B4000FFF7F0BAA068F7F7BEFB0028D4D1204693 +:109B500008F028F8F2E770B504460D460846F7F716 +:109B6000D8FB30B9601E1E2811D82846F7F7ABFB8A +:109B700008B1102070BD012C05D0022C03D0032C9D +:109B800001D0042C01D1062070BD072070BDA4F1C6 +:109B900020001F28F9D829462046BDE87040FAF772 +:109BA000B2BB08F0A7BA38B50446D148007B00F034 +:109BB0000105D9B904F034FB0DB1226800E00022A0 +:109BC000CC484178C06806F018FCCA481030C0780C +:109BD0008DF8000010B1012802D004E0012000E05F +:109BE00000208DF80000684606F093FD002D02D09D +:109BF00020682830206038BD30B5BD4D04466878F7 +:109C0000A04200D8FFDF686800EB041030BD70B5DB +:109C1000B74800252C46467807E02046FFF7ECFFC2 +:109C20004078641C2844C5B2E4B2B442F5D1284659 +:109C300070BD2DE9F0410C4607464FF0000800F0DA +:109C4000DEF80646FF2801D94FF013083868C01C1B +:109C500020F003023A6054EA080421D1A448F3B288 +:109C6000072124300CF00EFB09E0072C10D2DFE8AE +:109C700004F0060408080A0406009F4804E09F4810 +:109C800002E09F4800E09F480CF01CFB054600E006 +:109C9000FFDFA54200D0FFDF641CE4B2072CE4D351 +:109CA000386800EB06103860404678E5021D5143E5 +:109CB000452900D245210844C01CB0FBF2F0C0B2D7 +:109CC00070472DE9FC5F064689484FF000088B4637 +:109CD0004746444690F8019022E02046FFF78CFF6B +:109CE000050000D1FFDF687869463844C7B22846CE +:109CF000FEF7B2FF824601A92846FEF7C7FF0346DA +:109D0000BDF804005246001D81B2BDF80000001DE0 +:109D100080B208F0EDFE6A78641C00FB0288E4B2B1 +:109D20004C45DAD13068C01C20F003003060BBF134 +:109D3000000F00D000204246394608F0E7FE3168A7 +:109D400008443060BDE8FC9F69494031087100203B +:109D5000C870704766494031CA782AB10A7801EB69 +:109D600042110831814201D0012070470020704724 +:109D70002DE9F04106460078154600F00F0400205A +:109D80001080601E0F46052800D3FFDF57482A4683 +:109D9000183800EB8400394650F8043C3046BDE8E2 +:109DA000F041184770B50C46402802D0412806D132 +:109DB00020E0A07861780D18E178814201D9072070 +:109DC00070BD2078012801D9132070BDFF2D08D85F +:109DD0000AF026FD06460BF0FFFE301A801EA84250 +:109DE00001DA122070BD4248216881602179017337 +:109DF000002070BDBDE87040084600F02BB82DE98A +:109E0000F047DFF8EC900026344699F8090099F8FD +:109E10000A2099F801700244D5B299F80B20104439 +:109E200000F0FF0808E02046FFF7E6FE817B40785F +:109E300011FB0066641CE4B2BC42F4D199F809102D +:109E400099F80A0029442944414400B101200844FA +:109E5000304407E538B50446407800F00300012897 +:109E600003D002280BD0072038BD606858B1F7F73F +:109E700077FAD0B96068F7F76AFA20B915E0606838 +:109E8000F7F721FA88B969462046FCF791F80028CF +:109E9000EAD1607800F00300022808D19DF80000A4 +:109EA00028B16068F7F753FA08B1102038BD61890E +:109EB000F8290DD8208988420AD8607800F003027A +:109EC0000B48012A06D1D731026A89B28A4201D2EF +:109ED000092038BD94E80E0000F1100585E80E0059 +:109EE0000AB900210183002038BD00009C5A0200FD +:109EF000AC03002018000020574100001FAD0000F7 +:109F0000E92F0000334201002DE9F04107461446D5 +:109F10008846084601F022FD064608EB88001C2210 +:109F2000796802EBC0000D18688C58B1414638467C +:109F300001F01CFD014678680078C200082305F195 +:109F400020000CE0E88CA8B14146384601F015FD30 +:109F50000146786808234078C20005F1240008F023 +:109F600006FC38B1062121726681D0E90010C4E9EF +:109F7000031009E0287809280BD00520207266819B +:109F80006868E060002028702046BDE8F04101F0DC +:109F9000DBBC072020726681F4E72DE9F04116460C +:109FA0000D460746406801EB85011C2202EBC1010A +:109FB0004418204601F003FD40B10021708865F38C +:109FC0000F2160F31F4106200CF036FA09202070A3 +:109FD000324629463846BDE8F04195E72DE9F04183 +:109FE0000E46074600241C21F07816E004EB84039B +:109FF000726801EBC303D25C6AB1FFF77DFA05001A +:10A0000000D1FFDF6F802A4621463046FFF7C5FFAB +:10A010000120BDE8F081641CE4B2A042E6D8002033 +:10A02000F7E770B5064600241C21C0780AE000BF9F +:10A0300004EB8403726801EBC303D5182A782AB1B4 +:10A04000641CE4B2A042F3D8402070BD2821284609 +:10A050001BF013FB706880892881204670BD70B5A5 +:10A06000034600201C25DC780DE000BF00EB8006D5 +:10A070005A6805EBC6063244167816B1128A8A422F +:10A0800004D0401CC0B28442F0D8402070BDF0B56E +:10A09000044600201C26E5780EE000BF00EB800798 +:10A0A000636806EBC7073B441F788F4202D15B7899 +:10A0B000934204D0401CC0B28542EFD84020F0BD8E +:10A0C0000078032801D000207047012070470078F5 +:10A0D000022801D00020704701207047007807282F +:10A0E00001D000207047012070472DE9F04106465D +:10A0F00088461078F1781546884200D3FFDF2C7827 +:10A100001C27641CF078E4B2A04201D8201AC4B223 +:10A1100004EB8401706807EBC1010844017821B1A8 +:10A120004146884708B12C7073E72878A042E8D1EF +:10A13000402028706DE770B514460B880122A240BC +:10A14000134207D113430B8001230A22011D08F09B +:10A15000D8FA047070BD2DE9FF4F81B00878DDE9B1 +:10A160000E7B9A4691460E4640072CD4019808F083 +:10A1700085FD040000D1FFDF07F1040820461FFA27 +:10A1800088F107F0C4FE050000D1FFDF2046294614 +:10A190006A4608F00EF90098A0F80370A0F805A030 +:10A1A000284608F0B4F9017869F306016BF3C7118A +:10A1B000017020461FFA88F107F0ECFE00B9FFDFBE +:10A1C000019806F08CF806EB0900017F491C017725 +:10A1D00005B0BDE8F08F2DE9F84F0E469A4691463E +:10A1E0000746032108F006FC0446008DDFF8B88519 +:10A1F000002518B198F80000B0421ED1384608F08A +:10A200003DFD070000D1FFDF09F10401384689B2A6 +:10A2100007F07DFE050010D0384629466A4608F052 +:10A22000C8F8009800210A460180817006F010F9F4 +:10A230000098C01DCAF8000021E098F80000B04264 +:10A2400016D104F1260734F8341F012000FA06F96C +:10A2500011EA090F00D0FFDF2088012340EA09003E +:10A2600020800A22391D384608F066FA067006E09A +:10A27000324604F1340104F12600FFF75CFF0A21A5 +:10A2800088F800102846BDE8F88FFEB515460C4644 +:10A29000064602AB0C220621FFF79DFF002827D0BF +:10A2A0000299607812220A70801C487008224A8045 +:10A2B000A07002982988052381806988C180A988B7 +:10A2C0000181E988418100250C20CDE900050622A5 +:10A2D00021463046FFF73FFF2946002266F31F4123 +:10A2E000F02310460BF0FEFF6078801C60700120A8 +:10A2F000FEBDFEB514460D460622064602AB1146CB +:10A30000FFF769FF002812D0029B1320002118706C +:10A31000A8785870022058809C800620CDE9000162 +:10A320000246052329463046FFF715FF0120FEBDF2 +:10A330002DE9FE430C46804644E002AB0E22072185 +:10A340004046FFF748FF002841D060681C2267782C +:10A350008678BF1C06EB860102EBC1014518029806 +:10A360001421017047700A214180698A0181E98ABC +:10A370004181A9888180A9898181304601F0EEFA66 +:10A38000029905230722C8806F70042028700025D9 +:10A390000E20CDE9000521464046FFF7DCFE2946A8 +:10A3A00066F30F2168F31F41F023002206200BF013 +:10A3B00099FF6078FD49801C607062682046921C9D +:10A3C000FFF793FE606880784028B6D10120BDE891 +:10A3D000FE83FEB50D46064638E002AB0E2207218D +:10A3E0003046FFF7F8FE002835D068681C23C17896 +:10A3F00001EB810203EBC20284180298152202705D +:10A40000627842700A224280A2894281A2888281B7 +:10A41000084601F0A3FA014602988180618AC18052 +:10A42000E18A0181A088B8B10020207000210E20AF +:10A43000CDE900010523072229463046FFF78BFEB0 +:10A440006A68DB492846D21CFFF74FFE6868C0786F +:10A450004028C2D10120FEBD0620E6E72DE9FE43DB +:10A460000C46814644E0204601F093FAD0B302AB9B +:10A47000082207214846FFF7AEFE0028A7D06068F3 +:10A480001C2265780679AD1C06EB860102EBC10142 +:10A4900047180298B7F81080062101704570042112 +:10A4A0004180304601F05AFA0146029805230722FE +:10A4B000C180A0F804807D70082038700025CDE9A7 +:10A4C000000521464846FFF746FE294666F30F2160 +:10A4D00069F31F41F023002206200BF003FF607890 +:10A4E000801C60706268B3492046121DFFF7FDFDB5 +:10A4F000606801794029B6D1012068E72DE9F34F62 +:10A5000083B00E4680E0304601F043FA002875D053 +:10A5100071681C2091F8068008EB880200EBC200ED +:10A520000C184146304601F028FA0146A078C300D5 +:10A5300070684078C20004F1240008F034F907463E +:10A540008088E18B401A80B2002581B3AA46218B16 +:10A55000814200D808468146024602AB0721039893 +:10A56000FFF739FE010028D0BAF1000F03D0029A9C +:10A57000B888022510808B46E28B3968A9EB05006C +:10A580001FFA80FA0A440398009208F077FBED1D49 +:10A59000009A59465346009507F085FFE08B5044DA +:10A5A00080B2E083B988884209D1012508E0FFE73D +:10A5B000801C4FF0010A80B2C9E7002008E60025A0 +:10A5C000CDE90095238A072231460398FFF7C3FDA2 +:10A5D000E089401EE0818DB1A078401CA0707068B9 +:10A5E000F178427811FB02F1CAB2816901230E3081 +:10A5F00008F087F880F800800020E08372686E49D8 +:10A600003046921DFFF771FD7068817940297FF413 +:10A610007AAF0120DCE570B5064648680D46144661 +:10A620008179402910D104EB84011C2202EBC10185 +:10A63000084401F0E5F9002806D0686829468471CD +:10A640003046BDE8704059E770BDFEB50C46074680 +:10A65000002645E0204601F09CF9D8B360681C2232 +:10A66000417901EB810102EBC1014518688900B90C +:10A67000FFDF02AB082207213846FFF7ACFD0028B8 +:10A6800033D00299607816220A70801C487004202A +:10A6900048806068407901F061F90146029805231D +:10A6A000072281806989C1800820CDE90006214602 +:10A6B0003846FFF750FD6078801C6070A889698972 +:10A6C0000844B0F5803F00D3FFDFA88969890844BA +:10A6D000A8816E81626839492046521DFFF705FD49 +:10A6E000606841794029B5D10120FEBD30B5438C69 +:10A6F000458BC3F3C704002345B1838B641EED1A59 +:10A70000C38A6D1E1D4495FBF3F3E4B22CB100899E +:10A7100018B1A04200D8204603444FF6FF70834290 +:10A7200000D3034613800C7030BD2DE9FC41074671 +:10A7300016460D46486802EB86011C2202EBC10159 +:10A7400044186A4601A92046FFF7D0FFA089618915 +:10A7500001448AB2BDF80010914212D0081A00D507 +:10A76000002060816868407940280AD1204601F0C5 +:10A770003DF9002805D06868294646713846FFF73C +:10A7800064FFBDE8FC812DE9FE4F894680461546F1 +:10A790005088032108F02EF98346B8F802004028BB +:10A7A0000ED240200DE000002C000020C1A00000CF +:10A7B000CFA00000DDA0000001BA0000EDB900004C +:10A7C000403880B282460146584601F0E2F800283F +:10A7D0007ED00AEB8A001C22DBF8041002EBC000DA +:10A7E0000C18204601F0EBF8002877D1B8F80000EB +:10A7F000E18A88423CD8A189D1B348456ED1002670 +:10A800005146584601F0B2F8218C0F18608B48B9B8 +:10A81000B9F1020F62D3B8F804006083618A8842FC +:10A8200026D80226A9EB06001FFA80F9B888A28B69 +:10A83000801A002814DD4946814500DA084683B2B3 +:10A8400068886968029139680A44CDE9003208F0E5 +:10A8500003FADDE90121F61D009B009607F0EFFDEC +:10A86000A18B01EB090080B2A083618B884207D9DC +:10A87000688803B052465946BDE8F04F01F0DDB894 +:10A880001FD14FF009002872B8F802006881D7E99B +:10A890000001C5E90401608BA881284601F054F845 +:10A8A0005146584601F062F80146DBF804000823DF +:10A8B0000078C20004F1200007F059FF0020A083B7 +:10A8C0006083A0890AF0FF02401EA081688800E032 +:10A8D00004E003B05946BDE8F04F26E7BDE8FE8F1F +:10A8E0002DE9F041064615460F461C461846F6F778 +:10A8F000EAFC18B92068F6F70CFD08B1102013E443 +:10A900007168688C0978B0EBC10F01D313200BE498 +:10A910003946304601F02AF801467068082300786D +:10A92000C20005F1200007F0ECFED4E90012C0E9F6 +:10A9300000120020E3E710B50446032108F05AF89E +:10A940000146007800F00300022805D02046BDE84B +:10A95000104001F1140298E48A8A2046BDE81040B4 +:10A96000C7E470B50446032108F044F805460146E3 +:10A970002046FFF773FD002816D029462046FFF732 +:10A9800064FE002810D029462046FFF722FD00284B +:10A990000AD029462046FFF7CBFC002804D02946E0 +:10A9A0002046BDE87040A9E570BD2DE9F0410C4698 +:10A9B00080461EE0E178427811FB02F1CAB281695B +:10A9C00001230E3007F0D3FE077860681C22C1799E +:10A9D000491EC17107EB8701606802EBC10146188F +:10A9E0003946204600F0D5FF18B1304600F0E0FFB0 +:10A9F00020B16068C1790029DCD180E7FEF77CFDD9 +:10AA0000050000D1FFDF0A202872384600F0A6FFBB +:10AA100068813946204600F0B0FF0146606808238F +:10AA20004078C20006F1240007F0A1FED0E9001032 +:10AA3000C5E90310A5F80280284600F085FFB0782C +:10AA400000B9FFDFB078401EB07058E770B50C4613 +:10AA50000546032107F0CEFF01464068C279224433 +:10AA6000C2712846BDE870409FE72DE9FE4F82463F +:10AA7000507814460F464FF0000800284FD00128A8 +:10AA800007D0022822D0FFDF2068B8606068F86035 +:10AA900024E702AB0E2208215046FFF79CFB00285A +:10AAA000F2D00298152105230170217841700A2106 +:10AAB0004180C0F80480C0F80880A0F80C8062884B +:10AAC00082810E20CDE90008082221E0A6783046D8 +:10AAD00000F044FF054606EB86012C22786802EB65 +:10AAE000C1010822465A02AB11465046FFF773FBDC +:10AAF0000028C9D0029807210170217841700421F3 +:10AB0000418008218580C680CDE9001805230A46CA +:10AB100039465046FFF71FFB87F80880DEE6A67827 +:10AB2000022516B1022E13D0FFDF2A1D914602AB7B +:10AB300008215046FFF74FFB0028A5D002980121BD +:10AB4000022E0170217841704580868002D005E098 +:10AB50000625EAE7A188C180E1880181CDE9009856 +:10AB60000523082239465046D4E710B50446032190 +:10AB700007F040FF014600F108022046BDE8104002 +:10AB800073E72DE9F05F0C4601281DD0957992F806 +:10AB90000480567905EB85011F2202EBC10121F0EB +:10ABA000030B08EB060111FB05F14FF6FF7202EAF9 +:10ABB000C10909F1030115FB0611F94F21F0031A30 +:10ABC00040B101283DD124E06168E57891F800802A +:10ABD0004E78DFE75946786807F047FD606000B9B6 +:10ABE000FFDF594660681AF06AFDE57051467868E3 +:10ABF00007F03BFD6168486100B9FFDF60684269AA +:10AC000002EB09018161606880F80080606846702D +:10AC100017E0606852464169786807F051FD5A466E +:10AC20006168786807F04CFD032007F08BFE04464E +:10AC3000032007F08FFE201A012802D1786807F060 +:10AC400009FD0BEB0A00BDE8F09F0246002102203F +:10AC500097E773B5D24D0A202870009848B10024B8 +:10AC60004FEA0D0007F0E3FC002C01D10099696068 +:10AC70007CBD01240020F5E770B50C46154638214F +:10AC800020461AF01CFD012666700A2104F11C0002 +:10AC90001AF015FD05B9FFDF297A207861F301006C +:10ACA0002070A879002817D02A4621460020FFF7F7 +:10ACB00068FF6168402088706168C87061680871C9 +:10ACC0006168487161688871616828880881616875 +:10ACD000688848816068868170BDC878002802D085 +:10ACE000002201204DE7704770B50546002165F34D +:10ACF0001F4100200BF0A0FB0321284607F07AFE3D +:10AD0000040000D1FFDF21462846FFF767F900283D +:10AD100004D0207840F010002070012070BD2DE993 +:10AD2000FF4180460E460F0CFEF7E6FB050007D0FC +:10AD30006F800321384607F05DFE040008D106E06D +:10AD400004B03846BDE8F0411321F9F739BEFFDF02 +:10AD50005FEA080005D0B8F1060F18D0FFDFBDE8A4 +:10AD6000FF8120782A4620F0080020700020ADF8EE +:10AD7000020002208DF800004FF6FF70ADF80400CD +:10AD8000ADF8060069463846F9F711FAE7E7C6F369 +:10AD9000072101EB81021C23606803EBC202805C87 +:10ADA000042803D008280AD0FFDFD8E7012000904C +:10ADB0004FF440432A46204600F008FECFE704B097 +:10ADC0002A462046BDE8F041FFF7E7B82DE9F05FDD +:10ADD0000027B0F80A9090460C4605463E46B9F169 +:10ADE000400F01D2402001E0A9F140001FFA80FA93 +:10ADF000287AC01E08286BD2DFE800F00D04192065 +:10AE000058363C4772271026002C6CD0D5E9030138 +:10AE1000C4E902015CE070271226002C63D00A22EC +:10AE200005F10C0104F108001AF0EDFB50E0712768 +:10AE30000C26002C57D0E868A06049E07427102643 +:10AE40009CB3D5E90301C4E902016888032107F036 +:10AE5000D1FD8346FEF750FB02466888508051467C +:10AE60005846FFF751F833E075270A26ECB1A88958 +:10AE700020812DE076271426BCB105F10C0004F1E9 +:10AE8000080307C883E8070022E07727102664B18B +:10AE9000D5E90301C4E902016888032107F0AAFD8E +:10AEA00001466888FFF781FD12E01CE07327082641 +:10AEB000CCB16888032107F09DFD01460078C006EB +:10AEC00006D56888FFF78AF810B96888F8F786FD14 +:10AED000A8F800602CB12780A4F8069066806888E6 +:10AEE000A0800020AFE6A8F80060FAE72DE9FC4159 +:10AEF0000C461E4617468046032107F07BFD05469B +:10AF00000A2C0AD2DFE804F0050505050505090944 +:10AF10000907042303E0062301E0FFDF0023CDE956 +:10AF20000076224629464046FFF715F929E438B550 +:10AF30000546A0F57F40FF3830D0284607F08CFE4C +:10AF4000040000D1FFDF204607F011FA002815D0D9 +:10AF500001466A46204607F02CFA00980321B0F813 +:10AF60000540284607F046FD0546052C03D0402C39 +:10AF700005D2402404E0007A80B1002038BD403C76 +:10AF8000A4B2214600F005FD40B1686804EB8401DD +:10AF90003E2202EBC101405A0028EFD0012038BD0B +:10AFA0002C0000202DE9F04F054689B0408807F0BD +:10AFB00053FE040000D1FFDF06AA2046696800F0B6 +:10AFC000C1FC069C001F34F8031F21806388638046 +:10AFD000228881B28A4205D1042B0AD0052B1DD0CC +:10AFE000062B15D02A462046FFF7CDFB09B0BDE859 +:10AFF000F08F1646241D2A4621463046F7F73FFAC1 +:10B000000828F3D12A4621463046FCF7F4FBEDE749 +:10B010006888211D6B68FAF739FCE7E717466888EE +:10B02000032107F0E7FC4FF000088DF80480064686 +:10B03000ADF80680042FD9D36279002AD6D02079C2 +:10B040004FF6FF794FF01C0A13282CD008DC01289A +:10B0500078D0062847D0072875D0122874D106E08A +:10B06000142872D0152871D016286DD1ACE10C2FA0 +:10B070006AD1307800F00301022965D140F0080060 +:10B0800030706079B07001208DF804002089ADF82F +:10B0900008006089ADF80A00A089ADF80C00E089CD +:10B0A000ADF80E0019E0B07890429FD130780107DA +:10B0B0009CD5062F9AD120F0080030706888414650 +:10B0C00060F31F4100200BF0B7F902208DF8040057 +:10B0D000ADF808902089ADF80A0068882A4601A9D1 +:10B0E000F9F765F882E7082F80D12789B4F80A902C +:10B0F000402F01D2402001E0A7F1400080B28046FD +:10B100000146304600F045FC08B3716808EB880042 +:10B110002C2202EBC000095A4945E3D1FE4807AA98 +:10B12000D0E90210CDE9071060798DF81C0008F015 +:10B13000FF048DF81E4068883146FFF796FC2A46CA +:10B14000214639E0B6E014E03CE039E0E6E0F248C0 +:10B15000D0E90010CDE907106079ADF820708DF8C6 +:10B160001C00ADF82290688807AA3146FFF77DFCE5 +:10B170003CE7082FB6D16089B4F80880402801D296 +:10B18000402000E0403887B23946304600F001FCEC +:10B190000028A7D007EB870271680AEBC2000844B9 +:10B1A000028A42459ED1017808299BD14078617975 +:10B1B000884297D1F9B22A463046FEF7EEFE15E7EF +:10B1C0000E2F07D0CDF81C80CDF8208060798DF847 +:10B1D0001C00C8E76189E7898B46B4F80C903046BB +:10B1E000FEF73DFFABF14001402901D309204AE0C1 +:10B1F000B9F1170F01D3172F01D20B2043E04028DC +:10B200000ED000EB800271680AEBC200084401789E +:10B21000012903D1407861798842A9D00A2032E01F +:10B220003046FEF7FEFE014640282BD001EB81039D +:10B2300072680AEBC30002EB0008012288F80020C4 +:10B24000627988F80120706822894089B84200D963 +:10B250003846248A03232B72AA82EF812882A5F81C +:10B260000C906C82084600F079FB6881A8F8149075 +:10B27000A8F81870A8F80E40A8F810B0284600F0FA +:10B2800063FBB3E6042005212972A5F80A80E88152 +:10B2900001212973A049D1E90421CDE90721617970 +:10B2A0008DF81C10ADF81E00688807AA3146FFF71C +:10B2B000DCFBE3E7062FE4D3B078904215D1307879 +:10B2C000010712D520F0080030706888414660F30D +:10B2D0001F4100200BF0B0F802208DF804002089F7 +:10B2E000ADF80800ADF80A90F7E604213046FEF705 +:10B2F000CEFE04464028C4D00220830300902A4694 +:10B300002146304600F062FB4146688864F30F2115 +:10B3100060F31F4106200BF08FF867E60E2FB0D1C7 +:10B3200004213046FEF7B3FE81464028A9D04146AD +:10B33000688869F30F2160F31F4106200BF07CF849 +:10B34000208A0790E08900907068A7894089B842F8 +:10B3500000D938468346B4F80A80208905904846CB +:10B3600000F0FCFA6881079840B10220079B00902A +:10B370002A464946304600F029FB37E6B8F1170F58 +:10B380001ED3172F1CD30420287200986882EF81E7 +:10B39000A5F810B0A5F80C8009EB89020AEBC200F1 +:10B3A0007168009A0C180598A4F81480A4F818B0D5 +:10B3B000E2812082284600F0C7FA0620207015E6B8 +:10B3C00001200B230090D3E7082FA6D12189304616 +:10B3D000FEF745FE074640289FD007EB87027168BD +:10B3E0000AEBC2000844804600F0E9FA002894D134 +:10B3F0006489B8F80E002044B0F5803F05D3688812 +:10B400003A46314600F019FBF0E5002C85D0A8F84B +:10B410000E0068883A463146FFF7FDF8082028728A +:10B42000384600F09BFA6881AC8127E770B50D467D +:10B430000646032107F0DEFA040004D02078000756 +:10B4400004D5112070BD43F2020070BD2A4621468A +:10B450003046FEF71AFF18B9286860616868A06175 +:10B46000207840F008002070002070BD70B50D46B7 +:10B470000646032107F0BEFA040004D02078000736 +:10B4800004D4082070BD43F2020070BD2A46214654 +:10B490003046FEF72EFF00B9A582207820F0080084 +:10B4A0002070002070BD2DE9F04F0E4691B080460F +:10B4B000032107F09FFA0446404607F0DFFB0746EA +:10B4C0000020079008900990ADF830000A90029093 +:10B4D0000390049004B9FFDF0DF108091FBBFFDFE3 +:10B4E00021E038460BA9002206F004FE9DF82C004E +:10B4F00000F07F050A2D00D3FFDF6019017F491E90 +:10B5000001779DF82C0000060DD52A460CA907A846 +:10B51000FEF711FE02E00000AC5A020019F8051017 +:10B52000491C09F80510761EF6B2DAD204F134008F +:10B53000FA4D04F1260BDFF8E8A304F12A07069080 +:10B5400010E05846069900F06AFA064628700A2864 +:10B5500000D3FFDF5AF8261040468847E08CC05DD4 +:10B56000B04202D0208D0028EBD10A202870EC4D8B +:10B570004E4628350EE00CA907A800F050FA044604 +:10B58000375D55F8240000B9FFDF55F8242039460F +:10B5900040469047BDF81E000028ECD111B026E5CA +:10B5A00010B5032107F026FA040000D1FFDF0A21BD +:10B5B00004F11C001AF083F8207840F00400207099 +:10B5C00010BD10B50C46032107F014FA2044007F8B +:10B5D000002800D0012010BD2DE9F84F89461546FE +:10B5E0008246032107F006FA070004D02846F5F743 +:10B5F0006AFE40B903E043F20200BDE8F88F484616 +:10B60000F5F787FE08B11020F7E7786828B1698858 +:10B610000089814201D90920EFE7B9F800001C2414 +:10B6200018B1402809D2402008E03846FEF7F9FC5E +:10B630008046402819D11320DFE7403880B2804689 +:10B640000146384600F0A5F948B108EB8800796852 +:10B6500004EBC000085C012803D00820CDE70520DA +:10B66000CBE7FDF749FF06000BD008EB88007968AF +:10B6700004EBC0000C18B9F8000020B1E88910B143 +:10B6800013E01120B9E72888172802D36888172803 +:10B6900001D20720B1E7686838B12B1D2246414628 +:10B6A0003846FFF71DF90028A7D104F10C026946BE +:10B6B0002046FFF71BF8288860826888E082B9F886 +:10B6C000000030B102202070E889A080E889A0B194 +:10B6D0002BE003202070A889A08078688178402919 +:10B6E00005D180F8028039465046FEF721FE4046DB +:10B6F00000F034F9A9F8000021E07868218B408936 +:10B70000884200D908462083A6F802A0042030729F +:10B71000B9F800007081E0897082F181208B30825D +:10B72000A08AB081304600F00FF97868C1784029CE +:10B7300005D180F8038039465046FEF74AFE0020C6 +:10B740005BE770B50D460646032107F053F9040088 +:10B7500003D0402D04D2402503E043F2020070BD27 +:10B76000403DADB2294600F014F958B105EB850112 +:10B770001C22606802EBC101084400F020F918B1F6 +:10B78000082070BD052070BD2A462146304600F0D5 +:10B7900054F9002070BD2DE9F0410D461646804653 +:10B7A000032107F027F90446402D01D2402500E08F +:10B7B000403DADB28CB1294600F0EBF880B105EB0D +:10B7C00085011C22606802EBC1014718384600F071 +:10B7D000F6F838B10820BDE8F08143F20200FAE73C +:10B7E0000520F8E733463A4629462046FFF778F821 +:10B7F0000028F0D1EAB221464046FEF796FF00202D +:10B80000E9E72DE9F0410D4616468046032107F091 +:10B81000F1F80446402D01D2402500E0403DAFB292 +:10B8200024B13046F5F74FFD38B902E043F202008B +:10B83000D1E73068F5F747FD08B11020CBE739466E +:10B84000204600F0A6F860B107EB87011C22606873 +:10B8500002EBC1014518284600F0B1F818B10820E4 +:10B86000B9E70520B7E7B088A98A884201D90C203A +:10B87000B1E76168E88C4978B0EBC10F01D31320C0 +:10B88000A9E73946204600F078F8014660680823A9 +:10B890004078C20005F1240006F033FFD6E900121B +:10B8A000C0E90012FAB221464046FEF7B4FE00207D +:10B8B00091E72DE9F0470D461F469046814603214A +:10B8C00007F098F80446402D01D2402001E0A5F190 +:10B8D000400086B23CB14DB13846F5F738FD50B165 +:10B8E0001020BDE8F08743F20200FAE76068C8B1B3 +:10B8F000A0F80C8024E03146204600F04AF888B1D8 +:10B9000006EB86011C22606802EBC101451828463F +:10B9100000F055F840B10820E3E700002C000020BB +:10B92000C45A02000520DCE7A5F80880F2B22146DF +:10B930004846FEF7FAFE1FB1A88969890844388095 +:10B940000020CEE706F035BD017821F00F01491C3B +:10B9500021F0F00110310170FDF7D1BD10B50446A2 +:10B96000402800D9FFDF4034A0B210BD40684269D2 +:10B970000078484302EBC0007047C2784068037803 +:10B9800012FB03F24378406901FB032100EBC10085 +:10B990007047C2788A4209D9406801EB81011C22B4 +:10B9A00002EBC101405C08B10120704700207047E4 +:10B9B0000078062801D901207047002070470078E0 +:10B9C000062801D00120704700207047F0B401EB39 +:10B9D00081061C27446807EBC6063444049D0526EF +:10B9E0002670E3802571F0BCFEF78EBA10B5418950 +:10B9F00011B1FFF7DDFF08B1002010BD012010BD1F +:10BA000010B5C18C8278B1EBC20F04D9C18911B1D4 +:10BA1000FFF7CEFF08B1002010BD012010BD10B50A +:10BA20000C4601230A22011D06F0A1FE00782188A0 +:10BA3000012282409143218010BDF0B402EB8205C7 +:10BA40001C264C6806EBC505072363554B681C791B +:10BA5000402C03D11A71F0BCFEF700BDF0BC70475A +:10BA600010B5EFF3108000F0010472B6F948417888 +:10BA7000491C41704078012801D10AF01DF9002CC1 +:10BA800000D162B610BD70B5F24CA07848B901255E +:10BA9000A570FFF7E5FF0AF020F920B100200AF0B9 +:10BAA000EAF8002070BD4FF08040E570C0F8045304 +:10BAB000F7E770B5EFF3108000F0010572B6E54CC2 +:10BAC000607800B9FFDF6078401E6070607808B968 +:10BAD0000AF0F6F8002D00D162B670BDDD4810B551 +:10BAE000817821B10021C1708170FFF7E2FF002051 +:10BAF00010BD10B504460AF0F0F8D6498978084020 +:10BB000000D001202060002010BD10B5FFF7A8FF75 +:10BB10000AF0E3F802220123CE49540728B1CE48A7 +:10BB2000026023610320087202E00A72C4F8043341 +:10BB30000020887110BD2DE9F05FDFF8189342787E +:10BB4000817889F80420002689F80510074689F8CD +:10BB500006600078DFF804B3354620B1012811D023 +:10BB6000022811D0FFDF0AF0CAF84FF0804498B1E4 +:10BB70000AF0CCF8B0420FD130460AF0CBF80028DA +:10BB8000FAD041E00126EEE7FFF76AFF5846016868 +:10BB9000C907FCD00226E6E70120E060C4F80451A2 +:10BBA000AF490E600107D1F84412AD4AC1F34231EA +:10BBB00024321160AA49343108604FF0020AC4F8F7 +:10BBC00004A3A060A7480168C94341F3001101F133 +:10BBD0000108016841F01001016000E020BFD4F8C5 +:10BBE00004010028FAD030460AF094F80028FAD070 +:10BBF000B8F1000F04D19B48016821F010010160E9 +:10BC0000C4F808A3C4F8045199F805004E4688B159 +:10BC1000387878B90AF061F880460AF0F5F90146FB +:10BC20006FF00042B8F1000F02D0C6E9032101E035 +:10BC3000C6E90312DBF80000C00701D00AF049F89A +:10BC4000387810B13572BDE8F09F4FF01808C4F88D +:10BC50000883C4F82C510127C4F81870D4F82C01BB +:10BC60000028FBD0C4F80C51C4F810517948C01D0D +:10BC70000AF062F83570FFF748FF6761784930795C +:10BC800020310860C4F80483DDE770B5050000D1F9 +:10BC9000FFDF4FF080424FF0FF30C2F80803002171 +:10BCA000C2F80011C2F80411C2F80C11C2F8101148 +:10BCB000684C61700AF01DF810B10120A07060702E +:10BCC00066480068C00701D00AF003F82846BDE8BE +:10BCD000704030E75F48007A002800D001207047AC +:10BCE0002DE9FF5F6048D0F800805F4A5F49083265 +:10BCF00011608406D4F8080100B10120D4F82411A1 +:10BD000001B101218A46D4F81C1101B101218946F3 +:10BD1000D4F8201109B1012700E00027D4F8001160 +:10BD200001B101218B46D4F8041101B10121039125 +:10BD3000D4F80C1101B101210291D4F8101101B114 +:10BD40000121444D019129780026009120B1C4F8C9 +:10BD50000861012009F08FFFBAF1000F04D0C4F888 +:10BD60002461092009F087FFB9F1000F04D0C4F85D +:10BD70001C610A2009F07FFF27B1C4F820610B2065 +:10BD800009F079FF3348C01D09F0DEFF00B1FFDF85 +:10BD9000DFF8C4900127BBF1000F10D0C4F808737E +:10BDA000E87818B1EE70002009F065FF287A0228C3 +:10BDB00005D1032028720221C9F8001027610398D9 +:10BDC00008B1C4F80461029850B1C4F80C61287A33 +:10BDD000032800D0FFDFC9F800602F72FFF769FE6B +:10BDE000019838B1C4F81061287A012801D100F017 +:10BDF0005DF86761009838B12E70287A012801D16A +:10BE0000FFF783FEFFF755FE1248C01D09F0B2FF91 +:10BE10001549091DC1F80080BDE8FF9F0D4810B508 +:10BE2000C01D09F091FF0B4940B1012008704FF08F +:10BE3000E021C1F80002BDE8104011E6087A0128AF +:10BE400001D1FFF762FE0348BDE81040C01D09F0B4 +:10BE500091BF00003C000020340C00400C04004066 +:10BE60001805004010ED00E010050240010000013F +:10BE700070B5224CA07808B909F022FF012085078F +:10BE8000A861207A002603280AD100BFD5F80C014A +:10BE900020B9002009F03EFF0028F7D1C5F80C6159 +:10BEA00026724FF0FF30C5F8080370BD70B5134C13 +:10BEB0006079F0B1012803D0A179401E814218DADF +:10BEC00009F00BFF05460AF09FF86179012902D9B4 +:10BED000A179491CA1710DB1216900E0E168411A05 +:10BEE000022902DA11F1020F06DC0DB1206100E037 +:10BEF000E060BDE8704008E670BD00003C00002036 +:10BF000010B5202000F07FF8202000F08DF84D497A +:10BF1000202081F80004F5F771FA4B4908604B487E +:10BF2000D0F8041341F00101C0F80413D0F8041351 +:10BF300041F08071C0F80413424901201C39C1F856 +:10BF4000000110BD10B5202000F05DF83E48002132 +:10BF5000C8380160001D01603D4A481E10603B4A20 +:10BF6000C2F80803384B1960C2F80001C2F860013A +:10BF700038490860BDE81040202000F055B8344929 +:10BF80003548091F086070473149334808607047D9 +:10BF90002D48C8380160001D521E026070472C49B0 +:10BFA00001200860BFF34F8F70472DE9F041284909 +:10BFB000D0F8188028480860244CD4F800010025E7 +:10BFC000244E6F1E28B14046F5F776F940B900219E +:10BFD00011E0D4F8600198B14046F5F76DF948B129 +:10BFE000C4F80051C4F860513760BDE8F04120202A +:10BFF00000F01AB831684046BDE8F04119F08ABB3C +:10C00000FFDFBDE8F08100280DDB00F01F020121F9 +:10C0100091404009800000F1E020C0F88011BFF39A +:10C020004F8FBFF36F8F7047002809DB00F01F02AE +:10C03000012191404009800000F1E020C0F8801209 +:10C040007047000020E000E0C80602400000024007 +:10C050001805024000040240010000010F4A126866 +:10C060000D498A420CD118470C4A12680A4B9A4271 +:10C0700006D101B509F09AFFFFF781FFBDE8014045 +:10C08000074909680958084706480749054A064B01 +:10C090007047000000000000BEBAFECA5400002035 +:10C0A000040000208013002080130020F8B51D46F6 +:10C0B000DDE906470E000AD006F0E0FD2346FF1D2D +:10C0C000BCB231462A46009406F0EDF9F8BDD0190D +:10C0D0002246194619F052FA2046F8BD70B50D46B1 +:10C0E0000446102119F0C9FA258117206081A07B30 +:10C0F00040F00A00A07370BD4FF6FF720A8001463F +:10C1000002200AF099B9704700897047827BD307F3 +:10C1100001D1920703D48089088000207047052050 +:10C120007047827B920700D58181704701460020CD +:10C13000098841F6FE52114200D00120704700B537 +:10C140000346807BC00701D0052000BD59811846F9 +:10C15000FFF7ECFFC00703D0987B40F00400987312 +:10C16000987B40F001009873002000BD827B52074D +:10C1700000D509B14089704717207047827B61F371 +:10C18000C302827370472DE9FC5F0E4604460178B6 +:10C190009646012000FA01F14DF6FF5201EA02092C +:10C1A00062684FF6FF7B1188594502D10920BDE82E +:10C1B000FC9FB9F1000F05D041F6FE55294201D090 +:10C1C0000120F4E741EA090111801D0014D0002389 +:10C1D0002B7094F800C0052103221F464FF0020A7D +:10C1E000BCF10E0F76D2DFE80CF0F909252F476479 +:10C1F0006B77479193B4D1D80420D8E76168208940 +:10C200008B7B9B0767D517284AD30B89834247D37B +:10C210008989172901D3814242D185F800A0A5F868 +:10C2200001003280616888816068817B21F00201B1 +:10C230008173C6E0042028702089A5F80100608978 +:10C24000A5F803003180BCE0208A3188C01D1FFAA8 +:10C2500080F8414524D3062028702089A5F80100E4 +:10C260006089A5F80300A089A5F805000721208AA8 +:10C27000CDE90001636941E00CF0FF00082810D00F +:10C28000082028702089A5F801006089A5F803001E +:10C2900031806A1D694604F10C0008F057F910B1AD +:10C2A0005EE01020EDE730889DF8001008443080F3 +:10C2B00087E00A2028702089A5F80100328044E038 +:10C2C0000C2028702089A5F801006089A5F80300DA +:10C2D00031803AE082E064E02189338800EB41025A +:10C2E0001FFA82F843453BD3B8F1050F38D30E222D +:10C2F0002A700BEA4101CDE90010E36860882A4604 +:10C300007146FFF7D3FEA6F800805AE0402028705F +:10C3100060893188C01C1FFA80F8414520D32878F5 +:10C32000714620F03F00123028702089A5F80100E6 +:10C330006089CDE9000260882A46E368FFF7B6FE0F +:10C34000A6F80080287840063BD461682089888060 +:10C3500037E0A0893288401D1FFA80F8424501D29B +:10C3600004273DE0162028702089A5F80100608987 +:10C37000A5F80300A089CDE9000160882A4671462E +:10C380002369FFF793FEA6F80080DEE718202870E7 +:10C39000207A6870A6F800A013E061680A88920409 +:10C3A00001D405271CE0C9882289914201D00627C3 +:10C3B00016E01E21297030806068018821F4005148 +:10C3C0000180B9F1000F0BD0618878230022022090 +:10C3D00009F088FF61682078887006E033800327C1 +:10C3E0006068018821EA090101803846DFE62DE90D +:10C3F000FF4F85B01746129C0D001E461CD03078AA +:10C40000C10703D000F03F00192801D9012100E045 +:10C4100000212046FFF7AAFEA8420DD32088A0F5F0 +:10C420007F41FF3908D03078410601D4000605D598 +:10C43000082009B0BDE8F08F0720FAE700208DF84A +:10C4400000008DF8010030786B1E00F03F0C0121D8 +:10C45000A81E4FF0050A4FF002094FF0030B9AB2E5 +:10C46000BCF1200F75D2DFE80CF08B10745E74689D +:10C47000748C749C74B674BB74C974D574E274748F +:10C4800074F274F074EF74EE748B052D78D18DF81E +:10C490000090A0788DF804007088ADF8060030791F +:10C4A0008DF80100707800F03F000C2829D00ADCDC +:10C4B000A0F10200092863D2DFE800F012621562E1 +:10C4C0001A621D622000122824D004DC0E281BD022 +:10C4D0001028DBD11BE016281FD01828D6D11FE06A +:10C4E0002078800701E020784007002848DAEFE054 +:10C4F00020780007F9E72078C006F6E72078800664 +:10C50000F3E720784006F0E720780006EDE7208882 +:10C51000C005EAE720884005E7E720880005E4E752 +:10C520002088C004E1E72078800729D5032D27D192 +:10C530008DF800B0B6F8010082E0217849071FD5D8 +:10C54000062D1DD381B27078012803D0022817D19F +:10C5500002E0CAE0022000E0102004228DF8002052 +:10C5600072788DF80420801CB1FBF0F2ADF8062043 +:10C5700092B242438A4203D10397ADF80890A7E0F4 +:10C580007AE02078000777D598B282088DF800A06D +:10C59000ADF80420B0EB820F6ED10297ADF8061013 +:10C5A00096E02178C90667D5022D65D381B20620B1 +:10C5B0008DF80000707802285ED300BFB1FBF0F266 +:10C5C0008DF80400ADF8062092B242438A4253D15E +:10C5D000ADF808907BE0207880064DD5072003E079 +:10C5E000207840067FD508208DF80000A088ADF89F +:10C5F0000400ADF80620ADF8081068E020780006C9 +:10C6000071D50920ADF804208DF80000ADF80610B2 +:10C6100002975DE02188C90565D5022D63D381B2FB +:10C620000A208DF80000707804285CD3C6E72088C3 +:10C63000400558D5012D56D10B208DF80000A0885B +:10C64000ADF8040044E021E026E016E0FFE7208892 +:10C65000000548D5052D46D30C208DF80000A08894 +:10C66000ADF80400B6F803006D1FADF80850ADF842 +:10C670000600ADF80AA02AE035E02088C00432D5D3 +:10C68000012D30D10D208DF8000021E0208880049C +:10C6900029D4B6F80100E080A07B000723D5032D44 +:10C6A00021D3307800F03F001B2818D00F208DF8E0 +:10C6B0000000208840F40050A4F80000B6F8010003 +:10C6C000ADF80400ED1EADF80650ADF808B00397C4 +:10C6D00069460598F5F71EFC050008D016E00E2007 +:10C6E0008DF80000EAE7072510E008250EE0307815 +:10C6F00000F03F001B2809D01D2807D00220059913 +:10C7000009F09AFE208800F400502080A07B4007AA +:10C7100008D52046FFF70AFDC00703D1A07B20F013 +:10C720000400A073284684E61FB5022806D1012024 +:10C730008DF8000088B26946F5F7ECFB1FBD0000DC +:10C74000F8B51D46DDE906470E000AD006F096FA58 +:10C750002346FF1DBCB231462A46009405F0A3FED5 +:10C76000F8BDD0192246194618F008FF2046F8BD3A +:10C770002DE9FF4F8DB09B46DDE91B57DDF87CA00E +:10C780000C46082B05D0E06901F002F950B11020E9 +:10C79000D2E02888092140F0100028808AF8001093 +:10C7A000022617E0E16901208871E2694FF4205107 +:10C7B0009180E1698872E06942F601010181E069D6 +:10C7C000002181732888112140F0200028808AF8F8 +:10C7D0000010042638780A900A2038704FF00209B9 +:10C7E00004F118004D460C9001F095FBB04681E035 +:10C7F000BBF1100F0ED1022D0CD0A9EB0800801C4C +:10C8000080B20221CDE9001005AB52461E990D9869 +:10C81000FFF796FFBDF816101A98814203D9F74822 +:10C8200000790F9004E003D10A9808B138702FE026 +:10C830004FF00201CDE900190DF1160352461E9981 +:10C840000D98FFF77DFF1D980088401B801B83B269 +:10C85000C6F1FF00984200D203461E990BA8D9B139 +:10C860005FF00002DDF878C0CDE9032009EB060196 +:10C8700089B2CDE901C10F980090BDF816100022D1 +:10C880000D9801F0CBFB387070B1C0B2832807D08F +:10C89000BDF8160020833AE00AEB09018A19E1E7A6 +:10C8A000022011B0BDE8F08FBDF82C00811901F015 +:10C8B000FF08022D0DD09AF80120424506D1BDF89F +:10C8C0002010814207D0B8F1FF0F04D09AF8018000 +:10C8D0001FE08AF80180C94800680178052902D163 +:10C8E000BDF81610818009EB08001FFA80F905EBEE +:10C8F000080085B2DDE90C1005AB0F9A01F00EFBC4 +:10C9000028B91D980088411B4145BFF671AF022D23 +:10C9100013D0BBF1100F0CD1A9EB0800801C81B221 +:10C920000220CDE9000105AB52461E990D98FFF794 +:10C9300007FF1D980580002038700020B1E72DE921 +:10C94000F8439C46089E13460027B26B9AB3491FD2 +:10C950008CB2F18FA1F57F45FF3D05D05518AD880C +:10C960002944891D8DB200E000252919B6F83C80C4 +:10C970000831414520D82A44BCF8011022F8021B96 +:10C98000BCF8031022F8021B984622F8024B91468D +:10C9900006F062F94FF00C0C41464A462346CDF8AA +:10C9A00000C005F04CFDF587B16B00202944A41DA3 +:10C9B0002144088003E001E0092700E0832738468E +:10C9C000BDE8F88310B50B88848F9C420CD9846B2A +:10C9D000E018048844B1848824F40044A41D23444E +:10C9E0000B801060002010BD0A2010BD2DE9F0471B +:10C9F0008AB00025904689468246ADF81850072730 +:10CA00004BE0059806888088000446D4A8F80060AA +:10CA100007A8019500970295CDE903504FF40073E4 +:10CA200000223146504601F0F9FA04003CD1BDF82D +:10CA30001800ADF82000059804888188B44216D10A +:10CA40000A0414D401950295039521F4004100973E +:10CA5000049541F4804342882146504601F0B4F8E1 +:10CA600004000BD10598818841F40041818005AA1A +:10CA700008A94846FFF7A6FF0400DCD000970598F8 +:10CA800002950195039504950188BDF81C3000229C +:10CA9000504601F099F80A2C06D105AA06A9484685 +:10CAA000FFF790FF0400ACD0ADF8185004E00598F3 +:10CAB000818821F40041818005AA06A94846FFF734 +:10CAC00081FF0028F3D00A2C03D020460AB0BDE82D +:10CAD000F0870020FAE710B50C46896B86B051B19B +:10CAE0000C218DF80010A18FADF80810A16B0191F9 +:10CAF0006946FAF718FB00204FF6FF71A063E18743 +:10CB0000A08706B010BD2DE9F0410D460746896BA0 +:10CB10000020069E1446002911D0012B0FD1324669 +:10CB200029463846FFF762FF002808D1002C06D0BE +:10CB3000324629463846BDE8F04100F038BFBDE82E +:10CB4000F0812DE9FC411446DDE9087C0E46DDE963 +:10CB50000A15521DBCF800E092B2964502D2072099 +:10CB6000BDE8FC81ACF8002017222A70A5F801600E +:10CB7000A5F803300522CDE900423B462A46FFF7DF +:10CB8000DFFD0020ECE770B50C4615464821204635 +:10CB900018F095FD04F1080044F81C0F00204FF632 +:10CBA000FF71E06161842084A5841720E08494F8FB +:10CBB0002A0040F00A0084F82A0070BD4FF6FF7288 +:10CBC0000A800146032009F037BC30B585B00C4619 +:10CBD0000546FFF780FFA18E284629B101218DF877 +:10CBE00000106946FAF79FFA0020E0622063606354 +:10CBF00005B030BDB0F8400070470000580000207C +:10CC000090F84620920703D4408808800020F3E77C +:10CC10000620F1E790F846209207EDD5A0F84410E1 +:10CC2000EAE70146002009880A0700D5012011F033 +:10CC3000F00F01D040F00200CA0501D540F0040019 +:10CC40008A0501D540F008004A0501D540F01000E2 +:10CC50000905D1D540F02000CEE700B5034690F895 +:10CC60004600C00701D0062000BDA3F842101846B8 +:10CC7000FFF7D7FF10F03E0F05D093F8460040F0C5 +:10CC8000040083F8460013F8460F40F001001870C6 +:10CC9000002000BD90F84620520700D511B1B0F831 +:10CCA0004200A9E71720A7E710F8462F61F3C30257 +:10CCB0000270A1E72DE9FF4F9BB00E00DDE92B3498 +:10CCC000DDE92978289D24D02878C10703D000F019 +:10CCD0003F00192801D9012100E000212046FFF77B +:10CCE000D9FFB04215D32878410600F03F010CD49B +:10CCF0001E290CD0218811F47F6F0AD13A8842B1E5 +:10CD0000A1F57F42FF3A04D001E0122901D10006CB +:10CD100002D504201FB0C5E5F9491D984FF0000A5F +:10CD200008718DF818A08DF83CA00FAA0A60ADF824 +:10CD30001CA0ADF850A02978994601F03F02701F61 +:10CD40005B1C04F1180C4FF0060E4FF0040BCDF8ED +:10CD500058C01F2A7ED2DFE802F07D7D107D267D3F +:10CD6000AC7DF47DF37DF27DF17DF47DF07D7D7D04 +:10CD7000EF7DEE7D7D7D7D7DED0094F84610B5F86C +:10CD80000100890701D5032E02D08DF818B022E3E7 +:10CD90004FF40061ADF85010608003218DF83C1015 +:10CDA000ADF84000D8E2052EEFD1B5F801002083A0 +:10CDB000ADF81C00B5F80310618308B1884201D9B1 +:10CDC00001207FE10020A07220814FF6FF702084B7 +:10CDD000169801F0A0F8052089F8000002200290C2 +:10CDE00083460AAB1D9A16991B9801F097F890BBE1 +:10CDF0009DF82E00012804D0022089F8010010209F +:10CE000003E0012089F8010002200590002203A917 +:10CE10000BA807F09BFBE8BB9DF80C00059981422D +:10CE20003DD13A88801CA2EB0B01814237DB02998D +:10CE30000220CDE900010DF12A034A4641461B9824 +:10CE4000FFF77EFC02980BF1020B801C80B217AA40 +:10CE500003A901E0A0E228E002900BA807F076FB0E +:10CE600002999DF80C00CDE9000117AB4A464146F6 +:10CE70001B98FFF765FC9DF80C100AAB0BEB01004B +:10CE80001FFA80FB02981D9A084480B202901699FE +:10CE90001B9800E003E001F041F80028B6D0BBF198 +:10CEA000020F02D0A7F800B053E20A208DF8180054 +:10CEB0004FE200210391072EFFF467AFB5F80100A0 +:10CEC0002083ADF81C00B5F80320628300283FF4EE +:10CED00077AF90423FF674AF0120A072B5F805001D +:10CEE00020810020A073E06900F052FD78B9E1696B +:10CEF00001208871E2694FF420519180E1698872C4 +:10CF0000E06942F601010181E06900218173F01FAF +:10CF100020841E98606207206084169800F0FBFF52 +:10CF2000072089F800000120049002900020ADF84D +:10CF30002A0028E01DE2A3E13AE1EAE016E2AEE0D1 +:10CF400086E049E00298012814D0E0698079012840 +:10CF500003D1BDF82800ADF80E00049803ABCDE96D +:10CF600000B04A4641461B98FFF7EAFB0498001DB3 +:10CF700080B20490BDF82A00ADF80C00ADF80E00A8 +:10CF8000059880B202900AAB1D9A16991B9800F082 +:10CF9000C5FF28B902983988001D05908142D1D279 +:10CFA0000298012881D0E0698079012805D0BDF878 +:10CFB0002810A1F57F40FF3803D1BDF82800ADF857 +:10CFC0000E00049803ABCDE900B04A4641461B98D9 +:10CFD000FFF7B6FB0298BBE1072E02D0152E7FF4B7 +:10CFE000D4AEB5F801102183ADF81C10B5F80320BC +:10CFF000628300293FF4E4AE91423FF6E1AE0121A5 +:10D00000A1724FF0000BA4F808B084F80EB0052E02 +:10D0100007D0C0B2691DE26907F079FA00287FF4F1 +:10D0200044AF4FF6FF70208401A906AA14A8CDF8DA +:10D0300000B081E885032878214600F03F031D9A5F +:10D040001B98FFF795FB8246208BADF81C0080E112 +:10D050000120032EC3D14021ADF85010B5F80110C6 +:10D060002183ADF81C100AAAB8F1000F00D00023EC +:10D07000CDE9020304921D98CDF804800090388811 +:10D080000022401E83B21B9800F0C8FF8DF81800E4 +:10D0900090BB0B2089F80000BDF8280037E04FF066 +:10D0A000010C052E9BD18020ADF85000B5F8011081 +:10D0B0002183B5F803002084ADF81C10B0F5007F83 +:10D0C00003D907208DF8180085E140F47C422284C2 +:10D0D0000CA8B8F1000F00D00023CDE90330CDE952 +:10D0E000018C1D9800903888401E83B21B9800F078 +:10D0F00095FF8DF8180028B18328A8D10220BDE043 +:10D10000580000200D2189F80010BDF83000401CA7 +:10D110001EE1032E04D248067FF537AE002017E14A +:10D12000B5F80110ADF81C102878400602D58DF82E +:10D130003CE002E007208DF83C004FF0000803209F +:10D14000CDE902081E9BCDF810801D980193A6F131 +:10D15000030B00901FFA8BF342461B9800F034FD3E +:10D160008DF818008DF83C80297849060DD5208867 +:10D17000C00506D5208BBDF81C10884201D1C4F82B +:10D18000248040468DF81880E2E0832801D14FF0DA +:10D19000020A4FF48070ADF85000BDF81C002083E7 +:10D1A000A4F820B01E986062032060841321CCE0B4 +:10D1B000052EFFF4EAADB5F80110ADF81C10A28FF2 +:10D1C00062B3A2F57F43FE3B28D008228DF83C20B5 +:10D1D0004FF0000B0523CDE9023BDDF878C0CDF818 +:10D1E00010B01D9A80B2CDF804C040F40043009204 +:10D1F000B5F803201B9800F0E7FC8DF83CB04FF425 +:10D2000000718DF81800ADF85010832810D0F8B1D7 +:10D21000A18FA1F57F40FE3807D0DCE00B228DF80E +:10D220003C204FF6FE72A287D2E7A4F83CB0D2E0D1 +:10D2300000942B4631461E9A1B98FFF780FB8DF811 +:10D24000180008B183284BD1BDF81C00208355E796 +:10D2500000942B4631461E9A1B98FFF770FB8DF801 +:10D260001800E8BBE18FA06B0844811D8DE88203A4 +:10D270004388828801881B98FFF763FC824668E038 +:10D2800095F80180022E70D15FEA080002D0B8F153 +:10D29000010F6AD109208DF83C0007A800908DF895 +:10D2A00040804346002221461B98FFF72CFC8DF856 +:10D2B00042004FF0000B8DF843B050B9B8F1010FA8 +:10D2C00012D0B8F1000F04D1A18FA1F57F40FF3833 +:10D2D0000AD0A08F40B18DF83CB04FF4806000E0E0 +:10D2E00037E0ADF850000DE00FA91B98F9F71BFFD0 +:10D2F00082468DF83CB04FF48060ADF85000BAF132 +:10D30000020F06D0FC480068C07928B18DF81800DB +:10D3100027E0A4F8188044E0BAF1000F03D0812080 +:10D320008DF818003DE007A80090434601222146F1 +:10D330001B98FFF7E8FB8DF8180021461B98FFF7B4 +:10D34000CAFB9DF8180020B9192189F800100120A6 +:10D3500038809DF83C0020B10FA91B98F9F7E3FE37 +:10D360008246BAF1000F33D01BE018E08DF818E0C8 +:10D3700031E02078000712D5012E10D10A208DF857 +:10D380003C00E088ADF8400003201B9909F054F8F8 +:10D390000820ADF85000C1E648067FF5F6AC4FF026 +:10D3A000040A2088BDF8501008432080BDF85000C2 +:10D3B00080050BD5A18FA1F57F40FE3806D11E98C0 +:10D3C000E06228982063A6864FF0030A5046A1E445 +:10D3D0009DF8180078B1012089F80000297889F8B3 +:10D3E0000110BDF81C10A9F802109DF8181089F85A +:10D3F0000410052038802088BDF850108843208014 +:10D40000E4E72DE9FF4F8846087895B00121814077 +:10D410004FF20900249C0140ADF820102088DDF86F +:10D420008890A0F57F424FF0000AFF3A06D039B14C +:10D43000000705D5012019B0BDE8F08F0820FAE7F4 +:10D44000239E4FF0000B0EA886F800B018995D4699 +:10D450000988ADF83410A8498DF81CB0179A0A71E4 +:10D460008DF838B0086098F8000001283BD00228F9 +:10D4700009D003286FD1307820F03F001D30307084 +:10D48000B8F80400E08098F800100320022904D1C5 +:10D49000317821F03F011B31317094F846100907B3 +:10D4A00059D505ABB9F1000F13D0002102AA82E8CB +:10D4B0000B000720CDE90009BDF83400B8F80410CE +:10D4C000C01E83B20022159800F0A8FD0028D1D11B +:10D4D00001E0F11CEAE7B8F80400A6F80100BDF885 +:10D4E0001400C01C04E198F805108DF81C1098F881 +:10D4F0000400012806D04FF4007A02282CD003281B +:10D50000B8D16CE12188B8F8080011F40061ADF8D9 +:10D51000201020D017281CD3B4F84010814218D313 +:10D52000B4F84410172901D3814212D1317821F087 +:10D530003F01C91C3170A6F801000321ADF8341079 +:10D54000A4F8440094F8460020F0020084F8460055 +:10D5500065E105257EE177E1208808F1080700F400 +:10D56000FE60ADF8200010F0F00F1BD010F0C00FDF +:10D5700003D03888228B9042EBD199B9B878C00794 +:10D5800010D0B9680720CDE902B1CDF804B0009001 +:10D59000CDF810B0FB88BA883988159800F014FBD4 +:10D5A0000028D6D12398BDF82010401C80294ED0E9 +:10D5B00006DC10290DD020290BD0402987D124E08A +:10D5C000B1F5807F6ED051457ED0B1F5806F97D197 +:10D5D000DEE0C80601D5082000E0102082460DA933 +:10D5E00007AA0520CDE902218DF83800ADF83CB03E +:10D5F000CDE9049608A93888CDE9000153460722F1 +:10D6000021461598FFF7B4F8A8E09DF81C200121E9 +:10D610004FF00A0A002A9BD105ABB9F1000F00D0E8 +:10D620000020CDE902100720CDE90009BDF8340043 +:10D630000493401E83B2218B0022159800F0EEFC6B +:10D640008DF81C000B203070BDF8140020E09DF810 +:10D650001C2001214FF00C0A002A22D113ABB9F192 +:10D66000000F00D00020CDE902100720CDE900090D +:10D670000493BDF83400228C401E83B2218B159890 +:10D6800000F0CCFC8DF81C000D203070BDF84C0073 +:10D69000401CADF8340005208DF83800208BADF823 +:10D6A0003C00BCE03888218B88427FF452AF9DF863 +:10D6B0001C004FF0120A00281CD1606AA8B1B8788B +:10D6C000C0073FF446AF00E018E0BA680720CDE994 +:10D6D00002B2CDF804B00090CDF810B0FB88BA8843 +:10D6E000159800F071FA8DF81C001320307001209D +:10D6F000ADF8340093E00000580000203988208BFA +:10D700008142D2D19DF81C004FF0160A0028A06B70 +:10D7100008D0E0B34FF6FF7000215F46ADF808B0C7 +:10D72000019027E068B1B978C907BED1E18F0DAB90 +:10D730000844821D03968DE80C0243888288018884 +:10D7400009E0B878C007BCD0BA680DAB03968DE885 +:10D750000C02BB88FA881598FFF7F3F905005ED034 +:10D76000072D72D076E0019005AA02A92046FFF7A6 +:10D7700029F90146E28FBDF80800824201D0002954 +:10D78000F1D0E08FA16B084407800198E08746E064 +:10D790009DF81C004FF0180A40B1208BC8B13888A2 +:10D7A000208321461598FFF796F938E004F1180018 +:10D7B0000090237E012221461598FFF7A4F98DF8E9 +:10D7C0001C000028EDD1192030700120ADF8340084 +:10D7D000E7E7052521461598FFF77DF93AE020880F +:10D7E00000F40070ADF8200050452DD1A08FA0F5B9 +:10D7F0007F41FE3901D006252CE0D8F808004FF013 +:10D80000160A48B1A063B8F80C10A1874FF6FF7153 +:10D81000E187A0F800B002E04FF6FF70A087BDF8E6 +:10D82000200030F47F611AD078230022032015995C +:10D8300008F058FD98F8000020712088BDF82010ED +:10D84000084320800EE000E007252088BDF8201066 +:10D8500088432080208810F47F6F1CD03AE0218814 +:10D86000814321809DF8380020B10EA91598F9F761 +:10D870005AFC05469DF81C000028EBD086F801A054 +:10D8800001203070208B70809DF81C0030710520C5 +:10D89000ADF83400DEE7A18EE1B118980DAB008839 +:10D8A000ADF834002398CDE90304CDE90139206BAC +:10D8B0000090E36A179A1598FFF7FCF905460120D6 +:10D8C0008DF838000EA91598F9F72DFC00B1054622 +:10D8D000A4F834B094F8460040070AD52046FFF774 +:10D8E000A0F910F03E0F04D114F8460F20F0040008 +:10D8F00020701898BDF83410018028469BE500B5CB +:10D9000085B0032806D102208DF8000088B2694650 +:10D91000F9F709FC05B000BD10B5384C0B7822684A +:10D92000012B02D0022B2AD111E013780BB1052B69 +:10D9300001D10423137023688A889A802268CB88D7 +:10D94000D38022680B891381498951810DE08B882E +:10D9500093802268CB88D38022680B8913814B89FE +:10D9600053818B899381096911612168F9F7DBFB88 +:10D97000226800210228117003D0002800D08120E5 +:10D9800010BD832010BD806B002800D0012070479F +:10D990008178012909D10088B0F5205F03D042F6D3 +:10D9A0000101884201D10020704707207047F0B57F +:10D9B00087B0002415460E460746ADF8184011E022 +:10D9C00005980088288005980194811DCDE90241C1 +:10D9D000072104940091838842880188384600F02A +:10D9E000F3F830B905AA06A93046FEF7EBFF002888 +:10D9F000E6D00A2800D1002007B0F0BD5800002072 +:10DA000010B58B7883B102789A4205D10B885BB14F +:10DA100002E08B79091D4BB18B789A42F9D1B0F8AD +:10DA200001300C88A342F4D1002010BD812010BD2C +:10DA3000072826D012B1012A27D103E0497801F046 +:10DA4000070102E04978C1F3C20105291DD2DFE8D0 +:10DA500001F00318080C12000AB1032070470220DD +:10DA6000704704280DD250B10DE0052809D2801E60 +:10DA7000022808D303E0062803D0032803D005209A +:10DA80007047002070470F20704781207047C0B258 +:10DA900082060BD4000607D5FE48807A4143C01D9C +:10DAA00001EBD00080B270470846704700207047F5 +:10DAB00070B513880B800B781C0625D5F54CA47A1D +:10DAC000844204D843F010000870002070BD9568AF +:10DAD00000F0070605EBD0052D78F54065F304133B +:10DAE0000B701378D17803F0030341EA032140F26D +:10DAF0000123B1FBF3F503FB15119268E41D00FB54 +:10DB0000012000EBD40070BD906870BD37B514469D +:10DB1000BDF8041011809DF804100A061ED5C1F34B +:10DB20000013DC49A568897A814208D8FE2811D102 +:10DB3000C91DC9085A42284617F097FD0AE005EBAF +:10DB4000D00100F00702012508789540A8439340D2 +:10DB500018430870207820F0100020703EBD2DE999 +:10DB6000F0410746C81C0E4620F00300B04202D028 +:10DB70008620BDE8F081C74D002034462E60AF807E +:10DB80002881AA72E8801AE0E988491CE9808106A8 +:10DB900014D4E17800F0030041EA002040F20121B2 +:10DBA000B0FBF1F201FB12012068FFF770FF298939 +:10DBB000084480B22881381A3044A0600C342078A0 +:10DBC0004107E1D40020D4E72DE9FF4F89B0164684 +:10DBD000DDE9168A0F46994623F44045084600F0D1 +:10DBE0000DFB04000FD0099804F0CAFE02902078C3 +:10DBF00000060AD5A748817A0298814205D8872075 +:10DC00000DB0BDE8F08F0120FAE7224601A9029885 +:10DC1000FFF74EFF834600208DF80C004046B8F118 +:10DC2000070F1AD001222146FFF702FF0028E7D193 +:10DC30002078400611D502208DF80C00ADF8107048 +:10DC4000BDF80400ADF81200ADF814601898ADF8F6 +:10DC50001650CDF81CA0ADF818005FEA094004D5B5 +:10DC600000252E46A84601270CE02178E07801F037 +:10DC7000030140EA012040F20121B0FBF1F28046AD +:10DC800001FB12875FEA494009D5B84507D1A17861 +:10DC9000207901F0030140EA0120B04201D3BE42E5 +:10DCA00001D90720ACE7A8191FFA80F9B94501D9B5 +:10DCB0000D20A5E79DF80C0028B103A90998F9F7F4 +:10DCC00030FA00289CD1B84507D1A0784FEA192135 +:10DCD00061F30100A07084F804901A9800B10580E7 +:10DCE000199850EA0A0027D0199830B10BEB0600BA +:10DCF0002A46199917F042FC0EE00BEB060857462E +:10DD0000189E099804F0A8FF2B46F61DB5B23946B7 +:10DD10004246009504F093FB224601A90298FFF7C2 +:10DD2000C7FE9DF80400224620F010008DF8040084 +:10DD3000DDE90110FFF7EAFE002061E72DE9FF4F62 +:10DD4000DFF8509182461746B9F80610D9F800005E +:10DD500001EB410100EB810440F20120B2FBF0F144 +:10DD600085B000FB11764D46DDF84C8031460698B3 +:10DD7000FFF78DFE29682A898B46611A0C31014410 +:10DD80001144AB8889B28B4202D8842009B038E7AD +:10DD90000699CDB2290603D5A90601D50620F5E7D7 +:10DDA000B9F806C00CF1010C1FFA8CFCA9F806C0EA +:10DDB000149909B1A1F800C0A90602D5C4F80880D9 +:10DDC00007E0104480B2A9F80800191A01EB0B0013 +:10DDD000A0602246FE200699FFF798FEE7702671A4 +:10DDE0002078390A61F30100320AA17840F004007A +:10DDF00062F30101A17020709AF802006071BAF814 +:10DE00000000E08000262673280602D599F80A70E3 +:10DE100000E00127A80601D54FF000084D46002478 +:10DE20004FF007090FE0CDE902680196CDF80090A8 +:10DE30000496E9882046129B089AFFF7C5FE002841 +:10DE4000A4D1641CE4B2BC42EDD300209EE72DE9CE +:10DE5000F047804600F0D2F9070005D0002644467E +:10DE60000C4D40F2012919E00120BDE8F087204661 +:10DE700000F0C4F90278C17802F0030241EA0222FC +:10DE8000B2FBF9F309FB13210068FFF700FE3044F1 +:10DE900086B201E0F8050020641CA4B2E988601E87 +:10DEA0008142E4DCA8F10100E8802889801B2881F8 +:10DEB00000203870D9E710B5144631B1491E2180D1 +:10DEC00004F05EFDA070002010BD012010BD10B553 +:10DED000D24904460088CA88904201D30A2010BD66 +:10DEE000096800EB400001EB80025079A072D088F5 +:10DEF00020819178107901F0030140EA0120A0818E +:10DF0000A078E11CFFF7D4FD20612088401C208010 +:10DF1000E080002010BD0121018270472DE9FF4FF4 +:10DF200085B04FF6FF788246A3F8008048681F4608 +:10DF30000D4680788DF8060048680088ADF804002A +:10DF400000208DF80A00088A0C88A04200D30446FD +:10DF50002C8241E0288A401C2882701D6968FFF7E6 +:10DF60004FFDB8BB3988414501D1601E38806888B3 +:10DF7000A04236D3B178307901F0030140EA01299B +:10DF800001A9701DFFF73CFD20BB298941452CD01C +:10DF9000002231460798FFF74BFDD8B9298949453A +:10DFA00018D1E9680391B5F80AC0D6F808B0504610 +:10DFB000CDF800C004F050FEDDF800C05A460CF168 +:10DFC000070C1FFA8CFC4B460399CDF800C004F0F7 +:10DFD00000FA50B1641CA4B2204600F00FF906000C +:10DFE000B8D1641E2C820A20D0E67C807079B8718A +:10DFF000F088B8803178F07801F0030140EA012020 +:10E000007881A7F80C90504604F0BAFC324607F12C +:10E010000801FFF74DFD38610020B7E62DE9FF4FFD +:10E0200087B081461C469246DDF860B0DDF854802A +:10E03000089800F0E3F805000CD0484604F0A0FC76 +:10E040002978090608D57549897A814204D887203C +:10E050000BB0D6E50120FBE7CAF309062A4601A961 +:10E06000FFF726FD0746149807281CD000222946F2 +:10E07000FFF7DEFC0028EBD12878400613D50120FD +:10E080008DF808000898ADF80C00BDF80400ADF854 +:10E090000E00ADF81060ADF8124002A94846F9F73D +:10E0A00040F80028D4D12978E87801F0030140EA4B +:10E0B0000121AA78287902F0030240EA022056459D +:10E0C00007D0B1F5007F04D9611E814201DD0B202C +:10E0D000BEE7864201D90720BAE7801B85B2A54278 +:10E0E00000D92546BBF1000F01D0ABF800501798BE +:10E0F00018B1B9192A4617F041FAB8F1000F0DD03E +:10E100003E4448464446169F04F0B8FD2146FF1D94 +:10E11000BCB232462B46009404F0C5F9002097E7C4 +:10E120002DE9F04107461D461646084600F066F800 +:10E1300004000BD0384604F023FC2178090607D5EB +:10E140003649897A814203D8872012E5012010E5FB +:10E1500022463146FFF7ACFC65B12178E07801F04A +:10E16000030140EA0120B0F5007F01D8012000E062 +:10E17000002028700020FCE42DE9F04107461D46F0 +:10E180001646084600F03AF804000BD0384604F072 +:10E19000F7FB2178090607D52049897A814203D8FF +:10E1A0008720E6E40120E4E422463146FFF7AEFC96 +:10E1B000FF2D14D02178E07801F0030240EA02201C +:10E1C00040F20122B0FBF2F302FB130015B900F29A +:10E1D000012080B2E070000A60F30101217000208C +:10E1E000C7E410B50C4600F009F828B1C1882180B9 +:10E1F0004079A070002010BD012010BD0749CA88D9 +:10E20000824209D340B1096800EB40006FF00B0275 +:10E2100002EB80000844704700207047F80500209A +:10E2200010B508F0EFFAF4F741FB08F051F9BDE83A +:10E23000104008F019BA302834BF01200020704780 +:10E24000202834BF4FF0A0420C4A012300F01F00E9 +:10E2500003FA00F0002914BFC2F80C05C2F8080543 +:10E260007047202834BF4FF0A041044900F01F0040 +:10E27000012202FA00F0C1F81805704700030050AF +:10E2800070B50346002002466FF02F050EE09C5C3F +:10E29000A4F130060A2E02D34FF0FF3070BD00EB20 +:10E2A000800005EB4000521C2044D2B28A42EED3DB +:10E2B00070BD30B50A230BE0B0FBF3F403FB14048C +:10E2C000B0FBF3F08D183034521E05F8014CD2B279 +:10E2D000002AF1D130BD30B500234FF6FF7510E0B4 +:10E2E000040A44EA002084B2C85C6040C0F303140E +:10E2F000604005EA00344440E0B25B1C84EA401010 +:10E300009BB29342ECD330BD2DE9F041FA4B00268D +:10E31000012793F860501C7893F864C0B8B183F873 +:10E320008D40A3F88E1083F88C2083F88A70BCF19E +:10E33000000F0CBF83F8906083F89050EF4880681E +:10E34000008804F089FCBDE8F04104F01FB94FF6E5 +:10E35000FF7083F88D40A3F88E0083F88C2083F83B +:10E360008A70BCF1000F14BF83F8905083F890605E +:10E37000BDE8F08170B5E14E0446306890F8981021 +:10E380000025012919D090F89210012924D090F885 +:10E39000681001292AD090F88A1001291CBF00209A +:10E3A00070BD657017212170D0F88C106160B0F8D5 +:10E3B0009010218180F88A5016E065701C21217030 +:10E3C000D0F899106160D0F89D10A16090F8A1106C +:10E3D000217380F8985007E0657007212170D0F80C +:10E3E0009410616080F89250012070BD6570142116 +:10E3F000217000F16A012022201D17F0BFF80121D1 +:10E400002172306880F86850BB48B0F86C20A0F8E2 +:10E410009420B268537B80F8963080F89210108870 +:10E4200004F01AFC04F0C1F8DEE7B448006890F884 +:10E430006810002914BFB0F86C004FF6FF707047E9 +:10E4400070B5AE4C06462068002808BFFFDF0025E7 +:10E45000206845706660002808BFFFDF20684178AB +:10E4600000291CBFFFDF70BDA42117F028F9206828 +:10E47000FF2101707F2180F836101321418428216B +:10E4800080F86510012180F8581080F85D5008F080 +:10E4900082FEBDE8704008F048B8984909680978DC +:10E4A00081420CBF0120002070479448006890F81A +:10E4B0002200C0F3400070479048006890F82200A6 +:10E4C00000F0010070478D48006890F82200C0F30A +:10E4D000001070472DE9F04388480024016891F846 +:10E4E0002400B1F822C0C0F38002C0F340031A44F4 +:10E4F00000F001000244CCF3001060B3BCF1130F34 +:10E5000021D00BDCBCF1100F02BF7D4830F81200A7 +:10E51000BDE8F083BCF1120F15D008E0BCF1150F77 +:10E5200009D0BCF11D0F04BF7648BDE8F083FFDFC2 +:10E530002046BDE8F0837449002031F8121012FB28 +:10E540000010BDE8F0837149002031F8121012FB71 +:10E550000010BDE8F08391F85A3091F85B002E2648 +:10E560004FF47A774FF014084FF04009022B04BFA4 +:10E570004AF2D745B5FBF7F510D0012B04BF4AF29C +:10E580002F75B5FBF7F510D04AF62315B5FBF7F557 +:10E59000082B08BF4E4613D0042B18D02646082B54 +:10E5A0000ED0042B13D0022B49D004F12806042BE3 +:10E5B0000FD0082B1CBF4FF01908082304D00AE025 +:10E5C0004FF0140806F5A8764FF0400303E006F577 +:10E5D000A8764FF0100318FB036313FB0253C2EB42 +:10E5E00002124B4D02EB820205EB82021A441CF030 +:10E5F000010F4FF4C8734FF4BF7504BFCCF340064E +:10E60000002E77D0CCF3400602F5A572EEB10828B3 +:10E6100004BF1E4640270CD0042804BF2E461027F6 +:10E6200007D0022807BF04F11806042704F12806C2 +:10E63000082707EB870808EB87173E441BE004F127 +:10E6400018064FF019080423C5E7082804BF1E4622 +:10E6500040270CD0042804BF2E46102707D00228DC +:10E6600007BF04F11806042704F12806082707EB62 +:10E67000871706EB8706324402F19C0691F8652065 +:10E6800010F00C0F08BF00223244082804BF1E46B9 +:10E6900040270CD0042804BF2E46102707D002289C +:10E6A00007BF04F11806042704F128060827C7EB62 +:10E6B000C70707EB470706EB4706324498321CF0C2 +:10E6C000010F27D0082808BF40200CD0042804BF21 +:10E6D0002B46102007D0022807BF04F1180304209E +:10E6E00004F12803082000EB400101EB001018445E +:10E6F00002444AE04DE000000406002060000020D3 +:10E70000285B02008E891300305B0200205B020050 +:10E71000D4FEFFFF082804BF9C4640260CD00428E6 +:10E7200004BFAC46102607D0022807BF04F1180C1E +:10E73000042604F1280C082606EB8616898F0CEBBC +:10E74000860C6244EB2920D944F2552C0B3101FB95 +:10E750000CF1890D082807D0042802D0022805D022 +:10E7600008E02B46102008E0402006E004F11803E2 +:10E77000042002E004F12803082000EB801003EBE2 +:10E78000800000F5A57001FB002202F26510BDE8D3 +:10E79000F08302F5A572082804BF9C4640260CD0E1 +:10E7A000042804BFAC46102607D0022807BF04F196 +:10E7B000180C042604F1280C082606EB8616B1F87E +:10E7C00044100CEB860C6244EB29DED944F2552C44 +:10E7D0000B3101FB0CF1890D0828C5D00428C0D0ED +:10E7E0000228C7D1C2E7FE4840F271210068806A62 +:10E7F00048437047FA48006890F83500002818BF71 +:10E800000120704710B5F74C207B022818BF032861 +:10E8100008D1207D04F115010DF0A1FC08281CBFD2 +:10E82000012010BD207B002816BF022800200120F7 +:10E83000BDE8104009F0C0B9EA4908737047E849DB +:10E84000096881F8300070472DE9F047E44C2168F1 +:10E85000087B002816BF022800200120487301F120 +:10E860000E0109F093F92168087B022816BF0328DE +:10E870000122002281F82F204FF0080081F82D009E +:10E88000487B01F10E034FF001064FF0000701280D +:10E8900004BF5B7913F0C00F0AD001F10E03012809 +:10E8A00004D1587900F0C000402801D0002000E0D9 +:10E8B000012081F82E00002A04BF91F8220010F0F8 +:10E8C000040F07D0087D01F115010DF048FC216807 +:10E8D00081F82D002068476006F0CEF92168C14D0F +:10E8E0004FF00009886095F82D000DF054FC80462B +:10E8F00095F82F00002818BFB8F1000F04D095F844 +:10E900002D000DF00FFA68B195F8300000281CBFFB +:10E9100095F82E0000281DD0697B05F10E00012915 +:10E920000ED012E06E734A4605F10E01404609F022 +:10E9300082F995F82D1005F10E000DF023FD09E088 +:10E94000407900F0C000402831D0394605F10E0072 +:10E9500009F0A8F92068C77690F8220010F0040F9B +:10E9600008BFBDE8F087002795F82D000DF08EFA5E +:10E97000050008BFBDE8F08710210EF04CFA002812 +:10E9800018BFBDE8F08720683A4600F11C01C67642 +:10E99000284609F050F9206800F11C0160680EF06B +:10E9A00093FE6068BDE8F04701210EF0A8BE0DF0AF +:10E9B00026FD4A4605F10E0109F03DF9CAE7884AED +:10E9C0001268137B0370D2F80E000860508A8880AA +:10E9D000704778B583490446814D407B08732A68A7 +:10E9E000207810706088ADF8000080B200F001015E +:10E9F000C0F3400341EA4301C0F3800341EA8301CD +:10EA0000C0F3C00341EAC301C0F3001341EA03119C +:10EA1000C0F3401341EA4311C0F3801041EA801073 +:10EA20005084E07D012808BF012607D0022808BFD6 +:10EA3000022603D0032814BFFFDF0826286880F8C9 +:10EA40005A60607E012808BF012607D0022808BF4F +:10EA5000022603D0032814BFFFDF0826286880F8A9 +:10EA60005B60217B80F82410418C1D290CBF0021A4 +:10EA700061688162617D80F83510A17B002916BF35 +:10EA80000229002101210175D4F80F10C0F81510DA +:10EA9000B4F81310A0F81910A17EB0F8662061F345 +:10EAA0000302A0F86620E17E012918BF002180F84A +:10EAB0003410002078BD4A480068408CC0F3001133 +:10EAC00031B1C0F38000002804BF1F20704702E06E +:10EAD000C0F3400109B10020704710F0010F14BFCE +:10EAE000EE20FF2070473E480068408CC0F30011C4 +:10EAF00019B1C0F3800028B102E0C0F3400008B1B2 +:10EB000000207047012070473549002209680A66D5 +:10EB10004B8C1D2B0CBF81F8642081F8640070477A +:10EB200000232F4A126882F859309164A2F84C00F1 +:10EB3000012082F859007047294A0023126882F8A0 +:10EB40005830A2F854000120116582F8580070472F +:10EB50002349096881F85D0070472148006890F9F1 +:10EB60005D0070471E48006890F82200C0F3401016 +:10EB700070471B48006890F82200C0F3C00070473F +:10EB8000012070471648006890F85B00704770B528 +:10EB900008F0EBFA08F0CAFA08F0A2F908F020FA37 +:10EBA0000F4C2068016E491C016690F83300002567 +:10EBB00030B108F0F0FA07F0B8FC206880F8335064 +:10EBC0002068457090F8371021B1BDE870400420EE +:10EBD00009F0D7BC90F8641001B3006E814203E0E5 +:10EBE000600000200406002018D8042009F0C9FCA9 +:10EBF000206890F8220010F0010F07D0A06843228F +:10EC00000188BDE870400120FFF77EBBBDE8704081 +:10EC100043224FF6FF710020FFF776BBBDE870403E +:10EC2000002009F0AEBC2DE9F04782B00F468146C6 +:10EC3000FE4E4FF000083068458C15F0030F10D0E1 +:10EC400015F0010F05F0020005D0002808BF4FF0B5 +:10EC5000010806D004E0002818BF4FF0020800D1D8 +:10EC6000FFDF4FF0000A544615F0010F05F00200D7 +:10EC70000DD080B915F0040F0DD04AF00800002F18 +:10EC80001CBF40F0010040F0020440D08FE010B102 +:10EC900015F0040F0DD015F0070F10D015F0010F6F +:10ECA00005F0020036D0002808BF15F0040F27D069 +:10ECB0003DE0002F18BF4AF0090478D134E02FB1AD +:10ECC0004AF0080415F0200F14D070E0316805F008 +:10ECD0002002B1F84400104308BF4AF0010466D096 +:10ECE0004AF0180415F0200F61D191F85A10082944 +:10ECF00059D155E0316891F85A10082950D152E0A5 +:10ED00004AF00800002F18BF40F001044FD140F036 +:10ED100010044CE0002818BF15F0040F07D0002F96 +:10ED200018BF4AF00B0442D14AF018043FE015F036 +:10ED3000030F3BD115F0040F38D077B131684AF09A +:10ED4000080091F85A1008290CBF40F0020420F086 +:10ED5000020415F0200F21D029E0316805F02002CF +:10ED6000B1F84400104308BF4AF003041FD04AF032 +:10ED7000180015F0200F08D091F85A10082914BF78 +:10ED800040F0020420F0020411E091F85A20082A11 +:10ED900014BF40F0010020F00100EDE7082902D087 +:10EDA00024F0010403E044F0010400E0FFDF15F06B +:10EDB000400F1BD0C7B93168B1F84400002804BF28 +:10EDC000488C10F0010F0BD110F0020F08BF10F0AB +:10EDD000200F05D115F0010F08BF15F0020F03D069 +:10EDE00091F85A00082801D044F040047068A0F857 +:10EDF00000A0017821F02001017007210EF030FC05 +:10EE0000414670680EF023FE214670680EF02BFE1E +:10EE100014F0010F0AD006230022854970680EF015 +:10EE2000FCFD3068417B70680EF05CFC14F0020F52 +:10EE300018D0D6E90010B9F1000F4FF006034FF0DB +:10EE4000010207D01C310EF0E8FD012170680EF0C0 +:10EE500056FC07E015310EF0E0FD3068017D70686A +:10EE60000EF04DFC14F0040F18BFFFDF14F0080F74 +:10EE700017D0CDF800A03068BDF800100223B0F81C +:10EE80006600020962F30B01ADF800109DF8011055 +:10EE9000032260F307118DF80110694670680EF0C7 +:10EEA000BCFD012F61D13068B0F84410E9B390F88F +:10EEB0002200C0F34000C0BB70680EF0C4FD401CCF +:10EEC000C7B23068B0F84420B0F85610551AC7F1F0 +:10EED000FF018D42A8BF0D46AA423AD990F8220000 +:10EEE00010F0010F35D144F01004214670680EF087 +:10EEF000BAFDF81CC0B2ED1E284482B23068B0F8EA +:10EF00006610036E090951FA83F190F85C30494F9D +:10EF10001944BC460023E1FB07C31B096FF0240C16 +:10EF200003FB0C1180F85C1000E01EE090F85B0021 +:10EF3000012101F037F80090BDF800009DF80210A3 +:10EF4000032340EA01400190042201A970680EF0F9 +:10EF500064FD3068AAB2016C70680EF0B2FD3068D2 +:10EF6000B0F856102944A0F8561014F0400F06D0FF +:10EF7000D6E90010012306225D310EF04EFD14F09B +:10EF8000200F18BFFFDF0020002818BFFFDF02B0EE +:10EF9000BDE8F0872DE9F843244C2068002808BF1D +:10EFA000FFDF2068417839BB0178FF2924D0002693 +:10EFB00080F83160A0F85660867080F8376030467F +:10EFC00008F022F807F0E2FC206890F95D0007F0F5 +:10EFD00082FD194807F085FD184807F0FBFF6068BF +:10EFE00008F015F8206890F8240010F0010F06D002 +:10EFF000252007F07EFD09E00C20BDE8F88310F025 +:10F00000020F18BF262075D007F073FD206890F816 +:10F010005A10252007F078FC206880F82C6007F053 +:10F02000EDFF206890F85A10002009E060000020F1 +:10F030001206002053E4B36E1C5B0200195B020051 +:10F0400007F04BFE0F21052007F019FD206890F80E +:10F050002E10002901BF90F82F10002990F82200EF +:10F0600010F0040F75D005F007FE0546206829460C +:10F07000806806F01AFBDFF83084074690FBF8F052 +:10F0800008FB10704142284605F0F7FA21688860B5 +:10F0900097FBF8F04A68104448600DF05DF80146AF +:10F0A0002068426891426FD8C0E90165FF4D4FF07A +:10F0B000010895F82D000DF06EF8814695F82F00A7 +:10F0C0000127002818BFB9F1000F04D095F82D00D2 +:10F0D0000CF028FEA8B195F8300000281CBF95F868 +:10F0E0002E00002825D0697B05F10E00012916D0DD +:10F0F0001AE0FFE710F0040F14BF2720FFDF83D1D1 +:10F1000084E73A466F7305F10E01484608F093FD17 +:10F1100095F82D1005F10E000DF034F909E0407955 +:10F1200000F0C000402815D0414605F10E0008F05F +:10F13000B9FD206890F8220010F0040F24D095F853 +:10F140002D000CF0A3FE05001ED010210DF063FE73 +:10F1500040B119E00DF053F93A4605F10E0108F0FF +:10F160006AFDE6E720683A4600F11C01C7762846AA +:10F1700008F061FD206800F11C0160680EF0A4FA3F +:10F18000012160680EF0BBFA2068417B0E3007F069 +:10F190005AFC206890F8581059B3B0F85410A0F8F1 +:10F1A0004410016D016490F82210C1F30011E9B917 +:10F1B000B0F8660002210509ADF80050684606F077 +:10F1C0003DFE28B1BDF80000C0F30B00A84204D1F9 +:10F1D000BDF80000401CADF800002168BDF800003B +:10F1E000B1F8662060F30F12A1F86620206880F85D +:10F1F0005860206890F8591031B1B0F84C108187F0 +:10F20000816C816380F85960B0F86610026E09095C +:10F2100051FA82F190F85C20DFF894C21144634601 +:10F220000022E1FB0C3212096FF0240302FB0311F0 +:10F2300080F85C100DF013F8032160680DF092F86F +:10F24000216881F833000020BDE8F883994988607F +:10F2500070472DE9F043974C83B0226892F8313023 +:10F260003BB1508C1D2808BFFFDF03B0BDE8F04361 +:10F270008DE401260027F1B1054692F85C0007F005 +:10F2800038FC206890F85B10FF2007F03DFB2068F9 +:10F290004FF4A57190F85B20002007F0E4FD206892 +:10F2A00090F8221011F0030F00F02E81002D00F0D5 +:10F2B000258100F029B992F822108046D07EC1F352 +:10F2C0000011002956D0054660680780017821F0BA +:10F2D00020010170518C132937D01FDC102908BF81 +:10F2E000022144D0122908BF062140D0FFDF6F4D14 +:10F2F000606805F10E010EF0D9F9697B60680EF0C7 +:10F30000F1F92068418C1D2918BF152965D0B0F886 +:10F310004420016C60680EF0FEF95EE0152918BF0C +:10F320001D29E3D14FF001010EF09AF960680178D0 +:10F3300041F020010170216885B11C310EF0C4F943 +:10F34000012160680EF0DBF9D1E700210EF088F9A9 +:10F350006068017841F020010170C8E715310EF0B6 +:10F36000B3F92068017D60680EF0C9F9BFE70EF0BF +:10F3700077F9BCE70021FFF756FC6068C17811F00F +:10F380003F0F2AD0017911F0100F26D00EF066F948 +:10F390002368024693F82410C1F38000C1F3400CA7 +:10F3A000604401F0010100EB010C93F82C10C1F353 +:10F3B0008000C1F34005284401F001010844ACEB92 +:10F3C0000000C1B293F85A0000F0ECFD0090032356 +:10F3D0000422694660680EF020FB2068002590F842 +:10F3E000241090F82C0021EA000212F0010F18BF3F +:10F3F00001250ED111F0020F04D010F0020F08BF4A +:10F40000022506D011F0040F03D010F0040F08BF3E +:10F410000425B8F1000F2BD0012D1BD0022D08BF01 +:10F4200026201BD0042D14BFFFDF272016D0206814 +:10F4300090F85A10252007F067FA206890F82210FB +:10F44000C1F3001169B101224FF49671002007F059 +:10F450000AFD0DE0252007F04CFBE8E707F049FB2B +:10F46000E5E790F85A204FF49671002007F0FBFC76 +:10F47000206890F82C10294380F82C1090F8242054 +:10F4800032EA01011DD04670418C13292CD027DCB3 +:10F49000102904BF03B0BDE8F083122924D000BFB7 +:10F4A000C1F30010002807E040420F0004060020CE +:10F4B00053E4B36E6000002018BFFFDF03B0BDE867 +:10F4C000F083418C1D2908BF80F82C70DBD0C1F37C +:10F4D0000011002914BF80F8316080F83170D2E744 +:10F4E000152918BF1D29DBD190F85A2003B04FF021 +:10F4F0000101BDE8F043084607F092BE90F85B209A +:10F500000121084607F08CFE2168002DC87E7CD0C2 +:10F510004A8C3D46C2F34000002808BF47F008056A +:10F5200012F0400F18BF45F04005002819BFD1F870 +:10F530003890B1F83C80D1F84090B1F844806068D0 +:10F54000072107800EF08CF8002160680EF07FFA2A +:10F55000294660680EF087FA15F0080F15D020686C +:10F56000BDF800100223B0F86600020962F30B0137 +:10F57000ADF800109DF80110032260F307118DF81B +:10F580000110694660680EF048FA60680EF024F9D0 +:10F590002168C0F1FE00B1F85620A8EB02018142BB +:10F5A000A8BF0146CFB2D019404542D245F0100164 +:10F5B00060680EF058FA60680EF00EF92168C0F12C +:10F5C000FE00B1F85610A8EB01018142A8BF014628 +:10F5D000CFB260680EF037FA3844421C2068B0F8A9 +:10F5E0006610036E090951FA83F190F85C30FF4D03 +:10F5F0001944AC460023E1FB05C31B096FF0240C42 +:10F6000003FB0C1180F85C1000E038E090F85B0020 +:10F61000012100F0C7FC0090BDF800009DF8021029 +:10F62000032340EA01400190042201A960680EF022 +:10F63000F4F9216891F8220010F0400F05D0012361 +:10F6400006225D3160680EF0E8F920683A46B0F8AD +:10F65000560000EB090160680EF033FA2068B0F83C +:10F6600056103944A0F8561008F0C1F9002818BF08 +:10F67000FFDF20684670867003B0BDE8F08301218B +:10F68000FFF7D1FAF0E7DA4810B50068417841B9E0 +:10F690000078FF2805D000210846FFF7DAFD00209A +:10F6A00010BD07F062FD07F041FD07F019FC07F0FF +:10F6B00097FC0C2010BD10B5CD4C206890F82200AE +:10F6C00010F0010F1CBFA06801884FF03C0212BF70 +:10F6D00001204FF6FF710020FEF716FE2168012081 +:10F6E00081F8370010BDC249096881F832007047BF +:10F6F0002DE9F041002508F010FF002800F00581F9 +:10F70000BB4C2068417801270026012906D0022938 +:10F7100001D003297ED0FFDFBDE8F0818178022689 +:10F720000029418C46D0C1F34002002A08BF11F0E5 +:10F73000010F70D090F85B204FF001014FF00000F6 +:10F7400007F06EFD216891F82200C0F34000002808 +:10F7500014BF0C20222091F85B1007F0D5F8206828 +:10F76000467090F8330058B106F0CBFE206890F850 +:10F770005B0010F00C0F0CBF4020452007F001FD8E +:10F78000206890F83400002818BF07F019FD2168A0 +:10F7900091F85B0091F8651010F00C0F08BF002184 +:10F7A000962007F055FC08F019F9002818BFFFDF74 +:10F7B000BDE8F081C1F3001282B110293FD090F86A +:10F7C000330020B106F09DFE402007F0DAFC2068EF +:10F7D00090F8221011F0040F36D043E090F8242066 +:10F7E00090F82C309A422AD1B0F84400002808BF83 +:10F7F00011F0010F05D111F0020F08BF11F0200F19 +:10F800007ED04FF001014FF00000FFF722FD20688D +:10F81000418C01E040E034E011F0010F04BFC1F37E +:10F820004001002907D1B0F85610B0F844209142A9 +:10F8300018BFBDE8F08180F83170BDE8F081BDE807 +:10F84000F0410021012004E590F83510012914BF92 +:10F850000329102545F00E0190F85A204FF00000C2 +:10F8600007F0DEFC206890F83400002818BF07F08D +:10F87000A7FC0021962007F0EBFB20684670BDE84E +:10F88000F081B0F85610B0F8440081423DD0BDE898 +:10F89000F04101210846DCE48178D9B1418C11F0B6 +:10F8A000010F1CD080F8687090F86A20B0F86C10D6 +:10F8B0000120FEF729FD2068467007F056FC07F08E +:10F8C00035FC07F00DFB07F08BFBBDE8F041032092 +:10F8D00008F057BE8178BDE8F0410120B9E411F08D +:10F8E000020F04BFFFDFBDE8F081B0F85610808F33 +:10F8F00081420AD001210846FFF7ABFC032000E05B +:10F9000003E021684870BDE8F081BDE8F041FFF7F1 +:10F910003EB9FFF73CB910B5354C206890F834106B +:10F9200049B1363007F05BFC18B921687F2081F8B7 +:10F93000360007F03BFC206890F8330018B107F060 +:10F940002AFC06F0F2FD08F0E8FDA8B1206890F866 +:10F950002210C1F3001179B14078022818BFFFDFEF +:10F9600000210120FFF775FC2068417800291EBFA7 +:10F9700040780128FFDF10BDBDE81040FFF707B950 +:10F980002DE9F0471A4C0F4680462168B8F1030F65 +:10F99000488C08BFC0F3400508D000F0010591F87D +:10F9A0003200002818BF4FF0010901D14FF00009C3 +:10F9B00007F093F80646B8F1030F0CBF4FF00208AA +:10F9C0004FF0010835EA090008BFBDE8F08720685C +:10F9D00090F8330090B10CF025FC38700146FF28F8 +:10F9E0000CD06068C01C0CF0F6FB03E053E4B36E6F +:10F9F0006000002038780CF022FC06436068017833 +:10FA0000C1F3801221680B7D9A4208D10622C01CE6 +:10FA1000153115F087FD002808BF012000D0002017 +:10FA20003978FF2906D0C8B9206890F82D0088429F +:10FA300016D113E0A0B1616811F8030BC0F3801078 +:10FA40000CF08DFB05460CF0EDFC38B128460CF0AF +:10FA50001DFA18B110210DF0DEF908B1012000E007 +:10FA60000020216891F8221011F0040F01D0F0B1AC +:10FA70001AE0CEB9FE4890F83500002818BF40457E +:10FA800015D1616811F8030BC0F380100CF067FB0F +:10FA900004460CF0C7FC38B120460CF0F7F918B159 +:10FAA00010210DF0B8F910B10120BDE8F087002059 +:10FAB000BDE8F0872DE9F04FEE4D074683B028688A +:10FAC00000264078022818BFFFDF28684FF07F0922 +:10FAD00090F8341049B1363007F081FB002804BF9C +:10FAE000286880F8369007F061FB68680DF0DAFD51 +:10FAF0000446002F00F0048268680DF05EFF0028C5 +:10FB000000F0FE8106F0B7FF002800F0F981FF2029 +:10FB1000DFF864B3DFF8588300274FF0010A062CA2 +:10FB200080F00082DFE804F0EFEFEF03EFF78DF8ED +:10FB3000000069460320FFF723FF002800F0E4805F +:10FB4000296891F8340010B191F89800D0B1286874 +:10FB5000817801294CD06868042107800DF080FD70 +:10FB600008F10E0168680DF0A1FD98F80D106868A5 +:10FB70000DF0B8FD2868828F816B68680DF0EFFD8D +:10FB800000F04DB99DF8000081F898A00A7881F83E +:10FB90009920FF280FD001F19B029A310CF004FB51 +:10FBA000002808BFFFDF286890F89A1041F0020192 +:10FBB00080F89A100DE068680278C2F3801281F82C +:10FBC0009A20D0F80320C1F89B20B0F80700A1F8D4 +:10FBD0009F00286800F1A10490F836007F2808BF34 +:10FBE000FFDF286890F83610217080F83690AEE775 +:10FBF00090F822009BF80490C0F38014686864F3C6 +:10FC00008619072107800DF02BFD002168680DF093 +:10FC10001EFF494668680DF026FF0623002208F102 +:10FC20000E0168680DF0F9FE2868417B68680DF0E8 +:10FC300059FD68680DF0D0FD29688A8FC0F1FE017A +:10FC40008A42B8BF1146CFB2BA423DD9F81EC7B2F8 +:10FC500049F0100A514668680DF005FF68680DF01C +:10FC6000F2FE3844431C2868B0F86610026E090999 +:10FC700051FA82F190F85C20DFF800920A44C846FD +:10FC80004FF0000CE2FB098C4FEA1C116FF0240CC2 +:10FC900001FB0C2180F85C1090F85B001A460121F2 +:10FCA00000F080F90190BDF804009DF806100323D0 +:10FCB00040EA01400290042202A968680DF0ADFEFE +:10FCC000514668680DF0CFFE34B1D5E9001001232C +:10FCD00006225D310DF0A1FE28683A46816B686806 +:10FCE0000DF0EFFE2868A0F85670818F8F420CBF90 +:10FCF0000121002180F8311007F079FE002818BF9B +:10FD0000FFDF8CE007E00DE128688078002840F0F4 +:10FD1000F98000F0F5B88DF8000068680178C1F34B +:10FD20008019D0F803100191B0F80700ADF8080071 +:10FD300069460520FFF724FE0028286873D08178E3 +:10FD4000002972D090F85BA0D5E90104D0F80F101B +:10FD5000C4F80E10B0F813106182417D2175817DC9 +:10FD60006175B0F81710E182B0F819106180B0F831 +:10FD70001B10A180B0F81D10E18000F11F0104F1FB +:10FD8000080015F0B0FD686890F8241001F01F011C +:10FD9000217690F82400400984F8740184F854A076 +:10FDA00084F855A0286890F8651084F8561090F8EB +:10FDB0005D0084F857009DF80010A86800F05BF91A +:10FDC000022008F0DEFB6868DBF800400DF1040A51 +:10FDD000078008210DF044FC002168680DF037FE13 +:10FDE000214668680DF03FFE0623002208F10E014F +:10FDF00068680DF012FE2868417B68680DF072FC9F +:10FE0000494668680DF07BFC06230122514668686C +:10FE10000DF003FE07F0EBFD002818BFFFDF032005 +:10FE20002968487070E066E0FFE76868AC684FF0EA +:10FE300001080278617BC2F3401211406173D0F86F +:10FE40000F10C4F80E10B0F813106182417D2175B7 +:10FE5000817D6175B0F81710E182B0F819106180EA +:10FE6000B0F81B10A180B0F81D10E18008E0000080 +:10FE70000406002060000020145B020053E4B36E0F +:10FE800000F11F0104F1080015F02DFD686890F8DD +:10FE9000241001F01F01217690F82400400984F815 +:10FEA000740184F8548084F85580286890F86510AF +:10FEB00084F8561090F85D0084F857009DF8001003 +:10FEC000A86800F0D8F8286880F868A090F86A2040 +:10FED000B0F86C100120FEF717FA2868477007F099 +:10FEE00044F907F023F906F0FBFF07F079F8012049 +:10FEF00008F047FB08E090F82200C0F3001008B1BA +:10FF0000012601E0FEF743FE286890F8330018B19F +:10FF100007F041F906F009FB66B100210120FFF767 +:10FF200098F910E0286890F82200C0F3001000282B +:10FF3000E8D0E5E728688178012904D190F85B10C2 +:10FF4000FF2006F0E1FC28684178002919BF4178BC +:10FF5000012903B0BDE8F08F4078032818BFFFDF08 +:10FF600003B0BDE8F08F70B57E4C06460D462068A4 +:10FF7000807858B106F07EFC21680346304691F83F +:10FF80005B202946BDE8704009F0C6B806F072FC57 +:10FF900021680346304691F85A202946BDE8704052 +:10FFA00009F0BAB878B50C4600210091082804BFC2 +:10FFB0004FF4C87040210DD0042804BF4FF4BF7027 +:10FFC000102107D0022807BF01F11800042101F118 +:10FFD00028000821521D02FB010562489DF800100F +:10FFE000006890F85C2062F3050141F040068DF84E +:10FFF000006090F85B00012828D002282DD0082846 +:020000040001F9 +:1000000018BFFFDF2FD000BF26F080008DF8000062 +:10001000C4EB041000EB80001E2101EB800005FB07 +:1000200004045148844228BFFFDF5048A0FB04105D +:10003000BDF80110000960F30C01ADF80110BDF826 +:1000400000009DF8021040EA014078BD9DF80200D2 +:1000500020F0E0008DF80200D6E79DF8020020F0C5 +:10006000E000203004E09DF8020020F0E000403085 +:100070008DF80200C8E72DE9F0413A4D04460E46DE +:10008000286890F86800002818BFFFDF002728685C +:1000900080F86A702188A0F86C106188A0F882103E +:1000A000A188A0F88410E188A0F8861094F8741153 +:1000B00080F8881090F82F1049B1427B00F10E01B2 +:1000C000012A04D1497901F0C001402934D090F8C7 +:1000D000301041B1427B00F10E01012A04BF497981 +:1000E00011F0C00F28D000F1760015F0F3FB68681E +:1000F000FF2E0178C1F380116176D0F80310C4F8A7 +:100100001A10B0F80700E08328681DD0C167E18BA2 +:10011000A0F8801000F17002511E30460CF044F837 +:10012000002808BFFFDF286890F86F1041F0020137 +:1001300080F86F10BDE8F081D0F80E10C0F876108E +:10014000418AA0F87A10D2E7C767A0F88070617E74 +:1001500080F86F10D4F81A100167E18BA0F87410C2 +:10016000BDE8F08160000020C4BF03008988888852 +:100170000178406829B190F8141190F8730038B9EB +:1001800001E001F0CDBD19B1042901D00120704773 +:100190000020704770B50C460546062102F02AFC87 +:1001A000606008B1002006E00721284602F022FC2A +:1001B000606018B101202070002070BD022070BD69 +:1001C0002DE9FC470C4606466946FFF7E3FF002889 +:1001D0007DD19DF8000050B1FEF727F9B0427CD0E8 +:1001E000214630460AF088F9002873D12DE00DF041 +:1001F000E7FEB04271D02146304613F027FB0028BD +:1002000068D1019D95F8D80022E0012000E000208F +:10021000804695F837004FF0010A4FF00009F0B121 +:1002200095F8380080071AD584F8019084F800A06A +:1002300084F80490E68095F839102172698F618105 +:10024000A98FA18185F8379044E0019D95F81401AC +:1002500058350028DBD1E87E0028D8D0D5E73046D5 +:1002600002F00CFD070000D1FFDF384601F01CFF53 +:1002700040B184F801900F212170E680208184F83C +:1002800004A027E0304602F0E7FC070000D1FFDFC2 +:10029000B8F1000F21D0384601F05DFFB8B19DF8EC +:1002A000000038B90198D0F800014188B14201D16D +:1002B00080F80090304607F0E8FB84F801900C21AC +:1002C000217084F80490E680297F217200E004E028 +:1002D00085F81B900120BDE8FC870020FBE71CB5DA +:1002E0006946FFF757FF00B1FFDF684601F024FDC4 +:1002F000FB4900208968A1F8DA001CBD2DE9FC410A +:1003000004460E46062002F01DFB0546072002F0BB +:1003100019FB2844C7B20025A8463E4417E02088B0 +:10032000401C80B22080B04202D34046A4F8008036 +:1003300080B2B84204D3B04202D20020BDE8FC81B2 +:100340006946FFF727FF0028F8D06D1CEDB2AE42DA +:10035000E5D84FF6FF7020801220EFE738B54FF652 +:10036000FF70ADF800000DE00621BDF8000002F0BE +:1003700053FB04460721BDF8000002F04DFB0CB111 +:1003800000B1FFDF00216846FFF7B8FF0028EBD07F +:1003900038BD70B507F0E6FB0BF0CDFCD14C4FF645 +:1003A000FF7600256683A683CFA0257001680079BB +:1003B000A4F14002657042F8421FA11C1071601C3C +:1003C00013F065FB25721B2060814FF4A471A1819D +:1003D000E08121820321A1740422E274A082E082E0 +:1003E000A4F13E00218305704680BD480C300570A5 +:1003F000A4F110000570468070BD70B5B84C16466B +:100400000D466060217007F027FBFFF7A7FFFFF79D +:10041000C0FF207810F0CDFFB5480EF07CFA2178AF +:10042000606813F0D9FA20780AF0D4FE284608F064 +:1004300010FCAF48FEF704F8217860680AF042F932 +:100440003146207813F0DAFDBDE870400BF073BC44 +:1004500010B501240AB1002010BD21B1012903D03B +:100460000024204610BD02210DF068FBF9E72DE9BC +:10047000F047040000D1FFDF9A4802211C3081467A +:10048000FFF73CFF00B1FFDF964D0620B5F81C805A +:1004900002F058FA0646072002F054FA3044C6B279 +:1004A000701CC7B2A88BB04228D120460DF0FEFCCC +:1004B000B0B1207818283FD1207901283CD1E088BC +:1004C000062102F097FA040000D1FFDF208807F030 +:1004D000DCFA2088062102F09FFA40B3FFDF2BE010 +:1004E000287860B300266670142020702021201D1B +:1004F00015F0E5F8022020712E701DE0B84217D1EA +:100500002046FDF737FFD0B12078172814D1207985 +:1005100068B1E088072102F06DFA40B1008807F069 +:10052000B4FAE088072102F077FA00B1FFDF03E0B8 +:100530002146FFF745FE10B10120BDE8F0870221FA +:100540004846FFF7DBFE10B9A98B4145AAD12046EA +:10055000BDE8F04713F098BD10B501F089FB08B174 +:100560000C2010BD0BF03AFC002010BD10B5044665 +:10057000007818B1012801D0122010BD01F089FBCC +:1005800020B10BF0DBFD08B10C2010BD207801F08C +:1005900036FBE21D04F11703611CBDE810400BF0AF +:1005A000C2BC10B5044601F063FB08B10C2010BDBD +:1005B000207828B1012803D0FF280BD0122010BDCD +:1005C00001F01DFB611C0BF0C9FB08B1002010BD40 +:1005D000072010BD01200BF0FBFBF7E710B50BF077 +:1005E000B0FD08B1002010BD302010BD10B504468C +:1005F00001F04FFB08B10C2010BD20460BF09BFD15 +:10060000002010BD10B501F044FB20B10BF096FDA9 +:1006100008B10C2010BD0BF0EBFC002010BDFF2139 +:1006200081704FF6FF7181802D4949680A78827187 +:100630008A880281498841810121417000207047E8 +:100640007CB50025022A19D015DC12F10C0F15D04B +:1006500009DC12F1280F11D012F1140F0ED012F193 +:10066000100F11D10AE012F1080F07D012F1040F98 +:1006700004D04AB902E0D31E052B05D8012806D0C4 +:10068000022808D003280AD0122528467CBD10462F +:10069000FEF75EFAF9E710460EF0E8F8F5E70846CF +:1006A00014466946FFF776FD08B10225EDE79DF88F +:1006B00000000198002580F85740E6E710B5134682 +:1006C00001220CF0E5FB002010BD10B5044611F02E +:1006D00070FC05280ED0204610F05AFE002010BDF8 +:1006E0006C000020E8070020FFFFFFFF1F00000054 +:1006F000A80600200C20F2E710B5044601F0C9FA64 +:1007000008B10C20EBE72146002007F02CFA00206E +:10071000E5E710B5044610F0C9FE50B108F02AFD17 +:1007200038B1207808F0BBFA20780EF0DBFB00200F +:10073000D5E70C20D3E710B5044601F0AAFA08B1BA +:100740000C20CCE72146012007F00DFA0020C6E777 +:1007500038B504464FF6FF70ADF80000A079E17996 +:10076000884216D02079FDF766FD90B16079FDF7DB +:1007700062FD70B10022A079114614F0B3F840B9BF +:100780000022E079114614F0ADF810B9207A07285C +:1007900001D9122038BD08F0FAFC60B911F009FC4B +:1007A00048B900216846FFF7A9FD20B1204606F0B0 +:1007B00086F8002038BD0C2038BD2DE9FC41817839 +:1007C00005461A2925D00EDC16292DD2DFE801F0C6 +:1007D0002C2C2C2C2C212C2C2C2C2C2C2C2C2C2C64 +:1007E0002C2C2C2121212A291ED00BDCA1F11E0149 +:1007F0000C2919D2DFE801F0181818181818181861 +:100800001818180D3A3904290ED2DFE801F00D024C +:100810000D022888B0F5706F06D201276946FFF7F0 +:10082000B9FC18B1022089E5122087E59DF8000087 +:1008300001F0ECF9019C08B1FC3401E004F5BC7452 +:100840009DF8000001F0E2F9019E08B1FD3601E0DB +:1008500006F279166846FFF78BFC08B1207808B1DC +:100860000C206BE52770A8783070684601F064FAB8 +:10087000002063E57CB50D466946FFF78BFC00263A +:1008800018B12E602E7102207CBD9DF8000001F091 +:10089000BDF9019C9DF80000583401F0B7F90198AA +:1008A00084F8406081682960017B297194F84010C8 +:1008B0000029F5D100207CBD70B5044691F85500A3 +:1008C00091F856300D4610F00C0F00D1002321890D +:1008D000A0880CF0A1FC696A81421DD2401A401C1C +:1008E000A1884008091A8AB2A2802189081A2081A9 +:1008F000668895F8541010460CF035FC864200D2FC +:1009000030466080E68895F8551020890CF02BFC65 +:10091000864200D23046E08070BDF0B585B00D460D +:10092000064603A9FFF736FC00282DD19DF80C00E0 +:1009300060B300220499FB20B1F84A30FB2B00D3AE +:100940000346B1F84C40FB20FB2C00D30446DFF8F3 +:100950003CCC9CE8811000900197CDF808C0ADF820 +:100960000230ADF806406846FFF7A6FF6E80BDF87E +:100970000400E880BDF808006881BDF80200A88086 +:10098000BDF806002881002005B0F0BD0122D1E7A6 +:100990002DE9F04186B0044600886946FFF7FAFB6E +:1009A000002876D12189E08801F0D5F9002870D19E +:1009B000A188608801F0CFF900286AD12189E088F8 +:1009C00001F0D7F9002864D1A188608801F0D1F93D +:1009D00007005ED1208802A9FFF79FFF00B1FFDF6B +:1009E000BDF8101062880920914252D3BDF80C1056 +:1009F000E28891424DD3BDF81210BDF80E20238934 +:100A00001144A2881A44914243D39DF80010019DDD +:100A10004FF00008012640F6480041B185F8A36177 +:100A2000019991F8E61105F5D17541B91AE085F8FB +:100A30000D61019991F8301105F5867509B13A27D4 +:100A400024E0E18869806188E9802189814200D3BE +:100A50000146A980A188814200D20846288101224E +:100A600001990FE0E18869806188E98021898142EC +:100A700000D30146A980A188814200D2084628817E +:100A8000019900222846FFF717FF2E7085F8018094 +:100A9000384606B0BDE8F0817AE710B5044601F0AB +:100AA000F8F820B10BF04AFB08B10C2017E62078CB +:100AB00001F0A5F8E279611C0BF0C1FC08B100203F +:100AC0000DE602200BE610B503780446002B4068C3 +:100AD00013460A46014609D05FF001000CF0A5FB61 +:100AE0006168496A884203D90120F8E50020F5E7EA +:100AF0000020F4E52DE9F04117468A781E4680462D +:100B000042B11546C87838B10446690706D52AB1FE +:100B1000012104E00725F5E70724F6E70021620735 +:100B200002D508B1012000E00020014206D00122D8 +:100B300011464046FFF7C7FF98B93BE051B100228C +:100B400001214046FFF7BFFF58B9600732D50122A7 +:100B500011461FE058B1012200214046FFF7B3FFC4 +:100B600008B1092096E7680724D5012206E0680746 +:100B70004FEA44700AD5002813DB002201214046C9 +:100B8000FFF7A1FFB0B125F0040513E0002811DA4A +:100B9000012200214046FFF796FF58B124F00404DB +:100BA00008E0012211464046FFF78DFF10B125F005 +:100BB0000405F3E73D70347000206BE710B586B094 +:100BC0000446008803A9FFF7E5FA002806D1A088AB +:100BD00030B1012804D0022802D0122006B07EE5F0 +:100BE0006B4602AA214603A8FFF784FF0028F5D12F +:100BF0009DF80C3000220121002B049B06D083F8C5 +:100C0000AD11049B93F8FA316BBB24E083F8171104 +:100C1000049B93F83C313BB9049B93F816311BB904 +:100C2000049B93F87D300BB13A2010E0049B83F8CD +:100C30001611049B9DF8081083F81811049B9DF869 +:100C4000001083F81911049BA188A3F81A110499C4 +:100C500081F81721C2E7049B93F8AC311BB9049BC0 +:100C600093F87D300BB13A2010E0049B83F8AC116F +:100C7000049B9DF8081083F8AE11049B9DF80010AA +:100C800083F8AF11049BA188A3F8B011049981F8EF +:100C9000AD21A3E710B504460020A17801B90120D9 +:100CA000E2780AB940F0020001F06CF8002803D1A4 +:100CB0002046BDE8104081E711E570B51C460D46A1 +:100CC00018B1012801D0122070BD1946104601F05C +:100CD00069F830B12146284601F06EF808B10020CD +:100CE00070BD302070BD70B5044600780E460128F6 +:100CF00004D018B1022801D0032841D1607828B16E +:100D0000012803D0022801D0032839D1E07B10B993 +:100D1000A078012834D1A07830F0050130D110F04E +:100D2000050F2DD06289E188E0783346FFF7C5FFD3 +:100D3000002826D1A07805281ED16589A28921899D +:100D400020793346FFF7B9FF00281AD15FF0010080 +:100D500004EB40014A8915442218D37892789342D3 +:100D60000ED1CA8889888A420AD1401CC0B20228A2 +:100D7000EED3E088A84203D3A07B08B1072801D9AD +:100D8000122070BD002070BD10B586B0044600F082 +:100D900062FF08B10C2021E7022104F10A0001F0F2 +:100DA0001EF8A0788DF80800A0788DF80000607813 +:100DB0008DF8040020788DF80300A07B8DF80500E5 +:100DC000E07B00B101208DF80600A078C10717D0A4 +:100DD000E07800F0FBFF8DF80100E088ADF80A0034 +:100DE0006089ADF80C00A078400716D5207900F096 +:100DF000EDFF8DF802002089ADF80E00A0890AE011 +:100E000040070AD5E07800F0E1FF8DF80200E088A5 +:100E1000ADF80E006089ADF8100002A810F052FB8A +:100E20000028B8D168460EF062F8D7E610B504463F +:100E30000121FFF758FF002803D12046BDE81040EC +:100E4000A2E74CE40278012A01D0BAB118E0427856 +:100E50003AB1012A05D0022A12D189B1818879B12B +:100E600000E059B1418849B1808838B101EB810176 +:100E7000490000EB8000B1EB002F01D20020704749 +:100E80001220704770B5044600780D46012809D03D +:100E900011F08FF8052803D010F025FA002800D0B3 +:100EA0000C2070BD0DF0F0FE88B10DF002FF0DF0CA +:100EB000FBFF0028F5D125B160780DF08CFF0028EC +:100EC000EFD1A1886088BDE8704010F021BB1220EE +:100ED00070BD10B504460121FFF7B4FF002804D10E +:100EE0002046BDE810400121CCE704E42DE9F0479D +:100EF0000746B0F84C50FB2092460E46FB2D00D31F +:100F00000546DFF88C86B8F80A00A84200D20546EC +:100F100097F85510284600F08DFEB8F80C10814265 +:100F200000D208468146B7F84A40FB20FB2C00D38C +:100F30000446B8F80E00A04200D2044697F85410B8 +:100F4000204600F077FEB8F81010814200D2084623 +:100F50004FF4A4721B2C01D0904203D11B2D25D03D +:100F6000914523D0F580A6F808907480B080524651 +:100F700039463046FFF7A0FC01203070F0881B385E +:100F8000E02800D9FFDF70881B38E02800D9FFDF98 +:100F9000308940F64814A0F5A470A04200D9FFDFC4 +:100FA000B088A0F5A470A04200D9FFDFBDE8F087AB +:100FB000F0B5871FDDE9056540F67B44A74213D2F3 +:100FC0008F1FA74210D288420ED8B2F5FA7F0BD2FB +:100FD000A3F10A00241FA04206D2521C4A43B2EBDE +:100FE000830F01DAAE4201D90020F0BD0120F0BD2F +:100FF0002DE9FC47477A8946044617F0050F7DD056 +:10100000F8087BD194F83A0008B9012F76D1002571 +:10101000A8462E46F90789F0010A19D0208A5146C0 +:1010200000F0C0FEF0B36089514600F0C5FEC8B3C1 +:10103000208A6189884261D8A18EE08DCDE90001C6 +:10104000238D628CA18BE08AFFF7B2FF50B301259C +:10105000B8070ED504EB4500828EC18DCDE9001294 +:10106000038D428C818BC08AFFF7A2FFD0B1A846C6 +:101070006D1C78071ED504EB45065146308A00F0FA +:1010800091FE78B17089514600F096FE50B1308AD9 +:10109000718988425ED8B18EF08DCDE90001338D23 +:1010A000728C00E00AE0B18BF08AFFF781FF28B173 +:1010B0002E466D1CB9F1000F03D030E03020BDE8A2 +:1010C000FC87F80707D0780705D504EB460160894F +:1010D000498988423ED1228A01211BE0414503D043 +:1010E00004EB4100008A024404EB4100C38A868A73 +:1010F000B3422FD1838B468BB34200E02AE029D143 +:10110000438C068CB34225D1038DC08C834221D100 +:10111000491CC9B2A942E1D3608990421AD3207810 +:1011200010B1012816D10DE0A078B9F1000F07D059 +:1011300040B1012806D0022804D003280AD101E0DA +:101140000028EED1607838B1012805D0022803D0FC +:10115000032801D01220B2E70020B0E7002147E7C2 +:101160000178C90702D0406812F061BF12F02EBFAB +:101170002DE9F04788B00D46AFF69422D2E90092EF +:10118000014690462846FFF733FF06000CD100F0D9 +:1011900062FD40B9FE4F387828B90CF011FFA0F578 +:1011A0007F41FF3902D00C2008B0FFE6032105F192 +:1011B000100000F014FEF64801AA3E380190F548F0 +:1011C0000290F34806211038039007A801F0E0FBD5 +:1011D000040035D003210BF0BBFBB98AA4F84A10F8 +:1011E000FA8AA4F84C20FB7C0093BA46BB7C20888A +:1011F00001F0BBFC00B1FFDF208806F045FC218830 +:1012000004F10E0000F04FFDE3A004F112070068A6 +:1012100000900321684604F007FE002069460A5C3E +:101220003A54401CC0B20328F9D3A88B6080688C64 +:10123000A080288DE080687A410703D508270AE05E +:101240000920B1E7C10701D0012704E0800701D5DB +:10125000022700E000273A46BAF81800114610F0BD +:10126000EBF90146A062204610F0F4F917F00C0FDC +:1012700009D001231A46214600200BF0D6FF616AEF +:10128000884200D90926002784F85E7084F85F70D0 +:10129000A87800F0B4FC6076D5F80300C4F81A0012 +:1012A000B5F80700E083C4F8089084F80C800120AA +:1012B00084F80801024604F586712046FFF716FE01 +:1012C0008DF800700121684604F0AEFD9DF8000025 +:1012D00000F00701C0F3C1021144C0F340100844FC +:1012E0008DF80000401D2076092801D208302076B4 +:1012F000002120460BF02CFB68780DF0D0FCEEBBF3 +:10130000A9782878EA1C0DF092FC48B10DF0D1FCC8 +:10131000A9782878EA1C0DF038FD060002D052E0CA +:10132000122650E0687A00F005010020CA0700D0BC +:1013300001208A0701D540F00200490701D540F09D +:1013400008000DF05DFC06003DD1214603200DF0A4 +:1013500046FD060037D10DF04CFD060033D1697A09 +:1013600001F005018DF81010697AC90708D0688965 +:10137000ADF81200288AADF8140000E023E0012047 +:10138000697A8A0700D5401C490707D505EB40005C +:101390004189ADF81610008AADF8180004A810F0C5 +:1013A00091F8064695F83A0000B101200DF03AFC9C +:1013B0004EB90DF079FD060005D1A98F204610F039 +:1013C00023F8060008D0208806F05FFB208806215D +:1013D00001F022FB00B1FFDF3046E5E601460020C8 +:1013E000C6E638B56A48007878B910F0E2FD0528FD +:1013F00005D00CF0E5FDA0F57F41FF3905D068462A +:1014000010F0C9F8040002D00CE00C2038BD0098A0 +:10141000008806F03AFB00980621008801F0FCFAEB +:1014200000B1FFDF204638BD1CB582894189CDE976 +:1014300000120389C28881884088FFF7B9FD08B18E +:1014400000201CBD30201CBD70B50546FFF7ECFF29 +:1014500000280ED12888062101F0CCFA040007D01C +:1014600000F05EFC20B1D4F80001017831B901E050 +:10147000022070BDD4F84C11097809B13A2070BD32 +:1014800005218171D4F8001100200881D4F80011E1 +:10149000A8884881D4F80011E8888881D4F8001120 +:1014A0002889C881D4F80001028941898A4204D878 +:1014B0008279082A01D88A4201D3122070BD298876 +:1014C0004180D4F8001102200870002070BD3EB5A4 +:1014D00004460BF06FFCB0B12D480125A0F140028D +:1014E0004570236842F8423F23790021137141700F +:1014F0006946062001F007FA00B1FFDF684601F0F7 +:10150000E0F910B10EE012203EBDBDF80440029893 +:1015100080F80851684601F0D4F918B9BDF8040004 +:10152000A042F4D100203EBD70B5054600880621DA +:1015300001F060FA040007D000F0F2FB20B1D4F80B +:101540000011087830B901E0022070BDD4F84C01D8 +:10155000007808B13A2070BD9620005D10F0010FB0 +:1015600024D0D5F802004860D5F806008860D4F889 +:101570000001698910228181D4F8000105F10C0174 +:101580000E3004F5807413F0F9FF07E0385B0200B9 +:10159000E807002078000020112233002168032092 +:1015A0000870216828884880002070BD0C2070BD1C +:1015B00038B504460078EF284DD86088ADF80000B3 +:1015C000009800F01DFC88B36188080708D4D4E9AE +:1015D000012082423FD8202A3DD3B0F5804F3AD82F +:1015E000207B18B3072836D8607B28B1012803D0A8 +:1015F000022801D003282ED14A0703D4022801D0A3 +:10160000032805D1A07B08B1012824D1480707D4BD +:10161000607D28B1012803D0022801D003281AD107 +:10162000C806E07D03D5012815D110E013E001289C +:1016300001D003280FD1C80609D4607E012803D049 +:10164000022801D0032806D1A07E0F2803D8E07E0F +:1016500018B1012801D0122038BD002038BDF8B5DE +:1016600014460D46064607F092FD08B10C20F8BD61 +:101670003046FFF79DFF0028F9D1FDF76EFA28707C +:10168000B07554B9FF208DF8000069460020FDF7C1 +:1016900053FA69460020FDF743FA3046BDE8F840AA +:1016A000FDF797B90022DAE770B50C46054612B18E +:1016B0001F2907D80CE0FF2C04D8FCF704FF18B151 +:1016C0001F2C01D9122070BD2846FCF7E6FE08B198 +:1016D000002070BD422070BD10B50446408810B196 +:1016E000FDF701FA78B12078618800F00102607896 +:1016F000FFF7DAFF002805D1FDF7DDF962888242A5 +:1017000003D9072010BD122010BD10466168FDF7F7 +:1017100013FA002010BD10B50446408810B1FCF744 +:10172000C4FE70B12078618800F001026078FFF794 +:10173000BBFF002804D160886168FDF7F1F9002043 +:1017400010BD122010BD7CB504464078422501280A +:1017500008D8A078FCF7A1FE20B120781225012836 +:1017600002D090B128467CBDFDF703FA20B1A088D5 +:101770000028F7D08028F5D8FDF702FA60B160782C +:101780000028EFD02078012808D006F09DFA044602 +:1017900007F0BCF900287DD00C207CBDFDF732F8A5 +:1017A00010B9FDF7DFF990B307F0F1FC0028F3D191 +:1017B000FCF73BFEA0F57F41FF39EDD1FDF744F882 +:1017C000A68842F210704643A079FDF79DF9FCF718 +:1017D00073FEF8B10022072101A801F0D9F8040036 +:1017E00043D0FA480321846020460AF0B6FF204621 +:1017F000FDF72CFDF64DA88AA4F84A00E88AA4F863 +:101800004C00FCF760FE60B1288B01210DE0FFE782 +:1018100012207CBD3146002007F044FAD8B3FFDF28 +:101820004CE0FDF7AFF90146288B07F0F0FA0146CE +:10183000A0620022204606F04AFAFCF744FEB0B946 +:10184000FDF7A0F910F00C0F11D001231A46214624 +:1018500018460BF0EAFC616A884208D90721BDF8F6 +:10186000040001F0D9F800B1FFDF09207CBDE87C5D +:101870000090AB7CEA8AA98A208801F076F900B151 +:10188000FFDF208806F000F93146204607F00AFA0B +:1018900018B101E008E011E0FFDF002204F5D1718A +:1018A0002046FFF723FB09E044B1208806F0EDF85D +:1018B0002088072101F0B0F800B1FFDF00207CBDD7 +:1018C000002140E770B50D46072101F093F80400B0 +:1018D00003D094F87B0110B10AE0022070BD94F8A7 +:1018E0006500142801D0152802D194F8C80108B168 +:1018F0000C2070BD1022294604F5BE7013F03EFE88 +:10190000012084F87B01002070BD10B5072101F093 +:1019100071F818B190F87B1111B107E0022010BDE9 +:1019200090F86510142903D0152901D00C2010BDA2 +:10193000022180F87B11002010BD2DE9FC410C46EE +:101940004BF68032122194421DD8E4B16946FEF76D +:1019500021FC002815D19DF8000000F057F9019EE8 +:101960009DF80000583600F051F9019DAD1C2F88FC +:101970002246394630460AF0E6FE2888B842F6D1BB +:101980000020BDE8FC810846FBE77CB504460088E2 +:101990006946FEF7FFFB002810D19DF8000000F01B +:1019A00035F9019D9DF80000583500F02FF9019898 +:1019B000A27890F82C10914201D10C207CBD7F219F +:1019C0002972A9720021E972E17880F82D1021793D +:1019D00080F82E10A17880F82C1000207CBD1CB55A +:1019E0000C466946FEF7D6FB00280AD19DF8000098 +:1019F00000F00CF9019890F8730000B101202070FC +:101A000000201CBD7CB50D4614466946FEF7C2FB9E +:101A1000002809D19DF8000000F0F8F8019890F82E +:101A20002C00012801D00C207CBD9DF8000000F0A6 +:101A3000EDF8019890F86010297090F8610020701E +:101A400000207CBD70B50D461646072100F0D2FF80 +:101A500018B381880124C388428804EB4104AC4256 +:101A600017D842F210746343A4106243B3FBF2F23E +:101A7000521E94B24FF4FA72944200D91446A54211 +:101A800000D22C46491C641CB4FBF1F24A43521E9E +:101A900091B290F8B4211AB901E0022070BD01841E +:101AA0003180002070BD10B50C46072100F0A2FF68 +:101AB00048B180F8E74024B190F8E51009B107F08B +:101AC000BCF9002010BD022010BD017899B1417809 +:101AD00089B141881B290ED381881B290BD3C1886A +:101AE000022908D33A490268403941F8522F406828 +:101AF0004860002070471220704710B504460FF070 +:101B000097FD204607F052F9002010BD10B507F0F0 +:101B100050F9002010BD2DE9F04115460F4606464C +:101B20000122114638460FF087FD04460121384650 +:101B300007F06DF9844200D2044601213046653C2D +:101B400000F069F806460121002000F064F83044F6 +:101B500001219630844206D900F19601201AB0FB8B +:101B6000F1F0401C81B229800020BDE8F08110B561 +:101B7000044600F08EF808B10C2010BD601C0AF07D +:101B800039FC207800F00100FCF759FE207800F0C5 +:101B900001000DF089F8002010BD10B507F003F921 +:101BA000002010BD10B50446072000F0BDFE08B1AE +:101BB0000C2010BD2078C00716D000226078114696 +:101BC00012F090FE30B1122010BD00006C00002019 +:101BD000E8070020A06809F0D4F86078D4F8041071 +:101BE00009F0D8F80020EFE7002009F0CAF800213A +:101BF0000846F5E710B505F02BFB0020E4E718B127 +:101C0000022801D0012070470020704708B1002051 +:101C100070470120704710B5012904D0022905D072 +:101C2000FFDF2046D0E7C000503001E080002C30BC +:101C300084B2F6E711F00C0F04D04FF4747101EB8D +:101C4000801006E0022902D0C000703001E0800060 +:101C50003C3080B2704710B510F0ABF9042805D0C5 +:101C600010F0A7F9052801D00020ADE70120ABE76F +:101C700010B5FFF7F0FF10B10DF0DAF828B907F052 +:101C800086FA20B1FCF7B6FD08B101209CE70020E0 +:101C90009AE710B5FFF7DFFF18B907F078FA0028C8 +:101CA00092D0012090E72DE9FE4300250F468046A3 +:101CB0000A260421404604F0E0F840460BF01BF8E9 +:101CC000062000F03FFE044615E06946062000F0BD +:101CD0001AFE0AE0BDF80400B84206D002980422B9 +:101CE00041460E3013F01EFC50B1684600F0E9FD8D +:101CF0000500EFD0641E002C06DD002DE5D005E0C8 +:101D000040460BF001F8F5E705B9FFDFD8F8000011 +:101D10000BF015F8761E01D00028CAD0BDE8FE836E +:101D200090F8D81090F8730020B919B1042901D0A7 +:101D30000120704700207047017800290AD04168CF +:101D400091F8E520002A05D0002281F8E5204068BE +:101D500007F073B870471B38E12806D2B1F5A47FAD +:101D600003D344F29020814201D912207047002011 +:101D70007047FB2802D8B1F5296F01D911207047AF +:101D80000020704770B514460546012200F05CF84B +:101D9000002806D121462846BDE87040002200F008 +:101DA00053B870BD042803D321B9B0F5804F01D9D1 +:101DB0000020704701207047042803D321B9B0F5F3 +:101DC000804F01D90020704701207047012802D0C0 +:101DD00018B100207047022070470120704710B5ED +:101DE00000224FF4C84408E030F81230A34200D972 +:101DF000234620F81230521CD2B28A42F4D3E3E6D2 +:101E000080B2C1060BD401071CD481064FEAC07111 +:101E100001D5B9B900E099B1800713D410E04106AB +:101E200010D481060ED4C1074FEA807104D0002976 +:101E300002DB400704D405E0010703D4400701D4C6 +:101E400001207047002070470AB1012200E0022201 +:101E5000024202D1C80802D109B100207047112006 +:101E60007047000030B5058825F4004421448CB249 +:101E70004FF4004194420AD2121B92B21B339A4291 +:101E800001D2A94307E005F40041214303E0A21A6F +:101E900092B2A9431143018030BD08440830504339 +:101EA0004A31084480B2704770B51D4616460B464D +:101EB000044629463046049AFFF7EFFF0646B34230 +:101EC00000D2FFDF2821204613F0F9FB4FF6FF7008 +:101ED000A082283EB0B265776080B0F5004F00D98F +:101EE000FFDF618805F13C00814200D2FFDF60889E +:101EF0000835401B343880B220801B2800D21B20BC +:101F000020800020A07770BD8161886170472DE935 +:101F1000F05F0D46C188044600F12809008921F4CC +:101F2000004620F4004800F062FB10B10020BDE83C +:101F3000F09F4FF0000A4FF0010BB0450CD9617FC4 +:101F4000A8EB0600401A0838854219DC09EB0600A8 +:101F50000021058041801AE06088617F801B471A5C +:101F6000083F0DD41B2F00DAFFDFBD4201DC2946FC +:101F700000E0B9B2681A0204120C04D0424502DD36 +:101F800084F817A0D2E709EB06000180428084F8AC +:101F900017B0CCE770B5044600F12802C088E37D95 +:101FA00020F400402BB110440288438813448B4234 +:101FB00001D2002070BD00258A4202D301804580F5 +:101FC00008E0891A0904090C418003D0A01D00F023 +:101FD0001EFB08E0637F00880833184481B26288E2 +:101FE000A01DFFF73FFFE575012070BD70B50346EA +:101FF00000F12804C588808820F400462644A842C1 +:1020000002D10020188270BD98893588A84206D375 +:10201000401B75882D1A2044ADB2C01E05E02C1A55 +:10202000A5B25C7F20443044401D0C88AC4200D9EE +:102030000D809C8924B1002414700988198270BD18 +:102040000124F9E770B5044600F12801808820F4E6 +:1020500000404518208A002825D0A189084480B274 +:10206000A08129886A881144814200D2FFDF288834 +:10207000698800260844A189884212D1A069807F1E +:102080002871698819B1201D00F0C1FA08E0637F4A +:1020900028880833184481B26288201DFFF7E2FEC9 +:1020A000A6812682012070BD2DE9F04141898788F3 +:1020B0000026044600F12805B94218D004F10A08A8 +:1020C00021F400402844418819B1404600F09FFAAD +:1020D00008E0637F00880833184481B26288404674 +:1020E000FFF7C0FE761C6189B6B2B942E8D130462E +:1020F000BDE8F0812DE9F04104460B4627892830E0 +:10210000A68827F40041B4F80A8001440D46B7427E +:1021100001D10020ECE70AB1481D106023B1627FB5 +:10212000691D184613F02AFA2E88698804F1080000 +:1021300021B18A1996B200F06AFA06E0637F6288DC +:102140000833991989B2FFF78DFE474501D12089DF +:1021500060813046CCE78188C088814201D101206E +:1021600070470020704701898088814201D1012099 +:1021700070470020704770B58588C38800F1280437 +:1021800025F4004223F4004114449D421AD083896F +:10219000058A5E1925886388EC18A64214D313B10A +:1021A0008B4211D30EE0437F08325C1922444088F1 +:1021B00092B2801A80B22333984201D211B103E067 +:1021C0008A4201D1002070BD012070BD2DE9F04789 +:1021D0008846C1880446008921F4004604F1280796 +:1021E00020F4004507EB060900F001FA002178BB56 +:1021F000B54204D9627FA81B801A002503E06088DD +:10220000627F801B801A083823D4E28962B1B9F852 +:102210000020B9F802303BB1E81A2177404518DBBD +:10222000E0893844801A09E0801A217740450ADBAA +:10223000607FE1890830304439440844C01EA4F866 +:102240001280BDE8F087454503DB01202077E7E7F2 +:10225000FFE761820020F4E72DE9F74F044600F123 +:102260002805C088884620F4004A608A05EB0A06E3 +:1022700008B1404502D20020BDE8FE8FE08978B168 +:102280003788B6F8029007EB0901884200D0FFDFDB +:10229000207F4FF0000B50EA090106D088B33BE0E5 +:1022A0000027A07FB9463071F2E7E18959B1607F1C +:1022B0002944083050440844B4F81F1020F8031D86 +:1022C00094F821108170E28907EB080002EB080105 +:1022D000E1813080A6F802B002985F4650B1637F7A +:1022E00030880833184481B26288A01DFFF7BAFD18 +:1022F000E78121E0607FE1890830504429440844A7 +:102300002DE0FFE7E089B4F81F102844C01B20F837 +:10231000031D94F82110817009EB0800E28981B255 +:1023200002EB0800E081378071800298A0B1A01D07 +:1023300000F06DF9A4F80EB0A07F401CA077A07D3E +:1023400008B1E088A08284F816B000BFA4F812B0EB +:1023500084F817B001208FE7E0892844C01B30F8CB +:10236000031DA4F81F10807884F82100EEE710B553 +:10237000818800F1280321F400442344848AC28820 +:10238000A14212D0914210D0818971B9826972B193 +:102390001046FFF7E8FE50B91089283220F40040BB +:1023A000104419790079884201D1002010BD1846E7 +:1023B00010BD00F12803407F08300844C01E1060A3 +:1023C000088808B9DB1E136008884988084480B271 +:1023D00070472DE9F04100F12806407F1C46083087 +:1023E0009046431808884D88069ADB1EA0B1C01C91 +:1023F00080B2904214D9801AA04200DB204687B2F6 +:1024000098183A46414613F08DF8002816D1E01B83 +:1024100084B2B844002005E0ED1CADB2F61EE8E73A +:10242000101A80B20119A94206D83044224641460A +:10243000BDE8F04113F076B84FF0FF3058E62DE9D3 +:10244000F04100F12804407F1E46083090464318B2 +:10245000002508884F88069ADB1E90B1C01C80B208 +:10246000904212D9801AB04200DB304685B29918EA +:102470002A46404613F082F8701B86B2A84400201A +:1024800005E0FF1CBFB2E41EEAE7101A80B2811912 +:10249000B94206D821183246404613F06FF8A81901 +:1024A00085B2284624E62DE9F04100F12804407F5A +:1024B0001E46083090464318002508884F88069A23 +:1024C000DB1E90B1C01C80B2904212D9801AB0427B +:1024D00000DB304685B298182A46414613F04EF884 +:1024E000701B86B2A844002005E0FF1CBFB2E41EAA +:1024F000EAE7101A80B28119B94206D82044324660 +:10250000414613F03BF8A81985B22846F0E5401D76 +:10251000704710B5044600F12801C288808820F475 +:1025200000431944904206D0A28922B9228A12B9E6 +:10253000A28A904201D1002010BD0888498831B19B +:10254000201D00F064F800202082012010BD637F70 +:1025500062880833184481B2201DFFF783FCF2E73C +:102560000021C18101774182C1758175704703885F +:102570001380C28942B1C28822F4004300F12802CC +:102580001A440A60C08970470020704710B504469D +:10259000808AA0F57F41FF3900D0FFDFE088A0826C +:1025A000E08900B10120A07510BD4FF6FF71818256 +:1025B00000218175704710B50446808AA0F57F41DF +:1025C000FF3900D1FFDFA07D28B9A088A18A884209 +:1025D00001D1002010BD012010BD8188828A914266 +:1025E00001D1807D08B1002070470120704720F4A0 +:1025F000004221F400439A4207D100F4004001F464 +:102600000041884201D0012070470020704730B55A +:10261000044600880D4620F40040A84200D2FFDFA7 +:1026200021884FF4004088432843208030BD70B596 +:102630000C00054609D0082C00D2FFDF1DB1A1B265 +:10264000286800F044F8201D70BD0DB100202860FE +:10265000002070BD0021026803E0938812681944CD +:1026600089B2002AF9D100F032B870B500260D46C3 +:102670000446082900D2FFDF206808B91EE004469E +:1026800020688188A94202D001680029F7D1818899 +:102690000646A94201D100680DE005F1080293B297 +:1026A0000022994209D32844491B02608180216895 +:1026B000096821600160206000E00026304670BD9E +:1026C00000230B608A8002680A6001607047002363 +:1026D0004360021D018102607047F0B50F4601881A +:1026E000408815460C181E46AC4200D3641B30448B +:1026F000A84200D9FFDFA019A84200D9FFDF38198E +:10270000F0BD2DE9F041884606460188408815460F +:102710000C181F46AC4200D3641B3844A84200D9B1 +:10272000FFDFE019A84200D9FFDF708838447080CD +:1027300008EB0400BDE8F0812DE9F0410546008872 +:102740001E461746841B8846BC4200D33C442C805E +:1027500068883044B84200D9FFDFA019B84200D9D8 +:10276000FFDF68883044688008EB0400E2E72DE969 +:10277000F04106881D460446701980B21746884607 +:102780002080B84201D3C01B20806088A84200D2BC +:10279000FFDF7019B84200D9FFDF6088401B6080FE +:1027A00008EB0600C6E730B50D460188CC18944208 +:1027B00000D3A41A4088984200D8FFDF281930BD02 +:1027C0002DE9F041C84D04469046A8780E46A04237 +:1027D00000D8FFDF05EB8607B86A50F8240000B187 +:1027E000FFDFB868002816D0304600F044F90146F3 +:1027F000B868FFF73AFF05000CD0B86A082E40F819 +:10280000245000D3FFDFB9484246294650F826300D +:10281000204698472846BDE8F0812DE9F8431E463A +:102820008C1991460F460546FF2C00D9FFDFB145B4 +:1028300000D9FFDFE4B200954DB300208046E81CCC +:1028400020F00300A84200D0FFDF4946DFF898924D +:10285000684689F8001089F8017089F8024089F803 +:10286000034089F8044089F8054089F8066089F832 +:102870000770414600F008F9002142460F464B46DA +:102880000098C01C20F00300009012B10EE001205F +:10289000D4E703EB8106B062002005E0D6F828C03B +:1028A0004CF82070401CC0B2A042F7D30098491CDD +:1028B00000EB8400C9B200900829E1D3401BBDE8B9 +:1028C000F88310B50446EEF724FD08B1102010BDC2 +:1028D0002078854A618802EB800092780EE0836A56 +:1028E00053F8213043B14A1C6280A180806A50F8BD +:1028F0002100A060002010BD491C89B28A42EED898 +:102900006180052010BD70B505460C460846EEF7FF +:1029100000FD08B1102070BD082D01D3072070BD47 +:1029200025700020608070BD0EB56946FFF7EBFF93 +:1029300000B1FFDF6846FFF7C4FF08B100200EBDFD +:1029400001200EBD10B50446082800D3FFDF6648FD +:10295000005D10BD3EB5054600246946FFF7D3FF74 +:1029600018B1FFDF01E0641CE4B26846FFF7A9FF7D +:102970000028F8D02846FFF7E5FF001BC0B23EBD97 +:1029800059498978814201D9C0B27047FF20704708 +:102990002DE9F041544B062903D007291CD19D791C +:1029A00000E0002500244FF6FF7603EB810713F8C3 +:1029B00001C00AE06319D7F828E09BB25EF823E073 +:1029C000BEF1000F04D0641CA4B2A445F2D8334673 +:1029D00003801846B34201D100201CE7BDE8F04156 +:1029E000EEE6A0F57F43FF3B01D0082901D300208C +:1029F0007047E5E6A0F57F42FF3A0BD0082909D2DF +:102A0000394A9378834205D902EB8101896A51F8EA +:102A100020007047002070472DE9F04104460D4624 +:102A2000A4F57F4143F20200FF3902D0082D01D303 +:102A30000720F0E62C494FF000088A78A242F8D926 +:102A400001EB8506B26A52F82470002FF1D02748B6 +:102A50003946203050F8252020469047B16A284654 +:102A600041F8248000F007F802463946B068FFF7C5 +:102A700027FE0020CFE61D49403131F810004FF607 +:102A8000FC71C01C084070472DE9F843164E88467B +:102A9000054600242868C01C20F00300286020465A +:102AA000FFF7E9FF315D4843B8F1000F01D0002284 +:102AB00000E02A680146009232B100274FEA0D007B +:102AC000FFF7B5FD1FB106E001270020F8E706EB90 +:102AD0008401009A8A602968641C0844E4B2286072 +:102AE000082CD7D3EBE6000008080020445B020066 +:102AF00070B50E461D46114600F0D4F8044629462E +:102B0000304600F0D8F82044001D70BD2DE9F0419A +:102B100090460D4604004FF0000610D00027E01C40 +:102B200020F00300A04200D0FFDFDDB141460020CD +:102B3000FFF77DFD0C3000EB850617B112E0012791 +:102B4000EDE7614F04F10C00A9003C602572606064 +:102B500000EB85002060606812F0B1FD41463868E6 +:102B6000FFF765FD3046BDE8F0812DE9FF4F564C7B +:102B7000804681B020689A46934600B9FFDF2068FE +:102B8000027A424503D9416851F8280020B143F246 +:102B9000020005B0BDE8F08F5146029800F082F8BF +:102BA00086B258460E9900F086F885B27019001D5D +:102BB00087B22068A14639460068FFF756FD040039 +:102BC0001FD0678025802946201D0E9D07465A4646 +:102BD00001230095FFF768F9208831463844012326 +:102BE000029ACDF800A0FFF75FF92088C119384696 +:102BF000FFF78AF9D9F800004168002041F8284021 +:102C0000C7E70420C5E770B52F4C0546206800B91A +:102C1000FFDF2068017AA9420ED9426852F82510D8 +:102C200051B1002342F825304A880068FFF748FD7B +:102C3000216800200A7A08E043F2020070BD4B6868 +:102C400053F8203033B9401CC0B28242F7D808682C +:102C5000FFF700FD002070BD70B51B4E0546002437 +:102C6000306800B9FFDF3068017AA94204D94068B2 +:102C700050F8250000B1041D204670BD70B5124EFD +:102C800005460024306800B9FFDF3068017AA942A8 +:102C900006D9406850F8251011B131F8040B4418DA +:102CA000204670BD10B50A460121FFF7F6F8C01C9A +:102CB00020F0030010BD10B50A460121FFF7EDF822 +:102CC000C01C20F0030010BD8000002070B5044639 +:102CD000C2F11005281912F051FC15F0FF0108D0BF +:102CE000491EC9B2802060542046BDE8704012F0F1 +:102CF000C4BC70BD30B505E05B1EDBB2CC5CD55CFE +:102D00006C40C454002BF7D130BD10B5002409E04D +:102D10000B78521E44EA430300F8013B11F8013BD3 +:102D2000D2B2DC09002AF3D110BD2DE9F04389B0FD +:102D30001E46DDE9107990460D00044622D0024679 +:102D40000846F949FDF7BAFC102221463846FFF73C +:102D5000DCFFE07B000606D5F34A3946102310322B +:102D60000846FFF7C7FF102239464846FFF7CDFF58 +:102D7000F87B000606D5EC4A494610231032084677 +:102D8000FFF7B8FF1021204612F077FC0DE0103E4F +:102D9000B6B208EB0601102322466846FFF7AAFFE9 +:102DA000224628466946FDF789FC102EEFD818D038 +:102DB000F2B241466846FFF789FF10234A4669464A +:102DC00004A8FFF797FF1023224604A96846FFF7DF +:102DD00091FF224628466946FDF770FC09B0BDE820 +:102DE000F08310233A464146EAE770B59CB01E4690 +:102DF0000546134620980C468DF8080020221946F7 +:102E00000DF1090012F0BAFB202221460DF1290034 +:102E100012F0B4FB17A913A8CDE90001412302AABF +:102E200031462846FFF781FF1CB070BD2DE9FF4FEA +:102E30009FB014AEDDE92D5410AFBB49CDE900764B +:102E4000202320311AA8FFF770FF4FF000088DF8FB +:102E500008804FF001098DF8099054F8010FCDF862 +:102E60000A00A088ADF80E0014F8010C1022C0F37F +:102E700040008DF8100055F8010FCDF81100A8881A +:102E8000ADF8150015F8010C2C99C0F340008DF831 +:102E9000170006A8824612F071FB0AA8834610228A +:102EA000229912F06BFBA0483523083802AA40682B +:102EB0008DF83C80CDE900760E901AA91F98FFF797 +:102EC00034FF8DF808808DF809902068CDF80A004D +:102ED000A088ADF80E0014F8010C1022C0F34000D9 +:102EE0008DF810002868CDF81100A888ADF81500FD +:102EF00015F8010C2C99C0F340008DF817005046CE +:102F000012F03CFB58461022229912F037FB8648FB +:102F10003523083802AA40688DF83C90CDE9007648 +:102F20000E901AA92098FFF700FF23B0BDE8F08F9C +:102F3000F0B59BB00C460546DDE922101E4617464B +:102F4000DDE92032D0F801C0CDF808C0B0F805C0E6 +:102F5000ADF80CC00078C0F340008DF80E00D1F839 +:102F60000100CDF80F00B1F80500ADF813000878A6 +:102F70001946C0F340008DF815001088ADF8160012 +:102F800090788DF818000DF11900102212F0F6FA61 +:102F90000DF129001022314612F0F0FA0DF139003E +:102FA0001022394612F0EAFA17A913A8CDE9000158 +:102FB000412302AA21462846FFF7B7FE1BB0F0BD09 +:102FC000F0B5A3B017460D4604461E46102202A8CF +:102FD000289912F0D3FA06A82022394612F0CEFA28 +:102FE0000EA82022294612F0C9FA1EA91AA8CDE976 +:102FF0000001502302AA314616A8FFF796FE169844 +:10300000206023B0F0BDF0B589B00446DDE90E07BD +:103010000D463978109EC1F340018DF800103178CB +:103020009446C1F340018DF801101968CDF80210E3 +:103030009988ADF8061099798DF808100168CDF8D7 +:1030400009108188ADF80D1080798DF80F001023DC +:103050006A46614604A8FFF74DFE2246284604A9A9 +:10306000FDF72CFBD6F801000090B6F80500ADF88E +:103070000400D7F80100CDF80600B7F80500ADF858 +:103080000A000020039010236A46214604A8FFF797 +:1030900031FE2246284604A9FDF710FB09B0F0BD19 +:1030A0001FB51C6800945B68019313680293526813 +:1030B0000392024608466946FDF700FB1FBD10B5A6 +:1030C00088B004461068049050680590002006906F +:1030D000079008466A4604A9FDF7F0FABDF800001B +:1030E000208008B010BD1FB51288ADF800201A88E6 +:1030F000ADF8022000220192029203920246084695 +:103100006946FDF7DBFA1FBD7FB5074B1446054640 +:10311000083B9A1C6846FFF7E6FF224669462846A8 +:10312000FFF7CDFF7FBD00009C5B020070B5044639 +:1031300000780E46012813D0052802D0092813D1A3 +:103140000EE0A06861690578042003F075F9052D8B +:103150000AD0782300220420616903F0C3F803E059 +:103160000420616903F068F931462046BDE87040EB +:1031700001F084B810B500F12D03C2799C78411D8F +:10318000144064F30102C271D2070DD04A795C7910 +:1031900022404A710A791B791A400A718278C978EB +:1031A0008A4200D9817010BD00224A71F5E741784A +:1031B000012900D00C21017070472DE9F04F93B028 +:1031C0004FF0000B0C690D468DF820B009780126F0 +:1031D0000C2017464FF00D084FF0110A4FF0080968 +:1031E0001B2975D2DFE811F01B00C20205031D0385 +:1031F0005C036F03A103B603F70318046004920491 +:103200009F04EB042905330551055C05ED053006E7 +:10321000330662067E06F8061C07E506EA0614B1C8 +:1032200020781D282AD0D5F808805FEA08004FD002 +:1032300001208DF82000686A02220D908DF824206C +:103240000A208DF82500A8690A90A8880028EED0E9 +:1032500098F8001091B10F2910D27DD2DFE801F06B +:103260007C1349DEFCFBFAF9F8F738089CF6F50008 +:1032700002282DD124B120780C2801D00026EEE3BD +:103280008DF82020CAE10420696A03F0D5F8A888E7 +:103290000728EED1204600F0ECFF022809D0204696 +:1032A00000F0E7FF032807D9204600F0E2FF0728D7 +:1032B00002D20120207004E0002CB8D02078012830 +:1032C000D7D198F80400C11F0A2902D30A2061E06F +:1032D000C3E1A070D8F80010E162B8F804102186AC +:1032E00098F8060084F8320001202870032020702E +:1032F00044E00728BDD1002C99D020780D28B8D102 +:1033000098F8031094F82F20C1F3C000C2F3C00254 +:10331000104201D0062000E00720890707D198F865 +:1033200005100142D2D198F806100142CED194F88E +:10333000312098F8051020EA02021142C6D194F813 +:10334000322098F8061090430142BFD198F804004B +:10335000C11F0A29BAD200E006E2617D81427CD811 +:10336000D8F800106160B8F80410218198F80600C0 +:10337000A072012028700E20207003208DF82000FC +:10338000686A0D9004F12D000990601D0A900F30BD +:103390000B9021E12875FDE3412891D1204600F0F2 +:1033A00068FF042802D1E078C00704D1204600F06D +:1033B00060FF0F2884D1A88CD5F80C8080B24FF024 +:1033C000400BE669FFF748FC324641465B464E46F5 +:1033D000CDF80090FFF733F80B208DF82000686AD5 +:1033E0000D90E0690990002108A8FFF79FFE207862 +:1033F000042806D0A07D58B1012809D003280AD09E +:1034000048E305202070032028708DF82060CCE16F +:1034100084F800A032E712202070E8E11128BCD126 +:10342000204600F026FF042802D1E078C00719D01A +:10343000204600F01EFF062805D1E078C00711D114 +:10344000A07D02280ED0204608E0CBE084E070E1A9 +:103450004FE122E102E1E8E019E0AEE100F009FF0E +:1034600011289AD1102208F1010104F13C0012F058 +:1034700085F8607801286ED012202070E078C007AF +:1034800060D0A07D0028C8D00128C6D05AE01128FD +:1034900090D1204600F0EDFE082804D0204600F030 +:1034A000E8FE132886D104F16C00102208F1010116 +:1034B000064612F063F8207808280DD014202070FA +:1034C000E178C8070DD0A07D02280AD06278022AD0 +:1034D00004D00328A1D035E00920F0E708B1012885 +:1034E00037D1C80713D0A07D02281DD0002000903E +:1034F000D4E9062133460EA8FFF777FC10220EA967 +:1035000004F13C0012F00EF8C8B1042042E7D4E9FF +:103510000912201D8DE8070004F12C0332460EA885 +:10352000616BFFF770FDE9E7606BC1F34401491E71 +:103530000068C84000F0010040F08000D7E7207824 +:10354000092806D185F800908DF8209032E3287084 +:10355000EBE30920FBE79CE1112899D1204600F01C +:1035600088FE0A2802D1E078C00704D1204600F086 +:1035700080FE15288CD104F13C00102208F10101D5 +:10358000064611F0FBFF20780A2816D0162020707E +:10359000D4E90932606B611D8DE80F0004F15C0312 +:1035A00004F16C0247310EA8FFF7C2FC10220EA9ED +:1035B000304611F0B7FF18B1F6E20B20207071E22F +:1035C0002046FFF7D7FDA078216A0A18C0F1100144 +:1035D000104612F052F823E3394608A8FFF7A6FD7B +:1035E00006463BE20228B8D1204600F042FE0428FD +:1035F00004D3204600F03DFE082809D3204600F001 +:1036000038FE0E2829D3204600F033FE122824D29B +:10361000A07D0228A1D10E208DF82000686A0D90AF +:1036200098F801008DF82400F0E3022895D1204697 +:1036300000F01FFE002810D0204600F01AFE0128DE +:10364000F9D0204600F015FE0C28F4D004208DF8A7 +:10365000240098F801008DF825005EE21128FCD1C5 +:10366000002CFAD020781728F7D16178606A0229F7 +:1036700011D0002101EB4101182606EBC1011022F7 +:10368000405808F1010111F079FF0420696A00F047 +:10369000E3FD2670F2E50121ECE70B28DDD1002CDB +:1036A000DBD020781828D8D16078616A02281CD035 +:1036B0005FF0000000EB4002102000EBC200095850 +:1036C000B8F8010008806078616A02280FD00020F5 +:1036D00000EB4002142000EBC2000958404650F8AD +:1036E000032F0A604068486039E00120E2E70120CA +:1036F000EEE71128B1D1002CAFD020781928ACD139 +:103700006178606A022912D05FF0000101EB41018B +:103710001C2202EBC1011022405808F1010111F0F6 +:103720002DFF0420696A00F097FD1A20B6E0012100 +:10373000ECE7082891D1002C8FD020781A288CD162 +:10374000606A98F80120017862F347010170616AAC +:10375000D8F8022041F8012FB8F80600888004202C +:10376000696A00F079FD8EE2072013E638780128B7 +:1037700094D1182204F11400796811F044FFE07923 +:10378000C10894F82F0001EAD001E07861F300004D +:10379000E070217D002974D12178032909D0C00768 +:1037A00025D0032028708DF82090686A0D90412064 +:1037B00004E3607DA178884201D90620EAE502266B +:1037C0002671E179204621F0E001E171617A21F072 +:1037D000F0016172A17A21F0F001A172FFF7CAFC39 +:1037E0002E708DF82090686A0D900720E6E2042084 +:1037F000ADE6387805289DD18DF82000686A0D90D7 +:10380000B8680A900720ADF824000A988DF830B007 +:103810006168016021898180A17A81710420207012 +:10382000F4E23978052985D18DF82010696A0D9167 +:10383000391D09AE0EC986E80E004121ADF82410ED +:103840008DF830B01070A88CD7F80C8080B240266C +:10385000A769FFF713FA41463A463346C846CDF802 +:103860000090FEF720FE002108A8FFF75FFCE0783B +:1038700020F03E00801CE0702078052802D00F2048 +:103880000CE049E1A07D20B1012802D0032802D03C +:1038900002E10720C0E584F80080EFE42070EDE449 +:1038A000102104F15C0002F0E8FA606BB0BBA07D6F +:1038B00018B1012801D00520FDE006202870F74846 +:1038C0006063A063BEE23878022894D1387908B1E9 +:1038D0002875B3E3A07D022802D0032805D022E09A +:1038E000B8680028F5D060631CE06078012806D035 +:1038F000A07994F82E10012805D0E84806E0A179B7 +:1039000094F82E00F7E7B8680028E2D06063E0780A +:10391000C00701D0012902D0E04803E003E0F868C5 +:103920000028D6D0A063062011E68DF82090696AA1 +:103930000D91E1784846C90709D06178022903D181 +:10394000A17D29B1012903D0A17D032900D0072041 +:10395000287031E138780528BBD1207807281ED09F +:1039600084F800A005208DF82000686A0D90B868E2 +:103970000A90ADF824A08DF830B003210170E178F1 +:10398000CA070FD0A27D022A1AD000210091D4E9E3 +:10399000061204F15C03401CFFF727FA67E384F882 +:1039A0000090DFE7D4E90923211D8DE80E0004F122 +:1039B0002C0304F15C02401C616BFFF724FB56E30F +:1039C000626BC1F34401491E1268CA4002F0010152 +:1039D00041F08001DAE738780528BDD18DF8200064 +:1039E000686A0D90B8680A90ADF824A08DF830B0E0 +:1039F000042100F8011B102204F15C0111F0BEFD4E +:103A0000002108A8FFF792FB2078092801D0132095 +:103A100044E70A2020709CE5E078C10742D0A17DF0 +:103A2000012902D0022927D038E0617808A80129AD +:103A300016D004F16C010091D4E9061204F15C0384 +:103A4000001DFFF7BDFA0A20287003268DF820809C +:103A5000686A0D90002108A8FFF768FBDDE2C3E269 +:103A600004F15C010091D4E9062104F16C03001D0E +:103A7000FFF7A6FA0026E9E7C0F3440114290DD2A6 +:103A80004FF0006101EBB0104FEAB060E070607879 +:103A9000012801D01020BFE40620FFE6607801284D +:103AA0003FF4B8AC0A2052E5E178C90708D0A17DFF +:103AB000012903D10B20287004202FE028702DE06D +:103AC0000E2028706078616B012817D004F15C0328 +:103AD00004F16C020EA8FFF7E3FA2046FFF74AFB59 +:103AE000A0780EAEC0F11001304411F0C6FD0620E2 +:103AF0008DF82000686A09960D909AE004F16C0335 +:103B000004F15C020EA8FFF7CBFAE9E73978022945 +:103B100003D139790029D1D029758FE28DF82000A1 +:103B2000686A0D9058E538780728F6D1D4E909215C +:103B30006078012809D000BF04F16C00CDE90002D3 +:103B4000029105D104F16C0304E004F15C00F5E797 +:103B500004F15C0304F14C007A680646216AFFF721 +:103B600065F96078012821D1A078216A0A18C0F18E +:103B70001001104611F081FDD4E90923606B04F1B6 +:103B80002D018DE80F0004F15C0304F16C02314655 +:103B90000EA800E054E2FFF7CBF910220EA904F1C1 +:103BA0003C0011F0BFFC08B10B20AFE485F80080A9 +:103BB0008DF82090686A0D908DF824A00CE5387877 +:103BC0000528AAD18DF82000686A0D90B8680A907F +:103BD000ADF824A08DF830B080F80080617801291C +:103BE0001AD0D4E9093204F12D01A66B0392009694 +:103BF000CDE9011304F16C0304F15C0204F14C0102 +:103C0000401CFFF795F9002108A8FFF78FFA6078AC +:103C1000012805D0152041E6D4E90923611DE4E718 +:103C20000E20287006208DF82000686ACDF824B098 +:103C30000D90A0788DF82800CEE438780328C0D104 +:103C4000E079C00770D00F202870072066E7387829 +:103C500004286BD11422391D04F1140011F0D3FC97 +:103C6000616A208CA1F80900616AA078C871E179C5 +:103C7000626A01F003011172616A627A0A73616A11 +:103C8000A07A81F82400162061E485F800A08DF860 +:103C90002090696A50460D9190E000009C5B020004 +:103CA0003878052842D1B868A8616178606A02292D +:103CB00001D0012100E0002101EB4101142606EBB7 +:103CC000C1014058082102F0D8F86178606A0229E1 +:103CD00001D0012100E0002101EB410106EBC1010F +:103CE000425802A8E169FFF70FFA6078626A022879 +:103CF00001D0012000E0002000EB4001102000EB8B +:103D0000C1000223105802A90932FEF7F3FF626ACC +:103D1000FD4B0EA80932A169FFF7E5F96178606AE9 +:103D2000022904D0012103E042E18BE0BDE0002143 +:103D300001EB4101182606EBC101A27840580EA9FB +:103D400011F01CFC6178606A022901D0012100E0B9 +:103D5000002101EB410106EBC1014058A178084464 +:103D6000C1F1100111F089FC05208DF82000686A6E +:103D70000D90A8690A90ADF824A08DF830B0062106 +:103D800001706278616A022A01D0012200E00022FB +:103D900002EB420206EBC202401C8958102211F0CD +:103DA000EDFB002108A8FFF7C1F91220C5F818B0F3 +:103DB00028708DF82090686A0D900B208DF82400F3 +:103DC0000AE43878052870D18DF82000686A0D90D3 +:103DD000B8680A900B20ADF824000A9807210170FA +:103DE0006178626A022901D0012100E0002101EB23 +:103DF0004103102101EBC30151580988A0F80110BB +:103E00006178626A022902D0012101E02FE10021DC +:103E100001EB4103142101EBC30151580A6840F83A +:103E2000032F4968416059E01920287001208DF85E +:103E3000300077E6162028708DF830B0002108A8F1 +:103E4000FFF774F9032617E114202870B0E63878DC +:103E500005282AD18DF82000686A0D90B8680A906C +:103E6000ADF824A08DF830B080F800906278616AD7 +:103E70004E46022A01D0012200E0002202EB42025B +:103E80001C2303EBC202401C8958102211F076FB60 +:103E9000002108A8FFF74AF9152028708DF8206046 +:103EA000686A0D908DF824603CE680E0387805283B +:103EB0007DD18DF82000686A0D90B8680A90ADF841 +:103EC000249009210170616909784908417061698C +:103ED00051F8012FC0F802208988C18020781C2861 +:103EE000A8D1A1E7E078C00702D04FF0060C01E0AE +:103EF0004FF0070C607802280AD000BF4FF0000096 +:103F000000EB040101F1090105D04FF0010004E0CC +:103F10004FF00100F4E74FF000000B78204413EA63 +:103F20000C030B7010F8092F02EA0C02027004D186 +:103F30004FF01B0C84F800C0D2B394F801C0BCF160 +:103F4000010F00D09BB990F800C0E0465FEACC7C3E +:103F500004D028F001060670102606E05FEA887C8F +:103F600005D528F00206067013262E70032694F855 +:103F700001C0BCF1020F00D092B991F800C05FEA15 +:103F8000CC7804D02CF001060E70172106E05FEA11 +:103F90008C7805D52CF002060E70192121700026B0 +:103FA0000078D0BBCAB3C3BB1C20207035E012E040 +:103FB00002E03878062841D11A2019E42078012837 +:103FC0003CD00C283AD02046FFF7F1F809208DF8B4 +:103FD0002000686A0D9031E03878052805D0062069 +:103FE000387003261820287046E005218DF820102F +:103FF000686A0D90B8680A900220ADF8240001208C +:104000008DF830000A980170297D4170394608A862 +:10401000FFF78CF8064618202870012E0ED02BE0F2 +:1040200001208DF82000686A0D9003208DF824008F +:10403000287D8DF8250085F814B012E0287D80B128 +:104040001D202070172028708DF82090686A0D9030 +:1040500002208DF82400394608A8FFF767F80646C5 +:104060000AE00CB1FE2020709DF8200020B1002154 +:1040700008A8FFF75BF810E413B03046BDE8F08FF6 +:104080002DE9F04387B00C464E6900218DF80410ED +:1040900001202578034602274FF007094FF0050C51 +:1040A00085B1012D53D0022D39D1FE2030708DF80D +:1040B0000030606A059003208DF80400207E8DF8A2 +:1040C000050063E02179012925D002292DD003299B +:1040D00028D0042923D1B17D022920D131780D1FA8 +:1040E000042D04D30A3D032D01D31D2917D12189A5 +:1040F000022914D38DF80470237020899DF80410D0 +:1041000088421BD2082001E0945B02008DF8000079 +:10411000606A059057E070780128EBD0052007B061 +:10412000BDE8F0831D203070E4E771780229F5D1F5 +:1041300031780C29F3D18DF80490DDE7083402F8CA +:1041400004CB94E80B0082E80B000320E7E7157826 +:10415000052DE4D18DF800C0656A05959568029536 +:104160008DF8101094F80480B8F1010F13D0B8F155 +:10417000020F2DD0B8F1030F1CD0B8F1040FCED12F +:10418000ADF804700E202870207E6870002168460B +:10419000FEF7CCFF0CE0ADF804700B202870207EF9 +:1041A000002100F01F0068706846FEF7BFFF3770FF +:1041B0000020B4E7ADF804708DF8103085F800C029 +:1041C000207E6870277011466846FEF7AFFFA6E7AD +:1041D000ADF804902B70207F6870607F00F00100C4 +:1041E000A870A07F00F01F00E870E27F2A71C0076E +:1041F0001CD094F8200000F00700687194F82100AA +:1042000000F00700A87100216846FEF78FFF2868BC +:10421000F062A8883086A87986F83200A0694078D4 +:1042200070752879B0700D203070C1E7A97169717F +:10423000E9E700B587B004280CD101208DF8000013 +:104240008DF80400002005918DF8050001466846B0 +:10425000FEF76CFF07B000BD70B50C46054602F0D6 +:10426000EBF821462846BDE870407823002202F092 +:1042700039B808B1007870470C20704770B50C0051 +:1042800005784FF000010CD021702146F0F7D9FFDE +:1042900069482178405D884201D1032070BD022029 +:1042A00070BDF0F7CEFF002070BD0279012A05D065 +:1042B00000220A704B78012B02D003E004207047E3 +:1042C0000A758A6102799300521C0271C150032061 +:1042D0007047F0B587B00F4605460124287905EBF5 +:1042E000800050F8046C7078411E02290AD25249AD +:1042F0003A46083901EB8000314650F8043C284624 +:10430000984704460CB1012C11D12879401E10F0B9 +:10431000FF00287101D00324E0E70A208DF8000097 +:10432000706A0590002101966846FFF7A7FF032CED +:10433000D4D007B02046F0BD70B515460A460446F5 +:1043400029461046FFF7C5FF064674B12078FE28BF +:104350000BD1207C30B100202870294604F10C00DC +:10436000FFF7B7FF2046FEF722FF304670BD7047CB +:1043700070B50E4604467C2111F0A1F90225012EEC +:1043800003D0022E04D0052070BD0120607000E033 +:1043900065702046FEF70BFFA575002070BD28B1A3 +:1043A000027C1AB10A4600F10C01C5E701207047F2 +:1043B00010B5044686B0042002F03EF82078FE28AE +:1043C00006D000208DF8000069462046FFF7E7FF81 +:1043D00006B010BD7CB50E4600218DF80410417862 +:1043E000012903D0022903D0002405E0046900E07C +:1043F00044690CB1217C89B16D4601462846FFF71E +:1044000054FF032809D1324629462046FFF794FF7E +:104410009DF80410002900D004207CBD04F10C0597 +:10442000EBE730B40C460146034A204630BC034B50 +:104430000C3AFEF758BE0000D85B0200945B020005 +:1044400070B50D46040011D085B12101284611F048 +:1044500014F910225449284611F090F852480121CD +:104460000838018044804560002070BD012070BD87 +:1044700070B54D4E00240546083E10E07068AA7BDA +:1044800000EB0410817B914208D1C17BEA7B914211 +:1044900004D10C22294611F045F830B1641C308853 +:1044A0008442EBDB4FF0FF3070BD204670BD70B52D +:1044B0000D46060006D02DB1FFF7DAFF002803DB1A +:1044C000401C14E0102070BD374C083C20886288E6 +:1044D000411C914201D9042070BD6168102201EB9A +:1044E0000010314611F04AF82088401C20802870C6 +:1044F000002070BD2C480838008870472A490839C8 +:104500000888012802D0401E08800020704770B53E +:1045100014460D0018D0BCB10021A170022802D0B1 +:10452000102811D105E0288870B10121A1701080F8 +:1045300008E02846FFF79CFF002805DB401CA07020 +:10454000A8892080002070BD012070BD70B505468F +:1045500014460E000BD000203070A878012808D037 +:1045600005D91149A1F108010A8890420AD9012010 +:1045700070BD24B1287820702888000A507002206D +:1045800008700FE064B14968102201EB0011204669 +:10459000103910F0F3FF287820732888000A607320 +:1045A00010203070002070BD8C0000202DE9F041FB +:1045B00090460C4607460025FE48072F00EB88165C +:1045C00007D2DFE807F00707070704040400012506 +:1045D00000E0FFDF06F81470002D13D0F54880309E +:1045E00000EB880191F82700202803D006EB40005B +:1045F000447001E081F8264006EB44022020507010 +:1046000081F82740BDE8F081F0B51F4614460E46FC +:104610000546202A00D1FFDFE649E648803100EB5D +:10462000871C0CEB440001EB8702202E07D00CEB1B +:10463000460140784B784870184620210AE092F8ED +:104640002530407882F82500F6E701460CEB410062 +:1046500005704078A142F8D192F82740202C03D071 +:104660000CEB4404637001E082F826300CEB41044B +:104670002023637082F82710F0BD30B50D46CE4B75 +:1046800044190022181A72EB020100D2FFDFCB4856 +:10469000854200DDFFDFC9484042854200DAFFDF86 +:1046A000C548401C844207DA002C01DB204630BD9F +:1046B000C148401C201830BDBF48C043FAE710B5C0 +:1046C00004460168407ABE4A52F82020114450B195 +:1046D0000220084420F07F40EEF7AFFA94F908106A +:1046E000BDE81040C9E70420F3E72DE9F047B14EDB +:1046F000803696F82D50DFF8BC9206EB850090F8D6 +:10470000264034E009EB85174FF0070817F814002E +:10471000012806D004282ED005282ED0062800D047 +:10472000FFDF01F00AF9014607EB4400427806EB8F +:10473000850080F8262090F82720A24202D120226E +:1047400080F82720084601F003F92A462146012077 +:10475000FFF72CFF9B48414600EB041002682046FF +:10476000904796F82D5006EB850090F82640202CB7 +:10477000C8D1BDE8F087022000E003208046D0E7E2 +:1047800010B58C4C2021803484F8251084F8261034 +:1047900084F82710002084F8280084F82D0084F87D +:1047A0002E10411EA16044F8100B20746074207319 +:1047B0006073A0738449E077207508704870002109 +:1047C0007C4A103C02F81100491CC9B22029F9D3D7 +:1047D0000120EEF722F90020EEF71FF9012084F8FE +:1047E0002200EEF765FB7948EEF777FB764CA41EC6 +:1047F00020707748EEF771FB6070BDE81040EEF76F +:1048000099B810B5EEF7BBF86F4CA41E2078EEF700 +:104810007DFB6078EEF77AFBBDE8104001F0C5B88B +:10482000202070472DE9F34F624C0025803404EBC3 +:10483000810A89B09AF82500202821D0691E0291AA +:104840006049009501EB0017391D03AB07C983E8E8 +:104850000700A18BADF81C10A07F8DF81E009DF8FD +:104860001500A046C8B10226554951F820400399C9 +:10487000A219114421F07F41019184B102210FE07E +:104880000120EEF7CAF80020EEF7C7F8EEF795F82A +:1048900001F08BF884F82F50A7E00426E4E700210C +:1048A0008DF81810022801D0012820D10398011991 +:1048B0000998081A801C9DF81C1020F07F4001B157 +:1048C0000221353181420BD203208DF81500039867 +:1048D000C4F13201401A20F07F40322403900CE0F2 +:1048E00098F8240018B901F0F8F900284DD0322CBE +:1048F00003D214B101F04DF801E001F056F8324A4C +:10490000107820B393465278039B121B00219DF828 +:104910001840994601281BD0032819D05FF00000E9 +:104920008DF81E00002A04DD981A039001208DF8EE +:1049300018009DF81C0000B102210398254A20F0C0 +:104940007F40039003AB099801F03BF810B110E0F1 +:104950000120E5E79DF81D0018B99BF80000032829 +:1049600012D08DF81C50CDF80C908DF818408DF8B1 +:104970001E509DF8180058B1039801238119002298 +:104980001846EEF79DF806E000200BB0BDE8F08F6A +:104990000120EEF742F897F90C200123002001993D +:1049A000EEF78EF8F87BC00701D0EEF772F901211F +:1049B00012E00000500A0020FF7F841E0020A107A3 +:1049C000E85B0200500800209E0000209361010077 +:1049D000EB460100FFFF3F0088F82F108AF82850AF +:1049E00020226946F74810F00EFE0120CDE72DE9A0 +:1049F000F05FDFF8D083064608EB860090F825507C +:104A0000202D1FD0A8F180002C4600EB8617A0F5C2 +:104A10000079DFF8B4B305E0A24607EB4A0044781A +:104A2000202C0AD0EEF797F809EB04135A4601211F +:104A30001B1D00F0C6FF0028EED0AC4202D033466A +:104A400052461EE0E14808B1AFF30080EEF783F86C +:104A500098F82F206AB1D8F80C20411C891A090255 +:104A6000CA1701EB12610912002902DD0020BDE81E +:104A7000F09F3146FFF7D6FE08B10120F7E7334635 +:104A80002A4620210420FFF7BFFDEFE72DE9F04182 +:104A9000CC4C2569EEF75FF8401B0002C11700EB14 +:104AA0001160001200D4FFDF94F8220000B1FFDF94 +:104AB000012784F8227094F82E00202800D1FFDF0F +:104AC00094F82E60202084F82E00002584F82F50C2 +:104AD00084F8205084F82150BD48256000780228D1 +:104AE00033D0032831D000202077A068401C05D0A7 +:104AF0004FF0FF30A0600120EDF78FFF0020EDF7B1 +:104B00008CFFEEF788F8EEF780F8EDF756FF0FF020 +:104B100085FFB048056005604FF0E0214FF400408C +:104B2000B846C1F88002EEF722F994F82D703846A5 +:104B3000FFF75DFF0028FAD0A248803800EB87100D +:104B400010F81600022802D006E00120CCE73A4611 +:104B500031460620FFF72AFD84F8238004EB870006 +:104B600090F82600202804D09948801E4078EEF75F +:104B7000D3F9207F002803D0EEF73DF8257765773D +:104B800040E5904910B591F82D200024803901EBC3 +:104B9000821100BF11F814302BB1641CE4B2202C38 +:104BA000F8D3202010BD8C4901EB041108600020CF +:104BB000C87321460120FFF7F9FC204610BD10B54F +:104BC000012801D0032800D171B37E4A92F82D301C +:104BD0007C4C0022803C04EB831300BF13F812408E +:104BE0000CB1082010BD521CD2B2202AF6D3784A4C +:104BF00048B1022807D0072916D2DFE801F01506D0 +:104C0000080A0C0E100000210AE01B2108E03A21DE +:104C100006E0582104E0772102E0962100E0B5216A +:104C200051701070002010BD072010BD684810B5ED +:104C30004078EEF702F880B210BD10B5202811D2EE +:104C4000604991F82D30A1F1800202EB831414F831 +:104C500010303BB191F82D3002EB831212F8102086 +:104C6000012A01D0002010BD91F82D20014600201E +:104C7000FFF79CFC012010BD10B5EDF76CFFBDE8FF +:104C80001040EDF7DABF2DE9F0410E464D4F0178A7 +:104C90002025803F0C4607EB831303E0254603EBFA +:104CA00045046478944202D0202CF7D108E0202CEF +:104CB00006D0A14206D103EB41014978017007E01B +:104CC00000209FE403EB440003EB4501407848706B +:104CD000424F7EB127B1002140F2DD30AFF30080BA +:104CE0003078A04206D127B100214FF47870AFF39D +:104CF0000080357027B1002140F2E530AFF300802D +:104D000001207FE410B542680B689A1A1202D4178A +:104D100002EB1462121216D4497A91B1427A82B926 +:104D20002F4A006852F82110126819441044001DDF +:104D3000891C081A0002C11700EB1160001232280A +:104D400001DB012010BD002010BD2DE9F047814698 +:104D50001C48214E00EB8100984690F82540202009 +:104D6000107006F50070154600EB81170BE000BFD0 +:104D700006EB04104946001DFFF7C4FF28B107EBFE +:104D800044002C704478202CF2D1297888F8001047 +:104D900013E000BF06EB0415291D4846FFF7B2FFDC +:104DA00068B988F80040A97B99F80A00814201D8C7 +:104DB0000020DEE407EB44004478202CEAD10120F7 +:104DC000D7E40000D00A0020FFFF3F0000000000F1 +:104DD0009E00002000F50040500800200000000068 +:104DE000E85B02002DE9FC410E4607460024FE4D1B +:104DF00009E000BF9DF8000005EB0010816838460F +:104E000000F0F3FD01246B4601AA31463846FFF756 +:104E10009CFF0028EED02046BDE8FC8170B504461A +:104E2000F2480125A54300EB841100EB85104022D8 +:104E300010F0A4FBEE4E26B1002140F25F40AFF32C +:104E40000080EA48803000EB850100EB8400D0F858 +:104E50002500C1F8250026B1002140F26340AFF3E0 +:104E60000080284670BD2DE9FC418446DF48154688 +:104E7000089C00EB85170E4617F81400012803D094 +:104E8000022801D00020C7E70B46DA4A012160461C +:104E900000F097FDA8B101AB6A4629463046FFF7FE +:104EA00054FF70B1D1489DF804209DF80010803067 +:104EB00000EB85068A4208D02B460520FFF7A4FBAD +:104EC0000BE02A462146042014E0202903D007EBFA +:104ED0004100407801E096F8250007EB4401487056 +:104EE0009DF80000202809D007EB400044702A46B6 +:104EF00021460320FFF75AFB01208DE706F8254FD6 +:104F00000120F070F3E7B84901EB0010001DFFF736 +:104F1000D6BB7CB51D46134604460E4600F108027A +:104F200021461846EDF796FE94F908000F2804DD97 +:104F30001F3820722068401C206096B10220AE49C4 +:104F400051F82610461820686946801B20F07F40E3 +:104F5000206094F908002844C01C1F2803DA0120AF +:104F600009E00420EBE701AAEDF774FE9DF80400C8 +:104F700010B10098401C009000992068314408440A +:104F8000C01C20F07F4060607CBD2DE9FE430C46D4 +:104F900006460978607990722079984615465072D5 +:104FA00041B19248803090F82E1020290AD0006933 +:104FB000401D0BE0D4E90223217903B02846BDE867 +:104FC000F043A6E78D484178701D084420F07F47E4 +:104FD000217900222846A368FFF79BFF394628461F +:104FE00000F003FDD4E9023221796846FFF791FF12 +:104FF00041462846019CFFF7F5FE2B46224600213C +:10500000304600F0DEFC002803D13146284600F08F +:10501000ECFCBDE8FE832DE9FE4F814600F0A1FCCB +:1050200030B1002799F8000020B10020BDE8FE8FC4 +:105030000127F7E76D4D6E4C4FF0000A803524B123 +:10504000002140F2D640AFF3008095F82D8085F81E +:1050500023A0002624B1002140F2DB40AFF3008002 +:105060001FB94046FFF7DAFE804624B1002140F226 +:10507000E340AFF30080EDF76EFD43466A464946D4 +:10508000FFF783FF24B1002140F2E940AFF3008035 +:1050900095F82E0020280CD029690098401A0002AB +:1050A000C21700EB1260001203D5684600F09DFCA9 +:1050B000012624B1002140F2F340AFF3008095F8BF +:1050C00023000028BBD124B1002140F2F940AFF306 +:1050D0000080EDF740FD6B46464A002100F071FC70 +:1050E0000028A3D027B941466846FFF77BFE064358 +:1050F00026B16846FFF7E3FAC9F8080024B1002199 +:1051000040F20C50AFF3008001208FE72DE9F04F03 +:1051100089B08B46824600F024FC344C803428B39E +:105120009BF80000002710B1012800D0FFDF304DB0 +:1051300025B1002140F28250AFF300802A490120BE +:1051400001EB0A18A94607905FEA090604D000217E +:1051500040F28A50AFF30080079800F0F9FB94F812 +:105160002D50002084F8230067B119E094F82E0038 +:105170000127202800D1FFDF9BF800000028D6D0AF +:10518000FFDFD4E72846FFF749FE054626B1002198 +:1051900040F29450AFF3008094F823000028D3D15C +:1051A00026B1002140F29E50AFF30080EDF7D3FC12 +:1051B0002B4602AA59460790FFF7E7FE98F80F0022 +:1051C0005FEA060900F001008DF8130004D0002109 +:1051D0004FF4B560AFF300803B462A4602A9CDF8F4 +:1051E00000A007980CE0000050080020500A0020A2 +:1051F00000000000FFFF3F00E85B02009E0000206F +:10520000FFF731FE064604EB850090F82800009079 +:10521000B9F1000F04D0002140F2AF50AFF300808D +:1052200000F08BFB0790B9F1000F04D0002140F291 +:10523000B550AFF3008094F82300002884D1B9F171 +:10524000000F04D0002140F2BD50AFF300800DF1FB +:10525000080C9CE80E00C8E90112C8F80C304EB3E7 +:105260005FEA090604D0002140F2CA50AFF3008083 +:105270000098B84312D094F82E0020280ED126B101 +:10528000002140F2CF50AFF300802846FFF7AFFB7C +:1052900020B99BF80000D8B3012849D0B9F1000F1C +:1052A00004D0002140F2EC50AFF30080284600F01B +:1052B0003DFB01265FEA090504D0002140F2F550CC +:1052C000AFF30080079800F043FB25B1002140F2C6 +:1052D000F950AFF300808EB194F82D0004EB8000FC +:1052E00090F82600202809D025B100214FF4C06095 +:1052F000AFF30080F9484078EDF70EFE25B10021AC +:1053000040F20560AFF3008009B03046BDE8F08F91 +:10531000FFE7B9F1000F04D0002140F2D750AFF3FE +:10532000008094F82D2051460420FFF73FF9C0E794 +:10533000002E3FF409AF002140F2E250AFF30080AD +:1053400002E72DE9F84FE64D814695F82D004FF024 +:105350000008E44C4FF0010B474624B1002140F215 +:105360001360AFF30080584600F0F2FA85F823701E +:1053700024B100214FF4C360AFF3008095F82D00F5 +:10538000FFF74CFD064695F8230028B1002CE4D029 +:10539000002140F21E604BE024B1002140F2226067 +:1053A000AFF30080CE48803800EB861111F8190069 +:1053B000032856D1334605EB830A4A469AF825005E +:1053C000904201D1012000E0002000900AF1250068 +:1053D0000021FFF758FC01460098014203D001224A +:1053E0008AF82820AF77E1B324B1002140F227608A +:1053F000AFF30080324649460120FFF7D7F89AF80C +:1054000028A024B1002140F23260AFF3008000F008 +:1054100094FA834624B1002140F23760AFF3008054 +:1054200095F8230038B1002C97D0002140F23B6062 +:10543000AFF3008091E7BAF1000F07D095F82E0086 +:10544000202803D13046FFF7D2FAE0B124B1002181 +:1054500040F24F60AFF30080304600F067FA4FF043 +:10546000010824B100214FF4CB60AFF3008058460F +:1054700000F06EFA24B1002140F25C60AFF30080CE +:105480004046BDE8F88F002CF1D0002140F24A6080 +:10549000AFF30080E6E70020EDF798BA0120EDF7C2 +:1054A00095BA8E48007870472DE9F0418C4C94F8FD +:1054B0002E0020281FD194F82D6004EB860797F862 +:1054C0002550202D00D1FFDF8549803901EB861062 +:1054D00000EB4500407807F8250F0120F87084F8AC +:1054E0002300294684F82E50324602202234FFF74A +:1054F0005DF80020207004E42DE9F0417A4E784CEC +:10550000012538B1012821D0022879D003287DD087 +:10551000FFDFF0E700F03DFAFFF7C6FF207E00B1A5 +:10552000FFDF84F821500020EDF777FAA168481CCE +:1055300004D0012300221846EDF7C2FA14F82E0F0A +:10554000217806EB01110A68012154E0FFF7ACFF56 +:105550000120EDF762FA94F8210050B1A068401CD8 +:1055600007D014F82E0F217806EB01110A680621E6 +:1055700041E0207EDFF86481002708F1020801285D +:1055800003D002281ED0FFDFB5E7A777EDF733FB86 +:1055900098F80000032801D165772577607D53498D +:1055A00051F8200094F8201051B948B161680123E6 +:1055B000091A00221846EDF783FA022020769AE7AE +:1055C000277698E784F8205000F0E3F9A07F50B1E7 +:1055D00098F8010061680123091A00221846EDF7C6 +:1055E0006FFA257600E0277614F82E0F217806EB67 +:1055F00001110A680021BDE8F041104700E005E014 +:1056000036480078BDE8F041EDF786BCFFF74CFF67 +:1056100014F82E0F217806EB01110A680521EAE73C +:1056200010B52F4C94F82E00202800D1FFDF14F87D +:105630002E0F21782C4A02EB01110A68BDE81040B8 +:10564000042110477CB5264C054694F82E002028EE +:1056500000D1FFDFA068401C00D0FFDF94F82E00CF +:10566000214901AA01EB0010694690F90C00284479 +:10567000EDF7F0FA9DF904000F2801DD012000E0AC +:105680000020009908446168084420F07F41A1602F +:1056900094F82100002807D002B00123BDE8704033 +:1056A00000221846EDF70CBA7CBD30B5104A0B1A33 +:1056B000541CB3EB940F1FD3451AB5EB940F1BD3B7 +:1056C000934203D9101A43185B1C15E0954211D977 +:1056D000511A0844401C43420EE000009C00002088 +:1056E000D00A00200000000050080020E85B020003 +:1056F000FF7F841EFFDF0023184630BD01230022F8 +:1057000001460220EDF7DCB90220EDF786B9EDF78E +:1057100022BA2DE9FC47BA4C054694F82E00202801 +:1057200000D1FFDF642D58D3B64A0021521B71EB24 +:10573000010052D394F82E20A0462046DFF8C892EC +:1057400090F82D7009EB0214D8F8000001AA284443 +:105750006946EDF77FFA9DF90400002802DD009804 +:10576000401C0090A068009962684618B21A22F0A6 +:105770007F42B2F5800F30D208EB8702444692F8A0 +:105780002520202A0AD009EB02125268101A0002C2 +:10579000C21700EB1260001288421EDBA068401C9A +:1057A00010D0EDF7D8F9A168081A0002C11700EB74 +:1057B00011600012022810DD0120EDF72EF94FF0E4 +:1057C000FF30A06020682844206026F07F402061E0 +:1057D000012084F82300BDE8FC870020FBE72DE9C9 +:1057E000F047874C074694F82D00A4F1800606EB9D +:1057F000801010F8170000B9FFDF94F82D50A04674 +:10580000824C24B100214FF40760AFF3008040F6D2 +:105810007C0940F6850A06EB851600BF16F81700CE +:10582000012818D0042810D005280ED006280CD046 +:105830001CB100214846AFF3008020BF002CEDD002 +:1058400000215046AFF30080E8E72A4639460120A0 +:10585000FEF7ACFEF2E74FF0010A4FF000094546B3 +:1058600024B1002140F68C00AFF30080504600F0D8 +:105870006FF885F8239024B1002140F69100AFF332 +:10588000008095F82D00FFF7C9FA064695F8230029 +:1058900028B1002CE4D0002140F697001FE024B18D +:1058A000002140F69B00AFF3008005EB860000F17D +:1058B000270133463A462630FFF7E5F924B10021A7 +:1058C00040F69F00AFF3008000F037F8824695F86D +:1058D000230038B1002CC3D0002140F6A500AFF35F +:1058E0000080BDE785F82D60012085F82300504633 +:1058F00000F02EF8002C04D0002140F6B200AFF3E7 +:105900000080BDE8F08730B504463D480D4690F86C +:105910002D003B49803901EB801010F8140000B9CC +:10592000FFDF394800EB0410C57330BD344981F8FE +:105930002D00012081F82300704710B5344808B1CC +:10594000AFF30080EFF3108000F0010072B610BDDD +:1059500010B5002804D12F4808B1AFF3008062B61B +:1059600010BD2D480068C005C00D10D0103840B2E1 +:10597000002804DB00F1E02090F8000405E000F0CE +:105980000F0000F1E02090F8140D40097047082046 +:10599000704710B51A4C94F82400002804D1F6F78B +:1059A0005FF8012084F8240010BD10B5144C94F861 +:1059B0002400002804D0F6F77CF8002084F82400A6 +:1059C00010BD10B51C685B68241A181A24F07F44B7 +:1059D00020F07F40A14206D8B4F5800F03D2904258 +:1059E00001D8012010BD002010BDD0E90032D21A2C +:1059F00021F07F43114421F07F41C0E9003170471D +:105A0000D00A0020FF1FA10750080020000000005E +:105A1000000000000000000004ED00E02DE9F0416E +:105A2000044680074FF000054FF001060CD56B4887 +:105A3000056006600EF01BFE20B16948016841F464 +:105A40008061016024F00204E0044FF0FF3705D5C7 +:105A500064484660C0F8087324F48054600003D59D +:105A60006148056024F08044E0050FD55F48C0F828 +:105A70000052C0F808735E490D60091D0D605C4A54 +:105A800004210C321160066124F48074A00409D54D +:105A900058484660C0F80052C0F808735648056080 +:105AA00024F40054C4F38030C4F3C031884200D0E1 +:105AB000FFDF14F4404F14D050484660C0F808731C +:105AC0004F488660C0F80052C0F808734D490D6019 +:105AD0000A1D16608660C0F808730D60166024F415 +:105AE000404420050AD5484846608660C0F80873DF +:105AF000C0F848734548056024F400640EF068FF60 +:105B00004348044200D0FFDFBDE8F081F0B5002239 +:105B1000202501234FEA020420FA02F1C9072DD003 +:105B200051B2002910DB00BF4FEA51174FEA870737 +:105B300001F01F0607F1E02703FA06F6C7F88061B7 +:105B4000BFF34F8FBFF36F8F0CDB00BF4FEA5117CE +:105B50004FEA870701F01F0607F1E02703FA06F670 +:105B6000C7F8806204DB01F1E02181F8004405E020 +:105B700001F00F0101F1E02181F8144D02F1010261 +:105B8000AA42C9D3F0BD10B5224C20600846F6F7F2 +:105B90007CF82068FFF742FF2068FFF7B7FF0EF0A0 +:105BA000FDFA00F01AF90EF013FF0EF056FEEDF7B5 +:105BB0007FF9BDE810400EF0A1BB10B5154C206870 +:105BC000FFF72CFF2068FFF7A1FF0EF001FFF6F7AB +:105BD0004FF90020206010BD0A207047FC1F0040D4 +:105BE0003C17004000C0004004E501400080004038 +:105BF0000485004000D0004004D5004000E0004093 +:105C000000F0004000F5004000B0004008B5004042 +:105C1000FEFF0FFDA000002070B526490A680AB3F8 +:105C20000022154601244B685B1C4B600C2B00D3F3 +:105C30004D600E7904FA06F30E681E420FD0EFF3A2 +:105C4000108212F0010272B600D001220C689C434F +:105C50000C6002B962B649680160002070BD521C38 +:105C60000C2AE0D3052070BD4FF0E0214FF48000F6 +:105C7000C1F800027047EFF3108111F0010F72B606 +:105C80004FF0010202FA00F20A48036842EA0302F6 +:105C9000026000D162B6E7E706480021016041607A +:105CA00070470121814003480068084000D001206E +:105CB00070470000A40000200120810708607047A1 +:105CC0000121880741600021C0F8001118480170C7 +:105CD000704717490120087070474FF08040D0F896 +:105CE0000001012803D012480078002800D00120CC +:105CF000704710480068C00700D0012070470D4869 +:105D00000C300068C00700D00120704709481430EB +:105D100000687047074910310A68D20306D5096840 +:105D200001F00301814201D101207047002070473A +:105D3000AC000020080400400021017008467047B4 +:105D40000146002008707047EFF3108101F0010157 +:105D500072B60278012A01D0012200E0002201235C +:105D6000037001B962B60AB1002070474FF40050C9 +:105D70007047E9E7EFF3108111F0010F72B64FF0B1 +:105D80000002027000D162B600207047F2E7000006 +:105D90002DE9F04115460E460446002700F0E7F8CD +:105DA000A84215D3002341200FE000BF94F8422001 +:105DB000A25CF25494F84210491CB1FBF0F200FBD3 +:105DC00012115B1C84F84210DBB2AB42EED3012708 +:105DD00000F0D9F83846BDE8F081704910B5802050 +:105DE00081F800046E49002081F8420081F84100EA +:105DF000433181F8420081F84100433181F842008B +:105E000081F841006748FFF797FF6648401CFFF79D +:105E100093FFECF7BBFFBDE8104000F0B4B84020A2 +:105E200070475F4800F0A3B80A4601465C48AFE7F8 +:105E3000402070475A48433000F099B80A4601465E +:105E400057484330A4E7402101700020704710B547 +:105E500004465348863000F08AF82070002010BDB8 +:105E60000A4601464E4810B58630FFF791FF08B14B +:105E7000002010BD42F2070010BD70B50C4605466B +:105E8000412900D9FFDF48480068103840B200F0CF +:105E900050F8C6B20D2000F04CF8C0B2864203D2D2 +:105EA000FFDF01E0ECF7C2FF224629463C48FFF73E +:105EB0006FFF0028F6D070BD2DE9F041394F002565 +:105EC00006463F1D57F82540204600F041F810B324 +:105ED0006D1CEDB2032DF5D33148433000F038F896 +:105EE000002825D02E4800F033F8002820D02C4878 +:105EF000863000F02DF800281AD0ECF76DFF294805 +:105F0000FFF722FFB0F5005F00D0FFDFBDE8F041F2 +:105F10002448FFF72FBF94F841004121265414F87C +:105F2000410F401CB0FBF1F201FB12002070D3E7DF +:105F300051E7002804DB00F1E02090F8000405E0C0 +:105F400000F00F0000F1E02090F8140D40097047B8 +:105F500010F8411F4122491CB1FBF2F302FB13115F +:105F60004078814201D1012070470020704710F82D +:105F7000411F4078814201D3081A02E0C0F141007C +:105F80000844C0B2704710B50648FFF7DDFE002890 +:105F900003D1BDE81040ECF70ABF10BD0DE000E0F2 +:105FA000000B0020B000002004ED00E070B5154D9E +:105FB0002878401CC4B26878844202D0F5F7EFFF1D +:105FC0002C7070BD2DE9F0410E4C4FF0E02600BF63 +:105FD000F5F7DAFF20BF40BF20BF677820786070F8 +:105FE000D6F80052EBF70CFA854305D1D6F8040237 +:105FF00010B92078B842EBD0F5F7C1FF0020BDE81A +:10600000F0810000C00000202DE9F04101252803A7 +:106010004FF0E0210026C1F88001BFF34F8FBFF39E +:106020006F8F1F4CC4F800610C2000F02CF81D4845 +:1060300001680268C94341F3001142F01002026096 +:10604000C4F804532560491C00E020BFD4F80021A7 +:10605000002AFAD019B9016821F010010160124834 +:1060600007686560C4F80853C4F800610C2000F0AC +:106070000AF83846BDE8F08110B50446FFF7C4FFC2 +:106080002060002010BD002809DB00F01F02012164 +:1060900091404009800000F1E020C0F88012704774 +:1060A00000C0004010ED00E008C500402DE9F047B9 +:1060B000FF4C0646FF21A06800EB06121170217804 +:1060C000FF2910D04FF0080909EB011109EB061761 +:1060D0004158C05900F0F4F9002807DDA168207884 +:1060E00001EB061108702670BDE8F08794F8008077 +:1060F00045460DE0A06809EB05114158C05900F074 +:10610000DFF9002806DCA068A84600EB0810057837 +:10611000FF2DEFD1A06800EB061100EB08100D7009 +:106120000670E1E7F0B5E24B0446002001259A68CD +:106130000C269B780CE000BF05EB0017D75DA7424B +:1061400004D106EB0017D7598F4204D0401CC0B2CF +:106150008342F1D8FF20F0BD70B5FFF7D8FAD44CD8 +:1061600008252278A16805EB0212895800F0A8F9E9 +:10617000012808DD2178A06805EB01114058BDE831 +:106180007040FFF7BBBAFFF78CF9BDE87040ECF741 +:10619000C3BE2DE9F041C64C2578FFF7B8FAFF2DB4 +:1061A0006ED04FF00808A26808EB0516915900F070 +:1061B00087F90228A06801DD80595DE000EB051138 +:1061C00009782170022101EB0511425C5AB1521E7F +:1061D0004254815901F5800121F07F4181512846C7 +:1061E000FFF764FF34E00423012203EB051302EB05 +:1061F000051250F803C0875CBCF1000F10D0BCF54D +:10620000007F10D9CCF3080250F806C00CEB423CDA +:106210002CF07F4C40F806C0C3589A1A520A09E085 +:10622000FF2181540AE0825902EB4C3222F07F4276 +:106230008251002242542846FFF738FF0C21A06803 +:1062400001EB05114158E06850F827203846904787 +:106250002078FF2814D0FFF75AFA2278A16808EBBB +:1062600002124546895800F02BF9012893DD217868 +:10627000A06805EB01114058BDE8F041FFF73EBAB8 +:10628000BDE8F081F0B51D4614460E460746FF2BCB +:1062900000D3FFDFA00700D0FFDF8548FF210022E9 +:1062A000C0E90247C57006710170427082701046E5 +:1062B000012204E002EB0013401CE154C0B2A842EA +:1062C000F8D3F0BD70B57A4C0646657820798542E2 +:1062D00000D3FFDFE06840F825606078401C607004 +:1062E000284670BD2DE9FF5F1D468B460746FF24FB +:1062F000FFF70DFADFF8B891064699F80100B842A9 +:1063000000D8FFDF00214FF001084FF00C0A99F888 +:106310000220D9F808000EE008EB0113C35CFF2B44 +:106320000ED0BB4205D10AEB011350F803C0DC4587 +:106330000CD0491CC9B28A42EED8FF2C02D00DE025 +:106340000C46F6E799F803108A4203D1FF2004B007 +:10635000BDE8F09F1446521C89F8022008EB041196 +:106360000AEB0412475440F802B00421029B0022B9 +:10637000012B01EB04110CD040F801204FF4007800 +:1063800008234FF0020C454513D9E905C90D02D089 +:1063900002E04550F2E7414606EB413203EB0413BD +:1063A00022F07F42C250691A0CEB0412490A815450 +:1063B0000BE005B9012506EB453103EB041321F091 +:1063C0007F41C1500CEB0411425499F80050204613 +:1063D000FFF76CFE99F80000A84201D0FFF7BCFE61 +:1063E0003846B4E770B50C460546FFF790F9064607 +:1063F00021462846FFF796FE0446FF281AD02C4D6A +:10640000082101EB0411A8684158304600F058F803 +:1064100000F58050C11700EBD14040130221AA685B +:1064200001EB0411515C09B100EB4120002800DCB4 +:10643000012070BD002070BD2DE9F04788468146DF +:10644000FFF770FE0746FF281BD0194D2E78A8686D +:106450003146344605E0BC4206D0264600EB061223 +:106460001478FF2CF7D10CE0FF2C0AD0A6420CD1F7 +:1064700000EB011000782870FF2804D0FFF76CFEB5 +:1064800003E0002030E6FFF73FF941464846FFF7BA +:10649000A9FF0123A968024603EB0413FF20C85497 +:1064A000A878401EB84200D1A87001EB041001E0AA +:1064B000CC0B002001EB061100780870104613E6A3 +:1064C000081A0002C11700EB1160001270470000AB +:1064D0005E4800210170417010218170704770B5D5 +:1064E000054616460C460220ECF7F2F95749012002 +:1064F00008705749F01E086056480560001F046088 +:1065000070BD10B50220ECF7E3F950490120087086 +:1065100051480021C0F80011C0F80411C0F808115A +:106520004E494FF40000086010BD48480178D9B1C9 +:106530004B4A4FF4000111604749D1F80031002265 +:10654000002B1CBFD1F80431002B02D0D1F8081168 +:1065500019B142704FF0100104E04FF00101417099 +:1065600040490968817002704FF00000ECF7B0B943 +:1065700010B50220ECF7ACF934480122002102707A +:106580003548C0F80011C0F80411C0F808110260C5 +:1065900010BD2E480178002904BF407870472E486E +:1065A000D0F80011002904BF02207047D0F8001174 +:1065B00000291CBFD0F80411002905D0D0F808012B +:1065C000002804BF01207047002070471F4800B515 +:1065D0000278214B4078C821491EC9B282B1D3F854 +:1065E00000C1BCF1000F10D0D3F8000100281CBF7F +:1065F000D3F8040100280BD0D3F8080150B107E00C +:10660000022802D0012805D002E00029E4D1FFDFF2 +:10661000002000BD012000BD0C480178002904BF06 +:10662000807870470C48D0F8001100291CBFD0F8C2 +:106630000411002902D0D0F8080110B14FF0100069 +:10664000704708480068C0B270470000C2000020D0 +:1066500010F5004008F5004000F0004004F501404E +:1066600008F5014000F400405648002101704170D7 +:10667000704770B5064614460D460120ECF728F920 +:1066800051480660001D0460001D05604F49002050 +:10669000C1F850014E49032008604F494D48086039 +:1066A000091D4E48086070BD2DE9F041054645487A +:1066B0000C46012606704A4945EA024040F08070C7 +:1066C00008600DF0AAFF002804BF464804600027B8 +:1066D000454CC4F80471464944480860002D02BF87 +:1066E000C4F800622660BDE8F081012D18BFFFDF0D +:1066F000C4F80072266040493E480860BDE8F08159 +:106700003048017871B13A4A384911603649D1F8B8 +:1067100004210021002A08BF417002D0374A1268C4 +:10672000427001700020ECF7D3B8264801780029A8 +:1067300004BF407870472C48D0F80401002808BFF7 +:1067400070472E480068C0B27047002808BF7047E5 +:1067500030B51C480078002808BFFFDF2248D0F879 +:106760000411002918BF30BD0224C0F80443DFF82B +:1067700090C0DCF80010C1F30015DCF8001041F007 +:106780001001CCF80010D0F80411002904BF4FF418 +:1067900000414FF0E02206D1C2F8801220BFD0F8AD +:1067A0000431002BF8D02DB9DCF8001021F01001D5 +:1067B000CCF80010C0F8084330BD0B4901208860B8 +:1067C00070470000C500002008F5004000100040A0 +:1067D0001CF500405011004098F501400CF00040BD +:1067E00004F5004018F5004000F0004000000203EE +:1067F00008F501400000020204F5014000F40040E9 +:1068000010ED00E010B5FE48002401214470047032 +:1068100044728472C17280F82540C462846380F837 +:106820003C4080F83D40FF2180F83E105F2180F819 +:106830003F1018300FF052F8F249601E0860091D31 +:106840000860091D0C60091D0860091D0C60091D08 +:106850000860091D0860091D0860091D0860091D00 +:106860000860091D0860091D0860091D0860091DF0 +:10687000086010BDE448016801F00F01032904BF5E +:1068800001207047016801F00F01042904BF0220B4 +:106890007047016801F00F01052904D0006800F07D +:1068A0000F00062807D1D948006810F0060F0CBF6A +:1068B00008200420704700B5FFDF012000BD10B59F +:1068C000CF4C0168A1614168E161007A84F8200041 +:1068D000207E48B1207FF7F7C4FCA07E011C18BFC2 +:1068E0000121207FF7F7ACFC607E002808BF10BDB7 +:1068F000607FF7F7B6FCE07E011C18BF0121607FC6 +:10690000BDE81040F7F79CBC30B5002405460129CE +:106910000AD0022908BF4FF0807405D0042916BFA1 +:1069200008294FF0C744FFDF44F4847040F480101E +:10693000B749086045F4403001F1040140F00070AF +:10694000086030BD30B50024054601290AD002296F +:1069500008BF4FF0807405D0042916BF08294FF0F6 +:10696000C744FFDF44F4847040F48010A8490860F5 +:1069700045F4403001F1040140F000700860A54882 +:10698000D0F80001002818BFFFDF30BD2DE9F0412D +:1069900002274FF0E02801250024C8F88071BFF3DA +:1069A0004F8FBFF36F8F9C48046005600DF05FFE52 +:1069B0009A4E18B1306840F4806030600DF02DFEC2 +:1069C00038B1306820F0770040F0880040F0004097 +:1069D00030609449924808604FF01020806CB0F10C +:1069E000FF3F04D090490A6860F317420A608F495C +:1069F00040F25B600860091F40F203100860081F46 +:106A00000560814903200860894805608A4A8949F0 +:106A100011608B4A89491160121F8A49116001680F +:106A200021F440710160016841F480710160C8F88F +:106A3000807278491020C1F80403714880F8314011 +:106A4000C462BDE8F0816E4A0368C2F802308088F3 +:106A5000D080117270476A4B10B51A7A8A4208D1F9 +:106A600001460622981C0EF05DFD002804BF01209F +:106A700010BD002010BD624890F825007047604AA4 +:106A8000517010707047F0B50546800000F18040ED +:106A900000F580508B88C0F820360B78D1F80110B3 +:106AA00043EA0121C0F8001605F10800012707FAA2 +:106AB00000F6654C002A04BF2068B04304D0012AC8 +:106AC00018BFFFDF206830432060206807FA05F117 +:106AD00008432060F0BD0EF0D1B8494890F832006C +:106AE00070475A4AC178116000685949000208602D +:106AF0007047252808BF02210ED0262808BF1A217A +:106B00000AD0272808BF502106D00A2894BF0422A3 +:106B1000062202EB4001C9B24E4A11604E4908609C +:106B2000704737498A7A012A49D0022A18BF70472C +:106B30004B7E002B08BF7047012A44D0CB7E4A7F92 +:106B400013F1000C18BF4FF0010C24231844434BE1 +:106B50001860434B0020C3F84C0110028CF0010276 +:106B600040EA025040F0031291F82000830003F144 +:106B7000804303F5C043C3F810253A4A8B7F02EBEC +:106B80008000DA0002F1804202F5F832C2F8140502 +:106B9000DFF8D4C0C2F810C5C97FCA0002F1804234 +:106BA00002F5F832C2F814052648C2F81005012093 +:106BB00000FA03F288402D491043086070470B7EAD +:106BC000002BB9D170478B7E0A7F002B14BF4FF08A +:106BD000010C4FF0000C1123B8E72DE9F0410D4EE8 +:106BE000804603200D46C6F8000220492048086070 +:106BF00028460EF082F80124014FB8F1000F39E069 +:106C0000DC0B0020000E0040101500401414004062 +:106C10001415004000100040FC1F00403C170040CD +:106C20002C000089781700408C1500403815004072 +:106C30005016004000000E0408F50140408000405E +:106C4000A4F50140101100404016004024150040FA +:106C50001C15004008150040541500404C850040AC +:106C600000800040006000404C81004004F501407D +:106C70000000040404BFBC72346026D0B8F1010FD8 +:106C800023D1FE48006860B915F00C0F09D0C6F892 +:106C90000443012000F0B4FEF463346487F83C4000 +:106CA00002E0002000F0ACFE28460EF00EF90220B3 +:106CB000B8720DF0CAFC38B90DF0D9FC20B9F04813 +:106CC000016841F4C02101607460EE48C464EE487C +:106CD00000682946BDE8F04123E72DE9F047EB4E77 +:106CE000814603200D46C6F80002DFF8A883E84875 +:106CF000C8F8000008460EF000F828460EF0E5F847 +:106D00000124E54FB9F1000F03D0B9F1010F0AD00A +:106D100026E0BC72B86B40F48010B8634FF480106A +:106D2000C8F800001CE00220B872B86B40F40010F4 +:106D3000B8634FF40010C8F80000D048006860B98C +:106D400015F00C0F09D0C6F80443012000F058FEDE +:106D5000F463346487F83C4002E0002000F050FE09 +:106D6000EBF794FF2946BDE8F047DAE62DE9F84F46 +:106D7000C64C8246032088461746C4F80002DFF856 +:106D80001493C348C9F8000010460DF0B6FFDFF8B1 +:106D90000CB3C14E0125BAF1000F04BFCBF800407F +:106DA000B57204D0BAF1010F18BFFFDF2FD0BC4875 +:106DB000C0F80080BC49BB480860B06B40F40020BC +:106DC000B063D4F800321021C4F808130020C4F8CE +:106DD0000002DFF8D8C28A03CCF80020C4F8000112 +:106DE000C4F80C01C4F81001C4F80401C4F814017B +:106DF000C4F81801AE4800680090C4F80032C9F821 +:106E00000020C4F80413BAF1010F09D01BE0384682 +:106E10000EF05BF8A748CBF800000220B072C6E77E +:106E20009648006860B917F00C0F09D0C4F80453F5 +:106E3000012000F0E5FDE563256486F83C5002E0A2 +:106E4000002000F0DDFD4FF40020C9F800008D485F +:106E5000C5648D480068404528BFFFDF394640467D +:106E6000BDE8F84F5DE62DE9F0418B4C0646002564 +:106E700094F8310017468846002808BFFFDF16B196 +:106E8000012E16D021E094F83100012808D094F8A2 +:106E90003020394640460DF045FFE16A451814E0C0 +:106EA00094F830103A4640460DF07AFFE16A4518F2 +:106EB0000BE094F8310094F8301001283A4640462F +:106EC00009D00DF095FFE16A45183A46294630464B +:106ED000BDE8F0414AE70DF045FFE16A4518F4E7E7 +:106EE0002DE9F84F694CD4F8000220F00B09D4F8D2 +:106EF00004034FF0100AC0F30018C4F808A30026DA +:106F0000C4F8006269486C490160634D0127A97AA1 +:106F1000012902D0022903D015E0297E11B912E01F +:106F2000697E81B1A97FEA7F07FA01F107FA02F2CF +:106F30001143016095F82000800000F1804000F5C9 +:106F4000C040C0F81065FF208DF80000C4F8106143 +:106F5000276104E09DF80000401E8DF800009DF8B8 +:106F6000000018B1D4F810010028F3D09DF80000FB +:106F7000002808BFFFDFC4F81061002000F040FDCA +:106F80006E72AE72EF72C4F80092B8F1000F18BFC3 +:106F9000C4F804A3BDE8F88FFF2008B58DF8000001 +:106FA0003A480021C0F810110121016105E000BF3D +:106FB0009DF80010491E8DF800109DF8001019B1C1 +:106FC000D0F810110029F3D09DF80000002808BF68 +:106FD000FFDF08BD0068394920F07F400860704736 +:106FE0004FF0E0200221C0F8801100F5C070BFF31F +:106FF0004F8FBFF36F8FC0F8001170474FF0E02143 +:107000000220C1F8000170472D49087070472D49D2 +:107010000860704770B50546EBF738FE1E4C2844F3 +:10702000E16A884298BFFFDF01202074EBF72EFE53 +:10703000144A284400216061C2F8441122490860C2 +:10704000A06B144940F48000A063D001086070BDBB +:1070500070B5114C05461D4A0220207410680E467A +:1070600000F00F00032808BF01223ED0106800F096 +:107070000F00042808BF022237D029E088170040FB +:1070800068150040008000404C8500400010004022 +:107090000000040404F50140DC0B0020ACF50140C5 +:1070A0004885004048810040A8F5014008F50140AE +:1070B000181100400410004000000E043C15004070 +:1070C000C700002004150040448500401015004012 +:1070D000106800F00F0005281BD0106800F00F00AA +:1070E00006281CBFFFDF012213D094F8310094F86A +:1070F0003010012815D028460DF0C1FEFF4960610F +:107100000020C1F844016169E06A0844FC49086054 +:1071100070BDFC48006810F0060F0CBF0822042266 +:10712000E3E7334628460DF078FEE7E7F6494FF4EB +:1071300080000860F548816B21F4800181630021A3 +:1071400001747047C20002F1804202F5F832F04B40 +:10715000C2F81035C2F8141501218140ED480160D4 +:10716000EA48826B114381637047E4480121416022 +:10717000C1600021C0F84411E1480160E348C162E8 +:107180007047E5490860E548D0F8001241F0400139 +:10719000C0F800127047E148D0F8001221F0400119 +:1071A000C0F80012DC49002008607047DB48D0F8C6 +:1071B000001221F01001C0F8001201218161704716 +:1071C000D249FF2081F83E00D4480021C0F81C11AC +:1071D000D0F8001241F01001C0F800127047CF49FA +:1071E00081B0D1F81C21012A0DD0C84991F83E1078 +:1071F000FF290DBF00204942017001B008BF704750 +:10720000012001B07047C64A126802F07F02524264 +:1072100002700020C1F81C01C24800680090EFE72E +:10722000F0B517460C00064608BFFFDFB74D14F057 +:10723000010F2F731CBF012CFFDF002E0CBF01209C +:1072400002206872EC7201281CBF0228FFDFF0BD2B +:10725000AE4981F83F0070472DE9F84FDFF8C8A22A +:107260009AF80000042828BFFFDFA84CDFF89882B6 +:10727000AA4D94F83C0000260127E0B1D5F804019E +:1072800010F1000918BF4FF00109D5F810010028CE +:1072900018BF012050EA09014FF4002B17D08021BC +:1072A000C5F80813C8F800B084F83C6090F0010FEE +:1072B00018BFBDE8F88FDFF84492D9F84C010028D8 +:1072C0007ED0A07A01287CD002287BD0AEE0D5F811 +:1072D0000001DFF84CA230B3C5F800616F61FF20F8 +:1072E000009002E0401E009005D0D5F81C01002857 +:1072F0000098F7D000B9FFDFDAF8000000F07F0A4D +:1073000094F83F0050453CBF002000F079FB84F822 +:107310003EA0C5F81C61C5F808738248006800905B +:107320002F64AF6302E0B9F1000F03D0B9F1000F91 +:107330002BD05DE0DAF8000000F07F0084F83E001A +:10734000C5F81C6194F83D1049B194F83F10814292 +:1073500018D2002000F054FB2F64AF6312E0734991 +:10736000096894F83F308AB2090C984203D30F2A77 +:1073700006D9022904D2012000F042FB2F6401E06B +:107380002F64AF636748006800908022C5F804232B +:107390005A48876466490B68A1F1040CDCF800C008 +:1073A00043F698273B44634519D20A6842F21073AA +:1073B0001A440A60C0F848615F495E48086002E00C +:1073C00034E01CE01EE0091F5C4808605148C0F82A +:1073D00000B0A06B40F40020A063BDE8F88F0E6001 +:1073E000C0F84861C5F80823C8F800B0C0F8486183 +:1073F0008020C5F80803C8F800B0BDE8F88F207EEB +:1074000010B913E0607E88B1A07FE17F07FA00F039 +:1074100007FA01F10843C8F8000094F82000800042 +:1074200000F1804000F5C040C0F810653648A16BFF +:107430000160A663217C002019B1D9F8441101290B +:1074400000D00021A27A012A56D0022A55D000BFCE +:10745000D5F8101101290CBF1021002141EA0008C4 +:107460003748016811F0FF0F03D0D5F81411012936 +:1074700000D0002184F83210006810F0FF0F03D014 +:10748000D5F81801012800D0002084F833002D48D9 +:10749000006884F83400FFF77CF8012818BF00204A +:1074A00084F83500C5F80061C5F80C61C5F81061B5 +:1074B000C5F80461C5F81461C5F818612248006870 +:1074C00000900E48C0F8446120480068DFF8309012 +:1074D0000090D9F80000A062A9F104000068E06201 +:1074E0001B48016801F00F01032908BF012042D0A9 +:1074F000016801F00F012DE045E04BE00080004005 +:10750000448500401414004008F50140DC0B0020C5 +:107510000411004004F501406015004000100040D7 +:10752000481500401C110040C700002074150040A1 +:107530004885004014100040ACF5014048810040EF +:1075400040160040101400401811004044810040D3 +:1075500010150040042908BF02200CD0016801F07A +:107560000F01052925D0006800F00F0006281CBF78 +:10757000FFDF01201DD084F83000A07A84F83100AC +:1075800002282BD11DE0D5F80C01012814BF0020E2 +:1075900008205DE7D5F80C01012814BF0020022067 +:1075A000F64A1268012A14BF04220022104308433D +:1075B0004EE7F348006810F0060F0CBF08200420C7 +:1075C000D9E7607850B1EF49096809780840217817 +:1075D00031EA000008BF84F8247001D084F82460E8 +:1075E00018F0020F0AD0EBF751FBA16AE64A081A1D +:1075F0009AF80010490852F82110884718F0010F36 +:1076000018BF4FF0000B11D0EBF740FBE16A9AF87E +:107610000020081ADD4951F822205946904700BF42 +:107620009AF8000010F0010F2FD10CE018F0020FB3 +:1076300018BF4FF0010BE7D118F0080F18BF4FF03B +:10764000020BE1D1ECE7DFF83CB3DBF80000007897 +:1076500000F00F00072828BF84F8256015D2DBF85A +:107660000000062200F10901A01C0DF05BFF40B9EB +:10767000207ADBF800100978B0EBD11F08BF012099 +:1076800001D04FF0000084F82500E17A4FF00000AF +:1076900011F0020F1CBF18F0020F18F0040F19D1DF +:1076A00011F0100F1CBF94F83320002A02D094F878 +:1076B00035207AB111F0080F1CBF94F82420002A5D +:1076C00008D111F0040F02D094F8251011B118F070 +:1076D000010F01D04FF00100617A19B168B1FFF7D5 +:1076E000FFFB10E0AB48AA490160D5F8000220F08A +:1076F0000300C5F80002E77205E001290DD0022958 +:1077000018BFFFDF10D018F0010F17D0A2489AF869 +:10771000001050F82100804756E06672E772A772A9 +:107720009621227B002006E06672E7720220A0729A +:10773000227B96210120FFF796FBE4E718F0020F69 +:107740002DD018F0040F21D10CF07FFFF0B90CF010 +:107750008EFFD8B991480168001F0068C0F3006C23 +:10776000C0F3425500F00F03C0F30312C0F303202F +:10777000BCF1000F0AD0002B1CBF002A002805D145 +:10778000002918BF032D38BF48F0040827EA9800E5 +:1077900083499AF8002051F82210884714E018F025 +:1077A000080F06D07F489AF8001050F82100804753 +:1077B0000AE018F0100F08BFFFDF05D07A489AF8EA +:1077C000001050F821008047A07A022818BFBDE8B9 +:1077D000F88F207C002808BFBDE8F88F7349C1F8F6 +:1077E0004461022814D0012818BFFFDFE16A6069F4 +:1077F000884298BFFFDF6069C9F80000A06B4FF4B2 +:10780000800140F48000A06369480160BDE8F88F02 +:107810006169E06A0844EFE738B5664D0024002846 +:1078200018BFC5F800426448006864498A7A012A92 +:1078300002D0022A03D018E00A7E12B915E04A7E6F +:107840009AB18B7F012291F81FC002FA03F302FA6A +:107850000CF21A434F4B1A6091F82010890001F185 +:10786000804101F5C041C1F810450121FFF759F9E8 +:10787000C5F80041C5F80C41C5F81041C5F80441F0 +:10788000C5F81441C5F818414D480068009038BD4E +:10789000012804BF28207047022804BF1820704721 +:1078A000042812BF08284FF4A870704700B5FFDF06 +:1078B000282000BD012804BF41F6A47070470228AB +:1078C00004BF41F288307047042804BF46F2180014 +:1078D0007047082804BF47F2A030704700B5FFDFAB +:1078E00041F6A47000BD10B502280DD0012804BFD8 +:1078F00042F6CE3010BD042817BF082843F6A44036 +:10790000FFDF41F66A0010BD0CF07AFE30B90CF0D2 +:1079100084FE002808BF41F6583001D041F264309F +:1079200041F29A01084410BD012812BF022800202C +:107930007047042812BF08284FF4C870704700B57C +:10794000FFDF002000BD1B490820C1F800021149DB +:107950000F4808601C491B480860091D1B48086047 +:107960001C491B480860091D1B48086010494FF45A +:10797000602008601149022088727047001400409E +:107980001414004004150040005C0200485C020032 +:107990000000040408F50140085C02005414004093 +:1079A000185C0200285C0200385C02000080004085 +:1079B00004F501400010004040850040DC0B002031 +:1079C000181100400011004098F5014014100040CB +:1079D0001C110040A8F50140101000401948016832 +:1079E00003291BBF006802280120002070471548AA +:1079F00001680B291BBF00680A280120002070477E +:107A000011490968C9B9114A1149136870B123F0C5 +:107A1000820343F07D0343F0004313600A6822F0C1 +:107A2000100242F0600242F0004205E023F0004301 +:107A300013600A6822F000420A60064981F83D009E +:107A40007047000050150040881700403C17004068 +:107A50007C170040DC0B002010B53F4822210DF0C0 +:107A60000CFE3D480024017821F010010170012135 +:107A700006F064F839494FF6FF7081F82240888497 +:107A800037490880488010BD704734498A8C82424B +:107A900018BF7047002081F822004FF6FF708884DD +:107AA00070472D49016070472D49088070472B4968 +:107AB0008A8CA2F57F43FF3B03D00021016008467A +:107AC000704791F822202549012A1ABF0160012040 +:107AD00000207047214901F1220091F82220012A5B +:107AE00004BF00207047012202701D48008888846E +:107AF000104670471A49488070471849184B8A8CBD +:107B00005B889A4206D191F82220002A1EBF0160AC +:107B100001207047002070471048114A818C52881C +:107B2000914209D14FF6FF71818410F8221F19B1DB +:107B30000021017001207047002070470748084A63 +:107B4000818C5288914205D190F8220000281CBFF8 +:107B50000020704701207047420C00201C0C0020C0 +:107B6000C80000207047574A012340B1012818BFC0 +:107B700070471370086890608888908170475370D0 +:107B80000868C2F802008888D08070474D4A10B15A +:107B9000012807D00EE0507860B1D2F802000860EA +:107BA000D08804E0107828B19068086090898880B7 +:107BB0000120704700207047424910B1012803D0CE +:107BC00006E0487810B903E0087808B10120704752 +:107BD0000020704730B58DB00C4605460D2104A835 +:107BE0000DF06DFDE0788DF81F0020798DF81E00F6 +:107BF00060798DF81D002868009068680190A86879 +:107C00000290E868039068460CF062FB20789DF8CB +:107C10002F1088420CD160789DF82E10884207D131 +:107C2000A0789DF82D10884202BF01200DB030BD14 +:107C300000200DB030BD30B50C4605468DB04FF07C +:107C4000030104F1030012B1FEF7F8F801E0FEF7BA +:107C500014F960790D2120F0C00040F040006071FF +:107C600004A80DF02CFDE0788DF81F0020798DF828 +:107C70001E0060798DF81D002868009068680190EA +:107C8000A8680290E868039068460CF021FB9DF814 +:107C90002F0020709DF82E0060709DF82D00A070C0 +:107CA0000DB030BD10B5002904464FF0060102D0DA +:107CB000FEF7C4F801E0FEF7E0F8607920F0C000BC +:107CC000607110BDCC000020FE48406870472DE96F +:107CD000F0410F46064601461446012005F0F8FA29 +:107CE000054696F85500FFF7E5FD4AF2B121084434 +:107CF0004FF47A71B0FBF1F0718840F27122514378 +:107D0000C0EB4100001BA0F2653403F03DF80028F1 +:107D100018BF1E3CAF4234BF28463846A04203D2AB +:107D2000AF422CBF3C462C467462BDE8F0812DE981 +:107D3000FF4F95B0044690F8550089461190DDE953 +:107D4000171008431390E048002605780C2D28BF33 +:107D5000FFDFDE4F37F8158094F874510C2D28BFE3 +:107D6000FFDFDA4830F8150040441FFA80F894F835 +:107D700065000D280CBF012000200C9017980028EA +:107D800004BF94F8140103282BD10C9848B3B4F81D +:107D90009601484525D1D4F81C01C4F80801608833 +:107DA00040F2E2414843C4F80C01B4F86201B4F86F +:107DB000EE100844C4F81001204602F0EFFFB4F8BA +:107DC0009A01E08294F898016075B4F89C01608093 +:107DD000B4F89E01A080B4F8A001E080022084F8ED +:107DE0001401D4F86C011090D4F868010F90B4F825 +:107DF000EE70B4F86001D4F85C110891179921B1C4 +:107E000094F8281151B100F0DDB804F1E8010391B4 +:107E100074310D9104F5A475091D07E004F59E71F8 +:107E20000391091D0D9104F59675091D0E91B4F885 +:107E30005810A9EB0000A9EB01010FFA80FA0FFA24 +:107E400081FBBAF1000F05DAD4F85801089001203F +:107E5000DA461390002002909B480079E8B3F3F7CC +:107E600039FFD0B3B4F80001022836D394F81401D6 +:107E7000022832D094F82B0178BB94F87481B8F1C1 +:107E80000C0F28BFFFDF914830F8180000F5C860DC +:107E90001FFA80F894F8140101287DD0618840F21F +:107EA000E24041430020B8F1000F05D0884808FBAC +:107EB00001F1B1FBF0F0401C07EB0B01A1EB0A0252 +:107EC000D4F81C1180B2431A029902FB03110291EB +:107ED000C4F81C01012084F82B0194F81401002837 +:107EE00074D0012800F04682022800F09481032813 +:107EF00018BFFFDF00F078820298311A0898FCF76B +:107F0000BCFB0D99012640F2712208600E98A0F882 +:107F10000090002028702E710D980068A86061887C +:107F2000D4F81C015143C0EB41006749A0F2353041 +:107F30000862C969814287BF03990860039801609C +:107F40000398616A0068084400F2A510E86002F036 +:107F50001BFF10B1E8681E30E8606E71B4F8D800FD +:107F6000A0EB090000B20028C4BF032068710C9880 +:107F70000028189800F09A82D8B100BFB4F8001118 +:107F800000290CBF0020B4F80201A4F8020194F803 +:107F90000421401C504300E019E0884209D268796E +:107FA000401E002805DD6E71B4F80201401CA4F8E3 +:107FB00002011798002800F0A18294F828010028F7 +:107FC00000F0988219B00220BDE8F08F65E094F8C7 +:107FD0006800032857D03B4894F8551090F83000BB +:107FE00005F023FBE18A40F27122514300EB41018D +:107FF0000020D4F80C21B8F1000F06D0344808FB5B +:1080000002F2B2FBF0F000F10100D4F80831D4F82C +:108010001021A0EB030C029BC4F8080102FB0C33F7 +:108020004FF0000007D000BF294808FB01F1B1FB69 +:10803000F0F000F10100D4F81811C4F81801A0EB19 +:1080400001011944608840F2E24300FB03F34FF062 +:10805000000006D01E4808FB03F3B3FBF0F000F16C +:10806000010007EB0B03A3EB0A03A3EB0202D4F816 +:108070001C31A2F10102A0EB030302FB03110291E8 +:10808000C4F81C0126E7E18A40F27122D4F80C0101 +:1080900001FB02F100EB4101AAE70F98002808BF9D +:1080A000FFDF94F85510074890F8300005F0BDFA4E +:1080B0000790E18A40F271204143079800EB4101AB +:1080C000002007E0640C0020DC000020585C020067 +:1080D00040420F00B8F1000F07D000BFFF4808FB77 +:1080E00001F1B1FBF0F000F10100C4F81801618862 +:1080F00040F2E24001FB00F14FF0000006D0F748EB +:1081000008FB01F1B1FBF0F000F10100C4F81C0123 +:1081100086B221464FF00100D4F828A005F0D8F827 +:10812000074694F85500FFF7C5FB4AF2B12B5844B7 +:108130004FF47A78B0FBF8F0618840F27122514335 +:10814000C0EB4100801BA0F2653602F01DFE002846 +:1081500018BF1E3EBA4534BF38465046B04203D21F +:10816000BA452CBF56463E46666294F85500FFF766 +:10817000DBFB00F2E140B0FBF8F10F980E1894F829 +:108180005500FFF7D1FB074694F85500FFF792FB27 +:1081900038444AF2AB310844B0FBF8F1E28A40F2CD +:1081A000712042430798D4F8187100EB4200401A3E +:1081B000C01B3044A0F12006617D40F2E24011FB7B +:1081C00000FA94F85500009010F00C0F0ABF0098C8 +:1081D0004EF62830FFF76EFB5844B0FBF8F000EB8A +:1081E000470000EB0A070098FFF752FB384400F104 +:1081F0006201BB48C16194F85500FFF795FB00F29E +:10820000E140B0FBF8F10F980844301AB0F53D7F1B +:1082100098BFFFDF70E6E18A40F27122D4F80C01CA +:10822000514300EB41010020B8F1000F07D000BF1F +:10823000AA4808FB01F1B1FBF0F000F10100C4F81D +:108240001801608840F2E24100FB01F14FF00000AC +:1082500006D0A24808FB01F1B1FBF0F000F10100EB +:10826000C4F81C0186B221464FF00100D4F828A0C2 +:1082700005F02EF8804694F85500FFF71BFB4AF2F4 +:10828000B12B00EB0B014FF47A70B1FBF0F0618879 +:1082900040F271225143C0EB4100801BA0F26536D1 +:1082A00002F072FD002818BF1E3EC24534BF404692 +:1082B0005046B04203D2C2452CBF5646464666627F +:1082C0000FBB1898F8B194F855603046FFF7F2FAF2 +:1082D00000EB0B014FF47A70B1FBF0F0D4F81811F9 +:1082E000E38A084440F27122D4F80C115A4301EB9E +:1082F00042010F1A3046FFF7CBFA1099081A38449A +:10830000A0F120060AE0E18A40F27122D4F80C01C3 +:10831000514300EB4100D4F81811461AD4F810214B +:10832000D4F80811D4F8180101FB020A607D40F26C +:10833000E24110FB01F894F8557017F00C0F0ABFDA +:1083400038464EF62830FFF7B5FA00EB0B014FF434 +:108350007A70B1FBF0F000EB4A0080443846FFF73A +:1083600097FA404400F160015D48C161012084F842 +:108370001401C1E5618840F271225143D4F81C0117 +:10838000D4F81021C0EB410101FB0AF607EB0B0109 +:10839000891AD4F808C1D4F81831491E0CFB0232EE +:1083A00001FB002A607D40F2E24110FB01F894F8E5 +:1083B000557017F00C0F0ABF38464EF62830FFF7FD +:1083C00079FA4AF2B12101444FF47A70B1FBF0F02E +:1083D00000EB4A0080443846FFF75AFA404400F167 +:1083E00060013F48C16187E5628840F27121D4F89D +:1083F0001C015143C0EB410000FB0AF694F86400F5 +:1084000024281CBF94F8650024280BD1B4F89601E9 +:10841000A9EB000000B2002804DB94F899010028C1 +:1084200018BF1190139800B3FFB9109800281ABF15 +:108430000F980028FFDF94F8550010F00C0F14BFC0 +:108440004EF62830FFF736FA4AF2B12101444FF4D4 +:108450007A70B1FBF0F0361A94F85500FFF718FA6D +:108460001099081A3044A0F12006D4F81C1107EB2B +:108470000B0000FB01F7119810F00C0F0ABF1198C8 +:108480004EF62830FFF716FA4AF2B12101444FF4B4 +:108490007A70B1FBF0F000EB47071198FFF7F8F99D +:1084A000384400F160010E48C16125E500287FF4E1 +:1084B00065AD94F8140100283FF47BAD618840F26B +:1084C0007122D4F81C015143C0EB4101284604F04D +:1084D000CFFD0004000C3FF46CAD03E040420F0000 +:1084E000DC0000202299002918BF0880012019B063 +:1084F000BDE8F08F94F86401FCF723FF94F8640161 +:108500002946FCF703FE20B1179880F0010084F89B +:10851000290119B00020BDE8F08F70B5FE4C607ADB +:1085200000281CBF002070BD94F8340038B1A16B46 +:10853000606A884203D9F7F7BEF8002070BDA06AD0 +:10854000E8B1F6F750F90546F5F7C4FF284442F2C2 +:1085500010714618FCF790FB05462946E06AFDF7C6 +:10856000A4F8E562A16A8219914224BF081AA062A8 +:1085700005D20120A062F7F79EF8002070BD01200F +:1085800070BDF8B5E44C02460025E44E6168606AAF +:10859000052A4ED2DFE802F003353A3D4400A07AC6 +:1085A000002760B101216846FDF748FC9DF80000F6 +:1085B00042F210710002B0FBF1F201FB1207F6F774 +:1085C00012F9C119A069FCF758F8A06125740320BD +:1085D00060757079002814BF012003202075607A2F +:1085E00038B9207B04F11001FCF790FD002808BF8A +:1085F000FFDF2584FCF74AFAB079BDE8F840EAF7D6 +:108600008BBCBDE8F840002100F0C7BDC1F868018F +:10861000F8BDD1F86801BDE8F840012100F0BDBD0A +:1086200084F83450FCF732FAB079BDE8F840EAF744 +:1086300073BCFFDFF8BD2DE9F04FDFF8DC820446A4 +:1086400083B098F800008B4601270025B34E4FF009 +:108650000209032804BF98F80C00A04240F0E7800C +:10866000D8F80400B06198F80000032818BFFFDFB5 +:108670000324BBF1080F80F0D680DFE80BF0040F75 +:1086800031312CD4D4CBC8F82450F6F783FC002821 +:1086900018BFFFDFB47003B0BDE8F08FF5F71AFF25 +:1086A0000446D8F81C00A04228BFC8F81C4005D2D8 +:1086B000201AFDF72EF8C8F81C4038B1F6F7E3FF92 +:1086C000002818BFFFDF03B0BDE8F08F03B0002023 +:1086D000BDE8F04F55E703B0BDE8F04FFEF7BCBD75 +:1086E00070794FF0010A002814BF0120032088F898 +:1086F000140088F8105098F8340042F2107B68B1EA +:108700004FF47A71D8F81800FBF7B7FFC8F81800D3 +:10871000002108F1100004F0ABFC1CE001216846C8 +:10872000FDF782FB9DF800000002B0FBFBF10BFBA4 +:10873000110AF6F758F800EB0A018A46D8F8180033 +:10874000FBF79BFFC8F81800514608F1100004F031 +:108750008FFC00F1010AB8F82000411C0A293CBF37 +:108760005044A8F82000D8F8040038B1B8F8200028 +:10877000401C0A2828BF88F8159001D288F81540B7 +:1087800098F8090070BB98F8340040B1D8F8381058 +:10879000D8F82400884202D9F6F78DFF22E0D8F8F5 +:1087A000280058B3F6F71FF80446F5F793FE204467 +:1087B00000EB0B09FCF760FA04462146D8F82C00C0 +:1087C000FCF773FFC8F82C40D8F8281000EB09021A +:1087D000914224BF081AC8F828000FD2C8F82870A0 +:1087E000F6F769FF98F80C00FCF727FA88F80050B4 +:1087F000B07903B0BDE8F04FEAF78EBB98F80C00F3 +:1088000008F11001FCF782FC002808BFFFDF03B06D +:10881000BDE8F08F98F80C00FCF70FFA88F80050CC +:1088200003B0BDE8F08FFFDF03B0BDE8F08F202C70 +:1088300028BFFFDFDFF8E880072138F81400FAF7D7 +:10884000D9F85FEA000A08BFFFDF202C28BFFFDF4E +:1088500038F81400BAF80010884218BFFFDF5446F9 +:10886000C6F818A04FF0200ABBF1080F80F04A812B +:10887000DFE80BF0049FA9A9A2F4F3F2C4F8685151 +:108880003581C4F86C5194F8290138B9FCF7F4F932 +:10889000D4F83411FCF709FF00281BDCB4F82611CA +:1088A000B4F85800814206D1B4F8DC10081AA4F8D4 +:1088B000DE00204605E0081AA4F8DE00B4F8261110 +:1088C0002046A4F85810D4F85011C4F83411C0F858 +:1088D00058111DE0B4F82411B4F85800081AA4F88F +:1088E000DE00B4F824112046A4F85810D4F834114E +:1088F000C4F85011C4F85811D4F83C11C4F8E81069 +:10890000D4F84011C4F85C11B4F84411A4F8601113 +:1089100002F020F906E00000640C0020DC000020DA +:10892000A00C0020FCF782F9804694F85500FEF771 +:10893000C1FF4AF2B12108444FF47A71B0FBF1F063 +:10894000D4F81C1140F27122084461885143C0EBF5 +:108950004100A0F1300AB8F1B70F98BF4FF0B70847 +:108960002146012004F0B4FC4044AAEB0000A0F230 +:108970001A38A2462146012004F0AAFC00F19C010D +:10898000DAF82400884288BF451AC6F810804545A9 +:1089900028BF4546F560D4F85401A0F2A5107061D7 +:1089A000FCF750FE84F8287186F8029003B0BDE809 +:1089B000F08F02F0E4F901E0FEF74EFC84F8287134 +:1089C00003B0BDE8F08FFCF757F9D4F85821014601 +:1089D0001046FCF76AFE48B1628840F27123D4F871 +:1089E0001C115A43C1EB4201B0FBF1F094F8651041 +:1089F0000D290FD0B4F85820B4F826111318994255 +:108A0000AEBF481C401C1044A4F8260194F82A016B +:108A100078B905E0B4F82601401CA4F8260108E066 +:108A2000B4F82601B4F8DC10884204BF401CA4F856 +:108A30002601B4F862010DF1040B401CA4F8620198 +:108A4000B4F88000B4F87E10401AB4F85810401EF4 +:108A500008441FFA80F912E046E03EE052E00023AD +:108A60001A462046CDF800B0FFF761F9002804BF90 +:108A700003B0BDE8F08F012818BFFFDF25D0B4F8A0 +:108A80002611A9EB010000B20028E8DA082084F8DA +:108A9000740084F87370204601F01EFE84F81451AF +:108AA00094F864514FF6FF77202D00D3FFDF28F8AC +:108AB000157094F86401FCF7C0F884F864A1B079EB +:108AC00003B0BDE8F04FEAF727BAB4F82601BDF8C5 +:108AD00004100844A4F82601D1E7FEF75DFA03B0BC +:108AE000BDE8F04FFEF7B8BB94F81401042818BF96 +:108AF000FFDF84F8145194F864514FF6FF77202D6E +:108B0000D5D3D3E7FFDF03B0BDE8F08F10B5FA4C43 +:108B1000207850B101206072F6F7E5FD2078032837 +:108B200005D0207A002808BF10BD0C2010BD207B86 +:108B3000FCF707FC207BFCF752FE207BFCF77DF85E +:108B4000002808BFFFDF0020207010BD2DE9F04F86 +:108B5000E94F83B0387801244FF0000840B17C72AF +:108B60000120F6F7C0FD3878032818BF387A0DD0F9 +:108B7000DFF8889389F8034069460720F9F7C3FEB8 +:108B8000002818BFFFDF4FF6FF7440E0387BFCF78A +:108B9000D8FB387BFCF723FE387BFCF74EF8002827 +:108BA00008BFFFDF87F80080E2E7029800281CBFBB +:108BB00090F8141100292AD00088A0421CBFDFF8C9 +:108BC00040A34FF0200B3AD00721F9F713FF040020 +:108BD00008BFFFDF94F86401FCF701FE84F81481FC +:108BE00094F864514FF6FF76202D28BFFFDF2AF856 +:108BF000156094F86401FCF720F884F864B16946C4 +:108C00000720F9F780FE002818BFFFDF12E0684652 +:108C1000F9F757FE0028C8D011E0029800281CBFC1 +:108C200090F81411002905D00088A0F57F41FF3984 +:108C3000CAD104E06846F9F744FE0028EDD089F86F +:108C4000038087F8348087F80B8003B00020BDE8EC +:108C5000F08F70B50446AB4890F80004AA4D400967 +:108C600095F800144909884218BFFFDF95F8140DE4 +:108C70004009A64991F800144909884218BFFFDF4E +:108C80009E49002001228C7188700A7048700A7118 +:108C9000C870487198490870BDE8704056E7974918 +:108CA000087070472DE9F843934C064688462078B3 +:108CB000002867D19648FBF764FF2073202861D015 +:108CC000032766602770002565722572AEB1012109 +:108CD00006F58270FDF7D1F80620F9F733FE8146DC +:108CE0000720F9F72FFE96F804114844B1FBF0F283 +:108CF00000FB1210401C86F80401FBF797FF40F2BE +:108D0000F651884238BF40F2F65000F59F7086B2A7 +:108D1000F5F7E0FBE061F5F766FD4FF0010900288B +:108D200033D084F80A90FBF7A7FF814601216846FB +:108D3000FDF77AF89DF8000042F210710002B0FBD6 +:108D4000F1F201FB120081194846FBF796FCA06185 +:108D5000C4E90A8969484079002814BF012003202A +:108D6000207567752574207B04F11001FCF7CEF99E +:108D7000002808BFFFDF25840020F6F7B4FC0020A0 +:108D8000BDE8F8830C20BDE8F883FBF775FF31469A +:108D9000FBF773FCA061A57284F83490A8F28B50A5 +:108DA000A562A063D6E7554948717047534948709A +:108DB00070475249087170472DE9F0414F4C064603 +:108DC0002089401C2081D4E903516078D6F868716D +:108DD00020B13A46284604F076F90546E068854217 +:108DE00005D06169281A08446061FCF72BFCE56036 +:108DF000AF4209D896F81401012805D0E078002880 +:108E000004BF0120BDE8F0810020BDE8F08110B56D +:108E100004460846FEF74EFD4AF2B12108444FF4DD +:108E20007A71B0FBF1F040F2E241614300F235307B +:108E300081428CBF081A002010BD70B5044682B074 +:108E4000002084F8280194F8E600002807BF94F871 +:108E50001401032802B070BDFBF70EFFD4F85821AF +:108E600001461046FCF721FC0028DCBF02B070BDB3 +:108E7000628840F27123D4F81C115A43C1EB4201BD +:108E8000B0FBF1F0B4F85810401C0844A4F82401D9 +:108E9000B4F8DC00B4F82421801A00B20028DCBF4A +:108EA00002B070BD012084F82A01B4F88000B4F843 +:108EB0007E2001AE801A401E084485B212E0009662 +:108EC000B4F82411002301222046FEF730FF0028C9 +:108ED00004BF02B070BD01281CD0022812BFFFDF02 +:108EE00002B070BDB4F82401281A00B20028BCBF3B +:108EF00002B070BDE3E70000640C0020DC0000203D +:108F0000A00C002001E000E00BE000E019E000E030 +:108F100037860100B4F82401BDF804100844A4F811 +:108F20002401DFE7F8B50422002506295BD2DFE83B +:108F300001F007260319192A044680F8142107E0D6 +:108F40000446BD48C078002818BF84F814210AD010 +:108F5000FBF79CFDA4F86251B4F85800A4F8260170 +:108F600084F82A51F8BD0095B4F8DC1001230022E2 +:108F70002046FEF7DCFE002818BFFFDFE8E70321EC +:108F800080F81411F8BD0646876AB0F81C01314616 +:108F900085B2012004F09CF9044696F85500FEF7CE +:108FA00089FC4AF2B12108444FF47A71B0FBF1F028 +:108FB000718840F271225143C0EB4100401BA0F286 +:108FC000653501F0E1FE002818BF1E3DA74234BF01 +:108FD00020463846A84228BF2C4602D2A74228BFC6 +:108FE0003C467462F8BDFFDFF8BD2DE9F05F924E9C +:108FF000B178022906BF31890029BDE8F09FB46924 +:10900000C4F86C0194F85500FEF742FCD4F86C11DA +:10901000081AF1680144F160316908443061B469AB +:1090200094F82B01002808BFBDE8F09F94F81401C4 +:10903000032818BFBDE8F09F94F8555036780C2EE1 +:1090400028BFFFDF7D4F37F8168094F874610C2E2F +:1090500028BFFFDF37F81600404494F8748186B2C9 +:10906000B8F10C0F28BFFFDF37F8180000F5C86013 +:109070001FFA80F82846FEF70BFCD4F86C114FF06D +:10908000000A0F1A15F00C0F0ABF28464EF62830BA +:10909000FEF710FC4FF47A7900F2E730B0FBF9F0FC +:1090A0003F1A2846FEF7F4FBD4F8E81015F00C0F31 +:1090B000A1EB000B0ABF28464EF62830FEF7FAFB5C +:1090C0004AF2B1210844B0FBF9F0ABEB0000A0F18B +:1090D00060017143B1FBF8F1292202EB50006031CD +:1090E000A0EB510200EB5100BA4201D8B84201D8BE +:1090F000F2F794FE608840F2E241414300202EB135 +:1091000006FB01F04E49B0FBF1F0401CC4F81C0115 +:1091100084F82BA1BDE8F09F70B50546464890F84D +:1091200002C0BCF1020F07BF806900F5B474454866 +:1091300000F12404002904BF256070BD4FF47A7645 +:1091400001290DD002291CBFFFDF70BD1046FEF7BC +:10915000CAFB00F2E140B0FBF6F0281A206070BDB7 +:109160001846FEF7E1FB00F2E140B0FBF6F0281AEA +:10917000206070BD3348007800281CBF0020704775 +:1091800010B50720F9F7D0FB80F0010010BD2D4885 +:109190000078002818BF012070472DE9F843294CBA +:1091A0000025814684F83450D4F8188084F83010B3 +:1091B000E5722570012727722946606803F0CDFA11 +:1091C0006168C1F85881267B81F86461C1F86891B3 +:1091D000C1F85C81B1F80080202E28BFFFDF1A485B +:1091E00020F81680646884F814510023A4F86051B4 +:1091F0001A46194620460095FEF799FD002818BF2B +:10920000FFDFC4F81051C4F8085184F81471A4F8B1 +:109210002651A4F8245184F82A51B4F85800401E6D +:10922000A4F85800A4F86251FBF730FC024880799A +:10923000BDE8F843E9F770BEDC000020585C02008E +:1092400040420F00640C0020A00C0020012804D034 +:10925000022805D0032808D105E0012907D004E041 +:10926000022904D001E0042901D000207047012028 +:1092700070472DE9F0410E46044604F07CFD05469A +:10928000204604F07CFD044604F097F8FE4F0100F0 +:1092900015D0386990F854208A4210D090F8AC313B +:1092A0001BB190F8AE3123421FD02EB990F8513047 +:1092B000234201D18A4218D890F8AC01A8B12846BF +:1092C00004F07BF870B1396991F85520824209D0D9 +:1092D00091F8AC0118B191F8AF01284205D091F88E +:1092E000AC0110B10120BDE8F0810020FBE730B5F2 +:1092F000E54C85B0E06900285DD0142168460CF08B +:10930000DEF9206990F85500FEF7D4FA4FF47A712F +:1093100000F5FA70B0FBF1F5206990F85500FEF702 +:10932000B7FA2844ADF8060020690188ADF80010AE +:10933000B0F85810ADF804104188ADF8021090F85C +:109340008E0130B1A069C11C039104F0F5FB8DF8CA +:109350001000206990F88D018DF80800E1696846D9 +:1093600088472069002180F88E1180F88D110399BB +:10937000002920D090F88C1100291CD190F864109D +:10938000272918D09DF81010039A002913D01378BC +:109390000124FF2B11D0072B0DD102290BD15178BD +:1093A000FF2908D180F88C410399C0F890119DF8ED +:1093B000101080F88F1105B030BD1B29F2D9FAE7E3 +:1093C00070B5B14C206990F865001B2800D0FFDF14 +:1093D0002069002580F88D5090F8C00100B1FFDFB2 +:1093E000206990F88E1041B180F88E500188A0F865 +:1093F000C41180F8C2510E2108E00188A0F8C41100 +:1094000080F8C251012180F8C6110D2180F8C011E9 +:109410000088F9F721FCF9F7B9F82079E9F77CFD24 +:10942000206980F8655070BD70B5974CA0798007B1 +:109430002CD5A078002829D162692046D37801690B +:109440000D2B01F158005FD00DDCA3F102034FF0AA +:1094500001050B2B19D2DFE803F01A1844506127DD +:10946000182C183A6400152B6FD008DC112B4BD048 +:10947000122B5AD0132B62D0142B06D166E0162B78 +:1094800071D0172B70D0FF2B6FD0FFDF70BD91F81C +:1094900067200123194603F081FD0028F6D12169D8 +:1094A000082081F8670070BD1079BDE8704001F0B8 +:1094B00008BD91F86600C00700D1FFDF01F0C0FCD5 +:1094C000206910F8661F21F00101017070BD91F84C +:1094D0006500102800D0FFDF2069112180F88D5031 +:1094E00008E091F86500142800D0FFDF20691521FD +:1094F00080F88D5080F8651070BD91F865001528D2 +:1095000000D0FFDF172005E091F86500152800D096 +:10951000FFDF1920216981F8650070BDBDE870404A +:109520004EE7BDE8704001F0A0BC91F86420012333 +:10953000002103F033FD00B9FFDF0E200FE011F82A +:10954000660F20F0040008701DE00FE091F8642021 +:109550000123002103F022FD00B9FFDF1C20216957 +:1095600081F8640070BD12E01BE022E091F8660013 +:10957000C0F30110012800D0FFDF206910F8661F3A +:1095800021F010010170BDE8704001F059BC91F864 +:1095900064200123002103F001FD00B9FFDF1F203B +:1095A000DDE791F86500212801D000B1FFDF22201E +:1095B000B0E7BDE8704001F04FBC3348016991F855 +:1095C0006620130702D501218170704742F008021E +:1095D00081F866208069C07881F8C90001F027BC55 +:1095E00010B5294C21690A88A1F8042281F80202E9 +:1095F00091F8540001F009FC216981F8060291F804 +:10960000550001F002FC216981F80702012081F870 +:109610000002002081F8AC012079BDE81040E9F794 +:109620007BBCF0B4184C206900F5DA730188198509 +:10963000018E5985818E9985018FB0F84420914221 +:1096400000D31146D985828FB0F846108A4200D2E5 +:109650001146198690F855204FF0010512F00C0FB5 +:109660004FF4296203D0914200D81146198690F830 +:10967000540010F00C0F04D0988D904200D902468F +:109680009A8583F8265001E0000100202079F0BC83 +:10969000E9F742BC10B5F84C01230921206990F884 +:1096A0006420583003F07AFC38B12169002001F8B9 +:1096B0007C0F087301F8180C10BD0120A07010BDBC +:1096C00070B5ED4D012329462869896990F8642019 +:1096D00009790E2A01D1122903D000241C2A03D0B3 +:1096E00004E0BDE87040D5E7142902D0202A08D054 +:1096F00009E080F8644080F88840BDE8704001F0DF +:1097000003BC162906D0262A01D1162902D0172912 +:1097100009D00CE000F8644F80F8244040782128FC +:109720000CD01A2017E090F86520222A07D0EA69A9 +:10973000002A03D0FF2901D180F88E3112E780F88A +:10974000654001F07DFB286980F87D4090F8AC0110 +:109750000028F3D00020BDE8704041E72DE9F84330 +:10976000C54C206990F86410202909D05FF00007EB +:1097700090F86510222905D07FB300F1640503E05D +:109780000127F5E700F1650510F8961F41F0040187 +:109790000170A06904F0FBFA4FF00108002608B33D +:1097A0003946A069FFF765FDE0B16A46A169206905 +:1097B00003F012FE90B3A06904F0E7FA2169A1F862 +:1097C0009601B1F8581001F014FB40B3206928212C +:1097D00080F8741080F8738058E0FFE70220A070D2 +:1097E000BDE8F883206990F8AC0110B11E20FFF7A6 +:1097F000F7FEAFB1A0692169C07881F8CA0008FA04 +:1098000000F1C1F3006000B9FFDF20690A2180F890 +:10981000641090F8880040B9FFDF06E009E02AE014 +:109820002E7001F00DFBFFF7C8FE206980F87D6007 +:10983000D6E7226992F8AC0170B1B2F8583092F8CC +:109840005410B2F8B00102F5CB7203F0B7FE68B164 +:109850002169252081F86400206900F1650180F804 +:109860007D608D4212D180F865600FE00020FFF727 +:10987000B7FE2E70F0E720699DF8001080F898116F +:109880009DF8011080F8991124202870206900F1BA +:1098900065018D4203D1BDE8F84301F0D1BA80F8EB +:1098A00088609DE770B5744C01230B21206990F806 +:1098B0006520583003F072FB202650BB206901233D +:1098C000002190F86520583003F068FB0125F0B1C5 +:1098D000206990F8640024281BD0A06904F035FAB0 +:1098E000C8B1206990F8961041F0040180F89610F4 +:1098F000A1694A7902F0070280F85120097901F044 +:10990000070180F8501090F8AD311BBB06E0A57040 +:1099100028E6A67026E6BDE870404EE690F8AC3129 +:10992000C3B900F154035E788E4205D11978914293 +:1099300002D180F87D500DE000F5FD710D700288B8 +:109940004A8090F850200A7190F8510048712079AF +:10995000E9F7E2FA2169212081F86500BDE870404D +:1099600001F065BA70B54448006990F84E20448E05 +:10997000C38E418FB0F84050022A23D0A94200D3B1 +:1099800029460186C18FB0F84220914200D311468A +:109990008186018FB0F84420914200D31146418660 +:1099A000818FB0F84620914200D31146C186418E86 +:1099B000A14200D90C464486C18E994200D90B467B +:1099C000C386CFE5028E914200D31146C68F828EA8 +:1099D000964200D23246A94200D329460186B0F809 +:1099E00042108A4200D30A468286002180F84E1037 +:1099F000CFE770B5204C206990F8660010F0300F6A +:109A000004D0A07840F00100A070ABE5A06904F09C +:109A100081F948B32569A06904F078F92887256998 +:109A2000A06904F06FF968872569A06904F070F9EE +:109A3000A8872569A06904F067F9E887A0794FF045 +:109A40000102800703D56069C07814280FD020690F +:109A500090F864101C290AD090F84E10012910D0FB +:109A600090F8A31169B909E0BDE87040A5E5206947 +:109A700080F84E2005E000000001002090F8A211BF +:109A800031B1206910F8661F41F01001017016E035 +:109A900090F8661041F0200180F8661000F5DA7148 +:109AA00003888B86038FCB86438F0B87838F4B87EF +:109AB000C08F888781F832202079E9F72DFABDE838 +:109AC000704001F0B4B970B5FE4C206990F8661092 +:109AD000890707D590F8642001230821583003F046 +:109AE0005DFAE8B1206990F89000800712D4A0696F +:109AF00004F0ECF8216981F89100A06930F8052F95 +:109B0000A1F892204088A1F8940011F8900F40F03D +:109B100002000870206990F89010C90703D00FE088 +:109B20000120A0701EE590F86600800700D5FFDFD9 +:109B3000206910F8661F41F00201017001F077F909 +:109B40002069002590F86410062906D180F8645039 +:109B500080F888502079E9F7DFF9206990F89411AE +:109B60000429DFD180F894512079E9F7D5F92069EB +:109B700090F864100029D5D180F88850F2E470B5CF +:109B8000D04C01230021206990F86520583003F063 +:109B900005FA012578B9206990F86520122A0AD0C3 +:109BA00001230521583003F0F9F910B10820A07005 +:109BB000D8E4A570D6E4206990F88E0008B901F0C9 +:109BC00036F92169A069F03104F061F82169A069D2 +:109BD000C03104F067F8206990F8C80100B1FFDFD8 +:109BE00021690888A1F8CA0101F5E671A06904F0AD +:109BF0003CF82169A06901F5EA7104F03EF820699A +:109C000080F8C851142180F865102079BDE87040B3 +:109C1000E9F782B970B5AB4C01230021206990F8B7 +:109C20006520583003F0BAF90125A8B1A06903F006 +:109C3000E8FF98B1A0692169B0F80D00A1F896017C +:109C4000B1F8581001F0D5F858B12069282180F8F2 +:109C5000741080F8735085E4A57083E4BDE870400B +:109C6000ABE4A0692169027981F89821B0F8052058 +:109C7000A1F89A2103F0B8FF2169A1F89C01A0691D +:109C800003F0B5FF2169A1F89E01A06903F0B6FFBA +:109C90002169A1F8A0010D2081F8650062E47CB57E +:109CA000884CA079C00738D0A06901230521C57868 +:109CB000206990F86520583003F070F968B1AD1E46 +:109CC0000A2D06D2DFE805F0090905050909050591 +:109CD0000909A07840F00800A070A07800281CD1E5 +:109CE000A06903F057FF00287AD0A0690226C57842 +:109CF0001DB1012D01D0162D18D1206990F86400F6 +:109D000003F034F990B1206990F864101F290DD048 +:109D1000202903D0162D16D0A6707CBD262180F8F0 +:109D20006410162D02D02A20FFF75AFC0C2D58D0B3 +:109D30000CDC0C2D54D2DFE805F033301D44A7A70E +:109D4000479E57A736392020A0707CBD0120152DD5 +:109D500075D008DC112D73D0122D69D0132D64D06D +:109D6000142D3DD178E0162D7CD0182D7DD0FF2DFF +:109D700036D183E020690123194690F867205830D6 +:109D800003F00CF9F8B9A06903F068FF216981F8C4 +:109D90007A01072081F8670078E001F03CF975E06E +:109DA000FFF738FF72E001F016F96FE0206990F8D4 +:109DB0006510112901D0A67068E0122180F86510A5 +:109DC00064E0FFF7DCFE61E0206990F86500172889 +:109DD000F1D101F035F821691B2081F8650055E0CB +:109DE00052E0FFF770FE51E0206990F86600C0076E +:109DF00003D0A07840F001001FE06946A06903F09D +:109E00006CFF9DF8000000F02501206900F8961F06 +:109E10009DF8011001F04901417001F008F8206936 +:109E200010F8661F41F0010114E0FFF733FC2DE04C +:109E3000216991F86610490705D5A07026E00EE06B +:109E400016E00FE011E000F0F2FF206910F8661F45 +:109E500041F00401017019E0FFF7CBFD16E001F0BD +:109E600087F813E0FFF71EFD10E0FFF777FC0DE029 +:109E700001F05DF80AE0FFF723FC07E0E16919B1A2 +:109E8000216981F88E0101E0FFF797FB2069F0E975 +:109E90002A12491C42F10002C0E900127CBD70B5D3 +:109EA000084CA07900074DD5A07800284AD1206938 +:109EB00090F8CC00FE2800D1FFDF2069FE2180F859 +:109EC000CC1001E00001002090F865100025192950 +:109ED00006D180F88D5000F0B3FF206980F86550FE +:109EE000206990F864101F2902D0272921D119E098 +:109EF00090F8650003F03AF878B120692621012333 +:109F000080F8641090F865200B21583003F046F873 +:109F100078B92A20FFF764FB0BE02169202081F843 +:109F2000640006E0012180F88D1180F8645080F80B +:109F30008850206990F86710082903D10221217008 +:109F400080F8CC10E4E4F949096991F898210AB93C +:109F500091F8542081F8542091F899210AB991F888 +:109F6000552081F85520002802D00020FFF738BB8B +:109F7000704770B5ED4C06460D46206990F8CC0050 +:109F8000FE2800D0FFDF2269002082F8CC6015B1E6 +:109F9000A2F88A00BCE422F8840F01201071B7E413 +:109FA00070B5E24C01230021206990F864205830FC +:109FB00002F0F4FF00287AD0206990F8A21111B1C4 +:109FC00090F8A31139B190F8AC1100296ED090F837 +:109FD000AD1111B36AE090F8651024291BD090F8F8 +:109FE0006410242917D0002300F5CC7200F5D1713C +:109FF00003F084F82169002081F8A20101461420B1 +:10A00000FFF7B7FF206930F8421FA0F88C10818855 +:10A01000A0F88E1050E00123E6E790F865200123B8 +:10A020000B21583002F0BAFF68BB206990F8540049 +:10A0300000F0EBFE0646206990F8550000F0E5FEC2 +:10A040000546206990F8AE113046FFF7FFF8D8B109 +:10A05000206990F8AF112846FFF7F8F8A0B12269FF +:10A06000B2F8583092F85410B2F8B00102F5CB7241 +:10A0700003F0A4FA20B12169252081F864001BE0D7 +:10A080000020FFF7ADFA11E020690123032190F8C9 +:10A090006520583002F082FF40B920690123022177 +:10A0A00090F86520583002F079FF08B100202FE4C5 +:10A0B00000211620FFF75DFF012029E410B5E8BB61 +:10A0C0009A4C206990F86610CA0702D00121092035 +:10A0D00052E08A070AD501210C20FFF74AFF2069C8 +:10A0E00010F8901F41F00101017047E04A0702D5C6 +:10A0F0000121132040E00A0705D510F8C91F41715E +:10A100000121072038E011F0300F3BD090F8A31167 +:10A11000A1B990F8A211E1B190F8651024292FD0CF +:10A1200090F8641024292BD05FF0000300F5CC7266 +:10A1300000F5D17102F0E2FF206900E022E010F8A2 +:10A14000661F21F0200141F010010170002180F80C +:10A150003C11206990F86600C00613D5FFF702FC99 +:10A1600000F0D2FE206930F8421FA0F88C108188E0 +:10A17000A0F88E1001211520FFF7FBFE012010BD75 +:10A180000123D3E7002010BD70B5684C206990F81A +:10A19000CC10FE2978D1A178002975D190F86720DC +:10A1A00001231946583002F0F9FE00286CD12069CD +:10A1B00090F8781149B10021A0F8821090F8791137 +:10A1C00080F8CE10002102205BE090F8652001238A +:10A1D0000421583002F0E2FE0546FFF76FFF002829 +:10A1E00052D1284600F07BFF00284DD12069012381 +:10A1F000002190F86420583002F0D0FE78B1206938 +:10A200000123042190F86520583002F0C7FE30B9D0 +:10A21000206990F87C0010B10021122031E0206903 +:10A2200090F864200A2A0DD0002D2DD101230021A1 +:10A23000583002F0B3FE78B1206990F894110429E7 +:10A240000AD105E010F8CA1F01710021072018E0AB +:10A2500090F89000800718D0FFF7A2FE002813D1D5 +:10A2600020690123002190F86420583002F096FE06 +:10A27000002809D0206990F88C01002804D0002122 +:10A28000FF20BDE8704074E609E000210C20FFF7D4 +:10A2900070FE206910F8901F41F00101017041E447 +:10A2A0003EB505466846FDF702FC00B9FFDF2221F6 +:10A2B00000980BF0E2F90321009803F053FC00989A +:10A2C000017821F010010170294603F070FC174C51 +:10A2D0000D2D43D00BDCA5F102050B2D19D2DFE8C3 +:10A2E00005F01F184A19191F185518192700152DA0 +:10A2F0005DD008DC112D28D0122D0BD0132D09D0E4 +:10A30000142D06D153E0162D2CD0172D68D0FF2D1B +:10A3100072D0FFDFFDF7DEFB002800D1FFDF3EBD7E +:10A320002169009891F8CE101AE000000001002089 +:10A33000E26800981178017191884171090A817170 +:10A340005188C171090A0172E4E70321009803F002 +:10A3500038FD0621009803F038FDDBE70098062160 +:10A360000171D7E70098216991F8AE21027191F847 +:10A37000AF114171CEE721690098F83103F096FCE6 +:10A3800021690098C43103F09BFCC3E7F849D1E987 +:10A390000001CDE90101206901A990F8960000F0C3 +:10A3A00025008DF80400009803F0C5FCB2E7206991 +:10A3B000B0F84410009803F095FC2069B0F8D01074 +:10A3C000009803F093FC2069B0F84010009803F067 +:10A3D00091FC2069B0F8CE10009803F08FFC99E74B +:10A3E000216991F8AC0100280098BDD111F8542FD3 +:10A3F00002714978BDE7FFE7206990F88F21D0F816 +:10A400009011009803F0E1FB84E7DA4810B5006989 +:10A4100090F86A1041B990F8652001230621583060 +:10A4200002F0BCFD002800D0012010BD70B5D14D58 +:10A43000286990F8681039B1012905D0022906D0A1 +:10A44000032904D0FFDF06E4B0F8DC1037E090F811 +:10A450006710082936D0B0F87E10B0F880200024AC +:10A460008B1C9A4206D3511A891E0C04240C01D06D +:10A47000641EA4B290F87C1039B190F864200123D6 +:10A480000921583002F08AFD40B3FFF7BEFF78B1D2 +:10A4900029690020B1F87820B1F876108B1C9A4217 +:10A4A00003D3501A801E00D0401EA04200D284B2B6 +:10A4B0000CB1641EA4B22869B0F8DC102144A0F8E5 +:10A4C000D8103FE5B0F87E100329BDD330F8581FEF +:10A4D000028D1144491CA0F8801033E50024EAE7FE +:10A4E00070B50C4605464FF4027120460BF0E7F8B4 +:10A4F000258027E5F8F787BB2DE9F0410D46074693 +:10A500000721F8F777FA041E3CD094F8B40100262E +:10A51000A8B16E70092028700BE0268484F8B4611D +:10A52000D4F8B6016860D4F8BA01A860B4F8BE01E6 +:10A53000A88194F8B4010028EFD12E71BAE094F804 +:10A54000C00190B394F8C0010D2813D00E2801D09B +:10A55000FFDFAFE02088F8F77FFB0746F8F72BF81E +:10A5600078B96E700E20287094F8C2012871208886 +:10A57000E88014E02088F8F76FFB0746F8F71BF82F +:10A5800010B10020BDE8F0816E700D20287094F8A5 +:10A59000C20128712088E88094F8C601287284F8E6 +:10A5A000C0613846F8F701F884E0FFE794F8F80155 +:10A5B00030B16E701020287084F8F861AF8079E0B7 +:10A5C00094F8C80190B16E700A2028702088A88085 +:10A5D000D4F8CC11C5F80610D4F8D011C5F80A107B +:10A5E000B4F8D401E88184F8C86163E094F8D60136 +:10A5F00040B16E701A202870B4F8D801A88084F891 +:10A60000D66157E094F8F20170B16E701B2028708B +:10A6100005E000BF84F8F261D4F8F401686094F8B2 +:10A62000F2010028F6D145E094F8DA0190B16E709D +:10A630001520287004F5EE7707E000BF84F8DA6192 +:10A640000A223946281D0AF0DEFF94F8DA010028B4 +:10A65000F4D12FE094F8E60158B16E701D202870F7 +:10A6600084F8E6610A2204F5F471281D0AF0CBFF94 +:10A6700020E094F8FA0138B11E20287084F8FA61BD +:10A68000D4F8FC01686015E094F8000200283FF45B +:10A6900079AF6E701620287008E000BF84F8006261 +:10A6A000D4F802026860B4F80602288194F8000227 +:10A6B0000028F3D1012065E72E480021C161016225 +:10A6C0000846704730B52B4D0C46E860FFF7F4FFA5 +:10A6D00000B1FFDF2C7130BD002180F8641080F8DC +:10A6E000651080F8681090F8E61009B1022100E0CA +:10A6F0000321FEF717BC2DE9F0411E4C05462069E9 +:10A7000009B1002104E0B0F8EE10B0F8DE201144E9 +:10A71000A0F8EE1090F8781139B990F8672001236D +:10A720001946583002F03AFC30B1206930F8821FE7 +:10A73000B0F85C2011440180206990F8883033B172 +:10A74000B0F88410B0F8DE201144A0F8841090F91D +:10A750008C70002F06DDB0F88A10B0F8DE201144AE +:10A76000A0F88A1001213D2635B180F8746017E009 +:10A77000705C0200000100202278022A0AD0012A1F +:10A7800011D0A2782AB380F8731012F0140F0DD0F4 +:10A790001E2113E090F8CE20062A3CD016223AE083 +:10A7A00080F8731044E090F87A2134E0110702D564 +:10A7B00080F874603CE0910603D5232180F8741082 +:10A7C00036E0900700D1FFDF21692A2081F874006C +:10A7D0002AE02BB1B0F88420B0F886309A4210D22B +:10A7E000002F05DDB0F88A20B0F886309A4208D2F2 +:10A7F000B0F88230B0F88020934204D390F87831DA +:10A800000BB1222207E090F868303BB1B0F87E30FF +:10A81000934209D3082280F87420C1E7B0F87E2063 +:10A82000062A01D33E22F6E7206990F8731019B189 +:10A830002069BDE8F0414FE7BDE8F0410021FEF797 +:10A8400071BB2DE9F047FA4C81460D46206900881E +:10A85000F8F714FA060000D1FFDFA0782843A070B3 +:10A86000A0794FF000058006206904D5A0F87E503D +:10A8700080F8EC5003E030F87E1F491C0180FFF7A0 +:10A88000C4FD012740B3E088000506D5206990F893 +:10A890006A1011B1A0F876501EE02069B0F8761069 +:10A8A000491C89B2A0F87610B0F878208A4201D30A +:10A8B000531A00E00023B4F808C00CF1050C6345FE +:10A8C00001D880F87C70914206D3A0F8765080F8C9 +:10A8D000F8712079E8F720FBA0794FF0020810F01A +:10A8E000600F0ED0206990F8681011B1032908D1CB +:10A8F00002E080F8687001E080F868800121FEF7CE +:10A9000011FB206990F86810012904D1E188C9057C +:10A9100001D580F86880B9F1000F71D1E18889050F +:10A9200002D5A0F8005104E0B0F80011491CA0F8CD +:10A93000001100F09BFBFEF7DAFCFFF725FC00F0AE +:10A9400057FF0028206902D0A0F8E05003E030F85B +:10A95000E01F491C018000F04EFF38B1216991F8D9 +:10A96000EC00022807D8401C81F8EC00206990F820 +:10A97000EC00022804D9206920F8E05F45800573C7 +:10A9800020690123002190F86520583002F006FB71 +:10A9900020B9206990F865000C2859D1206901235D +:10A9A000002190F86420583002F0F8FA48B320698A +:10A9B0000123002190F86720583002F0EFFA00B32D +:10A9C000206990F86810022942D190F8EC00C0B9D3 +:10A9D0003046F7F7C0FBA0B1216991F8CC00FE2802 +:10A9E00036D1B1F8DA00012832D981F8E570B1F832 +:10A9F0008000B1F87E20831E9A4203DB012004E030 +:10AA000032E025E0801A401E80B2B1F8E0202389B0 +:10AA10009A4201D3012202E09A1A521C92B2904249 +:10AA200000D91046012801D181F8E55091F8702134 +:10AA300092B1B1F8E220B1F872118A4201D301213A +:10AA400002E0891A491C89B2884205D9084603E008 +:10AA50002169012081F8E5502169B1F8582010449E +:10AA6000A1F8DC00FFF7E2FCE088C0F34021484693 +:10AA7000FFF741FE206980F8E650BDE8F047FDF79A +:10AA80004BB86B4902468878CB78184312D10846F8 +:10AA9000006942B18979090703D590F86700082851 +:10AAA00008D001207047B0F84810028E914201D8BA +:10AAB000FEF782B90020704770B55D4C05460E4622 +:10AAC000E0882843E080A80703D5E80700D0FFDF2F +:10AAD0006661EA074FF000014FF001001AD0A6614D +:10AAE000F278062A02D00B2A14D10AE0226992F8E1 +:10AAF0006530172B0ED10023E2E9283302F8370C1A +:10AB000008E0226992F86530112B03D182F86910B0 +:10AB100082F88E00AA0718D56269D278052A02D079 +:10AB20000B2A12D10AE0216991F86520152A0CD16F +:10AB30000022E1E92A2201F83E0C06E0206990F8A3 +:10AB40006520102A01D180F86A10280601D5082056 +:10AB5000E07078E42DE9F84F354C00254FF00108FE +:10AB6000E580A570E5704146257061F3070220611C +:10AB70009246814680F8E6800088F8F77FF8070063 +:10AB800000D1FFDF20690088FCF78EFF2069008874 +:10AB9000FCF7B0FF2069B0F8DA1071B190F8CC1072 +:10ABA000FE290FD190F8781191B190F86720012318 +:10ABB0001946583002F0F2F980B1206990F8CC00C3 +:10ABC000FE2805D0206990F8CC0000BFFFF768FB95 +:10ABD000206990F8E71089B1258118E02069A0F874 +:10ABE000825090F8791180F8CE1000210220FFF7F2 +:10ABF000C0F9206980F8E5500220E7E790F8B41129 +:10AC000019B9018C8288914200D881882181B0F8DD +:10AC1000DE10491E8EB2B0F8E0103144A0F8E0100A +:10AC200090F8E41031B1A0F8E25080F8E45006E06A +:10AC300000010020B0F8E2103144A0F8E21030F832 +:10AC40007E1F31440180FFF7E0FB20B1206930F81E +:10AC5000761F314401802069B0F8DA10012902D84A +:10AC6000491CA0F8DA100EB180F8EC5090F8E5100D +:10AC7000A1B1B0F8E000218988420FD23846F7F739 +:10AC80006AFA58B1206990F8701139B1B0F8E21041 +:10AC9000B0F87201814201D300F0B0FD206980F864 +:10ACA000E55090F865100B2901D00C2916D1B0F8A9 +:10ACB0005820B0F89631D21A12B2002A0EDBD0F822 +:10ACC0009811816090F89C110173022101F045FDFB +:10ACD000206980F8655080F8988026E0242910D1FA +:10ACE000B0F85810B0F89621891A09B2002908DB8B +:10ACF00090F8AC01FFF727F9206900F8655F057649 +:10AD000013E090F86410242901D025290DD1B0F862 +:10AD10005810B0F89601081A00B2002805DB01208F +:10AD2000FFF711F9206980F8645020690146B0F8F6 +:10AD3000DE20583001F0E9FE206990F8701109B169 +:10AD4000A0F8E250F9480090F94BFA4A49465046BB +:10AD500000F0AEFC216A11B16078FCF7F3F92069CC +:10AD60000123052190F86520583002F017F90028DA +:10AD700003D0BDE8F84F00F036BABDE8F88F00F018 +:10AD80001DBDED49C8617047EB48C069002800D07F +:10AD900001207047E84A50701162704710B50446B0 +:10ADA000B0F89C214388B0F89E11B0F8A0019A42F7 +:10ADB00005D1A388994202D1E38898420FD0238815 +:10ADC000A4F8B831A4F8BA21A4F8BC11A4F8BE01C3 +:10ADD000012084F8B401D8480079E8F79DF80121F2 +:10ADE000204601F0BAFC002004F8650F0320E07053 +:10ADF00010BD401A00B247F6FE71884201DC0028FF +:10AE000001DC012070470020704710B5012808D0F0 +:10AE1000022808D0042808D0082806D0FFDF2046E2 +:10AE200010BD0124FBE70224F9E70324F7E7C24839 +:10AE30000021006920F88A1F8178491C81707047C1 +:10AE4000BD4800B5016911F88C0F401E40B2087072 +:10AE5000002800DAFFDF00BDB7482721006980F82D +:10AE60006410002180F88C11704710B5B24C206935 +:10AE700090F89411042916D190F864200123002140 +:10AE8000583002F08BF800B9FFDF206990F890107D +:10AE9000890703D4062180F8641004E0002180F8BB +:10AEA000881080F89411206990F86600800707D513 +:10AEB000FFF7C6FF206910F8661F21F0020101703C +:10AEC00010BD9D4910B5096991F864200A2A09D17D +:10AED00091F8CA20824205D1002081F8640081F8EF +:10AEE000880010BD91F86620130706D522F00800EF +:10AEF00081F86600BDE81040A2E7FF2801D0FFDF1F +:10AF000010BDBDE81040A7E710B58B4C05212069A6 +:10AF1000FEF708F8206990F84E10012903D0BDE82B +:10AF20001040FEF77EBB022180F84E1010BD10B518 +:10AF3000814C206910F8961F41F004010170A0694E +:10AF400002F041FF162806D1206990F864002028FD +:10AF500002D0262805D010BDA06902F038FFFEF708 +:10AF60003FFB2169002081F8640081F8880010BD52 +:10AF700070B5714C01230A21206990F86420583083 +:10AF800002F00CF810B3A06902F0C4FEA8B1256964 +:10AF9000A06902F0BBFE28872569A06902F0B2FE15 +:10AFA00068872569A06902F0B3FEA8872569A069B2 +:10AFB00002F0AAFEE887FEF7D5FC2169002081F89F +:10AFC000880081F86400BDE870409DE7A07840F0FB +:10AFD0000100A070BDE510B5574C01230021206988 +:10AFE00090F86520583001F0D9FF30B1FFF71FFF0E +:10AFF0002169102081F8650010BD20690123052119 +:10B0000090F86520583001F0C9FF08B1082000E031 +:10B010000120A07010BD70B5474C012300212069AC +:10B0200090F86520583001F0B9FF012588B1A0697A +:10B0300002F011FE2169A1F89601B1F85810FFF74E +:10B04000D8FE40B12069282180F8741080F8735030 +:10B050007FE5A5707DE52169A06901F5CC7102F05D +:10B06000F5FD21690B2081F8650072E510B5FEF74A +:10B0700016FFFEF714FE304CA079400708D5A078E3 +:10B0800030B9206990F86700072801D101202070AD +:10B09000FEF7CAF9A079C00609D5A07838B92069A9 +:10B0A00090F865100B2902D10C2180F86510E0782A +:10B0B00000070ED520690123052190F8652058303E +:10B0C00001F06CFF30B10820A0702169002081F8E8 +:10B0D000C00110BDBDE81040002000F093BB10B5CA +:10B0E000154C216991F86520F8B1102A06D0142A70 +:10B0F00007D0152A22D01B2A34D122E001210B20AF +:10B1000021E0FAF797FE0C281FD320690821F830B8 +:10B11000FAF794FE28B120690421C430FAF78EFEB4 +:10B1200000B9FFDF012104200DE010E043A8010079 +:10B1300083AA0100B9AA01000001002000F017F85D +:10B1400003E001210620FEF714FF012010BD212A93 +:10B1500008D191F87D0038B991F8AC0110B191F89F +:10B16000AD0108B1002010BD01211720EBE770B53B +:10B17000174C0025206990F87B1101290AD002297B +:10B1800025D190F88E10A9B1062180F8CE100121AA +:10B19000022017E090F8C011002918D100F1B00387 +:10B1A00000F1F001002200F5BE7001F071FE0121F6 +:10B1B000052007E090F89600400701D5112000E037 +:10B1C0000D200121FEF7D5FE206980F87B51C0E4F7 +:10B1D0000001002030B5FA4C05462078002818BF41 +:10B1E000FFDF257230BDF6490120C87170472DE997 +:10B1F000F14FF44E30464068044600F1580990F88B +:10B20000551001F0D2FF94F85510658E80B20829D0 +:10B210006CD001F0A8FF854238BF284600F0FF0837 +:10B22000DFF89CA3E848CAF824007768384697F806 +:10B230006AB07D8E97F8551001F0B7FF97F855105A +:10B2400080B2082956D001F08EFF854238BF2846CB +:10B25000BBF1000F1CBF001D80B2C0B297F85510A3 +:10B26000FBF770FB99F81200002847D009F158014C +:10B27000D54891E80E1000F5027585E80E10D9F852 +:10B280006810C0F82112D9F86C10C0F8251200F52A +:10B290008170FBF7BCFE307800280CBF0120002035 +:10B2A00080F00101C9480176D9E91412C0E90412FD +:10B2B000A0F58372DAF82410FBF7DBF994F8550057 +:10B2C000012808BF00220CD0022808BF012208D0A4 +:10B2D000042808BF032204D008281ABFFFDF002279 +:10B2E000022241460120FBF7DFF90DE0042101F0C5 +:10B2F0003AFF90E7042101F036FFA6E7DAF82400D0 +:10B30000FBF785FEFBF7FCF9009850B994F855005F +:10B3100094F8561010F00C0F08BF00219620FBF790 +:10B3200097FE94F8542001210020FBF779FF94F850 +:10B330002C00012808BFFBF743FF02208AF8000019 +:10B34000FCF74CFB002818BFFFDFBDE8F88F2DE9A4 +:10B35000F04FDFF870A28BB050469AF80020416899 +:10B360001438049091F85D0001F158050C464FF037 +:10B3700008080127AAF13406A0B3012800F00681CD +:10B38000022800F00781032818BFFFDF00F01081BA +:10B39000306A0423017821F008010170AA7908EAD3 +:10B3A000C202114321F004010170EA7903EA82022A +:10B3B000114321F01001017095F80590F06AF6F73D +:10B3C000DAFE8046FCF7BAFBB9F1020F00F000810B +:10B3D000B9F1010F00F00081B9F1030F00F0008115 +:10B3E00000F003B9FFE72B7B4FF002094FF0000B91 +:10B3F000242B1CBF95F80DC0BCF1240F07D01F2BC8 +:10B4000018BF202B2AD0BCF1220F4DD077E091F845 +:10B41000540092B191F89811002974D0082818BFEF +:10B42000042869D0082918BF042965D0012818BF4D +:10B43000012953D04FF0020065E091F8FA1000297D +:10B4400061D0082818BF042856D0082918BF04293D +:10B4500052D0012818BF012940D0EBE7BCF1220FE0 +:10B4600022D0002A4BD091F8540091F8AE1111F07F +:10B47000040F18BF41460CD0082818BF04283BD041 +:10B48000082918BF042937D0012818BF012925D061 +:10B49000D0E711F0010F18BF3946EDD111F0020FBE +:10B4A00018BF4946E8D12EE04AB391F8540091F80C +:10B4B000AE2191F8511002EA010111F0040F18BFFA +:10B4C00041460ED0082818BF042815D0082918BFF7 +:10B4D000042911D0012818BF0129ABD14FF0010078 +:10B4E00011E011F0010F18BF3946EBD111F0020F36 +:10B4F00018BF4946E6D106E04FF0080003E091F896 +:10B5000054000428F8D001460290204601F058FE6D +:10B5100080B2029901F027FE218E814238BF084691 +:10B52000ADF80C00A4F848000498FCF7E6FA60B106 +:10B53000B289316A42F48062B28172694FF48060EC +:10B54000904703206871EF7022E709AA03A9F06A07 +:10B55000F6F74CFD306210B195F8351021B1049822 +:10B56000FCF79FFA6F7113E79DF8241031B9A0F82A +:10B5700000B080F802B0012102F0F4FABDF80C101E +:10B58000306A02F026FC85F8059001E70498FCF784 +:10B5900088FAFDE6B4F84800ADF8080009AA02A947 +:10B5A000F06AF6F723FD3062002808BFFFDFEFE600 +:10B5B0000498FCF7A2FA002808BFFFDFE8E60000C5 +:10B5C0002401002058010020E00C0020E80E00209B +:10B5D00030EA080009D106E030EA080005D102E0AF +:10B5E000B8F1000F01D0012100E00021306A02789B +:10B5F00042EA01110170697C00291CBF69790129A7 +:10B600003DD005F15801FD4891E80E1000F5027893 +:10B6100088E80E10A96EC0F82112E96EC0F8251254 +:10B6200000F58170FBF7F3FC9AF8000000280CBFCE +:10B6300001200020F2490876D5E91202C1E904028E +:10B64000A1F5837101F58370326AFBF712F894F863 +:10B650005400012808BF00220CD0022808BF012294 +:10B6600008D0042808BF032204D008281ABFFFDF2F +:10B6700000220222FB210020FBF716F803E0FBF773 +:10B68000C6FCFBF73DF8012194F855200846FBF76E +:10B69000C7FD3771306A018831828078B0743770A5 +:10B6A000FCF7A5F9002818BFFFDF0BB0BDE8F08F4D +:10B6B0002DE9F047D34C8146DDF8208020781E46E6 +:10B6C00017460D4628B9002F1CBF002EB8F1000FF9 +:10B6D00000D1FFDFC4F81C80C4E90D95C4E90576EC +:10B6E0004FF00000E071A071E070A07020716071F7 +:10B6F000C54EA081E081307805F158072888F7F71A +:10B70000BDFAE0622888F7F7A7FA2063FBF73EF955 +:10B7100095F95700FBF7DFF905F11200FBF75AFC2A +:10B7200005F10E00FBF7DDF9307800280CBF03208F +:10B730000120FBF769FCB87EFBF7DBF9FBF75EFC49 +:10B740003078002804BFFF2095F8544019D0BF7C02 +:10B750006C8E95F85510284601F027FD95F8551088 +:10B7600080B208291FD001F0FEFC014620468C4221 +:10B7700028BF0846002F1CBF001D80B2C0B295F83C +:10B7800055402146FBF7DEF83078214680B1012094 +:10B79000FBF7A3FA7068D0F8E800FBF73BFCBDE8C4 +:10B7A000F047012023E5042101F0DDFC0146DDE73F +:10B7B0000020FBF792FABDE8F047C8E5924800B5D3 +:10B7C00001783438007819B1022818BFFFDF00BDB6 +:10B7D000012818BFFFDF00BD8A4810B50078022895 +:10B7E00018BFFFDFBDE8104000F034BA00F032BAF5 +:10B7F0008448007970478348C078704781490120A8 +:10B80000487170472DE9F04706007F487D4D40683C +:10B8100000F15804686A90F8019018BF012E03D116 +:10B82000296B09F069FB6870687800274FF0010800 +:10B83000A0B101283CD0022860D003281CBFFFDF44 +:10B84000BDE8F087012E08BFBDE8F087286BF6F74A +:10B8500087FE287ABDE8F047E7F75EBB012E14D0DB +:10B86000A86A002808BFFFDF6889C21CD5E9091053 +:10B8700009F084FEA86A686201224946286BF6F73F +:10B88000EBFC022E08BFBDE8F087D4E91401401C90 +:10B8900041F10001C4E91401E079012801D1E77107 +:10B8A00001E084F80780287ABDE8F047E7F734BB69 +:10B8B000012E14D0A86A002808BFFFDF6889C21CC7 +:10B8C000D5E9091009F05AFEA86A686200224946C3 +:10B8D000286BF6F7C1FC022E08BFBDE8F087D4E95B +:10B8E0001410491C40F10000C4E91410E07901284B +:10B8F0000CBFE77184F80780BDE8F087012E06D001 +:10B90000286BF6F72DFE022E08BFBDE8F087D4E9BC +:10B910001410491C40F10000C4E91410E07901281A +:10B92000BFD1BCE770B5384E3046A6F1340440684C +:10B9300000F158052078012818BFFFDFA87868B10A +:10B940000021A970A289042042F00402A281626948 +:10B950009047307800281CBF01202871216A0322FB +:10B96000087832EA000009D1A28912F4806F05D06C +:10B9700042F00202A2816269022090470121002068 +:10B9800000F087F918B1BDE8704000F063B9BDE878 +:10B99000704000202BE42DE9F14F1B4E002730466C +:10B9A000A6F134054068317800F1580A2878B84685 +:10B9B000022818BFFFDFE88940F40070E881716851 +:10B9C0003078FF2091F85410FAF7BCFF0098002857 +:10B9D0009AF8120000F00681FAF7B7FEFAF7A5FE12 +:10B9E0004FF00109E0B99AF81200C8B1686A4178CD +:10B9F000B1B10078C0F3C00008E00000E00C002006 +:10BA0000E80E002024010020580100209AF80710B9 +:10BA1000884205D185F80290BDE8F84F00F01AB9C8 +:10BA2000686A41786981002908BFAF6203D0286B3A +:10BA3000F6F7CCFBA862E88940F02000E881EF70BF +:10BA40003078706800F15804834690F82C00012883 +:10BA50001AD1FBF7ABFB2146584601F05AFA98B1D0 +:10BA60003078002870680CBF00F58E7000F5F97012 +:10BA7000BBF800104180217A0171617A417180F830 +:10BA80000090287AE7F748FA686A9AF80610007872 +:10BA9000C0F3800088423BD03078706800F15804D1 +:10BAA00090F85D0000282FD002284BD067713078C5 +:10BAB00000281CBF2079002809D02771AA8939469F +:10BAC00042F01002AA816A694FF010009047E078B6 +:10BAD000A0B1E770FCF720F8002808BFFFDF0820BE +:10BAE000AA89002142F00802AA816A699047D4E934 +:10BAF0001202411C42F10000C4E91210A079012891 +:10BB00000CBFA77184F80690E88940F48070E88142 +:10BB1000696A9AF807300878C0F3C0029A424ED199 +:10BB20003278726800F0030002F15804012818BF4F +:10BB300002282DD003281CBFA87940F0040012D0A1 +:10BB4000A8713CE0E86AF6F77DFA002808BFFFDF3D +:10BB5000D4E91202411C42F10000C4E91210287A13 +:10BB6000E7F7DAF9A2E784F80290EA89484642F456 +:10BB70000062EA81AA8942F00102AA816A699047BB +:10BB8000E079012801D1E77119E084F8079016E007 +:10BB9000487818B3E98941F40061E981A96A71B173 +:10BBA000FB2884BFA87940F01000C9D8E8790028A4 +:10BBB00008BFC84603D080206A6900219047012051 +:10BBC000009900F066F8B0B1B8F1000F1CBF00207A +:10BBD000FFF718FEBDE8F84F00F03CB8E079012807 +:10BBE000D3D1D0E7002818BFFAF7E7FDE88940F085 +:10BBF0004000E881E3E7B8F1000F1CBF0120FFF728 +:10BC000001FEFFF7A4FBB8F1000F08BFBDE8F88FF5 +:10BC10000220BDE8F84FF5E570B50D4606463D48F3 +:10BC20003C4900784C6850B1FAF724FE034694F87A +:10BC3000542029463046BDE87040FDF76DBAFAF74A +:10BC400019FE034694F8542029463046BDE870405A +:10BC500006F091B92F4910B54C68FBF786FAFBF74F +:10BC600065FAFBF73DF9FBF7BBF9FAF749FD94F8E4 +:10BC70002C00012808BFFBF799FA274C00216269C4 +:10BC8000E0899047E269A179A07890470020207070 +:10BC900010BD70B5204C0546002908BF012D06D106 +:10BCA000E07800F10100C0B2E07001282ED8A1694F +:10BCB00028468847002829D06179184839B1012DD4 +:10BCC00001BF41780029017811F0100F1ED0A17931 +:10BCD000E1B910490978002908BF012D01D091B1BF +:10BCE0008DB90F49097811F0100F04BF007810F0DA +:10BCF000100F0BD0A08948B9A06A20B9608910B193 +:10BD000011F0100F02D04FF0000070BD4FF0010095 +:10BD100070BD00005801002024010020E00C00202C +:10BD200034010020FE498A78824286BF084490F898 +:10BD300043010020704710B540F2D311F84809F0D4 +:10BD40009CFCFF220821F74809F08FFCF6480021EF +:10BD5000417081704FF46171818010BD2DE9F04117 +:10BD60000E46054600F0ADFBED4C102816D004EB56 +:10BD7000C00191F85A0110F0010F1CBF0120BDE86D +:10BD8000F081607808283CBF012081F85A011CD25C +:10BD90006078401C60700120BDE8F0816078082860 +:10BDA00013D222780127501C207004EBC20830689F +:10BDB000C8F85401B088A8F85801102A28BFFFDF3E +:10BDC00088F8535188F85A71E2E70020BDE8F08105 +:10BDD000D54988707047D4488078704770B4D0488F +:10BDE00000250178491E4BB2002B46DB00EBC30156 +:10BDF00091F85A1111F0010F3BD04278D9B2521E7E +:10BE0000427000EBC10282F85A5190F802C0002241 +:10BE1000BCF1000F0BD9841894F803618E4202D153 +:10BE2000102A26D103E0521CD2B29445F3D80278EE +:10BE3000521ED2B202708A421BD000EBC20200EB4B +:10BE4000C10CD2F85341CCF85341D2F85721CCF869 +:10BE50005721847890F800C00022002C09D9861858 +:10BE600096F8036166450CD1102A1CBF024482F883 +:10BE70000311591E4BB2002BB8DAAB48857070BC69 +:10BE80007047521CD2B29442E9D8F2E7A4498A78AA +:10BE9000824286BF01EB0010C01C002070472DE9D4 +:10BEA000F04101261F4690463446002500F009FB6C +:10BEB00010282AD09A494FF0000C01EBC00292F8EA +:10BEC0005A2102F001058A78002A1ED901EB0C03E1 +:10BED00093F8033183421FD1BCF1100F15D0002F0E +:10BEE00018BF87F800C0887860450ED901EB0C10A8 +:10BEF00010F1030F09D001EB0C0090F84B4190F8C2 +:10BF00003B0101280CBF0126002648EA050046EA4D +:10BF100004010840BDE8F0810CF1010303F0FF0CBF +:10BF20006245D3D8F1E72DE9F05F1F4690460E46F3 +:10BF3000814600F0C6FA7A4D044610283CD00146EE +:10BF4000AB780020002B0ED92A1892F803218A42E0 +:10BF500005D110281CBF1220BDE8F09F03E0401C53 +:10BF6000C0B28342F0D8082B3FD2102C27D0AE7835 +:10BF70001022701CA87005EB061909F10300414658 +:10BF800000F06CFF09F183001022394600F066FFD3 +:10BF90001021384600F03FFF3544102185F8430159 +:10BFA000404600F038FF85F84B0185F8034100203A +:10BFB00085F83B01BDE8F09FAB78082B15D22C78B3 +:10BFC000CA46601C287005EBC4093068C9F85401E2 +:10BFD000B0884FF0000BA9F85801102C28BFFFDFE4 +:10BFE00089F853A189F85AB1C1E70720BDE8F09F4D +:10BFF00070B44B488178491E4BB2002BBCBF70BC5B +:10C00000704700BF817803F0FF0C491ECAB28270EE +:10C0100050FA83F191F8031194453ED000EB0215DC +:10C0200000EB0C14D5F80360C4F80360D5F8076082 +:10C03000C4F80760D5F80B60C4F80B60D5F80F6042 +:10C04000C4F80F60D5F88360C4F88360D5F88760C2 +:10C05000C4F88760D5F88B60C4F88B60D5F88F5032 +:10C06000C4F88F50851800EB0C0402EB420295F8DF +:10C0700003610CEB4C0C00EB420284F8036100EB13 +:10C080004C0CD2F80B61CCF80B61B2F80F21ACF874 +:10C090000F2195F83B2184F83B2100EBC10292F877 +:10C0A0005A2112F0010F33D190F802C00022BCF1E6 +:10C0B000000F0BD9841894F803518D4202D1102A35 +:10C0C00026D103E0521CD2B29445F3D80278521E16 +:10C0D000D2B202708A421BD000EBC20200EBC10C4C +:10C0E000D2F85341CCF85341D2F85721CCF857211C +:10C0F000847890F800C00022002C09D9851895F8A2 +:10C100000351654512D1102A1CBF024482F8031165 +:10C11000591E4BB2002BBFF675AF70BC70470000C4 +:10C12000100F00206C01002060010020521CD2B2D0 +:10C130009442E3D8ECE7FE4948707047FC484078E9 +:10C14000704738B14AF2B811884203D8F84988805C +:10C150000120704700207047F5488088704710B56F +:10C1600000F0AFF9102814D0F24A0146002092F8EE +:10C1700002C0BCF1000F0CD9131893F803318B42A5 +:10C1800003D1102818BF10BD03E0401CC0B2844585 +:10C19000F2D8082010BDE7498A78824286BF01EBB9 +:10C1A0000010833000207047E24B93F802C08445B2 +:10C1B0009CBF00207047184490F8030103EBC000B7 +:10C1C00090F853310B70D0F854111160B0F8580149 +:10C1D000908001207047D74A114491F80321D44937 +:10C1E0000A700268C1F8062080884881704770B5DF +:10C1F00016460C460546FAF7CEFFFAF796F9CC48F4 +:10C20000407868B1CB48817851B12A19002E0CBF13 +:10C210008330C01CFAF763F9FAF7AAF9012070BD60 +:10C22000002070BD10B5FAF7D1F9002804BFFF2037 +:10C2300010BDBDE81040FAF7EFB9FAF7C7B9BD492C +:10C240008A7882429CBF00207047084490F803011E +:10C2500001EBC00090F85A0100F0010070472DE991 +:10C26000F047B44E00273D46307800288CBFDFF8F9 +:10C27000C882BDE8F0870024B078002808D93119B9 +:10C2800091F80321AA4204D0611CCCB2A042F6D896 +:10C290001024A04286BF06EB0410C01C002006EB51 +:10C2A000C50999F85A1111F0010F16D050B1102C90 +:10C2B00004D0311991F83B11012903D0102100F06D +:10C2C000AAFD50B108F8074038467B1C99F8532165 +:10C2D00009F5AA71DFB2FAF7D6FB681CC5B230784F +:10C2E000A842C8D8BDE8F0872DE9F041914C00265E +:10C2F0003546A07800288CBF8F4FBDE8F0816119CA +:10C30000C0B291F80381A84286BF04EB0510C01C9F +:10C31000002091F83B11012903D0102100F07BFD92 +:10C3200058B104EBC800BD5590F8532100F5AA712F +:10C330003046731CDEB2FAF7A6FB681CC5B2A078C3 +:10C34000A842DCD8BDE8F08101447A4810B500EB82 +:10C3500002100A4601218330FAF7C1F8BDE8104007 +:10C36000FAF706B90A46724910B5497841B1714BDE +:10C37000997829B10244D81CFAF7B1F8012010BD10 +:10C38000002010BD6B4A01EB410102EB4101026844 +:10C39000C1F80B218088A1F80F0170472DE9F04109 +:10C3A000644D07460024A878002898BFBDE8F081B6 +:10C3B000C0B2A04217D905EB041010F1830612D0C9 +:10C3C0001021304600F027FD68B904EB440005EB6E +:10C3D000400808F20B113A463046FBF72CFCB8F83F +:10C3E0000F01A8F80F01601CC4B2A878A042DFD8E2 +:10C3F000BDE8F08101461022504800F02FBD4F48A3 +:10C4000070474C498A78824203D90A1892F843212E +:10C410000AB10020704700EB400001EB400000F241 +:10C420000B10704743498A78824206D9084490F835 +:10C430003B01002804BF01207047002070472DE910 +:10C44000F0410E46074615460621304600F0E3FC53 +:10C45000384C98B1A17871B104F59D7011F0010FBD +:10C4600018BF00F8015FA178490804D0457000F8B2 +:10C47000025F491EFAD10120BDE8F08138463146FD +:10C4800000F01FF8102819D0A3780021002B15D92F +:10C49000621892F8032182420BD1102918BF082993 +:10C4A0000CD004EB010080F83B514FF00100BDE8D7 +:10C4B000F08101F10101C9B28B42E9D80020BDE849 +:10C4C000F0812DE9F0411B4D0646002428780F46E7 +:10C4D000002811D905EBC40090F85311B14206D1E0 +:10C4E0000622394600F5AA7009F01CF838B1601C24 +:10C4F000C4B22878A042EDD81020BDE8F0812046D3 +:10C50000BDE8F0810B4910B44A7801EBC003521E1C +:10C510004A70002283F85A2191F802C0BCF1000F42 +:10C5200016D98B1893F8034184420DD1102A07E0E5 +:10C5300060010020100F00206C010020E31000209B +:10C540001CBF10BC704703E0521CD2B29445E8D81F +:10C550000A78521ED2B20A7082421BD001EBC2028C +:10C5600001EBC003D2F853C1C3F853C1D2F857212D +:10C57000C3F857218C7891F800C00022002C09D90B +:10C580008B1893F80331634506D1102A1CBF114460 +:10C5900081F8030110BC7047521CD2B29442EFD80C +:10C5A00010BC704770B449490D188A78521ED3B236 +:10C5B0008B7095F8032198423DD001EB001401EBFC +:10C5C000031C00EB4000DCF80360C4F80360DCF8F7 +:10C5D0000760C4F80760DCF80B60C4F80B60DCF897 +:10C5E0000F60C4F80F60DCF88360C4F88360DCF887 +:10C5F0008760C4F88760DCF88B60C4F88B60DCF877 +:10C600008FC0C4F88FC001EB030C03EB43039CF80D +:10C61000034101EB430385F8034101EB4000D3F8EC +:10C620000B41C0F80B41B3F80F31A0F80F319CF863 +:10C630003B0185F83B0101EBC20090F85A0110F074 +:10C64000010F1CBF70BC704700208C78002C0DD9E6 +:10C650000B1893F803C1944504D110281CBF70BC7B +:10C66000704703E0401CC0B28442F1D80878401EF5 +:10C67000C0B20870904204BF70BC704701EBC203A7 +:10C6800001EBC000D0F853C1C3F853C1D0F8570133 +:10C69000C3F857018C780B780020002C9CBF70BC2D +:10C6A000704700BF01EB000C9CF803C19C4506D10C +:10C6B00010281CBF084480F8032170BC7047401C40 +:10C6C000C0B28442EED870BC70470000100F00204A +:10C6D00010B50A7B02F01F020A73002202768B1843 +:10C6E00093F808C00CF001034FEA5C0C0CF0010455 +:10C6F00023444FEA5C0C0CF0010423444FEA5C0C29 +:10C700000CF001041C444FEA5C0303F0010CA44448 +:10C710005B0803F00104A4445B0803F00104A44493 +:10C720000CEB530300EB020C521C8CF8133090F806 +:10C7300018C0D2B263440376052AD0D3D8B22528D4 +:10C7400088BFFFDF10BD0023C383428401EBC20218 +:10C75000521EB2FBF1F10184704770B5002504460A +:10C7600003290DD04FF4FA4200297FD001297CD053 +:10C77000022918BF70BD0146BDE870405830A7E7D8 +:10C7800004F158068021304608F099FFB571F57123 +:10C7900035737573F573357475717576B5762120BB +:10C7A00086F83E00492086F83F00FE2086F8740097 +:10C7B00084F82C502584012084F8540084F8550016 +:10C7C000282184F856101B21218761874FF4A4711A +:10C7D000E187A1871B21218661864FF4A471E18640 +:10C7E000A1861B21A4F84010A4F844104FF4A471B2 +:10C7F000A4F84610A4F842101B21A4F84A10A4F88B +:10C800004C10A4F8481060734FF448606080A4F89E +:10C81000D850A4F8DA50A4F8DC50A4F8DE50A4F8FC +:10C82000E050A4F8E25084F8E55084F8E750A4F80A +:10C83000EE5084F8EC50A4F80051A4F8025184F8AA +:10C84000A25184F8A35184F8AC5184F8AD5184F816 +:10C85000705184F8785184F87B5184F89451C4F86D +:10C860008C51C4F8905170BD00E041E0A4F8EE5046 +:10C8700084F8E6506088FE490144B1FBF0F1A4F869 +:10C8800078104BF68031A4F87A10E388A4F87E5033 +:10C89000B4F882C0DB000CFB00FCB3FBF0F39CFBA4 +:10C8A000F0FC5B1CA4F882C09BB203FB00FC04F10B +:10C8B0005801A4F88030BCF5C84FC4BF5B1E0B857F +:10C8C000B2FBF0F2521CCA8500F5802202F5EE326E +:10C8D000531EB3FBF0F20A84CB8B03FB00F2B2FBD6 +:10C8E000F0F0C883214604F15800BDE87040EFE63F +:10C8F000B4F89C11B4F8A031B4F802C004F15800A7 +:10C90000A4F87E50B4F88240DB0004FB0CF4B3FBC7 +:10C91000F1F394FBF1F45B1C44859BB203FB01F43F +:10C920000385B4F5C84FC4BF5B1E0385B2FBF1F2AB +:10C93000521CC285428C01EBC202521EB2FBF1F2C4 +:10C940000284C28B02FB0CF2B2FBF1F1C18370BD19 +:10C9500070B50025044603290DD04FF4FA42002992 +:10C9600063D001297ED0022918BF70BD0146BDE801 +:10C9700070405830ACE604F158068021304608F08B +:10C980009EFEB571F57135737573F57335747571F8 +:10C990007576B576212086F83E00492086F83F005E +:10C9A000FE2086F8740084F82C502584012084F839 +:10C9B000540084F85500282184F856101B21218743 +:10C9C00061874FF4A471E187A1871B2121866186CD +:10C9D0004FF4A471E186A1861B21A4F84010A4F8AD +:10C9E00044104FF4A471A4F84610A4F842101B217F +:10C9F000A4F84A10A4F84C10A4F848106073A4F8E6 +:10CA0000E050202084F8E20084F8D850C4F8DC50CC +:10CA100084F80C5184F80D5184F8165184F817519C +:10CA200084F8FC5084F8085170BD60889049014436 +:10CA3000B1FBF0F1A4F878104BF68031A4F87A102D +:10CA4000E388A4F87E50B4F882C0DB000CFB00FC45 +:10CA50009CFBF0FCB3FBF0F304F15801A4F882C096 +:10CA60005B1C00E021E09BB203FB00FCA4F88030DB +:10CA7000BCF5C84FC4BF5B1E0B85B2FBF0F2521C65 +:10CA8000CA8500F5802202F5EE32531EB3FBF0F2A8 +:10CA90000A84CB8B03FB00F2B2FBF0F0C883214683 +:10CAA00004F15800BDE8704012E6D4F80031B4F843 +:10CAB00002C004F158005989DB89A4F87E50B4F80B +:10CAC0008240DB0004FB0CF4B3FBF1F394FBF1F4C4 +:10CAD0005B1C44859BB203FB01F40385B4F5C84F8E +:10CAE000C4BF5B1E0385B2FBF1F2521CC285428CAF +:10CAF00001EBC202521EB2FBF1F20284C28B02FBB6 +:10CB00000CF2B2FBF1F1C18370BD2DE9F003047E9C +:10CB10000CB1252C03D9BDE8F00312207047002A80 +:10CB200002BF0020BDE8F003704791F80DC01F263A +:10CB30000123504D4FF00008BCF1000F74D0BCF140 +:10CB4000010F1EBF1F20BDE8F0037047B0F800C002 +:10CB50000A7C8F7B91F80F907A404F7C87EA090717 +:10CB600042EA072282EA0C0C5FF000070CF0FF0992 +:10CB70004FEA1C2C99FAA9F99CFAACFC4FEA196906 +:10CB80004FEA1C6C49EA0C2C0CEB0C1C7F1C9444E7 +:10CB9000FFB21FFA8CFC032FE8D38CEA020C354F4E +:10CBA0000022ECFB057212096FF0240502FB05C29E +:10CBB000D2B201EBD207427602F007053F7A03FAC0 +:10CBC00005F52F4218BF82767ED104FB0CF2120CC1 +:10CBD000521CD2B25FF0000400EB040C9CF813C0AE +:10CBE00094453CBFA2EB0C02D2B212D30D194FF008 +:10CBF000000C2D7A03FA0CF73D421CBF521ED2B234 +:10CC0000002A71D00CF1010C0CF0FF0CBCF1080FE4 +:10CC1000F0D304F1010C0CF0FF04052CDCD33046FA +:10CC2000BDE8F0037047FFE790F819C00C7E474657 +:10CC300004FB02C20F4C4FF0000CE2FB054C4FEA24 +:10CC40001C1C6FF024040CFB0422D2B201EBD204B2 +:10CC5000427602F0070C247A03FA0CFC14EA0C0F5B +:10CC60001FBF82764046BDE8F003704704E0000035 +:10CC7000FFDB050053E4B36E90F818C0B2FBFCF480 +:10CC80000CFB1422521CD2B25FF0000400EB040C27 +:10CC90009CF813C094453CBFA2EB0C02D2B212D355 +:10CCA0000D194FF0000C2D7A03FA0CF815EA080F55 +:10CCB0001CBF521ED2B27AB10CF1010C0CF0FF0C69 +:10CCC000BCF1080FF0D300E011E004F1010C0CF00E +:10CCD000FF04052CDAD3A2E70CEBC40181763846B9 +:10CCE000BDE8F0037047FFE70CEBC40181764046D6 +:10CCF000BDE8F0037047FD4A016812681140FC4A24 +:10CD0000126811430160704730B4FA49F74B0024B0 +:10CD10004FF0010C0A78521CD2B20A70202A08BFC8 +:10CD20000C700D781A680CFA05F52A42F2D00978D1 +:10CD300002680CFA01F15140016030BC704770B4D8 +:10CD40006FF01F02010C02EA90251F23A1F5AA40F3 +:10CD500054381CBFA1F5AA40B0F1550009D0A1F587 +:10CD60002850AA381EBFA1F52A40B0F1AA00012020 +:10CD700000D100204FF0000C624664468CEA0106A8 +:10CD8000F6431643B6F1FF3F11D005F001064FEA16 +:10CD90005C0C4CEAC63C03F0010652086D085B08C7 +:10CDA000641C42EAC632162CE8D370BC704770BCD3 +:10CDB00000207047017931F01F0113BF00200022CD +:10CDC0001146704710B4435C491C03F0010C5B082A +:10CDD00003F00104A4445B0803F00104A4445B08CD +:10CDE00003F00104A4445B0803F00104A4445B08BD +:10CDF00003F001045B08A44403F00104A4440CEB19 +:10CE000053031A44D2B20529DDDB012A8CBF01206D +:10CE1000002010BC704730B40022A1F1010CBCF11D +:10CE2000000F11DD431E11F0010F08BF13F8012F91 +:10CE30005C785FEA6C0C07D013F8025F22435C78E1 +:10CE40002A43BCF1010CF7D1491E5CBF405C024390 +:10CE5000002A0CBF0120002030BC7047002A08BF08 +:10CE600070471144401E12F0010F03D011F8013D2C +:10CE700000F8013F520808BF704700BF11F8013C9D +:10CE8000437011F8023D00F8023F521EF6D1704780 +:10CE900070B58CB000F110041D4616460DF1FF3C34 +:10CEA0005FF0080014F8012C8CF8012014F8022D12 +:10CEB0000CF8022F401EF5D101F1100C6C460DF15B +:10CEC0000F0108201CF8012C4A701CF8022D01F8F3 +:10CED000022F401EF6D1204607F0FAF97EB16A1EF5 +:10CEE00004F130005FF0080110F8013C537010F8B5 +:10CEF000023D02F8023F491EF6D10CB070BD089801 +:10CF00002860099868600A98A8600B98E8600CB0DF +:10CF100070BD38B505460C466846FAF760F900283A +:10CF200008BF38BD9DF900202272A07E607294F97E +:10CF30000A100020511A48BF494295F82D308B4203 +:10CF4000C8BF38BDFF2B08BF38BDE17A491CC9B244 +:10CF5000E17295F82E30994203D8A17A7F2918BF43 +:10CF600038BDA2720020E072012038BD0C2818BF25 +:10CF70000B2810D00D2818BF1F280CD0202818BF50 +:10CF8000212808D0222818BF232804D024281EBF17 +:10CF90002628002070474FF0010070470C2963D20B +:10CFA000DFE801F006090E13161B323C415C484EC7 +:10CFB000002A5BD058E0072A18BF082A56D053E051 +:10CFC0000C2A18BF0B2A51D04EE00D2A4ED04BE050 +:10CFD000A2F10F000C2849D946E023B1A2F11000BC +:10CFE0000B2843D940E0122A18BF112A3ED090F8EE +:10CFF000360020B1122A37D31A2A37D934E0162A3C +:10D0000032D31A2A32D92FE0A2F10F0103292DD9E8 +:10D0100090F8360008B31B2A28D925E0002B08BF5A +:10D02000042A21D122E013B1062A1FD01CE0012AD4 +:10D030001AD11BE01C2A1CBF1D2A1E2A16D013E081 +:10D040001F2A18BF202A11D0212A18BF222A0DD04A +:10D05000232A1CBF242A262A08D005E013B10E2A51 +:10D0600004D001E0052A01D000207047012070475C +:10D070002DE9F0410D4604468668F7F7CCFF58B914 +:10D08000F7F7FAFD40F23471F7F7F7FAA06020469F +:10D09000F7F7C1FF0028F3D095B13046A168F8F743 +:10D0A00004FB00280CDD2844401EB0FBF5F707FB0D +:10D0B00005F13046F7F7E1FAA0603846BDE8F081A7 +:10D0C0000020BDE8F08170B50446904228BF70BDD5 +:10D0D000101B642810D325188D4205D8F8F719FBCA +:10D0E00000281CBF284670BD204670BD785C020039 +:10D0F0007C5C0200740100206420ECE710B4B1F8FD +:10D1000002C0A0F840C0B1F806C0A0F844C0B1F811 +:10D1100004C090F85440098914F00C0F15D000BFDA +:10D12000BCF5296F98BF4FF4296C90F8554014F066 +:10D130000C0F11D0B1F5296F98BF4FF42961A0F8F9 +:10D1400042C0A0F8461010BC7047002B1CBF1478DA +:10D1500014F00C0FE4D1E8E7002B1CBF527812F05A +:10D160000C0FE7D1EBE711F00C0F13D001F0040125 +:10D1700000290DBF4022102296214FF4167101F5AF +:10D18000BC71A0EB010388428CBF93FBF2F000203E +:10D1900080B27047022919BF6FF00D0101EBD0007A +:10D1A0006FF00E0101EB9000F2E7C08E11F00C0F52 +:10D1B00008BF7047B0F5296F38BF4FF4296070473A +:10D1C0000246808E11F00C0F08BF704792F8553060 +:10D1D000D18E13F00C0F04D0B1F5296F38BF4FF486 +:10D1E0002961538840F2E24C03FB0CF3528E4FF45A +:10D1F000747C0CEB821C8C459CBF910101F5747111 +:10D20000591AA1F59671884228BF0846B0F5296FD2 +:10D2100038BF4FF429607047084418449830002AFA +:10D2200014BF0421002108447047F0B4002A14BF41 +:10D2300008220122002B14BF0824012412F00C0F35 +:10D240008B8ECA8E25D091F85550944615F00C0F50 +:10D2500004D0BCF5296F38BF4FF4296C4D8840F2DB +:10D26000E2466E434D8E4FF4747707EB85176745A2 +:10D270009CBF4FEA851C0CF5747CA6EB0C0CACF53E +:10D28000967C634528BF6346B3F5296F38BF4FF4DA +:10D29000296314F00C0F04D0B2F5296F38BF4FF496 +:10D2A00029621FFA83FC00280CBF0123002391F898 +:10D2B000560014F00C0F08BF00200CEB02010844CC +:10D2C0009830002B14BF042100210844F0BC7047A3 +:10D2D0002DE9F00391F854200B8E12F00C0F4FF44F +:10D2E00074771CBF07EB83139CB255D012F00C0F60 +:10D2F0008B8ECA8E4D8E91F855C021D016461CF0EB +:10D300000C0F04D0B6F5296F38BF4FF42966B1F879 +:10D31000028040F2E24908FB09F807EB8519B145A4 +:10D3200002D8AE0106F57476A8EB0606A6F5967649 +:10D33000B34228BF3346B3F5296F38BF4FF4296392 +:10D34000A34228BF23469CB21CF00C0F1CBF07EB66 +:10D3500085139BB228D000BF1CF00C0F04D0B2F58F +:10D36000296F38BF4FF429629A4228BF1A46002815 +:10D370000CBF0123002391F856001CF00C0F08BFCE +:10D380000020A11808449830002B14BF042100216C +:10D390000844BDE8F0037047022A07BF9B003C33F6 +:10D3A000DB0070339CB2A1E7BCF1020F07BFAB00FA +:10D3B0003C33EB0070339BB2CEE710F0010F1CBF83 +:10D3C0000120704710F0020F1CBF0220704710F0C0 +:10D3D000040018BF082070472DE9F047044617469F +:10D3E00089464FF00108084600F0C5FC054648464E +:10D3F00000F0C5FC10F0010F18BF012625D000BFBA +:10D4000015F0010F18BF01232AD000BF56EA03010F +:10D4100008BF4FF0000810F0070F08BF002615F0F6 +:10D42000070F08BF002394F85400B0420CBF00203F +:10D430003046387094F85510994208BF00237B702D +:10D44000002808BF002B25D115E010F0020F18BFEF +:10D450000226D5D110F0040F14BF08260026CFE70E +:10D4600015F0020F18BF0223D0D115F0040F14BF1E +:10D4700008230023CAE7484600F087FCB4F8581098 +:10D48000401A00B247F6FE71884201DC002801DC38 +:10D490004FF0000816B1082E0CD018E094F8540094 +:10D4A000012818BF022812D004281EBF0828FFDF59 +:10D4B000032D0CD194F8AC0148B1B4F8B0010128A7 +:10D4C00094F8540006D0082801D00820387040464F +:10D4D000BDE8F087042818BF0420F7D1F5E701283C +:10D4E00014BF0228704710F00C0018BF04207047CA +:10D4F00038B4CBB2C1F3072CC1B2C0F30724012B5F +:10D5000007D0022B09D0042B08BFBCF1040F2DD08B +:10D5100006E0BCF1010F03D128E0BCF1020F25D0D9 +:10D52000012906D0022907D0042908BF042C1DD0E8 +:10D5300004E0012C02D119E0022C17D001EA0C0101 +:10D5400061F3070204EA030161F30F22D1B211F083 +:10D55000020F18BF022310D0C2F307218DF800304C +:10D5600011F0020F18BF02211BD111E0214003EA84 +:10D570000C03194061F30702E6E711F0010F18BF31 +:10D580000123E9D111F0040F14BF08230023E3E7BE +:10D5900011F0010F18BF012103D111F0040118BFD0 +:10D5A00008218DF80110082B01BF000C0128042070 +:10D5B0008DF80000BDF8000038BC70474FF0000C3B +:10D5C000082902D0042909D011E001280FD1042034 +:10D5D000907082F803C0138001207047012806D0A4 +:10D5E0000820907082F803C013800120704700204B +:10D5F0007047162A10D12A220C2818BF0D280FD0E8 +:10D600004FF0230C1F280DD031B10878012818BF26 +:10D61000002805D0162805D000207047012070474B +:10D620001A70FBE783F800C0F8E7012908D0022947 +:10D630000BD0042912BF082940F6A660704707E006 +:10D64000002804BF40F2E240704740F6C410704723 +:10D6500000B5FFDF40F2E24000BD000040787047B7 +:10D6600030B50546007801F00F0220F00F0010439E +:10D670002870092912D2DFE801F00507050705091E +:10D68000050B0F0006240BE00C2409E0222407E020 +:10D6900001240020E87003E00E2401E00024FFDFF5 +:10D6A0006C7030BD007800F00F0070470A68C0F859 +:10D6B00003208988A0F807107047D0F803200A607B +:10D6C000B0F80700888070470A68C0F80920898888 +:10D6D000A0F80D107047D0F809200A60B0F80D00CE +:10D6E000888070470278402322F0400203EA8111CB +:10D6F0001143017070470078C0F3801070470278C2 +:10D70000802322F0800203EAC111114301707047A7 +:10D710000078C009704770B514460E4605461F2AAA +:10D7200088BFFFDF2246314605F1090007F026FFDA +:10D73000A01D687070BD70B544780E460546062C75 +:10D7400038BFFFDFA01F84B21F2C88BF1F242246D2 +:10D7500005F10901304607F011FF204670BD70B594 +:10D7600014460E4605461F2A88BFFFDF2246314673 +:10D7700005F1090007F002FFA01D687070BD09687F +:10D78000C0F80F1070470A88A0F8132089784175F7 +:10D79000704790F8242001F01F0122F01F0211436E +:10D7A00080F824107047072988BF072190F82420AB +:10D7B000E02322F0E00203EA4111114380F8241033 +:10D7C00070471F3008F08FB810B5044600F009FB11 +:10D7D000002818BF204410BDC17811F03F0F1BBFB7 +:10D7E000027912F0010F0022012211F03F0F1BBF3E +:10D7F000037913F0020F002301231A4402EB4202C3 +:10D80000530011F03F0F1BBF027912F0080F0022E6 +:10D81000012203EB420311F03F0F1BBF027912F00C +:10D82000040F00220122134411F03F0F1BBF0279A5 +:10D8300012F0200F0022012202EBC20203EB42038E +:10D8400011F03F0F1BBF027912F0100F00220122CE +:10D8500002EB42021A4411F03F0F1BBF007910F097 +:10D86000400F00200120104410F0FF0014BF0121E0 +:10D8700000210844C0B2704770B50278417802F0C8 +:10D880000F02082A4DD2DFE802F004080B4C4C4C82 +:10D890000F14881F1F280AD943E00C2907D040E045 +:10D8A000881F1F2803D93CE0881F1F2839D8012072 +:10D8B00070BD4A1EFE2A34D88446C07800258209ED +:10D8C000032A09D000F03F04601C884204D8604657 +:10D8D000FFF782FFA04201D9284670BD9CF80300E3 +:10D8E0004FF0010610F03F0F1EBF1CF1040000783E +:10D8F00010F0100F13D064460421604600F071FA56 +:10D90000002818BF14EB0000E6D0017801F03F01B9 +:10D910002529E1D280780221B1EB501FDCD33046BB +:10D9200070BD002070BD70B50178012501F00F01B8 +:10D93000002404290AD007290DD008291CBF002083 +:10D9400070BD40780E2836D0204670BD4078801FCC +:10D950001F2830D9F8E7844640789CF803108A09DC +:10D96000032AF1D001F03F06711C8142ECD86046D9 +:10D97000FFF732FFB042E7D89CF8030010F03F0FEA +:10D980001EBF1CF10400007810F0100F13D0664683 +:10D990000421604600F025FA002818BF16EB0000AD +:10D9A000D2D0017801F03F012529CDD28078022123 +:10D9B000B1EB501FC8D3284670BD10B4017801F0F8 +:10D9C0000F01032920D0052921D14478B0F819107E +:10D9D000B0F81BC0B0F81730827D222C17D1062971 +:10D9E00015D3B1F5486F98BFBCF5FA7F0FD272B16D +:10D9F000082A98BF8A420AD28B429CBFB0F81D0009 +:10DA0000B0F5486F03D805E040780C2802D010BC70 +:10DA10000020704710BC012070472DE9F0411F46DF +:10DA200014460D00064608BFFFDF2146304600F0D1 +:10DA3000D8F9040008BFFFDF30193A462946BDE88F +:10DA4000F04107F09BBDC07800F03F007047C02256 +:10DA500002EA8111C27802F03F021143C17070479F +:10DA6000C07880097047C9B201F00102C1F34003D8 +:10DA70001A4402EB4202C1F3800303EB4202C1F3FA +:10DA8000C00302EB4302C1F3001303EB43031A4448 +:10DA9000C1F3401303EBC30302EB4302C1F3801352 +:10DAA0001A4412F0FF0202D0521CD2B20171C378A4 +:10DAB00002F03F0103F0C0031943C170511C4170D3 +:10DAC00070472DE9F0410546C078164600F03F0446 +:10DAD0001019401C0F46FF2888BFFFDF2819324667 +:10DAE0003946001D07F04AFDA019401C6870BDE8CA +:10DAF000F081C178407801F03F01401A401E80B2A9 +:10DB0000704710B590F803C00B460CF03F01447805 +:10DB10000CF03F0CA4EB0C0CACF1010C1FFA8CF4D4 +:10DB2000944288BF14462BB10844011D2246184672 +:10DB300007F024FD204610BD4078704700B50278FC +:10DB400001F0030322F003021A430270012914BFFB +:10DB50000229002104D0032916BFFFDF012100BDE7 +:10DB6000417000BD00B5027801F0030322F003020A +:10DB70001A430270012914BF0229002104D003298D +:10DB800016BFFFDF012100BD417000BD007800F02D +:10DB900003007047417841B1C078192803D2C04AC8 +:10DBA000105C884201D1012070470020704730B5D9 +:10DBB00001240546C17019293CBFB948445C02D311 +:10DBC000FF2918BFFFDF6C7030BD70B515460E46DB +:10DBD00004461B2A88BFFFDF65702A463146E01CD9 +:10DBE000BDE8704007F0CABCB0F807007047B0F855 +:10DBF00009007047C172090A01737047B0F80B0041 +:10DC0000704730B4B0F80720B0F809C0B0F805305C +:10DC10000179941F40F67A45AC4298BFBCF5FA7F73 +:10DC20000ED269B1082998BF914209D293429FBF91 +:10DC3000B0F80B00B0F5486F012030BC98BF7047BA +:10DC4000002030BC7047001D07F04DBE021D084685 +:10DC5000114607F048BEB0F80900704700797047D8 +:10DC60000A68426049688160704742680A6080685B +:10DC700048607047098881817047808908807047B3 +:10DC80000A68C0F80E204968C0F812107047D0F832 +:10DC90000E200A60D0F81200486070470968C0F88A +:10DCA00016107047D0F81600086070470A68426086 +:10DCB00049688160704742680A60806848607047C0 +:10DCC0000968C1607047C068086070470079704794 +:10DCD0000A68426049688160704742680A608068EB +:10DCE000486070470171090A417170478171090AE2 +:10DCF000C17170470172090A417270478172090A45 +:10DD0000C172704780887047C0887047008970472B +:10DD10004089704701891B2924BF4189B1F5A47F3F +:10DD200007D381881B2921BFC088B0F5A47F0120BB +:10DD30007047002070470A684260496881607047F8 +:10DD400042680A60806848607047017911F0070FE7 +:10DD50001BBF407910F0070F0020012070470179A8 +:10DD600011F0070F1BBF407910F0070F00200120B2 +:10DD70007047017170470079704741717047407971 +:10DD800070478171090AC1717047C088704745A208 +:10DD900082B0D2E90012CDE900120179407901F098 +:10DDA000070269461DF80220012A07D800F0070083 +:10DDB000085C01289EBF012002B07047002002B01D +:10DDC0007047017170470079704741717047407921 +:10DDD000704730B50C460546FB2988BFFFDF6C70E5 +:10DDE00030BDC378024613F03F0008BF70470520DE +:10DDF000127903F03F0312F0010F36D0002914BF4F +:10DE00000B20704712F0020F32D0012914BF801D81 +:10DE1000704700BF12F0040F2DD0022914BF401C20 +:10DE2000704700BF12F0080F28D0032914BF801CD0 +:10DE3000704700BF12F0100F23D0042914BFC01C7C +:10DE4000704700BF12F0200F1ED005291ABF1230F4 +:10DE5000C0B2704712F0400F19D006291ABF401CFB +:10DE6000C0B27047072918D114E00029CAD114E0C4 +:10DE70000129CFD111E00229D4D10EE00329D9D153 +:10DE80000BE00429DED108E00529E3D105E00629ED +:10DE9000E8D102E0834288BF70470020704700004D +:10DEA000805C020000010102010202032DE9F04141 +:10DEB000FC4E0446736893F828000127002528B11A +:10DEC00093F8A001D8B993F84801C0B193F848017C +:10DED00098B383F8A071D3F84C113C2269B36570F4 +:10DEE000201D07F04BFB052020702771706890F80B +:10DEF000A011002918BF80F8485107D034E083F8FA +:10DF0000A05103F12A014FF48E72E7E71D212A3058 +:10DF100007F0B3FB70687F2180F84510FF2180F87F +:10DF2000381080F82B1080F83E10818E21F06001AF +:10DF30002031818680F8285016E0FFE793F8220010 +:10DF4000012814D0187801281BD093F8500101281B +:10DF50001CBF0020BDE8F081657018202070D3F848 +:10DF60005201606083F850510120BDE8F081657076 +:10DF700007202070586A606083F822500120BDE8B5 +:10DF8000F0816570142020702022991C201D07F05C +:10DF9000F5FA257271680D7081F85051C248828877 +:10DFA0008284D0F86421527B80F8262080F8227089 +:10DFB000D1F864010088F4F74FFEF4F7F6FAD3E7DE +:10DFC000B84840680178002914BF80884FF6FF7078 +:10DFD000704770B5B34C0546606890F874112046E0 +:10DFE0000629806803D0FFF73BFDB8B127E0FFF7B3 +:10DFF00037FD10BBA068FFF733FD00BB606890F8E9 +:10E00000A40110F00C0F1AD0A068C17811F03F0FD6 +:10E010001CBF007910F0100F11D00EE0616891F86C +:10E020007401082809D025B191F83E00FF2806D0D8 +:10E0300003E091F82B00FF2801D0012070BD0020E3 +:10E0400070BDF8B5974C07460E46606890F82810EA +:10E05000002906BF90F848110029F8BD00F13305EA +:10E0600020787F2808BFFFDF207828707F2020706D +:10E07000606890F89A1100F5D470085C012808BF18 +:10E08000012508D0022808BF022504D0042816BFA5 +:10E0900008280325FFDF606880F8365090F8971154 +:10E0A00080F8461090F87411072911D190F8A40156 +:10E0B000012808BF012508D0022808BF022504D086 +:10E0C000042816BF08280325FFDF606880F8375052 +:10E0D000606890F874014FF00005062804D1A0682C +:10E0E000FFF7BEFC00283CD0606890F87411082946 +:10E0F00004BF90F8A10102280ED04FF00301A068E0 +:10E10000FFF762FB40B141780A09616881F8382065 +:10E110000088C0F30B0048870095A068FFF7C2FA9B +:10E120006168BDF8005091F83420520962F3461539 +:10E13000ADF80050072818BFFFDF1CD0BDF8000065 +:10E1400000906068BDF8001081860421A068FFF788 +:10E150003BFB00287DD0B0F80100C004C00C79D092 +:10E16000B0E0A068C17811F03F0F1CBF007910F03B +:10E17000100FB9D1D0E791F87401062816D00728FE +:10E1800036D0082873D00A2818BFFFDFD6D145F053 +:10E190000A00ADF8000091F83E10FF2914BF0121DC +:10E1A000002161F38200ADF80000C7E7A068FFF727 +:10E1B00057FC58B1012808BF45F0010046D002289D +:10E1C00014BFFFDF45F0020040D0B7E7A068C17878 +:10E1D00011F03F0F1CBF007910F0020FAED00120EC +:10E1E000FFF7F7FE002808BF45F004002ED0A5E792 +:10E1F000A068FFF735FCB0B1012804BF45F001006D +:10E20000ADF800000FD0022898D145F00200ADF81B +:10E210000000A168CA7812F03F0F1CBF097911F005 +:10E22000020F21D118E0A068C17811F03F0F1CBF88 +:10E23000007910F0020F05D1606890F83E00FF28C9 +:10E240003FF47CAFBDF8000040F00400ADF80000E2 +:10E2500074E72BE02FE00AE0616891F83E10FF2997 +:10E2600008BF20F00400F1D040F00400EEE791F880 +:10E270003E00FF281CBF45F00400ADF8000091F8F7 +:10E28000A1010228BDF800000CBF40F0080020F0FA +:10E290000800ADF800000CBF40F0020020F00200C2 +:10E2A000D4E7000078010020F41000206068818E1F +:10E2B00021F0600105E06068818E21F0600101F1CC +:10E2C00040018186606890F8741106290DD190F89C +:10E2D000A40110F00C0F08D0A068C17811F03F0F16 +:10E2E0001CBF007910F0100F10D1A068C17811F098 +:10E2F0003F0F0BD0017911F0400F07D04FF006010E +:10E30000FFF762FA6168007881F84500606890F86C +:10E310007401062804D00020FFF75BFE18BB04E060 +:10E32000022F18BF012FF6D1F8BDA068C17811F0F7 +:10E330003F0F33D0017911F0010F2FD0616801F147 +:10E340002C0791F8783101F12B05FF2B0CD03A46C0 +:10E3500029461846FDF728FF002808BFFFDF287868 +:10E3600040F00200287019E0FFF7C5F92870A06896 +:10E37000FFF798F9072804D23946A068FFF79DF9FE +:10E380000CE0A068FFF78EF9072807D10021A068EC +:10E39000FFF71AFA016839608088B8800120FFF71A +:10E3A00018FE80BBA068C17811F03F0F2BD0017917 +:10E3B00011F0020F27D0616801F13F0591F8762135 +:10E3C0006F1E1AB1022E18BF032E08D0FFF76AF98C +:10E3D00007280AD22946A068FFF77DF912E0D1F894 +:10E3E0005A012860B1F85E010BE0A068FFF75AF906 +:10E3F000072807D10121A068FFF7E6F90168296025 +:10E400008088A8803E70606890F87401062808BF74 +:10E41000F8BD072818BF082802D00A2806D0F8BD82 +:10E42000A068FFF71DFB022808BFF8BD606800F177 +:10E430004705A068FFF75DFB626892F83230C3F1D0 +:10E44000FF01884228BF084605D9918E21F060015E +:10E4500001F140019186C2B203EB0501A068FFF70C +:10E4600050FB616891F83220104481F83200F8BD09 +:10E470002DE9F047FB4D06466C6894F8280000280B +:10E4800018BFBDE8F0871D212A34204607F0F5F8B3 +:10E4900001272770A868FFF705F920B3012827D0C6 +:10E4A00002282AD0062818BFFFDF2BD004F11D0157 +:10E4B000A868FFF740F92072686804F1020904F1C6 +:10E4C000010890F87801FF2821D04A464146FDF71F +:10E4D0006BFE002808BFFFDF98F8000040F0020044 +:10E4E00088F8000031E0608940F013006081DDE7CA +:10E4F000608940F015006081DEE7608940F010001F +:10E500006081D3E7608940F012006081CEE7A8689F +:10E51000FFF7F1F888F80000A868FFF7C3F80728AC +:10E5200004D24946A868FFF7C8F80EE0A868FFF7CC +:10E53000B9F8072809D10021A868FFF745F9016853 +:10E54000C9F800108088A9F80400287804F10908A7 +:10E550007F2808BFFFDF287888F800004FF07F0988 +:10E5600085F80090277300206073FF20A073A17AC4 +:10E5700011F0040F08BF20752DD0686804F115084C +:10E5800004F1140A90F8761119B1022E18BF032E67 +:10E5900009D0A868FFF786F807280BD24146A8687B +:10E5A000FFF799F815E0D0F85A11C8F80010B0F844 +:10E5B0005E010CE0A868FFF775F8072809D1012172 +:10E5C000A868FFF701F90168C8F800108088A8F86A +:10E5D00004008AF8006084F81B90686890F897112E +:10E5E000217780F82870BDE8F047062003F077BC5B +:10E5F0002DE9F0419B4C606890F82810FF2500271A +:10E60000A1B91D212A3007F038F860687F2180F811 +:10E61000451080F8385080F82B5080F83E50818E9D +:10E6200021F060012031818680F82870606800F553 +:10E63000D47290F89A11895C80F8A411002003F03C +:10E640005EF818B3F8F7DAFC6068874990F879014A +:10E650000E5C3046F8F74DFA606880F8976190F8E4 +:10E66000A41111F00C0F0CBF25200F20F8F74CF966 +:10E67000606890F8A4110120F8F7AFFA606890F88C +:10E680006811032918BF022910D103E0BDE8F04149 +:10E6900001F040B990F89A1100F5D470085C012897 +:10E6A00004D1012211460020F8F7BAFDF8F788FDE1 +:10E6B000606890F8A461012E07BF4FF001080321A4 +:10E6C0004FF000080521A068FDF74CFE616881F855 +:10E6D000760150B1B8F1000F18BF402623D000BF1B +:10E6E000F7F70FFF3046F8F74CFD6068D0F87C0173 +:10E6F000F8F790FC606890F87811FF291CBF00F2D1 +:10E700009110FDF768FD6068062180F8775180F868 +:10E71000785180F8867180F8857180F8A17180F851 +:10E720007411BDE8F08116F00C0F14BF5526502669 +:10E73000D6E770B54B4C0646606800F5BA752046C2 +:10E74000806841B1D0F80510C5F81D10B0F8090077 +:10E75000A5F8210003E005F11D01FEF7AEFFA0685A +:10E76000FEF7C9FF85F82400A0680021032E018070 +:10E7700002D0052E04D046E00321FEF771FF42E0EF +:10E780000521FEF76DFF6068D0F8640100F10E010D +:10E79000A068FEF7F4FF6068D0F8640100F1120190 +:10E7A000A068FEF7F0FFD4E90110D1F86421527D92 +:10E7B0008275D1F86421D28AC275120A0276D1F824 +:10E7C000642152884276120A8276D1F864219288B6 +:10E7D000C276120A0277D1F86421D2884277120AEF +:10E7E0008277D1F864110831FEF7EBFF6068D0F84A +:10E7F0006401017EA068FEF7CCFF606890F8AA1162 +:10E80000A068FEF7D0FF05F11D01A068FEF75CFFD0 +:10E8100095F82410A068FEF772FF606800F5AD75EA +:10E8200090F8596190F8751191B190F86811032929 +:10E8300006D190F86111002918BF90F87A0101D132 +:10E8400090F87701FDF7DDFD00281CBF0126054685 +:10E850002946A068FEF72AFF3146A068BDE870404F +:10E86000FEF740BF780100209C5C0200FD4949682A +:10E8700081F87301704770B5FA4D686890F87411AB +:10E8800002291FBF90F8741101290C2070BD00F1FE +:10E8900066014FF00004C0F84C1180F848414FF079 +:10E8A0001D0100F12A0006F0E8FE68687F2180F86B +:10E8B0004510FF2180F8381080F82B1080F83E10AA +:10E8C000818E21F060012031818680F8284004701B +:10E8D00080F8224080F85041012680F8A06190F82D +:10E8E000760130B1F8F757FCF7F71FFE686880F83B +:10E8F00076416868072180F8724180F8616180F88C +:10E90000684180F8794180F8734180F8A14180F82E +:10E910006011002070BDD34910B58860486800219F +:10E92000A0F8A51180F8A711012180F87411FFF754 +:10E93000A2FF002818BFFFDF10BD2DE9F041C94D2F +:10E940000446686890F87401012818BF022804D0B2 +:10E9500003281CBF0C20BDE8F081607A022823D078 +:10E96000F8F714F80220F8F74FFB686890F9730184 +:10E97000F8F7B1F8A868F8F74AFBBB48F8F72AFBA4 +:10E98000BA48F8F7AEF8686890F8591100F5AD701C +:10E99000F8F759F80F210720F8F771F8686890F830 +:10E9A0006101F0B1FDF7A0FC6868217A00F5D4722E +:10E9B00080F89A11217A895C80F8A4116168C0F806 +:10E9C0007C112168C0F88011627A6AB1012A23D0D3 +:10E9D0000524022A08BF80F8744175D0032A7FD02D +:10E9E00087E0FDF73CFCDFE7A14C90F860C1002117 +:10E9F00090F87921521CA4FB02635B08A3EB83030C +:10EA00001A4480F879212CFA02F212F0010F03D196 +:10EA1000491CC9B20329EBD3002680F8A16190F804 +:10EA20007111002904BF90F87501002848D0F6F74D +:10EA300023F9044668682146D0F86C01F6F735FEE4 +:10EA4000DFF83082074690FBF8F008FB1070414277 +:10EA50002046F5F712FE6968C1F86C0197FBF8F0E3 +:10EA6000D1F89C211044C1F89C01FDF775FB6A6840 +:10EA7000D2F89C11884223D8C2F89C61C2F86C413C +:10EA800092F8750100281CBF0120FDF787FC0121C9 +:10EA9000686890F87221002A1CBF90F87121002A42 +:10EAA0000ED090F8592100F5AD73012A04D15A799E +:10EAB00002F0C002402A09D000F5AD70F9F7F2F873 +:10EAC0006968042081F8740113E009E00124FDF76E +:10EAD00096FC6968224601F5AD71F9F7ACF8EFE7ED +:10EAE000002918BFFFDF012000F066FF686880F88A +:10EAF00074410020BDE8F08170B55A4C606890F810 +:10EB00007411042932D005291CBF0C2070BD90F867 +:10EB1000A1110026002900F2A51190F8A7114FEAD3 +:10EB2000511126D0002908BF012507D0012908BFAF +:10EB3000022503D0022914BF00250825D0F8800142 +:10EB400000281CBF002000F037FF6068D0F87C016F +:10EB5000F8F760FA606890F8681102293DD003293F +:10EB600004BF90F8900101283BD03FE0FFF740FD43 +:10EB700044E0002908BF012507D0012908BF02256C +:10EB800003D0022914BF00250825D0F880010028F1 +:10EB90001CBF002000F010FF6068D0F87C01F8F77F +:10EBA00039FA606890F86811022906D0032904BF79 +:10EBB00090F89001012804D008E090F89001022814 +:10EBC00004D12A4601210020F8F72AFB60680721BA +:10EBD00080F8A45180F885610EE090F89001022839 +:10EBE00004D12A4601210020F8F71AFB60680821A9 +:10EBF00080F8A45180F8856180F87411002070BD00 +:10EC00001849002210F0010F496802D0012281F852 +:10EC1000A82110F0080F03D01144082081F8A801A2 +:10EC2000002070470F49496881F87001704710B59E +:10EC30000C4C636893F85831022B14BF032B002847 +:10EC40000BD100291ABF0229012000201146FDF72F +:10EC500086FA08281CBF012010BD606890F8580192 +:10EC6000002809E078010020995C02009F5C020006 +:10EC7000ABAAAAAA40420F0016BF0228002001201A +:10EC8000BDE81040F8F798BFFE48406890F858017A +:10EC9000002816BF022800200120F8F78DBFF9498F +:10ECA000496881F858017047F649496881F872014E +:10ECB000704770B5F34C616891F85801002816BF91 +:10ECC00002280020012081F8590101F5AD71F8F703 +:10ECD0005DFF606890F85811022916BF03290121D1 +:10ECE000002180F8751190F8592100F5AD734FF0AF +:10ECF0000005012A04BF5B7913F0C00F0AD000F5AC +:10ED0000AD73012A04D15A7902F0C002402A01D021 +:10ED1000002200E0012280F87121002A04BF0029AE +:10ED200070BDC0F89C51F5F7A7FF6168C1F86C0190 +:10ED300091F8750100281CBF0020FDF72FFB00266D +:10ED4000606890F8721100291ABF90F871110029BB +:10ED500070BD90F8592100F5AD71012A04D14979AF +:10ED600001F0C001402906D02946BDE8704000F5F9 +:10ED7000AD70F8F797BFFDF742FB61683246BDE81A +:10ED8000704001F5AD71F8F756BF70B5BD4D0C463A +:10ED900000280CBF01230023696881F8613181F8E4 +:10EDA0006A014FF0080081F87A010CD1002C1ABFDB +:10EDB000022C012000201146FDF7D1F969680828CE +:10EDC00081F87A0101D0002070BD022C14BF032C01 +:10EDD0001220F8D170BD002818BF112070470328F9 +:10EDE000A84A526808BFC2F8641182F8680100207E +:10EDF000704710B5A34C606890F8681103291CBFD8 +:10EE0000002180F8841101D0002010BD0123D0F82A +:10EE100064111A460020FEF708FA6168D1F86421EF +:10EE2000526A904294BF0120002081F88401EBE7F0 +:10EE30009448416891F86801032804D0012818BF5C +:10EE4000022807D004E091F86A01012808BF704742 +:10EE50000020704791F86901012814BF03280120A0 +:10EE6000F6D1704770B5F8F780F9F8F75FF9F8F761 +:10EE700037F8F8F7B5F8834C0025606890F876010C +:10EE800030B1F8F788F9F7F750FB606880F87651F1 +:10EE900060680121A0F8A55180F8A75180F874118D +:10EEA00080F85051002070BD764810B5406800F5DC +:10EEB000C47006F0A8F8002010BD72480121406817 +:10EEC00090F86821032A03BF80F85211D0F864211A +:10EED0001288002218BF80F85221A0F8542180F82F +:10EEE000501170476749496881F8AA017047017855 +:10EEF000002311F0010F634949680AD04278032AC0 +:10EF000008BFC1F8643181F86821012281F8A82185 +:10EF10001346027812F0040F0CD082784FF0000CE8 +:10EF2000032A08BFC1F864C181F868210B44082294 +:10EF300083F8A821C27881F858210279002A16BFE7 +:10EF4000022A0123002381F8613181F86921427985 +:10EF500081F86021807981F870014FF000007047DE +:10EF60004848406800F5D27070472DE9F041454CA3 +:10EF700005460E46606890F87401032818BFFFDF4D +:10EF8000022D1EBF032DFFDFBDE8F0814FF000070B +:10EF90004FF00105AEB1606890F8371089B1818EED +:10EFA00021F0600101F14001818690F8282042B9EA +:10EFB00080F8285011F0080F14BF0720062002F037 +:10EFC0008EFF6068A0F8A57180F8A77180F8745171 +:10EFD000BDE8F08100F09EBC2DE9F047294C0646C3 +:10EFE000894660684FF00108072E90F8617138BFBC +:10EFF000032533D3082E4FF0000088BFBDE8F0870B +:10F00000FEF7E7FF002878D1A068C17811F03F0F24 +:10F0100012D0027912F0010F0ED061684FF0050591 +:10F0200091F87621002A18BFB9F1000F16D091F897 +:10F03000A411012909D011E011F03F0F1ABF007986 +:10F0400010F0100F002F58D151E04FF001024FF097 +:10F050000501FDF7CCF8616881F87601A1680878B0 +:10F060002944C0F3801030B1487900F0C000402836 +:10F0700008BF012000D00020616891F876110029B6 +:10F0800002E000007801002018BF002807D0FDF73B +:10F09000C9F80146606880F8771180F8858160685A +:10F0A00090F87711FF292BD080F878110846FDF7EA +:10F0B000C6F840EA0705606890F87721FF2A18BF74 +:10F0C000002D10D0072E0ED3A068C17811F03F0F8D +:10F0D00009D0017911F0020F05D00B21FDF734F9A9 +:10F0E000606880F886812846BDE8F08705E0FCF777 +:10F0F00072FE002808BFBDE8F0870120BDE8F08758 +:10F10000A36890F8612159191B78C3F3801C00F2A1 +:10F1100077136046FCF7C3FE0546CCE72DE9F041C6 +:10F12000FE4C84B0A068FEF79BFC0126002550B180 +:10F13000022501287ED002287DD0F7F7D1FE04B049 +:10F140000620BDE8F081F7F7CBFE606890F8680113 +:10F15000032800F0C480A068C17811F03F0F05D0EB +:10F16000027912F0100F18BF012600D10026002EE0 +:10F1700014BF0822012211F03F0F43D0007932EA78 +:10F1800000013FD110F0020F06D00120FEF721FF51 +:10F19000002808BF012000D000208DF800508DF815 +:10F1A00004508DF80850FF27D0B102AA694601A883 +:10F1B00000F051FC606890F859719DF8000000283B +:10F1C00018BF47F002070BD1A068FEF7A1FA8046EE +:10F1D0000121A068FEF7F8FA4146F7F73CFC90B130 +:10F1E00066B1012000F0B9FB002878D03946002034 +:10F1F000FEF727FF606880F890516CE039460020E8 +:10F2000000F06CFB6BE0606890F86901032818BFA0 +:10F21000022864D19DF80400002860D09DF8000009 +:10F2200000285CD17EB1012000F097FB002856D069 +:10F23000FE2101E00CE032E00020FEF702FF6068F2 +:10F2400080F8905147E0FE21002000F047FB46E0A7 +:10F25000F7F746FEA0681821C27812F03F0F3ED0A3 +:10F26000027991433BD10421FEF7AEFA616891F82F +:10F270006821032A01BF8078B5EB501F91F8840103 +:10F2800000282CD04FF0010000F067FB38B3FF21BD +:10F290000120FEF7D6FE606880F890611BE0F7F76A +:10F2A0001FFE606890F86801032818D0A068182134 +:10F2B000C27812F03F0F12D0007931EA00000ED16F +:10F2C000012000F04AFB50B1FF210220FEF7B9FEF9 +:10F2D000606880F8905104B00320BDE8F08104B06C +:10F2E0000620BDE8F081F0B58C4C074683B060681D +:10F2F0006D460078002818BFFFDF002661688E7019 +:10F30000D1F8640102888A8042884A8382888A838D +:10F31000C088C88381F8206047B10121A068FEF74A +:10F3200053FA0546A0680078C10907E06946A0685D +:10F33000FEF7C3F9A0680078C0F380116068012768 +:10F3400090F87521002A18BF002904D06A7902F0CC +:10F35000C002402A26D090F87221002A18BF002946 +:10F3600003D0697911F0C00F1CD000F10E0006F037 +:10F37000B1FA616891F87801FF2819D001F108020B +:10F38000C91DFCF711FF002808BFFFDF6068C179C5 +:10F3900041F00201C171D0F891114161B0F89511AD +:10F3A000018310E02968C0F80E10A9884182E0E7C7 +:10F3B000D1F86401427ECA71D0F81A208A60C08BED +:10F3C00088814E610E8360680770D0F8642190F8E0 +:10F3D000731182F85710D0F864010088F3F73CFCF1 +:10F3E000F3F7D4F803B0F0BD2DE9F0414B4C0546DE +:10F3F00001276068002690F86811012918BF0229CA +:10F4000002D0032918BFFFDF55B1A068FEF734FA18 +:10F4100018B9A068FEF787FA10B100F0C6FB2DE01E +:10F42000606890F874017F25801F062828BFBDE81A +:10F43000F081DFE800F003191930443E3748F7F750 +:10F44000CEFE002808BF2570F7F7B0FE606890F880 +:10F45000760130B1F7F79FFEF7F767F8606880F83C +:10F460007661F7F73DFD20E02C48F7F7B8FE00285D +:10F4700008BF2570F7F79AFE00F07DFB102880F09A +:10F480004481DFE800F036B9C2C6F7F712CFF6F7CD +:10F49000F7F7249F386C2148F7F7A1FE002808BF32 +:10F4A0002570F7F783FEF7F71BFDBDE8F041FFF786 +:10F4B0009FB81A48F7F793FE30B9257004E0174853 +:10F4C000F7F78DFE0028F8D0F7F770FE9DE00320D7 +:10F4D00002F015F9002874D000210320FFF729F964 +:10F4E000012211461046F7F79BFE61680C2081F857 +:10F4F0007401BDE8F081606800F5BA75042002F07F +:10F50000FEF800285DD00E202870012002F0E7FCF4 +:10F51000A06861680078C0F3401001E07801002025 +:10F5200081F8990100210520FFF703F9F749A06848 +:10F530004FF0200CD1F864210378527B23F0200394 +:10F540000CEA42121A430270D1F8640195F8253092 +:10F55000427B1A4042732820D1F864112DE0062026 +:10F5600002F0CDF8002850D0E84D0F2085F8740146 +:10F57000022002F0B4FC6068012190F8A421084642 +:10F58000F7F74EFEA06861680078C0F3401081F87C +:10F59000990101210520FFF7CCF8D5F864014773E4 +:10F5A000A068017821F020010170F8F720FA002806 +:10F5B00018BFFFDF2820D5F8641181F85600BDE898 +:10F5C000F08122E0052002F09AF8F0B10121032039 +:10F5D000FFF7AFF8F8F70BFA002818BFFFDF6068F5 +:10F5E000012190F8A4210846F7F71AFE61680D2062 +:10F5F00081F87401BDE8F0816068A0F8A56180F829 +:10F60000A76180F87471BDE8F081BDE8F04100F0B9 +:10F6100081B96168032081F87401BDE8F0410820D8 +:10F6200002F05DBC606890F8A711490908BF012588 +:10F6300007D0012908BF022503D0022914BF0025E5 +:10F640000825D0F8800100281CBF002000F0B4F984 +:10F650006068D0F87C01F7F7DDFC606890F868110D +:10F66000022908D0032904BF90F89001012806D090 +:10F670000AE010E049E090F89001022804D12A46FF +:10F6800001210020F7F7CCFD6068072180F8A45124 +:10F6900080F8856135E0606890F8A711490908BFD6 +:10F6A000012507D0012908BF022503D0022914BF74 +:10F6B00000250825D0F8800100281CBF002000F09C +:10F6C0007BF96068D0F87C01F7F7A4FC606890F8DB +:10F6D0006811022906D0032904BF90F8900101287F +:10F6E00004D008E090F89001022804D12A460121B4 +:10F6F0000020F7F795FD6068082180F8A45180F894 +:10F70000856180F87411BDE8F081FFDFBDE8F0810C +:10F7100070B57F4C606890F8743100210C2B38D0A4 +:10F7200001220D2B40D00E2B55D00F2B1CBFFFDF1D +:10F7300070BD042002F0D3FB606890F8A4110E2085 +:10F74000F7F7E2F8606890F8A40110F00C0F14BF0E +:10F75000282100219620F7F77BFCF7F731FD606840 +:10F76000052190F8A451A068FCF7FCFD616881F8C0 +:10F77000760148B115F00C0F0CBF50255525F6F752 +:10F78000C0FE2846F7F7FDFC61680B2081F8740184 +:10F7900070BDF7F715FD00219620F7F759FC616859 +:10F7A000092081F8740170BD90F8A411FF20F7F7CB +:10F7B000ABF8606890F8A40110F00C0F14BF28217A +:10F7C00000219620F7F744FCF7F7FAFC61680A205D +:10F7D00081F8740170BDA0F8A51180F8A71180F818 +:10F7E00074210020FFF77FFDBDE87040032002F088 +:10F7F00076BB70B5464C606890F874117F25891F00 +:10F80000062928BF70BDDFE801F017321D033D1146 +:10F810003F48F7F7E4FC002808BF2570F7F7C6FC5F +:10F82000F7F75EFBBDE87040FEF7E2BE3848F7F739 +:10F83000D6FC60BB25702AE03548F7F7D0FCD8B974 +:10F84000257019E090F8371089B1818E012221F0DE +:10F8500060014031818690F8283043B980F8282033 +:10F8600011F0080F14BF0720062002F038FB2848CB +:10F87000F7F7B5FC0028E3D0F7F798FCBDE8704037 +:10F8800000F048B82248F7F7AAFC0028D2D0F7F7D2 +:10F890008DFC6068002100F5C47005F065FBBDE8D3 +:10F8A000704000F037B870B5194C06460D46012976 +:10F8B00008D0606890F8A4213046BDE87040134637 +:10F8C00002F059BBF6F7D6FF61680346304691F85F +:10F8D000A4212946BDE8704002F04DBB10B5FEF7EB +:10F8E000B0FB0B48406890F82810002918BF10BDE5 +:10F8F000012280F8282090F8340010F0080F14BF7F +:10F9000007200620BDE8104002F0E9BAF4100020FC +:10F910007801002070B5F7F728FCF7F707FCF7F738 +:10F92000DFFAF7F75DFBFE4C0025606890F8760182 +:10F9300030B1F7F730FCF6F7F8FD606880F87651E3 +:10F940006068022180F87411A0F8A55180F8A751D1 +:10F95000BDE87040002002F0C2BA70B5F04D064616 +:10F960000421A868FDF730FF0446686890F8280075 +:10F97000A0B901F0A7FE217811F0800F14BF4FF459 +:10F9800096711E21B4F80120C2F30C0212FB01F1A2 +:10F990000A1AB2F5877F28BF814201D2002070BDCC +:10F9A00068682188A0F8A511A17880F8A7113046D1 +:10F9B000BDE8704001F0A3BE2DE9F041D84C0746E8 +:10F9C000606800F2A51690F8A701400908BF01255C +:10F9D00007D0012808BF022503D0022814BF002544 +:10F9E0000825F7F70BFB307800F03F063046F7F7B5 +:10F9F00080F8606880F8976190F8900102280CBF49 +:10FA00004020FF202946F6F77FFF27B12946012035 +:10FA1000F7F763F906E060682A46D0F88011012004 +:10FA2000F7F7A4F9F7F7CCFB0521A068FCF79AFCDF +:10FA30006168002881F8760108BFBDE8F08115F003 +:10FA40000C0F0CBF50245524F6F75BFD2046BDE893 +:10FA5000F041F7F796BB2DE9F74FB14C00259146E1 +:10FA600060688A4690F8750100280CBF4FF00108C5 +:10FA70004FF00008A0680178CE090121FDF7A4FE2F +:10FA800036B1407900F0C000402808BF012600D000 +:10FA90000026606890F87611002963D090F868110C +:10FAA0004FF0000B03291ED190F86111002918BFF7 +:10FAB00090F87A7117D0FF2F18BF082F22D0384640 +:10FAC000FCF730F9002818BF4FF00108002E49D08C +:10FAD000606890F88601D0B1FCF7AFFB054660681E +:10FAE00080F886B13EE0A168CA7812F03F0F19BFD6 +:10FAF000097911F0010F90F82B10FF2918BF90F829 +:10FB00007771D8D176B390F8850170B12AE0384684 +:10FB1000FCF741FB05460121A068FDF755FE0146B3 +:10FB20002846F8F757F805461CE0A068C17811F0A0 +:10FB30003F0F05D0017911F0010F18BF0B2101D142 +:10FB40004FF005014FF00002FCF751FB616881F8AE +:10FB5000760138B1FCF766FBFF2803D06168012508 +:10FB600081F877018AF800500098067089F80080C3 +:10FB700003B0BDE8F08F6A4810B5406890F83710C0 +:10FB800089B1818E012221F060014031818690F897 +:10FB9000283043B980F8282011F0080F14BF07203F +:10FBA000062002F09CF9022010BD2DE9F04F5C4DBB +:10FBB00083B00024686890F874017F27801F264670 +:10FBC0004FF00108062880F04082DFE800F00308CB +:10FBD0000893FEFD00F01EFC044600F037BA5048C2 +:10FBE000F7F7FDFA002808BF2F70F7F7DFFAA868CB +:10FBF000FDF758FD044607286AD1A868FDF730FFD5 +:10FC0000696891F89021824262D191F874010628C6 +:10FC100004D1A868FDF724FF002836D0686890F862 +:10FC20007411082904BF90F8A101022813D04FF0E5 +:10FC30000301A868FDF7C8FD002849D0696843782A +:10FC400091F83820B2EB131F42D10088498FC0F3DE +:10FC50000B0088423CD100212046FFF7BDF9B0B32C +:10FC60008DF800608DF804608DF80860A868FF24A6 +:10FC7000C17811F03F0F1CBF007910F0020F1CD0AB +:10FC80000120FEF7A6F950B117E0A868C17811F07D +:10FC90003F0F1CBF007910F0100FBFD1DBE702AAA5 +:10FCA000694601A8FFF7D7FE686890F859419DF8AA +:10FCB0000000002818BF44F0020423469DF80820E5 +:10FCC0009DF804109DF8000000F012FA02E0FFE732 +:10FCD000FFF751FF0446686890F87601002800F0AD +:10FCE000B581F7F758FAF6F720FC686880F8766176 +:10FCF00000F0ACB9A868FDF7D5FC8146A968686832 +:10FD0000CA7890F891319A4224D10A7990F89231C8 +:10FD10009A421FD14A7990F893319A421AD101E060 +:10FD2000780100208A7990F894319A4212D1CA79E8 +:10FD300090F895319A420DD10A7A90F896319A420C +:10FD400008D1097890F89801C1F38011814208BF69 +:10FD5000012400D00024F7F7C3F8FB48F7F73FFA77 +:10FD6000002808BF2F70F7F721FAB9F1040F76D1F8 +:10FD7000002C74D0686890F8481100296FD190F871 +:10FD8000281021B190F8341011F0100F67D0D0F87E +:10FD90004C411D21204605F070FC84F80080686805 +:10FDA00004F1020A04F1010990F87801FF2810D04B +:10FDB00052464946FCF7F8F9002808BFFFDF99F8DA +:10FDC000000040F0020001E04CE0FFE089F8000094 +:10FDD0001DE0A868FDF78FFC89F80000A868FDF712 +:10FDE00061FC072804D25146A868FDF766FC0EE0C6 +:10FDF000A868FDF757FC072809D10021A868FDF77E +:10FE0000E3FC0168CAF800108088AAF8040004F135 +:10FE10001D01A868FDF78FFC2072287804F10909FC +:10FE20007F2808BFFFDF287889F800002F706868F6 +:10FE3000618990F8A12162F3000141F01A0161810A +:10FE400084F80C806673FF21A1732175E77690F822 +:10FE50009711217780F84881072002F040F80624A6 +:10FE600000F0F4B84FF00208B748F7F7B8F90028E7 +:10FE700008BF2F70F7F79AF9A868FDF713FC04463E +:10FE8000A868FDF7EDFD082C08BF00287ED1A86802 +:10FE90004FF00301C27812F03F0F77D0007931EABA +:10FEA000000073D1686800F5BA7790F86101002806 +:10FEB00014BFBE79FE784FF00009B87878B1FCF72E +:10FEC000B1F90446FF280AD00146A868401DFCF796 +:10FED00082F9B4420CBF4FF001094FF00009002134 +:10FEE000A868FDF771FC062207F11D0105F01AFB59 +:10FEF00040B9A868FDF7FFFB97F82410884208BFB7 +:10FF0000012000D0002059EA00095DD0686800F5A2 +:10FF1000AD7490F859A1787838B13046FCF771FA91 +:10FF200000281CBF04464FF0010A0027A86801788A +:10FF30004FEAD11B0121FDF747FCBBF1000F07D0B1 +:10FF4000407900F0C000402808BF4FF0010B01D0FD +:10FF50004FF0000B0121A868FDF736FC0622214670 +:10FF600005F0E0FA30B9A868FDF7D2FB504508BFAC +:10FF7000012401D04FF000043BEA040018BFFF2E1B +:10FF80000FD03046FCF707F9060000E01CE008D06F +:10FF90000121A868FDF718FC01463046F7F71AFE64 +:10FFA000074644EA070019EA000F0DD068680121EE +:10FFB00000F5C47004F0D8FF4FF001084046FFF789 +:10FFC00092F9052001F08BFF44463FE002245E4891 +:10FFD000F7F705F9002808BF2F70F7F7E7F8A868CA +:10FFE000FDF760FB0646A868FDF73AFD072E08BF3F +:10FFF00000282BD1A8684FF00101C27812F03F0F02 +:020000040002F8 +:1000000024D00279914321D1696801F5BA760021A3 +:10001000FDF7DAFB062206F11D0105F083FAA8B907 +:10002000A868FDF768FB96F8241088420ED168682E +:10003000012100F5C47004F097FFFF21022000F0B9 +:1000400009F8002818BF032400E0FFDF03B02046B2 +:10005000BDE8F08F2DE9F0413B4C02460025606879 +:1000600090F8A1310BB3A0684FF000064FF00107E4 +:10007000C37813F03F0F1CBF007910F0100F1BD096 +:100080000020FDF7DEFF606890F83400C0F34110F7 +:1000900002281BD00220FFF760FC88B160680125B0 +:1000A00080F89061F6F71CFF1FE0002A14BF0223BE +:1000B000012380F8A131D6E71046FDF7C2FF05E025 +:1000C0006068818E21F0600140318186606890F81F +:1000D000281051B980F8287090F8340010F0080FFB +:1000E00014BF0720062001F0FAFE2846BDE8F08183 +:1000F0002DE9F047144C05461F4690460E46A06871 +:10010000FDF7AEFC002800F0D180012805D00228C0 +:1001100000F00E81BDE8F0472DE5A0680921C27806 +:1001200012F03F0F00F042810279914340F03E818E +:10013000616891F86811032908D012F0020F08BF16 +:10014000FF211BD075B118E0780100200021FDF7D8 +:100150003BFB61680622D1F864111A3105F0E2F91F +:1001600050BB1EE0FDF7D4FA05460121A068FDF75B +:100170002BFB2946F6F76FFC18B13946012000F039 +:1001800039B9606890F86901032818BF022840F067 +:100190000D81002E1CBFFE21012040F02B8100F0BC +:1001A00005B9A068FDF7A7FA6168D1F86411497E26 +:1001B000884208BF012600D00026A068C17811F04F +:1001C0003F0F05D0017911F0020F01D05DB338E087 +:1001D000616891F86A21012A01D0A6B119E0C6B977 +:1001E0000021FDF7F1FA61680268D1F86411C1F8E5 +:1001F0001A208088C883A068FDF77DFA6168D1F86D +:100200006411487605E091F8770191F87A118842F7 +:100210004BD1606800F5C47004F0EAFE002844D0B9 +:100220000F20BDE8F087B8F1000F0CD0FDF770FA91 +:1002300005460121A068FDF7C7FA2946F6F70BFC31 +:1002400008B1012200E00022616891F86A010128EA +:1002500007D040B92EB991F8773191F87A118B42D5 +:1002600001D1012100E000210A421ED0012808BF6F +:10027000002E13D14FF00001A068FDF7A5FA6168C8 +:100280000268D1F86411C1F81A208088C883A06878 +:10029000FDF731FA6168D1F864114876606800F5BD +:1002A000C47004F0A5FE0028BAD17FE06068A846BB +:1002B0004FF0020990F8680103282AD0A068C1789D +:1002C00011F03F0F1BBF007910F0020F002001203A +:1002D0004FF0FF05A8B14FF00100FDF77AFE0028AE +:1002E00004BF3D46B8F1000F0BD1A068FDF710FA2E +:1002F00007460121A068FDF767FA3946F6F7ABFB20 +:1003000050B129460020FFF7A5FE002818BF4FF086 +:1003100003094846BDE8F087606890F86901032842 +:1003200018BF0228F5D1002E18BFFE25E9D1F0E74D +:10033000626892F86831032B38D0A0684FF0090C3E +:10034000C17811F03F0F31D001793CEA010C2DD179 +:10035000022B01F0020105D0002908BFFF2147D080 +:10036000CDB344E009B135B113E002F5C47004F037 +:100370003FFEA0B91AE0B8F1000F1AD0FDF7C8F996 +:1003800005460121A068FDF71FFA2946F6F763FB31 +:1003900078B1606800F5C47004F02AFE30B13946C7 +:1003A0000220FDF74EFE0D20BDE8F0870220BDE8DB +:1003B000F087606890F86901032818BF0228F5D11A +:1003C000002EF3D04FF0FE014FF00200FFF786FA47 +:1003D0000220BDE8F087FFE7FDF79AF90546012105 +:1003E000A068FDF7F1F92946F6F735FB28B1394643 +:1003F0005FF00200FFF772FAD8E7606890F86901D1 +:10040000032818BF0228D1D1002E1CBFFE210220D4 +:10041000F0D1CBE72DE9F84F0027D048F6F7DFFE03 +:10042000CE4C002804BF7F202070F6F7BFFEA068E6 +:10043000FDF738F980460121FEF7CEFD61684FF0E7 +:10044000000B91F8A421012A13D0042A1CBF082A0A +:10045000FFDF00F07781606890F8760130B1F6F741 +:100460009AFEF6F762F8606880F876B13846BDE823 +:10047000F88F0125BA4EB8F1080F19D2DFE808F05D +:1004800024860418181811FD0546F6F729FD002DDD +:100490007AD0606890F86801012818BF022858D007 +:1004A00072E028B191F86801022805D0012850D0E7 +:1004B000F6F716FD0627CEE7FF20FDF7D9FF6068A7 +:1004C0000C2780F8A1B1C6E70027002800F02081A2 +:1004D00091F86801022834D001283AD00328BAD113 +:1004E000A068D1F86421C37892F81AC0634521D17D +:1004F000037992F81BC063451CD1437992F81CC064 +:10050000634517D1837992F81DC0634512D1C37931 +:1005100092F81EC063450DD1037A92F81FC063455F +:1005200008D1037892F819C0C3F38013634508BF5C +:10053000012300D0002391F86A1101290DD0D3B115 +:10054000E4E0FF20FDF794FF60680C2780F8A151DC +:1005500081E7FF20FDF78CFF16E0002B71D102F13F +:100560001A01FDF7AAF8A068FDF7C5F86168D1F88F +:1005700064114876CAE096F87A0108287CD096F88B +:10058000771181425DD0C3E0062764E7054691F804 +:10059000750100280CBF4FF001094FF0000900273A +:1005A000A06810F8092BD20907D0407900F0C000EC +:1005B000402808BF4FF0010A01D04FF0000A91F81F +:1005C0006801032806D191F86101002818BF91F84D +:1005D0007A0101D191F877010090FBF7DCFD5FEA29 +:1005E00000082AD00098FBF79DFB002818BF4FF0A9 +:1005F0000109BAF1000F20D0A06800F109014046BE +:10060000F7F7E8FA0700606890F8598118BF48F0DA +:100610000208606890F86811032913D0F6F760FCAF +:10062000002DB1D0F6F727FA00280CBF002F404666 +:1006300072D000BFFDF71CFFA6E7606890F85981F3 +:10064000E7E763E0A168D0F86401CA78837E9A4244 +:100650001FD10A79C37E9A421BD14A79037F9A42FD +:1006600017D18A79437F9A4213D1CA79837F9A42FC +:100670000FD10A7AC37F01E04AE05BE09A4208D1D9 +:100680000978407EC1F38011814208BF4FF0010814 +:1006900001D04FF0000896F87701082806D096F8A8 +:1006A0007A11884208BF4FF0010A01D04FF0000ACA +:1006B0002FB9B9F1000F04D0F6F7DDF908B1012028 +:1006C00000E000204DB196F86A11012903D021B94C +:1006D00058EA0A0101D0012100E00021084217D0A8 +:1006E000606890F86A11012908BFB8F1000F0DD1B8 +:1006F000D0F8640100F11A01A068FCF7DEFFA068E1 +:10070000FCF7F9FF6168D1F8641148760E27A2E67C +:10071000F6F7E6FB38E7FFE7606890F86901032821 +:1007200018BF02287FF430AFBAF1000F18BFFE20C7 +:1007300080D129E791F87011002918BF00283FF4F3 +:10074000B7AE06E0B8F1070F7FF4B2AE00283FF471 +:10075000AFAEFEF7E3FC07467DE60000780100201F +:10076000F4100020D0F8E81049B1D0E93B231A4436 +:100770008B691A448A61D0E93912D16003E0F74AE3 +:10078000D0F8E4101162D0E9391009B1086170475E +:100790000028FCD00021816170472DE9FF4F0646FB +:1007A0000C46488883B040F2E24148430190E08A19 +:1007B000002500FB01FA94F8640090460D2822D031 +:1007C0000C2820D024281ED094F8650024281AD0A4 +:1007D00000208346069818B10121204603F000F955 +:1007E00094F8541094F85500009094F8D8200F46CF +:1007F0004FF47A794AB1012A61D0022A44D0032AFF +:100800005DD0FFDFB5E00120E3E7B8F1000F00D1D4 +:10081000FFDFD24814F8541F243090F83800FCF75A +:1008200004FF01902078F7F75EF84D4600F2E730BC +:10083000B0FBF5F1DFF82493D9F80C0001EB0008C8 +:100840002078F7F750F8014614F85409022816D01A +:10085000012816D040F6340008444AF2EF0108445B +:10086000B0FBF5F10198D9F81C20411A514402EB74 +:1008700008000D18012084F8D8002D1D78E02846C6 +:10088000EAE74FF4C860E7E7DFF8D092A8F101008B +:10089000D9F80810014300D1FFDFB148B8F1000FCB +:1008A000016801EB0A0506D0D9F8080000F22330F0 +:1008B000A84200D9FFDF032084F8D80058E094F85C +:1008C0006420019D242A05D094F86530242B01D0A2 +:1008D000252A3AD1B4F85820B4F8F830D21A521C6C +:1008E00012B2002A31DB94F8FA2072B3174694F85A +:1008F000FB2002B110460090022916D0012916D023 +:1009000040F6340049F608528118022F12D0012F08 +:1009100012D040F634001044814210D9081A00F574 +:10092000FA70B0FBF9F005440FE04846EAE74FF4EF +:10093000C860E7E74846EEE74FF4C860EBE7401AC7 +:1009400000F5FA70B0FBF9F02D1AB8F1000F0FD0D6 +:10095000DFF80882D8F8080018B9B8F8020000B12A +:10096000FFDFD8F8080000F22330A84200D9FFDFEB +:1009700005B9FFDF2946D4F8DC00F3F77EFEC4F8A2 +:10098000DC00B060002030704FF0010886F8048071 +:10099000204603F080F8ABF10101084202D186F84D +:1009A000058005E094F8D80001282FD0032070714D +:1009B000606A3946009A01F026FBF060069830EA3A +:1009C0000B0020D029463046FCF752FB87B2204668 +:1009D00003F061F8B8420FD8074686F8058005FB9A +:1009E00007F1D4F8DC00F3F748FEB0602946304642 +:1009F000FCF73EFB384487B23946204602F0F0FF50 +:100A0000B068C4F8DC0007B0BDE8F08F0220CEE784 +:100A10002DE9F04106460C46012001F0D6FAC5B298 +:100A20000B2001F0D2FAC0B2854200D0FFDF0025D2 +:100A3000082C7DD2DFE804F00461696965C98E96EF +:100A4000304601F0D6FA0621F1F7D4FF040000D1B8 +:100A5000FFDF304601F0CDFA2188884200D0FFDF69 +:100A600094F8D80000B9FFDF204602F060FE3B4E4C +:100A700021460020B5607580F561FCF729FC00F186 +:100A80009807606AB84217D994F85500F6F712FF34 +:100A9000014694F854004FF47A72022828D00128B5 +:100AA00028D040F6340008444AF247310844B0FBED +:100AB000F2F1606A0844C51B214600203561FCF74D +:100AC00007FC618840F2E24251439830081AA0F2D4 +:100AD0002330706194F8552094F85410606A01F046 +:100AE00092FAA0F29310B061BDE8F041F4F7AABD0C +:100AF0001046D8E74FF4C860D5E7BDE8F04102F0F2 +:100B000080BEBDE8F041F6F7A7BB6FF0040001F02E +:100B10005CFAC4B2192001F058FAC0B2844200D085 +:100B2000FFDF304601F065FA0621F1F763FF00E0D0 +:100B30004BE0040000D1FFDF304601F05AFA218873 +:100B4000884200D0FFDF2046BDE8F04101220021AD +:100B500001F076BAF6F720FAD3E70000A0120020E1 +:100B600088010020304601F044FA0621F1F742FFE7 +:100B7000040000D1FFDF304601F03BFA21888842B3 +:100B800000D0FFDF94F8D800042800D0FFDF84F8FD +:100B9000D85094F8E2504FF6FF76202D00D3FFDFB7 +:100BA000FB4820F8156094F8E200F4F746F800B925 +:100BB000FFDF202084F8E2002046FFF7D3FDF54850 +:100BC0000078BDE8F041E2F7A7B9FFDFBDE8F081AA +:100BD00070B5EF4C0025483C84F82C50E07868B1A3 +:100BE000E570FEF76AF92078042803D0A06AFFF7C1 +:100BF000B9FDA562E7480078E2F78EF9BDE87040DC +:100C000001F02FBA70B5E24C0146483C206AF4F777 +:100C10004CFD6568A27890FBF5F172B140F271224B +:100C2000B5FBF2F292B2E36B01FB02F6B34202D9DA +:100C300001FB123200E00022E2634D43002800DA9B +:100C4000FFDF2946206AF3F718FD206270BD2DE909 +:100C5000F05FFEF785F98246CD486C3800F1240834 +:100C600081684646D8F81C00F3F707FD0146306A54 +:100C7000F4F71BFD4FF00009074686F839903C4613 +:100C80004FF423754E461CE00AEB06000079F6F798 +:100C900011FE4AF2B12101444FF47A70B1FBF0F138 +:100CA00008EB86024046926811448C4207D3641ACE +:100CB00090F83910A4F52374491C88F83910761C73 +:100CC000F6B298F83A00B042DED8002C0FDD98F862 +:100CD0003910404608EB81018968A14207D241687A +:100CE000C91BA94200D90D466C4288F8399098F882 +:100CF0003960C3460AEB060898F80400F6F7DAFDF7 +:100D000001464AF2B12001444FF47A7AB1FBFAF27B +:100D100098F80410082909D0042909D000201318D4 +:100D200004290AD0082908D0252007E0082000E07F +:100D3000022000EB40002830F1E70F20401D4FF467 +:100D4000A872082913D0042914D0022915D04FF015 +:100D5000080C282210FB0C20184462190BEB8603A8 +:100D600002449868D84682420BD8791925E04FF0A2 +:100D7000400CEFE74FF0100CECE74FF0040C18229A +:100D8000E8E798F8392098F83A604046B24210D225 +:100D9000521C88F839203C1B986862198418084650 +:100DA000F6F788FD4AF2B1210144B1FBFAF00119CE +:100DB00003E080F83990D8F80410D8F82000BDE896 +:100DC000F05FF3F75ABC2DE9FE4F14460546FEF7D7 +:100DD000C7F8DFF8BCB10290ABF1480B58469BF85E +:100DE00039604FF0000A0BEB86018968CBF84010A0 +:100DF000ECB3044600780027042827D0052840D00B +:100E0000FFDFA0463946A069F3F737FC0746F3F742 +:100E100033FF81463946D8F80440F4F746FC401EBB +:100E200090FBF4F0C14361433846F3F726FC0146DA +:100E3000C8F820004846F4F738FC002800DDFFDF42 +:100E4000012088F8140088F813008FE0D4F8189077 +:100E5000D4F8048001F06FF9070010D0387800B999 +:100E6000FFDF796978684A460844414600E00EE0B1 +:100E700001F049F907464045C3D9FFDFC1E75746AE +:100E8000BFE7A06A01F0FAF840F6B837B9E7016A9F +:100E90000BEB46000191C08D08B35C46DBF81800EF +:100EA000FFF7B0FE6168206AF3F7E7FB074684F8B6 +:100EB00039A0019CD8462046DBF81810F4F7F5FB62 +:100EC000814639462046F4F7F0FBD8F80420B9FBF8 +:100ED000F2F3B0FBF2F0834243D0012142E0F3F79A +:100EE000CBFEFFF78FFEFFF7B2FE9BF83910DBF861 +:100EF00004900BEB81010746896800913946DBF8C5 +:100F00002000F4F7D2FB00248046484504DB98FB20 +:100F1000F9F404FB09F41BE0002059469BF8392042 +:100F200008E000BF01EB800304F523749B68401CBC +:100F30001C44C0B28242F5D852B10120F6F7BAFC87 +:100F40004AF2B12101444FF47A70B1FBF0F004444D +:100F50000099A8EB04000C1A00D5FFDFCBF8404045 +:100F6000A7E7002188F8141088F813A09BF8020066 +:100F70005C46B8B13946206AF4F797FB0146E26B4C +:100F800040F2712042438A4206D2C4F840A009E0F0 +:100F90000C13002084010020206C511A884200D3D9 +:100FA00008462064AF6085F800A001202871029FE8 +:100FB00094F839003F1DC05DF6F77CFC4AF23B51C6 +:100FC00001444FF47A70B1FBF0F0216CFB3008441F +:100FD000E8602078042808D194F8390004EB400038 +:100FE000C08D0A2801D2032000E00220687104EBC2 +:100FF0004600C08DC0B128466168FCF739F882B25E +:101000000020761C0CE000BF04EB4003B042D98DF9 +:10101000114489B2D98501D3491CD985401CC0B27D +:1010200094F83A108142EFD2A868A061E06194F888 +:10103000390004EB4000C18D491CC18594F839008A +:10104000C05D082803D0042803D000210BE008214C +:1010500000E0022101EB410128314FF4A872082879 +:1010600004D0042802D0022807D028220A440428E9 +:1010700005D0082803D0252102E01822F6E70F2129 +:10108000491D08280CD004280CD002280CD00820B8 +:1010900011FB0020216C884208D20120BDE8FE8FA0 +:1010A0004020F5E71020F3E70420F1E70020F5E702 +:1010B00070B5FB4C061D14F8392F905DF6F7FAFB5E +:1010C0004FF47A7100F2E730B0FBF1F0D4F807107A +:1010D00045182078805DF6F7DBFB2178895D0829CB +:1010E00003D0042903D000220BE0082200E00222F2 +:1010F00002EB420228324FF4A873082904D00429D5 +:1011000002D0022907D028231344042905D0082936 +:1011100003D0252202E01823F6E70F22521D0829EA +:101120000AD004290AD002290AD0082112FB013171 +:10113000081A281A293070BD4021F7E71021F5E779 +:101140000421F3E7FEB504460F46012000F03DFF01 +:10115000C5B20B2000F039FFC0B2854200D0FFDFDE +:1011600001260025CE48082F50D2DFE807F00430D2 +:101170004747434F4F4C0446467406744078002856 +:1011800019D1FDF7EDFE009594F839108DF808108F +:101190004188C90410D0606C019003208DF80900CB +:1011A000BF4824388560C56125746846FDF7C5FBD6 +:1011B000002800D0FFDFFEBDFFF77AFF0190207D01 +:1011C00010B18DF80950EBE78DF80960E8E70446A7 +:1011D000407840B1207C08B9FDF744FE6574BDE855 +:1011E000FE40F3F753BCA674FDF786FC0028E2D05E +:1011F000FFDFFEBDBDE8FE40F6F72EB82046BDE895 +:10120000FE4000F0A1BFBDE8FE40E1E4FFDFFEBD0F +:10121000A34950B101228A704A6840F27123B2FB9F +:10122000F3F202EB0010C86370470020887070472B +:101230002DE9F05F894640F27121994E484300251F +:101240000446706090462F46D0074AF2B12A4FF408 +:101250007A7B0FD0B9F800004843B0600120F6F760 +:1012600029FB00EB0A01B1FBFBF0241AB76801254A +:10127000A4F523745FEA087016D539F8151040F20A +:101280007120414306EB85080820C8F80810F6F7DE +:1012900011FB00EB0A01B1FBFBF0241AD8F808009F +:1012A000A4F5237407446D1CA7421AD9002D18D049 +:1012B000391BB1FBF5F0B268101AB1FBF5F205FB72 +:1012C0001212801AB060012009E000BFB1FBF5F3F3 +:1012D00006EB80029468E31A401CC0B29360A842F7 +:1012E000F4D3BDE8F09F2DE9F0416D4C0026207845 +:1012F000042804D02078052801D00C2066E40120C1 +:101300006070607C002538B1EFF3108010F0010FA1 +:1013100072B610D001270FE0FDF722FE074694F8C1 +:101320002400F4F70EF87888C00411D000210320BF +:10133000FDF71BFE0CE00027607C38B1A07C28B1D3 +:10134000FDF790FD6574A574F3F7A0FB07B962B6CD +:1013500094F82400F4F743FA94F82C0030B184F8A0 +:101360002C502078052800D0FFDF0C26657000F097 +:1013700078FE30462AE44A4810B5007808B1FFF7F5 +:10138000B2FF00F011FF464900202439086210BD69 +:1013900010B5444C58B1012807D0FFDFA06841F6D2 +:1013A0006A01884200D3FFDF10BD40F6C410A06080 +:1013B000F4E73C4908B508703949002008704870C6 +:1013C00081F82C00C87008744874887420228862E0 +:1013D00081F82420243948704FF6FF7211F16C0116 +:1013E00021F81020401CC0B22028F9D30020FFF7BC +:1013F000CFFFFFF7C0FF1020ADF8000001226946C3 +:101400000420FFF715FF08BD7FB5254C05460E46A5 +:10141000207810B10C2004B070BD95F8552095F8D7 +:101420005410686A00F002FFC5F8EC00A56295F858 +:10143000D80000B1FFDF1A4900202439C861052116 +:101440002170607084F82C00014604E004EB410236 +:10145000491CD085C9B294F83A208A42F6D284F861 +:1014600039003046FFF7D4FE0F48F3F78AFB84F8C3 +:101470002400202800D1FFDFF3F7FEFBA06194F8E1 +:10148000241001226846FFF79EFC00B9FFDF94F8A4 +:1014900024006946F3F73AFE00B9FFDF0020BAE7FF +:1014A000C41200208401002045110200F84810B544 +:1014B000007808B1002010BD0620F1F735FA80F061 +:1014C000010010BDF8B5F24D0446287800B1FFDFE9 +:1014D0000020009023780246DE0701466B4605D0C7 +:1014E0006088A188ADF800100122114626787607A1 +:1014F00006D5E088248923F8114042F00802491CEF +:10150000491E85F83A101946FFF792FE0020F8BDF3 +:101510001FB511B1112004B010BDDD4C217809B107 +:101520000C20F8E70022627004212170114604E0CB +:1015300004EB4103491CDA85C9B294F83A308B4276 +:10154000F6D284F83920FFF763FED248F3F719FB8F +:1015500084F82400202800D1FFDF00F0ECFD10B15A +:10156000F3F78AFB05E0F3F787FB40F6B831F3F7B2 +:1015700084F8A06194F8241001226846FFF723FC48 +:1015800000B9FFDF94F824006946F3F7BFFD00B906 +:10159000FFDF0020BFE770B5BD4CA16A0160FFF717 +:1015A000A2FE050002D1A06AFFF7DCF80020A062CD +:1015B000284670BD7FB5B64C2178052901D00C2096 +:1015C00029E7B3492439C860A06A00B9FFDFA06ADF +:1015D00090F8D80000B1FFDFA06A90F8E200202860 +:1015E00000D0FFDFAC48F3F7CCFAA16A054620280B +:1015F00081F8E2000E8800D3FFDFA548483020F8CC +:101600001560A06A90F8E200202800D1FFDF0023D7 +:1016100001226846A16AFFF7C0F8A06A694690F8FF +:10162000E200F3F773FD00B9FFDF0020A062F2E6ED +:10163000974924394870704710B540F2E24300FBE7 +:1016400003F4002000F0F2FD844201D9201A10BDFD +:10165000002010BD70B50D46064601460020FBF780 +:1016600037FE044696F85500F6F724F9014696F839 +:1016700054004FF47A72022815D0012815D040F694 +:10168000340008444AF247310844B0FBF2F1708854 +:1016900040F271225043C1EB4000A0F22330A5423A +:1016A00006D2214605E01046EBE74FF4C860E8E7B4 +:1016B0002946814204D2A54201D2204600E02846B4 +:1016C000706270BD70B5F5F7D5F80446F6F7E0F82E +:1016D00001466F48243882684068101A0E18204668 +:1016E00000F06AFC05462046F6F7E4F8281A4FF4A5 +:1016F0007A7100F2E730B0FBF1F0304470BD70B5A4 +:101700000546FDF72DFC6249007824398C68983431 +:10171000072D30D2DFE805F0043434252C343400B2 +:1017200014214FF4A873042810D00822082809D0E7 +:101730002A2102280FD011FB024000222823D118B1 +:10174000441819E0402211FB0240F8E7102211FB77 +:1017500002402E22F3E7042211FB0240002218234C +:10176000EDE7282100F040FC044404F5317403E067 +:1017700004F5B07400E0FFDF4548006CA04201D9D9 +:10178000012070BD002070BD70B5414C243C6078D4 +:1017900070B1D4E904512846A268FBF794FC20619B +:1017A000A84205D0A169401B0844A061F3F74AFF95 +:1017B0002169A068884201D8207808B1002070BD56 +:1017C000012070BD2DE9F04F054685B016460F4645 +:1017D0001C461846F6F75CF805EB4701471820460B +:1017E00000F0EAFB4AF2C5714FF47A7908444D469D +:1017F000B0FBF5F0384400F16008254824388068D3 +:10180000304404902046F6F743F8A8EB0007204642 +:1018100000F0D2FB06462046F6F74CF8301AB0FB33 +:10182000F5F03A1A182128254FF4C8764FF4BF77FF +:101830004FF0020B082C34D0042C2FD00020022CA7 +:1018400032D0082310F1280003EB830C0CEB831338 +:10185000184402444FF0000A082C2DD0042C26D046 +:101860000020022C2DD0082100F5B07001EB0111F1 +:101870002944884232D2082C2AD0042C25D00020BA +:10188000022C28D00821283001EB011134E000009F +:10189000C412002045110200110A0200384610232C +:1018A000D2E730464023CFE704231830CCE73D464B +:1018B00040F2EE301021D9E735464FF43560402133 +:1018C000D4E70D460421B430D0E738461021DBE7D9 +:1018D00030464021D8E704211830D5E7082C4FD0F6 +:1018E000042C4AD00020022C4DD0082110F12800F1 +:1018F000C1EBC10303EB4111084415182821204610 +:1019000000F072FB05EB4000082C42D0042C3DD0C7 +:101910000026022C3FD0082116F1280601EB811188 +:1019200006EB810146180120FC4D8DF804008DF86E +:1019300000A08DF805B0E86906F227260499F2F7B1 +:101940009CFECDE902062046F5F7B4FF4AF23B5172 +:101950000144B1FBF9F0301AFB3828640298C5F84D +:101960004480E86195F824006946F3F7CFFB00282E +:1019700000D1FFDF05B0BDE8F08F38461021B7E792 +:1019800030464021B4E704211830B1E73E4610212B +:10199000C4E74021C2E704211836BFE72DE9FE4F16 +:1019A00004461D46174688464FF0010A1846F5F7CB +:1019B0006FFFDA4E0146243EB068021907EB48007B +:1019C00010440F18284600F0F7FA4FF47A7B00F61F +:1019D000FB01D846B1FBF8F0384400F12009284655 +:1019E000F5F756FFB1680246A9EB0100001B861A05 +:1019F000284600F0E1FA07462846F5F75BFF381A5B +:101A0000B0FBF8F0311A182628234FF4C8774FF4AA +:101A1000BF78082D2CD0042D27D00020022D2AD0ED +:101A20000822283002EB820C0CEB82121044014495 +:101A3000082D28D0042D21D00020022D28D01E46AC +:101A4000082200F5B07000BF02EB0212324490424F +:101A50002AD2082D22D0042D1DD00020022D20D006 +:101A60000822283002EB02122CE040461022D9E76F +:101A700038464022D6E704221830D3E7464640F2E3 +:101A8000EE301022E0E73E464FF435604022DBE7BF +:101A90000422B430D8E740461022E3E7384640221B +:101AA000E0E704221830DDE7082D4DD0042D48D0A2 +:101AB0000020022D4BD0082210F12800C2EBC203F7 +:101AC00003EB421210440E182821284600F08CFA2D +:101AD00006EB4000082D40D0042D3BD00027022DFE +:101AE0003DD0082117F1280701EB811107EB810197 +:101AF000451805F596750C98F5F7DCFE4AF23B5152 +:101B00000144B1FBFBF0854EFB30A6F12407316C9C +:101B100004F1FB020844B9684B191A44824228D9DF +:101B2000621911440D1AFB35E1F7B0F8B9680844A1 +:101B300061190844B0F1807F36D2642D12D264203E +:101B400011E040461022B9E738464022B6E70422A9 +:101B50001830B3E747461021C6E74021C4E7042107 +:101B60001837C1E72846F3F7D4FDE8B1306C2844B4 +:101B70003064E1F78BF8B968293821440844CDE98D +:101B8000000996F839008DF8080002208DF8090048 +:101B90006846FCF7D2FE00B1FFDFFCF7ADFF00B1F5 +:101BA000FFDF5046BDE8FE8F4FF0000AF9E71FB592 +:101BB00000F042FB594C607880B994F82410002260 +:101BC0006846FFF700F938B194F824006946F3F746 +:101BD0009DFA18B9FFDF01E00120E070F2F756FF2F +:101BE00000206074A0741FBD2DE9F84FFDF7B8F90F +:101BF0000646451CC07840090CD001280CD00228AC +:101C00000CD000202978824608064FF4967407D439 +:101C10001E2006E00120F5E70220F3E70820F1E7A7 +:101C20002046B5F80120C2F30C0212FB00F7C809E8 +:101C300001D010B103E01E2401E0FFDF0024FFF714 +:101C400041FDA7EB00092878B77909EB0408C0F338 +:101C5000801010B120B1322504E04FF4FA7501E094 +:101C6000FFDF00250C2F00D3FFDF2D482D4A30F871 +:101C70001700291801FB0821501CB1FBF0F5F4F7FF +:101C8000F9FDF5F717FE4FF47A7100F27160B0FBC1 +:101C9000F1F1A9EB0100471BA7F15900103FB0F586 +:101CA000237F11D31D4E717829B902465346294628 +:101CB0002046FFF787FD00F0BFFAF2F7E7FE0020AD +:101CC0007074B074BDE8F88F3078009053462246A7 +:101CD00029463846FFF762FE0028F3D10121022091 +:101CE000FDF743F9BDE8F84F61E710B50446012957 +:101CF00003D10A482438007830B1042084F8D80091 +:101D0000BDE81040F2F7C2BE00220121204600F0DB +:101D100097F934F8580F401C2080F1E7C4120020D6 +:101D2000A45C02003F420F002DE9F0410746FDF799 +:101D300017F9050000D1FFDF29783846FBF775FC5D +:101D4000F84C0146A4F12406E069B268024467B386 +:101D50002878082803D0042803D000270BE00823A4 +:101D600000E0022303EB430728374FF4A873082849 +:101D700004D0042802D002280FD028233B4408288E +:101D80000DD004280DD002280DD00820C0EBC007CC +:101D900007EB40101844983009E01823EEE7402084 +:101DA000F4E71020F2E70420F0E74FF4FC70104451 +:101DB000471828783F1DF5F77DFD024628784FF437 +:101DC0007A7102281DD001281DD040F6340010443D +:101DD0004AF2EF021044B0FBF1F03A1AA06A40F266 +:101DE000E241B0464788D8304F43316A81420DD036 +:101DF0003946606B00F087F90646B84207D9FFDF25 +:101E000005E00846E3E74FF4C860E0E70026C6486F +:101E10008068864207D2A16A40F271224888424314 +:101E200006EB420604E040F2E240B6FBF0F0A16AA5 +:101E3000C882A06A297880F85410297880F8551053 +:101E400005214175C08A6FF41C71484306EB4000C0 +:101E500040F63541C8F81C00B0EB410F00D3FFDF5E +:101E6000BDE8F08110B5052937D2DFE801F005099A +:101E7000030D3100002100E00121BDE8104034E7EE +:101E8000032180F8D81010BD0446408840F2E2419A +:101E90004843A549091D0860D4F800010089E08283 +:101EA000D4F8000180796075D4F800014089608021 +:101EB000D4F800018089A080D4F80001C089E080B6 +:101EC0002046A16AFFF7C6FB022084F8D80010BDA7 +:101ED000816ABDE81040FFF7BDBBFFDF10BD70B5E4 +:101EE000904C243C0928A1683FD2DFE800F0050BA4 +:101EF0000B15131538380800BDE8704057E6BDE8EB +:101F0000704071E6022803D00020BDE870400BE766 +:101F10000120FAE7E16070BD032802D005281CD03B +:101F200000E0E1605FF0000600F086F97D4D0120E1 +:101F300085F82C0085F83860A86AE9690026C0F8A1 +:101F4000DC1080F8D860E068FFF734FB00B1FFDFF9 +:101F5000F2F79CFD6E74AE7470BD0126E4E7724822 +:101F60000078BDE87040E0F7D7BFFFDF70BD6D4976 +:101F700024394860704770B56A4D0446243DB1B1BC +:101F80004FF47A76012903D0022905D0FFDF70BD16 +:101F90001846F5F7C9FC05E06888401C68801046C3 +:101FA000F5F7A1FC00F2E730B0FBF6F0201AA860CC +:101FB00070BD5C4800787047082803D0042801D021 +:101FC000F5F778BC4EF628307047002804DB00F1A6 +:101FD000E02090F8000405E000F00F0000F1E020A0 +:101FE00090F8140D4009704710F00C0000D008461E +:101FF000704710B50446202800D3FFDF4948483019 +:1020000030F8140010BD70B505460C461046F5F7C3 +:1020100051FC4FF47A71022C0DD0012C0DD040F6FA +:10202000340210444AF247321044B0FBF1F0284425 +:1020300000F2931070BD0A46F3E74FF4C862F0E770 +:102040001FB513460A46044601466846FEF7A5FB3F +:1020500094F8E2006946F3F759F8002800D1FFDF51 +:102060001FBD70B52F4C0025257094F82400F2F7A1 +:10207000E4FD00B9FFDF84F8245070BD2DE9F04184 +:10208000050000D1FFDF274A0024243AD5F8EC6090 +:102090002046631E116A08E08869B04203D3984263 +:1020A00001D203460C460846C9680029F4D104B998 +:1020B00004460021C5F8E840D835C4B1E068E560C1 +:1020C000E86000B105612E698846A96156B1B06922 +:1020D00030B16F69B84200D2FFDFB069C01BA861A0 +:1020E000C6F818800F4D5CB1207820B902E0E96095 +:1020F0001562E8E7FFDF6169606808446863AFE67E +:10210000C5F83480ACE610B50C4601461046F3F72E +:10211000CCFA00280ADA211A491EB1FBF4F101FBBE +:10212000040010BDC41200208401002090FBF4F1D3 +:1021300001FB1400F5E74648016A002001E008466B +:10214000C9680029FBD170477FB504466FF00400D1 +:10215000FFF73BFFC5B21920FFF737FFC0B285423A +:1021600000D0FFDFFCF7FCFE4088C00407D001214F +:102170000320FCF7FAFE37480078E0F7CDFE002296 +:1021800021466846FEF71FFE38B169462046F2F741 +:10219000BDFF002800D1FFDF7FBD2D490120243184 +:1021A000C870FEF715FD7FBD2DE9FE43284D0120C7 +:1021B000287000264FF6FF7420E00621F0F71AFC85 +:1021C000070000D1FFDF97F8E200D837F3F707FBED +:1021D00007F80A6BA14617F8E289B8F1200F00D37F +:1021E000FFDF1B4A6C3222F8189097F8E200F2F7F2 +:1021F00024FD00B9FFDF202087F8E20069460620B1 +:10220000F0F781FB50B1FFDF08E0029830B190F8A1 +:10221000D81019B10088A042CFD104E06846F0F789 +:1022200050FB0028F1D02E70BDE8FE8310B5FFF7FB +:10223000EAFE00F5C87074E705480021243090F8E4 +:10224000392000EB4200C18502480078E0F764BE07 +:10225000A012002084010020012804D0022805D00B +:10226000032808D105E0012907D004E0022904D0A1 +:1022700001E0042901D00020704701207047FE488A +:10228000806890F8881029B1B0F88410B0F88620E2 +:10229000914215D290F88C1029B1B0F88A10B0F89C +:1022A000862091420CD2B0F88220B0F880108A4289 +:1022B00006D290F86820B0F87E001AB1884203D3A5 +:1022C000012070470628FBD2002070472DE9F0411D +:1022D000E94D0746A86800F1580490F8FC0030B9B1 +:1022E000E27B002301212046FAF758FE10B1608DF1 +:1022F000401C608501263D21AFB92878022808D00E +:1023000001280AD06878C8B110F0140F09D01E2037 +:1023100039E0162037E0E6763EE0A86890F8FE0047 +:1023200031E0020701D52177F5E7810701D02A20A6 +:1023300029E0800600D4FFDF232024E094F8300059 +:1023400028B1A08D411CA185E18D884213D294F85B +:10235000340028B1608E411C6186E18D88420AD22A +:10236000618D208D814203D3AA6892F8FC2012B9B6 +:10237000E28D914201D3222005E0217C29B1E18C3C +:10238000814207D308202077C5E7E08C062801D3D7 +:102390003E20F8E7E07EB0B1002020736073207427 +:1023A0000221A868FFF75EFDA86890F8CC1001290B +:1023B00004D1D0F804110878401E0870E878BDE810 +:1023C000F041E0F7A9BDA868BDE8F0410021FFF7A2 +:1023D00049BDA9490C28896881F8CC0014D013287C +:1023E00012D0182810D0002211280ED007280BD0A8 +:1023F00015280AD0012807D0002805D0022803D0CC +:1024000021F8842F012008717047A1F88A207047B5 +:1024100010B5994CA1680A88A1F8462181F84401B9 +:1024200091F8540001F073FBA16881F8480191F81C +:10243000550001F06CFBA16881F84901012081F889 +:102440004201002081F81601E078BDE81040E0F775 +:1024500063BD70B5884C00231946A06890F86420CD +:102460005830FAF79BFD00283DD0A06890F808117D +:102470000025C9B3A1690978B1BB90F86500FAF7E6 +:1024800075FD88BBA168B1F858000A282DD905222E +:102490000831E06903F046F810B3A068D0F80411E1 +:1024A000087858B10522491CE06903F03BF8002880 +:1024B00019D1A068D0F80401007840B9A068E1699A +:1024C000D0F804010A68C0F8012009794171A068B8 +:1024D000D0F804110878401C08700120FFF779FF3C +:1024E000A06880F8085170BDFFE7A06890F80C1153 +:1024F00011B190F80D11B9B390F816110029F2D06E +:1025000090F817110029EED190F86500FAF72EFD2A +:102510000028E8D1A06890F8540001F0F8FA0646C7 +:10252000A06890F8550001F0F2FA0546A06890F80E +:1025300018113046FFF790FE90B3A06890F819117B +:102540002846FFF789FE58B3A268B2F8583092F8CF +:102550005410B2F81A01F832FBF730F818B3A1683A +:10256000252081F86400BEE7FFE790F86510242974 +:1025700017D090F86410242913D0002300F1FA0238 +:1025800000F58671FAF7BAFDA06880F80C5130F8B2 +:10259000421FA0F88C108188A0F88E10142007E04C +:1025A00005E00123EAE7BDE87040002030E716208F +:1025B000BDE870400DE710B5F3F73CFC0C2813D3D1 +:1025C0002D4C0821A068D0F800011E30F3F736FC2E +:1025D00028B1A0680421C030F3F730FC00B9FFDF58 +:1025E000BDE810400320F4E610BD10B5224CA068F1 +:1025F000D0F800110A78002A1FD049880288914239 +:102600001BD190F86420002319465830FAF7C6FC15 +:10261000002812D0A068D0F800110978022907D04C +:1026200003290BD0042917D0052906D108200DE075 +:1026300090F86500FAF79AFC40B110BD90F8691067 +:1026400039B190F86A0000B9FFDF0A20BDE81040F8 +:10265000BFE6BDE81040AEE790F890008007ECD1EF +:102660000C20FFF7B6FEA068002120F8841F01218E +:102670000171017B02E000009001002041F00101A6 +:10268000017310BD70B5FE4CA268556DFAF730FFAE +:10269000EBB2C1B200228B4203D0A36883F8FA10D8 +:1026A00002E0A16881F8FA20C5F30721C0F30720F2 +:1026B000814203D0A16881F8FB0014E7A06880F88C +:1026C000FB2010E770B5EE48806890F84E20448EED +:1026D000C38E418FB0F84050022A23D0A94200D3C4 +:1026E00029460186C18FB0F84220914200D311469D +:1026F0008186018FB0F84420914200D31146418673 +:10270000818FB0F84620914200D31146C186418E98 +:10271000A14200D90C464486C18E994200D90B468D +:10272000C386E0E6028E914200D31146C68F828EA8 +:10273000964200D23246A94200D329460186B0F81B +:1027400042108A4200D30A468286002180F84E1049 +:10275000CFE770B5CA4CA06890F8CC10FE2955D1CF +:102760006178002952D190F8672000230121583068 +:10277000FAF714FC002849D1A06890F8FC1009B1C0 +:10278000022037E090F86420002319465830FAF709 +:1027900005FC28B1A06890F87C0008B1122029E05F +:1027A000A068002590F86420122A1DD004DC032ABA +:1027B00023D0112A04D119E0182A1AD0232A26D0AE +:1027C000002304215830FAF7E9FB00281ED1A06845 +:1027D00090F86510192970D020DC01292AD002292F +:1027E00035D0032932D120E00B2003E0BDE8704052 +:1027F000E1E60620BDE87040EBE510F8CA1F017164 +:102800000720FFF7E6FDA06880F864506BE618200B +:10281000FFF7DFFDA068A0F8845064E61D2918D0FA +:102820001E2916D0212964D148E010F8C91F417132 +:1028300007206EE00C20FFF7CCFDA06820F88A5F2F +:10284000817941F00101817100F8255C51E013208C +:102850002AE090F80D217ABB90F80C21AAB1242926 +:1028600011D090F8641024290DD0002300F1FA0251 +:1028700000F58671FAF742FCA0681E2180F8651009 +:1028800080F80C5103E00123F0E71E2931D1FFF756 +:1028900019FF01F04EF9A06830F8421FA0F88C1023 +:1028A0008188A0F88E101520FFF793FDA068A0F88E +:1028B0008A5000BF80F865501BE029E090F87D1039 +:1028C00049B100F8FA5F45701820FFF782FDA06853 +:1028D000A0F88A500DE090F8171151B990F8161130 +:1028E00039B1016DD0F81801FFF7CCFE1820FFF7C1 +:1028F00070FDA06890F8CC00FE2887D1FFF775FE28 +:10290000A06890F8CC00FE2887D1BDE87040A0E513 +:102910001120FFF75EFDA068CCE7594A01299268B3 +:1029200019D0002302290FD003291ED010B301288B +:102930002BD0032807D192F86400132803D016285F +:1029400001D0182804D1704792F8CC000028FAD0A2 +:10295000D2F8000117E092F8CC000128F3D0D2F8A9 +:1029600004110878401E0870704792F8CC000328C4 +:10297000EED17047D2F80001B2F858108288891A57 +:1029800009B20029F5DB03707047B2F85800B2F8BD +:102990000A11401A00B20028F6DBD2F804010178CF +:1029A000491E0170704770B5044690F86400002518 +:1029B0000C2810D00D282ED1D4F80011B4F85800EE +:1029C0008988401C884226D1D4F84C012C4E0178CD +:1029D00011B3FFDF42E0B4F85800B4F80A11401C0C +:1029E000884218D1D4F80401D0F80110A1604079D0 +:1029F000207302212046F9F7ABFFD4F804010078D8 +:102A000000B9FFDF0121FE20FFF787FF84F8645043 +:102A1000012084F8980066E52188C180D4F800017F +:102A2000D4F84C1140890881D4F80001D4F84C1135 +:102A300080894881D4F80001D4F84C11C08988817C +:102A4000D4F84C010571D4F84C1109200870D4F861 +:102A50004C1120884880F078E0F75EFA012120468A +:102A6000F9F776FF03212046FFF7FCF9B068D0F8AC +:102A700000010078022800D0FFDF0221FE2001E0E3 +:102A800090010020FFF749FF84F864502BE52DE901 +:102A9000F041002603270125FE4CD4F808C088B178 +:102AA0002069C0788CF8CA0005FA00F0C0F3C05065 +:102AB00000B9FFDFA06800F8647F068480F8245026 +:102AC000BDE8F08100239CF8652019460CF1580000 +:102AD000FAF764FA70B160780028F1D12069C17802 +:102AE000A06880F8C91080F86570A0F88A6080F846 +:102AF0008C50E5E76570E3E7F0B5E64C002385B060 +:102B0000A068194690F865205830FAF747FA012571 +:102B100080B1A06890F8640023280ED024280CD03F +:102B20006846F4F7EAFF68B1009801A9C0788DF80B +:102B3000040008E0657005B0F0BD607840F020004A +:102B40006070F8E70021A06803AB162290F86400DB +:102B5000FAF74FFD002670B1A0689DF80C201621F1 +:102B600000F8F42F4170192100F88F1C00F8685C00 +:102B700020F86A6CDFE72069FBF7E7F878B1216994 +:102B8000087900F00702A06880F85020497901F028 +:102B9000070180F8511090F817310BBB03E00020BB +:102BA000FFF775FFC7E790F81631CBB900F1540372 +:102BB0005F78974205D11A788A4202D180F87D5019 +:102BC0000EE000F59F71028821F8022990F850204C +:102BD0000A7190F8510048710D70E078E0F79CF9A7 +:102BE000A068212180F8651080F88C50A0F88A60D8 +:102BF000A1E770B5A74C00231946A06890F865209E +:102C00005830FAF7CBF928B32069FBF783F830B3D3 +:102C1000A5682069FBF77AF82887A5682069FBF783 +:102C200071F86887A5682069FBF772F8A887A5681E +:102C30002069FBF769F8E887A068012590F864101F +:102C40001C2910D090F84E10012912D090F80D11C7 +:102C500079B90BE0607840F00100607043E4BDE8B2 +:102C60007040002013E780F84E5002E090F80C11FD +:102C700019B11E2180F8651012E01D2180F8651041 +:102C800000F58E710288CA82028F0A83428F4A83BE +:102C9000828F8A83C08FC8830D75E078E0F73CF996 +:102CA000A068002120F88A1F85701CE410B5794CBB +:102CB00000230921A06890F864205830FAF76EF9D3 +:102CC00048B16078002805D1A16801F87C0F08732D +:102CD00001F8180C10BD0120607010BD7CB56D4C62 +:102CE00000230721A06890F864205830FAF756F9BD +:102CF00038B36078002826D169462069FBF720F8B0 +:102D00009DF80000002500F02501A06880F89610CD +:102D10009DF8011001F0490180F8971080F8885063 +:102D2000D0F8001100884988814200D0FFDFA068F8 +:102D3000D0F800110D70D0F84C110A7822B1FFDFE5 +:102D400016E0012060707CBD30F8D02BCA80C16FC6 +:102D50000D71C16F009A8A60019ACA60C26F082122 +:102D6000117030F8D01CC06F4180E078E0F7D4F8E3 +:102D7000A06880F864507CBD70B5464C00231946AD +:102D8000A06890F865205830FAF708F9012540B995 +:102D9000A0680023082190F864205830FAF7FEF864 +:102DA00010B36078002820D1A06890F890008007C8 +:102DB00012D42069FAF78AFFA16881F8910020698E +:102DC00030F8052FA1F892204088A1F8940011F85E +:102DD000900F40F002000870A0684FF0000690F8D5 +:102DE0009010C90702D011E0657066E490F8652084 +:102DF000002319465830FAF7D1F800B9FFDFA06870 +:102E000080F8655080F88C50A0F88A60A06890F82F +:102E10006410012906D180F8646080F88860E07849 +:102E2000E0F77AF8A168D1F80001098842888A425F +:102E3000DBD101780429D8D10670E078E0F76CF88E +:102E4000A06890F864100029CFD180F8886034E43D +:102E500070B5104DA86890F864101A2902D00220AD +:102E600068702AE469780029FBD1002480F88D403D +:102E700080F88840D0F8001100884988814200D04D +:102E8000FFDFA868D0F800110C70D0F84C110A7858 +:102E900022B101E090010020FFDF25E090F88E20B4 +:102EA00072B180F88E400288CA80D0F84C110C7143 +:102EB000D0F84C210E2111700188D0F84C010DE0A2 +:102EC00030F8D02BCA80C16F0C71C26F0121117212 +:102ED000C26F0D21117030F8D01CC06F418000F01E +:102EE000A2FEE878E0F718F8A86880F8644018E4D3 +:102EF00070B5FA4CA16891F86420162A01D0132A03 +:102F000002D191F88E2012B10220607009E462783B +:102F1000002AFBD181F8C800002581F88D5081F886 +:102F20008850D1F8000109884088884200D0FFDF2E +:102F3000A068D0F800010078032800D0FFDF03214B +:102F4000FE20FFF7EAFCA068D0F84C110A780AB11D +:102F5000FFDF14E030F8C82BCA8010F8081BC26FDE +:102F60001171C16F0D72C26F0D21117030F8D01C3C +:102F7000C06F418000F057FEE078DFF7CDFFA0681A +:102F800080F8645042E470B5D44C09210023A06855 +:102F900090F864205830FAF701F8002518B120693C +:102FA000007912281ED0A0680A21002390F864201E +:102FB0005830F9F7F3FF18B120690079142814D0BC +:102FC0002069007916281AD1A06890F864101F298A +:102FD00015D180F8645080F88850BDE870401A2000 +:102FE000FFF716BABDE8704060E6A06800F8645FBD +:102FF000058480F82450BDE8704000F09ABD05E4D7 +:1030000070B5B64C2079C00773D020690023052124 +:10301000C578A06890F864205830F9F7BFFF98B1E0 +:10302000062D11D006DC022D0ED0042D0CD0052D5E +:1030300006D109E00B2D07D00D2D05D0112D03D0A1 +:10304000607840F0080060706078002851D12069F5 +:10305000FAF7A0FD00287ED0206900250226C1785D +:10306000891E162977D2DFE801F00B763437472224 +:10307000764D76254A457676763A53506A6D70736A +:10308000A0680023012190F867205830F9F786FFE7 +:1030900008BB2069FAF7E2FDA16881F8FE0007206D +:1030A00081F8670081F88C5081F8885056E0FFF76E +:1030B0006AFF53E0A06890F864100F2901D0667091 +:1030C0004CE0617839B980F86950122180F86410B9 +:1030D00044E000F0D3FD41E000F0AFFD3EE0FAF740 +:1030E00072FE03283AD12069FAF771FEFFF700FF5C +:1030F00034E03BE00079F9E7FFF7AAFE2EE0FFF7A6 +:103100003BFE2BE0FFF7EAFD28E0FFF7CFFD25E0CF +:10311000A0680023194690F865205830F9F73EFF63 +:10312000012110B16078C8B901E0617016E0A068B3 +:1031300020F88A5F817000F8256C0FE00BE0FFF744 +:1031400058FD0BE000F03CFD08E0FFF7D5FC05E082 +:1031500000F002FD02E00020FFF799FCA268F2E90E +:103160002A01401C41F10001C2E9000153E42DE9AC +:10317000F0415A4C2079800741D5607800283ED133 +:10318000E06801270026C17820461929856805F1E5 +:1031900058006FD2DFE801F04B3E0D6FC1C1801CBB +:1031A00034C1556287C1C1C1C1BE8B9598A4B0C15D +:1031B000BA0095F8672000230121F9F7EFFE0028F7 +:1031C0001DD1A068082180F8671080F8886090E021 +:1031D000002395F865201946F9F7E0FE10B1A068C4 +:1031E00080F88C60A0680023194690F8642058305D +:1031F000F9F7D4FE002802D0A06880F888605FE468 +:10320000002395F864201946F9F7C8FE00B9FFDFDE +:10321000042008E0002395F864201946F9F7BEFE63 +:1032200000B9FFDF0C20A16881F8640048E40023A6 +:1032300095F864201946F9F7B1FE00B9FFDF0D20BB +:10324000F1E7002395F864201946F9F7A7FE00B9C5 +:10325000FFDFA0680F2180F88D7008E095F864000A +:10326000122800D0FFDFA068112180F88E7080F84E +:10327000641025E451E0002395F864201946F9F71D +:103280008DFE20B9A06890F88E0000B9FFDFA0681D +:10329000132180F88D70EAE795F86400182800D0B3 +:1032A000FFDF1A20BFE7BDE8F04100F066BD002354 +:1032B00095F864201946F9F771FE00B9FFDF052083 +:1032C000B1E785F88C6014E4002395F86420194672 +:1032D000F9F764FE00B9FFDF1C20A4E7900100208D +:1032E000002395F865201946F9F758FE00B9FFDF6D +:1032F000A06880F88C6082E7002395F86420194666 +:10330000F9F74CFE00B9FFDF1F208CE7BDE8F04164 +:1033100000F0FBBC85F86560D3E7FFDF6FE710B511 +:10332000F74C6078002837D1207940070FD5A06886 +:1033300090F86400032800D1FFDFA06890F86710C0 +:10334000072904D101212170002180F86710FFF7BF +:103350000EFF00F0B8FCFFF753FEA078000716D56B +:10336000A0680023052190F864205830F9F716FE74 +:1033700050B108206070A068D0F84C1108780D2872 +:1033800000D10020087002E00020F8F73BFAA068A6 +:10339000BDE81040FFF707BB10BD2DE9F041D84C48 +:1033A00007464FF000056078084360702079810679 +:1033B0002046806802D5A0F87E5004E0B0F87E1068 +:1033C000491CA0F87E1000F01AFD0126F8B1A08873 +:1033D000000506D5A06890F86A1011B1A0F87650E3 +:1033E00015E0A068B0F87610491CA0F8761000F03F +:1033F000F5FCA068B0F87610B0F87820914206D3BA +:10340000A0F8765080F82261E078DFF785FD20791A +:1034100010F0600F08D0A06890F8681021B980F80B +:1034200068600121FEF71EFD1FB9FFF778FFFFF767 +:1034300090F93846FEF74AFFBDE8F041F4F76CBB5F +:10344000AF4A51789378194313D1114601288968FE +:1034500008D01079400703D591F86700072808D0F5 +:1034600001207047B1F84800098E884201D8FEF764 +:103470008BB900207047A249C2788968012A06D01A +:103480005AB1182A08D1B1F8F810FAF77ABCB1F895 +:103490000A114172090A81727047D1F800118988B6 +:1034A0004173090A8173704770B5954C05460E4605 +:1034B000A0882843A080A80703D5E80700D0FFDF35 +:1034C000E660E80700D02661A80719D5F07806283D +:1034D00002D00B2814D10BE0A06890F864101829D2 +:1034E0000ED10021E0E92A11012100F83E1C07E07D +:1034F000A06890F86410122902D1002180F86A10A7 +:10350000280601D50820A07068050AD5A068828821 +:10351000B0F85810304600F081FC3046BDE87040ED +:10352000A9E762E43EB505466846F4F7C0FA00B97B +:10353000FFDF2221009802F0A0F803210098FAF79B +:1035400011FB0098017821F0100101702946FAF76B +:103550002EFB6B4C192D71D2DFE805F020180D3EC3 +:10356000C8C8C91266C8C9C959C8C8C8C8BBC9C96A +:1035700071718AC89300A168009891F8FD1003E06A +:10358000A168009891F8CE100171B0E0A068D0F861 +:1035900004110098491CFAF756FBA8E0A1680098AE +:1035A000D1F8002192790271D1F80021128942717B +:1035B000120A8271D1F800215289C271120A027274 +:1035C000D1F8002192894272120A8272D1F8001158 +:1035D000C989FAF70FFB8AE0A068D0F800110098BB +:1035E000091DFAF73DFBA068D0F8001100980C31D6 +:1035F000FAF740FBA068D0F8001100981E31FAF7E6 +:103600003FFBA1680098C031FAF748FB6FE06269A0 +:1036100000981178017191884171090A817151886E +:10362000C171090A017262E03649D1E90001CDE9B0 +:10363000010101A90098FAF74BFB58E056E0A06899 +:10364000B0F840100098FAF755FBA068B0F8CE101B +:103650000098FAF753FBA068B0F844100098FAF706 +:1036600041FBA068B0F8D0100098FAF73FFB3EE0AD +:10367000A268009892F81811017192F8191141711D +:1036800035E0A06890F8FB00F9F729FF01460098A3 +:10369000FAF773FBA06890F8FA0000F033FA70B103 +:1036A000A06890F8540000F02DFA40B1A06890F89E +:1036B000FA1090F85400814201D0002002E0A06886 +:1036C00090F8FA00F9F70BFF01460098FAF751FB62 +:1036D0000DE0A06890F8F5100098FAF772FBA0686A +:1036E00090F8F4100098FAF770FB00E0FFDFF4F7B1 +:1036F000F1F900B9FFDF0098FFF7BDFE3EBD000005 +:1037000090010020BC5C0200F948806890F8FA1033 +:1037100009B990F8541080F8541090F8FB1009B9CA +:1037200090F8551080F855100020FEF771BEF8B5DE +:10373000EF4E00250446B060B5807570B5703570E9 +:103740000088F4F7B1F9B0680088F4F7D3F9B4F859 +:10375000E000B168401C82B201F15800F9F7D5F9D8 +:1037600000B1FFDF94F86500242809D1B4F858109F +:10377000B4F8F800081A00B2002801DB707830B104 +:1037800094F8640024280AD0252808D015E0FFF713 +:10379000BBFF84F86550B16881F87D500DE0B4F846 +:1037A0005810B4F8F800081A00B2002805DB707849 +:1037B00018B9FFF7A9FF84F86450A4F8E050FEF7A9 +:1037C0005EFD00281CD1B06890F8CC00FE2801D026 +:1037D000FFF7A8FEC7480090C74BC84A21462846B5 +:1037E000F7F766FFB0680023052190F86420583091 +:1037F000F9F7D4FB002803D0BDE8F840F7F7F3BC95 +:10380000F8BD10B5FEF73BFD20B10020BDE810402B +:103810000146C2E5BDE81040F7F7D0BF70B50C46D1 +:10382000064615464FF4A871204601F048FF268051 +:1038300005B9FFDF2868C4F800016868C4F804010E +:10384000A868C4F84C0191E4EFF7DDB92DE9F04127 +:103850000D4607460621EFF7CDF8041E3DD0D4F8FB +:103860004C110026087858B14A8821888A4207D12D +:1038700009280FD00E2819D00D2826D008283ED0B0 +:1038800094F82201D0B36E701020287084F8226161 +:10389000AF809FE06E7009202870D4F84C01416819 +:1038A00069608168A9608089A88133E00846EFF7E4 +:1038B000D3F90746EEF77FFE70B96E700E202870C0 +:1038C000D4F84C014068686011E00846EFF7C4F98D +:1038D0000746EEF770FE08B1002090E46E700D20F0 +:1038E0002870D4F84C014168696000892881D4F8B7 +:1038F0004C0106703846EEF758FE6BE00EE06E7035 +:1039000008202870D4F84C01416869608168A9607A +:10391000C068E860D4F84C0106705BE094F82401BC +:10392000A0B16E70152028700BE000BF84F82461F0 +:10393000D4F826016860D4F82A01A860B4F82E01F2 +:10394000A88194F824010028F0D143E094F83001D4 +:1039500070B16E701D20287084F83061D4F8320187 +:103960006860D4F83601A860B4F83A01A88131E063 +:1039700094F83C0140B16E701E20287084F83C61C0 +:10398000D4F83E01686025E094F81C0170B16E70B7 +:103990001B20287005E000BF84F81C61D4F81E01CC +:1039A000686094F81C010028F6D113E094F84201F5 +:1039B000002892D06E701620287007E084F84261CB +:1039C000D4F844016860B4F84801288194F84201B1 +:1039D0000028F3D1012012E4454A5061D1707047AC +:1039E00070B50D4604464EE0B4F8E000401CA4F863 +:1039F000E000B4F87E00401CA4F87E00204600F0F1 +:103A0000FEF9B8B1B4F87600401CA4F87600204660 +:103A100000F0E4F9B4F87600B4F87810884209D3DD +:103A20000020A4F87600012084F822013048C078F4 +:103A3000DFF772FA94F8880020B1B4F88400401CD3 +:103A4000A4F8840094F88C0020B1B4F88A00401CDB +:103A5000A4F88A0094F8FC0040B994F86720002389 +:103A6000012104F15800F9F799FA20B1B4F8820065 +:103A7000401CA4F882002046FEF795FFB4F85800D9 +:103A8000401CA4F858006D1EADB2ADD249E5184AED +:103A9000C2E90601704770B50446B0F87E0094F89C +:103AA0006810D1B1B4F880100D1A2D1F94F87C0065 +:103AB00040B194F864200023092104F15800F9F77B +:103AC0006DFA70B1B4F87660204600F098F938B11C +:103AD000B4F87800801B001F03E0C0F10205E5E7A1 +:103AE0002846A84200DA0546002D09DC002018E52A +:103AF000900100209B33020041340200A9340200EF +:103B0000A8B20EE510F00C0000D00120704710B5EF +:103B1000012808D0022808D0042808D0082806D098 +:103B2000FFDF204610BD0124FBE70224F9E7032450 +:103B3000F7E710B5EF4C0421A068FEF793F9A068F1 +:103B400090F84E10012903D0BDE8104000F098B95C +:103B5000022180F84E1010BD70B5E64CA06890F8B8 +:103B600064001F2804D0607840F001006070D8E441 +:103B70002069FAF7F4F8D8B1206901220179407977 +:103B800001F0070161F30705294600F0070060F323 +:103B90000F21A06880F888200022A0F8842023222A +:103BA00000F8642FD0F8B400BDE87040FEF76ABD9D +:103BB0000120FEF76CFFBDE870401E20FEF728BC18 +:103BC00070B5CC4C00230A21A06890F864205830CE +:103BD000F9F7E4F910B32069FAF79CF8A8B1A568E1 +:103BE0002069FAF793F82887A5682069FAF78AF818 +:103BF0006887A5682069FAF78BF8A887A568206907 +:103C0000FAF782F8E887FEF75DFDA168002081F8E9 +:103C1000880081F86400BDE870408AE7607840F071 +:103C2000010060707DE4B34810B580680088EFF74C +:103C300013F8BDE81040EEF7A9BC10B5AD4CA36871 +:103C400093F86400162802D00220607010BD6078DE +:103C50000028FBD1D3F80001002200F11E010E3034 +:103C6000B033F9F715F9A0680021C0E92811012146 +:103C700080F86910182180F8641010BD10B59D4CB3 +:103C8000A06890F86410132902D00220607010BD63 +:103C900061780029FBD1D0F8001100884988814261 +:103CA00000D0FFDFA068D0F8001120692631FAF7B4 +:103CB00002F8A1682069C431FAF705F8A168162056 +:103CC00081F8640010BD10B58A4C207900071BD51F +:103CD0006078002818D1A068002190F8CC00FEF789 +:103CE0001CFEA06890F8CC00FE2800D1FFDFA06881 +:103CF000FE2180F8CC1090F86710082904D1022129 +:103D00002170002180F8671010BD70B5794D242115 +:103D10000024A86890F86520212A05D090F8642036 +:103D2000232A18D0FFDF8EE590F8FA2012B990F818 +:103D3000FB202AB180F86510A86880F88C4082E5E5 +:103D400000F8654F047690F8B1000028F4D0002008 +:103D5000FEF75EFBF0E790F8FA2012B990F8FB202E +:103D60002AB180F86410A86880F888406BE580F874 +:103D700064400020FEF74CFBF5E770B55D4C002574 +:103D8000A068D0F8001103884A889A4218D10978AF +:103D9000042915D190F86420002319465830F9F70A +:103DA000FDF800B9FFDFA06890F89010890703D4F0 +:103DB000012180F8641003E000F8885F806F0570CF +:103DC000A0680023194690F865205830F9F7E6F806 +:103DD000002802D0A06880F88C5034E5B0F8782034 +:103DE000B0F876108A4201D3511A00E0002182888F +:103DF000521D8A4202D3012180F87C10704710B511 +:103E000090F86A1041B990F86420002306215830D8 +:103E1000F9F7C4F8002800D0012010BD70B5114496 +:103E2000344D891D8CB2C078A968012806D040B1F4 +:103E3000182805D191F8FA0038B109E0A1F80A4133 +:103E400001E5D1F800018480FDE491F8FB1091B107 +:103E5000FFF758FE80B1A86890F85400FFF752FEB3 +:103E600050B1A86890F8FA1090F85420914203D00D +:103E700090F8FB0000B90024A868A0F8F840E2E43C +:103E80002DE9F0411B4DA86800F58E740188618111 +:103E9000018EA181818EE181018FB0F84420914291 +:103EA00000D311462182828FB0F846108A4200D298 +:103EB0001146618290F85500FFF724FE4FF4296700 +:103EC00028B1608A3E46B84200D906466682A86894 +:103ED00090F85400FFF716FE20B1E089B84200D9EF +:103EE0000746E78101202072E878BDE8F041DFF75E +:103EF00013B800009001002070B58D4C0829207A7D +:103F000062D2DFE801F0041959592561615978B18D +:103F1000F2F73CFD01210846F2F7DFFEF3F713FD4F +:103F20000020A072F2F7E5FDBDE87040F3F766B837 +:103F3000BDE87040F0F7AABDD4E90001F0F79DFBA1 +:103F40002060A07A401CC0B2A07228281CD370BD8B +:103F5000A07A0025401EC6B2E0683044F3F73FF96E +:103F600010B9E1687F208855A07A272828BF01254D +:103F70002846F3F751FCA07A282809D2401CC0B289 +:103F8000A072282828BF70BDBDE87040F2F7B1BD0F +:103F9000207A00281CBF012000F085F8F2F7A0FF6E +:103FA000F3F71EF80120E07262480078DEF7B4FFF4 +:103FB000BDE87040F0F76ABD002808BF70BD002062 +:103FC000BDE8704000F06FB8FFDF70BD10B5584C11 +:103FD000207A002804BF0C2010BD00202072E0725F +:103FE000607AF1F7AEF9607AF1F7F9FB607AF0F7F1 +:103FF00024FE00280CBF1F20002010BD002270B539 +:104000004B4C06460D46207A68B12272E272607A05 +:10401000F1F797F9607AF1F7E2FB607AF0F70DFEBD +:10402000002808BFFFDF4348E560067070BD70B52B +:10403000050007D0A5F5E8503F494C3881429CBFA8 +:10404000122070BD3A4CE068002804BF092070BD02 +:10405000207A00281CBF0C2070BD3848F0F791FD75 +:104060006072202804BF1F2070BDF0F705FE20609D +:10407000002D1CBF284420600120656020720020B4 +:1040800000F011F8002070BD2949CA7A002A04BF47 +:10409000002070471F22027000224270CB684360EC +:1040A000CA72012070472DE9F04184B00746F0F74D +:1040B000E3FD1F4D8046414668682C6800EB800098 +:1040C00046002046F1F7F1FAB04206DB6868811B32 +:1040D0004046F0F7D2FA0446286040F23476214692 +:1040E0004046F1F7E2FAB04204DA31464046F0F7D2 +:1040F000C4FA044600208DF800004FF4DD60039000 +:1041000004208DF80500002F14BF012003208DF836 +:10411000040068460294F0F77EFF687A6946F0F77B +:10412000F5FF002808BFFFDF04B0BDE8F081000004 +:104130004C130020B0010020B5EB3C00F93E02001A +:104140002DE9F0410C4612490D68114A1149083217 +:104150001160A0F12001312901D301200CE0412898 +:1041600010D040CC0C4F94E80E0007EB8000241FC9 +:1041700050F8807C3046B84720600548001D056037 +:10418000BDE8F0812046DDF743F8F5E706207047EB +:104190001005024001000001C45C020010B5534844 +:1041A000F1F7CAFD00B1FFDF5048401CF1F7C4FD34 +:1041B000002800D0FFDF10BD2DE9F14F4C4ED6F89E +:1041C00000B001274948F1F7BFFDDFF8208128B989 +:1041D0005FF0000708F10100F1F7CCFD454C002528 +:1041E0004FF0030901206060C4F80051C4F8045185 +:1041F000009931602060DFF800A118E0DAF80000D3 +:10420000C00614D50E2000F064F8EFF3108010F013 +:10421000010072B600D00120C4F80493D4F8001154 +:1042200019B9D4F8041101B920BF00B962B6D4F8A5 +:10423000000118B9D4F804010028DFD0D4F8040133 +:104240000028CFD137B1C6F800B008F10100F1F76E +:104250007BFD11E008F10100F1F776FD0028B9D1EE +:10426000C4F80893C4F80451C4F800510E2000F0BB +:1042700030F81E48F1F77EFD0020BDE8F88F2DE9EB +:10428000F0438DB00D46064600240DF110090DF1E6 +:10429000200817E004EB4407102255F82710684661 +:1042A00001F06CF905EB870710224846796801F0A8 +:1042B00065F96846FFF780FF10224146B86801F0B3 +:1042C0005DF9641CB442E5DB0DB00020BDE8F0836D +:1042D00072E7002809DB00F01F020121914040092C +:1042E000800000F1E020C0F880127047B10100208A +:1042F00004E5004000E0004010ED00E0B14900207E +:104300000870704770B5B04D01232B60AF4B1C682F +:10431000002CFCD0002407E00E6806601E68002E0A +:10432000FCD0001D091D641C9442F5D300202860B8 +:1043300018680028FCD070BD70B5A24E0446A44D8C +:104340003078022800D0FFDFAC4200D3FFDF716974 +:10435000A048012903D847F23052944201DD0322DC +:104360004271491C7161291BC1609A497078F0F74C +:10437000CDFE002800D1FFDF70BD70B5914C0D4619 +:104380006178884200D0FFDF914E082D4BD2DFE8E4 +:1043900005F04A041E2D4A4A4A382078022800D0E7 +:1043A000FFDF03202070A078012801D020B108E0B1 +:1043B000A06800F039FE04E004F1080007C8FFF728 +:1043C000A1FF05202070BDE87040F0F75FBBF0F75B +:1043D00053FC01466068F1F768F9B04202D26169A6 +:1043E00002290BD30320F1F746FC12E0F0F744FC5E +:1043F00001466068F1F759F9B042F3D2BDE8704068 +:104400009AE7207802280AD0052806D0FFDF04208A +:104410002070BDE8704000F0CAB8022000E0032020 +:10442000F1F729FCF3E7FFDF70BD70B50546F0F743 +:1044300023FC644C60602078012800D0FFDF6549D0 +:10444000012008700020087104208D6048716048C8 +:10445000C860022020706078F0F758FE002800D174 +:10446000FFDF70BD10B5574C207838B90220F1F746 +:1044700018FC18B90320F1F714FC08B1112010BD85 +:104480005548F0F77EFB6070202804D00120207092 +:104490000020606110BD032010BD2DE9F0471446D7 +:1044A000054600EB84000E46A0F1040800F0CFFDA5 +:1044B00007464FF0805001694F4306EB8401091F06 +:1044C000B14201D2012100E0002189461CB10069FE +:1044D000B4EB900F02D90920BDE8F0872846DCF73D +:1044E000EBFE90B9A84510D3BD4205D2B84503D222 +:1044F00045EA0600800701D01020EDE73046DCF7E2 +:10450000DBFE10B9B9F1000F01D00F20E4E733480A +:1045100033490068884205D0224631462846FFF7D5 +:10452000F1FE14E0FFF79EFF0028D5D125480021B9 +:104530008560C0E90364817000F06FF810B14FF43A +:10454000A97000E0292060431830FFF76EFF0020BB +:10455000C2E770B505464FF0805004696C432046B1 +:10456000DCF7AAFE08B10F2070BD00F070FDA84274 +:1045700001D8102070BD194819490068884203D03D +:10458000204600F051FD10E0FFF76CFF0028F1D14C +:104590000C4801218460817000F03FF808B1114897 +:1045A00000E011481830FFF740FF002070BD10B543 +:1045B000044C6078F0F741FB00B9FFDF0020207069 +:1045C00010BD0000B401002004E5014000E40140FA +:1045D000105C0C005C1300207B43020054000020A0 +:1045E000BEBAFECA645E0100084C01004FF0805064 +:1045F000D0F830010A2801D0002070470120704710 +:1046000000B5FFF7F3FF20B14FF08050D0F8340130 +:1046100008B1002000BD012000BD4FF08050D0F84F +:104620003011062905D0D0F83001401C01D00020FF +:104630007047012070474FF08050D0F830010828B3 +:1046400001D0002070470120704700B5FFF7E5FF5B +:1046500048B14FF08050D0F83411062905D3D0F876 +:104660003401401C01D0002000BD012000BD00B578 +:10467000FFF7D3FF58B14FF08050D0F8341106291E +:1046800005D3D0F83401401C01D0012000BD00202A +:1046900000BD00007B49096801600020704779492E +:1046A00008600020704701218A0720B1012804D04A +:1046B00042F204007047916700E0D1670020704724 +:1046C00071490120086042F20600704708B50423D2 +:1046D0006D4A1907103230B1C1F80433106840F048 +:1046E000010010600BE0106820F001001060C1F8BC +:1046F00008330020C1F808016448006800900020D9 +:1047000008BD011F0B2909D85F4910310A6822F042 +:104710001E0242EA400008600020704742F2050095 +:1047200070470F2809D8584910310A6822F470627E +:1047300042EA002008600020704742F205007047FE +:10474000000100F18040C0F804190020704700010A +:1047500000F18040C0F8081900207047000100F106 +:104760008040D0F80009086000207047012801D976 +:1047700007207047464A52F8200002680A43026048 +:1047800000207047012801D907207047404A52F89D +:10479000200002688A43026000207047012801D986 +:1047A000072070473A4A52F820000068086000204D +:1047B0007047020037494FF0000003D0012A01D0B2 +:1047C000072070470A607047020033494FF000002D +:1047D00003D0012A01D0072070470A60704708B54E +:1047E0004FF40072510510B1C1F8042308E0C1F87C +:1047F00008230020C1F8240124481C3000680090E0 +:10480000002008BD08B58022D10510B1C1F80423ED +:1048100008E0C1F808230020C1F81C011B4814302F +:1048200000680090002008BD08B54FF48072910523 +:1048300010B1C1F8042308E0C1F808230020C1F832 +:1048400020011248183000680090002008BD0D4972 +:10485000383109680160002070474FF08041002026 +:10486000C1F80801C1F82401C1F81C01C1F82001F8 +:104870004FF0E020802180F800140121C0F80011E1 +:10488000704700000004004000050040080100409F +:10489000885D020078050040800500406249634B56 +:1048A0000A6863499A42096801D1C1F310010160A5 +:1048B000002070475C495D4B0A685D49091D9A42BA +:1048C00001D1C0F310000860002070475649574BD3 +:1048D0000A68574908319A4201D1C0F310000860B4 +:1048E0000020704730B5504B504D1C6842F2080311 +:1048F000AC4202D0142802D203E0112801D318469A +:1049000030BDC3004B481844C0F81015C0F814253A +:10491000002030BD4449454B0A6842F209019A42E1 +:1049200002D0062802D203E0042801D308467047CB +:10493000404A012142F83010002070473A493B4B71 +:104940000A6842F209019A4202D0062802D203E024 +:10495000042801D308467047364A012102EBC00003 +:1049600041600020704770B52F4A304E314C1568B9 +:1049700042F2090304EB8002B54204D0062804D2B7 +:10498000C2F8001807E0042801D3184670BDC1F32F +:104990001000C2F80008002070BD70B5224A234EF6 +:1049A000244C156842F2090304EB8002B54204D09E +:1049B000062804D2D2F8000807E0042801D31846DC +:1049C00070BDD2F80008C0F310000860002070BD70 +:1049D000174910B50831184808601120154A002100 +:1049E00002EBC003C3F81015C3F81415401C1428BB +:1049F000F6D3002006E0042804D302EB8003C3F8BA +:104A0000001807E002EB8003D3F80048C4F3100459 +:104A1000C3F80048401C0628EDD310BD04490648E1 +:104A2000083108607047000054000020BEBAFECA7A +:104A300000F5014000F001400000FEFF834B1B68C1 +:104A400003B19847BFF34F8F81480168814A01F451 +:104A5000E06111430160BFF34F8F00BFFDE710B568 +:104A6000EFF3108010F0010F72B601D0012400E0C6 +:104A7000002400F0E1F850B1DCF7BEFCEFF7C1FE16 +:104A8000F1F79BF8E7F75EFA73490020086004B974 +:104A900062B6002010BD2DE9F0410C460546EFF34B +:104AA000108010F0010F72B601D0012600E0002640 +:104AB00000F0C2F820B106B962B60820BDE8F08166 +:104AC000DCF78EFBDCF79CFC024600200123470943 +:104AD000BF0007F1E02700F01F01D7F80071CF40B9 +:104AE000F9071BD0202803D222FA00F1C90727D1E9 +:104AF00041B2002904DB01F1E02191F8001405E046 +:104B000001F00F0101F1E02191F8141D4909082974 +:104B100016D203FA01F717F0EC0F11D0401C6428ED +:104B2000D5D3E7F7EDF94D4A4D490020E7F730FAC4 +:104B300049494C4808602046DCF7C5FB60B904E0F1 +:104B400006B962B641F20100B8E7404804602DB1F1 +:104B50002846DCF705FC18B110242CE0424D19E082 +:104B60002878022802D94FF4805424E00724002832 +:104B7000687801D0F8B908E0E8B120281BD8A878F7 +:104B8000212818D8012816D001E0A87898B9E8782B +:104B90000B2810D83549802081F8140DDCF730FC43 +:104BA0002946F0F7F0FFEFF7EBFD00F083FA284617 +:104BB000DCF7F4FB044606B962B61CB1FFF74FFF01 +:104BC00020467BE7002079E710B5044600F034F872 +:104BD00000B101202070002010BD25490860002090 +:104BE000704770B50C4623490D682249224E0831A2 +:104BF0000E60102807D011280CD012280FD01328CF +:104C000011D0012013E0D4E90001FFF744FF35463D +:104C100020600DE0FFF723FF0025206008E02068FA +:104C2000FFF7D2FF03E012492068086000202060EF +:104C30001048001D056070BD07480A490068884299 +:104C400001D101207047002070470000CC010020F6 +:104C50000CED00E00400FA0554000020F8130020D9 +:104C600000000020BEBAFECA905D02000BE000E02A +:104C700004000020100502400100000100B59B491E +:104C800002282ED021DC10F10C0F08BFF42028D010 +:104C90000FDC10F1280F08BFD82022D010F1140F1C +:104CA00008BFEC201DD010F1100F08BFF02018D065 +:104CB00021E010F1080F08BFF82012D010F1040F06 +:104CC0000CBFFC2000280CD015E0A0F10300062842 +:104CD00011D2DFE800F00E0C0A080503082000E0FE +:104CE0000720086000BD0620FBE70520F9E7042047 +:104CF000F7E70320F5E7FFDF00BD00B57C49012899 +:104D000008BF03200CD0022808BF042008D00428C4 +:104D100008BF062004D0082816BFFFDF052000BD0D +:104D2000086000BD70B505460C4616461046F2F701 +:104D3000C1FD022C08BF4FF47A7105D0012C0CBFC5 +:104D40004FF4C86140F6340144183046F2F7ECFDE8 +:104D5000204449F6797108444FF47A71B0FBF1F0C0 +:104D6000281A70BD70B505460C460846F2F7BBFD23 +:104D7000022C08BF40F24C4105D0012C0CBF40F67C +:104D800034014FF4AF5149F6CA62511A08444FF446 +:104D90007A7100F2E140B0FBF1F0281A801E70BD7C +:104DA00070B5064615460C460846F2F79CFD022DE6 +:104DB00008BF4FF47A7105D0012D0CBF4FF4C861C4 +:104DC00040F63401022C08BF40F24C4205D0012CC1 +:104DD0000CBF40F634024FF4AF52891A084449F62A +:104DE000FC6108444FF47A71B0FBF1F0301A70BDE9 +:104DF00070B504460E460846F2F75CFD054630469F +:104E0000F2F792FD28444AF2AB3108444FF47A712C +:104E1000B0FBF1F0201A801E70BD2DE9F04107466D +:104E20001E460D4614461046082A16BF04284EF6A4 +:104E30002830F2F73FFD07EB4701C1EBC71100EB4C +:104E4000C100022D08BF40F24C4105D0012D0CBF1E +:104E500040F634014FF4AF5147182846F2F743FDAE +:104E6000381A4FF47A7100F6B730B0FBF1F52046EE +:104E7000F2F70EFD28443044401DBDE8F08170B5C6 +:104E8000054614460E460846F2F714FD05EB4502AA +:104E9000C2EBC512C0EBC2053046F2F745FD2D1A34 +:104EA0002046082C16BF04284EF62830F2F702FDE3 +:104EB00028444FF47A7100F6B730B0FBF1F5204684 +:104EC000F2F7E6FC2844401D70BD0A49082818BFC7 +:104ED0000428086803BF20F46C5040F4444040F0BC +:104EE000004020F000400860704700000C150040B2 +:104EF00010150040401700402DE9FE430C46804647 +:104F0000F8F744FE074698F80160204601A96A4672 +:104F1000EDF72DFB05000DD0012F02D00320BDE8D9 +:104F2000FE83204602AA0199EDF743FA0298B0F8F1 +:104F300003000AE0022F14D1042E12D3B8F80300A4 +:104F4000BDF80020011D914204D8001D80B2A919AE +:104F5000814202D14FF00000E1E702D24FF00100A0 +:104F6000DDE74FF00200DAE7C2790D2341B342BB1F +:104F70008188012904D94908818004BF01228280E7 +:104F80000168012918BF002930D001686FEA0101CA +:104F9000C1EBC10202EB011281796FEA010101EB61 +:104FA0008103C3EB811111444FEA91420160818872 +:104FB000B2FBF1F301FB132181714FF0010102E01B +:104FC0001AB14FF00001C17170478188FF2908D2E2 +:104FD0004FF6FF7202EA41018180FF2984BFFF2260 +:104FE00082800168012918BF0029CED10360CCE777 +:104FF000817931B1491E11F0FF0181711CBF002080 +:1050000070470120704710B50121C1718171818005 +:1050100004460421F0F712FF002818BF10BD2068D5 +:10502000401C206010BD00000B4A022111600B499A +:105030000B68002BFCD0084B1B1D1860086800286B +:10504000FCD00020106008680028FCD070474FF0AA +:10505000805040697047000004E5014000E40140D1 +:1050600002000B464FF00000014620D0012A04D078 +:10507000022A04D0032A0DD103E0012002E002201D +:1050800015E00320072B05D2DFE803F00406080A29 +:105090000C0E100007207047012108E0022106E0F5 +:1050A000032104E0042102E0052100E00621EFF7DE +:1050B00086BD0000F9480521817000210170417012 +:1050C0007047F7490A78012A05D0CA681044C860B9 +:1050D0004038F0F7B7BA8A6810448860F8E70028CB +:1050E00019D00378EF49F04A13B1012B0ED011E02B +:1050F0000379012B00D06BB943790BB1012B09D196 +:105100008368643B8B4205D2C0680EE00379012BB3 +:1051100002D00BB10020704743790BB1012BF9D1BC +:10512000C368643B8B42F5D280689042F2D801207C +:105130007047DB4910B501220A700279A2B1002242 +:105140000A71427992B104224A718268D34C523278 +:105150008A60C0681434C8606060EFF78DFDCF4985 +:1051600020600220887010BD0322E9E70322EBE7EC +:1051700070B5CB4D044600202870207988B10020FE +:105180002871607978B10420C44E6871A168F06814 +:10519000EFF773FAA860E0685230E8600320B0705F +:1051A00070BD0120ECE70320EEE72DE9F041054654 +:1051B0000226F0F773F9006800B1FFDFB74C012752 +:1051C0003DB12878B0B1012805D0022810D00328BD +:1051D00013D027710CE06868C82807D3F0F799FA54 +:1051E00020B16868FFF76DFF012603E0002601E0AB +:1051F00000F05EF93046BDE8F08120780028F7D154 +:105200006868FFF76CFF0028E3D06868017879B11F +:10521000A078042800D0FFDF01216868FFF7A8FF0D +:105220009F49E078EFF772FF0028E1D1FFDFDFE769 +:10523000FFF77FFF6770DBE72DE9F047974C884663 +:10524000E178884200D0FFDFDFF850920025012787 +:10525000934E09F11409B8F1080F75D2DFE808F090 +:10526000040C28527A808D95A078032802D0022859 +:1052700000D0FFDFBDE8F087A078032802D0022825 +:1052800000D0FFDF0420A07025712078002878D19D +:10529000FFF717FF3078012806D0B068E06000F013 +:1052A00027F92061002060E0E078EFF72CFEF5E7B9 +:1052B000A078032802D0022800D0FFDF2078002841 +:1052C0006DD1A078032816D0EFF7D6FC01464F46E3 +:1052D000D9F80000F0F7E9F900280EDB796881427F +:1052E0000BDB081AF0606E49E078EFF70FFF00283B +:1052F000C0D1FFDFBEE7042028E00420F0F7BBFCAC +:10530000A570B7E7A078032802D0022800D0FFDFFD +:10531000207888BBA078032817D0EFF7ADFC0146B2 +:105320004F46D9F80000F0F7C0F90028E5DB7968AE +:105330008142E2DB081AF0605949E078EFF7E6FEB7 +:10534000002897D1FFDF95E740E00520F0F793FCB8 +:10535000A7708FE7A078042800D0FFDF022004E0C8 +:10536000A078042800D0FFDF0120A1688847FFF75C +:105370001CFF054630E004E011E0A078042800D0CE +:10538000FFDFBDE8F04700F093B8A078042804D010 +:10539000617809B1022800D0FFDF207818B1BDE89C +:1053A000F04700F08EB8207920B10620F0F763FCBA +:1053B0002571CDE7607838B13949E078EFF7A6FE7E +:1053C00000B9FFDF657055E70720BFE7FFDF51E752 +:1053D0003DB1012D03D0FFDF022DF9D14AE70420B2 +:1053E000C3E70320C1E770B5050004D02B4CA078BB +:1053F000052806D101E0102070BD0820F0F751FC0F +:1054000008B1112070BD2948EFF7BBFBE0702028E0 +:1054100006D00121F0F777FA0020A560A07070BDDA +:10542000032070BD1D4810B5017809B1112010BDD1 +:105430008178052906D0012906D029B10121017002 +:10544000002010BD0F2010BD00F03BF8F8E770B54C +:10545000124C0546A07808B1012809D155B128465B +:10546000FFF73DFE40B1287840B1A078012809D06F +:105470000F2070BD102070BD072070BD2846FFF7BB +:1054800058FE03E000212846FFF772FE0449E07849 +:10549000EFF73CFE00B9FFDF002070BDD001002017 +:1054A0006C1300203D860100FF1FA1073952020046 +:1054B0000A4810B5006900F013F8BDE81040EFF796 +:1054C000E5BA064810B5C078EFF7B7FB00B9FFDFC3 +:1054D0000820F0F7D0FBBDE81040EBE5D00100203C +:1054E0000C490A6848F202139A4302430A60704763 +:1054F000084A116848F2021301EA03009943116057 +:1055000070470246044B10201344FC2B01D8116055 +:1055100000207047C80602400018FEBF7047704761 +:105520007047704740EA010310B59B070FD1042A6A +:105530000DD310C808C9121F9C42F8D020BA19BA5E +:10554000884201D9012010BD4FF0FF3010BD1AB1C3 +:10555000D30703D0521C07E0002010BD10F8013B18 +:1055600011F8014B1B1B07D110F8013B11F8014B3F +:105570001B1B01D1921EF1D1184610BD032A40F227 +:10558000308010F0030C00F0158011F8013BBCF1E5 +:10559000020F624498BF11F801CB00F8013B38BFFD +:1055A00011F8013BA2F1040298BF00F801CB38BF0B +:1055B00000F8013B11F0030300F02580083AC0F029 +:1055C000088051F8043B083A51F804CBA0E80810D1 +:1055D000F5E7121D5CBF51F8043B40F8043BAFF304 +:1055E0000080D20724BF11F8013B11F801CB48BF5E +:1055F00011F8012B24BF00F8013B00F801CB48BF94 +:1056000000F8012B704710B5203AC0F00B80B1E8CC +:105610001850203AA0E81850B1E81850A0E81850E7 +:10562000BFF4F5AF5FEA027C24BFB1E81850A0E8F0 +:10563000185044BF18C918C0BDE810405FEA827C0A +:1056400024BF51F8043B40F8043B08BF7047D20721 +:1056500028BF31F8023B48BF11F8012B28BF20F8C2 +:10566000023B48BF00F8012B704702F0FF0343EAFA +:10567000032242EA024200F002B84FF0000204297D +:10568000C0F0128010F0030C00F01B80CCF1040C71 +:10569000BCF1020F18BF00F8012BA8BF20F8022BA5 +:1056A000A1EB0C0100F00DB85FEAC17C24BF00F84B +:1056B000012B00F8012B48BF00F8012B70474FF079 +:1056C000000200B5134694469646203922BFA0E852 +:1056D0000C50A0E80C50B1F12001BFF4F7AF09075E +:1056E00028BFA0E80C5048BF0CC05DF804EB89004F +:1056F00028BF40F8042B08BF704748BF20F8022B92 +:1057000011F0804F18BF00F8012B704770477047A9 +:1057100070477047FEDF18490978F9B904207146CF +:1057200008421BD10699154A914217DC06990229B5 +:1057300014DB02394878DF2810D10878FE2807D01A +:10574000FF280BD14FF001004FF000020C4B18471F +:1057500041F201000099019A094B1847094B002BAF +:1057600002D01B68DB6818474FF0FF3071464FF0DE +:105770000002034B1847000028ED00E00060020023 +:105780003D4A020004000020174818497047FFF7FF +:10579000FBFFDBF713FD00BD154816490968884279 +:1057A00003D1154A13605B68184700BD20BFFDE7B1 +:1057B0000F4810490968884210D1104B18684FF003 +:1057C000FF318842F2D080F308884FF020218842D0 +:1057D00004DD0B48026803210A4302600948804740 +:1057E00009488047FFDF000080130020801300205D +:1057F00000100000000000200400002000600200F3 +:1058000014090040C52F000099570200042071467A +:10581000084202D0EFF3098101E0EFF308818869C3 +:1058200002380078102813DB20280FDB2C280BDB34 +:105830000A4A12680A4B9A4203D1602804DB094ADB +:105840001047022008607047074A1047074A104770 +:10585000074A12682C3212681047000054000020DA +:10586000BEBAFECA0514000041410200E34B02002B +:10587000040000200D4B0E4908470E4B0C49084709 +:105880000D4B0B4908470D4B094908470C4B08497C +:1058900008470C4B064908470B4B054908470B4B7B +:1058A000034908470A4B024908470000E1BC0000D1 +:1058B0005DC00000552D0000CF2B00005D2B0000C7 +:1058C000F72D0000211400001B2900004D2F0000BF +:1058D000C911000000210160818070470021016032 +:1058E0004160017270470A6802600B79037170476A +:1058F000959600003F980000A1990000059A0000CD +:105900003F9A0000739A0000AD9A0000DD9A0000F3 +:10591000579B00008D970000C5990000A71200005A +:10592000C14300000D44000073440000FF44000028 +:1059300023460000E546000017470000EF4700003F +:1059400087480000DB480000C1490000E149000031 +:10595000C3160000E7160000171600006B160000C3 +:1059600019170000AD17000047600000F761000044 +:10597000BD650000D56600005F670000DD670000C0 +:105980004168000061690000316A00009D6A000002 +:10599000034A0000094A0000134A00007B4A000045 +:1059A000A74A0000634C00008D4C0000C54C00006D +:1059B0002F4D0000194E00002F4E00003144000012 +:1059C000A7120000A7120000A7120000A7120000F3 +:1059D000A7120000A7120000A7120000A3250000D4 +:1059E000292600004526000061260000EF27000060 +:1059F0008B26000095260000D7260000F92600001F +:105A0000D527000017280000A7120000A7120000E9 +:105A1000CB830000EB830000F58300002F8400009F +:105A20005D8400004D850000DB850000EF850000EF +:105A30003D86000053870000F9880000218A00009D +:105A40004F730000398A0000A7120000A71200005F +:105A5000D9B5000043B7000097B7000003B80000B5 +:105A6000B3B80000010000000000000010011001A8 +:105A70003A0200001A02000405060000FFFFFFFFC3 +:105A80000000FFFFCDAD0000233D000049210000D4 +:105A900099730000118F000000000000D5910000F4 +:105AA00099910000C3910000AB910000000002003A +:105AB00000000000000200000000000000010000E3 +:105AC000000000007781000057810000C5810000C0 +:105AD00025250000E72400000725000037A9000065 +:105AE00063A900006BAB000041590000E581000094 +:105AF0000000000015820000732500000000000077 +:105B000000000000000000004DAA0000000000009E +:105B1000D55900000300000001555555D6BE898EA9 +:105B200000006306630C631200000703AB054F0817 +:105B3000000053044308330C00000000900A0000EA +:105B4000900A0000C3560000C35600009D430000A9 +:105B500079AC00001B7600005B2000001D380200BD +:105B6000E1A401000157000001570000BF430000FD +:105B7000DBAC00009F760000CD2000004938020019 +:105B8000F5A4010070017001400038005C002400A1 +:105B90005001080200000300656C74620000000000 +:105BA000000000000000000000000000870000006E +:105BB000000000000000000000000000BE83605AEA +:105BC000DB0B376038A5F5AA9183886C01000000D3 +:105BD000BB31010081400100000000010206030406 +:105BE00005000000070000000000000006000000A3 +:105BF0000A0000003200000073000000B400000042 +:105C0000EB8F01006F1F020017F90000D9B70100E8 +:105C1000F3F70100D9B70100B5FA000097B9010008 +:105C2000E9F3010097B90100F1F6000025B9010080 +:105C300011F7010025B9010013F90000EDB70100CB +:105C4000D5EF0100EDB7010067FF000019BC0100AE +:105C5000A7F8010019BC0100F401FA0096006400E5 +:105C60004B0032001E0014000A0005000200010073 +:105C70000049000000000000AAAED7AB154120107B +:105C80000C0802170D010102090901010602091899 +:105C9000180301010909030305555555252627D683 +:105CA000BE898E00F401FA00960064004B003200B9 +:105CB0001E0014000A000500020001002549000032 +:105CC000000000009D480200B5480200CD480200D7 +:105CD000E5480200154902003D49020067490200FB +:105CE0009B490200534502009B4402008D41020083 +:105CF00003550200395D0100495D0100755D010039 +:105D0000475E01004F5E0100615E0100A746020090 +:105D1000C1460200954602009F460200CD460200A1 +:105D20000347020023470200414702004F47020099 +:105D30005D4702006D470200854702009D47020053 +:105D4000B3470200C94702000000000087BA000004 +:105D5000DDBA0000F3BA000061500200B941020050 +:105D60007F420200E7530200255402004F54020014 +:105D7000195C010079600100DF470200054802005C +:105D8000294802004F4802001C0500402005004041 +:105D900000100200B45D020008000020E4010000D1 +:105DA00044110000E85D0200EC01002094110000A5 +:105DB000A0110000011413F8130240200B20040668 +:105DC000441A0102228C2720FB349B5F8012800240 +:105DD0001E101B430B5419042A8608019F0916CB79 +:085DE000327F0B6CF410C000CF +:02000004000FEB +:1040000000000420CDB20F00F5B20F00F7B20F0090 +:10401000F9B20F00FBB20F00FDB20F00000000006C +:10402000000000000000000000000000C1450F007B +:1040300001B30F000000000003B30F00214D0F007B +:10404000354E0F0007B30F0007B30F0007B30F0083 +:1040500007B30F0007B30F0007B30F0007B30F003C +:1040600007B30F0007B30F0007B30F0007B30F002C +:1040700007B30F0007B30F0007B30F0007B30F001C +:1040800007B30F0085720F0007B30F0007B30F00CF +:1040900041730F0007B30F00814B0F0007B30F00F0 +:1040A00007B30F0007B30F0007B30F0007B30F00EC +:1040B00007B30F0007B30F0000000000000000006E +:1040C00007B30F0007B30F0007B30F0007B30F00CC +:1040D00007B30F0007B30F0007B30F0005850F00EC +:1040E00007B30F0007B30F0007B30F000000000075 +:1040F0000000000007B30F000000000007B30F002E +:1041000000000000000000000000000000000000AF +:10411000000000000000000000000000000000009F +:10412000000000000000000000000000000000008F +:10413000000000000000000000000000000000007F +:10414000000000000000000000000000000000006F +:10415000000000000000000000000000000000005F +:10416000000000000000000000000000000000004F +:10417000000000000000000000000000000000003F +:10418000000000000000000000000000000000002F +:10419000000000000000000000000000000000001F +:1041A000000000000000000000000000000000000F +:1041B00000000000000000000000000000000000FF +:1041C00000000000000000000000000000000000EF +:1041D00000000000000000000000000000000000DF +:1041E00000000000000000000000000000000000CF +:1041F00000000000000000000000000000000000BF +:104200000348044B834202D0034B03B11847704765 +:10421000C8860020C8860020000000000548064926 +:104220000B1AD90F01EBA301491002D0034B03B1C4 +:1042300018477047C8860020C8860020000000008C +:1042400010B5064C237843B9FFF7DAFF044B13B1DE +:104250000448AFF300800123237010BDC8860020FE +:10426000000000005CBD0F0008B5044B1BB1044901 +:104270000448AFF30080BDE80840CFE7000000002D +:10428000CC8600205CBD0F00A3F5803A704700BFCC +:10429000154B002B08BF114B9D46FFF7F5FF002182 +:1042A0008B460F461148124A121A00F075F80C4B53 +:1042B000002B00D098470B4B002B00D098470020D4 +:1042C000002104000D000B4800F016F800F040F843 +:1042D0002000290000F074FA00F014F80000080033 +:1042E000000000000000000000000420C88600203C +:1042F000A4CE002025430F00002301461A4618468D +:1043000000F09CB808B50021044600F0CBF8044B3F +:104310001868C36B03B19847204600F029F900BF25 +:1043200058BB0F0038B5084B084D5B1B9C1007D0DD +:10433000043B1D44013C55F804399847002CF9D141 +:10434000BDE8384007F002BCC8860020C4860020C3 +:1043500070B50D4E0D4D761BB61006D0002455F8E5 +:10436000043B01349847A642F9D1094E094D761B0A +:1043700007F0E6FBB61006D0002455F8043B0134E4 +:104380009847A642F9D170BDBC860020BC860020AB +:10439000C4860020BC860020830730B548D0541E58 +:1043A000002A3FD0CAB2034601E0013C3AD303F8E9 +:1043B000012B9D07F9D1032C2DD9CDB245EA052556 +:1043C0000F2C45EA054536D9A4F1100222F00F0C56 +:1043D00003F1200EE6444FEA121C03F1100242E9F9 +:1043E000045542E9025510327245F8D10CF1010230 +:1043F00014F00C0F03EB021204F00F0C13D0ACF10D +:10440000040323F003030433134442F8045B934290 +:10441000FBD10CF003042CB1CAB21C4403F8012BED +:104420009C42FBD130BD64461346002CF4D1F9E721 +:1044300003461446BFE71A46A446E0E770B4184C9A +:104440002568D5F848411CB365681F2D25DC38B9AF +:10445000AB1C0135656044F82310002070BC704728 +:1044600004EB850C0228CCF88820D4F888614FF042 +:10447000010202FA05F246EA0206C4F88861CCF8A5 +:104480000831E5D1D4F88C311343C4F88C31DFE71F +:1044900005F5A674C5F84841D6E74FF0FF30DDE7D3 +:1044A00058BB0F002DE9F84F2B4B1F68D7F8486118 +:1044B0002DED028BC6B108EE100A8B464FF00108B5 +:1044C0004FF000097468651E0ED4013406EB8404B5 +:1044D000BBF1000F0CD0D4F800315B4508D0013D92 +:1044E0006B1CA4F10404F3D1BDEC028BBDE8F88F82 +:1044F00073682268013BAB420CBF7560C4F8009042 +:10450000002AECD0D6F88801D6F804A008FA05F104 +:1045100001420BD190477268524513D1D7F8483108 +:10452000B342DCD01E46002ECCD1DDE7D6F88C019C +:1045300001420CD1D4F8801018EE100A904772682E +:104540005245EBD0D7F84861002EBBD1CCE7D4F868 +:1045500080009047DFE700BF58BB0F00024B13B14C +:104560000248FFF7C9BE70470000000025430F0056 +:10457000FEE700BF38B50C46E8B90968C9B10F4D70 +:10458000A9420BD06B1A3B2B11D93C22284606F0CE +:10459000EDFE03E0CA5CEA54013BFBD2074800226F +:1045A0003C2103F0D3F90023A887236038BD3D23C5 +:1045B000F2E70E23F9E70123F7E700BF807F002031 +:1045C000074A6FF002039E4502D1EFF3098101E033 +:1045D000EFF308818869A0F102000078104700BF5E +:1045E00075450F0038B50446A8B10D4D00223C2199 +:1045F000284603F0ABF9AB8F83420ED12A4605F172 +:104600003C0152F8040B44F8040B8A42F9D10133FF +:10461000AB87002038BD0E20FCE70B20FAE700BF77 +:10462000807F00200B2970B50446154608462FD917 +:104630002389053304EB43012044431ADAB2012AEB +:1046400026D9814224D8134806F090FE2388522BA5 +:1046500006D1AB0711D062884CF668639A420CD041 +:104660000F2014E034F8022B824204D0AE89964227 +:1046700003F1010308D1002009E0218900230A3455 +:104680004FF6FE704FF440559942EBD80B2070BDA9 +:104690000920FCE7E486002008B5002203F056F963 +:1046A000034B1B88834214BF0B20002008BD00BFB2 +:1046B000E486002038B50C4C21684B1C054612D00E +:1046C0000A484FF4805206F01FFE48B115B1206829 +:1046D00000F00CFC054920684FF4806200F026FCD5 +:1046E0004FF0FF33236038BD30840020F086002077 +:1046F0002DE9F041DFF84480044624F47F65184634 +:10470000D8F8003025F00F05AB420E46174609D009 +:10471000FFF7D0FF0848C8F800504FF480522946F0 +:1047200006F024FE0448C4F30B043A463146204404 +:10473000BDE8F04106F01ABEF0860020308400206B +:10474000BFF34F8F0549064BCA6802F4E06213437A +:10475000CB60BFF34F8F00BFFDE700BF00ED00E06F +:104760000400FA054BDF704710DF704711DF704718 +:1047700013DF704718DF704760DF704769DF7047ED +:1047800061DF70471FB50023CDE90133039368460D +:1047900002230093FFF7EEFF05B05DF804FB08B5B8 +:1047A0004FF0E023D3F8F03DDB0700D500BEFFF764 +:1047B000C7FF0000014B1878704700BFF19600203A +:1047C0002DE9FF484C4B40F60212C3F8402500F09B +:1047D00005FA00F021FE002000F0AAFA00F09CFE8D +:1047E00048B1052000F0A4FA00F0A8FE00F0CCFECD +:1047F000062000F09DFA4FF08043DFF81081D3F8D7 +:104800001C55B12D0CBF0123002388F800304AD07D +:10481000A5F1A8014C424C41384EDFF8F49004F069 +:10482000010333704FF08043D3F8007407F00107A1 +:10483000002C3BD14E2D38D0572D36D0304B1B6835 +:104840001A68304B9A4200D17FBB6D2D2ED01220BA +:1048500000F0B0F9044633789BBB122000F0AAF9AF +:1048600010B1122000F0A6F900F00100307000F045 +:1048700005FDDFF8A0B0824630B12CB9204B1B6893 +:104880001A68214B9A4257D04FF440535A684A4510 +:104890000ABF9B684FF4905303F500731B685B4598 +:1048A0003AD1012448E00124B6E701244FF08043C7 +:1048B00000226D2DC3F81C2500F0E480002CCAD125 +:1048C000C5E70120D0E7544636E03C4634E04FF4DB +:1048D00080030B6071E0022000F02AFAA5F14E037C +:1048E0005842584103F012FEAEE000221146C1E0EA +:1048F00003F05CFEC6E000BF00A00040F19600207F +:1049000034840020D51A5A007E67E54EF2960020C6 +:10491000DBE5B1517CB0EE87002CC2D1BAF1000FBB +:10492000D1D0002FD1D0654B654A1B6865481A600D +:10493000654B43F0010398474FF440535A684A458A +:1049400008BF9B685D4A0CBF03F500734FF490539A +:1049500012681B685B450CBF5C4B002313601CB9DD +:10496000BAF1000F40F08E803378002BB3D00820CE +:1049700000F0DEF998F800300BB9FFF703FF0123D0 +:104980004FF4742088F80030FFF7F2FE504B514985 +:104990001868019001A8FFF7E7FE4F4991F8163318 +:1049A0005A09EC231341DA0707D54C4B9A68002AC1 +:1049B0008DD01A6842F480021A600C22484B029390 +:1049C00000210DEB0200FFF7E7FC40F20113029A11 +:1049D000039303A94020FFF7D1FE0C2200210DEB29 +:1049E0000200FFF7D9FC9DF80C30029A43F0010356 +:1049F00003A9A0208DF80C30FFF7C0FE0C22002187 +:104A00000DEB0200FFF7C8FC01241723029AADF852 +:104A10000E3003A923208DF80C40FFF7AFFE0C22C7 +:104A200000210DEB0200FFF7B7FC0623029A8DF878 +:104A30000C4003A920208DF80E40ADF81030FFF790 +:104A40009DFE02A8FFF798FE4FF4405330785A6855 +:104A50004A450ABF9B684FF4905303F500731B68E7 +:104A60005B4504D0572D02D04E2D7FF43EAF01227E +:104A700040F6B83100F0E8FC3378002B3FF438AF53 +:104A8000FFF774FE00F0EEF800F0F8FBA0B100F0C4 +:104A900043FD88B94FF440535B684B4506D198F805 +:104AA00000300BB9FFF76EFEFFF760FE034B1B688B +:104AB00000221A6000F004FDFFF742FE348400205B +:104AC000D51A5A000048E80160BB0F007E67E54E2A +:104AD0005CBB0F009F470F0000E100E0409D0020FD +:104AE0000080002010B58EB03423ADF802300DF1F7 +:104AF0000201002301A8ADF80430FFF741FE04468F +:104B000040B9BDF80430102B07D0112B0CD001A8F0 +:104B100000F0CCFF20460EB010BD054B01221A70EC +:104B2000072000F005F9F2E7014B18700820F8E7BC +:104B3000F096002013B5002301A80193FFF712FEA1 +:104B4000044660B9019802F053FA019B0A2B09D080 +:104B5000092B09D00B2B02D1012004F09BFB20462E +:104B600002B010BD2046F8E70220F6E708B5FFF7CF +:104B7000B9FF0528FBD1FFF7DDFF0528F7D108BDF8 +:104B80000021024A084602F003BE00BF6D4B0F0031 +:104B90001F2884BF00F01F00044B054A98BF4FF048 +:104BA000A04300F5E07043F8202070470003005058 +:104BB0000C0003001F288ABF064B4FF0A04300F0F3 +:104BC0001F00D3F8103523FA00F0C04300F00100B5 +:104BD000704700BF000300507047000008B54FF059 +:104BE000804301220021DA601220C3F818159A6070 +:104BF000FFF7CEFF1220FFF7CBFF154B4FF4C85045 +:104C000043F001039847FFF7E7FF124A1E210820EF +:104C100002F09AFD08B102F051FE02F0FBFC0E49D1 +:104C20000E4BE02081F823001B684FF47A72B3FB2F +:104C3000F2F3013BB3F1807F08D24FF0E0225361E1 +:104C4000002381F8230093610723136108BD00BF8F +:104C500070BB0F00F496002000ED00E038840020C7 +:104C6000704700004FF0E0224FF40031002310B5F0 +:104C70001361C2F8801102F1C04202F540524FF4B4 +:104C80008031C2F84813C2F80813012151609160C5 +:104C90004FF080420A4CD16002201F2B1A46C6BF3B +:104CA00003F01F0221464FF0A04102F5E0720133EC +:104CB000302B41F82200F0D1FFF7D2FF10BD00BF2A +:104CC00000030050074B23F81010074B002282B05E +:104CD000C3F81021D3F810210192019A01229A60A1 +:104CE00002B07047E898002000C001400A4A0B4B10 +:104CF00011681B68B1FBF3F203FB1211B1EB530F08 +:104D00004FEA530288BF591A4F2359430020B1FB81 +:104D1000F2F189B2FFF7D6BFE4980020F0980020A6 +:104D2000024A136801331360FFF7E0BFE4980020E4 +:104D30001B490A68082823D8DFE800F0130513226E +:104D4000221A1E242A00174B40F6B83018604FF480 +:104D50007F4303F0103323F080539A4218BF0B6057 +:104D60007047104B4FF4967018604FF47F03F0E7D4 +:104D70000C4B64221A6070470A4B40F6B83018603A +:104D80001346E6E7074B40F6B8301860FF23E0E72C +:104D9000044B4FF4967018604FF0FF13D9E700BF33 +:104DA000F4980020F098002000F1804382B01A6847 +:104DB000002A14BF0120002004D000221A601B68C2 +:104DC0000193019B02B070470F4A1378D3B903785F +:104DD0004FF08041C3F34003C1F88035037803F0FE +:104DE0000103C1F87835094B1968C90706D4E021D9 +:104DF00083F800130121C3F88011196001230448CE +:104E000013707047034870470499002000E100E0E8 +:104E10000000AD0B0C00AD0B014B02681A6070472F +:104E2000009900204FF080434FF46072C3F80423D0 +:104E30007047000010B54FF08043D3F80443620779 +:104E400007D54FF48470FFF7AFFF10B11E4B1B68FE +:104E50009847A30608D54FF48A70FFF7A5FF18B14D +:104E60001A4B00201B689847600608D54FF48C70D9 +:104E7000FFF79AFF18B1154B01201B6898472106D0 +:104E800008D54FF48E70FFF78FFF18B1104B00203C +:104E90001B689847E20508D54FF49070FFF784FF30 +:104EA00018B10B4B01201B689847A3050AD54FF496 +:104EB0009270FFF779FF28B1054BBDE810401B68E1 +:104EC0000220184710BD00BFF8980020FC98002071 +:104ED00000990020044AD2F80034DB07FBD50160BA +:104EE000BFF35F8F704700BF00E001404FF0805379 +:104EF0001A69B0FBF2F302FB130373B9084B0222E9 +:104F0000C3F80425C3F80805D3F80024D207FBD55D +:104F100000220448C3F8042570470348704700BFC7 +:104F200000E001400000AD0B0A00AD0BF8B50B4BE3 +:104F30001546012206460F46C3F804250024A54263 +:104F400004D1064B0022C3F80425F8BD57F82410FD +:104F500006EB8400FFF7BEFF0134F0E700E00140FC +:104F6000BFF34F8F0549064BCA6802F4E062134352 +:104F7000CB60BFF34F8F00BFFDE700BF00ED00E047 +:104F80000400FA054FF08053D3F83021082A06D1E7 +:104F9000D3F83431032B02D8024AD05C704700208A +:104FA000704700BF76BB0F0008B54FF08053D3F8B1 +:104FB0003021082A4ED14FF080420021C2F80C1156 +:104FC000C2F81011C2F8381502F54042D3F80414A3 +:104FD000C2F82015D3F80814C2F82415D3F80C141D +:104FE000C2F82815D3F81014C2F82C15D3F81414ED +:104FF000C2F83015D3F81814C2F83415D3F81C14BD +:10500000C2F84015D3F82014C2F84415D3F824147C +:10501000C2F84815D3F82814C2F84C15D3F82C144C +:10502000C2F85015D3F83014C2F85415D3F834141C +:10503000C2F86015D3F83814C2F86415D3F83C14DC +:10504000C2F86815D3F84014C2F86C15D3F844348C +:10505000C2F87035FFF796FF18B1494B494AC3F8BB +:105060008C26FFF78FFF18B1474BFB22C3F818259A +:10507000FFF788FF70B14FF080414FF08053D1F8B7 +:10508000E42ED3F8583222F00F0203F00F0313433B +:10509000C1F8E43EFFF776FF20B13C4B4FF40072BD +:1050A000C3F840264FF08053D3F83031082B09D194 +:1050B0004FF08043D3F80024D10744BF6FF00102C2 +:1050C000C3F80024324AD2F8883043F47003C2F89F +:1050D0008830BFF34F8FBFF36F8F4FF01023D3F89B +:1050E0000C22D2071DD52B4B0122C3F80425D3F87F +:1050F0000024002AFBD04FF01022D2F80C3223F00B +:105100000103C2F80C32234BD3F80024002AFBD051 +:105110000022C3F80425D3F80024002AFBD0FFF7AF +:105120001FFFD3F80022002A03DBD3F80432002B40 +:1051300022DA184B0122C3F80425D3F80024002AF0 +:10514000FBD04FF010221221C2F80012D3F8002435 +:10515000002AFBD04FF010231222C3F804220D4B7B +:10516000D3F80024002AFBD00022C3F80425D3F88A +:105170000024002AFBD0D2E7074B084A1A6008BD7A +:10518000005000404881030000F0004000900240C1 +:1051900000ED00E000E00140388400200090D003E2 +:1051A00013DF704718DF7047064B1878012803D1CA +:1051B000012904BF0221197012B1104602F07EBB12 +:1051C000704700BF3599002008B5FFF7F3FA88B1A2 +:1051D00011481C2101F098FF08B102F06FFB0F4944 +:1051E0000D4800231C2201F07FFF98B1BDE8084064 +:1051F00002F064BB4FF47F20FFF778FE07220749D7 +:105200004FF47F20FFF792FE054B1A78012A04BF66 +:1052100002221A7008BD00BF2C9900203899002086 +:105220003599002070B5124C124D134ED4F800344D +:105230007BB1C4F80056C4F80456C4F80856C4F844 +:105240000C56C4F81056C4F81456C4F81856C4F8CE +:105250001C5602F005FB05F0F4FF20B104F0DAFD66 +:10526000002005F003FA3378023B022BDED870BD34 +:10527000000001403546526E3599002013B54FF4B9 +:105280004053124A596891420CBF9C684FF48054B5 +:1052900001A800F0A7F92368013302D16368013344 +:1052A00011D0019B1A88012A0DD1588820B1996824 +:1052B0000022204602F04AFB019B5B881B1A5842E1 +:1052C000584102B010BD0020FBE700BFDBE5B15143 +:1052D00084B02DE9F34108AC84E80F009DF820402C +:1052E000BDF8228001A80F4616461D4600F07AF947 +:1052F00054B9384B0122FF21A3F802809D601A8027 +:105300009980354B1A7012E0012C17D1314BBA1924 +:105310002A449A60A5221A80FF229A800C9AA3F848 +:105320000280C3E903765D619A612B4B1C70FFF725 +:105330004BFF02B0BDE8F04104B07047032C0FD121 +:10534000019A244B11881980518892689A60C3E9A8 +:105350000376AA2259809A805D611F4B0122D1E712 +:10536000022C15D1019A1B4B1188A5290AD10022C4 +:105370009A60FF221A60FF229A800022C3E903226A +:105380005A61EAE719805188926859809A60F2E779 +:10539000052C0ED1FFF70EFA40B100F097FD08B1D1 +:1053A00002F08CFA0C4B03221A70C2E700F010FADC +:1053B000F5E7042C08D1074B00229A60FF221A60FF +:1053C000019A92889A80B2E7062CB2D1024B04224D +:1053D000EAE700BF389900203599002000B50C4B52 +:1053E0001B7889B063B90B4B1B786BB905238DF81B +:1053F0000C30079B009303AB0FCBFFF769FF03E073 +:1054000004F000FB0028EED009B05DF804FB00BFFB +:1054100034990020289900201FB50023CDE90233DC +:10542000074B019301F030FE30B906494FF47F235A +:1054300001A84B6001F046FE05B05DF804FB00BF1B +:10544000A9510F002C99002070B505460E460AB1EF +:1054500080F00102154B02F001021A7000F0B0FF5B +:10546000044628B935B100F08BFC0446FFF7DAFE9C +:10547000204670BDBEB10E4B0E4A0F481D70294626 +:1054800002F0F8F84FF400444FF4FA7029464FF454 +:105490007A720023E6FB040106F0D0F92A460146A1 +:1054A000064802F0F9F800F06FF9DEE734990020C1 +:1054B00028990020DD530F007CBB0F0008990020C5 +:1054C0001FB5134B4FF0FF32C3F88020C3F8802183 +:1054D0004FF440530F4A596891420DD19C682046C1 +:1054E000FFF75EFE10B14FF000531C60204604B081 +:1054F000BDE8104000F09AB80023CDE902334FF424 +:10550000805406236846CDE90034FFF74BFEE9E7F7 +:1055100000E100E0DBE5B15107B501A800F062F859 +:10552000019B1A88A52A07D09888A0F1AA0358429F +:10553000584103B05DF804FB0120FAE710B501F013 +:105540005DF9A8B10E4B0F4843F00103984701F0F5 +:10555000BFF808B102F0B2F901F050F908B102F059 +:10556000ADF901F001F9044638B102F0A7F904E001 +:1055700001F01EF904460028E4D1204610BD00BF0A +:1055800080BB0F0000A8610000B589B003AB1422F6 +:1055900000211846FEF700FF02228DF80C200022A1 +:1055A00000920FC8FFF794FEFFF73CFE002009B001 +:1055B0005DF804FB13B5044601A800F013F8019B45 +:1055C0001A8822805A8862809A68A2609A88A2808B +:1055D000DA68E2601A6922615A6962619B69A361B3 +:1055E00002B010BD014B0360704700BF00F00F0018 +:1055F000F0B50346186880F308885868FF2464B241 +:10560000EFF30585002D01D1A64600472546064645 +:1056100021273FBAF0B40024002500260027F0B46B +:10562000F92040B2004700BFF0BD00BFFFF7E0BF68 +:1056300073B500230DF1020101A8ADF8023001930A +:1056400002F0C4FDF8B9019C25785DB3174B93F8BF +:105650003020032A28D00C2606FB00F29958E9B91D +:1056600098189D5093F830200132D2B283F8302040 +:10567000BDF802300E4A9B08013B0434436084604D +:10568000084602F085F8019B33B128B1184602F0B4 +:10569000B9FD08B102F012F902B070BD0130042862 +:1056A000DAD1F0E70720EEE70420ECE75499002078 +:1056B000F9560F00084609B102F000B97047000022 +:1056C00010B50C220B4B504319181A5882B193F89D +:1056D00030208C68013AD2B283F8302000221A5070 +:1056E000C1E90122201F02F08DFD08B102F0E6F8A9 +:1056F000002010BD54990020214B70B50122214E8D +:105700001A7096F8303003B970BD1E4C002523681E +:1057100083B1013B042B07D8DFE803F01C0612031A +:105720002800204600F0DEFEE8B2FFF7C9FF08B10E +:1057300002F0C4F80135042D04F10C04E7D1E0E7D0 +:10574000A3686360204600F073FE0028ECD002F0EE +:10575000B5F8E9E7204600F053FF00F02FFF08B14D +:1057600002F0ACF80520FFF7E3FADDE700F074FF84 +:1057700000F092FFBDE870400620FFF7D9BA00BFE5 +:10578000289900205499002008B50E4B002283F878 +:10579000302004210139C3E901221A6003F10C030E +:1057A000F8D1094800F03EFE02F0A8FC08B102F072 +:1057B00085F8064802F08EFC08B102F07FF8002060 +:1057C00008BD00BF54990020B5560F0031560F0098 +:1057D00008B50020FFF774FF0120FFF771FF0220DA +:1057E000FFF76EFF0320FFF76BFFBDE8084002F0F4 +:1057F000CDBC006870476CDF70476DDF70476EDFAF +:1058000070476FDF704772DF704773DF704774DF78 +:10581000704776DF704777DF70477ADF70477CDF4D +:1058200070477FDF704786DF70478FDF704790DFFC +:105830007047AFDF7047B0DF7047B1DF7047B2DF4E +:105840007047B5DF704764DF704766DF70470C282C +:1058500013D8DFE800F01412121212120912071204 +:10586000120D0B0002207047032070470420704780 +:10587000042914BF06200520704706207047012028 +:10588000704702F01BB810B5044608460321FFF725 +:10589000DEFF0246204601F075F918B1BDE8104060 +:1058A00002F00CB810BD00000346032B10B50846EB +:1058B000144620D0042B23D169B1124B18884FF61F +:1058C000FF7398421CD01321FFF7A3FFC0B1BDE8BE +:1058D000104001F0F3BF104602F094F808B101F057 +:1058E000EDFF094B1B689C420AD1012203210748A6 +:1058F00001F048F9EAE70121FFF7A9FF0246F6E7C0 +:1059000010BD00BF3E840020489A0020F899002076 +:10591000F8B50A4DAB889E181D2E14460DDC2F6875 +:10592000FE1802F1010C07F803C07070B01C05F0FE +:105930001DFDAB8802331A19AA80F8BDA899002072 +:10594000F0B54E4E317895B0002940F092804C4C25 +:10595000019110222046FEF71FFD4A4B019923605A +:1059600018220EA8FEF718FD01238DF838302823E1 +:105970001093454B1B78002B7DD0444B04AC03F1B6 +:10598000100518685968224603C20833AB42144612 +:10599000F7D13F4C10220DEB0201E01D05F0B4FCE5 +:1059A000002868D03B4801210460FFF728FF08B1B8 +:1059B00001F084FF384B08AA03F1100C1746186851 +:1059C0005968154603C5083363452A46F7D1206850 +:1059D0000C903248A288A379ADF8342007600122E8 +:1059E00000218DF83630FFF70CFF08B101F066FF9B +:1059F00003238DF84C3004238DF80E3041F23053E0 +:105A0000ADF81030264B08AA9B798DF812300DF1B5 +:105A10000F0104A8FFF717FF012210460DF10E0138 +:105A2000FFF776FF1F4805F04BFE1E49C2B2092062 +:105A3000FFF76EFF102208A90620FFF769FF104943 +:105A400019480EAAFFF7DFFE08B101F037FF164C28 +:105A5000042221780120FFF7DEFE08B101F02EFFBD +:105A600020780121FFF7D1FE08B101F027FF0123C3 +:105A7000337015B0F0BD0623BEE700BF2C9A00209E +:105A8000A899002088990020F4990020A9BB0F0054 +:105A9000B8990020449A0020BF990020289A00203D +:105AA000F899002086BB0F003C840020F0B5044626 +:105AB0000146B1B0A84801F099F82388262B3BD8BD +:105AC0000F2B04D8012B00F0CC8031B0F0BD103B7F +:105AD000162BFAD801A252F823F000BF655B0F0025 +:105AE000735B0F00CB5A0F00A55B0F009F5C0F008C +:105AF000CB5A0F00CB5A0F00CB5A0F00CB5A0F00D6 +:105B0000CB5A0F00BB5C0F00CB5A0F00CB5A0F00D3 +:105B1000CB5A0F00CB5A0F00CB5A0F00CB5A0F00B5 +:105B2000315D0F00CB5A0F00235D0F00CB5A0F00E1 +:105B3000CB5A0F00255C0F00513B9AB2052AC4D8FE +:105B4000052BC2D801A252F823F000BF6F5C0F00F2 +:105B5000BB5C0F00CB5A0F00CB5A0F00475D0F0004 +:105B60000B5C0F007D4BA2881A807D4B00221A70BF +:105B7000ABE78023794CADF824307A4B2088322271 +:105B80001A6010A9012309AAFFF759FE08B101F014 +:105B900095FE754B1B780BB9FFF7D2FE4FF6FF73DE +:105BA000238092E7714B03AC9A79186899888DF835 +:105BB00022200790DA1DADF8201017332646106812 +:105BC0005168254603C508329A422C46F7D1684BE6 +:105BD00009AA03F11807154618685968144603C442 +:105BE0000833BB422246F7D1186820605B48614AFF +:105BF000008810AB8521CDE91456FFF712FE00286E +:105C00003FF463AF01F05AFE5FE7A379002B7FF406 +:105C10005CAF524B13211888FFF7FBFD00283FF4BF +:105C200054AF79E0237A012B7FF44FAF4C4B002225 +:105C30001A704C4B19680139196069B910AB1422FC +:105C40001846FEF7A9FB05228DF84020149A009211 +:105C50000FC8FFF73DFB38E731B0BDE8F040FFF774 +:105C60006FBE3E4B00211888FFF7EFFDD6E7A37902 +:105C7000002B3FF42AAFA27B043A022A3FF625AF5D +:105C8000022B18BF01238DF840304FF4C173ADF8DB +:105C90004430324B10A91888FFF7CDFDAFE7334AE7 +:105CA000258A508D02F118010023854218BF19463C +:105CB0000732A088FFF7B7FDB0E7284B1C884FF6E6 +:105CC000FF75AC4227D02C4B1B78F3B12B49012335 +:105CD00008222046FFF7B1FDF0B902460146022333 +:105CE0002046FFF7AAFDB8B92A460C212046FFF747 +:105CF000A0FDA0F54053023B012B7FF6E6AE08283D +:105D00003FF4E3AE112889D1DFE61A461946204652 +:105D1000FFF793FD82E7082031B0BDE8F04001F0C5 +:105D2000CDBD0E4B002218881146FFF780FD75E7A8 +:105D300000238DF840308DF84130084B10A91888A9 +:105D4000FFF773FDC1E6E188044B1729188828BFC7 +:105D50001721FFF776FD61E7F89900203E840020C7 +:105D60002C9A0020408400203F9A0020B8990020FF +:105D7000D09900203A9A0020F4990020EC99002054 +:105D800030B5464A464800231370464A95B0137012 +:105D900000F048FB01F0F6FD0446002868D14248B7 +:105DA000FEF720FC002866D1404B01221A70112317 +:105DB0003F488DF8043005F083FC3D4982B201A8CC +:105DC000FFF72DFD08B101F079FD0822002104A89C +:105DD000FEF7E2FA0823ADF810301823ADF81230C0 +:105DE0000023ADF8143004A84FF4C873ADF8163092 +:105DF000FFF713FD08B101F061FD00210C2201A89D +:105E0000FEF7CAFA0823ADF804302A4B02932A4859 +:105E10002A4B039301A900F02FFD08B101F04EFDBC +:105E2000274D4022002104A8FEF7B6FA284605F0C7 +:105E300047FCADF810002846059505F041FC079594 +:105E4000204DADF81800284605F03AFC1123ADF8B6 +:105E5000300004A8ADF84C300D9500F0DBFF1A4B74 +:105E600030221A7007225A7010229A70FFF768FDCC +:105E7000204615B030BD04A8FFF7BFFC08B101F003 +:105E80001DFD9DF8113004A801338DF81130FFF786 +:105E9000B2FC00288BD001F011FD88E73F9A00206A +:105EA000A9580F00399A0020B8990020F4990020D1 +:105EB00086BB0F001D5F0F00F899002083580F006C +:105EC0008DBB0F0098BB0F003A9A002010B50F4B06 +:105ED00001221A700E4B18884FF6FF73984207D0B4 +:105EE0001321FFF796FC08B101F0E8FC002010BD7B +:105EF000084C2378002BF9D0074B1878FFF787FC64 +:105F000008B101F0DBFC00232370EFE73F9A00208B +:105F10003E8400202C9A00203C840020F0B50B78B1 +:105F200089B005460C46092B35D8DFE813F02D0063 +:105F3000360041000A001900260007011001440044 +:105F4000150100F089FB0421FFF781FC0246284679 +:105F500000F018FEF8B109B0BDE8F04001F0AEBCA9 +:105F6000FFF7B4FF08B101F0A9FC00F095FB90B178 +:105F700009B0BDE8F04000F09DBBFFF7A7FF002887 +:105F8000F6D001F09BFCF3E7764B01221A704B68C8 +:105F90001A78754B1A7009B0F0BD724B02261E704C +:105FA0004B681B78012BF6D100F008FB3146CBE79C +:105FB0006C4B0322EEE70520FEF7BAFE694B1E7814 +:105FC000022E37D0032E59D0012EE4D104AB10227B +:105FD00018460021FEF7E0F9634A237A12788DF81B +:105FE0001020002203920C2B4FF00302CDE9012078 +:105FF00008D03146284600F0C5FD0028CBD001F07E +:106000005DFCC8E763681846FFF7F3FB0590181DB1 +:10601000FFF7EFFB069003F10800FFF7EAFB07909C +:1060200001A800F005FA0028B5D03146FFF70FFCB3 +:106030000246DFE7237A13F0030011D0C0F1040217 +:106040001A44D2B219464FF0000C0E46013167686F +:10605000C9B2914207F806C0F7D11B1A0433237264 +:106060000123049363680693237A04A89B0805938D +:1060700000F0C6FA00288ED00221D7E7207A8307E5 +:1060800002D03246314662E7384E0190314601F087 +:106090008FFC014618B12846FFF7F5FB7BE76168E6 +:1060A000019A306805F062F9019801F0E7FC0146B9 +:1060B0000028F0D101A9304601F0F0FC014600288B +:1060C000E9D104230493019B9B08059304A833683A +:1060D000069300F007FA074640B9254A237A11686B +:1060E0000B441360234B32681A6054E709281BD114 +:1060F0001F4B217A1A68114419601F4B1B78002B23 +:106100003FF449AF1D4C2388013B9BB22380002BF9 +:106110007FF441AF284600F0FBFC08B101F0CEFB54 +:10612000174B1B88238036E7306801F06BFC014673 +:1061300010B12846FFF7A7FB3946ACE70E4B01220A +:106140001A700F4A8B8813800C4A138023E70A4A7F +:10615000002313700A4AF8E7054B196800F09CFC0D +:10616000F8E600BF399A0020409A00204C9A00209F +:10617000309A0020489A0020389A0020369A002051 +:10618000349A002018DF7047012973B514460D4674 +:106190001A4608D0032912D014B3204602B0BDE835 +:1061A000704001F08BBB0F4B1B78052BF4D10E4BCD +:1061B0001B68002BF0D0214604209847ECE7094EDD +:1061C0003378022BE8D1094B01925B689847064B64 +:1061D00035701B68002BDFD0019A21462846ECE77A +:1061E00002B070BD589A0020509A00205C9A00209E +:1061F00030B589B003AC142200212046FEF7CCF85C +:10620000094B1B88ADF80E30084BDB680693002560 +:10621000079B8DF80C50009394E80F00FFF758F897 +:10622000284609B030BD00BF689A0020B49A00200B +:1062300000B589B003238DF80C300A4B1B88ADF8EC +:106240000E30094B5A6804929A68DB680693079BE4 +:106250000093059203AB0FCBFFF73AF8002009B08B +:106260005DF804FB689A0020B49A002000B589B05C +:1062700001238DF80C300F4B0F4A1B88ADF80E3000 +:106280004FF440535968914208BF9A680B4B5968C4 +:10629000049118BF4FF480529968DB68069305910A +:1062A000009203AB0FCBFFF713F8002009B05DF8A5 +:1062B00004FB00BF689A0020DBE5B151B49A0020CE +:1062C00000B589B003AB142200211846FEF764F82C +:1062D00004228DF80C20002200920FC8FEF7F8FF70 +:1062E00009B05DF804FB0000194BF7B5194C1C60B0 +:1062F000194B02221A70FEF75DFA184B48B1196863 +:10630000204600F001FF00B303B0BDE8F04001F00B +:10631000D5BA1D68124F013D2D0B013504464FF4CF +:1063200040567368BB420CBFB0684FF4805000EB1E +:1063300004300134FEF7DAFDA542F2D80023054807 +:1063400000931A460321FFF71FFF03B0F0BD00BF03 +:10635000CC9A0020C49A0020589A00206C9A002001 +:10636000DBE5B15170B50C4686B00321CDE90210D2 +:106370000546960802A8019304940596FFF702FFCC +:10638000E0B1B4F5805F019B11D8012302A8CDE9EB +:106390000235CDE90446FFF7F5FE78B9032302A8DC +:1063A000CDE90235CDE90446FFF7ECFE06E01A46DA +:1063B000E11AE81AFFF7D6FF0028E6D006B070BD54 +:1063C0001FB5114B0193114B114900241C70114B47 +:1063D00001A81C80CDE9024400F074FE0E4B10B100 +:1063E0001C7004B010BD4FF440520C4954688C42EC +:1063F00007490CBF92684FF480524A60084A002156 +:10640000116001221A70ECE789610F00B09A002038 +:10641000C49A0020689A0020589A0020DBE5B15108 +:10642000549A0020014B1860704700BF509A00201A +:1064300038B54368214C0FCB84E80F002278500711 +:1064400001D5910733D16068830730D1A3689D07D8 +:106450002DD1E16811F0030429D1184408441849EA +:10646000B3F5204F086024D84FF4405315495D68B8 +:106470008D420ABF9B684FF46923C3F56A23984293 +:1064800017D8114B1149196011495960D10709D525 +:10649000104A9A60104B1B78012B0CD1FFF724FF98 +:1064A000204638BD92074CBF0C4A0D4AF1E706243E +:1064B000F6E70C24F4E70824F2E700BFB49A0020C2 +:1064C0006C9A0020DBE5B1515C9A0020E9620F0074 +:1064D000C1620F006D620F00589A002031620F00F8 +:1064E000F1610F002DE9F04385B0002853D0816899 +:1064F00011F0030451D12C4B1B78052B4FD12B4E9F +:1065000042683368DFF8AC9003EB82039500D9F85A +:106510000020934207D94FF0FF3333600C2420460C +:1065200005B0BDE8F0830391FEF744F9DFF88880F9 +:10653000039940B13368D8F800002A4600F0D4FD32 +:10654000D8B10446EBE74FF44053194A586837680E +:10655000904208BF9868039118BF4FF48050002301 +:106560002A463844FEF7C4F80399D8F8000000958D +:106570000B4600220121FFF707FE33681D44D9F8BE +:10658000003035609D420CD1FEF714F90028C6D1C9 +:10659000FEF790F8C3E70E24C1E71024BFE70824F4 +:1065A000BDE70924BBE700BF589A0020549A002099 +:1065B000DBE5B1516C9A0020CC9A002070B50B4BF2 +:1065C0001D6885B90A4E3378042B0CD1094C0A4B4F +:1065D00021781A780948FEF725F810B90523337099 +:1065E00070BD2570FCE70820FAE700BF549A002030 +:1065F000589A0020B09A0020B49A0020709A002087 +:10660000F8B5114B1A78032A03D0042A03D00824C2 +:1066100016E004221A700D4B1C68002CF7D10C4DAB +:1066200043682F789E0007EB8303402B0AD88168CC +:1066300008483246384404F099FE2B7833442B70D6 +:106640002046F8BD0924FBE7589A0020549A002000 +:10665000B09A0020709A002010B50B4C2378052BBF +:1066600010D10A4B0A4A1B68116899420AD10623C5 +:106670002370084B1B685868FEF70EF808B907230B +:10668000237010BD0820FCE7589A00206C9A002067 +:10669000549A0020CC9A0020044B1B78072B02D17F +:1066A000034B9B6818470820704700BF589A00208A +:1066B0005C9A002000B589B006238DF80C30079B4A +:1066C000009303AB0FCBFEF703FE09B05DF804FBAC +:1066D000F0B58DB005A8FEF76DFF089C002C3ED0EC +:1066E0000B9E04F58053B3422DD91E4BA6F5805561 +:1066F00003EA55054FF440539B689C420BD8690050 +:106700002B46A4EB450201F5805106EB4500FFF74F +:1067100029FE0DB0F0BD05F58053012701A8CDE994 +:106720000173CDE90337FFF72DFD0028F1D14FF4B8 +:10673000805301A8CDE9023301970497FFF722FDAA +:106740000028E6D1DBE70123CDE90136A4084FF4A8 +:10675000805301A803930494FFF714FDD9E7204662 +:10676000D7E700BF00F0FFFF00B58DB005A8FEF72A +:1067700021FF099880B1089B8BB94FF440530B4A15 +:10678000596891420ED19B6880080022039001A8AD +:10679000CDE90123FFF7F6FC0DB05DF804FB0B9A81 +:1067A0001344F1E74FF48053EEE700BFDBE5B1514E +:1067B00000B58DB005A8FEF7FDFE099898B1089BBD +:1067C000A3B94FF440530C4A5968914211D19B68C8 +:1067D0000393800803214FF47422049001A8CDE9AB +:1067E0000112FFF7CFFC0DB05DF804FB0B9A1344C8 +:1067F000EEE74FF48053EBE7DBE5B15110B58CB019 +:1068000005A8FEF7D7FE0898B8B10B9C00F5805399 +:10681000A34214D94FF440539B6898421BD80F4BA6 +:10682000A4F5805203EA52035900A0EB430201F59C +:10683000805104EB4300FFF795FD0CB010BD8008BC +:1068400003224FF48053049001A8CDE9012303945F +:10685000FFF798FCF1E70E20EFE700BF00F0FFFF25 +:10686000A8DF7047AADF7047ADDF7047AEDF704723 +:10687000B0DF704762DF70472DE9F0470E4694B0F5 +:106880000546002800F00181002900F0FE804B68D9 +:10689000002B00F0FA804FF6FF7303800023ADF861 +:1068A0000A307B4B04AA03F1100C1746186859688C +:1068B000144603C4083363452246F7D141F23053EE +:1068C0000DF10A013846ADF80830FFF7D3FF044652 +:1068D000002840F0D6802A1D02A90120FFF7C0FF42 +:1068E0000446002840F0CD809DF80A30AB71014687 +:1068F0001C220DA8FDF750FD9DF834300E9443F096 +:1069000004038DF8343001AFAB798DF80E30214699 +:1069100041F2325303223846CDE91044CDE9124406 +:10692000ADF80C30FDF738FD9DF806308DF80440C9 +:1069300023F01F0343F00303214614224FF0110AF2 +:1069400008A88DF806308DF805A00DF10C08FDF7AC +:1069500023FD4FF01409A8880A9405F1080308AA3A +:106960000DA90C94CDE90887ADF82C90FFF77AFFBC +:106970000446002840F0858001461C220DA8FDF742 +:106980000BFD9DF834300E9423F0180343F01803E8 +:106990008DF83430AB798DF80E30214641F2315309 +:1069A00003223846CDE91044CDE91244ADF80C304D +:1069B000FDF7F2FC9DF806308DF8044023F01F032C +:1069C00043F0130321464A4608A88DF806308DF897 +:1069D00005A0FDF7E1FC1723ADF82C30A8880A9438 +:1069E00005F1100308AA0DA90C94CDE90887FFF75B +:1069F00039FF0446002844D101461C220DA8FDF7AA +:106A0000CBFC9DF834300E9443F002038DF8343003 +:106A1000AB798DF80E30214641F2345303223846CB +:106A2000CDE91044CDE91244ADF80C30FDF7B4FCCB +:106A30009DF806308DF8054023F01F0343F0030353 +:106A400021464A4608A88DF806308DF804A0FDF7C7 +:106A5000A3FC02230A93ADF82C30A8880C9605F10C +:106A6000200308AA0DA9CDE90887FFF7FBFE04461D +:106A700038B97368AB62B36803B1EB62054B0122AE +:106A80001A70204614B0BDE8F0870E24F9E700BF65 +:106A9000B9BB0F00D09A002070B5054686B070B320 +:106AA00002884FF6FF739A422BD0174B1B7843B3E3 +:106AB000164C1022080AE170207121FA02F0090E2A +:106AC000072301266071A17102A800216370ADF84F +:106AD00006302270A670FDF75FFC2B8AADF80830F7 +:106AE0000023ADF80C3028888DF80A600DF10603FC +:106AF00002A9CDE90434FFF7B9FE06B070BD0E203F +:106B0000FBE70820F9E700BFD09A0020D19A0020C7 +:106B100030B5044687B060B302884FF6FF739A42DF +:106B200029D0164B1B7833B3154D11232B700B0A4C +:106B30006970AB700B0C090EEB70297105230021F5 +:106B4000102202A8ADF80630FDF726FC238AADF826 +:106B5000083001238DF80A300023ADF80C3020886E +:106B60000DF1060302A9CDE90435FFF77FFE07B05A +:106B700030BD0E20FBE70820F9E700BFD09A0020C7 +:106B8000D19A002030B5044687B038B300884FF65C +:106B9000FF73984224D0134B1B780BB3124D102374 +:106BA00069700321ADF80610AA7000211A4602A8E8 +:106BB0002B70FDF7F1FB238AADF8083001238DF827 +:106BC0000A300023ADF80C3020880DF1060302A92D +:106BD000CDE90435FFF74AFE07B030BD0E20FBE7D4 +:106BE0000820F9E7D09A0020D19A002070B50D4610 +:106BF00088B0044650B149B1826A3AB10B88502B33 +:106C000049D005D8102B43D0112B54D008B070BDFB +:106C1000512BFBD18E79022EF8D10A89038A9A4230 +:106C2000F4D18B7B043B022BF0D99DF816308DF804 +:106C3000106043F001038DF816300B8AADF8183060 +:106C40004B8AADF81A30082201F1140301A8002183 +:106C50000793FDF7A1FBA18A2088019601AACDF830 +:106C600008D0FFF701FE48B3E36A03B1984740F24A +:106C7000FD132088ADF8143004A9FFF7F9FD0028B2 +:106C8000C4D0E36A002BC1D008B0BDE870401847FB +:106C90008B882380BAE7C98803899942B6D1082333 +:106CA0008DF81030123535F8023C8DF81830059506 +:106CB00004A99047AAE74FF6FF73EAE7BDF8003052 +:106CC0002088DB07D3D5002604A9ADF81460FFF7B0 +:106CD000CFFD0028D5D1297D4B1E072B3ED8DFE8FC +:106CE00003F004192226282A3B2C6B8A8DF80460B5 +:106CF000012B05D8062201212046FFF743FFBEE7FE +:106D0000012315358DF80C300295A36A01A92046A0 +:106D100098477BE76A8A01239A428DF80430F0D8BD +:106D200006220221E8E702238DF80430EDE7032371 +:106D3000FAE70423F8E70523F6E76B8A022B02D86B +:106D400003220821D8E7B5F81530ADF80830002B3C +:106D50000CBF07230623E7E70923E5E70322CBE778 +:106D6000A8DF7047AADF70472DE9F04180468EB05A +:106D700015461F460E4611B9084600F09FFD15B98D +:106D8000284600F09BFD1C220DEB02000021FDF7C0 +:106D900003FB9DF81C30ADF80480002443F002038F +:106DA0008DF81C3021460123032268468DF80630F9 +:106DB000CDE90A44CDE90C440894FDF7EDFA3B789F +:106DC0008DF800307B788DF801309DF8023023F08B +:106DD0001F0343F002032146142202A88DF802305B +:106DE000FDF7DAFA0A48CDF80CD001AB029302AAFB +:106DF000149B0088ADF8105007A9ADF81240ADF80B +:106E000014500696FFF7AEFF0EB0BDE8F08100BF4C +:106E1000F89A002030B587B041F60A032B4AADF846 +:106E20000C30044603A901208DF80E00FFF798FFEF +:106E30000546D8B92288E2B922894AB1244B009389 +:106E4000E16804F13C0342F62420FFF78DFFD8B936 +:106E5000228C4AB11F4B0093616A04F13C0342F655 +:106E60002620FFF781FF78B9A36B7BB9284607B0CE +:106E700030BD194B0093616804F13C0342F62920B0 +:106E8000FFF772FF0028D7D00546EFE71A788DF894 +:106E900010205A888DF81120120A8DF812209A8835 +:106EA000DB888DF815301B0A8DF813208DF816300D +:106EB000120A0A4B8DF814200093072204F13C03B8 +:106EC00004A942F65020FFF74FFFDDE7F89A0020B3 +:106ED000E89A0020D89A0020E09A0020F09A00203A +:106EE00029DF704728DF7047064B182202FB00306D +:106EF00000230422C0E90423037183608361C3601B +:106F0000704700BF0C9B002023B502460846C968A5 +:106F100043680093044B53F82150436910F80C1B4D +:106F2000A84702B020BD00BFFC9A002038B5194C1C +:106F30002378182202FB03431A795869012A03D0E7 +:106F4000032A1AD00F2038BD134A996915689A6828 +:106F5000DB68A2EB0532B2F5805F184401EB053126 +:106F600000EB053034BF92084FF48062FFF7B8FFA2 +:106F70000028E8D10123A370E5E74FF080531B6997 +:106F80009BB2B0FBF3F0044B1B681844FFF7AAFF59 +:106F9000EEE700BF0C9B0020049C002070B5134D51 +:106FA0006C780A2C1FD02E783444E4B2092C84BFAC +:106FB0000A3CE4B2182606FB0454A261207103C9FE +:106FC000A360049BE360AB7804F1100282E8030045 +:106FD00023B100206B7801336B7070BDFFF7A6FF03 +:106FE0001128F7D1F5E70420F7E700BF0C9B00203C +:106FF00070B5234CA3782BB100260228A67002D0CE +:10700000032833D070BD25781E4A182101FB0541A5 +:10701000136889680133B1EB033F136014D86378B8 +:107020001660013B63706B1CDBB21821092B01FB5E +:10703000054188BFA5F10903002004312370FFF743 +:1070400063FF2846FFF750FF6378002BDAD0A37860 +:10705000002BD7D1FFF76AFF0028D3D01128D1D059 +:107060002178182303FB0141043105E0217818231E +:1070700003FB014104310D20BDE87040FFF744BF20 +:107080000C9B0020049C002008B50A4B00211960CD +:10709000094B1980997008460131FFF725FF0A292D +:1070A000F9D1064B00201860054BC3E90000C3E985 +:1070B000020008BD049C00200C9B0020009C0020C6 +:1070C000FC9A0020064A03461068042807D008608E +:1070D000411C11601A68034B43F8202000207047C0 +:1070E000009C0020FC9A002013B5CC180C43A40788 +:1070F00008D1009313460A4601460120FFF74EFFD0 +:1071000002B010BD1020FBE707B500220B4600922D +:1071100001460320FFF742FF03B05DF804FB0000C7 +:10712000094B5A7899780132D2B2914208BF0022B5 +:10713000197891421FBF02705878182202FB003064 +:1071400014BF043000207047089C0020082910B5A7 +:10715000044602D0002000F0B1FBD4E90030BDE8C5 +:107160001040184773B5054600240DF107000E4680 +:107170008DF8074000F0B0FB0DF10600FFF7D0FFDF +:1071800090B10670094B9DF8062045605A709DF835 +:10719000070000F0C5FB24B9054B4FF48012C3F87B +:1071A0000021204602B070BD0424F0E7089C0020B6 +:1071B00000E100E0204B21491A682F2300BF00BFE7 +:1071C00000BF00BF00BF00BF00BF00BF8A422FD07A +:1071D00000BF00BF00BF00BF00BF00BF00BF00BFB7 +:1071E00000BF00BF00BF00BF00BF00BF00BF00BFA7 +:1071F00000BF00BF00BF00BF00BF00BF00BF00BF97 +:1072000000BF00BF00BF00BF00BF00BF00BF00BF86 +:1072100000BF00BF00BF00BF00BF00BF00BF00BF76 +:1072200000BF00BF00BF00BF00BF00BF00BF00BF66 +:10723000013BC3D1704700BF388400200024F40014 +:107240000C4B0D484FF4003210B5C3F880200124D8 +:107250004FF48033C0F84833C0F808334460FFF778 +:10726000A9FF064B846000201860FFF7A3FF044BC2 +:10727000187010BD00E100E000100140249D0020C6 +:10728000159D00202DE9F3412549264B0025C1F825 +:107290004051C1F84451C1F84851C1F84C51C1F8AE +:1072A0000051C1F804511B68002B34D0D1F80445BB +:1072B0001D49DFF888800968641A24F07F442F464E +:1072C0001968A14212D81A7CDE69641A0D4462B1B1 +:1072D0005A691F7400929B690193424608216846CF +:1072E00000F056FA08B100F0E9FABEB90F4A104BA7 +:1072F00011781B788B4205D10133DBB2022B08BF1A +:107300000023137012780B4B43F822500A4B4FF4B2 +:107310008012C3F8002102B0BDE8F0813346CFE708 +:1073200000100140289D0020249D0020219D002068 +:10733000209D0020189D002000E100E04D710F000D +:107340002DE9F74FA84AA94913780978A84C994222 +:107350003BD00133DBB2022B08BF00231370A549D9 +:107360001278A54B0F6853F822003B1823F07F4397 +:1073700000220B60236815461646944613B942B1A5 +:10738000236006E0196881420DD902B12360091A11 +:10739000196001262368DFF8689200930027BDB9C1 +:1073A000DFF868A268E0401A0E44D968D3F81CE000 +:1073B000C3F800C031B1BA1922F07F42C3E90121FC +:1073C000DD611D4673460122D8E700252E46E1E720 +:1073D0002846ED69874BD0F804C01B68DFF830E21F +:1073E0008168ACEB030222F07F42724500F2AD806F +:1073F0000A4402600122027422680023C0E90133BA +:10740000C361002A40F0AB802060C8E75A1C9AF89C +:107410000210D4F800B0D2B291428AF8002004BF22 +:1074200000228AF80020182202FB03A31A79986828 +:10743000022A77D0032A00F08580012A1CD190F817 +:1074400010C0BCF1000F17D1D96841601A69826081 +:107450005A69C2609B698361684B1B78002B18BF17 +:1074600061464160B6E7904200F09E809046D26946 +:10747000002AF8D1002303749AF800309AF801200A +:107480009A42C3D1236827B9009A9A4201D1002EAB +:1074900042D0002B00F08580D3F80090584C554B1B +:1074A000D4F804651868574F351A3B7825F07F45A6 +:1074B00003359BB94FF48033C4F84433C4F8043324 +:1074C000514B4FF400324FF00108C3F880211A608D +:1074D000C4F80080FFF76EFE87F80080A9452CBF36 +:1074E0004844401920F07F40C4F84005D4F80435E2 +:1074F0009B1B23F07F43801B033320F07F4083429C +:107500000AD9D4F80435C4F84035FFF753FE3E4B92 +:107510004FF40032C3F80021384B00221A7003B038 +:10752000BDE8F08F5A46D846A2E78BF81020DBF86A +:107530001CB00123BBF1000FF7D1002B9CD0C4F885 +:1075400000B099E700231A46F4E7A3EB0C0323F0FD +:107550007F438B4234BFCB1A002303604AE70168A4 +:10756000136899421BD85B1A1360C2614CE7A1EB08 +:107570000C01D3F81CC01A46BCF1000F0AD06346B8 +:10758000D3F800C08C45F2D3ACEB010CC3F800C0BB +:107590009C4613460160C0F81CC0D861FFE6134644 +:1075A000EEE7FFF74DFEB7E7404510D1DBF81C30A2 +:1075B000236063B9DFF83CE001920121C9F80810AB +:1075C000CEF800300D4B1970FFF7F4FD019A1368E7 +:1075D000D269C8F81C2012B111680B4413602368EB +:1075E0005B4518BF012745E7209D0020219D002015 +:1075F000289D0020249D0020189D0020149D00201F +:1076000000100140159D002000E100E0089C0020D2 +:10761000FEFF7F0008B5FFF713FE104B00200B2282 +:1076200018809A700E4B18600E4B18700E4B187025 +:107630000E4B4FF48012E021C3F8802183F814131D +:107640001A6002F18042A2F56F22C2F8080583F8A1 +:107650001113074BD2F804251A6008BD089C0020BE +:10766000289D0020209D0020219D002000E100E0B9 +:10767000249D0020074B9B784BB132B128B10368A1 +:10768000187C20B959745A61704707207047082048 +:10769000704700BF089C00202DE9F743DFF8848085 +:1076A00098F8023005460E461746ABB3A0B304293E +:1076B00030D9436983B3437C0024012B0DF10700CB +:1076C0000CBF8946A1468DF8074000F005F90DF181 +:1076D0000600FFF725FDD8B1012303700F4B45606D +:1076E000D3F80435C0E90497C0E902369DF80630A6 +:1076F00088F801309DF8070000F012F924B9084B12 +:107700004FF48012C3F80021204603B0BDE8F08397 +:107710000424EFE70724F7E70824F5E70010014009 +:1077200000E100E0089C0020064A92783AB130B1AE +:10773000426922B1002202740221FFF713BD082022 +:10774000704700BF089C00204B1C30B5DB0004468E +:1077500012F003009BB20DD1074D2A601A44074B6B +:107760001A60074B1870074B1870074B1C80074BAB +:10777000198030BD0720FCE7349D0020309D00209B +:107780002C9D00203C9D0020389D00203A9D00202B +:107790002DE9F347DFF8C080B8F800308B42064689 +:1077A0000D4617464CD300240DF107008DF8074015 +:1077B00000F092F8244B254A25481B78008892F85F +:1077C00000C084455FFA8CF138BF4C1CDBB238BF77 +:1077D000E4B2A3422ED014781178CBB2884286BF8F +:1077E0000133DBB2002313709DF8070000F098F816 +:1077F0004FF6FF739C4225D0DFF86090D9F8002047 +:107800004FEAC40A42F8347002EBC403AEB1A5B12A +:10781000104BB8F800001B682A4604FB00303146C4 +:1078200003F0A4FDD9F80030534400209D8002B03D +:10783000BDE8F0874FF6FF74D6E700209880F6E7A2 +:107840000920F4E70420F2E73C9D00202C9D002055 +:107850003A9D0020309D0020389D0020349D00205E +:1078600070B5104C104D22782B789A4200D170BD23 +:107870000E480F4A2378126806880E4802EBC301AF +:10788000006852F83320898803FB060090470A49B4 +:1078900022780988D3B2914286BF0133DBB200233C +:1078A0002370E0E73C9D00202C9D0020389D0020A7 +:1078B000349D0020309D00203A9D00201FB50021FE +:1078C000CDE9021001AA44F20100ADF80410FCF762 +:1078D00066FF05B05DF804FB70B5EFF3108672B675 +:1078E0000C4A946801239CB993600B4B0B4DD3F861 +:1078F000801029401160C3F88050D3F88410516083 +:107900004FF0FF32C3F88420047006B962B670BD30 +:107910000370FAE7409D002000E100E0FC06FFBD97 +:1079200010B5084B9A685AB150B9EFF3108172B68E +:10793000054A1C6814605C685460986001B962B6BE +:1079400010BD00BF409D002000E100E003462AB1C9 +:1079500010881A4619448A4203D170474FF6FF70C7 +:10796000F7E712F8013B40BA80B25840C0F3031366 +:10797000584080EA0033580100F4FF509BB2584051 +:10798000E9E70000064B074A00201870064B1A6012 +:107990000822C3E90120C3E90300C3E905007047D9 +:1079A0004C9D0020509D002030B0002000207047EA +:1079B00030B5F9B1124B5C6800220A60E4B1B0F551 +:1079C000167F1BD8D868013C01305C60D8601C6809 +:1079D00018694FF4177505FB00440C60012101FA8A +:1079E00000F49969013000F00700214318619961A2 +:1079F000104630BD0E20FCE70420FAE70C20F8E723 +:107A000030B00020F0B51C498A689AB34D690E6801 +:107A1000AC1A04F0070423464FF4177707FB036CF6 +:107A2000604511D1012000FA03F58869684088613A +:107A300000204E68D1F818C04FF0010E73440025A5 +:107A4000164403F007030AE0013303F007039D42E5 +:107A5000E4D11020EDE74AB1013A1C4601250EFAA7 +:107A600004F414EA0C0FA6EB0207F4D00DB1C1E93F +:107A70000172F0BD0420FCE730B00020064A136913 +:107A80001268013B4FF4177103F0070301FB032356 +:107A9000C3F858020020704730B0002030B5C0B1A4 +:107AA000B9B10E4BDA68B2B1013ADA609A681C6873 +:107AB00001329A605A694FF4177505FB024404605D +:107AC0000132D4F85802086002F007025A6100201F +:107AD00030BD0E20FCE70420FAE700BF30B00020E4 +:107AE0003FB40C49086890B10B4B1C687CB10B4A41 +:107AF0001568CDE9025000238DF804300B60136047 +:107B000004AB13E90700234604B030BC184704B0A7 +:107B100030BC704754B0002058B0002064B0002042 +:107B2000DC2810B509D0DD2810D0C02816D1FFF709 +:107B3000D7FF0E4B0E4A1A6010BD0E4A0E4B196845 +:107B40001368581C1060C022CA54F2E7094A0A4B55 +:107B500019681368581C1060DB22F5E7064B054ACC +:107B6000196813685C1CC8541460E2E74484002060 +:107B7000917B0F0054B0002064B00020C02802BFE9 +:107B8000014B024A1A60704744840020917B0F0029 +:107B9000C02810B409D0DB280BD0094B094A19685A +:107BA00013685C1CC854146006E05DF8044BFFF7D2 +:107BB00097BF054B054A1A605DF8044B704700BF3C +:107BC00064B0002054B0002044840020217B0F00CA +:107BD00007B501228DF807000DF10701002002F022 +:107BE00085FD00280CBF0420002003B05DF804FBD5 +:107BF00010B5064A064C12682368D05CFFF7E8FF10 +:107C000010B923680133236010BD00BF68B00020A5 +:107C10005CB0002008B5C020FFF7DAFF28B9034B9D +:107C20001B6813B9024B034A1A6008BD5CB0002000 +:107C300048840020F17B0F0008B5DB20FFF7C8FF68 +:107C400010B9024B024A1A6008BD00BF48840020E8 +:107C5000557C0F0010B50C4A0C4C12682368D35C9D +:107C6000C02B03D0DB2B0DD0042010BDDC20FFF790 +:107C7000AFFF0028F9D12368054A01332360054B83 +:107C80001A60F2E7DD20F2E768B000205CB0002067 +:107C9000F17B0F00488400207FB5184C184D194E19 +:107CA000002002F0C3FC30B322689AB1296833681F +:107CB00099420FD2012201A9002002F0C3FC10B9A1 +:107CC0004FF0FF3001E09DF804000F4BC0B21B687D +:107CD0009847E5E70D4B1B686BB10292084A0221F9 +:107CE000126803928DF8041004AA12E9070004B088 +:107CF000BDE87040184704B070BD00BF64B00020FC +:107D000054B0002050B000204484002058B000201F +:107D1000014B18600020704758B00020034B1A78C0 +:107D20000AB901221A700020704700BF4CB0002031 +:107D3000014B0020187070474CB000202DE9F04F27 +:107D400085B0002851D02A4F3B78012B07D0022B59 +:107D500014BF08240424204605B0BDE8F08F254D4B +:107D6000DFF8A090244E254CDFF89C80DFF89CA023 +:107D7000DFF89CB0C9F8001000232B6002233060AC +:107D80003B70C4F800802A68D9F800309A4215D3B5 +:107D9000C4F80080FFF73EFF044608BB184B1B6881 +:107DA00001223A70E3B18DF80420326802922A6809 +:107DB000039204AA12E907009847CCE733682A68BF +:107DC0009A5CC02A03D02A689B5CDB2B04D1236811 +:107DD000534508BFC4F800B023689847042801D170 +:107DE0000024B8E71128CED1FAE71024B3E700BF8A +:107DF0004CB000205CB0002068B000204884002017 +:107E000058B0002060B00020157C0F00F17B0F00FF +:107E1000397C0F00054B064A1860064B1960064B6B +:107E200000201860054B1A60704700BF64B0002046 +:107E30007D7B0F0050B0002054B00020448400200F +:107E4000064B07481B68DB00DBB2002203705B4275 +:107E500042708270C3700421FFF770BF94B000209D +:107E60006CB0002070B52B4C2B4D02462378012BB3 +:107E700014D0022B21D0002B4BD1002A49D1274806 +:107E8000FFF752FC08B1FFF719FD254B1B68002BCB +:107E90003FD0244ABDE8704010781847012A38D1F5 +:107EA0002968214B06311868FFF748FF08B1FFF732 +:107EB00005FD022323700022D8E7022A16D0032AE8 +:107EC0000CD032BB194B15481A6041F67F21FFF7E1 +:107ED000E3FBF0B1BDE87040FFF7F0BC144A136853 +:107EE000013303F0070313600023E3E70F4A13682D +:107EF000052B0AD001331360074B19680A4BBDE804 +:107F0000704018680631FFF719BF064B01221A703E +:107F1000EAE770BDB8B00020ACB0002070B000201F +:107F2000A8B00020B0B00020C0B00020B4B0002045 +:107F300098B00020F0B585B004AB03E907009DF8C8 +:107F40000400032874D8DFE800F00802A3A601208B +:107F500005B0BDE8F040FFF785BF039E544C032EEB +:107F600040F28280029D6B7813F00F0265D00E2ADA +:107F70007AD1042E55D02A78500652D5110650D504 +:107F80001A44AB781A44EB781A4412F0FF0248D135 +:107F9000B71E39462846FFF7D9FCEB5B834240D138 +:107FA00044492A780B6802F00702D8B282422BD1EA +:107FB000013303F007030B60FFF742FF3E4B012242 +:107FC00030461A70FFF75AFD08B1FFF777FC3849C1 +:107FD0004FF41670FFF7ECFC002862D0042802D0A2 +:107FE0000020FFF76BFC35480521FFF713FF08B1B0 +:107FF000FFF764FC324B1B68002B56D04FF000009B +:1080000005B0BDE8F040184720684FF41671FFF73F +:1080100001FF08B1FFF752FC05B0BDE8F040FFF7E3 +:108020000FBF20684FF41671FFF7F4FE00283CD014 +:1080300005B0BDE8F040FFF741BC2978AA780B44B1 +:108040001344EA78134413F0FF030DD11D4A12685C +:108050000132C1F3C20102F00702914204D11A4A6F +:1080600003201370FFF7FEFE25681DB14FF4167153 +:108070002846D9E70E494FF41670FFF799FC60B116 +:10808000042802D02846FFF719FC0C480521CBE74D +:108090000A480521C8E70320CAE720684FF4167193 +:1080A000C2E720684FF416719FE705B0F0BD00BF2E +:1080B000BCB0002094B0002090B000209CB0002004 +:1080C000A4B0002098B00020B0B000200220FFF73C +:1080D000C9BE0000074B10B5044618600648FFF7FC +:1080E00017FE08B1FFF7EAFB002C0CBF0E200020A2 +:1080F00010BD00BFA4B00020357F0F00184A1948FA +:10810000002310B51360184A1360184A1360184A08 +:108110001370184A1370184B184A01211960184B34 +:108120001960184B1970FFF7A5FA08B1032010BDAC +:10813000FFF728FC0028FAD1FFF7F0FD0028F6D160 +:10814000114C4FF416702146FFF732FC0028EDD198 +:1081500020684FF41671BDE81040FFF75BBE00BF0A +:10816000C0B00020CCBB0F00ACB00020B4B00020E9 +:1081700090B00020B8B0002094B00020CD800F0057 +:1081800098B00020B0B00020BCB000200C4A08B568 +:10819000002313600B4A1360FFF708FC08B1FFF7D8 +:1081A0008DFBFFF7C5FD08B1FFF788FB0648FFF719 +:1081B000BBFA042802D10020FFF780FB002008BD95 +:1081C000A8B00020A4B0002070B0002037B50D4644 +:1081D000044698B191B10A4B19780022019259B125 +:1081E00001A91A70FFF75AFC019B063B2B802368FC +:1081F0000433236003B030BD0420FBE70E20F9E711 +:1082000090B000200438FFF7FDBB18DF7047000076 +:10821000F0B51D46154B87B018680F4659681B7A94 +:1082200003AC03C42370124B18685968114B0093B8 +:1082300001AC03C42046164603F042FA214602462A +:10824000384603F093F801A803F03AFA01A9024670 +:10825000304603F08BF8684603F032FA694602466E +:10826000284603F083F807B0F0BD00BFD0BB0F0075 +:10827000D9BB0F00312E30000120704710B51C46CD +:108280000B781E2B0AD000232022052102F07AFC55 +:108290004FF6FF70A04228BF204610BD0020F9E72E +:1082A000F8B5069F14460D463A46002118461E466C +:1082B000FCF772F87CB14FF0E023D3F8F03DDB0718 +:1082C00000D500BE4FF0FF300AE0284600F0F8F974 +:1082D000013504F50074BC4206EB0401F5D32046D9 +:1082E000F8BD0000F8B50A4F0D461E460024069B57 +:1082F0009C4206EB040101D32046F8BD3A462846CD +:1083000000F0B4FA0028F7D0013504F50074EEE768 +:10831000C4B0002030B5264D2A7A8DB09AB107AC92 +:108320001422002120460625FCF736F88DF81C5053 +:108330000B9B009394E80F00FCF7CAFF0620FCF7A4 +:10834000F7FC0DB030BD2B68002BFAD0194B197813 +:1083500019B105201A70FCF7EBFCD5E900329A42FE +:10836000EFD307AC142200212046FCF715F86B7AF6 +:10837000DBB106234FF420424FF474214FF4602008 +:108380008DF81C3002F0C0FF0028D1D0102200214F +:1083900003A8FCF701F84FF460224FF4205303A820 +:1083A000CDE90423FFF731FFC2E78DF81C30BFE7AA +:1083B000C4B000204C840020024B0B604FF40073CB +:1083C0001380704709010100012070470048704781 +:1083D000FA840020044B054A1878054B002814BF86 +:1083E00010461846704700BFE0B20020AF8400205E +:1083F0004D8400202DE9FF411E4B187020B11E4B0B +:108400002A229A720022DA721C4A1D4DDFF878C0C7 +:1084100017461C4BEE4603F110067446186859685F +:10842000F046A8E803000833B342C646F6D12B78DD +:1084300003F00F0310336B4413F8103CD373114B4C +:1084400018685968A646AEE803000833B34274467C +:10845000F6D115F8013B04A901EB1313654513F898 +:10846000103C9373A2F10202D3D100233B7404B0F9 +:10847000BDE8F081E0B20020FA84002064B300205F +:1084800060000010E1BB0F006800001010B570B96B +:10849000134B14481968022202F068FF01230133CC +:1084A000DBB211485B0043F44073038010BD052824 +:1084B00014D80B4B53F82040204603F001F9C3B207 +:1084C0001F2B28BF1F23084A2046E118884202F1CB +:1084D0000202E4D010F8014B1480F7E70020E5E732 +:1084E0000C850020E4B20020E2B200204DDF70478E +:1084F0004EDF70474FDF704750DF704712DF704725 +:1085000000F0C8BE002000F033BD00001FB5244BB2 +:10851000402283F8272300238DF807304FF440537F +:1085200004465A681F4B9A4227D10DF10700FFF706 +:10853000E5FF9DF8073003B30120FFF7D9FF0120C5 +:10854000FFF7D4FF0120FFF7D5FF02A8FFF7D4FF04 +:10855000029BDA0702D5002000F09CFE029B9B07DD +:1085600002D5022000F096FE2046FFF743FF00F000 +:1085700027F802F059FE04B010BD002301A88DF8C1 +:108580000430FCF721FC084B039303A8FCF744FCE0 +:10859000FCF748FC4FF08043D3F838340293D7E718 +:1085A00000E100E0DBE5B15101850F00012000F0A2 +:1085B00071BE0120FCF7BCBB0220FCF7B9BB000078 +:1085C0007FB52F492F4802F0E7FF4FF440532E4A62 +:1085D000596891424CD11A78102A46D9142A186940 +:1085E00044D95B69294CB3FBF4F50A2201A904FBC9 +:1085F000153403F021F92649224802F0CDFF01A9E4 +:10860000204802F0C9FF23491E4802F0C5FF0A2294 +:1086100001A9284603F010F901A91A4802F0BCFF8D +:108620001D49184802F0B8FF4FF47A760A2201A9D2 +:10863000B4FBF6F5284603F0FFF801A9114802F053 +:10864000ABFF15490F4802F0A7FF0A2201A906FB5C +:10865000154003F0F1F801A90A4802F09DFF0F4907 +:10866000084802F099FF04B070BD00200023B9E76C +:108670000B49044804B0BDE8704002F08DBF00BF54 +:10868000FFBB0F0024850020DBE5B15140420F0005 +:108690000CBC0F000ABC0F000EBC0F0019BC0F0071 +:1086A00010BC0F0010B503461C1A944200DB10BD2D +:1086B0000C781CB1013103F8014BF5E72024FAE7EF +:1086C0002DE9F3410C4605464FF400720021204687 +:1086D000FBF762FE6DB95E493E22204602F046FE7F +:1086E000552384F8FE31AA2384F8FF3102B0BDE897 +:1086F000F081B5F5017F2DD8691EB1F5817F24BFCA +:108700006FF4817805EB0801C9B10B02C1EBC151CF +:1087100003F5807004EB412440F693651A1FB2F50F +:10872000696F03F1010206D2AB4214BF91B24FF65A +:10873000FF7124F8131090421346EFD1D6E7F823C7 +:10874000237004F109022346FF2003F8010F93422E +:10875000FBD1DAE7B5F5027F3BD86FF4017C6544C5 +:108760003DB920463B490B22FFF79CFF2823E372CB +:10877000203439492E014FF0000801EB05256FF038 +:108780001F07022EB2D80B2229462046FFF78AFF88 +:108790005923212269216374E3746376B31C84F83E +:1087A0000D80A773E1732274A27484F8148084F896 +:1087B0001580A775E17522766383E86830B102F011 +:1087C0007FFFE061013620341035DAE74FF4E9101D +:1087D000F7E7224B9D4289D86FF40277EA19012A04 +:1087E0000FD81D4B03EB0213D9680191084602F024 +:1087F00067FF01990246204602B0BDE8F04102F051 +:10880000B5BD6FF4FD76A9190902B1F5801FBFF45B +:108810006DAF134B236003F1144303F52C1303F6E0 +:10882000023363600F4BC4F8FC314FF46963A361FA +:108830004FF40053A5F20B254FF48072A3600A4B4E +:108840006561E1602261E36104F12000D4E700BFCB +:108850001CBC0F0047BC0F00D4BC0F000801010076 +:108860005546320A306FB10A29009A23F7B5654B95 +:1088700014460A689A420D4639D103F114434A68F6 +:1088800003F52C1303F602339A4230D1D1F8FC21C0 +:108890005D4B9A422BD18B6823F4FF5323F01E03C8 +:1088A0009B049B0CB3F5005F21D10B69B3F5807F6E +:1088B0001DD1C86810F0FF0619D1CB69534A934205 +:1088C00005D0534A934215D0524A93420FD1A0F596 +:1088D0008053B3F5692F07D201234FF4807205F15D +:1088E0002001FBF705FF22E0B0F5805F1FD34FF0BA +:1088F000FF3021E001276772CB68B3F1102F1DD143 +:1089000004223431684602F031FD042205F13801B9 +:108910000DEB020002F02AFD009BB3F5742F03D18A +:10892000019BB3F57E2F01D02772E0E7A772AB69F8 +:10893000002B37D14FF4007003B0F0BDA3F57422C3 +:10894000B2F5204F28D2E27A01F12007DAB9324A93 +:10895000934218D32B69B34215D90422B91968463A +:1089600002F004FD009BD02B14D10422311D0DEB2D +:108970000200394402F0FAFC264B019A9A424FF069 +:1089800001030DD1E372E8682A6901233946A0F595 +:10899000A030A6E70836DDE7B3F5805FC7D3012333 +:1089A0002372A4E72268934207D041F263018B420D +:1089B00000D80AB14FF0FF3323606B6941F26302C4 +:1089C0009342B7D803F0070204EBD303012191408F +:1089D0001A7B1142C8B204D16168024301311A7393 +:1089E0006160D4E900329A42A4D30120FBF762FE11 +:1089F000637A002B9ED0A37A002B9BD10123237294 +:108A000098E700BF5546320A306FB10A4028A5AD3D +:108A10003C8263D629009A2300D80F004FF0805380 +:108A2000D3F83001082802D1D3F8343123B9A0F1AA +:108A30000D0358425841704701207047094B0122ED +:108A400083F8D8200260BFF36F8FBFF34F8F064AC1 +:108A5000904202D0043A904202D1002283F8D820FA +:108A6000704700BF78B300205070024042DF70476B +:108A700043DF704744DF704712DF704710B5134B78 +:108A8000134A5B68C3F3080373B9EFF310835BB950 +:108A900010494B681B0607D58024C1F8844092F822 +:108AA000D8307BB14C60F8E792F8D83033B101464A +:108AB000BDE810400848012201F094B8BDE810401C +:108AC000FFF7BCBFFFF7BAFF4C6010BD00ED00E040 +:108AD00078B3002000E100E07D8A0F000D4B1822E2 +:108AE00002FB0033598A1A8A521A998A92B28A4230 +:108AF00028BF0A46D9681423434303F1804303F592 +:108B00001C33C3F80016C3F80426034B03EB8000A4 +:108B1000FFF7B4BF78B300200470024007B54FF4EC +:108B2000405300205A68084B9A420AD18DF807003A +:108B30000DF10700FFF7A0FF9DF80700003818BFF0 +:108B4000012003B05DF804FBDBE5B15107B5FFF789 +:108B5000E5FF58B1002301A80193FFF78BFF0198AF +:108B6000003818BF012003B05DF804FB4FF08043CC +:108B7000D3F80C0400F00110A0F101135842584141 +:108B8000F1E70000074BD3F8C024D10309D406490C +:108B90000648D1F8C010C3F8A017C3F8A427FFF700 +:108BA0006DBF70470070024078B3002048700240EB +:108BB0000828F0B402D1F0BCFFF7E4BF0F4D104A13 +:108BC000182141436E1800F59473695852F82340F8 +:108BD000F788B388DB1B9BB2A4B2A34228BF23460D +:108BE000142404FB0022C2F80017C2F80437054B16 +:108BF000F0BC03EB8000FFF741BF00BF78B300205B +:108C0000007002402870024070470000014B802233 +:108C10005A60704700E100E0024B8022C3F88420D4 +:108C2000704700BF00E100E0074BD3F80014D3F811 +:108C300000240A43C3F800240022C3F858214FF44B +:108C40008002C3F8042370470070024070B5887832 +:108C5000404D00F07F0318220C26C4095A4306FB3E +:108C600004228E882A44C6F30A061681CA7802F0C6 +:108C70000302012A29D00121374A01FA03F5D4B9A8 +:108C800003F10C06B140C2F80413D2F8141503F531 +:108C900094732943C2F8141542F823402E4BC3F8AD +:108CA000180540F48070C3F80C05BFF36F8FBFF355 +:108CB0004F8F012014E002339940C2F80413D2F818 +:108CC00010352B43C2F81035E8E7082B09D04FF0D8 +:108CD000E023D3F8F00D10F0010001D000BE002019 +:108CE00070BD1D4BDCB9B5F8D42012B18022C3F899 +:108CF0001C250022C3F85021D3F8002312F40012DF +:108D000008BFC3F85421144B4FF44012C3F8042396 +:108D1000D3F8142542F48072C3F81425BEE700226C +:108D2000C3F82C21B5F8C82012B18022C3F81C2545 +:108D3000094BD3F8002312F4001208BFC3F85421E2 +:108D4000064AC3F80423D3F8102542F48072C3F80E +:108D50001025A3E778B3002000700240000820002F +:108D60001D4B2DE9F041802201241C4DDFF8788055 +:108D7000C3F88420274604F10C03A21C07FA02F270 +:108D800007FA03F31343C5F80833A30003F1804344 +:108D900003F51C330026182202FB04805E60314676 +:108DA0009E620134FBF7F8FA082C4FF01802E2D16A +:108DB0000B4BC5F808330B48C5F81C6531466E628D +:108DC000AE64FBF7E9FA044BC5F814758022C5F8C8 +:108DD00010755A60BDE8F08100E100E000700240CB +:108DE0000008300038B4002078B30020F7B51F46E3 +:108DF00001F07F0018231F4CCD090E4643430C2180 +:108E000001FB053104EB010C62500022ACF8047048 +:108E1000ACF8062018B1F5B1FFF760FE18E017BBFB +:108E2000154BD3F88034C3F3C0139D421BD01348B5 +:108E3000FFF724FE124B5B68C3F30803003B18BF27 +:108E4000012300933A463B463146384600F0B5FED2 +:108E5000012003B0F0BD1C44A37A002BF8D0A5720A +:108E6000FFF7A6FEF4E7002DD6D10648FFF706FE71 +:108E7000EEE700BF78B3002000700240507002405F +:108E800000ED00E04C70024011F07F0008B507D102 +:108E90000D4B01225A65BFF36F8FBFF34F8F08BD93 +:108EA0000828F8D0084B41F48072C909C3F8182586 +:108EB000F1D1064B182202FB00339A7A002AEAD03D +:108EC0009972FFF775FEE6E70070024078B3002064 +:108ED00011F0770F12D00A4B41F48072C3F80C15D1 +:108EE000C3F80C25CA09C3F8181504BF01F594711D +:108EF00043F82120BFF36F8FBFF34F8F704700BF40 +:108F000000700240174B0122002110B5C3F8142550 +:108F1000C3F810250A468B0003F1804303F51C3388 +:108F2000013108295A609A62F5D10E4B0E4C5A62F3 +:108F30009A64C3F85821D3F80014D3F800240A43E4 +:108F4000C3F80024D3F80023C3F80823074AC3F862 +:108F500004230021DC222046FBF71EFA4023A382D3 +:108F6000238110BD0070024078B300200514C001B9 +:108F70002DE9F04FB24BB34AD3F80013002385B06C +:108F80001C4601201D4621FA03F6F6070BD552F8C0 +:108F9000236046B100FA03F6344342F82350BFF38E +:108FA0006F8FBFF34F8F0133192BECD1E20706D53A +:108FB000FFF7A8FF00210122084600F0D5FD14F4B8 +:108FC000006FA14D08D09E4BD3F8A8369BB2A5F8F0 +:108FD000D230012385F8D730A30221D5984ED6F898 +:108FE000143513F4807302D0FFF7CCFD0123D6F8BB +:108FF0001025D70540F1188195F8D7305BB1B5F849 +:10900000D2200023012185F8D73092B20091184672 +:10901000882100F0D2FD01220321002000F094FD00 +:10902000660228D5864BD3F8006406F4E062F005AA +:10903000C3F8002406D50122C3F82C250421002002 +:1090400000F082FD71050FD57D4B0122C3F8082584 +:109050009A65D3F8002312F4001208BFC3F8542114 +:109060004FF40012C3F80423B20504D501220521F0 +:10907000002000F069FD23022BD5714BD3F880143A +:10908000C9B28DF80810D3F88424D2B28DF8092023 +:10909000D3F888048DF80A00D3F88C048DF80B00FF +:1090A000D3F890048DF80C00D3F894048DF80D00DB +:1090B000D3F898048DF80E00D3F89C348DF80F3057 +:1090C0004F0601D1052A04D0012202A9002000F098 +:1090D0005EFD5E4B23405BB195F8D830002B40F02D +:1090E000AB804FF0E023D3F8F03DDE0700D500BEA3 +:1090F000DFF85481DFF84891DFF858B14746002681 +:109100004FF0140A06F10C0324FA03F3D807F1B266 +:1091100022D50AFB0693082ED3F808273B6853FA9A +:1091200082F33B604FF0180303FB0653D2B2D8889A +:1091300012FA80F080B2D88000F08E803889904298 +:1091400040F08A80DB88BA889BB29A4240F28480E1 +:1091500011B95846FFF792FC0136092E07F118079E +:10916000D0D13B4B2340002B5BD0354BD3F86C94D4 +:10917000C3F86C94BFF36F8FBFF34F8F14F4806408 +:1091800007D0D3F88044D3F880341906C4F3C01450 +:1091900070D54FF0000A2C4F00264FF0180B29FA1B +:1091A00006F3DA07F0B201D4CEB9C4B1244B1422CD +:1091B00002FB0633FA68D3F8083652FA83F2FA60F3 +:1091C0000BFB0652518A89B251FA83F39BB2538248 +:1091D000538A398A9BB299424FD9FFF77FFC0136F7 +:1091E000082E07F11807DAD100241826012704F108 +:1091F000100329FA03F3DB07E0B203D464B9BAF130 +:10920000000F09D006FB0452B8F80410D3889BB2B3 +:1092100099423DD9FFF7CCFC0134082C08F118081D +:10922000E5D105B0BDE8F08F002B7FF4F4AE4FF42C +:109230000013C6F80833EEE6002385F8D83057E768 +:10924000007002400071024078B30020FCFB1F0058 +:10925000000400014C700240182303FB0653DA8817 +:10926000BA80DA8801230093002392B2184600F0F6 +:10927000A4FC71E74FF0010A8DE7528A01230093A5 +:10928000002340F0800192B2184600F096FCA6E759 +:109290009772C1E7012813B5044600F0C380022885 +:1092A00059D0002855D1784BD3F80025002A50D149 +:1092B0004FF48002C3F808234FF40062C3F800247F +:1092C000BFF36F8FBFF34F8FFFF7A8FB60B16F4BFA +:1092D000D3F8001C032269BB49F27531C3F8001CA6 +:1092E000C3F8142DC3F8001C4FF08053D3F830316D +:1092F000082B0CD1654BD3F8001CC022E9B949F208 +:109300007531C3F8001CC3F8142CC3F8001C5E4B65 +:109310000124C3F80045BFF36F8FBFF34F8FFFF7F2 +:1093200015FCB0B9FFF7FAFB50B102B0BDE8104030 +:10933000FFF79CBBC3F8142DD6E7C3F8142CE6E75F +:109340004FF08043C3F80001D3F800210192019A45 +:109350001C6002B010BD4C4CD4F804351BB1FFF7B3 +:10936000F5FB0028F5D1D4F800341B05FBD54FF4EC +:109370000063C4F80034BFF36F8FBFF34F8F4FF01B +:109380008053D3F83031082B0CD1404BD3F8001C5C +:1093900000293FD149F27532C3F8002CC3F8141CE0 +:1093A000C3F8002CFFF73AFB58B1384BD3F8001C38 +:1093B000A1BB49F27532C3F8002CC3F8141DC3F8E1 +:1093C000002C4FF08053D3F83031082B2E4B0AD1AC +:1093D00040F2E372C3F800284022C3F80428BFF328 +:1093E0006F8FBFF34F8F80220121C3F81C25C3F874 +:1093F0000413274BC3F884215A60FFF7A7FB00280A +:10940000FBD0214B0122C3F80425BFF36F8FBFF3BC +:109410004F8F9EE70022C3F8142CC3E70022C3F845 +:10942000142DCEE7184BD3F80025002A91D0002246 +:10943000C3F80425BFF36F8FBFF34F8F144980200B +:10944000C1F88400D3F80013C3F80813C3F800254B +:10945000BFF36F8FBFF34F8FFFF760FB78B1FFF75C +:1094600007FB0C4B5A68C2F30802003A18BF0122EE +:109470000221002002B0BDE8104000F065BB4FF0B3 +:1094800080435C60EDE700BF0070024000E00640F2 +:1094900000E100E000ED00E00A44034690B288429B +:1094A00002D39A89824202D25A89104480B270470C +:1094B00082888A4210B504D884898B1A9BB29C4258 +:1094C00003D243891A44891A8BB2038210BD9308D0 +:1094D00013B501EB8303044699420BD112F003024A +:1094E00006D0002301A8019301F040FF019B2360F7 +:1094F00002B010BD51F8040B2060EDE72DE9F043F8 +:1095000085B01446BDF83050AB4238BF4289A3EB5A +:1095100005091FFA89F938BFA9EB0209828838BF0B +:109520001FFA89F94A4588460746194605D2FFF7CA +:10953000BFFF058AB0F80490ADB2B9F1000F1FD09B +:10954000A14528BFA146BC88AC421DD9FA889DF828 +:1095500034003968661BA9EB04042C44B3B214FB35 +:1095600002F416FB02F60128B6B2A4B202FB051102 +:1095700016D099450BD802FB09F2404601F0F6FEE1 +:10958000484605B0BDE8F0832D1BADB2DCE732469E +:10959000404601F0EBFE3968224608EB0600EDE795 +:1095A000994506D819FB02F292B24046FFF78FFFA9 +:1095B000E6E726F00305ADB22A4640460191FFF7E3 +:1095C00086FF16F0030628D001990D44C6F1040168 +:1095D00089B2A1424FF0000328BF2146641A0393C9 +:1095E00003ABA4B2A8191A4685420CD13B68013ED0 +:1095F000164419448B420BD1039BC8F80030002C51 +:10960000BED02246D1E715F801CB03F801CBEBE73A +:1096100013F8012B06F8012FECE73968EFE713B5D3 +:10962000930800EB8303984209D112F0030204D09F +:109630000B68019301A901F099FE02B010BD0C68FE +:1096400040F8044BEFE770B59A42A2EB03041D46C5 +:1096500038BF4389A4B238BFE41A838838BFA4B2A4 +:10966000A3420E46114602D2FFF722FF848874B14E +:109670008288AA4208D9C2880168304602FB0511D7 +:1096800001F074FE012070BDAD1AADB2F1E72046C5 +:10969000F9E72DE9F0430746B0F80E908588048A73 +:1096A0001646C288007A85B01FFA89F9A4B288BB31 +:1096B000A145A9EB040038BF7C8980B23CBF001BE8 +:1096C00080B2281A80B2864228BF06464C46AC4279 +:1096D00028D2A5EB0408751B386825441FFA88FCBE +:1096E00015FB02F518FB02F8012B1FFA88F8ADB242 +:1096F00002FB040022D0664517D8724301F036FE03 +:10970000324649463846FFF7C7FEF881304605B075 +:10971000BDE8F083AE4221BF761B02FB0611A146D5 +:109720002E46D3E7641BA4B2D1E74246009101F074 +:109730001DFE009938682A464144DFE7664505D892 +:1097400016FB02F292B2FFF76AFFD9E728F0030492 +:10975000A4B22246CDE90001FFF761FF18F003082B +:10976000019929D0C8F104039BB2AB4200980A6862 +:10977000039228BF2B46013CED1A0DF10C0C20443E +:10978000ADB244466246013C1CF801EB00F801EF23 +:1097900014F0FF04F7D13868904408EB0304421E2C +:1097A000A04504D11844002DAAD02A46CBE718F8CA +:1097B00001CB02F801CFF3E73868F4E7B2F5004FC8 +:1097C00010D882805200C38092B29DF8003003729C +:1097D000531E838152420023C38101604281038270 +:1097E0000120704700207047C189028A89B292B275 +:1097F0009142A1EB020338BF428980889BB23CBFF3 +:109800009B1A9BB2984228BF18467047C289038AA8 +:1098100092B29BB2D31A58425841704710B5C189D1 +:10982000028A848889B292B2914238BF4089A1EB02 +:1098300002039BB23CBF1B1A9BB2E01A80B210BD60 +:1098400038B5C289038A04469BB292B2FFF7FBFE89 +:10985000218A054682B289B22046FFF71DFE20828A +:10986000284638BD73B5C389058A0026ADB20446C3 +:10987000CDE900569BB2FFF741FE218A054602461C +:1098800089B22046FFF708FE2082284602B070BD4C +:1098900038B5C589028AADB292B2AA42A5EB0203DD +:1098A00088BF42899BB288BF9B1A828888BF9BB2BF +:1098B0009A42044614D1007A90B938BD9B1A9BB2E3 +:1098C0009342FBD2E288206802FB030001F04EFDC8 +:1098D000012229462046FFF7DFFDE0810120ECE769 +:1098E0002B46EDE712B10023FFF7D3BE10467047B9 +:1098F0000023C381038283885B009BB25A1E5B42B4 +:10990000828143810120704701720120704700006D +:109910000B4B63B10B4B1B78834206D90A4B1B6878 +:1099200000EB400003EBC0007047C01AC0B2012832 +:1099300003D8064B00EB4000F4E70020704700BF5F +:109940000000000058B4002054B4002004BD0F00F3 +:1099500070B5104E054600242046FFF7D9FF436836 +:109960002846984733780134E4B20133A342F3DA4E +:10997000372200210848FAF70FFD1022FF2107487F +:10998000FAF70AFDBDE8704005481222FF21FAF7F8 +:1099900003BD00BF58B4002059B400205CB40020BF +:1099A0006CB4002037B50C460546C868019200F03B +:1099B000A5FDE368019A0021284603B0BDE83040C8 +:1099C000184773B5054614463AB90378012B04D1FC +:1099D00010460191FFF720F90199281DFFF758FF64 +:1099E00006462CB92B78012B02D12046FFF70EF941 +:1099F00036B94FF0E023D3F8F03DDB0700D500BEC9 +:109A000002B070BD024B5878003818BF0120704773 +:109A100059B40020024B1878C0F38000704700BF93 +:109A200059B40020014B1878704700BF90B4002053 +:109A3000F8B5164E3178054629BB154C1548372226 +:109A4000FAF7AAFC201DFFF753FF134B1C60134BC2 +:109A500023B11348AFF30080124B1860104F00245D +:109A60002046FFF755FF036898473B780134E4B27E +:109A70000133A342F4DA2846FFF7C6F82846FFF779 +:109A8000C5F8012333700120F8BD00BF90B4002059 +:109A9000A486002059B4002094B4002000000000E7 +:109AA00058B4002054B400201FB54378023B0A4646 +:109AB000032B12D8DFE803F0022A1921204B197872 +:109AC0006FF300011970197800246FF341011970C8 +:109AD0005C70197864F3820119701A4B014618689A +:109AE00004B0BDE81040FFF76CBF154B1978C907EB +:109AF00023D5197841F00401EEE711490B78DC0712 +:109B00001BD50B786FF382030B70E6E70C490B78DB +:109B10005B0712D50B786FF382030B700023CDE93E +:109B20000133039303788DF8043005238DF8053055 +:109B3000044B01A91868FFF744FF04B010BD00BF33 +:109B400059B4002094B400201FB50023CDE901339F +:109B50008DF804008DF8051001A811460393FFF756 +:109B6000A3FF05B05DF804FB1FB50023CDE9013369 +:109B700003938DF8040001238DF8081001A8114605 +:109B80008DF80530FFF790FF05B05DF804FB1FB5B9 +:109B9000144600230822CDE9013303938DF8040015 +:109BA00006230DEB02008DF8053001F0DFFB2146A6 +:109BB00001A8FFF779FF04B010BD1FB50024CDE95F +:109BC00001448DF8040007208DF805008DF8081079 +:109BD00001A89DF8181003928DF80930FFF764FF73 +:109BE00004B010BD1FB54FF40063CDE901300391FF +:109BF00001A81146FFF758FF05B05DF804FB00000F +:109C000038B58B7803F07F03082B05460C4608D93E +:109C10004FF0E023D3F8F03DDB0700D500BE002075 +:109C200038BD064B2046997801F00DFB0028EFD097 +:109C300021462846BDE83840FFF708B859B400204F +:109C40002DE9F047DDE9085681460C4690469A46D4 +:109C50000027B84501DC01200EE06378052B04D114 +:109C6000E37803F0030353450AD04FF0E023D3F821 +:109C7000F03DDA0702D40020BDE8F08700BEFAE725 +:109C800021464846FFF7BCFF38B94FF0E023D3F830 +:109C9000F03DDB07EFD500BEEEE7A378DA0914BF8D +:109CA00033702B70237801371C44D2E70B4B01F043 +:109CB0007F0203EB420303EBD1112031487910F00E +:109CC000010008D14B795B0706D44B7943F00403BC +:109CD0004B71012070470020704700BF59B400202D +:109CE0000B4B01F07F0203EB420303EBD111203158 +:109CF0004B7913F0010209D14B79C3F380005B0764 +:109D000005D54B7962F382034B7170470020704791 +:109D100059B4002070B5164D01F07F0605EB4605DD +:109D200005EBD11420346579ED0709D54FF0E02318 +:109D3000D3F8F03DDA0701D4002070BD00BEFBE788 +:109D4000657945F001056571FFF750F80028F4D1F9 +:109D5000637960F300036371637960F38203637175 +:109D60004FF0E023D3F8F03DDB07E5D500BEE4E794 +:109D700059B40020054B01F07F0203EB420303EBD3 +:109D8000D11191F8250000F00100704759B400206E +:109D900010B50B4B01F07F0203EB420303EBD11430 +:109DA000203463799B0709D4FFF76EF8637943F099 +:109DB00002036371637943F00103637110BD00BF57 +:109DC00059B4002010B50B4B01F07F0203EB4203A6 +:109DD00003EBD114203463799B0709D5FFF778F89A +:109DE00063796FF34103637163796FF30003637108 +:109DF00010BD00BF59B40020054B01F07F0203EBFA +:109E0000420303EBD11191F82500C0F340007047E5 +:109E100059B400202DE9F04F87B001F012FA002864 +:109E200000F09182AF4B1D682B78012B02D10020EE +:109E3000FEF7F2FE03A9281DFFF702FD2B78012B88 +:109E4000044602D10020FEF7E1FE002C00F07B82E8 +:109E50009DF80D30013B072B00F2B382DFE813F0D1 +:109E600008001300A8027E028D021F004A02AA0207 +:109E70009DF80C00FFF76CFD00F038FB9A4B9DF845 +:109E800010209A70CEE79DF80C00FFF761FD00F0FE +:109E90002DFB964B002BC5D0FEF78EFBC2E7924CF4 +:109EA0009DF80C50237843F00103237094F825307B +:109EB0006FF3000384F8253094F825306FF38203A4 +:109EC00084F8253094F826306FF3000384F82630A8 +:109ED00094F826306FF3820384F82630002000F0D7 +:109EE0000DFB9DF8106006F06002602A11D14FF062 +:109EF000E023D3F8F03DDC0700D500BE9DF80C0050 +:109F00000021FEF7C1FF9DF80C008021FEF7BCFF89 +:109F100088E7402A0DD176480028EFD000F0EEFA0D +:109F200004AA00212846AFF3008000287FF47AAF0E +:109F3000E4E706F01F06012E00F07181022E00F00A +:109F40009881002ED3D1202A0FD19DF814300F2BE9 +:109F5000D4D82344D878FFF7DBFC01460028CDD0C5 +:109F600004AA2846FFF71EFDDFE7002ABFD19DF8AF +:109F70001130092BBBD801A252F823F009A20F001F +:109F8000F7A10F00EF9E0F00E3A10F00EF9E0F005F +:109F9000A59F0F0021A10F00EF9E0F00BF9F0F0094 +:109FA000D59F0F0004A800F0AFFA9DF812102846C4 +:109FB000FEF73AFE237843F00203237032E763781A +:109FC0008DF80A3001230DF10A0204A9284600F099 +:109FD00059FA27E79DF812906378994537D063784E +:109FE0003BB12846FEF7BCFEA6782846FFF7B0FC3A +:109FF000A670B9F1000F2AD009F1FF30C0B2FEF708 +:10A00000E9F910B14378022B08D04FF0E023D3F8E0 +:10A01000F03DDF077FF56BAF00BE68E7C379C3F3A0 +:10A020008012C3F340131B0143EA4213227822F04B +:10A030003002134323704388C31800F109060093CC +:10A04000009BB3420AD82B4B0BB1FEF7B2FA84F84F +:10A05000019004A9284600F003FAE3E673780B2B7D +:10A0600003BF337896F80380F6184FF00108737831 +:10A07000042BCAD1009B9A1B93B201934FF0000BA3 +:10A080001D4B1B785FFA8BF70133BB42BDDB3846B3 +:10A09000FFF73EFC31468368019A8246284698477E +:10A0A0000828024639D9019B834236D3B8F1010F03 +:10A0B00006D1DAF8083011498B4208BF4FF0020888 +:10A0C0000021CBB298451DD83B4631460C48019241 +:10A0D00001F0E9F8084B019A1B7801339F421644BE +:10A0E000AEDD92E794B4002059B40020B9850F008A +:10A0F00000000000B3850F0058B40020A9A70F008E +:10A100006CB40020B078034454FA83F30131D8785A +:10A11000FF287FF47AAFDF70D3E70BF1010BAFE7D5 +:10A12000BDF81200030A5A1EC0B20E2A3FF6E6AE70 +:10A1300001A151F822F000BF75A10F009FA10F00EF +:10A14000C1A10F00FD9E0F00FD9E0F00D5A10F00C5 +:10A150009FA10F00FD9E0F00FD9E0F00FD9E0F00B2 +:10A16000FD9E0F00FD9E0F00FD9E0F00FD9E0F0047 +:10A1700087A10F00FEF72AF91223024604A92846F8 +:10A1800000F080F9D1E6934B002B3FF4B7AEAFF36C +:10A190000080024600283FF4AAAE4388EEE7022B77 +:10A1A00007D1FEF717F900283FF4A1AE4388024615 +:10A1B000E4E7894B002B3FF4A1AEAFF30080F2E758 +:10A1C000BDF81410FEF762F9024600283FF496AE7F +:10A1D0000378D3E7814B002B3FF490AEAFF30080C0 +:10A1E000F2E7BDF81230012B7FF488AE237843F0FC +:10A1F000080323702DE7BDF81230012B7FF47EAEEB +:10A2000023786FF3C303F4E72378C3F340129B086A +:10A2100003F002031343ADF80A300223D3E69DF89E +:10A2200014300F2B3FF66AAE2344D878FFF770FB4B +:10A23000014600283FF462AE04AA2846FFF7B2FBAD +:10A2400000287FF4EFAD9DF8103013F060047FF428 +:10A2500055AE9DF811300A3B012B3FF64FAE00F092 +:10A260004DF99DF811300A2B7FF4F3AE8DF80A40BA +:10A27000A8E69DF8141001F07F03082B3FF637AED7 +:10A2800004EB430303EBD113D87CFFF741FB0746F4 +:10A290002AB100283FF432AE04AA014661E69DF8D7 +:10A2A000113003F0FD02012A08D0002B7FF41FAE0D +:10A2B0002846FFF7A1FDADF80A00AEE7BDF8122071 +:10A2C00022B9012B284612D1FFF77CFD002F3FF465 +:10A2D000A9AD04AA39462846FFF764FB002000F028 +:10A2E0000DF994F82630DE073FF59CADB1E6FFF797 +:10A2F0004FFDEBE79DF81010394B01F07F0403EBA5 +:10A30000440303EBD11393F825006FF3000083F8A7 +:10A31000250093F825006FF3820083F825003CB9EF +:10A32000059B9DF811209DF80C0000F0FBF879E5E5 +:10A33000D87CFFF7EDFA48B94FF0E023D3F8F03DB1 +:10A34000D80700D500BE07B0BDE8F08F0469059BB3 +:10A350009DF811209DF80C00A04763E5204B1A786A +:10A36000D1077FF55FAD1F4A002A3FF45BAD187837 +:10A37000C0F3C000AFF3008054E5194B1B78DA0737 +:10A380007FF550AD184B002B3FF44CADAFF3008080 +:10A3900048E5FFF7BDFA436913B19DF80C009847F3 +:10A3A0000134124B1B78E0B201338342F1DA39E514 +:10A3B0000024F6E7049B002B3FF434AD0598984742 +:10A3C00030E54FF0E023D3F8F03DDB077FF52AAD11 +:10A3D00000BE27E5000000000000000000000000B3 +:10A3E00059B40020000000000000000058B4002014 +:10A3F00037B514490446CA89888991F90050831AEF +:10A400009BB2402B28BF4023002D10DA904214D07D +:10A410001A4689680C48019300F0A8FF0A4A019B7C +:10A420008021204603B0BDE83040FFF773BC904266 +:10A430004FF0000103D10022F3E78021FBE7024A3D +:10A44000EFE700BF58B500206CB5002011F0800F79 +:10A450004FF000031A460CBF80211946FFF75ABC83 +:10A4600030B4074C05460B4608684968224603C2CB +:10A470000022C4E902222846197830BCFFF7E6BF63 +:10A4800058B50020F8B5184E0C46054608684968CE +:10A49000B260374603C70021F181E1888B4228BFB3 +:10A4A0000B46B381E18889B153B14AB94FF0E0233B +:10A4B000D3F8F03DDA0701D40020F8BD00BEFBE779 +:10A4C0002846FFF795FF30B10120F6E721782846AE +:10A4D000FFF7BCFFF7E74FF0E023D3F8F03DDB07D1 +:10A4E000EAD500BEE9E700BF58B5002002481422B3 +:10A4F0000021F9F751BF00BF58B50020014B18618A +:10A50000704700BF58B5002010B50246044C0068E3 +:10A510005168234603C30023C4E9023310BD00BFC2 +:10A5200058B5002070B52D4C1E462378C909B1EBF3 +:10A53000D31F054618D04EB14FF0E023D3F8F03DBD +:10A54000DA0701D4002070BD00BEFBE7244B13B135 +:10A550002146AFF3008023690BB90120F3E71F4ABE +:10A56000022128469847F8E794F90030002B06DBD3 +:10A57000A0680028E6D01B49324600F0F7FEA2682A +:10A58000E38932443344A260E2889BB29A42E38179 +:10A5900001D03F2E1ED823696BB921782846FFF7DA +:10A5A00055FF0028D9D14FF0E023D3F8F03DDB0769 +:10A5B000C8D500BEC7E70121084A2846984701468A +:10A5C0000028EAD12846FEF75FFC80212846FEF7E6 +:10A5D0005BFCC2E72846FFF70BFFE2E758B5002017 +:10A5E000000000006CB5002070B500F110050446B5 +:10A5F0002846FFF713F93F2817D9E1780020FFF725 +:10A6000055FB90B12846FFF709F93F28E17807D9B3 +:10A6100004F638024023BDE870400020FFF77ABB03 +:10A62000BDE870400020FFF75BBB70BD08B5044B70 +:10A6300040F6B80202FB00301030FFF7D5F808BD35 +:10A64000ACB5002070B540F6B804074E444304F1A1 +:10A65000100092B23044FFF705F905463019FFF7B4 +:10A66000C3FF284670BD00BFACB500202DE9F04106 +:10A670000446FFF7C7F910B90020BDE8F081FFF7E5 +:10A68000C9F906460028F7D140F6B801164D4C43EB +:10A6900004F12408A8444046FFF7A6F80028EBD0B0 +:10A6A0002F193046B978FFF701FB0028E4D004F6F3 +:10A6B00078042544294640224046FFF7D3F8B9786C +:10A6C000044668B103462A463046FFF723FB48B9E3 +:10A6D0004FF0E023D3F8F03DDB07CDD500BECCE74B +:10A6E000FFF7FEFA2046C8E7ACB5002070B50B4C6A +:10A6F00040F6B80303FB0044243492B205462046DA +:10A70000FFF7F0F806462046FFF76EF83F2802D91B +:10A710002846FFF7ABFF304670BD00BFACB5002048 +:10A7200037B5144C40F6B80200212046F9F734FE44 +:10A73000FF234FF4424201256371E2800023082287 +:10A7400063812273009304F138012B464FF4806239 +:10A7500004F110002581FFF731F800952B464FF4E6 +:10A76000806204F5876104F12400FFF727F803B045 +:10A7700030BD00BFACB5002010B50A4C0021052249 +:10A780002046F9F709FE04F110002434FFF7B0F871 +:10A790002046FFF7ADF820460121BDE81040FFF745 +:10A7A000B3B800BFACB50020F7B54B79022B064615 +:10A7B00003D00025284603B0F0BD8B79022BF8D1D9 +:10A7C000204FBB787BBB8B783B700C7809250C4401 +:10A7D00003E023781D44ADB21C446378242B1BD1C5 +:10A7E0009542F6D96378042B12D163790A2B0FD1E5 +:10A7F000154B277801930133009302231A46E11980 +:10A800003046FFF71DFA70B10E3517FA85F5ADB277 +:10A810000C48FFF7E9FECDE7052BE3D12146304692 +:10A82000FFF7EEF938B94FF0E023D3F8F03DDB073E +:10A83000BFD500BEBDE7A3787B7023781D44ADB2C1 +:10A840001C44CFE7ACB50020AEB5002070B50B4678 +:10A850001146127802F06002202A45D1234E8A88E0 +:10A860003478944240D14A78203A032A3CD8DFE831 +:10A8700002F00213162F2BB91D4A0723FFF702FE21 +:10A88000012070BD022BFBD11A4B002BF8D01849C8 +:10A890000020AFF30080F3E7002BF1D1ECE713B910 +:10A8A000FFF7DEFDECE7022BEAD14C881248347149 +:10A8B00004F0010585F00101FFF726F80F4B002B8E +:10A8C000DED0C4F3400229460020AFF30080D7E772 +:10A8D000002BE5D0022BD3D1094B002BD0D04988D7 +:10A8E0000020AFF30080CBE70020CAE7ACB5002022 +:10A8F000B2B5002000000000D0B50020000000002C +:10A90000000000002DE9F347374D1C46EB788B42E1 +:10A9100007460E4607D0AB788B4258D1AB78B3428E +:10A9200032D001245CE0A2B205F6380105F1100036 +:10A93000FEF7D8FF2D4B2BB92D4BEBB92A48FFF76B +:10A9400053FEEBE76B79FF2BF6D005F638094FF095 +:10A95000000805F1100AA045EED019F8013B6A790C +:10A960009A4206D15046FEF751FF10B96979AFF30C +:10A97000008008F10108EEE71E48FEF747FF0028B7 +:10A98000DCD1FDF789F9D9E71B4B13B10020AFF3F8 +:10A9900000800020FFF76AFE0028C2D11748FEF7AA +:10A9A00023FF0028BDD1002CBBD014F03F03B8D149 +:10A9B000A97801933846FFF779F9019B04460028EE +:10A9C000AFD0A9781A463846FFF7A4F908E04FF04F +:10A9D000E023D3F8F04D14F0010401D000BE0024B0 +:10A9E000204602B0BDE8F087ACB5002000000000B2 +:10A9F000997C0F00BCB5002000000000D0B50020FD +:10AA000030B4104B02249A6B83F82C10996883F8A9 +:10AA1000304093F83C408A1A9A6224B942F2050504 +:10AA20009D8783F83E4051B14AB11A7BD20930BCB0 +:10AA300014BF93F82E1093F82F10FFF7A9B930BC6C +:10AA4000704700BF64CE002038B5154B154C054645 +:10AA500073B1607BAFF3008050B942F2077384F8A2 +:10AA60003E00A38728460121BDE83840FFF7C8BF54 +:10AA7000A26BA36894F82F109B1AB3F5805F28BFD0 +:10AA80004FF48053084A9BB22846FFF743F930B988 +:10AA90004FF0E023D3F8F03DDB0700D500BE38BD12 +:10AAA0000000000064CE002064BE002073B5234C7B +:10AAB000E28AA36852BA92B2054612B1B3FBF2F22F +:10AAC00092B2A06BD4F81110B0FBF2F61B1AB3F5DA +:10AAD000805F28BF4FF4805309BA009302FB16022F +:10AAE000174B607B3144FDF7DBFB031E0CDA43F2AE +:10AAF0000333A38701210023284684F83E3002B0A7 +:10AB0000BDE87040FFF77CBF94F82E1006D100938B +:10AB10001A462846FFF751F802B070BD084A9BB2AA +:10AB20002846FFF7F7F80028F6D14FF0E023D3F8D6 +:10AB3000F03DDB07F0D500BEEEE700BF64CE00209D +:10AB400064BE0020C28A836852BA92B223B9002A36 +:10AB50000CBF002002207047C17B282904D1017B53 +:10AB6000C90906D1022070472A2902D1017BC909EF +:10AB7000F8D122B1934234BF022000207047012057 +:10AB800070470000044880F83C1080F83D2080F8B1 +:10AB90003E300120704700BF64CE002002484022B2 +:10ABA0000021F9F7F9BB00BF64CE00200248402223 +:10ABB0000021F9F7F1BB00BF64CE002073B54B79DB +:10ABC000082B054602D0002002B070BD8B79062B01 +:10ABD000F9D1CB79502BF6D1162A07D84FF0E023C4 +:10ABE000D3F8F03DD907EED500BEECE7164C8B78D4 +:10ABF00084F82D3004F12E030E78019304F12F0315 +:10AC0000009302231A463144FFF71AF838B94FF07F +:10AC1000E023D3F8F03DDA07D5D500BED4E7002312 +:10AC200084F8303094F82F101F2322462846FFF76F +:10AC300071F830B94FF0E023D3F8F03DDB0700D5D1 +:10AC400000BE1720C0E700BF64CE00207FB50646D7 +:10AC50001546A1B9137803F07F02022A48D16C7817 +:10AC6000012C45D16A88002A42D1AB883C4D95F829 +:10AC70003020042ADBB204D11946FFF789F80120FD +:10AC80001AE095F82E10994218D1022AF7D1AA6B32 +:10AC9000AB689B1AAB62032385F8303005F12002C4 +:10ACA0000D23FFF737F80028E9D14FF0E023D3F860 +:10ACB000F03DDB0752D500BE04B070BD95F82F10F3 +:10ACC0009942DCD1002ADAD10191FFF753F80199BA +:10ACD0000028D4D13046FFF78FF80028CFD10023C9 +:10ACE00085F830301E4A95F82F101F233046D8E7DC +:10ACF00003F06003202B31D16B78FE2B13D0FF2B98 +:10AD00002CD16B8853BBE9888AB23ABB144B3046CE +:10AD100099872946C3E90D2283F8302083F83E2025 +:10AD2000FFF79EFBABE76B88C3B9EB88012B15D10E +:10AD30008DF80F300B4B1BB1AFF300808DF80F0077 +:10AD40009DF80F3053B1013B8DF80F300DF10F021C +:10AD5000012329463046FFF795FB90E70020ABE73B +:10AD600064CE0020000000002DE9F041AB4C94F8C7 +:10AD70003070012F90B005461E4600F0A181032FD0 +:10AD800000F00D82002F41D194F82F308B4201D07A +:10AD9000012013E01F2E03D12268A14B9A4210D04C +:10ADA00094F82E100423284684F83030FEF7F0FF84 +:10ADB00094F82F102846FEF7EBFF002010B0BDE8F6 +:10ADC000F081984B23626368E67BD4F8088084F8AE +:10ADD0002C70C4E90937012384F8303006F0FD03F4 +:10ADE000282BC4E90D872BD12046FFF7ABFE014687 +:10ADF00018B12846FFF704FE08E0B8F1000F56D05E +:10AE0000282E40F0A8812846FFF750FE94F83030F5 +:10AE1000022BBDD194F82E102846FEF7EDFF002836 +:10AE2000B6D1A368A26B94F82E10934240F2E08151 +:10AE3000207BC00900F0DC812846FEF7A9FFA7E7C8 +:10AE4000B8F1000F17D0237BDB0914D1B8F5805F70 +:10AE500001D90121CDE7744A1FFA88F32846FEF78D +:10AE600059FF0028D2D14FF0E023D3F8F03DDA07A4 +:10AE7000A3D500BEA2E7252E677B08D8192E1AD8C5 +:10AE8000032E00F0F780122E00F09F8096B394F806 +:10AE90003C30002BDDD1A38E634A6449607BFDF713 +:10AEA000EDF9031ED5DB60D1A368002BD1D10223BD +:10AEB00084F83030AAE71A3E0B2EE8D801A353F8E5 +:10AEC00026F000BF35B00F0015AF0F008FAE0F009A +:10AED0008FAE0F008FAE0F008FAE0F008FAE0F0042 +:10AEE0008FAE0F008FAE0F0085AF0F008FAE0F003B +:10AEF0002FAF0F003846FDF7BFF90028D4D194F8E2 +:10AF00003C30002BC3D140F20243A387002384F8D6 +:10AF10003E30BCE7464B002BC6D0E17C3846C1F33F +:10AF2000400301F001020909FDF74EFAE5E70DF1D2 +:10AF3000160206A93846FDF73FFA069B13B1BDF885 +:10AF400016203AB994F83C30002BA0D140F20242CE +:10AF5000A287DCE712BA013B1BBA0892324807937A +:10AF6000082207A900F002FA0823A06800283FF48D +:10AF700070AF834228BF034663632B4A94F82E10B8 +:10AF80009BB26BE70023CDE90733099308238DF8C3 +:10AF90001F300DF11602022306A938468DF8243021 +:10AFA000FDF70AFA069A002ACCD0BDF81630002B1D +:10AFB000C8D012BA5BBA08921B48ADF826300C22F2 +:10AFC00007A900F0D3F90C23CFE72422002107A81A +:10AFD000F9F7E2F980238DF81D30082202232021A1 +:10AFE00009A88DF81E308DF81F30F9F7D5F9102219 +:10AFF00020210BA8F9F7D0F9042220210FA8F9F796 +:10B00000CBF90FAB0BAA09A93846FDF701F90648A1 +:10B01000242207A900F0AAF92423A6E764CE002081 +:10B02000555342435553425364BE002073CE002013 +:10B03000C9830F0003238DF81C3000238DF81D30C9 +:10B040008DF81E308DF81F30704B8BB13846AFF342 +:10B0500000809DF81E3080F0010060F3C7130422C9 +:10B060006B488DF81E3007A900F080F904237CE7B7 +:10B070000120EEE71222002107A8F9F78DF9F0234D +:10B0800094F83C208DF81C300A238DF823304FF0C3 +:10B09000000362F303038DF81E3094F83D308DF801 +:10B0A00028305B4894F83E308DF82930122207A9E9 +:10B0B00000F05CF90023A38784F83E30122354E7A4 +:10B0C000E37BA06B282B06D1636B30449842A063CE +:10B0D000BFF4EDAE97E62A2B41D1E28A52BA92B282 +:10B0E0001AB1A368B3FBF2F292B2D4F81110484F30 +:10B0F000B0FBF2FC09BA02FB1C020096607B3B46E7 +:10B100006144FDF7EFF8002809DAA36B3344A36329 +:10B1100043F20333A387002384F83E3099E6864246 +:10B1200012D9321A40B1A36B03920344391838463E +:10B13000A36300F0B5F9039A94F82F10002300934D +:10B140002846FEF73AFD61E6A36B1E44636BA663D7 +:10B150009E42BFF4ACAE2846FFF776FC56E6237B52 +:10B160003044DB09A0630CD1A38E294A607B04F133 +:10B170000F01FDF783F8002803DA39462846FFF768 +:10B180003FFCD4E90D329A42BFF491AE4FF0E02378 +:10B19000D3F8F03DDB077FF539AE00BE36E694F814 +:10B1A0002E308B427FF432AE0D2E7FF42FAEE37B38 +:10B1B000282B09D02A2B14D0164B53B1607B04F1F5 +:10B1C0000F01AFF3008004E0134B13B1607BAFF3CA +:10B1D0000080002384F83030104A94F82F101F2389 +:10B1E0003CE60F4B002BF4D0607BFDF793F8F0E7C3 +:10B1F0009B1AA362032384F830300A4A0D232846A1 +:10B20000FEF788FD00287FF4C3AD2CE600000000A7 +:10B2100064BE0020000000000000000064CE00209A +:10B2200015830F0084CE002008B50020FEF700FC37 +:10B2300030B94FF0E023D3F8F03DDB0700D500BE76 +:10B2400008BDFEF7EFBB8388C07800F0030002283A +:10B25000C3F30A0315D003281DD001280FD10229FA +:10B2600040F2FF3208BF4FF480629A420FD24FF093 +:10B27000E023D3F8F00D10F0010008D000BE00204C +:10B280007047022904D1B3F5007FF0D10120704747 +:10B29000402BFBD9EBE702290CBF4FF48062402220 +:10B2A0009A42F3D2E3E730B50A44914200D330BD6D +:10B2B0004C78052C06D18C7804F07F0500EB450511 +:10B2C000E4092B550C782144EFE700000649074AB2 +:10B2D000074B9B1A03DD043BC858D050FBDCF9F741 +:10B2E00063FEF8F7D5FF000068BD0F000080002066 +:10B2F000C8860020FEE7FEE7FEE7FEE7FEE7FEE782 +:10B30000FEE7FEE7FEE7FEE7032A70B515D940EA3F +:10B31000010C1CF0030F04460B4621D119462046B0 +:10B320000E680568B54204F1040403F1040317D163 +:10B33000043A032A20461946F0D8541EA2B100F15F +:10B34000FF3C013901E0C3180CD01CF801EF11F8E3 +:10B35000012F9645A4EB0C03F5D0AEEB020070BDB7 +:10B36000541EECE7184670BD104670BD844641EA95 +:10B37000000313F003036DD1403A41D351F8043B6D +:10B3800040F8043B51F8043B40F8043B51F8043BBF +:10B3900040F8043B51F8043B40F8043B51F8043BAF +:10B3A00040F8043B51F8043B40F8043B51F8043B9F +:10B3B00040F8043B51F8043B40F8043B51F8043B8F +:10B3C00040F8043B51F8043B40F8043B51F8043B7F +:10B3D00040F8043B51F8043B40F8043B51F8043B6F +:10B3E00040F8043B51F8043B40F8043B51F8043B5F +:10B3F00040F8043B51F8043B40F8043B403ABDD2CE +:10B40000303211D351F8043B40F8043B51F8043B6F +:10B4100040F8043B51F8043B40F8043B51F8043B2E +:10B4200040F8043B103AEDD20C3205D351F8043BFE +:10B4300040F8043B043AF9D2043208D0D2071CBFCA +:10B4400011F8013B00F8013B01D30B8803806046F3 +:10B45000704700BF082A13D38B078DD010F0030369 +:10B460008AD0C3F10403D21ADB071CBF11F8013BD9 +:10B4700000F8013B80D331F8023B20F8023B7BE728 +:10B48000043AD9D3013A11F8013B00F8013BF9D253 +:10B490000B7803704B7843708B78837060467047ED +:10B4A00088420DD98B1883420AD900EB020CBAB13D +:10B4B000624613F801CD02F801CD9942F9D17047E7 +:10B4C0000F2A0ED8034602F1FF3C4AB10CF1010CE1 +:10B4D000013B8C4411F8012B03F8012F6145F9D190 +:10B4E000704740EA01039B0750D1A2F1100370B5E9 +:10B4F00001F1200C23F00F0501F1100E00F11004F2 +:10B50000AC441B095EF8105C44F8105C5EF80C5CFF +:10B5100044F80C5C5EF8085C44F8085C5EF8045C77 +:10B5200044F8045C0EF1100EE64504F11004E9D174 +:10B53000013312F00C0F01EB031102F00F0400EBCA +:10B54000031327D0043C24F003064FEA940C1E4456 +:10B550001C1F8E465EF8045B44F8045FB442F9D1C8 +:10B560000CF1010402F0030203EB840301EB8401FC +:10B5700002F1FF3C4AB10CF1010C013B8C4411F883 +:10B58000012B03F8012F6145F9D170BD02F1FF3C99 +:10B5900003469BE72246EBE7830710B5044610D12C +:10B5A0000268A2F1013323EA020313F0803F08D1BD +:10B5B00050F8042FA2F1013323EA020313F0803F75 +:10B5C000F6D003781BB110F8013F002BFBD100F03F +:10B5D00003F8204610BD00BF80EA0102844612F045 +:10B5E000030F4FD111F0030F32D14DF8044D11F07C +:10B5F000040F51F8043B0BD0A3F101329A4312F02F +:10B60000803F04BF4CF8043B51F8043B16D100BF07 +:10B6100051F8044BA3F101329A4312F0803FA4F198 +:10B6200001320BD14CF8043BA24312F0803F04BF1F +:10B6300051F8043B4CF8044BEAD023460CF8013B8C +:10B6400013F0FF0F4FEA3323F8D15DF8044B704736 +:10B6500011F0010F06D011F8012B0CF8012B002A74 +:10B6600008BF704711F0020FBFD031F8022B12F063 +:10B67000FF0F16BF2CF8022B8CF8002012F47F4F1E +:10B68000B3D1704711F8012B0CF8012B002AF9D126 +:10B69000704700BF00000000000000000000000034 +:10B6A000000000000000000000000000000000009A +:10B6B000000000000000000000000000000000008A +:10B6C00090F800F06DE9024520F007016FF0000CE2 +:10B6D00010F0070491F820F040F049804FF000048A +:10B6E0006FF00700D1E9002391F840F000F1080065 +:10B6F00082FA4CF2A4FA8CF283FA4CF3A2FA8CF39D +:10B700004BBBD1E9022382FA4CF200F10800A4FA03 +:10B710008CF283FA4CF3A2FA8CF3E3B9D1E9042357 +:10B7200082FA4CF200F10800A4FA8CF283FA4CF38E +:10B73000A2FA8CF37BB9D1E9062301F1200182FA48 +:10B740004CF200F10800A4FA8CF283FA4CF3A2FA4E +:10B750008CF3002BC6D0002A04BF04301A4612BA5C +:10B76000B2FA82F2FDE8024500EBD2007047D1E95F +:10B77000002304F00305C4F100004FEAC50514F0EE +:10B78000040F91F840F00CFA05F562EA05021CBFBF +:10B7900063EA050362464FF00004A9E7F0B5254FC0 +:10B7A000A2F1020E164605460C460FCF8BB0EC46B2 +:10B7B000ACE80F000FCFACE80F0097E803004CF89F +:10B7C000040BBEF1220F8CF800102ED804F1FF3EBE +:10B7D00070464FF0000CB5FBF6F206FB125328330F +:10B7E0006B44614613F828CC00F801CF2B469E42EB +:10B7F00001F1010C1546EED9002304F80C3089B193 +:10B80000A44472461EF8010F1CF8015D8EF800502A +:10B810006FEA0E0302322344121B0B449A428CF847 +:10B820000000EEDB20460BB0F0BD002020700BB016 +:10B83000F0BD00BF34BD0F00FFF7B0BF53B94AB928 +:10B84000002908BF00281CBF4FF0FF314FF0FF3028 +:10B8500000F074B9ADF1080C6DE904CE00F006F803 +:10B86000DDF804E0DDE9022304B070472DE9F0477C +:10B87000089D04468E46002B4DD18A42944669D9D4 +:10B88000B2FA82F252B101FA02F3C2F1200120FAB7 +:10B8900001F10CFA02FC41EA030E94404FEA1C4805 +:10B8A000210CBEFBF8F61FFA8CF708FB16E341EA01 +:10B8B000034306FB07F199420AD91CEB030306F187 +:10B8C000FF3080F01F81994240F21C81023E6344A8 +:10B8D0005B1AA4B2B3FBF8F008FB103344EA03444C +:10B8E00000FB07F7A7420AD91CEB040400F1FF3361 +:10B8F00080F00A81A74240F207816444023840EA9E +:10B900000640E41B00261DB1D4400023C5E90043D6 +:10B910003146BDE8F0878B4209D9002D00F0EF8059 +:10B920000026C5E9000130463146BDE8F087B3FA8C +:10B9300083F6002E4AD18B4202D3824200F2F98074 +:10B94000841A61EB030301209E46002DE0D0C5E977 +:10B95000004EDDE702B9FFDEB2FA82F2002A40F0C3 +:10B960009280A1EB0C014FEA1C471FFA8CFE0126C6 +:10B97000200CB1FBF7F307FB131140EA01410EFB6A +:10B9800003F0884208D91CEB010103F1FF3802D211 +:10B99000884200F2CB804346091AA4B2B1FBF7F00B +:10B9A00007FB101144EA01440EFB00FEA64508D92E +:10B9B0001CEB040400F1FF3102D2A64500F2BB806B +:10B9C0000846A4EB0E0440EA03409CE7C6F12007BA +:10B9D000B34022FA07FC4CEA030C20FA07F401FA00 +:10B9E00006F31C43F9404FEA1C4900FA06F3B1FB89 +:10B9F000F9F8200C1FFA8CFE09FB181140EA0141EE +:10BA000008FB0EF0884202FA06F20BD91CEB01018A +:10BA100008F1FF3A80F08880884240F28580A8F1E2 +:10BA200002086144091AA4B2B1FBF9F009FB101134 +:10BA300044EA014100FB0EFE8E4508D91CEB0101D2 +:10BA400000F1FF346CD28E456AD90238614440EA75 +:10BA50000840A0FB0294A1EB0E01A142C846A646F5 +:10BA600056D353D05DB1B3EB080261EB0E0101FA7E +:10BA700007F722FA06F3F1401F43C5E900710026DB +:10BA80003146BDE8F087C2F12003D8400CFA02FC31 +:10BA900021FA03F3914001434FEA1C471FFA8CFE41 +:10BAA000B3FBF7F007FB10360B0C43EA064300FB31 +:10BAB0000EF69E4204FA02F408D91CEB030300F1CF +:10BAC000FF382FD29E422DD9023863449B1B89B286 +:10BAD000B3FBF7F607FB163341EA034106FB0EF30F +:10BAE0008B4208D91CEB010106F1FF3816D28B42BC +:10BAF00014D9023E6144C91A46EA004638E72E4688 +:10BB0000284605E70646E3E61846F8E64B45A9D27F +:10BB1000B9EB020864EB0C0E0138A3E74646EAE7EE +:10BB2000204694E74046D1E7D0467BE7023B61449C +:10BB300032E7304609E76444023842E7704700BF05 +:10BB4000F8B500BFF8BC08BC9E467047F8B500BF0A +:10BB5000F8BC08BC9E467047088000200010020018 +:10BB60000338FDD87047000000000000000000000E +:10BB70000338FDD87047010000000000089900203C +:10BB80000338FDD87047416461444655004D6573E4 +:10BB90006874617374696300302E392E32207331FA +:10BBA000343020362E312E3100000000000000001D +:10BBB00000000000000000000023D1BCEA5F7823F1 +:10BBC00015DEEF12120000000000000070B000202F +:10BBD0004164616672756974006E52462055463242 +:10BBE00000303132333435363738394142434445F9 +:10BBF00046006E52462053657269616C0009045319 +:10BC00006F66744465766963653A200053002E00C0 +:10BC10006E6F7420666F756E640D0A00EB3C905574 +:10BC20004632205546322000020101000240000049 +:10BC300000F80201010001000000000009010100FC +:10BC4000800029420042004D455348544153544915 +:10BC5000430046415431362020203C21646F6374F8 +:10BC60007970652068746D6C3E0A3C68746D6C3E3A +:10BC70003C626F64793E3C7363726970743E0A6C17 +:10BC80006F636174696F6E2E7265706C6163652895 +:10BC90002268747470733A2F2F6275796D656163D1 +:10BCA0006F666665652E636F6D2F6D61726B2E62B8 +:10BCB0006972737322293B0A3C2F73637269707433 +:10BCC0003E3C2F626F64793E3C2F68746D6C3E0A77 +:10BCD00000000000494E464F5F554632545854000C +:10BCE00024850020494E44455820202048544D00CA +:10BCF0005ABC0F0043555252454E5420554632000F +:10BD00000000000021A70F0079A70F00A9A70F00CE +:10BD10004DA80F0005A90F00000000009DAB0F000B +:10BD2000ADAB0F00BDAB0F004DAC0F0069AD0F0008 +:10BD30000000000030313233343536373839616233 +:10BD4000636465666768696A6B6C6D6E6F7071724B +:10BD5000737475767778797A00000000000000002F +:10BD60002885FF7F010000000880002000000000FF +:10BD700000000000F48200205C830020C4830020C7 +:10BD800000000000000000000000000000000000B3 +:10BD900000000000000000000000000000000000A3 +:10BDA0000000000000000000000000000000000093 +:10BDB0000000000000000000000000000000000083 +:10BDC0000000000000000000000000000000000073 +:10BDD0000000000000000000000000000000000063 +:10BDE0000000000000000000000000000000000053 +:10BDF0000000000000000000000000000000000043 +:10BE00000000000000000000000000000000000032 +:10BE10000000000000000000010000000000000021 +:10BE20000E33CDAB34126DE6ECDE05000B000000E6 +:10BE30000000000000000000000000000000000002 +:10BE400000000000000000000000000000000000F2 +:10BE500000000000000000000000000000000000E2 +:10BE600000000000000000000000000000000000D2 +:10BE700000000000000000000000000000000000C2 +:10BE800000000000000000000000000000000000B2 +:10BE900000000000000000000000000000000000A2 +:10BEA0000000000000000000000000000000000092 +:10BEB0000000000000000000000000000000000082 +:10BEC0000000000000000000000000000000000072 +:10BED0000000000000000000000000000000000062 +:10BEE0000000000000000000000000000000000052 +:10BEF0000000000000000000000000000000000042 +:10BF00000000000000000000000000000000000031 +:10BF10000000000000000000000000000000000021 +:10BF20000000000000000000000000000000000011 +:10BF30000000000000000000000000000000000001 +:10BF400000000000000000000000000000000000F1 +:10BF500000000000000000000000000000000000E1 +:10BF600000000000000000000000000000000000D1 +:10BF700000000000000000000000000000000000C1 +:10BF800000000000000000000000000000000000B1 +:10BF900000000000000000000000000000000000A1 +:10BFA0000000000000000000000000000000000091 +:10BFB0000000000000000000000000000000000081 +:10BFC0000000000000000000000000000000000071 +:10BFD0000000000000000000000000000000000061 +:10BFE0000000000000000000000000000000000051 +:10BFF0000000000000000000000000000000000041 +:10C000000000000000000000000000000000000030 +:10C010000000000000000000000000000000000020 +:10C020000000000000000000000000000000000010 +:10C030000000000000000000000000000000000000 +:10C0400000000000000000000000000000000000F0 +:10C0500000000000000000000000000000000000E0 +:10C0600000000000000000000000000000000000D0 +:10C0700000000000000000000000000000000000C0 +:10C0800000000000000000000000000000000000B0 +:10C0900000000000000000000000000000000000A0 +:10C0A0000000000000000000000000000000000090 +:10C0B0000000000000000000000000000000000080 +:10C0C0000000000000000000000000000000000070 +:10C0D0000000000000000000000000000000000060 +:10C0E0000000000000000000000000000000000050 +:10C0F0000000000000000000000000000000000040 +:10C10000000000000000000000000000000000002F +:10C11000000000000000000000000000000000001F +:10C12000000000000000000000000000000000000F +:10C1300000000000000000000000000000000000FF +:10C1400000000000000000000000000000000000EF +:10C1500000000000000000000000000000000000DF +:10C1600000000000000000000000000000000000CF +:10C1700000000000000000000000000000000000BF +:10C1800000000000000000000000000000000000AF +:10C190000000000000000000FFFFFFFF7C7F002088 +:10C1A0000090D003FF00FFFF320000007D7B0F00F6 +:10C1B000F17B0F0001090262000301008032080BCD +:10C1C000000202020000090400000102020004054E +:10C1D0002400200105240100010424020205240694 +:10C1E00000010705810308001009040100020A008C +:10C1F000000007050202400000070582024000001F +:10C200000904020002080650050705030240000069 +:10C210000705830240000009024B00020100803242 +:10C22000080B0002020200000904000001020200E3 +:10C230000405240020010524010001042402020554 +:10C24000240600010705810308001009040100020B +:10C250000A000000070502024000000705820240B4 +:10C26000000012010002EF0201409A2329000001A0 +:10C2700001020301FDBB0F008DBB0F008DBB0F0042 +:10C2800064B30020F2BB0F00D9BB0F00554632202B +:10C29000426F6F746C6F6164657220302E392E327C +:10C2A000206C69622F6E726678202876322E302ECE +:10C2B0003029206C69622F74696E79757362202849 +:10C2C000302E31322E302D3134352D673937373518 +:10C2D000653736393129206C69622F75663220281E +:10C2E00072656D6F7465732F6F726967696E2F6306 +:10C2F0006F6E6669677570646174652D392D67614D +:10C30000646262386337290D0A4D6F64656C3A20A8 +:10C3100068747470733A2F2F6275796D6561636FFD +:10C32000666665652E636F6D2F6D61726B2E626937 +:10C330007273730D0A426F6172642D49443A204D45 +:10C340006573687461737469630D0A446174653A56 +:10C350002053657020203120323032340D0A000025 +:10C3600000000000000000000000000000000000CD +:10C3700000000000000000000000000000000000BD +:10C3800000000000000000000000000000000000AD +:10C39000000000000000000000000000000000009D +:10C3A000000000000000000000000000000000008D +:10C3B000000000000000000000000000000000007D +:10C3C000000000000000000000000000000000006D +:10C3D000000000000000000000000000000000005D +:10C3E000000000000000000000000000000000004D +:10C3F000000000000000000000000000000000003D +:10C40000000000000000000000000000010000002B +:10C4100098B4002010000C000000E0FF1F00000096 +:10C42000000000005D450F0069420F0041420F000F +:10D80000F1109E1E797A22200500000064000000BD +:10D81000CC00000000001000CD000000000004005B +:10D82000D000000029009A23D10000004028A5ADB7 +:10D83000D2000000200000000000000000000000F6 +:10D8400000000000000000000000000000000000D8 +:08D850000000000000000000D0 +:020000041000EA +:0810140000400F0000E00F0096 +:00000001FF diff --git a/bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.zip b/bin/generic/Meshtastic_6.1.0_bootloader-0.9.2_s140_6.1.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..ae035898dfa0e679bcfb1726c0f63075ec084f08 GIT binary patch literal 190874 zcmbq+dwdkt+5ef@+1=UAZE^tuBxE)hGKruYKn<4CO~6TlTH>wM*47PPx=}03McHsM zn}E2%(uTfJq1L*ntqGQz4aS#AC+6+QVF{s68Y=y`;o-*n?_06*$_G{~eRz#s#J|GvZ-v*1 z$uGv&d+&eb?nm!izM^sI!^m)6dG!pVw2`-B>HRD3z3zv7 z+uLx@gZHhx8`%y20~4d62Cw&_#(Nr8qC=N-Y|gB?bLL)i?X0<%^p6&&{|d+YxEx&_ z8;7fBRnGYuj+d5P+2o}?sho9rH;f|pH!lD2s)ptF-F?sUdn&F$sjFu@EB_s=OGbD3 zealer`xi!@0vi8!&DB@Wp7TF)esI~Bb(iBoXJKrxE3U3^R$OzT@H!*4&c5z)^4a~5 zvro8AvV=YtX%4gK&n9B%yDVw!CuGvto2KdW^?zx1B+Jq7<7FPfw>4wm$G=_rZn-4y ziLvx%y#E8wj!W_`t@HKtv;RxE-~2CmUx)L*{U#c#7i-@du?O2vk<`|O>lf}CV5}cr z&xDJ$IU_HhwWYsyDuZNeOh)ehktqu_#*|xg$BiuU;ju4VtdUefXTFiGSvh_`Qa9vh z8t?DlJI$=%_k;INWAJwc`WDj&7;` z+hK1F2D*q8lM*srxK7&dJ1}zW7OS-1d7yTGirZUL&_lFu>YIUALEkiu%She- za?s{y773{P(ta?US6p(Pe2e^jIpY;J$T*oEOD(A(``}^3$^RI&I6A zNl0s>sjj7AvBnT#A^ld8MF{iCv{y(^j`pWG^9r+lqs+X{bWQ6cq*#05HE++98_lv! z6urSbqJ{N4#h|}T7UjNEUKg+ZWiPM&*IsW=s+?J8^Oc2y18(MrW&B}A%vRkJr!-2e zvfwO3QdRNK_HS=Jl};>Y{3LkZUte6H=>CT#xvNHWd;1uX;=&Um=Hk&X=d5Dagl`}LEwK?@C)f`$ksoAKdA0nck zj#!`2BZd;ue8@yybaGAuf_jJF(D2{ zq_a#MDR|G-ImcwYuf{tK?+U!r@t%oy2Hs_Ozq^2FCA!5i5x*UHPry507v)5>EI~1P z)xr3^HHqk~{h77i8i78Ggq|Ev#tj*Z_}Nq3iD+pewU*>_ZlMn&TT^GXr4xz!hzzKr zUne=32`z&pf92%Uc`*%5! zk0!)kw~uHh!b*&S-l;?!__lL-Eijy#$PEeNgs#3*+WI<~5gF~LaUy*cW=b~e)%A9@xZ?m3H8hp>_(@4W$HF06~HY5FvPKdqJrLp*hNod=xJIQW+me<{8eR;P&hon5^ z_6WaAZ?|vtp#9f}h}048(?#O+Q!J{#Vnm7og-YAB5I@>XLS~UeVa*}g9jM<)>;DS% z14ezXzaqa@CfaO#dBW&X^;f?&U|sA>o6#3QsQ-Fzol|_8uZ0BhmnSOz+R0(z-C-3X2 z3WZ%57XX{<0JfItP4s)MiloiwK3}@b~Gg(15?3uoHhR!U_M0 z2-DNrMMsyAyK7mr?K0Yq#%K+;jG%UUUtSM!kS(kOr39uu5lu6Cl-GA_?E`YvSes%Z zSz{12^7`UEH{NKGC&=RJ47uNGR(Rn0=TSM|4h#iDHijh+dU_Gf_KGrCzAp8=cIq_{LbNoRpZ%)w-o{971fQ!fU-A+x*a z2yR6$c%ifUkH9J3x3OKf35_q{*Z`$H8C6p>s z$!=XB86Dvj?{OmM2M^2L&i71KVsiM;lNzG4Y z@QSqBBv}+`O)_Va=x)*aI@(4eGf!(Z8@qDkEqZEu`Y+Su-T@{fMG+ii&6S%1vRSe! z7HKwMSt#duf_0pYoN7yTkTWjA;K}vR76rinQaRP;ZXu;pRPqZ?fFEE@%&x{pqHRc6 z&hS`i)-yHC#%}IR6$=U_?Ja2o7RDu~&&tvck2dG+?{>HCtv+$=&yhKRcB+lykf~%j zY8RwCa{;~IfL?3hMif>QSCG=Q0nT2aS{(n3T;AdhV=Zp%rckmJ>zQ{52TbY8IM5;# z+6P!ha29EQxzO%EjjPZIeEhX$tix#WT|bJL&pe(=&fJJGst|cUDLZMp6Jk%8F7+}eUZu1DQk%9Eo=XKJ5bx{h&5xmeRd!v6gO6lU5u$eu`+nwx;Jv&Pw_A z2Ue_DK^!vn3atC4c;6}gXX2VIG7mY*WUPj3{Z7%YlKq{!-T!5T=5b@+D@4gp$95q6 z-WXF2WK$3w1sQ8QVr+ofJ>XFYiGo7l;23)68o zE7tAIn#udjC7frfx*c5BCYh8pDGPm(RqGB@Nv)@APu-g8z_JBfs^@;Nse4a1lP_pb z$2i&QXI1O3xDtL&YDf$SA=AthnSH5Du9rc9!&btCgL!Pbb^CR| z>s_q34$!Wt(_wwxRxB`qf>D_hJtNyMy^nWAu#7t>EJ zIBOy)C#|CGWSVF_X%fvR`_NL6{Qha;Ek|3{9YmWj`rTgBOg?PgzNfotjmxua!6pOm zTVu4>|D{-Ww1Dd#2F=G8Z;_`2^5jjjUmgyzFVz9pT|;Iv8PxOJywZT6eHEW2Tc?-F z-;l{K1dSUJ14di*i{zVS7v!VkF(Qh7M(jHk{}~ZSAggce%9D46xT4KEab{CF#CZ#q zL&QzkgVRMD(y0O|dO&tyE=m#>$Qc6ad3(}ivqR8Uk8*^B6UWPBZXk zkOmydbgOn|n3E-+Ao+}Sxau2oZmp=us;H>o@l;euO{Ac|o650*B!qMJC5~UMa=nS; zW(PQ{cEc!HL-!+2N>Qwmgn713zg?A@{6-y7sSkY>k(yk_ugQQgN%l^dwVM5aky+KOPAYr*FdgaZVhUD{6MCG!-k@oAa)=#a zG?Gu{bpg6zb`RNJ8zRLX^mK5T%wWUOMPx^&JFkx^CgM3`M@fYQRqPPuBCUMHjGlG) z+$*HOas8F&4VcW!zBc(ov@dAda1v9>`KG(UbNs79wh`-qE#IyrOB^37aliWy$Ys=n zI_ED7BPzO>;IprV6(oT%GbQ&!kfNaJQC#1AFL|S8Sr*xmuPG+1@8qlVIHcQlG z8;y;oUG}GmHZ*MCPO+$6NFnnBsVvu-u{}$>X(%@Yyt;r@7r2SQs7KVlsuY(#VB4H` z5r@AW`;(g10eSbOLb*()lJ?Y*H&y2v9{WJzxXDOSYChE_I4O457}${+-%>i;8d?V0 zcD9tFjLp~!I@-uUsxeyEj*Zp~jMk|((*9B##s%1wHGW4u)i&25IQ`ueT5?QR8nC3* z>5^Sx9gY>J+fKAdn@IZ^)Iv{xIZtIAgOSUzymMqJ+D-0FS-{DF<-Kta#p(rJz}%;I zZ2(WaOBGdCtx)Hx)hM6bzoboQ*PCVZXG4fXi?kMPwQQ54)q|F=*hKNoVyWa=GkhIt zCU?7wEzR3wM!gqwyU`MDvGi->c-Qc>uYGxSWP&O=}@!T!{Cie#9Cf9E3SS~lfn;g)dD97gtL!XZ4FCWXNc|{(YYc6~axfWyP-*cgM z7O`n3M-3ik&QzIv5-=4m$aNSr`XuzY6ve&frEbrH)^4|l?%}2(x2GyV%1(#9ecqmQ z@CA$ZMk3i$MiiIdzx+PGc!|8>bV3M0>lW?zl(Q^&vMUW5Pm65nOhk8#5Gf}NJ^4|l zbn!Xyj-P}~B6dF_fYJnFh4y6KCCXf9ruLr+U&tz^7}gX+>#oP1=%r<`L%Oju`cqZQ z_MJXvr6X{lh2~N1lYXc956G;eR<=Byh_;P<9N~MI!p%Ah{f=dK>qNB6rlLly$oVn8 z?_!>5ERQqtoX8_RPjoefSky%wx<89`bbmJXWV0g?{r-rxix_*dEo3d4_^H*f<^_!Y zT8#cW(BJg%+K@%0*o|TDvON8O%sW1Ap6gPq`Z#Pc4xgpW0shNqqS-vrlHKnH8=qPcfI3D^~I4 zh|R!_T1&?5BtegI4%Znu%l)jP{xlFcZXs1VY z$kmzh-%qpE^FpRFlR}D|uk+fReN54CWQyMj8+1}mPeiX8p)1g=eKVF^qm09>$*(Wg z)WXH*h>JMh)cwrGSR>Za^et|DVxE(#IyhXfE$p7%N(aoF^{+JmU(& zwvmH0hqSkc+G6{Lc>H~BC~28Jf;auG44u1?DY?)68#fUT>MrJ>z7S6>Lu>RHHM)mr zddpCW+Y5U!O>Z~8ZyWlomT^?MTil=4**&B@7AB5<-Idv|FT_)uNlR`W>N_R4Xgfda zP1b$#!k3M`q{kUzAaAmGfV)Wg4%a51L$K!(aGr1-Bor6#R&MG~d0XdAf2}3OocnFv zRmF=3uO`~9LvQQzs{UBZdA(TG2Nn^B1c;e*!~l`+TYS#%d=vHF)N4)qYUeo__nW}5 z?-cJ+A{fUDd9T#EjJ$=Jdumfv)Dp*Q?XF#DI}f}j!kWWTjq?NGUHE)0 zQ$(eqCtc*s2Ihe7x_x>Ge=!=|Y?Ior^Rtt`7S8tefjd^^gE!Ki&jy~AbZ5J6+ZG2vax| zSqUEjrDmcX)4lMI904Ve>5OCxAHnGLo6JY)NFGEQG}3ScGy=Lb^86+qd1NeaVFM#( zX`QhnprG**E~A7W)J1F8Hn1qMJ@&&|Gw6x7_^kfDo2brG4^M<8;qm1{tMz!Zw2iUb zYdMrLiFyPp$$?gc13F>7HPl1!qrErOF(ThU``fU$wr=scwaDd7FzU(78NcZ*-Bp6e z;mYgpwn*pUo2s%e_{w(6%?tVmc_NIk8i5UiIa3e*E%Yo%sXcq`z4t7bntB(T>aM)e zG{TX}8*V%sEncwT`)S!D+&NgQL|Vg@C3tyH?m`~kh8eXkj9MA+Na95#+ebtzU>V02 zm&@iid=C9O$%M~AO8YlC{>*WnKui3)y8f*Hw?6~Ej{@ScFG{P+%FhHx0bl6Cy6+(_ zR||113?=yi@#FX7#5D&pQI45)LD5evO=SoJR%x$==f-2Jv(@qyygN(#K@2`O!=GXS z-SKWZ`YihB$JmeiO=1PaCG@0W^jGNzyO(N^4YcKYfA@xJD&fwjb3^?xMu}Se8#hw! zLKvn5TF|Yu)?&THfa}h|*fEr|(66&~r@>PwCm~p;f{T8gyLX0xVR_vZ28N||VG0ZW zxWsYz#tcjy$<!i3!_orgsdRUng z=hT^0=1jk7YSR@V*R=j_{ZQg~0-Ui*cZ&GVkitdFHZ5Zc?aN4C8TcK0rk^m&C=GMe za&|azyttN}Imn67#_3GBn|3zQHPC{2aWA`w4Pga?7fr4;dvfb08{?W&%T1@f%3kcU zO}LSha#4Sx>hM{iO|eZ(IUlZWTHo}`YEG$cYF+kSy@L2_L?4ANdO3+X3|oiy(P>|G ze|Ct&Y?$4YuT55odJN8m@$sk!{h4bmix|%qcoeDcjWcM4((P@?qkcr!XLQe^+Ne!? z+Spl>mJUC5+!=^B`RKFI&D00^DoG@M0&;KM@_q6jcgOz~+%Rv2zH&*<4=w$FT7EX$ z?d;LPSu8U;ed&;lIL(xvo^G~bUr^aHy5n%Fsf@*&KwJ2o*9k8JCkvHlZn8?LhV_I? zwWQ)7p}K_hSSGB5$+9yZbsA%rsl7YG!3Ml@RGjj5w?)j*Zh=-#-`|NjrhIUt_*fRk zkcb(%R(x9+v&i&a8*@&Xn=3w+jh&r{%)+3LgN+dDY@sxzznhL3BYp<1J%Hz2mc>6~QhoGB7t=Q}c?XtFQak!o=}v z6_UF2Em&2)slHO%;xm`cRR}P)>3Z+Q83VoZ2l7&(^eo4m?$EO>=itycG zq^2pw*j1-XQRs~o-JADm_wTCDfjX1BuURgT+adFjV&aKM zpU|DXodLQy40S8QTMq|C$rU~lM0d4K%Dgz_tu zc^^+GzYe3#YICy+3ni(%yrN?6g@1*F|ABMgy?HNmKUY0;Y_`fue}{#WzbgQ_%_=d6 zTvq0YU9?7=KI-+HpdMK8p4F-{7&ie@`N&Dex^uvPGbuZ-~uYxx`9X9-Q%ro`C@qPKVjdF>T(S8k|;(2@>jF?2CQg0g}|B6%^>$wp& z?Vln)EsQ#X=xZ3P!L;12GZ_bUhI9m8(9Pfhm3@^x*}jetD`hJQ(3|yzAIrravh~I$ z^O>zV#lCw7YfRZ*6IM-z`cH<4CN{woX>ojGrNQvAZaSi7b%7Qqf>(f+%HYj=-b4$5 zz;kG!vX6CSXh&Y7IN5xKuq^Uub2sp$8ly)}(>g(Ccg4-b6n4NGn23HmF3FYDD{J)T zeL&g`I3sj4Fb<5uprJ_)-s7rt=8Zd_hUe6=ru8|UE}H% z*E)OMYsKE+TA^26+izhMi?|)NNDw1|`C_4qGVMVR+s+MS5JsCH+YH}8BDy8MSx9`m~mh;dqZW6);?ZE%p3ne7dTpdn$g*R=fvQl_vF;>CpiPYaapZL#6WIn;o15I#PNJ!6%Sxsa)JG1H}z-k zMjx5&)^BJ5G4(T_$&a9^rp^YCAYz5~`y@Y6&Yd)TBFV8z))E#kM{b0iV{UaS(Znc_^!iO(nwXk%ByOO_+m4n{^Qs_&`eRI&yeh7`g#oiM* z?04loAL#GCwS|?m!{TaRFEl5E*HCyTBktAC-r#DnS6waiiffXz7Kp$~2fE4{p>#y~ z!2V&gHWwq+hOrdfoAu0k6_lNY(afpmFp}(uSt~<+AT0XmTtcrw&lAyoBSdU~tuax< ziX-3N49g`c_5PGbuI!aT_qH2VrXyYZC}AcR#rxBaJ-^jw`H}0NubO@pY-X%8k8%UW zgtR~RQ#!j2_=>JEG1}cRGiK(d>&unQne)J%u2X}}qQ*07b2U86?_};=)xtLn5~8l! zsyhhu7_tG;5_AWj9xgMu;GaeuYL4o%+K*DW+(0_R!2W3_X@Dw(Nl9~FqMH@dwZA4@ zd3&pWcg#F9Q{jzPI1*fCQZr{#u1GnPg?KlbXL5=pb6xLzM)4=&5a`DOURyB=+o$%# zn5XN0Kcp9|L%-qI6rG~i1pDJQeLit7H|(9-BUBbcv;;;8GLGnb`KF8Iy(kZhrmwDG zYT0aXT}F$~nl!Ve zZ8_j`O+;@PaYEj>6@N|I_v00?Hz86Y)`3wJNXM!~bUJ*ew1<|iAACl!*2p-%_xmX% z_j0o~ZS+{=?Er<=7bEa6;rqCx?qOD2x~Tm~tFdbTcs+^DF3cM3msw**-JS}Wv`H;) zkE_jtzy7uk$ckAsW=HIJq@$S_F>3q6iRcM%)_uUFH$s|N=~abLF>(8?*Kn8Z-ZwVW)v5EADLe%br1o*}2O3 zq}bwp(%sVaWUys}Uf8}_=ly#eLyxDP$nm6ANPXl1+o{bGmCJ$%tlV?g`8BP zQ{N{s?c$7_M4Z;KIH(7Z(ViW0?eRAGt>kFp__8K-9hDf=NB#y13O0D#g6nCyRFr#i zNDFN<dTxF7+T*yJvHjs;(0u9r*Joq%1(vHhmCr zW5(z=w^#Gfa?VYve=nj{IA!ku3vYPhcorm@MD)l|dPIW!OlLY1zds$aL@Zr2f>f(v zw5$8f+Kku)-I+lpHM45gO2&S1MDh-tlpy!plnK(Y@I=)8!;ppUwl!ug>-8KFHatS* znNG;)HrQkT5H@R;7z-{+%Os*t^^zt!ucv&B_6O`o`iKc!l;XmMJ&!Tk>w2bm#PDMx z?#c&_$rIagy}x6NoGQzRK6(W9rj_vQ+7-LB4A^(-#GMmDgoyustUuGIjUJ|y@-RE< zvZ;{FS4=8@OiBMZr+k)T`Pf|UQEcUcB9uQ87S##mE0l@lLt$Oby^b$`$dH2vAAHdM z$Fs)=-VGo!f)}PcK?2zzW5Q7(`ux8 z^ZJo~3+cV9Nv12$J;-ITS~KE6e7;?IiRiC~E7zF^Y^-%)0)v?wu3W=twYs#@hxbC= zvr>PoW2JBPsg;$@(j$AvQ!>-6=RUj(JwSPrXrHs-~c1Axa)m z-Zf@*4B~2{{a3<`xpX0QC=p!#my>xYIUObU8?C!A?;h~M;PT67#Ecm+Wg(|&9(-{N z@Fb3Roh)7c`;!}VzW=3@0-zKF%C9bB)&h!I?Z#iy7NqGmwdE({vM56>P z=ekfX*&CLrRGolZg7S|8yGD;xSm$wQn}=7N%)$4m`2JG^Ys#NrQ%aYAc-krePvM@E zix9=&R;ASpFl7-1kvM**(RKmqv?(5pNk^LqU#%OSS75hEZArGtFq<%v3r;3ihbMqI z=fu2|(&~5jFt7u&(8x`Y7Zb;4owNaiOarr}flAEJi$m>tVem!2XSKMTgMU6p;(X!pJD?V)`! z8$8Y1x4E0{Pd^?S-%%aXImN8KG2{<1K8KdUwBD76MXQ=9rxL`bU=MR%vXcu=r&sh*iH|kxOyF=WG%{os;%jJG6LvI60Yh6M zTk_Xz#NT+WpgRkyG=^ckkq9lDH&%A15i?3kTdLXcCjof6lor?%)PO5w*6zOq!po2R zDblfyk=_maib!D-A4ogNQ3<7%`Bhog7J5MGvFh5I4(du$$mJN!9)}WKt=FG)+YP z?1UDvmQYQU`hS!7k})fXIMb>)W~M2HGgFk3nKnqk-aav~GcQ#O4%4~ev~zD*@a=cC zB~EV*-!sRttJb0Y=MW>tqYuV2@z)>!O`S)cUSZbm)}OD1el7}nNx_oA8pdIaIdN1i zUwp1%Sx~nMtfOUlJQ|8qxneZ>1D(+}4DqnxrphkJ3l6Puh{~2GNPQw~f|Ncs=tTtf zU}&eYDytleK9-lRlf@#s|vI&QQLs=?5&NzIorG+#4xhg9t@otB{z^~=?t90rtn0(ex?-PAP! z(R&=3rew%`VHw(Q8p9~tXoKP;IEnTlGz&}kTcPg)%UJXeM%=o+E9KK9o}V8k(|cknBzcL{|qSwswiM^4f97~_}3$UPb~TiqvvKsW4i;3&hE_&R9lXTKK&-@DWtwf4r}GhNIbe|G*@-| z=x?fnQv1TEk;h>jInYfI!%pK&*s>XlsjL)hoX*hgI;SmyE*{2;W?=CZeRp6a=!~Wf zFpx;U7o$4h4~J<_Mc?DFw#1|Vh`R&sP{-r`?i4Lx^a7{Zpo^Qy(TGbNh8?fL`{fv2 zXUB2w#8VlHMQ0e%|3V*a=c?ErYn}Rxf(HKqUxU~0qpOQr^7REdpb8869UTq!*5W-S z?njh;EIKjn?(Ms&1H6`m=Y=YK3D~~tj5R=G0^UScWf7HUD)h@m@+4Sx$&PsR4b)S@!8XYs zTyyFX@H*1b`Z9eBHdrxtwpE07ww3w&tr%%gjz#r5qOj}4qouKpkVWetN$U$h?a{jZ zYkPhQZ)Cb0#Jre{-H|uW3(yt3w$BP(b0?@R=%Z1K@#xhtDMZtpVpFqFE}Nblz;2g~ zh>W4|NpC#*^r%f6P9$fES`47g6VH03d&ge&9O(m3DeFKzrf7pdt=!7QBDbzAgqk!-G43tThd={kIo^^hRxr%xb zDp2lbXo_EknX5q4S4{zpO7h~*s23r)t^%HFM#&6W)lBjF&pd#>1vB(PxDnBU7VV)? zGdUGzz@tAyS&Uw|0+tu2VO_d%Pr5oi6l```ryU^<*y9khY0*}o#$Ut1N63|bRw@94 zfNPrNOzG`#u(`_m=8+1|LOs1;nWw=`ZIj|kQnELaS@J3(H7tmVqJEutbp9x8v`XPv zOjA5McPzSDfFxoKXWsC%kGX;XXFAhDUy4?Q*l_{7*cL=z5bfsINYNB|GFrY7C;BYL zUKoqs7Q?v`toTO}`%W$YRF~3kDt|1qoKv#P$>I61xeZ2W)DuQE?hgGcViU8=)0CX@ z6lG^1QTEy437G5HFcFWOCRuMoOZYglGf+_W!QpJgoagm?R!gA0QETz|JX1bUFwAK( zDC}IA?xKH3?L5k-7a!L>&$q~1x_*iCAWtL8BvsCqOotQEkFZ-5gz3tRjhxyi;jNFn zyHAA9#aR1c>mm%bb6w}9cCPCL(H9uAZxO@3WfKnYCal3ssuTXKYtI4LjW+8xZ;N5m zvI$ih%iy<=v~k(SY+N?jH*KT^Hei=VsoqAgQq>9S!?NX!i&+v_)Z)r}4)!j4s|mhx zSfdPUTnScOkwSUNiz5<7^k5_bUS;zhi|oLfcJaJ|2mx?VE6SM-j=2ZgYFMy zi=N0GpcDaT#-0g3u21fwv-Te3sRt1mv=y2&(XxiJFn5U|QN9IMs0VMNIyG(U1fP||Fav=7l){v0L$a*|RWomaYE=Z)m{(z&c0q0&hINgCh2K`#PmLgW3CBqSlvw1}9 zT^OPi88z_v68pI5#<^Idb-&U2XAx7U6OjKrDug)gFGGKipmo@WbI5MA-=|N+n?~je zc(Md)tAx)^SQjb_*h2UvG#YPK=rrEEaN2lt!pD$>E%5U(AMjH|KRqu;VJ3gm_)H$~ zs4s9LJf(n)Q@+VOSQCKF!uZ}Xn@L{JZFM#e;`{|VyT5{bzIG_j@YvAvXY||8@ohQ2 z34T6M@2?HC``--+ErEan{iVL89p^s{AG-Eu-5GNEoEQ&II|CjqAW~20%ff?vjmD8u z{lx7vtam=wG`BaI({&*S)&P9V>%jLa2ik52bmFz22|bikn$!(<6)m_W?@;$9oE%O;gpGhR$~f`cpC3xf+H~cy0w-CIxqViNJG|2;lrhIJ zGl#5zs`{#|mLra)MOM=h0zW4Y3bN4F@Cfki-NmnOzIX7Q?Dea%eZS)0LYkP~S|#}2 zveo+T8LT3;-fUlS%0mYa)MtZ_XzSOFbt zssqyJ!7)v0-fJ|PkZM)g&<8%IT5gR+<~!Qt_ES!ddY5xxFBUa^ggE{Psb?>|XC{UE z&`jYRwGy`eOoi@Kg+u-OXr47Jda|67TlC~OJ^0(`d4*p0Gea6+@@oZa}%0Ifq?&5Hhn_=6$KadF5kCJG2cR0_rJn!?zEc0PNkYO5aY{-Oog5%HZ@7#gEl+)Nnq0rPJYQG<%Youk!q^-j)YYOjduF?e9s`)NTeYD0&$XWVgi|pTo z6Eoed;JZUQCp&$|^%q)6DeRuMjscO+ki3SEcInw``O0-ad3Qkja`5iKL1IW<@c7yw z!x7?#UR)3P?&T5cDQ4j7J`W4}cKt4R-B{(A?j|X!$zdMI$9WNT^eE0cZ>j4V892*~ zWRbV=%j@y{sLRvwBN{J3t;v6cuOw;1zZ`z^h~*OaG!}q8>hP7oHb{4KrZ)X!MD)W^>Iu*n7db|wv)`E2w99Ws?7j`24Nm(>{FGY=nSoWwg`QkGH&0&H z4{l%y!xjmJd;v6wA*;8dsmg1M;Q9{HRh@Bx5-+X)AohWaq2BEc62|JX_tS2O;BMp&ZrSnT|c%^z|aYcFEuqgkQO{`HwdGbqU6wUrAHx0F$DO^Ou!ESektJJ^>5+dbpifeWAJ|p z_;+0b|DA+nM8}x*+?4Saf;RT5g_tLL*4d2Mw|2DRx7vQ#1Y1U%AUXpJ25NJzGGL0Z?qm(g9XEM1)3EH=#$QFp=A1i`n_Pq8y%+& zs%4F@q4yw3e(T)g-zSb&on&YPJ+v&Zr^b8T$;9zWNQLnmNy!HNuKJSu)3qPQX=Jbn zkKu>$4Tu_`F|TQW{6;(}M=UkO4wL%NCj|6#L8`n0^RGEnkgUbjHs@VZRe8*ebM`FcFm zJ2@Kt{uq1%Ylbaj!_*fFs~_64D;{_>Z6VU+hX$GCYfFILc={#S^e{uxi}?1Nh)LTL z-`UJtt~jbT_v8M8tz$cjAc^3$X859u;EOJk@)3ph_izOwB?WClJUL6vv2mEr3w+rL zzI-0g-vRWup)vd;!igV87{fz4zGi5dFl;Xu*G!2q>)(wfPpCA?$r;!>%1X~GMA`{h z&tkU!0&K5~|B`yz7Ch0lL!KxHWGhDF2sHH@3{AcEaFHY{DtOz1BjdPTDn#89FgA!{aC5SxSnbRX5jkRD1*qSX&?BcG&;w?Y3l#LNd#Sv!XJHc zo-CKOa1geYq~G0$X}z0jSEQ8q+GWNzAMNoeugPrLI>m};PkWgyEUTbQtQo{omN~Iz zuEf0VJCReGH=WFQP@$g4(dZYkyFVv3Dpl{$Cz0>TF5Whf%Zx_5WAIuw!B{PEN$^&eKAa#WAzOR^%*#pulG@DyUIvW zDZbT6O|{*Fn+zPwO_0tlnU3WP@O~!~o({EBZC7`yj^!J4TkwEy=W@2ZOktZ&tX={s z{={;&=@vw)R2iwC8+RhmZ-|RGiXQaum-&zBuIxx($m8e5`Gd`*&cc@>}_%4DQr;+JOAf2c?cXpYrh9pN3(>TM<;N)LMC-IuW$;=^kl~xcu9yv}ST? z`t;xJ!3m7u3xGhQOj+!A)M`U*mx<%>I4!rTjcecwf!R$neG~jx!aDxXp&XS{leJxW|A=(JQXqiSeI7{-QSYd<+5?X# z&ZkO?DJ0~khDwbX1aPQlY46C4PYo)(V&vb^U=;Spi!?~(YB~nIwrF^Ro*w*LWRkH? z-ZCKAF+%?i#?i}Qc%W%9M_Jg!Fy>erbT+#&=66Qu$dMV(7_dGa9_I<;BMJX>Fu2-< zQ3}!(CA^2#)oP>*MEX1H^KgM#*`bR9qZKeh*%er!d@^5fucj*zUij6?Rr6e!T1(x= z%A_lrl}@dms5sUj8UZxDYqgtFH*v4{eDy8HD!@tG)pf=?cdxk1klPPM8jR8nC@mVL z#S5hyE|eBmTn`UJ{6?HWlBp~wk^;o6F(RZBVpuXDErmzFsuPh*ZLVTPfyVCh_ME-R zLcL&IwlWfZmf8V|aT*h+>3Hptp{hKyBHe#CxX9!BT?F@y6pJ!qvEfr7K0(W1sYL@; zhVs;~pb{J6jq&#)6()Uf@L1N+Xk%{;{FF6jsoc=OOxBD^1%!uunIO|t`o4HG*2K$x zi_Y}Remk9(%YM6@N$9j4?A)-m(^H$Il$S@M8-_Z1U7*}S_`FTV4n85w!KcL%p6;40 z*Z0D*Z8059&I@i&fIT?ae89pSVW5kp!LIyyH zF{k7EtP6YEf8dNgt7L%Y&I4MqR{Gd_B89X>6G~W>@jU9+VBo9b#66J`+@(-qcxk?q zXoNL_Xx7oBq(F8cVrW2RYjNhJ*uSU&F~}{?HM0hn&QlnXUnWuSGRr@kc-r=O2P^>Q&@pj z&ov*yoiwdTeS$A<;l12P;RIH7_}*Q`3^-WWMJnRa{Det00TZ*xKT4&SSoE1h5FTR} zWJjixY)^;mHZC<(%JU#;B^KYhC{5nc^-J0MNBc{pbp0tSnY?{8I%UX$C`hMoI;0|A z%%`*UGdgcWkuEx-wqC?;*_9vkPTd}lJ_AYmUt!}rjoJ=R-QK=${MY!rOx~WVy#pQj zB7b>&rleRuJ|AkCIZbKBvmSScb58IVp*S~=ndsfTGE5%Yz1 zv1xDU`zJD0f;+(&@t^Q>eU2!yQxUUx5>n}>@T7eLFN7MgLqfHRzm8;zzlu@Qta{(l;fQ;wU!JAFLqM2z*eD2Tr`F}Ezl=w}8=tzJ(D`IKpf4ka-WzI8a-k0I zKr`hq9o`1ya6*Sy#LP)vlOdx#1lt}g$(Tcrcj|V(?-W*^XPLCjq_QH@&WNPF!?zup zQvYB-IWXAI@GkJal$lDsJRA)seEU8`?DTN-=|nwJc}V>tAsOF-Zu(nwyHT4Ft3R)2 z$bKq|xV;9nZt{6%CXF(6qdjpI*Zb_af9f?d|co&WuE_UCCYea~2Dsi2-yMjJe8^I^;e zV^!;A`u;4!Z9gBOz8h}fg|WI_sM~_P^s)N?gh%gPSnvJTIY-9F){n9EGyH-plroGL z?(*A3v9Kt!5XrZIeCWcewc6ELx**rLxg@N`*&vfHl zQ|b8#Q#zRR=)&L2m3F~C;U`_I2AUtlJ~FV1wr@FV5`)WV-SjXKCCoT`RW9J>D%@3N z3ZgZZbzv>iS|#A`vFKk%Gdig(pQdFXYJ)ZQe+c+ov%iw^n%ZHit> z{dbW`yDH?#+i?bX_#;q$aM`gjeTm*Z#Niw}d?tzH+ATHKZc;zFWbI;&UeuYW4lqKw z^98-+#iqMIx504=vej-D8;gwM!|c!s10hjJB+JhijKl1p!y z_$C6{&GIy@G(l%Xpfh64I7%fC(Q7quO6q3Jwlr-zut{oX@Or}cMSVrF=+byDtl*_( z4p<6lE8f0wtj75ywH3_1Y(9cbm@n#`vRag+_bVP{;VsHgdH7-r%^jRk6u1(+z~h&Z8z=+^!EMse6uT2nXb@L7UBD=@)gQA@GW_{ z_shNh$n_ z3ZlKs@+sYqz8Hng75hr5G-YfI29i6z=MkpOl_&0PYI8Brmy^Zo7&|)d<1LL$<3R-NA z=som41RA0BYm9n_^ggm}R$xz_L#EMN z#j#9j0xS@N;W9)O?Sxc7Pa2f1{RDdxVLw5tjfW*z*xBsRT(H#ZeLEpXjU4$K`1(Qp z8bTwz59-$uXCSyyK(rIJl1ml z7x!vHPt}{$y-wWuQ9H*;Z=S%Jz9RA;*|Hr}T!-jAf-A9bUvUUpd|4&9EHJNO0=OeK zD==kCb%m1qG1BbLGc4IsU9O~lT(u{3zW?qLWhrjC<#QcM@VwWZsGJ(kQyHAuVjq+e zVL7rQ64ZfcSdoFp%U6ZB!&`-jrcNKVxNH>MUN`U5^y; z`EBmOjYw65c<>M=Of_T3gOFg|@Y!a=TUzbA75*(&vzicdLHnep!3A@$e7)~6lG4Env>9A_| z<6Qi7`C6IY7_{`9c;^bx;{-#xC;RDr#jqcQ`}91a>iQHV^NfwyK|}PWlvIVAk*=6$ zWPmc5VxqFeefktPubPT2Do4{^+!}}P7Md1rzP=_rW91urAi43vuEm(8MT8Q|Qh`Ga} z4T1)qgs-y&#^(FGF1CX^W6UUbpf@Y#hK`0Z4X-Xcb6i5U4CD~A))R+a z1$Qid12^r|!~5{y>xZEw9L!>At#Llo$*{aB*rmg(~ zxBZrAjF~A)Tj{`U>n5#t{}eyHb^YJnifGab5Q#m0D`JZF;3c;rrf5yWm*0w*h`xn* z%}Z}Zq&?i&)hefVw8MkmMcQPX62dvi&Cv0t$yAo0{=+p1(+(!YIOckZlp7wZ;|6kA z?j_!B+HT%KOIG9F_EN9w|S!8BkXlr?b`iZp#ZIH|Uep+~<1?Old!mDrUn5N~!R>C@Pq7R;=RDx4| zUYzH1-y3?)cL--Kaemae`Dlu9gK#bhdaO7#Nw&Rznh~mHQ)jLGAlCTCgw3$VYdueP zkz(8%wbi=STFP%Vci6TvTgg-G*7r}FTxqgURERyv0{PU}YwxghUdTg4OpcRaKcu;L zLgK->f#{Oa6!^(4+7NF0j8&x?H$t-7Cqrgg^rcDbj1hU=n!Tr_udeQ%^)GLGnsnga z7ZI`)^^#Y4cSAbH&N9@ga*%LSv;{G&Sx}mFAct|`q*%e|Z}w#)XEd4@dupsq#aI~{ zu|+V-dt1Ou31j7s_FHg*IIy?HtkRp9U6#l2OK+jKD$?8WXtZu$jGewhJwa;iq8o{@I^ZwG8xZ6cQ1qm^sf z=~OPSTuECbI0rV~YHMunSpCBz%vv&&(OAoYK^N1oj9KeI^c=G?f$TH+);Yoy()dXhw>(kp16Ezh5%+Q*>xLrLN1G5d$VpOh< z-JOca7g&i)86RIrR?nB|O_7g5HnB5gi@lWyKx*v9UP{O57=xzf_ro$up6 z7~mW!njMi2l;XPD#Lz9J*6DAm?T|@shDDvsSZAE9p_(M_Ml2*d60k!MX+@ZxwUKbf zCam`8S?yM-IDBdiu{e(2TxtU++}KTKoKVJVJb@bD3sW2P2Hg^*H34shv`K7z0d+fY zTcIa#gWTE3{1PWIMmp4bz{oOS%r{`%2|jcmU{o3~5Mc#dwiUM&u&~rq98RslUCb+9 zz^;*m#i;95zj_0t)-1NO@koT)z$um&al0SAqCMX{yk4ewjbl0stvnpQfTeni`XiP5 zzv6m+xU=mg+(x-r^#n$u?FlPjQK(CXapwr=--5@prXTz8&80ch8T2b_`i*;Nd-aGH zrmaY8?!<~bF?=Pw1v~SHBJsM8wz{^KYIFFuQp@yT8FqV7y|pw2El?~~oL)(`;T)#^ z$=geL%#jwR*8EKMJD58%LpNp&H=*O?8X`_$`~14KKlbc*jgtRul=Q3)F8d;4+)_wv zp{fP8e0rNXy}9+HMTi|+HhzC=ik7WISPlQ}vin<;x+{%$yvvX$>h%9Z*qgvNRi^F3 z&pFvn(zH!kTWHHk3ngWP$$)Q#<0woq7El5NXqh3RJ5Ru z!2xYS#-@lvMaJSdj>|azgk@e((Srg3>T=4K(=GX~`=p@G_x``Hzh9Q-ob&9@{oLzy zldnd|?_Kh;--(D@PJIC$P%VK|fr6k36pe7;p6>^} zZz6rC1zI`cS{&do==qiXrQk1IENxq7yS>$}YKU|&z*njSbYTig)P!OqY>Q!zRBT zzSu$wXcSI84!#+MBKevwz&Uxg?+DK9|Dm&ZZaWa68>B`(`lTAQH7{X7PpVP!_Zlk# z8t${8FM|2m9a^r3);!IAna=yX4;pIO2B})-D9Uwyf=a)wZ)T-syV-N}v}p*&fqeph z5uCdsS~2xGwBjy2{jeA39*}%tt(pQq<0-=;#wld~vbGWRQ`ga)F&CmoH-dw}2~WHV zl|8TT+n5(TK5%P%r}Xx3gpHf{OjjjZDDTijyvE8QL``Ew;gyZoB)nWQt0R25S%}yb zz|oq?5)3;X4=FsS?Y$V$;4k}Sc#ikPKkN7G@pyaUV+Y*$tPFb^osb&%!z3+`TyQdm zhywJS?DhYEMDqpwKOj$>D&VP-`o8L!1t ze~pT@C>vUh7q#Kgq?9u5kh+s8${s?Ykj)uDR~SB661C5`waIt| zO8P_8Q|k`D2;LW`8)T^)qE!F%NLp(McThHwwP=+KWnOXcMWA-b=7|SGwbgiH^57GN z`V&kUqvS^vFQi(3B%BLbF>_l@E+MtZJroC7b1qY?2QJ5Ljt;5(qJd??>#!PeIXCgy ztuC)!t%p|0BK`sh86GygmFB%O7qh;esmU}}n0A&cm?sSbAO2vE3ZDOVW^Wp(n$YN~ zWl!NQ?%~G0TB+#rnm3xUk2v=GD0%PmHnIny#fN}M+T^LN?y$9*Tas~a5A|2XY3@dD zlVY5}E+(|P)ek^XTzK8)14gNX+Gp5?mV{7)lNGwp8VZKBFeP8Hp_T&Fa!d5{$~lry z`b%H{?9gsR-U-MmO`m8+yd1C!SgfpodV=;kKxKSpbq->%lJQ??U!QytQV27TJ5(%@ z1eWQfTIZf0TTQj1wbk>HS;h{dCs`PYtDWzsmgsgy{eQIVG&e5QCbRk(_&Fn@o`O5Y z+LjKcJgJ!Uo}SZm?J{O=IQm*p4ez-O3U+Y>yv4Y2 ztJ+3LY1r+MsUi6r6`&y?caT_Z(7)joyWK2sW0`Xq85MVajPOmT);@1 zM2gkO*zh8>bXgn73x;y|T!%R+h36gFZDDi4f76+@HTB` zTdYkxv?8C5g^cq+U~6EnR>=spJwA6tRR~g) zFn#1Rwz|y{hiD%j{_}!Z*f#mZM~DX|Bp@y3Y+fbdd(;BkEhjob5h@w>31;{==mnof z`(lT7v%Ew5uQv1gwb~*#uQ1jO{xi3AhX!Bo_1G=)5X^F=#>P#Nu8`8VTMC9CM}b)_ z*Zg6d>)z;wo&UXahjxGU4(&nDi}0Glns?{C#<}i~Jgm_8jDg?7UFcb<f_y>Q(1*4pn3&!z*v*jmxu!LPTn>kp zIVGZECiTpKQKFnWh+o9HOFGo=2FRaV-4&l}lvcKt)*)u@@(F3d>M(9Iw9ypDDTkEo`{Ly$DFmKLs$UH?hr+<0d4n!n@YrY4;+e(2t(Rt zC9+X0g|Fr((2-N*g&k5HadU(-Ox#$xhWMIy67iRS8%$d8R1D>ZSj!sX{(#&9FKU;> zz%|~bb?G*rVG=8#zMFP%PpvsMiCGHRQ{>-SsK>LL)L$bX)i8%sPQ-sZK=vNuaE(rz z`sg5{KWW2Jyi*djFTJ>n~ru>wmf0imSvG+SHo|uM#Ji!V~8Z;b1~80keMn zy20yIFXGvMf+)@g5ycbnZ(%6~qWI$}MDY+1#ck@Ry`di=il=HD`lH$w=;-A&d4#OvE`iXAo^$GNfh=s4xZ>36(D`j=Zwh;0?sIM6xNLUT zfZaYU6p?bkGn9lw-j*;2`aR4M!YOwrc*!`B+n^h^)BQSLIe*d{&mr=|i#MBZ31P5*Kw=rkXvh7ar(bh!C9-x}5SqX>!nVzVG1Yd;p zYoc9@Y=L9v@zeRp-1F7d`(ui?4H`r~L_5-i^WPXUKJ{|Y8N|$&P-zeM(<=OJ|K8YU zFRemBeo-64NVfuvFL!$^t+_P3+0US}H$`O2DA+SvVwKV>u}(`5q*C%iXz^kUtf6r2 zOo9k5&22}3MX zToMy8hHf^jRo8BLi*M9B|7g~1Ju6H*MKPT(I>8vee@3TgJIPc-UJkX?rA{MQL;(UZ{l4U-&_ zhVwE4o?1ixzqUOo>m%%eu<9I3%JA!ES1Du?3%R+ywar=n0Vf^K{SPz-(h7_G-z3#u?!) zjcX8Nw!YkSgzhlo6aZExq)dl{AzvPc6PlHfZkBhLTKVeC3gP%Jr;fE(=0-3w^G-Eg5pJ?PjLL%rNLI!`8(!)2O zH4JSnF9N<&7*?{6fzsK7IQydL`?1XzS9ZsOhuKXFvVFtHd?M{6Sts5xS_}pOm zJ<%QQ=~cU7BM&yZ;U>h1V|ryxvv84hjZB#iw#ZY%xT)KHn*VD`AXifjj7M zU4QtRypoD1fwXbW{!k7dET4Jqz&zkqya+FC-ZJ~pGdqo4X5pH=-H5wd6r z_gD3K_Lz1Oe{x;WS>ejD=B+9(Ij~pD7e$Sqh~M>3^r#~xr8+{m=D_c;TF~>wh?6#m zFTwhP{a?ZImbaTRK9BZ_;G+r4p4y;U~hSz_I~u=6<`l0$gZAETzpi zsq_0e$(6&&uxmk{dQ`{G&~RQ7XXn@(tc0&VekX2`a+1Km1U8&p_4)+)D3SH%k_HPD zrkbex+5Sp>?GFJZC;|TTKExdm4?vWFtSh?4j6w6HeykByC7&DdaUMQE9FPvIEh!zA zr68rjO2pTrM^4xk;0?dz3wY<0Kx=chUuZ2w3Gg!xQ)U{`J#rGVTfxREN-a2BCrP|A zauVA=axCfClkp+Iz^3Q=ac;g1?(`xlaFqhfRp$&qitWF)(&*8nDrSAD z9d4@1GXvenz>P$N#SyG-tBxK)>ldp>xZ1N79E5Z5%6;&#rYpbIe_e(wR+JqpEs}b! zdeFz=4bOXc1}DzS_ccN8C2CYQQmV>BEqu-#TC771DK) zPe6Sz@K{c`tZ{ugJ7SXx^AxdXt?$1@D^a)gy$3R_8HZoRZx>Zu$irF9$l2sDS#dpHWvXD=+?O$*smR&{g# z*;`4=eHL2o^&Ym7t6=M|US{g%OvdOb;Kyw0qrSlBx69<{%oQ;!Z2dhQzNPI;9To2a zvqtchtdZM|>hKiKO!L%-gbD_~Q7*F{6I``Z5z2)EiTU)9iO8{^3NBw#|f!0Efu%P#!g7ycHWjoZB#^EHs$@tk^)hHnT zni&;(CvPhr8UHAU-?TmJs+h<&8Po=?FMdz&s!CY#kbz5!AJcJuo~(2)OT=&2N4%kO z?)i%g-6grIv0tu)Ewe<{PG`y5nJlwY*1EC`PHth!L&=ZKz3yqEA2}w5D}LeXrjAff zlXeP$l^KzcKT}8b7h%)LRln@>`X7Xsir2@9iq8cfd)hCT;+;&+Ch2DqMP`#M0(ve8uh-)t zY<>6a;ob#a$5k(u^ZSh=(E0aXcgwy0yS;pu2{|?9`3z!29w|1-oEY&n!8+Nr_)g>& zxD)uTjuMXtq2-`#W@$1qz|8bpKq;Ae0r6tta$_Wg@O~jmcF6cHiyq1D*qH>JsY!aZ zTQrNXahay%fL}7JnG+`t9|c|VR|DzcT0b--PWPzIez{$!NVA>clQg1l?$eqQke zN4h2h&mgf^T&jd?kbzJ;uo>?c4s5Pt#b+bO@cwPY(MLtR&*1g4IjDo&j`ethA$$y28mQxeE4GFb_9jfT(jT~27je6y}AwYqZkdxwwC z98G`N7xG+uPfk8z$Y8=1nTyFlEL4`Nz9PT zg*>|~IDZB8d;Rw|{rA+JFX_L$8q3(l`8k}BYq1l{2ETC*4*phd{Kn(A)HefuYZTT( zRw~m!O^yJxcV@>vyjvWknT53lo;n+Plkq2#8Q2>dPy01kBWkw@+|+eatv7X_v-g>v)11-u*{8_E zqu$eJffw^u<%jE8y=RF-bhxFe+{SifFa@8-9RO~g%sYv5v=YsRE|9+0;#3Lt={fE} z#bK3a{&4D^e_W32tHeFm0h676=hfSKQrEZrZuSZiOY5a`p;BhK*MJd{C+@HGpor`zB&ZaCoU+_9b(;Lu(SK#rudbZ*b zykgDkXFqfF#2*|WoRyW8*u$lqZ+Ru>TTxZ!AwNnJcx1WSE=9C0j;dT!G@`XQKAd4S zTJ33(YArN|R$H;J%Su++y%h5b z?zyRl&II2CogjLzWu zdTtE>MTAiOQNv4>QapZ@9ybt=ou4iGg^pzWuKvwSJxgK1t9TDG`OCeba?5^k)V}r! z@cm52AAuBkEV|Hps`A5JHl9w;g`S-_&2N<6l5QaWOGaK5jP+x^Wq$dwj0#5|cL$w& z%W^Abo{hfW^*;CMt+uX}jCBQA_fpoiLjU#|XyrZ9%xn&xVwLFIyTJM$5~2F;LKJdZ z#MC{M)p)@>GGr$p2?2OUKHmeLQt9~^d6&^MEjnjIT)W2u+0o~r@x}SPN}mm*w5M_; zWmCD-_ke)DuhS*Zcx=}km)`f!`aJphKnvm~6+}z?3D=mzE_i3hVq3ZqIpQ)MR@$$F zmnF?j{_!o{Zv8ikZlhm-D*V=#?ht-69%f=_k|DBrk{M^p2eGO8SyB7wUt?3f)&i}V zJFps>1c$UI_VD8d=+BVnEc)>3|GDag_BG|_4j0eYHD1sFWgx;J9$zp}37o`u{0Y!_ zYd4k&#=L4KkJvucy7I|NaGf@HNx)wV|2S~VU1h_BY1am?M0^m1I;~MoZ0m*e(j9O8W#%qtMUVgD&weaT>>yTPGwc)%6R`H zw7L|@5|95kP74=4+jL2se92N;RvYm8i4NG*X9nJDr#toz-f?x<<)7rs4#RH+vkRRr z{0#3;0536WqcaE;CT_PSUUJT=@rPgf1&;iwSy33bVVV2ogNM?xfqka<3TReVCzu0j@SoFr@uEBajL6?rBKy+;&UilYl?+^EX z-&fM%#f}+vnA&zq$CN|Vz+}qZfy|+xzA#>5k5_9(cs)=V!mO=>d+Cz?8}S%>)X74m zd-8E`mI4rE>AqxKeJg}^`~~ek7!^UsT-sT*G=-8Ij~_jKa)oH=>ayG2Tqo&MD&zGe%Om#US5BwoFO+A zaKF2dM;asc=Sntik|g;pppHOUo*)4dy&m?57Y$E>mqyhSJBfCvvd4YD) zNU%IeF6y_5htO-tzro|}vuA!9ElKr@m3Sz|k73`GgVz<*SYj&jT$OmB(SE76$~{$p z{P_WV@gKzZkNk)f@pE0}S;AD{xJ5Yd^dtk2e2J{H{^6ixnMP>RWKVvkWYT5QUsO(z zvP+Tkl1&HJqwxmaBdRPL{1^)MTP$l$;Ok*QY~prM%LK1Q*UaZZT~ueSM?;iF(HLFST!)SkW|TBTy_i0{IJQ z2|m8!Q2RzL4SC*~niK?mEKeo^kGcI>ee6M+o43o)NAU!(6>O}yqPU{00z4;ju9Z|+ zPS9#~gI-faDcSSVKz~a2dc1dru#kiqj8^@X4|PDG&GYX_edrbM$|v@((ovi;wc)lVVm?DQ3kT~*F8nimV~HbKxAPA$L~3SB{# zTTlmix|5}6d)M3uOQ6^@HWumw{|MxS zUv5~0vi@q=f6qhkWO`x{=kO6ZWhqR?{{}2yoYn!F;ZXTW@CR|wGOSmW(CG(<`pyq! z9Lk=6HJ-Zj++jD?l?ytR(Le|GA%B1?K!^ptT?TMtx#K?f!=HVUFNrZ?DaQQw1K1@Z49 z=8m+Z8NetCX$D_NGl6dr)HixKf9*Ll_b_7pIINV^Q{U)$UAShmx<)f2VuxE+mCHS9 z(k<&Gsb@%3vjtKJtwN0J+Ap=;E7-Vf*sd6Bg9l3~%D!>HqStNhHgcs>W4lFt0RSNv8x{EB+v}t2-+Vq-xvd&mRA<>s0SLi-t zg)P!nFg=fIda&2*Gb>j@8$JzDmPITXzb9#P1|ipwT&Ar{kZqab^|sp!C>QmM(BX`a z@Ebk;Aq6&{oLlJ=3T}osKi^y&G!+Kq+TcF9HOK`{2g?F%oLz)%#AJq-3u*XoN*2Se z@*sAGTIc{ZOeEn+2NY4D^8 z@sg>KIGAL7IQ9l1#wTV_BuItAZ_g;OASxjluS5xD$hVeT@GJk5{&#ZCT~BhfS7&DP z&BG9pJIiN-&Z2e|+PbPN&>9TLt&M>|3HI@Kz5GT~p{3vh`4JzX>#=bedOw_qgh<9e zPZUcJVuyS6ey{?wn5J$-PX?-}FY~>k^b{iNo=%WQj@$KSl=g0-f$;UsQt4p(sO1{` z2d)NsSZ?7yoJaxQ(Dg3A9Bmt3!29O=%YC+ja{1-P^1#c%dPo}_Fj0Yt)*O&)R_&9I zq1A{w2}9!ZF{j=-j#``=lhuibyq{IMr2S~e+ys!E(2jS}jsz?t6!Al26~I^#&3d{w zvXkE~V9ZDg`V-ppsy=29_|0`YE|1wPwCm5BLpJM?%tnkGjnmzFi>Tgx@bERtPxz2w z5TiwN?$o6!X>hD+e>7Hx!C9a0-3j!SA$iI8zyK`gb?#ysD=*r2Zf-+ch9o2Ulu+v! zu%Q-ubH(+VSqAmzS=YPFo?V97=TsM35c2;O7?VWXDQXs(%X=)696Yw#JxXq8;{rRk zNzT>K(H<7iXCAA|;xsfPJADGmgveXq9B>wH+8yM_Ipo#SSF5LcTf30IqF3&a+bq&*7c4Bg*Qq;s$qvw~@40sWm`ZSYCrnfSldf;UfljxKXT>k&Ua4Q)_x? z4bqHbr#J4C1=<&8ih)kXs|QlMxuvnTQK&#n>`g?=Q~$LDeiNAQosRjw4gE4q zUHY0V*)?O$%+-HA_je;JoEdIdeSiM9qyD<;*ZFxB8~VSZ50C|`5maTkR2?LGJ*B5XBJG~rm9gI~;_PIT7!&t} z82dkX9mR`#3t2!r7;~2r>M92dH>Z9D{^|D-Th}V^Eu^U+&c&|t3thPKXcSg?c5GF! z{D#Au8>2fbXZ{9O?s@u??V58%pv zp=Nck+;YSlG_>6&-9;SATuwc@dkk6P8&JT>XraSnB1x*PYgL4FlJkmwbnQxHele0Xj=r(9v+I)9{Du5vW zQ|Q}&AS>?*Zj8?DLvNWTbeXxVK2ta7(IWoLJ}Y!<-0(hgR?l2q9p#3se`9nkG*&14 zke}pR{a?oJf_!38XAQv0huOIc^EgU8UGb%voRtf%E*CwAwduy z4_vbhDbt>apM>|SCGv<&`M@mdy?9cHA_l%KC2b9L_Qa?ECHdujX9Q+KhC_2EWqaRy zDj5*{6W|p>5vNQf-FFJbhE5mKf0Onbx(QhKvwCB(_s?Nvf<`D z`Q_mlC$grw=iM_<9@e>#_&+gY8-2&#j5U~}Qy6xcB z1~LDMTkyTeg_-*+hLRpN-h{IA5bh_f$}IyyFdKX(zZdK_CjYG7#O1DDvJ>l`48CE zsh$^ch50+p=0kLS6|&ZNgBq}B$TmMIJYL?SnVTMzDYlY{ub-q}$rpyAxZ(2!DoSNM zhRmq8Nj6<8WzzqwR;$HWXi&_B9NB&gv4ek?@{9ui-&1^?@|uywC0SY$q4l%o5*{$V zXMukl4j=Z1Fe4U<$7?n?+ccA#?vp47EA)NtNv8eou||b0mBCS$?sea=1s;7&H9?+7 zHbHP%KhyWU`)*{niEy}s&V9`5ToWSo+(C=_^T8U;+eFw=nYypv`~n*@O*4#f$_s)E z#wpfM^cr?z*$lsLvuwQL$btEFY{I`HVjb3T9dc6IhPo!{xFmi= zSIk0A%-=?axRzjl40A0)cFu*75w2fHUpr>M;vv{8ptk|G@dLnT6o9twscXYtL+Om- zz@hQQJ2bv{w=)Ir87xO>^EZ{hgeN)63x~{CY>Lu;D)D*Y_e$Ci+R=aRp?yXBNzmiT zZOj{-0M4u@{(x2=6Rwb>;~U?=&TwCm+7GW{KgjJePGmYWX7&p^7kWwKGYRA0$*gx* z&HOr=sI+X0oaW(6WN~<44bKJ7pER!3^4;FxRE+4e{WNmZl{b}P$_>hN<)m`9GUa?6iy*rxZ#{Ih?$VX*#CJfui&OvA2+nVQq1OiK zL#W_amCyV`6cOOX&qv+^2U@n)r?dn7oWkr;(EafSl^G6Jp4<=WskP;3~irxm%duY_cj zh$j*_&0P~CpiIyo*}DG9_%0@J1(4mv`E{_Gh(2#Yqq}i8{Jct~v8xP)6Bli(|32WV z--#E9BsryEa=;L(kPmo#f!FTHu+Qls4I&@RngN% zCq~^_L!<84q2*&XQzQ6!-`6qo6~_*H2eNo0eJ)sTGCw)D%BS{)dQ$ zz!~Oty%*ud1_NtKt$e1=?<>PvzyGo}Elpjg{pw`K6>ZVfQ#%s@Jk=VZyJ~-Q*W;SF z%F?7XvL>JR)ypH$fU&p9h=OUVL=XFWy#Y(JJIsrg=c&djeWr{1u06RMbCHj^$UOkr ze-Uf;V$8N;Trg^~wX2b*6|m6DHrcaH&0Dl#plR7{Uj@W^8n?gqnjw)7Kgo${g{3hmv7_fXGz&x#z5ud5os{IeKuBZc%}Mql(!M0(qmr;yc|ACcLp5}o zrp`Xn23~spFF!p0BgCjn&rd-vaubdy-!|l~O6K6}l7En4qXerYAA$$`c!pLAoBfweY`_0ECrD(y?{)E|xA6D;N?hjOQ6 z-pJI4$dlb?)AjgwYeldq24^X2I`3^Ohs|Xzb9F_DC;Dd2t&;2N425bUYo$@T_TD=X z!$RB`KH=r(mYQb4PZx^sZ=zXkMaVZp<6VUDA`Rgs9;rdc1_Z@~zXo|?(mlg^;rF|Y zZ0H>PgGd9|2o9@Wzh;J=+K3bDLj0kGyCda&OFFko{IYV;uI~4`kRO;Ks)IXX1uANX z##rvy?6BKq1(gyoj>zrNFJo=xM2CuB#z^uc`bl!b)}G2RQO`P@VVz94hxGoC*x_0H zacm%71zf_k$Pd@;*;F=irZhUjLzOHb{=48C#$z>(1Vw~bf6R+KeG~HvB+|Gi<9XoA zUWJ~~&Xb)W86T~=f}VPBP^qVKO0cs;->Jrsj8}LfVsF4R;y=GP;CC0BqB5iv;=mJe zXTpuPPU}IYV(2gZUgv5zED7w0Ac+mHAR68%mMo$YeF3G>SKl2FSC`a>R<%J>2U|^# z-@9+HJi}mlHzU^Wmp|GSKkor{UBo@vxZgc4HO9WZ?i+WsLl>zI>$x*be0^>~N-~50 zr7HGh!@mLcN4zdkqTAtjKf)h^40)Zjj3Il{F4%AWz$St z)Jju669L+jy_f+lb``S3q@+zh^Z;t~40sMV#^YH?(5FfMDEpzVZe(xyp|024$3kwr z5T83xiik!kF&ZoJUWm`qrRR@hDOvFq%@Zb>^HSetW)p=9Yt*|AwBXONwqPrC2^2ve zhQ<8UnySkZck5soen`@no)a!bt4jx}VPgh&yfQTw9`6TXk{ZV<Kk~*oIAb(_KWw;(<+QFnu>~iU(4ebs&6Oa|NWL@ zS6O2g>^?_&}{LN%3TyV!N-rPF(BtvwRULV^__*RZx-=A~H;B1VG-t2*`x+=HK z=O&9Kyb!RjC$Z@KHyt<`&LZz)Wd}EIt1k&e92@qAh&I~PGvG0Nd*L(YKH-Dp|8ufO z4VGwH^yZ1#umXa1dZ2+IggBwYh8@5rQ>nhaM=Kq>0dH}*!#6t3vISTg=l5;{EZhnD^bUvv_(p5j4$^IosY@Nkx>RxxD9&HK)kPaS(@ zmSptOefv`O>0@$Z4pJE%=U?R}B=H2b{wG{?NU%1%VC&eyKS zTvR51RpYNJsT0u?)ZcIv^O~}PVVuh6s>zafH311_-ql#vyP6R%J6?_~@9Y{MR*nWA z2QlRH_#Uie-c(x*T1wMY61%UGElr(+WN%O2=c$C(rF`Gb_f-iPz2)YEn!u5@`k*#8 zivt4MDtII2>fajiP0X@}IX%|x=!Yh^-gH z=zk3dU)FU=()SzD^RYEn{J%0u{!H|LZjBB9FH4Tcy<$xozSkwM!njz~FLW>WFSYlY zxbfs?;K1F&;#2VX{SKM~tNK9FwG>{TQ3JR#-U;+M$M_mUvw(7pHGFe5KJ7KGrHHA( zdc#%E6^mfoN^pp0#n|Ht?K!62tNp5m;kPNi)Gx_+Ln0Z!BQZp8Lwj;~l{If4MhlvY zm(sC+sAcPuX;siOAp&BqsyK0#Cp~A@NkK0YIB5w(CHbLKs@%;B)5FeMrX2H@ zjY1BcPqPRE`t$U#Si=;>qkBJ`1e(6yPRA}0o?^>1dS9!}sS?G2#*7!@3wv*=5g3qCv9~^w?+Uwd+WoG_4PS>WMQt5%iUquI0U_-?ypQ@J_H?SkKS5;`4NJ~+1p zSthM_3bV|WmMYWD=3A&d`uodUiH~ka(cpVUd1&gT?aBjspm`R&IwEFdfctT*mKnL- zwY`y?0%WnxQE%%-6s0b27$t`@NByvOTwo+DF`>uYDs&5raiTz*YLj7O!Wpx%1GP5# zX4OA-0Qj6wAW)bdb{bNrdqgo&Up8`l5)?nGi#BEvdQPNLc_&3PF0psA! z!S>!2$Od=%O|lbckIksb$eI_^Cx=gl>-CS_hLWsdH|iRxmkd04zJ4*yZU224@blyR z&q^HZ=v?=li^R4RJ8yftJ0tUV&~Vw+9_zR(X5IhrqqEjO{YLb#5opTrRBc@S*z=ZO z9UAGn`9NA@L;0)&jg2FrDF~F8M^|Vpv{mESl`{0J(5VIB^KSIfO7W+z| ztLK8w^K2d`(|SmY)CB#&_@Fq7AT;F2a_loJSILI)`h8d*oLnk>d6N86ReAcoVa`iX*nqL*0n` za4pdF*F)wHpFe7T-h7b_agB`_KhJQD2F}W7(b38XU>;|?{uLdgh=d)Ecee%TJD+}! zZ_LsY6QMc2h<6!T9dXU9;+>hl1p-5-=?vc8rWoFt={WsjJVtj~iVqnkkEa&4y@@u} z1EsjiGUkwZa^8VJyKgXteI&5WKT>!R=bdOQz`f!k>WwJp63Q@ir{YuXjm5&+JU>bZFtW({TQ2&%fpLBw4SFV$>bc*u(XC}Svtrqb-- zpSxFa20t`CfEpK|Mw@=chn(#99C%+JnL7`0lm8={_M;Lj6sJ<8jDU9njalU((`4y@ z{YTf|1daIh7}Qyf8c>TZG(ovqp^?23WtBwJ^?RN8MK%^6q|sH7>7H2%G2+exCHm}- zc4bF0_4_b8=UMd@w3qHg+u67IC>Lc5ZHDKNW3Kr^JRjcpLS%%Jp+9jZW*O&DgmD?l zrLwG%JQs2{iKdM>ixOxxN)%D28(E@g{o4jW!=s_cM9^^HSxU=Kj@bp|xG3qV84I@8 z;=KK)Zwz>dpPot2_Wt-xGO2$|Tso7oRUt`d679gXZO`Kz8~ux;+T$VkGfy-{lv<#s z_K>u?UvUMkPO?C`cktUz-$A_H;XtrGlSqkF>(mT+Wr-d1zXh!?gW2 zDSu-+d`O-}ob3q5C}12Jj>Hiqli38DdejC^v($Hmf-;GE=A?IzFkE`#iDX_g3%WMCR_xY$jZ_5x@vYZ|2mG`$~Pj zaA$i1=7cg|iw)N)HrORs}^SK z*Z5T)qpZUFhmZ<5zX|yhsg?k=seK{hOg1(x-8^HSrx(sO9X1pK?e;5js*7k=t?W`KJroDUJ z<_pVaCT}!F?#SK|EUswGTiXu0I<7e9P3;C*m|8YdJUpS|ZNwTBX#D86k;|GsnV7kr8*nWHA)EYiIc%k)}=T5CjG19a-?C8e$gASs-S*kD@|B^&uZu4FiwNGIC( z@RM!x!geQitYh;!qIjDh=GNCD7BL$+1He@+3@tdeGPIIVPf|KC$nO|s^B;gCRh$*! zCT0TthP-JZ;h+{F$`B{>gMTe>8{Z+`nXHP0`z<`4fp^>R+w$JlnDSKnvom?`XBGgGZpYHF5? z%eO8cDJsjK(IznBV3*_b2ObtXOTG5(l3go+Fls`Xa&q&3yQE@BsnmDs@k>BzRpyHe zJPS;~y=nu>kPm$JoeWXInj{j(>kbmfdoE@cyJM-}zC9GuDja8GDX!NB&J}s1RVcY4 zgaiGNi919b?kfFLU^nZZ4rVn)@w>?W8Drp$QIf7HFoXO3qgL)9{`h~A4)tf?V4ub{ zDxm?NLp3Rr_~dNtDz=yECr&vl+@_VY*4Rk#)tEz*t{`ri_ie_mOU`}1ErT6BJ#pI6 z)5U(*i(`SO`oD8BN0M#j!@!iRPhN+7YRVAc z{xHPo7{xJde>>4rmDdcbr%hP6R?7|n%_)?*&F0ym<%WP-7b^B|#5%o+_l@~wE-0*h zu4>LelzZAI+MS(aCj1`U39ml!gyV^#CtiEv{1fM%IQv95KJn8N-#_sk-k*5lTlS15 zIdWGEMnDO81HlXaU;=no-jrgh`{y ziOY_`4{DJZz>4;X%SV8o!f#k?#5u)_d{cvD+n%>+#KzJ1{W+}(2%}Trl@CNe+`%yi zI0R1rhO@AV0c9ht#iBmncMA8}nQ3F7{6>tDocl6j`2*$Ge#Rpg8qve_?g@sQp`9O% z^WyjYd~;eipGIfN$ZdvJP6>U597s)6((iyFKqb*LBe!3A;sKx@+8ws-Xw+;9^{{K!=b&R+?zu7b2B6={A47rIc`bcu3 z5w<1r- znLcAP#g`ay@=d@UcKt3>Gxht@95MB8NmmEoJUz(rOih9E=zc0YFj%$;r=ttFR^)&1 zf3N3FjOU(X7Obe@i1$7CCybKAN$Uoy%|N46eAD^ke_D@GI`${dxf%KumbL}F6LLMa znWk7Go$a-GU)5TWXMnkuov(O3wXV2U^R{6vIFH%_r&btTe)3wl$&j~+R=|e7dP!VO zD^otkiH0eEjk=w*hJUEpwvBX%h@62(1#d7umDl`*@zdn%|~9 zY5IuMC|(P0gR`5aJRg-Y^3_=P)D!fAiOZ=!Z9wHUAO?n>TXD>N{_*mH<@S~zYBH37 z*I0w|9rawQWrJReE8O5}iSpuJg=k>96T8K+7ZyZ!ZvHXD6yyF%^Rf%q{s~n37}ZI2 zk&MS5!j4pUg=)NkVcZ15Hs*!==Ybe2=QVRrztU|;V@+dvSD!1#-hi!XfZB$;Xsdm=B4oan|XWlF$#zZs6)>%U>xC(9e;?KKH!rquvsxso1bZ6WeG-8n7aVI{%@V zu=3CBxYVkb*UjJg@`YtH64$#T0_2SwM4C+#jq>$6*9B{G-X^&nqumLvc?Nrm)&R9E z){nc}Ky8S=VI@{O8X%2p;myRoxD%z8$mdF0s>F+lMK7=1AKQZa_bcp~iLegm>+>!n zvNxLv7|UC*>Z!GN#D09;r(Zv{=h(W?!$L(MZ|$ji!;D~kkPFhPSc7P{{Ooic8eP1W)B`WEv$Y>>7LHWv5 zUEUjVh@0YoMVR_Y5zuaiS%EBaJ8bUMu6-wI1zvm&XNpY(XH<| zG)2^te?uGR47lf>wX(4;T^c-GthS7=Y4X!Rn^+uIR>&-9Tb?L^{0G zBR-5h+6KII?AX5GTfyl;>eDdznf@zU*i3ScJ6A5g75S_k#pw~TI4$BXUKt&Wj79RY z-$drk{5>)n$&Wl2{Q{a;4)Q)}AnK*`R?9XMmF^_W;2qe(pPV*63$&~toyb22!lY9g zj=lJX)&hjGT#<9mRc$sl;rVE4)IqW7X~z#^ZhV?3sdIH|%)i5q%w=g}iBHEJyIh7H zWfVk*>NEG#GK^2t%oVIjl|p^|hh9dyUdC)tV2moGjOV3{&XgP2+e&HB3E%N$=Wlx* zZN&Z`Vq>n{sOO=UFpc0%=cU&fcGp{}aU@#zqj97f7WPsrhoS~fq|%>OQoQfnR7BZHCmwU&2MI}NS&RPf_mow?PhTy(GXFmOBo7NEaOuD6HO6=4)E8xk?WvI z3cyxO$kk#vqjfka@ZJ3;iTVhSn8=eF7vj)zakUTkDN*7Ws5BR5g+`;mu-^f9F@Rr2J17B)#+v}tX$AKez4uE`1YkhEcp z4ugb2+8oiBjomcO!NG(5`4bE>lL302lus_{b$WoIWol!v>6Fe+^A~uzi9ivbI3b#) z4E3jlO%u7Ov2dub(7P(QNiOhy96W&cw8o)XSNbY*q$>7V?1OXJT2s~evVSZlLt zoIvpZtgpZeI_ApqLF)=jT>w&f177mOm+X}Rc;H#1EcoNR6_86{EkLaAEESn8+1OM@ z!5vz*I|~vq3%X=eGxtm&4?DHY;xs3jri)^2_Ri6nipa1NI2K zrhvV7C#De?F#d)x@81NU(X`+%!@RP(&YE{icr||MiMDB6i9Ps7#QKsBfm&;E6-9=G zxe4Qe;aBA+!~!0l`_8?~nTqou_aHVkTd)sdHlPvuX7P9#UFrL)!|93%endc0cL@jJ zUkeMK0WY$zP~QjzDS%gJd%ZagdS=U^M5Vm6Y(^(CmPFvE35+Bubti0q!8AF|p9}Q; z3$wQ@U0!ZHJOkP?0g|I{?*@3pTFj=dh-tF_g8KpFVoUQ4^+)}LUQu?&-~>W`VYu(y z1w-HPtf#!k^MJM{vaW-WrJKCIz1Jc#kdR}uuo{gHn>qi`zH@Bo=T%$1jY|XN!r_D~ zEBayHhr-?EE@`Ct+<;9%O&n_E5u4@T;BJ}mv^#r-;1hAWInbP+>3Ri=^g(V0tOy3g zT=J18p%G96f1d*jsWip&y6g&UTXaeurJN2{<(`%U=3xhq`6>Rn>F`Q1T|db=oa6=YmgTXpZcR4eI3qMix3s(4k~u0u^d=eS zv3g=aK5(0}4$*!g`NaXcPa5SRcn$MpH}B9!RDYt&$k`0pWZghE0VS%*fgpc^gzyOu0CU9<0CoECE+y0F-?$i zHjY%w`!f{$C;URn{sWCoOLC%gF>e>VaLW;UXH=i=yEGbqhu5Hy6=41-V~r6y2hNd< zpMfV*jXvgIYSruO;Zfe9nH@)n%jqiPfEi>p zioFxypIbA^K9#G;s@NWEY+nt8v*oa?l*Y^(KWQ&UKEqa=;%3B2pxjP-H)kKyyQaSt z--m6qhBrei*a~UnJx!1;iUTX66|_8O6xWr;LY@LCA)1lRsW}nz6)hC~(`_^+SK2ni z6Vu3$SDf4I6MdUB9(YV|p~Z^Kii#GyP#*MdK46-TZ$aJQ>I7h?j-@;pnF=V2mwOh{ zUz73K(?P^iJ}+!Sv^ZjywnS5C0N);9$hJ)yMj5MaKjHmGSW$&!pM#Mo&QUm8_bbWk z>b)M`zt4|M%zW7yzM#FY=bW`~sFPuRvjkzmatev<_vdbDKOOYI#((^CLcu*HyBg)u zl+mx$cLe&qVbauaRlZX;0>3hZy?@D@X@3N0(+0oNp!}?vCt@B^tMyhp< zuu-i}9K{;ATe5ds^p$aoWY~z==`^aWw;D0|TiR))GkeTzcl7@JN%b}{a`JK6SGRki z8r&^U^{bb6qCfB|e6*`2qxx3=m6D}f;F9q<1En=g{IhN?T-ips`kuo2^|T$;ZkGk{ zQ(t3(?2$@56Y4MaTg}3u-&PTYG=iR_wf*Y^2MpUM`X31YnBI}5f;)_}Mq#k=GO_>oPs_dTa=G!o zRJr2ia!(%T#N)?P<^HKZIeA!Quw;D%u)7L?gmRAW8_I@vFi;&t4EvbPgMIEqO@BX5 zx^SjikxgM=&?&p?e%LLRD$3?zJ>U+fcF7}Z8qashh&o7}wX_c%{|{yF0^dZL?vKAS znIw}oZKoHYEp3_fMv7blcmZ@Z4I#9MQd!r<)m>8*H=wJjiUt8mpkRu)1Vk6SoNZm# zExYPAMfOm&+v4hq9^I3GuAu8WMW9GmoQl}#HUIB3Y0*7r&-tHUKc6O(nfHC?y*|(T zK9}#aA4r1(U-wlGit2U!WVs@$d9OI^7k8jF;Mg**mZ*&`>msfM79`9tz*JU|(*b)e zBWz!c7gAIT)%2Vz=8@u{mWwCMz-p2R5j^b7W*F0zr*D3)*I`PT&L1JGAK2)iHb$)9 zr3JG4F#KB19`OUYCZ!}*X&F5CwP7$vz_5UUQCM&_ZT`eg> ziW{7?zV*<}UT5byLyp&JJi{(_JC)X(3hK`|rKegSbN%CpL+8{3SF>_%@?)B}rXcW` zc3bCx=qBw^=dZM%OPjQ9@+NJo|251RYsV%n;>CZ>msLSf0YPm5?joXKtCQMj*F2gB za_w*Puu2@k}VX}w$_YSR`?6_`+IOdradDyc_UhrWV<)0?XEH7sab!z3r~aE&KgcH zDF^ph?fbznDH1_9;9Iy%#K>F+OQo#&j) zzc4cT3znt$_2`9XOwui-=7)i6%K3J{wmzyAcXpl&^2le%ht)O5c=x;KnB?8}T5ooS zEtdMX*P!v^JJwe@u#ds`Y<{u zlfM>yQ!_PtfulMj-S6>4=UEYZQe0KpNdM^3;(7j8(?pQ5!ynD`R zV$&ap(Kmno*Y})_igux+$m5)hsK2$*}Ym#h!;JTcQC*!f@kxk1?Oe6>QKOEePy z*BV42of@md-~US;-b5WbQHQtUuXm4BIlrpI9xJ%2YQBHrfWBHT!5KUkYm02O`xeV< zdHga!%PFrHddG2Tz{?ix1$ZvC%WL7U-T~xonRaoK)1r2^ov9XKw{{w9Gz7W1zUCI5 zSZwo&-$`3_FV~ySS=7h+={{m{p?t2G6^FDL@)G#=gb;JAYAw5NSgI(BHqkgExZ2rjPY^cq(U0p*I~nE4iZ zP3m9p)`QoeR7=?VIPRvY*TZ||aczd)Ze3~|OWw~I@I@a-D#-p z=lGG=`_TXP{3fpr+@w^h+*Vj&?O?WAUub#l`k;1+)U=jWU{+NCpUcLreqYOwI<;}q z%UZV7sO3pb54C8w%eChWd?UWQ9^xu;kvj;<4U+@yi!g(o+fe7snNidfBPMiA!c68B z)St6NX=fsVHso}il@qvzW$tMDJUzV~at@?~?t)U%8n~pI6^6oyO34iL@kZ@N$Bx^c)fO{O#x?F&EMg^ef0^T1#506udT^z_~plLt7kw|-@*U0{h}%lWGt3_++9F+x;>)JW~|H+ z6;*Y>tR5JW(fhM&h~GxE%Osl~Q<(7jKYT5s-6nDS*j=#TQM{mWMC)>o(zCabD>*Yc zteZyP{9@Q$0G)vloF96XuI3kmuh)^M{r&oHvc`VtH9FUAY{`Jf&B(QMGIaPBY#XN< zV52?`y=;W++X#7<2)@LLrkJ46yoN4IWbj!-`jqQEW9iaj!M1T&wP~?Mpa5(}K?mvm zVNoDBW7`!entV1+HGikiAg6gDHbW07hR0`ZHL8BvrXj8o-|oS0vk=`*S_ z51PoN`6AMA!tHoVuKv6>4xTKD6*tCzfON_0n)p&BS&Lk%g|Gx3$&#>GuRXoZ9810{ zRdB_roU0US^W|}RHYfH#bf%ATC(E*;1YUp^K!2MpR`K6zV-O}$Z5Y*7j3smBT*#HF z;EpU#p*tM32^{;PTdd$?rKp!dapRM)5yz=c=n>v(o9Uxg(yXuAM)N!sQ9OC@Q3+)m zQ9lmX<8a47?}42y7jvsl!S91?w~;)RWRAoBFkN*FRRZTET`d{0>E9QP( zTuo1gh8@1jwRCm!P!3AWjlnBRB8?1e9*A!9=2*eZboKNwXQNa%44IX2_^luxx1R1? zgM30pE+|HhDrQW8w=!#V_y{A1FTomTuyH<@%7H>bL&ZskV(&R~CagYTw?esJ8|G%z z0)epwobK~|GgD+bs|B#nERtjbEU?!Vnd&wZ5(Byc`p>Nt(od?|P(hxeDsX7wTPEh{)V zEXQa>JIdwNF|&H_Fs1vEL@5F~b(!ilt6wCNcR>eqT9O4?^8FM0?kd1uDG&c<_3Duf zn-O{P^!(FtKHxec+6;V;@xawFL<#{uybQ#spwFb{4}PjS>dl_Wpr(q0lD`u2RHNEA zVDp8&zrejkKFM9>O@l`ThdeId4%gjBJ3r@EB*4u%G092q(n2~f=O$Cn(R>tiZf;fW zBhCMXo0nbS=Hp}euu`R6Lr%wJIq~VHczyRg&)xSDcdtTUJ~sRRCVT`*! zH^AA5%fC8+v(nmhhgtoRPNA1cH|}de+*z`~;(o^6hMtrli_ZMKOKiXae1_6TcLh7G|=3h|L6&>oK5b9re-JNzM9oJdV6n@5|}~X z8ow6;Mb>?g_v5=wiEpk^u!*bK4BF%#EkGAg$8Yu zvR$_=yW+CgBxp*pU$MDkS@<^DobZdf`x>v0#yVX+*1vKs^?Wb(TC_LG?HE_FA_giW zb9d7Hk^Y}3<8ZgI;xkxfaLDmwKWym(l{)RT`8G$1>b-&J$dvly{&+Xg@zn5l(j4k0 zI*9mJpo5g=?f#DLboGJ$*N`&Ncf$~Hm;#yVRT+p?z9v(ZG7z~8@sUe>yyr3OmkwfA zbMTzg>IL2!2Z=IgeN3A=cGqav?~3jhdKf#{`WR?D;t341h@nI%UWvX)S7r1iEImcz#7(Yz}ZI~Dn zrqO!DAHSr&=R*#D&4korrvGqvatuF?Q!NaLUdeGZ3q_y@?EE9mGtNfW+eU0Y zHtotc(xeb4hcI1JI=g{o-G0@cp#SfDzM%%0Z~*XBf`+7-dL=ES!fUKnPZIhYOv zPNvU;4FeT=?>{^+4!mw0`U`9D|Ci6vcvTJ1XDQAsoxLMiwKvGkJ@B>xFXKG_*&54p zE^r-Q0FO;dwMBrT@z&(Y%LyCKBqyW$r!mjxdBE)$gXK2enT;7j+;6VV{Y3R3y!dI-jG@S#gG;Er2X%K zKaTPDf{sRb!z@v+;InXEP3~Vu`o?2S>9lwmL^QAKyYgAsVUygIm5HhZZ5D8MLa(Bn z$^f`XIagt#*pEPke+ZNUvm_J>@a3$ca+?S9%E_}SVn-&{kUvKAhGr6Ww#=;FHJt1b z+Lr(uufq!A8e~6`S^WT<*W;&s`?9D>{f`l<@lD{vR>%;Y@&c?9PN#4eBVTR@aZoDh zChRCzNTYHQ+Y?CjgCWRVM|+Rzg;}1pF)CGDg__KUX5FMdGvY+LkMz}540gV{QD+5YyY#1NsDj zV3aXhHiNBcjLPFM_J2DO3LJ}8ZlhWh#2}wUFRsEK?@kHWqJ0&pr^j!my{38z{?i75 zO*Tk*;i;|u@QJvIWQi*f z0rwJpeqAh8M=u3ez6q#-Hs1!!rb{v6m+LK>%@8eIoib!nzZw>?|80(bOA*&ZodJ@k zU}uNkNg#e6%`#R|)Zq+p_Qa?JTgW z-wYa*G1@sNU{#BUO!JAKfzM>we5vc7bOFt*xU*o~%yH_&u%yC?Una|)x!8A_Vu#wO z6+pOwEu=G6JjmC~(`Y>KDMdw&S~S05MOo6Z$)1L8AnP$@=Qh=Oh)(B-HvZzx>khW#6ONQLO|9UZSr=&v7DHffai( z5chW={!A=t2j&P@u^9NC0{B9j8XOj}+n{$f!D=Q=qN@YLcHhSBp*!gM%VDNo)AkHX z4Qaa@QvxAvkDRb)K=$<%|Kv=oDxeJ{SGS}0pOh-$dlSTWn&R{Ef!8r|A?-;y28+&v z+EZ8^X4a(rlA%v}n}i5v>V^w>FO_UKFI*aV3Ea4tJ*5c|j9re;ciXuwdBnle)Qg6( zVML9J64f61E>F-IaIE(IBX;ah>Hdww^cfqj@NL}=PQ`6?9q7mehkC{Jy<=L@Tdmcq zlyaS3f-ylUzZ#k2o?l?sInh7wFAl^E}A2ibFuaN3NgCp9HS-ti{ z^aYceeN4Tnp9jVJJLWbp8JuaoLW&u9mT9URRF?YZGMww_`>K)ej%L<{(Y$9UIofN7 zfOuD&>`TLlS?vJ@(05pJ1*oH6xgDn_pYH{%FizwHwQI3M%F1-s-uwU7Vo~{4?w*6Q zBhjO8dtuuD+zUbULhyg?g`*m+a$nVSS4Xof-BR>%AXxw{uu0iRn7e%dEYh z*7X~R9ZV}5#`nI$*ukH6J<_NJCD!ZbuPbtZ1G!;+Fh+mx9;Lr`OSGGaVV!qs_e+&q z8?^_e!`*NF6gmC14+XWmrT0-Prd?HH5=qF5vW4L?c>GW<+)WEUR1!&lVrgpk0x(0VA$Gt>@K`sl2Mx!Gw@1zaBZ&_F#u)Rk96&@u7losXd$8I zQ%vs9z?0U;iJR0Q=JMqJ7T3C%4YMo-=Tc_BH$DxLiSCgWSDk)0Tb&H<;)`?7JgyyO z#R{i$YtoQh2D?xWKHSWrLXt9v1Eg zgflvQ+F)V9sXtB}h*De7N4tUF!c~T&hZh!s%ZRnisC1k%e^B`*p{+ju=N09o8tFj+KUW@sQk(3cP&Qo_Fi(GJc{#&Uk+vg zL2b@{x_%LDNK;HUS}VNzee7_|qu19ATBCZ*S(^`~96%{Ou{l7kFsr8qQ`Pr|;){|0 zepYt-rsp^PXJeV4^RYG0^*Z?#ewXH&)KnnVc?+`D6@z8>Oa!*YlCPhuYYHhL+eU+} z={}+O_>_j&D`s?flf|F?8M&ym7_BO zjgk6ZVt-Y-x^BQ0@cOdVV*_#^MSXvSq*v0=@2f`o1dX1gsIGn>X$7*euOb?js^0sW zw4WRDf1z@~H36j#_g$8Vq0|ZX9F*#d zDq%Cq{Mmpr%23|R1IgbH=uz8nT0o5#jEwC^d0Q2Je>@_!?R}(j!EaF-Xv4y6bvI6^ zoZ5hBPjp&MRp$*+j}Y<{8;#+ah2NAyj;5*~15J(6$*^&{bntkg&^y6UTRL8|&Tp6jP&DpyWb2M7CLC6|i5Me@F5^!=Q=WUM5_ z#du0TVN+e*dQF}ftx2ldqt`fL3;8hz>q5b4)z!X~z{a|*VLntHt_*RuN}@pv*X?Iz z{FJ=dxW8rfS|RbU;M<;&8O!R;$$;(@mKB-t6eX)ygf=CsSAg7U5s>7L6d5lv9wko< z*aJhKm=en>vwPOHOR!S@$v}U%^n@RB{LKate8M{1yHn=*`(2x5IfJ{y3P5AXS)+bSO!&HYa9(F5b=}H%QzF6RQTE8xfTCJy+Q$_C%>XBmj1W#71MO<>#6i- z(?7>PY&P4JiXzy#H1!s7)(VQ-%c;5F{-w>ED^#TGchmSZ-VWPy|J|m;++^zcwxL|$ zD>j_Wk{>>)_rCg%e(q^8I&?9o2$qiQZqS1Q_=%WhX03ELs zHjJ0TW^o!gtCfM86bpN>ShPis!HXz5L{wYs9FzyAlJ)1O+7<6UeL5r|_UkX+)5K2T zeLH|n;iMjUK5;DeT4df8u)8==~3IecR~$IP7kJ5|_g~>ikQcuC{BEv^;Eqsb%}chTwmnMo!?Zk%IIe8SGC-p9onf(xP*EIc(19!HpwnXPmE>*h1D}%M+&(Xh#eR72J>*+dh8*tnA?-_zo*#4 zitT9Wgv50JW{ps4OtfPTJvh3?G>s_aBAsSUI7Jll*V^$lMDY${hQW996fdWq0!nHC zeRh%4m+eUbCaEQMi^?&p`s=}3U-hamkSsV^J4v|nO#b>L$p9yKwg!qIb#X<6e@N560veoXZ4D_)H*YCJ;GpciZL4by&%qjed6| zmbH1!Kr_MfNy;+F!17^?ucr0&Iq9t0U3dU@O z*)u;H%lSlNS+rCY1W_4#=c5TpPj{~Sd;W_-d+$IGC1;8OqkR&#B&K*{*a7YU?`~Kh zWyz+Nhfdz%;eTuM7@8A(_1IsWr#bAPH?xa`r%bU;+KZXOQ;%r_Gw*n{RN16W(0^Sq z^YS*jJAURw{d>J5H9Aj*E#*_2wDCys46qb7X}S6p!&M%xn!>-*@-u}!A#H-~^=``j z!oSG82ej(OjA^mu4P9X+FdO6iCi<`(PmT7X_=<*S8*B~o8oJ+x z`}uL2a{?^Q7B!4>7v~-c>_oJ(^!PjBhVWKbN}S@H@%FPvlyI}laHMgq7)?@pSh=Ex zqhqNq$BePcwr<`g^roQIMH=BDiHtrO^WqaeO!zdg#Nl~6I{XXSM*Yik>ZU0Za4#uI)vZ2b=l zNPE2`Bkco7D~+bbF8rQ}i0T;u#?!=UWw9L#N@C}O&ue1jzFgU~r9HgDyS{53os}d~RIf{AR8DH?Hi%+b8|T zB^?gC(vP;DAJ~w3_*a-UrIQ+6ro&4M7R_(%*_6S3^b#!MaWKceP}R?i3m|oqMKx`ppeE|K-Kxt=FM-og?Z#Zv$PuIdW*9ytNjX$+btD zey%)9KBOg&F5lMlh_W?-xSBQlxUvK0^bdrd1#IKb57P52!=tWkp+`FL{8{X>n>{}( z2$+i~h3PtvV=Gg4j0B>7z4Rxr<8o}RL+L*`8vVJdmp=Q^d$utpMTtJ5qJ&Vw9-WR7 zNZW6SITVL}&eP21SO8whLXU^@HdG`3mGR=OoNb~~xxf$;dMX*)%Dd_NPg}Qa-RGJS z-}H#*k$oQo~ASg z=&j}$a4_{4d8RJJh+ZM z_KN4#b%@HRz6w7{XyL*pL_M&D1z0sOb;Zrd>~FvQ7k|~ItctjZnB^NY3&0ZxI}F7JkKleL(jy% ztOx$Cjmv32{&oKZ?9wdI8Ri2go?0$A206JN*sZ)rIKQQiaJt5ifGYYqy~ZyM)4mbjc(7$W$Y;8tMFC&c3=gU=N%rfF;9F6(~%6Xzcf#VH5BOmMU50 zHHz;+XbL$dzhfrU$E|23ainVfTV$|P88Wf&#ldCeG(`F^vCHCVz-%(H+3}SRa>)58 zsKd$!-!_XeALwotvu}mB9Aip}!pSQi+zO-{XF;{u`~i77R3Zg%v5*4tJWyc7qJuFt#4ARLLu}cSOd>x*Xr#STTpHI!%zp9D> z)mKuF44CkfchLs^$zq7Un-RZnn!r*KEN z!n=!TeMPu3NU0$Or_-n&TRK&}d#EKkt%@sagsfzNzr+V=$yFt5m@T`m(!UbY9(b+l z?V=Nj(l15Rl`KS}#@^h??+$9>b#(vz{@ga&tvBkvuUG2rL*Jl(y`$~=3WorCw-|7I z+b)jb%tP&ZLS{cEi>uPu2jI6=-VZQNVK230WrI{4(%VF_b6A&uqW+sCVsm_d3(#i-7r*-1iuPbB8^yy)<5BHoxy(|J`)? z@k1tMz2O7SJ}xdP*$VBo4E7A%wY*PuzZBh)R68zmQ&y$-{{BoBWJC#Pa?H&d48iCuO2p z(Mc8NqgEv`RX94-Cm^cXq^WdbM(kO{4?cxR2Gmb3{L?PL8J42MecV6*C(+t4^r^fz z0ILGL-H5k;(vRq2I2o8BB_K(PgjiR@ec3pn;(9dqP~RIP{(s8-Dsl^PZX zcO9he_Q zQw^jb(BdN=gjszJ=ehNu_>^ZBqyoczpY})gnblsLLC?j9`YsBNe+N>7Ul+`B;)R5c$*=%OIyoH z(G;E1?9x`S9L&&hb{@N&LmbUP%#Gp-aF9Rxv(+m{h@-3pZKoMSWz=c8kj}5o%#4~* zf&?tG6;cl5nByRyCm9_N?crKbE5eMUXi`^y6P%T0+vO@edLp_K(kmNAX&g>j#EE_g zsfaGixRk+nj(g_Cf*IjreFvDLo*J=-54gUGC#A@uXRw>*5S{u*`V!c6OQU66f_!=z zmoRk((Wi*|pQM#$n6;aER@)?{T^t)cE!XMFU(QQdt z48@Ny+u-+`$lVtTbESU*{s4Ad!V9hmM=?L|yLJ9^((?KH2EY1hY0<9MC0`HRvkQDb z#{>V&!&5!g1-BKvJlOlyqWMdUcC<#cmu>l@6qT!=sZ&1kVWje(O;kmLTDP+lV{}ro zKwF2^LZgCzF0dzB8EC+1V`4@6kq|ypj~op172=V3)x6I+w^Ca=!|AK?qSE@QgVuNk zy!dI}E!9iG41n)Na%|N~iWzBwd@&OUfElofptBD}e*0N}y}tYB5Km~u6$~d%q_Q!Y zskdnkH)ngoQSUMxW4%vsa(>HTuj(uawN{`NB-NmIGWF*;nbKVH7Nn?ihd}OC4y*9S zk>pjH3hW*{rP>%q>jk=i^4me*l5*ls^LTjEdIOoBEHyIXh!Xu`Y)sbK=(DIm0S^GR zUb3i1QQDW-k6I9efb6Qy<9uyYFM=%dM0fHmJf?Tjsi*Fd0Eut~qzDy8SVGC3Sa&C@ zad1kIO?A|_e$$ zJFT0Oe}{J``cz0VV2h#I+sx_~P{pCXKY%-M^3$m0lX{9z5kUx6$T$J+#EvThso2PT zRz_AbDdhqrD}t(MPEeYOnhE`K6=FJ#Q;4$z-FzfPA*yo#(i(oZP+aLrcVw$YLp4z! zdj507yZkbCTM>{PKVj2O#*7M;!a>qZF$d98EEV?B_ZOkQ_IF|wO*x_+g}(7#LU{To z9UTXKZ%V8Z)*mNjh7&J&HJy~5KG;zofRD$VF1zV)^!#%h3`g8wMP&FU6PH>@b8pbj zAB~=`xSKnM$VvJgiY5*$lVYac3>k+BJIQ1u1BN@_Kj3di48S9{Hsa@Wj!XKC{HuG! zZE}eNE+pIy8dX@;!7}rZTU6lN==c@ZT!}}<)zSj;rv3WJXQzU#2Z6Z3nG4P?-uY(5h`YA=2IblO zovnN4XU^w>j&{*4I%?)Gnn{QkBqJan>{OgvDe|-c^nouECJ#<=hhZIqJw0|%gCuuv z+-SeDrFF#v-XMIgm(U5iHf-#@0@h`e(i;{paZbD#k&9%239!QxWf?28@d1B(s5V@y zTL<2f<~bO~D($sfSOc&|J@VJAiXwzbXc5-%FCig?Zj?)HSztX@yP+AF63%>a&SqEM z-+*rmvuk6p5_lfiA#4avT$nFjmG=I`8SEq2pt-oeRd|@v*6PdWg)FNOb1mRMsn;en z9`2@`T+kK7ua~9Zj23KZ zH~QOmH(Ua_lY!Llxmw;Y@e3|LtK!m?{_DO*o}z#T!=!H`wb| zhV!B_YJPBlYK|zLqcxXN^W!7Whsrln-AQI!+3+xO>wRNvbLGM#Hbuk;IqmLBl;Ons z{-s{Vx2-4HZ0E@R1+@)-Xl-+yM${Fe!c>-kxiq=K?sz^t8FP=~wQ=q=&%cAO5*}+x zw5m|@mPD6tSXr2hxiw{AI%-b1thVUZyeC@qcweaPa@2NDUPCLPdMpISgWTXnw5WHx zL*Y)yz`6L?>fjoJ|Mc6xe{=XxVHyjp3s(thkmG*Fl^@&GVBGqXyt44E;eX^l@5(un z9eu==c_dw#zd@XJb6&CA8Q$3%M6~ge*}E|lGNT3sCn3}r#EDd7*mb`zwVR^I-yrK;{i_4>@^KB1c zC_h#j=D36T7A2_vPWjlop{qf3INWpHqPw%`IV~$lQmF+C3?Fl{{6KeiqPun@WbB~W zeM^!f)fo=c8d~JtYV1idiBTtTacL!uY*!ir(aXYr(D;ZMHp7qv5VzTY^_5z4#=@Cm z;eD4wme9DN&Eu&1s=KS<#;A8g1yKdO`(_!@sb3=hgbVNM!bH(sAVbhZz z7d1vZVa*V68Qh9jFcGd%53%i1BB183ionfkotUQbfV`FZXL_7cxF6t@*`&1!pgE0AUMv7jEGsZ*}o zuxeybDXBe45s2~<_1ZM_6=Z-1L)-(E8xO7yt@bZ8gtLRMH@Kp&hl>e~v)2xv{1k5| z;(-v?rZ)$-CQo;?7HDQ6wj_2&jjJV!SW*|-$ZS;3Kc$3^W#f8#TVuG1Vyx+Pqq>B` z8@JLqA&5Cjr4uc}jB;V_&41Emjx)?I4wMIZu*XW)z(H*f!%hv)nstt83xi`j!}IOH zb#s7&02LV8HE}9&)52&|tKEl)+nQVmDLvj|_v+lq#`_G)W8o}cp1T2v9Q}}M&N|@V z&&t!lzr_m5EBnN@B1M2jTqwL3xLB}lspyM`!Y0pNYJ(4WVTh6a7_c4p0HMV0uAazy zLqVI*;Jm6hN zZky-;$D=(#V_x!mP990b)ygpK^heW98cjQAG_5k4)*qhZ_6NltfsOGHBl?P>3D-{y zdtO8NSGcDF;m6@VF#3+-tA&XvG2)q=(RrtOS*`s8Tu`(YTP&WY%w2L zRRcRxI$s}vAK`dJ0K+)p9De|qMDr4a-*aGH^kN&)#lj5wW5RdzB>mz2G_9SBevagjp{KHZQ6T6N=5wK)SMN z)%P`~yZ;mI7W4I1%_q?d@qfO42#j=x=;#vL((rkazb({)QSAn2h-Nt^!$^mI&42j5NCrA4A1`SbQFNc|v%#LOrq{rwS0s1l!Y3SneX zuDy^RX^6u~G49yJ&ekj2Z>(+r#tC<<6MJ0pRHxZ^{naK&9LyC3@n_axo|gfI3wB`W z7jw48b*3n{=6$Hym8V*NqLnCrgjPa`5#rNx3%)u!+hYjkVpmL5prD&*_1ROPC-W1R zb1@NdeTWX6q&vVoPsJU};kk<*3v6;3kMYpWHLSW0c#la+K=lxyUwsaUkrX{KF_(`e z`$Ro8wVJF1wg#ScX~(#p&8F5>OSMuzo6#GaTs7AtJ8HBtS@uL0MpxNAm0D>KGI@=M z+@l3^Z!+`>|9Psg+BtKkCILfcsIId>bW*#u`Qxt*W2UiEJ3JO>UfChFO7wl6nEB(X z`D7P}QyI<}Q+$wX@0=Bdg+5}-C;IjePl}p=TE-CR(u4@w?8&X(3g2x7qWeUv-78N0 z@JP=o#zYZ8Zy{gx?@tGsQzqshPRIILg5iWLyBR1LwK;+|3$TA=CMm`yD|$V!sxZGl ziDKr$B6$5UH0WscglHDt`bBr`5{g!|mtEm4yT|Lr6HFK{^gYpGL@z~)R}7EsIulE7 zSRC|(T4djop2s{Cw@~)+54si@j#c`u1Kzb9rJgpD{loU_JUk@38?`O1%RK3O80>yF zxBBx~;lr)6uL*rgcxg8b3v5#Cr@^uyqI(vmKV$a%2+d%$!N#G) zrIk4rh3Sn|@K8cj#s@_`7O21dYTx%Y*)h9~R_I1o-%$~17;MVutTFLg8cT;>CVatB zrf|&Z!`uK>5(;I|ZvXzQ|CYv-Oe1ARXKF&KJD1W)`(e`oz4JF(a$b;ZBzfnH3wMGH zZw2>yy;m~Y^Rm?IhQ_H0PK;F-z1 z&FHTl?t5iO0!AYB?%xv7$RjHKxkYn5Qi(K6#J#r?Qt@0w4n++1gr-0J$D-;BPY)y{ zoL{rl2ZquV>B>K}g5q;hNwJ6M#cvbEkOi!S)V5(rn*Qa{+}KyGmHLy0gjBK!PlV<~ z-!E`o>=E`-jvW_r_=ogJO`D_=+ToqS7(b2L8$H<`dAAiP*jNz7d7iXh3(Hbp8I)1~ zwB8C_zdSf$Z+0{3b}LJGL@l?Y)KH?Jh1N%6$uC=SUi#$oY1d1?f3+HE?w3pCnfa)L zKS4ABr3P^}wmhm?BG+qN#(U7KH|jP8IT~k`iaXCaVI3~9AL!3ASu>uEW8?5_>Yn7Y z3Cz^I5xr42n9{t-BNZ3#$x`naY^=JZM4ZF_*69Occ97ni9?9Q32{?Hd!)`$g()(oJ z|MWIHp8V@~)!+rq@%~RfmuJO5 zmEVK4SBqwZ#h0~{1(Iz^XK{^V?`awE=QP55B%6tWJWul3I3}U(<5>i@9Jws5H;+lh zS?V_fGzJnd0J9)%PwSnF>$$i-Gw|IpA^A|E@83rz!e&g}|0~4vqJ32nTEREl(O*Xf zOwF>M?=J)CxT-)2Zx0mh&DTe@MjzGj7_%ov#_yKBC3-0husQqW^K^5XXKq0HH}d<# zi9nhe*yTxHb5b69k(c(M)+TAs8uYrv;f_TL;ZB+q#GMQ#m8|mA>T~j%5t_NeUg#SJ z|B7B*MFyA-5Mg4&!*>_c_E)PXhF>;?Jw}LT{ro-$FhC=V@rL8 zowVj?MKHu)lJ4y9v2I0_9Wz4KX0d0- z)0IYF?Mh=nkPYhUe&e(h9VuCmYgZc7KWPRfdF9jf8$^LPwf+DoP46_r>ua74R2Jqq zZ#&2Fuh$(m0}*A-&1;ICi|SI=Ok9)iOsU%#cssBad&!U=r@rDvb?3XuzXUchz);h7 z3QHb?M>9*cu1v#vmRl?VuG}h5fKOGLxJ1o_luEQrtf1&?nd(ILI&U--8g4e+ZD=z5 z!O&;O6}-Yu;g>>>P+^>Hyv}&D@e8BFG~2Y?^pHt26_`(&&X{tb#VWKqH zoD}dT=3B)Vr$ruxe<@+um$BxRDUoLAw$8?Y4K3}6W>iRr*XtIg>$0o=J~TMcul3Cl zpZ?}NgV#yC6s1~8CR~2@Gn6L3(}>T2{ujw^L7Cv$ zIo*oc-i}B)D~Fk4I$paz>Qd{~>k^{6G~PosNk>f# zqdl1x5qhUFOT=5463K9+Mr>X7)x)1v+IQ&v;7b5WEUo}!oNSqx>P=+W>*LMa5GxhF z2uoCF>Zz6B%fRle^xWjRmheF%uqM$cJ{yfwT0~iCi9qg;_5Mp}7z9|aG_GF;eJ>&L zr^Kp0GwOA*`;p%JZdbj=8fQTZh=}T4nfBN8ca~qML!Le&MF|r$s8pxSc;k9rx3?CR z|4`}`f7g<(;x4}Hw34U1)CJVZM5g`8xCB2!4!&+zfV*slUV^jV1sfUJPbGL!8)iQg z3fs4O4klW1=-d8 z{^yn~8F4DiOz53zn1yt;cYwDIf8OoAhn%FZal&8f!)`fXfxLb*`W(F% znf007-}9LTekV_(@Al{|36G@w)A$8P#@V-_Jy%?4PbO^I;9r3=+IX}FHJIhSqZV}l z-a+rp2DF@CIFsu8x?W$0CllVPxzTTqry_OTaoQn&INTVptxC!5?3FkP{j*dn`E0I- zM*R6_D{TNPZ33eC)&{(* zc*NC$UmY-#bAh0D5FyZ<%V<{usG)?MGo_RlAA@ zhU_gWg9V|Jmt@xSI@SSgDvQbmLSW@+#Px6MHEpzBzZlM!|DaI4M666UbvyFsV{D|v^LNE_i-q1Cw#JvKx)aKpgCeL8X zRE6uRt9{U>s$A_oP5O5e;y=GLH2p$evwZ5fAis!O812u*$Hw=7FC}7@U&Oej?0!^p zwAA`IFD$u(O%I0Z1kA9nkIzLMz0H(fTDdB1$J|28OX+WKIh_uL4RT48NAfps`%tGx z&hp=Y8f6budggg%dk*Q;XrxabNz$KNhLnJ;UWurKieE-8ctmuxD%D+$>t(D|9xE4^ ziCt#7^CYNH@XHgj|BDk6>|k$(-_+>=?;S0#Hm*Y4Q#1zD16_eC&)a58tTw<`7~)Oq5oZmj^``X(aAq^= zG_#9(_|>=^Fx!h_L{BN2MRGM!Z==^5sn-nP;$4Av%o(v~*Yi{Iu`bvSP^TC;j`tVbScaCj*|x_MWq_#KI*0#wExo> zkG)l};bb#W!W_IAtL~5R`X)NDbC4(*7t5C2-6p`IsT{P^d3Dm#_>)ArG9>Zm4Dvd32K^C zk#-pSms+l;MOt#E#jE&fjJTL2cbqWEiBt6Js;>{ckRmTV*#$dFQLm37o{F~#@(ji9 zpQfba8ms>f&OQM(OVUfCmY*R4bfRz3U}GTtC8CQCcc%L1VTzwhBVlMCYzy74cZb>g~tJMs3;2IT*`t;ur@T)yV>XS>$CnAMpGRBXK8#fYNSe z=ev`2Sj-;unLM^tIQ{oi4qN=Y6gpE44bzxG_uFqE%Ywt zXr?_5X&lXh?E_R-V{DvUd5Y>+TX>?I`gg3?-qT83(jsJuZfAcvF$W&bopEJNr`eBn zZ1w?x;jVEnKL`m+ssfDii0bg@%l7C=z+8`6tc}R}VL%>1cuXj}Y@6UdtAJ!=XI>`hGkN5ruxNTa%>LvXUT6OLg2T&fOdGG+875T{DQ{jDp8o! zQF;!0CVwrpntY4Ldd(oA@BYMHx6A^cYEDW%hy2~fDKonCel_HbrbzKQ!9S~gmEIeb zJd7~y{T=LqQxwmdC|I&)_w^gfCv@fDUW?9Kru91F|5|#)LakhB2~>-f_`VLjw-Y?LA$%|>2MQ47t*M)+ zIuD#1la*5t&%o*hw@ASn2JK<2N8q?#3uh!8946p7$G+~SC#eD1oqfh?NcBsaWd?u7 zirA9^Fjyv9L6tiX@7sk_|7(aq#$r#kMQ}&)+;ZmKbwbRpawd~sDmN*&oC&WpeqdL5 z{LU_)4hosf!z0hmwjIcW904IV6*B&WJy0oNhyjoI)XIMpk{J3Ib&|pq!ZaKx>R&KN!1>PMCIC*4D}Nv zPA&@=JUq}>%oaovw{DJd)4mTRvbpgQcuYVh;qb09MyM_VlOG}vm|GRw8m^)YTxuGy zo9r>6q9h(&&sWHZ*OfL&m?j)a`JMsN{KAR}@R;K(a*iOczXb8zpTYaWu8D_5nNxP* ze2>x@mHtD;kDf&k8QDxSpG}2$^NH>{aEJ3K(^_#3b5MwU(k))hYr=o*LQFD>OJs+zQ`1s>K{&BMX-lYjaidC15Sam%7i3q)?0G~p}U*a#KE<|JHhkSnW z^c?AXH(^!iE^#^eA?yl(n(u@6nL#4Ye09*B>sQTn$v1CL*nR5q=CET+ z^O`LH&V(89 zlaltjyxOCUSu+KIC&R0A4?u4c@GpcN=0aVY7{FTN5R=t!^Qe8t5-Y200q2iNk7THJWgzKTui3SEL+XT0PGr&Lw0EL^algE9}sA0dXkGP0ftk*EowL6$+X*AV>~yd+7Acs-;m7Cp`}<=>T{HNmix6=3m+`Wt(?9WPsv*04zSI9@Yc zZJjsLx9pFM9mRT~HAA+y0?UkTj@mdo&Zarow_IOMR}pP#6=xIYa6xx)ftOl?`lH{j zxfXTth*wIA-}l?KpubXR?GB(%9b9_`-$L99d1a=$0DgS86*$86uAYyRu^^Hf19;>z zQ*2r^X~%8&*jbRHNGSKR#o`rmczL+806Onum>KQ0KB<^6-)EwZP}B=hrCL1?PN+zt)OG1%&x;9uhQb4o}ncba$>v_1q+T%w2oQHWCv0<(7v2q5k4pjMbJ+duD*{PK| zoPmwt2;U%1v=u&9M!z%AW6-&?N3E(&41bF4p8BwrLhkSQ>%w#9MAE%~3Z*E2}xB`?XsFlkR zZL<6IJ^*F0m7BzX@nv^Qv=ee_Zjp2a5A6o|t;rSrMbj9(fUwHXAoQsui(kpxtTo9E$SgKd6Ro z*=gAGY+k0@rf#U@cGO3CTL$(QXZv86jL)~UvwesLt3IODYcpzoqX^|EU_Arvh@|h@ z?Q-0(o6EE87W0Dzh!!Z(D03*|l9?e_>S5XujAMq~Z}iUMocZ)@R;eLkRd!=HlH10q z8oVm5iOrg7IGhvZ)N3?O;nW+nS&O8Ayzep3(tzQqtclEq7^zM~_O?%i^dL`{HBl=N zZQzsL`GyE_yj--Y02)l*X4qY}h~8s}7=CBj&8e4bQdB~FP6DN(5bt~nRM}w9iXlcL zVxn0i$m>LDH58$BSrlz43DMk#%>j_j6!^pG`X%ZQm-dT(s|-!9%hP5JSaUBdyD?+8OlUmM)_khHyw9jSiy6O&xdZal z6ukKtnx#j`hIZLDv5={M2K74|djVEEvpq{aMM_td82$Y@7ChUxN!txs`l{$r%_>(G zz%rG8X9CxgRcpzD0xNPOwzFRcs zM8+SSz(h}uv_^QK;t@o%eXuPXC0 z?DJ=1Bxiyi%MUdAoCUPYPdFOAocu-KcI+y?=-ZY!+ZV>n3u<5V-H^yoHz!)Ej9#O6 ziK^Jh^67x*pcdq5P3_XA`ANh3W3+xC_H0#f4ydCfL5D|t0u~7JyTFo0j5oAq%5c3_ z5)^tKCJKX^3b8#^!6GMC){%aB1H7lvr{B}c9$4{!^JJu2+P_1aCbQBYb|xD$Sx2Up z7HwrZ~?h2cojGt>TgiW zJcgbH!fkr39Bv`CywQexz2L0Jhq;|mv8$bxCm{`PM zZMz^=#8wj?EdkO5U&Ypce;XeCfsZm+AV9R81j0_TA^ZQHnMBav@Bhf>vzeJYckc7t zbI&>VJf3ZyjNRMO^JN~vWS$?k&>fUgcFFIflpB5e`!m{mF0D8%?kV!ddtXaS7LJY) zx>6P;CyzerUf$4Lw0vdDb+Z}tAZ#M(vWd_VS(k1}y9$}t+d1@JaXN<|?JtOx=CaL0 z{6E&{#OiWj?tJE6<{O38RXZkLW^HEd3vMUL!9I-In`u{>=A%{CgBWLZI4gn|2+A`m zXS@rrwlnH&L8{~Jrx2SDI_&&7lm2m4 z%@)y-Au8J<|C*PrjqrmG8qW(FmGK-<&uSLJ&TtSv{ryAQdpqb}YQbvfN!KW+<#m)Z zW`tAUYjyiUL@DcxcYZdT+dMd%c=gKmX8z6+O*e<=Q(tojJlPo~v|1#3<~j5FB1>qh zll(Ali^R*c?;52PSt6Y&P#_LJmp7pe7$xok--!W%#4bjoeJC$^@4OFjKVR%Lt661S zQhXOX_hNm)R~}~N@B5#IrNKzh=%AsYfoOB4I={h5IcN!%WH){}u|TSkfJr3JCeRx1 zVpq!+!7h8NoPlw)8%Q3?X)kF{pJ+FH#^E*J2>(KS$2Z}nS|mwW5i~l|Sv}h)%D3Y%ZX_|}6CkYI1ZsvP6v<)i0?OxrR; zlcuLv{M*hKU4?$%c#ZBUwJjrmDsOnB;a^5?0fo;L17wZD^|oci!8Ak*k;Y$-hIECE;AJLnrZEIhLps}ha=7z9<~}|LTw7e= zMpy!1rTtx-XL!yJJf~&`S+<_~xt2Py#esT18w@!0)PHKJ--I8(QjBwYH{Pv^>Tk{u zl|q<3A(T^val%S_AA3MD_5i~%vfugD03Ry-;!y5P#vp6gOXwRa+i~Slr*k#~=Uk1R zy%Cz+qU+2&HE1r*h8++|hd+V-i~dzYIYBY$Y-0tNt!sMTxe_%^8kjLBFvGr!c5ybD zyZ{g_m^U{|>+wqMn<;Lb{B*^ozU_>uN3>Hj>|-z*>$P_=jSLnk*fYwBn#*L5AT9vy zT70s;Z)xS?Y$1B`XeV(LZp25>SItK4`;GX9*^hMSX}{Lez6l>0mLrX)Cq(J?6Ppgx`vvSNese7(z-Rh33xm&gL#BNK%>o})4be<<4f~T2zyRMGQ zU-a@C_ZBBZ20^ntVyJijslB05crPMpbkp%&-0lutE&c1E95}g*@NAT6(#N`2OQ*3e zj_5Pfr=@IY#>m4LOJHdKjSbP;vPR3>eKkB%_Y3f=P zH`_^cIBBDEU7}qd*A3k|^3O7Wowkt|86Z;z4Jf_$^HaO@@+NELU5|A~oXUTxuc`K+ z{^ok^4O2GuuNrFk%|mU>WCdv1t6Ix`prti*?$dl>P^>KQm9VObh7b44&3Z{?S{}kH zkMv_CER91NUjl<*pdcOO$6bE)1^(8i~TPuIImUgz2D6__WM5Vp5Zv(s+Xxc7? zFU-;5W+L=1ay>?9Ilrn=utWzw=O*fD4Sbi*!VVClW+lPnlaE$jjuX^j`Sngg%9sJ_ z4ZH^QN3{5x%jfy$@T>x?Z5!;^mw{4z2J2Dj1eEnh!yiW>eFMcw)Fid%=lxr}u5_FR zK#oOw!u$Ox>hvTVctUQC^K#n)IU4?5l-j(gpCoJL%|vm=gs#nHwiU|Kv+yxp%O+d8 z1&eM$KLNGw>3r+w--K_7Kz{~v%sUqUJnQG4XMRXr6NAJ~oZsSt*KM8|o2J*ksvqq8 zldpA7ezLKXXkMJ0_XSs!H`D3+wfTP9pY@E0_&DXrWljWRrXoMwgzx39mT- zSd3nq&t!vD1~fxAYV%B^tiPQ$ThRJU)SJ;J!UteBv`4SzqJ9&$q!gQ*@1VW4%30rs zsLdtcgq8kZ^&uv;oqst5x0@{`6<%%kD%|4HWRjpzg(jX!`cist6ZSm~d4V`PK5aqz zuC9$q6OYVF+k@-4Bfo$JznE5>`s4Ib@+(Ew6Z6w7C64ssq7gFAkAUBZHe&cA{6{#C z_$JtvXQo1yKr_h!7vF@xXrcdWTD}Qq>;tV*y6-#vsmxt?kqnUWU_OPU9$sz6l@i+v2^J>J;oE#_pT&^gf-Nk-YX2 z$&*dgM+NcxnXph8SWEs-i6&h;CkAtq%eHj~%x2i@is~3fdcH_24?0)Q(bs2;z8Wt- z9qYLKTpZ*TqgmT%xj!Q1T!03x|tp1;>z=HJ}2DsdyrRrGl6L|gn4i>!pDx0Wd!OOqY^1-Ts zvF^$I2Iv-K=h69`UdnH@w+vC4R%#^>w)%ju$>le-H?5t|0b6hh^)hO~bd^ohc8|+D z_4km+klo8|u0+u6x$FlTCZTbHj~0y6EiRJfeDp+K>kKzV7vxecN+L<@j3K`F-xtLI z$=YlFKzt4Phq!L&%_5}ax|p}!)Sf7Y!~(}oy#1VY%a9;3qe(`LX7q`kQW71ph;pw< zc+;@rV7q~GE`t-M8AbFal$C0b@1(TUv`NYeP4)#2>G*Cync*;Y zhvUQiN$5Pg{%{&(FU7h9ru{t2_myhA!^ABy!~6oR75X!N!(`&hV38DqUCo3UH76W< z6uhz7GeQ{${}xp)?k&6&sNkMSheP@kWbXMy*s`2Ffy|ES_0*K`=`FLiZ;zxuN+xS&sNQ&I&KgA5(T=Z47+0n`64= z$%cOOj!YALoy%^v_)0wO~9#JLRK zq6-E=@UV_Wf9UKqyKw5_DfGZ@%~tC$U=3N&ZaM*BY#Z8#RTyK@-qytwdYBC`U`=(A zUhEi9CF+PheeXDh+~cC&v(94^sgfv-<(hjKvN;gG~`Nfu?;-)xSQcx#A?+-R-FIe?;dfH z4Z*`M(gpcN^y~0`O)djJTzeg8)q^gkys;Pd7S8)!?{!kjeXim>rfk;I?!o=GP6tMV zdO-5{AG+4y?Rr&03g8gm&4b)%4KQ|tW-}$W7WKpcpXn!rg*jwV=E9t4*ik80Ki-)r zF=dY0zH?2H0cQeS{yO|Vw6SxXGtthULOyXhL=pw1@Rl-`Wp>^%{vMLF0J9gW;x6*S zZOH9ClCQO~09LJ8q3P(s4Y-p+)3mnb;yx|(b@)G`IVdqB3QqwK61_rn&?KZ>hmkIX zep~i}6tytPkq$2&Jv@?(4j$>8-}!&X;G$YMu`q*XM~lgBb&z$}x6ngxwUi^4G(m3f zE<~(#viuVFm{ISS(5y7zd_Z)39gb@gTN7oh(0F)~1I!UV#+l<>yWf%i;ZCoAHDqGF z@PRPRr;o)5a2;|sYwLx7BdN{NCbd1{m;4^z-+>#u!U_G)6`MN#t{Me+8TRij>cY1N zA^#*eVL%W1*%HsPBch{hW7&{-@?p$2an6S-t4P3{OjE}L=L_CXiVG2Utw<5ET0c~c zKg{yLE>D));N8l`ru2u#ws5Zvv8uQy*%jkmj69Yf4wn%(1f2vaBma!s_jR={pB{mimDZD6* zDO}ioFN3c+#0>j#3!>lhF0<9*%f@MkvUVeMqGmZ0p|Ky^WwPoyPa>!Ji=cNqBy<+h z!mW;lMBzrm`}ak| zF95ImFz+Ayi6HyqIa%;jwYNW-`#AN^vej$pGL3FP&(`({_Mn%48MyZWnV}c4ew$B# zzK+RSbpL(LcTFf-P(b9;_6PPnSMM9~|#|p30vT4JY=WRQEQRotwca!O|A?vfxj_ zr-zIU1cNgc9Wh=jG1L zupw9vf1gpwNM|^|Npaql4(nG5w!+r9E@N`(0*5Lz8EwKZ!Ea0@Nq$1ep(0Y*C3uU= zM4a4&S+ho-IB)tS5xSQ0tn!}DLZ`W_@OlHzTxiVSFRw~VKsoNX+6D2lsfA~myJqFn zTbkvA<#=Z;S^z0sIi2A@Hc0Zi?L{23aA2^&xw})xw9#;VA9ND6Q~yoym=gsKoHo}2 z*fCnSdkYul0fR17S#O}6&Rsejj*)_G2jPxpWmu*g?Ob6Ko(tP1;?Dc)-w( zAH=xg#BA)`>Hh8BeCG_ONj?CJ`E0Bxb8?D53ducrY>Ny1wg?_ByST{wc3%`{BGguc zjI;8yBwH!eEacvf^31mp1Ce?cl@2Y~?srVUEOl)6p(P@hKLtLdHI0!2G>klZEnh^m z!B2>8@D(C1N-cI}vg;iU%U(hHI^5HDk472X<5nhEJVaZviAIkPv=_boXJ5JYc0Rr* zTB2Pw;%BkWb7PheTU%^(xj15y-8mc2-0CTHcc?6%Ww5E~hze-TXH$hx2l9_?F7lND z_i37BywK;M$a6jcX^edxZbD=+s_l1uGhCn$Y=ZAbqKUt)O+Y=PIrNV zL4-?V8#G}`_51X;5ZcDzn+eWaId-GAVcKM(i|rBowmTjELpHK|T-kg?We4?@jyvQ{ zZ#$wIag7fh!MEkj@NtH-E6sPQ%yLeH!)FqMo(5}87-~g;bajSWFOv_mtx)ExhC7=aNmyil$wWrRyW@|yTEPr+=Mwb+Gr26 zv=#b<(9MYaUF0Lm=Iij0z1i2y%QTD#jiaTF=4hz@+Ed=1bdA=es4*Fo3p174me(pw z0z?&`RO{Qnz&QpY_>H1#At2>bT=jeu-G>!o2hK2eypP>aqL~I{siE8!%1wJJrW>5A7ftPkInX&juS*5DrUsIvq+@%#rA+M z1eA(<{&NoZZmB5ro~mHWAqRs_psQJ05e|Pi5PX)WBi^Zvo_;srK|~tF$8PoD=???) z&^ylxqPD`}*AL{u`WHH5IAejwIB13jmYSeRIJ(V}Q>Og6Bzm&s(rTaMQb7Ep+v1sZ z$&tsqjohIyz+WCDQ~!V8ZvSuJzDX+!a72)qr_r>e`6$~tZ2k|af$Cuz>~+Al36TEa z;A`_zoAbPAi6;-wxq}6Gl9Zn_hf;?hbL4q8x?cutV{J--$C|pMcNjzU;N(0P#}vhR zUUEBne(b@#Omy@ZM2p8V(d4PsR@*w@!}YV*VHG^D{ZF2_9`~T*E;*_>uHf`>MVo5; zZV{{0XsC7}jvAnZR?*|L4c_Lnll>HM<@>1UT0 z&w)Nh8)AzVLl!%QMo#kP6-yq;8`F?vd3`sI9z8$QibvD)u^ zmQAHq&!?1x{`&0f6(hS@0>!%L36nBYON#GI^%UR9qz}|NTohgaZ|k@qlsgndasyy< z6*F`PiVeN(#UH3*)~&OjUqOB#D zw2abxLw^h$i9Ra32QNJ|qjD{r4r9zB+iu4iH`xN zO%(Y)TRu>;_fcBudmk#pf6AXOQ@xbRv{a_0hW>|kFZ^D+%LlulQ>nG6^fP!dQp-M` zvl1<;gXK7OBH^T(@{12#pP&vA(T)BPJ*g(_IyjGK(fNw8?XNib-&11@?L*7-IBf@r zS_m9)@5f9wQG2AOhro4RFWSIkjYO0&t<2-^uQyWW&j5 zJ|xW+oD1ej^i`rVD|%|ltke{|$rEqYQkH8e@mgL=<#tMs4J9hCXm4(Xgh10NFPz;u z{5|<0Q_HI6V2lJwiI0WP4{*3*T>C|qiV=H*Wn&Z;RkbI(I_`Q*2k>-J9Y_bTT+ zYi&uEtDdcGetX4*6*2w1JEmXgfp0?3D_F~cA#By|XjjF>x(8#7bH(Heigw4CV*+XA z(p@UNM2o=j7en-<_nUGOTsy8jUh{6vziZHnmuWqwf?hGcK__BS$+Cw^xK*P^MJu=Q z3X`t8A-bt-5v7S^n?ovAU(U55t0W0fm_YLnSM00BIYyxmO z<2YY)lN1a8@eNVUUEzYk33Kcvr{KxA5p&5UX=zt@`e4dzdkM37D&AL1tQp81d^E+4 z)xv@gg*d(iFJ{Hr$@tkgomwh-p+kWk+yD&AGj0!w+J0^%{F`xeC`YaXUhx7?5RTAr z1@MlBAAnN`FZ~Wz7jTFRxMMr+3Fo8&*FZxV`9>IL;@dwn$VPB2K|aD^dL1V4Bl?{g zG%s48F%qzq&vdvM1rI!l%E7^?+k;s+sFv`GDtU>eWPp0#Iso_`qBSy%8UKWi8s+v6 zc6MF{FAxAP(Ceyw)OIPrYaaryvD8=k{V=l_l0wc=u1&!A0=LgIdoJ9gtk8I5=hoQjb&Gvu)`43(&RHAVw1@GA27<)F`4pEZU*enRsXr@AU0bpHfz1V79HGkA4LD=x*3lVHS~^&?bV9 zD!jlfH)DskDpBmO#}M;U&kuhLBCRn!HI>cU-3w_RYeNbXY1Ko9 zLuF}eHNLYWRk3LILj0+YuR9U1|p~h+;n$k?KV}Z&Yu9mt_Gs{6=YcvlVqy z+9ae|ki(cnZ?fPsih4vPLI+~`*k&)RM4}NW;-v(eUEd}~ay zbtEV&q6%WRY=|CjX#mVI7;S;dc&zA~d`Z(du|PZNzncTc#n zw^ay`$I@Dgo;E|bo+_f&f{$W9 ztJL@)NGPWmLaB1xv&k)^L$!!*U*`@8E zPKfKblY9#A3NLR%3gs&7smr5$kO1|hV&|bK$)!C(q)~p#M{)uI{zB-xqRW5F6X8*N z3`#A&yv;#5mN2n3L0LYShxuELGZ;vJE-%dEp~9ZZyuzN>xTQoyoypkY=!|Q)MTdr3 z1bp8*&TSz&O0c?<3sN1pWJdfJenh{p`vvC0kLVx5 zMHbvIAiRRB9lrv42YjW;ZGe9qz(a(u2=72bsL6W>FA=UHT1mwHSiqqnXIvZe!h%Ag zwvR$eyp|F-lwt+u_@u=;+v*byCYqFcG|DvKE1ilwTQz@%2JAZ=C+5D= z2H>hJH=mN$204Y+OXwVRXCN|U2DHzOz^UNYGX&pw7ptpVE#iwbw+m&Zx>N65|H6;! zUW0x(Zlc zj=PwSAntN;CjeI`HswL~Pyh3@2-Pn(mEm;U*ll3Rh&Qdmre-jl(&t&0Xp@w7NTVFg zQ%xpX7@DPEG1=^mRD0$hUz*;Pj$xp;&y0^vvI+m z#WM`bZ8#TBZJRLjy`mXyc4)%~asK>X(X_Ui&S}o2nw*O~;JpHmWA!L=ozq(X<@qt9 zrFdk$>|)s_e?l(Nx&J7;SsfKXtn&H#^JlI+SSC42CiVwZibs-YC%PF?XzRx`l1ywP zxN-xifP_ZZuHEV;?wXa=aTK&F_)`uausvTDx{8U5->ojf?_Y>%Mf|+v6qJ8QcdM_# zv~Hu?$UQ&Xt-j7Zf!*q_c@Y1D2-hHhdP?C()yKG4e^mAIq#wT~4bam;$|j_YXxObT6yNS*?Cmbj zlllYwN&U4El2w>KJE|_^mU=FI<{VkiB%%J7F1GC4u8ZMYq9OE(alw8Q^yX4LEM^k%#->o6%M#(;(0f2kjhL=@ zzNITpz(`pGJB!nV%|gUr6&gOo$dSg>R8F-q`%++irZ`sucPkrf4cb1)6#q)0F+a`H zI1oM%N!-k{2^K7#5sg6=n)$HZonD>T$h_M<6?HTdI+Q%(4))xbXIb<~Rc+DQHp6C% z2R2;zRV;ySN(t+EQ#|JplyQh{an3s-@0{oH>fP#b7VL^;3|e1^|1No}6ENecjZArM z~=&vAhuuYYg^#LO*bAL>$9<4iiLCfH~^%^~Qo zMTDN67*E#C*-_P!4@reB*kwo%eo4?LL=tWwnk)u%ea^RIujfs*k|ZezI2c=^L1{6I z7%oz7b7%tc8ZPb{Di12_d}Vj)Ekw&Wj|kn=&K;de=qJ%R z)RP#rlHZW2TkxQLb{WLKR${8e@tCS_`}~6Q57m_7=Hu^f82x^@}M1nKW^VQ zYerjvv)tJP{IPG=w6-5PZ+AYtNG}I5qs;Skis|b-M;8VUN*e#hWo}sm2jWFmM-_2M(#bAnSV~z7J+o5Umcw`}! zps+4%&s}jb3?3}+LfNc)5#R4RSfSVZ$~A4Vl;ou~_1?CEWIevKSLb6sY<~!9hR1Zr z_eLyr2uCrjOnpqZelN%O$8@?H7#h=Y+L$t|WQ?kh*7g51sx>${pizAvqv{wM)%Ueg zbz)SL_NO+gz+!iMNDIBE|Ju?pYSw?QhdwZ73b19pub6ig@uW1$`PY7Nvn8!S8_PKP zd;P`ir&^t9v>LD~tau{0jPuZ#7m5)MJbt&2WTJ7HXMM!?e5Ymx+sQx)*;BpR@4%>g z_yZVq!b>Uq;F;lG5^kY7+A+dB5_g$!+B(!~Ifxqb-4hZb!u5dFDgU>6m-W-W#tAO9 z5xiRSeM2?hftuHzSw!%P-V9?ihWa+Dm2kF9oYZ1m<2z{gA?!~0%J}(4bu;iwYMZrw zWFy-ZZ4t2^)7ofNYHJc)VM0<$o8J4IzSoMI(sT$*)kax|Sk!Gr4;dgOY;Dxx)j9;P zqrdk(;5Nnzm{t1-W<7ia`jB;o*;iaz{Cgd*-*M@w-|OS}ZJ#*&jVk|ng&A4G9hV;a zy+I~iKzJczGjUlK&`x7D8(Z|74}>$Lbj?BBI0Lj<$2118>Q}2dd~B1Z7xj{F&)^)- z@*$e8bt9w_1X}7bmaH7xP&BE{k``QKg6t=_C>;K^_B|*z#K=26M}TDf^t=i$ypK$o2g^)B zG2D3TZ6%Nqu*sfg4!Ir4i`C; z&cJci$eJoBkG}UBChhOGBn6be2Tq=g2j`rhKkewzzaCO2m0w1@1cJBd$=yDKHc!3b zw5ZW}9QM}1B755*HQix4ETvruPgm)_03-iTocz-ldVYgGb7gmTYR_eZ;RCI{Wx(cf z!y{4$o$fkSEN{eYVkDpPHWNK~9JEIS+O~&?2C-SXMP^jq8AzA)abF3l6l(%w9_fyc zSmQ2ZtbtScdbEtb`=$2Xe5d~1O9R*+vDJ^e3kcxe=#VP0Kdf| zVjm&}+ED*A#0`4_^-pskCv0D01s%=~;k%^6`0u+Mu8PuZ48WgU9W;cRU^fU_q3l9< z18PlRXfcX4_;t#^sWAmQujfM zQU^{hdU63y9#ey5iWH~M`s`Y5)_?Q%+gmSP?DcB|P|P@J%PH3K(&AzIS5ssCV-(mq15J8VEeAa^19~(It>fk0 z>Ll&E;arox%{2qLDCa`p^qIJx!o(P66%?t@EHgGtQv^XWl0vnb(olXpP=f&;q{$o-jPGkRQA!}_TBHB7ib^x;md(sNFV&*7q2O#Bj z{+Tx4b#1fKK32uRES#Ioa`t+Tstb4!6s50XAY48OZxGdMRH0s<3qd*EN1WEY0ZP}? z2EzAX1e zwyD{CqdJBATTB?gY>n2;<`(Uq#=foy8cCo(vpMl0iQUF{dLoV_z;>(8ijFjtD#DKn z@i~3TvQxfYzDs6^^(&DV$fjtx0-Bb_Hod*NJSqz<$+m)*&~}`2pINX!E(H#L0zPmM z`5gUD0c2Fp2mk$Z39-5%{ZU8xbnM*@FA!7)nkaAyih*aJv&M2o!l)Id09K*ImK z2I>DZs=jWRB*8~BVa4-htD4~Fa}sDFi=+AJ8ZKQ9w_hP&d8us$%M(K<)w*9$RE#QT zjMNwYK%*F=C(*;D4!Hm&9*@xf%t87;Z>aRODhqtoDPZSKZ-b=>c!ntkE&J8X^0o-I?|`PUBZQG=5J?8gDv99p%yAN;H0mSV^r247DOz_B#8* z_u%PJ$SPl3pNZ;2B#7uOf9Wu4xk`}hn-F>uSgX#!FFr7YeX(yEo$r8NXT}|K9kH*d zEX5tGV}n;TE0^Em9O`P?uW`1;GhV2!^TolRNnG_IpR2kNXOW~0`=icR^;1d2hWG_e zj}a}!&+LzMY;r}Q)haz9*(*YZ$q#!b`&icEnyi|4u~V9RSNg7mJ5)3Jb;Dq-*1zip zH$e}}RWhV8FFMlK;N&R+op(pi<@t3*1#P&Nv)>IHR{v%ea zarF`SeyWA{3)+X5tN(2uX0>Bo&PaIEK(OM&^Kt8{T#jq@;75LzwRCt7{u|Z+%}~~A z2DsQgK$M2MDna`n3I8mbk5ZmMDR0(b->u)?^L+W6i-HgRz4LCjs~XZGw+k}IyWAGs zlcb{zVqsAm77DuhF>@J1(#g z->9C2^eYCGB*`)a#0rRnM?|k;BpddZFWM;mf5J-|=0LoTZR#Z8lP`czCfNdMM1|_# z5Zydw7}H4n-9Q@Qndf#U%WMbwLIFH1MU@}IUo{)#zwek*VZTM&B zckw%ynH-PbSp05Y!>RsnG07x;vQyoI)0ZrB&W|;p88O+0v16wgodGq)1{sO7)zyx1 zOvQ;u3bctL;n%MKFV07w7~vtbe7@SsULhRHVQ)z7{9-?5X2Gp#mY z$+)C6(*22p#Ep1~!LT?0nrs>C>C9`2^eK0wQSM8Tqu8q{-Ge>HDEagdPeQOqEa4Do>3C0gL2MqIDy3k_;?hOV%h>#tata)SuWN+$w>ORS4&4y3Z7_W5ducal8lt+a7*Sq8t2IP{w{wUBK^LbpIJ09G#y?x=jUa@nr3}b>T$f| z+YwpYs63_mns2FUQ13?M^Vm=W(o^Aq<~?o)?xQEj$%imp1aY*^%z3U!u448FLMg!J z3CchEB-BUnf3%-?0oL2^12pswr~a|uUV?}hOse7(K|*W-l>;A(Vj)G6fP`%W&ub1= z9ZWYg7?s`##WtZYU*w37!A@0qopq&B6~ijuU=64*S`FYtCG_hB?BVG+GpH0N=cI;y z1s{=y*I3TT&`3}Y(W%x@%+$mVAt%0LKI~g89Rc-)Lc`{EwcbiO1L{jw6Qttfa(Yhm zL$|+q4%N7rA)YC$CQw@zYAUZ++c{aU)4L5~A)+R4DCh9_`XSo!{`?W}^>g)!qkSJ+ z6Y;;@%If+M!@4irqDsroho8P8RRz?=<)qj1>YU)RK+LpbL14R_jzv|H^N62UB~Int z3)zFA#4q0H7NA?jP4QF50#6#0wA7OFipi1w zkTp72+6Sa6QGIx#P2n82e|Wc;i9_RHMSor_x5$*1GL$w-OGA%DJ?|(jeT-z%a_LWF zC0kW3zVWqEjq?yii|P#?XizKBHfA#@H(VhNk`YppY*imlk`z!~zVUJr${5a(q2)-| zN~%Oj$?`Zk1MkuDi(G_6TAL}oC>j$%$AvF!Pr|iHD zE)J-FSQ;549D^jJN1GAAah9vO5(J#(F<{g4d0Gj&Hd4x!MhzpW`nz zsU=ba>}{;h6VI*0lM^;D?6B22b`92(M39gSd|h@aXORIBF4=MY%>qqsWDGUFSdYji zg7dZKR;k(L*nv4{=w-@*^Wh@28LB;*u{MI;N@-!9L0LXf&}^#$hEbnRvOK}n)C8Q! zNtm;8O`BXlN<7r zI{`_6jb|cutdeIOgp)|mk7y~Mqp*`O_($66tP~}bj2Htk6i+?_wi_#UvF=Kf0TIM{ zJ6ZQ)lc;W*Sg61ku`gIqq zKw{SnjKr0ch+~&jWe0pO1%`Mcun=)gp|1eQQdiM95+0lYy@l}Ld%#yV4MP6HEd8t- zwg?`AXQ?UO0 zPPK^(y??DSD1RLEVwSQ1Kb63O=oP0gUPPo{jFTrxCT}x;9Gvb`I;ASm&}e5PFdD~o zvvm?qUG#5Wge4X2GF<7{hk5e+3OH^l;2~avhs7E^)I_B0^8LWcSdTO}1S8%E&r+H- zn1M8NT!_Z##{NTE$|hJ^7}1(d;+Cq}%LFAx2&nU=*kxRaL25br{QLZI`_1y3KKt3< z`m)c`{LMQ{`_Bh0tb2jsC;knv0vqdZfX_$tsT`J>y;rIeeL~&rK)qTn#Z_Z(VyR@TtFgKG*mY{9BYaF(n&xM4Hy;+-F?`Oh=L27MiV{L;z|o3IA2M+{B9WRy#* z@<^O>AV+53vJ-eg|lepzobCG-9=Vu3cGGbm#B0uhs8<+UFg* z^Yv{Xx_hMW+}a??)`UQe;g|>TlwwZ`X`?Tu!-@jE6*+7(>(w``RV}gFDtz+{!O%Ql zTgcO}-s{z^)_lwl;X$&5;l9yWX%AzrB+SagB5+Q%q3-!LhL`@jrue1!wX0sbb8Y)e zb!%%8b%#bew_hLO-7O@mrMe#$5w+(SVGW*Xu@O`}(0@*YfO-~)Nj^&b`dTI2*8uOv zd8+BUE$YjX1K4pRv#Rw;4h3iKfWvyVUi>7+5F-DSLEnTkC>h_gW3)>2)(U7XSAGua zfo0J?Zq6|SGAhdfvE|gZlNw#I(Q3>&iS&*0Deah+Rtf*Q$BFMQh|O2tJ-n* zh)VAdY5PBRQ1tFEocErsrryuga`D~^)kg9ljQKb;KLwDnSR{<^1IAy8(R}B7t=iYe zv$g|o7?g9-Mh$;7sfp|=_Bp05#3+A@eafFhD_5PS^4>X3*hW}Zt2WvYA5Ge#zJ?ui zZzqp5qut}w8ux$D?t?s0TR)3VKfR|8R7ZPE0ai1O#7BO8WN2o6ectBZPl(Gk zgW51Dv9TtT{v&v{0G^3XN$;Ri?*U)993=?ZANj|_pEwVV&Q0y0k}8=&nbB9TK}e-I z5gK9SMQ(KN=+JL{Pq$`cTq?!Uxk6|PFxPzp3AqbH311}SF3@VcA#Z%iTW^*A`kuFL zx<$gwJdGV`RPN7+&dGful$=`?8kt)eO3A&g!!M7{eL9qy3-3a?H+8I%32%*s4#6WD z-b(0GTd0*@^u1PH&STK`N0?vc%FMo>$z*3+gZt5aKgD;ndssnB|JLV~leJl^M2;^8 zEA)44jJ>;qD>q_}$7v&YH)L>Rp*dfBPjnNa#B1w)qFjZY#h^R}kIz_*N)`tsV-F&H z%oR5*7eQ+QK6Gg3JO$<4Fpw+Th~^O8F{WcdU$#6#9u2z3D#N=~bT}{VVPlc|uUAH+ z-D}m6xSw!E#D9u#$dw1wINWCr#K@EEZYTSZx))Sk9DPXb+&eT$^RgtPq-< zW9t|T{IVExeyi%ooM+p3roQ7Xmg2Ql=2ZPHG}o2zD`=~dFou=PEE9eFqV?O=jahg^ zv>^72V6*wC1*cE# z9IY>)1v5a~tW-bsjB4Jhu4e~MXQ->;ze=qBBQV5Q->yZXg#_i22F>*KR;v}<(HquV z@c(z{s4{k_$?YYFvIoRl*T&y~2239(Gp`)Lj9?Wi^=@MneEYSpcrJ5$gm_$LP@ zESeZpUqCFsAh?BcnP;BuBn(8mF4eg&>cwsgz6_Ak1%GjLk5gym47}FS{kv8=Q;n*} zwUzXqmF&Y+HQ1a{_1~$4ZT-@>RKhroCis27Ra+B=p}kyCR+6SDR*CR!$}-J&#C4~c zZ8X~aK$J^4r^P+z_P;iS_y2+j58M_j-6gM&i32^HfLyaRn5@U%Lfk>AJX@obM@kMk z4)80gpG+*Q=C&BAK(=Bh{DHPtZ2_0L)p`meN+WpP&X_{B-$*1CAj=y6I zEP#L3K^THC1Gt-SOs z{E}*O=EG=->hPQuo~BsJBz46|f+o$dvBr4it3Cs+2IYqX@N(zZH4AgVGx`R`%t_2G z4#j`*X18F9&m}(8q)bemJ$CHG)Vov1PG+NWAJFi#9r!sO_&FB%+1&9%;OA+fF}Ysg z=NhcWE8%}cpjiVB^Y&J(mBt2)aoP~g@|Bl0TK&Abp{}qxHmx62zzqK=plIqo$Y$3=LDCeoj>dw#LrG+7NiCBm=K(J)iX8Qm*aTX$2 z_3i1HYex>RcuO;GH-(yTBGjY?;M4f|bm&(enV+_9 zLV|Hp0uNPA*tQc8h7xotEmo^z{T0WqX!uW2e>41kO3kVtzb8fd)^MaeEii$>Lw^vI z?dqKg%~XONeg8tVl?2t};vHwGHbYM9kvw(bMBznii90>02E-fAut^s{?Iysdec2_U zT5`t2pH5a_@)&_;DPXW84EANR$A7m1g%SFcugR~%LU zz`Zz++sC(icB_Bl+iSIa_J4Owuihtbgp{Rz7yVDPj|`0fmmWR&o-Ea=;T6#7QNvTQ zpFHlP_#PY8_qn}Vsyi1*(cb@5dp{07!*$8~#$J~xnfcA<4l*854;O90% z#WD}=6OnVZs#d%gScVoH;`KQq z^B67PXBBaXGix6or~D&IIhg%Y$92eimM=tk54J>HcGnR~{_KGNd`?~{R8m7wmYSVLR=Q+9!47n7Bgq{Pq! z+eoaUze(mPGee^@ZiTes39$qz%WX4cEA}rcWv1Ltd9M|ThuL)|YR%BI<#3a_GLKf~FN%yu(9it%=8TLOl<$S#ZmgrcUlhe2 zv1FiR>0l?G(GGs(_KeFKzIX!mhEf%zZ)Su$ zgMNE4sBrLz%oH$7RmNjM^;z&oUzZ!?JoVX$?sOi22cRo_vSXKA$4us9G^R^n#Sv7W zET4eArwKT~oxW}-o-vw!$b|WkAEq&!D(^mil38V2GW5=nwPfQ#m>CXL*i?QQ84Q0m5DjNxcPHLLsuDs$Ro40?YyD!%yeLVm8q}|0;S+P* zkzR{Cs)?SBC!{J#l^>iHFg-E6C+j<~8UAl6*J74nzxR84r`qp=gh$`OiF1JdZ$Co{ zwO+_6>Y%dQTk@RYaC;=E)lJmpO&!hZXN=pgKT)fm@ipVLz8SN+Y637TJeM2g>*Xw& z&eH-rn^jrLT=yEr7~1kOy#J)Xypv>f@VNtjr1-kLLG{^QZf;h;0(P#k(rj>>Ag@cK zIjSo%ov=6!pqfdFc&6M|1-XIJv{(WK~7Y>z2hrrhxUwEU*ctGlM48C6bhbA0GsNN+$`vJr7|0j&RC# z_F_4(9r+P@0@(NAiVNhgkV7)T(n7zj=<%CjHxxf5r6W!z$<^Y#F;ho$u*|Q{+iwum zn-eTUsn)56j+JVaxDtHzj+oSAi>eO$P1={q_>yLFu{3H5{MTfhIzJ_YtG6eZHR@=w zBtmu=0NmQm{Fp^T=qUJ>Rm*O~$qm|IITLu(cg9!f%<@%Sj1i=Ym*F!fpq>rTN?D=C zXe$M@WZgh`1JRVYHsRW&ekN8ogFgT@n1q@2TO06n6(9BV1+Wrz{s9^a+8y*=!(I)V zJH>U)S+%KXAE?CgYpKL&xV?|~qBG?JqE*$ayRGom^}1#eA&DududGmzyo_0SFp5)i2`$P~pVXf99>A9RIIWq_ z#1X)LU!x^w#4W%wAobSO6(t=m0IV+QC&@v*>H`-;!a+!}RqSocABhEJ}WGP=`_Acece`E*QEa1+60~c*C2&_qi!># zkpHY}g#X3Nx^3#-{B7z!+cx!pbsOjtIxFAJkCXO&7`(iVG->uh&%mfIPBH*G#a0LU zR(g4`8ZoxMV0Qim)krIfVo6wVerJKdS4+r(I5?1YIl-Y!igXH-`Z^7h!kzD9*JZ$6 z>nGOw-RX4Z&h4~W{sTL0*pjGVLw(@y*Xtj)zeJ2ei6xQ5Yy zY5lB(@YwUJE`KJe+qxLTQM0yp8v!Pel8KO>S!uLM0v7|G3VxY2sf0}-8JyVqEBHAi z?wk0OSXlD>1?PaUs{J;}p zKgR?)++dib9=i0B=1O!7hM(Oppe6G}5tb;m=)<+LdI4_CMk59^eFLdC`21{~fQqe< zHYo+s!SEdso!cI6i*HXg`1v?Z;-~Xr3GiA-xj!7--c0gVLD@g3OI`(K=b$cmC7w%{ zyb8*W!E3il3Ng{dg zr`3Yed&O22uhm$CRt<(nV@y&8^B~o6#8@EJ(K*gXU^B`=*GWE{p!Y%IXO6`?=&oW^ zY4r;o37CP!A~aTFMu1MZx~e!NSb3UcR3DFauFoiwnDPtFN2-py)n;*uIh2AkoKaIU zLgS{)=*WOvCtkA2{oz*;y?Wvismfwg^ZKD5U`LOPwhwW?VUQq`+l*48EXu{(Ia{@Z zvlje_hGWVY6(=}0vxGiDA|bZgV2|3MKEnzi2ci>~2KDzG@$T@3+r}Ucgm2Fn>u;QM{RLWzKS#8XSJfp=&un;A z`|6KTy3#j4!Z#a+zS$4T37QS#13TMy()&o*7ul>PLp!K66x+R!A9FRFL%^9{;b+g` zdIGddyJ|~5p+1IdRx<0Y^+n*dE)Q+jt)!qA!9Q{FCr_MDNp3%HOb+(0s)5cV%KaP> zd2o6O-4vFQKzIv2CN$jDX2>uCf2Bbu0}%%^3Y}bvKpvkc8?i&&8m;f7>rIH2NV=Di z@NxyrX-DIfw`cOflDz zT~f@qc1dDw-V*p~giON%+_qzx<-+@dwb-Ve3tm`exUf=t67lX8)l{!6xvWLj7zpo+ zv_MORDXf;f*yvY-c18S;mT2eHr${p!bW!{ySRTKkCPJ%a5^Q89PEBo-rcMbR=*~!* z8~Qm;s28x80kw^Qzoi(%)Hb8NDrAB7yfJ4nEUBQGlJi5@R|{-0^7i8-qhcu6kA`yH zHI&N{>guFYGeT_2ogpe05)dodYNxgpIunMz`oYjwHxGT46$*8dU*OcXTb$p(vYYJ9 z-Oh6z)8*N_hRU!tsPjeP0_VeJjTkCx)KFR0p|ajVtX9sfknQQ^b$wgj*$%UenP3L3 zzec+nf*1ID+9?+f26hUYac(|n+fa)&U~&E)I?~%z7c;_E>}8s!L|Fn7}VuDQ)^IO0_GzH)b z66;*K)ZeNmh<&RIF%u=~dV~KyOL?~T>rdy- zGBye8eIb5w0{q*Lz}mln@6bQGn!xRl-3|`{ezBXPE`QMlZsoqJr=fq+R)x5!w}nht zl~;yWrN0iV^6kfIO`ga9;T3st0MLvztgp!)x8=!W=aYql=Z(Ua-c{ASca`t@C6RD4 z>|&r@@c&$q5p6Y%2JVc6KOTr65*D{%?MW7BKmPg3&DD8JcwPR=fWK+!&Ax%~o1mI# zed+Phh}%2@Zu3i33P}D5@S~LIw70P9fODo(Uy1XC9#`PQ^NFu~c;56RV4lgW}6HYvtNJKYATwuL~n~xx9$3Z}m$LMZ&lDz0!9NaKgzxQ+G11_YIi3N8tKp zpN^3X65*1nfxXWX{)vQtGC=9Zwy5-qy1+3u)b{ja8(sm{LyR3|@)f!x{5TQ#aS$;% zBI&vP3RjMM0KkH}kOvf!jSRKy< zw)H^Ei?rU*lC^xgrXRT0{^;fK+ClqrYF%geC;R#Il+gAiI;?uvy`O&Ue5@D?3;XR$ zyo-2-+e!Jm285xnkDgC~)_Gw!d{*I3UPfGJXEm2st1*G$GH3iB%Dhi2lMR)5@Y*sV zL2z_4hB8GeGoh`sno88$Z4OZzatE(z!}s#HbsWI1U4T9Fx7{<0+W45@RahI>?S3z$ zM{oPKt}{wSic#2FyQr$Zlpq2gawL1TZHXbn;=djqIbu7uu1mfY_^ByWqzGC*L8^ym zbD|;E_N6w{NhwnvYSFIJRB({pW#i_t$y%)@hd1Akd5N?8_d4|%J+}_NzkKIvtG#*t zs07R?_Ji}_gh90Tb$5w)Nb!=#I^^SG7ILQn3j@l(v@ao0tOJ#qs zxf3`;Qgx`aNITXs8|g(+p8C?Gz*>cLz6f|o7K0i*yajkL*VBjwFrrmxM@*iD#z=Ur zXkN$l{u(d#P+{E4TL5iYA&$`bLvLsf^de2Z?Uo&E2%ycvV9B+AZe|XZM0d|`Uq#Vu5n$K}1 z{2%-J!(+dfYE~Lrt^-Fl#~TT+>5qgL<98b{<^I0@u=~nY`e}3j8 zCqFS0LR*%dY7yPBNTIKJsG<4AmLxpWTX=%-i@3&zg8!SiHvx<4y!Xf7b7qD)z$j&I1J+mMkAP}HHmEomvpf9W>7SvSsZXfV1_uT&VhlM-{*S<%+lW9z0dReKhOW+Ih=Lg_4|Ie_q*&Z zYin9BwK?~SK6*pB;Tf#-h+lA@VvOi1tqEEm^Kp)r@AK5#cDD=mMwlhVWo2vMS))>x z7=C6rmLr&CTpbu5fOK5qu5&NHQdLvuRsBI=>59IDe--(!rJ{L~U)$5c9aL`TMDQep zBY=hKoF2h^J+ABUJgdv8My>wxOy_Ip$<~?WP<)!?^E=ZIDVnN>p=S^arw24a${%@X(dBO5vtn9TEAQTCo8o6Z zr(s#$Y3NdJDR~b^m-2nndpLG_;QN7+SW)8n-RX~`oVykoP)=4mZ?9K0#@p1^oc!}k zWxL$&*Wu4VW!t{!?h73m9cRiH)R9Hw49Zl(P0w~TBC0gYXW_(%XfE_WRgU>Ga6KLT zBg*aIq&VF^52AOR=Xwm9M-|Q79k%Si>%C&iL_eF8-VX1Xjs}l&(Rg|eZZa~utO@%#ZTdZnne-e!`;HngF~^;C z)UE4D$Vf!oj1rW#A(q&?gNfKj!Ag#ared5I#;&o8rd@zGEAEP8yOf)_U5ciJNxRTu zD*GjmFmqy8k>_29GYh*mJ#QvjT<@r`fw4*c1;K~vtG8FVrFpM%yRS2(SKH)1ep4BA zH|iX`-cIAs+Uq>j)ADKWw${5VhIaHG#4H_JbR%Y%pU8R)6W&)e&Np$qujp~s+A)i!bLB~H9v%V-lDDA@@n+m7 zC>pxXF(m&XHY7hj_#S%T^8F4FQRyqppoo4uEFD=x@)}_47YET7HP$=);^Y9haN=|0 z+La;sA8kYOf3%%a+Zk^YVMh&zRf#s;&N0HJDrb2de9R3FqTKu8uXd&$(7T7_wcuVj zKR1I~`JzqhugZF_waGTnE#@@=(^8gH!TmdgZ4z@7vZ7& z;z)&0Q1sSf$GurkDz}&t`(LcN&^jdlDdNg{tJUH;YCF?SH4-K{xRg!5#zA&r``ZsJ z$J4s1jE+{cpzbK@)b{AqI=@=_y(24q+MYA))U6n26GPb+$EK{mp!}b!tvBCsEE(98Tm6If_iU7vgPoh&dHyEIjAb@-p}0rRp4N&L;tb zm73z2YFLjIIp3{}^(Bk$#ii;O3Fm2sufrbiml5Zwg_O5pq9Z|V$EU-^OAq^+5m8;+ z|ExPMc{7E(v)NNFX#I=aOyTb=pEeYT)3;(|OGefYMMSRcpLv*x&763CwTegUZOH+V zeD(_rpqU)=D%VvKbyi2Z7aF0~2SG976pz|4Wf@}2=9xVyh%0Nv=)6hm!svtFa021g z{ukYe;Vh-tN8@0|IQ&<)NZ7EB^4x~&lFP;8OOPp~L$hMq5;x1)0jzUybfr!@{~)c@ zlAX43z!Lf(;ej!n@TrIxPwgUb=op2(*qx+O$Ru|?C?p38Nu`29cD{jwDC8SB_Iuy~ zIZ7eb(&w&Ol%V1uqrDDmh)Rs(ntycDwflH^`MC6bDEVybgV6kFOoweZpma8Y>(OM< zTzxo-p+8jO82ZCe4E^CKhW>CALw`7mp+6kO&>t!XI)I@&P>w;u9eJ>_sFT0`@H zR!J`Dz;R4TncIZp8O%RXAoNUK<#9en#gx*14=WmXKAA?V^Uxx#N(br9C}y+ub_>Ym z(1AUIM)(rb8`C5ktHl@q24B1%0c0X5uIHiDU_bCo==eDl2yb#j%u1xS^?Fiu~V<&3*Yp&U} zc~k$UYic*wY~FHWi%Ht8+{nuDe<6$FKY2$NRpjraU7qja`c`HJt^A%i#bnx3wP*R2 zBQ+oIY2Jgo-Br8QyWP#Zrir`XGSpyIev-|+!6MA$Im z2VH(95+1$-e&+QZPq%9D45P&zQM{Jd`_C+Yv;B@~$-5A}sBcBqySrGwH;3>4`@CDG z;dgyWJAd~ezE@)#o!No>(7h{xJKWZPK)3dc5z(>cBA!wQJsqvn`T^5M3|EdtdM_V} z^j^6m%f5?h$AQFv;M$0 zX8o6+qvp|B|M%$spR-Q$D@i+*PuNb({F|8dor(c7Hx9=n9FuVj39ZB~cxc8uv(zwmK{JuSx z67{xA`keYp1_Sy27{al{<(IOGo*eqc;4?$N9A17YqZ;=c$}bs;3_XUd?OBKHvDWON z{ll6$7tij$#2cX}48$Z`$;JaE=&yLF=xoH-DWsq+`^Z=6OSYeGHvJP-# z%$oXHkS($i=;=WR= zxB0%_W)?ICzijuP&psD2^t{Q{ZBaAfsqK&7kF{iE3Fk_68Lh*Ju1gW!VMBI&s9e5@ zA#MX_01d||Cb{7+)^2c*^JKaV80SJ;pFh#Vv32S)y_7*NOY*w$2S_b7_D~mScqdn|Qo* zRe2WCuWwSXe|>+4aKs*`azx!S?Om<9f{#An3im?`0qzF%SdO!tX21bKx$&dYX~fA1 z?P&@(XO-v9OB)P&e;PQsoYGaE!cwdOgB&QIwyNE_iZ5k_@jU_>9m=EQUTm~mr;!Y& z@f#KH25^H5R|xc+wd4N!Qnyjz@swY&}NXBiZJj&)jpfa?_v8}LO6CrDX%zr>w|x19QRARsyP;i16?eCwxado2uQa3bou z#Vw*P9(9$YE<=m^lyKPWOg!vzCLKQNOg?_Iewe z9&m0vG?)aQuQIc&z1)VpwU!5MXYf9Tj3C9QKHf%|!9L!Itk{+(Aq_Tf+YVY^eCoN4 z&?If-q~cQsNP5Z0r8^GiY7v z(+9*P8;{vGVhdV6N{u zL^4V?m!7upXoS9Gg;T zKA#_?Oefkx6L!kedm>`fkMV6h%Vk^!pjZ@**js_0@RYS?@oawjLpEKv zB4^;&S=h;TpOSERV0(EVj#}{WYo))1#N$420Zodv!>1(RY{yyK0WSw@;@%0^1BU*n z{q_EWyAjFfO2G!~m0Rhl(eE33R@J1~@H>Y{YldPvPw>zep9L<4=x&9FJYYkC9_(ov zQqr&sq~#dm?^|=koX3$9eG+!gUavt6MNI>uYCIb03p2@!Oj>+FM*H32ap1E3?C$V* zoEs4iq2pmHg;q`r>My1?Iq|K-d-eGv%B_~b!)f?RtF}@7rs@`oqPbKPF7oT!YJ~%i6O!Qa`YUhJ0XWd&NH0z zxIF6JgAc#^a6a~TZ>>1wp!f`0hp}+RN zpZ6FHzwCefzBPz>w8n94>{n}O1hxLwvHR88LlhSbSS6R`9#p=^=skAlB|{I-3Dick zCf>#{W`U?U_CuuPhvVMIwfAJT>w9*jJ=1!tf2_UB9!7gRO#VrZEVZs*kJWV-;K7SF zmkW_Z1?|o7E#kXh<`VjAw;b(BNcegG<6Dklq`vi)*8U9lxsGRAsU)gtSK8C9X24Cn zi6u!%ZboymK=nLvcbNbhN5vP@Wp}zfTcKx?LVD4`{)7p%^bc#ap5}x$n$*rPN361#iT^U=# zVUsou5xWtoX_g^y2(~q4zT@ya=FId>0xSSHzaSO6RrIef z(PCVRkN>W<^Rm9Ev$f;0K98G;O!G4n{aRqU*L$^y=AIBFo=y`m$f{F|ajUwVk`ri+TP*OoqW|<|y$M-bETd0MQv$}I z$*Q?z&}3NYH=mE%IdHjOz%D@@FO1BmV^2*QddgugQ3@vtGr4wXyvNhK{`>%}cnJgAJeXwI_syCl!i;!W^PS;e5{@x^o#~E*c7U%{LRH9czb<2ngCPd#h!#@{8X@5ZU`64iI( z)OWncnwt~0@|sI^MsRy>w37B2@g48+;*A$?I=_5_JnZfhl#f*B$Sx%LJ_mX#U2h`O zho{Gyrn=lD<=H*-KB1@>@0+rBwq_SKv}fn#(07~#+=nqn6z9JyhaW8`5j{#lJCe|+ zG-<2qO?Ck zdwmk()g$Lwcwx$vc77}IUdTI|6TjDNO~Z;r#H8FzJ89%^LB@r#?*#QbO^;3U3FQFAfEmUUa=+|tk7`8%x4n3zG**+MeQmVA112oQ!OqC@b9&jNP zE(1L9$bXQ$bm`KR2@ToV=A`qbK?7EuHAr`w^Vh5A1>K~PUNS9OIKO+Rqr=1U>k~DF ziGJ*tXq{PzP}l8*eR+MX$;O>0nh%{31NKW0(Zgx&S(Fh(-2qJVuXSimPK;L#c5Tz! zCm?dr=Y9@Qx{%8+__)jK;Z44tifod9pt(SuZrE9H`IJY5<-zC8uZLDgfCUY`vwFsx zzwM{lwtBp1t>wHD+Uc-*P`kJf?dt{IUzkvnRcHH~+P-=RfZfxgWQZ-wl#UiUz-jOnVC=!MEV8rdt=ZdJM2#6;=5{e0_{l%ce}7quGB< z&?&QU_h3B6oF#+uZGl#m=)+H+!d!$Y>rY1xk2Q80^SThQ+liId;qhWUz0=BJ$Gh{g z719PlNatm*2GE0ZhlcMd#hIm%{xQ>zZ*cX-=u>Z6rad(r<9R6tpnQ@{SsNf2xi82Rr`d3!|lj}(DkV8 zr+}Gc6=pce^7>(t;&8@)jPbJH@>2ze5hGOg+ju=>X@smr&T?0s6VYj0Nlve;3|EN_ zb=ZsY?iIXoyggnj2OdlXp7Sf?fw%pzR#Csh(@E;nNyvoo-<0L*#G4LeM?fq7oBB9- zXqLdvsSf8(oH_Kr1o=1WaPGvJcIrBCu)UySIJmu23i8@Z_WJsdY}cQyYnW1CYVT-h z!QI$A{HtDZTaDnBrnFmu_4F#%LrMSY8CpOsr50In9%~h~V9|5+EnV<@|2+4>HBeOv3Cm`Zy{9Z^g1E%qQ^j=KK1s^b> zy<_xVg6)N_AhSKyLbyE@SUfb`pa=V$MYIm>BBc`9h+Q_BEBt$h{I;zqwtF8 zZhKTI<2rC%#vMMkLuslxt1LBMpHqEoom&EA-W1VEl6yvt+tsKvk!5weS1GI1`{xJo zZKbBS5b?(!-WUo^H5FR);bRfEYrDy-ED|Z}Z87{0X5zR}tb5oLc5>3(y4_yITrmm$ zn2#do2RKZBIyk2({zs9ynDU^-CDL{7bkuoPS;0A-JCusb_>R{)w;oSduBT@?6=TmP z;2DBcXHi@Ghw?YpxW*wnN=4=AvigSloqlDlX~n%>WxC{5a@+yL_GArLt;vO04kPR} z`sN61Q4A@W{w7zPV?g@>zapgE@xdzBL8X=xU~82Ro8TK+4E!Y7%+ACWY)VIWhZRHy z(*yHRX=YdiCm=>?8AcgV4|~6*lRVoYA;6QwV|H&xKk2Kdkc;q~n;;ohI{B*4Njsmf z1&@z>yJk&(?1IzfRlZ%(s61ixLb|4&=nIUn0XTvY{=fa^+6uMb@cx27p;;TCp8gin z^xlusmzDA{) zd9|Kq*SpYLEQCav2<*ipw^jN#`oO2?0yLfk5eu)}_X>>zS;J9TwV|28Il&|qcBl_R zJHH28@pN=LK;Gjv!4^=8Cc zfy=TQ59O^vvzun{@SxtT?Lu5HTt7Q#z}Uv*hw)4wU@8P<4sospPH)3WFU?+h)Qj~R zZ~08MWx>-l_;a}}VTquv|URY?#jv@53wx&`opNm%XF>zF(>Lf#e1DJLvF7whQB4i1Re|hWvk3w`c5m$dvuS8s(Rj*QU^@k9Z z-mB1W=;{rHW~$sl*JqURK{~hLSt4eNt`}hhkw;DRHvB@-GP6h9n<&Jwo!LWWEDvu_ z+yx8Q8JhsRn_^Parsi!V>tju9p5><=Ocb=e6OtO0Ek;;ytFxHsr;>N0mgKGywdN<4 zi9tVXV9ubX7R;nUZO3M{9h}+@4(*_~m`k(+UMbF<$`s^xBK&E(2h@uA-o*)$CVY?T z`Tq1$f^@SO%V#s1+u~HOSaFV0J>EoCZZCy_m`n3yK0 z1Efafmt#$?sQeDB_wrbi3y~vdb6(!Bo2~Ub)Hnn+FNZ0-twU@3|#U6vcfvz^uxzgcu>PXPr)2FX-2Wh-BU5BH1G2ycNeL9L+eKfNJ-tv#{4{aF$`t znw00!k{65=1FF`Ds5ZdtM62W0z3;#dRP6`&64Fyb^20b#Ja5?bA|qq2AMp^ty({wa z2)-Lc=t|gq9mT%JlE`aiid-0Z75`sWsEpgd75&Ps$hW};QjcHMs86f7ux~_e8g{u% z+i3N9u{KQG;_I&p^3i=p6aIURtGBK9afP{lT^^5JH?*~6vtLuV-M=R|B40mzA=JFI zQE_&{W?b27T9I`m!mqhQ_*u^mNZjbZsc4ArBbh)Gpgk;2LC;x+@oUET)nKlPXW9(A zuQLCDoQufRXp6&-xX}ahHBsbTt=Bsmk!whZi0%@bggh0g(Q4SSZQ!23(*XRJMSCY= zy^YA5BkNsVu)k=q<#wM|o|d!AO?H2E62h0|EgwC{-yGd5OMpE z#3csXTAKgblCd45+f^)B3vc9kkX>lj+^4`B8rgJKfNWmIM$G#1OO?jykduh#)I&l- zUe|n$DjBhSA>XffF)GCIv+%X*>LUE&9K~xqe5(*02mNRSbX@0O2=3cDVuu{`KbIz6 zEp5To($rWxqou*WF-YYC;sS-RHzfR67;&nQeWViDt7`l|z0bsZ0Z<%&PtZ(|o?gy! zXg{Fb#rru#h&^f&k#iJ#wRVcU3x`)oVJYgmbz|L%Ck#v1K>Auam&dM)W+p?{AO<;) zFTe;(M$q5gLjqv}STz!US&8vU4AT0466^n`I9_`3#TO|r&m^oC(hQP~q#_?~r~G~K z@5kOK@4;}*r?o%DgGnM^_|4)&&H*g0>;gtbi z=S0|as%<0ING8E6&IChhT{Q%+<&fYzI4kxLNLS{z##i_g?IW@ce9=7hS-jofYj>YG zg(x;-J4DsMlvQ)r%`RI}rE}{K2Xh1#k`p|5OCw`_vK4EjfF2-l4kxHOgWS~Mu>{7v>SpZk8QkV6O zwnLat255kRTj)?(3C7-qDF4{mRdF6g`96qtl2j6x+7Bw#xI=5q$bJzZY_G`PK}Eh3 z$F+(gpHOzd3z|kQj-3&5TCM_Ae^C0m9yc#;GU z2n}#$r?M8~>@vlnKh42I`q8Es{Pi9{i(&MOPWmF*F2FYQsOdDMg_S0LD=P&271F+B ze6FfZHpIDfn!Nb$COXF4i^~AltiKC#IcYBzsWD5a=d~C?!c6?EX~_Sp`q95Sf|Pu414<& z5oZ$idBMA>yG^tokU6R=U~^UmD&bMf@%RPs{Ci5A6Ki$}o&r1Vv{-y)TN&j91&jce z0By8_w_v^S!MJ>fT;IriIwHTLaA+}jG0gm~IvR(2z^TpjgEKPt~NYJ0>H+w?ma$b^c`2h&&(sCiuBJGij>#=z_!YMpZtbJ5~F#=Vz#)%5+=C z6we!%y;-d9o-`*i=)8gWdGCd2#-dAiwh%}2^e}ux@+*9NK4*qKA7p%44n(yr2`a6Z z<=;iAO|&jp_)YIvYY-z1`!Axp+X~B{>1=j<2c1T*)X%oUH;`M1{Zby;7&)vulPWjH zN9ZAF8W8&phfool;rH<`Ry(+(EL^Gbuea4a3kieP`Qk9IPsaR3<>nzTXaQX*LnW}1 zxPG|Ncb5;Ph1;N&@c5w%M;$W&F<#hYZV1vn{7)i34pQ5zRfy!xYXi?hieT{YjMuO# z@PJyz6DZ?*{-}uy8W1&zO5#ULS{#gm05g_(;WhjWaDe-tS`7BkT({0U=;7FJC`Y1voDH61ejSh=Yf zJIZuEK0v)&5~=XTOgw(s7XM|Xwnd}P%h>#cPwa<|YDbIH^%Lwy#yL1Py~_2ls!!t^ z_IYg$OLo*Z?Ay`NaArrHA~_fP;v;zrGRQVTKhT+|r--Q-+cyU39#Cxru9^YP2$TYF zh&XubN4b;f;8}==p{E*uHQ=!$CCd-)9(=4%9R-qDGyGhyz`rwfST?G&PFkbb9Ow~P;$t}^+33A5xW!_85aAE*OtlhAJp}f@0R5jb*2cT%kod7 zviu`Rl)&g!vV6mc6fDBGC$E0{RFolVx@h)>?Z^m*XgF=B-<@CrSpQiyF_0dvn8*#&F4&N_#&vFOMnt7Tdjw^1t<8E5J#o_8@wqj!61Q? zR2Jt=uTi1ye^=wZS8F_SwZ{KDr!32lp|-Dc!qopb4R)W{9+wN|A|C0?V(`S{W?KE@ zXWr^hfkhYPs35303vPw<7~>^b{seH2YgLavq1_CQ^Km>y`Yw4c>Y?aQM=G0K!}9P* zoGO<2$WF@8UXg!mK5~Oa&47WC;VKtJ$*Dyu2w9#u5-;O6JWU*ZO0~sM?o0o&Halt? z!q-WoUyn(xm|I}xn7kdU$8Dw6vE?=Q3CF19vps)rc0z#}R&!)!IC`#L~F~vD;KD-Rkf1+|rBw3X#%-ESAKRe>> z%~B)sEs~rQ=b}gk;lp3NN;+m*^X?O}Y(~%ZI4%sy&~`?@!mrB6gfk#3nAFR3?Fh40K(P`CSd&)Lc;e# zPbbSqz@5JrAsySW{1jk}LFU*3J26@_qmZ0;YukYN+2ty9?B@KwVfhv0V5Pq9_T3m9+tbKU_cXYaM(pX)&N;Zt zn(n}sV~xr#L_jSJQh6o#9nt;~`PL|hTx!rBDLO}1@JpVKI83sjOEz(cF{2|+pC6Vh zA<@yu6r=9DA~CauwDkFP(2qeQTY*@>?!5~0su>9ntXHL(|LwYYW{`)xgBf4GYT1q! zmKWDJZJ*)Io~~N=C4i1v>s7ie2#-cbSa{knSp!}BY6Vu-2xx)OJ_oD$!VtmY$`P$Y zZ(bjW!(}yivPM(AZFL~NC#6-N=Yb>nHZ;_vnfr3jEKS`rqu-r@zOUS0?J9*QTd~yI zla_4@u!iyjw0G8EyB_*+36?mg23wASi*P_bFRmAZcr(@(8Z7150rAD6TRCaUNhF z?hoNUHSjL39>dkRK+-lu;lh;=NJI&TIast9>tGYft(vqndI~`(K?*z1{yBtZ_Lpp7eOI(tOZI#>|MMi;}G1rRxWe!%h_v z1x33`qDTe*| zuuS%0S?yX{Wl3ztQQ)oRmjY7=b1fdGNN2E6yNbQ=O)`fS!jfrMW<>rrYBgadgF_az z&aOzsB7zU~8|`(M$Wk{pJH=1+=m!%JjwLIIXs7CL(-G^3uytls@33Fevy4j&v4LIS z8i_J$!`(5|CRnk?9vRfZTJXV`-LWvp=lu|)th+kOA!wRMWM?En9p&$g=n-j$nP;k_ z%#Dt6o+_c40@W_A|J5xFJ^OPcAtyl{tsO&0)t0*=kE^3K9y2l4f+u1malzsc+%0w= z^H`9wsHzlzqM(&rr{=UKT;4o4`55udECFrSzu|!`3ig)cRtAV70X^DZAV(5K?zmhc z^01`f!Lc}p8=wW@@<|RHkh5ahL9#yQVYdjq^xQv%MCbxe$G+SNCa!}@F^iSNceuY! z;kob-_$;DWXxrimkb|<-XbyzsUk2yF!T~R3#N=@`o;cyfv;NxTN*tA9S=d%ba;!Cc zW1T9)LUSg-nw-Z@Ud%MbzuA|r$_g}k*MSNoaouBoxdl5EqUF9iI}P6N$k9JS#2f7!0NSKBq#lIPKqR|DI@l^n%N{s+jJm50J)?sTIG|w%_2O){};0L>7niqx0~ip#^CFOVAt?KnoH> zq@*|(p-C)LbwyvDmHq(3-fT4gi$FL;``)g(8b}5N(}{+73+6)Wbp0jHFe|{%L_ReR z_S4Yv$4ra;zdvRVCi<@Nr3HC)R2N2@+A`IsY?&C(Gsw~!hl^}=Jl8}#fcp6O7e+0B z5gzN~=IA(e7AQ7Pc+WRmb{RM)3Lb=K*f$=Ee-jIh0LPA%-8)Egbxp*K^6pf>+c?x6 z`f51=^pKzqM%sbjLGOhKwKM!>1M-FFAC}S>lI6~T+zv@~L@q`yY>GMg2goyo?TF{* z3e)^r?AZu~u?sAGkE*vLO~8QsRCGXIt@4~(eB`mC9N_X_z4;_O4JkW=5YF4JO;1lZ zA`|3*Trk3BZBsco7a)oD(^mR72F= zAvS|nu~ZlTc0RuS4))?+#jFGFO>cM}Q8GK$EHVW&TOWai?3zVcfl^#$;c9YV2d<{# zDmCy1uBPE?TtKt!5hWW}LO_GxW^_P1n1(y@LP0haz&i9NeAAivN) z+gIqHudOOw}_GD=_v&M-0;RtfG3-IhX9Fd<@ zEyJ-c5?{q?kN~|5mg-i}n2SStMzC;OU3z;NC^cQv-as10+BwW-`EZ^AItcHNIabF; z?5U3cAOBp1pWEs}p*7&HN#FFYXU7A?Jtb{@1LrB=qBPr|vP}-`+ggfq8hEE2IH!Pf zdSg2mToSw^80Vg>4~+3nMAOuM2^bfQPXfk=a1!~XaHBn*(?*(TE!y{9jxLoBIc=Gwu7T{9i0c4LCJHd5ItKwT-KBkNgBe74}ce!_ZyT;cC_w zvE>QyL@aCA(E^W(lCn~+F}RQAvj{hY9Nw(aG0vRhH<%^z8}uW$JIga?f}ZYhBpgnM zE=WQ~6owe7-xVpYDQi5WbW177gt2Yw66_RL`T0USc8Gs~D9>dBuPTAxxlm70}t^H;Ftf+Bh86f%#0Z zFX{US{IlS-*UKF~uVhIjnU%2p7TCCSieAvoW=-`3&5lc<MwJ|L{9OXugL`Z*lbdRQyj z#Pf&(u4aQB4D0ME!LqAuEpz-`ZFNt`di9H0nbg`8kv=4z;O;y@h{5{MB#DITAZ+K$ zVQp1)^}2T+e%yjMe}5WEF!jpkkbR!!jh_5|$kfoF{({=BM)yW-&ggYFn?P+(I5&Iy zk&nD!nDrjkJJ+8?L>q$90Xa3sQH(hTDBZ}L&44EJRcKl~m!PSy*STJ>zcq!*xdvtE zhpWiT&{kP`*dk;$0YcnW6ibxg-npI&KQh$R_o&!Xh5nUXkzYiP=5BTF-d8hN@L}(7 z>CJ%r!fSWKLXiuv8hW!e1AShJKJQd}K>b;#_Ha#%1#b#|*~{Cn_A;*d04HSP!dqU2 zv= z=p&le!+ve#`PCu(>J!);lCFvi&w-{2ImR4i*GucKz)otN>p4#&R_#WgwXY`UM5M3p zk=9Rt%tiQtV>bRg7fGE*{N5*PmX(6yBG!9>Lt1}XP9Kq0yGciJXH?!bSnv3K*@j}) zn*<1h^?e zh|sf}w>lni8SVRR`m<{@8`WKH6*Y{+M+B`4a~YbT7yal)E4vkm^#6BWmT$*yZ*=(t_@hPT|4=R{ulxAM zmwL;RS2~_Xe=*ugXbDE-cZdV?w`0%@A(pEawv{@w6)@KXADO1=%ks6LT9@T-#p-a6 z@~wckVe7QU$dzzsX;o=4-c5&8dIq(YIj+dXBsG0RmNgB^1Xx+7u>(QmC~rIm9L9z3 z9@I0@U+kfkzqQ)qTH+Awh;D(jmnUXD8eeoNpRt`(w>NLj!%A!H740g_t_G<-c+nji9tgeldvl) zNgFCNfkDp-4r%QxPFRsPafm3(|MZZufGwQb@q+>RKGc0(<<*rEHf@Ml1p7rNt#tuD zvJ03NklOK3l9}t$sF3Tb(PDHv9_m2e^eS*Z3zYg+CT+<=#$!Z3vuavw=aD^YZ|nJ8 z+}ZN1^Sj7<@aPjngzKIW}Bdbt~seGz{NC;XS&_=^s)fi!tX#;q^ zF4~`u9Z-()xaOh zggF(Un)YeVaoJUTP-$w+s7#z{`M^FEee?EA-M(Mxu48-FeOxfv;hpmWf-w9B@YihVP;p!079Vy3y{63 z75>#t&MTqo%Y_qydjWnSxXa)tRK(7>h1#;(hFU47VRapRg!2}ZxxqnfH1=-bOo}Mc zSV-oB*`QC43ZXcP!OA(qMD1Y^(@TC z-a>Y%=T|>Q+#Gn?Otrmds&vSJ>U5)*jB{v%+MW`=Jj=i99l{#O)-})te9b5`=(%W0 z*=2cOWGBW`QZp-FmZOm-;FCO&%W>h0e^>K6(&$x#(tJnFyTPPaLvMnIB7C^W$TMYh z)X4fPLF$#S8S+BUBc4)D^uZ3-?4g!1i8bcJp3T7(bO?MK%Co$uzG7+O`gBoPy8Bf(-ja57le+L=PfwnY+6shZCc+r zIzw~?E>VTNU!rC24K=SvP7v(feZs~!5JWv+R%RjI^6Ho1-QgFG*=R;>9ZJ*sYV6#7 z0?uvIXuFzL_kI2ZYAXXCfHhv7+a=04f?I(h_pZHI_y&JrIO-@B=Cny*3^3Q`VLWdp{9X%@7elb+wsIzIip2zN} z1bzae0j|@1qF;Vr(SlbBLK3|j7_;7G1%?=(j&H*25%)sEyU!GJ=2xkO#ZJG}0jtg$ zUX3W%c?)3Yc3y-W@`Nii@rasWjPO>LW6lkv z+tV|G)*QN5LZW(}odas6UcnME_rF4B6AN31wRUa)+*(ag+uu@4y7EP3Yy`_6r#tu#BHL;UPR!&22zo6g@{ZJi?6o2o7&qb)M)xZn}oEqN%f3?d2Q z0CrohbL?uF1S*sT|DyqUm7?oe%q>>?e<`x0@2wj2e5+tz=72mNF1E8?r;v|v^13W` zEizvW$fe5B9;T=9>cV~jJz1()GU@B@WTusf zU&s}E%}mp?!(c$1m2^9ACyrAnnuTv*JB@ECGLnV$84-o|~CI-JFXV zz6Uex5s(X4J8K4w+N?rAz1hO|)MIYzQ$;&x7iO99)}+2Q`=>d;RbggXJKw{n&a*e# z3cG|^Irw6#`h_(5dp@-&Xhqc7^Oz-T&Y!S8(k*90j`(cm1krPKHgi6}o8y-55Pkcp z`fX7C_M?z6YZ*!{Q%k)GrP5rUM*L7j5kR}fV=k#@`-c)}E@xTs?R9=OgP(N+=5lhc zSxJcYS<}bnD-zyybHwuV-aP%>4cguaFWB2$H)Ukn>uu)qt22!D zahZ{8K9*aQHkV+p%77N4*Sr1OU2A8hcCD3+YQNglw*Fde>#@*c%;Vdb$2Vynuk8$( zLD9~QT=ZEsnC+IeAIlG-q=I6TU9iK?JMnSQNN|3PSNS~`G&x;SqGZGJuheX&6~v_- zpfxoZ%0@hCTjaK-PLH>Zs0dSOCmSsGjZw~LVZjHfEXX0(TOa10NzU4)+HBwmW%&qp zU=tjRvN}B0L<8_|oabgzfv4UfB)}G>R)21qlz`v%hj{SZ0(fr8aY7lhPGRzyM9wez zG{I?z&H`$Yhir}`FZ8fPv*$Nk6MM7*+4_n;$u}=w8${I8&FWYAhB-uUW|hraVo@eT zamqymQ$-e}`~EBP;7GqbIU2{yHBNBxy#%R?r@YX68xt3{^yh9jo}GGgji;cEciaN+ zBGP;8frUf+P@fzE#dg=WfXh`5z`~{R7Do=gD8?77v72)lJ5RKGQVKnXvb-up5mFE} zI*@%A4bGb5V+s5~?SGDj|Cj$A~x(3`JSWsa-zPzq#@ z>+Av|?`U!GI0UJqr$evrv5FLVD-klt-$PdE3Z$PtNIzlFzhSp%Uz~3CES%ae-#@~G z^kluj+fD09Z`|To+|X@VUOIPuooyU!fSo@^>?Od#Nt+%r zxGpF-K2>RJFvO<6UFLP|hTfzZzpTkAYp{VgwxrIcVEe>DZ{r`W*xYuMy~t_4-)!n#DwxVel$O5{v5 z^7D~3+lw5|;weZjq{oAn`}y+3se%*HJ~_5GSWjo~R_A6qTlYG>>iym(_1v*H2z@DW zD{n`5bvDIQb?d2I*q*#bJhS^mBdBVkx!JW4^w>NZ#4GWNxN9Dc>)i*!Qsz;#Y!%9MFOEYQ=kJ$2uirdiw$RRj z_5;=%gl!ws>Vgz8DSkI$oD~sRC;}Gq3luF9>i~Qq=B6ARpat3+8YhZ*NVQcCt1wkG z&d=e1E*#Lbq;K)OI82%C4BAxpsU*i`f|=D}V5Brh5~7Z9h?p3apO28Hk~sL=U>88N zE*oXffkralO&F?A{!Ha1AqP~bd`n4uHO*MU^76j(zVbJx?K z3oX?hQ;t@>t)zQXmaXzh9;uBFe&L+B`eG<{p{g1dpmx`qS3ZABneYAO=kRMn4@9*4 zJ?wNP{n?aRKFt@o(#u7(7@aX01iCOhnZWu(jxugNcAK$ws9h2GxNOI$k_H(O0H6cN zfJAWZRUH6qKciMiknkXbh3>FC6ulO67?gh>Wv~#Tam$Vt`>KK4`s7ow__%F9HLIMH z^5&j_{&wOB?!JhYngM@iH=7RGu2u>rn8I@AuwKPRPhix$uZ4~^qF0iB-0YaFe6sgs zFCsNzmZvP)FfRw1Ir^qg&Qr%AO!it7FZ&6BgZpqXtMn=pbfel zVff`RbFY6kJcy$^#1kZ!o>mX7<3V9hfu8@A91SiO9a4=$|Gn2u_l&4Sv4mMp#NUDpB~3 z^T@Plx~D%>r|0~IQbEw0z|O@kpBE07PHU!a7k9x zZhDf`RrQecP#xO&BJ2i+rPQ`h9zqu5vpJG3Xu7Y!_woKZXtf1Ny63G>aDRdsxwav1 z1|MhvZ!c|fZjx$w*aOAnAC6FK-V-THwO`R1DU!P2zNqT{gYqv<9fhxLxP+Nva`~xW zlpD-@VTt%2o8V7FJ&SyLXgodyeZO*63cLREABR^cjX-Atg&IP>y$|_lq0+wl-FRrH!Nkj z8IC5jcr9{|4#`i$TJQ$!I5#SSk>$U0thqYT<9m&=To1{cM;cyhb|F_ZOES+ougekU z2CFq;2jxV?t}^v>rB9bro<&cu15U}njy|4q%wvUR%*WMqO>yC%0oqzpGxi&6mH>PH z7&10C!Rn8qSI0tBzrkn(jE^(6!Qyig_Hg_Bjml)4{~n09W;R-S5j7wNDEg@kC8)ii zKKH49Ac$HF8{C-O7;QxR5EI#G#@*iN%45&DT9uWSM?e=_l}t{V7m+RSjBj@3V|3QC z-(p9ZGMVNTOh%2AZ#WHB1gv+6XZ^cthTz*y?KD(c6kB!^puY|_zYmKOBz+KTC-)Ne zs$eNCSwfRkDm4zg4Ec5#%xPz5Efq}EYsO8pT!zgV;L)Wub>P~f>K15LG}4vNNwbu* zRQ=MJglsw4{_+MMd7r1C=W8XJFPf>hp(9MiOrbm@VjW}EYSf5-li?wo5D<_BH9k&H z4O@L8JRPgW1sKou>JDEL+9q1tK~qKR%jj(*^7Z~LV8AT=8g?VhBq=+yITKgI-Ag`S zjewhyFvoG5i26jAKIgjiCquB4?rH68WjV-;;{Taz2Qp0Zur8>>?(JKMr`T~>FJUeD z=}Cl5z>IpapDRLJ@^=-F+$`B2e&gYi11*ZRf`>0Oc7D#nrg?WKp4fm#*jb~aZa0TJ z`{-TC?mFB#M(;Xzmpt5z-6Dg-zPt0`CvlbRcw=|)bILqUzkT~a{lkxgw#;*km5g$M zli1lLNBwRFPUdFuk7&)i80UEI?pp&@h|RMCWBenGF=wJNo`5kfS7B@zd;UDI%~xkY zr_Nxbavu|XslYZExqNz`T8P+_A8ODmBm7D;g6V~sh_ws(I#;Wf`UUXxG_}-qVn%`6 zFMY@r6oRX`1~WJ>AO({pim%|de*8Ikthuc{e-)K?v$+~Q=I3)2`qozb`15q!UBF$F zva@3C=Ti&P{nM~VZYo$CC<}_pJJ}`p{@;Q$r|;n_7x6ufe+wem2uEUhBX;87eh^@o;Eb!Q4Ph&WLg0n+Ka*Utb9m zQRAa(yY^Kucz3R=m=Y{NP4~mDf+<6HE<&^=ME)E7f}nJ=v9gWUIC6yj#IK|y&dYmI z`o@iK;(62eptj9`%&|Kfm8QQM@y!qM&3mESmT(1+xH8agz90poIL$92BFRipG=1O^ ztm!mh&q>fDr^5%^z4#H}TV@{ecVQ(?4$>WmD8^k^adrF)jrs5Rmc9YD5bGP_S;_l9 z&_JVxC#+RTGQy?+xyJ`Sz;8Lq-NoUZ8{7q;fU$go8b5k=0DQ0!b`ESW_{0X=blflH zif-mS#$&MywjjMZPG#X;I^JnHKca{>JFQzKnCyZ&lw=u~-Zlxwu^73zrZrfcusva8n}dmC`Ql+)GFn?&fN=`1yrdBfmtemF#Q zcTnCE)8HIKj@=d&Pf=D%(ueb?l?xIc0M%0Kp7~dz;B2~P2A3aQdJdU88hlc9hiAX? zF4GOcOW<-xg{tLrF=g%cgq?_gSwZpz%Y8;!CuKMcy(>Xc)(*LmeH~m&K&tNa5RP~U zP`1w9r0jDd6M4m7L+jk1hQcR42bJ2@(&Wm($gWfA)Z!s7-wXUnBb^I+nFk5kg}uc~ zG30qX%>AOnLpmTKxX!JEN1YR~eI({|Lq7}*ta)it4*hOOUOvL6yHU!sib0xWV#<{{ zCGHHI4`eaLwMtUfZ@3VWsv~kp@%T*D0_=zNJhieyB5!d$=S6;7!0+7T*EB&Io6WKR z05+X8_jhfq_q&=qJx?IVSf*zmtV6uY{T1VbtpBGqe1l2h3mQ>IQca^$$;xWRxr3O4 zXRwk?qw8#c1&u&6@Y4%d>2ti_zZ*0kqZaK@EXSUA(bYD^yeEEDqqs(|HY!I)ud0=c zd*H)CcuCAE5V68<24|9r^El(yw&Wm<42O{^{VYzm(n7$D6+Bj@0da2P>pnF&Rc#mY zLZbEW*7FCFcGwQU2lD`YFb|yBF>W)p|9Z5aD966+UGA*U#1mW5@~ZDU|&Z(b>9i>`Q+-q^8NP)i;od(Ob>wDMn5IxS%qO$v2Z>| zbMQEP2{;jZ)R=`Q05uI2LAMA!(Tz7*9e0;_sXymn3FyTtyAL$a@r_v!2DyCVX;`jc8tY#a zd=Rx)QZYMG)YNlQ^^m+TMl|psld3I>ZcelEh-3xFc8D33#uV}g5ZrN#YfxeF9X*fe z!0X(1eMWFTM(G2pEeW+|T?MTQf(>}ZKG%#L~902OVpSE+Q+q>fzpC#8C!5zY&n78oNP1i_d1gxeV+F7zMs$g{`V%I zYtA|Mx%YD~*LGi*>sXlcYbGBF>(udeKiWg1fWfLC#h-b?*KV3exax9K9?4ck$v5Rh z9rnP1Fe?Zs8=Z*d{MSBhRk01@r9K^A6OI>RygVze2nLjAofshj>T_c~ek~d3Rjv_t zTzX`r-;MSAAL1-oEA~H>ep#P0`IY!7w80Ebc(loBM*aGM-)_eE89jvyY`b$7Hp22RG91*D)5xJ<- zM;Hqx#V~IFLs=LP>Hn)%Ka5BG$77$X*y-{b7i#sV>J$sJeLu1}g%z5y`mt5n(C%xI zQt>u9D*9p<63I8YusujIM#JlB@;gkNJc_;@zGx#>MxQee>&NJK=+65MMwYVtPVMlj zNhj}`JGChJx7FgRZv9Hi$)>e@h!u0BoUTMuP&X|rr2CSemI02cF{Fh@&Xh>nG<_(2 znl_X*O$gCkH(;*QC|!sZw?Ebb9Qvn}TOmJbpMU*?(UX6y@_y1Do%w^g)<@?(T39vb z(MKP>c5%(U5BOHnc;_(Q@4MpLi`4o?<1N;rI~(A~ ztnmKKNE*^C9ZEx4*9{qPe;9ll$V!KR--?OYw!v4V^QWf_`!wEvnsJNa*Hr9)3T@b;qU#YpKp4#ZQncW z`C(kLFLn-G&hhy2be8oVTCKdBBFy$YX8Bm`yAja4G{2w0yiT@>*33T0Vi|Z_^rq2! z=Lw3xT#f&cDs$D4#>=I!_th|?NMB<)tGFz7P<@k5S{avO|5^=ekup)AjXpyJIkRka zp0r3=2z%B?U#RfJ!pln77#FeTVWz7zM2nQ8racp-v}QGJ9^|1abu0e}ezrzxTd$U- z6@$?y(8AoyYPMW`lP|s+CES8iB>Q9OaP6o^)?8NWYBhYK_{?xA1MfYd-0eLCF403# zQ+o`&c4q%USX|}QXC78Z9>d%ZlXM=V)}#kT&OtUz*#AAIL@WdH{Jn#T*y4DLpXo;< z1-iOudOn_0xY)W55q_ft=Jg7dG1kJvJ=Ph8ai zNmD@L54XTmmL>u&2)tF}-o)g*A#RcqY-xnuOSP8wnzDi|%A+--FlO77N7Z7||4txhg$b_B}4sXW1)f0aOJcS_q*{3{9Qp!5 zyi&Q}zh2^|=t8#!%~K5FprV-iV~@QT1kPFttGo@c%8PY3YkgzsN<6_x_6ZUDfuqIw^ zfd@L))KB^{!vgcJIc!DYH~;J)gy)9NvYwX$eVzu}{gKFo@7KFlAe`>AK3?A+%N*FC<^{~9DP??r?D_$9*TR!=dVkD$>1hI%@*HeEely2(WLPU_!R z+Rx1=ou@W$d*BgYyKk0{Z1Dy`+0gY+-`h%r1)(LlW;chGyf1dRk7NQ#-TV~NOE*C; zok6c06drfX=XXGEw;X!=si8v5l~LG3-UTIecTEuM?SIvz6CHP13N2DVx)5n^wSQ73aS~!o;aa_KD_3%@4HS#n)dLEm`9 zU#B<_cZM4GYBlb)h&vr|uH&4DGeM1W9pc=8IE4t$SJRF|IIswmm5+B5@Q&Vq9}vz# z9B^ChTS5=|MkAI!vQBX!PC4Sd4c@FBHKF&Ady3g7=2X?xhE>i1d0Uzvp^@o?C&dVR z8@zM6`zdtDnH=xi8+==NR1JAl4LN~P{3v4iaXvN$I%BWmiMRq*&QZ~gU19}v+Zy3T z>TPA6nra<;PI*Rs-k?5j!1Ew;LOz}3d6AdJzSy>YO0h_TCr3z*XLrcHbfTrnrj80_jPmSnyUV2wb1Ava=j z6Xf8iHw%no6NN~353fB5*?X&teE}aN(^9@h$ z+H6zI7z59q#Et~%4fX_a3MkUMO+-&V<6Lu+DR(c>;M{u<+`nGzNm!8wWbkV)kG))t z-RQjUB--tyIMo9e!H2m6a?#|@EHqX%HiJ`_0@`{MxWzQWi6h?^8ycq5iIQKh7yd*& zxEy!`QyT9KJfsCCc{#oEjWUAUKT|XFB;k03>urFwh&DbwDL!)(RJP@QlT*4BuMZkCIUfs2UZwlzs0bu&n%PorH{F_PF>vXNZX{E6&GGs`hg(#D27wW}b|5M1KM{B;!=2=xuq7^lMvI*zNg; ze4Q}-HR$<*D06Ygy;-7!gO80SQsgZ{K@Qy*#3f8%Mzw!#3$fK4PrY&G=UnGrt4U^pbb8_sb z3F)yq#J>o6JoSL4P31JRYMjq#!yeLp_Bz&g1Fz}@8Qs~H=~7lzR;6J^mbaxxZie5<2$7{WmtLXj;`j;1@I@*D>ypv;$ z`v5qpseN%pPwbN+!XaxaocTSm4}k3v)kD-_&w}dte^yuV?497P4*Ay`uNH!d^Bv9ig;43UwhBe^Xir>=g zN8GJ{!YVwr19E~au0!l%&~8TvXQmL~8OMR$(e;nv9|V7odL&DIdH-VmThcH2bReS(tAuP={=%h4D`s$^(O00$`vLFSm*tH z+OtpLxx&3l!b!Y7(iRUSOx9BOUOeB?w>Z-5IudkgvZlAZg(&v{+=R@TlP!5}`)7`cHMwPR?dA?zqN2MC8G!v#(RaKRU!#NA0 z=xc$85iTSK4WMptx+D%=`yK<+f0T&5oY3NZa#gtVT{*TGXSBRhPfY4-bMXgj>)^5N zkN{nI_h5EJj+GC;y#;SC9HdmuQ7R`Dx=w`aXomA*@+W(7(k((MCl6i#FGQXDm=CtW z<<(xCG2+y5csF!Z3_kQ?J?4?OqTXBak(WmvUbGbamf@C~+J9Ir`b#=^x6f|Mij_FXROM7td9O2-c zGdgh5p~Wcv1G=|g#*W2J>WOj=mM%_2iz|vN8T6~QA@cb~vi+WTE~v6!U==tVF05dd zC%oOKh2`J?$`2_fXm9QGvB+f6TV{47Vh=*wazFNUty)J--Th@EN_ZXANG3iJlYK+2 z;DMNMKel45T8;p}VX){qWI`GDql})g5ms&2c`5%J74%RAJR4U~-cICAE4&8$C>b`Y zR4Nwzg;kurt&VHX$xd)i)9prfJJzVj%XkL5S$zMU_$SN%562AE9HX8KfkwfJun=-n zHE`gs5By{i7BlGGOpYig1}u|x4#&>YK{Jg~IrG0yHIftgUA#P}lZlFvoG9HP$}yQP znJ1NYD6#b4#-TE)Y{nk1Op!P*<-4lVpk6u42EB+=F`AI;yoSLS_dP5UzK3Ge(+Kps z&g&3rXxG>ts2Zy`&2R+wDVk!FL~zD$NA-y1u3Ch1hrECK@(zUG0X@-X7Z>inU>g2jH&^}; z;XkZ2z%JM2;g0qiPtCl1$#vc!yy4mj0|A$#9GfdNeDL0?YgkwXAKpc)KJCsMrz?<(@P<+ z6GF7|rE_&$h=Y|V28&={D>cC{;g{)LY+t-5wq1!rS70UW#a<0?48lwa-!o$E|CiL0 zka`l6_bXyhhj<2B;~Tz=Jv;Da?8QMISRH4Zo6^PfJ&`N0|8sv)Sp6TJefn|mvn?F7 z_OqUk^;d*Tcf(TeF|H?K*^Ds=?J9UsOg`m~hlSJ0qYn!GTx^W|Ej(a3^OxHXABx8Q z(KoMZ9_km3Ir=LrtI@Z#u75b-?a)N1CZ*>&wm0gm(0HfeWbKzREv!443Z_O}^TtL< z%aA{cxwE7oqAy@}$q!YG4nyQrP_&Ob((1R^%}2;)1{Z1dH+7uyr`b(MW>p42*XE{w z2_3G<*mLE?x!76gysz!2+TPK2nLM65h9%Po4-u@~7AtWfkCA|iAT z4<>|2cBQDUKBCv8Vy(VTeMPFegTcNNW&+Iy#y-P7WCUKNVf|X#dvGXR#Ozl0piP+9 z;Gy9i>Nbqyvi9wjy#30MS5dOxa#mNZvxcHr%|Lt=U5cdP0RWWS)EwD9eDNz93Kn9s-FRZ~09SC=MJQ_2Os zZ!N6+?vrH;^OC99`LCVHlz|D9mN>A<)GrS4(=L@g?fbH+M#vLb3)P5pwvp$e^~kUC zyTRJ;>*7%BqJghAs>|)LxkPo|GGvE__byo17^$ffbCN!_(H($w&vKmeZBjVQX>RAe?tjFl z4QLs(->-sYY^*TxwXl=&I=Af~v9f-?sZ9}BDtvVt?_woE$F1ukBJ>k@nZ~!NlvdR~nf!?sUp>g*>&joHE$I?)b+&Mmy2V9E5 zVmSOrHp1K5K|1%#pDE=U0*T4%x)PU_@Q1MA{VdwBYI?A8LYAC>&R%{O&D&wVr)6J# zLp^GR({wWIu#hz=Q>Q55B#f7E%(N=rlI2aSOp{pAV94MZH{R?usI(B~^Y9&cqz=?L z>u4>|oJ``QnDxMhLD0HI;49Dx2MAMOUn>H8+Yw$I1sX$Nq{XZiWR1>{YRoV(!fC*R zfh7SQ{3Lzq=eT>%V$3g+Mrv4KT5oV{16zh zZ*KB^U-*Ff{^G%aw8?e;2=&!5?690Q)yJ=TCCIF)^{!Q0f*Na8{Tk^|^`aTD$u8y2 z{K)I|o{Mc9`eUUCdZ-atx?nvW6|84L9ej$DAD|>5&22BiFZnWZk>;v`)&Q!(T9emCAyZ@1A#L-8*YMzGo?3Hrb-p(`JCo5zKrN3 zLsgdg%$*U~wz4dQ@NSLAB<7S47I8DTx^51!e6HM*f^_gMKl=MTU zj|?AYPkhm(gv58oz5}fWoZ&WsT244joq54SdI7$w^%26rObRzUOW{1@#_7Y~-*e$D z6MDjCNR@;s&hrjfoe+#n9u+{$+3)r6naj$Xx^Vv~!*dDU&3>gv!#8!|`O^~mot*`r z)6iH97_RM>4S|JM+}8u8i|{L&p=yLvCab>|uWCk0(2A?01>kImiG!*`xpgYc2E$otPh zy}fI?!p}~YWA75x`(zosoDj;+fFpK>cR@>PFLni9Gr3#N9`1p0fzNt0M(P3Vhp1I6 zr7n8;tdV1K`rbRw>_b@;PB2n$%7ftq+H>v~+~PIXu9j0q)Y@1R1P`{WH}+Nv>O;>J zUB>P^6wTT7%-)55DWbYosQJSV`+{V7BVQY*l8&1zQ~Z4$8nNDOzzGT6woz4^U1Pq~ zZG%tbWBfH0{^NF2*x-+ol@55oqJ5rfV)U5j5-q^Cam^{XPw_D9C*!1Jk{LT=!ck8< zAur$!V|L)l%*(Uut9V8@@t6v8r4Q@FK;jrl9Xdxfm6N5*d5)B-87A{KNjI05W~F%`H!2-*gUl3<1Dn! z0!%3YECkn5F9%(sREhVdtrCDGJ^un@H#RZ$@;1h_&A11w1iT6O3h=}2jBWfmV;|!E zud^B3SH@U+IpP2k0Gpe!lYlX(&pE(T6^tDN?5jlm00%}h76ps}+y@vt26+SKdQsO} z#yan2?C_5myYn8D2Pl;otF42NQXHar2_RH4mIBBDJP!CZ;I3-a3GgA{s(HwBKCXc2 zs1McgKf#Vd#wJf=H+&h%_{R#YY*tO@uA>5}tkiD#y4VB0oSE z-XFvJU*i4ecwdS47x5mR^*ARhVYh=hQo|Y0I)UAN|HI3ujLZMf?f>%k{#x&?$I^b_ zEqU$Y>zmg$-p}FfyVE(=;G{do@j5}H#f_ex@P{sEs4|-YKsU3u0cU^4v4r{h!1W`W~UQUpPZ z{x6(#h58BpQ(lF`dA(GDO#tvY;3QyFCDv8IJpgbR*y1_3mh)^Q)*#^~o?UYX$0`Aj z0Oo=>z5q}Q__dp7`vA`aehK&-Ab_)K1C#<50PX-h0C)oM5?~MDV*vd<^$zAV-~ynw zhG!ipZ?%hO(ZxI~T!Qp~vA1()` zb_&nm0+)FX_bmV}H%H0gono4((Us?My!DwY#DSUH?+j!I_z9+QWob*sYNkAC;C`7h z`qLS!)9&vzb)$9~@$=o8>>?lr=mQJ@h5-NbZ{M6u#_dUl3%FY5WwKfGGnpRu|8L-l z72l^XU75UZ@O&Tk@A4k0+rN7^5~h9PU-P&;4c$|`|G)nU6&lZ4aI*!E5Htg?{pd+1 z5>UTrD|D4oz*=B`n|jSO8`o|l06L&k1-8n%nFDzdAx^O*dI zV#He(Y62FJyrP~K4NGTf6-*wjFnE>_*<=uU5BD=U7Ze|6%cVb!+OAjsz{J8be0Lk? z&U2f=8+~!Fp&hy4?o&kr-08r5#JeW>+vGmhwe;OgR?=?p80OtJ?nM($tIr-^&Rutt zqPeDNE}PmH?mSiE)XV&P)YcCGuyHj%ecq$verwV}UW|CNxtUFKf92{6r_4`5xL*Ed z;D5jKb-stR1e6C|*XkYZ6`>^)*pwSXpX?Ugu-tOpwTA@YxG(AZ!ot*PM?c>^=Zv;6 zb1Hu4Tz&M_9usesGmS!znDGs`LfQEOLR_acH@QypH<@Ovn4^S^Jsh74A5#Ks@@Nn@ zC-g>-MzDcu&=lvCyA;iBTU`TTqbz8q20z)IUrwdHh!98Ta(9lKP3;Xs(+zyNu4tb7 zRd*!nd)r)HC>@4B=KMW(OB1IRep%A7E1#Qnx3^rena~&S`Q~cQK4IkTpFwl&PfBAg zFk+tX*PeHu;dLM2`7^~Z{UpvIJ~KV1>;wEBk7_Ue>fsNO_@^oLX<*Hq0hn{j!_wUoGXBKPgydI4-ZzZUKw#5vn*%NL>+j2d zwMV155bpezf0{H4R!UZho4pIZKpr98hfQqCx1m2PjohCVO^cyl+rzoi!kgxOi z@D+*J$M0igEQc2U;R>#TOSPW}hz-cN2nvxJ*H za8O^ttk+ot`A{FTwLWF@mULckJ0H!k^+h>b4EY3BGf`jlkjx9en8SxJ$Ie@Nu=DtV zYj-_@HM;~dfYxC8Jj%`P7UW%hyMDITl>raI+MesUMT#b^9Iydnlq=r#6cbB2GsMAY z8Z4e9%Sf9{dY7ZXjlJrqZN0WwbGD2V%gge<*v(CubhN=$a3rTfCvS?|8W$Dm!n^cY zp=FKL-${2hyNsagb0afBudRVx>o@CPa0xw}UH_Roa24Rq-MT5JZ4yWVO>2hPi`&Rf?{4B1+(@Xu}$Ce#eObv&W{F{Q{&U5fp+lZd13cc3T%2m6nOH1Y-S9&*T zWKJlC4e~%4Z?|c& zaFMMXC*3yZ+Au`Y5w763QLcdM!K@EVJD}vFr6&Sx>L=k3qnsFuE^%KSxz0Tkwxt3> znzuf}qs%G&ul39qmb#Xf3UYS*HxbynaPNx<^6l~8LywxZFemljOnk$`dAG8k-g9#P zc|0FG8C>|2wtqgwrtS~FgS8|aVpoMjcYE%x8YkUdk>|bJQ-(finmbZo1L}K6m9dNJ zOZCo{sMf}=+da&d>Q!wHX(m&N$qpbqaTT-Yh%eTMT*#e`g5iu0Lc<}iyi)d_XRZ=D|~4F zwAH;gyv13auy)DhQ-jnWH};mim54ozarsZ|-O4}Z z={-D$oxk->MT5GR!UmBnR`AYvnLcvyp?hgKOgD#r@p&)S{G73LveB4|AeM-aY^s5pojUN|YDBiyENi z(vi?Y6FL$53fEW|E1HF`GQbAD@ILE$066bN`?@YjshP&3t!jdQi#72{3NogCRoU?XY-ID$jb}kygV?NQJLY1K;~>?<1i~9Td@arw7J=P zVCN1JB{^bUTuVN#v1HZiZ8^2HKKs!Bz33xa`${mks2&t^K`)!mY^L^{+Wqh!$oF)+ z_>0a(n6K0KD-}8W6+;eZS?JvitKUyQ8aiXBCz ziWP2|Nd64&vf5U?$EKJ}dJnU%X@gFK1wCTnws6gywb{$J`R1N#LVdb9XKGcNCkyj4 z2du-#II!wT>tuzq(U_UEn>NFxLIsvLmj+3~p z+;(bXW>t>=P>0}0|CJuN;1u>LfmwoIz>MFg_-Cbf#!4USb$^Oj)+!7=eE{EJte3NC zDsmtx6DokGZ9^Yn&VVbGW(05^UZQ$3gwrTm(Cb}s0J=i-Jx%JkI;3dImSJ65(7Vou zwIX2(PQd(Zn!7`1K-dO^y@vD1c^)QGs8UM+EJA;VP}6YSziBCl zO$c3w(6r&WJ8%+jLGKoXu1eU0S`TrW$4R_so=f8Z#wo->0M%Dh1>KjK$Mge-N#o?s zpP|lOuq@|9I~M2F!4CWdejrC!*Me=9hM6wIorps-}GB-2r!qym)o zG|wnEXVrlwp*XDIHJ2u=!?>d-rSpfdya*hWC1URavt}}7X$JL3eh-*2n1-Mq=YR9!2*QvPH;OfA239d77_2OEB z>vCML!&So7iR=Bime1U;_+}+yFW|dXdMc|9D9=nve%ra80rZl&FcE7^_#m4Olv1n1 z%84rvDNAR7a^VFzQ^+aXr<}MU0ox#A@H)%{hB4=yL@`l^=?2JUaZk8NL0Dhj^$@5D z=p_}*!1r!+l=b?A{kT$(cU^c0mN8AhoQ&3k`tgU0u$I#|qLetv4}T0M>p{gh!-n~R zUYud`jJ!#H%aNxGi#p(ZoLvY*OQ4IljP+&1TMaM&^j#Vs zvoPjqZ@JLl2+h!Z_!?H^GKxSon1+(Q_Ie9hC>=V_^%gf)zDuo@uAJ<#;mC5@p zajhp0(sJaDJ&)G$smQYgKzWn&E_+_id{9+09}j|Ry7>b|126!L023e=ULJw(sULyz1fTfLMa2xi2@=sGWz-_;+aa}~MRay5)&SR<=^hPxxlDenZ%Ic9zF)$!L*WB2dGi9>0<%1MzU&A$bxe4= z=bf=r%A38fS@Qx3YjeQZOtB7}79PE$wz;;*laIB>li*3?zZ90x=L%CZ!(4NLC#!L6 zU}57Hso6_$U$@>8Xb#*_I}zoCqx{rSh%*jxUYnd6X4sJuShp#6jI`5&_14-m(PV0* z@}7@sr)FSp%fP8tCjVUBiJ$2=dIWrx{ZS^H`r%n%sv%5!T{0)Gc}rt+V0TnEb$6Ke zXsl<@4PW-&z7DO8FhBivwpG}b`Dr3n2MsQb%y91WpP>|$-e6rb z@m=hIBu&B>>pj?EWC#4J>u9V1@9}s!*&R>PC#-Ad!c0bEg*0|D+^N9;=Hw_3kKa6C z`)%;KYVVkZwmc1=p5PJ1MQAg=AI$J%RAp9-$H{13Rus&0Xz%pniEs>N`({AH>Euw~ z6xd$WRq%2ju)~M?{u&*r+gFP-*-vOqRO^`oOeiq}I~p*Y0IYI57iX#?Dn0z7MD~UL z59}Q*klEsVX!6i$4U-cVF7^_|#TV0v)7d09?Cs9Pw~e2U(jG|TNZ(E)ahXD+@3krV zP*Y<|&+AEQLhUKC2MXyo&Kb0#HJKGjJc^UMpq~7iP=wt>HHw*YqW#4)-u+)QuLQo3{^rfAv)0~Px2$%1-HuwC9h^s?og8>&CX?F* zw^<^&;8J+qUB3I=1?>}T8W;YRo6Rl6c?-)GH@+jkja9M`I+a(*Z}*AL-3)1E)Rl0c zmB4)oizXbJcW_&I`GX;)gL|>if5)CNo~?>Q6sFii#!0#Xc7EH^(;ZFbmue`Kn{jYA z>PR^mRe*xc$dT6<5pzX2* z-@ET-e0Mi=A!OEpHUSqgTGKo=Wn;~GOV=Z*#jy8ql^XvIMT^z;@BLq4{yvP* zE$Ue<9m0;j!@6j$QGE>U8JN@5LmCTr%>mRp&nxs81tpwP!Q>l&jkgZcczQyi8S$)o zzZ$KfwVKkmqhF>|-?WpBk%tpx<*+_M9#bDo2>87ZrPAvE`Fl+A&AlI7hX();0v-aa z0IUQ&40sgqW58p8p8!?^o&@|1unF)MpbZcN>;|+0mIHnSfaO6IGtSMXzm#l!c6NFi zC(iGXkb-`?pxe;1zE1O*Tp&X>#k?2-aaPoyN+8dw4OyuN$BvOEtzUWE~ zb5_BkufS>bzB5q{@pq!6@1isZZ@^w#(G0y*^rOY(;#zeg6FE{^#4+WT(6(P>mz+ChSnK+&Wa{;kMbl zW@|=k9A2cXg1D%XuOG-9pj0U*Ck3khS!nOI0iHM7HO1Q_|Q;%*PJOxD@w*qGb&zb z+_CCSWu~b2679E9x!RG48P&8uN(iV&wL^p73`gmC$6})5Cn3M>t2QdxppP>-4e0qX z3C?PAr}tz)`}fhr#0stKiBG89zdJ=8PbRBjCyn2_gsF0DMaJu}4T!TWocx2t=xUAC z;-|6qS2g6y1Wp8@ufyuo72tOi`$oY=n$Gq<_Nl4pbLzuJKrdh?*22BGmLt3iSNrfc zc@p8*;Ty@c?zw|)t@w)D6kFq@qS;3>i!?`|`yqc8n2vl+vqu;8AK_kEq}XKEb@y$V zY$f2+TQk{fxc>6?pj97&8y7Pw#JQ$Rn%|#V*HzmXc|*I$!(GrN`>_OXEeK};`>XU6pReB zev~>ga^1oQhT?Z!R=hDwqW89n+A7v)+VzCx^V7LE6Ze8VQ{MF!C*K;sGrD6HD_Y%q z`&)}VITmvxG){0%@5bIGZ>@^h9^>$4!CTd83#vg&a@UOY)^(V$U(s&0b3lvT$|>eH zCwHss;VE-EVM)$J?4kE>@L*3ve&>e%td?nGZ)NJH?%;4HQ0eX6@TR)=(?|wJb7PWiwY5qU*hU<#Z zRP@<}U{NJ!Ghp^5V!I;XYY^cr5Isb=lmQg?s`-kH9_<)pes%>M#t~bLG&!Z@zBS zc*aaNH`=?#V6;CvR2!wWgw_=r<4#oPga_|0}O{N_3|t>E>bcSmC-$Wi0h4l#?-$|3$IYJ81j3b=_=DyDdZ zt+3=GU!}&HKXk=B=WxEi#VG@=Ja-UzGu%C|l}iWp&x`ZkRLt}&UpttEvK&kU%%Iwl z{ydY9CGtED)LL)7)n~Fc1ACT+ME9sRM<=Z@UD(4otfwz2S*RaZyb)LUJwj`r4smkU zP-B$(Z0FKUcFl0#QJ>LTF#gU=M*X`M?>6GO2|#^nYR^=ADdW=4pjUT>7u1qY4bt9k zfV7eo^@a4Hr(oMseRFJ(L0)-IEpOzTCD)rYV9#U$J*8+C_O%>&8oQd6LT6ztkRv^` zR?68Zj>+o>H2CTbIBR0Z&Toz@!4!4$bMkNAOWPEtOq*4!iI`5ys2l7h8tg@>Os!RL|2`MIL+!b~;{Om}}8y&tr|KXGOu9(yNV z=b7N=ulzwsxbl_|cjbw&W|s-`k-;ugr0=dO+hf_VOKAcQDQqzo;k+*o83WET(D_9s ze~8)rDayWM@T2Hmk{P4>!hm*@c|7LnfNqn3Q>3tdHlPPDT9ql-+mS92%S0QGM{SSg z$O6j>bwmF!ul(TdvEa`!V}_fXevkC=?#(W}qU(-@$K`2?Z7aZI+Ip9y+iH7E)4JWD z-OA+O_cQymA>Q(rsQB1g*dUw%xyrI=$$W0pbnN>-gKn)^)epTm@OZRV;+h?jR$kV} zHF0tj@5rJGx05_)%IeeUum&Y?+bM5NYcWcKy$tvbHaLs9{^51N{swT*dBA!F*+164Jg6Q0Ww`fksE_aLN?Vt&(O- zGqf3XTZy*RZb?#?*8)3D(kqvzrM2GZ$n<1ZCCepxjp%&)68tqbjF40ZP!E^bZZi>g0#4ugU#(HAyW2^JMW5M&wgYEn~`puq- z;8;+I;F>4c9$|RK9)4%Ml)Zqvep(u^e9)uLn^#Kjf;-Vh+=&`?R^#Ai#^E3IHP`tg zu%1-Gw&tmL>%@eBIm@~}LmzzrpgzAJK=q`PR!_DG*mZf6*8PHkhwMP}oc4u8j88;@ zZ(j`D$!*U%!rAy%#F+K@X`+-b#{C__zl3}n*G2N&U+1051AiJT$2TDzzAgNcfkMyn zWL2_)d>`&x^8?7G9JEF}!($5O`^aY*+Of!3xkQ4m)JkL57HZ3)DzBl6;p_(OE8j-f z*JiS|;kwiJqWV+am*5K7hnMI_8bc-RWs;>j^HbulSp7AyfxlT_9QI1kR|xNrUeZqs ze=R`|Su+_fungtq$~_mw1qN@y5&F)fD({dsx{?yUI@^JZ_u86VV~$YW$9l6WZ}Eop zDThr}&0Z5mJ6B``AEW@Xn3B#d`syxYReDuVR9Em_)L76PH58l&{z%_{q>pKApI(;9 z_5mIMQ2nRl+JkmM=3Dke8?!GCHK=u47$$r{TeKitG~Xo^OUIQrHVJ3f2+zaIG!wVN z{|Pt>#l>D@f#}xKxL7M}j>pRTjw_7;VVg5pEJ13_reNg!Y3N8$qxv9oE*@L8RQdr< zgBZG+Bq2G9YnW|{yQDl0+bO~}O2eK{bCX9ISn`V(OXJH?o@18xCFm5yW4Sm<@OxZ0 zDF>n%1^c3=f-bbPGn!KHF4{`%p>ILogxXBw%m;q-ckBb^QQo7 zC{#?~_Y^}@ztA@be1>HCl%DdT?@WE?11K0jd46FEeA5671*0PxjE8g&Sv-h^jq}Q+ ztNg!!jf%B`vncgQgJh_fffi}xZSf+1ae0vsvaQY(SV0=8ALUK?n%dK_(`t)M;Z#p5 zWT#ouHqc-??6Wu{BrTQV@SbSs(5rev2FUy=J++JKNNt0*kY5iun&8-L-ysUUkh$m;d+`h3*3#{gWgyBUt^@dQ|FEkeei}$#u9i8C$)y2R~!kL@2 zG#0mmCQLh0$BNvq;&d5zF|bf~b`Dm&$? zyIZlhy$*O2@EZVe4weDvoKS}QIbc_?1|f3|P}`E@0T1>rCie%3@(}`ryQVrG_h>Go zc9S&CU2}-0A^J7FCzUI4zo{62J01eJk=dQ{p953I^E-(O?H&&KJn#~7+EvfF^R4Nv zL$MlQoiiO7&y~p{dgu62#X5>(@AK z@Hid;%D0b&y(FPh7 zG=_{0J1FN=Z+eeT;CEtw1+TXwPP=CYFDz-YV2QKxW z$}HK#hWS|qp&XQ%wkLa@$yVnREXd#VI?grbx%t*2w5@00|E{E}e<^9CZRCHmu}$Ny z304Nz+>t+A%4GNe6XDk_8a!;+8zJ+iQ9ApGnq{z@gBx?b4O?CK3a{kR6Q8~Nx< zpdLX_c!|@!5%+0mt=ZwhZ=Sw%G znx*j9ZA59>=QTSw&3!#e>jS)>VD}UokLeE`W|K5Kk*gPL9r8RJp8Q{)3HqWNR4%S& ztaXxo%IvB4ZNU8Xaz)xBI_$v27br`-kCl7WEW$|n=Xc!8?G)Wjb03f1yMePcyNJ$~ z5tp}|it?z3#_^A+VC#p+NnrQz1AX5H9&%d2YJgwKc4d@99c@L%F1<}>i$+syCvmRx z)9A?hM|x)g_p@!I+!G-u$?7lwd-&zhs1Bkvwhv)df))?648??jmy~?urGZhXGHCW~ z#FNF%*#vm>qm}0K)2UdOjOB*s;2*kwHYhyicpSf+<0Wv`ldBW2u1-8w1Rmq^i~iJ# z&lTvl?Bcibujg)O=zmj<9{rUK9iH489q`cKBr<3YHb9Q9&Vi@eO211LimGVtXxswU zQml?x^_y@?@kocY@oyd0^;N+F58L{+r0Ka%aD7*R)plb7@1Cg&PDFU^y~*%N2yaOE zJ9ganIND0%e=NNnPBj^C=?v*#La^x&%~R>sTN}X%5VKRkj})65kKt*QV;r7xP>0cu z?9^1xMkQBdTlH2dL9^5KT@7+skoYP}bw33-0*C-)z+~(-b^zq3R(^nCUTUn}r!_XA zeyw+H?Ew|9HEw63t(^x4`eUg~Vp}zy#fqh-tj?qu$F{S&Ip7H&XWnj@`;)HQfkxG~3@d*m+vXfHSW zF5i9m$99`7DMb{cvFo*-6ah7yZu7yvae~fh)+Fu%SLKV)4*dQFc>BF*=OI{=2!||w zvrQP_N;T89k?-%*MVjrfEODu=hW1qO3CDV&sS?=cGk}+nh!rMMC-;$lg=qF_JqJ$X zG|g$8fas*Yp*~``(s$8S3E+fkaO%$BEzQsJ=1GF5Oc9|a1FUg4PEPx0W@Fi_dgz{R zCC_x6oA87A36hVQunXv+ZwlOe3+E0jc^mFHIfHa|(Ph(=}BvHs1jiCw&J zsX7jrBezTApmKn1iiXJ~$KXS#nH(H|?F#ESdhWvm3#LYesCR|+APblbRMo)SLdiiBxTWg z)_Lg*l7t5O^*%^k&<1AWoCVO&8wDVVePQJ!4=dcKI2Z0y$^jLCY5)Uh77|ZmW+<#^ z=8Uf-DlA1>o>w5RF2Bk>R(jc$eI)NEf2~`dmtMXscx&CnhuN0xb=+2$+hCEfy58Im z+g0-E{`WgFt4y9O|2RL+^+VJ7*lr#t4TxWp?N7savDTzs>T^}b-%#wv83okSjyh6Z zEdUW<22dO3)e~(QThLmyvogn@;%}|q=9(S4Jm=Rcv;D7mGNr}PS(|OP$*ZBin&!1Z zW-}URQ*%08L7P_>8H@Ai=|_3or{qB^YmyBVskDV{Wmj}|;O-SE;>^n^KPo|ACxQ{t zJw5o&SgX9+1u3EBtJ?AImC#8#5@m%~y1$Ov-T$b2$^(1h-Q$pp<8KYp8)pTa3|Q@2 zF^8RhGmu)&gW~5Ow!%JWH*}5<)Uqi~c~?O6eiXemzggi;(DG=b(Y^t+um&TZuIie% zBX3oc#83G#);w6D3~*EOVUg6-{T?)2$AphcrI0(X@!ndu-@CnTZLI_hhJm+V>n`>F zHo6HXdw&tw%E z16zW3o24Hvn3Xr%UnsAhBh19c%MrRCSVOI2F2&J`cgG?aL-5cF;)`+tdtIM8t&FuO#4{h{TML8)4 zbnmz$x8>z{_t#cO>C`g5Vt1aKdM!0{mmBTuerC@3LvNhrjPz|^#7PUz+l@F!Y7^h& zxc>>u3iIN>i#q3+!YJKg^WKFqaa{!Zjh2rUgXs_G$8^tFti-)TTyu_eunt~U;kRU% z^9fyK$XBkI6QyxW<9B2%(-_XBag6azbCAX}tuOJ!YRHfwC5V$YUYgY$X!e1h?gh{l zK8tUr;O~7GDvt*2W*PfO=n#qo%Wp!)!l>LMi za7DDD8oSoJUmjjnwSCD|wGdnxU`)PD&m1qbwq{|!|9SUm{%WF|8dXe*R@v;n0%!n! zzsjoLdab75TvQKh&W3`E>f9gc7hTmB|FuC!&{b!Jbq^l?7tu$sl`v9YPrjr57}L13 z!5QDaJ3XwQH^rM*uJ`15A!+a>AwQtU?&g4vgTd%b^n(L16>uG(1aK_?YvJ7EN_Su{ zzU$L29XP^c+|`GYyGL^G*rF783abh$FuxkNV8pDOb7%iEJtbY$*rQ9jPVC;{x-yjO z&+>n|yV3Pz7p+FL#wB-_CWSEP7hQ#+4Wi>AO6u?C)qsSVT?Y6G>4+Cy!G z-=AvJJgdLRPp1SxaaCki+5Fj(t}&y6KYM^<(jui;T;C`R=x}l-wbJTOX*`70XQbvf zU*uBWBY9H(R1S2MRGupLdO1$l17|i3!m52zGs#C5#-3d9TVvI85@&m{W2-mKlQ!=V zPVP*0W_dAsYYr*vObelJOu9qsM2(MZXTh$8uRrQU-t^rEbqQEjG(hkE)?iHs@@rV} zTWwXolrK>bM;J7Yn9ifG!W!$-VYC>a6~>9s3r1W^VX~lE738kXcXNdq{hHp~ zGsetco#pktq4XfMaaPEk8kA4v>Z)?;vT+&zlg->6QYP1HQDI+EYSb!G=z^eKauJ zza&U^4O08gjH(RCcygqr`6h3-^^+y%qbXIXmqLzNVYe4L{1oH%|Es-k4{Pd5_g;JN z+_)$a&;p_cf{H{jpsjeB8bjD3!D_|M*VfLIi?m_1B_i#_+H;I(N7Q!4TRSRkN5!^Y zs+CyIDOHNC?MyqJ&Lp67Kzp&ZHKX(r1k8p2`F`&XSkIhuzWL{Sp6{IR5uTNOTbFme zYpr*!_1;$Ehf8A%zw%lj2j0Fmic$WQ_nB0_33}GZ%|3tV+nH4Dn_=#YIFU8X8g@%+ z)g`P)twH~9o0P~kp^%=4_E0{QAEi%1-q2f12AyD_jU2Ssea~P&90?z0Ocz?w!d^2^ zl^<=>+uRQY{eKH;8%WnukNQBaf_xrA(A1yNkv(T>16f?eH1JjdC2AV!TQ17!KslQF zi-_|f((`f+NDqm=-S>19>15?a%4M`XbjBdKFB))~Ib^S&BE=in8T4SrSLRWv&^m?A z<7c2BLuFIml+U{Qxcs<<@MwTKob5B7Ot$Ti8WR_@;`s2|Li%TE&rQv@_}d5tjkln}Bx+cZ&z{M{Q2B4(fk& zFMu;1-azsZdc&IXd!#5QSC6hm@@-;7@fYo~1~ z_l(c~LVD4Oun*zHM-tOAy`hlT8w+RKX~mLeBO1HcJhCOUez_Z5I?bs#)PqZ&Wxrg9 z8t5`@{Ejr#CRCH%^Z(?XW=pf}edr8!p=q|7hb|fvz-vK{rioT1dSC*+DRH{mTiy|= zfA&CIG3BH;R0nED!Z+cW>RE^|675E~pNXr|s>+KiR9WK@P+Pf`hWgq+Ar9edsLz3R z<-(H*!z`05*q52j_A2{XJITZCO}h|pC4#-)kZ)MXi+bQF7C1`68qexsX$w3PbKwgf zl2)AW**_k&g-wtxi4rrDb_0*^;cPj5u4+p9?v9(68+0Av0iD@A7G*uo8`Q zXP%0;j?7IcoOfd#O1&KLK?{ubmcG-mvh4;@mY71OzwCvLgiUF{ImfW)z)TE(C30`D zsj%0KheZvw^fj-&@h!moI>C)_2;l_68H6_xb|d)epE;p8Dxg16zY|g`Wt}$-U;8Sj zljTJy{h21=<)!&Ty6JkAlYeQ2BL*7orS9QrPr*aneeN{JV%3uKBUFpeCs>X;5-jZq z(Y_#`>-pEQGiCq6ZGG*_HkeHw%YrmVgpH_MtEGNH3D0O)ugDl&-j^w3`lrx>hmAM!eJ~=(Vsg|f z@ap6^OZtZD5!E#Gr3>0ZbKFDk=bfJ9IO2eG-Ez$lV~K$epJARc@BzbRzT5d8NASY; zohKnr(8U0rfr0V1G4P5!YE`P(<{dr=<(|I6I)afn2=RT9fbnB*IDS9$3c5dfL*>3V zc>SH1&emj2r-6U!SKAO?L^y~*Z9;8C+KwN+1G|1J?ZeT7(Yx+|RuyS7FnB;54k}1= zj?&z@E{etF#Y6K3Hc5qX@TD^t?6}(Fta0$u8ebTPF@S&?yn*l5Lep>rICmVLb=bWMK67u9$ctIH+bMB}1mCP)?l2F}k%Dil zWW#S}ZyCOmz3~P~Ew`N;cxXe&NH;Rl{M6PbiV=>n7?Dh`OI9ZFzNh+V&9u)94^gBY z4fv#b)$d3zVixpT;^1|%&tD-u4Snugtzu~o2){&}RH+A2`)*cw+N^3KkK%WS(tYoJANVlVPgt1t)N?2H)~N?IB~;`4{C7#< z#V(G3h6CwF^!fi7RO$Nse#q90jRsJzUUxjO9uIrl1bd&qz3=>02CcjwT88rI^N*BP zJaw^71)EQu84@~3=?cNqOf>SoGnf@UZY{JjghPFP4jS==kgMqm1jqvwkOyW2-}6$; zCKu?A4;LviJ{jz|dhv~H2jd({D@%=qCM^61WA3`-V!Ai*Z6N|| zhUbmNu0#imt+eF56)gEeMPl9<%vFvvcZiAB??q^rsw!n2LZ5gDTt&^aljh%xH#`Pd zltl^JX)7!-l6FqZ>Jf6kcvU<;7^f0(Mq<6IILx)j#FXYxtevY5bntu&cKx#}Y5w)C zCs+v1+|QrW}R%DvZnJpAw9-Egk-fGe3@{_Y1KO%dfn+$JboKaF}ALL z>Vm1z2=@jF+H_eCoR4#WwP|8oY+78IrEOW+r2LbCuI_NGniPSN(*<8r&x8fZNb^Y$ z5)WKI3hEb(#r1z<&z-qJCBgEs;mKtii9=;VWY8ARKsppB@GCUX>v zJRQZt5r!kgBWNkWW+5?QmziDEHby!U>$nVfrm%K0FwWIoRt}E^ys8$~}Nvs=r%xTi}kKWWg3vlm1p2mLPwQ}vfCbkk=pNarx~Y9JjLU+x>H z=F?rR5y_Ke-P0|>2lPqFWl1HKmn%!2Jejyr8s>a$^_Q!YD-Tp|lwzG5rIF6pp7`>K z}<)w7#G!-t%!;LEcWY(oMf3-AnKr<=IEN9^GYA)y-sC@2r6Q znG5)p!Q@LPAdwl@LNi6uC2r!Q6(8G9c4()L@hFm~XTa-%FQZ?PtnA3*NDjyQ()wA= z$?nRju(!kYpQm!}i4~W;uT>m{!pID0fiOF1d)0PVCcB{Dl-m5&Q(T8meW4)}P{g$u z%1)X`wkeVu;wfhZ@^TyC88|s(4COx4!uxvrCRjOeJh6+$y6JMo-)w1RoPMUbuX~^I zRj=l*&%CJX!+&UjADxPW-eenelHBvImvCFntTi07ANjOcQHD4CPV>LEVnxU++FJxV z*(71@2=j+7g{R2IXLA_|tBVRDhrm2^u!Ymn*eH358D4hR;~lNt0j8^JP2d$IDe#^f!to$b`X8W;SP4a*v|3!w3;QlEFG zyen-q?T}iHYwPmiv6t?q)p77pt2JqIu>0lg`lXgousmeX8zNb zC^jr4^PBd-mV_-!683D_;o5?gMYz~zi`8dm3$Umbx)Y~?>pBXyniJpwsoe<*|E3JD z0`RJ3`$VRD&;}k5w3i0=)rhl?W&y^sz*`;MR}=kGgKzo`PO%vB4zM-I?Y=b*@Cmve zserzVc3%ey8VOq$1>x8)E$O-R|3?S>9PnJ)B>0k@ zk;j3jbglt5PO3T+V2f$SNdyAwF)8~!+?i3AbSo@|m8LGI{j3zJ9O`47F7^^XZO>pD z?VSe1*WURzFRX*HqmkEz!?ZrEh};Y_JG|6dKW(U&&VeURHG=05{4R!%0H%-bV$qtW zv(nO}Snz>OkhUrGu#p~b2IauH2;*U+g1D2D>ZK2FrVb-K+>%lS8)r(T9ZaPiU+W#h zE7R!M7^^J#Pv%k*X?^7+oK6&BJtiDTRQK}N)xAPzO!fzmmesPM9?2$R*D;u`+<~Lnjt=3e0+Jpf3;VEx3Nz+lRuEOPdr#COY+fX z)Fw=0WLa-A9l_+U9|yhZbu3OlF8`CT;bL}nB*}a;6p%d>FJ{TFOA7nzl1dj>*Nr_> z;XvG_J5G&6eVxj!8&_!<)} zL}4WND;1DI4|I9il)rjt_o5mg`SuWgh$hgO&Bt{F;L_BqJ;Oj#9xNSdYse5%CAY>p z4=wUh7}<|^Kpreh@~7>^xnS4adr-m?16RC5V`PNymr?KvB&R^H!TA=E^o2Oz%n4XOhuy_bkZd$zIThCW&)p_#0&62oe4X}N_KemgN?_a2K zzl>HobXSe*6{*?C>7SAQ%~;DPo!^afz+&%=>2j{--9eoD#p(Zw+Wni-)DgG-dBj;( zS1&bVRmLSziXAStJx;7nV!q~nvX!GX?5_St_|)^iqCUS|1^-4qC5?U6d9tRTJYFHk zsp?;)j5_~Mz#(ujes3vZ_@KWAjJYNKBXCV zVY(KnS^@nj)*?NNYXq*_aE-)u7p}u_-Hq#TTwk32oD^kTq~bhE_>PB66Ffn9G4KDm zRc~5W_dMncW+2Jt&|dL#*@My-($e)`NV(g;knXRWFEaQRhz(a^G^Su2wyHZ^$>+lZ zs+}5mCD2TqZ7b-l>YNQ3L7V{jL_xEwwK}@j>2tbToeVyGvNmrsn|xcG7pvc{u5s-( zo33wkj%c_aEBzgP++AVmYFJ{qJ+lE1Gc0r8iTbYGbrxZ$Rb#uH%hhgR!9rs!UiE(G znOOUItPR5_FCxg|A|~~!IxnbwF7Di)8{UV$O)ThwYFaNO!J504^lrG`TNn{gExC8g zX4k#F8#-%T1yu!GW^So*?J+NEgT5J{(8y3+03R@J0yhy_BElrp^rHHbOE|Y&_D&S$ z29;j&7I>sBsKp-hPmuCYecW`-WKBn$<{OWa_H4f)%ZRSyXw8`4gZ_Mf1$`G7{$KLA zW2-{_yDu<~F$LpjJd@Uks^CKP+s+HsL?f7fhYPkTzJqq=C!RSOX-*)E9?viE$AqtShH@ zi9&MOsRh@c$iVkRjA{6bVu_f02YpCHdP?P3XOgcr+!AXt#rnCGOTjhg$6QDDoq#P~ zZ(wm9t(a-O@!~1U^RHg~PQF4~JTxCds*6zxpEHStq*0QePg3R4prYNduJ4A&I5vH> zG64T2tOVTI=|lP73YAHQicOm90aTgD6A;A$el|JipB1>jE=&HtpK3Prbqsy|F^_a> zxW{z;GUhI5!D2KjQisZMLgS6*KD>*Wh*@=G(u0MtKLTt4cal7E72}8EzAVprv6Ih+ zu2cT(jiZu*{q1g=4}Xjqi}Pr(O2b%WF=!J6b~6{u9{~sIuMR}QKIFYl4bNs_wtzMr zco#AznXmK|Veg^B?=UCGbJZo4iPjo4CxlxBaT_FqWEBoV_{0vN>U^OQ_N|?$pH8bI?^+hO?*YT|sRAXlT8 zTL+%WBXlSgv^e{j6R~2WWL$jIoYiFBT-&Y;8`rzJC#&gjJ0Dh6oz+B7c{$ZMy*gf0 zHp6NysN8ZwQfxcsYJdcPoO^#xYSVu4boJ}zF>Sro(4}QFZI6sT+^$eeww!du2KcT~ z0saP;`Y%|K@d4$CQSLoC5v_b6?79ha@r{ApK?{Q^qmQ{h^=9HMO3v3lFcTKRnsycY z&buc^)fyk*ukUb~Kv#YfyyBhU6O_kXX%usN(EqF8D4(E8b#r@$S$^N4;5NJVV^;yD zbW@VJJONyVqX%7w;uFMYU}G@b#@#*25^vcG+z2)Bac5+c-@%D?9xr${A7lUeal02j z1q>>QoBFMHrtJrBO#My`+&&At&0#V&748V~ICL2Hw*%DABW>`SZv6rN{r8#=vX{SN4hLP;r}pOnw=Apa>CH~poF+Ef=}Jv-x_*VV zu1d@oUpJ>VztXPIJTW@UHr_I>x4Lg+fbWhA@Te7@q+@6U1xt26l5J3Qije~7*+?>?(B&$E2teI!TO ziq=Kjkj7h^Hx+g-2VKeJO@a&hi-K`JLBs8t|vQ zBXyQzE{j-<=R}EXhX?x~Vtqk{-;-tAX3+qj=CS*76tJXWQ;xXPVEq?|HbPzg(&^2z zb$MOgY>~FL%DIG{b?$Jzg!U9F3dU|&zr>97$a=2Oyk4jRZs%haJpbLzJZ6a>_^Gm;qf?D(rVw*Pq?2+`=Xtb14WnKdnK`d2&o%48_#Q zF-gxYtNsI&c?`K7F~5S+ssU+@s~T`tRpS@4tU`?Su&1^)WHl1(;J7q@Rjso@CzEl$ zd$4i|aZSfTJgtz4;T_hrgxMyBzx%PiUW%Y-jAGDt zxQDd;sQzuiY+EWcil;fa>0i6JX|?9g3p23N)a3U1qx!Y*LZilAct1&@l)2G(?)3|h zPf&gjAUuSy5@81H$CToSHf{&=z0!XfHj#|{c!G)=2ek`h#aZzE#X$n7m8pWRhuf11 zTh9A~Dsggy0bh9d8-ZV$p1UA1y_I)PIOnbRJ3{+Z`+O>whi&YcPABTjn&=raQQ6<* zHrz|~qu}pADNZ^yGJj<5jO1Sbkv<(tDhS3{6AQsRs*90507GI>s9hAo!+k*k(nv^1 z2eHs~fh0b^hixHb8xr{h;B?^AH&Olv;a8K&+=Xx}ZpYPU!-r5fyg)?DTHSnFe3B0Q z_VP}~#oHn6NGb1}hr5Sxzr3>wcL`QllG=#ZNVE1zcovR#e5BT&k9W-TDY);wBNjwB zK2?uCAAvlogb4c;ZzSF+@TSo01tt+gZd~#--VqNgKPveavaJt=-+lTR;H1Y zA^=TraGBT0Wh2KF#IsN=HRK3N#G*W3lH#-^^A1D4%Lil9x&v{I!ghj-`o*CC9{7gc z>JYGF*BHpoSp&9r9=05>{{G5Cy?ZqdvC4dLRf6mG`qnLx$%rdOS2cA;C$*7&|CN<@J$K40N>spxCd~4<9ps6`S`LJ{>4#Wy?vK7-bl6Bh->J+ zYVdx-;QI-9PxthGf)Nx1y%uE@s(gifVcH1KP-}cD?**w;vX*}r+`LHU#NFv&wbuk+ zV^8K%Ux#1PUd0ijwdB*BrLOCG>W#0x@HMRB(jkdi&*OLZdY0be(WOOsUV>Fil;K>f z-qH%s0vBcL^iSN1BShuK_J84JX-$YT!Wd>aHRv%U>;Qc3?~h_Xyd1?Y4_;sS9sKGK z-Z$a?Jc8m7^S#mgk`%2?h0XS?0m26ld}Mk{K$*g^>NW7HgWx#P_o|)Nt!eJCj-c_3YXB!=Y3o2)4Vs(d^9%L5?iFvacWmCX zG)AJ&cA8>FbJ^F-G0Bc(pOKHr*4wkI&&IKiFctQ1H7CvCtp;mE-GJZKyHVn{h2Ri+ zFJD>hh%g9E(;P2Ig7Zckc72-Fuv7h`ht=#Iz0<|*#5y%%4CdO0d$;;I_$g_;NGq|s z`!d80nB%HIX?D*C?^&GL#?@>v-_`O~2N$LCT!2;Ahy`1rjgJ%bG%V?3*Y2D#t;B)C z8Ob|1-&IKi-a0IZr~S@6%5wa(9bTf(G`1QbL!-ZwxY8VZ__ZiT^XmzRU|EX&Gnavu zlUw-+bBgdA^gFyXI#VKujI;cZvVYfGJMNCfj0Ub!n+9lXg><)2j2}n z|7X`th%?|%>xtImd?~rboIRc^JJILocKz#d>SsJ`80U5!Ii6kpy_YwrVG(=>EQ9BE z?Z+El{Mx%&j=NuudmZq_Y6twqJ)u-D92Xod-t}^fZ8u{?8_9o5^>OMq8uXhfR4e)2 zKjU3_=UJi4t9PKCqiZw*dR9#KG4v{4-*P3?r{~`I#qm(Dwg<=q!`!aoxW|)W5yU?p<-RveaESfhr_?h+#8FIGvC4<`5EPR9!Fm_9ilw7pnsTg=H8fb9M+^n z&0|`h#+opoh6W|&pNafAu?TTT7zJNHc*Yv&46W-Lcbt0h{(iv6j#Djk$qk$#RgCro z{ZIF^sdKvyA5$C&Y0xZ(egwvU4^f2A)pNUFJXUuFc0&E_7#~{gOro+1q1_^vz7sSU zl!a};HFRGk-_L1K)&K^bOeuT;Y0WnT=XbriH)pF>nUbin%JBV0QTAi(ee+Ac(LsOA zgVFTxv2l6uBYo197UXsHKJ8{bspV_P75 zxnjxhU*+NHKHg!34C9KNR;rJ?hgP^0$5l8(6#l@HI@W(#y%qbC2=OHDHrEx@<=w=2 zX#T~D5%Vwghv$RFIu8DolVlo)Y{8WoTE$$$tP34mxg0*1{9g3g?5nqMiNy?#pmSZJ?(6js;XGEOPRx$n8DfpY6wAO-&ZljONV zUl!58={mjRpn*tKO!DsP!EW22y0{N#V_tEOUXTe|?H@Q`ySzEmGR<o^~Yk^{G=s zcgs%UWI&wZ0YAq)+0l{j@AHzT@pU;8VOS|O zQ9P_G=8nkdqWTkH>QAtq80giBA9_P@M&zR$ z@N$_YW%>@6QkJ>Bx$z~dzH-t^ zy<~mzYFG?Ha%_Okw6mBnFdp^Fkl6EsZ@M=@I-Y`6&qRqduR^%c)`#b_BqbyTJ^ru( zN|geu`9Wb`hCx>Yx5>xwGnIcwOEyh%K3|o+v9RShybrA z8dAPOUYC~R(ciPBdb(p5v>5Xox>-Wn9PHe9A0IPns-a;_o*H{{UgrJqusMdGXvmA= zaGEwz!A>tTba=k?#(?+wc|W|^7_NEptD|$LuTo&O{R~dH4f-cYQAqQ-`~6N6KS>Ow zgT9R68)%rez#|SbxZMk?L+?MvZvBrkoG!pgW5PXnrF*iun6WTXY=>o-8nz~C_fg#4 z?>Xu?Y0kvykw0l>q2^wy=5hD!LPlH(D z`*OZ_$3qY57i;#KRi4Edm-j+bx@-;AP1C_?zW0uWkB<2m+n;*+{hNE*yrV%oLa@@f z`vr6@-}BsoI5=@Twb8>SpK{0g<_d3l4x{yGm9(aZt07+kZ@3RT5@Zh6!5+<}LAtjv z*2gJi`6u`gvZlu->i<;NJx@om)3@Nj_@>s%r|ey^L7J@HV}t)m(@trQX-mT1;*Z*QR7UVtoo7Gk)ZUe>k8LW zEf*uTdi6%>ZdSV~RqoBSXZ*0~z=~SiE~KtWAPt99^7T`o$*XbExXbP_-lXxN^DeTW zmBkq4zZ|mM)v#UtYD=wc$=ft`qwC;}9=x}GE6zzHwD9VEWArU~hP{m*9vH>nVJ%#n zVd?i@3t?`_#2!=!IuX^}WPnfzV8yEb)_!o#ioC`{#{=d?*U697+=C%JAXEX1G{ zhPU!wl__$a(j2W*`iag7nB;eI-h3|kUhDDqoC=jS^xQG{yc#rt_U)wQOYm-8qcZe( zK3=mJ{=!D3sauC;!8Y~xfcRVQsQj&tjnWjOq9a`xj(()*m@0(mKn5swg7o7!zo0&k z6wv3~AJ}uX?(!ov6@%*BirI|fP ztLH3yp8T7pU4(uLtqwLzoG#(g^T7NbbDW1y^Lvi0IBKg&I9e<~x@0s$dxsweDlr52 zzj_P)mjE`>D0?1o?J<7}P4WvK;)NGT+#Z&C&OD#Isk2L3hQdL(j)pd^vE`yQ2DR zLwhx67zrJaC?oiTb=1dk4-N1rV*z}|(tCK%gG@cS5i%}cr5;nuIc7#8b!3nLn*-X?wGt;_?ry_4cRfIOOSw%rAFE!ysYcYOMz zYc;L$=?Sd@_RxN3G=6{QeBtCSX%1FNpLuG3U6QwaLE&2XIOq|^p1)WKD44yPYNh{+n?MVZZ;LURaiNjN(;?DgE4DRZUD?qclo4t09!j zKFY;>VAH4G=(oAqquMOyjI?XthI0jpX;EGaSM5mswlD=^AF zxvHKPwv)VFj6*LP#q)Y9$2pv z?@M^&1V5{=^A9nKiX{Z zZ?B{o=6Sx_Li0pIU9BxNPtYupy9o0{U)bAxAdzP;6t0~u7GZAq0kk6QLhXuamfDig z3=!%Hd#?P-9Nm_gV5+NKA?y$=Gvl(v+7f8F^ay)t95TqSsgKXGH+Bzp9uyu!5#_}A_CZ$Np%h0rc_Zr{19!=Rtn)V2Ni z5WS|I+|1+box84}hkz=q!+v;J`ns;E$1krR zwidmMXx^MFv`Xr7(^%Rm`Mfm=wV{)l#s zM0;<FL~eJQ-4k4fd7Y68>-JZ7%X4;)U%!8!bo_P7~|$rqdfd1)Ae8`I5k)q z?}g>oU=3ycEbQn>KWjdZc@i3)>wF%X`AmjDTf3K^6e*`MCGRzNHedD{>%%SE&8)@P znD0(rn z6T~66oXxdFR13~RlCU*t(qdY+s-JV?1ao1F`z-L*oN)1$xXzXgiJy&Ayl1cVDrU8{ zaI=87*_2C9bA&^%TOHZon5w^yWojz;lBj2to79pYj1TxT1_TaoaoO_ct zyj2i+v_oDU&NqzYW6Rhg!*2pYz@#9pN1=VAz_oAW{tu8g(l zFljx!?LY@CC9i=i=X1{0cos{Fskgql;T9aD>tOLPmthx$F^Ih*@hzO!IDR8pqem_B zz#n5&re~p~SUAF`h@##W;jZm~C2e*z@>DeC;|;wX{WIb zUsLjlO(oZqLNWQ%RUG#E>c=mF?ac5|;EhF!QGeNb;#=``*d9gwg?@hvn;W!5#5*0y zQNX4)y;ac>yxUG`BkKb9I^fzQ-iqDMzq_L`9|`~IR%j3E0hoz5;_o2G-7f>+U0(2&=_ zCc%eFY9e^?!3@%_hu_hLV;!2v$F3^=%zP^8Gf8pjGhmp`OTij=zUC!`_-m0F7J9ar zcZp?fgr6mKTrc1G9`LE_@W+CPlszkZz3Yeb%oDjJk~X&?#1T zz?WoOV!I%yAjNR{#;xCjwQ1sd{_YaXvV?=KlK~0dlC{$;*8~d-sQs_Wu@wqX>uFA3 z`1;onTfKg{h0iiwQ<=UV%_fYgJ7;4053aK83`jS9o3@(6+SnB4`@zX3;4No_bb;QV z?A>sKrRRCJLNBSx%&zx1HzpJT4o>&7$fjI!Bknz)dHIIm=TtB9tg^#NK0i>C`KZGH zH$fL!7w~tg12H2&m*x!*2)=6Q!z8wIoX`=>;+w0H>&MP)F%0!UOrJWS9bvh~S%wGL zz^geFQ?5q_-~1~4xtwxp;Jc3T->bSFAw!|*2+nf)s-4xq(KF6N9lWN<5`v}27Z!qL z9t#W~__YiR#{s9)S2q7T=ND~!R*}VYO=H>@;CJnBAI|MJe<;2x{yIKN)azn8sxTSF6~B5MI&tLaT{d9-6S7+f7{d5XMf1aBx>dWcD>xHcGznvRTo~ zcM&E80i|b6ZGIWvJ^8E>OU>xbXnX$+bzsyq^ptt3!0=AUiR5~rr(}(4-U{!Q{M|*C zj}pXdOw978V^6E}s>I7z;za3+HRo`1Lp!fo-X1xe%c}K?;x%X_?edzSA>|g=dimK! zmQ1fv4D(XoF?)$tN%aV)HK$+3Ty^Wc25SvEvs=jOZ$-SDf1zhie%9=Pjk)}N-tr=g zcpqC>y4qG*w7Rmagc;Lkrf2F_XG}Nh?n=){&$yYv(2d@6`r5+|{m9RMh>jk7M>e3y z>5+;j$|~98OGF(EJK)13bK3Mf@0yV{bDlZdk|W9=3Q8-=idM2PMM3!^m6p;c%SuXT z=~w|vXOEXx>K-Y7;_>Cf!dd&AZODOg(J>xP&6`k=M0X+h$?nj}CYhO;EzBv%T{^E| z>4Iz~&Rd#smrkdfleE0Nq;ieDRA;MvY~|hIbL7jM;_~Hd?w+%{q@v7Td3X5A@{*#; zvhv5%D@yGvi%LpUlWmoi_SLhdPAz_7&10oSCFPGiQd*i`QvTS~$BHUeq!*V}tX`cw zX?FOWse@U}nHtJ*YN!-I$QIlqE_xVHFTH=MY+JVA9!t*sfGBtALiQl+lCtkxvLxr8 zr80UbeiGz>YY<2txbP6{-w%i6gfAV9V#g2~5f;84#mbI_-v5ZwqT-U}rH?#nEBnQY zm5)7MZeLZgy7Gx9f9Y8B6y-~Qsm}!05w_(1AirU(s|vb38{j7zVer*|yGy8r85=(B zlcUq4AG`AH=Smjw-ww*#0NuvG|DPBL;_vBopbt0$Fvx&m4bz4xK)}P1i(1L^Cc0G7B}@T( zM~!N+Qmqt1uQ$lAc`Hc%|CJJ=QSJ{sXWFZu1RyzH;QC?nOXqe_ARF-OxRPH0nrO`B z<&`VTi3-X+$D=4+=bQ!u# zW9D>Bq5q@)i01Exj)0ayFN$3X!kR<=`&ka=KGM>hj0`KIyT~uWvG4;TeU0 cUt%RP3zb-0hGa?=Wh~(TFs>K>hOg{@1BBmfGXMYp literal 0 HcmV?d00001 diff --git a/bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.hex b/bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.hex new file mode 100644 index 000000000..37750fc2c --- /dev/null +++ b/bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.hex @@ -0,0 +1,11851 @@ +:04000003F000B2CD8A +:020000040000FA +:1000000000040020810A000015070000610A0000BA +:100010001F07000029070000330700000000000050 +:10002000000000000000000000000000A50A000021 +:100030003D070000000000004707000051070000D6 +:100040005B070000650700006F07000079070000EC +:10005000830700008D07000097070000A10700003C +:10006000AB070000B5070000BF070000C90700008C +:10007000D3070000DD070000E7070000F1070000DC +:10008000FB070000050800000F0800001908000029 +:10009000230800002D080000370800004108000078 +:1000A0004B080000550800005F08000069080000C8 +:1000B000730800007D080000870800009108000018 +:1000C0009B080000A5080000AF080000B908000068 +:1000D000C3080000CD080000D7080000E1080000B8 +:1000E000EB080000F5080000FF0800000909000007 +:1000F000130900001D090000270900003109000054 +:100100003B0900001FB500F003F88DE80F001FBD8C +:1001100000F0ACBC40F6FC7108684FF01022401CA7 +:1001200008D00868401C09D00868401C04D0086842 +:1001300000F037BA9069F5E79069F9E7704770B554 +:100140000B46010B184400F6FF70040B4FF0805073 +:100150000022090303692403406943431D1B104621 +:1001600000F048FA29462046BDE8704000F042BA47 +:10017000F0B54FF6FF734FF4B4751A466E1E11E0DA +:10018000A94201D3344600E00C46091B30F8027B3B +:10019000641E3B441A44F9D19CB204EB134394B25D +:1001A00004EB12420029EBD198B200EB134002EBB2 +:1001B000124140EA0140F0BDF34992B00446D1E952 +:1001C0000001CDE91001FF224021684600F0F4FB58 +:1001D00094E80F008DE80F00684610A902E004C8FB +:1001E00041F8042D8842FAD110216846FFF7C0FF7C +:1001F0001090AA208DF8440000F099F9FFF78AFFCB +:1002000040F6FC7420684FF01025401C0FD0206889 +:1002100010226946803000F078F92068401C08D030 +:100220002068082210A900F070F900F061F9A869AF +:10023000EEE7A869F5E74FF080500369406940F6A2 +:10024000FC71434308684FF01022401C06D0086838 +:1002500000F58050834203D2092070479069F7E788 +:100260000868401C04D00868401C03D00020704778 +:100270009069F9E70420704770B504460068C34DE3 +:10028000072876D2DFE800F033041929631E250021 +:10029000D4E9026564682946304600F062F92A46CE +:1002A0002146304600F031F9AA002146304600F0E0 +:1002B00057FB002800D0032070BD00F009FC4FF46C +:1002C000805007E0201D00F040F90028F4D100F034 +:1002D000FFFB60682860002070BD241D94E80700C3 +:1002E000920000F03DFB0028F6D00E2070BDFFF715 +:1002F000A2FF0028FAD1D4E901034FF0805100EBAE +:10030000830208694D69684382420ED840F6F8704E +:1003100005684FF010226D1C09D0056805EB8305B8 +:100320000B6949694B439D4203D9092070BD55694A +:10033000F4E70168491C03D00068401C02D003E0C8 +:100340005069FAE70F2070BD2046FFF735FFFFF731 +:1003500072FF0028F7D1201D00F0F7F80028F2D135 +:1003600060680028F0D100F0E2F8FFF7D3FE00F05B +:10037000BFF8072070BD10B50C46182802D0012028 +:10038000086010BD2068FFF777FF206010BD41684E +:10039000054609B1012700E0002740F6F8742068FF +:1003A0004FF01026401C2BD02068AA68920000F065 +:1003B000D7FA38B3A86881002068401C27D020688D +:1003C000FFF7BDFED7B12068401C22D026684FF051 +:1003D0008050AC686D68016942695143A9420DD9EA +:1003E000016940694143A14208D92146304600F0E5 +:1003F000B8F822462946304600F087F800F078F831 +:100400007069D2E700F093F8FFF784FEF6E77069B1 +:10041000D6E77669DBE740F6FC7420684FF01026DB +:10042000401C23D02068401C0CD02068401C1FD0EA +:100430002568206805F18005401C1BD027683879A5 +:10044000AA2819D040F6F8700168491C42D001680A +:10045000491C45D00168491C3ED001680968491C07 +:100460003ED00168491C39D000683EE0B069DAE747 +:10047000B569DEE7B769E2E710212846FFF778FEA5 +:100480003968814222D12068401C05D0D4F8001080 +:1004900001F18002C03107E0B169F9E730B108CA63 +:1004A00051F8040D984201D1012000E000208A4259 +:1004B000F4D158B1286810B1042803D0FEE72846CB +:1004C000FFF765FF3149686808600EE0FFF722FE1C +:1004D00000F00EF87169BBE77169BFE7706904E06D +:1004E0004FF480500168491C01D000F0CBFAFEE7C0 +:1004F000BFF34F8F26480168264A01F4E06111439B +:100500000160BFF34F8F00BFFDE72DE9F0411746B3 +:100510000D460646002406E03046296800F054F8EF +:10052000641C2D1D361DBC42F6D3BDE8F08140F69B +:10053000FC700168491C04D0D0F800004FF48051D1 +:10054000FDE54FF010208069F8E74FF080510A690F +:10055000496900684A43824201D810207047002050 +:10056000704770B50C4605464FF4806608E0284693 +:1005700000F017F8B44205D3A4F5806405F5805562 +:10058000002CF4D170BD0000F40A0000000000202F +:100590000CED00E00400FA05144801680029FCD0C5 +:1005A0007047134A0221116010490B68002BFCD0E0 +:1005B0000F4B1B1D186008680028FCD0002010603D +:1005C00008680028FCD07047094B10B501221A605A +:1005D000064A1468002CFCD0016010680028FCD08A +:1005E0000020186010680028FCD010BD00E4014015 +:1005F00004E5014070B50C46054600F073F810B9EB +:1006000000F07EF828B121462846BDE8704000F091 +:1006100007B821462846BDE8704000F037B8000012 +:100620007FB5002200920192029203920A0B000B06 +:100630006946012302440AE0440900F01F0651F80C +:10064000245003FA06F6354341F82450401C8242F8 +:10065000F2D80D490868009A10430860081D016827 +:10066000019A1143016000F03DF800280AD00649C4 +:1006700010310868029A10430860091D0868039A3F +:10068000104308607FBD00000006004030B50F4CED +:10069000002200BF04EB0213D3F800582DB9D3F8A1 +:1006A000045815B9D3F808581DB1521C082AF1D3C3 +:1006B00030BD082AFCD204EB0212C2F80008C3F8CD +:1006C00004180220C3F8080830BD000000E0014013 +:1006D0004FF08050D0F83001082801D0002070473A +:1006E000012070474FF08050D0F83011062905D016 +:1006F000D0F83001401C01D0002070470120704725 +:100700004FF08050D0F830010A2801D00020704707 +:100710000120704708208F490968095808471020B0 +:100720008C4909680958084714208A4909680958FA +:100730000847182087490968095808473020854923 +:100740000968095808473820824909680958084744 +:100750003C20804909680958084740207D490968BC +:100760000958084744207B49096809580847482028 +:1007700078490968095808474C207649096809589A +:10078000084750207349096809580847542071499F +:1007900009680958084758206E49096809580847E8 +:1007A0005C206C4909680958084760206949096854 +:1007B00009580847642067490968095808476820AC +:1007C00064490968095808476C2062490968095852 +:1007D000084770205F4909680958084774205D4937 +:1007E00009680958084778205A490968095808478C +:1007F0007C205849096809580847802055490968EC +:10080000095808478420534909680958084788202F +:1008100050490968095808478C204E490968095809 +:10082000084790204B4909680958084794204949CE +:10083000096809580847982046490968095808472F +:100840009C204449096809580847A0204149096883 +:1008500009580847A4203F49096809580847A820B3 +:100860003C49096809580847AC203A4909680958C1 +:100870000847B0203749096809580847B420354966 +:10088000096809580847B8203249096809580847D3 +:10089000BC203049096809580847C0202D4909681B +:1008A00009580847C4202B49096809580847C82037 +:1008B0002849096809580847CC2026490968095879 +:1008C0000847D0202349096809580847D4202149FE +:1008D000096809580847D8201E4909680958084777 +:1008E000DC201C49096809580847E02019490968B3 +:1008F00009580847E4201749096809580847E820BB +:100900001449096809580847EC2012490968095830 +:100910000847F0200F49096809580847F4200D4995 +:10092000096809580847F8200A490968095808471A +:10093000FC2008490968095808475FF48070054998 +:10094000096809580847000003480449024A034B54 +:100950007047000000000020000B0000000B0000AA +:1009600040EA010310B59B070FD1042A0DD310C82C +:1009700008C9121F9C42F8D020BA19BA884201D97E +:10098000012010BD4FF0FF3010BD1AB1D30703D0C6 +:10099000521C07E0002010BD10F8013B11F8014B7C +:1009A0001B1B07D110F8013B11F8014B1B1B01D198 +:1009B000921EF1D1184610BD02F0FF0343EA032254 +:1009C00042EA024200F005B87047704770474FF0A6 +:1009D00000020429C0F0128010F0030C00F01B800C +:1009E000CCF1040CBCF1020F18BF00F8012BA8BF1A +:1009F00020F8022BA1EB0C0100F00DB85FEAC17CDE +:100A000024BF00F8012B00F8012B48BF00F8012B90 +:100A100070474FF0000200B51346944696462039C1 +:100A200022BFA0E80C50A0E80C50B1F12001BFF4A7 +:100A3000F7AF090728BFA0E80C5048BF0CC05DF80D +:100A400004EB890028BF40F8042B08BF704748BF5B +:100A500020F8022B11F0804F18BF00F8012B7047CF +:100A6000014B1B68DB6818470000002009480A4951 +:100A70007047FFF7FBFFFFF745FB00BD20BFFDE719 +:100A8000064B1847064A1060016881F308884068E1 +:100A900000470000000B0000000B000017040000DE +:100AA000000000201EF0040F0CBFEFF30881EFF3ED +:100AB0000981886902380078182803D100E0000015 +:100AC000074A1047074A12682C3212681047000084 +:100AD00000B5054B1B68054A9B58984700BD0000B0 +:100AE0007703000000000020F00A0000040000006E +:100AF000001000000000000000FFFFFF0090D00386 +:10100000C8130020395E020085C100009F5D020008 +:1010100085C1000085C1000085C1000000000000FE +:10102000000000000000000000000000C55E02009B +:1010300085C100000000000085C1000085C10000DE +:101040002D5F0200335F020085C1000085C10000F2 +:1010500085C1000085C1000085C1000085C1000078 +:10106000395F020085C1000085C100003F5F0200BA +:1010700085C10000455F02004B5F0200515F020026 +:1010800085C1000085C1000085C1000085C1000048 +:1010900085C1000085C1000085C1000085C1000038 +:1010A00085C10000575F020085C1000085C10000B6 +:1010B00085C1000085C1000085C1000085C1000018 +:1010C0005D5F020085C1000085C1000085C1000090 +:1010D00085C1000085C1000085C1000085C10000F8 +:1010E00085C1000085C1000085C1000085C10000E8 +:1010F00085C1000085C1000085C1000085C10000D8 +:1011000085C1000085C1000000F002F824F083FED4 +:101110000AA090E8000C82448344AAF10107DA4552 +:1011200001D124F078FEAFF2090EBAE80F0013F0F7 +:10113000010F18BFFB1A43F00103184718530200B0 +:10114000385302000A444FF0000C10F8013B13F032 +:10115000070408BF10F8014B1D1108BF10F8015B10 +:10116000641E05D010F8016B641E01F8016BF9D103 +:1011700013F0080F1EBF10F8014BAD1C0C1B09D15A +:101180006D1E58BF01F801CBFAD505E014F8016BCC +:1011900001F8016B6D1EF9D59142D6D3704700005E +:1011A0000023002400250026103A28BF78C1FBD870 +:1011B000520728BF30C148BF0B6070471FB500F011 +:1011C00003F88DE80F001FBD24F022BE70B51A4C45 +:1011D00005460A202070A01C00F0D5F85920A080F8 +:1011E00029462046BDE8704008F082B908F08BB966 +:1011F00070B50C461149097829B1A0F160015E294A +:1012000008D3012013E0602804D0692802D043F2FB +:1012100001000CE020CC0A4E94E80E0006EB8000A2 +:10122000A0F58050241FD0F8806E2846B04720607B +:1012300070BD012070470000080000201C00002045 +:10124000A05F02003249884201D20120704700208D +:10125000704770B50446A0F500002E4EB0F1786FCF +:1012600002D23444A4F500042948844201D2012565 +:1012700000E0002500F043F848B125B9B44204D39A +:101280002548006808E0012070BD002070BD002DD9 +:10129000F9D1B442F9D321488442F6D2F3E710B52C +:1012A0000446A0F50000B0F1786F03D21948044459 +:1012B000A4F5000400F023F84FF0804130B1164847 +:1012C000006804E08C4204D2012003E01348844209 +:1012D000F8D2002080F0010010BD10B520B1FFF75A +:1012E000DEFF08B1012010BD002010BD10B520B1F7 +:1012F000FFF7AFFF08B1012010BD002010BD084866 +:1013000008490068884201D10120704700207047D9 +:1013100000700200000000202000002008000020D3 +:101320005C000020BEBAFECA10B5044600210120B0 +:1013300000F042F800210B2000F03EF800210820C8 +:1013400000F03AF80421192000F036F804210D20AD +:1013500000F032F804210E2000F02EF804210F20B6 +:1013600000F02AF80421C84300F026F806211620D0 +:1013700000F022F80621152000F01EF82046FFF7A5 +:1013800025FF002010BD40F2231101807047FFF7B8 +:101390002DBF1148704710487047104A10B51468A7 +:1013A0000E4B0F4A08331A60FFF722FF0B48001D4F +:1013B000046010BD704770474907090E002804DB20 +:1013C00000F1E02080F80014704700F00F0000F1F9 +:1013D000E02080F8141D704703F900421005024018 +:1013E00001000001FD48002101604160018170475A +:1013F0002DE9FF4F93B09B46209F160004460DD069 +:101400001046FFF726FF18B1102017B0BDE8F08F87 +:101410003146012001F0D3FE0028F6D101258DF8D8 +:1014200042504FF4C050ADF84000002210A92846A9 +:1014300006F0C5FC0028E8D18DF84250A8464FF4CC +:1014400028500025ADF840001C2229466846079523 +:101450000DF01DF89DF81C000DF11C0A20F00F0086 +:10146000401C20F0F00010308DF81C0020788DF822 +:101470001D0061789DF81E000DF1400961F34200E6 +:1014800040F001008DF81E009DF8000008AA40F011 +:1014900002008DF800002089ADF83000ADF8325020 +:1014A0006089ADF83400CDF82CA060680E900AA9D0 +:1014B000CDF82890684606F090FA0028A5D160681B +:1014C000FFF70BFF40B16068FFF710FF20B96078AD +:1014D00000F00300022801D0012000E00020BF4CF2 +:1014E00008AA0AA92072BDF8200020808DF8428049 +:1014F00042F60120ADF840009DF81E0020F00600E5 +:10150000801C20F001008DF81E000220ADF8300094 +:10151000ADF8340014A80E90684606F05EFA002874 +:1015200089D1BDF82000608036B1211D304600F021 +:101530005FF90028C2D109E0BBF1000F05D00CF023 +:1015400021FDE8BB0CF01EFDD0BBA58017B1012F1B +:1015500043D04AE08DF8428042F6A620ADF8400024 +:1015600046461C220021684607950CF090FF9DF826 +:101570001C00ADF8346020F00F00401C20F0F0009B +:1015800010308DF81C009DF81D0020F0FF008DF834 +:101590001D009DF81E0020F0060040F00100801C98 +:1015A0008DF81E009DF800008DF8446040F00200A8 +:1015B0008DF80000CDE90A9AADF8306011A800E07E +:1015C00011E00E9008AA0AA9684606F006FA00285B +:1015D000A6D1BDF82000E08008E00CF0D3FC10B9E3 +:1015E0000CF0D0FC08B103200FE7E58000200CE7E9 +:1015F0003EB50446794D0820ADF80000A88828B112 +:101600002046FFF726FE18B110203EBD06203EBD45 +:101610002146012001F0D3FD0028F8D12088ADF843 +:1016200004006088ADF80600A088ADF80800E088E6 +:10163000ADF80A00A88801AB6A46002106F0AAFDB1 +:10164000BDF800100829E2D003203EBD7FB5634DF0 +:101650000446A88868B1002002900820ADF8080070 +:10166000CDF80CD02046FFF7F4FD20B1102004B0D7 +:1016700070BD0620FBE7A98802AA4FF6FF7006F0AE +:10168000CCFF0028F3D1BDF80810082901D00320B1 +:10169000EDE7BDF800102180BDF802106180BDF8B3 +:1016A0000410A180BDF80610E180E0E701B582B02A +:1016B0000220ADF80000494802AB6A46408800218C +:1016C00006F068FDBDF80010022900D003200EBD11 +:1016D0001CB5002100910221ADF800100190FFF728 +:1016E000DEFD08B110201CBD3C486A4641884FF61B +:1016F000FF7006F092FFBDF800100229F3D003201E +:101700001CBDFEB5354C06461546207A0F46C0076F +:1017100005D00846FFF79DFD18B11020FEBD0F2033 +:10172000FEBDF82D01D90C20FEBD3046FFF791FD1E +:1017300018BB208801A905F03AFE0028F4D13078C2 +:101740008DF80500208801A906F003FD0028EBD1E3 +:1017500000909DF800009DF8051040F002008DF803 +:101760000000090703D040F008008DF80000208831 +:10177000694606F08BFC0028D6D1ADF808502088C9 +:101780003B4602AA002106F005FDBDF80810A9425B +:10179000CAD00320FEBD7CB5054600200090019014 +:1017A0000888ADF800000C4628460195FFF795FD26 +:1017B00018B92046FFF773FD08B110207CBD15B1A4 +:1017C000BDF8000060B105486A4601884FF6FF7019 +:1017D00006F023FFBDF8001021807CBD240200200C +:1017E0000C20FAE72F48C088002800D0012070475D +:1017F00030B5044693B000200D46014600901422F7 +:1018000001A80CF044FE1C22002108A80CF03FFEA9 +:101810009DF80000CDF808D020F00F00401C20F00B +:10182000F00010308DF800009DF8010006AA20F0AD +:10183000FF008DF801009DF8200001A940F0020092 +:101840008DF8200001208DF8460042F60420ADF806 +:10185000440011A801902088ADF83C006088ADF8E4 +:101860003E00A088ADF84000E088ADF842009DF849 +:10187000020020F00600801C20F001008DF802001C +:101880000820ADF80C00ADF810000FA8059008A8CE +:1018900006F0A3F8002803D1BDF818002880002026 +:1018A00013B030BD24020020F0B5007B059F1E461A +:1018B00014460D46012800D0FFDF0C2030803A206E +:1018C0003880002C08D0287A032806D0287B0128ED +:1018D00000D0FFDF17206081F0BDA889FBE72DE96C +:1018E000F0470D4686B095F80C900E991446B9F164 +:1018F000010F0BD01022007B2E8A9046052807D0BE +:10190000062839D0FFDF06B0BDE8F0870222F2E7F3 +:10191000E8890C2200EB400002EB400018803320E5 +:101920000880002CEFD0E8896081002720E0009635 +:10193000688808F1020301AA696900F097FF06EBC5 +:101940000800801C07EB470186B204EB4102BDF89A +:1019500004009081F848007808B1012300E00023DA +:101960000DF1060140460E3214F029F87F1CBFB27B +:101970006089B842DBD8C6E734200880E889B9F12D +:10198000010F11D0122148430E301880002CBAD01C +:10199000E88960814846B9F1010F00D00220207328 +:1019A00000270DF1040A1FE00621ECE70096688885 +:1019B00008F1020301AA696900F058FF06EB08006C +:1019C000801C86B2B9F1010F12D007EBC70004EBFF +:1019D0004000BDF80410C18110220AF1020110304C +:1019E0000CF02BFD7F1CBFB26089B842DED88AE7BD +:1019F00007EB470104EB4102BDF80400D0810AF176 +:101A000002014046103213F0FCFFEBE72DE9F047EE +:101A10000E4688B090F80CC096F80C80378AF5898D +:101A20000C20DFF81493109902F10C04BCF1030FA1 +:101A300008D0BCF1040F3DD0BCF1070F75D0FFDF1B +:101A400008B061E705EB850C00EB4C0018803120F5 +:101A50000880002AF4D0A8F1060000F0FF0A5581A2 +:101A600024E01622002101A80CF011FD00977088D7 +:101A7000434601AA716900F0F9FEBDF80400208018 +:101A8000BDF80600E080BDF80800208199F800004C +:101A900008B1012300E00023A21C0DF10A01504609 +:101AA00013F08DFF07EB080087B20A346D1EADB24C +:101AB000D7D2C5E705EB850C00EB4C00188032202F +:101AC0000880002ABCD0A8F1050000F0FF0A55816B +:101AD00037E000977088434601AA716900F0C6FE9E +:101AE0009DF80600BDF80410E1802179420860F3FA +:101AF000000162F34101820862F38201C20862F3CD +:101B0000C301020962F30411420962F3451182091B +:101B100062F386112171C0096071BDF80700208150 +:101B200099F8000010B1012301E00EE000232246E5 +:101B30000DF10901504613F042FF07EB080087B290 +:101B40000A346D1EADB2C4D27AE7A8F1020084B2A5 +:101B500005FB08FC0CF10E00188035200880002AD7 +:101B6000A7D05581948100971FFA8CF370880E32AC +:101B7000716900F07BFE63E72DE9F84F1E460A9D70 +:101B80000C4681462AB1607A00F58070D080E089E9 +:101B9000108199F80C000C274FF000084FF00E0A46 +:101BA0000D2872D2DFE800F09D070E1B272F374566 +:101BB000546972727200214648460095FFF774FE20 +:101BC000BDE8F88F207B9146082802D0032800D07A +:101BD000FFDF3780302009E0A9F80A80F0E7207B9A +:101BE0009146042800D0FFDF378031202880B9F1EA +:101BF000000FF1D1E4E7207B9146042800D0FFDFFD +:101C000037803220F2E7207B9146022800D0FFDFA8 +:101C100037803320EAE7207B1746022800D0FFDF19 +:101C20003420A6F800A02880002FC9D0A7F80A8089 +:101C3000C6E7207B1746042800D0FFDF3520A6F832 +:101C400000A02880002FBBD04046A7F80A8012E0F1 +:101C5000207B1746052802D0062800D0FFDF102081 +:101C6000308036202880002FAAD0E0897881A7F81C +:101C70000E80B9F80E00B881A2E7207B91460728B4 +:101C800000D0FFDF37803720B0E72AE04FF01200A6 +:101C900018804FF038001700288091D0E0897881B3 +:101CA000A7F80E80A7F8108099F80C000A2805D034 +:101CB0000B2809D00C280DD0FFDF81E7207B0A28F4 +:101CC00000D0FFDF01200AE0207B0B2800D0FFDFDF +:101CD000042004E0207B0C2800D0FFDF05203873AF +:101CE0006EE7FFDF6CE770B50C46054601F0AAFB16 +:101CF00020B10078222804D2082070BD43F20200EF +:101D000070BD0521284612F0D1F8206008B10020EE +:101D100070BD032070BD30B44880087820F00F00FB +:101D2000C01C20F0F000903001F8080B1DCA81E8BB +:101D30001D0030BC07F05DBC100000202DE9FF47FE +:101D400084B0002782460297079890468946123051 +:101D50000AF069FA401D20F00306079828B907A980 +:101D60005046FFF7C0FF002854D1B9F1000F05D04D +:101D70000798017B19BB052504681BE098F8000053 +:101D8000092803D00D2812D0FFDF46E0079903256C +:101D90004868B0B3497B42887143914239D98AB2CD +:101DA000B3B2011D11F0F5FE0446078002E0079C66 +:101DB000042508340CB1208810B1032D29D02CE063 +:101DC0000798012112300AF060FAADF80C000246C3 +:101DD00002AB2946504608F0B8FA070001D1A01C12 +:101DE000029007983A461230C8F80400A8F802A0FA +:101DF00003A94046029B0AF055FAD8B10A2817D227 +:101E000000E006E0DFE800F007091414100B0D14E1 +:101E10001412132014E6002012E6112010E6082008 +:101E20000EE643F203000BE6072009E60D2007E665 +:101E3000032005E6BDF80C002346CDE900702A46D4 +:101E40005046079900F022FD57B9032D08D1079895 +:101E5000B3B2417B406871438AB2011D11F0ADFEFF +:101E6000B9F1000FD7D0079981F80C90D3E72DE98D +:101E7000FE4F91461A881C468A468046FAB102AB4C +:101E8000494608F062FA050019D04046A61C27888A +:101E900012F04FF93246072629463B46009611F0CC +:101EA0005EFD20882346CDE900504A465146404613 +:101EB00000F0ECFC002020800120BDE8FE8F002017 +:101EC000FBE710B586B01C46AAB104238DF800309C +:101ED0001388ADF808305288ADF80A208A788DF85A +:101EE0000E200988ADF80C1000236A462146FFF742 +:101EF00025FF06B010BD1020FBE770B50D4605218B +:101F000011F0D4FF040000D1FFDF294604F11200D4 +:101F1000BDE870400AF0A2B92DE9F8430D468046AD +:101F2000002607F063FB04462878102878D2DFE803 +:101F300000F0773B345331311231313108313131D6 +:101F400031312879001FC0B2022801D0102810D1E9 +:101F500014BBFFDF35E004B9FFDF0521404611F077 +:101F6000A5FF007B032806D004280BD0072828D023 +:101F7000FFDF072655E02879801FC0B2022820D055 +:101F800050B1F6E72879401FC0B2022819D01028B6 +:101F900017D0EEE704B9FFDF13E004B9FFDF2879BB +:101FA00001280ED1172137E00521404611F07EFFB0 +:101FB000070000D1FFDF07F1120140460AF02BF9BC +:101FC0002CB12A4621464046FFF7A5FE29E0132101 +:101FD000404602F01FFD24E004B9FFDF0521404622 +:101FE00011F064FF060000D1FFDF694606F1120020 +:101FF0000AF01BF9060000D0FFDFA988172901D2DB +:10200000172200E00A46BDF80000824202D90146CC +:1020100002E005E01729C5D3404600F047FCD0E7B1 +:10202000FFDF3046BDE8F883401D20F0030219B100 +:1020300002FB01F0001D00E000201044704713B5C2 +:10204000009858B10024684611F04DFD002C04D1D1 +:10205000F749009A4A6000220A701CBD0124002042 +:10206000F2E72DE9F0470C461546242200212046D0 +:102070000CF00DFA05B9FFDFA87860732888DFF847 +:10208000B0A3401D20F00301AF788946DAF80400C0 +:1020900011F047FD060000D1FFDF4FF00008266079 +:1020A000A6F8008077B109FB07F1091D0AD0DAF81C +:1020B000040011F036FD060000D1FFDF6660C6F8AF +:1020C000008001E0C4F80480298804F11200BDE812 +:1020D000F0470AF091B82DE9F047804601F112006F +:1020E0000D4681460AF09FF8401DD14F20F00302B3 +:1020F0006E7B14462968786811F03EFD3EB104FB02 +:1021000006F2121D03D06968786811F035FD0520CC +:1021100011F074FE0446052011F078FE201A012803 +:1021200002D1786811F0F2FC49464046BDE8F0471C +:102130000AF078B870B50546052111F0B7FE040025 +:1021400000D1FFDF04F112012846BDE870400AF01B +:1021500062B82DE9F04F91B04FF0000BADF828B008 +:10216000ADF804B047880C4605469246052138462E +:1021700011F09CFE060000D1FFDF24B1A780A4F877 +:1021800006B0A4F808B0297809220B20B2EB111F81 +:1021900073D12A7A04F1100138274FF00C084FF060 +:1021A00012090291102A69D2DFE802F068F2F1F018 +:1021B0008008D3898EA03DDCF3EEB7B7307B0228D0 +:1021C00000D0FFDFA88908EBC001ADF80410302172 +:1021D000ADF82810002C25D06081B5F80E800027BE +:1021E0001DE004EBC709317C89F80E10F189A9F8CC +:1021F0000C10CDF800806888042305AA296900F036 +:1022000035FBBDF81410A9F8101008F10400BDF852 +:1022100016107F1C1FFA80F8A9F81210BFB260894F +:10222000B842DED80CE1307B022800D0FFDFE9891C +:1022300008EBC100ADF804003020ADF8280095F897 +:102240000C90002CA9F10400C0B20F90EAD061817B +:10225000B5F81080002725E0CDF8008068884B464F +:1022600003AA696900F002FB08EB09001FFA80F875 +:102270006F48007818B1012302E0DDE0DAE00023C6 +:1022800004EBC702009204A90C320F9813F097FBDD +:10229000009ABDF80C007F1C1082009ABDF80E0059 +:1022A000BFB250826089B842D6D8C9E00AA800906F +:1022B00001AB224629463046FFF711FBC0E0307BD8 +:1022C000082805D0FFDF03E0307B082800D0FFDFBF +:1022D000E8891030ADF804003620ADF82800002C55 +:1022E0003FD0A9896181F189A18127E0307B09284C +:1022F00000D0FFDFA88901460C30ADF8040037207C +:10230000ADF82800002C2CD06181E8890090AB89C1 +:10231000688804F10C02296955E0E88939211030F8 +:1023200080B2ADF80400ADF82810002C72D0A98955 +:102330006181287A0E280AD002212173E989E1817E +:10234000288A0090EB8968886969029A3BE001213C +:10235000F3E70AA8009001AB224629463046FFF772 +:1023600055FB6DE0307B0A2800D0FFDFADF804900C +:10237000ADF828704CB3A9896181A4F810B0A4F815 +:102380000EB0012020735BE020E002E030E038E096 +:1023900041E0307B0B2800D0FFDF288AADF82870A1 +:1023A0001230ADF8040084B104212173A989618140 +:1023B000E989E181298A2182688A00902B8A6888CC +:1023C00004F11202696900F051FA39E0307B0C28FF +:1023D00000D0FFDFADF80490ADF828703CB30521C4 +:1023E0002173A4F80AB0A4F80EB0A4F810B027E046 +:1023F0000AA8009001AB224629463046FFF754FA5E +:102400001EE00AA8009001AB224629463046FFF79D +:10241000B3FB15E034E03B21ADF80400ADF8281023 +:1024200074B30120E080A4F808B084F80AB007E093 +:1024300010000020FFDF03E0297A012917D0FFDF19 +:10244000BDF80400AAF800006CB1BDF82800208097 +:10245000BDF804006080BDF82800392803D03C286E +:1024600001D086F80CB011B00020BDE8F08F3C21FF +:10247000ADF80400ADF8281014B1697AA172DFE755 +:10248000AAF80000EFE72DE9F84356880F4680468A +:1024900015460521304611F009FD040000D1FFDF8B +:1024A000123400943B46414630466A680AF02EF8E2 +:1024B000B8E570B50D46052111F0F8FC040000D117 +:1024C000FFDF294604F11200BDE8704009F0B8BEF4 +:1024D00070B50D46052111F0E9FC040000D1FFDFC5 +:1024E000294604F11200BDE8704009F0D6BE70B56F +:1024F0000546052111F0DAFC040000D1FFDF04F1EC +:10250000080321462846BDE870400422AFE470B5B8 +:102510000546052111F0CAFC040000D1FFDF214669 +:1025200028462368BDE870400522A0E470B5064641 +:10253000052111F0BBFC040000D1FFDF04F1120003 +:1025400009F071FE401D20F0030511E0011D008817 +:102550000322431821463046FFF789FC00280BD0A0 +:10256000607BABB2684382B26068011D11F05BFB17 +:10257000606841880029E9D170BD70B50E460546F6 +:1025800007F034F8040000D1FFDF012020726672EA +:102590006580207820F00F00C01C20F0F000303063 +:1025A0002070BDE8704007F024B8602801D00720F3 +:1025B00070470878C54900F0010008700020704796 +:1025C0002DE9F0438BB00D461446814606A9FFF76E +:1025D0008AFB002814D14FF6FF7601274FF42058CC +:1025E0008CB103208DF800001020ADF8100007A872 +:1025F000059007AA204604A913F005FA78B1072030 +:102600000BB0BDE8F0830820ADF808508DF80E70CF +:102610008DF80000ADF80A60ADF80C800CE006986B +:10262000A17801742188C1818DF80E70ADF8085031 +:10263000ADF80C80ADF80A606A4602214846069B58 +:10264000FFF77CFBDCE708B501228DF8022042F69B +:102650000202ADF800200A4603236946FFF731FC69 +:1026600008BD08B501228DF8022042F60302ADF83C +:1026700000200A4604236946FFF723FC08BD00B585 +:1026800087B079B102228DF800200A88ADF80820C1 +:102690004988ADF80A1000236A460521FFF74EFB72 +:1026A00007B000BD1020FBE709B1072309E40720AC +:1026B000704770B588B00D461446064606A9FFF768 +:1026C00012FB00280ED17CB10620ADF808508DF821 +:1026D0000000ADF80A40069B6A460821DC813046BE +:1026E000FFF72CFB08B070BD05208DF80000ADF899 +:1026F0000850F0E700B587B059B107238DF80030D6 +:10270000ADF80820039100236A460921FFF716FB64 +:10271000C6E71020C4E770B588B00C460646002511 +:1027200006A9FFF7E0FA0028DCD106980121123053 +:1027300009F0ABFD9CB12178062921D2DFE801F038 +:10274000200505160318801E80B2C01EE28880B2E4 +:102750000AB1A3681BB1824203D90C20C2E7102042 +:10276000C0E7042904D0A08850B901E00620B9E7E9 +:10277000012913D0022905D004291CD005292AD00B +:102780000720AFE709208DF800006088ADF8080049 +:10279000E088ADF80A00A068039023E00A208DF8D5 +:1027A00000006088ADF80800E088ADF80A00A06875 +:1027B0000A25039016E00B208DF800006088ADF824 +:1027C0000800A088ADF80A00E088ADF80C00A06809 +:1027D0000B25049006E00C208DF8000060788DF841 +:1027E00008000C256A4629463046069BFFF7A6FAE4 +:1027F00078E700B587B00D228DF80020ADF80810FD +:1028000000236A461946FFF799FA49E700B587B0F1 +:1028100071B102228DF800200A88ADF8082049889D +:10282000ADF80A1000236A460621FFF787FA37E75A +:10283000102035E770B586B0064601200D46ADF88C +:1028400008108DF80000014600236A463046FFF765 +:1028500075FA040008D12946304605F0B5FC002180 +:10286000304605F0CFFC204606B070BDF8B51C46DA +:1028700015460E46069F11F04AFC2346FF1DBCB2CA +:1028800031462A46009411F036F8F8BD30B41146AE +:10289000DDE902423CB1032903D0002330BC08F03B +:1028A00032BE0123FAE71A8030BC704770B50C467F +:1028B0000546FFF722FB2146284605F094FC2846F2 +:1028C000BDE87040012105F09DBC00001000002013 +:1028D0004FF0E0224FF400400021C2F88001BFF326 +:1028E0004F8FBFF36F8F1748016001601649900248 +:1028F00008607047134900B500220A600A60124B55 +:102900004FF060721A60002808BF00BD0F4A104BDC +:10291000DFF840C001280CD002281CBFFFDF00BD3B +:10292000032008601A604FF4000000BFCCF80000DC +:1029300000BD022008601A604FF04070F6E700B555 +:10294000FFDF00BD00F5004008F50140A4020020B3 +:1029500014F5004004F5014070B50B2000F0BDF9FE +:10296000082000F0BAF900210B2000F0D4F9002172 +:10297000082000F0D0F9F44C01256560A560002026 +:10298000C4F84001C4F84401C4F848010B2000F029 +:10299000B5F9082000F0B2F90B2000F091F925609C +:1029A00070BD10B50B2000F098F9082000F095F9E3 +:1029B000E548012141608160E4490A68002AFCD1B0 +:1029C0000021C0F84011C0F84411C0F848110B2094 +:1029D00000F094F9BDE81040082000F08FB910B560 +:1029E0000B2000F08BF9BDE81040082000F086B9FC +:1029F00000B530B1012806D0022806D0FFDF002044 +:102A000000BDD34800BDD34800BDD248001D00BD65 +:102A100070B5D1494FF000400860D04DC00BC5F8EB +:102A20000803CF4800240460C5F840410820C4359D +:102A300000F053F9C5F83C41CA48047070BD08B5B0 +:102A4000C14A002128B1012811D002281CD0FFDF83 +:102A500008BD4FF48030C2F80803C2F84803BB48F1 +:102A60003C300160C2F84011BDE80840D0E74FF4A7 +:102A70000030C2F80803C2F84803B448403001608F +:102A8000C2F84411B3480CE04FF48020C2F80803A8 +:102A9000C2F84803AD4844300160C2F84811AD485F +:102AA000001D0068009008BD70B516460D4604462E +:102AB000022800D9FFDF0022A348012304F11001FE +:102AC0008B4000EB8401C1F8405526B1C1F840218C +:102AD000C0F8043303E0C0F80833C1F84021C0F85F +:102AE000443370BD2DE9F0411D46144630B1012834 +:102AF00033D0022838D0FFDFBDE8F081891E0022E4 +:102B000021F07F411046FFF7CFFF012D23D0002099 +:102B1000944D924F012668703E61914900203C39E6 +:102B200008600220091D08608D49042030390860C2 +:102B30008B483D34046008206C6000F0DFF83004FE +:102B4000C7F80403082000F0BBF88349F007091F09 +:102B500008602E70D0E70120DAE7012B02D00022B6 +:102B6000012005E00122FBE7012B04D00022022016 +:102B7000BDE8F04198E70122F9E774480068704722 +:102B800070B500F0D8F8704C0546D4F84001002626 +:102B9000012809D1D4F80803C00305D54FF48030CB +:102BA000C4F80803C4F84061D4F8440101280CD1EA +:102BB000D4F80803800308D54FF40030C4F80803A4 +:102BC000C4F84461012013F0EEFED4F84801012856 +:102BD0000CD1D4F80803400308D54FF48020C4F882 +:102BE0000803C4F84861022013F0DDFE5E4805606A +:102BF00070BD70B500F09FF85A4D0446287850B16A +:102C0000FFF706FF687818B10020687013F0CBFE5C +:102C10005548046070BD0320F8E74FF0E0214FF401 +:102C20000010C1F800027047152000F067B84B494A +:102C300001200861082000F061B848494FF47C1079 +:102C4000C1F808030020024601EB8003C3F84025C9 +:102C5000C3F84021401CC0B20628F5D37047410A92 +:102C600043F609525143C0F3080010FB02F000F58F +:102C7000807001EB5020704710B5430B48F2376469 +:102C800063431B0C5C020C60384C03FB0400384BA4 +:102C90004CF2F72443435B0D13FB04F404EB402098 +:102CA00000F580704012107008681844086010BD6C +:102CB0002C484068704729490120C1F8000270473C +:102CC000002809DB00F01F0201219140400980002B +:102CD00000F1E020C0F80011704700280DDB00F083 +:102CE0001F02012191404009800000F1E020C0F85E +:102CF0008011BFF34F8FBFF36F8F7047002809DB40 +:102D000000F01F02012191404009800000F1E02005 +:102D1000C0F8801270474907090E002804DB00F153 +:102D2000E02080F80014704700F00F0000F1E02070 +:102D300080F8141D70470C48001F00680A4A0D49AE +:102D4000121D11607047000000B0004004B5004043 +:102D50004081004044B1004008F50140008000403F +:102D6000408500403C00002014050240F7C2FFFFF0 +:102D70006F0C0100010000010A4810B50468094900 +:102D800009480831086013F0A2FE0648001D0460DF +:102D900010BD0649002008604FF0E0210220C1F874 +:102DA000800270471005024001000001FC1F004036 +:102DB000374901200860704770B50D2000F049F8D0 +:102DC000344C0020C4F800010125C4F804530D2040 +:102DD00000F050F825604FF0E0216014C1F80001C8 +:102DE00070BD10B50D2000F034F82A480121416073 +:102DF0000021C0F80011BDE810400D2000F03AB8E5 +:102E0000254810B504682449244808310860214940 +:102E1000D1F80001012804D0FFDF1F48001D046025 +:102E200010BD1B48001D00680022C0B2C1F800217F +:102E300014F07FFBF1E710B5164800BFD0F8001181 +:102E40000029FBD0FFF7DCFFBDE810400D2000F0AB +:102E500011B800280DDB00F01F020121914040094C +:102E6000800000F1E020C0F88011BFF34F8FBFF366 +:102E70006F8F7047002809DB00F01F02012191408D +:102E80004009800000F1E020C0F880127047000087 +:102E900004D5004000D000401005024001000001B0 +:102EA0004FF0E0214FF00070C1F8800101F5C071D2 +:102EB000BFF34F8FBFF36F8FC1F80001394B8022F2 +:102EC00083F8002441F8800C704700B502460420C6 +:102ED000354903E001EBC0031B792BB1401EC0B2A2 +:102EE000F8D2FFDFFF2000BD41F8302001EBC00128 +:102EF00000224A718A7101220A7100BD2A4A00210A +:102F000002EBC0000171704710B50446042800D3DD +:102F1000FFDF254800EBC4042079012800D0FFDF43 +:102F20006079A179401CC0B2814200D060714FF03D +:102F3000E0214FF00070C1F8000210BD70B504250B +:102F4000194E1A4C16E0217806EBC1000279012ACD +:102F500008D1427983799A4204D04279827156F835 +:102F6000310080472078401CC0B22070042801D373 +:102F7000002020706D1EEDB2E5D270BD0C4810B57A +:102F800004680B490B4808310860064890F80004B3 +:102F90004009042800D0FFDFFFF7D0FF0448001DE0 +:102FA000046010BD19E000E0E0050020580000209A +:102FB00010050240010000010548064A01689142DF +:102FC00001D1002101600449012008607047000020 +:102FD0005C000020BEBAFECA40E5014070B50C4658 +:102FE000054609F02FFC21462846BDE870400AF04E +:102FF00010BD7047704770470021016081807047A5 +:103000002CFFFFFFDBE5B151007002002301FFFF41 +:103010008C00000078DB6A007A2E9AC67DB66CFAC6 +:10302000F35721CCC310D5E51471FB3C30B5FC4DF2 +:103030000446062CA9780ED2DFE804F0030E0E0E2B +:103040000509FFDF08E0022906D0FFDF04E00329BD +:1030500002D0FFDF00E0FFDFAC7030BD30B50446CA +:103060001038EF4D07280CD2DFE800F0040C060CF6 +:103070000C0C0C00FFDF05E0287E112802D0FFDFDA +:1030800000E0FFDF2C7630BD2DE9F04112F026FE86 +:10309000044614F063F8201AC5B2062010F0AEFE04 +:1030A0000446062010F0B2FE211ADD4C207E1228C4 +:1030B00018D000200F18072010F0A0FE06460720A9 +:1030C00010F0A4FE301A3918207E13280CD00020EE +:1030D0000144A078042809D000200844281AC0B26E +:1030E000BDE8F0810120E5E70120F1E70120F4E7E8 +:1030F000CB4810B590F825004108C94800F12600DA +:1031000005D00EF0F5FEBDE8104006F08CB80EF0CC +:10311000D0FEF8E730B50446A1F120000D460A289C +:103120004AD2DFE800F005070C1C2328353A3F445B +:10313000FFDF42E0207820283FD1FFDF3DE0B848A4 +:103140008178052939D0007E122836D020782428AD +:1031500033D0252831D023282FD0FFDF2DE0207851 +:1031600022282AD0232828D8FFDF26E0207822280A +:1031700023D0FFDF21E0207822281ED024281CD075 +:1031800026281AD0272818D0292816D0FFDF14E0C7 +:103190002078252811D0FFDF0FE0207825280CD0DB +:1031A000FFDF0AE02078252807D0FFDF05E0207840 +:1031B000282802D0FFDF00E0FFDF257030BD1FB5FB +:1031C00004466A46002001F0A5FEB4B1BDF8022015 +:1031D0004FF6FF700621824201D1ADF80210BDF812 +:1031E0000420824201D1ADF80410BDF808108142DC +:1031F00003D14FF44860ADF8080068460FF0E2FADA +:1032000006F011F804B010BD70B516460C46054620 +:10321000FEF71FF848B90CB1B44208D90C2070BDB4 +:1032200055F82400FEF715F808B1102070BD2046AF +:10323000641EE4B2F4D270BD2DE9F04105461F468C +:1032400090460E4600240068FEF750F830B9A98871 +:1032500028680844401EFEF749F808B110203FE7EF +:1032600028680028A88802D0B84202D850E0002878 +:10327000F5D0092034E72968085DB8B1671CCA5D3C +:10328000152A2ED03CDC152A3AD2DFE802F039129A +:10329000222228282A2A313139393939393939391C +:1032A00039392200085D30BB641CA4B2A242F9D8AF +:1032B00033E00228DDD1A01C085C88F80000072854 +:1032C00001D2400701D40A200AE7307840F001001B +:1032D00015E0C143C90707E0012807D010E0062028 +:1032E000FEE60107A1F180510029F5D01846F7E666 +:1032F0003078810701D50B20F2E640F002003070F3 +:103300002868005D384484B2A888A04202D2B0E7A1 +:103310004FF4485382B2A242ADD80020E0E610B587 +:10332000027843F2022354080122022C12D003DC5B +:103330003CB1012C16D106E0032C10D07F2C11D10A +:1033400012E0002011E080790324B4EB901F09D132 +:103350000A700BE08079B2EB901F03D1F8E7807917 +:103360008009F5D0184610BDFF200870002010BD60 +:1033700008B500208DF80000294890F82E1051B1B2 +:1033800090F82F0002280FD003280FD0FFDF00BFD6 +:103390009DF8000008BD22486946253001F009FE6D +:1033A0000028F5D0FFDFF3E7032000E001208DF8CF +:1033B0000000EDE738B50C460546694601F0F9FD19 +:1033C00000280DD19DF80010207861F3470020708F +:1033D00055F8010FC4F80100A888A4F805000020E2 +:1033E00038BD38B5137888B102280FD0FF281BD01C +:1033F0000CA46D46246800944C7905EB9414247851 +:1034000064F347031370032805D010E023F0FE0394 +:1034100013700228F7D1D8B240F001000AE0000092 +:10342000F00100200302FF0143F0FE00107010784D +:1034300020F0010010700868C2F801008888A2F826 +:10344000050038BD022110F031BD38B50C460978B1 +:10345000222901D2082038BDADF800008DF80220E5 +:1034600068460EF087FD05F0DEFE050003D1212140 +:103470002046FFF74FFE284638BD1CB500208DF8CA +:103480000000CDF80100ADF80500FB4890F82E00D3 +:10349000022801D0012000E000208DF807006846D6 +:1034A0000EF0F0FD002800D0FFDF1CBD00220A80D6 +:1034B000437892B263F3451222F040020A8000780A +:1034C0000C282BD2DFE800F02A06090E1116191C71 +:1034D0001F220C2742F0110009E042F01D00088075 +:1034E0000020704742F0110012E042F0100040F05E +:1034F0000200F4E742F01000F1E742F00100EEE7CD +:1035000042F0010004E042F00200E8E742F002006D +:1035100040F00400E3E742F00400E0E707207047D2 +:103520002DE9FF478AB00025BDF82C6082461C4675 +:1035300091468DF81C50700703D56068FDF789FE31 +:1035400068B9CD4F4FF0010897F82E0058B197F8A1 +:103550002F00022807D16068FDF7C8FE18B11020BF +:103560000EB0BDE8F087300702D5A08980283DD88D +:10357000700705D4B9F1000F02D097F8240098B372 +:10358000E07DC0F300108DF81B00627D0720032151 +:103590005AB3012A2CD0022AE2D0042AE0D18DF8B5 +:1035A0001710F00627D4A27D072022B3012A22D0CB +:1035B000022A23D0042AD3D18DF819108DF8159042 +:1035C000606810B307A9FFF7AAFE0028C8D19DF8CC +:1035D0001C00FF2816D0606850F8011FCDF80F10AE +:1035E0008088ADF8130014E000E001E00720B7E7A1 +:1035F0008DF81780D5E78DF81980DFE702208DF868 +:103600001900DBE743F20220AAE7CDF80F50ADF82E +:103610001350E07B40B9207C30B9607C20B9A07C9D +:1036200010B9E07CC00601D0062099E78DF800A013 +:10363000BDF82C00ADF80200A0680190A0680290CF +:1036400004F10F0001F0A9FC8DF80C00FFF790FECB +:103650008DF80D009DF81C008DF80E008DF81650A9 +:103660008DF81850E07D08A900F00F008DF81A00C1 +:1036700068460FF0E3F905F0D6FD71E7F0B58FB0BD +:1036800000258DF830508DF814508DF834500646D2 +:103690008DF82850019502950395049519B10FC92D +:1036A00001AC84E80F00744CA078052801D00428F0 +:1036B0000CD101986168884200D120B90398E16873 +:1036C000884203D110B108200FB0F0BD207DC006A4 +:1036D00001D51F2700E0FF273B460DAA05A903A837 +:1036E000FFF7AAFD0028EFD1A08AC10702D0C006CB +:1036F00000D4EE273B460AAA0CA901A8FFF79CFDBF +:103700000028E1D19DF81400C00701D00A20DBE7B2 +:10371000A08A410708D4A17D31B19DF828108907FE +:1037200002D043F20120CFE79DF82810C90709D045 +:10373000400707D4208818B144F25061884201D96B +:103740000720C1E78DF818508DF81960BDF8080002 +:10375000ADF81A000198079006A80FF07BF905F064 +:1037600062FD0028B0D18DF820508DF82160BDF8A1 +:103770001000ADF822000398099008A80FF08CF90A +:1037800005F051FD00289FD101AD241D95E80F00E3 +:1037900084E80F00002097E770B586B00D4604005E +:1037A00005D0FDF7A3FD20B1102006B070BD0820A4 +:1037B000FBE72078C107A98802D0FF2902D303E0E4 +:1037C0001F2901D20920F0E7800763D4FFF75CFCD2 +:1037D00038B12178C1F3C100012804D0032802D0F8 +:1037E00005E01320E1E7244890F82400C8B1C80799 +:1037F0004FF001064FF0000502D08DF80F6001E098 +:103800008DF80F50FFF7B4FD8DF800002078694661 +:10381000C0F3C1008DF8010060788DF80250C20835 +:1038200001D00720C1E730B3C20701D08DF8026094 +:10383000820705D59DF8022042F002028DF8022091 +:10384000400705D59DF8020040F004008DF8020005 +:10385000002022780B18C2F38002DA7001EB4002DC +:103860006388D380401CA388C0B253810228F0D360 +:10387000207A78B905E001E0F00100208DF80260BF +:10388000E6E7607A30B9A07A20B9E07A10B9207BF7 +:10389000C00601D0062088E704F1080001F07DFB96 +:1038A0008DF80E0068460EF0F6FC05F0BCFC002812 +:1038B00089D18DF810608DF81150E088ADF81200B4 +:1038C000ADF8145004A80EF039FD05F0ACFC00284A +:1038D00088D12078C00701D0152000E01320FFF721 +:1038E000BDFB002061E72DE9FF470220FD4E8DF86A +:1038F00004000027708EADF80600B84643F20209B6 +:103900004CE001A810F039FA050006D0708EA8B37B +:10391000A6F83280ADF806803EE0039CA07F010748 +:103920002DD504F124000090A28EBDF80800214698 +:1039300004F1360301F0BCFC050005D04D452AD04A +:10394000112D3CD0FFDF3AE0A07F20F00801E07F9E +:10395000420862F3C711A177810861F30000E077A4 +:1039600094F8210000F01F0084F820002078282817 +:1039700026D129212046FFF7CDFB21E014E04007A6 +:103980000AD5BDF8080004F10E0101F01CFB05008A +:103990000DD04D4510D100257F1CFFB2022010F044 +:1039A0002DFA401CB842ACD8052D11D008E0A07FFC +:1039B00020F00400A07703E0112D00D0FFDF0025E8 +:1039C000BDF806007086052D04D0284604B0C8E571 +:1039D000A6F832800020F9E770B50646FFF732FD01 +:1039E000054605F003FE040000D1FFDF6680207865 +:1039F00020F00F00801C20F0F00020302070032009 +:103A0000207295F83E006072BDE8704005F0F1BD8F +:103A10002DE9F04786B0040000D1FFDF2078B14DDA +:103A200020F00F00801C20F0F000703020706068E3 +:103A30000178491F1B2933D2DFE801F0FE32323210 +:103A400055FD320EFDFD42FC32323278FCFCFBFAB1 +:103A500032FCFCF9F8FCFC00C6883046FFF7F2FCAB +:103A60000546304607F045FCE0B16068007A85F80D +:103A70003E0021212846FFF74DFB3046FEF75AFB5A +:103A8000304603F0D7FE3146012014F017F8A87F26 +:103A900020F01000A877FFF726FF002800D0FFDFF6 +:103AA00006B05EE5207820F0F00020302070032082 +:103AB000207266806068007A607205F09AFDD8E72F +:103AC000C5882846FFF7BEFC00B9FFDF60680079B3 +:103AD000012800D0FFDF6068017A06B02846BDE803 +:103AE000F04707F0EBBDC6883046FFF7ABFC05009A +:103AF00000D1FFDF05F07DFD606831460089288137 +:103B000060684089688160688089A881012013F01D +:103B1000D5FF0020A875A87F00F003000228BFD1C0 +:103B2000FFF7E1FE0028BBD0FFDFB9E7007928B13D +:103B30000228B5D03C28B3D0FFDFB1E705F059FD2E +:103B40006668B6F806A0307A361D012806D0687E71 +:103B5000814605F0D4FA070003D101E0E878F7E7E1 +:103B6000FFDF00220221504610F097F9040000D137 +:103B7000FFDF22212046FFF7CDFA3079012800D05F +:103B80000220A17F804668F30101A177308B20815C +:103B9000708B6081B08BA08184F822908DF80880B2 +:103BA000B8680090F86801906A460321504610F00A +:103BB00074F900B9FFDFB888ADF81000B8788DF857 +:103BC000120004AA0521504610F067F900B9FFDF82 +:103BD000B888ADF80C00F8788DF80E0003AA04211F +:103BE000504610F05AF900B9FFDF062106F1120025 +:103BF0000DF00EF940B37079800700D5FFDF7179C1 +:103C0000E07D61F34700E075D6F80600A061708999 +:103C1000A083062106F10C000DF0FAF8F0B195F83A +:103C200025004108607861F3470006E041E039E093 +:103C300071E059E04EE02FE043E06070D5F82600D7 +:103C4000C4F80200688D12E0E07D20F0FE00801CC8 +:103C5000E075D6F81200A061F08AD9E7607820F00C +:103C6000FE00801C6070F068C4F80200308AE080BA +:103C7000B8F1010F04D0B8F1020F05D0FFDF0FE754 +:103C80000320FFF7D3F90BE7287E122800D0FFDFCF +:103C90001120FFF7E3F903E706B02046BDE8F0473F +:103CA00001F092BD05F0A5FC15F8300F40F00200C0 +:103CB00005E005F09EFC15F8300F40F00400287078 +:103CC000EEE6287E13280AD01528D8D15FF016001A +:103CD000FFF7C4F906B0BDE8F04705F08ABC142030 +:103CE000F6E70000F0010020A978052909D0042991 +:103CF000C5D105F07EFC022006B0BDE8F047FFF715 +:103D000095B900790028BAD0E87801F02DF905F0CE +:103D100070FC0320F0E7287E122802D1687E01F0B3 +:103D200023F91120D4E72DE9F05F054600784FF024 +:103D300000080009DFF8B8A891460C46464601285D +:103D40006ED002286DD007280BD00A286AD0FFDF7A +:103D5000A9F8006014B1A4F8008066800020BDE8D6 +:103D6000F09F6968012704F108000B784FF0020BFF +:103D70005B1F4FF6FF721B2B7ED2DFE803F0647DE2 +:103D80007D7D0E7D7D7D7D7D7D217D7D7D2BFDFC81 +:103D9000FBFA7D14D2F9E7F8F700C8884FF0120853 +:103DA000102621469AE14FF01C080A26BCB38888E9 +:103DB000A0806868807920726868C0796072C7E7FF +:103DC0004FF01B08142654B30320207268688088C3 +:103DD000A080BDE70A793C2ABAD00D1D4FF010082B +:103DE0002C26E4B16988A180298B6182298B2182EC +:103DF000698BA182A98BE1826B790246A91D1846C5 +:103E0000FFF7EFFA2979002001290CD084F80FB0D0 +:103E1000FF212176E06120626062A06298E70FE0F6 +:103E20003BE15EE199E1E77320760AF1040090E856 +:103E30000E00DAF81000C4E90930C4E9071287E778 +:103E4000A9F800608AE72C264FF01D08002CF7D057 +:103E5000A28005460F1D897B008861F30000288041 +:103E6000B97A490861F341002880B97A890861F379 +:103E700082002880B97A00E00CE1C90861F3C30030 +:103E80002880B97AAA1C0911491C61F3041000F0BA +:103E90007F0028807878B91CFFF7A3FA387D05F1F8 +:103EA000090207F11501FFF79CFA387B01F0A9F828 +:103EB0002874787B01F0A5F86874F87EA874787A85 +:103EC000E874387F2875B87B6875388AE882DAF834 +:103ED0001C10A961B97A504697F808A0C1F34111A6 +:103EE000012904D0008C504503D2824609E0FFDF4F +:103EF00010E0022903D0288820F0600009E0504536 +:103F000004D1288820F06000403002E0288840F08A +:103F100060002880A4F824A0524607F11D01A8697A +:103F20009BE011264FF02008002C89D0A280686801 +:103F300004F10A02007920726868007B6072696887 +:103F40008B1D48791946FFF74CFA01E70A264FF016 +:103F50002108002CE9D08888A080686880792072C8 +:103F60006868C07960729AF8301006E078E06BE01B +:103F700052E07FE019E003E03AE021F00401A6E01E +:103F80000B264FF02208002CCFD0C888A08068688C +:103F9000007920726868007A01F033F8607268680E +:103FA000407A01F02EF8A072D2E61C264FF02608C7 +:103FB000002CBAD0A2806868407960726868007A84 +:103FC000A0720AF1040090E80E00DAF81000C4E9CB +:103FD0000530C4E90312686800793C2803D04328FF +:103FE00003D0FFDFB4E62772B2E684F808B0AFE68C +:103FF00010264FF02408002C97D08888A08068688D +:10400000807920816868807A608168680089A081F1 +:1040100068688089E0819BE610264FF02308002C19 +:1040200098D08888A0806868C088208168680089E6 +:10403000608168684089A08168688089E0819AF819 +:10404000301021F0020142E030264FF02508002C0C +:104050009AD0A2806968282249680AF0EEF977E6CA +:104060002A264FF02F08002C8ED0A28069682222C9 +:10407000091DF2E714264FF01B08002C84D0A28003 +:10408000686800790128B0D02772DAE90710C4E91E +:1040900003105DE64A46214660E0287A012803D0F5 +:1040A000022817D0FFDF53E610264FF01F08002C20 +:1040B000A2D06888A080A8892081E8896081288AA8 +:1040C000A081688AE0819AF8301021F001018AF815 +:1040D00030103DE64FF012081026688800F07EFF91 +:1040E00036E6287AC8B3012838D0022836D003280B +:1040F00001D0FFDF2CE609264FF01108002C8FD0ED +:104100006F883846FFF79EF990F822A0A780687A5A +:104110002072042138460FF0DBFE052138460FF0EF +:10412000D7FE002138460FF0D3FE012138460FF0AC +:10413000CFFE032138460FF0CBFE022138460FF0A8 +:10414000C7FE062138460FF0C3FE072138460FF0A0 +:10415000BFFE504600F008FFFAE5FFE72846BDE83D +:10416000F05F01F0BBBC70B5012803D0052800D07A +:10417000FFDF70BD8DB22846FFF764F9040000D15F +:10418000FFDF20782128F4D005F030FA80B10178E3 +:1041900021F00F01891C21F0F00110310170022182 +:1041A000017245800020A075BDE8704005F021BA7D +:1041B00021462846BDE870401322FFF746B92DE995 +:1041C000F04116460C00804600D1FFDF307820F029 +:1041D0000F00801C20F0F000103030702078012893 +:1041E00004D0022818D0FFDFBDE8F0814046FFF779 +:1041F00029F9050000D1FFDF0320A87505F0F9F9C2 +:1042000094E80F00083686E80F00F94810F8301FD0 +:1042100041F001010170E7E74046FFF713F905009F +:1042200000D1FFDFA1884FF6FF700027814202D145 +:10423000E288824203D0814201D1E08840B105F09A +:10424000D8F994E80F00083686E80F00AF75CBE781 +:10425000A87D0128C8D178230022414613F084FBB1 +:104260000220A875C0E738B50C4624285CD008DCCD +:1042700020280FD0212825D022284BD0232806D152 +:104280004CE0252841D0262832D03F2851D00725A0 +:10429000284638BD0021052013F0E6FB08B11120A7 +:1042A00038BDA01C0EF0E1FA04F0BDFF0500EFD10F +:1042B000002208231146052013F056FB0528E7D0FD +:1042C000FFDFE5E76068FDF708F808B1102038BDAA +:1042D000618820886A460EF071FD04F0A4FF050095 +:1042E000D6D160680028D3D0BDF800100180CFE798 +:1042F000206820B1FCF7FAFF08B11025C8E7204676 +:104300000EF03BFE1DE00546C2E7A17820880EF0C6 +:1043100086FD16E0086801F08DFEF4E7087800F0ED +:1043200001000DF0B9FD0CE0618820880EF0C1FCA1 +:1043300007E0087800F001008DF8000068460EF0F4 +:10434000DFF804F070FFDEE770B505460C4608465E +:10435000FCF7A5FF08B1102070BD202D07D0212D3E +:104360000DD0222D0BD0252D09D0072070BD20881F +:10437000A11C0DF065FEBDE8704004F054BF06209E +:1043800070BD9B482530704708B5342200219848FD +:104390000AF07DF80120FEF749FE1120FEF75EFECF +:1043A00093496846263105F0B7F891489DF80020FA +:1043B00010F8251F62F3470121F00101017000216F +:1043C00041724FF46171A0F8071002218172FEF76B +:1043D0008FFE00B1FFDFFDF705F801F0BEF908BD63 +:1043E00010B50C464022002120460AF050F8A07F6C +:1043F00020F00300A077202020700020A07584F812 +:10440000230010BD70472DE9FC410746FCF721FF52 +:1044100010B11020BDE8FC81754E06F12501D6F8DB +:1044200025000090B6F82950ADF8045096F82B40BE +:104430008DF806403846FEF7BDFF0028EAD1FEF7AA +:1044400057FE0028E6D0009946F8251FB580B471C4 +:10445000E0E710B50446FCF722FF08B1102010BDBC +:1044600063486349224690F8250026314008FEF74C +:10447000B8FF002010BD3EB504460D460846FCF7C7 +:104480000EFF08B110203EBD14B143F204003EBD42 +:1044900057488078052803D0042801D008203EBD65 +:1044A000694602A80AF012FC2A4669469DF80800EF +:1044B000FEF797FF00203EBDFEB50D4604004FF00D +:1044C000000712D00822FEF79FFE002812D1002616 +:1044D00009E000BF54F826006946FEF720FF0028D7 +:1044E00008D1761CF6B2AE42F4D30DF01CFC10B12C +:1044F00043F20320FEBD3E4E86F824700CB3002725 +:104500001BE000BF54F8270002A9FEF708FF00B126 +:10451000FFDF9DF808008DF8000054F8270050F8E0 +:10452000011FCDF801108088ADF8050068460DF038 +:104530001FFC00B1FFDF7F1CFFB2AF42E2D386F861 +:1045400024500020FEBD2DE9F0418AB01546884672 +:1045500004001ED00F4608222946FEF755FE00280B +:1045600011D1002613E000BF54F826006946103030 +:1045700000F01FFD002806D13FB157F82600FCF7D8 +:1045800068FE10B110200AB02EE6761CF6B2AE42DC +:10459000EAD3681EC6B217E0701CC7B212E000BFB3 +:1045A00054F82600017C4A0854F827100B7CB2EB23 +:1045B000530F05D106221130113109F011FF50B10E +:1045C0007F1CFFB2AF42EBD3761EF6B2E4D2464672 +:1045D00024B1012003E043F20520D4E700200DF0D0 +:1045E000ECFB10B90DF0F5FB20B143F20420CAE753 +:1045F000F001002064B300270DF1170826E000BF8A +:1046000054F827006946103000F0D3FC00B1FFDFFA +:1046100054F82700102250F8111FCDF8011080889F +:10462000ADF8050054F827100DF1070009F005FF5B +:1046300096B156F827101022404609F0FEFE684653 +:104640000DF07BFB00B1FFDF7F1CFFB2AF42D7D381 +:10465000FEF713FF002096E7404601F0DFFCEEE78F +:1046600030B585B00446FDF7BDF830B906200FF02F +:10467000C5FB10B1062005B030BD2046FCF7E9FDB2 +:1046800018B96068FCF732FE08B11020F3E76088C3 +:104690004AF2B811884206D82078F94D28B101288D +:1046A00006D0022804D00720E5E7FEF721FD18E038 +:1046B0006078022804D0032802D043F20220DAE70F +:1046C00085F82F00C1B200200090ADF80400022947 +:1046D0002CD0032927D0FFDF68460DF009FC04F039 +:1046E000A2FD0028C7D1606801F08BFC207858B18A +:1046F00001208DF800000DF1010001F08FFC6846EB +:104700000EF0FDFB00B1FFDF207885F82E00FEF7EC +:10471000B4FE608860B1A88580B20DF046FB00B1A0 +:10472000FFDF0020A7E78DF80500D5E74020FAE776 +:104730004FF46170EFE710B50446FCF7B0FD20B907 +:10474000606838B1FCF7C9FD08B1102010BD606881 +:1047500001F064FCCA4830F82C1F6180C178617098 +:1047600080782070002010BD2DE9F843144689465A +:104770000646FCF794FDA0B94846FCF7B7FD80B9A2 +:104780002046FCF7B3FD60B9BD4DA878012800D1E3 +:104790003CB13178FF2906D049B143F20400BDE8AD +:1047A000F8831020FBE7012801D00420F7E7CCB301 +:1047B000052811D004280FD069462046FEF776FE62 +:1047C0000028ECD1217D49B1012909D0022909D065 +:1047D000032909D00720E2E70820E0E7024604E0C9 +:1047E000012202E0022200E003228046234617460F +:1047F00000200099FEF794FE0028D0D1A0892880DF +:10480000A07BE875BDF80000A882AF75BDF8001068 +:10481000090701D5A18931B1A1892980C00704D038 +:10482000032003E006E08021F7E70220FEF7FEFB0D +:1048300086F800804946BDE8F8430020FEF71EBF19 +:104840007CB58F4C05460E46A078022803D003287D +:1048500001D008207CBD15B143F204007CBD0720C7 +:104860000FF0D4FA10B9A078032806D0FEF70CFC9C +:1048700028B1A078032804D009E012207CBD1320C1 +:104880007CBD304600F053FB0028F9D1E670FEF7FE +:104890006FFD0AF058F901208DF800008DF8010035 +:1048A0008DF802502088ADF80400E07D8DF80600F8 +:1048B00068460EF0C1F904F0B6FC0028E0D1A078FB +:1048C000032805D05FF00400FEF7B0FB00207CBD9C +:1048D000E07800F03CFB0520F6E71CB510B143F290 +:1048E00004001CBD664CA078042803D0052801D024 +:1048F00008201CBD00208DF8000001218DF801105A +:104900008DF8020068460EF097F904F08CFC002840 +:10491000EFD1A078052805D05FF00200FEF786FBF6 +:1049200000201CBDE07800F01FFB0320F6E72DE916 +:10493000FC4180460E4603250846FCF7D7FC0028BC +:1049400066D14046FEF77EFD040004D02078222880 +:1049500004D208205EE543F202005BE5A07F00F090 +:1049600003073EB1012F0CD000203146FEF727FC93 +:104970000500EFD1012F06D0022F1AD0FFDF284605 +:1049800048E50120F1E7A07D3146022801D011B1B0 +:1049900007E011203EE56846FCF758FE0028D9D113 +:1049A0006946404606F04DFE0500E8D10120A0759D +:1049B000E5E7A07D032804D1314890F83000C00716 +:1049C00001D02EB30EE026B1A07F40071ED40021F7 +:1049D00000E00121404606F054FE0500CFD1A0754D +:1049E000002ECCD03146404600F0EDFA05461128A5 +:1049F000C5D1A07F4107C2D4316844F80E1F716849 +:104A0000616040F0040020740025B8E71125B6E786 +:104A10001020FFE470B50C460546FEF713FD0100BB +:104A200005D022462846BDE87040FEF70EBD43F291 +:104A3000020070BD10B5012807D1114B9B78012BE6 +:104A400000D011B143F2040010BD0DF0E0F9BDE853 +:104A5000104004F0E8BB012300F090BA00231A468E +:104A6000194600F08BBA70B506460C460846FCF7AE +:104A7000F0FB18B92068FCF712FC18B1102070BDCB +:104A8000F0010020F84D2A7E112A04D0132A00D309 +:104A90003EB10820F3E721463046FEF77DFE60B1C7 +:104AA000EDE70920132A0DD0142A0BD0A188FF2985 +:104AB000E5D31520FEF7D2FA0020D4E90012C5E9AB +:104AC0000712DCE7A1881F29D9D31320F2E71CB510 +:104AD000E548007E132801D208201CBD00208DF877 +:104AE000000068460DF02AFC04F09DFB0028F4D17C +:104AF0001120FEF7B3FA00201CBD2DE9F04FDFF8BE +:104B000068A3814691B09AF818009B4615460C465A +:104B1000132803D3FFF7DBFF00281FD12046FCF743 +:104B200098FBE8BB2846FCF794FBC8BB20784FF005 +:104B30000107C0074FF0000102D08DF83A7001E084 +:104B40008DF83A1020788846C0F3C1008DF8000037 +:104B500060788DF80910C10803D0072011B0BDE8B6 +:104B6000F08FB0B3C10701D08DF80970810705D56A +:104B70009DF8091041F002018DF80910400705D594 +:104B80009DF8090040F004008DF809009DF8090027 +:104B9000810703D540F001008DF80900002000E0F6 +:104BA00015E06E4606EB400162884A81401CA288EF +:104BB000C0B20A820328F5D32078C0F3C1000128CF +:104BC00025D0032823D04846FCF743FB28B110200A +:104BD000C4E7FFE78DF80970D8E799F800004008AE +:104BE00008D0012809D0022807D0032805D043F2B5 +:104BF0000220B3E78DF8028001E08DF8027048468C +:104C000050F8011FCDF803108088ADF80700FEF7BB +:104C1000AFFB8DF801000021424606EB41002B88D6 +:104C2000C3826B888383AB884384EB880385491CEC +:104C3000C285C9B282860329EFD3E088ADF83C0073 +:104C400068460DF053FC002887D19AF818005546A5 +:104C5000112801D0082081E706200FF0D7F838B1DD +:104C60002078C0F3C100012804D0032802D006E058 +:104C7000122073E795F8240000283FF46EAFFEF78A +:104C800003FA022801D2132068E7584600F04FF9D2 +:104C900000289DD185F819B068460DF06DFD04F02F +:104CA000C2FA040094D1687E00F051F91220FEF798 +:104CB000D5F9204652E770B56B4D287E122801D0F9 +:104CC0000820DCE60DF05BFD04F0ADFA040005D130 +:104CD000687E00F049F91120FEF7C0F92046CEE6C3 +:104CE00070B5064615460C460846FCF7D8FA18B9C2 +:104CF0002846FCF7D4FA08B11020C0E62A4621461F +:104D000030460EF03BF804F08EFA0028F5D12178F9 +:104D10007F29F2D10520B2E67CB505460C4608464F +:104D2000FCF797FA08B110207CBD2846FEF78AFBF5 +:104D300020B10078222804D208207CBD43F2020072 +:104D40007CBD494890F83000400701D511207CBD5A +:104D50002078C00802D16078C00801D007207CBD4F +:104D6000ADF8005020788DF8020060788DF80300CF +:104D70000220ADF8040068460CF03BFE04F053FA44 +:104D80007CBD70B586B014460D460646FEF75AFB4C +:104D900028B10078222805D2082006B06FE643F239 +:104DA0000200FAE72846FCF7A1FA20B944B12046F0 +:104DB000FCF793FA08B11020EFE700202060A080F4 +:104DC000294890F83000800701D51120E5E703A9B4 +:104DD00030460CF05EFE10B104F025FADDE7ADF8C8 +:104DE0000060BDF81400ADF80200BDF81600ADF883 +:104DF0000400BDF81000BDF81210ADF80600ADF8C3 +:104E000008107DB1298809B1ADF80610698809B18B +:104E1000ADF80210A98809B1ADF80810E98809B108 +:104E2000ADF80410DCB1BDF80610814201D9081AB2 +:104E30002080BDF80210BDF81400814201D9081A83 +:104E40006080BDF80800BDF80410BDF816200144CC +:104E5000BDF812001044814201D9081AA0806846AA +:104E60000CF0D5FEB8E70000F00100201CB56C493D +:104E70000968CDE9001068460DF03AFB04F0D3F95B +:104E80001CBD1CB500200090019068460DF030FB61 +:104E900004F0C9F91CBD70B505460C460846FCF780 +:104EA000FEF908B11020EAE5214628460DF012F976 +:104EB000BDE8704004F0B7B93EB505460C4608465B +:104EC000FCF7EDF908B110203EBD002000900190E4 +:104ED0000290ADF800502089ADF8080020788DF8D8 +:104EE0000200606801902089ADF808006089ADF883 +:104EF0000A0068460DF000F904F095F93EBD0EB5C4 +:104F0000ADF800000020019068460DF0F5F804F0BF +:104F10008AF90EBD10800888508048889080C88823 +:104F200010818888D080002050819081704710B512 +:104F3000044604F0E4F830B1407830B1204604F083 +:104F4000FCFB002010BD052010BD122010BD10B5C7 +:104F500004F0D5F8040000D1FFDF607800B9FFDF6E +:104F60006078401E607010BD10B504F0C8F80400F1 +:104F700000D1FFDF6078401C607010BD1CB5ADF83B +:104F800000008DF802308DF803108DF8042068467B +:104F90000DF0B7FE04F047F91CBD0CB521A2D2E913 +:104FA0000012CDE900120079694601EB501000783B +:104FB0000CBD0278520804D0012A02D043F202202C +:104FC0007047FEF7ACB91FB56A46FFF7A3FF684606 +:104FD0000DF008FC04F027F904B010BD70B50C000A +:104FE00006460DD0FEF72EFA050000D1FFDFA680A1 +:104FF00028892081288960816889A081A889E08129 +:105000003DE500B540B1012805D0022803D00328B2 +:1050100004D0FFDF002000BDFF2000BD042000BD44 +:1050200014610200070605040302010010B50446DE +:10503000FCF70FF908B1102010BD2078C0F3021062 +:10504000042807D86078072804D3A178102901D84C +:10505000814201D2072010BDE078410706D42179B2 +:105060004A0703D4000701D4080701D5062010BD64 +:10507000002010BD10B513785C08837F64F3C7135C +:10508000837713789C08C37F64F30003C377107899 +:10509000C309487863F34100487013781C090B7802 +:1050A00064F347130B701378DB0863F30000487058 +:1050B0005078487110BD10B5C4780B7864F30003C4 +:1050C0000B70C478640864F341030B70C478A408BF +:1050D00064F382030B70C478E40864F3C3030B70B9 +:1050E0000379117863F30001117003795B0863F3AE +:1050F0004101117003799B0863F3820111700079FB +:10510000C00860F3C301117010BD70B514460D46A0 +:10511000064604F06BFA80B10178182221F00F01E5 +:10512000891C21F0F001A03100F8081B214609F08C +:1051300084F9BDE8704004F05CBA29463046BDE809 +:1051400070401322FEF781B92DE9F047064608A802 +:10515000904690E8300489461F46142200212846D4 +:1051600009F095F90021CAF80010B8F1000F03D03A +:10517000B9F1000F03D114E03878C00711D02068CE +:10518000FCF78DF8C0BBB8F1000F07D120681230D2 +:1051900028602068143068602068A8602168CAF818 +:1051A00000103878800724D56068FCF796F818BBA3 +:1051B000B9F1000F21D0FFF7E4F80168C6F86811D3 +:1051C0008188A6F86C11807986F86E0101F013FDD4 +:1051D000F94FEF60626862B196F8680106F26911F2 +:1051E00040081032FEF7FDF810223946606809F0D9 +:1051F00024F90020BDE8F08706E0606820B1E8608F +:105200006068C6F86401F4E71020F3E730B505469E +:1052100008780C4620F00F00401C20F0F0011031FF +:1052200021700020607095F8230030B104280FD061 +:10523000052811D0062814D0FFDF20780121B1EB1A +:10524000101F04D295F8200000F01F00607030BDE0 +:1052500021F0F000203002E021F0F000303020702A +:10526000EBE721F0F0004030F9E7F0B591B002270C +:1052700015460C4606463A46ADF80870092103ABC0 +:1052800005F063F80490002810D004208DF8040085 +:105290008DF80170E034099605948DF818500AA92C +:1052A000684610F0FCFA00B1FFDF012011B0F0BD3C +:1052B00010B588B00C460A99ADF80000CBB118685B +:1052C000CDF80200D3F80400CDF80600ADF80A20AE +:1052D000102203A809F0B1F868460DF0F3FA03F0C4 +:1052E000A2FF002803D1A17F41F01001A17708B0EF +:1052F00010BD0020CDF80200E6E72DE9F84F064684 +:10530000808A0D4680B28246FEF79CF804463078CB +:10531000DFF8A48200274FF00209A8F120080F2827 +:1053200070D2DFE800F06FF23708387D8CC8F1F0FA +:10533000EFF35FF3F300A07F00F00300022809D031 +:105340005FF0000080F0010150460EF0AFFD050057 +:1053500003D101E00120F5E7FFDF98F85C10C907F1 +:1053600002D0D8F860000BE0032105F11D0012F017 +:10537000BEF8D5F81D009149B0FBF1F201FB120017 +:10538000C5F81D0070686867B068A8672078252890 +:1053900000D0FFDFCAE0A07F00F00300022809D0A0 +:1053A0005FF0000080F0010150460EF07FFD060026 +:1053B00003D101E00120F5E7FFDF3078810702D556 +:1053C0002178252904D040F001003070BDE8F88F25 +:1053D00085F80090307F287106F11D002D36C5E953 +:1053E0000206F3E7A07F00F00300022808D00020A7 +:1053F00080F0010150460EF059FD040004D102E096 +:105400000120F5E7A7E1FFDF2078C10604D50720DA +:1054100028703D346C60D9E740F008002070D5E773 +:10542000E07F000700D5FFDF307CB28800F0010389 +:1054300001B05046BDE8F04F092106F064B804B948 +:10544000FFDF716821B1102204F1240008F0F5FF9C +:1054500028212046FDF75EFEA07F00F00300022811 +:105460000ED104F12400002300901A462146504634 +:10547000FFF71EFF112807D029212046FDF74AFE1D +:10548000307A84F82000A1E7A07F000700D5FFDF75 +:1054900014F81E0F40F008002070E782A761E76152 +:1054A000C109607861F34100014660F382016170D7 +:1054B000307AE0708AE7A07F00F00300022809D06C +:1054C0005FF0000080F0010150460EF0EFFC040098 +:1054D00003D101E00120F5E7FFDF022104F185009F +:1054E00012F005F80420287004F5B4706860B4F870 +:1054F00085002882304810387C346C61C5E9028010 +:1055000064E703E024E15BE02DE015E0A07F00F01C +:105510000300022807D0002080F0010150460EF061 +:10552000C5FC18B901E00120F6E7FFDF324621464D +:105530005046BDE8F84FE8E504B9FFDF20782128A0 +:10554000A1D93079012803D1E07F40F00800E0774D +:10555000324621465046FFF7D8FD2046BDE8F84FB9 +:105560002321FDF7D7BD3279AA8005F1080309216F +:10557000504604F0EAFEE86010B10520287025E7E7 +:10558000A07F00F00300022808D0002080F0010175 +:1055900050460EF08BFC040003D101E00120F5E73A +:1055A000FFDF04F1620102231022081F0EF005FB49 +:1055B00007703179417009E75002002040420F0026 +:1055C000A07F00F00300022808D0002080F0010135 +:1055D00050460EF06BFC050003D101E00120F5E719 +:1055E000FFDF95F8840000F0030001287AD1A07F46 +:1055F00000F00307E07F10F0010602D0022F04D173 +:1056000033E095F8A000C0072BD0D5F8601121B386 +:1056100095F88320087C62F387000874A17FCA098B +:10562000D5F8601162F341000874D5F8601166F393 +:1056300000000874AEB1D5F86001102204F1240115 +:10564000883508F0FAFE287E40F001002876287898 +:1056500020F0010005F8880900E016B1022F04D0FF +:105660002DE095F88800C00727D0D5F85C1121B34C +:1056700095F88320087C62F387000874A17FCA092B +:10568000D5F85C1162F341000874D5F85C1166F33B +:10569000000008748EB1D5F85C01102204F12401D9 +:1056A000883508F0CAFE287840F0010005F8180B8C +:1056B000287820F0010005F8A009022F44D000202E +:1056C00000EB400005EBC00090F88800800709D58A +:1056D00095F87C00D5F86421400805F17D01103271 +:1056E000FDF77FFE8DF8009095F884006A4600F083 +:1056F00003008DF8010095F888108DF8021095F8D8 +:10570000A0008DF803002146504601F05DFA207894 +:10571000252805D0212807D0FFDF2078222803D9AB +:1057200022212046FDF7F6FCA07F00F003000228AE +:105730000CD0002080F0010150460EF0C9FB00287B +:105740003FF44FAEFFDF41E60120B9E70120F1E76A +:10575000706847703AE6FFDF38E670B5FE4C00250A +:1057600084F85C50256610F066F804F110012046BC +:1057700003F0F8FE84F8305070BD70B50D46FDF7AB +:1057800061FE040000D1FFDF4FF4B872002128460B +:1057900008F07DFE04F124002861A07F00F00300E2 +:1057A000022809D05FF0010105F1E00010F044F893 +:1057B000002800D0FFDF70BD0221F5E70A46014650 +:1057C00002F1E00010F059B870B50546406886B0A7 +:1057D00001780A2906D00D2933D00E292FD0FFDFFA +:1057E00006B070BD86883046FDF72CFE040000D15F +:1057F000FFDF20782128F3D028281BD168680221F8 +:105800000E3001F0D6F9A8B168680821801D01F0BA +:10581000D0F978B104F1240130460DF00FFA03F00D +:1058200002FD00B1FFDF06B02046BDE8704029212F +:10583000FDF770BC06B0BDE8704003F0DABE012190 +:1058400001726868C6883046FDF7FCFD040000D18F +:10585000FFDFA07F00F00301022902D120F0100039 +:10586000A077207821280AD06868017A09B10079E8 +:1058700080B1A07F00F00300022862D0FFDFA07F8C +:1058800000F003000228ABD1FEF72DF80028A7D0C6 +:10589000FFDFA5E703F0ADFEA17F08062BD5E07F73 +:1058A000C00705D094F8200000F01F00102820D079 +:1058B0005FF0050084F82300207829281DD02428D3 +:1058C000DDD13146042012F0F9F822212046FDF7FF +:1058D00021FCA07F00F00300022830D05FF0000020 +:1058E00080F0010130460EF0F3FA0028C7D0FFDF48 +:1058F000C5E70620DEE70420DCE701F0030002280C +:1059000008D0002080F0010130460EF0CFFA0500EB +:1059100003D101E00120F5E7FFDF25212046FDF757 +:10592000F9FB03208DF80000694605F1E0000FF057 +:105930009BFF0228A3D00028A1D0FFDF9FE7012012 +:10594000CEE703F056FE9AE72DE9F04387B099467B +:10595000164688460746FDF775FD04004BD02078B3 +:10596000222848D3232846D0E07F000743D4A07FD5 +:1059700000F00300022809D05FF0000080F0010170 +:1059800038460EF093FA050002D00CE00120F5E74E +:10599000A07F00F00300022805D001210022384634 +:1059A0000EF07BFA05466946284601F034F9009866 +:1059B00000B9FFDF45B10098E03505612078222865 +:1059C00006D0242804D007E000990020086103E0F5 +:1059D00025212046FDF79EFB00980121417047627A +:1059E000868001A9C0E902890FF059FF022802D080 +:1059F000002800D0FFDF07B0BDE8F08370B586B0A7 +:105A00000546FDF71FFD017822291ED9807F00F091 +:105A10000300022808D0002080F0010128460EF083 +:105A200045FA04002FD101E00120F5E7FFDF2AE06D +:105A3000B4F85E0004F1620630440178427829B17E +:105A400021462846FFF711FCB0B9C9E6ADF804209D +:105A50000921284602AB04F078FC03900028F4D01A +:105A600005208DF80000694604F1E0000FF0FCFE0F +:105A7000022801D000B1FFDF02231022314604F1D9 +:105A80005E000EF0D0F8B4F860000028D0D1A7E690 +:105A900010B586B00446FDF7D5FC017822291BD944 +:105AA000807F00F00300022808D0002080F0010170 +:105AB00020460EF0FBF9040003D101E00120F5E7D8 +:105AC000FFDF06208DF80000694604F1E0000FF0CA +:105AD000CBFE002800D0FFDF06B010BD2DE9F05F3F +:105AE00005460C4600270078904601093E4604F121 +:105AF000080BBA4602297DD0072902D00A2909D10C +:105B000046E0686801780A2905D00D2930D00E29B1 +:105B10002ED0FFDFBBE114271C26002C6BD0808821 +:105B2000A080FDF78FFC5FEA000900D1FFDF99F844 +:105B300017005A46400809F11801FDF752FC686841 +:105B4000C0892082696851F8060FC4F812004868BD +:105B5000C4F81600A07E01E03002002020F006000C +:105B600040F00100A07699F81E0040F020014DE0C1 +:105B70001A270A26002CD1D0C088A080FDF762FC2D +:105B8000050000D1FFDF59462846FFF73FFB7EE1C5 +:105B90000CB1A88BA080287A0B287DD006DC0128C8 +:105BA0007BD0022808D0032804D135E00D2875D019 +:105BB0000E2874D0FFDF6AE11E270926002CADD025 +:105BC000A088FDF73FFC5FEA000900D1FFDF287BDA +:105BD00000F003000128207A1BD020F00100207281 +:105BE000297B890861F341002072297BC90861F390 +:105BF000820001E041E1F2E02072297B090961F3B2 +:105C0000C300207299F81E0040F0400189F81E1070 +:105C10003DE140F00100E2E713270D26002CAAD059 +:105C2000A088FDF70FFC8146807F00F0030002286A +:105C300008D0002080F00101A0880EF037F905009F +:105C400003D101E00120F5E7FFDF99F81E0000F025 +:105C50000302022A50D0686F817801F00301012904 +:105C6000217A4BD021F00101217283789B0863F3E4 +:105C7000410121728378DB0863F38201217283780A +:105C80001B0963F3C3012172037863F306112172C8 +:105C9000437863F3C71103E061E0A9E090E0A1E07D +:105CA000217284F809A0C178A172022A29D0027950 +:105CB000E17A62F30001E1720279520862F3410174 +:105CC000E1720279920862F38201E1720279D208EC +:105CD00062F3C301E1724279217B62F30001217317 +:105CE0004279520862F3410121734279920862F3CA +:105CF00082012173407928E0A86FADE741F00101EE +:105D0000B2E74279E17A62F30001E1724279520826 +:105D100062F34101E1724279920862F38201E17219 +:105D20004279D20862F3C301E1720279217B62F306 +:105D3000000121730279520862F341012173027953 +:105D4000920862F3820121730079C00860F3C301F5 +:105D5000217399F80000232831D9262140E0182723 +:105D60001026E4B3A088FDF76DFB8346807F00F02A +:105D70000300022809D0002080F00101A0880EF065 +:105D800095F85FEA000903D101E00120F4E7FFDFA5 +:105D9000E868A06099F8000040F0040189F800105C +:105DA00099F80100800708D5012020739BF80000B6 +:105DB00023286CD92721584651E084F80CA066E0CE +:105DC00015270F265CB1A088FDF73CFB8146062213 +:105DD0005946E86808F0C7FB0120A073A0E041E045 +:105DE00048463CE016270926E4B3287B20724EE0A3 +:105DF000287B19270E26ACB3C4F808A0A4F80CA081 +:105E0000012807D0022805D0032805D0042803D094 +:105E1000FFDF0DE0207207E0697B042801F00F012D +:105E200041F0800121721ED0607A20F00300607280 +:105E3000A088FDF707FB05460078212827D02328F6 +:105E400000D0FFDFA87F00F00300022813D000205D +:105E500080F00101A0880EF03BF822212846FDF7D2 +:105E600059F914E004E0607A20F00300401CDEE7FA +:105E7000A8F8006010E00120EAE70CB16888A08073 +:105E8000287A68B301280AD002284FD0FFDFA8F88B +:105E900000600CB1278066800020BDE8F09F1527C8 +:105EA0000F26002CE4D0A088FDF7CCFA807F00F00C +:105EB0000300022808D0002080F00101A0880DF026 +:105EC000F5FF050003D101E00120F5E7FFDFD5F87C +:105ED0001D000622594608F046FB84F80EA0D6E7BE +:105EE00017270926002CC3D0A088FDF7ABFA8146FE +:105EF000807F00F00300022808D0002080F001011C +:105F0000A0880DF0D3FF050003D101E00120F5E7E3 +:105F1000FFDF6878800701D5022000E001202072B1 +:105F200099F800002328B2D9272159E719270E260E +:105F3000002C9DD0A088FDF785FA5FEA000900D10A +:105F4000FFDFC4F808A0A4F80CA084F808A0A07A89 +:105F500040F00300A07299F81E10C90961F3820095 +:105F6000A07299F81F2099F81E1012EAD11F05D0CF +:105F700099F8201001F01F0110292BD020F0080003 +:105F8000A07299F81F10607A61F3C3006072697A99 +:105F900001F003010129A2D140F00400607299F8D8 +:105FA0001E0000F003000228E87A16D0217B60F37F +:105FB00000012173AA7A607B62F300006073EA7AC1 +:105FC000520862F341012173A97A490861F3410043 +:105FD00060735CE740F00800D2E7617B60F300018A +:105FE0006173AA7A207B62F300002073EA7A520878 +:105FF00062F341016173A97A490861F3410020739A +:1060000045E710B5FE4C30B10146102204F12000E6 +:1060100008F013FA012084F8300010BD10B50446D2 +:1060200000F0E9FDF64920461022BDE8104020317D +:1060300008F003BA70B5F24D06004FF0000413D01B +:10604000FBF707F908B110240CE00621304608F0F0 +:1060500071FA411C05D028665FF0010085F85C00EC +:1060600000E00724204670BD0020F7E7007810F01C +:106070000F0204D0012A05D0022A0CD110E0000939 +:1060800009D10AE00009012807D0022805D0032819 +:1060900003D0042801D00720704708700020704703 +:1060A0000620704705282AD2DFE800F003070F1703 +:1060B0001F00087820F0FF001EE0087820F00F0095 +:1060C000401C20F0F000103016E0087820F00F009F +:1060D000401C20F0F00020300EE0087820F00F0087 +:1060E000401C20F0F000303006E0087820F00F006F +:1060F000401C20F0F000403008700020704707205E +:1061000070472DE9F041804688B00D4600270846CB +:10611000FBF7ECF8A8B94046FDF794F9040003D06A +:106120002078222815D104E043F2020008B0BDE82F +:10613000F08145B9A07F410603D500F00300022895 +:1061400001D01020F2E7A07FC10601D4010702D5DB +:106150000DB10820EAE7E17F090701D50D20E5E749 +:1061600000F0030002280DD165B12846FEF75EFF5E +:106170000700DBD1FBF736FB20B9E878800701D5B3 +:106180000620D3E7A07F00F00300022808D00020FB +:1061900080F0010140460DF089FE060002D00FE0BC +:1061A0000120F5E7A07F00F0030002280ED00020B8 +:1061B00080F00101002240460DF06FFE060007D07E +:1061C000A07F00F00300022804D009E00120EFE7DF +:1061D0000420ABE725B12A4631462046FEF74AFFA8 +:1061E0006946304600F017FD009800B9FFDF0099BE +:1061F000022006F1E0024870C1F824804A610022C2 +:106200000A81A27F02F00302022A1CD00120087139 +:10621000287800F00102087E62F3010008762A78EF +:10622000520862F3820008762A78920862F3C3006B +:1062300008762A78D20862F30410087624212046D2 +:10624000FCF768FF33E035B30871301D88613078A2 +:10625000400908777078C0F340004877287800F04C +:106260000102887F62F301008877A27FD20962F37E +:1062700082008877E27F62F3C3008877727862F3E6 +:1062800004108877A878C87701F1210228462031C8 +:10629000FEF711FF03E00320087105200876252191 +:1062A0002046FCF737FFA07F20F04000A07701A92F +:1062B00000980FF0F4FA022801D000B1FFDF384651 +:1062C00034E72DE9FF4F8DB09A4693460D460027DF +:1062D0000D98FDF7B7F8060006D03078262806D0CE +:1062E000082011B0BDE8F08F43F20200F9E7B07F5B +:1062F00000F00309B9F1020F11D04DB95846FEF76D +:1063000095FE0028EDD1B07F00F00300022806D0F2 +:10631000BBF1000F11D0FBF765FA20B10DE0BBF126 +:10632000000F50D109E006200DF068FD28B19BF860 +:106330000300800701D50620D3E7B07F00F00300FB +:10634000022809D05FF0000080F001010D980DF0E7 +:10635000ADFD040003D101E00120F5E7FFDF852D4D +:1063600027D007DCEDB1812D1DD0822D1DD0832DCE +:1063700008D11CE0862D1ED0882D1ED0892D1ED060 +:106380008A2D1ED00F2020710F281CD003F02EF96B +:10639000D8B101208DF81400201D06902079B0B1ED +:1063A00056E10020EFE70120EDE70220EBE70320B4 +:1063B000E9E70520E7E70620E5E70820E3E709200D +:1063C000E1E70A20DFE707208BE7112089E7B9F131 +:1063D000020F03D0A56F03D1A06F02E0656FFAE74B +:1063E000606F804631D04FF0010001904FF0020005 +:1063F00000905A4621463046FEF73CFE02E000007F +:10640000300200209BF8000000F00101A87861F341 +:106410000100A870B17FC90961F38200A870F17F03 +:1064200061F3C300A870617861F30410A87020784C +:10643000400928706078C0F3400068709BF8020043 +:10644000E87000206871287103E0022001900120AB +:106450000090A87898F80210C0F3C000C1F3C00102 +:10646000084003902CD05046FAF7F3FEC0BBDAF890 +:106470000C00FAF7EEFE98BBDAF81C00FAF7E9FE1A +:1064800070BBDAF80C00A060DAF81C00E0606078FD +:1064900098F8012042EA500161F34100607098F8D9 +:1064A0000210C0B200EA111161F300006070002018 +:1064B0002077009906F11700022907D0012106E094 +:1064C000607898F8012002EA5001E5E7002104EB2A +:1064D000810148610199701C022902D0012101E06B +:1064E00028E0002104EB81014861A87800F0030056 +:1064F000012857D198F8020000F00300012851D17B +:10650000B9F1020F04D02A1D691D5846FEF7D3FDCC +:10651000287998F8041008408DF82C00697998F8CB +:10652000052011408DF8301008433BD05046FAF753 +:1065300090FE08B11020D4E60AF110018B46B9F1A3 +:10654000020F17D00846002104F18C03CDE90003A7 +:1065500004F5AE7202920BAB2046039AFEF7F4FDEF +:106560000028E8D1B9F1020F08D0504608D14FF009 +:10657000010107E050464FF00101E5E75846F5E715 +:106580004FF0000104F1A403CDE9000304F5B0725B +:10659000029281F001010CAB2046039AFEF7D4FD74 +:1065A0000028C8D16078800733D4A87898F8021002 +:1065B000C0F38000C1F3800108432AD0297898F8FD +:1065C0000000F94AB9F1020F06D032F81120430059 +:1065D000DA4002F003070AE032F810204B00DA40FC +:1065E00012F0030705D0012F0AD0022F0AD0032F83 +:1065F00006D0039A6AB1012906D0042904D008E024 +:106600000227F6E70127F4E7012801D0042800D18A +:106610000427B07F40F08000B077F17F039860F3EB +:106620000001F1776078800705D50320A0710398F9 +:1066300070B9002029E00220022F18D0012F18D0B5 +:10664000042F2AD00020A071B07F20F08000B07706 +:1066500025213046FCF75EFD05A904F1E0000FF0AE +:1066600003F910B1022800D0FFDF002039E6A07145 +:10667000DFE7A0710D22002104F1200007F007FFE1 +:10668000207840F00200207001208DF8100004AA4C +:1066900031460D9800F098FADAE70120A071D7E7AB +:1066A0002DE9F04387B09046894604460025FCF763 +:1066B000C9FE060006D03078272806D0082007B08B +:1066C000BDE8F08343F20200F9E7B07F00F0030079 +:1066D000022809D05FF0000080F0010120460DF093 +:1066E000E5FB040003D101E00120F5E7FFDFA77916 +:1066F0005FEA090005D0012821D0B9F1020F26D1A7 +:1067000010E0B8F1000F22D1012F05D0022F05D0E3 +:10671000032F05D0FFDF2EE00C252CE001252AE019 +:10672000022528E04046FAF794FDB0B9032F0ED1B8 +:106730001022414604F11D0007F07FFE1BE0012FEF +:1067400002D0022F03D104E0B8F1000F13D00720CC +:10675000B5E74046FAF77DFD08B11020AFE71022FB +:10676000002104F11D0007F092FE0621404607F0CB +:10677000E1FEC4F81D002078252140F002002070C1 +:106780003046FCF7C7FC2078C10713D020F0010089 +:10679000207002208DF8000004F11D0002908DF899 +:1067A00004506946C3300FF05FF8022803D010B1DF +:1067B000FFDF00E02577002081E730B587B00D4688 +:1067C0000446FCF73FFE98B1807F00F003000228EA +:1067D00011D0002080F0010120460DF067FB04007D +:1067E0000ED02846FAF735FD38B1102007B030BD7D +:1067F00043F20200FAE70120ECE72078400701D4D9 +:106800000820F3E7294604F13D002022054607F061 +:1068100014FE207840F01000207001070FD520F002 +:106820000800207007208DF80000694604F1E000A0 +:1068300001950FF019F8022801D000B1FFDF002008 +:10684000D4E770B50D460646FCF7FCFD18B101789B +:10685000272921D102E043F2020070BD807F00F0C1 +:106860000300022808D0002080F0010130460DF01E +:106870001DFB040003D101E00120F5E7FFDFA07953 +:10688000022809D16078C00706D02A462146304642 +:10689000FEF7EBFC10B10FE0082070BDB4F860000B +:1068A0000E280BD204F1620102231022081F0DF002 +:1068B00084F9012101704570002070BD112070BD68 +:1068C00070B5064614460D460846FAF7C2FC18B9DC +:1068D0002046FAF7E4FC08B1102070BDA6F57F4011 +:1068E000FF380ED03046FCF7ADFD38B14178224676 +:1068F0004B08811C1846FCF774FD07E043F20200C8 +:1069000070BD2046FDF7A5FD0028F9D11021E01D3E +:1069100010F0EDFDE21D294604F1170000F08BF99F +:10692000002070BD2DE9F04104468AB01546884626 +:1069300000270846FAF7DAFC18B92846FAF7D6FC19 +:1069400018B110200AB0BDE8F0812046FCF77AFDAE +:10695000060003D0307827281BD102E043F2020062 +:10696000F0E7B07F00F00300022809D05FF00000DC +:1069700080F0010120460DF099FA040003D101E0F6 +:106980000120F5E7FFDF2078400702D56078800717 +:1069900001D40820D6E7B07F00F00300022805D01C +:1069A000A06F05D1A16F04E01C610200606FF8E7E1 +:1069B000616F407800B19DB1487810B1B8F1000F17 +:1069C0000ED0ADB1EA1D06A8E16800F034F910223E +:1069D00006A905F1170007F003FD18B1042707E029 +:1069E0000720AFE71022E91D04F12D0007F025FD77 +:1069F000B8F1000F06D0102208F1070104F11D00C4 +:106A000007F01BFD2078252140F002002070304661 +:106A1000FCF780FB2078C10715D020F00100207022 +:106A200002208DF8000004F11D0002901030039048 +:106A30008DF804706946B3300EF016FF022803D0BB +:106A400010B1FFDF00E0277700207BE7F8B515469F +:106A50000E460746FCF7F6FC040004D020782228F6 +:106A600004D00820F8BD43F20200F8BDA07F00F07A +:106A70000300022802D043F20500F8BD3046FAF7C1 +:106A8000E8FB18B92846FAF7E4FB08B11020F8BD76 +:106A900000953288B31C21463846FEF709FC1128C0 +:106AA00015D00028F3D1297C4A08A17F62F3C711D1 +:106AB000A177297CE27F61F30002E277297C8908D3 +:106AC00084F82010A17F21F04001A177F8BDA17FBB +:106AD0000907FBD4D6F80200C4F83600D6F8060041 +:106AE000C4F83A003088A0861022294604F1240018 +:106AF00007F0A3FC287C4108E07F61F34100E077C8 +:106B0000297C61F38200E077287C800884F82100EA +:106B1000A07F40F00800A0770020D3E770B50D46B5 +:106B200006460BB1072070BDFCF78CFC040007D0B3 +:106B30002078222802D3A07F800604D4082070BDCC +:106B400043F2020070BDADB1294630460CF076F834 +:106B500002F069FB297C4A08A17F62F3C711A17783 +:106B6000297CE27F61F30002E277297C890884F8BE +:106B7000201004E030460CF084F802F054FBA17FB2 +:106B800021F02001A17770BD70B50D46FCF75AFCCD +:106B9000040005D02846FAF782FB20B1102070BD12 +:106BA00043F2020070BD29462046FEF72FFB00206D +:106BB00070BD04E010F8012B0AB100207047491E97 +:106BC00089B2F7D20120704770B51546064602F02B +:106BD0000DFD040000D1FFDF207820F00F00801CA5 +:106BE00020F0F0002030207066802868A060BDE8AA +:106BF000704002F0FEBC10B5134C94F83000002831 +:106C000008D104F12001A1F110000EF06FFE012067 +:106C100084F8300010BD10B190F8B9202AB10A48AC +:106C200090F8350018B1002003E0B83001E00648C4 +:106C300034300860704708B50023009313460A46B5 +:106C40000DF031FB08BD00003002002018B1817842 +:106C5000012938D101E010207047018842F6011265 +:106C6000881A914231D018DC42F60102A1EB0200F1 +:106C700091422AD00CDC41B3B1F5C05F25D06FF44E +:106C8000C050081821D0A0F57060FF381BD11CE05F +:106C900001281AD002280AD117E0B0F5807F14D05D +:106CA00008DC012811D002280FD003280DD0FF28BE +:106CB00009D10AE0B0F5817F07D0A0F580700338D4 +:106CC00003D0012801D0002070470F2070470A2808 +:106CD0001FD008DC0A2818D2DFE800F0191B1F1F9C +:106CE000171F231D1F21102815D008DC0B2812D0D8 +:106CF0000C2810D00D2816D00F2806D10DE0112831 +:106D00000BD084280BD087280FD003207047002099 +:106D1000704705207047072070470F2070470420F8 +:106D20007047062070470C20704743F202007047FE +:106D300038B50C46050041D06946FFF797F90028A1 +:106D400019D19DF80010607861F302006070694607 +:106D5000681CFFF78BF900280DD19DF800106078B2 +:106D600061F3C5006070A978C1F34101012903D026 +:106D7000022905D0072038BD217821F0200102E04A +:106D8000217841F020012170410704D0A978C90879 +:106D900061F386106070607810F0380F07D0A97822 +:106DA000090961F3C710607010F0380F02D16078E4 +:106DB000400603D5207840F040002070002038BD08 +:106DC00070B504460020088015466068FFF7B0FFE4 +:106DD000002816D12089A189884211D8606880785E +:106DE000C0070AD0B1F5007F0AD840F20120B1FBFC +:106DF000F0F200FB1210288007E0B1F5FF7F01D907 +:106E00000C2070BD01F201212980002070BD10B559 +:106E10000478137864F3000313700478640864F34F +:106E2000410313700478A40864F382031370047898 +:106E3000E40864F3C30313700478240964F30413AF +:106E400013700478640964F34513137000788009A3 +:106E500060F38613137031B10878C10701D1800740 +:106E600001D5012000E0002060F3C713137010BDAE +:106E70004278530702D002F0070306E012F0380F01 +:106E800002D0C2F3C20300E001234A7863F3020296 +:106E90004A70407810F0380F02D0C0F3C20005E00D +:106EA000430702D000F0070000E0012060F3C502B4 +:106EB0004A7070472DE9F04F95B00D00824613D00F +:106EC00012220021284607F0E2FA4FF6FF7B05AABE +:106ED0000121584607F0A3F80024264637464FF410 +:106EE00020586FF4205972E0102015B0BDE8F08FE3 +:106EF0009DF81E0001280AD1BDF81C1041450BD099 +:106F000011EB09000AD001280CD002280CD0042C67 +:106F10000ED0052C0FD10DE0012400E00224BDF8B5 +:106F20001A6008E0032406E00424BDF81A7002E0A9 +:106F3000052400E00624BDF81A10514547D12C74F1 +:106F4000BEB34FF0000810AA4FF0070ACDE9028245 +:106F5000CDE900A80DF13C091023CDF81090424670 +:106F60003146584607F02BF908BBBDF83C002A46CD +:106F7000C0B210A90EF045FDC8B9AE81CFB1CDE9C0 +:106F800000A80DF1080C0AAE40468CE8410213231C +:106F900000223946584607F012F940B9BDF83C00C6 +:106FA000F11CC01EC0B22A1D0EF02BFD10B1032033 +:106FB0009BE70AE0BDF82900E881062C05D19DF881 +:106FC0001E00A872BDF81C00288100208DE705A8CE +:106FD00007F031F800288BD0FFF779FE85E72DE91F +:106FE000F0471C46DDE90978DDF8209015460E00D3 +:106FF000824600D1FFDF0CB1208818B1D5B1112035 +:10700000BDE8F087022D01D0012100E0002106F14A +:10701000140005F0CDFEA8F8000002463B462946C4 +:10702000504603F092F9C9F8000008B9A41C3C606E +:107030000020E5E71320E3E7F0B41446DDE904524D +:107040008DB1002314B1022C09D101E0012306E027 +:107050000D7CEE0703D025F0010501230D742146B8 +:10706000F0BC04F050BA1A80F0BC70472DE9FE4F16 +:1070700091461A881C468A468046FAB102AB4946B8 +:1070800003F063F9050019D04046A61C27880DF0CF +:1070900050F83246072629463B4600960CF05FFC26 +:1070A00020882346CDE900504A4651464046FFF726 +:1070B000C3FF002020800120BDE8FE8F0020FBE7F9 +:1070C0002DE9F04786B082460EA8904690E8B000C1 +:1070D000894604AA05A903A88DE807001E462A468A +:1070E00021465046FFF77BFF039901B1012139701A +:1070F000002818D1FA4904F1140204AB086003987F +:1071000005998DE8070042464946504606F003FAC5 +:10711000A8B1092811D2DFE800F005080510100A0F +:107120000C0C0E00002006B06AE71120FBE70720D8 +:10713000F9E70820F7E70D20F5E70320F3E7BDF8AE +:1071400010100398CDE9000133462A4621465046E7 +:10715000FFF772FFE6E72DE9F04389B01646DDE957 +:1071600010870D4681461C461422002103A807F013 +:107170008EF9012002218DF810108DF80C008DF889 +:107180001170ADF8146064B1A278D20709D08DF8FF +:107190001600E088ADF81A00A088ADF81800A068C5 +:1071A000079008A80095CDE90110424603A948467A +:1071B0006B68FFF785FF09B0BDE8F083F0B58BB0D1 +:1071C00000240646069407940727089405A8099406 +:1071D000019400970294CDE903400D461023224606 +:1071E000304606F0ECFF78B90AA806A9019400978A +:1071F0000294CDE90310BDF8143000222946304630 +:1072000006F07BFD002801D0FFF761FD0BB0F0BD5B +:1072100006F00CBC2DE9FC410C468046002602F02D +:10722000E5F9054620780D287ED2DFE800F0BC079E +:1072300013B325BD49496383AF959B00A8480068F7 +:1072400020B1417841F010014170ADE0404602F0BC +:10725000FDF9A9E0042140460CF028FE070000D10A +:10726000FFDF07F11401404605F037FDA5BB1321F0 +:107270004046FDF7CFFB97E0042140460CF016FE98 +:10728000070000D1FFDFE088ADF800000020B881E2 +:107290009DF80000010704D5C00602D5A088B8817A +:1072A00005E09DF8010040067ED5A088F88105B96B +:1072B000FFDF22462946404601F0ACFC022673E07F +:1072C000E188ADF800109DF8011009060FD50728D8 +:1072D00003D006280AD00AE024E0042140460CF03E +:1072E000E5FD060000D1FFDFA088F0810226CDB9C0 +:1072F000FFDF17E0042140460CF0D8FD070000D165 +:10730000FFDF07F1140006F0C8FB90F0010F02D177 +:10731000E079000648D5387C022640F00200387437 +:1073200005B9FFDF224600E03DE02946404601F076 +:1073300071FC39E0042140460CF0B8FD017C002DC1 +:1073400001F00206C1F340016171017C21F00201EC +:107350000174E7D1FFDFE5E702260121404602F094 +:10736000A7F921E0042140460CF0A0FD0546606825 +:1073700000902089ADF8040001226946404602F0E1 +:10738000B8F9287C20F0020028740DE0002DC9D146 +:10739000FFDFC7E7022600214046FBF799F8002DE2 +:1073A000C0D1FFDFBEE7FFDF3046BDE8FC813EB560 +:1073B0000C0009D001466B4601AA002006F084FFAC +:1073C00020B1FFF784FC3EBD10203EBD0020208090 +:1073D000A0709DF8050002A900F00700FEF762FE0C +:1073E00050B99DF8080020709DF8050002A9C0F36F +:1073F000C200FEF757FE08B103203EBD9DF808000D +:1074000060709DF80500C109A07861F30410A070B8 +:107410009DF80510890961F3C300A0709DF8041060 +:10742000890601D5022100E0012161F342009DF8A7 +:10743000001061F30000A07000203EBD70B514463E +:1074400006460D4651EA040005D075B10846F9F725 +:1074500044FF78B901E0072070BD2946304606F0A8 +:107460009AFF10B1BDE8704031E454B12046F9F7FD +:1074700034FF08B1102070BD21463046BDE8704091 +:1074800095E7002070BD2DE9FC5F0C46904605464F +:10749000002701780822007A3E46B2EB111F7DD109 +:1074A00004F10A0100910A31821E4FF0020A04F130 +:1074B000080B0191092A72D2DFE802F0EDE005F530 +:1074C00028287BAACE00688804210CF0EFFC060077 +:1074D00000D1FFDFB08928B152270726C3E00000A2 +:1074E0009402002051271026002C7DD06888A080AF +:1074F0000120A071A88900220099FFF79FFF0028B2 +:1075000073D1A8892081288AE081D1E0B5F8129052 +:10751000072824D1E87B000621D5512709F1140062 +:1075200086B2002CE1D0A88900220099FFF786FFDF +:1075300000285AD16888A08084F806A0A8892081F4 +:107540000120A073288A2082A4F81290A88A0090B3 +:1075500068884B46A969019A01F038FBA8E05027DA +:1075600009F1120086B2002C3ED0A88900225946AB +:10757000FFF764FF002838D16888A080A889E080E0 +:10758000287A072813D002202073288AE081E87B1C +:10759000C0096073A4F81090A88A01E085E082E039 +:1075A000009068884B4604F11202A969D4E70120D3 +:1075B000EAE7B5F81290512709F1140086B2002CC1 +:1075C00066D0688804210CF071FC83466888A0802E +:1075D000A88900220099FFF731FF00286ED184F8B6 +:1075E00006A0A889208101E052E067E00420A07392 +:1075F000288A2082A4F81290A88A009068884B46B6 +:10760000A969019A01F0E2FAA989ABF80E104FE0DE +:107610006888FBF717FF0746688804210CF046FCD2 +:10762000064607B9FFDF06B9FFDF687BC00702D057 +:107630005127142601E0502712264CB36888A080F9 +:10764000502F06D084F806A0287B594601F0CEFAC8 +:107650002EE0287BA11DF9E7FE49A88949898142CE +:1076600005D1542706269CB16888A08020E05327C6 +:107670000BE06888A080A889E08019E06888042170 +:107680000CF014FC00B9FFDF55270826002CF0D1C0 +:10769000A8F8006011E056270726002CF8D068886B +:1076A000A080002013E0FFDF02E0012808D0FFDF08 +:1076B000A8F800600CB1278066800020BDE8FC9F20 +:1076C00057270726002CE3D06888A080687AA0712D +:1076D000EEE7401D20F0030009B14143091D01EB15 +:1076E0004000704713B5DB4A00201071009848B184 +:1076F000002468460CF0F7F9002C02D1D64A009914 +:1077000011601CBD01240020F4E770B50D4614463D +:10771000064686B05C220021284606F0B8FE04B971 +:10772000FFDFA0786874A2782188284601F089FAE2 +:107730000020A881E881228805F11401304605F077 +:10774000B0FA6A460121304606F069FC1AE000BF33 +:107750009DF80300000715D5BDF806103046FFF769 +:107760002DFD9DF80300BDF8061040F010008DF8C7 +:107770000300BDF80300ADF81400FF233046059A5E +:1077800006F0D1FD684606F056FC0028E0D006B0B1 +:1077900070BD10B50C4601F1140005F0BAFA0146AF +:1077A000627C2046BDE8104001F080BA30B5044646 +:1077B000A84891B04FF6FF75C18905AA284606F082 +:1077C0002EFC30E09DF81E00A0422AD001282AD1CC +:1077D000BDF81C00B0F5205F03D042F601018842DD +:1077E00021D1002002AB0AAA0CA9019083E807006E +:1077F00007200090BDF81A1010230022284606F03A +:10780000DEFC38B9BDF828000BAAC0B20CA90EF0F6 +:10781000F8F810B1032011B030BD9DF82E00A04241 +:1078200001D10020F7E705A806F005FC0028C9D023 +:107830000520F0E770B5054604210CF037FB040085 +:1078400000D1FFDF04F114010C46284605F045FA8B +:1078500021462846BDE8704005F046BA70B58AB0AA +:107860000C460646FBF7EEFD050014D028782228CA +:1078700027D30CB1A08890B101208DF80C00032013 +:107880008DF8100000208DF8110054B1A088ADF8DB +:107890001800206807E043F202000AB070BD09201A +:1078A000FBE7ADF818000590042130460CF0FEFA15 +:1078B000040000D1FFDF04F1140005F040FA0007D6 +:1078C00001D40820E9E701F091FE60B108A8022187 +:1078D0000094CDE9011095F8232003A93046636890 +:1078E000FFF7EEFBD9E71120D7E72DE9F04FB2F80B +:1078F00002A0834689B0154689465046FBF7A2FD93 +:107900000746042150460CF0D1FA0026044605969D +:107910004FF002080696ADF81C6007B9FFDF04B906 +:10792000FFDF4146504603F055FF50B907AA06A9AC +:1079300005A88DE807004246214650466368FFF7D8 +:107940004EFB444807AB0660DDE9051204F1140064 +:10795000CDF80090CDE90320CDE9013197F823203F +:10796000594650466B6805F033FA06000AD0022EDD +:1079700004D0032E14D0042E00D0FFDF09B030460F +:10798000BDE8F08FBDF81C000028F7D00599CDE9BF +:1079900000104246214650466368FFF74DFBEDE775 +:1079A000687840F008006870E8E710B50C46FFF70B +:1079B000BFF900280BD1607800F00701012905D13B +:1079C00010F0380F02D02078810601D5072010BDB5 +:1079D00040F0C8002070002010BD2DE9F04F99B094 +:1079E00004464FF000081B48ADF81C80ADF820801D +:1079F000ADF82480A0F80880ADF81480ADF81880A8 +:107A0000ADF82880ADF82C80007916460D46474623 +:107A1000012808D0022806D0032804D0042802D068 +:107A2000082019B0ACE72046F9F713FCF0BB284654 +:107A3000F9F70FFCD0BB6068F9F758FCB0BB606881 +:107A400068B160892189884202D8B1F5007F05D9E3 +:107A50000C20E6E7940200201800002080460EAAC1 +:107A600006A92846FFF7ACF90028DAD168688078C3 +:107A7000C0F34100022808D19DF8190010F0380F1A +:107A800003D02869F9F729FC80B905A92069FFF717 +:107A90004FF90028C5D1206950B1607880079DF862 +:107AA000150000F0380002D5F0B301E011E0D8BBBA +:107AB0009DF8140080060ED59DF8150010F0380FC3 +:107AC00003D06068F9F709FC18B96068F9F70EFC93 +:107AD00008B11020A5E70BA906A8FFF7C9F99DF882 +:107AE0002D000BA920F00700401C8DF82D006069C7 +:107AF000FFF75BFF002894D10AA9A069FFF718F9E6 +:107B000000288ED19DF8280080062BD4A06940B1B2 +:107B10009DF8290000F00701012923D110F0380F4A +:107B200020D0E06828B100E01CE00078D0B11C282B +:107B300018D20FAA611C2046FFF769F901213846C7 +:107B400061F30F2082468DF85210B94642F60300C9 +:107B50000F46ADF850000DF13F0218A928680DF04E +:107B600052FF08B107205CE79DF8600015A9CDF829 +:107B70000090C01CCDE9019100F0FF0B00230BF237 +:107B80000122514614A806F075F9E8BBBDF854006F +:107B90000C90FB482A8929690092CDE901106B8974 +:107BA000BDF838202868069906F064F9010077D1FD +:107BB00020784FF0020AC10601D4800616D58DF850 +:107BC000527042F60210ADF85000CDF80C9008A9A2 +:107BD00003AACDF800A0CDE90121002340F2032241 +:107BE00014A80B9906F046F9010059D1E4484D4616 +:107BF00008380089ADF83D000FA8CDE90290CDF816 +:107C00000490CDF8109000E00CE04FF007095B46BF +:107C10000022CDF80090BDF854104FF6FF7006F02A +:107C20006CF810B1FFF753F8FBE69DF83C00000636 +:107C300024D52946012060F30F218DF852704FF4AE +:107C400024500395ADF8500062789DF80C00002395 +:107C500062F300008DF80C006278CDF800A05208A5 +:107C600062F341008DF80C0003AACDE9012540F232 +:107C7000032214A806F0FEF8010011D1606880B359 +:107C80002069A0B905A906A8FFF7F2F86078800777 +:107C900007D49DF8150020F038008DF8150006E097 +:107CA00077E09DF8140040F040008DF814008DF846 +:107CB000527042F60110ADF85000208940F20121C7 +:107CC000B0FBF1F201FB1202606809ABCDF8008055 +:107CD000CDE90103002314A8059906F0CBF80100B3 +:107CE00057D12078C00728D00395A06950B90AA9B8 +:107CF00006A8FFF7BDF89DF8290020F00700401CFA +:107D00008DF829009DF8280007A940F040008DF863 +:107D100028008DF8527042F60310ADF8500003AA07 +:107D2000CDF800A0CDE90121002340F2032214A8E0 +:107D30000A9906F09FF801002BD1E06868B3294644 +:107D4000012060F30F218DF8527042F60410ADF857 +:107D50005000E068002302788DF8582040788DF8B4 +:107D60005900E06816AA4088ADF85A00E06800792A +:107D70008DF85C00E068C088ADF85D00CDF800903B +:107D8000CDE901254FF4027214A806F073F8010042 +:107D900003D00C9800F0B6FF43E679480321083879 +:107DA000017156B100893080BDF824007080BDF8A3 +:107DB0002000B080BDF81C00F080002031E670B5D6 +:107DC00001258AB016460B46012802D0022816D19A +:107DD00004E08DF80E504FF4205003E08DF80E5063 +:107DE00042F60100ADF80C005BB10024601C60F3AA +:107DF0000F2404AA08A918460DF005FE18B10720A3 +:107E00004BE5102049E504A99DF820205C48CDE908 +:107E10000021801E02900023214603A802F20122C5 +:107E200006F028F810B1FEF752FF36E5544808383E +:107E30000EB1C1883180057100202EE5F0B593B0F8 +:107E4000044601268DF83E6041F601000F46ADF86C +:107E50003C0011AA0FA93046FFF7B1FF002837D127 +:107E60002000474C4FF00005A4F1080432D01C223A +:107E7000002102A806F00BFB9DF808008DF83E607B +:107E800040F020008DF8080042F60520ADF83C00D7 +:107E900004200797ADF82C00ADF8300039480A905F +:107EA0000EA80D900E950FA80990ADF82E506A46B9 +:107EB00009A902A8FFF791FD002809D1BDF800002B +:107EC0006081BDF80400A081401CE0812571002084 +:107ED00013B0F0BD6581A581BDF84400F4E72DE93C +:107EE000F74F2749A0B00024083917940A79A14612 +:107EF000012A04D0022A02D0082023B040E5CA8813 +:107F0000824201D00620F8E721988A46824201D1B8 +:107F10000720F2E70120214660F30F21ADF8480069 +:107F20004FF6FF788DF86E000691ADF84A8042F664 +:107F3000020B8DF872401CA9ADF86CB0ADF8704022 +:107F40001391ADF8508012A806F0A4F800252E4633 +:107F50002F460DAB072212A9404606F09EF898B1B5 +:107F60000A2861D1B5B3AEB3ADF86450ADF8666020 +:107F70009DF85E008DF8144019AC012868D06FE0C0 +:107F80009C020020266102009DF83A001FB30128E0 +:107F900059D1BDF8381059451FD118A809A9019425 +:107FA0000294CDE9031007200090BDF8361010238D +:107FB0000022404606F003F9B0BBBDF8600004287B +:107FC00001D006284AD1BDF82410219881423AD127 +:107FD0000F2092E73AE0012835D1BDF83800B0F51E +:107FE000205F03D042F6010188422CD1BAF8060086 +:107FF000BDF83610884201D1012700E0002705B105 +:108000009EB1219881421ED118A809AA0194029418 +:10801000CDE90320072000900D46102300224046A2 +:1080200006F0CDF800B902E02DE04E460BE0BDF8B9 +:108030006000022801D0102810D1C0B217AA09A9E7 +:108040000DF0DFFC50B9BDF8369082E7052054E70B +:1080500005A917A8221D0DF0D6FC08B103204CE796 +:108060009DF814000023001DC2B28DF81420229840 +:108070000092CDE901401BA8069905F0FBFE10B95E +:1080800002228AF80420FEF722FE36E710B50B46DE +:10809000401E88B084B205AA00211846FEF7B7FE3C +:1080A00000200DF1080C06AA05A901908CE8070034 +:1080B000072000900123002221464FF6FF7005F0B3 +:1080C0001CFE0446BDF81800012800D0FFDF204642 +:1080D000FEF7FDFD08B010BDF0B5FF4F044687B0B8 +:1080E00038790E46032804D0042802D0082007B0AF +:1080F000F0BD04AA03A92046FEF762FE0500F6D1F2 +:1081000060688078C0F3410002280AD19DF80D0014 +:1081100010F0380F05D02069F9F7DFF808B110200A +:10812000E5E7208905AA21698DE807006389BDF884 +:1081300010202068039905F09DFE10B1FEF7C7FDE1 +:10814000D5E716B1BDF814003080042038712846F8 +:10815000CDE7F8B50C0006460BD001464FF6FF758B +:1081600000236A46284606F0AFF820B1FEF7AFFDBF +:10817000F8BD1020F8BD69462046FEF7D9FD00285D +:10818000F8D1A078314600F001032846009A06F0A5 +:10819000CAF8EBE730B587B0144600220DF1080CA1 +:1081A00005AD01928CE82C00072200920A46014698 +:1081B00023884FF6FF7005F0A0FDBDF81410218054 +:1081C000FEF785FD07B030BD70B50D4604210BF0FC +:1081D0006DFE040000D1FFDF294604F11400BDE864 +:1081E000704004F0A5BD70B50D4604210BF05EFE95 +:1081F000040000D1FFDF294604F11400BDE87040FF +:1082000004F0B9BD70B50D4604210BF04FFE04001B +:1082100000D1FFDF294604F11400BDE8704004F0EE +:10822000D1BD70B5054604210BF040FE040000D11D +:10823000FFDF214628462368BDE870400122FEF793 +:1082400015BF70B5064604210BF030FE040000D1C6 +:10825000FFDF04F1140004F05CFD401D20F0030575 +:1082600011E0011D00880022431821463046FEF728 +:10827000FDFE00280BD0607CABB2684382B2A068E0 +:10828000011D0BF0D0FCA06841880029E9D170BD28 +:1082900070B5054604210BF009FE040000D1FFDF94 +:1082A000214628466368BDE870400222FEF7DEBE24 +:1082B00070B50E46054601F099F9040000D1FFDFC4 +:1082C0000120207266726580207820F00F00001D6A +:1082D00020F0F00040302070BDE8704001F089B916 +:1082E00010B50446012900D0FFDF2046BDE810404C +:1082F0000121FAF7EDB82DE9F04F97B04FF0000AE1 +:108300000C008346ADF814A0D04619D0E06830B117 +:10831000A068A8B10188ADF81410A0F800A05846D4 +:10832000FBF790F8070043F2020961D03878222861 +:108330005CD3042158460BF0B9FD050005D103E0DC +:10834000102017B0BDE8F08FFFDF05F1140004F036 +:10835000E0FC401D20F00306A078012803D002288D +:1083600001D00720EDE7218807AA584605F057FEFF +:1083700030BB07A805F05FFE10BB07A805F05BFE49 +:1083800048B99DF82600012805D1BDF82400A0F5C4 +:108390002451023902D04FF45050D2E7E068B0B116 +:1083A000CDE902A00720009005AACDF804A0049210 +:1083B000A2882188BDF81430584605F09EFC10B103 +:1083C000FEF785FCBDE7A168BDF8140008809DF8A4 +:1083D0001F00C00602D543F20140B2E70B9838B146 +:1083E000A1780078012905D080071AD40820A8E7D1 +:1083F0004846A6E7C007F9D002208DF83C00A868DF +:108400004FF00009A0B1697C4288714391420FD9B5 +:108410008AB2B3B2011D0BF0BCFB8046A0F800A0ED +:1084200006E003208DF83C00D5F800804FF00109EC +:108430009DF8200010F0380F00D1FFDF9DF82000DC +:108440002649C0F3C200084497F8231010F8010C25 +:10845000884201D90F2074E72088ADF8400014A9A4 +:108460000095CDE90191434607220FA95846FEF732 +:1084700027FE002891D19DF8500050B9A07801281E +:1084800007D1687CB3B2704382B2A868011D0BF0BB +:1084900094FB002055E770B5064615460C46084685 +:1084A000FEF7D4FB002805D12A4621463046BDE818 +:1084B000704084E470BD12E570B51E4614460D0090 +:1084C0000ED06CB1616859B160B10349C98881426D +:1084D00008D0072070BD000094020020296102002E +:1084E0001020F7E72068FEF7B1FB0028F2D13246F2 +:1084F00021462846BDE87040FFF76FBA70B51546B3 +:108500000C0006D038B1FE490989814203D007200A +:10851000E0E71020DEE72068FEF798FB0028D9D1BD +:1085200029462046BDE87040D6E570B5064686B0BF +:108530000D4614461046F8F7B2FED0BB6068F8F757 +:10854000D5FEB0BBA6F57F40FF3803D03046FAF722 +:1085500079FF80B128466946FEF7ACFC00280CD1B3 +:108560009DF810100F2008293DD2DFE801F0080621 +:108570000606060A0A0843F2020006B0AAE703202C +:10858000FBE79DF80210012908D1BDF80010B1F5F4 +:10859000C05FF2D06FF4C052D142EED09DF8061009 +:1085A00001290DD1BDF80410A1F52851062907D2E3 +:1085B00000E029E0DFE801F0030304030303DCE744 +:1085C0009DF80A1001290FD1BDF80810B1F5245FFC +:1085D000D3D0A1F60211B1F50051CED00129CCD0F3 +:1085E000022901D1C9E7FFDF606878B9002305AA35 +:1085F0002946304605F068FE10B1FEF768FBBCE77F +:108600009DF81400800601D41020B6E76188224648 +:1086100028466368FFF7BEFDAFE72DE9F0438146CA +:1086200087B0884614461046F8F739FE18B1102076 +:1086300007B0BDE8F083002306AA4146484605F08E +:1086400043FE10B1FEF743FBF2E79DF81800C006A9 +:1086500002D543F20140EBE70025072705A8019565 +:1086600000970295CDE9035062884FF6FF734146AB +:10867000484605F0A4FD060013D16068F8F70FFE28 +:1086800060B960680195CDE9025000970495238890 +:1086900062884146484605F092FD0646BDF8140042 +:1086A00020803046CEE739B1954B0A889B899A42A3 +:1086B00002D843F2030070471DE610B586B0904C17 +:1086C0000423ADF81430638943B1A4898C4201D2EC +:1086D000914205D943F2030006B010BD0620FBE726 +:1086E000ADF81010002100910191ADF80030022189 +:1086F0008DF8021005A9029104A90391ADF812208A +:108700006946FFF7F8FDE7E72DE9FC4781460D468E +:108710000846F8F79EFD88BB4846FAF793FE5FEAE5 +:1087200000080AD098F80000222829D304214846DE +:108730000BF0BCFB070005D103E043F20200BDE8EB +:10874000FC87FFDF07F1140004F0F9FA06462878E9 +:10875000012803D0022804D00720F0E7B0070FD586 +:1087600002E016F01C0F0BD0A8792C1DC00709D011 +:10877000E08838B1A068F8F76CFD18B11020DEE78A +:108780000820DCE721882A780720B1F5847F35D0DE +:108790001EDC40F20315A1F20313A94226D00EDC21 +:1087A000B1F5807FCBD003DCF9B1012926D1C6E732 +:1087B000A1F58073013BC2D0012B1FD113E0012B27 +:1087C000BDD0022B1AD0032BB9D0042B16D112E046 +:1087D000A1F20912082A11D2DFE802F00B040410FA +:1087E00010101004ABE7022AA9D007E0012AA6D096 +:1087F00004E0320700E0F206002AA0DACDB200F071 +:10880000F5FE50B198F82300CDE90005FA8923461A +:1088100039464846FEF79FFC91E711208FE72DE986 +:10882000F04F8BB01F4615460C4683460026FAF7DC +:1088300009FE28B10078222805D208200BB081E576 +:1088400043F20200FAE7B80801D00720F6E7032F49 +:1088500000D100274FF6FF79CCB1022D71D320460D +:10886000F8F744FD30B904EB0508A8F10100F8F76A +:108870003DFD08B11020E1E7AD1E38F8028CAAB228 +:108880002146484605F081FE40455AD1ADB21C490B +:10889000B80702D58889401C00E001201FFA80F843 +:1088A000F80701D08F8900E04F4605AA4146584697 +:1088B00005F0B5FB4FF0070A4FF00009FCB1204668 +:1088C00008E0408810283CD8361D304486B2AE42BD +:1088D00037D2A01902884245F3D352E09DF8170021 +:1088E00002074ED57CB304EB0608361DB8F80230FB +:1088F000B6B2102B25D89A19AA4222D802E040E03D +:1089000094020020B8F8002091421AD1C0061BD56D +:10891000CDE900A90DF1080C0AAAA11948468CE876 +:108920000700B8F800100022584605F0E6F910B12B +:10893000FEF7CDF982E7B8F80200BDF828108842AA +:1089400002D00B207AE704E0B8F80200304486B287 +:1089500006E0C00604D55846FEF730FC00288AD150 +:108960009DF81700BDF81A1020F010008DF81700C0 +:10897000BDF81700ADF80000FF235846009A05F037 +:10898000D2FC05A805F057FB18B9BDF81A10B9427A +:10899000A4D9042158460BF089FA040000D1FFDF66 +:1089A000A2895AB1CDE900A94D4600232146584677 +:1089B000FEF7D1FB0028BDD1A5813FE700203DE7B0 +:1089C0002DE9FF4F8BB01E4617000D464FF00004F7 +:1089D00012D0B00802D007200FB0B3E4032E00D1AC +:1089E00000265DB10846F8F778FC28B93888691E7A +:1089F0000844F8F772FC08B11020EDE7C74AB00749 +:108A000001D5D18900E00121F0074FF6FF7802D0AF +:108A1000D089401E00E0404686B206AA0B9805F0B9 +:108A2000FEFA4FF000094FF0070B0DF1140A38E081 +:108A30009DF81B00000734D5CDF80490CDF800B0A8 +:108A4000CDF80890CDE9039A434600220B9805F033 +:108A5000B6FB60BB05B3BDF814103A882144281951 +:108A6000091D8A4230D3BDF81E2020F8022BBDF824 +:108A7000142020F8022BCDE900B9CDE90290CDF801 +:108A800010A0BDF81E10BDF8143000220B9805F0A0 +:108A900096FB08B103209FE7BDF814002044001D99 +:108AA00084B206A805F0C7FA20B10A2806D0FEF75E +:108AB0000EF991E7BDF81E10B142B9D934B17DB1BC +:108AC0003888A11C884203D20C2085E7052083E763 +:108AD00022462946404605F058FD014628190180E6 +:108AE000A41C3C80002077E710B50446F8F7D7FBBC +:108AF00008B1102010BD8948C0892080002010BD19 +:108B0000F0B58BB00D4606461422002103A805F0EF +:108B1000BEFC01208DF80C008DF8100000208DF8AF +:108B20001100ADF814503046FAF78CFC48B10078CB +:108B3000222812D3042130460BF0B8F9040005D1E5 +:108B400003E043F202000BB0F0BDFFDF04F11400BC +:108B5000074604F0F4F8800601D40820F3E7207CEF +:108B6000022140F00100207409A80094CDE9011011 +:108B7000072203A930466368FEF7A2FA20B1217CE0 +:108B800021F001012174DEE729463046F9F791FC16 +:108B900008A9384604F0C2F800B1FFDFBDF8204054 +:108BA000172C01D2172000E02046A84201D92C46FC +:108BB00002E0172C00D2172421463046FFF713FBA2 +:108BC00021463046F9F799F90020BCE7F8B51C4674 +:108BD00015460E46069F0BF09AFA2346FF1DBCB2BF +:108BE00031462A4600940AF086FEF8BD70B50C4660 +:108BF00005460E220021204605F049FC0020208079 +:108C00002DB1012D01D0FFDF64E4062000E0052036 +:108C1000A0715FE410B548800878134620F00F007B +:108C2000001D20F0F00080300C4608701422194618 +:108C300004F1080005F001FC00F0DBFC374804609B +:108C400010BD2DE9F047DFF8D890491D064621F008 +:108C5000030117460C46D9F800000AF062FF050030 +:108C600000D1FFDF4FF000083560A5F800802146F5 +:108C7000D9F800000AF055FF050000D1FFDF75604C +:108C8000A5F800807FB104FB07F1091D0BD0D9F8CE +:108C900000000AF046FF040000D1FFDFB460C4F812 +:108CA0000080BDE8F087C6F80880FAE72DE9F041BA +:108CB0001746491D21F00302194D06460168144666 +:108CC00028680AF059FF2246716828680AF054FFA4 +:108CD0003FB104FB07F2121D03D0B16828680AF007 +:108CE0004BFF04200BF08AF8044604200BF08EF8AA +:108CF000201A012804D12868BDE8F0410AF006BF17 +:108D0000BDE8F08110B50C4605F058F900B1FFDF61 +:108D10002046BDE81040FDF7DABF000094020020B5 +:108D20001800002038B50C468288817B19B1418932 +:108D3000914200D90A462280C188121D90B26A462B +:108D40000AF0B2F8BDF80000032800D30320C1B236 +:108D5000208801F020F838BD38B50C468288817B28 +:108D600019B10189914200D90A462280C188121D99 +:108D700090B26A460AF098F8BDF80000022800D3C5 +:108D80000220C1B2208801F006F8401CC0B238BDF4 +:108D90002DE9FF5F82468B46F74814460BF103022C +:108DA000D0E90110CDE9021022F0030201A84FF42E +:108DB000907101920AF097FEF04E002C02D1F0491A +:108DC000019A8A60019901440191B57F05F101057D +:108DD00004D1E8B20CF098FD00B1FFDF019800EB80 +:108DE0000510C01C20F0030101915CB9707AB27AC1 +:108DF0001044C2B200200870B08C80B204F03DFF75 +:108E000000B1FFDF0198716A08440190214601A872 +:108E100000F084FF80460198C01C20F00300019000 +:108E2000B37AF27A717A04B100200AF052FF019904 +:108E300008440190214601A800F0B8FFCF48002760 +:108E40003D4690F801900CE0284600F04AFF0646A7 +:108E500081788088F9F7E8F871786D1C00FB01775C +:108E6000EDB24D45F0D10198C01C20F003000190F7 +:108E700004B100203946F9F7E2F8019900270844C7 +:108E80000190BE483D4690F801900CE0284600F065 +:108E900028FF0646C1788088FEF71BFC71786D1CA0 +:108EA00000FB0177EDB24D45F0D10198C01C20F0D8 +:108EB0000300019004B100203946FEF713FC01992C +:108EC0004FF0000908440190AC484D4647780EE049 +:108ED000284600F006FF0646807B30B106F1080008 +:108EE00002F09CF9727800FB02996D1CEDB2BD4254 +:108EF000EED10198C01C20F00300019004B10020C5 +:108F00009F494A78494602F08DF901990844019039 +:108F1000214601A800F0B8FE0198C01D20F007000E +:108F20000190DAF80010814204D3A0EB0B01B1F5F7 +:108F3000803F04DB4FF00408CAF8000004E0CAF8E0 +:108F40000000B8F1000F03D0404604B0BDE8F09F28 +:108F500084BB8C490020019A0EF044FEFBF714FA02 +:108F6000864C207F0090607F012825D0002328B305 +:108F70000022824800211030F8F73AFA00B1FFDFF2 +:108F80007E49E07F2031FEF759FF00B1FFDF7B48CB +:108F90004FF4F6720021443005F079FA7748042145 +:108FA000443080F8E91180F8EA11062180F8EB11CD +:108FB000032101710020C8E70123D8E702AAD8E7FE +:108FC00070B56E4C06464434207804EB4015E078CA +:108FD000083598B9A01990F8E80100280FD0A078BA +:108FE0000F2800D3FFDF20220021284605F04FFA8A +:108FF000687866F3020068700120E070284670BD52 +:109000002DE9F04105460C460027007805219046E1 +:109010003E46B1EB101F00D0FFDF287A50B1012887 +:109020000ED0FFDFA8F800600CB12780668000201A +:10903000BDE8F0810127092674B16888A08008E0A6 +:109040000227142644B16888A0802869E060A88AB5 +:109050002082287B2072E5E7A8F80060E7E730B5BA +:10906000464C012000212070617020726072032242 +:10907000A272E07261772177217321740521218327 +:109080001F216183607440A161610A21A177E077AB +:1090900039483B4DB0F801102184C07884F8220093 +:1090A0004FF4B06060626868C11C21F00301814226 +:1090B00000D0FFDF6868606030BD30B5304C1568A7 +:1090C000636810339D4202D20420136030BD2B4BE5 +:1090D0005D785A6802EB0512107051700320D08041 +:1090E000172090800120D0709070002090735878E5 +:1090F000401C5870606810306060002030BD70B552 +:1091000006461E480024457807E0204600F0E9FDA9 +:109110000178B14204D0641CE4B2AC42F5D1002025 +:1091200070BDF7B5074608780C4610B3FFF7E7FFA8 +:109130000546A7F12006202F06D0052E19D2DFE81C +:1091400006F00F383815270000F0D6FD0DB169780C +:1091500000E00021401AA17880B20844FF2808D816 +:10916000A07830B1A088022831D202E060881728A8 +:109170002DD20720FEBD000030610200B0030020A8 +:109180001C000020000000206E52463578000000D0 +:10919000207AE0B161881729EBD3A1881729E8D399 +:1091A000A1790029E5D0E1790029E2D0402804D94D +:1091B000DFE7242F0BD1207A48B161884FF6FB708E +:1091C000814202D8A188814201D90420D2E765B941 +:1091D000207802AA0121FFF770FF0028CAD1207869 +:1091E000FFF78DFF050000D1FFDF052E18D2DFE865 +:1091F00006F0030B0E081100A0786870A088E880C4 +:109200000FE06088A8800CE0A078A87009E0A07842 +:10921000E87006E054F8020FA8606068E86000E0BB +:10922000FFDF0020A6E71A2835D00DDC132832D244 +:10923000DFE800F01B31203131272723252D313184 +:1092400029313131312F0F00302802D003DC1E28A4 +:1092500021D1072070473A3809281CD2DFE800F0F6 +:10926000151B0F1B1B1B1B1B07000020704743F225 +:109270000400704743F202007047042070470D203D +:1092800070470F2070470820704711207047132047 +:109290007047062070470320704710B5007800F033 +:1092A000010009F0F3FDBDE81040BCE710B50078FF +:1092B00000F0010009F0F3FDBDE81040B3E70EB582 +:1092C000017801F001018DF80010417801F00101F1 +:1092D0008DF801100178C1F340018DF8021041783A +:1092E000C1F340018DF80310017889088DF804104E +:1092F000417889088DF8051081788DF80610C178BD +:109300008DF8071000798DF80800684608F0FDFD1B +:10931000FFF789FF0EBD2DE9FC5FDFF8F883FE4CF7 +:1093200000264FF490771FE0012000F082FD01201D +:10933000FFF746FE05463946D8F808000AF0F1FB6B +:10934000686000B9FFDF686808F0AAFCB0B1284681 +:10935000FAF75EFB284600F072FD28B93A466968C4 +:10936000D8F808000AF008FC94F9E9010428DBDACF +:1093700002200AF043FD07460025AAE03A46696844 +:10938000D8F808000AF0F8FBF2E7B8F802104046F7 +:10939000491C89B2A8F80210B94201D300214180CA +:1093A0000221B8F802000AF081FD002866D0B8F862 +:1093B0000200694609F0CFFCFFF735FF00B1FFDF7F +:1093C0009DF80000019078B1B8F802000AF0B1FEF3 +:1093D0005FEA000900D1FFDF48460AF020F918B122 +:1093E000B8F8020002F0E4F9B8F802000AF08FFEC3 +:1093F0005FEA000900D1FFDF48460AF008F9E8BB40 +:109400000321B8F802000AF051FD5FEA000B4BD1CE +:10941000FFDF49E0DBF8100010B10078FF284DD0E5 +:10942000022000F006FD0220FFF7CAFD82464846F2 +:109430000AF0F9F9CAF8040000B9FFDFDAF804000D +:109440000AF0C1FA002100900170B8F802105046ED +:10945000AAF8021002F0B2F848460AF0B6FA00B9CB +:10946000FFDF019800B10126504600F0E8FC18B972 +:109470009AF80100000705D5009800E027E0CBF836 +:10948000100011E0DBF8101039B10878401C10F022 +:10949000FF00087008D1FFDF06E0002211464846B1 +:1094A00000F0F0FB00B9FFDF94F9EA01022805DBC8 +:1094B000B8F8020002F049F80028ABD194F9E901AC +:1094C000042804DB48460AF0E4FA00B101266D1CCA +:1094D000EDB2BD4204D294F9EA010228BFF655AFBD +:1094E000002E7FF41DAFBDE8FC5F032000F0A1BC9F +:1094F00010B5884CE06008682061AFF2E510F9F71C +:10950000E4FC607010BD844800214438017081483B +:10951000017082494160704770B505464FF0805038 +:109520000C46D0F8A410491C05D1D0F8A810C943A6 +:109530000904090C0BD050F8A01F01F0010129709B +:10954000416821608068A080287830B970BD06210C +:1095500020460DF0CCFF01202870607940F0C0005B +:10956000607170BD70B54FF080540D46D4F8801016 +:10957000491C0BD1D4F88410491C07D1D4F88810A9 +:10958000491C03D1D4F88C10491C0CD0D4F880109D +:109590000160D4F884104160D4F888108160D4F858 +:1095A0008C10C16002E010210DF0A1FFD4F89000F2 +:1095B000401C0BD1D4F89400401C07D1D4F898007B +:1095C000401C03D1D4F89C00401C09D054F8900FE3 +:1095D000286060686860A068A860E068E86070BDA6 +:1095E0002846BDE8704010210DF081BF4A4800793F +:1095F000E6E470B5484CE07830B3207804EB4010D6 +:10960000407A00F00700204490F9E801002800DCCF +:10961000FFDF2078002504EB4010407A00F00700BF +:10962000011991F8E801401E81F8E8012078401CFA +:10963000C0B220700F2800D12570A078401CA07007 +:109640000DF0D4FDE57070BDFFDF70BD3EB5054681 +:1096500003210AF02BFC044628460AF058FD054673 +:1096600004B9FFDF206918B10078FF2800D1FFDFBF +:1096700001AA6946284600F005FB60B9FFDF0AE051 +:10968000002202A9284600F0FDFA00B9FFDF9DF88C +:10969000080000B1FFDF9DF80000411E8DF80010AA +:1096A000EED220690199884201D1002020613EBD9F +:1096B00070B50546A0F57F400C46FF3800D1FFDFAE +:1096C000012C01D0FFDF70BDFFF790FF040000D137 +:1096D000FFDF207820F00F00401D20F0F000503018 +:1096E000207065800020207201202073BDE870404A +:1096F0007FE72DE9F04116460D460746FFF776FF56 +:10970000040000D1FFDF207820F00F00401D20F082 +:10971000F00005E01C000020F403002048140020A5 +:109720005030207067800120207228682061A8884E +:10973000A0822673BDE8F0415BE77FB5FFF7DFFC51 +:10974000040000D1FFDF02A92046FFF7EBFA05462F +:1097500003A92046FFF700FB8DF800508DF80100AB +:10976000BDF80800001DADF80200BDF80C00001D9A +:10977000ADF80400E088ADF80600684609F070FB1B +:10978000002800D0FFDF7FBD2DE9F05FFC4E814651 +:10979000307810B10820BDE8F09F4846F7F77FFD0C +:1097A00008B11020F7E7F74C207808B9FFF757FC0D +:1097B000A17A607A4D460844C4B200F09DFAA042F6 +:1097C00007D2201AC1B22A460020FFF776FC0028F3 +:1097D000E1D17168EB48C91C002721F003017160D9 +:1097E000B3463E463D46BA463C4690F801800AE004 +:1097F000204600F076FA4178807B0E4410FB01553C +:10980000641CE4B27F1C4445F2D10AEB870000EBF4 +:10981000C600DC4E00EB85005C46F17A012200EBCD +:109820008100DBF80410451829464846FFF7B0FAD6 +:10983000070012D00020FFF762FC05000BD005F1F5 +:109840001300616820F00300884200D0FFDF7078C9 +:10985000401E7070656038469DE7002229464846E4 +:10986000FFF796FA00B1FFDFD9F8000060604FF60D +:10987000FF7060800120207000208CE72DE9F0410E +:109880000446BF4817460D46007810B10820BDE8D1 +:10989000F0810846F7F7DDFC08B11020F7E7B94E74 +:1098A000307808B9FFF7DBFB601E1E2807D8012CB3 +:1098B00023D12878FE2820D8B0770020E7E7A4F14C +:1098C00020001F2805D8E0B23A462946BDE8F041FD +:1098D00027E4A4F140001F2805D829462046BDE80A +:1098E000F04100F0D4BAA4F1A0001F2805D8294601 +:1098F0002046BDE8F04100F006BB0720C7E72DE990 +:10990000F05F81460F460846F7F7C9FC48B948465C +:10991000F7F7E3FC28B909F1030020F003014945FA +:1099200001D0102037E797484FF0000B4430817882 +:1099300069B14178804600EB411408343E883A46CC +:109940000021204600F089FA050004D027E0A7F89E +:1099500000B005201FE7B9F1000F24D03888B042CD +:1099600001D90C251FE0607800F00700824600F066 +:1099700060FA08EB0A063A4696F8E8014946401CA8 +:1099800086F8E801204600F068FA054696F8E801F6 +:10999000401E86F8E801032000F04BFA2DB10C2D93 +:1099A00001D0A7F800B02846F5E6754F5046BAF149 +:1099B000010F25D002280DD0BAF1030F35D0FFDFFB +:1099C00098F801104046491CC9B288F801100F29C7 +:1099D00037D038E0606828B16078000702D460882A +:1099E000FFF734FE98F8EA014446012802D178785E +:1099F000F9F78AFA94F9EA010428E1DBFFDFDFE7EF +:109A0000616821B14FF49072B8680AF0B5F898F81F +:109A1000E9014446032802D17878F9F775FA94F9F8 +:109A2000E9010428CCDBFFDFCAE76078C00602D575 +:109A30006088FFF70BFE98F9EB010628C0DBFFDF1B +:109A4000BEE780F801B08178491E88F8021096F8C8 +:109A5000E801401C86F8E801A5E770B50C4605460C +:109A6000F7F7F7FB18B92046F7F719FC08B11020F3 +:109A700070BD28460BF07FFF207008B1002070BD3C +:109A8000042070BD70B505460BF08EFFC4B22846A9 +:109A9000F7F723FC08B1102070BD35B128782C7081 +:109AA00018B1A04201D0072070BD2046FDF77EFE10 +:109AB000052805D10BF07BFF012801D0002070BDE7 +:109AC0000F2070BD70B5044615460E460846F7F7E0 +:109AD000C0FB18B92846F7F7E2FB08B1102070BDAB +:109AE000022C03D0102C01D0092070BD2A4631462B +:109AF00020460BF086FF0028F7D0052070BD70B51A +:109B000014460D460646F7F7A4FB38B92846F7F782 +:109B1000C6FB18B92046F7F7E0FB08B1102070BD6E +:109B20002246294630460BF06EFF0028F7D007206A +:109B300070BD3EB50446F7F7B2FB08B110203EBD3C +:109B4000684608F053F9FFF76EFB0028F7D19DF83F +:109B500006002070BDF808006080BDF80A00A080F3 +:109B600000203EBD70B505460C460846F7F7B5FB2C +:109B700020B95CB12068F7F792FB28B1102070BDC6 +:109B80001C000020B0030020A08828B121462846F0 +:109B9000BDE87040FDF762BE0920F0E770B50546EC +:109BA0000C460846F7F755FBA0BB681E1E280ED8CA +:109BB000032D01D90720E2E705B9FFDFFE4800EBDE +:109BC000850050F8041C2046BDE870400847A5F108 +:109BD00020001F2805D821462846BDE87040FAF726 +:109BE00042BBA5F160001F2805D821462846BDE8E4 +:109BF0007040F8F7DABCF02D0DD0F12D15D0BF2D47 +:109C0000D8D1A078218800F0010001F08DFB98B137 +:109C10000020B4E703E0A068F7F71BFB08B11020B1 +:109C2000ADE7204609F081F902E0207809F0A0F9BB +:109C3000BDE87040FFF7F7BA0820A0E770B504460A +:109C40000D460846F7F72BFB30B9601E1E280FD8CB +:109C50002846F7F7FEFA08B1102090E7012C03D050 +:109C6000022C01D0032C01D1062088E7072086E7CB +:109C7000A4F120001F28F9D829462046BDE87040ED +:109C8000FAF762BB09F092BC38B50446CB48007BBA +:109C900000F00105F9B904F01DFC0DB1226800E0E7 +:109CA0000022C7484178C06807F06DFDC4481030F5 +:109CB000C0788DF8000010B1012802D004E0012026 +:109CC00000E000208DF80000684608F0FFF8BA4870 +:109CD000243808F0B5FE002D02D02068283020601E +:109CE00038BD30B5B54D04466878A04200D8FFDFD6 +:109CF000686800EB041030BD70B5B04800252C46F4 +:109D0000467807E02046FFF7ECFF4078641C2844C3 +:109D1000C5B2E4B2B442F5D1284630E72DE9F041AE +:109D20000C4607464FF0000800F01FF90646FF28D2 +:109D300001D94FF013083868C01C20F003023A60C4 +:109D400054EA080421D19D48F3B2072128300DF0D0 +:109D5000DBFD09E0072C10D2DFE804F00604080858 +:109D60000A040600974804E0974802E0974800E09C +:109D700097480DF0E9FD054600E0FFDFA54200D061 +:109D8000FFDF641CE4B2072CE4D3386800EB061054 +:109D9000386040467BE5021D5143452900D24521EC +:109DA0000844C01CB0FBF2F0C0B270472DE9FC5F64 +:109DB000064682484FF000088B464746444690F8D6 +:109DC000019022E02046FFF78CFF050000D1FFDF65 +:109DD000687869463844C7B22846FEF7A3FF824632 +:109DE00001A92846FEF7B8FF0346BDF80400524615 +:109DF000001D81B2BDF80000001D80B20AF0D4F849 +:109E00006A78641C00FB0288E4B24C45DAD1306801 +:109E1000C01C20F003003060BBF1000F00D0002018 +:109E2000424639460AF0CEF8316808443060BDE851 +:109E3000FC9F6249443108710020C87070475F4937 +:109E40004431CA782AB10A7801EB421108318142C3 +:109E500001D001207047002070472DE9F0410646EF +:109E60000078154600F00F0400201080601E0F4699 +:109E7000052800D3FFDF50482A46183800EB84003D +:109E8000394650F8043C3046BDE8F04118472DE90A +:109E9000F0414A4E0C46402806D0412823D04228A3 +:109EA0002BD0432806D123E0A07861780D18E17803 +:109EB000814201D90720EAE42078012801D9132042 +:109EC000E5E4FF2D08D80BF009FF07460DF046F931 +:109ED000381A801EA84201DA1220D8E42068B06047 +:109EE000207930730DE0BDE8F041084600F078B805 +:109EF00008780228DED8307703E008780228D9D81D +:109F000070770020C3E4F8B500242C4DA02805D0BC +:109F1000A12815D0A22806D00720F8BD087800F0A7 +:109F20000100E8771FE00E4669463046FDF73DFD2B +:109F30000028F2D130882884B07885F8220012E019 +:109F400008680921F82801D3820701D00846F8BD26 +:109F50006A7C02F00302012A04D16A8BD73293B2E1 +:109F60008342F3D868622046F8BD2DE9F047DFF858 +:109F70004C900026344699F8090099F80A2099F87F +:109F800001700244D5B299F80B20104400F0FF088C +:109F900008E02046FFF7A5FE817B407811FB0066B4 +:109FA000641CE4B2BC42F4D199F8091099F80A0093 +:109FB0002944294441440DE054610200B0030020CB +:109FC0001C0000206741000045B30000DD2F0000A9 +:109FD000FB56010000B1012008443044BDE8F08781 +:109FE00038B50446407800F00300012803D0022869 +:109FF0000BD0072038BD606858B1F7F777F9D0B9B2 +:10A000006068F7F76AF920B915E06068F7F721F999 +:10A0100088B969462046FCF729F80028EAD160781B +:10A0200000F00300022808D19DF8000028B1606804 +:10A03000F7F753F908B1102038BD6189F8290DD818 +:10A04000208988420AD8607800F003020A48012A71 +:10A0500006D1D731426A89B28A4201D2092038BD7D +:10A0600094E80E0000F1100585E80E000AB9002101 +:10A070000183002038BD0000B00300202DE9F0412D +:10A08000074614468846084601F08AFD064608EB56 +:10A0900088001C22796802EBC0000D18688C58B14A +:10A0A0004146384601F08BFD014678680078C200D1 +:10A0B000082305F120000CE0E88CA8B141463846A1 +:10A0C00001F084FD0146786808234078C20005F15C +:10A0D000240009F0A8FD38B1062121726681D0E97B +:10A0E0000010C4E9031009E0287809280BD00520E6 +:10A0F000207266816868E060002028702046BDE814 +:10A10000F04101F02EBD072020726681F4E72DE9B1 +:10A11000F04116460D460746406801EB85011C22BA +:10A1200002EBC1014418204601F072FD40B100214C +:10A13000708865F30F2160F31F4106200DF0BEFC0F +:10A1400009202070324629463846BDE8F04195E79F +:10A150002DE9F0410E46074600241C21F07816E058 +:10A1600004EB8403726801EBC303D25C6AB1FFF7AE +:10A170003DFA050000D1FFDF6F802A4621463046B8 +:10A18000FFF7C5FF0120BDE8F081641CE4B2A042E6 +:10A19000E6D80020F7E770B5064600241C21C078F9 +:10A1A0000AE000BF04EB8403726801EBC303D51817 +:10A1B0002A782AB1641CE4B2A042F3D8402070BDD2 +:10A1C00028220021284604F062F9706880892881DD +:10A1D000204670BD70B5034600201C25DC780CE0DD +:10A1E00000EB80065A6805EBC6063244167816B1B5 +:10A1F000128A8A4204D0401CC0B28442F0D8402067 +:10A2000070BDF0B5044600201C26E5780EE000BFC6 +:10A2100000EB8007636806EBC7073B441F788F425B +:10A2200002D15B78934204D0401CC0B28542EFD883 +:10A230004020F0BD0078032801D0002070470120A5 +:10A2400070470078022801D0002070470120704735 +:10A250000078072801D000207047012070472DE9C1 +:10A26000F041064688461078F1781546884200D3BA +:10A27000FFDF2C781C27641CF078E4B2A04201D8E0 +:10A28000201AC4B204EB8401706807EBC1010844D2 +:10A29000017821B14146884708B12C7073E72878CE +:10A2A000A042E8D1402028706DE770B514460B88B5 +:10A2B0000122A240134207D113430B8001230A223B +:10A2C000011D09F07AFC047070BD2DE9FF4F81B0CB +:10A2D0000878DDE90E7B9A4691460E4640072CD45D +:10A2E000019809F026FF040000D1FFDF07F1040800 +:10A2F00020461FFA88F109F065F8050000D1FFDF5C +:10A30000204629466A4609F0B0FA0098A0F8037082 +:10A31000A0F805A0284609F056FB017869F306016C +:10A320006BF3C711017020461FFA88F109F08DF810 +:10A3300000B9FFDF019807F094F906EB0900017FEF +:10A34000491C017705B0BDE8F08F2DE9F84F0E46A6 +:10A350009A4691460746032109F0A8FD0446008D60 +:10A36000DFF8B885002518B198F80000B0421ED17A +:10A37000384609F0DEFE070000D1FFDF09F10401D5 +:10A38000384689B209F01EF8050010D03846294633 +:10A390006A4609F06AFA009800210A460180817035 +:10A3A00007F01CFA0098C01DCAF8000021E098F8D8 +:10A3B0000000B04216D104F1260734F8341F012002 +:10A3C00000FA06F911EA090F00D0FFDF2088012307 +:10A3D00040EA090020800A22391D384609F008FCAD +:10A3E000067006E0324604F1340104F12600FFF75E +:10A3F0005CFF0A2188F800102846BDE8F88FFEB5FA +:10A4000015460C46064602AB0C220621FFF79DFFBF +:10A41000002827D00299607812220A70801C4870A8 +:10A4200008224A80A07002982988052381806988C3 +:10A43000C180A9880181E988418100250C20CDE9EE +:10A440000005062221463046FFF73FFF294600223D +:10A4500066F31F41F02310460DF086FA6078801CE9 +:10A4600060700120FEBDFEB514460D46062206466C +:10A4700002AB1146FFF769FF002812D0029B1320A0 +:10A4800000211870A8785870022058809C800620FF +:10A49000CDE900010246052329463046FFF715FFA6 +:10A4A0000120FEBD2DE9FE430C46804644E002AB90 +:10A4B0000E2207214046FFF748FF002841D0606880 +:10A4C0001C2267788678BF1C06EB860102EBC1016F +:10A4D000451802981421017047700A214180698A49 +:10A4E0000181E98A4181A9888180A98981813046D9 +:10A4F00001F056FB029905230722C8806F700420E3 +:10A50000287000250E20CDE9000521464046FFF7C2 +:10A51000DCFE294666F30F2168F31F41F023002279 +:10A5200006200DF021FA6078FD49801C6070626899 +:10A530002046921CFFF793FE606880784028B6D1D1 +:10A540000120BDE8FE83FEB50D46064638E002ABAD +:10A550000E2207213046FFF7F8FE002835D0686844 +:10A560001C23C17801EB810203EBC202841802981C +:10A5700015220270627842700A224280A2894281CA +:10A58000A2888281084601F00BFB01460298818077 +:10A59000618AC180E18A0181A088B8B10020207061 +:10A5A00000210E20CDE9000105230722294630466F +:10A5B000FFF78BFE6A68DB492846D21CFFF74FFE87 +:10A5C0006868C0784028C2D10120FEBD0620E6E7B9 +:10A5D0002DE9FE430C46814644E0204601F002FB93 +:10A5E000D0B302AB082207214846FFF7AEFE002891 +:10A5F000A7D060681C2265780679AD1C06EB860141 +:10A6000002EBC10147180298B7F8108006210170CB +:10A61000457004214180304601F0C2FA014602989B +:10A6200005230722C180A0F804807D7008203870BF +:10A630000025CDE9000521464846FFF746FE29469C +:10A6400066F30F2169F31F41F023002206200DF06D +:10A650008BF96078801C60706268B3492046121DD7 +:10A66000FFF7FDFD606801794029B6D1012068E758 +:10A670002DE9F34F83B00D4691E0284601F0B2FA80 +:10A6800000287DD068681C2290F806A00AEB8A0199 +:10A6900002EBC10144185146284601F097FAA1780F +:10A6A000CB0069684978CA00014604F1240009F02A +:10A6B000D6FA07468188E08B4FF00009091A8EB25E +:10A6C00008B1C84607E04FF00108504601F053FAC0 +:10A6D00008B9B61CB6B2208BB04200D80646B346C5 +:10A6E00002AB324607210398FFF72FFE060007D082 +:10A6F000B8F1000F0BD0504601F03DFA10B106E062 +:10A7000000201FE60299B8884FF0020908800196E0 +:10A71000E28B3968ABEB09001FFA80F80A44039812 +:10A720004E46009209F005FDDDE90021F61D434685 +:10A73000009609F014F9E08B404480B2E083B988B8 +:10A74000884201D1012600E00026CDE900B6238A27 +:10A75000072229460398FFF7B8FD504601F00BFA8F +:10A7600010B9E089401EE08156B1A078401CA0706D +:10A770006868E978427811FB02F1CAB2012300E06F +:10A7800007E081690E3009F018FA80F800A0002077 +:10A79000E0836A6865492846921DFFF760FD686896 +:10A7A000817940297FF469AF0120CBE570B5064679 +:10A7B00048680D4614468179402910D104EB840184 +:10A7C0001C2202EBC101084401F043FA002806D024 +:10A7D0006868294684713046BDE8704048E770BD1E +:10A7E000FEB50C460746002645E0204601F0FAF982 +:10A7F000D8B360681C22417901EB810102EBC101F1 +:10A800004518688900B9FFDF02AB082207213846E6 +:10A81000FFF79BFD002833D00299607816220A705A +:10A82000801C4870042048806068407901F0B8F9C5 +:10A83000014602980523072281806989C18008208A +:10A84000CDE9000621463846FFF73FFD6078801CC1 +:10A850006070A88969890844B0F5803F00D3FFDFA4 +:10A86000A88969890844A8816E81626830492046B8 +:10A87000521DFFF7F4FC606841794029B5D10120F1 +:10A88000FEBD30B5438C458BC3F3C704002345B1EF +:10A89000838B641EED1AC38A6D1E1D4495FBF3F372 +:10A8A000E4B22CB1008918B1A04200D8204603447C +:10A8B0004FF6FF70834200D3034613800C7030BD07 +:10A8C0002DE9FC41074616460D46486802EB860115 +:10A8D0001C2202EBC10144186A4601A92046FFF779 +:10A8E000D0FFA089618901448AB2BDF8001091426D +:10A8F00012D0081A00D5002060816868407940288D +:10A900000AD1204601F09BF9002805D06868294645 +:10A9100046713846FFF764FFBDE8FC813000002037 +:10A9200035A2000043A2000051A2000053BC000069 +:10A930003FBC00002DE9FE4F0F468146154650886A +:10A94000032109F0B3FA0190B9F8020001F01BF9F4 +:10A9500082460146019801F045F9002824D001986B +:10A960001C2241680AEB8A0002EBC0000C1820464A +:10A9700001F04EF9002817D1B9F80000E18A8842A9 +:10A980000ED8A18961B1B8420ED100265146019876 +:10A9900001F015F9218C01EB0008608B30B114E057 +:10A9A000504601F0E8F8A0B3BDE8FE8F504601F034 +:10A9B000E2F808B1678308E0022FF5D3B9F8040084 +:10A9C0006083618A884224D80226B81B87B2B8F80F +:10A9D0000400A28B801A002814DD874200DA384672 +:10A9E0001FFA80FB688869680291D8F800100A4451 +:10A9F000009209F08CFBF61D009A5B4602990096C6 +:10AA000008F079FFA08B384480B2A083618B884224 +:10AA100007D96888019903B05246BDE8F04F01F0AC +:10AA200035B91FD14FF009002872B9F802006881CA +:10AA3000D8E90010C5E90410608BA881284601F010 +:10AA400090F85146019801F0BAF8014601980823A0 +:10AA500040680078C20004F1200009F0E4F800200A +:10AA6000A0836083504601F086F810B9A089401E8B +:10AA7000A0816888019903B00AF0FF02BDE8F04F99 +:10AA80001EE72DE9F041064615460F461C461846BE +:10AA9000F6F7DFFB18B92068F6F701FC10B11020BB +:10AAA000BDE8F0817168688C0978B0EBC10F01D303 +:10AAB0001320F5E73946304601F081F80146706809 +:10AAC00008230078C20005F1200009F076F8D4E9E7 +:10AAD0000012C0E900120020E2E710B5044603218D +:10AAE00009F0E4F90146007800F00300022805D0DF +:10AAF0002046BDE8104001F1140280E48A8A204615 +:10AB0000BDE81040AFE470B50446032109F0CEF96A +:10AB1000054601462046FFF75BFD002816D0294672 +:10AB20002046FFF75DFE002810D029462046FFF79B +:10AB30000AFD00280AD029462046FFF7B3FC00286A +:10AB400004D029462046BDE8704091E570BD2DE94E +:10AB5000F0410C4680461EE0E178427811FB02F19C +:10AB6000CAB2816901230E3009F05DF80778606888 +:10AB70001C22C179491EC17107EB8701606802EB95 +:10AB8000C10146183946204601F02CF818B130466C +:10AB900001F037F820B16068C1790029DCD17FE786 +:10ABA000FEF724FD050000D1FFDF0A202872384699 +:10ABB00000F0F6FF68813946204601F007F80146AB +:10ABC000606808234078C20006F1240009F02BF8E1 +:10ABD000D0E90010C5E90310A5F80280284600F06E +:10ABE000C0FFB07800B9FFDFB078401EB07057E703 +:10ABF00070B50C460546032109F058F90146406836 +:10AC0000C2792244C2712846BDE870409FE72DE911 +:10AC1000FE4F8246507814460F464FF00008002839 +:10AC20004FD0012807D0022822D0FFDF2068B8606B +:10AC30006068F860B8E602AB0E2208215046FFF7C4 +:10AC400084FB0028F2D00298152105230170217899 +:10AC500041700A214180C0F80480C0F80880A0F843 +:10AC60000C80628882810E20CDE90008082221E054 +:10AC7000A678304600F094FF054606EB86012C22AC +:10AC8000786802EBC1010822465A02AB11465046D1 +:10AC9000FFF75BFB0028C9D00298072101702178DB +:10ACA00041700421418008218580C680CDE90018CB +:10ACB00005230A4639465046FFF707FB87F8088008 +:10ACC00072E6A678022516B1022E13D0FFDF2A1DE8 +:10ACD000914602AB08215046FFF737FB0028A5D06C +:10ACE00002980121022E01702178417045808680F2 +:10ACF00002D005E00625EAE7A188C180E18801814C +:10AD0000CDE900980523082239465046D4E710B50E +:10AD10000446032109F0CAF8014600F10802204662 +:10AD2000BDE8104073E72DE9F04F0F4605468DB0A2 +:10AD300014465088032109F0B9F84FF000088DF847 +:10AD400014800646ADF81680042F7BD36A78002A5B +:10AD500078D028784FF6FF794FF01C0A132834D0AA +:10AD600008DC012871D006284AD007286ED01228A6 +:10AD70000ED106E014286AD0152869D0162807D10C +:10AD8000AAE10C2F04D1307800F00301022907D08A +:10AD9000CDF80880CDF80C8068788DF808004CE07C +:10ADA00040F0080030706878B07001208DF8140011 +:10ADB000A888ADF81800E888ADF81A002889ADF821 +:10ADC0001C006889ADF81E0011E1B078904239D1BD +:10ADD0003078010736D5062F34D120F008003070C6 +:10ADE0006088414660F31F4100200CF067FE02209E +:10ADF0008DF81400ADF81890A888ADF81A00F6E0A8 +:10AE0000082F1FD1A888EF88814600F0BCFE80463D +:10AE10000146304600F0E6FE18B1404600F0ABFEB9 +:10AE2000B8B1FC48D0E90010CDE902106878ADF85F +:10AE30000C908DF80800ADF80E70608802AA3146BB +:10AE4000FFF7E5FE0DB0BDE8F08FB6E01EE041E093 +:10AE5000ECE0716808EB88002C2202EBC000085A75 +:10AE6000B842EFD1EB4802AAD0E90210CDE90210B6 +:10AE700068788DF8080008F0FF058DF80A506088A2 +:10AE80003146FFF7C4FE224629461FE0082FD9D1DC +:10AE9000B5F80480E88800F076FE074601463046A3 +:10AEA00000F0A0FE0028CDD007EB870271680AEB06 +:10AEB000C2000844028A4245C4D101780829C1D1A0 +:10AEC000407869788842BDD1F9B222463046FFF712 +:10AED0001EF9B7E70E2F7FF45BAFE9886F898B46C9 +:10AEE000B5F808903046FFF775F9ABF140014029FD +:10AEF00001D309204AE0B9F1170F01D3172F01D26E +:10AF00000B2043E040280ED000EB800271680AEB72 +:10AF1000C20008440178012903D140786978884249 +:10AF200090D00A2032E03046FFF735F9014640283C +:10AF30002BD001EB810372680AEBC30002EB00081F +:10AF4000012288F800206A7888F801207068AA88B1 +:10AF50004089B84200D93846AD8903232372A282C2 +:10AF6000E7812082A4F80C906582084600F018FE64 +:10AF70006081A8F81490A8F81870A8F80E50A8F8E6 +:10AF800010B0204600F0EDFD5CE7042005212172A1 +:10AF9000A4F80A80E081012121739E49D1E90421AE +:10AFA000CDE9022169788DF80810ADF80A006088B3 +:10AFB00002AA3146FFF72BFEE3E7062F89D3B078CC +:10AFC00090421AD13078010717D520F00800307070 +:10AFD0006088414660F31F4100200CF06FFD0220A5 +:10AFE0008DF81400A888ADF81800ADF81A906088A4 +:10AFF000224605A9F9F7E3F824E704213046FFF7D4 +:10B0000000F905464028BFD0022083030090224665 +:10B010002946304600F003FE4146608865F30F2163 +:10B0200060F31F4106200CF049FD0BE70E2FABD15A +:10B0300004213046FFF7E5F881464028A4D0414678 +:10B04000608869F30F2160F31F4106200CF036FD84 +:10B05000A8890B906889099070682F894089B84247 +:10B0600000D938468346B5F80680A8880A90484635 +:10B0700000F096FD60810B9818B1022000900B9BA8 +:10B0800024E0B8F1170F1ED3172F1CD30420207211 +:10B0900009986082E781A4F810B0A4F80C8009EB4D +:10B0A000890271680AEBC2000D18DDE90913A5F8E1 +:10B0B0001480A5F818B0E9812B82204600F051FDDC +:10B0C00006202870BEE601200B2300902246494648 +:10B0D000304600F0A4FDB5E6082F8DD1A988304692 +:10B0E000FFF778F80746402886D000F044FD002896 +:10B0F0009BD107EB870271680AEBC20008448046C7 +:10B1000000F086FD002890D1ED88B8F80E002844A4 +:10B11000B0F5803F05D360883A46314600F0B6FD71 +:10B1200090E6002DCED0A8F80E0060883A46314651 +:10B13000FFF73CFB08202072384600F031FD6081AB +:10B14000A5811EE72DE9F05F0C4601281FD09579F7 +:10B1500092F8048092F8056005EB85011F2202EB4E +:10B16000C10121F0030B08EB060111FB05F14FF6BD +:10B17000FF7202EAC10909F1030115FB0611264F0E +:10B1800021F0031ABB6840B101283ED125E0616877 +:10B19000E57891F800804E78DEE75946184608F0C9 +:10B1A000C0FC606000B9FFDF5A460021606803F010 +:10B1B0006EF9E5705146B86808F0B3FC6168486103 +:10B1C00000B9FFDF6068426902EB090181616068D4 +:10B1D00080F800806068467017E0606852464169F8 +:10B1E000184608F0C9FC5A466168B86808F0C4FC03 +:10B1F000032008F003FE0446032008F007FE201A8F +:10B20000012802D1B86808F081FC0BEB0A00BDE808 +:10B21000F09F000060610200300000200246002123 +:10B2200002208FE7F7B5FF4C0A20164620700098E1 +:10B2300060B100254FEA0D0008F055FC0021A17017 +:10B240006670002D01D10099A160FEBD012500208E +:10B25000F2E770B50C46154638220021204603F06F +:10B2600016F9012666700A22002104F11C0003F081 +:10B270000EF905B9FFDF297A207861F3010020700B +:10B28000A87900282DD02A4621460020FFF75AFF32 +:10B2900061684020E34A88706168C870616808711D +:10B2A000616848716168887161682888088161688F +:10B2B00068884881606886819078002811D061682C +:10B2C0000620087761682888C885616828884886CC +:10B2D00060680685606869889288018681864685EF +:10B2E000828570BDC878002802D00022012029E79D +:10B2F000704770B50546002165F31F4100200CF032 +:10B30000DDFB0321284608F0D1FD040000D1FFDF5A +:10B3100021462846FEF71CFF002804D0207840F084 +:10B3200010002070012070BD70B505460C4603204A +:10B3300008F056FD08B1002070BDBA4885708480C1 +:10B34000012070BD2DE9FF4180460E460F0CFEF72F +:10B350004DF9050007D06F800321384608F0A6FD9F +:10B36000040008D106E004B03846BDE8F0411321DE +:10B37000F9F750BBFFDF5FEA080005D0B8F1060F10 +:10B3800018D0FFDFBDE8FF8120782A4620F00800B2 +:10B3900020700020ADF8020002208DF800004FF66A +:10B3A000FF70ADF80400ADF8060069463846F8F7BE +:10B3B00006FFE7E7C6F3072101EB81021C23606863 +:10B3C00003EBC202805C042803D008280AD0FFDF08 +:10B3D000D8E7012000904FF440432A46204600F071 +:10B3E0001EFCCFE704B02A462046BDE8F041FEF738 +:10B3F0008EBE2DE9F05F05464089002790460C4639 +:10B400003E46824600F0BFFB8146287AC01E0828CF +:10B410006BD2DFE800F00D04192058363C47722744 +:10B420001026002C6CD0D5E90301C4E902015CE0D0 +:10B4300070271226002C63D00A2205F10C0104F1BA +:10B44000080002F0FAFF50E071270C26002C57D0BC +:10B45000E868A06049E0742710269CB3D5E9030191 +:10B46000C4E902016888032108F020FD8346FEF745 +:10B47000BDF802466888508049465846FEF7FEFDF2 +:10B4800033E075270A26ECB1A88920812DE07627C4 +:10B490001426BCB105F10C0004F1080307C883E8C9 +:10B4A000070022E07727102664B1D5E90301C4E93B +:10B4B00002016888032108F0F9FC01466888FFF75B +:10B4C00046FB12E01CE073270826CCB168880321F4 +:10B4D00008F0ECFC01460078C00606D56888FEF747 +:10B4E00037FE10B96888F8F777FAA8F800602CB131 +:10B4F0002780A4F806A066806888A080002086E6E1 +:10B50000A8F80060FAE72DE9FC410C461E461746F4 +:10B510008046032108F0CAFC05460A2C0AD2DFE85F +:10B5200004F005050505050509090907042303E0DD +:10B53000062301E0FFDF0023CDE9007622462946FD +:10B540004046FEF7C2FEBDE8FC81F8B50546A0F511 +:10B550007F40FF382BD0284608F0D9FD040000D1E9 +:10B56000FFDF204608F05FF9002821D001466A4637 +:10B57000204608F07AF900980321B0F805602846C3 +:10B5800008F094FC0446052E13D0304600F0FBFA78 +:10B5900005460146204600F025FB40B1606805EBFA +:10B5A00085013E2202EBC101405A002800D0012053 +:10B5B000F8BD007A0028FAD00020F8BDF8B504469E +:10B5C000408808F0A4FD050000D1FFDF6A46284648 +:10B5D000616800F0C4FA01460098091F8BB230F888 +:10B5E000032F0280428842800188994205D1042AB3 +:10B5F00008D0052A20D0062A16D022461946FFF781 +:10B6000099F9F8BD001D0E46054601462246304612 +:10B61000F6F739FF0828F4D1224629463046FCF7D0 +:10B6200064F9F8BD30000020636864880A46011D93 +:10B630002046FAF789F9F4E72246001DFFF773FB6D +:10B64000EFE770B50D460646032108F02FFC040015 +:10B6500004D02078000704D5112070BD43F2020009 +:10B6600070BD2A4621463046FEF7C9FE18B9286843 +:10B6700060616868A061207840F0080020700020B8 +:10B6800070BD70B50D460646032108F00FFC04009E +:10B6900004D02078000704D4082070BD43F20200D3 +:10B6A00070BD2A4621463046FEF7DDFE00B9A58270 +:10B6B000207820F008002070002070BD2DE9F04FA8 +:10B6C0000E4691B08046032108F0F0FB0446404648 +:10B6D00008F02FFD07460020079008900990ADF86C +:10B6E00030000A9002900390049004B9FFDF0DF13E +:10B6F0000809FFB9FFDF1DE038460BA9002207F05B +:10B7000055FF9DF82C0000F07F050A2D00D3FFDFC8 +:10B710006019017F491E01779DF82C00000609D5AC +:10B720002A460CA907A8FEF7C0FD19F80510491C08 +:10B7300009F80510761EF6B2DED204F13400F84D99 +:10B7400004F1260BDFF8DCA304F12A07069010E0D1 +:10B750005846069900F08CFA064628700A2800D34D +:10B76000FFDF5AF8261040468847E08CC05DB042A3 +:10B7700002D0208D0028EBD10A202870E94D4E46DA +:10B7800028350EE00CA907A800F072FA0446375DD0 +:10B7900055F8240000B9FFDF55F82420394640460B +:10B7A0009047BDF81E000028ECD111B0BDE8F08F25 +:10B7B00010B5032108F07AFB040000D1FFDF0A2254 +:10B7C000002104F11C0002F062FE207840F0040029 +:10B7D000207010BD10B50C46032108F067FB204413 +:10B7E000007F002800D0012010BD2DE9F84F8946C8 +:10B7F00015468246032108F059FB070004D028466D +:10B80000F5F727FD40B903E043F20200BDE8F88FE9 +:10B810004846F5F744FD08B11020F7E7786828B1ED +:10B8200069880089814201D90920EFE7B9F8000051 +:10B830001C2488B100F0A7F980460146384600F084 +:10B84000D1F988B108EB8800796804EBC000085C86 +:10B8500001280BD00820D9E73846FEF79CFC80462B +:10B86000402807D11320D1E70520CFE7FDF7BEFE22 +:10B8700006000BD008EB8800796804EBC0000C18B8 +:10B88000B9F8000020B1E88910B113E01120BDE73C +:10B890002888172802D36888172801D20720B5E71F +:10B8A000686838B12B1D224641463846FFF7E9F853 +:10B8B0000028ABD104F10C0269462046FEF7E1FFF7 +:10B8C000288860826888E082B9F8000030B10220E0 +:10B8D0002070E889A080E889A0B12BE003202070C7 +:10B8E000A889A08078688178402905D180F80280F5 +:10B8F00039465046FEF7D6FD404600F051F9A9F80A +:10B90000000021E07868218B4089884200D90846F0 +:10B910002083A6F802A004203072B9F800007081DC +:10B92000E0897082F181208B3082A08AB08130461C +:10B9300000F017F97868C178402905D180F80380B4 +:10B9400039465046FEF7FFFD00205FE770B50D4613 +:10B950000646032108F0AAFA04000ED0284600F09B +:10B9600012F905460146204600F03CF918B1284678 +:10B9700000F001F920B1052070BD43F2020070BD56 +:10B9800005EB85011C22606802EBC101084400F050 +:10B990003FF908B1082070BD2A462146304600F024 +:10B9A00075F9002070BD2DE9F0410C461746804620 +:10B9B000032108F07BFA0546204600F0E4F804462F +:10B9C00095B10146284600F00DF980B104EB8401E1 +:10B9D0001C22686802EBC1014618304600F018F9D5 +:10B9E00038B10820BDE8F08143F20200FAE70520F3 +:10B9F000F8E73B46324621462846FFF742F8002842 +:10BA0000F0D1E2B229464046FEF75AFF708C083862 +:10BA1000082803D242484078F7F776FA0020E1E799 +:10BA20002DE9F0410D4617468046032108F03EFA05 +:10BA30000446284600F0A7F8064624B13846F5F734 +:10BA400008FC38B902E043F20200CBE73868F5F7AA +:10BA500000FC08B11020C5E73146204600F0C2F8CE +:10BA600060B106EB86011C22606802EBC10145183B +:10BA7000284600F0CDF818B10820B3E70520B1E75B +:10BA8000B888A98A884201D90C20ABE76168E88CA4 +:10BA90004978B0EBC10F01D31320A3E7314620460C +:10BAA00000F094F80146606808234078C20005F170 +:10BAB000240008F082F8D7E90012C0E90012F2B2BF +:10BAC00021464046FEF772FE00208BE72DE9F04745 +:10BAD0000D461F4690468146032108F0E7F90446CB +:10BAE000284600F050F806463CB14DB13846F5F70F +:10BAF000F4FB50B11020BDE8F08743F20200FAE7F2 +:10BB0000606858B1A0F80C8027E03146204600F06C +:10BB100069F818B1304600F02EF828B10520EAE7A0 +:10BB2000300000207861020006EB86011C2260686C +:10BB300002EBC1014518284600F06AF808B1082058 +:10BB4000D9E7A5F80880F2B221464846FEF7B8FECC +:10BB50001FB1A8896989084438800020CBE707F025 +:10BB600084BE017821F00F01491C21F0F001103151 +:10BB70000170FDF73EBD20B94E48807808B1012024 +:10BB80007047002070474B498988884201D10020C6 +:10BB90007047402801D2402000E0403880B2704712 +:10BBA00010B50446402800D9FFDF2046FFF7E3FF29 +:10BBB00010B14048808810BD4034A0B210BD40682C +:10BBC00042690078484302EBC0007047C278406881 +:10BBD000037812FB03F24378406901FB032100EB79 +:10BBE000C1007047C2788A4209D9406801EB8101DF +:10BBF0001C2202EBC101405C08B10120704700200B +:10BC000070470078062801D901207047002070474E +:10BC10000078062801D00120704700207047F0B45A +:10BC200001EB81061C27446807EBC6063444049DDB +:10BC300005262670E3802571F0BCFEF71FBA10B50B +:10BC4000418911B1FFF7DDFF08B1002010BD0120CF +:10BC500010BD10B5C18C8278B1EBC20F04D9C18977 +:10BC600011B1FFF7CEFF08B1002010BD012010BDBB +:10BC700010B50C4601230A22011D07F0D4FF0078FD +:10BC80002188012282409143218010BDF0B402EB53 +:10BC900082051C264C6806EBC505072363554B68D7 +:10BCA0001C79402C03D11A71F0BCFEF791BCF0BC9A +:10BCB000704700003000002010B5EFF3108000F056 +:10BCC000010472B6FC484178491C41704078012853 +:10BCD00001D10BF0B3FA002C00D162B610BD70B5E3 +:10BCE000F54CA07848B90125A570FFF7E5FF0BF0EA +:10BCF000B6FA20B100200BF080FA002070BD4FF0A2 +:10BD00008040E570C0F80453F7E770B5EFF310809A +:10BD100000F0010572B6E84C607800B9FFDF60788A +:10BD2000401E6070607808B90BF08CFA002D00D1CD +:10BD300062B670BDE04810B5817821B10021C170B4 +:10BD40008170FFF7E2FF002010BD10B504460BF034 +:10BD500086FAD9498978084000D001202060002067 +:10BD600010BD10B5FFF7A8FF0BF079FA02220123EE +:10BD7000D149540728B1D1480260236103200872D9 +:10BD800002E00A72C4F804330020887110BD2DE966 +:10BD9000F84FDFF824934278817889F80420002650 +:10BDA00089F80510074689F806600078DFF810B3B7 +:10BDB000354620B1012811D0022811D0FFDF0BF049 +:10BDC00060FA4FF0804498B10BF062FAB0420FD1A4 +:10BDD00030460BF061FA0028FAD042E00126EEE787 +:10BDE000FFF76AFF58460168C907FCD00226E6E75C +:10BDF0000120E060C4F80451B2490E600107D1F897 +:10BE00004412B04AC1F3423124321160AD49343199 +:10BE100008604FF0020AC4F804A3A060AA480168B1 +:10BE2000C94341F3001101F10108016841F010011B +:10BE3000016001E019F0A8FFD4F804010028F9D04E +:10BE400030460BF029FA0028FAD0B8F1000F04D1DF +:10BE50009D48016821F010010160C4F808A3C4F8EE +:10BE6000045199F805004E4680B1387870B90BF04E +:10BE7000F6F980460BF006FC6FF00042B8F1000FB7 +:10BE800002D0C6E9032001E0C6E90302DBF80000A6 +:10BE9000C00701D00BF0DFF9387810B13572BDE87A +:10BEA000F88F4FF01808C4F808830127A7614FF4F2 +:10BEB0002070ADF8000000BFBDF80000411EADF8D5 +:10BEC0000010F9D2C4F80C51C4F810517A48C01DC2 +:10BED0000BF06CFA3570FFF744FF676179493079F0 +:10BEE00020310860C4F80483D9E770B5050000D19B +:10BEF000FFDF4FF080424FF0FF30C2F8080300210F +:10BF0000C2F80011C2F80411C2F80C11C2F81011E5 +:10BF1000694C61700BF0AFF910B10120A070607036 +:10BF200067480068C00701D00BF095F92846BDE8C6 +:10BF300070402CE76048007A002800D0012070474C +:10BF40002DE9F04F61484FF0000A85B0D0F800B0FD +:10BF5000D14657465D4A5E49083211608406D4F8DE +:10BF6000080110B14FF0010801E04FF000080BF09C +:10BF7000F0F978B1D4F8240100B101208246D4F858 +:10BF80001C0100B101208146D4F8200108B101272D +:10BF900000E00027D4F8000100B101200490D4F89B +:10BFA000040100B101200390D4F80C0100B101207C +:10BFB0000290D4F8100100B101203F4D0190287883 +:10BFC00000260090B8F1000F04D0C4F808610120E9 +:10BFD0000BF013F9BAF1000F04D0C4F82461092062 +:10BFE0000BF00BF9B9F1000F04D0C4F81C610A2062 +:10BFF0000BF003F927B1C4F820610B200BF0FDF81A +:10C000002D48C01D0BF0DAF900B1FFDFDFF8AC807E +:10C010000498012780B1C4F80873E87818B1EE706D +:10C0200000200BF0EAF8287A022805D103202872B4 +:10C030000221C8F800102761039808B1C4F8046110 +:10C04000029850B1C4F80C61287A032800D0FFDFB1 +:10C05000C8F800602F72FFF758FE019838B1C4F895 +:10C060001061287A012801D100F05CF8676100981E +:10C0700038B12E70287A012801D1FFF772FEFFF740 +:10C0800044FE0D48C01D0BF0AFF91049091DC1F861 +:10C0900000B005B0BDE8F08F074810B5C01D0BF02B +:10C0A0008DF90549B0B1012008704FF0E021C1F8C9 +:10C0B0000002BDE81040FFE544000020340C0040C1 +:10C0C0000C0400401805004010ED00E0100502408F +:10C0D00001000001087A012801D1FFF742FEBDE806 +:10C0E000104024480BF080B970B5224CE41FA078B2 +:10C0F00008B90BF0A7F801208507A861207A00266F +:10C10000032809D1D5F80C0120B900200BF0C4F8A0 +:10C110000028F7D1C5F80C6126724FF0FF30C5F842 +:10C12000080370BD70B5134CE41F6079F0B10128AD +:10C1300003D0A179401E814218DA0BF090F8054631 +:10C140000BF0A0FA6179012902D9A179491CA171EA +:10C150000DB1216900E0E168411A022902DA11F10A +:10C16000020F06DC0DB1206100E0E060BDE8704028 +:10C17000F7E570BD4B0000200F4A12680D498A4256 +:10C180000CD118470C4A12680A4B9A4206D101B5E5 +:10C190000BF04AFA0BF01DFDBDE8014007490968A4 +:10C1A0000958084706480749054A064B70470000EA +:10C1B00000000000BEBAFECA5C000020040000209F +:10C1C000C8130020C8130020F8B51D46DDE9064756 +:10C1D0000E000AD007F0ADFF2346FF1DBCB231466A +:10C1E0002A46009407F0BBFBF8BDD0192246194639 +:10C1F00002F023F92046F8BD70B50D460446102222 +:10C20000002102F044F9258117206081A07B40F0D5 +:10C210000A00A07370BD4FF6FF720A80014602202B +:10C220000BF04CBC704700897047827BD30701D16B +:10C23000920703D48089088000207047052070474A +:10C24000827B920700D581817047014600200988D2 +:10C2500041F6FE52114200D00120704700B503465E +:10C26000807BC00701D0052000BD59811846FFF72B +:10C27000ECFFC00703D0987B40F004009873987BD4 +:10C2800040F001009873002000BD827B520700D56A +:10C2900009B14089704717207047827B61F3C30260 +:10C2A000827370472DE9FC5F0E460446017896467E +:10C2B000012000FA01F14DF6FF5201EA020962681D +:10C2C0004FF6FF7B1188594502D10920BDE8FC9F3C +:10C2D000B9F1000F05D041F6FE55294201D00120E9 +:10C2E000F4E741EA090111801D0014D000232B70EE +:10C2F00094F800C0052103221F464FF0020ABCF14A +:10C300000E0F76D2DFE80CF0F909252F47646B7722 +:10C31000479193B4D1D80420D8E7616820898B7BFA +:10C320009B0767D517284AD30B89834247D389894E +:10C33000172901D3814242D185F800A0A5F8010058 +:10C340003280616888816068817B21F0020181739D +:10C35000C6E0042028702089A5F801006089A5F8AE +:10C3600003003180BCE0208A3188C01D1FFA80F8AC +:10C37000414524D3062028702089A5F80100608952 +:10C38000A5F80300A089A5F805000721208ACDE9BA +:10C390000001636941E00CF0FF00082810D008207C +:10C3A00028702089A5F801006089A5F80300318074 +:10C3B0006A1D694604F10C0009F025FB10B15EE02E +:10C3C0001020EDE730889DF800100844308087E0A9 +:10C3D0000A2028702089A5F80100328044E00C2052 +:10C3E00028702089A5F801006089A5F80300318034 +:10C3F0003AE082E064E02189338800EB41021FFAD1 +:10C4000082F843453BD3B8F1050F38D30E222A708A +:10C410000BEA4101CDE90010E36860882A467146C5 +:10C42000FFF7D2FEA6F800805AE04020287060890D +:10C430003188C01C1FFA80F8414520D32878714606 +:10C4400020F03F00123028702089A5F80100608993 +:10C45000CDE9000260882A46E368FFF7B5FEA6F83A +:10C460000080287840063BD461682089888037E0C6 +:10C47000A0893288401D1FFA80F8424501D2042766 +:10C480003DE0162028702089A5F801006089A5F8F4 +:10C490000300A089CDE9000160882A46714623691E +:10C4A000FFF792FEA6F80080DEE718202870207AB9 +:10C4B0006870A6F800A013E061680A88920401D4AD +:10C4C00005271CE0C9882289914201D0062716E081 +:10C4D0001E21297030806068018821F4005101809C +:10C4E000B9F1000F0BD061887823002202200BF0F5 +:10C4F0003BFA61682078887006E033800327606823 +:10C50000018821EA090101803846DFE62DE9FF4F65 +:10C5100085B01746129C0D001E461CD03078C1070E +:10C5200003D000F03F00192801D9012100E00021CB +:10C530002046FFF7AAFEA8420DD32088A0F57F4130 +:10C54000FF3908D03078410601D4000605D508200F +:10C5500009B0BDE8F08F0720FAE700208DF8000051 +:10C560008DF8010030786B1E00F03F0C0121A81EF1 +:10C570004FF0050A4FF002094FF0030B9AB2BCF1DD +:10C58000200F75D2DFE80CF08B10745E7468748C29 +:10C59000749C74B574BA74C874D474E1747474F10E +:10C5A00074EF74EE74ED748B052D78D18DF80090D6 +:10C5B000A0788DF804007088ADF8060030798DF809 +:10C5C0000100707800F03F000C2829D00ADCA0F1AF +:10C5D0000200092863D2DFE800F0126215621A62D5 +:10C5E0001D622000122824D004DC0E281BD0102845 +:10C5F000DBD11BE016281FD01828D6D11FE02078E9 +:10C60000800701E020784007002848DAEEE0207833 +:10C610000007F9E72078C006F6E720788006F3E700 +:10C6200020784006F0E720780006EDE72088C00576 +:10C63000EAE720884005E7E720880005E4E720884E +:10C64000C004E1E72078800729D5032D27D18DF894 +:10C6500000B0B6F8010081E0217849071FD5062D0A +:10C660001DD381B27078012803D0022817D102E0CF +:10C67000C9E0022000E0102004228DF8002072782A +:10C680008DF80420801CB1FBF0F2ADF8062092B2C8 +:10C6900042438A4203D10397ADF80890A6E079E0BF +:10C6A0002078000776D598B282088DF800A0ADF802 +:10C6B0000420B0EB820F6DD10297ADF8061095E023 +:10C6C0002178C90666D5022D64D381B206208DF883 +:10C6D0000000707802285DD3B1FBF0F28DF8040001 +:10C6E000ADF8062092B242438A4253D1ADF8089089 +:10C6F0007BE0207880064DD5072003E020784006B7 +:10C700007FD508208DF80000A088ADF80400ADF8B2 +:10C710000620ADF8081068E02078000671D50920E1 +:10C72000ADF804208DF80000ADF8061002975DE02A +:10C730002188C90565D5022D63D381B20A208DF801 +:10C740000000707804285CD3C6E72088400558D5DF +:10C75000012D56D10B208DF80000A088ADF8040003 +:10C7600044E021E026E016E0FFE72088000548D5F8 +:10C77000052D46D30C208DF80000A088ADF80400EC +:10C78000B6F803006D1FADF80850ADF80600ADF81F +:10C790000AA02AE035E02088C00432D5012D30D12E +:10C7A0000D208DF8000021E02088800429D4B6F8FF +:10C7B0000100E080A07B000723D5032D21D3307832 +:10C7C00000F03F001B2818D00F208DF800002088B3 +:10C7D00040F40050A4F80000B6F80100ADF80400E1 +:10C7E000ED1EADF80650ADF808B003976946059800 +:10C7F000F5F792FB050008D016E00E208DF800003A +:10C80000EAE7072510E008250EE0307800F03F0049 +:10C810001B2809D01D2807D0022005990BF04EF9DE +:10C82000208800F400502080A07B400708D52046D7 +:10C83000FFF70BFDC00703D1A07B20F00400A0731D +:10C84000284685E61FB5022806D101208DF8000094 +:10C8500088B26946F5F760FB1FBD0000F8B51D46BC +:10C86000DDE906470E000AD007F063FC2346FF1DF2 +:10C87000BCB231462A46009407F071F8F8BDD019D1 +:10C880002246194601F0D9FD2046F8BD2DE9FF4F9B +:10C890008DB09B46DDE91B57DDF87CA00C46082BCC +:10C8A00005D0E06901F0FEF850B11020D2E02888F0 +:10C8B000092140F0100028808AF80010022617E0B5 +:10C8C000E16901208871E2694FF420519180E169AA +:10C8D0008872E06942F601010181E06900218173FB +:10C8E0002888112140F0200028808AF800100426B2 +:10C8F00038780A900A2038704FF0020904F11800C5 +:10C900004D460C9001F0C6FBB04681E0BBF1100F24 +:10C910000ED1022D0CD0A9EB0800801C80B20221A0 +:10C92000CDE9001005AB52461E990D98FFF796FF12 +:10C93000BDF816101A98814203D9F74800790F9074 +:10C9400004E003D10A9808B138702FE04FF00201DB +:10C95000CDE900190DF1160352461E990D98FFF707 +:10C960007DFF1D980088401B801B83B2C6F1FF002D +:10C97000984200D203461E990BA8D9B15FF000027D +:10C98000DDF878C0CDE9032009EB060189B2CDE9D5 +:10C9900001C10F980090BDF8161000220D9801F00B +:10C9A0000EFC387070B1C0B2832807D0BDF81600F5 +:10C9B00020833AE00AEB09018A19E1E7022011B06D +:10C9C000BDE8F08FBDF82C00811901F0FF08022DA1 +:10C9D0000DD09AF80120424506D1BDF820108142C1 +:10C9E00007D0B8F1FF0F04D09AF801801FE08AF851 +:10C9F0000180C94800680178052902D1BDF81610E8 +:10CA0000818009EB08001FFA80F905EB080085B268 +:10CA1000DDE90C1005AB0F9A01F03FFB28B91D981A +:10CA20000088411B4145BFF671AF022D13D0BBF109 +:10CA3000100F0CD1A9EB0800801C81B20220CDE9B7 +:10CA4000000105AB52461E990D98FFF707FF1D9890 +:10CA50000580002038700020B1E72DE9F8439C469E +:10CA6000089E13460027B26B9AB3491F8CB2F18F10 +:10CA7000A1F57F45FF3D05D05518AD882944891D96 +:10CA80008DB200E000252919B6F83C8008314145F7 +:10CA900020D82A44BCF8011022F8021BBCF803106D +:10CAA00022F8021B984622F8024B914607F02FFB12 +:10CAB0004FF00C0C41464A462346CDF800C006F024 +:10CAC0001AFFF587B16B00202944A41D214408807A +:10CAD00003E001E0092700E083273846BDE8F8833A +:10CAE00010B50B88848F9C420CD9846BE0180488A5 +:10CAF00044B1848824F40044A41D23440B801060B6 +:10CB0000002010BD0A2010BD2DE9F0478AB0002595 +:10CB1000904689468246ADF8185007274BE00598A5 +:10CB200006888088000446D4A8F8006007A801950C +:10CB300000970295CDE903504FF40073002231466F +:10CB4000504601F03CFB04003CD1BDF81800ADF8A4 +:10CB50002000059804888188B44216D10A0414D4B0 +:10CB600001950295039521F400410097049541F445 +:10CB7000804342882146504601F0BFF804000BD1A3 +:10CB80000598818841F40041818005AA08A948469A +:10CB9000FFF7A6FF0400DCD00097059802950195E9 +:10CBA000039504950188BDF81C300022504601F021 +:10CBB000A4F80A2C06D105AA06A94846FFF790FF5B +:10CBC0000400ACD0ADF8185004E00598818821F439 +:10CBD0000041818005AA06A94846FFF781FF002889 +:10CBE000F3D00A2C03D020460AB0BDE8F08700201D +:10CBF000FAE710B50C46896B86B051B10C218DF85F +:10CC00000010A18FADF80810A16B01916946FAF7E9 +:10CC100001FB00204FF6FF71A063E187A08706B0FB +:10CC200010BD2DE9F0410D460746896B0020069E98 +:10CC30001446002911D0012B0FD13246294638461F +:10CC4000FFF762FF002808D1002C06D032462946A3 +:10CC50003846BDE8F04100F034BFBDE8F0812DE971 +:10CC6000FC411446DDE9087C0E46DDE90A15521D3B +:10CC7000BCF800E092B2964502D20720BDE8FC81E4 +:10CC8000ACF8002017222A70A5F80160A5F803303F +:10CC90000522CDE900423B462A46FFF7DFFD002092 +:10CCA000ECE770B50C46154648220021204601F0FD +:10CCB000EEFB04F1080044F81C0F00204FF6FF7152 +:10CCC000E06161842084A5841720E08494F82A0020 +:10CCD00040F00A0084F82A0070BD4FF6FF720A8007 +:10CCE000014603200AF0EABE30B585B00C46054681 +:10CCF000FFF77FFFA18E284629B101218DF8001092 +:10CD00006946FAF787FA0020E0622063606305B0A5 +:10CD100030BDB0F8400070476000002090F8462019 +:10CD2000920703D4408808800020F4E70620F2E749 +:10CD300090F846209207EED5A0F84410EBE70146A4 +:10CD4000002009880A0700D5012011F0F00F01D05A +:10CD500040F00200CA0501D540F004008A0501D563 +:10CD600040F008004A0501D540F010000905D2D571 +:10CD700040F02000CFE700B5034690F84600C0071A +:10CD800001D0062000BDA3F842101846FFF7D7FFD8 +:10CD900010F03E0F05D093F8460040F0040083F8F1 +:10CDA000460013F8460F40F001001870002000BD47 +:10CDB00090F84620520700D511B1B0F84200AAE71A +:10CDC0001720A8E710F8462F61F3C3020270A2E70C +:10CDD0002DE9FF4F9BB00E00DDE92B34DDE929780A +:10CDE000289D24D02878C10703D000F03F001928DF +:10CDF00001D9012100E000212046FFF7D9FFB04210 +:10CE000015D32878410600F03F010CD41E290CD020 +:10CE1000218811F47F6F0AD13A8842B1A1F57F428F +:10CE2000FF3A04D001E0122901D1000602D5042006 +:10CE30001FB0C5E5FA491D984FF0000A08718DF83A +:10CE400018A08DF83CA00FAA0A60ADF81CA0ADF8A0 +:10CE500050A02978994601F03F02701F5B1C04F135 +:10CE6000180C4FF0060E4FF0040BCDF858C01F2AD7 +:10CE70007ED2DFE802F07D7D107D267DAC7DF47DE5 +:10CE8000F37DF27DF17DF47DF07D7D7DEF7DEE7DA6 +:10CE90007D7D7D7DED0094F84610B5F80100890791 +:10CEA00001D5032E02D08DF818B01EE34FF40061B7 +:10CEB000ADF85010608003218DF83C10ADF84000B3 +:10CEC000D4E2052EEFD1B5F801002083ADF81C00A7 +:10CED000B5F80310618308B1884201D9012079E1D6 +:10CEE0000020A07220814FF6FF702084169801F078 +:10CEF000D1F8052089F800000220029083460AAB91 +:10CF00001D9A16991B9801F0C8F890BB9DF82E0049 +:10CF1000012804D0022089F80100102003E001203C +:10CF200089F8010002200590002203A90BA808F04F +:10CF30006AFDE8BB9DF80C00059981423DD1398816 +:10CF4000801CA1EB0B01814237DB02990220CDE965 +:10CF500000010DF12A034A4641461B98FFF77EFC6B +:10CF600002980BF1020B801C81B217AA029101E01A +:10CF70009CE228E003A90BA808F045FD02999DF862 +:10CF80000C00CDE9000117AB4A4641461B98FFF75C +:10CF900065FC9DF80C000AAB0BEB00011FFA81FB4E +:10CFA00002991D9A084480B2029016991B9800E0DD +:10CFB00003E001F072F80028B6D0BBF1020F02D0F6 +:10CFC000A7F800B04FE20A208DF818004BE20021CC +:10CFD0000391072EFFF467AFB5F801002083ADF889 +:10CFE0001C00B5F80320628300283FF477AF90421D +:10CFF0003FF674AF0120A072B5F805002081002033 +:10D00000A073E06900F04EFD78B9E16901208871F4 +:10D01000E2694FF420519180E1698872E16942F63A +:10D0200001000881E06900218173F01F20841E98AF +:10D03000606207206084169801F02CF8072089F8B8 +:10D0400000000120049002900020ADF82A0028E0A2 +:10D0500019E29FE135E1E5E012E2A8E080E043E07B +:10D060000298012814D0E0698079012803D1BDF825 +:10D070002800ADF80E00049803ABCDE900B04A4695 +:10D0800041461B98FFF7EAFB0498001D80B204900C +:10D09000BDF82A00ADF80C00ADF80E00059880B27E +:10D0A00002900AAB1D9A16991B9800F0F6FF28B95A +:10D0B00002983988001D05908142D1D2029801283A +:10D0C00081D0E0698079012803D1BDF82800ADF84E +:10D0D0000E00049803ABCDE900B04A4641461B98C8 +:10D0E000FFF7BCFB0298BDE1072E02D0152E7FF49E +:10D0F000DAAEB5F801102183ADF81C10B5F80320A5 +:10D10000628300293FF4EAAE91423FF6E7AE012187 +:10D11000A1724FF0000BA4F808B084F80EB0052EF1 +:10D1200007D0C0B2691DE26908F06BFC00287FF4EB +:10D130004AAF4FF6FF70208401A906AA14A8CDF8C3 +:10D1400000B081E885032878214600F03F031D9A4E +:10D150001B98FFF79BFB8246208BADF81C0082E1F9 +:10D160000120032EC3D14021ADF85010B5F80110B5 +:10D170002183ADF81C100AAAB8F1000F00D00023DB +:10D18000CDE9020304921D98CDF804800090388800 +:10D190000022401E83B21B9801F011F88DF8180090 +:10D1A00090BB0B2089F80000BDF8280035E04FF057 +:10D1B000010C052E9BD18020ADF85000B5F8011070 +:10D1C0002183B5F803002084ADF81C10B0F5007F72 +:10D1D00003D907208DF8180087E140F47C422284AF +:10D1E0000CA8B8F1000F00D00023CDE90330CDE941 +:10D1F000018C1D9800903888401E83B21B9800F067 +:10D20000DEFF8DF8180018B18328A8D10220BFE0F6 +:10D210000D2189F80010BDF83000401C22E100000B +:10D2200060000020032E04D248067FF53CAE0020AB +:10D2300018E1B5F80110ADF81C102878400602D5A9 +:10D240008DF83CE002E007208DF83C004FF000082C +:10D250000320CDE902081E9BCDF810801D98019394 +:10D26000A6F1030B00901FFA8BF342461B9800F0C7 +:10D2700044FD8DF818008DF83C80297849060DD5BD +:10D280002088C00506D5208BBDF81C10884201D12E +:10D29000C4F8248040468DF81880E3E0832801D14B +:10D2A0004FF0020A4FF48070ADF85000BDF81C003A +:10D2B0002083A4F820B01E986062032060841321AC +:10D2C000CDE0052EFFF4EFADB5F80110ADF81C1060 +:10D2D000A28F6AB3A2F57F43FE3B29D008228DF8C6 +:10D2E0003C2000BF4FF0000B0523CDE9023BDDF8E9 +:10D2F00078C0CDF810B01D9A80B2CDF804C040F4CB +:10D3000000430092B5F803201B9800F0F6FC8DF85E +:10D310003CB04FF400718DF81800ADF85010832820 +:10D3200010D0F8B1A18FA1F57F40FE3807D0DCE026 +:10D330000B228DF83C204FF6FE72A287D2E7A4F8AC +:10D340003CB0D2E000942B4631461E9A1B98FFF762 +:10D3500084FB8DF8180008B183284BD1BDF81C0060 +:10D36000208353E700942B4631461E9A1B98FFF703 +:10D3700074FB8DF81800E8BBE18FA06B0844831D97 +:10D380008DE888034388828801881B98FFF767FC33 +:10D39000824668E095F80180022E70D15FEA0800AD +:10D3A00002D0B8F1010F6AD109208DF83C0007A81E +:10D3B00000908DF840804346002221461B98FFF7DD +:10D3C00030FC8DF842004FF0000B8DF843B050B99F +:10D3D000B8F1010F12D0B8F1000F04D1A18FA1F55F +:10D3E0007F40FF380AD0A08F40B18DF83CB04FF499 +:10D3F000806000E037E0ADF850000DE00FA91B9809 +:10D40000F9F708FF82468DF83CB04FF48060ADF824 +:10D410005000BAF1020F06D0FC480068C07928B16C +:10D420008DF8180027E0A4F8188044E0BAF1000F46 +:10D4300003D081208DF818003DE007A800904346F6 +:10D44000012221461B98FFF7ECFB8DF818002146BE +:10D450001B98FFF7CEFB9DF8180020B9192189F819 +:10D460000010012038809DF83C0020B10FA91B98C6 +:10D47000F9F7D0FE8246BAF1000F33D01BE018E076 +:10D480008DF818E031E02078000712D5012E10D178 +:10D490000A208DF83C00E088ADF8400003201B997D +:10D4A0000AF00CFB0820ADF85000C0E648067FF5F6 +:10D4B000FAAC4FF0040A2088BDF8501008432080D1 +:10D4C000BDF8500080050BD5A18FA1F57F40FE3837 +:10D4D00006D11E98E06228982063A6864FF0030AC2 +:10D4E0005046A5E49DF8180078B1012089F80000A5 +:10D4F000297889F80110BDF81C10A9F802109DF8D0 +:10D50000181089F80410052038802088BDF85010C4 +:10D5100088432080E4E72DE9FF4F8846087895B0DE +:10D52000012181404FF20900249C0140ADF82010F8 +:10D530002088DDF88890A0F57F424FF0000AFF3A7E +:10D5400006D039B1000705D5012019B0BDE8F08F2C +:10D550000820FAE7239E4FF0000B0EA886F800B0D3 +:10D5600018995D460988ADF83410A8498DF81CB0AB +:10D57000179A0A718DF838B0086098F800000128F1 +:10D580003BD0022809D003286FD1307820F03F002B +:10D590001D303070B8F80400E08098F800100320C7 +:10D5A000022904D1317821F03F011B31317094F808 +:10D5B0004610090759D505ABB9F1000F13D000216A +:10D5C00002AA82E80B000720CDE90009BDF834006B +:10D5D000B8F80410C01E83B20022159800F0EFFDC9 +:10D5E0000028D1D101E0F11CEAE7B8F80400A6F860 +:10D5F0000100BDF81400C01C04E198F805108DF876 +:10D600001C1098F80400012806D04FF4007A022874 +:10D610002CD00328B8D16CE12188B8F8080011F4A7 +:10D620000061ADF8201020D017281CD3B4F84010AA +:10D63000814218D3B4F84410172901D3814212D182 +:10D64000317821F03F01C91C3170A6F80100032197 +:10D65000ADF83410A4F8440094F8460020F002001D +:10D6600084F8460065E105257EE177E1208808F130 +:10D67000080700F4FE60ADF8200010F0F00F1BD09A +:10D6800010F0C00F03D03888228B9042EBD199B9AB +:10D69000B878C00710D0B9680720CDE902B1CDF83D +:10D6A00004B00090CDF810B0FB88BA88398815987E +:10D6B00000F023FB0028D6D12398BDF82010401C91 +:10D6C00080294ED006DC10290DD020290BD040290E +:10D6D00087D124E0B1F5807F6ED051457ED0B1F581 +:10D6E000806F97D1DEE0C80601D5082000E0102049 +:10D6F00082460DA907AA0520CDE902218DF8380040 +:10D70000ADF83CB0CDE9049608A93888CDE9000110 +:10D710005346072221461598FFF7B8F8A8E09DF870 +:10D720001C2001214FF00A0A002A9BD105ABB9F158 +:10D73000000F00D00020CDE902100720CDE900093C +:10D74000BDF834000493401E83B2218B002215984B +:10D7500000F035FD8DF81C000B203070BDF8140072 +:10D7600020E09DF81C2001214FF00C0A002A22D154 +:10D7700013ABB9F1000F00D00020CDE90210072053 +:10D78000CDE900090493BDF83400228C401E83B219 +:10D79000218B159800F013FD8DF81C000D203070C2 +:10D7A000BDF84C00401CADF8340005208DF8380061 +:10D7B000208BADF83C00BCE03888218B88427FF498 +:10D7C00052AF9DF81C004FF0120A00281CD1606A6D +:10D7D000A8B1B878C0073FF446AF00E018E0BA68D7 +:10D7E0000720CDE902B2CDF804B00090CDF810B01A +:10D7F000FB88BA88159800F080FA8DF81C00132079 +:10D8000030700120ADF8340093E00000600000208B +:10D810003988208B8142D2D19DF81C004FF0160A26 +:10D820000028A06B08D0E0B34FF6FF7000215F46E0 +:10D83000ADF808B0019027E068B1B978C907BED14A +:10D84000E18F0DAB0844821D03968DE80C024388DE +:10D850008288018809E0B878C007BCD0BA680DABEF +:10D8600003968DE80C02BB88FA881598FFF7F7F944 +:10D8700005005ED0072D72D076E0019005AA02A9BE +:10D880002046FFF72DF90146E28FBDF808008242DD +:10D8900001D00029F1D0E08FA16B084407800198E6 +:10D8A000E08746E09DF81C004FF0180A40B1208B3D +:10D8B000C8B13888208321461598FFF79AF938E0D7 +:10D8C00004F118000090237E012221461598FFF7ED +:10D8D000A8F98DF81C000028EDD119203070012026 +:10D8E000ADF83400E7E7052521461598FFF781F9E3 +:10D8F0003AE0208800F40070ADF8200050452DD1AA +:10D90000A08FA0F57F41FE3901D006252CE0D8F884 +:10D9100008004FF0160A48B1A063B8F80C10A187B0 +:10D920004FF6FF71E187A0F800B002E04FF6FF70FC +:10D93000A087BDF8200030F47F611AD07823002240 +:10D94000032015990AF010F898F80000207120883B +:10D95000BDF82010084320800EE000E00725208855 +:10D96000BDF8201088432080208810F47F6F1CD0E1 +:10D970003AE02188814321809DF8380020B10EA92A +:10D980001598F9F747FC05469DF81C000028EBD0D8 +:10D9900086F801A001203070208B70809DF81C005B +:10D9A00030710520ADF83400DEE7A18EE1B11898A2 +:10D9B0000DAB0088ADF834002398CDE90304CDE920 +:10D9C0000139206B0090E36A179A1598FFF700FA67 +:10D9D000054601208DF838000EA91598F9F71AFCB4 +:10D9E00000B10546A4F834B094F8460040070AD5C3 +:10D9F0002046FFF7A4F910F03E0F04D114F8460FAB +:10DA000020F0040020701898BDF8341001802846DA +:10DA10009BE500B585B0032806D102208DF80000F3 +:10DA200088B26946F9F7F6FB05B000BD10B5384C71 +:10DA30000B782268012B02D0022B2AD111E0137837 +:10DA40000BB1052B01D10423137023688A889A80B7 +:10DA50002268CB88D38022680B8913814989518140 +:10DA60000DE08B8893802268CB88D38022680B8955 +:10DA700013814B8953818B899381096911612168D5 +:10DA8000F9F7C8FB226800210228117003D0002892 +:10DA900000D0812010BD832010BD806B002800D0F5 +:10DAA000012070478178012909D10088B0F5205FF5 +:10DAB00003D042F60101884201D1002070470720BF +:10DAC0007047F0B587B0002415460E460746ADF8FE +:10DAD000184011E005980088288005980194811D60 +:10DAE000CDE902410721049400918388428801888E +:10DAF000384600F002F930B905AA06A93046FEF70B +:10DB0000EFFF0028E6D00A2800D1002007B0F0BDC2 +:10DB10006000002010B58B7883B102789A4205D15D +:10DB20000B885BB102E08B79091D4BB18B789A426F +:10DB3000F9D1B0F801300C88A342F4D1002010BD17 +:10DB4000812010BD072826D012B1012A27D103E079 +:10DB5000497801F0070102E04978C1F3C2010529C3 +:10DB60001DD2DFE801F00318080C12000AB10320EF +:10DB700070470220704704280DD250B10DE00528EF +:10DB800009D2801E022808D303E0062803D0032808 +:10DB900003D005207047002070470F207047812078 +:10DBA0007047C0B282060BD4000607D5FA48807AC7 +:10DBB0004143C01D01EBD00080B27047084670475A +:10DBC0000020704770B513880B800B781C0625D594 +:10DBD000F14CA47A844204D843F01000087000206D +:10DBE00070BD956800F0070605EBD0052D78F5406F +:10DBF00065F304130B701378D17803F0030341EA43 +:10DC0000032140F20123B1FBF3F503FB15119268E8 +:10DC1000E41D00FB012000EBD40070BD906870BDD6 +:10DC200037B51446BDF804101180117841F0040195 +:10DC300011709DF804100A061ED5D74AA368C1F3D7 +:10DC40000011927A824208D8FE2811D1D21DD20842 +:10DC50004942184600F01BFC0AE003EBD00200F03A +:10DC60000703012510789D40A84399400843107090 +:10DC7000207820F0100020703EBD2DE9F0410746CD +:10DC8000C81C0E4620F00300B04202D08620BDE83A +:10DC9000F081C14D002034462E60AF802881AA72E9 +:10DCA000E8801AE0E988491CE980810614D4E1780B +:10DCB00000F0030041EA002040F20121B0FBF1F244 +:10DCC00001FB12012068FFF76CFF2989084480B22C +:10DCD0002881381A3044A0600C3420784107E1D400 +:10DCE0000020D4E7AC4801220189C08800EB400045 +:10DCF00002EB8000084480B270472DE9FF4F89B0E5 +:10DD00001646DDE9168A0F46994623F44045084633 +:10DD100000F054FB040002D02078400703D4012017 +:10DD20000DB0BDE8F08F099806F086F802902078D3 +:10DD3000000606D59848817A0298814201D887204A +:10DD4000EEE7224601A90298FFF73CFF8346002038 +:10DD50008DF80C004046B8F1070F1AD00122214679 +:10DD6000FFF7F0FE0028DBD12078400611D5022015 +:10DD70008DF80C00ADF81070BDF80400ADF812007D +:10DD8000ADF814601898ADF81650CDF81CA0ADF899 +:10DD900018005FEA094004D500252E46A846012751 +:10DDA0000CE02178E07801F0030140EA012040F224 +:10DDB0000121B0FBF1F2804601FB12875FEA494086 +:10DDC00009D5B84507D1A178207901F0030140EACF +:10DDD0000120B04201D3BE4201D90720A0E7A81913 +:10DDE0001FFA80F9B94501D90D2099E79DF80C007B +:10DDF00028B103A90998F9F70BFA002890D1B84582 +:10DE000007D1A0784FEA192161F30100A07084F8CE +:10DE100004901A9800B10580199850EA0A0027D09A +:10DE2000199830B10BEB06002A46199900F005FB52 +:10DE30000EE00BEB06085746189E099806F067F9A6 +:10DE40002B46F61DB5B239464246009505F053FD06 +:10DE5000224601A90298FFF7B5FE9DF8040022466C +:10DE600020F010008DF80400DDE90110FFF7D8FE66 +:10DE7000002055E72DE9FF4FDFF81C91824685B061 +:10DE8000B9F80610D9F8000001EB410100EB81045C +:10DE900040F20120B2FBF0F1174600FB1175DDE9FD +:10DEA000138B4E4629460698FFF77BFE0346FFF785 +:10DEB00019FF1844B1880C30884202D9842009B077 +:10DEC0002FE70698C6B2300603D5B00601D5062066 +:10DED000F5E7B9F80620521C92B2A9F80620BBF16A +:10DEE000000F01D0ABF80020B00602D5C4F80880BE +:10DEF0000AE0B9F808201A4492B2A9F80820D9F823 +:10DF00000000891A0844A0602246FE200699FFF707 +:10DF100087FEE77025712078390A61F301002A0A2B +:10DF2000A17840F0040062F30101A17020709AF81A +:10DF300002006071BAF80000E08000252573300609 +:10DF400002D599F80A7000E00127B00601D54FF01C +:10DF500000084E4600244FF007090FE0CDE90258B3 +:10DF60000195CDF800900495F1882046129B089AFF +:10DF7000FFF7C3FE0028A2D1641CE4B2BC42EDD37B +:10DF800000209CE700B5FFF7ADFE03490C308A88FE +:10DF9000904203D9842000BD00060020CA8808688A +:10DFA00002EB420300EB8300521C037823F00403CE +:10DFB0000370CA80002101730846ECE72DE9F047A1 +:10DFC000804600F0FBF9070005D000264446F74DD7 +:10DFD00040F2012916E00120BDE8F087204600F05C +:10DFE000EDF90278C17802F0030241EA0222B2FBA5 +:10DFF000F9F309FB13210068FFF7D3FD3044641CDB +:10E0000086B2A4B2E988601E8142E7DCA8F1010073 +:10E01000E8802889801B288100203870DCE710B553 +:10E02000144631B1491E218005F006FFA070002082 +:10E0300010BD012010BD70B50446DC48C1880368DE +:10E0400001E0401C20802088884207D200EB40027B +:10E0500013EB820202D015786D07F2D580B28842A8 +:10E0600016D2AAB15079A072D08820819178107907 +:10E0700001F0030140EA0120A081A078E11CFFF734 +:10E08000A1FD20612088401C2080E080002070BD20 +:10E090000A2070BD0121018270472DE9FF4F85B034 +:10E0A0004FF6FF798246A3F8009048681E460D4659 +:10E0B00080788DF8060048680088ADF804000020DC +:10E0C0008DF80A00088A0C88A04200D304462C82EE +:10E0D00051E03878400708D4641C288AA4B2401C58 +:10E0E000288208F10100C0B246E0288A401C28823C +:10E0F000781D6968FFF70EFDD8BB3188494501D10D +:10E10000601E30803188A1EB080030806888A04212 +:10E1100038D3B878397900F0030041EA002801A922 +:10E12000781DFFF7F7FC20BB298949452ED0002236 +:10E1300039460798FFF706FDD8B92989414518D116 +:10E14000E9680391B5F80AC0D7F808B05046CDF891 +:10E1500000C005F0DCFFDDF800C05A460CF1070CEA +:10E160001FFA8CFC43460399CDF800C005F08DFBE7 +:10E1700060B1641CA4B200208046204600F01EF965 +:10E180000700A6D1641E2C820A2098E67480787954 +:10E19000B071F888B0803978F87801F0030140EA6E +:10E1A00001207081A6F80C80504605F045FE3A46E5 +:10E1B00006F10801FFF706FD306100207FE62DE93A +:10E1C000FF4F87B081461C469246DDF860B0DDF80F +:10E1D0005480089800F0F2F8050002D02878400733 +:10E1E00002D401200BB09CE5484605F025FE2978B5 +:10E1F000090605D56D49897A814201D88720F1E762 +:10E20000CAF309062A4601A9FFF7DCFC0746149861 +:10E2100007281CD000222946FFF794FC0028E1D1F2 +:10E220002878400613D501208DF808000898ADF82D +:10E230000C00BDF80400ADF80E00ADF81060ADF8AC +:10E24000124002A94846F8F7E3FF0028CAD129780E +:10E25000E87801F0030140EA0121AA78287902F068 +:10E26000030240EA0220564507D0B1F5007F04D9E9 +:10E27000611E814201DD0B20B4E7864201D90720EF +:10E28000B0E7801B85B2A54200D92546BBF1000F3F +:10E2900001D0ABF80050179818B1B9192A4600F010 +:10E2A000CCF8B8F1000F0DD03E4448464446169FC6 +:10E2B00005F03FFF2146FF1DBCB232462B460094BD +:10E2C00005F04DFB00208DE72DE9F04107461D4686 +:10E2D0001646084600F072F8040002D02078400785 +:10E2E00001D40120D3E4384605F0A6FD21780906C3 +:10E2F00005D52E49897A814201D88720C7E4224674 +:10E300003146FFF75FFC65B12178E07801F0030149 +:10E3100040EA0120B0F5007F01D8012000E0002094 +:10E3200028700020B3E42DE9F04107461D4616464B +:10E33000084600F043F8040002D02078400701D4DA +:10E340000120A4E4384605F077FD2178090605D5BB +:10E350001649897A814201D8872098E422463146BD +:10E36000FFF75EFCFF2D14D02178E07801F0030266 +:10E3700040EA022040F20122B0FBF2F302FB13005C +:10E3800015B900F2012080B2E070000A60F30101CB +:10E39000217000207BE410B50C4600F00FF810B19E +:10E3A0000178490704D4012010BD000000060020B8 +:10E3B000C18821804079A0700020F5E70749CA880C +:10E3C000824209D340B1096800EB40006FF00B02B4 +:10E3D00002EB8000084470470020704700060020D0 +:10E3E00070B504460D4621462B460AB9002070BD83 +:10E3F00001E0491C5B1C501E021E03D008781E78E9 +:10E40000B042F6D008781E78801BF0E730B50C4695 +:10E4100001462346051B954206D202E0521E9D5C32 +:10E420008D54002AFAD107E004E01D780D70491CD4 +:10E430005B1C521E002AF8D130BDF0B50E460146D5 +:10E44000334680EA030404F00304B4B906E002B9D9 +:10E45000F0BD13F8017B01F8017B521E01F00307A8 +:10E46000002FF4D10C461D4602E080CD80C4121F5F +:10E47000042AFAD221462B4600BF04E013F8014BD0 +:10E4800001F8014B521E002AF8D100BFE0E7F0B5B9 +:10E490000C460146E6B204E002B9F0BD01F8016B9A +:10E4A000521E01F00307002FF6D10B46E5B245EAF4 +:10E4B000052545EA054501E020C3121F042AFBD2C9 +:10E4C000194602E001F8016B521E002AFAD100BF82 +:10E4D000E3E7000010B509F0A0FDF4F7F9F909F041 +:10E4E000E7FBBDE8104009F0AFBC302834BF012085 +:10E4F00000207047202834BF4FF0A0420C4A01236F +:10E5000000F01F0003FA00F0002914BFC2F80C0548 +:10E51000C2F808057047202834BF4FF0A0410449D5 +:10E5200000F01F00012202FA00F0C1F81805704740 +:10E530000003005070B50346002002466FF02F051F +:10E540000EE09C5CA4F130060A2E02D34FF0FF309F +:10E5500070BD00EB800005EB4000521C2044D2B29D +:10E560008A42EED370BD30B50A230BE0B0FBF3F462 +:10E5700003FB1404B0FBF3F08D183034521E05F881 +:10E58000014CD2B2002AF1D130BD30B500234FF694 +:10E59000FF7510E0040A44EA002084B2C85C6040C1 +:10E5A000C0F30314604005EA00344440E0B25B1C51 +:10E5B00084EA40109BB29342ECD330BD2DE9F04188 +:10E5C000FE4B0026012793F864501C7893F868C02E +:10E5D000B8B183F89140A3F8921083F8902083F8A3 +:10E5E0008E70BCF1000F0CBF83F8946083F89450D8 +:10E5F000F3488068008805F08AFDBDE8F04105F029 +:10E6000021BA4FF6FF7083F89140A3F8920083F887 +:10E61000902083F88E70BCF1000F14BF83F89450E3 +:10E6200083F89460BDE8F0812DE9F041E44D29685C +:10E6300091F89C200024012A23D091F89620012AE9 +:10E6400030D091F86C301422DC4E0127012B32D0EF +:10E6500091F88E30012B4FD091F8A620012A1CBFD3 +:10E660000020BDE8F08144701F2200F8042B222214 +:10E67000A731FFF7E2FE286880F8A6400120BDE838 +:10E68000F08144701B220270D1F89D204260D1F8C5 +:10E69000A120826091F8A520027381F89C4001209E +:10E6A000BDE8F081447007220270D1F898204260E2 +:10E6B00081F89640E2E78046447000F8042B20225F +:10E6C0006E31FFF7BAFE88F80870286880F86C4051 +:10E6D00090F86E000028D1D1B6F87000A6F8980026 +:10E6E000A868417B86F89A1086F89670008805F035 +:10E6F0000EFD05F0B6F9C1E791F86C30012B0BD097 +:10E70000447017220270D1F890204260B1F8942032 +:10E71000028181F88E40B1E78046447000F8042BF6 +:10E7200020226E31FFF789FE88F80870286880F88B +:10E730006C4090F86E000028A0D1CDE7A04800689A +:10E7400090F86C10002914BFB0F870004FF6FF70FD +:10E75000704770B59A4C06462068002808BFFFDF56 +:10E760000025206845706660002808BFFFDF20682C +:10E77000417800291CBFFFDF70BDCC220021FFF7CC +:10E7800086FE2068FF2101707F2180F83810132158 +:10E790004184282180F86910012180F85C1080F8FC +:10E7A00061500AF0C1F9BDE8704009F0AEBA844981 +:10E7B0000968097881420CBF012000207047804819 +:10E7C000006890F82200C0F3400070477C48006861 +:10E7D00090F8220000F0010070477948006890F836 +:10E7E0002200C0F3001070472DE9F0437448002464 +:10E7F000036893F82400B3F822C0C0F38001C0F38B +:10E800004002114400F001000844CCF3001121B390 +:10E81000BCF1100F02BF6B4931F81000BDE8F08366 +:10E82000BCF1120F18BFBCF1130F0ED0BCF1150FC5 +:10E830001EBFFFDF2046BDE8F0830021624A32F8A8 +:10E84000102010FB0120BDE8F083604A002132F85F +:10E85000102010FB0120BDE8F08393F85E2093F8B0 +:10E860005F102E264FF47A774FF014084FF04009CE +:10E87000022A04BF4AF2D745B5FBF7F510D0012AAA +:10E8800004BF4AF22F75B5FBF7F510D04AF62315F1 +:10E89000B5FBF7F5082A08BF4E4613D0042A18D056 +:10E8A0002646082A0ED0042A13D0022A49D004F1A1 +:10E8B0002806042A0FD0082A1CBF4FF01908082286 +:10E8C00004D00AE04FF0140806F5A8764FF0400295 +:10E8D00003E006F5A8764FF0100218FB026212FB67 +:10E8E0000052C0EB00103A4D00EB800005EB8000B9 +:10E8F00010441CF0010F4FF4C8724FF4BF7504BFF1 +:10E90000CCF34006002E65D0CCF3400600F5A57090 +:10E91000EEB1082904BF174640260CD0042904BFD5 +:10E920002F46102607D0022907BF04F11807042636 +:10E9300004F12807082606EB860808EB86163E44F5 +:10E940001BE004F118064FF019080422C5E7082956 +:10E9500004BF164640270CD0042904BF2E461027BA +:10E9600007D0022907BF04F11806042704F128067E +:10E97000082707EB871706EB8706304400F19C0653 +:10E9800093F8690001F00C07002F08BF0020304405 +:10E9900018BF00F5416027D1082904BF164640275B +:10E9A0001BD0042904BF2E46102716D0022906BF0B +:10E9B00004F11806042704F128060CE00C060020D8 +:10E9C00068000020DC610200E4610200D461020002 +:10E9D000D4FEFFFF64E018BF0827C7EBC70707EBAB +:10E9E000470706EB4706304498301CF0010F17D05C +:10E9F000082908BF40210CD0042904BF2A46102151 +:10EA000007D0022907BF04F11802042104F12802EB +:10EA1000082101EB410303EB0111114408443BE0E1 +:10EA2000082904BF944640260CD0042904BFAC46F4 +:10EA3000102607D0022907BF04F1180C042604F1A0 +:10EA4000280C082606EB8616B3F840300CEB860C33 +:10EA50006044EB2B20D944F2552C0B3303FB0CF311 +:10EA60009B0D082907D0042902D0022905D008E00F +:10EA70002A46102108E0402106E004F11802042192 +:10EA800002E004F12802082101EB811102EB81016F +:10EA900001F5A57103FB010000F5B470BDE8F0833A +:10EAA00000F5A570082904BF944640260CD004291F +:10EAB00004BFAC46102607D0022907BF04F1180C8A +:10EAC000042604F1280C082606EB8616B3F8483015 +:10EAD0000CEB860C6044EB2BDED944F2552C0B3347 +:10EAE00003FB0CF39B0D0829C5D00429C0D00229D3 +:10EAF000C7D1C2E7FE4840F271210068806A4843EE +:10EB00007047FB48006890F83700002818BF0120C4 +:10EB1000704710B5F74C207B022818BF032808D196 +:10EB2000207D04F115010EF0E6FE08281CBF01202F +:10EB300010BD207B002816BF022800200120BDE860 +:10EB400010400AF021BDEB4908737047E849096895 +:10EB500081F8300070472DE9F047E54C2168087BCB +:10EB6000002816BF022800200120487301F10E0181 +:10EB70000AF0F4FC2168087B022816BF0328012252 +:10EB8000002281F82F204FF0080081F82D00487BEB +:10EB900001F10E034FF001064FF00007012804BFFA +:10EBA0005B7913F0C00F0AD001F10E03012804D1E4 +:10EBB000587900F0C000402801D0002000E001207A +:10EBC00081F82E00002A04BF91F8220010F0040FF3 +:10EBD00007D0087D01F115010EF08DFE216881F846 +:10EBE0002D002068476007F0BFFA2168C14D4FF043 +:10EBF0000009886095F82D000EF089FE804695F892 +:10EC00002F00002818BFB8F1000F04D095F82D0090 +:10EC10000EF0B1FC68B195F8300000281CBF95F8E3 +:10EC20002E0000281DD0697B05F10E0001290ED0B1 +:10EC300012E06E734A4605F10E0140460AF0E4FC0C +:10EC400095F82D1005F10E000EF063FF09E04079F4 +:10EC500000F0C000402831D0394605F10E000AF01E +:10EC60000BFD2068C77690F8220010F0040F08BF53 +:10EC7000BDE8F087002795F82D000EF017FD050080 +:10EC800008BFBDE8F087102102F0C2F8002818BFC5 +:10EC9000BDE8F08720683A4600F11C01C676284698 +:10ECA0000AF0B2FC206800F11C0160680FF08EF8D9 +:10ECB0006068BDE8F04701210FF0A3B80EF066FFD1 +:10ECC0004A4605F10E010AF09FFCCAE7884A12681D +:10ECD000137B0370D2F80E000860508A888070475A +:10ECE00078B584490446824E407B087332682078A8 +:10ECF00010706088ADF8000080B200F00101C0F330 +:10ED0000400341EA4301C0F3800341EA8301C0F3B9 +:10ED1000C00341EAC301C0F3001341EA0311C0F389 +:10ED2000401341EA4311C0F3801041EA801050843F +:10ED3000E07D012808BF012507D0022808BF022571 +:10ED400003D0032814BFFFDF0825306880F85E5029 +:10ED5000607E012808BF012507D0022808BF0225D0 +:10ED600003D0032814BFFFDF0825316881F85F5006 +:10ED700091F83500012829D0207B81F82400488CA7 +:10ED80001D280CBF002060688862607D81F8370014 +:10ED9000A07B002816BF0228002001200875D4F8A7 +:10EDA0000F00C1F81500B4F81300A1F81900A07EF7 +:10EDB00091F86B2060F3071281F86B20E07E012848 +:10EDC00018BF002081F83400002078BD91F85E2043 +:10EDD0000420082A08BF81F85E00082D08BF81F8CA +:10EDE0005F00C9E742480068408CC0F3001131B1B0 +:10EDF000C0F38000002804BF1F20704702E0C0F36A +:10EE0000400109B10020704710F0010F14BFEE203F +:10EE1000FF20704736480068408CC0F3001119B1DC +:10EE2000C0F3800028B102E0C0F3400008B1002028 +:10EE30007047012070472E49002209684A664B8CB2 +:10EE40001D2B0CBF81F8682081F8680070470023F3 +:10EE5000274A126882F85D30D164A2F85000012080 +:10EE600082F85D007047224A0023126882F85C3005 +:10EE7000A2F858000120516582F85C0070471C49D7 +:10EE8000096881F8360070471949096881F86100FE +:10EE900070471748006890F961007047144800688F +:10EEA00090F82200C0F3401070471148006890F8B5 +:10EEB0002200C0F3C0007047012070470C48006872 +:10EEC00090F85F00704770B509F018FE09F0F7FD83 +:10EED00009F0C0FC09F06CFD054C2068416E491C2E +:10EEE000416690F83300002558B109F01DFE03E09B +:10EEF000680000200C06002008F007FF206880F85A +:10EF000033502068457090F8391021B1BDE8704049 +:10EF100004200AF0AEBF90F86810D9B1406E81426B +:10EF200018D804200AF0A5FF206890F8220010F0FD +:10EF3000010F07D0A06843220188BDE8704001207E +:10EF4000FFF73CBBBDE8704043224FF6FF71002045 +:10EF5000FFF734BBBDE8704000200AF08ABF2DE9FE +:10EF6000F04782B00F468146FE4E4FF000083068F1 +:10EF7000458C15F0030F10D015F0010F05F00200BD +:10EF800005D0002808BF4FF0010806D004E0002893 +:10EF900018BF4FF0020800D1FFDF4FF0000A5446BF +:10EFA00015F0010F05F002000DD080B915F0040F27 +:10EFB0000DD04AF00800002F1CBF40F0010040F0C7 +:10EFC00002044DD09EE010B115F0040F0DD015F0E5 +:10EFD000070F10D015F0010F05F0020043D00028F4 +:10EFE00008BF15F0040F34D04AE0002F18BF4AF0D4 +:10EFF000090444D141E037B14AF00800044615F055 +:10F00000200F1BD07EE0316805F02002B1F84800E7 +:10F01000104308BF4AF0010474D04AF018000446B7 +:10F0200015F0200F6ED191F85E1011F00C0118BF91 +:10F030000121C94361F30000044663E0316891F89F +:10F040005E1011F00C0118BF012161F300000446AD +:10F0500058E04AF00800002F18BF40F0010451D1D9 +:10F0600040F010044EE0002818BF15F0040F07D040 +:10F07000002F18BF4AF00B0444D14AF0180441E0B5 +:10F0800015F0030F3DD115F0040F3AD077B1306879 +:10F090004AF0080490F85E0010F00C0118BF01213E +:10F0A00061F3410415F0200F24D02BE0306805F007 +:10F0B0002002B0F84810114308BF4AF0030421D0E1 +:10F0C0004AF0180415F0200F0AD000BF90F85E0037 +:10F0D00010F00C0018BF0120C04360F3410411E0A0 +:10F0E00090F85E1011F00C0118BF0121C94361F3C3 +:10F0F0000004EBE710F00C0018BF012060F30004DF +:10F1000000E0FFDF15F0400F1CD0CFB93168B1F837 +:10F110004800002804BF488C10F0010F0BD110F0FC +:10F12000020F08BF10F0200F05D115F0010F08BF26 +:10F1300015F0020F04D091F85E0010F00C0F01D111 +:10F1400044F040047068A0F800A0017821F020018C +:10F1500001704FF007010EF005FE414670680EF099 +:10F16000F8FF214670680FF000F814F0010F0CD082 +:10F170004FF006034FF000027B4970680EF0CFFF9E +:10F180003068417B70680EF02FFE14F0020F18D02B +:10F19000D6E90010B9F1000F4FF006034FF001025D +:10F1A00007D01C310EF0BBFF012170680EF029FE64 +:10F1B00007E015310EF0B3FF3068017D70680EF086 +:10F1C00020FE14F0040F18BFFFDF14F0080F19D051 +:10F1D000CDF800A03068BDF800200223B0F86A1016 +:10F1E00061F30B02ADF8002090F86B0003220109D7 +:10F1F0009DF8010061F307108DF801006946706801 +:10F200000EF08DFF012F62D13068B0F84810E1B3E5 +:10F2100090F82200C0F34000B8BB70680EF095FF74 +:10F22000401CC7B23068C7F1FF05B0F84820B0F8FD +:10F230005A10511AA942B8BF0D46AA423BD990F8BC +:10F24000220010F0010F36D144F0100421467068FE +:10F250000EF08BFFF81CC0B2ED1E284482B230685D +:10F26000B0F86A10436EC1F30B0151FA83F190F8C4 +:10F2700060303E4F1944BC460023E1FB07C31B0925 +:10F280006FF0240C03FB0C1100E020E080F860100C +:10F2900090F85F00012101F01FF90090BDF8000017 +:10F2A0009DF80210032340EA01400190042201A9C5 +:10F2B00070680EF034FF3068AAB2416C70680EF0CE +:10F2C00082FF3068B0F85A102944A0F85A1014F0A0 +:10F2D000400F06D0D6E900100123062261310EF05E +:10F2E0001EFF14F0200F18BFFFDF0020002818BFFA +:10F2F000FFDF02B0BDE8F0872DE9F043194C89B07B +:10F300002068002808BFFFDF20684178002944D129 +:10F310000178FF2941D0002680F83160A0F85A60BA +:10F32000867080F83960304609F062FB104802AD03 +:10F3300000F1240191E80E1085E80E10D0E90D10BF +:10F34000CDE9061002A809F041FB08F0BCFF2068D7 +:10F3500090F9610009F090F8064809F093F8064822 +:10F360000CE00000680000201A06002053E4B36E91 +:10F37000C8610200D0610200CD61020009F012FBF9 +:10F38000606809F038FB206890F8240010F0010F45 +:10F3900007D0252009F07EF80AE009B00C20BDE86E +:10F3A000F08310F0020F18BF262069D009F072F820 +:10F3B000206890F85E10252008F043FF206880F850 +:10F3C0002C6009F00FFB206890F85E10002009F017 +:10F3D00028F90F21052008F0F8FF206890F82E107A +:10F3E000002901BF90F82F10002990F8220010F09A +:10F3F000040F74D006F0B8FE0546206829468068E0 +:10F4000007F0AAFBDFF82884074690FBF8F008FB1A +:10F4100010704142284606F08EFB2168886097FBF9 +:10F42000F8F04A68104448600EF062FA014620681D +:10F43000426891426ED8C0E90165FE4D4FF0010867 +:10F4400095F82D000EF063FA814695F82F000127FC +:10F45000002818BFB9F1000F04D095F82D000EF068 +:10F460008AF8A0B195F8300000281CBF95F82E004E +:10F47000002824D0687B05F10E01012815D019E081 +:10F4800010F0040F14BF2720FFDF8FD190E73A461A +:10F490006F7305F10E0148460AF0B6F895F82D1085 +:10F4A00005F10E000EF035FB09E0487900F0C000D0 +:10F4B000402815D0414605F10E000AF0DDF820681D +:10F4C00090F8220010F0040F24D095F82D000EF0D3 +:10F4D000EDF805001ED0102101F09AFC40B119E0B2 +:10F4E0000EF054FB3A4605F10E010AF08DF8E6E7FE +:10F4F00020683A4600F11C01C77628460AF084F8D5 +:10F50000206800F11C0160680EF060FC0121606859 +:10F510000EF077FC2068417B0E3008F038FF206841 +:10F5200090F85C1061B3B0F85810A0F84810416D25 +:10F53000416490F82210C1F30011F1B9B0F86A00EB +:10F540000221C0F30B05ADF80050684607F0B0FF8C +:10F5500028B1BDF80000C0F30B00A84204D1BDF8EB +:10F560000000401CADF800002168BDF80000B1F8B3 +:10F570006A2060F30B02A1F86A20206880F85C60C2 +:10F58000206890F85D1039B1B0F85010A0F8401024 +:10F59000C16CC16380F85D60B0F86A10426EC1F35F +:10F5A0000B0151FA82F190F86020DFF88CC211440F +:10F5B00063460022E1FB0C3212096FF0240302FBC8 +:10F5C000031180F860100EF00CFA032160680EF051 +:10F5D00090FA216881F8330009B00020BDE8F0837B +:10F5E0009649886070472DE9F043944C83B02268B7 +:10F5F00092F831303BB1508C1D2808BFFFDF03B0BB +:10F60000BDE8F0435FE401260027F1B1054692F81A +:10F61000600008F03FFF206890F85F10FF2008F0BE +:10F6200010FE20684FF4A57190F85F20002009F0CB +:10F63000D4F8206890F8221011F0030F00F02C810C +:10F64000002D00F0238100F027B992F822108046A7 +:10F65000D07EC1F30011002956D0054660680780AE +:10F66000017821F020010170518C132937D01FDC63 +:10F67000102908BF022144D0122908BF062140D01A +:10F68000FFDF6C4D606805F10E010EF091FB697BA8 +:10F6900060680EF0A9FB2068418C1D2918BF152950 +:10F6A00063D0B0F84820416C60680EF0B6FB5CE0B7 +:10F6B000152918BF1D29E3D14FF001010EF052FBAF +:10F6C0006068017841F020010170216885B11C312A +:10F6D0000EF07CFB012160680EF093FBD1E7002166 +:10F6E0000EF040FB6068017841F020010170C8E72E +:10F6F00015310EF06BFB2068017D60680EF081FB18 +:10F70000BFE70EF02FFBBCE70021FFF728FC606885 +:10F71000C17811F03F0F28D0017911F0100F24D0DB +:10F720000EF01EFB2368024693F82410C1F38000FC +:10F73000C1F3400C604401F00101084493F82C101F +:10F74000C1F3800CC1F34005AC4401F001016144F8 +:10F75000401AC1B293F85E0000F0BEFE0090032391 +:10F760000422694660680EF0DAFC2068002590F8F3 +:10F77000241090F82C0021EA000212F0010F18BFAB +:10F7800001250ED111F0020F04D010F0020F08BFB6 +:10F79000022506D011F0040F03D010F0040F08BFAB +:10F7A0000425B8F1000F2BD0012D1BD0022D08BF6E +:10F7B00026201BD0042D14BFFFDF272016D0206881 +:10F7C00090F85E10252008F03CFD206890F822108B +:10F7D000C1F3001169B101224FF49671002008F0C5 +:10F7E000FCFF0DE0252008F055FEE8E708F052FE8A +:10F7F000E5E790F85E204FF49671002008F0EDFFE9 +:10F80000206890F82C10294380F82C1090F82420C0 +:10F8100032EA01011CD04670418C13292BD026DC22 +:10F82000102904BF03B0BDE8F083122923D007E0FC +:10F8300040420F000C06002053E4B36E6800002025 +:10F84000C1F30010002818BFFFDF03B0BDE8F0834C +:10F85000418C1D2908BF80F82C70DCD0C1F3001149 +:10F86000002914BF80F8316080F83170D3E7152982 +:10F8700018BF1D29DBD190F85E2003B04FF00101C5 +:10F88000BDE8F043084609F094B900BF90F85F2046 +:10F890000121084609F08DF92168002DC87E7CD031 +:10F8A0004A8C3D46C2F34000002808BF47F00805D7 +:10F8B00012F0400F18BF45F04005002819BFD1F8DD +:10F8C0003C90B1F84080D1F84490B1F8488060682D +:10F8D000072107800EF046FA002160680EF039FC1F +:10F8E000294660680EF041FC15F0080F17D020681B +:10F8F000BDF800100223B0F86A2062F30B01ADF8E6 +:10F90000001090F86B00032201099DF8010061F3DB +:10F9100007108DF80100694660680EF000FC606811 +:10F920000EF0DCFA2168C0F1FE00B1F85A20A8EB15 +:10F9300002018142A8BF0146CFB2D019404544D24E +:10F9400045F0100160680EF010FC60680EF0C6FA19 +:10F950002168C0F1FE00B1F85A10A8EB0101814204 +:10F96000A8BF0146CFB260680EF0EFFB3844421CDE +:10F970002068B0F86A10436EC1F30B0151FA83F1AD +:10F9800090F86030FE4D1944AC460023E1FB05C3FE +:10F990004FEA131C6FF0240300E03CE00CFB031162 +:10F9A00080F8601090F85F00012100F095FD009054 +:10F9B000BDF800009DF80210032340EA01400190C9 +:10F9C000042201A960680EF0AAFB216891F82200C8 +:10F9D00010F0400F05D001230622613160680EF05F +:10F9E0009EFB20683A46B0F85A0000EB09016068B7 +:10F9F0000EF0E9FB2068B0F85A103944A0F85A100C +:10FA000009F0BFFC002818BFFFDF20684670867031 +:10FA100003B0BDE8F0830121FFF7A1FAF0E7D94870 +:10FA200010B50068417841B90078FF2805D0002161 +:10FA30000846FFF7D8FD002010BD09F05FF809F077 +:10FA40003EF808F007FF08F0B3FF0C2010BD2DE9C9 +:10FA5000F041CC4D0446174628680E4690F86C00DD +:10FA6000002818BFFFDF2868002F80F86E7018BFCD +:10FA7000BDE8F0812188A0F870106188A0F8861098 +:10FA8000A188A0F88810E188A0F88A1094F888115D +:10FA900080F88C1090F82F10002749B1427B00F1BC +:10FAA0000E01012A04D1497901F0C001402935D065 +:10FAB00090F8301041B1427B00F10E01012A04BFE1 +:10FAC000497911F0C00F29D000F17A00F3F794FAC8 +:10FAD0006868FF2E0178C1F380116176D0F80310B9 +:10FAE000C4F81A10B0F80700E08328681ED0C0F8E8 +:10FAF0008010E18BA0F8841000F17402511E304692 +:10FB00000DF014FF002808BFFFDF286890F873107D +:10FB100041F0020180F87310BDE8F081D0F80E10BA +:10FB2000C0F87A10418AA0F87E10D1E7C0F8807042 +:10FB3000A0F88470617E80F87310D4F81A104167C1 +:10FB4000E18BA0F87810BDE8F08170B58D4C0125EF +:10FB5000206890F82200C0F3C00038B13C22FF2199 +:10FB6000A068FFF774FF206880F86C50206890F858 +:10FB7000220010F0010F1CBFA06801884FF03C026A +:10FB800012BF01204FF6FF710020FEF717FD20681D +:10FB900080F8395070BD7B49096881F832007047A0 +:10FBA0002DE9F041774C0026206841780127354641 +:10FBB000012906D0022901D003297DD0FFDFBDE84D +:10FBC000F081817802250029418C46D0C1F34002A2 +:10FBD000002A08BF11F0010F6FD090F85F204FF09E +:10FBE00001014FF0000008F0E4FF216891F82200C5 +:10FBF000C0F34000002814BF0C20222091F85F10B1 +:10FC000008F01FFB2068457090F8330058B108F0E9 +:10FC100068F8206890F85F0010F00C0F0CBF4020CF +:10FC2000452008F077FF206890F83400002818BFBE +:10FC300008F08FFF216891F85F0091F8691010F0CB +:10FC40000C0F08BF0021962008F0F6FE09F090FB8B +:10FC5000002818BFFFDFBDE8F081C1F3001282B1B8 +:10FC600010293FD090F8330020B108F03AF8402036 +:10FC700008F050FF206890F8221011F0040F36D0E1 +:10FC800043E090F8242090F82C309A422AD1B0F822 +:10FC90004800002808BF11F0010F05D111F0020F34 +:10FCA00008BF11F0200F6FD04FF001014FF000009E +:10FCB000FFF799FC206801E041E035E0418C11F04C +:10FCC000010F04BFC1F34001002907D1B0F85A1059 +:10FCD000B0F84820914218BFBDE8F08180F831703B +:10FCE000BDE8F081BDE8F041002101207BE490F8FF +:10FCF0003710012914BF0329102646F00E0190F891 +:10FD00005E204FF0000008F054FF206890F83400A7 +:10FD1000002818BF08F01DFF0021962008F08CFE77 +:10FD200020684570BDE8F081B0F85A10B0F848007E +:10FD3000814242D0BDE8F0410121084653E4817878 +:10FD4000D9B1418C11F0010F22D080F86C7090F87D +:10FD50006E20B0F870100120FEF730FC206845706E +:10FD600008F0CCFE08F0ABFE08F074FD08F020FEB1 +:10FD7000BDE8F04103200AF07CB88178012004E05E +:10FD800053E4B36E6800002017E0BDE8F0412AE4B8 +:10FD900011F0020F04BFFFDFBDE8F081B0F85A1088 +:10FDA000B0F84000814208D001210846FFF71BFC53 +:10FDB000216803204870BDE8F081BDE8F041FFF7FD +:10FDC00082B8FFF780B810B5FE4C206890F8341068 +:10FDD00049B1383008F0CCFE18B921687F2081F88D +:10FDE000380008F0ACFE206890F8330018B108F035 +:10FDF0009BFE07F08AFF0AF02EFCA8B1206890F85D +:10FE00002210C1F3001179B14078022818BFFFDF3A +:10FE100000210120FFF7E7FB2068417800291EBF81 +:10FE200040780128FFDF10BDBDE81040FFF74BB858 +:10FE30002DE9F047E34C0F4680462168B8F1030FE7 +:10FE4000488C08BFC0F3400508D000F0010591F8C8 +:10FE50003200002818BF4FF0010901D14FF000090E +:10FE600008F00CFB0646B8F1030F0CBF4FF0020878 +:10FE70004FF0010835EA090008BFBDE8F0872068A7 +:10FE800090F8330068B10DF08FFD38700146FF28FF +:10FE900007D06068C01C0DF060FD38780DF091FD52 +:10FEA000064360680178C1F3801221680B7D9A4295 +:10FEB00008D10622C01C1531FEF792FA002808BFAF +:10FEC000012000D000203978FF2906D0C8B9206869 +:10FED00090F82D00884216D113E0A0B1616811F8A6 +:10FEE000030BC0F380100DF006FD05460DF061FE1A +:10FEF00038B128460DF0DAFB18B1102100F088FF68 +:10FF000008B1012000E00020216891F8221011F0D2 +:10FF1000040F01D0F0B11AE0CEB9AB4890F8370029 +:10FF2000002818BF404515D1616811F8030BC0F3D4 +:10FF300080100DF0E0FC04460DF03BFE38B1204689 +:10FF40000DF0B4FB18B1102100F062FF10B10120D8 +:10FF5000BDE8F0870020BDE8F0872DE9F04F994D0E +:10FF6000044683B0286800264078022818BFFFDFC7 +:10FF700028684FF07F0B90F8341049B1383008F002 +:10FF8000F7FD002804BF286880F838B008F0D7FDD6 +:10FF900068680DF009FF8046002C00F0458208F0EB +:10FFA00010FA002800F04082012400274FF0FF09DA +:10FFB000B8F1050F1ED1686890F8240000F01F000A +:10FFC000102817D9286890F8360098B18DF800905D +:10FFD00069460520FFF72CFF002800F025822868DD +:10FFE00080F8A64069682222A730C91CFEF725FACE +:10FFF00000F01ABA68680EF062F8002800F0148267 +:020000040001F9 +:100000004046DFF8C4814FF0030A062880F02182C1 +:10001000DFE800F0FCFCFC03FCFB8DF80090694677 +:100020000320FFF705FF002800F0F180296891F810 +:10003000340010B191F89C00D8B12868817801296A +:100040004DD06868042107800DF08CFE08F10E0188 +:1000500068680DF0ADFE98F80D1068680DF0C4FEEC +:100060002868B0F84020C16B68680DF0FAFE00F017 +:1000700063B99DF8000081F89C400A7881F89D20C2 +:10008000FF280FD001F19F029E310DF04FFC002898 +:1000900008BFFFDF286890F89E1041F0020180F849 +:1000A0009E100DE068680278C2F3801281F89E20ED +:1000B000D0F80320C1F89F20B0F80700A1F8A300F2 +:1000C000286800F1A50490F838007F2808BFFFDFFA +:1000D000286890F83810217080F838B0ADE790F8B3 +:1000E00022000721C0F3801938480479686869F351 +:1000F000861407800DF036FE002168680EF029F89E +:10010000214668680EF031F80623002208F10E013E +:1001100068680EF004F82868417B68680DF064FE9A +:1001200068680DF0DBFE2968B1F84020C0F1FE01DF +:100130008A42B8BF1146CFB2BA423CD9F81EC7B204 +:1001400044F0100B594668680EF00FF868680DF01F +:10015000FCFF384400F101082868B0F86A10426ECC +:10016000C1F30B0151FA82F190F86020184C0A4457 +:10017000A4460023E2FB04C319096FF0240301FB2A +:10018000032180F8601090F85F004246012100F0E2 +:10019000A3F90190BDF804009DF80610032340EA7E +:1001A00001400290042202A968680DF0B8FF594688 +:1001B00068680DF0DAFFB9F1000F0FD0D5E9001033 +:1001C000012307E0680000200C060020C86102003F +:1001D00053E4B36E062261310DF0A1FF28683A4660 +:1001E000C16B68680DF0EFFF2868A0F85A70B0F88E +:1001F00040108F420CBF0121002180F8311009F01E +:10020000C0F8002818BFFFDF96E007E021E128686A +:100210008078002840F00A8100F006B98DF800903F +:1002200068680178C1F38019D0F803100191B0F823 +:100230000700ADF8080069460520FFF7F9FD002822 +:1002400028687DD0817800297CD090F85FB0D5E90E +:100250000104D0F80F10C4F80E10B0F8131061822A +:10026000417D2175817D6175B0F81710E182B0F88C +:1002700019106180B0F81B10A180B0F81D10E1804A +:1002800000F11F0104F1080015F085FE686890F880 +:10029000241001F01F01217690F82400400984F811 +:1002A000880184F864B084F865B01BF00C0F0CBFB3 +:1002B0000021012104F130000EF0ABF92868002282 +:1002C00090F8691084F8661090F8610084F867006F +:1002D0009DF80010A868FFF7BAFB022009F0C9FDDD +:1002E000B2480DF1040B08210468686807800DF01E +:1002F00039FD002168680DF02CFF214668680DF07B +:1003000034FF0623002208F10E0168680DF007FF94 +:100310002868417B68680DF067FD494668680DF004 +:1003200070FD06230122594668680DF0F8FE09F0B9 +:1003300028F8002818BFFFDF286880F801A077E0C0 +:100340006DE0FFE76868D5F808804FF00109027892 +:1003500098F80D10C2F34012114088F80D10D0F833 +:100360000F10C8F80E10B0F81310A8F81210417D45 +:1003700088F81410817D88F81510B0F81710A8F8C7 +:100380001610B0F81910A8F80210B0F81B10A8F851 +:100390000410B0F81D10A8F8061000F11F0108F1B4 +:1003A000080015F0F8FD686890F8241001F01F01AE +:1003B00088F8181090F824000021400988F8880176 +:1003C00088F8649088F8659008F130000EF021F903 +:1003D0002868002290F8691088F8661090F861008B +:1003E00088F867009DF80010A868FFF730FB2868C0 +:1003F00080F86C4090F86E20B0F870100120FEF785 +:10040000DDF82868477008F079FB08F058FB08F021 +:1004100021FA08F0CDFA012009F02BFD08E090F850 +:100420002200C0F3001008B1012601E0FEF74BFDE9 +:10043000286890F8330018B108F076FB07F065FCE7 +:1004400096B10AF008F960B100210120FFF7CBF85E +:1004500013E0286890F82200C0F300100028E5D0CF +:10046000E2E7FEF730FD08E028688178012904D131 +:1004700090F85F10FF2007F0E4FE2868417800291B +:1004800019BF4178012903B0BDE8F08F40780328F7 +:1004900018BFFFDF03B0BDE8F08F70B5444C0646CF +:1004A0000D462068807858B107F0F2FD21680346B8 +:1004B000304691F85F202946BDE870400AF085BAC1 +:1004C00007F0E6FD21680346304691F85E20294694 +:1004D000BDE870400AF079BA78B50C460021009169 +:1004E000082804BF4FF4C87040210DD0042804BF71 +:1004F0004FF4BF70102107D0022807BF01F1180088 +:10050000042101F128000821521D02FB01062848A0 +:100510009DF80010006890F8602062F3050141F03A +:1005200040058DF8005090F85F00012829D002287E +:100530002ED004281CBF0828FFDF2FD025F0800014 +:100540008DF80000C4EB041000EB80004FF01E019A +:1005500001EB800006FB04041648844228BFFFDF3D +:100560001548A0FB0410BDF80110000960F30C0150 +:10057000ADF80110BDF800009DF8021040EA0140FE +:1005800078BD9DF8020020F0E0008DF80200D5E76C +:100590009DF8020020F0E000203004E09DF8020009 +:1005A00020F0E00040308DF80200C7E7C86102008B +:1005B00068000020C4BF0300898888880023C383A3 +:1005C000428401EBC202521EB2FBF1F1018470477A +:1005D0002DE9F04104460026D9B3552333224FF4C8 +:1005E000FA4501297DD0022900F01481032918BFA2 +:1005F000BDE8F08104F17001207B00F01F00207342 +:1006000084F889605FF0000004EB000C9CF808C0DF +:1006100003EA5C05ACEB050C0CF0FF0C0CF03305A9 +:1006200002EA9C0CAC440D180CEB1C1C0CF00F0CDB +:1006300085F814C04D7E401CAC44C0B281F819C08E +:100640000528E1D30CF0FF00252898BFBDE8F08114 +:10065000DCE0FFE704F17005802200212846FDF769 +:1006600016FFAE71EE712E736E73EE732E746E7193 +:10067000AE76EE76212085F84000492085F84100CD +:10068000FE2085F874002588702200212046FDF7A1 +:10069000FEFE2580012584F8645084F865502820EA +:1006A00084F86600002104F130000DF0B2FF1B2237 +:1006B000A4F84E20A4F85020A4F85220A4F8542006 +:1006C0004FF4A470A4F85600A4F8580065734FF4D2 +:1006D00048606080A4F8F060A4F8F260A4F8F460C8 +:1006E00000E023E0A4F8F660A4F8F86084F8FA606B +:1006F00084F8FD60A4F8066184F80461A4F8186128 +:10070000A4F81A6184F8B66184F8B76184F8C0610E +:1007100084F8C16184F88C6184F88F6184F8A861E1 +:10072000C4F8A061C4F8A461BDE8F081A4F8066132 +:1007300084F8FB606088FE490144B1FBF0F1A4F845 +:1007400090104BF68031A4F89210B4F806C0A4F8CB +:100750009860B4F89C704FEACC0C4743BCFBF0FCAB +:1007600097FBF0F70CF1010CA4F89C701FFA8CFCBD +:100770000CFB00F704F17001A4F89AC0B7F5C84F5C +:10078000C4BFACF1010CA1F82AC0B5FBF0FC0CF120 +:10079000010CA1F830C000F5802C0CF5EE3CACF15A +:1007A0000105B5FBF0FCA1F820C0CD8B05FB00FCDA +:1007B000BCFBF0F0C8830846217B01F01F012173C8 +:1007C0004676002104EB010C9CF808C003EA5C05A6 +:1007D000ACEB050C0CF0FF0C0CF0330502EA9C0CA2 +:1007E000AC4445180CEB1C1C0CF00F0C85F814C025 +:1007F000457E491CAC44C9B280F819C00529E1D333 +:100800000CF0FF00252898BFBDE8F081FFDFBDE8B0 +:10081000F08100BFB4F8B011B4F8B4316288A4F824 +:100820009860B4F89CC0DB000CFB02FCB3FBF1F356 +:100830009CFBF1FC5B1CA4F89CC09BB203FB01FC7D +:1008400004F17000A4F89A30BCF5C84FC4BF5B1E19 +:100850004385B5FBF1F35B1C0386438C01EBC303BB +:100860005B1EB3FBF1F30384C38B5A43B2FBF1F17C +:10087000C183BDE8F0812DE9F04104460025A1B314 +:1008800055234FF4FA464FF0330C01297DD002294D +:1008900000F0E080032918BFBDE8F08104F170008A +:1008A000217B01F01F01217384F889500021621817 +:1008B000127A03EA5205521BD2B202F033050CEA57 +:1008C00092022A44451802EB121202F00F022A7516 +:1008D000457E491C2A44C9B242760529E7D3D0B2E5 +:1008E000252898BFBDE8F081B1E0FFE704F170066C +:1008F000802200213046FDF7CAFDB571F5713573D0 +:100900007573F57335747571B576F576212086F8B3 +:100910004000492086F84100FE2086F874002688B1 +:10092000702200212046FDF7B2FD2680012684F8C2 +:10093000646084F86560282084F86600002104F172 +:1009400030000DF066FE1B22A4F84E20A4F85020C3 +:10095000A4F85220A4F854204FF4A470A4F8560030 +:10096000A4F858006673A4F8F850202084F8FA0020 +:1009700084F8F050C4F8F45084F8245184F82551D8 +:1009800084F82E5184F82F5100E005E084F81451CA +:1009900084F82051BDE8F081618865480844B0FBC7 +:1009A000F1F0A4F890004BF68030A4F89200E288B1 +:1009B000A4F89850B4F89C70D2004F43B2FBF1F207 +:1009C00097FBF1F7521CA4F89C7092B202FB01F75E +:1009D00004F17000A4F89A20B7F5C84FC4BF521EA6 +:1009E0004285B6FBF1F2521C028601F5802202F527 +:1009F000EE32561EB6FBF1F20284C68B06FB01F204 +:100A0000B2FBF1F1C1830146207B00F01F0020738F +:100A10004D7600202218127A03EA5205521BD2B2F8 +:100A200002F033050CEA92022A440D1802EB12126E +:100A300002F00F022A754D7E401C2A44C0B24A764D +:100A40000528E7D3D0B2252898BFBDE8F081FFDFA5 +:100A5000BDE8F081D0F81811628804F1700348896C +:100A6000C989A4F89850B4F89CC0C9000CFB02FCDA +:100A7000B1FBF0F19CFBF0FC491CA4F89CC089B2CE +:100A800001FB00FCA4F89A10BCF5C84FC4BF491E76 +:100A90005985B6FBF0F1491C1986598C00EBC10150 +:100AA000491EB1FBF0F11984D98B5143B1FBF0F031 +:100AB000D883BDE8F0812DE9F003447E0CB1252CEC +:100AC00003D9BDE8F00312207047002A02BF0020BE +:100AD000BDE8F003704791F80DC01F260123154DA6 +:100AE0004FF00008BCF1000F7AD0BCF1010F1EBF1F +:100AF0001F20BDE8F0037047B0F800C00A7C8F7B70 +:100B000091F80F907A404F7C87EA090742EA072262 +:100B100082EA0C0C5FF000070CF0FF0999FAA9F9C2 +:100B20004FEA1C2C4FEA19699CFAACFC04E0000067 +:100B3000FFDB050053E4B36E4FEA1C6C49EA0C2C52 +:100B40000CEB0C1C7F1C9444FFB21FFA8CFC032F8F +:100B5000E2D38CEA020CFB4F0022ECFB0572120977 +:100B60006FF0240502FB05C2D2B201EBD2078276F8 +:100B700002F007053F7A03FA05F52F4218BFC27647 +:100B80007ED104FB0CF2120C521CD2B25FF00004B6 +:100B900000EB040C9CF814C094453CBFA2EB0C0283 +:100BA000D2B212D30D194FF0000C2D7A03FA0CF7C4 +:100BB0003D421CBF521ED2B2002A69D00CF1010C7A +:100BC0000CF0FF0CBCF1080FF0D304F1010C0CF099 +:100BD000FF04052CDCD33046BDE8F0037047FFE787 +:100BE00090F81AC00C7E474604FB02C2D54C4FF069 +:100BF000000CE2FB054C4FEA1C1C6FF024040CFBBC +:100C00000422D2B201EBD204827602F0070C247ADD +:100C100003FA0CFC14EA0C0F1FBFC2764046BDE875 +:100C2000F003704790F819C0B2FBFCF40CFB1422DF +:100C3000521CD2B25FF0000400EB040C9CF814C00C +:100C400094453CBFA2EB0C02D2B212D30D194FF067 +:100C5000000C2D7A03FA0CF815EA080F1CBF521E7F +:100C6000D2B272B10CF1010C0CF0FF0CBCF1080F08 +:100C7000F0D304F1010C0CF0FF04052CDCD3AAE73F +:100C800009E00CEBC401C1763846BDE8F0037047BB +:100C90000CEBC401C1764046BDE8F0037047AA4A98 +:100CA000016812681140A94A126811430160704737 +:100CB00030B4A749A44B00244FF0010C0A78521C11 +:100CC000D2B20A70202A08BF0C700D781A680CFA8C +:100CD00005F52A42F2D0097802680CFA01F1514078 +:100CE000016030BC704770B46FF01F02010C02EA63 +:100CF00090251F23A1F5AA4054381CBFA1F5AA4096 +:100D0000B0F1550009D0A1F52850AA381EBFA1F5B1 +:100D10002A40B0F1AA00012000D100204FF0000CC1 +:100D2000624601248CEA0106F6431643B6F1FF3F02 +:100D300011D005F001064FEA5C0C4CEAC63C03F00A +:100D4000010652086D085B08641C42EAC632162C84 +:100D5000E8DD70BC704770BC0020704790F804C09C +:100D60003CF01F011CBF0020704730B401785522B1 +:100D700002EA5103C91AC9B201F03304332303EA6A +:100D800091012144447801EB111102EA5405641BDE +:100D9000E4B204F0330503EA94042C4404EB141485 +:100DA00001F00F0104F00F040C448178C07802EACE +:100DB0005105491BC9B201F0330503EA91012944E9 +:100DC00001EB111101F00F01214402EA5004001B54 +:100DD000C0B200F0330403EA9000204400EB10108E +:100DE00000F00F00014402EA5C00ACEB0000C0B26E +:100DF00000F0330203EA9000104400EB101000F002 +:100E00000F00084401288CBF0120002030BC70472F +:100E10000A000ED00123012A0BDB491EC9B210F8CB +:100E200001C0BCF1000F01D0002070475B1C934251 +:100E3000F3DD01207047002A08BF70471144401EAF +:100E400012F0010F03D011F8013D00F8013F5208E4 +:100E500008BF704711F8013C437011F8023D00F8DB +:100E6000023F521EF6D1704770B58CB000F11004ED +:100E70001D4616460DF1FF3C5FF0080014F8012CEA +:100E80008CF8012014F8022D0CF8022F401EF5D129 +:100E900001F1100C6C460DF10F0108201CF8012C1B +:100EA0004A701CF8022D01F8022F401EF6D1204690 +:100EB00013F01CFB7EB16A1E04F130005FF00801E4 +:100EC00010F8013C537010F8023D02F8023F491E31 +:100ED000F6D10CB070BD08982860099868600A982F +:100EE000A8600B98E8600CB070BD38B505460C469C +:100EF000684607F03DFE002808BF38BD9DF9002078 +:100F00002272E07E607294F90A100020511A48BFE4 +:100F1000494295F82D308B42C8BF38BDFF2B08BF22 +:100F200038BDE17A491CC9B2E17295F82E30994278 +:100F300003D8A17A7F2918BF38BDA2720020E072C1 +:100F4000012038BD53E4B36E04620200086202005F +:100F5000740000200C2818BF0B2810D00D2818BFD3 +:100F60001F280CD0202818BF212808D0222818BFFD +:100F7000232804D024281EBF2628002070474FF0C5 +:100F8000010070470C2963D2DFE801F006090E1357 +:100F9000161B323C415C484E002A5BD058E0072AC1 +:100FA00018BF082A56D053E00C2A18BF0B2A51D07C +:100FB0004EE00D2A4ED04BE0A2F10F000C2849D98B +:100FC00046E023B1A2F110000B2843D940E0122AD9 +:100FD00018BF112A3ED090F8380020B1122A37D31A +:100FE0001A2A37D934E0162A32D31A2A32D92FE0F6 +:100FF000A2F10F0103292DD990F8380008B31B2A5C +:1010000028D925E0002B08BF042A21D122E013B102 +:10101000062A1FD01CE0012A1AD11BE01C2A1CBF83 +:101020001D2A1E2A16D013E01F2A18BF202A11D00D +:10103000212A18BF222A0DD0232A1CBF242A262A9F +:1010400008D005E013B10E2A04D001E0052A01D032 +:1010500000207047012070472DE9F0410D460446FD +:10106000866805F02FFA58B905F07EF840F236711F +:1010700004F061FDA060204605F024FA0028F3D0BA +:1010800095B13046A16805F067FD00280CDD2844C5 +:10109000401EB0FBF5F707FB05F1304604F04BFDB1 +:1010A000A0603846BDE8F0810020BDE8F08170B551 +:1010B0000446904228BF70BD101B64280BD325182E +:1010C0008D4206D8042105F07AFD00281CBF284671 +:1010D00070BD204670BD6420F1E711F00C0F13D0F5 +:1010E00001F0040100290DBF4022102296214FF487 +:1010F000167101F5BC71A0EB010388428CBF93FB14 +:10110000F2F0002080B27047022919BF6FF00D0184 +:1011100001EBD0006FF00E0101EB9000F2E7084404 +:1011200018449830002A14BF042100210844704755 +:1011300010B4002A14BF4FF429624FF4A472002B9C +:1011400019BF4FF429634FF0080C4FF4A4734FF00C +:10115000010C00280CBF0124002491F866001CF04B +:101160000C0F08BF0020D11808449830002C14BF81 +:1011700004210021084410BC704700280CBF012343 +:10118000002391F86600002BA0F6482000F50050DF +:1011900018BF04231844496A81422CBF0120002053 +:1011A00012F00C0118BF012131EA000014BF002029 +:1011B0000120704710B413680B66137813F00C030A +:1011C00018BF0123527812F00C0218BF012253EA13 +:1011D000020C04BF10BC7047002B0CBF4FF4A4736B +:1011E0004FF42963002A19BF4FF429624FF0080C0D +:1011F0004FF4A4724FF0010C00280CBF012400240E +:1012000091F866001CF00C0F08BF00201A4410442F +:101210009830002C14BF0422002210444A6A8242F3 +:1012200024BF10BC704791F860004FF0030230F00B +:101230000C0381F8603091F8610020F00C0081F817 +:10124000610008BF81F86020002808BF81F8612094 +:1012500010BC704710F0010F1CBF0120704710F048 +:10126000020F1CBF0220704710F0040018BF0820B6 +:1012700070472DE9F0470446174689464FF00108AC +:1012800008460DF0FAF8054648460DF0FAF810F059 +:10129000010F18BF012624D015F0010F18BF01233C +:1012A0002AD000BF56EA030108BF4FF0000810F033 +:1012B000070F08BF002615F0070F08BF002394F89A +:1012C0006400B0420CBF00203046387094F86510BE +:1012D000994208BF00237B70002808BF002B25D14E +:1012E00015E010F0020F18BF0226D5D110F0040F40 +:1012F00014BF08260026CFE715F0020F18BF0223FF +:10130000D0D115F0040F14BF08230023CAE74846C4 +:101310000DF0BDF8B4F87010401A00B247F6FE7137 +:10132000884201DC002801DC4FF0000816B1082ECD +:101330000CD018E094F86400012818BF022812D0DD +:1013400004281EBF0828FFDF032D0CD194F8C0012C +:1013500048B1B4F8C401012894F8640006D0082804 +:1013600001D0082038704046BDE8F087042818BF37 +:101370000420F7D1F5E7012814BF0228704710F0C8 +:101380000C0018BF0420704738B4CBB2C1F3072C4F +:10139000C1B2C0F30724012B07D0022B09D0042BC4 +:1013A00008BFBCF1040F2DD006E0BCF1010F03D142 +:1013B00028E0BCF1020F25D0012906D0022907D070 +:1013C000042908BF042C1DD004E0012C02D119E02F +:1013D000022C17D001EA0C0161F3070204EA0301B1 +:1013E00061F30F22D1B211F0020F18BF022310D007 +:1013F000C2F307218DF8003011F0020F18BF02214F +:101400001BD111E0214003EA0C03194061F30702EC +:10141000E6E711F0010F18BF0123E9D111F0040F25 +:1014200014BF08230023E3E711F0010F18BF0121C7 +:1014300003D111F0040118BF08218DF80110082B09 +:1014400001BF000C012804208DF80000BDF8000049 +:1014500038BC70474FF0000C082902D0042909D08D +:1014600011E001280FD10420907082F803C013808E +:1014700001207047012806D00820907082F803C030 +:1014800013800120704700207047162A10D12A22AD +:101490000C2818BF0D280FD04FF0230C1F280DD09B +:1014A00031B10878012818BF002805D0162805D0CA +:1014B00000207047012070471A70FBE783F800C0D6 +:1014C000F8E7012908D002290BD0042912BF082906 +:1014D00040F6A660704707E0002804BF40F2E240F3 +:1014E000704740F6C410704700B5FFDF40F2E2409D +:1014F00000BD00000178406829B190F82C1190F8E7 +:101500008C0038B901E001F0BDBD19B1042901D04A +:10151000012070470020704770B50C460546062133 +:1015200002F0C4FC606008B1002006E007212846F4 +:1015300002F0BCFC606018B101202070002070BD7A +:10154000022070BD2DE9FC470C4606466946FFF7B0 +:10155000E3FF00287DD19DF8000050B1FDF7EEF8C3 +:10156000B0427CD0214630460AF008FC002873D1F6 +:101570002DE00DF097F9B04271D02146304612F0BF +:10158000B6FA002868D1019D95F8F00022E001200C +:1015900000E00020804695F839004FF0010A4FF036 +:1015A0000009F0B195F83A0080071AD584F8019047 +:1015B00084F800A084F80490E68095F83B1021722E +:1015C000A98F6181E98FA18185F8399044E0019D5F +:1015D00095F82C0170350028DBD1287F0028D8D061 +:1015E000D5E7304602F0A5FD070000D1FFDF384601 +:1015F00001F0B5FF40B184F801900F212170E68021 +:10160000208184F804A027E0304602F080FD070026 +:1016100000D1FFDFB8F1000F21D0384601F0F7FF0D +:10162000B8B19DF8000038B90198D0F81801418888 +:10163000B14201D180F80090304607F00DFF84F8E8 +:1016400001900C21217084F80490E680697F21725A +:1016500000E004E085F81C900120BDE8FC87002034 +:10166000FBE71CB56946FFF757FF00B1FFDF68468F +:1016700001F014FDFE4900208968A1F8F2001CBDAC +:101680002DE9FC4104460E46062002F0B7FB054654 +:10169000072002F0B3FB2844C7B20025A8463E4409 +:1016A00017E02088401C80B22080B04202D3404620 +:1016B000A4F8008080B2B84204D3B04202D2002025 +:1016C000BDE8FC816946FFF727FF0028F8D06D1CB4 +:1016D000EDB2AE42E5D84FF6FF7020801220EFE762 +:1016E00038B54FF6FF70ADF800000DE00621BDF8EB +:1016F000000002F0EDFB04460721BDF8000002F0F7 +:10170000E7FB0CB100B1FFDF00216846FFF7B8FF2F +:101710000028EBD038BD70B507F00CFF0BF034FF9C +:10172000D44C4FF6FF76002526836683D2A0257021 +:1017300001680079A4F14002657042F8421FA11CC3 +:101740001071601C12F0EFFA1B2020814FF4A4717D +:101750006181A081E18107212177617703212174D3 +:10176000042262746082A082A4F13E00E1820570CE +:101770004680BF480C300570A4F11000057046800B +:1017800084F8205070BD70B5B94C16460D466060A7 +:10179000217007F047FEFFF7A3FFFFF7BCFF20789B +:1017A0000FF0BDFFB6480DF0D0F92178606812F057 +:1017B0005FFA20780BF0DCF8284608F0AFFEB0485E +:1017C000FCF7C7FF217860680AF0B2FB3146207849 +:1017D00012F024FDBDE870400BF0D6BE10B5012418 +:1017E0000AB1002010BD21B1012903D000242046F8 +:1017F00010BD02210CF024FDF9E710B50378044672 +:10180000002B406813460A46014609D05FF00100EC +:10181000FFF78EFC6168496A884203D9012010BD38 +:101820000020F5E7002010BD2DE9F04117468A7829 +:101830001E46804642B11546C87838B1044669074D +:1018400006D52AB1012104E00725F5E70724F6E7CC +:101850000021620702D508B1012000E0002001420A +:1018600006D0012211464046FFF7C7FF98B93DE078 +:1018700051B1002201214046FFF7BFFF58B9600770 +:1018800034D50122114620E060B1012200214046FA +:10189000FFF7B3FF10B10920BDE8F081680725D537 +:1018A000012206E068074FEA44700AD5002814DBDD +:1018B000002201214046FFF7A0FFB8B125F0040542 +:1018C00014E0002812DA012200214046FFF795FFBC +:1018D00060B100BF24F0040408E001221146404634 +:1018E000FFF78BFF10B125F00405F3E73D7034706E +:1018F0000020D1E770B58AB0044600886946FFF73A +:101900000BFE002806D1A08830B1012804D002289F +:1019100002D012200AB070BD04AB03AA214668466B +:10192000FFF782FF0500F5D19DF800100120002689 +:101930000029019906D081F8C101019991F80C1292 +:10194000B1BB2DE081F82F01019991F8561139B9F9 +:10195000019991F82E1119B9019991F8971009B1CF +:101960003A2519E00199059681F82E01019A9DF812 +:101970000C0082F83001019B9DF8102083F8312182 +:10198000A388019CA4F832318DF814008DF815203D +:1019900005AA0020FFF70EFC019880F82F6126E0D1 +:1019A000019991F8C01119B9019991F8971009B1ED +:1019B0003A2519E00199059681F8C00101989DF832 +:1019C0000C2080F8C221019B9DF8100083F8C30110 +:1019D000A388019CA4F8C4318DF814208DF815005B +:1019E00005AA0120FFF7E6FB019880F8C1612846AF +:1019F00090E710B504460020A17801B90120E278F3 +:101A00000AB940F0020001F058FB002803D120463B +:101A1000BDE810406EE710BD70B5044691F8650052 +:101A200091F866300D4610F00C0F00D1002321898B +:101A3000A088FFF774FB696A814229D2401A401CD2 +:101A4000A1884008091A8AB2A2802189081A208137 +:101A5000668895F864101046FFF73FFB864200D277 +:101A600030466080E68895F8651020890AE000001D +:101A70007800002018080020FFFFFFFF1F00000073 +:101A8000D8060020FFF729FB864200D23046E080CE +:101A900070BDF0B585B00D46064603A9FFF73CFDC5 +:101AA00000282DD19DF80C0060B300220499FB2082 +:101AB000B1F84E30FB2B00D30346B1F85040FB2069 +:101AC000FB2C00D30446DFF85CC59CE88110009035 +:101AD0000197CDF808C0ADF80230ADF80640684671 +:101AE000FFF79AFF6E80BDF80400E880BDF808009B +:101AF0006881BDF80200A880BDF80600288100209A +:101B000005B0F0BD0122D1E72DE9F04186B00446D1 +:101B100000886946FFF700FD002876D12189E0881A +:101B200001F0E4FA002870D1A188608801F0DEFAA3 +:101B300000286AD12189E08801F0CFFA002864D119 +:101B4000A188608801F0C9FA07005ED1208802A947 +:101B5000FFF79FFF00B1FFDFBDF81010628809207A +:101B6000914252D3BDF80C10E28891424DD3BDF89A +:101B70001210BDF80E2023891144A2881A44914204 +:101B800043D39DF80010019D4FF00008012640F658 +:101B9000480041B185F8B761019991F8F81105F550 +:101BA000DB7541B91AE085F82561019991F84A1170 +:101BB00005F5927509B13A2724E0E18869806188CA +:101BC000E9802189814200D30146A980A188814210 +:101BD00000D208462881012201990FE0E18869803E +:101BE0006188E9802189814200D30146A980A188CA +:101BF000814200D208462881019900222846FFF739 +:101C00000BFF2E7085F80180384606B044E67BE76E +:101C100070B504460CF0FCFDB0B12078182811D145 +:101C2000207901280ED1E088062102F03FF9040056 +:101C300008D0208807F010FC2088062102F048F91F +:101C400000B1FFDF012070BDF74D28780028FAD0E1 +:101C5000002666701420207020223146201DFCF7DB +:101C600016FC022020712E70ECE710B50446FCF73C +:101C7000DBFC002813D0207817280FD1207968B119 +:101C8000E088072102F012F940B1008807F0E4FB78 +:101C9000E088072102F01CF900B1FFDF012010BD30 +:101CA0002DE9F0475FEA000800D1FFDFDE4802219E +:101CB0001A308146FFF7E4FC00B1FFDFDA4C062062 +:101CC000678B02F09BF80546072002F097F828443E +:101CD000C5B2681CC6B2608BB04203D14046FFF764 +:101CE000C4FF58B9608BA84203D14046FFF790FF6C +:101CF00020B9608B4146FFF725FC38B1404601F022 +:101D000003FA0028E7D10120BDE8F0870221484608 +:101D1000FFF7B6FC10B9608BB842DCD14046BDE895 +:101D2000F04712F0C1BA10B501F053F908B10C2018 +:101D300010BD0BF07DFC002010BD10B504460078EE +:101D400018B1012801D0122010BD01F053F920B1C3 +:101D50000BF0C0FD08B10C2010BD207801F013F984 +:101D6000E21D04F11703611CBDE810400BF0DABC62 +:101D700010B5044601F02DF908B10C2010BD2078F3 +:101D800028B1012803D0FF280BD0122010BD01F08C +:101D9000FAF8611C0BF00CFC08B1002010BD072004 +:101DA00010BD01200BF03EFCF7E710B50BF095FDE0 +:101DB00008B1002010BD302010BD10B5044601F060 +:101DC00019F908B10C2010BD20460BF080FD002051 +:101DD00010BD10B501F00EF920B10BF07BFD08B17C +:101DE0000C2010BD0BF0F6FC002010BDFF2181700F +:101DF0004FF6FF7181808D4949680A7882718A881F +:101E000002814988418101214170002070477CB5E1 +:101E10000025022A19D015DC12F10C0F15D009DCAF +:101E200012F1280F11D012F1140F0ED012F1100F71 +:101E300011D10AE012F1080F07D012F1040F04D0FB +:101E40004AB902E0D31E052B05D8012806D0022886 +:101E500008D003280AD0122528467CBD1046FDF77D +:101E600013F8F9E710460CF06BFEF5E70846144648 +:101E70006946FFF751FB08B10225EDE79DF8000028 +:101E80000198002580F86740E6E710B51346012267 +:101E9000FEF7EAFF002010BD10B5044610F02FFA3F +:101EA000052804D020460FF029FC002010BD0C208E +:101EB00010BD10B5044601F09DF808B10C2010BD0E +:101EC0002146002007F037FB002010BD10B5044666 +:101ED0000FF0A3FC50B108F0A6FD38B1207808F04F +:101EE00029FB20780DF04DF9002010BD0C2010BD0D +:101EF00010B5044601F07EF808B10C2010BD214653 +:101F0000012007F018FB002010BD38B504464FF63D +:101F1000FF70ADF80000A079E179884216D02079F1 +:101F2000FCF7E3FA90B16079FCF7DFFA70B10022B8 +:101F3000A079114612F0A0FD40B90022E0791146C7 +:101F400012F09AFD10B9207A072801D9122038BD65 +:101F500008F076FD60B910F0D2F948B90021684662 +:101F6000FFF78EFB20B1204606F044F9002038BD73 +:101F70000C2038BD2DE9FC41817805461A2925D071 +:101F80000EDC16292ED2DFE801F02D2D2D2D2D216E +:101F90002D2D2D2D2D2D2D2D2D2D2D2D2D21212195 +:101FA0002A291FD00BDCA1F11E010C291AD2DFE86F +:101FB00001F019191919191919191919190D3A399D +:101FC00004290FD2DFE801F00E020E022888B0F5D6 +:101FD000706F07D201276946FFF79EFA20B10220F1 +:101FE000BDE8FC811220FBE79DF8000000F0D2FF65 +:101FF000019C10B104F58A7401E004F5C6749DF8E3 +:10200000000000F0C7FF019E10B106F2151601E0B6 +:1020100006F28D166846FFF76DFA08B1207838B1E0 +:102020000C20DDE70C620200180800207800002078 +:102030002770A8783070684601F030F80020CFE7AC +:102040007CB50D466946FFF767FA002618B12E6089 +:102050002E7102207CBD9DF8000000F09BFF019CCA +:102060009DF80000703400F095FF019884F84260FC +:1020700081682960017B297194F842100029F5D10B +:1020800000207CBD10B5044600F0B4FF20B10BF079 +:1020900021FC08B10C2010BD207800F074FFE2791B +:1020A000611C0BF093FD08B1002010BD022010BD93 +:1020B00010B5886E60B1002241F8682F0120CA7106 +:1020C0008979884012F0CCFC002800D01F2010BD78 +:1020D0000C2010BD1CB50C466946FFF71DFA002800 +:1020E00009D19DF8000000280198B0F8700000D0D8 +:1020F000401C208000201CBD1CB504460088694699 +:10210000FFF70AFA08B102201CBD606828B1DDE9BA +:102110000001224601F04CF81CBDDDE90001FFF78B +:10212000C7FF1CBD70B51C460D4618B1012801D073 +:10213000122070BD1946104601F078F830B12146E2 +:10214000284601F07DF808B1002070BD302070BD38 +:1021500070B5044600780E46012804D018B1022854 +:1021600001D0032840D1607828B1012803D002288B +:1021700001D0032838D1E07B10B9A078012833D1F1 +:10218000A07830F005012FD110F0050F2CD0628916 +:10219000E188E0783346FFF7C5FF002825D1A07815 +:1021A00005281DD16589A289218920793346FFF749 +:1021B000B9FF002819D1012004EB40014A891544D8 +:1021C0002218D378927893420ED1CA8889888A429D +:1021D0000AD1401CC0B20228EED3E088A84203D343 +:1021E000A07B08B1072801D9122070BD002070BD66 +:1021F00010B586B0044600F0E1FE10B10C2006B028 +:1022000010BD022104F10A0001F02FF8A0788DF82A +:102210000800A0788DF8000060788DF80400207820 +:102220008DF80300A07B8DF80500E07B00B1012054 +:102230008DF80600A078C10717D0E07801F00CF8FF +:102240008DF80100E088ADF80A006089ADF80C0057 +:10225000A078400716D5207900F0FEFF8DF8020027 +:102260002089ADF80E00A0890AE040070AD5E07881 +:1022700000F0F2FF8DF80200E088ADF80E006089F2 +:10228000ADF8100002A80FF0D4FA0028B7D16846C4 +:102290000CF07CFFB3E710B504460121FFF758FFAF +:1022A000002803D12046BDE81040A1E710BD027808 +:1022B000012A01D0BAB118E042783AB1012A05D01A +:1022C000022A12D189B1818879B100E059B14188DF +:1022D00049B1808838B101EB8101490000EB8000F1 +:1022E000B1EB002F01D2002070471220704770B56B +:1022F000044600780D46012809D010F000F80528A2 +:1023000003D00FF0A6F9002800D00C2070BD0CF00F +:102310000AFE88B10CF01CFE0CF018FF0028F5D165 +:1023200025B160780CF0ACFE0028EFD1A188608860 +:10233000BDE870400FF0A3BA122070BD10B504467E +:102340000121FFF7B4FF002804D12046BDE810406A +:102350000121CCE710BDF0B5871FDDE9056540F62A +:102360007B44A74213D28F1FA74210D288420ED8B7 +:10237000B2F5FA7F0BD2A3F10A00241FA04206D2C5 +:10238000521C4A43B2EB830F01DAAE4201D900205E +:10239000F0BD0120F0BD2DE9FC47477A894604468F +:1023A00017F0050F7ED0F8087CD194F83A0008B9F0 +:1023B000012F77D10025A8462E46F90789F0010A9A +:1023C00019D0208A514600F031FFE8B360895146A8 +:1023D00000F036FFC0B3208A6189884262D8A18E9E +:1023E000E08DCDE90001238D628CA18BE08AFFF79F +:1023F000B2FF48B30125B8070ED504EB4500828E25 +:10240000C18DCDE90012038D428C818BC08AFFF70C +:10241000A2FFC8B1A8466D1C78071ED504EB45067F +:102420005146308A00F002FF70B17089514600F0C9 +:1024300007FF48B1308A7189884253D8B18EF08D38 +:10244000CDE90001338D00E00BE0728CB18BF08A96 +:10245000FFF781FF28B12E466D1CB9F1000F03D0A4 +:1024600030E03020BDE8FC87F80707D0780705D5B5 +:1024700004EB460160894989884233D1228A0121CF +:102480001BE0414503D004EB4100008A024404EB09 +:102490004100C38A868AB34224D1838B468BB342E0 +:1024A00020D100E01EE0438C068CB3421AD1038D8C +:1024B000C08C834216D1491CC9B2A942E1D36089BC +:1024C00090420FD3207810B101280BD102E0A07800 +:1024D0000028F9D1607838B1012805D0022803D04E +:1024E000032801D01220BDE70020BBE7002152E7FE +:1024F0000178C90702D0406811F0A9BE11F076BE7C +:1025000010B50078012800D00020FCF7B8FC0020AE +:1025100010BD2DE9F0478EB00D46AFF6A422D2E9EA +:102520000092014690462846FFF735FF06000CD181 +:1025300000F044FD40B9FE4F387828B90CF0B2F9EC +:10254000A0F57F41FF3903D00C200EB0BDE8F08725 +:10255000032105F1100000F088FEF54809AA3E3875 +:102560000990F4480A90F248062110380B900CA804 +:1025700001F06AFC040037D00021FEF77CF904F179 +:1025800030017B8ABA8ACB830A84797C0091BA466F +:102590003B7CBA8A798A208801F044FD00B1FFDFD4 +:1025A000208806F058FF218804F10E0000F02CFD71 +:1025B000E1A004F1120700680590032105A804F0CA +:1025C0006DFF002005A90A5C3A54401CC0B20328E4 +:1025D000F9D3A88B6080688CA080288DE080687A11 +:1025E000410703D508270AE00920AEE7C10701D05B +:1025F000012704E0800701D5022700E000273A46C2 +:10260000BAF8160011460FF0CFF90146A062204635 +:102610000FF0D8F93A4621460020FEF7AEFD00B98A +:102620000926C34A21461C320020FEF7C3FD0027BD +:1026300084F8767084F87770A87800F0A4FC60764F +:10264000D5F80300C4F81A00B5F80700E083C4F811 +:10265000089084F80C80012084F8200101468DF850 +:102660000070684604F01AFF9DF8000000F00701B2 +:10267000C0F3C1021144C0F3401008448DF80000BB +:10268000401D2076092801D20830207601212046FD +:10269000FEF7F1F868780CF051FCEEBBA9782878C9 +:1026A000EA1C0CF01EFC48B10CF052FCA97828780A +:1026B000EA1C0CF0BFFC060002D052E0122650E0EB +:1026C000687A00F005010020CA0700D001208A07BF +:1026D00001D540F00200490701D540F008000CF098 +:1026E000E9FB06003DD1214603200CF0CDFC06009D +:1026F00037D10CF0D2FC060033D1697A01F0050124 +:102700008DF80810697AC90708D06889ADF80A0001 +:10271000288AADF80C0000E023E00120697A8A07DE +:1027200000D5401C490707D505EB40004189ADF8AD +:102730000E10008AADF8100002A80FF07AF80646D5 +:1027400095F83A0000B101200CF0C6FB4EB90CF030 +:10275000FDFC060005D1A98F20460FF00BF80600FE +:1027600008D0208806F078FE2088062101F0B0FB12 +:1027700000B1FFDF3046E8E601460020C9E638B583 +:102780006B48007878B90FF0BAFD052805D00CF039 +:1027900089F8A0F57F41FF3905D068460FF0B3F8FE +:1027A000040002D00CE00C2038BD0098008806F030 +:1027B00053FE00980621008801F08AFB00B1FFDF7C +:1027C000204638BD1CB582894189CDE900120389B4 +:1027D000C28881884088FFF7BEFD08B100201CBD7B +:1027E00030201CBD70B50546FFF7ECFF00280ED168 +:1027F0002888062101F05AFB040007D000F042FCB3 +:1028000020B1D4F81801017831B901E0022070BD7F +:10281000D4F86411097809B13A2070BD052181719D +:10282000D4F8181100200881D4F81811A88848811C +:10283000D4F81811E8888881D4F818112889C8813B +:10284000D4F81801028941898A4204D88279082A79 +:1028500001D88A4201D3122070BD29884180D4F862 +:10286000181102200870002070BD3EB50446FEF726 +:1028700075FAB0B12E480125A0F1400245702368D9 +:1028800042F8423F237900211371417069460620C6 +:1028900001F095FA00B1FFDF684601F06EFA10B161 +:1028A0000EE012203EBDBDF80440029880F8205191 +:1028B000684601F062FA18B9BDF80400A042F4D1EC +:1028C00000203EBD70B505460088062101F0EEFAF5 +:1028D000040007D000F0D6FB20B1D4F81811087816 +:1028E00030B901E0022070BDD4F86401007808B16D +:1028F0003A2070BDB020005D10F0010F22D0D5F855 +:1029000002004860D5F806008860D4F8180169898B +:1029100010228181D4F8180105F10C010E3004F564 +:102920008C74FBF78AFD216803200870288805E075 +:1029300018080020840000201122330021684880FC +:10294000002070BD0C2070BD38B504460078EF281B +:102950004DD86088ADF80000009800F097FC88B36F +:102960006188080708D4D4E9012082423FD8202A90 +:102970003DD3B0F5804F3AD8207B18B3072836D81E +:10298000607B28B1012803D0022801D003282ED172 +:102990004A0703D4022801D0032805D1A07B08B13F +:1029A000012824D1480707D4607D28B1012803D02D +:1029B000022801D003281AD1C806E07D03D50128DA +:1029C00015D110E013E0012801D003280FD1C8066B +:1029D00009D4607E012803D0022801D0032806D143 +:1029E000A07E0F2803D8E07E18B1012801D0122064 +:1029F00038BD002038BDF8B514460D46064608F02F +:102A00001FF808B10C20F8BD3046FFF79DFF0028E5 +:102A1000F9D1FCF73EFA2870B07554B9FF208DF853 +:102A2000000069460020FCF71EFA69460020FCF70A +:102A30000EFA3046BDE8F840FCF752B90022DAE75A +:102A40000078C10801D012207047FA4981F82000AF +:102A50000020704710B504460078C00704D1608894 +:102A600010B1FCF7D7F980B12078618800F001023D +:102A7000607800F02FFC002806D1FCF7B3F901467E +:102A80006088884203D9072010BD122010BD6168FC +:102A9000FCF7E9F9002010BD10B504460078C00726 +:102AA00004D1608810B1FBF78AFE70B1207861888C +:102AB00000F00102607800F00DFC002804D160886D +:102AC0006168FCF7C4F9002010BD122010BD7CB570 +:102AD000044640784225012808D8A078FBF767FE15 +:102AE00020B120781225012802D090B128467CBD63 +:102AF000FCF7DBF920B1A0880028F7D08028F5D8B2 +:102B0000FCF7DAF960B160780028EFD0207801286E +:102B100008D006F0C3FD044607F05DFC00287FD016 +:102B20000C207CBDFBF7F5FF10B9FCF7B7F990B3AB +:102B300007F086FF0028F3D1FBF700FEA0F57F41E8 +:102B4000FF39EDD1FCF707F8A68842F21070464332 +:102B5000A079FCF770F9FBF739FEF8B100220721E4 +:102B600001A801F071F9040058D0B3480021846035 +:102B70002046FDF72DFD2046FCF732FDAD4D04F15A +:102B800030006A8AA98AC2830184FBF726FE60B1FD +:102B9000E88A01210DE0FFE712207CBD31460020CC +:102BA00007F0CBFC88B3FFDF44E0FCF787F9014670 +:102BB000E88A07F091FD0146A0620022204606F057 +:102BC00070FDFBF70AFE38B9FCF778F9024621469A +:102BD0000120FEF7D2FAD0B1964A21461C320120DC +:102BE000FEF7E8FA687C00902B7CAA8A698A208824 +:102BF00001F018FA00B1FFDF208806F02CFC314606 +:102C0000204607F09AFC00B1FFDF13E008E007213F +:102C1000BDF8040001F05CF900B1FFDF09207CBDC4 +:102C200044B1208806F018FC2088072101F050F9F3 +:102C300000B1FFDF00207CBD002148E770B50D46E4 +:102C4000072101F033F9040003D094F88F0110B18B +:102C50000AE0022070BD94F87D00142801D01528E8 +:102C600002D194F8DC0108B10C2070BD1022294675 +:102C700004F5C870FBF7E1FB012084F88F01002008 +:102C800070BD10B5072101F011F918B190F88F113E +:102C900011B107E0022010BD90F87D10142903D077 +:102CA000152901D00C2010BD022180F88F110020C1 +:102CB00010BD2DE9FC410C464BF6803212219442A6 +:102CC0001DD8E4B16946FEF727FC002815D19DF810 +:102CD000000000F05FF9019E9DF80000703600F0E2 +:102CE00059F9019DAD1C2F88224639463046FDF723 +:102CF00065FC2888B842F6D10020BDE8FC81084672 +:102D0000FBE77CB5044600886946FEF705FC002811 +:102D100010D19DF8000000F03DF9019D9DF80000E4 +:102D2000703500F037F90198A27890F82C10914294 +:102D300001D10C207CBD7F212972A9720021E9728A +:102D4000E17880F82D10217980F82E10A17880F894 +:102D50002C1000207CBD1CB50C466946FEF7DCFB40 +:102D600000280AD19DF8000000F014F9019890F8AD +:102D70008C0000B10120207000201CBD7CB50D46E8 +:102D800014466946FEF7C8FB002809D19DF80000EB +:102D900000F000F9019890F82C00012801D00C20D7 +:102DA0007CBD9DF8000000F0F5F8019890F87810CF +:102DB000297090F87900207000207CBD70B50D4618 +:102DC0001646072101F072F818B381880124C388E0 +:102DD000428804EB4104AC4217D842F210746343BA +:102DE000A4106243B3FBF2F2521E94B24FF4FA7293 +:102DF000944200D91446A54200D22C46491C641CBA +:102E0000B4FBF1F24A43521E91B290F8C8211AB9AC +:102E100001E0022070BD01843180002070BD10B53A +:102E20000C46072101F042F840B1022C08D91220CB +:102E300010BD000018080020780000200220F7E7ED +:102E400014F0010180F8FD10C4F3400280F8FC206A +:102E500004D090F8FA1009B107F054FC0020E7E71D +:102E6000017889B1417879B141881B290CD38188D7 +:102E70001B2909D3C188022906D3F64902680A65CD +:102E800040684865002070471220704710B504461E +:102E90000EF086FD204607F0D8FB0020C8E710B5ED +:102EA00007F0D6FB0020C3E72DE9F04115460F4699 +:102EB00006460122114638460EF076FD04460121F1 +:102EC000384607F009FC844200D20446012130460E +:102ED00000F065F806460121002000F060F8311886 +:102EE000012096318C4206D901F19600611AB1FB9E +:102EF000F0F0401C80B228800020BDE8F08110B5C1 +:102F0000044600F077F808B10C2091E7601C0AF045 +:102F100038FE207800F00100FBF718FE207800F062 +:102F200001000CF010F8002082E710B504460720DD +:102F300000F056FF08B10C207AE72078C00711D0C6 +:102F400000226078114611F097FD08B112206FE75A +:102F5000A06809F01DFB6078D4F8041009F021FB8B +:102F6000002065E7002009F013FB00210846F5E783 +:102F700010B505F036FE00205AE710B5006805F0E0 +:102F800084F8002054E718B1022801D001207047CE +:102F90000020704708B1002070470120704710B52D +:102FA000012904D0022905D0FFDF204640E7C000F8 +:102FB000503001E080002C3084B2F6E710B50FF0FD +:102FC0009EF9042803D0052801D0002030E7012015 +:102FD0002EE710B5FFF7F2FF10B10CF07BF828B91F +:102FE00007F02EFD20B1FBF78CFD08B101201FE793 +:102FF00000201DE710B5FFF7E1FF18B907F020FD2D +:10300000002800D0012013E72DE9FE4300250F46DC +:1030100080460A260421404604F069FA4046FDF73E +:103020003EFE062000F0EAFE044616E06946062051 +:1030300000F0C5FE0BE000BFBDF80400B84206D0AA +:103040000298042241460E30FBF7CAF950B1684697 +:1030500000F093FE0500EFD0641E002C06DD002D6D +:10306000E4D005E04046FDF723FEF5E705B9FFDFB4 +:10307000D8F80000FDF737FE761E01D00028C9D031 +:10308000BDE8FE8390F8F01090F88C0020B919B1DB +:10309000042901D0012070470020704701780029E1 +:1030A0000AD0416891F8FA20002A05D0002281F860 +:1030B000FA20406807F026BB704770B514460546F5 +:1030C000012200F01BF9002806D121462846BDE860 +:1030D0007040002200F012B970BDFB2802D8B1F593 +:1030E000296F01D911207047002070471B38E12853 +:1030F00006D2B1F5A47F03D344F29020814201D9D6 +:1031000012207047002070471FB55249403191F896 +:103110002010CA0702D102781D2A0AD08A0702D4D9 +:1031200002781C2A28D049073DD40178152937D0C8 +:1031300039E08088ADF8000002A9FEF7EDF900B192 +:10314000FFDF9DF80800FFF725FF039810F8601FC8 +:103150008DF8021040788DF803000020ADF80400CF +:1031600001B9FFDF9DF8030000B9FFDF6846FEF7F5 +:1031700040FCD8B1FFDF19E08088ADF800004FF4C3 +:103180002961FB20ADF80410ADF80200ADF806008F +:10319000ADF808106846FEF73AFD38B1FFDF05E0EC +:1031A000807BC00702D0002004B041E60120FBE78D +:1031B000F8B50746508915460C4640B1B0F5004FAA +:1031C00005D20022A878114611F056FC08B1122051 +:1031D000F8BDA06E04F1700630B1A97894F86E00C5 +:1031E000814201D00C20F8BD012184F86F10A9782C +:1031F00084F86E106968A1666989A4F86C10288942 +:10320000B084002184F86F1028886946FEF762FFB9 +:10321000B08CBDF80010081A00B2002804DD214669 +:103220003846FEF745FFDDE70020F8BD042803D34C +:1032300021B9B0F5804F01D90020704701207047B7 +:10324000042803D321B9B0F5804F01D9002070477D +:1032500001207047D8070020012802D018B10020B3 +:103260007047022070470120704710B500224FF4CC +:10327000C84408E030F81230A34200D9234620F8B1 +:103280001230521CD2B28A42F4D3D1E580B2C106C8 +:103290000BD401071CD481064FEAC07101D5B9B91E +:1032A00000E099B1800713D410E0410610D48106E4 +:1032B0000ED4C1074FEA807104D0002902DB400719 +:1032C00004D405E0010703D4400701D4012070476E +:1032D0000020704770B50C460546FF2904D8FBF75F +:1032E0007CFA18B11F2C01D9122070BD2846FBF7BB +:1032F0005EFA08B1002070BD422070BD0AB1012203 +:1033000000E00222024202D1C80802D109B1002025 +:10331000704711207047000030B5058825F400443F +:1033200021448CB24FF4004194420AD2121B92B253 +:103330001B339A4201D2A94307E005F4004121431F +:1033400003E0A21A92B2A9431143018030BD0844A0 +:10335000083050434A31084480B2704770B51D466A +:1033600016460B46044629463046049AFFF7EFFFFF +:103370000646B34200D2FFDF282200212046FBF799 +:1033800086F84FF6FF70A082283EB0B26577608065 +:10339000B0F5004F00D9FFDF618805F13C008142A4 +:1033A00000D2FFDF60880835401B343880B22080AF +:1033B0001B2800D21B2020800020A07770BD8161D7 +:1033C000886170472DE9F05F0D46C188044600F121 +:1033D0002809008921F4004620F4004800F063FB2E +:1033E00010B10020BDE8F09F4FF0000A4FF0010B34 +:1033F000B0450CD9617FA8EB0600401A0838854219 +:1034000019DC09EB06000021058041801AE0608884 +:10341000617F801B471A083F0DD41B2F00DAFFDFA6 +:10342000BD4201DC294600E0B9B2681A0204120C60 +:1034300004D0424502DD84F817A0D2E709EB06006C +:103440000180428084F817B0CCE770B5044600F1E3 +:103450002802C088E37D20F400402BB1104402888C +:10346000438813448B4201D2002070BD00258A425C +:1034700002D30180458008E0891A0904090C4180C3 +:1034800003D0A01D00F01FFB08E0637F0088083315 +:10349000184481B26288A01DFFF73EFFE575012048 +:1034A00070BD70B5034600F12804C588808820F4FB +:1034B00000462644A84202D10020188270BD988997 +:1034C0003588A84206D3401B75882D1A2044ADB21A +:1034D000C01E05E02C1AA5B25C7F20443044401D7C +:1034E0000C88AC4200D90D809C8924B10024147052 +:1034F0000988198270BD0124F9E770B5044600F10E +:103500002801808820F400404518208A002825D012 +:10351000A189084480B2A08129886A881144814227 +:1035200000D2FFDF2888698800260844A1898842E4 +:1035300012D1A069807F2871698819B1201D00F01F +:10354000C2FA08E0637F28880833184481B2628891 +:10355000201DFFF7E1FEA6812682012070BD2DE926 +:10356000F041418987880026044600F12805B942C8 +:1035700019D004F10A0800BF21F400402844418812 +:1035800019B1404600F09FFA08E0637F00880833D5 +:10359000184481B262884046FFF7BEFE761C6189FE +:1035A000B6B2B942E8D13046BDE8F0812DE9F0412C +:1035B00004460B4627892830A68827F40041B4F832 +:1035C0000A8001440D46B74201D10020ECE70AB160 +:1035D000481D106023B1627F691D1846FAF72DFF60 +:1035E0002E88698804F1080021B18A1996B200F08A +:1035F0006AFA06E0637F62880833991989B2FFF797 +:103600008BFE474501D1208960813046CCE7818817 +:10361000C088814201D10120704700207047018994 +:103620008088814201D1012070470020704770B529 +:103630008588C38800F1280425F4004223F4004162 +:1036400014449D421AD08389058A5E1925886388AF +:10365000EC18A64214D313B18B4211D30EE0437F72 +:1036600008325C192244408892B2801A80B2233317 +:10367000984201D211B103E08A4201D1002070BD0D +:10368000012070BD2DE9F0478846C18804460089B5 +:1036900021F4004604F1280720F4004507EB060951 +:1036A00000F001FA002178BBB54204D9627FA81B63 +:1036B000801A002503E06088627F801B801A08382A +:1036C00023D4E28962B1B9F80020B9F802303BB1E5 +:1036D000E81A2177404518DBE0893844801A09E070 +:1036E000801A217740450ADB607FE1890830304449 +:1036F00039440844C01EA4F81280BDE8F08745454F +:1037000003DB01202077E7E7FFE761820020F4E791 +:103710002DE9F74F044600F12805C088884620F4BB +:10372000004A608A05EB0A0608B1404502D2002033 +:10373000BDE8FE8FE08978B13788B6F8029007EBD4 +:103740000901884200D0FFDF207F4FF0000B50EAD4 +:10375000090106D088B33BE00027A07FB94630714D +:10376000F2E7E18959B1607F2944083050440844A8 +:10377000B4F81F1020F8031D94F821108170E2891D +:1037800007EB080002EB0801E1813080A6F802B0E7 +:1037900002985F4650B1637F30880833184481B285 +:1037A0006288A01DFFF7B8FDE78121E0607FE18915 +:1037B00008305044294408442DE0FFE7E089B4F87C +:1037C0001F102844C01B20F8031D94F8211081709D +:1037D00009EB0800E28981B202EB0800E081378042 +:1037E00071800298A0B1A01D00F06DF9A4F80EB090 +:1037F000A07F401CA077A07D08B1E088A08284F85B +:1038000016B000BFA4F812B084F817B001208FE7FB +:10381000E0892844C01B30F8031DA4F81F108078ED +:1038200084F82100EEE710B5818800F1280321F427 +:1038300000442344848AC288A14212D0914210D00D +:10384000818971B9826972B11046FFF7E8FE50B9FB +:103850001089283220F400401044197900798842F8 +:1038600001D1002010BD184610BD00F12803407F93 +:1038700008300844C01E1060088808B9DB1E1360B9 +:1038800008884988084480B270472DE9F04100F16A +:103890002806407F1C4608309046431808884D880B +:1038A000069ADB1EA0B1C01C80B2904214D9801AC7 +:1038B000A04200DB204687B298183A464146FAF704 +:1038C0008FFD002816D1E01B84B2B844002005E02B +:1038D000ED1CADB2F61EE8E7101A80B20119A9423C +:1038E00006D8304422464146BDE8F041FAF778BD9B +:1038F0004FF0FF3058E62DE9F04100F12804407FF9 +:103900001E46083090464318002508884F88069ABE +:10391000DB1E90B1C01C80B2904212D9801AB04216 +:1039200000DB304685B299182A464046FAF785FDF5 +:10393000701B86B2A844002005E0FF1CBFB2E41E45 +:10394000EAE7101A80B28119B94206D82118324626 +:103950004046FAF772FDA81985B2284624E62DE9FB +:10396000F04100F12804407F1E460830904643187D +:10397000002508884F88069ADB1E90B1C01C80B2D3 +:10398000904212D9801AB04200DB304685B29818B6 +:103990002A464146FAF751FD701B86B2A844002022 +:1039A00005E0FF1CBFB2E41EEAE7101A80B28119DD +:1039B000B94206D8204432464146FAF73EFDA819DE +:1039C00085B22846F0E5401D704710B5044600F169 +:1039D0002801C288808820F400431944904206D010 +:1039E000A28922B9228A12B9A28A904201D100206A +:1039F00010BD0888498831B1201D00F064F800200E +:103A00002082012010BD637F62880833184481B290 +:103A1000201DFFF781FCF2E70021C181017741827F +:103A2000C1758175704703881380C28942B1C2880D +:103A300022F4004300F128021A440A60C08970474A +:103A40000020704710B50446808AA0F57F41FF39F9 +:103A500000D0FFDFE088A082E08900B10120A075DE +:103A600010BD4FF6FF71818200218175704710B53E +:103A70000446808AA0F57F41FF3900D1FFDFA07D99 +:103A800028B9A088A18A884201D1002010BD012058 +:103A900010BD8188828A914201D1807D08B10020C9 +:103AA00070470120704720F4004221F400439A42FD +:103AB00007D100F4004001F40041884201D0012008 +:103AC00070470020704730B5044600880D4620F44A +:103AD0000040A84200D2FFDF21884FF40040884315 +:103AE0002843208030BD70B50C00054609D0082C55 +:103AF00000D2FFDF1DB1A1B2286800F044F8201DFC +:103B000070BD0DB100202860002070BD002102684A +:103B100003E093881268194489B2002AF9D100F0B1 +:103B200032B870B500260D460446082900D2FFDFE2 +:103B3000206808B91EE0044620688188A94202D0A6 +:103B400001680029F7D181880646A94201D10068A1 +:103B50000DE005F1080293B20022994209D32844EE +:103B6000491B026081802168096821600160206032 +:103B700000E00026304670BD00230B608A8002689A +:103B80000A600160704700234360021D01810260EA +:103B90007047F0B50F460188408815460C181E4640 +:103BA000AC4200D3641B3044A84200D9FFDFA01907 +:103BB000A84200D9FFDF3819F0BD2DE9F041884651 +:103BC00006460188408815460C181F46AC4200D3B3 +:103BD000641B3844A84200D9FFDFE019A84200D98D +:103BE000FFDF70883844708008EB0400BDE8F08186 +:103BF0002DE9F041054600881E461746841B88467D +:103C0000BC4200D33C442C8068883044B84200D980 +:103C1000FFDFA019B84200D9FFDF68883044688010 +:103C200008EB0400E2E72DE9F04106881D46044652 +:103C3000701980B2174688462080B84201D3C01B55 +:103C400020806088A84200D2FFDF7019B84200D9F6 +:103C5000FFDF6088401B608008EB0600C6E730B5D8 +:103C60000D460188CC18944200D3A41A408898428B +:103C700000D8FFDF281930BD2DE9F041C84D0446BA +:103C80009046A8780E46A04200D8FFDF05EB8607D5 +:103C9000B86A50F8240000B1FFDFB868002816D0D9 +:103CA000304600F044F90146B868FFF73AFF0500D6 +:103CB0000CD0B86A082E40F8245000D3FFDFB94872 +:103CC0004246294650F82630204698472846BDE807 +:103CD000F0812DE9F8431E468C1991460F460546A2 +:103CE000FF2C00D9FFDFB14500D9FFDFE4B200951A +:103CF0004DB300208046E81C20F00300A84200D00D +:103D0000FFDF4946DFF89892684689F8001089F885 +:103D1000017089F8024089F8034089F8044089F865 +:103D2000054089F8066089F80770414600F008F9F7 +:103D3000002142460F464B460098C01C20F003006D +:103D4000009012B10EE00120D4E703EB8106B062CF +:103D5000002005E0D6F828C04CF82070401CC0B206 +:103D6000A042F7D30098491C00EB8400C9B2009030 +:103D70000829E1D3401BBDE8F88310B50446EDF7F0 +:103D80008EFA08B1102010BD2078854A618802EBB8 +:103D9000800092780EE0836A53F8213043B14A1CC8 +:103DA0006280A180806A50F82100A060002010BDD0 +:103DB000491C89B28A42EED86180052010BD70B5D9 +:103DC00005460C460846EDF76AFA08B1102070BDAA +:103DD000082D01D3072070BD25700020608070BDC4 +:103DE0000EB56946FFF7EBFF00B1FFDF6846FFF74E +:103DF000C4FF08B100200EBD01200EBD10B5044661 +:103E0000082800D3FFDF6648005D10BD3EB50546BB +:103E100000246946FFF7D3FF18B1FFDF01E0641CFF +:103E2000E4B26846FFF7A9FF0028F8D02846FFF75C +:103E3000E5FF001BC0B23EBD59498978814201D9D6 +:103E4000C0B27047FF2070472DE9F041544B06295E +:103E500003D007291CD19D7900E0002500244FF6EE +:103E6000FF7603EB810713F801C00AE06319D7F866 +:103E700028E09BB25EF823E0BEF1000F04D0641C82 +:103E8000A4B2A445F2D8334603801846B34201D108 +:103E900000201CE7BDE8F041EEE6A0F57F43FF3BC4 +:103EA00001D0082901D300207047E5E6A0F57F4244 +:103EB000FF3A0BD0082909D2394A9378834205D9B1 +:103EC00002EB8101896A51F8200070470020704799 +:103ED0002DE9F04104460D46A4F57F4143F202006E +:103EE000FF3902D0082D01D30720F0E62C494FF00E +:103EF00000088A78A242F8D901EB8506B26A52F826 +:103F00002470002FF1D027483946203050F8252062 +:103F100020469047B16A284641F8248000F007F80F +:103F200002463946B068FFF727FE0020CFE61D495C +:103F3000403131F810004FF6FC71C01C084070474A +:103F40002DE9F843164E8846054600242868C01C13 +:103F500020F0030028602046FFF7E9FF315D484369 +:103F6000B8F1000F01D0002200E02A68014600925B +:103F700032B100274FEA0D00FFF7B5FD1FB106E093 +:103F800001270020F8E706EB8401009A8A6029687F +:103F9000641C0844E4B22860082CD7D3EBE6000088 +:103FA0003C0800201862020070B50E461D461146FE +:103FB00000F0D3F804462946304600F0D7F82044F4 +:103FC000001D70BD2DE9F04190460D4604004FF0F4 +:103FD000000610D00027E01C20F00300A04200D013 +:103FE000FFDFE5B141460020FFF77DFD0C3000EB1F +:103FF000850617B113E00127EDE7614F04F10C00CE +:10400000AA003C602572606000EB85002060002102 +:104010006068FAF73CFA41463868FFF764FD3046BD +:10402000BDE8F0812DE9FF4F554C804681B02068F6 +:104030009A46934600B9FFDF2068027A424503D9C9 +:10404000416851F8280020B143F2020005B0BDE8F4 +:10405000F08F5146029800F080F886B258460E99CB +:1040600000F084F885B27019001D87B22068A1465F +:1040700039460068FFF755FD04001FD06780258092 +:104080002946201D0E9D07465A4601230095FFF73D +:1040900065F92088314638440123029ACDF800A002 +:1040A000FFF75CF92088C1193846FFF788F9D9F87D +:1040B00000004168002041F82840C7E70420C5E718 +:1040C00070B52F4C0546206800B9FFDF2068017AE3 +:1040D000A9420DD9426852F8251049B1002342F88F +:1040E00025304A880068FFF747FD2168087A06E016 +:1040F00043F2020070BD4A6852F820202AB9401EDF +:10410000C0B2F8D20868FFF701FD002070BD70B59D +:104110001B4E05460024306800B9FFDF3068017A85 +:10412000A94204D9406850F8250000B1041D20467A +:1041300070BD70B5124E05460024306800B9FFDF2F +:104140003068017AA94206D9406850F8251011B1AB +:1041500031F8040B4418204670BD10B50A46012101 +:10416000FFF7F5F8C01C20F0030010BD10B50A469B +:104170000121FFF7ECF8C01C20F0030010BD000087 +:104180008C00002070B50446C2F110052819FAF71A +:1041900054F915F0FF0109D0491ECAB28020A0547D +:1041A0002046BDE870400021FAF771B970BD30B506 +:1041B00005E05B1EDBB2CC5CD55C6C40C454002BCC +:1041C000F7D130BD10B5002409E00B78521E44EA47 +:1041D000430300F8013B11F8013BD2B2DC09002A8D +:1041E000F3D110BD2DE9F04389B01E46DDE9107909 +:1041F00090460D00044622D002460846F949FDF7D4 +:1042000044FE102221463846FFF7DCFFE07B000623 +:1042100006D5F44A3946102310320846FFF7C7FF87 +:10422000102239464846FFF7CDFFF87B000606D539 +:10423000EC4A4946102310320846FFF7B8FF102217 +:1042400000212046FAF723F90DE0103EB6B208EB44 +:104250000601102322466846FFF7A9FF224628469A +:104260006946FDF712FE102EEFD818D0F2B2414683 +:104270006846FFF787FF10234A46694604A8FFF700 +:1042800096FF1023224604A96846FFF790FF2246B6 +:1042900028466946FDF7F9FD09B0BDE8F083102313 +:1042A0003A464146EAE770B59CB01E4605461346BD +:1042B00020980C468DF80800202219460DF10900BF +:1042C000FAF7BBF8202221460DF12900FAF7B5F8DC +:1042D00017A913A8CDE90001412302AA31462846B7 +:1042E000FFF780FF1CB070BD2DE9FF4F9FB014AEEB +:1042F000DDE92D5410AFBB49CDE9007620232031F4 +:104300001AA8FFF76FFF4FF000088DF808804FF0F4 +:1043100001098DF8099054F8010FCDF80A00A08822 +:10432000ADF80E0014F8010C1022C0F340008DF817 +:10433000100055F8010FCDF81100A888ADF8150050 +:1043400015F8010C2C99C0F340008DF8170006A851 +:104350008246FAF772F80AA8834610222299FAF7E1 +:104360006CF8A0483523083802AA40688DF83C80D4 +:10437000CDE900760E901AA91F98FFF733FF8DF84C +:1043800008808DF809902068CDF80A00A088ADF863 +:104390000E0014F8010C1022C0F340008DF810003C +:1043A0002868CDF81100A888ADF8150015F8010CA3 +:1043B0002C99C0F340008DF817005046FAF73DF8ED +:1043C000584610222299FAF738F8864835230838DB +:1043D00002AA40688DF83C90CDE900760E901AA9AB +:1043E0002098FFF7FFFE23B0BDE8F08FF0B59BB03B +:1043F0000C460546DDE922101E461746DDE920324F +:10440000D0F801C0CDF808C0B0F805C0ADF80CC0B8 +:104410000078C0F340008DF80E00D1F80100CDF80F +:104420000F00B1F80500ADF8130008781946C0F385 +:1044300040008DF815001088ADF8160090788DF8C2 +:1044400018000DF119001022F9F7F7FF0DF12900FE +:1044500010223146F9F7F1FF0DF1390010223946EB +:10446000F9F7EBFF17A913A8CDE90001412302AA30 +:1044700021462846FFF7B6FE1BB0F0BDF0B5A3B04D +:1044800017460D4604461E46102202A82899F9F741 +:10449000D4FF06A820223946F9F7CFFF0EA8202224 +:1044A0002946F9F7CAFF1EA91AA8CDE90001502331 +:1044B00002AA314616A8FFF795FE1698206023B091 +:1044C000F0BDF0B589B00446DDE90E070D46397838 +:1044D000109EC1F340018DF8001031789446C1F36D +:1044E00040018DF801101968CDF802109988ADF8D7 +:1044F000061099798DF808100168CDF809108188A7 +:10450000ADF80D1080798DF80F0010236A466146D2 +:1045100004A8FFF74CFE2246284604A9FDF7B5FC87 +:10452000D6F801000090B6F80500ADF80400D7F801 +:104530000100CDF80600B7F80500ADF80A0000202C +:10454000039010236A46214604A8FFF730FE224656 +:10455000284604A9FDF799FC09B0F0BD1FB51C68F9 +:1045600000945B68019313680293526803920246B9 +:1045700008466946FDF789FC1FBD10B588B00446A2 +:104580001068049050680590002006900790084637 +:104590006A4604A9FDF779FCBDF80000208008B048 +:1045A00010BD1FB51288ADF800201A88ADF80220A2 +:1045B0000022019202920392024608466946FDF7E4 +:1045C00064FC1FBD7FB5074B14460546083B9A1C8B +:1045D0006846FFF7E6FF224669462846FFF7CDFF0B +:1045E0007FBD00007062020070B5044600780E4680 +:1045F000012813D0052802D0092813D10EE0A068A5 +:1046000061690578042003F059FA052D0AD0782352 +:1046100000220420616903F0A7F903E00420616926 +:1046200003F04CFA31462046BDE8704001F08AB8EC +:1046300010B500F12D03C2799C78411D144064F33C +:104640000102C271D2070DD04A795C7922404A71C9 +:104650000A791B791A400A718278C9788A4200D98E +:10466000817010BD00224A71F5E74178012900D020 +:104670000C21017070472DE9F04F93B04FF0000B03 +:104680000C690D468DF820B0097801260C201746DC +:104690004FF00D084FF0110A4FF008091B2975D291 +:1046A000DFE811F01B00C40207031F035E03710360 +:1046B000A303B803F9031A0462049504A204EF04E7 +:1046C0002D05370555056005F305360639066806DC +:1046D0008406FE062207EB06F00614B120781D289A +:1046E0002AD0D5F808805FEA08004FD001208DF865 +:1046F0002000686A02220D908DF824200A208DF88F +:104700002500A8690A90A8880028EED098F8001023 +:1047100091B10F2910D27DD2DFE801F07C1349DE80 +:10472000FCFBFAF9F8F738089CF6F50002282DD1C1 +:1047300024B120780C2801D00026F0E38DF8202049 +:10474000CBE10420696A03F0B9F9A8880728EED103 +:10475000204600F0F2FF022809D0204600F0EDFFCD +:10476000032807D9204600F0E8FF072802D20120DD +:10477000207004E0002CB8D020780128D7D198F818 +:104780000400C11F0A2902D30A2061E0C4E1A0701D +:10479000D8F80010E162B8F80410218698F80600F5 +:1047A00084F83200012028700320207044E007289C +:1047B000BDD1002C99D020780D28B8D198F80310DD +:1047C00094F82F20C1F3C000C2F3C002104201D000 +:1047D000062000E00720890707D198F8051001425C +:1047E000D2D198F806100142CED194F8312098F831 +:1047F000051020EA02021142C6D194F8322098F83E +:10480000061090430142BFD198F80400C11F0A2945 +:10481000BAD200E008E2617D81427CD8D8F800106D +:104820006160B8F80410218198F80600A072012098 +:1048300028700E20207003208DF82000686A0D90EB +:1048400004F12D000990601D0A900F300B9022E1B9 +:104850002875FCE3412891D1204600F06EFF042822 +:1048600002D1E078C00704D1204600F066FF0F288F +:1048700084D1A88CD5F80C8080B24FF0400BE6694B +:10488000FFF745FC324641465B464E46CDF8009068 +:10489000FFF731F80B208DF82000686A0D90E06971 +:1048A0000990002108A8FFF79FFE2078042806D071 +:1048B000A07D58B1012809D003280AD04AE3052079 +:1048C0002070032028708DF82060CEE184F800A0CD +:1048D00032E712202070EAE11128BCD1204600F016 +:1048E0002CFF042802D1E078C00719D0204600F040 +:1048F00024FF062805D1E078C00711D1A07D022849 +:104900000ED0204608E0CCE084E072E151E124E1E1 +:1049100003E1E9E019E0B0E100F00FFF11289AD1BE +:10492000102208F1010104F13C00F9F786FD6078DE +:1049300001286ED012202070E078C00760D0A07DE2 +:104940000028C8D00128C6D05AE0112890D12046AE +:1049500000F0F3FE082804D0204600F0EEFE1328F5 +:1049600086D104F16C00102208F101010646F9F726 +:1049700064FD207808280DD014202070E178C80745 +:104980000DD0A07D02280AD06278022A04D0032824 +:10499000A1D035E00920F0E708B1012837D1C807D8 +:1049A00013D0A07D02281DD000200090D4E906215C +:1049B00033460EA8FFF777FC10220EA904F13C0045 +:1049C000F9F70EFDC8B1042042E7D4E90912201D11 +:1049D0008DE8070004F12C0332460EA8616BFFF747 +:1049E00070FDE9E7606BC1F34401491E0068C840EF +:1049F00000F0010040F08000D7E72078092806D1B8 +:104A000085F800908DF8209036E32870EFE30920B8 +:104A1000FBE79EE1112899D1204600F08EFE0A287E +:104A200002D1E078C00704D1204600F086FE1528A8 +:104A30008CD104F13C00102208F101010646F9F77F +:104A4000FCFC20780A2816D016202070D4E9093200 +:104A5000606B611D8DE80F0004F15C0304F16C02D2 +:104A600047310EA8FFF7C2FC10220EA93046F9F715 +:104A7000B7FC18B1F9E20B20207073E22046FFF773 +:104A8000D7FDA078216AC0F110020B18002118464A +:104A9000F9F7FDFC26E3394608A8FFF7A5FD064611 +:104AA0003CE20228B7D1204600F047FE042804D398 +:104AB000204600F042FE082809D3204600F03DFEC3 +:104AC0000E2829D3204600F038FE122824D2A07DDB +:104AD0000228A0D10E208DF82000686A0D9098F869 +:104AE00001008DF82400F5E3022894D1204600F05F +:104AF00024FE002810D0204600F01FFE0128F9D027 +:104B0000204600F01AFE0C28F4D004208DF8240072 +:104B100098F801008DF8250060E21128FCD1002CE6 +:104B2000FAD020781728F7D16178606A022912D06C +:104B30005FF0000101EB4101182606EBC1011022D4 +:104B4000405808F10101F9F778FC0420696A00F087 +:104B5000E7FD2670F0E50121ECE70B28DCD1002C05 +:104B6000DAD020781828D7D16078616A02281CD062 +:104B70005FF0000000EB4002102000EBC20009587B +:104B8000B8F8010008806078616A02280FD0002020 +:104B900000EB4002142000EBC2000958404650F8D8 +:104BA000032F0A604068486039E00120E2E70120F5 +:104BB000EEE71128B0D1002CAED020781928ABD167 +:104BC0006178606A022912D05FF0000101EB4101B7 +:104BD0001C2202EBC1011022405808F10101F9F733 +:104BE0002CFC0420696A00F09BFD1A20B6E001212C +:104BF000ECE7082890D1002C8ED020781A288BD191 +:104C0000606A98F80120017862F347010170616AD7 +:104C1000D8F8022041F8012FB8F806008880042057 +:104C2000696A00F07DFD90E2072011E638780128DE +:104C300094D1182204F114007968F9F7FEFBE079A9 +:104C4000C10894F82F0001EAD001E07861F3000078 +:104C5000E070217D002974D12178032909D0C00793 +:104C600025D0032028708DF82090686A0D9041208F +:104C700008E3607DA178884201D90620E8E5022694 +:104C80002671E179204621F0E001E171617A21F09D +:104C9000F0016172A17A21F0F001A172FFF7C8FC66 +:104CA0002E708DF82090686A0D900720EAE20420AB +:104CB000ABE6387805289DD18DF82000686A0D9004 +:104CC000B8680A900720ADF824000A988DF830B033 +:104CD0006168016021898180A17A8171042020703E +:104CE000F8E23978052985D18DF82010696A0D918F +:104CF000391D09AE0EC986E80E004121ADF8241019 +:104D00008DF830B01070A88CD7F80C8080B2402697 +:104D1000A769FFF70EFA41463A463346C846CDF832 +:104D20000090FEF71CFE002108A8FFF75DFCE0786C +:104D300020F03E00801CE0702078052802D00F2073 +:104D40000CE04AE1A07D20B1012802D0032802D066 +:104D500002E10720BEE584F80080EDE42070EBE47A +:104D6000102104F15C0002F0C2FB606BB0BBA07DBF +:104D700018B1012801D00520FDE006202870F84870 +:104D80006063A063C2E23878022894D1387908B110 +:104D90002875B7E3A07D022802D0032805D022E0C1 +:104DA000B8680028F5D060631CE06078012806D060 +:104DB000A07994F82E10012805D0E94806E0A179E1 +:104DC00094F82E00F7E7B8680028E2D06063E07836 +:104DD000C00701D0012902D0E14803E003E0F868F0 +:104DE0000028D6D0A06306200FE68DF82090696ACF +:104DF0000D91E1784846C90709D06178022903D1AD +:104E0000A17D29B1012903D0A17D032900D007206C +:104E1000287033E138780528BBD1207807281ED0C8 +:104E200084F800A005208DF82000686A0D90B8680D +:104E30000A90ADF824A08DF830B003210170E1781C +:104E4000CA070FD0A27D022A1AD000210091D4E90E +:104E5000061204F15C03401CFFF725FA6BE384F8AB +:104E60000090DFE7D4E90923211D8DE80E0004F14D +:104E70002C0304F15C02401C616BFFF722FB5AE338 +:104E8000626BC1F34401491E1268CA4002F001017D +:104E900041F08001DAE738780528BDD18DF820008F +:104EA000686A0D90B8680A90ADF824A08DF830B00B +:104EB000042100F8011B102204F15C01F9F7BDFA8E +:104EC000002108A8FFF790FB2078092801D01320C3 +:104ED00044E70A2020709AE5E078C10742D0A17D1E +:104EE000012902D0022927D038E0617808A80129D9 +:104EF00016D004F16C010091D4E9061204F15C03B0 +:104F0000001DFFF7BBFA0A20287003268DF82080C9 +:104F1000686A0D90002108A8FFF766FBE1E2C7E28E +:104F200004F15C010091D4E9062104F16C03001D39 +:104F3000FFF7A4FA0026E9E7C0F3440114290DD2D3 +:104F40004FF0006101EBB0104FEAB060E0706078A4 +:104F5000012801D01020BDE40620FFE6607801287A +:104F60003FF4B6AC0A2050E5E178C90708D0A17D2E +:104F7000012903D10B202870042030E028702EE096 +:104F80000E2028706078616B012818D004F15C0352 +:104F900004F16C020EA8FFF7E1FA2046FFF748FB88 +:104FA000A0780EAEC0F1100230440021F9F76FFA7C +:104FB00006208DF82000686A09960D909BE004F1A8 +:104FC0006C0304F15C020EA8FFF7C8FAE8E7397831 +:104FD000022903D139790029D0D0297592E28DF8C0 +:104FE0002000686A0D9056E538780728F6D1D4E994 +:104FF00009216078012808D004F16C00CDE9000295 +:10500000029105D104F16C0304E004F15C00F5E7C2 +:1050100004F15C0304F14C007A680646216AFFF74C +:1050200063F96078012822D1A078216AC0F11002CA +:105030000B1800211846F9F72AFAD4E90923606B06 +:1050400004F12D018DE80F0004F15C0300E05BE248 +:1050500004F16C0231460EA8FFF7C8F910220EA920 +:1050600004F13C00F9F7BCF908B10B20ACE485F879 +:10507000008000BF8DF82090686A0D908DF824A004 +:1050800009E538780528A9D18DF82000686A0D90C7 +:10509000B8680A90ADF824A08DF830B080F8008090 +:1050A000617801291AD0D4E9092104F12D03A66BF6 +:1050B00003910096CDE9013204F16C0304F15C0226 +:1050C00004F14C01401CFFF791F9002108A8FFF7FB +:1050D0008BFA6078012805D015203FE6D4E9091243 +:1050E000631DE4E70E20287006208DF82000686A12 +:1050F000CDF824B00D90A0788DF82800CBE4387856 +:105100000328C0D1E079C00770D00F202870072095 +:1051100065E7387804286BD11422391D04F1140096 +:10512000F9F78BF9616A208CA1F80900616AA0780F +:10513000C871E179626A01F003011172616A627AF1 +:105140000A73616AA07A81F8240016205DE485F86C +:1051500000A08DF82090696A50460D9192E0000001 +:10516000706202003878052842D1B868A861617879 +:10517000606A022901D0012100E0002101EB410118 +:10518000142606EBC1014058082102F0B0F96178FD +:10519000606A022901D0012100E0002101EB4101F8 +:1051A00006EBC101425802A8E169FFF70BFA6078EB +:1051B000626A022801D0012000E0002000EB4001DB +:1051C000102000EBC1000223105802A90932FEF79B +:1051D000EEFF626AFD4B0EA80932A169FFF7E1F903 +:1051E0006178606A022904D0012103E044E18DE086 +:1051F000BFE0002101EB4101182606EBC101A278B6 +:1052000040580EA9F9F719F96178606A022901D0AE +:10521000012100E0002101EB410106EBC1014158F1 +:10522000A0780B18C0F1100200211846F9F72FF9E9 +:1052300005208DF82000686A0D90A8690A90ADF8E5 +:1052400024A08DF830B0062101706278616A022ACC +:1052500001D0012200E0002202EB420206EBC20272 +:10526000401C89581022F9F7E8F8002108A8FFF738 +:10527000BBF91220C5F818B028708DF82090686A24 +:105280000D900B208DF8240005E43878052870D1A6 +:105290008DF82000686A0D90B8680A900B20ADF870 +:1052A00024000A98072101706178626A022901D0FE +:1052B000012100E0002101EB4103102101EBC301BA +:1052C00051580988A0F801106178626A022902D059 +:1052D000012101E02FE1002101EB4103142101EB49 +:1052E000C30151580A6840F8032F4968416059E0EA +:1052F0001920287001208DF8300074E616202870DF +:105300008DF830B0002108A8FFF76EF9032617E1E9 +:1053100014202870AEE6387805282AD18DF82000B0 +:10532000686A0D90B8680A90ADF824A08DF830B086 +:1053300080F800906278616A4E46022A01D001220C +:1053400000E0002202EB42021C2303EBC202401CDD +:1053500089581022F9F771F8002108A8FFF744F9DD +:10536000152028708DF82060686A0D908DF82460F3 +:1053700039E680E0387805287DD18DF82000686A0C +:105380000D90B8680A90ADF8249009210170616908 +:10539000097849084170616951F8012FC0F802206D +:1053A0008988C18020781C28A8D1A1E7E078C007AF +:1053B00002D04FF0060C01E04FF0070C6078022895 +:1053C0000AD000BF4FF0000000EB040101F1090119 +:1053D00005D04FF0010004E04FF00100F4E74FF07A +:1053E00000000B78204413EA0C030B7010F8092F0F +:1053F00002EA0C02027004D14FF01B0C84F800C0CA +:10540000D2B394F801C0BCF1010F00D09BB990F861 +:1054100000C0E0465FEACC7C04D028F001060670AC +:10542000102606E05FEA887C05D528F002060670A3 +:1054300013262E70032694F801C0BCF1020F00D091 +:1054400092B991F800C05FEACC7804D02CF0010644 +:105450000E70172106E05FEA8C7805D52CF0020665 +:105460000E701921217000260078D0BBCAB3C3BBCF +:105470001C20207035E012E002E03878062841D187 +:105480001A2015E4207801283CD00C283AD0204678 +:10549000FFF7EBF809208DF82000686A0D9031E0E5 +:1054A0003878052805D00620387003261820287083 +:1054B00046E005208DF82000696A0D91B9680A91CF +:1054C0000221ADF8241001218DF830100A990870DE +:1054D000287D4870394608A8FFF786F80646182048 +:1054E0002870012E0ED02BE001208DF82000686A74 +:1054F0000D9003208DF82400287D8DF8250085F877 +:1055000014B012E0287D80B11D2020701720287073 +:105510008DF82090686A0D9002208DF8240039469D +:1055200008A8FFF761F806460AE00CB1FE202070DB +:105530009DF8200020B1002108A8FFF755F80CE4E1 +:1055400013B03046BDE8F08F2DE9F04387B00C462C +:105550004E6900218DF804100120257803460227AA +:105560004FF007094FF0050C85B1012D53D0022DE6 +:1055700039D1FE2030708DF80030606A059003202C +:105580008DF80400207E8DF8050063E02179012963 +:1055900025D002292DD0032928D0042923D1B17D7B +:1055A000022920D131780D1F042D04D30A3D032D8B +:1055B00001D31D2917D12189022914D38DF8047034 +:1055C000237020899DF80410884201E0686202007F +:1055D00018D208208DF80000606A059057E07078B6 +:1055E0000128EBD0052007B0BDE8F0831D20307006 +:1055F000E4E771780229F5D131780C29F3D18DF8DF +:105600000490DDE7083402F804CB94E80B0082E84C +:105610000B000320E7E71578052DE4D18DF800C0D5 +:10562000656A0595956802958DF8101094F80480C8 +:10563000B8F1010F13D0B8F1020F2DD0B8F1030F5C +:105640001CD0B8F1040FCED1ADF804700E20287034 +:10565000207E687000216846FEF7C6FF0CE0ADF8BA +:1056600004700B202870207E002100F01F0068705D +:105670006846FEF7B9FF37700020B4E7ADF8047054 +:105680008DF8103085F800C0207E687027701146B4 +:105690006846FEF7A9FFA6E7ADF804902B70207FBF +:1056A0006870607F00F00100A870A07F00F01F000C +:1056B000E870E27F2A71C0071CD094F8200000F047 +:1056C0000700687194F8210000F00700A87100211C +:1056D0006846FEF789FF2868F062A8883086A879B6 +:1056E00086F83200A069407870752879B0700D2076 +:1056F0003070C1E7A9716971E9E700B587B0042886 +:105700000CD101208DF800008DF8040000200591D7 +:105710008DF8050001466846FEF766FF07B000BD3C +:1057200070B50C46054602F0C9F921462846BDE889 +:1057300070407823002202F017B908B10078704752 +:105740000C20704770B50C0005784FF000010CD0AC +:1057500021702146EFF7D1FD69482178405D8842EC +:1057600001D1032070BD022070BDEFF7C6FD0020FF +:1057700070BD0279012A05D000220A704B78012BF6 +:1057800002D003E0042070470A758A610279930011 +:10579000521C0271C15003207047F0B587B00F460C +:1057A00005460124287905EB800050F8046C7078D8 +:1057B000411E02290AD252493A46083901EB8000BB +:1057C000314650F8043C2846984704460CB1012C59 +:1057D00011D12879401E10F0FF00287101D0032458 +:1057E000E0E70A208DF80000706A0590002101961C +:1057F0006846FFF7A7FF032CD4D007B02046F0BDC2 +:1058000070B515460A46044629461046FFF7C5FFFF +:10581000064674B12078FE280BD1207C30B10020E0 +:105820002870294604F10C00FFF7B7FF2046FEF769 +:105830001CFF304670BD704770B50E4604467C2292 +:105840000021F8F724FE0225012E03D0022E04D0F9 +:10585000052070BD0120607000E065702046FEF7F5 +:1058600004FFA575002070BD28B1027C1AB10A465C +:1058700000F10C01C4E70120704710B5044686B062 +:10588000042002F01BF92078FE2806D000208DF8B5 +:10589000000069462046FFF7E7FF06B010BD7CB563 +:1058A0000E4600218DF804104178012903D0022909 +:1058B00003D0002405E0046900E044690CB1217CB8 +:1058C00089B16D4601462846FFF753FF032809D1E9 +:1058D000324629462046FFF793FF9DF80410002921 +:1058E00000D004207CBD04F10C05EBE730B40C467D +:1058F0000146034A204630BC024B0C3AFEF751BE2B +:10590000AC6202006862020070B50D46040011D05E +:1059100085B1220100212846F8F7B9FD102250492F +:105920002846F8F78AFD4F48012101704470456010 +:10593000002070BD012070BD70B505460024494EA1 +:1059400011E07068AA7B00EB0410817B914208D1C2 +:10595000C17BEA7B914204D10C222946F8F740FD35 +:1059600030B1641CE4B230788442EAD3002070BDC8 +:10597000641CE0B270BD70B50546FFF7DDFF00287E +:1059800005D1384C20786178884201D3002070BD61 +:105990006168102201EB00102946F8F74EFD2078CF +:1059A000401CC0B2207070BD2E48007870472D4951 +:1059B0000878012802D0401E08700020704770B59A +:1059C0000D460021917014461180022802D0102843 +:1059D00015D105E0288890B10121A17010800CE05C +:1059E000284613B1FFF7C7FF01E0FFF7A5FFA0703E +:1059F00010F0FF0F03D0A8892080002070BD012087 +:105A000070BD0023DBE770B5054614460E0009D0D3 +:105A100000203070A878012806D003D911490A78EF +:105A200090420AD9012070BD24B1287820702888BE +:105A3000000A5070022008700FE064B1496810221B +:105A400001EB001120461039F8F7F7FC2878207395 +:105A50002888000A607310203070002070BD00009C +:105A6000BB620200900000202DE9F04190460C46F8 +:105A700007460025FE48072F00EB881607D2DFE80F +:105A800007F00707070704040400012500E0FFDF13 +:105A900006F81470002D13D0F548803000EB880113 +:105AA00091F82700202803D006EB4000447001E065 +:105AB00081F8264006EB44022020507081F82740F0 +:105AC000BDE8F081F0B51F4614460E460546202A73 +:105AD00000D1FFDFE649E648803100EB871C0CEB84 +:105AE000440001EB8702202E07D00CEB46014078E2 +:105AF0004B784870184620210AE092F8253040780B +:105B000082F82500F6E701460CEB4100057040786D +:105B1000A142F8D192F82740202C03D00CEB44048A +:105B2000637001E082F826300CEB4104202363709F +:105B300082F82710F0BD30B50D46CE4B4419002237 +:105B4000181A72EB020100D2FFDFCB48854200DD5C +:105B5000FFDFC9484042854200DAFFDFC548401CEC +:105B6000844207DA002C01DB204630BDC148401CCE +:105B7000201830BDBF48C043FAE710B5044601689D +:105B8000407ABE4A52F82020114450B10220084405 +:105B900020F07F40EDF763F894F90810BDE810405D +:105BA000C9E70420F3E72DE9F047B14E803696F8B7 +:105BB0002D50DFF8BC9206EB850090F8264034E0CB +:105BC00009EB85174FF0070817F81400012806D0D5 +:105BD00004282ED005282ED0062800D0FFDF01F0A3 +:105BE00025F9014607EB4400427806EB850080F872 +:105BF000262090F82720A24202D1202280F82720D8 +:105C0000084601F01EF92A4621460120FFF72CFF25 +:105C10009B48414600EB041002682046904796F8E6 +:105C20002D5006EB850090F82640202CC8D1BDE809 +:105C3000F087022000E003208046D0E710B58C4CAE +:105C40002021803484F8251084F8261084F8271049 +:105C5000002084F8280084F82D0084F82E10411EBE +:105C6000A16044F8100B2074607420736073A073FB +:105C70008449E07720750870487000217C4A103C08 +:105C800002F81100491CC9B22029F9D30120ECF710 +:105C9000D6FE0020ECF7D3FE012084F82200EDF7B9 +:105CA000FFF87948EDF711F9764CA41E207077487B +:105CB000EDF70BF96070BDE81040ECF74DBE10B584 +:105CC000ECF76FFE6F4CA41E2078EDF717F96078A3 +:105CD000EDF714F9BDE8104001F0E0B8202070475E +:105CE0000020ECF785BE70B5054601240E46AC4099 +:105CF0005AB1FFF7F5FF0146654800EBC500C0F853 +:105D00001015C0F81465634801E06248001D046086 +:105D100070BD2DE9F34F564C0025803404EB810A09 +:105D200089B09AF82500202821D0691E0291544993 +:105D3000009501EB0017391D03AB07C983E8070085 +:105D4000A18BADF81C10A07F8DF81E009DF81500EA +:105D5000A046C8B10226494951F820400399A2192A +:105D6000114421F07F41019184B102210FE0012013 +:105D7000ECF765FE0020ECF762FEECF730FE01F078 +:105D80008DF884F82F50A9E00426E4E700218DF86F +:105D90001810022801D0012820D103980119099870 +:105DA000081A801C9DF81C1020F07F4001B10221D0 +:105DB000353181420BD203208DF815000398C4F1D0 +:105DC0003201401A20F07F40322403900CE098F812 +:105DD000240018B901F043FA002863D0322C03D212 +:105DE00014B101F04FF801E001F058F8254A10789D +:105DF00018B393465278039B121B00219DF818405C +:105E0000994601281AD0032818D000208DF81E00CA +:105E1000002A04DD981A039001208DF818009DF8DF +:105E20001C0000B1022103981B4A20F07F40039020 +:105E300003AB099801F03EF810B110E00120E5E74E +:105E40009DF81D0018B99BF80000032829D08DF893 +:105E50001C50CDF80C908DF818408DF81E509DF810 +:105E6000180010B30398012381190022184615E089 +:105E7000840A0020FF7F841E0020A107CC6202005C +:105E8000840800209A00002017780100A75B010019 +:105E900000F0014004F50140FFFF3F00ECF722FE57 +:105EA00006E000200BB0BDE8F08F0120ECF7C7FD45 +:105EB00097F90C20012300200199ECF713FEF87BE1 +:105EC000C00701D0ECF7F7FE012188F82F108AF8FF +:105ED000285020226946FE48F8F7AFFA0120E1E792 +:105EE0002DE9F05FDFF8E883064608EB860090F8BE +:105EF0002550202D1FD0A8F180002C4600EB8617DE +:105F0000A0F50079DFF8CCB305E0A24607EB4A0024 +:105F10004478202C0AD0ECF730FE09EB04135A46E3 +:105F200001211B1D00F0C6FF0028EED0AC4202D0BC +:105F3000334652461EE0E84808B1AFF30080ECF764 +:105F40001CFE98F82F206AB1D8F80C20411C891A41 +:105F50000902CA1701EB12610912002902DD0020B3 +:105F6000BDE8F09F3146FFF7D4FE08B10120F7E706 +:105F700033462A4620210420FFF7A4FDEFE72DE950 +:105F8000F041D34C2569ECF7F8FD401B0002C11726 +:105F900000EB1160001200D4FFDF94F8220000B182 +:105FA000FFDF012784F8227094F82E00202800D10A +:105FB000FFDF94F82E60202084F82E00002584F85E +:105FC0002F5084F8205084F82150C4482560007870 +:105FD000022833D0032831D000202077A068401C4D +:105FE00005D04FF0FF30A0600120ECF728FD002025 +:105FF000ECF725FDECF721FEECF719FEECF7EFFCD2 +:106000000EF0D6FDB648056005604FF0E0214FF474 +:106010000040B846C1F88002ECF7BBFE94F82D7042 +:106020003846FFF75DFF0028FAD0A948803800EB1A +:10603000871010F81600022802D006E00120CCE7F5 +:106040003A4631460620FFF70FFD84F8238004EB23 +:10605000870090F82600202804D0A048801E4078B1 +:10606000ECF752FF207F002803D0ECF7D6FD257710 +:10607000657725E5964910B591F82D2000248039E3 +:1060800001EB821111F814302BB1641CE4B2202C06 +:10609000F8D3202010BD934901EB041108600020C3 +:1060A000C87321460120FFF7DFFC204610BD10B564 +:1060B000012801D0032800D171B3854A92F82D3010 +:1060C000834C0022803C04EB831300BF13F8124082 +:1060D0000CB1082010BD521CD2B2202AF6D37F4A40 +:1060E00048B1022807D0072916D2DFE801F01506CB +:1060F000080A0C0E100000210AE01B2108E03A21DA +:1061000006E0582104E0772102E0962100E0B52165 +:1061100051701070002010BD072010BD6F4810B5E1 +:106120004078ECF79CFD80B210BD10B5202811D24C +:10613000674991F82D30A1F1800202EB831414F825 +:1061400010303BB191F82D3002EB831212F8102081 +:10615000012A01D0002010BD91F82D200146002019 +:10616000FFF782FC012010BD10B5ECF706FDBDE87D +:106170001040ECF774BD2DE9F0410E46544F017804 +:106180002025803F0C4607EB831303E0254603EBF5 +:1061900045046478944202D0202CF7D108E0202CEA +:1061A00006D0A14206D103EB41014978017007E016 +:1061B000002085E403EB440003EB45014078487080 +:1061C000494F7EB127B1002140F22D40AFF300804E +:1061D0003078A04206D127B100214FF48660AFF39A +:1061E0000080357027B1002140F23540AFF30080C8 +:1061F000012065E410B542680B689A1A1202D417A0 +:1062000002EB1462121216D4497A91B1427A82B921 +:10621000364A006852F82110126819441044001DD3 +:10622000891C081A0002C11700EB11600012322805 +:1062300001DB012010BD002010BD2DE9F047294EE3 +:10624000814606F500709846144600EB811712E06F +:1062500006EB0415291D4846FFF7CCFF68B988F8FE +:106260000040A97B99F80A00814201D80020DEE4B1 +:1062700007EB44004478202CEAD10120D7E42DE933 +:10628000F047824612480E4600EB8600DFF8548045 +:1062900090F825402020107008F5007099461546AA +:1062A00000EB86170BE000BF08EB04105146001D01 +:1062B000FFF7A0FF28B107EB44002C704478202C96 +:1062C000F2D1297889F800104B46224631460FE07A +:1062D000040B0020FFFF3F00000000009A00002098 +:1062E00000F500408408002000000000CC6202009D +:1062F0005046BDE8F047A0E72DE9FC410F460446B3 +:106300000025FE4E10E000BF9DF80000256806EB5A +:1063100000108168204600F0E1FD2068A84202D10B +:106320000020BDE8FC8101256B4601AA39462046C4 +:10633000FFF7A5FF0028E7D02846F2E770B504462E +:10634000EF480125A54300EB841100EB85104022A6 +:10635000F8F773F8EB4E26B1002140F29D40AFF301 +:106360000080E748803000EB850100EB8400D0F826 +:106370002500C1F8250026B1002140F2A140AFF36D +:106380000080284670BD8A4203D003460520FFF7EF +:1063900099BB202906D0DA4A02EB801000EB4100BD +:1063A00040787047D649803101EB800090F8250095 +:1063B0007047D24901EB0010001DFFF7DEBB7CB532 +:1063C0001D46134604460E4600F1080221461846B3 +:1063D000ECF752FC94F908000F2804DD1F382072F6 +:1063E0002068401C206096B10220C74951F8261051 +:1063F000461820686946801B20F07F40206094F991 +:1064000008002844C01C1F2803DA012009E00420EA +:10641000EBE701AAECF730FC9DF8040010B10098FE +:10642000401C00900099206831440844C01C20F0B2 +:106430007F4060607CBDFEB50C46064609786079F9 +:10644000907220791F461546507279B12179002249 +:106450002846A368FFF7B3FFA9492846803191F881 +:106460002E20202A0AD00969491D0DE0D4E9022313 +:10647000217903B02846BDE8F040A0E7A349497858 +:10648000052900D20521314421F07F4100F026FD8D +:1064900039462846FFF730FFD4E9023221796846B1 +:1064A000FFF78DFF2B4600213046019A00F002FDD8 +:1064B000002806D103B031462846BDE8F04000F080 +:1064C0000DBDFEBD2DE9F14F84B000F0C3FCF0B16D +:1064D00000270498007800284FF000006DD1884D07 +:1064E000884C82468346803524B1002140F2045016 +:1064F000AFF3008095F82D8085F823B0002624B1F5 +:10650000002140F20950AFF3008017B105E00127E8 +:10651000DFE74046FFF712FF804624B1002140F23A +:106520001150AFF30080ECF728FB814643466A46E2 +:106530000499FFF780FF24B1002140F21750AFF318 +:10654000008095F82E0020280CD029690098401A68 +:106550000002C21700EB1260001203D5684600F07B +:10656000BDFC01264CB1002140F22150AFF3008068 +:10657000002140F22650AFF300806B46644A0021B0 +:10658000484600F097FC98B127B941466846FFF7A6 +:10659000B3FE064326B16846FFF7EFFA0499886018 +:1065A0004FF0010A24B1002140F23A50AFF30080CD +:1065B00095F82300002897D1504605B073E42DE9E3 +:1065C000F04F89B08B46824600F044FC4C4C80343E +:1065D00030B39BF80000002710B1012800D0FFDF86 +:1065E000484D25B1002140F2F950AFF300804349F6 +:1065F000012001EB0A18A946CDF81C005FEA090644 +:1066000004D0002140F20160AFF30080079800F051 +:1066100018FC94F82D50002084F8230067B119E08D +:1066200094F82E000127202800D1FFDF9BF80000FE +:106630000028D5D0FFDFD3E72846FFF77FFE0546C9 +:1066400026B1002140F20B60AFF3008094F82300E4 +:106650000028D3D126B1002140F21560AFF30080AD +:10666000ECF78BFA2B4602AA59460790FFF7E3FE98 +:1066700098F80F005FEA060900F001008DF813009A +:1066800004D0002140F21F60AFF300803B462A4651 +:1066900002A9CDF800A0079800F02BFC064604EBF9 +:1066A000850090F828000090B9F1000F04D0002177 +:1066B00040F22660AFF3008000F0B8FB0790B9F11C +:1066C000000F04D0002140F22C60AFF3008094F85A +:1066D0002300002892D1B9F1000F04D0002140F22C +:1066E0003460AFF300800DF1080C9CE80E00C8E99F +:1066F0000112C8F80C30BEB30CE000008408002082 +:10670000840A002000000000CC6202009A000020F1 +:10671000FFFF3F005FEA090604D0002140F241601C +:10672000AFF300800098B84312D094F82E002028D0 +:106730000ED126B1002140F24660AFF3008028461A +:10674000FFF7CEFB20B99BF80000D8B3012849D051 +:10675000B9F1000F04D0002140F26360AFF3008074 +:10676000284600F05CFB01265FEA090504D0002101 +:1067700040F26C60AFF30080079800F062FB25B137 +:1067800000214FF4CE60AFF300808EB194F82D005D +:1067900004EB800090F82600202809D025B10021C4 +:1067A00040F27760AFF30080F7484078ECF7ACFB3D +:1067B00025B1002140F27C60AFF3008009B0304683 +:1067C000BDE8F08FFFE7B9F1000F04D0002140F2DF +:1067D0004E60AFF3008094F82D2051460420FFF75F +:1067E00043F9C0E7002E3FF409AF002140F25960A1 +:1067F000AFF3008002E72DE9F84FE44D814695F8AC +:106800002D004FF00008E24C4FF0010B474624B139 +:10681000002140F28A60AFF30080584600F011FB7F +:1068200085F8237024B1002140F28F60AFF300801F +:1068300095F82D00FFF782FD064695F8230028B154 +:10684000002CE4D0002140F295604BE024B10021FF +:1068500040F29960AFF30080CC48803800EB86119D +:1068600011F81900032856D1334605EB830A4A462E +:106870009AF82500904201D1012000E0002000900C +:106880000AF125000021FFF776FC0146009801423D +:1068900003D001228AF82820AF77E1B324B1002188 +:1068A00040F29E60AFF30080324649460120FFF778 +:1068B000DBF89AF828A024B1002140F2A960AFF3D8 +:1068C000008000F0B3FA834624B1002140F2AE60AC +:1068D000AFF3008095F8230038B1002C97D0002149 +:1068E00040F2B260AFF3008091E7BAF1000F07D039 +:1068F00095F82E00202803D13046FFF7F1FAE0B1D9 +:1069000024B1002140F2C660AFF30080304600F0B1 +:1069100086FA4FF0010824B1002140F2CF60AFF3B6 +:106920000080584600F08DFA24B1002140F2D36077 +:10693000AFF300804046BDE8F88F002CF1D0002175 +:1069400040F2C160AFF30080E6E70120ECF750B8F9 +:106950008D48007870472DE9F0418C4C94F82E005A +:1069600020281FD194F82D6004EB860797F8255056 +:10697000202D00D1FFDF8549803901EB861000EB27 +:106980004500407807F8250F0120F87084F82300AF +:10699000294684F82E50324602202234FFF764F84C +:1069A0000020207005E42DE9F0417A4E774C012556 +:1069B00038B1012821D0022879D003287DD0FFDF0B +:1069C00017E400F05FFAFFF7C6FF207E00B1FFDF9B +:1069D00084F821500020ECF732F8A168481C04D05C +:1069E000012300221846ECF77DF814F82E0F2178C9 +:1069F00006EB01110A68012154E0FFF7ACFF01200A +:106A0000ECF71DF894F8210050B1A068401C07D0A5 +:106A100014F82E0F217806EB01110A68062141E0D7 +:106A2000207EDFF86481002708F10208012803D0E6 +:106A300002281ED0FFDFB5E7A777ECF7EEF898F84D +:106A40000000032801D165772577607D524951F810 +:106A5000200094F8201051B948B161680123091A47 +:106A600000221846ECF73EF8022020769AE72776B7 +:106A700098E784F8205000F005FAA07F50B198F80C +:106A8000010061680123091A00221846ECF72AF870 +:106A9000257600E0277614F82E0F217806EB0111F9 +:106AA0000A680021BDE8F041104700E005E03648E3 +:106AB0000078BDE8F041ECF727BAFFF74CFF14F877 +:106AC0002E0F217806EB01110A680521EAE710B5BF +:106AD0002E4C94F82E00202800D1FFDF14F82E0F42 +:106AE00021782C4A02EB01110A68BDE8104004210C +:106AF00010477CB5254C054694F82E00202800D17F +:106B0000FFDFA068401C00D0FFDF94F82E00214971 +:106B100001AA01EB0010694690F90C002844ECF73B +:106B2000ABF89DF904000F2801DD012000E00020F2 +:106B3000009908446168084420F07F41A16094F8FE +:106B40002100002807D002B00123BDE870400022D8 +:106B50001846EBF7C7BF7CBD30B5104A0B1A541C62 +:106B6000B3EB940F1ED3451AB5EB940F1AD393428F +:106B700003D9101A43185B1C14E0954210D9511A1E +:106B80000844401C43420DE098000020040B002004 +:106B90000000000084080020CC620200FF7F841EF9 +:106BA000FFDF0023184630BD0123002201460220EA +:106BB000EBF798BF0220EBF742BFEBF7DEBF2DE902 +:106BC000FE4FEE4C05468A4694F82E00202800D150 +:106BD000FFDFEA4E94F82E10A0462046A6F520725C +:106BE00002EB011420218DF8001090F82D10376968 +:106BF00000EB8101D8F8000091F82590284402AA02 +:106C000001A90C36ECF738F89DF90800002802DDE0 +:106C10000198401C0190A0680199642D084452D34A +:106C2000D74B00225B1B72EB02014CD36168411A07 +:106C300021F07F41B1F5800F45D220F07F40706098 +:106C400086F80AA098F82D1044466B464A4630460E +:106C5000FFF7F3FAB0B3A068401C10D0EBF78DFF3C +:106C6000A168081A0002C11700EB11600012022887 +:106C70002BDD0120EBF7E3FE4FF0FF30A06094F82E +:106C80002D009DF8002020210F34FFF77CFBA17F11 +:106C9000BA4A803A02EB8111E27F01EB420148706F +:106CA00054F80F0C284444F80F0C012020759DF86F +:106CB0000000202803D0B3484078ECF725F90120E4 +:106CC000BDE8FE8F01E00020FAE77760FBE72DE9E1 +:106CD000F047AA4C074694F82D00A4F1800606EB75 +:106CE000801010F8170000B9FFDF94F82D50A0466F +:106CF000A54C24B1002140F6EA00AFF3008040F635 +:106D0000F60940F6FF0A06EB851600BF16F81700D5 +:106D1000012819D0042811D005280FD006280DD03D +:106D20001CB100214846AFF300800FF02DF8002C75 +:106D3000ECD000215046AFF30080E7E72A46394601 +:106D40000120FEF791FEF2E74FF0010A4FF0000933 +:106D5000454624B1002140F60610AFF300805046AE +:106D600000F06FF885F8239024B1002140F60B1055 +:106D7000AFF3008095F82D00FFF7E0FA064695F88E +:106D8000230028B1002CE4D0002140F611101FE0B0 +:106D900024B1002140F61510AFF3008005EB86000A +:106DA00000F1270133463A462630FFF7E4F924B1D3 +:106DB000002140F61910AFF3008000F037F882464A +:106DC00095F8230038B1002CC3D0002140F61F10E5 +:106DD000AFF30080BDE785F82D60012085F8230022 +:106DE000504600F02EF8002C04D0002140F62C1064 +:106DF000AFF30080BDE8F08730B504465F480D462C +:106E000090F82D005D49803901EB801010F81400D6 +:106E100000B9FFDF5D4800EB0410C57330BD574972 +:106E200081F82D00012081F82300704710B55848E3 +:106E300008B1AFF30080EFF3108000F0010072B6EC +:106E400010BD10B5002804D1524808B1AFF300803E +:106E500062B610BD50480068C005C00D10D0103893 +:106E600040B2002804DB00F1E02090F8000405E0C7 +:106E700000F00F0000F1E02090F8140D4009704779 +:106E80000820704710B53D4C94F82400002804D128 +:106E9000F4F712FF012084F8240010BD10B5374C20 +:106EA00094F82400002804D0F4F72FFF002084F881 +:106EB000240010BD10B51C685B68241A181A24F051 +:106EC0007F4420F07F40A14206D8B4F5800F03D262 +:106ED000904201D8012010BD002010BDD0E9003241 +:106EE000D21A21F07F43114421F07F41C0E90031E3 +:106EF00070472DE9FC418446204815468038089C9F +:106F000000EB85160F4616F81400012804D002285D +:106F100002D00020BDE8FC810B46204A01216046DA +:106F2000FFF7C8FFF0B101AB6A4629463846FFF7C4 +:106F3000A6F9B8B19DF804209DF800102846FFF787 +:106F400022FA06EB440148709DF8000020280DD07D +:106F500006EB400044702A4621460320FEF784FDDC +:106F60000120D7E72A4621460420F7E703480121FC +:106F700000EB850000F8254FC170ECE7040B002002 +:106F8000FF1FA107980000200000000084080020D7 +:106F9000000000000000000004ED00E0FFFF3F00E3 +:106FA0002DE9F041044680074FF000054FF001063F +:106FB0000CD56B480560066000F0E8F920B169481F +:106FC000016841F48061016024F00204E0044FF0A4 +:106FD000FF3705D564484660C0F8087324F4805430 +:106FE000600003D56148056024F08044E0050FD5BA +:106FF0005F48C0F80052C0F808735E490D60091D73 +:107000000D605C4A04210C321160066124F4807426 +:10701000A00409D558484660C0F80052C0F808736B +:107020005648056024F40054C4F38030C4F3C031E2 +:10703000884200D0FFDF14F4404F14D0504846601F +:10704000C0F808734F488660C0F80052C0F8087353 +:107050004D490D600A1D16608660C0F808730D600A +:10706000166024F4404420050AD5484846608660EE +:10707000C0F80873C0F848734548056024F40064FC +:107080000DF070FD4348044200D0FFDFBDE8F08101 +:10709000F0B50022202501234FEA020420FA02F174 +:1070A000C9072DD051B2002910DB00BF4FEA51179C +:1070B0004FEA870701F01F0607F1E02703FA06F6FB +:1070C000C7F88061BFF34F8FBFF36F8F0CDB00BF3A +:1070D0004FEA51174FEA870701F01F0607F1E02733 +:1070E00003FA06F6C7F8806204DB01F1E02181F8BB +:1070F000004405E001F00F0101F1E02181F8144D99 +:1071000002F10102AA42C9D3F0BD10B5224C2060A1 +:107110000846F4F7EAFE2068FFF742FF2068FFF711 +:10712000B7FF0DF045F900F092F90DF01BFD0DF0E1 +:1071300058FCEBF7B5FEBDE810400DF0EDB910B509 +:10714000154C2068FFF72CFF2068FFF7A1FF0DF01A +:1071500009FDF4F7C9FF0020206010BD0A20704728 +:10716000FC1F00403C17004000C0004004E5014007 +:10717000008000400485004000D0004004D500405D +:1071800000E0004000F0004000F5004000B000408A +:1071900008B50040FEFF0FFD9C00002070B5264999 +:1071A0000A680AB30022154601244B685B1C4B6039 +:1071B0000C2B00D34D600E7904FA06F30E681E42C4 +:1071C0000FD0EFF3108212F0010272B600D001224C +:1071D0000C689C430C6002B962B6496801600020EB +:1071E00070BD521C0C2AE0D3052070BD4FF0E02189 +:1071F0004FF48000C1F800027047EFF3108111F0E6 +:10720000010F72B64FF0010202FA00F20A48036859 +:1072100042EA0302026000D162B6E7E706480021B5 +:1072200001604160704701218140034800680840C7 +:1072300000D0012070470000A0000020012081073D +:10724000086070470121880741600021C0F80011E3 +:1072500018480170704717490120087070474FF0B7 +:107260008040D0F80001012803D01248007800289F +:1072700000D00120704710480068C00700D00120EE +:1072800070470D480C300068C00700D001207047DF +:107290000948143000687047074910310A68D20362 +:1072A00006D5096801F00301814201D10120704730 +:1072B00000207047A8000020080400404FF08050D4 +:1072C000D0F830010A2801D0002070470120704713 +:1072D00000B5FFF7F3FF20B14FF08050D0F8340134 +:1072E00008B1002000BD012000BD4FF08050D0F853 +:1072F00030010E2801D000207047012070474FF068 +:107300008050D0F83001062803D0401C01D0002066 +:107310007047012070474FF08050D0F830010D28A1 +:1073200001D000207047012070474FF08050D0F806 +:107330003001082801D000207047012070474FF02D +:107340008050D0F83001102801D000207047012073 +:10735000704700B5FFF7F3FF30B9FFF7DCFF18B94E +:10736000FFF7E3FF002800D0012000BD00B5FFF7C4 +:10737000C6FF38B14FF08050D0F83401062803D34F +:10738000401C01D0002000BD012000BD00B5FFF76A +:10739000B6FF48B14FF08050D0F83401062803D32F +:1073A000401C01D0012000BD002000BD0021017063 +:1073B000084670470146002008707047EFF31081BF +:1073C00001F0010172B60278012A01D0012200E029 +:1073D00000220123037001B962B60AB10020704790 +:1073E0004FF400507047E9E7EFF3108111F0010FFF +:1073F00072B64FF00002027000D162B600207047F2 +:10740000F2E700002DE9F04115460E46044600273C +:1074100000F0EBF8A84215D3002341200FE000BF95 +:1074200094F84220A25CF25494F84210491CB1FB3B +:10743000F0F200FB12115B1C84F84210DBB2AB428D +:10744000EED3012700F0DDF83846BDE8F08172493F +:1074500010B5802081F800047049002081F84200B6 +:1074600081F84100433181F8420081F84100433105 +:1074700081F8420081F841006948FFF797FF6848AA +:10748000401CFFF793FFEBF793FCBDE8104000F0C2 +:10749000B8B840207047614800F0A7B80A460146D6 +:1074A0005E48AFE7402070475C48433000F09DB82D +:1074B0000A46014659484330A4E7402101700020A4 +:1074C000704710B504465548863000F08EF820709D +:1074D000002010BD0A460146504810B58630FFF71F +:1074E00091FF08B1002010BD42F2070010BD70B539 +:1074F0000C460646412900D9FFDF4A48006810388B +:1075000040B200F054F8C5B20D2000F050F8C0B2FF +:10751000854201D3012504E0002502E00DB1EBF71F +:107520008AFC224631463D48FFF76CFF0028F5D023 +:1075300070BD2DE9F0413A4F0025064617F10407CA +:1075400057F82540204600F041F810B36D1CEDB20D +:10755000032DF5D33148433000F038F8002825D00A +:107560002E4800F033F8002820D02C48863000F058 +:107570002DF800281AD0EBF734FC2948FFF71EFF3E +:10758000B0F5005F00D0FFDFBDE8F0412448FFF711 +:107590002BBF94F841004121265414F8410F401CA0 +:1075A000B0FBF1F201FB12002070D3E74DE7002899 +:1075B00004DB00F1E02090F8000405E000F00F008B +:1075C00000F1E02090F8140D4009704710F8411FB9 +:1075D0004122491CB1FBF2F302FB131140788142B6 +:1075E00001D1012070470020704710F8411F4078FA +:1075F000814201D3081A02E0C0F141000844C0B240 +:10760000704710B50648FFF7D9FE002803D1BDE842 +:107610001040EBF7D1BB10BD0DE000E0340B0020B3 +:10762000AC00002004ED00E070B5154D2878401C3A +:10763000C4B26878844202D000F0DBFA2C7070BDCE +:107640002DE9F0410E4C4FF0E02600BF00F0C6FAE5 +:107650000EF09AFB40BF20BF677820786070D6F8A4 +:107660000052E9F798FE854305D1D6F8040210B917 +:107670002078B842EAD000F0ACFA0020BDE8F081F2 +:10768000BC0000202DE9F04101264FF0E02231033B +:107690004FF000084046C2F88011BFF34F8FBFF390 +:1076A0006F8F204CC4F800010C2000F02EF81E4D06 +:1076B0002868C04340F30017286840F01000286095 +:1076C000C4F8046326607F1C02E000BF0EF05CFB80 +:1076D000D4F800010028F9D01FB9286820F0100064 +:1076E0002860124805686660C4F80863C4F8008121 +:1076F0000C2000F00AF82846BDE8F08110B50446D9 +:10770000FFF7C0FF2060002010BD002809DB00F05B +:107710001F02012191404009800000F1E020C0F8E3 +:107720008012704700C0004010ED00E008C5004026 +:107730002DE9F047FF4C0646FF21A06800EB06123A +:1077400011702178FF2910D04FF0080909EB0111C1 +:1077500009EB06174158C05900F0F4F9002807DD7D +:10776000A168207801EB061108702670BDE8F0874B +:1077700094F8008045460DE0A06809EB05114158DA +:10778000C05900F0DFF9002806DCA068A84600EB2D +:1077900008100578FF2DEFD1A06800EB061100EB73 +:1077A00008100D700670E1E7F0B5E24B04460020CA +:1077B00001259A680C269B780CE000BF05EB0017AA +:1077C000D75DA74204D106EB0017D7598F4204D0EA +:1077D000401CC0B28342F1D8FF20F0BD70B5FFF766 +:1077E000ECF9D44C08252278A16805EB02128958DF +:1077F00000F0A8F9012808DD2178A06805EB011147 +:107800004058BDE87040FFF7CFB9FFF7A1F8BDE8D9 +:107810007040EBF779BB2DE9F041C64C2578FFF7B6 +:10782000CCF9FF2D6ED04FF00808A26808EB0516C2 +:10783000915900F087F90228A06801DD80595DE0C8 +:1078400000EB051109782170022101EB0511425C62 +:107850005AB1521E4254815901F5800121F07F41F5 +:1078600081512846FFF764FF34E00423012203EB33 +:10787000051302EB051250F803C0875CBCF1000F42 +:1078800010D0BCF5007F10D9CCF3080250F806C028 +:107890000CEB423C2CF07F4C40F806C0C3589A1ABF +:1078A000520A09E0FF2181540AE0825902EB4C326E +:1078B00022F07F428251002242542846FFF738FFCF +:1078C0000C21A06801EB05114158E06850F8272011 +:1078D000384690472078FF2814D0FFF76EF92278B9 +:1078E000A16808EB02124546895800F02BF90128DF +:1078F00093DD2178A06805EB01114058BDE8F04107 +:10790000FFF752B9BDE8F081F0B51D4614460E46AA +:107910000746FF2B00D3FFDFA00700D0FFDF85481D +:10792000FF210022C0E90247C57006710170427054 +:1079300082701046012204E002EB0013401CE15467 +:10794000C0B2A842F8D3F0BD70B57A4C064665784F +:107950002079854200D3FFDFE06840F82560607839 +:10796000401C6070284670BD2DE9FF5F1D468B46A8 +:107970000746FF24FFF721F9DFF8B891064699F88A +:107980000100B84200D8FFDF00214FF001084FF09E +:107990000C0A99F80220D9F808000EE008EB011350 +:1079A000C35CFF2B0ED0BB4205D10AEB011350F88C +:1079B00003C0DC450CD0491CC9B28A42EED8FF2C6A +:1079C00002D00DE00C46F6E799F803108A4203D185 +:1079D000FF2004B0BDE8F09F1446521C89F8022035 +:1079E00008EB04110AEB0412475440F802B00421DA +:1079F000029B0022012B01EB04110CD040F8012066 +:107A00004FF4007808234FF0020C454513D9E905DF +:107A1000C90D02D002E04550F2E7414606EB413283 +:107A200003EB041322F07F42C250691A0CEB0412DC +:107A3000490A81540BE005B9012506EB453103EBFA +:107A4000041321F07F41C1500CEB0411425499F80A +:107A500000502046FFF76CFE99F80000A84201D0C4 +:107A6000FFF7BCFE3846B4E770B50C460546FFF795 +:107A7000A4F8064621462846FFF796FE0446FF284E +:107A80001AD02C4D082101EB0411A868415830464A +:107A900000F058F800F58050C11700EBD1404013BA +:107AA0000221AA6801EB0411515C09B100EB4120ED +:107AB000002800DC012070BD002070BD2DE9F047DA +:107AC00088468146FFF770FE0746FF281BD0194DF8 +:107AD0002E78A8683146344605E0BC4206D02646DA +:107AE00000EB06121478FF2CF7D10CE0FF2C0AD023 +:107AF000A6420CD100EB011000782870FF2804D0BA +:107B0000FFF76CFE03E0002030E6FFF753F8414634 +:107B10004846FFF7A9FF0123A968024603EB0413B7 +:107B2000FF20C854A878401EB84200D1A87001EBCD +:107B3000041001E0000C002001EB06110078087031 +:107B4000104613E6081A0002C11700EB116000127C +:107B50007047000010B5202000F07FF8202000F0D2 +:107B60008DF84D49202081F80004E9F712FC4B49BB +:107B700008604B48D0F8041341F00101C0F8041329 +:107B8000D0F8041341F08071C0F804134249012079 +:107B90001C39C1F8000110BD10B5202000F05DF8BF +:107BA0003E480021C8380160001D01603D4A481E62 +:107BB00010603B4AC2F80803384B1960C2F8000154 +:107BC000C2F8600138490860BDE81040202000F08C +:107BD00055B834493548091F086070473149334862 +:107BE000086070472D48C8380160001D521E0260B1 +:107BF00070472C4901200860BFF34F8F70472DE973 +:107C0000F0412849D0F8188028480860244CD4F85E +:107C100000010025244E6F1E28B14046E9F712FBF3 +:107C200040B9002111E0D4F8600198B14046E9F76D +:107C300009FB48B1C4F80051C4F860513760BDE891 +:107C4000F041202000F01AB831684046BDE8F0410C +:107C50000EF0A4B8FFDFBDE8F08100280DDB00F0D6 +:107C60001F02012191404009800000F1E020C0F88E +:107C70008011BFF34F8FBFF36F8F7047002809DB70 +:107C800000F01F02012191404009800000F1E02036 +:107C9000C0F880127047000020E000E0C8060240F3 +:107CA00000000240180502400004024001000001EB +:107CB0005E4800210170417010218170704770B5DD +:107CC000054616460C460220EAF714FE57490120E5 +:107CD00008705749F01E086056480560001F046090 +:107CE00070BD10B50220EAF705FE5049012008706A +:107CF00051480021C0F80011C0F80411C0F8081163 +:107D00004E494FF40000086010BD48480178D9B1D1 +:107D10004B4A4FF4000111604749D1F8003100226D +:107D2000002B1CBFD1F80431002B02D0D1F8081170 +:107D300019B142704FF0100104E04FF001014170A1 +:107D400040490968817002704FF00000EAF7D2BD27 +:107D500010B50220EAF7CEFD34480122002102705E +:107D60003548C0F80011C0F80411C0F808110260CD +:107D700010BD2E480178002904BF407870472E4876 +:107D8000D0F80011002904BF02207047D0F800117C +:107D900000291CBFD0F80411002905D0D0F8080133 +:107DA000002804BF01207047002070471F4800B51D +:107DB0000278214B4078C821491EC9B282B1D3F85C +:107DC00000C1BCF1000F10D0D3F8000100281CBF87 +:107DD000D3F8040100280BD0D3F8080150B107E014 +:107DE000022802D0012805D002E00029E4D1FFDFFB +:107DF000002000BD012000BD0C480178002904BF0F +:107E0000807870470C48D0F8001100291CBFD0F8CA +:107E10000411002902D0D0F8080110B14FF0100071 +:107E2000704708480068C0B270470000BE000020DC +:107E300010F5004008F5004000F0004004F5014056 +:107E400008F5014000F400405748002101704170DE +:107E5000704770B5064614460D460120EAF74AFD04 +:107E600052480660001D0460001D05605049002056 +:107E7000C1F850014F490320086050494E4808603E +:107E8000091D4F48086070BD2DE9F0410546464880 +:107E90000C46012606704B4945EA024040F08070CE +:107EA0000860FFF72CFA002804BF47480460002749 +:107EB000464CC4F80471474945480860002D02BF8C +:107EC000C4F800622660BDE8F081012D18BFFFDF15 +:107ED000C4F80072266041493F480860BDE8F0815F +:107EE0003148017871B13B4A394911603749D1F8BD +:107EF00004210021002A08BF417002D0384A1268CC +:107F0000427001700020EAF7F5BC2748017800298B +:107F100004BF407870472D48D0F80401002808BFFE +:107F200070472F480068C0B27047002808BF7047EC +:107F30002DE9F0471C480078002808BFFFDF234CDC +:107F4000D4F80401002818BFBDE8F0874FF00209FB +:107F5000C4F80493234F3868C0F30018386840F021 +:107F600010003860D4F80401002804BF4FF4004525 +:107F70004FF0E02608D100BFC6F880520DF004FF94 +:107F8000D4F804010028F7D0B8F1000F03D1386805 +:107F900020F010003860C4F80893BDE8F0870B4962 +:107FA0000120886070470000C100002008F50040F3 +:107FB000001000401CF500405011004098F50140B1 +:107FC0000CF0004004F5004018F5004000F00040BF +:107FD0000000020308F501400000020204F5014020 +:107FE00000F4004010ED00E0012804BF41F6A47049 +:107FF0007047022804BF41F288307047042804BF4C +:1080000046F218007047082804BF47F2A0307047B6 +:1080100000B5FFDF41F6A47000BD10B5FE48002496 +:1080200001214470047044728472C17280F825404A +:10803000C462846380F83C4080F83D40FF2180F8B2 +:108040003E105F2180F83F1018300DF09FFFF3497C +:10805000601E0860091D0860091D0C60091D08608C +:10806000091D0C60091D0860091D0860091D0860D4 +:10807000091D0860091D0860091D0860091D0860C8 +:10808000091D0860091D086010BDE549486070477A +:10809000E448016801F00F01032904BF0120704783 +:1080A000016801F00F01042904BF02207047016834 +:1080B00001F00F01052904D0006800F00F00062828 +:1080C00007D1D948006810F0060F0CBF0820042023 +:1080D000704700B5FFDF012000BD012812BF022854 +:1080E00000207047042812BF08284FF4C87070475A +:1080F00000B5FFDF002000BD012804BF2820704725 +:10810000022804BF18207047042812BF08284FF423 +:10811000A870704700B5FFDF282000BD70B5C148CA +:10812000016801F00F01032908BF012414D0016880 +:1081300001F00F01042904BF022418210DD00168A9 +:1081400001F00F0105294BD0006800F00F00062850 +:108150001CBFFFDF012443D02821AF48C26A806AD8 +:10816000101A0E18082C04BF4EF6981547F2A030CE +:108170002DD02046042C08BF4EF628350BD0012800 +:1081800008BF41F6A47506D0022C1ABFFFDF41F6E6 +:10819000A47541F28835012C08BF41F6A47016D0B1 +:1081A000022C08BF002005D0042C1ABFFFDF0020DE +:1081B0004FF4C8702D1A022C08BF41F2883006D047 +:1081C000042C1ABFFFDF41F6A47046F21800281AEB +:1081D0004FF47A7100F2E730B0FBF1F0304470BD3B +:1081E0009148006810F0060F0CBF082404244FF4D7 +:1081F000A871B2E710B58D49026801F118040A634D +:1082000042684A63007A81F83800207E48B1207FB6 +:10821000F6F781F9A07E011C18BF0121207FF6F737 +:1082200069F9607E002808BF10BD607FF6F773F91A +:10823000E07E011C18BF0121607FBDE81040F6F709 +:1082400059B930B50024054601290AD0022908BFD2 +:108250004FF0807405D0042916BF08294FF0C74499 +:10826000FFDF44F4847040F480107149086045F4E5 +:10827000403001F1040140F00070086030BD30B5BD +:108280000024054601290AD0022908BF4FF0807456 +:1082900005D0042916BF08294FF0C744FFDF44F476 +:1082A000847040F480106249086045F4403001F168 +:1082B000040140F0007008605E48D0F8000100281A +:1082C00018BFFFDF30BD2DE9F04102274FF0E02855 +:1082D00001250024C8F88071BFF34F8FBFF36F8F63 +:1082E000554804600560FFF751F8544E18B13068E6 +:1082F00040F480603060FFF702F838B1306820F059 +:10830000690040F0960040F0004030604D494C4814 +:1083100008604FF01020806CB0F1FF3F04D04A4954 +:108320000A6860F317420A60484940F25B600860DF +:10833000091F40F203100860081F05603949032037 +:10834000086043480560444A42491160444A434931 +:108350001160121F43491160016821F440710160EE +:10836000016841F480710160C8F8807231491020C1 +:10837000C1F80403284880F83140C46228484068A6 +:10838000002808BFBDE8F081BDE8F0410047274A5A +:108390000368C2F81A308088D08302F11800017295 +:1083A00070471D4B10B51A7A8A4208D10146062241 +:1083B000981CF6F715F8002804BF012010BD002016 +:1083C00010BD154890F825007047134A5170107081 +:1083D0007047F0B50546800000F1804000F5805000 +:1083E0008B88C0F820360B78D1F8011043EA0121C0 +:1083F000C0F8001605F10800012707FA00F61A4C2C +:10840000002A04BF2068B04304D0012A18BFFFDF50 +:1084100020683043206029E0280C0020000E004036 +:10842000C40000201015004014140040100C00205F +:108430001415004000100040FC1F00403C17004095 +:108440002C000089781700408C150040381500403A +:108450005016004000000E0408F501404080004026 +:10846000A4F501401011004040160040206807FAB2 +:1084700005F108432060F0BD0CF0C4BCFE4890F844 +:1084800032007047FD4AC17811600068FC49000263 +:1084900008607047252808BF02210ED0262808BF93 +:1084A0001A210AD0272808BF502106D00A2894BFD5 +:1084B0000422062202EB4001C9B2F24A1160F249DD +:1084C000086070472DE9F047EB4CA17A012956D09E +:1084D000022918BFBDE8F087627E002A08BFBDE808 +:1084E000F087012950D0E17E667F0D1C18BF012561 +:1084F0005FF02401DFF894934FF00108C9F84C8035 +:10850000DFF88CA34718DAF80000B84228BFFFDF75 +:108510000020C9F84C01CAF80070300285F0010152 +:1085200040EA015040F0031194F82000820002F16B +:10853000804202F5C042C2F81015D64901EB800115 +:10854000A07FC20002F1804202F5F832C2F8141591 +:10855000D14BC2F81035E27FD30003F1804303F51D +:10856000F833C3F81415CD49C3F8101508FA00F014 +:1085700008FA02F10843CA490860BDE8F087227E84 +:10858000002AAED1BDE8F087A17E267F002914BF66 +:10859000012500251121ADE72DE9F041C14E8046AE +:1085A00003200D46C6F80002BD49BF4808602846B2 +:1085B0000CF02CFCB04F0124B8F1000F04BFBC72CA +:1085C000346026D0B8F1010F23D1B848006860B9F3 +:1085D00015F00C0F09D0C6F80443012000F0DAFEB4 +:1085E000F463346487F83C4002E0002000F0D2FEDF +:1085F00028460CF0F3FC0220B872FEF7B7FE38B93B +:10860000FEF7C4FE20B9AA48016841F4C021016008 +:1086100074609E48C4649E4800682946BDE8F041E5 +:1086200050E72DE9F0479F4E814603200D46C6F8DE +:108630000002DFF86C829C48C8F8000008460CF085 +:10864000E5FB28460CF0CAFC01248B4FB9F1000F62 +:1086500003D0B9F1010F0AD031E0BC72B86B40F41D +:108660008010B8634FF48010C8F8000027E00220A3 +:10867000B872B86B40F40010B8634FF40010C8F83B +:1086800000008A48006860B915F00C0F09D0C6F8E0 +:108690000443012000F07EFEF463346487F83C401C +:1086A00002E0002000F076FEFEF760FE38B9FEF72B +:1086B0006DFE20B97E48016841F4C0210160EAF7EF +:1086C000F7FA2946BDE8F047FCE62DE9F84F754C6E +:1086D0008246032088461746C4F80002DFF8C0919E +:1086E0007148C9F8000010460CF090FBDFF8C4B1E7 +:1086F000614E0125BAF1000F04BFCBF80040B572FE +:1087000004D0BAF1010F18BFFFDF2FD06A48C0F8BC +:1087100000806B4969480860B06B40F40020B0638A +:10872000D4F800321021C4F808130020C4F8000265 +:10873000DFF890C18A03CCF80020C4F80001C4F827 +:108740000C01C4F81001C4F80401C4F81401C4F801 +:1087500018015D4800680090C4F80032C9F8002094 +:10876000C4F80413BAF1010F14D026E038460CF017 +:1087700035FCFEF7FBFD38B9FEF708FE20B94C4882 +:10878000016841F4C02101605048CBF8000002208C +:10879000B072BBE74548006860B917F00C0F09D00C +:1087A000C4F80453012000F0F5FDE563256486F864 +:1087B0003C5002E0002000F0EDFD4FF40020C9F82D +:1087C00000003248C56432480068404528BFFFDFDA +:1087D00039464046BDE8F84F74E62DE9F041264C95 +:1087E0000646002594F8310017468846002808BF41 +:1087F000FFDF16B1012E16D021E094F831000128D8 +:1088000008D094F83020394640460CF014FBE16A59 +:10881000451814E094F830103A4640460CF049FBF5 +:10882000E16A45180BE094F8310094F83010012803 +:108830003A46404609D00CF064FBE16A45183A46D6 +:1088400029463046BDE8F0413FE70CF014FBE16AF1 +:108850004518F4E72DE9F84F124CD4F8000220F047 +:108860000309D4F804034FF0100AC0F30018C4F849 +:1088700008A300262CE00000280C0020241500404E +:108880001C150040081500405415004000800040B1 +:108890004C850040006000404C81004010110040B9 +:1088A00004F5014000100040000004048817004057 +:1088B00068150040ACF50140488500404881004003 +:1088C000A8F5014008F501401811004004100040CF +:1088D000C4F80062FC48FB490160FC4D0127A97AFD +:1088E000012902D0022903D015E0297E11B912E036 +:1088F000697E81B1A97FEA7F07FA01F107FA02F2E6 +:108900001143016095F82000800000F1804000F5DF +:10891000C040C0F81065FF208DF80000C4F8106159 +:10892000276104E09DF80000401E8DF800009DF8CE +:10893000000018B1D4F810010028F3D09DF8000011 +:10894000002808BFFFDFC4F81061002000F022FDFE +:108950006E72AE72EF72C4F80092B8F1000F18BFD9 +:10896000C4F804A3BDE8F88FFF2008B58DF8000017 +:10897000D7480021C0F810110121016105E000BFB6 +:108980009DF80010491E8DF800109DF8001019B1D7 +:10899000D0F810110029F3D09DF80000002808BF7E +:1089A000FFDF08BD0068CB4920F07F4008607047BA +:1089B0004FF0E0200221C0F8801100F5C070BFF335 +:1089C0004F8FBFF36F8FC0F80011704710B490E85D +:1089D0001C10C14981E81C10D0E90420C1E9042021 +:1089E00010BC70474FF0E0210220C1F80001704731 +:1089F000BA4908707047BA490860704770B50546B3 +:108A0000EAF756F9B14C2844E16A884298BFFFDF83 +:108A100001202074EAF74CF9B24A28440021606131 +:108A2000C2F84411B0490860A06BB04940F480001E +:108A3000A063D001086070BD70B5A44C0546AC4A77 +:108A40000220207410680E4600F00F00032808BFB3 +:108A5000012213D0106800F00F00042808BF022282 +:108A60000CD0106800F00F0005281BD0106800F033 +:108A70000F0006281CBFFFDF012213D094F831003D +:108A800094F83010012815D028460CF081FA954949 +:108A900060610020C1F844016169E06A08449249BC +:108AA000086070BD9348006810F0060F0CBF0822E4 +:108AB0000422E3E7334628460CF038FAE7E7824918 +:108AC0004FF4800008608148816B21F4800181634C +:108AD000002101747047C20002F1804202F5F832B1 +:108AE000854BC2F81035C2F81415012181407F482A +:108AF00001607648826B1143816370477948012198 +:108B00004160C1600021C0F84411774801606F489E +:108B1000C1627047794908606D48D0F8001241F091 +:108B20004001C0F8001270476948D0F8001221F0E7 +:108B30004001C0F800127149002008607047644885 +:108B4000D0F8001221F01001C0F80012012181615B +:108B500070475E49FF2081F83E005D480021C0F863 +:108B60001C11D0F8001241F01001C0F8001270473B +:108B7000574981B0D1F81C21012A0DD0534991F8F1 +:108B80003E10FF290DBF00204942017001B008BF0F +:108B90007047012001B07047594A126802F07F0205 +:108BA000524202700020C1F81C0156480068009033 +:108BB000EFE7F0B517460C00064608BFFFDF434D50 +:108BC00014F0010F2F731CBF012CFFDF002E0CBF10 +:108BD000012002206872EC7201281CBF0228FFDF0E +:108BE000F0BD3A4981F83F007047384A136C036082 +:108BF000506C086070472DE9F84F38480078042819 +:108C000028BFFFDF314CDFF8C080314D94F83C00C5 +:108C100000260127E0B1D5F8040110F1000918BFC2 +:108C20004FF00109D5F81001002818BF012050EAC3 +:108C300009014FF4002B17D08021C5F80813C8F89C +:108C400000B084F83C6090F0010F18BFBDE8F88FC9 +:108C5000DFF89090D9F84C01002871D0A07A012853 +:108C60006FD002286ED0D1E0D5F80001DFF890A0D7 +:108C700030B3C5F800616F61FF20009002E0401E34 +:108C8000009005D0D5F81C0100280098F7D000B955 +:108C9000FFDFDAF8000000F07F0A94F83F0050454B +:108CA0003CBF002000F076FB84F83EA0C5F81C61B4 +:108CB000C5F808731348006800902F64AF6326E07E +:108CC00022E0000000000E0408F50140280C0020FE +:108CD000001000403C150040100C0020C400002093 +:108CE00004150040008000404485004004F5014028 +:108CF000101500401414004004110040601500409D +:108D0000481500401C110040B9F1000F03D0B9F123 +:108D1000000F2ED05CE0DAF8000000F07F0084F84D +:108D20003E00C5F81C6194F83D1061B194F83F1005 +:108D300081421BD2002000F02DFB2F64AF6315E0B1 +:108D400064E04CE04EE0FE49096894F83F308AB296 +:108D5000090C984203D30F2A06D9022904D2012014 +:108D600000F018FB2F6401E02F64AF63F548006842 +:108D700000908022C5F80423F3498F64F348036808 +:108D8000A0F1040CDCF800C043F698273B4463458F +:108D900015D2026842F210731A440260C1F84861A9 +:108DA000EC49EB480860091FEB480860EB48C0F845 +:108DB00000B0A06B40F40020A063BDE8F88F06600F +:108DC000C1F84861C5F80823C8F800B0C1F8486187 +:108DD0008020C5F80803C8F800B0BDE8F88F207EF1 +:108DE00010B913E0607E88B1A07FE17F07FA00F040 +:108DF00007FA01F10843C8F8000094F82000800049 +:108E000000F1804000F5C040C0F81065C9F84C7012 +:108E1000D34800682064D34800686064D248A16BDE +:108E20000160A663217C002019B1D9F84411012901 +:108E300000D00021A27A012A6ED0022A74D000BF8D +:108E4000D5F8101101290CBF1021002141EA0008BA +:108E5000C648016811F0FF0F03D0D5F8141101299D +:108E600000D0002184F83210006810F0FF0F03D00A +:108E7000D5F81801012800D0002084F83300BC4840 +:108E8000006884F83400FEF774FF012818BF002042 +:108E900084F83500C5F80061C5F80C61C5F81061AB +:108EA000C5F80461C5F81461C5F81861B1480068D7 +:108EB0000090A548C0F84461AF480068DFF8BC9254 +:108EC0000090D9F80000A062A9F104000068E062F7 +:108ED000AB48016801F00F01032908BF012013D03E +:108EE000016801F00F01042908BF02200CD00168BD +:108EF00001F00F01052926D0006800F00F000628B8 +:108F00001CBFFFDF01201ED084F83000A07A84F857 +:108F1000310002282CD11EE0D5F80C01012814BF25 +:108F2000002008208CE7FFE7D5F80C01012814BFCA +:108F300000200220934A1268012A14BF0422002252 +:108F4000104308437CE79048006810F0060F0CBF00 +:108F500008200420D8E7607850B18C490968097866 +:108F60000840217831EA000008BF84F8247001D05D +:108F700084F82460DFF818A218F0020F06D0E9F791 +:108F800097FEA16A081ADAF81010884718F0010F46 +:108F900018BF4FF0000B0DD0E9F78AFEE16ADAF84E +:108FA0001420081A594690477A48007810F0010FAB +:108FB0002FD10CE018F0020F18BF4FF0010BEBD1CE +:108FC00018F0080F18BF4FF0020BE5D1ECE7DFF8FF +:108FD000BCB1DBF80000007800F00F00072828BFC4 +:108FE00084F8256015D2DBF80000062200F10901A3 +:108FF000A01CF5F7F5F940B9207ADBF800100978E4 +:10900000B0EBD11F08BF012001D04FF0000084F861 +:109010002500E17A4FF0000011F0020F1CBF18F09C +:10902000020F18F0040F19D111F0100F1CBF94F8A3 +:109030003320002A02D094F835207AB111F0080FBD +:109040001CBF94F82420002A08D111F0040F02D08C +:1090500094F8251011B118F0010F01D04FF0010064 +:10906000617A19B168B1FFF7F5FB10E03E484A4953 +:109070000160D5F8000220F00300C5F80002E77295 +:1090800005E001290AD0022918BFFFDF0DD018F032 +:10909000010F14D0DAF80000804745E06672E772ED +:1090A000A7729621227B002006E06672E7720220FA +:1090B000A072227B96210120FFF78FFBE7E718F0D3 +:1090C000020F2AD018F0040F21D1FEF74FF9F0B9A2 +:1090D000FEF75CF9D8B931480168001F0068C0F399 +:1090E000006CC0F3425500F00F03C0F30312C0F34D +:1090F0000320BCF1000F0AD0002B1CBF002A00285F +:1091000005D1002918BF032D38BF48F0040827EA0D +:109110009800DAF80410884706E018F0080F18BF26 +:10912000DAF8080056D08047A07A022818BFBDE8B8 +:10913000F88F207C002808BFBDE8F88F02492FE097 +:10914000741500401C11004000800040488500401C +:1091500014100040ACF501404881004004F5014086 +:1091600004B500404C85004008F501404016004021 +:109170001014004018110040448100404485004014 +:109180001015004000140040141400400415004065 +:10919000100C0020C40000200000040454140040FF +:1091A000C1F8446102281DD0012818BFFFDFE16A21 +:1091B0006069884298BFFFDFD4F81400C9F8000046 +:1091C000A06B4FF4800140F48000A06382480160EE +:1091D000BDE8F88F18F0100F14BFDAF80C00FFDFAD +:1091E000A1D1A1E76169E06A0844E7E738B57B49A6 +:1091F00004460220887201212046FFF763F9784A6D +:1092000004F13D001060774B0020C3F8440176491B +:10921000C1F80001C1F80C01C1F81001C1F8040146 +:10922000C1F81401C1F818017048006800900120CD +:109230009864101D00681168884228BFFFDF38BDA0 +:109240002DE9F843654A88460024917A0125684F44 +:10925000012902D0022903D015E0117E11B912E0D4 +:10926000517E81B1917FD37F05FA01F105FA03F3B5 +:109270001943396092F82010890001F1804101F50D +:10928000C041C1F8104506460220907201213046C7 +:10929000FFF718F9524906F13D000860514AC2F83B +:1092A00044415148C0F80041C0F80C41C0F8104199 +:1092B000C0F80441C0F81441C0F818414B48006898 +:1092C00000909564081D00680968884228BFFFDF88 +:1092D000B8F1000F1CBF4FF400303860BDE8F883D0 +:1092E000022810B50DD0012804BF42F6CE3010BDC3 +:1092F000042817BF082843F6A440FFDF41F66A00A0 +:1093000010BDFDF7E5FF30B9FDF7F9FF002808BFF4 +:1093100041F6583001D041F2643041F29A010844DC +:1093200010BD314910B50020C1F800023049314864 +:109330000860324930480860091D31480860091D3D +:1093400030480860091D30480860091D2F48086032 +:10935000091D2F48086001200BF058FD1E494FF4ED +:109360003810086010BD22494FF43810086070476B +:109370002848016803291BBF00680228012000203B +:109380007047244801680B291BBF00680A28012088 +:109390000020704720490968C9B9204A204913684C +:1093A00070B123F0820343F07D0343F00043136068 +:1093B0000A6822F0100242F0600242F0004205E02A +:1093C00023F0004313600A6822F000420A60034958 +:1093D00081F83D007047000004F50140280C002092 +:1093E00044850040008000400010004018110040FB +:1093F00008F50140000004041011004098F50140F8 +:109400000410004044810040141000401C11004032 +:109410001010004050150040881700403C170040D5 +:109420007C17004010B5404822220021F5F72FF8A4 +:109430003D480024017821F010010170012104F061 +:10944000FFFE3A494FF6FF7081F822408884384980 +:109450000880488010BD704734498A8C824218BF0A +:109460007047002081F822004FF6FF708884704713 +:109470002D49016070472E49088070472B498A8C1E +:10948000A2F57F43FF3B03D00021016008467047EF +:1094900091F822202549012A1ABF016001200020ED +:1094A0007047224901F1220091F82220012A04BFCD +:1094B00000207047012202701D48008888841046F1 +:1094C00070471B49488070471849194B8A8C5B8844 +:1094D0009A4206D191F82220002A1EBF0160012085 +:1094E0007047002070471148114A818C5288914280 +:1094F00009D14FF6FF71818410F8221F19B10021A4 +:10950000017001207047002070470848084A818C8C +:109510005288914205D190F8220000281CBF0020FB +:109520007047012070470000960C0020700C00204E +:10953000CC0000207047584A012340B1012818BFD1 +:1095400070471370086890608888908170475370E6 +:109550000868C2F802008888D08070474E4A10B16F +:10956000012807D00EE0507860B1D2F80200086000 +:10957000D08804E0107828B19068086090898880CD +:109580000120704700207047434910B1012803D0E3 +:1095900006E0487810B903E0087808B10120704768 +:1095A0000020704730B58DB00C4605460D220021D5 +:1095B00004A8F4F76CFFE0788DF81F0020798DF88F +:1095C0001E0060798DF81D00286800906868019081 +:1095D000A8680290E868039068460AF087FF207840 +:1095E0009DF82F1088420CD160789DF82E1088428B +:1095F00007D1A0789DF82D10884202BF01200DB040 +:1096000030BD00200DB030BD30B50C4605468DB0E4 +:109610004FF0030104F1030012B1FDF749FF01E02F +:10962000FDF765FF60790D2220F0C00040F040009A +:109630006071002104A8F4F72AFFE0788DF81F007C +:1096400020798DF81E0060798DF81D002868009043 +:1096500068680190A8680290E868039068460AF07C +:1096600045FF9DF82F0020709DF82E0060709DF83A +:109670002D00A0700DB030BD10B5002904464FF08C +:10968000060102D0FDF714FF01E0FDF730FF60791D +:1096900020F0C000607110BDD0000020FE4840687E +:1096A00070472DE9F0410F46064601461446012059 +:1096B00005F06FF8054696F86500FEF795FC4AF24E +:1096C000B12108444FF47A71B0FBF1F0718840F297 +:1096D00071225143C0EB4100001BA0F55A7402F007 +:1096E0005AFF002818BF1E3CAF4234BF28463846F8 +:1096F000A04203D2AF422CBF3C462C467462BDE868 +:10970000F0812DE9FF4F8BB0044690F86500884644 +:109710000390DDE90D1008430A90E0480027057822 +:109720000C2D28BFFFDFDE4E36F8159094F88851D7 +:109730000C2D28BFFFDFDA4830F81500484480B20E +:10974000009094F87D000D280CBF012000200790A8 +:109750000D98002804BF94F82C0103282BD10798FA +:1097600048B3B4F8AA01404525D1D4F83401C4F86F +:109770002001608840F2E2414843C4F82401B4F873 +:109780007A01B4F806110844C4F82801204602F012 +:109790000CFFB4F8AE01E08294F8AC016075B4F847 +:1097A000B0016080B4F8B201A080B4F8B401E080E8 +:1097B000022084F82C01D4F884010990D4F88001A7 +:1097C0000690B4F80661B4F87801D4F874110191E8 +:1097D0000D9921B194F8401151B100F0D6B804F5BB +:1097E000807104917431059104F5B075091D07E08D +:1097F00004F5AA710491091D059104F5A275091DCE +:109800000891B4F87010A8EB0000A8EB01010FFA62 +:1098100080F90FFA81FBB9F1000F05DAD4F8700175 +:1098200001900120D9460A909C484FF0000A007927 +:10983000A8B3F2F77FFB90B3B4F8180102282ED337 +:1098400094F82C0102282AD094F8430138BB94F8EC +:10985000880100900C2828BFFFDF9148009930F85C +:10986000110000F5C86080B2009094F82C01012826 +:109870007ED0608840F2E2414843009901F0E6F86A +:10988000D4F8342180B206EB0B01A1EB0901821A56 +:1098900001FB02AAC4F83401012084F8430194F8C2 +:1098A0002C01002865D0012800F01482022800F065 +:1098B0007181032818BFFFDF00F04782A7EB0A0180 +:1098C0000198FCF738F90599012640F271220860E9 +:1098D0000898A0F80080002028702E710598006874 +:1098E000A8606188D4F834015143C0EB41006B4952 +:1098F000A0F54E70C8618969814287BF04990860EC +:10990000049801600498616A0068084400F5D47006 +:10991000E86002F040FE10B1E8681E30E8606E7149 +:10992000B4F8F000A0EB080000B20028C4BF032088 +:109930006871079800280E9800F06982E0B100BFB6 +:10994000B4F8181100290CBF0020B4F81A01A4F8CB +:109950001A0194F81C21401C504388420CD26879AB +:10996000401E002808DD6E71B4F81A01401C01E0A9 +:109970000FE05AE0A4F81A010D98002800F06A825E +:1099800094F84001002800F061820FB00220BDE889 +:10999000F08F94F8800003283DD03F4894F865107C +:1099A00090F82C00F7F78DFDE18A40F271225143C7 +:1099B00000EB4100CDF80800D4F82401009901F033 +:1099C00045F8D4F82021D4F82811821A01FB02AA04 +:1099D000C4F820010099029801F038F8D4F8301149 +:1099E000C4F83001411A8A44608840F2E241484399 +:1099F000009901F02BF806EB0B01D4F82821A1EB1C +:109A00000901891AD4F83421C4F83401821A491E94 +:109A100001FB02AA40E7E18A40F27122D4F8240156 +:109A2000514300EB41000290C6E70698002808BFAA +:109A3000FFDF94F86510184890F82C00F7F741FD07 +:109A40000990E08A40F271214143099800EB4100FE +:109A5000009900F0FBFFC4F83001608840F2E24159 +:109A60004843009900F0F2FFC4F8340103A902A8AA +:109A7000FFF7BBF8DDE90160039FE9F7F0F8014665 +:109A80003046FDF769F800F10F06E9F711F9381AC9 +:109A9000801B009006E00000B80C0020E0000020D1 +:109AA000E4620200B4F83401214686B20120D4F801 +:109AB000289004F06EFE074694F86500FEF794FACD +:109AC0004AF2B12108444FF47A7BB0FBFBF0618885 +:109AD00040F271225143C0EB4100801BA0F55A7641 +:109AE00002F059FD002818BF1E3EB94534BF384664 +:109AF0004846B04203D2B9452CBF4E463E46666248 +:109B000094F86500FEF7E9FA00F2E140B0FBFBF1E2 +:109B100006980F1894F86500FEF7DFFA064694F8E9 +:109B20006500FEF761FA30444AF2AB310844B0FBFD +:109B3000FBF1E08A40F2712242430998D4F8306187 +:109B400000EB4200401A801B384400993138471A14 +:109B5000607D40F2E24110FB01F994F8650000904D +:109B600010F00C0F0ABF00984EF62830FEF73CFAB2 +:109B70004AF2B1210844B0FBFBF000EB460000EBD9 +:109B800009060098FEF7B8FA304400F18401FE4857 +:109B9000816193E6E18A40F27122D4F824015143B5 +:109BA00000EB4100009900F051FFC4F830016188DA +:109BB00040F2E2404843009900F048FFC4F8340105 +:109BC00087B221460120D4F828B004F0E2FD814696 +:109BD00094F86500FEF708FA4AF2B12101444FF407 +:109BE0007A70B1FBF0F0618840F271225143C0EB12 +:109BF0004100C01BA0F55A7702F0CDFC002818BF29 +:109C00001E3FCB4534BF48465846B84203D2CB45E9 +:109C10002CBF5F464F4667621EBB0E9808B394F890 +:109C200065603046FEF7E0F94AF2B12101444FF495 +:109C30007A70B1FBF0F0D4F83011E28A084440F2B7 +:109C40007123D4F824115A4301EB42010F1A304614 +:109C5000FEF752FA01460998401A3844A0F120074D +:109C60000AE0E18A40F27122D4F82401514300EB6A +:109C70004100D4F83011471AD4F82821D4F8201123 +:109C8000D4F8300101FB0209607D40F2E24110FB93 +:109C900001FB94F8656016F00C0F0ABF30464EF6D3 +:109CA0002830FEF7A1F94AF2B12101444FF47A704D +:109CB000B1FBF0F000EB490000EB0B093046FEF77A +:109CC0001BFA484400F16001AF488161012084F82B +:109CD0002C01F3E5618840F271225143D4F834013C +:109CE000D4F82821C0EB410101FB09F706EB0B0179 +:109CF000891AD4F820C1D4F83031491E0CFB023245 +:109D000001FB0029607D40F2E24110FB01FB94F869 +:109D1000656016F00C0F0ABF30464EF62830FEF78D +:109D200063F94AF2B12101444FF47A70B1FBF0F0CB +:109D300000EB490000EB0B093046FEF7DDF9484423 +:109D400000F1600190488161B8E5618840F27122BC +:109D5000D4F834015143C0EB410000FB09F794F8FB +:109D60007C0024281CBF94F87D0024280BD1B4F873 +:109D7000AA01A8EB000000B2002804DB94F8AD01B2 +:109D8000002818BF03900A9800B3FEB9099800286C +:109D90001ABF06980028FFDF94F8650010F00C0F3A +:109DA00014BF4EF62830FEF71FF94AF2B1210144E4 +:109DB0004FF47A70B1FBF0F03F1A94F86500FEF7AB +:109DC0009BF90999081A3844A0F12007D4F83411F6 +:109DD00006EB0B0000FB01F6039810F00C0F0ABF16 +:109DE00003984EF62830FEF7FFF84AF2B1210144FD +:109DF0004FF47A70B1FBF0F000EB46060398FEF7E3 +:109E00007BF9304400F160015F48816156E500282C +:109E10007FF496AD94F82C0100283FF4ADAD618835 +:109E200040F27122D4F834015143C0EB410128467D +:109E3000F7F712F90004000C3FF49EAD18990029C1 +:109E400018BF088001200FB0BDE8F08F94F87C01A6 +:109E5000FCF7D1FC94F87C012946FCF7B0FB20B15B +:109E60000D9880F0010084F841010FB00020BDE89A +:109E7000F08F2DE9F843454C0246434F00266168B8 +:109E8000606A052A60D2DFE802F003464B4F5600B5 +:109E9000A07A002560B101216846FDF709FB9DF815 +:109EA000000042F210710002B0FBF1F201FB12055A +:109EB000F4F720FE4119A069FBF73DFEA06126746E +:109EC000032060754FF0010884F81480607AD0B9DF +:109ED000A06A80B1F4F70EFE0544F4F785FC411941 +:109EE000A06A884224BF401BA06204D2C4F8288024 +:109EF000F5F72BFE07E0207B04F11001FCF75FFB78 +:109F0000002808BFFFDF2684FCF739F87879BDE820 +:109F1000F843E8F7F9BFBDE8F843002100F0B3BD0E +:109F2000C1F88001BDE8F883D1F88001BDE8F843AD +:109F3000012100F0A8BD84F83060FCF720F87879A2 +:109F4000BDE8F843E8F7E0BFFFDFBDE8F8832DE99F +:109F5000F04F0E4C824683B020788B4601270025B7 +:109F6000094E4FF00209032804BF207B50457DD1E4 +:109F7000606870612078032818BFFFDF4FF0030886 +:109F8000BBF1080F73D203E0E0000020B80C002002 +:109F9000DFE80BF0040F32322D9999926562F5F7E4 +:109FA000ABF9002818BFFFDF86F8028003B0BDE8D8 +:109FB000F08FF4F77AFF68B9F4F716FC0546E0690C +:109FC000A84228BFE56105D2281A0421FCF7F7FD55 +:109FD000E56138B1F5F723FD002818BFFFDF03B0B6 +:109FE000BDE8F08F03B00020BDE8F04F41E703B0BB +:109FF000BDE8F04FFEF7FFBD2775257494F83000DB +:10A000004FF0010A58B14FF47A71A069FBF793FD44 +:10A01000A061002104F11000F7F71EF80EE0F4F73C +:10A0200069FD82465146A069FBF785FDA061514656 +:10A0300004F11000F7F710F800F1010A208C411C20 +:10A040000A293CBF50442084606830B1208C401CF9 +:10A050000A2828BF84F8159001D284F81580607A08 +:10A06000A8B9A06AE8B1F4F745FD01E02FE02AE0C5 +:10A070008046F4F7B9FB00EB0801A06A884224BFD0 +:10A08000A0EB0800A0620CD2A762F5F75EFD207B72 +:10A09000FCF74BF82570707903B0BDE8F04FE8F796 +:10A0A00033BF207B04F11001FCF789FA002808BFB8 +:10A0B000FFDF03B0BDE8F08F207BFCF736F825709A +:10A0C00003B0BDE8F08FFFDF03B0BDE8F08FBAF159 +:10A0D000200F28BFFFDFDFF8E886072138F81A00D5 +:10A0E000F9F7E4FE040008BFFFDFBAF1200F28BF34 +:10A0F000FFDF38F81A002188884218BFFFDF4FF0D1 +:10A10000200A7461BBF1080F80F06181DFE80BF079 +:10A110000496A0A099FEFDFCC4F88051F580C4F817 +:10A12000845194F8410138B9FCF71EF8D4F84C1169 +:10A13000FCF712FD00281BDCB4F83E11B4F87000E7 +:10A14000814206D1B4F8F410081AA4F8F6002046AB +:10A1500005E0081AA4F8F600B4F83E112046A4F869 +:10A160007010D4F86811C4F84C11C0F870111DE0DB +:10A17000B4F83C11B4F87000081AA4F8F600B4F86A +:10A180003C112046A4F87010D4F84C11C4F86811A2 +:10A19000C4F87011D4F85411C4F80011D4F858114F +:10A1A000C4F87411B4F85C11A4F8781102F008F93D +:10A1B000FBF7B4FF804694F86500FDF715FF4AF2FF +:10A1C000B12108444FF47A71B0FBF1F0D4F83411A6 +:10A1D00040F27122084461885143C0EB4100A0F174 +:10A1E000300AB8F1B70F98BF4FF0B70821460120E9 +:10A1F00004F0CFFA4044AAEB0000A0F21D38A246BA +:10A200002146012004F0C5FADAF824109C3081427E +:10A2100088BF0D1AC6F80C80454528BF4546B56075 +:10A22000D4F86C01A0F5D4703061FCF762FC84F8BE +:10A23000407186F8029003B0BDE8F08F02F0A6F9F5 +:10A2400001E0FEF7D8FC84F8407103B0BDE8F08F60 +:10A25000FBF78AFFD4F8702101461046FCF77CFC1E +:10A2600048B1628840F27123D4F834115A43C1EBEB +:10A270004201B0FBF1F094F87D100D290FD0B4F835 +:10A280007010B4F83E210B189A42AEBF501C401C0F +:10A290000844A4F83E0194F8420178B905E0B4F806 +:10A2A0003E01401CA4F83E0108E0B4F83E01B4F8B9 +:10A2B000F410884204BF401CA4F83E01B4F87A01AF +:10A2C0000DF1040B401CA4F87A01B4F89A00B4F81C +:10A2D0009810401AB4F87010401E08441FFA80F914 +:10A2E0000BE000231A462046CDF800B0FFF709FA2C +:10A2F00068B3012818BFFFDF48D0B4F83E11A9EBBE +:10A30000010000B2002802E053E047E05FE0E8DA35 +:10A31000082084F88D0084F88C70204601F012FE2D +:10A3200084F82C5194F87C514FF6FF77202D00D300 +:10A33000FFDF28F8157094F87C01FBF7F6FE84F82F +:10A340007CA1707903B0BDE8F04FE8F7DDBDA06EE9 +:10A35000002804BF03B0BDE8F08FB4F83E01B4F8A4 +:10A360009420801A01B20029DCBF03B0BDE8F08F51 +:10A37000B4F86C000144491E91FBF0F189B201FB75 +:10A380000020A4F8940003B0BDE8F08FB4F83E01BB +:10A39000BDF804100844A4F83E01AEE7FEF7E4FA65 +:10A3A000FEF729FC4FF0E020C0F8809203B0BDE832 +:10A3B000F08F94F82C01042818BFFFDF84F82C518B +:10A3C00094F87C514FF6FF77202DB2D3B0E7FFDF32 +:10A3D00003B0BDE8F08F10B5FA4C207850B10120E1 +:10A3E0006072F5F7D8FB2078032805D0207A002882 +:10A3F00008BF10BD0C2010BD207BFCF7FCF9207BB2 +:10A40000FCF765FC207BFBF790FE002808BFFFDF10 +:10A410000020207010BD2DE9F04FEA4F83B038784E +:10A4200001244FF0000840B17C720120F5F7B3FB26 +:10A430003878032818BF387A0DD0DFF88C9389F864 +:10A44000034069460720F9F7BAFC002818BFFFDF70 +:10A450004FF6FF7440E0387BFCF7CDF9387BFCF712 +:10A4600036FC387BFBF761FE002808BFFFDF87F86A +:10A470000080E2E7029800281CBF90F82C11002908 +:10A480002AD00088A0421CBFDFF834A34FF0200B75 +:10A490003AD00721F9F70AFD040008BFFFDF94F85E +:10A4A0007C01FCF714FC84F82C8194F87C514FF665 +:10A4B000FF76202D28BFFFDF2AF8156094F87C0175 +:10A4C000FBF733FE84F87CB169460720F9F777FC87 +:10A4D000002818BFFFDF12E06846F9F74EFC00289D +:10A4E000C8D011E0029800281CBF90F82C11002958 +:10A4F00005D00088A0F57F41FF39CAD104E0684645 +:10A50000F9F73BFC0028EDD089F8038087F830800C +:10A5100087F80B8003B00020BDE8F08FAA4948718E +:10A520000020887001220A7048700A71C870A5491D +:10A53000087070E7A449087070472DE9F84FA14CE6 +:10A5400006460F462078002862D1A048FBF792FD0E +:10A55000207320285CD04FF00308666084F80080E8 +:10A56000002565722572AEB1012106F58E70FCF7EB +:10A57000BEFF0620F9F742FC81460720F9F73EFCB2 +:10A5800096F81C114844B1FBF0F200FB1210401C7D +:10A5900086F81C01FBF7C2FD40F2F651884238BF35 +:10A5A00040F2F65000F5A0701FFA80F9F4F7A2FA15 +:10A5B000012680B3A672F4F717F9E061FBF7D4FD2A +:10A5C000824601216846FCF769FF9DF8000042F2CF +:10A5D00010710002B0FBF1F201FB120000EB090167 +:10A5E0005046FBF7A8FAA762A061267584F815808B +:10A5F0002574207B04F11001FBF7E1FF002808BF60 +:10A60000FFDF25840020F5F7C6FA0020BDE8F88FAB +:10A610000C20BDE8F88FFFE7E761FBF7A5FD494691 +:10A62000FBF789FAA061A57284F830600120FDF77C +:10A6300054FD4FF47A7100F2E140B0FBF1F0381AAA +:10A64000A0F5AB60A5626063CFE75F4948707047D3 +:10A650005D49087170475B4810B5417A00291CBFFD +:10A66000002010BD816A51B990F8301039B1416AAB +:10A67000406B814203D9F5F768FA002010BD012034 +:10A6800010BD2DE9F041504C0646E088401CE080AA +:10A69000D4E902516078D6F8807120B13A46284654 +:10A6A000F6F705FD0546A068854205D02169281A00 +:10A6B00008442061FCF71DFAA560AF4209D896F85E +:10A6C0002C01012805D0E078002804BF0120BDE856 +:10A6D000F0810020BDE8F08110B504460846FDF782 +:10A6E00083FC4AF2B12108444FF47A71B0FBF1F0D7 +:10A6F00040F2E241614300F54E7081428CBF081A7E +:10A70000002010BD70B5044682B0002084F84001DE +:10A7100094F8FB00002807BF94F82C01032802B02E +:10A7200070BDFBF721FDD4F8702101461046FCF7FF +:10A7300013FA0028DCBF02B070BD628840F27123BA +:10A74000D4F834115A43C1EB4201B0FBF1F0B4F834 +:10A750007010401C0844A4F83C01B4F8F400B4F8AC +:10A760003C21801A00B20028DCBF02B070BD01207D +:10A7700084F84201B4F89A00B4F8982001AE801A27 +:10A78000401E084485B212E00096B4F83C11002344 +:10A7900001222046FEF7B5FF002804BF02B070BDBD +:10A7A000012815D0022812BFFFDF02B070BDB4F837 +:10A7B0003C01281A00B20028BCBF02B070BDE3E71C +:10A7C000F00C0020B80C0020E00000204F9F01009A +:10A7D000B4F83C01BDF804100844A4F83C01E6E7D5 +:10A7E000F8B50422002506295BD2DFE801F0072630 +:10A7F0000319192A044680F82C2107E00446C948A9 +:10A80000C078002818BF84F82C210AD0FBF7B7FBCA +:10A81000A4F87A51B4F87000A4F83E0184F84251CB +:10A82000F8BD0095B4F8F410012300222046FEF78D +:10A8300068FF002818BFFFDFE8E7032180F82C112C +:10A84000F8BD0646876AB0F83401314685B201206A +:10A8500003F09FFF044696F86500FDF7C5FB4AF23A +:10A86000B12108444FF47A71B0FBF1F0718840F2E5 +:10A8700071225143C0EB4100401BA0F55A7501F015 +:10A880008AFE002818BF1E3DA74234BF2046384626 +:10A89000A84228BF2C4602D2A74228BF3C46746279 +:10A8A000F8BDFFDFF8BD2DE9F05F9E4EB1780229BB +:10A8B00006BFF1880029BDE8F09F7469C4F88401DF +:10A8C00094F86500FDF718FCD4F88411081AB168F3 +:10A8D0000144B160F1680844F060746994F8430180 +:10A8E000002808BFBDE8F09F94F82C01032818BF8A +:10A8F000BDE8F09F94F8655037780C2F28BFFFDF34 +:10A90000894E36F8178094F888710C2F28BFFFDF26 +:10A9100036F81700404494F8888187B2B8F10C0FDC +:10A9200028BFFFDF36F8180000F5C8601FFA80F86E +:10A930002846FDF7E1FBD4F884114FF0000A0E1A07 +:10A9400015F00C0F0ABF28464EF62830FDF74CFBD9 +:10A950004FF47A7900F2E730B0FBF9F0361A284666 +:10A96000FDF7CAFBD4F8001115F00C0FA1EB000B9A +:10A970000ABF28464EF62830FDF736FB4AF2B121D1 +:10A980000844B0FBF9F0ABEB0000A0F160017943A3 +:10A99000B1FBF8F1292202EB50006031A0EB51022B +:10A9A00000EB5100B24201D8B04201D8F1F774FB7C +:10A9B000608840F2E2414843394600F047F8C4F865 +:10A9C000340184F843A1BDE8F09F70B505465548B1 +:10A9D00090F802C0BCF1020F07BF406900F5C074D7 +:10A9E000524800F12404002904BF256070BD4FF4D3 +:10A9F0007A7601290DD002291CBFFFDF70BD1046F9 +:10AA0000FEF76EFC00F2E140B0FBF6F0281A206081 +:10AA100070BD1846FDF761FB00F2E140B0FBF6F0B7 +:10AA2000281A206070BD4148007800281CBF002013 +:10AA3000704710B50720F9F7D3F980F0010010BD79 +:10AA40003A480078002818BF0120704730B5024608 +:10AA50000020002908BF30BDA2FB0110490A41EACD +:10AA6000C051400A4C1C40F100000022D4F1FF31DB +:10AA700040F2A17572EB000038BFFFDF04F5F4600F +:10AA8000B0FBF5F030BD2DE9F843284C0025814698 +:10AA900084F83050D4F8188084F82C10E5722570B2 +:10AAA0000127277239466068F5F792FD6168C1F8A1 +:10AAB0007081267B81F87C61C1F88091C1F8748136 +:10AAC000B1F80080202E28BFFFDF194820F816803B +:10AAD000646884F82C510023A4F878511A4619466A +:10AAE00020460095FEF70DFE002818BFFFDFC4F8D2 +:10AAF0002851C4F8205184F82C71A4F83E51A4F8D0 +:10AB00003C5184F84251B4F87000401EA4F8700023 +:10AB1000A4F87A51FBF733FA02484079BDE8F843CC +:10AB2000E8F7F2B9E0000020E4620200B80C00206F +:10AB3000F00C0020012804D0022805D0032808D1F9 +:10AB400005E0012907D004E0022904D001E004292E +:10AB500001D000207047012070472DE9F0410E46DA +:10AB6000044603F08AFC0546204603F08AFC0446AE +:10AB7000F6F770FBFB4F010015D0386990F86420A0 +:10AB80008A4210D090F8C0311BB190F8C2312342F4 +:10AB90001FD02EB990F85D30234201D18A4218D8D7 +:10ABA00090F8C001A8B12846F6F754FB70B1396996 +:10ABB00091F86520824209D091F8C00118B191F84E +:10ABC000C301284205D091F8C00110B10120BDE8B1 +:10ABD000F0810020FBE730B5E24C85B0E069002849 +:10ABE0005FD0142200216846F3F751FC206990F8E9 +:10ABF0006500FDF7F9F94FF47A7100F5FA70B0FBD2 +:10AC0000F1F5206990F86500FDF776FA2844ADF873 +:10AC1000060020690188ADF80010B0F87010ADF89A +:10AC200004104188ADF8021090F8A20130B1A0697B +:10AC3000C11C039103F002FB8DF81000206990F80D +:10AC4000A1018DF80800E169684688472069002164 +:10AC500080F8A21180F8A1110399002921D090F861 +:10AC6000A01100291DD190F87C10272919D09DF83A +:10AC70001010039A002914D013780124FF2B12D04E +:10AC8000072B0ED102290CD15178FF2909D100BF21 +:10AC900080F8A0410399C0F8A4119DF8101080F825 +:10ACA000A31105B030BD1B29F2D9FAE770B5AD4C40 +:10ACB000206990F87D001B2800D0FFDF2069002567 +:10ACC00080F8A75090F8D40100B1FFDF206990F818 +:10ACD000A81041B180F8A8500188A0F8D81180F8D8 +:10ACE000D6510E2108E00188A0F8D81180F8D6517D +:10ACF000012180F8DA110D2180F8D4110088F9F7CC +:10AD000006FAF8F79FFE2079E8F7FEF8206980F848 +:10AD10007D5070BD70B5934CA07980072CD5A0787C +:10AD2000002829D162692046D37801690D2B01F1F1 +:10AD300070005FD00DDCA3F102034FF001050B2B77 +:10AD400019D2DFE803F01A1844506127182C183A7A +:10AD50006400152B6FD008DC112B4BD0122B5AD06E +:10AD6000132B62D0142B06D166E0162B71D0172B53 +:10AD700070D0FF2B6FD0FFDF70BD91F87F200123D3 +:10AD80001946F6F7FFF80028F6D12169082081F866 +:10AD90007F0070BD1079BDE8704001F090BC91F863 +:10ADA0007E00C00700D1FFDF01F048FC206910F8E9 +:10ADB0007E1F21F00101017070BD91F87D00102807 +:10ADC00000D0FFDF2069112180F8A75008E091F83A +:10ADD0007D00142800D0FFDF2069152180F8A750DE +:10ADE00080F87D1070BD91F87D00152800D0FFDF40 +:10ADF000172005E091F87D00152800D0FFDF19200D +:10AE0000216981F87D0070BDBDE870404EE7BDE866 +:10AE1000704001F028BC91F87C2001230021F6F756 +:10AE2000B1F800B9FFDF0E200FE011F87E0F20F01F +:10AE3000040008701DE00FE091F87C200123002140 +:10AE4000F6F7A0F800B9FFDF1C20216981F87C002B +:10AE500070BD12E01BE022E091F87E00C0F301100B +:10AE6000012800D0FFDF206910F87E1F21F01001BB +:10AE70000170BDE8704001F0E1BB91F87C20012336 +:10AE80000021F6F77FF800B9FFDF1F20DDE791F81A +:10AE90007D00212801D000B1FFDF2220B0E7BDE80E +:10AEA000704001F0D7BB2F48016991F87E2013074D +:10AEB00002D501218170704742F0080281F87E209E +:10AEC0008069C07881F8E10001F0AFBB10B5254C76 +:10AED00021690A88A1F8162281F8140291F8640009 +:10AEE00001F091FB216981F8180291F8650001F0E9 +:10AEF0008AFB216981F81902012081F812020020E1 +:10AF000081F8C0012079BDE81040E7F7FDBF10B51A +:10AF1000144C05212069FFF763FC206990F85A1052 +:10AF2000012908D000F5F57103F001FC2079BDE896 +:10AF30001040E7F7E9BF022180F85A1010BD10B5A4 +:10AF4000084C01230921206990F87C207030F6F725 +:10AF500019F848B12169002001F8960F087301F82B +:10AF60001A0C10BD000100200120A070F9E770B597 +:10AF7000F74D012329462869896990F87C200979D1 +:10AF80000E2A01D1122903D000241C2A03D004E088 +:10AF9000BDE87040D3E7142902D0202A07D008E08A +:10AFA00080F87C4080F8A240BDE87040AFE71629E9 +:10AFB00006D0262A01D1162902D0172909D00CE083 +:10AFC00000F87C4F80F82640407821280CD01A20C9 +:10AFD00017E090F87D20222A07D0EA69002A03D0E2 +:10AFE000FF2901D180F8A23132E780F87D4001F0DD +:10AFF00025FB286980F8974090F8C0010028F3D01D +:10B000000020BDE8704061E710B5D14C216991F88E +:10B010007C10202902D0262902D0A2E7FFF756FF94 +:10B020002169002081F87C0081F8A20099E72DE9D0 +:10B03000F843C74C206990F87C10202908D00027DD +:10B0400090F87D10222905D07FB300F17C0503E044 +:10B050000127F5E700F17D0510F8B01F41F004016C +:10B060000170A06903F015FA4FF00108002608B33B +:10B070003946A069FFF771FDE0B16A46A169206910 +:10B08000F6F7F7F890B3A06903F001FA2169A1F887 +:10B09000AA01B1F8701001F0AAFA40B32069282182 +:10B0A00080F88D1080F88C8058E0FFE70220A070B7 +:10B0B000BDE8F883206990F8C00110B11E20FFF7A9 +:10B0C00005FFAFB1A0692169C07881F8E20008FAF4 +:10B0D00000F1C1F3006000B9FFDF20690A2180F8A8 +:10B0E0007C1090F8A20040B9FFDF06E009E02AE0FA +:10B0F0002E7001F0A3FAFFF7D6FE206980F8976062 +:10B10000D6E7226992F8C00170B1B2F8703092F8B7 +:10B110006410B2F8C40102F5D572F6F79BF968B174 +:10B120002169252081F87C00206900F17D0180F8EB +:10B1300097608D4212D180F87D600FE00020FFF70C +:10B14000C5FE2E70F0E720699DF8001080F8AC1164 +:10B150009DF8011080F8AD1124202870206900F1BD +:10B160007D018D4203D1BDE8F84301F067BA80F854 +:10B17000A2609DE770B5764C01230B21206990F801 +:10B180007D207030F5F7FEFE202650BB206901239C +:10B19000002190F87D207030F5F7F4FE0125F0B124 +:10B1A000206990F87C0024281BD0A06903F04FF997 +:10B1B000C8B1206990F8B01041F0040180F8B010D7 +:10B1C000A1694A7902F0070280F85D20097901F04F +:10B1D000070180F85C1090F8C1311BBB06E0A57038 +:10B1E00036E6A67034E6BDE870405CE690F8C03103 +:10B1F000C3B900F164035E788E4205D1197891429B +:10B2000002D180F897500DE000F503710D700288AF +:10B210004A8090F85C200A7190F85D0048712079AE +:10B22000E7F772FE2169212081F87D00BDE87040BA +:10B2300001F0FBB9F8B5464C206990F87E0010F09B +:10B24000300F04D0A07840F00100A070F8BDA069D4 +:10B2500003F0E2F850B3A06903F0D8F80746A069FC +:10B2600003F0D8F80646A06903F0CEF80546A069B9 +:10B2700003F0CEF801460097206933462A46303065 +:10B2800003F0BFF9A079800703D56069C07814285E +:10B290000FD0216991F87C001C280AD091F85A003F +:10B2A00001280ED091F8B70158B907E0BDE8F84081 +:10B2B000F9E52169012081F85A0002E091F8B60110 +:10B2C00030B1206910F87E1F41F0100101700EE0CE +:10B2D00091F87E0001F5FC7240F0200081F87E00BC +:10B2E00031F8300B03F017FA2079E7F70DFEBDE8CF +:10B2F000F84001F09AB970B5154C206990F87E10AD +:10B30000890707D590F87C20012308217030F5F7D4 +:10B3100039FEF8B1206990F8AA00800712D4A0691C +:10B3200003F056F8216981F8AB00A06930F8052FC9 +:10B33000A1F8AC204088A1F8AE0011F8AA0F40F0A7 +:10B3400002000870206990F8AA10C90705D011E022 +:10B35000000100200120A0707AE590F87E008007AF +:10B3600000D5FFDF206910F87E1F41F00201017057 +:10B3700001F05BF92069002590F87C10062906D1C0 +:10B3800080F87C5080F8A2502079E7F7BDFD206955 +:10B3900090F8A8110429DFD180F8A8512079E7F7A7 +:10B3A000B3FD206990F87C100029D5D180F8A25017 +:10B3B0004EE570B5FB4C01230021206990F87D20FB +:10B3C0007030F5F7DFFD012578B9206990F87D2010 +:10B3D000122A0AD0012305217030F5F7D3FD10B1F0 +:10B3E0000820A07034E5A57032E5206990F8A80027 +:10B3F00008B901F01AF92169A06901F5847102F018 +:10B40000C8FF2169A069D83102F0CEFF206990F809 +:10B41000DC0100B1FFDF21690888A1F8DE0101F538 +:10B42000F071A06902F0A3FF2169A06901F5F47130 +:10B4300002F0A5FF206980F8DC51142180F87D100E +:10B440002079BDE87040E7F75FBD70B5D54C0123AA +:10B450000021206990F87D207030F5F793FD0125DB +:10B46000A8B1A06902F04FFF98B1A0692169B0F8B6 +:10B470000D00A1F8AA01B1F8701001F0B8F858B1A8 +:10B480002069282180F88D1080F88C50E0E4A570A8 +:10B49000DEE4BDE8704006E5A0692169027981F823 +:10B4A000AC21B0F80520A1F8AE2102F01FFF216900 +:10B4B000A1F8B001A06902F01CFF2169A1F8B20156 +:10B4C000A06902F01DFF2169A1F8B4010D2081F8E7 +:10B4D0007D00BDE47CB5B34CA079C00738D0A0692D +:10B4E00001230521C578206990F87D207030F5F79B +:10B4F00049FD68B1AD1E0A2D06D2DFE805F0090945 +:10B500000505090905050909A07840F00800A070A3 +:10B51000A07800281CD1A06902F0BEFE00286ED0E1 +:10B52000A0690226C5781DB1012D01D0162D18D1B4 +:10B53000206990F87C00F5F70DFD90B1216991F834 +:10B540007C001F280DD0202803D0162D16D0A67001 +:10B550007CBD262081F87C00162D02D02A20FFF722 +:10B56000B5FC0C2D5BD00CDC0C2D48D2DFE805F0CF +:10B5700036331F48BEBE4BB55ABE393C2020A070A2 +:10B580007CBD0120142D6ED008DC0D2D6CD0112D4A +:10B590006BD0122D6ED0132D31D168E0152D7FD0D8 +:10B5A000162D6FD0182D6ED0FF2D28D198E0206970 +:10B5B0000123194690F87F207030F5F7E3FC00284E +:10B5C00008D1A06902F0CCFE216981F88E01072024 +:10B5D00081F87F008CE001F0EDF889E0FFF735FF9E +:10B5E00086E001F0C7F883E0206990F87D1011290A +:10B5F00001D0A6707CE0122180F87D1078E075E023 +:10B60000FFF7D7FE74E0206990F87D001728F0D18D +:10B6100001F014F821691B2081F87D0068E0FFF734 +:10B620006AFE65E0206990F87E00C00703D0A0782C +:10B6300040F0010023E06946A06902F0D0FE9DF8C9 +:10B64000000000F02501206900F8B01F9DF80110EE +:10B6500001F04901417000F0E8FF206910F87E1FF9 +:10B6600041F0010117E018E023E025E002E0FFF7D8 +:10B6700066FC3DE0216991F87E10490704D5A07071 +:10B6800036E00DE00FE011E000F0CFFF206910F888 +:10B690007E1F41F0040101702AE0FFF7CBFD27E097 +:10B6A00001F030F824E0FFF765FD21E0FFF7BFFC73 +:10B6B0001EE0A06900790DE0206910F8B01F41F08C +:10B6C00004010170A06902F0F7FE162810D1A069EC +:10B6D00002F0F6FEFFF798FC0AE0FFF748FC07E0EF +:10B6E000E16919B1216981F8A20101E0FFF7DBFBF3 +:10B6F0002169F1E93002401C42F10002C1E9000277 +:10B700007CBD70B5274CA07900074AD5A0780028E9 +:10B7100047D1206990F8E400FE2800D1FFDF2069BE +:10B72000FE21002580F8E41090F87D10192906D13B +:10B7300080F8A75000F082FF206980F87D502069D2 +:10B7400090F87C101F2902D0272921D119E090F808 +:10B750007D00F5F7FFFB78B120692621012380F8F1 +:10B760007C1090F87D200B217030F5F70BFC78B938 +:10B770002A20FFF7ABFB0BE02169202081F87C0039 +:10B7800006E0012180F8A11180F87C5080F8A250D9 +:10B79000206990F87F10082903D10221217080F8D8 +:10B7A000E41021E40001002010B5FD4C216991F85E +:10B7B000AC210AB991F8642081F8642091F8AD2198 +:10B7C0000AB991F8652081F8652010B10020FFF7D3 +:10B7D0007DFB206902F041FF002806D02069BDE80A +:10B7E000104000F5F57102F0A2BF16E470B5EC4C04 +:10B7F00006460D46206990F8E400FE2800D0FFDFE1 +:10B800002269002082F8E46015B1A2F8A400E7E400 +:10B8100022F89E0F01201071E2E470B5E04C012384 +:10B820000021206990F87C207030F5F7ABFB0028F0 +:10B830007BD0206990F8B61111B190F8B71139B1E9 +:10B8400090F8C01100296FD090F8C11119B36BE0C6 +:10B8500090F87D1024291CD090F87C10242918D051 +:10B860005FF0000300F5D67200F5DB7102F096FE82 +:10B870002169002081F8B60101461420FFF7B6FFC8 +:10B88000216901F13000C28A21F8E62F408B4880FF +:10B8900050E00123E6E790F87D2001230B21703072 +:10B8A000F5F770FB68BB206990F8640000F0ABFE10 +:10B8B0000646206990F8650000F0A5FE054620695F +:10B8C00090F8C2113046FFF735F9D8B1206990F8E9 +:10B8D000C3112846FFF72EF9A0B12269B2F87030E3 +:10B8E00092F86410B2F8C40102F5D572F5F7B2FD12 +:10B8F00020B12169252081F87C001BE00020FFF7A2 +:10B90000E5FA11E020690123032190F87D207030D1 +:10B91000F5F738FB40B920690123022190F87D201A +:10B920007030F5F72FFB08B1002059E400211620F4 +:10B93000FFF75CFF012053E410B5E8BB984C206989 +:10B9400090F87E10CA0702D00121092052E08A0730 +:10B950000AD501210C20FFF749FF206910F8AA1F22 +:10B9600041F00101017047E04A0702D5012113208F +:10B9700040E00A0705D510F8E11F417101210720B9 +:10B9800038E011F0300F3BD090F8B711A1B990F822 +:10B99000B611E1B190F87D1024292FD090F87C10D9 +:10B9A00024292BD05FF0000300F5D67200F5DB717F +:10B9B00002F0F4FD216900E022E011F87E0F20F092 +:10B9C000200040F010000870002081F83801206944 +:10B9D00090F87E10C90613D502F03FFEFFF797FAE4 +:10B9E000216901F13000C28A21F8E62F408B48809E +:10B9F00001211520FFF7FAFE0120F6E60123D3E727 +:10BA00000020F2E670B5664C206990F8E410FE293B +:10BA100078D1A178002975D190F87F2001231946AB +:10BA20007030F5F7AFFA00286CD1206990F88C11CE +:10BA300049B10021A0F89C1090F88D1180F8E61013 +:10BA4000002102205BE090F87D200123042170306A +:10BA5000F5F798FA0546FFF76FFF002852D1284600 +:10BA600000F00CFF00284DD120690123002190F83F +:10BA70007C207030F5F786FA78B120690123042123 +:10BA800090F87D207030F5F77DFA30B9206990F894 +:10BA9000960010B10021122031E0206990F87C203E +:10BAA0000A2A0DD0002D2DD1012300217030F5F789 +:10BAB00069FA78B1206990F8A81104290AD105E043 +:10BAC00010F8E21F01710021072018E090F8AA0089 +:10BAD000800718D0FFF7A1FE002813D120690123A9 +:10BAE000002190F87C207030F5F74CFA002809D03E +:10BAF000206990F8A001002804D00021FF20BDE8B3 +:10BB0000704073E609E000210C20FFF76FFE20690A +:10BB100010F8AA1F41F0010101701DE43EB5054671 +:10BB20006846FDF7ABFC00B9FFDF22220021009838 +:10BB3000F2F7ADFC0321009802F096FB0098017823 +:10BB400021F010010170294602F0B3FB144C0D2DB9 +:10BB500043D00BDCA5F102050B2D19D2DFE805F06F +:10BB600022184B191922185718192700152D5FD0C4 +:10BB700008DC112D28D0122D0BD0132D09D0142D37 +:10BB800006D155E0162D2CD0172D6AD0FF2D74D07C +:10BB9000FFDFFDF786FC002800D1FFDF3EBD00007F +:10BBA000000100202169009891F8E61017E0E26892 +:10BBB00000981178017191884171090A8171518849 +:10BBC000C171090A0172E4E70321009802F072FCD6 +:10BBD0000621009802F072FCDBE700980621017153 +:10BBE000D7E70098D4F8101091F8C221027191F8AB +:10BBF000C3114171CDE72169009801F5887102F008 +:10BC0000D7FB21690098DC3102F0DCFBC1E7FA497F +:10BC1000D1E90001CDE90101206901A990F8B00046 +:10BC200000F025008DF80400009802F006FCB0E753 +:10BC30002069B0F84810009802F0D6FB2069B0F8EF +:10BC4000E810009802F0D4FB2069B0F84410009886 +:10BC500002F0D2FB2069B0F8E610009802F0D0FBA9 +:10BC600097E7216991F8C00100280098BCD111F82C +:10BC7000642F02714978BCE7FFE7206990F8A3219F +:10BC8000D0F8A411009802F022FB82E7DB4810B53F +:10BC9000006990F8821041B990F87D2001230621B7 +:10BCA0007030F5F76FF9002800D001209DE570B5E0 +:10BCB000D24D286990F8801039B1012905D00229A8 +:10BCC00006D0032904D0FFDF03E4B0F8F41037E016 +:10BCD00090F87F10082936D0B0F89810B0F89A2064 +:10BCE00000248B1C9A4206D3511A891E0C04240C82 +:10BCF00001D0641EA4B290F8961039B190F87C205F +:10BD0000012309217030F5F73DF940B3FFF7BEFF7D +:10BD100078B129690020B1F89020B1F88E108B1C01 +:10BD20009A4203D3501A801E00D0401EA04200D277 +:10BD300084B20CB1641EA4B22869B0F8F410214496 +:10BD4000A0F8F0102DE5B0F898100329BDD330F815 +:10BD5000701F428D1144491CA0F8801021E5002479 +:10BD6000EAE770B50C4605464FF4087200212046FC +:10BD7000F2F78DFB258014E5F8F7A2B92DE9F04123 +:10BD80000D4607460721F8F791F8041E3CD094F8B9 +:10BD9000C8010026A8B16E70092028700BE0268427 +:10BDA00084F8C861D4F8CA016860D4F8CE01A860EC +:10BDB000B4F8D201A88194F8C8010028EFD12E71FF +:10BDC000AEE094F8D40190B394F8D4010D2813D0C8 +:10BDD0000E2801D0FFDFA3E02088F8F798F9074686 +:10BDE000F7F745FE78B96E700E20287094F8D601EA +:10BDF00028712088E88014E02088F8F788F9074641 +:10BE0000F7F735FE10B10020BDE8F0816E700D200F +:10BE1000287094F8D60128712088E88094F8DA0117 +:10BE2000287284F8D4613846F7F71BFE78E0FFE704 +:10BE300094F80A0230B16E701020287084F80A62FB +:10BE4000AF806DE094F8DC0190B16E700A2028702C +:10BE50002088A880D4F8E011C5F80610D4F8E411C1 +:10BE6000C5F80A10B4F8E801E88184F8DC6157E00D +:10BE700094F8040270B16E701A20287005E000BFBB +:10BE800084F80462D4F80602686094F8040200287A +:10BE9000F6D145E094F8EA0188B16E70152028705B +:10BEA00008E000BF84F8EA6104F5F6702B1D07C8AE +:10BEB00083E8070094F8EA010028F3D130E094F811 +:10BEC000F80170B16E701C20287084F8F861D4F805 +:10BED000FA016860D4F8FE01A860B4F80202A881F3 +:10BEE0001EE094F80C0238B11D20287084F80C6212 +:10BEF000D4F80E02686013E094F81202002883D090 +:10BF00006E701620287007E084F81262D4F81402CC +:10BF10006860B4F81802288194F812020028F3D15E +:10BF2000012071E735480021C16101620846704770 +:10BF300030B5324D0C46E860FFF7F4FF00B1FFDF8B +:10BF40002C7130BD002180F87C1080F87D1080F8C5 +:10BF5000801090F8FB1009B1022100E00321FEF7E8 +:10BF60003FBC2DE9F041254C0546206909B100216F +:10BF700004E0B0F80611B0F8F6201144A0F806115C +:10BF800090F88C1139B990F87F2001231946703050 +:10BF9000F4F7F8FF30B1206930F89C1FB0F85A2050 +:10BFA00011440180206990F8A23033B1B0F89E109E +:10BFB000B0F8F6201144A0F89E1090F9A670002F5A +:10BFC00006DDB0F8A410B0F8F6201144A0F8A410D3 +:10BFD00001213D2615B180F88D6017E02278022AF4 +:10BFE0000ED0012A15D0A2784AB380F88C1012F036 +:10BFF000140F11D01E2117E0FC6202000001002086 +:10C0000090F8E620062A3CD016223AE080F88C1000 +:10C0100044E090F88E2134E0110702D580F88D605D +:10C020003CE0910603D5232180F88D1036E090077F +:10C0300000D1FFDF21692A2081F88D002AE02BB191 +:10C04000B0F89E20B0F8A0309A4210D2002F05DD43 +:10C05000B0F8A420B0F8A0309A4208D2B0F89C30D2 +:10C06000B0F89A20934204D390F88C310BB122227D +:10C0700007E090F880303BB1B0F89830934209D394 +:10C08000082280F88D20C1E7B0F89820062A01D355 +:10C090003E22F6E7206990F88C1019B12069BDE8BE +:10C0A000F0414FE7BDE8F0410021FEF799BB2DE9D3 +:10C0B000F047FF4C81460D4620690088F8F739F8B3 +:10C0C000060000D1FFDFA0782843A070A0794FF0D0 +:10C0D00000058006206904D5A0F8985080F8045126 +:10C0E00003E030F8981F491C0180FFF7CFFD4FF0A7 +:10C0F000010830B3E088000506D5206990F8821069 +:10C1000011B1A0F88E501CE02069B0F88E10491CC7 +:10C1100089B2A0F88E10B0F890208A4201D3531A49 +:10C1200000E0002327897F1DBB4201D880F896805C +:10C13000914206D3A0F88E5080F80A822079E6F763 +:10C14000E3FEA0794FF0020710F0600F0ED02069D7 +:10C1500090F8801011B1032908D102E080F88080A6 +:10C1600001E080F880700121FEF73AFB206990F829 +:10C170008010012904D1E188C90501D580F88070BB +:10C18000B9F1000F72D1E188890502D5A0F81851E4 +:10C1900004E0B0F81811491CA0F8181100F035FBA4 +:10C1A000FEF719FDFFF72EFC2769B7F8F800401CD1 +:10C1B000A7F8F80097F8FC0028B100F01BFFA8B121 +:10C1C000A7F8F85012E000F012FF08B1A7F8F850F5 +:10C1D00000F015FF50B197F80401401CC0B287F879 +:10C1E0000401022802D927F8F85F3D732069012372 +:10C1F000002190F87D207030F4F7C4FE20B920694A +:10C2000090F87D000C2859D120690123002190F875 +:10C210007C207030F4F7B6FE48B32069012300217A +:10C2200090F87F207030F4F7ADFE00B3206990F8ED +:10C230008010022942D190F80401C0B93046F7F7C6 +:10C24000E6F9A0B1216991F8E400FE2836D1B1F8F1 +:10C25000F200012832D981F8FA80B1F89A00B1F8D9 +:10C260009820831E9A4203DB012004E032E025E09F +:10C27000801A401E80B2B1F8F82023899A4201D377 +:10C28000012202E09A1A521C92B2904200D9104642 +:10C29000012801D181F8FA5091F86F2092B98A6E85 +:10C2A00082B1B1F89420B1F87010511A09B2002986 +:10C2B00008DD884200DB084680B203E021690120E6 +:10C2C00081F8FA502169B1F870201044A1F8F40007 +:10C2D000FFF7EDFCE088C0F340214846FFF741FE40 +:10C2E000206980F8FB50BDE8F047FDF7FCB87049C5 +:10C2F00002468878CB78184312D10846006942B1CB +:10C300008979090703D590F87F00082808D0012013 +:10C310007047B0F84C10028E914201D8FEF7B1B9C7 +:10C320000020704770B5624C05460E46E0882843F1 +:10C33000E080A80703D5E80700D0FFDF6661EA07C1 +:10C340004FF000014FF001001AD0A661F278062AE2 +:10C3500002D00B2A14D10AE0226992F87D30172B03 +:10C360000ED10023E2E92E3302F8370C08E02269EF +:10C3700092F87D30112B03D182F8811082F8A80049 +:10C38000AA0718D56269D278052A02D00B2A12D1E1 +:10C390000AE0216991F87D20152A0CD10022E1E9FB +:10C3A000302201F83E0C06E0206990F87D20102A2A +:10C3B00001D180F88210280601D50820E07083E4BE +:10C3C0002DE9F84301273A4C002567F30701E58082 +:10C3D000A570E570257020618946804680F8FB7065 +:10C3E0000088F7F7A6FE00B9FFDF20690088FDF797 +:10C3F00042F820690088FDF764F82069B0F8F2106F +:10C4000071B190F8E410FE290FD190F88C1189B128 +:10C4100090F87F20012319467030F4F7B3FD78B10E +:10C42000206990F8E400FE2804D0206990F8E40028 +:10C43000FFF774FB206990F8FD1089B1258118E0A1 +:10C440002069A0F89C5090F88D1180F8E61000212A +:10C450000220FFF7CBF9206980F8FA500220E7E7C5 +:10C4600090F8C81119B9018C8288914200D881884E +:10C47000218130F8F61F491E8EB230F8021F314478 +:10C4800020F86019018831440180FFF7FFFB20B1DB +:10C49000206930F88E1F314401802069B0F8F21015 +:10C4A000012902D8491CA0F8F2102EB102E00000C8 +:10C4B0000001002080F8045180F8FA5090F87D10B7 +:10C4C0000B2901D00C2916D1B0F87020B0F8AA3190 +:10C4D000D21A12B2002A0EDBD0F8AC11816090F8AB +:10C4E000B01101730321F4F773F8206980F87D50CF +:10C4F00080F8B27026E0242910D1B0F87010B0F89E +:10C50000AA21891A09B2002908DB90F8C001FFF7B7 +:10C510004BF9206900F87D5F857613E090F87C1078 +:10C52000242901D025290DD1B0F87010B0F8AA0146 +:10C53000081A00B2002805DB0120FFF735F9206951 +:10C5400080F87C5020690146B0F8F6207030F4F78E +:10C55000B2FAFC480090FC4BFC4A4146484600F0C9 +:10C560007DFC216A11B16078FCF7B5FA20690123DE +:10C57000052190F87D207030F4F704FD002803D0E9 +:10C58000BDE8F84300F0FDB9BDE8F88300F015BD43 +:10C59000EF49C8617047EE48C069002800D001200B +:10C5A0007047EB4A50701162704710B5044600881E +:10C5B000A4F8CC01B4F8B001A4F8CE01B4F8B201EB +:10C5C000A4F8D001B4F8B401A4F8D201012084F891 +:10C5D000C801DF480079E6F797FC02212046F3F70F +:10C5E000F7FF002004F87D0F0320E07010BD401A13 +:10C5F00000B247F6FE71884201DC002801DC012010 +:10C6000070470020704710B5012808D0022808D0D4 +:10C61000042808D0082806D0FFDF204610BD0124DA +:10C62000FBE70224F9E70324F7E7C9480021006982 +:10C6300020F8A41F8178491C81707047C44800B558 +:10C64000016911F8A60F401E40B20870002800DAF8 +:10C65000FFDF00BDBE482721006980F87C10002163 +:10C6600080F8A011704710B5B94C206990F8A81156 +:10C67000042916D190F87C20012300217030F4F7B2 +:10C6800081FC00B9FFDF206990F8AA10890703D464 +:10C69000062180F87C1004E0002180F8A21080F8C8 +:10C6A000A811206990F87E00800707D5FFF7C6FF24 +:10C6B000206910F87E1F21F00201017010BDA4490D +:10C6C00010B5096991F87C200A2A09D191F8E22075 +:10C6D000824205D1002081F87C0081F8A20010BDC3 +:10C6E00091F87E20130706D522F0080081F87E001D +:10C6F000BDE81040A2E7FF2801D0FFDF10BDBDE874 +:10C700001040A7E7F8B5924C01230A21206990F860 +:10C710007C207030F4F736FC38B3A06901F07CFE61 +:10C72000C8B1A06901F072FE0746A06901F072FE6F +:10C730000646A06901F068FE0546A06901F068FEA2 +:10C7400001460097206933462A46303001F059FFF0 +:10C75000206901F082FF2169002081F8A20081F8A0 +:10C760007C00BDE8F840FEF7D2BBA07840F00100A5 +:10C77000A070F8BD10B5764C01230021206990F817 +:10C780007D207030F4F7FEFB30B1FFF74EFF2169DA +:10C79000102081F87D0010BD20690123052190F84B +:10C7A0007D207030F4F7EEFB08B1082000E0012096 +:10C7B000A07010BD70B5664C01230021206990F86F +:10C7C0007D207030F4F7DEFB012588B1A06901F00F +:10C7D000C4FD2169A1F8AA01B1F87010FFF707FFA5 +:10C7E00040B12069282180F88D1080F88C50E6E552 +:10C7F000A570E4E52169A06901F5D67101F0A8FDF5 +:10C8000021690B2081F87D00D9E510B5FEF779FF8D +:10C81000FEF760FE4E4CA079400708D5A07830B9ED +:10C82000206990F87F00072801D101202070FEF7D1 +:10C8300071FAA079C00609D5A07838B9206990F8B6 +:10C840007D100B2902D10C2180F87D10E0780007C3 +:10C850000ED520690123052190F87D207030F4F772 +:10C8600091FB30B10820A0702169002081F8D4012B +:10C8700010BDBDE81040002000F0C4BB10B5344C22 +:10C88000216991F87D2048B3102A06D0142A07D0D8 +:10C89000152A1AD01B2A2CD11AE001210B2019E0ED +:10C8A000FAF702FE0C2817D32069082100F58870DA +:10C8B000FAF7FEFD28B120690421DC30FAF7F8FD13 +:10C8C00000B9FFDF0121042004E000F017F803E0C5 +:10C8D00001210620FEF78AFF012010BD212A08D180 +:10C8E00091F8970038B991F8C00110B191F8C101E1 +:10C8F00008B1002010BD01211720EBE770B5144CE2 +:10C900000025206990F88F1101290AD002292ED123 +:10C9100090F8A810F1B1062180F8E610012102205C +:10C9200020E090F8D411002921D100F1C80300F5CE +:10C930008471002200F5C870F4F796FA01210520F1 +:10C9400010E00000AFC00100EFC2010025C30100EC +:10C950000001002090F8B000400701D5112000E050 +:10C960000D200121FEF742FF206980F88F5126E556 +:10C9700030B5FB4C05462078002818BFFFDFE57175 +:10C9800030BDF7490120887170472DE9F14FF54D11 +:10C990002846446804F1700794F86510608F94F895 +:10C9A0008280268F082978D0F4F797FBB8F1000F22 +:10C9B00004BF001D80B2864238BF304600F0FF0839 +:10C9C000DFF89C93E848C9F8240009F134006E6848 +:10C9D000406800F1700A90F882B096F86510358FC3 +:10C9E000708F08295DD0F4F778FB00BFBBF1000F12 +:10C9F00004BF001D80B2854238BF2846C0B29AF8F5 +:10CA00001210002918BF04210844C0B296F865101E +:10CA1000FBF735FCB87C002847D007F15801D24815 +:10CA200091E80E1000F5027585E80E10B96EC0F899 +:10CA30002112F96EC0F8251200F58170FBF7DBFFBB +:10CA4000C848007800280CBF0120002080F00101B8 +:10CA5000C6480176D7E91412C0E90412A0F5837222 +:10CA6000D9F82410FBF7F5F994F86500012808BF00 +:10CA700000220CD0022808BF012208D0042808BFD9 +:10CA8000032204D008281ABFFFDF002202224146F9 +:10CA90000120FBF7F9F90EE0FFE70421F4F71DFB95 +:10CAA00084E70421F4F719FBA0E7D9F82400FBF789 +:10CAB000A2FFFBF715FA009850B994F8650094F8B6 +:10CAC000661010F00C0F08BF00219620FBF7B4FF92 +:10CAD00094F8642001210020FCF76BF894F82C00F6 +:10CAE000012808BFFCF735F8022089F80000FCF7A0 +:10CAF0003FFC002818BFFFDFBDE8F88F2DE9F04F9D +:10CB0000DFF860A28BB050469AF800204068AAF186 +:10CB10001401059190F8751000F1700504464FF06E +:10CB200008080127AAF13406A1B3012900F0068103 +:10CB3000022900F00781032918BFFFDF00F01881E8 +:10CB4000306A0423017821F008010170AA7908EA0B +:10CB5000C202114321F004010170EA7903EA820262 +:10CB6000114321F01001017095F80590F06AF6F775 +:10CB70005EFD8046FCF7C9FCB9F1020F00F00081B0 +:10CB8000B9F1010F00F00081B9F1030F00F000814D +:10CB900000F003B9FFE795F80CC04FF002094FF021 +:10CBA000000BBCF1240F1CBF6B7B242B08D0BCF105 +:10CBB0001F0F18BFBCF1200F2AD0222B4DD077E0D9 +:10CBC00094F864109AB190F8AC01002874D0082948 +:10CBD00018BF042969D0082818BF042865D0012986 +:10CBE00018BF012853D000BF4FF0020164E090F855 +:10CBF0001201002860D0082918BF042955D0082840 +:10CC000018BF042851D0012918BF01283FD0EBE7F5 +:10CC1000222B22D0002A4BD090F8C20194F8641045 +:10CC200010F0040F18BF40460CD0082918BF042983 +:10CC30003BD0082818BF042837D0012918BF012885 +:10CC400025D0D1E710F0010F18BF3846EDD110F014 +:10CC5000020F18BF4846E8D12EE04AB390F8C2212F +:10CC600090F85D0094F8641002EA000010F0040FE0 +:10CC700018BF40460ED0082918BF042915D008282F +:10CC800018BF042811D0012918BF0128ACD14FF0DA +:10CC9000010111E010F0010F18BF3846EBD110F080 +:10CCA000020F18BF4846E6D106E04FF0080103E046 +:10CCB00094F864100429F8D0A08E11F00C0F18BF5E +:10CCC0004FF42960F4F709FA218E814238BF0846F3 +:10CCD000ADF80400A4F84C000598FCF7F5FB60B132 +:10CCE0007289316A42F48062728172694FF48060A5 +:10CCF000904703206871EF7022E709AA01A9F06A42 +:10CD0000F6F7CFFB306210B195F8371021B10598D6 +:10CD1000FCF7AEFB6F7113E79DF8241031B9A0F852 +:10CD200000B080F802B0012101F09EFABDF80410B5 +:10CD3000306A01F0C7FB85F8059001E70598FCF71C +:10CD400097FBFDE6B4F84C00ADF8040009AA01A970 +:10CD5000F06AF6F7A6FB3062002808BFFFDFEFE6B7 +:10CD60002401002058010020300D0020380F002041 +:10CD70000598FCF7A9FB002808BFFFDFE0E600BF2D +:10CD800030EA080009D106E030EA080005D102E0E7 +:10CD9000B8F1000F01D0012100E00021306A0278D3 +:10CDA00042EA01110170697C00291CBF69790129DF +:10CDB0003BD005F15801FD4891E80E1000F50278CE +:10CDC00088E80E10A96EC0F82112E96EC0F825128D +:10CDD00000F58170FBF70FFE9AF8000000280CBFE9 +:10CDE00001210021F2480176D5E91212C0E90412AE +:10CDF000A0F58371326AFBF72CF894F864000128DF +:10CE000008BF00220CD0022808BF012208D0042845 +:10CE100008BF032204D008281ABFFFDF0022022225 +:10CE2000FB210020FBF730F803E0FBF7E4FDFBF704 +:10CE300057F8012194F865200846FBF7BAFE3771D0 +:10CE4000306A0188F181807830743770FCF799FA84 +:10CE5000002818BFFFDF0BB0BDE8F08F2DE9F043CD +:10CE6000D44D87B081462878DDF838801E461746B5 +:10CE70000C4628B9002F1CBF002EB8F1000F00D1BE +:10CE8000FFDFC5F81C80C5E90D94C5E905764FF0B4 +:10CE90000000A8716871E870A8702871C64E68819A +:10CEA000A881307804F170072088F7F742F9E8622A +:10CEB0002088F7F72CF92863FBF705FA94F9670047 +:10CEC000FBF7DAFA04F11200FBF76CFD04F10E0037 +:10CED000FBF7D8FA307800280CBF03200120FBF7BD +:10CEE00087FDB64890E80E108DE80E10D0E90410CA +:10CEF000CDE90410307800280CBFB148B148049047 +:10CF00006846FBF763FDF87EFBF7C4FAFBF76AFDA2 +:10CF100094F86F0078B9A06E68B1B88C39888842EF +:10CF200009D1B4F86C1001220844B88494F86E005A +:10CF3000A16EF8F7D8FE3078002804BFFF2094F8DF +:10CF400064401AD094F8651097F81280258F608F8E +:10CF5000082926D0F4F7C1F8B8F1000F04BF001D6E +:10CF600080B2854238BF2846C0B2B97C002918BFBC +:10CF70000421084494F86540C0B22146FBF77FF9CC +:10CF80003078214688B10120FBF74BFB7068D0F860 +:10CF90000001FBF733FD0120FFF7F7FC07B0BDE808 +:10CFA000F0830421F4F799F8D6E70020FBF739FB6A +:10CFB000FFF7A4FD07B0BDE8F0837F4800B5017816 +:10CFC0003438007819B1022818BFFFDF00BD0128EE +:10CFD00018BFFFDF00BD774810B50078022818BFE2 +:10CFE000FFDFBDE8104000F070BA00F06EBA714883 +:10CFF000007970476F488089C0F3002070476D4802 +:10D00000C07870472DE9F04706006B48694D4068CD +:10D0100000F17004686A90F8019018BF012E03D1E6 +:10D02000296B07F0F1FF6870687800274FF001085E +:10D03000A0B101283CD0022860D003281CBFFFDF2C +:10D04000BDE8F087012E08BFBDE8F087286BF6F732 +:10D05000E3FCE879BDE8F047E5F756BF012E14D0B0 +:10D06000A86A002808BFFFDF2889C21CD5E909107B +:10D07000F1F7E3F9A86A686201224946286BF6F7DE +:10D0800047FB022E08BFBDE8F087D4E91401401C1D +:10D0900041F10001C4E91401E079012801D1E771EF +:10D0A00001E084F80780E879BDE8F047E5F72CBF98 +:10D0B000012E14D0A86A002808BFFFDF2889C21CEF +:10D0C000D5E90910F1F7B9F9A86A68620022494662 +:10D0D000286BF6F71DFB022E08BFBDE8F087D4E9E8 +:10D0E0001410491C40F10000C4E91410E079012833 +:10D0F0000CBFE77184F80780BDE8F087012E06D0E9 +:10D10000286BF6F789FC022E08BFBDE8F087D4E94A +:10D110001410491C40F10000C4E91410E079012802 +:10D12000BFD1BCE72DE9F041234D2846A5F13404D9 +:10D13000406800F170062078012818BFFFDFB07842 +:10D140000127002158B1B1706289042042F0040225 +:10D150006281626990472878002818BF3771216A78 +:10D160000322087832EA000009D1628912F4806F44 +:10D1700005D042F002026281626902209047A169F3 +:10D180000020884760B3607950BB287818B30E48F8 +:10D19000007810F0100F04D10449097811F0100F35 +:10D1A0001ED06189E1B9A16AA9B90FE0300D002054 +:10D1B000380F0020240100205801002004630200E1 +:10D1C000BB220200A7A8010032010020218911B171 +:10D1D00010F0100F04D0BDE8F0410020FFF7D5BBE0 +:10D1E000BDE8F04100F071B92DE9F05FCC4E044686 +:10D1F0003046A6F134054068002700F1700A28780F +:10D20000B846022818BFFFDFA889FF2240F400704B +:10D21000A881706890F864101046FBF730F89AF80F +:10D2200012004FF00109002C00F0F080FAF77DFEAB +:10D23000FAF76BFE90B99AF8120078B1686A4178F3 +:10D2400061B100789AF80710C0F3C000884205D198 +:10D2500085F80290BDE8F05F00F037B9686A417860 +:10D260002981002908BFAF6203D0286BF6F70AFABC +:10D27000A862A88940F02000A881EF70706800F1D2 +:10D28000700B044690F82C0001281BD1FBF757FCCB +:10D2900059462046F3F729FEA0B13078002870687F +:10D2A0000CBF00F59A7000F50170218841809BF851 +:10D2B000081001719BF80910417180F80090E8791D +:10D2C000E5F722FE686A9AF806100078C0F380003D +:10D2D00088423AD0706800F1700490F87500002818 +:10D2E0002FD002284AD06771307800281CBF2079DF +:10D2F000002809D027716A89394642F010026A81F4 +:10D300006A694FF010009047E078A0B1E770FCF731 +:10D31000EAF8002808BFFFDF08206A89002142F0F0 +:10D3200008026A816A699047D4E91210491C40F1E9 +:10D330000000C4E91210A07901280CBFA77184F87D +:10D340000690A88940F48070A881696A9AF807302D +:10D350000878C0F3C0029A424DD1726800F0030011 +:10D3600002F17004012818BF02282DD003281CBF29 +:10D37000687940F0040012D068713CE0E86AF6F782 +:10D38000BCF8002808BFFFDFD4E91210491C40F1A7 +:10D390000000C4E91210E879E5F7B6FDA3E784F8C8 +:10D3A0000290AA89484642F40062AA816A8942F042 +:10D3B00001026A816A699047E079012801D1E77129 +:10D3C00019E084F8079016E04878D8B1A98941F4AB +:10D3D0000061A981A96A71B1FB2884BF687940F016 +:10D3E0001000C9D8A879002808BFC84603D08020FB +:10D3F0006A69002190470120A9698847E0B36879EC +:10D40000A0B13AE0E0790128DBD1D8E7002818BFC5 +:10D41000FAF7C5FDA88940F04000A881E97801200D +:10D42000491CC9B2E97001292DD8E5E7307890B9D7 +:10D430003C48007810F0100F04D13B49097811F0F6 +:10D44000100F1AD06989B9B9A96A21B9298911B10E +:10D4500010F0100F11D0B8F1000F1CBF0120FFF722 +:10D46000D1FDFFF74BFBB8F1000F08BFBDE8F09FFF +:10D470000220BDE8F05FC5E5FFE7B8F1000F1CBF73 +:10D480000020FFF7BFFDBDE8F05F00F01EB870B5EB +:10D490000D4606462248224900784C6850B1FAF7FA +:10D4A000F7FD034694F8642029463046BDE87040F5 +:10D4B000FDF78BBAFAF7ECFD034694F86420294691 +:10D4C0003046BDE8704004F0FCBE154910B54C680C +:10D4D000FBF714FBFBF7F3FAFBF7BCF9FBF768FA71 +:10D4E000FAF7FEFC94F82C00012808BFFBF727FB95 +:10D4F00094F86F0038B9A06E28B1002294F86E003D +:10D500001146F8F7F0FB094C00216269A0899047A9 +:10D51000E2696179A07890470020207010BD00007A +:10D520005801002032010020300D0020240100208D +:10D530002DE9F047FA4F894680463D782C0014D0FB +:10D540000126012D11DB601EC4B207EBC40090F868 +:10D550005311414506D10622494600F5AA70F0F75D +:10D560003FFF28B1761CAE42EDDD1020BDE8F0870C +:10D570002046BDE8F087EA498A78824286BF08449F +:10D5800090F843010020704710B540F2D3120021FB +:10D59000E348F0F77CFF0822FF21E248F0F777FF2D +:10D5A000E1480021417081704FF46171818010BDAC +:10D5B0002DE9F0410E460546FFF7BAFFD84C10287A +:10D5C00016D004EBC00191F85A0110F0010F1CBFF6 +:10D5D0000120BDE8F081607808283CBF012081F877 +:10D5E0005A011CD26078401C60700120BDE8F081B7 +:10D5F0006078082813D222780127501C207004EB91 +:10D60000C2083068C8F85401B088A8F85801102A38 +:10D6100028BFFFDF88F8535188F85A71E2E70020ED +:10D62000BDE8F081C04988707047BF488078704776 +:10D630002DE9F041BA4D00272878401E44B2002C55 +:10D6400030DB00BF05EBC40090F85A0110F0010F69 +:10D6500024D06878E6B2401E687005EBC6083046F4 +:10D6600088F85A7100F0E8FA102817D12878401E7F +:10D67000C0B22870B04211D005EBC001D1F85301FF +:10D68000C8F85301D1F85701C8F85701287800F0BD +:10D69000D3FA10281CBF284480F80361601E44B2EE +:10D6A000002CCFDAA0488770BDE8F0819C498A78C9 +:10D6B000824286BF01EB0010C01C002070472DE99C +:10D6C000F0470127994690463D460026FFF730FF78 +:10D6D000102820D0924C04EBC00191F85A1101F0AF +:10D6E000010600F0A9FA102815D0B9F1000F18BFF3 +:10D6F00089F80000A17881420DD904EB001111F1E5 +:10D70000030F08D0204490F84B5190F83B010128BA +:10D710000CBF0127002748EA060047EA0501084038 +:10D72000BDE8F0872DE9F05F1F4690468946064622 +:10D73000FFF7FEFE7A4C054610282ED000F07CFA4A +:10D7400010281CBF1220BDE8F09FA07808283ED208 +:10D75000A6781022701CA07004EB061909F10300D2 +:10D760004146F3F768FB09F1830010223946F3F7CD +:10D7700062FB10213846F3F74BFB3444102184F848 +:10D7800043014046F3F744FB84F84B0184F803510E +:10D79000002084F83B01BDE8F09FA078082816D24D +:10D7A00025784FF0000A681C207004EBC50BD9F8EF +:10D7B0000000CBF85401B9F80400ABF85801102D63 +:10D7C00028BFFFDF8BF853618BF85AA1C0E7072011 +:10D7D000BDE8F09F2DE9F041514CA078401E45B2C4 +:10D7E000002DB8BFBDE8F081EAB2A078401EC1B2FA +:10D7F000A17054FA85F090F803618A423DD004EBA1 +:10D80000011004EB0213D0F803C0C3F803C0D0F832 +:10D8100007C0C3F807C0D0F80BC0C3F80BC0D0F8DE +:10D820000FC0C3F80FC0D0F883C0C3F883C0D0F8CE +:10D8300087C0C3F887C0D0F88BC0C3F88BC0D0F8BE +:10D840008F00C3F88F006318A01801EB410193F813 +:10D8500003C102EB420204EB410180F803C104EB77 +:10D860004202D1F80BC1C2F80BC1B1F80F11A2F8F6 +:10D870000F1193F83B1180F83B1104EBC60797F8A2 +:10D880005A0110F0010F1CD1304600F0D5F91028D4 +:10D8900017D12078401EC0B22070B04211D004EBE6 +:10D8A000C000D0F85311C7F85311D0F85701C7F88A +:10D8B0005701207800F0C0F910281CBF204480F8E0 +:10D8C0000361681E45B2002D8EDABDE8F08116496D +:10D8D0004870704714484078704738B14AF2B81120 +:10D8E000884203D810498880012070470020704783 +:10D8F0000D488088704710B5FFF71AFE102804D035 +:10D9000000F09AF9102818BF10BD082010BD044976 +:10D910008A78824286BF01EB001083300020704776 +:10D92000600F00206C01002060010020FE4B93F886 +:10D9300002C084459CBF00207047184490F8030142 +:10D9400003EBC00090F853310B70D0F85411116004 +:10D95000B0F85801908001207047F34A114491F8C3 +:10D960000321F2490A700268C1F8062080884881C4 +:10D97000704770B516460C460546FBF7D5F8FAF722 +:10D98000C4F9EA48407868B1E748817851B12A196A +:10D99000002E0CBF8330C01CFAF791F9FAF7D8F9C2 +:10D9A000012070BD002070BD10B5FAF7FFF9002806 +:10D9B00004BFFF2010BDBDE81040FAF71DBAFAF70A +:10D9C000F5B9D9498A7882429CBF00207047084443 +:10D9D00090F8030101EBC00090F85A0100F001003B +:10D9E00070472DE9F047D04D00273E4628780028A3 +:10D9F00086BF4FF01009DFF83883BDE8F087AC78B8 +:10DA000021000CD00122012909DB601EC4B22819B3 +:10DA100090F80331B34203D0521C8A42F5DD4C46E4 +:10DA2000A14286BF05EB0410C01C002005EBC60A0E +:10DA30009AF85A1111F0010F16D050B1102C04D0E1 +:10DA4000291991F83B11012903D01021F3F7E0F9CE +:10DA500050B108F8074038467B1C9AF853210AF564 +:10DA6000AA71DFB2FAF7B5FC701CC6B22878B042D2 +:10DA7000C5D8BDE8F0872DE9F041AB4C002635460E +:10DA8000A07800288CBFAA4FBDE8F0816119C0B210 +:10DA900091F80381A84286BF04EB0510C01C00204A +:10DAA00091F83B11012903D01021F3F7B1F958B1D6 +:10DAB00004EBC800BD5590F8532100F5AA7130461B +:10DAC000731CDEB2FAF785FC681CC5B2A078A842C8 +:10DAD000DCD8BDE8F0810144934810B500EB02109A +:10DAE0000A4601218330FAF7EAF8BDE81040FAF758 +:10DAF0002FB90A468D4910B5497841B18A4B9978BA +:10DB000029B10244D81CFAF7DAF8012010BD002030 +:10DB100010BD854A01EB410102EB41010268C1F8E9 +:10DB20000B218088A1F80F0170472DE9F0417E4D4F +:10DB300007460024A878002898BFBDE8F081C0B24D +:10DB4000A04217D905EB041010F1830612D0102162 +:10DB50003046F3F75DF968B904EB440005EB400883 +:10DB600008F20B113A463046FBF74EFDB8F80F01AC +:10DB7000A8F80F01601CC4B2A878A042DFD8BDE8A5 +:10DB8000F081014610226B48F3F755B96948704798 +:10DB900065498A78824203D90A1892F843210AB16A +:10DBA0000020704700EB400001EB400000F20B103A +:10DBB00070475D498A78824206D9084490F83B0153 +:10DBC000002804BF01207047002070472DE9F04174 +:10DBD0000E460746144606213046F3F719F9524D12 +:10DBE00098B1A97871B105F59D7011F0010F18BFBA +:10DBF00000F8014FA978490804D0447000F8024F9A +:10DC0000491EFAD10120BDE8F08138463146FFF7C0 +:10DC10008FFC10280CD000F00FF8102818BF08282F +:10DC200006D0284480F83B414FF00100BDE8F08168 +:10DC30004FF00000BDE8F0813B4B10B4844698786B +:10DC400001000ED0012201290BDB401EC0B21C18BE +:10DC500094F80341644504BF10BC7047521C8A42CB +:10DC6000F3DD10BC1020704770B52F4C01466218D0 +:10DC7000A078401EC0B2A07092F8035181423CD0FF +:10DC800004EB011304EB001C01EB4101DCF8036021 +:10DC9000C3F80360DCF80760C3F80760DCF80B60CA +:10DCA000C3F80B60DCF80F60C3F80F60DCF883602A +:10DCB000C3F88360DCF88760C3F88760DCF88B60AA +:10DCC000C3F88B60DCF88FC0C3F88FC0231800EB5B +:10DCD000400093F803C104EB400082F803C104EB59 +:10DCE0004101D0F80BC1C1F80BC1B0F80F01A1F888 +:10DCF0000F0193F83B0182F83B0104EBC50696F84F +:10DD00005A0110F0010F18BF70BD2846FFF794FFAD +:10DD1000102818BF70BD2078401EC0B22070A842E5 +:10DD200008BF70BD08E00000600F00206001002007 +:10DD30006C0100203311002004EBC000D0F8531117 +:10DD4000C6F85311D0F85701C6F857012078FFF7ED +:10DD500073FF10281CBF204480F8035170BD0000E1 +:10DD60004078704730B50546007801F00F0220F08A +:10DD70000F0010432870092912D2DFE801F00507CF +:10DD800005070509050B0F0006240BE00C2409E02C +:10DD9000222407E001240020E87003E00E2401E0C3 +:10DDA0000024FFDF6C7030BD007800F00F0070477A +:10DDB0000A68C0F803208988A0F807107047D0F8D7 +:10DDC00003200A60B0F80700888070470A68C0F82E +:10DDD00009208988A0F80D107047D0F809200A6042 +:10DDE000B0F80D00888070470278402322F040028E +:10DDF00003EA81111143017070470078C0F380106D +:10DE000070470278802322F0800203EAC111114397 +:10DE1000017070470078C009704770B514460E460F +:10DE200005461F2A88BFFFDF2246314605F109005B +:10DE3000F0F703FBA01D687070BD70B544780E4606 +:10DE40000546062C38BFFFDFA01F84B21F2C88BFF9 +:10DE50001F24224605F109013046F0F7EEFA20466C +:10DE600070BD70B514460E4605461F2A88BFFFDFF9 +:10DE70002246314605F10900F0F7DFFAA01D68706F +:10DE800070BD0968C0F80F1070470A88A0F8132009 +:10DE900089784175704790F8242001F01F0122F025 +:10DEA0001F02114380F824107047072988BF0721FB +:10DEB00090F82420E02322F0E00203EA411111430C +:10DEC00080F8241070471F3008F065B810B504467C +:10DED00000F000FB002818BF204410BDC17811F0ED +:10DEE0003F0F1BBF027912F0010F0022012211F037 +:10DEF0003F0F1BBF037913F0020F002301231A44C5 +:10DF000002EB4202530011F03F0F1BBF027912F0E7 +:10DF1000080F0022012203EB420311F03F0F1BBF49 +:10DF2000027912F0040F00220122134411F03F0F76 +:10DF30001BBF027912F0200F0022012202EBC20265 +:10DF400003EB420311F03F0F1BBF027912F0100FD9 +:10DF50000022012202EB42021A4411F03F0F1BBFC4 +:10DF6000007910F0400F00200120104410F0FF0055 +:10DF700014BF012100210844C0B2704770B5027877 +:10DF8000417802F00F02082A4DD2DFE802F00408BF +:10DF90000B4C4C4C0F14881F1F280AD943E00C2946 +:10DFA00007D040E0881F1F2803D93CE0881F1F28A6 +:10DFB00039D8012070BD4A1EFE2A34D88446C07864 +:10DFC00000258209032A09D000F03F04601C884222 +:10DFD00004D86046FFF782FFA04201D9284670BDF1 +:10DFE0009CF803004FF0010610F03F0F1EBF1CF11C +:10DFF0000400007810F0100F13D06446042160462E +:10E0000000F068FA002818BF14EB0000E6D0017891 +:10E0100001F03F012529E1D280780221B1EB501FA8 +:10E02000DCD3304670BD002070BD70B5017801258D +:10E0300001F00F01002404290AD007290DD0082976 +:10E040001CBF002070BD40780E2836D0204670BD21 +:10E050004078801F1F2830D9F8E7844640789CF824 +:10E0600003108A09032AF1D001F03F06711C814296 +:10E07000ECD86046FFF732FFB042E7D89CF80300C7 +:10E0800010F03F0F1EBF1CF10400007810F0100FBD +:10E0900013D066460421604600F01CFA002818BF21 +:10E0A00016EB0000D2D0017801F03F012529CDD236 +:10E0B00080780221B1EB501FC8D3284670BD10B440 +:10E0C000017801F00F01032920D0052921D14478DE +:10E0D000B0F81910B0F81BC0B0F81730827D222CB0 +:10E0E00017D1062915D3B1F5486F98BFBCF5FA7F53 +:10E0F0000FD272B1082A98BF8A420AD28B429CBFC3 +:10E10000B0F81D00B0F5486F03D805E040780C2842 +:10E1100002D010BC0020704710BC012070472DE9D0 +:10E12000F0411F4614460D00064608BFFFDF21469A +:10E13000304600F0CFF9040008BFFFDF30193A463F +:10E140002946BDE8F041F0F778B9C07800F03F000B +:10E150007047C02202EA8111C27802F03F021143E7 +:10E16000C1707047C07880097047C9B201F00102E0 +:10E17000C1F340031A4402EB4202C1F3800303EBF4 +:10E180004202C1F3C00302EB4302C1F3001303EBED +:10E1900043031A44C1F3401303EBC30302EB4302EE +:10E1A000C1F380131A4412F0FF0202D0521CD2B203 +:10E1B0000171C37802F03F0103F0C0031943C1703D +:10E1C000511C417070472DE9F0410546C078164654 +:10E1D00000F03F041019401C0F46FF2888BFFFDFE6 +:10E1E000281932463946001DF0F727F9A019401CBE +:10E1F0006870BDE8F081C178407801F03F01401AB5 +:10E20000401E80B2704710B590F803C00B460CF06A +:10E210003F0144780CF03F0CA4EB0C0CACF1010C6A +:10E220001FFA8CF4944288BF14462BB10844011D98 +:10E2300022461846F0F701F9204610BD4078704795 +:10E2400000B5027801F0030322F003021A430270C2 +:10E25000012914BF0229002104D0032916BFFFDFC2 +:10E26000012100BD417000BD00B5027801F003033B +:10E2700022F003021A430270012914BF022900216F +:10E2800004D0032916BFFFDF012100BD417000BD8E +:10E29000007800F003007047417841B1C078192838 +:10E2A00003D2BC4A105C884201D101207047002093 +:10E2B000704730B501240546C17019293CBFB548E7 +:10E2C000445C02D3FF2918BFFFDF6C7030BD70B50E +:10E2D00015460E4604461B2A88BFFFDF65702A4696 +:10E2E0003146E01CBDE87040F0F7A7B8B0F8070071 +:10E2F0007047B0F809007047C172090A017370478E +:10E30000B0F80B00704730B4B0F80720B0F809C07F +:10E31000B0F805300179941F40F67A45AC4298BFB9 +:10E32000BCF5FA7F0ED269B1082998BF914209D293 +:10E3300093429FBFB0F80B00B0F5486F012030BC8E +:10E3400098BF7047002030BC7047001D07F023BE07 +:10E35000021D0846114607F01EBEB0F809007047BE +:10E36000007970470A684260496881607047426876 +:10E370000A60806848607047098881817047808999 +:10E38000088070470A68C0F80E204968C0F812106B +:10E390007047D0F80E200A60D0F81200486070472D +:10E3A0000968C0F816107047D0F81600086070476A +:10E3B0000A68426049688160704742680A60806804 +:10E3C000486070470968C1607047C068086070475E +:10E3D000007970470A684260496881607047426806 +:10E3E0000A608068486070470171090A417170478E +:10E3F0008171090AC17170470172090A417270473F +:10E400008172090AC172704780887047C08870475E +:10E41000008970474089704701891B2924BF4189C1 +:10E42000B1F5A47F07D381881B2921BFC088B0F52F +:10E43000A47F01207047002070470A684260496845 +:10E440008160704742680A6080684860704701795F +:10E4500011F0070F1BBF407910F0070F00200120BB +:10E460007047017911F0070F1BBF407910F0070FBB +:10E470000020012070470171704700797047417199 +:10E480007047407970478171090AC1717047C0882F +:10E4900070470179407901F007023F498A5C012AFF +:10E4A00006D800F00700085C01289CBF01207047D7 +:10E4B00000207047017170470079704741717047C3 +:10E4C0004079704730B50C460546FB2988BFFFDF11 +:10E4D0006C7030BDC378024613F03F0008BF704730 +:10E4E0000520127903F03F0312F0010F37D0002905 +:10E4F00014BF0B20704700BF12F0020F32D0012969 +:10E5000014BF801D704700BF12F0040F2DD00229E8 +:10E5100014BF401C704700BF12F0080F28D0032919 +:10E5200014BF801C704700BF12F0100F23D00429C5 +:10E5300014BFC01C704700BF12F0200F1ED0052969 +:10E540001ABF1230C0B2704712F0400F19D006291E +:10E550001ABF401CC0B27047072918D114E0002927 +:10E56000CAD114E00129CFD111E00229D4D10EE0A3 +:10E570000329D9D10BE00429DED108E00529E3D134 +:10E5800005E00629E8D102E0834288BF70470020F9 +:10E5900070470000246302001C63020030B490F84E +:10E5A00064508C88B1F808C015F00C0F1BD000BF68 +:10E5B000B4F5296F98BF4FF4296490F8655015F0B1 +:10E5C0000C0F17D0BCF5296F98BF4FF4296C4A88FF +:10E5D000C988A0F84420A0F84810A0F84640A0F848 +:10E5E0004AC030BC7047002B1CBF157815F00C0FCB +:10E5F000DED1E2E7002B1CBF527812F00C0FE1D104 +:10E60000E5E7DDF800C08181C2810382A0F812C075 +:10E6100070471B2202838282C281828142800281F2 +:10E62000028042848284828359B14FF429614183FC +:10E63000C18241820182C18041818180C184018582 +:10E6400070474FF4A4714183C18241820182C1802D +:10E6500041818180C18401857047F0B4B0F84820C1 +:10E66000818F468EC58E8A4228BF0A4690F8651073 +:10E670004FF0000311F00C0F18BF4FF4296106D1C1 +:10E68000B0F84AC0B0F840108C4538BF61464286A9 +:10E69000C186048FB0F83AC0944238BF14468C4506 +:10E6A00038BF8C460487A0F83AC0B2420ABFA942DC +:10E6B0004FF0010C4FF0000C058EB0F84410C28FE3 +:10E6C000848E914228BF114690F8642012F00C0FFE +:10E6D00018BF4FF4296206D1B0F84660B0F8422066 +:10E6E000964238BF324690F85A60022E0AD0018610 +:10E6F0008286A9420ABFA2420120002040EA0C0003 +:10E70000F0BC70478D4238BF2946944238BF22463C +:10E7100080F85A30EBE7508088899080C889D08093 +:10E72000088A1081488A508101201070704730B4E7 +:10E7300002884A80B0F830C0A1F804C0838ECB8034 +:10E74000428E0A81C48E4C81B0F85650A54204BF57 +:10E75000B0F85240944208D1B0F858409C4202BFF1 +:10E76000B0F854306345002301D04FF001030B7320 +:10E7700000F13003A0F852201A464B89D3848B88CD +:10E780009384CA88A0F858204FF00100087030BC6C +:10E79000704730B404460A46088E91F864104FF46E +:10E7A000747311F00C0F1CBF03EB801080B21ED0ED +:10E7B000918E814238BF0846118F92F865C01CF0D7 +:10E7C0000C0F1CBF03EB811189B218D0538F8B4201 +:10E7D00038BF194692F866301CF00C0F08BF0023B2 +:10E7E000002C0CBF0122002230BCF2F798BC022999 +:10E7F00007BF80003C30C000703080B2D8E7BCF169 +:10E80000020F07BF89003C31C900703189B2DDE7D2 +:10E810002DE9F041044606F099FCC8B9FE4F78682E +:10E8200090F8221001260025012914D00178012931 +:10E830001BD090F8281001291CBF0020BDE8F081F2 +:10E84000657018212170D0F82A10616080F8285076 +:10E850000120BDE8F081657007212170416A616087 +:10E8600080F822500120BDE8F081657014212170EC +:10E87000811C2022201DEFF7E0FD257279680D70C4 +:10E8800081F82850E54882888284C26B527B80F8E8 +:10E89000262080F82260C86B0088F5F738FCF5F771 +:10E8A000E0F8D5E7DC4840680178002914BF80888B +:10E8B0004FF6FF70704730B5D74C83B00D462078C7 +:10E8C0007F2808BFFFDF94F900307F202070D4F844 +:10E8D00004C09CF85000062808BF002205D09CF810 +:10E8E000500008280CBF022201229CF85400CDE9F8 +:10E8F000000302929CF873309CF880200CF13201E6 +:10E90000284606F08FFC03B0BDE8304006F01FBE7D +:10E910002DE9F04106F05FFC002818BF06F0E4FB8B +:10E92000BD4C606800F1840290F87610895C80F834 +:10E930008010002003F07EF828B3FAF753F86068DF +:10E94000B74990F855000D5C2846F9F7A3FD6068BB +:10E950004FF0000680F8735090F8801011F00C0F03 +:10E960000CBF25200F20F9F76CFC606890F8801030 +:10E970000120F9F711FE606890F84010032918BFD4 +:10E9800002290FD103E0BDE8F04101F02FB990F862 +:10E9900076108430085C012804D101221146002041 +:10E9A000FAF707F9FAF7D5F8606890F88050012D6A +:10E9B00007BF0127032100270521A068FFF799F869 +:10E9C000616881F8520040B1002F18BF402521D066 +:10E9D000F9F787F92846FAF79DF86068806DFAF72D +:10E9E0000DF8606890F85410FF291CBF6D30FEF7D9 +:10E9F000B4FFFF21606880F8531080F8541080F84D +:10EA0000626080F8616080F87D60062180F85010B7 +:10EA1000BDE8F08115F00C0F14BF55255025D7E740 +:10EA200070B57D4C0646606800F150052046806850 +:10EA300041B1D0F80510C5F81D10B0F80900A5F8CF +:10EA4000210003E005F11D01FFF7B9F9A068FFF708 +:10EA5000D4F985F82400A0680021032E018002D09B +:10EA6000052E04D03DE00321FFF77CF939E00521B4 +:10EA7000FFF778F96068C06B00F10E01A068FFF73E +:10EA800000FA6068C06B00F11201A068FFF7FDF9A1 +:10EA9000D4E90110CA6B527D8275CA6BD28AC275E5 +:10EAA000120A0276CA6B52884276120A8276CA6BC2 +:10EAB0009288C276120A0277CA6BD2884277120A0B +:10EAC0008277C96B0831FFF7FEF96068C06B017E81 +:10EAD000A068FFF7E0F9606890F88610A068FFF77B +:10EAE000E4F905F11D01A068FFF770F995F824100D +:10EAF000A068FFF786F9606800F1320590F8316090 +:10EB000090F8511091B190F84010032906D190F877 +:10EB10003910002918BF90F8560001D190F8530021 +:10EB2000FFF736F800281CBF012605462946A068D5 +:10EB3000FFF73EF93146A068BDE87040FFF754B9D1 +:10EB40003549496881F84B00704770B5324D002453 +:10EB50000126A8606968A1F8814081F8834081F8A6 +:10EB6000506091F85020022A1FBF91F850100129DF +:10EB7000FFDF70BD06F0CDFA6868047080F82240AF +:10EB800080F8284090F8520030B1F9F7CDFFF9F73E +:10EB9000BCF8686880F852406868072180F84A40ED +:10EBA00080F8396080F8404080F8554080F84B404C +:10EBB00080F87D4080F8381070BD2DE9F041164C8A +:10EBC000054686B0606890F85000012818BF0228FA +:10EBD00005D003281EBF0C2006B0BDE8F081687A7E +:10EBE000022839D0F9F76FFB0220F9F701FF0D4930 +:10EBF00001F10C0090E80D108DE80D10D1E907012E +:10EC0000CDE904016846F9F7E1FE606890F94B0030 +:10EC1000F9F732FCA06807E07401002044110020DD +:10EC20004363020040630200F9F7E5FEFC48F9F790 +:10EC3000B9FEFC48F9F726FC606890F831103230D4 +:10EC4000F9F7A5FB0F210720F9F7BFFB606890F8E3 +:10EC50003900E0B1FEF70FFF6168287A01F1840204 +:10EC600081F87600287A805C81F880006868886581 +:10EC70002A68CA65687A68B1012824D00525022867 +:10EC800008BF81F850506FD0032878D080E0FEF79D +:10EC9000A8FEE1E7E44B91F83850002291F85500C6 +:10ECA000401CA3FB006C4FEA5C0CACEB8C0C60448A +:10ECB00081F8550025FA00F010F0010F03D1501C27 +:10ECC000C2B2032AEAD3002681F87D6091F8490098 +:10ECD000002804BF91F85100002841D0F7F744FA0A +:10ECE000074660683946406CF7F736FFDFF83C832B +:10ECF000054690FBF8F008FB105041423846F6F705 +:10ED00001AFF6168486495FBF8F08A6F10448867C1 +:10ED1000FEF7EEFD01466068826F914220D847649D +:10ED2000866790F8510000281CBF0120FEF7FDFE09 +:10ED30000121606890F84A20002A1CBF90F8492001 +:10ED4000002A0DD090F8313000F13202012B04D1AD +:10ED5000527902F0C002402A08D03230FAF78CFC17 +:10ED60006168042081F8500012E008E00125FEF7F8 +:10ED70000DFF61682A463231FAF746FCF0E7002AB7 +:10ED800018BFFFDF012000F089FF606880F8505055 +:10ED900006B00020BDE8F08170B5A54D686890F818 +:10EDA000501004292ED005291CBF0C2070BD90F8EE +:10EDB0007D100026002990F883104FEA511124D0CD +:10EDC000002908BF012407D0012908BF022403D06D +:10EDD000022914BF00240824C06D00281CBF002095 +:10EDE00000F05CFF6868806DF9F708FE686890F8CD +:10EDF0004010022943D0032904BF90F86C10012968 +:10EE000041D04DE0FFF784FD52E0002908BF012406 +:10EE100007D0012908BF022403D0022914BF00240F +:10EE20000824C06D00281CBF002000F037FF686870 +:10EE3000806DF9F7E3FD686890F84010022906D06C +:10EE4000032904BF90F86C10012904D010E090F859 +:10EE50006C1002290CD1224614F00C0F04D090F84B +:10EE60004C00012808BF042201210020F9F7A1FE6F +:10EE70006868072180F8804080F8616016E090F8AB +:10EE80006C1002290CD1224614F00C0F04D090F81B +:10EE90004C00012808BF042201210020F9F789FE57 +:10EEA0006868082180F8804080F8616080F8501020 +:10EEB000002070BD5E49002210F0010F496802D0A9 +:10EEC000012281F8842010F0080F03D0114408209B +:10EED00081F88400002070475549496881F848004E +:10EEE000704710B5524C636893F83030022B14BF52 +:10EEF000032B00280BD100291ABF02290120002072 +:10EF00001146FEF7F8FC08281CBF012010BD606800 +:10EF100090F83000002816BF022800200120BDE82C +:10EF20001040FAF731BB4248406890F830000028A2 +:10EF300016BF022800200120FAF726BB3C49496889 +:10EF400081F8300070473A49496881F84A007047B3 +:10EF500070B5374C616891F83000002816BF022860 +:10EF60000020012081F8310001F13201FAF7F6FAB0 +:10EF7000606890F83010022916BF03290121002192 +:10EF800080F8511090F8312000F132034FF0000565 +:10EF9000012A04BF5B7913F0C00F0AD000F13203DD +:10EFA000012A04D15A7902F0C002402A01D000227D +:10EFB00000E0012280F84920002A04BF002970BD2A +:10EFC0008567F7F7D1F86168486491F85100002827 +:10EFD0001CBF0020FEF7A9FD0026606890F84A10CB +:10EFE00000291ABF90F84910002970BD90F831200F +:10EFF00000F13201012A04D1497901F0C001402910 +:10F0000005D02946BDE870403230FAF735BBFEF72F +:10F01000BDFD61683246BDE870403231FAF7F4BA9E +:10F020004063020046630200ABAAAAAA40420F0056 +:10F030007401002070B5FF4D0C4600280CBF012361 +:10F040000023696881F8393081F842004FF00800E8 +:10F0500081F856000CD1002C1ABF022C0120002090 +:10F060001146FEF748FC6968082881F8560001D06F +:10F07000002070BD022C14BF032C1220F8D170BDEB +:10F08000002818BF112070470328EA4A526808BFB9 +:10F09000D16382F840000020704710B5E54C6068ED +:10F0A00090F8401003291CBF002180F8601001D0A7 +:10F0B000002010BD0123C16B1A460020F2F738F87A +:10F0C0006168CA6B526A904294BF0120002081F8A7 +:10F0D0006000EDE7D748416891F84000032804D06C +:10F0E000012818BF022807D004E091F84200012847 +:10F0F00008BF70470020704791F84100012814BFF5 +:10F1000003280120F6D1704770B5F9F7F7FCF9F73D +:10F11000D6FCF9F79FFBF9F74BFCC64C002560685D +:10F1200090F8520030B1F9F7FFFCF8F7EEFD606897 +:10F1300080F8525060680121A0F8815080F8835017 +:10F1400080F8501080F82850002070BDB94810B5E4 +:10F150004068643006F0B1FB002010BDB5480121C5 +:10F16000406890F84020032A03BF80F82A10C26B41 +:10F170001288002218BF80F82A20828580F8281083 +:10F180007047AC49496881F88600704701780023D0 +:10F1900011F0010FA749496809D04278032A08BF36 +:10F1A000CB6381F84020012281F884201346027845 +:10F1B00012F0040F0CD082784FF0000C032A08BF25 +:10F1C000C1F83CC081F840200B44082283F8842019 +:10F1D000C27881F830200279002A16BF022A012362 +:10F1E000002381F8393081F84120427981F83820B4 +:10F1F000807981F848004FF0000070478D484068E2 +:10F200008030704770B58B4C06460D46606890F8AC +:10F210005000032818BFFFDF022E1EBF032EFFDFA2 +:10F2200070BD002D18BF06F0A1F900216068A0F89C +:10F23000811080F88310012180F8501070BD00F01B +:10F24000D5BC2DE9F0477B4C0646894660684FF0F7 +:10F250000108072E90F8397038BF032540D3082ED7 +:10F2600084BF0020BDE8F08790F85010062908BF41 +:10F27000002105D090F8501008290CBF022101216F +:10F2800090F8800005F0AEFF002873D1A068C17827 +:10F2900011F03F0F12D0027912F0010F0ED0616809 +:10F2A0004FF0050591F85220002A18BFB9F1000F60 +:10F2B00016D091F88010012909D011E011F03F0F0C +:10F2C0001ABF007910F0100F002F53D14CE04FF00F +:10F2D00001024FF00501FEF74CFB616881F8520016 +:10F2E000A16808782944C0F3801030B1487900F053 +:10F2F000C000402808BF012000D00020616891F8BC +:10F300005210002918BF002807D0FEF74DFB014618 +:10F31000606880F8531080F86180606890F853103E +:10F32000FF292AD080F854100846FEF74AFB40EA2D +:10F330000705606890F85320FF2A18BF002D10D0F1 +:10F34000072E0ED3A068C17811F03F0F09D00179C4 +:10F3500011F0020F05D00B21FEF7BDFB606880F8AD +:10F3600062802846BDE8F087FEF75FF9002808BFF5 +:10F37000BDE8F0870120BDE8F087A36890F8392048 +:10F3800059191B78C3F3801C00F153036046FEF744 +:10F3900096F90546CDE72DE9F043264C87B0A068E5 +:10F3A000FEF7E0FE7F264FF00108002558B1022746 +:10F3B00001287DD0022800F0EF80F9F74BFA07B062 +:10F3C0000620BDE8F083F9F745FA616891F840003E +:10F3D000032800F01581A068C27812F03F0F05D015 +:10F3E000037913F0100F18BF012700D10027002F59 +:10F3F00014BF0823012312F03F0F00F001810079B0 +:10F4000033EA000240F0FC8010F0020F08D091F8BF +:10F410008000002105F064FE002808BF012000D014 +:10F4200000208DF80C508DF810508DF814504FF0CE +:10F43000FF0801E074010020D0B105AA03A904A8C7 +:10F4400000F07AFC606890F831809DF80C0000288C +:10F4500018BF48F002080BD1A068FEF7DBFC81461C +:10F460000121A068FEF732FD4946F8F79AFF28B35C +:10F47000FFB1012000F0DDFB002852D020787F286A +:10F4800008BFFFDF94F900102670606890F85420E0 +:10F49000CDE90021029590F8733090F8802000F1BA +:10F4A0003201404605F0BEFE606880F86C50A3E073 +:10F4B00038E041460020FFF7FEF9A1E0606890F8CF +:10F4C0004100032818BF02282BD19DF81000002806 +:10F4D00027D09DF80C00002823D1F7B1012000F0BF +:10F4E000A8FB00281DD020787F2808BFFFDF94F9F3 +:10F4F00000102670606890F85420CDE90021029534 +:10F5000090F8733090F8802000F13201FE2005F071 +:10F5100089FE606880F86C506EE0FE210020FFF7E5 +:10F52000CAF96DE0F9F796F9A0681821C27812F0CF +:10F530003F0F65D00279914362D10421FEF7C6FCEA +:10F54000616891F84020032A01BF8078B7EB501F13 +:10F5500091F86000002853D04FF0010000F069FBE3 +:10F56000E8B320787F2808BFFFDF94F900102670E9 +:10F57000606890F85420CDE90021029590F873302E +:10F5800090F8802000F13201FF2005F04BFE60680A +:10F5900080F86C8030E000BFF9F75CF9606890F8A3 +:10F5A000400003282CD0A0681821C27812F03F0F29 +:10F5B00026D0007931EA000022D1012000F039FB89 +:10F5C00068B120787F2808BFFFDF94F9001026700B +:10F5D000606890F85420CDE90021029500E00FE02A +:10F5E00090F8733090F8802000F13201FF2005F090 +:10F5F00019FE606880F86C7007B00320BDE8F083E6 +:10F6000007B00620BDE8F083F0B5FE4C074683B096 +:10F6100060686D460078002818BFFFDF002661682B +:10F620008E70C86B02888A8042884A8382888A8367 +:10F63000C088C88381F8206047B10121A068FEF727 +:10F6400045FC0546A0680078C10907E06946A06846 +:10F65000FEF7B5FBA0680078C0F380116068012751 +:10F6600090F85120002A18BF002904D06A7902F0CE +:10F67000C002402A26D090F84A20002A18BF00294C +:10F6800003D0697911F0C00F1CD000F10E00E3F730 +:10F69000B3FC616891F85400FF2819D001F1080209 +:10F6A000C91DFEF743F9002808BFFFDF6068C17974 +:10F6B00041F00201C171D0F86D104161B0F87110D4 +:10F6C00001830FE02968C0F80E10A9884182E0E7A5 +:10F6D000C86B427ECA71D0F81A208A60C08B8881BC +:10F6E0004E610E8360680770C26B90F84B1082F811 +:10F6F0006710C06B0088F4F70AFDF4F7A3F903B0B4 +:10F70000F0BD2DE9F041BF4C0546002760684FF081 +:10F7100001083E4690F84000012818BF022802D098 +:10F72000032818BFFFDF5DB1A068FEF727FC18B9FA +:10F73000A068FEF77AFC18B100F08FFB074645E0A1 +:10F74000606890F850007F25801F06283ED2DFE8D1 +:10F7500000F003191924352FAA48F9F709FA0028EF +:10F7600008BF2570F9F7EBF9606890F8520030B1E6 +:10F77000F9F7DAF9F8F7C9FA606880F85260F9F732 +:10F7800069F830E09F48F9F7F3F9002808BF2570C1 +:10F79000F9F7D5F905F0EAFEC3E09A48F9F7E8F978 +:10F7A000002808BF2570F9F7CAF9F9F753F81AE0ED +:10F7B0009448F9F7DDF930B9257004E09148F9F77C +:10F7C000D7F90028F8D0F9F7BAF9AAE0102F80F09D +:10F7D0003881DFE807F01E9DA6AAF1F108B3F2F127 +:10F7E000F1F10C832051BDE8F041FFF791B80320FF +:10F7F00002F020F9002870D000210320FFF710F953 +:10F80000012211461046F9F7D4F961680C2081F8FD +:10F810005000BDE8F081606800F15005042002F05E +:10F8200009F900287DD00E202870012002F0FDFC8F +:10F83000A06861680078C0F3401081F8750000216D +:10F840000520FFF7EDF87048A1684FF0200CC26B5F +:10F850000B78527B23F020030CEA42121A430A7001 +:10F86000C16B95F825304A7B1A404A73C06B28213A +:10F8700080F86610BDE8F081062002F0DBF8002871 +:10F880004FD0614D0F2085F85000022002F0CDFCD2 +:10F890006068012190F880200846F9F78AF9A0688D +:10F8A00061680078C0F3401081F8750001210520DF +:10F8B000FFF7B6F8E86B80F80D80A068017821F0BA +:10F8C00020010170F9F75DFD002818BFFFDF282037 +:10F8D000E96B81F86600BDE8F08122E0052002F0C6 +:10F8E000A9F8F0B101210320FFF79AF8F9F749FDD3 +:10F8F000002818BFFFDF6068012190F880200846CB +:10F90000F9F757F961680D2081F85000BDE8F081E2 +:10F910006068A0F8816080F8836080F85080BDE85E +:10F92000F081BDE8F04100F061B96168032081F821 +:10F930005000BDE8F041082002F077BC606890F804 +:10F940008310490908BF012507D0012908BF0225F6 +:10F9500003D0022914BF00250825C06D00281CBF54 +:10F96000002000F09BF96068806DF9F747F8606847 +:10F9700090F84010022906D0032904BF90F86C10BB +:10F98000012904D010E090F86C1002290CD12A460D +:10F9900015F00C0F04D090F84C00012808BF042289 +:10F9A00001210020F9F705F96068072180F88050EF +:10F9B00080F8616041E000E043E0606890F8831007 +:10F9C000490908BF012507D0012908BF022503D036 +:10F9D000022914BF00250825C06D00281CBF002087 +:10F9E00000F05CF96068806DF9F708F8606890F8DD +:10F9F000401002290AD0032904BF90F86C10012995 +:10FA000008D014E0740100204411002090F86C101C +:10FA100002290CD12A4615F00C0F04D090F84C00A6 +:10FA2000012808BF042201210020F9F7C2F860680C +:10FA3000082180F8805080F8616080F85010BDE89F +:10FA4000F081FFDFBDE8F08170B5FE4C606890F892 +:10FA5000503000210C2B38D001220D2B40D00E2B22 +:10FA600055D00F2B1CBFFFDF70BD042002F0DDFB63 +:10FA7000606890F880100E20F8F7E3FB606890F85B +:10FA8000800010F00C0F14BF282100219620F8F7F9 +:10FA9000D3FFF9F75EF86068052190F88050A06800 +:10FAA000FEF727F8616881F8520048B115F00C0F95 +:10FAB0000CBF50255525F8F714F92846F9F72AF810 +:10FAC00061680B2081F8500070BDF9F742F8002101 +:10FAD0009620F8F7B1FF6168092081F8500070BDE9 +:10FAE00090F88010FF20F8F7ACFB606890F8800079 +:10FAF00010F00C0F14BF282100219620F8F79CFF6E +:10FB0000F9F727F861680A2081F8500070BDA0F865 +:10FB1000811080F8831080F850200020FFF774FDDA +:10FB2000BDE87040032002F080BB70B5C54C606832 +:10FB300090F850007F25801F062828BF70BDDFE8A1 +:10FB400000F0171F1D032A11BE48F9F711F800280D +:10FB500008BF2570F8F7F3FFF8F77CFEBDE87040AA +:10FB6000FEF7D6BEB748F9F703F8C8B9257017E015 +:10FB7000B448F8F7FDFF40B9257006E005F0F6FC43 +:10FB8000B048F8F7F5FF0028F6D0F8F7D8FFBDE841 +:10FB9000704000F02BB8AB48F8F7EAFF0028E5D03A +:10FBA000F8F7CDFF60680021643005F037FEBDE84E +:10FBB000704000F01BB870B5A24C06460D460129F6 +:10FBC00008D0606890F880203046BDE87040134649 +:10FBD00002F077BBF8F75CFA61680346304691F8AB +:10FBE00080202946BDE8704002F06BBB70B5F8F785 +:10FBF00085FFF8F764FFF8F72DFEF8F7D9FE914C72 +:10FC00000025606890F8520030B1F8F78DFFF8F7E2 +:10FC10007CF8606880F852506068022180F85010CB +:10FC2000A0F8815080F88350BDE87040002002F0B9 +:10FC3000FCBA70B5834D06460421A868FEF746F964 +:10FC4000044605F0C8FA002808BF70BD207800F00F +:10FC50003F00252814D2F8F761FA217811F0800FBF +:10FC60000CBF1E214FF49671B4F80120C2F30C02B0 +:10FC700012FB01F10A1AB2F5877F28BF814201D237 +:10FC8000002070BD68682188A0F88110A17880F8F4 +:10FC900083103046BDE8704001F0CCBE2DE9F04144 +:10FCA000684C0746606800F1810690F883004009BF +:10FCB00008BF012507D0012808BF022503D002286C +:10FCC00014BF00250825F8F78DFE307800F03F06B8 +:10FCD0003046F8F7DFFB606880F8736090F86C00DE +:10FCE00002280CBF4020FF202946F8F7AAFA27B1C6 +:10FCF00029460120F8F795FC05E060682A46C16DA9 +:10FD00000120F8F7E2FCF8F724FF0521A068FDF7D1 +:10FD1000F0FE6168002881F8520008BFBDE8F0815C +:10FD200015F00C0F0CBF50245524F7F7DAFF2046CE +:10FD3000BDE8F041F8F7EEBE2DE9F74F414C002544 +:10FD4000914660688A4690F8510000280CBF4FF039 +:10FD500001084FF00008A0680178CE090121FEF7E4 +:10FD6000B5F836B1407900F0C000402808BF012640 +:10FD700000D00026606890F85210002961D090F8F9 +:10FD800040104FF0000B032906D190F839100029DC +:10FD900018BF90F856700ED1A068C17811F03F0FCF +:10FDA0001CBF007910F0010F02D105F061F940B3DA +:10FDB000606890F85370FF2F18BF082F21D0384685 +:10FDC000FDF7D9FB002818BF4FF00108002E38D0EE +:10FDD000606890F8620030B1FDF7F1FD054660689B +:10FDE00080F862B02DE03846FDF791FD054601210F +:10FDF000A068FEF76BF801462846F9F7D3FB0546E5 +:10FE00001FE0F6B1606890F86100D0B9A068C178D1 +:10FE100011F03F0F05D0017911F0010F18BF0B2130 +:10FE200000D105210022FDF7A4FD616881F8520090 +:10FE300038B1FDF7B9FDFF2803D06168012581F8CD +:10FE4000530001E0740100208AF800500098067009 +:10FE500089F8008003B0BDE8F08F2DE9F04FFF4C2A +:10FE600087B00025606890F850002E46801F4FF044 +:10FE70007F08062880F0D581DFE800F00308088BB2 +:10FE8000FDDB00F0F8FB054600F0CCB9F348F8F7CD +:10FE90006FFE002808BF84F80080F8F750FEA068C5 +:10FEA000FDF782FF0546072861D1A068FEF75AF9E1 +:10FEB0000146606890F86C208A4258D190F8501042 +:10FEC000062908BF002005D090F8500008280CBF74 +:10FED0000220012005F08AF970B90321A068FDF71E +:10FEE000F5FF002843D001884078C1F30B010009D9 +:10FEF00005F07BFC00283AD000212846FFF7A1F945 +:10FF0000A0B38DF80C608DF808608DF8046062680D +:10FF1000FF2592F8500008280CBF02210121A0689B +:10FF2000C37813F03F0F1CBF007910F0020F12D0FE +:10FF300092F8800005F0D4F868B901AA03A902A8D4 +:10FF4000FFF7FAFE606890F831509DF80C00002829 +:10FF500018BF45F002052B469DF804209DF80810B7 +:10FF60009DF80C0000F0D5F9054603E0FFE705F029 +:10FF7000FDFA0225606890F85200002800F05281D6 +:10FF8000F8F7D2FDF7F7C1FE606880F8526000F024 +:10FF900049B9A068FDF708FF0646A1686068CA78FD +:10FFA00090F86D309A4221D10A7990F86E309A42D9 +:10FFB0001CD14A7990F86F309A4217D18A7990F81B +:10FFC00070309A4212D1CA7990F871309A420DD1AC +:10FFD0000A7A90F872309A4208D1097890F8740041 +:10FFE000C1F38011814208BF012500D00025F8F738 +:10FFF00031FC9A48F8F7BCFD002808BF84F800805F +:020000040002F8 +:10000000F8F79DFD042E11D185B120787F2808BF17 +:10001000FFDF94F9003084F80080606890F8732066 +:1000200090F87D1090F8540005F06EFB062500F066 +:10003000F9B802278948F8F79BFD002808BF84F823 +:100040000080F8F77CFDA068FDF7AEFE0546A068CD +:10005000FEF788F8082D08BF00287CD1A0684FF073 +:100060000301C27812F03F0F75D0007931EA000029 +:1000700071D1606800E095E000F1500890F8390017 +:10008000002814BF98F8066098F803604FF0000944 +:1000900098F8020078B1FDF787FC0546FF280AD0E2 +:1000A0000146A068401DFDF758FCB5420CBF4FF05B +:1000B00001094FF000090021A068FDF707FF0622A3 +:1000C00008F11D01EEF78CF940B9A068FDF795FE27 +:1000D00098F82410884208BF012000D0002059EA77 +:1000E00000095DD0606800F1320590F831A098F801 +:1000F000010038B13046FDF74BFD00281CBF054616 +:100100004FF0010A4FF00008A06801784FEAD11BB8 +:100110000121FDF7DBFEBBF1000F07D0407900F0B5 +:10012000C000402808BF4FF0010B01D04FF0000B7A +:100130000121A068FDF7CAFE06222946EEF750F914 +:1001400030B9A068FDF766FE504508BF012501D013 +:100150004FF0000500E023E03BEA050018BFFF2E4A +:100160000DD03046FDF7D3FB060008D00121A06872 +:10017000FDF7ACFE01463046F9F714FA804645EA31 +:10018000080019EA000F0BD060680121643005F007 +:1001900045FB01273846FFF737FA052002F045F8FE +:1001A0003D463FE002252D48F8F7E2FC002808BF55 +:1001B00084F80080F8F7C3FCA068FDF7F5FD06465B +:1001C000A068FDF7CFFF072E08BF00282AD1A0683E +:1001D0004FF00101C27812F03F0F23D00279914312 +:1001E00020D1616801F150060021FDF76FFE062263 +:1001F00006F11D01EEF7F4F8A0B9A068FDF7FDFDCA +:1002000096F8241088420DD160680121643005F011 +:1002100005FBFF21022000F009F8002818BF032584 +:1002200000E0FFDF07B02846BDE8F08F2DE9F0437E +:100230000A4C0F4601466068002683B090F87D2086 +:10024000002A35D090F8500008280CBF022501255F +:10025000A168C87810F03F0F02E000007401002090 +:10026000FD484FF000084FF07F0990F900001CBFD7 +:10027000097911F0100F22D07F2808BFFFDF94F911 +:10028000001084F80090606890F85420CDE90021B7 +:10029000029590F8733090F8802000F132013846D2 +:1002A00004F0C0FF05F0AAFA10B305F050F92CE0F5 +:1002B000002914BF0221012180F87D10C2E77F28A8 +:1002C00008BFFFDF94F9001084F80090606890F890 +:1002D0005420CDE90021029590F8733090F88020E9 +:1002E00000F13201384604F09DFF05F030F90CE0D2 +:1002F0000220FFF79EFC30B16068012680F86C8018 +:10030000F8F7A8FA01E005F031F903B03046BDE88E +:10031000F0832DE9F047D04C054684B09A46174645 +:100320000E46A068FDF71EFF4FF00109002800F0FF +:10033000CF804FF00208012808D0022800F00E817B +:1003400005F014F904B04046BDE8F087A068092123 +:10035000C27812F03F0F00F059810279914340F0CA +:100360005581616891F84010032906D012F0020F00 +:1003700008BFFF2118D05DB115E00021FDF7A6FDF3 +:1003800061680622C96B1A31EEF72AF848BB1EE0F5 +:10039000FDF740FD05460121A068FDF797FD2946C0 +:1003A000F7F7FFFF18B15146012000F051B960681E +:1003B00090F84100032818BF022840F02781002E42 +:1003C0001CBFFE21012040F0438100F01FB9A0684E +:1003D000FDF713FD6168C96B497E884208BF01269D +:1003E00000D00026A068C17811F03F0F05D0017938 +:1003F00011F0020F01D06DB338E0616891F842202E +:10040000012A01D096B11BE0D6B90021FDF75EFDAF +:1004100061680268C96BC1F81A208088C883A06827 +:10042000FDF7EBFC6168C96B487609E091F8530071 +:1004300091F85610884203D004B04046BDE8F087DA +:100440006068643005F02EFA002840D004B00F2018 +:10045000BDE8F08767B1FDF7DDFC05460121A06826 +:10046000FDF734FD2946F7F79CFF08B1012200E0B3 +:100470000022616891F84200012807D040B92EB9E6 +:1004800091F8533091F856108B4201D1012100E0D0 +:1004900000210A421BD0012808BF002E11D14FF0C5 +:1004A0000001A068FDF712FD61680268C96BC1F820 +:1004B0001A208088C883A068FDF79FFC6168C96B1B +:1004C00048766068643005F0EDF90028BED19DE003 +:1004D00060682F46554690F840104FF002080329F7 +:1004E000AAD0A168CA7812F03F0F1BBF097911F09A +:1004F000020F002201224FF0FF0A90F85010082945 +:100500000CBF0221012192B190F8800004F0E8FDB7 +:1005100068B95FB9A068FDF77DFC07460121A068B6 +:10052000FDF7D4FC3946F7F73CFF48B1AA465146DF +:100530000020FFF77BFE002818BF4FF003087BE781 +:10054000606890F84100032818BF02287FF474AF58 +:10055000002E18BF4FF0FE0AE9D16DE7616891F8EF +:100560004030032B52D0A0684FF0090CC27812F033 +:100570003F0F4BD002793CEA020C47D1022B06D048 +:1005800012F0020F08BFFF2161D0E5B35EE012F068 +:10059000020F4FF07F0801D04DB114E001F164006B +:1005A00005F080F980B320787F2842D013E067B34C +:1005B000FDF730FC05460121A068FDF787FC2946C0 +:1005C000F7F7EFFE08B36068643005F06BF9D8B157 +:1005D00020787F282DD094F9001084F8008060687E +:1005E00090F85420CDE90021CDF8089090F87330B0 +:1005F00090F8802000F13201504604F013FE0D20E7 +:1006000004B0BDE8F08716E000E001E00220F7E763 +:10061000606890F84100032818BF0228F6D1002E28 +:10062000F4D04FF0FE014FF00200FEF744F9022033 +:10063000E6E7FFDFCFE7FDF7EDFB05460121A06808 +:10064000FDF744FC2946F7F7ACFE38B151460220CD +:10065000FEF731F9DAE7000074010020606890F8D5 +:100660004100032818BF0228D0D1002E1CBFFE2154 +:100670000220EDD1CAE72DE9F84F4FF00008F74806 +:10068000F8F776FA7F27F54C002808BF2770F8F7AF +:1006900056FAA068FDF788FB81460121FEF7D1FDDF +:1006A000616891F88020012A14D0042A1CBF082A0E +:1006B000FFDF00F0D781606890F8520038B1F8F79A +:1006C00033FAF7F722FB6168002081F852004046B8 +:1006D000BDE8F88F0125E24EB9F1080F3AD2DFE804 +:1006E00009F03EC00439393914FC0546F8F7B2F870 +:1006F000002D72D0606890F84000012818BF0228D1 +:100700006BD120787F2869D122E018B391F840009E +:10071000022802D0012818D01CE020787F2808BFCA +:10072000FFDF94F90000277000906068FF2190F8C7 +:10073000733090F85420323004F02FFF61680020AD +:100740004FF00C0881F87D00B5E720787F2860D154 +:10075000FFDF5EE0F8F77EF84FF00608ABE74FF0FA +:100760000008002800F0508191F84000022836D09F +:1007700001284BD003289ED1A068CA6BC37892F899 +:100780001AC0634521D1037992F81BC063451CD17F +:10079000437992F81CC0634517D1837992F81DC044 +:1007A000634512D1C37992F81EC063450DD1037A17 +:1007B00092F81FC0634508D1037892F819C0C3F3BB +:1007C0008013634508BF012300D0002391F8421035 +:1007D00001292CD0C3B300F013B93FE019E0207811 +:1007E0007F2808BFFFDF94F9000027700090606841 +:1007F000FF2190F8733090F85420323004F0CDFE91 +:1008000060684FF00C0880F87D5054E720787F280E +:100810009ED094F90000277000906068FF2190F846 +:10082000733090F85420323004F0B7FE16E0002BFD +:100830007ED102F11A01FDF7C2FAA068FDF7DDFAD8 +:100840006168C96B4876DBE0FFE796F85600082838 +:1008500070D096F8531081426AD0D5E04FF0060868 +:1008600029E7054691F8510000280CBF4FF0010B15 +:100870004FF0000B4FF00008A06810F8092BD209C8 +:1008800007D0407900F0C000402808BF4FF0010AAF +:1008900001D04FF0000A91F84000032806D191F8EA +:1008A0003900002818BF91F8569001D191F8539063 +:1008B0004846FDF72CF80090D8B34846FCF75BFE9D +:1008C000002818BF4FF0010BBAF1000F37D0A06815 +:1008D000A14600F10901009800E0B6E0F8F762FED9 +:1008E0005FEA0008D9F8040090F8319018BF49F089 +:1008F0000209606890F84010032924D0F7F7AAFF96 +:10090000002DABD0F7F75DFD002808BFB8F1000F50 +:100910007DD020787F2808BFFFDF94F90000277082 +:1009200000906068494690F8733090F8542002E0D7 +:1009300066E004E068E0323004F02FFE8EE7606885 +:1009400090F83190D5E7A168C06BCA78837E9A424F +:100950001BD10A79C37E9A4217D14A79037F9A4202 +:1009600013D18A79437F9A420FD1CA79837F9A4201 +:100970000BD10A7AC37F9A4207D10978407EC1F32E +:100980008011814208BF012700D0002796F853004C +:10099000082806D096F85610884208BF4FF0010983 +:1009A00001D04FF00009B8F1000F05D1BBF1000FE5 +:1009B00004D0F7F706FD08B1012000E000204DB19A +:1009C00096F84210012903D021B957EA090101D054 +:1009D000012100E00021084216D0606890F8421022 +:1009E000012908BF002F0BD1C06B00F11A01A068CC +:1009F000FDF7E5F9A068FDF700FA6168C96B487674 +:100A00004FF00E0857E602E0F7F724FF26E760688C +:100A100090F84100032818BF02287FF41FAFBAF1F5 +:100A2000000F3FF41BAF20787F2808BFFFDF94F949 +:100A30000000277000906068FE2190F8733090F8F5 +:100A40005420323004F0A9FD08E791F8481000293D +:100A500018BF00283FF47EAE0BE0000074010020B8 +:100A600044110020B9F1070F7FF474AE00283FF461 +:100A700071AEFEF790FC80461DE60000D0F8001134 +:100A800049B1D0E941231A448B691A448A61D0E9FB +:100A90003F12D16003E0FE4AD0F8FC101162D0E9A9 +:100AA0003F1009B1086170470028FCD00021816126 +:100AB00070472DE9FF4F06460C46488883B040F248 +:100AC000E24148430190E08A002500FB01FA94F8D6 +:100AD0007C0090460D2822D00C2820D024281ED03F +:100AE00094F87D0024281AD000208346069818B177 +:100AF0000121204603F0C0F894F8641094F86500D2 +:100B0000009094F8F0200F464FF47A794AB1012A08 +:100B100061D0022A44D0032A5DD0FFDFB5E0012076 +:100B2000E3E7B8F1000F00D1FFDFD94814F8641FE4 +:100B3000243090F83400F0F7C4FC01902078F8F7E6 +:100B4000CFFB4D4600F2E730B0FBF5F1DFF8409304 +:100B5000D9F80C0001EB00082078F8F7C1FB01463A +:100B600014F86409022816D0012816D040F6340083 +:100B700008444AF2EF010844B0FBF5F10198D9F8B6 +:100B80001C20411A514402EB08000D18012084F882 +:100B9000F0002D1D78E02846EAE74FF4C860E7E74B +:100BA000DFF8EC92A8F10100D9F80810014300D158 +:100BB000FFDFB848B8F1000F016801EB0A0506D065 +:100BC000D9F8080000F22630A84200D9FFDF032040 +:100BD00084F8F00058E094F87C20019D242A05D088 +:100BE00094F87D30242B01D0252A3AD1B4F8702016 +:100BF000B4F81031D21A521C12B2002A31DB94F828 +:100C0000122172B3174694F8132102B110460090D6 +:100C1000022916D0012916D040F6340049F60852B0 +:100C20008118022F12D0012F12D040F63400104448 +:100C3000814210D9081A00F5FA70B0FBF9F00544AA +:100C40000FE04846EAE74FF4C860E7E74846EEE7BA +:100C50004FF4C860EBE7401A00F5FA70B0FBF9F00A +:100C60002D1AB8F1000F0FD0DFF82482D8F8080051 +:100C700018B9B8F8020000B1FFDFD8F8080000F298 +:100C80002630A84200D9FFDF05B9FFDF2946D4F896 +:100C9000F400F4F750FFC4F8F400B06000203070A6 +:100CA0004FF0010886F80480204603F040F8ABF1CD +:100CB0000101084202D186F8058005E094F8F000B1 +:100CC000012844D003207071606A3946009A01F00F +:100CD00042FBF060069830EA0B0035D029463046DA +:100CE000F0F7BAF987B2204603F021F8B8420FD8DE +:100CF000074686F8058005FB07F1D4F8F400F4F701 +:100D00001AFFB06029463046F0F7A6F9384487B29A +:100D10003946204602F0B0FFB068C4F8F400A06E77 +:100D2000002811D0B4F87000B4F89420801A01B2F1 +:100D3000002909DD34F86C0F0144491E91FBF0F1E4 +:100D400089B201FB0020208507B0BDE8F08F0220AA +:100D5000B9E72DE9F04106460C46012001F0DBFA27 +:100D6000C5B20B2001F0D7FAC0B2854200D0FFDF38 +:100D70000025082C7ED2DFE804F00461696965C6AD +:100D80008293304601F0DDFA0621F3F78FF8040074 +:100D900000D1FFDF304601F0D4FA2188884200D02C +:100DA000FFDF94F8F00000B9FFDF204602F00FFEED +:100DB000374E21460020B5607580F561FDF7E9FCEE +:100DC00000F19807606AB84217D994F86500F7F700 +:100DD0000BF9014694F864004FF47A72022828D087 +:100DE000012828D040F6340008444AF2473108442C +:100DF000B0FBF2F1606A0844C51B21460020356152 +:100E0000FDF7C7FC618840F2E24251439830081A6E +:100E1000A0F22630706194F8652094F86410606A3E +:100E200001F099FAA0F5CB70B061BDE8F041F5F79B +:100E300060BE1046D8E74FF4C860D5E7BDE8F04182 +:100E400002F02FBEBDE8F041F7F7D5BE304601F005 +:100E500078FA0621F3F72AF8040000D1FFDF3046C4 +:100E600001F06FFA2188884200D0FFDF01220021C3 +:100E7000204600E047E0BDE8F04101F089BAF7F70D +:100E800073FDF7F7B8FE02204FF0E02104E0000008 +:100E9000CC11002084010020C1F88002BDE8F0815F +:100EA000304601F04EFA0621F3F700F8040000D1B5 +:100EB000FFDF304601F045FA2188884200D0FFDF8D +:100EC00094F8F000042800D0FFDF84F8F05094F884 +:100ED000FA504FF6FF76202D00D3FFDFFA4820F8B6 +:100EE000156094F8FA00F5F720F900B9FFDF20202B +:100EF00084F8FA002046FFF7C1FDF4480078BDE809 +:100F0000F041E2F701B8FFDFC8E770B5EE4C00250D +:100F1000443C84F82850E07868B1E570FEF71EF98B +:100F20002078042803D0606AFFF7A8FD6562E748CF +:100F30000078E1F7E9FFBDE8704001F03ABA70B51A +:100F4000E14C0146443CE069F5F706FE6568A2788D +:100F500090FBF5F172B140F27122B5FBF2F292B260 +:100F6000A36B01FB02F6B34202D901FB123200E08F +:100F70000022A2634D43002800DAFFDF2946E06922 +:100F8000F4F7D9FDE06170BD2DE9F05FFEF736F9A9 +:100F90008246CD48683800F1240881684646D8F872 +:100FA0001800F4F7C8FD0146F069F5F7D5FD4FF0DC +:100FB0000009074686F835903C4640F28F254E469C +:100FC0001EE000BF0AEB06000079F7F70DF80146B6 +:100FD0004AF2B12001444FF47A70B1FBF0F008EB13 +:100FE0008602414692681044844207D3241A91F83D +:100FF0003500A4F28F24401C88F83500761CF6B228 +:1010000098F83600B042DDD8002C10DD98F8351085 +:10101000404608EB81018968A14208D24168C91B9A +:10102000B1F5247F00D30D466C4288F8359098F8CE +:101030003560C3460AEB060898F80400F6F7D4FFBB +:101040004AF2B12101444FF47A7AB1FBFAF298F8EE +:101050000410082909D0042909D0002013180429F4 +:101060000AD0082908D0252207E0082000E0022045 +:1010700000EB40002830F1E70F22521D4FF4A8701A +:10108000082914D0042915D0022916D04FF0080CD5 +:101090005FF0280012FB0C00184462190BEB86036A +:1010A00010449A68D84690420BD8791925E04FF041 +:1010B000400CEFE74FF0100CECE74FF0040C182059 +:1010C000E8E798F8352098F836604046B24210D2EA +:1010D000521C88F835203C1B986862198418084611 +:1010E000F6F782FF4AF2B1210144B1FBFAF001198F +:1010F00003E080F83590D8F80410D8F81C00BDE85B +:10110000F05FF4F718BD2DE9FE4F14460546FEF7D3 +:1011100075F8DFF8B4A10290AAF1440A50469AF893 +:1011200035604FF0000B0AEB86018968CAF83C1065 +:10113000F4B3044600780027042825D005283ED0C3 +:10114000FFDFA04639466069F4F7F5FC0746F5F77E +:101150000BF881463946D8F80440F5F7FDFC401EEF +:1011600090FBF4F0C14361433846F4F7E4FC0146D8 +:10117000C8F81C004846F5F7EFFC002800DDFFDF4B +:10118000012188F813108DE0D4F81490D4F804806D +:1011900001F07AF9070010D0387800B9FFDF7969DB +:1011A00078684A460844414601F05AF9074600E08B +:1011B0000BE04045C5D9FFDFC3E75F46C1E7606A82 +:1011C00001F004F940F6B837BBE7C1690AEB460005 +:1011D0000191408D10B35446DAF81400FFF7AFFECA +:1011E0006168E069F4F7A7FC074684F835B0019C14 +:1011F000D0462046DAF81410F5F7AEFC81463946A1 +:101200002046F5F7A9FCD8F804200146B9FBF2F016 +:10121000B1FBF2F1884242D0012041E0F4F7A4FF93 +:10122000FFF78DFEFFF7B0FE9AF83510DAF804905C +:101230000AEB81010746896800913946DAF81C00FB +:10124000F5F78AFC00248046484504DB98FBF9F456 +:1012500004FB09F41AE0002052469AF8351007E022 +:1012600002EB800304F28F249B68401C1C44C0B234 +:101270008142F5D851B10120F6F7B6FE4AF2B1210C +:1012800001444FF47A70B1FBF0F004440099A8EBEC +:1012900004000C1A00D5FFDFCAF83C40A7E7002085 +:1012A00088F813009AF802005446B8B13946E0694C +:1012B000F5F752FC0146A26B40F2712042438A428C +:1012C00006D2C4F83CB009E03412002080010020AE +:1012D000E06B511A884200D30846E063AF6085F89E +:1012E00000B001202871029F94F835003F1DC05DB9 +:1012F000F6F77AFE4AF23B5101444FF47A70B1FBA3 +:10130000F0F0E16BFE300844E8602078042808D152 +:1013100094F8350004EB4000408D0A2801D20320E8 +:1013200000E00220687104EB4600408DC0B1284601 +:101330006168EFF791FE82B20020761C0CE000BFDE +:1013400004EB4001B0424B8D13449BB24B8501D35B +:101350005B1C4B85401CC0B294F836108142EFD222 +:10136000A8686061A06194F8350004EB4001488DE5 +:10137000401C488594F83500C05D082803D0042837 +:1013800003D000210BE0082100E0022101EB410124 +:1013900028314FF4A872082804D0042802D002286B +:1013A00007D028220A44042805D0082803D0252184 +:1013B00002E01822F6E70F21491D08280CD0042866 +:1013C0000CD002280CD0082011FB0020E16B8842D1 +:1013D00008D20120BDE8FE8F4020F5E71020F3E79A +:1013E0000420F1E70020F5E770B5FE4C061D14F867 +:1013F000352F905DF6F7F8FD4FF47A7100F2E73083 +:10140000B0FBF1F0D4F8071045182078805DF6F7AE +:1014100073FE2178895D082903D0042903D00022B6 +:101420000BE0082200E0022202EB420228324FF4D5 +:10143000A873082904D0042902D0022907D0282340 +:101440001344042905D0082903D0252202E01823DB +:10145000F6E70F22521D08290AD004290AD00229D2 +:101460000AD0082112FB0131081A281A293070BD50 +:101470004021F7E71021F5E70421F3E72DE9FF41CB +:1014800007460C46012000F046FFC5B20B2000F0D5 +:1014900042FFC0B2854200D0FFDF20460126002572 +:1014A000D04C082869D2DFE800F004304646426894 +:1014B0006865667426746078002819D1FDF79EFE71 +:1014C000009594F835108DF808104188C90411D0A2 +:1014D000206C019003208DF80900C24824388560F3 +:1014E000C56125746846FDF768FB002800D0FFDF62 +:1014F000BDE8FF81FFF778FF0190E07C10B18DF827 +:101500000950EAE78DF80960E7E7607840B1207C90 +:1015100008B9FDF7F9FD6574BDE8FF41F4F72FBD8B +:10152000A674FDF739FC0028E2D0FFDFE0E7BDE854 +:10153000FF41F7F760BBFDF761FE4088C00407D0AC +:1015400001210320FDF75EFEA7480078E1F7DCFCEF +:10155000002239466846FFF7D6FD38B1694638465D +:1015600000F0EDFE0028C3D1FFDFC1E7E670FFF712 +:10157000CCFCBDE7BDE8FF41C7E4FFDFB8E7994910 +:1015800050B101228A704A6840F27123B2FBF3F233 +:1015900002EB0010886370470020887070472DE9C7 +:1015A000F05F894640F271218E4E48430025044683 +:1015B000706090462F46D0074AF2B12A4FF47A7BEA +:1015C0000FD0B9F800004843B0600120F6F70CFDD9 +:1015D00000EB0A01B1FBFBF0241AB7680125A4F265 +:1015E0008F245FEA087016D539F8151040F2712083 +:1015F000414306EB85080820C8F80810F6F7F4FC0C +:1016000000EB0A01B1FBFBF0241AD8F80800A4F2A1 +:101610008F2407446D1CA74219D9002D17D0391B00 +:10162000B1FBF5F0B268101AB1FBF5F205FB12122E +:10163000801AB060012008E0B1FBF5F306EB8002F0 +:101640009468E31A401CC0B29360A842F4D3BDE88A +:10165000F09F2DE9F041634C00262078042804D047 +:101660002078052801D00C2018E401206070607CEF +:10167000002538B1EFF3108010F0010F72B610D0D2 +:1016800001270FE0FDF7BAFD074694F82000F5F7B3 +:10169000B2F87888C00411D000210320FDF7B2FD14 +:1016A0000CE00027607C38B1A07C28B1FDF72CFD50 +:1016B0006574A574F4F763FC07B962B694F820006A +:1016C000F5F705FB94F8280030B184F8285020780D +:1016D000052800D0FFDF0C26657000F06AFE30465A +:1016E00012E4404810B5007808B1FFF7B2FF00F0EF +:1016F000D4FE3C4900202439086210BD10B53A4C94 +:1017000058B1012807D0FFDFA06841F66A0188427E +:1017100000D3FFDF10BD40F6C410A060F4E73249EB +:1017200008B508702F4900200870487081F828001B +:10173000C8700874487488742022486281F8202098 +:10174000243948704FF6FF7211F1680121F810201A +:10175000401CC0B22028F9D30020FFF7CFFFFFF7CD +:10176000C0FF1020ADF80000012269460420FFF7F9 +:1017700016FF08BD7FB51B4C05460E46207810B1FC +:101780000C2004B070BD95F8652095F86410686A67 +:1017900000F0C5FEC5F80401656295F8F00000B1DF +:1017A000FFDF104900202439C861052121706070D5 +:1017B00084F82800014604E004EB4102491C5085EE +:1017C000C9B294F836208A42F6D284F83500304601 +:1017D000FFF7D5FE0548F4F74DFC84F820002028DB +:1017E00007D105E0F0110020800100207D140200E7 +:1017F000FFDFF4F7B9FC606194F82010012268461D +:10180000FFF781FC00B9FFDF94F82000694600F083 +:1018100096FD00B9FFDF0020B3E7F94810B5007866 +:1018200008B1002010BD0620F2F7DAFA80F00100BE +:1018300010BDF8B5F24D0446287800B1FFDF002056 +:10184000009023780246DE0701466B4605D060888B +:10185000A188ADF80010012211462678760706D53A +:10186000E088248923F8114042F00802491C491EEF +:1018700085F836101946FFF792FE0020F8BD1FB517 +:1018800011B1112004B010BDDD4C217809B10C203C +:10189000F8E70022627004212170114605E000BFC4 +:1018A00004EB4103491C5A85C9B294F836308B4287 +:1018B000F6D284F83520FFF762FED248F4F7DAFB5F +:1018C00084F82000202800D1FFDF00F0DDFD10B1FA +:1018D000F4F74AFC05E0F4F747FC40F6B831F4F7BA +:1018E0002AF9606194F8201001226846FFF70BFC8A +:1018F00000B9FFDF94F82000694600F020FD00B930 +:10190000FFDF0020BEE770B5BD4C616A0160FFF7E4 +:10191000A0FE050002D1606AFFF7B0F80020606207 +:10192000284670BD7FB5B64C2178052901D00C2022 +:1019300027E7B3492439C860606A00B9FFDF606AED +:1019400090F8F00000B1FFDF606A90F8FA002028FC +:1019500000D0FFDFAC48F4F78DFB616A0546202814 +:1019600081F8FA000E8800D3FFDFA548443020F844 +:101970001560606A90F8FA00202800D1FFDF00238C +:1019800001226846616AFFF794F8606A694690F838 +:10199000FA0000F0D4FC00B9FFDF00206062F0E63E +:1019A000974924394870704710B540F2E24300FB74 +:1019B00003F4002000F0B3FD844201D9201A10BDC9 +:1019C000002010BD70B50D46064601460020FCF70C +:1019D000E0FE044696F86500F6F706FB014696F829 +:1019E00064004FF47A72022815D0012815D040F611 +:1019F000340008444AF247310844B0FBF2F17088E1 +:101A000040F271225043C1EB4000A0F22630A542C3 +:101A100006D2214605E01046EBE74FF4C860E8E740 +:101A20002946814204D2A54201D2204600E0284640 +:101A3000706270BD70B50546FDF7E0FB7049007837 +:101A400024398C689834072D30D2DFE805F004344F +:101A500034252C34340014214FF4A873042810D0FA +:101A60000822082809D02A2102280FD011FB0240A1 +:101A700000222823D118441819E0402211FB02400B +:101A8000F8E7102211FB02402E22F3E7042211FB9B +:101A9000024000221823EDE7282100F04BFC04440B +:101AA00004F5317403E004F5B07400E0FFDF54483E +:101AB000C06BA04201D9012070BD002070BD70B57F +:101AC0004F4C243C607870B1D4E904512846A26898 +:101AD000EFF7EDFA2061A84205D0A169401B084448 +:101AE000A061F5F706F82169A068884201D820783E +:101AF00008B1002070BD012070BD2DE9F04F0546F2 +:101B000085B016460F461C461846F6F7F5FA05EB63 +:101B100047014718204600F0F5FB4AF2C5714FF423 +:101B20007A7908444D46B0FBF5F0384400F160087E +:101B30003348761C24388068304404902046F6F7F9 +:101B4000DBFAA8EB0007204600F0DCFB0646204647 +:101B5000F6F74AFA301AB0FBF5F03A1A18252820A1 +:101B60004FF4C8764FF4BF774FF0020B082C30D0FB +:101B7000042C2BD00021022C2ED0082311F1280197 +:101B800003EB830C0CEB831319440A444FF0000A57 +:101B9000082C29D0042C22D00021022C29D0054663 +:101BA000082001F5B07100BF00EB0010284481420D +:101BB00032D2082C2AD0042C1ED00020022C28D08F +:101BC0000821283001EB0111084434E03946102384 +:101BD000D6E731464023D3E704231831D0E73D460A +:101BE00040F2EE311020DFE735464FF435614020FA +:101BF000DAE70420B431D7E738461021E2E70000E5 +:101C0000F01100207D140200530D020030464021E7 +:101C1000D8E704211830D5E7082C4FD0042C4AD03F +:101C20000021022C4DD0082311F12801C3EBC30081 +:101C300000EB4310084415182821204600F07AFBD9 +:101C400005EB4001082C42D0042C3DD00026022C8C +:101C50003FD0082016F1280600EB801006EB80002C +:101C60000E180120FA4D8DF804008DF800A08DF8B3 +:101C700005B0A86906F22A260499F3F75CFFCDE9BE +:101C800002062046F6F7B0F94AF23B510144B1FB97 +:101C9000F9F0301AFE38E8630298C5F84080A86170 +:101CA00095F82000694600F04AFB002800D1FFDFCC +:101CB00005B0BDE8F08F39461023B7E73146402321 +:101CC000B4E704231831B1E73E461020C4E74020B2 +:101CD000C2E704201836BFE72DE9FE4F06461C4632 +:101CE000174688464FF0010A1846F6F705FAD84D10 +:101CF000243DA9688A1907EB48011144471820467A +:101D000000F000FB4FF47A7BD84600F6FB00B0FBF6 +:101D1000F8F0384400F120092046F6F7EDF9A968FB +:101D20000246A9EB0100801B871A204600F0EAFA60 +:101D300005462046F6F758F9281AB0FBF8F03A1A8B +:101D4000182528204FF4C8774FF4BF78082C2DD0E1 +:101D5000042C28D00021022C2BD0082311F12801BB +:101D600003EB830C0CEB831319440A44082C28D092 +:101D7000042C21D00021022C28D00546082001F592 +:101D8000B07100BF00EB0010284481422AD2082C19 +:101D900022D0042C1DD00020022C20D00821283075 +:101DA00001EB01112CE041461023D9E739464023CD +:101DB000D6E704231831D3E7454640F2EE31102030 +:101DC000E0E73D464FF435614020DBE70420B431C5 +:101DD000D8E740461021E3E738464021E0E70421F8 +:101DE0001830DDE7082C48D0042C43D00020022C0A +:101DF00046D0082110F12800C1EBC10303EB4111CB +:101E0000084415182821204600F094FA05EB4001FB +:101E1000082C3BD0042C36D00027022C38D00820C8 +:101E200017F1280700EB801007EB80000C1804F571 +:101E300096740C98F6F7D8F84AF23B510144B1FB7E +:101E4000FBF0834DFE30A5F12407E96B06F1FE029D +:101E50000844B9680B191A44824224D93219114432 +:101E60000C1AFE342044B0F1807F37D2642C12D299 +:101E7000642011E040461021BEE738464021BBE710 +:101E800004211830B8E747461020CBE74020C9E7C7 +:101E900004201837C6E720460421F4F790FEE8B185 +:101EA000E86B2044E863E0F703FFB9682938314460 +:101EB0000844CDE9000995F835008DF808000220A6 +:101EC0008DF809006846FCF778FE00B1FFDFFCF7EB +:101ED00063FF00B1FFDF5046BDE8FE8F4FF0000A00 +:101EE000F9E71FB500F021FB594C607880B994F8F0 +:101EF000201000226846FFF706F938B194F8200058 +:101F0000694600F01CFA18B9FFDF01E00120E0701B +:101F1000F4F735F800206074A0741FBD2DE9F84F68 +:101F2000FDF76CF90646451CC07840090CD0012825 +:101F30000CD002280CD000202978824608064FF4E5 +:101F4000967407D41E2006E00120F5E70220F3E78F +:101F50000820F1E72046B5F80120C2F30C0212FB7D +:101F600000F7C80901D010B103E01E2401E0FFDF33 +:101F70000024F6F7D3F8A7EB00092878B77909EB26 +:101F80000408C0F3801010B120B1322504E04FF4F2 +:101F9000FA7501E0FFDF00250C2F00D3FFDF2D488D +:101FA0002D4A30F81700291801FB0821501CB1FBFD +:101FB000F0F5F6F76DF8F6F717F84FF47A7100F2CE +:101FC0007160B0FBF1F1A9EB0100471BA7F15900CB +:101FD000103FB0F5247F11D31D4E717829B9024608 +:101FE000534629462046FFF788FD00F09EFAF3F796 +:101FF000C6FF00207074B074BDE8F88F3078009090 +:102000005346224629463846FFF766FE0028F3D19C +:1020100001210220FDF7F6F8BDE8F84F61E710B5A1 +:102020000446012903D10A482438007830B104203D +:1020300084F8F000BDE81040F3F7A1BF00220121B1 +:10204000204600F0A5F934F8700F401C2080F1E71D +:10205000F0110020646302003F420F002DE9F041BF +:102060000746FDF7CBF8050000D1FFDF287810F018 +:102070000C0F01D0012100E00021F74C606A3030E4 +:10208000FCF7C7FA29783846EFF71BFAA4F12406C3 +:102090000146A069B26802446FB32878082803D0CB +:1020A000042803D000230BE0082300E0022303EB05 +:1020B000430328334FF4A877082804D0042802D01B +:1020C000022810D028273B4408280ED004280ED020 +:1020D00002280ED05FF00800C0EBC00707EB4010ED +:1020E0001844983009E01827EDE74020F4E7102065 +:1020F000F2E70420F0E74FF4FC701044471828780A +:102100003F1DF5F771FF014628784FF47A720228D7 +:102110001DD001281DD040F6340008444AF2EF01DA +:102120000844B0FBF2F03A1A606A40F2E241B0466D +:102130004788F0304F43316A81420DD03946206BD9 +:1021400000F08EF90646B84207D9FFDF05E01046D9 +:10215000E3E74FF4C860E0E70026C04880688642A5 +:1021600007D2616A40F271224888424306EB420678 +:1021700004E040F2E240B6FBF0F0616AC882606AB7 +:10218000297880F86410297880F865100521417558 +:10219000C08A6FF41C71484306EB400040F635419D +:1021A000C8F81C00B0EB410F00D3FFDFBDE8F081A1 +:1021B00010B5052937D2DFE801F00509030D31001C +:1021C000002100E00121BDE8104028E7032180F84C +:1021D000F01010BD0446408840F2E24148439F4958 +:1021E000091D0860D4F818010089E082D4F81801AC +:1021F00080796075D4F8180140896080D4F818019E +:102200008089A080D4F81801C089E0802046A16AA6 +:10221000FFF7D8FB022084F8F00010BD816ABDE80A +:102220001040FFF7CFBBFFDF10BD70B58A4C243CD8 +:102230000928A1683FD2DFE800F0050B0B15131544 +:1022400038380800BDE870404BE6BDE8704065E6F0 +:10225000022803D00020BDE87040FFE60120FAE725 +:10226000E16070BD032802D005281CD000E0E160C9 +:102270005FF0000600F059F9774D012085F828003D +:1022800085F83460686AA9690026C0F8F41080F8FF +:10229000F060E068FFF746FB00B1FFDFF3F76FFE89 +:1022A0006E74AE7470BD0126E4E76C480078BDE83A +:1022B0007040E0F729BEFFDF70BD674924394860F0 +:1022C000704770B5644D0446243DB1B14FF47A7641 +:1022D000012903D0022905D0FFDF70BD1846F5F7AC +:1022E000FCFE05E06888401C68801046F6F7F8FFA1 +:1022F00000F2E730B0FBF6F0201AA86070BD564837 +:1023000000787047082803D0042801D0F5F76CBE88 +:102310004EF628307047002804DB00F1E02090F8EA +:10232000000405E000F00F0000F1E02090F8140D2B +:102330004009704710F00C0000D008467047F4F7D1 +:102340003EB910B50446202800D3FFDF4248443090 +:1023500030F8140010BD70B505460C461046F5F770 +:1023600043FE4FF47A71022C0DD0012C0DD040F6B3 +:10237000340210444AF247321044B0FBF1F02844D2 +:1023800000F5CB7070BD0A46F3E74FF4C862F0E782 +:102390001FB513460A46044601466846FEF789FB08 +:1023A00094F8FA006946FFF7CAFF002800D1FFDF62 +:1023B0001FBD70B5284C0025257094F82000F3F758 +:1023C000B4FE00B9FFDF84F8205070BD2DE9F04164 +:1023D000050000D1FFDF204A0024243AD5F804612B +:1023E0002046631E116A08E08869B04203D3984210 +:1023F00001D203460C460846C9680029F4D104B945 +:1024000004460021C5F80041F035C4B1E068E5603C +:10241000E86000B105612E698846A96156B1B069CE +:1024200030B16F69B84200D2FFDFB069C01BA8614C +:10243000C6F81880084D5CB1207820B902E0E96048 +:102440001562E8E7FFDF6169606808442863ADE66C +:10245000C5F83080AAE60000F011002080010020BD +:1024600010B50C4601461046F4F776FB002806DA54 +:10247000211A491EB1FBF4F101FB040010BD90FBD1 +:10248000F4F101FB140010BD2E48016A002001E0A8 +:102490000846C9680029FBD170472DE9FE43294D44 +:1024A0000120287000264FF6FF7420E00621F1F786 +:1024B000FDFC070000D1FFDF97F8FA00F037F4F7D2 +:1024C00006FC07F80A6BA14617F8FA89B8F1200F45 +:1024D00000D3FFDF1B4A683222F8189097F8FA0001 +:1024E000F3F723FE00B9FFDF202087F8FA006946E2 +:1024F0000620F1F764FC50B1FFDF08E0029830B12C +:1025000090F8F01019B10088A042CFD104E06846DD +:10251000F1F733FC0028F1D02E70BDE8FE8310B532 +:10252000FFF719FF00F5C87010BD064800212430E0 +:1025300090F8352000EB4200418503480078E0F731 +:10254000E3BC0000CC11002080010020012804D051 +:10255000022805D0032808D105E0012907D004E0AE +:10256000022904D001E0042901D000207047012095 +:102570007047F748806890F8A21029B1B0F89E1013 +:10258000B0F8A020914215D290F8A61029B1B0F869 +:10259000A410B0F8A02091420CD2B0F89C20B0F862 +:1025A0009A108A4206D290F88020B0F898001AB1AA +:1025B000884203D3012070470628FBD200207047D1 +:1025C0002DE9F041E24D0746A86800F1700490F84B +:1025D000140130B9E27B002301212046EEF7D2FC42 +:1025E00010B1A08D401CA08501263D21AFB92878EF +:1025F000022808D001280AD06878C8B110F0140F5A +:1026000009D01E2039E0162037E026773EE0A86882 +:1026100090F8160131E0020701D56177F5E78107EF +:1026200001D02A2029E0800600D4FFDF232024E007 +:1026300094F8320028B1E08D411CE185218E88425A +:1026400013D294F8360028B1A08E411CA186218EA9 +:1026500088420AD2A18D608D814203D3AA6892F884 +:10266000142112B9228E914201D3222005E0217C4F +:1026700029B1218D814207D308206077C5E7208DDD +:10268000062801D33E20F8E7207FB0B10020207358 +:10269000607320740221A868FFF78AFDA86890F88B +:1026A000E410012904D1D0F81C110878401E0870EC +:1026B000E878BDE8F041E0F727BCA868BDE8F04144 +:1026C0000021FFF775BDA2490C28896881F8E40054 +:1026D00014D0132812D0182810D0002211280ED0A0 +:1026E00007280BD015280AD0012807D0002805D0CC +:1026F000022803D021F89E2F012008717047A1F80D +:10270000A420704710B5924CA1680A88A1F86021F6 +:1027100081F85E0191F8640001F046FBA16881F840 +:10272000620191F8650001F03FFBA16881F8630147 +:10273000012081F85C01002081F82E01E078BDE8DD +:102740001040E0F7E1BB70B5814C00231946A0684A +:1027500090F87C207030EEF715FC00283DD0A06882 +:1027600090F820110025C9B3A1690978B1BB90F890 +:102770007D00EEF7EFFB88BBA168B1F870000A2876 +:102780002DD905220831E069EBF72AFE10B3A068C5 +:10279000D0F81C11087858B10522491CE069EBF704 +:1027A0001FFE002819D1A068D0F81C01007840B99C +:1027B000A068E169D0F81C010A68C0F80120097915 +:1027C0004171A068D0F81C110878401C08700120E5 +:1027D000FFF779FFA06880F8205170BDFFE7A0687F +:1027E00090F8241111B190F82511C1B390F82E1171 +:1027F0000029F2D090F82F110029EED190F87D0039 +:10280000EEF7A8FB0028E8D1A06890F8640001F07A +:10281000CBFA0646A06890F8650001F0C5FA0546B7 +:10282000A06890F830113046FFF790FEA0B3A06882 +:1028300090F831112846FFF789FE68B3A268B2F814 +:10284000703092F86410B2F8320102F58872EEF737 +:1028500001FE20B3A168252081F87C00BDE7FFE7D9 +:1028600090F87D10242918D090F87C10242914D0D9 +:102870005FF0000300F5897200F59271FBF78EFEA0 +:10288000A16881F8245101F13000C28A21F8E62FB5 +:10289000408B4880142007E005E00123EAE7BDE80B +:1028A000704000202EE71620BDE870400BE710B501 +:1028B000F4F7FAFD0C2813D3254C0821A068D0F8B2 +:1028C00018011E30F4F7F4FD28B1A0680421D830B7 +:1028D000F4F7EEFD00B9FFDFBDE810400320F2E69B +:1028E00010BD10B51A4CA068D0F818110A78002A4B +:1028F0001FD04988028891421BD190F87C20002388 +:1029000019467030EEF73EFB002812D0A068D0F8D0 +:1029100018110978022907D003290BD0042919D0EE +:10292000052906D108200DE090F87D00EEF712FB96 +:1029300040B110BD90F8811039B190F8820000B913 +:10294000FFDF0A20BDE81040BDE6BDE81040AEE75D +:102950008C01002090F8AA008007EAD10C20FFF734 +:10296000B2FEA068002120F89E1F01210171017BA9 +:1029700041F00101017310BD70B5F74CA268556EAE +:10298000EEF702FDEBB2C1B200228B4203D0A36886 +:1029900083F8121102E0A16881F81221C5F3072122 +:1029A000C0F30720814203D0A16881F8130114E726 +:1029B000A06880F8132110E710B5E74C0421A06847 +:1029C000FFF7F6FBA06890F85A10012908D000F52F +:1029D0009E71FBF7ACFEE078BDE81040E0F794BADA +:1029E000022180F85A1010BD70B5DB4CA06890F839 +:1029F000E410FE2955D16178002952D190F87F204A +:102A0000002301217030EEF7BDFA002849D1A068FB +:102A100090F8141109B1022037E090F87C200023CF +:102A200019467030EEF7AEFA28B1A06890F896001B +:102A300008B1122029E0A068002590F87C20122A15 +:102A40001DD004DC032A23D0112A04D119E0182A4E +:102A50001AD0232A26D0002304217030EEF792FAF0 +:102A600000281ED1A06890F87D10192971D020DCB3 +:102A700001292AD0022935D0032932D120E00B20A8 +:102A800003E0BDE8704012E70620BDE870401AE69A +:102A900010F8E21F01710720FFF715FEA06880F80B +:102AA0007C509AE61820FFF70EFEA068A0F89E5012 +:102AB00093E61D2918D01E2916D0212966D149E098 +:102AC00010F8E11F4171072070E00C20FFF7FBFDBB +:102AD000A06820F8A45F817941F00101817100F8BC +:102AE000275C53E013202CE090F8252182BB90F85E +:102AF0002421B2B1242912D090F87C1024290ED0C0 +:102B00005FF0000300F5897200F59271FBF746FD56 +:102B1000A0681E2180F87D1080F8245103E0012375 +:102B2000F0E71E2932D1A068FBF797FDFFF744FFBD +:102B3000A16801F13000C28A21F8E62F408B48805D +:102B40001520FFF7C0FDA068A0F8A45080F87D50C4 +:102B50001CE02AE090F8971051B180F8125180F8EB +:102B600013511820FFF7AFFDA068A0F8A4500DE0A6 +:102B700090F82F1151B990F82E1139B1C16DD0F8DC +:102B80003001FFF7F9FE1820FFF79DFDA06890F8CF +:102B9000E400FE2885D1FFF7A4FEA06890F8E400C9 +:102BA000FE2885D1BDE87040CDE51120FFF78BFDF3 +:102BB000A068CBE7684A0129926819D0002302294E +:102BC0000FD003291ED010B301282BD0032807D122 +:102BD00092F87C00132803D0162801D0182804D1BD +:102BE000704792F8E4000028FAD0D2F8180117E0F4 +:102BF00092F8E4000128F3D0D2F81C110878401EA6 +:102C00000870704792F8E4000328EED17047D2F8BC +:102C10001801B2F870108288891A09B20029F5DB10 +:102C200003707047B2F87000B2F82211401A00B277 +:102C30000028F6DBD2F81C010178491E01707047AC +:102C400070B5044690F87C0000250C2810D00D28A3 +:102C50002ED1D4F81811B4F870008988401C88422D +:102C600026D1D4F864013C4E017811B3FFDF42E075 +:102C7000B4F87000B4F82211401C884218D1D4F87E +:102C80001C01D0F80110A160407920730321204677 +:102C9000EDF7F1FDD4F81C01007800B9FFDF012148 +:102CA000FE20FFF787FF84F87C50012084F8B200F3 +:102CB00093E52188C180D4F81801D4F864114089C3 +:102CC0000881D4F81801D4F8641180894881D4F8B7 +:102CD0001801D4F86411C0898881D4F864010571A1 +:102CE000D4F8641109200870D4F864112088488051 +:102CF000F078E0F709F902212046EDF7BCFD032149 +:102D00002046FFF755FAB068D0F81801007802287D +:102D100000D0FFDF0221FE20FFF74CFF84F87C503B +:102D20005BE52DE9F0410C4C00260327D4F808C0E0 +:102D3000012598B12069C0788CF8E20005FA00F00E +:102D4000C0F3C05000B9FFDFA06800F87C7F468464 +:102D500080F82650BDE8F0818C01002000239CF80B +:102D60007D2019460CF17000EEF70CF970B1607817 +:102D70000028EFD12069C178A06880F8E11080F8C0 +:102D80007D70A0F8A46080F8A650E3E76570E1E7E5 +:102D9000F0B5F74C002385B0A068194690F87D2067 +:102DA0007030EEF7EFF8012580B1A06890F87C0054 +:102DB00023280ED024280CD06846F6F785FB68B18E +:102DC000009801A9C0788DF8040008E0657005B08E +:102DD000F0BD607840F020006070F8E70021A06846 +:102DE00003AB162290F87C00EEF74FFB002648B1AB +:102DF000A0689DF80C20162180F80C2180F80D1198 +:102E0000192136E02069FBF722FB78B121690879A6 +:102E100000F00702A06880F85C20497901F0070102 +:102E200080F85D1090F82F310BBB03E00020FFF716 +:102E300078FFCCE790F82E31CBB900F164035F78CE +:102E4000974205D11A788A4202D180F897500EE055 +:102E500000F5AC71028821F8022990F85C200A7113 +:102E600090F85D0048710D70E078E0F74DF8A068CB +:102E7000212180F87D1080F8A650A0F8A460A6E774 +:102E8000F8B5BB4C00231946A06890F87D2070303F +:102E9000EEF778F840B32069FBF7BEFA48B3206933 +:102EA000FBF7B4FA07462069FBF7B4FA0646206937 +:102EB000FBF7AAFA05462069FBF7AAFA0146009734 +:102EC000A06833462A463030FBF79BFBA1680125FA +:102ED00091F87C001C2810D091F85A00012812D0DB +:102EE00091F8250178B90BE0607840F0010060703E +:102EF000F8BDBDE8F840002013E781F85A5002E021 +:102F000091F8240118B11E2081F87D000BE01D20EE +:102F100081F87D0001F5A57231F8300BFBF7FBFB62 +:102F2000E078DFF7F1FFA068002120F8A41F85708A +:102F3000F8BD10B58E4C00230921A06890F87C20C4 +:102F40007030EEF71FF848B16078002805D1A1680D +:102F500001F8960F087301F81A0C10BD012060707B +:102F600010BD7CB5824C00230721A06890F87C201E +:102F70007030EEF707F838B36078002826D169463C +:102F80002069FBF75FFA9DF80000002500F025019D +:102F9000A06880F8B0109DF8011001F0490180F898 +:102FA000B11080F8A250D0F81811008849888142E9 +:102FB00000D0FFDFA068D0F818110D70D0F86411B0 +:102FC0000A7822B1FFDF16E0012060707CBD30F886 +:102FD000E82BCA80C16F0D71C16F009A8A60019A97 +:102FE000CA60C26F0821117030F8E81CC06F4180C0 +:102FF000E078DFF789FFA06880F87C507CBD70B571 +:103000005B4C00231946A06890F87D207030EDF7E6 +:10301000B9FF012540B9A0680023082190F87C2061 +:103020007030EDF7AFFF10B36078002820D1A068B2 +:1030300090F8AA00800712D42069FBF7C9F9A168AB +:1030400081F8AB00206930F8052FA1F8AC2040884A +:10305000A1F8AE0011F8AA0F40F002000870A068B5 +:103060004FF0000690F8AA10C90702D011E0657071 +:103070009DE490F87D20002319467030EDF782FF23 +:1030800000B9FFDFA06880F87D5080F8A650A0F856 +:10309000A460A06890F87C10012906D180F87C60BB +:1030A00080F8A260E078DFF72FFFA168D1F818015F +:1030B000098842888A42DBD101780429D8D1067078 +:1030C000E078DFF721FFA06890F87C100029CFD1CD +:1030D00080F8A2606BE470B5254DA86890F87C106C +:1030E0001A2902D00220687061E469780029FBD1B6 +:1030F000002480F8A74080F8A240D0F8181100887A +:103100004988814200D0FFDFA868D0F818110C7000 +:10311000D0F864110A780AB1FFDF25E090F8A82002 +:1031200072B180F8A8400288CA80D0F864110C718E +:10313000D0F864210E2111700188D0F864010DE0EF +:1031400030F8E82BCA80C16F0C71C26F0121117277 +:10315000C26F0D21117030F8E81CC06F418000F083 +:10316000A1FEE878DFF7D0FEA86880F87C401EE476 +:103170008C01002070B5FA4CA16891F87C20162AC9 +:1031800001D0132A02D191F8A82012B10220607058 +:103190000DE46278002AFBD181F8E000002581F877 +:1031A000A75081F8A250D1F81801098840888842B8 +:1031B00000D0FFDFA068D0F818010078032800D005 +:1031C000FFDF0321FE20FFF7F5FCA068D0F86411B3 +:1031D0000A780AB1FFDF14E030F8E02BCA8010F85B +:1031E000081BC26F1171C16F0D72C26F0D2111707A +:1031F00030F8E81CC06F418000F054FEE078DFF743 +:1032000083FEA06880F87C504BE470B5D44C092153 +:103210000023A06890F87C207030EDF7B3FE002505 +:1032200018B12069007912281ED0A0680A21002355 +:1032300090F87C207030EDF7A5FE18B12069007978 +:10324000142814D02069007916281AD1A06890F8A3 +:103250007C101F2915D180F87C5080F8A250BDE861 +:1032600070401A20FFF74EBABDE8704061E6A068D2 +:1032700000F87C5F458480F82650BDE87040FFF779 +:103280009BBB0EE470B5B64C2079C00773D02069A3 +:1032900000230521C578A06890F87C207030EDF7F8 +:1032A00071FE98B1062D11D006DC022D0ED0042D32 +:1032B0000CD0052D06D109E00B2D07D00D2D05D022 +:1032C000112D03D0607840F008006070607800280D +:1032D00051D12069FAF7E0FF00287ED0206900254F +:1032E0000226C178891E162977D2DFE801F00B7615 +:1032F00034374722764D76254A457676763A5350CE +:103300006A6D7073A0680023012190F87F207030EF +:10331000EDF738FE08BB2069FBF722F8A16881F8B9 +:103320001601072081F87F0081F8A65081F8A2508D +:1033300056E0FFF76AFF53E0A06890F87C100F2971 +:1033400001D066704CE0617839B980F88150122163 +:1033500080F87C1044E000F0D0FD41E000F0ACFDCE +:103360003EE0FBF7A9F803283AD12069FBF7A8F85B +:10337000FFF700FF34E03BE00079F9E7FFF7ABFE31 +:103380002EE0FFF73CFE2BE0FFF7EBFD28E0FFF718 +:10339000D0FD25E0A0680023194690F87D2070300C +:1033A000EDF7F0FD012110B16078C8B901E061705E +:1033B00016E0A06820F8A45F817000F8276C0FE089 +:1033C0000BE0FFF75DFD0BE000F034FD08E0FFF7D8 +:1033D000DFFC05E000F0FAFC02E00020FFF7A1FCB2 +:1033E000A268F2E93001401C41F10001C2E900018C +:1033F0005EE42DE9F0415A4C2079800741D5607890 +:1034000000283ED1E06801270026C178204619290E +:10341000856805F170006FD2DFE801F04B3E0D6F5B +:10342000C1C1801C34C1556287C1C1C1C1BE8B9569 +:1034300098A4B0C1BA0095F87F2000230121EDF7D0 +:10344000A1FD00281DD1A068082180F87F1080F818 +:10345000A26090E0002395F87D201946EDF792FDDB +:1034600010B1A06880F8A660A0680023194690F803 +:103470007C207030EDF786FD002802D0A06880F82F +:10348000A26067E4002395F87C201946EDF77AFDE9 +:1034900000B9FFDF042008E0002395F87C201946DE +:1034A000EDF770FD00B9FFDF0C20A16881F87C000A +:1034B00050E4002395F87C201946EDF763FD00B930 +:1034C000FFDF0D20F1E7002395F87C201946EDF78A +:1034D00059FD00B9FFDFA0680F2180F8A77008E050 +:1034E00095F87C00122800D0FFDFA068112180F839 +:1034F000A87080F87C102DE451E0002395F87C2022 +:103500001946EDF73FFD20B9A06890F8A80000B972 +:10351000FFDFA068132180F8A770EAE795F87C0028 +:10352000182800D0FFDF1A20BFE7BDE8F04100F007 +:1035300063BD002395F87C201946EDF723FD00B903 +:10354000FFDF0520B1E785F8A66003E4002395F8C6 +:103550007C201946EDF716FD00B9FFDF1C20A4E71B +:103560008C010020002395F87D201946EDF70AFD17 +:1035700000B9FFDFA06880F8A66006E4002395F894 +:103580007C201946EDF7FEFC00B9FFDF1F208CE719 +:10359000BDE8F04100F0F8BC85F87D60D3E7FFDFBF +:1035A0006FE710B5F74C6078002837D120794007D5 +:1035B0000FD5A06890F87C00032800D1FFDFA06839 +:1035C00090F87F10072904D101212170002180F893 +:1035D0007F10FFF70EFF00F0B5FCFFF753FEA07859 +:1035E000000716D5A0680023052190F87C207030D4 +:1035F000EDF7C8FC50B108206070A068D0F86411E5 +:1036000008780D2800D10020087002E00020F9F7AA +:10361000F9FCA068BDE81040FFF712BB10BD2DE912 +:10362000F041D84C07464FF00005607808436070C1 +:10363000207981062046806802D5A0F8985004E0E1 +:10364000B0F89810491CA0F8981000F018FD012659 +:10365000F8B1A088000506D5A06890F8821011B1D5 +:10366000A0F88E5015E0A068B0F88E10491CA0F8A4 +:103670008E1000F0F3FCA068B0F88E10B0F8902027 +:10368000914206D3A0F88E5080F83A61E078DFF7D7 +:103690003BFC207910F0600F08D0A06890F88010F3 +:1036A00021B980F880600121FEF782FD1FB9FFF784 +:1036B00078FFFFF799F93846FEF782FFBDE8F04141 +:1036C000F5F711BFAF4A51789378194313D11146DA +:1036D0000128896808D01079400703D591F87F0048 +:1036E000072808D001207047B1F84C00098E8842A5 +:1036F00001D8FEF7E4B900207047A249C278896872 +:10370000012A06D05AB1182A08D1B1F81011FAF7D7 +:10371000BABEB1F822114172090A81727047D1F81C +:10372000181189884173090A8173704770B5954CE7 +:1037300005460E46A0882843A080A80703D5E807C1 +:1037400000D0FFDFE660E80700D02661A80719D5A2 +:10375000F078062802D00B2814D10BE0A06890F86E +:103760007C1018290ED10021E0E93011012100F868 +:103770003E1C07E0A06890F87C10122902D10021BD +:1037800080F88210280601D50820A07068050AD5A7 +:10379000A0688288B0F87010304600F07FFC304698 +:1037A000BDE87040A9E763E43EB505466846F5F715 +:1037B00065FE00B9FFDF222200210098EAF767FECC +:1037C00003210098FAF750FD0098017821F01001CC +:1037D00001702946FAF76DFD6A4C192D71D2DFE8A8 +:1037E00005F020180D3EC8C8C91266C8C9C959C815 +:1037F000C8C8C8BBC9C971718AC89300A1680098BC +:1038000091F8151103E0A168009891F8E610017194 +:10381000B0E0A068D0F81C110098491CFAF795FD9B +:10382000A8E0A1680098D1F8182192790271D1F826 +:10383000182112894271120A8271D1F81821528915 +:10384000C271120A0272D1F8182192894272120AC8 +:103850008272D1F81811C989FAF74EFD8AE0A06882 +:10386000D0F818110098091DFAF77CFDA068D0F86F +:10387000181100980C31FAF77FFDA068D0F81811E4 +:1038800000981E31FAF77EFDA1680098D831FAF74A +:1038900087FD6FE06269009811780171918841712C +:1038A000090A81715188C171090A017262E03649C1 +:1038B000D1E90001CDE9010101A90098FAF78AFDDB +:1038C00058E056E0A068B0F844100098FAF794FD6C +:1038D000A068B0F8E6100098FAF792FDA068B0F87A +:1038E00048100098FAF780FDA068B0F8E81000983A +:1038F000FAF77EFD3EE0A168009891F83021027150 +:1039000091F83111417135E0A06890F81301EDF79D +:1039100032FD01460098FAF7B2FDA06890F8120156 +:1039200000F03DFA70B1A06890F8640000F037FA3A +:1039300040B1A06890F8121190F86400814201D063 +:10394000002002E0A06890F81201EDF714FD014696 +:103950000098FAF790FD0DE0A06890F80D1100981E +:10396000FAF7A8FDA06890F80C110098FAF7A6FDE8 +:1039700000E0FFDFF5F795FD00B9FFDF0098FFF7E6 +:10398000BCFE3EBD8C0100207C63020010B5F94CEA +:10399000A06890F8121109B990F8641080F86410CA +:1039A00090F8131109B990F8651080F8651000209F +:1039B000FEF7A8FEA068FAF750FE002806D0A0681F +:1039C000BDE8104000F59E71FAF7B1BE10BDF8B524 +:1039D000E84E00250446B060B5807570B57035704E +:1039E0000088F5F748FDB0680088F5F76AFDB4F87F +:1039F000F800B168401C82B201F17000EDF75BF88D +:103A000000B1FFDF94F87D00242809D1B4F87010CC +:103A1000B4F81001081A00B2002801DB707830B148 +:103A200094F87C0024280AD0252808D015E0FFF758 +:103A3000ADFF84F87D50B16881F897500DE0B4F87F +:103A40007010B4F81001081A00B2002805DB707875 +:103A500018B9FFF79BFF84F87C50A4F8F850FEF7E4 +:103A600088FD00281CD1B06890F8E400FE2801D041 +:103A7000FFF79AFEC0480090C04BC14A2146284635 +:103A8000F9F7ECF9B0680023052190F87C2070303C +:103A9000EDF778FA002803D0BDE8F840F8F771BFD9 +:103AA000F8BD10B5FEF765FD20B10020BDE810405F +:103AB0000146B4E5BDE81040F9F77FBA70B50C4691 +:103AC000154606464FF4B47200212046EAF7DFFCA3 +:103AD000268005B9FFDF2868C4F818016868C4F8B3 +:103AE0001C01A868C4F8640182E4F0F7E9BA2DE982 +:103AF000F0410D4607460621F0F7D8F9041E3DD0E7 +:103B0000D4F864110026087858B14A8821888A427E +:103B100007D109280FD00E2819D00D2826D0082843 +:103B20003ED094F83A01D0B36E701020287084F81B +:103B30003A61AF809AE06E7009202870D4F8640171 +:103B4000416869608168A9608089A88133E008467E +:103B5000F0F7DDFA0746EFF78AFF70B96E700E20B6 +:103B60002870D4F864014068686011E00846F0F7F6 +:103B7000CEFA0746EFF77BFF08B1002081E46E70B4 +:103B80000D202870D4F8640141686960008928819B +:103B9000D4F8640106703846EFF763FF66E00EE084 +:103BA0006E7008202870D4F86401416869608168EB +:103BB000A960C068E860D4F86401067056E094F823 +:103BC0003C0198B16E70152028700AE084F83C61C1 +:103BD000D4F83E016860D4F84201A860D4F84601E8 +:103BE000E86094F83C010028F0D13FE094F84A01E5 +:103BF00058B16E701C20287084F84A610A2204F5BE +:103C0000A671281DEAF719FC30E094F8560140B17E +:103C10006E701D20287084F85661D4F858016860D1 +:103C200024E094F8340168B16E701A20287004E022 +:103C300084F83461D4F83601686094F834010028BF +:103C4000F6D113E094F85C01002897D06E7016202E +:103C5000287007E084F85C61D4F85E016860B4F80D +:103C60006201288194F85C010028F3D1012008E466 +:103C7000404A5061D170704770B50D4604464EE021 +:103C8000B4F8F800401CA4F8F800B4F89800401C00 +:103C9000A4F89800204600F0F2F9B8B1B4F88E000C +:103CA000401CA4F88E00204600F0D8F9B4F88E002D +:103CB000B4F89010884209D30020A4F88E000120A7 +:103CC00084F83A012B48C078DFF71EF994F8A20077 +:103CD00020B1B4F89E00401CA4F89E0094F8A60001 +:103CE00020B1B4F8A400401CA4F8A40094F8140176 +:103CF00040B994F87F200023012104F17000EDF712 +:103D000041F920B1B4F89C00401CA4F89C00204666 +:103D1000FEF796FFB4F87000401CA4F870006D1E0A +:103D2000ADB2ADD23FE5134AC2E90601704770B5A6 +:103D30000446B0F8980094F88010D1B1B4F89A1005 +:103D40000D1A2D1F94F8960040B194F87C200023A2 +:103D5000092104F17000EDF715F9B8B1B4F88E60DF +:103D6000204600F08CF980B1B4F89000801B001F51 +:103D70000CE007E08C0100201F360200C53602006F +:103D80002D370200C0F10205DCE72846A84200DA20 +:103D90000546002D01DC002005E5A8B203E510F082 +:103DA0000C0000D00120704710B5012808D002286F +:103DB00008D0042808D0082806D0FFDF204610BD10 +:103DC0000124FBE70224F9E70324F7E770B5CC4CA4 +:103DD000A06890F87C001F2804D0607840F00100B3 +:103DE0006070E0E42069FAF73CFBD8B12069012259 +:103DF0000179407901F0070161F30705294600F0D8 +:103E0000070060F30F21A06880F8A2200022A0F82C +:103E10009E20232200F87C2FD0F8B400BDE870402B +:103E2000FEF7AABD0120FEF77CFFBDE870401E2012 +:103E3000FEF768BCF8B5B24C00230A21A06890F8E0 +:103E40007C207030EDF79EF838B32069FAF7E4FA79 +:103E5000C8B12069FAF7DAFA07462069FAF7DAFA00 +:103E600006462069FAF7D0FA05462069FAF7D0FA33 +:103E700001460097A06833462A463030FAF7C1FB66 +:103E8000A068FAF7EAFBA168002081F8A20081F897 +:103E90007C00BDE8F840FEF78FBD607840F001007F +:103EA0006070F8BD964810B580680088F0F72FF96B +:103EB000BDE81040EFF7C6BD10B5914CA36893F86C +:103EC0007C00162802D00220607010BD60780028A7 +:103ED000FBD1D3F81801002200F11E010E30C833C7 +:103EE000ECF7C2FFA0680021C0E92E11012180F883 +:103EF0008110182180F87C1010BD10B5804CA0688E +:103F000090F87C10132902D00220607010BD6178F7 +:103F10000029FBD1D0F8181100884988814200D0CF +:103F2000FFDFA068D0F8181120692631FAF745FAAA +:103F3000A1682069DC31FAF748FAA168162081F8F7 +:103F40007C0010BD10B56E4C207900071BD5607841 +:103F5000002818D1A068002190F8E400FEF72AFE9E +:103F6000A06890F8E400FE2800D1FFDFA068FE21E1 +:103F700080F8E41090F87F10082904D10221217004 +:103F8000002180F87F1010BD70B55D4D2421002404 +:103F9000A86890F87D20212A05D090F87C20232A5B +:103FA00018D0FFDFA0E590F8122112B990F8132184 +:103FB0002AB180F87D10A86880F8A64094E500F842 +:103FC0007D4F847690F8B1000028F4D00020FEF7F1 +:103FD00099FBF0E790F8122112B990F813212AB159 +:103FE00080F87C10A86880F8A2407DE580F87C40CD +:103FF0000020FEF787FBF5E770B5414C0025A0686F +:10400000D0F8181103884A889A4219D109780429EE +:1040100016D190F87C20002319467030ECF7B2FFDF +:1040200000B9FFDFA06890F8AA10890703D4012126 +:1040300080F87C1004E080F8A250D0F818010570D8 +:10404000A0680023194690F87D207030ECF79AFFA5 +:10405000002802D0A06880F8A65045E5B0F890206E +:10406000B0F88E108A4201D3511A00E000218288F4 +:10407000521D8A4202D3012180F89610704710B574 +:1040800090F8821041B990F87C200023062170300E +:10409000ECF778FF002800D0012010BD70B5114466 +:1040A000174D891D8CB2C078A968012806D040B18F +:1040B000182805D191F8120138B109E0A1F8224180 +:1040C00012E5D1F8180184800EE591F8131191B131 +:1040D000FFF765FE80B1A86890F86400FFF75FFE07 +:1040E00050B1A86890F8121190F86420914203D062 +:1040F00090F8130100B90024A868A0F81041F3E477 +:104100008C01002070B58F4C0829207A6CD2DFE832 +:1041100001F004176464276B6B6458B1F4F7D3F8AB +:10412000F5F7FFF80020A072F4F7B4F9BDE870408D +:10413000F4F758BCF5F717F9BDE87040F1F71FBF69 +:10414000DEF7B6FDF5F752F8D4E90001F1F7F3FC1C +:104150002060A07A401CC0B2A072282824D370BD71 +:10416000A07A0025401EC6B2E0683044F4F700FD96 +:1041700010B9E1687F208855A07A272828BF01253B +:10418000DEF796FDA17A01EB4102C2EB81110844F2 +:104190002946F5F755F8A07A282809D2401CC0B264 +:1041A000A072282828BF70BDBDE87040F4F772B92E +:1041B000207A002818BF00F086F8F4F74BFBF4F7DC +:1041C000F7FBF5F7D0F80120E0725F480078DEF7E2 +:1041D0009BFEBDE87040F1F7D2BE002808BF70BD5D +:1041E000BDE8704000F06FB8FFDF70BD10B5554CF2 +:1041F000207A002804BF0C2010BD00202072E0723D +:10420000607AF2F7F8FA607AF2F761FD607AF1F716 +:104210008CFF00280CBF1F20002010BD002270B5AD +:10422000484C06460D46207A68B12272E272607AE6 +:10423000F2F7E1FA607AF2F74AFD607AF1F775FF7A +:10424000002808BFFFDF4048E560067070BD70B50C +:10425000050007D0A5F5E8503C494C3881429CBF89 +:10426000122070BD374CE068002804BF092070BDE3 +:10427000207A00281CBF0C2070BD3548F1F7FAFEEB +:104280006072202804BF1F2070BDF1F76DFF206011 +:104290001DB12946F1F74FFC2060012065602072B6 +:1042A00000F011F8002070BD2649CA7A002A04BF28 +:1042B000002070471E22027000224270CB684360CB +:1042C000CA7201207047F0B585B0F1F74DFF1D4D62 +:1042D0000746394668682C6800EB80004600204697 +:1042E000F2F73AFCB04206DB6868811B3846F1F70A +:1042F00022FC0446286040F2367621463846F2F722 +:104300002BFCB04204DA31463846F1F714FC04467F +:1043100000208DF8000040F6E210039004208DF894 +:10432000050001208DF8040068460294F2F7CAF8EF +:10433000687A6946F2F743F9002808BFFFDF05B045 +:10434000F0BD000074120020AC010020B5EB3C0071 +:10435000054102002DE9F0410C4612490D68114A51 +:10436000114908321160A0F12001312901D3012047 +:104370000CE0412810D040CC0C4F94E80E0007EB25 +:104380008000241F50F8807C3046B84720600548E4 +:10439000001D0560BDE8F081204601F0EBFCF5E76B +:1043A00006207047100502400100000184630200EE +:1043B00010B55548F2F7FAFF00B1FFDF5248401C34 +:1043C000F2F7F4FF002800D0FFDF10BD2DE9F14F18 +:1043D0004E4E82B0D6F800B001274B48F2F7EEFF00 +:1043E000DFF8248120B9002708F10100F2F7FCFF73 +:1043F000474C00254FF0030901206060C4F80051CC +:10440000C4F80451029931602060DFF808A11BE074 +:10441000DAF80000C00617D50E2000F068F8EFF3B8 +:10442000108010F0010072B600D001200090C4F896 +:104430000493D4F8000120B9D4F8040108B901F0BC +:10444000A3FC009800B962B6D4F8000118B9D4F8FA +:1044500004010028DCD0D4F804010028CCD137B105 +:10446000C6F800B008F10100F2F7A8FF11E008F16A +:104470000100F2F7A3FF0028B6D1C4F80893C4F8EE +:104480000451C4F800510E2000F031F81E48F2F734 +:10449000ABFF0020BDE8FE8F2DE9F0438DB00D4647 +:1044A000064600240DF110090DF1200818E000BFA8 +:1044B00004EB4407102255F827106846E9F7BDFFC2 +:1044C00005EB8707102248467968E9F7B6FF68468A +:1044D000FFF77CFF10224146B868E9F7AEFF641C85 +:1044E000B442E5DB0DB00020BDE8F0836EE70028A4 +:1044F00009DB00F01F02012191404009800000F11A +:10450000E020C0F880127047AD01002004E50040B3 +:1045100000E0004010ED00E0B54900200870704751 +:1045200070B5B44D01232B60B34B1C68002CFCD03C +:10453000002407E00E6806601E68002EFCD0001DF7 +:10454000091D641C9442F5D30020286018680028D7 +:10455000FCD070BD70B5A64E0446A84D3078022838 +:1045600000D0FFDFAC4200D3FFDF7169A44801290E +:1045700003D847F23052944201DD03224271491CB4 +:104580007161291BC1609E49707800F02EF90028E6 +:1045900000D1FFDF70BD70B5954C0D466178884243 +:1045A00000D0FFDF954E082D4BD2DFE805F04A041E +:1045B0001E2D4A4A4A382078022800D0FFDF032007 +:1045C0002070A078012801D020B108E0A06801F097 +:1045D00085F904E004F1080007C8FFF7A1FF0520F2 +:1045E0002070BDE87040F1F7CABCF1F7BDFD01468F +:1045F0006068F2F7B1FAB04202D2616902290BD3C6 +:104600000320F2F7FAFD12E0F1F7AEFD0146606813 +:10461000F2F7A2FAB042F3D2BDE870409AE72078F0 +:1046200002280AD0052806D0FFDF04202070BDE84C +:10463000704000F0D0B8022000E00320F2F7DDFD6A +:10464000F3E7FFDF70BD70B50546F1F78DFD684CEF +:1046500060602078012800D0FFDF694901200870E0 +:104660000020087104208D6048716448C8600220F1 +:104670002070607800F0B9F8002800D1FFDF70BD2D +:1046800010B55B4C207838B90220F2F7CCFD18B990 +:104690000320F2F7C8FD08B1112010BD5948F1F709 +:1046A000E9FC6070202804D00120207000206061A7 +:1046B00010BD032010BD2DE9F0471446054600EB60 +:1046C00084000E46A0F1040801F01BF907464FF0E4 +:1046D000805001694F4306EB8401091FB14201D2AA +:1046E000012100E0002189461CB10069B4EB900F64 +:1046F00002D90920BDE8F0872846DCF7A3FD90B970 +:10470000A84510D3BD4205D2B84503D245EA0600FC +:10471000800701D01020EDE73046DCF793FD10B99B +:10472000B9F1000F01D00F20E4E73748374900689E +:10473000884205D0224631462846FFF7F1FE1AE0AE +:10474000FFF79EFF0028D5D1294800218560C0E9E8 +:1047500003648170F2F7D3FD08B12D4801E04AF2FD +:10476000F87060434FF47A7100F2E730B0FBF1F07B +:104770001830FFF768FF0020BCE770B505464FF022 +:10478000805004696C432046DCF75CFD08B10F20C3 +:1047900070BD01F0B6F8A84201D8102070BD1A48CB +:1047A0001A490068884203D0204601F097F810E0CB +:1047B000FFF766FF0028F1D10D4801218460817068 +:1047C000F2F79DFD08B1134800E013481830FFF7D9 +:1047D0003AFF002070BD10B5054C6078F1F7A5FCDC +:1047E00000B9FFDF0020207010BDF1F7E8BE000027 +:1047F000B001002004E5014000E40140105C0C0021 +:1048000084120020974502005C000020BEBAFECA58 +:1048100050280500645E0100A85B01007E4909681C +:104820000160002070477C4908600020704701212A +:104830008A0720B1012804D042F204007047916732 +:1048400000E0D1670020704774490120086042F2FF +:104850000600704708B50423704A1907103230B1BA +:10486000C1F80433106840F0010010600BE01068DC +:1048700020F001001060C1F808330020C1F80801E1 +:10488000674800680090002008BD011F0B2909D867 +:10489000624910310A6822F01E0242EA40000860B4 +:1048A0000020704742F2050070470F2809D85B4985 +:1048B00010310A6822F4706242EA00200860002089 +:1048C000704742F205007047000100F18040C0F8D7 +:1048D000041900207047000100F18040C0F8081959 +:1048E00000207047000100F18040D0F80009086006 +:1048F00000207047012801D907207047494A52F823 +:10490000200002680A43026000207047012801D994 +:1049100007207047434A52F8200002688A43026029 +:1049200000207047012801D9072070473D4A52F8FE +:104930002000006808600020704702003A494FF0EC +:10494000000003D0012A01D0072070470A60704799 +:10495000020036494FF0000003D0012A01D00720A1 +:1049600070470A60704708B54FF40072510510B1E6 +:10497000C1F8042308E0C1F808230020C1F824018D +:1049800027481C3000680090002008BD08B5802230 +:10499000D10510B1C1F8042308E0C1F808230020B4 +:1049A000C1F81C011E48143000680090002008BDAA +:1049B00008B54FF48072910510B1C1F8042308E0E6 +:1049C000C1F808230020C1F82001154818300068FC +:1049D0000090002008BD10493831096801600020AE +:1049E000704770B54FF080450024C5F80841F2F7D4 +:1049F00092FC10B9F2F799FC28B1C5F82441C5F82A +:104A00001C41C5F820414FF0E020802180F80014BF +:104A10000121C0F8001170BD0004004000050040F5 +:104A2000080100404864020078050040800500400D +:104A30006249634B0A6863499A42096801D1C1F32C +:104A400010010160002070475C495D4B0A685D49B8 +:104A5000091D9A4201D1C0F3100008600020704780 +:104A60005649574B0A68574908319A4201D1C0F359 +:104A7000100008600020704730B5504B504D1C6846 +:104A800042F20803AC4202D0142802D203E01128FB +:104A900001D3184630BDC3004B481844C0F8101568 +:104AA000C0F81425002030BD4449454B0A6842F245 +:104AB00009019A4202D0062802D203E0042801D359 +:104AC00008467047404A012142F8301000207047E4 +:104AD0003A493B4B0A6842F209019A4202D0062841 +:104AE00002D203E0042801D308467047364A012168 +:104AF00002EBC00041600020704770B52F4A304E75 +:104B0000314C156842F2090304EB8002B54204D02F +:104B1000062804D2C2F8001807E0042801D318467A +:104B200070BDC1F31000C2F80008002070BD70B560 +:104B3000224A234E244C156842F2090304EB8002FA +:104B4000B54204D0062804D2D2F8000807E00428B1 +:104B500001D3184670BDD2F80008C0F310000860F9 +:104B6000002070BD174910B50831184808601120A1 +:104B7000154A002102EBC003C3F81015C3F8141541 +:104B8000401C1428F6D3002006E0042804D302EBCE +:104B90008003C3F8001807E002EB8003D3F8004855 +:104BA000C4F31004C3F80048401C0628EDD310BD20 +:104BB0000449064808310860704700005C00002086 +:104BC000BEBAFECA00F5014000F001400000FEFF41 +:104BD000814B1B6803B19847BFF34F8F7F48016833 +:104BE0007F4A01F4E06111430160BFF34F8F00BFC2 +:104BF000FDE710B5EFF3108010F0010F72B601D091 +:104C0000012400E0002400F0DDF850B1DCF7BFFB28 +:104C1000F1F755F8F2F793FAF2F7BEFF7149002069 +:104C2000086004B962B6002010BD2DE9F0410C46C1 +:104C30000546EFF3108010F0010F72B601D0012687 +:104C400000E0002600F0BEF820B106B962B60820E8 +:104C5000BDE8F08101F01EF9DCF79DFB0246002063 +:104C600001234709BF0007F1E02700F01F01D7F833 +:104C70000071CF40F9071BD0202803D222FA00F19F +:104C8000C90727D141B2002904DB01F1E02191F8E5 +:104C9000001405E001F00F0101F1E02191F8141D6D +:104CA0004909082916D203FA01F717F0EC0F11D0C1 +:104CB000401C6428D5D3F2F74DFF4B4A4B490020E6 +:104CC000F2F790FF47494A4808602046DCF7C1FAEE +:104CD00060B904E006B962B641F20100B8E73E48A7 +:104CE00004602DB12846DCF701FB18B1102428E040 +:104CF000404D19E02878022802D94FF4805420E072 +:104D000007240028687801D0D8B908E0C8B1202865 +:104D100017D8A878212814D8012812D001E0A87843 +:104D200078B9E8780B280CD8DCF735FB2946F2F780 +:104D3000ECF9F0F783FF00F017FE2846DCF7F4FAF1 +:104D4000044606B962B61CB1FFF753FF20467FE761 +:104D500000207DE710B5044600F034F800B10120D2 +:104D60002070002010BD244908600020704770B5F5 +:104D70000C4622490D682149214E08310E60102849 +:104D800007D011280CD012280FD0132811D00120E1 +:104D900013E0D4E90001FFF748FF354620600DE03D +:104DA000FFF727FF0025206008E02068FFF7D2FF0B +:104DB00003E0114920680860002020600F48001DB2 +:104DC000056070BD07480A490068884201D101208A +:104DD0007047002070470000C80100200CED00E083 +:104DE0000400FA055C0000204814002000000020A8 +:104DF000BEBAFECA50640200040000201005024042 +:104E0000010000017D49C0B20860704700B57C49CF +:104E1000012808BF03200CD0022808BF042008D0B6 +:104E2000042808BF062004D0082816BFFFDF05208D +:104E300000BD086000BD70B505460C46164610461C +:104E4000F3F7D2F8022C08BF4FF47A7105D0012C89 +:104E50000CBF4FF4C86140F6340144183046F3F7F4 +:104E60003CF9204449F6797108444FF47A71B0FB5B +:104E7000F1F0281A70BD70B505460C460846F4F7E7 +:104E80002FFA022C08BF40F24C4105D0012C0CBF78 +:104E900040F634014FF4AF5149F6CA62511A084442 +:104EA0004FF47A7100F2E140B0FBF1F0281A801E55 +:104EB00070BD70B5064615460C460846F4F710FA64 +:104EC000022D08BF4FF47A7105D0012D0CBF4FF4AD +:104ED000C86140F63401022C08BF40F24C4205D0B4 +:104EE000012C0CBF40F634024FF4AF52891A08442B +:104EF00049F6FC6108444FF47A71B0FBF1F0301AC6 +:104F000070BD70B504460E460846F3F76DF80546C9 +:104F10003046F3F7E2F828444AF2AB3108444FF444 +:104F20007A71B0FBF1F0201A801E70BD2DE9F041BE +:104F300007461E460D4614461046082A16BF04288A +:104F40004EF62830F3F750F807EB4701C1EBC711D5 +:104F500000EBC100022D08BF40F24C4105D0012DED +:104F60000CBF40F634014FF4AF5147182846F4F710 +:104F7000B7F9381A4FF47A7100F6B730B0FBF1F593 +:104F80002046F3F7B9F828443044401DBDE8F081CD +:104F900070B5054614460E460846F3F725F805EBAE +:104FA0004502C2EBC512C0EBC2053046F3F795F8D7 +:104FB0002D1A2046082C16BF04284EF62830F3F789 +:104FC00013F828444FF47A7100F6B730B0FBF1F5CE +:104FD0002046F3F791F82844401D70BD0949082880 +:104FE00018BF0428086803BF20F46C5040F4444004 +:104FF00040F0004020F00040086070470C15004071 +:105000001015004040170040F0B585B00C4605462D +:10501000F9F73EF907466E78204603A96A46EEF78F +:1050200002FD81198EB258B1012F02D0032005B0C4 +:10503000F0BD204604AA0399EEF717FC049D01E099 +:10504000022F0FD1ED1C042E0FD32888BDF80010BD +:10505000001D80B2884201D8864202D14FF0000084 +:10506000E5E702D34FF00200E1E74FF00100DEE791 +:10507000FA48C078FF2814BF0120002070472DE9AE +:10508000F041F74C0746160060680D4603D0F9F76B +:1050900069F8A0B121E0F9F765F8D8B96068F9F7C7 +:1050A00061F8D0B915F00C0F17D06068C17811F015 +:1050B0003F0F1CBF007910F0100F0ED00AE0022E37 +:1050C00008D0E6481FB1807DFF2806D002E0C078F6 +:1050D000FF2802D00120BDE8F0810020BDE8F0816A +:1050E0000A4601460120CAE710B5DC4C1D2200210A +:1050F000A01CE9F7CCF97F206077FF202074E070D6 +:10510000A075A08920F060002030A08100202070D0 +:1051100010BD70B5D249486001200870D248D1490D +:10512000002541600570CD4C1D222946A01CE9F7E1 +:10513000AEF97F206077FF202074E070A075A08911 +:1051400020F060002030A081257070BD2DE9F0476F +:10515000C24C06462078C24F4FF0010907F10808FB +:10516000002520B13878D0B998F80000B8B198F887 +:10517000000068B387F80090D8F804103C2239B3D7 +:105180007570301DE9F759F90520307086F80490E4 +:105190003878002818BF88F8005005D015E03D7019 +:1051A000A11C4FF48E72EAE71D220021A01CE9F732 +:1051B0006EF97F206077FF202074E070A075A089D1 +:1051C00020F060002030A08125700120BDE8F0872C +:1051D0000020BDE8F087A148007800280CBF01201E +:1051E000002070470A460146002048E710B510B17C +:1051F000022810D014E09A4C6068F8F7B3FF78B931 +:105200006068C17811F03F0F1CBF007910F0100FDB +:1052100006D1012010BD9148007B10F0080FF8D195 +:10522000002010BD2DE9FF4F81B08C4D8346DDE994 +:105230000F042978DDF838A09846164600291CBFCF +:1052400005B0BDE8F08F8849097800291CBF05B07A +:10525000BDE8F08FE872B4B1012E08BF012708D075 +:10526000022E08BF022704D0042E16BF082E0327E3 +:10527000FFDFEF7385F81E804FF00008784F8CB188 +:10528000022C1DD020E0012E08BF012708D0022EDD +:1052900008BF022704D0042E16BF082E0327FFDF05 +:1052A000AF73E7E77868F8F75DFF68B97868C178A9 +:1052B00011F03F0F1CBF007910F0100F04D110E067 +:1052C000287B10F0080F0CD14FF003017868F8F735 +:1052D000FDFD30B14178090929740088C0F30B0045 +:1052E0006882CDF800807868F8F73CFF0146012815 +:1052F000BDF8000005F102090CBF40F0010020F0EC +:105300000100ADF8000099F80A2012F0020F4ED10A +:10531000022918BF20F0020049D000BFADF80000FC +:1053200010F0020F04D0002908BF40F0080801D097 +:1053300020F00808ADF800807868C17811F03F0FC0 +:105340001CBF007910F0020F0CD0314622464FF0FE +:105350000100FFF794FE002804BF48F00400ADF8F8 +:10536000000006D099F80A00800860F38208ADF8C2 +:10537000008099F80A004109BDF8000061F3461069 +:10538000ADF8000080B20090BDF80000A8810421B3 +:105390007868F8F79BFD002804BFA88920F060001A +:1053A0000CD0B0F80100C004C00C03D007E040F0FE +:1053B0000200B3E7A88920F060004030A8815CB902 +:1053C00016F00C0F08D07868C17811F03F0F1CBFA1 +:1053D000007910F0100F0DD17868C17811F03F0FEF +:1053E00008D0017911F0400F04D00621F8F76EFDC6 +:1053F00000786877314622460020FFF740FE60BB08 +:105400007968C87810F03F0F3FD0087910F0010F8D +:105410003BD0504605F1040905F10308BAF1FF0F2E +:105420000DD04A464146F8F781FA002808BFFFDF51 +:1054300098F8000040F0020088F8000025E00846D7 +:10544000F8F7DBFC88F800007868F8F7ADFC07286F +:105450000CD249467868F8F7B2FC16E094120020A6 +:10546000CC010020D2120020D40100207868F8F787 +:105470009BFC072809D100217868F8F727FD01680F +:10548000C9F800108088A9F804003146224601209E +:10549000FFF7F5FD80BB7868C17811F03F0F2BD086 +:1054A000017911F0020F27D005F1170605F1160852 +:1054B000BBF1020F18BFBBF1030F08D0F8F774FC63 +:1054C00007280AD231467868F8F787FC12E002987C +:1054D000016831608088B0800CE07868F8F764FC7F +:1054E000072807D101217868F8F7F0FC01683160DE +:1054F0008088B08088F800B0002C04BF05B0BDE8FB +:10550000F08F7868F8F72EFE022804BF05B0BDE8DA +:10551000F08F05F11F047868F8F76DFEAB7AC3F1E0 +:10552000FF01884228BF084605D9A98921F06001FA +:1055300001F14001A981C2B203EB04017868F8F7D8 +:1055400062FEA97A0844A87205B0BDE8F08FB048A1 +:105550000178002918BF704701220270007B10F00B +:10556000080F14BF07200620FCF75FBEA848C17BC8 +:10557000002908BF70470122818921F06001403174 +:1055800081810378002B18BF7047027011F0080F5B +:1055900014BF07200620FCF748BE2DE9FF5F9C4F93 +:1055A000DDF838B0914638780E4600281CBF04B0AC +:1055B000BDE8F09FBC1C1D2200212046E8F767FFD4 +:1055C000944D4FF0010A84F800A06868F8F7ECFBEE +:1055D00018B3012826D0022829D0062818BFFFDFDB +:1055E0002AD000BF04F11D016868F8F726FC20727C +:1055F000484604F1020904F10108FF2821D04A4677 +:105600004146F8F793F9002808BFFFDF98F800003B +:1056100040F0020088F8000031E0608940F013009B +:105620006081DFE7608940F015006081E0E7608914 +:1056300040F010006081D5E7608940F01200608181 +:10564000D0E76868F8F7D9FB88F800006868F8F7D1 +:10565000ABFB072804D249466868F8F7B0FB0EE0B8 +:105660006868F8F7A1FB072809D100216868F8F7F6 +:105670002DFC0168C9F800108088A9F8040084F89E +:1056800009B084F80CA000206073FF20A073A17AF9 +:1056900011F0040F08BF20752AD004F1150804F199 +:1056A0001409022E18BF032E09D06868F8F77CFB96 +:1056B00007280CD241466868F8F78FFB16E000987F +:1056C0000168C8F800108088A8F804000EE0686837 +:1056D000F8F76AFB072809D101216868F8F7F6FB9B +:1056E0000168C8F800108088A8F8040089F80060F4 +:1056F0007F20E0760398207787F800A004B006208A +:10570000BDE8F05FFCF791BD2DE9FF5F424F814698 +:105710009A4638788B4600281CBF04B0BDE8F09F3D +:105720003B48017831B1007B10F0100F04BF04B08A +:10573000BDE8F09F1D227C6800212046E8F7A7FE07 +:1057400048464FF00108661C324D84F8008004F191 +:105750000209FF280BD04A463146F8F7E7F800283F +:1057600008BFFFDF307840F0020030701CE068684E +:10577000F8F743FB30706868F8F716FB072804D287 +:1057800049466868F8F71BFB0EE06868F8F70CFB01 +:10579000072809D100216868F8F798FB0168C9F863 +:1057A00000108088A9F8040004F11D016868F8F76A +:1057B00044FB207284F809A060896BF3000040F07C +:1057C0001A00608184F80C8000206073FF20A073B1 +:1057D00020757F20E0760298207787F8008004B05B +:1057E0000720BDE8F05FFCF720BD094A137C834227 +:1057F00005BF508A88420020012070470448007B82 +:10580000C0F3411002280CBF0120002070470000A7 +:1058100094120020CC010020D4010020C2790D2375 +:1058200041B342BB8188012904D94908818004BF62 +:10583000012282800168012918BF002930D0016847 +:105840006FEA0101C1EBC10202EB011281796FEA3B +:10585000010101EB8103C3EB811111444FEA914235 +:1058600001608188B2FBF1F301FB132181714FF0DC +:10587000010102E01AB14FF00001C1717047818847 +:10588000FF2908D24FF6FF7202EA41018180FF2909 +:1058900084BFFF2282800168012918BF0029CED170 +:1058A0000360CCE7817931B1491E11F0FF018171AC +:1058B0001CBF002070470120704710B50121C17145 +:1058C0008171818004460421F1F7E8FD002818BFAA +:1058D00010BD2068401C206010BD00000B4A022152 +:1058E00011600B490B68002BFCD0084B1B1D186086 +:1058F00008680028FCD00020106008680028FCD050 +:1059000070474FF0805040697047000004E5014047 +:1059100000E4014002000B464FF00000014620D099 +:10592000012A04D0022A04D0032A0DD103E0012069 +:1059300002E0022015E00320072B05D2DFE803F088 +:105940000406080A0C0E100007207047012108E029 +:10595000022106E0032104E0042102E0052100E029 +:105960000621F0F7A4BB0000E24805218170002168 +:10597000017041707047E0490A78012A05D0CA6871 +:105980001044C8604038F1F7B4B88A6810448860A1 +:10599000F8E7002819D00378D849D94A13B1012B68 +:1059A0000ED011E00379012B00D06BB943790BB114 +:1059B000012B09D18368643B8B4205D2C0680EE09D +:1059C0000379012B02D00BB10020704743790BB152 +:1059D000012BF9D1C368643B8B42F5D280689042B9 +:1059E000F2D801207047C44901220A70027972B1CD +:1059F00000220A71427962B104224A7182685232ED +:105A00008A60C068C860BB49022088707047032262 +:105A1000EFE70322F1E770B5B74D04460020287088 +:105A2000207988B100202871607978B10420B14EC6 +:105A30006871A168F068F0F77EF8A860E0685230FD +:105A4000E8600320B07070BD0120ECE70320EEE7B2 +:105A50002DE9F04105460226F0F777FF006800B116 +:105A6000FFDFA44C01273DB12878B8B1012805D04B +:105A7000022811D0032814D027710DE06868C828C7 +:105A800008D30421F1F79BF820B16868FFF773FF92 +:105A9000012603E0002601E000F014F93046BDE8DD +:105AA000F08120780028F7D16868FFF772FF00289E +:105AB000E2D06868017879B1A078042800D0FFDFCF +:105AC00001216868FFF7A7FF8B49E07800F003F930 +:105AD0000028E1D1FFDFDFE7FFF785FF6770DBE735 +:105AE0002DE9F041834C0F46E178884200D0FFDF7A +:105AF00000250126082F7DD2DFE807F0040B2828B7 +:105B00003D434F57A0780328C9D00228C7D0FFDFF4 +:105B1000C5E7A078032802D0022800D0FFDF0420C8 +:105B2000A07025712078B8BB0020FFF724FF7248D1 +:105B30000178012906D08068E06000F0EDF820616E +:105B4000002023E0E078F0F734FCF5E7A0780328A4 +:105B500002D0022800D0FFDF207880BB022F08D0BF +:105B60005FF00500F1F749FBA078032840D0A5704D +:105B700095E70420F6E7A078042800D0FFDF022094 +:105B800004E0A078042800D0FFDF0120A168884746 +:105B9000FFF75EFF054633E003E0A078042800D05D +:105BA000FFDFBDE8F04100F08DB8A078042804D0F4 +:105BB000617809B1022800D0FFDF207818B1BDE874 +:105BC000F04100F08AB8207920B10620F1F715FBEA +:105BD00025710DE0607840B14749E07800F07BF82E +:105BE00000B9FFDF65705AE704E00720F1F705FB15 +:105BF000A67054E7FFDF52E73DB1012D03D0FFDF70 +:105C0000022DF9D14BE70420C0E70320BEE770B5B1 +:105C1000050004D0374CA078052806D101E01020FB +:105C200070BD0820F1F7FFFA08B1112070BD3548AA +:105C3000F0F720FAE070202806D00121F1F7DCF817 +:105C40000020A560A07070BD032070BD294810B56C +:105C5000017809B1112010BD8178052906D00129EC +:105C600006D029B101210170002010BD0F2010BD08 +:105C700000F033F8F8E770B51E4C0546A07808B17F +:105C8000012809D155B12846FFF783FE40B1287895 +:105C900040B1A078012809D00F2070BD102070BD40 +:105CA000072070BD2846FFF79EFE03E0002128462E +:105CB000FFF7B1FE1049E07800F00DF800B9FFDF02 +:105CC000002070BD0B4810B5006900F01DF8BDE85C +:105CD0001040F0F754B9F0F772BC064810B5C07820 +:105CE000F0F723FA00B9FFDF0820F1F786FABDE8E4 +:105CF000104039E6DC010020B41300203D8601008D +:105D0000FF1FA107E15A02000C490A6848F202137A +:105D10009A4302430A607047084A116848F2021326 +:105D200001EA03009943116070470246044B1020BA +:105D30001344FC2B01D8116000207047C8060240B4 +:105D40000018FEBF1EF0040F0CBFEFF30880EFF346 +:105D50000980014A10470000FF7B010001B41EB416 +:105D600000B5F1F76DFC01B40198864601BC01B0A5 +:105D70001EBD00008269034981614FF0010010449B +:105D8000704700005D5D02000FF20C0000F10000A2 +:105D9000694641F8080C20BF70470000FEDF184933 +:105DA0000978F9B90420714608421BD10699154AB1 +:105DB000914217DC0699022914DB02394878DF2862 +:105DC00010D10878FE2807D0FF280BD14FF0010032 +:105DD0004FF000020C4B184741F201000099019A64 +:105DE000094B1847094B002B02D01B68DB6818478A +:105DF0004FF0FF3071464FF00002034B1847000090 +:105E000028ED00E000700200D14B020004000020E9 +:105E1000174818497047FFF7FBFFDBF7CFF900BDC4 +:105E2000154816490968884203D1154A13605B6812 +:105E3000184700BD20BFFDE70F4810490968884298 +:105E400010D1104B18684FF0FF318842F2D080F328 +:105E500008884FF02021884204DD0B4802680321A6 +:105E60000A4302600948804709488047FFDF000075 +:105E7000C8130020C81300200010000000000020FC +:105E8000040000200070020014090040B92F000037 +:105E9000215E0200F0B44046494652465B460FB4CC +:105EA00002A0013001B50648004700BF01BC86468C +:105EB0000FBC8046894692469B46F0BC7047000066 +:105EC0000911000004207146084202D0EFF3098155 +:105ED00001E0EFF30881886902380078102813DBAD +:105EE00020280FDB2C280BDB0A4A12680A4B9A4247 +:105EF00003D1602804DB094A10470220086070477C +:105F0000074A1047074A1047074A12682C3212689E +:105F1000104700005C000020BEBAFECA9B130000C0 +:105F2000554302006F4D0200040000200D4B0E4946 +:105F300008470E4B0C4908470D4B0B4908470D4BC2 +:105F4000094908470C4B084908470C4B06490847C4 +:105F50000B4B054908470B4B034908470A4B0249BD +:105F60000847000041BF000079C10000792D000002 +:105F7000F32B0000812B0000012E0000B71300005E +:105F80003F2900007D2F0000455D020000210160D7 +:105F90004160017270470A6802600B7903717047B3 +:105FA00089970000FF9800005B9A0000C59A0000E6 +:105FB000FF9A0000339B0000659B00009D9B000042 +:105FC0003D9C00007D980000859A0000331200007F +:105FD0000744000053440000B94400004745000056 +:105FE0006146000037470000694700004148000053 +:105FF000DB4800002F490000154A0000354A000028 +:10600000AD160000D1160000F11500004D1600007D +:10601000031700009717000003610000C36200002F +:10602000A1660000BB67000043680000C168000073 +:10603000256900004D6A00001D6B0000896B00009F +:10604000574A00005D4A0000674A0000CF4A00003E +:10605000FB4A0000B74C0000E14C0000194D000065 +:10606000834D00006D4E0000834E00007744000019 +:10607000974E0000B94E0000FF4E000033120000A2 +:10608000331200003312000033120000C12500005B +:1060900047260000632600007F2600000D28000030 +:1060A000A9260000B3260000F526000017270000EF +:1060B000F3270000352800003312000033120000DF +:1060C00097840000B7840000B9840000FD840000BC +:1060D0002B8500001B860000A7860000BB86000001 +:1060E000098700001F880000C1890000E98A0000BC +:1060F0003D740000018B00003312000033120000D9 +:10610000EBB700004DB90000A7B9000021BA0000AC +:10611000CDBA0000010000000000000010011001D5 +:106120003A0200001A020000020004050600000006 +:1061300007111102FFFFFFFF0000FFFFF3B3000094 +:10614000273D0000532100008774000001900000EB +:1061500000000000BF9200009B920000AD92000082 +:10616000000002000000000000020000000000002B +:1061700000010000000000004382000023820000B4 +:10618000918200002D250000EF2400000F25000063 +:10619000DBAA000007AB00000FAD0000FD590000B6 +:1061A000B182000000000000E18200007B250000B9 +:1061B000000000000000000000000000F1AB000043 +:1061C00000000000915A00000300000001555555E1 +:1061D000D6BE898E00006606660C661200000A03B1 +:1061E000AE055208000056044608360CC7FD0000F4 +:1061F0005BFF0000A1FB0000C3FD0000A7A8010099 +:106200009B040100AAAED7AB15412010000000008E +:10621000900A0000900A00007B5700007B570000A6 +:10622000E143000053B200000B7700006320000040 +:10623000BD3A020063BD0100BD570000BD5700001C +:1062400005440000E5B2000093770000D72000006D +:10625000EB3A020079BD0100700170014000380086 +:106260005C0024006801200200000300656C746279 +:10627000000000000000000000000000000000001E +:106280008700000000000000000000000000000087 +:10629000BE83605ADB0B376038A5F5AA9183886C02 +:1062A000010000007746010049550100000000018F +:1062B0000206030405000000070000FB349B5F801A +:1062C000000080001000000000000000000000003E +:1062D000060000000A000000320000007300000009 +:1062E000B4000000F401FA00960064004B00320094 +:1062F0001E0014000A000500020001000049000011 +:1063000000000000D7CF0100E9D1010025D1010034 +:10631000EBCF0100000000008FD40100000101025A +:10632000010202030C0802170D0101020909010113 +:1063300006020918180301010909030305000000FA +:10634000555555252627D6BE898E00002BFB01000A +:1063500003F7010049FA01003FF20100BB220200ED +:10636000B7FB0100F401FA00960064004B00320014 +:106370001E0014000A00050002000100254900006B +:1063800000000000314A0200494A0200614A02004E +:10639000794A0200A94A0200D14A0200FB4A0200DF +:1063A0002F4B02007B470200B7460200A1430200C8 +:1063B0002B5D0200AD730100BD730100E9730100A4 +:1063C000BB740100C3740100D57401002F480200A2 +:1063D000494802001D4802002748020055480200B3 +:1063E0008B480200AB480200C9480200D7480200AF +:1063F000E5480200F54802000D4902002549020067 +:106400003B4902005149020000000000DFBC0000CF +:1064100035BD00004BBD000015590200CD43020000 +:10642000994402000F5C02004D5C0200775C0200A0 +:106430009D710100FD760100674902008D4902004F +:10644000B1490200D74902001C0500402005004068 +:10645000001002007464020008000020E80100003F +:106460004411000098640200F0010020D8110000DF +:10647000A011000001181348140244200B440C061C +:106480004813770B1B2034041ABA0401A40213101A +:08649000327F0B744411C000BF +:02000004000FEB +:1040000000000420CDB20F00F5B20F00F7B20F0090 +:10401000F9B20F00FBB20F00FDB20F00000000006C +:10402000000000000000000000000000C1450F007B +:1040300001B30F000000000003B30F00214D0F007B +:10404000354E0F0007B30F0007B30F0007B30F0083 +:1040500007B30F0007B30F0007B30F0007B30F003C +:1040600007B30F0007B30F0007B30F0007B30F002C +:1040700007B30F0007B30F0007B30F0007B30F001C +:1040800007B30F0085720F0007B30F0007B30F00CF +:1040900041730F0007B30F00814B0F0007B30F00F0 +:1040A00007B30F0007B30F0007B30F0007B30F00EC +:1040B00007B30F0007B30F0000000000000000006E +:1040C00007B30F0007B30F0007B30F0007B30F00CC +:1040D00007B30F0007B30F0007B30F0005850F00EC +:1040E00007B30F0007B30F0007B30F000000000075 +:1040F0000000000007B30F000000000007B30F002E +:1041000000000000000000000000000000000000AF +:10411000000000000000000000000000000000009F +:10412000000000000000000000000000000000008F +:10413000000000000000000000000000000000007F +:10414000000000000000000000000000000000006F +:10415000000000000000000000000000000000005F +:10416000000000000000000000000000000000004F +:10417000000000000000000000000000000000003F +:10418000000000000000000000000000000000002F +:10419000000000000000000000000000000000001F +:1041A000000000000000000000000000000000000F +:1041B00000000000000000000000000000000000FF +:1041C00000000000000000000000000000000000EF +:1041D00000000000000000000000000000000000DF +:1041E00000000000000000000000000000000000CF +:1041F00000000000000000000000000000000000BF +:104200000348044B834202D0034B03B11847704765 +:10421000C8860020C8860020000000000548064926 +:104220000B1AD90F01EBA301491002D0034B03B1C4 +:1042300018477047C8860020C8860020000000008C +:1042400010B5064C237843B9FFF7DAFF044B13B1DE +:104250000448AFF300800123237010BDC8860020FE +:10426000000000005CBD0F0008B5044B1BB1044901 +:104270000448AFF30080BDE80840CFE7000000002D +:10428000CC8600205CBD0F00A3F5803A704700BFCC +:10429000154B002B08BF114B9D46FFF7F5FF002182 +:1042A0008B460F461148124A121A00F075F80C4B53 +:1042B000002B00D098470B4B002B00D098470020D4 +:1042C000002104000D000B4800F016F800F040F843 +:1042D0002000290000F074FA00F014F80000080033 +:1042E000000000000000000000000420C88600203C +:1042F000A4CE002025430F00002301461A4618468D +:1043000000F09CB808B50021044600F0CBF8044B3F +:104310001868C36B03B19847204600F029F900BF25 +:1043200058BB0F0038B5084B084D5B1B9C1007D0DD +:10433000043B1D44013C55F804399847002CF9D141 +:10434000BDE8384007F002BCC8860020C4860020C3 +:1043500070B50D4E0D4D761BB61006D0002455F8E5 +:10436000043B01349847A642F9D1094E094D761B0A +:1043700007F0E6FBB61006D0002455F8043B0134E4 +:104380009847A642F9D170BDBC860020BC860020AB +:10439000C4860020BC860020830730B548D0541E58 +:1043A000002A3FD0CAB2034601E0013C3AD303F8E9 +:1043B000012B9D07F9D1032C2DD9CDB245EA052556 +:1043C0000F2C45EA054536D9A4F1100222F00F0C56 +:1043D00003F1200EE6444FEA121C03F1100242E9F9 +:1043E000045542E9025510327245F8D10CF1010230 +:1043F00014F00C0F03EB021204F00F0C13D0ACF10D +:10440000040323F003030433134442F8045B934290 +:10441000FBD10CF003042CB1CAB21C4403F8012BED +:104420009C42FBD130BD64461346002CF4D1F9E721 +:1044300003461446BFE71A46A446E0E770B4184C9A +:104440002568D5F848411CB365681F2D25DC38B9AF +:10445000AB1C0135656044F82310002070BC704728 +:1044600004EB850C0228CCF88820D4F888614FF042 +:10447000010202FA05F246EA0206C4F88861CCF8A5 +:104480000831E5D1D4F88C311343C4F88C31DFE71F +:1044900005F5A674C5F84841D6E74FF0FF30DDE7D3 +:1044A00058BB0F002DE9F84F2B4B1F68D7F8486118 +:1044B0002DED028BC6B108EE100A8B464FF00108B5 +:1044C0004FF000097468651E0ED4013406EB8404B5 +:1044D000BBF1000F0CD0D4F800315B4508D0013D92 +:1044E0006B1CA4F10404F3D1BDEC028BBDE8F88F82 +:1044F00073682268013BAB420CBF7560C4F8009042 +:10450000002AECD0D6F88801D6F804A008FA05F104 +:1045100001420BD190477268524513D1D7F8483108 +:10452000B342DCD01E46002ECCD1DDE7D6F88C019C +:1045300001420CD1D4F8801018EE100A904772682E +:104540005245EBD0D7F84861002EBBD1CCE7D4F868 +:1045500080009047DFE700BF58BB0F00024B13B14C +:104560000248FFF7C9BE70470000000025430F0056 +:10457000FEE700BF38B50C46E8B90968C9B10F4D70 +:10458000A9420BD06B1A3B2B11D93C22284606F0CE +:10459000EDFE03E0CA5CEA54013BFBD2074800226F +:1045A0003C2103F0D3F90023A887236038BD3D23C5 +:1045B000F2E70E23F9E70123F7E700BF807F002031 +:1045C000074A6FF002039E4502D1EFF3098101E033 +:1045D000EFF308818869A0F102000078104700BF5E +:1045E00075450F0038B50446A8B10D4D00223C2199 +:1045F000284603F0ABF9AB8F83420ED12A4605F172 +:104600003C0152F8040B44F8040B8A42F9D10133FF +:10461000AB87002038BD0E20FCE70B20FAE700BF77 +:10462000807F00200B2970B50446154608462FD917 +:104630002389053304EB43012044431ADAB2012AEB +:1046400026D9814224D8134806F090FE2388522BA5 +:1046500006D1AB0711D062884CF668639A420CD041 +:104660000F2014E034F8022B824204D0AE89964227 +:1046700003F1010308D1002009E0218900230A3455 +:104680004FF6FE704FF440559942EBD80B2070BDA9 +:104690000920FCE7E486002008B5002203F056F963 +:1046A000034B1B88834214BF0B20002008BD00BFB2 +:1046B000E486002038B50C4C21684B1C054612D00E +:1046C0000A484FF4805206F01FFE48B115B1206829 +:1046D00000F00CFC054920684FF4806200F026FCD5 +:1046E0004FF0FF33236038BD30840020F086002077 +:1046F0002DE9F041DFF84480044624F47F65184634 +:10470000D8F8003025F00F05AB420E46174609D009 +:10471000FFF7D0FF0848C8F800504FF480522946F0 +:1047200006F024FE0448C4F30B043A463146204404 +:10473000BDE8F04106F01ABEF0860020308400206B +:10474000BFF34F8F0549064BCA6802F4E06213437A +:10475000CB60BFF34F8F00BFFDE700BF00ED00E06F +:104760000400FA054BDF704710DF704711DF704718 +:1047700013DF704718DF704760DF704769DF7047ED +:1047800061DF70471FB50023CDE90133039368460D +:1047900002230093FFF7EEFF05B05DF804FB08B5B8 +:1047A0004FF0E023D3F8F03DDB0700D500BEFFF764 +:1047B000C7FF0000014B1878704700BFF19600203A +:1047C0002DE9FF484C4B40F60212C3F8402500F09B +:1047D00005FA00F021FE002000F0AAFA00F09CFE8D +:1047E00048B1052000F0A4FA00F0A8FE00F0CCFECD +:1047F000062000F09DFA4FF08043DFF81081D3F8D7 +:104800001C55B12D0CBF0123002388F800304AD07D +:10481000A5F1A8014C424C41384EDFF8F49004F069 +:10482000010333704FF08043D3F8007407F00107A1 +:10483000002C3BD14E2D38D0572D36D0304B1B6835 +:104840001A68304B9A4200D17FBB6D2D2ED01220BA +:1048500000F0B0F9044633789BBB122000F0AAF9AF +:1048600010B1122000F0A6F900F00100307000F045 +:1048700005FDDFF8A0B0824630B12CB9204B1B6893 +:104880001A68214B9A4257D04FF440535A684A4510 +:104890000ABF9B684FF4905303F500731B685B4598 +:1048A0003AD1012448E00124B6E701244FF08043C7 +:1048B00000226D2DC3F81C2500F0E480002CCAD125 +:1048C000C5E70120D0E7544636E03C4634E04FF4DB +:1048D00080030B6071E0022000F02AFAA5F14E037C +:1048E0005842584103F012FEAEE000221146C1E0EA +:1048F00003F05CFEC6E000BF00A00040F19600207F +:1049000034840020D51A5A007E67E54EF2960020C6 +:10491000DBE5B1517CB0EE87002CC2D1BAF1000FBB +:10492000D1D0002FD1D0654B654A1B6865481A600D +:10493000654B43F0010398474FF440535A684A458A +:1049400008BF9B685D4A0CBF03F500734FF490539A +:1049500012681B685B450CBF5C4B002313601CB9DD +:10496000BAF1000F40F08E803378002BB3D00820CE +:1049700000F0DEF998F800300BB9FFF703FF0123D0 +:104980004FF4742088F80030FFF7F2FE504B514985 +:104990001868019001A8FFF7E7FE4F4991F8163318 +:1049A0005A09EC231341DA0707D54C4B9A68002AC1 +:1049B0008DD01A6842F480021A600C22484B029390 +:1049C00000210DEB0200FFF7E7FC40F20113029A11 +:1049D000039303A94020FFF7D1FE0C2200210DEB29 +:1049E0000200FFF7D9FC9DF80C30029A43F0010356 +:1049F00003A9A0208DF80C30FFF7C0FE0C22002187 +:104A00000DEB0200FFF7C8FC01241723029AADF852 +:104A10000E3003A923208DF80C40FFF7AFFE0C22C7 +:104A200000210DEB0200FFF7B7FC0623029A8DF878 +:104A30000C4003A920208DF80E40ADF81030FFF790 +:104A40009DFE02A8FFF798FE4FF4405330785A6855 +:104A50004A450ABF9B684FF4905303F500731B68E7 +:104A60005B4504D0572D02D04E2D7FF43EAF01227E +:104A700040F6B83100F0E8FC3378002B3FF438AF53 +:104A8000FFF774FE00F0EEF800F0F8FBA0B100F0C4 +:104A900043FD88B94FF440535B684B4506D198F805 +:104AA00000300BB9FFF76EFEFFF760FE034B1B688B +:104AB00000221A6000F004FDFFF742FE348400205B +:104AC000D51A5A000048E80160BB0F007E67E54E2A +:104AD0005CBB0F009F470F0000E100E0409D0020FD +:104AE0000080002010B58EB03423ADF802300DF1F7 +:104AF0000201002301A8ADF80430FFF741FE04468F +:104B000040B9BDF80430102B07D0112B0CD001A8F0 +:104B100000F0CCFF20460EB010BD054B01221A70EC +:104B2000072000F005F9F2E7014B18700820F8E7BC +:104B3000F096002013B5002301A80193FFF712FEA1 +:104B4000044660B9019802F053FA019B0A2B09D080 +:104B5000092B09D00B2B02D1012004F09BFB20462E +:104B600002B010BD2046F8E70220F6E708B5FFF7CF +:104B7000B9FF0528FBD1FFF7DDFF0528F7D108BDF8 +:104B80000021024A084602F003BE00BF6D4B0F0031 +:104B90001F2884BF00F01F00044B054A98BF4FF048 +:104BA000A04300F5E07043F8202070470003005058 +:104BB0000C0003001F288ABF064B4FF0A04300F0F3 +:104BC0001F00D3F8103523FA00F0C04300F00100B5 +:104BD000704700BF000300507047000008B54FF059 +:104BE000804301220021DA601220C3F818159A6070 +:104BF000FFF7CEFF1220FFF7CBFF154B4FF4C85045 +:104C000043F001039847FFF7E7FF124A1E210820EF +:104C100002F09AFD08B102F051FE02F0FBFC0E49D1 +:104C20000E4BE02081F823001B684FF47A72B3FB2F +:104C3000F2F3013BB3F1807F08D24FF0E0225361E1 +:104C4000002381F8230093610723136108BD00BF8F +:104C500070BB0F00F496002000ED00E038840020C7 +:104C6000704700004FF0E0224FF40031002310B5F0 +:104C70001361C2F8801102F1C04202F540524FF4B4 +:104C80008031C2F84813C2F80813012151609160C5 +:104C90004FF080420A4CD16002201F2B1A46C6BF3B +:104CA00003F01F0221464FF0A04102F5E0720133EC +:104CB000302B41F82200F0D1FFF7D2FF10BD00BF2A +:104CC00000030050074B23F81010074B002282B05E +:104CD000C3F81021D3F810210192019A01229A60A1 +:104CE00002B07047E898002000C001400A4A0B4B10 +:104CF00011681B68B1FBF3F203FB1211B1EB530F08 +:104D00004FEA530288BF591A4F2359430020B1FB81 +:104D1000F2F189B2FFF7D6BFE4980020F0980020A6 +:104D2000024A136801331360FFF7E0BFE4980020E4 +:104D30001B490A68082823D8DFE800F0130513226E +:104D4000221A1E242A00174B40F6B83018604FF480 +:104D50007F4303F0103323F080539A4218BF0B6057 +:104D60007047104B4FF4967018604FF47F03F0E7D4 +:104D70000C4B64221A6070470A4B40F6B83018603A +:104D80001346E6E7074B40F6B8301860FF23E0E72C +:104D9000044B4FF4967018604FF0FF13D9E700BF33 +:104DA000F4980020F098002000F1804382B01A6847 +:104DB000002A14BF0120002004D000221A601B68C2 +:104DC0000193019B02B070470F4A1378D3B903785F +:104DD0004FF08041C3F34003C1F88035037803F0FE +:104DE0000103C1F87835094B1968C90706D4E021D9 +:104DF00083F800130121C3F88011196001230448CE +:104E000013707047034870470499002000E100E0E8 +:104E10000000AD0B0C00AD0B014B02681A6070472F +:104E2000009900204FF080434FF46072C3F80423D0 +:104E30007047000010B54FF08043D3F80443620779 +:104E400007D54FF48470FFF7AFFF10B11E4B1B68FE +:104E50009847A30608D54FF48A70FFF7A5FF18B14D +:104E60001A4B00201B689847600608D54FF48C70D9 +:104E7000FFF79AFF18B1154B01201B6898472106D0 +:104E800008D54FF48E70FFF78FFF18B1104B00203C +:104E90001B689847E20508D54FF49070FFF784FF30 +:104EA00018B10B4B01201B689847A3050AD54FF496 +:104EB0009270FFF779FF28B1054BBDE810401B68E1 +:104EC0000220184710BD00BFF8980020FC98002071 +:104ED00000990020044AD2F80034DB07FBD50160BA +:104EE000BFF35F8F704700BF00E001404FF0805379 +:104EF0001A69B0FBF2F302FB130373B9084B0222E9 +:104F0000C3F80425C3F80805D3F80024D207FBD55D +:104F100000220448C3F8042570470348704700BFC7 +:104F200000E001400000AD0B0A00AD0BF8B50B4BE3 +:104F30001546012206460F46C3F804250024A54263 +:104F400004D1064B0022C3F80425F8BD57F82410FD +:104F500006EB8400FFF7BEFF0134F0E700E00140FC +:104F6000BFF34F8F0549064BCA6802F4E062134352 +:104F7000CB60BFF34F8F00BFFDE700BF00ED00E047 +:104F80000400FA054FF08053D3F83021082A06D1E7 +:104F9000D3F83431032B02D8024AD05C704700208A +:104FA000704700BF76BB0F0008B54FF08053D3F8B1 +:104FB0003021082A4ED14FF080420021C2F80C1156 +:104FC000C2F81011C2F8381502F54042D3F80414A3 +:104FD000C2F82015D3F80814C2F82415D3F80C141D +:104FE000C2F82815D3F81014C2F82C15D3F81414ED +:104FF000C2F83015D3F81814C2F83415D3F81C14BD +:10500000C2F84015D3F82014C2F84415D3F824147C +:10501000C2F84815D3F82814C2F84C15D3F82C144C +:10502000C2F85015D3F83014C2F85415D3F834141C +:10503000C2F86015D3F83814C2F86415D3F83C14DC +:10504000C2F86815D3F84014C2F86C15D3F844348C +:10505000C2F87035FFF796FF18B1494B494AC3F8BB +:105060008C26FFF78FFF18B1474BFB22C3F818259A +:10507000FFF788FF70B14FF080414FF08053D1F8B7 +:10508000E42ED3F8583222F00F0203F00F0313433B +:10509000C1F8E43EFFF776FF20B13C4B4FF40072BD +:1050A000C3F840264FF08053D3F83031082B09D194 +:1050B0004FF08043D3F80024D10744BF6FF00102C2 +:1050C000C3F80024324AD2F8883043F47003C2F89F +:1050D0008830BFF34F8FBFF36F8F4FF01023D3F89B +:1050E0000C22D2071DD52B4B0122C3F80425D3F87F +:1050F0000024002AFBD04FF01022D2F80C3223F00B +:105100000103C2F80C32234BD3F80024002AFBD051 +:105110000022C3F80425D3F80024002AFBD0FFF7AF +:105120001FFFD3F80022002A03DBD3F80432002B40 +:1051300022DA184B0122C3F80425D3F80024002AF0 +:10514000FBD04FF010221221C2F80012D3F8002435 +:10515000002AFBD04FF010231222C3F804220D4B7B +:10516000D3F80024002AFBD00022C3F80425D3F88A +:105170000024002AFBD0D2E7074B084A1A6008BD7A +:10518000005000404881030000F0004000900240C1 +:1051900000ED00E000E00140388400200090D003E2 +:1051A00013DF704718DF7047064B1878012803D1CA +:1051B000012904BF0221197012B1104602F07EBB12 +:1051C000704700BF3599002008B5FFF7F3FA88B1A2 +:1051D00011481C2101F098FF08B102F06FFB0F4944 +:1051E0000D4800231C2201F07FFF98B1BDE8084064 +:1051F00002F064BB4FF47F20FFF778FE07220749D7 +:105200004FF47F20FFF792FE054B1A78012A04BF66 +:1052100002221A7008BD00BF2C9900203899002086 +:105220003599002070B5124C124D134ED4F800344D +:105230007BB1C4F80056C4F80456C4F80856C4F844 +:105240000C56C4F81056C4F81456C4F81856C4F8CE +:105250001C5602F005FB05F0F4FF20B104F0DAFD66 +:10526000002005F003FA3378023B022BDED870BD34 +:10527000000001403546526E3599002013B54FF4B9 +:105280004053124A596891420CBF9C684FF48054B5 +:1052900001A800F0A7F92368013302D16368013344 +:1052A00011D0019B1A88012A0DD1588820B1996824 +:1052B0000022204602F04AFB019B5B881B1A5842E1 +:1052C000584102B010BD0020FBE700BFDBE5B15143 +:1052D00084B02DE9F34108AC84E80F009DF820402C +:1052E000BDF8228001A80F4616461D4600F07AF947 +:1052F00054B9384B0122FF21A3F802809D601A8027 +:105300009980354B1A7012E0012C17D1314BBA1924 +:105310002A449A60A5221A80FF229A800C9AA3F848 +:105320000280C3E903765D619A612B4B1C70FFF725 +:105330004BFF02B0BDE8F04104B07047032C0FD121 +:10534000019A244B11881980518892689A60C3E9A8 +:105350000376AA2259809A805D611F4B0122D1E712 +:10536000022C15D1019A1B4B1188A5290AD10022C4 +:105370009A60FF221A60FF229A800022C3E903226A +:105380005A61EAE719805188926859809A60F2E779 +:10539000052C0ED1FFF70EFA40B100F097FD08B1D1 +:1053A00002F08CFA0C4B03221A70C2E700F010FADC +:1053B000F5E7042C08D1074B00229A60FF221A60FF +:1053C000019A92889A80B2E7062CB2D1024B04224D +:1053D000EAE700BF389900203599002000B50C4B52 +:1053E0001B7889B063B90B4B1B786BB905238DF81B +:1053F0000C30079B009303AB0FCBFFF769FF03E073 +:1054000004F000FB0028EED009B05DF804FB00BFFB +:1054100034990020289900201FB50023CDE90233DC +:10542000074B019301F030FE30B906494FF47F235A +:1054300001A84B6001F046FE05B05DF804FB00BF1B +:10544000A9510F002C99002070B505460E460AB1EF +:1054500080F00102154B02F001021A7000F0B0FF5B +:10546000044628B935B100F08BFC0446FFF7DAFE9C +:10547000204670BDBEB10E4B0E4A0F481D70294626 +:1054800002F0F8F84FF400444FF4FA7029464FF454 +:105490007A720023E6FB040106F0D0F92A460146A1 +:1054A000064802F0F9F800F06FF9DEE734990020C1 +:1054B00028990020DD530F007CBB0F0008990020C5 +:1054C0001FB5134B4FF0FF32C3F88020C3F8802183 +:1054D0004FF440530F4A596891420DD19C682046C1 +:1054E000FFF75EFE10B14FF000531C60204604B081 +:1054F000BDE8104000F09AB80023CDE902334FF424 +:10550000805406236846CDE90034FFF74BFEE9E7F7 +:1055100000E100E0DBE5B15107B501A800F062F859 +:10552000019B1A88A52A07D09888A0F1AA0358429F +:10553000584103B05DF804FB0120FAE710B501F013 +:105540005DF9A8B10E4B0F4843F00103984701F0F5 +:10555000BFF808B102F0B2F901F050F908B102F059 +:10556000ADF901F001F9044638B102F0A7F904E001 +:1055700001F01EF904460028E4D1204610BD00BF0A +:1055800080BB0F0000A8610000B589B003AB1422F6 +:1055900000211846FEF700FF02228DF80C200022A1 +:1055A00000920FC8FFF794FEFFF73CFE002009B001 +:1055B0005DF804FB13B5044601A800F013F8019B45 +:1055C0001A8822805A8862809A68A2609A88A2808B +:1055D000DA68E2601A6922615A6962619B69A361B3 +:1055E00002B010BD014B0360704700BF00F00F0018 +:1055F000F0B50346186880F308885868FF2464B241 +:10560000EFF30585002D01D1A64600472546064645 +:1056100021273FBAF0B40024002500260027F0B46B +:10562000F92040B2004700BFF0BD00BFFFF7E0BF68 +:1056300073B500230DF1020101A8ADF8023001930A +:1056400002F0C4FDF8B9019C25785DB3174B93F8BF +:105650003020032A28D00C2606FB00F29958E9B91D +:1056600098189D5093F830200132D2B283F8302040 +:10567000BDF802300E4A9B08013B0434436084604D +:10568000084602F085F8019B33B128B1184602F0B4 +:10569000B9FD08B102F012F902B070BD0130042862 +:1056A000DAD1F0E70720EEE70420ECE75499002078 +:1056B000F9560F00084609B102F000B97047000022 +:1056C00010B50C220B4B504319181A5882B193F89D +:1056D00030208C68013AD2B283F8302000221A5070 +:1056E000C1E90122201F02F08DFD08B102F0E6F8A9 +:1056F000002010BD54990020214B70B50122214E8D +:105700001A7096F8303003B970BD1E4C002523681E +:1057100083B1013B042B07D8DFE803F01C0612031A +:105720002800204600F0DEFEE8B2FFF7C9FF08B10E +:1057300002F0C4F80135042D04F10C04E7D1E0E7D0 +:10574000A3686360204600F073FE0028ECD002F0EE +:10575000B5F8E9E7204600F053FF00F02FFF08B14D +:1057600002F0ACF80520FFF7E3FADDE700F074FF84 +:1057700000F092FFBDE870400620FFF7D9BA00BFE5 +:10578000289900205499002008B50E4B002283F878 +:10579000302004210139C3E901221A6003F10C030E +:1057A000F8D1094800F03EFE02F0A8FC08B102F072 +:1057B00085F8064802F08EFC08B102F07FF8002060 +:1057C00008BD00BF54990020B5560F0031560F0098 +:1057D00008B50020FFF774FF0120FFF771FF0220DA +:1057E000FFF76EFF0320FFF76BFFBDE8084002F0F4 +:1057F000CDBC006870476CDF70476DDF70476EDFAF +:1058000070476FDF704772DF704773DF704774DF78 +:10581000704776DF704777DF70477ADF70477CDF4D +:1058200070477FDF704786DF70478FDF704790DFFC +:105830007047AFDF7047B0DF7047B1DF7047B2DF4E +:105840007047B5DF704764DF704766DF70470C282C +:1058500013D8DFE800F01412121212120912071204 +:10586000120D0B0002207047032070470420704780 +:10587000042914BF06200520704706207047012028 +:10588000704702F01BB810B5044608460321FFF725 +:10589000DEFF0246204601F075F918B1BDE8104060 +:1058A00002F00CB810BD00000346032B10B50846EB +:1058B000144620D0042B23D169B1124B18884FF61F +:1058C000FF7398421CD01321FFF7A3FFC0B1BDE8BE +:1058D000104001F0F3BF104602F094F808B101F057 +:1058E000EDFF094B1B689C420AD1012203210748A6 +:1058F00001F048F9EAE70121FFF7A9FF0246F6E7C0 +:1059000010BD00BF3E840020489A0020F899002076 +:10591000F8B50A4DAB889E181D2E14460DDC2F6875 +:10592000FE1802F1010C07F803C07070B01C05F0FE +:105930001DFDAB8802331A19AA80F8BDA899002072 +:10594000F0B54E4E317895B0002940F092804C4C25 +:10595000019110222046FEF71FFD4A4B019923605A +:1059600018220EA8FEF718FD01238DF838302823E1 +:105970001093454B1B78002B7DD0444B04AC03F1B6 +:10598000100518685968224603C20833AB42144612 +:10599000F7D13F4C10220DEB0201E01D05F0B4FCE5 +:1059A000002868D03B4801210460FFF728FF08B1B8 +:1059B00001F084FF384B08AA03F1100C1746186851 +:1059C0005968154603C5083363452A46F7D1206850 +:1059D0000C903248A288A379ADF8342007600122E8 +:1059E00000218DF83630FFF70CFF08B101F066FF9B +:1059F00003238DF84C3004238DF80E3041F23053E0 +:105A0000ADF81030264B08AA9B798DF812300DF1B5 +:105A10000F0104A8FFF717FF012210460DF10E0138 +:105A2000FFF776FF1F4805F04BFE1E49C2B2092062 +:105A3000FFF76EFF102208A90620FFF769FF104943 +:105A400019480EAAFFF7DFFE08B101F037FF164C28 +:105A5000042221780120FFF7DEFE08B101F02EFFBD +:105A600020780121FFF7D1FE08B101F027FF0123C3 +:105A7000337015B0F0BD0623BEE700BF2C9A00209E +:105A8000A899002088990020F4990020A9BB0F0054 +:105A9000B8990020449A0020BF990020289A00203D +:105AA000F899002086BB0F003C840020F0B5044626 +:105AB0000146B1B0A84801F099F82388262B3BD8BD +:105AC0000F2B04D8012B00F0CC8031B0F0BD103B7F +:105AD000162BFAD801A252F823F000BF655B0F0025 +:105AE000735B0F00CB5A0F00A55B0F009F5C0F008C +:105AF000CB5A0F00CB5A0F00CB5A0F00CB5A0F00D6 +:105B0000CB5A0F00BB5C0F00CB5A0F00CB5A0F00D3 +:105B1000CB5A0F00CB5A0F00CB5A0F00CB5A0F00B5 +:105B2000315D0F00CB5A0F00235D0F00CB5A0F00E1 +:105B3000CB5A0F00255C0F00513B9AB2052AC4D8FE +:105B4000052BC2D801A252F823F000BF6F5C0F00F2 +:105B5000BB5C0F00CB5A0F00CB5A0F00475D0F0004 +:105B60000B5C0F007D4BA2881A807D4B00221A70BF +:105B7000ABE78023794CADF824307A4B2088322271 +:105B80001A6010A9012309AAFFF759FE08B101F014 +:105B900095FE754B1B780BB9FFF7D2FE4FF6FF73DE +:105BA000238092E7714B03AC9A79186899888DF835 +:105BB00022200790DA1DADF8201017332646106812 +:105BC0005168254603C508329A422C46F7D1684BE6 +:105BD00009AA03F11807154618685968144603C442 +:105BE0000833BB422246F7D1186820605B48614AFF +:105BF000008810AB8521CDE91456FFF712FE00286E +:105C00003FF463AF01F05AFE5FE7A379002B7FF406 +:105C10005CAF524B13211888FFF7FBFD00283FF4BF +:105C200054AF79E0237A012B7FF44FAF4C4B002225 +:105C30001A704C4B19680139196069B910AB1422FC +:105C40001846FEF7A9FB05228DF84020149A009211 +:105C50000FC8FFF73DFB38E731B0BDE8F040FFF774 +:105C60006FBE3E4B00211888FFF7EFFDD6E7A37902 +:105C7000002B3FF42AAFA27B043A022A3FF625AF5D +:105C8000022B18BF01238DF840304FF4C173ADF8DB +:105C90004430324B10A91888FFF7CDFDAFE7334AE7 +:105CA000258A508D02F118010023854218BF19463C +:105CB0000732A088FFF7B7FDB0E7284B1C884FF6E6 +:105CC000FF75AC4227D02C4B1B78F3B12B49012335 +:105CD00008222046FFF7B1FDF0B902460146022333 +:105CE0002046FFF7AAFDB8B92A460C212046FFF747 +:105CF000A0FDA0F54053023B012B7FF6E6AE08283D +:105D00003FF4E3AE112889D1DFE61A461946204652 +:105D1000FFF793FD82E7082031B0BDE8F04001F0C5 +:105D2000CDBD0E4B002218881146FFF780FD75E7A8 +:105D300000238DF840308DF84130084B10A91888A9 +:105D4000FFF773FDC1E6E188044B1729188828BFC7 +:105D50001721FFF776FD61E7F89900203E840020C7 +:105D60002C9A0020408400203F9A0020B8990020FF +:105D7000D09900203A9A0020F4990020EC99002054 +:105D800030B5464A464800231370464A95B0137012 +:105D900000F048FB01F0F6FD0446002868D14248B7 +:105DA000FEF720FC002866D1404B01221A70112317 +:105DB0003F488DF8043005F083FC3D4982B201A8CC +:105DC000FFF72DFD08B101F079FD0822002104A89C +:105DD000FEF7E2FA0823ADF810301823ADF81230C0 +:105DE0000023ADF8143004A84FF4C873ADF8163092 +:105DF000FFF713FD08B101F061FD00210C2201A89D +:105E0000FEF7CAFA0823ADF804302A4B02932A4859 +:105E10002A4B039301A900F02FFD08B101F04EFDBC +:105E2000274D4022002104A8FEF7B6FA284605F0C7 +:105E300047FCADF810002846059505F041FC079594 +:105E4000204DADF81800284605F03AFC1123ADF8B6 +:105E5000300004A8ADF84C300D9500F0DBFF1A4B74 +:105E600030221A7007225A7010229A70FFF768FDCC +:105E7000204615B030BD04A8FFF7BFFC08B101F003 +:105E80001DFD9DF8113004A801338DF81130FFF786 +:105E9000B2FC00288BD001F011FD88E73F9A00206A +:105EA000A9580F00399A0020B8990020F4990020D1 +:105EB00086BB0F001D5F0F00F899002083580F006C +:105EC0008DBB0F0098BB0F003A9A002010B50F4B06 +:105ED00001221A700E4B18884FF6FF73984207D0B4 +:105EE0001321FFF796FC08B101F0E8FC002010BD7B +:105EF000084C2378002BF9D0074B1878FFF787FC64 +:105F000008B101F0DBFC00232370EFE73F9A00208B +:105F10003E8400202C9A00203C840020F0B50B78B1 +:105F200089B005460C46092B35D8DFE813F02D0063 +:105F3000360041000A001900260007011001440044 +:105F4000150100F089FB0421FFF781FC0246284679 +:105F500000F018FEF8B109B0BDE8F04001F0AEBCA9 +:105F6000FFF7B4FF08B101F0A9FC00F095FB90B178 +:105F700009B0BDE8F04000F09DBBFFF7A7FF002887 +:105F8000F6D001F09BFCF3E7764B01221A704B68C8 +:105F90001A78754B1A7009B0F0BD724B02261E704C +:105FA0004B681B78012BF6D100F008FB3146CBE79C +:105FB0006C4B0322EEE70520FEF7BAFE694B1E7814 +:105FC000022E37D0032E59D0012EE4D104AB10227B +:105FD00018460021FEF7E0F9634A237A12788DF81B +:105FE0001020002203920C2B4FF00302CDE9012078 +:105FF00008D03146284600F0C5FD0028CBD001F07E +:106000005DFCC8E763681846FFF7F3FB0590181DB1 +:10601000FFF7EFFB069003F10800FFF7EAFB07909C +:1060200001A800F005FA0028B5D03146FFF70FFCB3 +:106030000246DFE7237A13F0030011D0C0F1040217 +:106040001A44D2B219464FF0000C0E46013167686F +:10605000C9B2914207F806C0F7D11B1A0433237264 +:106060000123049363680693237A04A89B0805938D +:1060700000F0C6FA00288ED00221D7E7207A8307E5 +:1060800002D03246314662E7384E0190314601F087 +:106090008FFC014618B12846FFF7F5FB7BE76168E6 +:1060A000019A306805F062F9019801F0E7FC0146B9 +:1060B0000028F0D101A9304601F0F0FC014600288B +:1060C000E9D104230493019B9B08059304A833683A +:1060D000069300F007FA074640B9254A237A11686B +:1060E0000B441360234B32681A6054E709281BD114 +:1060F0001F4B217A1A68114419601F4B1B78002B23 +:106100003FF449AF1D4C2388013B9BB22380002BF9 +:106110007FF441AF284600F0FBFC08B101F0CEFB54 +:10612000174B1B88238036E7306801F06BFC014673 +:1061300010B12846FFF7A7FB3946ACE70E4B01220A +:106140001A700F4A8B8813800C4A138023E70A4A7F +:10615000002313700A4AF8E7054B196800F09CFC0D +:10616000F8E600BF399A0020409A00204C9A00209F +:10617000309A0020489A0020389A0020369A002051 +:10618000349A002018DF7047012973B514460D4674 +:106190001A4608D0032912D014B3204602B0BDE835 +:1061A000704001F08BBB0F4B1B78052BF4D10E4BCD +:1061B0001B68002BF0D0214604209847ECE7094EDD +:1061C0003378022BE8D1094B01925B689847064B64 +:1061D00035701B68002BDFD0019A21462846ECE77A +:1061E00002B070BD589A0020509A00205C9A00209E +:1061F00030B589B003AC142200212046FEF7CCF85C +:10620000094B1B88ADF80E30084BDB680693002560 +:10621000079B8DF80C50009394E80F00FFF758F897 +:10622000284609B030BD00BF689A0020B49A00200B +:1062300000B589B003238DF80C300A4B1B88ADF8EC +:106240000E30094B5A6804929A68DB680693079BE4 +:106250000093059203AB0FCBFFF73AF8002009B08B +:106260005DF804FB689A0020B49A002000B589B05C +:1062700001238DF80C300F4B0F4A1B88ADF80E3000 +:106280004FF440535968914208BF9A680B4B5968C4 +:10629000049118BF4FF480529968DB68069305910A +:1062A000009203AB0FCBFFF713F8002009B05DF8A5 +:1062B00004FB00BF689A0020DBE5B151B49A0020CE +:1062C00000B589B003AB142200211846FEF764F82C +:1062D00004228DF80C20002200920FC8FEF7F8FF70 +:1062E00009B05DF804FB0000194BF7B5194C1C60B0 +:1062F000194B02221A70FEF75DFA184B48B1196863 +:10630000204600F001FF00B303B0BDE8F04001F00B +:10631000D5BA1D68124F013D2D0B013504464FF4CF +:1063200040567368BB420CBFB0684FF4805000EB1E +:1063300004300134FEF7DAFDA542F2D80023054807 +:1063400000931A460321FFF71FFF03B0F0BD00BF03 +:10635000CC9A0020C49A0020589A00206C9A002001 +:10636000DBE5B15170B50C4686B00321CDE90210D2 +:106370000546960802A8019304940596FFF702FFCC +:10638000E0B1B4F5805F019B11D8012302A8CDE9EB +:106390000235CDE90446FFF7F5FE78B9032302A8DC +:1063A000CDE90235CDE90446FFF7ECFE06E01A46DA +:1063B000E11AE81AFFF7D6FF0028E6D006B070BD54 +:1063C0001FB5114B0193114B114900241C70114B47 +:1063D00001A81C80CDE9024400F074FE0E4B10B100 +:1063E0001C7004B010BD4FF440520C4954688C42EC +:1063F00007490CBF92684FF480524A60084A002156 +:10640000116001221A70ECE789610F00B09A002038 +:10641000C49A0020689A0020589A0020DBE5B15108 +:10642000549A0020014B1860704700BF509A00201A +:1064300038B54368214C0FCB84E80F002278500711 +:1064400001D5910733D16068830730D1A3689D07D8 +:106450002DD1E16811F0030429D1184408441849EA +:10646000B3F5204F086024D84FF4405315495D68B8 +:106470008D420ABF9B684FF46923C3F56A23984293 +:1064800017D8114B1149196011495960D10709D525 +:10649000104A9A60104B1B78012B0CD1FFF724FF98 +:1064A000204638BD92074CBF0C4A0D4AF1E706243E +:1064B000F6E70C24F4E70824F2E700BFB49A0020C2 +:1064C0006C9A0020DBE5B1515C9A0020E9620F0074 +:1064D000C1620F006D620F00589A002031620F00F8 +:1064E000F1610F002DE9F04385B0002853D0816899 +:1064F00011F0030451D12C4B1B78052B4FD12B4E9F +:1065000042683368DFF8AC9003EB82039500D9F85A +:106510000020934207D94FF0FF3333600C2420460C +:1065200005B0BDE8F0830391FEF744F9DFF88880F9 +:10653000039940B13368D8F800002A4600F0D4FD32 +:10654000D8B10446EBE74FF44053194A586837680E +:10655000904208BF9868039118BF4FF48050002301 +:106560002A463844FEF7C4F80399D8F8000000958D +:106570000B4600220121FFF707FE33681D44D9F8BE +:10658000003035609D420CD1FEF714F90028C6D1C9 +:10659000FEF790F8C3E70E24C1E71024BFE70824F4 +:1065A000BDE70924BBE700BF589A0020549A002099 +:1065B000DBE5B1516C9A0020CC9A002070B50B4BF2 +:1065C0001D6885B90A4E3378042B0CD1094C0A4B4F +:1065D00021781A780948FEF725F810B90523337099 +:1065E00070BD2570FCE70820FAE700BF549A002030 +:1065F000589A0020B09A0020B49A0020709A002087 +:10660000F8B5114B1A78032A03D0042A03D00824C2 +:1066100016E004221A700D4B1C68002CF7D10C4DAB +:1066200043682F789E0007EB8303402B0AD88168CC +:1066300008483246384404F099FE2B7833442B70D6 +:106640002046F8BD0924FBE7589A0020549A002000 +:10665000B09A0020709A002010B50B4C2378052BBF +:1066600010D10A4B0A4A1B68116899420AD10623C5 +:106670002370084B1B685868FEF70EF808B907230B +:10668000237010BD0820FCE7589A00206C9A002067 +:10669000549A0020CC9A0020044B1B78072B02D17F +:1066A000034B9B6818470820704700BF589A00208A +:1066B0005C9A002000B589B006238DF80C30079B4A +:1066C000009303AB0FCBFEF703FE09B05DF804FBAC +:1066D000F0B58DB005A8FEF76DFF089C002C3ED0EC +:1066E0000B9E04F58053B3422DD91E4BA6F5805561 +:1066F00003EA55054FF440539B689C420BD8690050 +:106700002B46A4EB450201F5805106EB4500FFF74F +:1067100029FE0DB0F0BD05F58053012701A8CDE994 +:106720000173CDE90337FFF72DFD0028F1D14FF4B8 +:10673000805301A8CDE9023301970497FFF722FDAA +:106740000028E6D1DBE70123CDE90136A4084FF4A8 +:10675000805301A803930494FFF714FDD9E7204662 +:10676000D7E700BF00F0FFFF00B58DB005A8FEF72A +:1067700021FF099880B1089B8BB94FF440530B4A15 +:10678000596891420ED19B6880080022039001A8AD +:10679000CDE90123FFF7F6FC0DB05DF804FB0B9A81 +:1067A0001344F1E74FF48053EEE700BFDBE5B1514E +:1067B00000B58DB005A8FEF7FDFE099898B1089BBD +:1067C000A3B94FF440530C4A5968914211D19B68C8 +:1067D0000393800803214FF47422049001A8CDE9AB +:1067E0000112FFF7CFFC0DB05DF804FB0B9A1344C8 +:1067F000EEE74FF48053EBE7DBE5B15110B58CB019 +:1068000005A8FEF7D7FE0898B8B10B9C00F5805399 +:10681000A34214D94FF440539B6898421BD80F4BA6 +:10682000A4F5805203EA52035900A0EB430201F59C +:10683000805104EB4300FFF795FD0CB010BD8008BC +:1068400003224FF48053049001A8CDE9012303945F +:10685000FFF798FCF1E70E20EFE700BF00F0FFFF25 +:10686000A8DF7047AADF7047ADDF7047AEDF704723 +:10687000B0DF704762DF70472DE9F0470E4694B0F5 +:106880000546002800F00181002900F0FE804B68D9 +:10689000002B00F0FA804FF6FF7303800023ADF861 +:1068A0000A307B4B04AA03F1100C1746186859688C +:1068B000144603C4083363452246F7D141F23053EE +:1068C0000DF10A013846ADF80830FFF7D3FF044652 +:1068D000002840F0D6802A1D02A90120FFF7C0FF42 +:1068E0000446002840F0CD809DF80A30AB71014687 +:1068F0001C220DA8FDF750FD9DF834300E9443F096 +:1069000004038DF8343001AFAB798DF80E30214699 +:1069100041F2325303223846CDE91044CDE9124406 +:10692000ADF80C30FDF738FD9DF806308DF80440C9 +:1069300023F01F0343F00303214614224FF0110AF2 +:1069400008A88DF806308DF805A00DF10C08FDF7AC +:1069500023FD4FF01409A8880A9405F1080308AA3A +:106960000DA90C94CDE90887ADF82C90FFF77AFFBC +:106970000446002840F0858001461C220DA8FDF742 +:106980000BFD9DF834300E9423F0180343F01803E8 +:106990008DF83430AB798DF80E30214641F2315309 +:1069A00003223846CDE91044CDE91244ADF80C304D +:1069B000FDF7F2FC9DF806308DF8044023F01F032C +:1069C00043F0130321464A4608A88DF806308DF897 +:1069D00005A0FDF7E1FC1723ADF82C30A8880A9438 +:1069E00005F1100308AA0DA90C94CDE90887FFF75B +:1069F00039FF0446002844D101461C220DA8FDF7AA +:106A0000CBFC9DF834300E9443F002038DF8343003 +:106A1000AB798DF80E30214641F2345303223846CB +:106A2000CDE91044CDE91244ADF80C30FDF7B4FCCB +:106A30009DF806308DF8054023F01F0343F0030353 +:106A400021464A4608A88DF806308DF804A0FDF7C7 +:106A5000A3FC02230A93ADF82C30A8880C9605F10C +:106A6000200308AA0DA9CDE90887FFF7FBFE04461D +:106A700038B97368AB62B36803B1EB62054B0122AE +:106A80001A70204614B0BDE8F0870E24F9E700BF65 +:106A9000B9BB0F00D09A002070B5054686B070B320 +:106AA00002884FF6FF739A422BD0174B1B7843B3E3 +:106AB000164C1022080AE170207121FA02F0090E2A +:106AC000072301266071A17102A800216370ADF84F +:106AD00006302270A670FDF75FFC2B8AADF80830F7 +:106AE0000023ADF80C3028888DF80A600DF10603FC +:106AF00002A9CDE90434FFF7B9FE06B070BD0E203F +:106B0000FBE70820F9E700BFD09A0020D19A0020C7 +:106B100030B5044687B060B302884FF6FF739A42DF +:106B200029D0164B1B7833B3154D11232B700B0A4C +:106B30006970AB700B0C090EEB70297105230021F5 +:106B4000102202A8ADF80630FDF726FC238AADF826 +:106B5000083001238DF80A300023ADF80C3020886E +:106B60000DF1060302A9CDE90435FFF77FFE07B05A +:106B700030BD0E20FBE70820F9E700BFD09A0020C7 +:106B8000D19A002030B5044687B038B300884FF65C +:106B9000FF73984224D0134B1B780BB3124D102374 +:106BA00069700321ADF80610AA7000211A4602A8E8 +:106BB0002B70FDF7F1FB238AADF8083001238DF827 +:106BC0000A300023ADF80C3020880DF1060302A92D +:106BD000CDE90435FFF74AFE07B030BD0E20FBE7D4 +:106BE0000820F9E7D09A0020D19A002070B50D4610 +:106BF00088B0044650B149B1826A3AB10B88502B33 +:106C000049D005D8102B43D0112B54D008B070BDFB +:106C1000512BFBD18E79022EF8D10A89038A9A4230 +:106C2000F4D18B7B043B022BF0D99DF816308DF804 +:106C3000106043F001038DF816300B8AADF8183060 +:106C40004B8AADF81A30082201F1140301A8002183 +:106C50000793FDF7A1FBA18A2088019601AACDF830 +:106C600008D0FFF701FE48B3E36A03B1984740F24A +:106C7000FD132088ADF8143004A9FFF7F9FD0028B2 +:106C8000C4D0E36A002BC1D008B0BDE870401847FB +:106C90008B882380BAE7C98803899942B6D1082333 +:106CA0008DF81030123535F8023C8DF81830059506 +:106CB00004A99047AAE74FF6FF73EAE7BDF8003052 +:106CC0002088DB07D3D5002604A9ADF81460FFF7B0 +:106CD000CFFD0028D5D1297D4B1E072B3ED8DFE8FC +:106CE00003F004192226282A3B2C6B8A8DF80460B5 +:106CF000012B05D8062201212046FFF743FFBEE7FE +:106D0000012315358DF80C300295A36A01A92046A0 +:106D100098477BE76A8A01239A428DF80430F0D8BD +:106D200006220221E8E702238DF80430EDE7032371 +:106D3000FAE70423F8E70523F6E76B8A022B02D86B +:106D400003220821D8E7B5F81530ADF80830002B3C +:106D50000CBF07230623E7E70923E5E70322CBE778 +:106D6000A8DF7047AADF70472DE9F04180468EB05A +:106D700015461F460E4611B9084600F09FFD15B98D +:106D8000284600F09BFD1C220DEB02000021FDF7C0 +:106D900003FB9DF81C30ADF80480002443F002038F +:106DA0008DF81C3021460123032268468DF80630F9 +:106DB000CDE90A44CDE90C440894FDF7EDFA3B789F +:106DC0008DF800307B788DF801309DF8023023F08B +:106DD0001F0343F002032146142202A88DF802305B +:106DE000FDF7DAFA0A48CDF80CD001AB029302AAFB +:106DF000149B0088ADF8105007A9ADF81240ADF80B +:106E000014500696FFF7AEFF0EB0BDE8F08100BF4C +:106E1000F89A002030B587B041F60A032B4AADF846 +:106E20000C30044603A901208DF80E00FFF798FFEF +:106E30000546D8B92288E2B922894AB1244B009389 +:106E4000E16804F13C0342F62420FFF78DFFD8B936 +:106E5000228C4AB11F4B0093616A04F13C0342F655 +:106E60002620FFF781FF78B9A36B7BB9284607B0CE +:106E700030BD194B0093616804F13C0342F62920B0 +:106E8000FFF772FF0028D7D00546EFE71A788DF894 +:106E900010205A888DF81120120A8DF812209A8835 +:106EA000DB888DF815301B0A8DF813208DF816300D +:106EB000120A0A4B8DF814200093072204F13C03B8 +:106EC00004A942F65020FFF74FFFDDE7F89A0020B3 +:106ED000E89A0020D89A0020E09A0020F09A00203A +:106EE00029DF704728DF7047064B182202FB00306D +:106EF00000230422C0E90423037183608361C3601B +:106F0000704700BF0C9B002023B502460846C968A5 +:106F100043680093044B53F82150436910F80C1B4D +:106F2000A84702B020BD00BFFC9A002038B5194C1C +:106F30002378182202FB03431A795869012A03D0E7 +:106F4000032A1AD00F2038BD134A996915689A6828 +:106F5000DB68A2EB0532B2F5805F184401EB053126 +:106F600000EB053034BF92084FF48062FFF7B8FFA2 +:106F70000028E8D10123A370E5E74FF080531B6997 +:106F80009BB2B0FBF3F0044B1B681844FFF7AAFF59 +:106F9000EEE700BF0C9B0020049C002070B5134D51 +:106FA0006C780A2C1FD02E783444E4B2092C84BFAC +:106FB0000A3CE4B2182606FB0454A261207103C9FE +:106FC000A360049BE360AB7804F1100282E8030045 +:106FD00023B100206B7801336B7070BDFFF7A6FF03 +:106FE0001128F7D1F5E70420F7E700BF0C9B00203C +:106FF00070B5234CA3782BB100260228A67002D0CE +:10700000032833D070BD25781E4A182101FB0541A5 +:10701000136889680133B1EB033F136014D86378B8 +:107020001660013B63706B1CDBB21821092B01FB5E +:10703000054188BFA5F10903002004312370FFF743 +:1070400063FF2846FFF750FF6378002BDAD0A37860 +:10705000002BD7D1FFF76AFF0028D3D01128D1D059 +:107060002178182303FB0141043105E0217818231E +:1070700003FB014104310D20BDE87040FFF744BF20 +:107080000C9B0020049C002008B50A4B00211960CD +:10709000094B1980997008460131FFF725FF0A292D +:1070A000F9D1064B00201860054BC3E90000C3E985 +:1070B000020008BD049C00200C9B0020009C0020C6 +:1070C000FC9A0020064A03461068042807D008608E +:1070D000411C11601A68034B43F8202000207047C0 +:1070E000009C0020FC9A002013B5CC180C43A40788 +:1070F00008D1009313460A4601460120FFF74EFFD0 +:1071000002B010BD1020FBE707B500220B4600922D +:1071100001460320FFF742FF03B05DF804FB0000C7 +:10712000094B5A7899780132D2B2914208BF0022B5 +:10713000197891421FBF02705878182202FB003064 +:1071400014BF043000207047089C0020082910B5A7 +:10715000044602D0002000F0B1FBD4E90030BDE8C5 +:107160001040184773B5054600240DF107000E4680 +:107170008DF8074000F0B0FB0DF10600FFF7D0FFDF +:1071800090B10670094B9DF8062045605A709DF835 +:10719000070000F0C5FB24B9054B4FF48012C3F87B +:1071A0000021204602B070BD0424F0E7089C0020B6 +:1071B00000E100E0204B21491A682F2300BF00BFE7 +:1071C00000BF00BF00BF00BF00BF00BF8A422FD07A +:1071D00000BF00BF00BF00BF00BF00BF00BF00BFB7 +:1071E00000BF00BF00BF00BF00BF00BF00BF00BFA7 +:1071F00000BF00BF00BF00BF00BF00BF00BF00BF97 +:1072000000BF00BF00BF00BF00BF00BF00BF00BF86 +:1072100000BF00BF00BF00BF00BF00BF00BF00BF76 +:1072200000BF00BF00BF00BF00BF00BF00BF00BF66 +:10723000013BC3D1704700BF388400200024F40014 +:107240000C4B0D484FF4003210B5C3F880200124D8 +:107250004FF48033C0F84833C0F808334460FFF778 +:10726000A9FF064B846000201860FFF7A3FF044BC2 +:10727000187010BD00E100E000100140249D0020C6 +:10728000159D00202DE9F3412549264B0025C1F825 +:107290004051C1F84451C1F84851C1F84C51C1F8AE +:1072A0000051C1F804511B68002B34D0D1F80445BB +:1072B0001D49DFF888800968641A24F07F442F464E +:1072C0001968A14212D81A7CDE69641A0D4462B1B1 +:1072D0005A691F7400929B690193424608216846CF +:1072E00000F056FA08B100F0E9FABEB90F4A104BA7 +:1072F00011781B788B4205D10133DBB2022B08BF1A +:107300000023137012780B4B43F822500A4B4FF4B2 +:107310008012C3F8002102B0BDE8F0813346CFE708 +:1073200000100140289D0020249D0020219D002068 +:10733000209D0020189D002000E100E04D710F000D +:107340002DE9F74FA84AA94913780978A84C994222 +:107350003BD00133DBB2022B08BF00231370A549D9 +:107360001278A54B0F6853F822003B1823F07F4397 +:1073700000220B60236815461646944613B942B1A5 +:10738000236006E0196881420DD902B12360091A11 +:10739000196001262368DFF8689200930027BDB9C1 +:1073A000DFF868A268E0401A0E44D968D3F81CE000 +:1073B000C3F800C031B1BA1922F07F42C3E90121FC +:1073C000DD611D4673460122D8E700252E46E1E720 +:1073D0002846ED69874BD0F804C01B68DFF830E21F +:1073E0008168ACEB030222F07F42724500F2AD806F +:1073F0000A4402600122027422680023C0E90133BA +:10740000C361002A40F0AB802060C8E75A1C9AF89C +:107410000210D4F800B0D2B291428AF8002004BF22 +:1074200000228AF80020182202FB03A31A79986828 +:10743000022A77D0032A00F08580012A1CD190F817 +:1074400010C0BCF1000F17D1D96841601A69826081 +:107450005A69C2609B698361684B1B78002B18BF17 +:1074600061464160B6E7904200F09E809046D26946 +:10747000002AF8D1002303749AF800309AF801200A +:107480009A42C3D1236827B9009A9A4201D1002EAB +:1074900042D0002B00F08580D3F80090584C554B1B +:1074A000D4F804651868574F351A3B7825F07F45A6 +:1074B00003359BB94FF48033C4F84433C4F8043324 +:1074C000514B4FF400324FF00108C3F880211A608D +:1074D000C4F80080FFF76EFE87F80080A9452CBF36 +:1074E0004844401920F07F40C4F84005D4F80435E2 +:1074F0009B1B23F07F43801B033320F07F4083429C +:107500000AD9D4F80435C4F84035FFF753FE3E4B92 +:107510004FF40032C3F80021384B00221A7003B038 +:10752000BDE8F08F5A46D846A2E78BF81020DBF86A +:107530001CB00123BBF1000FF7D1002B9CD0C4F885 +:1075400000B099E700231A46F4E7A3EB0C0323F0FD +:107550007F438B4234BFCB1A002303604AE70168A4 +:10756000136899421BD85B1A1360C2614CE7A1EB08 +:107570000C01D3F81CC01A46BCF1000F0AD06346B8 +:10758000D3F800C08C45F2D3ACEB010CC3F800C0BB +:107590009C4613460160C0F81CC0D861FFE6134644 +:1075A000EEE7FFF74DFEB7E7404510D1DBF81C30A2 +:1075B000236063B9DFF83CE001920121C9F80810AB +:1075C000CEF800300D4B1970FFF7F4FD019A1368E7 +:1075D000D269C8F81C2012B111680B4413602368EB +:1075E0005B4518BF012745E7209D0020219D002015 +:1075F000289D0020249D0020189D0020149D00201F +:1076000000100140159D002000E100E0089C0020D2 +:10761000FEFF7F0008B5FFF713FE104B00200B2282 +:1076200018809A700E4B18600E4B18700E4B187025 +:107630000E4B4FF48012E021C3F8802183F814131D +:107640001A6002F18042A2F56F22C2F8080583F8A1 +:107650001113074BD2F804251A6008BD089C0020BE +:10766000289D0020209D0020219D002000E100E0B9 +:10767000249D0020074B9B784BB132B128B10368A1 +:10768000187C20B959745A61704707207047082048 +:10769000704700BF089C00202DE9F743DFF8848085 +:1076A00098F8023005460E461746ABB3A0B304293E +:1076B00030D9436983B3437C0024012B0DF10700CB +:1076C0000CBF8946A1468DF8074000F005F90DF181 +:1076D0000600FFF725FDD8B1012303700F4B45606D +:1076E000D3F80435C0E90497C0E902369DF80630A6 +:1076F00088F801309DF8070000F012F924B9084B12 +:107700004FF48012C3F80021204603B0BDE8F08397 +:107710000424EFE70724F7E70824F5E70010014009 +:1077200000E100E0089C0020064A92783AB130B1AE +:10773000426922B1002202740221FFF713BD082022 +:10774000704700BF089C00204B1C30B5DB0004468E +:1077500012F003009BB20DD1074D2A601A44074B6B +:107760001A60074B1870074B1870074B1C80074BAB +:10777000198030BD0720FCE7349D0020309D00209B +:107780002C9D00203C9D0020389D00203A9D00202B +:107790002DE9F347DFF8C080B8F800308B42064689 +:1077A0000D4617464CD300240DF107008DF8074015 +:1077B00000F092F8244B254A25481B78008892F85F +:1077C00000C084455FFA8CF138BF4C1CDBB238BF77 +:1077D000E4B2A3422ED014781178CBB2884286BF8F +:1077E0000133DBB2002313709DF8070000F098F816 +:1077F0004FF6FF739C4225D0DFF86090D9F8002047 +:107800004FEAC40A42F8347002EBC403AEB1A5B12A +:10781000104BB8F800001B682A4604FB00303146C4 +:1078200003F0A4FDD9F80030534400209D8002B03D +:10783000BDE8F0874FF6FF74D6E700209880F6E7A2 +:107840000920F4E70420F2E73C9D00202C9D002055 +:107850003A9D0020309D0020389D0020349D00205E +:1078600070B5104C104D22782B789A4200D170BD23 +:107870000E480F4A2378126806880E4802EBC301AF +:10788000006852F83320898803FB060090470A49B4 +:1078900022780988D3B2914286BF0133DBB200233C +:1078A0002370E0E73C9D00202C9D0020389D0020A7 +:1078B000349D0020309D00203A9D00201FB50021FE +:1078C000CDE9021001AA44F20100ADF80410FCF762 +:1078D00066FF05B05DF804FB70B5EFF3108672B675 +:1078E0000C4A946801239CB993600B4B0B4DD3F861 +:1078F000801029401160C3F88050D3F88410516083 +:107900004FF0FF32C3F88420047006B962B670BD30 +:107910000370FAE7409D002000E100E0FC06FFBD97 +:1079200010B5084B9A685AB150B9EFF3108172B68E +:10793000054A1C6814605C685460986001B962B6BE +:1079400010BD00BF409D002000E100E003462AB1C9 +:1079500010881A4619448A4203D170474FF6FF70C7 +:10796000F7E712F8013B40BA80B25840C0F3031366 +:10797000584080EA0033580100F4FF509BB2584051 +:10798000E9E70000064B074A00201870064B1A6012 +:107990000822C3E90120C3E90300C3E905007047D9 +:1079A0004C9D0020509D002030B0002000207047EA +:1079B00030B5F9B1124B5C6800220A60E4B1B0F551 +:1079C000167F1BD8D868013C01305C60D8601C6809 +:1079D00018694FF4177505FB00440C60012101FA8A +:1079E00000F49969013000F00700214318619961A2 +:1079F000104630BD0E20FCE70420FAE70C20F8E723 +:107A000030B00020F0B51C498A689AB34D690E6801 +:107A1000AC1A04F0070423464FF4177707FB036CF6 +:107A2000604511D1012000FA03F58869684088613A +:107A300000204E68D1F818C04FF0010E73440025A5 +:107A4000164403F007030AE0013303F007039D42E5 +:107A5000E4D11020EDE74AB1013A1C4601250EFAA7 +:107A600004F414EA0C0FA6EB0207F4D00DB1C1E93F +:107A70000172F0BD0420FCE730B00020064A136913 +:107A80001268013B4FF4177103F0070301FB032356 +:107A9000C3F858020020704730B0002030B5C0B1A4 +:107AA000B9B10E4BDA68B2B1013ADA609A681C6873 +:107AB00001329A605A694FF4177505FB024404605D +:107AC0000132D4F85802086002F007025A6100201F +:107AD00030BD0E20FCE70420FAE700BF30B00020E4 +:107AE0003FB40C49086890B10B4B1C687CB10B4A41 +:107AF0001568CDE9025000238DF804300B60136047 +:107B000004AB13E90700234604B030BC184704B0A7 +:107B100030BC704754B0002058B0002064B0002042 +:107B2000DC2810B509D0DD2810D0C02816D1FFF709 +:107B3000D7FF0E4B0E4A1A6010BD0E4A0E4B196845 +:107B40001368581C1060C022CA54F2E7094A0A4B55 +:107B500019681368581C1060DB22F5E7064B054ACC +:107B6000196813685C1CC8541460E2E74484002060 +:107B7000917B0F0054B0002064B00020C02802BFE9 +:107B8000014B024A1A60704744840020917B0F0029 +:107B9000C02810B409D0DB280BD0094B094A19685A +:107BA00013685C1CC854146006E05DF8044BFFF7D2 +:107BB00097BF054B054A1A605DF8044B704700BF3C +:107BC00064B0002054B0002044840020217B0F00CA +:107BD00007B501228DF807000DF10701002002F022 +:107BE00085FD00280CBF0420002003B05DF804FBD5 +:107BF00010B5064A064C12682368D05CFFF7E8FF10 +:107C000010B923680133236010BD00BF68B00020A5 +:107C10005CB0002008B5C020FFF7DAFF28B9034B9D +:107C20001B6813B9024B034A1A6008BD5CB0002000 +:107C300048840020F17B0F0008B5DB20FFF7C8FF68 +:107C400010B9024B024A1A6008BD00BF48840020E8 +:107C5000557C0F0010B50C4A0C4C12682368D35C9D +:107C6000C02B03D0DB2B0DD0042010BDDC20FFF790 +:107C7000AFFF0028F9D12368054A01332360054B83 +:107C80001A60F2E7DD20F2E768B000205CB0002067 +:107C9000F17B0F00488400207FB5184C184D194E19 +:107CA000002002F0C3FC30B322689AB1296833681F +:107CB00099420FD2012201A9002002F0C3FC10B9A1 +:107CC0004FF0FF3001E09DF804000F4BC0B21B687D +:107CD0009847E5E70D4B1B686BB10292084A0221F9 +:107CE000126803928DF8041004AA12E9070004B088 +:107CF000BDE87040184704B070BD00BF64B00020FC +:107D000054B0002050B000204484002058B000201F +:107D1000014B18600020704758B00020034B1A78C0 +:107D20000AB901221A700020704700BF4CB0002031 +:107D3000014B0020187070474CB000202DE9F04F27 +:107D400085B0002851D02A4F3B78012B07D0022B59 +:107D500014BF08240424204605B0BDE8F08F254D4B +:107D6000DFF8A090244E254CDFF89C80DFF89CA023 +:107D7000DFF89CB0C9F8001000232B6002233060AC +:107D80003B70C4F800802A68D9F800309A4215D3B5 +:107D9000C4F80080FFF73EFF044608BB184B1B6881 +:107DA00001223A70E3B18DF80420326802922A6809 +:107DB000039204AA12E907009847CCE733682A68BF +:107DC0009A5CC02A03D02A689B5CDB2B04D1236811 +:107DD000534508BFC4F800B023689847042801D170 +:107DE0000024B8E71128CED1FAE71024B3E700BF8A +:107DF0004CB000205CB0002068B000204884002017 +:107E000058B0002060B00020157C0F00F17B0F00FF +:107E1000397C0F00054B064A1860064B1960064B6B +:107E200000201860054B1A60704700BF64B0002046 +:107E30007D7B0F0050B0002054B00020448400200F +:107E4000064B07481B68DB00DBB2002203705B4275 +:107E500042708270C3700421FFF770BF94B000209D +:107E60006CB0002070B52B4C2B4D02462378012BB3 +:107E700014D0022B21D0002B4BD1002A49D1274806 +:107E8000FFF752FC08B1FFF719FD254B1B68002BCB +:107E90003FD0244ABDE8704010781847012A38D1F5 +:107EA0002968214B06311868FFF748FF08B1FFF732 +:107EB00005FD022323700022D8E7022A16D0032AE8 +:107EC0000CD032BB194B15481A6041F67F21FFF7E1 +:107ED000E3FBF0B1BDE87040FFF7F0BC144A136853 +:107EE000013303F0070313600023E3E70F4A13682D +:107EF000052B0AD001331360074B19680A4BBDE804 +:107F0000704018680631FFF719BF064B01221A703E +:107F1000EAE770BDB8B00020ACB0002070B000201F +:107F2000A8B00020B0B00020C0B00020B4B0002045 +:107F300098B00020F0B585B004AB03E907009DF8C8 +:107F40000400032874D8DFE800F00802A3A601208B +:107F500005B0BDE8F040FFF785BF039E544C032EEB +:107F600040F28280029D6B7813F00F0265D00E2ADA +:107F70007AD1042E55D02A78500652D5110650D504 +:107F80001A44AB781A44EB781A4412F0FF0248D135 +:107F9000B71E39462846FFF7D9FCEB5B834240D138 +:107FA00044492A780B6802F00702D8B282422BD1EA +:107FB000013303F007030B60FFF742FF3E4B012242 +:107FC00030461A70FFF75AFD08B1FFF777FC3849C1 +:107FD0004FF41670FFF7ECFC002862D0042802D0A2 +:107FE0000020FFF76BFC35480521FFF713FF08B1B0 +:107FF000FFF764FC324B1B68002B56D04FF000009B +:1080000005B0BDE8F040184720684FF41671FFF73F +:1080100001FF08B1FFF752FC05B0BDE8F040FFF7E3 +:108020000FBF20684FF41671FFF7F4FE00283CD014 +:1080300005B0BDE8F040FFF741BC2978AA780B44B1 +:108040001344EA78134413F0FF030DD11D4A12685C +:108050000132C1F3C20102F00702914204D11A4A6F +:1080600003201370FFF7FEFE25681DB14FF4167153 +:108070002846D9E70E494FF41670FFF799FC60B116 +:10808000042802D02846FFF719FC0C480521CBE74D +:108090000A480521C8E70320CAE720684FF4167193 +:1080A000C2E720684FF416719FE705B0F0BD00BF2E +:1080B000BCB0002094B0002090B000209CB0002004 +:1080C000A4B0002098B00020B0B000200220FFF73C +:1080D000C9BE0000074B10B5044618600648FFF7FC +:1080E00017FE08B1FFF7EAFB002C0CBF0E200020A2 +:1080F00010BD00BFA4B00020357F0F00184A1948FA +:10810000002310B51360184A1360184A1360184A08 +:108110001370184A1370184B184A01211960184B34 +:108120001960184B1970FFF7A5FA08B1032010BDAC +:10813000FFF728FC0028FAD1FFF7F0FD0028F6D160 +:10814000114C4FF416702146FFF732FC0028EDD198 +:1081500020684FF41671BDE81040FFF75BBE00BF0A +:10816000C0B00020CCBB0F00ACB00020B4B00020E9 +:1081700090B00020B8B0002094B00020CD800F0057 +:1081800098B00020B0B00020BCB000200C4A08B568 +:10819000002313600B4A1360FFF708FC08B1FFF7D8 +:1081A0008DFBFFF7C5FD08B1FFF788FB0648FFF719 +:1081B000BBFA042802D10020FFF780FB002008BD95 +:1081C000A8B00020A4B0002070B0002037B50D4644 +:1081D000044698B191B10A4B19780022019259B125 +:1081E00001A91A70FFF75AFC019B063B2B802368FC +:1081F0000433236003B030BD0420FBE70E20F9E711 +:1082000090B000200438FFF7FDBB18DF7047000076 +:10821000F0B51D46154B87B018680F4659681B7A94 +:1082200003AC03C42370124B18685968114B0093B8 +:1082300001AC03C42046164603F042FA214602462A +:10824000384603F093F801A803F03AFA01A9024670 +:10825000304603F08BF8684603F032FA694602466E +:10826000284603F083F807B0F0BD00BFD0BB0F0075 +:10827000D9BB0F00312E30000120704710B51C46CD +:108280000B781E2B0AD000232022052102F07AFC55 +:108290004FF6FF70A04228BF204610BD0020F9E72E +:1082A000F8B5069F14460D463A46002118461E466C +:1082B000FCF772F87CB14FF0E023D3F8F03DDB0718 +:1082C00000D500BE4FF0FF300AE0284600F0F8F974 +:1082D000013504F50074BC4206EB0401F5D32046D9 +:1082E000F8BD0000F8B50A4F0D461E460024069B57 +:1082F0009C4206EB040101D32046F8BD3A462846CD +:1083000000F0B4FA0028F7D0013504F50074EEE768 +:10831000C4B0002030B5264D2A7A8DB09AB107AC92 +:108320001422002120460625FCF736F88DF81C5053 +:108330000B9B009394E80F00FCF7CAFF0620FCF7A4 +:10834000F7FC0DB030BD2B68002BFAD0194B197813 +:1083500019B105201A70FCF7EBFCD5E900329A42FE +:10836000EFD307AC142200212046FCF715F86B7AF6 +:10837000DBB106234FF420424FF474214FF4602008 +:108380008DF81C3002F0C0FF0028D1D0102200214F +:1083900003A8FCF701F84FF460224FF4205303A820 +:1083A000CDE90423FFF731FFC2E78DF81C30BFE7AA +:1083B000C4B000204C840020024B0B604FF40073CB +:1083C0001380704709010100012070470048704781 +:1083D000FA840020044B054A1878054B002814BF86 +:1083E00010461846704700BFE0B20020AF8400205E +:1083F0004D8400202DE9FF411E4B187020B11E4B0B +:108400002A229A720022DA721C4A1D4DDFF878C0C7 +:1084100017461C4BEE4603F110067446186859685F +:10842000F046A8E803000833B342C646F6D12B78DD +:1084300003F00F0310336B4413F8103CD373114B4C +:1084400018685968A646AEE803000833B34274467C +:10845000F6D115F8013B04A901EB1313654513F898 +:10846000103C9373A2F10202D3D100233B7404B0F9 +:10847000BDE8F081E0B20020FA84002064B300205F +:1084800060000010E1BB0F006800001010B570B96B +:10849000134B14481968022202F068FF01230133CC +:1084A000DBB211485B0043F44073038010BD052824 +:1084B00014D80B4B53F82040204603F001F9C3B207 +:1084C0001F2B28BF1F23084A2046E118884202F1CB +:1084D0000202E4D010F8014B1480F7E70020E5E732 +:1084E0000C850020E4B20020E2B200204DDF70478E +:1084F0004EDF70474FDF704750DF704712DF704725 +:1085000000F0C8BE002000F033BD00001FB5244BB2 +:10851000402283F8272300238DF807304FF440537F +:1085200004465A681F4B9A4227D10DF10700FFF706 +:10853000E5FF9DF8073003B30120FFF7D9FF0120C5 +:10854000FFF7D4FF0120FFF7D5FF02A8FFF7D4FF04 +:10855000029BDA0702D5002000F09CFE029B9B07DD +:1085600002D5022000F096FE2046FFF743FF00F000 +:1085700027F802F059FE04B010BD002301A88DF8C1 +:108580000430FCF721FC084B039303A8FCF744FCE0 +:10859000FCF748FC4FF08043D3F838340293D7E718 +:1085A00000E100E0DBE5B15101850F00012000F0A2 +:1085B00071BE0120FCF7BCBB0220FCF7B9BB000078 +:1085C0007FB52F492F4802F0E7FF4FF440532E4A62 +:1085D000596891424CD11A78102A46D9142A186940 +:1085E00044D95B69294CB3FBF4F50A2201A904FBC9 +:1085F000153403F021F92649224802F0CDFF01A9E4 +:10860000204802F0C9FF23491E4802F0C5FF0A2294 +:1086100001A9284603F010F901A91A4802F0BCFF8D +:108620001D49184802F0B8FF4FF47A760A2201A9D2 +:10863000B4FBF6F5284603F0FFF801A9114802F053 +:10864000ABFF15490F4802F0A7FF0A2201A906FB5C +:10865000154003F0F1F801A90A4802F09DFF0F4907 +:10866000084802F099FF04B070BD00200023B9E76C +:108670000B49044804B0BDE8704002F08DBF00BF54 +:10868000FFBB0F0024850020DBE5B15140420F0005 +:108690000CBC0F000ABC0F000EBC0F0019BC0F0071 +:1086A00010BC0F0010B503461C1A944200DB10BD2D +:1086B0000C781CB1013103F8014BF5E72024FAE7EF +:1086C0002DE9F3410C4605464FF400720021204687 +:1086D000FBF762FE6DB95E493E22204602F046FE7F +:1086E000552384F8FE31AA2384F8FF3102B0BDE897 +:1086F000F081B5F5017F2DD8691EB1F5817F24BFCA +:108700006FF4817805EB0801C9B10B02C1EBC151CF +:1087100003F5807004EB412440F693651A1FB2F50F +:10872000696F03F1010206D2AB4214BF91B24FF65A +:10873000FF7124F8131090421346EFD1D6E7F823C7 +:10874000237004F109022346FF2003F8010F93422E +:10875000FBD1DAE7B5F5027F3BD86FF4017C6544C5 +:108760003DB920463B490B22FFF79CFF2823E372CB +:10877000203439492E014FF0000801EB05256FF038 +:108780001907022EB2D80B2229462046FFF78AFF8E +:108790005923212269216374E3746376B31C84F83E +:1087A0000D80A773E1732274A27484F8148084F896 +:1087B0001580A775E17522766383E86830B102F011 +:1087C0007FFFE061013620341035DAE74FF4E9101D +:1087D000F7E7224B9D4289D86FF40277EA19012A04 +:1087E0000FD81D4B03EB0213D9680191084602F024 +:1087F00067FF01990246204602B0BDE8F04102F051 +:10880000B5BD6FF4FD76A9190902B1F5801FBFF45B +:108810006DAF134B236003F1144303F52C1303F6E0 +:10882000023363600F4BC4F8FC314FF46963A361FA +:108830004FF40053A5F20B254FF48072A3600A4B4E +:108840006561E1602261E36104F12000D4E700BFCB +:108850001CBC0F0047BC0F00D4BC0F000801010076 +:108860005546320A306FB10A29009A23F7B5654B95 +:1088700014460A689A420D4639D103F114434A68F6 +:1088800003F52C1303F602339A4230D1D1F8FC21C0 +:108890005D4B9A422BD18B6823F4FF5323F01E03C8 +:1088A0009B049B0CB3F5005F21D10B69B3F5807F6E +:1088B0001DD1C86810F0FF0619D1CB69534A934205 +:1088C00005D0534A934215D0524A93420FD1A0F596 +:1088D0008053B3F5692F07D201234FF4807205F15D +:1088E0002001FBF705FF22E0B0F5805F1FD34FF0BA +:1088F000FF3021E001276772CB68B3F1102F1DD143 +:1089000004223431684602F031FD042205F13801B9 +:108910000DEB020002F02AFD009BB3F5742F03D18A +:10892000019BB3F57E2F01D02772E0E7A772AB69F8 +:10893000002B37D14FF4007003B0F0BDA3F57422C3 +:10894000B2F5204F28D2E27A01F12007DAB9324A93 +:10895000934218D32B69B34215D90422B91968463A +:1089600002F004FD009BD02B14D10422311D0DEB2D +:108970000200394402F0FAFC264B019A9A424FF069 +:1089800001030DD1E372E8682A6901233946A0F595 +:10899000A030A6E70836DDE7B3F5805FC7D3012333 +:1089A0002372A4E72268934207D041F263018B420D +:1089B00000D80AB14FF0FF3323606B6941F26302C4 +:1089C0009342B7D803F0070204EBD303012191408F +:1089D0001A7B1142C8B204D16168024301311A7393 +:1089E0006160D4E900329A42A4D30120FBF762FE11 +:1089F000637A002B9ED0A37A002B9BD10123237294 +:108A000098E700BF5546320A306FB10A4028A5AD3D +:108A10003C8263D629009A2300D80F004FF0805380 +:108A2000D3F83001082802D1D3F8343123B9A0F1AA +:108A30000D0358425841704701207047094B0122ED +:108A400083F8D8200260BFF36F8FBFF34F8F064AC1 +:108A5000904202D0043A904202D1002283F8D820FA +:108A6000704700BF78B300205070024042DF70476B +:108A700043DF704744DF704712DF704710B5134B78 +:108A8000134A5B68C3F3080373B9EFF310835BB950 +:108A900010494B681B0607D58024C1F8844092F822 +:108AA000D8307BB14C60F8E792F8D83033B101464A +:108AB000BDE810400848012201F094B8BDE810401C +:108AC000FFF7BCBFFFF7BAFF4C6010BD00ED00E040 +:108AD00078B3002000E100E07D8A0F000D4B1822E2 +:108AE00002FB0033598A1A8A521A998A92B28A4230 +:108AF00028BF0A46D9681423434303F1804303F592 +:108B00001C33C3F80016C3F80426034B03EB8000A4 +:108B1000FFF7B4BF78B300200470024007B54FF4EC +:108B2000405300205A68084B9A420AD18DF807003A +:108B30000DF10700FFF7A0FF9DF80700003818BFF0 +:108B4000012003B05DF804FBDBE5B15107B5FFF789 +:108B5000E5FF58B1002301A80193FFF78BFF0198AF +:108B6000003818BF012003B05DF804FB4FF08043CC +:108B7000D3F80C0400F00110A0F101135842584141 +:108B8000F1E70000074BD3F8C024D10309D406490C +:108B90000648D1F8C010C3F8A017C3F8A427FFF700 +:108BA0006DBF70470070024078B3002048700240EB +:108BB0000828F0B402D1F0BCFFF7E4BF0F4D104A13 +:108BC000182141436E1800F59473695852F82340F8 +:108BD000F788B388DB1B9BB2A4B2A34228BF23460D +:108BE000142404FB0022C2F80017C2F80437054B16 +:108BF000F0BC03EB8000FFF741BF00BF78B300205B +:108C0000007002402870024070470000014B802233 +:108C10005A60704700E100E0024B8022C3F88420D4 +:108C2000704700BF00E100E0074BD3F80014D3F811 +:108C300000240A43C3F800240022C3F858214FF44B +:108C40008002C3F8042370470070024070B5887832 +:108C5000404D00F07F0318220C26C4095A4306FB3E +:108C600004228E882A44C6F30A061681CA7802F0C6 +:108C70000302012A29D00121374A01FA03F5D4B9A8 +:108C800003F10C06B140C2F80413D2F8141503F531 +:108C900094732943C2F8141542F823402E4BC3F8AD +:108CA000180540F48070C3F80C05BFF36F8FBFF355 +:108CB0004F8F012014E002339940C2F80413D2F818 +:108CC00010352B43C2F81035E8E7082B09D04FF0D8 +:108CD000E023D3F8F00D10F0010001D000BE002019 +:108CE00070BD1D4BDCB9B5F8D42012B18022C3F899 +:108CF0001C250022C3F85021D3F8002312F40012DF +:108D000008BFC3F85421144B4FF44012C3F8042396 +:108D1000D3F8142542F48072C3F81425BEE700226C +:108D2000C3F82C21B5F8C82012B18022C3F81C2545 +:108D3000094BD3F8002312F4001208BFC3F85421E2 +:108D4000064AC3F80423D3F8102542F48072C3F80E +:108D50001025A3E778B3002000700240000820002F +:108D60001D4B2DE9F041802201241C4DDFF8788055 +:108D7000C3F88420274604F10C03A21C07FA02F270 +:108D800007FA03F31343C5F80833A30003F1804344 +:108D900003F51C330026182202FB04805E60314676 +:108DA0009E620134FBF7F8FA082C4FF01802E2D16A +:108DB0000B4BC5F808330B48C5F81C6531466E628D +:108DC000AE64FBF7E9FA044BC5F814758022C5F8C8 +:108DD00010755A60BDE8F08100E100E000700240CB +:108DE0000008300038B4002078B30020F7B51F46E3 +:108DF00001F07F0018231F4CCD090E4643430C2180 +:108E000001FB053104EB010C62500022ACF8047048 +:108E1000ACF8062018B1F5B1FFF760FE18E017BBFB +:108E2000154BD3F88034C3F3C0139D421BD01348B5 +:108E3000FFF724FE124B5B68C3F30803003B18BF27 +:108E4000012300933A463B463146384600F0B5FED2 +:108E5000012003B0F0BD1C44A37A002BF8D0A5720A +:108E6000FFF7A6FEF4E7002DD6D10648FFF706FE71 +:108E7000EEE700BF78B3002000700240507002405F +:108E800000ED00E04C70024011F07F0008B507D102 +:108E90000D4B01225A65BFF36F8FBFF34F8F08BD93 +:108EA0000828F8D0084B41F48072C909C3F8182586 +:108EB000F1D1064B182202FB00339A7A002AEAD03D +:108EC0009972FFF775FEE6E70070024078B3002064 +:108ED00011F0770F12D00A4B41F48072C3F80C15D1 +:108EE000C3F80C25CA09C3F8181504BF01F594711D +:108EF00043F82120BFF36F8FBFF34F8F704700BF40 +:108F000000700240174B0122002110B5C3F8142550 +:108F1000C3F810250A468B0003F1804303F51C3388 +:108F2000013108295A609A62F5D10E4B0E4C5A62F3 +:108F30009A64C3F85821D3F80014D3F800240A43E4 +:108F4000C3F80024D3F80023C3F80823074AC3F862 +:108F500004230021DC222046FBF71EFA4023A382D3 +:108F6000238110BD0070024078B300200514C001B9 +:108F70002DE9F04FB24BB34AD3F80013002385B06C +:108F80001C4601201D4621FA03F6F6070BD552F8C0 +:108F9000236046B100FA03F6344342F82350BFF38E +:108FA0006F8FBFF34F8F0133192BECD1E20706D53A +:108FB000FFF7A8FF00210122084600F0D5FD14F4B8 +:108FC000006FA14D08D09E4BD3F8A8369BB2A5F8F0 +:108FD000D230012385F8D730A30221D5984ED6F898 +:108FE000143513F4807302D0FFF7CCFD0123D6F8BB +:108FF0001025D70540F1188195F8D7305BB1B5F849 +:10900000D2200023012185F8D73092B20091184672 +:10901000882100F0D2FD01220321002000F094FD00 +:10902000660228D5864BD3F8006406F4E062F005AA +:10903000C3F8002406D50122C3F82C250421002002 +:1090400000F082FD71050FD57D4B0122C3F8082584 +:109050009A65D3F8002312F4001208BFC3F8542114 +:109060004FF40012C3F80423B20504D501220521F0 +:10907000002000F069FD23022BD5714BD3F880143A +:10908000C9B28DF80810D3F88424D2B28DF8092023 +:10909000D3F888048DF80A00D3F88C048DF80B00FF +:1090A000D3F890048DF80C00D3F894048DF80D00DB +:1090B000D3F898048DF80E00D3F89C348DF80F3057 +:1090C0004F0601D1052A04D0012202A9002000F098 +:1090D0005EFD5E4B23405BB195F8D830002B40F02D +:1090E000AB804FF0E023D3F8F03DDE0700D500BEA3 +:1090F000DFF85481DFF84891DFF858B14746002681 +:109100004FF0140A06F10C0324FA03F3D807F1B266 +:1091100022D50AFB0693082ED3F808273B6853FA9A +:1091200082F33B604FF0180303FB0653D2B2D8889A +:1091300012FA80F080B2D88000F08E803889904298 +:1091400040F08A80DB88BA889BB29A4240F28480E1 +:1091500011B95846FFF792FC0136092E07F118079E +:10916000D0D13B4B2340002B5BD0354BD3F86C94D4 +:10917000C3F86C94BFF36F8FBFF34F8F14F4806408 +:1091800007D0D3F88044D3F880341906C4F3C01450 +:1091900070D54FF0000A2C4F00264FF0180B29FA1B +:1091A00006F3DA07F0B201D4CEB9C4B1244B1422CD +:1091B00002FB0633FA68D3F8083652FA83F2FA60F3 +:1091C0000BFB0652518A89B251FA83F39BB2538248 +:1091D000538A398A9BB299424FD9FFF77FFC0136F7 +:1091E000082E07F11807DAD100241826012704F108 +:1091F000100329FA03F3DB07E0B203D464B9BAF130 +:10920000000F09D006FB0452B8F80410D3889BB2B3 +:1092100099423DD9FFF7CCFC0134082C08F118081D +:10922000E5D105B0BDE8F08F002B7FF4F4AE4FF42C +:109230000013C6F80833EEE6002385F8D83057E768 +:10924000007002400071024078B30020FCFB1F0058 +:10925000000400014C700240182303FB0653DA8817 +:10926000BA80DA8801230093002392B2184600F0F6 +:10927000A4FC71E74FF0010A8DE7528A01230093A5 +:10928000002340F0800192B2184600F096FCA6E759 +:109290009772C1E7012813B5044600F0C380022885 +:1092A00059D0002855D1784BD3F80025002A50D149 +:1092B0004FF48002C3F808234FF40062C3F800247F +:1092C000BFF36F8FBFF34F8FFFF7A8FB60B16F4BFA +:1092D000D3F8001C032269BB49F27531C3F8001CA6 +:1092E000C3F8142DC3F8001C4FF08053D3F830316D +:1092F000082B0CD1654BD3F8001CC022E9B949F208 +:109300007531C3F8001CC3F8142CC3F8001C5E4B65 +:109310000124C3F80045BFF36F8FBFF34F8FFFF7F2 +:1093200015FCB0B9FFF7FAFB50B102B0BDE8104030 +:10933000FFF79CBBC3F8142DD6E7C3F8142CE6E75F +:109340004FF08043C3F80001D3F800210192019A45 +:109350001C6002B010BD4C4CD4F804351BB1FFF7B3 +:10936000F5FB0028F5D1D4F800341B05FBD54FF4EC +:109370000063C4F80034BFF36F8FBFF34F8F4FF01B +:109380008053D3F83031082B0CD1404BD3F8001C5C +:1093900000293FD149F27532C3F8002CC3F8141CE0 +:1093A000C3F8002CFFF73AFB58B1384BD3F8001C38 +:1093B000A1BB49F27532C3F8002CC3F8141DC3F8E1 +:1093C000002C4FF08053D3F83031082B2E4B0AD1AC +:1093D00040F2E372C3F800284022C3F80428BFF328 +:1093E0006F8FBFF34F8F80220121C3F81C25C3F874 +:1093F0000413274BC3F884215A60FFF7A7FB00280A +:10940000FBD0214B0122C3F80425BFF36F8FBFF3BC +:109410004F8F9EE70022C3F8142CC3E70022C3F845 +:10942000142DCEE7184BD3F80025002A91D0002246 +:10943000C3F80425BFF36F8FBFF34F8F144980200B +:10944000C1F88400D3F80013C3F80813C3F800254B +:10945000BFF36F8FBFF34F8FFFF760FB78B1FFF75C +:1094600007FB0C4B5A68C2F30802003A18BF0122EE +:109470000221002002B0BDE8104000F065BB4FF0B3 +:1094800080435C60EDE700BF0070024000E00640F2 +:1094900000E100E000ED00E00A44034690B288429B +:1094A00002D39A89824202D25A89104480B270470C +:1094B00082888A4210B504D884898B1A9BB29C4258 +:1094C00003D243891A44891A8BB2038210BD9308D0 +:1094D00013B501EB8303044699420BD112F003024A +:1094E00006D0002301A8019301F040FF019B2360F7 +:1094F00002B010BD51F8040B2060EDE72DE9F043F8 +:1095000085B01446BDF83050AB4238BF4289A3EB5A +:1095100005091FFA89F938BFA9EB0209828838BF0B +:109520001FFA89F94A4588460746194605D2FFF7CA +:10953000BFFF058AB0F80490ADB2B9F1000F1FD09B +:10954000A14528BFA146BC88AC421DD9FA889DF828 +:1095500034003968661BA9EB04042C44B3B214FB35 +:1095600002F416FB02F60128B6B2A4B202FB051102 +:1095700016D099450BD802FB09F2404601F0F6FEE1 +:10958000484605B0BDE8F0832D1BADB2DCE732469E +:10959000404601F0EBFE3968224608EB0600EDE795 +:1095A000994506D819FB02F292B24046FFF78FFFA9 +:1095B000E6E726F00305ADB22A4640460191FFF7E3 +:1095C00086FF16F0030628D001990D44C6F1040168 +:1095D00089B2A1424FF0000328BF2146641A0393C9 +:1095E00003ABA4B2A8191A4685420CD13B68013ED0 +:1095F000164419448B420BD1039BC8F80030002C51 +:10960000BED02246D1E715F801CB03F801CBEBE73A +:1096100013F8012B06F8012FECE73968EFE713B5D3 +:10962000930800EB8303984209D112F0030204D09F +:109630000B68019301A901F099FE02B010BD0C68FE +:1096400040F8044BEFE770B59A42A2EB03041D46C5 +:1096500038BF4389A4B238BFE41A838838BFA4B2A4 +:10966000A3420E46114602D2FFF722FF848874B14E +:109670008288AA4208D9C2880168304602FB0511D7 +:1096800001F074FE012070BDAD1AADB2F1E72046C5 +:10969000F9E72DE9F0430746B0F80E908588048A73 +:1096A0001646C288007A85B01FFA89F9A4B288BB31 +:1096B000A145A9EB040038BF7C8980B23CBF001BE8 +:1096C00080B2281A80B2864228BF06464C46AC4279 +:1096D00028D2A5EB0408751B386825441FFA88FCBE +:1096E00015FB02F518FB02F8012B1FFA88F8ADB242 +:1096F00002FB040022D0664517D8724301F036FE03 +:10970000324649463846FFF7C7FEF881304605B075 +:10971000BDE8F083AE4221BF761B02FB0611A146D5 +:109720002E46D3E7641BA4B2D1E74246009101F074 +:109730001DFE009938682A464144DFE7664505D892 +:1097400016FB02F292B2FFF76AFFD9E728F0030492 +:10975000A4B22246CDE90001FFF761FF18F003082B +:10976000019929D0C8F104039BB2AB4200980A6862 +:10977000039228BF2B46013CED1A0DF10C0C20443E +:10978000ADB244466246013C1CF801EB00F801EF23 +:1097900014F0FF04F7D13868904408EB0304421E2C +:1097A000A04504D11844002DAAD02A46CBE718F8CA +:1097B00001CB02F801CFF3E73868F4E7B2F5004FC8 +:1097C00010D882805200C38092B29DF8003003729C +:1097D000531E838152420023C38101604281038270 +:1097E0000120704700207047C189028A89B292B275 +:1097F0009142A1EB020338BF428980889BB23CBFF3 +:109800009B1A9BB2984228BF18467047C289038AA8 +:1098100092B29BB2D31A58425841704710B5C189D1 +:10982000028A848889B292B2914238BF4089A1EB02 +:1098300002039BB23CBF1B1A9BB2E01A80B210BD60 +:1098400038B5C289038A04469BB292B2FFF7FBFE89 +:10985000218A054682B289B22046FFF71DFE20828A +:10986000284638BD73B5C389058A0026ADB20446C3 +:10987000CDE900569BB2FFF741FE218A054602461C +:1098800089B22046FFF708FE2082284602B070BD4C +:1098900038B5C589028AADB292B2AA42A5EB0203DD +:1098A00088BF42899BB288BF9B1A828888BF9BB2BF +:1098B0009A42044614D1007A90B938BD9B1A9BB2E3 +:1098C0009342FBD2E288206802FB030001F04EFDC8 +:1098D000012229462046FFF7DFFDE0810120ECE769 +:1098E0002B46EDE712B10023FFF7D3BE10467047B9 +:1098F0000023C381038283885B009BB25A1E5B42B4 +:10990000828143810120704701720120704700006D +:109910000B4B63B10B4B1B78834206D90A4B1B6878 +:1099200000EB400003EBC0007047C01AC0B2012832 +:1099300003D8064B00EB4000F4E70020704700BF5F +:109940000000000058B4002054B4002004BD0F00F3 +:1099500070B5104E054600242046FFF7D9FF436836 +:109960002846984733780134E4B20133A342F3DA4E +:10997000372200210848FAF70FFD1022FF2107487F +:10998000FAF70AFDBDE8704005481222FF21FAF7F8 +:1099900003BD00BF58B4002059B400205CB40020BF +:1099A0006CB4002037B50C460546C868019200F03B +:1099B000A5FDE368019A0021284603B0BDE83040C8 +:1099C000184773B5054614463AB90378012B04D1FC +:1099D00010460191FFF720F90199281DFFF758FF64 +:1099E00006462CB92B78012B02D12046FFF70EF941 +:1099F00036B94FF0E023D3F8F03DDB0700D500BEC9 +:109A000002B070BD024B5878003818BF0120704773 +:109A100059B40020024B1878C0F38000704700BF93 +:109A200059B40020014B1878704700BF90B4002053 +:109A3000F8B5164E3178054629BB154C1548372226 +:109A4000FAF7AAFC201DFFF753FF134B1C60134BC2 +:109A500023B11348AFF30080124B1860104F00245D +:109A60002046FFF755FF036898473B780134E4B27E +:109A70000133A342F4DA2846FFF7C6F82846FFF779 +:109A8000C5F8012333700120F8BD00BF90B4002059 +:109A9000A486002059B4002094B4002000000000E7 +:109AA00058B4002054B400201FB54378023B0A4646 +:109AB000032B12D8DFE803F0022A1921204B197872 +:109AC0006FF300011970197800246FF341011970C8 +:109AD0005C70197864F3820119701A4B014618689A +:109AE00004B0BDE81040FFF76CBF154B1978C907EB +:109AF00023D5197841F00401EEE711490B78DC0712 +:109B00001BD50B786FF382030B70E6E70C490B78DB +:109B10005B0712D50B786FF382030B700023CDE93E +:109B20000133039303788DF8043005238DF8053055 +:109B3000044B01A91868FFF744FF04B010BD00BF33 +:109B400059B4002094B400201FB50023CDE901339F +:109B50008DF804008DF8051001A811460393FFF756 +:109B6000A3FF05B05DF804FB1FB50023CDE9013369 +:109B700003938DF8040001238DF8081001A8114605 +:109B80008DF80530FFF790FF05B05DF804FB1FB5B9 +:109B9000144600230822CDE9013303938DF8040015 +:109BA00006230DEB02008DF8053001F0DFFB2146A6 +:109BB00001A8FFF779FF04B010BD1FB50024CDE95F +:109BC00001448DF8040007208DF805008DF8081079 +:109BD00001A89DF8181003928DF80930FFF764FF73 +:109BE00004B010BD1FB54FF40063CDE901300391FF +:109BF00001A81146FFF758FF05B05DF804FB00000F +:109C000038B58B7803F07F03082B05460C4608D93E +:109C10004FF0E023D3F8F03DDB0700D500BE002075 +:109C200038BD064B2046997801F00DFB0028EFD097 +:109C300021462846BDE83840FFF708B859B400204F +:109C40002DE9F047DDE9085681460C4690469A46D4 +:109C50000027B84501DC01200EE06378052B04D114 +:109C6000E37803F0030353450AD04FF0E023D3F821 +:109C7000F03DDA0702D40020BDE8F08700BEFAE725 +:109C800021464846FFF7BCFF38B94FF0E023D3F830 +:109C9000F03DDB07EFD500BEEEE7A378DA0914BF8D +:109CA00033702B70237801371C44D2E70B4B01F043 +:109CB0007F0203EB420303EBD1112031487910F00E +:109CC000010008D14B795B0706D44B7943F00403BC +:109CD0004B71012070470020704700BF59B400202D +:109CE0000B4B01F07F0203EB420303EBD111203158 +:109CF0004B7913F0010209D14B79C3F380005B0764 +:109D000005D54B7962F382034B7170470020704791 +:109D100059B4002070B5164D01F07F0605EB4605DD +:109D200005EBD11420346579ED0709D54FF0E02318 +:109D3000D3F8F03DDA0701D4002070BD00BEFBE788 +:109D4000657945F001056571FFF750F80028F4D1F9 +:109D5000637960F300036371637960F38203637175 +:109D60004FF0E023D3F8F03DDB07E5D500BEE4E794 +:109D700059B40020054B01F07F0203EB420303EBD3 +:109D8000D11191F8250000F00100704759B400206E +:109D900010B50B4B01F07F0203EB420303EBD11430 +:109DA000203463799B0709D4FFF76EF8637943F099 +:109DB00002036371637943F00103637110BD00BF57 +:109DC00059B4002010B50B4B01F07F0203EB4203A6 +:109DD00003EBD114203463799B0709D5FFF778F89A +:109DE00063796FF34103637163796FF30003637108 +:109DF00010BD00BF59B40020054B01F07F0203EBFA +:109E0000420303EBD11191F82500C0F340007047E5 +:109E100059B400202DE9F04F87B001F012FA002864 +:109E200000F09182AF4B1D682B78012B02D10020EE +:109E3000FEF7F2FE03A9281DFFF702FD2B78012B88 +:109E4000044602D10020FEF7E1FE002C00F07B82E8 +:109E50009DF80D30013B072B00F2B382DFE813F0D1 +:109E600008001300A8027E028D021F004A02AA0207 +:109E70009DF80C00FFF76CFD00F038FB9A4B9DF845 +:109E800010209A70CEE79DF80C00FFF761FD00F0FE +:109E90002DFB964B002BC5D0FEF78EFBC2E7924CF4 +:109EA0009DF80C50237843F00103237094F825307B +:109EB0006FF3000384F8253094F825306FF38203A4 +:109EC00084F8253094F826306FF3000384F82630A8 +:109ED00094F826306FF3820384F82630002000F0D7 +:109EE0000DFB9DF8106006F06002602A11D14FF062 +:109EF000E023D3F8F03DDC0700D500BE9DF80C0050 +:109F00000021FEF7C1FF9DF80C008021FEF7BCFF89 +:109F100088E7402A0DD176480028EFD000F0EEFA0D +:109F200004AA00212846AFF3008000287FF47AAF0E +:109F3000E4E706F01F06012E00F07181022E00F00A +:109F40009881002ED3D1202A0FD19DF814300F2BE9 +:109F5000D4D82344D878FFF7DBFC01460028CDD0C5 +:109F600004AA2846FFF71EFDDFE7002ABFD19DF8AF +:109F70001130092BBBD801A252F823F009A20F001F +:109F8000F7A10F00EF9E0F00E3A10F00EF9E0F005F +:109F9000A59F0F0021A10F00EF9E0F00BF9F0F0094 +:109FA000D59F0F0004A800F0AFFA9DF812102846C4 +:109FB000FEF73AFE237843F00203237032E763781A +:109FC0008DF80A3001230DF10A0204A9284600F099 +:109FD00059FA27E79DF812906378994537D063784E +:109FE0003BB12846FEF7BCFEA6782846FFF7B0FC3A +:109FF000A670B9F1000F2AD009F1FF30C0B2FEF708 +:10A00000E9F910B14378022B08D04FF0E023D3F8E0 +:10A01000F03DDF077FF56BAF00BE68E7C379C3F3A0 +:10A020008012C3F340131B0143EA4213227822F04B +:10A030003002134323704388C31800F109060093CC +:10A04000009BB3420AD82B4B0BB1FEF7B2FA84F84F +:10A05000019004A9284600F003FAE3E673780B2B7D +:10A0600003BF337896F80380F6184FF00108737831 +:10A07000042BCAD1009B9A1B93B201934FF0000BA3 +:10A080001D4B1B785FFA8BF70133BB42BDDB3846B3 +:10A09000FFF73EFC31468368019A8246284698477E +:10A0A0000828024639D9019B834236D3B8F1010F03 +:10A0B00006D1DAF8083011498B4208BF4FF0020888 +:10A0C0000021CBB298451DD83B4631460C48019241 +:10A0D00001F0E9F8084B019A1B7801339F421644BE +:10A0E000AEDD92E794B4002059B40020B9850F008A +:10A0F00000000000B3850F0058B40020A9A70F008E +:10A100006CB40020B078034454FA83F30131D8785A +:10A11000FF287FF47AAFDF70D3E70BF1010BAFE7D5 +:10A12000BDF81200030A5A1EC0B20E2A3FF6E6AE70 +:10A1300001A151F822F000BF75A10F009FA10F00EF +:10A14000C1A10F00FD9E0F00FD9E0F00D5A10F00C5 +:10A150009FA10F00FD9E0F00FD9E0F00FD9E0F00B2 +:10A16000FD9E0F00FD9E0F00FD9E0F00FD9E0F0047 +:10A1700087A10F00FEF72AF91223024604A92846F8 +:10A1800000F080F9D1E6934B002B3FF4B7AEAFF36C +:10A190000080024600283FF4AAAE4388EEE7022B77 +:10A1A00007D1FEF717F900283FF4A1AE4388024615 +:10A1B000E4E7894B002B3FF4A1AEAFF30080F2E758 +:10A1C000BDF81410FEF762F9024600283FF496AE7F +:10A1D0000378D3E7814B002B3FF490AEAFF30080C0 +:10A1E000F2E7BDF81230012B7FF488AE237843F0FC +:10A1F000080323702DE7BDF81230012B7FF47EAEEB +:10A2000023786FF3C303F4E72378C3F340129B086A +:10A2100003F002031343ADF80A300223D3E69DF89E +:10A2200014300F2B3FF66AAE2344D878FFF770FB4B +:10A23000014600283FF462AE04AA2846FFF7B2FBAD +:10A2400000287FF4EFAD9DF8103013F060047FF428 +:10A2500055AE9DF811300A3B012B3FF64FAE00F092 +:10A260004DF99DF811300A2B7FF4F3AE8DF80A40BA +:10A27000A8E69DF8141001F07F03082B3FF637AED7 +:10A2800004EB430303EBD113D87CFFF741FB0746F4 +:10A290002AB100283FF432AE04AA014661E69DF8D7 +:10A2A000113003F0FD02012A08D0002B7FF41FAE0D +:10A2B0002846FFF7A1FDADF80A00AEE7BDF8122071 +:10A2C00022B9012B284612D1FFF77CFD002F3FF465 +:10A2D000A9AD04AA39462846FFF764FB002000F028 +:10A2E0000DF994F82630DE073FF59CADB1E6FFF797 +:10A2F0004FFDEBE79DF81010394B01F07F0403EBA5 +:10A30000440303EBD11393F825006FF3000083F8A7 +:10A31000250093F825006FF3820083F825003CB9EF +:10A32000059B9DF811209DF80C0000F0FBF879E5E5 +:10A33000D87CFFF7EDFA48B94FF0E023D3F8F03DB1 +:10A34000D80700D500BE07B0BDE8F08F0469059BB3 +:10A350009DF811209DF80C00A04763E5204B1A786A +:10A36000D1077FF55FAD1F4A002A3FF45BAD187837 +:10A37000C0F3C000AFF3008054E5194B1B78DA0737 +:10A380007FF550AD184B002B3FF44CADAFF3008080 +:10A3900048E5FFF7BDFA436913B19DF80C009847F3 +:10A3A0000134124B1B78E0B201338342F1DA39E514 +:10A3B0000024F6E7049B002B3FF434AD0598984742 +:10A3C00030E54FF0E023D3F8F03DDB077FF52AAD11 +:10A3D00000BE27E5000000000000000000000000B3 +:10A3E00059B40020000000000000000058B4002014 +:10A3F00037B514490446CA89888991F90050831AEF +:10A400009BB2402B28BF4023002D10DA904214D07D +:10A410001A4689680C48019300F0A8FF0A4A019B7C +:10A420008021204603B0BDE83040FFF773BC904266 +:10A430004FF0000103D10022F3E78021FBE7024A3D +:10A44000EFE700BF58B500206CB5002011F0800F79 +:10A450004FF000031A460CBF80211946FFF75ABC83 +:10A4600030B4074C05460B4608684968224603C2CB +:10A470000022C4E902222846197830BCFFF7E6BF63 +:10A4800058B50020F8B5184E0C46054608684968CE +:10A49000B260374603C70021F181E1888B4228BFB3 +:10A4A0000B46B381E18889B153B14AB94FF0E0233B +:10A4B000D3F8F03DDA0701D40020F8BD00BEFBE779 +:10A4C0002846FFF795FF30B10120F6E721782846AE +:10A4D000FFF7BCFFF7E74FF0E023D3F8F03DDB07D1 +:10A4E000EAD500BEE9E700BF58B5002002481422B3 +:10A4F0000021F9F751BF00BF58B50020014B18618A +:10A50000704700BF58B5002010B50246044C0068E3 +:10A510005168234603C30023C4E9023310BD00BFC2 +:10A5200058B5002070B52D4C1E462378C909B1EBF3 +:10A53000D31F054618D04EB14FF0E023D3F8F03DBD +:10A54000DA0701D4002070BD00BEFBE7244B13B135 +:10A550002146AFF3008023690BB90120F3E71F4ABE +:10A56000022128469847F8E794F90030002B06DBD3 +:10A57000A0680028E6D01B49324600F0F7FEA2682A +:10A58000E38932443344A260E2889BB29A42E38179 +:10A5900001D03F2E1ED823696BB921782846FFF7DA +:10A5A00055FF0028D9D14FF0E023D3F8F03DDB0769 +:10A5B000C8D500BEC7E70121084A2846984701468A +:10A5C0000028EAD12846FEF75FFC80212846FEF7E6 +:10A5D0005BFCC2E72846FFF70BFFE2E758B5002017 +:10A5E000000000006CB5002070B500F110050446B5 +:10A5F0002846FFF713F93F2817D9E1780020FFF725 +:10A6000055FB90B12846FFF709F93F28E17807D9B3 +:10A6100004F638024023BDE870400020FFF77ABB03 +:10A62000BDE870400020FFF75BBB70BD08B5044B70 +:10A6300040F6B80202FB00301030FFF7D5F808BD35 +:10A64000ACB5002070B540F6B804074E444304F1A1 +:10A65000100092B23044FFF705F905463019FFF7B4 +:10A66000C3FF284670BD00BFACB500202DE9F04106 +:10A670000446FFF7C7F910B90020BDE8F081FFF7E5 +:10A68000C9F906460028F7D140F6B801164D4C43EB +:10A6900004F12408A8444046FFF7A6F80028EBD0B0 +:10A6A0002F193046B978FFF701FB0028E4D004F6F3 +:10A6B00078042544294640224046FFF7D3F8B9786C +:10A6C000044668B103462A463046FFF723FB48B9E3 +:10A6D0004FF0E023D3F8F03DDB07CDD500BECCE74B +:10A6E000FFF7FEFA2046C8E7ACB5002070B50B4C6A +:10A6F00040F6B80303FB0044243492B205462046DA +:10A70000FFF7F0F806462046FFF76EF83F2802D91B +:10A710002846FFF7ABFF304670BD00BFACB5002048 +:10A7200037B5144C40F6B80200212046F9F734FE44 +:10A73000FF234FF4424201256371E2800023082287 +:10A7400063812273009304F138012B464FF4806239 +:10A7500004F110002581FFF731F800952B464FF4E6 +:10A76000806204F5876104F12400FFF727F803B045 +:10A7700030BD00BFACB5002010B50A4C0021052249 +:10A780002046F9F709FE04F110002434FFF7B0F871 +:10A790002046FFF7ADF820460121BDE81040FFF745 +:10A7A000B3B800BFACB50020F7B54B79022B064615 +:10A7B00003D00025284603B0F0BD8B79022BF8D1D9 +:10A7C000204FBB787BBB8B783B700C7809250C4401 +:10A7D00003E023781D44ADB21C446378242B1BD1C5 +:10A7E0009542F6D96378042B12D163790A2B0FD1E5 +:10A7F000154B277801930133009302231A46E11980 +:10A800003046FFF71DFA70B10E3517FA85F5ADB277 +:10A810000C48FFF7E9FECDE7052BE3D12146304692 +:10A82000FFF7EEF938B94FF0E023D3F8F03DDB073E +:10A83000BFD500BEBDE7A3787B7023781D44ADB2C1 +:10A840001C44CFE7ACB50020AEB5002070B50B4678 +:10A850001146127802F06002202A45D1234E8A88E0 +:10A860003478944240D14A78203A032A3CD8DFE831 +:10A8700002F00213162F2BB91D4A0723FFF702FE21 +:10A88000012070BD022BFBD11A4B002BF8D01849C8 +:10A890000020AFF30080F3E7002BF1D1ECE713B910 +:10A8A000FFF7DEFDECE7022BEAD14C881248347149 +:10A8B00004F0010585F00101FFF726F80F4B002B8E +:10A8C000DED0C4F3400229460020AFF30080D7E772 +:10A8D000002BE5D0022BD3D1094B002BD0D04988D7 +:10A8E0000020AFF30080CBE70020CAE7ACB5002022 +:10A8F000B2B5002000000000D0B50020000000002C +:10A90000000000002DE9F347374D1C46EB788B42E1 +:10A9100007460E4607D0AB788B4258D1AB78B3428E +:10A9200032D001245CE0A2B205F6380105F1100036 +:10A93000FEF7D8FF2D4B2BB92D4BEBB92A48FFF76B +:10A9400053FEEBE76B79FF2BF6D005F638094FF095 +:10A95000000805F1100AA045EED019F8013B6A790C +:10A960009A4206D15046FEF751FF10B96979AFF30C +:10A97000008008F10108EEE71E48FEF747FF0028B7 +:10A98000DCD1FDF789F9D9E71B4B13B10020AFF3F8 +:10A9900000800020FFF76AFE0028C2D11748FEF7AA +:10A9A00023FF0028BDD1002CBBD014F03F03B8D149 +:10A9B000A97801933846FFF779F9019B04460028EE +:10A9C000AFD0A9781A463846FFF7A4F908E04FF04F +:10A9D000E023D3F8F04D14F0010401D000BE0024B0 +:10A9E000204602B0BDE8F087ACB5002000000000B2 +:10A9F000997C0F00BCB5002000000000D0B50020FD +:10AA000030B4104B02249A6B83F82C10996883F8A9 +:10AA1000304093F83C408A1A9A6224B942F2050504 +:10AA20009D8783F83E4051B14AB11A7BD20930BCB0 +:10AA300014BF93F82E1093F82F10FFF7A9B930BC6C +:10AA4000704700BF64CE002038B5154B154C054645 +:10AA500073B1607BAFF3008050B942F2077384F8A2 +:10AA60003E00A38728460121BDE83840FFF7C8BF54 +:10AA7000A26BA36894F82F109B1AB3F5805F28BFD0 +:10AA80004FF48053084A9BB22846FFF743F930B988 +:10AA90004FF0E023D3F8F03DDB0700D500BE38BD12 +:10AAA0000000000064CE002064BE002073B5234C7B +:10AAB000E28AA36852BA92B2054612B1B3FBF2F22F +:10AAC00092B2A06BD4F81110B0FBF2F61B1AB3F5DA +:10AAD000805F28BF4FF4805309BA009302FB16022F +:10AAE000174B607B3144FDF7DBFB031E0CDA43F2AE +:10AAF0000333A38701210023284684F83E3002B0A7 +:10AB0000BDE87040FFF77CBF94F82E1006D100938B +:10AB10001A462846FFF751F802B070BD084A9BB2AA +:10AB20002846FFF7F7F80028F6D14FF0E023D3F8D6 +:10AB3000F03DDB07F0D500BEEEE700BF64CE00209D +:10AB400064BE0020C28A836852BA92B223B9002A36 +:10AB50000CBF002002207047C17B282904D1017B53 +:10AB6000C90906D1022070472A2902D1017BC909EF +:10AB7000F8D122B1934234BF022000207047012057 +:10AB800070470000044880F83C1080F83D2080F8B1 +:10AB90003E300120704700BF64CE002002484022B2 +:10ABA0000021F9F7F9BB00BF64CE00200248402223 +:10ABB0000021F9F7F1BB00BF64CE002073B54B79DB +:10ABC000082B054602D0002002B070BD8B79062B01 +:10ABD000F9D1CB79502BF6D1162A07D84FF0E023C4 +:10ABE000D3F8F03DD907EED500BEECE7164C8B78D4 +:10ABF00084F82D3004F12E030E78019304F12F0315 +:10AC0000009302231A463144FFF71AF838B94FF07F +:10AC1000E023D3F8F03DDA07D5D500BED4E7002312 +:10AC200084F8303094F82F101F2322462846FFF76F +:10AC300071F830B94FF0E023D3F8F03DDB0700D5D1 +:10AC400000BE1720C0E700BF64CE00207FB50646D7 +:10AC50001546A1B9137803F07F02022A48D16C7817 +:10AC6000012C45D16A88002A42D1AB883C4D95F829 +:10AC70003020042ADBB204D11946FFF789F80120FD +:10AC80001AE095F82E10994218D1022AF7D1AA6B32 +:10AC9000AB689B1AAB62032385F8303005F12002C4 +:10ACA0000D23FFF737F80028E9D14FF0E023D3F860 +:10ACB000F03DDB0752D500BE04B070BD95F82F10F3 +:10ACC0009942DCD1002ADAD10191FFF753F80199BA +:10ACD0000028D4D13046FFF78FF80028CFD10023C9 +:10ACE00085F830301E4A95F82F101F233046D8E7DC +:10ACF00003F06003202B31D16B78FE2B13D0FF2B98 +:10AD00002CD16B8853BBE9888AB23ABB144B3046CE +:10AD100099872946C3E90D2283F8302083F83E2025 +:10AD2000FFF79EFBABE76B88C3B9EB88012B15D10E +:10AD30008DF80F300B4B1BB1AFF300808DF80F0077 +:10AD40009DF80F3053B1013B8DF80F300DF10F021C +:10AD5000012329463046FFF795FB90E70020ABE73B +:10AD600064CE0020000000002DE9F041AB4C94F8C7 +:10AD70003070012F90B005461E4600F0A181032FD0 +:10AD800000F00D82002F41D194F82F308B4201D07A +:10AD9000012013E01F2E03D12268A14B9A4210D04C +:10ADA00094F82E100423284684F83030FEF7F0FF84 +:10ADB00094F82F102846FEF7EBFF002010B0BDE8F6 +:10ADC000F081984B23626368E67BD4F8088084F8AE +:10ADD0002C70C4E90937012384F8303006F0FD03F4 +:10ADE000282BC4E90D872BD12046FFF7ABFE014687 +:10ADF00018B12846FFF704FE08E0B8F1000F56D05E +:10AE0000282E40F0A8812846FFF750FE94F83030F5 +:10AE1000022BBDD194F82E102846FEF7EDFF002836 +:10AE2000B6D1A368A26B94F82E10934240F2E08151 +:10AE3000207BC00900F0DC812846FEF7A9FFA7E7C8 +:10AE4000B8F1000F17D0237BDB0914D1B8F5805F70 +:10AE500001D90121CDE7744A1FFA88F32846FEF78D +:10AE600059FF0028D2D14FF0E023D3F8F03DDA07A4 +:10AE7000A3D500BEA2E7252E677B08D8192E1AD8C5 +:10AE8000032E00F0F780122E00F09F8096B394F806 +:10AE90003C30002BDDD1A38E634A6449607BFDF713 +:10AEA000EDF9031ED5DB60D1A368002BD1D10223BD +:10AEB00084F83030AAE71A3E0B2EE8D801A353F8E5 +:10AEC00026F000BF35B00F0015AF0F008FAE0F009A +:10AED0008FAE0F008FAE0F008FAE0F008FAE0F0042 +:10AEE0008FAE0F008FAE0F0085AF0F008FAE0F003B +:10AEF0002FAF0F003846FDF7BFF90028D4D194F8E2 +:10AF00003C30002BC3D140F20243A387002384F8D6 +:10AF10003E30BCE7464B002BC6D0E17C3846C1F33F +:10AF2000400301F001020909FDF74EFAE5E70DF1D2 +:10AF3000160206A93846FDF73FFA069B13B1BDF885 +:10AF400016203AB994F83C30002BA0D140F20242CE +:10AF5000A287DCE712BA013B1BBA0892324807937A +:10AF6000082207A900F002FA0823A06800283FF48D +:10AF700070AF834228BF034663632B4A94F82E10B8 +:10AF80009BB26BE70023CDE90733099308238DF8C3 +:10AF90001F300DF11602022306A938468DF8243021 +:10AFA000FDF70AFA069A002ACCD0BDF81630002B1D +:10AFB000C8D012BA5BBA08921B48ADF826300C22F2 +:10AFC00007A900F0D3F90C23CFE72422002107A81A +:10AFD000F9F7E2F980238DF81D30082202232021A1 +:10AFE00009A88DF81E308DF81F30F9F7D5F9102219 +:10AFF00020210BA8F9F7D0F9042220210FA8F9F796 +:10B00000CBF90FAB0BAA09A93846FDF701F90648A1 +:10B01000242207A900F0AAF92423A6E764CE002081 +:10B02000555342435553425364BE002073CE002013 +:10B03000C9830F0003238DF81C3000238DF81D30C9 +:10B040008DF81E308DF81F30704B8BB13846AFF342 +:10B0500000809DF81E3080F0010060F3C7130422C9 +:10B060006B488DF81E3007A900F080F904237CE7B7 +:10B070000120EEE71222002107A8F9F78DF9F0234D +:10B0800094F83C208DF81C300A238DF823304FF0C3 +:10B09000000362F303038DF81E3094F83D308DF801 +:10B0A00028305B4894F83E308DF82930122207A9E9 +:10B0B00000F05CF90023A38784F83E30122354E7A4 +:10B0C000E37BA06B282B06D1636B30449842A063CE +:10B0D000BFF4EDAE97E62A2B41D1E28A52BA92B282 +:10B0E0001AB1A368B3FBF2F292B2D4F81110484F30 +:10B0F000B0FBF2FC09BA02FB1C020096607B3B46E7 +:10B100006144FDF7EFF8002809DAA36B3344A36329 +:10B1100043F20333A387002384F83E3099E6864246 +:10B1200012D9321A40B1A36B03920344391838463E +:10B13000A36300F0B5F9039A94F82F10002300934D +:10B140002846FEF73AFD61E6A36B1E44636BA663D7 +:10B150009E42BFF4ACAE2846FFF776FC56E6237B52 +:10B160003044DB09A0630CD1A38E294A607B04F133 +:10B170000F01FDF783F8002803DA39462846FFF768 +:10B180003FFCD4E90D329A42BFF491AE4FF0E02378 +:10B19000D3F8F03DDB077FF539AE00BE36E694F814 +:10B1A0002E308B427FF432AE0D2E7FF42FAEE37B38 +:10B1B000282B09D02A2B14D0164B53B1607B04F1F5 +:10B1C0000F01AFF3008004E0134B13B1607BAFF3CA +:10B1D0000080002384F83030104A94F82F101F2389 +:10B1E0003CE60F4B002BF4D0607BFDF793F8F0E7C3 +:10B1F0009B1AA362032384F830300A4A0D232846A1 +:10B20000FEF788FD00287FF4C3AD2CE600000000A7 +:10B2100064BE0020000000000000000064CE00209A +:10B2200015830F0084CE002008B50020FEF700FC37 +:10B2300030B94FF0E023D3F8F03DDB0700D500BE76 +:10B2400008BDFEF7EFBB8388C07800F0030002283A +:10B25000C3F30A0315D003281DD001280FD10229FA +:10B2600040F2FF3208BF4FF480629A420FD24FF093 +:10B27000E023D3F8F00D10F0010008D000BE00204C +:10B280007047022904D1B3F5007FF0D10120704747 +:10B29000402BFBD9EBE702290CBF4FF48062402220 +:10B2A0009A42F3D2E3E730B50A44914200D330BD6D +:10B2B0004C78052C06D18C7804F07F0500EB450511 +:10B2C000E4092B550C782144EFE700000649074AB2 +:10B2D000074B9B1A03DD043BC858D050FBDCF9F741 +:10B2E00063FEF8F7D5FF000068BD0F000080002066 +:10B2F000C8860020FEE7FEE7FEE7FEE7FEE7FEE782 +:10B30000FEE7FEE7FEE7FEE7032A70B515D940EA3F +:10B31000010C1CF0030F04460B4621D119462046B0 +:10B320000E680568B54204F1040403F1040317D163 +:10B33000043A032A20461946F0D8541EA2B100F15F +:10B34000FF3C013901E0C3180CD01CF801EF11F8E3 +:10B35000012F9645A4EB0C03F5D0AEEB020070BDB7 +:10B36000541EECE7184670BD104670BD844641EA95 +:10B37000000313F003036DD1403A41D351F8043B6D +:10B3800040F8043B51F8043B40F8043B51F8043BBF +:10B3900040F8043B51F8043B40F8043B51F8043BAF +:10B3A00040F8043B51F8043B40F8043B51F8043B9F +:10B3B00040F8043B51F8043B40F8043B51F8043B8F +:10B3C00040F8043B51F8043B40F8043B51F8043B7F +:10B3D00040F8043B51F8043B40F8043B51F8043B6F +:10B3E00040F8043B51F8043B40F8043B51F8043B5F +:10B3F00040F8043B51F8043B40F8043B403ABDD2CE +:10B40000303211D351F8043B40F8043B51F8043B6F +:10B4100040F8043B51F8043B40F8043B51F8043B2E +:10B4200040F8043B103AEDD20C3205D351F8043BFE +:10B4300040F8043B043AF9D2043208D0D2071CBFCA +:10B4400011F8013B00F8013B01D30B8803806046F3 +:10B45000704700BF082A13D38B078DD010F0030369 +:10B460008AD0C3F10403D21ADB071CBF11F8013BD9 +:10B4700000F8013B80D331F8023B20F8023B7BE728 +:10B48000043AD9D3013A11F8013B00F8013BF9D253 +:10B490000B7803704B7843708B78837060467047ED +:10B4A00088420DD98B1883420AD900EB020CBAB13D +:10B4B000624613F801CD02F801CD9942F9D17047E7 +:10B4C0000F2A0ED8034602F1FF3C4AB10CF1010CE1 +:10B4D000013B8C4411F8012B03F8012F6145F9D190 +:10B4E000704740EA01039B0750D1A2F1100370B5E9 +:10B4F00001F1200C23F00F0501F1100E00F11004F2 +:10B50000AC441B095EF8105C44F8105C5EF80C5CFF +:10B5100044F80C5C5EF8085C44F8085C5EF8045C77 +:10B5200044F8045C0EF1100EE64504F11004E9D174 +:10B53000013312F00C0F01EB031102F00F0400EBCA +:10B54000031327D0043C24F003064FEA940C1E4456 +:10B550001C1F8E465EF8045B44F8045FB442F9D1C8 +:10B560000CF1010402F0030203EB840301EB8401FC +:10B5700002F1FF3C4AB10CF1010C013B8C4411F883 +:10B58000012B03F8012F6145F9D170BD02F1FF3C99 +:10B5900003469BE72246EBE7830710B5044610D12C +:10B5A0000268A2F1013323EA020313F0803F08D1BD +:10B5B00050F8042FA2F1013323EA020313F0803F75 +:10B5C000F6D003781BB110F8013F002BFBD100F03F +:10B5D00003F8204610BD00BF80EA0102844612F045 +:10B5E000030F4FD111F0030F32D14DF8044D11F07C +:10B5F000040F51F8043B0BD0A3F101329A4312F02F +:10B60000803F04BF4CF8043B51F8043B16D100BF07 +:10B6100051F8044BA3F101329A4312F0803FA4F198 +:10B6200001320BD14CF8043BA24312F0803F04BF1F +:10B6300051F8043B4CF8044BEAD023460CF8013B8C +:10B6400013F0FF0F4FEA3323F8D15DF8044B704736 +:10B6500011F0010F06D011F8012B0CF8012B002A74 +:10B6600008BF704711F0020FBFD031F8022B12F063 +:10B67000FF0F16BF2CF8022B8CF8002012F47F4F1E +:10B68000B3D1704711F8012B0CF8012B002AF9D126 +:10B69000704700BF00000000000000000000000034 +:10B6A000000000000000000000000000000000009A +:10B6B000000000000000000000000000000000008A +:10B6C00090F800F06DE9024520F007016FF0000CE2 +:10B6D00010F0070491F820F040F049804FF000048A +:10B6E0006FF00700D1E9002391F840F000F1080065 +:10B6F00082FA4CF2A4FA8CF283FA4CF3A2FA8CF39D +:10B700004BBBD1E9022382FA4CF200F10800A4FA03 +:10B710008CF283FA4CF3A2FA8CF3E3B9D1E9042357 +:10B7200082FA4CF200F10800A4FA8CF283FA4CF38E +:10B73000A2FA8CF37BB9D1E9062301F1200182FA48 +:10B740004CF200F10800A4FA8CF283FA4CF3A2FA4E +:10B750008CF3002BC6D0002A04BF04301A4612BA5C +:10B76000B2FA82F2FDE8024500EBD2007047D1E95F +:10B77000002304F00305C4F100004FEAC50514F0EE +:10B78000040F91F840F00CFA05F562EA05021CBFBF +:10B7900063EA050362464FF00004A9E7F0B5254FC0 +:10B7A000A2F1020E164605460C460FCF8BB0EC46B2 +:10B7B000ACE80F000FCFACE80F0097E803004CF89F +:10B7C000040BBEF1220F8CF800102ED804F1FF3EBE +:10B7D00070464FF0000CB5FBF6F206FB125328330F +:10B7E0006B44614613F828CC00F801CF2B469E42EB +:10B7F00001F1010C1546EED9002304F80C3089B193 +:10B80000A44472461EF8010F1CF8015D8EF800502A +:10B810006FEA0E0302322344121B0B449A428CF847 +:10B820000000EEDB20460BB0F0BD002020700BB016 +:10B83000F0BD00BF34BD0F00FFF7B0BF53B94AB928 +:10B84000002908BF00281CBF4FF0FF314FF0FF3028 +:10B8500000F074B9ADF1080C6DE904CE00F006F803 +:10B86000DDF804E0DDE9022304B070472DE9F0477C +:10B87000089D04468E46002B4DD18A42944669D9D4 +:10B88000B2FA82F252B101FA02F3C2F1200120FAB7 +:10B8900001F10CFA02FC41EA030E94404FEA1C4805 +:10B8A000210CBEFBF8F61FFA8CF708FB16E341EA01 +:10B8B000034306FB07F199420AD91CEB030306F187 +:10B8C000FF3080F01F81994240F21C81023E6344A8 +:10B8D0005B1AA4B2B3FBF8F008FB103344EA03444C +:10B8E00000FB07F7A7420AD91CEB040400F1FF3361 +:10B8F00080F00A81A74240F207816444023840EA9E +:10B900000640E41B00261DB1D4400023C5E90043D6 +:10B910003146BDE8F0878B4209D9002D00F0EF8059 +:10B920000026C5E9000130463146BDE8F087B3FA8C +:10B9300083F6002E4AD18B4202D3824200F2F98074 +:10B94000841A61EB030301209E46002DE0D0C5E977 +:10B95000004EDDE702B9FFDEB2FA82F2002A40F0C3 +:10B960009280A1EB0C014FEA1C471FFA8CFE0126C6 +:10B97000200CB1FBF7F307FB131140EA01410EFB6A +:10B9800003F0884208D91CEB010103F1FF3802D211 +:10B99000884200F2CB804346091AA4B2B1FBF7F00B +:10B9A00007FB101144EA01440EFB00FEA64508D92E +:10B9B0001CEB040400F1FF3102D2A64500F2BB806B +:10B9C0000846A4EB0E0440EA03409CE7C6F12007BA +:10B9D000B34022FA07FC4CEA030C20FA07F401FA00 +:10B9E00006F31C43F9404FEA1C4900FA06F3B1FB89 +:10B9F000F9F8200C1FFA8CFE09FB181140EA0141EE +:10BA000008FB0EF0884202FA06F20BD91CEB01018A +:10BA100008F1FF3A80F08880884240F28580A8F1E2 +:10BA200002086144091AA4B2B1FBF9F009FB101134 +:10BA300044EA014100FB0EFE8E4508D91CEB0101D2 +:10BA400000F1FF346CD28E456AD90238614440EA75 +:10BA50000840A0FB0294A1EB0E01A142C846A646F5 +:10BA600056D353D05DB1B3EB080261EB0E0101FA7E +:10BA700007F722FA06F3F1401F43C5E900710026DB +:10BA80003146BDE8F087C2F12003D8400CFA02FC31 +:10BA900021FA03F3914001434FEA1C471FFA8CFE41 +:10BAA000B3FBF7F007FB10360B0C43EA064300FB31 +:10BAB0000EF69E4204FA02F408D91CEB030300F1CF +:10BAC000FF382FD29E422DD9023863449B1B89B286 +:10BAD000B3FBF7F607FB163341EA034106FB0EF30F +:10BAE0008B4208D91CEB010106F1FF3816D28B42BC +:10BAF00014D9023E6144C91A46EA004638E72E4688 +:10BB0000284605E70646E3E61846F8E64B45A9D27F +:10BB1000B9EB020864EB0C0E0138A3E74646EAE7EE +:10BB2000204694E74046D1E7D0467BE7023B61449C +:10BB300032E7304609E76444023842E7704700BF05 +:10BB4000F8B500BFF8BC08BC9E467047F8B500BF0A +:10BB5000F8BC08BC9E467047088000200010020018 +:10BB60000338FDD87047000000000000000000000E +:10BB70000338FDD87047010000000000089900203C +:10BB80000338FDD87047416461444655004D6573E4 +:10BB90006874617374696300302E392E32207331FA +:10BBA000343020372E332E3000000000000000001B +:10BBB00000000000000000000023D1BCEA5F7823F1 +:10BBC00015DEEF12120000000000000070B000202F +:10BBD0004164616672756974006E52462055463242 +:10BBE00000303132333435363738394142434445F9 +:10BBF00046006E52462053657269616C0009045319 +:10BC00006F66744465766963653A200053002E00C0 +:10BC10006E6F7420666F756E640D0A00EB3C905574 +:10BC20004632205546322000020101000240000049 +:10BC300000F80201010001000000000009010100FC +:10BC4000800029420042004D455348544153544915 +:10BC5000430046415431362020203C21646F6374F8 +:10BC60007970652068746D6C3E0A3C68746D6C3E3A +:10BC70003C626F64793E3C7363726970743E0A6C17 +:10BC80006F636174696F6E2E7265706C6163652895 +:10BC90002268747470733A2F2F6275796D656163D1 +:10BCA0006F666665652E636F6D2F6D61726B2E62B8 +:10BCB0006972737322293B0A3C2F73637269707433 +:10BCC0003E3C2F626F64793E3C2F68746D6C3E0A77 +:10BCD00000000000494E464F5F554632545854000C +:10BCE00024850020494E44455820202048544D00CA +:10BCF0005ABC0F0043555252454E5420554632000F +:10BD00000000000021A70F0079A70F00A9A70F00CE +:10BD10004DA80F0005A90F00000000009DAB0F000B +:10BD2000ADAB0F00BDAB0F004DAC0F0069AD0F0008 +:10BD30000000000030313233343536373839616233 +:10BD4000636465666768696A6B6C6D6E6F7071724B +:10BD5000737475767778797A00000000000000002F +:10BD60002885FF7F010000000880002000000000FF +:10BD700000000000F48200205C830020C4830020C7 +:10BD800000000000000000000000000000000000B3 +:10BD900000000000000000000000000000000000A3 +:10BDA0000000000000000000000000000000000093 +:10BDB0000000000000000000000000000000000083 +:10BDC0000000000000000000000000000000000073 +:10BDD0000000000000000000000000000000000063 +:10BDE0000000000000000000000000000000000053 +:10BDF0000000000000000000000000000000000043 +:10BE00000000000000000000000000000000000032 +:10BE10000000000000000000010000000000000021 +:10BE20000E33CDAB34126DE6ECDE05000B000000E6 +:10BE30000000000000000000000000000000000002 +:10BE400000000000000000000000000000000000F2 +:10BE500000000000000000000000000000000000E2 +:10BE600000000000000000000000000000000000D2 +:10BE700000000000000000000000000000000000C2 +:10BE800000000000000000000000000000000000B2 +:10BE900000000000000000000000000000000000A2 +:10BEA0000000000000000000000000000000000092 +:10BEB0000000000000000000000000000000000082 +:10BEC0000000000000000000000000000000000072 +:10BED0000000000000000000000000000000000062 +:10BEE0000000000000000000000000000000000052 +:10BEF0000000000000000000000000000000000042 +:10BF00000000000000000000000000000000000031 +:10BF10000000000000000000000000000000000021 +:10BF20000000000000000000000000000000000011 +:10BF30000000000000000000000000000000000001 +:10BF400000000000000000000000000000000000F1 +:10BF500000000000000000000000000000000000E1 +:10BF600000000000000000000000000000000000D1 +:10BF700000000000000000000000000000000000C1 +:10BF800000000000000000000000000000000000B1 +:10BF900000000000000000000000000000000000A1 +:10BFA0000000000000000000000000000000000091 +:10BFB0000000000000000000000000000000000081 +:10BFC0000000000000000000000000000000000071 +:10BFD0000000000000000000000000000000000061 +:10BFE0000000000000000000000000000000000051 +:10BFF0000000000000000000000000000000000041 +:10C000000000000000000000000000000000000030 +:10C010000000000000000000000000000000000020 +:10C020000000000000000000000000000000000010 +:10C030000000000000000000000000000000000000 +:10C0400000000000000000000000000000000000F0 +:10C0500000000000000000000000000000000000E0 +:10C0600000000000000000000000000000000000D0 +:10C0700000000000000000000000000000000000C0 +:10C0800000000000000000000000000000000000B0 +:10C0900000000000000000000000000000000000A0 +:10C0A0000000000000000000000000000000000090 +:10C0B0000000000000000000000000000000000080 +:10C0C0000000000000000000000000000000000070 +:10C0D0000000000000000000000000000000000060 +:10C0E0000000000000000000000000000000000050 +:10C0F0000000000000000000000000000000000040 +:10C10000000000000000000000000000000000002F +:10C11000000000000000000000000000000000001F +:10C12000000000000000000000000000000000000F +:10C1300000000000000000000000000000000000FF +:10C1400000000000000000000000000000000000EF +:10C1500000000000000000000000000000000000DF +:10C1600000000000000000000000000000000000CF +:10C1700000000000000000000000000000000000BF +:10C1800000000000000000000000000000000000AF +:10C190000000000000000000FFFFFFFF7C7F002088 +:10C1A0000090D003FF00FFFF320000007D7B0F00F6 +:10C1B000F17B0F0001090262000301008032080BCD +:10C1C000000202020000090400000102020004054E +:10C1D0002400200105240100010424020205240694 +:10C1E00000010705810308001009040100020A008C +:10C1F000000007050202400000070582024000001F +:10C200000904020002080650050705030240000069 +:10C210000705830240000009024B00020100803242 +:10C22000080B0002020200000904000001020200E3 +:10C230000405240020010524010001042402020554 +:10C24000240600010705810308001009040100020B +:10C250000A000000070502024000000705820240B4 +:10C26000000012010002EF0201409A2329000001A0 +:10C2700001020301FDBB0F008DBB0F008DBB0F0042 +:10C2800064B30020F2BB0F00D9BB0F00554632202B +:10C29000426F6F746C6F6164657220302E392E327C +:10C2A000206C69622F6E726678202876322E302ECE +:10C2B0003029206C69622F74696E79757362202849 +:10C2C000302E31322E302D3134352D673937373518 +:10C2D000653736393129206C69622F75663220281E +:10C2E00072656D6F7465732F6F726967696E2F6306 +:10C2F0006F6E6669677570646174652D392D67614D +:10C30000646262386337290D0A4D6F64656C3A20A8 +:10C3100068747470733A2F2F6275796D6561636FFD +:10C32000666665652E636F6D2F6D61726B2E626937 +:10C330007273730D0A426F6172642D49443A204D45 +:10C340006573687461737469630D0A446174653A56 +:10C350002053657020203120323032340D0A000025 +:10C3600000000000000000000000000000000000CD +:10C3700000000000000000000000000000000000BD +:10C3800000000000000000000000000000000000AD +:10C39000000000000000000000000000000000009D +:10C3A000000000000000000000000000000000008D +:10C3B000000000000000000000000000000000007D +:10C3C000000000000000000000000000000000006D +:10C3D000000000000000000000000000000000005D +:10C3E000000000000000000000000000000000004D +:10C3F000000000000000000000000000000000003D +:10C40000000000000000000000000000010000002B +:10C4100098B4002010000C000000E0FF1F00000096 +:10C42000000000005D450F0069420F0041420F000F +:10D80000F1109E1E797A22200500000064000000BD +:10D81000CC00000000001000CD000000000004005B +:10D82000D000000029009A23D10000004028A5ADB7 +:10D83000D2000000200000000000000000000000F6 +:10D8400000000000000000000000000000000000D8 +:08D850000000000000000000D0 +:020000041000EA +:0810140000400F0000E00F0096 +:00000001FF diff --git a/bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.zip b/bin/generic/Meshtastic_7.3.0_bootloader-0.9.2_s140_7.3.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..bccd58625239b212c8db2bb00cab841b1b7c2129 GIT binary patch literal 192586 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⩔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%0D58=Ao1&aE=w9%(l}&h-bA!8ch@p?Q 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 literal 0 HcmV?d00001 diff --git a/bin/generic/update-Meshtastic_6.1.0_bootloader-0.9.2_nosd.uf2 b/bin/generic/update-Meshtastic_6.1.0_bootloader-0.9.2_nosd.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..65032ce66e57cb31c7de84ba2dec18760aaaddba GIT binary patch literal 74752 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 a>mmHVvlK?Olgr=t_3x~kf1yB%|NjC8yB})+ literal 0 HcmV?d00001 diff --git a/bin/generic/update-Meshtastic_7.3.0_bootloader-0.9.2_nosd.uf2 b/bin/generic/update-Meshtastic_7.3.0_bootloader-0.9.2_nosd.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..beffb3206f141e73c51046df8e0cce83d883bce9 GIT binary patch literal 74752 zcmd?Sdwf*Y)jzz?WiCl3$s`kGG6Xm?lR!uSCj>N#)nO7&E)z_+*dl5jZtY2}b+~Bb zr4Pf!8c-^NT1cP|6}2E*Gr_b*jDte^^V;7Ghz4kDybc&^Pat7VuIK&knF*#(d_V8= z`Mm!nlh2yHb1wUw+26JIUVH7e*1koYmH4BDcl?eBB=%uq$xb9UY+Csd;fb&@k&tmZ zLY5<)jC2~P5cEGk&*A(J;>SyoE(F~MS`E4fbU$b_Xglb)px=WIgPK7B&^w@0pnrhE zpiWR9NTVkt6_g3GgJyuP1hIANo11DCFHW|<&&ypUp28M za9m9`=d39%o;blM61l8*nka~^=eJm=SpQ*OOfzO#`RLl+41 z!386u+MHGi&+6)X62F4}5=k^3V%zz*=PCS*I#+MVi1TXXm}=bdg0NjFAw=HOqeix= zV+wyZhCfY)@CVlG1&Vc!Gb=SHNJd+Y=yMRcwnqT&dK}T)jE9Kq?IH5=o`W^th7SUL ziZoZ_tf8DzTs*`pTFM{YzUs{`?qq`CE!|NQ2~+N&eB;dGf+TvZC(I-DHuEBpjxW&V z-goko^F%J>GpDVbltVtez^+~;O%okJb45>{XpQCydR`#0+>iQ5E(vmi7o3{VU)ANS z*0l>0iR|nlxm_V5t9>3R*MlduW>0+bypH}(`3Jb?axewQ)W|a`$?Xk&c!A;Y8y1p| zF@--D!=ENY_-|(QH4AE_;!UoUHs@up#$CM6#hn47t+&*4 zg&C=wVhKdqAlF`9(;H6383C?!M|5rMo4j##OVh_>e343@fPm>}1y)`GpMBW8-whUelduu)l-&b=ke5F@F-P=*i&MZUpdvY*G5+qHh zPvgv*5S%I%-Ty*vW{~m?^f`egN#%i^oT$1#jly)^@1Baz3Yif>$*VF{?Yxs8BL(n&1f<%6=cQ72dRQ33j zGDezG!F07RPb(f%_-kVL(_{#L#zSj_1O~!0F39u7i^-x^B-z?_hd2#m{SswW){KeQ zOnlvS`Bdw9c_aA3OKX!42GMd*w+jcp#MmMDY7{yqEKEdiL8J=wCQO?Vy*%yly=dzi z5!e1i-=2$JeZ2CDOLOYkZr>_Rx9=7*y(@&ioOD69#78jvi?s8kSCavZ$h7{Tx71X@ zTGKqn@+67m_Xktm6DFEH7-w?(F`gLXId5r#+t|$5EFNt|hD4_K2N{nsevLVvGq#d1 z7$^TFDLJPPm3Q4;g2Rm_!IfRlw7}6%#BNEx>vu;U;8Q{8=8rY z>|}PbJGq^SNhGO8WbAB7VtYvfktb^xD%sUsulDks;(5q9vo^Udwk4MXoqA)j-lLz$ zNKAcNG2_7)jL{%5s4X`Z=p{CKHesS(;-a|?z#lK=bTp?{;%NcCgEgH}$kG`trxbqa zIDG4uPV_Iz(&u%aa&qkC!J%2}|{*M&Yq|E<@NB z&zXfs;yH)#a6DHeY>MY@5Pb2RQ}|UpS0X$Z&y@+a@m!^FUp!YWtc&NC2y5cGrNS@b zx!Z-G$8#Q`CZ1a*{4}1EgjMm}&xIB7oL9Iro?9>65zo~M%i_6*gr)JEPq+oSQ42nP z*a-YF2d4PbWC(wX@)lunyiV$b>Ui!k;pTX5hv1Iqo)pUCx!r;o&+QdT;<^38ym;sTIOqYF6Im)7+b4k)nRP4_)qD$;G!&DJI3`^qr%KhQyBMz3A)gC zc_y@TQfJ648e3Ue<%+-NY_6|a7m+ouLe9Wnl}uQE+A%rZXOuYuktg`x@8k`yce1JG z08yCyg8@Nd^MBuAV2GUj>YZP|`4D~C(fS#IKb5h7KTU@4&yLm$t(&rFE>?%6*(5$G zJ}nB@*#i5|8>%V!I|U}t6?r*9mm57+7BC#UL*YBNlH7n(;q&zYZ)sVye$!-MRjj@- z{<Rh8@r#?HnMjkaV2{?61W{ITAb0A+;_@C39|0B|7B)|Iov0zq^0lx>$ar`@3V`SN71mec!pEW15Hm zvy9>Q&^*0vG=E0mKQD&A0YCaX{6}*QjP2hx0)JNw|Kwr#W88Lcc0rTp=KN-ipl|=0 z2$X2eY09+aNtQFIOveGH-1z<17@M_~_S)hWHF8$v-6lU@_VOj-W9)XXv301N+gnqK zzL|GV@bl%d^R4Ig&i@;x&v+ltqUR5E`>u!V5E$ns7)jFH`=HtBR*CgdF;z?}ORq?` z5cyukfU_jHyEG}D;ytZANhV2UM7|38Jf|X%Y0wg`?%l^ zdayI1MKp^;XGdc?0)P6uG5)9E$6jgw^4?eV&8S6aX5LeHw^=%V7w6wyD&YTVJtVO7 zXeudc*1Po!Zkw>zs0;FQCzdcb+@kQ;VSJt56KXwQlEt~Aa8T>&B+VyoA4*2 zHb}B@-CSn&?$YO7J)wk~6L4H7f7LhgoVWFL+)Ig;LDS7TM{`+l$t03@Q}Ep;PGs7d z8?HaaK~CAH>j`nwXPo(t`bcm)ztNbIWa>Fge~H;Vtd&8in%gJI<1t=%Q~`J znmi-t??o+#vzR%nJtd0W2wr&ITgrDlY+!TWSL%chmAd6sGQ+aHnv3Gsto8){e_zoT zd>Q&sd91)xd;;H}gf-pIum1~5{0RC))$xxo<-Zvhl&XC9Wa(q2Z263Tv5)@Mum9GV z_^61|^pN-y)=R4=rF_WD)^=>+k9Lw&1JxuK+%{hyWPWg0*0Y^Fe<{@Z4f|;8dFAnS zl1*af9(EZ5_j*n!WXDiljmC5Y{*d2j`==uCmG+L8_v?E#os26fw4-#b zv}nF5g!(Qx;`)1Vl1OI0ANoi5BjquMaT$P@&u9h$vFkd56c;k{Xz2Yg&X66YpM+!m z28;24EmI@!yb+rf(tdXk=Zc^aKHricz2i?^aLAPuyvs5-Kkdv7wp>vw|7(zIfA{vU zmoRhtPU^~t?FK6+pXwp@gOAufMXf)uUks<%d%}!8f^vKth@h*uM`pR-&1XY&ZostD zSwdaS{!XlM)?z*oxEH^<2y2Z8{mBbxX}KaNAMQE)_-0p1C{NUkt@ba*@J}0t{|(F{ zg- zS|xXuMobSTmSJUKkyf5OSmqyxbwDB;`ZeWP(eCoCAo7%c^dg0na~xYR2gJQ}mE#ko z#K()-T^m-9vGKn&hJX4n{M!`b$dOYuhg~V+crhWUMuMuo>}`cq(|VnT7MVTBmz}tj z#9uEKh(ZZgf#zvt%Nw*VX`Ry5BTc#1{jS7zwXZN0zjtLli3CV|Eyw+dx6}wq1DQZ( zkOx!)S`LE!iT#5MOd+>R5?MRhrAEG0HI2|M^y!XOTaMwZZoXwfJveFSOpp zpD4zSSY;T+pAr3kSqy*EF#NM_@y{>>7(21offFi%&vhPTDqWTHif;Zy>Ds}|3|Hu- zwS}5muag-anLamk^Ngb4k7isGbU@Q$ksR)N7YS`RdiRW(!F1GaV-GJD);1ia`4@VO zewscH>k)=HyufGR6J>wnCeh)a-XcWrx8nXE1g_&&n9>jL{jN?ickp99)roS8dOPCRY;U(fV?Kg1r>R<=ehn#{M2@M)-L+8%)?^6s8ww}H@cW6KCzYKWZvFH_J3jc~2 z{^N$>AFI8$`mE{yrQ zU|PN*$Q;BNsS09Bqp?-vhCPK>hZd{pxfsTn>(vWNSVkXV!g$RbWH3&rqXs=K%x+d* z+{-j1<|hObKuP&5>~(m#p-&K5lq85sm=!LE_04LeMb+f?h1AGDkVZm~=&WpoUSwtN z4HCE;?>9MjO8}Mygm-Hyb_c5D{l%oa-CL{(Sg$7>sW#wia_+W()*U^ED^DrLId+Wi z$BO9~L*)W`9a89==!n6fA2{nthn0YNT)jt){7p?q|L~qV&Rx~@cJ(NRe`Nn(8N+}4 zF#Kb^Ir>jEy<(D8FR*g`fZor_3kO-buRo!RZ-WWOSeEffcOWmOj`Aa-N3Y57}pOp_~h@q zbdl>zPxE&yMum*t&xrPq^%~m#&BO4o`g$>27r50@WxutU2>!l{om-pK$VY)MP-ilT ztuRSUp$YA#J$h_Hd7`AxwYO25U6aOSvsor(=aKP{z+ZKkJyl&BiaFU>XqSD9>s{tR zk_VDM+S$|I=s8Xf8{DhVgL_L8<9C?Eufn=`UbVM}`Jt<2)pQ2)&#w4;kWOFmrbY`S zfQ$fxxgZ~;Bf$iT*~K(aj8iL2wWnIRTJX=jOoR zAJU@VrT;0dKT@)d(7_Tl@`lRHmcz8p|2O&p@Ymu~io>eU(}qqdvkSOjA(`j%oydrL8bipF6yae*T<-cp2duopy{jnsfN7pW2Hbfg(b z9Z1bcXCuv`Q=w7&8PWe&$MBzkUp%D$eM@no9t6}!3DRt&Wk_?8RwA8_v>K@c=@O)~ zk$R98Azg*^2BZ>FC(@rIEt&n6;+>;Lo<_SZcb8WjS00-ZYulynK5&USM~&2}UO(-n zDe58Ri&&Y^Y9BKfwn=I^P?bhJg1 zK6DqY`E**}w&eepa!+E+*H>6PkQl3pvuq>GaAe{nJ6I=`8)?5mWk+hCGo&wsolq?U zn;&KxA7HJS-gLhavxfgVO8bX7==15^;!ms*}&M(kNOM&6+A z_S02oR$g@!QVM1OwSzjFu6#`4zbJSE`h#%-x(@=&*!^HDi|I_k6tMC;bu zoL-r}z;rWK)n-5F$Ew*6PbfS{4>Ev^pbU^1l=VYa*7RxQF0^1?K081Jjm-5H*0OWi z{C~E2VUfi+=ZK4bLCZBY)0uvH#jVm4SdZIFb@of>4D=5@cF3G;XeGYWf=q*@j>$_7 zEv*y$docgUoKmjnV;XL&n_!{ZJvzfALLX#Bs=M0KIOD>ldHNT(zz1-jA~;(Wn~yn| zDqbZ{#ESLnLF4F5g8Ic|$Ly`7mpK2+d-Eit`{qbsoz;w?`sNcr#FRIH*fnI zd@#hfq}kw}P`kBpWlIv$yIM4Ms`=A3kX_v2)c4d#jmou;Dg2kj@Si*ke_kf=j{G*5 z5M6H}f!S!6xuDpZ3|pvkV}>O=1|2Qha`Y9@`?x)fEDpxjXGq`>#uVCDc$+n1ikRs4 zVN4$9X7O!-w!cy3#oU%T!1B?49u_@k2L;jF`i4Knol=onHqko`ZCOz;&X-hm_2NIO zV<7gg`glew2jTt6h}tq%?tymjo}Pb(hu-ZU;Nyp=Oc;Gn6Es2UEX?Sj;qh5%kCGbg zQIbP@CF+&<<3S_j4YD}PBZxeB*XSg2{i}4vi7mvFNAYLG_F3@*O<~fo5u1OpnCC zzmtxU)&>wixqoede*$ z*p@G=S+TsK=D>0~IxudI&SariOeJzNCz>T6Y>k&nYWAw8Q4Ie``R|7@{H??A|Mw-$ z!(;{%dR`=@Iq?0nPQB?h`4vdXIb;f%D!txerq^-CsrJ_O4%;jen zd$xF}?)PEfSm@97n~`sc_W9{O(CM@?4nFs6NDk|SZ{aikO?U~~dk=nt9zMdXOj>{` zbP)zylDoP%(`;F~$xom3n8aKkdlocA)o)&4Y{Fz#-X1OYD~0+N&~mJ9FZ>0qH;*a& ze-y)?Dm{kge-`i4io?ORcv~k`k{r9V*$-s$bmL+#-M(n|EJ@TD9ix!(j?xmmdorvEQiiF|eR`WF=qr0vgp|AFy)J&x{F zX)FjWj-7dp1n5{p@z6J?U`(eR!dsc^jsrD|hwC(ON+_X>$Tvb8eXyVQCl4!hMEFfK zzX51cS(V=348AC!ywOZh@_LoQ_ZpSO_b*fq-*@4uRQmtmbs~97e-5jIyFtJBWK7|| zG={%@82;YUdr%K+LF+*4LHB`v33>qZ5NHGF*PzEiJ3udk8bN+g6Q~)q3iK0@A=ebu z3}>X%PeQsbJv}*zu+ZWlVnZp7`@m1j2xtWja#|4e&Sd2YuNu*#>qXA0M*gX?h!(=g z_x6}EUo!$R!>br42q$v+#%4XN4}~C~Zx7Y@)7|De8cSriD=TQiod?uo(K{IVQh?44 zpTtPS$p2Ckpq1R~N(?bJ&Z;Y8!PT`Juy^UEKdE&s~I=0Y~`GQYrJ&5b&=9_OW zsC}xDOmoUReU+8W6GqI?1|yRPD%{K-yT@!xIXD_4ul|~y@rJ44L8u^uek}C!0mN8b|qegPkpD^+kwaBf1 z0e2e4VkIddD@&ty=pVn5Rt#&ru$#19gE8K^A-!X$g)8kmkmD^4B+^h0?+n8q-DbVZ%R^OIN)`8x`?|u&Hf8QR<)BlHP zOyQ3@p!5GJ!|-ibC%{##e-`yL!0UREjY zNy4#Txu2=C_7=1o25N<+ZCIj+i`Z{ONw9|UpPfHN(-eN+Wu$NyQZW4OYQ{p3})T9 zXwQ%#awqhKM_o3YF-wBywzB0Fq|SKwVe1DMGJZ&{7Z6Km6n{p{|9}O>f9f#&Z|Yk4 zG9xb=yfb{@ev-eT>-LuyxlLAc9lSILNbXHtOJ2Ue-TnZBGgU8FMiH#US}Jp0rl+RG zi1`|wtsUyqV7BHIGU{WqwIB2qg$t;b$f-u|dE-Vm=5#3U-vb{-pK()H-pkKC$Y0Oq z-E=wUWj5a&pkss6QPuV4x|fs^$1Jz;5WW6`ftSOD6*=3gx3isQSnWKF_2f)ZH)}u6 z{du4+?URxCG8jQl|> zmCMa_X*lB>xHn9qSZ!D4IrMRy)(yP*)u(m5d;BzWU2b4Jdi(nZmWQbrgsl&_ z>ncbBrs(y{2Z+^RgBD>Fe@3+bAII>AhujtX2P5xB>x*~f!J0C!EYHpDVi51`>S!(9 zJTP^EGhVNM>dh53a%n&6mLT_(MxNKNdrDaFl47R+%h&g(p{^Z-hi0JCk^B^qx- z1E}e2%e+QgJ+#NtfFO=*bhJ_#BY-&^19@_vk_LR3!tF@aNE6V0G|0$l19f4_Glwu| zxh~E-lxL_cF!9b*LiuYm&TYs4yFrwvjLoT0E=jpEGtfn622V$ybiSX?{q^uxB>6o- z9oCc7$Rquu82*v^AJiLd|9Qjkrx={;C-B$&ceK8T+H6eTN`m%WBO*3q{v6EfO!5qJ z4Vj1d0hy2?bc~THA>$Y#Z|&pJ>NobYh$2K}^Pu8Si1z-B{KwbT$SYCF`%piTpX%EY zKLZ>5qiW<2h!uAkSU2=v3f~t+&MjGL@5OpV0%W%j91U5n10?Aifv= zIo1Nc4QqtK$ZLZ&?n$-m%xi<(%v*!Z%r8RxVI#&PVjYaM_pGe2KagI#T`@w3$n7-b zSIHa;9|J)jEQr44Uog78hi8u|{5>)J^M~PoNB`U5m692~`=vh34)a8e(|y_ zk+1u7u*Hu0ROB|J-qc7cF#aTLf50SjBrSNXlV>d@Cz~>1pGOQSB2(~F>7Ax0TslR2 zEE1Yv;S23+VPmyxrKH_ue}F&Opx5jo@?U#N?r(yu_2aPcz-ApAoCUv<72%?rnH>d~ z_dnjxh-N>H>(<@(V0by=M>!-7B0(~{899t|G@2lD$bl>6=zkYNJf6Tb(6af1g?JK? zb-g;#*^&beNRyIEOdh*?7HpZ8gcEWngeJSK4_KtMl1$I!=sa{x;lDhF|MX$_-_%w9 z^2ADe9W%F~$?l^P;JlY@0zefV&uC}zbi) zfKRqQ$W2QRW*nim!&Fnq$d4XH)VEqqepe`kT7c)vL956MyGy?bC)|vc<^1!Z1UIwu zHdvWy-E+M3icf+}J&j}bO_=JDm*N@KpxZ$Xy3X(-U1y+LiuaWF@9%)dzr-VtVfg= z?Jl~us@WM^UA-RK+1Pr`@O!DOZ*Zi#Q!8T6rR#Nc{a+ubmy+DKO8gV%0PFx3?TMDB zd4j}CGhHby_AYdd>dO0ESvOh*o0IR>jRqr%8ei8WneIq)W@VNq6E=Sq?_lJPUbDN* z4;~%GpAquk${7ALhv83k^^8i6aPblQ^lKBP^eW~DGm@a?$9k;uhwG%Hu+C_t)*02L zgLg2q2P0<(o^xGnBX&(0*=339Z6~W7#%vOJA3SmbMBCiYK@=w%Z}HcS9A@3DQ6oOB zXW=`*KdE_Ijj(FF|CMgvolHYo8{!Hc#1(TsxIkBmy9aAqxc@lv&7ij3B7SN)Z-IR> zB!{npTD0xS&1qPnPIIS~lk6-n@&~hgD5V5z4TC9eqd&{bV+9buW09eJiS(FDS8fRG zq`zfRg-2gOtfLtIk@DZF82(oe!#@o=Qj7PS@K%fsjdAQ~d!_hM?3N&f?}vx3pVB_G zsJU3OR;Ip3?Q3kc)jH<%6S_h~`9-{j&;eriIKU`Vw>bk#3<%@5yeX z?R;GM9nyAJ%)_t7e!Tl<_T8@WZ4|pqPg?n{o{%o#w6UVzV?=MyewOk$-R@e$XI zGn3rUBi=Cg^(`98tqoWcOlqqcwb;`iHUj^j#_*pt4F7!b*^c=v)9qWR?U)aVZ`xoo z#&JYuUd&8oFdj4OKDfYLOSNnyw=;xsb1^;27Bau(e~WnAIQmuS9O7}{%4?q0$If#Y z-PK6Oz!_Kqe2jL7sQfC4<6>14cKiJLhDW1bo=H(lt&~uMPDRI zMcmhmxLe8oj^ZevAo>URO!UF}HgG^b+Wg zAZj_V0)#QSG(~(8+9jx?Wo9G7cFBW2H|B0c?)A}?AI?X0w~3Ai-TbiC-q@PX%K3Cn zhpyiycP27L;!BDiy2DejHYB-D`6FN6MD`F}g*+BN@_}z3N;`(4A=m#S9fhsv8;b2|jlbTLFr+*>&km1P> zF6bY5NtwPwjXV&?<6++r5=n6+?6Mij=3y>~8C1r6Bqh5C zeMSp(`9X~F)Smd)jou?{)W)aYl^=vAs}xCw}S`)>wO4IUz-7XQp`8{C*94 zkXToGUjk>?yw7_FB6s%ZZ=<~e?L!QXT&x@?dXhV}9D4}!OW3-$4AR+SiZG_|zbl6S zHN)`lgKSLeoz{5>f1y{Nf?`zdWdEGv*f~`oof-9xY;FeqZP}_p>vVP~ui@TRrw$dGtXZzB}@n zeONukdWwfydT&QQ322)gZhTuDdK=T3!C|eys@pXX`F-E-w+VI3)65GWDEgopVFt^N zeEu^URnmEJz$8H5h!xpGYNQ9!Ty_qhHP(u0?b zj`TTm6!wPgc$(%Z{?Lxn7s6CN_&VH**`rW*PIu}wnZh4JsUFCBDD!k^+OOP`bon<% ztyuX?+mzhAxzq33hVje8X_!kYspRtddLp8GOQwM&= z91_Icr4NRGwvDmZyXZQ*jJjg2=ff;;;2r-8bN1daZ-;i@emQ*CHWq%moK0_S3pXp{ z9MRr3-w@E*wf1m0!Tx2Kv%MD{lK*JjHJ|vK#&P0g_=%*o=%GFM?}2eGbgiLb08$yE z_z>$rgzMX5}kvtrK?&L)Xe()i#)BIFW7iT=rf$iI5$Vug=59>h1} z*ayq$8Hl*$CD$YGt;h&7-%YPO-J_W}&sb->4~sM6zc2F;lFW?3HZ;1ceu)to|x#~u4D*gm(E6?z#np5 zu0|OaEt-Oq#CK~8ni~5q$B?y?14uYcg9C^63?If#*6CI3QGD_o|VVDqX9uR#(ER=4|i)zCVWE{%64X zR4^?!6tvcw?OY!-k1x=)f0s`d=xDD-O0Hn7Z%%}rV5SF=T6}xFde{=EksLK~S`Upc zA(%ZH_wftZ-Sq-X^sSUPD3$~%sR~OWK=4GXMEE4A~Rs;cPmkwQHYJT5r zD1KIl*rU5lVj^qcLrZhj{tQg0a-R`Y25P3#rp zURJxOmU?f|HCkfJtbM{YIly|CM*9Kc$O!NbTF=n73i@f(j{|KAF~j_QA6_61>cL?y zU5B|G$xg<0zbP2Qb@0Dz?8Cf(sE9SX!>{!wxG|#_=6jZT_bEC=0{BZ`c8dnlAAE@E zNg%1wHGDo>4%25)|3txUEw@Nl+o4(fkH2lMtAqb}57< zqK5vXj+wc~Lv+lD#%IXxA_jMI8N6s`8JnpGE@E_c`3jFIWgvZFiHCaGXx&>pv;|WS z9mJRS!s7%O5+mcxMtr_;AnLK7Q$EE_a^6&&3*S^qKxLpx5CQQEsZGc1U`XNTPb{aa zPzlm1OSZhB$d zOKOGDomM-c_EDDxk&e$bvDj%~6vIDK{#zHr|Hk3?!(J!7HVOTutvc~an~VDRZ!8qC zM+0!m1x6GrD@XvDK@`IU=uc)`h*TY{I8<(`O{hIsxyLm(c%^1h@^j_swa>XzrNxNZ zHrH&IHz0m%lE)68ovX0Fkg3Jxw|lhhnb-p~`3#GEf(22zrr5C}iOpVjk*TdM$UP&q zJ1r?CXC%byX-AK6tibkyNuElhG|il1Uf(0|oD;tneOu?Z-} z@!3Y{#SRWT18i`uolh>lM;T6FhH+ zmu1x}tPxQ@8fkC84Qs(XdVHEjWxfNJ`*%xh-Y+5Z5g8iXysXd;#dz#>M0gz^IwQ@4 z-}NTXvYNL%4KZi` zOH*4&eLZ%0B{N+?x&rX953yu+EUmo7{h`9IHcLOMnq!$;n-;PW~72+~9Irw7VPoSA~W!FfV)YEagkh6$7leG|AsdUUgE zOyPfT41eb^{1ea6T0!kVKeu@B#%OKQQMV}S>k+jIdVHDKCO#8>6t=YIl&nU^PWM=d zgh>A0AZojD4}1bFVpsS;hXH#@Y$$%zH9PR01sHoOuvfGR>%J4(ZnKy?Z!ND3)1BAQ zijP@D$~B3>l_GFH_SpQ3r(Wz}476?U#ZD89=NPVOTdsa(62A?rkp+W)4m;-?LwLHw z?pcXG;f8j^ceK8v=#75^A0@jpA&GVkF!d(sWX;=3mXPj`TYe%IG5kxW&JWXmi}qhb zeHrb;GH5?Gia#U9|NCP2&l`q6?ceDbO#5>xUk$1o;6n{Bi9s5TFUe;1)q7!2?*Y*i zMCGz}8bJ}U6f;Z~58LT;J=1-U4KX}5o0T+^&V3S*wrFf8S{|+Mp}MEfpyPU`RRQaR}i6ni*Gk{I5e`zc=7O&{YUs|jx)tIn~xMf zM6#KDDp48&TgBhUrAkdtOsdR*RSGml-cO>$1bqf&~>A_sHTWYndv>* zhyXZ>;U78v|1yR@?evE9zu!sO(YC&s&;EBp_qOOFvPL`Cdi4e$=IfMeumB@>Fd05Su&fRI=SU6**-XU+Q+E$JjFe z&fdp5ivpFHqZS3eXgc7U8O*3nt9`Gj&h_g6l}4yda%^U?TcMicy{??#CyHkWwRRd) z_}9kpFCLEn*C!HP%4{#&af17!|2f3qM9q^4Ql5y}pQ8Y~r93-8t+W=G5MiG$Di=P2 zI$Zxzm5{fC zrHt)3&JbylQYdV#PtT;9L!IG@`9ygm5(XAo9cQhy$1C z`NH4Webt5uZEX^B1LL^e)8@&N%r#YAWcwl{1*Jo7Py{{u1n=MTf5)~no{RlZHCKeC`EMS9V~%R1f;%kS~O)b(*c_sma2 zEO*k&b=TEM&bsA9xLFD*=3|h!Q`VJ`oU2MWS;r-|WA_%M`&?gyjKaUd6@KRWERo4c z>E*jJzBZ&j-@)p-g2{eF<5?@W@~D6MTy2G^CLOzR;aO|w-Ao^6Kdvlgqd4QdqxqP> z1<|F>N=+#5tfyYel9*Zbm}A5+W=pW1H|s;(q4%(|j$MYg`wm?apwGDJ6$AF8-+?H? zS<*xPq>6D8tk|?oX0CA>KnzwZ5+_7BaD+$4KxkU0~(ZS@zDRM9*UMj%cJ*OQ8wb1*&rtn$c7A}m&*@hJ=hT7(Rai&n%J+h zLR*By>Yts%q;a7utyd9)(J|@upJab#J(U>iyGTtG>sTdXllh zLpgff*aAy+2h+Y!;Yq6dSQCvj{$)5BZ+(}a-Kno&t?3fC6K&ndzmN_-r}6)&l>wqx z;(aOajH&kjs~G-e!|M{11A$YV{v ziuzS74CYnMg~x_2$mVAR^x6E7-@xNJ3$WYMj_jLrDnC*7`at_ZjqfPLNbLC@*6#4k z^-_&Zo#(==Rbcia#SL^2)Cf8TdIhu_BsV|Y$RFm=pVEGp%UvIh`KD(N zJkJPGe<9TV!vXGhODegU&WoFT?7q8dO^EQmI+&98K!~4tOE9mtTD$0Unzs71rQ~p} zrKAItE~{)==+Bd`gWoP<1NL{^w%O$jm6YezCP`GfYiru%%E!75#WTr6*C=nq8#_b` zbbrebtwN3bvcCv1YOlio-yn*_iXfj$*CG-7uYXRNhUzP#9JH!`R+Q6zh$!%*8U63^ z|M11|r%JCO`7ef-xs>jhdDR-&>P72lQU4H*sez$0U5J)+LvZN$yw|7IzEBJA`jW3} zO(iB-<&r}aD|Ua>X;@lsHOz`aOP%5yd;R z-vEr>3n$|H?Jy_28;(8qt$6z1VNKM(^mXtb?N@ey_JW=TQJ$ncN~1l#^9CV0H(iHD z4~E`lJfcd`NCsrOA&>$okjgpqo;hd>=88-@6pq@@2>tKI82**`#Y6ZLJN9Jqm@BT3 z7kRJQZLVNojbX+Z00M%~VHK4_?I8*u+E#A}{VOEv%b~PN+TyH0a2AgL3laDz%5>dd zjogMky=cGj{?SxI`xDxKP~2$$LNWXR{5XUi#nn)O=$+U#FbQv?M)c8fVSAq%`Af8Q z*Y#06kM@FNuzxI~5%tmNpR|9XczQwC!oHUmcl)34V;)iqKf|53`k6z~{-K{~(9X^G z*(JoJ@Nlp~<6-lVLM@+1@n;17n_~Dc7=}N^gD+Ae14n5T2B^iGCyL|Tk2IR4RPk16 z!UNok+zGoEu{IHDF%6b`4E`6eddtbKUaQE8Mab<^n1h^reJ^u}6&?8hQANV{tlk=& zr~55$R&>#}8w4Mm0Ts-25E}W@*qkLM)lR~QOzeV9V`b$B`l-%nzbiKojSdZblxFRJ z)3^}VBd&{CoFw)~)+!Gnp7-!wVd)$ov7K6yDn0O8?dEo6{e;Mu9DEbrK>x(AW_Dk#|+C&>xAwx6aX;56P`Jn2A=GiT!ph-u_5O|LF?^ zQThITYN(9<$T;P$2hRDm*z;3x!9x#TdLGzxW;s~-7-of`pb=3yxP$!>1`**s@GBD* zbMOb`;SX4#z7?id2BPm*3AvqhCtDAtH4OheTm4#MJG(ZkVj|{hwa3Pb zS?+H|L|4_WkH%1x#e=Y_)E6Fc{Z+gi%EyjsshqL!t`d`Cyy4Wkv}nKZy!glXHS2NB zIOu!%LrjOsw0b_hYS+4FI$8DrB+E`gav{1TwlU8;cDeiK@9}s&|=4P}d!8%oN3g}mup5slE^K%1}?fMEq{M20M zV)3Lef<}dLXZT?8DSR0=JWPZy@Vw7J3(&*e45|WEgVI4`3jc>=_*W0ZpU7{&fmyIz zh<aOhCSQq9#2KH*Q02#i0>OWo0F|QQ_5|)=K~dbq*44C(f|KC zhW|qR=Ar&yKs~&p+RG;){fkQLh>qCC9>`5N^13>)+>G>9)b0cK(X+4gUGAn=s0wo^ z4QCFkW8?r1lI`z zfvsp?WX0g!Ek7>5F}hEXICzVFi!xa70>0IPm{nVIH?v_zTg@bY_r?v`fl&o_WWg#J<__=0dL9=jA)*WjinLT5Xx!)LnBA*U7NjlZee7c%li? z*_v#{WZdC0iT@6L6jq>%;X4ypyQm7lBz2doFLXBC+|6#7+jdVE^U&&&AG8SK-0~!w z8riGn+CN__%)YLCCWD!`9^L^t_sE$#c4kvsl5JWv_UJbCR=d?!V_mua^Ytt5JCe0U zN%s9=-KXnp>z`S_Mal4OQO5cHaPO!0+U{Gq`Ml)7egGTP_T|hq!ffN5v+(S{V2p%q z?tQMUcsl#buQQpv)V7Iku7fq5k(ejwUK2l6nLRYZe3TdFv7=f8Frxj}#qg&=&xhK7 zbL%}_1NR-uOkn-pzR7{EyI(fBF+=LiDCWASo4Ctt;Yv@c`-IY8P}Nnl^1yPmv&GL{ zOv-`X$l3#*MEC0DpL%S|KVQyw3gkoRSF_MwOFu9WB4x_hCgqcT_B-d6b$e-O=l59{qR^hZ-6u$>0W;| z_S-}HUsp51scYNAe^`6ChU8z1C?Ld3qkSFdUTCLtzKaNfn0%%d*SmWbJ(T4?v&Rf8k!F4v#ipb=#Gj#C_C zeZpyZFwOOL6CYYGvBgY*W!-WQ{17k?dG-_|(6M3V10?@(ZFZ=QI&+_U>4r@!Kca1ww51ZrVRYY3Mh}e(-IvwgvcEGqx(8Hj=MF?>XWu zu-Iky9_+bU^|-cThl>sJ*W(*|z0q@Y?o|spzuL<>U68knK_zk78=5D#sp2;2_$57d zu3o{=)_#l;+Ul;*m}6#kFK z@c-d3{Id#Sb$b=|>YT!1ob*A$e0medrMlElC3|L<8hKP@b`QS>`~B4JrE{4{O9smm+*>!n2PRU6-}Wf6s$$(t5rtk+gBuqpbm z1$#Vc{Z3$GI;mug=>+ZcKN)=6@zA|k70z2cD8U&{2t?+d|Ypw zb3TmSfw7_!)e9yYWoU$g1T$N2>qPh8Tbh**VG~Y&QC1Fp^J*)8i9NlGq>p0wN6P=( zWB4x}hQB^=Tv=kxfK|5>-gdkldt~RhAUPdJis%TatLfHeFpA+5Vy!?_K7Jda{FODNho;gA~0&1o}>q=E;Z~y@8nRlPf<+ND+&R4c0M*|8HXW z-#QF`c=Q(V@V!`3O>8eJy!S;#E13P=STpksn%l=89S6L9vyWdoX4>bP5Hw*0CVMd5 z9UHUZ(uKV{$gbadh!?6KS1)n4(`Dd z?j8IpJTyk6$-hiZ4<<(MK(8?ll=CjVa~vOFx}RKAtXPGi($Q;;t@;1sG5nVe!=KjM zc+@$uQV-6Ioa1Q8iQ=PI+9Fe+pSH|XzoHwlw^J&Yt*Bo``~CkM3uupUT+!Qg(Y}CK z{|?n!k#XTuo4v}iq>B4@?~ApX^h|= z(GP3W%d?9y0#pwn#S~`de-rvlr#-Dk4ywe;*!1AG(*qe@HMjYj$5#B09WnfG z8;1W$Xyq6yt+Aa=0g0|zymq;t0_0oku^3xV6GS)SDG|D?$Si>s2Q-c`KQ;=o+aa*rtR?J zf-QdbN#qiQQ%W6=_)+AP@)*)2q&turkUoht8R>4MDMC<*olUmWiGA8#`kAK`{nWYA;@(Y{=6W#la`4Yse6p#n zF1^<$`G#7Y~ z{M&+afcMXzJPsOa)frvBRk|R!poESwnc5SVj%9dHLpPW>y^2i1QDD$xY0M ze``5`crzK03$%s3@C4Rhv#59T#a>TRpLWs0ZCjfb_HMq^(6o5d;%(P$YiQczsyc&s zV|WXF^exW94$wS>nTjYO+%){sbGr5>?!#3ruXT=2J6lI(1Y+OWguPkhXZzP6@(Yu=J-i4}rs#2}>|{M)r}hM8Qt!!wbO``FxDgCzHsP%Z6&a!6h^jX=xNu?BClg7!s2yo7O~&*Q&` zUYEt$YL1p)yte@7voNO3peg2_RF_epN1W)@(W{QxF0_u3isAp`VfbIErRlVm ze(Ywlk$=>VlTelzG^?!m{Y5sy+>)CeiX*{^si_XA37g|S1(58olJd}hei~s ztfXGW6Cg!)V|V%P5PZsKPtf$825Ku|ojZG|{NboLqo|N+H-&)RER=~i$^d?3x*EB@ zZ>hg1dj9tm*`c!&>DlkfnCVUoIWK;WxeKDeVl?7&ho0j@#5X$k!!grT%qq87mwB-N z5Z-M};s0a||2v1_-xst-M{Yi6sIGn&o%Ll*KOReG{4H#zHILT!M3Bx0zq>M{+$TUw zhq0KL&?Z6TN-3B>^wk1?U7rE_gS>S~&ywpgTR=29*lt8QiO8#uQtr9k^DoQ^qI0!& zt&{2+=$s(2gcEnbgOfc~zjc#~J-KYFvBcNpE!|YN%?0bewk=y9wKO#Cb#W()w^~ZJ zS{`lUHW^!Y=}pZHVwLma9#>KbzNoAgwnbDM#mG*TX|PARbD_c~{Xgw}3sh9sz4tzk zdBF%HC{ge+FvcJgVMH78Q5}cjh~g{3bfq=D;b|bK;3K(1$gKmIikhUplB(2H#il+2 zk+JlKC>ayeHf@?_29vcw^Dr@SDwF6SgqdN4`F{HxF!P1nulHN)`_^qShjln-_St8j zz4vcF{`Kh6evc$Ki}b`(fd1&C90o?Y%Xy zB}--%t&ThH=J|Wgr){GGY7ylrfNy}8?F1YPlq;8oXT4=1a)CDE9>g5IA8V?3NZ z(ZTE=Vg6$$&up_D#;yW{lI`(AK`f*SP9C)#8yhP;4Icw@ikNvZ=CS4-Xd7JZ+t4^5 z)D_aOJ%-@_f`orAN<4`F>y8A$>4MD8JJ`Q{$L)&tF?zX=N%_(>tLTO+x@nJ+pg-+1 zsl?vIJHm_5myW~#Za*F8)kQ|=Vcl?*H61V=adZ55Az?3D`PlrLiE(>46|7~#zt z^5oN}XgfS=t2uKx18s|>*`L~GwavAYIIAr&)@u7Xc)fCAiSV*1vGq4@Uio-jTG8F+ ziM=(qMhDoh#{^iE3cq~yXoEaSaIDU_t8GI%c~xZjAB-d9O3AXs20;_GFrHD1(P588Y>RBMILB#-NP?%lEomX+_;G z+t*~drmf2Q06A~nZ99nm%x%b?P+j?e3H*pkCdX9CRiSMy1Q&SW8{1f{5_Utg{IHwl z=vZe3wTl)sjE^Ds@00M)55d0{v*@?PWnwN;7m-?Opl{5IKkGP&Ny z)3))WTEtu+#$*L4rc#Va?YyNm{Q=*sM`|ZbzrojPfK;um#$Fp+RfGRnW%9x(CwX#b zb6N|{J0wn8KdaFe!6qePJj=kjkR(Yy>APIXXuovng|A#u*pY&FfY&ut5)>_itE#S9 z1uGRlnXu~4vj4MR!oMH{|EkJ#)D!k|H;6rjglFYLS31s8ZIZXc!&?z?8wuw=N6HqS zi#=y!61g_$gdq$(UD~t6{__y{>nm~cEs+HF9+pvSe-!`rV0uv^EDBGzFf%^4G1Kcz zT^DD9)2Phs^+xt-5`ysx+=cZ~eJxpL6n^)5Ip}B5^nQ%IknecWIvzvH+ z({}|vAxS)B&_G2R2CNH?5@t7Z&@SVh8j&i{`kDQS@MZZ>P%hlttj819Nplq8zYiLt zp|az{xcd(s{|`v`7lzpgKxi<-_3MrV!7f$UIYbml8v#0rgp9L;j69XuH1pp6L)(ScUb zeUWPX{1Ltdq1)kO;H8PtLQY>y|LD&p?DyhZ6q~`eHL)q(!p;d3IDM38)tgNk8?S}D zd1)76#+E_bV^V3?0^B`}`+}}2+{I?WlV}l@HKeQ7HTKbn{bz;lgRzzc9-g`G8o4Ok z@}Yu$W4Ine#{XIg|J5k)ApIBjK{)ELiVH8h>QbXdRw<%xhY;%3XF6zT*ky*#ZO8=6 z415-IoN8m5A=629sc8G4Siu&H7#lGO;%S&;BLxmJ0CfZQSO%YSduR;mBS0y}p=k!a zJ8#DtB@p;Q2>xkJJ1iV_?3H@z=UEBQZ~xoq z2+Pj$EUf?wu$~d%mfUc}LuaPt?`h~!%2z4&SVJ%T=$i6Jm3Vuna6lmUyqdv$$Y>)j z=fnFK@4SN4IW9XRY;bS>hma3{pTPdFXDfNV*A*{h0*|x7w2*Q62Ke;XU3Hd=s!{go zW%@Gc4bL2BZgqucVfU8h+$N55J4b4#WtP)@ex)<3e3e4}epY$9hv%}^i}RQ`H_`lk z`<<-vXb;1x@XmRRaI5aD{(n%yAG#K|`@igitnxhc|E%>G;ZbX}&dQo%mZGNRGPfX; zxt*i5GYZSUf^WvbwsBFx0Ku{r@_N8kSf1fwWQFTt0~%bnZfAaZP8Z?w*VAhiu7mGf zQhWhaKX=je;rAs3$6da=2_%9r+t6q58yET%-_9?uRjA&FO-Elo5N5@+(;+vQzkYE1 z`7hqVUZDl=U|ta4!AueI&R`yBKN+sakpBOognuy#JUISKeTialuFE)p0o|kBdW!Kx zK{Jeac2&IsFN+cl1^a;BoBiBzN?WmgGz|8hp^e$^{j?vv6i-FfLEi`iGe@B(H&0hl zkMT-u^B!Y=#y-nVB#YS->_P11VK(j~p0vOL*nQS$50w4QC57f9bZX#*ETaE|qu={n z;7!-=C3vzzXQ6oO%J%y1Hc%9|;40l$4ct#22%n5_dQZbA8$dx|)WSD%yGJK~HqsPt#rm?PNf_o$9U67S(ias%!1%E0wE$YcD#y&d--;N*`H&nvwL zeNmc3`0SqDPul~F_K@nz2W1k59e;R%V|i$<5YLZ$??G#d+L>?JpE1*sZ&nt=&W|CTDmnJ=YEPTO+VolBjs0F*?-n1kQ^JAk zy>fMNRrF4p^gK?qQWA{vF%CjPY$$ z0!f{T9VpK=pAxd=@E|<>*QOYA!$-Sals=_%OEcAf!*Lo{x~@3>Tj{qLI1K*{>Hp|g zgdt=N$~rjy)AjW!3uj)5{c~m-dal^ZKPkK53h!jXUvahgq6ioElL_Z+5lQoVt~M}- z@AoBmi2ZS-^=q7O;Os;HrRN6&VS~TF>$(+j`n^g1C><+EEye10UfqK+e}4CWHqdc~ zg%8L1-6tB-Yp%Oky#gMA?}BIG`Q3*R!U~_ewux~Mi*auvf1@=0-aNnb)(Z`s<$|kH zjIr}}j3@(x-RqhLI&LU2ZcL@662pGhFbrY8U8EuSW4xvKuMNRpht=PCuG^)vpr1$8 zDmjd-(dqRVRaxDItJ0W0zvrnCkSxd) zz&-@#e?O%NK32^C`d9UhSK%k9&yD#(qnSmiOrCS!uB>5rGX(!S3IBB=_?yMr-vjy& zlohGQRl2Vd@8>qlYJq`Pq?9}XU-=glr>}Z5Z?D$cL`q5lE{o?EeDOE24=v39LJRw0 zPRu5c)K6SYlIC}>s;7LVbAzqxNA zGb0M|E`xhmZR)%@>k9%8@O&*DEoRiq_U&6}_+pTzyM(lo>dEW(d=UyI9rp)+(=mT$ z)2Kzf@Rw~h+jjZMit5Rd%9fK_<)ODO#S>m)C4{)&XFE#B;9+B71VZX z{8;qVpPV)PPO(HLgbT`|McC8j-XsZcU1Jjxg|QX`bU3bx@A{g?ETbG=8poD1Q{etY z;yWupt=WNniE!aG?zS~%H!i;2=A`u}IEGk%((&N=prMh`_PF9j8V|`rDrJz1(E&Oi zHfF`|u$iO4efE3rhwcZ>Gx(ah!x8tvG7Z81HxmA3A^20iF0Bg<{D1LFNYVX8I)OV+ zbU5@Wld9;On~*aKdTzu7bePtF^*-oEwF_00w!feH^z%=1zmA?Go$TtO@swT zO2zqHd;QpL*UK**DuTywN8F-R(CXg)BSlxXrJARkkK}wNN)TSDWE}I8C$#B`OPVD# zAB1I)_S@^WONzIF=bv0mymB#3m(E5VPD{To@792eD2_TEd1T(DS|P9#<<{Xw(vqxBkWGY?oiOH3w$##T~fS@mhxA4 zkLd5!3U9lle1pnxZ|Z5!$Eq_5oXIeKq;quEJr+o?OecF-crE#K>>Y*wF$w>TA^6jNGrqU1G2#bz z^*;S|y-f`*oooH?x@7t*&Ump`UH6Cb=I!WJE#S>pfGaQ6^Lt|7Y7obPaLD>ZLCY_P z)h1;)b<4Kb%&_c*701OE?QAY-E_Uu%4;vkmqHi9*Sb;rxR+Rl=;d3|^G@#EhW)ZA3 zfeP$$5q+ogOII{xPe1Pa66?SY=aQPJ%o*!>aGRg@Fpw{q?2E)ZA3NUaGO|+y={?ws z(SHF8qZgbiJaae})kxv*V7K}&Vm@uiC&k<`aHaRd^%&Csk4yM(LV*Xze_D3xb7SVx zae^D3(G>8tqHvtV-9yfkmeZzG2Q%s6%SUXoo-{^=UfS<%3r@|_H&4xSHz%R3WcRyW zCFMbVj{`AOp#GGI6?Q1&x??QtKs{D|z$ACBz`T4vEJYWWQ*4!;jPkl`LT0~rA?EfE zoqgVI{&rU!Xa@-^I`2FNTa#}(??N0M;nftt#0^9`k#q^up^t!D~Jkccz z$eV#ZMbjUy#}NEaO87s40uSOZ*-C!Oz89#<;Tttpq>%6TQMr&oT)d#fdO~P$59R=> z$2o}0o7<~JOmxj8No|Mx2=)=*s7Rc`JDG|{j0m~+3 zI@eg3do!Gm)sQ5U^JLB3rO*2eT0ZF#?6Xkrz&0PFjlKLlTHb!s7$=+Lb)G0WSyUT) zGLM6Hk--4#J?scj2{Y0Du7$L}2aq>vQS5o-YrpA3SQ5YJq_W`IK4w2jeBViFnqT0Z z_ncHq$zYhUn8uu_GYD-i&XdDw_0yasmVSM-BdEWl@UNHfw}jw-ud}2k`^>eP?Y7xx zMr9d<{adF9=dX=`?G}R;BR}Ti7Xv38 zL&HaOMzV}!gd05wwo!+VA9&y*@)3ld|)aj9O#$9IoV2z@e=oDGQuv zWAOLII2dj8<%!9hGok1(Y>sFgbX?-a(b{0>_j-b~Ez)(qI|!ZyEjahZ(SR)RWC~ao$QB1n7G>I`<0hp#XjJV95ipk^%&Cs z8zlV8QQ*PxKQYxiJ!G#n3jcp?ec|+8-&}BsK62JQm%q4RQBFml_bWd);e$&#$OX|moveuK z30LgA@Z`U@F?HF#$=Hkf!Kp|x`jtrX3zYA_I-eeRo`P$d(C4*dPw9P>bhu(q%QXal zl%1~spA5mD*7td6&9UiqTj^*&&v4cyVD0or$EQ!F32$^6IflwhsrtOjdf{26GlrEP zr}a0#S6&<4*y4-P&Tf`aJ49149ohP!E9y07PTXtC-Km+1J<0^dapGZnl{(7#o8Mjb zN;SB;oRFWdUrbw7t*r#J%)8eV(^Y$vkP4iLQ6z?Cb@VIG`X(9rykWia10?CM4txuF zecKWB+A5?SmsyW}9krPH1;1MNo@)|l1;sJH_cOm0>5Mlh6Ih)#UIiPY6AUz6zxM;b zCP5n?jyMiKkGn|R-BI|XpHTcOLhygbA9*tTgi6Ge@yjzOi8Xq{KS}qJICejnZA!

e*CXWQvIA-1uuNLnY*RYK-+ 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 Y#Q!@>VMIH*{C!{l&bs**3Z(e|FF42_X8-^I literal 0 HcmV?d00001 From d83f8edd54135e208a51f525deaf29700167a837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 12:09:53 +0200 Subject: [PATCH 1041/1377] include radiolib CRC fix --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index bd7ab4f5a..31274ec90 100644 --- a/platformio.ini +++ b/platformio.ini @@ -87,7 +87,8 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = - jgromes/RadioLib@~6.6.0 +; jgromes/RadioLib@~6.6.0 + https://github.com/jgromes/RadioLib.git#eda4ec22ae0039f3b9a2844d68ac2023ac0076a5 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 From 6fc4a1754bd3aa9c01e640617cdde2f84610f41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 13:12:39 +0200 Subject: [PATCH 1042/1377] Radiolib API Changes --- src/mesh/LR11x0Interface.h | 2 +- src/mesh/SX126xInterface.cpp | 5 ++++- src/mesh/SX126xInterface.h | 2 +- src/mesh/SX128xInterface.cpp | 4 ++++ src/mesh/SX128xInterface.h | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 11a389d25..9272f43f0 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -25,7 +25,7 @@ template class LR11x0Interface : public RadioLibInterface /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. virtual bool sleep() override; - bool isIRQPending() override { return lora.getIrqStatus() != 0; } + bool isIRQPending() override { return lora.getIrqFlags() != 0; } protected: /** diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 39ffb0ac9..47ac5da07 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -12,6 +12,9 @@ #define SX126X_MAX_POWER 22 #endif +#define RADIOLIB_SX126X_IRQ_RX_DEFAULT \ + RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT | RADIOLIB_SX126X_IRQ_CRC_ERR | RADIOLIB_SX126X_IRQ_HEADER_ERR + template SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) @@ -301,7 +304,7 @@ template bool SX126xInterface::isActivelyReceiving() // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet // received and handled the interrupt for reading the packet/handling errors. - uint16_t irq = lora.getIrqStatus(); + uint16_t irq = lora.getIrqFlags(); bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED)); // Handle false detections if (detected) { diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index f2c861743..b392cd3e4 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -25,7 +25,7 @@ template class SX126xInterface : public RadioLibInterface /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. virtual bool sleep() override; - bool isIRQPending() override { return lora.getIrqStatus() != 0; } + bool isIRQPending() override { return lora.getIrqFlags() != 0; } protected: float currentLimit = 140; // Higher OCP limit for SX126x PA diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index fdb2b9a39..ae5fd6941 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -12,6 +12,10 @@ #define SX128X_MAX_POWER 13 #endif +#define RADIOLIB_SX128X_IRQ_RX_DEFAULT \ + RADIOLIB_SX128X_IRQ_RX_DONE | RADIOLIB_SX128X_IRQ_RX_TX_TIMEOUT | RADIOLIB_SX128X_IRQ_CRC_ERROR | \ + RADIOLIB_SX128X_IRQ_HEADER_ERROR + template SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index 3aaf31b1c..f7fd35b25 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -27,7 +27,7 @@ template class SX128xInterface : public RadioLibInterface /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. virtual bool sleep() override; - bool isIRQPending() override { return lora.getIrqStatus() != 0; } + bool isIRQPending() override { return lora.getIrqFlags() != 0; } protected: /** From ff40a3f120c453f6f45e5bc6a7085022982f04ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 3 Sep 2024 21:21:40 +0200 Subject: [PATCH 1043/1377] fix RF switch for Tracker E (#4621) * fix RF switch for Tracker E * fix all flags for Radiolib * hopefully fix LR1110 * update to latest radiolib master - thanks @GUVWAF --- platformio.ini | 2 +- src/mesh/LR11x0Interface.cpp | 24 ++++++++++++++++++------ src/mesh/SX126xInterface.cpp | 7 +------ src/mesh/SX128xInterface.cpp | 8 +------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/platformio.ini b/platformio.ini index 31274ec90..87eb0afb7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -88,7 +88,7 @@ monitor_filters = direct lib_deps = ; jgromes/RadioLib@~6.6.0 - https://github.com/jgromes/RadioLib.git#eda4ec22ae0039f3b9a2844d68ac2023ac0076a5 + https://github.com/jgromes/RadioLib.git#3115fc2d6700a9aee05888791ac930a910f2628f https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 0201282db..8f0dc062e 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -50,11 +50,23 @@ template bool LR11x0Interface::init() limitPower(); +#ifdef TRACKER_T1000_E // Tracker T1000E uses DIO5, DIO6, DIO7, DIO8 for RF switching + + static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, + RADIOLIB_LR11X0_DIO8, RADIOLIB_NC}; + + static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 DIO7 DIO8 + {LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH}}, + {LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW}}, END_OF_MODE_TABLE, + }; + +#else + // set RF switch configuration for Wio WM1110 // Wio WM1110 uses DIO5 and DIO6 for RF switching - // NOTE: other boards may be different. If you are - // using a different board, you may need to wrap - // this in a conditional. static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; @@ -67,6 +79,8 @@ template bool LR11x0Interface::init() {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, }; +#endif + // We need to do this before begin() call #ifdef LR11X0_DIO_AS_RF_SWITCH LOG_DEBUG("Setting DIO RF switch\n"); @@ -218,9 +232,7 @@ template void LR11x0Interface::startReceive() // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving - int err = lora.startReceive( - RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_LR11X0_IRQ_RX_DONE, - 0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving + int err = lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); assert(err == RADIOLIB_ERR_NONE); RadioLibInterface::startReceive(); diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 47ac5da07..fcb3e4edb 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -12,9 +12,6 @@ #define SX126X_MAX_POWER 22 #endif -#define RADIOLIB_SX126X_IRQ_RX_DEFAULT \ - RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT | RADIOLIB_SX126X_IRQ_CRC_ERR | RADIOLIB_SX126X_IRQ_HEADER_ERR - template SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) @@ -267,9 +264,7 @@ template void SX126xInterface::startReceive() // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving - int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, - RADIOLIB_SX126X_IRQ_RX_DEFAULT | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | - RADIOLIB_SX126X_IRQ_HEADER_VALID); + int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index ae5fd6941..9ff9ac2d7 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -12,10 +12,6 @@ #define SX128X_MAX_POWER 13 #endif -#define RADIOLIB_SX128X_IRQ_RX_DEFAULT \ - RADIOLIB_SX128X_IRQ_RX_DONE | RADIOLIB_SX128X_IRQ_RX_TX_TIMEOUT | RADIOLIB_SX128X_IRQ_CRC_ERROR | \ - RADIOLIB_SX128X_IRQ_HEADER_ERROR - template SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) @@ -260,9 +256,7 @@ template void SX128xInterface::startReceive() #endif // We use the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving - int err = - lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED | - RADIOLIB_SX128X_IRQ_HEADER_VALID); + int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); From bb9ddcf2b56dd113bec2077189920c13e83aaf71 Mon Sep 17 00:00:00 2001 From: gitbisector Date: Wed, 4 Sep 2024 06:31:30 -0700 Subject: [PATCH 1044/1377] Same priority packets processed in enqueue order (#4608) * Same priority packets processed in enqueue order * Prefer same prio pkts on mesh over new ones. --------- Co-authored-by: Ben Meadors --- src/mesh/MeshPacketQueue.cpp | 54 ++++++++++++++---------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index 6581b1ce4..da49ecb61 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -1,4 +1,5 @@ #include "MeshPacketQueue.h" +#include "NodeDB.h" #include "configuration.h" #include @@ -16,13 +17,9 @@ bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_Mes { assert(p1 && p2); auto p1p = getPriority(p1), p2p = getPriority(p2); - // If priorities differ, use that - // for equal priorities, order by id (older packets have higher priority - this will briefly be wrong when IDs roll over but - // no big deal) - return (p1p != p2p) - ? (p1p < p2p) // prefer bigger priorities - : ((p1->id & ID_COUNTER_MASK) >= (p2->id & ID_COUNTER_MASK)); // Mask to counter portion, prefer smaller packet ids + // for equal priorities, prefer packets already on mesh. + return (p1p != p2p) ? (p1p > p2p) : (getFrom(p1) != nodeDB->getNodeNum() && getFrom(p2) == nodeDB->getNodeNum()); } MeshPacketQueue::MeshPacketQueue(size_t _maxLen) : maxLen(_maxLen) {} @@ -69,8 +66,9 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p) return replaceLowerPriorityPacket(p); } - queue.push_back(p); - std::push_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); + // Find the correct position using upper_bound to maintain a stable order + auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc); + queue.insert(it, p); // Insert packet at the found position return true; } @@ -81,9 +79,7 @@ meshtastic_MeshPacket *MeshPacketQueue::dequeue() } auto *p = queue.front(); - std::pop_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); - queue.pop_back(); - + queue.erase(queue.begin()); // Remove the highest-priority packet return p; } @@ -104,7 +100,6 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id) auto p = (*it); if (getFrom(p) == from && p->id == id) { queue.erase(it); - std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); return p; } } @@ -115,28 +110,21 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id) /** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) { - std::sort_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); // sort ascending based on priority (0 -> 127) - // find first packet which does not compare less (in priority) than parameter packet - auto low = std::lower_bound(queue.begin(), queue.end(), p, &CompareMeshPacketFunc); - - if (low == queue.begin()) { // if already at start, there are no packets with lower priority - return false; + if (queue.empty()) { + return false; // No packets to replace + } + // Check if the packet at the back has a lower priority than the new packet + auto &backPacket = queue.back(); + if (backPacket->priority < p->priority) { + // Remove the back packet + packetPool.release(backPacket); + queue.pop_back(); + // Insert the new packet in the correct order + enqueue(p); + return true; } - if (low == queue.end()) { - // all priorities in the vector are smaller than the incoming packet. Replace the lowest priority (first) element - low = queue.begin(); - } else { - // 'low' iterator points to first packet which does not compare less than parameter - --low; // iterate to lower priority packet - } - - if (getPriority(p) > getPriority(*low)) { - packetPool.release(*low); // deallocate and drop the packet we're replacing - *low = p; // replace low-pri packet at this position with incoming packet with higher priority - } - - std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); - return true; + // If the back packet's priority is not lower, no replacement occurs + return false; } \ No newline at end of file From 8d29ce939d458876087a20d0a2bd7e30161114a1 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Wed, 4 Sep 2024 15:27:00 -0700 Subject: [PATCH 1045/1377] changes from feedback --- CONTRIBUTING.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 736e2ba5b..d802c385e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,10 +33,15 @@ We encourage open communication and discussion before diving into code changes: 2. Create a new branch for your feature or bug fix 3. Make your changes 4. Test your changes thoroughly -5. Create a pull request with a clear description, using the provided template, of your changes +5. Create a pull request with a clear description, using the provided template, of your changes. Be sure to enable "Allow edits from maintainers". ## Coding Standards -[Placeholder for coding standards] +To ensure consistent code formatting across the project: -Thank you for contributing to Meshtastic! \ No newline at end of file +1. Install the [Trunk](https://marketplace.visualstudio.com/items?itemName=Trunk.io) extension for Visual Studio Code. +2. Before submitting your changes, run trunk fmt to automatically format your code according to our standards. + +Adhering to these formatting guidelines helps maintain code consistency and makes the review process smoother. + +Thank you for contributing to Meshtastic! From 4d57c99ad11b497fd00fed73ea66951542bff404 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Wed, 4 Sep 2024 15:28:17 -0700 Subject: [PATCH 1046/1377] add ticks --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d802c385e..a3659e2a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,7 @@ We encourage open communication and discussion before diving into code changes: To ensure consistent code formatting across the project: 1. Install the [Trunk](https://marketplace.visualstudio.com/items?itemName=Trunk.io) extension for Visual Studio Code. -2. Before submitting your changes, run trunk fmt to automatically format your code according to our standards. +2. Before submitting your changes, run `trunk fmt` to automatically format your code according to our standards. Adhering to these formatting guidelines helps maintain code consistency and makes the review process smoother. From 1d3d44061b9885f49ce0444bf8e94149ce395709 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Wed, 4 Sep 2024 15:33:28 -0700 Subject: [PATCH 1047/1377] lol of course trunk fmt --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3659e2a9..f579a7fe0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ We encourage open communication and discussion before diving into code changes: 2. Create a new branch for your feature or bug fix 3. Make your changes 4. Test your changes thoroughly -5. Create a pull request with a clear description, using the provided template, of your changes. Be sure to enable "Allow edits from maintainers". +5. Create a pull request with a clear description, using the provided template, of your changes. Be sure to enable "Allow edits from maintainers". ## Coding Standards From 9e55e6befbfb9438204d4e35aeeb7797eceb98f8 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Thu, 5 Sep 2024 19:16:06 +0800 Subject: [PATCH 1048/1377] Minor GPS fixes (#4630) 1. Remove unused line in GPS::probe 2. update new PositionModule::hasQualityTimeSource to handle MESHTASTIC_EXCLUDE_GPS --- src/gps/GPS.cpp | 5 +---- src/modules/PositionModule.cpp | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 12ef34c52..da9b5b1a8 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1252,9 +1252,6 @@ GnssModel_t GPS::probe(int serialSpeed) LOG_INFO("Aioha AG3335 detected, using AG3335 Module\n"); return GNSS_MODEL_AG3335; } - // Get version information for Airoha AG3335 - clearBuffer(); - _serial_gps->write("$PMTK605*31\r\n"); // Get version information clearBuffer(); @@ -1843,4 +1840,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 7c08835bc..cb6a58b2e 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -146,7 +146,11 @@ bool PositionModule::hasQualityTimesource() { bool setFromPhoneOrNtpToday = lastSetFromPhoneNtpOrGps == 0 ? false : (millis() - lastSetFromPhoneNtpOrGps) <= (SEC_PER_DAY * 1000UL); +#if MESHTASTIC_EXCLUDE_GPS + bool hasGpsOrRtc = (rtc_found.address != ScanI2C::ADDRESS_NONE.address); +#else bool hasGpsOrRtc = (gps && gps->isConnected()) || (rtc_found.address != ScanI2C::ADDRESS_NONE.address); +#endif return hasGpsOrRtc || setFromPhoneOrNtpToday; } From 7c6454f171014106dbcd946ac81f493ab14b6aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 5 Sep 2024 22:49:08 +0200 Subject: [PATCH 1049/1377] bring 2.4G back in line with preset bandwidth (#4634) --- src/mesh/RadioInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index eacd49644..7b6b4f5fa 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -420,7 +420,7 @@ void RadioInterface::applyModemConfig() switch (loraConfig.modem_preset) { case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 812.5 : 500; + bw = (myRegion->wideLora) ? 1625.0 : 500; cr = 5; sf = 7; break; From 972a5d57798d9045500b3fc93d814b0156b16154 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:25:34 -0700 Subject: [PATCH 1050/1377] Update Pull Request Template --- .github/pull_request_template.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 512dea311..6ccb4a105 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,9 @@ +### ❌ (Please delete all these tips and replace them with your text) ❌ + ## Thank you for sending in a pull request, here's some tips to get started! -(Please delete all these tips and replace with your text) - - Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first - to say "hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback + to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc... - Please do not check in files that don't have real changes - Please do not reformat lines that you didn't have to change the code on @@ -12,3 +12,4 @@ - If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description. - If your other co-developers have comments on your PR please tweak as needed. - Please also enable "Allow edits by maintainers". +- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord From bcdc36c07c270b8a5394adcd6a430d89f2be6b3f Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Fri, 6 Sep 2024 11:25:41 +1200 Subject: [PATCH 1051/1377] Refresh E-Ink to show changes in GPS icon --- src/graphics/Screen.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 04fe73e44..68fdba207 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1766,6 +1766,11 @@ void Screen::forceDisplay(bool forceUiUpdate) #ifdef USE_EINK // If requested, make sure queued commands are run, and UI has rendered a new frame if (forceUiUpdate) { + // Force a display refresh, in addition to the UI update + // Changing the GPS status bar icon apparently doesn't register as a change in image + // (False negative of the image hashing algorithm used to skip identical frames) + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); + // No delay between UI frame rendering setFastFramerate(); From e4e1ea971f12e203180bbc42378916c0f3ebc493 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 6 Sep 2024 08:45:57 +0800 Subject: [PATCH 1052/1377] Add missing linefeeds to gps code As reported by @caveman99, the required CRLFs were missing from the AG3335 setup code. --- src/gps/GPS.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index da9b5b1a8..6e6228d03 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -507,19 +507,19 @@ bool GPS::setup() delay(250); } else if (gnssModel == GNSS_MODEL_AG3335) { - _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B"); // Enable GPS+GALILEO+NAVIC + _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC // Configure NMEA (sentences will output once per fix) - _serial_gps->write("$PAIR062,0,0*3F"); // GGA ON - _serial_gps->write("$PAIR062,1,0*3F"); // GLL OFF - _serial_gps->write("$PAIR062,2,1*3D"); // GSA ON - _serial_gps->write("$PAIR062,3,0*3D"); // GSV OFF - _serial_gps->write("$PAIR062,4,0*3B"); // RMC ON - _serial_gps->write("$PAIR062,5,0*3B"); // VTG OFF - _serial_gps->write("$PAIR062,6,1*39"); // ZDA ON + _serial_gps->write("$PAIR062,0,0*3F\r\n"); // GGA ON + _serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF + _serial_gps->write("$PAIR062,2,1*3D\r\n"); // GSA ON + _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF + _serial_gps->write("$PAIR062,4,0*3B\r\n"); // RMC ON + _serial_gps->write("$PAIR062,5,0*3B\r\n"); // VTG OFF + _serial_gps->write("$PAIR062,6,1*39\r\n"); // ZDA ON delay(250); - _serial_gps->write("$PAIR513*3D"); // save configuration + _serial_gps->write("$PAIR513*3D\r\n"); // save configuration } else if (gnssModel == GNSS_MODEL_UBLOX) { // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) From 011e640e951ef8369c6d7da1f78be1258c1ae531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 6 Sep 2024 09:47:43 +0200 Subject: [PATCH 1053/1377] Add LR11x0 firmware version to init. --- src/mesh/LR11x0Interface.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 8f0dc062e..c0742f241 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -104,6 +104,13 @@ template bool LR11x0Interface::init() if (res == RADIOLIB_ERR_CHIP_NOT_FOUND) return false; + LR11x0VersionInfo_t version; + res = lora.getVersionInfo(&version); + if (res == RADIOLIB_ERR_NONE) + LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d\n", version.device, version.hardware, + version.fwMajor, version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, + version.almanacGNSS); + LOG_INFO("Frequency set to %f\n", getFreq()); LOG_INFO("Bandwidth set to %f\n", bw); LOG_INFO("Power output set to %d\n", power); From d72a836e072b9bc2183699f6bc62c2f113744e31 Mon Sep 17 00:00:00 2001 From: zerolint <179066619+zerolint@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:04:37 -0400 Subject: [PATCH 1054/1377] RAK13800 Ethernet improvements Fixes (#3618) by allowing more time for slower requests. Resolve Syslog not maintaining client causing issues on RAK13800. Resolve Ethernet static IP setting subnet as gateway IP. Reduce comment and log message ambiguity around API. Remove duplicate #if !MESHTASTIC_EXCLUDE_WEBSERVER block. --- src/DebugConfiguration.cpp | 4 +++- src/mesh/api/ServerAPI.cpp | 15 ++++++++++++++- src/mesh/api/ServerAPI.h | 6 +++++- src/mesh/api/ethServerAPI.h | 2 +- src/mesh/eth/ethClient.cpp | 7 ++++--- src/mesh/wifi/WiFiAPClient.cpp | 6 ++---- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index 23b140daf..1c081ae29 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -97,12 +97,14 @@ Syslog &Syslog::logMask(uint8_t priMask) void Syslog::enable() { + this->_client->begin(this->_port); this->_enabled = true; } void Syslog::disable() { this->_enabled = false; + this->_client->stop(); } bool Syslog::isEnabled() @@ -193,4 +195,4 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess return true; } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 140567ad2..3a483aac1 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -5,7 +5,7 @@ template ServerAPI::ServerAPI(T &_client) : StreamAPI(&client), concurrency::OSThread("ServerAPI"), client(_client) { - LOG_INFO("Incoming wifi connection\n"); + LOG_INFO("Incoming API connection\n"); } template ServerAPI::~ServerAPI() @@ -49,6 +49,16 @@ template int32_t APIServerPort::runOnce() if (client) { // Close any previous connection (see FIXME in header file) if (openAPI) { +#if RAK_4631 + // RAK13800 Ethernet requests periodically take more time + // This backoff addresses most cases keeping max wait < 1s + // Reconnections are delayed by full wait time + if (waitTime < 400) { + waitTime *= 2; + LOG_INFO("Previous TCP connection still open, trying again in %dms\n", waitTime); + return waitTime; + } +#endif LOG_INFO("Force closing previous TCP connection\n"); delete openAPI; } @@ -56,5 +66,8 @@ template int32_t APIServerPort::runOnce() openAPI = new T(client); } +#if RAK_4631 + waitTime = 100; +#endif return 100; // only check occasionally for incoming connections } diff --git a/src/mesh/api/ServerAPI.h b/src/mesh/api/ServerAPI.h index dd2a767c9..5b84fddd7 100644 --- a/src/mesh/api/ServerAPI.h +++ b/src/mesh/api/ServerAPI.h @@ -31,7 +31,7 @@ template class ServerAPI : public StreamAPI, private concurrency::OSTh }; /** - * Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed + * Listens for incoming connections and does accepts and creates instances of ServerAPI as needed */ template class APIServerPort : public U, private concurrency::OSThread { @@ -41,6 +41,10 @@ template class APIServerPort : public U, private concurrency: * delegate to the worker. Once coroutines are implemented we can relax this restriction. */ T *openAPI = NULL; +#if RAK_4631 + // Track wait time for RAK13800 Ethernet requests + int32_t waitTime = 100; +#endif public: explicit APIServerPort(int port); diff --git a/src/mesh/api/ethServerAPI.h b/src/mesh/api/ethServerAPI.h index 59673a684..6f214c75a 100644 --- a/src/mesh/api/ethServerAPI.h +++ b/src/mesh/api/ethServerAPI.h @@ -14,7 +14,7 @@ class ethServerAPI : public ServerAPI }; /** - * Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed + * Listens for incoming connections and does accepts and creates instances of EthernetServerAPI as needed */ class ethServerPort : public APIServerPort { diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 9f3bb8ab7..1c97f3bed 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -38,7 +38,7 @@ static int32_t reconnectETH() Ethernet.maintain(); if (!ethStartupComplete) { // Start web server - LOG_INFO("... Starting network services\n"); + LOG_INFO("Starting Ethernet network services\n"); #ifndef DISABLE_NTP LOG_INFO("Starting NTP time client\n"); @@ -131,7 +131,8 @@ bool initEthernet() status = Ethernet.begin(mac); } else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) { LOG_INFO("starting Ethernet Static\n"); - Ethernet.begin(mac, config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.subnet); + Ethernet.begin(mac, config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.gateway, + config.network.ipv4_config.subnet); status = 1; } else { LOG_INFO("Ethernet Disabled\n"); @@ -186,4 +187,4 @@ bool isEthernetAvailable() } } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index e733d1801..07b03222e 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -15,10 +15,8 @@ #include #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER -#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" #endif -#endif #include #include static void WiFiEvent(WiFiEvent_t event); @@ -58,7 +56,7 @@ static void onNetworkConnected() { if (!APStartupComplete) { // Start web server - LOG_INFO("Starting network services\n"); + LOG_INFO("Starting WiFi network services\n"); #ifdef ARCH_ESP32 // start mdns @@ -422,4 +420,4 @@ uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; } -#endif \ No newline at end of file +#endif From 8e0a342f065f8ecec55d67614e3c3d43fd8a6466 Mon Sep 17 00:00:00 2001 From: Robert Fisk Date: Wed, 4 Sep 2024 06:20:26 -0400 Subject: [PATCH 1055/1377] Gather canned message magic numbers into header defines. --- src/input/LinuxInput.cpp | 5 ++-- src/input/SerialKeyboard.cpp | 3 ++- src/input/kbI2cBase.cpp | 40 +++++++++++++++-------------- src/modules/CannedMessageModule.cpp | 36 +++++++++++++------------- src/modules/CannedMessageModule.h | 14 ++++++++++ 5 files changed, 58 insertions(+), 40 deletions(-) diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp index 6194195ed..d6bd4333b 100644 --- a/src/input/LinuxInput.cpp +++ b/src/input/LinuxInput.cpp @@ -1,6 +1,7 @@ #include "configuration.h" #if ARCH_PORTDUINO #include "LinuxInput.h" +#include "modules/CannedMessageModule.h" #include "platform/portduino/PortduinoGlue.h" #include #include @@ -147,11 +148,11 @@ int32_t LinuxInput::runOnce() case KEY_LEFT: // Left e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; break; - e.kbchar = 0xb4; + e.kbchar = CANNED_MESSAGE_KEY_LEFT; case KEY_RIGHT: // Right e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; break; - e.kbchar = 0xb7; + e.kbchar = CANNED_MESSAGE_KEY_RIGHT; case KEY_ENTER: // Enter e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; break; diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index fa3eb2528..e705ac550 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -1,5 +1,6 @@ #include "SerialKeyboard.h" #include "configuration.h" +#include "modules/CannedMessageModule.h" #ifdef INPUTBROKER_SERIAL_TYPE #define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file @@ -87,7 +88,7 @@ int32_t SerialKeyboard::runOnce() e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; } else if (!(shiftRegister2 & (1 << 2))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; - e.kbchar = 0xb7; + e.kbchar = CANNED_MESSAGE_KEY_RIGHT; } else if (!(shiftRegister2 & (1 << 1))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; } else if (!(shiftRegister2 & (1 << 0))) { diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 024b16b9e..f9d9f4748 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -2,6 +2,7 @@ #include "configuration.h" #include "detect/ScanI2C.h" #include "detect/ScanI2CTwoWire.h" +#include "modules/CannedMessageModule.h" extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -94,7 +95,7 @@ int32_t KbI2cBase::runOnce() case 'e': // sym e if (is_sym) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; - e.kbchar = 0xb5; + e.kbchar = CANNED_MESSAGE_KEY_UP; is_sym = false; // reset sym state after second keypress } else { e.inputEvent = ANYKEY; @@ -104,7 +105,7 @@ int32_t KbI2cBase::runOnce() case 'x': // sym x if (is_sym) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; - e.kbchar = 0xb6; + e.kbchar = CANNED_MESSAGE_KEY_DOWN; is_sym = false; // reset sym state after second keypress } else { e.inputEvent = ANYKEY; @@ -134,8 +135,8 @@ int32_t KbI2cBase::runOnce() case 0x13: // Code scanner says the SYM key is 0x13 is_sym = !is_sym; e.inputEvent = ANYKEY; - e.kbchar = - is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active + e.kbchar = is_sym ? CANNED_MESSAGE_KEY_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that + : CANNED_MESSAGE_KEY_FN_SYMBOL_OFF; // the modifier key is active break; case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; @@ -214,7 +215,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = 0xac; // mute notifications + e.kbchar = CANNED_MESSAGE_KEY_MUTE_TOGGLE; // mute notifications } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -224,7 +225,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = 0x11; // Increase Brightness code + e.kbchar = CANNED_MESSAGE_KEY_BRIGHTNESS_UP; // Increase Brightness code } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -234,7 +235,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = 0x12; // Decrease Brightness code + e.kbchar = CANNED_MESSAGE_KEY_BRIGHTNESS_DOWN; // Decrease Brightness code } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -244,7 +245,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = 0xaf; // (fn + space) + e.kbchar = CANNED_MESSAGE_KEY_SEND_PING; // (fn + space) } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -254,7 +255,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = 0x9e; + e.kbchar = CANNED_MESSAGE_KEY_GPS_TOGGLE; } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -269,32 +270,33 @@ int32_t KbI2cBase::runOnce() break; case 0xb5: // Up e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; - e.kbchar = 0xb5; + e.kbchar = CANNED_MESSAGE_KEY_UP; break; case 0xb6: // Down e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; - e.kbchar = 0xb6; + e.kbchar = CANNED_MESSAGE_KEY_DOWN; break; case 0xb4: // Left e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; - e.kbchar = 0xb4; + e.kbchar = CANNED_MESSAGE_KEY_LEFT; break; case 0xb7: // Right e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; - e.kbchar = 0xb7; + e.kbchar = CANNED_MESSAGE_KEY_RIGHT; break; case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker)) // toggle moddifiers button. is_sym = !is_sym; e.inputEvent = ANYKEY; - e.kbchar = is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active + e.kbchar = is_sym ? CANNED_MESSAGE_KEY_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the + : CANNED_MESSAGE_KEY_FN_SYMBOL_OFF; // modifier key is active break; - case 0x90: // fn+r + case 0x90: // fn+r CANNED_MESSAGE_KEY_REBOOT case 0x91: // fn+t - case 0x9b: // fn+s - case 0xac: // fn+m - case 0x9e: // fn+g - case 0xaf: // fn+space + case 0x9b: // fn+s CANNED_MESSAGE_KEY_SHUTDOWN + case 0xac: // fn+m CANNED_MESSAGE_KEY_MUTE_TOGGLE + case 0x9e: // fn+g CANNED_MESSAGE_KEY_GPS_TOGGLE + case 0xaf: // fn+space CANNED_MESSAGE_KEY_SEND_PING // just pass those unmodified e.inputEvent = ANYKEY; e.kbchar = c; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 4df5a03fc..6f8739a01 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -190,17 +190,17 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) #if defined(T_WATCH_S3) || defined(RAK14014) if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { - this->payload = 0xb4; + this->payload = CANNED_MESSAGE_KEY_LEFT; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { - this->payload = 0xb7; + this->payload = CANNED_MESSAGE_KEY_RIGHT; } #else // tweak for left/right events generated via trackball/touch with empty kbchar if (!event->kbchar) { if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { - this->payload = 0xb4; + this->payload = CANNED_MESSAGE_KEY_LEFT; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { - this->payload = 0xb7; + this->payload = CANNED_MESSAGE_KEY_RIGHT; } } else { // pass the pressed key @@ -222,26 +222,26 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) // Run modifier key code below, (doesnt inturrupt typing or reset to start screen page) switch (event->kbchar) { - case 0x11: // make screen brighter + case CANNED_MESSAGE_KEY_BRIGHTNESS_UP: // make screen brighter if (screen) screen->increaseBrightness(); LOG_DEBUG("increasing Screen Brightness\n"); break; - case 0x12: // make screen dimmer + case CANNED_MESSAGE_KEY_BRIGHTNESS_DOWN: // make screen dimmer if (screen) screen->decreaseBrightness(); LOG_DEBUG("Decreasing Screen Brightness\n"); break; - case 0xf1: // draw modifier (function) symbal + case CANNED_MESSAGE_KEY_FN_SYMBOL_ON: // draw modifier (function) symbal if (screen) screen->setFunctionSymbal("Fn"); break; - case 0xf2: // remove modifier (function) symbal + case CANNED_MESSAGE_KEY_FN_SYMBOL_OFF: // remove modifier (function) symbal if (screen) screen->removeFunctionSymbal("Fn"); break; // mute (switch off/toggle) external notifications on fn+m - case 0xac: + case CANNED_MESSAGE_KEY_MUTE_TOGGLE: if (moduleConfig.external_notification.enabled == true) { if (externalNotificationModule->getMute()) { externalNotificationModule->setMute(false); @@ -257,7 +257,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } break; - case 0x9e: // toggle GPS like triple press does + case CANNED_MESSAGE_KEY_GPS_TOGGLE: // toggle GPS like triple press does #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) { gps->toggleGpsMode(); @@ -267,7 +267,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) showTemporaryMessage("GPS Toggled"); #endif break; - case 0xaf: // fn+space send network ping like double press does + case CANNED_MESSAGE_KEY_SEND_PING: // fn+space send network ping like double press does service->refreshLocalMeshNode(); if (service->trySendPosition(NODENUM_BROADCAST, true)) { showTemporaryMessage("Position \nUpdate Sent"); @@ -283,7 +283,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) validEvent = true; break; } - if (screen && (event->kbchar != 0xf1)) { + if (screen && (event->kbchar != CANNED_MESSAGE_KEY_FN_SYMBOL_ON)) { screen->removeFunctionSymbal("Fn"); // remove modifier (function) symbal } } @@ -505,7 +505,7 @@ int32_t CannedMessageModule::runOnce() } } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { switch (this->payload) { - case 0xb4: // left + case CANNED_MESSAGE_KEY_LEFT: if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { @@ -540,7 +540,7 @@ int32_t CannedMessageModule::runOnce() } } break; - case 0xb7: // right + case CANNED_MESSAGE_KEY_RIGHT: if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { @@ -602,19 +602,19 @@ int32_t CannedMessageModule::runOnce() this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } break; - case 0xb4: // left - case 0xb7: // right + case CANNED_MESSAGE_KEY_LEFT: + case CANNED_MESSAGE_KEY_RIGHT: // already handled above break; // handle fn+s for shutdown - case 0x9b: + case CANNED_MESSAGE_KEY_SHUTDOWN: if (screen) screen->startAlert("Shutting down..."); shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; // and fn+r for reboot - case 0x90: + case CANNED_MESSAGE_KEY_REBOOT: if (screen) screen->startAlert("Rebooting..."); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 9e6af8890..9ff1ec192 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -43,6 +43,20 @@ struct Letter { #define CANNED_MESSAGE_MODULE_ENABLE 0 #endif +#define CANNED_MESSAGE_KEY_BRIGHTNESS_UP 0x11 +#define CANNED_MESSAGE_KEY_BRIGHTNESS_DOWN 0x12 +#define CANNED_MESSAGE_KEY_REBOOT 0x90 +#define CANNED_MESSAGE_KEY_SHUTDOWN 0x9b +#define CANNED_MESSAGE_KEY_GPS_TOGGLE 0x9e +#define CANNED_MESSAGE_KEY_MUTE_TOGGLE 0xac +#define CANNED_MESSAGE_KEY_SEND_PING 0xaf +#define CANNED_MESSAGE_KEY_LEFT 0xb4 +#define CANNED_MESSAGE_KEY_UP 0xb5 +#define CANNED_MESSAGE_KEY_DOWN 0xb6 +#define CANNED_MESSAGE_KEY_RIGHT 0xb7 +#define CANNED_MESSAGE_KEY_FN_SYMBOL_ON 0xf1 +#define CANNED_MESSAGE_KEY_FN_SYMBOL_OFF 0xf2 + class CannedMessageModule : public SinglePortModule, public Observable, private concurrency::OSThread { CallbackObserver inputObserver = From 962d9ff2207dce18b6828d42acd1fb97889e1905 Mon Sep 17 00:00:00 2001 From: Robert Fisk Date: Thu, 5 Sep 2024 22:32:24 -0400 Subject: [PATCH 1056/1377] Move defines to input broker --- src/input/InputBroker.h | 14 ++++++++++ src/input/LinuxInput.cpp | 5 ++-- src/input/SerialKeyboard.cpp | 3 +-- src/input/kbI2cBase.cpp | 41 ++++++++++++++--------------- src/modules/CannedMessageModule.cpp | 36 ++++++++++++------------- src/modules/CannedMessageModule.h | 16 +---------- 6 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 57c25af4b..082268f0a 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -4,6 +4,20 @@ #define ANYKEY 0xFF #define MATRIXKEY 0xFE +#define INPUT_BROKER_MSG_BRIGHTNESS_UP 0x11 +#define INPUT_BROKER_MSG_BRIGHTNESS_DOWN 0x12 +#define INPUT_BROKER_MSG_REBOOT 0x90 +#define INPUT_BROKER_MSG_SHUTDOWN 0x9b +#define INPUT_BROKER_MSG_GPS_TOGGLE 0x9e +#define INPUT_BROKER_MSG_MUTE_TOGGLE 0xac +#define INPUT_BROKER_MSG_SEND_PING 0xaf +#define INPUT_BROKER_MSG_LEFT 0xb4 +#define INPUT_BROKER_MSG_UP 0xb5 +#define INPUT_BROKER_MSG_DOWN 0xb6 +#define INPUT_BROKER_MSG_RIGHT 0xb7 +#define INPUT_BROKER_MSG_FN_SYMBOL_ON 0xf1 +#define INPUT_BROKER_MSG_FN_SYMBOL_OFF 0xf2 + typedef struct _InputEvent { const char *source; char inputEvent; diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp index d6bd4333b..57a87b0ef 100644 --- a/src/input/LinuxInput.cpp +++ b/src/input/LinuxInput.cpp @@ -1,7 +1,6 @@ #include "configuration.h" #if ARCH_PORTDUINO #include "LinuxInput.h" -#include "modules/CannedMessageModule.h" #include "platform/portduino/PortduinoGlue.h" #include #include @@ -148,11 +147,11 @@ int32_t LinuxInput::runOnce() case KEY_LEFT: // Left e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; break; - e.kbchar = CANNED_MESSAGE_KEY_LEFT; + e.kbchar = INPUT_BROKER_MSG_LEFT; case KEY_RIGHT: // Right e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; break; - e.kbchar = CANNED_MESSAGE_KEY_RIGHT; + e.kbchar = INPUT_BROKER_MSG_RIGHT; case KEY_ENTER: // Enter e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; break; diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index e705ac550..7b7a2f3ec 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -1,6 +1,5 @@ #include "SerialKeyboard.h" #include "configuration.h" -#include "modules/CannedMessageModule.h" #ifdef INPUTBROKER_SERIAL_TYPE #define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file @@ -88,7 +87,7 @@ int32_t SerialKeyboard::runOnce() e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; } else if (!(shiftRegister2 & (1 << 2))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; - e.kbchar = CANNED_MESSAGE_KEY_RIGHT; + e.kbchar = INPUT_BROKER_MSG_RIGHT; } else if (!(shiftRegister2 & (1 << 1))) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; } else if (!(shiftRegister2 & (1 << 0))) { diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index f9d9f4748..2692fc80d 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -2,7 +2,6 @@ #include "configuration.h" #include "detect/ScanI2C.h" #include "detect/ScanI2CTwoWire.h" -#include "modules/CannedMessageModule.h" extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -95,7 +94,7 @@ int32_t KbI2cBase::runOnce() case 'e': // sym e if (is_sym) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; - e.kbchar = CANNED_MESSAGE_KEY_UP; + e.kbchar = INPUT_BROKER_MSG_UP; is_sym = false; // reset sym state after second keypress } else { e.inputEvent = ANYKEY; @@ -105,7 +104,7 @@ int32_t KbI2cBase::runOnce() case 'x': // sym x if (is_sym) { e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; - e.kbchar = CANNED_MESSAGE_KEY_DOWN; + e.kbchar = INPUT_BROKER_MSG_DOWN; is_sym = false; // reset sym state after second keypress } else { e.inputEvent = ANYKEY; @@ -135,8 +134,8 @@ int32_t KbI2cBase::runOnce() case 0x13: // Code scanner says the SYM key is 0x13 is_sym = !is_sym; e.inputEvent = ANYKEY; - e.kbchar = is_sym ? CANNED_MESSAGE_KEY_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that - : CANNED_MESSAGE_KEY_FN_SYMBOL_OFF; // the modifier key is active + e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that + : INPUT_BROKER_MSG_FN_SYMBOL_OFF; // the modifier key is active break; case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; @@ -215,7 +214,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = CANNED_MESSAGE_KEY_MUTE_TOGGLE; // mute notifications + e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; // mute notifications } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -225,7 +224,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = CANNED_MESSAGE_KEY_BRIGHTNESS_UP; // Increase Brightness code + e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_UP; // Increase Brightness code } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -235,7 +234,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = CANNED_MESSAGE_KEY_BRIGHTNESS_DOWN; // Decrease Brightness code + e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_DOWN; // Decrease Brightness code } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -245,7 +244,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = CANNED_MESSAGE_KEY_SEND_PING; // (fn + space) + e.kbchar = INPUT_BROKER_MSG_SEND_PING; // (fn + space) } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -255,7 +254,7 @@ int32_t KbI2cBase::runOnce() if (is_sym) { is_sym = false; e.inputEvent = ANYKEY; - e.kbchar = CANNED_MESSAGE_KEY_GPS_TOGGLE; + e.kbchar = INPUT_BROKER_MSG_GPS_TOGGLE; } else { e.inputEvent = ANYKEY; e.kbchar = c; @@ -270,33 +269,33 @@ int32_t KbI2cBase::runOnce() break; case 0xb5: // Up e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; - e.kbchar = CANNED_MESSAGE_KEY_UP; + e.kbchar = INPUT_BROKER_MSG_UP; break; case 0xb6: // Down e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; - e.kbchar = CANNED_MESSAGE_KEY_DOWN; + e.kbchar = INPUT_BROKER_MSG_DOWN; break; case 0xb4: // Left e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; - e.kbchar = CANNED_MESSAGE_KEY_LEFT; + e.kbchar = INPUT_BROKER_MSG_LEFT; break; case 0xb7: // Right e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; - e.kbchar = CANNED_MESSAGE_KEY_RIGHT; + e.kbchar = INPUT_BROKER_MSG_RIGHT; break; case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker)) // toggle moddifiers button. is_sym = !is_sym; e.inputEvent = ANYKEY; - e.kbchar = is_sym ? CANNED_MESSAGE_KEY_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the - : CANNED_MESSAGE_KEY_FN_SYMBOL_OFF; // modifier key is active + e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the + : INPUT_BROKER_MSG_FN_SYMBOL_OFF; // modifier key is active break; - case 0x90: // fn+r CANNED_MESSAGE_KEY_REBOOT + case 0x90: // fn+r INPUT_BROKER_MSG_REBOOT case 0x91: // fn+t - case 0x9b: // fn+s CANNED_MESSAGE_KEY_SHUTDOWN - case 0xac: // fn+m CANNED_MESSAGE_KEY_MUTE_TOGGLE - case 0x9e: // fn+g CANNED_MESSAGE_KEY_GPS_TOGGLE - case 0xaf: // fn+space CANNED_MESSAGE_KEY_SEND_PING + case 0x9b: // fn+s INPUT_BROKER_MSG_SHUTDOWN + case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE + case 0x9e: // fn+g INPUT_BROKER_MSG_GPS_TOGGLE + case 0xaf: // fn+space INPUT_BROKER_MSG_SEND_PING // just pass those unmodified e.inputEvent = ANYKEY; e.kbchar = c; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 6f8739a01..87a3e8927 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -190,17 +190,17 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) #if defined(T_WATCH_S3) || defined(RAK14014) if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { - this->payload = CANNED_MESSAGE_KEY_LEFT; + this->payload = INPUT_BROKER_MSG_LEFT; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { - this->payload = CANNED_MESSAGE_KEY_RIGHT; + this->payload = INPUT_BROKER_MSG_RIGHT; } #else // tweak for left/right events generated via trackball/touch with empty kbchar if (!event->kbchar) { if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { - this->payload = CANNED_MESSAGE_KEY_LEFT; + this->payload = INPUT_BROKER_MSG_LEFT; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { - this->payload = CANNED_MESSAGE_KEY_RIGHT; + this->payload = INPUT_BROKER_MSG_RIGHT; } } else { // pass the pressed key @@ -222,26 +222,26 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) // Run modifier key code below, (doesnt inturrupt typing or reset to start screen page) switch (event->kbchar) { - case CANNED_MESSAGE_KEY_BRIGHTNESS_UP: // make screen brighter + case INPUT_BROKER_MSG_BRIGHTNESS_UP: // make screen brighter if (screen) screen->increaseBrightness(); LOG_DEBUG("increasing Screen Brightness\n"); break; - case CANNED_MESSAGE_KEY_BRIGHTNESS_DOWN: // make screen dimmer + case INPUT_BROKER_MSG_BRIGHTNESS_DOWN: // make screen dimmer if (screen) screen->decreaseBrightness(); LOG_DEBUG("Decreasing Screen Brightness\n"); break; - case CANNED_MESSAGE_KEY_FN_SYMBOL_ON: // draw modifier (function) symbal + case INPUT_BROKER_MSG_FN_SYMBOL_ON: // draw modifier (function) symbal if (screen) screen->setFunctionSymbal("Fn"); break; - case CANNED_MESSAGE_KEY_FN_SYMBOL_OFF: // remove modifier (function) symbal + case INPUT_BROKER_MSG_FN_SYMBOL_OFF: // remove modifier (function) symbal if (screen) screen->removeFunctionSymbal("Fn"); break; // mute (switch off/toggle) external notifications on fn+m - case CANNED_MESSAGE_KEY_MUTE_TOGGLE: + case INPUT_BROKER_MSG_MUTE_TOGGLE: if (moduleConfig.external_notification.enabled == true) { if (externalNotificationModule->getMute()) { externalNotificationModule->setMute(false); @@ -257,7 +257,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } } break; - case CANNED_MESSAGE_KEY_GPS_TOGGLE: // toggle GPS like triple press does + case INPUT_BROKER_MSG_GPS_TOGGLE: // toggle GPS like triple press does #if !MESHTASTIC_EXCLUDE_GPS if (gps != nullptr) { gps->toggleGpsMode(); @@ -267,7 +267,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) showTemporaryMessage("GPS Toggled"); #endif break; - case CANNED_MESSAGE_KEY_SEND_PING: // fn+space send network ping like double press does + case INPUT_BROKER_MSG_SEND_PING: // fn+space send network ping like double press does service->refreshLocalMeshNode(); if (service->trySendPosition(NODENUM_BROADCAST, true)) { showTemporaryMessage("Position \nUpdate Sent"); @@ -283,7 +283,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) validEvent = true; break; } - if (screen && (event->kbchar != CANNED_MESSAGE_KEY_FN_SYMBOL_ON)) { + if (screen && (event->kbchar != INPUT_BROKER_MSG_FN_SYMBOL_ON)) { screen->removeFunctionSymbal("Fn"); // remove modifier (function) symbal } } @@ -505,7 +505,7 @@ int32_t CannedMessageModule::runOnce() } } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { switch (this->payload) { - case CANNED_MESSAGE_KEY_LEFT: + case INPUT_BROKER_MSG_LEFT: if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { @@ -540,7 +540,7 @@ int32_t CannedMessageModule::runOnce() } } break; - case CANNED_MESSAGE_KEY_RIGHT: + case INPUT_BROKER_MSG_RIGHT: if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { @@ -602,19 +602,19 @@ int32_t CannedMessageModule::runOnce() this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } break; - case CANNED_MESSAGE_KEY_LEFT: - case CANNED_MESSAGE_KEY_RIGHT: + case INPUT_BROKER_MSG_LEFT: + case INPUT_BROKER_MSG_RIGHT: // already handled above break; // handle fn+s for shutdown - case CANNED_MESSAGE_KEY_SHUTDOWN: + case INPUT_BROKER_MSG_SHUTDOWN: if (screen) screen->startAlert("Shutting down..."); shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; break; // and fn+r for reboot - case CANNED_MESSAGE_KEY_REBOOT: + case INPUT_BROKER_MSG_REBOOT: if (screen) screen->startAlert("Rebooting..."); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 9ff1ec192..368574c40 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -43,20 +43,6 @@ struct Letter { #define CANNED_MESSAGE_MODULE_ENABLE 0 #endif -#define CANNED_MESSAGE_KEY_BRIGHTNESS_UP 0x11 -#define CANNED_MESSAGE_KEY_BRIGHTNESS_DOWN 0x12 -#define CANNED_MESSAGE_KEY_REBOOT 0x90 -#define CANNED_MESSAGE_KEY_SHUTDOWN 0x9b -#define CANNED_MESSAGE_KEY_GPS_TOGGLE 0x9e -#define CANNED_MESSAGE_KEY_MUTE_TOGGLE 0xac -#define CANNED_MESSAGE_KEY_SEND_PING 0xaf -#define CANNED_MESSAGE_KEY_LEFT 0xb4 -#define CANNED_MESSAGE_KEY_UP 0xb5 -#define CANNED_MESSAGE_KEY_DOWN 0xb6 -#define CANNED_MESSAGE_KEY_RIGHT 0xb7 -#define CANNED_MESSAGE_KEY_FN_SYMBOL_ON 0xf1 -#define CANNED_MESSAGE_KEY_FN_SYMBOL_OFF 0xf2 - class CannedMessageModule : public SinglePortModule, public Observable, private concurrency::OSThread { CallbackObserver inputObserver = @@ -237,4 +223,4 @@ class CannedMessageModule : public SinglePortModule, public Observable Date: Fri, 6 Sep 2024 13:55:56 +0200 Subject: [PATCH 1057/1377] tryfix #4384 (#4642) * tryfix #4384 - don't assume we want that functionality if the Accelerometer was found. This is only for T-Watch * Add config.display.wake_on_tap_or_motion default to RAK --------- Co-authored-by: Ben Meadors --- src/main.cpp | 2 -- src/mesh/NodeDB.cpp | 5 ++++- src/modules/AdminModule.cpp | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index e7261c5fa..83758d5ee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -635,8 +635,6 @@ void setup() #if !MESHTASTIC_EXCLUDE_I2C #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (acc_info.type != ScanI2C::DeviceType::NONE) { - config.display.wake_on_tap_or_motion = true; - moduleConfig.external_notification.enabled = true; accelerometerThread = new AccelerometerThread(acc_info.type); } #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index fa3667f32..6613ad3c3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -362,6 +362,9 @@ void NodeDB::installDefaultConfig() #ifdef DISPLAY_FLIP_SCREEN config.display.flip_screen = true; #endif +#ifdef RAK4630 + config.display.wake_on_tap_or_motion = true; +#endif #ifdef T_WATCH_S3 config.display.screen_on_secs = 30; config.display.wake_on_tap_or_motion = true; @@ -1190,4 +1193,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} \ No newline at end of file +} diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index bfe3a9ba5..d88f17a86 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -406,7 +406,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_INFO("Setting config: Device\n"); config.has_device = true; #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true) { + if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true && + accelerometerThread->enabled == false) { accelerometerThread->start(); } #endif @@ -484,7 +485,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) requiresReboot = false; } #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true) { + if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true && + accelerometerThread->enabled == false) { accelerometerThread->start(); } #endif From b8cee51e84ed967e3cdfd1b22d00dbc7274991b0 Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:27:28 +0000 Subject: [PATCH 1058/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 15 ++++++++++----- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/protobufs b/protobufs index 5f7c91adb..96b10c036 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5f7c91adb97187e0cb2140de7057344d93444bd1 +Subproject commit 96b10c0364c3f13940df30ddabc74488ff1ba76e diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index f5bacea52..31211a91b 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 676 +#define meshtastic_ChannelSet_size 679 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index eb03ddc58..66ffa0a4b 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -510,6 +510,8 @@ typedef struct _meshtastic_Config_LoRaConfig { uint32_t ignore_incoming[3]; /* If true, the device will not process any packets received via LoRa that passed via MQTT anywhere on the path towards it. */ bool ignore_mqtt; + /* Sets the ok_to_mqtt bit on outgoing packets */ + bool config_ok_to_mqtt; } meshtastic_Config_LoRaConfig; typedef struct _meshtastic_Config_BluetoothConfig { @@ -656,7 +658,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} @@ -667,7 +669,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} -#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} +#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} @@ -746,6 +748,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_pa_fan_disabled_tag 15 #define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103 #define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104 +#define meshtastic_Config_LoRaConfig_config_ok_to_mqtt_tag 105 #define meshtastic_Config_BluetoothConfig_enabled_tag 1 #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 @@ -887,7 +890,8 @@ X(a, STATIC, SINGULAR, BOOL, sx126x_rx_boosted_gain, 13) \ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \ X(a, STATIC, SINGULAR, BOOL, pa_fan_disabled, 15) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ -X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) +X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) \ +X(a, STATIC, SINGULAR, BOOL, config_ok_to_mqtt, 105) #define meshtastic_Config_LoRaConfig_CALLBACK NULL #define meshtastic_Config_LoRaConfig_DEFAULT NULL @@ -944,7 +948,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_DeviceConfig_size 98 #define meshtastic_Config_DisplayConfig_size 30 -#define meshtastic_Config_LoRaConfig_size 82 +#define meshtastic_Config_LoRaConfig_size 85 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 692402210..209084220 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -358,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3497 +#define meshtastic_OEMStore_size 3500 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 91a23dc4f..72f29500c 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 664 +#define meshtastic_LocalConfig_size 667 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 9d7ff74a1..053b69a77 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -580,6 +580,9 @@ typedef struct _meshtastic_Data { /* Defaults to false. If true, then what is in the payload should be treated as an emoji like giving a message a heart or poop emoji. */ uint32_t emoji; + /* Defaults to false. Indicates the user approves the packet being uploaded to MQTT. */ + bool has_ok_to_mqtt; + bool ok_to_mqtt; } meshtastic_Data; /* Waypoint message, used to share arbitrary locations across the mesh */ @@ -1082,7 +1085,7 @@ extern "C" { #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} -#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} +#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} @@ -1107,7 +1110,7 @@ extern "C" { #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} -#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} +#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} @@ -1176,6 +1179,7 @@ extern "C" { #define meshtastic_Data_request_id_tag 6 #define meshtastic_Data_reply_id_tag 7 #define meshtastic_Data_emoji_tag 8 +#define meshtastic_Data_ok_to_mqtt_tag 9 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1351,7 +1355,8 @@ X(a, STATIC, SINGULAR, FIXED32, dest, 4) \ X(a, STATIC, SINGULAR, FIXED32, source, 5) \ X(a, STATIC, SINGULAR, FIXED32, request_id, 6) \ X(a, STATIC, SINGULAR, FIXED32, reply_id, 7) \ -X(a, STATIC, SINGULAR, FIXED32, emoji, 8) +X(a, STATIC, SINGULAR, FIXED32, emoji, 8) \ +X(a, STATIC, OPTIONAL, BOOL, ok_to_mqtt, 9) #define meshtastic_Data_CALLBACK NULL #define meshtastic_Data_DEFAULT NULL @@ -1629,13 +1634,13 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ChunkedPayload_size 245 #define meshtastic_ClientNotification_size 415 #define meshtastic_Compressed_size 243 -#define meshtastic_Data_size 270 +#define meshtastic_Data_size 272 #define meshtastic_DeviceMetadata_size 46 #define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 426 -#define meshtastic_MeshPacket_size 364 +#define meshtastic_MeshPacket_size 366 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 From c77b89d85c836a9ec2b2d0302c98eb88abcefe3b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:51:22 -0500 Subject: [PATCH 1059/1377] [create-pull-request] automated change (#4645) --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index 96b10c036..0acaec6ef 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 96b10c0364c3f13940df30ddabc74488ff1ba76e +Subproject commit 0acaec6eff00e748beeae89148093221f131cd9c diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 053b69a77..948e89f22 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -580,9 +580,9 @@ typedef struct _meshtastic_Data { /* Defaults to false. If true, then what is in the payload should be treated as an emoji like giving a message a heart or poop emoji. */ uint32_t emoji; - /* Defaults to false. Indicates the user approves the packet being uploaded to MQTT. */ - bool has_ok_to_mqtt; - bool ok_to_mqtt; + /* Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT. */ + bool has_bitfield; + uint8_t bitfield; } meshtastic_Data; /* Waypoint message, used to share arbitrary locations across the mesh */ @@ -1179,7 +1179,7 @@ extern "C" { #define meshtastic_Data_request_id_tag 6 #define meshtastic_Data_reply_id_tag 7 #define meshtastic_Data_emoji_tag 8 -#define meshtastic_Data_ok_to_mqtt_tag 9 +#define meshtastic_Data_bitfield_tag 9 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1356,7 +1356,7 @@ X(a, STATIC, SINGULAR, FIXED32, source, 5) \ X(a, STATIC, SINGULAR, FIXED32, request_id, 6) \ X(a, STATIC, SINGULAR, FIXED32, reply_id, 7) \ X(a, STATIC, SINGULAR, FIXED32, emoji, 8) \ -X(a, STATIC, OPTIONAL, BOOL, ok_to_mqtt, 9) +X(a, STATIC, OPTIONAL, UINT32, bitfield, 9) #define meshtastic_Data_CALLBACK NULL #define meshtastic_Data_DEFAULT NULL @@ -1634,13 +1634,13 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ChunkedPayload_size 245 #define meshtastic_ClientNotification_size 415 #define meshtastic_Compressed_size 243 -#define meshtastic_Data_size 272 +#define meshtastic_Data_size 273 #define meshtastic_DeviceMetadata_size 46 #define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 426 -#define meshtastic_MeshPacket_size 366 +#define meshtastic_MeshPacket_size 367 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 From 2f2ddae12a7a41ce13d97c1486833f25cf0cb267 Mon Sep 17 00:00:00 2001 From: git bisector Date: Fri, 6 Sep 2024 17:19:53 -0700 Subject: [PATCH 1060/1377] Report PWD when no battery present. --- src/modules/Telemetry/DeviceTelemetry.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index f22685d43..305be9904 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -100,8 +100,9 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() #if ARCH_PORTDUINO t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL; #else - t.variant.device_metrics.battery_level = - powerStatus->getIsCharging() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent(); + t.variant.device_metrics.battery_level = (!powerStatus->getHasBattery() || powerStatus->getIsCharging()) + ? MAGIC_USB_BATTERY_LEVEL + : powerStatus->getBatteryChargePercent(); #endif t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent(); t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0; From ba28ffb65ab5bc40fe7c89a4a8d3da48e2ae40db Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 7 Sep 2024 11:59:45 +0800 Subject: [PATCH 1061/1377] Simplify GNSS Probe code This patch takes inspiration from our I2CDetect code where we have many sensors that can be detected rather simply. It creates a new macro, PROBE_SIMPLE(Chip name, Command to run, response, Driver, timeout) and converts existing simple cases to use this macro. --- src/gps/GPS.cpp | 76 +++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 6e6228d03..b5e1991ae 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1184,6 +1184,15 @@ int GPS::prepareDeepSleep(void *unused) return 0; } +#define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \ + LOG_DEBUG("Trying " TOWRITE " (" CHIP ") ...\n"); \ + clearBuffer(); \ + _serial_gps->write(TOWRITE "\r\n"); \ + if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \ + LOG_INFO(CHIP " detected, using " #DRIVER " Module\n"); \ + return DRIVER; \ + } + GnssModel_t GPS::probe(int serialSpeed) { #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) @@ -1198,11 +1207,7 @@ GnssModel_t GPS::probe(int serialSpeed) #ifdef GNSS_AIROHA return GNSS_MODEL_AG3335; #endif -#ifdef GPS_DEBUG - for (int i = 0; i < 20; i++) { - getACK("$GP", 200); - } -#endif + memset(&info, 0, sizeof(struct uBloxGnssModelInfo)); uint8_t buffer[768] = {0}; delay(100); @@ -1211,67 +1216,24 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); delay(20); - // get version information from Unicore UFirebirdII Series - // Works for: UC6580, UM620, UM621, UM670A, UM680A, or UM681A - _serial_gps->write("$PDTINFO\r\n"); - delay(750); - if (getACK("UC6580", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("UC6580 detected, using UC6580 Module\n"); - return GNSS_MODEL_UC6580; - } - - clearBuffer(); - _serial_gps->write("$PDTINFO\r\n"); - delay(750); - if (getACK("UM600", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("UM600 detected, using UC6580 Module\n"); - return GNSS_MODEL_UC6580; - } - - // Get version information for ATGM336H - clearBuffer(); - _serial_gps->write("$PCAS06,1*1A\r\n"); - if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("ATGM336H GNSS init succeeded, using ATGM336H Module\n"); - return GNSS_MODEL_ATGM336H; - } - + // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A + PROBE_SIMPLE("UC6580", "$PDTINFO", "UC6580", GNSS_MODEL_UC6580, 500); + PROBE_SIMPLE("UM600", "$PDTINFO", "UM600", GNSS_MODEL_UC6580, 500); + PROBE_SIMPLE("ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H, 500); /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */ - clearBuffer(); - _serial_gps->write("$PCAS06,1*1A\r\n"); - if (getACK("$GPTXT,01,01,02,HW=ATGM332D", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("ATGM332D detected, using ATGM336H Module\n"); - return GNSS_MODEL_ATGM336H; - } + PROBE_SIMPLE("ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H, 500); /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ - clearBuffer(); - _serial_gps->write("PAIR020*38\r\n"); - if (getACK("$PAIR020,AG3335", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("Aioha AG3335 detected, using AG3335 Module\n"); - return GNSS_MODEL_AG3335; - } + PROBE_SIMPLE("AG3335", "PAIR020*38", "$PAIR020,AG3335", GNSS_MODEL_AG3335, 500); - // Get version information - clearBuffer(); - _serial_gps->write("$PCAS06,0*1B\r\n"); - if (getACK("$GPTXT,01,01,02,SW=", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("L76K GNSS init succeeded, using L76K GNSS Module\n"); - return GNSS_MODEL_MTK; - } + PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); // Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS) _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); delay(20); - // Get version information - clearBuffer(); - _serial_gps->write("$PMTK605*31\r\n"); - if (getACK("Quectel-L76B", 500) == GNSS_RESPONSE_OK) { - LOG_INFO("L76B GNSS init succeeded, using L76B GNSS Module\n"); - return GNSS_MODEL_MTK_L76B; - } + PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500); uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; UBXChecksum(cfg_rate, sizeof(cfg_rate)); @@ -1840,4 +1802,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file From bf343290331cf7b007d5a1111d78ac24d2105779 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 7 Sep 2024 18:21:59 -0500 Subject: [PATCH 1062/1377] Adds the data bitfield and ok_to_mqtt bit (#4643) * Don't filter PKI packets just for being encrypted. * Add ok_to_mqtt config and bit * Bitfield * Adjust dontmqttmebro logic. * Manipulate bitfield only in router.cpp * Want_ack is not want_response * Bitfield macros * Use new Bitfield macro in MQTT.cpp --------- Co-authored-by: Ben Meadors --- src/mesh/Channels.cpp | 4 ---- src/mesh/Channels.h | 10 +++++++++- src/mesh/NodeDB.cpp | 1 + src/mesh/Router.cpp | 8 ++++++++ src/mesh/Router.h | 5 +++++ src/mqtt/MQTT.cpp | 35 +++++++++++++++++++++++------------ 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index bba7571d2..47c013443 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -13,10 +13,6 @@ #include "mqtt/MQTT.h" #endif -/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) -static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, - 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}; - Channels channels; const char *Channels::adminChannel = "admin"; diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 4f87cb309..e5a750f71 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -129,4 +129,12 @@ class Channels }; /// Singleton channel table -extern Channels channels; \ No newline at end of file +extern Channels channels; + +/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) +static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, + 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}; + +static const uint8_t eventpsk[] = {0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, + 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74, + 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1}; \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6613ad3c3..06180310a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -285,6 +285,7 @@ void NodeDB::installDefaultConfig() config.lora.tx_enabled = true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) config.lora.override_duty_cycle = false; + config.lora.config_ok_to_mqtt = false; #ifdef CONFIG_LORA_REGION_USERPREFS config.lora.region = CONFIG_LORA_REGION_USERPREFS; #else diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index d8e578db1..b222872fa 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -378,6 +378,8 @@ bool perhapsDecode(meshtastic_MeshPacket *p) // parsing was successful p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded p->channel = chIndex; // change to store the index instead of the hash + if (p->decoded.has_bitfield) + p->decoded.want_response |= p->decoded.bitfield & BITFIELD_WANT_RESPONSE_MASK; /* Not actually ever used. // Decompress if needed. jm @@ -424,6 +426,12 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + if (p->from == nodeDB->getNodeNum()) { + p->decoded.has_bitfield = true; + p->decoded.bitfield |= (config.lora.config_ok_to_mqtt << BITFIELD_OK_TO_MQTT_SHIFT); + p->decoded.bitfield |= (p->decoded.want_response << BITFIELD_WANT_RESPONSE_SHIFT); + } + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); /* Not actually used, so save the cycles diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 98486745b..fd4b0ccf9 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -148,3 +148,8 @@ extern Router *router; /// Generate a unique packet id // FIXME, move this someplace better PacketId generatePacketId(); + +#define BITFIELD_WANT_RESPONSE_SHIFT 1 +#define BITFIELD_OK_TO_MQTT_SHIFT 0 +#define BITFIELD_WANT_RESPONSE_MASK (1 << BITFIELD_WANT_RESPONSE_SHIFT) +#define BITFIELD_OK_TO_MQTT_MASK (1 << BITFIELD_OK_TO_MQTT_SHIFT) \ No newline at end of file diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 797fc7dc3..d14c7a923 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -514,19 +514,29 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & return; // no channels have an uplink enabled auto &ch = channels.getByIndex(chIndex); - if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { - LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); - return; - } + if (!mp.pki_encrypted) { + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } - if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { - LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); - return; - } + // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. + if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && + !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && + (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || + (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { + LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); + return; + } - if (ch.settings.uplink_enabled || mp.pki_encrypted) { + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { + LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); + return; + } + } + if (mp.pki_encrypted || ch.settings.uplink_enabled) { const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); @@ -537,7 +547,8 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & if (moduleConfig.mqtt.encryption_enabled) { env->packet = (meshtastic_MeshPacket *)∓ LOG_DEBUG("encrypted message\n"); - } else { + } else if (mp_decoded.which_payload_variant == + meshtastic_MeshPacket_decoded_tag) { // Don't upload a still-encrypted PKI packet env->packet = (meshtastic_MeshPacket *)&mp_decoded; LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); } From e470619e3d86f6c1d8caacda43dd9d8951b5b7d4 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 9 Sep 2024 01:33:56 +0800 Subject: [PATCH 1063/1377] Remove undefined declaration (#4652) The getNMEA method was introduced to the header but never defined in code. As it's unused, remove it. --- src/gps/GPS.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index c0e9fb8b6..87607851c 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -297,7 +297,6 @@ class GPS : private concurrency::OSThread virtual int32_t runOnce() override; // Get GNSS model - String getNMEA(); GnssModel_t probe(int serialSpeed); // delay counter to allow more sats before fixed position stops GPS thread @@ -310,4 +309,4 @@ class GPS : private concurrency::OSThread }; extern GPS *gps; -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS From 6217e97c4168cf1e1b7304ba24a430b723ca9098 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 8 Sep 2024 09:09:01 +0800 Subject: [PATCH 1064/1377] Add support for AG3352 and fix AG3335 support AG33352 is a Mediatek/Airoha GPS/GLONASS/Galileo/BeiDou receiver. Patch adds relevant detection and setup code. Thanks to Bluebrolly and kongduino for providing the relevant information and testing. This patch also fixes support for the A3335, which is a related chip. The setup and detection code now works as tested on a real life T-1000E! Thanks to @gpsfan for the guidance. --- src/gps/GPS.cpp | 22 ++++++++++++---------- src/gps/GPS.h | 5 +++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index b5e1991ae..7e5207f5e 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -505,18 +505,18 @@ bool GPS::setup() delay(250); _serial_gps->write("$CFGMSG,6,1,0\r\n"); delay(250); - } else if (gnssModel == GNSS_MODEL_AG3335) { + } else if (gnssModel == GNSS_MODEL_AG3335 || gnssModel == GNSS_MODEL_AG3352) { _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC // Configure NMEA (sentences will output once per fix) - _serial_gps->write("$PAIR062,0,0*3F\r\n"); // GGA ON + _serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON _serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF - _serial_gps->write("$PAIR062,2,1*3D\r\n"); // GSA ON + _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF - _serial_gps->write("$PAIR062,4,0*3B\r\n"); // RMC ON + _serial_gps->write("$PAIR062,4,1*3B\r\n"); // RMC ON _serial_gps->write("$PAIR062,5,0*3B\r\n"); // VTG OFF - _serial_gps->write("$PAIR062,6,1*39\r\n"); // ZDA ON + _serial_gps->write("$PAIR062,6,0*38\r\n"); // ZDA ON delay(250); _serial_gps->write("$PAIR513*3D\r\n"); // save configuration @@ -1204,9 +1204,6 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->updateBaudRate(serialSpeed); } #endif -#ifdef GNSS_AIROHA - return GNSS_MODEL_AG3335; -#endif memset(&info, 0, sizeof(struct uBloxGnssModelInfo)); uint8_t buffer[768] = {0}; @@ -1225,7 +1222,12 @@ GnssModel_t GPS::probe(int serialSpeed) PROBE_SIMPLE("ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H, 500); /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ - PROBE_SIMPLE("AG3335", "PAIR020*38", "$PAIR020,AG3335", GNSS_MODEL_AG3335, 500); + _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume + _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume + _serial_gps->write("$PAIR513*3D\r\n"); // save configuration + PROBE_SIMPLE("AG3335", "$PAIR021*39", "$PAIR021,AG3335", GNSS_MODEL_AG3335, 500); + PROBE_SIMPLE("AG3352", "$PAIR021*39", "$PAIR021,AG3352", GNSS_MODEL_AG3352, 500); + PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500); PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); @@ -1802,4 +1804,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS diff --git a/src/gps/GPS.h b/src/gps/GPS.h index c0e9fb8b6..c2e660a49 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -30,7 +30,8 @@ typedef enum { GNSS_MODEL_UC6580, GNSS_MODEL_UNKNOWN, GNSS_MODEL_MTK_L76B, - GNSS_MODEL_AG3335 + GNSS_MODEL_AG3335, + GNSS_MODEL_AG3352 } GnssModel_t; typedef enum { @@ -310,4 +311,4 @@ class GPS : private concurrency::OSThread }; extern GPS *gps; -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS From ebe1b40bee4e7d4bcb332e91a5cb7f072ed2f662 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 9 Sep 2024 09:28:04 +0800 Subject: [PATCH 1065/1377] If GPS sleepTime is Zero, don't sleep. At the moment if the result of sleepTime calculations comes out to zero, we put the GPS into HARDSLEEP (losing all its status) and then immediately make it ACTIVE again. This patch avoids that toga. fixes https://github.com/meshtastic/firmware/issues/4657 --- src/gps/GPS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index b5e1991ae..46c76c4ae 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1019,7 +1019,7 @@ void GPS::down() LOG_DEBUG("%us until next search\n", sleepTime / 1000); // If update interval less than 10 seconds, no attempt to sleep - if (updateInterval <= 10 * 1000UL) + if (updateInterval <= 10 * 1000UL || sleepTime == 0) setPowerState(GPS_IDLE); else { From fabd6b0d6fbd6781d80207167a99683fe38d943b Mon Sep 17 00:00:00 2001 From: thebentern <9000580+thebentern@users.noreply.github.com> Date: Mon, 9 Sep 2024 02:54:25 +0000 Subject: [PATCH 1066/1377] [create-pull-request] automated change --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 95d3d2538..69c478482 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 0 +build = 1 From a5b79528b35a0e7212440574e75d86c97787bc25 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Mon, 9 Sep 2024 11:56:37 +0800 Subject: [PATCH 1067/1377] Add RAK4631 Ethernet Gateway with working JSON output to MQTT --- platformio.ini | 3 +- src/mqtt/MQTT.cpp | 12 +- src/serialization/MeshPacketSerializer.cpp | 4 +- .../MeshPacketSerializer_nRF52.cpp | 325 ++++++++++++++++++ variants/rak4631_mqtt_json/platformio.ini | 58 ++++ variants/rak4631_mqtt_json/variant.cpp | 45 +++ variants/rak4631_mqtt_json/variant.h | 273 +++++++++++++++ 7 files changed, 712 insertions(+), 8 deletions(-) create mode 100644 src/serialization/MeshPacketSerializer_nRF52.cpp create mode 100644 variants/rak4631_mqtt_json/platformio.ini create mode 100644 variants/rak4631_mqtt_json/variant.cpp create mode 100644 variants/rak4631_mqtt_json/variant.h diff --git a/platformio.ini b/platformio.ini index 87eb0afb7..a39de33c1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +;default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,6 +29,7 @@ default_envs = tbeam ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 +default_envs = rak4631_mqtt_json ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index d14c7a923..63bdd5bba 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -379,13 +379,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 +// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } -#endif // ARCH_NRF52 +// #endif // ARCH_NRF52 } } #if !MESHTASTIC_EXCLUDE_PKI @@ -480,7 +480,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); -#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 +// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); @@ -496,7 +496,7 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } -#endif // ARCH_NRF52 +// #endif // ARCH_NRF52 mqttPool.release(env); } } @@ -562,7 +562,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); -#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 +// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); @@ -573,7 +573,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topicJson.c_str(), jsonString.c_str(), false); } } -#endif // ARCH_NRF52 +// #endif // ARCH_NRF52 } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index e00dde024..227205d84 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -1,3 +1,4 @@ +#ifndef NRF52_USE_JSON #include "MeshPacketSerializer.h" #include "JSON.h" #include "NodeDB.h" @@ -353,4 +354,5 @@ std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPa delete value; return jsonStr; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp new file mode 100644 index 000000000..7302e0915 --- /dev/null +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -0,0 +1,325 @@ +#ifdef NRF52_USE_JSON +#warning 'Using nRF52 Serializer' + +#include "MeshPacketSerializer.h" +#include "ArduinoJson.h" +#include "NodeDB.h" +#include "mesh/generated/meshtastic/mqtt.pb.h" +#include "mesh/generated/meshtastic/telemetry.pb.h" +#include "modules/RoutingModule.h" +#include +#include +#include "mesh/generated/meshtastic/remote_hardware.pb.h" + +StaticJsonDocument<512> jsonObj; +// StaticJsonDocument<512> msgPayload; + +std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) +{ + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; + // JSONObject jsonObj; + jsonObj.clear(); + + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + // JSONObject msgPayload; + switch (mp->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload + StaticJsonDocument<512> text_doc; + DeserializationError error = deserializeJson(text_doc, payloadStr); + if (error) { + // if it isn't, then we need to create a json object + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); + jsonObj["payload"]["text"] = payloadStr; + // jsonObj["payload"] = msgPayload; + } else { + // if it is, then we can just use the json object + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); + jsonObj["payload"] = text_doc; + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; + jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; + jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; + jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; + jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; + } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; + jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; + jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; + jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; + jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; + jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; + jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; + jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; + jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; + jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; + jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; + jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; + jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; + } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; + jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; + jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; + jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; + jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; + jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; + jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; + jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; + jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = decoded->id; + jsonObj["payload"]["longname"] = decoded->long_name; + jsonObj["payload"]["shortname"] = decoded->short_name; + jsonObj["payload"]["hardware"] = decoded->hw_model; + jsonObj["payload"]["role"] = (int)decoded->role; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + } + break; + } + case meshtastic_PortNum_POSITION_APP: { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { + decoded = &scratch; + if ((int)decoded->time) { + jsonObj["payload"]["time"] = (unsigned int)decoded->time; + } + if ((int)decoded->timestamp) { + jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; + } + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + if ((int)decoded->altitude) { + jsonObj["payload"]["altitude"] = (int)decoded->altitude; + } + if ((int)decoded->ground_speed) { + jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; + } + if (int(decoded->ground_track)) { + jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; + } + if (int(decoded->sats_in_view)) { + jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; + } + if ((int)decoded->PDOP) { + jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; + } + if ((int)decoded->HDOP) { + jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; + } + if ((int)decoded->VDOP) { + jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; + } + if ((int)decoded->precision_bits) { + jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = (unsigned int)decoded->id; + jsonObj["payload"]["name"] = decoded->name; + jsonObj["payload"]["description"] = decoded->description; + jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; + jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) { + decoded = &scratch; + jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; + jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; + jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; + jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; + + JsonArray neighbors = jsonObj.createNestedArray("neighbors"); + JsonObject neighbors_0 = neighbors.createNestedObject(); + + for (uint8_t i = 0; i < decoded->neighbors_count; i++) { + neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; + neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors_0.clear(); + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + } + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: { + if (mp->decoded.request_id) { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) { + decoded = &scratch; + JsonArray route = jsonObj.createNestedArray("route"); + + route.add(mp->to); + for (uint8_t i = 0; i < decoded->route_count; i++) { + route.add(decoded->route[i]); + } + route.add(mp->from); // Ended at the original destination (source of response) + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + } + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + jsonObj["payload"]["text"] = payloadStr; + break; + } + case meshtastic_PortNum_REMOTE_HARDWARE_APP: { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { + msgType = "gpios_changed"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { + msgType = "gpios_read_reply"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + } + break; + } + // add more packet types here if needed + default: + break; + } + } else if (shouldLog) { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + } + + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["type"] = msgType.c_str(); + jsonObj["sender"] = owner.id; + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + + // serialize and write it to the stream + + Serial.printf("serialized json message: \r\n"); + serializeJson(jsonObj, Serial); + Serial.println(""); + + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); + + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + + return jsonStr; +} + +std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) +{ + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["time_ms"] = (double)millis(); + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["want_ack"] = mp->want_ack; + + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + jsonObj["size"] = (unsigned int)mp->encrypted.size; + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = encryptedStr.c_str(); + + // serialize and write it to the stream + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); + + return jsonStr; +} +#endif \ No newline at end of file diff --git a/variants/rak4631_mqtt_json/platformio.ini b/variants/rak4631_mqtt_json/platformio.ini new file mode 100644 index 000000000..5d459a91a --- /dev/null +++ b/variants/rak4631_mqtt_json/platformio.ini @@ -0,0 +1,58 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:rak4631_mqtt_json] +extends = nrf52840_base +board = wiscore_rak4631 +board_check = true +build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_mqtt_json -D RAK_4631 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DNRF52_USE_JSON=1 +; -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_WIFI=1 +; -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_PKI=1 + -DMESHTASTIC_EXCLUDE_POWER_FSM=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_mqtt_json> + + + +lib_deps = + ${nrf52840_base.lib_deps} + ${networking_base.lib_deps} + melopero/Melopero RV3028@^1.1.0 + https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 + rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 + bblanchon/ArduinoJson @ 6.21.4 +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds +;upload_protocol = jlink + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" +[env:rak4631_mqtt_json_dbg] +extends = env:rak4631 +board_level = extra + +; if the builtin version of openocd has a buggy version of semihosting, so use the external version +; platform_packages = platformio/tool-openocd@^3.1200.0 + +build_flags = + ${env:rak4631_mqtt_json.build_flags} + -D USE_SEMIHOSTING + +lib_deps = + ${env:rak4631_mqtt_json.lib_deps} + https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 + +; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. +; However the built in openocd version in platformio has buggy support for TCP to semihosting. +; +; So I'm now trying the external openocd - but the openocd scripts for nrf52.cfg assume you are using a DAP adapter not an STLINK adapter. +; In theory I could change those scripts. But for now I'm trying going back to a DAP adapter but with the external openocd. + +upload_protocol = stlink +; eventually use platformio/tool-pyocd@^2.3600.0 instad +;upload_protocol = custom +;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE \ No newline at end of file diff --git a/variants/rak4631_mqtt_json/variant.cpp b/variants/rak4631_mqtt_json/variant.cpp new file mode 100644 index 000000000..e84b60b3b --- /dev/null +++ b/variants/rak4631_mqtt_json/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/rak4631_mqtt_json/variant.h b/variants/rak4631_mqtt_json/variant.h new file mode 100644 index 000000000..bc5541336 --- /dev/null +++ b/variants/rak4631_mqtt_json/variant.h @@ -0,0 +1,273 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library 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 Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ + +#define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion +#define BUTTON_NEED_PULLUP +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ + +#define PIN_EINK_CS (0 + 26) +#define PIN_EINK_BUSY (0 + 4) +#define PIN_EINK_DC (0 + 17) +#define PIN_EINK_RES (-1) +#define PIN_EINK_SCLK (0 + 3) +#define PIN_EINK_MOSI (0 + 30) // also called SDI + +// #define USE_EINK + +// RAKRGB +#define HAS_NCP5623 + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ + +// RAK4630 LoRa module + +/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) + +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) + +Important for successful SX1262 initialization: + +* Setup DIO2 to control the antenna switch +* Setup DIO3 to control the TCXO power supply +* Setup the SX1262 to use it's DCDC regulator and not the LDO +* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the +control of the antenna switch + +SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG + +*/ + +#define DETECTION_SENSOR_EN 4 + +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +// #define SX126X_TXEN (39) +// #define SX126X_RXEN (37) +#define SX126X_POWER_EN (37) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +// #define PIN_GPS_RESET (34) +// #define PIN_GPS_EN PIN_3V3_EN +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press + +// RAK12002 RTC Module +#define RV3028_RTC (uint8_t)0b1010010 + +// RAK18001 Buzzer in Slot C +// #define PIN_BUZZER 21 // IO3 is PWM2 +// NEW: set this via protobuf instead! + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define HAS_ETHERNET 1 + +#define RAK_4631 1 + +#define PIN_ETHERNET_RESET 21 +#define PIN_ETHERNET_SS PIN_EINK_CS +#define ETH_SPI_PORT SPI1 +#define AQ_SET_PIN 10 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file From c6bffd7d7f27f84f44559b6d5b4df40e08eac70a Mon Sep 17 00:00:00 2001 From: Bernd Giesecke Date: Mon, 9 Sep 2024 12:39:14 +0800 Subject: [PATCH 1068/1377] Update platformio.ini Fix default build environment --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index a39de33c1..c10128609 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -;default_envs = tbeam +default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,7 +29,7 @@ ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -default_envs = rak4631_mqtt_json +;default_envs = rak4631_mqtt_json ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 @@ -161,4 +161,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee From d02ba45109beea43a2a49c878ac99d356b9e92ee Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Mon, 9 Sep 2024 12:40:56 +0800 Subject: [PATCH 1069/1377] Fix default build platform --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index a39de33c1..552e026aa 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -;default_envs = tbeam +default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,7 +29,7 @@ ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -default_envs = rak4631_mqtt_json +;default_envs = rak4631_mqtt_json ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 From dacb452d47859db93ca8d3e90732ac8cf2437b01 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:16:58 +1000 Subject: [PATCH 1070/1377] Bugfix (#4660) --- src/input/cardKbI2cImpl.cpp | 4 ++-- .../heltec_wireless_tracker/variant.cpp | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 1bff49475..8aaebcb45 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -9,7 +9,7 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {} void CardKbI2cImpl::init() { -#ifndef ARCH_PORTDUINO +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) if (cardkb_found.address == 0x00) { LOG_DEBUG("Rescanning for I2C keyboard\n"); uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; @@ -57,4 +57,4 @@ void CardKbI2cImpl::init() } #endif inputBroker->registerSource(this); -} +} \ No newline at end of file diff --git a/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp b/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp index 84264ef58..0a19a9c3b 100644 --- a/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp +++ b/src/platform/extra_variants/heltec_wireless_tracker/variant.cpp @@ -10,10 +10,14 @@ void lateInitVariant() { // LOG_DEBUG("Heltec tracker initVariant\n"); -#ifdef VEXT_ENABLE - GpioPin *hwEnable = new GpioHwPin(VEXT_ENABLE); - GpioVirtPin *virtGpsEnable = gps ? gps->enablePin : new GpioVirtPin(); +#ifndef MESHTASTIC_EXCLUDE_GPS + GpioVirtPin *virtGpsEnable = gps ? gps->enablePin : new GpioVirtPin(); +#else + GpioVirtPin *virtGpsEnable = new GpioVirtPin(); +#endif + +#ifndef MESHTASTIC_EXCLUDE_SCREEN // On this board we are actually using the backlightEnable signal to already be controlling a physical enable to the // display controller. But we'd _ALSO_ like to have that signal drive a virtual GPIO. So nest it as needed. GpioVirtPin *virtScreenEnable = new GpioVirtPin(); @@ -25,8 +29,11 @@ void lateInitVariant() // Assume screen is initially powered splitter->set(true); } +#endif +#if defined(VEXT_ENABLE) && (!defined(MESHTASTIC_EXCLUDE_GPS) || !defined(MESHTASTIC_EXCLUDE_SCREEN)) // If either the GPS or the screen is on, turn on the external power regulator + GpioPin *hwEnable = new GpioHwPin(VEXT_ENABLE); new GpioBinaryTransformer(virtGpsEnable, virtScreenEnable, hwEnable, GpioBinaryTransformer::Or); #endif } From e9d55de3cb7e5b028a33b82146394d948a1d73ce Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 9 Sep 2024 20:54:11 +0800 Subject: [PATCH 1071/1377] Fix out-of-bound array access in T1000X Sensor (#4663) if u8i == 135, then u8i++ runs, the loop exits since u8i == 136, then value for u8i is 136 after the for loop. then in the next line, ntc_res2[u8i] will read past the end of the array --- src/modules/Telemetry/Sensor/T1000xSensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp index 4079d8ae3..4772aeb9e 100644 --- a/src/modules/Telemetry/Sensor/T1000xSensor.cpp +++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp @@ -95,7 +95,7 @@ float T1000xSensor::getTemp() Vout = ntc_vot; Rt = (HEATER_NTC_RP * vcc_vot) / Vout - HEATER_NTC_RP; - for (u8i = 0; u8i < 136; u8i++) { + for (u8i = 0; u8i < 135; u8i++) { if (Rt >= ntc_res2[u8i]) { break; } From dc8cc122a62242536021a2fbbe86d84928a693b6 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 9 Sep 2024 22:20:21 +0800 Subject: [PATCH 1072/1377] Add explicit to JSONValue constructors (#4665) --- src/serialization/JSONValue.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/serialization/JSONValue.h b/src/serialization/JSONValue.h index 0380d324b..16d53e89f 100644 --- a/src/serialization/JSONValue.h +++ b/src/serialization/JSONValue.h @@ -40,15 +40,15 @@ class JSONValue public: JSONValue(/*NULL*/); - JSONValue(const char *m_char_value); - JSONValue(const std::string &m_string_value); - JSONValue(bool m_bool_value); - JSONValue(double m_number_value); - JSONValue(int m_integer_value); - JSONValue(unsigned int m_integer_value); - JSONValue(const JSONArray &m_array_value); - JSONValue(const JSONObject &m_object_value); - JSONValue(const JSONValue &m_source); + explicit JSONValue(const char *m_char_value); + explicit JSONValue(const std::string &m_string_value); + explicit JSONValue(bool m_bool_value); + explicit JSONValue(double m_number_value); + explicit JSONValue(int m_integer_value); + explicit JSONValue(unsigned int m_integer_value); + explicit JSONValue(const JSONArray &m_array_value); + explicit JSONValue(const JSONObject &m_object_value); + explicit JSONValue(const JSONValue &m_source); ~JSONValue(); bool IsNull() const; From 2f9dcee954ed9568aa7bc7fee163670598aba901 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Mon, 9 Sep 2024 19:13:00 +0200 Subject: [PATCH 1073/1377] Fix size calculation of route/SNR array --- src/modules/TraceRouteModule.cpp | 8 ++++---- src/modules/TraceRouteModule.h | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index dd3d0b4f9..23b4f1ccf 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -53,7 +53,7 @@ void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_Ro uint8_t hopsTaken = p.hop_start - p.hop_limit; int8_t diff = hopsTaken - *route_count; for (uint8_t i = 0; i < diff; i++) { - if (*route_count < sizeof(*route) / sizeof(route[0])) { + if (*route_count < ROUTE_SIZE) { route[*route_count] = NODENUM_BROADCAST; // This will represent an unknown hop *route_count += 1; } @@ -61,7 +61,7 @@ void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_Ro // Add unknown SNR values if necessary diff = *route_count - *snr_count; for (uint8_t i = 0; i < diff; i++) { - if (*snr_count < sizeof(*snr_list) / sizeof(snr_list[0])) { + if (*snr_count < ROUTE_SIZE) { snr_list[*snr_count] = INT8_MIN; // This will represent an unknown SNR *snr_count += 1; } @@ -89,7 +89,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa snr_list = updated->snr_back; } - if (*snr_count < sizeof(*snr_list) / sizeof(snr_list[0])) { + if (*snr_count < ROUTE_SIZE) { snr_list[*snr_count] = (int8_t)(snr * 4); // Convert SNR to 1 byte *snr_count += 1; } @@ -97,7 +97,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa return; // Length of route array can normally not be exceeded due to the max. hop_limit of 7 - if (*route_count < sizeof(*route) / sizeof(route[0])) { + if (*route_count < ROUTE_SIZE) { route[*route_count] = myNodeInfo.my_node_num; *route_count += 1; } else { diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index fe69300de..afe2b3871 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -1,6 +1,8 @@ #pragma once #include "ProtobufModule.h" +#define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0]) + /** * A module that traces the route to a certain destination node */ From 106dab23db826b52873bead8a88f7a1bab60c8fb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 9 Sep 2024 14:20:14 -0500 Subject: [PATCH 1074/1377] Revert "Changes by create-pull-request action" (#4671) --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 69c478482..95d3d2538 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 1 +build = 0 From 4ed12bf21d43dcb40368025e96dea414f8ab1f9b Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:22:32 +0200 Subject: [PATCH 1075/1377] Try fix repeatedly getting a new NodeNum (#4670) --- src/mesh/NodeDB.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 06180310a..5244fd10f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -121,6 +121,8 @@ NodeDB::NodeDB() owner.hw_model = HW_VENDOR; // Ensure user (nodeinfo) role is set to whatever we're configured to owner.role = config.device.role; + // Ensure macaddr is set to our macaddr as it will be copied in our info below + memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); // Include our owner in the node db under our nodenum meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); @@ -1194,4 +1196,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} +} \ No newline at end of file From 91887865818bda76ea62a12c00ee3096a5380981 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Tue, 10 Sep 2024 11:58:25 +0800 Subject: [PATCH 1076/1377] Fix #ifndef and rename the variant --- platformio.ini | 2 +- src/mqtt/MQTT.cpp | 12 +++++----- .../platformio.ini | 24 ++++++++++++------- .../variant.cpp | 0 .../variant.h | 0 5 files changed, 23 insertions(+), 15 deletions(-) rename variants/{rak4631_mqtt_json => rak4631_eth_gw}/platformio.ini (80%) rename variants/{rak4631_mqtt_json => rak4631_eth_gw}/variant.cpp (100%) rename variants/{rak4631_mqtt_json => rak4631_eth_gw}/variant.h (100%) diff --git a/platformio.ini b/platformio.ini index c10128609..f47b801ce 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,7 +29,7 @@ default_envs = tbeam ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -;default_envs = rak4631_mqtt_json +;default_envs = rak4631_eth_gw ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 63bdd5bba..33a22e5d4 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -379,13 +379,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } -// #endif // ARCH_NRF52 +#endif // ARCH_NRF52 NRF52_USE_JSON } } #if !MESHTASTIC_EXCLUDE_PKI @@ -480,7 +480,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); -// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); @@ -496,7 +496,7 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } -// #endif // ARCH_NRF52 +#endif // ARCH_NRF52 NRF52_USE_JSON mqttPool.release(env); } } @@ -562,7 +562,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); -// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); @@ -573,7 +573,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topicJson.c_str(), jsonString.c_str(), false); } } -// #endif // ARCH_NRF52 +#endif // ARCH_NRF52 NRF52_USE_JSON } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { diff --git a/variants/rak4631_mqtt_json/platformio.ini b/variants/rak4631_eth_gw/platformio.ini similarity index 80% rename from variants/rak4631_mqtt_json/platformio.ini rename to variants/rak4631_eth_gw/platformio.ini index 5d459a91a..bf8713293 100644 --- a/variants/rak4631_mqtt_json/platformio.ini +++ b/variants/rak4631_eth_gw/platformio.ini @@ -1,21 +1,29 @@ ; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 -[env:rak4631_mqtt_json] +[env:rak4631_eth_gw] extends = nrf52840_base board = wiscore_rak4631 board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_mqtt_json -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_eth_gw -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 -DNRF52_USE_JSON=1 -; -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_GPS=1 -DMESHTASTIC_EXCLUDE_WIFI=1 ; -DMESHTASTIC_EXCLUDE_SCREEN=1 - -DMESHTASTIC_EXCLUDE_PKI=1 +; -DMESHTASTIC_EXCLUDE_PKI=1 -DMESHTASTIC_EXCLUDE_POWER_FSM=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_mqtt_json> + + + + -DMESHTASTIC_EXCLUDE_POWERMON=1 + -DMESHTASTIC_EXCLUDE_TZ=1 + -DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 + -DMESHTASTIC_EXCLUDE_STOREFORWARD=1 + -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1 + -DMESHTASTIC_EXCLUDE_WAYPOINT=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_eth_gw> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} @@ -31,7 +39,7 @@ lib_deps = ; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) ; programming time is about the same as the bootloader version. ; For information on this see the meshtastic developers documentation for "Development on the NRF52" -[env:rak4631_mqtt_json_dbg] +[env:rak4631_eth_gw_dbg] extends = env:rak4631 board_level = extra @@ -39,11 +47,11 @@ board_level = extra ; platform_packages = platformio/tool-openocd@^3.1200.0 build_flags = - ${env:rak4631_mqtt_json.build_flags} + ${env:rak4631_eth_gw.build_flags} -D USE_SEMIHOSTING lib_deps = - ${env:rak4631_mqtt_json.lib_deps} + ${env:rak4631_eth_gw.lib_deps} https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 ; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. diff --git a/variants/rak4631_mqtt_json/variant.cpp b/variants/rak4631_eth_gw/variant.cpp similarity index 100% rename from variants/rak4631_mqtt_json/variant.cpp rename to variants/rak4631_eth_gw/variant.cpp diff --git a/variants/rak4631_mqtt_json/variant.h b/variants/rak4631_eth_gw/variant.h similarity index 100% rename from variants/rak4631_mqtt_json/variant.h rename to variants/rak4631_eth_gw/variant.h From 4fc3782ea3133f6bd097053687456d00725dd512 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Tue, 10 Sep 2024 18:43:47 +0800 Subject: [PATCH 1077/1377] Fix traceroute, neighborinfo and waypoint --- .../MeshPacketSerializer_nRF52.cpp | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 7302e0915..777c10ada 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -11,19 +11,18 @@ #include #include "mesh/generated/meshtastic/remote_hardware.pb.h" -StaticJsonDocument<512> jsonObj; -// StaticJsonDocument<512> msgPayload; +StaticJsonDocument<1024> jsonObj; +StaticJsonDocument<1024> arrayObj; std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { // the created jsonObj is immutable after creation, so // we need to do the heavy lifting before assembling it. std::string msgType; - // JSONObject jsonObj; jsonObj.clear(); + arrayObj.clear(); if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - // JSONObject msgPayload; switch (mp->decoded.portnum) { case meshtastic_PortNum_TEXT_MESSAGE_APP: { msgType = "text"; @@ -43,7 +42,6 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (shouldLog) LOG_INFO("text message payload is of type plaintext\n"); jsonObj["payload"]["text"] = payloadStr; - // jsonObj["payload"] = msgPayload; } else { // if it is, then we can just use the json object if (shouldLog) @@ -96,6 +94,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + return ""; } break; } @@ -113,6 +112,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"]["role"] = (int)decoded->role; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + return ""; } break; } @@ -157,6 +157,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; } break; } @@ -176,6 +177,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; } break; } @@ -192,16 +194,21 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; - JsonArray neighbors = jsonObj.createNestedArray("neighbors"); + JsonObject neighbors_obj = arrayObj.to(); + JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); JsonObject neighbors_0 = neighbors.createNestedObject(); for (uint8_t i = 0; i < decoded->neighbors_count; i++) { neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors[i+1] = neighbors_0; neighbors_0.clear(); } + neighbors.remove(0); + jsonObj["payload"]["neighbors"] = neighbors; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + return ""; } break; } @@ -214,17 +221,32 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, &scratch)) { decoded = &scratch; - JsonArray route = jsonObj.createNestedArray("route"); + JsonArray route = arrayObj.createNestedArray("route"); - route.add(mp->to); + auto addToRoute = [](JsonArray *route, NodeNum num) { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->add(long_name); + }; + + addToRoute(&route,mp->to); //route.add(mp->to); for (uint8_t i = 0; i < decoded->route_count; i++) { - route.add(decoded->route[i]); + addToRoute(&route,decoded->route[i]); //route.add(decoded->route[i]); } - route.add(mp->from); // Ended at the original destination (source of response) + addToRoute(&route,mp->from); //route.add(mp->from); // Ended at the original destination (source of response) + + jsonObj["payload"]["route"] = route; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + return ""; } - } + } else { + LOG_WARN("Traceroute response not reported"); + return ""; + } break; } case meshtastic_PortNum_DETECTION_SENSOR_APP: { @@ -252,15 +274,19 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + return ""; } break; } // add more packet types here if needed default: + LOG_WARN("Unsupported packet type %d\n",mp->decoded.portnum); + return ""; break; } } else if (shouldLog) { LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + return ""; } jsonObj["id"] = (unsigned int)mp->id; @@ -281,9 +307,9 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, // serialize and write it to the stream - Serial.printf("serialized json message: \r\n"); - serializeJson(jsonObj, Serial); - Serial.println(""); + // Serial.printf("serialized json message: \r\n"); + // serializeJson(jsonObj, Serial); + // Serial.println(""); std::string jsonStr = ""; serializeJson(jsonObj, jsonStr); @@ -296,6 +322,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) { + jsonObj.clear(); jsonObj["id"] = (unsigned int)mp->id; jsonObj["time_ms"] = (double)millis(); jsonObj["timestamp"] = (unsigned int)mp->rx_time; From 4e850296b6af3c3091680f301926f6a674277573 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 10 Sep 2024 13:24:57 -0500 Subject: [PATCH 1078/1377] Fix repeatedly getting new NodeNum and add more debug (#4674) * All the debug * Change `memccpy()` to `memcpy()` * Brint all the bytes of the MAC Address from the NodeDB * Check for blank MAC Address in ourown NodeDB entry * One more `memccpy()` * Clean-up debug log --------- Co-authored-by: GUVWAF --- src/mesh/NodeDB.cpp | 4 +++- src/mesh/TypeConversions.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5244fd10f..5183166cd 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -646,7 +646,9 @@ void NodeDB::pickNewNodeNum() while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) || ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0)) { NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice - LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate); + LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so " + "trying for 0x%x\n", + nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate); nodeNum = candidate; } LOG_DEBUG("Using nodenum 0x%x \n", nodeNum); diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 513728ca5..6a90ac703 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -78,7 +78,7 @@ meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user) lite.hw_model = user.hw_model; lite.role = user.role; lite.is_licensed = user.is_licensed; - memccpy(lite.macaddr, user.macaddr, sizeof(user.macaddr), sizeof(lite.macaddr)); + memcpy(lite.macaddr, user.macaddr, sizeof(lite.macaddr)); memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes)); lite.public_key.size = user.public_key.size; return lite; @@ -94,7 +94,7 @@ meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_User user.hw_model = lite.hw_model; user.role = lite.role; user.is_licensed = lite.is_licensed; - memccpy(user.macaddr, lite.macaddr, sizeof(lite.macaddr), sizeof(user.macaddr)); + memcpy(user.macaddr, lite.macaddr, sizeof(user.macaddr)); memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes)); user.public_key.size = lite.public_key.size; From 013021941e6209ae52e4c7571fabc46e58f1c493 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 10 Sep 2024 15:30:40 -0500 Subject: [PATCH 1079/1377] Remove scaling of smart position broadcast minimum interval specifically (#4677) * Remove scaling of smart position broacast minimum interval specifically * Trunk --- src/modules/PositionModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index c4ef66501..41b86b795 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -63,7 +63,7 @@ class PositionModule : public ProtobufModule, private concu bool hasQualityTimesource(); const uint32_t minimumTimeThreshold = - Default::getConfiguredOrDefaultMsScaled(config.position.broadcast_smart_minimum_interval_secs, 30, numOnlineNodes); + Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); }; struct SmartPosition { From 6724f1f7ea3e9e9d87d4592c033ff8950cbfb6fe Mon Sep 17 00:00:00 2001 From: zerolint <179066619+zerolint@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:51:28 -0400 Subject: [PATCH 1080/1377] Print Unix epoch on time_t 64bit platforms (#4673) Fixes (#4600) by using unsigned 32bit for epoch. Co-authored-by: Ben Meadors --- src/gps/RTC.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 070038672..c056bb9e4 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -43,7 +43,10 @@ void readFromRTC() t.tm_sec = rtc.getSecond(); tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - LOG_DEBUG("Read RTC time from RV3028 as %ld\n", tv.tv_sec); + + uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms + LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)\n", t.tm_year + 1900, t.tm_mon + 1, + t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); timeStartMsec = now; zeroOffsetSecs = tv.tv_sec; if (currentQuality == RTCQualityNone) { @@ -71,7 +74,10 @@ void readFromRTC() t.tm_sec = tc.second; tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - LOG_DEBUG("Read RTC time from PCF8563 as %ld\n", tv.tv_sec); + + uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms + LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)\n", t.tm_year + 1900, + t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); timeStartMsec = now; zeroOffsetSecs = tv.tv_sec; if (currentQuality == RTCQualityNone) { @@ -81,7 +87,8 @@ void readFromRTC() #else if (!gettimeofday(&tv, NULL)) { uint32_t now = millis(); - LOG_DEBUG("Read RTC time as %ld\n", tv.tv_sec); + uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms + LOG_DEBUG("Read RTC time as %ld\n", printableEpoch); timeStartMsec = now; zeroOffsetSecs = tv.tv_sec; } @@ -101,6 +108,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) { static uint32_t lastSetMsec = 0; uint32_t now = millis(); + uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms bool shouldSet; if (forceUpdate) { @@ -113,7 +121,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) } else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { // Every 12 hrs we will slam in a new GPS or Phone GPS / NTP time, to correct for local RTC clock drift shouldSet = true; - LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", tv->tv_sec); + LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", printableEpoch); } else { shouldSet = false; LOG_DEBUG("Current RTC quality: %s. Ignoring time of RTC quality of %s\n", RtcName(currentQuality), RtcName(q)); @@ -140,8 +148,8 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) #endif tm *t = gmtime(&tv->tv_sec); rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); - LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec); + LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d (%ld)\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec, printableEpoch); } #elif defined(PCF8563_RTC) if (rtc_found.address == PCF8563_RTC) { @@ -154,8 +162,8 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) #endif tm *t = gmtime(&tv->tv_sec); rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); - LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec); + LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)\n", t->tm_year + 1900, t->tm_mon + 1, + t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch); } #elif defined(ARCH_ESP32) settimeofday(tv, NULL); @@ -272,4 +280,4 @@ time_t gm_mktime(struct tm *tm) #else return mktime(tm); #endif -} \ No newline at end of file +} From e8e9826adc3905d1e3b50b2a851de88ba21f9276 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 10 Sep 2024 19:27:59 -0500 Subject: [PATCH 1081/1377] Temp: Grab pre-release tag --- .github/workflows/build_esp32.yml | 1 + .github/workflows/build_esp32_c3.yml | 1 + .github/workflows/build_esp32_s3.yml | 1 + .github/workflows/package_amd64.yml | 1 + .github/workflows/package_raspbian.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 4cbb4c7a4..db9f049a7 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -20,6 +20,7 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web + version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index 07727d711..fc836e170 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -22,6 +22,7 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web + version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 10773833e..dc89f3ae3 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -20,6 +20,7 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web + version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml index ae7bf3242..5ae3349ba 100644 --- a/.github/workflows/package_amd64.yml +++ b/.github/workflows/package_amd64.yml @@ -27,6 +27,7 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web + version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 5471332c5..0720e3433 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -27,6 +27,7 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web + version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} From 1ba4f6e2223d6e6f1595445add774f2e71d8b19e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 10 Sep 2024 20:07:06 -0500 Subject: [PATCH 1082/1377] Revert "Temp: Grab pre-release tag" This reverts commit e8e9826adc3905d1e3b50b2a851de88ba21f9276. --- .github/workflows/build_esp32.yml | 1 - .github/workflows/build_esp32_c3.yml | 1 - .github/workflows/build_esp32_s3.yml | 1 - .github/workflows/package_amd64.yml | 1 - .github/workflows/package_raspbian.yml | 1 - 5 files changed, 5 deletions(-) diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index db9f049a7..4cbb4c7a4 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -20,7 +20,6 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web - version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index fc836e170..07727d711 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -22,7 +22,6 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web - version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index dc89f3ae3..10773833e 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -20,7 +20,6 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web - version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml index 5ae3349ba..ae7bf3242 100644 --- a/.github/workflows/package_amd64.yml +++ b/.github/workflows/package_amd64.yml @@ -27,7 +27,6 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web - version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 0720e3433..5471332c5 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -27,7 +27,6 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web - version: "tags/pre-release" file: build.tar target: build.tar token: ${{ secrets.GITHUB_TOKEN }} From 9ac0e26d426132162614be9ef74fedf46e305410 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 11 Sep 2024 08:42:26 -0500 Subject: [PATCH 1083/1377] Add option to preserve private key for factory reset (config) (#4679) * Add option to preserve private key for factory reset (config) * Typo fix * Copy the key in the right direction, and set the size. * Don't set the key size back to 0 right after setting it to 32. * Set the key size before using it to do a memcpy. * Use the right key_size for backing up private_key * Don't factoryReset() for a missing nodeDB * Disable Bluetooth in AdminModule when resetting device settings or nodeDB to avoid race * Add checks for valid objects before deinit bluetooth * Add disableBluetooth to handleSetConfig, handleSetModuleConfig, and commit settings --------- Co-authored-by: Jonathan Bennett --- src/mesh/NodeDB.cpp | 23 +++++++++++++++++------ src/mesh/NodeDB.h | 3 ++- src/modules/AdminModule.cpp | 23 ++++++++++++++++++++++- src/modules/AdminModule.h | 4 +++- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5183166cd..5d1db88ae 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -245,7 +245,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds) #endif // second, install default state (this will deal with the duplicate mac address issue) installDefaultDeviceState(); - installDefaultConfig(); + installDefaultConfig(!eraseBleBonds); // Also preserve the private key if we're not erasing BLE bonds installDefaultModuleConfig(); installDefaultChannels(); // third, write everything to disk @@ -268,8 +268,13 @@ bool NodeDB::factoryReset(bool eraseBleBonds) return true; } -void NodeDB::installDefaultConfig() +void NodeDB::installDefaultConfig(bool preserveKey = false) { + uint8_t private_key_temp[32]; + bool shouldPreserveKey = preserveKey && config.has_security && config.security.private_key.size > 0; + if (shouldPreserveKey) { + memcpy(private_key_temp, config.security.private_key.bytes, config.security.private_key.size); + } LOG_INFO("Installing default LocalConfig\n"); memset(&config, 0, sizeof(meshtastic_LocalConfig)); config.version = DEVICESTATE_CUR_VER; @@ -310,8 +315,14 @@ void NodeDB::installDefaultConfig() #else config.security.admin_key[0].size = 0; #endif + if (shouldPreserveKey) { + config.security.private_key.size = 32; + memcpy(config.security.private_key.bytes, private_key_temp, config.security.private_key.size); + printBytes("Restored key", config.security.private_key.bytes, config.security.private_key.size); + } else { + config.security.private_key.size = 0; + } config.security.public_key.size = 0; - config.security.private_key.size = 0; #ifdef PIN_GPS_EN config.position.gps_en_gpio = PIN_GPS_EN; #endif @@ -714,7 +725,7 @@ void NodeDB::loadFromDisk() //} else { if (devicestate.version < DEVICESTATE_MIN_VER) { LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version); - factoryReset(); + installDefaultDeviceState(); } else { LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version, devicestate.node_db_lite.size()); @@ -730,7 +741,7 @@ void NodeDB::loadFromDisk() } else { if (config.version < DEVICESTATE_MIN_VER) { LOG_WARN("config %d is old, discarding\n", config.version); - installDefaultConfig(); + installDefaultConfig(true); } else { LOG_INFO("Loaded saved config version %d\n", config.version); } @@ -1043,7 +1054,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde if (p.public_key.size > 0) { printBytes("Incoming Pubkey: ", p.public_key.bytes, 32); if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one - LOG_INFO("Public Key set for node, not updateing!\n"); + LOG_INFO("Public Key set for node, not updating!\n"); // we copy the key into the incoming packet, to prevent overwrite memcpy(p.public_key.bytes, info->user.public_key.bytes, 32); } else { diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index a71f3e134..c94a7653c 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -182,7 +182,8 @@ class NodeDB void cleanupMeshDB(); /// Reinit device state from scratch (not loading from disk) - void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig(); + void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(bool preserveKey), + installDefaultModuleConfig(); /// write to flash /// @return true if the save was successful diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index d88f17a86..4deb99eb7 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -186,18 +186,22 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_factory_reset_config_tag: { + disableBluetooth(); LOG_INFO("Initiating factory config reset\n"); nodeDB->factoryReset(); + LOG_INFO("Factory config reset finished, rebooting soon.\n"); reboot(DEFAULT_REBOOT_SECONDS); break; } case meshtastic_AdminMessage_factory_reset_device_tag: { + disableBluetooth(); LOG_INFO("Initiating full factory reset\n"); nodeDB->factoryReset(true); reboot(DEFAULT_REBOOT_SECONDS); break; } case meshtastic_AdminMessage_nodedb_reset_tag: { + disableBluetooth(); LOG_INFO("Initiating node-db reset\n"); nodeDB->resetNodes(); reboot(DEFAULT_REBOOT_SECONDS); @@ -209,6 +213,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_commit_edit_settings_tag: { + disableBluetooth(); LOG_INFO("Committing transaction for edited settings\n"); hasOpenEditTransaction = false; saveChanges(SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); @@ -559,12 +564,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) break; } + if (requiresReboot) { + disableBluetooth(); + } saveChanges(changes, requiresReboot); } void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { + disableBluetooth(); switch (c.which_payload_variant) { case meshtastic_ModuleConfig_mqtt_tag: LOG_INFO("Setting module config: MQTT\n"); @@ -636,7 +645,6 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.paxcounter = c.payload_variant.paxcounter; break; } - saveChanges(SEGMENT_MODULECONFIG); } @@ -1031,3 +1039,16 @@ bool AdminModule::messageIsRequest(meshtastic_AdminMessage *r) else return false; } + +void disableBluetooth() +{ +#if HAS_BLUETOOTH +#ifdef ARCH_ESP32 + if (nimbleBluetooth) + nimbleBluetooth->deinit(); +#elif defined(ARCH_NRF52) + if (nrf52Bluetooth) + nrf52Bluetooth->shutdown(); +#endif +#endif +} \ No newline at end of file diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 61c54d1b1..328e1c824 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -59,4 +59,6 @@ class AdminModule : public ProtobufModule, public Obser bool messageIsRequest(meshtastic_AdminMessage *r); }; -extern AdminModule *adminModule; \ No newline at end of file +extern AdminModule *adminModule; + +void disableBluetooth(); \ No newline at end of file From ba9a3cd7195ab93e899314f6a0e4de8f854f3c88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:51:52 -0500 Subject: [PATCH 1084/1377] [create-pull-request] automated change (#4685) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 95d3d2538..69c478482 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 0 +build = 1 From f37df4d6bf0f82df7e19840422f7fdaeeafe0a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:53:17 +0200 Subject: [PATCH 1085/1377] Radiomaster Bandit Accelerometer support (#4667) * Added STK8xxxx Accelerometer chip Added detection of STK8BA53 to I2C scanner. Change the way and order MCP9808, lLISH3DH and STK8BA53 is detected since they all shares the same I2C address. * Accelerometer support Radiomaster Bandit. Enables tap to wake screen if enabled in config, * Trunk Trunk --- platformio.ini | 3 +- src/AccelerometerThread.h | 22 ++++++++++++++ src/configuration.h | 1 + src/detect/ScanI2C.cpp | 4 +-- src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 37 +++++++++++++++++------ variants/radiomaster_900_bandit/variant.h | 7 ++++- 7 files changed, 62 insertions(+), 15 deletions(-) diff --git a/platformio.ini b/platformio.ini index 87eb0afb7..7613a2f43 100644 --- a/platformio.ini +++ b/platformio.ini @@ -160,4 +160,5 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index c2910007e..629d63c6a 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -11,6 +11,9 @@ #include #include #include +#ifdef STK8XXX_INT +#include +#endif #include #include #include @@ -24,6 +27,8 @@ #define ACCELEROMETER_CHECK_INTERVAL_MS 100 #define ACCELEROMETER_CLICK_THRESHOLD 40 +volatile static bool STK_IRQ; + static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) { Wire.beginTransmission(address); @@ -79,6 +84,11 @@ class AccelerometerThread : public concurrency::OSThread if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) { wakeScreen(); + } else if (acceleremoter_type == ScanI2C::DeviceType::STK8BAXX && STK_IRQ) { + STK_IRQ = false; + if (config.display.wake_on_tap_or_motion) { + wakeScreen(); + } } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) { uint8_t click = lis.getClick(); if (!config.device.double_tap_as_button_press) { @@ -188,6 +198,15 @@ class AccelerometerThread : public concurrency::OSThread mpu.setMotionDetectionDuration(20); mpu.setInterruptPinLatch(true); // Keep it latched. Will turn off when reinitialized. mpu.setInterruptPinPolarity(true); +#ifdef STK8XXX_INT + } else if (acceleremoter_type == ScanI2C::DeviceType::STK8BAXX && stk8baxx.STK8xxx_Initialization(STK8xxx_VAL_RANGE_2G)) { + STK_IRQ = false; + LOG_DEBUG("STX8BAxx initialized\n"); + stk8baxx.STK8xxx_Anymotion_init(); + pinMode(STK8XXX_INT, INPUT_PULLUP); + attachInterrupt( + digitalPinToInterrupt(STK8XXX_INT), [] { STK_IRQ = true; }, RISING); +#endif } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.begin(accelerometer_found.address)) { LOG_DEBUG("LIS3DH initializing\n"); lis.setRange(LIS3DH_RANGE_2_G); @@ -262,6 +281,9 @@ class AccelerometerThread : public concurrency::OSThread ScanI2C::DeviceType acceleremoter_type; Adafruit_MPU6050 mpu; Adafruit_LIS3DH lis; +#ifdef STK8XXX_INT + STK8xxx stk8baxx; +#endif Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; bool BMA_IRQ = false; diff --git a/src/configuration.h b/src/configuration.h index 047dbd727..4ab33ef2b 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -144,6 +144,7 @@ along with this program. If not, see . // ACCELEROMETER // ----------------------------------------------------------------------------- #define MPU6050_ADDR 0x68 +#define STK8BXX_ADR 0x18 #define LIS3DH_ADR 0x18 #define BMA423_ADDR 0x19 #define LSM6DS3_ADDR 0x6A diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 73bdf973b..eaba62a78 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const { - ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160}; - return firstOfOrNONE(5, types); + ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX}; + return firstOfOrNONE(6, types); } ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 0a5b360de..638e8cd23 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -52,7 +52,8 @@ class ScanI2C AHT10, BMX160, DFROBOT_LARK, - NAU7802 + NAU7802, + STK8BAXX } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 21e7ca8ac..ad5d9fe4c 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -313,17 +313,34 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; case MCP9808_ADDR: - registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2); - if (registerValue == 0x0400) { - type = MCP9808; - LOG_INFO("MCP9808 sensor found\n"); - } else { - type = LIS3DH; - LOG_INFO("LIS3DH accelerometer found\n"); + // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some + // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips. + { + // Check register 0x00 for 0x8700 response to ID STK8BA53 chip. + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 2); + if (registerValue == 0x8700) { + type = STK8BAXX; + LOG_INFO("STK8BAXX accelerometer found\n"); + break; + } + + // Check register 0x07 for 0x0400 response to ID MCP9808 chip. + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2); + if (registerValue == 0x0400) { + type = MCP9808; + LOG_INFO("MCP9808 sensor found\n"); + break; + } + + // Check register 0x0F for 0x3300 response to ID LIS3DH chip. + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2); + if (registerValue == 0x3300) { + type = LIS3DH; + LOG_INFO("LIS3DH accelerometer found\n"); + } + + break; } - - break; - case SHT31_4x_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) { diff --git a/variants/radiomaster_900_bandit/variant.h b/variants/radiomaster_900_bandit/variant.h index 0499970f5..0c7417cac 100644 --- a/variants/radiomaster_900_bandit/variant.h +++ b/variants/radiomaster_900_bandit/variant.h @@ -11,12 +11,17 @@ /* I2C SDA and SCL. - 0x18 - STK8XXX Accelerometer, Not supported yet. + 0x18 - STK8XXX Accelerometer 0x3C - SH1115 Display Driver */ #define I2C_SDA 14 #define I2C_SCL 12 +/* + I2C STK8XXX Accelerometer Interrupt PIN to ESP32 Pin 6 - SENSOR_CAPP (GPIO37) +*/ +#define STK8XXX_INT 37 + /* No GPS - but free pins are available. */ From 371c3e05bf9bff7f0b4fd07c4eb39a5eb6401a53 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Thu, 12 Sep 2024 08:30:29 +0800 Subject: [PATCH 1086/1377] Beautify GPS_DEBUG getACK logging code (#4672) This getACK is used to look for ASCII responses, so print ASCII when GPS_DEBUG is enabled. This markedly assisted with recent AG3335 debugging. It works great with other chips too (tested eg ATGM336H). Even UBLOX prints understandable "GPTXT,01,01,01,PDTI inv format*35." responses. Credit to bluebrolly. on discord. --- src/gps/GPS.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 3ce0abe75..046f277ff 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -166,18 +166,21 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis) b = _serial_gps->read(); #ifdef GPS_DEBUG - LOG_DEBUG("%02X", (char *)buffer); + LOG_DEBUG("%c", (b >= 32 && b <= 126) ? b : '.'); #endif buffer[bytesRead] = b; bytesRead++; if ((bytesRead == 767) || (b == '\r')) { if (strnstr((char *)buffer, message, bytesRead) != nullptr) { #ifdef GPS_DEBUG - LOG_DEBUG("\r"); + LOG_DEBUG("\r\nFound: %s\r\n", message); // Log the found message #endif return GNSS_RESPONSE_OK; } else { bytesRead = 0; +#ifdef GPS_DEBUG + LOG_DEBUG("\r\n"); +#endif } } } @@ -1804,4 +1807,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file From 910b6b7512c432e2ab7a59bb9acf4627dd824028 Mon Sep 17 00:00:00 2001 From: panaceya Date: Thu, 12 Sep 2024 03:31:30 +0300 Subject: [PATCH 1087/1377] OLED_ can be configured via userPrefs.h (#4624) --- src/graphics/Screen.cpp | 2 +- variants/diy/platformio.ini | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 68fdba207..da573bade 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -19,8 +19,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "Screen.h" #include "../userPrefs.h" +#include "Screen.h" #include "PowerMon.h" #include "configuration.h" #if HAS_SCREEN diff --git a/variants/diy/platformio.ini b/variants/diy/platformio.ini index adc10de44..2a55f7a79 100644 --- a/variants/diy/platformio.ini +++ b/variants/diy/platformio.ini @@ -7,7 +7,6 @@ build_flags = ${esp32_base.build_flags} -D DIY_V1 -D EBYTE_E22 - -D OLED_RU -I variants/diy/v1 ; Meshtastic DIY v1.1 new schematic based on ESP32-WROOM-32 & SX1262/SX1268 modules @@ -19,7 +18,6 @@ build_flags = ${esp32_base.build_flags} -D DIY_V1 -D EBYTE_E22 - -D OLED_RU -I variants/diy/v1_1 ; Port to Disaster Radio's ESP32-v3 Dev Board @@ -52,7 +50,6 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -I variants/diy/nrf52_promicro_diy_xtal -D NRF52_PROMICRO_DIY - -D OLED_RU -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_xtal> lib_deps = @@ -68,7 +65,6 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -I variants/diy/nrf52_promicro_diy_tcxo -D NRF52_PROMICRO_DIY - -D OLED_RU -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_tcxo> lib_deps = From 35cdc81d452dddf96edecb80609957756165d16f Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 09:53:13 +0800 Subject: [PATCH 1088/1377] Disable SCREEN and enable TZ --- src/AccelerometerThread.h | 16 +++++++++------- src/platform/nrf52/NRF52Bluetooth.cpp | 10 ++++++---- variants/rak4631_eth_gw/platformio.ini | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index c2910007e..c39504c08 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -94,8 +94,9 @@ class AccelerometerThread : public concurrency::OSThread wakeScreen(); return 500; } -#ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { +#if defined(RAK_4631) +#if !defined (MESHTASTIC_EXCLUDE_SCREEN) + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; @@ -165,7 +166,7 @@ class AccelerometerThread : public concurrency::OSThread } screen->setHeading(heading); - +#endif #endif } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { wakeScreen(); @@ -230,9 +231,10 @@ class AccelerometerThread : public concurrency::OSThread // It corresponds to isDoubleClick interrupt bmaSensor.enableWakeupIRQ(); #ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate - +#endif #endif } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) { LOG_DEBUG("LSM6DS3 initializing\n"); @@ -265,8 +267,8 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; bool BMA_IRQ = false; -#ifdef RAK_4631 - bool showingScreen = false; +#if defined(RAK_4631) && !defined(MESHTASTIC_EXCLUDE_SCREEN) + bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 1405ea4f3..177255b2f 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -310,7 +310,9 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { +#if !defined(MESHTASTIC_EXCLUDE_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; @@ -333,9 +335,9 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke 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); - }); - if (match_request) { + 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)) diff --git a/variants/rak4631_eth_gw/platformio.ini b/variants/rak4631_eth_gw/platformio.ini index bf8713293..62b7e737d 100644 --- a/variants/rak4631_eth_gw/platformio.ini +++ b/variants/rak4631_eth_gw/platformio.ini @@ -12,11 +12,11 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_eth_gw -D RAK_4631 -DNRF52_USE_JSON=1 -DMESHTASTIC_EXCLUDE_GPS=1 -DMESHTASTIC_EXCLUDE_WIFI=1 -; -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_SCREEN=1 ; -DMESHTASTIC_EXCLUDE_PKI=1 -DMESHTASTIC_EXCLUDE_POWER_FSM=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 - -DMESHTASTIC_EXCLUDE_TZ=1 +; -DMESHTASTIC_EXCLUDE_TZ=1 -DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 From a388e78842c308427635281024c32016d4e2ccb5 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 10:00:46 +0800 Subject: [PATCH 1089/1377] Fix platformio.ini conflict --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index f47b801ce..1847bd113 100644 --- a/platformio.ini +++ b/platformio.ini @@ -162,3 +162,4 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file From ca8d2204ba4ed0cf147bf64fe4dcc4d0e4846cd4 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 11:06:13 +0800 Subject: [PATCH 1090/1377] Fix formatting --- src/AccelerometerThread.h | 6 +- src/mqtt/MQTT.cpp | 1103 +++++++++-------- src/platform/nrf52/NRF52Bluetooth.cpp | 452 +++---- .../MeshPacketSerializer_nRF52.cpp | 647 +++++----- 4 files changed, 1194 insertions(+), 1014 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index c39504c08..37e7aab0d 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -96,7 +96,7 @@ class AccelerometerThread : public concurrency::OSThread } #if defined(RAK_4631) #if !defined (MESHTASTIC_EXCLUDE_SCREEN) - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; @@ -232,7 +232,7 @@ class AccelerometerThread : public concurrency::OSThread bmaSensor.enableWakeupIRQ(); #ifdef RAK_4631 #if !defined(MESHTASTIC_EXCLUDE_SCREEN) - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate #endif #endif @@ -268,7 +268,7 @@ class AccelerometerThread : public concurrency::OSThread SensorBMA423 bmaSensor; bool BMA_IRQ = false; #if defined(RAK_4631) && !defined(MESHTASTIC_EXCLUDE_SCREEN) - bool showingScreen = false; + bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 33a22e5d4..3e0e692b4 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -33,161 +33,194 @@ Allocator &mqttPool = staticMqttPool; void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) { - mqtt->onReceive(topic, payload, length); + mqtt->onReceive(topic, payload, length); } void MQTT::onClientProxyReceive(meshtastic_MqttClientProxyMessage msg) { - onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); + onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); } void MQTT::onReceive(char *topic, byte *payload, size_t length) { - meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; + meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; - if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) { - // check if this is a json payload message by comparing the topic start - char payloadStr[length + 1]; - memcpy(payloadStr, payload, length); - payloadStr[length] = 0; // null terminated string - JSONValue *json_value = JSON::Parse(payloadStr); - if (json_value != NULL) { - // check if it is a valid envelope - JSONObject json; - json = json_value->AsObject(); + if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) + { + // check if this is a json payload message by comparing the topic start + char payloadStr[length + 1]; + memcpy(payloadStr, payload, length); + payloadStr[length] = 0; // null terminated string + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) + { + // check if it is a valid envelope + JSONObject json; + json = json_value->AsObject(); - // parse the channel name from the topic string - // the topic has been checked above for having jsonTopic prefix, so just move past it - char *ptr = topic + jsonTopic.length(); - ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character - meshtastic_Channel sendChannel = channels.getByName(ptr); - // We allow downlink JSON packets only on a channel named "mqtt" - if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && - sendChannel.settings.downlink_enabled) { - if (isValidJsonEnvelope(json)) { - // this is a valid envelope - if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) { - std::string jsonPayloadStr = json["payload"]->AsString(); - LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); + // parse the channel name from the topic string + // the topic has been checked above for having jsonTopic prefix, so just move past it + char *ptr = topic + jsonTopic.length(); + ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character + meshtastic_Channel sendChannel = channels.getByName(ptr); + // We allow downlink JSON packets only on a channel named "mqtt" + if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && + sendChannel.settings.downlink_enabled) + { + if (isValidJsonEnvelope(json)) + { + // this is a valid envelope + if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) + { + std::string jsonPayloadStr = json["payload"]->AsString(); + LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); - // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { - memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); - p->decoded.payload.size = jsonPayloadStr.length(); - service->sendToMesh(p, RX_SRC_LOCAL); - } else { - LOG_WARN("Received MQTT json payload too long, dropping\n"); - } - } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) { - // invent the "sendposition" type for a valid envelope - JSONObject posit; - posit = json["payload"]->AsObject(); // get nested JSON Position - meshtastic_Position pos = meshtastic_Position_init_default; - if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) - pos.latitude_i = posit["latitude_i"]->AsNumber(); - if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) - pos.longitude_i = posit["longitude_i"]->AsNumber(); - if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) - pos.altitude = posit["altitude"]->AsNumber(); - if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) - pos.time = posit["time"]->AsNumber(); + // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) + { + memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); + p->decoded.payload.size = jsonPayloadStr.length(); + service->sendToMesh(p, RX_SRC_LOCAL); + } + else + { + LOG_WARN("Received MQTT json payload too long, dropping\n"); + } + } + else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) + { + // invent the "sendposition" type for a valid envelope + JSONObject posit; + posit = json["payload"]->AsObject(); // get nested JSON Position + meshtastic_Position pos = meshtastic_Position_init_default; + if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) + pos.latitude_i = posit["latitude_i"]->AsNumber(); + if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) + pos.longitude_i = posit["longitude_i"]->AsNumber(); + if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) + pos.altitude = posit["altitude"]->AsNumber(); + if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) + pos.time = posit["time"]->AsNumber(); - // construct protobuf data packet using POSITION, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_POSITION_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), - &meshtastic_Position_msg, &pos); // make the Data protobuf from position - service->sendToMesh(p, RX_SRC_LOCAL); - } else { - LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); - } - } else { - LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); - } - } else { - LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); - } - } else { - // no json, this is an invalid payload - LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); - } - delete json_value; - } else { - if (length == 0) { - LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); - return; - } else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } else { - if (e.channel_id == NULL || e.gateway_id == NULL) { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } - meshtastic_Channel ch = channels.getByName(e.channel_id); - if (strcmp(e.gateway_id, owner.id) == 0) { - // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. - // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node - // receives it when we get our own packet back. Then we'll stop our retransmissions. - if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) - routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); - else - LOG_INFO("Ignoring downlink message we originally sent.\n"); - } else { - // Find channel by channel_id and check downlink_enabled - if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || - (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) { - LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); - meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); - p->via_mqtt = true; // Mark that the packet was received via MQTT + // construct protobuf data packet using POSITION, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_POSITION_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + &meshtastic_Position_msg, &pos); // make the Data protobuf from position + service->sendToMesh(p, RX_SRC_LOCAL); + } + else + { + LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); + } + } + else + { + LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); + } + } + else + { + LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); + } + } + else + { + // no json, this is an invalid payload + LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); + } + delete json_value; + } + else + { + if (length == 0) + { + LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); + return; + } + else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) + { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } + else + { + if (e.channel_id == NULL || e.gateway_id == NULL) + { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } + meshtastic_Channel ch = channels.getByName(e.channel_id); + if (strcmp(e.gateway_id, owner.id) == 0) + { + // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. + // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node + // receives it when we get our own packet back. Then we'll stop our retransmissions. + if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) + routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); + else + LOG_INFO("Ignoring downlink message we originally sent.\n"); + } + else + { + // Find channel by channel_id and check downlink_enabled + if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || + (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) + { + LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); + meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); + p->via_mqtt = true; // Mark that the packet was received via MQTT - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - p->channel = ch.index; - } + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) + { + p->channel = ch.index; + } - // PKI messages get accepted even if we can't decrypt - if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && - strcmp(e.channel_id, "PKI") == 0) { - meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); - meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); - // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's - // likely they discovered each other via a channel we have downlink enabled for - if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) - router->enqueueReceivedMessage(p); - } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key - router->enqueueReceivedMessage(p); - else - packetPool.release(p); - } - } - } - // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) - free(e.channel_id); - free(e.gateway_id); - free(e.packet); - } + // PKI messages get accepted even if we can't decrypt + if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && + strcmp(e.channel_id, "PKI") == 0) + { + meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); + meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); + // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's + // likely they discovered each other via a channel we have downlink enabled for + if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) + router->enqueueReceivedMessage(p); + } + else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key + router->enqueueReceivedMessage(p); + else + packetPool.release(p); + } + } + } + // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) + free(e.channel_id); + free(e.gateway_id); + free(e.packet); + } } void mqttInit() { - new MQTT(); + new MQTT(); } #if HAS_NETWORKING @@ -196,483 +229,551 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) #endif { - if (moduleConfig.mqtt.enabled) { - LOG_DEBUG("Initializing MQTT\n"); + if (moduleConfig.mqtt.enabled) + { + LOG_DEBUG("Initializing MQTT\n"); - assert(!mqtt); - mqtt = this; + assert(!mqtt); + mqtt = this; - if (*moduleConfig.mqtt.root) { - cryptTopic = moduleConfig.mqtt.root + cryptTopic; - jsonTopic = moduleConfig.mqtt.root + jsonTopic; - mapTopic = moduleConfig.mqtt.root + mapTopic; - } else { - cryptTopic = "msh" + cryptTopic; - jsonTopic = "msh" + jsonTopic; - mapTopic = "msh" + mapTopic; - } + if (*moduleConfig.mqtt.root) + { + cryptTopic = moduleConfig.mqtt.root + cryptTopic; + jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + mapTopic; + } + else + { + cryptTopic = "msh" + cryptTopic; + jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; + } - if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { - map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, - default_map_position_precision); - map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( - moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); - } + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) + { + map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, + default_map_position_precision); + map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( + moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); + } #if HAS_NETWORKING - if (!moduleConfig.mqtt.proxy_to_client_enabled) - pubSub.setCallback(mqttCallback); + if (!moduleConfig.mqtt.proxy_to_client_enabled) + pubSub.setCallback(mqttCallback); #endif - if (moduleConfig.mqtt.proxy_to_client_enabled) { - LOG_INFO("MQTT configured to use client proxy...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; - publishNodeInfo(); - } - // preflightSleepObserver.observe(&preflightSleep); - } else { - disable(); - } + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + LOG_INFO("MQTT configured to use client proxy...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; + publishNodeInfo(); + } + // preflightSleepObserver.observe(&preflightSleep); + } + else + { + disable(); + } } bool MQTT::isConnectedDirectly() { #if HAS_NETWORKING - return pubSub.connected(); + return pubSub.connected(); #else - return false; + return false; #endif } bool MQTT::publish(const char *topic, const char *payload, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; + strcpy(msg->topic, topic); + strcpy(msg->payload_variant.text, payload); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) { - return pubSub.publish(topic, payload, retained); - } + else if (isConnectedDirectly()) + { + return pubSub.publish(topic, payload, retained); + } #endif - return false; + return false; } bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; - strcpy(msg->topic, topic); - msg->payload_variant.data.size = length; - memcpy(msg->payload_variant.data.bytes, payload, length); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; + strcpy(msg->topic, topic); + msg->payload_variant.data.size = length; + memcpy(msg->payload_variant.data.bytes, payload, length); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) { - return pubSub.publish(topic, payload, length, retained); - } + else if (isConnectedDirectly()) + { + return pubSub.publish(topic, payload, length, retained); + } #endif - return false; + return false; } void MQTT::reconnect() { - if (wantsLink()) { - if (moduleConfig.mqtt.proxy_to_client_enabled) { - LOG_INFO("MQTT connecting via client proxy instead...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; + if (wantsLink()) + { + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + LOG_INFO("MQTT connecting via client proxy instead...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - return; // Don't try to connect directly to the server - } + publishNodeInfo(); + return; // Don't try to connect directly to the server + } #if HAS_NETWORKING - // Defaults - int serverPort = 1883; - const char *serverAddr = default_mqtt_address; - const char *mqttUsername = default_mqtt_username; - const char *mqttPassword = default_mqtt_password; + // Defaults + int serverPort = 1883; + const char *serverAddr = default_mqtt_address; + const char *mqttUsername = default_mqtt_username; + const char *mqttPassword = default_mqtt_password; - if (*moduleConfig.mqtt.address) { - serverAddr = moduleConfig.mqtt.address; - mqttUsername = moduleConfig.mqtt.username; - mqttPassword = moduleConfig.mqtt.password; - } + if (*moduleConfig.mqtt.address) + { + serverAddr = moduleConfig.mqtt.address; + mqttUsername = moduleConfig.mqtt.username; + mqttPassword = moduleConfig.mqtt.password; + } #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (moduleConfig.mqtt.tls_enabled) { - // change default for encrypted to 8883 - try { - serverPort = 8883; - wifiSecureClient.setInsecure(); + if (moduleConfig.mqtt.tls_enabled) + { + // change default for encrypted to 8883 + try + { + serverPort = 8883; + wifiSecureClient.setInsecure(); - pubSub.setClient(wifiSecureClient); - LOG_INFO("Using TLS-encrypted session\n"); - } catch (const std::exception &e) { - LOG_ERROR("MQTT ERROR: %s\n", e.what()); - } - } else { - LOG_INFO("Using non-TLS-encrypted session\n"); - pubSub.setClient(mqttClient); - } + pubSub.setClient(wifiSecureClient); + LOG_INFO("Using TLS-encrypted session\n"); + } + catch (const std::exception &e) + { + LOG_ERROR("MQTT ERROR: %s\n", e.what()); + } + } + else + { + LOG_INFO("Using non-TLS-encrypted session\n"); + pubSub.setClient(mqttClient); + } #elif HAS_NETWORKING - pubSub.setClient(mqttClient); + pubSub.setClient(mqttClient); #endif - String server = String(serverAddr); - int delimIndex = server.indexOf(':'); - if (delimIndex > 0) { - String port = server.substring(delimIndex + 1, server.length()); - server[delimIndex] = 0; - serverPort = port.toInt(); - serverAddr = server.c_str(); - } - pubSub.setServer(serverAddr, serverPort); - pubSub.setBufferSize(512); + String server = String(serverAddr); + int delimIndex = server.indexOf(':'); + if (delimIndex > 0) + { + String port = server.substring(delimIndex + 1, server.length()); + server[delimIndex] = 0; + serverPort = port.toInt(); + serverAddr = server.c_str(); + } + pubSub.setServer(serverAddr, serverPort); + pubSub.setBufferSize(512); - LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, - serverPort, mqttUsername, mqttPassword); + LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, + serverPort, mqttUsername, mqttPassword); - bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); - if (connected) { - LOG_INFO("MQTT connected\n"); - enabled = true; // Start running background process again - runASAP = true; - reconnectCount = 0; + bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); + if (connected) + { + LOG_INFO("MQTT connected\n"); + enabled = true; // Start running background process again + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - sendSubscriptions(); - } else { + publishNodeInfo(); + sendSubscriptions(); + } + else + { #if HAS_WIFI && !defined(ARCH_PORTDUINO) - reconnectCount++; - LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); - if (reconnectCount >= reconnectMax) { - needReconnect = true; - wifiReconnect->setIntervalFromNow(0); - reconnectCount = 0; - } + reconnectCount++; + LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); + if (reconnectCount >= reconnectMax) + { + needReconnect = true; + wifiReconnect->setIntervalFromNow(0); + reconnectCount = 0; + } #endif - } + } #endif - } + } } void MQTT::sendSubscriptions() { #if HAS_NETWORKING - bool hasDownlink = false; - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) { - const auto &ch = channels.getByIndex(i); - if (ch.settings.downlink_enabled) { - hasDownlink = true; - std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### - if (moduleConfig.mqtt.json_enabled == true) { - std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); - pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? - } + bool hasDownlink = false; + size_t numChan = channels.getNumChannels(); + for (size_t i = 0; i < numChan; i++) + { + const auto &ch = channels.getByIndex(i); + if (ch.settings.downlink_enabled) + { + hasDownlink = true; + std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### + if (moduleConfig.mqtt.json_enabled == true) + { + std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); + pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? + } #endif // ARCH_NRF52 NRF52_USE_JSON - } - } + } + } #if !MESHTASTIC_EXCLUDE_PKI - if (hasDownlink) { - std::string topic = cryptTopic + "PKI/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); - } + if (hasDownlink) + { + std::string topic = cryptTopic + "PKI/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); + } #endif #endif } bool MQTT::wantsLink() const { - bool hasChannelorMapReport = - moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); + bool hasChannelorMapReport = + moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); - if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) - return true; + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) + return true; #if HAS_WIFI - return hasChannelorMapReport && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif - return false; + return false; } int32_t MQTT::runOnce() { #if HAS_NETWORKING - if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) - return disable(); + if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) + return disable(); - bool wantConnection = wantsLink(); + bool wantConnection = wantsLink(); - perhapsReportToMap(); + perhapsReportToMap(); - // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server - if (moduleConfig.mqtt.proxy_to_client_enabled) { - publishQueuedMessages(); - return 200; - } + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + publishQueuedMessages(); + return 200; + } + else if (!pubSub.loop()) + { + if (!wantConnection) + return 5000; // If we don't want connection now, check again in 5 secs + else + { + reconnect(); + // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP + // connections are EXPENSIVE so try rarely) + if (isConnectedDirectly()) + { + publishQueuedMessages(); + return 200; + } + else + return 30000; + } + } + else + { + // we are connected to server, check often for new requests on the TCP port + if (!wantConnection) + { + LOG_INFO("MQTT link not needed, dropping\n"); + pubSub.disconnect(); + } - else if (!pubSub.loop()) { - if (!wantConnection) - return 5000; // If we don't want connection now, check again in 5 secs - else { - reconnect(); - // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP - // connections are EXPENSIVE so try rarely) - if (isConnectedDirectly()) { - publishQueuedMessages(); - return 200; - } else - return 30000; - } - } else { - // we are connected to server, check often for new requests on the TCP port - if (!wantConnection) { - LOG_INFO("MQTT link not needed, dropping\n"); - pubSub.disconnect(); - } - - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) - return 20; - } + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) + return 20; + } #endif - return 30000; + return 30000; } void MQTT::publishNodeInfo() { - // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) + // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) } void MQTT::publishQueuedMessages() { - if (!mqttQueue.isEmpty()) { - LOG_DEBUG("Publishing enqueued MQTT message\n"); - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic; - if (env->packet->pki_encrypted) { - topic = cryptTopic + "PKI/" + owner.id; - } else { - topic = cryptTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); + if (!mqttQueue.isEmpty()) + { + LOG_DEBUG("Publishing enqueued MQTT message\n"); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic; + if (env->packet->pki_encrypted) + { + topic = cryptTopic + "PKI/" + owner.id; + } + else + { + topic = cryptTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); - if (jsonString.length() != 0) { - std::string topicJson; - if (env->packet->pki_encrypted) { - topicJson = jsonTopic + "PKI/" + owner.id; - } else { - topicJson = jsonTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### + if (moduleConfig.mqtt.json_enabled) + { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); + if (jsonString.length() != 0) + { + std::string topicJson; + if (env->packet->pki_encrypted) + { + topicJson = jsonTopic + "PKI/" + owner.id; + } + else + { + topicJson = jsonTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - mqttPool.release(env); - } + mqttPool.release(env); + } } void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex) { - if (mp.via_mqtt) - return; // Don't send messages that came from MQTT back into MQTT - bool uplinkEnabled = false; - for (int i = 0; i <= 7; i++) { - if (channels.getByIndex(i).settings.uplink_enabled) - uplinkEnabled = true; - } - if (!uplinkEnabled) - return; // no channels have an uplink enabled - auto &ch = channels.getByIndex(chIndex); + if (mp.via_mqtt) + return; // Don't send messages that came from MQTT back into MQTT + bool uplinkEnabled = false; + for (int i = 0; i <= 7; i++) + { + if (channels.getByIndex(i).settings.uplink_enabled) + uplinkEnabled = true; + } + if (!uplinkEnabled) + return; // no channels have an uplink enabled + auto &ch = channels.getByIndex(chIndex); - if (!mp.pki_encrypted) { - if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { - LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); - return; - } + if (!mp.pki_encrypted) + { + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) + { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } - // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && - !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && - (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || - (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { - LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); - return; - } + // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. + if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && + !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && + (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || + (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) + { + LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); + return; + } - if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { - LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); - return; - } - } - if (mp.pki_encrypted || ch.settings.uplink_enabled) { - const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) + { + LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); + return; + } + } + if (mp.pki_encrypted || ch.settings.uplink_enabled) + { + const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); - meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); - env->channel_id = (char *)channelId; - env->gateway_id = owner.id; + meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); + env->channel_id = (char *)channelId; + env->gateway_id = owner.id; - LOG_DEBUG("MQTT onSend - Publishing "); - if (moduleConfig.mqtt.encryption_enabled) { - env->packet = (meshtastic_MeshPacket *)∓ - LOG_DEBUG("encrypted message\n"); - } else if (mp_decoded.which_payload_variant == - meshtastic_MeshPacket_decoded_tag) { // Don't upload a still-encrypted PKI packet - env->packet = (meshtastic_MeshPacket *)&mp_decoded; - LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); - } + LOG_DEBUG("MQTT onSend - Publishing "); + if (moduleConfig.mqtt.encryption_enabled) + { + env->packet = (meshtastic_MeshPacket *)∓ + LOG_DEBUG("encrypted message\n"); + } + else if (mp_decoded.which_payload_variant == + meshtastic_MeshPacket_decoded_tag) + { // Don't upload a still-encrypted PKI packet + env->packet = (meshtastic_MeshPacket *)&mp_decoded; + LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); + } - if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic = cryptTopic + channelId + "/" + owner.id; - LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); + if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) + { + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic = cryptTopic + channelId + "/" + owner.id; + LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); - if (jsonString.length() != 0) { - std::string topicJson = jsonTopic + channelId + "/" + owner.id; - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### + if (moduleConfig.mqtt.json_enabled) + { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); + if (jsonString.length() != 0) + { + std::string topicJson = jsonTopic + channelId + "/" + owner.id; + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - } else { - LOG_INFO("MQTT not connected, queueing packet\n"); - if (mqttQueue.numFree() == 0) { - LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); - meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); - if (d) - mqttPool.release(d); - } - // make a copy of serviceEnvelope and queue it - meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); - assert(mqttQueue.enqueue(copied, 0)); - } - mqttPool.release(env); - } + } + else + { + LOG_INFO("MQTT not connected, queueing packet\n"); + if (mqttQueue.numFree() == 0) + { + LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); + meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); + if (d) + mqttPool.release(d); + } + // make a copy of serviceEnvelope and queue it + meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); + assert(mqttQueue.enqueue(copied, 0)); + } + mqttPool.release(env); + } } void MQTT::perhapsReportToMap() { - if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) - return; + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; - if (millis() - last_report_to_map < map_publish_interval_msecs) { - return; - } else { - if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { - last_report_to_map = millis(); - if (map_position_precision == 0) - LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); - if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) - LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); - return; - } + if (millis() - last_report_to_map < map_publish_interval_msecs) + { + return; + } + else + { + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) + { + last_report_to_map = millis(); + if (map_position_precision == 0) + LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) + LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); + return; + } - // Allocate ServiceEnvelope and fill it - meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); - se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id - se->gateway_id = owner.id; + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; - // Allocate MeshPacket and fill it - meshtastic_MeshPacket *mp = packetPool.allocZeroed(); - mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; - mp->from = nodeDB->getNodeNum(); - mp->to = NODENUM_BROADCAST; - mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB->getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; - // Fill MapReport message - meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; - memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); - memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); - mapReport.role = config.device.role; - mapReport.hw_model = owner.hw_model; - strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); - mapReport.region = config.lora.region; - mapReport.modem_preset = config.lora.modem_preset; - mapReport.has_default_channel = channels.hasDefaultChannel(); + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); - // Set position with precision (same as in PositionModule) - if (map_position_precision < 32 && map_position_precision > 0) { - mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.latitude_i += (1 << (31 - map_position_precision)); - mapReport.longitude_i += (1 << (31 - map_position_precision)); - } else { - mapReport.latitude_i = localPosition.latitude_i; - mapReport.longitude_i = localPosition.longitude_i; - } - mapReport.altitude = localPosition.altitude; - mapReport.position_precision = map_position_precision; + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) + { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } + else + { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; - mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); + mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); - // Encode MapReport message and set it to MeshPacket in ServiceEnvelope - mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), - &meshtastic_MapReport_msg, &mapReport); - se->packet = mp; + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); - LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); - publish(mapTopic.c_str(), bytes, numBytes, false); + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); - // Release the allocated memory for ServiceEnvelope and MeshPacket - mqttPool.release(se); - packetPool.release(mp); + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); - // Update the last report time - last_report_to_map = millis(); - } + // Update the last report time + last_report_to_map = millis(); + } } bool MQTT::isValidJsonEnvelope(JSONObject &json) { - // if "sender" is provided, avoid processing packets we uplinked - return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && - (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number - (json.find("from") != json.end()) && json["from"]->IsNumber() && - (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us - (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type - (json.find("payload") != json.end()); // should have a payload + // if "sender" is provided, avoid processing packets we uplinked + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number + (json.find("from") != json.end()) && json["from"]->IsNumber() && + (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us + (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type + (json.find("payload") != json.end()); // should have a payload } diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 177255b2f..9ed2a0d6a 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -19,7 +19,7 @@ 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 @@ -32,23 +32,23 @@ 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); + /** + * 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\n"); - fromNum.notify32(fromRadioNum); - } + LOG_INFO("BLE notify fromNum\n"); + fromNum.notify32(fromRadioNum); + } - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override - { - BLEConnection *connection = Bluefruit.Connection(connectionHandle); - return connection->connected(); - } + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override + { + BLEConnection *connection = Bluefruit.Connection(connectionHandle); + return connection->connected(); + } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; @@ -56,12 +56,12 @@ 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\n", central_name); + // 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\n", central_name); } /** * Callback invoked when a connection is dropped @@ -70,246 +70,258 @@ void onConnect(uint16_t conn_handle) */ void onDisconnect(uint16_t conn_handle, uint8_t reason) { - // FIXME - we currently assume only one active connection - LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); + // FIXME - we currently assume only one active connection + LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { - // Display the raw request packet - LOG_INFO("CCCD Updated: %u\n", cccd_value); - // Check the characteristic this CCCD update is associated with in case - // this handler is used for multiple CCCD records. + // Display the raw request packet + LOG_INFO("CCCD Updated: %u\n", 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\n"); - } else { - LOG_INFO("Notify/Indicate disabled\n"); - } - } + 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\n"); + } + else + { + LOG_INFO("Notify/Indicate disabled\n"); + } + } } 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 = 152.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, 244); // 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 + // 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 = 152.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, 244); // 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); + 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("Ignoring successor read\n"); - } - authorizeRead(conn_hdl); + 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("Ignoring successor read\n"); + } + authorizeRead(conn_hdl); } void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { - LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); + LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); + bluetoothPhoneAPI->handleToRadio(data, len); } 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(); + 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\n"); - uint8_t connection_num = Bluefruit.connected(); - if (connection_num) { - for (uint8_t i = 0; i < connection_num; i++) { - LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); - Bluefruit.disconnect(i); - } - delay(100); // wait for ondisconnect; - } - Bluefruit.Advertising.stop(); + // Shutdown bluetooth for minimum power draw + LOG_INFO("Disable NRF52 bluetooth\n"); + uint8_t connection_num = Bluefruit.connected(); + if (connection_num) + { + for (uint8_t i = 0; i < connection_num; i++) + { + LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); + Bluefruit.disconnect(i); + } + delay(100); // wait for ondisconnect; + } + 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("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); + // Setup Bluetooth + nrf52Bluetooth->setup(); + // Shutdown bluetooth for minimum power draw + Bluefruit.Advertising.stop(); + Bluefruit.setTxPower(-40); // Minimum power + LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } bool NRF52Bluetooth::isConnected() { - return Bluefruit.connected(connectionHandle); + return Bluefruit.connected(connectionHandle); } int NRF52Bluetooth::getRssi() { - return 0; // FIXME figure out where to source this + return 0; // FIXME figure out where to source this } void NRF52Bluetooth::setup() { - // Initialise the Bluefruit module - LOG_INFO("Initialize the Bluefruit nRF52 module\n"); - 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'\n", 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); + // Initialise the Bluefruit module + LOG_INFO("Initialize the Bluefruit nRF52 module\n"); + 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'\n", 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); #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("Configuring the Device Information Service\n"); - bledis.setModel(optstr(HW_VERSION)); - bledis.setFirmwareRev(optstr(APP_VERSION)); - bledis.begin(); - // Start the BLE Battery Service and set it to 100% - LOG_INFO("Configuring the Battery Service\n"); - blebas.begin(); - blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using - // BLEService and BLECharacteristic classes - LOG_INFO("Configuring the Mesh bluetooth service\n"); - setupMeshService(); - // Setup the advertising packet(s) - LOG_INFO("Setting up the advertising payload(s)\n"); - startAdv(); - LOG_INFO("Advertising\n"); + // Configure and Start the Device Information Service + LOG_INFO("Configuring the Device Information Service\n"); + bledis.setModel(optstr(HW_VERSION)); + bledis.setFirmwareRev(optstr(APP_VERSION)); + bledis.begin(); + // Start the BLE Battery Service and set it to 100% + LOG_INFO("Configuring the Battery Service\n"); + blebas.begin(); + blebas.write(0); // Unknown battery level for now + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + LOG_INFO("Configuring the Mesh bluetooth service\n"); + setupMeshService(); + // Setup the advertising packet(s) + LOG_INFO("Setting up the advertising payload(s)\n"); + startAdv(); + LOG_INFO("Advertising\n"); } void NRF52Bluetooth::resumeAdvertising() { - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // 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); + blebas.write(level); } void NRF52Bluetooth::clearBonds() { - LOG_INFO("Clearing bluetooth bonds!\n"); - bond_print_list(BLE_GAP_ROLE_PERIPH); - bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.Periph.clearBonds(); - Bluefruit.Central.clearBonds(); + LOG_INFO("Clearing bluetooth bonds!\n"); + 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\n"); + LOG_INFO("BLE connection secured\n"); } bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { - LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); #if !defined(MESHTASTIC_EXCLUDE_SCREEN) screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { @@ -337,31 +349,33 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke 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; - } - } - LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); - return true; + if (match_request) + { + uint32_t start_time = millis(); + while (millis() < start_time + 30000) + { + if (!Bluefruit.connected(conn_handle)) + break; + } + } + LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); + return true; } void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { - if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) - LOG_INFO("BLE pairing success\n"); - else - LOG_INFO("BLE pairing failed\n"); - screen->endAlert(); + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + LOG_INFO("BLE pairing success\n"); + else + LOG_INFO("BLE pairing failed\n"); + 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); + 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/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 777c10ada..8c58fba27 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -16,296 +16,360 @@ StaticJsonDocument<1024> arrayObj; std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { - // the created jsonObj is immutable after creation, so - // we need to do the heavy lifting before assembling it. - std::string msgType; + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; jsonObj.clear(); arrayObj.clear(); - if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - switch (mp->decoded.portnum) { - case meshtastic_PortNum_TEXT_MESSAGE_APP: { - msgType = "text"; - // convert bytes to string - if (shouldLog) - LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) + { + switch (mp->decoded.portnum) + { + case meshtastic_PortNum_TEXT_MESSAGE_APP: + { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - // check if this is a JSON payload + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload StaticJsonDocument<512> text_doc; DeserializationError error = deserializeJson(text_doc, payloadStr); - if (error) { + if (error) + { // if it isn't, then we need to create a json object - // with the string as the value - if (shouldLog) - LOG_INFO("text message payload is of type plaintext\n"); + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); jsonObj["payload"]["text"] = payloadStr; - } else { + } + else + { // if it is, then we can just use the json object - if (shouldLog) - LOG_INFO("text message payload is of type json\n"); + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); jsonObj["payload"] = text_doc; - } - break; - } - case meshtastic_PortNum_TELEMETRY_APP: { - msgType = "telemetry"; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { - jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; - jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; - jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; - jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; - jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; - } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { - jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; - jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; - jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; - jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; - jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; - jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; - jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; - jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; - jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; - jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; - jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; - jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; - jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; - } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { - jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; - jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; - jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; - jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; - jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; - jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; - } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { - jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; - jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; - jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; - jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; - jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; - jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; - } - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for telemetry message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NODEINFO_APP: { - msgType = "nodeinfo"; - meshtastic_User scratch; - meshtastic_User *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { - decoded = &scratch; - jsonObj["payload"]["id"] = decoded->id; - jsonObj["payload"]["longname"] = decoded->long_name; - jsonObj["payload"]["shortname"] = decoded->short_name; - jsonObj["payload"]["hardware"] = decoded->hw_model; - jsonObj["payload"]["role"] = (int)decoded->role; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_POSITION_APP: { - msgType = "position"; - meshtastic_Position scratch; - meshtastic_Position *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { - decoded = &scratch; - if ((int)decoded->time) { - jsonObj["payload"]["time"] = (unsigned int)decoded->time; - } - if ((int)decoded->timestamp) { - jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; - } - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - if ((int)decoded->altitude) { - jsonObj["payload"]["altitude"] = (int)decoded->altitude; - } - if ((int)decoded->ground_speed) { - jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; - } - if (int(decoded->ground_track)) { - jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; - } - if (int(decoded->sats_in_view)) { - jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; - } - if ((int)decoded->PDOP) { - jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; - } - if ((int)decoded->HDOP) { - jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; - } - if ((int)decoded->VDOP) { - jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; - } - if ((int)decoded->precision_bits) { - jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; - } - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_WAYPOINT_APP: { - msgType = "position"; - meshtastic_Waypoint scratch; - meshtastic_Waypoint *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { - decoded = &scratch; - jsonObj["payload"]["id"] = (unsigned int)decoded->id; - jsonObj["payload"]["name"] = decoded->name; - jsonObj["payload"]["description"] = decoded->description; - jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; - jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NEIGHBORINFO_APP: { - msgType = "neighborinfo"; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, - &scratch)) { - decoded = &scratch; - jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; - jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; - jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; - jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; - + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: + { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) + { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) + { + jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; + jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; + jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; + jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; + jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; + } + else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) + { + jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; + jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; + jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; + jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; + jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; + jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; + jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; + jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; + jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; + jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; + jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; + jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; + jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; + } + else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) + { + jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; + jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; + jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; + jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + } + else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) + { + jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; + jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; + jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; + jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; + jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; + jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; + } + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: + { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) + { + decoded = &scratch; + jsonObj["payload"]["id"] = decoded->id; + jsonObj["payload"]["longname"] = decoded->long_name; + jsonObj["payload"]["shortname"] = decoded->short_name; + jsonObj["payload"]["hardware"] = decoded->hw_model; + jsonObj["payload"]["role"] = (int)decoded->role; + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_POSITION_APP: + { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) + { + decoded = &scratch; + if ((int)decoded->time) + { + jsonObj["payload"]["time"] = (unsigned int)decoded->time; + } + if ((int)decoded->timestamp) + { + jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; + } + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + if ((int)decoded->altitude) + { + jsonObj["payload"]["altitude"] = (int)decoded->altitude; + } + if ((int)decoded->ground_speed) + { + jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; + } + if (int(decoded->ground_track)) + { + jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; + } + if (int(decoded->sats_in_view)) + { + jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; + } + if ((int)decoded->PDOP) + { + jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; + } + if ((int)decoded->HDOP) + { + jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; + } + if ((int)decoded->VDOP) + { + jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; + } + if ((int)decoded->precision_bits) + { + jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; + } + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: + { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) + { + decoded = &scratch; + jsonObj["payload"]["id"] = (unsigned int)decoded->id; + jsonObj["payload"]["name"] = decoded->name; + jsonObj["payload"]["description"] = decoded->description; + jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; + jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: + { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) + { + decoded = &scratch; + jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; + jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; + jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; + jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; + JsonObject neighbors_obj = arrayObj.to(); JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); JsonObject neighbors_0 = neighbors.createNestedObject(); - for (uint8_t i = 0; i < decoded->neighbors_count; i++) { - neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; - neighbors_0["snr"] = (int)decoded->neighbors[i].snr; - neighbors[i+1] = neighbors_0; - neighbors_0.clear(); - } + for (uint8_t i = 0; i < decoded->neighbors_count; i++) + { + neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; + neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors[i + 1] = neighbors_0; + neighbors_0.clear(); + } neighbors.remove(0); jsonObj["payload"]["neighbors"] = neighbors; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_TRACEROUTE_APP: { - if (mp->decoded.request_id) { // Only report the traceroute response - msgType = "traceroute"; - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, - &scratch)) { - decoded = &scratch; - JsonArray route = arrayObj.createNestedArray("route"); - - auto addToRoute = [](JsonArray *route, NodeNum num) { - char long_name[40] = "Unknown"; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); - bool name_known = node ? node->has_user : false; - if (name_known) - memcpy(long_name, node->user.long_name, sizeof(long_name)); - route->add(long_name); - }; - - addToRoute(&route,mp->to); //route.add(mp->to); - for (uint8_t i = 0; i < decoded->route_count; i++) { - addToRoute(&route,decoded->route[i]); //route.add(decoded->route[i]); - } - addToRoute(&route,mp->from); //route.add(mp->from); // Ended at the original destination (source of response) - - jsonObj["payload"]["route"] = route; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for traceroute message!\n"); - return ""; - } - } else { - LOG_WARN("Traceroute response not reported"); + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); return ""; } - break; - } - case meshtastic_PortNum_DETECTION_SENSOR_APP: { - msgType = "detection"; - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - jsonObj["payload"]["text"] = payloadStr; - break; - } - case meshtastic_PortNum_REMOTE_HARDWARE_APP: { - meshtastic_HardwareMessage scratch; - meshtastic_HardwareMessage *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, - &scratch)) { - decoded = &scratch; - if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { - msgType = "gpios_changed"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { - msgType = "gpios_read_reply"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; - } - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); - return ""; - } - break; - } - // add more packet types here if needed - default: - LOG_WARN("Unsupported packet type %d\n",mp->decoded.portnum); + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: + { + if (mp->decoded.request_id) + { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) + { + decoded = &scratch; + JsonArray route = arrayObj.createNestedArray("route"); + + auto addToRoute = [](JsonArray *route, NodeNum num) + { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->add(long_name); + }; + + addToRoute(&route, mp->to); // route.add(mp->to); + for (uint8_t i = 0; i < decoded->route_count; i++) + { + addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]); + } + addToRoute(&route, mp->from); // route.add(mp->from); // Ended at the original destination (source of response) + + jsonObj["payload"]["route"] = route; + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + return ""; + } + } + else + { + LOG_WARN("Traceroute response not reported"); + return ""; + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: + { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + jsonObj["payload"]["text"] = payloadStr; + break; + } + case meshtastic_PortNum_REMOTE_HARDWARE_APP: + { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) + { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) + { + msgType = "gpios_changed"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + } + else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) + { + msgType = "gpios_read_reply"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; + } + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + return ""; + } + break; + } + // add more packet types here if needed + default: + LOG_WARN("Unsupported packet type %d\n", mp->decoded.portnum); return ""; - break; - } - } else if (shouldLog) { - LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + break; + } + } + else if (shouldLog) + { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); return ""; - } + } - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["type"] = msgType.c_str(); - jsonObj["sender"] = owner.id; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["type"] = msgType.c_str(); + jsonObj["sender"] = owner.id; + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } - // serialize and write it to the stream + // serialize and write it to the stream // Serial.printf("serialized json message: \r\n"); // serializeJson(jsonObj, Serial); @@ -314,39 +378,40 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, std::string jsonStr = ""; serializeJson(jsonObj, jsonStr); - if (shouldLog) - LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); - return jsonStr; + return jsonStr; } std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) { jsonObj.clear(); - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["time_ms"] = (double)millis(); - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["want_ack"] = mp->want_ack; + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["time_ms"] = (double)millis(); + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["want_ack"] = mp->want_ack; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } - jsonObj["size"] = (unsigned int)mp->encrypted.size; - auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); - jsonObj["bytes"] = encryptedStr.c_str(); + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + jsonObj["size"] = (unsigned int)mp->encrypted.size; + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = encryptedStr.c_str(); - // serialize and write it to the stream + // serialize and write it to the stream std::string jsonStr = ""; serializeJson(jsonObj, jsonStr); - return jsonStr; + return jsonStr; } #endif \ No newline at end of file From c4c85777d035402140d96270e280051185e0a218 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 13:20:09 +0800 Subject: [PATCH 1091/1377] Another try to get the code format correct. --- src/mqtt/MQTT.cpp | 1097 +++++++++++-------------- src/platform/nrf52/NRF52Bluetooth.cpp | 456 +++++----- 2 files changed, 719 insertions(+), 834 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 3e0e692b4..a4921e684 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -33,194 +33,161 @@ Allocator &mqttPool = staticMqttPool; void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) { - mqtt->onReceive(topic, payload, length); + mqtt->onReceive(topic, payload, length); } void MQTT::onClientProxyReceive(meshtastic_MqttClientProxyMessage msg) { - onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); + onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); } void MQTT::onReceive(char *topic, byte *payload, size_t length) { - meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; + meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; - if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) - { - // check if this is a json payload message by comparing the topic start - char payloadStr[length + 1]; - memcpy(payloadStr, payload, length); - payloadStr[length] = 0; // null terminated string - JSONValue *json_value = JSON::Parse(payloadStr); - if (json_value != NULL) - { - // check if it is a valid envelope - JSONObject json; - json = json_value->AsObject(); + if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) { + // check if this is a json payload message by comparing the topic start + char payloadStr[length + 1]; + memcpy(payloadStr, payload, length); + payloadStr[length] = 0; // null terminated string + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) { + // check if it is a valid envelope + JSONObject json; + json = json_value->AsObject(); - // parse the channel name from the topic string - // the topic has been checked above for having jsonTopic prefix, so just move past it - char *ptr = topic + jsonTopic.length(); - ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character - meshtastic_Channel sendChannel = channels.getByName(ptr); - // We allow downlink JSON packets only on a channel named "mqtt" - if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && - sendChannel.settings.downlink_enabled) - { - if (isValidJsonEnvelope(json)) - { - // this is a valid envelope - if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) - { - std::string jsonPayloadStr = json["payload"]->AsString(); - LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); + // parse the channel name from the topic string + // the topic has been checked above for having jsonTopic prefix, so just move past it + char *ptr = topic + jsonTopic.length(); + ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character + meshtastic_Channel sendChannel = channels.getByName(ptr); + // We allow downlink JSON packets only on a channel named "mqtt" + if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && + sendChannel.settings.downlink_enabled) { + if (isValidJsonEnvelope(json)) { + // this is a valid envelope + if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) { + std::string jsonPayloadStr = json["payload"]->AsString(); + LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); - // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) - { - memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); - p->decoded.payload.size = jsonPayloadStr.length(); - service->sendToMesh(p, RX_SRC_LOCAL); - } - else - { - LOG_WARN("Received MQTT json payload too long, dropping\n"); - } - } - else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) - { - // invent the "sendposition" type for a valid envelope - JSONObject posit; - posit = json["payload"]->AsObject(); // get nested JSON Position - meshtastic_Position pos = meshtastic_Position_init_default; - if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) - pos.latitude_i = posit["latitude_i"]->AsNumber(); - if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) - pos.longitude_i = posit["longitude_i"]->AsNumber(); - if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) - pos.altitude = posit["altitude"]->AsNumber(); - if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) - pos.time = posit["time"]->AsNumber(); + // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { + memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); + p->decoded.payload.size = jsonPayloadStr.length(); + service->sendToMesh(p, RX_SRC_LOCAL); + } else { + LOG_WARN("Received MQTT json payload too long, dropping\n"); + } + } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) { + // invent the "sendposition" type for a valid envelope + JSONObject posit; + posit = json["payload"]->AsObject(); // get nested JSON Position + meshtastic_Position pos = meshtastic_Position_init_default; + if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) + pos.latitude_i = posit["latitude_i"]->AsNumber(); + if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) + pos.longitude_i = posit["longitude_i"]->AsNumber(); + if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) + pos.altitude = posit["altitude"]->AsNumber(); + if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) + pos.time = posit["time"]->AsNumber(); - // construct protobuf data packet using POSITION, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_POSITION_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), - &meshtastic_Position_msg, &pos); // make the Data protobuf from position - service->sendToMesh(p, RX_SRC_LOCAL); - } - else - { - LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); - } - } - else - { - LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); - } - } - else - { - LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); - } - } - else - { - // no json, this is an invalid payload - LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); - } - delete json_value; - } - else - { - if (length == 0) - { - LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); - return; - } - else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) - { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } - else - { - if (e.channel_id == NULL || e.gateway_id == NULL) - { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } - meshtastic_Channel ch = channels.getByName(e.channel_id); - if (strcmp(e.gateway_id, owner.id) == 0) - { - // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. - // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node - // receives it when we get our own packet back. Then we'll stop our retransmissions. - if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) - routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); - else - LOG_INFO("Ignoring downlink message we originally sent.\n"); - } - else - { - // Find channel by channel_id and check downlink_enabled - if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || - (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) - { - LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); - meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); - p->via_mqtt = true; // Mark that the packet was received via MQTT + // construct protobuf data packet using POSITION, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_POSITION_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + &meshtastic_Position_msg, &pos); // make the Data protobuf from position + service->sendToMesh(p, RX_SRC_LOCAL); + } else { + LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); + } + } else { + LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); + } + } else { + LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); + } + } else { + // no json, this is an invalid payload + LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); + } + delete json_value; + } else { + if (length == 0) { + LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); + return; + } else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } else { + if (e.channel_id == NULL || e.gateway_id == NULL) { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } + meshtastic_Channel ch = channels.getByName(e.channel_id); + if (strcmp(e.gateway_id, owner.id) == 0) { + // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. + // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node + // receives it when we get our own packet back. Then we'll stop our retransmissions. + if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) + routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); + else + LOG_INFO("Ignoring downlink message we originally sent.\n"); + } else { + // Find channel by channel_id and check downlink_enabled + if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || + (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) { + LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); + meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); + p->via_mqtt = true; // Mark that the packet was received via MQTT - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) - { - p->channel = ch.index; - } + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + p->channel = ch.index; + } - // PKI messages get accepted even if we can't decrypt - if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && - strcmp(e.channel_id, "PKI") == 0) - { - meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); - meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); - // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's - // likely they discovered each other via a channel we have downlink enabled for - if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) - router->enqueueReceivedMessage(p); - } - else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key - router->enqueueReceivedMessage(p); - else - packetPool.release(p); - } - } - } - // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) - free(e.channel_id); - free(e.gateway_id); - free(e.packet); - } + // PKI messages get accepted even if we can't decrypt + if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && + strcmp(e.channel_id, "PKI") == 0) { + meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); + meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); + // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's + // likely they discovered each other via a channel we have downlink enabled for + if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) + router->enqueueReceivedMessage(p); + } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key + router->enqueueReceivedMessage(p); + else + packetPool.release(p); + } + } + } + // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) + free(e.channel_id); + free(e.gateway_id); + free(e.packet); + } } void mqttInit() { - new MQTT(); + new MQTT(); } #if HAS_NETWORKING @@ -229,551 +196,483 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) #endif { - if (moduleConfig.mqtt.enabled) - { - LOG_DEBUG("Initializing MQTT\n"); + if (moduleConfig.mqtt.enabled) { + LOG_DEBUG("Initializing MQTT\n"); - assert(!mqtt); - mqtt = this; + assert(!mqtt); + mqtt = this; - if (*moduleConfig.mqtt.root) - { - cryptTopic = moduleConfig.mqtt.root + cryptTopic; - jsonTopic = moduleConfig.mqtt.root + jsonTopic; - mapTopic = moduleConfig.mqtt.root + mapTopic; - } - else - { - cryptTopic = "msh" + cryptTopic; - jsonTopic = "msh" + jsonTopic; - mapTopic = "msh" + mapTopic; - } + if (*moduleConfig.mqtt.root) { + cryptTopic = moduleConfig.mqtt.root + cryptTopic; + jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + mapTopic; + } else { + cryptTopic = "msh" + cryptTopic; + jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; + } - if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) - { - map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, - default_map_position_precision); - map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( - moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); - } + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { + map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, + default_map_position_precision); + map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( + moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); + } #if HAS_NETWORKING - if (!moduleConfig.mqtt.proxy_to_client_enabled) - pubSub.setCallback(mqttCallback); + if (!moduleConfig.mqtt.proxy_to_client_enabled) + pubSub.setCallback(mqttCallback); #endif - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - LOG_INFO("MQTT configured to use client proxy...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; - publishNodeInfo(); - } - // preflightSleepObserver.observe(&preflightSleep); - } - else - { - disable(); - } + if (moduleConfig.mqtt.proxy_to_client_enabled) { + LOG_INFO("MQTT configured to use client proxy...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; + publishNodeInfo(); + } + // preflightSleepObserver.observe(&preflightSleep); + } else { + disable(); + } } bool MQTT::isConnectedDirectly() { #if HAS_NETWORKING - return pubSub.connected(); + return pubSub.connected(); #else - return false; + return false; #endif } bool MQTT::publish(const char *topic, const char *payload, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; + strcpy(msg->topic, topic); + strcpy(msg->payload_variant.text, payload); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) - { - return pubSub.publish(topic, payload, retained); - } + else if (isConnectedDirectly()) { + return pubSub.publish(topic, payload, retained); + } #endif - return false; + return false; } bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; - strcpy(msg->topic, topic); - msg->payload_variant.data.size = length; - memcpy(msg->payload_variant.data.bytes, payload, length); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; + strcpy(msg->topic, topic); + msg->payload_variant.data.size = length; + memcpy(msg->payload_variant.data.bytes, payload, length); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) - { - return pubSub.publish(topic, payload, length, retained); - } + else if (isConnectedDirectly()) { + return pubSub.publish(topic, payload, length, retained); + } #endif - return false; + return false; } void MQTT::reconnect() { - if (wantsLink()) - { - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - LOG_INFO("MQTT connecting via client proxy instead...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; + if (wantsLink()) { + if (moduleConfig.mqtt.proxy_to_client_enabled) { + LOG_INFO("MQTT connecting via client proxy instead...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - return; // Don't try to connect directly to the server - } + publishNodeInfo(); + return; // Don't try to connect directly to the server + } #if HAS_NETWORKING - // Defaults - int serverPort = 1883; - const char *serverAddr = default_mqtt_address; - const char *mqttUsername = default_mqtt_username; - const char *mqttPassword = default_mqtt_password; + // Defaults + int serverPort = 1883; + const char *serverAddr = default_mqtt_address; + const char *mqttUsername = default_mqtt_username; + const char *mqttPassword = default_mqtt_password; - if (*moduleConfig.mqtt.address) - { - serverAddr = moduleConfig.mqtt.address; - mqttUsername = moduleConfig.mqtt.username; - mqttPassword = moduleConfig.mqtt.password; - } + if (*moduleConfig.mqtt.address) { + serverAddr = moduleConfig.mqtt.address; + mqttUsername = moduleConfig.mqtt.username; + mqttPassword = moduleConfig.mqtt.password; + } #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (moduleConfig.mqtt.tls_enabled) - { - // change default for encrypted to 8883 - try - { - serverPort = 8883; - wifiSecureClient.setInsecure(); + if (moduleConfig.mqtt.tls_enabled) { + // change default for encrypted to 8883 + try { + serverPort = 8883; + wifiSecureClient.setInsecure(); - pubSub.setClient(wifiSecureClient); - LOG_INFO("Using TLS-encrypted session\n"); - } - catch (const std::exception &e) - { - LOG_ERROR("MQTT ERROR: %s\n", e.what()); - } - } - else - { - LOG_INFO("Using non-TLS-encrypted session\n"); - pubSub.setClient(mqttClient); - } + pubSub.setClient(wifiSecureClient); + LOG_INFO("Using TLS-encrypted session\n"); + } catch (const std::exception &e) { + LOG_ERROR("MQTT ERROR: %s\n", e.what()); + } + } else { + LOG_INFO("Using non-TLS-encrypted session\n"); + pubSub.setClient(mqttClient); + } #elif HAS_NETWORKING - pubSub.setClient(mqttClient); + pubSub.setClient(mqttClient); #endif - String server = String(serverAddr); - int delimIndex = server.indexOf(':'); - if (delimIndex > 0) - { - String port = server.substring(delimIndex + 1, server.length()); - server[delimIndex] = 0; - serverPort = port.toInt(); - serverAddr = server.c_str(); - } - pubSub.setServer(serverAddr, serverPort); - pubSub.setBufferSize(512); + String server = String(serverAddr); + int delimIndex = server.indexOf(':'); + if (delimIndex > 0) { + String port = server.substring(delimIndex + 1, server.length()); + server[delimIndex] = 0; + serverPort = port.toInt(); + serverAddr = server.c_str(); + } + pubSub.setServer(serverAddr, serverPort); + pubSub.setBufferSize(512); - LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, - serverPort, mqttUsername, mqttPassword); + LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, + serverPort, mqttUsername, mqttPassword); - bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); - if (connected) - { - LOG_INFO("MQTT connected\n"); - enabled = true; // Start running background process again - runASAP = true; - reconnectCount = 0; + bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); + if (connected) { + LOG_INFO("MQTT connected\n"); + enabled = true; // Start running background process again + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - sendSubscriptions(); - } - else - { + publishNodeInfo(); + sendSubscriptions(); + } else { #if HAS_WIFI && !defined(ARCH_PORTDUINO) - reconnectCount++; - LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); - if (reconnectCount >= reconnectMax) - { - needReconnect = true; - wifiReconnect->setIntervalFromNow(0); - reconnectCount = 0; - } + reconnectCount++; + LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); + if (reconnectCount >= reconnectMax) { + needReconnect = true; + wifiReconnect->setIntervalFromNow(0); + reconnectCount = 0; + } #endif - } + } #endif - } + } } void MQTT::sendSubscriptions() { #if HAS_NETWORKING - bool hasDownlink = false; - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) - { - const auto &ch = channels.getByIndex(i); - if (ch.settings.downlink_enabled) - { - hasDownlink = true; - std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? + bool hasDownlink = false; + size_t numChan = channels.getNumChannels(); + for (size_t i = 0; i < numChan; i++) { + const auto &ch = channels.getByIndex(i); + if (ch.settings.downlink_enabled) { + hasDownlink = true; + std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? #if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### - if (moduleConfig.mqtt.json_enabled == true) - { - std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); - pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? - } + if (moduleConfig.mqtt.json_enabled == true) { + std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); + pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? + } #endif // ARCH_NRF52 NRF52_USE_JSON - } - } + } + } #if !MESHTASTIC_EXCLUDE_PKI - if (hasDownlink) - { - std::string topic = cryptTopic + "PKI/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); - } + if (hasDownlink) { + std::string topic = cryptTopic + "PKI/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); + } #endif #endif } bool MQTT::wantsLink() const { - bool hasChannelorMapReport = - moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); + bool hasChannelorMapReport = + moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); - if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) - return true; + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) + return true; #if HAS_WIFI - return hasChannelorMapReport && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif - return false; + return false; } int32_t MQTT::runOnce() { #if HAS_NETWORKING - if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) - return disable(); + if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) + return disable(); - bool wantConnection = wantsLink(); + bool wantConnection = wantsLink(); - perhapsReportToMap(); + perhapsReportToMap(); - // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - publishQueuedMessages(); - return 200; - } - else if (!pubSub.loop()) - { - if (!wantConnection) - return 5000; // If we don't want connection now, check again in 5 secs - else - { - reconnect(); - // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP - // connections are EXPENSIVE so try rarely) - if (isConnectedDirectly()) - { - publishQueuedMessages(); - return 200; - } - else - return 30000; - } - } - else - { - // we are connected to server, check often for new requests on the TCP port - if (!wantConnection) - { - LOG_INFO("MQTT link not needed, dropping\n"); - pubSub.disconnect(); - } + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server + if (moduleConfig.mqtt.proxy_to_client_enabled) { + publishQueuedMessages(); + return 200; + } - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) - return 20; - } + else if (!pubSub.loop()) { + if (!wantConnection) + return 5000; // If we don't want connection now, check again in 5 secs + else { + reconnect(); + // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP + // connections are EXPENSIVE so try rarely) + if (isConnectedDirectly()) { + publishQueuedMessages(); + return 200; + } else + return 30000; + } + } else { + // we are connected to server, check often for new requests on the TCP port + if (!wantConnection) { + LOG_INFO("MQTT link not needed, dropping\n"); + pubSub.disconnect(); + } + + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) + return 20; + } #endif - return 30000; + return 30000; } void MQTT::publishNodeInfo() { - // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) + // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) } void MQTT::publishQueuedMessages() { - if (!mqttQueue.isEmpty()) - { - LOG_DEBUG("Publishing enqueued MQTT message\n"); - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic; - if (env->packet->pki_encrypted) - { - topic = cryptTopic + "PKI/" + owner.id; - } - else - { - topic = cryptTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); + if (!mqttQueue.isEmpty()) { + LOG_DEBUG("Publishing enqueued MQTT message\n"); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic; + if (env->packet->pki_encrypted) { + topic = cryptTopic + "PKI/" + owner.id; + } else { + topic = cryptTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); #if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) - { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); - if (jsonString.length() != 0) - { - std::string topicJson; - if (env->packet->pki_encrypted) - { - topicJson = jsonTopic + "PKI/" + owner.id; - } - else - { - topicJson = jsonTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } + if (moduleConfig.mqtt.json_enabled) { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); + if (jsonString.length() != 0) { + std::string topicJson; + if (env->packet->pki_encrypted) { + topicJson = jsonTopic + "PKI/" + owner.id; + } else { + topicJson = jsonTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - mqttPool.release(env); - } + mqttPool.release(env); + } } void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex) { - if (mp.via_mqtt) - return; // Don't send messages that came from MQTT back into MQTT - bool uplinkEnabled = false; - for (int i = 0; i <= 7; i++) - { - if (channels.getByIndex(i).settings.uplink_enabled) - uplinkEnabled = true; - } - if (!uplinkEnabled) - return; // no channels have an uplink enabled - auto &ch = channels.getByIndex(chIndex); + if (mp.via_mqtt) + return; // Don't send messages that came from MQTT back into MQTT + bool uplinkEnabled = false; + for (int i = 0; i <= 7; i++) { + if (channels.getByIndex(i).settings.uplink_enabled) + uplinkEnabled = true; + } + if (!uplinkEnabled) + return; // no channels have an uplink enabled + auto &ch = channels.getByIndex(chIndex); - if (!mp.pki_encrypted) - { - if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) - { - LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); - return; - } + if (!mp.pki_encrypted) { + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } - // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && - !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && - (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || - (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) - { - LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); - return; - } + // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. + if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && + !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && + (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || + (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { + LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); + return; + } - if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) - { - LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); - return; - } - } - if (mp.pki_encrypted || ch.settings.uplink_enabled) - { - const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { + LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); + return; + } + } + if (mp.pki_encrypted || ch.settings.uplink_enabled) { + const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); - meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); - env->channel_id = (char *)channelId; - env->gateway_id = owner.id; + meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); + env->channel_id = (char *)channelId; + env->gateway_id = owner.id; - LOG_DEBUG("MQTT onSend - Publishing "); - if (moduleConfig.mqtt.encryption_enabled) - { - env->packet = (meshtastic_MeshPacket *)∓ - LOG_DEBUG("encrypted message\n"); - } - else if (mp_decoded.which_payload_variant == - meshtastic_MeshPacket_decoded_tag) - { // Don't upload a still-encrypted PKI packet - env->packet = (meshtastic_MeshPacket *)&mp_decoded; - LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); - } + LOG_DEBUG("MQTT onSend - Publishing "); + if (moduleConfig.mqtt.encryption_enabled) { + env->packet = (meshtastic_MeshPacket *)∓ + LOG_DEBUG("encrypted message\n"); + } else if (mp_decoded.which_payload_variant == + meshtastic_MeshPacket_decoded_tag) { // Don't upload a still-encrypted PKI packet + env->packet = (meshtastic_MeshPacket *)&mp_decoded; + LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); + } - if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) - { - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic = cryptTopic + channelId + "/" + owner.id; - LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); + if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic = cryptTopic + channelId + "/" + owner.id; + LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); #if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) - { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); - if (jsonString.length() != 0) - { - std::string topicJson = jsonTopic + channelId + "/" + owner.id; - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } + if (moduleConfig.mqtt.json_enabled) { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); + if (jsonString.length() != 0) { + std::string topicJson = jsonTopic + channelId + "/" + owner.id; + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - } - else - { - LOG_INFO("MQTT not connected, queueing packet\n"); - if (mqttQueue.numFree() == 0) - { - LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); - meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); - if (d) - mqttPool.release(d); - } - // make a copy of serviceEnvelope and queue it - meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); - assert(mqttQueue.enqueue(copied, 0)); - } - mqttPool.release(env); - } + } else { + LOG_INFO("MQTT not connected, queueing packet\n"); + if (mqttQueue.numFree() == 0) { + LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); + meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); + if (d) + mqttPool.release(d); + } + // make a copy of serviceEnvelope and queue it + meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); + assert(mqttQueue.enqueue(copied, 0)); + } + mqttPool.release(env); + } } void MQTT::perhapsReportToMap() { - if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) - return; + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; - if (millis() - last_report_to_map < map_publish_interval_msecs) - { - return; - } - else - { - if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) - { - last_report_to_map = millis(); - if (map_position_precision == 0) - LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); - if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) - LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); - return; - } + if (millis() - last_report_to_map < map_publish_interval_msecs) { + return; + } else { + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + last_report_to_map = millis(); + if (map_position_precision == 0) + LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) + LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); + return; + } - // Allocate ServiceEnvelope and fill it - meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); - se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id - se->gateway_id = owner.id; + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; - // Allocate MeshPacket and fill it - meshtastic_MeshPacket *mp = packetPool.allocZeroed(); - mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; - mp->from = nodeDB->getNodeNum(); - mp->to = NODENUM_BROADCAST; - mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB->getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; - // Fill MapReport message - meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; - memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); - memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); - mapReport.role = config.device.role; - mapReport.hw_model = owner.hw_model; - strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); - mapReport.region = config.lora.region; - mapReport.modem_preset = config.lora.modem_preset; - mapReport.has_default_channel = channels.hasDefaultChannel(); + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); - // Set position with precision (same as in PositionModule) - if (map_position_precision < 32 && map_position_precision > 0) - { - mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.latitude_i += (1 << (31 - map_position_precision)); - mapReport.longitude_i += (1 << (31 - map_position_precision)); - } - else - { - mapReport.latitude_i = localPosition.latitude_i; - mapReport.longitude_i = localPosition.longitude_i; - } - mapReport.altitude = localPosition.altitude; - mapReport.position_precision = map_position_precision; + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } else { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; - mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); + mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); - // Encode MapReport message and set it to MeshPacket in ServiceEnvelope - mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), - &meshtastic_MapReport_msg, &mapReport); - se->packet = mp; + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); - LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); - publish(mapTopic.c_str(), bytes, numBytes, false); + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); - // Release the allocated memory for ServiceEnvelope and MeshPacket - mqttPool.release(se); - packetPool.release(mp); + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); - // Update the last report time - last_report_to_map = millis(); - } + // Update the last report time + last_report_to_map = millis(); + } } bool MQTT::isValidJsonEnvelope(JSONObject &json) { - // if "sender" is provided, avoid processing packets we uplinked - return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && - (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number - (json.find("from") != json.end()) && json["from"]->IsNumber() && - (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us - (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type - (json.find("payload") != json.end()); // should have a payload + // if "sender" is provided, avoid processing packets we uplinked + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number + (json.find("from") != json.end()) && json["from"]->IsNumber() && + (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us + (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type + (json.find("payload") != json.end()); // should have a payload } diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 9ed2a0d6a..817583821 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -32,23 +32,23 @@ 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); + /** + * 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\n"); - fromNum.notify32(fromRadioNum); - } + LOG_INFO("BLE notify fromNum\n"); + fromNum.notify32(fromRadioNum); + } - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override - { - BLEConnection *connection = Bluefruit.Connection(connectionHandle); - return connection->connected(); - } + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override + { + BLEConnection *connection = Bluefruit.Connection(connectionHandle); + return connection->connected(); + } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; @@ -56,12 +56,12 @@ 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\n", central_name); + // 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\n", central_name); } /** * Callback invoked when a connection is dropped @@ -70,261 +70,248 @@ void onConnect(uint16_t conn_handle) */ void onDisconnect(uint16_t conn_handle, uint8_t reason) { - // FIXME - we currently assume only one active connection - LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); + // FIXME - we currently assume only one active connection + LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { - // Display the raw request packet - LOG_INFO("CCCD Updated: %u\n", cccd_value); - // Check the characteristic this CCCD update is associated with in case - // this handler is used for multiple CCCD records. + // Display the raw request packet + LOG_INFO("CCCD Updated: %u\n", 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\n"); - } - else - { - LOG_INFO("Notify/Indicate disabled\n"); - } - } + 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\n"); + } else { + LOG_INFO("Notify/Indicate disabled\n"); + } + } } 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 = 152.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, 244); // 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 + // 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 = 152.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, 244); // 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); + 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("Ignoring successor read\n"); - } - authorizeRead(conn_hdl); + 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("Ignoring successor read\n"); + } + authorizeRead(conn_hdl); } void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { - LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); + LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); + bluetoothPhoneAPI->handleToRadio(data, len); } 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(); + 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\n"); - uint8_t connection_num = Bluefruit.connected(); - if (connection_num) - { - for (uint8_t i = 0; i < connection_num; i++) - { - LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); - Bluefruit.disconnect(i); - } - delay(100); // wait for ondisconnect; - } - Bluefruit.Advertising.stop(); + // Shutdown bluetooth for minimum power draw + LOG_INFO("Disable NRF52 bluetooth\n"); + uint8_t connection_num = Bluefruit.connected(); + if (connection_num) { + for (uint8_t i = 0; i < connection_num; i++) { + LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); + Bluefruit.disconnect(i); + } + delay(100); // wait for ondisconnect; + } + 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("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); + // Setup Bluetooth + nrf52Bluetooth->setup(); + // Shutdown bluetooth for minimum power draw + Bluefruit.Advertising.stop(); + Bluefruit.setTxPower(-40); // Minimum power + LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } bool NRF52Bluetooth::isConnected() { - return Bluefruit.connected(connectionHandle); + return Bluefruit.connected(connectionHandle); } int NRF52Bluetooth::getRssi() { - return 0; // FIXME figure out where to source this + return 0; // FIXME figure out where to source this } void NRF52Bluetooth::setup() { - // Initialise the Bluefruit module - LOG_INFO("Initialize the Bluefruit nRF52 module\n"); - 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'\n", 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); + // Initialise the Bluefruit module + LOG_INFO("Initialize the Bluefruit nRF52 module\n"); + 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'\n", 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); #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("Configuring the Device Information Service\n"); - bledis.setModel(optstr(HW_VERSION)); - bledis.setFirmwareRev(optstr(APP_VERSION)); - bledis.begin(); - // Start the BLE Battery Service and set it to 100% - LOG_INFO("Configuring the Battery Service\n"); - blebas.begin(); - blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using - // BLEService and BLECharacteristic classes - LOG_INFO("Configuring the Mesh bluetooth service\n"); - setupMeshService(); - // Setup the advertising packet(s) - LOG_INFO("Setting up the advertising payload(s)\n"); - startAdv(); - LOG_INFO("Advertising\n"); + // Configure and Start the Device Information Service + LOG_INFO("Configuring the Device Information Service\n"); + bledis.setModel(optstr(HW_VERSION)); + bledis.setFirmwareRev(optstr(APP_VERSION)); + bledis.begin(); + // Start the BLE Battery Service and set it to 100% + LOG_INFO("Configuring the Battery Service\n"); + blebas.begin(); + blebas.write(0); // Unknown battery level for now + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + LOG_INFO("Configuring the Mesh bluetooth service\n"); + setupMeshService(); + // Setup the advertising packet(s) + LOG_INFO("Setting up the advertising payload(s)\n"); + startAdv(); + LOG_INFO("Advertising\n"); } void NRF52Bluetooth::resumeAdvertising() { - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // 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); + blebas.write(level); } void NRF52Bluetooth::clearBonds() { - LOG_INFO("Clearing bluetooth bonds!\n"); - bond_print_list(BLE_GAP_ROLE_PERIPH); - bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.Periph.clearBonds(); - Bluefruit.Central.clearBonds(); + LOG_INFO("Clearing bluetooth bonds!\n"); + 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\n"); + LOG_INFO("BLE connection secured\n"); } bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { - LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); #if !defined(MESHTASTIC_EXCLUDE_SCREEN) - screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void - { + 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; @@ -347,35 +334,34 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke 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->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; - } - } - LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); - return true; + if (match_request) { + uint32_t start_time = millis(); + while (millis() < start_time + 30000) { + if (!Bluefruit.connected(conn_handle)) + break; + } + } + LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); + return true; } void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { - if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) - LOG_INFO("BLE pairing success\n"); - else - LOG_INFO("BLE pairing failed\n"); - screen->endAlert(); + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + LOG_INFO("BLE pairing success\n"); + else + LOG_INFO("BLE pairing failed\n"); + 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); + 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 From 625254cf909a7e0137cefbff19c733541739a6fb Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:44:30 +0200 Subject: [PATCH 1092/1377] Support Seeed SenseCAP Indicator (#4279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * preliminary target environment * add debug tool * add screen definitions * screen definitions * remove rtc, debug build * correct rotation * Add real hwmodel * fix width * use IO expander ports * link to modified arduino-esp32 * added config_detail * rotate screen * remove touch INT * add delay to display log * color log and radiolib log * LoRa init * make trunk happy * add lovyanGFX patch lib for io expander * fix lib * fix display&touch function * touch driver I2C scan * remove delay * build for release * minor code cleanup * allow trunk to be happy --------- Co-authored-by: Ben Meadors Co-authored-by: Thomas Göttgens --- boards/seeed-sensecap-indicator.json | 42 ++++++ src/configuration.h | 6 + src/detect/ScanI2C.h | 2 + src/detect/ScanI2CTwoWire.cpp | 2 + src/graphics/Screen.cpp | 24 ++-- src/graphics/ScreenFonts.h | 4 +- src/graphics/TFTDisplay.cpp | 123 +++++++++++++++++- src/graphics/images.h | 4 +- src/main.cpp | 4 +- src/platform/esp32/architecture.h | 2 + .../seeed-sensecap-indicator/pins_arduino.h | 56 ++++++++ .../seeed-sensecap-indicator/platformio.ini | 28 ++++ variants/seeed-sensecap-indicator/variant.h | 64 +++++++++ 13 files changed, 340 insertions(+), 21 deletions(-) create mode 100644 boards/seeed-sensecap-indicator.json create mode 100644 variants/seeed-sensecap-indicator/pins_arduino.h create mode 100644 variants/seeed-sensecap-indicator/platformio.ini create mode 100644 variants/seeed-sensecap-indicator/variant.h diff --git a/boards/seeed-sensecap-indicator.json b/boards/seeed-sensecap-indicator.json new file mode 100644 index 000000000..3fc57126f --- /dev/null +++ b/boards/seeed-sensecap-indicator.json @@ -0,0 +1,42 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=0", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x1A86", "0x7523"]], + "mcu": "esp32s3", + "variant": "esp32s3r8" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino"], + "name": "Seeed Studio SenseCAP Indicator", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "require_upload_port": true, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "speed": 921600 + }, + "url": "https://www.seeedstudio.com/Indicator-for-Meshtastic.html", + "vendor": "Seeed Studio" +} diff --git a/src/configuration.h b/src/configuration.h index 4ab33ef2b..72420cc59 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -163,6 +163,7 @@ along with this program. If not, see . // ----------------------------------------------------------------------------- // IO Expander // ----------------------------------------------------------------------------- +#define TCA9535_ADDR 0x20 #define TCA9555_ADDR 0x26 // ----------------------------------------------------------------------------- @@ -172,6 +173,11 @@ along with this program. If not, see . #define GPS_THREAD_INTERVAL 200 #endif +// ----------------------------------------------------------------------------- +// Touchscreen +// ----------------------------------------------------------------------------- +#define FT6336U_ADDR 0x48 + // convert 24-bit color to 16-bit (56K) #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 638e8cd23..743de7a9a 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -42,6 +42,7 @@ class ScanI2C BMA423, BQ24295, LSM6DS3, + TCA9535, TCA9555, VEML7700, RCWL9620, @@ -53,6 +54,7 @@ class ScanI2C BMX160, DFROBOT_LARK, NAU7802, + FT6336U, STK8BAXX } DeviceType; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index ad5d9fe4c..48341034b 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -388,12 +388,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(BMX160_ADDR, BMX160, "BMX160 accelerometer found\n"); SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(TCA9535_ADDR, TCA9535, "TCA9535 I2C expander found\n"); SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n"); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n"); SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n"); SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found\n"); SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); + SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index da573bade..ff1254812 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -19,8 +19,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "../userPrefs.h" #include "Screen.h" +#include "../userPrefs.h" #include "PowerMon.h" #include "configuration.h" #if HAS_SCREEN @@ -1093,8 +1093,8 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat { char usersString[20]; snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ - defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ + defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x, y + 3, 8, 8, imgUser); #else @@ -1515,7 +1515,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS) +#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || defined(RAK14014) || \ + defined(HX8357_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -1707,7 +1708,8 @@ void Screen::setup() // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically // flip it. If you have a headache now, you're welcome. if (!config.display.flip_screen) { -#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ + defined(RAK14014) || defined(HX8357_CS) static_cast(dispdev)->flipScreenVertically(); #else dispdev->flipScreenVertically(); @@ -2420,8 +2422,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #ifdef ARCH_ESP32 if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ - defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ + defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -2432,8 +2434,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 imgQuestion); #endif } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ - defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ + defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -2447,8 +2449,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #endif } else { // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ - defined(HX8357_CS) || ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ + defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 267f6e0a8..34c832635 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -12,8 +12,8 @@ #include "graphics/fonts/OLEDDisplayFontsUA.h" #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ - defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ + defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL ArialMT_Plain_16 // Height: 19 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 2849dd9a9..c0326abec 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -500,10 +500,126 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; +#elif defined(ST7701_CS) +#include // Graphics and font library for ST7701 driver chip +#include +#include + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_ST7701 _panel_instance; + lgfx::Bus_RGB _bus_instance; + lgfx::Light_PWM _light_instance; + lgfx::Touch_FT5x06 _touch_instance; + + public: + LGFX(void) + { + { + auto cfg = _panel_instance.config(); + cfg.memory_width = 800; + cfg.memory_height = 480; + cfg.panel_width = TFT_WIDTH; + cfg.panel_height = TFT_HEIGHT; + cfg.offset_x = TFT_OFFSET_X; + cfg.offset_y = TFT_OFFSET_Y; + _panel_instance.config(cfg); + } + + { + auto cfg = _panel_instance.config_detail(); + cfg.pin_cs = ST7701_CS; + cfg.pin_sclk = ST7701_SCK; + cfg.pin_mosi = ST7701_SDA; + // cfg.use_psram = 1; + _panel_instance.config_detail(cfg); + } + + { + auto cfg = _bus_instance.config(); + cfg.panel = &_panel_instance; +#ifdef SENSECAP_INDICATOR + cfg.pin_d0 = GPIO_NUM_15; // B0 + cfg.pin_d1 = GPIO_NUM_14; // B1 + cfg.pin_d2 = GPIO_NUM_13; // B2 + cfg.pin_d3 = GPIO_NUM_12; // B3 + cfg.pin_d4 = GPIO_NUM_11; // B4 + + cfg.pin_d5 = GPIO_NUM_10; // G0 + cfg.pin_d6 = GPIO_NUM_9; // G1 + cfg.pin_d7 = GPIO_NUM_8; // G2 + cfg.pin_d8 = GPIO_NUM_7; // G3 + cfg.pin_d9 = GPIO_NUM_6; // G4 + cfg.pin_d10 = GPIO_NUM_5; // G5 + + cfg.pin_d11 = GPIO_NUM_4; // R0 + cfg.pin_d12 = GPIO_NUM_3; // R1 + cfg.pin_d13 = GPIO_NUM_2; // R2 + cfg.pin_d14 = GPIO_NUM_1; // R3 + cfg.pin_d15 = GPIO_NUM_0; // R4 + + cfg.pin_henable = GPIO_NUM_18; + cfg.pin_vsync = GPIO_NUM_17; + cfg.pin_hsync = GPIO_NUM_16; + cfg.pin_pclk = GPIO_NUM_21; + cfg.freq_write = 12000000; + + cfg.hsync_polarity = 0; + cfg.hsync_front_porch = 10; + cfg.hsync_pulse_width = 8; + cfg.hsync_back_porch = 50; + + cfg.vsync_polarity = 0; + cfg.vsync_front_porch = 10; + cfg.vsync_pulse_width = 8; + cfg.vsync_back_porch = 20; + + cfg.pclk_active_neg = 0; + cfg.de_idle_high = 1; + cfg.pclk_idle_high = 0; +#endif + _bus_instance.config(cfg); + } + _panel_instance.setBus(&_bus_instance); + + { + auto cfg = _light_instance.config(); + cfg.pin_bl = ST7701_BL; + _light_instance.config(cfg); + } + _panel_instance.light(&_light_instance); + + { + auto cfg = _touch_instance.config(); + cfg.pin_cs = -1; + cfg.x_min = 0; + cfg.x_max = 479; + cfg.y_min = 0; + cfg.y_max = 479; + cfg.pin_int = -1; // don't use SCREEN_TOUCH_INT; + cfg.pin_rst = SCREEN_TOUCH_RST; + cfg.bus_shared = true; + cfg.offset_rotation = TFT_OFFSET_ROTATION; + + cfg.i2c_port = TOUCH_I2C_PORT; + cfg.i2c_addr = TOUCH_SLAVE_ADDRESS; + cfg.pin_sda = I2C_SDA; + cfg.pin_scl = I2C_SCL; + cfg.freq = 400000; + _touch_instance.config(cfg); + _panel_instance.setTouch(&_touch_instance); + } + + setPanel(&_panel_instance); + } +}; + +static LGFX *tft = nullptr; + #endif -#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || \ - (ARCH_PORTDUINO && HAS_SCREEN != 0) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || \ + defined(HX8357_CS) || (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" #include @@ -709,7 +825,6 @@ bool TFTDisplay::connect() #ifdef UNPHONE unphone.backlight(true); // using unPhone library - LOG_INFO("Power to TFT Backlight\n"); #endif tft->init(); @@ -725,7 +840,7 @@ bool TFTDisplay::connect() attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING); #elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2) tft->setRotation(1); // T-Deck has the TFT in landscape -#elif defined(T_WATCH_S3) +#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR) tft->setRotation(2); // T-Watch S3 left-handed orientation #else tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label diff --git a/src/graphics/images.h b/src/graphics/images.h index ab3767a89..7028f18e3 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -20,8 +20,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \ - defined(HX8357_CS) || ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ + defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/main.cpp b/src/main.cpp index 83758d5ee..1401f4f0b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -745,8 +745,8 @@ void setup() #if !MESHTASTIC_EXCLUDE_I2C // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ - defined(USE_ST7789) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || \ + defined(HX8357_CS) || defined(USE_ST7789) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index f86b342ce..93630aa8a 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -166,6 +166,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_VISION_MASTER_E290 #elif defined(HELTEC_MESH_NODE_T114) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 +#elif defined(SENSECAP_INDICATOR) +#define HW_VENDOR meshtastic_HardwareModel_SENSECAP_INDICATOR #endif // ----------------------------------------------------------------------------- diff --git a/variants/seeed-sensecap-indicator/pins_arduino.h b/variants/seeed-sensecap-indicator/pins_arduino.h new file mode 100644 index 000000000..300f0e0f5 --- /dev/null +++ b/variants/seeed-sensecap-indicator/pins_arduino.h @@ -0,0 +1,56 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +// static const uint8_t LED_BUILTIN = -1; + +// static const uint8_t TX = 43; +// static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 40; + +// Default SPI will be mapped to Radio +static const uint8_t SS = -1; +static const uint8_t MOSI = 48; +static const uint8_t MISO = 47; +static const uint8_t SCK = 41; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/seeed-sensecap-indicator/platformio.ini b/variants/seeed-sensecap-indicator/platformio.ini new file mode 100644 index 000000000..e6bb2145e --- /dev/null +++ b/variants/seeed-sensecap-indicator/platformio.ini @@ -0,0 +1,28 @@ +; Seeed Studio SenseCAP Indicator +[env:seeed-sensecap-indicator] +extends = esp32s3_base +platform_packages = + platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32.git#add_tca9535 ; based on 2.0.16 + +board = seeed-sensecap-indicator +board_check = true +upload_protocol = esptool + +build_flags = ${esp32_base.build_flags} + -Ivariants/seeed-sensecap-indicator + -DSENSECAP_INDICATOR + -DCONFIG_ARDUHAL_LOG_COLORS + -DRADIOLIB_DEBUG_SPI=0 + -DRADIOLIB_DEBUG_PROTOCOL=0 + -DRADIOLIB_DEBUG_BASIC=0 + -DRADIOLIB_VERBOSE_ASSERT=0 + -DRADIOLIB_SPI_PARANOID=0 + -DIO_EXPANDER=0x40 + -DIO_EXPANDER_IRQ=42 + ;-DIO_EXPANDER_DEBUG + -DUSE_ARDUINO_HAL_GPIO + +lib_deps = ${esp32s3_base.lib_deps} + https://github.com/mverch67/LovyanGFX#develop + earlephilhower/ESP8266Audio@^1.9.7 + earlephilhower/ESP8266SAM@^1.0.1 \ No newline at end of file diff --git a/variants/seeed-sensecap-indicator/variant.h b/variants/seeed-sensecap-indicator/variant.h new file mode 100644 index 000000000..d7ed329eb --- /dev/null +++ b/variants/seeed-sensecap-indicator/variant.h @@ -0,0 +1,64 @@ +#define I2C_SDA 39 +#define I2C_SCL 40 + +#define BUTTON_PIN 38 +// #define BUTTON_NEED_PULLUP + +// #define BATTERY_PIN 27 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +// #define ADC_CHANNEL ADC1_GPIO27_CHANNEL +// #define ADC_MULTIPLIER 2 + +// ST7701 TFT LCD +#define ST7701_CS (4 | IO_EXPANDER) +#define ST7701_RS -1 // DC +#define ST7701_SDA 48 // MOSI +#define ST7701_SCK 41 +#define ST7701_RESET (5 | IO_EXPANDER) +#define ST7701_MISO 47 +#define ST7701_BUSY -1 +#define ST7701_BL 45 +#define ST7701_SPI_HOST SPI2_HOST +#define ST7701_BACKLIGHT_EN 45 +#define SPI_FREQUENCY 20000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 480 +#define TFT_WIDTH 480 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 +#define TFT_BL 45 +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 // fps + +#define HAS_TOUCHSCREEN 1 +#define SCREEN_TOUCH_INT (6 | IO_EXPANDER) +#define SCREEN_TOUCH_RST (7 | IO_EXPANDER) +#define TOUCH_I2C_PORT 0 +#define TOUCH_SLAVE_ADDRESS 0x48 + +// Buzzer +#define PIN_BUZZER 19 + +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define USE_SX1262 +#define USE_SX1268 + +#define LORA_SCK 41 +#define LORA_MISO 47 +#define LORA_MOSI 48 +#define LORA_CS (0 | IO_EXPANDER) + +#define LORA_DIO0 -1 // a no connect on the SX1262 module +#define LORA_RESET (1 | IO_EXPANDER) +#define LORA_DIO1 (3 | IO_EXPANDER) // SX1262 IRQ +#define LORA_DIO2 (2 | IO_EXPANDER) // SX1262 BUSY +#define LORA_DIO3 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH From 9f3a1c121404960bdf917082d5ba68712101a4c7 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Thu, 12 Sep 2024 19:12:57 +0200 Subject: [PATCH 1093/1377] Trunk fmt --- src/AccelerometerThread.h | 2 +- src/mqtt/MQTT.cpp | 11 +- src/platform/nrf52/NRF52Bluetooth.cpp | 12 +- .../MeshPacketSerializer_nRF52.cpp | 694 ++++++++---------- 4 files changed, 329 insertions(+), 390 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 37e7aab0d..7c133d9ab 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -95,7 +95,7 @@ class AccelerometerThread : public concurrency::OSThread return 500; } #if defined(RAK_4631) -#if !defined (MESHTASTIC_EXCLUDE_SCREEN) +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a4921e684..fa8e2745c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -378,8 +378,9 @@ void MQTT::sendSubscriptions() hasDownlink = true; std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### + pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#if !defined(ARCH_NRF52) || \ + defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); @@ -480,7 +481,8 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || \ + defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); @@ -562,7 +564,8 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || \ + defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 817583821..2e9dbc1f8 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -19,7 +19,7 @@ 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 @@ -114,8 +114,8 @@ void startAdv(void) */ Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); // 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 + 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) @@ -172,7 +172,7 @@ void setupMeshService(void) 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 + 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(); @@ -262,7 +262,7 @@ void NRF52Bluetooth::setup() 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.begin(); // Install the DFU helper #endif // Configure and Start the Device Information Service LOG_INFO("Configuring the Device Information Service\n"); @@ -286,7 +286,7 @@ void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } /// Given a level between 0-100, update the BLE attribute diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 8c58fba27..cd3aa1630 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -1,417 +1,353 @@ #ifdef NRF52_USE_JSON #warning 'Using nRF52 Serializer' -#include "MeshPacketSerializer.h" #include "ArduinoJson.h" +#include "MeshPacketSerializer.h" #include "NodeDB.h" #include "mesh/generated/meshtastic/mqtt.pb.h" +#include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "mesh/generated/meshtastic/telemetry.pb.h" #include "modules/RoutingModule.h" #include #include -#include "mesh/generated/meshtastic/remote_hardware.pb.h" StaticJsonDocument<1024> jsonObj; StaticJsonDocument<1024> arrayObj; std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { - // the created jsonObj is immutable after creation, so - // we need to do the heavy lifting before assembling it. - std::string msgType; - jsonObj.clear(); - arrayObj.clear(); + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; + jsonObj.clear(); + arrayObj.clear(); - if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) - { - switch (mp->decoded.portnum) - { - case meshtastic_PortNum_TEXT_MESSAGE_APP: - { - msgType = "text"; - // convert bytes to string - if (shouldLog) - LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + switch (mp->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - // check if this is a JSON payload - StaticJsonDocument<512> text_doc; - DeserializationError error = deserializeJson(text_doc, payloadStr); - if (error) - { - // if it isn't, then we need to create a json object - // with the string as the value - if (shouldLog) - LOG_INFO("text message payload is of type plaintext\n"); - jsonObj["payload"]["text"] = payloadStr; - } - else - { - // if it is, then we can just use the json object - if (shouldLog) - LOG_INFO("text message payload is of type json\n"); - jsonObj["payload"] = text_doc; - } - break; - } - case meshtastic_PortNum_TELEMETRY_APP: - { - msgType = "telemetry"; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) - { - decoded = &scratch; - if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) - { - jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; - jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; - jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; - jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; - jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; - } - else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) - { - jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; - jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; - jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; - jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; - jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; - jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; - jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; - jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; - jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; - jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; - jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; - jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; - jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; - } - else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) - { - jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; - jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; - jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; - jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; - jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; - jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; - } - else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) - { - jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; - jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; - jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; - jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; - jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; - jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; - } - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for telemetry message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NODEINFO_APP: - { - msgType = "nodeinfo"; - meshtastic_User scratch; - meshtastic_User *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) - { - decoded = &scratch; - jsonObj["payload"]["id"] = decoded->id; - jsonObj["payload"]["longname"] = decoded->long_name; - jsonObj["payload"]["shortname"] = decoded->short_name; - jsonObj["payload"]["hardware"] = decoded->hw_model; - jsonObj["payload"]["role"] = (int)decoded->role; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_POSITION_APP: - { - msgType = "position"; - meshtastic_Position scratch; - meshtastic_Position *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) - { - decoded = &scratch; - if ((int)decoded->time) - { - jsonObj["payload"]["time"] = (unsigned int)decoded->time; - } - if ((int)decoded->timestamp) - { - jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; - } - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - if ((int)decoded->altitude) - { - jsonObj["payload"]["altitude"] = (int)decoded->altitude; - } - if ((int)decoded->ground_speed) - { - jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; - } - if (int(decoded->ground_track)) - { - jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; - } - if (int(decoded->sats_in_view)) - { - jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; - } - if ((int)decoded->PDOP) - { - jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; - } - if ((int)decoded->HDOP) - { - jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; - } - if ((int)decoded->VDOP) - { - jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; - } - if ((int)decoded->precision_bits) - { - jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; - } - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_WAYPOINT_APP: - { - msgType = "position"; - meshtastic_Waypoint scratch; - meshtastic_Waypoint *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) - { - decoded = &scratch; - jsonObj["payload"]["id"] = (unsigned int)decoded->id; - jsonObj["payload"]["name"] = decoded->name; - jsonObj["payload"]["description"] = decoded->description; - jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; - jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NEIGHBORINFO_APP: - { - msgType = "neighborinfo"; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, - &scratch)) - { - decoded = &scratch; - jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; - jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; - jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; - jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload + StaticJsonDocument<512> text_doc; + DeserializationError error = deserializeJson(text_doc, payloadStr); + if (error) { + // if it isn't, then we need to create a json object + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); + jsonObj["payload"]["text"] = payloadStr; + } else { + // if it is, then we can just use the json object + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); + jsonObj["payload"] = text_doc; + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; + jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; + jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; + jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; + jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; + } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; + jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; + jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; + jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; + jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; + jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; + jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; + jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; + jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; + jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; + jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; + jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; + jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; + } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; + jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; + jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; + jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; + jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; + jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; + jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; + jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; + jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = decoded->id; + jsonObj["payload"]["longname"] = decoded->long_name; + jsonObj["payload"]["shortname"] = decoded->short_name; + jsonObj["payload"]["hardware"] = decoded->hw_model; + jsonObj["payload"]["role"] = (int)decoded->role; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_POSITION_APP: { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { + decoded = &scratch; + if ((int)decoded->time) { + jsonObj["payload"]["time"] = (unsigned int)decoded->time; + } + if ((int)decoded->timestamp) { + jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; + } + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + if ((int)decoded->altitude) { + jsonObj["payload"]["altitude"] = (int)decoded->altitude; + } + if ((int)decoded->ground_speed) { + jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; + } + if (int(decoded->ground_track)) { + jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; + } + if (int(decoded->sats_in_view)) { + jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; + } + if ((int)decoded->PDOP) { + jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; + } + if ((int)decoded->HDOP) { + jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; + } + if ((int)decoded->VDOP) { + jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; + } + if ((int)decoded->precision_bits) { + jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = (unsigned int)decoded->id; + jsonObj["payload"]["name"] = decoded->name; + jsonObj["payload"]["description"] = decoded->description; + jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; + jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) { + decoded = &scratch; + jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; + jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; + jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; + jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; - JsonObject neighbors_obj = arrayObj.to(); - JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); - JsonObject neighbors_0 = neighbors.createNestedObject(); + JsonObject neighbors_obj = arrayObj.to(); + JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); + JsonObject neighbors_0 = neighbors.createNestedObject(); - for (uint8_t i = 0; i < decoded->neighbors_count; i++) - { - neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; - neighbors_0["snr"] = (int)decoded->neighbors[i].snr; - neighbors[i + 1] = neighbors_0; - neighbors_0.clear(); - } - neighbors.remove(0); - jsonObj["payload"]["neighbors"] = neighbors; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_TRACEROUTE_APP: - { - if (mp->decoded.request_id) - { // Only report the traceroute response - msgType = "traceroute"; - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, - &scratch)) - { - decoded = &scratch; - JsonArray route = arrayObj.createNestedArray("route"); + for (uint8_t i = 0; i < decoded->neighbors_count; i++) { + neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; + neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors[i + 1] = neighbors_0; + neighbors_0.clear(); + } + neighbors.remove(0); + jsonObj["payload"]["neighbors"] = neighbors; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: { + if (mp->decoded.request_id) { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) { + decoded = &scratch; + JsonArray route = arrayObj.createNestedArray("route"); - auto addToRoute = [](JsonArray *route, NodeNum num) - { - char long_name[40] = "Unknown"; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); - bool name_known = node ? node->has_user : false; - if (name_known) - memcpy(long_name, node->user.long_name, sizeof(long_name)); - route->add(long_name); - }; + auto addToRoute = [](JsonArray *route, NodeNum num) { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->add(long_name); + }; - addToRoute(&route, mp->to); // route.add(mp->to); - for (uint8_t i = 0; i < decoded->route_count; i++) - { - addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]); - } - addToRoute(&route, mp->from); // route.add(mp->from); // Ended at the original destination (source of response) + addToRoute(&route, mp->to); // route.add(mp->to); + for (uint8_t i = 0; i < decoded->route_count; i++) { + addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]); + } + addToRoute(&route, + mp->from); // route.add(mp->from); // Ended at the original destination (source of response) - jsonObj["payload"]["route"] = route; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for traceroute message!\n"); - return ""; - } - } - else - { - LOG_WARN("Traceroute response not reported"); - return ""; - } - break; - } - case meshtastic_PortNum_DETECTION_SENSOR_APP: - { - msgType = "detection"; - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - jsonObj["payload"]["text"] = payloadStr; - break; - } - case meshtastic_PortNum_REMOTE_HARDWARE_APP: - { - meshtastic_HardwareMessage scratch; - meshtastic_HardwareMessage *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, - &scratch)) - { - decoded = &scratch; - if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) - { - msgType = "gpios_changed"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - } - else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) - { - msgType = "gpios_read_reply"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; - } - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); - return ""; - } - break; - } - // add more packet types here if needed - default: - LOG_WARN("Unsupported packet type %d\n", mp->decoded.portnum); - return ""; - break; - } - } - else if (shouldLog) - { - LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); - return ""; - } + jsonObj["payload"]["route"] = route; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + return ""; + } + } else { + LOG_WARN("Traceroute response not reported"); + return ""; + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + jsonObj["payload"]["text"] = payloadStr; + break; + } + case meshtastic_PortNum_REMOTE_HARDWARE_APP: { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { + msgType = "gpios_changed"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { + msgType = "gpios_read_reply"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + return ""; + } + break; + } + // add more packet types here if needed + default: + LOG_WARN("Unsupported packet type %d\n", mp->decoded.portnum); + return ""; + break; + } + } else if (shouldLog) { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + return ""; + } - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["type"] = msgType.c_str(); - jsonObj["sender"] = owner.id; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) - { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["type"] = msgType.c_str(); + jsonObj["sender"] = owner.id; + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } - // serialize and write it to the stream + // serialize and write it to the stream - // Serial.printf("serialized json message: \r\n"); - // serializeJson(jsonObj, Serial); - // Serial.println(""); + // Serial.printf("serialized json message: \r\n"); + // serializeJson(jsonObj, Serial); + // Serial.println(""); - std::string jsonStr = ""; - serializeJson(jsonObj, jsonStr); + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); - if (shouldLog) - LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); - return jsonStr; + return jsonStr; } std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) { - jsonObj.clear(); - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["time_ms"] = (double)millis(); - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["want_ack"] = mp->want_ack; + jsonObj.clear(); + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["time_ms"] = (double)millis(); + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["want_ack"] = mp->want_ack; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) - { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } - jsonObj["size"] = (unsigned int)mp->encrypted.size; - auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); - jsonObj["bytes"] = encryptedStr.c_str(); + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + jsonObj["size"] = (unsigned int)mp->encrypted.size; + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = encryptedStr.c_str(); - // serialize and write it to the stream - std::string jsonStr = ""; - serializeJson(jsonObj, jsonStr); + // serialize and write it to the stream + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); - return jsonStr; + return jsonStr; } #endif \ No newline at end of file From 3d72fbb19e524bf87d4851b7b4134130ab09aa5c Mon Sep 17 00:00:00 2001 From: Vertex <5567402+RealityAnomaly@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:20:38 +0100 Subject: [PATCH 1094/1377] Define SX126X_ANT_SW for the RAK11200 to allow it to function correctly on the RAK19007 base (#4690) --- variants/rak11200/variant.h | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak11200/variant.h b/variants/rak11200/variant.h index 3cd601254..259fa6e87 100644 --- a/variants/rak11200/variant.h +++ b/variants/rak11200/variant.h @@ -70,6 +70,7 @@ static const uint8_t SCK = 33; #define LORA_CS SS #define USE_SX1262 +#define SX126X_ANT_SW WB_IO2 #define SX126X_CS SS // NSS for SX126X #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 From cd480846e93efc2d06da8f84a5028082bbd093ac Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Thu, 12 Sep 2024 19:52:36 +0200 Subject: [PATCH 1095/1377] Remove accelerometer lib --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1847bd113..167d8710b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -161,5 +161,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file From 9527874815ca98427d6a1d114b65095e64ff60f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 12 Sep 2024 22:42:10 +0200 Subject: [PATCH 1096/1377] First stab at ESP32-C6 support for TLora-C6 --- arch/esp32/esp32c6.ini | 47 ++++++++++++++++++++++++ src/gps/GPS.cpp | 5 ++- src/graphics/Screen.cpp | 28 +++++++------- src/main.cpp | 14 ++++--- src/mesh/NodeDB.cpp | 2 +- src/mesh/TypedQueue.h | 2 +- src/mesh/api/ServerAPI.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 3 +- src/modules/ExternalNotificationModule.h | 2 +- src/modules/PositionModule.cpp | 16 ++++---- src/modules/SerialModule.cpp | 21 ++++++++++- src/mqtt/MQTT.cpp | 4 ++ src/mqtt/MQTT.h | 4 ++ src/platform/esp32/main-esp32.cpp | 12 +++++- src/power.h | 1 + src/serialization/JSON.cpp | 2 +- src/sleep.cpp | 4 +- variants/tlora_c6/platformio.ini | 5 +++ variants/tlora_c6/variant.h | 16 ++++++++ 19 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 arch/esp32/esp32c6.ini create mode 100644 variants/tlora_c6/platformio.ini create mode 100644 variants/tlora_c6/variant.h diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini new file mode 100644 index 000000000..a7bcdc0e8 --- /dev/null +++ b/arch/esp32/esp32c6.ini @@ -0,0 +1,47 @@ +[esp32c6_base] +extends = esp32_base +platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 +build_flags = + ${arduino_base.build_flags} + -Wall + -Wextra + -Isrc/platform/esp32 + -std=c++11 + -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL + -DAXP_DEBUG_PORT=Serial + -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DSERIAL_BUFFER_SIZE=4096 + -DLIBPAX_ARDUINO + -DLIBPAX_WIFI + -DLIBPAX_BLE + -DMESHTASTIC_EXCLUDE_WEBSERVER + ;-DDEBUG_HEAP + ; TEMP + -DHAS_BLUETOOTH=0 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER + -DMESHTASTIC_EXCLUDE_BLUETOOTH + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${environmental_base.lib_deps} + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f + rweather/Crypto@^0.4.0 + +build_src_filter = + ${esp32_base.build_src_filter} - + +monitor_speed = 115200 +monitor_filters = esp32_c3_exception_decoder + +lib_ignore = + NonBlockingRTTTL + NimBLE-Arduino + libpax + \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 046f277ff..145d8b941 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -58,7 +58,8 @@ const char *getGPSPowerStateString(GPSPowerState state) case GPS_OFF: return "OFF"; default: - assert(false); // Unhandled enum value.. + assert(false); // Unhandled enum value.. + return "FALSE"; // to make new ESP-IDF happy } } @@ -328,7 +329,7 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t { uint16_t ubxFrameCounter = 0; uint32_t startTime = millis(); - uint16_t needRead; + uint16_t needRead = 0; while (millis() - startTime < waitMillis) { if (_serial_gps->available()) { diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ff1254812..2de5e3f0a 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1004,55 +1004,55 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->setColor(WHITE); #ifndef EXCLUDE_EMOJI - if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) { + if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbup); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44E") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbdown); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"❓") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "❓") == 0) { display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height, question); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"‼️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "‼️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5, bang_width, bang_height, bang); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F4A9") == 0) { display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5, poo_width, poo_height, poo); } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\xa4\xa3") == 0) { display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5, haha_width, haha_height, haha); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44B") == 0) { display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width, wave_icon_height, wave_icon); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F920") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F920") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height, cowboy); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F42D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height, deadmau5); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xE2\x98\x80\xEF\xB8\x8F") == 0) { display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5, sun_width, sun_height, sun); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\u2614") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\u2614") == 0) { display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10, rain_width, rain_height, rain); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"☁️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "☁️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"🌫️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "🌫️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5, fog_width, fog_height, fog); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\x98\x88") == 0) { display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"♥️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "♥️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart); } else { diff --git a/src/main.cpp b/src/main.cpp index 1401f4f0b..cbbe824e4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -322,15 +322,19 @@ void setup() #ifdef BUTTON_PIN #ifdef ARCH_ESP32 - // If the button is connected to GPIO 12, don't enable the ability to use - // meshtasticAdmin on the device. - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); - +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN #ifdef BUTTON_NEED_PULLUP gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); delay(10); #endif - +#endif #endif #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5d1db88ae..33fe5a090 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -49,7 +49,7 @@ NodeDB *nodeDB = nullptr; // we have plenty of ram so statically alloc this tempbuf (for now) -EXT_RAM_ATTR meshtastic_DeviceState devicestate; +EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate; meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node; meshtastic_LocalConfig config; meshtastic_LocalModuleConfig moduleConfig; diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index c96edae8e..f7d016f10 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -14,7 +14,7 @@ */ template class TypedQueue { - static_assert(std::is_pod::value, "T must be pod"); + static_assert(std::is_standard_layout::value, "T must be standard layout"); QueueHandle_t h; concurrency::OSThread *reader = NULL; diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 3a483aac1..097f4fa21 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -45,7 +45,7 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { - auto client = U::available(); + auto client = U::accept(); if (client) { // Close any previous connection (see FIXME in header file) if (openAPI) { diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 07b03222e..e3203e6f7 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -309,7 +309,8 @@ static void WiFiEvent(WiFiEvent_t event) onNetworkConnected(); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - LOG_INFO("Obtained IP6 address: %s\n", WiFi.localIPv6().toString().c_str()); + LOG_INFO("Obtained Local IP6 address: %s\n", WiFi.linkLocalIPv6().toString().c_str()); + LOG_INFO("Obtained GlobalIP6 address: %s\n", WiFi.globalIPv6().toString().c_str()); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0\n"); diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 08e72c35a..a5dff3651 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -3,7 +3,7 @@ #include "SinglePortModule.h" #include "concurrency/OSThread.h" #include "configuration.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else // Noop class for portduino. diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index cb6a58b2e..2a962c268 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -273,7 +273,7 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() meshtastic_TAKPacket takPacket = {.is_compressed = true, .has_contact = true, - .contact = {0}, + .contact = meshtastic_Contact_init_default, .has_group = true, .group = {meshtastic_MemberRole_TeamMember, meshtastic_Team_Cyan}, .has_status = true, @@ -282,13 +282,13 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() .battery = powerStatus->getBatteryChargePercent(), }, .which_payload_variant = meshtastic_TAKPacket_pli_tag, - {.pli = { - .latitude_i = localPosition.latitude_i, - .longitude_i = localPosition.longitude_i, - .altitude = localPosition.altitude_hae, - .speed = localPosition.ground_speed, - .course = static_cast(localPosition.ground_track), - }}}; + .payload_variant = {.pli = { + .latitude_i = localPosition.latitude_i, + .longitude_i = localPosition.longitude_i, + .altitude = localPosition.altitude_hae, + .speed = localPosition.ground_speed, + .course = static_cast(localPosition.ground_track), + }}}; auto length = unishox2_compress_lines(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign, sizeof(takPacket.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f0ba64f65..549fcf1d7 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -62,6 +62,9 @@ SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial; +#elif defined(CONFIG_IDF_TARGET_ESP32C6) +SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("SerialModule") {} +static Print *serialPrint = &Serial1; #else SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial2; @@ -137,7 +140,16 @@ int32_t SerialModule::runOnce() // Give it a chance to flush out 💩 delay(10); } -#ifdef ARCH_ESP32 +#if defined(CONFIG_IDF_TARGET_ESP32C6) + if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { + Serial1.setRxBufferSize(RX_BUFFER); + Serial1.begin(baud, SERIAL_8N1, moduleConfig.serial.rxd, moduleConfig.serial.txd); + } else { + Serial.begin(baud); + Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); + } + +#elif defined(ARCH_ESP32) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { Serial2.setRxBufferSize(RX_BUFFER); @@ -205,8 +217,13 @@ int32_t SerialModule::runOnce() processWXSerial(); } else { +#if defined(CONFIG_IDF_TARGET_ESP32C6) + while (Serial1.available()) { + serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#else while (Serial2.available()) { serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#endif serialModuleRadio->sendPayload(); } } @@ -392,7 +409,7 @@ uint32_t SerialModule::getBaudRate() */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index d14c7a923..c59661a62 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -310,6 +310,7 @@ void MQTT::reconnect() mqttPassword = moduleConfig.mqtt.password; } #if HAS_WIFI && !defined(ARCH_PORTDUINO) +#ifndef CONFIG_IDF_TARGET_ESP32C6 if (moduleConfig.mqtt.tls_enabled) { // change default for encrypted to 8883 try { @@ -325,6 +326,9 @@ void MQTT::reconnect() LOG_INFO("Using non-TLS-encrypted session\n"); pubSub.setClient(mqttClient); } +#else + pubSub.setClient(mqttClient); +#endif #elif HAS_NETWORKING pubSub.setClient(mqttClient); #endif diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index ba0987783..c827e12ca 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -9,9 +9,11 @@ #if HAS_WIFI #include #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 #include #endif #endif +#endif #if HAS_ETHERNET #include #endif @@ -33,9 +35,11 @@ class MQTT : private concurrency::OSThread #if HAS_WIFI WiFiClient mqttClient; #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 WiFiClientSecure wifiSecureClient; #endif #endif +#endif #if HAS_ETHERNET EthernetClient mqttClient; #endif diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 1b997500a..12a6b5f74 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -13,6 +13,7 @@ #include "mesh/wifi/WiFiAPClient.h" #endif +#include "esp_mac.h" #include "meshUtils.h" #include "sleep.h" #include "soc/rtc.h" @@ -140,9 +141,16 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 +#ifdef CONFIG_IDF_TARGET_ESP32C6 + esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); + wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; + wdt_config->trigger_panic = true; + res = esp_task_wdt_init(wdt_config); + assert(res == ESP_OK); +#else res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); assert(res == ESP_OK); - +#endif res = esp_task_wdt_add(NULL); assert(res == ESP_OK); @@ -216,7 +224,7 @@ void cpuDeepSleep(uint32_t msecToWake) // to detect wake and in normal operation the external part drives them hard. #ifdef BUTTON_PIN // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. -#if SOC_RTCIO_HOLD_SUPPORTED +#if SOC_RTCIO_HOLD_SUPPORTED && SOC_PM_SUPPORT_EXT_WAKEUP uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif diff --git a/src/power.h b/src/power.h index a4307ee07..5a41b55f2 100644 --- a/src/power.h +++ b/src/power.h @@ -5,6 +5,7 @@ #include "configuration.h" #ifdef ARCH_ESP32 +// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h #include #include #endif diff --git a/src/serialization/JSON.cpp b/src/serialization/JSON.cpp index e98bf47b9..42e615108 100644 --- a/src/serialization/JSON.cpp +++ b/src/serialization/JSON.cpp @@ -184,7 +184,7 @@ bool JSON::ExtractString(const char **data, std::string &str) // End of the string? else if (next_char == '"') { (*data)++; - str.reserve(); // Remove unused capacity + str.shrink_to_fit(); // Remove unused capacity return true; } diff --git a/src/sleep.cpp b/src/sleep.cpp index 27e81ce54..b0802c624 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -446,12 +446,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss + static esp_pm_config_t esp32_config; // filled with zeros because bss #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32C6 + esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32C3 esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; #else diff --git a/variants/tlora_c6/platformio.ini b/variants/tlora_c6/platformio.ini new file mode 100644 index 000000000..977e8ba86 --- /dev/null +++ b/variants/tlora_c6/platformio.ini @@ -0,0 +1,5 @@ +[env:tlora-c6] +extends = esp32c6_base +board = esp32-c6-devkitm-1 +build_flags = + ${esp32c6_base.build_flags} -D PRIVATE_HW -D TLORA_C6 -I variants/tlora_c6 \ No newline at end of file diff --git a/variants/tlora_c6/variant.h b/variants/tlora_c6/variant.h new file mode 100644 index 000000000..08fefa809 --- /dev/null +++ b/variants/tlora_c6/variant.h @@ -0,0 +1,16 @@ +#define I2C_SDA 4 // I2C pins for this board +#define I2C_SCL 15 + +#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller + +#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost +#define LED_PIN 2 // If defined we will blink this LED +#define BUTTON_PIN 0 // If defined, this will be used for user button presses +#define BUTTON_NEED_PULLUP +#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. + +#define USE_RF95 +#define LORA_DIO0 26 // a No connect on the SX1262 module +#define LORA_RESET 14 +#define LORA_DIO1 33 // Must be manually wired: https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 +#define LORA_DIO2 32 // Not really used \ No newline at end of file From d36c69396b79caa734dc3009798389fd9b6299a5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Sep 2024 10:42:40 -0500 Subject: [PATCH 1097/1377] Exclude meshtasticd binaries from firmware.zip (#4698) * Exclude meshtasticd binaries from firmware.zip * Incorrect --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 36125f72f..9b97dcb2e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -266,7 +266,7 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x *.deb + run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x meshtasticd_* - uses: actions/download-artifact@v4 with: From 8b911f14cff15c4de6d7bdedb1bac854e7382d85 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Fri, 13 Sep 2024 14:11:39 -0700 Subject: [PATCH 1098/1377] Update Bug Report.yml (#4702) --- .github/ISSUE_TEMPLATE/Bug Report.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml index b5ca0db40..f2d2f6507 100644 --- a/.github/ISSUE_TEMPLATE/Bug Report.yml +++ b/.github/ISSUE_TEMPLATE/Bug Report.yml @@ -49,10 +49,24 @@ body: - Heltec V3 - Heltec Wireless Paper - Heltec Wireless Tracker + - Heltec Mesh Node T114 + - Heltec Vision Master E213 + - Heltec Vision Master E290 + - Heltec Vision Master T190 + - Nano G1 + - Nano G1 Explorer + - Nano G2 Ultra - Raspberry Pi Pico (W) - Relay v1 - Relay v2 - Seeed Wio Tracker 1110 + - Seeed Card Tracker T1000-E + - Station G1 + - Station G2 + - unPhone + - CanaryOne + - Chatter + - Linux Native - DIY - Other validations: From b59bd6fee9feb5370d042a4408019627138373cb Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Fri, 13 Sep 2024 14:11:54 -0700 Subject: [PATCH 1099/1377] Update feature.yml (#4700) --- .github/ISSUE_TEMPLATE/feature.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index b027a36cc..b50ccac26 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -18,6 +18,7 @@ body: - ESP32 - RP2040 - Linux Native + - Cross-Platform - other validations: required: true From 35cfe4318ac422f5820415fe96ceb91ddcf79cf5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Sep 2024 19:42:31 -0500 Subject: [PATCH 1100/1377] Stop past timestamps from setting our system time RTC (#4704) * Ignore attempts to set times in the past (before our build epoch) * TRONK --- platformio.ini | 1 + src/gps/RTC.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/platformio.ini b/platformio.ini index 7613a2f43..d561aaf74 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,6 +81,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS -DRADIOLIB_EXCLUDE_LORAWAN -DMESHTASTIC_EXCLUDE_DROPZONE=1 + -DBUILD_EPOCH=$UNIX_TIME ;-D OLED_PL monitor_speed = 115200 diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index c056bb9e4..42a98f568 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -109,6 +109,12 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) static uint32_t lastSetMsec = 0; uint32_t now = millis(); uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms +#ifdef BUILD_EPOCH + if (tv->tv_sec < BUILD_EPOCH) { + LOG_WARN("Ignoring time (%ld) before build epoch (%ld)!\n", printableEpoch, BUILD_EPOCH); + return false; + } +#endif bool shouldSet; if (forceUpdate) { From ae791ca7e19583f2ccc9d7190256e8d23d7dfc89 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Sep 2024 19:43:50 -0500 Subject: [PATCH 1101/1377] Add buildstamp epoch to initial debug output --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 1401f4f0b..df90c4722 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -586,6 +586,9 @@ void setup() // Hello printInfo(); +#ifdef BUILD_EPOCH + LOG_INFO("Build timestamp: %ld\n", BUILD_EPOCH); +#endif #ifdef ARCH_ESP32 esp32Setup(); From 1ab5bf43559413519d9b4e95273951eaa00da4b8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 14 Sep 2024 07:44:40 -0500 Subject: [PATCH 1102/1377] Use the time.age() value to correct stale GPS times (#4705) * Use the time.age() value to correct stale GPS times * Trunk --------- Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 046f277ff..8abb4edb1 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1539,7 +1539,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). */ struct tm t; - t.tm_sec = ti.second(); + t.tm_sec = ti.second() + round(ti.age() / 1000); t.tm_min = ti.minute(); t.tm_hour = ti.hour(); t.tm_mday = d.day(); @@ -1547,8 +1547,8 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s t.tm_year = d.year() - 1900; t.tm_isdst = false; if (t.tm_mon > -1) { - LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d\n", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, - t.tm_sec); + LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d\n", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, + t.tm_sec, ti.age()); perhapsSetRTC(RTCQualityGPS, t); return true; } else @@ -1807,4 +1807,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS \ No newline at end of file +#endif // Exclude GPS From 8893529653cb23060f88f3ff56b092e5291facac Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 15 Sep 2024 00:53:27 +0200 Subject: [PATCH 1103/1377] Make local stats number of Rx packets sum of good and bad (#4709) --- src/modules/Telemetry/DeviceTelemetry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 305be9904..04789af5e 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -124,7 +124,7 @@ void DeviceTelemetryModule::sendLocalStatsToPhone() telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes(); if (RadioLibInterface::instance) { telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; - telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood; + telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; } From 3a10a2785153e6282102f9376aee0102c6e29400 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 15 Sep 2024 06:27:59 -0500 Subject: [PATCH 1104/1377] Actually restrict remote hardware to gpio channel (#4717) --- src/modules/RemoteHardwareModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index 6910005a8..0242b59bc 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -47,6 +47,8 @@ RemoteHardwareModule::RemoteHardwareModule() : ProtobufModule("remotehardware", meshtastic_PortNum_REMOTE_HARDWARE_APP, &meshtastic_HardwareMessage_msg), concurrency::OSThread("RemoteHardwareModule") { + // restrict to the gpio channel for rx + boundChannel = Channels::gpioChannel; } bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_HardwareMessage *pptr) From ff8baa1c85d624b1354346ba3b2e19b733e0947c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 15 Sep 2024 09:26:43 -0500 Subject: [PATCH 1105/1377] Don't use PKC on a non-primary channel unless specifically requested (#4715) * Don't use PKC on a non-primary channel unless specifically requested * Don't change from channel 0 if we can send a PKC packet. --- src/mesh/Router.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b222872fa..deb4ef2bf 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -185,9 +185,12 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) handleReceived(p, src); } - if (!p->channel) { // don't override if a channel was requested - p->channel = nodeDB->getMeshNodeChannel(p->to); - LOG_DEBUG("localSend to channel %d\n", p->channel); + if (!p->channel && !p->pki_encrypted) { // don't override if a channel was requested + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); + if (node && node->user.public_key.size == 0) { + p->channel = node->channel; + LOG_DEBUG("localSend to channel %d\n", p->channel); + } } return send(p); @@ -478,10 +481,20 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) #if !(MESHTASTIC_EXCLUDE_PKI) meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); - if (!owner.is_licensed && config.security.private_key.size == 32 && p->to != NODENUM_BROADCAST && node != nullptr && - node->user.public_key.size > 0 && p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && - p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && - p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { + // We may want to retool things so we can send a PKC packet when the client specifies a key and nodenum, even if the node + // is not in the local nodedb + if ( + // Don't use PKC with Ham mode + !owner.is_licensed && + // Don't use PKC if it's not explicitly requested and a non-primary channel is requested + !(p->pki_encrypted != true && p->channel > 0) && + // Check for valid keys and single node destination + config.security.private_key.size == 32 && p->to != NODENUM_BROADCAST && node != nullptr && + // Check for a known public key for the destination + (node->user.public_key.size == 32) && + // Some portnums either make no sense to send with PKC + p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && + p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); if (numbytes + 12 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; From dc3eba91002ff5510d6e28a286a75f5229aa4579 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 15 Sep 2024 18:57:02 -0500 Subject: [PATCH 1106/1377] Expand to MqttClientProxyMessage_size (#4726) --- src/mqtt/MQTT.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index d14c7a923..0f4c5a8c5 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -468,7 +468,7 @@ void MQTT::publishQueuedMessages() LOG_DEBUG("Publishing enqueued MQTT message\n"); // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + static uint8_t bytes[meshtastic_MqttClientProxyMessage_size]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); std::string topic; if (env->packet->pki_encrypted) { @@ -555,7 +555,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + static uint8_t bytes[meshtastic_MqttClientProxyMessage_size]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); std::string topic = cryptTopic + channelId + "/" + owner.id; LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); @@ -651,7 +651,7 @@ void MQTT::perhapsReportToMap() se->packet = mp; // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + static uint8_t bytes[meshtastic_MqttClientProxyMessage_size]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); From 8d57b6164abcd0f0d214b1981e61d21d1cab7256 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 15 Sep 2024 20:37:19 -0500 Subject: [PATCH 1107/1377] Add Heltec T1114 hardware model to build (#4719) --- src/platform/nrf52/architecture.h | 2 ++ variants/heltec_mesh_node_t114/platformio.ini | 1 + 2 files changed, 3 insertions(+) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 5a04dd6a7..d08f83965 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -71,6 +71,8 @@ #define HW_VENDOR meshtastic_HardwareModel_MS24SF1 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW +#elif defined(HELTEC_T1114) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #endif diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini index 1009ecce5..e0d8ca0cc 100644 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -8,6 +8,7 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_node_t114 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE + -DHELTEC_T114 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node_t114> lib_deps = From 41a769aa06732279ae0dcec66eeb4c8ed0c74c73 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 16 Sep 2024 13:55:27 +0800 Subject: [PATCH 1108/1377] Fix Heltec T114 vendor definition @dahanc pointed out there was an extra one in there. --- src/platform/nrf52/architecture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index d08f83965..b2b7b5a20 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -71,7 +71,7 @@ #define HW_VENDOR meshtastic_HardwareModel_MS24SF1 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW -#elif defined(HELTEC_T1114) +#elif defined(HELTEC_T114) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN From 4e8672cce45fb49f77878f21dd93128edaa6a73a Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Mon, 16 Sep 2024 10:40:52 +0200 Subject: [PATCH 1109/1377] Update variant.h --- variants/ME25LS01-4Y10TD_e-ink/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/ME25LS01-4Y10TD_e-ink/variant.h index 60996d468..0b2b13068 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.h @@ -97,7 +97,7 @@ static const uint8_t MISO = PIN_SPI_MISO; static const uint8_t SCK = PIN_SPI_SCK; // EPD SPI -#define PIN_SPI1_MISO (-1) // Not Used for EPD +#define PIN_SPI1_MISO (32 + 2) // Not Used for EPD but needs to be defined #define PIN_SPI1_MOSI (0 + 10) // EPD_MOSI P0.10 #define PIN_SPI1_SCK (0 + 9) // EPD_SCLK P0.09 From ea6f6c3668317d6ca5dc51e8dd4b2a99d84d7a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 12 Sep 2024 22:42:10 +0200 Subject: [PATCH 1110/1377] First stab at ESP32-C6 support for TLora-C6 --- arch/esp32/esp32c6.ini | 47 ++++++++++++++++++++++++ src/gps/GPS.cpp | 5 ++- src/graphics/Screen.cpp | 28 +++++++------- src/main.cpp | 14 ++++--- src/mesh/NodeDB.cpp | 2 +- src/mesh/TypedQueue.h | 2 +- src/mesh/api/ServerAPI.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 3 +- src/modules/ExternalNotificationModule.h | 2 +- src/modules/PositionModule.cpp | 16 ++++---- src/modules/SerialModule.cpp | 21 ++++++++++- src/mqtt/MQTT.cpp | 4 ++ src/mqtt/MQTT.h | 4 ++ src/platform/esp32/main-esp32.cpp | 12 +++++- src/power.h | 1 + src/serialization/JSON.cpp | 2 +- src/sleep.cpp | 4 +- variants/tlora_c6/platformio.ini | 5 +++ variants/tlora_c6/variant.h | 16 ++++++++ 19 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 arch/esp32/esp32c6.ini create mode 100644 variants/tlora_c6/platformio.ini create mode 100644 variants/tlora_c6/variant.h diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini new file mode 100644 index 000000000..a7bcdc0e8 --- /dev/null +++ b/arch/esp32/esp32c6.ini @@ -0,0 +1,47 @@ +[esp32c6_base] +extends = esp32_base +platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 +build_flags = + ${arduino_base.build_flags} + -Wall + -Wextra + -Isrc/platform/esp32 + -std=c++11 + -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL + -DAXP_DEBUG_PORT=Serial + -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DSERIAL_BUFFER_SIZE=4096 + -DLIBPAX_ARDUINO + -DLIBPAX_WIFI + -DLIBPAX_BLE + -DMESHTASTIC_EXCLUDE_WEBSERVER + ;-DDEBUG_HEAP + ; TEMP + -DHAS_BLUETOOTH=0 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER + -DMESHTASTIC_EXCLUDE_BLUETOOTH + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${environmental_base.lib_deps} + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f + rweather/Crypto@^0.4.0 + +build_src_filter = + ${esp32_base.build_src_filter} - + +monitor_speed = 115200 +monitor_filters = esp32_c3_exception_decoder + +lib_ignore = + NonBlockingRTTTL + NimBLE-Arduino + libpax + \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 8abb4edb1..8938cc371 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -58,7 +58,8 @@ const char *getGPSPowerStateString(GPSPowerState state) case GPS_OFF: return "OFF"; default: - assert(false); // Unhandled enum value.. + assert(false); // Unhandled enum value.. + return "FALSE"; // to make new ESP-IDF happy } } @@ -328,7 +329,7 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t { uint16_t ubxFrameCounter = 0; uint32_t startTime = millis(); - uint16_t needRead; + uint16_t needRead = 0; while (millis() - startTime < waitMillis) { if (_serial_gps->available()) { diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ff1254812..2de5e3f0a 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1004,55 +1004,55 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->setColor(WHITE); #ifndef EXCLUDE_EMOJI - if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) { + if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbup); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44E") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbdown); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"❓") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "❓") == 0) { display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height, question); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"‼️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "‼️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5, bang_width, bang_height, bang); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F4A9") == 0) { display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5, poo_width, poo_height, poo); } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\xa4\xa3") == 0) { display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5, haha_width, haha_height, haha); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44B") == 0) { display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width, wave_icon_height, wave_icon); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F920") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F920") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height, cowboy); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F42D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height, deadmau5); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xE2\x98\x80\xEF\xB8\x8F") == 0) { display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5, sun_width, sun_height, sun); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\u2614") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\u2614") == 0) { display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10, rain_width, rain_height, rain); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"☁️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "☁️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"🌫️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "🌫️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5, fog_width, fog_height, fog); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\x98\x88") == 0) { display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"♥️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "♥️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart); } else { diff --git a/src/main.cpp b/src/main.cpp index df90c4722..2b7410ac1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -322,15 +322,19 @@ void setup() #ifdef BUTTON_PIN #ifdef ARCH_ESP32 - // If the button is connected to GPIO 12, don't enable the ability to use - // meshtasticAdmin on the device. - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); - +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN #ifdef BUTTON_NEED_PULLUP gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); delay(10); #endif - +#endif #endif #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5d1db88ae..33fe5a090 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -49,7 +49,7 @@ NodeDB *nodeDB = nullptr; // we have plenty of ram so statically alloc this tempbuf (for now) -EXT_RAM_ATTR meshtastic_DeviceState devicestate; +EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate; meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node; meshtastic_LocalConfig config; meshtastic_LocalModuleConfig moduleConfig; diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index c96edae8e..f7d016f10 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -14,7 +14,7 @@ */ template class TypedQueue { - static_assert(std::is_pod::value, "T must be pod"); + static_assert(std::is_standard_layout::value, "T must be standard layout"); QueueHandle_t h; concurrency::OSThread *reader = NULL; diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 3a483aac1..097f4fa21 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -45,7 +45,7 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { - auto client = U::available(); + auto client = U::accept(); if (client) { // Close any previous connection (see FIXME in header file) if (openAPI) { diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 07b03222e..e3203e6f7 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -309,7 +309,8 @@ static void WiFiEvent(WiFiEvent_t event) onNetworkConnected(); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - LOG_INFO("Obtained IP6 address: %s\n", WiFi.localIPv6().toString().c_str()); + LOG_INFO("Obtained Local IP6 address: %s\n", WiFi.linkLocalIPv6().toString().c_str()); + LOG_INFO("Obtained GlobalIP6 address: %s\n", WiFi.globalIPv6().toString().c_str()); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0\n"); diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 08e72c35a..a5dff3651 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -3,7 +3,7 @@ #include "SinglePortModule.h" #include "concurrency/OSThread.h" #include "configuration.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else // Noop class for portduino. diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index cb6a58b2e..2a962c268 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -273,7 +273,7 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() meshtastic_TAKPacket takPacket = {.is_compressed = true, .has_contact = true, - .contact = {0}, + .contact = meshtastic_Contact_init_default, .has_group = true, .group = {meshtastic_MemberRole_TeamMember, meshtastic_Team_Cyan}, .has_status = true, @@ -282,13 +282,13 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() .battery = powerStatus->getBatteryChargePercent(), }, .which_payload_variant = meshtastic_TAKPacket_pli_tag, - {.pli = { - .latitude_i = localPosition.latitude_i, - .longitude_i = localPosition.longitude_i, - .altitude = localPosition.altitude_hae, - .speed = localPosition.ground_speed, - .course = static_cast(localPosition.ground_track), - }}}; + .payload_variant = {.pli = { + .latitude_i = localPosition.latitude_i, + .longitude_i = localPosition.longitude_i, + .altitude = localPosition.altitude_hae, + .speed = localPosition.ground_speed, + .course = static_cast(localPosition.ground_track), + }}}; auto length = unishox2_compress_lines(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign, sizeof(takPacket.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f0ba64f65..549fcf1d7 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -62,6 +62,9 @@ SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial; +#elif defined(CONFIG_IDF_TARGET_ESP32C6) +SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("SerialModule") {} +static Print *serialPrint = &Serial1; #else SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial2; @@ -137,7 +140,16 @@ int32_t SerialModule::runOnce() // Give it a chance to flush out 💩 delay(10); } -#ifdef ARCH_ESP32 +#if defined(CONFIG_IDF_TARGET_ESP32C6) + if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { + Serial1.setRxBufferSize(RX_BUFFER); + Serial1.begin(baud, SERIAL_8N1, moduleConfig.serial.rxd, moduleConfig.serial.txd); + } else { + Serial.begin(baud); + Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); + } + +#elif defined(ARCH_ESP32) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { Serial2.setRxBufferSize(RX_BUFFER); @@ -205,8 +217,13 @@ int32_t SerialModule::runOnce() processWXSerial(); } else { +#if defined(CONFIG_IDF_TARGET_ESP32C6) + while (Serial1.available()) { + serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#else while (Serial2.available()) { serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#endif serialModuleRadio->sendPayload(); } } @@ -392,7 +409,7 @@ uint32_t SerialModule::getBaudRate() */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 0f4c5a8c5..9068846e2 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -310,6 +310,7 @@ void MQTT::reconnect() mqttPassword = moduleConfig.mqtt.password; } #if HAS_WIFI && !defined(ARCH_PORTDUINO) +#ifndef CONFIG_IDF_TARGET_ESP32C6 if (moduleConfig.mqtt.tls_enabled) { // change default for encrypted to 8883 try { @@ -325,6 +326,9 @@ void MQTT::reconnect() LOG_INFO("Using non-TLS-encrypted session\n"); pubSub.setClient(mqttClient); } +#else + pubSub.setClient(mqttClient); +#endif #elif HAS_NETWORKING pubSub.setClient(mqttClient); #endif diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index ba0987783..c827e12ca 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -9,9 +9,11 @@ #if HAS_WIFI #include #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 #include #endif #endif +#endif #if HAS_ETHERNET #include #endif @@ -33,9 +35,11 @@ class MQTT : private concurrency::OSThread #if HAS_WIFI WiFiClient mqttClient; #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 WiFiClientSecure wifiSecureClient; #endif #endif +#endif #if HAS_ETHERNET EthernetClient mqttClient; #endif diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 1b997500a..12a6b5f74 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -13,6 +13,7 @@ #include "mesh/wifi/WiFiAPClient.h" #endif +#include "esp_mac.h" #include "meshUtils.h" #include "sleep.h" #include "soc/rtc.h" @@ -140,9 +141,16 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 +#ifdef CONFIG_IDF_TARGET_ESP32C6 + esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); + wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; + wdt_config->trigger_panic = true; + res = esp_task_wdt_init(wdt_config); + assert(res == ESP_OK); +#else res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); assert(res == ESP_OK); - +#endif res = esp_task_wdt_add(NULL); assert(res == ESP_OK); @@ -216,7 +224,7 @@ void cpuDeepSleep(uint32_t msecToWake) // to detect wake and in normal operation the external part drives them hard. #ifdef BUTTON_PIN // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. -#if SOC_RTCIO_HOLD_SUPPORTED +#if SOC_RTCIO_HOLD_SUPPORTED && SOC_PM_SUPPORT_EXT_WAKEUP uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif diff --git a/src/power.h b/src/power.h index a4307ee07..5a41b55f2 100644 --- a/src/power.h +++ b/src/power.h @@ -5,6 +5,7 @@ #include "configuration.h" #ifdef ARCH_ESP32 +// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h #include #include #endif diff --git a/src/serialization/JSON.cpp b/src/serialization/JSON.cpp index e98bf47b9..42e615108 100644 --- a/src/serialization/JSON.cpp +++ b/src/serialization/JSON.cpp @@ -184,7 +184,7 @@ bool JSON::ExtractString(const char **data, std::string &str) // End of the string? else if (next_char == '"') { (*data)++; - str.reserve(); // Remove unused capacity + str.shrink_to_fit(); // Remove unused capacity return true; } diff --git a/src/sleep.cpp b/src/sleep.cpp index 27e81ce54..b0802c624 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -446,12 +446,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss + static esp_pm_config_t esp32_config; // filled with zeros because bss #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32C6 + esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32C3 esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; #else diff --git a/variants/tlora_c6/platformio.ini b/variants/tlora_c6/platformio.ini new file mode 100644 index 000000000..977e8ba86 --- /dev/null +++ b/variants/tlora_c6/platformio.ini @@ -0,0 +1,5 @@ +[env:tlora-c6] +extends = esp32c6_base +board = esp32-c6-devkitm-1 +build_flags = + ${esp32c6_base.build_flags} -D PRIVATE_HW -D TLORA_C6 -I variants/tlora_c6 \ No newline at end of file diff --git a/variants/tlora_c6/variant.h b/variants/tlora_c6/variant.h new file mode 100644 index 000000000..08fefa809 --- /dev/null +++ b/variants/tlora_c6/variant.h @@ -0,0 +1,16 @@ +#define I2C_SDA 4 // I2C pins for this board +#define I2C_SCL 15 + +#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller + +#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost +#define LED_PIN 2 // If defined we will blink this LED +#define BUTTON_PIN 0 // If defined, this will be used for user button presses +#define BUTTON_NEED_PULLUP +#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. + +#define USE_RF95 +#define LORA_DIO0 26 // a No connect on the SX1262 module +#define LORA_RESET 14 +#define LORA_DIO1 33 // Must be manually wired: https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 +#define LORA_DIO2 32 // Not really used \ No newline at end of file From 905194c6040bce4c1f036f1d040d23ba9029b187 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 16 Sep 2024 22:06:13 +0800 Subject: [PATCH 1111/1377] Remove lora_isp4520 (#4735) Per Ben: "We haven't supported any NRF5832 based board in quite some time. It's relatively resource constrained compared to the NRF52840" --- boards/lora_isp4520.json | 40 ------------ variants/lora_isp4520/platformio.ini | 18 ------ variants/lora_isp4520/variant.cpp | 52 --------------- variants/lora_isp4520/variant.h | 94 ---------------------------- 4 files changed, 204 deletions(-) delete mode 100644 boards/lora_isp4520.json delete mode 100644 variants/lora_isp4520/platformio.ini delete mode 100644 variants/lora_isp4520/variant.cpp delete mode 100644 variants/lora_isp4520/variant.h diff --git a/boards/lora_isp4520.json b/boards/lora_isp4520.json deleted file mode 100644 index 8125aa666..000000000 --- a/boards/lora_isp4520.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52832_s132_v6.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DNRF52832_XXAA -DNRF52", - "f_cpu": "64000000L", - "mcu": "nrf52832", - "variant": "lora_isp4520", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS132", - "sd_name": "s132", - "sd_version": "6.1.1", - "sd_fwid": "0x00B7" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52832_xxAA", - "svd_path": "nrf52.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "lora ISP4520", - "upload": { - "maximum_ram_size": 65536, - "maximum_size": 524288, - "require_upload_port": true, - "speed": 115200, - "protocol": "nrfutil", - "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"] - }, - "url": "", - "vendor": "PsiSoft" -} diff --git a/variants/lora_isp4520/platformio.ini b/variants/lora_isp4520/platformio.ini deleted file mode 100644 index 9d6563515..000000000 --- a/variants/lora_isp4520/platformio.ini +++ /dev/null @@ -1,18 +0,0 @@ -[env:lora_isp4520] -extends = nrf52_base -board = lora_isp4520 -board_level = extra - -# add our variants files to the include and src paths -build_flags = ${nrf52_base.build_flags} -Ivariants/lora_isp4520 - -# No screen and GPS on the board. We still need RTC.cpp for the RTC clock. -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/lora_isp4520> - - + + -lib_ignore = ${nrf52_base.lib_ignore} - ESP8266_SSD1306 - SparkFun Ublox Arduino Library - AXP202X_Library - TinyGPSPlus - -upload_protocol = jlink -monitor_port = /dev/ttyUSB0 \ No newline at end of file diff --git a/variants/lora_isp4520/variant.cpp b/variants/lora_isp4520/variant.cpp deleted file mode 100644 index 41b31384a..000000000 --- a/variants/lora_isp4520/variant.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - 25, // D0 SPI_MISO - 24, // D1 SPI_NSS - 23, // D2 SPI_SCK - 4, // D3 VBAT - 11, // D4 DIO1 - 27, // D5 BUSY - 19, // D6 NRESET - 12, // D7 BUTTON2 - 22, // D8 BUTTON3 - 26, // D9 SPI_MOSI - 31, // D10 UART_RX - 2, // D11 UART_TX - 10, // D12 LED1 GREEN - 17, // D13 LED2 RED - 9, // D14 BUZZER - 7, // D15 BUTTON1 -}; - -#include -void initVariant() -{ - for (int i : {PIN_LED1, PIN_LED2}) { - pinMode(i, OUTPUT); - ledOff(i); - } -} diff --git a/variants/lora_isp4520/variant.h b/variants/lora_isp4520/variant.h deleted file mode 100644 index 30b8fc169..000000000 --- a/variants/lora_isp4520/variant.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _VARIANT_LORA_ISP4520_ -#define _VARIANT_LORA_ISP4520_ - -#define USE_SEGGER -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#define USE_LFXO - -// #define USE_SEGGER - -// Number of pins defined in PinDescription array -#define PINS_COUNT (16) -#define NUM_DIGITAL_PINS (16) -#define NUM_ANALOG_INPUTS (1) -#define NUM_ANALOG_OUTPUTS (1) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 1 - -// These are in arduino pin numbers, -// translation in g_ADigitalPinMap in variants.cpp -#define PIN_SPI_MISO (0) -#define PIN_SPI_MOSI (9) -#define PIN_SPI_SCK (2) - -/* - * Wire Interfaces (I2C) - */ -#define WIRE_INTERFACES_COUNT 0 - -// GPIOs the SX1262 is connected -#define USE_SX1262 -#define SX126X_CS 1 // aka SPI_NSS -#define SX126X_DIO1 (4) -#define SX126X_BUSY (5) -#define SX126X_RESET (6) - -/* - * Serial interfaces - */ -#define PIN_SERIAL_RX (10) -#define PIN_SERIAL_TX (11) -// LEDs -#define PIN_LED1 (12) -#define PIN_LED2 (13) -#define PIN_BUZZER (14) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_RED PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ -#define PIN_BUTTON1 (15) -#define PIN_BUTTON2 (7) -#define PIN_BUTTON3 (8) - -// ADC pin and voltage divider -#define BATTERY_PIN 3 -#define ADC_MULTIPLIER 1.436 - -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // Not really an E22 but this board clones using DIO3 for tcxo control - -#endif From f37276d5fcde65478194be334c1a0a7c05bd6978 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 10:32:21 -0500 Subject: [PATCH 1112/1377] [create-pull-request] automated change (#4736) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/atak.pb.h | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index 0acaec6ef..0c0d061ec 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0acaec6eff00e748beeae89148093221f131cd9c +Subproject commit 0c0d061ec58dec08b80334fd007ecb6c82cd8ed9 diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index 5fd18f963..50b57cd04 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -120,6 +120,7 @@ typedef struct _meshtastic_PLI { uint16_t course; } meshtastic_PLI; +typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacket_detail_t; /* Packets for the official ATAK Plugin */ typedef struct _meshtastic_TAKPacket { /* Are the payloads strings compressed for LoRA transport? */ @@ -139,6 +140,9 @@ typedef struct _meshtastic_TAKPacket { meshtastic_PLI pli; /* ATAK GeoChat message */ meshtastic_GeoChat chat; + /* Generic CoT detail XML + May be compressed / truncated by the sender */ + meshtastic_TAKPacket_detail_t detail; } payload_variant; } meshtastic_TAKPacket; @@ -199,6 +203,7 @@ extern "C" { #define meshtastic_TAKPacket_status_tag 4 #define meshtastic_TAKPacket_pli_tag 5 #define meshtastic_TAKPacket_chat_tag 6 +#define meshtastic_TAKPacket_detail_tag 7 /* Struct field encoding specification for nanopb */ #define meshtastic_TAKPacket_FIELDLIST(X, a) \ @@ -207,7 +212,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, contact, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, group, 3) \ X(a, STATIC, OPTIONAL, MESSAGE, status, 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,pli,payload_variant.pli), 5) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 6) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 6) \ +X(a, STATIC, ONEOF, BYTES, (payload_variant,detail,payload_variant.detail), 7) #define meshtastic_TAKPacket_CALLBACK NULL #define meshtastic_TAKPacket_DEFAULT NULL #define meshtastic_TAKPacket_contact_MSGTYPE meshtastic_Contact From b3343303a9b77ce973bd253e138f21e7051e985e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 16 Sep 2024 21:36:54 +0200 Subject: [PATCH 1113/1377] write firmware version and hardware platform to Flash memory --- src/platform/esp32/main-esp32.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 1b997500a..0b6f7cf23 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -120,17 +120,24 @@ void esp32Setup() uint32_t rebootCounter = preferences.getUInt("rebootCounter", 0); rebootCounter++; preferences.putUInt("rebootCounter", rebootCounter); + // store firmware version and hwrevision for access from OTA firmware + String fwrev = preferences.getString("firmwareVersion", ""); + if (fwrev.compareTo(optstr(APP_VERSION)) != 0) + preferences.putString("firmwareVersion", optstr(APP_VERSION)); + uint8_t hwven = preferences.getUInt("hwVendor", 0); + if (hwven != HW_VENDOR) + preferences.putUInt("hwVendor", HW_VENDOR); preferences.end(); LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); #if !MESHTASTIC_EXCLUDE_BLUETOOTH String BLEOTA = BleOta::getOtaAppVersion(); if (BLEOTA.isEmpty()) { - LOG_DEBUG("No OTA firmware available\n"); + LOG_INFO("No OTA firmware available\n"); } else { - LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); + LOG_INFO("OTA firmware version %s\n", BLEOTA.c_str()); } #else - LOG_DEBUG("No OTA firmware available\n"); + LOG_INFO("No OTA firmware available\n"); #endif // enableModemSleep(); From 1e665d5181e68be033790ec400327e0a27c855b5 Mon Sep 17 00:00:00 2001 From: jhps Date: Mon, 16 Sep 2024 17:11:55 -0700 Subject: [PATCH 1114/1377] Update T114 LED definitions to include only one simple controllable LED and two NEOPIXELS. (#4710) --- variants/heltec_mesh_node_t114/variant.cpp | 8 +------- variants/heltec_mesh_node_t114/variant.h | 23 ++++++++++------------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/variants/heltec_mesh_node_t114/variant.cpp b/variants/heltec_mesh_node_t114/variant.cpp index cae079b74..85c9f4a72 100644 --- a/variants/heltec_mesh_node_t114/variant.cpp +++ b/variants/heltec_mesh_node_t114/variant.cpp @@ -32,13 +32,7 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 + // LED1 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - - pinMode(PIN_LED3, OUTPUT); - ledOff(PIN_LED3); } diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index e8c305990..454e66931 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -67,20 +67,17 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (32 + 3) // 13 red (confirmed on 1.0 board) -// Unused(by firmware) LEDs: -#define PIN_LED2 (1 + 1) // 14 blue -#define PIN_LED3 (1 + 11) // 15 green - -#define LED_RED PIN_LED3 -#define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 - -#define LED_BUILTIN LED_BLUE -#define LED_CONN PIN_GREEN - +#define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define LED_GREEN PIN_LED1 +#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 2 // How many neopixels are connected +#define NEOPIXEL_DATA 14 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use + /* * Buttons */ @@ -206,4 +203,4 @@ No longer populated on PCB * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif From 97fd189f43bbdc9b9467dcb161990960084c9960 Mon Sep 17 00:00:00 2001 From: Szetya Date: Thu, 12 Sep 2024 18:15:31 +0200 Subject: [PATCH 1115/1377] Compass update https://github.com/meshtastic/firmware/issues/4494 New compass arrow and replacement of the north marker with a small circle. --- src/graphics/Screen.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ff1254812..6cf9e8f03 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1285,8 +1285,8 @@ static int8_t prevFrame = -1; // Draw the arrow pointing to a node's location void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian) { - Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially - float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f; + Point tip(0.0f, 0.5f), tail(0.0f, -0.35f); // pointing up initially + float arrowOffsetX = 0.14f, arrowOffsetY = 1.0f; Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY); Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow}; @@ -1296,9 +1296,15 @@ void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t com arrowPoints[i]->scale(compassDiam * 0.6); arrowPoints[i]->translate(compassX, compassY); } + /* Old arrow display->drawLine(tip.x, tip.y, tail.x, tail.y); display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y); display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y); + display->drawLine(leftArrow.x, leftArrow.y, tail.x, tail.y); + display->drawLine(rightArrow.x, rightArrow.y, tail.x, tail.y); + */ + display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y); + display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y); } // Get a string representation of the time passed since something happened @@ -1336,22 +1342,27 @@ void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t co // If north is supposed to be at the top of the compass we want rotation to be +0 if (config.display.compass_north_top) myHeading = -0; - - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); + /* N sign points currently not deleted*/ + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); //N sign points (N1-N4) Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point *rosePoints[] = {&N1, &N2, &N3, &N4}; + Point NC1(0.00f, 0.50f); //north circle center point + Point *rosePoints[] = {&N1, &N2, &N3, &N4, &NC1}; uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 5; i++) { // North on compass will be negative of heading rosePoints[i]->rotate(-myHeading); rosePoints[i]->scale(compassDiam); rosePoints[i]->translate(compassX, compassY); } + + /* changed the N sign to a small circle on the compass circle. display->drawLine(N1.x, N1.y, N3.x, N3.y); display->drawLine(N2.x, N2.y, N4.x, N4.y); display->drawLine(N1.x, N1.y, N4.x, N4.y); + */ + display->drawCircle(NC1.x, NC1.y, 4); // North sign circle, 4px radius is sufficient for all displays. } uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight) @@ -2756,4 +2767,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN From 33e6f7f6e03950ed3ae0219cf27a6f231cff5f2d Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sat, 14 Sep 2024 21:29:46 +1200 Subject: [PATCH 1116/1377] Hollow triangle for E-Ink; trunk formatting --- src/graphics/Screen.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 6cf9e8f03..ae09ee408 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1303,7 +1303,11 @@ void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t com display->drawLine(leftArrow.x, leftArrow.y, tail.x, tail.y); display->drawLine(rightArrow.x, rightArrow.y, tail.x, tail.y); */ +#ifdef USE_EINK + display->drawTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y); +#else display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y); +#endif display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y); } @@ -1343,9 +1347,9 @@ void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t co if (config.display.compass_north_top) myHeading = -0; /* N sign points currently not deleted*/ - Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); //N sign points (N1-N4) + Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); // N sign points (N1-N4) Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f); - Point NC1(0.00f, 0.50f); //north circle center point + Point NC1(0.00f, 0.50f); // north circle center point Point *rosePoints[] = {&N1, &N2, &N3, &N4, &NC1}; uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); @@ -1356,13 +1360,13 @@ void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t co rosePoints[i]->scale(compassDiam); rosePoints[i]->translate(compassX, compassY); } - + /* changed the N sign to a small circle on the compass circle. display->drawLine(N1.x, N1.y, N3.x, N3.y); display->drawLine(N2.x, N2.y, N4.x, N4.y); display->drawLine(N1.x, N1.y, N4.x, N4.y); */ - display->drawCircle(NC1.x, NC1.y, 4); // North sign circle, 4px radius is sufficient for all displays. + display->drawCircle(NC1.x, NC1.y, 4); // North sign circle, 4px radius is sufficient for all displays. } uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight) From 39c90dd581cbc742e2d55dc69936ab688471257d Mon Sep 17 00:00:00 2001 From: jp-bennett <5630967+jp-bennett@users.noreply.github.com> Date: Tue, 17 Sep 2024 02:53:44 +0000 Subject: [PATCH 1117/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 8 ++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 4 ++-- src/mesh/generated/meshtastic/mesh.pb.h | 10 +++++++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/protobufs b/protobufs index 0c0d061ec..c5108cfd6 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0c0d061ec58dec08b80334fd007ecb6c82cd8ed9 +Subproject commit c5108cfd6bbc59adef44575dcec3067cbfbfeac1 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 66ffa0a4b..da2e43972 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -535,7 +535,7 @@ typedef struct _meshtastic_Config_SecurityConfig { meshtastic_Config_SecurityConfig_private_key_t private_key; /* The public key authorized to send admin messages to this node. */ pb_size_t admin_key_count; - meshtastic_Config_SecurityConfig_admin_key_t admin_key[1]; + meshtastic_Config_SecurityConfig_admin_key_t admin_key[3]; /* If true, device is considered to be "managed" by a mesh administrator via admin messages Device is managed by a mesh administrator. */ bool is_managed; @@ -660,7 +660,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} -#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} +#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} @@ -671,7 +671,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} -#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}}, 0, 0, 0, 0} +#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ @@ -953,7 +953,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_NetworkConfig_size 196 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 -#define meshtastic_Config_SecurityConfig_size 111 +#define meshtastic_Config_SecurityConfig_size 178 #define meshtastic_Config_SessionkeyConfig_size 0 #define meshtastic_Config_size 199 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 209084220..f87c84abe 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -358,7 +358,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3500 +#define meshtastic_OEMStore_size 3568 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 72f29500c..19600856f 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -186,8 +186,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 667 +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define meshtastic_LocalConfig_size 735 #define meshtastic_LocalModuleConfig_size 687 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 948e89f22..f4e0a896b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -316,7 +316,11 @@ typedef enum _meshtastic_Routing_Error { /* The client specified a PKI transport, but the node was unable to send the packet using PKI (and did not send the message at all) */ meshtastic_Routing_Error_PKI_FAILED = 34, /* The receiving node does not have a Public Key to decode with */ - meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY = 35 + meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY = 35, + /* Admin packet otherwise checks out, but uses a bogus or expired session key */ + meshtastic_Routing_Error_ADMIN_BAD_SESSION_KEY = 36, + /* Admin packet sent using PKC, but not from a public key on the admin key list */ + meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37 } meshtastic_Routing_Error; /* The priority of this message for sending. @@ -1026,8 +1030,8 @@ extern "C" { #define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1)) #define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE -#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY -#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY+1)) +#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED +#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED+1)) #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX From 6f1db6fc630a69ee506abc5b0c462488e450850d Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 17 Sep 2024 08:37:12 +0800 Subject: [PATCH 1118/1377] Fix I2C address for QMC5883L. QMC5883L and HMC5883L are 3-axis compasses which are often confused. As reported by @Africmod, we had the wrong I2C address for the QMC5883L. This patch fixes the address and adds its HMC5883L so we keep info about both. Fixes https://github.com/meshtastic/firmware/issues/4144 --- src/configuration.h | 5 +++-- src/detect/ScanI2C.h | 3 ++- src/detect/ScanI2CTwoWire.cpp | 1 + src/main.cpp | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 72420cc59..349bd2870 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -124,7 +124,8 @@ along with this program. If not, see . #define INA3221_ADDR 0x42 #define QMC6310_ADDR 0x1C #define QMI8658_ADDR 0x6B -#define QMC5883L_ADDR 0x1E +#define QMC5883L_ADDR 0x0D +#define HMC5883L_ADDR 0x1E #define SHTC3_ADDR 0x70 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D @@ -331,4 +332,4 @@ along with this program. If not, see . #endif #include "DebugConfiguration.h" -#include "RF95Configuration.h" \ No newline at end of file +#include "RF95Configuration.h" diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 743de7a9a..090b1a968 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -36,6 +36,7 @@ class ScanI2C QMC6310, QMI8658, QMC5883L, + HMC5883L, PMSA0031, MPU6050, LIS3DH, @@ -118,4 +119,4 @@ class ScanI2C private: bool shouldSuppressScreen = false; -}; \ No newline at end of file +}; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 48341034b..f09eb3b95 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -382,6 +382,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n") + SCAN_SIMPLE_CASE(HMC5883L_ADDR, HMC5883L, "HMC5883L 3-Axis digital compass found\n") SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n"); diff --git a/src/main.cpp b/src/main.cpp index df90c4722..2c8c0dcb8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -560,6 +560,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700) @@ -1144,4 +1145,4 @@ void loop() } // if (didWake) LOG_DEBUG("wake!\n"); } -#endif \ No newline at end of file +#endif From b025eeb13cb299cfc9caa109cec29673929ddd6d Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:11:57 +0100 Subject: [PATCH 1119/1377] Update variant.h --- variants/rak11200/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak11200/variant.h b/variants/rak11200/variant.h index 259fa6e87..01edb8b73 100644 --- a/variants/rak11200/variant.h +++ b/variants/rak11200/variant.h @@ -70,12 +70,12 @@ static const uint8_t SCK = 33; #define LORA_CS SS #define USE_SX1262 -#define SX126X_ANT_SW WB_IO2 +#define SX126X_ANT_SW WB_IO3 #define SX126X_CS SS // NSS for SX126X #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET -#define SX126X_POWER_EN WB_IO3 +#define SX126X_POWER_EN WB_IO2 // DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 From cd6bd1e9a33c831c6220f997819ae78b2c9f1e21 Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:24:31 +0100 Subject: [PATCH 1120/1377] Update main.cpp --- src/main.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2c8c0dcb8..b6cfd91aa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -764,10 +764,11 @@ void setup() screen->print("Started...\n"); -#ifdef SX126X_ANT_SW - // make analog PA vs not PA switch on SX126x eval board work properly +// FIXME: move to SX126XInterface.cpp +// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually dealt in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to the RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself. One solution would be to set that pin as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=0 has the opposite and stable RF path effect as CTRL=1 and ¬CTRL=1, this depends on the RF switch, but for the cases where this works, this pin can be used. Better hardware design, which is done most the time, prevents this issue. +#ifdef SX126X_ANT_SW // Add RADIOLIB_NC check, and beforehand define as such if it is undefined. + digitalWrite(SX126X_ANT_SW, HIGH); pinMode(SX126X_ANT_SW, OUTPUT); - digitalWrite(SX126X_ANT_SW, 1); #endif #ifdef PIN_PWR_DELAY_MS From af3048561185a3450e044ff00467fe4e9917703e Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:27:45 +0100 Subject: [PATCH 1121/1377] Update main.cpp --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index b6cfd91aa..f88fa890e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -765,7 +765,7 @@ void setup() screen->print("Started...\n"); // FIXME: move to SX126XInterface.cpp -// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually dealt in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to the RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself. One solution would be to set that pin as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=0 has the opposite and stable RF path effect as CTRL=1 and ¬CTRL=1, this depends on the RF switch, but for the cases where this works, this pin can be used. Better hardware design, which is done most the time, prevents this issue. +// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time, means this workaround is not necessary. #ifdef SX126X_ANT_SW // Add RADIOLIB_NC check, and beforehand define as such if it is undefined. digitalWrite(SX126X_ANT_SW, HIGH); pinMode(SX126X_ANT_SW, OUTPUT); From 06cd9abd810781b991d17823ea7bb75a895d8570 Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:36:10 +0100 Subject: [PATCH 1122/1377] Update SX126xInterface.cpp --- src/mesh/SX126xInterface.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index fcb3e4edb..6f9a455b7 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -25,9 +25,16 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs /// \return true if initialisation succeeded. template bool SX126xInterface::init() { -#ifdef SX126X_POWER_EN - pinMode(SX126X_POWER_EN, OUTPUT); + +// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time, means this workaround is not necessary. +#ifdef SX126X_ANT_SW // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly used and not part of the 'default' set of pin definitions. + digitalWrite(SX126X_ANT_SW, HIGH); + pinMode(SX126X_ANT_SW, OUTPUT); +#endif + +#ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly used and not part of the 'default' set of pin definitions. digitalWrite(SX126X_POWER_EN, HIGH); + pinMode(SX126X_POWER_EN, OUTPUT); #endif #if ARCH_PORTDUINO From 34a543ec74967d6848ee7fb1fa73b4213845826e Mon Sep 17 00:00:00 2001 From: S5NC <145265251+S5NC@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:36:52 +0100 Subject: [PATCH 1123/1377] Update main.cpp --- src/main.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f88fa890e..86268ecac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -764,13 +764,6 @@ void setup() screen->print("Started...\n"); -// FIXME: move to SX126XInterface.cpp -// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time, means this workaround is not necessary. -#ifdef SX126X_ANT_SW // Add RADIOLIB_NC check, and beforehand define as such if it is undefined. - digitalWrite(SX126X_ANT_SW, HIGH); - pinMode(SX126X_ANT_SW, OUTPUT); -#endif - #ifdef PIN_PWR_DELAY_MS // This may be required to give the peripherals time to power up. delay(PIN_PWR_DELAY_MS); From bc753e69032c022288d57019f3bf7151bb69afe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 12 Sep 2024 20:50:07 +0200 Subject: [PATCH 1124/1377] trunk fmt --- src/mesh/SX126xInterface.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 6f9a455b7..6d23206bd 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -26,13 +26,22 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs template bool SX126xInterface::init() { -// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time, means this workaround is not necessary. -#ifdef SX126X_ANT_SW // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly used and not part of the 'default' set of pin definitions. +// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO +// paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of +// RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and +// DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be +// to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another +// PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and +// ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time, +// means this workaround is not necessary. +#ifdef SX126X_ANT_SW // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly + // used and not part of the 'default' set of pin definitions. digitalWrite(SX126X_ANT_SW, HIGH); pinMode(SX126X_ANT_SW, OUTPUT); #endif - -#ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly used and not part of the 'default' set of pin definitions. + +#ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly + // used and not part of the 'default' set of pin definitions. digitalWrite(SX126X_POWER_EN, HIGH); pinMode(SX126X_POWER_EN, OUTPUT); #endif From 11378325e0d2c911d3bd35dbbfafbc6f818268e4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 17 Sep 2024 06:29:18 -0500 Subject: [PATCH 1125/1377] Separate GPS and NTP RTCQuality logic and allow GPS time to always set us (#4721) --- src/gps/RTC.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 42a98f568..728284242 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -124,8 +124,11 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) } else if (q > currentQuality) { shouldSet = true; LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q)); - } else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { - // Every 12 hrs we will slam in a new GPS or Phone GPS / NTP time, to correct for local RTC clock drift + } else if (q == RTCQualityGPS) { + shouldSet = true; + LOG_DEBUG("Reapplying GPS time: %ld secs\n", printableEpoch); + } else if (q == RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { + // Every 12 hrs we will slam in a new NTP or Phone GPS / NTP time, to correct for local RTC clock drift shouldSet = true; LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", printableEpoch); } else { From a967dd52f31a4ea00e79219dde18499fea803c76 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 17 Sep 2024 06:31:39 -0500 Subject: [PATCH 1126/1377] More useful PKC logging (#4742) --- src/mesh/CryptoEngine.cpp | 9 +++++---- src/mesh/Router.cpp | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index a5322a65a..79666b321 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -84,7 +84,7 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ // Calculate the shared secret with the destination node and encrypt printBytes("Attempting encrypt using nonce: ", nonce, 13); - printBytes("Attempting encrypt using shared_key: ", shared_key, 32); + printBytes("Attempting encrypt using shared_key starting with: ", shared_key, 8); aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); // this can write up to 15 bytes longer than numbytes past bytesOut *extraNonce = extraNonceTmp; @@ -117,7 +117,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size } initNonce(fromNode, packetNum, *extraNonce); printBytes("Attempting decrypt using nonce: ", nonce, 13); - printBytes("Attempting decrypt using shared_key: ", shared_key, 32); + printBytes("Attempting decrypt using shared_key starting with: ", shared_key, 8); return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 12, nullptr, 0, auth, bytesOut); } @@ -137,11 +137,12 @@ bool CryptoEngine::setDHKey(uint32_t nodeNum) LOG_DEBUG("Node %d or their public_key not found\n", nodeNum); return false; } - + printBytes("Generating DH with remote pubkey: ", node->user.public_key.bytes, 32); + printBytes("And local pubkey: ", config.security.public_key.bytes, 32); if (!setDHPublicKey(node->user.public_key.bytes)) return false; - printBytes("DH Output: ", shared_key, 32); + // printBytes("DH Output: ", shared_key, 32); /** * D.J. Bernstein reccomends hashing the shared key. We want to do this because there are diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index deb4ef2bf..3cf30519b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -347,8 +347,11 @@ bool perhapsDecode(meshtastic_MeshPacket *p) // memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers // chIndex = 8; } else { + LOG_ERROR("PKC Decrypted, but pb_decode failed!\n"); return false; } + } else { + LOG_WARN("PKC decrypt attempted but failed!\n"); } } #endif From be306cc3849d080bc2fb6549ee16c6b39e5ca94e Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 17 Sep 2024 19:48:56 +0800 Subject: [PATCH 1127/1377] Increase GPS FIFO Buffer Size for RP2040 (#4741) As discovered and tested by @Mictronics, default Serial FIFO size on the Pico is 32bytes, which is not enough for GPS messages. This patch increases the Serial GPS FIFO buffer size to 256 for the RP2040 Architecture fixes https://github.com/meshtastic/firmware/issues/3989 --- src/gps/GPS.cpp | 17 +++++++++++++++-- src/gps/GPS.h | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 8abb4edb1..01fa65816 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -26,6 +26,8 @@ #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) HardwareSerial *GPS::_serial_gps = &Serial1; +#elif defined(ARCH_RP2040) +SerialUART *GPS::_serial_gps = &Serial1; #else HardwareSerial *GPS::_serial_gps = NULL; #endif @@ -1198,9 +1200,13 @@ int GPS::prepareDeepSleep(void *unused) GnssModel_t GPS::probe(int serialSpeed) { -#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) +#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) _serial_gps->end(); _serial_gps->begin(serialSpeed); +#elif defined(ARCH_RP2040) + _serial_gps->end(); + _serial_gps->setFIFOSize(256); + _serial_gps->begin(serialSpeed); #else if (_serial_gps->baudRate() != serialSpeed) { LOG_DEBUG("Setting Baud to %i\n", serialSpeed); @@ -1265,9 +1271,13 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->write(_message_prt, sizeof(_message_prt)); delay(500); serialSpeed = 9600; -#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL) +#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) _serial_gps->end(); _serial_gps->begin(serialSpeed); +#elif defined(ARCH_RP2040) + _serial_gps->end(); + _serial_gps->setFIFOSize(256); + _serial_gps->begin(serialSpeed); #else _serial_gps->updateBaudRate(serialSpeed); #endif @@ -1428,6 +1438,9 @@ GPS *GPS::createGps() LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio); LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio); _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); +#elif defined(ARCH_RP2040) + _serial_gps->setFIFOSize(256); + _serial_gps->begin(GPS_BAUDRATE); #else _serial_gps->begin(GPS_BAUDRATE); #endif diff --git a/src/gps/GPS.h b/src/gps/GPS.h index caff48f32..3423edb68 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -102,7 +102,7 @@ class GPS : private concurrency::OSThread public: /** If !NULL we will use this serial port to construct our GPS */ -#if defined(RPI_PICO_WAVESHARE) +#if defined(ARCH_RP2040) static SerialUART *_serial_gps; #else static HardwareSerial *_serial_gps; From db4dc88d6f1e624a03cf10e35233463a5620d60e Mon Sep 17 00:00:00 2001 From: Andre K Date: Tue, 17 Sep 2024 08:50:49 -0300 Subject: [PATCH 1128/1377] feat: enable remote admin to set/clear fixed positions (#4713) Co-authored-by: Ben Meadors --- src/modules/AdminModule.cpp | 38 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 4deb99eb7..7b96d9c43 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -257,34 +257,26 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_set_fixed_position_tag: { - if (fromOthers) { - LOG_INFO("Ignoring set_fixed_position command from another node.\n"); - } else { - LOG_INFO("Client is receiving a set_fixed_position command.\n"); - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); - node->has_position = true; - node->position = TypeConversions::ConvertToPositionLite(r->set_fixed_position); - nodeDB->setLocalPosition(r->set_fixed_position); - config.position.fixed_position = true; - saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); + LOG_INFO("Client is receiving a set_fixed_position command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); + node->has_position = true; + node->position = TypeConversions::ConvertToPositionLite(r->set_fixed_position); + nodeDB->setLocalPosition(r->set_fixed_position); + config.position.fixed_position = true; + saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); #if !MESHTASTIC_EXCLUDE_GPS - if (gps != nullptr) - gps->enable(); - // Send our new fixed position to the mesh for good measure - positionModule->sendOurPosition(); + if (gps != nullptr) + gps->enable(); + // Send our new fixed position to the mesh for good measure + positionModule->sendOurPosition(); #endif - } break; } case meshtastic_AdminMessage_remove_fixed_position_tag: { - if (fromOthers) { - LOG_INFO("Ignoring remove_fixed_position command from another node.\n"); - } else { - LOG_INFO("Client is receiving a remove_fixed_position command.\n"); - nodeDB->clearLocalPosition(); - config.position.fixed_position = false; - saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); - } + LOG_INFO("Client is receiving a remove_fixed_position command.\n"); + nodeDB->clearLocalPosition(); + config.position.fixed_position = false; + saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); break; } case meshtastic_AdminMessage_set_time_only_tag: { From a47570d65a09275caac90be91956fd99be7af297 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 06:52:42 -0500 Subject: [PATCH 1129/1377] [create-pull-request] automated change (#4746) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 7 ++++--- src/mesh/generated/meshtastic/mesh.pb.h | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index c5108cfd6..1e212f113 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c5108cfd6bbc59adef44575dcec3067cbfbfeac1 +Subproject commit 1e212f113583dfd8d21a0daa47a32b080d3f842f diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index f87c84abe..a905c4526 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -94,6 +94,7 @@ typedef struct _meshtastic_NodeInfoLite { /* True if we witnessed the node over MQTT instead of LoRA transport */ bool via_mqtt; /* Number of hops away from us this node is (0 if adjacent) */ + bool has_hops_away; uint8_t hops_away; /* True if node is in our favorites list Persists between NodeDB internal clean ups */ @@ -202,13 +203,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0} #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -287,7 +288,7 @@ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ X(a, STATIC, SINGULAR, UINT32, channel, 7) \ X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ -X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index f4e0a896b..44159858b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -745,6 +745,7 @@ typedef struct _meshtastic_NodeInfo { /* True if we witnessed the node over MQTT instead of LoRA transport */ bool via_mqtt; /* Number of hops away from us this node is (0 if adjacent) */ + bool has_hops_away; uint8_t hops_away; /* True if node is in our favorites list Persists between NodeDB internal clean ups */ @@ -1093,7 +1094,7 @@ extern "C" { #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -1118,7 +1119,7 @@ extern "C" { #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -1415,7 +1416,7 @@ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ X(a, STATIC, SINGULAR, UINT32, channel, 7) \ X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ -X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL From 2a6921292e77194bb199a4697eace5c47a2c277c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 17 Sep 2024 10:05:55 -0500 Subject: [PATCH 1130/1377] Implement CoT detail support in TAKPacket (#4748) * Implement CoT detail support in TAKPacket * dest, src * More coffee is needed * SAVE --- src/modules/AtakPluginModule.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index 437a341db..72d069619 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -52,6 +52,10 @@ meshtastic_TAKPacket AtakPluginModule::cloneTAKPacketData(meshtastic_TAKPacket * } else if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) { clone.which_payload_variant = meshtastic_TAKPacket_chat_tag; clone.payload_variant.chat = {0}; + } else if (t->which_payload_variant == meshtastic_TAKPacket_detail_tag) { + clone.which_payload_variant = meshtastic_TAKPacket_detail_tag; + clone.payload_variant.detail.size = t->payload_variant.detail.size; + memcpy(clone.payload_variant.detail.bytes, t->payload_variant.detail.bytes, t->payload_variant.detail.size); } return clone; From f5016763fdda5b428dd62caaf1ab9e2550bdd08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 17 Sep 2024 17:33:21 +0200 Subject: [PATCH 1131/1377] change evaluation order to silence warning about "found" (#4749) --- src/mesh/NodeDB.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5d1db88ae..7e8abb747 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -654,8 +654,8 @@ void NodeDB::pickNewNodeNum() } meshtastic_NodeInfoLite *found; - while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) || - ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0)) { + while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) || + (nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) { NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so " "trying for 0x%x\n", From 50fb4ab22ac412f7aec766e7cad2313109f9510a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 17 Sep 2024 12:08:04 -0500 Subject: [PATCH 1132/1377] Implement optional hops_away on NodeInfo/Lite (#4747) * Implement optional hops_away on NodeInfo/Lite * Trunk --- src/mesh/NodeDB.cpp | 4 +++- src/mesh/PhoneAPI.cpp | 2 +- src/mesh/TypeConversions.cpp | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 7e8abb747..dca639070 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1112,8 +1112,10 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT // If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway - if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) + if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) { + info->has_hops_away = true; info->hops_away = mp.hop_start - mp.hop_limit; + } } } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 30af9d7b0..742bdbf34 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -194,7 +194,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) auto us = nodeDB->readNextMeshNode(readIndex); if (us) { nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us); - nodeInfoForPhone.hops_away = 0; + nodeInfoForPhone.has_hops_away = false; nodeInfoForPhone.is_favorite = true; fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; fromRadioScratch.node_info = nodeInfoForPhone; diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 6a90ac703..550f87021 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -11,9 +11,13 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo info.last_heard = lite->last_heard; info.channel = lite->channel; info.via_mqtt = lite->via_mqtt; - info.hops_away = lite->hops_away; info.is_favorite = lite->is_favorite; + if (lite->has_hops_away) { + info.has_hops_away = true; + info.hops_away = lite->hops_away; + } + if (lite->has_position) { info.has_position = true; if (lite->position.latitude_i != 0) From 923458bc18fc74462f8d09b3a9990fd543fb5611 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:55:16 -0500 Subject: [PATCH 1133/1377] [create-pull-request] automated change (#4754) Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/protobufs b/protobufs index 1e212f113..164e59873 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 1e212f113583dfd8d21a0daa47a32b080d3f842f +Subproject commit 164e598734a813ae00a2a74266e9e06438f030ce diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 44159858b..921dfa55b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -876,6 +876,8 @@ typedef struct _meshtastic_DeviceMetadata { meshtastic_HardwareModel hw_model; /* Has Remote Hardware enabled */ bool hasRemoteHardware; + /* Has PKC capabilities */ + bool hasPKC; } meshtastic_DeviceMetadata; /* Packets from the radio to the phone will appear on the fromRadio characteristic. @@ -1105,7 +1107,7 @@ extern "C" { #define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} #define meshtastic_Neighbor_init_default {0, 0, 0, 0} -#define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0, 0} #define meshtastic_Heartbeat_init_default {0} #define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} #define meshtastic_ChunkedPayload_init_default {0, 0, 0, {0, {0}}} @@ -1130,7 +1132,7 @@ extern "C" { #define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}} #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} #define meshtastic_Neighbor_init_zero {0, 0, 0, 0} -#define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0, 0} #define meshtastic_Heartbeat_init_zero {0} #define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero} #define meshtastic_ChunkedPayload_init_zero {0, 0, 0, {0, {0}}} @@ -1261,6 +1263,7 @@ extern "C" { #define meshtastic_DeviceMetadata_position_flags_tag 8 #define meshtastic_DeviceMetadata_hw_model_tag 9 #define meshtastic_DeviceMetadata_hasRemoteHardware_tag 10 +#define meshtastic_DeviceMetadata_hasPKC_tag 11 #define meshtastic_FromRadio_id_tag 1 #define meshtastic_FromRadio_packet_tag 2 #define meshtastic_FromRadio_my_info_tag 3 @@ -1541,7 +1544,8 @@ X(a, STATIC, SINGULAR, BOOL, hasEthernet, 6) \ X(a, STATIC, SINGULAR, UENUM, role, 7) \ X(a, STATIC, SINGULAR, UINT32, position_flags, 8) \ X(a, STATIC, SINGULAR, UENUM, hw_model, 9) \ -X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) +X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) \ +X(a, STATIC, SINGULAR, BOOL, hasPKC, 11) #define meshtastic_DeviceMetadata_CALLBACK NULL #define meshtastic_DeviceMetadata_DEFAULT NULL @@ -1640,7 +1644,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ClientNotification_size 415 #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 273 -#define meshtastic_DeviceMetadata_size 46 +#define meshtastic_DeviceMetadata_size 48 #define meshtastic_FileInfo_size 236 #define meshtastic_FromRadio_size 510 #define meshtastic_Heartbeat_size 0 From c78302a2ee979925e7aa9d008350c0495110a424 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 17 Sep 2024 19:34:05 -0500 Subject: [PATCH 1134/1377] Add hasPKC to deviceMetadata (#4755) --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 86268ecac..a6e6ad631 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1091,6 +1091,9 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() deviceMetadata.position_flags = config.position.position_flags; deviceMetadata.hw_model = HW_VENDOR; deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled; +#if !(MESHTASTIC_EXCLUDE_PKI) + deviceMetadata.hasPKC = true; +#endif return deviceMetadata; } #ifndef PIO_UNIT_TESTING From 2d52803dbde083d3e2ddce29cc3314e75d9f2fb5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 17 Sep 2024 21:09:24 -0500 Subject: [PATCH 1135/1377] Add new admin error types (#4750) Co-authored-by: Ben Meadors --- src/modules/AdminModule.cpp | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 7b96d9c43..48048b054 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -73,12 +73,38 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta meshtastic_Channel *ch = &channels.getByIndex(mp.channel); // Could tighten this up further by tracking the last public_key we went an AdminMessage request to // and only allowing responses from that remote. - if (!((mp.from == 0 && !config.security.is_managed) || messageIsResponse(r) || - (strcasecmp(ch->settings.name, Channels::adminChannel) == 0 && config.security.admin_channel_enabled) || - (mp.pki_encrypted && memcmp(mp.public_key.bytes, config.security.admin_key[0].bytes, 32) == 0))) { - LOG_INFO("Ignoring admin payload %i\n", r->which_payload_variant); + if (messageIsResponse(r)) { + LOG_DEBUG("Allowing admin response message\n"); + } else if (mp.from == 0) { + if (config.security.is_managed) { + LOG_INFO("Ignoring local admin payload because is_managed.\n"); + return handled; + } + } else if (strcasecmp(ch->settings.name, Channels::adminChannel) == 0) { + if (!config.security.admin_channel_enabled) { + LOG_INFO("Ignoring admin channel, as legacy admin is disabled.\n"); + myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp); + return handled; + } + } else if (mp.pki_encrypted) { + if ((config.security.admin_key[0].size == 32 && + memcmp(mp.public_key.bytes, config.security.admin_key[0].bytes, 32) == 0) || + (config.security.admin_key[1].size == 32 && + memcmp(mp.public_key.bytes, config.security.admin_key[1].bytes, 32) == 0) || + (config.security.admin_key[2].size == 32 && + memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) { + LOG_INFO("PKC admin payload with authorized sender key.\n"); + } else { + myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp); + LOG_INFO("Received PKC admin payload, but the sender public key does not match the admin authorized key!\n"); + return handled; + } + } else { + LOG_INFO("Ignoring unauthorized admin payload %i\n", r->which_payload_variant); + myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp); return handled; } + LOG_INFO("Handling admin payload %i\n", r->which_payload_variant); // all of the get and set messages, including those for other modules, flow through here first. @@ -86,6 +112,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta if (mp.from != 0 && !messageIsRequest(r) && !messageIsResponse(r)) { if (!checkPassKey(r)) { LOG_WARN("Admin message without session_key!\n"); + myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_BAD_SESSION_KEY, &mp); return handled; } } From 4289cb089b92024a0945a1c8fbbe55cb66b88bcd Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 18 Sep 2024 00:17:48 -0500 Subject: [PATCH 1136/1377] Update package_raspbian.yml to build on self-hosted (#4761) * Update package_raspbian.yml to build on self-hosted * Update package_raspbian_armv7l.yml to use self hosted --- .github/workflows/package_raspbian.yml | 2 +- .github/workflows/package_raspbian_armv7l.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 5471332c5..ab541899f 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_raspbian.yml package-raspbian: - runs-on: ubuntu-latest + runs-on: [linux] needs: build-raspbian steps: - name: Checkout code diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml index 5b9c9aa71..cadce17c3 100644 --- a/.github/workflows/package_raspbian_armv7l.yml +++ b/.github/workflows/package_raspbian_armv7l.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_raspbian_armv7l.yml package-raspbian_armv7l: - runs-on: ubuntu-latest + runs-on: [linux] needs: build-raspbian_armv7l steps: - name: Checkout code From 3eebdcefa417619549163469aa70624c6178f777 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 18 Sep 2024 00:28:54 -0500 Subject: [PATCH 1137/1377] More CI fun --- .github/workflows/build_esp32.yml | 2 +- .github/workflows/package_raspbian.yml | 2 +- .github/workflows/package_raspbian_armv7l.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 4cbb4c7a4..68525fc7d 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -9,7 +9,7 @@ on: jobs: build-esp32: - runs-on: ubuntu-latest + runs-on: [linux, x64] steps: - uses: actions/checkout@v4 - name: Build base diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index ab541899f..5471332c5 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_raspbian.yml package-raspbian: - runs-on: [linux] + runs-on: ubuntu-latest needs: build-raspbian steps: - name: Checkout code diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml index cadce17c3..5b9c9aa71 100644 --- a/.github/workflows/package_raspbian_armv7l.yml +++ b/.github/workflows/package_raspbian_armv7l.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_raspbian_armv7l.yml package-raspbian_armv7l: - runs-on: [linux] + runs-on: ubuntu-latest needs: build-raspbian_armv7l steps: - name: Checkout code From c6196b226062fa650c943d1778d590f9415bb43c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 18 Sep 2024 01:11:08 -0500 Subject: [PATCH 1138/1377] Update build_esp32.yml -- less CI fun --- .github/workflows/build_esp32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 68525fc7d..4cbb4c7a4 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -9,7 +9,7 @@ on: jobs: build-esp32: - runs-on: [linux, x64] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build base From 19c57e8ec6746d1769533ca1d2615eda0313222a Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 18 Sep 2024 22:05:32 +0800 Subject: [PATCH 1139/1377] Fix Chatter 2 blank screen on boot (#4759) As reported by @eureekasigns and @GPSFan, Chatter 2 had begun to show a blank screen on boot after recent TFT display changes. Setting TFT_BACKLIGHT_ON LOW resolves the issue. Fixes https://github.com/meshtastic/firmware/issues/4751 --- variants/chatter2/variant.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/chatter2/variant.h b/variants/chatter2/variant.h index b7f946970..5c27e2fb5 100644 --- a/variants/chatter2/variant.h +++ b/variants/chatter2/variant.h @@ -66,6 +66,7 @@ #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 // fps #define DISPLAY_FORCE_SMALL_FONTS +#define TFT_BACKLIGHT_ON LOW // Battery @@ -121,4 +122,4 @@ // cannot serve any extra function even if requested to LORA_DIO3 value is never used in src (as we are not using RF95), so no // need to define, and DIO3_AS_TCXO_AT_1V8 is set so it cannot serve any extra function even if requested to (from 13.3.2.1 // DioxMask in SX1262 datasheet: Note that if DIO2 or DIO3 are used to control the RF Switch or the TCXO, the IRQ will not be -// generated even if it is mapped to the pins.) \ No newline at end of file +// generated even if it is mapped to the pins.) From 35e1c401e2f6b041a87546c44f3f79fd624c33d4 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Wed, 18 Sep 2024 23:12:49 +0800 Subject: [PATCH 1140/1377] PMSA0031 sensors require ~3secs before coming up on I2C (#4743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PMSA0031 sensors require ~3secs before coming up on I2C As reported by @MALAONE1 and debugged by @shodan8192 , PMSA0031s on a RAK4631 take 3 seconds before they can become detectable on I2c. Add a delay(4000) before I2C scan if the air quality sensor pin is defined. Fixes https://github.com/meshtastic/firmware/issues/3690 * Remove 4 second wait and rescan during air quality init for the sensor * works without but this triggers my OCD --------- Co-authored-by: Thomas Göttgens Co-authored-by: Ben Meadors --- src/main.cpp | 4 +-- src/modules/Telemetry/AirQualityTelemetry.cpp | 25 +++++++++++++++---- src/modules/Telemetry/AirQualityTelemetry.h | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a6e6ad631..e24ba68b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -390,7 +390,7 @@ void setup() #endif #ifdef AQ_SET_PIN - // RAK-12039 set pin for Air quality sensor + // RAK-12039 set pin for Air quality sensor. Detectable on I2C after ~3 seconds, so we need to rescan later pinMode(AQ_SET_PIN, OUTPUT); digitalWrite(AQ_SET_PIN, HIGH); #endif @@ -1142,4 +1142,4 @@ void loop() } // if (didWake) LOG_DEBUG("wake!\n"); } -#endif +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index d07296710..56d308cfa 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -10,11 +10,11 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "detect/ScanI2CTwoWire.h" #include "main.h" int32_t AirQualityTelemetryModule::runOnce() { - int32_t result = INT32_MAX; /* Uncomment the preferences below if you want to use the module without having to configure it from the PythonAPI or WebUI. @@ -29,21 +29,36 @@ int32_t AirQualityTelemetryModule::runOnce() if (firstTime) { // This is the first time the OSThread library has called this function, so do some setup - firstTime = 0; + firstTime = false; if (moduleConfig.telemetry.air_quality_enabled) { LOG_INFO("Air quality Telemetry: Initializing\n"); if (!aqi.begin_I2C()) { - LOG_WARN("Could not establish i2c connection to AQI sensor\n"); + LOG_WARN("Could not establish i2c connection to AQI sensor. Rescanning...\n"); + // rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty. + uint8_t i2caddr_scan[] = {PMSA0031_ADDR}; + uint8_t i2caddr_asize = 1; + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); +#if defined(I2C_SDA1) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); + auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031); + if (found.type != ScanI2C::DeviceType::NONE) { + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address; + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second = + i2cScanner->fetchI2CBus(found.address); + return 1000; + } return disable(); } return 1000; } - return result; + return disable(); } else { // if we somehow got to a second run of this module with measurement disabled, then just wait forever if (!moduleConfig.telemetry.air_quality_enabled) - return result; + return disable(); uint32_t now = millis(); if (((lastSentToMesh == 0) || diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 23df6ac58..fb8edd07e 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -44,7 +44,7 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf private: Adafruit_PM25AQI aqi; PM25_AQI_Data data = {0}; - bool firstTime = 1; + bool firstTime = true; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; From 777bcf691a1a033cc31f96820aa1681499493e85 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 18 Sep 2024 10:13:07 -0500 Subject: [PATCH 1141/1377] Initial PhoneAPI rate-limiting of messages on certain ports (#4756) --- src/mesh/Default.h | 2 ++ src/mesh/PhoneAPI.cpp | 26 +++++++++++++++++++++++++- src/mesh/PhoneAPI.h | 10 ++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/mesh/Default.h b/src/mesh/Default.h index bafa60898..5641b5d25 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -3,6 +3,8 @@ #include #define ONE_DAY 24 * 60 * 60 #define ONE_MINUTE_MS 60 * 1000 +#define THIRTY_SECONDS_MS 30 * 1000 +#define FIVE_SECONDS_MS 5 * 1000 #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) #define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 742bdbf34..121687c49 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -25,6 +25,7 @@ #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #endif +#include PhoneAPI::PhoneAPI() { @@ -541,14 +542,37 @@ bool PhoneAPI::available() return false; } +void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t replyId, const char *message) +{ + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->has_reply_id = true; + cn->reply_id = replyId; + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, message, sizeof(cn->message)); + service->sendClientNotification(cn); + delete cn; +} + /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) { printPacket("PACKET FROM PHONE", &p); + if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && lastPortNumToRadio[p.decoded.portnum] && + (millis() - lastPortNumToRadio[p.decoded.portnum]) < (THIRTY_SECONDS_MS)) { + LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); + sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "TraceRoute can only be sent once every 30 seconds"); + return false; + } else if (p.decoded.portnum == meshtastic_PortNum_POSITION_APP && lastPortNumToRadio[p.decoded.portnum] && + (millis() - lastPortNumToRadio[p.decoded.portnum]) < (FIVE_SECONDS_MS)) { + LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); + sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); + return false; + } + lastPortNumToRadio[p.decoded.portnum] = millis(); service->handleToRadio(p); - return true; } diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 5feb1c4bf..1e09b9535 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -2,8 +2,10 @@ #include "Observer.h" #include "mesh-pb-constants.h" +#include "meshtastic/portnums.pb.h" #include #include +#include #include // Make sure that we never let our packets grow too large for one BLE packet @@ -48,6 +50,9 @@ class PhoneAPI uint8_t config_state = 0; + // Hashmap of timestamps for last time we received a packet on the API per portnum + std::unordered_map lastPortNumToRadio; + /** * Each packet sent to the phone has an incrementing count */ @@ -99,6 +104,11 @@ class PhoneAPI */ virtual bool handleToRadio(const uint8_t *buf, size_t len); + /** + * Send a (client)notification to the phone + */ + virtual void sendNotification(meshtastic_LogRecord_Level level, uint32_t replyId, const char *message); + /** * Get the next packet we want to send to the phone * From deada41ceec0a783cb2c2770c935cd4fcd8abb36 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:49:12 -0500 Subject: [PATCH 1142/1377] [create-pull-request] automated change (#4765) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/clientonly.pb.h | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index 164e59873..5709c0a05 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 164e598734a813ae00a2a74266e9e06438f030ce +Subproject commit 5709c0a05eaefccbc9cb8ed3917adbf5fd134197 diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h index dc323292a..5720c1c02 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.h +++ b/src/mesh/generated/meshtastic/clientonly.pb.h @@ -5,6 +5,7 @@ #define PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED #include #include "meshtastic/localonly.pb.h" +#include "meshtastic/mesh.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -28,6 +29,15 @@ typedef struct _meshtastic_DeviceProfile { /* The ModuleConfig of the node */ bool has_module_config; meshtastic_LocalModuleConfig module_config; + /* Fixed position data */ + bool has_fixed_position; + meshtastic_Position fixed_position; + /* Ringtone for ExternalNotification */ + bool has_ringtone; + char ringtone[231]; + /* Predefined messages for CannedMessage */ + bool has_canned_messages; + char canned_messages[201]; } meshtastic_DeviceProfile; @@ -36,8 +46,8 @@ extern "C" { #endif /* Initializer values for message structs */ -#define meshtastic_DeviceProfile_init_default {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} -#define meshtastic_DeviceProfile_init_zero {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} +#define meshtastic_DeviceProfile_init_default {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_Position_init_default, false, "", false, ""} +#define meshtastic_DeviceProfile_init_zero {false, "", false, "", {{NULL}, NULL}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero, false, meshtastic_Position_init_zero, false, "", false, ""} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceProfile_long_name_tag 1 @@ -45,6 +55,9 @@ extern "C" { #define meshtastic_DeviceProfile_channel_url_tag 3 #define meshtastic_DeviceProfile_config_tag 4 #define meshtastic_DeviceProfile_module_config_tag 5 +#define meshtastic_DeviceProfile_fixed_position_tag 6 +#define meshtastic_DeviceProfile_ringtone_tag 7 +#define meshtastic_DeviceProfile_canned_messages_tag 8 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceProfile_FIELDLIST(X, a) \ @@ -52,11 +65,15 @@ X(a, STATIC, OPTIONAL, STRING, long_name, 1) \ X(a, STATIC, OPTIONAL, STRING, short_name, 2) \ X(a, CALLBACK, OPTIONAL, STRING, channel_url, 3) \ X(a, STATIC, OPTIONAL, MESSAGE, config, 4) \ -X(a, STATIC, OPTIONAL, MESSAGE, module_config, 5) +X(a, STATIC, OPTIONAL, MESSAGE, module_config, 5) \ +X(a, STATIC, OPTIONAL, MESSAGE, fixed_position, 6) \ +X(a, STATIC, OPTIONAL, STRING, ringtone, 7) \ +X(a, STATIC, OPTIONAL, STRING, canned_messages, 8) #define meshtastic_DeviceProfile_CALLBACK pb_default_field_callback #define meshtastic_DeviceProfile_DEFAULT NULL #define meshtastic_DeviceProfile_config_MSGTYPE meshtastic_LocalConfig #define meshtastic_DeviceProfile_module_config_MSGTYPE meshtastic_LocalModuleConfig +#define meshtastic_DeviceProfile_fixed_position_MSGTYPE meshtastic_Position extern const pb_msgdesc_t meshtastic_DeviceProfile_msg; From 2ebfcea94eaf8e4826ce52795c43c316aada0a39 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Wed, 18 Sep 2024 19:43:13 +0200 Subject: [PATCH 1143/1377] DetectionSensor: broadcast all state changes Closes #4753 --- src/modules/DetectionSensorModule.cpp | 14 ++++++++++---- src/modules/DetectionSensorModule.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index 20d91a381..d637fa7c6 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -49,10 +49,16 @@ int32_t DetectionSensorModule::runOnce() // LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin)); - if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && - hasDetectionEvent()) { - sendDetectionMessage(); - return DELAYED_INTERVAL; + if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs)) { + if (hasDetectionEvent()) { + wasDetected = true; + sendDetectionMessage(); + return DELAYED_INTERVAL; + } else if (wasDetected) { + wasDetected = false; + sendCurrentStateMessage(); + return DELAYED_INTERVAL; + } } // Even if we haven't detected an event, broadcast our current state to the mesh on the scheduled interval as a sort // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state diff --git a/src/modules/DetectionSensorModule.h b/src/modules/DetectionSensorModule.h index ed6cddda5..eb17bf3a2 100644 --- a/src/modules/DetectionSensorModule.h +++ b/src/modules/DetectionSensorModule.h @@ -15,6 +15,7 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh private: bool firstTime = true; uint32_t lastSentToMesh = 0; + bool wasDetected = false; void sendDetectionMessage(); void sendCurrentStateMessage(); bool hasDetectionEvent(); From cc89e85e712fec5fbf16d62a9207ca9636da6b6a Mon Sep 17 00:00:00 2001 From: David Huang Date: Thu, 19 Sep 2024 00:53:33 -0500 Subject: [PATCH 1144/1377] Another missed define for the T114 --- boards/heltec_mesh_node_t114.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/heltec_mesh_node_t114.json b/boards/heltec_mesh_node_t114.json index 5c97d8c75..2bd306eb9 100644 --- a/boards/heltec_mesh_node_t114.json +++ b/boards/heltec_mesh_node_t114.json @@ -5,7 +5,7 @@ }, "core": "nRF5", "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "extra_flags": "-DHELTEC_T114 -DNRF52840_XXAA", "f_cpu": "64000000L", "hwids": [ ["0x239A", "0x4405"], From 7c32ab3023c3e1a19c97bf9d4b724743124e2d48 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 19 Sep 2024 08:20:53 +0200 Subject: [PATCH 1145/1377] Update ms24sf1.json --- boards/ms24sf1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/ms24sf1.json b/boards/ms24sf1.json index 4e28507da..8356e3012 100644 --- a/boards/ms24sf1.json +++ b/boards/ms24sf1.json @@ -5,7 +5,7 @@ }, "core": "nRF5", "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "extra_flags": "-DMS24SF1 -DNRF52840_XXAA", "f_cpu": "64000000L", "hwids": [ ["0x239A", "0x8029"], From 7289b295e85caf13c28930c795c525a0aab4a396 Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 19 Sep 2024 08:20:57 +0200 Subject: [PATCH 1146/1377] Update me25ls01-4y10td.json --- boards/me25ls01-4y10td.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/me25ls01-4y10td.json b/boards/me25ls01-4y10td.json index 46c526a7c..9e1d63265 100644 --- a/boards/me25ls01-4y10td.json +++ b/boards/me25ls01-4y10td.json @@ -5,7 +5,7 @@ }, "core": "nRF5", "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "extra_flags": "-DME25LS01_4Y10TD -DNRF52840_XXAA", "f_cpu": "64000000L", "hwids": [ ["0x239A", "0x8029"], From 5c4c0965af040878e9270f8030673c0b3d099b0e Mon Sep 17 00:00:00 2001 From: Mark Trevor Birss Date: Thu, 19 Sep 2024 08:21:00 +0200 Subject: [PATCH 1147/1377] Update nordic_pca10059.json --- boards/nordic_pca10059.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/nordic_pca10059.json b/boards/nordic_pca10059.json index b99e3c763..6540817a2 100644 --- a/boards/nordic_pca10059.json +++ b/boards/nordic_pca10059.json @@ -5,7 +5,7 @@ }, "core": "nRF5", "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "extra_flags": "-DNORDIC_PCA10059 -DNRF52840_XXAA", "f_cpu": "64000000L", "hwids": [ ["0x239A", "0x8029"], From 6473cf0b698176fe0dc2fb8e88155b1087cfc7fb Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Thu, 19 Sep 2024 19:48:22 +0800 Subject: [PATCH 1148/1377] Update RadioLib to 7.0.0 (#4771) We were not too many commits behind, and the changes since then were either for LoraWAN or useful minor bug fixes for SX1280. --- platformio.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index d561aaf74..f38ec41e4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -88,8 +88,7 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = -; jgromes/RadioLib@~6.6.0 - https://github.com/jgromes/RadioLib.git#3115fc2d6700a9aee05888791ac930a910f2628f + jgromes/RadioLib@~7.0.0 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 @@ -162,4 +161,4 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 From 91b4199f9d63526f254f1ced374c20194a98b749 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 19 Sep 2024 10:46:18 -0500 Subject: [PATCH 1149/1377] Revert "DetectionSensor: broadcast all state changes" (#4776) --- src/modules/DetectionSensorModule.cpp | 14 ++++---------- src/modules/DetectionSensorModule.h | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index d637fa7c6..20d91a381 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -49,16 +49,10 @@ int32_t DetectionSensorModule::runOnce() // LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin)); - if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs)) { - if (hasDetectionEvent()) { - wasDetected = true; - sendDetectionMessage(); - return DELAYED_INTERVAL; - } else if (wasDetected) { - wasDetected = false; - sendCurrentStateMessage(); - return DELAYED_INTERVAL; - } + if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && + hasDetectionEvent()) { + sendDetectionMessage(); + return DELAYED_INTERVAL; } // Even if we haven't detected an event, broadcast our current state to the mesh on the scheduled interval as a sort // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state diff --git a/src/modules/DetectionSensorModule.h b/src/modules/DetectionSensorModule.h index eb17bf3a2..ed6cddda5 100644 --- a/src/modules/DetectionSensorModule.h +++ b/src/modules/DetectionSensorModule.h @@ -15,7 +15,6 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh private: bool firstTime = true; uint32_t lastSentToMesh = 0; - bool wasDetected = false; void sendDetectionMessage(); void sendCurrentStateMessage(); bool hasDetectionEvent(); From d3a293a0d82d044bee9c9894011862f0a38dafa6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 19 Sep 2024 12:10:39 -0500 Subject: [PATCH 1150/1377] Flag semgrep to not run on self-hosted The semgrep action runs inside a docker container, and docker in podman just doesn't work. --- .github/workflows/sec_sast_semgrep_pull.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml index b6c288494..2575cbf01 100644 --- a/.github/workflows/sec_sast_semgrep_pull.yml +++ b/.github/workflows/sec_sast_semgrep_pull.yml @@ -4,7 +4,7 @@ on: pull_request jobs: semgrep-diff: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: image: returntocorp/semgrep From 114df8cb1bfa1c7d124935b48e7faec0fba99fb3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 19 Sep 2024 13:29:17 -0500 Subject: [PATCH 1151/1377] Pin sensorlib version --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index f38ec41e4..9b869a036 100644 --- a/platformio.ini +++ b/platformio.ini @@ -156,7 +156,7 @@ lib_deps = https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 https://github.com/KodinLanewave/INA3221@^1.0.0 - lewisxhe/SensorLib@^0.2.0 + lewisxhe/SensorLib@0.2.0 mprograms/QMC5883LCompass@^1.2.0 From 0f3450ad441f766bd451e9f2d2cfea09b5730431 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 19 Sep 2024 18:21:30 -0500 Subject: [PATCH 1152/1377] Mark package workflows for gh hosted runners --- .github/workflows/package_amd64.yml | 2 +- .github/workflows/package_raspbian.yml | 2 +- .github/workflows/package_raspbian_armv7l.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml index ae7bf3242..0b5093f24 100644 --- a/.github/workflows/package_amd64.yml +++ b/.github/workflows/package_amd64.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_native.yml package-native: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: build-native steps: - name: Checkout code diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index 5471332c5..bcbda53e2 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_raspbian.yml package-raspbian: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: build-raspbian steps: - name: Checkout code diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml index 5b9c9aa71..1308fe925 100644 --- a/.github/workflows/package_raspbian_armv7l.yml +++ b/.github/workflows/package_raspbian_armv7l.yml @@ -13,7 +13,7 @@ jobs: uses: ./.github/workflows/build_raspbian_armv7l.yml package-raspbian_armv7l: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: build-raspbian_armv7l steps: - name: Checkout code From 921d92c649be1f7088e74e7d32cd0bed1fcfd373 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 20 Sep 2024 06:55:16 -0500 Subject: [PATCH 1153/1377] Drop received packets from self --- src/mqtt/MQTT.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 0f4c5a8c5..c15ac3325 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -158,6 +158,11 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); p->via_mqtt = true; // Mark that the packet was received via MQTT + if (p->from == 0 || p->from == nodeDB->getNodeNum()) { + LOG_INFO("Ignoring downlink message we originally sent.\n"); + packetPool.release(p); + return; + } if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { p->channel = ch.index; } From 85d722232e470ed22720b97e634098616350354a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 20 Sep 2024 07:22:11 -0500 Subject: [PATCH 1154/1377] Additional decoded packet ignores --- src/mqtt/MQTT.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index c15ac3325..6840700e5 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -164,6 +164,16 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) return; } if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + if (moduleConfig.mqtt.encryption_enabled) { + LOG_INFO("Ignoring decoded message on MQTT, encryption is enabled.\n"); + packetPool.release(p); + return; + } + if (p->decoded.portnum == meshtastic_PortNum_ADMIN_APP) { + LOG_INFO("Ignoring decoded admin packet.\n"); + packetPool.release(p); + return; + } p->channel = ch.index; } From 6ffdc9875bfadb2da05fffbcb9f6a3371bd9c448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 12 Sep 2024 22:42:10 +0200 Subject: [PATCH 1155/1377] First stab at ESP32-C6 support for TLora-C6 --- arch/esp32/esp32c6.ini | 47 ++++++++++++++++++++++++ src/gps/GPS.cpp | 5 ++- src/graphics/Screen.cpp | 28 +++++++------- src/main.cpp | 14 ++++--- src/mesh/NodeDB.cpp | 2 +- src/mesh/TypedQueue.h | 2 +- src/mesh/api/ServerAPI.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 3 +- src/modules/ExternalNotificationModule.h | 2 +- src/modules/PositionModule.cpp | 16 ++++---- src/modules/SerialModule.cpp | 21 ++++++++++- src/mqtt/MQTT.cpp | 4 ++ src/mqtt/MQTT.h | 4 ++ src/platform/esp32/main-esp32.cpp | 12 +++++- src/power.h | 1 + src/serialization/JSON.cpp | 2 +- src/sleep.cpp | 4 +- variants/tlora_c6/platformio.ini | 5 +++ variants/tlora_c6/variant.h | 16 ++++++++ 19 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 arch/esp32/esp32c6.ini create mode 100644 variants/tlora_c6/platformio.ini create mode 100644 variants/tlora_c6/variant.h diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini new file mode 100644 index 000000000..a7bcdc0e8 --- /dev/null +++ b/arch/esp32/esp32c6.ini @@ -0,0 +1,47 @@ +[esp32c6_base] +extends = esp32_base +platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 +build_flags = + ${arduino_base.build_flags} + -Wall + -Wextra + -Isrc/platform/esp32 + -std=c++11 + -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL + -DAXP_DEBUG_PORT=Serial + -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DSERIAL_BUFFER_SIZE=4096 + -DLIBPAX_ARDUINO + -DLIBPAX_WIFI + -DLIBPAX_BLE + -DMESHTASTIC_EXCLUDE_WEBSERVER + ;-DDEBUG_HEAP + ; TEMP + -DHAS_BLUETOOTH=0 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER + -DMESHTASTIC_EXCLUDE_BLUETOOTH + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${environmental_base.lib_deps} + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f + rweather/Crypto@^0.4.0 + +build_src_filter = + ${esp32_base.build_src_filter} - + +monitor_speed = 115200 +monitor_filters = esp32_c3_exception_decoder + +lib_ignore = + NonBlockingRTTTL + NimBLE-Arduino + libpax + \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 01fa65816..19cfa0044 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -60,7 +60,8 @@ const char *getGPSPowerStateString(GPSPowerState state) case GPS_OFF: return "OFF"; default: - assert(false); // Unhandled enum value.. + assert(false); // Unhandled enum value.. + return "FALSE"; // to make new ESP-IDF happy } } @@ -330,7 +331,7 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t { uint16_t ubxFrameCounter = 0; uint32_t startTime = millis(); - uint16_t needRead; + uint16_t needRead = 0; while (millis() - startTime < waitMillis) { if (_serial_gps->available()) { diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ae09ee408..07fb5d28e 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1004,55 +1004,55 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->setColor(WHITE); #ifndef EXCLUDE_EMOJI - if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) { + if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbup); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44E") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbdown); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"❓") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "❓") == 0) { display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height, question); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"‼️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "‼️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5, bang_width, bang_height, bang); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F4A9") == 0) { display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5, poo_width, poo_height, poo); } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\xa4\xa3") == 0) { display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5, haha_width, haha_height, haha); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44B") == 0) { display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width, wave_icon_height, wave_icon); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F920") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F920") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height, cowboy); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F42D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height, deadmau5); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xE2\x98\x80\xEF\xB8\x8F") == 0) { display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5, sun_width, sun_height, sun); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\u2614") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\u2614") == 0) { display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10, rain_width, rain_height, rain); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"☁️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "☁️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"🌫️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "🌫️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5, fog_width, fog_height, fog); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\x98\x88") == 0) { display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"♥️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "♥️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart); } else { diff --git a/src/main.cpp b/src/main.cpp index e24ba68b3..5057d7f57 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -322,15 +322,19 @@ void setup() #ifdef BUTTON_PIN #ifdef ARCH_ESP32 - // If the button is connected to GPIO 12, don't enable the ability to use - // meshtasticAdmin on the device. - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); - +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN #ifdef BUTTON_NEED_PULLUP gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); delay(10); #endif - +#endif #endif #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dca639070..12b731ab1 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -49,7 +49,7 @@ NodeDB *nodeDB = nullptr; // we have plenty of ram so statically alloc this tempbuf (for now) -EXT_RAM_ATTR meshtastic_DeviceState devicestate; +EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate; meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node; meshtastic_LocalConfig config; meshtastic_LocalModuleConfig moduleConfig; diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index c96edae8e..f7d016f10 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -14,7 +14,7 @@ */ template class TypedQueue { - static_assert(std::is_pod::value, "T must be pod"); + static_assert(std::is_standard_layout::value, "T must be standard layout"); QueueHandle_t h; concurrency::OSThread *reader = NULL; diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 3a483aac1..097f4fa21 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -45,7 +45,7 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { - auto client = U::available(); + auto client = U::accept(); if (client) { // Close any previous connection (see FIXME in header file) if (openAPI) { diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 07b03222e..e3203e6f7 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -309,7 +309,8 @@ static void WiFiEvent(WiFiEvent_t event) onNetworkConnected(); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - LOG_INFO("Obtained IP6 address: %s\n", WiFi.localIPv6().toString().c_str()); + LOG_INFO("Obtained Local IP6 address: %s\n", WiFi.linkLocalIPv6().toString().c_str()); + LOG_INFO("Obtained GlobalIP6 address: %s\n", WiFi.globalIPv6().toString().c_str()); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0\n"); diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 08e72c35a..a5dff3651 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -3,7 +3,7 @@ #include "SinglePortModule.h" #include "concurrency/OSThread.h" #include "configuration.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else // Noop class for portduino. diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index cb6a58b2e..2a962c268 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -273,7 +273,7 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() meshtastic_TAKPacket takPacket = {.is_compressed = true, .has_contact = true, - .contact = {0}, + .contact = meshtastic_Contact_init_default, .has_group = true, .group = {meshtastic_MemberRole_TeamMember, meshtastic_Team_Cyan}, .has_status = true, @@ -282,13 +282,13 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() .battery = powerStatus->getBatteryChargePercent(), }, .which_payload_variant = meshtastic_TAKPacket_pli_tag, - {.pli = { - .latitude_i = localPosition.latitude_i, - .longitude_i = localPosition.longitude_i, - .altitude = localPosition.altitude_hae, - .speed = localPosition.ground_speed, - .course = static_cast(localPosition.ground_track), - }}}; + .payload_variant = {.pli = { + .latitude_i = localPosition.latitude_i, + .longitude_i = localPosition.longitude_i, + .altitude = localPosition.altitude_hae, + .speed = localPosition.ground_speed, + .course = static_cast(localPosition.ground_track), + }}}; auto length = unishox2_compress_lines(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign, sizeof(takPacket.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f0ba64f65..549fcf1d7 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -62,6 +62,9 @@ SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial; +#elif defined(CONFIG_IDF_TARGET_ESP32C6) +SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("SerialModule") {} +static Print *serialPrint = &Serial1; #else SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial2; @@ -137,7 +140,16 @@ int32_t SerialModule::runOnce() // Give it a chance to flush out 💩 delay(10); } -#ifdef ARCH_ESP32 +#if defined(CONFIG_IDF_TARGET_ESP32C6) + if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { + Serial1.setRxBufferSize(RX_BUFFER); + Serial1.begin(baud, SERIAL_8N1, moduleConfig.serial.rxd, moduleConfig.serial.txd); + } else { + Serial.begin(baud); + Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); + } + +#elif defined(ARCH_ESP32) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { Serial2.setRxBufferSize(RX_BUFFER); @@ -205,8 +217,13 @@ int32_t SerialModule::runOnce() processWXSerial(); } else { +#if defined(CONFIG_IDF_TARGET_ESP32C6) + while (Serial1.available()) { + serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#else while (Serial2.available()) { serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#endif serialModuleRadio->sendPayload(); } } @@ -392,7 +409,7 @@ uint32_t SerialModule::getBaudRate() */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 6840700e5..240fae0f9 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -325,6 +325,7 @@ void MQTT::reconnect() mqttPassword = moduleConfig.mqtt.password; } #if HAS_WIFI && !defined(ARCH_PORTDUINO) +#ifndef CONFIG_IDF_TARGET_ESP32C6 if (moduleConfig.mqtt.tls_enabled) { // change default for encrypted to 8883 try { @@ -340,6 +341,9 @@ void MQTT::reconnect() LOG_INFO("Using non-TLS-encrypted session\n"); pubSub.setClient(mqttClient); } +#else + pubSub.setClient(mqttClient); +#endif #elif HAS_NETWORKING pubSub.setClient(mqttClient); #endif diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index ba0987783..c827e12ca 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -9,9 +9,11 @@ #if HAS_WIFI #include #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 #include #endif #endif +#endif #if HAS_ETHERNET #include #endif @@ -33,9 +35,11 @@ class MQTT : private concurrency::OSThread #if HAS_WIFI WiFiClient mqttClient; #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 WiFiClientSecure wifiSecureClient; #endif #endif +#endif #if HAS_ETHERNET EthernetClient mqttClient; #endif diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 0b6f7cf23..f16accab6 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -13,6 +13,7 @@ #include "mesh/wifi/WiFiAPClient.h" #endif +#include "esp_mac.h" #include "meshUtils.h" #include "sleep.h" #include "soc/rtc.h" @@ -147,9 +148,16 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 +#ifdef CONFIG_IDF_TARGET_ESP32C6 + esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); + wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; + wdt_config->trigger_panic = true; + res = esp_task_wdt_init(wdt_config); + assert(res == ESP_OK); +#else res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); assert(res == ESP_OK); - +#endif res = esp_task_wdt_add(NULL); assert(res == ESP_OK); @@ -223,7 +231,7 @@ void cpuDeepSleep(uint32_t msecToWake) // to detect wake and in normal operation the external part drives them hard. #ifdef BUTTON_PIN // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. -#if SOC_RTCIO_HOLD_SUPPORTED +#if SOC_RTCIO_HOLD_SUPPORTED && SOC_PM_SUPPORT_EXT_WAKEUP uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif diff --git a/src/power.h b/src/power.h index a4307ee07..5a41b55f2 100644 --- a/src/power.h +++ b/src/power.h @@ -5,6 +5,7 @@ #include "configuration.h" #ifdef ARCH_ESP32 +// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h #include #include #endif diff --git a/src/serialization/JSON.cpp b/src/serialization/JSON.cpp index e98bf47b9..42e615108 100644 --- a/src/serialization/JSON.cpp +++ b/src/serialization/JSON.cpp @@ -184,7 +184,7 @@ bool JSON::ExtractString(const char **data, std::string &str) // End of the string? else if (next_char == '"') { (*data)++; - str.reserve(); // Remove unused capacity + str.shrink_to_fit(); // Remove unused capacity return true; } diff --git a/src/sleep.cpp b/src/sleep.cpp index 27e81ce54..b0802c624 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -446,12 +446,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss + static esp_pm_config_t esp32_config; // filled with zeros because bss #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32C6 + esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32C3 esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; #else diff --git a/variants/tlora_c6/platformio.ini b/variants/tlora_c6/platformio.ini new file mode 100644 index 000000000..977e8ba86 --- /dev/null +++ b/variants/tlora_c6/platformio.ini @@ -0,0 +1,5 @@ +[env:tlora-c6] +extends = esp32c6_base +board = esp32-c6-devkitm-1 +build_flags = + ${esp32c6_base.build_flags} -D PRIVATE_HW -D TLORA_C6 -I variants/tlora_c6 \ No newline at end of file diff --git a/variants/tlora_c6/variant.h b/variants/tlora_c6/variant.h new file mode 100644 index 000000000..08fefa809 --- /dev/null +++ b/variants/tlora_c6/variant.h @@ -0,0 +1,16 @@ +#define I2C_SDA 4 // I2C pins for this board +#define I2C_SCL 15 + +#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller + +#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost +#define LED_PIN 2 // If defined we will blink this LED +#define BUTTON_PIN 0 // If defined, this will be used for user button presses +#define BUTTON_NEED_PULLUP +#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. + +#define USE_RF95 +#define LORA_DIO0 26 // a No connect on the SX1262 module +#define LORA_RESET 14 +#define LORA_DIO1 33 // Must be manually wired: https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 +#define LORA_DIO2 32 // Not really used \ No newline at end of file From ba31a7c753e829c6dba1253f02f85681989f47fb Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sat, 21 Sep 2024 06:27:41 +1200 Subject: [PATCH 1156/1377] Honor flip & color for Heltec T114 and T190 (#4786) * Honor TFT_MESH color if defined for Heltec T114 or T190 * Temporary: point lib_deps at fork of Heltec's ST7789 library For demo only, until ST7789 is merged * Update lib_deps; tidy preprocessor logic --- src/graphics/Screen.cpp | 7 +++++++ variants/heltec_mesh_node_t114/platformio.ini | 2 +- variants/heltec_vision_master_t190/platformio.ini | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ae09ee408..31f522a43 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1670,6 +1670,11 @@ void Screen::setup() static_cast(dispdev)->setSubtype(7); #endif +#if defined(USE_ST7789) && defined(TFT_MESH) + // Heltec T114 and T190: honor a custom text color, if defined in variant.h + static_cast(dispdev)->setRGB(TFT_MESH); +#endif + // Initialising the UI will init the display too. ui->init(); @@ -1726,6 +1731,8 @@ void Screen::setup() #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ defined(RAK14014) || defined(HX8357_CS) static_cast(dispdev)->flipScreenVertically(); +#elif defined(USE_ST7789) + static_cast(dispdev)->flipScreenVertically(); #else dispdev->flipScreenVertically(); #endif diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/heltec_mesh_node_t114/platformio.ini index e0d8ca0cc..1b06c7f5e 100644 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ b/variants/heltec_mesh_node_t114/platformio.ini @@ -14,4 +14,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/meshtastic/st7789#7181320e7ed05c7fb5fd2d32f14723bce6088b7b \ No newline at end of file + https://github.com/meshtastic/st7789#bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini index fd0001439..0c504d62b 100644 --- a/variants/heltec_vision_master_t190/platformio.ini +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -9,5 +9,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 - https://github.com/meshtastic/st7789#7181320e7ed05c7fb5fd2d32f14723bce6088b7b + https://github.com/meshtastic/st7789#bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f upload_speed = 921600 \ No newline at end of file From 0664c09f9dff0184be16752b5cae7095f1a23465 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 20 Sep 2024 14:55:53 -0500 Subject: [PATCH 1157/1377] Download debian files after firmware zip --- .github/workflows/main_matrix.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 9b97dcb2e..549a5d60f 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -251,12 +251,6 @@ jobs: merge-multiple: true path: ./output - - uses: actions/download-artifact@v4 - with: - pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb - merge-multiple: true - path: ./output - - name: Display structure of downloaded files run: ls -R @@ -314,6 +308,12 @@ jobs: asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip asset_content_type: application/zip + - uses: actions/download-artifact@v4 + with: + pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb + merge-multiple: true + path: ./output + - name: Add raspbian aarch64 .deb uses: actions/upload-release-asset@v1 env: From c2272ce5a102ded4606bf989c30bff377303b2bd Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:30:32 -0700 Subject: [PATCH 1158/1377] set title for protobufs bump PR (#4792) --- .github/workflows/update_protobufs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 4402a280e..b46c070f1 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -28,6 +28,7 @@ jobs: - name: Create pull request uses: peter-evans/create-pull-request@v6 with: + title: Update protobufs and classes add-paths: | protobufs src/mesh From ed13105aec493a771f3750b3bc50d45fba0f4601 Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:30:49 -0700 Subject: [PATCH 1159/1377] set title for version bump PR (#4791) --- .github/workflows/main_matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 549a5d60f..588f0981a 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -351,5 +351,6 @@ jobs: - name: Create version.properties pull request uses: peter-evans/create-pull-request@v6 with: + title: Bump version.properties add-paths: | version.properties From ec848bab5298559f149da4a58965b1fa7f58ad86 Mon Sep 17 00:00:00 2001 From: Jason Murray Date: Fri, 20 Sep 2024 14:40:10 -0700 Subject: [PATCH 1160/1377] Enable Dependabot --- .github/dependabot.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..c81c7fc07 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +updates: +- package-ecosystem: docker + directory: devcontainer + schedule: + interval: daily + time: "05:00" + timezone: US/Pacific +- package-ecosystem: docker + directory: / + schedule: + interval: daily + time: "05:00" + timezone: US/Pacific +- package-ecosystem: gitsubmodule + directory: / + schedule: + interval: daily + time: "05:00" + timezone: US/Pacific +- package-ecosystem: github-actions + directory: /.github/workflows + schedule: + interval: daily + time: "05:00" + timezone: US/Pacific From 7368cb99dc146beb0c469c351571be04ed5a6d72 Mon Sep 17 00:00:00 2001 From: Jason Murray Date: Fri, 20 Sep 2024 19:22:45 -0700 Subject: [PATCH 1161/1377] chore: trunk fmt --- .github/dependabot.yml | 48 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c81c7fc07..b15e8c1ce 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,26 +1,26 @@ version: 2 updates: -- package-ecosystem: docker - directory: devcontainer - schedule: - interval: daily - time: "05:00" - timezone: US/Pacific -- package-ecosystem: docker - directory: / - schedule: - interval: daily - time: "05:00" - timezone: US/Pacific -- package-ecosystem: gitsubmodule - directory: / - schedule: - interval: daily - time: "05:00" - timezone: US/Pacific -- package-ecosystem: github-actions - directory: /.github/workflows - schedule: - interval: daily - time: "05:00" - timezone: US/Pacific + - package-ecosystem: docker + directory: devcontainer + schedule: + interval: daily + time: 05:00 + timezone: US/Pacific + - package-ecosystem: docker + directory: / + schedule: + interval: daily + time: 05:00 + timezone: US/Pacific + - package-ecosystem: gitsubmodule + directory: / + schedule: + interval: daily + time: 05:00 + timezone: US/Pacific + - package-ecosystem: github-actions + directory: /.github/workflows + schedule: + interval: daily + time: 05:00 + timezone: US/Pacific From 74e647043924bc60e5a573170d60b01b69daacb5 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 21 Sep 2024 14:20:30 +0800 Subject: [PATCH 1162/1377] fix dependabot syntax (#4795) * fix dependabot syntax * Update dependabot.yml * Update dependabot.yml --- .github/dependabot.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b15e8c1ce..616c16ce2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,23 +4,23 @@ updates: directory: devcontainer schedule: interval: daily - time: 05:00 + time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check timezone: US/Pacific - package-ecosystem: docker directory: / schedule: interval: daily - time: 05:00 + time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check timezone: US/Pacific - package-ecosystem: gitsubmodule directory: / schedule: interval: daily - time: 05:00 + time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check timezone: US/Pacific - package-ecosystem: github-actions directory: /.github/workflows schedule: interval: daily - time: 05:00 + time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check timezone: US/Pacific From 38828412830e0d6f6a6b23219c646648bceb013a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Sep 2024 08:17:31 +0000 Subject: [PATCH 1163/1377] Bump peter-evans/create-pull-request from 6 to 7 in /.github/workflows (#4797) --- .github/workflows/main_matrix.yml | 2 +- .github/workflows/update_protobufs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 588f0981a..be09c24b2 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -349,7 +349,7 @@ jobs: bin/bump_version.py - name: Create version.properties pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: title: Bump version.properties add-paths: | diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index b46c070f1..f93930a83 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -26,7 +26,7 @@ jobs: ./bin/regen-protos.sh - name: Create pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: title: Update protobufs and classes add-paths: | From f1cf2bf413d80935f7abbae2fe68cbd7c9a66b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 12 Sep 2024 22:42:10 +0200 Subject: [PATCH 1164/1377] First stab at ESP32-C6 support for TLora-C6 --- arch/esp32/esp32c6.ini | 47 ++++++++++++++++++++++++ src/gps/GPS.cpp | 5 ++- src/graphics/Screen.cpp | 28 +++++++------- src/main.cpp | 14 ++++--- src/mesh/NodeDB.cpp | 2 +- src/mesh/TypedQueue.h | 2 +- src/mesh/api/ServerAPI.cpp | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 3 +- src/modules/ExternalNotificationModule.h | 2 +- src/modules/PositionModule.cpp | 16 ++++---- src/modules/SerialModule.cpp | 21 ++++++++++- src/mqtt/MQTT.cpp | 4 ++ src/mqtt/MQTT.h | 4 ++ src/platform/esp32/main-esp32.cpp | 12 +++++- src/power.h | 1 + src/serialization/JSON.cpp | 2 +- src/sleep.cpp | 4 +- variants/tlora_c6/platformio.ini | 5 +++ variants/tlora_c6/variant.h | 16 ++++++++ 19 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 arch/esp32/esp32c6.ini create mode 100644 variants/tlora_c6/platformio.ini create mode 100644 variants/tlora_c6/variant.h diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini new file mode 100644 index 000000000..a7bcdc0e8 --- /dev/null +++ b/arch/esp32/esp32c6.ini @@ -0,0 +1,47 @@ +[esp32c6_base] +extends = esp32_base +platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 +build_flags = + ${arduino_base.build_flags} + -Wall + -Wextra + -Isrc/platform/esp32 + -std=c++11 + -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL + -DAXP_DEBUG_PORT=Serial + -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DSERIAL_BUFFER_SIZE=4096 + -DLIBPAX_ARDUINO + -DLIBPAX_WIFI + -DLIBPAX_BLE + -DMESHTASTIC_EXCLUDE_WEBSERVER + ;-DDEBUG_HEAP + ; TEMP + -DHAS_BLUETOOTH=0 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER + -DMESHTASTIC_EXCLUDE_BLUETOOTH + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${environmental_base.lib_deps} + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f + rweather/Crypto@^0.4.0 + +build_src_filter = + ${esp32_base.build_src_filter} - + +monitor_speed = 115200 +monitor_filters = esp32_c3_exception_decoder + +lib_ignore = + NonBlockingRTTTL + NimBLE-Arduino + libpax + \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 01fa65816..19cfa0044 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -60,7 +60,8 @@ const char *getGPSPowerStateString(GPSPowerState state) case GPS_OFF: return "OFF"; default: - assert(false); // Unhandled enum value.. + assert(false); // Unhandled enum value.. + return "FALSE"; // to make new ESP-IDF happy } } @@ -330,7 +331,7 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t { uint16_t ubxFrameCounter = 0; uint32_t startTime = millis(); - uint16_t needRead; + uint16_t needRead = 0; while (millis() - startTime < waitMillis) { if (_serial_gps->available()) { diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 31f522a43..a34a6f1ed 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1004,55 +1004,55 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->setColor(WHITE); #ifndef EXCLUDE_EMOJI - if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) { + if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbup); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44E") == 0) { display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height, thumbdown); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"❓") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "❓") == 0) { display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height, question); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"‼️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "‼️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5, bang_width, bang_height, bang); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F4A9") == 0) { display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5, poo_width, poo_height, poo); } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\xa4\xa3") == 0) { display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5, haha_width, haha_height, haha); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F44B") == 0) { display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width, wave_icon_height, wave_icon); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F920") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F920") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height, cowboy); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\U0001F42D") == 0) { display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height, deadmau5); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xE2\x98\x80\xEF\xB8\x8F") == 0) { display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5, sun_width, sun_height, sun); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\u2614") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\u2614") == 0) { display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10, rain_width, rain_height, rain); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"☁️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "☁️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"🌫️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "🌫️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5, fog_width, fog_height, fog); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\x98\x88") == 0) { display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil); - } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"♥️") == 0) { + } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "♥️") == 0) { display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart); } else { diff --git a/src/main.cpp b/src/main.cpp index e24ba68b3..5057d7f57 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -322,15 +322,19 @@ void setup() #ifdef BUTTON_PIN #ifdef ARCH_ESP32 - // If the button is connected to GPIO 12, don't enable the ability to use - // meshtasticAdmin on the device. - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); - +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN #ifdef BUTTON_NEED_PULLUP gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); delay(10); #endif - +#endif #endif #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dca639070..12b731ab1 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -49,7 +49,7 @@ NodeDB *nodeDB = nullptr; // we have plenty of ram so statically alloc this tempbuf (for now) -EXT_RAM_ATTR meshtastic_DeviceState devicestate; +EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate; meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node; meshtastic_LocalConfig config; meshtastic_LocalModuleConfig moduleConfig; diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index c96edae8e..f7d016f10 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -14,7 +14,7 @@ */ template class TypedQueue { - static_assert(std::is_pod::value, "T must be pod"); + static_assert(std::is_standard_layout::value, "T must be standard layout"); QueueHandle_t h; concurrency::OSThread *reader = NULL; diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 3a483aac1..097f4fa21 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -45,7 +45,7 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { - auto client = U::available(); + auto client = U::accept(); if (client) { // Close any previous connection (see FIXME in header file) if (openAPI) { diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 07b03222e..e3203e6f7 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -309,7 +309,8 @@ static void WiFiEvent(WiFiEvent_t event) onNetworkConnected(); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - LOG_INFO("Obtained IP6 address: %s\n", WiFi.localIPv6().toString().c_str()); + LOG_INFO("Obtained Local IP6 address: %s\n", WiFi.linkLocalIPv6().toString().c_str()); + LOG_INFO("Obtained GlobalIP6 address: %s\n", WiFi.globalIPv6().toString().c_str()); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0\n"); diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 08e72c35a..a5dff3651 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -3,7 +3,7 @@ #include "SinglePortModule.h" #include "concurrency/OSThread.h" #include "configuration.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else // Noop class for portduino. diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index cb6a58b2e..2a962c268 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -273,7 +273,7 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() meshtastic_TAKPacket takPacket = {.is_compressed = true, .has_contact = true, - .contact = {0}, + .contact = meshtastic_Contact_init_default, .has_group = true, .group = {meshtastic_MemberRole_TeamMember, meshtastic_Team_Cyan}, .has_status = true, @@ -282,13 +282,13 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli() .battery = powerStatus->getBatteryChargePercent(), }, .which_payload_variant = meshtastic_TAKPacket_pli_tag, - {.pli = { - .latitude_i = localPosition.latitude_i, - .longitude_i = localPosition.longitude_i, - .altitude = localPosition.altitude_hae, - .speed = localPosition.ground_speed, - .course = static_cast(localPosition.ground_track), - }}}; + .payload_variant = {.pli = { + .latitude_i = localPosition.latitude_i, + .longitude_i = localPosition.longitude_i, + .altitude = localPosition.altitude_hae, + .speed = localPosition.ground_speed, + .course = static_cast(localPosition.ground_track), + }}}; auto length = unishox2_compress_lines(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign, sizeof(takPacket.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f0ba64f65..549fcf1d7 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -62,6 +62,9 @@ SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial; +#elif defined(CONFIG_IDF_TARGET_ESP32C6) +SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("SerialModule") {} +static Print *serialPrint = &Serial1; #else SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial2; @@ -137,7 +140,16 @@ int32_t SerialModule::runOnce() // Give it a chance to flush out 💩 delay(10); } -#ifdef ARCH_ESP32 +#if defined(CONFIG_IDF_TARGET_ESP32C6) + if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { + Serial1.setRxBufferSize(RX_BUFFER); + Serial1.begin(baud, SERIAL_8N1, moduleConfig.serial.rxd, moduleConfig.serial.txd); + } else { + Serial.begin(baud); + Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); + } + +#elif defined(ARCH_ESP32) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { Serial2.setRxBufferSize(RX_BUFFER); @@ -205,8 +217,13 @@ int32_t SerialModule::runOnce() processWXSerial(); } else { +#if defined(CONFIG_IDF_TARGET_ESP32C6) + while (Serial1.available()) { + serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#else while (Serial2.available()) { serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#endif serialModuleRadio->sendPayload(); } } @@ -392,7 +409,7 @@ uint32_t SerialModule::getBaudRate() */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 6840700e5..240fae0f9 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -325,6 +325,7 @@ void MQTT::reconnect() mqttPassword = moduleConfig.mqtt.password; } #if HAS_WIFI && !defined(ARCH_PORTDUINO) +#ifndef CONFIG_IDF_TARGET_ESP32C6 if (moduleConfig.mqtt.tls_enabled) { // change default for encrypted to 8883 try { @@ -340,6 +341,9 @@ void MQTT::reconnect() LOG_INFO("Using non-TLS-encrypted session\n"); pubSub.setClient(mqttClient); } +#else + pubSub.setClient(mqttClient); +#endif #elif HAS_NETWORKING pubSub.setClient(mqttClient); #endif diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index ba0987783..c827e12ca 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -9,9 +9,11 @@ #if HAS_WIFI #include #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 #include #endif #endif +#endif #if HAS_ETHERNET #include #endif @@ -33,9 +35,11 @@ class MQTT : private concurrency::OSThread #if HAS_WIFI WiFiClient mqttClient; #if !defined(ARCH_PORTDUINO) +#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR < 3 WiFiClientSecure wifiSecureClient; #endif #endif +#endif #if HAS_ETHERNET EthernetClient mqttClient; #endif diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 0b6f7cf23..f16accab6 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -13,6 +13,7 @@ #include "mesh/wifi/WiFiAPClient.h" #endif +#include "esp_mac.h" #include "meshUtils.h" #include "sleep.h" #include "soc/rtc.h" @@ -147,9 +148,16 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 +#ifdef CONFIG_IDF_TARGET_ESP32C6 + esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); + wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; + wdt_config->trigger_panic = true; + res = esp_task_wdt_init(wdt_config); + assert(res == ESP_OK); +#else res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); assert(res == ESP_OK); - +#endif res = esp_task_wdt_add(NULL); assert(res == ESP_OK); @@ -223,7 +231,7 @@ void cpuDeepSleep(uint32_t msecToWake) // to detect wake and in normal operation the external part drives them hard. #ifdef BUTTON_PIN // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. -#if SOC_RTCIO_HOLD_SUPPORTED +#if SOC_RTCIO_HOLD_SUPPORTED && SOC_PM_SUPPORT_EXT_WAKEUP uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif diff --git a/src/power.h b/src/power.h index a4307ee07..5a41b55f2 100644 --- a/src/power.h +++ b/src/power.h @@ -5,6 +5,7 @@ #include "configuration.h" #ifdef ARCH_ESP32 +// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h #include #include #endif diff --git a/src/serialization/JSON.cpp b/src/serialization/JSON.cpp index e98bf47b9..42e615108 100644 --- a/src/serialization/JSON.cpp +++ b/src/serialization/JSON.cpp @@ -184,7 +184,7 @@ bool JSON::ExtractString(const char **data, std::string &str) // End of the string? else if (next_char == '"') { (*data)++; - str.reserve(); // Remove unused capacity + str.shrink_to_fit(); // Remove unused capacity return true; } diff --git a/src/sleep.cpp b/src/sleep.cpp index 27e81ce54..b0802c624 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -446,12 +446,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss + static esp_pm_config_t esp32_config; // filled with zeros because bss #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32C6 + esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32C3 esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; #else diff --git a/variants/tlora_c6/platformio.ini b/variants/tlora_c6/platformio.ini new file mode 100644 index 000000000..977e8ba86 --- /dev/null +++ b/variants/tlora_c6/platformio.ini @@ -0,0 +1,5 @@ +[env:tlora-c6] +extends = esp32c6_base +board = esp32-c6-devkitm-1 +build_flags = + ${esp32c6_base.build_flags} -D PRIVATE_HW -D TLORA_C6 -I variants/tlora_c6 \ No newline at end of file diff --git a/variants/tlora_c6/variant.h b/variants/tlora_c6/variant.h new file mode 100644 index 000000000..08fefa809 --- /dev/null +++ b/variants/tlora_c6/variant.h @@ -0,0 +1,16 @@ +#define I2C_SDA 4 // I2C pins for this board +#define I2C_SCL 15 + +#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller + +#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost +#define LED_PIN 2 // If defined we will blink this LED +#define BUTTON_PIN 0 // If defined, this will be used for user button presses +#define BUTTON_NEED_PULLUP +#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. + +#define USE_RF95 +#define LORA_DIO0 26 // a No connect on the SX1262 module +#define LORA_RESET 14 +#define LORA_DIO1 33 // Must be manually wired: https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 +#define LORA_DIO2 32 // Not really used \ No newline at end of file From 8e5928276b734f56fe2936ab8be7f6ee92de1713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 21 Sep 2024 11:27:14 +0200 Subject: [PATCH 1165/1377] update pin definitions update toolchain enable telemetry fix compilation --- arch/esp32/esp32c6.ini | 11 ++--------- platformio.ini | 2 -- src/Power.cpp | 2 ++ src/sleep.cpp | 1 + variants/tlora_c6/platformio.ini | 8 +++++++- variants/tlora_c6/variant.h | 32 +++++++++++++++++++------------- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini index a7bcdc0e8..65c63b299 100644 --- a/arch/esp32/esp32c6.ini +++ b/arch/esp32/esp32c6.ini @@ -1,18 +1,12 @@ [esp32c6_base] extends = esp32_base -platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 +platform = https://github.com/Jason2866/platform-espressif32.git#d8d8f1bcf4bfcae82db2bef70a2b0014f1d2bc13 build_flags = ${arduino_base.build_flags} -Wall -Wextra -Isrc/platform/esp32 -std=c++11 - -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG - -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL - -DAXP_DEBUG_PORT=Serial - -DCONFIG_BT_NIMBLE_ENABLED - -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING -DSERIAL_BUFFER_SIZE=4096 -DLIBPAX_ARDUINO @@ -24,7 +18,6 @@ build_flags = -DHAS_BLUETOOTH=0 -DMESHTASTIC_EXCLUDE_PAXCOUNTER -DMESHTASTIC_EXCLUDE_BLUETOOTH - -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR lib_deps = ${arduino_base.lib_deps} @@ -37,7 +30,7 @@ lib_deps = build_src_filter = ${esp32_base.build_src_filter} - -monitor_speed = 115200 +monitor_speed = 460800 monitor_filters = esp32_c3_exception_decoder lib_ignore = diff --git a/platformio.ini b/platformio.ini index 9b869a036..7e4116991 100644 --- a/platformio.ini +++ b/platformio.ini @@ -152,13 +152,11 @@ lib_deps = emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 - https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 https://github.com/KodinLanewave/INA3221@^1.0.0 lewisxhe/SensorLib@0.2.0 mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 diff --git a/src/Power.cpp b/src/Power.cpp index 61a6c987d..047f3d768 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -136,6 +136,7 @@ using namespace meshtastic; */ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor +// warning: 'void adcEnable()' defined but not used [-Wunused-function] static void adcEnable() { #ifdef ADC_CTRL // enable adc voltage divider when we need to read @@ -149,6 +150,7 @@ static void adcEnable() #endif } +// warning: 'void adcDisable()' defined but not used [-Wunused-function] static void adcDisable() { #ifdef ADC_CTRL // disable adc voltage divider when we need to read diff --git a/src/sleep.cpp b/src/sleep.cpp index b0802c624..9efbabb35 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -17,6 +17,7 @@ #include "target_specific.h" #ifdef ARCH_ESP32 +// "esp_pm_config_esp32_t is deprecated, please include esp_pm.h and use esp_pm_config_t instead" #include "esp32/pm.h" #include "esp_pm.h" #if HAS_WIFI diff --git a/variants/tlora_c6/platformio.ini b/variants/tlora_c6/platformio.ini index 977e8ba86..3a8fd2c19 100644 --- a/variants/tlora_c6/platformio.ini +++ b/variants/tlora_c6/platformio.ini @@ -2,4 +2,10 @@ extends = esp32c6_base board = esp32-c6-devkitm-1 build_flags = - ${esp32c6_base.build_flags} -D PRIVATE_HW -D TLORA_C6 -I variants/tlora_c6 \ No newline at end of file + ${esp32c6_base.build_flags} + -D PRIVATE_HW + -D TLORA_C6 + -I variants/tlora_c6 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/esp32c3" \ No newline at end of file diff --git a/variants/tlora_c6/variant.h b/variants/tlora_c6/variant.h index 08fefa809..e16bfb767 100644 --- a/variants/tlora_c6/variant.h +++ b/variants/tlora_c6/variant.h @@ -1,16 +1,22 @@ -#define I2C_SDA 4 // I2C pins for this board -#define I2C_SCL 15 +#define I2C_SDA 8 // I2C pins for this board +#define I2C_SCL 9 -#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller - -#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 2 // If defined we will blink this LED -#define BUTTON_PIN 0 // If defined, this will be used for user button presses -#define BUTTON_NEED_PULLUP +#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost +#define LED_PIN 7 // If defined we will blink this LED #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. -#define USE_RF95 -#define LORA_DIO0 26 // a No connect on the SX1262 module -#define LORA_RESET 14 -#define LORA_DIO1 33 // Must be manually wired: https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 -#define LORA_DIO2 32 // Not really used \ No newline at end of file +#define USE_SX1262 +#define LORA_SCK 6 +#define LORA_MISO 1 +#define LORA_MOSI 0 +#define LORA_CS 18 +#define LORA_RESET 21 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 23 +#define SX126X_DIO2 20 +#define SX126X_BUSY 22 +#define SX126X_RESET LORA_RESET +#define SX126X_RXEN 15 +#define SX126X_TXEN 14 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file From 6490cadd35b1703361a2cd820aa0a84acf5696d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Sep 2024 09:30:36 +0000 Subject: [PATCH 1166/1377] Bump docker/build-push-action from 5 to 6 in /.github/workflows (#4800) --- .github/workflows/build_native.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 51bef0c13..1fb44a717 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -67,7 +67,7 @@ jobs: - name: Docker build and push tagged versions if: ${{ github.event_name == 'workflow_dispatch' }} continue-on-error: true # FIXME: Failing docker login auth - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile @@ -77,7 +77,7 @@ jobs: - name: Docker build and push if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} continue-on-error: true # FIXME: Failing docker login auth - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile From 52cef05c70bdebbad972327b748bc7bd964c86c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 21 Sep 2024 12:42:51 +0200 Subject: [PATCH 1167/1377] heltec-wireless-bridge requires Proto PR first --- src/platform/esp32/architecture.h | 2 ++ .../heltec_wireless_bridge/platformio.ini | 6 ++++ variants/heltec_wireless_bridge/variant.h | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 variants/heltec_wireless_bridge/platformio.ini create mode 100644 variants/heltec_wireless_bridge/variant.h diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 93630aa8a..90c4c392d 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -76,6 +76,8 @@ #ifdef HELTEC_V2_1 #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V2_1 #endif +#elif defined(HELTEC_WIRELESS_BRIDGE) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_BRIDGE #elif defined(ARDUINO_HELTEC_WIFI_LORA_32) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V1 #elif defined(TLORA_V1) diff --git a/variants/heltec_wireless_bridge/platformio.ini b/variants/heltec_wireless_bridge/platformio.ini new file mode 100644 index 000000000..45c3aba74 --- /dev/null +++ b/variants/heltec_wireless_bridge/platformio.ini @@ -0,0 +1,6 @@ +[env:heltec-wireless-bridge] +;build_type = debug ; to make it possible to step through our jtag debugger +extends = esp32_base +board = heltec_wifi_lora_32 +build_flags = + ${esp32_base.build_flags} -D HELTEC_WIRELESS_BRIDGE -I variants/heltec_wireless_bridge \ No newline at end of file diff --git a/variants/heltec_wireless_bridge/variant.h b/variants/heltec_wireless_bridge/variant.h new file mode 100644 index 000000000..7c4f41660 --- /dev/null +++ b/variants/heltec_wireless_bridge/variant.h @@ -0,0 +1,29 @@ +// the default ESP32 Pin of 15 is the Oled SCL, set to 36 and 37 and works fine. +// Tested on Neo6m module. +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 36 +#define GPS_TX_PIN 33 + +#ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag +#define I2C_SDA 4 // I2C pins for this board +#define I2C_SCL 15 +#endif + +#define LED_PIN 25 // If defined we will blink this LED +#define BUTTON_PIN 0 // If defined, this will be used for user button presses + +#define USE_RF95 +#define LORA_DIO0 26 // a No connect on the SX1262 module +#ifndef USE_JTAG +#define LORA_RESET 14 +#endif +#define LORA_DIO1 35 +#define LORA_DIO2 34 // Not really used + +// ratio of voltage divider = 3.20 (R1=100k, R2=220k) +#define ADC_MULTIPLIER 3.2 + +#define BATTERY_PIN 13 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC2_GPIO13_CHANNEL +#define BAT_MEASURE_ADC_UNIT 2 \ No newline at end of file From de706523f56070cf3a4b7393b00485432774facf Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 21 Sep 2024 19:10:59 +0800 Subject: [PATCH 1168/1377] Actions: Semgrep Images have moved from returntocorp to semgrep (#4774) https://hub.docker.com/r/returntocorp/semgrep notes: "We've moved! Official Docker images for Semgrep now available at semgrep/semgrep." Patch updates our CI workflow for these images. Co-authored-by: Ben Meadors --- .github/workflows/sec_sast_semgrep_cron.yml | 2 +- .github/workflows/sec_sast_semgrep_pull.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index 2a0361f5e..54bbbe6d2 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -12,7 +12,7 @@ jobs: semgrep-full: runs-on: ubuntu-latest container: - image: returntocorp/semgrep + image: semgrep/semgrep steps: # step 1 diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml index 2575cbf01..9013f1c74 100644 --- a/.github/workflows/sec_sast_semgrep_pull.yml +++ b/.github/workflows/sec_sast_semgrep_pull.yml @@ -6,7 +6,7 @@ jobs: semgrep-diff: runs-on: ubuntu-22.04 container: - image: returntocorp/semgrep + image: semgrep/semgrep steps: # step 1 From acd044fdeac80a5ba9d1431c929b541833336f8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Sep 2024 06:11:32 -0500 Subject: [PATCH 1169/1377] Bump meshtestic from `31ee3d9` to `37245b3` (#4799) Bumps [meshtestic](https://github.com/meshtastic/meshTestic) from `31ee3d9` to `37245b3`. - [Commits](https://github.com/meshtastic/meshTestic/compare/31ee3d90c8bef61e835c3271be2c7cda8c4a5cc2...37245b3d612a9272f546bbb092837bafdad46bc2) --- updated-dependencies: - dependency-name: meshtestic dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- meshtestic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtestic b/meshtestic index 31ee3d90c..37245b3d6 160000 --- a/meshtestic +++ b/meshtestic @@ -1 +1 @@ -Subproject commit 31ee3d90c8bef61e835c3271be2c7cda8c4a5cc2 +Subproject commit 37245b3d612a9272f546bbb092837bafdad46bc2 From 953beb56b1104b1016a334789e3e53b075eb5ede Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 21 Sep 2024 06:12:05 -0500 Subject: [PATCH 1170/1377] [create-pull-request] automated change (#4789) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 69c478482..b30827191 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 1 +build = 2 From c053c7d9aef30873cb0a039bbed051a6a0db4036 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Sep 2024 06:13:09 -0500 Subject: [PATCH 1171/1377] Bump pnpm/action-setup from 2 to 4 in /.github/workflows (#4798) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 4. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v2...v4) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f58b38ac9..bf3d15dba 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,7 +77,7 @@ jobs: pio upgrade - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 with: version: latest From e6c7c80b3f6d4bc3fdbc6c369146b5d00de8df9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 21 Sep 2024 14:50:19 +0200 Subject: [PATCH 1172/1377] Raspberry Pico2 - needs protos --- arch/esp32/esp32.ini | 2 +- arch/nrf52/nrf52.ini | 2 +- arch/portduino/portduino.ini | 2 +- arch/{rp2040 => rp2xx0}/rp2040.ini | 2 +- arch/rp2xx0/rp2350.ini | 23 +++++++++ arch/stm32/stm32.ini | 2 +- .../{rp2040 => rp2xx0}/architecture.h | 2 + .../main-rp2xx0.cpp} | 0 variants/rpipico2/platformio.ini | 16 ++++++ variants/rpipico2/variant.h | 50 +++++++++++++++++++ 10 files changed, 96 insertions(+), 5 deletions(-) rename arch/{rp2040 => rp2xx0}/rp2040.ini (97%) create mode 100644 arch/rp2xx0/rp2350.ini rename src/platform/{rp2040 => rp2xx0}/architecture.h (90%) rename src/platform/{rp2040/main-rp2040.cpp => rp2xx0/main-rp2xx0.cpp} (100%) create mode 100644 variants/rpipico2/platformio.ini create mode 100644 variants/rpipico2/variant.h diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 0dd6cbc1d..13360be78 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -5,7 +5,7 @@ custom_esp32_kind = esp32 platform = platformio/espressif32@6.7.0 build_src_filter = - ${arduino_base.build_src_filter} - - - - - + ${arduino_base.build_src_filter} - - - - - upload_speed = 921600 debug_init_break = tbreak setup diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 503da2aab..04880d540 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -16,7 +16,7 @@ build_flags = -DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818 build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - lib_deps= ${arduino_base.lib_deps} diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 482b1f9c5..aae3525b6 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -9,7 +9,7 @@ build_src_filter = - - - - - + - - - + diff --git a/arch/rp2040/rp2040.ini b/arch/rp2xx0/rp2040.ini similarity index 97% rename from arch/rp2040/rp2040.ini rename to arch/rp2xx0/rp2040.ini index dd3a4d7ff..d3f27a676 100644 --- a/arch/rp2040/rp2040.ini +++ b/arch/rp2xx0/rp2040.ini @@ -8,7 +8,7 @@ board_build.core = earlephilhower board_build.filesystem_size = 0.5m build_flags = ${arduino_base.build_flags} -Wno-unused-variable - -Isrc/platform/rp2040 + -Isrc/platform/rp2xx0 -D__PLAT_RP2040__ # -D _POSIX_THREADS build_src_filter = diff --git a/arch/rp2xx0/rp2350.ini b/arch/rp2xx0/rp2350.ini new file mode 100644 index 000000000..96ed0cb21 --- /dev/null +++ b/arch/rp2xx0/rp2350.ini @@ -0,0 +1,23 @@ +; Common settings for rp2040 Processor based targets +[rp2350_base] +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#9e55f6db5c56b9867c69fe473f388beea4546672 +extends = arduino_base +platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#a6ab6e1f95bc1428d667d55ea7173c0744acc03c ; 4.0.2+ + +board_build.core = earlephilhower +board_build.filesystem_size = 0.5m +build_flags = + ${arduino_base.build_flags} -Wno-unused-variable + -Isrc/platform/rp2xx0 + -D__PLAT_RP2040__ +# -D _POSIX_THREADS +build_src_filter = + ${arduino_base.build_src_filter} - - - - - - - - - + +lib_ignore = + BluetoothOTA + +lib_deps = + ${arduino_base.lib_deps} + ${environmental_base.lib_deps} + rweather/Crypto \ No newline at end of file diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index 050dbf7f0..4be290015 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -22,7 +22,7 @@ build_flags = -fdata-sections build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - board_upload.offset_address = 0x08000000 upload_protocol = stlink diff --git a/src/platform/rp2040/architecture.h b/src/platform/rp2xx0/architecture.h similarity index 90% rename from src/platform/rp2040/architecture.h rename to src/platform/rp2xx0/architecture.h index 3f75735d3..8c7dfc0cd 100644 --- a/src/platform/rp2040/architecture.h +++ b/src/platform/rp2xx0/architecture.h @@ -23,6 +23,8 @@ #if defined(RPI_PICO) #define HW_VENDOR meshtastic_HardwareModel_RPI_PICO +#elif defined(RPI_PICO2) +#define HW_VENDOR meshtastic_HardwareModel_RPI_PICO2 #elif defined(RAK11310) #define HW_VENDOR meshtastic_HardwareModel_RAK11310 #elif defined(SENSELORA_RP2040) diff --git a/src/platform/rp2040/main-rp2040.cpp b/src/platform/rp2xx0/main-rp2xx0.cpp similarity index 100% rename from src/platform/rp2040/main-rp2040.cpp rename to src/platform/rp2xx0/main-rp2xx0.cpp diff --git a/variants/rpipico2/platformio.ini b/variants/rpipico2/platformio.ini new file mode 100644 index 000000000..a63414418 --- /dev/null +++ b/variants/rpipico2/platformio.ini @@ -0,0 +1,16 @@ +[env:pico2] +extends = rp2350_base +board = rpipico2 +upload_protocol = picotool + +# add our variants files to the include and src paths +build_flags = ${rp2350_base.build_flags} + -DRPI_PICO2 + -Ivariants/rpipico2 + -DDEBUG_RP2040_PORT=Serial + -DHW_SPI1_DEVICE + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" +lib_deps = + ${rp2350_base.lib_deps} +debug_build_flags = ${rp2350_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/rpipico2/variant.h b/variants/rpipico2/variant.h new file mode 100644 index 000000000..7efaeaf7a --- /dev/null +++ b/variants/rpipico2/variant.h @@ -0,0 +1,50 @@ +// #define RADIOLIB_CUSTOM_ARDUINO 1 +// #define RADIOLIB_TONE_UNSUPPORTED 1 +// #define RADIOLIB_SOFTWARE_SERIAL_UNSUPPORTED 1 + +#define ARDUINO_ARCH_AVR + +// default I2C pins: +// SDA = 4 +// SCL = 5 + +// Recommended pins for SerialModule: +// txd = 8 +// rxd = 9 + +#define EXT_NOTIFY_OUT 22 +#define BUTTON_PIN 17 + +#define LED_PIN PIN_LED + +#define BATTERY_PIN 26 +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +#define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION + +#define USE_SX1262 + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +#define LORA_SCK 10 +#define LORA_MISO 12 +#define LORA_MOSI 11 +#define LORA_CS 3 + +#define LORA_DIO0 RADIOLIB_NC +#define LORA_RESET 15 +#define LORA_DIO1 20 +#define LORA_DIO2 2 +#define LORA_DIO3 RADIOLIB_NC + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif From 54f56438da20d27e5fb3d5262af95138557a2883 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Sun, 22 Sep 2024 00:59:17 +1200 Subject: [PATCH 1173/1377] Re-order doDeepSleep (#4802) Make sure PMU sleep takes place before I2C ends --- src/sleep.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 27e81ce54..e6814f027 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -271,13 +271,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) digitalWrite(LORA_CS, HIGH); gpio_hold_en((gpio_num_t)LORA_CS); } - -#if defined(I2C_SDA) - Wire.end(); - pinMode(I2C_SDA, ANALOG); - pinMode(I2C_SCL, ANALOG); -#endif - #endif #ifdef HAS_PMU @@ -315,6 +308,14 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) } #endif +#if defined(ARCH_ESP32) && defined(I2C_SDA) + // Added by https://github.com/meshtastic/firmware/pull/4418 + // Possibly to support Heltec Capsule Sensor? + Wire.end(); + pinMode(I2C_SDA, ANALOG); + pinMode(I2C_SCL, ANALOG); +#endif + console->flush(); cpuDeepSleep(msecToWake); } From f324ab7de762351f19941847377875444fe56842 Mon Sep 17 00:00:00 2001 From: thebentern <9000580+thebentern@users.noreply.github.com> Date: Sat, 21 Sep 2024 13:12:45 +0000 Subject: [PATCH 1174/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 5709c0a05..9b8490784 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5709c0a05eaefccbc9cb8ed3917adbf5fd134197 +Subproject commit 9b84907847b67047b72f9792f4b47532b308bbe4 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 921dfa55b..aa83ff27a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -71,6 +71,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RAK2560 = 22, /* Heltec HRU-3601: https://heltec.org/project/hru-3601/ */ meshtastic_HardwareModel_HELTEC_HRU_3601 = 23, + /* Heltec Wireless Bridge */ + meshtastic_HardwareModel_HELTEC_WIRELESS_BRIDGE = 24, /* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */ meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ @@ -199,6 +201,8 @@ typedef enum _meshtastic_HardwareModel { /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK_COREBASIC = 77, meshtastic_HardwareModel_M5STACK_CORE2 = 78, + /* Pico2 with Waveshare Hat, same as Pico */ + meshtastic_HardwareModel_RPI_PICO2 = 79, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 202699239fca94e8a3804e46bd42afc3a22bd5c6 Mon Sep 17 00:00:00 2001 From: Jason Murray Date: Sat, 21 Sep 2024 07:37:37 -0700 Subject: [PATCH 1175/1377] feat: trigger class update when protobufs are changed --- .github/workflows/update_protobufs.yml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index f93930a83..466767273 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -1,5 +1,10 @@ -name: "Update protobufs and regenerate classes" -on: workflow_dispatch +name: Update protobufs and regenerate classes +on: + pull_request: + branches: + - master + paths: + - meshtastic/** jobs: update-protobufs: @@ -11,10 +16,6 @@ jobs: with: submodules: true - - name: Update submodule - run: | - git submodule update --remote protobufs - - name: Download nanopb run: | wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz @@ -25,10 +26,11 @@ jobs: run: | ./bin/regen-protos.sh - - name: Create pull request - uses: peter-evans/create-pull-request@v7 + - name: Commit changes + uses: EndBug/add-and-commit@v9 with: - title: Update protobufs and classes - add-paths: | - protobufs - src/mesh + add: src/mesh + author_name: CI Bot + author_email: meshtastic-ci-bot@users.noreply.github.com + default_author: github_actor + message: Update classes from protobugs From 2072ebd196d7dbf70e82d3f1af1d335596b95f62 Mon Sep 17 00:00:00 2001 From: Jason Murray Date: Sat, 21 Sep 2024 08:49:36 -0700 Subject: [PATCH 1176/1377] meshtastic/ is a test suite; protobufs/ contains protobufs; --- .github/workflows/update_protobufs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 466767273..eb1ca3648 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -4,7 +4,7 @@ on: branches: - master paths: - - meshtastic/** + - protobufs/** jobs: update-protobufs: @@ -33,4 +33,4 @@ jobs: author_name: CI Bot author_email: meshtastic-ci-bot@users.noreply.github.com default_author: github_actor - message: Update classes from protobugs + message: Update classes from protobufs From d21087f6396a46bb63748a015109d9b68a77b164 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 21 Sep 2024 16:17:30 -0500 Subject: [PATCH 1177/1377] Update platform-native to pick up portduino crash fix (#4807) --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index aae3525b6..30cc190d2 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#ad8112adf82ce1f5b917092cf32be07a077801a0 +platform = https://github.com/meshtastic/platform-native.git#6b3796d697481c8f6e3f4aa5c111bd9979f29e64 framework = arduino build_src_filter = From 893bbe09d18e3d479f724f9eb30b0182421b4b8a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 21 Sep 2024 16:34:26 -0500 Subject: [PATCH 1178/1377] Hopefully extract and commit to meshtastic.github.io --- .github/workflows/main_matrix.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index be09c24b2..3564b8f93 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -354,3 +354,29 @@ jobs: title: Bump version.properties add-paths: | version.properties + + - name: Checkout meshtastic/meshtastic.github.io + uses: actions/checkout@v4 + with: + repository: meshtastic/meshtastic.github.io + token: ${{ secrets.GITHUB_TOKEN }} + path: meshtastic.github.io + + - name: Display structure of downloaded files + run: ls -R + + - name: Extract firmware.zip + run: | + unzip ./firmware-${{ steps.version.outputs.version }}.zip -d meshtastic.github.io/firmware-${{ steps.version.outputs.version }} + + - name: Display structure of downloaded files + run: ls -R + + - name: Commit and push changes + run: | + cd meshtastic.github.io + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "Add firmware version ${{ steps.version.outputs.version }}" + git push From 51af7475080c6d83d2b5c0c82de2b651dcd7268f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 21 Sep 2024 20:53:23 -0500 Subject: [PATCH 1179/1377] CI fixes --- .github/workflows/main_matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 3564b8f93..19a92d8b8 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -359,7 +359,7 @@ jobs: uses: actions/checkout@v4 with: repository: meshtastic/meshtastic.github.io - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ARTIFACTS_TOKEN }} path: meshtastic.github.io - name: Display structure of downloaded files @@ -375,6 +375,7 @@ jobs: - name: Commit and push changes run: | cd meshtastic.github.io + find . -type f -name 'meshtasticd_*' -exec rm -f {} + git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" git add . From 2ff0af55b1bc79ea5889f120e9a981edf06a5b18 Mon Sep 17 00:00:00 2001 From: Austin Date: Sun, 22 Sep 2024 02:47:49 -0400 Subject: [PATCH 1180/1377] [Board] DIY "t-energy-s3_e22" (#4782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New variant "t-energy-s3_e22" - Lilygo T-Energy-S3 - NanoVHF "Mesh-v1.06-TTGO-T18" board - Ebyte E22 Series * add board_level = extra * Update variant.h --------- Co-authored-by: Thomas Göttgens Co-authored-by: Tom Fifield --- variants/diy/platformio.ini | 20 ++++++++++- variants/diy/t-energy-s3_e22/variant.h | 46 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 variants/diy/t-energy-s3_e22/variant.h diff --git a/variants/diy/platformio.ini b/variants/diy/platformio.ini index 2a55f7a79..f3c22b7a8 100644 --- a/variants/diy/platformio.ini +++ b/variants/diy/platformio.ini @@ -69,4 +69,22 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_tcxo> lib_deps = ${nrf52840_base.lib_deps} -debug_tool = jlink \ No newline at end of file +debug_tool = jlink + +; NanoVHF T-Energy-S3 + E22(0)-xxxM - DIY +[env:t-energy-s3_e22] +extends = esp32s3_base +board = esp32-s3-devkitc-1 +board_level = extra +board_upload.flash_size = 16MB ;Specify the FLASH capacity as 16MB +board_build.arduino.memory_type = qio_opi ;Enable internal PSRAM +build_unflags = + ${esp32s3_base.build_unflags} + -D ARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} + -D EBYTE_ESP32_S3 + -D BOARD_HAS_PSRAM + -D ARDUINO_USB_MODE=0 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -I variants/diy/t-energy-s3_e22 diff --git a/variants/diy/t-energy-s3_e22/variant.h b/variants/diy/t-energy-s3_e22/variant.h new file mode 100644 index 000000000..6933d7715 --- /dev/null +++ b/variants/diy/t-energy-s3_e22/variant.h @@ -0,0 +1,46 @@ +// NanoVHF T-Energy-S3 + E22(0)-xxxM - DIY +// https://github.com/NanoVHF/Meshtastic-DIY/tree/main/PCB/ESP-32-devkit_EBYTE-E22/Mesh-v1.06-TTGO-T18 + +// Battery +#define BATTERY_PIN 3 +#define ADC_MULTIPLIER 2.0 +#define ADC_CHANNEL ADC1_GPIO3_CHANNEL + +// Button on NanoVHF PCB +#define BUTTON_PIN 39 + +// I2C via connectors on NanoVHF PCB +#define I2C_SCL 2 +#define I2C_SDA 42 + +// Screen (disabled) +#define HAS_SCREEN 0 // Assume no screen present by default to prevent crash... + +// GPS via T-Energy-S3 onboard connector +#define HAS_GPS 1 +#define GPS_TX_PIN 43 +#define GPS_RX_PIN 44 + +// LoRa +#define USE_SX1262 // E22-900M30S, E22-900M22S, and E22-900MM22S (not E220!) use SX1262 +#define USE_SX1268 // E22-400M30S, E22-400M33S, E22-400M22S, and E22-400MM22S use SX1268 + +#define SX126X_MAX_POWER 22 // SX126xInterface.cpp defaults to 22 if not defined, but here we define it for good practice +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V + +#define SX126X_CS 5 // EBYTE module's NSS pin // FIXME: rename to SX126X_SS +#define SX126X_SCK 6 // EBYTE module's SCK pin +#define SX126X_MOSI 13 // EBYTE module's MOSI pin +#define SX126X_MISO 4 // EBYTE module's MISO pin +#define SX126X_RESET 1 // EBYTE module's NRST pin +#define SX126X_BUSY 48 // EBYTE module's BUSY pin +#define SX126X_DIO1 47 // EBYTE module's DIO1 pin + +#define SX126X_TXEN 10 // Schematic connects EBYTE module's TXEN pin to MCU +#define SX126X_RXEN 12 // Schematic connects EBYTE module's RXEN pin to MCU + +#define LORA_CS SX126X_CS // Compatibility with variant file configuration structure +#define LORA_SCK SX126X_SCK // Compatibility with variant file configuration structure +#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure +#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure +#define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure From 9f8d86cb25febfb86c57f395549b7deb82458065 Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Sun, 22 Sep 2024 04:22:00 -0700 Subject: [PATCH 1181/1377] Consolidate variant build steps (#4806) * poc: consolidate variant build steps * use build-variant action * only checkout once and clean up after run --- .github/actions/build-variant/action.yml | 97 ++++++++++++++++++++++++ .github/workflows/build_esp32.yml | 60 ++++----------- .github/workflows/build_esp32_c3.yml | 59 ++++---------- .github/workflows/build_esp32_s3.yml | 60 ++++----------- .github/workflows/build_nrf52.yml | 22 ++---- .github/workflows/build_rpi2040.yml | 22 ++---- .github/workflows/build_stm32.yml | 24 ++---- 7 files changed, 159 insertions(+), 185 deletions(-) create mode 100644 .github/actions/build-variant/action.yml diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml new file mode 100644 index 000000000..0a96a0155 --- /dev/null +++ b/.github/actions/build-variant/action.yml @@ -0,0 +1,97 @@ +name: Setup Build Variant Composite Action +description: Variant build actions for Meshtastic Platform IO steps + +inputs: + board: + description: The board to build for + required: true + github_token: + description: GitHub token + required: true + build-script-path: + description: Path to the build script + required: true + remove-debug-flags: + description: A newline separated list of files to remove debug flags from + required: false + default: "" + ota-firmware-source: + description: The OTA firmware file to pull + required: false + default: "" + ota-firmware-target: + description: The target path to store the OTA firmware file + required: false + default: "" + artifact-paths: + description: A newline separated list of paths to store as artifacts + required: false + default: "" + include-web-ui: + description: Include the web UI in the build + required: false + default: "false" + +runs: + using: composite + steps: + - uses: actions/checkout@v4 + + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + if: ${{ inputs.include-web-ui == "true" }} + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ inputs.github_token }} + + - name: Unpack web ui + if: ${{ inputs.include-web-ui == "true" }} + shell: bash + run: | + tar -xf build.tar -C data/static + rm build.tar + + - name: Remove debug flags for release + shell: bash + if: ${{ inputs.remove-debug-flags != "" }} + run: | + for PATH in ${{ inputs.remove-debug-flags }}; do + sed -i '/DDEBUG_HEAP/d' ${PATH} + done + + - name: Build ${{ inputs.board }} + shell: bash + run: ${{ inputs.build-script-path }} ${{ inputs.board }} + + - name: Pull OTA Firmware + if: ${{ inputs.ota-firmware-source != "" && inputs.ota-firmware-target != "" }} + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/firmware-ota + file: ${{ inputs.ota-firmware-source }} + target: ${{ inputs.ota-firmware-target }} + token: ${{ inputs.github_token }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | + ${{ inputs.artifact-paths }} + + - name: Clean up resources + shell: bash + run: | + rm -rf . diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 4cbb4c7a4..8b7017fc9 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -11,53 +11,21 @@ jobs: build-esp32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Unpack web ui - run: | - tar -xf build.tar -C data/static - rm build.tar - - - name: Remove debug flags for release - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - - name: Build ESP32 - run: bin/build-esp32.sh ${{ inputs.board }} - - - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@master + id: build + uses: ./.github/actions/build-variant with: - repo: meshtastic/firmware-ota - file: firmware.bin - target: release/bleota.bin - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: | + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware.bin + ota-firmware-target: release/bleota.bin + artifact-paths: | release/*.bin release/*.elf + include-web-ui: true diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index 07727d711..249f6f126 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -13,51 +13,20 @@ jobs: build-esp32-c3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@master + - name: Build ESP32-C3 + id: build + uses: ./.github/actions/build-variant with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Unpack web ui - run: | - tar -xf build.tar -C data/static - rm build.tar - - name: Remove debug flags for release - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - - name: Build ESP32 - run: bin/build-esp32.sh ${{ inputs.board }} - - - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/firmware-ota - file: firmware-c3.bin - target: release/bleota-c3.bin - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: | + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware-c3.bin + ota-firmware-target: release/bleota-c3.bin + artifact-paths: | release/*.bin release/*.elf diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 10773833e..7df3e5737 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -11,51 +11,21 @@ jobs: build-esp32-s3: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@master + - name: Build ESP32-S3 + id: build + uses: ./.github/actions/build-variant with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Unpack web ui - run: | - tar -xf build.tar -C data/static - rm build.tar - - name: Remove debug flags for release - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - - name: Build ESP32 - run: bin/build-esp32.sh ${{ inputs.board }} - - - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/firmware-ota - file: firmware-s3.bin - target: release/bleota-s3.bin - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: | + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware-s3.bin + ota-firmware-target: release/bleota-s3.bin + artifact-paths: | release/*.bin release/*.elf + include-web-ui: true diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index ac509a096..85d3635cd 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -11,24 +11,14 @@ jobs: build-nrf52: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - name: Build NRF52 - run: bin/build-nrf52.sh ${{ inputs.board }} - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + id: build + uses: ./.github/actions/build-variant with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + build-script-path: bin/build-nrf52.sh + artifact-paths: | release/*.hex release/*.uf2 release/*.elf diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index 6e258fe2a..d3e19f8fd 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -11,23 +11,13 @@ jobs: build-rpi2040: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - name: Build Raspberry Pi 2040 - run: ./bin/build-rpi2040.sh ${{ inputs.board }} - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + id: build + uses: ./.github/actions/build-variant with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + build-script-path: bin/build-rpi2040.sh + artifact-paths: | release/*.uf2 release/*.elf diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml index d13c52c8a..9252087df 100644 --- a/.github/workflows/build_stm32.yml +++ b/.github/workflows/build_stm32.yml @@ -11,23 +11,13 @@ jobs: build-stm32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - - name: Build STM32 - run: bin/build-stm32.sh ${{ inputs.board }} - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + - name: Build Raspberry Pi 2040 + id: build + uses: ./.github/actions/build-variant with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + build-script-path: bin/build-stm32.sh + artifact-paths: | release/*.hex release/*.bin From 7db98ca1dade6459dd170f8fe1fda1adee05da67 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 22 Sep 2024 19:39:35 +0800 Subject: [PATCH 1182/1377] Revert "Consolidate variant build steps (#4806)" (#4816) This reverts commit 9f8d86cb25febfb86c57f395549b7deb82458065. --- .github/actions/build-variant/action.yml | 97 ------------------------ .github/workflows/build_esp32.yml | 62 +++++++++++---- .github/workflows/build_esp32_c3.yml | 59 ++++++++++---- .github/workflows/build_esp32_s3.yml | 60 +++++++++++---- .github/workflows/build_nrf52.yml | 22 ++++-- .github/workflows/build_rpi2040.yml | 22 ++++-- .github/workflows/build_stm32.yml | 24 ++++-- 7 files changed, 186 insertions(+), 160 deletions(-) delete mode 100644 .github/actions/build-variant/action.yml diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml deleted file mode 100644 index 0a96a0155..000000000 --- a/.github/actions/build-variant/action.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Setup Build Variant Composite Action -description: Variant build actions for Meshtastic Platform IO steps - -inputs: - board: - description: The board to build for - required: true - github_token: - description: GitHub token - required: true - build-script-path: - description: Path to the build script - required: true - remove-debug-flags: - description: A newline separated list of files to remove debug flags from - required: false - default: "" - ota-firmware-source: - description: The OTA firmware file to pull - required: false - default: "" - ota-firmware-target: - description: The target path to store the OTA firmware file - required: false - default: "" - artifact-paths: - description: A newline separated list of paths to store as artifacts - required: false - default: "" - include-web-ui: - description: Include the web UI in the build - required: false - default: "false" - -runs: - using: composite - steps: - - uses: actions/checkout@v4 - - - name: Build base - id: base - uses: ./.github/actions/setup-base - - - name: Pull web ui - if: ${{ inputs.include-web-ui == "true" }} - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ inputs.github_token }} - - - name: Unpack web ui - if: ${{ inputs.include-web-ui == "true" }} - shell: bash - run: | - tar -xf build.tar -C data/static - rm build.tar - - - name: Remove debug flags for release - shell: bash - if: ${{ inputs.remove-debug-flags != "" }} - run: | - for PATH in ${{ inputs.remove-debug-flags }}; do - sed -i '/DDEBUG_HEAP/d' ${PATH} - done - - - name: Build ${{ inputs.board }} - shell: bash - run: ${{ inputs.build-script-path }} ${{ inputs.board }} - - - name: Pull OTA Firmware - if: ${{ inputs.ota-firmware-source != "" && inputs.ota-firmware-target != "" }} - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/firmware-ota - file: ${{ inputs.ota-firmware-source }} - target: ${{ inputs.ota-firmware-target }} - token: ${{ inputs.github_token }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | - ${{ inputs.artifact-paths }} - - - name: Clean up resources - shell: bash - run: | - rm -rf . diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 8b7017fc9..4cbb4c7a4 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -11,21 +11,53 @@ jobs: build-esp32: runs-on: ubuntu-latest steps: - - name: Build ESP32 - id: build - uses: ./.github/actions/build-variant + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: | - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware.bin - ota-firmware-target: release/bleota.bin - artifact-paths: | + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + + - name: Remove debug flags for release + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini + + - name: Build ESP32 + run: bin/build-esp32.sh ${{ inputs.board }} + + - name: Pull OTA Firmware + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/firmware-ota + file: firmware.bin + target: release/bleota.bin + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | release/*.bin release/*.elf - include-web-ui: true diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index 249f6f126..07727d711 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -13,20 +13,51 @@ jobs: build-esp32-c3: runs-on: ubuntu-latest steps: - - name: Build ESP32-C3 - id: build - uses: ./.github/actions/build-variant + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: | - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware-c3.bin - ota-firmware-target: release/bleota-c3.bin - artifact-paths: | + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + - name: Remove debug flags for release + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini + - name: Build ESP32 + run: bin/build-esp32.sh ${{ inputs.board }} + + - name: Pull OTA Firmware + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/firmware-ota + file: firmware-c3.bin + target: release/bleota-c3.bin + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | release/*.bin release/*.elf diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 7df3e5737..10773833e 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -11,21 +11,51 @@ jobs: build-esp32-s3: runs-on: ubuntu-latest steps: - - name: Build ESP32-S3 - id: build - uses: ./.github/actions/build-variant + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: | - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware-s3.bin - ota-firmware-target: release/bleota-s3.bin - artifact-paths: | + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + - name: Remove debug flags for release + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini + - name: Build ESP32 + run: bin/build-esp32.sh ${{ inputs.board }} + + - name: Pull OTA Firmware + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/firmware-ota + file: firmware-s3.bin + target: release/bleota-s3.bin + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | release/*.bin release/*.elf - include-web-ui: true diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index 85d3635cd..ac509a096 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -11,14 +11,24 @@ jobs: build-nrf52: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + - name: Build NRF52 - id: build - uses: ./.github/actions/build-variant + run: bin/build-nrf52.sh ${{ inputs.board }} + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - build-script-path: bin/build-nrf52.sh - artifact-paths: | + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | release/*.hex release/*.uf2 release/*.elf diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index d3e19f8fd..6e258fe2a 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -11,13 +11,23 @@ jobs: build-rpi2040: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + - name: Build Raspberry Pi 2040 - id: build - uses: ./.github/actions/build-variant + run: ./bin/build-rpi2040.sh ${{ inputs.board }} + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - build-script-path: bin/build-rpi2040.sh - artifact-paths: | + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | release/*.uf2 release/*.elf diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml index 9252087df..d13c52c8a 100644 --- a/.github/workflows/build_stm32.yml +++ b/.github/workflows/build_stm32.yml @@ -11,13 +11,23 @@ jobs: build-stm32: runs-on: ubuntu-latest steps: - - name: Build Raspberry Pi 2040 - id: build - uses: ./.github/actions/build-variant + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Build STM32 + run: bin/build-stm32.sh ${{ inputs.board }} + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - build-script-path: bin/build-stm32.sh - artifact-paths: | + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | release/*.hex release/*.bin From 2e24d244beb0f1ef89f88b44da91b65d6876be50 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 22 Sep 2024 23:00:32 +0800 Subject: [PATCH 1183/1377] Make Ublox code more readable (#4727) * Simplify Ublox code Ublox comes in a myriad of versions and settings. Presently our configuration code does a lot of branching based on versions being or not being present. This patch adds version detection earlier in the piece and branches on the set gnssModel instead to create separate setup methods for Ublox 6, Ublox 7/8/9, and Ublox10. Additionally, adds a macro to make the code much shorter and more readable. * Make trunk happy * Make trunk happy --------- Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 410 ++++++++++++++++++------------------------------ src/gps/GPS.h | 8 +- src/gps/ubx.h | 7 + 3 files changed, 164 insertions(+), 261 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 01fa65816..147858cdb 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -525,268 +525,144 @@ bool GPS::setup() delay(250); _serial_gps->write("$PAIR513*3D\r\n"); // save configuration + } else if (gnssModel == GNSS_MODEL_UBLOX6) { + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "Unable to disable text info messages.\n", 500); + SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "Unable to enable interference resistance.\n", 500); + SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "Unable to configure NAVX5 settings.\n", 500); - } else if (gnssModel == GNSS_MODEL_UBLOX) { - // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) - // We need set it because by default it is GPS only, and we want to use GLONASS too - // Also we need SBAS for better accuracy and extra features - // ToDo: Dynamic configure GNSS systems depending of LoRa region + // Turn off unwanted NMEA messages, set update rate + SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "Unable to set GPS update rate.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "Unable to disable NMEA GLL.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "Unable to Enable NMEA GSA.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "Unable to disable NMEA GSV.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "Unable to disable NMEA VTG.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "Unable to enable NMEA RMC.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "Unable to enable NMEA GGA.\n", 500); - if (strncmp(info.hwVersion, "000A0000", 8) != 0) { - if (strncmp(info.hwVersion, "00040007", 8) != 0) { - // The original ublox Neo-6 is GPS only and doesn't support the UBX-CFG-GNSS message - // Max7 seems to only support GPS *or* GLONASS - // Neo-7 is supposed to support GPS *and* GLONASS but NAKs the CFG-GNSS command to do it - // So treat all the u-blox 7 series as GPS only - // M8 can support 3 constallations at once so turn on GPS, GLONASS and Galileo (or BeiDou) + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_ECO, "Unable to enable powersaving ECO mode for Neo-6.\n", 500); + SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "Unable to enable powersaving details for GPS.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_AID, "Unable to disable UBX-AID.\n", 500); - if (strncmp(info.hwVersion, "00070000", 8) == 0) { - LOG_DEBUG("Setting GPS+SBAS\n"); - msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7); - _serial_gps->write(UBXscratch, msglen); - } else { - msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8); - _serial_gps->write(UBXscratch, msglen); - } + msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); + _serial_gps->write(UBXscratch, msglen); + if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { + LOG_WARN("Unable to save GNSS module configuration.\n"); + } else { + LOG_INFO("GNSS module configuration saved!\n"); + } + } else if (gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || gnssModel == GNSS_MODEL_UBLOX9) { + if (gnssModel == GNSS_MODEL_UBLOX7) { + LOG_DEBUG("Setting GPS+SBAS\n"); + msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7); + _serial_gps->write(UBXscratch, msglen); + } else { // 8,9 + msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8); + _serial_gps->write(UBXscratch, msglen); + } - if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) { - // It's not critical if the module doesn't acknowledge this configuration. - LOG_INFO("Unable to reconfigure GNSS - defaults maintained. Is this module GPS-only?\n"); - } else { - if (strncmp(info.hwVersion, "00070000", 8) == 0) { - LOG_INFO("GNSS configured for GPS+SBAS. Pause for 0.75s before sending next command.\n"); - } else { - LOG_INFO( - "GNSS configured for GPS+SBAS+GLONASS+Galileo. Pause for 0.75s before sending next command.\n"); - } - // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next - // commands for the M8 it tends to be more... 1 sec should be enough ;>) - delay(1000); - } + if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) { + // It's not critical if the module doesn't acknowledge this configuration. + LOG_INFO("Unable to reconfigure GNSS - defaults maintained. Is this module GPS-only?\n"); + } else { + if (gnssModel == GNSS_MODEL_UBLOX7) { + LOG_INFO("GNSS configured for GPS+SBAS.\n"); + } else { // 8,9 + LOG_INFO("GNSS configured for GPS+SBAS+GLONASS+Galileo.\n"); } - // Disable Text Info messages - msglen = makeUBXPacket(0x06, 0x02, sizeof(_message_DISABLE_TXT_INFO), _message_DISABLE_TXT_INFO); + // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next + // commands for the M8 it tends to be more... 1 sec should be enough ;>) + delay(1000); + } + + // Disable Text Info messages //6,7,8,9 + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "Unable to disable text info messages.\n", 500); + + if (gnssModel == GNSS_MODEL_UBLOX8) { // 8 clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x02, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable text info messages.\n"); - } - // ToDo add M10 tests for below - if (strncmp(info.hwVersion, "00080000", 8) == 0) { - msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM_8), _message_JAM_8); + SEND_UBX_PACKET(0x06, 0x39, _message_JAM_8, "Unable to enable interference resistance.\n", 500); + + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5_8, "Unable to configure NAVX5_8 settings.\n", 500); + } else { // 6,7,9 + SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "Unable to enable interference resistance.\n", 500); + SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "Unable to configure NAVX5 settings.\n", 500); + } + // Turn off unwanted NMEA messages, set update rate + SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "Unable to set GPS update rate.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "Unable to disable NMEA GLL.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "Unable to Enable NMEA GSA.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "Unable to disable NMEA GSV.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "Unable to disable NMEA VTG.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "Unable to enable NMEA RMC.\n", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "Unable to enable NMEA GGA.\n", 500); + + if (uBloxProtocolVersion >= 18) { + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "Unable to enable powersaving for GPS.\n", 500); + SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "Unable to enable powersaving details for GPS.\n", 500); + + // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats. + if (gnssModel == GNSS_MODEL_UBLOX8) { clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x39, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable interference resistance.\n"); - } - - msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5_8), _message_NAVX5_8); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x23, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to configure NAVX5_8 settings.\n"); - } - } else { - msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM_6_7), _message_JAM_6_7); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x39, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable interference resistance.\n"); - } - - msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5), _message_NAVX5); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x23, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to configure NAVX5 settings.\n"); - } - } - // Turn off unwanted NMEA messages, set update rate - - msglen = makeUBXPacket(0x06, 0x08, sizeof(_message_1HZ), _message_1HZ); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x08, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to set GPS update rate.\n"); - } - - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GLL), _message_GLL); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable NMEA GLL.\n"); - } - - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSA), _message_GSA); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to Enable NMEA GSA.\n"); - } - - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSV), _message_GSV); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable NMEA GSV.\n"); - } - - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_VTG), _message_VTG); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable NMEA VTG.\n"); - } - - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_RMC), _message_RMC); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable NMEA RMC.\n"); - } - - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GGA), _message_GGA); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable NMEA GGA.\n"); - } - - if (uBloxProtocolVersion >= 18) { - msglen = makeUBXPacket(0x06, 0x86, sizeof(_message_PMS), _message_PMS); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x86, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving for GPS.\n"); - } - msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving details for GPS.\n"); - } - // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats. - if (strncmp(info.hwVersion, "00080000", 8) == 0) { - msglen = makeUBXPacket(0x06, 0x17, sizeof(_message_NMEA), _message_NMEA); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x17, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable NMEA 4.10.\n"); - } - } - } else { - if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode is only for Neo-6 - msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_ECO); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x11, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving ECO mode for Neo-6.\n"); - } - msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving details for GPS.\n"); - } - msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_AID), _message_AID); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable UBX-AID.\n"); - } - } else { - msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_PSM); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x11, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving mode for GPS.\n"); - } - - msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving details for GPS.\n"); - } - } + SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "Unable to enable NMEA 4.10.\n", 500); } } else { - // LOG_INFO("u-blox M10 hardware found.\n"); - delay(1000); - // First disable all NMEA messages in RAM layer - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_RAM), _message_VALSET_DISABLE_NMEA_RAM); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable NMEA messages for M10 GPS RAM.\n"); - } - delay(250); - // Next disable unwanted NMEA messages in BBR layer - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_BBR), _message_VALSET_DISABLE_NMEA_BBR); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable NMEA messages for M10 GPS BBR.\n"); - } - delay(250); - // Disable Info txt messages in RAM layer - msglen = - makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_RAM), _message_VALSET_DISABLE_TXT_INFO_RAM); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable Info messages for M10 GPS RAM.\n"); - } - delay(250); - // Next disable Info txt messages in BBR layer - msglen = - makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_BBR), _message_VALSET_DISABLE_TXT_INFO_BBR); - clearBuffer(); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable Info messages for M10 GPS BBR.\n"); - } - // Do M10 configuration for Power Management. - - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_RAM), _message_VALSET_PM_RAM); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving for M10 GPS RAM.\n"); - } - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_BBR), _message_VALSET_PM_BBR); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable powersaving for M10 GPS BBR.\n"); - } - - delay(250); - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_RAM), _message_VALSET_ITFM_RAM); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable Jamming detection M10 GPS RAM.\n"); - } - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_BBR), _message_VALSET_ITFM_BBR); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable Jamming detection M10 GPS BBR.\n"); - } - - // Here is where the init commands should go to do further M10 initialization. - delay(250); - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_RAM), _message_VALSET_DISABLE_SBAS_RAM); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable SBAS M10 GPS RAM.\n"); - } - delay(750); // will cause a receiver restart so wait a bit - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_BBR), _message_VALSET_DISABLE_SBAS_BBR); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to disable SBAS M10 GPS BBR.\n"); - } - delay(750); // will cause a receiver restart so wait a bit - // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep. - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_BBR), _message_VALSET_ENABLE_NMEA_BBR); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable messages for M10 GPS BBR.\n"); - } - delay(250); - // Next enable wanted NMEA messages in RAM layer - msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_RAM), _message_VALSET_ENABLE_NMEA_RAM); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to enable messages for M10 GPS RAM.\n"); - } - // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. - // BBR will survive a restart, and power off for a while, but modules with small backup - // batteries or super caps will not retain the config for a long power off time. + SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_PSM, "Unable to enable powersaving mode for GPS.\n", 500); + SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "Unable to enable powersaving details for GPS.\n", 500); } + + msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); + _serial_gps->write(UBXscratch, msglen); + if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { + LOG_WARN("Unable to save GNSS module configuration.\n"); + } else { + LOG_INFO("GNSS module configuration saved!\n"); + } + } else if (gnssModel == GNSS_MODEL_UBLOX10) { + delay(1000); + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "Unable to disable NMEA messages in M10 RAM.\n", 300); + delay(750); + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "Unable to disable NMEA messages in M10 BBR.\n", 300); + delay(750); + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, + "Unable to disable Info messages for M10 GPS RAM.\n", 300); + delay(750); + // Next disable Info txt messages in BBR layer + clearBuffer(); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, + "Unable to disable Info messages for M10 GPS BBR.\n", 300); + delay(750); + // Do M10 configuration for Power Management. + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_RAM, "Unable to enable powersaving for M10 GPS RAM.\n", 300); + delay(750); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_BBR, "Unable to enable powersaving for M10 GPS BBR.\n", 300); + delay(750); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_RAM, "Unable to enable Jamming detection M10 GPS RAM.\n", 300); + delay(750); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_BBR, "Unable to enable Jamming detection M10 GPS BBR.\n", 300); + delay(750); + // Here is where the init commands should go to do further M10 initialization. + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "Unable to disable SBAS M10 GPS RAM.\n", 300); + delay(750); // will cause a receiver restart so wait a bit + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "Unable to disable SBAS M10 GPS BBR.\n", 300); + delay(750); // will cause a receiver restart so wait a bit + + // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep. + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "Unable to enable messages for M10 GPS BBR.\n", 300); + delay(750); + // Next enable wanted NMEA messages in RAM layer + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "Unable to enable messages for M10 GPS RAM.\n", 500); + delay(750); + + // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. + // BBR will survive a restart, and power off for a while, but modules with small backup + // batteries or super caps will not retain the config for a long power off time. msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); _serial_gps->write(UBXscratch, msglen); if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { @@ -949,7 +825,8 @@ void GPS::setPowerPMU(bool on) void GPS::setPowerUBLOX(bool on, uint32_t sleepMs) { // Abort: if not UBLOX hardware - if (gnssModel != GNSS_MODEL_UBLOX) + if (!(gnssModel == GNSS_MODEL_UBLOX6 || gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || + gnssModel == GNSS_MODEL_UBLOX9 || gnssModel == GNSS_MODEL_UBLOX10)) return; // If waking @@ -972,7 +849,7 @@ void GPS::setPowerUBLOX(bool on, uint32_t sleepMs) } // Determine hardware version - if (strncmp(info.hwVersion, "000A0000", 8) != 0) { + if (gnssModel == GNSS_MODEL_UBLOX10) { // Encode the sleep time in millis into the packet for (int i = 0; i < 4; i++) gps->_message_PMREQ[0 + i] = sleepMs >> (i * 8); @@ -1031,7 +908,9 @@ void GPS::down() // Check whether the GPS hardware is capable of GPS_SOFTSLEEP // If not, fallback to GPS_HARDSLEEP instead bool softsleepSupported = false; - if (gnssModel == GNSS_MODEL_UBLOX) // U-blox is supported via PMREQ + // U-blox is supported via PMREQ + if (gnssModel == GNSS_MODEL_UBLOX6 || gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || + gnssModel == GNSS_MODEL_UBLOX9 || gnssModel == GNSS_MODEL_UBLOX10) softsleepSupported = true; #ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin softsleepSupported = true; @@ -1106,7 +985,9 @@ int32_t GPS::runOnce() // if we have received valid NMEA claim we are connected setConnected(); } else { - if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) { + if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && + (gnssModel == GNSS_MODEL_UBLOX6 || gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || + gnssModel == GNSS_MODEL_UBLOX9 || gnssModel == GNSS_MODEL_UBLOX10)) { // reset the GPS on next bootup if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); @@ -1335,9 +1216,9 @@ GnssModel_t GPS::probe(int serialSpeed) strncpy((char *)buffer, &(info.extension[i][4]), sizeof(buffer)); // LOG_DEBUG("GetModel:%s\n", (char *)buffer); if (strlen((char *)buffer)) { - LOG_INFO("UBlox GNSS probe succeeded, using UBlox %s GNSS Module\n", (char *)buffer); + LOG_INFO("%s detected, using GNSS_MODEL_UBLOX\n", (char *)buffer); } else { - LOG_INFO("UBlox GNSS probe succeeded, using UBlox GNSS Module\n"); + LOG_INFO("Generic Ublox detected, using GNSS_MODEL_UBLOX\n"); } } else if (!strncmp(info.extension[i], "PROTVER", 7)) { char *ptr = nullptr; @@ -1352,9 +1233,20 @@ GnssModel_t GPS::probe(int serialSpeed) } } } + if (strncmp(info.hwVersion, "00040007", 8) == 0) { + return GNSS_MODEL_UBLOX6; + } else if (strncmp(info.hwVersion, "00070000", 8) == 0) { + return GNSS_MODEL_UBLOX7; + } else if (strncmp(info.hwVersion, "00080000", 8) == 0) { + return GNSS_MODEL_UBLOX8; + } else if (strncmp(info.hwVersion, "00190000", 8) == 0) { + return GNSS_MODEL_UBLOX9; + } else if (strncmp(info.hwVersion, "000A0000", 8) == 0) { + return GNSS_MODEL_UBLOX10; + } } - return GNSS_MODEL_UBLOX; + return GNSS_MODEL_UNKNOWN; } GPS *GPS::createGps() diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 3423edb68..48aecc8b9 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -26,7 +26,11 @@ struct uBloxGnssModelInfo { typedef enum { GNSS_MODEL_ATGM336H, GNSS_MODEL_MTK, - GNSS_MODEL_UBLOX, + GNSS_MODEL_UBLOX6, + GNSS_MODEL_UBLOX7, + GNSS_MODEL_UBLOX8, + GNSS_MODEL_UBLOX9, + GNSS_MODEL_UBLOX10, GNSS_MODEL_UC6580, GNSS_MODEL_UNKNOWN, GNSS_MODEL_MTK_L76B, @@ -310,4 +314,4 @@ class GPS : private concurrency::OSThread }; extern GPS *gps; -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file diff --git a/src/gps/ubx.h b/src/gps/ubx.h index 0852c331d..64e45f160 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -1,3 +1,10 @@ +#define SEND_UBX_PACKET(TYPE, ID, DATA, ERRMSG, TIMEOUT) \ + msglen = makeUBXPacket(TYPE, ID, sizeof(DATA), DATA); \ + _serial_gps->write(UBXscratch, msglen); \ + if (getACK(TYPE, ID, TIMEOUT) != GNSS_RESPONSE_OK) { \ + LOG_WARN(#ERRMSG); \ + } + // Power Management uint8_t GPS::_message_PMREQ[] PROGMEM = { From 18aac0ba25e0a638fd7cbab4b73c95105892b88e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 22 Sep 2024 16:09:46 -0500 Subject: [PATCH 1184/1377] Consider the LoRa header when checking packet length --- src/mesh/RadioInterface.h | 1 + src/mesh/Router.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index f1016e3d8..d0d20926c 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -10,6 +10,7 @@ #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_RHPACKETLEN 256 +#define LORA_HEADER_LENGTH 16 #define PACKET_FLAGS_HOP_LIMIT_MASK 0x07 #define PACKET_FLAGS_WANT_ACK_MASK 0x08 diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3cf30519b..a6b946761 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -475,7 +475,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) } } */ - if (numbytes > MAX_RHPACKETLEN) + if (numbytes + LORA_HEADER_LENGTH > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; // printBytes("plaintext", bytes, numbytes); @@ -499,7 +499,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); - if (numbytes + 12 > MAX_RHPACKETLEN) + if (numbytes + LORA_HEADER_LENGTH + 12 > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { From 1f8aa1efc75dd337a77fa6b56425de71a33b8704 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 23 Sep 2024 18:22:06 +0800 Subject: [PATCH 1185/1377] Minor fix (#4666) * Minor fixes It turns out setting a map value with the index notation causes an lookup that can be avoided with emplace. Apply this to one line in the StoreForward module. Fix also Cppcheck-determined highly minor performance increase by passing gpiochipname as a const reference :) The amount of cycles used on this laptop while learning about these callouts from cppcheck is unlikely to ever be more than the cycles saved by the fixes ;) * Update PortduinoGlue.cpp --- src/modules/esp32/StoreForwardModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index c696d342a..db09a0bfd 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -127,7 +127,7 @@ uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_ { uint32_t count = 0; if (lastRequest.find(dest) == lastRequest.end()) { - lastRequest[dest] = 0; + lastRequest.emplace(dest, 0); } for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) { if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) { From 11598beb160d590cf10948423a7341cda69df007 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Tue, 17 Sep 2024 22:12:12 +1200 Subject: [PATCH 1186/1377] Implement optional second I2C bus for NRF52840 Enabled at compile-time if WIRE_INFERFACES_COUNT defined as 2 --- src/detect/ScanI2CTwoWire.cpp | 6 +++--- src/gps/RTC.cpp | 8 ++++---- src/input/cardKbI2cImpl.cpp | 2 +- src/input/kbI2cBase.cpp | 2 +- src/main.cpp | 4 ++++ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index f09eb3b95..472a9d70a 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -162,13 +162,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) Melopero_RV3028 rtc; #endif -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) if (port == I2CPort::WIRE1) { i2cBus = &Wire1; } else { #endif i2cBus = &Wire; -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) } #endif @@ -423,7 +423,7 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const if (address.port == ScanI2C::I2CPort::WIRE) { return &Wire; } else { -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) return &Wire1; #else return &Wire; diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 728284242..98dea4674 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -29,7 +29,7 @@ void readFromRTC() if (rtc_found.address == RV3028_RTC) { uint32_t now = millis(); Melopero_RV3028 rtc; -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.initI2C(); @@ -58,7 +58,7 @@ void readFromRTC() uint32_t now = millis(); PCF8563_Class rtc; -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.begin(); @@ -150,7 +150,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) #ifdef RV3028_RTC if (rtc_found.address == RV3028_RTC) { Melopero_RV3028 rtc; -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.initI2C(); @@ -164,7 +164,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) if (rtc_found.address == PCF8563_RTC) { PCF8563_Class rtc; -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.begin(); diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 8aaebcb45..1f7fd284d 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -16,7 +16,7 @@ void CardKbI2cImpl::init() uint8_t i2caddr_asize = 3; auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); -#if defined(I2C_SDA1) +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); #endif i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 2692fc80d..a25a85d82 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -33,7 +33,7 @@ int32_t KbI2cBase::runOnce() if (!i2cBus) { switch (cardkb_found.port) { case ScanI2C::WIRE1: -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) LOG_DEBUG("Using I2C Bus 1 (the second one)\n"); i2cBus = &Wire1; if (cardkb_found.address == BBQ10_KB_ADDR) { diff --git a/src/main.cpp b/src/main.cpp index e24ba68b3..0ab9c0e34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -360,6 +360,8 @@ void setup() Wire1.begin(); #elif defined(I2C_SDA1) && !defined(ARCH_RP2040) Wire1.begin(I2C_SDA1, I2C_SCL1); +#elif defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2) + Wire1.begin(); #endif #if defined(I2C_SDA) && defined(ARCH_RP2040) @@ -427,6 +429,8 @@ void setup() #elif defined(I2C_SDA1) && !defined(ARCH_RP2040) Wire1.begin(I2C_SDA1, I2C_SCL1); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); +#elif defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); #endif #if defined(I2C_SDA) && defined(ARCH_RP2040) From f960164c0e68a02c6a66546e470295619efb10d4 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Tue, 17 Sep 2024 22:17:53 +1200 Subject: [PATCH 1187/1377] Add I2C bus to Heltec T114 header pins SDA: P0.13 SCL: P0.16 Uses bus 1, leaving bus 0 routed to the unpopulated footprint for the RTC (general future-proofing) --- variants/heltec_mesh_node_t114/variant.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index 454e66931..6a7c85b92 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -92,13 +92,22 @@ No longer populated on PCB #define PIN_SERIAL2_TX (0 + 10) // #define PIN_SERIAL2_EN (0 + 17) -/** - Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 +/* + * I2C + */ -#define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) +#define WIRE_INTERFACES_COUNT 2 + +// I2C bus 0 +// Routed to footprint for PCF8563TS RTC +// Not populated on T114 V1, maybe in future? +#define PIN_WIRE_SDA (0 + 26) // P0.26 +#define PIN_WIRE_SCL (0 + 27) // P0.27 + +// I2C bus 1 +// Available on header pins, for general use +#define PIN_WIRE1_SDA (0 + 13) // P0.13 +#define PIN_WIRE1_SCL (0 + 16) // P0.16 // QSPI Pins #define PIN_QSPI_SCK (32 + 14) From 1487ca2a3025267a5fb3150935863001fc82a5e4 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Wed, 18 Sep 2024 03:28:23 +1200 Subject: [PATCH 1188/1377] Tidier macros --- src/configuration.h | 10 ++++++++++ src/detect/ScanI2CTwoWire.cpp | 6 +++--- src/gps/RTC.cpp | 8 ++++---- src/input/cardKbI2cImpl.cpp | 2 +- src/input/kbI2cBase.cpp | 2 +- src/main.cpp | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 349bd2870..7416e0a3e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -210,6 +210,16 @@ along with this program. If not, see . #define MINIMUM_SAFE_FREE_HEAP 1500 #endif +#ifndef WIRE_INTERFACES_COUNT +// Officially an NRF52 macro +// Repurposed cross-platform to identify devices using Wire1 +#if defined(I2C_SDA1) || defined(PIN_WIRE_SDA) +#define WIRE_INTERFACES_COUNT 2 +#elif HAS_WIRE +#define WIRE_INTERFACES_COUNT 1 +#endif +#endif + /* Step #3: mop up with disabled values for HAS_ options not handled by the above two */ #ifndef HAS_WIFI diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 472a9d70a..98f40be76 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -162,13 +162,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) Melopero_RV3028 rtc; #endif -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 if (port == I2CPort::WIRE1) { i2cBus = &Wire1; } else { #endif i2cBus = &Wire; -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 } #endif @@ -423,7 +423,7 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const if (address.port == ScanI2C::I2CPort::WIRE) { return &Wire; } else { -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 return &Wire1; #else return &Wire; diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 98dea4674..b6cab5a6e 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -29,7 +29,7 @@ void readFromRTC() if (rtc_found.address == RV3028_RTC) { uint32_t now = millis(); Melopero_RV3028 rtc; -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.initI2C(); @@ -58,7 +58,7 @@ void readFromRTC() uint32_t now = millis(); PCF8563_Class rtc; -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.begin(); @@ -150,7 +150,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) #ifdef RV3028_RTC if (rtc_found.address == RV3028_RTC) { Melopero_RV3028 rtc; -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.initI2C(); @@ -164,7 +164,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) if (rtc_found.address == PCF8563_RTC) { PCF8563_Class rtc; -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); #else rtc.begin(); diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 1f7fd284d..f1df6b137 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -16,7 +16,7 @@ void CardKbI2cImpl::init() uint8_t i2caddr_asize = 3; auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); #endif i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index a25a85d82..1d8154bcf 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -33,7 +33,7 @@ int32_t KbI2cBase::runOnce() if (!i2cBus) { switch (cardkb_found.port) { case ScanI2C::WIRE1: -#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) +#if WIRE_INTERFACES_COUNT == 2 LOG_DEBUG("Using I2C Bus 1 (the second one)\n"); i2cBus = &Wire1; if (cardkb_found.address == BBQ10_KB_ADDR) { diff --git a/src/main.cpp b/src/main.cpp index 0ab9c0e34..447e12a62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -360,7 +360,7 @@ void setup() Wire1.begin(); #elif defined(I2C_SDA1) && !defined(ARCH_RP2040) Wire1.begin(I2C_SDA1, I2C_SCL1); -#elif defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2) +#elif WIRE_INTERFACES_COUNT == 2 Wire1.begin(); #endif From 76900555e8382baab00ed95ea40de5fc12e0c707 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 19 Sep 2024 02:49:24 +1200 Subject: [PATCH 1189/1377] Swap SDA and SCL SDA=P0.16, SCL=P0.13 --- variants/heltec_mesh_node_t114/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index 6a7c85b92..2cea3ef2f 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -106,8 +106,8 @@ No longer populated on PCB // I2C bus 1 // Available on header pins, for general use -#define PIN_WIRE1_SDA (0 + 13) // P0.13 -#define PIN_WIRE1_SCL (0 + 16) // P0.16 +#define PIN_WIRE1_SDA (0 + 16) // P0.16 +#define PIN_WIRE1_SCL (0 + 13) // P0.13 // QSPI Pins #define PIN_QSPI_SCK (32 + 14) From 3ff9398b92e43bf15a5fe5858f651d9f88b1e237 Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Mon, 23 Sep 2024 05:34:19 -0700 Subject: [PATCH 1190/1377] Revert "Update classes on protobufs update" (#4824) * Revert "Update classes on protobufs update" * remove quotes to fix trunk. --------- Co-authored-by: Tom Fifield --- .github/workflows/update_protobufs.yml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index eb1ca3648..7ce767370 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -1,10 +1,5 @@ name: Update protobufs and regenerate classes -on: - pull_request: - branches: - - master - paths: - - protobufs/** +on: workflow_dispatch jobs: update-protobufs: @@ -16,6 +11,10 @@ jobs: with: submodules: true + - name: Update submodule + run: | + git submodule update --remote protobufs + - name: Download nanopb run: | wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz @@ -26,11 +25,10 @@ jobs: run: | ./bin/regen-protos.sh - - name: Commit changes - uses: EndBug/add-and-commit@v9 + - name: Create pull request + uses: peter-evans/create-pull-request@v7 with: - add: src/mesh - author_name: CI Bot - author_email: meshtastic-ci-bot@users.noreply.github.com - default_author: github_actor - message: Update classes from protobufs + title: Update protobufs and classes + add-paths: | + protobufs + src/mesh From e8829b8f52045796a1b2410d241332f833274d10 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 23 Sep 2024 08:58:14 -0500 Subject: [PATCH 1191/1377] Refactor and consolidate time window logic (#4826) * Refactor and consolidate windowing logic * Trunk * Fixes * More * Fix braces and remove unused now variables. There was a brace in src/mesh/RadioLibInterface.cpp that was breaking compile on some architectures. Additionally, there were some brace errors in src/modules/Telemetry/AirQualityTelemetry.cpp src/modules/Telemetry/EnvironmentTelemetry.cpp src/mesh/wifi/WiFiAPClient.cpp Move throttle include in WifiAPClient.cpp to top. Add Default.h to sleep.cpp rest of files just remove unused now variables. * Remove a couple more meows --------- Co-authored-by: Tom Fifield --- src/Power.cpp | 4 +++- src/SerialConsole.cpp | 6 ++++-- src/gps/GPS.cpp | 19 +++++++++---------- src/gps/RTC.cpp | 3 ++- src/graphics/EInkDynamicDisplay.cpp | 9 ++++----- src/graphics/Screen.cpp | 8 +++++--- src/input/ExpressLRSFiveWay.cpp | 9 ++++----- src/input/ScanAndSelect.cpp | 5 +++-- src/input/SerialKeyboard.cpp | 3 ++- src/main.cpp | 3 ++- src/mesh/LR11x0Interface.cpp | 10 ++++++---- src/mesh/PacketHistory.cpp | 14 ++++++-------- src/mesh/PhoneAPI.cpp | 5 +++-- src/mesh/RadioLibInterface.cpp | 5 +++-- src/mesh/SX126xInterface.cpp | 12 +++++++----- src/mesh/SX128xInterface.cpp | 9 +++++---- src/mesh/StreamAPI.cpp | 6 +++--- src/mesh/Throttle.cpp | 8 ++++++++ src/mesh/Throttle.h | 1 + src/mesh/wifi/WiFiAPClient.cpp | 5 +++-- src/modules/CannedMessageModule.cpp | 3 ++- src/modules/DetectionSensorModule.cpp | 9 ++++++--- src/modules/NeighborInfoModule.cpp | 4 +++- src/modules/NodeInfoModule.cpp | 8 ++++---- src/modules/PositionModule.cpp | 2 +- src/modules/PowerStressModule.cpp | 3 ++- src/modules/RangeTestModule.cpp | 3 ++- src/modules/RemoteHardwareModule.cpp | 6 +++--- src/modules/SerialModule.cpp | 10 +++++----- src/modules/Telemetry/AirQualityTelemetry.cpp | 10 +++++----- .../Telemetry/EnvironmentTelemetry.cpp | 14 +++++++------- src/modules/Telemetry/PowerTelemetry.cpp | 14 +++++++------- .../Telemetry/Sensor/NAU7802Sensor.cpp | 3 ++- src/modules/esp32/StoreForwardModule.cpp | 6 +++++- src/mqtt/MQTT.cpp | 3 ++- src/sleep.cpp | 5 ++++- 36 files changed, 143 insertions(+), 104 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 61a6c987d..b3a67abd5 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -13,6 +13,7 @@ #include "power.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "Throttle.h" #include "buzz/buzz.h" #include "configuration.h" #include "main.h" @@ -30,6 +31,7 @@ #if HAS_WIFI #include #endif + #endif #ifndef DELAY_FOREVER @@ -244,7 +246,7 @@ class AnalogBatteryLevel : public HasBatteryLevel config.power.adc_multiplier_override > 0 ? config.power.adc_multiplier_override : ADC_MULTIPLIER; // Do not call analogRead() often. const uint32_t min_read_interval = 5000; - if (millis() - last_read_time_ms > min_read_interval) { + if (!Throttle::isWithinTimespanMs(last_read_time_ms, min_read_interval)) { last_read_time_ms = millis(); uint32_t raw = 0; diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index b911e15da..2c1133771 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -1,6 +1,8 @@ #include "SerialConsole.h" +#include "Default.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "Throttle.h" #include "configuration.h" #include "time.h" @@ -47,7 +49,7 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con #if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040) time_t timeout = millis(); while (!Port) { - if ((millis() - timeout) < 5000) { + if (Throttle::isWithinTimespanMs(timeout, FIVE_SECONDS_MS)) { delay(100); } else { break; @@ -73,7 +75,7 @@ void SerialConsole::flush() bool SerialConsole::checkIsConnected() { uint32_t now = millis(); - return (now - lastContactMsec) < SERIAL_CONNECTION_TIMEOUT; + return Throttle::isWithinTimespanMs(lastContactMsec, SERIAL_CONNECTION_TIMEOUT); } /** diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 147858cdb..4fa676913 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -6,6 +6,7 @@ #include "NodeDB.h" #include "PowerMon.h" #include "RTC.h" +#include "Throttle.h" #include "main.h" // pmu_found #include "sleep.h" @@ -206,7 +207,7 @@ GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMilli // ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX | // ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX | - while (millis() - startTime < waitMillis) { + while (Throttle::isWithinTimespanMs(startTime, waitMillis)) { if (_serial_gps->available()) { buffer[bufferPos++] = _serial_gps->read(); @@ -275,7 +276,7 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) buf[9] += buf[8]; } - while (millis() - startTime < waitMillis) { + while (Throttle::isWithinTimespanMs(startTime, waitMillis)) { if (ack > 9) { #ifdef GPS_DEBUG LOG_DEBUG("\n"); @@ -332,7 +333,7 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t uint32_t startTime = millis(); uint16_t needRead; - while (millis() - startTime < waitMillis) { + while (Throttle::isWithinTimespanMs(startTime, waitMillis)) { if (_serial_gps->available()) { int c = _serial_gps->read(); switch (ubxFrameCounter) { @@ -1420,16 +1421,15 @@ bool GPS::lookForTime() #ifdef GNSS_AIROHA uint8_t fix = reader.fixQuality(); - uint32_t now = millis(); if (fix > 0) { if (lastFixStartMsec > 0) { - if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { + if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { return false; } else { clearBuffer(); } } else { - lastFixStartMsec = now; + lastFixStartMsec = millis(); return false; } } else { @@ -1473,16 +1473,15 @@ bool GPS::lookForLocation() #ifdef GNSS_AIROHA if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { uint8_t fix = reader.fixQuality(); - uint32_t now = millis(); if (fix > 0) { if (lastFixStartMsec > 0) { - if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) { + if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { return false; } else { clearBuffer(); } } else { - lastFixStartMsec = now; + lastFixStartMsec = millis(); return false; } } else { @@ -1712,4 +1711,4 @@ void GPS::toggleGpsMode() enable(); } } -#endif // Exclude GPS +#endif // Exclude GPS \ No newline at end of file diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index b6cab5a6e..d9ac56b74 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -2,6 +2,7 @@ #include "configuration.h" #include "detect/ScanI2C.h" #include "main.h" +#include #include #include @@ -127,7 +128,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) } else if (q == RTCQualityGPS) { shouldSet = true; LOG_DEBUG("Reapplying GPS time: %ld secs\n", printableEpoch); - } else if (q == RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) { + } else if (q == RTCQualityNTP && !Throttle::isWithinTimespanMs(lastSetMsec, (12 * 60 * 60 * 1000UL))) { // Every 12 hrs we will slam in a new NTP or Phone GPS / NTP time, to correct for local RTC clock drift shouldSet = true; LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", printableEpoch); diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index c31941a60..ca994b2c9 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -1,3 +1,4 @@ +#include "Throttle.h" #include "configuration.h" #if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) @@ -231,15 +232,13 @@ void EInkDynamicDisplay::checkForPromotion() // Is it too soon for another frame of this type? void EInkDynamicDisplay::checkRateLimiting() { - uint32_t now = millis(); - // Sanity check: millis() overflow - just let the update run.. - if (previousRunMs > now) + if (previousRunMs > millis()) return; // Skip update: too soon for BACKGROUND if (frameFlags == BACKGROUND) { - if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) { + if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_BACKGROUND_SEC * 1000)) { refresh = SKIPPED; reason = EXCEEDED_RATELIMIT_FULL; return; @@ -252,7 +251,7 @@ void EInkDynamicDisplay::checkRateLimiting() // Skip update: too soon for RESPONSIVE if (frameFlags & RESPONSIVE) { - if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) { + if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000)) { refresh = SKIPPED; reason = EXCEEDED_RATELIMIT_FAST; LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 31f522a43..19b20e8dc 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -22,6 +22,7 @@ along with this program. If not, see . #include "Screen.h" #include "../userPrefs.h" #include "PowerMon.h" +#include "Throttle.h" #include "configuration.h" #if HAS_SCREEN #include @@ -117,6 +118,7 @@ static bool heartbeat = false; #define SCREEN_HEIGHT display->getHeight() #include "graphics/ScreenFonts.h" +#include #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) @@ -1949,7 +1951,7 @@ int32_t Screen::runOnce() if (showingNormalScreen) { // standard screen loop handling here if (config.display.auto_screen_carousel_secs > 0 && - (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) { + !Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) { // If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead // Carousel is potentially a major source of E-Ink display wear @@ -2442,8 +2444,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo if (moduleConfig.store_forward.enabled) { #ifdef ARCH_ESP32 - if (millis() - storeForwardModule->lastHeartbeat > - (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit + if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat, + (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp index c444800ba..af4433dae 100644 --- a/src/input/ExpressLRSFiveWay.cpp +++ b/src/input/ExpressLRSFiveWay.cpp @@ -1,5 +1,5 @@ - #include "ExpressLRSFiveWay.h" +#include "Throttle.h" #ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE @@ -76,11 +76,10 @@ void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed) *keyValue = NO_PRESS; int newKey = readKey(); - uint32_t now = millis(); if (keyInProcess == NO_PRESS) { // New key down if (newKey != NO_PRESS) { - keyDownStart = now; + keyDownStart = millis(); // DBGLN("down=%u", newKey); } } else { @@ -88,7 +87,7 @@ void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed) if (newKey == NO_PRESS) { // DBGLN("up=%u", keyInProcess); if (!isLongPressed) { - if ((now - keyDownStart) > KEY_DEBOUNCE_MS) { + if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_DEBOUNCE_MS)) { *keyValue = keyInProcess; *keyLongPressed = false; } @@ -101,7 +100,7 @@ void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed) } // else still pressing, waiting for long if not already signaled else if (!isLongPressed) { - if ((now - keyDownStart) > KEY_LONG_PRESS_MS) { + if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_LONG_PRESS_MS)) { *keyValue = keyInProcess; *keyLongPressed = true; isLongPressed = true; diff --git a/src/input/ScanAndSelect.cpp b/src/input/ScanAndSelect.cpp index d693d768c..65ca7e332 100644 --- a/src/input/ScanAndSelect.cpp +++ b/src/input/ScanAndSelect.cpp @@ -6,6 +6,7 @@ #include "ScanAndSelect.h" #include "modules/CannedMessageModule.h" +#include // Config static const char name[] = "scanAndSelect"; // should match "allow input source" string @@ -75,7 +76,7 @@ int32_t ScanAndSelectInput::runOnce() else { // Duration enough for long press // Long press not yet fired (prevent repeat firing while held) - if (!longPressFired && now - downSinceMs > durationLongMs) { + if (!longPressFired && Throttle::isWithinTimespanMs(downSinceMs, durationLongMs)) { longPressFired = true; longPress(); } @@ -91,7 +92,7 @@ int32_t ScanAndSelectInput::runOnce() // Long press event didn't already fire if (held && !longPressFired) { // Duration enough for short press - if (now - downSinceMs > durationShortMs) { + if (!Throttle::isWithinTimespanMs(downSinceMs, durationShortMs)) { shortPress(); } } diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index 7b7a2f3ec..4827e8995 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -1,5 +1,6 @@ #include "SerialKeyboard.h" #include "configuration.h" +#include #ifdef INPUTBROKER_SERIAL_TYPE #define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file @@ -73,7 +74,7 @@ int32_t SerialKeyboard::runOnce() // Serial.print ("X"); // Serial.println (shiftRegister2, BIN); - if (millis() - lastPressTime > 500) { + if (!Throttle::isWithinTimespanMs(lastPressTime, 500)) { quickPress = 0; } diff --git a/src/main.cpp b/src/main.cpp index 447e12a62..01fccf2b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #include "Led.h" #include "RTC.h" #include "SPILock.h" +#include "Throttle.h" #include "concurrency/OSThread.h" #include "concurrency/Periodic.h" #include "detect/ScanI2C.h" @@ -1122,7 +1123,7 @@ void loop() #ifdef DEBUG_STACK static uint32_t lastPrint = 0; - if (millis() - lastPrint > 10 * 1000L) { + if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) { lastPrint = millis(); meshtastic::printThreadInfo("main"); } diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index c0742f241..e237c354f 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -1,7 +1,9 @@ #include "LR11x0Interface.h" +#include "Throttle.h" #include "configuration.h" #include "error.h" #include "mesh/NodeDB.h" + #ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" #endif @@ -275,15 +277,15 @@ template bool LR11x0Interface::isActivelyReceiving() bool detected = (irq & (RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); // Handle false detections if (detected) { - uint32_t now = millis(); if (!activeReceiveStart) { - activeReceiveStart = now; - } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID)) { + activeReceiveStart = millis(); + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && + !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID)) { // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag activeReceiveStart = 0; LOG_DEBUG("Ignore false preamble detection.\n"); return false; - } else if (now - activeReceiveStart > maxPacketTimeMsec) { + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag activeReceiveStart = 0; LOG_DEBUG("Ignore false header detection.\n"); diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 26a73a3fe..ed1c3c59c 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -5,6 +5,7 @@ #ifdef ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif +#include "Throttle.h" PacketHistory::PacketHistory() { @@ -22,18 +23,17 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd return false; // Not a floodable message ID, so we don't care } - uint32_t now = millis(); - PacketRecord r; r.id = p->id; r.sender = getFrom(p); - r.rxTimeMsec = now; + r.rxTimeMsec = millis(); auto found = recentPackets.find(r); bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently - if (seenRecently && (now - found->rxTimeMsec) >= FLOOD_EXPIRE_TIME) { // Check whether found packet has already expired - recentPackets.erase(found); // Erase and pretend packet has not been seen recently + if (seenRecently && + !Throttle::isWithinTimespanMs(found->rxTimeMsec, FLOOD_EXPIRE_TIME)) { // Check whether found packet has already expired + recentPackets.erase(found); // Erase and pretend packet has not been seen recently found = recentPackets.end(); seenRecently = false; } @@ -64,12 +64,10 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd */ void PacketHistory::clearExpiredRecentPackets() { - uint32_t now = millis(); - LOG_DEBUG("recentPackets size=%ld\n", recentPackets.size()); for (auto it = recentPackets.begin(); it != recentPackets.end();) { - if ((now - it->rxTimeMsec) >= FLOOD_EXPIRE_TIME) { + if (!Throttle::isWithinTimespanMs(it->rxTimeMsec, FLOOD_EXPIRE_TIME)) { it = recentPackets.erase(it); // erase returns iterator pointing to element immediately following the one erased } else { ++it; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 121687c49..2ed7a69db 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -25,6 +25,7 @@ #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #endif +#include "Throttle.h" #include PhoneAPI::PhoneAPI() @@ -561,12 +562,12 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) { printPacket("PACKET FROM PHONE", &p); if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && lastPortNumToRadio[p.decoded.portnum] && - (millis() - lastPortNumToRadio[p.decoded.portnum]) < (THIRTY_SECONDS_MS)) { + Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], THIRTY_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "TraceRoute can only be sent once every 30 seconds"); return false; } else if (p.decoded.portnum == meshtastic_PortNum_POSITION_APP && lastPortNumToRadio[p.decoded.portnum] && - (millis() - lastPortNumToRadio[p.decoded.portnum]) < (FIVE_SECONDS_MS)) { + Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], FIVE_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); return false; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index f299ebff2..6cdb3b99e 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -3,6 +3,7 @@ #include "NodeDB.h" #include "PowerMon.h" #include "SPILock.h" +#include "Throttle.h" #include "configuration.h" #include "error.h" #include "main.h" @@ -41,7 +42,7 @@ void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in) uint32_t start = millis(); while (digitalRead(busy)) { - if (millis() - start >= 2000) { + if (!Throttle::isWithinTimespanMs(start, 2000)) { LOG_ERROR("GPIO mid-transfer timeout, is it connected?"); return; } @@ -114,7 +115,7 @@ bool RadioLibInterface::canSendImmediately() } // If we've been trying to send the same packet more than one minute and we haven't gotten a // TX IRQ from the radio, the radio is probably broken. - if (busyTx && (millis() - lastTxStart > 60000)) { + if (busyTx && !Throttle::isWithinTimespanMs(lastTxStart, 60000)) { LOG_ERROR("Hardware Failure! busyTx for more than 60s\n"); RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_TRANSMIT_FAILED); // reboot in 5 seconds when this condition occurs. diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 6d23206bd..2c6096062 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -6,6 +6,8 @@ #include "PortduinoGlue.h" #endif +#include "Throttle.h" + // Particular boards might define a different max power based on what their hardware can do, default to max power output if not // specified (may be dangerous if using external PA and SX126x power config forgotten) #ifndef SX126X_MAX_POWER @@ -319,15 +321,15 @@ template bool SX126xInterface::isActivelyReceiving() bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED)); // Handle false detections if (detected) { - uint32_t now = millis(); if (!activeReceiveStart) { - activeReceiveStart = now; - } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID)) { + activeReceiveStart = millis(); + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && + !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID)) { // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag activeReceiveStart = 0; LOG_DEBUG("Ignore false preamble detection.\n"); return false; - } else if (now - activeReceiveStart > maxPacketTimeMsec) { + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag activeReceiveStart = 0; LOG_DEBUG("Ignore false header detection.\n"); @@ -359,4 +361,4 @@ template bool SX126xInterface::sleep() #endif return true; -} +} \ No newline at end of file diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 9ff9ac2d7..270356e26 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -1,4 +1,5 @@ #include "SX128xInterface.h" +#include "Throttle.h" #include "configuration.h" #include "error.h" #include "mesh/NodeDB.h" @@ -294,15 +295,15 @@ template bool SX128xInterface::isActivelyReceiving() // Handle false detections if (detected) { - uint32_t now = millis(); if (!activeReceiveStart) { - activeReceiveStart = now; - } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_SX128X_IRQ_HEADER_VALID)) { + activeReceiveStart = millis(); + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && + !(irq & RADIOLIB_SX128X_IRQ_HEADER_VALID)) { // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag activeReceiveStart = 0; LOG_DEBUG("Ignore false preamble detection.\n"); return false; - } else if (now - activeReceiveStart > maxPacketTimeMsec) { + } else if (Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag activeReceiveStart = 0; LOG_DEBUG("Ignore false header detection.\n"); diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 9f59aa971..c3d85ed33 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -1,6 +1,7 @@ #include "StreamAPI.h" #include "PowerFSM.h" #include "RTC.h" +#include "Throttle.h" #include "configuration.h" #define START1 0x94 @@ -20,10 +21,9 @@ int32_t StreamAPI::runOncePart() */ int32_t StreamAPI::readStream() { - uint32_t now = millis(); if (!stream->available()) { // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time - bool recentRx = (now - lastRxMsec) < 2000; + bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); return recentRx ? 5 : 250; } else { while (stream->available()) { // Currently we never want to block @@ -71,7 +71,7 @@ int32_t StreamAPI::readStream() } // we had bytes available this time, so assume we might have them next time also - lastRxMsec = now; + lastRxMsec = millis(); return 0; } } diff --git a/src/mesh/Throttle.cpp b/src/mesh/Throttle.cpp index d8f23f9dc..f278cc843 100644 --- a/src/mesh/Throttle.cpp +++ b/src/mesh/Throttle.cpp @@ -24,4 +24,12 @@ bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, vo onDefer(); } return false; +} + +/// @brief Check if the last execution time is within the interval +/// @param lastExecutionMs The last execution time in milliseconds +/// @param timeSpanMs The interval in milliseconds of the timespan +bool Throttle::isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t timeSpanMs) +{ + return (millis() - lastExecutionMs) < timeSpanMs; } \ No newline at end of file diff --git a/src/mesh/Throttle.h b/src/mesh/Throttle.h index 8115595a4..8b4bb5d30 100644 --- a/src/mesh/Throttle.h +++ b/src/mesh/Throttle.h @@ -6,4 +6,5 @@ class Throttle { public: static bool execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*func)(void), void (*onDefer)(void) = NULL); + static bool isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t intervalMs); }; \ No newline at end of file diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 07b03222e..d1169dc3b 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -23,6 +23,7 @@ static void WiFiEvent(WiFiEvent_t event); #endif #ifndef DISABLE_NTP +#include "Throttle.h" #include #endif @@ -142,7 +143,7 @@ static int32_t reconnectWiFi() } #ifndef DISABLE_NTP - if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours + if (WiFi.isConnected() && (!Throttle::isWithinTimespanMs(lastrun_ntp, 43200000) || (lastrun_ntp == 0))) { // every 12 hours LOG_DEBUG("Updating NTP time from %s\n", config.network.ntp_server); if (timeClient.update()) { LOG_DEBUG("NTP Request Success - Setting RTCQualityNTP if needed\n"); @@ -420,4 +421,4 @@ uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; } -#endif +#endif \ No newline at end of file diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 87a3e8927..a1b9c4dc0 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -27,6 +27,7 @@ #endif #include "graphics/ScreenFonts.h" +#include // Remove Canned message screen if no action is taken for some milliseconds #define INACTIVATE_AFTER_MS 20000 @@ -422,7 +423,7 @@ int32_t CannedMessageModule::runOnce() this->notifyObservers(&e); } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && - ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { + !Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) { // Reset module e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen this->currentMessageIndex = -1; diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index 20d91a381..670fd3208 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -5,6 +5,7 @@ #include "PowerFSM.h" #include "configuration.h" #include "main.h" +#include DetectionSensorModule *detectionSensorModule; #define GPIO_POLLING_INTERVAL 100 @@ -49,7 +50,8 @@ int32_t DetectionSensorModule::runOnce() // LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin)); - if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && + if (!Throttle::isWithinTimespanMs(lastSentToMesh, + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs)) && hasDetectionEvent()) { sendDetectionMessage(); return DELAYED_INTERVAL; @@ -58,8 +60,9 @@ int32_t DetectionSensorModule::runOnce() // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && - (millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, - default_telemetry_broadcast_interval_secs)) { + !Throttle::isWithinTimespanMs(lastSentToMesh, + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, + default_telemetry_broadcast_interval_secs))) { sendCurrentStateMessage(); return DELAYED_INTERVAL; } diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 218fb8801..a3a3b9bb4 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -3,6 +3,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" +#include NeighborInfoModule *neighborInfoModule; @@ -87,7 +88,8 @@ void NeighborInfoModule::cleanUpNeighbors() NodeNum my_node_id = nodeDB->getNodeNum(); for (auto it = neighbors.rbegin(); it != neighbors.rend();) { // We will remove a neighbor if we haven't heard from them in twice the broadcast interval - if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) { + if (!Throttle::isWithinTimespanMs(it->last_rx_time, it->node_broadcast_interval_secs * 2) && + (it->node_id != my_node_id)) { LOG_DEBUG("Removing neighbor with node ID 0x%x\n", it->node_id); it = std::vector::reverse_iterator( neighbors.erase(std::next(it).base())); // Erase the element and update the iterator diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index cb047a4dc..41f008fb0 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -6,6 +6,7 @@ #include "Router.h" #include "configuration.h" #include "main.h" +#include NodeInfoModule *nodeInfoModule; @@ -67,13 +68,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() LOG_DEBUG("Skip sending NodeInfo due to > 40 percent channel util.\n"); return NULL; } - uint32_t now = millis(); // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. - if (!shorterTimeout && lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) { + if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) { LOG_DEBUG("Skip sending NodeInfo since we just sent it less than 5 minutes ago.\n"); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; - } else if (shorterTimeout && lastSentToMesh && (now - lastSentToMesh) < (60 * 1000)) { + } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { LOG_DEBUG("Skip sending actively requested NodeInfo since we just sent it less than 60 seconds ago.\n"); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; @@ -82,7 +82,7 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() meshtastic_User &u = owner; LOG_INFO("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); - lastSentToMesh = now; + lastSentToMesh = millis(); return allocDataProtobuf(u); } } diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index cb6a58b2e..4ba09385d 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -145,7 +145,7 @@ void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUp bool PositionModule::hasQualityTimesource() { bool setFromPhoneOrNtpToday = - lastSetFromPhoneNtpOrGps == 0 ? false : (millis() - lastSetFromPhoneNtpOrGps) <= (SEC_PER_DAY * 1000UL); + lastSetFromPhoneNtpOrGps == 0 ? false : Throttle::isWithinTimespanMs(lastSetFromPhoneNtpOrGps, SEC_PER_DAY * 1000UL); #if MESHTASTIC_EXCLUDE_GPS bool hasGpsOrRtc = (rtc_found.address != ScanI2C::ADDRESS_NONE.address); #else diff --git a/src/modules/PowerStressModule.cpp b/src/modules/PowerStressModule.cpp index 4c9f0df88..48159ba54 100644 --- a/src/modules/PowerStressModule.cpp +++ b/src/modules/PowerStressModule.cpp @@ -9,6 +9,7 @@ #include "main.h" #include "sleep.h" #include "target_specific.h" +#include extern void printInfo(); @@ -114,7 +115,7 @@ int32_t PowerStressModule::runOnce() break; case meshtastic_PowerStressMessage_Opcode_CPU_FULLON: { uint32_t start_msec = millis(); - while ((millis() - start_msec) < (uint32_t)sleep_msec) + while (Throttle::isWithinTimespanMs(start_msec, sleep_msec)) ; // Don't let CPU idle at all sleep_msec = 0; // we already slept break; diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 8154a661e..b02494ef3 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -19,6 +19,7 @@ #include "configuration.h" #include "gps/GeoCoord.h" #include +#include RangeTestModule *rangeTestModule; RangeTestModuleRadio *rangeTestModuleRadio; @@ -79,7 +80,7 @@ int32_t RangeTestModule::runOnce() } // If we have been running for more than 8 hours, turn module back off - if (millis() - started > 28800000) { + if (!Throttle::isWithinTimespanMs(started, 28800000)) { LOG_INFO("Range Test Module - Disabling after 8 hours\n"); return disable(); } else { diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index 0242b59bc..f6b8b2e90 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -5,6 +5,7 @@ #include "Router.h" #include "configuration.h" #include "main.h" +#include #define NUM_GPIOS 64 @@ -118,11 +119,10 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r int32_t RemoteHardwareModule::runOnce() { if (moduleConfig.remote_hardware.enabled && watchGpios) { - uint32_t now = millis(); - if (now - lastWatchMsec >= WATCH_INTERVAL_MSEC) { + if (!Throttle::isWithinTimespanMs(lastWatchMsec, WATCH_INTERVAL_MSEC)) { uint64_t curVal = digitalReads(watchGpios); - lastWatchMsec = now; + lastWatchMsec = millis(); if (curVal != previousWatch) { previousWatch = curVal; diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f0ba64f65..a4dbb072f 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -7,6 +7,7 @@ #include "Router.h" #include "configuration.h" #include +#include /* SerialModule @@ -97,8 +98,7 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio") */ bool SerialModule::checkIsConnected() { - uint32_t now = millis(); - return (now - lastContactMsec) < SERIAL_CONNECTION_TIMEOUT; + return Throttle::isWithinTimespanMs(lastContactMsec, SERIAL_CONNECTION_TIMEOUT); } int32_t SerialModule::runOnce() @@ -182,13 +182,13 @@ int32_t SerialModule::runOnce() return runOncePart(); } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) && HAS_GPS) { // in NMEA mode send out GGA every 2 seconds, Don't read from Port - if (millis() - lastNmeaTime > 2000) { + if (!Throttle::isWithinTimespanMs(lastNmeaTime, 2000)) { lastNmeaTime = millis(); printGGA(outbuf, sizeof(outbuf), localPosition); serialPrint->printf("%s", outbuf); } } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && HAS_GPS) { - if (millis() - lastNmeaTime > 10000) { + if (!Throttle::isWithinTimespanMs(lastNmeaTime, 10000)) { lastNmeaTime = millis(); uint32_t readIndex = 0; const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex); @@ -500,7 +500,7 @@ void SerialModule::processWXSerial() LOG_INFO("WS85 : %i %.1fg%.1f %.1fv %.1fv\n", atoi(windDir), strtof(windVel, nullptr), strtof(windGust, nullptr), batVoltageF, capVoltageF); } - if (gotwind && millis() - lastAveraged > averageIntervalMillis) { + if (gotwind && !Throttle::isWithinTimespanMs(lastAveraged, averageIntervalMillis)) { // calulate averages and send to the mesh float velAvg = 1.0 * velSum / velCount; diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 56d308cfa..0b6be1b7e 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -12,6 +12,7 @@ #include "Router.h" #include "detect/ScanI2CTwoWire.h" #include "main.h" +#include int32_t AirQualityTelemetryModule::runOnce() { @@ -60,15 +61,14 @@ int32_t AirQualityTelemetryModule::runOnce() if (!moduleConfig.telemetry.air_quality_enabled) return disable(); - uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs, - numOnlineNodes))) && + !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = now; + lastSentToMesh = millis(); } else if (service->isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 31cb2f838..f94f7956b 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -64,6 +64,7 @@ T1000xSensor t1000xSensor; #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true #include "graphics/ScreenFonts.h" +#include int32_t EnvironmentTelemetryModule::runOnce() { @@ -155,21 +156,20 @@ int32_t EnvironmentTelemetryModule::runOnce() result = bme680Sensor.runTrigger(); } - uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= - Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = now; - } else if (((lastSentToPhone == 0) || ((now - lastSentToPhone) >= sendToPhoneIntervalMs)) && + lastSentToMesh = millis(); + } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); - lastSentToPhone = now; + lastSentToPhone = millis(); } } return min(sendToPhoneIntervalMs, result); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 318acf456..a493042a0 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -19,6 +19,7 @@ #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true #include "graphics/ScreenFonts.h" +#include int32_t PowerTelemetryModule::runOnce() { @@ -69,20 +70,19 @@ int32_t PowerTelemetryModule::runOnce() if (!moduleConfig.telemetry.power_measurement_enabled) return disable(); - uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.power_update_interval, - default_telemetry_broadcast_interval_secs, - numOnlineNodes))) && + !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.power_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = now; - } else if (((lastSentToPhone == 0) || ((now - lastSentToPhone) >= sendToPhoneIntervalMs)) && + lastSentToMesh = millis(); + } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); - lastSentToPhone = now; + lastSentToPhone = millis(); } } return min(sendToPhoneIntervalMs, result); diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp index d7dcbd09f..59f310a24 100644 --- a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp @@ -7,6 +7,7 @@ #include "NAU7802Sensor.h" #include "SafeFile.h" #include "TelemetrySensor.h" +#include #include #include @@ -40,7 +41,7 @@ bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement) uint32_t start = millis(); while (!nau7802.available()) { delay(100); - if (millis() - start > 1000) { + if (!Throttle::isWithinTimespanMs(start, 1000)) { nau7802.powerDown(); return false; } diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index db09a0bfd..51ec2a942 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -17,6 +17,7 @@ #include "NodeDB.h" #include "RTC.h" #include "Router.h" +#include "Throttle.h" #include "airtime.h" #include "configuration.h" #include "memGet.h" @@ -29,6 +30,9 @@ StoreForwardModule *storeForwardModule; +uint32_t lastHeartbeat = 0; +uint32_t heartbeatInterval = 60; // Default to 60 seconds, adjust as needed + int32_t StoreForwardModule::runOnce() { #ifdef ARCH_ESP32 @@ -42,7 +46,7 @@ int32_t StoreForwardModule::runOnce() this->busy = false; } } - } else if (this->heartbeat && (millis() - lastHeartbeat > (heartbeatInterval * 1000)) && + } else if (this->heartbeat && (!Throttle::isWithinTimespanMs(lastHeartbeat, heartbeatInterval * 1000)) && airTime->isTxAllowedChannelUtil(true)) { lastHeartbeat = millis(); LOG_INFO("*** Sending heartbeat\n"); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 6840700e5..56af9f663 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -21,6 +21,7 @@ #include "Default.h" #include "serialization/JSON.h" #include "serialization/MeshPacketSerializer.h" +#include #include const int reconnectMax = 5; @@ -610,7 +611,7 @@ void MQTT::perhapsReportToMap() if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) return; - if (millis() - last_report_to_map < map_publish_interval_msecs) { + if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs)) { return; } else { if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { diff --git a/src/sleep.cpp b/src/sleep.cpp index e6814f027..f32d24caa 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -5,6 +5,7 @@ #endif #include "ButtonThread.h" +#include "Default.h" #include "Led.h" #include "MeshRadio.h" #include "MeshService.h" @@ -28,6 +29,7 @@ esp_sleep_source_t wakeCause; // the reason we booted this time #endif +#include "Throttle.h" #ifndef INCLUDE_vTaskSuspend #define INCLUDE_vTaskSuspend 0 @@ -168,7 +170,8 @@ static void waitEnterSleep(bool skipPreflight = false) while (!doPreflightSleep()) { delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) - if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep + if (!Throttle::isWithinTimespanMs(now, + THIRTY_SECONDS_MS)) { // If we wait too long just report an error and go to sleep RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_SLEEP_ENTER_WAIT); assert(0); // FIXME - for now we just restart, need to fix bug #167 break; From be01c18c74d6a4bd8e3093ba4d81ded44bf94aca Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Thu, 19 Sep 2024 21:59:42 +0200 Subject: [PATCH 1192/1377] DetectionSensor: more flexible triggering --- src/mesh/NodeDB.cpp | 2 +- .../generated/meshtastic/module_config.pb.h | 45 +++++++++--- src/modules/DetectionSensorModule.cpp | 72 +++++++++++++++---- src/modules/DetectionSensorModule.h | 3 +- 4 files changed, 96 insertions(+), 26 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dca639070..c51ab9fb9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -472,7 +472,7 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_detection_sensor = true; moduleConfig.detection_sensor.enabled = false; - moduleConfig.detection_sensor.detection_triggered_high = true; + moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; moduleConfig.detection_sensor.minimum_broadcast_secs = 45; moduleConfig.has_ambient_lighting = true; diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 2e1985660..d4b82c93b 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -19,6 +19,23 @@ typedef enum _meshtastic_RemoteHardwarePinType { meshtastic_RemoteHardwarePinType_DIGITAL_WRITE = 2 } meshtastic_RemoteHardwarePinType; +typedef enum _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType { + /* Event is triggered if pin is low */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_LOW = 0, + /* Event is triggered if pin is high */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH = 1, + /* Event is triggered when pin goes high to low */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_FALLING_EDGE = 2, + /* Event is triggered when pin goes low to high */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_RISING_EDGE = 3, + /* Event is triggered on every pin state change, low is considered to be + "active" */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_LOW = 4, + /* Event is triggered on every pin state change, high is considered to be + "active" */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH = 5 +} meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType; + /* Baudrate for codec2 voice */ typedef enum _meshtastic_ModuleConfig_AudioConfig_Audio_Baud { meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_DEFAULT = 0, @@ -144,11 +161,13 @@ typedef struct _meshtastic_ModuleConfig_NeighborInfoConfig { typedef struct _meshtastic_ModuleConfig_DetectionSensorConfig { /* Whether the Module is enabled */ bool enabled; - /* Interval in seconds of how often we can send a message to the mesh when a state change is detected */ + /* Interval in seconds of how often we can send a message to the mesh when a + trigger event is detected */ uint32_t minimum_broadcast_secs; - /* Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes - When set to 0, only state changes will be broadcasted - Works as a sort of status heartbeat for peace of mind */ + /* Interval in seconds of how often we should send a message to the mesh + with the current state regardless of trigger events When set to 0, only + trigger events will be broadcasted Works as a sort of status heartbeat + for peace of mind */ uint32_t state_broadcast_secs; /* Send ASCII bell with alert message Useful for triggering ext. notification on bell */ @@ -159,9 +178,8 @@ typedef struct _meshtastic_ModuleConfig_DetectionSensorConfig { char name[20]; /* GPIO pin to monitor for state changes */ uint8_t monitor_pin; - /* Whether or not the GPIO pin state detection is triggered on HIGH (1) - Otherwise LOW (0) */ - bool detection_triggered_high; + /* The type of trigger event to be used */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType detection_trigger_type; /* Whether or not use INPUT_PULLUP mode for GPIO pin Only applicable if the board uses pull-up resistors on the pin */ bool use_pullup; @@ -427,6 +445,10 @@ extern "C" { #define _meshtastic_RemoteHardwarePinType_MAX meshtastic_RemoteHardwarePinType_DIGITAL_WRITE #define _meshtastic_RemoteHardwarePinType_ARRAYSIZE ((meshtastic_RemoteHardwarePinType)(meshtastic_RemoteHardwarePinType_DIGITAL_WRITE+1)) +#define _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_LOW +#define _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MAX meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH +#define _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_ARRAYSIZE ((meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType)(meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH+1)) + #define _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_DEFAULT #define _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MAX meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700B #define _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_AudioConfig_Audio_Baud)(meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700B+1)) @@ -448,6 +470,7 @@ extern "C" { +#define meshtastic_ModuleConfig_DetectionSensorConfig_detection_trigger_type_ENUMTYPE meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType #define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud @@ -473,7 +496,7 @@ extern "C" { #define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0} -#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0} +#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0} #define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} @@ -489,7 +512,7 @@ extern "C" { #define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0} -#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0} +#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0} #define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} @@ -523,7 +546,7 @@ extern "C" { #define meshtastic_ModuleConfig_DetectionSensorConfig_send_bell_tag 4 #define meshtastic_ModuleConfig_DetectionSensorConfig_name_tag 5 #define meshtastic_ModuleConfig_DetectionSensorConfig_monitor_pin_tag 6 -#define meshtastic_ModuleConfig_DetectionSensorConfig_detection_triggered_high_tag 7 +#define meshtastic_ModuleConfig_DetectionSensorConfig_detection_trigger_type_tag 7 #define meshtastic_ModuleConfig_DetectionSensorConfig_use_pullup_tag 8 #define meshtastic_ModuleConfig_AudioConfig_codec2_enabled_tag 1 #define meshtastic_ModuleConfig_AudioConfig_ptt_pin_tag 2 @@ -688,7 +711,7 @@ X(a, STATIC, SINGULAR, UINT32, state_broadcast_secs, 3) \ X(a, STATIC, SINGULAR, BOOL, send_bell, 4) \ X(a, STATIC, SINGULAR, STRING, name, 5) \ X(a, STATIC, SINGULAR, UINT32, monitor_pin, 6) \ -X(a, STATIC, SINGULAR, BOOL, detection_triggered_high, 7) \ +X(a, STATIC, SINGULAR, UENUM, detection_trigger_type, 7) \ X(a, STATIC, SINGULAR, BOOL, use_pullup, 8) #define meshtastic_ModuleConfig_DetectionSensorConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DetectionSensorConfig_DEFAULT NULL diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index 20d91a381..20aa6d8e9 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -10,6 +10,41 @@ DetectionSensorModule *detectionSensorModule; #define GPIO_POLLING_INTERVAL 100 #define DELAYED_INTERVAL 1000 +typedef enum { + DetectionSensorVerdictDetected, + DetectionSensorVerdictSendState, + DetectionSensorVerdictNoop, +} DetectionSensorTriggerVerdict; + +typedef DetectionSensorTriggerVerdict (*DetectionSensorTriggerHandler)(bool prev, bool current); + +static DetectionSensorTriggerVerdict detection_trigger_logic_level(bool prev, bool current) +{ + return current ? DetectionSensorVerdictDetected : DetectionSensorVerdictNoop; +} + +static DetectionSensorTriggerVerdict detection_trigger_single_edge(bool prev, bool current) +{ + return (!prev && current) ? DetectionSensorVerdictDetected : DetectionSensorVerdictNoop; +} + +static DetectionSensorTriggerVerdict detection_trigger_either_edge(bool prev, bool current) +{ + if (prev == current) { + return DetectionSensorVerdictNoop; + } + return current ? DetectionSensorVerdictDetected : DetectionSensorVerdictSendState; +} + +const static DetectionSensorTriggerHandler handlers[_meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MAX + 1] = { + [meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_LOW] = detection_trigger_logic_level, + [meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH] = detection_trigger_logic_level, + [meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_FALLING_EDGE] = detection_trigger_single_edge, + [meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_RISING_EDGE] = detection_trigger_single_edge, + [meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_LOW] = detection_trigger_either_edge, + [meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH] = detection_trigger_either_edge, +}; + int32_t DetectionSensorModule::runOnce() { /* @@ -21,8 +56,8 @@ int32_t DetectionSensorModule::runOnce() // moduleConfig.detection_sensor.monitor_pin = 21; // WisBlock RAK12013 Radar IO6 // moduleConfig.detection_sensor.minimum_broadcast_secs = 30; // moduleConfig.detection_sensor.state_broadcast_secs = 120; - // moduleConfig.detection_sensor.detection_triggered_high = true; - // strcpy(moduleConfig.detection_sensor.name, "Motion"); + // moduleConfig.detection_sensor.detection_trigger_type = + // meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; strcpy(moduleConfig.detection_sensor.name, "Motion"); if (moduleConfig.detection_sensor.enabled == false) return disable(); @@ -49,18 +84,29 @@ int32_t DetectionSensorModule::runOnce() // LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin)); - if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && - hasDetectionEvent()) { - sendDetectionMessage(); - return DELAYED_INTERVAL; + if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs)) { + bool isDetected = hasDetectionEvent(); + DetectionSensorTriggerVerdict verdict = + handlers[moduleConfig.detection_sensor.detection_trigger_type](wasDetected, isDetected); + wasDetected = isDetected; + switch (verdict) { + case DetectionSensorVerdictDetected: + sendDetectionMessage(); + return DELAYED_INTERVAL; + case DetectionSensorVerdictSendState: + sendCurrentStateMessage(isDetected); + return DELAYED_INTERVAL; + case DetectionSensorVerdictNoop: + break; + } } // Even if we haven't detected an event, broadcast our current state to the mesh on the scheduled interval as a sort // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. - else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && - (millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, - default_telemetry_broadcast_interval_secs)) { - sendCurrentStateMessage(); + if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && + (millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, + default_telemetry_broadcast_interval_secs)) { + sendCurrentStateMessage(hasDetectionEvent()); return DELAYED_INTERVAL; } return GPIO_POLLING_INTERVAL; @@ -86,10 +132,10 @@ void DetectionSensorModule::sendDetectionMessage() delete[] message; } -void DetectionSensorModule::sendCurrentStateMessage() +void DetectionSensorModule::sendCurrentStateMessage(bool state) { char *message = new char[40]; - sprintf(message, "%s state: %i", moduleConfig.detection_sensor.name, hasDetectionEvent()); + sprintf(message, "%s state: %i", moduleConfig.detection_sensor.name, state); meshtastic_MeshPacket *p = allocDataPacket(); p->want_ack = false; @@ -105,5 +151,5 @@ bool DetectionSensorModule::hasDetectionEvent() { bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin); // LOG_DEBUG("Detection Sensor Module: Current state: %i\n", currentState); - return moduleConfig.detection_sensor.detection_triggered_high ? currentState : !currentState; + return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState; } \ No newline at end of file diff --git a/src/modules/DetectionSensorModule.h b/src/modules/DetectionSensorModule.h index ed6cddda5..b960c8744 100644 --- a/src/modules/DetectionSensorModule.h +++ b/src/modules/DetectionSensorModule.h @@ -15,8 +15,9 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh private: bool firstTime = true; uint32_t lastSentToMesh = 0; + bool wasDetected = false; void sendDetectionMessage(); - void sendCurrentStateMessage(); + void sendCurrentStateMessage(bool state); bool hasDetectionEvent(); }; From fa1cc5984170013d924f454454a1c1eeff3de918 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 23 Sep 2024 09:20:32 -0500 Subject: [PATCH 1193/1377] Rename message length headers and set payload max to 255 (#4827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename message length headers and set payload max to 255 * Add MESHTASTIC_PKC_OVERHEAD * compare to MESHTASTIC_HEADER_LENGTH --------- Co-authored-by: Thomas Göttgens --- src/mesh/RadioInterface.cpp | 4 ++-- src/mesh/RadioInterface.h | 7 ++++--- src/mesh/Router.cpp | 14 +++++++------- src/modules/RangeTestModule.cpp | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 7b6b4f5fa..6fca67188 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -151,7 +151,7 @@ const RegionInfo regions[] = { const RegionInfo *myRegion; bool RadioInterface::uses_default_frequency_slot = true; -static uint8_t bytes[MAX_RHPACKETLEN]; +static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; void initRegion() { @@ -326,7 +326,7 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p) RadioInterface::RadioInterface() { - assert(sizeof(PacketHeader) == 16); // make sure the compiler did what we expected + assert(sizeof(PacketHeader) == MESHTASTIC_HEADER_LENGTH); // make sure the compiler did what we expected } bool RadioInterface::reconfigure() diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index d0d20926c..129861441 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -9,8 +9,9 @@ #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission -#define MAX_RHPACKETLEN 256 -#define LORA_HEADER_LENGTH 16 +#define MAX_LORA_PAYLOAD_LEN 255 // max length of 255 per Semtech's datasheets on SX12xx +#define MESHTASTIC_HEADER_LENGTH 16 +#define MESHTASTIC_PKC_OVERHEAD 12 #define PACKET_FLAGS_HOP_LIMIT_MASK 0x07 #define PACKET_FLAGS_WANT_ACK_MASK 0x08 @@ -90,7 +91,7 @@ class RadioInterface /** * A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need * */ - uint8_t radiobuf[MAX_RHPACKETLEN]; + uint8_t radiobuf[MAX_LORA_PAYLOAD_LEN + 1]; /** * Enqueue a received packet for the registered receiver diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a6b946761..a993ee7c0 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -36,8 +36,8 @@ static MemoryDynamic staticPool; Allocator &packetPool = staticPool; -static uint8_t bytes[MAX_RHPACKETLEN]; -static uint8_t ScratchEncrypted[MAX_RHPACKETLEN]; +static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; +static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1]; /** * Constructor @@ -330,13 +330,13 @@ bool perhapsDecode(meshtastic_MeshPacket *p) // Attempt PKI decryption first if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && p->to != NODENUM_BROADCAST && nodeDB->getMeshNode(p->from) != nullptr && nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && - nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > 12) { + nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > MESHTASTIC_PKC_OVERHEAD) { LOG_DEBUG("Attempting PKI decryption\n"); if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) { LOG_INFO("PKI Decryption worked!\n"); memset(&p->decoded, 0, sizeof(p->decoded)); - rawSize -= 12; + rawSize -= MESHTASTIC_PKC_OVERHEAD; if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded) && p->decoded.portnum != meshtastic_PortNum_UNKNOWN_APP) { decrypted = true; @@ -475,7 +475,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) } } */ - if (numbytes + LORA_HEADER_LENGTH > MAX_RHPACKETLEN) + if (numbytes + MESHTASTIC_HEADER_LENGTH > MAX_LORA_PAYLOAD_LEN) return meshtastic_Routing_Error_TOO_LARGE; // printBytes("plaintext", bytes, numbytes); @@ -499,7 +499,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Using PKI!\n"); - if (numbytes + LORA_HEADER_LENGTH + 12 > MAX_RHPACKETLEN) + if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN) return meshtastic_Routing_Error_TOO_LARGE; if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { @@ -508,7 +508,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) return meshtastic_Routing_Error_PKI_FAILED; } crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); - numbytes += 12; + numbytes += MESHTASTIC_PKC_OVERHEAD; memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes); p->channel = 0; p->pki_encrypted = true; diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index b02494ef3..c98f15d40 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -115,7 +115,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) packetSequence++; - static char heartbeatString[MAX_RHPACKETLEN]; + static char heartbeatString[MAX_LORA_PAYLOAD_LEN + 1]; snprintf(heartbeatString, sizeof(heartbeatString), "seq %u", packetSequence); p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply From 9a7a4d38148433dc11bd07d8486005673ff620bc Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 23 Sep 2024 13:56:26 -0500 Subject: [PATCH 1194/1377] Check for null before printing debug (#4835) --- src/mesh/NodeDB.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dca639070..2820cafd4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -657,9 +657,10 @@ void NodeDB::pickNewNodeNum() while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) || (nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) { NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice - LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so " - "trying for 0x%x\n", - nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate); + if (found) + LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so " + "trying for 0x%x\n", + nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate); nodeNum = candidate; } LOG_DEBUG("Using nodenum 0x%x \n", nodeNum); From 9cbabb0468133474ad19a0c7be637bd8bd974289 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 23 Sep 2024 15:51:05 -0500 Subject: [PATCH 1195/1377] Teardown bluetooth phoneAPI better and fix client notification issue (#4834) * Teardown bluetooth phoneAPI better and fix client notification issue * Fix client notification draining --- src/mesh/MeshService.h | 3 +++ src/mesh/PhoneAPI.cpp | 39 +++++++++++++++++++-------- src/mesh/PhoneAPI.h | 7 ++--- src/nimble/NimbleBluetooth.cpp | 9 ++++++- src/platform/nrf52/NRF52Bluetooth.cpp | 5 ++-- 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index ea1c4e345..e78c2be2c 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -92,6 +92,9 @@ class MeshService /// Return the next MqttClientProxyMessage packet destined to the phone. meshtastic_MqttClientProxyMessage *getMqttClientProxyMessageForPhone() { return toPhoneMqttProxyQueue.dequeuePtr(0); } + /// Return the next ClientNotification packet destined to the phone. + meshtastic_ClientNotification *getClientNotificationForPhone() { return toPhoneClientNotificationQueue.dequeuePtr(0); } + // search the queue for a request id and return the matching nodenum NodeNum getNodenumFromRequestId(uint32_t request_id); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 2ed7a69db..103572990 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -62,9 +62,11 @@ void PhoneAPI::handleStartConfig() void PhoneAPI::close() { + LOG_INFO("PhoneAPI::close()\n"); + if (state != STATE_SEND_NOTHING) { state = STATE_SEND_NOTHING; - + resetReadIndex(); unobserve(&service->fromNumChanged); #ifdef FSCom unobserve(&xModem.packetReady); @@ -72,8 +74,17 @@ void PhoneAPI::close() releasePhonePacket(); // Don't leak phone packets on shutdown releaseQueueStatusPhonePacket(); releaseMqttClientProxyPhonePacket(); - + releaseClientNotification(); onConnectionChanged(false); + fromRadioScratch = {}; + toRadioScratch = {}; + nodeInfoForPhone = {}; + packetForPhone = NULL; + filesManifest.clear(); + fromRadioNum = 0; + config_nonce = 0; + config_state = 0; + pauseBluetoothLogging = false; } } @@ -405,6 +416,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.which_payload_variant = meshtastic_FromRadio_xmodemPacket_tag; fromRadioScratch.xmodemPacket = xmodemPacketForPhone; xmodemPacketForPhone = meshtastic_XModem_init_zero; + } else if (clientNotification) { + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_clientNotification_tag; + fromRadioScratch.clientNotification = *clientNotification; + releaseClientNotification(); } else if (packetForPhone) { printPacket("phone downloaded packet", packetForPhone); @@ -444,13 +459,6 @@ void PhoneAPI::sendConfigComplete() pauseBluetoothLogging = false; } -void PhoneAPI::handleDisconnect() -{ - filesManifest.clear(); - pauseBluetoothLogging = false; - LOG_INFO("PhoneAPI disconnect\n"); -} - void PhoneAPI::releasePhonePacket() { if (packetForPhone) { @@ -475,6 +483,14 @@ void PhoneAPI::releaseMqttClientProxyPhonePacket() } } +void PhoneAPI::releaseClientNotification() +{ + if (clientNotification) { + service->releaseClientNotificationToPool(clientNotification); + clientNotification = NULL; + } +} + /** * Return true if we have data available to send to the phone */ @@ -509,7 +525,9 @@ bool PhoneAPI::available() queueStatusPacketForPhone = service->getQueueStatusForPhone(); if (!mqttClientProxyMessageForPhone) mqttClientProxyMessageForPhone = service->getMqttClientProxyMessageForPhone(); - bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone; + if (!clientNotification) + clientNotification = service->getClientNotificationForPhone(); + bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone || !!clientNotification; if (hasPacket) return true; @@ -552,7 +570,6 @@ void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t reply cn->time = getValidTime(RTCQualityFromNet); strncpy(cn->message, message, sizeof(cn->message)); service->sendClientNotification(cn); - delete cn; } /** diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 1e09b9535..cf6f55416 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -147,11 +147,6 @@ class PhoneAPI */ virtual void onNowHasData(uint32_t fromRadioNum) {} - /** - * Subclasses can use this to find out when a client drops the link - */ - virtual void handleDisconnect(); - private: void releasePhonePacket(); @@ -159,6 +154,8 @@ class PhoneAPI void releaseMqttClientProxyPhonePacket(); + void releaseClientNotification(); + /// begin a new connection void handleStartConfig(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index d959553a4..03fa80415 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -124,7 +124,14 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks } } - virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) { LOG_INFO("BLE disconnect\n"); } + virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) + { + LOG_INFO("BLE disconnect\n"); + + if (bluetoothPhoneAPI) { + bluetoothPhoneAPI->close(); + } + } }; static NimbleBluetoothToRadioCallback *toRadioCallbacks; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 1405ea4f3..ec3ff3e8d 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -55,7 +55,6 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; void onConnect(uint16_t conn_handle) { - // Get the reference to current connection BLEConnection *connection = Bluefruit.Connection(conn_handle); connectionHandle = conn_handle; @@ -70,8 +69,10 @@ void onConnect(uint16_t conn_handle) */ void onDisconnect(uint16_t conn_handle, uint8_t reason) { - // FIXME - we currently assume only one active connection LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); + if (bluetoothPhoneAPI) { + bluetoothPhoneAPI->close(); + } } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { From c442cd7267820681a2259d5b5b4531370af2377c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 23 Sep 2024 15:53:42 -0500 Subject: [PATCH 1196/1377] Remove some straggler now --- src/SerialConsole.cpp | 3 +-- src/modules/NeighborInfoModule.cpp | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 2c1133771..15366d222 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -74,7 +74,6 @@ void SerialConsole::flush() // For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages bool SerialConsole::checkIsConnected() { - uint32_t now = millis(); return Throttle::isWithinTimespanMs(lastContactMsec, SERIAL_CONNECTION_TIMEOUT); } @@ -122,4 +121,4 @@ void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_l emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg); } else RedirectablePrint::log_to_serial(logLevel, format, arg); -} +} \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index a3a3b9bb4..55ed46b5e 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -84,7 +84,6 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb */ void NeighborInfoModule::cleanUpNeighbors() { - uint32_t now = getTime(); NodeNum my_node_id = nodeDB->getNodeNum(); for (auto it = neighbors.rbegin(); it != neighbors.rend();) { // We will remove a neighbor if we haven't heard from them in twice the broadcast interval From e78c7069996454157c53d1c8b3428a8d7a979a27 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 23 Sep 2024 18:40:54 -0500 Subject: [PATCH 1197/1377] Fix RAK4631 accelerometer (#4837) --- src/detect/ScanI2CTwoWire.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 98f40be76..c0e70503b 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -334,11 +334,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) // Check register 0x0F for 0x3300 response to ID LIS3DH chip. registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2); - if (registerValue == 0x3300) { + if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333 type = LIS3DH; LOG_INFO("LIS3DH accelerometer found\n"); } - break; } case SHT31_4x_ADDR: @@ -435,4 +434,4 @@ size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); } -#endif +#endif \ No newline at end of file From 0ad1f776aec9a0c6bf9165fe15c054aa4dee5d32 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 23 Sep 2024 18:53:01 -0500 Subject: [PATCH 1198/1377] Manually regen protos for now --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/protobufs b/protobufs index 9b8490784..9651aa59e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 9b84907847b67047b72f9792f4b47532b308bbe4 +Subproject commit 9651aa59eaa3b54d801b9c251efcfe842790db32 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index aa83ff27a..9ba69c612 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -109,7 +109,7 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_NRF52840_PCA10059 = 40, /* Custom Disaster Radio esp32 v3 device https://github.com/sudomesh/disaster-radio/tree/master/hardware/board_esp32_v3 */ meshtastic_HardwareModel_DR_DEV = 41, - /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */ + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK = 42, /* New Heltec LoRA32 with ESP32-S3 CPU */ meshtastic_HardwareModel_HELTEC_V3 = 43, @@ -198,11 +198,13 @@ typedef enum _meshtastic_HardwareModel { https://www.adafruit.com/product/938 ^^^ short A0 to switch to I2C address 0x3C */ meshtastic_HardwareModel_RP2040_FEATHER_RFM95 = 76, - /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */ + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK_COREBASIC = 77, meshtastic_HardwareModel_M5STACK_CORE2 = 78, /* Pico2 with Waveshare Hat, same as Pico */ meshtastic_HardwareModel_RPI_PICO2 = 79, + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ + meshtastic_HardwareModel_M5STACK_CORES3 = 80, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 428a567078bec2d45aac8fbffad1ccfb26ab841e Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 24 Sep 2024 08:16:44 +0800 Subject: [PATCH 1199/1377] Wire 1 is PIN_WIRE1_SDA (#4840) Based on #4745, PIN_WIRE1_SDA is the 'second' wire interface. This pach amends the check to determine whether a device has two wire interfaces should use PIN_WIRE1_SDA, rather than PIN_WIRE_SDA. --- src/configuration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configuration.h b/src/configuration.h index 7416e0a3e..0b470eef3 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -213,7 +213,7 @@ along with this program. If not, see . #ifndef WIRE_INTERFACES_COUNT // Officially an NRF52 macro // Repurposed cross-platform to identify devices using Wire1 -#if defined(I2C_SDA1) || defined(PIN_WIRE_SDA) +#if defined(I2C_SDA1) || defined(PIN_WIRE1_SDA) #define WIRE_INTERFACES_COUNT 2 #elif HAS_WIRE #define WIRE_INTERFACES_COUNT 1 From c39d270f4094316b51e41f8b50a3f11fc0cf8587 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 23 Sep 2024 23:41:28 -0500 Subject: [PATCH 1200/1377] Build message in printBytes, to not spam BLE log (#4843) --- src/meshUtils.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index c6f2c69b4..4819f6ed7 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -60,10 +60,17 @@ char *strnstr(const char *s, const char *find, size_t slen) void printBytes(const char *label, const uint8_t *p, size_t numbytes) { - LOG_DEBUG("%s: ", label); - for (size_t i = 0; i < numbytes; i++) - LOG_DEBUG("%02x ", p[i]); - LOG_DEBUG("\n"); + char *messageBuffer; + int labelSize = strlen(label); + if (labelSize < 100 && numbytes < 64) { + messageBuffer = new char[labelSize + (numbytes * 3) + 2]; + strncpy(messageBuffer, label, labelSize); + for (size_t i = 0; i < numbytes; i++) + snprintf(messageBuffer + labelSize + i * 3, 4, " %02x", p[i]); + strcpy(messageBuffer + labelSize + numbytes * 3, "\n"); + LOG_DEBUG(messageBuffer); + delete messageBuffer; + } } bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes) From b4c09ace23744e47afd0ea8e1f9be69c5c44446b Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:47:31 -0700 Subject: [PATCH 1201/1377] Consolidate variant build steps (#4820) * poc: consolidate variant build steps * use build-variant action * only checkout once and clean up after run * checkout before local action --- .github/actions/build-variant/action.yml | 95 ++++++++++++++++++++++++ .github/workflows/build_esp32.yml | 58 ++++----------- .github/workflows/build_esp32_c3.yml | 57 ++++---------- .github/workflows/build_esp32_s3.yml | 58 ++++----------- .github/workflows/build_nrf52.yml | 20 ++--- .github/workflows/build_rpi2040.yml | 20 ++--- .github/workflows/build_stm32.yml | 22 ++---- 7 files changed, 157 insertions(+), 173 deletions(-) create mode 100644 .github/actions/build-variant/action.yml diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml new file mode 100644 index 000000000..f60620f24 --- /dev/null +++ b/.github/actions/build-variant/action.yml @@ -0,0 +1,95 @@ +name: Setup Build Variant Composite Action +description: Variant build actions for Meshtastic Platform IO steps + +inputs: + board: + description: The board to build for + required: true + github_token: + description: GitHub token + required: true + build-script-path: + description: Path to the build script + required: true + remove-debug-flags: + description: A newline separated list of files to remove debug flags from + required: false + default: "" + ota-firmware-source: + description: The OTA firmware file to pull + required: false + default: "" + ota-firmware-target: + description: The target path to store the OTA firmware file + required: false + default: "" + artifact-paths: + description: A newline separated list of paths to store as artifacts + required: false + default: "" + include-web-ui: + description: Include the web UI in the build + required: false + default: "false" + +runs: + using: composite + steps: + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + if: ${{ inputs.include-web-ui == "true" }} + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/web + file: build.tar + target: build.tar + token: ${{ inputs.github_token }} + + - name: Unpack web ui + if: ${{ inputs.include-web-ui == "true" }} + shell: bash + run: | + tar -xf build.tar -C data/static + rm build.tar + + - name: Remove debug flags for release + shell: bash + if: ${{ inputs.remove-debug-flags != "" }} + run: | + for PATH in ${{ inputs.remove-debug-flags }}; do + sed -i '/DDEBUG_HEAP/d' ${PATH} + done + + - name: Build ${{ inputs.board }} + shell: bash + run: ${{ inputs.build-script-path }} ${{ inputs.board }} + + - name: Pull OTA Firmware + if: ${{ inputs.ota-firmware-source != "" && inputs.ota-firmware-target != "" }} + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: meshtastic/firmware-ota + file: ${{ inputs.ota-firmware-source }} + target: ${{ inputs.ota-firmware-target }} + token: ${{ inputs.github_token }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + overwrite: true + path: | + ${{ inputs.artifact-paths }} + + - name: Clean up resources + shell: bash + run: | + rm -rf . diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 4cbb4c7a4..350454e18 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -12,52 +12,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Unpack web ui - run: | - tar -xf build.tar -C data/static - rm build.tar - - - name: Remove debug flags for release - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - name: Build ESP32 - run: bin/build-esp32.sh ${{ inputs.board }} - - - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@master + id: build + uses: ./.github/actions/build-variant with: - repo: meshtastic/firmware-ota - file: firmware.bin - target: release/bleota.bin - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: | + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware.bin + ota-firmware-target: release/bleota.bin + artifact-paths: | release/*.bin release/*.elf + include-web-ui: true diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index 07727d711..abfeb1f5a 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -14,50 +14,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@master + - name: Build ESP32-C3 + id: build + uses: ./.github/actions/build-variant with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Unpack web ui - run: | - tar -xf build.tar -C data/static - rm build.tar - - name: Remove debug flags for release - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - - name: Build ESP32 - run: bin/build-esp32.sh ${{ inputs.board }} - - - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/firmware-ota - file: firmware-c3.bin - target: release/bleota-c3.bin - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: | + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware-c3.bin + ota-firmware-target: release/bleota-c3.bin + artifact-paths: | release/*.bin release/*.elf diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 10773833e..174c0f941 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -12,50 +12,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - name: Pull web ui - uses: dsaltares/fetch-gh-release-asset@master + - name: Build ESP32-S3 + id: build + uses: ./.github/actions/build-variant with: - repo: meshtastic/web - file: build.tar - target: build.tar - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Unpack web ui - run: | - tar -xf build.tar -C data/static - rm build.tar - - name: Remove debug flags for release - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini - sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - - name: Build ESP32 - run: bin/build-esp32.sh ${{ inputs.board }} - - - name: Pull OTA Firmware - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: meshtastic/firmware-ota - file: firmware-s3.bin - target: release/bleota-s3.bin - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release version string - shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 - with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: | + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware-s3.bin + ota-firmware-target: release/bleota-s3.bin + artifact-paths: | release/*.bin release/*.elf + include-web-ui: true diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index ac509a096..606cb8a3e 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -12,23 +12,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - name: Build NRF52 - run: bin/build-nrf52.sh ${{ inputs.board }} - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + id: build + uses: ./.github/actions/build-variant with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + build-script-path: bin/build-nrf52.sh + artifact-paths: | release/*.hex release/*.uf2 release/*.elf diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index 6e258fe2a..b0508877d 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -12,22 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - name: Build Raspberry Pi 2040 - run: ./bin/build-rpi2040.sh ${{ inputs.board }} - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + id: build + uses: ./.github/actions/build-variant with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + build-script-path: bin/build-rpi2040.sh + artifact-paths: | release/*.uf2 release/*.elf diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml index d13c52c8a..6858812e1 100644 --- a/.github/workflows/build_stm32.yml +++ b/.github/workflows/build_stm32.yml @@ -12,22 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build base - id: base - uses: ./.github/actions/setup-base - - name: Build STM32 - run: bin/build-stm32.sh ${{ inputs.board }} - - - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - - name: Store binaries as an artifact - uses: actions/upload-artifact@v4 + - name: Build Raspberry Pi 2040 + id: build + uses: ./.github/actions/build-variant with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip - overwrite: true - path: | + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + build-script-path: bin/build-stm32.sh + artifact-paths: | release/*.hex release/*.bin From 682133501ab9b979906e603f3d9df60a331f4df5 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 24 Sep 2024 14:49:01 +0800 Subject: [PATCH 1202/1377] Syntax fix for github action (#4846) https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions --- .github/actions/build-variant/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml index f60620f24..b698f9e57 100644 --- a/.github/actions/build-variant/action.yml +++ b/.github/actions/build-variant/action.yml @@ -40,7 +40,7 @@ runs: uses: ./.github/actions/setup-base - name: Pull web ui - if: ${{ inputs.include-web-ui == "true" }} + if: inputs.include-web-ui == 'true' uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/web @@ -49,7 +49,7 @@ runs: token: ${{ inputs.github_token }} - name: Unpack web ui - if: ${{ inputs.include-web-ui == "true" }} + if: inputs.include-web-ui == 'true' shell: bash run: | tar -xf build.tar -C data/static @@ -57,7 +57,7 @@ runs: - name: Remove debug flags for release shell: bash - if: ${{ inputs.remove-debug-flags != "" }} + if: inputs.remove-debug-flags != '' run: | for PATH in ${{ inputs.remove-debug-flags }}; do sed -i '/DDEBUG_HEAP/d' ${PATH} @@ -68,7 +68,7 @@ runs: run: ${{ inputs.build-script-path }} ${{ inputs.board }} - name: Pull OTA Firmware - if: ${{ inputs.ota-firmware-source != "" && inputs.ota-firmware-target != "" }} + if: inputs.ota-firmware-source != '' && inputs.ota-firmware-target != '' uses: dsaltares/fetch-gh-release-asset@master with: repo: meshtastic/firmware-ota From c72612d8266dbeebdd747c469ac9301b9068f82f Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:41:40 -0700 Subject: [PATCH 1203/1377] sed doesn't like newlines (#4847) * sed doesn't like newlines * fold remove-debug-flags block * PATH is a system env var * Runners don't like rm -f ${workspace path} --- .github/actions/build-variant/action.yml | 11 +++-------- .github/workflows/build_esp32.yml | 2 +- .github/workflows/build_esp32_c3.yml | 2 +- .github/workflows/build_esp32_s3.yml | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml index b698f9e57..f9410eb03 100644 --- a/.github/actions/build-variant/action.yml +++ b/.github/actions/build-variant/action.yml @@ -12,7 +12,7 @@ inputs: description: Path to the build script required: true remove-debug-flags: - description: A newline separated list of files to remove debug flags from + description: A space separated list of files to remove debug flags from required: false default: "" ota-firmware-source: @@ -59,8 +59,8 @@ runs: shell: bash if: inputs.remove-debug-flags != '' run: | - for PATH in ${{ inputs.remove-debug-flags }}; do - sed -i '/DDEBUG_HEAP/d' ${PATH} + for INI_FILE in ${{ inputs.remove-debug-flags }}; do + sed -i '/DDEBUG_HEAP/d' ${INI_FILE} done - name: Build ${{ inputs.board }} @@ -88,8 +88,3 @@ runs: overwrite: true path: | ${{ inputs.artifact-paths }} - - - name: Clean up resources - shell: bash - run: | - rm -rf . diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 350454e18..5f97a349f 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -19,7 +19,7 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} board: ${{ inputs.board }} - remove-debug-flags: | + remove-debug-flags: > ./arch/esp32/esp32.ini ./arch/esp32/esp32s2.ini ./arch/esp32/esp32s3.ini diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index abfeb1f5a..8f3ded187 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -21,7 +21,7 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} board: ${{ inputs.board }} - remove-debug-flags: | + remove-debug-flags: > ./arch/esp32/esp32.ini ./arch/esp32/esp32s2.ini ./arch/esp32/esp32s3.ini diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 174c0f941..40d05573c 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -19,7 +19,7 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} board: ${{ inputs.board }} - remove-debug-flags: | + remove-debug-flags: > ./arch/esp32/esp32.ini ./arch/esp32/esp32s2.ini ./arch/esp32/esp32s3.ini From 4fde1ca2a879bba65852cbd1d8c2c457d825ef43 Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Tue, 24 Sep 2024 01:27:46 -0700 Subject: [PATCH 1204/1377] chomp trailing newline (#4848) --- .github/workflows/build_esp32.yml | 2 +- .github/workflows/build_esp32_c3.yml | 2 +- .github/workflows/build_esp32_s3.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 5f97a349f..041191d34 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -19,7 +19,7 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} board: ${{ inputs.board }} - remove-debug-flags: > + remove-debug-flags: >- ./arch/esp32/esp32.ini ./arch/esp32/esp32s2.ini ./arch/esp32/esp32s3.ini diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index 8f3ded187..ddc2e2859 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -21,7 +21,7 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} board: ${{ inputs.board }} - remove-debug-flags: > + remove-debug-flags: >- ./arch/esp32/esp32.ini ./arch/esp32/esp32s2.ini ./arch/esp32/esp32s3.ini diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 40d05573c..29857ef17 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -19,7 +19,7 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} board: ${{ inputs.board }} - remove-debug-flags: > + remove-debug-flags: >- ./arch/esp32/esp32.ini ./arch/esp32/esp32s2.ini ./arch/esp32/esp32s3.ini From 139686d6399de7341d1c8d0f9015a7e44575d8b1 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Tue, 24 Sep 2024 11:11:16 +0200 Subject: [PATCH 1205/1377] bump protobufs --- src/mesh/generated/meshtastic/mesh.pb.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index aa83ff27a..9ba69c612 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -109,7 +109,7 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_NRF52840_PCA10059 = 40, /* Custom Disaster Radio esp32 v3 device https://github.com/sudomesh/disaster-radio/tree/master/hardware/board_esp32_v3 */ meshtastic_HardwareModel_DR_DEV = 41, - /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */ + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK = 42, /* New Heltec LoRA32 with ESP32-S3 CPU */ meshtastic_HardwareModel_HELTEC_V3 = 43, @@ -198,11 +198,13 @@ typedef enum _meshtastic_HardwareModel { https://www.adafruit.com/product/938 ^^^ short A0 to switch to I2C address 0x3C */ meshtastic_HardwareModel_RP2040_FEATHER_RFM95 = 76, - /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ */ + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK_COREBASIC = 77, meshtastic_HardwareModel_M5STACK_CORE2 = 78, /* Pico2 with Waveshare Hat, same as Pico */ meshtastic_HardwareModel_RPI_PICO2 = 79, + /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ + meshtastic_HardwareModel_M5STACK_CORES3 = 80, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From ce480ae626563da2bf1904c34ab53b3d9e68c265 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Tue, 24 Sep 2024 11:16:04 +0200 Subject: [PATCH 1206/1377] fix comment style --- src/modules/DetectionSensorModule.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index de782b2ba..285aba582 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -58,7 +58,8 @@ int32_t DetectionSensorModule::runOnce() // moduleConfig.detection_sensor.minimum_broadcast_secs = 30; // moduleConfig.detection_sensor.state_broadcast_secs = 120; // moduleConfig.detection_sensor.detection_trigger_type = - // meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; strcpy(moduleConfig.detection_sensor.name, "Motion"); + // meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; + // strcpy(moduleConfig.detection_sensor.name, "Motion"); if (moduleConfig.detection_sensor.enabled == false) return disable(); From b709d478327e3fb71f9f8a6c9b5c698b70e5a64e Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 24 Sep 2024 18:50:03 +0800 Subject: [PATCH 1207/1377] Fix Ublox M10 Setup (#4842) There is no EXTINT pin available on the Tdeck, which uses the Ublox M10 GPS. Therefore our previous hack to use that pin makes the GPS not work. That workaround was implemented to fix sleep issues which have now since been fixed with the state machine. This patch restores the state prior to the hack, which is known-working. Additionaly, it was discovered that M10s hate it when you try and save to non-extistent eeprom/SPI flash. This patch creates a new SAVE command for the M10 that fixes this issue. Many thanks to @MisterC925 whose report and testing was essential for this fix. fixes https://github.com/meshtastic/firmware/issues/4625 Co-authored-by: Ken McGuire --- src/gps/GPS.cpp | 2 +- src/gps/GPS.h | 1 + src/gps/ubx.h | 57 +++++++++++++++++++++++++++---------------------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 4fa676913..285f3a2fd 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -664,7 +664,7 @@ bool GPS::setup() // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. - msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); + msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE_10), _message_SAVE_10); _serial_gps->write(UBXscratch, msglen); if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { LOG_WARN("Unable to save GNSS module configuration.\n"); diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 48aecc8b9..317d80544 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -134,6 +134,7 @@ class GPS : private concurrency::OSThread static const uint8_t _message_GGA[]; static const uint8_t _message_PMS[]; static const uint8_t _message_SAVE[]; + static const uint8_t _message_SAVE_10[]; // VALSET Commands for M10 static const uint8_t _message_VALSET_PM[]; diff --git a/src/gps/ubx.h b/src/gps/ubx.h index 64e45f160..c73a7efb3 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -323,6 +323,13 @@ const uint8_t GPS::_message_SAVE[] = { 0x17 // deviceMask: BBR, Flash, EEPROM, and SPI Flash }; +const uint8_t GPS::_message_SAVE_10[] = { + 0x00, 0x00, 0x00, 0x00, // clearMask: no sections cleared + 0xFF, 0xFF, 0x00, 0x00, // saveMask: save all sections + 0x00, 0x00, 0x00, 0x00, // loadMask: no sections loaded + 0x01 // deviceMask: only save to BBR +}; + // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. @@ -342,36 +349,36 @@ const uint8_t GPS::_message_SAVE[] = { // has details on low-power modes /* -CFG-PM2 has been replaced by many CFG-PM commands -CFG-PMS has been removed - -CFG-PM-OPERATEMODE E1 (0 | 1 | 2) -> 1 (PSMOO), because sporadic position updates are required instead of continous tracking <10s -(PSMCT) CFG-PM-POSUPDATEPERIOD U4 -> 0ms, no self-timed wakup because receiver power mode is controlled via "software standby -mode" by legacy UBX-RXM-PMREQ request CFG-PM-ACQPERIOD U4 -> 0ms, because receiver power mode is controlled via "software standby -mode" by legacy UBX-RXM-PMREQ request CFG-PM-ONTIME U4 -> 0ms, optional I guess CFG-PM-EXTINTBACKUP L -> 1, force receiver into -BACKUP mode when EXTINT (should be connected to GPS_EN_PIN) pin is "low" - -This is required because the receiver never enters low power mode if microcontroller is in deep-sleep. -Maybe the changing UART_RX levels trigger a wakeup but even with UBX-RXM-PMREQ[12] = 0x00 (all external wakeup sources disabled) -the receivcer remains in aquisition state -> potentially a bug - -Workaround: Control the EXTINT pin by the GPS_EN_PIN signal - -As mentioned in the M10 operational issues down below, power save won't allow the use of BDS B1C. -CFG-SIGNAL-BDS_B1C_ENA L -> 0 +OPERATEMODE E1 2 (0 | 1 | 2) +POSUPDATEPERIOD U4 5 +ACQPERIOD U4 10 +GRIDOFFSET U4 0 +ONTIME U2 1 +MINACQTIME U1 0 +MAXACQTIME U1 0 +DONOTENTEROFF L 1 +WAITTIMEFIX L 1 +UPDATEEPH L 1 +EXTINTWAKE L 0 no ext ints +EXTINTBACKUP L 0 no ext ints +EXTINTINACTIVE L 0 no ext ints +EXTINTACTIVITY U4 0 no ext ints +LIMITPEAKCURRENT L 1 // Ram layer config message: -// 01 01 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01 +// b5 62 06 8a 26 00 00 01 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0 +// 10 01 8b de // BBR layer config message: -// 01 02 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01 +// b5 62 06 8a 26 00 00 02 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0 +// 10 01 8c 03 */ -const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x01, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01, - 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01}; -const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x01, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01, - 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01}; +const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0, + 0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01}; +const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0, + 0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01}; /* CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR From adb094ebc961bf2354ab06cd1f5ef0aea6bbf327 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 24 Sep 2024 19:08:32 +0800 Subject: [PATCH 1208/1377] Remove old comments from main (#4849) These comments were circa 4 years old. Remove them. --- src/main.cpp | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 01fccf2b0..df4b622f1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,6 @@ #include "airtime.h" #include "buzz.h" -#include "error.h" -#include "power.h" -// #include "debug.h" #include "FSCommon.h" #include "Led.h" #include "RTC.h" @@ -22,6 +19,8 @@ #include "concurrency/OSThread.h" #include "concurrency/Periodic.h" #include "detect/ScanI2C.h" +#include "error.h" +#include "power.h" #if !MESHTASTIC_EXCLUDE_I2C #include "detect/ScanI2CTwoWire.h" @@ -39,7 +38,6 @@ #include "target_specific.h" #include #include -// #include #ifdef ARCH_ESP32 #if !MESHTASTIC_EXCLUDE_WEBSERVER @@ -1106,10 +1104,6 @@ void loop() { runASAP = false; - // axpDebugOutput.loop(); - - // heap_caps_check_integrity_all(true); // FIXME - disable this expensive check - #ifdef ARCH_ESP32 esp32Loop(); #endif @@ -1118,9 +1112,6 @@ void loop() #endif powerCommandsCheck(); - // For debugging - // if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving(); - #ifdef DEBUG_STACK static uint32_t lastPrint = 0; if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) { @@ -1129,22 +1120,13 @@ void loop() } #endif - // TODO: This should go into a thread handled by FreeRTOS. - // handleWebResponse(); - service->loop(); long delayMsec = mainController.runOrDelay(); - /* if (mainController.nextThread && delayMsec) - LOG_DEBUG("Next %s in %ld\n", mainController.nextThread->ThreadName.c_str(), - mainController.nextThread->tillRun(millis())); */ - // We want to sleep as long as possible here - because it saves power if (!runASAP && loopCanSleep()) { - // if(delayMsec > 100) LOG_DEBUG("sleeping %ld\n", delayMsec); mainDelay.delay(delayMsec); } - // if (didWake) LOG_DEBUG("wake!\n"); } #endif \ No newline at end of file From 4fbf666cd9887c50816efc148384a707efb1ba9c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 07:12:43 -0500 Subject: [PATCH 1209/1377] Try v3 --- .github/workflows/main_matrix.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 19a92d8b8..15e49d36c 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -156,10 +156,10 @@ jobs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 with: path: ./ - merge-multiple: true + #merge-multiple: true - name: Display structure of downloaded files run: ls -R From d7badcc9cbb92e6957209e63e5e7ab75af6dc5e9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 07:17:38 -0500 Subject: [PATCH 1210/1377] Don't run checks on workflow_dispatch --- .github/workflows/main_matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 15e49d36c..f8c7c65ab 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -51,6 +51,7 @@ jobs: matrix: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' }} steps: - uses: actions/checkout@v4 - name: Build base From e4d0e38f37845537560b6eeeb29694cb3f918198 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 08:25:25 -0500 Subject: [PATCH 1211/1377] Remove native and add v4 back --- .github/workflows/main_matrix.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index f8c7c65ab..e4c228cce 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -120,8 +120,8 @@ jobs: package-raspbian-armv7l: uses: ./.github/workflows/package_raspbian_armv7l.yml - package-native: - uses: ./.github/workflows/package_amd64.yml + # package-native: + # uses: ./.github/workflows/package_amd64.yml after-checks: runs-on: ubuntu-latest @@ -138,8 +138,7 @@ jobs: contents: write pull-requests: write runs-on: ubuntu-latest - needs: - [ + needs: [ build-esp32, build-esp32-s3, build-esp32-c3, @@ -148,7 +147,7 @@ jobs: build-stm32, package-raspbian, package-raspbian-armv7l, - package-native, + # package-native, ] steps: - name: Checkout code @@ -157,10 +156,10 @@ jobs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: ./ - #merge-multiple: true + merge-multiple: true - name: Display structure of downloaded files run: ls -R From 10869ea10a8954d2471ae120aa0e4c76327c4fe7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 09:10:29 -0500 Subject: [PATCH 1212/1377] Don't wait for after-checks --- .github/workflows/main_matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index e4c228cce..acbe8a7d4 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -125,6 +125,7 @@ jobs: after-checks: runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' }} needs: [check] steps: - name: Checkout code @@ -231,7 +232,7 @@ jobs: release-artifacts: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [gather-artifacts, after-checks] + needs: [gather-artifacts] steps: - name: Checkout uses: actions/checkout@v4 From 771cb52616ae719a872d08188b2ee3395022d7bb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 10:52:46 -0500 Subject: [PATCH 1213/1377] Remove amd64 --- .github/workflows/main_matrix.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index acbe8a7d4..2ad346f09 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -335,15 +335,15 @@ jobs: asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb asset_content_type: application/vnd.debian.binary-package - - name: Add raspbian amd64 .deb - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - asset_content_type: application/vnd.debian.binary-package + # - name: Add raspbian amd64 .deb + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ github.token }} + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + # asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + # asset_content_type: application/vnd.debian.binary-package - name: Bump version.properties run: >- From 6c488fe81684e815a7555b5fc7663f05a12132b0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 12:43:39 -0500 Subject: [PATCH 1214/1377] Ony run on test runner label --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf3d15dba..40abcaf87 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ on: jobs: test-simulator: - runs-on: ubuntu-latest + runs-on: test-runner steps: - name: Install libbluetooth shell: bash From 5488c8f57995a05eefb46efde5680646e303459d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 12:54:04 -0500 Subject: [PATCH 1215/1377] Got the runner labels backwards --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40abcaf87..58d0ca445 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ on: jobs: test-simulator: - runs-on: test-runner + runs-on: ubuntu-latest steps: - name: Install libbluetooth shell: bash @@ -57,7 +57,7 @@ jobs: reporter: java-junit hardware-tests: - runs-on: ubuntu-latest + runs-on: test-runner steps: - name: Checkout code uses: actions/checkout@v4 From c679932248ebf12e051c8ce9da0eff38ad1bb9fe Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 12:58:41 -0500 Subject: [PATCH 1216/1377] Setup python --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 58d0ca445..357b4fe30 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,6 +62,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Upgrade python tools shell: bash run: | From c3e53d916df45f820936d96b40831fd4e8641a76 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:04:52 -0500 Subject: [PATCH 1217/1377] [create-pull-request] automated change (#4858) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index b30827191..8c8e94bec 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 2 +build = 3 From f8f9329529a7dd4411669d04da7a2022818bf7e7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:16:24 -0500 Subject: [PATCH 1218/1377] pip3 --- .github/workflows/tests.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 357b4fe30..98b38254d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,18 +62,17 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' + # - uses: actions/setup-python@v5 + # with: + # python-version: '3.10' - name: Upgrade python tools shell: bash run: | - python -m pip install --upgrade pip - pip install -U --no-build-isolation --no-cache-dir "setuptools<72" - pip install -U platformio adafruit-nrfutil --no-build-isolation - pip install -U poetry --no-build-isolation - pip install -U meshtastic --pre --no-build-isolation + pip3 install -U --no-build-isolation --no-cache-dir "setuptools<72" + pip3 install -U platformio adafruit-nrfutil --no-build-isolation + pip3 install -U poetry --no-build-isolation + pip3 install -U meshtastic --pre --no-build-isolation - name: Upgrade platformio shell: bash From 752192b09a677e16ed45790f09ef17900ab4ff46 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:27:13 -0500 Subject: [PATCH 1219/1377] pipx --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 98b38254d..b0e6e3c7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,10 +69,10 @@ jobs: - name: Upgrade python tools shell: bash run: | - pip3 install -U --no-build-isolation --no-cache-dir "setuptools<72" - pip3 install -U platformio adafruit-nrfutil --no-build-isolation - pip3 install -U poetry --no-build-isolation - pip3 install -U meshtastic --pre --no-build-isolation + pipx install -U --no-cache-dir "setuptools<72" + pipx install -U platformio adafruit-nrfutil + pipx install -U poetry + pipx install -U meshtastic --pre - name: Upgrade platformio shell: bash From 4d269501dd3fbb0767f55bac007e307ecb6a4512 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:30:26 -0500 Subject: [PATCH 1220/1377] No args --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0e6e3c7c..f371b85b9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,10 +69,10 @@ jobs: - name: Upgrade python tools shell: bash run: | - pipx install -U --no-cache-dir "setuptools<72" - pipx install -U platformio adafruit-nrfutil - pipx install -U poetry - pipx install -U meshtastic --pre + pipx install "setuptools<72" + pipx install platformio adafruit-nrfutil + pipx install poetry + pipx install meshtastic --pre - name: Upgrade platformio shell: bash From f2801a660bf34439ffdd61b6073d0b96e2761006 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:33:51 -0500 Subject: [PATCH 1221/1377] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f371b85b9..b727b4c49 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,10 +66,10 @@ jobs: # with: # python-version: '3.10' + # pipx install "setuptools<72" - name: Upgrade python tools shell: bash run: | - pipx install "setuptools<72" pipx install platformio adafruit-nrfutil pipx install poetry pipx install meshtastic --pre From 9710ac79d3330e0028bf51d40a01513b6f283693 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:37:40 -0500 Subject: [PATCH 1222/1377] Pipargs --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b727b4c49..fc8912da4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,7 +72,7 @@ jobs: run: | pipx install platformio adafruit-nrfutil pipx install poetry - pipx install meshtastic --pre + pipx install meshtastic --pip-args=--pre - name: Upgrade platformio shell: bash From 3c126212d5c4a04390c731418cf2825586a826ac Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:45:02 -0500 Subject: [PATCH 1223/1377] PIO script --- .github/workflows/tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fc8912da4..e173c619d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -70,10 +70,16 @@ jobs: - name: Upgrade python tools shell: bash run: | - pipx install platformio adafruit-nrfutil + pipx install adafruit-nrfutil pipx install poetry pipx install meshtastic --pip-args=--pre + - name: Install PlatformIO from script + shell: bash + run: | + curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py + python3 get-platformio.py + - name: Upgrade platformio shell: bash run: | From 292027f40f7591b37e5f029015eb0863177844b6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 13:57:53 -0500 Subject: [PATCH 1224/1377] Setup node --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e173c619d..e76273e9e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -85,6 +85,11 @@ jobs: run: | pio upgrade + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Setup pnpm uses: pnpm/action-setup@v4 with: From a9d636c025c614da5877a22a11a91d93cdeaf320 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 14:02:12 -0500 Subject: [PATCH 1225/1377] Consolidate commands --- .github/workflows/tests.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e76273e9e..4b71816c9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -95,11 +95,10 @@ jobs: with: version: latest - - name: Install Dependencies - run: pnpm install - - - name: Setup devices - run: pnpm run setup - - - name: Execute end to end tests on connected hardware - run: pnpm run test + - name: Install dependencies, setup devices and run + shell: bash + run: | + cd meshtastic/ + pnpm install + pnpm run setup + pnpm run test From d6a008500a5828dc8cae33c110744d90b154df50 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 14:05:12 -0500 Subject: [PATCH 1226/1377] Who chose that ridiculous name anyway?! --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b71816c9..2f273529a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -98,7 +98,7 @@ jobs: - name: Install dependencies, setup devices and run shell: bash run: | - cd meshtastic/ + cd meshtestic/ pnpm install pnpm run setup pnpm run test From 64b2bf5f93169fc8fcc4386a029623c6bed3eaed Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 14:18:19 -0500 Subject: [PATCH 1227/1377] Checkout should handle this but oh well --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2f273529a..431338721 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -98,6 +98,7 @@ jobs: - name: Install dependencies, setup devices and run shell: bash run: | + git submodule update --init --recursive cd meshtestic/ pnpm install pnpm run setup From e7569838c7fb38abe431e5060fa4e9d436bfc456 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 14:32:20 -0500 Subject: [PATCH 1228/1377] Bin path --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 431338721..925b91215 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,6 +83,7 @@ jobs: - name: Upgrade platformio shell: bash run: | + export PATH=$PATH:$HOME/.local/bin pio upgrade - name: Setup Node From cac640ea97ecde816ddada8d14484f0b4bd54dd8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 14:57:20 -0500 Subject: [PATCH 1229/1377] Meshtestic submodule update --- meshtestic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtestic b/meshtestic index 37245b3d6..f56bbb54c 160000 --- a/meshtestic +++ b/meshtestic @@ -1 +1 @@ -Subproject commit 37245b3d612a9272f546bbb092837bafdad46bc2 +Subproject commit f56bbb54ce048d5670154fd77512a7d15eb5b7e6 From 1d0013918b80f9f8e48e414098add6405ddd30ae Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 15:15:55 -0500 Subject: [PATCH 1230/1377] master ref --- meshtestic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtestic b/meshtestic index f56bbb54c..37245b3d6 160000 --- a/meshtestic +++ b/meshtestic @@ -1 +1 @@ -Subproject commit f56bbb54ce048d5670154fd77512a7d15eb5b7e6 +Subproject commit 37245b3d612a9272f546bbb092837bafdad46bc2 From 67fd4b64af49cfb9189b1d78c81642989e9f4068 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 15:20:05 -0500 Subject: [PATCH 1231/1377] Actual ref --- meshtestic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtestic b/meshtestic index 37245b3d6..f568cbe9e 160000 --- a/meshtestic +++ b/meshtestic @@ -1 +1 @@ -Subproject commit 37245b3d612a9272f546bbb092837bafdad46bc2 +Subproject commit f568cbe9ec18bd4c1fd3a06d17209a7e5144d9be From 453b3a59b242645d3245e3052d71a6378196af3f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 15:24:08 -0500 Subject: [PATCH 1232/1377] python3 ref --- meshtestic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtestic b/meshtestic index f568cbe9e..dcac7e567 160000 --- a/meshtestic +++ b/meshtestic @@ -1 +1 @@ -Subproject commit f568cbe9ec18bd4c1fd3a06d17209a7e5144d9be +Subproject commit dcac7e5673005f4d8a2b1f0f6e06877b689d7519 From 10c51d8a05fd54e540ffebc027c8f8d88d685cd3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 15:44:16 -0500 Subject: [PATCH 1233/1377] Put this back --- .github/workflows/main_matrix.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 2ad346f09..af8111a63 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -120,8 +120,8 @@ jobs: package-raspbian-armv7l: uses: ./.github/workflows/package_raspbian_armv7l.yml - # package-native: - # uses: ./.github/workflows/package_amd64.yml + package-native: + uses: ./.github/workflows/package_amd64.yml after-checks: runs-on: ubuntu-latest @@ -335,15 +335,15 @@ jobs: asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb asset_content_type: application/vnd.debian.binary-package - # - name: Add raspbian amd64 .deb - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ github.token }} - # with: - # upload_url: ${{ steps.create_release.outputs.upload_url }} - # asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - # asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - # asset_content_type: application/vnd.debian.binary-package + - name: Add raspbian amd64 .deb + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + asset_content_type: application/vnd.debian.binary-package - name: Bump version.properties run: >- From 65104d5d8c7eae04770cc0f486848b12173c2b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 24 Sep 2024 23:51:07 +0200 Subject: [PATCH 1234/1377] fix #4844 (#4859) --- src/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index df4b622f1..6f0099cb1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -624,7 +624,13 @@ void setup() buttonThread = new ButtonThread(); #endif - playStartMelody(); + // only play start melody when role is not tracker or sensor + if (config.power.is_power_saving == true && (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)) + LOG_DEBUG("Tracker/Sensor: Skipping start melody\n"); + else + playStartMelody(); // fixed screen override? if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) From c50df710bad0fbdd97093df19de20c75e7ea8c0d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Sep 2024 20:12:42 -0500 Subject: [PATCH 1235/1377] Also put this back --- .github/workflows/main_matrix.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index af8111a63..ca9f1d37f 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -139,7 +139,8 @@ jobs: contents: write pull-requests: write runs-on: ubuntu-latest - needs: [ + needs: + [ build-esp32, build-esp32-s3, build-esp32-c3, @@ -148,7 +149,7 @@ jobs: build-stm32, package-raspbian, package-raspbian-armv7l, - # package-native, + package-native, ] steps: - name: Checkout code From f73aa8aa82575dbe1d83de54f349a49ca9e17af0 Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Wed, 25 Sep 2024 08:28:33 +0000 Subject: [PATCH 1236/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 9651aa59e..eb915e71f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 9651aa59eaa3b54d801b9c251efcfe842790db32 +Subproject commit eb915e71fc907bef97d98760aa4c6c18698b6c32 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 9ba69c612..b323ddf07 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -205,6 +205,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RPI_PICO2 = 79, /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK_CORES3 = 80, + /* Seeed XIAO S3 DK */ + meshtastic_HardwareModel_SEEED_XIAO_S3 = 81, + /* Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1 */ + meshtastic_HardwareModel_MS24SF1 = 82, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 1293c5cdd4aa54a18af162f670316aaa7e3ca829 Mon Sep 17 00:00:00 2001 From: dylanli Date: Wed, 25 Sep 2024 18:27:14 +0800 Subject: [PATCH 1237/1377] Support for Seeed XIAO S3 Board (#4850) * feat: add seeed-xiao-s3 board defination and pins defination * chore: add SEEED XIAO S3 into mesh pb * fix: fix trunk fmt check failed * Trunk fmt variant.h * Restore automatically generated file --------- Co-authored-by: Tom Fifield --- boards/seeed-xiao-s3.json | 41 +++++++++++++ src/platform/esp32/architecture.h | 2 + variants/seeed_xiao_s3/pins_arduino.h | 21 +++++++ variants/seeed_xiao_s3/platformio.ini | 17 ++++++ variants/seeed_xiao_s3/variant.h | 84 +++++++++++++++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 boards/seeed-xiao-s3.json create mode 100644 variants/seeed_xiao_s3/pins_arduino.h create mode 100644 variants/seeed_xiao_s3/platformio.ini create mode 100644 variants/seeed_xiao_s3/variant.h diff --git a/boards/seeed-xiao-s3.json b/boards/seeed-xiao-s3.json new file mode 100644 index 000000000..0b7b432a0 --- /dev/null +++ b/boards/seeed-xiao-s3.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x2886", "0x0059"]], + "mcu": "esp32s3", + "variant": "seeed-xiao-s3" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "seeed-xiao-s3", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 8388608, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.seeedstudio.com/XIAO-ESP32S3-Sense-p-5639.html", + "vendor": "Seeed Studio" +} diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 90c4c392d..a2e7dfc4e 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -170,6 +170,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #elif defined(SENSECAP_INDICATOR) #define HW_VENDOR meshtastic_HardwareModel_SENSECAP_INDICATOR +#elif defined(SEEED_XIAO_S3) +#define HW_VENDOR meshtastic_HardwareModel_SEEED_XIAO_S3 #endif // ----------------------------------------------------------------------------- diff --git a/variants/seeed_xiao_s3/pins_arduino.h b/variants/seeed_xiao_s3/pins_arduino.h new file mode 100644 index 000000000..52e96eaeb --- /dev/null +++ b/variants/seeed_xiao_s3/pins_arduino.h @@ -0,0 +1,21 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x2886 +#define USB_PID 0x0059 + +// GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600 + +// The default Wire will be mapped to Screen and Sensors +static const uint8_t SDA = 47; +static const uint8_t SCL = 48; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 8; +static const uint8_t SCK = 7; +static const uint8_t MOSI = 9; +static const uint8_t SS = 41; + +#endif /* Pins_Arduino_h */ diff --git a/variants/seeed_xiao_s3/platformio.ini b/variants/seeed_xiao_s3/platformio.ini new file mode 100644 index 000000000..3d10d7136 --- /dev/null +++ b/variants/seeed_xiao_s3/platformio.ini @@ -0,0 +1,17 @@ +[env:seeed-xiao-s3] +extends = esp32s3_base +board = seeed-xiao-s3 +board_check = true +board_build.mcu = esp32s3 +upload_protocol = esptool +upload_speed = 921600 +lib_deps = + ${esp32s3_base.lib_deps} +build_unflags = + ${esp32s3_base.build_unflags} + -DARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} -DSEEED_XIAO_S3 -I variants/seeed_xiao_s3 + -DBOARD_HAS_PSRAM + + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/seeed_xiao_s3/variant.h b/variants/seeed_xiao_s3/variant.h new file mode 100644 index 000000000..ab886d354 --- /dev/null +++ b/variants/seeed_xiao_s3/variant.h @@ -0,0 +1,84 @@ +/* + ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄ +▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ +▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌ +▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ +▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ +▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌ + ▀▀▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░▌ + ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▄▄▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌ +▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ + ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ + + ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ + ▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ + ▐░▌ ▐░▌ ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀█░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄█░▌ + ▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ + ▐░▌░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▐░▌ ▀▀▀▀▀▀▀▀▀█░▌ ▀▀▀▀▀▀▀▀▀█░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▌ ▐░▌ ▄▄▄▄█░█▄▄▄▄ ▐░▌ ▐░▌▐░█▄▄▄▄▄▄▄█░▌ ▄▄▄▄▄▄▄▄▄█░▌ ▄▄▄▄▄▄▄▄▄█░▌ + ▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ + ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ +*/ + +/* +Board Information: https://www.seeedstudio.com/XIAO-ESP32S3-Sense-p-5639.html +Expansion Board Infomation : https://www.seeedstudio.com/Seeeduino-XIAO-Expansion-board-p-4746.html +L76K GPS Module Information : https://www.seeedstudio.com/L76K-GNSS-Module-for-Seeed-Studio-XIAO-p-5864.html +*/ + +#define LED_PIN 48 +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN 21 // This is the Program Button +#define BUTTON_NEED_PULLUP + +/*Warning: + https://www.seeedstudio.com/L76K-GNSS-Module-for-Seeed-Studio-XIAO-p-5864.html + L76K Expansion Board can not directly used, L76K Reset Pin needs to override or physically remove it, + otherwise it will conflict with the SPI pins +*/ +// #define GPS_L76K +#ifdef GPS_L76K +#define GPS_RX_PIN 44 +#define GPS_TX_PIN 43 +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 +#define GPS_THREAD_INTERVAL 50 +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_GPS_STANDBY 1 +#endif + +// XIAO S3 Expansion board has 1.3 inch OLED Screen +#define USCREEN_SSD1306 + +#define I2C_SDA 5 +#define I2C_SCL 6 + +// XIAO S3 LORA module +#define USE_SX1262 + +#define LORA_MISO 8 +#define LORA_SCK 7 +#define LORA_MOSI 9 +#define LORA_CS 41 + +#define LORA_RESET 42 +#define LORA_DIO1 39 + +#define LORA_DIO2 38 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 40 +#define SX126X_RESET LORA_RESET + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file From 1129c929743c8eed8981ba3db3c735e24df5cf5f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Sep 2024 05:31:29 -0500 Subject: [PATCH 1238/1377] Add a second delay() to get the unit tests running on Rak4631 (#4862) --- test/test_crypto/test_main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index 129c88283..0c820178a 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -129,6 +129,7 @@ void setup() { // NOTE!!! Wait for >2 secs // if board doesn't support software reset via Serial.DTR/RTS + delay(10); delay(2000); UNITY_BEGIN(); // IMPORTANT LINE! From 40b3dbaa702f801eb803c806da2923f553695fc7 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Wed, 25 Sep 2024 20:34:53 +1000 Subject: [PATCH 1239/1377] Add MAX17048 lipo fuel gauge (#4851) * Initial commit * Update MAX17048Sensor.cpp * Update EnvironmentTelemetry.cpp --------- Co-authored-by: Ben Meadors --- platformio.ini | 1 + src/Power.cpp | 121 +++++++++++- src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 1 + src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 14 +- src/modules/Telemetry/PowerTelemetry.cpp | 35 ++-- .../Telemetry/Sensor/MAX17048Sensor.cpp | 176 ++++++++++++++++++ src/modules/Telemetry/Sensor/MAX17048Sensor.h | 110 +++++++++++ src/power.h | 7 + 11 files changed, 449 insertions(+), 19 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/MAX17048Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/MAX17048Sensor.h diff --git a/platformio.ini b/platformio.ini index 9b869a036..81632ae0e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -136,6 +136,7 @@ lib_deps = adafruit/Adafruit MCP9808 Library@^2.0.0 adafruit/Adafruit INA260 Library@^1.5.0 adafruit/Adafruit INA219@^1.2.0 + adafruit/Adafruit MAX1704X@^1.0.3 adafruit/Adafruit SHTC3 Library@^1.0.0 adafruit/Adafruit LPS2X@^2.0.4 adafruit/Adafruit SHT31 Library@^2.2.2 diff --git a/src/Power.cpp b/src/Power.cpp index b3a67abd5..bbb6cd2c3 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -77,6 +77,15 @@ INA219Sensor ina219Sensor; INA3221Sensor ina3221Sensor; #endif +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#include "modules/Telemetry/Sensor/MAX17048Sensor.h" +#include +extern std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1]; +#if HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY) +MAX17048Sensor max17048Sensor; +#endif +#endif + #if HAS_RAKPROT && !defined(ARCH_PORTDUINO) RAK9154Sensor rak9154Sensor; #endif @@ -167,6 +176,7 @@ static void adcDisable() */ class AnalogBatteryLevel : public HasBatteryLevel { + public: /** * Battery state of charge, from 0 to 100 or -1 for unknown */ @@ -553,7 +563,12 @@ bool Power::analogInit() */ bool Power::setup() { - bool found = axpChipInit() || analogInit(); + // initialise one power sensor (only) + bool found = axpChipInit(); + if (!found) + found = lipoInit(); + if (!found) + found = analogInit(); #ifdef NRF_APM found = true; @@ -1044,4 +1059,106 @@ bool Power::axpChipInit() #else return false; #endif -} \ No newline at end of file +} + +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) + +/** + * Wrapper class for an I2C MAX17048 Lipo battery sensor. If there is no + * I2C sensor present, the class falls back to analog battery sensing + */ +class LipoBatteryLevel : public AnalogBatteryLevel +{ + private: + MAX17048Singleton *max17048 = nullptr; + + public: + /** + * Init the I2C MAX17048 Lipo battery level sensor + */ + bool runOnce() + { + if (max17048 == nullptr) { + max17048 = MAX17048Singleton::GetInstance(); + } + + // try to start if the sensor has been detected + if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].first != 0) { + return max17048->runOnce(nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].second); + } + return false; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::getBatteryPercent(); + return max17048->getBusBatteryPercent(); + } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::getBattVoltage(); + return max17048->getBusVoltageMv(); + } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::isBatteryConnect(); + return max17048->isBatteryConnected(); + } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::isVbusIn(); + return max17048->isExternallyPowered(); + } + + /** + * return true if the battery is currently charging + */ + virtual bool isCharging() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::isCharging(); + return max17048->isBatteryCharging(); + } +}; + +LipoBatteryLevel lipoLevel; + +/** + * Init the Lipo battery level sensor + */ +bool Power::lipoInit() +{ + bool result = lipoLevel.runOnce(); + LOG_DEBUG("Power::lipoInit lipo sensor is %s\n", result ? "ready" : "not ready yet"); + batteryLevel = &lipoLevel; + return true; +} + +#else +/** + * The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::lipoInit() +{ + return false; +} +#endif \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 0b470eef3..60d5a18ac 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -122,6 +122,7 @@ along with this program. If not, see . #define INA_ADDR_ALTERNATE 0x41 #define INA_ADDR_WAVESHARE_UPS 0x43 #define INA3221_ADDR 0x42 +#define MAX1704X_ADDR 0x36 #define QMC6310_ADDR 0x1C #define QMI8658_ADDR 0x6B #define QMC5883L_ADDR 0x0D diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 090b1a968..50959411b 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -28,6 +28,7 @@ class ScanI2C INA260, INA219, INA3221, + MAX17048, MCP9808, SHT31, SHT4X, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index c0e70503b..285210c94 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -396,6 +396,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); + SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/main.cpp b/src/main.cpp index 6f0099cb1..c93cff85f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -555,6 +555,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index f94f7956b..969529881 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -83,7 +83,7 @@ int32_t EnvironmentTelemetryModule::runOnce() */ // moduleConfig.telemetry.environment_measurement_enabled = 1; - // moduleConfig.telemetry.environment_screen_enabled = 1; + // moduleConfig.telemetry.environment_screen_enabled = 1; // moduleConfig.telemetry.environment_update_interval = 15; if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) { @@ -144,6 +144,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = mlx90632Sensor.runOnce(); if (nau7802Sensor.hasSensor()) result = nau7802Sensor.runOnce(); + if (max17048Sensor.hasSensor()) + result = max17048Sensor.runOnce(); #endif } return result; @@ -156,6 +158,7 @@ int32_t EnvironmentTelemetryModule::runOnce() result = bme680Sensor.runTrigger(); } + uint32_t now = millis(); if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( moduleConfig.telemetry.environment_update_interval, @@ -397,6 +400,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } + if (max17048Sensor.hasSensor()) { + valid = valid && max17048Sensor.getMetrics(m); + hasSensor = true; + } #endif return valid && hasSensor; @@ -587,6 +594,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (max17048Sensor.hasSensor()) { + result = max17048Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } return result; } diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index a493042a0..6130c2c83 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -60,6 +60,8 @@ int32_t PowerTelemetryModule::runOnce() result = ina260Sensor.runOnce(); if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized()) result = ina3221Sensor.runOnce(); + if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized()) + result = max17048Sensor.runOnce(); } return result; #else @@ -128,19 +130,23 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s return; } + // Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags display->setFont(FONT_SMALL); - String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); - if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) { + if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " + - String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); + "Ch1 Volt: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) + + "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); + } + if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " + - String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); + "Ch2 Volt: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) + + "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); + } + if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " + - String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); + "Ch3 Volt: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) + + "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); } } @@ -150,8 +156,8 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m #ifdef DEBUG_PORT const char *sender = getSenderShortName(mp); - LOG_INFO("(Received from %s): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " - "ch3_voltage=%f, ch3_current=%f\n", + LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, " + "ch3_voltage=%.1f, ch3_current=%.1f\n", sender, t->variant.power_metrics.ch1_voltage, t->variant.power_metrics.ch1_current, t->variant.power_metrics.ch2_voltage, t->variant.power_metrics.ch2_current, t->variant.power_metrics.ch3_voltage, t->variant.power_metrics.ch3_current); @@ -172,12 +178,7 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) m->time = getTime(); m->which_variant = meshtastic_Telemetry_power_metrics_tag; - m->variant.power_metrics.ch1_voltage = 0; - m->variant.power_metrics.ch1_current = 0; - m->variant.power_metrics.ch2_voltage = 0; - m->variant.power_metrics.ch2_current = 0; - m->variant.power_metrics.ch3_voltage = 0; - m->variant.power_metrics.ch3_current = 0; + m->variant.power_metrics = meshtastic_PowerMetrics_init_zero; #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) if (ina219Sensor.hasSensor()) valid = ina219Sensor.getMetrics(m); @@ -185,6 +186,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) valid = ina260Sensor.getMetrics(m); if (ina3221Sensor.hasSensor()) valid = ina3221Sensor.getMetrics(m); + if (max17048Sensor.hasSensor()) + valid = max17048Sensor.getMetrics(m); #endif return valid; diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp new file mode 100644 index 000000000..a3890df43 --- /dev/null +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp @@ -0,0 +1,176 @@ +#include "MAX17048Sensor.h" + +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) + +MAX17048Singleton *MAX17048Singleton::GetInstance() +{ + if (pinstance == nullptr) { + pinstance = new MAX17048Singleton(); + } + return pinstance; +} + +MAX17048Singleton::MAX17048Singleton() {} + +MAX17048Singleton::~MAX17048Singleton() {} + +MAX17048Singleton *MAX17048Singleton::pinstance{nullptr}; + +bool MAX17048Singleton::runOnce(TwoWire *theWire) +{ + initialized = begin(theWire); + LOG_DEBUG("MAX17048Sensor::runOnce %s\n", initialized ? "began ok" : "begin failed"); + return initialized; +} + +bool MAX17048Singleton::isBatteryCharging() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::isBatteryCharging is not connected\n"); + return 0; + } + + MAX17048ChargeSample sample; + sample.chargeRate = chargeRate(); // charge/discharge rate in percent/hr + sample.cellPercent = cellPercent(); // state of charge in percent 0 to 100 + chargeSamples.push(sample); // save a sample into a fifo buffer + + // Keep the fifo buffer trimmed + while (chargeSamples.size() > MAX17048_CHARGING_SAMPLES) + chargeSamples.pop(); + + // Based on the past n samples, is the lipo charging, discharging or idle + if (chargeSamples.front().chargeRate > MAX17048_CHARGING_MINIMUM_RATE && + chargeSamples.back().chargeRate > MAX17048_CHARGING_MINIMUM_RATE) { + if (chargeSamples.front().cellPercent > chargeSamples.back().cellPercent) + chargeState = MAX17048ChargeState::EXPORT; + else if (chargeSamples.front().cellPercent < chargeSamples.back().cellPercent) + chargeState = MAX17048ChargeState::IMPORT; + else + chargeState = MAX17048ChargeState::IDLE; + } else { + chargeState = MAX17048ChargeState::IDLE; + } + + LOG_DEBUG("MAX17048Sensor::isBatteryCharging %s volts: %.3f soc: %.3f rate: %.3f\n", chargeLabels[chargeState], volts, + sample.cellPercent, sample.chargeRate); + return chargeState == MAX17048ChargeState::IMPORT; +} + +uint16_t MAX17048Singleton::getBusVoltageMv() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::getBusVoltageMv is not connected\n"); + return 0; + } + LOG_DEBUG("MAX17048Sensor::getBusVoltageMv %.3fmV\n", volts); + return (uint16_t)(volts * 1000.0f); +} + +uint8_t MAX17048Singleton::getBusBatteryPercent() +{ + float soc = cellPercent(); + LOG_DEBUG("MAX17048Sensor::getBusBatteryPercent %.1f%%\n", soc); + return clamp(static_cast(round(soc)), static_cast(0), static_cast(100)); +} + +uint16_t MAX17048Singleton::getTimeToGoSecs() +{ + float rate = chargeRate(); // charge/discharge rate in percent/hr + float soc = cellPercent(); // state of charge in percent 0 to 100 + soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100% + float ttg = ((100.0f - soc) / rate) * 3600.0f; // calculate seconds to charge/discharge + LOG_DEBUG("MAX17048Sensor::getTimeToGoSecs %.0f seconds\n", ttg); + return (uint16_t)ttg; +} + +bool MAX17048Singleton::isBatteryConnected() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::isBatteryConnected is not connected\n"); + return false; + } + + // if a valid voltage is returned, then battery must be connected + return true; +} + +bool MAX17048Singleton::isExternallyPowered() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + // if the battery is not connected then there must be external power + LOG_DEBUG("MAX17048Sensor::isExternallyPowered battery is\n"); + return true; + } + // if the bus voltage is over MAX17048_BUS_POWER_VOLTS, then the external power + // is assumed to be connected + LOG_DEBUG("MAX17048Sensor::isExternallyPowered %s connected\n", volts >= MAX17048_BUS_POWER_VOLTS ? "is" : "is not"); + return volts >= MAX17048_BUS_POWER_VOLTS; +} + +#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)) + +MAX17048Sensor::MAX17048Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MAX17048, "MAX17048") {} + +int32_t MAX17048Sensor::runOnce() +{ + if (isInitialized()) { + LOG_INFO("Init sensor: %s is already initialised\n", sensorName); + return true; + } + + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + // Get a singleton instance and initialise the max17048 + if (max17048 == nullptr) { + max17048 = MAX17048Singleton::GetInstance(); + } + status = max17048->runOnce(nodeTelemetrySensorsMap[sensorType].second); + return initI2CSensor(); +} + +void MAX17048Sensor::setup() {} + +bool MAX17048Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("MAX17048Sensor::getMetrics id: %i\n", measurement->which_variant); + + float volts = max17048->cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::getMetrics battery is not connected\n"); + return false; + } + + float rate = max17048->chargeRate(); // charge/discharge rate in percent/hr + float soc = max17048->cellPercent(); // state of charge in percent 0 to 100 + soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100% + float ttg = (100.0f - soc) / rate; // calculate hours to charge/discharge + + LOG_DEBUG("MAX17048Sensor::getMetrics volts: %.3fV soc: %.1f%% ttg: %.1f hours\n", volts, soc, ttg); + if ((int)measurement->which_variant == meshtastic_Telemetry_power_metrics_tag) { + measurement->variant.power_metrics.has_ch1_voltage = true; + measurement->variant.power_metrics.ch1_voltage = volts; + } else if ((int)measurement->which_variant == meshtastic_Telemetry_device_metrics_tag) { + measurement->variant.device_metrics.has_battery_level = true; + measurement->variant.device_metrics.has_voltage = true; + measurement->variant.device_metrics.battery_level = static_cast(round(soc)); + measurement->variant.device_metrics.voltage = volts; + } + return true; +} + +uint16_t MAX17048Sensor::getBusVoltageMv() +{ + return max17048->getBusVoltageMv(); +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.h b/src/modules/Telemetry/Sensor/MAX17048Sensor.h new file mode 100644 index 000000000..20dca324c --- /dev/null +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.h @@ -0,0 +1,110 @@ +#pragma once + +#ifndef MAX17048_SENSOR_H +#define MAX17048_SENSOR_H + +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) + +// Samples to store in a buffer to determine if the battery is charging or discharging +#define MAX17048_CHARGING_SAMPLES 3 + +// Threshold to determine if the battery is on charge, in percent/hour +#define MAX17048_CHARGING_MINIMUM_RATE 1.0f + +// Threshold to determine if the board has bus power +#define MAX17048_BUS_POWER_VOLTS 4.195f + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "VoltageSensor.h" + +#include "meshUtils.h" +#include +#include + +struct MAX17048ChargeSample { + float cellPercent; + float chargeRate; +}; + +enum MAX17048ChargeState { IDLE, EXPORT, IMPORT }; + +// Singleton wrapper for the Adafruit_MAX17048 class +class MAX17048Singleton : public Adafruit_MAX17048 +{ + private: + static MAX17048Singleton *pinstance; + bool initialized = false; + std::queue chargeSamples; + MAX17048ChargeState chargeState = IDLE; + const String chargeLabels[3] = {F("idle"), F("export"), F("import")}; + + protected: + MAX17048Singleton(); + ~MAX17048Singleton(); + + public: + // Create a singleton instance (not thread safe) + static MAX17048Singleton *GetInstance(); + + // Singletons should not be cloneable. + MAX17048Singleton(MAX17048Singleton &other) = delete; + + // Singletons should not be assignable. + void operator=(const MAX17048Singleton &) = delete; + + // Initialise the sensor (not thread safe) + virtual bool runOnce(TwoWire *theWire = &Wire); + + // Get the current bus voltage + uint16_t getBusVoltageMv(); + + // Get the state of charge in percent 0 to 100 + uint8_t getBusBatteryPercent(); + + // Calculate the seconds to charge/discharge + uint16_t getTimeToGoSecs(); + + // Returns true if the battery sensor has started + inline virtual bool isInitialised() { return initialized; }; + + // Returns true if the battery is currently on charge (not thread safe) + bool isBatteryCharging(); + + // Returns true if a battery is actually connected + bool isBatteryConnected(); + + // Returns true if there is bus or external power connected + bool isExternallyPowered(); +}; + +#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)) + +class MAX17048Sensor : public TelemetrySensor, VoltageSensor +{ + private: + MAX17048Singleton *max17048 = nullptr; + + protected: + virtual void setup() override; + + public: + MAX17048Sensor(); + + // Initialise the sensor + virtual int32_t runOnce() override; + + // Get the current bus voltage and state of charge + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + // Get the current bus voltage + virtual uint16_t getBusVoltageMv() override; +}; + +#endif + +#endif + +#endif \ No newline at end of file diff --git a/src/power.h b/src/power.h index a4307ee07..19a4f1228 100644 --- a/src/power.h +++ b/src/power.h @@ -48,6 +48,11 @@ extern INA219Sensor ina219Sensor; extern INA3221Sensor ina3221Sensor; #endif +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#include "modules/Telemetry/Sensor/MAX17048Sensor.h" +extern MAX17048Sensor max17048Sensor; +#endif + #if HAS_RAKPROT && !defined(ARCH_PORTDUINO) #include "../variants/rak2560/RAK9154Sensor.h" extern RAK9154Sensor rak9154Sensor; @@ -82,6 +87,8 @@ class Power : private concurrency::OSThread bool axpChipInit(); /// Setup a simple ADC input based battery sensor bool analogInit(); + /// Setup a Lipo battery level sensor + bool lipoInit(); private: // open circuit voltage lookup table From 9456c42fc0981cba2159b8ddeef75cd5405f9048 Mon Sep 17 00:00:00 2001 From: David <2941290+dhskinner@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:25:31 +1000 Subject: [PATCH 1240/1377] Refactor AccelerometerThread.h (#4831) * Initial upload * Tidy up * Update ICM20948Sensor.cpp * Update AccelerometerThread.h * Initial upload * Tidy up * Update ICM20948Sensor.cpp * Update AccelerometerThread.h --------- Co-authored-by: Ben Meadors --- platformio.ini | 3 +- src/AccelerometerThread.h | 319 ------------------------------- src/configuration.h | 2 + src/detect/ScanI2C.cpp | 4 +- src/detect/ScanI2C.h | 10 +- src/detect/ScanI2CTwoWire.cpp | 21 +- src/main.cpp | 7 +- src/main.h | 6 +- src/modules/AdminModule.cpp | 4 +- src/motion/AccelerometerThread.h | 149 +++++++++++++++ src/motion/BMA423Sensor.cpp | 62 ++++++ src/motion/BMA423Sensor.h | 26 +++ src/motion/BMX160Sensor.cpp | 100 ++++++++++ src/motion/BMX160Sensor.h | 41 ++++ src/motion/ICM20948Sensor.cpp | 186 ++++++++++++++++++ src/motion/ICM20948Sensor.h | 96 ++++++++++ src/motion/LIS3DHSensor.cpp | 36 ++++ src/motion/LIS3DHSensor.h | 24 +++ src/motion/LSM6DS3Sensor.cpp | 33 ++++ src/motion/LSM6DS3Sensor.h | 28 +++ src/motion/MPU6050Sensor.cpp | 31 +++ src/motion/MPU6050Sensor.h | 24 +++ src/motion/MotionSensor.cpp | 79 ++++++++ src/motion/MotionSensor.h | 85 ++++++++ src/motion/STK8XXXSensor.cpp | 40 ++++ src/motion/STK8XXXSensor.h | 37 ++++ 26 files changed, 1116 insertions(+), 337 deletions(-) delete mode 100644 src/AccelerometerThread.h create mode 100755 src/motion/AccelerometerThread.h create mode 100755 src/motion/BMA423Sensor.cpp create mode 100755 src/motion/BMA423Sensor.h create mode 100755 src/motion/BMX160Sensor.cpp create mode 100755 src/motion/BMX160Sensor.h create mode 100755 src/motion/ICM20948Sensor.cpp create mode 100755 src/motion/ICM20948Sensor.h create mode 100755 src/motion/LIS3DHSensor.cpp create mode 100755 src/motion/LIS3DHSensor.h create mode 100755 src/motion/LSM6DS3Sensor.cpp create mode 100755 src/motion/LSM6DS3Sensor.h create mode 100755 src/motion/MPU6050Sensor.cpp create mode 100755 src/motion/MPU6050Sensor.h create mode 100755 src/motion/MotionSensor.cpp create mode 100755 src/motion/MotionSensor.h create mode 100755 src/motion/STK8XXXSensor.cpp create mode 100755 src/motion/STK8XXXSensor.h diff --git a/platformio.ini b/platformio.ini index 81632ae0e..b3898ba93 100644 --- a/platformio.ini +++ b/platformio.ini @@ -149,6 +149,7 @@ lib_deps = adafruit/Adafruit SHT4x Library@^1.0.4 adafruit/Adafruit TSL2591 Library@^1.4.5 sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5 + sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@^1.2.13 ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 @@ -162,4 +163,4 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h deleted file mode 100644 index 629d63c6a..000000000 --- a/src/AccelerometerThread.h +++ /dev/null @@ -1,319 +0,0 @@ -#pragma once -#include "configuration.h" - -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -#include "PowerFSM.h" -#include "concurrency/OSThread.h" -#include "main.h" -#include "power.h" - -#include -#include -#include -#ifdef STK8XXX_INT -#include -#endif -#include -#include -#include -#ifdef RAK_4631 -#include "Fusion/Fusion.h" -#include "graphics/Screen.h" -#include "graphics/ScreenFonts.h" -#include -#endif - -#define ACCELEROMETER_CHECK_INTERVAL_MS 100 -#define ACCELEROMETER_CLICK_THRESHOLD 40 - -volatile static bool STK_IRQ; - -static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) -{ - Wire.beginTransmission(address); - Wire.write(reg); - Wire.endTransmission(); - Wire.requestFrom((uint8_t)address, (uint8_t)len); - uint8_t i = 0; - while (Wire.available()) { - data[i++] = Wire.read(); - } - return 0; // Pass -} - -static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) -{ - Wire.beginTransmission(address); - Wire.write(reg); - Wire.write(data, len); - return (0 != Wire.endTransmission()); -} - -class AccelerometerThread : public concurrency::OSThread -{ - public: - explicit AccelerometerThread(ScanI2C::DeviceType type) : OSThread("AccelerometerThread") - { - if (accelerometer_found.port == ScanI2C::I2CPort::NO_I2C) { - LOG_DEBUG("AccelerometerThread disabling due to no sensors found\n"); - disable(); - return; - } - acceleremoter_type = type; -#ifndef RAK_4631 - if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { - LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n"); - disable(); - return; - } -#endif - init(); - } - - void start() - { - init(); - setIntervalFromNow(0); - }; - - protected: - int32_t runOnce() override - { - canSleep = true; // Assume we should not keep the board awake - - if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) { - wakeScreen(); - } else if (acceleremoter_type == ScanI2C::DeviceType::STK8BAXX && STK_IRQ) { - STK_IRQ = false; - if (config.display.wake_on_tap_or_motion) { - wakeScreen(); - } - } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) { - uint8_t click = lis.getClick(); - if (!config.device.double_tap_as_button_press) { - wakeScreen(); - } - - if (config.device.double_tap_as_button_press && (click & 0x20)) { - buttonPress(); - return 500; - } - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) { - if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) { - wakeScreen(); - return 500; - } -#ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { - sBmx160SensorData_t magAccel; - sBmx160SensorData_t gAccel; - - /* Get a new sensor event */ - bmx160.getAllData(&magAccel, NULL, &gAccel); - - // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 12 * 1000 && millis() < 30 * 1000) { - if (!showingScreen) { - showingScreen = true; - screen->startAlert((FrameCallback)drawFrameCalibration); - } - if (magAccel.x > highestX) - highestX = magAccel.x; - if (magAccel.x < lowestX) - lowestX = magAccel.x; - if (magAccel.y > highestY) - highestY = magAccel.y; - if (magAccel.y < lowestY) - lowestY = magAccel.y; - if (magAccel.z > highestZ) - highestZ = magAccel.z; - if (magAccel.z < lowestZ) - lowestZ = magAccel.z; - } else if (showingScreen && millis() >= 30 * 1000) { - showingScreen = false; - screen->endAlert(); - } - - int highestRealX = highestX - (highestX + lowestX) / 2; - - magAccel.x -= (highestX + lowestX) / 2; - magAccel.y -= (highestY + lowestY) / 2; - magAccel.z -= (highestZ + lowestZ) / 2; - FusionVector ga, ma; - ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board - ga.axis.y = -gAccel.y; - ga.axis.z = gAccel.z; - ma.axis.x = -magAccel.x; - ma.axis.y = -magAccel.y; - ma.axis.z = magAccel.z * 3; - - // If we're set to one of the inverted positions - if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { - ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); - ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); - } - - float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); - - switch (config.display.compass_orientation) { - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0: - break; - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED: - heading += 90; - break; - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED: - heading += 180; - break; - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED: - heading += 270; - break; - } - - screen->setHeading(heading); - -#endif - } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { - wakeScreen(); - return 500; - } - - return ACCELEROMETER_CHECK_INTERVAL_MS; - } - - private: - void init() - { - LOG_DEBUG("AccelerometerThread initializing\n"); - - if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.begin(accelerometer_found.address)) { - LOG_DEBUG("MPU6050 initializing\n"); - // setup motion detection - mpu.setHighPassFilter(MPU6050_HIGHPASS_0_63_HZ); - mpu.setMotionDetectionThreshold(1); - mpu.setMotionDetectionDuration(20); - mpu.setInterruptPinLatch(true); // Keep it latched. Will turn off when reinitialized. - mpu.setInterruptPinPolarity(true); -#ifdef STK8XXX_INT - } else if (acceleremoter_type == ScanI2C::DeviceType::STK8BAXX && stk8baxx.STK8xxx_Initialization(STK8xxx_VAL_RANGE_2G)) { - STK_IRQ = false; - LOG_DEBUG("STX8BAxx initialized\n"); - stk8baxx.STK8xxx_Anymotion_init(); - pinMode(STK8XXX_INT, INPUT_PULLUP); - attachInterrupt( - digitalPinToInterrupt(STK8XXX_INT), [] { STK_IRQ = true; }, RISING); -#endif - } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.begin(accelerometer_found.address)) { - LOG_DEBUG("LIS3DH initializing\n"); - lis.setRange(LIS3DH_RANGE_2_G); - // Adjust threshold, higher numbers are less sensitive - lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD); - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && - bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) { - LOG_DEBUG("BMA423 initializing\n"); - bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4, - bmaSensor.PERF_CONTINUOUS_MODE); - bmaSensor.enableAccelerometer(); - bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE, - BMA4_INPUT_DISABLE); - -#ifdef BMA423_INT - pinMode(BMA4XX_INT, INPUT); - attachInterrupt( - BMA4XX_INT, - [] { - // Set interrupt to set irq value to true - BMA_IRQ = true; - }, - RISING); // Select the interrupt mode according to the actual circuit -#endif - -#ifdef T_WATCH_S3 - // Need to raise the wrist function, need to set the correct axis - bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER); -#else - bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); -#endif - // bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true); - bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true); - bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true); - // bmaSensor.resetPedometer(); - - // Turn on feature interrupt - bmaSensor.enablePedometerIRQ(); - bmaSensor.enableTiltIRQ(); - // It corresponds to isDoubleClick interrupt - bmaSensor.enableWakeupIRQ(); -#ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { - bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate - -#endif - } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) { - LOG_DEBUG("LSM6DS3 initializing\n"); - // Default threshold of 2G, less sensitive options are 4, 8 or 16G - lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G); -#ifndef LSM6DS3_WAKE_THRESH -#define LSM6DS3_WAKE_THRESH 20 -#endif - lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH); - // Duration is number of occurances needed to trigger, higher threshold is less sensitive - } - } - void wakeScreen() - { - if (powerFSM.getState() == &stateDARK) { - LOG_INFO("Tap or motion detected. Turning on screen\n"); - powerFSM.trigger(EVENT_INPUT); - } - } - - void buttonPress() - { - LOG_DEBUG("Double-tap detected. Firing button press\n"); - powerFSM.trigger(EVENT_PRESS); - } - - ScanI2C::DeviceType acceleremoter_type; - Adafruit_MPU6050 mpu; - Adafruit_LIS3DH lis; -#ifdef STK8XXX_INT - STK8xxx stk8baxx; -#endif - Adafruit_LSM6DS3TRC lsm; - SensorBMA423 bmaSensor; - bool BMA_IRQ = false; -#ifdef RAK_4631 - bool showingScreen = false; - RAK_BMX160 bmx160; - float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; - - static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - display->drawString(x, y, "Calibrating\nCompass"); - int16_t compassX = 0, compassY = 0; - uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); - - // coordinates for the center of the compass/circle - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + display->getHeight() / 2; - } else { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; - } - display->drawCircle(compassX, compassY, compassDiam / 2); - screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); - } -#endif -}; - -#endif \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 60d5a18ac..55206bf4e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -151,6 +151,8 @@ along with this program. If not, see . #define BMA423_ADDR 0x19 #define LSM6DS3_ADDR 0x6A #define BMX160_ADDR 0x69 +#define ICM20948_ADDR 0x69 +#define ICM20948_ADDR_ALT 0x68 // ----------------------------------------------------------------------------- // LED diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index eaba62a78..a9d70edaa 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const { - ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX}; - return firstOfOrNONE(6, types); + ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948}; + return firstOfOrNONE(7, types); } ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 50959411b..3b49026ce 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -57,7 +57,8 @@ class ScanI2C DFROBOT_LARK, NAU7802, FT6336U, - STK8BAXX + STK8BAXX, + ICM20948 } DeviceType; // typedef uint8_t DeviceAddress; @@ -68,8 +69,9 @@ class ScanI2C } I2CPort; typedef struct DeviceAddress { - I2CPort port; - uint8_t address; + // set default values for ADDRESS_NONE + I2CPort port = I2CPort::NO_I2C; + uint8_t address = 0; explicit DeviceAddress(I2CPort port, uint8_t address); DeviceAddress(); @@ -120,4 +122,4 @@ class ScanI2C private: bool shouldSuppressScreen = false; -}; +}; \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 285210c94..43340765a 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -382,10 +382,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n") SCAN_SIMPLE_CASE(HMC5883L_ADDR, HMC5883L, "HMC5883L 3-Axis digital compass found\n") - SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") - SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n"); - SCAN_SIMPLE_CASE(BMX160_ADDR, BMX160, "BMX160 accelerometer found\n"); SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9535_ADDR, TCA9535, "TCA9535 I2C expander found\n"); @@ -398,6 +395,24 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); + case ICM20948_ADDR: // same as BMX160_ADDR + case ICM20948_ADDR_ALT: // same as MPU6050_ADDR + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); + if (registerValue == 0xEA) { + type = ICM20948; + LOG_INFO("ICM20948 9-dof motion processor found\n"); + break; + } else if (addr.address == BMX160_ADDR) { + type = BMX160; + LOG_INFO("BMX160 accelerometer found\n"); + break; + } else { + type = MPU6050; + LOG_INFO("MPU6050 accelerometer found\n"); + break; + } + break; + default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); } diff --git a/src/main.cpp b/src/main.cpp index c93cff85f..c2d275177 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -101,8 +101,8 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "AmbientLightingThread.h" #include "PowerFSMThread.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AccelerometerThread.h" +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +#include "motion/AccelerometerThread.h" AccelerometerThread *accelerometerThread = nullptr; #endif @@ -574,6 +574,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948) i2cScanner.reset(); #endif @@ -647,7 +648,7 @@ void setup() #endif #if !MESHTASTIC_EXCLUDE_I2C -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) if (acc_info.type != ScanI2C::DeviceType::NONE) { accelerometerThread = new AccelerometerThread(acc_info.type); } diff --git a/src/main.h b/src/main.h index 52a3fff1f..5722f7cf0 100644 --- a/src/main.h +++ b/src/main.h @@ -56,8 +56,8 @@ extern AudioThread *audioThread; // Global Screen singleton. extern graphics::Screen *screen; -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AccelerometerThread.h" +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +#include "motion/AccelerometerThread.h" extern AccelerometerThread *accelerometerThread; #endif @@ -86,4 +86,4 @@ void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearB meshtastic_DeviceMetadata getDeviceMetadata(); // We default to 4MHz SPI, SPI mode 0 -extern SPISettings spiSettings; +extern SPISettings spiSettings; \ No newline at end of file diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 48048b054..389a0db8d 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -34,8 +34,8 @@ #include "modules/PositionModule.h" #endif -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AccelerometerThread.h" +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +#include "motion/AccelerometerThread.h" #endif AdminModule *adminModule; diff --git a/src/motion/AccelerometerThread.h b/src/motion/AccelerometerThread.h new file mode 100755 index 000000000..f9cbb49a1 --- /dev/null +++ b/src/motion/AccelerometerThread.h @@ -0,0 +1,149 @@ +#pragma once +#ifndef _ACCELEROMETER_H_ +#define _ACCELEROMETER_H_ + +#include "configuration.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include "../concurrency/OSThread.h" +#include "BMA423Sensor.h" +#include "BMX160Sensor.h" +#include "ICM20948Sensor.h" +#include "LIS3DHSensor.h" +#include "LSM6DS3Sensor.h" +#include "MPU6050Sensor.h" +#include "MotionSensor.h" +#include "STK8XXXSensor.h" + +extern ScanI2C::DeviceAddress accelerometer_found; + +class AccelerometerThread : public concurrency::OSThread +{ + private: + MotionSensor *sensor = nullptr; + bool isInitialised = false; + + public: + explicit AccelerometerThread(ScanI2C::FoundDevice foundDevice) : OSThread("AccelerometerThread") + { + device = foundDevice; + init(); + } + + explicit AccelerometerThread(ScanI2C::DeviceType type) : AccelerometerThread(ScanI2C::FoundDevice{type, accelerometer_found}) + { + } + + void start() + { + init(); + setIntervalFromNow(0); + }; + + protected: + int32_t runOnce() override + { + // Assume we should not keep the board awake + canSleep = true; + + if (isInitialised) + return sensor->runOnce(); + + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + private: + ScanI2C::FoundDevice device; + + void init() + { + if (isInitialised) + return; + + if (device.address.port == ScanI2C::I2CPort::NO_I2C || device.address.address == 0 || device.type == ScanI2C::NONE) { + LOG_DEBUG("AccelerometerThread disabling due to no sensors found\n"); + disable(); + return; + } + +#ifndef RAK_4631 + if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { + LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n"); + disable(); + return; + } +#endif + + switch (device.type) { + case ScanI2C::DeviceType::BMA423: + sensor = new BMA423Sensor(device); + break; + case ScanI2C::DeviceType::MPU6050: + sensor = new MPU6050Sensor(device); + break; + case ScanI2C::DeviceType::BMX160: + sensor = new BMX160Sensor(device); + break; + case ScanI2C::DeviceType::LIS3DH: + sensor = new LIS3DHSensor(device); + break; + case ScanI2C::DeviceType::LSM6DS3: + sensor = new LSM6DS3Sensor(device); + break; + case ScanI2C::DeviceType::STK8BAXX: + sensor = new STK8XXXSensor(device); + break; + case ScanI2C::DeviceType::ICM20948: + sensor = new ICM20948Sensor(device); + break; + default: + disable(); + return; + } + + isInitialised = sensor->init(); + if (!isInitialised) { + clean(); + } + LOG_DEBUG("AccelerometerThread::init %s\n", isInitialised ? "ok" : "failed"); + } + + // Copy constructor (not implemented / included to avoid cppcheck warnings) + AccelerometerThread(const AccelerometerThread &other) : OSThread::OSThread("AccelerometerThread") { this->copy(other); } + + // Destructor (included to avoid cppcheck warnings) + virtual ~AccelerometerThread() { clean(); } + + // Copy assignment (not implemented / included to avoid cppcheck warnings) + AccelerometerThread &operator=(const AccelerometerThread &other) + { + this->copy(other); + return *this; + } + + // Take a very shallow copy (does not copy OSThread state nor the sensor object) + // If for some reason this is ever used, make sure to call init() after any copy + void copy(const AccelerometerThread &other) + { + if (this != &other) { + clean(); + this->device = ScanI2C::FoundDevice(other.device.type, + ScanI2C::DeviceAddress(other.device.address.port, other.device.address.address)); + } + } + + // Cleanup resources + void clean() + { + isInitialised = false; + if (sensor != nullptr) { + delete sensor; + sensor = nullptr; + } + } +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/BMA423Sensor.cpp b/src/motion/BMA423Sensor.cpp new file mode 100755 index 000000000..ec07b7c40 --- /dev/null +++ b/src/motion/BMA423Sensor.cpp @@ -0,0 +1,62 @@ +#include "BMA423Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +using namespace MotionSensorI2C; + +BMA423Sensor::BMA423Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool BMA423Sensor::init() +{ + if (sensor.begin(deviceAddress(), &MotionSensorI2C::readRegister, &MotionSensorI2C::writeRegister)) { + sensor.configAccelerometer(sensor.RANGE_2G, sensor.ODR_100HZ, sensor.BW_NORMAL_AVG4, sensor.PERF_CONTINUOUS_MODE); + sensor.enableAccelerometer(); + sensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE, BMA4_INPUT_DISABLE); + +#ifdef BMA423_INT + pinMode(BMA4XX_INT, INPUT); + attachInterrupt( + BMA4XX_INT, + [] { + // Set interrupt to set irq value to true + BMA_IRQ = true; + }, + RISING); // Select the interrupt mode according to the actual circuit +#endif + +#ifdef T_WATCH_S3 + // Need to raise the wrist function, need to set the correct axis + sensor.setReampAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER); +#else + sensor.setReampAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); +#endif + // sensor.enableFeature(sensor.FEATURE_STEP_CNTR, true); + sensor.enableFeature(sensor.FEATURE_TILT, true); + sensor.enableFeature(sensor.FEATURE_WAKEUP, true); + // sensor.resetPedometer(); + + // Turn on feature interrupt + sensor.enablePedometerIRQ(); + sensor.enableTiltIRQ(); + + // It corresponds to isDoubleClick interrupt + sensor.enableWakeupIRQ(); + LOG_DEBUG("BMA423Sensor::init ok\n"); + return true; + } + LOG_DEBUG("BMA423Sensor::init failed\n"); + return false; +} + +int32_t BMA423Sensor::runOnce() +{ + if (sensor.readIrqStatus() != DEV_WIRE_NONE) { + if (sensor.isTilt() || sensor.isDoubleTap()) { + wakeScreen(); + return 500; + } + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/BMA423Sensor.h b/src/motion/BMA423Sensor.h new file mode 100755 index 000000000..0bc0ddf8c --- /dev/null +++ b/src/motion/BMA423Sensor.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef _BMA423_SENSOR_H_ +#define _BMA423_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include +#include + +class BMA423Sensor : public MotionSensor +{ + private: + SensorBMA423 sensor; + volatile bool BMA_IRQ = false; + + public: + explicit BMA423Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp new file mode 100755 index 000000000..f18c4b586 --- /dev/null +++ b/src/motion/BMX160Sensor.cpp @@ -0,0 +1,100 @@ +#include "BMX160Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +BMX160Sensor::BMX160Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +#ifdef RAK_4631 + +// screen is defined in main.cpp +extern graphics::Screen *screen; + +bool BMX160Sensor::init() +{ + if (sensor.begin()) { + // set output data rate + sensor.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); + LOG_DEBUG("BMX160Sensor::init ok\n"); + return true; + } + LOG_DEBUG("BMX160Sensor::init failed\n"); + return false; +} + +int32_t BMX160Sensor::runOnce() +{ + sBmx160SensorData_t magAccel; + sBmx160SensorData_t gAccel; + + /* Get a new sensor event */ + sensor.getAllData(&magAccel, NULL, &gAccel); + + // experimental calibrate routine. Limited to between 10 and 30 seconds after boot + if (millis() > 12 * 1000 && millis() < 30 * 1000) { + if (!showingScreen) { + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameCalibration); + } + if (magAccel.x > highestX) + highestX = magAccel.x; + if (magAccel.x < lowestX) + lowestX = magAccel.x; + if (magAccel.y > highestY) + highestY = magAccel.y; + if (magAccel.y < lowestY) + lowestY = magAccel.y; + if (magAccel.z > highestZ) + highestZ = magAccel.z; + if (magAccel.z < lowestZ) + lowestZ = magAccel.z; + } else if (showingScreen && millis() >= 30 * 1000) { + showingScreen = false; + screen->endAlert(); + } + + int highestRealX = highestX - (highestX + lowestX) / 2; + + magAccel.x -= (highestX + lowestX) / 2; + magAccel.y -= (highestY + lowestY) / 2; + magAccel.z -= (highestZ + lowestZ) / 2; + FusionVector ga, ma; + ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board + ga.axis.y = -gAccel.y; + ga.axis.z = gAccel.z; + ma.axis.x = -magAccel.x; + ma.axis.y = -magAccel.y; + ma.axis.z = magAccel.z * 3; + + // If we're set to one of the inverted positions + if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { + ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); + ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); + } + + float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); + + switch (config.display.compass_orientation) { + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0: + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED: + heading += 90; + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED: + heading += 180; + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED: + heading += 270; + break; + } + screen->setHeading(heading); + + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h new file mode 100755 index 000000000..26f477271 --- /dev/null +++ b/src/motion/BMX160Sensor.h @@ -0,0 +1,41 @@ +#pragma once + +#ifndef _BMX160_SENSOR_H_ +#define _BMX160_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#ifdef RAK_4631 + +#include "Fusion/Fusion.h" +#include + +class BMX160Sensor : public MotionSensor +{ + private: + RAK_BMX160 sensor; + bool showingScreen = false; + float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + + public: + explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#else + +// Stub +class BMX160Sensor : public MotionSensor +{ + public: + explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice); +}; + +#endif + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp new file mode 100755 index 000000000..d16968e06 --- /dev/null +++ b/src/motion/ICM20948Sensor.cpp @@ -0,0 +1,186 @@ +#include "ICM20948Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +// Flag when an interrupt has been detected +volatile static bool ICM20948_IRQ = false; + +// Interrupt service routine +void ICM20948SetInterrupt() +{ + ICM20948_IRQ = true; +} + +ICM20948Sensor::ICM20948Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool ICM20948Sensor::init() +{ + // Initialise the sensor + sensor = ICM20948Singleton::GetInstance(); + if (!sensor->init(device)) + return false; + + // Enable simple Wake on Motion + return sensor->setWakeOnMotion(); +} + +#ifdef ICM_20948_INT_PIN + +int32_t ICM20948Sensor::runOnce() +{ + // Wake on motion using hardware interrupts - this is the most efficient way to check for motion + if (ICM20948_IRQ) { + ICM20948_IRQ = false; + sensor->clearInterrupts(); + wakeScreen(); + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#else + +int32_t ICM20948Sensor::runOnce() +{ + // Wake on motion using polling - this is not as efficient as using hardware interrupt pin (see above) + auto status = sensor->setBank(0); + if (sensor->status != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::isWakeOnMotion failed to set bank - %s\n", sensor->statusString()); + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + ICM_20948_INT_STATUS_t int_stat; + status = sensor->read(AGB0_REG_INT_STATUS, (uint8_t *)&int_stat, sizeof(ICM_20948_INT_STATUS_t)); + if (status != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::isWakeOnMotion failed to read interrupts - %s\n", sensor->statusString()); + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + if (int_stat.WOM_INT != 0) { + // Wake up! + wakeScreen(); + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif + +// ---------------------------------------------------------------------- +// ICM20948Singleton +// ---------------------------------------------------------------------- + +// Get a singleton wrapper for an Sparkfun ICM_20948_I2C +ICM20948Singleton *ICM20948Singleton::GetInstance() +{ + if (pinstance == nullptr) { + pinstance = new ICM20948Singleton(); + } + return pinstance; +} + +ICM20948Singleton::ICM20948Singleton() {} + +ICM20948Singleton::~ICM20948Singleton() {} + +ICM20948Singleton *ICM20948Singleton::pinstance{nullptr}; + +// Initialise the ICM20948 Sensor +bool ICM20948Singleton::init(ScanI2C::FoundDevice device) +{ +#ifdef ICM_20948_DEBUG + // Set ICM_20948_DEBUG to enable helpful debug messages on Serial + enableDebugging(); +#endif + +// startup +#ifdef Wire1 + ICM_20948_Status_e status = + begin(device.address.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire, device.address.address == ICM20948_ADDR ? 1 : 0); +#else + ICM_20948_Status_e status = begin(Wire, device.address.address == ICM20948_ADDR ? 1 : 0); +#endif + if (status != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init begin - %s\n", statusString()); + return false; + } + + // SW reset to make sure the device starts in a known state + if (swReset() != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init reset - %s\n", statusString()); + return false; + } + delay(200); + + // Now wake the sensor up + if (sleep(false) != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init wake - %s\n", statusString()); + return false; + } + + if (lowPower(false) != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init high power - %s\n", statusString()); + return false; + } + +#ifdef ICM_20948_INT_PIN + + // Active low + cfgIntActiveLow(true); + LOG_DEBUG("ICM20948Sensor::init set cfgIntActiveLow - %s\n", statusString()); + + // Push-pull + cfgIntOpenDrain(false); + LOG_DEBUG("ICM20948Sensor::init set cfgIntOpenDrain - %s\n", statusString()); + + // If enabled, *ANY* read will clear the INT_STATUS register. + cfgIntAnyReadToClear(true); + LOG_DEBUG("ICM20948Sensor::init set cfgIntAnyReadToClear - %s\n", statusString()); + + // Latch the interrupt until cleared + cfgIntLatch(true); + LOG_DEBUG("ICM20948Sensor::init set cfgIntLatch - %s\n", statusString()); + + // Set up an interrupt pin with an internal pullup for active low + pinMode(ICM_20948_INT_PIN, INPUT_PULLUP); + + // Set up an interrupt service routine + attachInterrupt(ICM_20948_INT_PIN, ICM20948SetInterrupt, FALLING); + +#endif + return true; +} + +#ifdef ICM_20948_DMP_IS_ENABLED + +// Stub +bool ICM20948Sensor::initDMP() +{ + return false; +} + +#endif + +bool ICM20948Singleton::setWakeOnMotion() +{ + // Set WoM threshold in milli G's + auto status = WOMThreshold(ICM_20948_WOM_THRESHOLD); + if (status != ICM_20948_Stat_Ok) + return false; + + // Enable WoM Logic mode 1 = Compare the current sample with the previous sample + status = WOMLogic(true, 1); + LOG_DEBUG("ICM20948Sensor::init set WOMLogic - %s\n", statusString()); + if (status != ICM_20948_Stat_Ok) + return false; + + // Enable interrupts on WakeOnMotion + status = intEnableWOM(true); + LOG_DEBUG("ICM20948Sensor::init set intEnableWOM - %s\n", statusString()); + return status == ICM_20948_Stat_Ok; + + // Clear any current interrupts + ICM20948_IRQ = false; + clearInterrupts(); + return true; +} + +#endif \ No newline at end of file diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h new file mode 100755 index 000000000..d5e246c8d --- /dev/null +++ b/src/motion/ICM20948Sensor.h @@ -0,0 +1,96 @@ +#pragma once +#ifndef _ICM_20948_SENSOR_H_ +#define _ICM_20948_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include + +// Set the default gyro scale - dps250, dps500, dps1000, dps2000 +#ifndef ICM_20948_MPU_GYRO_SCALE +#define ICM_20948_MPU_GYRO_SCALE dps250 +#endif + +// Set the default accelerometer scale - gpm2, gpm4, gpm8, gpm16 +#ifndef ICM_20948_MPU_ACCEL_SCALE +#define ICM_20948_MPU_ACCEL_SCALE gpm2 +#endif + +// Define a threshold for Wake on Motion Sensing (0mg to 1020mg) +#ifndef ICM_20948_WOM_THRESHOLD +#define ICM_20948_WOM_THRESHOLD 16U +#endif + +// Define a pin in variant.h to use interrupts to read the ICM-20948 +#ifndef ICM_20948_WOM_THRESHOLD +#define ICM_20948_INT_PIN 255 +#endif + +// Uncomment this line to enable helpful debug messages on Serial +// #define ICM_20948_DEBUG 1 + +// Uncomment this line to enable the onboard digital motion processor (to be added in a future PR) +// #define ICM_20948_DMP_IS_ENABLED 1 + +// Check for a mandatory compiler flag to use the DMP (to be added in a future PR) +#ifdef ICM_20948_DMP_IS_ENABLED +#ifndef ICM_20948_USE_DMP +#error To use the digital motion processor, please either set the compiler flag ICM_20948_USE_DMP or uncomment line 29 (#define ICM_20948_USE_DMP) in ICM_20948_C.h +#endif +#endif + +// The I2C address of the Accelerometer (if found) from main.cpp +extern ScanI2C::DeviceAddress accelerometer_found; + +// Singleton wrapper for the Sparkfun ICM_20948_I2C class +class ICM20948Singleton : public ICM_20948_I2C +{ + private: + static ICM20948Singleton *pinstance; + + protected: + ICM20948Singleton(); + ~ICM20948Singleton(); + + public: + // Create a singleton instance (not thread safe) + static ICM20948Singleton *GetInstance(); + + // Singletons should not be cloneable. + ICM20948Singleton(ICM20948Singleton &other) = delete; + + // Singletons should not be assignable. + void operator=(const ICM20948Singleton &) = delete; + + // Initialise the motion sensor singleton for normal operation + bool init(ScanI2C::FoundDevice device); + + // Enable Wake on Motion interrupts (sensor must be initialised first) + bool setWakeOnMotion(); + +#ifdef ICM_20948_DMP_IS_ENABLED + // Initialise the motion sensor singleton for digital motion processing + bool initDMP(); +#endif +}; + +class ICM20948Sensor : public MotionSensor +{ + private: + ICM20948Singleton *sensor = nullptr; + + public: + explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice); + + // Initialise the motion sensor + virtual bool init() override; + + // Called each time our sensor gets a chance to run + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/LIS3DHSensor.cpp b/src/motion/LIS3DHSensor.cpp new file mode 100755 index 000000000..e2df60b1c --- /dev/null +++ b/src/motion/LIS3DHSensor.cpp @@ -0,0 +1,36 @@ +#include "LIS3DHSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +LIS3DHSensor::LIS3DHSensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool LIS3DHSensor::init() +{ + if (sensor.begin(deviceAddress())) { + sensor.setRange(LIS3DH_RANGE_2_G); + // Adjust threshold, higher numbers are less sensitive + sensor.setClick(config.device.double_tap_as_button_press ? 2 : 1, MOTION_SENSOR_CHECK_INTERVAL_MS); + LOG_DEBUG("LIS3DHSensor::init ok\n"); + return true; + } + LOG_DEBUG("LIS3DHSensor::init failed\n"); + return false; +} + +int32_t LIS3DHSensor::runOnce() +{ + if (sensor.getClick() > 0) { + uint8_t click = sensor.getClick(); + if (!config.device.double_tap_as_button_press) { + wakeScreen(); + } + + if (config.device.double_tap_as_button_press && (click & 0x20)) { + buttonPress(); + return 500; + } + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/LIS3DHSensor.h b/src/motion/LIS3DHSensor.h new file mode 100755 index 000000000..603d195a8 --- /dev/null +++ b/src/motion/LIS3DHSensor.h @@ -0,0 +1,24 @@ +#pragma once +#ifndef _LIS3DH_SENSOR_H_ +#define _LIS3DH_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include + +class LIS3DHSensor : public MotionSensor +{ + private: + Adafruit_LIS3DH sensor; + + public: + explicit LIS3DHSensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/LSM6DS3Sensor.cpp b/src/motion/LSM6DS3Sensor.cpp new file mode 100755 index 000000000..64ef9a23b --- /dev/null +++ b/src/motion/LSM6DS3Sensor.cpp @@ -0,0 +1,33 @@ +#include "LSM6DS3Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +LSM6DS3Sensor::LSM6DS3Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool LSM6DS3Sensor::init() +{ + if (sensor.begin_I2C(deviceAddress())) { + + // Default threshold of 2G, less sensitive options are 4, 8 or 16G + sensor.setAccelRange(LSM6DS_ACCEL_RANGE_2_G); + + // Duration is number of occurances needed to trigger, higher threshold is less sensitive + sensor.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH); + + LOG_DEBUG("LSM6DS3Sensor::init ok\n"); + return true; + } + LOG_DEBUG("LSM6DS3Sensor::init failed\n"); + return false; +} + +int32_t LSM6DS3Sensor::runOnce() +{ + if (sensor.shake()) { + wakeScreen(); + return 500; + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/LSM6DS3Sensor.h b/src/motion/LSM6DS3Sensor.h new file mode 100755 index 000000000..77069ef3c --- /dev/null +++ b/src/motion/LSM6DS3Sensor.h @@ -0,0 +1,28 @@ +#pragma once +#ifndef _LSM6DS3_SENSOR_H_ +#define _LSM6DS3_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#ifndef LSM6DS3_WAKE_THRESH +#define LSM6DS3_WAKE_THRESH 20 +#endif + +#include + +class LSM6DS3Sensor : public MotionSensor +{ + private: + Adafruit_LSM6DS3TRC sensor; + + public: + explicit LSM6DS3Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/MPU6050Sensor.cpp b/src/motion/MPU6050Sensor.cpp new file mode 100755 index 000000000..77aaca46d --- /dev/null +++ b/src/motion/MPU6050Sensor.cpp @@ -0,0 +1,31 @@ +#include "MPU6050Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +MPU6050Sensor::MPU6050Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool MPU6050Sensor::init() +{ + if (sensor.begin(deviceAddress())) { + // setup motion detection + sensor.setHighPassFilter(MPU6050_HIGHPASS_0_63_HZ); + sensor.setMotionDetectionThreshold(1); + sensor.setMotionDetectionDuration(20); + sensor.setInterruptPinLatch(true); // Keep it latched. Will turn off when reinitialized. + sensor.setInterruptPinPolarity(true); + LOG_DEBUG("MPU6050Sensor::init ok\n"); + return true; + } + LOG_DEBUG("MPU6050Sensor::init failed\n"); + return false; +} + +int32_t MPU6050Sensor::runOnce() +{ + if (sensor.getMotionInterruptStatus()) { + wakeScreen(); + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/MPU6050Sensor.h b/src/motion/MPU6050Sensor.h new file mode 100755 index 000000000..2e6eafecd --- /dev/null +++ b/src/motion/MPU6050Sensor.h @@ -0,0 +1,24 @@ +#pragma once +#ifndef _MPU6050_SENSOR_H_ +#define _MPU6050_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include + +class MPU6050Sensor : public MotionSensor +{ + private: + Adafruit_MPU6050 sensor; + + public: + explicit MPU6050Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp new file mode 100755 index 000000000..20e396bba --- /dev/null +++ b/src/motion/MotionSensor.cpp @@ -0,0 +1,79 @@ +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +// screen is defined in main.cpp +extern graphics::Screen *screen; + +MotionSensor::MotionSensor(ScanI2C::FoundDevice foundDevice) +{ + device.address.address = foundDevice.address.address; + device.address.port = foundDevice.address.port; + device.type = foundDevice.type; + LOG_DEBUG("MotionSensor::MotionSensor port: %s address: 0x%x type: %d\n", + devicePort() == ScanI2C::I2CPort::WIRE1 ? "Wire1" : "Wire", (uint8_t)deviceAddress(), deviceType()); +} + +ScanI2C::DeviceType MotionSensor::deviceType() +{ + return device.type; +} + +uint8_t MotionSensor::deviceAddress() +{ + return device.address.address; +} + +ScanI2C::I2CPort MotionSensor::devicePort() +{ + return device.address.port; +} + +#ifdef RAK_4631 +void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // int x_offset = display->width() / 2; + // int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + display->drawString(x, y, "Calibrating\nCompass"); + int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); + + // coordinates for the center of the compass/circle + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + display->getHeight() / 2; + } else { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; + } + display->drawCircle(compassX, compassY, compassDiam / 2); + screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); +} +#endif + +#if !MESHTASTIC_EXCLUDE_POWER_FSM +void MotionSensor::wakeScreen() +{ + if (powerFSM.getState() == &stateDARK) { + LOG_DEBUG("MotionSensor::wakeScreen detected\n"); + powerFSM.trigger(EVENT_INPUT); + } +} + +void MotionSensor::buttonPress() +{ + LOG_DEBUG("MotionSensor::buttonPress detected\n"); + powerFSM.trigger(EVENT_PRESS); +} + +#else + +void MotionSensor::wakeScreen() {} + +void MotionSensor::buttonPress() {} + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h new file mode 100755 index 000000000..0f7f3479b --- /dev/null +++ b/src/motion/MotionSensor.h @@ -0,0 +1,85 @@ +#pragma once +#ifndef _MOTION_SENSOR_H_ +#define _MOTION_SENSOR_H_ + +#define MOTION_SENSOR_CHECK_INTERVAL_MS 100 +#define MOTION_SENSOR_CLICK_THRESHOLD 40 + +#include "../configuration.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include "../PowerFSM.h" +#include "../detect/ScanI2C.h" +#include "../graphics/Screen.h" +#include "../graphics/ScreenFonts.h" +#include "../power.h" + +// Base class for motion processing +class MotionSensor +{ + public: + explicit MotionSensor(ScanI2C::FoundDevice foundDevice); + virtual ~MotionSensor(){}; + + // Get the device type + ScanI2C::DeviceType deviceType(); + + // Get the device address + uint8_t deviceAddress(); + + // Get the device port + ScanI2C::I2CPort devicePort(); + + // Initialise the motion sensor + inline virtual bool init() { return false; }; + + // The method that will be called each time our sensor gets a chance to run + // Returns the desired period for next invocation (or RUN_SAME for no change) + // Refer to /src/concurrency/OSThread.h for more information + inline virtual int32_t runOnce() { return MOTION_SENSOR_CHECK_INTERVAL_MS; }; + + protected: + // Turn on the screen when a tap or motion is detected + virtual void wakeScreen(); + + // Register a button press when a double-tap is detected + virtual void buttonPress(); + +#ifdef RAK_4631 + // draw an OLED frame (currently only used by the RAK4631 BMX160 sensor) + static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +#endif + + ScanI2C::FoundDevice device; +}; + +namespace MotionSensorI2C +{ + +static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) +{ + Wire.beginTransmission(address); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)address, (uint8_t)len); + uint8_t i = 0; + while (Wire.available()) { + data[i++] = Wire.read(); + } + return 0; // Pass +} + +static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) +{ + Wire.beginTransmission(address); + Wire.write(reg); + Wire.write(data, len); + return (0 != Wire.endTransmission()); +} + +} // namespace MotionSensorI2C + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/STK8XXXSensor.cpp b/src/motion/STK8XXXSensor.cpp new file mode 100755 index 000000000..d4d69ef99 --- /dev/null +++ b/src/motion/STK8XXXSensor.cpp @@ -0,0 +1,40 @@ +#include "STK8XXXSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +STK8XXXSensor::STK8XXXSensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +#ifdef STK8XXX_INT + +volatile static bool STK_IRQ; + +bool STK8XXXSensor::init() +{ + if (sensor.STK8xxx_Initialization(STK8xxx_VAL_RANGE_2G)) { + STK_IRQ = false; + sensor.STK8xxx_Anymotion_init(); + pinMode(STK8XXX_INT, INPUT_PULLUP); + attachInterrupt( + digitalPinToInterrupt(STK8XXX_INT), [] { STK_IRQ = true; }, RISING); + + LOG_DEBUG("STK8XXXSensor::init ok\n"); + return true; + } + LOG_DEBUG("STK8XXXSensor::init failed\n"); + return false; +} + +int32_t STK8XXXSensor::runOnce() +{ + if (STK_IRQ) { + STK_IRQ = false; + if (config.display.wake_on_tap_or_motion) { + wakeScreen(); + } + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/STK8XXXSensor.h b/src/motion/STK8XXXSensor.h new file mode 100755 index 000000000..190b916b4 --- /dev/null +++ b/src/motion/STK8XXXSensor.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef _STK8XXX_SENSOR_H_ +#define _STK8XXX_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#ifdef STK8XXX_INT + +#include + +class STK8XXXSensor : public MotionSensor +{ + private: + STK8xxx sensor; + + public: + explicit STK8XXXSensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#else + +// Stub +class STK8XXXSensor : public MotionSensor +{ + public: + explicit STK8XXXSensor(ScanI2C::FoundDevice foundDevice); +}; + +#endif + +#endif + +#endif \ No newline at end of file From 9d7938f57047b8af0bea7cf9422029bd518133d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 06:25:56 -0500 Subject: [PATCH 1241/1377] [create-pull-request] automated change (#4865) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- .../generated/meshtastic/module_config.pb.cpp | 1 + .../generated/meshtastic/module_config.pb.h | 45 ++++++++++++++----- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/protobufs b/protobufs index eb915e71f..6ac91926c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit eb915e71fc907bef97d98760aa4c6c18698b6c32 +Subproject commit 6ac91926c201c15c429cb5d41684f0f40cb7dce8 diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index 88a771d5b..e1c33e2c1 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -60,3 +60,4 @@ PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) + diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 2e1985660..d4b82c93b 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -19,6 +19,23 @@ typedef enum _meshtastic_RemoteHardwarePinType { meshtastic_RemoteHardwarePinType_DIGITAL_WRITE = 2 } meshtastic_RemoteHardwarePinType; +typedef enum _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType { + /* Event is triggered if pin is low */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_LOW = 0, + /* Event is triggered if pin is high */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH = 1, + /* Event is triggered when pin goes high to low */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_FALLING_EDGE = 2, + /* Event is triggered when pin goes low to high */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_RISING_EDGE = 3, + /* Event is triggered on every pin state change, low is considered to be + "active" */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_LOW = 4, + /* Event is triggered on every pin state change, high is considered to be + "active" */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH = 5 +} meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType; + /* Baudrate for codec2 voice */ typedef enum _meshtastic_ModuleConfig_AudioConfig_Audio_Baud { meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_DEFAULT = 0, @@ -144,11 +161,13 @@ typedef struct _meshtastic_ModuleConfig_NeighborInfoConfig { typedef struct _meshtastic_ModuleConfig_DetectionSensorConfig { /* Whether the Module is enabled */ bool enabled; - /* Interval in seconds of how often we can send a message to the mesh when a state change is detected */ + /* Interval in seconds of how often we can send a message to the mesh when a + trigger event is detected */ uint32_t minimum_broadcast_secs; - /* Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes - When set to 0, only state changes will be broadcasted - Works as a sort of status heartbeat for peace of mind */ + /* Interval in seconds of how often we should send a message to the mesh + with the current state regardless of trigger events When set to 0, only + trigger events will be broadcasted Works as a sort of status heartbeat + for peace of mind */ uint32_t state_broadcast_secs; /* Send ASCII bell with alert message Useful for triggering ext. notification on bell */ @@ -159,9 +178,8 @@ typedef struct _meshtastic_ModuleConfig_DetectionSensorConfig { char name[20]; /* GPIO pin to monitor for state changes */ uint8_t monitor_pin; - /* Whether or not the GPIO pin state detection is triggered on HIGH (1) - Otherwise LOW (0) */ - bool detection_triggered_high; + /* The type of trigger event to be used */ + meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType detection_trigger_type; /* Whether or not use INPUT_PULLUP mode for GPIO pin Only applicable if the board uses pull-up resistors on the pin */ bool use_pullup; @@ -427,6 +445,10 @@ extern "C" { #define _meshtastic_RemoteHardwarePinType_MAX meshtastic_RemoteHardwarePinType_DIGITAL_WRITE #define _meshtastic_RemoteHardwarePinType_ARRAYSIZE ((meshtastic_RemoteHardwarePinType)(meshtastic_RemoteHardwarePinType_DIGITAL_WRITE+1)) +#define _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_LOW +#define _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MAX meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH +#define _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_ARRAYSIZE ((meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType)(meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH+1)) + #define _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_DEFAULT #define _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MAX meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700B #define _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_AudioConfig_Audio_Baud)(meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700B+1)) @@ -448,6 +470,7 @@ extern "C" { +#define meshtastic_ModuleConfig_DetectionSensorConfig_detection_trigger_type_ENUMTYPE meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType #define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud @@ -473,7 +496,7 @@ extern "C" { #define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0} -#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0} +#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0} #define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} @@ -489,7 +512,7 @@ extern "C" { #define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0} -#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0} +#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0} #define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} @@ -523,7 +546,7 @@ extern "C" { #define meshtastic_ModuleConfig_DetectionSensorConfig_send_bell_tag 4 #define meshtastic_ModuleConfig_DetectionSensorConfig_name_tag 5 #define meshtastic_ModuleConfig_DetectionSensorConfig_monitor_pin_tag 6 -#define meshtastic_ModuleConfig_DetectionSensorConfig_detection_triggered_high_tag 7 +#define meshtastic_ModuleConfig_DetectionSensorConfig_detection_trigger_type_tag 7 #define meshtastic_ModuleConfig_DetectionSensorConfig_use_pullup_tag 8 #define meshtastic_ModuleConfig_AudioConfig_codec2_enabled_tag 1 #define meshtastic_ModuleConfig_AudioConfig_ptt_pin_tag 2 @@ -688,7 +711,7 @@ X(a, STATIC, SINGULAR, UINT32, state_broadcast_secs, 3) \ X(a, STATIC, SINGULAR, BOOL, send_bell, 4) \ X(a, STATIC, SINGULAR, STRING, name, 5) \ X(a, STATIC, SINGULAR, UINT32, monitor_pin, 6) \ -X(a, STATIC, SINGULAR, BOOL, detection_triggered_high, 7) \ +X(a, STATIC, SINGULAR, UENUM, detection_trigger_type, 7) \ X(a, STATIC, SINGULAR, BOOL, use_pullup, 8) #define meshtastic_ModuleConfig_DetectionSensorConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DetectionSensorConfig_DEFAULT NULL From d1138d51e56c14320edadcb31c0f7b116bbb1367 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 25 Sep 2024 23:27:04 +1200 Subject: [PATCH 1242/1377] Wrangle module frames with I2C keyboard (#4817) * Only suppress UI nav if module using keyboard input * CardKB combo to dismiss text message and waypoint Currently assigned to Fn + Delete --- src/graphics/Screen.cpp | 58 +++++++++++++++++++++++++---- src/graphics/Screen.h | 8 +++- src/input/InputBroker.h | 1 + src/input/kbI2cBase.cpp | 1 + src/mesh/MeshModule.h | 3 +- src/modules/CannedMessageModule.cpp | 20 ++++++++++ src/modules/CannedMessageModule.h | 1 + 7 files changed, 82 insertions(+), 10 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 19b20e8dc..f8a64cc61 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -48,6 +48,7 @@ along with this program. If not, see . #include "modules/AdminModule.h" #include "modules/ExternalNotificationModule.h" #include "modules/TextMessageModule.h" +#include "modules/WaypointModule.h" #include "sleep.h" #include "target_specific.h" @@ -2112,8 +2113,13 @@ void Screen::setFrames(FrameFocus focus) // Check if the module being drawn has requested focus // We will honor this request later, if setFrames was triggered by a UIFrameEvent MeshModule *m = *i; - if (m->isRequestingFocus()) + if (m->isRequestingFocus()) { fsi.positions.focusedModule = numframes; + } + + // Identify the position of specific modules, if we need to know this later + if (m == waypointModule) + fsi.positions.waypoint = numframes; numframes++; } @@ -2132,8 +2138,8 @@ void Screen::setFrames(FrameFocus focus) #endif // If we have a text message - show it next, unless it's a phone message and we aren't using any special modules - fsi.positions.textMessage = numframes; if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) { + fsi.positions.textMessage = numframes; normalFrames[numframes++] = drawTextMessageFrame; } @@ -2235,6 +2241,31 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) setFastFramerate(); } +// Dismisses the currently displayed screen frame, if possible +// Relevant for text message, waypoint, others in future? +// Triggered with a CardKB keycombo +void Screen::dismissCurrentFrame() +{ + uint8_t currentFrame = ui->getUiState()->currentFrame; + bool dismissed = false; + + if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) { + LOG_INFO("Dismissing Text Message\n"); + devicestate.has_rx_text_message = false; + dismissed = true; + } + + else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) { + LOG_DEBUG("Dismissing Waypoint\n"); + devicestate.has_rx_waypoint = false; + dismissed = true; + } + + // If we did make changes to dismiss, we now need to regenerate the frameset + if (dismissed) + setFrames(); +} + void Screen::handleStartFirmwareUpdateScreen() { LOG_DEBUG("showing firmware screen\n"); @@ -2747,12 +2778,23 @@ int Screen::handleInputEvent(const InputEvent *event) } #endif - if (showingNormalScreen && moduleFrames.size() == 0) { - // LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); - if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { - showPrevFrame(); - } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { - showNextFrame(); + // Use left or right input from a keyboard to move between frames, + // so long as a mesh module isn't using these events for some other purpose + if (showingNormalScreen) { + + // Ask any MeshModules if they're handling keyboard input right now + bool inputIntercepted = false; + for (MeshModule *module : moduleFrames) { + if (module->interceptingKeyboardInput()) + inputIntercepted = true; + } + + // If no modules are using the input, move between frames + if (!inputIntercepted) { + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) + showPrevFrame(); + else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) + showNextFrame(); } } diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 021b11bda..b2e6e905b 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -454,6 +454,9 @@ class Screen : public concurrency::OSThread void setWelcomeFrames(); + // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint) + void dismissCurrentFrame(); + #ifdef USE_EINK /// Draw an image to remain on E-Ink display after screen off void setScreensaverFrames(FrameCallback einkScreensaver = NULL); @@ -503,11 +506,14 @@ class Screen : public concurrency::OSThread void handleStartFirmwareUpdateScreen(); // Info collected by setFrames method. - // Index location of specific frames. Used to apply the FrameFocus parameter of setFrames + // Index location of specific frames. + // - Used to apply the FrameFocus parameter of setFrames + // - Used to dismiss the currently shown frame (txt; waypoint) by CardKB combo struct FramesetInfo { struct FramePositions { uint8_t fault = 0; uint8_t textMessage = 0; + uint8_t waypoint = 0; uint8_t focusedModule = 0; uint8_t log = 0; uint8_t settings = 0; diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 082268f0a..17c621c8a 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -11,6 +11,7 @@ #define INPUT_BROKER_MSG_GPS_TOGGLE 0x9e #define INPUT_BROKER_MSG_MUTE_TOGGLE 0xac #define INPUT_BROKER_MSG_SEND_PING 0xaf +#define INPUT_BROKER_MSG_DISMISS_FRAME 0x8b #define INPUT_BROKER_MSG_LEFT 0xb4 #define INPUT_BROKER_MSG_UP 0xb5 #define INPUT_BROKER_MSG_DOWN 0xb6 diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 1d8154bcf..4fbca76e5 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -296,6 +296,7 @@ int32_t KbI2cBase::runOnce() case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE case 0x9e: // fn+g INPUT_BROKER_MSG_GPS_TOGGLE case 0xaf: // fn+space INPUT_BROKER_MSG_SEND_PING + case 0x8b: // fn+del INPUT_BROKEN_MSG_DISMISS_FRAME // just pass those unmodified e.inputEvent = ANYKEY; e.kbchar = c; diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index c341b301a..7929ba972 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -79,7 +79,8 @@ class MeshModule meshtastic_AdminMessage *response); #if HAS_SCREEN virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } - virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset + virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset + virtual bool interceptingKeyboardInput() { return false; } // Can screen use keyboard for nav, or is module handling input? #endif protected: const char *name; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index a1b9c4dc0..d4d112f90 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -276,6 +276,13 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) showTemporaryMessage("Node Info \nUpdate Sent"); } break; + case INPUT_BROKER_MSG_DISMISS_FRAME: // fn+del: dismiss screen frames like text or waypoint + // Avoid opening the canned message screen frame + // We're only handling the keypress here by convention, this has nothing to do with canned messages + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + // Attempt to close whatever frame is currently shown on display + screen->dismissCurrentFrame(); + return 0; default: // pass the pressed key // LOG_DEBUG("Canned message ANYKEY (%x)\n", event->kbchar); @@ -935,6 +942,19 @@ void CannedMessageModule::drawEnterIcon(OLEDDisplay *display, int x, int y, floa #endif +// Indicate to screen class that module is handling keyboard input specially (at certain times) +// This prevents the left & right keys being used for nav. between screen frames during text entry. +bool CannedMessageModule::interceptingKeyboardInput() +{ + switch (runState) { + case CANNED_MESSAGE_RUN_STATE_DISABLED: + case CANNED_MESSAGE_RUN_STATE_INACTIVE: + return false; + default: + return true; + } +} + void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { char buffer[50]; diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 368574c40..e9dc2bda0 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -114,6 +114,7 @@ class CannedMessageModule : public SinglePortModule, public ObservableshouldDraw(); } virtual Observable *getUIFrameObservable() override { return this; } virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; + virtual bool interceptingKeyboardInput() override; virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) override; From a7c379961a8606be6101c3ee0d1954d53bc61139 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 07:01:15 -0500 Subject: [PATCH 1243/1377] New detection sensor trigger type value --- src/mesh/NodeDB.cpp | 2 +- src/modules/DetectionSensorModule.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 2820cafd4..869729890 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -472,7 +472,7 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_detection_sensor = true; moduleConfig.detection_sensor.enabled = false; - moduleConfig.detection_sensor.detection_triggered_high = true; + moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; moduleConfig.detection_sensor.minimum_broadcast_secs = 45; moduleConfig.has_ambient_lighting = true; diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index 670fd3208..cb63b2c1a 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -108,5 +108,5 @@ bool DetectionSensorModule::hasDetectionEvent() { bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin); // LOG_DEBUG("Detection Sensor Module: Current state: %i\n", currentState); - return moduleConfig.detection_sensor.detection_triggered_high ? currentState : !currentState; + return moduleConfig.detection_sensor.detection_trigger_type ? currentState : !currentState; } \ No newline at end of file From 6e1616375e3406f5969ab5a1b83723f4293c7da6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 07:25:45 -0500 Subject: [PATCH 1244/1377] Trunk update --- .trunk/trunk.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index b20a1ec95..7905ed355 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,27 +8,27 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.81.9 + - trufflehog@3.82.4 - yamllint@1.35.1 - - bandit@1.7.9 - - checkov@3.2.238 + - bandit@1.7.10 + - checkov@3.2.255 - terrascan@1.19.1 - - trivy@0.54.1 + - trivy@0.55.2 #- trufflehog@3.63.2-rc0 - taplo@0.9.3 - - ruff@0.6.2 + - ruff@0.6.7 - isort@5.13.2 - - markdownlint@0.41.0 + - markdownlint@0.42.0 - oxipng@9.1.2 - svgo@3.3.2 - - actionlint@1.7.1 + - actionlint@1.7.2 - flake8@7.1.1 - hadolint@2.12.0 - shfmt@3.6.0 - shellcheck@0.10.0 - black@24.8.0 - git-diff-check - - gitleaks@8.18.4 + - gitleaks@8.19.2 - clang-format@16.0.3 - prettier@3.3.3 ignore: From 9dd769586f1444b9c83ae17132873046c3316a32 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 08:40:33 -0500 Subject: [PATCH 1245/1377] Version --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 7905ed355..b19f96bb5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.22.3 + version: 1.22.5 plugins: sources: - id: trunk From 26112ba0017606704f06127717810332ba3ee6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 25 Sep 2024 18:56:17 +0200 Subject: [PATCH 1246/1377] Support T3S3 LR1121 variant --- src/FSCommon.cpp | 6 +++--- src/detect/LoRaRadioType.h | 3 ++- src/main.cpp | 31 ++++++++++++++++++++------- src/mesh/InterfacesTemplates.cpp | 1 + src/mesh/LR1120Interface.cpp | 5 +++++ src/mesh/LR1120Interface.h | 1 + src/mesh/LR1121Interface.cpp | 14 +++++++++++++ src/mesh/LR1121Interface.h | 14 +++++++++++++ src/mesh/LR11x0Interface.cpp | 36 ++++++++++++++++++++++++++------ variants/tlora_t3s3_v1/variant.h | 17 +++++++++++++++ 10 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 src/mesh/LR1121Interface.cpp create mode 100644 src/mesh/LR1121Interface.h diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index d6a542808..24d3f477e 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -370,8 +370,8 @@ void setupSDCard() } uint64_t cardSize = SD.cardSize() / (1024 * 1024); - LOG_DEBUG("SD Card Size: %lluMB\n", cardSize); - LOG_DEBUG("Total space: %llu MB\n", SD.totalBytes() / (1024 * 1024)); - LOG_DEBUG("Used space: %llu MB\n", SD.usedBytes() / (1024 * 1024)); + LOG_DEBUG("SD Card Size: %lu MB\n", (uint32_t)cardSize); + LOG_DEBUG("Total space: %lu MB\n", (uint32_t)(SD.totalBytes() / (1024 * 1024))); + LOG_DEBUG("Used space: %lu MB\n", (uint32_t)(SD.usedBytes() / (1024 * 1024))); #endif } \ No newline at end of file diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h index 3975153b5..a059a3668 100644 --- a/src/detect/LoRaRadioType.h +++ b/src/detect/LoRaRadioType.h @@ -10,7 +10,8 @@ enum LoRaRadioType { LLCC68_RADIO, SX1280_RADIO, LR1110_RADIO, - LR1120_RADIO + LR1120_RADIO, + LR1121_RADIO }; extern LoRaRadioType radioType; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c2d275177..fa032a209 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,6 +71,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "LLCC68Interface.h" #include "LR1110Interface.h" #include "LR1120Interface.h" +#include "LR1121Interface.h" #include "RF95Interface.h" #include "SX1262Interface.h" #include "SX1268Interface.h" @@ -880,7 +881,7 @@ void setup() #endif #if defined(RF95_IRQ) - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); if (!rIf->init()) { LOG_WARN("Failed to find RF95 radio\n"); @@ -894,7 +895,7 @@ void setup() #endif #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { LOG_WARN("Failed to find SX1262 radio\n"); @@ -908,7 +909,7 @@ void setup() #endif #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // Try using the specified TCXO voltage rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { @@ -924,7 +925,7 @@ void setup() } } - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // If specified TCXO voltage fails, attempt to use DIO3 as a reference instea rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { @@ -942,7 +943,7 @@ void setup() #endif #if defined(USE_SX1268) - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { LOG_WARN("Failed to find SX1268 radio\n"); @@ -956,7 +957,7 @@ void setup() #endif #if defined(USE_LLCC68) - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { LOG_WARN("Failed to find LLCC68 radio\n"); @@ -970,7 +971,7 @@ void setup() #endif #if defined(USE_LR1110) - if (!rIf) { + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESER_PIN, LR1110_BUSY_PIN); if (!rIf->init()) { LOG_WARN("Failed to find LR1110 radio\n"); @@ -978,6 +979,7 @@ void setup() rIf = NULL; } else { LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio\n"); + radioType = LR1110_RADIO; } } #endif @@ -991,6 +993,21 @@ void setup() rIf = NULL; } else { LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio\n"); + radioType = LR1120_RADIO; + } + } +#endif + +#if defined(USE_LR1121) + if (!rIf) { + rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESER_PIN, LR1121_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("Failed to find LR1121 radio\n"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1121 Radio init succeeded, using LR1121 radio\n"); + radioType = LR1121_RADIO; } } #endif diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index f2cac8028..329f0b48e 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -14,6 +14,7 @@ template class SX126xInterface; template class SX128xInterface; template class LR11x0Interface; template class LR11x0Interface; +template class LR11x0Interface; #ifdef ARCH_STM32WL template class SX126xInterface; #endif diff --git a/src/mesh/LR1120Interface.cpp b/src/mesh/LR1120Interface.cpp index 94f3568f7..07e9e508d 100644 --- a/src/mesh/LR1120Interface.cpp +++ b/src/mesh/LR1120Interface.cpp @@ -6,4 +6,9 @@ LR1120Interface::LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R RADIOLIB_PIN_TYPE busy) : LR11x0Interface(hal, cs, irq, rst, busy) { +} + +bool LR1120Interface::wideLora() +{ + return true; } \ No newline at end of file diff --git a/src/mesh/LR1120Interface.h b/src/mesh/LR1120Interface.h index fc59293ec..a03fa0b20 100644 --- a/src/mesh/LR1120Interface.h +++ b/src/mesh/LR1120Interface.h @@ -10,4 +10,5 @@ class LR1120Interface : public LR11x0Interface public: LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); + bool wideLora() override; }; \ No newline at end of file diff --git a/src/mesh/LR1121Interface.cpp b/src/mesh/LR1121Interface.cpp new file mode 100644 index 000000000..0d6bba6ea --- /dev/null +++ b/src/mesh/LR1121Interface.cpp @@ -0,0 +1,14 @@ +#include "LR1121Interface.h" +#include "configuration.h" +#include "error.h" + +LR1121Interface::LR1121Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) + : LR11x0Interface(hal, cs, irq, rst, busy) +{ +} + +bool LR1121Interface::wideLora() +{ + return true; +} \ No newline at end of file diff --git a/src/mesh/LR1121Interface.h b/src/mesh/LR1121Interface.h new file mode 100644 index 000000000..32a6f9492 --- /dev/null +++ b/src/mesh/LR1121Interface.h @@ -0,0 +1,14 @@ +#pragma once + +#include "LR11x0Interface.h" + +/** + * Our adapter for LR1121 wideband radios + */ +class LR1121Interface : public LR11x0Interface +{ + public: + LR1121Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy); + bool wideLora() override; +}; \ No newline at end of file diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index e237c354f..9e911107a 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -10,8 +10,14 @@ // Particular boards might define a different max power based on what their hardware can do, default to max power output if not // specified (may be dangerous if using external PA and LR11x0 power config forgotten) -#ifndef LR11X0_MAX_POWER -#define LR11X0_MAX_POWER 22 +#ifndef LR1110_MAX_POWER +#define LR1110_MAX_POWER 22 +#endif + +// the 2.4G part maxes at 13dBm + +#ifndef LR1120_MAX_POWER +#define LR1120_MAX_POWER 13 #endif template @@ -47,8 +53,12 @@ template bool LR11x0Interface::init() RadioLibInterface::init(); - if (power > LR11X0_MAX_POWER) // Clamp power to maximum defined level - power = LR11X0_MAX_POWER; + if (power > LR1110_MAX_POWER) // Clamp power to maximum defined level + power = LR1110_MAX_POWER; + + if ((power > LR1120_MAX_POWER) && + (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // clamp again if wide freq range + power = LR1120_MAX_POWER; limitPower(); @@ -65,6 +75,18 @@ template bool LR11x0Interface::init() {LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW}}, END_OF_MODE_TABLE, }; +#elif defined(TLORA_T3S3_V1) + static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, + RADIOLIB_NC}; + + static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, + }; + #else // set RF switch configuration for Wio WM1110 @@ -174,8 +196,10 @@ template bool LR11x0Interface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - if (power > LR11X0_MAX_POWER) // This chip has lower power limits than some - power = LR11X0_MAX_POWER; + if (power > LR1110_MAX_POWER) // This chip has lower power limits than some + power = LR1110_MAX_POWER; + if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit + power = LR1120_MAX_POWER; err = lora.setOutputPower(power); assert(err == RADIOLIB_ERR_NONE); diff --git a/variants/tlora_t3s3_v1/variant.h b/variants/tlora_t3s3_v1/variant.h index 8a1af1ec2..ca10ef5fb 100644 --- a/variants/tlora_t3s3_v1/variant.h +++ b/variants/tlora_t3s3_v1/variant.h @@ -24,6 +24,7 @@ #define USE_RF95 // RFM95/SX127x #define USE_SX1262 #define USE_SX1280 +#define USE_LR1121 #define LORA_SCK 5 #define LORA_MISO 3 @@ -60,3 +61,19 @@ #define SX128X_TXEN 10 #define SX128X_MAX_POWER 3 #endif + +// LR1121 +#ifdef USE_LR1121 +#define LR1121_IRQ_PIN 36 +#define LR1121_NRESER_PIN LORA_RESET +#define LR1121_BUSY_PIN LORA_DIO2 +#define LR1121_SPI_NSS_PIN LORA_CS +#define LR1121_SPI_SCK_PIN LORA_SCK +#define LR1121_SPI_MOSI_PIN LORA_MOSI +#define LR1121_SPI_MISO_PIN LORA_MISO +#define LR11X0_DIO3_TCXO_VOLTAGE 3.0 +#define LR11X0_DIO_AS_RF_SWITCH +#endif + +#define HAS_SDCARD // Have SPI interface SD card slot +#define SDCARD_USE_SPI1 \ No newline at end of file From ed4527cfa529e423cfbabf3990c48efcd713eba0 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Wed, 25 Sep 2024 14:44:05 +0200 Subject: [PATCH 1247/1377] address review comments --- src/modules/DetectionSensorModule.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index 285aba582..eb13616de 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -86,7 +86,8 @@ int32_t DetectionSensorModule::runOnce() // LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin)); - if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs)) { + if (!Throttle::isWithinTimespanMs(lastSentToMesh, + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs))) { bool isDetected = hasDetectionEvent(); DetectionSensorTriggerVerdict verdict = handlers[moduleConfig.detection_sensor.detection_trigger_type](wasDetected, isDetected); @@ -106,8 +107,9 @@ int32_t DetectionSensorModule::runOnce() // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && - (millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, - default_telemetry_broadcast_interval_secs)) { + !Throttle::isWithinTimespanMs(lastSentToMesh, + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, + default_telemetry_broadcast_interval_secs))) { sendCurrentStateMessage(hasDetectionEvent()); return DELAYED_INTERVAL; } From 4128d75ad4b2ab0f3d55040f85ea2f8a77991d7f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 13:50:00 -0500 Subject: [PATCH 1248/1377] IS_ONE_OF macro to make long chains of conditions more concise and easy to follow (#4860) * Is one of macro * Moar * Whoops * Trunk * isOneOf function backed macro --- src/gps/GPS.cpp | 15 +++++++-------- src/main.cpp | 7 ++++--- src/mesh/NodeDB.h | 1 + src/meshUtils.cpp | 15 +++++++++++++++ src/meshUtils.h | 9 ++++++++- src/modules/PositionModule.cpp | 5 +++-- src/modules/Telemetry/DeviceTelemetry.cpp | 5 +++-- src/platform/nrf52/main-nrf52.cpp | 9 +++++---- 8 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 285f3a2fd..b13ca4b06 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -7,6 +7,7 @@ #include "PowerMon.h" #include "RTC.h" #include "Throttle.h" +#include "meshUtils.h" #include "main.h" // pmu_found #include "sleep.h" @@ -511,7 +512,7 @@ bool GPS::setup() delay(250); _serial_gps->write("$CFGMSG,6,1,0\r\n"); delay(250); - } else if (gnssModel == GNSS_MODEL_AG3335 || gnssModel == GNSS_MODEL_AG3352) { + } else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) { _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC @@ -553,7 +554,7 @@ bool GPS::setup() } else { LOG_INFO("GNSS module configuration saved!\n"); } - } else if (gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || gnssModel == GNSS_MODEL_UBLOX9) { + } else if (IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9)) { if (gnssModel == GNSS_MODEL_UBLOX7) { LOG_DEBUG("Setting GPS+SBAS\n"); msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7); @@ -826,8 +827,7 @@ void GPS::setPowerPMU(bool on) void GPS::setPowerUBLOX(bool on, uint32_t sleepMs) { // Abort: if not UBLOX hardware - if (!(gnssModel == GNSS_MODEL_UBLOX6 || gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || - gnssModel == GNSS_MODEL_UBLOX9 || gnssModel == GNSS_MODEL_UBLOX10)) + if (!IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX6, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9, GNSS_MODEL_UBLOX10)) return; // If waking @@ -910,8 +910,7 @@ void GPS::down() // If not, fallback to GPS_HARDSLEEP instead bool softsleepSupported = false; // U-blox is supported via PMREQ - if (gnssModel == GNSS_MODEL_UBLOX6 || gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || - gnssModel == GNSS_MODEL_UBLOX9 || gnssModel == GNSS_MODEL_UBLOX10) + if (IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX6, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9, GNSS_MODEL_UBLOX10)) softsleepSupported = true; #ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin softsleepSupported = true; @@ -987,8 +986,8 @@ int32_t GPS::runOnce() setConnected(); } else { if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && - (gnssModel == GNSS_MODEL_UBLOX6 || gnssModel == GNSS_MODEL_UBLOX7 || gnssModel == GNSS_MODEL_UBLOX8 || - gnssModel == GNSS_MODEL_UBLOX9 || gnssModel == GNSS_MODEL_UBLOX10)) { + IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX6, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9, + GNSS_MODEL_UBLOX10)) { // reset the GPS on next bootup if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) { LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n"); diff --git a/src/main.cpp b/src/main.cpp index c2d275177..87a4db97c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ #include "graphics/Screen.h" #include "main.h" #include "mesh/generated/meshtastic/config.pb.h" +#include "meshUtils.h" #include "modules/Modules.h" #include "shutdown.h" #include "sleep.h" @@ -627,9 +628,9 @@ void setup() #endif // only play start melody when role is not tracker or sensor - if (config.power.is_power_saving == true && (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)) + if (config.power.is_power_saving == true && + IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER, + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, meshtastic_Config_DeviceConfig_Role_SENSOR)) LOG_DEBUG("Tracker/Sensor: Skipping start melody\n"); else playStartMelody(); diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index c94a7653c..c3ebf3c6e 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -2,6 +2,7 @@ #include "Observer.h" #include +#include #include #include diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 4819f6ed7..853193a12 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -80,4 +80,19 @@ bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes) return false; } return true; +} + +bool isOneOf(int item, int count, ...) +{ + va_list args; + va_start(args, count); + bool found = false; + for (int i = 0; i < count; ++i) { + if (item == va_arg(args, int)) { + found = true; + break; + } + } + va_end(args); + return found; } \ No newline at end of file diff --git a/src/meshUtils.h b/src/meshUtils.h index ce063cb6a..aff3976f4 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -1,5 +1,8 @@ #pragma once #include "DebugConfiguration.h" +#include +#include +#include #include /// C++ v17+ clamp function, limits a given value to a range defined by lo and hi @@ -17,4 +20,8 @@ char *strnstr(const char *s, const char *find, size_t slen); void printBytes(const char *label, const uint8_t *p, size_t numbytes); // is the memory region filled with a single character? -bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes); \ No newline at end of file +bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes); + +bool isOneOf(int item, int count, ...); + +#define IS_ONE_OF(item, ...) isOneOf(item, sizeof((int[]){__VA_ARGS__}) / sizeof(int), __VA_ARGS__) diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 4ba09385d..6ae990bab 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -12,6 +12,7 @@ #include "gps/GeoCoord.h" #include "main.h" #include "mesh/compression/unishox2.h" +#include "meshUtils.h" #include "meshtastic/atak.pb.h" #include "sleep.h" #include "target_specific.h" @@ -347,8 +348,8 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha service->sendToMesh(p, RX_SRC_LOCAL, true); - if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && + if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER, + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && config.power.is_power_saving) { LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); sleepOnNextExecution = true; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 04789af5e..dd5067784 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -11,14 +11,15 @@ #include "main.h" #include #include +#include #define MAGIC_USB_BATTERY_LEVEL 101 int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); - bool isImpoliteRole = config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR || - config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER; + bool isImpoliteRole = + IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER); if (((lastSentToMesh == 0) || ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index b28640e65..4023a3cb9 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -12,6 +12,7 @@ #include "PowerMon.h" #include "error.h" #include "main.h" +#include "meshUtils.h" #ifdef BQ25703A_ADDR #include "BQ25713.h" @@ -157,6 +158,7 @@ void nrf52Loop() #ifdef USE_SEMIHOSTING #include +#include /** * Note: this variable is in BSS and therfore false by default. But the gdbinit @@ -261,10 +263,9 @@ void cpuDeepSleep(uint32_t msecToWake) // 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 && - (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) && - config.power.is_power_saving == true) { + (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(); From d4e8452c60822226755b506685b4a0c6e7bae188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 25 Sep 2024 20:51:11 +0200 Subject: [PATCH 1249/1377] Tbeams have no ADC (#4871) --- src/Power.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Power.cpp b/src/Power.cpp index bbb6cd2c3..c71d17586 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -147,6 +147,8 @@ using namespace meshtastic; */ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor +#ifdef BATTERY_PIN + static void adcEnable() { #ifdef ADC_CTRL // enable adc voltage divider when we need to read @@ -171,6 +173,8 @@ static void adcDisable() #endif } +#endif + /** * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input */ From ac5edf867c64fc1724e01c2ac3d19a0e0501212c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 13:55:04 -0500 Subject: [PATCH 1250/1377] Create SECURITY.md (#4868) --- SECURITY.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..74be2c181 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +| Firmware Version | Supported | +| ------------------- | ------------------ | +| 2.5.x | :white_check_mark: | +| <= 2.4.x | :x: | + +## Reporting a Vulnerability + +We support the private reporting of potential security vulnerabilities. Please go to the Security tab to file a report with a description of the potential vulnerability and reproduction scripts (preferred) or steps, and our developers will review. From 51e4b364b0b1d23813507cf1ee26fec088fb4b27 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 16:18:45 -0500 Subject: [PATCH 1251/1377] Trunk things --- .devcontainer/devcontainer.json | 9 +++------ .devcontainer/setup.sh | 2 +- .github/workflows/tests.yml | 8 ++++---- SECURITY.md | 8 ++++---- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e45fd5d93..d83d052b0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,16 +13,13 @@ }, "customizations": { "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "platformio.platformio-ide", - ] + "extensions": ["ms-vscode.cpptools", "platformio.platformio-ide"] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [ 4403 ], + "forwardPorts": [4403], // Run commands to prepare the container for use - "postCreateCommand": ".devcontainer/setup.sh", + "postCreateCommand": ".devcontainer/setup.sh" } diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 866a4a417..0b2665f84 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -git submodule update --init \ No newline at end of file +git submodule update --init diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 925b91215..241598fd0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -65,7 +65,7 @@ jobs: # - uses: actions/setup-python@v5 # with: # python-version: '3.10' - + # pipx install "setuptools<72" - name: Upgrade python tools shell: bash @@ -76,10 +76,10 @@ jobs: - name: Install PlatformIO from script shell: bash - run: | + run: | curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py python3 get-platformio.py - + - name: Upgrade platformio shell: bash run: | @@ -90,7 +90,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 18 - + - name: Setup pnpm uses: pnpm/action-setup@v4 with: diff --git a/SECURITY.md b/SECURITY.md index 74be2c181..fb4d9005a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,10 +2,10 @@ ## Supported Versions -| Firmware Version | Supported | -| ------------------- | ------------------ | -| 2.5.x | :white_check_mark: | -| <= 2.4.x | :x: | +| Firmware Version | Supported | +| ---------------- | ------------------ | +| 2.5.x | :white_check_mark: | +| <= 2.4.x | :x: | ## Reporting a Vulnerability From baf9cf5a5998fbc6b280c7f18debfd57285e7ed8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 16:19:18 -0500 Subject: [PATCH 1252/1377] Encapsulate RadioLibInterface receive IRQ logic (#4866) * Encapsulate RadioLibInterface receive IRQ logic * More concise * Trunk --- src/mesh/LR11x0Interface.cpp | 25 ++----------------- src/mesh/LR11x0Interface.h | 3 --- src/mesh/RadioLibInterface.cpp | 22 ++++++++++++++++ src/mesh/RadioLibInterface.h | 4 +++ src/mesh/SX126xInterface.cpp | 24 +----------------- src/mesh/SX126xInterface.h | 3 --- src/mesh/SX128xInterface.cpp | 23 +---------------- src/mesh/SX128xInterface.h | 3 --- .../Telemetry/EnvironmentTelemetry.cpp | 1 - 9 files changed, 30 insertions(+), 78 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index e237c354f..2ec659b06 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -272,29 +272,8 @@ template bool LR11x0Interface::isActivelyReceiving() { // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet // received and handled the interrupt for reading the packet/handling errors. - - uint16_t irq = lora.getIrqStatus(); - bool detected = (irq & (RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); - // Handle false detections - if (detected) { - if (!activeReceiveStart) { - activeReceiveStart = millis(); - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && - !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID)) { - // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false preamble detection.\n"); - return false; - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { - // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false header detection.\n"); - return false; - } - } - - // if (detected) LOG_DEBUG("rx detected\n"); - return detected; + return receiveDetected(lora.getIrqStatus(), RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID, + RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED); } template bool LR11x0Interface::sleep() diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 9272f43f0..5711b1f7f 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -65,7 +65,4 @@ template class LR11x0Interface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; - - private: - uint32_t activeReceiveStart = 0; }; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 6cdb3b99e..0968a6d89 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -129,6 +129,28 @@ bool RadioLibInterface::canSendImmediately() return true; } +bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag) +{ + bool detected = (irq & (syncWordHeaderValidFlag | preambleDetectedFlag)); + // Handle false detections + if (detected) { + if (!activeReceiveStart) { + activeReceiveStart = millis(); + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) { + // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag + activeReceiveStart = 0; + LOG_DEBUG("Ignore false preamble detection.\n"); + return false; + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { + // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag + activeReceiveStart = 0; + LOG_DEBUG("Ignore false header detection.\n"); + return false; + } + } + return detected; +} + /// Send a packet (possibly by enquing in a private fifo). This routine will /// later free() the packet to pool. This routine is not allowed to stall because it is called from /// bluetooth comms code. If the txmit queue is empty it might return an error diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index edcbb394f..13bef851a 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -167,6 +167,10 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified meshtastic_QueueStatus getQueueStatus(); protected: + uint32_t activeReceiveStart = 0; + + bool receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag); + /** Do any hardware setup needed on entry into send configuration for the radio. * Subclasses can customize, but must also call this base method */ virtual void configHardwareForSend(); diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 2c6096062..ad1ceeeeb 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -316,29 +316,7 @@ template bool SX126xInterface::isActivelyReceiving() { // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet // received and handled the interrupt for reading the packet/handling errors. - - uint16_t irq = lora.getIrqFlags(); - bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED)); - // Handle false detections - if (detected) { - if (!activeReceiveStart) { - activeReceiveStart = millis(); - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && - !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID)) { - // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false preamble detection.\n"); - return false; - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { - // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false header detection.\n"); - return false; - } - } - - // if (detected) LOG_DEBUG("rx detected\n"); - return detected; + return receiveDetected(lora.getIrqFlags(), RADIOLIB_SX126X_IRQ_HEADER_VALID, RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); } template bool SX126xInterface::sleep() diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index b392cd3e4..c437080c4 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -67,7 +67,4 @@ template class SX126xInterface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; - - private: - uint32_t activeReceiveStart = 0; }; diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 270356e26..5c740099c 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -290,28 +290,7 @@ template bool SX128xInterface::isChannelActive() /** Could we send right now (i.e. either not actively receiving or transmitting)? */ template bool SX128xInterface::isActivelyReceiving() { - uint16_t irq = lora.getIrqStatus(); - bool detected = (irq & (RADIOLIB_SX128X_IRQ_HEADER_VALID | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED)); - - // Handle false detections - if (detected) { - if (!activeReceiveStart) { - activeReceiveStart = millis(); - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && - !(irq & RADIOLIB_SX128X_IRQ_HEADER_VALID)) { - // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false preamble detection.\n"); - return false; - } else if (Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { - // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false header detection.\n"); - return false; - } - } - - return detected; + return receiveDetected(lora.getIrqStatus(), RADIOLIB_SX128X_IRQ_HEADER_VALID, RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED); } template bool SX128xInterface::sleep() diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index f7fd35b25..bba31dab4 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -67,7 +67,4 @@ template class SX128xInterface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; - - private: - uint32_t activeReceiveStart = 0; }; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 969529881..8f891593a 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -158,7 +158,6 @@ int32_t EnvironmentTelemetryModule::runOnce() result = bme680Sensor.runTrigger(); } - uint32_t now = millis(); if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( moduleConfig.telemetry.environment_update_interval, From 118809fbfc2bc71782be2d8ba3216766358fdf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 00:13:04 +0200 Subject: [PATCH 1253/1377] Account for size of Envelope when allocating buffer. (#4819) * Account for size of Envelope when allocating buffer. INFO | 09:29:20 568 [mqtt] Subscribing to msh/2/e/LongFast/+ INFO | 09:29:20 568 [mqtt] Subscribing to msh/2/json/LongFast/+ INFO | 09:29:20 568 [mqtt] Subscribing to msh/2/e/PKI/+ DEBUG | 09:29:20 568 [mqtt] Publishing enqueued MQTT message ERROR | 09:29:20 568 [mqtt] Panic: can't encode protobuf reason='bytes size exceeded' assert failed: size_t pb_encode_to_bytes(uint8_t*, size_t, const pb_msgdesc_t*, const void*) mesh-pb-constants.cpp:18 (0) * save some mem --- src/mqtt/MQTT.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 56af9f663..2ce5cd755 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -32,6 +32,9 @@ static MemoryDynamic staticMqttPool; Allocator &mqttPool = staticMqttPool; +// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets +static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid + void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) { mqtt->onReceive(topic, payload, length); @@ -482,9 +485,7 @@ void MQTT::publishQueuedMessages() { if (!mqttQueue.isEmpty()) { LOG_DEBUG("Publishing enqueued MQTT message\n"); - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); - static uint8_t bytes[meshtastic_MqttClientProxyMessage_size]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); std::string topic; if (env->packet->pki_encrypted) { @@ -570,8 +571,6 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & } if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MqttClientProxyMessage_size]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); std::string topic = cryptTopic + channelId + "/" + owner.id; LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); @@ -666,8 +665,6 @@ void MQTT::perhapsReportToMap() &meshtastic_MapReport_msg, &mapReport); se->packet = mp; - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MqttClientProxyMessage_size]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); From 12481b568ab2d7cd16b0a42161024583c03d269f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 02:09:06 +0200 Subject: [PATCH 1254/1377] fix a lot of nuisances reported by cppcheck (#4872) * fix a lot of nuisances reported by cppcheck * fix portduino --- src/SafeFile.h | 2 +- src/gps/GPS.cpp | 2 +- src/graphics/Screen.cpp | 2 +- src/mesh/CryptoEngine.cpp | 4 ++-- src/mesh/ProtobufModule.h | 2 +- src/mesh/aes-ccm.cpp | 2 +- src/mesh/compression/unishox2.cpp | 4 ++-- src/meshUtils.cpp | 3 +-- src/modules/AdminModule.cpp | 4 ++-- src/modules/AdminModule.h | 4 ++-- src/modules/CannedMessageModule.cpp | 8 ++++---- src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 +- src/modules/Telemetry/PowerTelemetry.cpp | 2 +- src/modules/TraceRouteModule.cpp | 2 +- src/modules/WaypointModule.cpp | 2 +- src/mqtt/MQTT.cpp | 4 ++-- src/platform/nrf52/BLEDfuScure.cpp | 3 +-- src/serialization/JSONValue.cpp | 16 ++++++++-------- 18 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/SafeFile.h b/src/SafeFile.h index 7088074cd..61361d312 100644 --- a/src/SafeFile.h +++ b/src/SafeFile.h @@ -24,7 +24,7 @@ class SafeFile : public Print { public: - SafeFile(char const *filepath, bool fullAtomic = false); + explicit SafeFile(char const *filepath, bool fullAtomic = false); virtual size_t write(uint8_t); virtual size_t write(const uint8_t *buffer, size_t size); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index b13ca4b06..569254d02 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -405,9 +405,9 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t bool GPS::setup() { - int msglen = 0; if (!didSerialInit) { + int msglen = 0; if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate. diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f8a64cc61..f95146318 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -2201,7 +2201,7 @@ void Screen::setFrames(FrameFocus focus) case FOCUS_PRESERVE: // If we can identify which type of frame "originalPosition" was, can move directly to it in the new frameset - FramesetInfo &oldFsi = this->framesetInfo; + const FramesetInfo &oldFsi = this->framesetInfo; if (originalPosition == oldFsi.positions.log) ui->switchToFrame(fsi.positions.log); else if (originalPosition == oldFsi.positions.settings) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 79666b321..f705bae03 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -167,12 +167,12 @@ bool CryptoEngine::setDHKey(uint32_t nodeNum) void CryptoEngine::hash(uint8_t *bytes, size_t numBytes) { SHA256 hash; - size_t posn, len; + size_t posn; uint8_t size = numBytes; uint8_t inc = 16; hash.reset(); for (posn = 0; posn < size; posn += inc) { - len = size - posn; + size_t len = size - posn; if (len > inc) len = inc; hash.update(bytes + posn, len); diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 3b438ebeb..e4d4e5c09 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -108,7 +108,7 @@ template class ProtobufModule : protected SinglePortModule T *decoded = NULL; if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) { memset(&scratch, 0, sizeof(scratch)); - auto &p = mp.decoded; + const meshtastic_Data &p = mp.decoded; if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) { decoded = &scratch; } else { diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp index cd18ae6c5..b9af14fdb 100644 --- a/src/mesh/aes-ccm.cpp +++ b/src/mesh/aes-ccm.cpp @@ -95,7 +95,7 @@ static void aes_ccm_encr(size_t L, const uint8_t *in, size_t len, uint8_t *out, *out++ ^= *in++; } } -static void aes_ccm_encr_auth(size_t M, uint8_t *x, uint8_t *a, uint8_t *auth) +static void aes_ccm_encr_auth(size_t M, const uint8_t *x, uint8_t *a, uint8_t *auth) { size_t i; uint8_t tmp[AES_BLOCK_SIZE]; diff --git a/src/mesh/compression/unishox2.cpp b/src/mesh/compression/unishox2.cpp index 4e239d489..9fc012a76 100644 --- a/src/mesh/compression/unishox2.cpp +++ b/src/mesh/compression/unishox2.cpp @@ -339,7 +339,7 @@ int32_t readUTF8(const char *in, int len, int l, int *utf8len) /// This is also used for Unicode strings \n /// This is a crude implementation that is not optimized. Assuming only short strings \n /// are encoded, this is not much of an issue. -int matchOccurance(const char *in, int len, int l, char *out, int olen, int *ol, uint8_t *state, const uint8_t usx_hcodes[], +int matchOccurance(const char *in, int len, int l, char *out, int olen, int *ol, const uint8_t *state, const uint8_t usx_hcodes[], const uint8_t usx_hcode_lens[]) { int j, k; @@ -383,7 +383,7 @@ int matchOccurance(const char *in, int len, int l, char *out, int olen, int *ol, /// This is also used for Unicode strings \n /// This is a crude implementation that is not optimized. Assuming only short strings \n /// are encoded, this is not much of an issue. -int matchLine(const char *in, int len, int l, char *out, int olen, int *ol, struct us_lnk_lst *prev_lines, uint8_t *state, +int matchLine(const char *in, int len, int l, char *out, int olen, int *ol, struct us_lnk_lst *prev_lines, const uint8_t *state, const uint8_t usx_hcodes[], const uint8_t usx_hcode_lens[]) { int last_ol = *ol; diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 853193a12..2789a143c 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -60,10 +60,9 @@ char *strnstr(const char *s, const char *find, size_t slen) void printBytes(const char *label, const uint8_t *p, size_t numbytes) { - char *messageBuffer; int labelSize = strlen(label); if (labelSize < 100 && numbytes < 64) { - messageBuffer = new char[labelSize + (numbytes * 3) + 2]; + char *messageBuffer = new char[labelSize + (numbytes * 3) + 2]; strncpy(messageBuffer, label, labelSize); for (size_t i = 0; i < numbytes; i++) snprintf(messageBuffer + labelSize + i * 3, 4, " %02x", p[i]); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 389a0db8d..09311abda 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -1026,7 +1026,7 @@ bool AdminModule::checkPassKey(meshtastic_AdminMessage *res) memcmp(res->session_passkey.bytes, session_passkey, 8) == 0); } -bool AdminModule::messageIsResponse(meshtastic_AdminMessage *r) +bool AdminModule::messageIsResponse(const meshtastic_AdminMessage *r) { if (r->which_payload_variant == meshtastic_AdminMessage_get_channel_response_tag || r->which_payload_variant == meshtastic_AdminMessage_get_owner_response_tag || @@ -1043,7 +1043,7 @@ bool AdminModule::messageIsResponse(meshtastic_AdminMessage *r) return false; } -bool AdminModule::messageIsRequest(meshtastic_AdminMessage *r) +bool AdminModule::messageIsRequest(const meshtastic_AdminMessage *r) { if (r->which_payload_variant == meshtastic_AdminMessage_get_channel_request_tag || r->which_payload_variant == meshtastic_AdminMessage_get_owner_request_tag || diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 328e1c824..d6c0a7343 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -55,8 +55,8 @@ class AdminModule : public ProtobufModule, public Obser void setPassKey(meshtastic_AdminMessage *res); bool checkPassKey(meshtastic_AdminMessage *res); - bool messageIsResponse(meshtastic_AdminMessage *r); - bool messageIsRequest(meshtastic_AdminMessage *r); + bool messageIsResponse(const meshtastic_AdminMessage *r); + bool messageIsRequest(const meshtastic_AdminMessage *r); }; extern AdminModule *adminModule; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index d4d112f90..e38b7e063 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -78,16 +78,16 @@ int CannedMessageModule::splitConfiguredMessages() int messageIndex = 0; int i = 0; - String messages = cannedMessageModuleConfig.messages; + String canned_messages = cannedMessageModuleConfig.messages; #if defined(T_WATCH_S3) || defined(RAK14014) - String separator = messages.length() ? "|" : ""; + String separator = canned_messages.length() ? "|" : ""; - messages = "[---- Free Text ----]" + separator + messages; + canned_messages = "[---- Free Text ----]" + separator + canned_messages; #endif // collect all the message parts - strncpy(this->messageStore, messages.c_str(), sizeof(this->messageStore)); + strncpy(this->messageStore, canned_messages.c_str(), sizeof(this->messageStore)); // The first message points to the beginning of the store. this->messages[messageIndex++] = this->messageStore; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8f891593a..ac291c4ab 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -216,7 +216,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt uint32_t agoSecs = GetTimeSinceMeshPacket(lastMeasurementPacket); const char *lastSender = getSenderShortName(*lastMeasurementPacket); - auto &p = lastMeasurementPacket->decoded; + const meshtastic_Data &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { display->drawString(x, y, "Measurement Error"); LOG_ERROR("Unable to decode last packet"); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 6130c2c83..d31892042 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -122,7 +122,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s uint32_t agoSecs = GetTimeyWimeySinceMeshPacket(lastMeasurementPacket); const char *lastSender = getSenderShortName(*lastMeasurementPacket); - auto &p = lastMeasurementPacket->decoded; + const meshtastic_Data &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { display->setFont(FONT_SMALL); display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error"); diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 23b4f1ccf..1f652dbf6 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -11,7 +11,7 @@ bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { - auto &incoming = p.decoded; + const meshtastic_Data &incoming = p.decoded; // Insert unknown hops if necessary insertUnknownHops(p, r, !incoming.request_id); diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index e1974db73..628d7f62c 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -89,7 +89,7 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); // Decode the waypoint - meshtastic_MeshPacket &mp = devicestate.rx_waypoint; + const meshtastic_MeshPacket &mp = devicestate.rx_waypoint; meshtastic_Waypoint wp; memset(&wp, 0, sizeof(wp)); if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) { diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2ce5cd755..891d4b549 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -184,8 +184,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) // PKI messages get accepted even if we can't decrypt if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && strcmp(e.channel_id, "PKI") == 0) { - meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); - meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); + const meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); + const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's // likely they discovered each other via a channel we have downlink enabled for if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) diff --git a/src/platform/nrf52/BLEDfuScure.cpp b/src/platform/nrf52/BLEDfuScure.cpp index 8f7a327c4..82cb8905a 100644 --- a/src/platform/nrf52/BLEDfuScure.cpp +++ b/src/platform/nrf52/BLEDfuScure.cpp @@ -48,11 +48,10 @@ extern "C" void bootloader_util_app_start(uint32_t start_addr); static uint16_t crc16(const uint8_t *data_p, uint8_t length) { - uint8_t x; uint16_t crc = 0xFFFF; while (length--) { - x = crc >> 8 ^ *data_p++; + uint8_t x = crc >> 8 ^ *data_p++; x ^= x >> 4; crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x << 5)) ^ ((uint16_t)x); } diff --git a/src/serialization/JSONValue.cpp b/src/serialization/JSONValue.cpp index 51e0c1a3b..b2e9575bf 100644 --- a/src/serialization/JSONValue.cpp +++ b/src/serialization/JSONValue.cpp @@ -37,14 +37,14 @@ #define FREE_ARRAY(x) \ { \ JSONArray::iterator iter; \ - for (iter = x.begin(); iter != x.end(); iter++) { \ + for (iter = x.begin(); iter != x.end(); ++iter) { \ delete *iter; \ } \ } #define FREE_OBJECT(x) \ { \ JSONObject::iterator iter; \ - for (iter = x.begin(); iter != x.end(); iter++) { \ + for (iter = x.begin(); iter != x.end(); ++iter) { \ delete (*iter).second; \ } \ } @@ -430,7 +430,7 @@ JSONValue::JSONValue(const JSONValue &m_source) JSONArray source_array = *m_source.array_value; JSONArray::iterator iter; array_value = new JSONArray(); - for (iter = source_array.begin(); iter != source_array.end(); iter++) + for (iter = source_array.begin(); iter != source_array.end(); ++iter) array_value->push_back(new JSONValue(**iter)); break; } @@ -439,7 +439,7 @@ JSONValue::JSONValue(const JSONValue &m_source) JSONObject source_object = *m_source.object_value; object_value = new JSONObject(); JSONObject::iterator iter; - for (iter = source_object.begin(); iter != source_object.end(); iter++) { + for (iter = source_object.begin(); iter != source_object.end(); ++iter) { std::string name = (*iter).first; (*object_value)[name] = new JSONValue(*((*iter).second)); } @@ -462,12 +462,12 @@ JSONValue::~JSONValue() { if (type == JSONType_Array) { JSONArray::iterator iter; - for (iter = array_value->begin(); iter != array_value->end(); iter++) + for (iter = array_value->begin(); iter != array_value->end(); ++iter) delete *iter; delete array_value; } else if (type == JSONType_Object) { JSONObject::iterator iter; - for (iter = object_value->begin(); iter != object_value->end(); iter++) { + for (iter = object_value->begin(); iter != object_value->end(); ++iter) { delete (*iter).second; } delete object_value; @@ -722,7 +722,7 @@ std::vector JSONValue::ObjectKeys() const while (iter != object_value->end()) { keys.push_back(iter->first); - iter++; + ++iter; } } @@ -865,7 +865,7 @@ std::string JSONValue::StringifyString(const std::string &str) str_out += chr; } - iter++; + ++iter; } str_out += "\""; From 14019f2afa093174a6e1fbf6ef3a0b2154fc997d Mon Sep 17 00:00:00 2001 From: Szetya Date: Thu, 26 Sep 2024 02:09:27 +0200 Subject: [PATCH 1255/1377] Update WaypointModule.cpp (#4870) In INVERTED display mode, the compass ring was not visible. --- src/modules/WaypointModule.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index 628d7f62c..8194c3d01 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -170,17 +170,17 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, else strncpy(distStr, "? km", sizeof(distStr)); } - + + // Draw compass circle + display->drawCircle(compassX, compassY, compassDiam / 2); + // Undo color-inversion, if set prior to drawing header // Unsure of expected behavior? For now: copy drawNodeInfo if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { display->setColor(BLACK); } - - // Draw compass circle - display->drawCircle(compassX, compassY, compassDiam / 2); - + // Must be after distStr is populated screen->drawColumns(display, x, y, fields); } -#endif \ No newline at end of file +#endif From 9bebad2dbe3650c133d6efac5e028d52ce7fa12a Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Sep 2024 19:54:14 -0500 Subject: [PATCH 1256/1377] Trunkt --- src/modules/WaypointModule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index 8194c3d01..a4611e780 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -170,16 +170,16 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, else strncpy(distStr, "? km", sizeof(distStr)); } - + // Draw compass circle display->drawCircle(compassX, compassY, compassDiam / 2); - + // Undo color-inversion, if set prior to drawing header // Unsure of expected behavior? For now: copy drawNodeInfo if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { display->setColor(BLACK); } - + // Must be after distStr is populated screen->drawColumns(display, x, y, fields); } From 833d7f65bcffbbc991c4b3891541303b9a4e145c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 10:18:45 +0200 Subject: [PATCH 1257/1377] fix toolchains between old and new ESP32 --- arch/esp32/esp32c6.ini | 2 +- src/configuration.h | 3 +++ src/mesh/wifi/WiFiAPClient.cpp | 4 ++++ src/sleep.cpp | 5 ++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini index 65c63b299..fad9f3865 100644 --- a/arch/esp32/esp32c6.ini +++ b/arch/esp32/esp32c6.ini @@ -1,6 +1,6 @@ [esp32c6_base] extends = esp32_base -platform = https://github.com/Jason2866/platform-espressif32.git#d8d8f1bcf4bfcae82db2bef70a2b0014f1d2bc13 +platform = https://github.com/Jason2866/platform-espressif32.git#92cd8e2ed8ec8392b32adc89451aecfcbbaa1726 build_flags = ${arduino_base.build_flags} -Wall diff --git a/src/configuration.h b/src/configuration.h index 55206bf4e..3cd93ec83 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -69,6 +69,9 @@ along with this program. If not, see . #ifndef RTC_DATA_ATTR #define RTC_DATA_ATTR #endif +#ifndef EXT_RAM_BSS_ATTR +#define EXT_RAM_BSS_ATTR EXT_RAM_ATTR +#endif // ----------------------------------------------------------------------------- // Regulatory overrides diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index b9535a0b2..c835878d3 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -310,8 +310,12 @@ static void WiFiEvent(WiFiEvent_t event) onNetworkConnected(); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) LOG_INFO("Obtained Local IP6 address: %s\n", WiFi.linkLocalIPv6().toString().c_str()); LOG_INFO("Obtained GlobalIP6 address: %s\n", WiFi.globalIPv6().toString().c_str()); +#else + LOG_INFO("Obtained IP6 address: %s\n", WiFi.localIPv6().toString().c_str()); +#endif break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0\n"); diff --git a/src/sleep.cpp b/src/sleep.cpp index 9554c1ba4..60f83f537 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -451,8 +451,11 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) static esp_pm_config_t esp32_config; // filled with zeros because bss - +#else + static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss +#endif #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 From b4bdf604f551d7584abe4babcd4eda0a554edc80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 11:08:49 +0200 Subject: [PATCH 1258/1377] tryfix --- src/mesh/api/ServerAPI.cpp | 4 ++++ src/mqtt/MQTT.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 097f4fa21..8717123f6 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -45,7 +45,11 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) auto client = U::accept(); +#else + auto client = U::available(); +#endif if (client) { // Close any previous connection (see FIXME in header file) if (openAPI) { diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4fc0f1989..deb5c182b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -329,7 +329,7 @@ void MQTT::reconnect() mqttPassword = moduleConfig.mqtt.password; } #if HAS_WIFI && !defined(ARCH_PORTDUINO) -#ifndef CONFIG_IDF_TARGET_ESP32C6 +#if !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(RPI_PICO) if (moduleConfig.mqtt.tls_enabled) { // change default for encrypted to 8883 try { From 11c17ec78c8ab2270b178be1a5425640397810c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 11:39:35 +0200 Subject: [PATCH 1259/1377] oh well --- src/mesh/api/ServerAPI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 8717123f6..42217428d 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -45,8 +45,12 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { +#ifdef ARCH_ESP32 #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) auto client = U::accept(); +#else + auto client = U::available(); +#endif #else auto client = U::available(); #endif From 8f84a96b6985c586a901ccd443fc2cf25478141f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 12:12:08 +0200 Subject: [PATCH 1260/1377] refactor: typo fix in macro definition --- src/main.cpp | 4 ++-- variants/ME25LS01-4Y10TD/variant.h | 2 +- variants/ME25LS01-4Y10TD_e-ink/variant.h | 2 +- variants/tracker-t1000-e/variant.h | 2 +- variants/wio-sdk-wm1110/variant.h | 2 +- variants/wio-t1000-s/variant.h | 2 +- variants/wio-tracker-wm1110/variant.h | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 87a4db97c..cbde2ce39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -972,7 +972,7 @@ void setup() #if defined(USE_LR1110) if (!rIf) { - rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESER_PIN, LR1110_BUSY_PIN); + rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); if (!rIf->init()) { LOG_WARN("Failed to find LR1110 radio\n"); delete rIf; @@ -985,7 +985,7 @@ void setup() #if defined(USE_LR1120) if (!rIf) { - rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESER_PIN, LR1120_BUSY_PIN); + rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); if (!rIf->init()) { LOG_WARN("Failed to find LR1120 radio\n"); delete rIf; diff --git a/variants/ME25LS01-4Y10TD/variant.h b/variants/ME25LS01-4Y10TD/variant.h index 715de8ee6..0da391f0b 100644 --- a/variants/ME25LS01-4Y10TD/variant.h +++ b/variants/ME25LS01-4Y10TD/variant.h @@ -94,7 +94,7 @@ extern "C" { #define USE_LR1110 #define LR1110_IRQ_PIN LORA_DIO1 -#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_NRESET_PIN LORA_RESET #define LR1110_BUSY_PIN LORA_DIO2 #define LR1110_SPI_NSS_PIN LORA_CS #define LR1110_SPI_SCK_PIN LORA_SCK diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/ME25LS01-4Y10TD_e-ink/variant.h index 0b2b13068..105f9b873 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.h @@ -117,7 +117,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define USE_LR1110 #define LR1110_IRQ_PIN LORA_DIO1 -#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_NRESET_PIN LORA_RESET #define LR1110_BUSY_PIN LORA_DIO2 #define LR1110_SPI_NSS_PIN LORA_CS #define LR1110_SPI_SCK_PIN LORA_SCK diff --git a/variants/tracker-t1000-e/variant.h b/variants/tracker-t1000-e/variant.h index 470edd08c..b8eb59b31 100644 --- a/variants/tracker-t1000-e/variant.h +++ b/variants/tracker-t1000-e/variant.h @@ -93,7 +93,7 @@ extern "C" { #define USE_LR1110 #define LR1110_IRQ_PIN LORA_DIO1 -#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_NRESET_PIN LORA_RESET #define LR1110_BUSY_PIN LORA_DIO2 #define LR1110_SPI_NSS_PIN LORA_CS #define LR1110_SPI_SCK_PIN LORA_SCK diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h index 8f66b1f8c..b6e5c79df 100644 --- a/variants/wio-sdk-wm1110/variant.h +++ b/variants/wio-sdk-wm1110/variant.h @@ -95,7 +95,7 @@ extern "C" { #define USE_LR1110 #define LR1110_IRQ_PIN LORA_DIO1 -#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_NRESET_PIN LORA_RESET #define LR1110_BUSY_PIN LORA_DIO2 #define LR1110_SPI_NSS_PIN LORA_CS #define LR1110_SPI_SCK_PIN LORA_SCK diff --git a/variants/wio-t1000-s/variant.h b/variants/wio-t1000-s/variant.h index fa6ea4abc..8894a40c5 100644 --- a/variants/wio-t1000-s/variant.h +++ b/variants/wio-t1000-s/variant.h @@ -94,7 +94,7 @@ extern "C" { #define USE_LR1110 #define LR1110_IRQ_PIN LORA_DIO1 -#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_NRESET_PIN LORA_RESET #define LR1110_BUSY_PIN LORA_DIO2 #define LR1110_SPI_NSS_PIN LORA_CS #define LR1110_SPI_SCK_PIN LORA_SCK diff --git a/variants/wio-tracker-wm1110/variant.h b/variants/wio-tracker-wm1110/variant.h index de7da9911..32e84485d 100644 --- a/variants/wio-tracker-wm1110/variant.h +++ b/variants/wio-tracker-wm1110/variant.h @@ -91,7 +91,7 @@ extern "C" { #define USE_LR1110 #define LR1110_IRQ_PIN LORA_DIO1 -#define LR1110_NRESER_PIN LORA_RESET +#define LR1110_NRESET_PIN LORA_RESET #define LR1110_BUSY_PIN LORA_DIO2 #define LR1110_SPI_NSS_PIN LORA_CS #define LR1110_SPI_SCK_PIN LORA_SCK From a32233bb92635d15a3998f19269bd6f57834f784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 26 Sep 2024 12:15:37 +0200 Subject: [PATCH 1261/1377] fixa de typo too --- src/main.cpp | 2 +- variants/tlora_t3s3_v1/variant.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c1ce1cd0c..4e16f565f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1001,7 +1001,7 @@ void setup() #if defined(USE_LR1121) if (!rIf) { - rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESER_PIN, LR1121_BUSY_PIN); + rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); if (!rIf->init()) { LOG_WARN("Failed to find LR1121 radio\n"); delete rIf; diff --git a/variants/tlora_t3s3_v1/variant.h b/variants/tlora_t3s3_v1/variant.h index ca10ef5fb..babe44a58 100644 --- a/variants/tlora_t3s3_v1/variant.h +++ b/variants/tlora_t3s3_v1/variant.h @@ -65,7 +65,7 @@ // LR1121 #ifdef USE_LR1121 #define LR1121_IRQ_PIN 36 -#define LR1121_NRESER_PIN LORA_RESET +#define LR1121_NRESET_PIN LORA_RESET #define LR1121_BUSY_PIN LORA_DIO2 #define LR1121_SPI_NSS_PIN LORA_CS #define LR1121_SPI_SCK_PIN LORA_SCK From 4794cdb12004fc2cb17369297c0b087f546ef3f9 Mon Sep 17 00:00:00 2001 From: TheMalkavien Date: Fri, 27 Sep 2024 00:44:11 +0200 Subject: [PATCH 1262/1377] Fix (some ?) memory alignment issues on the crypto part - resulting in crashes or strange behavior (#4867) * Replace multiple potentially non aligned pointer dereference (#4855) First step to fix some Crypto crashes or strange behaviors * Makes the two Crypto byte buffers aligned (#4855) Fix #4855, and probably multiple Crypto problems depending on hardware --------- Co-authored-by: Ben Meadors Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> --- src/mesh/CryptoEngine.cpp | 18 +++++++++--------- src/mesh/Router.cpp | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index f705bae03..f9d7b4ad2 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -70,8 +70,8 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ long extraNonceTmp = random(); auth = bytesOut + numBytes; extraNonce = (uint32_t *)(auth + 8); - *extraNonce = extraNonceTmp; - LOG_INFO("Random nonce value: %d\n", *extraNonce); + memcpy(extraNonce, &extraNonceTmp, 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; + LOG_INFO("Random nonce value: %d\n", extraNonceTmp); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); if (node->num < 1 || node->user.public_key.size == 0) { LOG_DEBUG("Node %d or their public_key not found\n", toNode); @@ -80,14 +80,14 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ if (!crypto->setDHKey(toNode)) { return false; } - initNonce(fromNode, packetNum, *extraNonce); + initNonce(fromNode, packetNum, extraNonceTmp); // Calculate the shared secret with the destination node and encrypt printBytes("Attempting encrypt using nonce: ", nonce, 13); printBytes("Attempting encrypt using shared_key starting with: ", shared_key, 8); aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); // this can write up to 15 bytes longer than numbytes past bytesOut - *extraNonce = extraNonceTmp; + memcpy(extraNonce, &extraNonceTmp, 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; return true; } @@ -100,12 +100,12 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) { uint8_t *auth; // set to last 8 bytes of text? - uint32_t *extraNonce; + uint32_t extraNonce; // pointer was not really used auth = bytes + numBytes - 12; - extraNonce = (uint32_t *)(auth + 8); - LOG_INFO("Random nonce value: %d\n", *extraNonce); + memcpy(&extraNonce, auth +8, 4); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); + LOG_INFO("Random nonce value: %d\n", extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); - + if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) { LOG_DEBUG("Node or its public key not found in database\n"); return false; @@ -115,7 +115,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size if (!crypto->setDHKey(fromNode)) { return false; } - initNonce(fromNode, packetNum, *extraNonce); + initNonce(fromNode, packetNum, extraNonce); printBytes("Attempting decrypt using nonce: ", nonce, 13); printBytes("Attempting decrypt using shared_key starting with: ", shared_key, 8); return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 12, nullptr, 0, auth, bytesOut); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a993ee7c0..f06f54165 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -36,8 +36,8 @@ static MemoryDynamic staticPool; Allocator &packetPool = staticPool; -static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; -static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1]; +static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__ ((__aligned__)); +static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__ ((__aligned__)); /** * Constructor From 5f6d9c3e27b1bc3d85b0b729ee924399dc4b7486 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 26 Sep 2024 19:33:08 -0500 Subject: [PATCH 1263/1377] Add pkc test (#4878) * Add a second delay() to get the unit tests running on Rak4631 * Add test_PKC_Decrypt * Remove cruft from test case --- src/mesh/CryptoEngine.cpp | 2 ++ test/test_crypto/test_main.cpp | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index f9d7b4ad2..7b5c8f2bd 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -102,6 +102,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size uint8_t *auth; // set to last 8 bytes of text? uint32_t extraNonce; // pointer was not really used auth = bytes + numBytes - 12; +#ifndef PIO_UNIT_TESTING memcpy(&extraNonce, auth +8, 4); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); LOG_INFO("Random nonce value: %d\n", extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); @@ -115,6 +116,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size if (!crypto->setDHKey(fromNode)) { return false; } +#endif initNonce(fromNode, packetNum, extraNonce); printBytes("Attempting decrypt using nonce: ", nonce, 13); printBytes("Attempting decrypt using shared_key starting with: ", shared_key, 8); diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index 0c820178a..cbe366048 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -98,7 +98,44 @@ void test_DH25519(void) HexToBytes(private_key, "18630f93598637c35da623a74559cf944374a559114c7937811041fc8605564a"); crypto->setDHPrivateKey(private_key); TEST_ASSERT(!crypto->setDHPublicKey(public_key)); // Weak public key results in 0 shared key + + HexToBytes(public_key, "f7e13a1a067d2f4e1061bf9936fde5be6b0c2494a8f809cbac7f290ef719e91c"); + HexToBytes(private_key, "10300724f3bea134eb1575245ef26ff9b8ccd59849cd98ce1a59002fe1d5986c"); + HexToBytes(expected_shared, "24becd5dfed9e9289ba2e15b82b0d54f8e9aacb72f5e4248c58d8d74b451ce76"); + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(crypto->setDHPublicKey(public_key)); + crypto->hash(crypto->shared_key, 32); + TEST_ASSERT_EQUAL_MEMORY(expected_shared, crypto->shared_key, 32); } + +void test_PKC_Decrypt(void) +{ + uint8_t private_key[32]; + uint8_t public_key[32]; + uint8_t expected_shared[32]; + uint8_t expected_decrypted[32]; + uint8_t radioBytes[128] __attribute__((__aligned__)); + uint8_t decrypted[128] __attribute__((__aligned__)); + uint8_t expected_nonce[16]; + + uint32_t fromNode; + HexToBytes(public_key, "db18fc50eea47f00251cb784819a3cf5fc361882597f589f0d7ff820e8064457"); + HexToBytes(private_key, "a00330633e63522f8a4d81ec6d9d1e6617f6c8ffd3a4c698229537d44e522277"); + HexToBytes(expected_shared, "777b1545c9d6f9a2"); + HexToBytes(expected_decrypted, "08011204746573744800"); + HexToBytes(radioBytes, "8c646d7a2909000062d6b2136b00000040df24abfcc30a17a3d9046726099e796a1c036a792b"); + HexToBytes(expected_nonce, "62d6b213036a792b2909000000"); + fromNode = 0x0929; + crypto->setDHPrivateKey(private_key); + TEST_ASSERT(crypto->setDHPublicKey(public_key)); + crypto->hash(crypto->shared_key, 32); + crypto->decryptCurve25519(fromNode, 0x13b2d662, 22, radioBytes + 16, decrypted); + TEST_ASSERT_EQUAL_MEMORY(expected_shared, crypto->shared_key, 8); + TEST_ASSERT_EQUAL_MEMORY(expected_nonce, crypto->nonce, 13); + + TEST_ASSERT_EQUAL_MEMORY(expected_decrypted, decrypted, 10); +} + void test_AES_CTR(void) { uint8_t expected[32]; @@ -137,6 +174,7 @@ void setup() RUN_TEST(test_ECB_AES256); RUN_TEST(test_DH25519); RUN_TEST(test_AES_CTR); + RUN_TEST(test_PKC_Decrypt); } void loop() From 30356dcd970cef2c33e8cbf74ddcf7ba71a6c66c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 26 Sep 2024 19:46:17 -0500 Subject: [PATCH 1264/1377] Retroactive trunkinate --- src/mesh/CryptoEngine.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 7b5c8f2bd..535e11e9b 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -70,7 +70,8 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ long extraNonceTmp = random(); auth = bytesOut + numBytes; extraNonce = (uint32_t *)(auth + 8); - memcpy(extraNonce, &extraNonceTmp, 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; + memcpy(extraNonce, &extraNonceTmp, + 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; LOG_INFO("Random nonce value: %d\n", extraNonceTmp); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); if (node->num < 1 || node->user.public_key.size == 0) { @@ -87,7 +88,8 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ printBytes("Attempting encrypt using shared_key starting with: ", shared_key, 8); aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); // this can write up to 15 bytes longer than numbytes past bytesOut - memcpy(extraNonce, &extraNonceTmp, 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; + memcpy(extraNonce, &extraNonceTmp, + 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; return true; } @@ -99,14 +101,14 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ */ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) { - uint8_t *auth; // set to last 8 bytes of text? + uint8_t *auth; // set to last 8 bytes of text? uint32_t extraNonce; // pointer was not really used auth = bytes + numBytes - 12; #ifndef PIO_UNIT_TESTING - memcpy(&extraNonce, auth +8, 4); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); + memcpy(&extraNonce, auth + 8, 4); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); LOG_INFO("Random nonce value: %d\n", extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); - + if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) { LOG_DEBUG("Node or its public key not found in database\n"); return false; From 743fc2e812ff1c7ec40dcbdb1bc1656565febd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 09:07:14 +0200 Subject: [PATCH 1265/1377] Update radiolib, fixes CRC bug on SX127x and improves LR11xx support --- platformio.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b3898ba93..799645306 100644 --- a/platformio.ini +++ b/platformio.ini @@ -88,7 +88,9 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = - jgromes/RadioLib@~7.0.0 + ;jgromes/RadioLib@~7.0.0 + ;7.0.1pre needed for LR1121 support and SX127x CRC Bugfix + https://github.com/jgromes/RadioLib.git#42ae7c92ed584317d7206cfc463ba6260295dbbc https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 From 40932ea06c55d2441ff239fce37de04bc0e6f55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:21:34 +0200 Subject: [PATCH 1266/1377] update ci builder to include a 'quick' command line option that only outputs 3 random devices or check targets --- bin/generate_ci_matrix.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index 46398dd59..ea2480c58 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -6,6 +6,7 @@ import configparser import json import os import sys +import random rootdir = "variants/" @@ -39,5 +40,7 @@ for subdir, dirs, files in os.walk(rootdir): "check" in options ): outlist.append(section) - -print(json.dumps(outlist)) +if ("quick" in options): + print(json.dumps(random.sample(outlist, 3))) +else: + print(json.dumps(outlist)) From 39febad63057fd21c8b2a87ed6bcb8872449a646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:25:37 +0200 Subject: [PATCH 1267/1377] only sample a few builds for CI runs --- .github/workflows/main_matrix.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ca9f1d37f..bb42f2246 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -20,7 +20,33 @@ on: workflow_dispatch: jobs: + setup-quick: + if: ${{ github.event_name != 'workflow_dispatch' }} + strategy: + fail-fast: false + matrix: + arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check] + runs-on: ubuntu-latest + steps: + - id: checkout + uses: actions/checkout@v4 + name: Checkout base + - id: jsonStep + run: | + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) + echo "$TARGETS" + echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + outputs: + esp32: ${{ steps.jsonStep.outputs.esp32 }} + esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} + esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} + nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} + rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + stm32: ${{ steps.jsonStep.outputs.stm32 }} + check: ${{ steps.jsonStep.outputs.check }} + setup: + if: ${{ github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: From c35d780236fe235721910e74923367a7568e5397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:27:57 +0200 Subject: [PATCH 1268/1377] only randominze for at least 3 elements --- bin/generate_ci_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index ea2480c58..4d8759ecc 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -40,7 +40,7 @@ for subdir, dirs, files in os.walk(rootdir): "check" in options ): outlist.append(section) -if ("quick" in options): +if ("quick" in options) & (len(outlist) > 3): print(json.dumps(random.sample(outlist, 3))) else: print(json.dumps(outlist)) From bdb998c7637ab153cc704b407b86b1e5d3f8a786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:33:16 +0200 Subject: [PATCH 1269/1377] pick either setup or setup-quick as valid --- .github/workflows/main_matrix.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index bb42f2246..730241f50 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -71,13 +71,15 @@ jobs: check: ${{ steps.jsonStep.outputs.check }} check: - needs: setup + needs: [ setup, setup-quick ] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest - if: ${{ github.event_name != 'workflow_dispatch' }} + if: | + always() + && ${{ github.event_name != 'workflow_dispatch' }} steps: - uses: actions/checkout@v4 - name: Build base @@ -87,7 +89,8 @@ jobs: run: bin/check-all.sh ${{ matrix.board }} build-esp32: - needs: setup + needs: [ setup, setup-quick ] + if: always() strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32) }} @@ -96,7 +99,8 @@ jobs: board: ${{ matrix.board }} build-esp32-s3: - needs: setup + needs: [ setup, setup-quick ] + if: always() strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} @@ -105,7 +109,8 @@ jobs: board: ${{ matrix.board }} build-esp32-c3: - needs: setup + needs: [ setup, setup-quick ] + if: always() strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} @@ -114,7 +119,8 @@ jobs: board: ${{ matrix.board }} build-nrf52: - needs: setup + needs: [ setup, setup-quick ] + if: always() strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} @@ -123,7 +129,8 @@ jobs: board: ${{ matrix.board }} build-rpi2040: - needs: setup + needs: [ setup, setup-quick ] + if: always() strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} @@ -132,7 +139,8 @@ jobs: board: ${{ matrix.board }} build-stm32: - needs: setup + needs: [ setup, setup-quick ] + if: always() strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.stm32) }} From e02a7d1c68c9efaa7c2d5ed521d7ac2df6680bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:36:00 +0200 Subject: [PATCH 1270/1377] test if quick is working at all --- .github/workflows/main_matrix.yml | 53 ++++++------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 730241f50..ffe777f7e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -20,8 +20,8 @@ on: workflow_dispatch: jobs: - setup-quick: - if: ${{ github.event_name != 'workflow_dispatch' }} + setup: + if: ${{ github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: @@ -45,41 +45,14 @@ jobs: stm32: ${{ steps.jsonStep.outputs.stm32 }} check: ${{ steps.jsonStep.outputs.check }} - setup: - if: ${{ github.event_name == 'workflow_dispatch' }} - strategy: - fail-fast: false - matrix: - arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check] - runs-on: ubuntu-latest - steps: - - id: checkout - uses: actions/checkout@v4 - name: Checkout base - - id: jsonStep - run: | - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) - echo "$TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT - outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} - check: ${{ steps.jsonStep.outputs.check }} - check: - needs: [ setup, setup-quick ] + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest - if: | - always() - && ${{ github.event_name != 'workflow_dispatch' }} + if: ${{ github.event_name != 'workflow_dispatch' }} steps: - uses: actions/checkout@v4 - name: Build base @@ -89,8 +62,7 @@ jobs: run: bin/check-all.sh ${{ matrix.board }} build-esp32: - needs: [ setup, setup-quick ] - if: always() + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32) }} @@ -99,8 +71,7 @@ jobs: board: ${{ matrix.board }} build-esp32-s3: - needs: [ setup, setup-quick ] - if: always() + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} @@ -109,8 +80,7 @@ jobs: board: ${{ matrix.board }} build-esp32-c3: - needs: [ setup, setup-quick ] - if: always() + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} @@ -119,8 +89,7 @@ jobs: board: ${{ matrix.board }} build-nrf52: - needs: [ setup, setup-quick ] - if: always() + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} @@ -129,8 +98,7 @@ jobs: board: ${{ matrix.board }} build-rpi2040: - needs: [ setup, setup-quick ] - if: always() + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} @@ -139,8 +107,7 @@ jobs: board: ${{ matrix.board }} build-stm32: - needs: [ setup, setup-quick ] - if: always() + needs: setup strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.stm32) }} From 747046d3351d8ae0a74862e6c294c37e9348405f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:37:15 +0200 Subject: [PATCH 1271/1377] nope --- .github/workflows/main_matrix.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ffe777f7e..87ba8cf85 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -21,7 +21,6 @@ on: jobs: setup: - if: ${{ github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: From ae14ca78703eee9ec4a5c342ff16f21139106041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 10:42:27 +0200 Subject: [PATCH 1272/1377] the name is somewhat misleading --- .github/workflows/build_stm32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml index 6858812e1..e78178db3 100644 --- a/.github/workflows/build_stm32.yml +++ b/.github/workflows/build_stm32.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Build Raspberry Pi 2040 + - name: Build STM32WL id: build uses: ./.github/actions/build-variant with: From ef223b1195453f89edef1edbc3ff933e835037d2 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 27 Sep 2024 14:55:55 -0500 Subject: [PATCH 1273/1377] use delete[] to avoid leaking memory (#4883) --- src/meshUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index 2789a143c..e98a6f1ce 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -68,7 +68,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes) snprintf(messageBuffer + labelSize + i * 3, 4, " %02x", p[i]); strcpy(messageBuffer + labelSize + numbytes * 3, "\n"); LOG_DEBUG(messageBuffer); - delete messageBuffer; + delete[] messageBuffer; } } From 482361b2521f1bf28f830f1fe9e5fa85b0ec058f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 27 Sep 2024 22:56:42 +0200 Subject: [PATCH 1274/1377] Fix CAD IRQ setting credited to @GUVWAF --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 799645306..3cdac2ec7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -90,7 +90,7 @@ monitor_filters = direct lib_deps = ;jgromes/RadioLib@~7.0.0 ;7.0.1pre needed for LR1121 support and SX127x CRC Bugfix - https://github.com/jgromes/RadioLib.git#42ae7c92ed584317d7206cfc463ba6260295dbbc + https://github.com/jgromes/RadioLib.git#98ad30eb103d22bb7e75f3cc900597403b0cfb1f https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 @@ -165,4 +165,4 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 From fd1b68513a4f8bb305ffd84d2bf88aecdb3937a3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 27 Sep 2024 19:29:44 -0500 Subject: [PATCH 1275/1377] Add sx126x_ant_sw for Native (#4887) Co-authored-by: Ben Meadors --- bin/config-dist.yaml | 3 ++- src/mesh/SX126xInterface.cpp | 4 ++++ src/platform/portduino/PortduinoGlue.cpp | 11 ++++++++++- src/platform/portduino/PortduinoGlue.h | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 5590ed3b0..3a470b7bb 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -9,6 +9,7 @@ Lora: # IRQ: 16 # Busy: 20 # Reset: 18 +# SX126X_ANT_SW: 6 # Module: sx1262 # Waveshare SX1302 LISTEN ONLY AT THIS TIME! # CS: 7 @@ -153,4 +154,4 @@ Webserver: General: MaxNodes: 200 - MaxMessageQueue: 100 + MaxMessageQueue: 100 \ No newline at end of file diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index ad1ceeeeb..0ca5ef984 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -52,6 +52,10 @@ template bool SX126xInterface::init() float tcxoVoltage = 0; if (settingsMap[dio3_tcxo_voltage]) tcxoVoltage = 1.8; + if (settingsMap[sx126x_ant_sw] != RADIOLIB_NC) { + digitalWrite(settingsMap[sx126x_ant_sw], HIGH); + pinMode(settingsMap[sx126x_ant_sw], OUTPUT); + } // FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE #elif !defined(SX126X_DIO3_TCXO_VOLTAGE) float tcxoVoltage = diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index dc143c661..44f4d5227 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -80,6 +80,7 @@ void portduinoSetup() irq, busy, reset, + sx126x_ant_sw, txen, rxen, displayDC, @@ -180,6 +181,7 @@ void portduinoSetup() settingsMap[reset] = yamlConfig["Lora"]["Reset"].as(RADIOLIB_NC); settingsMap[txen] = yamlConfig["Lora"]["TXen"].as(RADIOLIB_NC); settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC); + settingsMap[sx126x_ant_sw] = yamlConfig["Lora"]["SX126X_ANT_SW"].as(RADIOLIB_NC); settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); settingsMap[ch341Quirk] = yamlConfig["Lora"]["ch341_quirk"].as(false); settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as(2000000); @@ -305,6 +307,8 @@ void portduinoSetup() gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. // Need to bind all the configured GPIO pins so they're not simulated + // TODO: Can we do this in the for loop above? + // TODO: If one of these fails, we should log and terminate if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) { if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) { settingsMap[cs] = RADIOLIB_NC; @@ -325,6 +329,11 @@ void portduinoSetup() settingsMap[reset] = RADIOLIB_NC; } } + if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) { + settingsMap[sx126x_ant_sw] = RADIOLIB_NC; + } + } if (settingsMap.count(user) > 0 && settingsMap[user] != RADIOLIB_NC) { if (initGPIOPin(settingsMap[user], gpioChipName) != ERRNO_OK) { settingsMap[user] = RADIOLIB_NC; @@ -391,4 +400,4 @@ int initGPIOPin(int pinNum, const std::string gpioChipName) #else return ERRNO_OK; #endif -} +} \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 3fee1db40..59956cb59 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -8,6 +8,7 @@ enum configNames { irq, busy, reset, + sx126x_ant_sw, txen, rxen, dio2_as_rf_switch, @@ -63,4 +64,4 @@ enum { level_error, level_warn, level_info, level_debug, level_trace }; extern std::map settingsMap; extern std::map settingsStrings; extern std::ofstream traceFile; -int initGPIOPin(int pinNum, std::string gpioChipname); +int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file From 0e0811eccdd9f92311568d72a4336152d2b50eff Mon Sep 17 00:00:00 2001 From: Ken Piper Date: Fri, 27 Sep 2024 19:31:05 -0500 Subject: [PATCH 1276/1377] Implement GPIO pin allowlist (#4882) --- src/modules/RemoteHardwareModule.cpp | 41 ++++++++++++++++++++++------ src/modules/RemoteHardwareModule.h | 3 ++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index f6b8b2e90..43612e450 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -15,26 +15,44 @@ // a max of one change per 30 seconds #define WATCH_INTERVAL_MSEC (30 * 1000) +// Tests for access to read from or write to a specified GPIO pin +static bool pinAccessAllowed(uint64_t mask, uint8_t pin) +{ + // If undefined pin access is allowed, don't check the pin and just return true + if (moduleConfig.remote_hardware.allow_undefined_pin_access) { + return true; + } + + // Test to see if the pin is in the list of allowed pins and return true if found + if (mask & (1ULL << pin)) { + return true; + } + + return false; +} + /// Set pin modes for every set bit in a mask -static void pinModes(uint64_t mask, uint8_t mode) +static void pinModes(uint64_t mask, uint8_t mode, uint64_t maskAvailable) { for (uint64_t i = 0; i < NUM_GPIOS; i++) { if (mask & (1ULL << i)) { - pinMode(i, mode); + if (pinAccessAllowed(maskAvailable, i)) { + pinMode(i, mode); + } } } } /// Read all the pins mentioned in a mask -static uint64_t digitalReads(uint64_t mask) +static uint64_t digitalReads(uint64_t mask, uint64_t maskAvailable) { uint64_t res = 0; - pinModes(mask, INPUT_PULLUP); + pinModes(mask, INPUT_PULLUP, maskAvailable); for (uint64_t i = 0; i < NUM_GPIOS; i++) { uint64_t m = 1ULL << i; - if (mask & m) { + if (mask & m && pinAccessAllowed(maskAvailable, i)) { if (digitalRead(i)) { res |= m; } @@ -50,6 +68,11 @@ RemoteHardwareModule::RemoteHardwareModule() { // restrict to the gpio channel for rx boundChannel = Channels::gpioChannel; + + // Pull available pin allowlist from config and build a bitmask out of it for fast comparisons later + for (uint8_t i = 0; i < 4; i++) { + availablePins += 1ULL << moduleConfig.remote_hardware.available_pins[i].gpio_pin; + } } bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_HardwareMessage *pptr) @@ -63,10 +86,10 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r // Print notification to LCD screen screen->print("Write GPIOs\n"); - pinModes(p.gpio_mask, OUTPUT); + pinModes(p.gpio_mask, OUTPUT, availablePins); for (uint8_t i = 0; i < NUM_GPIOS; i++) { uint64_t mask = 1ULL << i; - if (p.gpio_mask & mask) { + if (p.gpio_mask & mask && pinAccessAllowed(availablePins, i)) { digitalWrite(i, (p.gpio_value & mask) ? 1 : 0); } } @@ -79,7 +102,7 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r if (screen) screen->print("Read GPIOs\n"); - uint64_t res = digitalReads(p.gpio_mask); + uint64_t res = digitalReads(p.gpio_mask, availablePins); // Send the reply meshtastic_HardwareMessage r = meshtastic_HardwareMessage_init_default; @@ -121,7 +144,7 @@ int32_t RemoteHardwareModule::runOnce() if (moduleConfig.remote_hardware.enabled && watchGpios) { if (!Throttle::isWithinTimespanMs(lastWatchMsec, WATCH_INTERVAL_MSEC)) { - uint64_t curVal = digitalReads(watchGpios); + uint64_t curVal = digitalReads(watchGpios, availablePins); lastWatchMsec = millis(); if (curVal != previousWatch) { diff --git a/src/modules/RemoteHardwareModule.h b/src/modules/RemoteHardwareModule.h index dd39f5b69..4dc31d405 100644 --- a/src/modules/RemoteHardwareModule.h +++ b/src/modules/RemoteHardwareModule.h @@ -17,6 +17,9 @@ class RemoteHardwareModule : public ProtobufModule, /// The timestamp of our last watch event (we throttle watches to 1 change every 30 seconds) uint32_t lastWatchMsec = 0; + /// A bitmask of GPIOs that are exposed to the mesh if undefined access is not enabled + uint64_t availablePins = 0; + public: /** Constructor * name is for debugging output From 884e3f2e35ab1eee98748da0f90f83b4ced372d6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 27 Sep 2024 20:03:51 -0500 Subject: [PATCH 1277/1377] Remove remote hardware from release --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 3cdac2ec7..81aa2041e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,6 +81,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS -DRADIOLIB_EXCLUDE_LORAWAN -DMESHTASTIC_EXCLUDE_DROPZONE=1 + -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DBUILD_EPOCH=$UNIX_TIME ;-D OLED_PL From 8efc15f4d9c4a2b177fdbbc0c3389eb1c5394c8c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 27 Sep 2024 20:09:53 -0500 Subject: [PATCH 1278/1377] Ignore seen phoneapi packets (#4888) * Ignore PhoneAPI packet if it's been seen * ignoramus * Also keep track of the last 20 packet IDs * Fill * Make this match the nimble one * Add the log too * Ignore zero ID packets * Remove message entirely * TRunkt --- .trunk/trunk.yaml | 2 +- src/mesh/NodeDB.h | 1 + src/mesh/PhoneAPI.cpp | 30 ++++++++++++++++++++++++--- src/mesh/PhoneAPI.h | 3 +++ src/nimble/NimbleBluetooth.cpp | 11 +++++++++- src/platform/nrf52/NRF52Bluetooth.cpp | 11 +++++++++- 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index b19f96bb5..79cd91af5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,7 +8,7 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.82.4 + - trufflehog@3.82.5 - yamllint@1.35.1 - bandit@1.7.10 - checkov@3.2.255 diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index c3ebf3c6e..1be61759a 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -8,6 +8,7 @@ #include "MeshTypes.h" #include "NodeStatus.h" +#include "configuration.h" #include "mesh-pb-constants.h" #include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 103572990..ecc5effe9 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -8,6 +8,7 @@ #include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" +#include "PacketHistory.h" #include "PhoneAPI.h" #include "PowerFSM.h" #include "RadioInterface.h" @@ -31,6 +32,7 @@ PhoneAPI::PhoneAPI() { lastContactMsec = millis(); + std::fill(std::begin(recentToRadioPacketIds), std::end(recentToRadioPacketIds), 0); } PhoneAPI::~PhoneAPI() @@ -109,8 +111,6 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep lastContactMsec = millis(); - // return (lastContactMsec != 0) && - memset(&toRadioScratch, 0, sizeof(toRadioScratch)); if (pb_decode_from_bytes(buf, bufLength, &meshtastic_ToRadio_msg, &toRadioScratch)) { switch (toRadioScratch.which_payload_variant) { @@ -572,12 +572,35 @@ void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t reply service->sendClientNotification(cn); } +bool PhoneAPI::wasSeenRecently(uint32_t id) +{ + for (int i = 0; i < 20; i++) { + if (recentToRadioPacketIds[i] == id) { + return true; + } + if (recentToRadioPacketIds[i] == 0) { + recentToRadioPacketIds[i] = id; + return false; + } + } + // If the array is full, shift all elements to the left and add the new id at the end + memmove(recentToRadioPacketIds, recentToRadioPacketIds + 1, (19) * sizeof(uint32_t)); + recentToRadioPacketIds[19] = id; + return false; +} + /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) { printPacket("PACKET FROM PHONE", &p); + + if (p.id > 0 && wasSeenRecently(p.id)) { + LOG_DEBUG("Ignoring packet from phone, already seen recently\n"); + return false; + } + if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && lastPortNumToRadio[p.decoded.portnum] && Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], THIRTY_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); @@ -586,7 +609,8 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) } else if (p.decoded.portnum == meshtastic_PortNum_POSITION_APP && lastPortNumToRadio[p.decoded.portnum] && Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], FIVE_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); - sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); + // FIXME: Figure out why this continues to happen + // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); return false; } lastPortNumToRadio[p.decoded.portnum] = millis(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index cf6f55416..3247fee5c 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -52,6 +52,7 @@ class PhoneAPI // Hashmap of timestamps for last time we received a packet on the API per portnum std::unordered_map lastPortNumToRadio; + uint32_t recentToRadioPacketIds[20]; // Last 20 ToRadio MeshPacket IDs we have seen /** * Each packet sent to the phone has an incrementing count @@ -159,6 +160,8 @@ class PhoneAPI /// begin a new connection void handleStartConfig(); + bool wasSeenRecently(uint32_t packetId); + /** * Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it * @return true true if a packet was queued for sending diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 03fa80415..eedfe1a71 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -44,6 +44,9 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ +// Last ToRadio value received from the phone +static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; + class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks { virtual void onWrite(NimBLECharacteristic *pCharacteristic) @@ -51,7 +54,13 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks LOG_INFO("To Radio onwrite\n"); auto val = pCharacteristic->getValue(); - bluetoothPhoneAPI->handleToRadio(val.data(), val.length()); + if (memcmp(lastToRadio, val.data(), val.length()) != 0) { + LOG_DEBUG("New ToRadio packet\n"); + memcpy(lastToRadio, val.data(), val.length()); + bluetoothPhoneAPI->handleToRadio(val.data(), val.length()); + } else { + LOG_DEBUG("Dropping duplicate ToRadio packet we just saw\n"); + } } }; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index ec3ff3e8d..fbc0728d7 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -141,10 +141,19 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e } authorizeRead(conn_hdl); } +// Last ToRadio value received from the phone +static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; + void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); + if (memcmp(lastToRadio, data, len) != 0) { + LOG_DEBUG("New ToRadio packet\n"); + memcpy(lastToRadio, data, len); + bluetoothPhoneAPI->handleToRadio(data, len); + } else { + LOG_DEBUG("Dropping duplicate ToRadio packet we just saw\n"); + } } void setupMeshService(void) From 36a66df923d99b2d2bd6287c2a9fd8f8009802e3 Mon Sep 17 00:00:00 2001 From: David Huang Date: Fri, 27 Sep 2024 21:52:12 -0500 Subject: [PATCH 1279/1377] Don't log "Setting DIO2 as RF switch" unless we're actually going to do it. Also, if there's an error setting DIO2, log the error code. --- src/mesh/SX126xInterface.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 0ca5ef984..30024daf0 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -103,21 +103,19 @@ template bool SX126xInterface::init() LOG_DEBUG("Current limit set to %f\n", currentLimit); LOG_DEBUG("Current limit set result %d\n", res); -#ifdef SX126X_DIO2_AS_RF_SWITCH - LOG_DEBUG("Setting DIO2 as RF switch\n"); - bool dio2AsRfSwitch = true; -#elif defined(ARCH_PORTDUINO) - bool dio2AsRfSwitch = false; - if (settingsMap[dio2_as_rf_switch]) { - LOG_DEBUG("Setting DIO2 as RF switch\n"); - dio2AsRfSwitch = true; - } -#else - LOG_DEBUG("Setting DIO2 as not RF switch\n"); - bool dio2AsRfSwitch = false; -#endif if (res == RADIOLIB_ERR_NONE) { +#ifdef SX126X_DIO2_AS_RF_SWITCH + bool dio2AsRfSwitch = true; +#elif defined(ARCH_PORTDUINO) + bool dio2AsRfSwitch = false; + if (settingsMap[dio2_as_rf_switch]) { + dio2AsRfSwitch = true; + } +#else + bool dio2AsRfSwitch = false; +#endif res = lora.setDio2AsRfSwitch(dio2AsRfSwitch); + LOG_DEBUG("Set DIO2 as %sRF switch, result: %d\n", dio2AsRfSwitch ? "" : "not ", res); } // If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has From 7f59cb54ef2187673ef43c273ccf9557156bffc6 Mon Sep 17 00:00:00 2001 From: David Huang Date: Fri, 27 Sep 2024 23:35:57 -0500 Subject: [PATCH 1280/1377] Instead of having LipoBatteryLevel forward requests to AnalogBatteryLevel if there's no Lipo sensor, just have lipoInit return false. The forwarding didn't work because it never called analogInit. --- src/Power.cpp | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index c71d17586..6ed937648 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1068,10 +1068,9 @@ bool Power::axpChipInit() #if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) /** - * Wrapper class for an I2C MAX17048 Lipo battery sensor. If there is no - * I2C sensor present, the class falls back to analog battery sensing + * Wrapper class for an I2C MAX17048 Lipo battery sensor. */ -class LipoBatteryLevel : public AnalogBatteryLevel +class LipoBatteryLevel : public HasBatteryLevel { private: MAX17048Singleton *max17048 = nullptr; @@ -1096,52 +1095,27 @@ class LipoBatteryLevel : public AnalogBatteryLevel /** * Battery state of charge, from 0 to 100 or -1 for unknown */ - virtual int getBatteryPercent() override - { - if (!max17048->isInitialised()) - return AnalogBatteryLevel::getBatteryPercent(); - return max17048->getBusBatteryPercent(); - } + virtual int getBatteryPercent() override { return max17048->getBusBatteryPercent(); } /** * The raw voltage of the battery in millivolts, or NAN if unknown */ - virtual uint16_t getBattVoltage() override - { - if (!max17048->isInitialised()) - return AnalogBatteryLevel::getBattVoltage(); - return max17048->getBusVoltageMv(); - } + virtual uint16_t getBattVoltage() override { return max17048->getBusVoltageMv(); } /** * return true if there is a battery installed in this unit */ - virtual bool isBatteryConnect() override - { - if (!max17048->isInitialised()) - return AnalogBatteryLevel::isBatteryConnect(); - return max17048->isBatteryConnected(); - } + virtual bool isBatteryConnect() override { return max17048->isBatteryConnected(); } /** * return true if there is an external power source detected */ - virtual bool isVbusIn() override - { - if (!max17048->isInitialised()) - return AnalogBatteryLevel::isVbusIn(); - return max17048->isExternallyPowered(); - } + virtual bool isVbusIn() override { return max17048->isExternallyPowered(); } /** * return true if the battery is currently charging */ - virtual bool isCharging() override - { - if (!max17048->isInitialised()) - return AnalogBatteryLevel::isCharging(); - return max17048->isBatteryCharging(); - } + virtual bool isCharging() override { return max17048->isBatteryCharging(); } }; LipoBatteryLevel lipoLevel; @@ -1153,6 +1127,8 @@ bool Power::lipoInit() { bool result = lipoLevel.runOnce(); LOG_DEBUG("Power::lipoInit lipo sensor is %s\n", result ? "ready" : "not ready yet"); + if (!result) + return false; batteryLevel = &lipoLevel; return true; } From 48fa9f2242641e27a51b2f928f99ee648529194e Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sat, 28 Sep 2024 09:34:37 +0200 Subject: [PATCH 1281/1377] Only check threshold if GPS softsleep is supported --- src/gps/GPS.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 569254d02..fdad2b24b 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -916,20 +916,22 @@ void GPS::down() softsleepSupported = true; #endif - // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP? - // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050 - // https://www.desmos.com/calculator/6gvjghoumr - // This is not particularly accurate, but probably an impromevement over a single, fixed threshold - uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22)); - LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000); - - // If update interval too short: softsleep (if supported by hardware) - if (softsleepSupported && updateInterval < hardsleepThreshold) - setPowerState(GPS_SOFTSLEEP, sleepTime); + if (softsleepSupported) { + // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP? + // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050 + // https://www.desmos.com/calculator/6gvjghoumr + // This is not particularly accurate, but probably an impromevement over a single, fixed threshold + uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22)); + LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000); + // If update interval too short: softsleep (if supported by hardware) + if (updateInterval < hardsleepThreshold) { + setPowerState(GPS_SOFTSLEEP, sleepTime); + return; + } + } // If update interval long enough (or softsleep unsupported): hardsleep instead - else - setPowerState(GPS_HARDSLEEP, sleepTime); + setPowerState(GPS_HARDSLEEP, sleepTime); } } From 5ff265c196ad1015f9ab537d330b307e266cc372 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 27 Sep 2024 21:13:53 -0500 Subject: [PATCH 1282/1377] First stab at enabling store and forward for Native --- src/memGet.cpp | 4 ++++ src/modules/{esp32 => }/StoreForwardModule.cpp | 12 ++++++++---- src/modules/{esp32 => }/StoreForwardModule.h | 0 3 files changed, 12 insertions(+), 4 deletions(-) rename src/modules/{esp32 => }/StoreForwardModule.cpp (98%) rename src/modules/{esp32 => }/StoreForwardModule.h (100%) diff --git a/src/memGet.cpp b/src/memGet.cpp index e982ef7ee..ef1102f1e 100644 --- a/src/memGet.cpp +++ b/src/memGet.cpp @@ -57,6 +57,8 @@ uint32_t MemGet::getFreePsram() { #ifdef ARCH_ESP32 return ESP.getFreePsram(); +#elif defined(ARCH_PORTDUINO) + return 4194252; #else return 0; #endif @@ -71,6 +73,8 @@ uint32_t MemGet::getPsramSize() { #ifdef ARCH_ESP32 return ESP.getPsramSize(); +#elif defined(ARCH_PORTDUINO) + return 4194252; #else return 0; #endif diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp similarity index 98% rename from src/modules/esp32/StoreForwardModule.cpp rename to src/modules/StoreForwardModule.cpp index 51ec2a942..29fbd8d92 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -35,7 +35,7 @@ uint32_t heartbeatInterval = 60; // Default to 60 seconds, adjust as needed int32_t StoreForwardModule::runOnce() { -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) if (moduleConfig.store_forward.enabled && is_server) { // Send out the message queue. if (this->busy) { @@ -82,8 +82,12 @@ void StoreForwardModule::populatePSRAM() uint32_t numberOfPackets = (this->records ? this->records : (((memGet.getFreePsram() / 3) * 2) / sizeof(PacketHistoryStruct))); this->records = numberOfPackets; - +#if defined(ARCH_ESP32) this->packetHistory = static_cast(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct))); +#elif defined(ARCH_PORTDUINO) + this->packetHistory = static_cast(calloc(numberOfPackets, sizeof(PacketHistoryStruct))); + +#endif LOG_DEBUG("*** After PSRAM initialization: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreePsram(), memGet.getPsramSize()); @@ -376,7 +380,7 @@ void StoreForwardModule::statsSend(uint32_t to) */ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &mp) { -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) if (moduleConfig.store_forward.enabled) { if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { @@ -559,7 +563,7 @@ StoreForwardModule::StoreForwardModule() ProtobufModule("StoreForward", meshtastic_PortNum_STORE_FORWARD_APP, &meshtastic_StoreAndForward_msg) { -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) isPromiscuous = true; // Brown chicken brown cow diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/StoreForwardModule.h similarity index 100% rename from src/modules/esp32/StoreForwardModule.h rename to src/modules/StoreForwardModule.h From 6e1aa52723d21319e620a535b39ede23f9270ad9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 27 Sep 2024 21:22:27 -0500 Subject: [PATCH 1283/1377] More store-n-forward on native --- src/graphics/Screen.cpp | 5 +++-- src/mesh/MeshService.h | 4 ++-- src/mesh/NodeDB.cpp | 3 ++- src/modules/Modules.cpp | 5 ++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f95146318..d14dd95a9 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -58,10 +58,11 @@ along with this program. If not, see . #ifdef ARCH_ESP32 #include "esp_task_wdt.h" -#include "modules/esp32/StoreForwardModule.h" +#include "modules/StoreForwardModule.h" #endif #if ARCH_PORTDUINO +#include "modules/StoreForwardModule.h" #include "platform/portduino/PortduinoGlue.h" #endif @@ -2822,4 +2823,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN +#endif // HAS_SCREEN \ No newline at end of file diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index e78c2be2c..b91fedada 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -13,9 +13,9 @@ #if defined(ARCH_PORTDUINO) && !HAS_RADIO #include "../platform/portduino/SimRadio.h" #endif -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) #if !MESHTASTIC_EXCLUDE_STOREFORWARD -#include "modules/esp32/StoreForwardModule.h" +#include "modules/StoreForwardModule.h" #endif #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 869729890..6c554b538 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -32,12 +32,13 @@ #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif -#include "modules/esp32/StoreForwardModule.h" +#include "modules/StoreForwardModule.h" #include #include #endif #ifdef ARCH_PORTDUINO +#include "modules/StoreForwardModule.h" #include "platform/portduino/PortduinoGlue.h" #endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index eca0e8ac9..eedb3a37d 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -47,6 +47,9 @@ #endif #if ARCH_PORTDUINO #include "input/LinuxInputImpl.h" +#if !MESHTASTIC_EXCLUDE_STOREFORWARD +#include "modules/StoreForwardModule.h" +#endif #endif #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" @@ -67,7 +70,7 @@ #include "modules/esp32/PaxcounterModule.h" #endif #if !MESHTASTIC_EXCLUDE_STOREFORWARD -#include "modules/esp32/StoreForwardModule.h" +#include "modules/StoreForwardModule.h" #endif #endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) From cc101f9cd251e4f1fbac5fa3ec34a4379e70b707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 12:07:05 +0200 Subject: [PATCH 1284/1377] run the full suite when run on master --- .github/workflows/main_matrix.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 87ba8cf85..da619fe9f 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -32,7 +32,11 @@ jobs: name: Checkout base - id: jsonStep run: | - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) + if [[ "${{ github.ref }}" == "refs/heads/master" ]]; then + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) + else + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) + fi echo "$TARGETS" echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT outputs: From f28f5e07b773238e5e065845b52441efe2d57b19 Mon Sep 17 00:00:00 2001 From: caveman99 <25002+caveman99@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:38:10 +0000 Subject: [PATCH 1285/1377] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 6ac91926c..83c78e26e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 6ac91926c201c15c429cb5d41684f0f40cb7dce8 +Subproject commit 83c78e26e39031ae1c17ba5e50d0898842719c7f diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index b323ddf07..c04f4adb9 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -209,6 +209,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_SEEED_XIAO_S3 = 81, /* Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1 */ meshtastic_HardwareModel_MS24SF1 = 82, + /* Lilygo TLora-C6 with the new ESP32-C6 MCU */ + meshtastic_HardwareModel_TLORA_C6 = 83, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ From c650e7d273500a90e610dcc54997a580ab578423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 13:42:32 +0200 Subject: [PATCH 1286/1377] finish TLora C6 Support, without bluetooth for now --- arch/esp32/esp32c6.ini | 2 +- src/SerialConsole.cpp | 3 ++- src/platform/esp32/architecture.h | 2 ++ variants/tlora_c6/platformio.ini | 1 - 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini index fad9f3865..3c2165642 100644 --- a/arch/esp32/esp32c6.ini +++ b/arch/esp32/esp32c6.ini @@ -1,6 +1,6 @@ [esp32c6_base] extends = esp32_base -platform = https://github.com/Jason2866/platform-espressif32.git#92cd8e2ed8ec8392b32adc89451aecfcbbaa1726 +platform = https://github.com/Jason2866/platform-espressif32.git#22faa566df8c789000f8136cd8d0aca49617af55 build_flags = ${arduino_base.build_flags} -Wall diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 15366d222..fe6ccdefe 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -46,7 +46,8 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con Port.setRX(SERIAL2_RX); #endif Port.begin(SERIAL_BAUD); -#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040) +#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040) || \ + defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) time_t timeout = millis(); while (!Port) { if (Throttle::isWithinTimespanMs(timeout, FIVE_SECONDS_MS)) { diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index a2e7dfc4e..01f416629 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -90,6 +90,8 @@ #define HW_VENDOR meshtastic_HardwareModel_TLORA_V2_1_1P6 #elif defined(TLORA_V2_1_18) #define HW_VENDOR meshtastic_HardwareModel_TLORA_V2_1_1P8 +#elif defined(TLORA_C6) +#define HW_VENDOR meshtastic_HardwareModel_TLORA_C6 #elif defined(T_DECK) #define HW_VENDOR meshtastic_HardwareModel_T_DECK #elif defined(T_WATCH_S3) diff --git a/variants/tlora_c6/platformio.ini b/variants/tlora_c6/platformio.ini index 3a8fd2c19..d042cd78b 100644 --- a/variants/tlora_c6/platformio.ini +++ b/variants/tlora_c6/platformio.ini @@ -3,7 +3,6 @@ extends = esp32c6_base board = esp32-c6-devkitm-1 build_flags = ${esp32c6_base.build_flags} - -D PRIVATE_HW -D TLORA_C6 -I variants/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 From da346159f7fff5647b5a8839c2768abfc2645293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 14:01:42 +0200 Subject: [PATCH 1287/1377] fix overzealous pin definitions --- variants/tlora_c6/variant.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/variants/tlora_c6/variant.h b/variants/tlora_c6/variant.h index e16bfb767..55635fe13 100644 --- a/variants/tlora_c6/variant.h +++ b/variants/tlora_c6/variant.h @@ -1,9 +1,8 @@ #define I2C_SDA 8 // I2C pins for this board #define I2C_SCL 9 -#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 7 // If defined we will blink this LED -#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. +#define LED_PIN 7 // If defined we will blink this LED +#define LED_STATE_ON 0 // State when LED is lit #define USE_SX1262 #define LORA_SCK 6 @@ -19,4 +18,4 @@ #define SX126X_RXEN 15 #define SX126X_TXEN 14 #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 From 12efedec42ac3d2961e21633fe3890a039fe8d86 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:24:13 +0200 Subject: [PATCH 1288/1377] Potential fix for bad Rx performance on T1000-E (#4885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Potential fix for bad Rx performance on T1000-E * validate and refactor RF switching, eliminate godmode --------- Co-authored-by: Thomas Göttgens --- src/mesh/LR11x0Interface.cpp | 74 +++++++------------ variants/ME25LS01-4Y10TD/platformio.ini | 4 +- variants/ME25LS01-4Y10TD/rfswitch.h | 15 ++++ variants/ME25LS01-4Y10TD/variant.h | 1 - variants/ME25LS01-4Y10TD_e-ink/platformio.ini | 4 +- variants/ME25LS01-4Y10TD_e-ink/rfswitch.h | 15 ++++ variants/ME25LS01-4Y10TD_e-ink/variant.h | 3 +- variants/MakePython_nRF52840_eink/variant.h | 2 - variants/MakePython_nRF52840_oled/variant.h | 2 - variants/portduino/variant.h | 3 +- variants/tracker-t1000-e/rfswitch.h | 13 ++++ variants/wio-sdk-wm1110/rfswitch.h | 15 ++++ variants/wio-t1000-s/rfswitch.h | 13 ++++ variants/wio-t1000-s/variant.h | 3 +- variants/wio-tracker-wm1110/rfswitch.h | 15 ++++ 15 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 variants/ME25LS01-4Y10TD/rfswitch.h create mode 100644 variants/ME25LS01-4Y10TD_e-ink/rfswitch.h create mode 100644 variants/tracker-t1000-e/rfswitch.h create mode 100644 variants/wio-sdk-wm1110/rfswitch.h create mode 100644 variants/wio-t1000-s/rfswitch.h create mode 100644 variants/wio-tracker-wm1110/rfswitch.h diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 2ec659b06..df3b91e23 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -3,6 +3,15 @@ #include "configuration.h" #include "error.h" #include "mesh/NodeDB.h" +#ifdef LR11X0_DIO_AS_RF_SWITCH +#include "rfswitch.h" +#else +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; +static const Module::RfSwitchMode_t rfswitch_table[] = { + {LR11x0::MODE_STBY, {}}, {LR11x0::MODE_RX, {}}, {LR11x0::MODE_TX, {}}, {LR11x0::MODE_TX_HP, {}}, + {LR11x0::MODE_TX_HF, {}}, {LR11x0::MODE_GNSS, {}}, {LR11x0::MODE_WIFI, {}}, END_OF_MODE_TABLE, +}; +#endif #ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" @@ -52,54 +61,6 @@ template bool LR11x0Interface::init() limitPower(); -#ifdef TRACKER_T1000_E // Tracker T1000E uses DIO5, DIO6, DIO7, DIO8 for RF switching - - static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, - RADIOLIB_LR11X0_DIO8, RADIOLIB_NC}; - - static const Module::RfSwitchMode_t rfswitch_table[] = { - // mode DIO5 DIO6 DIO7 DIO8 - {LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH}}, - {LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH}}, - {LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW}}, - {LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW}}, END_OF_MODE_TABLE, - }; - -#else - - // set RF switch configuration for Wio WM1110 - // Wio WM1110 uses DIO5 and DIO6 for RF switching - - static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, - RADIOLIB_NC}; - - static const Module::RfSwitchMode_t rfswitch_table[] = { - // mode DIO5 DIO6 - {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, - {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, - {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, - {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, - }; - -#endif - -// We need to do this before begin() call -#ifdef LR11X0_DIO_AS_RF_SWITCH - LOG_DEBUG("Setting DIO RF switch\n"); - bool dioAsRfSwitch = true; -#elif defined(ARCH_PORTDUINO) - bool dioAsRfSwitch = false; - if (settingsMap[dio2_as_rf_switch]) { - LOG_DEBUG("Setting DIO RF switch\n"); - dioAsRfSwitch = true; - } -#else - bool dioAsRfSwitch = false; -#endif - - if (dioAsRfSwitch) - lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); - int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); // \todo Display actual typename of the adapter, not just `LR11x0` LOG_INFO("LR11x0 init result %d\n", res); @@ -123,6 +84,23 @@ template bool LR11x0Interface::init() // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option if (res == RADIOLIB_ERR_NONE) res = lora.setRegulatorDCDC(); + +#ifdef LR11X0_DIO_AS_RF_SWITCH + bool dioAsRfSwitch = true; +#elif defined(ARCH_PORTDUINO) + bool dioAsRfSwitch = false; + if (settingsMap[dio2_as_rf_switch]) { + dioAsRfSwitch = true; + } +#else + bool dioAsRfSwitch = false; +#endif + + if (dioAsRfSwitch) { + lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); + LOG_DEBUG("Setting DIO RF switch\n", res); + } + if (res == RADIOLIB_ERR_NONE) { if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate res = lora.setRxBoostedGainMode(true); diff --git a/variants/ME25LS01-4Y10TD/platformio.ini b/variants/ME25LS01-4Y10TD/platformio.ini index 985919d2d..479a4e79c 100644 --- a/variants/ME25LS01-4Y10TD/platformio.ini +++ b/variants/ME25LS01-4Y10TD/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = me25ls01-4y10td board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD ;-DRADIOLIB_GODMODE +build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld @@ -12,4 +12,4 @@ lib_deps = ${nrf52840_base.lib_deps} ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = nrfutil -upload_port = /dev/ttyACM1 +upload_port = /dev/ttyACM1 \ No newline at end of file diff --git a/variants/ME25LS01-4Y10TD/rfswitch.h b/variants/ME25LS01-4Y10TD/rfswitch.h new file mode 100644 index 000000000..cda6364f5 --- /dev/null +++ b/variants/ME25LS01-4Y10TD/rfswitch.h @@ -0,0 +1,15 @@ +#include "RadioLib.h" +#include "nrf.h" + +// set RF switch configuration for Wio WM1110 +// Wio WM1110 uses DIO5 and DIO6 for RF switching + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; diff --git a/variants/ME25LS01-4Y10TD/variant.h b/variants/ME25LS01-4Y10TD/variant.h index 0da391f0b..e772069da 100644 --- a/variants/ME25LS01-4Y10TD/variant.h +++ b/variants/ME25LS01-4Y10TD/variant.h @@ -103,7 +103,6 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 1.6 #define LR11X0_DIO_AS_RF_SWITCH -#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 #define HAS_GPS 0 diff --git a/variants/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/ME25LS01-4Y10TD_e-ink/platformio.ini index 4e837abd8..f2e3a49e3 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -3,7 +3,7 @@ extends = nrf52840_base board = me25ls01-4y10td board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD_e-ink -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD ;-DRADIOLIB_GODMODE +build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD_e-ink -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81 @@ -16,4 +16,4 @@ lib_deps = zinggjm/GxEPD2@^1.5.8 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = nrfutil -upload_port = /dev/ttyACM1 +upload_port = /dev/ttyACM1 \ No newline at end of file diff --git a/variants/ME25LS01-4Y10TD_e-ink/rfswitch.h b/variants/ME25LS01-4Y10TD_e-ink/rfswitch.h new file mode 100644 index 000000000..cda6364f5 --- /dev/null +++ b/variants/ME25LS01-4Y10TD_e-ink/rfswitch.h @@ -0,0 +1,15 @@ +#include "RadioLib.h" +#include "nrf.h" + +// set RF switch configuration for Wio WM1110 +// Wio WM1110 uses DIO5 and DIO6 for RF switching + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/ME25LS01-4Y10TD_e-ink/variant.h index 105f9b873..797394ce6 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/ME25LS01-4Y10TD_e-ink/variant.h @@ -126,7 +126,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define LR11X0_DIO3_TCXO_VOLTAGE 1.6 #define LR11X0_DIO_AS_RF_SWITCH -#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 #define HAS_GPS 0 @@ -159,4 +158,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_ME25LS01_4Y10TD__ +#endif // _VARIANT_ME25LS01_4Y10TD__ \ No newline at end of file diff --git a/variants/MakePython_nRF52840_eink/variant.h b/variants/MakePython_nRF52840_eink/variant.h index 16f803dd2..00c8dc199 100644 --- a/variants/MakePython_nRF52840_eink/variant.h +++ b/variants/MakePython_nRF52840_eink/variant.h @@ -25,8 +25,6 @@ extern "C" { #define NUM_ANALOG_INPUTS (6) #define NUM_ANALOG_OUTPUTS (0) -#define RADIOLIB_GODMODE - // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // diff --git a/variants/MakePython_nRF52840_oled/variant.h b/variants/MakePython_nRF52840_oled/variant.h index a2a5fa80a..28d941171 100644 --- a/variants/MakePython_nRF52840_oled/variant.h +++ b/variants/MakePython_nRF52840_oled/variant.h @@ -25,8 +25,6 @@ extern "C" { #define NUM_ANALOG_INPUTS (6) #define NUM_ANALOG_OUTPUTS (0) -#define RADIOLIB_GODMODE - // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h index 70d7cbd52..b7b39d6e8 100644 --- a/variants/portduino/variant.h +++ b/variants/portduino/variant.h @@ -2,5 +2,4 @@ #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE settingsMap[maxtophone] -#define MAX_NUM_NODES settingsMap[maxnodes] -#define RADIOLIB_GODMODE 1 +#define MAX_NUM_NODES settingsMap[maxnodes] \ No newline at end of file diff --git a/variants/tracker-t1000-e/rfswitch.h b/variants/tracker-t1000-e/rfswitch.h new file mode 100644 index 000000000..e229d77cf --- /dev/null +++ b/variants/tracker-t1000-e/rfswitch.h @@ -0,0 +1,13 @@ +#include "RadioLib.h" +#include "nrf.h" + +static const uint32_t rfswitch_dio_pins[Module::RFSWITCH_MAX_PINS] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, + RADIOLIB_LR11X0_DIO7, RADIOLIB_LR11X0_DIO8, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 DIO7 DIO8 + {LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH}}, + {LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW}}, END_OF_MODE_TABLE, +}; \ No newline at end of file diff --git a/variants/wio-sdk-wm1110/rfswitch.h b/variants/wio-sdk-wm1110/rfswitch.h new file mode 100644 index 000000000..cda6364f5 --- /dev/null +++ b/variants/wio-sdk-wm1110/rfswitch.h @@ -0,0 +1,15 @@ +#include "RadioLib.h" +#include "nrf.h" + +// set RF switch configuration for Wio WM1110 +// Wio WM1110 uses DIO5 and DIO6 for RF switching + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; diff --git a/variants/wio-t1000-s/rfswitch.h b/variants/wio-t1000-s/rfswitch.h new file mode 100644 index 000000000..e229d77cf --- /dev/null +++ b/variants/wio-t1000-s/rfswitch.h @@ -0,0 +1,13 @@ +#include "RadioLib.h" +#include "nrf.h" + +static const uint32_t rfswitch_dio_pins[Module::RFSWITCH_MAX_PINS] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, + RADIOLIB_LR11X0_DIO7, RADIOLIB_LR11X0_DIO8, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 DIO7 DIO8 + {LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH}}, + {LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW}}, END_OF_MODE_TABLE, +}; \ No newline at end of file diff --git a/variants/wio-t1000-s/variant.h b/variants/wio-t1000-s/variant.h index 8894a40c5..eb6a34d6c 100644 --- a/variants/wio-t1000-s/variant.h +++ b/variants/wio-t1000-s/variant.h @@ -103,7 +103,6 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 1.6 #define LR11X0_DIO_AS_RF_SWITCH -#define LR11X0_DIO_RF_SWITCH_CONFIG 0x0f, 0x0, 0x09, 0x0B, 0x0A, 0x0, 0x4, 0x0 #define HAS_GPS 1 #define GNSS_AIROHA @@ -158,4 +157,4 @@ extern "C" { * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_WIO_SDK_WM1110_ +#endif // _VARIANT_WIO_SDK_WM1110_ \ No newline at end of file diff --git a/variants/wio-tracker-wm1110/rfswitch.h b/variants/wio-tracker-wm1110/rfswitch.h new file mode 100644 index 000000000..cda6364f5 --- /dev/null +++ b/variants/wio-tracker-wm1110/rfswitch.h @@ -0,0 +1,15 @@ +#include "RadioLib.h" +#include "nrf.h" + +// set RF switch configuration for Wio WM1110 +// Wio WM1110 uses DIO5 and DIO6 for RF switching + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; From 06dab4fa135c2ddf53851481d59968209af4c80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 16:26:55 +0200 Subject: [PATCH 1289/1377] Don't process RX when region unset --- src/mesh/RadioLibInterface.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 0968a6d89..aee43676d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -167,7 +167,7 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p) } } else { - LOG_WARN("send - lora tx disable because RegionCode_Unset\n"); + LOG_WARN("send - lora tx disabled because RegionCode_Unset\n"); packetPool.release(p); return ERRNO_DISABLED; } @@ -379,6 +379,14 @@ void RadioLibInterface::handleReceiveInterrupt() xmitMsec = getPacketTime(length); +#ifndef DISABLE_WELCOME_UNSET + if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { + LOG_WARN("recv - lora rx disabled because RegionCode_Unset\n"); + airTime->logAirtime(RX_ALL_LOG, xmitMsec); + return; + } +#endif + int state = iface->readData(radiobuf, length); if (state != RADIOLIB_ERR_NONE) { LOG_ERROR("ignoring received packet due to error=%d\n", state); From 1e8d089c4ea8a56e293a5b6f4a4cd3e02d78617b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 16:40:00 +0200 Subject: [PATCH 1290/1377] yolo! --- .github/workflows/main_matrix.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index da619fe9f..54e721083 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -1,4 +1,7 @@ name: CI +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true #concurrency: # group: ${{ github.ref }} # cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} From 479b0856b4d9b04628086b70fd02e5f26712bc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 19:07:11 +0200 Subject: [PATCH 1291/1377] use rfswitch definition and update radiolib --- platformio.ini | 2 +- variants/tlora_t3s3_v1/rfswitch.h | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 variants/tlora_t3s3_v1/rfswitch.h diff --git a/platformio.ini b/platformio.ini index f528a6fde..d781f0b12 100644 --- a/platformio.ini +++ b/platformio.ini @@ -91,7 +91,7 @@ monitor_filters = direct lib_deps = ;jgromes/RadioLib@~7.0.0 ;7.0.1pre needed for LR1121 support and SX127x CRC Bugfix - https://github.com/jgromes/RadioLib.git#98ad30eb103d22bb7e75f3cc900597403b0cfb1f + https://github.com/jgromes/RadioLib.git#38fc7a97a4c195b7c10aa94215a1c53ec18a56ef https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 diff --git a/variants/tlora_t3s3_v1/rfswitch.h b/variants/tlora_t3s3_v1/rfswitch.h new file mode 100644 index 000000000..19080cec6 --- /dev/null +++ b/variants/tlora_t3s3_v1/rfswitch.h @@ -0,0 +1,11 @@ +#include "RadioLib.h" + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; \ No newline at end of file From ec23189407b0c8390d3faf4d5a50354f663bd82c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 28 Sep 2024 13:04:50 -0500 Subject: [PATCH 1292/1377] Making some board levels extra for uncommon hardware --- variants/monteops_hw1/platformio.ini | 1 + variants/senselora_rp2040/platformio.ini | 1 + variants/tbeam_v07/platformio.ini | 1 + 3 files changed, 3 insertions(+) diff --git a/variants/monteops_hw1/platformio.ini b/variants/monteops_hw1/platformio.ini index 551419abc..4b42dce39 100644 --- a/variants/monteops_hw1/platformio.ini +++ b/variants/monteops_hw1/platformio.ini @@ -1,5 +1,6 @@ ; MonteOps M.Node/M.Backbone/M.Eagle hardware based on hardware variant #1 (RAK4630 based) [env:monteops_hw1] +board_level = extra extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/monteops_hw1 -D MONTEOPS_HW1 diff --git a/variants/senselora_rp2040/platformio.ini b/variants/senselora_rp2040/platformio.ini index c719bd261..852ecbbb9 100644 --- a/variants/senselora_rp2040/platformio.ini +++ b/variants/senselora_rp2040/platformio.ini @@ -1,4 +1,5 @@ [env:senselora_rp2040] +board_level = extra extends = rp2040_base board = rpipico upload_protocol = picotool diff --git a/variants/tbeam_v07/platformio.ini b/variants/tbeam_v07/platformio.ini index 105d65912..0cba92400 100644 --- a/variants/tbeam_v07/platformio.ini +++ b/variants/tbeam_v07/platformio.ini @@ -1,5 +1,6 @@ ; The original TBEAM board without the AXP power chip and a few other changes [env:tbeam0_7] +board_level = extra extends = esp32_base board = ttgo-t-beam build_flags = From 6a355616c7f71b100e0d5d0fe0cf174a106d36f0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 28 Sep 2024 13:06:01 -0500 Subject: [PATCH 1293/1377] Another extra --- variants/Dongle_nRF52840-pca10059-v1/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini index c87f83d39..a98656e86 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -1,4 +1,5 @@ [env:pca10059_diy_eink] +board_level = extra extends = nrf52840_base board = nordic_pca10059 build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 From 22ecbcb04611b6c00a512ce4b417dc963ed60b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 20:35:48 +0200 Subject: [PATCH 1294/1377] Create build_esp32_c6.yml --- .github/workflows/build_esp32_c6.yml | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/build_esp32_c6.yml diff --git a/.github/workflows/build_esp32_c6.yml b/.github/workflows/build_esp32_c6.yml new file mode 100644 index 000000000..3be813afa --- /dev/null +++ b/.github/workflows/build_esp32_c6.yml @@ -0,0 +1,35 @@ +name: Build ESP32-C6 + +on: + workflow_call: + inputs: + board: + required: true + type: string + +permissions: read-all + +jobs: + build-esp32-c6: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build ESP32-C6 + id: build + uses: ./.github/actions/build-variant + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + board: ${{ inputs.board }} + remove-debug-flags: >- + ./arch/esp32/esp32.ini + ./arch/esp32/esp32s2.ini + ./arch/esp32/esp32s3.ini + ./arch/esp32/esp32c3.ini + ./arch/esp32/esp32c6.ini + build-script-path: bin/build-esp32.sh + ota-firmware-source: firmware-c3.bin + ota-firmware-target: release/bleota-c3.bin + artifact-paths: | + release/*.bin + release/*.elf From 448afb8345e306dfe040083ded179166db1964ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 20:37:07 +0200 Subject: [PATCH 1295/1377] Add C6 Target --- .github/workflows/main_matrix.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 54e721083..fc3a944cf 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -46,6 +46,7 @@ jobs: esp32: ${{ steps.jsonStep.outputs.esp32 }} esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} + esp32c3: ${{ steps.jsonStep.outputs.esp32c6 }} nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} rp2040: ${{ steps.jsonStep.outputs.rp2040 }} stm32: ${{ steps.jsonStep.outputs.stm32 }} @@ -93,6 +94,15 @@ jobs: uses: ./.github/workflows/build_esp32_c3.yml with: board: ${{ matrix.board }} + + build-esp32-c6: + needs: setup + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} + uses: ./.github/workflows/build_esp32_c6.yml + with: + board: ${{ matrix.board }} build-nrf52: needs: setup @@ -151,6 +161,7 @@ jobs: build-esp32, build-esp32-s3, build-esp32-c3, + build-esp32-c6, build-nrf52, build-rpi2040, build-stm32, From 9513c68544a3469d01572c29910454971a967805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 20:37:38 +0200 Subject: [PATCH 1296/1377] Update main_matrix.yml --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index fc3a944cf..adf3891ba 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -46,7 +46,7 @@ jobs: esp32: ${{ steps.jsonStep.outputs.esp32 }} esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c6 }} + esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} rp2040: ${{ steps.jsonStep.outputs.rp2040 }} stm32: ${{ steps.jsonStep.outputs.stm32 }} From a542d41ac79f619707c01544a39a6a186fdb6217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 28 Sep 2024 21:59:13 +0200 Subject: [PATCH 1297/1377] rats, missed one --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index adf3891ba..2ca3576aa 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: false matrix: - arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check] + arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check] runs-on: ubuntu-latest steps: - id: checkout From a70d5ee9d879b8530bc486f7f65b4965e2e25e52 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 28 Sep 2024 15:49:37 -0500 Subject: [PATCH 1298/1377] Temporarily remove native AGAIN due to gather-artifacts failure --- .github/workflows/main_matrix.yml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 2ca3576aa..df4929da7 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -94,7 +94,7 @@ jobs: uses: ./.github/workflows/build_esp32_c3.yml with: board: ${{ matrix.board }} - + build-esp32-c6: needs: setup strategy: @@ -137,8 +137,8 @@ jobs: package-raspbian-armv7l: uses: ./.github/workflows/package_raspbian_armv7l.yml - package-native: - uses: ./.github/workflows/package_amd64.yml + # package-native: + # uses: ./.github/workflows/package_amd64.yml after-checks: runs-on: ubuntu-latest @@ -156,8 +156,7 @@ jobs: contents: write pull-requests: write runs-on: ubuntu-latest - needs: - [ + needs: [ build-esp32, build-esp32-s3, build-esp32-c3, @@ -167,7 +166,7 @@ jobs: build-stm32, package-raspbian, package-raspbian-armv7l, - package-native, + # package-native, ] steps: - name: Checkout code @@ -354,15 +353,15 @@ jobs: asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb asset_content_type: application/vnd.debian.binary-package - - name: Add raspbian amd64 .deb - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - asset_content_type: application/vnd.debian.binary-package + # - name: Add raspbian amd64 .deb + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ github.token }} + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + # asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + # asset_content_type: application/vnd.debian.binary-package - name: Bump version.properties run: >- From 233962104c5989f50a9589862b5b0ef539d47c55 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 19:12:10 -0500 Subject: [PATCH 1299/1377] [create-pull-request] automated change (#4897) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 8c8e94bec..5f506ee26 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 3 +build = 4 From 7e0665a5cdc0f13cf26033652a636eb391e804c7 Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Sun, 29 Sep 2024 03:01:20 -0700 Subject: [PATCH 1300/1377] comment on PR with artifact link (#4896) --- .github/workflows/main_matrix.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index df4929da7..68f7bb31c 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -235,17 +235,11 @@ jobs: path: ./*.elf retention-days: 30 - - name: Create request artifacts - continue-on-error: true # FIXME: Why are we getting 502, but things still work? - if: ${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request' }} - uses: gavv/pull-request-artifacts@v2.1.0 + - uses: PicoCentauri/comment-artifact@main with: - commit: ${{ (github.event.pull_request_target || github.event.pull_request).head.sha }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - artifacts-token: ${{ secrets.ARTIFACTS_TOKEN }} - artifacts-repo: meshtastic/artifacts - artifacts-branch: device - artifacts: ./firmware-${{ steps.version.outputs.version }}.zip + name: firmware-${{ steps.version.outputs.version }} + description: "Download firmware-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation" + github-token: ${{ secrets.GITHUB_TOKEN }} release-artifacts: runs-on: ubuntu-latest From fa29386eb766dfc33308d3c86893e8a0e739d28f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 12:40:17 +0200 Subject: [PATCH 1301/1377] Update main_matrix.yml --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 68f7bb31c..bb2f43503 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -35,7 +35,7 @@ jobs: name: Checkout base - id: jsonStep run: | - if [[ "${{ github.ref }}" == "refs/heads/master" ]]; then + if [[ "${{ github.ref_name }}" == "master" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) From ef2035a60c5e92159e87eb0258eb99f1bdc4f607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 13:52:47 +0200 Subject: [PATCH 1302/1377] runner debug --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index bb2f43503..2daaeea24 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -40,7 +40,7 @@ jobs: else TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) fi - echo "$TARGETS" + echo "${{ github.ref_name }} ${{ github.base_ref }} ${{ github.head_ref }} ${{ github.ref }} $TARGETS" echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT outputs: esp32: ${{ steps.jsonStep.outputs.esp32 }} From d0440f3cac06d5c8f91f7a622cce320925044f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 13:54:46 +0200 Subject: [PATCH 1303/1377] don't interfere with the trunk check --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index bb2f43503..4c1493ae4 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -1,6 +1,6 @@ name: CI concurrency: - group: ${{ github.head_ref || github.run_id }} + group: ci-${{ github.head_ref || github.run_id }} cancel-in-progress: true #concurrency: # group: ${{ github.ref }} From 42a330118847213f7116c0b4047bbbb608aadb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 13:58:07 +0200 Subject: [PATCH 1304/1377] Update main_matrix.yml --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 62f215811..29602600a 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -40,7 +40,7 @@ jobs: else TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) fi - echo "${{ github.ref_name }} ${{ github.base_ref }} ${{ github.head_ref }} ${{ github.ref }} $TARGETS" + echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} Head: ${{ github.head_ref }} Ref: ${{ github.ref }} $TARGETS" echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT outputs: esp32: ${{ steps.jsonStep.outputs.esp32 }} From 88af23319ca80b3988c7cc588d23be90628de0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 14:00:36 +0200 Subject: [PATCH 1305/1377] Aha! --- .github/workflows/main_matrix.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 29602600a..529de3de5 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -35,12 +35,12 @@ jobs: name: Checkout base - id: jsonStep run: | - if [[ "${{ github.ref_name }}" == "master" ]]; then + if [[ "${{ github.head_ref }}" == "refs/heads/master" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) fi - echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} Head: ${{ github.head_ref }} Ref: ${{ github.ref }} $TARGETS" + echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} Head: ${{ github.head_ref }} Ref: ${{ github.ref }} Targets: $TARGETS" echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT outputs: esp32: ${{ steps.jsonStep.outputs.esp32 }} From 2e935fd9430b9f1cefed04784e908b42b5a67f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 14:03:22 +0200 Subject: [PATCH 1306/1377] why is this different than github docs? --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 529de3de5..b79b41d52 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -35,7 +35,7 @@ jobs: name: Checkout base - id: jsonStep run: | - if [[ "${{ github.head_ref }}" == "refs/heads/master" ]]; then + if [[ "${{ github.head_ref }}" == "master" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) From dcb2707d94d535cbca44abda71e941f9d5c03b17 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 29 Sep 2024 07:28:20 -0500 Subject: [PATCH 1307/1377] Return queue status on rate limit throttling (#4901) --- src/mesh/PhoneAPI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index ecc5effe9..ad4a1f33d 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -605,10 +605,14 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], THIRTY_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "TraceRoute can only be sent once every 30 seconds"); + meshtastic_QueueStatus qs = router->getQueueStatus(); + service->sendQueueStatusToPhone(qs, 0, p.id); return false; } else if (p.decoded.portnum == meshtastic_PortNum_POSITION_APP && lastPortNumToRadio[p.decoded.portnum] && Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], FIVE_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); + meshtastic_QueueStatus qs = router->getQueueStatus(); + service->sendQueueStatusToPhone(qs, 0, p.id); // FIXME: Figure out why this continues to happen // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); return false; From 403e5c304ecf22ed084b341c203147d5be2400c9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 29 Sep 2024 07:29:53 -0500 Subject: [PATCH 1308/1377] Fix: Not being able to stop Ext. Notification nagging for screenless devices (#4899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move logic up to button thread for screen-less devices * Comment doesn't apply * Fiddy --------- Co-authored-by: Thomas Göttgens --- src/ButtonThread.cpp | 5 +++++ src/graphics/Screen.cpp | 8 +------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 5351fa049..9e6ef55f5 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -124,6 +124,11 @@ int32_t ButtonThread::runOnce() switch (btnEvent) { case BUTTON_EVENT_PRESSED: { LOG_BUTTON("press!\n"); + // If a nag notification is running, stop it and prevent other actions + if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { + externalNotificationModule->stopNow(); + return 50; + } #ifdef BUTTON_PIN if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) != moduleConfig.canned_message.inputbroker_pin_press) || diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 4f6a5a45f..ef6b05ca4 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1884,13 +1884,7 @@ int32_t Screen::runOnce() handleSetOn(false); break; case Cmd::ON_PRESS: - // If a nag notification is running, stop it - if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { - externalNotificationModule->stopNow(); - } else { - // Don't advance the screen if we just wanted to switch off the nag notification - handleOnPress(); - } + handleOnPress(); break; case Cmd::SHOW_PREV_FRAME: handleShowPrevFrame(); From d41d4c930e9b8af4f3045d3b32702a8278c988f3 Mon Sep 17 00:00:00 2001 From: dahanc Date: Sun, 29 Sep 2024 07:30:10 -0500 Subject: [PATCH 1309/1377] =?UTF-8?q?When=20importing=20config,=20keep=20B?= =?UTF-8?q?luetooth=20on=20and=20defer=20rebooting=20until=20co=E2=80=A6?= =?UTF-8?q?=20(#4898)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * When importing config, keep Bluetooth on and defer rebooting until config is committed * One more place that was prematurely disabling Bluetooth --------- Co-authored-by: Ben Meadors --- src/modules/AdminModule.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 09311abda..8c354b6c8 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -583,7 +583,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) break; } - if (requiresReboot) { + if (requiresReboot && !hasOpenEditTransaction) { disableBluetooth(); } @@ -592,7 +592,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { - disableBluetooth(); + if (!hasOpenEditTransaction) + disableBluetooth(); switch (c.which_payload_variant) { case meshtastic_ModuleConfig_mqtt_tag: LOG_INFO("Setting module config: MQTT\n"); @@ -966,7 +967,7 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot) } else { LOG_INFO("Delaying save of changes to disk until the open transaction is committed\n"); } - if (shouldReboot) { + if (shouldReboot && !hasOpenEditTransaction) { reboot(DEFAULT_REBOOT_SECONDS); } } From d73cbf14d5b0cf8a3f94d8028b9ab60d297fed96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gjels=C3=B8?= <36234524+gjelsoe@users.noreply.github.com> Date: Sun, 29 Sep 2024 18:49:16 +0200 Subject: [PATCH 1310/1377] Get accelerometerThread running from AdminModule. (#4886) --- src/modules/AdminModule.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 8c354b6c8..4a7af4817 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -432,6 +432,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true && accelerometerThread->enabled == false) { + config.device.double_tap_as_button_press = c.payload_variant.device.double_tap_as_button_press; + accelerometerThread->enabled = true; accelerometerThread->start(); } #endif @@ -511,6 +513,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true && accelerometerThread->enabled == false) { + config.display.wake_on_tap_or_motion = c.payload_variant.display.wake_on_tap_or_motion; + accelerometerThread->enabled = true; accelerometerThread->start(); } #endif From 19f45d282f4305c3ef9781b717d549b4e176e58a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 29 Sep 2024 23:12:20 +0200 Subject: [PATCH 1311/1377] Update radiolib to 7.0.1 --- platformio.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index d781f0b12..b6b0d8249 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,9 +89,7 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = - ;jgromes/RadioLib@~7.0.0 - ;7.0.1pre needed for LR1121 support and SX127x CRC Bugfix - https://github.com/jgromes/RadioLib.git#38fc7a97a4c195b7c10aa94215a1c53ec18a56ef + jgromes/RadioLib@~7.0.1 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 From 3492c9aa995e83534a607b2e77a7f3f157e2d064 Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:17:23 +0200 Subject: [PATCH 1312/1377] Construct StoreForwardModule for Portduino as well (#4903) * Construct StoreForwardModule for Portduino as well * Remove duplicate variables --- src/modules/Modules.cpp | 8 +++++--- src/modules/StoreForwardModule.cpp | 3 --- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index eedb3a37d..554fad6a9 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -209,13 +209,15 @@ void setupModules() #if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO audioModule = new AudioModule(); #endif -#if !MESHTASTIC_EXCLUDE_STOREFORWARD - storeForwardModule = new StoreForwardModule(); -#endif #if !MESHTASTIC_EXCLUDE_PAXCOUNTER paxcounterModule = new PaxcounterModule(); #endif #endif +#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) +#if !MESHTASTIC_EXCLUDE_STOREFORWARD + storeForwardModule = new StoreForwardModule(); +#endif +#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION externalNotificationModule = new ExternalNotificationModule(); diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 29fbd8d92..5f30803a4 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -30,9 +30,6 @@ StoreForwardModule *storeForwardModule; -uint32_t lastHeartbeat = 0; -uint32_t heartbeatInterval = 60; // Default to 60 seconds, adjust as needed - int32_t StoreForwardModule::runOnce() { #if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) From 6f506cead573d4860448f686dedd0504699dd9af Mon Sep 17 00:00:00 2001 From: KodinLanewave Date: Sun, 29 Sep 2024 14:17:43 -0700 Subject: [PATCH 1313/1377] Update INA3221 to 1.0.1 (#4877) Added new release with compiler error fixes for INA3221 library - updating dependencies so new release will be included Co-authored-by: Ben Meadors --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index d781f0b12..fb39b55a0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -159,7 +159,7 @@ lib_deps = https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 - https://github.com/KodinLanewave/INA3221@^1.0.0 + https://github.com/KodinLanewave/INA3221@^1.0.1 lewisxhe/SensorLib@0.2.0 mprograms/QMC5883LCompass@^1.2.0 From b529099f906af25c40be06ff8573e2e4e4561eab Mon Sep 17 00:00:00 2001 From: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com> Date: Sun, 29 Sep 2024 20:08:23 -0700 Subject: [PATCH 1314/1377] Update main_matrix.yml --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index b79b41d52..81c4615b4 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -235,7 +235,7 @@ jobs: path: ./*.elf retention-days: 30 - - uses: PicoCentauri/comment-artifact@main + - uses: scruplelesswizard/comment-artifact@main with: name: firmware-${{ steps.version.outputs.version }} description: "Download firmware-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation" From 8ad89ba72422fa4f4c84f8c4968e013d15858964 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 30 Sep 2024 05:14:22 -0500 Subject: [PATCH 1315/1377] Allow for better target level Radiolib exclude plumbing (#4906) * WIP * LR11x0 * Anothern * =1 --- platformio.ini | 40 ++++++++++++++++---------------- src/main.cpp | 13 +++++------ src/mesh/InterfacesTemplates.cpp | 6 +++++ src/mesh/LLCC68Interface.cpp | 4 +++- src/mesh/LLCC68Interface.h | 5 ++-- src/mesh/LR1110Interface.cpp | 5 +++- src/mesh/LR1110Interface.h | 5 ++-- src/mesh/LR1120Interface.cpp | 5 +++- src/mesh/LR1120Interface.h | 5 ++-- src/mesh/LR1121Interface.cpp | 4 +++- src/mesh/LR1121Interface.h | 4 +++- src/mesh/LR11x0Interface.cpp | 4 +++- src/mesh/LR11x0Interface.h | 3 ++- src/mesh/RF95Interface.cpp | 2 ++ src/mesh/RF95Interface.h | 3 ++- src/mesh/RadioLibRF95.cpp | 4 +++- src/mesh/RadioLibRF95.h | 2 ++ src/mesh/SX1262Interface.cpp | 4 +++- src/mesh/SX1262Interface.h | 4 +++- src/mesh/SX1268Interface.cpp | 4 +++- src/mesh/SX1268Interface.h | 2 ++ src/mesh/SX126xInterface.cpp | 4 +++- src/mesh/SX126xInterface.h | 2 ++ src/mesh/SX1280Interface.cpp | 2 ++ src/mesh/SX1280Interface.h | 3 ++- src/mesh/SX128xInterface.cpp | 4 +++- variants/rak4631/platformio.ini | 3 +++ 27 files changed, 98 insertions(+), 48 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6f2a13865..4c85e2dd7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -60,26 +60,26 @@ build_flags = -Wno-missing-field-initializers -DUSE_THREAD_NAMES -DTINYGPS_OPTION_NO_CUSTOM_FIELDS -DPB_ENABLE_MALLOC=1 - -DRADIOLIB_EXCLUDE_CC1101 - -DRADIOLIB_EXCLUDE_NRF24 - -DRADIOLIB_EXCLUDE_RF69 - -DRADIOLIB_EXCLUDE_SX1231 - -DRADIOLIB_EXCLUDE_SX1233 - -DRADIOLIB_EXCLUDE_SI443X - -DRADIOLIB_EXCLUDE_RFM2X - -DRADIOLIB_EXCLUDE_AFSK - -DRADIOLIB_EXCLUDE_BELL - -DRADIOLIB_EXCLUDE_HELLSCHREIBER - -DRADIOLIB_EXCLUDE_MORSE - -DRADIOLIB_EXCLUDE_RTTY - -DRADIOLIB_EXCLUDE_SSTV - -DRADIOLIB_EXCLUDE_AX25 - -DRADIOLIB_EXCLUDE_DIRECT_RECEIVE - -DRADIOLIB_EXCLUDE_BELL - -DRADIOLIB_EXCLUDE_PAGER - -DRADIOLIB_EXCLUDE_FSK4 - -DRADIOLIB_EXCLUDE_APRS - -DRADIOLIB_EXCLUDE_LORAWAN + -DRADIOLIB_EXCLUDE_CC1101=1 + -DRADIOLIB_EXCLUDE_NRF24=1 + -DRADIOLIB_EXCLUDE_RF69=1 + -DRADIOLIB_EXCLUDE_SX1231=1 + -DRADIOLIB_EXCLUDE_SX1233=1 + -DRADIOLIB_EXCLUDE_SI443X=1 + -DRADIOLIB_EXCLUDE_RFM2X=1 + -DRADIOLIB_EXCLUDE_AFSK=1 + -DRADIOLIB_EXCLUDE_BELL=1 + -DRADIOLIB_EXCLUDE_HELLSCHREIBER=1 + -DRADIOLIB_EXCLUDE_MORSE=1 + -DRADIOLIB_EXCLUDE_RTTY=1 + -DRADIOLIB_EXCLUDE_SSTV=1 + -DRADIOLIB_EXCLUDE_AX25=1 + -DRADIOLIB_EXCLUDE_DIRECT_RECEIVE=1 + -DRADIOLIB_EXCLUDE_BELL=1 + -DRADIOLIB_EXCLUDE_PAGER=1 + -DRADIOLIB_EXCLUDE_FSK4=1 + -DRADIOLIB_EXCLUDE_APRS=1 + -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DBUILD_EPOCH=$UNIX_TIME diff --git a/src/main.cpp b/src/main.cpp index dfad9efbf..c11995837 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -885,7 +885,7 @@ void setup() } #endif -#if defined(RF95_IRQ) +#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); if (!rIf->init()) { @@ -899,7 +899,7 @@ void setup() } #endif -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { @@ -975,7 +975,7 @@ void setup() } #endif -#if defined(USE_LR1110) +#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); if (!rIf->init()) { @@ -989,7 +989,7 @@ void setup() } #endif -#if defined(USE_LR1120) +#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); if (!rIf->init()) { @@ -1003,7 +1003,7 @@ void setup() } #endif -#if defined(USE_LR1121) +#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); if (!rIf->init()) { @@ -1017,7 +1017,7 @@ void setup() } #endif -#if defined(USE_SX1280) +#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 if (!rIf) { rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); if (!rIf->init()) { @@ -1032,7 +1032,6 @@ void setup() #endif // check if the radio chip matches the selected region - if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) { LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index 329f0b48e..2720e8525 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -8,13 +8,19 @@ #include "api/ServerAPI.h" // We need this declaration for proper linking in derived classes +#if RADIOLIB_EXCLUDE_SX126X != 1 template class SX126xInterface; template class SX126xInterface; template class SX126xInterface; +#endif +#if RADIOLIB_EXCLUDE_SX128X != 1 template class SX128xInterface; +#endif +#if RADIOLIB_EXCLUDE_LR11X0 != 1 template class LR11x0Interface; template class LR11x0Interface; template class LR11x0Interface; +#endif #ifdef ARCH_STM32WL template class SX126xInterface; #endif diff --git a/src/mesh/LLCC68Interface.cpp b/src/mesh/LLCC68Interface.cpp index 8109765a6..d92ea5450 100644 --- a/src/mesh/LLCC68Interface.cpp +++ b/src/mesh/LLCC68Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "LLCC68Interface.h" #include "configuration.h" #include "error.h" @@ -6,4 +7,5 @@ LLCC68Interface::LLCC68Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R RADIOLIB_PIN_TYPE busy) : SX126xInterface(hal, cs, irq, rst, busy) { -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/LLCC68Interface.h b/src/mesh/LLCC68Interface.h index 7e0fa1439..1cd23e92b 100644 --- a/src/mesh/LLCC68Interface.h +++ b/src/mesh/LLCC68Interface.h @@ -1,5 +1,5 @@ #pragma once - +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "SX126xInterface.h" /** @@ -15,4 +15,5 @@ class LLCC68Interface : public SX126xInterface public: LLCC68Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/mesh/LR1110Interface.cpp b/src/mesh/LR1110Interface.cpp index c000bd838..5dbd3ff38 100644 --- a/src/mesh/LR1110Interface.cpp +++ b/src/mesh/LR1110Interface.cpp @@ -1,3 +1,5 @@ +#if RADIOLIB_EXCLUDE_LR11X0 != 1 + #include "LR1110Interface.h" #include "configuration.h" #include "error.h" @@ -6,4 +8,5 @@ LR1110Interface::LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R RADIOLIB_PIN_TYPE busy) : LR11x0Interface(hal, cs, irq, rst, busy) { -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/LR1110Interface.h b/src/mesh/LR1110Interface.h index 79e7c36ca..2a2e6e861 100644 --- a/src/mesh/LR1110Interface.h +++ b/src/mesh/LR1110Interface.h @@ -1,5 +1,5 @@ #pragma once - +#if RADIOLIB_EXCLUDE_LR11X0 != 1 #include "LR11x0Interface.h" /** @@ -10,4 +10,5 @@ class LR1110Interface : public LR11x0Interface public: LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/mesh/LR1120Interface.cpp b/src/mesh/LR1120Interface.cpp index 07e9e508d..a17ac87ef 100644 --- a/src/mesh/LR1120Interface.cpp +++ b/src/mesh/LR1120Interface.cpp @@ -1,3 +1,5 @@ +#if RADIOLIB_EXCLUDE_LR11X0 != 1 + #include "LR1120Interface.h" #include "configuration.h" #include "error.h" @@ -11,4 +13,5 @@ LR1120Interface::LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R bool LR1120Interface::wideLora() { return true; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/LR1120Interface.h b/src/mesh/LR1120Interface.h index a03fa0b20..d81a480a9 100644 --- a/src/mesh/LR1120Interface.h +++ b/src/mesh/LR1120Interface.h @@ -1,7 +1,7 @@ #pragma once +#if RADIOLIB_EXCLUDE_LR11X0 != 1 #include "LR11x0Interface.h" - /** * Our adapter for LR1120 wideband radios */ @@ -11,4 +11,5 @@ class LR1120Interface : public LR11x0Interface LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); bool wideLora() override; -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/mesh/LR1121Interface.cpp b/src/mesh/LR1121Interface.cpp index 0d6bba6ea..29bd07d08 100644 --- a/src/mesh/LR1121Interface.cpp +++ b/src/mesh/LR1121Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_LR11X0 != 1 #include "LR1121Interface.h" #include "configuration.h" #include "error.h" @@ -11,4 +12,5 @@ LR1121Interface::LR1121Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R bool LR1121Interface::wideLora() { return true; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/LR1121Interface.h b/src/mesh/LR1121Interface.h index 32a6f9492..ebc5b59a9 100644 --- a/src/mesh/LR1121Interface.h +++ b/src/mesh/LR1121Interface.h @@ -1,4 +1,5 @@ #pragma once +#if RADIOLIB_EXCLUDE_LR11X0 != 1 #include "LR11x0Interface.h" @@ -11,4 +12,5 @@ class LR1121Interface : public LR11x0Interface LR1121Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); bool wideLora() override; -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index f0f9119ea..6641634c4 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_LR11X0 != 1 #include "LR11x0Interface.h" #include "Throttle.h" #include "configuration.h" @@ -284,4 +285,5 @@ template bool LR11x0Interface::sleep() #endif return true; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 5711b1f7f..4829ddc1d 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -1,5 +1,5 @@ #pragma once - +#if RADIOLIB_EXCLUDE_LR11X0 != 1 #include "RadioLibInterface.h" /** @@ -66,3 +66,4 @@ template class LR11x0Interface : public RadioLibInterface virtual void setStandby() override; }; +#endif \ No newline at end of file diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 25df3258f..3cb6bfdda 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX127X != 1 #include "RF95Interface.h" #include "MeshRadio.h" // kinda yucky, but we need to know which region we are in #include "RadioLibRF95.h" @@ -336,3 +337,4 @@ bool RF95Interface::sleep() return true; } +#endif \ No newline at end of file diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index a50cf93a2..327e57900 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -1,5 +1,5 @@ #pragma once - +#if RADIOLIB_EXCLUDE_SX127X != 1 #include "MeshRadio.h" // kinda yucky, but we need to know which region we are in #include "RadioLibInterface.h" #include "RadioLibRF95.h" @@ -69,3 +69,4 @@ class RF95Interface : public RadioLibInterface /** Some boards require GPIO control of tx vs rx paths */ void setTransmitEnable(bool txon); }; +#endif \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp index a202d4f4d..fe9bbdc93 100644 --- a/src/mesh/RadioLibRF95.cpp +++ b/src/mesh/RadioLibRF95.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX127X != 1 #include "RadioLibRF95.h" #include "configuration.h" @@ -81,4 +82,5 @@ uint8_t RadioLibRF95::readReg(uint8_t addr) { Module *mod = this->getMod(); return mod->SPIreadRegister(addr); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.h b/src/mesh/RadioLibRF95.h index 3bdb794f2..916a33234 100644 --- a/src/mesh/RadioLibRF95.h +++ b/src/mesh/RadioLibRF95.h @@ -1,4 +1,5 @@ #pragma once +#if RADIOLIB_EXCLUDE_SX127X != 1 #include /*! @@ -69,3 +70,4 @@ class RadioLibRF95 : public SX1278 // use the previous value float currentLimit = 100; }; +#endif \ No newline at end of file diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index e96e72b71..4c0dea00b 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "SX1262Interface.h" #include "configuration.h" #include "error.h" @@ -6,4 +7,5 @@ SX1262Interface::SX1262Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R RADIOLIB_PIN_TYPE busy) : SX126xInterface(hal, cs, irq, rst, busy) { -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h index 31a12ae90..6e4616c8b 100644 --- a/src/mesh/SX1262Interface.h +++ b/src/mesh/SX1262Interface.h @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX126X != 1 #pragma once #include "SX126xInterface.h" @@ -10,4 +11,5 @@ class SX1262Interface : public SX126xInterface public: SX1262Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/mesh/SX1268Interface.cpp b/src/mesh/SX1268Interface.cpp index ea299fce5..fe6e9af89 100644 --- a/src/mesh/SX1268Interface.cpp +++ b/src/mesh/SX1268Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "SX1268Interface.h" #include "configuration.h" #include "error.h" @@ -15,4 +16,5 @@ float SX1268Interface::getFreq() return 433.125f; else return savedFreq; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/SX1268Interface.h b/src/mesh/SX1268Interface.h index c8bcf20a7..cc6dd3534 100644 --- a/src/mesh/SX1268Interface.h +++ b/src/mesh/SX1268Interface.h @@ -1,4 +1,5 @@ #pragma once +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "SX126xInterface.h" @@ -13,3 +14,4 @@ class SX1268Interface : public SX126xInterface SX1268Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); }; +#endif \ No newline at end of file diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 30024daf0..a23c1e457 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "SX126xInterface.h" #include "configuration.h" #include "error.h" @@ -341,4 +342,5 @@ template bool SX126xInterface::sleep() #endif return true; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index c437080c4..45b39a68a 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -1,4 +1,5 @@ #pragma once +#if RADIOLIB_EXCLUDE_SX126X != 1 #include "RadioLibInterface.h" @@ -68,3 +69,4 @@ template class SX126xInterface : public RadioLibInterface virtual void setStandby() override; }; +#endif \ No newline at end of file diff --git a/src/mesh/SX1280Interface.cpp b/src/mesh/SX1280Interface.cpp index 3287f141f..9e0d42122 100644 --- a/src/mesh/SX1280Interface.cpp +++ b/src/mesh/SX1280Interface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX128X != 1 #include "SX1280Interface.h" #include "configuration.h" #include "error.h" @@ -7,3 +8,4 @@ SX1280Interface::SX1280Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R : SX128xInterface(hal, cs, irq, rst, busy) { } +#endif \ No newline at end of file diff --git a/src/mesh/SX1280Interface.h b/src/mesh/SX1280Interface.h index 8f2c4ec2e..534dd8084 100644 --- a/src/mesh/SX1280Interface.h +++ b/src/mesh/SX1280Interface.h @@ -1,5 +1,5 @@ #pragma once - +#if RADIOLIB_EXCLUDE_SX128X != 1 #include "SX128xInterface.h" /** @@ -12,3 +12,4 @@ class SX1280Interface : public SX128xInterface SX1280Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy); }; +#endif diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 5c740099c..2a1bb4e41 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -1,3 +1,4 @@ +#if RADIOLIB_EXCLUDE_SX128X != 1 #include "SX128xInterface.h" #include "Throttle.h" #include "configuration.h" @@ -313,4 +314,5 @@ template bool SX128xInterface::sleep() #endif return true; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 8f1006eca..77b5e975c 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -9,6 +9,9 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + lib_deps = ${nrf52840_base.lib_deps} From a5bcf482402fca4f1d83ebcf3fa45ee570081ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 30 Sep 2024 18:12:35 +0200 Subject: [PATCH 1316/1377] Welp it's 7.0.2 now but the branch is still open :-) --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b6b0d8249..ca05342c9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,8 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = - jgromes/RadioLib@~7.0.1 + ;jgromes/RadioLib@~7.0.2 + https://github.com/jgromes/RadioLib.git#5a9ff5a4912f87918390348c8caa54ea8a6afada ; Radiolib 7.0.2pre https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 From 5fcad1d8c5b4340c196d0559a5aee2d977d8c6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 30 Sep 2024 18:12:35 +0200 Subject: [PATCH 1317/1377] Welp it's 7.0.2 now but the branch is still open :-) --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4c85e2dd7..009635686 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,8 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = - jgromes/RadioLib@~7.0.1 + ;jgromes/RadioLib@~7.0.2 + https://github.com/jgromes/RadioLib.git#5a9ff5a4912f87918390348c8caa54ea8a6afada ; Radiolib 7.0.2pre https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 From 199566a996c4817e85778dc26f81264ee7dba487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 30 Sep 2024 21:11:48 +0200 Subject: [PATCH 1318/1377] let's see if this works --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 81c4615b4..42fee7a16 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -35,7 +35,7 @@ jobs: name: Checkout base - id: jsonStep run: | - if [[ "${{ github.head_ref }}" == "master" ]]; then + if [[ "${{ github.head_ref }}" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) From 810a79668ca27b74e2f5c4f3bb493b20b544986c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 30 Sep 2024 21:54:00 +0200 Subject: [PATCH 1319/1377] 7.0.2 dropped --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 009635686..4c35dbd5c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,8 +89,7 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = - ;jgromes/RadioLib@~7.0.2 - https://github.com/jgromes/RadioLib.git#5a9ff5a4912f87918390348c8caa54ea8a6afada ; Radiolib 7.0.2pre + jgromes/RadioLib@~7.0.2 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 From dd587419c7ae0f351b513822af128eb1d596c5ee Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 30 Sep 2024 17:06:31 -0500 Subject: [PATCH 1320/1377] Regenerate public key on boot, to avoid accidental mismatch. (#4916) * Regenerate public key on boot, to avoid accidental mismatch. * Fix ifdefs --- src/mesh/NodeDB.cpp | 46 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 7760ae0e1..36b9f3ff4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -132,39 +132,31 @@ NodeDB::NodeDB() config.security.serial_enabled = config.device.serial_enabled; config.security.is_managed = config.device.is_managed; } -#if !(MESHTASTIC_EXCLUDE_PKI) + +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + bool keygenSuccess = false; + if (config.security.private_key.size == 32) { + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + keygenSuccess = true; + } + } else { + LOG_INFO("Generating new PKI keys\n"); + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; + } + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + } +#elif !(MESHTASTIC_EXCLUDE_PKI) // Calculate Curve25519 public and private keys - printBytes("Old Pubkey", config.security.public_key.bytes, 32); if (config.security.private_key.size == 32 && config.security.public_key.size == 32) { - LOG_INFO("Using saved PKI keys\n"); owner.public_key.size = config.security.public_key.size; memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); crypto->setDHPrivateKey(config.security.private_key.bytes); - } else { -#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) - bool keygenSuccess = false; - if (config.security.private_key.size == 32) { - LOG_INFO("Calculating PKI Public Key\n"); - if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { - keygenSuccess = true; - } - } else { - LOG_INFO("Generating new PKI keys\n"); - crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); - keygenSuccess = true; - } - if (keygenSuccess) { - config.security.public_key.size = 32; - config.security.private_key.size = 32; - printBytes("New Pubkey", config.security.public_key.bytes, 32); - owner.public_key.size = 32; - memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); - } -#else - LOG_INFO("No PKI keys set, and generation disabled!\n"); -#endif } - #endif info->user = TypeConversions::ConvertToUserLite(owner); From 1dace9a50892d114bc97e03958d016b18c92f7cc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:35:35 -0500 Subject: [PATCH 1321/1377] [create-pull-request] automated change (#4917) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/atak.pb.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index 83c78e26e..61d7ca656 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 83c78e26e39031ae1c17ba5e50d0898842719c7f +Subproject commit 61d7ca65652dfe832ead74719700d3d33d6bae7c diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index 50b57cd04..7d1ef2995 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -141,7 +141,7 @@ typedef struct _meshtastic_TAKPacket { /* ATAK GeoChat message */ meshtastic_GeoChat chat; /* Generic CoT detail XML - May be compressed / truncated by the sender */ + May be compressed / truncated by the sender (EUD) */ meshtastic_TAKPacket_detail_t detail; } payload_variant; } meshtastic_TAKPacket; From 553514e3b78b30c73849121694cdf2c52192b2c0 Mon Sep 17 00:00:00 2001 From: TheMalkavien Date: Tue, 1 Oct 2024 00:56:29 +0200 Subject: [PATCH 1322/1377] Fix #4911 : Partially rework some code to remove warnings about potential non-aligned memory accesses (#4912) * * Adding the -Wcast-align compilation flag for the rp2040. * * Some rework to use a struct to access radio data * Buffer will not be accessed by arithmetic pointer anymore * * Remplace arithmetic pointer to avoid Warning * * Avoid 2 little artitmetic pointer --------- Co-authored-by: Ben Meadors --- arch/rp2xx0/rp2040.ini | 2 +- src/gps/GPS.cpp | 4 ++-- src/gps/NMEAWPL.cpp | 7 +++++-- src/mesh/CryptoEngine.cpp | 11 +++++------ src/mesh/RadioInterface.cpp | 24 +++++++++++------------- src/mesh/RadioInterface.h | 17 +++++++++++++++-- src/mesh/RadioLibInterface.cpp | 26 ++++++++++++-------------- 7 files changed, 51 insertions(+), 40 deletions(-) diff --git a/arch/rp2xx0/rp2040.ini b/arch/rp2xx0/rp2040.ini index d3f27a676..5b4ec74d2 100644 --- a/arch/rp2xx0/rp2040.ini +++ b/arch/rp2xx0/rp2040.ini @@ -7,7 +7,7 @@ platform_packages = framework-arduinopico@https://github.com/earlephilhower/ardu board_build.core = earlephilhower board_build.filesystem_size = 0.5m build_flags = - ${arduino_base.build_flags} -Wno-unused-variable + ${arduino_base.build_flags} -Wno-unused-variable -Wcast-align -Isrc/platform/rp2xx0 -D__PLAT_RP2040__ # -D _POSIX_THREADS diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index b6d2776bc..f2b15ba78 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -91,9 +91,9 @@ void GPS::CASChecksum(uint8_t *message, size_t length) // Iterate over the payload as a series of uint32_t's and // accumulate the cksum - uint32_t const *payload = (uint32_t *)(message + 6); for (size_t i = 0; i < (length - 10) / 4; i++) { - uint32_t pl = payload[i]; + uint32_t pl = 0; + memcpy(&pl, (message + 6) + (i * sizeof(uint32_t)), sizeof(uint32_t)); // avoid pointer dereference cksum += pl; } diff --git a/src/gps/NMEAWPL.cpp b/src/gps/NMEAWPL.cpp index 71943b76c..f528c4607 100644 --- a/src/gps/NMEAWPL.cpp +++ b/src/gps/NMEAWPL.cpp @@ -75,10 +75,13 @@ uint32_t printWPL(char *buf, size_t bufsz, const meshtastic_Position &pos, const uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos) { GeoCoord geoCoord(pos.latitude_i, pos.longitude_i, pos.altitude); - tm *t = gmtime((time_t *)&pos.timestamp); + time_t timestamp = pos.timestamp; + + tm *t = gmtime(×tamp); if (getRTCQuality() > 0) { // use the device clock if we got time from somewhere. If not, use the GPS timestamp. uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice); - t = gmtime((time_t *)&rtc_sec); + timestamp = rtc_sec; + t = gmtime(×tamp); } uint32_t len = snprintf( diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 535e11e9b..fd3dd7a54 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -69,9 +69,8 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ uint32_t *extraNonce; long extraNonceTmp = random(); auth = bytesOut + numBytes; - extraNonce = (uint32_t *)(auth + 8); - memcpy(extraNonce, &extraNonceTmp, - 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; + memcpy((uint8_t *)(auth + 8), &extraNonceTmp, + sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; LOG_INFO("Random nonce value: %d\n", extraNonceTmp); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode); if (node->num < 1 || node->user.public_key.size == 0) { @@ -88,8 +87,8 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ printBytes("Attempting encrypt using shared_key starting with: ", shared_key, 8); aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut, auth); // this can write up to 15 bytes longer than numbytes past bytesOut - memcpy(extraNonce, &extraNonceTmp, - 4); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; + memcpy((uint8_t *)(auth + 8), &extraNonceTmp, + sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp; return true; } @@ -105,7 +104,7 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size uint32_t extraNonce; // pointer was not really used auth = bytes + numBytes - 12; #ifndef PIO_UNIT_TESTING - memcpy(&extraNonce, auth + 8, 4); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); + memcpy(&extraNonce, auth + 8, sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); LOG_INFO("Random nonce value: %d\n", extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 6fca67188..fe24624ce 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -595,26 +595,24 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) lastTxStart = millis(); - PacketHeader *h = (PacketHeader *)radiobuf; - - h->from = p->from; - h->to = p->to; - h->id = p->id; - h->channel = p->channel; - h->next_hop = 0; // *** For future use *** - h->relay_node = 0; // *** For future use *** + radioBuffer.header.from = p->from; + radioBuffer.header.to = p->to; + radioBuffer.header.id = p->id; + radioBuffer.header.channel = p->channel; + radioBuffer.header.next_hop = 0; // *** For future use *** + radioBuffer.header.relay_node = 0; // *** For future use *** if (p->hop_limit > HOP_MAX) { LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE); p->hop_limit = HOP_RELIABLE; } - h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0); - h->flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK; + radioBuffer.header.flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0); + radioBuffer.header.flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK; // if the sender nodenum is zero, that means uninitialized - assert(h->from); + assert(radioBuffer.header.from); - memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size); + memcpy(radioBuffer.payload, p->encrypted.bytes, p->encrypted.size); sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} +} \ No newline at end of file diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 129861441..6df51ce1a 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -45,6 +45,20 @@ typedef struct { uint8_t relay_node; } PacketHeader; +/** + * This structure represent the structured buffer : a PacketHeader then the payload. The whole is + * MAX_LORA_PAYLOAD_LEN + 1 length + * It makes the use of its data easier, and avoids manipulating pointers (and potential non aligned accesses) + */ +typedef struct { + /** The header, as defined just before */ + PacketHeader header; + + /** The payload, of maximum length minus the header, aligned just to be sure */ + uint8_t payload[MAX_LORA_PAYLOAD_LEN + 1 - sizeof(PacketHeader)] __attribute__ ((__aligned__)); + +} RadioBuffer; + /** * Basic operations all radio chipsets must implement. * @@ -91,8 +105,7 @@ class RadioInterface /** * A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need * */ - uint8_t radiobuf[MAX_LORA_PAYLOAD_LEN + 1]; - + RadioBuffer radioBuffer __attribute__ ((__aligned__)); /** * Enqueue a received packet for the registered receiver */ diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index aee43676d..647add0e5 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -387,7 +387,7 @@ void RadioLibInterface::handleReceiveInterrupt() } #endif - int state = iface->readData(radiobuf, length); + int state = iface->readData((uint8_t*)&radioBuffer, length); if (state != RADIOLIB_ERR_NONE) { LOG_ERROR("ignoring received packet due to error=%d\n", state); rxBad++; @@ -397,7 +397,6 @@ void RadioLibInterface::handleReceiveInterrupt() } else { // Skip the 4 headers that are at the beginning of the rxBuf int32_t payloadLen = length - sizeof(PacketHeader); - const uint8_t *payload = radiobuf + sizeof(PacketHeader); // check for short packets if (payloadLen < 0) { @@ -405,10 +404,9 @@ void RadioLibInterface::handleReceiveInterrupt() rxBad++; airTime->logAirtime(RX_ALL_LOG, xmitMsec); } else { - const PacketHeader *h = (PacketHeader *)radiobuf; rxGood++; // altered packet with "from == 0" can do Remote Node Administration without permission - if (h->from == 0) { + if (radioBuffer.header.from == 0) { LOG_WARN("ignoring received packet without sender\n"); return; } @@ -418,22 +416,22 @@ void RadioLibInterface::handleReceiveInterrupt() // nodes. meshtastic_MeshPacket *mp = packetPool.allocZeroed(); - mp->from = h->from; - mp->to = h->to; - mp->id = h->id; - mp->channel = h->channel; + mp->from = radioBuffer.header.from; + mp->to = radioBuffer.header.to; + mp->id = radioBuffer.header.id; + mp->channel = radioBuffer.header.channel; assert(HOP_MAX <= PACKET_FLAGS_HOP_LIMIT_MASK); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK; - mp->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; - mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); - mp->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK); + mp->hop_limit = radioBuffer.header.flags & PACKET_FLAGS_HOP_LIMIT_MASK; + mp->hop_start = (radioBuffer.header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; + mp->want_ack = !!(radioBuffer.header.flags & PACKET_FLAGS_WANT_ACK_MASK); + mp->via_mqtt = !!(radioBuffer.header.flags & PACKET_FLAGS_VIA_MQTT_MASK); addReceiveMetadata(mp); mp->which_payload_variant = meshtastic_MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point assert(((uint32_t)payloadLen) <= sizeof(mp->encrypted.bytes)); - memcpy(mp->encrypted.bytes, payload, payloadLen); + memcpy(mp->encrypted.bytes, radioBuffer.payload, payloadLen); mp->encrypted.size = payloadLen; printPacket("Lora RX", mp); @@ -475,7 +473,7 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) size_t numbytes = beginSending(txp); - int res = iface->startTransmit(radiobuf, numbytes); + int res = iface->startTransmit((uint8_t*)&radioBuffer, numbytes); if (res != RADIOLIB_ERR_NONE) { LOG_ERROR("startTransmit failed, error=%d\n", res); RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_RADIO_SPI_BUG); From dc55d7dd987b04658abb9a58f1c65f60cf885818 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 30 Sep 2024 18:07:11 -0500 Subject: [PATCH 1323/1377] Trunk it --- src/mesh/CryptoEngine.cpp | 3 ++- src/mesh/RadioInterface.cpp | 3 ++- src/mesh/RadioLibInterface.cpp | 4 ++-- src/mesh/Router.cpp | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index fd3dd7a54..085055f41 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -104,7 +104,8 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size uint32_t extraNonce; // pointer was not really used auth = bytes + numBytes - 12; #ifndef PIO_UNIT_TESTING - memcpy(&extraNonce, auth + 8, sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); + memcpy(&extraNonce, auth + 8, + sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); LOG_INFO("Random nonce value: %d\n", extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index fe24624ce..683ae7e01 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -605,7 +605,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE); p->hop_limit = HOP_RELIABLE; } - radioBuffer.header.flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0); + radioBuffer.header.flags = + p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0); radioBuffer.header.flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK; // if the sender nodenum is zero, that means uninitialized diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 647add0e5..664709ed1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -387,7 +387,7 @@ void RadioLibInterface::handleReceiveInterrupt() } #endif - int state = iface->readData((uint8_t*)&radioBuffer, length); + int state = iface->readData((uint8_t *)&radioBuffer, length); if (state != RADIOLIB_ERR_NONE) { LOG_ERROR("ignoring received packet due to error=%d\n", state); rxBad++; @@ -473,7 +473,7 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) size_t numbytes = beginSending(txp); - int res = iface->startTransmit((uint8_t*)&radioBuffer, numbytes); + int res = iface->startTransmit((uint8_t *)&radioBuffer, numbytes); if (res != RADIOLIB_ERR_NONE) { LOG_ERROR("startTransmit failed, error=%d\n", res); RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_RADIO_SPI_BUG); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f06f54165..bb04b66ac 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -36,8 +36,8 @@ static MemoryDynamic staticPool; Allocator &packetPool = staticPool; -static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__ ((__aligned__)); -static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__ ((__aligned__)); +static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__)); +static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__)); /** * Constructor From 8d288d5a3c6955ba06807a4b6cb7b9b1b60a930d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 30 Sep 2024 19:26:35 -0500 Subject: [PATCH 1324/1377] Only on pull request --- .github/workflows/main_matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 42fee7a16..277003a61 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -236,6 +236,7 @@ jobs: retention-days: 30 - uses: scruplelesswizard/comment-artifact@main + if: ${{ github.event_name == 'pull_request' }} with: name: firmware-${{ steps.version.outputs.version }} description: "Download firmware-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation" From b769d9f8542d109f37922c9fad3d027265b0009d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 1 Oct 2024 13:14:51 +0200 Subject: [PATCH 1325/1377] change workflow to build one zip per processor arch --- .github/actions/build-variant/action.yml | 6 +- .github/workflows/build_esp32.yml | 1 + .github/workflows/build_esp32_c3.yml | 1 + .github/workflows/build_esp32_c6.yml | 1 + .github/workflows/build_esp32_s3.yml | 1 + .github/workflows/build_nrf52.yml | 1 + .github/workflows/build_rpi2040.yml | 1 + .github/workflows/build_stm32.yml | 2 + .github/workflows/main_matrix.yml | 174 ++++++++++++----------- 9 files changed, 102 insertions(+), 86 deletions(-) diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml index f9410eb03..80d2a56bb 100644 --- a/.github/actions/build-variant/action.yml +++ b/.github/actions/build-variant/action.yml @@ -31,6 +31,10 @@ inputs: description: Include the web UI in the build required: false default: "false" + arch: + description: Processor arch name + required: true + default: "esp32" runs: using: composite @@ -84,7 +88,7 @@ runs: - name: Store binaries as an artifact uses: actions/upload-artifact@v4 with: - name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip overwrite: true path: | ${{ inputs.artifact-paths }} diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index 041191d34..7d069e3db 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -31,3 +31,4 @@ jobs: release/*.bin release/*.elf include-web-ui: true + arch: esp32 diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index ddc2e2859..5234dbe81 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -32,3 +32,4 @@ jobs: artifact-paths: | release/*.bin release/*.elf + arch: esp32c3 diff --git a/.github/workflows/build_esp32_c6.yml b/.github/workflows/build_esp32_c6.yml index 3be813afa..66f2764a6 100644 --- a/.github/workflows/build_esp32_c6.yml +++ b/.github/workflows/build_esp32_c6.yml @@ -33,3 +33,4 @@ jobs: artifact-paths: | release/*.bin release/*.elf + arch: esp32c6 diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 29857ef17..554b37cef 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -31,3 +31,4 @@ jobs: release/*.bin release/*.elf include-web-ui: true + arch: esp32s3 diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml index 606cb8a3e..ce26838f2 100644 --- a/.github/workflows/build_nrf52.yml +++ b/.github/workflows/build_nrf52.yml @@ -25,3 +25,4 @@ jobs: release/*.uf2 release/*.elf release/*.zip + arch: nrf52840 diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml index b0508877d..492a1f010 100644 --- a/.github/workflows/build_rpi2040.yml +++ b/.github/workflows/build_rpi2040.yml @@ -23,3 +23,4 @@ jobs: artifact-paths: | release/*.uf2 release/*.elf + arch: rp2040 diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml index e78178db3..b463bab71 100644 --- a/.github/workflows/build_stm32.yml +++ b/.github/workflows/build_stm32.yml @@ -23,3 +23,5 @@ jobs: artifact-paths: | release/*.hex release/*.bin + release/*.elf + arch: stm32 diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 277003a61..a69a105a0 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -2,9 +2,6 @@ name: CI concurrency: group: ci-${{ github.head_ref || github.run_id }} cancel-in-progress: true -#concurrency: -# group: ${{ github.ref }} -# cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} on: # # Triggers the workflow on push but only for the master branch push: @@ -155,8 +152,13 @@ jobs: permissions: contents: write pull-requests: write + strategy: + fail-fast: false + matrix: + arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32] runs-on: ubuntu-latest - needs: [ + needs: + [ build-esp32, build-esp32-s3, build-esp32-c3, @@ -164,9 +166,6 @@ jobs: build-nrf52, build-rpi2040, build-stm32, - package-raspbian, - package-raspbian-armv7l, - # package-native, ] steps: - name: Checkout code @@ -178,6 +177,7 @@ jobs: - uses: actions/download-artifact@v4 with: path: ./ + pattern: firmware-${{matrix.arch}}-* merge-multiple: true - name: Display structure of downloaded files @@ -188,12 +188,12 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml ./bin/device-*.sh ./bin/device-*.bat + run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 with: - name: firmware-${{ steps.version.outputs.version }} + name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} overwrite: true path: | ./firmware-*.bin @@ -202,8 +202,6 @@ jobs: ./firmware-*-ota.zip ./device-*.sh ./device-*.bat - ./meshtasticd_linux_* - ./config-dist.yaml ./littlefs-*.bin ./bleota*bin ./Meshtastic_nRF52_factory_erase*.uf2 @@ -211,7 +209,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: firmware-${{ steps.version.outputs.version }} + name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} merge-multiple: true path: ./output @@ -225,12 +223,12 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output - name: Repackage in single elfs zip uses: actions/upload-artifact@v4 with: - name: debug-elfs-${{ steps.version.outputs.version }}.zip + name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip overwrite: true path: ./*.elf retention-days: 30 @@ -238,14 +236,21 @@ jobs: - uses: scruplelesswizard/comment-artifact@main if: ${{ github.event_name == 'pull_request' }} with: - name: firmware-${{ steps.version.outputs.version }} - description: "Download firmware-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation" + name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} + description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} release-artifacts: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [gather-artifacts] + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + needs: [ + gather-artifacts, + package-raspbian, + package-raspbian-armv7l, + # package-native, + ] steps: - name: Checkout uses: actions/checkout@v4 @@ -259,36 +264,6 @@ jobs: run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - - uses: actions/download-artifact@v4 - with: - name: firmware-${{ steps.version.outputs.version }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -R - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x meshtasticd_* - - - uses: actions/download-artifact@v4 - with: - name: debug-elfs-${{ steps.version.outputs.version }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip Elfs - run: zip -j -9 -r ./debug-elfs-${{ steps.version.outputs.version }}.zip ./elfs - - # For diagnostics - - name: Show artifacts - run: ls -lR - - name: Create release uses: actions/create-release@v1 id: create_release @@ -302,32 +277,17 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} - - name: Add bins to release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./firmware-${{ steps.version.outputs.version }}.zip - asset_name: firmware-${{ steps.version.outputs.version }}.zip - asset_content_type: application/zip - - - name: Add debug elfs to release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./debug-elfs-${{ steps.version.outputs.version }}.zip - asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip - asset_content_type: application/zip - - - uses: actions/download-artifact@v4 + - name: Download deb files + uses: actions/download-artifact@v4 with: pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb merge-multiple: true path: ./output + # For diagnostics + - name: Display structure of downloaded files + run: ls -lR + - name: Add raspbian aarch64 .deb uses: actions/upload-release-asset@v1 env: @@ -369,29 +329,73 @@ jobs: add-paths: | version.properties - - name: Checkout meshtastic/meshtastic.github.io + release-firmware: + strategy: + fail-fast: false + matrix: + arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32] + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + needs: [release-artifacts] + steps: + - name: Checkout uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 with: - repository: meshtastic/meshtastic.github.io - token: ${{ secrets.ARTIFACTS_TOKEN }} - path: meshtastic.github.io + python-version: 3.x + + - name: Get release version string + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - uses: actions/download-artifact@v4 + with: + pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} + merge-multiple: true + path: ./output - name: Display structure of downloaded files - run: ls -R + run: ls -lR - - name: Extract firmware.zip + - name: Device scripts permissions run: | - unzip ./firmware-${{ steps.version.outputs.version }}.zip -d meshtastic.github.io/firmware-${{ steps.version.outputs.version }} + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output + + - uses: actions/download-artifact@v4 + with: + name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + merge-multiple: true + path: ./elfs + + - name: Zip firmware + run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./elfs + + # For diagnostics - name: Display structure of downloaded files - run: ls -R + run: ls -lR - - name: Commit and push changes - run: | - cd meshtastic.github.io - find . -type f -name 'meshtasticd_*' -exec rm -f {} + - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git add . - git commit -m "Add firmware version ${{ steps.version.outputs.version }}" - git push + - name: Add bins to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{needs.release-artifacts.outputs.upload_url}} + asset_path: ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + asset_name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + asset_content_type: application/zip + + - name: Add debug elfs to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{needs.release-artifacts.outputs.upload_url}} + asset_path: ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + asset_name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + asset_content_type: application/zip From 3440c640c378120867c9c1d4c2d921186daf894d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 1 Oct 2024 13:46:02 +0200 Subject: [PATCH 1326/1377] keep for 30 days only --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index a69a105a0..555d4d092 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -205,7 +205,7 @@ jobs: ./littlefs-*.bin ./bleota*bin ./Meshtastic_nRF52_factory_erase*.uf2 - retention-days: 90 + retention-days: 30 - uses: actions/download-artifact@v4 with: From 0d175a918c6fe752dd4b68ed36db73de9cb84491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 1 Oct 2024 16:02:10 +0200 Subject: [PATCH 1327/1377] misc library updates and compiler warnings, trunk upgrade --- .trunk/trunk.yaml | 12 ++++++------ arch/esp32/esp32.ini | 4 ++-- arch/esp32/esp32c6.ini | 2 +- arch/portduino/portduino.ini | 2 +- arch/rp2xx0/rp2040.ini | 4 ++-- arch/stm32/stm32.ini | 4 ++-- platformio.ini | 16 ++++++++-------- src/mesh/CryptoEngine.cpp | 1 - src/motion/BMA423Sensor.cpp | 4 ++-- variants/rak4631/platformio.ini | 2 +- 10 files changed, 25 insertions(+), 26 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 79cd91af5..9ed720c3f 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,14 +1,14 @@ version: 0.1 cli: - version: 1.22.5 + version: 1.22.6 plugins: sources: - id: trunk - ref: v1.6.2 + ref: v1.6.3 uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.82.5 + - trufflehog@3.82.6 - yamllint@1.35.1 - bandit@1.7.10 - checkov@3.2.255 @@ -16,19 +16,19 @@ lint: - trivy@0.55.2 #- trufflehog@3.63.2-rc0 - taplo@0.9.3 - - ruff@0.6.7 + - ruff@0.6.8 - isort@5.13.2 - markdownlint@0.42.0 - oxipng@9.1.2 - svgo@3.3.2 - - actionlint@1.7.2 + - actionlint@1.7.3 - flake8@7.1.1 - hadolint@2.12.0 - shfmt@3.6.0 - shellcheck@0.10.0 - black@24.8.0 - git-diff-check - - gitleaks@8.19.2 + - gitleaks@8.19.3 - clang-format@16.0.3 - prettier@3.3.3 ignore: diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 13360be78..36d8b9542 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -2,7 +2,7 @@ [esp32_base] extends = arduino_base custom_esp32_kind = esp32 -platform = platformio/espressif32@6.7.0 +platform = platformio/espressif32@6.9.0 build_src_filter = ${arduino_base.build_src_filter} - - - - - @@ -46,7 +46,7 @@ lib_deps = https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 h2zero/NimBLE-Arduino@^1.4.2 https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587 - https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + lewisxhe/XPowersLib@^0.2.6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f rweather/Crypto@^0.4.0 diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini index 3c2165642..53d7f92ec 100644 --- a/arch/esp32/esp32c6.ini +++ b/arch/esp32/esp32c6.ini @@ -23,7 +23,7 @@ lib_deps = ${arduino_base.lib_deps} ${networking_base.lib_deps} ${environmental_base.lib_deps} - https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + lewisxhe/XPowersLib@^0.2.6 https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f rweather/Crypto@^0.4.0 diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 30cc190d2..8778c32a0 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -24,7 +24,7 @@ lib_deps = ${env.lib_deps} ${networking_base.lib_deps} rweather/Crypto@^0.4.0 - https://github.com/lovyan03/LovyanGFX.git#5a39989aa2c9492572255b22f033843ec8900233 + lovyan03/LovyanGFX@^1.1.16 build_flags = ${arduino_base.build_flags} diff --git a/arch/rp2xx0/rp2040.ini b/arch/rp2xx0/rp2040.ini index 5b4ec74d2..8120960fd 100644 --- a/arch/rp2xx0/rp2040.ini +++ b/arch/rp2xx0/rp2040.ini @@ -1,8 +1,8 @@ ; Common settings for rp2040 Processor based targets [rp2040_base] -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2 +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#9e55f6db5c56b9867c69fe473f388beea4546672 extends = arduino_base -platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2 +platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#a6ab6e1f95bc1428d667d55ea7173c0744acc03c ; 4.0.2+ board_build.core = earlephilhower board_build.filesystem_size = 0.5m diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index 4be290015..715e8aa73 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -1,7 +1,7 @@ [stm32_base] extends = arduino_base platform = ststm32 -platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#361a7fdb67e2a7104e99b4f42a802469eef8b129 +platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#ea74156acd823b6d14739f389e6cdc648f8ee36e build_type = release @@ -33,5 +33,5 @@ lib_deps = https://github.com/caveman99/Crypto.git#f61ae26a53f7a2d0ba5511625b8bf8eff3a35d5e lib_ignore = - https://github.com/mathertel/OneButton@~2.6.1 + mathertel/OneButton@~2.6.1 Wire \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 4c35dbd5c..51e76249f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -82,7 +82,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 - -DBUILD_EPOCH=$UNIX_TIME + ;-DBUILD_EPOCH=$UNIX_TIME ;-D OLED_PL monitor_speed = 115200 @@ -91,11 +91,11 @@ monitor_filters = direct lib_deps = jgromes/RadioLib@~7.0.2 https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306 - https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce + mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0 - nanopb/Nanopb@^0.4.8 + nanopb/Nanopb@^0.4.9 erriez/ErriezCRC32@^1.0.1 ; Used for the code analysis in PIO Home / Inspect @@ -128,7 +128,7 @@ lib_deps = ; (not included in native / portduino) [environmental_base] lib_deps = - adafruit/Adafruit BusIO@^1.15.0 + adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit Unified Sensor@^1.1.11 adafruit/Adafruit BMP280 Library@^2.6.8 adafruit/Adafruit BMP085 Library@^1.2.4 @@ -141,9 +141,9 @@ lib_deps = adafruit/Adafruit SHTC3 Library@^1.0.0 adafruit/Adafruit LPS2X@^2.0.4 adafruit/Adafruit SHT31 Library@^2.2.2 - adafruit/Adafruit PM25 AQI Sensor@^1.0.6 + adafruit/Adafruit PM25 AQI Sensor@^1.1.1 adafruit/Adafruit MPU6050@^2.2.4 - adafruit/Adafruit LIS3DH@^1.2.4 + adafruit/Adafruit LIS3DH@^1.3.0 adafruit/Adafruit AHTX0@^2.0.5 adafruit/Adafruit LSM6DS@^4.7.2 adafruit/Adafruit VEML7700 Library@^2.1.6 @@ -158,8 +158,8 @@ lib_deps = https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 https://github.com/KodinLanewave/INA3221@^1.0.1 - lewisxhe/SensorLib@0.2.0 + lewisxhe/SensorLib@^0.2.1 mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 085055f41..5c8fdf6ae 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -66,7 +66,6 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_ uint8_t *bytesOut) { uint8_t *auth; - uint32_t *extraNonce; long extraNonceTmp = random(); auth = bytesOut + numBytes; memcpy((uint8_t *)(auth + 8), &extraNonceTmp, diff --git a/src/motion/BMA423Sensor.cpp b/src/motion/BMA423Sensor.cpp index ec07b7c40..70ddb0624 100755 --- a/src/motion/BMA423Sensor.cpp +++ b/src/motion/BMA423Sensor.cpp @@ -26,9 +26,9 @@ bool BMA423Sensor::init() #ifdef T_WATCH_S3 // Need to raise the wrist function, need to set the correct axis - sensor.setReampAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER); + sensor.setRemapAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER); #else - sensor.setReampAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); + sensor.setRemapAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); #endif // sensor.enableFeature(sensor.FEATURE_STEP_CNTR, true); sensor.enableFeature(sensor.FEATURE_TILT, true); diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 77b5e975c..ced93df66 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -19,7 +19,7 @@ lib_deps = melopero/Melopero RV3028@^1.1.0 https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 - https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 + https://github.com/RAKWireless/RAK12034-BMX160.git#dcead07ffa267d3c906e9ca4a1330ab989e957e2 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds From cae2e43dc6b55e3228718f30a7e90463ae055901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 1 Oct 2024 16:36:44 +0200 Subject: [PATCH 1328/1377] revert .... revert .... --- arch/rp2xx0/rp2040.ini | 4 ++-- platformio.ini | 4 ++-- src/motion/BMA423Sensor.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/rp2xx0/rp2040.ini b/arch/rp2xx0/rp2040.ini index 8120960fd..5b4ec74d2 100644 --- a/arch/rp2xx0/rp2040.ini +++ b/arch/rp2xx0/rp2040.ini @@ -1,8 +1,8 @@ ; Common settings for rp2040 Processor based targets [rp2040_base] -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#9e55f6db5c56b9867c69fe473f388beea4546672 +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2 extends = arduino_base -platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#a6ab6e1f95bc1428d667d55ea7173c0744acc03c ; 4.0.2+ +platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2 board_build.core = earlephilhower board_build.filesystem_size = 0.5m diff --git a/platformio.ini b/platformio.ini index 51e76249f..64d9e7754 100644 --- a/platformio.ini +++ b/platformio.ini @@ -82,7 +82,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 - ;-DBUILD_EPOCH=$UNIX_TIME + -DBUILD_EPOCH=$UNIX_TIME ;-D OLED_PL monitor_speed = 115200 @@ -158,7 +158,7 @@ lib_deps = https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 https://github.com/KodinLanewave/INA3221@^1.0.1 - lewisxhe/SensorLib@^0.2.1 + lewisxhe/SensorLib@0.2.0 mprograms/QMC5883LCompass@^1.2.0 https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d diff --git a/src/motion/BMA423Sensor.cpp b/src/motion/BMA423Sensor.cpp index 70ddb0624..ec07b7c40 100755 --- a/src/motion/BMA423Sensor.cpp +++ b/src/motion/BMA423Sensor.cpp @@ -26,9 +26,9 @@ bool BMA423Sensor::init() #ifdef T_WATCH_S3 // Need to raise the wrist function, need to set the correct axis - sensor.setRemapAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER); + sensor.setReampAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER); #else - sensor.setRemapAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); + sensor.setReampAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); #endif // sensor.enableFeature(sensor.FEATURE_STEP_CNTR, true); sensor.enableFeature(sensor.FEATURE_TILT, true); From 5f974d296147343dc8313684f954d450fd629559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 1 Oct 2024 21:04:23 +0200 Subject: [PATCH 1329/1377] save a couple of bytes (#4922) --- variants/rak3172/platformio.ini | 3 +++ variants/rak4631_epaper/platformio.ini | 3 +++ variants/rak4631_epaper_onrxtx/platformio.ini | 3 +++ variants/wio-e5/platformio.ini | 3 +++ 4 files changed, 12 insertions(+) diff --git a/variants/rak3172/platformio.ini b/variants/rak3172/platformio.ini index d1bd55e83..9e617e01e 100644 --- a/variants/rak3172/platformio.ini +++ b/variants/rak3172/platformio.ini @@ -28,4 +28,7 @@ build_flags = -DHAL_TIM_MODULE_DISABLED -DHAL_WWDG_MODULE_DISABLED -DHAL_EXTI_MODULE_DISABLED + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 upload_port = stlink \ No newline at end of file diff --git a/variants/rak4631_epaper/platformio.ini b/variants/rak4631_epaper/platformio.ini index 08342dcf7..2479f09c8 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/rak4631_epaper/platformio.ini @@ -7,6 +7,9 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini index f7035a1b1..8c1b8eee8 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -9,6 +9,9 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -D EINK_DISPLAY_MODEL=GxEPD2_213_BN -D EINK_WIDTH=250 -D EINK_HEIGHT=122 + -D RADIOLIB_EXCLUDE_SX128X=1 + -D RADIOLIB_EXCLUDE_SX127X=1 + -D RADIOLIB_EXCLUDE_LR11X0=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini index 51591d569..29c4a0a37 100644 --- a/variants/wio-e5/platformio.ini +++ b/variants/wio-e5/platformio.ini @@ -28,6 +28,9 @@ build_flags = -DHAL_TIM_MODULE_DISABLED -DHAL_WWDG_MODULE_DISABLED -DHAL_EXTI_MODULE_DISABLED + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 ; -D PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF upload_port = stlink \ No newline at end of file From e1e7bbc4206a579a4fd9a0efb2ef82afa8fbd5fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:04:44 -0500 Subject: [PATCH 1330/1377] [create-pull-request] automated change (#4918) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 5f506ee26..f6a385c9f 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 4 +build = 5 From 18f12584abc9b98963deac6f780b266284062156 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 1 Oct 2024 15:38:36 -0500 Subject: [PATCH 1331/1377] Consolidate and shrink down the re-used strings in logs (#4907) * Consolidate and shrink down the re-used strings in GPS * Condense all the things --------- Co-authored-by: GUVWAF --- src/Power.cpp | 2 - src/airtime.cpp | 6 +- src/gps/GPS.cpp | 101 +++++++++--------- src/gps/GPS.h | 2 + src/gps/ubx.h | 4 +- src/mesh/FloodingRouter.cpp | 8 +- src/mesh/MeshService.cpp | 13 ++- src/mesh/NodeDB.cpp | 2 +- src/mesh/RF95Interface.cpp | 12 +-- src/mesh/RadioInterface.cpp | 10 +- src/mesh/RadioLibInterface.cpp | 2 +- src/mesh/RadioLibInterface.h | 2 + src/mesh/ReliableRouter.cpp | 9 +- src/mesh/Router.cpp | 4 +- src/mesh/SX126xInterface.cpp | 18 ++-- src/mesh/SX128xInterface.cpp | 16 +-- src/mesh/http/ContentHandler.cpp | 2 +- src/modules/StoreForwardModule.cpp | 60 +++++------ src/modules/Telemetry/PowerTelemetry.cpp | 2 +- .../Telemetry/Sensor/MAX17048Sensor.cpp | 20 ++-- src/modules/Telemetry/Sensor/MAX17048Sensor.h | 1 + src/modules/TraceRouteModule.cpp | 2 +- src/serialization/MeshPacketSerializer.cpp | 18 ++-- src/serialization/MeshPacketSerializer.h | 1 + 24 files changed, 158 insertions(+), 159 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 6ed937648..2f5f441ae 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -971,7 +971,6 @@ bool Power::axpChipInit() PMU->enableVbusVoltageMeasure(); PMU->enableBattVoltageMeasure(); - LOG_DEBUG("=======================================================================\n"); if (PMU->isChannelAvailable(XPOWERS_DCDC1)) { LOG_DEBUG("DC1 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC1) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_DCDC1)); @@ -1020,7 +1019,6 @@ bool Power::axpChipInit() LOG_DEBUG("BLDO2: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_BLDO2) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); } - LOG_DEBUG("=======================================================================\n"); // We can safely ignore this approach for most (or all) boards because MCU turned off // earlier than battery discharged to 2.6V. diff --git a/src/airtime.cpp b/src/airtime.cpp index 2702ee2bf..fcda05468 100644 --- a/src/airtime.cpp +++ b/src/airtime.cpp @@ -13,17 +13,17 @@ void AirTime::logAirtime(reportTypes reportType, uint32_t airtime_ms) { if (reportType == TX_LOG) { - LOG_DEBUG("AirTime - Packet transmitted : %ums\n", airtime_ms); + LOG_DEBUG("Packet transmitted : %ums\n", airtime_ms); this->airtimes.periodTX[0] = this->airtimes.periodTX[0] + airtime_ms; air_period_tx[0] = air_period_tx[0] + airtime_ms; this->utilizationTX[this->getPeriodUtilHour()] = this->utilizationTX[this->getPeriodUtilHour()] + airtime_ms; } else if (reportType == RX_LOG) { - LOG_DEBUG("AirTime - Packet received : %ums\n", airtime_ms); + LOG_DEBUG("Packet received : %ums\n", airtime_ms); this->airtimes.periodRX[0] = this->airtimes.periodRX[0] + airtime_ms; air_period_rx[0] = air_period_rx[0] + airtime_ms; } else if (reportType == RX_ALL_LOG) { - LOG_DEBUG("AirTime - Packet received (noise?) : %ums\n", airtime_ms); + LOG_DEBUG("Packet received (noise?) : %ums\n", airtime_ms); this->airtimes.periodRX_ALL[0] = this->airtimes.periodRX_ALL[0] + airtime_ms; } diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index f2b15ba78..33c21c5bb 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -530,23 +530,23 @@ bool GPS::setup() _serial_gps->write("$PAIR513*3D\r\n"); // save configuration } else if (gnssModel == GNSS_MODEL_UBLOX6) { clearBuffer(); - SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "Unable to disable text info messages.\n", 500); - SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "Unable to enable interference resistance.\n", 500); - SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "Unable to configure NAVX5 settings.\n", 500); + SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500); + SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500); + SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500); // Turn off unwanted NMEA messages, set update rate - SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "Unable to set GPS update rate.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "Unable to disable NMEA GLL.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "Unable to Enable NMEA GSA.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "Unable to disable NMEA GSV.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "Unable to disable NMEA VTG.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "Unable to enable NMEA RMC.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "Unable to enable NMEA GGA.\n", 500); + SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500); clearBuffer(); - SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_ECO, "Unable to enable powersaving ECO mode for Neo-6.\n", 500); - SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "Unable to enable powersaving details for GPS.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_AID, "Unable to disable UBX-AID.\n", 500); + SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_ECO, "enable powersaving ECO mode for Neo-6", 500); + SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersaving details for GPS", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_AID, "disable UBX-AID", 500); msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); _serial_gps->write(UBXscratch, msglen); @@ -567,7 +567,7 @@ bool GPS::setup() if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) { // It's not critical if the module doesn't acknowledge this configuration. - LOG_INFO("Unable to reconfigure GNSS - defaults maintained. Is this module GPS-only?\n"); + LOG_INFO("reconfigure GNSS - defaults maintained. Is this module GPS-only?\n"); } else { if (gnssModel == GNSS_MODEL_UBLOX7) { LOG_INFO("GNSS configured for GPS+SBAS.\n"); @@ -581,40 +581,40 @@ bool GPS::setup() // Disable Text Info messages //6,7,8,9 clearBuffer(); - SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "Unable to disable text info messages.\n", 500); + SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500); if (gnssModel == GNSS_MODEL_UBLOX8) { // 8 clearBuffer(); - SEND_UBX_PACKET(0x06, 0x39, _message_JAM_8, "Unable to enable interference resistance.\n", 500); + SEND_UBX_PACKET(0x06, 0x39, _message_JAM_8, "enable interference resistance", 500); clearBuffer(); - SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5_8, "Unable to configure NAVX5_8 settings.\n", 500); + SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5_8, "configure NAVX5_8 settings", 500); } else { // 6,7,9 - SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "Unable to enable interference resistance.\n", 500); - SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "Unable to configure NAVX5 settings.\n", 500); + SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500); + SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500); } // Turn off unwanted NMEA messages, set update rate - SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "Unable to set GPS update rate.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "Unable to disable NMEA GLL.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "Unable to Enable NMEA GSA.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "Unable to disable NMEA GSV.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "Unable to disable NMEA VTG.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "Unable to enable NMEA RMC.\n", 500); - SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "Unable to enable NMEA GGA.\n", 500); + SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500); + SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500); if (uBloxProtocolVersion >= 18) { clearBuffer(); - SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "Unable to enable powersaving for GPS.\n", 500); - SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "Unable to enable powersaving details for GPS.\n", 500); + SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersaving for GPS", 500); + SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersaving details for GPS", 500); // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats. if (gnssModel == GNSS_MODEL_UBLOX8) { clearBuffer(); - SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "Unable to enable NMEA 4.10.\n", 500); + SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500); } } else { - SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_PSM, "Unable to enable powersaving mode for GPS.\n", 500); - SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "Unable to enable powersaving details for GPS.\n", 500); + SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_PSM, "enable powersaving mode for GPS", 500); + SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersaving details for GPS", 500); } msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); @@ -627,40 +627,38 @@ bool GPS::setup() } else if (gnssModel == GNSS_MODEL_UBLOX10) { delay(1000); clearBuffer(); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "Unable to disable NMEA messages in M10 RAM.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "disable NMEA messages in M10 RAM", 300); delay(750); clearBuffer(); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "Unable to disable NMEA messages in M10 BBR.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "disable NMEA messages in M10 BBR", 300); delay(750); clearBuffer(); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, - "Unable to disable Info messages for M10 GPS RAM.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, "disable Info messages for M10 GPS RAM", 300); delay(750); // Next disable Info txt messages in BBR layer clearBuffer(); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, - "Unable to disable Info messages for M10 GPS BBR.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, "disable Info messages for M10 GPS BBR", 300); delay(750); // Do M10 configuration for Power Management. - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_RAM, "Unable to enable powersaving for M10 GPS RAM.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_RAM, "enable powersaving for M10 GPS RAM", 300); delay(750); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_BBR, "Unable to enable powersaving for M10 GPS BBR.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_BBR, "enable powersaving for M10 GPS BBR", 300); delay(750); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_RAM, "Unable to enable Jamming detection M10 GPS RAM.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_RAM, "enable Jamming detection M10 GPS RAM", 300); delay(750); - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_BBR, "Unable to enable Jamming detection M10 GPS BBR.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_BBR, "enable Jamming detection M10 GPS BBR", 300); delay(750); // Here is where the init commands should go to do further M10 initialization. - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "Unable to disable SBAS M10 GPS RAM.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "disable SBAS M10 GPS RAM", 300); delay(750); // will cause a receiver restart so wait a bit - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "Unable to disable SBAS M10 GPS BBR.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "disable SBAS M10 GPS BBR", 300); delay(750); // will cause a receiver restart so wait a bit // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep. - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "Unable to enable messages for M10 GPS BBR.\n", 300); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "enable messages for M10 GPS BBR", 300); delay(750); // Next enable wanted NMEA messages in RAM layer - SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "Unable to enable messages for M10 GPS RAM.\n", 500); + SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "enable messages for M10 GPS RAM", 500); delay(750); // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. @@ -1073,12 +1071,15 @@ int GPS::prepareDeepSleep(void *unused) return 0; } +const char *PROBE_MESSAGE = "Trying %s (%s)...\n"; +const char *DETECTED_MESSAGE = "%s detected, using %s Module\n"; + #define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \ - LOG_DEBUG("Trying " TOWRITE " (" CHIP ") ...\n"); \ + LOG_DEBUG(PROBE_MESSAGE, TOWRITE, CHIP); \ clearBuffer(); \ _serial_gps->write(TOWRITE "\r\n"); \ if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \ - LOG_INFO(CHIP " detected, using " #DRIVER " Module\n"); \ + LOG_INFO(DETECTED_MESSAGE, CHIP, #DRIVER); \ return DRIVER; \ } @@ -1367,21 +1368,21 @@ bool GPS::factoryReset() 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1C, 0xA2}; _serial_gps->write(_message_reset1, sizeof(_message_reset1)); if (getACK(0x05, 0x01, 10000)) { - LOG_INFO("Get ack success!\n"); + LOG_INFO(ACK_SUCCESS_MESSAGE); } delay(100); byte _message_reset2[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1B, 0xA1}; _serial_gps->write(_message_reset2, sizeof(_message_reset2)); if (getACK(0x05, 0x01, 10000)) { - LOG_INFO("Get ack success!\n"); + LOG_INFO(ACK_SUCCESS_MESSAGE); } delay(100); byte _message_reset3[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0x1D, 0xB3}; _serial_gps->write(_message_reset3, sizeof(_message_reset3)); if (getACK(0x05, 0x01, 10000)) { - LOG_INFO("Get ack success!\n"); + LOG_INFO(ACK_SUCCESS_MESSAGE); } // Reset device ram to COLDSTART state // byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B}; diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 317d80544..6222881bc 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -156,6 +156,8 @@ class GPS : private concurrency::OSThread static const uint8_t _message_CAS_CFG_NAVX_CONF[]; static const uint8_t _message_CAS_CFG_RATE_1HZ[]; + const char *ACK_SUCCESS_MESSAGE = "Get ack success!\n"; + meshtastic_Position p = meshtastic_Position_init_default; /** This is normally bound to config.position.gps_en_gpio but some rare boards (like heltec tracker) need more advanced diff --git a/src/gps/ubx.h b/src/gps/ubx.h index c73a7efb3..b137d3349 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -1,8 +1,10 @@ +const char *failMessage = "Unable to %s\n"; + #define SEND_UBX_PACKET(TYPE, ID, DATA, ERRMSG, TIMEOUT) \ msglen = makeUBXPacket(TYPE, ID, sizeof(DATA), DATA); \ _serial_gps->write(UBXscratch, msglen); \ if (getACK(TYPE, ID, TIMEOUT) != GNSS_RESPONSE_OK) { \ - LOG_WARN(#ERRMSG); \ + LOG_WARN(failMessage, #ERRMSG); \ } // Power Management diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index fbe56159c..e2dbd8997 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -21,7 +21,7 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p) bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record - printPacket("Ignoring incoming msg we've already seen", p); + printPacket("Ignoring dupe incoming msg", p); if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! @@ -38,7 +38,7 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0); if (isAckorReply && p->to != getNodeNum() && p->to != NODENUM_BROADCAST) { // do not flood direct message that is ACKed or replied to - LOG_DEBUG("Receiving an ACK or reply not for me, but don't need to rebroadcast this direct message anymore.\n"); + LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast.\n"); Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM } if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) { @@ -55,7 +55,7 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas } #endif - LOG_INFO("Rebroadcasting received floodmsg to neighbors\n"); + LOG_INFO("Rebroadcasting received floodmsg\n"); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again Router::send(tosend); @@ -63,7 +63,7 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas LOG_DEBUG("Not rebroadcasting. Role = Role_ClientMute\n"); } } else { - LOG_DEBUG("Ignoring a simple (0 id) broadcast\n"); + LOG_DEBUG("Ignoring 0 id broadcast\n"); } } // handle the packet as normal diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index ac97d51a7..a8a207352 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -80,12 +80,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) { - LOG_DEBUG( - "Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our " - "request for its NodeInfo.\n"); + LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo.\n"); // because this potentially a Repeater which will + // ignore our request for its NodeInfo } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && nodeInfoModule) { - LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); + LOG_INFO("Heard new node on channel %d, sending NodeInfo and asking for a response.\n", mp->channel); if (airTime->isTxAllowedChannelUtil(true)) { nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); } else { @@ -223,7 +222,7 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, copied->mesh_packet_id = mesh_packet_id; if (toPhoneQueueStatusQueue.numFree() == 0) { - LOG_DEBUG("NOTE: tophone queue status queue is full, discarding oldest\n"); + LOG_INFO("tophone queue status queue is full, discarding oldest\n"); meshtastic_QueueStatus *d = toPhoneQueueStatusQueue.dequeuePtr(0); if (d) releaseQueueStatusToPool(d); @@ -317,7 +316,7 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m) { - LOG_DEBUG("Sending mqtt message on topic '%s' to client for proxying to server\n", m->topic); + LOG_DEBUG("Sending mqtt message on topic '%s' to client for proxy\n", m->topic); if (toPhoneMqttProxyQueue.numFree() == 0) { LOG_WARN("MqttClientProxyMessagePool queue is full, discarding oldest\n"); meshtastic_MqttClientProxyMessage *d = toPhoneMqttProxyQueue.dequeuePtr(0); @@ -407,4 +406,4 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) bool MeshService::isToPhoneQueueEmpty() { return toPhoneQueue.isEmpty(); -} +} \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 36b9f3ff4..b78827e65 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1079,7 +1079,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde // We just changed something about the user, store our DB Throttle::execute( &lastNodeDbSave, ONE_MINUTE_MS, []() { nodeDB->saveToDisk(SEGMENT_DEVICESTATE); }, - []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now, since we saved less than a minute ago\n"); }); + []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now\n"); }); // since we saved less than a minute ago } return changed; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 3cb6bfdda..581fd9034 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -220,17 +220,17 @@ bool RF95Interface::reconfigure() err = lora->setSyncWord(syncWord); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting RF95 setSyncWord!\n", err); + LOG_ERROR("RF95 setSyncWord %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora->setCurrentLimit(currentLimit); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting RF95 setCurrentLimit!\n", err); + LOG_ERROR("RF95 setCurrentLimit %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora->setPreambleLength(preambleLength); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting RF95 setPreambleLength!\n", err); + LOG_ERROR(" RF95 setPreambleLength %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora->setFrequency(getFreq()); @@ -266,7 +266,7 @@ void RF95Interface::setStandby() { int err = lora->standby(); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting RF95 standby!\n", err); + LOG_ERROR("RF95 standby %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more @@ -290,7 +290,7 @@ void RF95Interface::startReceive() setStandby(); int err = lora->startReceive(); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting RF95 startReceive!\n", err); + LOG_ERROR("RF95 startReceive %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -312,7 +312,7 @@ bool RF95Interface::isChannelActive() return true; } if (result != RADIOLIB_CHANNEL_FREE) - LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result); + LOG_ERROR("RF95 isChannelActive %s%d\n", radioLibErr, result); assert(result != RADIOLIB_ERR_WRONG_MODEM); // LOG_DEBUG("Channel is free!\n"); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 683ae7e01..b915f94bd 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -202,8 +202,6 @@ uint32_t RadioInterface::getPacketTime(uint32_t pl) uint32_t msecs = tPacket * 1000; - LOG_DEBUG("(bw=%d, sf=%d, cr=4/%d) packet symLen=%d ms, payloadSize=%u, time %d ms\n", (int)bw, sf, cr, (int)(tSym * 1000), - pl, msecs); return msecs; } @@ -550,11 +548,11 @@ void RadioInterface::applyModemConfig() LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, loraConfig.frequency_offset); LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset, channel_num, power); - LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd, + LOG_INFO("myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart); - LOG_INFO("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw); - LOG_INFO("Radio channel_num: %d\n", channel_num + 1); - LOG_INFO("Radio frequency: %f\n", getFreq()); + LOG_INFO("numChannels: %d x %.3fkHz\n", numChannels, bw); + LOG_INFO("channel_num: %d\n", channel_num + 1); + LOG_INFO("frequency: %f\n", getFreq()); LOG_INFO("Slot time: %u msec\n", slotTimeMsec); } diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 664709ed1..807c8aa02 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -466,7 +466,7 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) { printPacket("Starting low level send", txp); if (disabled || !config.lora.tx_enabled) { - LOG_WARN("startSend is dropping tx packet because we are disabled\n"); + LOG_WARN("Drop Tx packet because LoRa Tx disabled\n"); packetPool.release(txp); } else { configHardwareForSend(); // must be after setStandby diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 13bef851a..090c03046 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -196,4 +196,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified * Subclasses must override, implement and then call into this base class implementation */ virtual void setStandby(); + + const char *radioLibErr = "RadioLib err=\n"; }; \ No newline at end of file diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 1f2c01473..9482f4185 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -107,12 +107,12 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability) if (p->want_ack) { if (MeshModule::currentReply) { - LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n"); + LOG_DEBUG("Another module replied to this message, no need for 2nd ack\n"); } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 && (nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) { - LOG_INFO("This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY\n"); + LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY\n"); sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start, p->hop_limit); } else { @@ -124,7 +124,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c && c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) { if (owner.public_key.size == 32) { - LOG_INFO("This seems like a remote PKI decrypt failure, so send a NodeInfo"); + LOG_INFO("PKI decrypt failure, send a NodeInfo"); nodeInfoModule->sendOurNodeInfo(p->from, false, p->channel, true); } } @@ -136,11 +136,10 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas // We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records if (ackId || nakId) { + LOG_DEBUG("Received a %s for 0x%x, stopping retransmissions\n", ackId ? "ACK" : "NAK", ackId); if (ackId) { - LOG_DEBUG("Received an ack for 0x%x, stopping retransmissions\n", ackId); stopRetransmission(p->to, ackId); } else { - LOG_DEBUG("Received a nak for 0x%x, stopping retransmissions\n", nakId); stopRetransmission(p->to, nakId); } } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index bb04b66ac..610d5303e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -503,8 +503,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) return meshtastic_Routing_Error_TOO_LARGE; if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { - LOG_WARN("Client public key for client differs from requested! Requested 0x%02x, but stored key begins 0x%02x\n", - *p->public_key.bytes, *node->user.public_key.bytes); + LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x\n", *p->public_key.bytes, + *node->user.public_key.bytes); return meshtastic_Routing_Error_PKI_FAILED; } crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted); diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index a23c1e457..924cdfa9f 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -203,17 +203,17 @@ template bool SX126xInterface::reconfigure() err = lora.setSyncWord(syncWord); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err); + LOG_ERROR("SX126X setSyncWord %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora.setCurrentLimit(currentLimit); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err); + LOG_ERROR("SX126X setCurrentLimit %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err); + LOG_ERROR("SX126X setPreambleLength %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); @@ -225,7 +225,7 @@ template bool SX126xInterface::reconfigure() err = lora.setOutputPower(power); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err); + LOG_ERROR("SX126X setOutputPower %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving @@ -245,7 +245,7 @@ template void SX126xInterface::setStandby() int err = lora.standby(); if (err != RADIOLIB_ERR_NONE) - LOG_DEBUG("SX126x standby failed with error %d\n", err); + LOG_DEBUG("SX126x standby %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more @@ -287,7 +287,7 @@ template void SX126xInterface::startReceive() // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); + LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); RadioLibInterface::startReceive(); @@ -308,7 +308,7 @@ template bool SX126xInterface::isChannelActive() if (result == RADIOLIB_LORA_DETECTED) return true; if (result != RADIOLIB_CHANNEL_FREE) - LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result); + LOG_ERROR("SX126X scanChannel %s%d\n", radioLibErr, result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; @@ -326,8 +326,8 @@ template bool SX126xInterface::sleep() { // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet // \todo Display actual typename of the adapter, not just `SX126x` - LOG_DEBUG("SX126x entering sleep mode (FIXME, don't keep config)\n"); - setStandby(); // Stop any pending operations + LOG_DEBUG("SX126x entering sleep mode\n"); // (FIXME, don't keep config) + setStandby(); // Stop any pending operations // turn off TCXO if it was powered // FIXME - this isn't correct diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 2a1bb4e41..d379f26e6 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -129,12 +129,12 @@ template bool SX128xInterface::reconfigure() err = lora.setSyncWord(syncWord); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX128X setSyncWord!\n", err); + LOG_ERROR("SX128X setSyncWord %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX128X setPreambleLength!\n", err); + LOG_ERROR("SX128X setPreambleLength %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); @@ -146,7 +146,7 @@ template bool SX128xInterface::reconfigure() err = lora.setOutputPower(power); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX128X setOutputPower!\n", err); + LOG_ERROR("SX128X setOutputPower %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving @@ -171,7 +171,7 @@ template void SX128xInterface::setStandby() int err = lora.standby(); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("SX128x standby failed with error %d\n", err); + LOG_ERROR("SX128x standby %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); #if ARCH_PORTDUINO if (settingsMap[rxen] != RADIOLIB_NC) { @@ -261,7 +261,7 @@ template void SX128xInterface::startReceive() int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED); if (err != RADIOLIB_ERR_NONE) - LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); + LOG_ERROR("SX128X startReceive %s%d\n", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); RadioLibInterface::startReceive(); @@ -282,7 +282,7 @@ template bool SX128xInterface::isChannelActive() if (result == RADIOLIB_LORA_DETECTED) return true; if (result != RADIOLIB_CHANNEL_FREE) - LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result); + LOG_ERROR("SX128X scanChannel %s%d\n", radioLibErr, result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; @@ -298,8 +298,8 @@ template bool SX128xInterface::sleep() { // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet // \todo Display actual typename of the adapter, not just `SX128x` - LOG_DEBUG("SX128x entering sleep mode (FIXME, don't keep config)\n"); - setStandby(); // Stop any pending operations + LOG_DEBUG("SX128x entering sleep mode\n"); // (FIXME, don't keep config) + setStandby(); // Stop any pending operations // turn off TCXO if it was powered // FIXME - this isn't correct diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 772b3e821..c5ea86429 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -776,7 +776,7 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res) res->println("

Meshtastic

\n"); res->println("Restarting"); - LOG_DEBUG("***** Restarted on HTTP(s) Request *****\n"); + LOG_DEBUG("Restarted on HTTP(s) Request\n"); webServerThread->requestRestart = (millis() / 1000) + 5; } diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 5f30803a4..58be8c01e 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -46,7 +46,7 @@ int32_t StoreForwardModule::runOnce() } else if (this->heartbeat && (!Throttle::isWithinTimespanMs(lastHeartbeat, heartbeatInterval * 1000)) && airTime->isTxAllowedChannelUtil(true)) { lastHeartbeat = millis(); - LOG_INFO("*** Sending heartbeat\n"); + LOG_INFO("Sending heartbeat\n"); meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_HEARTBEAT; sf.which_variant = meshtastic_StoreAndForward_heartbeat_tag; @@ -70,8 +70,8 @@ void StoreForwardModule::populatePSRAM() https://learn.upesy.com/en/programmation/psram.html#psram-tab */ - LOG_DEBUG("*** Before PSRAM initialization: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(), - memGet.getFreePsram(), memGet.getPsramSize()); + LOG_DEBUG("Before PSRAM init: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreePsram(), + memGet.getPsramSize()); /* Use a maximum of 2/3 the available PSRAM unless otherwise specified. Note: This needs to be done after every thing that would use PSRAM @@ -86,9 +86,9 @@ void StoreForwardModule::populatePSRAM() #endif - LOG_DEBUG("*** After PSRAM initialization: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(), - memGet.getFreePsram(), memGet.getPsramSize()); - LOG_DEBUG("*** numberOfPackets for packetHistory - %u\n", numberOfPackets); + LOG_DEBUG("After PSRAM init: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreePsram(), + memGet.getPsramSize()); + LOG_DEBUG("numberOfPackets for packetHistory - %u\n", numberOfPackets); } /** @@ -105,11 +105,11 @@ void StoreForwardModule::historySend(uint32_t secAgo, uint32_t to) queueSize = this->historyReturnMax; if (queueSize) { - LOG_INFO("*** S&F - Sending %u message(s)\n", queueSize); + LOG_INFO("S&F - Sending %u message(s)\n", queueSize); this->busy = true; // runOnce() will pickup the next steps once busy = true. this->busyTo = to; } else { - LOG_INFO("*** S&F - No history to send\n"); + LOG_INFO("S&F - No history\n"); } meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_HISTORY; @@ -187,7 +187,7 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) const auto &p = mp.decoded; if (this->packetHistoryTotalCount == this->records) { - LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n"); + LOG_WARN("S&F - PSRAM Full. Starting overwrite.\n"); this->packetHistoryTotalCount = 0; for (auto &i : lastRequest) { i.second = 0; // Clear the last request index for each client device @@ -215,7 +215,7 @@ bool StoreForwardModule::sendPayload(NodeNum dest, uint32_t last_time) { meshtastic_MeshPacket *p = preparePayload(dest, last_time); if (p) { - LOG_INFO("*** Sending S&F Payload\n"); + LOG_INFO("Sending S&F Payload\n"); service->sendToMesh(p); this->requestCount++; return true; @@ -331,9 +331,9 @@ void StoreForwardModule::sendErrorTextMessage(NodeNum dest, bool want_response) pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; const char *str; if (this->busy) { - str = "** S&F - Busy. Try again shortly."; + str = "S&F - Busy. Try again shortly."; } else { - str = "** S&F - Not available on this channel."; + str = "S&F - Not available on this channel."; } LOG_WARN("%s\n", str); memcpy(pr->decoded.payload.bytes, str, strlen(str)); @@ -365,7 +365,7 @@ void StoreForwardModule::statsSend(uint32_t to) sf.variant.stats.return_max = this->historyReturnMax; sf.variant.stats.return_window = this->historyReturnWindow; - LOG_DEBUG("*** Sending S&F Stats\n"); + LOG_DEBUG("Sending S&F Stats\n"); storeForwardModule->sendMessage(to, sf); } @@ -384,7 +384,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m auto &p = mp.decoded; if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) { - LOG_DEBUG("*** Legacy Request to send\n"); + LOG_DEBUG("Legacy Request to send\n"); // Send the last 60 minutes of messages. if (this->busy || channels.isDefaultChannel(mp.channel)) { @@ -394,7 +394,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m } } else { storeForwardModule->historyAdd(mp); - LOG_INFO("*** S&F stored. Message history contains %u records now.\n", this->packetHistoryTotalCount); + LOG_INFO("S&F stored. Message history contains %u records now.\n", this->packetHistoryTotalCount); } } else if (getFrom(&mp) != nodeDB->getNodeNum() && mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) { auto &p = mp.decoded; @@ -440,7 +440,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, if (is_server) { // stop sending stuff, the client wants to abort or has another error if ((this->busy) && (this->busyTo == getFrom(&mp))) { - LOG_ERROR("*** Client in ERROR or ABORT requested\n"); + LOG_ERROR("Client in ERROR or ABORT requested\n"); this->requestCount = 0; this->busy = false; } @@ -450,7 +450,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, case meshtastic_StoreAndForward_RequestResponse_CLIENT_HISTORY: if (is_server) { requests_history++; - LOG_INFO("*** Client Request to send HISTORY\n"); + LOG_INFO("Client Request to send HISTORY\n"); // Send the last 60 minutes of messages. if (this->busy || channels.isDefaultChannel(mp.channel)) { sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response); @@ -467,7 +467,6 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, case meshtastic_StoreAndForward_RequestResponse_CLIENT_PING: if (is_server) { - LOG_INFO("*** StoreAndForward_RequestResponse_CLIENT_PING\n"); // respond with a ROUTER PONG storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_ROUTER_PONG); } @@ -475,17 +474,16 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, case meshtastic_StoreAndForward_RequestResponse_CLIENT_PONG: if (is_server) { - LOG_INFO("*** StoreAndForward_RequestResponse_CLIENT_PONG\n"); // NodeDB is already updated } break; case meshtastic_StoreAndForward_RequestResponse_CLIENT_STATS: if (is_server) { - LOG_INFO("*** Client Request to send STATS\n"); + LOG_INFO("Client Request to send STATS\n"); if (this->busy) { storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_ROUTER_BUSY); - LOG_INFO("*** S&F - Busy. Try again shortly.\n"); + LOG_INFO("S&F - Busy. Try again shortly.\n"); } else { storeForwardModule->statsSend(getFrom(&mp)); } @@ -495,7 +493,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, case meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR: case meshtastic_StoreAndForward_RequestResponse_ROUTER_BUSY: if (is_client) { - LOG_DEBUG("*** StoreAndForward_RequestResponse_ROUTER_BUSY\n"); + LOG_DEBUG("StoreAndForward_RequestResponse_ROUTER_BUSY\n"); // retry in messages_saved * packetTimeMax ms retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax * (meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); @@ -511,13 +509,12 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, heartbeatInterval = p->variant.heartbeat.period; } lastHeartbeat = millis(); - LOG_INFO("*** StoreAndForward Heartbeat received\n"); + LOG_INFO("StoreAndForward Heartbeat received\n"); } break; case meshtastic_StoreAndForward_RequestResponse_ROUTER_PING: if (is_client) { - LOG_DEBUG("*** StoreAndForward_RequestResponse_ROUTER_PING\n"); // respond with a CLIENT PONG storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_CLIENT_PONG); } @@ -525,7 +522,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, case meshtastic_StoreAndForward_RequestResponse_ROUTER_STATS: if (is_client) { - LOG_DEBUG("*** Router Response STATS\n"); + LOG_DEBUG("Router Response STATS\n"); // These fields only have informational purpose on a client. Fill them to consume later. if (p->which_variant == meshtastic_StoreAndForward_stats_tag) { this->records = p->variant.stats.messages_max; @@ -543,7 +540,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, // These fields only have informational purpose on a client. Fill them to consume later. if (p->which_variant == meshtastic_StoreAndForward_history_tag) { this->historyReturnWindow = p->variant.history.window / 60000; - LOG_INFO("*** Router Response HISTORY - Sending %d messages from last %d minutes\n", + LOG_INFO("Router Response HISTORY - Sending %d messages from last %d minutes\n", p->variant.history.history_messages, this->historyReturnWindow); } } @@ -577,7 +574,7 @@ StoreForwardModule::StoreForwardModule() // Router if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || moduleConfig.store_forward.is_server)) { - LOG_INFO("*** Initializing Store & Forward Module in Server mode\n"); + LOG_INFO("Initializing Store & Forward Module in Server mode\n"); if (memGet.getPsramSize() > 0) { if (memGet.getFreePsram() >= 1024 * 1024) { @@ -605,18 +602,17 @@ StoreForwardModule::StoreForwardModule() this->populatePSRAM(); is_server = true; } else { - LOG_INFO("*** Device has less than 1M of PSRAM free.\n"); - LOG_INFO("*** Store & Forward Module - disabling server.\n"); + LOG_INFO(".\n"); + LOG_INFO("S&F: not enough PSRAM free, disabling.\n"); } } else { - LOG_INFO("*** Device doesn't have PSRAM.\n"); - LOG_INFO("*** Store & Forward Module - disabling server.\n"); + LOG_INFO("S&F: device doesn't have PSRAM, disabling.\n"); } // Client } else { is_client = true; - LOG_INFO("*** Initializing Store & Forward Module in Client mode\n"); + LOG_INFO("Initializing Store & Forward Module in Client mode\n"); } } else { disable(); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index d31892042..038cbfadc 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -255,7 +255,7 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) service->sendToMesh(p, RX_SRC_LOCAL, true); if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { - LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); + LOG_DEBUG("Starting next execution in 5s then going to sleep.\n"); sleepOnNextExecution = true; setIntervalFromNow(5000); } diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp index a3890df43..96dd5ae80 100644 --- a/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp @@ -19,7 +19,7 @@ MAX17048Singleton *MAX17048Singleton::pinstance{nullptr}; bool MAX17048Singleton::runOnce(TwoWire *theWire) { initialized = begin(theWire); - LOG_DEBUG("MAX17048Sensor::runOnce %s\n", initialized ? "began ok" : "begin failed"); + LOG_DEBUG("%s::runOnce %s\n", sensorStr, initialized ? "began ok" : "begin failed"); return initialized; } @@ -27,7 +27,7 @@ bool MAX17048Singleton::isBatteryCharging() { float volts = cellVoltage(); if (isnan(volts)) { - LOG_DEBUG("MAX17048Sensor::isBatteryCharging is not connected\n"); + LOG_DEBUG("%s::isBatteryCharging is not connected\n", sensorStr); return 0; } @@ -53,7 +53,7 @@ bool MAX17048Singleton::isBatteryCharging() chargeState = MAX17048ChargeState::IDLE; } - LOG_DEBUG("MAX17048Sensor::isBatteryCharging %s volts: %.3f soc: %.3f rate: %.3f\n", chargeLabels[chargeState], volts, + LOG_DEBUG("%s::isBatteryCharging %s volts: %.3f soc: %.3f rate: %.3f\n", sensorStr, chargeLabels[chargeState], volts, sample.cellPercent, sample.chargeRate); return chargeState == MAX17048ChargeState::IMPORT; } @@ -62,17 +62,17 @@ uint16_t MAX17048Singleton::getBusVoltageMv() { float volts = cellVoltage(); if (isnan(volts)) { - LOG_DEBUG("MAX17048Sensor::getBusVoltageMv is not connected\n"); + LOG_DEBUG("%s::getBusVoltageMv is not connected\n", sensorStr); return 0; } - LOG_DEBUG("MAX17048Sensor::getBusVoltageMv %.3fmV\n", volts); + LOG_DEBUG("%s::getBusVoltageMv %.3fmV\n", sensorStr, volts); return (uint16_t)(volts * 1000.0f); } uint8_t MAX17048Singleton::getBusBatteryPercent() { float soc = cellPercent(); - LOG_DEBUG("MAX17048Sensor::getBusBatteryPercent %.1f%%\n", soc); + LOG_DEBUG("%s::getBusBatteryPercent %.1f%%\n", sensorStr, soc); return clamp(static_cast(round(soc)), static_cast(0), static_cast(100)); } @@ -82,7 +82,7 @@ uint16_t MAX17048Singleton::getTimeToGoSecs() float soc = cellPercent(); // state of charge in percent 0 to 100 soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100% float ttg = ((100.0f - soc) / rate) * 3600.0f; // calculate seconds to charge/discharge - LOG_DEBUG("MAX17048Sensor::getTimeToGoSecs %.0f seconds\n", ttg); + LOG_DEBUG("%s::getTimeToGoSecs %.0f seconds\n", sensorStr, ttg); return (uint16_t)ttg; } @@ -90,7 +90,7 @@ bool MAX17048Singleton::isBatteryConnected() { float volts = cellVoltage(); if (isnan(volts)) { - LOG_DEBUG("MAX17048Sensor::isBatteryConnected is not connected\n"); + LOG_DEBUG("%s::isBatteryConnected is not connected\n", sensorStr); return false; } @@ -103,12 +103,12 @@ bool MAX17048Singleton::isExternallyPowered() float volts = cellVoltage(); if (isnan(volts)) { // if the battery is not connected then there must be external power - LOG_DEBUG("MAX17048Sensor::isExternallyPowered battery is\n"); + LOG_DEBUG("%s::isExternallyPowered battery is\n", sensorStr); return true; } // if the bus voltage is over MAX17048_BUS_POWER_VOLTS, then the external power // is assumed to be connected - LOG_DEBUG("MAX17048Sensor::isExternallyPowered %s connected\n", volts >= MAX17048_BUS_POWER_VOLTS ? "is" : "is not"); + LOG_DEBUG("%s::isExternallyPowered %s connected\n", sensorStr, volts >= MAX17048_BUS_POWER_VOLTS ? "is" : "is not"); return volts >= MAX17048_BUS_POWER_VOLTS; } diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.h b/src/modules/Telemetry/Sensor/MAX17048Sensor.h index 20dca324c..bd109cbb1 100644 --- a/src/modules/Telemetry/Sensor/MAX17048Sensor.h +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.h @@ -40,6 +40,7 @@ class MAX17048Singleton : public Adafruit_MAX17048 std::queue chargeSamples; MAX17048ChargeState chargeState = IDLE; const String chargeLabels[3] = {F("idle"), F("export"), F("import")}; + const char *sensorStr = "MAX17048Sensor"; protected: MAX17048Singleton(); diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 1f652dbf6..be8278824 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -101,7 +101,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa route[*route_count] = myNodeInfo.my_node_num; *route_count += 1; } else { - LOG_WARN("Route exceeded maximum hop limit, are you bridging networks?\n"); + LOG_WARN("Route exceeded maximum hop limit!\n"); // Are you bridging networks? } } diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index e00dde024..acda94fac 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -93,7 +93,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } break; } @@ -111,7 +111,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["role"] = new JSONValue((int)decoded->role); jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } break; } @@ -156,12 +156,12 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for position message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } break; } case meshtastic_PortNum_WAYPOINT_APP: { - msgType = "position"; + msgType = "waypoint"; meshtastic_Waypoint scratch; meshtastic_Waypoint *decoded = NULL; memset(&scratch, 0, sizeof(scratch)); @@ -176,7 +176,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for position message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } break; } @@ -202,7 +202,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["neighbors"] = new JSONValue(neighbors); jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } break; } @@ -234,7 +234,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["route"] = new JSONValue(route); jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } } break; @@ -261,7 +261,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, msgPayload["uptime"] = new JSONValue((unsigned int)decoded->uptime); jsonObj["payload"] = new JSONValue(msgPayload); } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for Paxcount message!\n"); + LOG_ERROR(errStr, msgType.c_str()); } break; } @@ -284,7 +284,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"] = new JSONValue(msgPayload); } } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + LOG_ERROR(errStr, "RemoteHardware"); } break; } diff --git a/src/serialization/MeshPacketSerializer.h b/src/serialization/MeshPacketSerializer.h index 03860ab35..989f30fc0 100644 --- a/src/serialization/MeshPacketSerializer.h +++ b/src/serialization/MeshPacketSerializer.h @@ -2,6 +2,7 @@ #include static const char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; +static const char *errStr = "Error decoding protobuf for %s message!\n"; class MeshPacketSerializer { From b8044c498309be68f10e01d79d4e66d38d31eba1 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Wed, 2 Oct 2024 23:37:08 +1300 Subject: [PATCH 1332/1377] Tweak dimensions for Canned Message Notifications (#4924) --- src/modules/CannedMessageModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index e38b7e063..615a1ab54 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -998,7 +998,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int16_t rssiY = 130; // If dislay is *slighly* too small for the original consants, squish up a bit - if (display->getHeight() < rssiY) { + if (display->getHeight() < rssiY + FONT_HEIGHT_SMALL) { snrY = display->getHeight() - ((1.5) * FONT_HEIGHT_SMALL); rssiY = display->getHeight() - ((2.5) * FONT_HEIGHT_SMALL); } From 00f15459eceee4b63fce0b86371fa37e1e5e8a75 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 2 Oct 2024 06:14:24 -0500 Subject: [PATCH 1333/1377] Userprefs prefix macros for clarity and consistency (#4923) * Convert userprefs macros to prefixed ones for clarity * Fix key --- src/graphics/Screen.cpp | 4 ++-- src/graphics/img/icon.xbm | 2 +- src/mesh/Channels.cpp | 22 +++++++++++----------- src/mesh/Default.cpp | 2 +- src/mesh/FloodingRouter.cpp | 2 +- src/mesh/NodeDB.cpp | 24 ++++++++++++------------ src/mesh/Router.cpp | 2 +- src/modules/AdminModule.cpp | 2 +- userPrefs.h | 28 ++++++++++++++-------------- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ef6b05ca4..50350a310 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -163,8 +163,8 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl display->setFont(FONT_MEDIUM); display->setTextAlignment(TEXT_ALIGN_LEFT); -#ifdef SPLASH_TITLE_USERPREFS - const char *title = SPLASH_TITLE_USERPREFS; +#ifdef USERPREFS_SPLASH_TITLE + const char *title = USERPREFS_SPLASH_TITLE; #else const char *title = "meshtastic.org"; #endif diff --git a/src/graphics/img/icon.xbm b/src/graphics/img/icon.xbm index f90cf4946..4e78ae8a1 100644 --- a/src/graphics/img/icon.xbm +++ b/src/graphics/img/icon.xbm @@ -1,4 +1,4 @@ -#ifndef HAS_USERPREFS_SPLASH +#ifndef USERPREFS_HAS_SPLASH #define icon_width 50 #define icon_height 28 static uint8_t icon_bits[] = { diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 47c013443..c8ac09a37 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -99,26 +99,26 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) ch.has_settings = true; ch.role = meshtastic_Channel_Role_PRIMARY; -#ifdef LORACONFIG_MODEM_PRESET_USERPREFS - loraConfig.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS; +#ifdef USERPREFS_LORACONFIG_MODEM_PRESET + loraConfig.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET; #endif -#ifdef LORACONFIG_CHANNEL_NUM_USERPREFS - loraConfig.channel_num = LORACONFIG_CHANNEL_NUM_USERPREFS; +#ifdef USERPREFS_LORACONFIG_CHANNEL_NUM + loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM; #endif // Install custom defaults. Will eventually support setting multiple default channels if (chIndex == 0) { -#ifdef CHANNEL_0_PSK_USERPREFS - static const uint8_t defaultpsk[] = CHANNEL_0_PSK_USERPREFS; +#ifdef USERPREFS_CHANNEL_0_PSK + static const uint8_t defaultpsk[] = USERPREFS_CHANNEL_0_PSK; memcpy(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)); channelSettings.psk.size = sizeof(defaultpsk); #endif -#ifdef CHANNEL_0_NAME_USERPREFS - strcpy(channelSettings.name, CHANNEL_0_NAME_USERPREFS); +#ifdef USERPREFS_CHANNEL_0_NAME + strcpy(channelSettings.name, USERPREFS_CHANNEL_0_NAME); #endif -#ifdef CHANNEL_0_PRECISION_USERPREFS - channelSettings.module_settings.position_precision = CHANNEL_0_PRECISION_USERPREFS; +#ifdef USERPREFS_CHANNEL_0_PRECISION + channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_0_PRECISION; #endif } } @@ -273,7 +273,7 @@ void Channels::setChannel(const meshtastic_Channel &c) bool Channels::anyMqttEnabled() { -#if EVENT_MODE +#if USERPREFS_EVENT_MODE // Don't publish messages on the public MQTT broker if we are in event mode if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0) { return false; diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp index ac7441394..0bedcfc91 100644 --- a/src/mesh/Default.cpp +++ b/src/mesh/Default.cpp @@ -45,7 +45,7 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured) { -#if EVENT_MODE +#if USERPREFS_EVENT_MODE return (configured > HOP_RELIABLE) ? HOP_RELIABLE : config.lora.hop_limit; #else return (configured >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit; diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index e2dbd8997..037fdd2ae 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -47,7 +47,7 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it tosend->hop_limit--; // bump down the hop count -#if EVENT_MODE +#if USERPREFS_EVENT_MODE if (tosend->hop_limit > 2) { // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away. tosend->hop_start -= (tosend->hop_limit - 2); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b78827e65..25314a086 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -286,24 +286,24 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) config.lora.override_duty_cycle = false; config.lora.config_ok_to_mqtt = false; -#ifdef CONFIG_LORA_REGION_USERPREFS - config.lora.region = CONFIG_LORA_REGION_USERPREFS; +#ifdef USERPREFS_CONFIG_LORA_REGION + config.lora.region = USERPREFS_CONFIG_LORA_REGION; #else config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; #endif -#ifdef LORACONFIG_MODEM_PRESET_USERPREFS - config.lora.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS; +#ifdef USERPREFS_LORACONFIG_MODEM_PRESET + config.lora.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET; #else config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; #endif config.lora.hop_limit = HOP_RELIABLE; -#ifdef CONFIG_LORA_IGNORE_MQTT_USERPREFS - config.lora.ignore_mqtt = CONFIG_LORA_IGNORE_MQTT_USERPREFS; +#ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT + config.lora.ignore_mqtt = USERPREFS_CONFIG_LORA_IGNORE_MQTT; #else config.lora.ignore_mqtt = false; #endif -#ifdef ADMIN_KEY_USERPREFS - memcpy(config.security.admin_key[0].bytes, admin_key_userprefs, 32); +#ifdef USERPREFS_USE_ADMIN_KEY + memcpy(config.security.admin_key[0].bytes, USERPREFS_ADMIN_KEY, 32); config.security.admin_key[0].size = 32; #else config.security.admin_key[0].size = 0; @@ -617,13 +617,13 @@ void NodeDB::installDefaultDeviceState() // Set default owner name pickNewNodeNum(); // based on macaddr now -#ifdef CONFIG_OWNER_LONG_NAME_USERPREFS - snprintf(owner.long_name, sizeof(owner.long_name), CONFIG_OWNER_LONG_NAME_USERPREFS); +#ifdef USERPREFS_CONFIG_OWNER_LONG_NAME + snprintf(owner.long_name, sizeof(owner.long_name), USERPREFS_CONFIG_OWNER_LONG_NAME); #else snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]); #endif -#ifdef CONFIG_OWNER_SHORT_NAME_USERPREFS - snprintf(owner.short_name, sizeof(owner.short_name), CONFIG_OWNER_SHORT_NAME_USERPREFS); +#ifdef USERPREFS_CONFIG_OWNER_SHORT_NAME + snprintf(owner.short_name, sizeof(owner.short_name), USERPREFS_CONFIG_OWNER_SHORT_NAME); #else snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]); #endif diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 610d5303e..6ff4364d1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -590,7 +590,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) skipHandle = true; } -#if EVENT_MODE +#if USERPREFS_EVENT_MODE if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && (p->decoded.portnum == meshtastic_PortNum_ATAK_FORWARDER || p->decoded.portnum == meshtastic_PortNum_ATAK_PLUGIN || p->decoded.portnum == meshtastic_PortNum_PAXCOUNTER_APP || p->decoded.portnum == meshtastic_PortNum_IP_TUNNEL_APP || diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 4a7af4817..b94bbddf2 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -467,7 +467,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) requiresReboot = true; } } -#if EVENT_MODE +#if USERPREFS_EVENT_MODE // If we're in event mode, nobody is a Router or Repeater if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { diff --git a/userPrefs.h b/userPrefs.h index 3b32bf79b..40f2cc6d2 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -3,29 +3,29 @@ // Uncomment and modify to set device defaults -// #define EVENT_MODE 1 +// #define USERPREFS_EVENT_MODE 1 -// #define CONFIG_LORA_REGION_USERPREFS meshtastic_Config_LoRaConfig_RegionCode_US -// #define LORACONFIG_MODEM_PRESET_USERPREFS meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST -// #define LORACONFIG_CHANNEL_NUM_USERPREFS 31 -// #define CONFIG_LORA_IGNORE_MQTT_USERPREFS true +// #define USERPREFS_CONFIG_LORA_REGION meshtastic_Config_LoRaConfig_RegionCode_US +// #define USERPREFS_LORACONFIG_MODEM_PRESET meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST +// #define USERPREFS_LORACONFIG_CHANNEL_NUM 31 +// #define USERPREFS_CONFIG_LORA_IGNORE_MQTT true /* -#define CHANNEL_0_PSK_USERPREFS \ +#define USERPREFS_CHANNEL_0_PSK \ { \ 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, \ 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 \ } */ -// #define CHANNEL_0_NAME_USERPREFS "DEFCONnect" -// #define CHANNEL_0_PRECISION_USERPREFS 14 +// #define USERPREFS_CHANNEL_0_NAME "DEFCONnect" +// #define USERPREFS_CHANNEL_0_PRECISION 14 -// #define CONFIG_OWNER_LONG_NAME_USERPREFS "My Long Name" -// #define CONFIG_OWNER_SHORT_NAME_USERPREFS "MLN" +// #define USERPREFS_CONFIG_OWNER_LONG_NAME "My Long Name" +// #define USERPREFS_CONFIG_OWNER_SHORT_NAME "MLN" -// #define SPLASH_TITLE_USERPREFS "DEFCONtastic" +// #define USERPREFS_SPLASH_TITLE "DEFCONtastic" // #define icon_width 34 // #define icon_height 29 -// #define HAS_USERPREFS_SPLASH +// #define USERPREFS_HAS_SPLASH /* static unsigned char icon_bits[] = { 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, @@ -37,8 +37,8 @@ static unsigned char icon_bits[] = { 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x00, 0x00, 0x1C, 0x00}; */ /* -#define ADMIN_KEY_USERPREFS 1 -static unsigned char admin_key_userprefs[] = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, +#define USERPREFS_USE_ADMIN_KEY 1 +static unsigned char USERPREFS_ADMIN_KEY[] = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c}; */ From 0a93261c0646f93aea518cc0599e547e9dc0e997 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 06:14:55 -0500 Subject: [PATCH 1334/1377] [create-pull-request] automated change (#4926) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- .../generated/meshtastic/module_config.pb.h | 18 ++++++-- .../generated/meshtastic/telemetry.pb.cpp | 3 ++ src/mesh/generated/meshtastic/telemetry.pb.h | 44 +++++++++++++++++-- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/protobufs b/protobufs index 61d7ca656..62c4b0081 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 61d7ca65652dfe832ead74719700d3d33d6bae7c +Subproject commit 62c4b0081c8217a139484a24a47e9b35e133ebf3 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index a905c4526..0fc09daca 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -359,7 +359,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3568 +#define meshtastic_OEMStore_size 3576 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 19600856f..bac67942c 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -188,7 +188,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size #define meshtastic_LocalConfig_size 735 -#define meshtastic_LocalModuleConfig_size 687 +#define meshtastic_LocalModuleConfig_size 695 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index d4b82c93b..ce8b8891a 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -336,6 +336,12 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig { /* Interval in seconds of how often we should try to send our air quality metrics to the mesh */ bool power_screen_enabled; + /* Preferences for the (Health) Telemetry Module + Enable/Disable the telemetry measurement module measurement collection */ + bool health_measurement_enabled; + /* Interval in seconds of how often we should try to send our + health metrics to the mesh */ + uint32_t health_update_interval; } meshtastic_ModuleConfig_TelemetryConfig; /* TODO: REPLACE */ @@ -503,7 +509,7 @@ extern "C" { #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} -#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -519,7 +525,7 @@ extern "C" { #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} -#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -601,6 +607,8 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_power_measurement_enabled_tag 8 #define meshtastic_ModuleConfig_TelemetryConfig_power_update_interval_tag 9 #define meshtastic_ModuleConfig_TelemetryConfig_power_screen_enabled_tag 10 +#define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11 +#define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3 @@ -793,7 +801,9 @@ X(a, STATIC, SINGULAR, BOOL, air_quality_enabled, 6) \ X(a, STATIC, SINGULAR, UINT32, air_quality_interval, 7) \ X(a, STATIC, SINGULAR, BOOL, power_measurement_enabled, 8) \ X(a, STATIC, SINGULAR, UINT32, power_update_interval, 9) \ -X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) +X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \ +X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \ +X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL @@ -878,7 +888,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 -#define meshtastic_ModuleConfig_TelemetryConfig_size 36 +#define meshtastic_ModuleConfig_TelemetryConfig_size 44 #define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index 90859c98e..b9c1da7a0 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -21,6 +21,9 @@ PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO) PB_BIND(meshtastic_LocalStats, meshtastic_LocalStats, AUTO) +PB_BIND(meshtastic_HealthMetrics, meshtastic_HealthMetrics, AUTO) + + PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, AUTO) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index cedc2867e..61897fc5c 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -71,7 +71,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* MAX17048 1S lipo battery sensor (voltage, state of charge, time to go) */ meshtastic_TelemetrySensorType_MAX17048 = 28, /* Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor */ - meshtastic_TelemetrySensorType_CUSTOM_SENSOR = 29 + meshtastic_TelemetrySensorType_CUSTOM_SENSOR = 29, + /* MAX30102 Pulse Oximeter and Heart-Rate Sensor */ + meshtastic_TelemetrySensorType_MAX30102 = 30 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -233,6 +235,19 @@ typedef struct _meshtastic_LocalStats { uint16_t num_total_nodes; } meshtastic_LocalStats; +/* Health telemetry metrics */ +typedef struct _meshtastic_HealthMetrics { + /* Heart rate (beats per minute) */ + bool has_heart_bpm; + uint8_t heart_bpm; + /* SpO2 (blood oxygen saturation) level */ + bool has_spO2; + uint8_t spO2; + /* Body temperature in degrees Celsius */ + bool has_temperature; + float temperature; +} meshtastic_HealthMetrics; + /* Types of Measurements the telemetry module is equipped to handle */ typedef struct _meshtastic_Telemetry { /* Seconds since 1970 - or 0 for unknown/unset */ @@ -249,6 +264,8 @@ typedef struct _meshtastic_Telemetry { meshtastic_PowerMetrics power_metrics; /* Local device mesh statistics */ meshtastic_LocalStats local_stats; + /* Health telemetry metrics */ + meshtastic_HealthMetrics health_metrics; } variant; } meshtastic_Telemetry; @@ -267,8 +284,9 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_CUSTOM_SENSOR -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_CUSTOM_SENSOR+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_MAX30102 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_MAX30102+1)) + @@ -284,6 +302,7 @@ extern "C" { #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} @@ -291,6 +310,7 @@ extern "C" { #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} @@ -343,12 +363,16 @@ extern "C" { #define meshtastic_LocalStats_num_packets_rx_bad_tag 6 #define meshtastic_LocalStats_num_online_nodes_tag 7 #define meshtastic_LocalStats_num_total_nodes_tag 8 +#define meshtastic_HealthMetrics_heart_bpm_tag 1 +#define meshtastic_HealthMetrics_spO2_tag 2 +#define meshtastic_HealthMetrics_temperature_tag 3 #define meshtastic_Telemetry_time_tag 1 #define meshtastic_Telemetry_device_metrics_tag 2 #define meshtastic_Telemetry_environment_metrics_tag 3 #define meshtastic_Telemetry_air_quality_metrics_tag 4 #define meshtastic_Telemetry_power_metrics_tag 5 #define meshtastic_Telemetry_local_stats_tag 6 +#define meshtastic_Telemetry_health_metrics_tag 7 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 @@ -421,13 +445,21 @@ X(a, STATIC, SINGULAR, UINT32, num_total_nodes, 8) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL +#define meshtastic_HealthMetrics_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, UINT32, heart_bpm, 1) \ +X(a, STATIC, OPTIONAL, UINT32, spO2, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, temperature, 3) +#define meshtastic_HealthMetrics_CALLBACK NULL +#define meshtastic_HealthMetrics_DEFAULT NULL + #define meshtastic_Telemetry_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, FIXED32, time, 1) \ X(a, STATIC, ONEOF, MESSAGE, (variant,device_metrics,variant.device_metrics), 2) \ X(a, STATIC, ONEOF, MESSAGE, (variant,environment_metrics,variant.environment_metrics), 3) \ X(a, STATIC, ONEOF, MESSAGE, (variant,air_quality_metrics,variant.air_quality_metrics), 4) \ X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics), 5) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), 6) +X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,health_metrics,variant.health_metrics), 7) #define meshtastic_Telemetry_CALLBACK NULL #define meshtastic_Telemetry_DEFAULT NULL #define meshtastic_Telemetry_variant_device_metrics_MSGTYPE meshtastic_DeviceMetrics @@ -435,6 +467,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), #define meshtastic_Telemetry_variant_air_quality_metrics_MSGTYPE meshtastic_AirQualityMetrics #define meshtastic_Telemetry_variant_power_metrics_MSGTYPE meshtastic_PowerMetrics #define meshtastic_Telemetry_variant_local_stats_MSGTYPE meshtastic_LocalStats +#define meshtastic_Telemetry_variant_health_metrics_MSGTYPE meshtastic_HealthMetrics #define meshtastic_Nau7802Config_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \ @@ -447,6 +480,7 @@ extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg; extern const pb_msgdesc_t meshtastic_LocalStats_msg; +extern const pb_msgdesc_t meshtastic_HealthMetrics_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; @@ -456,6 +490,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg #define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg #define meshtastic_LocalStats_fields &meshtastic_LocalStats_msg +#define meshtastic_HealthMetrics_fields &meshtastic_HealthMetrics_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg #define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg @@ -464,6 +499,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_AirQualityMetrics_size 72 #define meshtastic_DeviceMetrics_size 27 #define meshtastic_EnvironmentMetrics_size 85 +#define meshtastic_HealthMetrics_size 11 #define meshtastic_LocalStats_size 42 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 From 07d4e6f5beadbd36ef54d1fba49f14ceb6fc09fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 07:57:34 -0500 Subject: [PATCH 1335/1377] Bump protobufs from `62c4b00` to `b419706` (#4934) Bumps [protobufs](https://github.com/meshtastic/protobufs) from `62c4b00` to `b419706`. - [Release notes](https://github.com/meshtastic/protobufs/releases) - [Commits](https://github.com/meshtastic/protobufs/compare/62c4b0081c8217a139484a24a47e9b35e133ebf3...b419706693e0120f7b032d0be0121ae758cfd6e4) --- updated-dependencies: - dependency-name: protobufs dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 62c4b0081..b41970669 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 62c4b0081c8217a139484a24a47e9b35e133ebf3 +Subproject commit b419706693e0120f7b032d0be0121ae758cfd6e4 From b2b60eccdbe1d1732859ad52ae05d6c05fbdc7ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:54:18 -0500 Subject: [PATCH 1336/1377] [create-pull-request] automated change (#4937) Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> --- src/mesh/generated/meshtastic/telemetry.pb.h | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 61897fc5c..cbe71ed52 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -73,7 +73,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor */ meshtastic_TelemetrySensorType_CUSTOM_SENSOR = 29, /* MAX30102 Pulse Oximeter and Heart-Rate Sensor */ - meshtastic_TelemetrySensorType_MAX30102 = 30 + meshtastic_TelemetrySensorType_MAX30102 = 30, + /* MLX90614 non-contact IR temperature sensor. */ + meshtastic_TelemetrySensorType_MLX90614 = 31 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -225,7 +227,7 @@ typedef struct _meshtastic_LocalStats { float air_util_tx; /* Number of packets sent */ uint32_t num_packets_tx; - /* Number of packets received good */ + /* Number of packets received (both good and bad) */ uint32_t num_packets_rx; /* Number of packets received that are malformed or violate the protocol */ uint32_t num_packets_rx_bad; @@ -233,6 +235,14 @@ typedef struct _meshtastic_LocalStats { uint16_t num_online_nodes; /* Number of nodes total */ uint16_t num_total_nodes; + /* Number of received packets that were duplicates (due to multiple nodes relaying). + If this number is high, there are nodes in the mesh relaying packets when it's unnecessary, for example due to the ROUTER/REPEATER role. */ + uint32_t num_rx_dupe; + /* Number of packets we transmitted that were a relay for others (not originating from ourselves). */ + uint32_t num_tx_relay; + /* Number of times we canceled a packet to be relayed, because someone else did it before us. + This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you. */ + uint32_t num_tx_relay_canceled; } meshtastic_LocalStats; /* Health telemetry metrics */ @@ -284,8 +294,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_MAX30102 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_MAX30102+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_MLX90614 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_MLX90614+1)) @@ -301,7 +311,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} @@ -309,7 +319,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} @@ -363,6 +373,9 @@ extern "C" { #define meshtastic_LocalStats_num_packets_rx_bad_tag 6 #define meshtastic_LocalStats_num_online_nodes_tag 7 #define meshtastic_LocalStats_num_total_nodes_tag 8 +#define meshtastic_LocalStats_num_rx_dupe_tag 9 +#define meshtastic_LocalStats_num_tx_relay_tag 10 +#define meshtastic_LocalStats_num_tx_relay_canceled_tag 11 #define meshtastic_HealthMetrics_heart_bpm_tag 1 #define meshtastic_HealthMetrics_spO2_tag 2 #define meshtastic_HealthMetrics_temperature_tag 3 @@ -441,7 +454,10 @@ X(a, STATIC, SINGULAR, UINT32, num_packets_tx, 4) \ X(a, STATIC, SINGULAR, UINT32, num_packets_rx, 5) \ X(a, STATIC, SINGULAR, UINT32, num_packets_rx_bad, 6) \ X(a, STATIC, SINGULAR, UINT32, num_online_nodes, 7) \ -X(a, STATIC, SINGULAR, UINT32, num_total_nodes, 8) +X(a, STATIC, SINGULAR, UINT32, num_total_nodes, 8) \ +X(a, STATIC, SINGULAR, UINT32, num_rx_dupe, 9) \ +X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \ +X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL @@ -500,7 +516,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_DeviceMetrics_size 27 #define meshtastic_EnvironmentMetrics_size 85 #define meshtastic_HealthMetrics_size 11 -#define meshtastic_LocalStats_size 42 +#define meshtastic_LocalStats_size 60 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 92 From befc2ece6f06f6002e5ce3128df3c4c82aa538fe Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 3 Oct 2024 20:51:22 -0500 Subject: [PATCH 1337/1377] Add a Userprefs Timezone String, to be replaced in the web flasher (#4938) * Add a Userprefs Timezone String, to be replaced in the web flasher * Use a volatile char buffer for slipstreamed strings. * More refinement --- src/main.cpp | 11 +++++++++-- userPrefs.h | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c11995837..a8ea69111 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "../userPrefs.h" #include "configuration.h" #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" @@ -706,10 +707,16 @@ void setup() // setup TZ prior to time actions. #if !MESHTASTIC_EXCLUDE_TZ - if (*config.device.tzdef) { + LOG_DEBUG("Using compiled/slipstreamed %s\n", USERPREFS_TZ_STRING); // important, removing this clobbers our magic string + if (*config.device.tzdef && config.device.tzdef[0] != 0) { + LOG_DEBUG("Saved TZ: %s \n", config.device.tzdef); setenv("TZ", config.device.tzdef, 1); } else { - setenv("TZ", "GMT0", 1); + if (strncmp((const char *)USERPREFS_TZ_STRING, "tzpl", 4) == 0) { + setenv("TZ", "GMT0", 1); + } else { + setenv("TZ", (const char *)USERPREFS_TZ_STRING, 1); + } } tzset(); LOG_DEBUG("Set Timezone to %s\n", getenv("TZ")); diff --git a/userPrefs.h b/userPrefs.h index 40f2cc6d2..337bd7976 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -1,6 +1,6 @@ #ifndef _USERPREFS_ #define _USERPREFS_ - +volatile static const char USERPREFS_TZ_STRING[] = "tzplaceholder "; // Uncomment and modify to set device defaults // #define USERPREFS_EVENT_MODE 1 From d6f26c682d05163d971d81af141ccf814dfa807c Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Fri, 4 Oct 2024 07:15:59 -0400 Subject: [PATCH 1338/1377] Enabling Ve pin on T114 (#4940) * Enabling Ve pin on T114 Problem: The Ve pin was not enabled in the firmware, and it was supposed to control the power to the GPS via the GPS_EN pin. As a result, users were forced to rely on the 3.3V pin to power their additional peripherals, which caused a constant power draw from the battery, even when the node was in deep sleep mode. Solution: To resolve this, Todd_Hervert and I decided to remove the GPS power toggle after testing revealed that the GPS only consumes 1mA in soft sleep mode. This minimal power consumption allowed us to enable the Ve pin without causing significant battery drain. Additionally, we added a delay to the I2C initialization process, as the Ve pin requires a few milliseconds to stabilize, which could prevent some peripherals from booting up in time. Result: The GPS operates as usual, drawing only 1mA of power. The keyboard and other peripherals attached to the Ve pin now power off correctly when the node is shut down. The I2C check initiates without issues after the delay, allowing all peripherals to function smoothly. * trunk format --------- Co-authored-by: Tom Fifield --- variants/heltec_mesh_node_t114/variant.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/heltec_mesh_node_t114/variant.h index 2cea3ef2f..426085a26 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/heltec_mesh_node_t114/variant.h @@ -154,8 +154,11 @@ No longer populated on PCB // #define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K #define GPS_RESET_MODE LOW -#define PIN_GPS_EN (21) -#define GPS_EN_ACTIVE HIGH +// #define PIN_GPS_EN (21) +#define VEXT_ENABLE (0 + 21) +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define VEXT_ON_VALUE HIGH +// #define GPS_EN_ACTIVE HIGH #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board From 236374491b36248d36519392d5d58ef4cadab9a7 Mon Sep 17 00:00:00 2001 From: gitbisector Date: Fri, 4 Oct 2024 04:17:23 -0700 Subject: [PATCH 1339/1377] cleanupNeighbors() time difference fix (#4941) --- src/modules/NeighborInfoModule.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 55ed46b5e..e4c9b44bd 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -62,7 +62,8 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb NodeNum my_node_id = nodeDB->getNodeNum(); neighborInfo->node_id = my_node_id; neighborInfo->last_sent_by_id = my_node_id; - neighborInfo->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; + neighborInfo->node_broadcast_interval_secs = + Default::getConfiguredOrDefault(moduleConfig.neighbor_info.update_interval, default_telemetry_broadcast_interval_secs); cleanUpNeighbors(); @@ -84,11 +85,12 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb */ void NeighborInfoModule::cleanUpNeighbors() { + uint32_t now = getTime(); NodeNum my_node_id = nodeDB->getNodeNum(); for (auto it = neighbors.rbegin(); it != neighbors.rend();) { // We will remove a neighbor if we haven't heard from them in twice the broadcast interval - if (!Throttle::isWithinTimespanMs(it->last_rx_time, it->node_broadcast_interval_secs * 2) && - (it->node_id != my_node_id)) { + // cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970 + if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) { LOG_DEBUG("Removing neighbor with node ID 0x%x\n", it->node_id); it = std::vector::reverse_iterator( neighbors.erase(std::next(it).base())); // Erase the element and update the iterator From 673fe294f346faf160ff2de2cab6cdf8bd500d7e Mon Sep 17 00:00:00 2001 From: GUVWAF <78759985+GUVWAF@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:28:51 +0200 Subject: [PATCH 1340/1377] Add `rxDupe`, `txRelay` and `txRelayCanceled` to LocalStats (#4936) * Introduce `isFromUs()` and `isToUs()` * Add rxDupe, txRelay and txRelayCanceled to LocalStats --- src/mesh/FloodingRouter.cpp | 8 +++++--- src/mesh/MeshModule.cpp | 5 ++--- src/mesh/MeshPacketQueue.cpp | 2 +- src/mesh/MeshTypes.h | 6 ++++++ src/mesh/NodeDB.cpp | 12 ++++++++++++ src/mesh/RadioLibInterface.cpp | 4 +++- src/mesh/RadioLibInterface.h | 2 +- src/mesh/ReliableRouter.cpp | 6 ++---- src/mesh/Router.cpp | 20 ++++++++++---------- src/mesh/Router.h | 4 ++++ src/modules/AdminModule.cpp | 2 +- src/modules/ExternalNotificationModule.cpp | 4 ++-- src/modules/NodeInfoModule.cpp | 2 +- src/modules/PositionModule.cpp | 4 ++-- src/modules/RangeTestModule.cpp | 2 +- src/modules/RoutingModule.cpp | 2 +- src/modules/SerialModule.cpp | 2 +- src/modules/StoreForwardModule.cpp | 7 +++---- src/modules/Telemetry/DeviceTelemetry.cpp | 5 +++++ src/modules/TraceRouteModule.cpp | 4 ++-- src/modules/esp32/AudioModule.cpp | 2 +- src/mqtt/MQTT.cpp | 10 +++++----- 22 files changed, 71 insertions(+), 44 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 037fdd2ae..23f6b6f92 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -22,10 +22,12 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record printPacket("Ignoring dupe incoming msg", p); + rxDupe++; if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! - Router::cancelSending(p->from, p->id); + if (Router::cancelSending(p->from, p->id)) + txRelayCanceled++; } return true; } @@ -36,12 +38,12 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) { bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0); - if (isAckorReply && p->to != getNodeNum() && p->to != NODENUM_BROADCAST) { + if (isAckorReply && !isToUs(p) && p->to != NODENUM_BROADCAST) { // do not flood direct message that is ACKed or replied to LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast.\n"); Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM } - if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) { + if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) { if (p->id != 0) { if (config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) { meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 3b137d4bd..27aca5832 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -86,7 +86,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB->getNodeNum(); - bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; + bool toUs = mp.to == NODENUM_BROADCAST || isToUs(&mp); for (auto i = modules->begin(); i != modules->end(); ++i) { auto &pi = **i; @@ -141,8 +141,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) // because currently when the phone sends things, it sends things using the local node ID as the from address. A // better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like // any other node. - if (isDecoded && mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && - !currentReply) { + if (isDecoded && mp.decoded.want_response && toUs && (!isFromUs(&mp) || isToUs(&mp)) && !currentReply) { pi.sendResponse(mp); ignoreRequest = ignoreRequest || pi.ignoreRequest; // If at least one module asks it, we may ignore a request LOG_INFO("Asked module '%s' to send a response\n", pi.name); diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index da49ecb61..99ef41c1e 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -19,7 +19,7 @@ bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_Mes auto p1p = getPriority(p1), p2p = getPriority(p2); // If priorities differ, use that // for equal priorities, prefer packets already on mesh. - return (p1p != p2p) ? (p1p > p2p) : (getFrom(p1) != nodeDB->getNodeNum() && getFrom(p2) == nodeDB->getNodeNum()); + return (p1p != p2p) ? (p1p > p2p) : (!isFromUs(p1) && isFromUs(p2)); } MeshPacketQueue::MeshPacketQueue(size_t _maxLen) : maxLen(_maxLen) {} diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 90cfd5897..27d100fbe 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -50,5 +50,11 @@ extern Allocator &packetPool; */ NodeNum getFrom(const meshtastic_MeshPacket *p); +// Returns true if the packet originated from the local node +bool isFromUs(const meshtastic_MeshPacket *p); + +// Returns true if the packet is destined to us +bool isToUs(const meshtastic_MeshPacket *p); + /* Some clients might not properly set priority, therefore we fix it here. */ void fixPriority(meshtastic_MeshPacket *p); \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 25314a086..aaf14c5b8 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -196,6 +196,18 @@ NodeNum getFrom(const meshtastic_MeshPacket *p) return (p->from == 0) ? nodeDB->getNodeNum() : p->from; } +// Returns true if the packet originated from the local node +bool isFromUs(const meshtastic_MeshPacket *p) +{ + return p->from == 0 || p->from == nodeDB->getNodeNum(); +} + +// Returns true if the packet is destined to us +bool isToUs(const meshtastic_MeshPacket *p) +{ + return p->to == nodeDB->getNodeNum(); +} + bool NodeDB::resetRadioConfig(bool factory_reset) { bool didFactoryReset = false; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 807c8aa02..818826018 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -186,7 +186,7 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p) #ifndef LORA_DISABLE_SENDING printPacket("enqueuing for send", p); - LOG_DEBUG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad); + LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d\n", txGood, txRelay, rxGood, rxBad); ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks @@ -353,6 +353,8 @@ void RadioLibInterface::completeSending() if (p) { txGood++; + if (!isFromUs(p)) + txRelay++; printPacket("Completed sending", p); // We are done sending that packet, release it diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 090c03046..673f53a32 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -107,7 +107,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified /** * Debugging counts */ - uint32_t rxBad = 0, rxGood = 0, txGood = 0; + uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; public: RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 9482f4185..fa05e7973 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -78,7 +78,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) * Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and * flooding this ACK back to the original sender already adds redundancy. */ bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit); - if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB->getNodeNum()) { + if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && !isToUs(p)) { LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n"); meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); tosend->hop_limit--; // bump down the hop count @@ -102,9 +102,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) */ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) { - NodeNum ourNode = getNodeNum(); - - if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability) + if (isToUs(p)) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability) if (p->want_ack) { if (MeshModule::currentReply) { LOG_DEBUG("Another module replied to this message, no need for 2nd ack\n"); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 6ff4364d1..b5732dee9 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -169,7 +169,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) LOG_ERROR("Packet received with to: of 0!\n"); } // No need to deliver externally if the destination is the local node - if (p->to == nodeDB->getNodeNum()) { + if (isToUs(p)) { printPacket("Enqueued local", p); enqueueReceivedMessage(p); return ERRNO_OK; @@ -204,7 +204,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) */ ErrorCode Router::send(meshtastic_MeshPacket *p) { - if (p->to == nodeDB->getNodeNum()) { + if (isToUs(p)) { LOG_ERROR("BUG! send() called with packet destined for local node!\n"); packetPool.release(p); return meshtastic_Routing_Error_BAD_REQUEST; @@ -226,7 +226,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) service->sendClientNotification(cn); #endif meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; - if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh + if (isFromUs(p)) { // only send NAK to API, not to the mesh abortSendAndNak(err, p); } else { packetPool.release(p); @@ -248,7 +248,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) p->from = getFrom(p); // If we are the original transmitter, set the hop limit with which we start - if (p->from == getNodeNum()) + if (isFromUs(p)) p->hop_start = p->hop_limit; // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) @@ -273,7 +273,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) } #if !MESHTASTIC_EXCLUDE_MQTT // Only publish to MQTT if we're the original transmitter of the packet - if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) { + if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) { mqtt->onSend(*p, *p_decoded, chIndex); } #endif @@ -328,9 +328,9 @@ bool perhapsDecode(meshtastic_MeshPacket *p) memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize); #if !(MESHTASTIC_EXCLUDE_PKI) // Attempt PKI decryption first - if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && p->to != NODENUM_BROADCAST && - nodeDB->getMeshNode(p->from) != nullptr && nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && - nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > MESHTASTIC_PKC_OVERHEAD) { + if (p->channel == 0 && isToUs(p) && p->to > 0 && p->to != NODENUM_BROADCAST && nodeDB->getMeshNode(p->from) != nullptr && + nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && + rawSize > MESHTASTIC_PKC_OVERHEAD) { LOG_DEBUG("Attempting PKI decryption\n"); if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) { @@ -432,7 +432,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - if (p->from == nodeDB->getNodeNum()) { + if (isFromUs(p)) { p->decoded.has_bitfield = true; p->decoded.bitfield |= (config.lora.config_ok_to_mqtt << BITFIELD_OK_TO_MQTT_SHIFT); p->decoded.bitfield |= (p->decoded.want_response << BITFIELD_WANT_RESPONSE_SHIFT); @@ -613,7 +613,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) #if !MESHTASTIC_EXCLUDE_MQTT // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet - if (decoded && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt) + if (decoded && moduleConfig.mqtt.enabled && !isFromUs(p) && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); #endif } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index fd4b0ccf9..8ebc1a3e5 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -82,6 +82,10 @@ class Router : protected concurrency::OSThread */ virtual ErrorCode send(meshtastic_MeshPacket *p); + /* Statistics for the amount of duplicate received packets and the amount of times we cancel a relay because someone did it + before us */ + uint32_t rxDupe = 0, txRelayCanceled = 0; + protected: friend class RoutingModule; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index b94bbddf2..01ef038e8 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -66,7 +66,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta // if handled == false, then let others look at this message also if they want bool handled = false; assert(r); - bool fromOthers = mp.from != 0 && mp.from != nodeDB->getNodeNum(); + bool fromOthers = !isFromUs(&mp); if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { return handled; } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 652db04d3..8abc386ec 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -428,7 +428,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP drv.setWaveform(2, 0); drv.go(); #endif - if (getFrom(&mp) != nodeDB->getNodeNum()) { + if (!isFromUs(&mp)) { // Check if the message contains a bell character. Don't do this loop for every pin, just once. auto &p = mp.decoded; bool containsBell = false; @@ -593,4 +593,4 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) if (changed) { nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } -} +} \ No newline at end of file diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 41f008fb0..61ec375cc 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -26,7 +26,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } // if user has changed while packet was not for us, inform phone - if (hasChanged && !wasBroadcast && mp.to != nodeDB->getNodeNum()) + if (hasChanged && !wasBroadcast && !isToUs(&mp)) service->sendToPhone(packetPool.allocCopy(mp)); // LOG_DEBUG("did handleReceived\n"); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f49a654af..6ad30f927 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -54,7 +54,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // FIXME this can in fact happen with packets sent from EUD (src=RX_SRC_USER) // to set fixed location, EUD-GPS location or just the time (see also issue #900) bool isLocal = false; - if (nodeDB->getNodeNum() == getFrom(&mp)) { + if (isFromUs(&mp)) { isLocal = true; if (config.position.fixed_position) { LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n"); @@ -110,7 +110,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p) { // Phone position packets need to be truncated to the channel precision - if (nodeDB->getNodeNum() == getFrom(&mp) && (precision < 32 && precision > 0)) { + if (isFromUs(&mp) && (precision < 32 && precision > 0)) { LOG_DEBUG("Truncating phone position to channel precision %i\n", precision); p->latitude_i = p->latitude_i & (UINT32_MAX << (32 - precision)); p->longitude_i = p->longitude_i & (UINT32_MAX << (32 - precision)); diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index c98f15d40..e78b4e68d 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -139,7 +139,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket LOG_INFO.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); */ - if (getFrom(&mp) != nodeDB->getNodeNum()) { + if (!isFromUs(&mp)) { if (moduleConfig.range_test.save) { appendFile(mp); diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index b7be4abc9..3b7be1ab7 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -28,7 +28,7 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh // FIXME - move this to a non promsicious PhoneAPI module? // Note: we are careful not to send back packets that started with the phone back to the phone - if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB->getNodeNum()) && (mp.from != 0)) { + if ((mp.to == NODENUM_BROADCAST || isToUs(&mp)) && (mp.from != 0)) { printPacket("Delivering rx packet", &mp); service->handleFromRadio(&mp); } diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 6d6a947bc..d40b59345 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -310,7 +310,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp // LOG_DEBUG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n", // nodeDB->getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); - if (getFrom(&mp) == nodeDB->getNodeNum()) { + if (!isFromUs(&mp)) { /* * If moduleConfig.serial.echo is true, then echo the packets that are sent out diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 58be8c01e..e0092839f 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -333,7 +333,7 @@ void StoreForwardModule::sendErrorTextMessage(NodeNum dest, bool want_response) if (this->busy) { str = "S&F - Busy. Try again shortly."; } else { - str = "S&F - Not available on this channel."; + str = "S&F not permitted on the public channel."; } LOG_WARN("%s\n", str); memcpy(pr->decoded.payload.bytes, str, strlen(str)); @@ -382,8 +382,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; - if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && - (p.payload.bytes[2] == 0x00)) { + if (isToUs(&mp) && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) { LOG_DEBUG("Legacy Request to send\n"); // Send the last 60 minutes of messages. @@ -396,7 +395,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m storeForwardModule->historyAdd(mp); LOG_INFO("S&F stored. Message history contains %u records now.\n", this->packetHistoryTotalCount); } - } else if (getFrom(&mp) != nodeDB->getNodeNum() && mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) { + } else if (!isFromUs(&mp) && mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) { auto &p = mp.decoded; meshtastic_StoreAndForward scratch; meshtastic_StoreAndForward *decoded = NULL; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index dd5067784..eb3f67e96 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -127,6 +127,11 @@ void DeviceTelemetryModule::sendLocalStatsToPhone() telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood; telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; + telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay; + } + if (router) { + telemetry.variant.local_stats.num_rx_dupe = router->rxDupe; + telemetry.variant.local_stats.num_tx_relay_canceled = router->txRelayCanceled; } LOG_INFO( diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index be8278824..955098c28 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -16,8 +16,8 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti // Insert unknown hops if necessary insertUnknownHops(p, r, !incoming.request_id); - // Append ID and SNR. For the last hop (p.to == nodeDB->getNodeNum()), we only need to append the SNR - appendMyIDandSNR(r, p.rx_snr, !incoming.request_id, p.to == nodeDB->getNodeNum()); + // Append ID and SNR. If the last hop is to us, we only need to append the SNR + appendMyIDandSNR(r, p.rx_snr, !incoming.request_id, isToUs(&p)); if (!incoming.request_id) printRoute(r, p.from, p.to, true); else diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 8a29f9a2e..89e4b4e25 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -273,7 +273,7 @@ ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp) { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; - if (getFrom(&mp) != nodeDB->getNodeNum()) { + if (!isFromUs(&mp)) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); radio_state = RadioState::rx; rx_encode_frame_index = p.payload.size; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 8d08f97a6..0c1ce0c7c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -150,7 +150,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node // receives it when we get our own packet back. Then we'll stop our retransmissions. - if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) + if (e.packet && isFromUs(e.packet)) routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); else LOG_INFO("Ignoring downlink message we originally sent.\n"); @@ -162,7 +162,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); p->via_mqtt = true; // Mark that the packet was received via MQTT - if (p->from == 0 || p->from == nodeDB->getNodeNum()) { + if (isFromUs(p)) { LOG_INFO("Ignoring downlink message we originally sent.\n"); packetPool.release(p); return; @@ -188,7 +188,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's // likely they discovered each other via a channel we have downlink enabled for - if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) + if (isToUs(p) || (tx && tx->has_user && rx && rx->has_user)) router->enqueueReceivedMessage(p); } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key router->enqueueReceivedMessage(p); @@ -542,7 +542,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & } // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && + if (!isFromUs(&mp_decoded) && mp_decoded.decoded.has_bitfield && !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { @@ -692,4 +692,4 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type (json.find("payload") != json.end()); // should have a payload -} +} \ No newline at end of file From e7cfadacd8dbd4f707ab7c09c050d2a9e7d23b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Fri, 4 Oct 2024 14:47:14 +0200 Subject: [PATCH 1341/1377] Add Panel_ILI9342 to TFTDisplay.cpp (#4822) * Add Panel_ILI9342 to TFTDisplay.cpp [Panel_ILI9342](https://github.com/lovyan03/LovyanGFX/blob/master/src/lgfx/v1/panel/Panel_ILI9342.hpp) * Add ILI9342_DRIVER to TFTDisplay.cpp * Add ILI9342_DRIVER to Screen.cpp * Add ILI9342_DRIVER to ScreenFonts.h * Add ILI9342_DRIVER to main.cpp * Add ILI9342_DRIVER to images.h * Add ILI9342_DRIVER to NodeDB.cpp * Add ILI9342 to PortduinoGlue.cpp * Add ili9342 to PortduinoGlue.h * Fix formatting * Update Screen.cpp to add ILI9342_DRIVER * Update TFTDisplay.cpp * Update TFTDisplay.cpp * Update Screen.cpp * Update Screen.cpp --------- Co-authored-by: Ben Meadors Co-authored-by: Tom Fifield --- src/graphics/Screen.cpp | 26 ++++++++++++------------ src/graphics/ScreenFonts.h | 6 +++--- src/graphics/TFTDisplay.cpp | 22 ++++++++++++++------ src/graphics/images.h | 4 ++-- src/main.cpp | 6 +++--- src/mesh/NodeDB.cpp | 6 +++--- src/platform/portduino/PortduinoGlue.cpp | 2 ++ src/platform/portduino/PortduinoGlue.h | 2 +- 8 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 50350a310..66900c53d 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1097,8 +1097,8 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat { char usersString[20]; snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ - defined(USE_ST7789) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x, y + 3, 8, 8, imgUser); #else @@ -1534,8 +1534,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || defined(RAK14014) || \ - defined(HX8357_CS) +#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ + defined(RAK14014) || defined(HX8357_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -1732,8 +1732,8 @@ void Screen::setup() // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically // flip it. If you have a headache now, you're welcome. if (!config.display.flip_screen) { -#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \ + defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS) static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); @@ -2472,8 +2472,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #ifdef ARCH_ESP32 if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat, (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ - defined(USE_ST7789) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -2484,8 +2484,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 imgQuestion); #endif } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ - defined(USE_ST7789) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -2499,8 +2499,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #endif } else { // TODO: Raspberry Pi supports more than just the one screen size -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ - defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); @@ -2817,4 +2817,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 34c832635..c9ce961fa 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -12,8 +12,8 @@ #include "graphics/fonts/OLEDDisplayFontsUA.h" #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ - defined(USE_ST7789) || defined(HX8357_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL ArialMT_Plain_16 // Height: 19 @@ -41,4 +41,4 @@ #define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL) #define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE) \ No newline at end of file +#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index c0326abec..0c32a7c32 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -244,9 +244,9 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; -#elif defined(ILI9341_DRIVER) +#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) -#include // Graphics and font library for ILI9341 driver chip +#include // Graphics and font library for ILI9341/ILI9342 driver chip #if defined(ILI9341_BACKLIGHT_EN) && !defined(TFT_BL) #define TFT_BL ILI9341_BACKLIGHT_EN @@ -254,7 +254,11 @@ static LGFX *tft = nullptr; class LGFX : public lgfx::LGFX_Device { +#if defined(ILI9341_DRIVER) lgfx::Panel_ILI9341 _panel_instance; +#elif defined(ILI9342_DRIVER) + lgfx::Panel_ILI9342 _panel_instance; +#endif lgfx::Bus_SPI _bus_instance; lgfx::Light_PWM _light_instance; @@ -265,7 +269,11 @@ class LGFX : public lgfx::LGFX_Device auto cfg = _bus_instance.config(); // configure SPI +#if defined(ILI9341_DRIVER) cfg.spi_host = ILI9341_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST +#elif defined(ILI9342_DRIVER) + cfg.spi_host = ILI9342_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST +#endif cfg.spi_mode = 0; cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing // 80MHz by an integer) @@ -336,7 +344,7 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; #elif defined(ST7735_CS) -#include // Graphics and font library for ILI9341 driver chip +#include // Graphics and font library for ILI9342 driver chip static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h #elif ARCH_PORTDUINO && HAS_SCREEN != 0 @@ -360,6 +368,8 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ST7735S; else if (settingsMap[displayPanel] == ili9341) _panel_instance = new lgfx::Panel_ILI9341; + else if (settingsMap[displayPanel] == ili9342) + _panel_instance = new lgfx::Panel_ILI9342; auto buscfg = _bus_instance.config(); buscfg.spi_mode = 0; buscfg.spi_host = settingsMap[displayspidev]; @@ -618,8 +628,8 @@ static LGFX *tft = nullptr; #endif -#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || \ - defined(HX8357_CS) || (ARCH_PORTDUINO && HAS_SCREEN != 0) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ + defined(RAK14014) || defined(HX8357_CS) || (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" #include @@ -850,4 +860,4 @@ bool TFTDisplay::connect() return true; } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/images.h b/src/graphics/images.h index 7028f18e3..2b0854a33 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -20,8 +20,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #endif -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || \ - defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/main.cpp b/src/main.cpp index a8ea69111..66b843ced 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -773,8 +773,8 @@ void setup() #if !MESHTASTIC_EXCLUDE_I2C // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) screen->setup(); #elif defined(ARCH_PORTDUINO) if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { @@ -1165,4 +1165,4 @@ void loop() mainDelay.delay(delayMsec); } } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index aaf14c5b8..d947f0cea 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -358,8 +358,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) // FIXME: Default to bluetooth capability of platform as default config.bluetooth.enabled = true; config.bluetooth.fixed_pin = defaultBLEPin; -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \ - defined(USE_ST7789) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ + defined(HX8357_CS) || defined(USE_ST7789) bool hasScreen = true; #elif ARCH_PORTDUINO bool hasScreen = false; @@ -1217,4 +1217,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co LOG_ERROR("A critical failure occurred, portduino is exiting..."); exit(2); #endif -} \ No newline at end of file +} diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 44f4d5227..e56016a5b 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -223,6 +223,8 @@ void portduinoSetup() settingsMap[displayPanel] = st7796; else if (yamlConfig["Display"]["Panel"].as("") == "ILI9341") settingsMap[displayPanel] = ili9341; + else if (yamlConfig["Display"]["Panel"].as("") == "ILI9342") + settingsMap[displayPanel] = ili9342; else if (yamlConfig["Display"]["Panel"].as("") == "ILI9488") settingsMap[displayPanel] = ili9488; else if (yamlConfig["Display"]["Panel"].as("") == "HX8357D") diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 59956cb59..8ee96717e 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -57,7 +57,7 @@ enum configNames { maxnodes, ascii_logs }; -enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; +enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9488, hx8357d }; enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; enum { level_error, level_warn, level_info, level_debug, level_trace }; From 4db0c75c8eda1309ac559dbf9576adfbd8b153b6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 4 Oct 2024 12:06:02 -0500 Subject: [PATCH 1342/1377] Don't use a static decleration in a header file (#4944) * Don't use a static decleration in a header file * Actually add the rest of the commit --- src/main.cpp | 8 +++++--- userPrefs.h | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 66b843ced..95ac7c1c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,6 +120,8 @@ float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this h using namespace concurrency; +volatile static const char slipstreamTZString[] = USERPREFS_TZ_STRING; + // We always create a screen object, but we only init it if we find the hardware graphics::Screen *screen = nullptr; @@ -707,15 +709,15 @@ void setup() // setup TZ prior to time actions. #if !MESHTASTIC_EXCLUDE_TZ - LOG_DEBUG("Using compiled/slipstreamed %s\n", USERPREFS_TZ_STRING); // important, removing this clobbers our magic string + LOG_DEBUG("Using compiled/slipstreamed %s\n", slipstreamTZString); // important, removing this clobbers our magic string if (*config.device.tzdef && config.device.tzdef[0] != 0) { LOG_DEBUG("Saved TZ: %s \n", config.device.tzdef); setenv("TZ", config.device.tzdef, 1); } else { - if (strncmp((const char *)USERPREFS_TZ_STRING, "tzpl", 4) == 0) { + if (strncmp((const char *)slipstreamTZString, "tzpl", 4) == 0) { setenv("TZ", "GMT0", 1); } else { - setenv("TZ", (const char *)USERPREFS_TZ_STRING, 1); + setenv("TZ", (const char *)slipstreamTZString, 1); } } tzset(); diff --git a/userPrefs.h b/userPrefs.h index 337bd7976..f544a6535 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -1,6 +1,10 @@ #ifndef _USERPREFS_ #define _USERPREFS_ -volatile static const char USERPREFS_TZ_STRING[] = "tzplaceholder "; + +// Slipstream values: + +#define USERPREFS_TZ_STRING "tzplaceholder " + // Uncomment and modify to set device defaults // #define USERPREFS_EVENT_MODE 1 From c3b9d493b6d4d131daa68cd7eb4641f9d7eca2cb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 4 Oct 2024 15:07:10 -0500 Subject: [PATCH 1343/1377] Leave the build epoch commented and uncomment when CI runs (#4943) --- .github/actions/setup-base/action.yml | 5 +++++ platformio.ini | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 929c1df38..c0f6c4e66 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -11,6 +11,11 @@ runs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Uncomment build epoch + shell: bash + run: | + sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini + - name: Install dependencies shell: bash run: | diff --git a/platformio.ini b/platformio.ini index 64d9e7754..22e2b6259 100644 --- a/platformio.ini +++ b/platformio.ini @@ -82,7 +82,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 - -DBUILD_EPOCH=$UNIX_TIME + #-DBUILD_EPOCH=$UNIX_TIME ;-D OLED_PL monitor_speed = 115200 From 7e946d15ca302aa412d0d637f0314702bf55da0f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 4 Oct 2024 22:59:00 -0500 Subject: [PATCH 1344/1377] Move ifndef to fix test (#4950) --- src/mesh/CryptoEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 5c8fdf6ae..a875eb8b2 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -102,9 +102,9 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size uint8_t *auth; // set to last 8 bytes of text? uint32_t extraNonce; // pointer was not really used auth = bytes + numBytes - 12; -#ifndef PIO_UNIT_TESTING memcpy(&extraNonce, auth + 8, sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); +#ifndef PIO_UNIT_TESTING LOG_INFO("Random nonce value: %d\n", extraNonce); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode); From e182ae75c261f0315ec4ac2ae7b20534034c7076 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:15:20 +0800 Subject: [PATCH 1345/1377] Remove ancient .gitignore lines (#4952) The files referenced here have not existed for some time. --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0f2202f8d..28f9a24cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ .pio -main/configuration.h -main/credentials.h # ignore vscode IDE settings files .vscode/* @@ -32,4 +30,4 @@ release/ .vscode/extensions.json /compile_commands.json src/mesh/raspihttp/certificate.pem -src/mesh/raspihttp/private_key.pem \ No newline at end of file +src/mesh/raspihttp/private_key.pem From 55049ed547103787d700e0df5331f88755b89d8d Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:24:12 +0800 Subject: [PATCH 1346/1377] Remove unused Jlink monitoring files (#4953) The NRF52 JLINK_MONITOR are unmodified copies of code from Nordic (https://github.com/NordicPlayground/j-link-monitoring-mode-debugging ), which are not used by our firmware and have not been touched in ~4 years. --- src/platform/nrf52/JLINK_MONITOR.c | 124 --- src/platform/nrf52/JLINK_MONITOR.h | 27 - src/platform/nrf52/JLINK_MONITOR_ISR_SES.S | 888 --------------------- 3 files changed, 1039 deletions(-) delete mode 100644 src/platform/nrf52/JLINK_MONITOR.c delete mode 100644 src/platform/nrf52/JLINK_MONITOR.h delete mode 100644 src/platform/nrf52/JLINK_MONITOR_ISR_SES.S diff --git a/src/platform/nrf52/JLINK_MONITOR.c b/src/platform/nrf52/JLINK_MONITOR.c deleted file mode 100644 index 160264905..000000000 --- a/src/platform/nrf52/JLINK_MONITOR.c +++ /dev/null @@ -1,124 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH & Co. KG * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ----------------------------------------------------------------------- -File : JLINK_MONITOR.c -Purpose : Implementation of debug monitor for J-Link monitor mode debug on Cortex-M devices. --------- END-OF-HEADER --------------------------------------------- -*/ - -#include "JLINK_MONITOR.h" - -/********************************************************************* - * - * Configuration - * - ********************************************************************** - */ - -/********************************************************************* - * - * Defines - * - ********************************************************************** - */ - -/********************************************************************* - * - * Types - * - ********************************************************************** - */ - -/********************************************************************* - * - * Static data - * - ********************************************************************** - */ - -volatile int MAIN_MonCnt; // Incremented in JLINK_MONITOR_OnPoll() while CPU is in debug mode - -/********************************************************************* - * - * Local functions - * - ********************************************************************** - */ - -/********************************************************************* - * - * Global functions - * - ********************************************************************** - */ - -/********************************************************************* - * - * JLINK_MONITOR_OnExit() - * - * Function description - * Called from DebugMon_Handler(), once per debug exit. - * May perform some target specific operations to be done on debug mode exit. - * - * Notes - * (1) Must not keep the CPU busy for more than 100 ms - */ -void JLINK_MONITOR_OnExit(void) -{ - // - // Add custom code here - // - // BSP_ClrLED(0); -} - -/********************************************************************* - * - * JLINK_MONITOR_OnEnter() - * - * Function description - * Called from DebugMon_Handler(), once per debug entry. - * May perform some target specific operations to be done on debug mode entry - * - * Notes - * (1) Must not keep the CPU busy for more than 100 ms - */ -void JLINK_MONITOR_OnEnter(void) -{ - // - // Add custom code here - // - // BSP_SetLED(0); - // BSP_ClrLED(1); -} - -/********************************************************************* - * - * JLINK_MONITOR_OnPoll() - * - * Function description - * Called periodically from DebugMon_Handler(), to perform some actions that need to be performed periodically during debug - * mode. - * - * Notes - * (1) Must not keep the CPU busy for more than 100 ms - */ -void JLINK_MONITOR_OnPoll(void) -{ - // - // Add custom code here - // - MAIN_MonCnt++; - // BSP_ToggleLED(0); - // _Delay(500000); -} - -/****** End Of File *************************************************/ diff --git a/src/platform/nrf52/JLINK_MONITOR.h b/src/platform/nrf52/JLINK_MONITOR.h deleted file mode 100644 index 87cf332fe..000000000 --- a/src/platform/nrf52/JLINK_MONITOR.h +++ /dev/null @@ -1,27 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH & Co. KG * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ----------------------------------------------------------------------- -File : JLINK_MONITOR.h -Purpose : Header file of debug monitor for J-Link monitor mode debug on Cortex-M devices. --------- END-OF-HEADER --------------------------------------------- -*/ - -#ifndef JLINK_MONITOR_H -#define JLINK_MONITOR_H - -void JLINK_MONITOR_OnExit(void); -void JLINK_MONITOR_OnEnter(void); -void JLINK_MONITOR_OnPoll(void); - -#endif - -/****** End Of File *************************************************/ diff --git a/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S b/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S deleted file mode 100644 index cda4b1a50..000000000 --- a/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S +++ /dev/null @@ -1,888 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH & Co. KG * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ----------------------------------------------------------------------- -File : JLINK_MONITOR_ISR_SES.s -Purpose : Implementation of debug monitor for J-Link monitor mode - debug on Cortex-M devices, supporting SES compiler. --------- END-OF-HEADER --------------------------------------------- -*/ - - .name JLINK_MONITOR_ISR - .syntax unified - - .extern JLINK_MONITOR_OnEnter - .extern JLINK_MONITOR_OnExit - .extern JLINK_MONITOR_OnPoll - - .global DebugMon_Handler - -/********************************************************************* -* -* Defines, configurable -* -********************************************************************** -*/ - -#define _MON_VERSION 100 // V x.yy - -/********************************************************************* -* -* Defines, fixed -* -********************************************************************** -*/ - -#define _APP_SP_OFF_R0 0x00 -#define _APP_SP_OFF_R1 0x04 -#define _APP_SP_OFF_R2 0x08 -#define _APP_SP_OFF_R3 0x0C -#define _APP_SP_OFF_R12 0x10 -#define _APP_SP_OFF_R14_LR 0x14 -#define _APP_SP_OFF_PC 0x18 -#define _APP_SP_OFF_XPSR 0x1C -#define _APP_SP_OFF_S0 0x20 -#define _APP_SP_OFF_S1 0x24 -#define _APP_SP_OFF_S2 0x28 -#define _APP_SP_OFF_S3 0x2C -#define _APP_SP_OFF_S4 0x30 -#define _APP_SP_OFF_S5 0x34 -#define _APP_SP_OFF_S6 0x38 -#define _APP_SP_OFF_S7 0x3C -#define _APP_SP_OFF_S8 0x40 -#define _APP_SP_OFF_S9 0x44 -#define _APP_SP_OFF_S10 0x48 -#define _APP_SP_OFF_S11 0x4C -#define _APP_SP_OFF_S12 0x50 -#define _APP_SP_OFF_S13 0x54 -#define _APP_SP_OFF_S14 0x58 -#define _APP_SP_OFF_S15 0x5C -#define _APP_SP_OFF_FPSCR 0x60 - -#define _NUM_BYTES_BASIC_STACKFRAME 32 -#define _NUM_BYTES_EXTENDED_STACKFRAME 72 - -#define _SYSTEM_DCRDR_OFF 0x00 -#define _SYSTEM_DEMCR_OFF 0x04 - -#define _SYSTEM_DHCSR 0xE000EDF0 // Debug Halting Control and Status Register (DHCSR) -#define _SYSTEM_DCRSR 0xE000EDF4 // Debug Core Register Selector Register (DCRSR) -#define _SYSTEM_DCRDR 0xE000EDF8 // Debug Core Register Data Register (DCRDR) -#define _SYSTEM_DEMCR 0xE000EDFC // Debug Exception and Monitor Control Register (DEMCR) - -#define _SYSTEM_FPCCR 0xE000EF34 // Floating-Point Context Control Register (FPCCR) -#define _SYSTEM_FPCAR 0xE000EF38 // Floating-Point Context Address Register (FPCAR) -#define _SYSTEM_FPDSCR 0xE000EF3C // Floating-Point Default Status Control Register (FPDSCR) -#define _SYSTEM_MVFR0 0xE000EF40 // Media and FP Feature Register 0 (MVFR0) -#define _SYSTEM_MVFR1 0xE000EF44 // Media and FP Feature Register 1 (MVFR1) - -/* -* Defines for determining if the current debug config supports FPU registers -* For some compilers like IAR EWARM when disabling the FPU in the compiler settings an error is thrown when -*/ -#ifdef __FPU_PRESENT - #if __FPU_PRESENT - #define _HAS_FPU_REGS 1 - #else - #define _HAS_FPU_REGS 0 - #endif -#else - #define _HAS_FPU_REGS 0 -#endif - -/********************************************************************* -* -* Signature of monitor -* -* Function description -* Needed for targets where also a boot ROM is present that possibly specifies a vector table with a valid debug monitor exception entry -*/ - .section .text, "ax" - - // - // JLINKMONHANDLER - // - .byte 0x4A - .byte 0x4C - .byte 0x49 - .byte 0x4E - .byte 0x4B - .byte 0x4D - .byte 0x4F - .byte 0x4E - .byte 0x48 - .byte 0x41 - .byte 0x4E - .byte 0x44 - .byte 0x4C - .byte 0x45 - .byte 0x52 - .byte 0x00 // Align to 8-bytes - -/********************************************************************* -* -* DebugMon_Handler() -* -* Function description -* Debug monitor handler. CPU enters this handler in case a "halt" request is made from the debugger. -* This handler is also responsible for handling commands that are sent by the debugger. -* -* Notes -* This is actually the ISR for the debug interrupt (exception no. 12) -*/ - .thumb_func - -DebugMon_Handler: - /* - General procedure: - DCRDR is used as communication register - DEMCR[19] is used as ready flag - For the command J-Link sends to the monitor: DCRDR[7:0] == Cmd, DCRDR[31:8] == ParamData - - 1) Monitor sets DEMCR[19] whenever it is ready to receive new commands/data - DEMCR[19] is initially set on debug monitor entry - 2) J-Link will clear once it has placed conmmand/data in DCRDR for J-Link - 3) Monitor will wait for DEMCR[19] to be cleared - 4) Monitor will process command (May cause additional data transfers etc., depends on command - 5) No restart-CPU command? => Back to 2), Otherwise => 6) - 6) Monitor will clear DEMCR[19] 19 to indicate that it is no longer ready - */ - PUSH {LR} - BL JLINK_MONITOR_OnEnter - POP {LR} - LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR - B.N _IndicateMonReady -_WaitProbeReadIndicateMonRdy: // while(_SYSTEM_DEMCR & (1uL << 19)); => Wait until J-Link has read item - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR - LSLS R0,R0,#+12 - BMI.N _WaitProbeReadIndicateMonRdy -_IndicateMonReady: - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] - /* - During command loop: - R0 = Tmp - R1 = Tmp - R2 = Tmp - R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) - R12 = Tmp - - Outside command loop R0-R3 and R12 may be overwritten by MONITOR_OnPoll() - */ -_WaitForJLinkCmd: // do { - PUSH {LR} - BL JLINK_MONITOR_OnPoll - POP {LR} - LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] - LSRS R0,R0,#+20 // DEMCR[19] -> Carry Clear? => J-Link has placed command for us - BCS _WaitForJLinkCmd - /* - Perform command - Command is placed by J-Link in DCRDR[7:0] and additional parameter data is stored in DCRDR[31:8] - J-Link clears DEMCR[19] to indicate that it placed a command/data or read data - Monitor sets DEMCR[19] to indicate that it placed data or read data / is ready for a new command - Setting DEMCR[19] indicates "monitor ready for new command / data" and also indicates: "data has been placed in DCRDR by monitor, for J-Link" - Therefore it is responsibility of the commands to respond to the commands accordingly - - Commands for debug monitor - Commands must not exceed 0xFF (255) as we only defined 8-bits for command-part. Higher 24-bits are parameter info for current command - - Protocol for different commands: - J-Link: Cmd -> DCRDR, DEMCR[19] -> 0 => Cmd placed by probe - */ - LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // ParamInfo = _SYSTEM_DCRDR - LSRS R1,R0,#+8 // ParamInfo >>= 8 - LSLS R0,R0,#+24 - LSRS R0,R0,#+24 // Cmd = ParamInfo & 0xFF - // - // switch (Cmd) - // - CMP R0,#+0 - BEQ.N _HandleGetMonVersion // case _MON_CMD_GET_MONITOR_VERSION - CMP R0,#+2 - BEQ.N _HandleReadReg // case _MON_CMD_READ_REG - BCC.N _HandleRestartCPU // case _MON_CMD_RESTART_CPU - CMP R0,#+3 - BEQ.N _HandleWriteReg_Veneer // case _MON_CMD_WRITE_REG - B.N _IndicateMonReady // default : while (1); - /* - Return - _MON_CMD_RESTART_CPU - CPU: DEMCR[19] -> 0 => Monitor no longer ready - */ -_HandleRestartCPU: - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR &= ~(1uL << 19); => Clear MON_REQ to indicate that monitor is no longer active - BIC R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] - PUSH {LR} - BL JLINK_MONITOR_OnExit - POP {PC} - // - // Place data section here to not get in trouble with load-offsets - // - .section .text, "ax", %progbits - .align 2 -_AddrDCRDR: - .long 0xE000EDF8 -_AddrCPACR: - .long 0xE000ED88 - - .section .text, "ax" - .thumb_func - -;/********************************************************************* -;* -;* _HandleGetMonVersion -;* -;*/ -_HandleGetMonVersion: - /* - _MON_CMD_GET_MONITOR_VERSION - CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready - J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read - CPU: DEMCR[19] -> 1 => Mon ready - */ - MOVS R0,#+_MON_VERSION - STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // _SYSTEM_DCRDR = x - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready - B _WaitProbeReadIndicateMonRdy - -/********************************************************************* -* -* _HandleReadReg -* -*/ -_HandleWriteReg_Veneer: - B.N _HandleWriteReg -_HandleReadReg: - /* - _MON_CMD_READ_REG - CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready - J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read - CPU: DEMCR[19] -> 1 => Mon ready - - - Register indexes - 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) - 16: XPSR - 17: MSP - 18: PSP - 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - 20: FPSCR - 21-52: FPS0-FPS31 - - - Register usage when entering this "subroutine": - R0 Cmd - R1 ParamInfo - R2 --- - R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) - R12 --- - - Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension - LR Return to Return SP Frame type - --------------------------------------------------------- - 0xFFFFFFE1 Handler mode. MSP Extended - 0xFFFFFFE9 Thread mode MSP Extended - 0xFFFFFFED Thread mode PSP Extended - 0xFFFFFFF1 Handler mode. MSP Basic - 0xFFFFFFF9 Thread mode MSP Basic - 0xFFFFFFFD Thread mode PSP Basic - - So LR[2] == 1 => Return stack == PSP else MSP - - R0-R3, R12, PC, xPSR can be read from application stackpointer - Other regs can be read directly - */ - LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP - ITE CS - MRSCS R2,PSP - MRSCC R2,MSP - CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) - BCS _HandleReadRegR4 - LDR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) - B.N _HandleReadRegDone -_HandleReadRegR4: - CMP R1,#+5 // if (RegIndex < 5) { (R4) - BCS _HandleReadRegR5 - MOV R0,R4 - B.N _HandleReadRegDone -_HandleReadRegR5: - CMP R1,#+6 // if (RegIndex < 6) { (R5) - BCS _HandleReadRegR6 - MOV R0,R5 - B.N _HandleReadRegDone -_HandleReadRegR6: - CMP R1,#+7 // if (RegIndex < 7) { (R6) - BCS _HandleReadRegR7 - MOV R0,R6 - B.N _HandleReadRegDone -_HandleReadRegR7: - CMP R1,#+8 // if (RegIndex < 8) { (R7) - BCS _HandleReadRegR8 - MOV R0,R7 - B.N _HandleReadRegDone -_HandleReadRegR8: - CMP R1,#+9 // if (RegIndex < 9) { (R8) - BCS _HandleReadRegR9 - MOV R0,R8 - B.N _HandleReadRegDone -_HandleReadRegR9: - CMP R1,#+10 // if (RegIndex < 10) { (R9) - BCS _HandleReadRegR10 - MOV R0,R9 - B.N _HandleReadRegDone -_HandleReadRegR10: - CMP R1,#+11 // if (RegIndex < 11) { (R10) - BCS _HandleReadRegR11 - MOV R0,R10 - B.N _HandleReadRegDone -_HandleReadRegR11: - CMP R1,#+12 // if (RegIndex < 12) { (R11) - BCS _HandleReadRegR12 - MOV R0,R11 - B.N _HandleReadRegDone -_HandleReadRegR12: - CMP R1,#+14 // if (RegIndex < 14) { (R12) - BCS _HandleReadRegR14 - LDR R0,[R2, #+_APP_SP_OFF_R12] - B.N _HandleReadRegDone -_HandleReadRegR14: - CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) - BCS _HandleReadRegR15 - LDR R0,[R2, #+_APP_SP_OFF_R14_LR] - B.N _HandleReadRegDone -_HandleReadRegR15: - CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) - BCS _HandleReadRegXPSR - LDR R0,[R2, #+_APP_SP_OFF_PC] - B.N _HandleReadRegDone -_HandleReadRegXPSR: - CMP R1,#+17 // if (RegIndex < 17) { (xPSR) - BCS _HandleReadRegMSP - LDR R0,[R2, #+_APP_SP_OFF_XPSR] - B.N _HandleReadRegDone -_HandleReadRegMSP: - /* - Stackpointer is tricky because we need to get some info about the SP used in the user app, first - - Handle reading R0-R3 which can be read right from application stackpointer - - Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension - LR Return to Return SP Frame type - --------------------------------------------------------- - 0xFFFFFFE1 Handler mode. MSP Extended - 0xFFFFFFE9 Thread mode MSP Extended - 0xFFFFFFED Thread mode PSP Extended - 0xFFFFFFF1 Handler mode. MSP Basic - 0xFFFFFFF9 Thread mode MSP Basic - 0xFFFFFFFD Thread mode PSP Basic - - So LR[2] == 1 => Return stack == PSP else MSP - Per architecture definition: Inside monitor (exception) SP = MSP - - Stack pointer handling is complicated because it is different what is pushed on the stack before entering the monitor ISR... - Cortex-M: 8 regs - Cortex-M + forced-stack-alignment: 8 regs + 1 dummy-word if stack was not 8-byte aligned - Cortex-M + FPU: 8 regs + 17 FPU regs + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned - Cortex-M + FPU + lazy mode: 8 regs + 17 dummy-words + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned - */ - CMP R1,#+18 // if (RegIndex < 18) { (MSP) - BCS _HandleReadRegPSP - MRS R0,MSP - LSRS R1,LR,#+3 // LR[2] -> Carry == 0 => CPU was running on MSP => Needs correction - BCS _HandleReadRegDone_Veneer // CPU was running on PSP? => No correction necessary -_HandleSPCorrection: - LSRS R1,LR,#+5 // LR[4] -> Carry == 0 => extended stack frame has been allocated. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry - ITE CS - ADDCS R0,R0,#+_NUM_BYTES_BASIC_STACKFRAME - ADDCC R0,R0,#+_NUM_BYTES_EXTENDED_STACKFRAME - LDR R1,[R2, #+_APP_SP_OFF_XPSR] // Get xPSR from application stack (R2 has been set to app stack on beginning of _HandleReadReg) - LSRS R1,R1,#+5 // xPSR[9] -> Carry == 1 => Stack has been force-aligned before pushing regs. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry - IT CS - ADDCS R0,R0,#+4 - B _HandleReadRegDone -_HandleReadRegPSP: // RegIndex == 18 - CMP R1,#+19 // if (RegIndex < 19) { - BCS _HandleReadRegCFBP - MRS R0,PSP // PSP is not touched by monitor - LSRS R1,LR,#+3 // LR[2] -> Carry == 1 => CPU was running on PSP => Needs correction - BCC _HandleReadRegDone_Veneer // CPU was running on MSP? => No correction of PSP necessary - B _HandleSPCorrection -_HandleReadRegCFBP: - /* - CFBP is a register that can only be read via debug probe and is a merger of the following regs: - CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode - */ - CMP R1,#+20 // if (RegIndex < 20) { (CFBP) - BCS _HandleReadRegFPU - MOVS R0,#+0 - MRS R2,PRIMASK - ORRS R0,R2 // Merge PRIMASK into CFBP[7:0] - MRS R2,BASEPRI - LSLS R2,R2,#+8 // Merge BASEPRI into CFBP[15:8] - ORRS R0,R2 - MRS R2,FAULTMASK - LSLS R2,R2,#+16 // Merge FAULTMASK into CFBP[23:16] - ORRS R0,R2 - MRS R2,CONTROL - LSRS R1,LR,#3 // LR[2] -> Carry. CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior - IT CS // As J-Link sees value of CONTROL at application time, we need reconstruct original value of CONTROL - ORRCS R2,R2,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor - LSRS R1,LR,#+5 // LR[4] == NOT(CONTROL.FPCA) -> Carry - ITE CS // Merge original value of FPCA (CONTROL[2]) into read data - BICCS R2,R2,#+0x04 // Remember LR contains NOT(CONTROL) - ORRCC R2,R2,#+0x04 - LSLS R2,R2,#+24 - ORRS R0,R2 - B.N _HandleReadRegDone -_HandleReadRegFPU: -#if _HAS_FPU_REGS - CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) - BCS _HandleReadRegDone_Veneer - /* - Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled - If not, access to floating point is not possible - CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - */ - LDR R0,_AddrCPACR - LDR R0,[R0] - LSLS R0,R0,#+8 - LSRS R0,R0,#+28 - CMP R0,#+0xF - BEQ _HandleReadRegFPU_Allowed - CMP R0,#+0x5 - BNE _HandleReadRegDone_Veneer -_HandleReadRegFPU_Allowed: - CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) - BCS _HandleReadRegFPS0_FPS31 - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleReadFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleReadFPSCRLazyMode - LDR R0,[R2, #+_APP_SP_OFF_FPSCR] - B _HandleReadRegDone -_HandleReadFPSCRLazyMode: - VMRS R0,FPSCR - B _HandleReadRegDone -_HandleReadRegFPS0_FPS31: // RegIndex == 21-52 - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleReadFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleReadFPS0_FPS31LazyMode - SUBS R1,#+21 // Convert absolute reg index into rel. one - LSLS R1,R1,#+2 // RegIndex to position on stack - ADDS R1,#+_APP_SP_OFF_S0 - LDR R0,[R2, R1] -_HandleReadRegDone_Veneer: - B _HandleReadRegDone -_HandleReadFPS0_FPS31LazyMode: - SUBS R1,#+20 // convert abs. RegIndex into rel. one - MOVS R0,#+6 - MULS R1,R0,R1 - LDR R0,=_HandleReadRegUnknown - SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) - ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr - BX R0 - // - // Table for reading FPS0-FPS31 - // - VMOV R0,S31 // v = FPSx - B _HandleReadRegDone - VMOV R0,S30 - B _HandleReadRegDone - VMOV R0,S29 - B _HandleReadRegDone - VMOV R0,S28 - B _HandleReadRegDone - VMOV R0,S27 - B _HandleReadRegDone - VMOV R0,S26 - B _HandleReadRegDone - VMOV R0,S25 - B _HandleReadRegDone - VMOV R0,S24 - B _HandleReadRegDone - VMOV R0,S23 - B _HandleReadRegDone - VMOV R0,S22 - B _HandleReadRegDone - VMOV R0,S21 - B _HandleReadRegDone - VMOV R0,S20 - B _HandleReadRegDone - VMOV R0,S19 - B _HandleReadRegDone - VMOV R0,S18 - B _HandleReadRegDone - VMOV R0,S17 - B _HandleReadRegDone - VMOV R0,S16 - B _HandleReadRegDone - VMOV R0,S15 - B _HandleReadRegDone - VMOV R0,S14 - B _HandleReadRegDone - VMOV R0,S13 - B _HandleReadRegDone - VMOV R0,S12 - B _HandleReadRegDone - VMOV R0,S11 - B _HandleReadRegDone - VMOV R0,S10 - B _HandleReadRegDone - VMOV R0,S9 - B _HandleReadRegDone - VMOV R0,S8 - B _HandleReadRegDone - VMOV R0,S7 - B _HandleReadRegDone - VMOV R0,S6 - B _HandleReadRegDone - VMOV R0,S5 - B _HandleReadRegDone - VMOV R0,S4 - B _HandleReadRegDone - VMOV R0,S3 - B _HandleReadRegDone - VMOV R0,S2 - B _HandleReadRegDone - VMOV R0,S1 - B _HandleReadRegDone - VMOV R0,S0 - B _HandleReadRegDone -#else - B _HandleReadRegUnknown -_HandleReadRegDone_Veneer: - B _HandleReadRegDone -#endif -_HandleReadRegUnknown: - MOVS R0,#+0 // v = 0 - B.N _HandleReadRegDone -_HandleReadRegDone: - - // Send register content to J-Link and wait until J-Link has read the data - - STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // DCRDR = v; - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready - B _WaitProbeReadIndicateMonRdy - - // Data section for register addresses - -_HandleWriteReg: - /* - _MON_CMD_WRITE_REG - CPU: DEMCR[19] -> 1 => Mon ready - J-Link: Data -> DCRDR, DEMCR[19] -> 0 => Data placed by probe - CPU: DCRDR -> Read, Process command, DEMCR[19] -> 1 => Data read & mon ready - - Register indexes - 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) - 16: XPSR - 17: MSP - 18: PSP - 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - 20: FPSCR - 21-52: FPS0-FPS31 - - - Register usage when entering this "subroutine": - R0 Cmd - R1 ParamInfo - R2 --- - R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) - R12 --- - - Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension - LR Return to Return SP Frame type - --------------------------------------------------------- - 0xFFFFFFE1 Handler mode. MSP Extended - 0xFFFFFFE9 Thread mode MSP Extended - 0xFFFFFFED Thread mode PSP Extended - 0xFFFFFFF1 Handler mode. MSP Basic - 0xFFFFFFF9 Thread mode MSP Basic - 0xFFFFFFFD Thread mode PSP Basic - - So LR[2] == 1 => Return stack == PSP else MSP - - R0-R3, R12, PC, xPSR can be written via application stackpointer - Other regs can be written directly - - - Read register data from J-Link into R0 - */ - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Monitor is ready to receive register data - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] -_HandleWRegWaitUntilDataRecv: - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] - LSLS R0,R0,#+12 - BMI.N _HandleWRegWaitUntilDataRecv // DEMCR[19] == 0 => J-Link has placed new data for us - LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // Get register data - // - // Determine application SP - // - LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP - ITE CS - MRSCS R2,PSP - MRSCC R2,MSP - CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) - BCS _HandleWriteRegR4 - STR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) - B.N _HandleWriteRegDone -_HandleWriteRegR4: - CMP R1,#+5 // if (RegIndex < 5) { (R4) - BCS _HandleWriteRegR5 - MOV R4,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR5: - CMP R1,#+6 // if (RegIndex < 6) { (R5) - BCS _HandleWriteRegR6 - MOV R5,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR6: - CMP R1,#+7 // if (RegIndex < 7) { (R6) - BCS _HandleWriteRegR7 - MOV R6,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR7: - CMP R1,#+8 // if (RegIndex < 8) { (R7) - BCS _HandleWriteRegR8 - MOV R7,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR8: - CMP R1,#+9 // if (RegIndex < 9) { (R8) - BCS _HandleWriteRegR9 - MOV R8,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR9: - CMP R1,#+10 // if (RegIndex < 10) { (R9) - BCS _HandleWriteRegR10 - MOV R9,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR10: - CMP R1,#+11 // if (RegIndex < 11) { (R10) - BCS _HandleWriteRegR11 - MOV R10,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR11: - CMP R1,#+12 // if (RegIndex < 12) { (R11) - BCS _HandleWriteRegR12 - MOV R11,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR12: - CMP R1,#+14 // if (RegIndex < 14) { (R12) - BCS _HandleWriteRegR14 - STR R0,[R2, #+_APP_SP_OFF_R12] - B.N _HandleWriteRegDone -_HandleWriteRegR14: - CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) - BCS _HandleWriteRegR15 - STR R0,[R2, #+_APP_SP_OFF_R14_LR] - B.N _HandleWriteRegDone -_HandleWriteRegR15: - CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) - BCS _HandleWriteRegXPSR - STR R0,[R2, #+_APP_SP_OFF_PC] - B.N _HandleWriteRegDone -_HandleWriteRegXPSR: - CMP R1,#+17 // if (RegIndex < 17) { (xPSR) - BCS _HandleWriteRegMSP - STR R0,[R2, #+_APP_SP_OFF_XPSR] - B.N _HandleWriteRegDone -_HandleWriteRegMSP: - // - // For now, SP cannot be modified because it is needed to jump back from monitor mode - // - CMP R1,#+18 // if (RegIndex < 18) { (MSP) - BCS _HandleWriteRegPSP - B.N _HandleWriteRegDone -_HandleWriteRegPSP: // RegIndex == 18 - CMP R1,#+19 // if (RegIndex < 19) { - BCS _HandleWriteRegCFBP - B.N _HandleWriteRegDone -_HandleWriteRegCFBP: - /* - CFBP is a register that can only be read via debug probe and is a merger of the following regs: - CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode - */ - CMP R1,#+20 // if (RegIndex < 20) { (CFBP) - BCS _HandleWriteRegFPU - LSLS R1,R0,#+24 - LSRS R1,R1,#+24 // Extract CFBP[7:0] => PRIMASK - MSR PRIMASK,R1 - LSLS R1,R0,#+16 - LSRS R1,R1,#+24 // Extract CFBP[15:8] => BASEPRI - MSR BASEPRI,R1 - LSLS R1,R0,#+8 // Extract CFBP[23:16] => FAULTMASK - LSRS R1,R1,#+24 - MSR FAULTMASK,R1 - LSRS R1,R0,#+24 // Extract CFBP[31:24] => CONTROL - LSRS R0,R1,#2 // Current CONTROL[1] -> Carry - ITE CS // Update saved CONTROL.SPSEL (CONTROL[1]). CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior - ORRCS LR,LR,#+4 - BICCC LR,LR,#+4 - BIC R1,R1,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor. Otherwise behavior is UNPREDICTABLE - LSRS R0,R1,#+3 // New CONTROL.FPCA (CONTROL[2]) -> Carry - ITE CS // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BICCS LR,LR,#+0x10 // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - ORRCC LR,LR,#+0x10 - MRS R0,CONTROL - LSRS R0,R0,#+3 // CONTROL[2] -> Carry - ITE CS // Preserve original value of current CONTROL[2] - ORRCS R1,R1,#+0x04 - BICCC R1,R1,#+0x04 - MSR CONTROL,R1 - ISB // Necessary after writing to CONTROL, see ARM DDI0403D, B1.4.4 The special-purpose CONTROL register - B.N _HandleWriteRegDone -_HandleWriteRegFPU: -#if _HAS_FPU_REGS - CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) - BCS _HandleWriteRegDone_Veneer - /* - Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled - If not, access to floating point is not possible - CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - */ - MOV R12,R0 // Save register data - LDR R0,_AddrCPACR - LDR R0,[R0] - LSLS R0,R0,#+8 - LSRS R0,R0,#+28 - CMP R0,#+0xF - BEQ _HandleWriteRegFPU_Allowed - CMP R0,#+0x5 - BNE _HandleWriteRegDone_Veneer -_HandleWriteRegFPU_Allowed: - CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) - BCS _HandleWriteRegFPS0_FPS31 - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleWriteFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleWriteFPSCRLazyMode - STR R12,[R2, #+_APP_SP_OFF_FPSCR] - B _HandleWriteRegDone -_HandleWriteFPSCRLazyMode: - VMSR FPSCR,R12 - B _HandleWriteRegDone -_HandleWriteRegFPS0_FPS31: // RegIndex == 21-52 - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleWriteFPS0_FPS31LazyMode - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleWriteFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - SUBS R1,#+21 // Convert absolute reg index into rel. one - LSLS R1,R1,#+2 // RegIndex to position on stack - ADDS R1,#+_APP_SP_OFF_S0 - STR R12,[R2, R1] -_HandleWriteRegDone_Veneer: - B _HandleWriteRegDone -_HandleWriteFPS0_FPS31LazyMode: - SUBS R1,#+20 // Convert abs. RegIndex into rel. one - MOVS R0,#+6 - MULS R1,R0,R1 - LDR R0,=_HandleReadRegUnknown - SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) - ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr - BX R0 - // - // Table for reading FPS0-FPS31 - // - VMOV S31,R12 // v = FPSx - B _HandleWriteRegDone - VMOV S30,R12 - B _HandleWriteRegDone - VMOV S29,R12 - B _HandleWriteRegDone - VMOV S28,R12 - B _HandleWriteRegDone - VMOV S27,R12 - B _HandleWriteRegDone - VMOV S26,R12 - B _HandleWriteRegDone - VMOV S25,R12 - B _HandleWriteRegDone - VMOV S24,R12 - B _HandleWriteRegDone - VMOV S23,R12 - B _HandleWriteRegDone - VMOV S22,R12 - B _HandleWriteRegDone - VMOV S21,R12 - B _HandleWriteRegDone - VMOV S20,R12 - B _HandleWriteRegDone - VMOV S19,R12 - B _HandleWriteRegDone - VMOV S18,R12 - B _HandleWriteRegDone - VMOV S17,R12 - B _HandleWriteRegDone - VMOV S16,R12 - B _HandleWriteRegDone - VMOV S15,R12 - B _HandleWriteRegDone - VMOV S14,R12 - B _HandleWriteRegDone - VMOV S13,R12 - B _HandleWriteRegDone - VMOV S12,R12 - B _HandleWriteRegDone - VMOV S11,R12 - B _HandleWriteRegDone - VMOV S10,R12 - B _HandleWriteRegDone - VMOV S9,R12 - B _HandleWriteRegDone - VMOV S8,R12 - B _HandleWriteRegDone - VMOV S7,R12 - B _HandleWriteRegDone - VMOV S6,R12 - B _HandleWriteRegDone - VMOV S5,R12 - B _HandleWriteRegDone - VMOV S4,R12 - B _HandleWriteRegDone - VMOV S3,R12 - B _HandleWriteRegDone - VMOV S2,R12 - B _HandleWriteRegDone - VMOV S1,R12 - B _HandleWriteRegDone - VMOV S0,R12 - B _HandleWriteRegDone -#else - B _HandleWriteRegUnknown -#endif -_HandleWriteRegUnknown: - B.N _HandleWriteRegDone -_HandleWriteRegDone: - B _IndicateMonReady // Indicate that monitor has read data, processed command and is ready for a new one - .end -/****** End Of File *************************************************/ From 783466f1165aeddd41ab1b1b76ef70aa2908c3b1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 5 Oct 2024 05:24:59 -0500 Subject: [PATCH 1347/1377] Revert "Remove unused Jlink monitoring files (#4953)" (#4959) This reverts commit 55049ed547103787d700e0df5331f88755b89d8d. --- src/platform/nrf52/JLINK_MONITOR.c | 124 +++ src/platform/nrf52/JLINK_MONITOR.h | 27 + src/platform/nrf52/JLINK_MONITOR_ISR_SES.S | 888 +++++++++++++++++++++ 3 files changed, 1039 insertions(+) create mode 100644 src/platform/nrf52/JLINK_MONITOR.c create mode 100644 src/platform/nrf52/JLINK_MONITOR.h create mode 100644 src/platform/nrf52/JLINK_MONITOR_ISR_SES.S diff --git a/src/platform/nrf52/JLINK_MONITOR.c b/src/platform/nrf52/JLINK_MONITOR.c new file mode 100644 index 000000000..160264905 --- /dev/null +++ b/src/platform/nrf52/JLINK_MONITOR.c @@ -0,0 +1,124 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH & Co. KG * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +---------------------------------------------------------------------- +File : JLINK_MONITOR.c +Purpose : Implementation of debug monitor for J-Link monitor mode debug on Cortex-M devices. +-------- END-OF-HEADER --------------------------------------------- +*/ + +#include "JLINK_MONITOR.h" + +/********************************************************************* + * + * Configuration + * + ********************************************************************** + */ + +/********************************************************************* + * + * Defines + * + ********************************************************************** + */ + +/********************************************************************* + * + * Types + * + ********************************************************************** + */ + +/********************************************************************* + * + * Static data + * + ********************************************************************** + */ + +volatile int MAIN_MonCnt; // Incremented in JLINK_MONITOR_OnPoll() while CPU is in debug mode + +/********************************************************************* + * + * Local functions + * + ********************************************************************** + */ + +/********************************************************************* + * + * Global functions + * + ********************************************************************** + */ + +/********************************************************************* + * + * JLINK_MONITOR_OnExit() + * + * Function description + * Called from DebugMon_Handler(), once per debug exit. + * May perform some target specific operations to be done on debug mode exit. + * + * Notes + * (1) Must not keep the CPU busy for more than 100 ms + */ +void JLINK_MONITOR_OnExit(void) +{ + // + // Add custom code here + // + // BSP_ClrLED(0); +} + +/********************************************************************* + * + * JLINK_MONITOR_OnEnter() + * + * Function description + * Called from DebugMon_Handler(), once per debug entry. + * May perform some target specific operations to be done on debug mode entry + * + * Notes + * (1) Must not keep the CPU busy for more than 100 ms + */ +void JLINK_MONITOR_OnEnter(void) +{ + // + // Add custom code here + // + // BSP_SetLED(0); + // BSP_ClrLED(1); +} + +/********************************************************************* + * + * JLINK_MONITOR_OnPoll() + * + * Function description + * Called periodically from DebugMon_Handler(), to perform some actions that need to be performed periodically during debug + * mode. + * + * Notes + * (1) Must not keep the CPU busy for more than 100 ms + */ +void JLINK_MONITOR_OnPoll(void) +{ + // + // Add custom code here + // + MAIN_MonCnt++; + // BSP_ToggleLED(0); + // _Delay(500000); +} + +/****** End Of File *************************************************/ diff --git a/src/platform/nrf52/JLINK_MONITOR.h b/src/platform/nrf52/JLINK_MONITOR.h new file mode 100644 index 000000000..87cf332fe --- /dev/null +++ b/src/platform/nrf52/JLINK_MONITOR.h @@ -0,0 +1,27 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH & Co. KG * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +---------------------------------------------------------------------- +File : JLINK_MONITOR.h +Purpose : Header file of debug monitor for J-Link monitor mode debug on Cortex-M devices. +-------- END-OF-HEADER --------------------------------------------- +*/ + +#ifndef JLINK_MONITOR_H +#define JLINK_MONITOR_H + +void JLINK_MONITOR_OnExit(void); +void JLINK_MONITOR_OnEnter(void); +void JLINK_MONITOR_OnPoll(void); + +#endif + +/****** End Of File *************************************************/ diff --git a/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S b/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S new file mode 100644 index 000000000..cda4b1a50 --- /dev/null +++ b/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S @@ -0,0 +1,888 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH & Co. KG * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +---------------------------------------------------------------------- +File : JLINK_MONITOR_ISR_SES.s +Purpose : Implementation of debug monitor for J-Link monitor mode + debug on Cortex-M devices, supporting SES compiler. +-------- END-OF-HEADER --------------------------------------------- +*/ + + .name JLINK_MONITOR_ISR + .syntax unified + + .extern JLINK_MONITOR_OnEnter + .extern JLINK_MONITOR_OnExit + .extern JLINK_MONITOR_OnPoll + + .global DebugMon_Handler + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ + +#define _MON_VERSION 100 // V x.yy + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ + +#define _APP_SP_OFF_R0 0x00 +#define _APP_SP_OFF_R1 0x04 +#define _APP_SP_OFF_R2 0x08 +#define _APP_SP_OFF_R3 0x0C +#define _APP_SP_OFF_R12 0x10 +#define _APP_SP_OFF_R14_LR 0x14 +#define _APP_SP_OFF_PC 0x18 +#define _APP_SP_OFF_XPSR 0x1C +#define _APP_SP_OFF_S0 0x20 +#define _APP_SP_OFF_S1 0x24 +#define _APP_SP_OFF_S2 0x28 +#define _APP_SP_OFF_S3 0x2C +#define _APP_SP_OFF_S4 0x30 +#define _APP_SP_OFF_S5 0x34 +#define _APP_SP_OFF_S6 0x38 +#define _APP_SP_OFF_S7 0x3C +#define _APP_SP_OFF_S8 0x40 +#define _APP_SP_OFF_S9 0x44 +#define _APP_SP_OFF_S10 0x48 +#define _APP_SP_OFF_S11 0x4C +#define _APP_SP_OFF_S12 0x50 +#define _APP_SP_OFF_S13 0x54 +#define _APP_SP_OFF_S14 0x58 +#define _APP_SP_OFF_S15 0x5C +#define _APP_SP_OFF_FPSCR 0x60 + +#define _NUM_BYTES_BASIC_STACKFRAME 32 +#define _NUM_BYTES_EXTENDED_STACKFRAME 72 + +#define _SYSTEM_DCRDR_OFF 0x00 +#define _SYSTEM_DEMCR_OFF 0x04 + +#define _SYSTEM_DHCSR 0xE000EDF0 // Debug Halting Control and Status Register (DHCSR) +#define _SYSTEM_DCRSR 0xE000EDF4 // Debug Core Register Selector Register (DCRSR) +#define _SYSTEM_DCRDR 0xE000EDF8 // Debug Core Register Data Register (DCRDR) +#define _SYSTEM_DEMCR 0xE000EDFC // Debug Exception and Monitor Control Register (DEMCR) + +#define _SYSTEM_FPCCR 0xE000EF34 // Floating-Point Context Control Register (FPCCR) +#define _SYSTEM_FPCAR 0xE000EF38 // Floating-Point Context Address Register (FPCAR) +#define _SYSTEM_FPDSCR 0xE000EF3C // Floating-Point Default Status Control Register (FPDSCR) +#define _SYSTEM_MVFR0 0xE000EF40 // Media and FP Feature Register 0 (MVFR0) +#define _SYSTEM_MVFR1 0xE000EF44 // Media and FP Feature Register 1 (MVFR1) + +/* +* Defines for determining if the current debug config supports FPU registers +* For some compilers like IAR EWARM when disabling the FPU in the compiler settings an error is thrown when +*/ +#ifdef __FPU_PRESENT + #if __FPU_PRESENT + #define _HAS_FPU_REGS 1 + #else + #define _HAS_FPU_REGS 0 + #endif +#else + #define _HAS_FPU_REGS 0 +#endif + +/********************************************************************* +* +* Signature of monitor +* +* Function description +* Needed for targets where also a boot ROM is present that possibly specifies a vector table with a valid debug monitor exception entry +*/ + .section .text, "ax" + + // + // JLINKMONHANDLER + // + .byte 0x4A + .byte 0x4C + .byte 0x49 + .byte 0x4E + .byte 0x4B + .byte 0x4D + .byte 0x4F + .byte 0x4E + .byte 0x48 + .byte 0x41 + .byte 0x4E + .byte 0x44 + .byte 0x4C + .byte 0x45 + .byte 0x52 + .byte 0x00 // Align to 8-bytes + +/********************************************************************* +* +* DebugMon_Handler() +* +* Function description +* Debug monitor handler. CPU enters this handler in case a "halt" request is made from the debugger. +* This handler is also responsible for handling commands that are sent by the debugger. +* +* Notes +* This is actually the ISR for the debug interrupt (exception no. 12) +*/ + .thumb_func + +DebugMon_Handler: + /* + General procedure: + DCRDR is used as communication register + DEMCR[19] is used as ready flag + For the command J-Link sends to the monitor: DCRDR[7:0] == Cmd, DCRDR[31:8] == ParamData + + 1) Monitor sets DEMCR[19] whenever it is ready to receive new commands/data + DEMCR[19] is initially set on debug monitor entry + 2) J-Link will clear once it has placed conmmand/data in DCRDR for J-Link + 3) Monitor will wait for DEMCR[19] to be cleared + 4) Monitor will process command (May cause additional data transfers etc., depends on command + 5) No restart-CPU command? => Back to 2), Otherwise => 6) + 6) Monitor will clear DEMCR[19] 19 to indicate that it is no longer ready + */ + PUSH {LR} + BL JLINK_MONITOR_OnEnter + POP {LR} + LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR + B.N _IndicateMonReady +_WaitProbeReadIndicateMonRdy: // while(_SYSTEM_DEMCR & (1uL << 19)); => Wait until J-Link has read item + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR + LSLS R0,R0,#+12 + BMI.N _WaitProbeReadIndicateMonRdy +_IndicateMonReady: + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] + /* + During command loop: + R0 = Tmp + R1 = Tmp + R2 = Tmp + R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) + R12 = Tmp + + Outside command loop R0-R3 and R12 may be overwritten by MONITOR_OnPoll() + */ +_WaitForJLinkCmd: // do { + PUSH {LR} + BL JLINK_MONITOR_OnPoll + POP {LR} + LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] + LSRS R0,R0,#+20 // DEMCR[19] -> Carry Clear? => J-Link has placed command for us + BCS _WaitForJLinkCmd + /* + Perform command + Command is placed by J-Link in DCRDR[7:0] and additional parameter data is stored in DCRDR[31:8] + J-Link clears DEMCR[19] to indicate that it placed a command/data or read data + Monitor sets DEMCR[19] to indicate that it placed data or read data / is ready for a new command + Setting DEMCR[19] indicates "monitor ready for new command / data" and also indicates: "data has been placed in DCRDR by monitor, for J-Link" + Therefore it is responsibility of the commands to respond to the commands accordingly + + Commands for debug monitor + Commands must not exceed 0xFF (255) as we only defined 8-bits for command-part. Higher 24-bits are parameter info for current command + + Protocol for different commands: + J-Link: Cmd -> DCRDR, DEMCR[19] -> 0 => Cmd placed by probe + */ + LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // ParamInfo = _SYSTEM_DCRDR + LSRS R1,R0,#+8 // ParamInfo >>= 8 + LSLS R0,R0,#+24 + LSRS R0,R0,#+24 // Cmd = ParamInfo & 0xFF + // + // switch (Cmd) + // + CMP R0,#+0 + BEQ.N _HandleGetMonVersion // case _MON_CMD_GET_MONITOR_VERSION + CMP R0,#+2 + BEQ.N _HandleReadReg // case _MON_CMD_READ_REG + BCC.N _HandleRestartCPU // case _MON_CMD_RESTART_CPU + CMP R0,#+3 + BEQ.N _HandleWriteReg_Veneer // case _MON_CMD_WRITE_REG + B.N _IndicateMonReady // default : while (1); + /* + Return + _MON_CMD_RESTART_CPU + CPU: DEMCR[19] -> 0 => Monitor no longer ready + */ +_HandleRestartCPU: + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR &= ~(1uL << 19); => Clear MON_REQ to indicate that monitor is no longer active + BIC R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] + PUSH {LR} + BL JLINK_MONITOR_OnExit + POP {PC} + // + // Place data section here to not get in trouble with load-offsets + // + .section .text, "ax", %progbits + .align 2 +_AddrDCRDR: + .long 0xE000EDF8 +_AddrCPACR: + .long 0xE000ED88 + + .section .text, "ax" + .thumb_func + +;/********************************************************************* +;* +;* _HandleGetMonVersion +;* +;*/ +_HandleGetMonVersion: + /* + _MON_CMD_GET_MONITOR_VERSION + CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready + J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read + CPU: DEMCR[19] -> 1 => Mon ready + */ + MOVS R0,#+_MON_VERSION + STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // _SYSTEM_DCRDR = x + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready + B _WaitProbeReadIndicateMonRdy + +/********************************************************************* +* +* _HandleReadReg +* +*/ +_HandleWriteReg_Veneer: + B.N _HandleWriteReg +_HandleReadReg: + /* + _MON_CMD_READ_REG + CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready + J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read + CPU: DEMCR[19] -> 1 => Mon ready + + + Register indexes + 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) + 16: XPSR + 17: MSP + 18: PSP + 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + 20: FPSCR + 21-52: FPS0-FPS31 + + + Register usage when entering this "subroutine": + R0 Cmd + R1 ParamInfo + R2 --- + R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) + R12 --- + + Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension + LR Return to Return SP Frame type + --------------------------------------------------------- + 0xFFFFFFE1 Handler mode. MSP Extended + 0xFFFFFFE9 Thread mode MSP Extended + 0xFFFFFFED Thread mode PSP Extended + 0xFFFFFFF1 Handler mode. MSP Basic + 0xFFFFFFF9 Thread mode MSP Basic + 0xFFFFFFFD Thread mode PSP Basic + + So LR[2] == 1 => Return stack == PSP else MSP + + R0-R3, R12, PC, xPSR can be read from application stackpointer + Other regs can be read directly + */ + LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP + ITE CS + MRSCS R2,PSP + MRSCC R2,MSP + CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) + BCS _HandleReadRegR4 + LDR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) + B.N _HandleReadRegDone +_HandleReadRegR4: + CMP R1,#+5 // if (RegIndex < 5) { (R4) + BCS _HandleReadRegR5 + MOV R0,R4 + B.N _HandleReadRegDone +_HandleReadRegR5: + CMP R1,#+6 // if (RegIndex < 6) { (R5) + BCS _HandleReadRegR6 + MOV R0,R5 + B.N _HandleReadRegDone +_HandleReadRegR6: + CMP R1,#+7 // if (RegIndex < 7) { (R6) + BCS _HandleReadRegR7 + MOV R0,R6 + B.N _HandleReadRegDone +_HandleReadRegR7: + CMP R1,#+8 // if (RegIndex < 8) { (R7) + BCS _HandleReadRegR8 + MOV R0,R7 + B.N _HandleReadRegDone +_HandleReadRegR8: + CMP R1,#+9 // if (RegIndex < 9) { (R8) + BCS _HandleReadRegR9 + MOV R0,R8 + B.N _HandleReadRegDone +_HandleReadRegR9: + CMP R1,#+10 // if (RegIndex < 10) { (R9) + BCS _HandleReadRegR10 + MOV R0,R9 + B.N _HandleReadRegDone +_HandleReadRegR10: + CMP R1,#+11 // if (RegIndex < 11) { (R10) + BCS _HandleReadRegR11 + MOV R0,R10 + B.N _HandleReadRegDone +_HandleReadRegR11: + CMP R1,#+12 // if (RegIndex < 12) { (R11) + BCS _HandleReadRegR12 + MOV R0,R11 + B.N _HandleReadRegDone +_HandleReadRegR12: + CMP R1,#+14 // if (RegIndex < 14) { (R12) + BCS _HandleReadRegR14 + LDR R0,[R2, #+_APP_SP_OFF_R12] + B.N _HandleReadRegDone +_HandleReadRegR14: + CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) + BCS _HandleReadRegR15 + LDR R0,[R2, #+_APP_SP_OFF_R14_LR] + B.N _HandleReadRegDone +_HandleReadRegR15: + CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) + BCS _HandleReadRegXPSR + LDR R0,[R2, #+_APP_SP_OFF_PC] + B.N _HandleReadRegDone +_HandleReadRegXPSR: + CMP R1,#+17 // if (RegIndex < 17) { (xPSR) + BCS _HandleReadRegMSP + LDR R0,[R2, #+_APP_SP_OFF_XPSR] + B.N _HandleReadRegDone +_HandleReadRegMSP: + /* + Stackpointer is tricky because we need to get some info about the SP used in the user app, first + + Handle reading R0-R3 which can be read right from application stackpointer + + Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension + LR Return to Return SP Frame type + --------------------------------------------------------- + 0xFFFFFFE1 Handler mode. MSP Extended + 0xFFFFFFE9 Thread mode MSP Extended + 0xFFFFFFED Thread mode PSP Extended + 0xFFFFFFF1 Handler mode. MSP Basic + 0xFFFFFFF9 Thread mode MSP Basic + 0xFFFFFFFD Thread mode PSP Basic + + So LR[2] == 1 => Return stack == PSP else MSP + Per architecture definition: Inside monitor (exception) SP = MSP + + Stack pointer handling is complicated because it is different what is pushed on the stack before entering the monitor ISR... + Cortex-M: 8 regs + Cortex-M + forced-stack-alignment: 8 regs + 1 dummy-word if stack was not 8-byte aligned + Cortex-M + FPU: 8 regs + 17 FPU regs + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned + Cortex-M + FPU + lazy mode: 8 regs + 17 dummy-words + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned + */ + CMP R1,#+18 // if (RegIndex < 18) { (MSP) + BCS _HandleReadRegPSP + MRS R0,MSP + LSRS R1,LR,#+3 // LR[2] -> Carry == 0 => CPU was running on MSP => Needs correction + BCS _HandleReadRegDone_Veneer // CPU was running on PSP? => No correction necessary +_HandleSPCorrection: + LSRS R1,LR,#+5 // LR[4] -> Carry == 0 => extended stack frame has been allocated. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry + ITE CS + ADDCS R0,R0,#+_NUM_BYTES_BASIC_STACKFRAME + ADDCC R0,R0,#+_NUM_BYTES_EXTENDED_STACKFRAME + LDR R1,[R2, #+_APP_SP_OFF_XPSR] // Get xPSR from application stack (R2 has been set to app stack on beginning of _HandleReadReg) + LSRS R1,R1,#+5 // xPSR[9] -> Carry == 1 => Stack has been force-aligned before pushing regs. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry + IT CS + ADDCS R0,R0,#+4 + B _HandleReadRegDone +_HandleReadRegPSP: // RegIndex == 18 + CMP R1,#+19 // if (RegIndex < 19) { + BCS _HandleReadRegCFBP + MRS R0,PSP // PSP is not touched by monitor + LSRS R1,LR,#+3 // LR[2] -> Carry == 1 => CPU was running on PSP => Needs correction + BCC _HandleReadRegDone_Veneer // CPU was running on MSP? => No correction of PSP necessary + B _HandleSPCorrection +_HandleReadRegCFBP: + /* + CFBP is a register that can only be read via debug probe and is a merger of the following regs: + CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode + */ + CMP R1,#+20 // if (RegIndex < 20) { (CFBP) + BCS _HandleReadRegFPU + MOVS R0,#+0 + MRS R2,PRIMASK + ORRS R0,R2 // Merge PRIMASK into CFBP[7:0] + MRS R2,BASEPRI + LSLS R2,R2,#+8 // Merge BASEPRI into CFBP[15:8] + ORRS R0,R2 + MRS R2,FAULTMASK + LSLS R2,R2,#+16 // Merge FAULTMASK into CFBP[23:16] + ORRS R0,R2 + MRS R2,CONTROL + LSRS R1,LR,#3 // LR[2] -> Carry. CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior + IT CS // As J-Link sees value of CONTROL at application time, we need reconstruct original value of CONTROL + ORRCS R2,R2,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor + LSRS R1,LR,#+5 // LR[4] == NOT(CONTROL.FPCA) -> Carry + ITE CS // Merge original value of FPCA (CONTROL[2]) into read data + BICCS R2,R2,#+0x04 // Remember LR contains NOT(CONTROL) + ORRCC R2,R2,#+0x04 + LSLS R2,R2,#+24 + ORRS R0,R2 + B.N _HandleReadRegDone +_HandleReadRegFPU: +#if _HAS_FPU_REGS + CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) + BCS _HandleReadRegDone_Veneer + /* + Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled + If not, access to floating point is not possible + CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + */ + LDR R0,_AddrCPACR + LDR R0,[R0] + LSLS R0,R0,#+8 + LSRS R0,R0,#+28 + CMP R0,#+0xF + BEQ _HandleReadRegFPU_Allowed + CMP R0,#+0x5 + BNE _HandleReadRegDone_Veneer +_HandleReadRegFPU_Allowed: + CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) + BCS _HandleReadRegFPS0_FPS31 + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleReadFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleReadFPSCRLazyMode + LDR R0,[R2, #+_APP_SP_OFF_FPSCR] + B _HandleReadRegDone +_HandleReadFPSCRLazyMode: + VMRS R0,FPSCR + B _HandleReadRegDone +_HandleReadRegFPS0_FPS31: // RegIndex == 21-52 + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleReadFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleReadFPS0_FPS31LazyMode + SUBS R1,#+21 // Convert absolute reg index into rel. one + LSLS R1,R1,#+2 // RegIndex to position on stack + ADDS R1,#+_APP_SP_OFF_S0 + LDR R0,[R2, R1] +_HandleReadRegDone_Veneer: + B _HandleReadRegDone +_HandleReadFPS0_FPS31LazyMode: + SUBS R1,#+20 // convert abs. RegIndex into rel. one + MOVS R0,#+6 + MULS R1,R0,R1 + LDR R0,=_HandleReadRegUnknown + SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) + ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr + BX R0 + // + // Table for reading FPS0-FPS31 + // + VMOV R0,S31 // v = FPSx + B _HandleReadRegDone + VMOV R0,S30 + B _HandleReadRegDone + VMOV R0,S29 + B _HandleReadRegDone + VMOV R0,S28 + B _HandleReadRegDone + VMOV R0,S27 + B _HandleReadRegDone + VMOV R0,S26 + B _HandleReadRegDone + VMOV R0,S25 + B _HandleReadRegDone + VMOV R0,S24 + B _HandleReadRegDone + VMOV R0,S23 + B _HandleReadRegDone + VMOV R0,S22 + B _HandleReadRegDone + VMOV R0,S21 + B _HandleReadRegDone + VMOV R0,S20 + B _HandleReadRegDone + VMOV R0,S19 + B _HandleReadRegDone + VMOV R0,S18 + B _HandleReadRegDone + VMOV R0,S17 + B _HandleReadRegDone + VMOV R0,S16 + B _HandleReadRegDone + VMOV R0,S15 + B _HandleReadRegDone + VMOV R0,S14 + B _HandleReadRegDone + VMOV R0,S13 + B _HandleReadRegDone + VMOV R0,S12 + B _HandleReadRegDone + VMOV R0,S11 + B _HandleReadRegDone + VMOV R0,S10 + B _HandleReadRegDone + VMOV R0,S9 + B _HandleReadRegDone + VMOV R0,S8 + B _HandleReadRegDone + VMOV R0,S7 + B _HandleReadRegDone + VMOV R0,S6 + B _HandleReadRegDone + VMOV R0,S5 + B _HandleReadRegDone + VMOV R0,S4 + B _HandleReadRegDone + VMOV R0,S3 + B _HandleReadRegDone + VMOV R0,S2 + B _HandleReadRegDone + VMOV R0,S1 + B _HandleReadRegDone + VMOV R0,S0 + B _HandleReadRegDone +#else + B _HandleReadRegUnknown +_HandleReadRegDone_Veneer: + B _HandleReadRegDone +#endif +_HandleReadRegUnknown: + MOVS R0,#+0 // v = 0 + B.N _HandleReadRegDone +_HandleReadRegDone: + + // Send register content to J-Link and wait until J-Link has read the data + + STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // DCRDR = v; + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready + B _WaitProbeReadIndicateMonRdy + + // Data section for register addresses + +_HandleWriteReg: + /* + _MON_CMD_WRITE_REG + CPU: DEMCR[19] -> 1 => Mon ready + J-Link: Data -> DCRDR, DEMCR[19] -> 0 => Data placed by probe + CPU: DCRDR -> Read, Process command, DEMCR[19] -> 1 => Data read & mon ready + + Register indexes + 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) + 16: XPSR + 17: MSP + 18: PSP + 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + 20: FPSCR + 21-52: FPS0-FPS31 + + + Register usage when entering this "subroutine": + R0 Cmd + R1 ParamInfo + R2 --- + R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) + R12 --- + + Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension + LR Return to Return SP Frame type + --------------------------------------------------------- + 0xFFFFFFE1 Handler mode. MSP Extended + 0xFFFFFFE9 Thread mode MSP Extended + 0xFFFFFFED Thread mode PSP Extended + 0xFFFFFFF1 Handler mode. MSP Basic + 0xFFFFFFF9 Thread mode MSP Basic + 0xFFFFFFFD Thread mode PSP Basic + + So LR[2] == 1 => Return stack == PSP else MSP + + R0-R3, R12, PC, xPSR can be written via application stackpointer + Other regs can be written directly + + + Read register data from J-Link into R0 + */ + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Monitor is ready to receive register data + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] +_HandleWRegWaitUntilDataRecv: + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] + LSLS R0,R0,#+12 + BMI.N _HandleWRegWaitUntilDataRecv // DEMCR[19] == 0 => J-Link has placed new data for us + LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // Get register data + // + // Determine application SP + // + LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP + ITE CS + MRSCS R2,PSP + MRSCC R2,MSP + CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) + BCS _HandleWriteRegR4 + STR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) + B.N _HandleWriteRegDone +_HandleWriteRegR4: + CMP R1,#+5 // if (RegIndex < 5) { (R4) + BCS _HandleWriteRegR5 + MOV R4,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR5: + CMP R1,#+6 // if (RegIndex < 6) { (R5) + BCS _HandleWriteRegR6 + MOV R5,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR6: + CMP R1,#+7 // if (RegIndex < 7) { (R6) + BCS _HandleWriteRegR7 + MOV R6,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR7: + CMP R1,#+8 // if (RegIndex < 8) { (R7) + BCS _HandleWriteRegR8 + MOV R7,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR8: + CMP R1,#+9 // if (RegIndex < 9) { (R8) + BCS _HandleWriteRegR9 + MOV R8,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR9: + CMP R1,#+10 // if (RegIndex < 10) { (R9) + BCS _HandleWriteRegR10 + MOV R9,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR10: + CMP R1,#+11 // if (RegIndex < 11) { (R10) + BCS _HandleWriteRegR11 + MOV R10,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR11: + CMP R1,#+12 // if (RegIndex < 12) { (R11) + BCS _HandleWriteRegR12 + MOV R11,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR12: + CMP R1,#+14 // if (RegIndex < 14) { (R12) + BCS _HandleWriteRegR14 + STR R0,[R2, #+_APP_SP_OFF_R12] + B.N _HandleWriteRegDone +_HandleWriteRegR14: + CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) + BCS _HandleWriteRegR15 + STR R0,[R2, #+_APP_SP_OFF_R14_LR] + B.N _HandleWriteRegDone +_HandleWriteRegR15: + CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) + BCS _HandleWriteRegXPSR + STR R0,[R2, #+_APP_SP_OFF_PC] + B.N _HandleWriteRegDone +_HandleWriteRegXPSR: + CMP R1,#+17 // if (RegIndex < 17) { (xPSR) + BCS _HandleWriteRegMSP + STR R0,[R2, #+_APP_SP_OFF_XPSR] + B.N _HandleWriteRegDone +_HandleWriteRegMSP: + // + // For now, SP cannot be modified because it is needed to jump back from monitor mode + // + CMP R1,#+18 // if (RegIndex < 18) { (MSP) + BCS _HandleWriteRegPSP + B.N _HandleWriteRegDone +_HandleWriteRegPSP: // RegIndex == 18 + CMP R1,#+19 // if (RegIndex < 19) { + BCS _HandleWriteRegCFBP + B.N _HandleWriteRegDone +_HandleWriteRegCFBP: + /* + CFBP is a register that can only be read via debug probe and is a merger of the following regs: + CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode + */ + CMP R1,#+20 // if (RegIndex < 20) { (CFBP) + BCS _HandleWriteRegFPU + LSLS R1,R0,#+24 + LSRS R1,R1,#+24 // Extract CFBP[7:0] => PRIMASK + MSR PRIMASK,R1 + LSLS R1,R0,#+16 + LSRS R1,R1,#+24 // Extract CFBP[15:8] => BASEPRI + MSR BASEPRI,R1 + LSLS R1,R0,#+8 // Extract CFBP[23:16] => FAULTMASK + LSRS R1,R1,#+24 + MSR FAULTMASK,R1 + LSRS R1,R0,#+24 // Extract CFBP[31:24] => CONTROL + LSRS R0,R1,#2 // Current CONTROL[1] -> Carry + ITE CS // Update saved CONTROL.SPSEL (CONTROL[1]). CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior + ORRCS LR,LR,#+4 + BICCC LR,LR,#+4 + BIC R1,R1,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor. Otherwise behavior is UNPREDICTABLE + LSRS R0,R1,#+3 // New CONTROL.FPCA (CONTROL[2]) -> Carry + ITE CS // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BICCS LR,LR,#+0x10 // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + ORRCC LR,LR,#+0x10 + MRS R0,CONTROL + LSRS R0,R0,#+3 // CONTROL[2] -> Carry + ITE CS // Preserve original value of current CONTROL[2] + ORRCS R1,R1,#+0x04 + BICCC R1,R1,#+0x04 + MSR CONTROL,R1 + ISB // Necessary after writing to CONTROL, see ARM DDI0403D, B1.4.4 The special-purpose CONTROL register + B.N _HandleWriteRegDone +_HandleWriteRegFPU: +#if _HAS_FPU_REGS + CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) + BCS _HandleWriteRegDone_Veneer + /* + Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled + If not, access to floating point is not possible + CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + */ + MOV R12,R0 // Save register data + LDR R0,_AddrCPACR + LDR R0,[R0] + LSLS R0,R0,#+8 + LSRS R0,R0,#+28 + CMP R0,#+0xF + BEQ _HandleWriteRegFPU_Allowed + CMP R0,#+0x5 + BNE _HandleWriteRegDone_Veneer +_HandleWriteRegFPU_Allowed: + CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) + BCS _HandleWriteRegFPS0_FPS31 + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleWriteFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleWriteFPSCRLazyMode + STR R12,[R2, #+_APP_SP_OFF_FPSCR] + B _HandleWriteRegDone +_HandleWriteFPSCRLazyMode: + VMSR FPSCR,R12 + B _HandleWriteRegDone +_HandleWriteRegFPS0_FPS31: // RegIndex == 21-52 + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleWriteFPS0_FPS31LazyMode + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleWriteFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + SUBS R1,#+21 // Convert absolute reg index into rel. one + LSLS R1,R1,#+2 // RegIndex to position on stack + ADDS R1,#+_APP_SP_OFF_S0 + STR R12,[R2, R1] +_HandleWriteRegDone_Veneer: + B _HandleWriteRegDone +_HandleWriteFPS0_FPS31LazyMode: + SUBS R1,#+20 // Convert abs. RegIndex into rel. one + MOVS R0,#+6 + MULS R1,R0,R1 + LDR R0,=_HandleReadRegUnknown + SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) + ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr + BX R0 + // + // Table for reading FPS0-FPS31 + // + VMOV S31,R12 // v = FPSx + B _HandleWriteRegDone + VMOV S30,R12 + B _HandleWriteRegDone + VMOV S29,R12 + B _HandleWriteRegDone + VMOV S28,R12 + B _HandleWriteRegDone + VMOV S27,R12 + B _HandleWriteRegDone + VMOV S26,R12 + B _HandleWriteRegDone + VMOV S25,R12 + B _HandleWriteRegDone + VMOV S24,R12 + B _HandleWriteRegDone + VMOV S23,R12 + B _HandleWriteRegDone + VMOV S22,R12 + B _HandleWriteRegDone + VMOV S21,R12 + B _HandleWriteRegDone + VMOV S20,R12 + B _HandleWriteRegDone + VMOV S19,R12 + B _HandleWriteRegDone + VMOV S18,R12 + B _HandleWriteRegDone + VMOV S17,R12 + B _HandleWriteRegDone + VMOV S16,R12 + B _HandleWriteRegDone + VMOV S15,R12 + B _HandleWriteRegDone + VMOV S14,R12 + B _HandleWriteRegDone + VMOV S13,R12 + B _HandleWriteRegDone + VMOV S12,R12 + B _HandleWriteRegDone + VMOV S11,R12 + B _HandleWriteRegDone + VMOV S10,R12 + B _HandleWriteRegDone + VMOV S9,R12 + B _HandleWriteRegDone + VMOV S8,R12 + B _HandleWriteRegDone + VMOV S7,R12 + B _HandleWriteRegDone + VMOV S6,R12 + B _HandleWriteRegDone + VMOV S5,R12 + B _HandleWriteRegDone + VMOV S4,R12 + B _HandleWriteRegDone + VMOV S3,R12 + B _HandleWriteRegDone + VMOV S2,R12 + B _HandleWriteRegDone + VMOV S1,R12 + B _HandleWriteRegDone + VMOV S0,R12 + B _HandleWriteRegDone +#else + B _HandleWriteRegUnknown +#endif +_HandleWriteRegUnknown: + B.N _HandleWriteRegDone +_HandleWriteRegDone: + B _IndicateMonReady // Indicate that monitor has read data, processed command and is ready for a new one + .end +/****** End Of File *************************************************/ From 6d6ed55ed7a91a7a64846b8be09a05dfa4c837e6 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:25:14 +0800 Subject: [PATCH 1348/1377] Retire PPR Boards (#4956) The Othernet project appears to have failed. Retire these boards/variants. --- boards/ppr.json | 47 --------- boards/ppr1.json | 47 --------- variants/ppr/platformio.ini | 8 -- variants/ppr/variant.cpp | 42 -------- variants/ppr/variant.h | 159 ------------------------------- variants/ppr1/platformio.ini | 9 -- variants/ppr1/variant.cpp | 41 -------- variants/ppr1/variant.h | 179 ----------------------------------- 8 files changed, 532 deletions(-) delete mode 100644 boards/ppr.json delete mode 100644 boards/ppr1.json delete mode 100644 variants/ppr/platformio.ini delete mode 100644 variants/ppr/variant.cpp delete mode 100644 variants/ppr/variant.h delete mode 100644 variants/ppr1/platformio.ini delete mode 100644 variants/ppr1/variant.cpp delete mode 100644 variants/ppr1/variant.h diff --git a/boards/ppr.json b/boards/ppr.json deleted file mode 100644 index 15e3025c0..000000000 --- a/boards/ppr.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52840_s140_v6.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_PPR -DNRF52840_XXAA", - "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4403"]], - "usb_product": "PPR", - "mcu": "nrf52840", - "variant": "ppr", - "variants_dir": "variants", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" - }, - "bootloader": { - "settings_addr": "0xFF000" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52840_xxAA", - "onboard_tools": ["jlink"], - "svd_path": "nrf52840.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "Meshtastic PPR (Adafruit BSP)", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "require_upload_port": true, - "speed": 115200, - "protocol": "jlink", - "protocols": ["jlink", "nrfjprog", "stlink"] - }, - "url": "https://meshtastic.org/", - "vendor": "Othernet" -} diff --git a/boards/ppr1.json b/boards/ppr1.json deleted file mode 100644 index 35bf7d1e4..000000000 --- a/boards/ppr1.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52833_s113_v7.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52833_PPR -DNRF52833_XXAA", - "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4406"]], - "usb_product": "PPR", - "mcu": "nrf52833", - "variant": "ppr", - "variants_dir": "variants", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS113", - "sd_name": "s113", - "sd_version": "7.2.0", - "sd_fwid": "0x00b6" - }, - "bootloader": { - "settings_addr": "0xFF000" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52833_xxAA", - "onboard_tools": ["jlink"], - "svd_path": "nrf52833.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "Meshtastic PPR1 (Adafruit BSP)", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "require_upload_port": true, - "speed": 115200, - "protocol": "jlink", - "protocols": ["jlink", "nrfjprog", "stlink"] - }, - "url": "https://meshtastic.org/", - "vendor": "Othernet" -} diff --git a/variants/ppr/platformio.ini b/variants/ppr/platformio.ini deleted file mode 100644 index 22273ce8e..000000000 --- a/variants/ppr/platformio.ini +++ /dev/null @@ -1,8 +0,0 @@ -; The PPR board -[env:ppr] -extends = nrf52_base -board = ppr -board_level = extra -lib_deps = - ${arduino_base.lib_deps} - industruino/UC1701@^1.1.0 \ No newline at end of file diff --git a/variants/ppr/variant.cpp b/variants/ppr/variant.cpp deleted file mode 100644 index f5f219e9b..000000000 --- a/variants/ppr/variant.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // P0 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xff, 12, 13, 0xff, 15, 0xff, 17, 18, 0xff, 20, 0xff, 22, 0xff, 24, 0xff, 26, 0xff, 28, 29, - 30, 31, - - // P1 - 32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); -} diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h deleted file mode 100644 index 4c6cc015c..000000000 --- a/variants/ppr/variant.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -// This board does not have a 32khz crystal -// #define USE_LFXO // Board uses 32khz crystal for LF -#define USE_LFRC // Board uses RC for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (46) -#define NUM_DIGITAL_PINS (46) -#define NUM_ANALOG_INPUTS (0) -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (0) -#define PIN_LED2 (1) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_RED PIN_LED1 -#define LED_GREEN PIN_LED2 - -// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this. -#define LED_BLUE LED_GREEN - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ -#define PIN_BUTTON1 4 // center -#define PIN_BUTTON2 2 -#define PIN_BUTTON3 3 -#define PIN_BUTTON4 5 -#define PIN_BUTTON5 6 - -/* - * Analog pins - */ -#define PIN_A0 (0xff) -#define PIN_A1 (0xff) -#define PIN_A2 (0xff) -#define PIN_A3 (0xff) -#define PIN_A4 (0xff) -#define PIN_A5 (0xff) -#define PIN_A6 (0xff) -#define PIN_A7 (0xff) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF (0xff) -// #define PIN_NFC1 (9) -// #define PIN_NFC2 (10) - -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ - -// GPS is on Serial1 -#define PIN_SERIAL1_RX (8) -#define PIN_SERIAL1_TX (9) - -// Connected to Jlink CDC -// #define PIN_SERIAL2_RX (8) -// #define PIN_SERIAL2_TX (6) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (15) -#define PIN_SPI_MOSI (13) -#define PIN_SPI_SCK (12) - -// static const uint8_t SS = 44; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (32 + 2) -#define PIN_WIRE_SCL (32) - -// CUSTOM GPIOs the SX1262 -#define USE_SX1262 -#define SX126X_CS (10) -#define SX126X_DIO1 (20) -#define SX1262_DIO2 (26) -#define SX126X_BUSY (31) // Supposed to be P0.18 but because of reworks, now on P0.31 (18) -#define SX126X_RESET (17) -// #define SX126X_ANT_SW (32 + 10) -#define SX126X_RXEN (22) -#define SX126X_TXEN (24) -// Indicates this SX1262 is inside of an ebyte E22 module and special config should be done for that -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// ERC12864-10 LCD -#define ERC12864_CS (32 + 4) -#define ERC12864_RESET (32 + 6) -#define ERC12864_CD (32 + 9) - -// L80 GPS -#define L80_PPS (28) -#define L80_RESET (29) - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ diff --git a/variants/ppr1/platformio.ini b/variants/ppr1/platformio.ini deleted file mode 100644 index f6c2a5e0b..000000000 --- a/variants/ppr1/platformio.ini +++ /dev/null @@ -1,9 +0,0 @@ -; The PPR board -[env:ppr1] -extends = nrf52_base -board = ppr1 -board_level = extra -build_flags = ${nrf52_base.build_flags} -Ivariants/ppr1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ppr1> -lib_deps = - ${arduino_base.lib_deps} \ No newline at end of file diff --git a/variants/ppr1/variant.cpp b/variants/ppr1/variant.cpp deleted file mode 100644 index acc3e344a..000000000 --- a/variants/ppr1/variant.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // P0 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); -} diff --git a/variants/ppr1/variant.h b/variants/ppr1/variant.h deleted file mode 100644 index ba3a25c2a..000000000 --- a/variants/ppr1/variant.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -// This board does have a 32khz crystal -#define USE_LFXO // Board uses 32khz crystal for LF -// #define USE_LFRC // Board uses RC for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (46) -#define NUM_DIGITAL_PINS (46) -#define NUM_ANALOG_INPUTS (0) -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (25) -#define PIN_LED2 (11) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_RED PIN_LED1 -#define LED_GREEN PIN_LED2 - -// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this. -#define LED_BLUE LED_GREEN - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ -#define PIN_BUTTON1 4 // up -#define PIN_BUTTON2 2 // left -#define PIN_BUTTON3 3 // center -#define PIN_BUTTON4 5 // right -#define PIN_BUTTON5 6 // down - -/* - * Analog pins - */ -#define PIN_A0 (0xff) -#define PIN_A1 (0xff) -#define PIN_A2 (0xff) -#define PIN_A3 (0xff) -#define PIN_A4 (0xff) -#define PIN_A5 (0xff) -#define PIN_A6 (0xff) -#define PIN_A7 (0xff) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF (0xff) -// #define PIN_NFC1 (9) -// #define PIN_NFC2 (10) - -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ - -// GPS is on Serial1 -#define PIN_SERIAL1_RX (8) -#define PIN_SERIAL1_TX (9) - -// We intentionally leave this undefined so we don't even try to make a Ublox driver -// #define GPS_TX_PIN PIN_SERIAL1_TX -// #define GPS_RX_PIN PIN_SERIAL1_RX - -#define PIN_GPS_RESET 29 // active high -#define PIN_GPS_PPS 28 -// #define PIN_GPS_WAKE 20 // CELL_CTRL in schematic? based on their example code -#define PIN_GPS_EN 7 // GPS_EN active high - -// #define PIN_VUSB_EN 21 - -// LCD - -#define PIN_LCD_RESET 23 // active low, pulse low for 20ms at boot -#define USE_ST7567 - -/// Charge controller I2C address -#define BQ25703A_ADDR 0x6b - -// Define if screen should be mirrored left to right -#define SCREEN_MIRROR - -// LCD screens are slow, so slowdown the wipe so it looks better -#define SCREEN_TRANSITION_FRAMERATE 10 // fps - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (15) -#define PIN_SPI_MOSI (13) -#define PIN_SPI_SCK (12) - -// static const uint8_t SS = 44; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (32 + 2) -#define PIN_WIRE_SCL (32) - -// CUSTOM GPIOs the SX1262 -#define USE_SX1262 -#define SX126X_CS (0 + 10) // FIXME - we really should define LORA_CS instead -#define SX126X_DIO1 (0 + 20) -#define SX1262_DIO2 (0 + 26) -#define SX126X_BUSY (0 + 19) -#define SX126X_RESET (0 + 17) -#define SX126X_TXEN (0 + 24) -#define SX126X_RXEN (0 + 22) -// Not really an E22 but this board clones using DIO3 for tcxo control -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// FIXME, to prevent burning out parts I've set the power level super low, because I don't have -// an antenna wired up -#define SX126X_MAX_POWER 1 - -#define LORA_DISABLE_SENDING // Define this to disable transmission for testing (power testing etc...) - -// To debug via the segger JLINK console rather than the CDC-ACM serial device -// #define USE_SEGGER - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ From 243421b2a5ca2641b40440580bf81a59c9aa7955 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:25:28 +0800 Subject: [PATCH 1349/1377] Retire lora-relay boards (#4957) The lora-relay boards were important pathfinders for nrf52 support some years back. They are no longer commonly produced and there are now many nrf52 options on the market. Retire these boards and associated variant. --- boards/lora-relay-v1.json | 47 ------- boards/lora-relay-v2.json | 47 ------- platformio.ini | 3 +- variants/lora_relay_v1/platformio.ini | 24 ---- variants/lora_relay_v1/variant.cpp | 105 -------------- variants/lora_relay_v1/variant.h | 161 ---------------------- variants/lora_relay_v2/platformio.ini | 26 ---- variants/lora_relay_v2/variant.cpp | 105 -------------- variants/lora_relay_v2/variant.h | 188 -------------------------- 9 files changed, 1 insertion(+), 705 deletions(-) delete mode 100644 boards/lora-relay-v1.json delete mode 100644 boards/lora-relay-v2.json delete mode 100644 variants/lora_relay_v1/platformio.ini delete mode 100644 variants/lora_relay_v1/variant.cpp delete mode 100644 variants/lora_relay_v1/variant.h delete mode 100644 variants/lora_relay_v2/platformio.ini delete mode 100644 variants/lora_relay_v2/variant.cpp delete mode 100644 variants/lora_relay_v2/variant.h diff --git a/boards/lora-relay-v1.json b/boards/lora-relay-v1.json deleted file mode 100644 index b390b8404..000000000 --- a/boards/lora-relay-v1.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52840_s140_v6.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA", - "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4404"]], - "usb_product": "LORA_RELAY", - "mcu": "nrf52840", - "variant": "lora_relay_v1", - "variants_dir": "variants", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" - }, - "bootloader": { - "settings_addr": "0xFF000" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52840_xxAA", - "onboard_tools": ["jlink"], - "svd_path": "nrf52840.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "Meshtastic Lora Relay V1 (Adafruit BSP)", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "require_upload_port": true, - "speed": 115200, - "protocol": "jlink", - "protocols": ["jlink", "nrfjprog", "stlink"] - }, - "url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay", - "vendor": "BigCorvus" -} diff --git a/boards/lora-relay-v2.json b/boards/lora-relay-v2.json deleted file mode 100644 index 52b775e58..000000000 --- a/boards/lora-relay-v2.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52840_s140_v6.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V2 -DNRF52840_XXAA", - "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4406"]], - "usb_product": "LORA_RELAY", - "mcu": "nrf52840", - "variant": "lora_relay_v2", - "variants_dir": "variants", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" - }, - "bootloader": { - "settings_addr": "0xFF000" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52840_xxAA", - "onboard_tools": ["jlink"], - "svd_path": "nrf52840.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "Meshtastic Lora Relay V1 (Adafruit BSP)", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "require_upload_port": true, - "speed": 115200, - "protocol": "jlink", - "protocols": ["jlink", "nrfjprog", "stlink"] - }, - "url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay", - "vendor": "BigCorvus" -} diff --git a/platformio.ini b/platformio.ini index 22e2b6259..d4cd89631 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,11 +17,10 @@ default_envs = tbeam ;default_envs = tlora-v2-1-1_6 ;default_envs = tlora-v2-1-1_6-tcxo ;default_envs = tlora-t3s3-v1 -;default_envs = lora-relay-v1 # nrf board ;default_envs = t-echo ;default_envs = canaryone ;default_envs = nrf52840dk-geeksville -;default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here +;default_envs = native ;default_envs = nano-g1 ;default_envs = pca10059_diy_eink ;default_envs = meshtastic-diy-v1 diff --git a/variants/lora_relay_v1/platformio.ini b/variants/lora_relay_v1/platformio.ini deleted file mode 100644 index 435d256c5..000000000 --- a/variants/lora_relay_v1/platformio.ini +++ /dev/null @@ -1,24 +0,0 @@ -; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus -[env:lora-relay-v1] -extends = nrf52840_base -board = lora-relay-v1 -board_level = extra -# add our variants files to the include and src paths -# define build flags for the TFT_eSPI library -build_flags = ${nrf52840_base.build_flags} -Ivariants/lora_relay_v1 - -DUSER_SETUP_LOADED - -DTFT_WIDTH=80 - -DTFT_HEIGHT=160 - -DST7735_GREENTAB160x80 - -DST7735_DRIVER - -DTFT_CS=ST7735_CS - -DTFT_DC=ST7735_RS - -DTFT_RST=ST7735_RESET - -DSPI_FREQUENCY=27000000 - -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/lora_relay_v1> -lib_deps = - ${nrf52840_base.lib_deps} - sparkfun/SparkFun BQ27441 LiPo Fuel Gauge Arduino Library@^1.1.0 - bodmer/TFT_eSPI@^2.4.76 - adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/lora_relay_v1/variant.cpp b/variants/lora_relay_v1/variant.cpp deleted file mode 100644 index 891c8bb29..000000000 --- a/variants/lora_relay_v1/variant.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // D0 .. D13 - 25, // D0 is P0.25 (UART TX) - 24, // D1 is P0.24 (UART RX - 10, // D2 is P0.10 (NFC2) - 47, // D3 is P1.15 (LED1) - 42, // D4 is P1.10 (LED2) - 40, // D5 is P1.08 - 7, // D6 is P0.07 - 34, // D7 is P1.02 (Button) - 16, // D8 is P0.16 (NeoPixel) - 26, // D9 is P0.26 D_RS (IPS data/command control) - 27, // D10 is P0.27 - 6, // D11 is P0.06 D_RES (IPS display reset) - 8, // D12 is P0.08 D_CS (IPS display chip select) - 41, // D13 is P1.09 BLT (IPS display backlight) - 4, // D14 is P0.04 SX1262 RXEN - 5, // D15 is P0.05 BOOST_EN (5V buck converter enable for the the radio power) - - // D14 .. D21 (aka A0 .. A7) - 30, // D16 is P0.30 (A0) - 28, // D17 is P0.28 (A1) - 2, // D18 is P0.02 (A2) - 3, // D19 is P0.03 (A3) - 29, // D20 is P0.29 (A4, Battery) - 31, // D21 is P0.31 (A5, ARef) - - // D22 .. D23 (aka I2C pins) - 12, // D22 is P0.12 (SDA) - 11, // D23 is P0.11 (SCL) - - // D24 .. D26 (aka SPI pins) - 15, // D24 is P0.15 (SPI MISO) - 13, // D25 is P0.13 (SPI MOSI) - 14, // D26 is P0.14 (SPI SCK ) - - // QSPI pins (not exposed via any header / test point) - // 19, // P0.19 (QSPI CLK) - // 20, // P0.20 (QSPI CS) - // 17, // P0.17 (QSPI Data 0) - // 22, // P0.22 (QSPI Data 1) - // 23, // P0.23 (QSPI Data 2) - // 21, // P0.21 (QSPI Data 3) - - // The remaining NFC pin - 9, // D27 P0.09 (NFC1, exposed only via test point on bottom of board) - - // The following pins were never listed as they were considered unusable - // 0, // P0.00 is XL1 (attached to 32.768kHz crystal) Never expose as GPIOs - // 1, // P0.01 is XL2 (attached to 32.768kHz crystal) - 18, // D28 P0.18 is RESET (attached to switch) - // 32, // P1.00 is SWO (attached to debug header) - - // D29-D43 - 27, // D29 P0.27 E22-SX1262 DIO1 - 28, // D30 P0.28 E22-SX1262 DIO2 - 30, // D31 P0.30 E22-SX1262 TXEN - 35, // D32 P1.03 E22-SX1262 NSS - 32 + 8, // D33 P1.08 E22-SX1262 BUSY - 32 + 12, // D34 P1.12 E22-SX1262 RESET - 32 + 1, // P1.01 BTN_UP - 32 + 2, // P1.02 SWITCH - 32 + 14, // D37 P1.14 is not connected per schematic - 36, // P1.04 is not connected per schematic - 37, // P1.05 is not connected per schematic - 38, // P1.06 is not connected per schematic - 39, // P1.07 is not connected per schematic - 43, // P1.11 is not connected per schematic - 45, // P1.13 is not connected per schematic -}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); -} diff --git a/variants/lora_relay_v1/variant.h b/variants/lora_relay_v1/variant.h deleted file mode 100644 index 6efd711c6..000000000 --- a/variants/lora_relay_v1/variant.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _VARIANT_LORA_RELAY_V1_ -#define _VARIANT_LORA_RELAY_V1_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF -// define USE_LFRC // Board uses RC for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (43) -#define NUM_DIGITAL_PINS (43) -#define NUM_ANALOG_INPUTS (6) // A6 is used for battery, A7 is analog reference -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (3) -#define PIN_LED2 (4) -// #define PIN_NEOPIXEL (8) -#define HAS_NEOPIXEL // Enable the use of neopixels -#define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA 8 // gpio pin used to send data to the neopixels -#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_RED PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ -#define PIN_BUTTON1 (7) - -/* - * Analog pins - */ -#define PIN_A0 (16) -#define PIN_A1 (17) -#define PIN_A2 (18) -#define PIN_A3 (19) -#define PIN_A4 (20) -#define PIN_A5 (21) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF PIN_A5 -#define PIN_VBAT PIN_A4 -#define BATTERY_PIN PIN_VBAT -#define PIN_NFC1 (33) -#define PIN_NFC2 (2) -#define PIN_PIEZO (37) -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ -#define PIN_SERIAL1_RX (1) -#define PIN_SERIAL1_TX (0) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (24) -#define PIN_SPI_MOSI (25) -#define PIN_SPI_SCK (26) - -static const uint8_t SS = (5); -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (22) -#define PIN_WIRE_SCL (23) - -// I2C device addresses -#define I2C_ADDR_BQ27441 0x55 // Battery gauge - -// SX1262 declaration -#define USE_SX1262 - -// CUSTOM GPIOs the SX1262 -#define SX126X_CS (32) - -// If you would prefer to get console debug output over the JTAG ICE connection rather than the CDC-ACM USB serial device, just -// define this. #define USE_SEGGER - -#define SX126X_DIO1 (29) -#define SX1262_DIO2 (30) -#define SX126X_BUSY (33) // Supposed to be P0.18 but because of reworks, now on P0.31 (18) -#define SX126X_RESET (34) -// #define SX126X_ANT_SW (32 + 10) -#define SX126X_RXEN (14) -#define SX126X_TXEN (31) -#define SX126X_POWER_EN \ - (15) // FIXME, see warning hre https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay/blob/master/LORA_RELAY_NRF52840.ino -// Indicates this SX1262 is inside of an ebyte E22 module and special config should be done for that -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -#define ST7735_RESET (11) // Output -#define ST7735_CS (12) -#define TFT_BL (13) -#define ST7735_RS (9) - -// #define LORA_DISABLE_SENDING // The board can brownout during lora TX if you don't have a battery connected. Disable sending -// to allow USB power only based debugging - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file diff --git a/variants/lora_relay_v2/platformio.ini b/variants/lora_relay_v2/platformio.ini deleted file mode 100644 index 3598466d5..000000000 --- a/variants/lora_relay_v2/platformio.ini +++ /dev/null @@ -1,26 +0,0 @@ -; The https://github.com/BigCorvus/LoRa-BLE-Relay-v2 board by @BigCorvus -[env:lora-relay-v2] -extends = nrf52840_base -board = lora-relay-v2 -board_level = extra -# add our variants files to the include and src paths -# define build flags for the TFT_eSPI library -build_flags = ${nrf52840_base.build_flags} -Ivariants/lora_relay_v2 - -DUSER_SETUP_LOADED - -DTFT_WIDTH=80 - -DTFT_HEIGHT=160 - -DST7735_GREENTAB160x80 - -DST7735_DRIVER - -DTFT_CS=ST7735_CS - -DTFT_DC=ST7735_RS - -DTFT_RST=ST7735_RESET - -DSPI_FREQUENCY=27000000 - -DTFT_WR=ST7735_SDA - -DTFT_SCLK=ST7735_SCK - -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/lora_relay_v2> -lib_deps = - ${nrf52840_base.lib_deps} - sparkfun/SparkFun BQ27441 LiPo Fuel Gauge Arduino Library@^1.1.0 - bodmer/TFT_eSPI@^2.4.76 - adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file diff --git a/variants/lora_relay_v2/variant.cpp b/variants/lora_relay_v2/variant.cpp deleted file mode 100644 index 23d648873..000000000 --- a/variants/lora_relay_v2/variant.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // D0 .. D13 - 25, // D0 is P0.25 (UART TX) - 24, // D1 is P0.24 (UART RX - 10, // D2 is P0.10 (NFC2) - 47, // D3 is P1.15 (LED1) - (32 + 10), // D4 is P1.10 (LED2) - 40, // D5 is P1.08 - 7, // D6 is P0.07 - 34, // D7 is P1.02 (Switch) - 16, // D8 is P0.16 (NeoPixel) - 26, // D9 is P0.26 D_RS (IPS data/command control) - 27, // D10 is P0.27 - 6, // D11 is P0.06 D_RES (IPS display reset) - 8, // D12 is P0.08 D_CS (IPS display chip select) - 41, // D13 is P0.23 BLT (IPS display backlight) - 4, // D14 is P0.04 SX1262 RXEN - 5, // D15 is P0.05 BOOST_EN (5V buck converter enable for the the radio power) - - // D14 .. D21 (aka A0 .. A7) - 30, // D16 is P0.30 (A0) - 28, // D17 is P0.28 (A1) - 2, // D18 is P0.02 (A2) - 3, // D19 is P0.03 (A3) - 29, // D20 is P0.29 (A4, Battery) - 31, // D21 is P0.31 (A5, ARef) - - // D22 .. D23 (aka I2C pins) - 12, // D22 is P0.12 (SDA) - 11, // D23 is P0.11 (SCL) - - // D24 .. D26 (aka SPI pins) - 15, // D24 is P0.15 (SPI MISO) - 13, // D25 is P0.13 (SPI MOSI) - 14, // D26 is P0.14 (SPI SCK ) - - // QSPI pins (not exposed via any header / test point) - // 19, // P0.19 (QSPI CLK) - // 20, // P0.20 (QSPI CS) - // 17, // P0.17 (QSPI Data 0) - // 22, // P0.22 (QSPI Data 1) - // 23, // P0.23 (QSPI Data 2) - // 21, // P0.21 (QSPI Data 3) - - // The remaining NFC pin - 9, // D27 P0.09 (NFC1, exposed only via test point on bottom of board) - - // The following pins were never listed as they were considered unusable - // 0, // P0.00 is XL1 (attached to 32.768kHz crystal) Never expose as GPIOs - // 1, // P0.01 is XL2 (attached to 32.768kHz crystal) - 18, // D28 P0.18 is RESET (attached to switch) - // 32, // P1.00 is SWO (attached to debug header) - - // D29-D43 - 32 + 12, // D29 P0.27 E22-SX1262 DIO1 - 28, // D30 P0.28 E22-SX1262 DIO2 - 30, // D31 P0.30 E22-SX1262 TXEN - 35, // D32 P1.03 E22-SX1262 NSS - 32 + 8, // D33 P1.08 E22-SX1262 BUSY - 27, // D34 P0.27 E22-SX1262 RESET - 32 + 1, // D35 P1.01 BTN_UP - 32, // D36 P1.0 GPS power - 21, // D37 P0.21 disp_clk - 36, // P1.04 BTN_OK - 37, // D39 P0.19 disp_SDA - 38, // D40 P1.06 BUZZER - 39, // P1.07 is not connected per schematic - 43, // P1.11 is not connected per schematic - 45, // P1.13 is not connected per schematic -}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); -} diff --git a/variants/lora_relay_v2/variant.h b/variants/lora_relay_v2/variant.h deleted file mode 100644 index f18f81034..000000000 --- a/variants/lora_relay_v2/variant.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _VARIANT_LORA_RELAY_V1_ -#define _VARIANT_LORA_RELAY_V1_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF -// define USE_LFRC // Board uses RC for LF - -/* -kevinh todo - -ok leds -ok buttons -ok gps power -ok gps signal -ok? lcd -ok buzzer -serial flash -ok lora (inc boost en) - -mention dat1 and dat2 on sd card -use hardware spi controller for lcd - not bitbang - -*/ - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (43) -#define NUM_DIGITAL_PINS (43) -#define NUM_ANALOG_INPUTS (6) // A6 is used for battery, A7 is analog reference -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (3) -#define PIN_LED2 (4) -// #define PIN_NEOPIXEL (8) -#define HAS_NEOPIXEL // Enable the use of neopixels -#define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA 8 // gpio pin used to send data to the neopixels -#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use - -#define PIN_BUZZER (40) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_RED PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ -#define PIN_BUTTON1 (7) -#define PIN_BUTTON2 (35) -#define PIN_BUTTON3 (37) - -/* - * Analog pins - */ -#define PIN_A0 (16) -#define PIN_A1 (17) -#define PIN_A2 (18) -#define PIN_A3 (19) -#define PIN_A4 (20) -#define PIN_A5 (21) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF PIN_A5 -#define PIN_VBAT PIN_A4 -#define BATTERY_PIN PIN_VBAT -#define PIN_NFC1 (33) -#define PIN_NFC2 (2) -#define PIN_PIEZO (37) -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ -#define PIN_SERIAL1_RX (1) -#define PIN_SERIAL1_TX (0) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (24) -#define PIN_SPI_MOSI (25) -#define PIN_SPI_SCK (26) - -static const uint8_t SS = (5); -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (22) -#define PIN_WIRE_SCL (23) - -// I2C device addresses -#define I2C_ADDR_BQ27441 0x55 // Battery gauge - -// SX1262 declaration -#define USE_SX1262 - -// CUSTOM GPIOs the SX1262 -#define SX126X_CS (32) - -// If you would prefer to get console debug output over the JTAG ICE connection rather than the CDC-ACM USB serial device, just -// define this. #define USE_SEGGER - -#define SX126X_DIO1 (29) -#define SX1262_DIO2 (30) -#define SX126X_BUSY (33) // Supposed to be P0.18 but because of reworks, now on P0.31 (18) -#define SX126X_RESET (34) -// #define SX126X_ANT_SW (32 + 10) -#define SX126X_RXEN (14) -#define SX126X_TXEN (31) -#define SX126X_POWER_EN \ - (15) // FIXME, see warning hre https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay/blob/master/LORA_RELAY_NRF52840.ino -// Indicates this SX1262 is inside of an ebyte E22 module and special config should be done for that -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// ST7565 SPI -#define ST7735_RESET (11) // Output -#define ST7735_CS (12) -#define TFT_BL (13) -#define ST7735_RS (9) -#define ST7735_SDA (39) // actually spi MOSI -#define ST7735_SCK (37) // actually spi clk - -#define PIN_GPS_EN 36 // Just kill GPS power when we want it to sleep? FIXME -#define GPS_EN_ACTIVE 0 // GPS Power output is active low - -// #define LORA_DISABLE_SENDING // The board can brownout during lora TX if you don't have a battery connected. Disable sending -// to allow USB power only based debugging - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file From 8acc9ccf5fcd95046fea7574dd4cbe57c87bcfa7 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:26:54 +0800 Subject: [PATCH 1350/1377] Remove support for pca10056-rc-clock (#4955) In 2020, geeksville had a NRF52840-dk development board with a busted oscilliator. Let's retire it from service :) Co-authored-by: Ben Meadors --- boards/nrf52840_dk_modified.json | 47 ------- platformio.ini | 1 - variants/pca10056-rc-clock/platformio.ini | 9 -- variants/pca10056-rc-clock/variant.cpp | 42 ------ variants/pca10056-rc-clock/variant.h | 162 ---------------------- 5 files changed, 261 deletions(-) delete mode 100644 boards/nrf52840_dk_modified.json delete mode 100644 variants/pca10056-rc-clock/platformio.ini delete mode 100644 variants/pca10056-rc-clock/variant.cpp delete mode 100644 variants/pca10056-rc-clock/variant.h diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json deleted file mode 100644 index 2932cb4b9..000000000 --- a/boards/nrf52840_dk_modified.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52840_s113_v7.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", - "f_cpu": "64000000L", - "hwids": [["0x239A", "0x4404"]], - "usb_product": "nrf52840dk", - "mcu": "nrf52840", - "variant": "pca10056-rc-clock", - "variants_dir": "variants", - "bsp": { - "name": "adafruit" - }, - "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" - }, - "bootloader": { - "settings_addr": "0xFF000" - } - }, - "connectivity": ["bluetooth"], - "debug": { - "jlink_device": "nRF52840_xxAA", - "onboard_tools": ["jlink"], - "svd_path": "nrf52840.svd", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino"], - "name": "A modified NRF52840-DK devboard (Adafruit BSP)", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "require_upload_port": true, - "speed": 115200, - "protocol": "jlink", - "protocols": ["jlink", "nrfjprog", "stlink"] - }, - "url": "https://meshtastic.org/", - "vendor": "Nordic Semi" -} diff --git a/platformio.ini b/platformio.ini index d4cd89631..5dcf61afd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,6 @@ default_envs = tbeam ;default_envs = tlora-t3s3-v1 ;default_envs = t-echo ;default_envs = canaryone -;default_envs = nrf52840dk-geeksville ;default_envs = native ;default_envs = nano-g1 ;default_envs = pca10059_diy_eink diff --git a/variants/pca10056-rc-clock/platformio.ini b/variants/pca10056-rc-clock/platformio.ini deleted file mode 100644 index f8cff4d73..000000000 --- a/variants/pca10056-rc-clock/platformio.ini +++ /dev/null @@ -1,9 +0,0 @@ -; The NRF52840-dk development board, but @geeksville's board - which has a busted oscilliator -[env:nrf52840dk-geeksville] -board_level = extra -extends = nrf52840_base -board = nrf52840_dk_modified -# add our variants files to the include and src paths -build_flags = ${nrf52_base.build_flags} -Ivariants/pca10056-rc-clock - -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/pca10056-rc-clock> \ No newline at end of file diff --git a/variants/pca10056-rc-clock/variant.cpp b/variants/pca10056-rc-clock/variant.cpp deleted file mode 100644 index a1882a33f..000000000 --- a/variants/pca10056-rc-clock/variant.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // P0 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - ; -} diff --git a/variants/pca10056-rc-clock/variant.h b/variants/pca10056-rc-clock/variant.h deleted file mode 100644 index 032e1de2b..000000000 --- a/variants/pca10056-rc-clock/variant.h +++ /dev/null @@ -1,162 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library 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 Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _VARIANT_PCA10056_ -#define _VARIANT_PCA10056_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so -// he has to use a RC clock. - -// #define USE_LFXO // Board uses 32khz crystal for LF -#define USE_LFRC // Board uses RC for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (6) -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (13) -#define PIN_LED2 (14) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_RED PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 0 // State when LED is litted - -/* - * Buttons - */ -#define PIN_BUTTON1 11 -#define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 - -/* - * Analog pins - */ -#define PIN_A0 (3) -#define PIN_A1 (4) -#define PIN_A2 (28) -#define PIN_A3 (29) -#define PIN_A4 (30) -#define PIN_A5 (31) -#define PIN_A6 (0xff) -#define PIN_A7 (0xff) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF (2) -#define PIN_NFC1 (9) -#define PIN_NFC2 (10) - -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ - -// Arduino Header D0, D1 -#define PIN_SERIAL1_RX (33) // P1.01 -#define PIN_SERIAL1_TX (34) // P1.02 - -// Connected to Jlink CDC -#define PIN_SERIAL2_RX (8) -#define PIN_SERIAL2_TX (6) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (46) -#define PIN_SPI_MOSI (45) -#define PIN_SPI_SCK (47) - -static const uint8_t SS = 44; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) - -// QSPI Pins -#define PIN_QSPI_SCK 19 -#define PIN_QSPI_CS 17 -#define PIN_QSPI_IO0 20 -#define PIN_QSPI_IO1 21 -#define PIN_QSPI_IO2 22 -#define PIN_QSPI_IO3 23 - -// On-board QSPI Flash -#define EXTERNAL_FLASH_DEVICES MX25R6435F -#define EXTERNAL_FLASH_USE_QSPI - -// CUSTOM GPIOs the SX1262MB2CAS shield when installed on the NRF52840-DK development board -#define USE_SX1262 -#define SX126X_CS (32 + 8) // P1.08 -#define SX126X_DIO1 (32 + 6) // P1.06 -#define SX126X_BUSY (32 + 4) // P1.04 -#define SX126X_RESET (0 + 3) // P0.03 -#define SX126X_ANT_SW (32 + 10) // P1.10 -#define SX126X_DIO2_AS_RF_SWITCH - -// To debug via the segger JLINK console rather than the CDC-ACM serial device -// #define USE_SEGGER - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif From dac433ed2feb9eabcffd7297dc5f32c30db8c60f Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:27:15 +0800 Subject: [PATCH 1351/1377] Remove rak4631_epaper_onrxtx variant (#4958) Appears to be a testing variant of rak4631_epaper. Due to little information available, let's remove it for now. --- variants/rak4631_epaper_onrxtx/platformio.ini | 25 --- variants/rak4631_epaper_onrxtx/variant.cpp | 45 ---- variants/rak4631_epaper_onrxtx/variant.h | 205 ------------------ 3 files changed, 275 deletions(-) delete mode 100644 variants/rak4631_epaper_onrxtx/platformio.ini delete mode 100644 variants/rak4631_epaper_onrxtx/variant.cpp delete mode 100644 variants/rak4631_epaper_onrxtx/variant.h diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini deleted file mode 100644 index 8c1b8eee8..000000000 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ /dev/null @@ -1,25 +0,0 @@ -; The very slick RAK wireless RAK 4631 / 4630 board - Firmware for 5005 with the RAK 14000 ePaper -[env:rak4631_eink_onrxtx] -board_level = extra -extends = nrf52840_base -board = wiscore_rak4631 -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 - -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" - -D PIN_EINK_EN=34 - -D EINK_DISPLAY_MODEL=GxEPD2_213_BN - -D EINK_WIDTH=250 - -D EINK_HEIGHT=122 - -D RADIOLIB_EXCLUDE_SX128X=1 - -D RADIOLIB_EXCLUDE_SX127X=1 - -D RADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> -lib_deps = - ${nrf52840_base.lib_deps} - zinggjm/GxEPD2@^1.5.1 - melopero/Melopero RV3028@^1.1.0 - rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 - beegee-tokyo/RAKwireless RAK12034@^1.0.0 -debug_tool = jlink -; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -;upload_protocol = jlink -;upload_port = /dev/ttyACM3 \ No newline at end of file diff --git a/variants/rak4631_epaper_onrxtx/variant.cpp b/variants/rak4631_epaper_onrxtx/variant.cpp deleted file mode 100644 index e84b60b3b..000000000 --- a/variants/rak4631_epaper_onrxtx/variant.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // P0 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - -void initVariant() -{ - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - - // 3V3 Power Rail - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); -} diff --git a/variants/rak4631_epaper_onrxtx/variant.h b/variants/rak4631_epaper_onrxtx/variant.h deleted file mode 100644 index 5888cff33..000000000 --- a/variants/rak4631_epaper_onrxtx/variant.h +++ /dev/null @@ -1,205 +0,0 @@ -#ifndef _VARIANT_RAK4630_ -#define _VARIANT_RAK4630_ - -#define RAK4630 - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF -// define USE_LFRC // Board uses RC for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (6) -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ - -#define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion -#define BUTTON_NEED_PULLUP -// #define PIN_BUTTON2 12 - -/* - * Analog pins - */ -#define PIN_A0 (-1) //(5) -#define PIN_A1 (31) -#define PIN_A2 (28) -#define PIN_A3 (29) -#define PIN_A4 (30) -#define PIN_A5 (31) -#define PIN_A6 (0xff) -#define PIN_A7 (0xff) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF (2) -// #define PIN_NFC1 (9) -// #define PIN_NFC2 (10) - -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) - -// Connected to Jlink CDC -#define PIN_SERIAL2_RX (-1) -#define PIN_SERIAL2_TX (-1) - -// Testing USB detection -#define NRF_APM - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -#define PIN_SPI_MISO (45) -#define PIN_SPI_MOSI (44) -#define PIN_SPI_SCK (43) - -#define PIN_SPI1_MISO (-1) -#define PIN_SPI1_MOSI (0 + 13) -#define PIN_SPI1_SCK (0 + 14) - -static const uint8_t SS = 42; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * eink display pins - */ - -#define USE_EINK - -#define PIN_EINK_CS (0 + 16) // TX1 -#define PIN_EINK_BUSY (0 + 15) // RX1 -#define PIN_EINK_DC (0 + 17) // IO1 -// #define PIN_EINK_RES (-1) //first try without RESET then connect it to AIN (AIN0 5 ) -#define PIN_EINK_RES (0 + 5) // 2.13 BN Display needs RESET -#define PIN_EINK_SCLK (0 + 14) // SCL -#define PIN_EINK_MOSI (0 + 13) // SDA - -// RAKRGB -#define HAS_NCP5623 - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (13) -#define PIN_WIRE_SCL (14) - -/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports - RAK5005-O <-> nRF52840 - IO1 <-> P0.17 (Arduino GPIO number 17) - IO2 <-> P1.02 (Arduino GPIO number 34) - IO3 <-> P0.21 (Arduino GPIO number 21) - IO4 <-> P0.04 (Arduino GPIO number 4) - IO5 <-> P0.09 (Arduino GPIO number 9) - IO6 <-> P0.10 (Arduino GPIO number 10) - IO7 <-> P0.28 (Arduino GPIO number 28) - SW1 <-> P0.01 (Arduino GPIO number 1) - A0 <-> P0.04/AIN2 (Arduino Analog A2 - A1 <-> P0.31/AIN7 (Arduino Analog A7 - SPI_CS <-> P0.26 (Arduino GPIO number 26) - */ - -// RAK4630 LoRa module -#define USE_SX1262 -#define SX126X_CS (42) -#define SX126X_DIO1 (47) -#define SX126X_BUSY (46) -#define SX126X_RESET (38) -// #define SX126X_TXEN (39) -// #define SX126X_RXEN (37) -#define SX126X_POWER_EN (37) -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// enables 3.3V periphery like GPS or IO Module -#define PIN_3V3_EN (34) - -// NO GPS -#undef GPS_RX_PIN -#undef GPS_TX_PIN - -// RAK1910 GPS module -// If using the wisblock GPS module and pluged into Port A on WisBlock base -// IO1 is hooked to PPS (pin 12 on header) = gpio 17 -// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). -// Therefore must be 1 to keep peripherals powered -// Power is on the controllable 3V3_S rail -// #define PIN_GPS_RESET (34) -// #define PIN_GPS_EN PIN_3V3_EN -// #define PIN_GPS_PPS (17) // Pulse per second input from the GPS - -// #define GPS_RX_PIN PIN_SERIAL1_RX -// #define GPS_TX_PIN PIN_SERIAL1_TX - -// RAK12002 RTC Module -#define RV3028_RTC (uint8_t)0b1010010 - -// Battery -// The battery sense is hooked to pin A0 (5) -// #define BATTERY_PIN PIN_A0 -// and has 12 bit resolution -// #define BATTERY_SENSE_RESOLUTION_BITS 12 -// #define BATTERY_SENSE_RESOLUTION 4096.0 -// #undef AREF_VOLTAGE -// #define AREF_VOLTAGE 3.0 -// #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -// #define ADC_MULTIPLIER 1.73 - -// #define HAS_RTC 1 - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file From 0c90a2274f95c5a434454a856de519dcb562454b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 18:39:13 +0800 Subject: [PATCH 1352/1377] Remove unused headers (#4954) These files had existing since 2020 without being used/modified. --- src/platform/esp32/CallbackCharacteristic.h | 12 ------------ src/platform/nrf52/pgmspace.h | 5 ----- 2 files changed, 17 deletions(-) delete mode 100644 src/platform/esp32/CallbackCharacteristic.h delete mode 100644 src/platform/nrf52/pgmspace.h diff --git a/src/platform/esp32/CallbackCharacteristic.h b/src/platform/esp32/CallbackCharacteristic.h deleted file mode 100644 index cd3bc6f51..000000000 --- a/src/platform/esp32/CallbackCharacteristic.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "BLECharacteristic.h" -#include "PowerFSM.h" // FIXME - someday I want to make this OTA thing a separate lb at at that point it can't touch this - -/** - * A characteristic with a set of overridable callbacks - */ -class CallbackCharacteristic : public BLECharacteristic, public BLECharacteristicCallbacks -{ - public: - CallbackCharacteristic(const char *uuid, uint32_t btprops) : BLECharacteristic(uuid, btprops) { setCallbacks(this); } -}; diff --git a/src/platform/nrf52/pgmspace.h b/src/platform/nrf52/pgmspace.h deleted file mode 100644 index 5ad8035be..000000000 --- a/src/platform/nrf52/pgmspace.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -// dummy file to keep old arduino code happy -#define PROGMEM -#define pgm_read_byte(addr) (*((unsigned const char *)addr)) \ No newline at end of file From d650001caac2a1fb68c738e20613375af9eef349 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 5 Oct 2024 08:05:44 -0500 Subject: [PATCH 1353/1377] [create-pull-request] automated change (#4960) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index f6a385c9f..df7ba70c7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 5 +build = 6 From a6f96cb9b4b2bc95e406d0d527e88d37d761fbae Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 5 Oct 2024 22:27:10 +0800 Subject: [PATCH 1354/1377] Revert "Remove rak4631_epaper_onrxtx variant (#4958)" (#4963) This reverts commit dac433ed2feb9eabcffd7297dc5f32c30db8c60f. --- variants/rak4631_epaper_onrxtx/platformio.ini | 25 +++ variants/rak4631_epaper_onrxtx/variant.cpp | 45 ++++ variants/rak4631_epaper_onrxtx/variant.h | 205 ++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 variants/rak4631_epaper_onrxtx/platformio.ini create mode 100644 variants/rak4631_epaper_onrxtx/variant.cpp create mode 100644 variants/rak4631_epaper_onrxtx/variant.h diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini new file mode 100644 index 000000000..8c1b8eee8 --- /dev/null +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -0,0 +1,25 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Firmware for 5005 with the RAK 14000 ePaper +[env:rak4631_eink_onrxtx] +board_level = extra +extends = nrf52840_base +board = wiscore_rak4631 +build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -D PIN_EINK_EN=34 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 + -D RADIOLIB_EXCLUDE_SX128X=1 + -D RADIOLIB_EXCLUDE_SX127X=1 + -D RADIOLIB_EXCLUDE_LR11X0=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> +lib_deps = + ${nrf52840_base.lib_deps} + zinggjm/GxEPD2@^1.5.1 + melopero/Melopero RV3028@^1.1.0 + rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + beegee-tokyo/RAKwireless RAK12034@^1.0.0 +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink +;upload_port = /dev/ttyACM3 \ No newline at end of file diff --git a/variants/rak4631_epaper_onrxtx/variant.cpp b/variants/rak4631_epaper_onrxtx/variant.cpp new file mode 100644 index 000000000..e84b60b3b --- /dev/null +++ b/variants/rak4631_epaper_onrxtx/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/rak4631_epaper_onrxtx/variant.h b/variants/rak4631_epaper_onrxtx/variant.h new file mode 100644 index 000000000..5888cff33 --- /dev/null +++ b/variants/rak4631_epaper_onrxtx/variant.h @@ -0,0 +1,205 @@ +#ifndef _VARIANT_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ + +#define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion +#define BUTTON_NEED_PULLUP +// #define PIN_BUTTON2 12 + +/* + * Analog pins + */ +#define PIN_A0 (-1) //(5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +// #define PIN_NFC1 (9) +// #define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (-1) +#define PIN_SERIAL1_TX (-1) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) + +// Testing USB detection +#define NRF_APM + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MOSI (0 + 13) +#define PIN_SPI1_SCK (0 + 14) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ + +#define USE_EINK + +#define PIN_EINK_CS (0 + 16) // TX1 +#define PIN_EINK_BUSY (0 + 15) // RX1 +#define PIN_EINK_DC (0 + 17) // IO1 +// #define PIN_EINK_RES (-1) //first try without RESET then connect it to AIN (AIN0 5 ) +#define PIN_EINK_RES (0 + 5) // 2.13 BN Display needs RESET +#define PIN_EINK_SCLK (0 + 14) // SCL +#define PIN_EINK_MOSI (0 + 13) // SDA + +// RAKRGB +#define HAS_NCP5623 + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ + +// RAK4630 LoRa module +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +// #define SX126X_TXEN (39) +// #define SX126X_RXEN (37) +#define SX126X_POWER_EN (37) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// enables 3.3V periphery like GPS or IO Module +#define PIN_3V3_EN (34) + +// NO GPS +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +// #define PIN_GPS_RESET (34) +// #define PIN_GPS_EN PIN_3V3_EN +// #define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +// #define GPS_RX_PIN PIN_SERIAL1_RX +// #define GPS_TX_PIN PIN_SERIAL1_TX + +// RAK12002 RTC Module +#define RV3028_RTC (uint8_t)0b1010010 + +// Battery +// The battery sense is hooked to pin A0 (5) +// #define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +// #define BATTERY_SENSE_RESOLUTION_BITS 12 +// #define BATTERY_SENSE_RESOLUTION 4096.0 +// #undef AREF_VOLTAGE +// #define AREF_VOLTAGE 3.0 +// #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +// #define ADC_MULTIPLIER 1.73 + +// #define HAS_RTC 1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file From 8a370c538149f5af659573963cd2f59fa669661e Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 6 Oct 2024 08:34:51 +0800 Subject: [PATCH 1355/1377] Revert "Revert "Remove unused Jlink monitoring files (#4953)" (#4959)" (#4966) This reverts commit 783466f1165aeddd41ab1b1b76ef70aa2908c3b1. --- src/platform/nrf52/JLINK_MONITOR.c | 124 --- src/platform/nrf52/JLINK_MONITOR.h | 27 - src/platform/nrf52/JLINK_MONITOR_ISR_SES.S | 888 --------------------- 3 files changed, 1039 deletions(-) delete mode 100644 src/platform/nrf52/JLINK_MONITOR.c delete mode 100644 src/platform/nrf52/JLINK_MONITOR.h delete mode 100644 src/platform/nrf52/JLINK_MONITOR_ISR_SES.S diff --git a/src/platform/nrf52/JLINK_MONITOR.c b/src/platform/nrf52/JLINK_MONITOR.c deleted file mode 100644 index 160264905..000000000 --- a/src/platform/nrf52/JLINK_MONITOR.c +++ /dev/null @@ -1,124 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH & Co. KG * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ----------------------------------------------------------------------- -File : JLINK_MONITOR.c -Purpose : Implementation of debug monitor for J-Link monitor mode debug on Cortex-M devices. --------- END-OF-HEADER --------------------------------------------- -*/ - -#include "JLINK_MONITOR.h" - -/********************************************************************* - * - * Configuration - * - ********************************************************************** - */ - -/********************************************************************* - * - * Defines - * - ********************************************************************** - */ - -/********************************************************************* - * - * Types - * - ********************************************************************** - */ - -/********************************************************************* - * - * Static data - * - ********************************************************************** - */ - -volatile int MAIN_MonCnt; // Incremented in JLINK_MONITOR_OnPoll() while CPU is in debug mode - -/********************************************************************* - * - * Local functions - * - ********************************************************************** - */ - -/********************************************************************* - * - * Global functions - * - ********************************************************************** - */ - -/********************************************************************* - * - * JLINK_MONITOR_OnExit() - * - * Function description - * Called from DebugMon_Handler(), once per debug exit. - * May perform some target specific operations to be done on debug mode exit. - * - * Notes - * (1) Must not keep the CPU busy for more than 100 ms - */ -void JLINK_MONITOR_OnExit(void) -{ - // - // Add custom code here - // - // BSP_ClrLED(0); -} - -/********************************************************************* - * - * JLINK_MONITOR_OnEnter() - * - * Function description - * Called from DebugMon_Handler(), once per debug entry. - * May perform some target specific operations to be done on debug mode entry - * - * Notes - * (1) Must not keep the CPU busy for more than 100 ms - */ -void JLINK_MONITOR_OnEnter(void) -{ - // - // Add custom code here - // - // BSP_SetLED(0); - // BSP_ClrLED(1); -} - -/********************************************************************* - * - * JLINK_MONITOR_OnPoll() - * - * Function description - * Called periodically from DebugMon_Handler(), to perform some actions that need to be performed periodically during debug - * mode. - * - * Notes - * (1) Must not keep the CPU busy for more than 100 ms - */ -void JLINK_MONITOR_OnPoll(void) -{ - // - // Add custom code here - // - MAIN_MonCnt++; - // BSP_ToggleLED(0); - // _Delay(500000); -} - -/****** End Of File *************************************************/ diff --git a/src/platform/nrf52/JLINK_MONITOR.h b/src/platform/nrf52/JLINK_MONITOR.h deleted file mode 100644 index 87cf332fe..000000000 --- a/src/platform/nrf52/JLINK_MONITOR.h +++ /dev/null @@ -1,27 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH & Co. KG * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ----------------------------------------------------------------------- -File : JLINK_MONITOR.h -Purpose : Header file of debug monitor for J-Link monitor mode debug on Cortex-M devices. --------- END-OF-HEADER --------------------------------------------- -*/ - -#ifndef JLINK_MONITOR_H -#define JLINK_MONITOR_H - -void JLINK_MONITOR_OnExit(void); -void JLINK_MONITOR_OnEnter(void); -void JLINK_MONITOR_OnPoll(void); - -#endif - -/****** End Of File *************************************************/ diff --git a/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S b/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S deleted file mode 100644 index cda4b1a50..000000000 --- a/src/platform/nrf52/JLINK_MONITOR_ISR_SES.S +++ /dev/null @@ -1,888 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH & Co. KG * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ----------------------------------------------------------------------- -File : JLINK_MONITOR_ISR_SES.s -Purpose : Implementation of debug monitor for J-Link monitor mode - debug on Cortex-M devices, supporting SES compiler. --------- END-OF-HEADER --------------------------------------------- -*/ - - .name JLINK_MONITOR_ISR - .syntax unified - - .extern JLINK_MONITOR_OnEnter - .extern JLINK_MONITOR_OnExit - .extern JLINK_MONITOR_OnPoll - - .global DebugMon_Handler - -/********************************************************************* -* -* Defines, configurable -* -********************************************************************** -*/ - -#define _MON_VERSION 100 // V x.yy - -/********************************************************************* -* -* Defines, fixed -* -********************************************************************** -*/ - -#define _APP_SP_OFF_R0 0x00 -#define _APP_SP_OFF_R1 0x04 -#define _APP_SP_OFF_R2 0x08 -#define _APP_SP_OFF_R3 0x0C -#define _APP_SP_OFF_R12 0x10 -#define _APP_SP_OFF_R14_LR 0x14 -#define _APP_SP_OFF_PC 0x18 -#define _APP_SP_OFF_XPSR 0x1C -#define _APP_SP_OFF_S0 0x20 -#define _APP_SP_OFF_S1 0x24 -#define _APP_SP_OFF_S2 0x28 -#define _APP_SP_OFF_S3 0x2C -#define _APP_SP_OFF_S4 0x30 -#define _APP_SP_OFF_S5 0x34 -#define _APP_SP_OFF_S6 0x38 -#define _APP_SP_OFF_S7 0x3C -#define _APP_SP_OFF_S8 0x40 -#define _APP_SP_OFF_S9 0x44 -#define _APP_SP_OFF_S10 0x48 -#define _APP_SP_OFF_S11 0x4C -#define _APP_SP_OFF_S12 0x50 -#define _APP_SP_OFF_S13 0x54 -#define _APP_SP_OFF_S14 0x58 -#define _APP_SP_OFF_S15 0x5C -#define _APP_SP_OFF_FPSCR 0x60 - -#define _NUM_BYTES_BASIC_STACKFRAME 32 -#define _NUM_BYTES_EXTENDED_STACKFRAME 72 - -#define _SYSTEM_DCRDR_OFF 0x00 -#define _SYSTEM_DEMCR_OFF 0x04 - -#define _SYSTEM_DHCSR 0xE000EDF0 // Debug Halting Control and Status Register (DHCSR) -#define _SYSTEM_DCRSR 0xE000EDF4 // Debug Core Register Selector Register (DCRSR) -#define _SYSTEM_DCRDR 0xE000EDF8 // Debug Core Register Data Register (DCRDR) -#define _SYSTEM_DEMCR 0xE000EDFC // Debug Exception and Monitor Control Register (DEMCR) - -#define _SYSTEM_FPCCR 0xE000EF34 // Floating-Point Context Control Register (FPCCR) -#define _SYSTEM_FPCAR 0xE000EF38 // Floating-Point Context Address Register (FPCAR) -#define _SYSTEM_FPDSCR 0xE000EF3C // Floating-Point Default Status Control Register (FPDSCR) -#define _SYSTEM_MVFR0 0xE000EF40 // Media and FP Feature Register 0 (MVFR0) -#define _SYSTEM_MVFR1 0xE000EF44 // Media and FP Feature Register 1 (MVFR1) - -/* -* Defines for determining if the current debug config supports FPU registers -* For some compilers like IAR EWARM when disabling the FPU in the compiler settings an error is thrown when -*/ -#ifdef __FPU_PRESENT - #if __FPU_PRESENT - #define _HAS_FPU_REGS 1 - #else - #define _HAS_FPU_REGS 0 - #endif -#else - #define _HAS_FPU_REGS 0 -#endif - -/********************************************************************* -* -* Signature of monitor -* -* Function description -* Needed for targets where also a boot ROM is present that possibly specifies a vector table with a valid debug monitor exception entry -*/ - .section .text, "ax" - - // - // JLINKMONHANDLER - // - .byte 0x4A - .byte 0x4C - .byte 0x49 - .byte 0x4E - .byte 0x4B - .byte 0x4D - .byte 0x4F - .byte 0x4E - .byte 0x48 - .byte 0x41 - .byte 0x4E - .byte 0x44 - .byte 0x4C - .byte 0x45 - .byte 0x52 - .byte 0x00 // Align to 8-bytes - -/********************************************************************* -* -* DebugMon_Handler() -* -* Function description -* Debug monitor handler. CPU enters this handler in case a "halt" request is made from the debugger. -* This handler is also responsible for handling commands that are sent by the debugger. -* -* Notes -* This is actually the ISR for the debug interrupt (exception no. 12) -*/ - .thumb_func - -DebugMon_Handler: - /* - General procedure: - DCRDR is used as communication register - DEMCR[19] is used as ready flag - For the command J-Link sends to the monitor: DCRDR[7:0] == Cmd, DCRDR[31:8] == ParamData - - 1) Monitor sets DEMCR[19] whenever it is ready to receive new commands/data - DEMCR[19] is initially set on debug monitor entry - 2) J-Link will clear once it has placed conmmand/data in DCRDR for J-Link - 3) Monitor will wait for DEMCR[19] to be cleared - 4) Monitor will process command (May cause additional data transfers etc., depends on command - 5) No restart-CPU command? => Back to 2), Otherwise => 6) - 6) Monitor will clear DEMCR[19] 19 to indicate that it is no longer ready - */ - PUSH {LR} - BL JLINK_MONITOR_OnEnter - POP {LR} - LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR - B.N _IndicateMonReady -_WaitProbeReadIndicateMonRdy: // while(_SYSTEM_DEMCR & (1uL << 19)); => Wait until J-Link has read item - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR - LSLS R0,R0,#+12 - BMI.N _WaitProbeReadIndicateMonRdy -_IndicateMonReady: - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] - /* - During command loop: - R0 = Tmp - R1 = Tmp - R2 = Tmp - R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) - R12 = Tmp - - Outside command loop R0-R3 and R12 may be overwritten by MONITOR_OnPoll() - */ -_WaitForJLinkCmd: // do { - PUSH {LR} - BL JLINK_MONITOR_OnPoll - POP {LR} - LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] - LSRS R0,R0,#+20 // DEMCR[19] -> Carry Clear? => J-Link has placed command for us - BCS _WaitForJLinkCmd - /* - Perform command - Command is placed by J-Link in DCRDR[7:0] and additional parameter data is stored in DCRDR[31:8] - J-Link clears DEMCR[19] to indicate that it placed a command/data or read data - Monitor sets DEMCR[19] to indicate that it placed data or read data / is ready for a new command - Setting DEMCR[19] indicates "monitor ready for new command / data" and also indicates: "data has been placed in DCRDR by monitor, for J-Link" - Therefore it is responsibility of the commands to respond to the commands accordingly - - Commands for debug monitor - Commands must not exceed 0xFF (255) as we only defined 8-bits for command-part. Higher 24-bits are parameter info for current command - - Protocol for different commands: - J-Link: Cmd -> DCRDR, DEMCR[19] -> 0 => Cmd placed by probe - */ - LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // ParamInfo = _SYSTEM_DCRDR - LSRS R1,R0,#+8 // ParamInfo >>= 8 - LSLS R0,R0,#+24 - LSRS R0,R0,#+24 // Cmd = ParamInfo & 0xFF - // - // switch (Cmd) - // - CMP R0,#+0 - BEQ.N _HandleGetMonVersion // case _MON_CMD_GET_MONITOR_VERSION - CMP R0,#+2 - BEQ.N _HandleReadReg // case _MON_CMD_READ_REG - BCC.N _HandleRestartCPU // case _MON_CMD_RESTART_CPU - CMP R0,#+3 - BEQ.N _HandleWriteReg_Veneer // case _MON_CMD_WRITE_REG - B.N _IndicateMonReady // default : while (1); - /* - Return - _MON_CMD_RESTART_CPU - CPU: DEMCR[19] -> 0 => Monitor no longer ready - */ -_HandleRestartCPU: - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR &= ~(1uL << 19); => Clear MON_REQ to indicate that monitor is no longer active - BIC R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] - PUSH {LR} - BL JLINK_MONITOR_OnExit - POP {PC} - // - // Place data section here to not get in trouble with load-offsets - // - .section .text, "ax", %progbits - .align 2 -_AddrDCRDR: - .long 0xE000EDF8 -_AddrCPACR: - .long 0xE000ED88 - - .section .text, "ax" - .thumb_func - -;/********************************************************************* -;* -;* _HandleGetMonVersion -;* -;*/ -_HandleGetMonVersion: - /* - _MON_CMD_GET_MONITOR_VERSION - CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready - J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read - CPU: DEMCR[19] -> 1 => Mon ready - */ - MOVS R0,#+_MON_VERSION - STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // _SYSTEM_DCRDR = x - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready - B _WaitProbeReadIndicateMonRdy - -/********************************************************************* -* -* _HandleReadReg -* -*/ -_HandleWriteReg_Veneer: - B.N _HandleWriteReg -_HandleReadReg: - /* - _MON_CMD_READ_REG - CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready - J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read - CPU: DEMCR[19] -> 1 => Mon ready - - - Register indexes - 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) - 16: XPSR - 17: MSP - 18: PSP - 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - 20: FPSCR - 21-52: FPS0-FPS31 - - - Register usage when entering this "subroutine": - R0 Cmd - R1 ParamInfo - R2 --- - R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) - R12 --- - - Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension - LR Return to Return SP Frame type - --------------------------------------------------------- - 0xFFFFFFE1 Handler mode. MSP Extended - 0xFFFFFFE9 Thread mode MSP Extended - 0xFFFFFFED Thread mode PSP Extended - 0xFFFFFFF1 Handler mode. MSP Basic - 0xFFFFFFF9 Thread mode MSP Basic - 0xFFFFFFFD Thread mode PSP Basic - - So LR[2] == 1 => Return stack == PSP else MSP - - R0-R3, R12, PC, xPSR can be read from application stackpointer - Other regs can be read directly - */ - LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP - ITE CS - MRSCS R2,PSP - MRSCC R2,MSP - CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) - BCS _HandleReadRegR4 - LDR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) - B.N _HandleReadRegDone -_HandleReadRegR4: - CMP R1,#+5 // if (RegIndex < 5) { (R4) - BCS _HandleReadRegR5 - MOV R0,R4 - B.N _HandleReadRegDone -_HandleReadRegR5: - CMP R1,#+6 // if (RegIndex < 6) { (R5) - BCS _HandleReadRegR6 - MOV R0,R5 - B.N _HandleReadRegDone -_HandleReadRegR6: - CMP R1,#+7 // if (RegIndex < 7) { (R6) - BCS _HandleReadRegR7 - MOV R0,R6 - B.N _HandleReadRegDone -_HandleReadRegR7: - CMP R1,#+8 // if (RegIndex < 8) { (R7) - BCS _HandleReadRegR8 - MOV R0,R7 - B.N _HandleReadRegDone -_HandleReadRegR8: - CMP R1,#+9 // if (RegIndex < 9) { (R8) - BCS _HandleReadRegR9 - MOV R0,R8 - B.N _HandleReadRegDone -_HandleReadRegR9: - CMP R1,#+10 // if (RegIndex < 10) { (R9) - BCS _HandleReadRegR10 - MOV R0,R9 - B.N _HandleReadRegDone -_HandleReadRegR10: - CMP R1,#+11 // if (RegIndex < 11) { (R10) - BCS _HandleReadRegR11 - MOV R0,R10 - B.N _HandleReadRegDone -_HandleReadRegR11: - CMP R1,#+12 // if (RegIndex < 12) { (R11) - BCS _HandleReadRegR12 - MOV R0,R11 - B.N _HandleReadRegDone -_HandleReadRegR12: - CMP R1,#+14 // if (RegIndex < 14) { (R12) - BCS _HandleReadRegR14 - LDR R0,[R2, #+_APP_SP_OFF_R12] - B.N _HandleReadRegDone -_HandleReadRegR14: - CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) - BCS _HandleReadRegR15 - LDR R0,[R2, #+_APP_SP_OFF_R14_LR] - B.N _HandleReadRegDone -_HandleReadRegR15: - CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) - BCS _HandleReadRegXPSR - LDR R0,[R2, #+_APP_SP_OFF_PC] - B.N _HandleReadRegDone -_HandleReadRegXPSR: - CMP R1,#+17 // if (RegIndex < 17) { (xPSR) - BCS _HandleReadRegMSP - LDR R0,[R2, #+_APP_SP_OFF_XPSR] - B.N _HandleReadRegDone -_HandleReadRegMSP: - /* - Stackpointer is tricky because we need to get some info about the SP used in the user app, first - - Handle reading R0-R3 which can be read right from application stackpointer - - Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension - LR Return to Return SP Frame type - --------------------------------------------------------- - 0xFFFFFFE1 Handler mode. MSP Extended - 0xFFFFFFE9 Thread mode MSP Extended - 0xFFFFFFED Thread mode PSP Extended - 0xFFFFFFF1 Handler mode. MSP Basic - 0xFFFFFFF9 Thread mode MSP Basic - 0xFFFFFFFD Thread mode PSP Basic - - So LR[2] == 1 => Return stack == PSP else MSP - Per architecture definition: Inside monitor (exception) SP = MSP - - Stack pointer handling is complicated because it is different what is pushed on the stack before entering the monitor ISR... - Cortex-M: 8 regs - Cortex-M + forced-stack-alignment: 8 regs + 1 dummy-word if stack was not 8-byte aligned - Cortex-M + FPU: 8 regs + 17 FPU regs + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned - Cortex-M + FPU + lazy mode: 8 regs + 17 dummy-words + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned - */ - CMP R1,#+18 // if (RegIndex < 18) { (MSP) - BCS _HandleReadRegPSP - MRS R0,MSP - LSRS R1,LR,#+3 // LR[2] -> Carry == 0 => CPU was running on MSP => Needs correction - BCS _HandleReadRegDone_Veneer // CPU was running on PSP? => No correction necessary -_HandleSPCorrection: - LSRS R1,LR,#+5 // LR[4] -> Carry == 0 => extended stack frame has been allocated. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry - ITE CS - ADDCS R0,R0,#+_NUM_BYTES_BASIC_STACKFRAME - ADDCC R0,R0,#+_NUM_BYTES_EXTENDED_STACKFRAME - LDR R1,[R2, #+_APP_SP_OFF_XPSR] // Get xPSR from application stack (R2 has been set to app stack on beginning of _HandleReadReg) - LSRS R1,R1,#+5 // xPSR[9] -> Carry == 1 => Stack has been force-aligned before pushing regs. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry - IT CS - ADDCS R0,R0,#+4 - B _HandleReadRegDone -_HandleReadRegPSP: // RegIndex == 18 - CMP R1,#+19 // if (RegIndex < 19) { - BCS _HandleReadRegCFBP - MRS R0,PSP // PSP is not touched by monitor - LSRS R1,LR,#+3 // LR[2] -> Carry == 1 => CPU was running on PSP => Needs correction - BCC _HandleReadRegDone_Veneer // CPU was running on MSP? => No correction of PSP necessary - B _HandleSPCorrection -_HandleReadRegCFBP: - /* - CFBP is a register that can only be read via debug probe and is a merger of the following regs: - CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode - */ - CMP R1,#+20 // if (RegIndex < 20) { (CFBP) - BCS _HandleReadRegFPU - MOVS R0,#+0 - MRS R2,PRIMASK - ORRS R0,R2 // Merge PRIMASK into CFBP[7:0] - MRS R2,BASEPRI - LSLS R2,R2,#+8 // Merge BASEPRI into CFBP[15:8] - ORRS R0,R2 - MRS R2,FAULTMASK - LSLS R2,R2,#+16 // Merge FAULTMASK into CFBP[23:16] - ORRS R0,R2 - MRS R2,CONTROL - LSRS R1,LR,#3 // LR[2] -> Carry. CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior - IT CS // As J-Link sees value of CONTROL at application time, we need reconstruct original value of CONTROL - ORRCS R2,R2,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor - LSRS R1,LR,#+5 // LR[4] == NOT(CONTROL.FPCA) -> Carry - ITE CS // Merge original value of FPCA (CONTROL[2]) into read data - BICCS R2,R2,#+0x04 // Remember LR contains NOT(CONTROL) - ORRCC R2,R2,#+0x04 - LSLS R2,R2,#+24 - ORRS R0,R2 - B.N _HandleReadRegDone -_HandleReadRegFPU: -#if _HAS_FPU_REGS - CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) - BCS _HandleReadRegDone_Veneer - /* - Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled - If not, access to floating point is not possible - CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - */ - LDR R0,_AddrCPACR - LDR R0,[R0] - LSLS R0,R0,#+8 - LSRS R0,R0,#+28 - CMP R0,#+0xF - BEQ _HandleReadRegFPU_Allowed - CMP R0,#+0x5 - BNE _HandleReadRegDone_Veneer -_HandleReadRegFPU_Allowed: - CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) - BCS _HandleReadRegFPS0_FPS31 - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleReadFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleReadFPSCRLazyMode - LDR R0,[R2, #+_APP_SP_OFF_FPSCR] - B _HandleReadRegDone -_HandleReadFPSCRLazyMode: - VMRS R0,FPSCR - B _HandleReadRegDone -_HandleReadRegFPS0_FPS31: // RegIndex == 21-52 - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleReadFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleReadFPS0_FPS31LazyMode - SUBS R1,#+21 // Convert absolute reg index into rel. one - LSLS R1,R1,#+2 // RegIndex to position on stack - ADDS R1,#+_APP_SP_OFF_S0 - LDR R0,[R2, R1] -_HandleReadRegDone_Veneer: - B _HandleReadRegDone -_HandleReadFPS0_FPS31LazyMode: - SUBS R1,#+20 // convert abs. RegIndex into rel. one - MOVS R0,#+6 - MULS R1,R0,R1 - LDR R0,=_HandleReadRegUnknown - SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) - ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr - BX R0 - // - // Table for reading FPS0-FPS31 - // - VMOV R0,S31 // v = FPSx - B _HandleReadRegDone - VMOV R0,S30 - B _HandleReadRegDone - VMOV R0,S29 - B _HandleReadRegDone - VMOV R0,S28 - B _HandleReadRegDone - VMOV R0,S27 - B _HandleReadRegDone - VMOV R0,S26 - B _HandleReadRegDone - VMOV R0,S25 - B _HandleReadRegDone - VMOV R0,S24 - B _HandleReadRegDone - VMOV R0,S23 - B _HandleReadRegDone - VMOV R0,S22 - B _HandleReadRegDone - VMOV R0,S21 - B _HandleReadRegDone - VMOV R0,S20 - B _HandleReadRegDone - VMOV R0,S19 - B _HandleReadRegDone - VMOV R0,S18 - B _HandleReadRegDone - VMOV R0,S17 - B _HandleReadRegDone - VMOV R0,S16 - B _HandleReadRegDone - VMOV R0,S15 - B _HandleReadRegDone - VMOV R0,S14 - B _HandleReadRegDone - VMOV R0,S13 - B _HandleReadRegDone - VMOV R0,S12 - B _HandleReadRegDone - VMOV R0,S11 - B _HandleReadRegDone - VMOV R0,S10 - B _HandleReadRegDone - VMOV R0,S9 - B _HandleReadRegDone - VMOV R0,S8 - B _HandleReadRegDone - VMOV R0,S7 - B _HandleReadRegDone - VMOV R0,S6 - B _HandleReadRegDone - VMOV R0,S5 - B _HandleReadRegDone - VMOV R0,S4 - B _HandleReadRegDone - VMOV R0,S3 - B _HandleReadRegDone - VMOV R0,S2 - B _HandleReadRegDone - VMOV R0,S1 - B _HandleReadRegDone - VMOV R0,S0 - B _HandleReadRegDone -#else - B _HandleReadRegUnknown -_HandleReadRegDone_Veneer: - B _HandleReadRegDone -#endif -_HandleReadRegUnknown: - MOVS R0,#+0 // v = 0 - B.N _HandleReadRegDone -_HandleReadRegDone: - - // Send register content to J-Link and wait until J-Link has read the data - - STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // DCRDR = v; - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready - B _WaitProbeReadIndicateMonRdy - - // Data section for register addresses - -_HandleWriteReg: - /* - _MON_CMD_WRITE_REG - CPU: DEMCR[19] -> 1 => Mon ready - J-Link: Data -> DCRDR, DEMCR[19] -> 0 => Data placed by probe - CPU: DCRDR -> Read, Process command, DEMCR[19] -> 1 => Data read & mon ready - - Register indexes - 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) - 16: XPSR - 17: MSP - 18: PSP - 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - 20: FPSCR - 21-52: FPS0-FPS31 - - - Register usage when entering this "subroutine": - R0 Cmd - R1 ParamInfo - R2 --- - R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) - R12 --- - - Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension - LR Return to Return SP Frame type - --------------------------------------------------------- - 0xFFFFFFE1 Handler mode. MSP Extended - 0xFFFFFFE9 Thread mode MSP Extended - 0xFFFFFFED Thread mode PSP Extended - 0xFFFFFFF1 Handler mode. MSP Basic - 0xFFFFFFF9 Thread mode MSP Basic - 0xFFFFFFFD Thread mode PSP Basic - - So LR[2] == 1 => Return stack == PSP else MSP - - R0-R3, R12, PC, xPSR can be written via application stackpointer - Other regs can be written directly - - - Read register data from J-Link into R0 - */ - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Monitor is ready to receive register data - ORR R0,R0,#0x80000 - STR R0,[R3, #+_SYSTEM_DEMCR_OFF] -_HandleWRegWaitUntilDataRecv: - LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] - LSLS R0,R0,#+12 - BMI.N _HandleWRegWaitUntilDataRecv // DEMCR[19] == 0 => J-Link has placed new data for us - LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // Get register data - // - // Determine application SP - // - LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP - ITE CS - MRSCS R2,PSP - MRSCC R2,MSP - CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) - BCS _HandleWriteRegR4 - STR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) - B.N _HandleWriteRegDone -_HandleWriteRegR4: - CMP R1,#+5 // if (RegIndex < 5) { (R4) - BCS _HandleWriteRegR5 - MOV R4,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR5: - CMP R1,#+6 // if (RegIndex < 6) { (R5) - BCS _HandleWriteRegR6 - MOV R5,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR6: - CMP R1,#+7 // if (RegIndex < 7) { (R6) - BCS _HandleWriteRegR7 - MOV R6,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR7: - CMP R1,#+8 // if (RegIndex < 8) { (R7) - BCS _HandleWriteRegR8 - MOV R7,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR8: - CMP R1,#+9 // if (RegIndex < 9) { (R8) - BCS _HandleWriteRegR9 - MOV R8,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR9: - CMP R1,#+10 // if (RegIndex < 10) { (R9) - BCS _HandleWriteRegR10 - MOV R9,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR10: - CMP R1,#+11 // if (RegIndex < 11) { (R10) - BCS _HandleWriteRegR11 - MOV R10,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR11: - CMP R1,#+12 // if (RegIndex < 12) { (R11) - BCS _HandleWriteRegR12 - MOV R11,R0 - B.N _HandleWriteRegDone -_HandleWriteRegR12: - CMP R1,#+14 // if (RegIndex < 14) { (R12) - BCS _HandleWriteRegR14 - STR R0,[R2, #+_APP_SP_OFF_R12] - B.N _HandleWriteRegDone -_HandleWriteRegR14: - CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) - BCS _HandleWriteRegR15 - STR R0,[R2, #+_APP_SP_OFF_R14_LR] - B.N _HandleWriteRegDone -_HandleWriteRegR15: - CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) - BCS _HandleWriteRegXPSR - STR R0,[R2, #+_APP_SP_OFF_PC] - B.N _HandleWriteRegDone -_HandleWriteRegXPSR: - CMP R1,#+17 // if (RegIndex < 17) { (xPSR) - BCS _HandleWriteRegMSP - STR R0,[R2, #+_APP_SP_OFF_XPSR] - B.N _HandleWriteRegDone -_HandleWriteRegMSP: - // - // For now, SP cannot be modified because it is needed to jump back from monitor mode - // - CMP R1,#+18 // if (RegIndex < 18) { (MSP) - BCS _HandleWriteRegPSP - B.N _HandleWriteRegDone -_HandleWriteRegPSP: // RegIndex == 18 - CMP R1,#+19 // if (RegIndex < 19) { - BCS _HandleWriteRegCFBP - B.N _HandleWriteRegDone -_HandleWriteRegCFBP: - /* - CFBP is a register that can only be read via debug probe and is a merger of the following regs: - CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] - To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode - */ - CMP R1,#+20 // if (RegIndex < 20) { (CFBP) - BCS _HandleWriteRegFPU - LSLS R1,R0,#+24 - LSRS R1,R1,#+24 // Extract CFBP[7:0] => PRIMASK - MSR PRIMASK,R1 - LSLS R1,R0,#+16 - LSRS R1,R1,#+24 // Extract CFBP[15:8] => BASEPRI - MSR BASEPRI,R1 - LSLS R1,R0,#+8 // Extract CFBP[23:16] => FAULTMASK - LSRS R1,R1,#+24 - MSR FAULTMASK,R1 - LSRS R1,R0,#+24 // Extract CFBP[31:24] => CONTROL - LSRS R0,R1,#2 // Current CONTROL[1] -> Carry - ITE CS // Update saved CONTROL.SPSEL (CONTROL[1]). CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior - ORRCS LR,LR,#+4 - BICCC LR,LR,#+4 - BIC R1,R1,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor. Otherwise behavior is UNPREDICTABLE - LSRS R0,R1,#+3 // New CONTROL.FPCA (CONTROL[2]) -> Carry - ITE CS // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BICCS LR,LR,#+0x10 // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - ORRCC LR,LR,#+0x10 - MRS R0,CONTROL - LSRS R0,R0,#+3 // CONTROL[2] -> Carry - ITE CS // Preserve original value of current CONTROL[2] - ORRCS R1,R1,#+0x04 - BICCC R1,R1,#+0x04 - MSR CONTROL,R1 - ISB // Necessary after writing to CONTROL, see ARM DDI0403D, B1.4.4 The special-purpose CONTROL register - B.N _HandleWriteRegDone -_HandleWriteRegFPU: -#if _HAS_FPU_REGS - CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) - BCS _HandleWriteRegDone_Veneer - /* - Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled - If not, access to floating point is not possible - CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved - */ - MOV R12,R0 // Save register data - LDR R0,_AddrCPACR - LDR R0,[R0] - LSLS R0,R0,#+8 - LSRS R0,R0,#+28 - CMP R0,#+0xF - BEQ _HandleWriteRegFPU_Allowed - CMP R0,#+0x5 - BNE _HandleWriteRegDone_Veneer -_HandleWriteRegFPU_Allowed: - CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) - BCS _HandleWriteRegFPS0_FPS31 - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleWriteFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleWriteFPSCRLazyMode - STR R12,[R2, #+_APP_SP_OFF_FPSCR] - B _HandleWriteRegDone -_HandleWriteFPSCRLazyMode: - VMSR FPSCR,R12 - B _HandleWriteRegDone -_HandleWriteRegFPS0_FPS31: // RegIndex == 21-52 - LDR R0,=_SYSTEM_FPCCR - LDR R0,[R0] - LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack - BCS _HandleWriteFPS0_FPS31LazyMode - LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack - BCS _HandleWriteFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame - SUBS R1,#+21 // Convert absolute reg index into rel. one - LSLS R1,R1,#+2 // RegIndex to position on stack - ADDS R1,#+_APP_SP_OFF_S0 - STR R12,[R2, R1] -_HandleWriteRegDone_Veneer: - B _HandleWriteRegDone -_HandleWriteFPS0_FPS31LazyMode: - SUBS R1,#+20 // Convert abs. RegIndex into rel. one - MOVS R0,#+6 - MULS R1,R0,R1 - LDR R0,=_HandleReadRegUnknown - SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) - ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr - BX R0 - // - // Table for reading FPS0-FPS31 - // - VMOV S31,R12 // v = FPSx - B _HandleWriteRegDone - VMOV S30,R12 - B _HandleWriteRegDone - VMOV S29,R12 - B _HandleWriteRegDone - VMOV S28,R12 - B _HandleWriteRegDone - VMOV S27,R12 - B _HandleWriteRegDone - VMOV S26,R12 - B _HandleWriteRegDone - VMOV S25,R12 - B _HandleWriteRegDone - VMOV S24,R12 - B _HandleWriteRegDone - VMOV S23,R12 - B _HandleWriteRegDone - VMOV S22,R12 - B _HandleWriteRegDone - VMOV S21,R12 - B _HandleWriteRegDone - VMOV S20,R12 - B _HandleWriteRegDone - VMOV S19,R12 - B _HandleWriteRegDone - VMOV S18,R12 - B _HandleWriteRegDone - VMOV S17,R12 - B _HandleWriteRegDone - VMOV S16,R12 - B _HandleWriteRegDone - VMOV S15,R12 - B _HandleWriteRegDone - VMOV S14,R12 - B _HandleWriteRegDone - VMOV S13,R12 - B _HandleWriteRegDone - VMOV S12,R12 - B _HandleWriteRegDone - VMOV S11,R12 - B _HandleWriteRegDone - VMOV S10,R12 - B _HandleWriteRegDone - VMOV S9,R12 - B _HandleWriteRegDone - VMOV S8,R12 - B _HandleWriteRegDone - VMOV S7,R12 - B _HandleWriteRegDone - VMOV S6,R12 - B _HandleWriteRegDone - VMOV S5,R12 - B _HandleWriteRegDone - VMOV S4,R12 - B _HandleWriteRegDone - VMOV S3,R12 - B _HandleWriteRegDone - VMOV S2,R12 - B _HandleWriteRegDone - VMOV S1,R12 - B _HandleWriteRegDone - VMOV S0,R12 - B _HandleWriteRegDone -#else - B _HandleWriteRegUnknown -#endif -_HandleWriteRegUnknown: - B.N _HandleWriteRegDone -_HandleWriteRegDone: - B _IndicateMonReady // Indicate that monitor has read data, processed command and is ready for a new one - .end -/****** End Of File *************************************************/ From 0952d1b2526edb795495548b8e53ce98f5f65829 Mon Sep 17 00:00:00 2001 From: medentem <122332405+medentem@users.noreply.github.com> Date: Sun, 6 Oct 2024 02:32:07 -0500 Subject: [PATCH 1356/1377] UserPrefs - Preconfigure up to 3 channels, GPS Mode (#4930) * added up to 3 channels via userprefs * added up to 3 channels via userprefs * added up to 3 channels via userprefs * trunk fmt * Added USERPREFS for GPS MODE --- src/mesh/Channels.cpp | 82 +++++++++++++++++++++++++++++++++---------- src/mesh/Channels.h | 7 +++- src/mesh/NodeDB.cpp | 4 ++- userPrefs.h | 22 ++++++++++++ 4 files changed, 94 insertions(+), 21 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index c8ac09a37..bb30e501d 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -76,6 +76,23 @@ meshtastic_Channel &Channels::fixupChannel(ChannelIndex chIndex) return ch; } +void Channels::initDefaultLoraConfig() +{ + meshtastic_Config_LoRaConfig &loraConfig = config.lora; + + loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast + loraConfig.use_preset = true; + loraConfig.tx_power = 0; // default + loraConfig.channel_num = 0; + +#ifdef USERPREFS_LORACONFIG_MODEM_PRESET + loraConfig.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET; +#endif +#ifdef USERPREFS_LORACONFIG_CHANNEL_NUM + loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM; +#endif +} + /** * Write a default channel to the specified channel index */ @@ -83,12 +100,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) { meshtastic_Channel &ch = getByIndex(chIndex); meshtastic_ChannelSettings &channelSettings = ch.settings; - meshtastic_Config_LoRaConfig &loraConfig = config.lora; - loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast - loraConfig.use_preset = true; - loraConfig.tx_power = 0; // default - loraConfig.channel_num = 0; uint8_t defaultpskIndex = 1; channelSettings.psk.bytes[0] = defaultpskIndex; channelSettings.psk.size = 1; @@ -97,21 +109,14 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) channelSettings.has_module_settings = true; ch.has_settings = true; - ch.role = meshtastic_Channel_Role_PRIMARY; + ch.role = chIndex == 0 ? meshtastic_Channel_Role_PRIMARY : meshtastic_Channel_Role_SECONDARY; -#ifdef USERPREFS_LORACONFIG_MODEM_PRESET - loraConfig.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET; -#endif -#ifdef USERPREFS_LORACONFIG_CHANNEL_NUM - loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM; -#endif - - // Install custom defaults. Will eventually support setting multiple default channels - if (chIndex == 0) { + switch (chIndex) { + case 0: #ifdef USERPREFS_CHANNEL_0_PSK - static const uint8_t defaultpsk[] = USERPREFS_CHANNEL_0_PSK; - memcpy(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)); - channelSettings.psk.size = sizeof(defaultpsk); + static const uint8_t defaultpsk0[] = USERPREFS_CHANNEL_0_PSK; + memcpy(channelSettings.psk.bytes, defaultpsk0, sizeof(defaultpsk0)); + channelSettings.psk.size = sizeof(defaultpsk0); #endif #ifdef USERPREFS_CHANNEL_0_NAME @@ -120,6 +125,37 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) #ifdef USERPREFS_CHANNEL_0_PRECISION channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_0_PRECISION; #endif + break; + case 1: +#ifdef USERPREFS_CHANNEL_1_PSK + static const uint8_t defaultpsk1[] = USERPREFS_CHANNEL_1_PSK; + memcpy(channelSettings.psk.bytes, defaultpsk1, sizeof(defaultpsk1)); + channelSettings.psk.size = sizeof(defaultpsk1); + +#endif +#ifdef USERPREFS_CHANNEL_1_NAME + strcpy(channelSettings.name, USERPREFS_CHANNEL_1_NAME); +#endif +#ifdef USERPREFS_CHANNEL_1_PRECISION + channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_1_PRECISION; +#endif + break; + case 2: +#ifdef USERPREFS_CHANNEL_2_PSK + static const uint8_t defaultpsk2[] = USERPREFS_CHANNEL_2_PSK; + memcpy(channelSettings.psk.bytes, defaultpsk2, sizeof(defaultpsk2)); + channelSettings.psk.size = sizeof(defaultpsk2); + +#endif +#ifdef USERPREFS_CHANNEL_2_NAME + strcpy(channelSettings.name, USERPREFS_CHANNEL_2_NAME); +#endif +#ifdef USERPREFS_CHANNEL_2_PRECISION + channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_2_PRECISION; +#endif + break; + default: + break; } } @@ -209,7 +245,15 @@ void Channels::initDefaults() channelFile.channels_count = MAX_NUM_CHANNELS; for (int i = 0; i < channelFile.channels_count; i++) fixupChannel(i); + initDefaultLoraConfig(); + +#ifdef USERPREFS_CHANNELS_TO_WRITE + for (int i = 0; i < USERPREFS_CHANNELS_TO_WRITE; i++) { + initDefaultChannel(i); + } +#else initDefaultChannel(0); +#endif } void Channels::onConfigChanged() @@ -359,4 +403,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) int16_t Channels::setActiveByIndex(ChannelIndex channelIndex) { return setCrypto(channelIndex); -} +} \ No newline at end of file diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index e5a750f71..b0c9b3d07 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -117,7 +117,12 @@ class Channels meshtastic_Channel &fixupChannel(ChannelIndex chIndex); /** - * Write a default channel to the specified channel index + * Writes the default lora config + */ + void initDefaultLoraConfig(); + + /** + * Write default channels defined in UserPrefs */ void initDefaultChannel(ChannelIndex chIndex); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d947f0cea..49f0cbc62 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -336,7 +336,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) #else config.device.disable_triple_click = true; #endif -#if !HAS_GPS || defined(T_DECK) || defined(TLORA_T3S3_EPAPER) +#if defined(USERPREFS_CONFIG_GPS_MODE) + config.position.gps_mode = USERPREFS_CONFIG_GPS_MODE; +#elif !HAS_GPS || defined(T_DECK) || defined(TLORA_T3S3_EPAPER) config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT; #elif !defined(GPS_RX_PIN) if (config.position.rx_gpio == 0) diff --git a/userPrefs.h b/userPrefs.h index f544a6535..ed622bcfb 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -13,6 +13,10 @@ // #define USERPREFS_LORACONFIG_MODEM_PRESET meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST // #define USERPREFS_LORACONFIG_CHANNEL_NUM 31 // #define USERPREFS_CONFIG_LORA_IGNORE_MQTT true + +// #define USERPREFS_CONFIG_GPS_MODE meshtastic_Config_PositionConfig_GpsMode_ENABLED + +// #define USERPREFS_CHANNELS_TO_WRITE 3 /* #define USERPREFS_CHANNEL_0_PSK \ { \ @@ -22,6 +26,24 @@ */ // #define USERPREFS_CHANNEL_0_NAME "DEFCONnect" // #define USERPREFS_CHANNEL_0_PRECISION 14 +/* +#define USERPREFS_CHANNEL_1_PSK \ + { \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \ + } +*/ +// #define USERPREFS_CHANNEL_1_NAME "REPLACEME" +// #define USERPREFS_CHANNEL_1_PRECISION 14 +/* +#define USERPREFS_CHANNEL_2_PSK \ + { \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \ + } +*/ +// #define USERPREFS_CHANNEL_2_NAME "REPLACEME" +// #define USERPREFS_CHANNEL_2_PRECISION 14 // #define USERPREFS_CONFIG_OWNER_LONG_NAME "My Long Name" // #define USERPREFS_CONFIG_OWNER_SHORT_NAME "MLN" From a3a97d3025822c38256a560d1eb83695e2b589f1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 6 Oct 2024 05:24:57 -0500 Subject: [PATCH 1357/1377] Start of generating json manifest of macros in userPrefs.h (#4946) * Start of generating json manifest for userPrefs.h * Just trunk this for now * Add automatic generation of json manifest in GH action * Trunk --- .github/workflows/generate-userprefs.yml | 33 ++++++++++++++++ bin/build-userprefs-json.py | 48 ++++++++++++++++++++++++ userPrefs.json | 16 ++++++++ 3 files changed, 97 insertions(+) create mode 100644 .github/workflows/generate-userprefs.yml create mode 100644 bin/build-userprefs-json.py create mode 100644 userPrefs.json diff --git a/.github/workflows/generate-userprefs.yml b/.github/workflows/generate-userprefs.yml new file mode 100644 index 000000000..9c4c75569 --- /dev/null +++ b/.github/workflows/generate-userprefs.yml @@ -0,0 +1,33 @@ +on: + push: + paths: + - userPrefs.h + branches: + - master + +jobs: + generate-userprefs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Clang + run: sudo apt-get install -y clang + + - name: Install trunk + run: curl https://get.trunk.io -fsSL | bash + + - name: Generate userPrefs.jsom + run: python3 ./bin/build-userprefs-json.py + + - name: Trunk format json + run: trunk format userPrefs.json + + - name: Commit userPrefs.json + run: | + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + git add userPrefs.json + git commit -m "Update userPrefs.json" + git push diff --git a/bin/build-userprefs-json.py b/bin/build-userprefs-json.py new file mode 100644 index 000000000..58f460bcf --- /dev/null +++ b/bin/build-userprefs-json.py @@ -0,0 +1,48 @@ +import json +import subprocess +import re + +def get_macros_from_header(header_file): + # Run clang to preprocess the header file and capture the output + result = subprocess.run(['clang', '-E', '-dM', header_file], capture_output=True, text=True) + if result.returncode != 0: + raise RuntimeError(f"Clang preprocessing failed: {result.stderr}") + + # Extract macros from the output + macros = {} + macro_pattern = re.compile(r'#define\s+(\w+)\s+(.*)') + for line in result.stdout.splitlines(): + match = macro_pattern.match(line) + if match and 'USERPREFS_' in line and '_USERPREFS_' not in line: + macros[match.group(1)] = match.group(2) + + return macros + +def write_macros_to_json(macros, output_file): + with open(output_file, 'w') as f: + json.dump(macros, f, indent=4) + +def main(): + header_file = 'userPrefs.h' + output_file = 'userPrefs.json' + # Uncomment all macros in the header file + with open(header_file, 'r') as file: + lines = file.readlines() + + uncommented_lines = [] + for line in lines: + stripped_line = line.strip().replace('/*', '').replace('*/', '') + if stripped_line.startswith('//') and 'USERPREFS_' in stripped_line: + # Replace "//" + stripped_line = stripped_line.replace('//', '') + uncommented_lines.append(stripped_line + '\n') + + with open(header_file, 'w') as file: + for line in uncommented_lines: + file.write(line) + macros = get_macros_from_header(header_file) + write_macros_to_json(macros, output_file) + print(f"Macros have been written to {output_file}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/userPrefs.json b/userPrefs.json new file mode 100644 index 000000000..bc62602be --- /dev/null +++ b/userPrefs.json @@ -0,0 +1,16 @@ +{ + "USERPREFS_CHANNEL_0_NAME": "\"DEFCONnect\"", + "USERPREFS_CHANNEL_0_PRECISION": "14", + "USERPREFS_CHANNEL_0_PSK": "{ 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 }", + "USERPREFS_CONFIG_LORA_IGNORE_MQTT": "true", + "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US", + "USERPREFS_CONFIG_OWNER_LONG_NAME": "\"My Long Name\"", + "USERPREFS_CONFIG_OWNER_SHORT_NAME": "\"MLN\"", + "USERPREFS_EVENT_MODE": "1", + "USERPREFS_HAS_SPLASH": "", + "USERPREFS_LORACONFIG_CHANNEL_NUM": "31", + "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST", + "USERPREFS_SPLASH_TITLE": "\"DEFCONtastic\"", + "USERPREFS_TZ_STRING": "\"tzplaceholder \"", + "USERPREFS_USE_ADMIN_KEY": "1" +} From 01df3ff4775c4ef368b71d4b64d15252c891fb68 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 6 Oct 2024 05:26:04 -0500 Subject: [PATCH 1358/1377] Update generate-userprefs.yml --- .github/workflows/generate-userprefs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/generate-userprefs.yml b/.github/workflows/generate-userprefs.yml index 9c4c75569..10dd1ff7d 100644 --- a/.github/workflows/generate-userprefs.yml +++ b/.github/workflows/generate-userprefs.yml @@ -1,3 +1,5 @@ +name: Generate UsersPrefs JSON manifest + on: push: paths: From 553e572eb52575726939b3cc309047f04ea0930b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 6 Oct 2024 19:40:06 +0800 Subject: [PATCH 1359/1377] Coalesce duplicated method GetTimeSinceMeshPacket (#4968) GetTimeSinceMeshPacket was duplicated in PowerTelemetry and EnvironmentalTelemetry, albeit one had a cooler name than the other. As we add HealthTelemetry, to avoid creating a third instance of this method, let's move it somewhere that makese sense. Adds a new method GetTimeSinceMeshPacket to MeshService and updates EnvironmentTelemetry and PowerTelemetry to use it. --- src/mesh/MeshService.cpp | 14 +++++++++++++- src/mesh/MeshService.h | 4 +++- src/modules/Telemetry/EnvironmentTelemetry.cpp | 16 ++-------------- src/modules/Telemetry/PowerTelemetry.cpp | 16 ++-------------- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index a8a207352..655348bd5 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -406,4 +406,16 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) bool MeshService::isToPhoneQueueEmpty() { return toPhoneQueue.isEmpty(); -} \ No newline at end of file +} + +uint32_t MeshService::GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp) +{ + uint32_t now = getTime(); + + uint32_t last_seen = mp->rx_time; + int delta = (int)(now - last_seen); + if (delta < 0) // our clock must be slightly off still - not set from GPS yet + delta = 0; + + return delta; +} diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index b91fedada..1ccca4e6d 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -151,6 +151,8 @@ class MeshService ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); + uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp); + private: #if HAS_GPS /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh @@ -163,4 +165,4 @@ class MeshService friend class RoutingModule; }; -extern MeshService *service; \ No newline at end of file +extern MeshService *service; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ac291c4ab..147f4ed34 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -187,18 +187,6 @@ float EnvironmentTelemetryModule::CelsiusToFahrenheit(float c) return (c * 9) / 5 + 32; } -uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp) -{ - uint32_t now = getTime(); - - uint32_t last_seen = mp->rx_time; - int delta = (int)(now - last_seen); - if (delta < 0) // our clock must be slightly off still - not set from GPS yet - delta = 0; - - return delta; -} - void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -213,7 +201,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt // Decode the last measurement packet meshtastic_Telemetry lastMeasurement; - uint32_t agoSecs = GetTimeSinceMeshPacket(lastMeasurementPacket); + uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); const char *lastSender = getSenderShortName(*lastMeasurementPacket); const meshtastic_Data &p = lastMeasurementPacket->decoded; @@ -601,4 +589,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 038cbfadc..be3048998 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -94,18 +94,6 @@ bool PowerTelemetryModule::wantUIFrame() return moduleConfig.telemetry.power_screen_enabled; } -uint32_t GetTimeyWimeySinceMeshPacket(const meshtastic_MeshPacket *mp) -{ - uint32_t now = getTime(); - - uint32_t last_seen = mp->rx_time; - int delta = (int)(now - last_seen); - if (delta < 0) // our clock must be slightly off still - not set from GPS yet - delta = 0; - - return delta; -} - void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -119,7 +107,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s meshtastic_Telemetry lastMeasurement; - uint32_t agoSecs = GetTimeyWimeySinceMeshPacket(lastMeasurementPacket); + uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); const char *lastSender = getSenderShortName(*lastMeasurementPacket); const meshtastic_Data &p = lastMeasurementPacket->decoded; @@ -265,4 +253,4 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) return false; } -#endif \ No newline at end of file +#endif From ebc3a66d109d9c3b49031f23d424c8e09a989c16 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 6 Oct 2024 19:40:23 +0800 Subject: [PATCH 1360/1377] Which Module wants a UI Frame? (#4967) Previously our debug message for screens blandly stated "Module wants a UI Frame" This patch replaces the word Module with the name of the Module in need of a frame a frame, enhancing debugging ability. --- src/mesh/MeshModule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 27aca5832..c60404c98 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -240,7 +240,7 @@ std::vector MeshModule::GetMeshModulesWithUIFrames() for (auto i = modules->begin(); i != modules->end(); ++i) { auto &pi = **i; if (pi.wantUIFrame()) { - LOG_DEBUG("Module wants a UI Frame\n"); + LOG_DEBUG("%s wants a UI Frame\n", pi.name); modulesWithUIFrames.push_back(&pi); } } @@ -255,7 +255,7 @@ void MeshModule::observeUIEvents(Observer *observer) auto &pi = **i; Observable *observable = pi.getUIFrameObservable(); if (observable != NULL) { - LOG_DEBUG("Module wants a UI Frame\n"); + LOG_DEBUG("%s wants a UI Frame\n", pi.name); observer->observe(observable); } } @@ -296,4 +296,4 @@ bool MeshModule::isRequestingFocus() } else return false; } -#endif \ No newline at end of file +#endif From ad031dd69f3e4808378b73dc432c68e11824c597 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 07:28:05 -0500 Subject: [PATCH 1361/1377] [create-pull-request] automated change (#4971) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- .../generated/meshtastic/module_config.pb.h | 20 ++++++++++--------- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/protobufs b/protobufs index b41970669..5df44cf80 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit b419706693e0120f7b032d0be0121ae758cfd6e4 +Subproject commit 5df44cf804916f94ce914a1cc4f5b3fb9dc5fe05 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 0fc09daca..d690acf06 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -359,7 +359,7 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size #define meshtastic_ChannelFile_size 718 #define meshtastic_NodeInfoLite_size 183 -#define meshtastic_OEMStore_size 3576 +#define meshtastic_OEMStore_size 3578 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 96 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index bac67942c..931de10f5 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -188,7 +188,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size #define meshtastic_LocalConfig_size 735 -#define meshtastic_LocalModuleConfig_size 695 +#define meshtastic_LocalModuleConfig_size 697 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index ce8b8891a..e9be480ce 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -327,14 +327,12 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig { /* Interval in seconds of how often we should try to send our air quality metrics to the mesh */ uint32_t air_quality_interval; - /* Interval in seconds of how often we should try to send our - air quality metrics to the mesh */ + /* Enable/disable Power metrics */ bool power_measurement_enabled; /* Interval in seconds of how often we should try to send our - air quality metrics to the mesh */ + power metrics to the mesh */ uint32_t power_update_interval; - /* Interval in seconds of how often we should try to send our - air quality metrics to the mesh */ + /* Enable/Disable the power measurement module on-device display */ bool power_screen_enabled; /* Preferences for the (Health) Telemetry Module Enable/Disable the telemetry measurement module measurement collection */ @@ -342,6 +340,8 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig { /* Interval in seconds of how often we should try to send our health metrics to the mesh */ uint32_t health_update_interval; + /* Enable/Disable the health telemetry module on-device display */ + bool health_screen_enabled; } meshtastic_ModuleConfig_TelemetryConfig; /* TODO: REPLACE */ @@ -509,7 +509,7 @@ extern "C" { #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} -#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -525,7 +525,7 @@ extern "C" { #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} -#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -609,6 +609,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_power_screen_enabled_tag 10 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12 +#define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3 @@ -803,7 +804,8 @@ X(a, STATIC, SINGULAR, BOOL, power_measurement_enabled, 8) \ X(a, STATIC, SINGULAR, UINT32, power_update_interval, 9) \ X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \ X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \ -X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) +X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \ +X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL @@ -888,7 +890,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 -#define meshtastic_ModuleConfig_TelemetryConfig_size 44 +#define meshtastic_ModuleConfig_TelemetryConfig_size 46 #define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 From 7febb41727a0c9ea763d7b4ec3ffebf3e539fd0a Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 6 Oct 2024 20:37:20 +0800 Subject: [PATCH 1362/1377] Trunk format Screen.cpp (#4970) --- src/graphics/Screen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 66900c53d..ad42fa979 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -2485,7 +2485,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #endif } else { #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -2817,4 +2817,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg) } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} -#endif // HAS_SCREEN +#endif // HAS_SCREEN \ No newline at end of file From 001a845ac314f7b3e6de4f7d25584d55373e47c6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 6 Oct 2024 07:55:02 -0500 Subject: [PATCH 1363/1377] Upgrade nanopb (#4973) --- .github/workflows/update_protobufs.yml | 6 +++--- .trunk/trunk.yaml | 4 ++-- bin/regen-protos.bat | 2 +- bin/regen-protos.sh | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 7ce767370..f1c92b860 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -17,9 +17,9 @@ jobs: - name: Download nanopb run: | - wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz - tar xvzf nanopb-0.4.8-linux-x86.tar.gz - mv nanopb-0.4.8-linux-x86 nanopb-0.4.8 + wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9-linux-x86.tar.gz + tar xvzf nanopb-0.4.9-linux-x86.tar.gz + mv nanopb-0.4.9-linux-x86 nanopb-0.4.9 - name: Re-generate protocol buffers run: | diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 9ed720c3f..ea4045a16 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -11,7 +11,7 @@ lint: - trufflehog@3.82.6 - yamllint@1.35.1 - bandit@1.7.10 - - checkov@3.2.255 + - checkov@3.2.256 - terrascan@1.19.1 - trivy@0.55.2 #- trufflehog@3.63.2-rc0 @@ -28,7 +28,7 @@ lint: - shellcheck@0.10.0 - black@24.8.0 - git-diff-check - - gitleaks@8.19.3 + - gitleaks@8.20.0 - clang-format@16.0.3 - prettier@3.3.3 ignore: diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat index f28ef0025..7fa8f333d 100644 --- a/bin/regen-protos.bat +++ b/bin/regen-protos.bat @@ -1 +1 @@ -cd protobufs && ..\nanopb-0.4.8\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto +cd protobufs && ..\nanopb-0.4.9\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index 2e60784e3..12546bfdc 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -2,10 +2,10 @@ set -e -echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.8 to be located in the" +echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.9 to be located in the" echo "firmware root directory if the following step fails, you should download the correct" -echo "prebuilt binaries for your computer into nanopb-0.4.8" +echo "prebuilt binaries for your computer into nanopb-0.4.9" # the nanopb tool seems to require that the .options file be in the current directory! cd protobufs -../nanopb-0.4.8/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto +../nanopb-0.4.9/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto From bb9f003c24e981373cef430011e1538f18594d6d Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 6 Oct 2024 20:55:21 +0800 Subject: [PATCH 1364/1377] Switch EnvironmentTelemetry to use UnitConversions (#4972) We already have a central class for unit conversions, switch EnvironmentTelemetry to that in preparation for HealthTelemetry. --- src/modules/Telemetry/EnvironmentTelemetry.cpp | 11 ++++------- src/modules/Telemetry/EnvironmentTelemetry.h | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 147f4ed34..1ccdedeb7 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "UnitConversions.h" #include "main.h" #include "power.h" #include "sleep.h" @@ -182,11 +183,6 @@ bool EnvironmentTelemetryModule::wantUIFrame() return moduleConfig.telemetry.environment_screen_enabled; } -float EnvironmentTelemetryModule::CelsiusToFahrenheit(float c) -{ - return (c * 9) / 5 + 32; -} - void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -216,7 +212,8 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; if (moduleConfig.telemetry.environment_display_fahrenheit) { - last_temp = String(CelsiusToFahrenheit(lastMeasurement.variant.environment_metrics.temperature), 0) + "°F"; + last_temp = + String(UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.environment_metrics.temperature), 0) + "°F"; } // Continue with the remaining details @@ -589,4 +586,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule return result; } -#endif +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index 59d272e78..e680d8bbd 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -52,7 +52,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu meshtastic_AdminMessage *response) override; private: - float CelsiusToFahrenheit(float c); bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute From 830281803f80954023ec5f5ac67228736e6b50bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 08:14:03 -0500 Subject: [PATCH 1365/1377] [create-pull-request] automated change (#4974) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 4 +++- src/mesh/generated/meshtastic/admin.pb.h | 2 +- src/mesh/generated/meshtastic/apponly.pb.cpp | 2 +- src/mesh/generated/meshtastic/apponly.pb.h | 2 +- src/mesh/generated/meshtastic/atak.pb.cpp | 4 +++- src/mesh/generated/meshtastic/atak.pb.h | 2 +- .../generated/meshtastic/cannedmessages.pb.cpp | 2 +- src/mesh/generated/meshtastic/cannedmessages.pb.h | 2 +- src/mesh/generated/meshtastic/channel.pb.cpp | 3 ++- src/mesh/generated/meshtastic/channel.pb.h | 2 +- src/mesh/generated/meshtastic/clientonly.pb.cpp | 2 +- src/mesh/generated/meshtastic/clientonly.pb.h | 2 +- src/mesh/generated/meshtastic/config.pb.cpp | 15 ++++++++++++++- src/mesh/generated/meshtastic/config.pb.h | 14 ++++++++++---- .../generated/meshtastic/connection_status.pb.cpp | 2 +- .../generated/meshtastic/connection_status.pb.h | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.cpp | 3 ++- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.cpp | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 11 ++++++++++- src/mesh/generated/meshtastic/mesh.pb.h | 2 +- .../generated/meshtastic/module_config.pb.cpp | 8 +++++++- src/mesh/generated/meshtastic/module_config.pb.h | 2 +- src/mesh/generated/meshtastic/mqtt.pb.cpp | 2 +- src/mesh/generated/meshtastic/mqtt.pb.h | 2 +- src/mesh/generated/meshtastic/paxcount.pb.cpp | 2 +- src/mesh/generated/meshtastic/paxcount.pb.h | 2 +- src/mesh/generated/meshtastic/portnums.pb.cpp | 3 ++- src/mesh/generated/meshtastic/portnums.pb.h | 2 +- src/mesh/generated/meshtastic/powermon.pb.cpp | 4 +++- src/mesh/generated/meshtastic/powermon.pb.h | 2 +- .../generated/meshtastic/remote_hardware.pb.cpp | 3 ++- .../generated/meshtastic/remote_hardware.pb.h | 2 +- src/mesh/generated/meshtastic/rtttl.pb.cpp | 2 +- src/mesh/generated/meshtastic/rtttl.pb.h | 2 +- src/mesh/generated/meshtastic/storeforward.pb.cpp | 3 ++- src/mesh/generated/meshtastic/storeforward.pb.h | 2 +- src/mesh/generated/meshtastic/telemetry.pb.cpp | 3 ++- src/mesh/generated/meshtastic/telemetry.pb.h | 2 +- src/mesh/generated/meshtastic/xmodem.pb.cpp | 3 ++- src/mesh/generated/meshtastic/xmodem.pb.h | 2 +- 43 files changed, 93 insertions(+), 46 deletions(-) diff --git a/protobufs b/protobufs index 5df44cf80..c9ae7fd47 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5df44cf804916f94ce914a1cc4f5b3fb9dc5fe05 +Subproject commit c9ae7fd478bffe5f954b30de6cb140821fe9ff52 diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index 339960302..8b3fd3d1b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/admin.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -18,3 +18,5 @@ PB_BIND(meshtastic_NodeRemoteHardwarePinsResponse, meshtastic_NodeRemoteHardware + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index c1ff7ebd4..bf81269b4 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/apponly.pb.cpp b/src/mesh/generated/meshtastic/apponly.pb.cpp index 44b0ea3cc..64d43b7ee 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.cpp +++ b/src/mesh/generated/meshtastic/apponly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/apponly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index 31211a91b..dc08d9ff3 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/atak.pb.cpp b/src/mesh/generated/meshtastic/atak.pb.cpp index 491336bcf..6dbc69fb4 100644 --- a/src/mesh/generated/meshtastic/atak.pb.cpp +++ b/src/mesh/generated/meshtastic/atak.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/atak.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -27,3 +27,5 @@ PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO) + + diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index 7d1ef2995..15a86788b 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.cpp b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp index 71e659be2..9f51e9634 100644 --- a/src/mesh/generated/meshtastic/cannedmessages.pb.cpp +++ b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/cannedmessages.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.h b/src/mesh/generated/meshtastic/cannedmessages.pb.h index c3f9a8b9b..06d14b98f 100644 --- a/src/mesh/generated/meshtastic/cannedmessages.pb.h +++ b/src/mesh/generated/meshtastic/cannedmessages.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/channel.pb.cpp b/src/mesh/generated/meshtastic/channel.pb.cpp index fe76d8140..52f923b13 100644 --- a/src/mesh/generated/meshtastic/channel.pb.cpp +++ b/src/mesh/generated/meshtastic/channel.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/channel.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -17,3 +17,4 @@ PB_BIND(meshtastic_Channel, meshtastic_Channel, AUTO) + diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index d9c7d4ffa..3d617ae39 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/clientonly.pb.cpp b/src/mesh/generated/meshtastic/clientonly.pb.cpp index 44c6f95ce..d99af8cf5 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.cpp +++ b/src/mesh/generated/meshtastic/clientonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/clientonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h index 5720c1c02..bf32d7875 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.h +++ b/src/mesh/generated/meshtastic/clientonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index 92c3313bd..23f4d542b 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -46,6 +46,19 @@ PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig, + + + + + + + + + + + + + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index da2e43972..988f852ff 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED @@ -238,7 +238,13 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode { /* Malaysia 919mhz */ meshtastic_Config_LoRaConfig_RegionCode_MY_919 = 17, /* Singapore 923mhz */ - meshtastic_Config_LoRaConfig_RegionCode_SG_923 = 18 + meshtastic_Config_LoRaConfig_RegionCode_SG_923 = 18, + /* Philippines 433mhz */ + meshtastic_Config_LoRaConfig_RegionCode_PH_433 = 19, + /* Philippines 868mhz */ + meshtastic_Config_LoRaConfig_RegionCode_PH_868 = 20, + /* Philippines 915mhz */ + meshtastic_Config_LoRaConfig_RegionCode_PH_915 = 21 } meshtastic_Config_LoRaConfig_RegionCode; /* Standard predefined channel settings @@ -615,8 +621,8 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1)) #define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET -#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_SG_923 -#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_SG_923+1)) +#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_PH_915 +#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_PH_915+1)) #define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST #define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO diff --git a/src/mesh/generated/meshtastic/connection_status.pb.cpp b/src/mesh/generated/meshtastic/connection_status.pb.cpp index fc5a364dd..d1495bb83 100644 --- a/src/mesh/generated/meshtastic/connection_status.pb.cpp +++ b/src/mesh/generated/meshtastic/connection_status.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/connection_status.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/connection_status.pb.h b/src/mesh/generated/meshtastic/connection_status.pb.h index 1c618e4d4..c433e370b 100644 --- a/src/mesh/generated/meshtastic/connection_status.pb.h +++ b/src/mesh/generated/meshtastic/connection_status.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 2747ac9d9..135634762 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/deviceonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -26,3 +26,4 @@ PB_BIND(meshtastic_OEMStore, meshtastic_OEMStore, 2) + diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index d690acf06..2aa8fda8e 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/localonly.pb.cpp b/src/mesh/generated/meshtastic/localonly.pb.cpp index 9bc98fb85..0a752a5a8 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.cpp +++ b/src/mesh/generated/meshtastic/localonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/localonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 931de10f5..6409aef74 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 8c8b9ded7..a0c1e2e73 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/mesh.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -90,4 +90,13 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU + + + + + + + + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index c04f4adb9..fb154e9d5 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index e1c33e2c1..c40041eab 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/module_config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -61,3 +61,9 @@ PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) + + + + + + diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index e9be480ce..32d5ded23 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/mqtt.pb.cpp b/src/mesh/generated/meshtastic/mqtt.pb.cpp index f00dd823b..74536cb79 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.cpp +++ b/src/mesh/generated/meshtastic/mqtt.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/mqtt.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h index 8ec9f98c3..4d1027374 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.h +++ b/src/mesh/generated/meshtastic/mqtt.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/paxcount.pb.cpp b/src/mesh/generated/meshtastic/paxcount.pb.cpp index 67f07a31b..403288147 100644 --- a/src/mesh/generated/meshtastic/paxcount.pb.cpp +++ b/src/mesh/generated/meshtastic/paxcount.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/paxcount.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/paxcount.pb.h b/src/mesh/generated/meshtastic/paxcount.pb.h index 09377d833..b6b51fdd5 100644 --- a/src/mesh/generated/meshtastic/paxcount.pb.h +++ b/src/mesh/generated/meshtastic/paxcount.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/portnums.pb.cpp b/src/mesh/generated/meshtastic/portnums.pb.cpp index 8f32c0851..8fca9af79 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.cpp +++ b/src/mesh/generated/meshtastic/portnums.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/portnums.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -8,3 +8,4 @@ + diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index b9e537ddf..df6cf32c2 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/powermon.pb.cpp b/src/mesh/generated/meshtastic/powermon.pb.cpp index ce41ea021..6a9b7551a 100644 --- a/src/mesh/generated/meshtastic/powermon.pb.cpp +++ b/src/mesh/generated/meshtastic/powermon.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/powermon.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -15,3 +15,5 @@ PB_BIND(meshtastic_PowerStressMessage, meshtastic_PowerStressMessage, AUTO) + + diff --git a/src/mesh/generated/meshtastic/powermon.pb.h b/src/mesh/generated/meshtastic/powermon.pb.h index 7de0618e9..5add85b85 100644 --- a/src/mesh/generated/meshtastic/powermon.pb.h +++ b/src/mesh/generated/meshtastic/powermon.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.cpp b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp index 4a23698b2..239950e7e 100644 --- a/src/mesh/generated/meshtastic/remote_hardware.pb.cpp +++ b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/remote_hardware.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -11,3 +11,4 @@ PB_BIND(meshtastic_HardwareMessage, meshtastic_HardwareMessage, AUTO) + diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.h b/src/mesh/generated/meshtastic/remote_hardware.pb.h index 936034b62..ade250e93 100644 --- a/src/mesh/generated/meshtastic/remote_hardware.pb.h +++ b/src/mesh/generated/meshtastic/remote_hardware.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/rtttl.pb.cpp b/src/mesh/generated/meshtastic/rtttl.pb.cpp index 8367fdbce..61ad8b73f 100644 --- a/src/mesh/generated/meshtastic/rtttl.pb.cpp +++ b/src/mesh/generated/meshtastic/rtttl.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/rtttl.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/rtttl.pb.h b/src/mesh/generated/meshtastic/rtttl.pb.h index 452b0cf4b..2b7e35f11 100644 --- a/src/mesh/generated/meshtastic/rtttl.pb.h +++ b/src/mesh/generated/meshtastic/rtttl.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/storeforward.pb.cpp b/src/mesh/generated/meshtastic/storeforward.pb.cpp index 5b3fadd9a..71a232bf6 100644 --- a/src/mesh/generated/meshtastic/storeforward.pb.cpp +++ b/src/mesh/generated/meshtastic/storeforward.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/storeforward.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -20,3 +20,4 @@ PB_BIND(meshtastic_StoreAndForward_Heartbeat, meshtastic_StoreAndForward_Heartbe + diff --git a/src/mesh/generated/meshtastic/storeforward.pb.h b/src/mesh/generated/meshtastic/storeforward.pb.h index 311596c7f..71f2fcad5 100644 --- a/src/mesh/generated/meshtastic/storeforward.pb.h +++ b/src/mesh/generated/meshtastic/storeforward.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index b9c1da7a0..f6d39da6e 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -32,3 +32,4 @@ PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO) + diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index cbe71ed52..a33988129 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/xmodem.pb.cpp b/src/mesh/generated/meshtastic/xmodem.pb.cpp index 8e5cde457..3960ccdaa 100644 --- a/src/mesh/generated/meshtastic/xmodem.pb.cpp +++ b/src/mesh/generated/meshtastic/xmodem.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #include "meshtastic/xmodem.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -11,3 +11,4 @@ PB_BIND(meshtastic_XModem, meshtastic_XModem, AUTO) + diff --git a/src/mesh/generated/meshtastic/xmodem.pb.h b/src/mesh/generated/meshtastic/xmodem.pb.h index 67bd0869f..76edc0df6 100644 --- a/src/mesh/generated/meshtastic/xmodem.pb.h +++ b/src/mesh/generated/meshtastic/xmodem.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 */ +/* Generated by nanopb-0.4.9 */ #ifndef PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED From 234a56446b6352490a2e30c5faec0860e8f17e27 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sun, 6 Oct 2024 22:31:13 +0800 Subject: [PATCH 1366/1377] Add frequencies for Philippines (#4951) There are three different frequencies available for Meshtastic in the Philippines, each with pros and cons: 433 - 434.7 MHz <10 mW erp 868 - 869.4 MHz <25 mW erp 915 - 918 MHz <250 mW EIRP, no external antennna allowed Philippines may also use LORA_24 unrestricted at up to 10mW, or up to 250mW if there is no external antennna. Frequency rules in the Philippines are determined by aggregating the information in laws, following the circulars referenced in the [National Radio Frequency Allocation Table (NRFAT)](https://ntc.gov.ph/wp-content/uploads/2022/frequencyallocations/NRFAT_Rev_2020.pdf) and then circulars that amend the circulars referenced in the NRFAT. A full description of the regulatory basis can be found in the github issue: https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135 For 433MHz and 868MHz we refer to the Low Power Equipment rules for "Non-specific Short Range Devices, Telemetry, Telecommand, Alarms, Data In General and Other Similar Applications.". For 915MHz and Wireless Data Network Services indoor device rules. A device approved by the NTC is required for any use of Meshtastic in the Philippines. fixes https://github.com/meshtastic/firmware/issues/4948 Co-authored-by: Ben Meadors --- src/mesh/RadioInterface.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index b915f94bd..7501852f2 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -136,6 +136,17 @@ const RegionInfo regions[] = { */ RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false), + /* + Philippines + 433 - 434.7 MHz <10 mW erp, NTC approved device required + 868 - 869.4 MHz <25 mW erp, NTC approved device required + 915 - 918 MHz <250 mW EIRP, no external antennna allowed + https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135 + */ + + RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false), + RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false), + /* 2.4 GHZ WLAN Band equivalent. Only for SX128x chips. */ @@ -614,4 +625,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} \ No newline at end of file +} From 93d874b0138de1c18dceff5825ac3de03a78af24 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 7 Oct 2024 05:09:19 -0500 Subject: [PATCH 1367/1377] set tz config from string if unset (#4979) --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index 95ac7c1c3..25059f3c7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -718,6 +718,7 @@ void setup() setenv("TZ", "GMT0", 1); } else { setenv("TZ", (const char *)slipstreamTZString, 1); + strcpy(config.device.tzdef, (const char *)slipstreamTZString); } } tzset(); From 53f189fff4b05b171d6f2500e17d6d14da1e6403 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 7 Oct 2024 06:43:55 -0500 Subject: [PATCH 1368/1377] Remove has_rx * on installDefaultDeviceState (#4982) --- src/mesh/NodeDB.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 49f0cbc62..639e8109b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -626,6 +626,8 @@ void NodeDB::installDefaultDeviceState() // devicestate.node_db_lite_count = 0; devicestate.version = DEVICESTATE_CUR_VER; devicestate.receive_queue_count = 0; // Not yet implemented FIXME + devicestate.has_rx_waypoint = false; + devicestate.has_rx_text_message = false; generatePacketId(); // FIXME - ugly way to init current_packet_id; From 94ecbad90442428a1ecf8a15b8c3d15a4bade060 Mon Sep 17 00:00:00 2001 From: Mictronics Date: Mon, 7 Oct 2024 19:44:21 +0200 Subject: [PATCH 1369/1377] Fix storage of admin key when installing default config. (#4995) * Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28 * Merge PR #420 * Fixed double and missing Default class. * Use correct format specifier and fixed typo. * Removed duplicate code. * Fix error: #if with no expression * Fix warning: extra tokens at end of #endif directive. * Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board. * Fix deprecated macros. * Set RP2040 in dormant mode when deep sleep is triggered. * Fix array out of bounds read. * Admin key count needs to be set otherwise the key will be zero loaded after reset. * Don't reset the admin key size when loading defaults. Preserve an existing key in config if possible. --------- Co-authored-by: Ben Meadors --- src/mesh/NodeDB.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 639e8109b..97ded5a50 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -317,8 +317,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) #ifdef USERPREFS_USE_ADMIN_KEY memcpy(config.security.admin_key[0].bytes, USERPREFS_ADMIN_KEY, 32); config.security.admin_key[0].size = 32; -#else - config.security.admin_key[0].size = 0; + config.security.admin_key_count = 1; #endif if (shouldPreserveKey) { config.security.private_key.size = 32; From 1c54388bb897ac2a5617fc9a85e1b045265826e3 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:16:10 -0400 Subject: [PATCH 1370/1377] Toggle Bluetooth with Fn+b shortcut (#4977) * Toggle Blutooth with Fn+b shortcut Problem: As many are aware, ESP32 devices are known for their high power consumption. For instance, the Heltec ESP32 V3 draws around 110mA when powered on with the screen active and connected to a phone via Bluetooth. The Bluetooth radio alone is responsible for approximately 50mA of that consumption. For keyboard-based standalone devices, which rarely need Bluetooth other than for changing settings, users were forced to keep Bluetooth on regardless of necessity. There was no way to toggle Bluetooth on or off without physically connecting the device to a computer via serial or using the admin channel, which required another node for access. Solution: I implemented a new feature that allows users to turn off Bluetooth on keyboard devices by pressing Fn+b and turn it back on when needed. This enhancement significantly improves power efficiency for these devices. Result: With Bluetooth off, the device now consumes only 55mA. When combined with Power Save mode, the consumption can drop as low as 11mA, a substantial reduction from the previous 110mA. Users can still easily reconnect to a phone using the shortcut when necessary, offering greater flexibility and extended battery life. * Remove 1 reboot at least. I was able to prevent a reboot using the disableBluetooth(); command, current tested at 47-55mA, it doesn't require a reboot to turn off, but it does need reboot to turn back on. * Update CannedMessageModule.cpp --- src/input/InputBroker.h | 1 + src/input/kbI2cBase.cpp | 1 + src/modules/CannedMessageModule.cpp | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 17c621c8a..db7524bb0 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -18,6 +18,7 @@ #define INPUT_BROKER_MSG_RIGHT 0xb7 #define INPUT_BROKER_MSG_FN_SYMBOL_ON 0xf1 #define INPUT_BROKER_MSG_FN_SYMBOL_OFF 0xf2 +#define INPUT_BROKER_MSG_BLUETOOTH_TOGGLE 0xAA typedef struct _InputEvent { const char *source; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 4fbca76e5..8b201cd22 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -297,6 +297,7 @@ int32_t KbI2cBase::runOnce() case 0x9e: // fn+g INPUT_BROKER_MSG_GPS_TOGGLE case 0xaf: // fn+space INPUT_BROKER_MSG_SEND_PING case 0x8b: // fn+del INPUT_BROKEN_MSG_DISMISS_FRAME + case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE // just pass those unmodified e.inputEvent = ANYKEY; e.kbchar = c; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 615a1ab54..ed0dce25f 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -12,6 +12,7 @@ #include "detect/ScanI2C.h" #include "input/ScanAndSelect.h" #include "mesh/generated/meshtastic/cannedmessages.pb.h" +#include "modules/AdminModule.h" #include "main.h" // for cardkb_found #include "modules/ExternalNotificationModule.h" // for buzzer control @@ -268,6 +269,21 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) showTemporaryMessage("GPS Toggled"); #endif break; + case INPUT_BROKER_MSG_BLUETOOTH_TOGGLE: // toggle Bluetooth on/off + if (config.bluetooth.enabled == true) { + config.bluetooth.enabled = false; + LOG_INFO("User toggled Bluetooth"); + nodeDB->saveToDisk(); + disableBluetooth(); + showTemporaryMessage("Bluetooth OFF"); + } else if (config.bluetooth.enabled == false) { + config.bluetooth.enabled = true; + LOG_INFO("User toggled Bluetooth"); + nodeDB->saveToDisk(); + rebootAtMsec = millis() + 2000; + showTemporaryMessage("Bluetooth ON\nReboot"); + } + break; case INPUT_BROKER_MSG_SEND_PING: // fn+space send network ping like double press does service->refreshLocalMeshNode(); if (service->trySendPosition(NODENUM_BROADCAST, true)) { @@ -1145,7 +1161,6 @@ void CannedMessageModule::loadProtoForModule() installDefaultCannedMessageModuleConfig(); } } - /** * @brief Save the module config to file. * From 411aedaf5d52d9d95219df43aa521546fb54cca6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 7 Oct 2024 19:50:44 -0500 Subject: [PATCH 1371/1377] Add health telemetry module (#4927) * Add stub health telemetry module * Add detection for MAX30102 Health Sensor It lives on I2C bus at 0x57, which conflicts with an existing sensor. Add code to check the PARTID register for its response 0x15 per spec. * Add detection for MLX90614 An IR Temperature sensor suitable for livestock monitoring. * Add libraries for MLX90614 and MAX30102 sensors * Fix Trunk * Add support for MLX90614 IR Temperature Sensor * Add support for MAX30102 (Temperature) * Make it build - our first HealthTelemetry on the mesh. If a MAX30102 is connected, its temperature will be sent to the mesh as HealthTelemetry. * Add spo2 and heart rate calculations to MAX30102 * Switch MLX90614 to Adafruit library Sparkfun was having fun with SDA/SCL variables which we can avoid by switching to this highly similar library. * Enable HealthTelemetry if MLX90614 detected * Change MLX90614 emissivity for human skin. * Add health screen! * Remove autogenerated file from branch * Preparing for review * Fix MeshService master sync from before. * Prepare for review * For the americans * Fix native build * Fix for devices with no screen * Remove extra log causing issues --------- Co-authored-by: Tom Fifield --- platformio.ini | 2 + src/configuration.h | 2 + src/detect/ScanI2C.h | 4 +- src/detect/ScanI2CTwoWire.cpp | 14 +- src/main.cpp | 2 + src/mesh/NodeDB.cpp | 2 + src/modules/Modules.cpp | 5 + src/modules/Telemetry/HealthTelemetry.cpp | 249 ++++++++++++++++++ src/modules/Telemetry/HealthTelemetry.h | 60 +++++ .../Telemetry/Sensor/MAX30102Sensor.cpp | 83 ++++++ src/modules/Telemetry/Sensor/MAX30102Sensor.h | 26 ++ .../Telemetry/Sensor/MLX90614Sensor.cpp | 44 ++++ src/modules/Telemetry/Sensor/MLX90614Sensor.h | 24 ++ 13 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 src/modules/Telemetry/HealthTelemetry.cpp create mode 100644 src/modules/Telemetry/HealthTelemetry.h create mode 100644 src/modules/Telemetry/Sensor/MAX30102Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/MAX30102Sensor.h create mode 100644 src/modules/Telemetry/Sensor/MLX90614Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/MLX90614Sensor.h diff --git a/platformio.ini b/platformio.ini index 5dcf61afd..2f76c8236 100644 --- a/platformio.ini +++ b/platformio.ini @@ -152,6 +152,8 @@ lib_deps = ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 + sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@^1.1.2 + adafruit/Adafruit MLX90614 Library@^2.1.5 https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 boschsensortec/BME68x Sensor Library@^1.1.40407 diff --git a/src/configuration.h b/src/configuration.h index 3cd93ec83..975c9fc68 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -144,6 +144,8 @@ along with this program. If not, see . #define MLX90632_ADDR 0x3A #define DFROBOT_LARK_ADDR 0x42 #define NAU7802_ADDR 0x2A +#define MAX30102_ADDR 0x57 +#define MLX90614_ADDR 0x5A // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 3b49026ce..920af06c7 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -52,13 +52,15 @@ class ScanI2C TSL2591, OPT3001, MLX90632, + MLX90614, AHT10, BMX160, DFROBOT_LARK, NAU7802, FT6336U, STK8BAXX, - ICM20948 + ICM20948, + MAX30102 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 43340765a..74597fbc3 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -356,7 +356,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n") - SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n") + case RCWL9620_ADDR: + // get MAX30102 PARTID + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 1); + if (registerValue == 0x15) { + type = MAX30102; + LOG_INFO("MAX30102 Health sensor found\n"); + break; + } else { + type = RCWL9620; + LOG_INFO("RCWL9620 sensor found\n"); + } + break; case LPS22HB_ADDR_ALT: SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n") @@ -394,6 +405,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); + SCAN_SIMPLE_CASE(MLX90614_ADDR, MLX90614, "MLX90614 IR temp sensor found\n"); case ICM20948_ADDR: // same as BMX160_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR diff --git a/src/main.cpp b/src/main.cpp index 25059f3c7..9ddc0864c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -580,10 +580,12 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102) i2cScanner.reset(); #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 97ded5a50..0d9605161 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -533,6 +533,7 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) moduleConfig.telemetry.device_update_interval = UINT32_MAX; moduleConfig.telemetry.environment_update_interval = UINT32_MAX; moduleConfig.telemetry.air_quality_interval = UINT32_MAX; + moduleConfig.telemetry.health_update_interval = UINT32_MAX; } } @@ -543,6 +544,7 @@ void NodeDB::initModuleConfigIntervals() moduleConfig.telemetry.environment_update_interval = 0; moduleConfig.telemetry.air_quality_interval = 0; moduleConfig.telemetry.power_update_interval = 0; + moduleConfig.telemetry.health_update_interval = 0; moduleConfig.neighbor_info.update_interval = 0; moduleConfig.paxcounter.paxcounter_update_interval = 0; } diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 554fad6a9..ad3f0ace4 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -58,6 +58,7 @@ #include "main.h" #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" +#include "modules/Telemetry/HealthTelemetry.h" #endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" @@ -194,6 +195,10 @@ void setupModules() if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } + if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 || + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) { + new HealthTelemetryModule(); + } #endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new PowerTelemetryModule(); diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp new file mode 100644 index 000000000..bcf9d9d57 --- /dev/null +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -0,0 +1,249 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" +#include "HealthTelemetry.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "PowerFSM.h" +#include "RTC.h" +#include "Router.h" +#include "UnitConversions.h" +#include "main.h" +#include "power.h" +#include "sleep.h" +#include "target_specific.h" +#include +#include + +// Sensors +#include "Sensor/MAX30102Sensor.h" +#include "Sensor/MLX90614Sensor.h" + +MAX30102Sensor max30102Sensor; +MLX90614Sensor mlx90614Sensor; + +#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 +#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true + +#if (HAS_SCREEN) +#include "graphics/ScreenFonts.h" +#endif +#include + +int32_t HealthTelemetryModule::runOnce() +{ + if (sleepOnNextExecution == true) { + sleepOnNextExecution = false; + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.health_update_interval, + default_telemetry_broadcast_interval_secs); + LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); + doDeepSleep(nightyNightMs, true); + } + + uint32_t result = UINT32_MAX; + + if (!(moduleConfig.telemetry.health_measurement_enabled || moduleConfig.telemetry.health_screen_enabled)) { + // If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it + return disable(); + } + + if (firstTime) { + // This is the first time the OSThread library has called this function, so do some setup + firstTime = false; + + if (moduleConfig.telemetry.health_measurement_enabled) { + LOG_INFO("Health Telemetry: Initializing\n"); + // Initialize sensors + if (mlx90614Sensor.hasSensor()) + result = mlx90614Sensor.runOnce(); + if (max30102Sensor.hasSensor()) + result = max30102Sensor.runOnce(); + } + return result; + } else { + // if we somehow got to a second run of this module with measurement disabled, then just wait forever + if (!moduleConfig.telemetry.health_measurement_enabled) { + return disable(); + } + + if (((lastSentToMesh == 0) || + !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.health_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && + airTime->isTxAllowedAirUtil()) { + sendTelemetry(); + lastSentToMesh = millis(); + } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && + (service->isToPhoneQueueEmpty())) { + // Just send to phone when it's not our time to send to mesh yet + // Only send while queue is empty (phone assumed connected) + sendTelemetry(NODENUM_BROADCAST, true); + lastSentToPhone = millis(); + } + } + return min(sendToPhoneIntervalMs, result); +} + +bool HealthTelemetryModule::wantUIFrame() +{ + return moduleConfig.telemetry.health_screen_enabled; +} + +void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + + if (lastMeasurementPacket == nullptr) { + // If there's no valid packet, display "Health" + display->drawString(x, y, "Health"); + display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement"); + return; + } + + // Decode the last measurement packet + meshtastic_Telemetry lastMeasurement; + uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); + const char *lastSender = getSenderShortName(*lastMeasurementPacket); + + const meshtastic_Data &p = lastMeasurementPacket->decoded; + if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { + display->drawString(x, y, "Measurement Error"); + LOG_ERROR("Unable to decode last packet"); + return; + } + + // Display "Health From: ..." on its own + display->drawString(x, y, "Health From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + + String last_temp = String(lastMeasurement.variant.health_metrics.temperature, 0) + "°C"; + if (moduleConfig.telemetry.environment_display_fahrenheit) { + last_temp = String(UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.health_metrics.temperature), 0) + "°F"; + } + + // Continue with the remaining details + display->drawString(x, y += _fontHeight(FONT_SMALL), "Temp: " + last_temp); + if (lastMeasurement.variant.health_metrics.has_heart_bpm) { + display->drawString(x, y += _fontHeight(FONT_SMALL), + "Heart Rate: " + String(lastMeasurement.variant.health_metrics.heart_bpm, 0) + " bpm"); + } + if (lastMeasurement.variant.health_metrics.has_spO2) { + display->drawString(x, y += _fontHeight(FONT_SMALL), + "spO2: " + String(lastMeasurement.variant.health_metrics.spO2, 0) + " %"); + } +} + +bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) +{ + if (t->which_variant == meshtastic_Telemetry_health_metrics_tag) { +#ifdef DEBUG_PORT + const char *sender = getSenderShortName(mp); + + LOG_INFO("(Received from %s): temperature=%f, heart_bpm=%d, spO2=%d,\n", sender, t->variant.health_metrics.temperature, + t->variant.health_metrics.heart_bpm, t->variant.health_metrics.spO2); + +#endif + // release previous packet before occupying a new spot + if (lastMeasurementPacket != nullptr) + packetPool.release(lastMeasurementPacket); + + lastMeasurementPacket = packetPool.allocCopy(mp); + } + + return false; // Let others look at this message also if they want +} + +bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m) +{ + bool valid = true; + bool hasSensor = false; + m->time = getTime(); + m->which_variant = meshtastic_Telemetry_health_metrics_tag; + m->variant.health_metrics = meshtastic_HealthMetrics_init_zero; + + if (max30102Sensor.hasSensor()) { + valid = valid && max30102Sensor.getMetrics(m); + hasSensor = true; + } + if (mlx90614Sensor.hasSensor()) { + valid = valid && mlx90614Sensor.getMetrics(m); + hasSensor = true; + } + + return valid && hasSensor; +} + +meshtastic_MeshPacket *HealthTelemetryModule::allocReply() +{ + if (currentRequest) { + auto req = *currentRequest; + const auto &p = req.decoded; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding HealthTelemetry module!\n"); + return NULL; + } + // Check for a request for health metrics + if (decoded->which_variant == meshtastic_Telemetry_health_metrics_tag) { + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + if (getHealthTelemetry(&m)) { + LOG_INFO("Health telemetry replying to request\n"); + return allocDataProtobuf(m); + } else { + return NULL; + } + } + } + return NULL; +} + +bool HealthTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) +{ + meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + m.which_variant = meshtastic_Telemetry_health_metrics_tag; + m.time = getTime(); + if (getHealthTelemetry(&m)) { + LOG_INFO("(Sending): temperature=%f, heart_bpm=%d, spO2=%d\n", m.variant.health_metrics.temperature, + m.variant.health_metrics.heart_bpm, m.variant.health_metrics.spO2); + + sensor_read_error_count = 0; + + meshtastic_MeshPacket *p = allocDataProtobuf(m); + p->to = dest; + p->decoded.want_response = false; + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + else + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + // release previous packet before occupying a new spot + if (lastMeasurementPacket != nullptr) + packetPool.release(lastMeasurementPacket); + + lastMeasurementPacket = packetPool.allocCopy(*p); + if (phoneOnly) { + LOG_INFO("Sending packet to phone\n"); + service->sendToPhone(p); + } else { + LOG_INFO("Sending packet to mesh\n"); + service->sendToMesh(p, RX_SRC_LOCAL, true); + + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { + LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); + sleepOnNextExecution = true; + setIntervalFromNow(5000); + } + } + return true; + } + return false; +} + +#endif diff --git a/src/modules/Telemetry/HealthTelemetry.h b/src/modules/Telemetry/HealthTelemetry.h new file mode 100644 index 000000000..4ad0da838 --- /dev/null +++ b/src/modules/Telemetry/HealthTelemetry.h @@ -0,0 +1,60 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) + +#pragma once +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "NodeDB.h" +#include "ProtobufModule.h" +#include +#include + +class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModule +{ + CallbackObserver nodeStatusObserver = + CallbackObserver(this, &HealthTelemetryModule::handleStatusUpdate); + + public: + HealthTelemetryModule() + : concurrency::OSThread("HealthTelemetryModule"), + ProtobufModule("HealthTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) + { + lastMeasurementPacket = nullptr; + nodeStatusObserver.observe(&nodeStatus->onNewStatus); + setIntervalFromNow(10 * 1000); + } + +#if !HAS_SCREEN + void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +#else + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif + + virtual bool wantUIFrame() override; + + protected: + /** Called to handle a particular incoming message + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; + virtual int32_t runOnce() override; + /** Called to get current Health telemetry data + @return true if it contains valid data + */ + bool getHealthTelemetry(meshtastic_Telemetry *m); + virtual meshtastic_MeshPacket *allocReply() override; + /** + * Send our Telemetry into the mesh + */ + bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + + private: + bool firstTime = 1; + meshtastic_MeshPacket *lastMeasurementPacket; + uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute + uint32_t lastSentToMesh = 0; + uint32_t lastSentToPhone = 0; + uint32_t sensor_read_error_count = 0; +}; + +#endif diff --git a/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp b/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp new file mode 100644 index 000000000..b3b20e5f2 --- /dev/null +++ b/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp @@ -0,0 +1,83 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "MAX30102Sensor.h" +#include "TelemetrySensor.h" +#include + +MAX30102Sensor::MAX30102Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MAX30102, "MAX30102") {} + +int32_t MAX30102Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + if (max30102.begin(*nodeTelemetrySensorsMap[sensorType].second, _speed, nodeTelemetrySensorsMap[sensorType].first) == + true) // MAX30102 init + { + byte brightness = 60; // 0=Off to 255=50mA + byte sampleAverage = 4; // 1, 2, 4, 8, 16, 32 + byte leds = 2; // 1 = Red only, 2 = Red + IR + byte sampleRate = 100; // 50, 100, 200, 400, 800, 1000, 1600, 3200 + int pulseWidth = 411; // 69, 118, 215, 411 + int adcRange = 4096; // 2048, 4096, 8192, 16384 + + max30102.enableDIETEMPRDY(); // Enable the temperature ready interrupt + max30102.setup(brightness, sampleAverage, leds, sampleRate, pulseWidth, adcRange); + LOG_DEBUG("MAX30102 Init Succeed\n"); + status = true; + } else { + LOG_ERROR("MAX30102 Init Failed\n"); + status = false; + } + return initI2CSensor(); +} + +void MAX30102Sensor::setup() {} + +bool MAX30102Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + uint32_t ir_buff[MAX30102_BUFFER_LEN]; + uint32_t red_buff[MAX30102_BUFFER_LEN]; + int32_t spo2; + int8_t spo2_valid; + int32_t heart_rate; + int8_t heart_rate_valid; + float temp = max30102.readTemperature(); + + measurement->variant.environment_metrics.temperature = temp; + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.health_metrics.temperature = temp; + measurement->variant.health_metrics.has_temperature = true; + for (byte i = 0; i < MAX30102_BUFFER_LEN; i++) { + while (max30102.available() == false) + max30102.check(); + + red_buff[i] = max30102.getRed(); + ir_buff[i] = max30102.getIR(); + max30102.nextSample(); + } + + maxim_heart_rate_and_oxygen_saturation(ir_buff, MAX30102_BUFFER_LEN, red_buff, &spo2, &spo2_valid, &heart_rate, + &heart_rate_valid); + LOG_DEBUG("heart_rate=%d(%d), sp02=%d(%d)", heart_rate, heart_rate_valid, spo2, spo2_valid); + if (heart_rate_valid) { + measurement->variant.health_metrics.has_heart_bpm = true; + measurement->variant.health_metrics.heart_bpm = heart_rate; + } else { + measurement->variant.health_metrics.has_heart_bpm = false; + } + if (spo2_valid) { + measurement->variant.health_metrics.has_spO2 = true; + measurement->variant.health_metrics.spO2 = spo2; + } else { + measurement->variant.health_metrics.has_spO2 = true; + } + return true; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/MAX30102Sensor.h b/src/modules/Telemetry/Sensor/MAX30102Sensor.h new file mode 100644 index 000000000..426d9d365 --- /dev/null +++ b/src/modules/Telemetry/Sensor/MAX30102Sensor.h @@ -0,0 +1,26 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#define MAX30102_BUFFER_LEN 100 + +class MAX30102Sensor : public TelemetrySensor +{ + private: + MAX30105 max30102 = MAX30105(); + uint32_t _speed = 200000UL; + + protected: + virtual void setup() override; + + public: + MAX30102Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif diff --git a/src/modules/Telemetry/Sensor/MLX90614Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90614Sensor.cpp new file mode 100644 index 000000000..92c22bf21 --- /dev/null +++ b/src/modules/Telemetry/Sensor/MLX90614Sensor.cpp @@ -0,0 +1,44 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "MLX90614Sensor.h" +#include "TelemetrySensor.h" +MLX90614Sensor::MLX90614Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MLX90614, "MLX90614") {} + +int32_t MLX90614Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + if (mlx.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second) == true) // MLX90614 init + { + LOG_DEBUG("MLX90614 emissivity: %f", mlx.readEmissivity()); + if (fabs(MLX90614_EMISSIVITY - mlx.readEmissivity()) > 0.001) { + mlx.writeEmissivity(MLX90614_EMISSIVITY); + LOG_INFO("MLX90614 emissivity updated. In case of weird data, power cycle."); + } + LOG_DEBUG("MLX90614 Init Succeed\n"); + status = true; + } else { + LOG_ERROR("MLX90614 Init Failed\n"); + status = false; + } + return initI2CSensor(); +} + +void MLX90614Sensor::setup() {} + +bool MLX90614Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.temperature = mlx.readAmbientTempC(); + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.health_metrics.temperature = mlx.readObjectTempC(); + measurement->variant.health_metrics.has_temperature = true; + return true; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/MLX90614Sensor.h b/src/modules/Telemetry/Sensor/MLX90614Sensor.h new file mode 100644 index 000000000..00f63449e --- /dev/null +++ b/src/modules/Telemetry/Sensor/MLX90614Sensor.h @@ -0,0 +1,24 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#define MLX90614_EMISSIVITY 0.98 // human skin + +class MLX90614Sensor : public TelemetrySensor +{ + private: + Adafruit_MLX90614 mlx = Adafruit_MLX90614(); + + protected: + virtual void setup() override; + + public: + MLX90614Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif From 37f294d0a6b4d8d224c0383632747fce3796c320 Mon Sep 17 00:00:00 2001 From: jhps Date: Mon, 7 Oct 2024 18:39:59 -0700 Subject: [PATCH 1372/1377] In shutdown, on button press, wake back to application rather than into the loader. (#4997) Suggested by lyusupov and implemented by todd-herbert. https://github.com/meshtastic/firmware/issues/4651 --- src/platform/nrf52/main-nrf52.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 4023a3cb9..f9329d875 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -270,6 +270,11 @@ void cpuDeepSleep(uint32_t msecToWake) 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_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 From 2e5399dbe46b548c0b567a7ea120e5f7ee224f19 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 8 Oct 2024 05:03:43 -0500 Subject: [PATCH 1373/1377] De-conflict MLX90614_ADDR macro --- src/configuration.h | 2 +- src/detect/ScanI2CTwoWire.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 975c9fc68..729d6b046 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -145,7 +145,7 @@ along with this program. If not, see . #define DFROBOT_LARK_ADDR 0x42 #define NAU7802_ADDR 0x2A #define MAX30102_ADDR 0x57 -#define MLX90614_ADDR 0x5A +#define MLX90614_ADDR_DEF 0x5A // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 74597fbc3..3f4f88f74 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -405,7 +405,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); - SCAN_SIMPLE_CASE(MLX90614_ADDR, MLX90614, "MLX90614 IR temp sensor found\n"); + SCAN_SIMPLE_CASE(MLX90614_ADDR_DEF, MLX90614, "MLX90614 IR temp sensor found\n"); case ICM20948_ADDR: // same as BMX160_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR From a0dd7b43d56c5a78d6e7f58ad1c355c95720be89 Mon Sep 17 00:00:00 2001 From: TheMalkavien Date: Tue, 8 Oct 2024 12:24:37 +0200 Subject: [PATCH 1374/1377] First version of a DeepSleep state for the RP2040 (#4976) * Adding pico-extra utils * RP2040 can now go to deepsleep * First RP2040 DeepSleep code - TODO : do better and restore * FIX RAK11310 compilation (revert SDK + missing defines) --------- Co-authored-by: Ben Meadors --- arch/rp2xx0/rp2040.ini | 6 +- src/Power.cpp | 2 +- .../hardware_rosc/include/hardware/rosc.h | 90 ++++++++++ src/platform/rp2xx0/hardware_rosc/rosc.c | 61 +++++++ src/platform/rp2xx0/main-rp2xx0.cpp | 57 ++++++- .../rp2xx0/pico_sleep/include/pico/sleep.h | 107 ++++++++++++ src/platform/rp2xx0/pico_sleep/sleep.c | 159 ++++++++++++++++++ src/shutdown.h | 2 +- variants/rak11310/platformio.ini | 2 + variants/rak11310/variant.h | 1 + variants/rp2040-lora/variant.h | 1 + 11 files changed, 479 insertions(+), 9 deletions(-) create mode 100644 src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h create mode 100644 src/platform/rp2xx0/hardware_rosc/rosc.c create mode 100644 src/platform/rp2xx0/pico_sleep/include/pico/sleep.h create mode 100644 src/platform/rp2xx0/pico_sleep/sleep.c diff --git a/arch/rp2xx0/rp2040.ini b/arch/rp2xx0/rp2040.ini index 5b4ec74d2..c004a3bec 100644 --- a/arch/rp2xx0/rp2040.ini +++ b/arch/rp2xx0/rp2040.ini @@ -1,14 +1,16 @@ ; Common settings for rp2040 Processor based targets [rp2040_base] -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2 +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#v1.2.0-gcc12 extends = arduino_base -platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2 +platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#4.0.3 board_build.core = earlephilhower board_build.filesystem_size = 0.5m build_flags = ${arduino_base.build_flags} -Wno-unused-variable -Wcast-align -Isrc/platform/rp2xx0 + -Isrc/platform/rp2xx0/hardware_rosc/include + -Isrc/platform/rp2xx0/pico_sleep/include -D__PLAT_RP2040__ # -D _POSIX_THREADS build_src_filter = diff --git a/src/Power.cpp b/src/Power.cpp index 2f5f441ae..2fe28633a 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -588,7 +588,7 @@ void Power::shutdown() { LOG_INFO("Shutting down\n"); -#if defined(ARCH_NRF52) || defined(ARCH_ESP32) +#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040) #ifdef PIN_LED1 ledOff(PIN_LED1); #endif diff --git a/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h b/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h new file mode 100644 index 000000000..2720f0b93 --- /dev/null +++ b/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _HARDWARE_ROSC_H_ +#define _HARDWARE_ROSC_H_ + +#include "pico.h" +#include "hardware/structs/rosc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file rosc.h + * \defgroup hardware_rosc hardware_rosc + * + * Ring Oscillator (ROSC) API + * + * A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of + * inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the + * first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a + * crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is + * more accurate than the ring oscillator. + */ + +/*! \brief Set frequency of the Ring Oscillator + * \ingroup hardware_rosc + * + * \param code The drive strengths. See the RP2040 datasheet for information on this value. + */ +void rosc_set_freq(uint32_t code); + +/*! \brief Set range of the Ring Oscillator + * \ingroup hardware_rosc + * + * Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT). + * Clock output will not glitch when changing the range up one step at a time. + * + * \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High. + */ +void rosc_set_range(uint range); + +/*! \brief Disable the Ring Oscillator + * \ingroup hardware_rosc + * + */ +void rosc_disable(void); + +/*! \brief Put Ring Oscillator in to dormant mode. + * \ingroup hardware_rosc + * + * The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt. + * This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low. + * If no IRQ is configured before going into dormant mode the ROSC will never restart. + * + * PLLs should be stopped before selecting dormant mode. + */ +void rosc_set_dormant(void); + +// FIXME: Add doxygen + +uint32_t next_rosc_code(uint32_t code); + +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz); + +void rosc_set_div(uint32_t div); + +inline static void rosc_clear_bad_write(void) { + hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); +} + +inline static bool rosc_write_okay(void) { + return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); +} + +inline static void rosc_write(io_rw_32 *addr, uint32_t value) { + rosc_clear_bad_write(); + assert(rosc_write_okay()); + *addr = value; + assert(rosc_write_okay()); +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/platform/rp2xx0/hardware_rosc/rosc.c b/src/platform/rp2xx0/hardware_rosc/rosc.c new file mode 100644 index 000000000..69b6012a1 --- /dev/null +++ b/src/platform/rp2xx0/hardware_rosc/rosc.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" + +// For MHZ definitions etc +#include "hardware/clocks.h" +#include "hardware/rosc.h" + +// Given a ROSC delay stage code, return the next-numerically-higher code. +// Top result bit is set when called on maximum ROSC code. +uint32_t next_rosc_code(uint32_t code) { + return ((code | 0x08888888u) + 1u) & 0xf7777777u; +} + +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { + // TODO: This could be a lot better + rosc_set_div(1); + for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { + rosc_set_freq(code); + uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000; + if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) { + return rosc_mhz; + } + } + return 0; +} + +void rosc_set_div(uint32_t div) { + assert(div <= 31 && div >= 1); + rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); +} + +void rosc_set_freq(uint32_t code) { + rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); + rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u)); +} + +void rosc_set_range(uint range) { + // Range should use enumvals from the headers and thus have the password correct + rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); +} + +void rosc_disable(void) { + uint32_t tmp = rosc_hw->ctrl; + tmp &= (~ROSC_CTRL_ENABLE_BITS); + tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); + rosc_write(&rosc_hw->ctrl, tmp); + // Wait for stable to go away + while(rosc_hw->status & ROSC_STATUS_STABLE_BITS); +} + +void rosc_set_dormant(void) { + // WARNING: This stops the rosc until woken up by an irq + rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); + // Wait for it to become stable once woken up + while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); +} \ No newline at end of file diff --git a/src/platform/rp2xx0/main-rp2xx0.cpp b/src/platform/rp2xx0/main-rp2xx0.cpp index 6306f34c1..60847f318 100644 --- a/src/platform/rp2xx0/main-rp2xx0.cpp +++ b/src/platform/rp2xx0/main-rp2xx0.cpp @@ -4,20 +4,67 @@ #include #include #include -#include +#include void setBluetoothEnable(bool enable) { // not needed } +static bool awake; + +static void sleep_callback(void) { + awake = true; +} + +void epoch_to_datetime(time_t epoch, datetime_t *dt) { + struct tm *tm_info; + + tm_info = gmtime(&epoch); + dt->year = tm_info->tm_year; + dt->month = tm_info->tm_mon + 1; + dt->day = tm_info->tm_mday; + dt->dotw = tm_info->tm_wday; + dt->hour = tm_info->tm_hour; + dt->min = tm_info->tm_min; + dt->sec = tm_info->tm_sec; +} + +void debug_date(datetime_t t) +{ + LOG_DEBUG("%d %d %d %d %d %d %d\n", t.year, t.month, t.day, t.hour, t.min, t.sec, t.dotw); + uart_default_tx_wait_blocking(); +} + void cpuDeepSleep(uint32_t msecs) { - /* Disable both PLL to avoid power dissipation */ - pll_deinit(pll_sys); - pll_deinit(pll_usb); + + time_t seconds = (time_t)(msecs/1000); + datetime_t t_init, t_alarm; + + awake = false; + // Start the RTC + rtc_init(); + epoch_to_datetime(0, &t_init); + rtc_set_datetime(&t_init); + epoch_to_datetime(seconds, &t_alarm); + // debug_date(t_init); + // debug_date(t_alarm); + uart_default_tx_wait_blocking(); + sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); + sleep_goto_sleep_until(&t_alarm, &sleep_callback); + + // Make sure we don't wake + while (!awake) { + delay(1); + } + + /* For now, I don't know how to revert this state + We just reboot in order to get back operational */ + rp2040.reboot(); + /* Set RP2040 in dormant mode. Will not wake up. */ - xosc_dormant(); + // xosc_dormant(); } void updateBatteryLevel(uint8_t level) diff --git a/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h b/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h new file mode 100644 index 000000000..b97a23169 --- /dev/null +++ b/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_SLEEP_H_ +#define _PICO_SLEEP_H_ + +#include "pico.h" +#include "hardware/rtc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file sleep.h + * \defgroup hardware_sleep hardware_sleep + * + * Lower Power Sleep API + * + * The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, + * until the source (either xosc or rosc) is started again by an external event. + * In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks + * block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) + * can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. + * + * \subsection sleep_example Example + * \addtogroup hardware_sleep + * \include hello_sleep.c + + */ +typedef enum { + DORMANT_SOURCE_NONE, + DORMANT_SOURCE_XOSC, + DORMANT_SOURCE_ROSC +} dormant_source_t; + +/*! \brief Set all clock sources to the the dormant clock source to prepare for sleep. + * \ingroup hardware_sleep + * + * \param dormant_source The dormant clock source to use + */ +void sleep_run_from_dormant_source(dormant_source_t dormant_source); + +/*! \brief Set the dormant clock source to be the crystal oscillator + * \ingroup hardware_sleep + */ +static inline void sleep_run_from_xosc(void) { + sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC); +} + +/*! \brief Set the dormant clock source to be the ring oscillator + * \ingroup hardware_sleep + */ +static inline void sleep_run_from_rosc(void) { + sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); +} + +/*! \brief Send system to sleep until the specified time + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param t The time to wake up + * \param callback Function to call on wakeup. + */ +void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback); + +/*! \brief Send system to sleep until the specified GPIO changes + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param gpio_pin The pin to provide the wake up + * \param edge true for leading edge, false for trailing edge + * \param high true for active high, false for active low + */ +void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high); + +/*! \brief Send system to sleep until a leading high edge is detected on GPIO + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param gpio_pin The pin to provide the wake up + */ +static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) { + sleep_goto_dormant_until_pin(gpio_pin, true, true); +} + +/*! \brief Send system to sleep until a high level is detected on GPIO + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param gpio_pin The pin to provide the wake up + */ +static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) { + sleep_goto_dormant_until_pin(gpio_pin, false, true); +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/platform/rp2xx0/pico_sleep/sleep.c b/src/platform/rp2xx0/pico_sleep/sleep.c new file mode 100644 index 000000000..1bb8db699 --- /dev/null +++ b/src/platform/rp2xx0/pico_sleep/sleep.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" + +#include "pico/stdlib.h" +#include "pico/sleep.h" + +#include "hardware/rtc.h" +#include "hardware/pll.h" +#include "hardware/clocks.h" +#include "hardware/xosc.h" +#include "hardware/rosc.h" +#include "hardware/regs/io_bank0.h" +// For __wfi +#include "hardware/sync.h" +// For scb_hw so we can enable deep sleep +#include "hardware/structs/scb.h" +// when using old SDK this macro is not defined +#ifndef XOSC_HZ + #define XOSC_HZ 12000000u +#endif +// The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, +// until the source (either xosc or rosc) is started again by an external event. +// In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks +// block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) +// can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. + + +// TODO: Optionally, memories can also be powered down. + +static dormant_source_t _dormant_source; + +bool dormant_source_valid(dormant_source_t dormant_source) { + return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); +} + +// In order to go into dormant mode we need to be running from a stoppable clock source: +// either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks +// and all PLLs +void sleep_run_from_dormant_source(dormant_source_t dormant_source) { + assert(dormant_source_valid(dormant_source)); + _dormant_source = dormant_source; + + // FIXME: Just defining average rosc freq here. + uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_HZ : 6.5 * MHZ; + uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? + CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : + CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; + + // CLK_REF = XOSC or ROSC + clock_configure(clk_ref, + clk_ref_src, + 0, // No aux mux + src_hz, + src_hz); + + // CLK SYS = CLK_REF + clock_configure(clk_sys, + CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, + 0, // Using glitchless mux + src_hz, + src_hz); + + // CLK USB = 0MHz + clock_stop(clk_usb); + + // CLK ADC = 0MHz + clock_stop(clk_adc); + + // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc + uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; + + clock_configure(clk_rtc, + 0, // No GLMUX + clk_rtc_src, + src_hz, + 46875); + + // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable + clock_configure(clk_peri, + 0, + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, + src_hz, + src_hz); + + pll_deinit(pll_sys); + pll_deinit(pll_usb); + + // Assuming both xosc and rosc are running at the moment + if (dormant_source == DORMANT_SOURCE_XOSC) { + // Can disable rosc + rosc_disable(); + } else { + // Can disable xosc + xosc_disable(); + } + + // Reconfigure uart with new clocks + /* This dones not work with our current core */ + //setup_default_uart(); +} + +// Go to sleep until woken up by the RTC +void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { + // We should have already called the sleep_run_from_dormant_source function + assert(dormant_source_valid(_dormant_source)); + + // Turn off all clocks when in sleep mode except for RTC + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + clocks_hw->sleep_en1 = 0x0; + + rtc_set_alarm(t, callback); + + uint save = scb_hw->scr; + // Enable deep sleep at the proc + scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; + + // Go to sleep + __wfi(); +} + +static void _go_dormant(void) { + assert(dormant_source_valid(_dormant_source)); + + if (_dormant_source == DORMANT_SOURCE_XOSC) { + xosc_dormant(); + } else { + rosc_set_dormant(); + } +} + +void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { + bool low = !high; + bool level = !edge; + + // Configure the appropriate IRQ at IO bank 0 + assert(gpio_pin < NUM_BANK0_GPIOS); + + uint32_t event = 0; + + if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; + if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; + if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; + if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; + + gpio_set_dormant_irq_enabled(gpio_pin, event, true); + + _go_dormant(); + // Execution stops here until woken up + + // Clear the irq so we can go back to dormant mode again if we want + gpio_acknowledge_irq(gpio_pin, event); +} \ No newline at end of file diff --git a/src/shutdown.h b/src/shutdown.h index 3f191eea8..481e7778d 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -44,7 +44,7 @@ void powerCommandsCheck() if (shutdownAtMsec && millis() > shutdownAtMsec) { LOG_INFO("Shutting down from admin command\n"); -#if defined(ARCH_NRF52) || defined(ARCH_ESP32) +#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040) playShutdownMelody(); power->shutdown(); #elif defined(ARCH_PORTDUINO) diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index e1bd2b1b0..c7b3504fe 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -2,6 +2,8 @@ extends = rp2040_base board = wiscore_rak11300 upload_protocol = picotool +# keep an old SDK to use less memory. +platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2 # add our variants files to the include and src paths build_flags = ${rp2040_base.build_flags} diff --git a/variants/rak11310/variant.h b/variants/rak11310/variant.h index f9dcbd91a..54e403ee7 100644 --- a/variants/rak11310/variant.h +++ b/variants/rak11310/variant.h @@ -6,6 +6,7 @@ #define LED_CONN PIN_LED2 #define LED_PIN LED_BUILTIN +#define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 #define BUTTON_NEED_PULLUP diff --git a/variants/rp2040-lora/variant.h b/variants/rp2040-lora/variant.h index d0bbc0ec3..f1826605f 100644 --- a/variants/rp2040-lora/variant.h +++ b/variants/rp2040-lora/variant.h @@ -23,6 +23,7 @@ // ratio of voltage divider = 3.0 (R17=200k, R18=100k) // #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define HAS_CPU_SHUTDOWN 1 #define USE_SX1262 #undef LORA_SCK From a05b009379fc15ddcbc08a0af096d9fbb8697554 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 8 Oct 2024 05:33:38 -0500 Subject: [PATCH 1375/1377] Posthumous tronkination --- src/mesh/RadioInterface.h | 4 +- .../hardware_rosc/include/hardware/rosc.h | 11 ++- src/platform/rp2xx0/hardware_rosc/rosc.c | 27 ++++--- src/platform/rp2xx0/main-rp2xx0.cpp | 12 +-- .../rp2xx0/pico_sleep/include/pico/sleep.h | 20 ++--- src/platform/rp2xx0/pico_sleep/sleep.c | 74 +++++++++---------- 6 files changed, 79 insertions(+), 69 deletions(-) diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6df51ce1a..89a4c7087 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -55,7 +55,7 @@ typedef struct { PacketHeader header; /** The payload, of maximum length minus the header, aligned just to be sure */ - uint8_t payload[MAX_LORA_PAYLOAD_LEN + 1 - sizeof(PacketHeader)] __attribute__ ((__aligned__)); + uint8_t payload[MAX_LORA_PAYLOAD_LEN + 1 - sizeof(PacketHeader)] __attribute__((__aligned__)); } RadioBuffer; @@ -105,7 +105,7 @@ class RadioInterface /** * A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need * */ - RadioBuffer radioBuffer __attribute__ ((__aligned__)); + RadioBuffer radioBuffer __attribute__((__aligned__)); /** * Enqueue a received packet for the registered receiver */ diff --git a/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h b/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h index 2720f0b93..e1e014f33 100644 --- a/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h +++ b/src/platform/rp2xx0/hardware_rosc/include/hardware/rosc.h @@ -7,8 +7,8 @@ #ifndef _HARDWARE_ROSC_H_ #define _HARDWARE_ROSC_H_ -#include "pico.h" #include "hardware/structs/rosc.h" +#include "pico.h" #ifdef __cplusplus extern "C" { @@ -68,15 +68,18 @@ uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz); void rosc_set_div(uint32_t div); -inline static void rosc_clear_bad_write(void) { +inline static void rosc_clear_bad_write(void) +{ hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); } -inline static bool rosc_write_okay(void) { +inline static bool rosc_write_okay(void) +{ return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); } -inline static void rosc_write(io_rw_32 *addr, uint32_t value) { +inline static void rosc_write(io_rw_32 *addr, uint32_t value) +{ rosc_clear_bad_write(); assert(rosc_write_okay()); *addr = value; diff --git a/src/platform/rp2xx0/hardware_rosc/rosc.c b/src/platform/rp2xx0/hardware_rosc/rosc.c index 69b6012a1..f79929f8d 100644 --- a/src/platform/rp2xx0/hardware_rosc/rosc.c +++ b/src/platform/rp2xx0/hardware_rosc/rosc.c @@ -12,11 +12,13 @@ // Given a ROSC delay stage code, return the next-numerically-higher code. // Top result bit is set when called on maximum ROSC code. -uint32_t next_rosc_code(uint32_t code) { +uint32_t next_rosc_code(uint32_t code) +{ return ((code | 0x08888888u) + 1u) & 0xf7777777u; } -uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) +{ // TODO: This could be a lot better rosc_set_div(1); for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { @@ -29,33 +31,40 @@ uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { return 0; } -void rosc_set_div(uint32_t div) { +void rosc_set_div(uint32_t div) +{ assert(div <= 31 && div >= 1); rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); } -void rosc_set_freq(uint32_t code) { +void rosc_set_freq(uint32_t code) +{ rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u)); } -void rosc_set_range(uint range) { +void rosc_set_range(uint range) +{ // Range should use enumvals from the headers and thus have the password correct rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); } -void rosc_disable(void) { +void rosc_disable(void) +{ uint32_t tmp = rosc_hw->ctrl; tmp &= (~ROSC_CTRL_ENABLE_BITS); tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); rosc_write(&rosc_hw->ctrl, tmp); // Wait for stable to go away - while(rosc_hw->status & ROSC_STATUS_STABLE_BITS); + while (rosc_hw->status & ROSC_STATUS_STABLE_BITS) + ; } -void rosc_set_dormant(void) { +void rosc_set_dormant(void) +{ // WARNING: This stops the rosc until woken up by an irq rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); // Wait for it to become stable once woken up - while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); + while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)) + ; } \ No newline at end of file diff --git a/src/platform/rp2xx0/main-rp2xx0.cpp b/src/platform/rp2xx0/main-rp2xx0.cpp index 60847f318..67bf1eb08 100644 --- a/src/platform/rp2xx0/main-rp2xx0.cpp +++ b/src/platform/rp2xx0/main-rp2xx0.cpp @@ -2,9 +2,9 @@ #include "hardware/xosc.h" #include #include +#include #include #include -#include void setBluetoothEnable(bool enable) { @@ -13,11 +13,13 @@ void setBluetoothEnable(bool enable) static bool awake; -static void sleep_callback(void) { +static void sleep_callback(void) +{ awake = true; } -void epoch_to_datetime(time_t epoch, datetime_t *dt) { +void epoch_to_datetime(time_t epoch, datetime_t *dt) +{ struct tm *tm_info; tm_info = gmtime(&epoch); @@ -39,7 +41,7 @@ void debug_date(datetime_t t) void cpuDeepSleep(uint32_t msecs) { - time_t seconds = (time_t)(msecs/1000); + time_t seconds = (time_t)(msecs / 1000); datetime_t t_init, t_alarm; awake = false; @@ -54,7 +56,7 @@ void cpuDeepSleep(uint32_t msecs) sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); sleep_goto_sleep_until(&t_alarm, &sleep_callback); - // Make sure we don't wake + // Make sure we don't wake while (!awake) { delay(1); } diff --git a/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h b/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h index b97a23169..17dff2468 100644 --- a/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h +++ b/src/platform/rp2xx0/pico_sleep/include/pico/sleep.h @@ -7,8 +7,8 @@ #ifndef _PICO_SLEEP_H_ #define _PICO_SLEEP_H_ -#include "pico.h" #include "hardware/rtc.h" +#include "pico.h" #ifdef __cplusplus extern "C" { @@ -30,11 +30,7 @@ extern "C" { * \include hello_sleep.c */ -typedef enum { - DORMANT_SOURCE_NONE, - DORMANT_SOURCE_XOSC, - DORMANT_SOURCE_ROSC -} dormant_source_t; +typedef enum { DORMANT_SOURCE_NONE, DORMANT_SOURCE_XOSC, DORMANT_SOURCE_ROSC } dormant_source_t; /*! \brief Set all clock sources to the the dormant clock source to prepare for sleep. * \ingroup hardware_sleep @@ -46,14 +42,16 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source); /*! \brief Set the dormant clock source to be the crystal oscillator * \ingroup hardware_sleep */ -static inline void sleep_run_from_xosc(void) { +static inline void sleep_run_from_xosc(void) +{ sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC); } /*! \brief Set the dormant clock source to be the ring oscillator * \ingroup hardware_sleep */ -static inline void sleep_run_from_rosc(void) { +static inline void sleep_run_from_rosc(void) +{ sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); } @@ -85,7 +83,8 @@ void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high); * * \param gpio_pin The pin to provide the wake up */ -static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) { +static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) +{ sleep_goto_dormant_until_pin(gpio_pin, true, true); } @@ -96,7 +95,8 @@ static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) { * * \param gpio_pin The pin to provide the wake up */ -static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) { +static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) +{ sleep_goto_dormant_until_pin(gpio_pin, false, true); } diff --git a/src/platform/rp2xx0/pico_sleep/sleep.c b/src/platform/rp2xx0/pico_sleep/sleep.c index 1bb8db699..65096be85 100644 --- a/src/platform/rp2xx0/pico_sleep/sleep.c +++ b/src/platform/rp2xx0/pico_sleep/sleep.c @@ -6,22 +6,22 @@ #include "pico.h" -#include "pico/stdlib.h" #include "pico/sleep.h" +#include "pico/stdlib.h" -#include "hardware/rtc.h" -#include "hardware/pll.h" #include "hardware/clocks.h" -#include "hardware/xosc.h" -#include "hardware/rosc.h" +#include "hardware/pll.h" #include "hardware/regs/io_bank0.h" +#include "hardware/rosc.h" +#include "hardware/rtc.h" +#include "hardware/xosc.h" // For __wfi #include "hardware/sync.h" // For scb_hw so we can enable deep sleep #include "hardware/structs/scb.h" // when using old SDK this macro is not defined #ifndef XOSC_HZ - #define XOSC_HZ 12000000u +#define XOSC_HZ 12000000u #endif // The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, // until the source (either xosc or rosc) is started again by an external event. @@ -29,41 +29,37 @@ // block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) // can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. - // TODO: Optionally, memories can also be powered down. static dormant_source_t _dormant_source; -bool dormant_source_valid(dormant_source_t dormant_source) { +bool dormant_source_valid(dormant_source_t dormant_source) +{ return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); } // In order to go into dormant mode we need to be running from a stoppable clock source: // either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks // and all PLLs -void sleep_run_from_dormant_source(dormant_source_t dormant_source) { +void sleep_run_from_dormant_source(dormant_source_t dormant_source) +{ assert(dormant_source_valid(dormant_source)); _dormant_source = dormant_source; // FIXME: Just defining average rosc freq here. uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_HZ : 6.5 * MHZ; - uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? - CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : - CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; + uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC + : CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; // CLK_REF = XOSC or ROSC - clock_configure(clk_ref, - clk_ref_src, + clock_configure(clk_ref, clk_ref_src, 0, // No aux mux - src_hz, - src_hz); + src_hz, src_hz); // CLK SYS = CLK_REF - clock_configure(clk_sys, - CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, + clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, // Using glitchless mux - src_hz, - src_hz); + src_hz, src_hz); // CLK USB = 0MHz clock_stop(clk_usb); @@ -72,22 +68,15 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source) { clock_stop(clk_adc); // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc - uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? - CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : - CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; + uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC + : CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; clock_configure(clk_rtc, 0, // No GLMUX - clk_rtc_src, - src_hz, - 46875); + clk_rtc_src, src_hz, 46875); // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable - clock_configure(clk_peri, - 0, - CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, - src_hz, - src_hz); + clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, src_hz, src_hz); pll_deinit(pll_sys); pll_deinit(pll_usb); @@ -103,11 +92,12 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source) { // Reconfigure uart with new clocks /* This dones not work with our current core */ - //setup_default_uart(); + // setup_default_uart(); } // Go to sleep until woken up by the RTC -void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { +void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) +{ // We should have already called the sleep_run_from_dormant_source function assert(dormant_source_valid(_dormant_source)); @@ -125,7 +115,8 @@ void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { __wfi(); } -static void _go_dormant(void) { +static void _go_dormant(void) +{ assert(dormant_source_valid(_dormant_source)); if (_dormant_source == DORMANT_SOURCE_XOSC) { @@ -135,7 +126,8 @@ static void _go_dormant(void) { } } -void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { +void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) +{ bool low = !high; bool level = !edge; @@ -144,10 +136,14 @@ void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { uint32_t event = 0; - if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; - if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; - if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; - if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; + if (level && low) + event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; + if (level && high) + event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; + if (edge && high) + event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; + if (edge && low) + event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; gpio_set_dormant_irq_enabled(gpio_pin, event, true); From 876993f0957c9d1b721dd8d09692b45b61d9bf10 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 8 Oct 2024 05:34:41 -0500 Subject: [PATCH 1376/1377] No idea why trunk wants to disturb these PNGs but... --- images/compass.png | Bin 845 -> 594 bytes images/face-24px.svg | 2 +- images/face.png | Bin 329 -> 225 bytes images/location_searching-24px.svg | 2 +- images/pin.png | Bin 288 -> 203 bytes images/room-24px.svg | 2 +- images/textsms-24px.svg | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) diff --git a/images/compass.png b/images/compass.png index 8639dde5241d720e47e97d63280ec8073928c2b4..c4e5b589bc42cd7b05148dbbdee01ce0fe689548 100644 GIT binary patch delta 561 zcmV-10?z%-2GRtO83+Ub008|9F$|G07k>f-NklyvaQ|Snd!k0 z`OI;De`Ye9Uv~M=M1R(0gMZLqh6X-u)u4?}gX=K75ti8{AR4f`yk!Xo>{a+clz*{~ zS-?t;4MHM~72@M!Xo7>D5=T6yMwv0j@Tjp$r>9Aaf?4_RvEr{NF-VEc*cngsTjXN9 zKczyNS{PNkjF4(sU2O#u zRdw#WqqAXKWlx_6mt!NXR2>tYyBeY9QCJ zQCGwKFHzsRiH&l9o#Y02Y~=MDT*1aw{RSm$c>Q&fFZs?;mwZSfUqV)~@l>*(k*J|Y zmW{8HeV;@#bY)bOt#9BLQ>56HuuV+8k<4vUEva!mi-~zPt`|w=@KFNKxqpVPB0nVS z2!{-FYA^e^=*aP2G6HU+!6%Y+K#?qys(i#cT3lKx<%}w6<~difdyJx~z==u=*`mxK z9i+&9KCP@vWKiMvJ}k34xi>OA2~&+|RTG33(J{P+!6XA~>9XlIu1FbDP=?y*BR z;Rj@!MI0F3G%GY?r#a*~4<4AhKtF+(4KB3b>M76a97!*<00000NkvXXu0mjfa`FP& delta 814 zcmV+}1JV4_1kDDJ85jlt00374`G)`i00eVFNmK|32nc)#WQdU=7k>a5bVXQnQ*UN; zcVTj60C#tHE@^ISb7Ns}WiD@WXPfRk8UO$S%1J~)RA_# zlVG9=+yX8ERp5{xHUHjipp{U_HZYT?`L8j?9|8**v=)F3qJMjFj~t4aCi=;L!@LW& zoCWqHbhl9+PU|sA$_m;QU?)QVzJfMjW(e8U+E1uRxQDyIqN{KL_$Y*b0JE;b5tK1Q zGMKA{`PNvoDg-^;#{4iA3HSCj7r~c8?17`fG&~YHpW6x@XK;Dok!`B9rN&?{bbvV< zp_>doiBC!f(|^zwxo^ZZOj=0E73EWRU#XF~>TwN|3OFgQ-&@?49B(<+B1st_7V#~3#4TA+tiP#lwt>$7&WC0Dr z{($5jXp8F$D%R3*QCv6U8fGcrs9sJi(|MUBDP<_{U(3CX4q z+fXn#j3*-JGZ(QHA?9JWfqAh+csaf7B3J-E2*IDg5ht6`RDkayR}+PUuXBW-$_~0) z%4y)O$k7GPXeC$^;yuhBwxXatOW1r5xTm1~k0NFxFO%%!w((xV3`wcGh3>&KU=Ex1 zB9eKSZGT{vDYgbYCi-CO3vgJ?wP1$mp6Ev#xC31FO_uzq`So0&Q1-? s({2H;f!`WQC;t \ No newline at end of file + \ No newline at end of file diff --git a/images/face.png b/images/face.png index 036e29f7349217607c53f48aed28e99484a3e648..eb6b91e13b89f3a0d27efbaf83e35f2c1f70b3c5 100644 GIT binary patch delta 188 zcmV;t07L)D0^tFW83+Ub007wHEccNy7=M#VL_t(|+J(?P4gpaVhT*4BON@<(8woX? zLS_XDt%M~QvlYFcZ7@+#}B!d5~QJk`Ieg!u6Zu6ANf zfL!gRuO;*HjTun5835X*7IGKhu7|lHM&<}gpfJPQ7!xa`&cN2*zmpA+s4PV*4=Ejh qpmLB3Qb!McqD1DRbhgoH-0ltI05a+a8KhYN0000MzCV_|S*E^l&Yo9;Xs0002SNkl|LzC>pdL!hvB*6^ z(FP3ghR+|)I{B>%HGlAmX$!bfu}tCvee7YJxCxG7C(s-nTw~VwY#*06!YQtBUMH70 zi@(7|{4jAn^8$AB@Q#!CLv-W2IEX*a+~1nu!S8y;ee \ No newline at end of file + \ No newline at end of file diff --git a/images/pin.png b/images/pin.png index 112a7ce8ed6a7537c73dc2626d2f6cab93d87fbc..3c91a726c3cf3c5605e1580a89e35f84850da2f6 100644 GIT binary patch delta 166 zcmV;X09pT_0?PrA83+Ub007wHEccNy7=L|9L_t(|+KtdL4#5BzhT&&nDRCHfEL=e( zB;q9E1TG-%!VTP@Sj56g`b(r~qv_D+8{h5I#MH^f(L}{sCVtDTDD}LA)J`fqbrr@! z7-A%Zk-|U-3oM1uS7>_(Z)YFju0?4pif#PqxaHw0Q8CZcOpSxES1YlXGMzCV_|S*E^l&Yo9;Xs0001;Nkl# BWEB7a diff --git a/images/room-24px.svg b/images/room-24px.svg index 79a4807e7..48e55ab80 100644 --- a/images/room-24px.svg +++ b/images/room-24px.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/images/textsms-24px.svg b/images/textsms-24px.svg index 4455f047e..84c4fdcc1 100644 --- a/images/textsms-24px.svg +++ b/images/textsms-24px.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 4f8f96ab2967660e03e7a9ef9beef633c60ddac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 8 Oct 2024 14:05:13 +0200 Subject: [PATCH 1377/1377] preliminary Othernet Dreamcatcher Support (#4933) * preliminary Othernet Dreamcatcher 2206 Support Need to adapt to 2301 final version * second target for latest revision * preliminary Othernet Dreamcatcher 2206 Support Need to adapt to 2301 final version * second target for latest revision * preliminary Othernet Dreamcatcher 2206 Support Need to adapt to 2301 final version * second target for latest revision * address comments --------- Co-authored-by: Ben Meadors Co-authored-by: Tom Fifield --- src/configuration.h | 5 ++ src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 3 + src/main.cpp | 20 +++++ src/mesh/LR11x0Interface.cpp | 12 +++ variants/dreamcatcher/platformio.ini | 27 +++++++ variants/dreamcatcher/rfswitch.h | 17 +++++ variants/dreamcatcher/variant.h | 109 +++++++++++++++++++++++++++ 8 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 variants/dreamcatcher/platformio.ini create mode 100644 variants/dreamcatcher/rfswitch.h create mode 100644 variants/dreamcatcher/variant.h diff --git a/src/configuration.h b/src/configuration.h index 729d6b046..10a4e0a99 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -187,6 +187,11 @@ along with this program. If not, see . // ----------------------------------------------------------------------------- #define FT6336U_ADDR 0x48 +// ----------------------------------------------------------------------------- +// BIAS-T Generator +// ----------------------------------------------------------------------------- +#define TPS65233_ADDR 0x60 + // convert 24-bit color to 16-bit (56K) #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 920af06c7..07db3fd57 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -60,7 +60,8 @@ class ScanI2C FT6336U, STK8BAXX, ICM20948, - MAX30102 + MAX30102, + TPS65233 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 3f4f88f74..af94290d2 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -405,6 +405,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); +#ifdef HAS_TPS65233 + SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233 BIAS-T found\n"); +#endif SCAN_SIMPLE_CASE(MLX90614_ADDR_DEF, MLX90614, "MLX90614 IR temp sensor found\n"); case ICM20948_ADDR: // same as BMX160_ADDR diff --git a/src/main.cpp b/src/main.cpp index 9ddc0864c..8387392fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -298,6 +298,11 @@ void setup() digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power #endif +#if defined(BIAS_T_ENABLE) + pinMode(BIAS_T_ENABLE, OUTPUT); + digitalWrite(BIAS_T_ENABLE, BIAS_T_VALUE); // turn on 5V for GPS Antenna +#endif + #if defined(VTFT_CTRL) pinMode(VTFT_CTRL, OUTPUT); digitalWrite(VTFT_CTRL, LOW); @@ -538,6 +543,21 @@ void setup() rgb_found = i2cScanner->find(ScanI2C::DeviceType::NCP5623); #endif +#ifdef HAS_TPS65233 + // TPS65233 is a power management IC for satellite modems, used in the Dreamcatcher + // We are switching it off here since we don't use an LNB. + if (i2cScanner->exists(ScanI2C::DeviceType::TPS65233)) { + Wire.beginTransmission(TPS65233_ADDR); + Wire.write(0); // Register 0 + Wire.write(128); // Turn off the LNB power, keep I2C Control enabled + Wire.endTransmission(); + Wire.beginTransmission(TPS65233_ADDR); + Wire.write(1); // Register 1 + Wire.write(0); // Turn off Tone Generator 22kHz + Wire.endTransmission(); + } +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) auto acc_info = i2cScanner->firstAccelerometer(); accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found; diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 6641634c4..a985a9006 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -72,6 +72,18 @@ template bool LR11x0Interface::init() limitPower(); +#ifdef LR11X0_RF_SWITCH_SUBGHZ + pinMode(LR11X0_RF_SWITCH_SUBGHZ, OUTPUT); + digitalWrite(LR11X0_RF_SWITCH_SUBGHZ, getFreq() < 1e9 ? HIGH : LOW); + LOG_DEBUG("Setting RF0 switch to %s\n", getFreq() < 1e9 ? "SubGHz" : "2.4GHz"); +#endif + +#ifdef LR11X0_RF_SWITCH_2_4GHZ + pinMode(LR11X0_RF_SWITCH_2_4GHZ, OUTPUT); + digitalWrite(LR11X0_RF_SWITCH_2_4GHZ, getFreq() < 1e9 ? LOW : HIGH); + LOG_DEBUG("Setting RF1 switch to %s\n", getFreq() < 1e9 ? "SubGHz" : "2.4GHz"); +#endif + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); // \todo Display actual typename of the adapter, not just `LR11x0` LOG_INFO("LR11x0 init result %d\n", res); diff --git a/variants/dreamcatcher/platformio.ini b/variants/dreamcatcher/platformio.ini new file mode 100644 index 000000000..46f9b9871 --- /dev/null +++ b/variants/dreamcatcher/platformio.ini @@ -0,0 +1,27 @@ +[env:dreamcatcher] ; 2301, latest revision +extends = esp32s3_base +board = esp32s3box +board_level = extra + +build_flags = + ${esp32s3_base.build_flags} + -D PRIVATE_HW + -D OTHERNET_DC_REV=2301 + -I variants/dreamcatcher + -DARDUINO_USB_CDC_ON_BOOT=1 + +lib_deps = ${esp32s3_base.lib_deps} + earlephilhower/ESP8266Audio@^1.9.7 + earlephilhower/ESP8266SAM@^1.0.1 + +[env:dreamcatcher-2206] +extends = esp32s3_base +board = esp32s3box +board_level = extra + +build_flags = + ${esp32s3_base.build_flags} + -D PRIVATE_HW + -D OTHERNET_DC_REV=2206 + -I variants/dreamcatcher + -DARDUINO_USB_CDC_ON_BOOT=1 \ No newline at end of file diff --git a/variants/dreamcatcher/rfswitch.h b/variants/dreamcatcher/rfswitch.h new file mode 100644 index 000000000..74edb25d1 --- /dev/null +++ b/variants/dreamcatcher/rfswitch.h @@ -0,0 +1,17 @@ +#include "RadioLib.h" + +// RF Switch Matrix SubG RFO_HP_LF / RFO_LP_LF / RFI_[NP]_LF0 +// DIO5 -> RFSW0_V1 +// DIO6 -> RFSW1_V2 +// DIO7 -> ANT_CTRL_ON + ESP_IO9/LR_GPS_ANT_DC_EN -> RFI_GPS (Bias-T GPS) + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, + RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 DIO7 + {LR11x0::MODE_STBY, {LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW}}, + {LR11x0::MODE_TX, {LOW, HIGH, LOW}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW}}, + {LR11x0::MODE_TX_HF, {LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH}}, + {LR11x0::MODE_WIFI, {LOW, LOW, LOW}}, END_OF_MODE_TABLE, +}; \ No newline at end of file diff --git a/variants/dreamcatcher/variant.h b/variants/dreamcatcher/variant.h new file mode 100644 index 000000000..eb95a95dd --- /dev/null +++ b/variants/dreamcatcher/variant.h @@ -0,0 +1,109 @@ +#undef I2C_SDA +#undef I2C_SCL +#define I2C_SDA 16 // I2C pins for this board +#define I2C_SCL 17 + +#define I2C_SDA1 45 +#define I2C_SCL1 46 + +#define LED_PIN 6 +#define LED_STATE_ON 1 +#define BUTTON_PIN 0 + +#define HAS_TPS65233 + +// V1 of SubG Switch SMA 0 or F Selector 1 +// #define RF_SW_SUBG1 8 +// V2 of SubG Switch SMA 1 or F Selector 0 +// #define RF_SW_SUBG2 5 + +#define RESET_OLED 8 // Emulate RF_SW_SUBG1, Use F Connector +#define VTFT_CTRL 5 // Emulate RF_SW_SUBG2, for SMA swap the pin values + +#if OTHERNET_DC_REV == 2206 +#define USE_LR1120 + +#define SPI_MISO 37 +#define SPI_MOSI 39 +#define SPI_SCK 38 +#define SDCARD_CS 40 + +#define PIN_BUZZER 48 + +// These can either be used for GPS or a serial link. Define through Protobufs +// #define GPS_RX_PIN 10 +// #define GPS_TX_PIN 21 + +#define PIN_POWER_EN 7 // RF section power supply enable +#define PERIPHERAL_WARMUP_MS 1000 // wait for TPS chip to initialize +#define TPS_EXTM 45 // connected, but not used +#define BIAS_T_ENABLE 9 // needs to be low +#define BIAS_T_VALUE 0 +#else // 2301 +#define USE_LR1121 +#define SPI_MISO 10 +#define SPI_MOSI 39 +#define SPI_SCK 38 + +#define SDCARD_CS 40 + +// This is only informational, we always use SD cards in 1 bit mode +#define SPI_DATA1 15 +#define SPI_DATA2 18 + +// These can either be used for GPS or a serial link. Define through Protobufs +// #define GPS_RX_PIN 36 +// #define GPS_TX_PIN 37 + +// dac / amp instead of buzzer +#define HAS_I2S +#define DAC_I2S_BCK 21 +#define DAC_I2S_WS 9 +#define DAC_I2S_DOUT 48 + +#define BIAS_T_ENABLE 7 // needs to be low +#define BIAS_T_VALUE 0 +#define BIAS_T_SUBGHZ 2 // also needs to be low, we hijack SENSOR_POWER_CTRL_PIN to emulate this +#define SENSOR_POWER_CTRL_PIN BIAS_T_SUBGHZ +#define SENSOR_POWER_ON 0 +#endif + +#define HAS_SDCARD // Have SPI interface SD card slot +#define SDCARD_USE_SPI1 + +#define LORA_RESET 3 +#define LORA_SCK 12 +#define LORA_MISO 13 +#define LORA_MOSI 11 +#define LORA_CS 14 +#define LORA_DIO9 4 +#define LORA_DIO2 47 + +#define LR1120_IRQ_PIN LORA_DIO9 +#define LR1120_NRESET_PIN LORA_RESET +#define LR1120_BUSY_PIN LORA_DIO2 +#define LR1120_SPI_NSS_PIN LORA_CS +#define LR1120_SPI_SCK_PIN LORA_SCK +#define LR1120_SPI_MOSI_PIN LORA_MOSI +#define LR1120_SPI_MISO_PIN LORA_MISO + +#define LR1121_IRQ_PIN LORA_DIO9 +#define LR1121_NRESET_PIN LORA_RESET +#define LR1121_BUSY_PIN LORA_DIO2 +#define LR1121_SPI_NSS_PIN LORA_CS +#define LR1121_SPI_SCK_PIN LORA_SCK +#define LR1121_SPI_MOSI_PIN LORA_MOSI +#define LR1121_SPI_MISO_PIN LORA_MISO + +#define LR11X0_DIO3_TCXO_VOLTAGE 1.8 +#define LR11X0_DIO_AS_RF_SWITCH + +// This board needs external switching between sub-GHz and 2.4G circuits + +// V1 of RF1 selector SubG 1 or 2.4GHz 0 +// #define RF_SW_SMA1 42 +// V2 of RF1 Selector SubG 0 or 2.4GHz 1 +// #define RF_SW_SMA2 41 + +#define LR11X0_RF_SWITCH_SUBGHZ 42 +#define LR11X0_RF_SWITCH_2_4GHZ 41